* [RFC][PATCH 0/5] OMAP Synchronous Serial Interface (SSI) driver @ 2008-10-03 11:50 Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 1/5] OMAP SSI hardware interface definitions Carlos Chinea 0 siblings, 1 reply; 14+ messages in thread From: Carlos Chinea @ 2008-10-03 11:50 UTC (permalink / raw) To: linux-kernel; +Cc: linux-omap Hi guys ! I'm working on adding support for Nokia HSPA modems to OMAP. Please consider integrating the following patch set into the linux-omap tree. The patch set implements a generic device driver for the OMAP Synchronous Serial Interface. The Synchronous Serial Interface (SSI) is a high speed communication interface that is used for connecting OMAP to a cellular modem engine. The patch set is based on linux-omap 2.6.27-rc7. Any comments will be appreciated. Br, Carlos Documentation/arm/OMAP/ssi/board-ssi.c.example | 216 ++++++++ Documentation/arm/OMAP/ssi/ssi | 232 +++++++++ arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h | 145 ++++++ .../plat-omap/include/mach/ssi/ssi_reg_common.h | 73 +++ arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h | 56 +++ arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h | 65 +++ arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h | 107 ++++ drivers/misc/Kconfig | 2 + drivers/misc/Makefile | 1 + drivers/misc/ssi/Kconfig | 11 + drivers/misc/ssi/Makefile | 7 + drivers/misc/ssi/ssi_driver.c | 513 ++++++++++++++++++++ drivers/misc/ssi/ssi_driver.h | 211 ++++++++ drivers/misc/ssi/ssi_driver_bus.c | 192 ++++++++ drivers/misc/ssi/ssi_driver_dma.c | 406 ++++++++++++++++ drivers/misc/ssi/ssi_driver_if.c | 335 +++++++++++++ drivers/misc/ssi/ssi_driver_int.c | 232 +++++++++ include/linux/ssi_driver_if.h | 137 ++++++ 18 files changed, 2941 insertions(+), 0 deletions(-) ^ permalink raw reply [flat|nested] 14+ messages in thread
* [RFC][PATCH 1/5] OMAP SSI hardware interface definitions 2008-10-03 11:50 [RFC][PATCH 0/5] OMAP Synchronous Serial Interface (SSI) driver Carlos Chinea @ 2008-10-03 11:52 ` Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 2/5] OMAP SSI driver interface Carlos Chinea 2008-10-06 23:16 ` [RFC][PATCH 1/5] OMAP SSI hardware interface definitions Felipe Balbi 0 siblings, 2 replies; 14+ messages in thread From: Carlos Chinea @ 2008-10-03 11:52 UTC (permalink / raw) To: linux-kernel; +Cc: linux-omap Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> --- arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h | 145 ++++++++++++++++++++ .../plat-omap/include/mach/ssi/ssi_reg_common.h | 73 ++++++++++ arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h | 56 ++++++++ arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h | 65 +++++++++ arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h | 107 ++++++++++++++ 5 files changed, 446 insertions(+), 0 deletions(-) create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_reg_common.h create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h b/arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h new file mode 100644 index 0000000..5ed91cc --- /dev/null +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h @@ -0,0 +1,145 @@ +/* + * ssi_gdd_reg.h + * + * Hardware defintions for SSI Controller GDD registers. + * + * HARDWARE: OMAP 2420, OMAP 3430 + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef __SSI_GDD_REG_H__ +#define __SSI_GDD_REG_H__ + +#include "ssi_reg_common.h" + +#define SSI_GDD_HW_ID_REG SSI_GDD_REG32(0x0000) +#define SSI_GDD_PPORT_ID_REG SSI_GDD_REG32(0x0010) +#define SSI_GDD_MPORT_ID_REG SSI_GDD_REG32(0x0014) + +#define SSI_GDD_PPORT_SR_REG SSI_GDD_REG32(0x0020) +# define SSI_PPORT_ACTIVE_LCH_NUMBER_MASK 0xFF + +#define SSI_GDD_MPORT_SR_REG SSI_GDD_REG32(0x0024) +# define SSI_MPORT_ACTIVE_LCH_NUMBER_MASK 0xFF + +#define SSI_GDD_TEST_REG SSI_GDD_REG32(0x0040) +# define SSI_TEST 0x1 + +#define SSI_GDD_GCR_REG SSI_GDD_REG32(0x0100) +# define SSI_CLK_AUTOGATING_ON (1<<3) +# define SSI_FREE (1<<2) +# define SSI_SWITCH_OFF 0x1 + +#define SSI_GDD_GRST_REG SSI_GDD_REG32(0x0200) +# define SSI_SWRESET 0x1 + +#define SSI_GDD_CSDP_BASE 0x0800 +#define SSI_GDD_CSDP_OFFSET 0x40 +#define SSI_GDD_CSDP_REG(channel) SSI_GDD_REG16(SSI_GDD_CSDP_BASE +\ + (channel*SSI_GDD_CSDP_OFFSET)) +# define SSI_DST_BURST_EN_MASK 0xC000 +# define SSI_DST_SINGLE_ACCESS0 0x0 +# define SSI_DST_SINGLE_ACCESS (0x1<<14) +# define SSI_DST_BURST_4X32_BIT (0x2<<14) +# define SSI_DST_BURST_8x32_BIT (0x3<<14) /*NOTE: NOT SUPPORTED */ + +# define SSI_DST_MASK 0x1E00 +# define SSI_DST_MEMORY_PORT (0x8<<9) +# define SSI_DST_PERIPHERAL_PORT (0x9<<9) + +# define SSI_SRC_BURST_EN_MASK 0x0180 +# define SSI_SRC_SINGLE_ACCESS0 0x0 +# define SSI_SRC_SINGLE_ACCESS (0x1<<7) +# define SSI_SRC_BURST_4x32_BIT (0x2<<7) +# define SSI_SRC_BURST_8x32_BIT (0x3<<7) /*NOTE: NOT SUPPORTED */ + +# define SSI_SRC_MASK 0x003C +# define SSI_SRC_MEMORY_PORT (0x8<<2) +# define SSI_SRC_PERIPHERAL_PORT (0x9<<2) + +# define SSI_DATA_TYPE_MASK 0x0003 +# define SSI_DATA_TYPE_S32 0x2 + +#define SSI_GDD_CCR_BASE 0x0802 +#define SSI_GDD_CCR_OFFSET 0x40 +#define SSI_GDD_CCR_REG(channel) SSI_GDD_REG16(SSI_GDD_CCR_BASE +\ + (channel*SSI_GDD_CCR_OFFSET)) +# define SSI_DST_AMODE_MASK (0x3<<14) +# define SSI_DST_AMODE_CONST 0x0 +# define SSI_DST_AMODE_POSTINC (0x1<<12) + +# define SSI_SRC_AMODE_MASK (0x3<<12) +# define SSI_SRC_AMODE_CONST 0x0 +# define SSI_SRC_AMODE_POSTINC (0x1<<12) + +# define SSI_CCR_ENABLE (0x1<<7) + +# define SSI_CCR_SYNC_MASK 0x001F + +#define SSI_GDD_CICR_BASE 0x0804 +#define SSI_GDD_CICR_OFFSET 0x40 +#define SSI_GDD_CICR_REG(channel) SSI_GDD_REG16(SSI_GDD_CICR_BASE +\ + (channel*SSI_GDD_CICR_OFFSET)) +# define SSI_BLOCK_IE (0x1<<5) +# define SSI_HALF_IE (0x1<<2) +# define SSI_TOUT_IE 0x1 + +#define SSI_GDD_CSR_BASE 0x0806 +#define SSI_GDD_CSR_OFFSET 0x40 +#define SSI_GDD_CSR_REG(channel) SSI_GDD_REG16(SSI_GDD_CSR_BASE +\ + (channel*SSI_GDD_CSR_OFFSET)) +# define SSI_CSR_SYNC (0x1<<6) +# define SSI_CSR_BLOCK (0x1<<5) +# define SSI_CSR_HALF (0x1<<2) +# define SSI_CSR_TOUR 0x1 + +#define SSI_GDD_CSSA_BASE 0x0808 +#define SSI_GDD_CSSA_OFFSET 0x40 +#define SSI_GDD_CSSA_REG(channel) SSI_GDD_REG32(SSI_GDD_CSSA_BASE +\ + (channel*SSI_GDD_CSSA_OFFSET)) + +#define SSI_GDD_CDSA_BASE 0x080C +#define SSI_GDD_CDSA_OFFSET 0x40 +#define SSI_GDD_CDSA_REG(channel) SSI_GDD_REG32(SSI_GDD_CDSA_BASE +\ + (channel*SSI_GDD_CDSA_OFFSET)) + +#define SSI_GDD_CEN_BASE 0x0810 +#define SSI_GDD_CEN_OFFSET 0x40 +#define SSI_GDD_CEN_REG(channel) SSI_GDD_REG16(SSI_GDD_CEN_BASE +\ + (channel*SSI_GDD_CEN_OFFSET)) + +#define SSI_GDD_CSAC_BASE 0x0818 +#define SSI_GDD_CSAC_OFFSET 0x40 +#define SSI_GDD_CSAC_REG(channel) SSI_GDD_REG16(SSI_GDD_CSAC_BASE +\ + (channel*SSI_GDD_CSAC_OFFSET)) + +#define SSI_GDD_CDAC_BASE 0x081A +#define SSI_GDD_CDAC_OFFSET 0x40 +#define SSI_GDD_CDAC_REG(channel) SSI_GDD_REG16(SSI_GDD_CDAC_BASE +\ + (channel*SSI_GDD_CDAC_OFFSET)) + +#define SSI_GDD_CLNK_CTRL_BASE 0x0828 +#define SSI_GDD_CLNK_CTRL_OFFSET 0x40 +#define SSI_GDD_CLNK_CTRL_REG(channel) SSI_GDD_REG16(SSI_GDD_CLNK_CTRL_BASE +\ + (channel*SSI_GDD_CLNK_CTRL_OFFSET)) +# define SSI_ENABLE_LNK (0x1<<15) +# define SSI_STOP_LNK (0x1<<14) +# define NEXT_CH_ID_MASK 0xF + +#endif diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_reg_common.h b/arch/arm/plat-omap/include/mach/ssi/ssi_reg_common.h new file mode 100644 index 0000000..e66fb43 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_reg_common.h @@ -0,0 +1,73 @@ +/* + * ssi_reg_common.h + * + * Common hardware definitions for SSI. + * + * HARDWARE: OMAP 2420, 3430 + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __SSI_REG_COMMON_H__ +#define __SSI_REG_COMMON_H__ + +#define SSI_COMMON_BASE_ADDR 0x48050000 + +/* SSI system registers */ +#define SSI_SYS_OFFSET 0x8000 +#define SSI_SYS_REG32(offset) (SSI_SYS_OFFSET + (offset)) +/* SSI GDD registers */ +#define SSI_GDD_OFFSET 0x9000 +#define SSI_GDD_REG32(offset) (SSI_GDD_OFFSET + (offset)) +#define SSI_GDD_REG16(offset) (SSI_GDD_OFFSET + (offset)) + +/* SSI SST registers */ +/* General offset of SST port 1. First SST port register.*/ +#define SSI_SST1_OFFSET 0xA000 +/* General offset of SST port 2.*/ +#define SSI_SST2_OFFSET 0xB000 +/* Offset among the SST ports.*/ +#define SSI_SST_PORT_OFFSET 0x1000 +#define SSI_SST_OFFSET(port) (SSI_SST1_OFFSET +\ + ((port-1)*(SSI_SST_PORT_OFFSET))) +#define SSI_SST_REG(port, offset) (SSI_SST_OFFSET(port) + (offset)) + +/* SSI SSR registers */ +/* General offset of SSR port 1. First SSR port register.*/ +#define SSI_SSR1_OFFSET 0xA800 +/* General offset of SSR port 2.*/ +#define SSI_SSR2_OFFSET 0xB800 +/* Offset among the SSR ports.*/ +#define SSI_SSR_PORT_OFFSET 0x1000 +#define SSI_SSR_OFFSET(port) (SSI_SSR1_OFFSET +\ + ((port-1)*(SSI_SSR_PORT_OFFSET))) +#define SSI_SSR_REG(port, offset) (SSI_SSR_OFFSET(port) + (offset)) + +#define SSI_IOMEM_BASE_ADDR SSI_COMMON_BASE_ADDR +#define SSI_IOMEM_SIZE 0x3C00 + +/* + * FIXME: Following definitions to be removed. + * They are used for checking that the SSI clocks are stable before accessing + * the SSI registers. + */ +#define OMAP_COMMON_BASE 0x48000000 +#define CM_IDLEST1_CORE_REG 0x4A20 +#define ST_SSI 1 +#endif diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h b/arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h new file mode 100644 index 0000000..b272047 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h @@ -0,0 +1,56 @@ +/* + * ssi_sst_reg.h + * + * Hardware definitions for SSI controller SSR registers. + * + * HARDWARE: OMAP 2420, 3430 + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __SSI_SSR_REG_H__ +#define __SSI_SSR_REG_H__ + +#include "ssi_reg_common.h" + +#define SSI_SSR_ID_REG(port) SSI_SSR_REG(port, 0x0000) +#define SSI_SSR_MODE_REG(port) SSI_SSR_REG(port, 0x0004) +#define SSI_SSR_FRAMESIZE_REG(port) SSI_SSR_REG(port, 0x0008) +#define SSI_SSR_RXSTATE_REG(port) SSI_SSR_REG(port, 0x000C) +#define SSI_SSR_BUFSTATE_REG(port) SSI_SSR_REG(port, 0x0010) +# define NOTEMPTY(channel) (1<<channel) +#define SSI_SSR_BREAK_REG(port) SSI_SSR_REG(port, 0x001C) +#define SSI_SSR_ERROR_REG(port) SSI_SSR_REG(port, 0x0020) +#define SSI_SSR_ERRORACK_REG(port) SSI_SSR_REG(port, 0x0024) +#define SSI_SSR_OVERRUN_REG(port) SSI_SSR_REG(port, 0x002C) +#define SSI_SSR_OVERRUNACK_REG(port) SSI_SSR_REG(port, 0x0030) +#define SSI_SSR_TIMEOUT_REG(port) SSI_SSR_REG(port, 0x0030) +# define SSI_TIMEOUT_DEFAULT 0 +#define SSI_SSR_CHANNELS_REG(port) SSI_SSR_REG(port, 0x0028) + +#define SSI_SSR_BUFFER_OFFSET_BASE 0x0080 +#define SSI_SSR_BUFFER_CH_REG(port, channel) SSI_SSR_REG(port, \ + (SSI_SSR_BUFFER_OFFSET_BASE +\ + (channel * 0x04))) + +#define SSI_SSR_SWAPBUFFER_OFFSET_BASE 0x00C0 +#define SSI_SSR_SWAPBUFFER_CH_REG(port, channel) SSI_SSR_REG(port, \ + (SSI_SSR_SWAPBUFFER_OFFSET_BASE\ + + (channel * 0x04))) +#endif diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h b/arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h new file mode 100644 index 0000000..ed15908 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h @@ -0,0 +1,65 @@ +/* + * ssi_sst_reg.h + * + * Hardware definitions for SSI controller SST registers. + * + * HARDWARE: OMAP 2420, 3430 + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __SSI_SST_REG_H__ +#define __SSI_SST_REG_H__ + +#include "ssi_reg_common.h" + +#define SSI_SST_ID_REG(port) SSI_SST_REG(port, 0x0000) +#define SSI_SST_MODE_REG(port) SSI_SST_REG(port, 0x0004) +# define SSI_MODE_VAL_MASK 0x3 +# define SSI_MODE_SLEEP 0x0 +# define SSI_MODE_STREAM 0x1 +# define SSI_MODE_FRAME 0x2 +# define SSI_MODE_MULTIPOINTS 0x3 +#define SSI_SST_FRAMESIZE_REG(port) SSI_SST_REG(port, 0x0008) +# define SSI_FRAMESIZE_DEFAULT 31 +#define SSI_SST_TXSTATE_REG(port) SSI_SST_REG(port, 0x000C) +# define TXSTATE_IDLE 0x0 +#define SSI_SST_BUFSTATE_REG(port) SSI_SST_REG(port, 0x0010) +# define NOTFULL(channel) (1<<channel) +#define SSI_SST_DIVISOR_REG(port) SSI_SST_REG(port, 0x0018) +# define SSI_DIVISOR_DEFAULT 1 + +#define SSI_SST_BREAK_REG(port) SSI_SST_REG(port, 0x0020) +#define SSI_SST_CHANNELS_REG(port) SSI_SST_REG(port, 0x0024) +# define SSI_CHANNELS_DEFAULT 4 + +#define SSI_SST_ARBMODE_REG(port) SSI_SST_REG(port, 0x0028) +# define SSI_ARBMODE_ROUNDROBIN 0x0 +# define SSI_ARBMODE_PRIORITY 0x1 + +#define SSI_SST_BUFFER_OFFSET_BASE 0x0080 +#define SSI_SST_BUFFER_CH_REG(port, channel) SSI_SST_REG(port, \ + (SSI_SST_BUFFER_OFFSET_BASE +\ + (channel * 0x4))) + +#define SSI_SST_SWAPBUF_OFFSET_BASE 0x00C0 +#define SSI_SST_SWAPBUF_CH_REG(port, channel) SSI_SST_REG(port, \ + (SSI_SST_SWAPBUF_OFFSET_BASE +\ + (channel * 0x4))) +#endif diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h b/arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h new file mode 100644 index 0000000..2f1e1f5 --- /dev/null +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h @@ -0,0 +1,107 @@ +/* + * ssi_sys_reg.h + * + * Hardware defintions for SSI Controller system registers. + * + * HARDWARE: OMAP 2420, 3430 + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __SSI_SYS_REG_H__ +#define __SSI_SYS_REG_H__ + +#include "ssi_reg_common.h" + +#define SSI_SYS_REVISION_REG SSI_SYS_REG32(0x0000) +# define SSI_REV_MASK 0x000000FF +# define SSI_REV_MAJOR 0xF0 +# define SSI_REV_MINOR 0x0F + +#define SSI_SYS_SYSCONFIG_REG SSI_SYS_REG32(0x0010) +# define SSI_AUTOIDLE 1 +# define SSI_SOFTRESET (1<<1) +# define SSI_SIDLEMODE_FORCE 0 +# define SSI_SIDLEMODE_NO (1<<3) +# define SSI_SIDLEMODE_SMART (1<<4) +# define SSI_SIDLEMODE_MASK 0x00000018 +# define SSI_MIDLEMODE_FORCE 0 +# define SSI_MIDLEMODE_NO (1<<12) +# define SSI_MIDLEMODE_SMART (1<<13) +# define SSI_MIDLEMODE_MASK 0x00003000 + +#define SSI_SYS_SYSSTATUS_REG SSI_SYS_REG32(0x0014) +# define SSI_RESETDONE 1 + +#define SSI_SYS_MPU_STATUS_BASE 0x0808 +#define SSI_SYS_MPU_STATUS_PORT_OFFSET 0x10 +#define SSI_SYS_MPU_STATUS_IRQ_OFFSET 0x2 +#define SSI_SYS_MPU_STATUS_REG(port, irq) \ + SSI_SYS_REG32(SSI_SYS_MPU_STATUS_BASE +\ + (((port-1)*SSI_SYS_MPU_STATUS_PORT_OFFSET) +\ + (irq*SSI_SYS_MPU_STATUS_IRQ_OFFSET))) + +#define SSI_SYS_MPU_ENABLE_BASE 0x080C +#define SSI_SYS_MPU_ENABLE_PORT_OFFSET 0x10 +#define SSI_SYS_MPU_ENABLE_IRQ_OFFSET 0x8 +#define SSI_SYS_MPU_ENABLE_REG(port, irq) \ + SSI_SYS_REG32(SSI_SYS_MPU_ENABLE_BASE +\ + (((port-1)*SSI_SYS_MPU_ENABLE_PORT_OFFSET) +\ + (irq*SSI_SYS_MPU_ENABLE_IRQ_OFFSET))) + +#define SSI_SYS_DSP_STATUS_BASE 0x0830 +#define SSI_SYS_DSP_STATUS_PORT_OFFSET 0x10 +#define SSI_SYS_DSP_STATUS_IRQ_OFFSET 0x8 +#define SSI_SYS_DSP_STATUS_REG(port, irq) \ + SSI_SYS_REG32(SSI_SYS_DSP_STATUS_BASE +\ + (((port-1)*SSI_SYS_DSP_STATUS_PORT_OFFSET) +\ + (irq*SSI_SYS_DSP_STATUS_IRQ_OFFSET))) + +#define SSI_SYS_DSP_ENABLE_BASE 0x0834 +#define SSI_SYS_DSP_ENABLE_PORT_OFFSET 0x10 +#define SSI_SYS_DSP_ENABLE_IRQ_OFFSET 0x8 +#define SSI_SYS_DSP_ENABLE_REG(port, irq) \ + SSI_SYS_REG32(SSI_SYS_DSP_ENABLE_BASE +\ + (((port-1)*SSI_SYS_DSP_ENABLE_PORT_OFFSET) +\ + (irq*SSI_SYS_DSP_ENABLE_IRQ_OFFSET))) +# define SSI_SST_DATAACCEPT(channel) (1<<channel) +# define SSI_SSR_DATAAVAILABLE(channel) (1<<(channel + 8)) +# define SSI_SSR_DATAOVERRUN(channel) (1<<(channel + 16)) +# define SSI_ERROROCCURED (1<<24) +# define SSI_BREAKDETECTED (1<<25) + +#define SSI_SYS_GDD_MPU_IRQ_STATUS_REG SSI_SYS_REG32(0x0800) +#define SSI_SYS_GDD_MPU_IRQ_ENABLE_REG SSI_SYS_REG32(0x0804) +#define SSI_SYS_GDD_DSP_IRQ_STATUS_REG SSI_SYS_REG32(0x0828) +#define SSI_SYS_GDD_DSP_IRQ_ENABLE_REG SSI_SYS_REG32(0x082C) +# define SSI_GDD_LCH(channel) (1<<channel) + +#define SSI_SYS_WAKE_OFFSET 0x10 +#define SSI_SYS_WAKE_BASE 0x0C00 +#define SSI_SYS_WAKE_REG(port) SSI_SYS_REG32(SSI_SYS_WAKE_BASE +\ + ((port-1)*SSI_SYS_WAKE_OFFSET)) +#define SSI_SYS_CLEAR_WAKE_BASE 0x0C04 +#define SSI_SYS_CLEAR_WAKE_REG(port) SSI_SYS_REG32(SSI_SYS_CLEAR_WAKE_BASE +\ + ((port-1)*SSI_SYS_WAKE_OFFSET)) +#define SSI_SYS_SET_WAKE_BASE 0x0C08 +#define SSI_SYS_SET_WAKE_REG(port) SSI_SYS_REG32(SSI_SYS_SET_WAKE_BASE +\ + ((port-1)*SSI_SYS_WAKE_OFFSET)) +# define SSI_WAKE(channel) (1<<channel) +# define SSI_WAKE_MASK 0xFF +#endif -- 1.5.3.6 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC][PATCH 2/5] OMAP SSI driver interface 2008-10-03 11:52 ` [RFC][PATCH 1/5] OMAP SSI hardware interface definitions Carlos Chinea @ 2008-10-03 11:52 ` Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 3/5] OMAP SSI driver code Carlos Chinea 2008-10-06 23:29 ` [RFC][PATCH 2/5] OMAP SSI driver interface Felipe Balbi 2008-10-06 23:16 ` [RFC][PATCH 1/5] OMAP SSI hardware interface definitions Felipe Balbi 1 sibling, 2 replies; 14+ messages in thread From: Carlos Chinea @ 2008-10-03 11:52 UTC (permalink / raw) To: linux-kernel; +Cc: linux-omap Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> --- include/linux/ssi_driver_if.h | 137 +++++++++++++++++++++++++++++++++++++++++ 1 files changed, 137 insertions(+), 0 deletions(-) create mode 100644 include/linux/ssi_driver_if.h diff --git a/include/linux/ssi_driver_if.h b/include/linux/ssi_driver_if.h new file mode 100644 index 0000000..3379dd0 --- /dev/null +++ b/include/linux/ssi_driver_if.h @@ -0,0 +1,137 @@ +/* + * ssi_driver_if.h + * + * Header for the SSI driver low level interface. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef __SSI_DRIVER_IF_H__ +#define __SSI_DRIVER_IF_H__ + +#include <linux/device.h> + +#define SSI_IOMEM_NAME "SSI_IO_MEM" +#define SSI_P1_MPU_IRQ0_NAME "SSI_P1_MPU_IRQ0" +#define SSI_P2_MPU_IRQ0_NAME "SSI_P2_MPU_IRQ0" +#define SSI_P1_MPU_IRQ1_NAME "SSI_P1_MPU_IRQ1" +#define SSI_P2_MPU_IRQ1_NAME "SSI_P2_MPU_IRQ1" +#define SSI_GDD_MPU_IRQ_NAME "GDD_MPU_IRQ" + +/* IRQ values */ +#define SSI_P1_MPU_IRQ0 67 +#define SSI_P2_MPU_IRQ0 68 +#define SSI_P1_MPU_IRQ1 69 +#define SSI_P2_MPU_IRQ1 70 +#define SSI_GDD_MPU_IRQ 71 + +/* The number of ports handled by the driver. (MAX:2) */ +#define SSI_MAX_PORTS 1 + +/* + * Masks used to enable or disable the reception of certain hardware events + * for the ssi_device_drivers + */ +#define SSI_EVENT_CLEAR 0x00 +#define SSI_EVENT_MASK 0xFF +#define SSI_EVENT_BREAK_DETECTED_MASK 0x01 +#define SSI_EVENT_ERROR_MASK 0x02 + +#define ANY_SSI_CONTROLLER -1 +#define ANY_CHANNEL -1 +#define CHANNEL(channel) (1<<channel) + +enum { + SSI_EVENT_BREAK_DETECTED = 0, + SSI_EVENT_ERROR, +}; + +enum { + SSI_IOCTL_WAKE_UP, + SSI_IOCTL_WAKE_DOWN, + SSI_IOCTL_SEND_BREAK, + SSI_IOCTL_WAKE, +}; + +/* Forward references */ +struct ssi_device; +struct ssi_dev; +struct ssi_port; +struct ssi_channel; + +struct ssi_port_pd { + u32 tx_mode; + u32 tx_frame_size; + u32 divisor; + u32 tx_ch; + u32 arb_mode; + u32 rx_mode; + u32 rx_frame_size; + u32 rx_ch; + u32 timeout; + u8 n_irq; +}; + +struct ssi_platform_data { + unsigned char *clk_name; + struct ssi_dev *ssi_ctrl; + struct ssi_port_pd *ports; + u8 num_ports; +}; + +struct ssi_device { + int n_ctrl; + unsigned int n_p; + unsigned int n_ch; + char modalias[BUS_ID_SIZE]; + struct ssi_channel *ch; + struct device device; +}; + +#define to_ssi_device(dev) container_of(dev, struct ssi_device, device) + +struct ssi_device_driver { + unsigned long ctrl_mask; + unsigned long ch_mask[SSI_MAX_PORTS]; + unsigned long event_mask; + void (*port_event) (int c_id, unsigned int port, + unsigned int event, void *arg); + int (*probe)(struct ssi_device *dev); + int (*remove)(struct ssi_device *dev); + int (*suspend)(struct ssi_device *dev, + pm_message_t mesg); + int (*resume)(struct ssi_device *dev); + struct device_driver driver; +}; + +#define to_ssi_device_driver(drv) container_of(drv, \ + struct ssi_device_driver, \ + driver) + +int register_ssi_driver(struct ssi_device_driver *driver); +void unregister_ssi_driver(struct ssi_device_driver *driver); +int ssi_open(struct ssi_device *dev); +int ssi_write(struct ssi_device *dev, u32 *data, unsigned int count); +void ssi_write_cancel(struct ssi_device *dev); +int ssi_read(struct ssi_device *dev, u32 *data, unsigned int w_count); +void ssi_read_cancel(struct ssi_device *dev); +int ssi_ioctl(struct ssi_device *dev, unsigned int command, void *arg); +void ssi_close(struct ssi_device *dev); +void ssi_dev_set_cb(struct ssi_device *dev, void (*r_cb)(struct ssi_device *dev) + , void (*w_cb)(struct ssi_device *dev)); +#endif -- 1.5.3.6 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC][PATCH 3/5] OMAP SSI driver code 2008-10-03 11:52 ` [RFC][PATCH 2/5] OMAP SSI driver interface Carlos Chinea @ 2008-10-03 11:52 ` Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 4/5] OMAP SSI integration into misc drivers Carlos Chinea 2008-10-07 0:03 ` [RFC][PATCH 3/5] OMAP SSI driver code Felipe Balbi 2008-10-06 23:29 ` [RFC][PATCH 2/5] OMAP SSI driver interface Felipe Balbi 1 sibling, 2 replies; 14+ messages in thread From: Carlos Chinea @ 2008-10-03 11:52 UTC (permalink / raw) To: linux-kernel; +Cc: linux-omap Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> --- drivers/misc/ssi/Kconfig | 11 + drivers/misc/ssi/Makefile | 7 + drivers/misc/ssi/ssi_driver.c | 513 +++++++++++++++++++++++++++++++++++++ drivers/misc/ssi/ssi_driver.h | 211 +++++++++++++++ drivers/misc/ssi/ssi_driver_bus.c | 192 ++++++++++++++ drivers/misc/ssi/ssi_driver_dma.c | 406 +++++++++++++++++++++++++++++ drivers/misc/ssi/ssi_driver_if.c | 335 ++++++++++++++++++++++++ drivers/misc/ssi/ssi_driver_int.c | 232 +++++++++++++++++ 8 files changed, 1907 insertions(+), 0 deletions(-) create mode 100644 drivers/misc/ssi/Kconfig create mode 100644 drivers/misc/ssi/Makefile create mode 100644 drivers/misc/ssi/ssi_driver.c create mode 100644 drivers/misc/ssi/ssi_driver.h create mode 100644 drivers/misc/ssi/ssi_driver_bus.c create mode 100644 drivers/misc/ssi/ssi_driver_dma.c create mode 100644 drivers/misc/ssi/ssi_driver_if.c create mode 100644 drivers/misc/ssi/ssi_driver_int.c diff --git a/drivers/misc/ssi/Kconfig b/drivers/misc/ssi/Kconfig new file mode 100644 index 0000000..5e0842c --- /dev/null +++ b/drivers/misc/ssi/Kconfig @@ -0,0 +1,11 @@ +# +# OMAP SSI HW kernel configuration +# +config OMAP_SSI + tristate "OMAP SSI hardware driver" + depends on ARCH_OMAP + default n + ---help--- + If you say Y here, you will enable the OMAP SSI hardware driver. + + If unsure, say N. diff --git a/drivers/misc/ssi/Makefile b/drivers/misc/ssi/Makefile new file mode 100644 index 0000000..2b74e02 --- /dev/null +++ b/drivers/misc/ssi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for SSI drivers +# +omap_ssi-objs := ssi_driver.o ssi_driver_dma.o ssi_driver_int.o \ + ssi_driver_if.o ssi_driver_bus.o + +obj-$(CONFIG_OMAP_SSI) += omap_ssi.o diff --git a/drivers/misc/ssi/ssi_driver.c b/drivers/misc/ssi/ssi_driver.c new file mode 100644 index 0000000..292e868 --- /dev/null +++ b/drivers/misc/ssi/ssi_driver.c @@ -0,0 +1,513 @@ +/* + * ssi_driver.c + * + * Implements SSI module interface, initialization, and PM related functions. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/err.h> +#include "ssi_driver.h" + +static void ssi_dev_release(struct device *dev) +{ +} + +static void __init ssi_undo_reg_dev(struct platform_device *pd, + int p, int ch) +{ + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + struct ssi_port *ssi_p; + int port; + int channel; + + for (port = p; port >= 0; port--) { + ssi_p = &p_data->ssi_ctrl->ssi_port[port]; + for (channel = ch; channel >= 0 ; channel--) + device_unregister(&ssi_p->ssi_channel[channel].dev + ->device); + } +} + +static int __init reg_ssi_dev_ch(struct platform_device *pd, + unsigned int p, unsigned int ch) +{ + struct ssi_device *dev; + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->n_ctrl = pd->id; + dev->n_p = p; + dev->n_ch = ch; + dev->ch = &p_data->ssi_ctrl->ssi_port[p].ssi_channel[ch]; + p_data->ssi_ctrl->ssi_port[p].ssi_channel[ch].dev = dev; + dev->device.bus = &ssi_bus_type; + dev->device.parent = &pd->dev; + dev->device.release = ssi_dev_release; + if (dev->n_ctrl < 0) + snprintf(dev->device.bus_id, sizeof(dev->device.bus_id), + "omap_ssi-p%u.c%u", p, ch); + else + snprintf(dev->device.bus_id, sizeof(dev->device.bus_id), + "omap_ssi%d-p%u.c%u", dev->n_ctrl, p, ch); + + return device_register(&dev->device); +} + +static int __init register_ssi_devices(struct platform_device *pd) +{ + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + unsigned int n_ch = 0; + int port; + int ch = 0; + int err = 0; + + for (port = 0; ((port < p_data->num_ports) && (err >= 0)); port++) { + n_ch = max(p_data->ports[port].tx_ch, + p_data->ports[port].rx_ch); + for (ch = 0; ((ch < n_ch) && (err >= 0)); ch++) + err = reg_ssi_dev_ch(pd, port, ch); + } + + if (err < 0) { + port--; + ch--; + dev_err(&pd->dev, "Error registering ssi device channel " + "p%d-c%d : %d\n", port, ch, err); + if ((port == 0) && (ch == 0)) + return err; + else if ((port > 0) && (ch == 0)) + ch = n_ch; + ssi_undo_reg_dev(pd, port, ch); + } + return err; +} + +static int __init ssi_softreset(struct ssi_dev *ssi_ctrl) +{ + int ind = 0; + void __iomem *base = ssi_ctrl->base; + u32 status; + + ssi_outl_or(SSI_SOFTRESET, base, SSI_SYS_SYSCONFIG_REG); + + status = ssi_inl(base, SSI_SYS_SYSSTATUS_REG); + while ((!(status & SSI_RESETDONE)) && (ind < SSI_RESETDONE_RETRIES)) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(SSI_RESETDONE_TIMEOUT)); + status = ssi_inl(base, SSI_SYS_SYSSTATUS_REG); + ind++; + } + + if (ind >= SSI_RESETDONE_RETRIES) + return -EIO; + + /* Reseting GDD */ + ssi_outl_or(SSI_SWRESET, base, SSI_GDD_GRST_REG); + + return 0; +} + +static void __init set_ssi_ports_default( + struct platform_device *pd) +{ + struct ssi_port_pd *cfg = NULL; + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + unsigned int port = 0; + void __iomem *base = p_data->ssi_ctrl->base; + + for (port = 1; port <= p_data->num_ports; port++) { + cfg = &p_data->ports[port - 1]; + ssi_outl(cfg->tx_mode, base, SSI_SST_MODE_REG(port)); + ssi_outl(cfg->tx_frame_size, base, SSI_SST_FRAMESIZE_REG(port)); + ssi_outl(cfg->divisor, base, SSI_SST_DIVISOR_REG(port)); + ssi_outl(cfg->tx_ch, base, SSI_SST_CHANNELS_REG(port)); + ssi_outl(cfg->arb_mode, base, SSI_SST_ARBMODE_REG(port)); + + ssi_outl(cfg->rx_mode, base, SSI_SSR_MODE_REG(port)); + ssi_outl(cfg->rx_frame_size, base, SSI_SSR_FRAMESIZE_REG(port)); + ssi_outl(cfg->rx_ch, base, SSI_SSR_CHANNELS_REG(port)); + ssi_outl(cfg->timeout, base, SSI_SSR_TIMEOUT_REG(port)); + } +} + +static int __init ssi_port_channels_init( + struct platform_device *pd, unsigned int lport) +{ + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + struct ssi_channel *ch; + struct ssi_port *port; + unsigned int n_ch; + unsigned int ch_i; + + n_ch = max(p_data->ports[lport].tx_ch, p_data->ports[lport].rx_ch); + port = &p_data->ssi_ctrl->ssi_port[lport]; + for (ch_i = 0; ch_i < n_ch; ch_i++) { + ch = &port->ssi_channel[ch_i]; + ch->channel_number = ch_i; + ch->flags = 0; + ch->ssi_port = port; + ch->read_data.addr = NULL; + ch->read_data.size = 0; + ch->read_data.lch = -1; + ch->write_data.addr = NULL; + ch->write_data.size = 0; + ch->write_data.lch = -1; + spin_lock_init(&ch->ssi_ch_lock); + } + + return 0; +} + +static int __init ssi_ports_init(struct platform_device *pd) +{ + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + struct ssi_port *ssi_p = NULL; + unsigned int port = 0; + unsigned int err = 0; + unsigned int n_ports; + + n_ports = min(p_data->num_ports, (u8)SSI_MAX_PORTS); + + for (port = 0; ((port < n_ports) && (err >= 0)); port++) { + ssi_p = &p_data->ssi_ctrl->ssi_port[port]; + ssi_p->port_number = port + 1; + ssi_p->ssi_controller = p_data->ssi_ctrl; + ssi_p->max_ch = max(p_data->ports[port].tx_ch, + p_data->ports[port].rx_ch); + ssi_p->max_ch = min(ssi_p->max_ch, (u8)SSI_PORT_MAX_CH); + ssi_p->n_irq = p_data->ports[port].n_irq; + ssi_p->irq = 0; + spin_lock_init(&ssi_p->lock); + err = ssi_port_channels_init(pd, port); + } + + return 0; +} + +static int __init ssi_controller_init(struct platform_device *pd) +{ + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; + int err; + + ssi_ctrl->id = pd->id; + ssi_ctrl->max_p = p_data->num_ports; + ssi_ctrl->pdev = pd; + spin_lock_init(&ssi_ctrl->lock); + ssi_ctrl->ssi_clk = clk_get(&pd->dev, p_data->clk_name); + if (IS_ERR(ssi_ctrl->ssi_clk)) { + dev_err(&pd->dev, "Unable to get SSI clocks"); + return PTR_ERR(ssi_ctrl->ssi_clk); + } + + err = ssi_ports_init(pd); + if (err < 0) { + dev_err(&pd->dev, "Error on ssi_controller initialization"); + clk_put(ssi_ctrl->ssi_clk); + return -EBUSY; + } + + return 0; +} + +static void ssi_controller_exit(struct platform_device *pd) +{ + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; + + p_data->ssi_ctrl = NULL; + ssi_ctrl->pdev = NULL; + clk_put(ssi_ctrl->ssi_clk); + +} + +static int __init request_ssi_irqs(struct platform_device *pd) +{ + struct ssi_platform_data *p_data = pd->dev.platform_data; + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; + struct ssi_port *ssi_p; + struct resource *mpu_irq; + int i; + int j; + int err = 0; + + for (i = 0; ((i < p_data->num_ports) && (!err)); i++) { + mpu_irq = platform_get_resource(pd, IORESOURCE_IRQ, i*2); + if (!mpu_irq) { + dev_err(&pd->dev, "SSI misses info for MPU IRQ" + " on port %d", i + 1); + err = -ENODEV; + } else { + ssi_p = &ssi_ctrl->ssi_port[i]; + ssi_p->n_irq = 0; /* We only use one irq line */ + ssi_p->irq = mpu_irq->start; + tasklet_init(&ssi_p->ssi_tasklet, + do_ssi_tasklet, (unsigned long)ssi_p); + err = request_irq(mpu_irq->start, ssi_mpu_handler, + IRQF_DISABLED, mpu_irq->name, ssi_p); + if (err < 0) { + dev_err(&pd->dev, "FAILED request IRQ (%d)", + mpu_irq->start); + err = -EBUSY; + } + } + } + + if (err < 0) { + /* Let's free the reserved resources if there are any errors */ + for (j = 0; (j > (i-1)); j++) { + printk(KERN_DEBUG LOG_NAME "Free resources on port %d", + j+1); + ssi_p = &ssi_ctrl->ssi_port[i]; + tasklet_disable(&ssi_p->ssi_tasklet); + free_irq(ssi_p->irq, ssi_p); + } + } + + return err; +} + +static int __init request_ssi_gdd_irq(struct platform_device *pd) +{ + struct ssi_platform_data *p_data = pd->dev.platform_data; + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; + struct resource *gdd_irq; + int err = 0; + + gdd_irq = platform_get_resource(pd, IORESOURCE_IRQ, 4); + if (!gdd_irq) { + dev_err(&pd->dev, "SSI device has no info about GDD IRQ"); + return -ENODEV; + } + + ssi_ctrl->gdd_irq = gdd_irq->start; + err = request_irq(gdd_irq->start, ssi_gdd_mpu_handler, + IRQF_DISABLED, gdd_irq->name, ssi_ctrl); + if (err < 0) { + dev_err(&pd->dev, "FAILED to request IRQ NUMBER (%d)", + gdd_irq->start); + return -EBUSY; + } + tasklet_init(&ssi_ctrl->ssi_gdd_tasklet, do_ssi_gdd_tasklet, + (unsigned long)ssi_ctrl); + + return err; +} + +static void free_ssi_irqs(struct ssi_dev *ssi_ctrl) +{ + struct ssi_port *ssi_p; + int i; + + for (i = 0; (i < ssi_ctrl->max_p); i++) { + ssi_p = &ssi_ctrl->ssi_port[i]; + tasklet_disable(&ssi_p->ssi_tasklet); + free_irq(ssi_p->irq, ssi_p); + } +} + +static void free_ssi_gdd_irq(struct ssi_dev *ssi_ctrl) +{ + tasklet_disable(&ssi_ctrl->ssi_gdd_tasklet); + free_irq(ssi_ctrl->gdd_irq, ssi_ctrl); +} + +static int __init ssi_probe(struct platform_device *pd) +{ + struct resource *mem, *ioarea; + struct ssi_dev *ssi_ctrl = NULL; + struct ssi_platform_data *p_data = NULL; + u32 revision = 0; + int err = 0; + + + if ((pd == NULL) || (pd->dev.platform_data == NULL)) { + pr_err(LOG_NAME "No device/platform_data found on " + "ssi device\n"); + return -ENODEV; + } + + ssi_ctrl = kzalloc(sizeof(*ssi_ctrl), GFP_KERNEL); + if (ssi_ctrl == NULL) { + dev_err(&pd->dev, "Could not allocate memory for" + " struct ssi_dev\n"); + return -ENOMEM; + } + + p_data = (struct ssi_platform_data *) pd->dev.platform_data; + p_data->ssi_ctrl = ssi_ctrl; + + mem = platform_get_resource(pd, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pd->dev, "SSI device does not have " + "SSI IO memory region information\n"); + err = -ENODEV; + goto rback5; + } + + err = ssi_controller_init(pd); + if (err < 0) { + dev_err(&pd->dev, "Could not initialize ssi controller:" + " %d\n", err); + goto rback5; + } + + ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1, + pd->dev.bus_id); + if (!ioarea) { + dev_err(&pd->dev, "Unable to request SSI IO memory " + "region\n"); + err = -EBUSY; + goto rollback4; + } + + ssi_ctrl->base = (void __iomem *)mem->start; + + clk_enable(ssi_ctrl->ssi_clk); + + err = request_ssi_irqs(pd); + if (err < 0) + goto rollback1; + + err = request_ssi_gdd_irq(pd); + if (err < 0) + goto rollback2; + + err = ssi_softreset(ssi_ctrl); + if (err < 0) + goto rollback3; + + /* Set default PM settings */ + ssi_outl((SSI_AUTOIDLE | SSI_SIDLEMODE_SMART | SSI_MIDLEMODE_SMART), + ssi_ctrl->base, SSI_SYS_SYSCONFIG_REG); + ssi_outl(SSI_CLK_AUTOGATING_ON, ssi_ctrl->base, SSI_GDD_GCR_REG); + + set_ssi_ports_default(pd); + + /* Gather info from registers for the driver.(REVISION) */ + revision = ssi_inl(ssi_ctrl->base, SSI_SYS_REVISION_REG); + dev_info(&pd->dev, "SSI Hardware REVISION %d.%d\n", + (revision & SSI_REV_MAJOR) >> 4, (revision & SSI_REV_MINOR)); + + err = register_ssi_devices(pd); + if (err < 0) + goto rollback3; + + clk_disable(ssi_ctrl->ssi_clk); + + return err; + +rollback3: + free_ssi_gdd_irq(ssi_ctrl); +rollback2: + free_ssi_irqs(ssi_ctrl); +rollback1: + release_mem_region(mem->start, mem->end - mem->start + 1); + clk_disable(ssi_ctrl->ssi_clk); +rollback4: + ssi_controller_exit(pd); +rback5: + kfree(ssi_ctrl); + return err; +} + +static void __exit close_all_ch(struct ssi_dev *ssi_ctrl) +{ + struct ssi_port *ssi_p; + unsigned int port; + unsigned int ch; + + for (port = 0; port < ssi_ctrl->max_p; port++) { + ssi_p = &ssi_ctrl->ssi_port[port]; + for (ch = 0; ch < ssi_p->max_ch; ch++) + ssi_close(ssi_p->ssi_channel[ch].dev); + } +} + +static int __exit ssi_remove(struct platform_device *pd) +{ + struct resource *mem; + struct ssi_platform_data *p_data = + (struct ssi_platform_data *) pd->dev.platform_data; + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; + + close_all_ch(ssi_ctrl); + free_ssi_gdd_irq(ssi_ctrl); + free_ssi_irqs(ssi_ctrl); + mem = platform_get_resource(pd, IORESOURCE_MEM, 0); + if (mem) + release_mem_region(mem->start, mem->end - mem->start + 1); + ssi_controller_exit(pd); + kfree(ssi_ctrl); + + return 0; +} + +static struct platform_driver ssi_pdriver = { + .probe = ssi_probe, + .remove = __exit_p(ssi_remove), + .driver = { + .name = "omap_ssi", + .owner = THIS_MODULE, + } +}; + +static int __init ssi_driver_init(void) +{ + int err = 0; + + pr_info("SSI DRIVER Version " SSI_DRIVER_VERSION "\n"); + + ssi_bus_init(); + err = platform_driver_probe(&ssi_pdriver, ssi_probe); + if (err < 0) { + pr_err(LOG_NAME "Platform DRIVER register FAILED: %d\n", err); + ssi_bus_exit(); + return err; + } + + return 0; +} + +static void __exit ssi_driver_exit(void) +{ + ssi_bus_exit(); + platform_driver_unregister(&ssi_pdriver); + + pr_info("SSI DRIVER removed\n"); +} + +module_init(ssi_driver_init); +module_exit(ssi_driver_exit); + +MODULE_ALIAS("platform:omap_ssi"); +MODULE_AUTHOR(SSI_DRIVER_AUTHOR); +MODULE_DESCRIPTION(SSI_DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/ssi/ssi_driver.h b/drivers/misc/ssi/ssi_driver.h new file mode 100644 index 0000000..3c6d849 --- /dev/null +++ b/drivers/misc/ssi/ssi_driver.h @@ -0,0 +1,211 @@ +/* + * ssi_driver.h + * + * Header file for the SSI driver low level interface. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __SSI_DRIVER_H__ +#define __SSI_DRIVER_H__ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> + +#include <mach/ssi/ssi_reg_common.h> +#include <mach/ssi/ssi_sys_reg.h> +#include <mach/ssi/ssi_ssr_reg.h> +#include <mach/ssi/ssi_sst_reg.h> +#include <mach/ssi/ssi_gdd_reg.h> + +#include <linux/ssi_driver_if.h> + +#define SSI_DRIVER_VERSION "1.1-rc2" +#define SSI_DRIVER_AUTHOR "Carlos Chinea / Nokia" +#define SSI_DRIVER_DESC "Synchronous Serial Interface Driver" +#define SSI_DRIVER_LINCESE "GPL" +#define SSI_DRIVER_NAME "ssi_driver" + +#define SSI_DEVICE_NAME "ssi_device" + +/* 10 ms */ +#define SSI_RESETDONE_TIMEOUT 10 +/* Let's retry 20 times.=>20x10 ms=200 ms */ +#define SSI_RESETDONE_RETRIES 20 + +/* Channel states */ +#define SSI_CH_OPEN 0x01 + +/* + * The number of channels to use by the driver in the ports, or the highest + * port channel number (+1) used. (MAX:8) + */ +#define SSI_PORT_MAX_CH 4 +/* Number of logical channel in GDD */ +#define SSI_NUM_LCH 8 + +#define LOG_NAME "OMAP SSI: " +#define SSI_PREFIX "ssi:" + +/* + * Callbacks use by the SSI upper layer (SSI protocol) to receive data + * from the port channels. + */ +struct ssi_channel_ops { + void (*write_done) (struct ssi_device *dev); + void (*read_done) (struct ssi_device *dev); +}; + +struct ssi_data { + /* Pointer to the data to be sent/received */ + u32 *addr; + /* + * Number of words to be sent or space reserved for data to be + * received. + */ + unsigned int size; + /* Holds GDD logical channed number */ + int lch; +}; + +struct ssi_channel { + struct ssi_channel_ops ops; + struct ssi_data read_data; + struct ssi_data write_data; + struct ssi_port *ssi_port; + u8 flags; + u8 channel_number; + spinlock_t ssi_ch_lock; + struct ssi_device *dev; + void *priv; +}; + +/* Holds information related to SSI port */ +struct ssi_port { + struct ssi_channel ssi_channel[SSI_PORT_MAX_CH]; + struct ssi_dev *ssi_controller; + u8 port_number; + u8 max_ch; + u8 n_irq; /* IRQ0 or IRQ1 */ + int irq /* Actual IRQ number */; + spinlock_t lock; + struct tasklet_struct ssi_tasklet; +}; + +/* + * Struct definition to hold information about the clocks, ssi controller + * and the ssi ports. + */ +struct ssi_dev { + /* Holds reference to PORT 1 (and PORT2 if defined) */ + struct ssi_port ssi_port[SSI_MAX_PORTS]; + int id; + u8 flags; + u8 max_p; + struct clk *ssi_clk; + void __iomem *base; + spinlock_t lock; + int gdd_irq; + struct tasklet_struct ssi_gdd_tasklet; + struct platform_device *pdev; +}; + +/* SSI Bus */ +struct ssi_port_event { + struct ssi_port *ssi_port; + unsigned int event; + void *priv; +}; +extern struct bus_type ssi_bus_type; + +int ssi_port_event_handler(struct ssi_port *p, unsigned int event, void *arg); +int ssi_bus_init(void); +void ssi_bus_exit(void); +/* End SSI Bus */ + +int ssi_driver_read_interrupt(struct ssi_channel *ssi_channel, u32 *data); +int ssi_driver_write_interrupt(struct ssi_channel *ssi_channel, u32 *data); +int ssi_driver_read_dma(struct ssi_channel *ssi_channel, u32 *data, + unsigned int count); +int ssi_driver_write_dma(struct ssi_channel *ssi_channel, u32 *data, + unsigned int count); + +void ssi_driver_cancel_write_interrupt(struct ssi_channel *ch); +void ssi_driver_cancel_read_interrupt(struct ssi_channel *ch); +void ssi_driver_cancel_write_dma(struct ssi_channel *ch); +void ssi_driver_cancel_read_dma(struct ssi_channel *ch); + +irqreturn_t ssi_mpu_handler(int irq, void *ssi_port); +irqreturn_t ssi_gdd_mpu_handler(int irq, void *ssi_controller); + +void do_ssi_tasklet(unsigned long ssi_port); +void do_ssi_gdd_tasklet(unsigned long device); + +static inline u32 ssi_inl(void __iomem *base, u32 offset) +{ + return inl(OMAP2_IO_ADDRESS(base + offset)); +} + +static inline void ssi_outl(u32 data, void __iomem *base, u32 offset) +{ + outl(data, OMAP2_IO_ADDRESS(base + offset)); +} + +static inline void ssi_outl_or(u32 data, void __iomem *base, u32 offset) +{ + u32 tmp = ssi_inl(base, offset); + ssi_outl((tmp | data), base, offset); +} + +static inline void ssi_outl_and(u32 data, void __iomem *base, u32 offset) +{ + u32 tmp = ssi_inl(base, offset); + ssi_outl((tmp & data), base, offset); +} + +static inline u16 ssi_inw(void __iomem *base, u32 offset) +{ + return inw(OMAP2_IO_ADDRESS(base + offset)); +} + +static inline void ssi_outw(u16 data, void __iomem *base, u32 offset) +{ + outw(data, OMAP2_IO_ADDRESS(base + offset)); +} + +static inline void ssi_outw_or(u16 data, void __iomem *base, u32 offset) +{ + u16 tmp = ssi_inw(base, offset); + ssi_outw((tmp | data), base, offset); +} + +static inline void ssi_outw_and(u16 data, void __iomem *base, u32 offset) +{ + u16 tmp = ssi_inw(base, offset); + ssi_outw((tmp & data), base, offset); +} +#endif diff --git a/drivers/misc/ssi/ssi_driver_bus.c b/drivers/misc/ssi/ssi_driver_bus.c new file mode 100644 index 0000000..6a07ee0 --- /dev/null +++ b/drivers/misc/ssi/ssi_driver_bus.c @@ -0,0 +1,192 @@ +/* + * ssi_driver_bus.c + * + * Implements SSI bus, device and driver interface. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include <linux/device.h> +#include "ssi_driver.h" + +/* LDM. defintions for the ssi bus, ssi device, and ssi_device driver */ +struct bus_type ssi_bus_type; + +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + return snprintf(buf, BUS_ID_SIZE + 1, "%s%s\n", SSI_PREFIX, + dev->bus_id); +} + +static struct device_attribute ssi_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int ssi_bus_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + add_uevent_var(env, "MODALIAS=%s%s", SSI_PREFIX, dev->bus_id); + return 0; +} + +/* NOTE: Function called in interrupt context */ +static int ssi_e_handler(struct device_driver *drv, void *p_event) +{ + struct ssi_port_event *event = (struct ssi_port_event *)p_event; + struct ssi_device_driver *ssi_drv = to_ssi_device_driver(drv); + struct ssi_port *p = event->ssi_port; + + BUG_ON(p_event == NULL); + + if ((ssi_drv->port_event) && + (test_bit(event->event, &ssi_drv->event_mask)) && + ((p->ssi_controller->id == -1) || + (test_bit(p->ssi_controller->id, &ssi_drv->ctrl_mask))) && + (ssi_drv->ch_mask[p->port_number - 1] != 0)) { + + (*ssi_drv->port_event)(p->ssi_controller->id, p->port_number, + event->event, event->priv); + } + + return 0; +} + +int ssi_port_event_handler(struct ssi_port *p, unsigned int event, void *arg) +{ + int err = 0; + struct ssi_port_event p_ev = { + .ssi_port = p, + .event = event, + .priv = arg + }; + + BUG_ON(p == NULL); + + err = bus_for_each_drv(&ssi_bus_type, NULL, &p_ev, ssi_e_handler); + + return err; +} + +static int ssi_bus_match(struct device *device, struct device_driver *driver) +{ + struct ssi_device *dev = to_ssi_device(device); + struct ssi_device_driver *drv = to_ssi_device_driver(driver); + + if (!test_bit(dev->n_ctrl, &drv->ctrl_mask)) + return 0; + + if (!test_bit(dev->n_ch, &drv->ch_mask[dev->n_p])) + return 0; + + return 1; +} + +int ssi_bus_unreg_dev(struct device *device, void *p) +{ + device->release(device); + device_unregister(device); + + return 0; +} + +int __init ssi_bus_init(void) +{ + + return bus_register(&ssi_bus_type); + +} + +void ssi_bus_exit(void) +{ + bus_for_each_dev(&ssi_bus_type, NULL, NULL, ssi_bus_unreg_dev); + bus_unregister(&ssi_bus_type); +} + +static int ssi_driver_probe(struct device *dev) +{ + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); + + return drv->probe(to_ssi_device(dev)); +} + +static int ssi_driver_remove(struct device *dev) +{ + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); + + return drv->remove(to_ssi_device(dev)); +} + +static int ssi_driver_suspend(struct device *dev, pm_message_t mesg) +{ + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); + + return drv->suspend(to_ssi_device(dev), mesg); +} + +static int ssi_driver_resume(struct device *dev) +{ + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); + + return drv->resume(to_ssi_device(dev)); +} + +struct bus_type ssi_bus_type = { + .name = "ssi", + .dev_attrs = ssi_dev_attrs, + .match = ssi_bus_match, + .uevent = ssi_bus_uevent, +}; + +/** + * register_ssi_driver - Register SSI device driver + * @driver - reference to the SSI device driver. + */ +int register_ssi_driver(struct ssi_device_driver *driver) +{ + int ret = 0; + + BUG_ON(driver == NULL); + + driver->driver.bus = &ssi_bus_type; + if (driver->probe) + driver->driver.probe = ssi_driver_probe; + if (driver->remove) + driver->driver.remove = ssi_driver_remove; + if (driver->suspend) + driver->driver.suspend = ssi_driver_suspend; + if (driver->resume) + driver->driver.resume = ssi_driver_resume; + + ret = driver_register(&driver->driver); + + return ret; +} +EXPORT_SYMBOL(register_ssi_driver); + +/** + * unregister_ssi_driver - Unregister SSI device driver + * @driver - reference to the SSI device driver. + */ +void unregister_ssi_driver(struct ssi_device_driver *driver) +{ + BUG_ON(driver == NULL); + + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL(unregister_ssi_driver); diff --git a/drivers/misc/ssi/ssi_driver_dma.c b/drivers/misc/ssi/ssi_driver_dma.c new file mode 100644 index 0000000..2524a12 --- /dev/null +++ b/drivers/misc/ssi/ssi_driver_dma.c @@ -0,0 +1,406 @@ +/* + * ssi_driver_dma.c + * + * Implements SSI low level interface driver functionality with DMA support. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include <linux/dma-mapping.h> +#include "ssi_driver.h" + +#define SSI_SYNC_WRITE 0 +#define SSI_SYNC_READ 1 + +static unsigned char sync_table[2][2][8] = { + { + {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00} + }, + { + {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}, + {0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F} + } +}; + +static unsigned int get_sync_type(unsigned int sync) +{ + return (sync & 0x10) ? SSI_SYNC_READ : SSI_SYNC_WRITE; +} + +static unsigned int get_sync_port(unsigned int sync) +{ + if (((sync >= 0x01) && (sync <= 0x08)) || + ((sync >= 0x10) && (sync <= 0x17))) + return 1; + else if (((sync >= 0x09) && (sync <= 0x0F)) || + ((sync >= 0x18) && (sync <= 0x1E))) + return 2; + else + return 3; +} + +static unsigned int get_sync_channel(unsigned int sync) +{ + if ((sync == 0x00) || (sync == 0x1F)) + return 8; + + if (sync & 0x10) + return (sync & 0x0F) % 8; + else + return (sync - 1) % 8; +} + +static unsigned int get_sync(unsigned int port, + unsigned int channel, unsigned int type) +{ + if ((port == 0) || (port > SSI_MAX_PORTS) || + (channel >= SSI_PORT_MAX_CH) || (type > SSI_SYNC_READ)) + return 0x00; + + return sync_table[type][port - 1][channel]; +} + +static void rst_ch_read(struct ssi_dev *ssi_ctrl, + unsigned int n_p, unsigned int n_ch) +{ + struct ssi_channel *ch = + &ssi_ctrl->ssi_port[n_p - 1].ssi_channel[n_ch]; + + ch->read_data.addr = NULL; + ch->read_data.size = 0; + ch->read_data.lch = -1; +} + +static void rst_ch_write(struct ssi_dev *ssi_ctrl, + unsigned int n_p, unsigned int n_ch) +{ + struct ssi_channel *ch = + &ssi_ctrl->ssi_port[n_p - 1].ssi_channel[n_ch]; + + ch->write_data.addr = NULL; + ch->write_data.size = 0; + ch->write_data.lch = -1; +} + +/* + * get_free_lch - Get a free GDD(DMA)logical channel + * @ssi_ctrl- SSI controller of the GDD. + * + * Needs to be called holding the gdd controller lock + */ +static unsigned int get_free_lch(struct ssi_dev *ssi_ctrl) +{ + unsigned int enable_reg; + unsigned int lch = 0; + + enable_reg = ssi_inl(ssi_ctrl->base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + while ((lch < SSI_NUM_LCH) && (enable_reg & SSI_GDD_LCH(lch))) + lch++; + + return lch; +} + +/* + * ssi_driver_write_dma - Program GDD [DMA] to write data from memory to + * the ssi channel buffer. + * @ssi_channel - pointer to the ssi_channel to write data to. + * @data - 32-bit word pointer to the data. + * @size - Number of 32bit words to be transfered. + * + * ssi_controller lock must be hold before calling this function. + */ +int ssi_driver_write_dma(struct ssi_channel *ssi_channel, u32 *data, + unsigned int size) +{ + struct ssi_dev *ssi_ctrl = ssi_channel->ssi_port->ssi_controller; + void __iomem *base = ssi_ctrl->base; + unsigned int port = ssi_channel->ssi_port->port_number; + unsigned int channel = ssi_channel->channel_number; + unsigned int sync; + int lch; + dma_addr_t dma_data; + u32 s_addr; + u16 tmp; + + if ((size < 1) || (data == NULL)) + return -EINVAL; + + clk_enable(ssi_ctrl->ssi_clk); + + lch = get_free_lch(ssi_ctrl); + if (lch >= SSI_NUM_LCH) { + dev_err(&ssi_ctrl->pdev->dev, "No free GDD logical " + "channels.\n"); + clk_disable(ssi_ctrl->ssi_clk); + return -EBUSY; /* No free GDD logical channels. */ + } + /* NOTE: Gettting a free gdd logical channel and + * reserve it must be done atomicaly. */ + ssi_channel->write_data.lch = lch; + + sync = get_sync(port, channel, SSI_SYNC_WRITE); + dma_data = dma_map_single(NULL, data, size * 4, DMA_TO_DEVICE); + dma_sync_single(NULL, dma_data, size * 4, DMA_TO_DEVICE); + + tmp = SSI_SRC_SINGLE_ACCESS0 | + SSI_SRC_MEMORY_PORT | + SSI_DST_SINGLE_ACCESS0 | + SSI_DST_PERIPHERAL_PORT | + SSI_DATA_TYPE_S32; + ssi_outw(tmp, base, SSI_GDD_CSDP_REG(lch)); + tmp = SSI_SRC_AMODE_POSTINC | SSI_DST_AMODE_CONST | sync; + ssi_outw(tmp, base, SSI_GDD_CCR_REG(lch)); + ssi_outw((SSI_BLOCK_IE | SSI_TOUT_IE), base, SSI_GDD_CICR_REG(lch)); + s_addr = (u32)base + SSI_SST_BUFFER_CH_REG(port, channel); + ssi_outl(s_addr, base, SSI_GDD_CDSA_REG(lch)); + ssi_outl(dma_data, base, SSI_GDD_CSSA_REG(lch)); + ssi_outw(size, base, SSI_GDD_CEN_REG(lch)); + ssi_outl_or(SSI_GDD_LCH(lch), base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + ssi_outw_or(SSI_CCR_ENABLE, base, SSI_GDD_CCR_REG(lch)); + + return 0; +} + +/* + * ssi_driver_read_dma - Program GDD [DMA] to write data to memory from + * the ssi channel buffer. + * @ssi_channel - pointer to the ssi_channel to read data from. + * @data - 32-bit word pointer where to store the incoming data. + * @size - Number of 32bit words to be transfered to the buffer. + * + * ssi_controller lock must be hold before calling this function. + */ +int ssi_driver_read_dma(struct ssi_channel *ssi_channel, u32 *data, + unsigned int count) +{ + struct ssi_dev *ssi_ctrl = ssi_channel->ssi_port->ssi_controller; + void __iomem *base = ssi_ctrl->base; + unsigned int port = ssi_channel->ssi_port->port_number; + unsigned int channel = ssi_channel->channel_number; + unsigned int sync; + unsigned int lch; + dma_addr_t dma_data; + u32 d_addr; + u16 tmp; + + clk_enable(ssi_ctrl->ssi_clk); + lch = get_free_lch(ssi_ctrl); + if (lch >= SSI_NUM_LCH) { + dev_err(&ssi_ctrl->pdev->dev, "No free GDD logical " + "channels.\n"); + clk_disable(ssi_ctrl->ssi_clk); + return -EBUSY; /* No free GDD logical channels. */ + } + /* + * NOTE: Gettting a free gdd logical channel and + * reserve it must be done atomicaly. + */ + ssi_channel->read_data.lch = lch; + + sync = get_sync(port, channel, SSI_SYNC_READ); + + dma_data = dma_map_single(NULL, data, count * 4, DMA_FROM_DEVICE); + + tmp = SSI_DST_SINGLE_ACCESS0 | + SSI_DST_MEMORY_PORT | + SSI_SRC_SINGLE_ACCESS0 | + SSI_SRC_PERIPHERAL_PORT | + SSI_DATA_TYPE_S32; + ssi_outw(tmp, base, SSI_GDD_CSDP_REG(lch)); + tmp = SSI_DST_AMODE_POSTINC | SSI_SRC_AMODE_CONST | sync; + ssi_outw(tmp, base, SSI_GDD_CCR_REG(lch)); + ssi_outw((SSI_BLOCK_IE | SSI_TOUT_IE), base, SSI_GDD_CICR_REG(lch)); + d_addr = (u32)base + SSI_SSR_BUFFER_CH_REG(port, channel); + ssi_outl(d_addr, base, SSI_GDD_CSSA_REG(lch)); + ssi_outl((u32)dma_data, base, SSI_GDD_CDSA_REG(lch)); + ssi_outw(count, base, SSI_GDD_CEN_REG(lch)); + + ssi_outl_or(SSI_GDD_LCH(lch), base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + ssi_outw_or(SSI_CCR_ENABLE, base, SSI_GDD_CCR_REG(lch)); + + return 0; +} + +void ssi_driver_cancel_write_dma(struct ssi_channel *ssi_ch) +{ + int lch = ssi_ch->write_data.lch; + unsigned int port = ssi_ch->ssi_port->port_number; + unsigned int channel = ssi_ch->channel_number; + struct ssi_dev *ssi_ctrl = ssi_ch->ssi_port->ssi_controller; + u32 ccr; + + if (lch < 0) + return; + + clk_enable(ssi_ctrl->ssi_clk); + ccr = ssi_inw(ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); + if (!(ccr & SSI_CCR_ENABLE)) { + dev_dbg(&ssi_ch->dev->device, LOG_NAME "Write cancel on not " + "enabled logical channel %d CCR REG 0x%08X\n", lch, ccr); + clk_disable(ssi_ctrl->ssi_clk); + return; + } + + ssi_outw_and(~SSI_CCR_ENABLE, ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); + ssi_outl_and(~SSI_GDD_LCH(lch), ssi_ctrl->base, + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + ssi_outl(SSI_GDD_LCH(lch), ssi_ctrl->base, + SSI_SYS_GDD_MPU_IRQ_STATUS_REG); + + ssi_outl_and(~NOTFULL(channel), ssi_ctrl->base, + SSI_SST_BUFSTATE_REG(port)); + + + ssi_ch->write_data.addr = NULL; + ssi_ch->write_data.size = 0; + ssi_ch->write_data.lch = -1; + clk_disable(ssi_ctrl->ssi_clk); + clk_disable(ssi_ctrl->ssi_clk); +} + +void ssi_driver_cancel_read_dma(struct ssi_channel *ssi_ch) +{ + int lch = ssi_ch->read_data.lch; + struct ssi_dev *ssi_ctrl = ssi_ch->ssi_port->ssi_controller; + unsigned int port = ssi_ch->ssi_port->port_number; + unsigned int channel = ssi_ch->channel_number; + u32 reg; + + if (lch < 0) + return; + + clk_enable(ssi_ctrl->ssi_clk); + reg = ssi_inw(ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); + if (!(reg & SSI_CCR_ENABLE)) { + dev_dbg(&ssi_ch->dev->device, LOG_NAME "Read cancel on not " + "enable logical channel %d CCR REG 0x%08X\n", lch, reg); + clk_disable(ssi_ctrl->ssi_clk); + return; + } + + ssi_outw_and(~SSI_CCR_ENABLE, ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); + ssi_outl_and(~SSI_GDD_LCH(lch), ssi_ctrl->base, + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + ssi_outl(SSI_GDD_LCH(lch), ssi_ctrl->base, + SSI_SYS_GDD_MPU_IRQ_STATUS_REG); + + ssi_outl_and(~NOTEMPTY(channel), ssi_ctrl->base, + SSI_SSR_BUFSTATE_REG(port)); + + ssi_ch->read_data.addr = NULL; + ssi_ch->read_data.size = 0; + ssi_ch->read_data.lch = -1; + clk_disable(ssi_ctrl->ssi_clk); + clk_disable(ssi_ctrl->ssi_clk); +} + +static void dma_read_cb(struct ssi_dev *ctrl, unsigned int port, + unsigned int channel) +{ + struct ssi_channel *ch = &ctrl->ssi_port[port - 1].ssi_channel[channel]; + + ch->ops.read_done(ch->dev); +} + +static void dma_write_cb(struct ssi_dev *ctrl, unsigned int port, + unsigned int channel) +{ + struct ssi_channel *ch = &ctrl->ssi_port[port - 1].ssi_channel[channel]; + + ch->ops.write_done(ch->dev); +} + +static void do_gdd_lch(struct ssi_dev *ssi_ctrl, unsigned int gdd_lch) +{ + void __iomem *base = ssi_ctrl->base; + unsigned int port; + unsigned int channel; + u32 sync; + u32 gdd_csr; + dma_addr_t dma_h; + size_t size; + + sync = ssi_inw(base, SSI_GDD_CCR_REG(gdd_lch)) & SSI_CCR_SYNC_MASK; + port = get_sync_port(sync); + channel = get_sync_channel(sync); + + spin_lock(&ssi_ctrl->lock); + + ssi_outl_and(~SSI_GDD_LCH(gdd_lch), base, + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); + gdd_csr = ssi_inw(base, SSI_GDD_CSR_REG(gdd_lch)); + + if (!(gdd_csr & SSI_CSR_TOUR)) { + if (get_sync_type(sync) == SSI_SYNC_READ) { + dma_h = ssi_inl(base, SSI_GDD_CDSA_REG(gdd_lch)); + size = ssi_inw(base, SSI_GDD_CEN_REG(gdd_lch)) * 4; + dma_sync_single(NULL, dma_h, size, DMA_FROM_DEVICE); + rst_ch_read(ssi_ctrl, port, channel); + spin_unlock(&ssi_ctrl->lock); + dma_read_cb(ssi_ctrl, port, channel); + } else { + rst_ch_write(ssi_ctrl, port, channel); + spin_unlock(&ssi_ctrl->lock); + dma_write_cb(ssi_ctrl, port, channel); + } + } else { + dev_err(&ssi_ctrl->pdev->dev, "Error on GDD transfer " + "on gdd channel %d port %d channel %d\n", + gdd_lch, port, channel); + spin_unlock(&ssi_ctrl->lock); + ssi_port_event_handler(&ssi_ctrl->ssi_port[port - 1], + SSI_EVENT_ERROR, NULL); + } +} + +void do_ssi_gdd_tasklet(unsigned long device) +{ + struct ssi_dev *ssi_ctrl = (struct ssi_dev *)device; + void __iomem *base = ssi_ctrl->base; + unsigned int gdd_lch = 0; + u32 status_reg = 0; + u32 lch_served = 0; + + clk_enable(ssi_ctrl->ssi_clk); + + status_reg = ssi_inl(base, SSI_SYS_GDD_MPU_IRQ_STATUS_REG); + + for (gdd_lch = 0; gdd_lch < SSI_NUM_LCH; gdd_lch++) { + if (status_reg & SSI_GDD_LCH(gdd_lch)) { + do_gdd_lch(ssi_ctrl, gdd_lch); + lch_served |= SSI_GDD_LCH(gdd_lch); + clk_disable(ssi_ctrl->ssi_clk); + } + } + + ssi_outl(lch_served, base, SSI_SYS_GDD_MPU_IRQ_STATUS_REG); + clk_disable(ssi_ctrl->ssi_clk); + + enable_irq(ssi_ctrl->gdd_irq); +} + +irqreturn_t ssi_gdd_mpu_handler(int irq, void *ssi_controller) +{ + struct ssi_dev *ssi_ctrl = (struct ssi_dev *)ssi_controller; + + tasklet_hi_schedule(&ssi_ctrl->ssi_gdd_tasklet); + disable_irq_nosync(ssi_ctrl->gdd_irq); + + return IRQ_HANDLED; +} diff --git a/drivers/misc/ssi/ssi_driver_if.c b/drivers/misc/ssi/ssi_driver_if.c new file mode 100644 index 0000000..385467e --- /dev/null +++ b/drivers/misc/ssi/ssi_driver_if.c @@ -0,0 +1,335 @@ +/* + * ssi_driver_if.c + * + * Implements SSI hardware driver interfaces for the upper layers. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "ssi_driver.h" + +/** + * ssi_open - open a ssi device channel. + * @dev - Reference to the ssi device channel to be openned. + * + * Returns 0 on success, -EINVAL on bad parameters, -EBUSY if is already opened. + */ +int ssi_open(struct ssi_device *dev) +{ + struct ssi_channel *ch; + struct ssi_port *port; + struct ssi_dev *ssi_ctrl; + + if (!dev || !dev->ch) { + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); + return -EINVAL; + } + + ch = dev->ch; + if (!ch->ops.read_done || !ch->ops.write_done) { + dev_err(&dev->device, "Trying to open with no callbacks " + "registered\n"); + return -EINVAL; + } + port = ch->ssi_port; + ssi_ctrl = port->ssi_controller; + spin_lock_bh(&ssi_ctrl->lock); + if (ch->flags & SSI_CH_OPEN) { + dev_err(&dev->device, "Port %d Channel %d already OPENED\n", + dev->n_p, dev->n_ch); + spin_unlock_bh(&ssi_ctrl->lock); + return -EBUSY; + } + clk_enable(ssi_ctrl->ssi_clk); + ch->flags |= SSI_CH_OPEN; + ssi_outl_or(SSI_ERROROCCURED | SSI_BREAKDETECTED, ssi_ctrl->base, + SSI_SYS_MPU_ENABLE_REG(port->port_number, port->n_irq)); + clk_disable(ssi_ctrl->ssi_clk); + spin_unlock_bh(&ssi_ctrl->lock); + + return 0; +} +EXPORT_SYMBOL(ssi_open); + +/** + * ssi_write - write data into the ssi device channel + * @dev - reference to the ssi device channel to write into. + * @data - pointer to a 32-bit word data to be written. + * @count - number of 32-bit word to be written. + * + * Return 0 on sucess, a negative value on failure. + * A success values only indicates that the request has been accepted. + * Transfer is only completed when the write_done callback is called. + * + */ +int ssi_write(struct ssi_device *dev, u32 *data, unsigned int count) +{ + struct ssi_channel *ch; + int err; + + if (unlikely(!dev || !dev->ch || !data || (count <= 0))) { + dev_err(&dev->device, "Wrong paramenters " + "ssi_device %p data %p count %d", dev, data, count); + return -EINVAL; + } + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { + dev_err(&dev->device, "SSI device NOT open\n"); + return -EINVAL; + } + + ch = dev->ch; + spin_lock_bh(&ch->ssi_port->ssi_controller->lock); + ch->write_data.addr = data; + ch->write_data.size = count; + + if (count == 1) + err = ssi_driver_write_interrupt(ch, data); + else + err = ssi_driver_write_dma(ch, data, count); + + if (unlikely(err < 0)) { + ch->write_data.addr = NULL; + ch->write_data.size = 0; + } + spin_unlock_bh(&ch->ssi_port->ssi_controller->lock); + + return err; + +} +EXPORT_SYMBOL(ssi_write); + +/** + * ssi_read - read data from the ssi device channel + * @dev - ssi device channel reference to read data from. + * @data - pointer to a 32-bit word data to store the data. + * @count - number of 32-bit word to be stored. + * + * Return 0 on sucess, a negative value on failure. + * A success values only indicates that the request has been accepted. + * Data is only available in the buffer when the read_done callback is called. + * + */ +int ssi_read(struct ssi_device *dev, u32 *data, unsigned int count) +{ + struct ssi_channel *ch; + int err; + + if (unlikely(!dev || !dev->ch || !data || (count <= 0))) { + dev_err(&dev->device, "Wrong paramenters " + "ssi_device %p data %p count %d", dev, data, count); + return -EINVAL; + } + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { + dev_err(&dev->device, "SSI device NOT open\n"); + return -EINVAL; + } + + ch = dev->ch; + spin_lock_bh(&ch->ssi_port->ssi_controller->lock); + ch->read_data.addr = data; + ch->read_data.size = count; + + if (count == 1) + err = ssi_driver_read_interrupt(ch, data); + else + err = ssi_driver_read_dma(ch, data, count); + + if (unlikely(err < 0)) { + ch->read_data.addr = NULL; + ch->read_data.size = 0; + } + spin_unlock_bh(&ch->ssi_port->ssi_controller->lock); + + return err; +} +EXPORT_SYMBOL(ssi_read); + +void __ssi_write_cancel(struct ssi_channel *ch) +{ + if (ch->write_data.size == 1) + ssi_driver_cancel_write_interrupt(ch); + else if (ch->write_data.size > 1) + ssi_driver_cancel_write_dma(ch); + +} +/** + * ssi_write_cancel - Cancel pending write request. + * @dev - ssi device channel where to cancel the pending write. + * + * write_done() callback will not be called after sucess of this function. + */ +void ssi_write_cancel(struct ssi_device *dev) +{ + if (unlikely(!dev || !dev->ch)) { + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); + return; + } + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { + dev_err(&dev->device, "SSI device NOT open\n"); + return; + } + + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); + __ssi_write_cancel(dev->ch); + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); +} +EXPORT_SYMBOL(ssi_write_cancel); + +void __ssi_read_cancel(struct ssi_channel *ch) +{ + if (ch->read_data.size == 1) + ssi_driver_cancel_read_interrupt(ch); + else if (ch->read_data.size > 1) + ssi_driver_cancel_read_dma(ch); +} + +/** + * ssi_read_cancel - Cancel pending read request. + * @dev - ssi device channel where to cancel the pending read. + * + * read_done() callback will not be called after sucess of this function. + */ +void ssi_read_cancel(struct ssi_device *dev) +{ + if (unlikely(!dev || !dev->ch)) { + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); + return; + } + + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { + dev_err(&dev->device, "SSI device NOT open\n"); + return; + } + + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); + __ssi_read_cancel(dev->ch); + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); + +} +EXPORT_SYMBOL(ssi_read_cancel); + +/** + * ssi_ioctl - SSI I/O control + * @dev - ssi device channel reference to apply the I/O control + * (or port associated to it) + * @command - SSI I/O control command + * @arg - parameter associated to the control command. NULL, if no parameter. + * + * Return 0 on sucess, a negative value on failure. + * + */ +int ssi_ioctl(struct ssi_device *dev, unsigned int command, void *arg) +{ + struct ssi_channel *ch; + struct ssi_dev *ssi_ctrl; + void __iomem *base; + unsigned int port, channel; + u32 wake; + int err = 0; + + if (unlikely((!dev) || + (!dev->ch) || + (!dev->ch->ssi_port) || + (!dev->ch->ssi_port->ssi_controller)) || + (!(dev->ch->flags & SSI_CH_OPEN))) { + pr_err(LOG_NAME "SSI IOCTL Invalid parameter\n"); + return -EINVAL; + } + + + ch = dev->ch; + ssi_ctrl = ch->ssi_port->ssi_controller; + port = ch->ssi_port->port_number; + channel = ch->channel_number; + base = ssi_ctrl->base; + clk_enable(ssi_ctrl->ssi_clk); + + switch (command) { + case SSI_IOCTL_WAKE_UP: + /* We only claim once the wake line per channel */ + wake = ssi_inl(base, SSI_SYS_WAKE_REG(port)); + if (!(wake & SSI_WAKE(channel))) { + clk_enable(ssi_ctrl->ssi_clk); + ssi_outl(SSI_WAKE(channel), base, + SSI_SYS_SET_WAKE_REG(port)); + } + break; + case SSI_IOCTL_WAKE_DOWN: + wake = ssi_inl(base, SSI_SYS_WAKE_REG(port)); + if ((wake & SSI_WAKE(channel))) { + ssi_outl(SSI_WAKE(channel), base, + SSI_SYS_CLEAR_WAKE_REG(port)); + clk_disable(ssi_ctrl->ssi_clk); + } + break; + case SSI_IOCTL_SEND_BREAK: + ssi_outl(1, base, SSI_SST_BREAK_REG(port)); + break; + case SSI_IOCTL_WAKE: + if (arg == NULL) + err = -EINVAL; + else + *(u32 *)arg = ssi_inl(base, SSI_SYS_WAKE_REG(port)); + break; + default: + err = -ENOIOCTLCMD; + break; + } + + clk_disable(ssi_ctrl->ssi_clk); + + return err; +} +EXPORT_SYMBOL(ssi_ioctl); + +/** + * ssi_close - close given ssi device channel + * @dev - reference to ssi device channel. + */ +void ssi_close(struct ssi_device *dev) +{ + if (!dev || !dev->ch) { + pr_err(LOG_NAME "Trying to close wrong SSI device %p\n", dev); + return; + } + + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); + if (dev->ch->flags & SSI_CH_OPEN) { + dev->ch->flags &= ~SSI_CH_OPEN; + __ssi_write_cancel(dev->ch); + __ssi_read_cancel(dev->ch); + } + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); + +} +EXPORT_SYMBOL(ssi_close); + +/** + * ssi_dev_set_cb - register read_done() and write_done() callbacks. + * @dev - reference to ssi device channel where callbacks are associated. + * @r_cb - callback to signal read transfer completed. + * @w_cb - callback to signal write transfer completed. + */ +void ssi_dev_set_cb(struct ssi_device *dev, void (*r_cb)(struct ssi_device *dev) + , void (*w_cb)(struct ssi_device *dev)) +{ + dev->ch->ops.read_done = r_cb; + dev->ch->ops.write_done = w_cb; +} +EXPORT_SYMBOL(ssi_dev_set_cb); diff --git a/drivers/misc/ssi/ssi_driver_int.c b/drivers/misc/ssi/ssi_driver_int.c new file mode 100644 index 0000000..6491e48 --- /dev/null +++ b/drivers/misc/ssi/ssi_driver_int.c @@ -0,0 +1,232 @@ +/* + * ssi_driver_int.c + * + * Implements SSI interrupt functionality. + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include "ssi_driver.h" + +static void reset_ch_read(struct ssi_channel *ch) +{ + ch->read_data.addr = NULL; + ch->read_data.size = 0; + ch->read_data.lch = -1; +} + +static void reset_ch_write(struct ssi_channel *ch) +{ + ch->write_data.addr = NULL; + ch->write_data.size = 0; + ch->write_data.lch = -1; +} + +int ssi_driver_write_interrupt(struct ssi_channel *ch, u32 *data) +{ + struct ssi_port *p = ch->ssi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + + clk_enable(p->ssi_controller->ssi_clk); + ssi_outl_or(SSI_SST_DATAACCEPT(channel), p->ssi_controller->base, + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); + + + return 0; +} + +int ssi_driver_read_interrupt(struct ssi_channel *ch, u32 *data) +{ + struct ssi_port *p = ch->ssi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + + clk_enable(p->ssi_controller->ssi_clk); + + ssi_outl_or(SSI_SSR_DATAAVAILABLE(channel), p->ssi_controller->base, + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); + + clk_disable(p->ssi_controller->ssi_clk); + + return 0; +} + +void ssi_driver_cancel_write_interrupt(struct ssi_channel *ch) +{ + struct ssi_port *p = ch->ssi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + void __iomem *base = p->ssi_controller->base; + u32 enable; + + clk_enable(p->ssi_controller->ssi_clk); + + enable = ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); + if (!(enable & SSI_SST_DATAACCEPT(channel))) { + dev_dbg(&ch->dev->device, LOG_NAME "Write cancel on not " + "enabled channel %d ENABLE REG 0x%08X", channel, enable); + clk_disable(p->ssi_controller->ssi_clk); + return; + } + ssi_outl_and(~SSI_SST_DATAACCEPT(channel), base, + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); + ssi_outl_and(~NOTFULL(channel), base, SSI_SST_BUFSTATE_REG(port)); + reset_ch_write(ch); + + clk_disable(p->ssi_controller->ssi_clk); + clk_disable(p->ssi_controller->ssi_clk); + +} + +void ssi_driver_cancel_read_interrupt(struct ssi_channel *ch) +{ + struct ssi_port *p = ch->ssi_port; + unsigned int port = p->port_number; + unsigned int channel = ch->channel_number; + void __iomem *base = p->ssi_controller->base; + + clk_enable(p->ssi_controller->ssi_clk); + + ssi_outl_and(~SSI_SSR_DATAAVAILABLE(channel), base, + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); + ssi_outl_and(~NOTEMPTY(channel), base, SSI_SSR_BUFSTATE_REG(port)); + reset_ch_read(ch); + + clk_disable(p->ssi_controller->ssi_clk); +} + +static void do_channel_tx(struct ssi_channel *ch) +{ + struct ssi_dev *ssi_ctrl = ch->ssi_port->ssi_controller; + void __iomem *base = ssi_ctrl->base; + unsigned int n_ch; + unsigned int n_p; + unsigned int irq; + + n_ch = ch->channel_number; + n_p = ch->ssi_port->port_number; + irq = ch->ssi_port->n_irq; + + spin_lock(&ssi_ctrl->lock); + + if (ch->write_data.addr == NULL) { + ssi_outl_and(~SSI_SST_DATAACCEPT(n_ch), base, + SSI_SYS_MPU_ENABLE_REG(n_p, irq)); + reset_ch_write(ch); + spin_unlock(&ssi_ctrl->lock); + clk_disable(ssi_ctrl->ssi_clk); + (*ch->ops.write_done)(ch->dev); + } else { + ssi_outl(*(ch->write_data.addr), base, + SSI_SST_BUFFER_CH_REG(n_p, n_ch)); + ch->write_data.addr = NULL; + spin_unlock(&ssi_ctrl->lock); + } +} + +static void do_channel_rx(struct ssi_channel *ch) +{ + struct ssi_dev *ssi_ctrl = ch->ssi_port->ssi_controller; + void __iomem *base = ch->ssi_port->ssi_controller->base; + unsigned int n_ch; + unsigned int n_p; + unsigned int irq; + + n_ch = ch->channel_number; + n_p = ch->ssi_port->port_number; + irq = ch->ssi_port->n_irq; + + spin_lock(&ssi_ctrl->lock); + + *(ch->read_data.addr) = ssi_inl(base, SSI_SSR_BUFFER_CH_REG(n_p, n_ch)); + + ssi_outl_and(~SSI_SSR_DATAAVAILABLE(n_ch), base, + SSI_SYS_MPU_ENABLE_REG(n_p, irq)); + reset_ch_read(ch); + + spin_unlock(&ssi_ctrl->lock); + + (*ch->ops.read_done)(ch->dev); +} + +void do_ssi_tasklet(unsigned long ssi_port) +{ + struct ssi_port *pport = (struct ssi_port *)ssi_port; + struct ssi_dev *ssi_ctrl = pport->ssi_controller; + void __iomem *base = ssi_ctrl->base; + unsigned int port = pport->port_number; + unsigned int channel = 0; + unsigned int irq = pport->n_irq; + u32 status_reg; + u32 enable_reg; + u32 ssr_err_reg; + u32 channels_served; + + clk_enable(ssi_ctrl->ssi_clk); + + channels_served = 0; + status_reg = ssi_inl(base, SSI_SYS_MPU_STATUS_REG(port, irq)); + enable_reg = ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, irq)); + + for (channel = 0; channel < pport->max_ch; channel++) { + if ((status_reg & SSI_SST_DATAACCEPT(channel)) && + (enable_reg & SSI_SST_DATAACCEPT(channel))) { + do_channel_tx(&pport->ssi_channel[channel]); + channels_served |= SSI_SST_DATAACCEPT(channel); + } + + if ((status_reg & SSI_SSR_DATAAVAILABLE(channel)) && + (enable_reg & SSI_SSR_DATAAVAILABLE(channel))) { + do_channel_rx(&pport->ssi_channel[channel]); + channels_served |= SSI_SSR_DATAAVAILABLE(channel); + } + } + + if ((status_reg & SSI_BREAKDETECTED) && + (enable_reg & SSI_BREAKDETECTED)) { + dev_info(&ssi_ctrl->pdev->dev, + "Hardware BREAK on port %d\n", port); + ssi_outl(0, base, SSI_SSR_BREAK_REG(port)); + ssi_port_event_handler(pport, SSI_EVENT_BREAK_DETECTED, NULL); + } + + if (status_reg & SSI_ERROROCCURED) { + ssr_err_reg = ssi_inl(base, SSI_SSR_ERROR_REG(port)); + dev_err(&ssi_ctrl->pdev->dev, "SSI ERROR Port %d: 0x%02x\n", + port, ssr_err_reg); + ssi_outl(ssr_err_reg, base, SSI_SSR_ERRORACK_REG(port)); + ssi_port_event_handler(pport, SSI_EVENT_ERROR, NULL); + } + + ssi_outl((channels_served | SSI_ERROROCCURED | SSI_BREAKDETECTED), base, + SSI_SYS_MPU_STATUS_REG(port, irq)); + + clk_disable(ssi_ctrl->ssi_clk); + enable_irq(pport->irq); +} + +irqreturn_t ssi_mpu_handler(int irq, void *ssi_port) +{ + struct ssi_port *p = (struct ssi_port *)ssi_port; + + tasklet_hi_schedule(&p->ssi_tasklet); + disable_irq_nosync(p->irq); + + return IRQ_HANDLED; +} -- 1.5.3.6 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC][PATCH 4/5] OMAP SSI integration into misc drivers 2008-10-03 11:52 ` [RFC][PATCH 3/5] OMAP SSI driver code Carlos Chinea @ 2008-10-03 11:52 ` Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 5/5] OMAP SSI API documentation Carlos Chinea 2008-10-07 0:08 ` [RFC][PATCH 4/5] OMAP SSI integration into misc drivers Felipe Balbi 2008-10-07 0:03 ` [RFC][PATCH 3/5] OMAP SSI driver code Felipe Balbi 1 sibling, 2 replies; 14+ messages in thread From: Carlos Chinea @ 2008-10-03 11:52 UTC (permalink / raw) To: linux-kernel; +Cc: linux-omap Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> --- drivers/misc/Kconfig | 2 ++ drivers/misc/Makefile | 1 + 2 files changed, 3 insertions(+), 0 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index f0202ee..b09dc68 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -488,4 +488,6 @@ config SGI_GRU_DEBUG This option enables addition debugging code for the SGI GRU driver. If you are unsure, say N. +source "drivers/misc/ssi/Kconfig" + endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index b6167e7..39e153d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_KGDB_TESTS) += kgdbts.o obj-$(CONFIG_SGI_XP) += sgi-xp/ obj-$(CONFIG_SGI_GRU) += sgi-gru/ obj-$(CONFIG_HP_ILO) += hpilo.o +obj-$(CONFIG_OMAP_SSI) += ssi/ -- 1.5.3.6 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC][PATCH 5/5] OMAP SSI API documentation 2008-10-03 11:52 ` [RFC][PATCH 4/5] OMAP SSI integration into misc drivers Carlos Chinea @ 2008-10-03 11:52 ` Carlos Chinea 2008-10-09 16:47 ` Felipe Balbi 2008-10-07 0:08 ` [RFC][PATCH 4/5] OMAP SSI integration into misc drivers Felipe Balbi 1 sibling, 1 reply; 14+ messages in thread From: Carlos Chinea @ 2008-10-03 11:52 UTC (permalink / raw) To: linux-kernel; +Cc: linux-omap Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> --- Documentation/arm/OMAP/ssi/board-ssi.c.example | 216 ++++++++++++++++++++++ Documentation/arm/OMAP/ssi/ssi | 232 ++++++++++++++++++++++++ 2 files changed, 448 insertions(+), 0 deletions(-) create mode 100644 Documentation/arm/OMAP/ssi/board-ssi.c.example create mode 100644 Documentation/arm/OMAP/ssi/ssi diff --git a/Documentation/arm/OMAP/ssi/board-ssi.c.example b/Documentation/arm/OMAP/ssi/board-ssi.c.example new file mode 100644 index 0000000..a346628 --- /dev/null +++ b/Documentation/arm/OMAP/ssi/board-ssi.c.example @@ -0,0 +1,216 @@ +/* + * board-ssi.c.example + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Contact: Carlos Chinea <carlos.chinea@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifdef CONFIG_OMAP_SSI + +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/ssi_driver_if.h> +#include <mach/ssi/ssi_sys_reg.h> +#include <mach/ssi/ssi_ssr_reg.h> +#include <mach/ssi/ssi_sst_reg.h> + +#include "clock.h" + +struct ssi_internal_clk { + struct clk clk; + struct clk **childs; + int n_childs; + struct platform_device *pdev; +}; + +static struct ssi_internal_clk ssi_clock; + +static void ssi_pdev_release(struct device *dev) +{ +} + +static struct ssi_port_pd ssi_ports[] = { + [0] = { + .tx_mode = SSI_MODE_FRAME, + .tx_frame_size = SSI_FRAMESIZE_DEFAULT, + .divisor = SSI_DIVISOR_DEFAULT, + .tx_ch = SSI_CHANNELS_DEFAULT, + .arb_mode = SSI_ARBMODE_ROUNDROBIN, + .rx_mode = SSI_MODE_FRAME, + .rx_frame_size = SSI_FRAMESIZE_DEFAULT, + .rx_ch = SSI_CHANNELS_DEFAULT, + .timeout = SSI_TIMEOUT_DEFAULT, + .n_irq = 0, + }, +}; + +static struct ssi_platform_data ssi_p_d = { + .clk_name = "ssi_clk", + .num_ports = ARRAY_SIZE(ssi_ports), + .ports = ssi_ports, +}; + +static struct resource ssi_resources[] = { + [0] = { + .start = SSI_IOMEM_BASE_ADDR, + .end = SSI_IOMEM_BASE_ADDR + SSI_IOMEM_SIZE, + .name = SSI_IOMEM_NAME, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = SSI_P1_MPU_IRQ0, + .end = SSI_P1_MPU_IRQ0, + .name = SSI_P1_MPU_IRQ0_NAME, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = SSI_P1_MPU_IRQ1, + .end = SSI_P1_MPU_IRQ1, + .name = SSI_P1_MPU_IRQ1_NAME, + .flags = IORESOURCE_IRQ, + }, + [3] = { + .start = SSI_P2_MPU_IRQ0, + .end = SSI_P2_MPU_IRQ0, + .name = SSI_P2_MPU_IRQ0_NAME, + .flags = IORESOURCE_IRQ, + }, + [4] = { + .start = SSI_P2_MPU_IRQ1, + .end = SSI_P2_MPU_IRQ1, + .name = SSI_P2_MPU_IRQ1_NAME, + .flags = IORESOURCE_IRQ, + }, + [5] = { + .start = SSI_GDD_MPU_IRQ, + .end = SSI_GDD_MPU_IRQ, + .name = SSI_GDD_MPU_IRQ_NAME, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device ssi_pdev = { + .name = "omap_ssi", + .id = -1, + .num_resources = ARRAY_SIZE(ssi_resources), + .resource = ssi_resources, + .dev = { + .release = ssi_pdev_release, + .platform_data = &ssi_p_d, + }, +}; + +static void set_ssi_mode(struct platform_device *pdev, u32 mode) +{ + void __iomem *base = (void __iomem *)pdev->resource[0].start; + int port; + int num_ports = ((struct ssi_platform_data *) + (pdev->dev.platform_data))->num_ports; + + for (port = 0; port < num_ports; port++) { + outl(mode, OMAP2_IO_ADDRESS(base + SSI_SST_MODE_REG(port))); + outl(mode, OMAP2_IO_ADDRESS(base + SSI_SSR_MODE_REG(port))); + } +} + +static int ssi_clk_init(struct ssi_internal_clk *ssi_clk) +{ + const char *clk_names[] = { "ssi_ick", "ssi_ssr_fck" }; + int i; + int j; + + ssi_clk->n_childs = ARRAY_SIZE(clk_names); + ssi_clk->childs = kzalloc(ssi_clk->n_childs * sizeof(*ssi_clk->childs), + GFP_KERNEL); + if (!ssi_clk->childs) + return -ENOMEM; + + for (i = 0; i < ssi_clk->n_childs; i++) { + ssi_clk->childs[i] = clk_get(NULL, clk_names[i]); + if (IS_ERR(ssi_clk->childs[i])) { + pr_err("Unable to get SSI clock: %s", clk_names[i]); + for (j = i - 1; j >= 0; j--) + clk_put(ssi_clk->childs[j]); + return -ENODEV; + } + } + + return 0; +} + +static int ssi_clk_enable(struct clk *clk) +{ + struct ssi_internal_clk *ssi_clk = + container_of(clk, struct ssi_internal_clk, clk); + int err = 0; + int i; + int j; + + for (i = 0; ((i < ssi_clk->n_childs) && (err >= 0)); i++) + err = omap2_clk_enable(ssi_clk->childs[i]); + + if (unlikely(err < 0)) { + pr_err("Error on SSI clk %d\n", i); + for (j = i - 1; j >= 0; j--) + omap2_clk_disable(ssi_clk->childs[j]); + } else { + if (ssi_clk->clk.usecount == 1) + set_ssi_mode(ssi_clk->pdev, SSI_MODE_FRAME); + } + + return err; +} + +static void ssi_clk_disable(struct clk *clk) +{ + struct ssi_internal_clk *ssi_clk = + container_of(clk, struct ssi_internal_clk, clk); + + int i; + + if (ssi_clk->clk.usecount == 0) + set_ssi_mode(ssi_clk->pdev, SSI_MODE_SLEEP); + + for (i = 0; i < ssi_clk->n_childs; i++) + omap2_clk_disable(ssi_clk->childs[i]); +} + +static struct ssi_internal_clk ssi_clock = { + .clk = { + .name = "ssi_clk", + .id = -1, + .enable = ssi_clk_enable, + .disable = ssi_clk_disable, + }, + .pdev = &ssi_pdev, +}; + +void __init ssi_init(void) +{ + int err; + + ssi_clk_init(&ssi_clock); + clk_register(&ssi_clock.clk); + + err = platform_device_register(&ssi_pdev); + if (err < 0) + pr_err("Unable to register SSI platform device: %d\n", err); +} +#endif diff --git a/Documentation/arm/OMAP/ssi/ssi b/Documentation/arm/OMAP/ssi/ssi new file mode 100644 index 0000000..990ae48 --- /dev/null +++ b/Documentation/arm/OMAP/ssi/ssi @@ -0,0 +1,232 @@ +OMAP SSI API's How To +===================== + +The Synchronous Serial Interface (SSI) is a high speed communication interface +that is used for connecting OMAP to a cellular modem engine. + +The SSI interface supports full duplex communication over multiple channels and +is capable of reaching speeds up to 110 Mbit/s + +I OMAP SSI driver API overview +----------------------------- + +A) SSI Bus, SSI channels and protocol drivers overview. + +The OMAP SSI driver is intended to be used inside the kernel by protocol drivers. + +The OMAP SSI abstracts the concept of SSI channels by creating an SSI bus an +attaching SSI channel devices to it.(see Figure 1) + +Protocol drivers will then claim one or more SSI channels, after registering with the OMAP SSI driver. + + +---------------------+ +----------------+ + + SSI channel device + + SSI protocol + + + (omap_ssi.pX-cY) + <-------+ driver + + +---------------------+ +----------------+ + | | +(/sys/bus/ssi/devices/omap_ssi.pX-cy) (/sys/bus/ssi/drivers/ssi_protocol) + | | ++---------------------------------------------------------------+ ++ SSI bus + ++---------------------------------------------------------------+ + + Figure 1. + +(NOTE: omap_ssi.pX-cY represents the SSI channel Y on port X from the omap_ssi +device) + +B) Data transfers + +The OMAP SSI driver exports an asynchronous interface for sending and receiving +data over the SSI channels. Protocol drivers will register a set of read and write +completion callbacks for each SSI channel they use. + +Protocol drivers call ssi_write/ssi_read functions to signal the OMAP SSI driver +that is willing to write/read data to/from a channel. Transfers are completed only +when the OMAP SSI driver calls the completion callback. + +An SSI channel can simultaneously have both a read and a write request +pending, however, requests cannot be queued. + +It is safe to call ssi_write/ssi_read functions inside the callbacks functions. +In fact, a protocol driver should normally re-issue the read request from within +the read callback, in order to not miss any incoming messages. + +C) Error handling + +SSI is a multi channel interface but the channels share the same physical wires. +Therefore, any transmission error potentially affects all the protocol drivers +that sit on top of the SSI driver. Whenever an error occurs, it is broadcasted to +all protocol drivers. + +Errors are signaled to the protocol drivers through the port_event callback. +Protocol drivers can avoid receiving those notifications by not setting the +SSI_EVENT_ERROR in the event_mask field.(see struct ssi_device_driver) + +Completion callbacks functions are only called when a transfer has succeed. + +II OMAP SSI API's +----------------- + +A) Include + +#include<linux/ssi_driver_if.h> + +B) int register_ssi_driver(struct ssi_device_driver *driver); + +Description: Register an SSI protocol driver + +Parameter: A protocol driver declaration (see struct ssi_device_driver) + +B) void unregister_ssi_driver(struct ssi_device_driver *driver); + +Description: Unregister an SSI protocol driver + +Parameter: A protocol driver declaration (see struct ssi_device_driver) + +C) int ssi_open(struct ssi_device *dev); + +Description: Open an SSI device channel + +Parameter: The SSI channel + +D) int ssi_write(struct ssi_device *dev, u32 *data, unsigned int count); + +Description: Send data through an SSI channel. The transfer is only completed +when the write_complete callback is called + +Parameters: + - dev: SSI channel + - data: pointer to the data to send + - count: number of 32-bit words to be sent + +E) void ssi_write_cancel(struct ssi_device *dev); + +Description: Cancel current pending write operation + +Parameters: SSI channel + +F) int ssi_read(struct ssi_device *dev, u32 *data, unsigned int w_count); + +Description: Receive data through an SSI channel. The transfer is only completed +when the read_complete callback is called + +Parameters: + - dev: SSI channel + - data: pointer where to store the data + - count: number of 32-bit words to be read + + +G) void ssi_read_cancel(struct ssi_device *dev); + +Description: Cancel current pending read operation + +Parameters: SSI channel + +H) int ssi_ioctl(struct ssi_device *dev, unsigned int command, void *arg); + +Description: Apply some control command to the port associated to the given +SSI channel + +Parameters: + - dev: SSI channel + - command: command to execute + - arg: parameter for the control command + +Commands: + - SSI_IOCTL_WAKE_UP: + Description: Set SSI wakeup line for the channel + Parameters: None + - SSI_IOCTL_WAKE_DOWN: + Description: Unset SSI wakeup line for the channel + Parameters: None + - SSI_IOCTL_SEND_BREAK: + Description: Send a HW BREAK frame in FRAME mode + Parameters: None + - SSI_IOCTL_WAKE: + Description: Get wakeup line status + Parameters: Pointer to a u32 variable to return result + (Result: 0 means wakeline DOWN, other result means wakeline UP) + +I)void ssi_close(struct ssi_device *dev); + +Description: Close an SSI channel + +Parameters: The SSI channel to close + +J) void ssi_dev_set_cb( struct ssi_device *dev, + void (*r_cb)(struct ssi_device *dev), + void (*w_cb)(struct ssi_device *dev)); + +Description: Set the read and write callbacks for the SSI channel. This +function is usually called in the probe function of the SSI protocol driver to +set completion callbacks for the asynchronous read and write transfer + +Parameters: + - dev: SSI channel + - r_cb: Pointer to a callback function to signal that a read transfer is + completed + - w_cb: Pointer to a callback function to signal that a write transfer + is completed + +H) struct ssi_device_driver + +Description: Protocol drivers pass this struct to the register_ssi_driver function +in order to register with the OMAP SSI driver. Among other things it tells the +OMAP SSI driver which channels the protocol driver wants to allocate for its use + +Declaration: +struct ssi_device_driver { + unsigned long ctrl_mask; + unsigned long ch_mask[SSI_MAX_PORTS]; + unsigned long event_mask; + void (*port_event) (int c_id, unsigned int port, + unsigned int event, void *arg); + int (*probe)(struct ssi_device *dev); + int (*remove)(struct ssi_device *dev); + int (*suspend)(struct ssi_device *dev, + pm_message_t mesg); + int (*resume)(struct ssi_device *dev); + struct device_driver driver; +}; + +Fields description: + ctrl_mask: SSI block ids to use + ch_mask[SSI_MAX_PORTS]: SSI channels to use + event_mask: SSI events to be notified + port_event: Function callback for notifying SSI events + (i.e.: error transfer) + Parameters: + c_id: SSI Block id which generate the event + port: Port number which generate the event + event: Event code + probe: Probe function + Parameters: SSI channel + remove: Remove function + Parameters: SSI channel + +Example: + +static struct ssi_device_driver ssi_protocol_driver = { + .ctrl_mask = ANY_SSI_CONTROLLER, + .ch_mask[0] = CHANNEL(0) | CHANNEL(1), + .event_mask = SSI_EVENT_ERROR_MASK, + .port_event = port_event_callback, + .probe = ssi_proto_probe, + .remove = __devexit_p(ssi_proto_remove), + .driver = { + .name = "ssi_protocol", + }, +}; + + +III OMAP SSI platform_device +---------------------------- + +You can find a example of how to define an SSI platform device in: + +Documentation/arm/OMAP/ssi/board-ssi.c.example + +================================================= +Contact: Carlos Chinea <carlos.chinea@nokia.com> +Copyright (C) 2008 Nokia Corporation. -- 1.5.3.6 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [RFC][PATCH 5/5] OMAP SSI API documentation 2008-10-03 11:52 ` [RFC][PATCH 5/5] OMAP SSI API documentation Carlos Chinea @ 2008-10-09 16:47 ` Felipe Balbi 0 siblings, 0 replies; 14+ messages in thread From: Felipe Balbi @ 2008-10-09 16:47 UTC (permalink / raw) To: Carlos Chinea; +Cc: linux-kernel, linux-omap On Fri, Oct 03, 2008 at 02:52:30PM +0300, Carlos Chinea wrote: there are issues in the documentation as well. > Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> > --- > Documentation/arm/OMAP/ssi/board-ssi.c.example | 216 ++++++++++++++++++++++ > Documentation/arm/OMAP/ssi/ssi | 232 ++++++++++++++++++++++++ > 2 files changed, 448 insertions(+), 0 deletions(-) > create mode 100644 Documentation/arm/OMAP/ssi/board-ssi.c.example > create mode 100644 Documentation/arm/OMAP/ssi/ssi > > diff --git a/Documentation/arm/OMAP/ssi/board-ssi.c.example b/Documentation/arm/OMAP/ssi/board-ssi.c.example > new file mode 100644 > index 0000000..a346628 > --- /dev/null > +++ b/Documentation/arm/OMAP/ssi/board-ssi.c.example > @@ -0,0 +1,216 @@ > +/* > + * board-ssi.c.example > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifdef CONFIG_OMAP_SSI don't ifdef here. This file should only be built if SSI is selected. > + > +#include <linux/platform_device.h> > +#include <linux/err.h> > +#include <linux/clk.h> > +#include <linux/io.h> > +#include <linux/ssi_driver_if.h> > +#include <mach/ssi/ssi_sys_reg.h> > +#include <mach/ssi/ssi_ssr_reg.h> > +#include <mach/ssi/ssi_sst_reg.h> you see how many includes you need to make it work ? It should be #include <linux/ssi.h> and that's all. This driver can be generic enough to build and work on non-omap platforms, can't it ? > + > +#include "clock.h" > + > +struct ssi_internal_clk { > + struct clk clk; > + struct clk **childs; > + int n_childs; > + struct platform_device *pdev; why do you need to let the internal clock know so much about the user ? I think struct device * should be enough here. > +}; > + > +static struct ssi_internal_clk ssi_clock; > + > +static void ssi_pdev_release(struct device *dev) > +{ > +} > + > +static struct ssi_port_pd ssi_ports[] = { > + [0] = { > + .tx_mode = SSI_MODE_FRAME, > + .tx_frame_size = SSI_FRAMESIZE_DEFAULT, > + .divisor = SSI_DIVISOR_DEFAULT, > + .tx_ch = SSI_CHANNELS_DEFAULT, > + .arb_mode = SSI_ARBMODE_ROUNDROBIN, > + .rx_mode = SSI_MODE_FRAME, > + .rx_frame_size = SSI_FRAMESIZE_DEFAULT, > + .rx_ch = SSI_CHANNELS_DEFAULT, > + .timeout = SSI_TIMEOUT_DEFAULT, > + .n_irq = 0, > + }, tabify these. > +}; > + > +static struct ssi_platform_data ssi_p_d = { > + .clk_name = "ssi_clk", > + .num_ports = ARRAY_SIZE(ssi_ports), > + .ports = ssi_ports, tabify > +}; > + > +static struct resource ssi_resources[] = { > + [0] = { > + .start = SSI_IOMEM_BASE_ADDR, > + .end = SSI_IOMEM_BASE_ADDR + SSI_IOMEM_SIZE, > + .name = SSI_IOMEM_NAME, > + .flags = IORESOURCE_MEM, > + }, tabify and the closing bracket should be aligned with [0] > + [1] = { ^ trailing whitespace > + .start = SSI_P1_MPU_IRQ0, > + .end = SSI_P1_MPU_IRQ0, > + .name = SSI_P1_MPU_IRQ0_NAME, > + .flags = IORESOURCE_IRQ, > + }, tabify tabify and the closing bracket should be aligned with [1] > + [2] = { ^ trailing whitespace > + .start = SSI_P1_MPU_IRQ1, > + .end = SSI_P1_MPU_IRQ1, > + .name = SSI_P1_MPU_IRQ1_NAME, > + .flags = IORESOURCE_IRQ, > + }, tabify tabify and the closing bracket should be aligned with [2] > + [3] = { ^ trailing whitespace > + .start = SSI_P2_MPU_IRQ0, > + .end = SSI_P2_MPU_IRQ0, > + .name = SSI_P2_MPU_IRQ0_NAME, > + .flags = IORESOURCE_IRQ, > + }, tabify tabify and the closing bracket should be aligned with [3] > + [4] = { ^ trailing whitespace > + .start = SSI_P2_MPU_IRQ1, > + .end = SSI_P2_MPU_IRQ1, > + .name = SSI_P2_MPU_IRQ1_NAME, > + .flags = IORESOURCE_IRQ, > + }, tabify tabify and the closing bracket should be aligned with [4] > + [5] = { ^ trailing whitespace > + .start = SSI_GDD_MPU_IRQ, > + .end = SSI_GDD_MPU_IRQ, > + .name = SSI_GDD_MPU_IRQ_NAME, > + .flags = IORESOURCE_IRQ, > + }, tabify and the closing bracket should be aligned with [5] > +}; > + > +static struct platform_device ssi_pdev = { > + .name = "omap_ssi", > + .id = -1, > + .num_resources = ARRAY_SIZE(ssi_resources), > + .resource = ssi_resources, > + .dev = { ^ trailing whitespace > + .release = ssi_pdev_release, > + .platform_data = &ssi_p_d, > + }, this bracket should be aligned with .dev > +}; > + > +static void set_ssi_mode(struct platform_device *pdev, u32 mode) > +{ > + void __iomem *base = (void __iomem *)pdev->resource[0].start; this is wrong, looks like mode sould be passed down to the driver and the driver would set_ssi_mode(). > + int port; > + int num_ports = ((struct ssi_platform_data *) the cast is unnecessary and you're abusing platform_data, I'd say. > + (pdev->dev.platform_data))->num_ports; > + > + for (port = 0; port < num_ports; port++) { > + outl(mode, OMAP2_IO_ADDRESS(base + SSI_SST_MODE_REG(port))); > + outl(mode, OMAP2_IO_ADDRESS(base + SSI_SSR_MODE_REG(port))); > + } > +} > + > +static int ssi_clk_init(struct ssi_internal_clk *ssi_clk) > +{ > + const char *clk_names[] = { "ssi_ick", "ssi_ssr_fck" }; > + int i; > + int j; > + > + ssi_clk->n_childs = ARRAY_SIZE(clk_names); > + ssi_clk->childs = kzalloc(ssi_clk->n_childs * sizeof(*ssi_clk->childs), > + GFP_KERNEL); > + if (!ssi_clk->childs) > + return -ENOMEM; > + > + for (i = 0; i < ssi_clk->n_childs; i++) { > + ssi_clk->childs[i] = clk_get(NULL, clk_names[i]); > + if (IS_ERR(ssi_clk->childs[i])) { > + pr_err("Unable to get SSI clock: %s", clk_names[i]); > + for (j = i - 1; j >= 0; j--) > + clk_put(ssi_clk->childs[j]); > + return -ENODEV; > + } > + } this looks like it should be done by the driver and not board-specific. > + > + return 0; > +} > + > +static int ssi_clk_enable(struct clk *clk) > +{ > + struct ssi_internal_clk *ssi_clk = > + container_of(clk, struct ssi_internal_clk, clk); > + int err = 0; > + int i; > + int j; > + > + for (i = 0; ((i < ssi_clk->n_childs) && (err >= 0)); i++) > + err = omap2_clk_enable(ssi_clk->childs[i]); > + > + if (unlikely(err < 0)) { > + pr_err("Error on SSI clk %d\n", i); > + for (j = i - 1; j >= 0; j--) > + omap2_clk_disable(ssi_clk->childs[j]); if you get and error, return already, will avoid the else below. > + } else { > + if (ssi_clk->clk.usecount == 1) > + set_ssi_mode(ssi_clk->pdev, SSI_MODE_FRAME); > + } > + > + return err; > +} > + > +static void ssi_clk_disable(struct clk *clk) > +{ > + struct ssi_internal_clk *ssi_clk = > + container_of(clk, struct ssi_internal_clk, clk); > + > + int i; > + > + if (ssi_clk->clk.usecount == 0) > + set_ssi_mode(ssi_clk->pdev, SSI_MODE_SLEEP); > + > + for (i = 0; i < ssi_clk->n_childs; i++) > + omap2_clk_disable(ssi_clk->childs[i]); > +} > + > +static struct ssi_internal_clk ssi_clock = { > + .clk = { > + .name = "ssi_clk", > + .id = -1, > + .enable = ssi_clk_enable, > + .disable = ssi_clk_disable, > + }, > + .pdev = &ssi_pdev, > +}; it doesn't look like you do anything useful with this ssi_internal_clk structure. I'd say you can move the clk definition to <mach/clock.h> if Tony, Paul and Kevin are ok with it. > + > +void __init ssi_init(void) > +{ > + int err; > + > + ssi_clk_init(&ssi_clock); > + clk_register(&ssi_clock.clk); > + > + err = platform_device_register(&ssi_pdev); > + if (err < 0) > + pr_err("Unable to register SSI platform device: %d\n", err); if the clk definition can be moved to <mach/clock.h> then you can simply: return platform_device_register(&ssi_pdev); > +} > +#endif > diff --git a/Documentation/arm/OMAP/ssi/ssi b/Documentation/arm/OMAP/ssi/ssi > new file mode 100644 > index 0000000..990ae48 > --- /dev/null > +++ b/Documentation/arm/OMAP/ssi/ssi let's use an extension to this file: ssi.txt > @@ -0,0 +1,232 @@ > +OMAP SSI API's How To > +===================== > + > +The Synchronous Serial Interface (SSI) is a high speed communication interface > +that is used for connecting OMAP to a cellular modem engine. > + > +The SSI interface supports full duplex communication over multiple channels and > +is capable of reaching speeds up to 110 Mbit/s > + > +I OMAP SSI driver API overview > +----------------------------- > + > +A) SSI Bus, SSI channels and protocol drivers overview. ^ trailing whitespace > + > +The OMAP SSI driver is intended to be used inside the kernel by protocol drivers. > + > +The OMAP SSI abstracts the concept of SSI channels by creating an SSI bus an s/an/and (at the end) > +attaching SSI channel devices to it.(see Figure 1) ^ add a space > + > +Protocol drivers will then claim one or more SSI channels, after registering with the OMAP SSI driver. > + > + +---------------------+ +----------------+ > + + SSI channel device + + SSI protocol + > + + (omap_ssi.pX-cY) + <-------+ driver + > + +---------------------+ +----------------+ > + | | > +(/sys/bus/ssi/devices/omap_ssi.pX-cy) (/sys/bus/ssi/drivers/ssi_protocol) > + | | > ++---------------------------------------------------------------+ > ++ SSI bus + > ++---------------------------------------------------------------+ > + > + Figure 1. > + > +(NOTE: omap_ssi.pX-cY represents the SSI channel Y on port X from the omap_ssi > +device) you could go simpler and call it: /sys/bus/ssi/devices/X-Y: (X == port, Y == channel); > + > +B) Data transfers > + > +The OMAP SSI driver exports an asynchronous interface for sending and receiving > +data over the SSI channels. Protocol drivers will register a set of read and write > +completion callbacks for each SSI channel they use. > + > +Protocol drivers call ssi_write/ssi_read functions to signal the OMAP SSI driver > +that is willing to write/read data to/from a channel. Transfers are completed only > +when the OMAP SSI driver calls the completion callback. > + > +An SSI channel can simultaneously have both a read and a write request > +pending, however, requests cannot be queued. > + > +It is safe to call ssi_write/ssi_read functions inside the callbacks functions. > +In fact, a protocol driver should normally re-issue the read request from within > +the read callback, in order to not miss any incoming messages. > + > +C) Error handling > + > +SSI is a multi channel interface but the channels share the same physical wires. > +Therefore, any transmission error potentially affects all the protocol drivers > +that sit on top of the SSI driver. Whenever an error occurs, it is broadcasted to > +all protocol drivers. > + > +Errors are signaled to the protocol drivers through the port_event callback. > +Protocol drivers can avoid receiving those notifications by not setting the > +SSI_EVENT_ERROR in the event_mask field.(see struct ssi_device_driver) > + > +Completion callbacks functions are only called when a transfer has succeed. > + > +II OMAP SSI API's > +----------------- > + > +A) Include > + > +#include<linux/ssi_driver_if.h> > + > +B) int register_ssi_driver(struct ssi_device_driver *driver); > + > +Description: Register an SSI protocol driver > + > +Parameter: A protocol driver declaration (see struct ssi_device_driver) > + > +B) void unregister_ssi_driver(struct ssi_device_driver *driver); > + > +Description: Unregister an SSI protocol driver > + > +Parameter: A protocol driver declaration (see struct ssi_device_driver) > + > +C) int ssi_open(struct ssi_device *dev); > + > +Description: Open an SSI device channel > + > +Parameter: The SSI channel > + > +D) int ssi_write(struct ssi_device *dev, u32 *data, unsigned int count); > + > +Description: Send data through an SSI channel. The transfer is only completed > +when the write_complete callback is called > + > +Parameters: > + - dev: SSI channel > + - data: pointer to the data to send > + - count: number of 32-bit words to be sent > + > +E) void ssi_write_cancel(struct ssi_device *dev); > + > +Description: Cancel current pending write operation > + > +Parameters: SSI channel > + > +F) int ssi_read(struct ssi_device *dev, u32 *data, unsigned int w_count); > + > +Description: Receive data through an SSI channel. The transfer is only completed > +when the read_complete callback is called > + > +Parameters: > + - dev: SSI channel > + - data: pointer where to store the data > + - count: number of 32-bit words to be read > + > + > +G) void ssi_read_cancel(struct ssi_device *dev); > + > +Description: Cancel current pending read operation > + > +Parameters: SSI channel > + > +H) int ssi_ioctl(struct ssi_device *dev, unsigned int command, void *arg); > + > +Description: Apply some control command to the port associated to the given > +SSI channel > + > +Parameters: > + - dev: SSI channel > + - command: command to execute > + - arg: parameter for the control command > + > +Commands: > + - SSI_IOCTL_WAKE_UP: > + Description: Set SSI wakeup line for the channel > + Parameters: None > + - SSI_IOCTL_WAKE_DOWN: > + Description: Unset SSI wakeup line for the channel > + Parameters: None > + - SSI_IOCTL_SEND_BREAK: > + Description: Send a HW BREAK frame in FRAME mode > + Parameters: None > + - SSI_IOCTL_WAKE: > + Description: Get wakeup line status > + Parameters: Pointer to a u32 variable to return result > + (Result: 0 means wakeline DOWN, other result means wakeline UP) > + > +I)void ssi_close(struct ssi_device *dev); > + > +Description: Close an SSI channel > + > +Parameters: The SSI channel to close > + > +J) void ssi_dev_set_cb( struct ssi_device *dev, > + void (*r_cb)(struct ssi_device *dev), > + void (*w_cb)(struct ssi_device *dev)); > + > +Description: Set the read and write callbacks for the SSI channel. This > +function is usually called in the probe function of the SSI protocol driver to > +set completion callbacks for the asynchronous read and write transfer > + > +Parameters: > + - dev: SSI channel > + - r_cb: Pointer to a callback function to signal that a read transfer is > + completed > + - w_cb: Pointer to a callback function to signal that a write transfer > + is completed > + > +H) struct ssi_device_driver > + > +Description: Protocol drivers pass this struct to the register_ssi_driver function > +in order to register with the OMAP SSI driver. Among other things it tells the > +OMAP SSI driver which channels the protocol driver wants to allocate for its use > + > +Declaration: > +struct ssi_device_driver { > + unsigned long ctrl_mask; > + unsigned long ch_mask[SSI_MAX_PORTS]; > + unsigned long event_mask; > + void (*port_event) (int c_id, unsigned int port, > + unsigned int event, void *arg); > + int (*probe)(struct ssi_device *dev); > + int (*remove)(struct ssi_device *dev); > + int (*suspend)(struct ssi_device *dev, > + pm_message_t mesg); > + int (*resume)(struct ssi_device *dev); > + struct device_driver driver; > +}; > + > +Fields description: > + ctrl_mask: SSI block ids to use > + ch_mask[SSI_MAX_PORTS]: SSI channels to use > + event_mask: SSI events to be notified > + port_event: Function callback for notifying SSI events > + (i.e.: error transfer) > + Parameters: > + c_id: SSI Block id which generate the event > + port: Port number which generate the event > + event: Event code > + probe: Probe function > + Parameters: SSI channel > + remove: Remove function > + Parameters: SSI channel > + > +Example: > + > +static struct ssi_device_driver ssi_protocol_driver = { > + .ctrl_mask = ANY_SSI_CONTROLLER, > + .ch_mask[0] = CHANNEL(0) | CHANNEL(1), > + .event_mask = SSI_EVENT_ERROR_MASK, > + .port_event = port_event_callback, > + .probe = ssi_proto_probe, > + .remove = __devexit_p(ssi_proto_remove), > + .driver = { > + .name = "ssi_protocol", > + }, > +}; > + > + > +III OMAP SSI platform_device > +---------------------------- > + > +You can find a example of how to define an SSI platform device in: > + > +Documentation/arm/OMAP/ssi/board-ssi.c.example > + > +================================================= > +Contact: Carlos Chinea <carlos.chinea@nokia.com> > +Copyright (C) 2008 Nokia Corporation. > -- > 1.5.3.6 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-omap" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- balbi ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC][PATCH 4/5] OMAP SSI integration into misc drivers 2008-10-03 11:52 ` [RFC][PATCH 4/5] OMAP SSI integration into misc drivers Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 5/5] OMAP SSI API documentation Carlos Chinea @ 2008-10-07 0:08 ` Felipe Balbi 1 sibling, 0 replies; 14+ messages in thread From: Felipe Balbi @ 2008-10-07 0:08 UTC (permalink / raw) To: Carlos Chinea; +Cc: linux-kernel, linux-omap On Fri, Oct 03, 2008 at 02:52:29PM +0300, Carlos Chinea wrote: > > Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> > --- > drivers/misc/Kconfig | 2 ++ > drivers/misc/Makefile | 1 + > 2 files changed, 3 insertions(+), 0 deletions(-) > > diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig > index f0202ee..b09dc68 100644 > --- a/drivers/misc/Kconfig > +++ b/drivers/misc/Kconfig > @@ -488,4 +488,6 @@ config SGI_GRU_DEBUG > This option enables addition debugging code for the SGI GRU driver. If > you are unsure, say N. > > +source "drivers/misc/ssi/Kconfig" > + > endif # MISC_DEVICES > diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile > index b6167e7..39e153d 100644 > --- a/drivers/misc/Makefile > +++ b/drivers/misc/Makefile > @@ -31,3 +31,4 @@ obj-$(CONFIG_KGDB_TESTS) += kgdbts.o > obj-$(CONFIG_SGI_XP) += sgi-xp/ > obj-$(CONFIG_SGI_GRU) += sgi-gru/ > obj-$(CONFIG_HP_ILO) += hpilo.o > +obj-$(CONFIG_OMAP_SSI) += ssi/ I believe the whole ssi should sit under drivers/ssi -- balbi ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC][PATCH 3/5] OMAP SSI driver code 2008-10-03 11:52 ` [RFC][PATCH 3/5] OMAP SSI driver code Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 4/5] OMAP SSI integration into misc drivers Carlos Chinea @ 2008-10-07 0:03 ` Felipe Balbi 2008-10-07 22:01 ` Felipe Balbi 1 sibling, 1 reply; 14+ messages in thread From: Felipe Balbi @ 2008-10-07 0:03 UTC (permalink / raw) To: Carlos Chinea; +Cc: linux-kernel, linux-omap On Fri, Oct 03, 2008 at 02:52:28PM +0300, Carlos Chinea wrote: Description. This seems to repeat in all your patches, take a look at linux kernel patch format: http://linux.yyz.us/patch-format.html > Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> > --- > drivers/misc/ssi/Kconfig | 11 + > drivers/misc/ssi/Makefile | 7 + > drivers/misc/ssi/ssi_driver.c | 513 +++++++++++++++++++++++++++++++++++++ > drivers/misc/ssi/ssi_driver.h | 211 +++++++++++++++ > drivers/misc/ssi/ssi_driver_bus.c | 192 ++++++++++++++ > drivers/misc/ssi/ssi_driver_dma.c | 406 +++++++++++++++++++++++++++++ > drivers/misc/ssi/ssi_driver_if.c | 335 ++++++++++++++++++++++++ > drivers/misc/ssi/ssi_driver_int.c | 232 +++++++++++++++++ > 8 files changed, 1907 insertions(+), 0 deletions(-) > create mode 100644 drivers/misc/ssi/Kconfig > create mode 100644 drivers/misc/ssi/Makefile > create mode 100644 drivers/misc/ssi/ssi_driver.c > create mode 100644 drivers/misc/ssi/ssi_driver.h > create mode 100644 drivers/misc/ssi/ssi_driver_bus.c > create mode 100644 drivers/misc/ssi/ssi_driver_dma.c > create mode 100644 drivers/misc/ssi/ssi_driver_if.c > create mode 100644 drivers/misc/ssi/ssi_driver_int.c > > diff --git a/drivers/misc/ssi/Kconfig b/drivers/misc/ssi/Kconfig > new file mode 100644 > index 0000000..5e0842c > --- /dev/null > +++ b/drivers/misc/ssi/Kconfig > @@ -0,0 +1,11 @@ > +# > +# OMAP SSI HW kernel configuration > +# > +config OMAP_SSI > + tristate "OMAP SSI hardware driver" > + depends on ARCH_OMAP > + default n > + ---help--- > + If you say Y here, you will enable the OMAP SSI hardware driver. > + > + If unsure, say N. > diff --git a/drivers/misc/ssi/Makefile b/drivers/misc/ssi/Makefile > new file mode 100644 > index 0000000..2b74e02 > --- /dev/null > +++ b/drivers/misc/ssi/Makefile > @@ -0,0 +1,7 @@ > +# > +# Makefile for SSI drivers > +# > +omap_ssi-objs := ssi_driver.o ssi_driver_dma.o ssi_driver_int.o \ ^ trailing whitespace > + ssi_driver_if.o ssi_driver_bus.o > + > +obj-$(CONFIG_OMAP_SSI) += omap_ssi.o > diff --git a/drivers/misc/ssi/ssi_driver.c b/drivers/misc/ssi/ssi_driver.c > new file mode 100644 > index 0000000..292e868 > --- /dev/null > +++ b/drivers/misc/ssi/ssi_driver.c > @@ -0,0 +1,513 @@ > +/* > + * ssi_driver.c > + * > + * Implements SSI module interface, initialization, and PM related functions. > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#include <linux/err.h> > +#include "ssi_driver.h" > + > +static void ssi_dev_release(struct device *dev) > +{ > +} > + > +static void __init ssi_undo_reg_dev(struct platform_device *pd, > + int p, int ch) Take a few tabs out of the second line, it'll look better. > +{ > + struct ssi_platform_data *p_data = normally we call it pdata, but it's your call in this case > + (struct ssi_platform_data *) pd->dev.platform_data; unnecessary cast. Remove > + struct ssi_port *ssi_p; > + int port; > + int channel; > + > + for (port = p; port >= 0; port--) { > + ssi_p = &p_data->ssi_ctrl->ssi_port[port]; > + for (channel = ch; channel >= 0 ; channel--) > + device_unregister(&ssi_p->ssi_channel[channel].dev > + ->device); This function should be unnecessary. pdata usage is wrong. > + } > +} > + > +static int __init reg_ssi_dev_ch(struct platform_device *pd, > + unsigned int p, unsigned int ch) the following should be probe(). > +{ > + struct ssi_device *dev; > + struct ssi_platform_data *p_data = > + (struct ssi_platform_data *) pd->dev.platform_data; unnecessary cast, remove. > + > + dev = kzalloc(sizeof(*dev), GFP_KERNEL); > + if (!dev) > + return -ENOMEM; > + dev->n_ctrl = pd->id; > + dev->n_p = p; > + dev->n_ch = ch; > + dev->ch = &p_data->ssi_ctrl->ssi_port[p].ssi_channel[ch]; > + p_data->ssi_ctrl->ssi_port[p].ssi_channel[ch].dev = dev; pdata usage is wrong. It should be used to initialize a few fields in the device structure and then vanish. > + dev->device.bus = &ssi_bus_type; > + dev->device.parent = &pd->dev; > + dev->device.release = ssi_dev_release; > + if (dev->n_ctrl < 0) > + snprintf(dev->device.bus_id, sizeof(dev->device.bus_id), > + "omap_ssi-p%u.c%u", p, ch); > + else > + snprintf(dev->device.bus_id, sizeof(dev->device.bus_id), > + "omap_ssi%d-p%u.c%u", dev->n_ctrl, p, ch); > + > + return device_register(&dev->device); > +} > + > +static int __init register_ssi_devices(struct platform_device *pd) > +{ > + struct ssi_platform_data *p_data = > + (struct ssi_platform_data *) pd->dev.platform_data; unnecessary cast. Remove > + unsigned int n_ch = 0; > + int port; > + int ch = 0; > + int err = 0; > + > + for (port = 0; ((port < p_data->num_ports) && (err >= 0)); port++) { > + n_ch = max(p_data->ports[port].tx_ch, > + p_data->ports[port].rx_ch); > + for (ch = 0; ((ch < n_ch) && (err >= 0)); ch++) > + err = reg_ssi_dev_ch(pd, port, ch); > + } > + > + if (err < 0) { > + port--; > + ch--; > + dev_err(&pd->dev, "Error registering ssi device channel " > + "p%d-c%d : %d\n", port, ch, err); > + if ((port == 0) && (ch == 0)) > + return err; > + else if ((port > 0) && (ch == 0)) > + ch = n_ch; > + ssi_undo_reg_dev(pd, port, ch); > + } > + return err; > +} > + > +static int __init ssi_softreset(struct ssi_dev *ssi_ctrl) > +{ > + int ind = 0; > + void __iomem *base = ssi_ctrl->base; > + u32 status; > + > + ssi_outl_or(SSI_SOFTRESET, base, SSI_SYS_SYSCONFIG_REG); > + > + status = ssi_inl(base, SSI_SYS_SYSSTATUS_REG); > + while ((!(status & SSI_RESETDONE)) && (ind < SSI_RESETDONE_RETRIES)) { > + set_current_state(TASK_UNINTERRUPTIBLE); > + schedule_timeout(msecs_to_jiffies(SSI_RESETDONE_TIMEOUT)); > + status = ssi_inl(base, SSI_SYS_SYSSTATUS_REG); > + ind++; > + } > + > + if (ind >= SSI_RESETDONE_RETRIES) > + return -EIO; > + > + /* Reseting GDD */ > + ssi_outl_or(SSI_SWRESET, base, SSI_GDD_GRST_REG); > + > + return 0; > +} > + > +static void __init set_ssi_ports_default( > + struct platform_device *pd) > +{ > + struct ssi_port_pd *cfg = NULL; > + struct ssi_platform_data *p_data = > + (struct ssi_platform_data *) pd->dev.platform_data; > + unsigned int port = 0; > + void __iomem *base = p_data->ssi_ctrl->base; is this really a virtual address?? then can you use __raw_{read,write}[blw]() and drop ssi-specific read/write functions? btw, the base address shouldn't come via pdata, it should come via struct resource and get ioremap:ed in the driver. Recently we fixed all the physical/virtual address mess and if we accept this it'll lead to similar issues. Please fix. > + > + for (port = 1; port <= p_data->num_ports; port++) { > + cfg = &p_data->ports[port - 1]; > + ssi_outl(cfg->tx_mode, base, SSI_SST_MODE_REG(port)); > + ssi_outl(cfg->tx_frame_size, base, SSI_SST_FRAMESIZE_REG(port)); > + ssi_outl(cfg->divisor, base, SSI_SST_DIVISOR_REG(port)); > + ssi_outl(cfg->tx_ch, base, SSI_SST_CHANNELS_REG(port)); > + ssi_outl(cfg->arb_mode, base, SSI_SST_ARBMODE_REG(port)); > + > + ssi_outl(cfg->rx_mode, base, SSI_SSR_MODE_REG(port)); > + ssi_outl(cfg->rx_frame_size, base, SSI_SSR_FRAMESIZE_REG(port)); > + ssi_outl(cfg->rx_ch, base, SSI_SSR_CHANNELS_REG(port)); > + ssi_outl(cfg->timeout, base, SSI_SSR_TIMEOUT_REG(port)); > + } > +} > + > +static int __init ssi_port_channels_init( > + struct platform_device *pd, unsigned int lport) > +{ > + struct ssi_platform_data *p_data = > + (struct ssi_platform_data *) pd->dev.platform_data; > + struct ssi_channel *ch; > + struct ssi_port *port; > + unsigned int n_ch; > + unsigned int ch_i; > + > + n_ch = max(p_data->ports[lport].tx_ch, p_data->ports[lport].rx_ch); > + port = &p_data->ssi_ctrl->ssi_port[lport]; > + for (ch_i = 0; ch_i < n_ch; ch_i++) { > + ch = &port->ssi_channel[ch_i]; > + ch->channel_number = ch_i; > + ch->flags = 0; > + ch->ssi_port = port; > + ch->read_data.addr = NULL; > + ch->read_data.size = 0; > + ch->read_data.lch = -1; > + ch->write_data.addr = NULL; > + ch->write_data.size = 0; > + ch->write_data.lch = -1; > + spin_lock_init(&ch->ssi_ch_lock); > + } > + > + return 0; > +} > + > +static int __init ssi_ports_init(struct platform_device *pd) > +{ > + struct ssi_platform_data *p_data = > + (struct ssi_platform_data *) pd->dev.platform_data; unnecessary cast. Remove > + struct ssi_port *ssi_p = NULL; > + unsigned int port = 0; > + unsigned int err = 0; > + unsigned int n_ports; > + > + n_ports = min(p_data->num_ports, (u8)SSI_MAX_PORTS); > + > + for (port = 0; ((port < n_ports) && (err >= 0)); port++) { > + ssi_p = &p_data->ssi_ctrl->ssi_port[port]; > + ssi_p->port_number = port + 1; > + ssi_p->ssi_controller = p_data->ssi_ctrl; > + ssi_p->max_ch = max(p_data->ports[port].tx_ch, > + p_data->ports[port].rx_ch); > + ssi_p->max_ch = min(ssi_p->max_ch, (u8)SSI_PORT_MAX_CH); > + ssi_p->n_irq = p_data->ports[port].n_irq; > + ssi_p->irq = 0; > + spin_lock_init(&ssi_p->lock); > + err = ssi_port_channels_init(pd, port); > + } > + > + return 0; > +} > + > +static int __init ssi_controller_init(struct platform_device *pd) > +{ > + struct ssi_platform_data *p_data = > + (struct ssi_platform_data *) pd->dev.platform_data; > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > + int err; > + > + ssi_ctrl->id = pd->id; > + ssi_ctrl->max_p = p_data->num_ports; in the header file you have a define for the max_ports and here you use platform_data to initialize it. Quite weird. Where are you using that define ? > + ssi_ctrl->pdev = pd; do not save the entire pdev pointer, save only the dev pointer use platform_set_drvdata(pdev, ssi_ctrl); > + spin_lock_init(&ssi_ctrl->lock); > + ssi_ctrl->ssi_clk = clk_get(&pd->dev, p_data->clk_name); please don't. > + if (IS_ERR(ssi_ctrl->ssi_clk)) { > + dev_err(&pd->dev, "Unable to get SSI clocks"); > + return PTR_ERR(ssi_ctrl->ssi_clk); > + } > + > + err = ssi_ports_init(pd); > + if (err < 0) { > + dev_err(&pd->dev, "Error on ssi_controller initialization"); > + clk_put(ssi_ctrl->ssi_clk); > + return -EBUSY; > + } > + > + return 0; > +} > + > +static void ssi_controller_exit(struct platform_device *pd) > +{ > + struct ssi_platform_data *p_data = > + (struct ssi_platform_data *) pd->dev.platform_data; unnecessary cast, remove. > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > + > + p_data->ssi_ctrl = NULL; > + ssi_ctrl->pdev = NULL; > + clk_put(ssi_ctrl->ssi_clk); > + unnecessary extra white line, remove. > +} > + > +static int __init request_ssi_irqs(struct platform_device *pd) > +{ > + struct ssi_platform_data *p_data = pd->dev.platform_data; > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > + struct ssi_port *ssi_p; > + struct resource *mpu_irq; unnecessary if it's only to get the irq. > + int i; > + int j; > + int err = 0; > + > + for (i = 0; ((i < p_data->num_ports) && (!err)); i++) { > + mpu_irq = platform_get_resource(pd, IORESOURCE_IRQ, i*2); no > + if (!mpu_irq) { > + dev_err(&pd->dev, "SSI misses info for MPU IRQ" > + " on port %d", i + 1); > + err = -ENODEV; use a goto to create a nice error path, then you remove this else below. > + } else { > + ssi_p = &ssi_ctrl->ssi_port[i]; > + ssi_p->n_irq = 0; /* We only use one irq line */ > + ssi_p->irq = mpu_irq->start; ssi_p->irq = platform_get_irq(pdev, 0); also gets rid of the unnecessary mpu_irq. > + tasklet_init(&ssi_p->ssi_tasklet, > + do_ssi_tasklet, (unsigned long)ssi_p); > + err = request_irq(mpu_irq->start, ssi_mpu_handler, > + IRQF_DISABLED, mpu_irq->name, ssi_p); > + if (err < 0) { > + dev_err(&pd->dev, "FAILED request IRQ (%d)", > + mpu_irq->start); > + err = -EBUSY; > + } > + } > + } > + > + if (err < 0) { > + /* Let's free the reserved resources if there are any errors */ > + for (j = 0; (j > (i-1)); j++) { > + printk(KERN_DEBUG LOG_NAME "Free resources on port %d", > + j+1); > + ssi_p = &ssi_ctrl->ssi_port[i]; > + tasklet_disable(&ssi_p->ssi_tasklet); > + free_irq(ssi_p->irq, ssi_p); > + } > + } > + > + return err; > +} > + > +static int __init request_ssi_gdd_irq(struct platform_device *pd) > +{ > + struct ssi_platform_data *p_data = pd->dev.platform_data; > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > + struct resource *gdd_irq; no > + int err = 0; > + > + gdd_irq = platform_get_resource(pd, IORESOURCE_IRQ, 4); no > + if (!gdd_irq) { > + dev_err(&pd->dev, "SSI device has no info about GDD IRQ"); > + return -ENODEV; > + } > + > + ssi_ctrl->gdd_irq = gdd_irq->start; hell no. Use platform_get_irq() like I said before. > + err = request_irq(gdd_irq->start, ssi_gdd_mpu_handler, > + IRQF_DISABLED, gdd_irq->name, ssi_ctrl); > + if (err < 0) { > + dev_err(&pd->dev, "FAILED to request IRQ NUMBER (%d)", > + gdd_irq->start); > + return -EBUSY; > + } > + tasklet_init(&ssi_ctrl->ssi_gdd_tasklet, do_ssi_gdd_tasklet, > + (unsigned long)ssi_ctrl); > + > + return err; > +} > + > +static void free_ssi_irqs(struct ssi_dev *ssi_ctrl) > +{ > + struct ssi_port *ssi_p; > + int i; > + > + for (i = 0; (i < ssi_ctrl->max_p); i++) { > + ssi_p = &ssi_ctrl->ssi_port[i]; > + tasklet_disable(&ssi_p->ssi_tasklet); > + free_irq(ssi_p->irq, ssi_p); > + } > +} > + > +static void free_ssi_gdd_irq(struct ssi_dev *ssi_ctrl) > +{ > + tasklet_disable(&ssi_ctrl->ssi_gdd_tasklet); > + free_irq(ssi_ctrl->gdd_irq, ssi_ctrl); > +} > + > +static int __init ssi_probe(struct platform_device *pd) > +{ > + struct resource *mem, *ioarea; > + struct ssi_dev *ssi_ctrl = NULL; > + struct ssi_platform_data *p_data = NULL; > + u32 revision = 0; > + int err = 0; > + > + > + if ((pd == NULL) || (pd->dev.platform_data == NULL)) { > + pr_err(LOG_NAME "No device/platform_data found on " > + "ssi device\n"); > + return -ENODEV; > + } aff... pd will never be NULL if this driver is probing. I'd rather: struct ssi_platform_data *pdata = pd->dev.platform_data; if (!pdata) { error messages goes here } > + > + ssi_ctrl = kzalloc(sizeof(*ssi_ctrl), GFP_KERNEL); > + if (ssi_ctrl == NULL) { > + dev_err(&pd->dev, "Could not allocate memory for" > + " struct ssi_dev\n"); > + return -ENOMEM; > + } > + > + p_data = (struct ssi_platform_data *) pd->dev.platform_data; no cast needed. > + p_data->ssi_ctrl = ssi_ctrl; hell no. platform_set_drvdata(pd, ssi_ctrl); > + > + mem = platform_get_resource(pd, IORESOURCE_MEM, 0); > + if (!mem) { > + dev_err(&pd->dev, "SSI device does not have " > + "SSI IO memory region information\n"); > + err = -ENODEV; > + goto rback5; > + } > + > + err = ssi_controller_init(pd); > + if (err < 0) { > + dev_err(&pd->dev, "Could not initialize ssi controller:" > + " %d\n", err); > + goto rback5; > + } > + > + ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1, > + pd->dev.bus_id); > + if (!ioarea) { > + dev_err(&pd->dev, "Unable to request SSI IO memory " > + "region\n"); > + err = -EBUSY; > + goto rollback4; > + } > + > + ssi_ctrl->base = (void __iomem *)mem->start; afff... this is terrible. Use ioremap(); > + > + clk_enable(ssi_ctrl->ssi_clk); > + > + err = request_ssi_irqs(pd); > + if (err < 0) > + goto rollback1; > + > + err = request_ssi_gdd_irq(pd); > + if (err < 0) > + goto rollback2; > + > + err = ssi_softreset(ssi_ctrl); > + if (err < 0) > + goto rollback3; > + > + /* Set default PM settings */ > + ssi_outl((SSI_AUTOIDLE | SSI_SIDLEMODE_SMART | SSI_MIDLEMODE_SMART), > + ssi_ctrl->base, SSI_SYS_SYSCONFIG_REG); > + ssi_outl(SSI_CLK_AUTOGATING_ON, ssi_ctrl->base, SSI_GDD_GCR_REG); > + > + set_ssi_ports_default(pd); > + > + /* Gather info from registers for the driver.(REVISION) */ > + revision = ssi_inl(ssi_ctrl->base, SSI_SYS_REVISION_REG); > + dev_info(&pd->dev, "SSI Hardware REVISION %d.%d\n", > + (revision & SSI_REV_MAJOR) >> 4, (revision & SSI_REV_MINOR)); > + > + err = register_ssi_devices(pd); > + if (err < 0) > + goto rollback3; > + > + clk_disable(ssi_ctrl->ssi_clk); > + > + return err; > + > +rollback3: > + free_ssi_gdd_irq(ssi_ctrl); > +rollback2: > + free_ssi_irqs(ssi_ctrl); > +rollback1: add iounmap() here > + release_mem_region(mem->start, mem->end - mem->start + 1); > + clk_disable(ssi_ctrl->ssi_clk); > +rollback4: > + ssi_controller_exit(pd); > +rback5: > + kfree(ssi_ctrl); > + return err; > +} > + > +static void __exit close_all_ch(struct ssi_dev *ssi_ctrl) > +{ > + struct ssi_port *ssi_p; > + unsigned int port; > + unsigned int ch; > + > + for (port = 0; port < ssi_ctrl->max_p; port++) { > + ssi_p = &ssi_ctrl->ssi_port[port]; > + for (ch = 0; ch < ssi_p->max_ch; ch++) > + ssi_close(ssi_p->ssi_channel[ch].dev); > + } > +} > + > +static int __exit ssi_remove(struct platform_device *pd) > +{ > + struct resource *mem; > + struct ssi_platform_data *p_data = > + (struct ssi_platform_data *) pd->dev.platform_data; > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; This should ssi_ctrl = platform_get_drvdata(pd); > + > + close_all_ch(ssi_ctrl); > + free_ssi_gdd_irq(ssi_ctrl); > + free_ssi_irqs(ssi_ctrl); > + mem = platform_get_resource(pd, IORESOURCE_MEM, 0); > + if (mem) > + release_mem_region(mem->start, mem->end - mem->start + 1); when you add proper ioremap(), add iounmap here. > + ssi_controller_exit(pd); > + kfree(ssi_ctrl); > + > + return 0; > +} > + > +static struct platform_driver ssi_pdriver = { > + .probe = ssi_probe, > + .remove = __exit_p(ssi_remove), > + .driver = { > + .name = "omap_ssi", > + .owner = THIS_MODULE, > + } > +}; > + > +static int __init ssi_driver_init(void) > +{ > + int err = 0; > + > + pr_info("SSI DRIVER Version " SSI_DRIVER_VERSION "\n"); > + > + ssi_bus_init(); > + err = platform_driver_probe(&ssi_pdriver, ssi_probe); > + if (err < 0) { > + pr_err(LOG_NAME "Platform DRIVER register FAILED: %d\n", err); > + ssi_bus_exit(); > + return err; > + } > + > + return 0; return platform_driver_register(&ssi_pdriver); should be enough. > +} > + > +static void __exit ssi_driver_exit(void) > +{ > + ssi_bus_exit(); > + platform_driver_unregister(&ssi_pdriver); > + > + pr_info("SSI DRIVER removed\n"); > +} > + > +module_init(ssi_driver_init); > +module_exit(ssi_driver_exit); > + > +MODULE_ALIAS("platform:omap_ssi"); > +MODULE_AUTHOR(SSI_DRIVER_AUTHOR); > +MODULE_DESCRIPTION(SSI_DRIVER_DESC); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/misc/ssi/ssi_driver.h b/drivers/misc/ssi/ssi_driver.h > new file mode 100644 > index 0000000..3c6d849 > --- /dev/null > +++ b/drivers/misc/ssi/ssi_driver.h > @@ -0,0 +1,211 @@ > +/* > + * ssi_driver.h > + * > + * Header file for the SSI driver low level interface. > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifndef __SSI_DRIVER_H__ > +#define __SSI_DRIVER_H__ > + > +#include <linux/module.h> > +#include <linux/init.h> > +#include <linux/clk.h> > +#include <linux/ioport.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/spinlock.h> > +#include <linux/io.h> > +#include <linux/platform_device.h> > +#include <linux/gpio.h> > + > +#include <mach/ssi/ssi_reg_common.h> > +#include <mach/ssi/ssi_sys_reg.h> > +#include <mach/ssi/ssi_ssr_reg.h> > +#include <mach/ssi/ssi_sst_reg.h> > +#include <mach/ssi/ssi_gdd_reg.h> > + > +#include <linux/ssi_driver_if.h> > + > +#define SSI_DRIVER_VERSION "1.1-rc2" > +#define SSI_DRIVER_AUTHOR "Carlos Chinea / Nokia" > +#define SSI_DRIVER_DESC "Synchronous Serial Interface Driver" > +#define SSI_DRIVER_LINCESE "GPL" > +#define SSI_DRIVER_NAME "ssi_driver" > + > +#define SSI_DEVICE_NAME "ssi_device" these should be in the C-file. Nobody needs to access them > + > +/* 10 ms */ > +#define SSI_RESETDONE_TIMEOUT 10 > +/* Let's retry 20 times.=>20x10 ms=200 ms */ > +#define SSI_RESETDONE_RETRIES 20 > + > +/* Channel states */ > +#define SSI_CH_OPEN 0x01 > + > +/* > + * The number of channels to use by the driver in the ports, or the highest > + * port channel number (+1) used. (MAX:8) > + */ > +#define SSI_PORT_MAX_CH 4 > +/* Number of logical channel in GDD */ > +#define SSI_NUM_LCH 8 > + > +#define LOG_NAME "OMAP SSI: " > +#define SSI_PREFIX "ssi:" > + > +/* > + * Callbacks use by the SSI upper layer (SSI protocol) to receive data > + * from the port channels. > + */ > +struct ssi_channel_ops { > + void (*write_done) (struct ssi_device *dev); > + void (*read_done) (struct ssi_device *dev); > +}; > + > +struct ssi_data { > + /* Pointer to the data to be sent/received */ > + u32 *addr; > + /* > + * Number of words to be sent or space reserved for data to be > + * received. > + */ > + unsigned int size; > + /* Holds GDD logical channed number */ > + int lch; > +}; > + > +struct ssi_channel { > + struct ssi_channel_ops ops; > + struct ssi_data read_data; > + struct ssi_data write_data; > + struct ssi_port *ssi_port; > + u8 flags; > + u8 channel_number; > + spinlock_t ssi_ch_lock; > + struct ssi_device *dev; > + void *priv; > +}; > + > +/* Holds information related to SSI port */ > +struct ssi_port { > + struct ssi_channel ssi_channel[SSI_PORT_MAX_CH]; > + struct ssi_dev *ssi_controller; > + u8 port_number; > + u8 max_ch; > + u8 n_irq; /* IRQ0 or IRQ1 */ > + int irq /* Actual IRQ number */; > + spinlock_t lock; > + struct tasklet_struct ssi_tasklet; > +}; > + > +/* > + * Struct definition to hold information about the clocks, ssi controller > + * and the ssi ports. > + */ > +struct ssi_dev { > + /* Holds reference to PORT 1 (and PORT2 if defined) */ > + struct ssi_port ssi_port[SSI_MAX_PORTS]; > + int id; > + u8 flags; > + u8 max_p; > + struct clk *ssi_clk; > + void __iomem *base; > + spinlock_t lock; > + int gdd_irq; > + struct tasklet_struct ssi_gdd_tasklet; > + struct platform_device *pdev; > +}; > + > +/* SSI Bus */ > +struct ssi_port_event { > + struct ssi_port *ssi_port; > + unsigned int event; > + void *priv; > +}; > +extern struct bus_type ssi_bus_type; > + > +int ssi_port_event_handler(struct ssi_port *p, unsigned int event, void *arg); > +int ssi_bus_init(void); > +void ssi_bus_exit(void); > +/* End SSI Bus */ > + > +int ssi_driver_read_interrupt(struct ssi_channel *ssi_channel, u32 *data); > +int ssi_driver_write_interrupt(struct ssi_channel *ssi_channel, u32 *data); > +int ssi_driver_read_dma(struct ssi_channel *ssi_channel, u32 *data, > + unsigned int count); > +int ssi_driver_write_dma(struct ssi_channel *ssi_channel, u32 *data, > + unsigned int count); > + > +void ssi_driver_cancel_write_interrupt(struct ssi_channel *ch); > +void ssi_driver_cancel_read_interrupt(struct ssi_channel *ch); > +void ssi_driver_cancel_write_dma(struct ssi_channel *ch); > +void ssi_driver_cancel_read_dma(struct ssi_channel *ch); > + > +irqreturn_t ssi_mpu_handler(int irq, void *ssi_port); > +irqreturn_t ssi_gdd_mpu_handler(int irq, void *ssi_controller); > + > +void do_ssi_tasklet(unsigned long ssi_port); > +void do_ssi_gdd_tasklet(unsigned long device); > + > +static inline u32 ssi_inl(void __iomem *base, u32 offset) > +{ > + return inl(OMAP2_IO_ADDRESS(base + offset)); > +} > + > +static inline void ssi_outl(u32 data, void __iomem *base, u32 offset) > +{ > + outl(data, OMAP2_IO_ADDRESS(base + offset)); > +} > + > +static inline void ssi_outl_or(u32 data, void __iomem *base, u32 offset) > +{ > + u32 tmp = ssi_inl(base, offset); > + ssi_outl((tmp | data), base, offset); > +} > + > +static inline void ssi_outl_and(u32 data, void __iomem *base, u32 offset) > +{ > + u32 tmp = ssi_inl(base, offset); > + ssi_outl((tmp & data), base, offset); > +} > + > +static inline u16 ssi_inw(void __iomem *base, u32 offset) > +{ > + return inw(OMAP2_IO_ADDRESS(base + offset)); > +} > + > +static inline void ssi_outw(u16 data, void __iomem *base, u32 offset) > +{ > + outw(data, OMAP2_IO_ADDRESS(base + offset)); > +} > + > +static inline void ssi_outw_or(u16 data, void __iomem *base, u32 offset) > +{ > + u16 tmp = ssi_inw(base, offset); > + ssi_outw((tmp | data), base, offset); > +} > + > +static inline void ssi_outw_and(u16 data, void __iomem *base, u32 offset) > +{ > + u16 tmp = ssi_inw(base, offset); > + ssi_outw((tmp & data), base, offset); > +} > +#endif > diff --git a/drivers/misc/ssi/ssi_driver_bus.c b/drivers/misc/ssi/ssi_driver_bus.c > new file mode 100644 > index 0000000..6a07ee0 > --- /dev/null > +++ b/drivers/misc/ssi/ssi_driver_bus.c > @@ -0,0 +1,192 @@ > +/* > + * ssi_driver_bus.c > + * > + * Implements SSI bus, device and driver interface. > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > +#include <linux/device.h> > +#include "ssi_driver.h" > + > +/* LDM. defintions for the ssi bus, ssi device, and ssi_device driver */ > +struct bus_type ssi_bus_type; > + > +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, > + char *buf) > +{ > + return snprintf(buf, BUS_ID_SIZE + 1, "%s%s\n", SSI_PREFIX, > + dev->bus_id); > +} > + > +static struct device_attribute ssi_dev_attrs[] = { > + __ATTR_RO(modalias), > + __ATTR_NULL, > +}; > + > +static int ssi_bus_uevent(struct device *dev, struct kobj_uevent_env *env) > +{ > + add_uevent_var(env, "MODALIAS=%s%s", SSI_PREFIX, dev->bus_id); > + return 0; > +} > + > +/* NOTE: Function called in interrupt context */ > +static int ssi_e_handler(struct device_driver *drv, void *p_event) > +{ > + struct ssi_port_event *event = (struct ssi_port_event *)p_event; > + struct ssi_device_driver *ssi_drv = to_ssi_device_driver(drv); > + struct ssi_port *p = event->ssi_port; > + > + BUG_ON(p_event == NULL); > + > + if ((ssi_drv->port_event) && > + (test_bit(event->event, &ssi_drv->event_mask)) && > + ((p->ssi_controller->id == -1) || > + (test_bit(p->ssi_controller->id, &ssi_drv->ctrl_mask))) && > + (ssi_drv->ch_mask[p->port_number - 1] != 0)) { > + > + (*ssi_drv->port_event)(p->ssi_controller->id, p->port_number, > + event->event, event->priv); > + } > + > + return 0; > +} > + > +int ssi_port_event_handler(struct ssi_port *p, unsigned int event, void *arg) > +{ > + int err = 0; > + struct ssi_port_event p_ev = { > + .ssi_port = p, > + .event = event, > + .priv = arg > + }; > + > + BUG_ON(p == NULL); > + > + err = bus_for_each_drv(&ssi_bus_type, NULL, &p_ev, ssi_e_handler); > + > + return err; > +} > + > +static int ssi_bus_match(struct device *device, struct device_driver *driver) > +{ > + struct ssi_device *dev = to_ssi_device(device); > + struct ssi_device_driver *drv = to_ssi_device_driver(driver); > + > + if (!test_bit(dev->n_ctrl, &drv->ctrl_mask)) > + return 0; > + > + if (!test_bit(dev->n_ch, &drv->ch_mask[dev->n_p])) > + return 0; > + > + return 1; > +} > + > +int ssi_bus_unreg_dev(struct device *device, void *p) > +{ > + device->release(device); > + device_unregister(device); > + > + return 0; > +} > + > +int __init ssi_bus_init(void) > +{ > + > + return bus_register(&ssi_bus_type); > + > +} > + > +void ssi_bus_exit(void) > +{ > + bus_for_each_dev(&ssi_bus_type, NULL, NULL, ssi_bus_unreg_dev); > + bus_unregister(&ssi_bus_type); > +} > + > +static int ssi_driver_probe(struct device *dev) > +{ > + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); > + > + return drv->probe(to_ssi_device(dev)); ^ trailing whitespace > +} > + > +static int ssi_driver_remove(struct device *dev) > +{ > + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); > + > + return drv->remove(to_ssi_device(dev)); > +} > + > +static int ssi_driver_suspend(struct device *dev, pm_message_t mesg) > +{ > + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); > + > + return drv->suspend(to_ssi_device(dev), mesg); > +} > + > +static int ssi_driver_resume(struct device *dev) > +{ > + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); > + > + return drv->resume(to_ssi_device(dev)); > +} > + > +struct bus_type ssi_bus_type = { > + .name = "ssi", > + .dev_attrs = ssi_dev_attrs, > + .match = ssi_bus_match, > + .uevent = ssi_bus_uevent, tabify these .name = "ssi", .dev_attrs = ssi_dev_attrs, .match = ssi_bus_match, .uevent = ssi_bus_uevent, > +}; > + > +/** > + * register_ssi_driver - Register SSI device driver > + * @driver - reference to the SSI device driver. > + */ > +int register_ssi_driver(struct ssi_device_driver *driver) > +{ > + int ret = 0; > + > + BUG_ON(driver == NULL); don't bug, just return. BUG will oops the kernel. Returning would be more appropriate since what needs fix is the driver trying to register not the bus driver. > + > + driver->driver.bus = &ssi_bus_type; > + if (driver->probe) > + driver->driver.probe = ssi_driver_probe; > + if (driver->remove) > + driver->driver.remove = ssi_driver_remove; > + if (driver->suspend) > + driver->driver.suspend = ssi_driver_suspend; > + if (driver->resume) > + driver->driver.resume = ssi_driver_resume; > + > + ret = driver_register(&driver->driver); > + > + return ret; > +} > +EXPORT_SYMBOL(register_ssi_driver); > + > +/** > + * unregister_ssi_driver - Unregister SSI device driver > + * @driver - reference to the SSI device driver. > + */ > +void unregister_ssi_driver(struct ssi_device_driver *driver) > +{ > + BUG_ON(driver == NULL); ditto. > + > + driver_unregister(&driver->driver); > +} > +EXPORT_SYMBOL(unregister_ssi_driver); > diff --git a/drivers/misc/ssi/ssi_driver_dma.c b/drivers/misc/ssi/ssi_driver_dma.c > new file mode 100644 > index 0000000..2524a12 > --- /dev/null > +++ b/drivers/misc/ssi/ssi_driver_dma.c > @@ -0,0 +1,406 @@ > +/* > + * ssi_driver_dma.c > + * > + * Implements SSI low level interface driver functionality with DMA support. > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > +#include <linux/dma-mapping.h> > +#include "ssi_driver.h" > + > +#define SSI_SYNC_WRITE 0 > +#define SSI_SYNC_READ 1 ^^ trailing whitespaces > + > +static unsigned char sync_table[2][2][8] = { > + { > + {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, > + {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00} > + }, > + { }, { would look better > + {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}, > + {0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F} > + } > +}; > + > +static unsigned int get_sync_type(unsigned int sync) > +{ > + return (sync & 0x10) ? SSI_SYNC_READ : SSI_SYNC_WRITE; > +} > + > +static unsigned int get_sync_port(unsigned int sync) > +{ > + if (((sync >= 0x01) && (sync <= 0x08)) || > + ((sync >= 0x10) && (sync <= 0x17))) > + return 1; > + else if (((sync >= 0x09) && (sync <= 0x0F)) || > + ((sync >= 0x18) && (sync <= 0x1E))) > + return 2; > + else > + return 3; > +} > + > +static unsigned int get_sync_channel(unsigned int sync) > +{ > + if ((sync == 0x00) || (sync == 0x1F)) > + return 8; > + > + if (sync & 0x10) > + return (sync & 0x0F) % 8; > + else > + return (sync - 1) % 8; > +} > + > +static unsigned int get_sync(unsigned int port, > + unsigned int channel, unsigned int type) > +{ > + if ((port == 0) || (port > SSI_MAX_PORTS) || > + (channel >= SSI_PORT_MAX_CH) || (type > SSI_SYNC_READ)) > + return 0x00; > + > + return sync_table[type][port - 1][channel]; > +} > + > +static void rst_ch_read(struct ssi_dev *ssi_ctrl, > + unsigned int n_p, unsigned int n_ch) > +{ > + struct ssi_channel *ch = > + &ssi_ctrl->ssi_port[n_p - 1].ssi_channel[n_ch]; > + > + ch->read_data.addr = NULL; > + ch->read_data.size = 0; > + ch->read_data.lch = -1; > +} > + > +static void rst_ch_write(struct ssi_dev *ssi_ctrl, > + unsigned int n_p, unsigned int n_ch) > +{ > + struct ssi_channel *ch = > + &ssi_ctrl->ssi_port[n_p - 1].ssi_channel[n_ch]; > + > + ch->write_data.addr = NULL; > + ch->write_data.size = 0; > + ch->write_data.lch = -1; > +} > + > +/* > + * get_free_lch - Get a free GDD(DMA)logical channel > + * @ssi_ctrl- SSI controller of the GDD. > + * > + * Needs to be called holding the gdd controller lock > + */ > +static unsigned int get_free_lch(struct ssi_dev *ssi_ctrl) > +{ > + unsigned int enable_reg; > + unsigned int lch = 0; > + > + enable_reg = ssi_inl(ssi_ctrl->base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > + while ((lch < SSI_NUM_LCH) && (enable_reg & SSI_GDD_LCH(lch))) > + lch++; > + > + return lch; > +} > + > +/* > + * ssi_driver_write_dma - Program GDD [DMA] to write data from memory to > + * the ssi channel buffer. > + * @ssi_channel - pointer to the ssi_channel to write data to. > + * @data - 32-bit word pointer to the data. > + * @size - Number of 32bit words to be transfered. > + * > + * ssi_controller lock must be hold before calling this function. > + */ > +int ssi_driver_write_dma(struct ssi_channel *ssi_channel, u32 *data, > + unsigned int size) > +{ > + struct ssi_dev *ssi_ctrl = ssi_channel->ssi_port->ssi_controller; > + void __iomem *base = ssi_ctrl->base; > + unsigned int port = ssi_channel->ssi_port->port_number; > + unsigned int channel = ssi_channel->channel_number; > + unsigned int sync; > + int lch; > + dma_addr_t dma_data; > + u32 s_addr; > + u16 tmp; > + > + if ((size < 1) || (data == NULL)) > + return -EINVAL; > + > + clk_enable(ssi_ctrl->ssi_clk); > + > + lch = get_free_lch(ssi_ctrl); > + if (lch >= SSI_NUM_LCH) { > + dev_err(&ssi_ctrl->pdev->dev, "No free GDD logical " > + "channels.\n"); > + clk_disable(ssi_ctrl->ssi_clk); > + return -EBUSY; /* No free GDD logical channels. */ > + } > + /* NOTE: Gettting a free gdd logical channel and > + * reserve it must be done atomicaly. */ > + ssi_channel->write_data.lch = lch; > + > + sync = get_sync(port, channel, SSI_SYNC_WRITE); > + dma_data = dma_map_single(NULL, data, size * 4, DMA_TO_DEVICE); > + dma_sync_single(NULL, dma_data, size * 4, DMA_TO_DEVICE); > + > + tmp = SSI_SRC_SINGLE_ACCESS0 | > + SSI_SRC_MEMORY_PORT | > + SSI_DST_SINGLE_ACCESS0 | > + SSI_DST_PERIPHERAL_PORT | > + SSI_DATA_TYPE_S32; > + ssi_outw(tmp, base, SSI_GDD_CSDP_REG(lch)); > + tmp = SSI_SRC_AMODE_POSTINC | SSI_DST_AMODE_CONST | sync; > + ssi_outw(tmp, base, SSI_GDD_CCR_REG(lch)); > + ssi_outw((SSI_BLOCK_IE | SSI_TOUT_IE), base, SSI_GDD_CICR_REG(lch)); > + s_addr = (u32)base + SSI_SST_BUFFER_CH_REG(port, channel); > + ssi_outl(s_addr, base, SSI_GDD_CDSA_REG(lch)); > + ssi_outl(dma_data, base, SSI_GDD_CSSA_REG(lch)); > + ssi_outw(size, base, SSI_GDD_CEN_REG(lch)); > + ssi_outl_or(SSI_GDD_LCH(lch), base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > + ssi_outw_or(SSI_CCR_ENABLE, base, SSI_GDD_CCR_REG(lch)); > + > + return 0; > +} > + > +/* > + * ssi_driver_read_dma - Program GDD [DMA] to write data to memory from > + * the ssi channel buffer. > + * @ssi_channel - pointer to the ssi_channel to read data from. > + * @data - 32-bit word pointer where to store the incoming data. > + * @size - Number of 32bit words to be transfered to the buffer. > + * > + * ssi_controller lock must be hold before calling this function. > + */ > +int ssi_driver_read_dma(struct ssi_channel *ssi_channel, u32 *data, > + unsigned int count) > +{ > + struct ssi_dev *ssi_ctrl = ssi_channel->ssi_port->ssi_controller; > + void __iomem *base = ssi_ctrl->base; > + unsigned int port = ssi_channel->ssi_port->port_number; > + unsigned int channel = ssi_channel->channel_number; > + unsigned int sync; > + unsigned int lch; > + dma_addr_t dma_data; > + u32 d_addr; > + u16 tmp; > + > + clk_enable(ssi_ctrl->ssi_clk); > + lch = get_free_lch(ssi_ctrl); > + if (lch >= SSI_NUM_LCH) { > + dev_err(&ssi_ctrl->pdev->dev, "No free GDD logical " > + "channels.\n"); > + clk_disable(ssi_ctrl->ssi_clk); > + return -EBUSY; /* No free GDD logical channels. */ > + } > + /* > + * NOTE: Gettting a free gdd logical channel and > + * reserve it must be done atomicaly. > + */ > + ssi_channel->read_data.lch = lch; > + > + sync = get_sync(port, channel, SSI_SYNC_READ); > + > + dma_data = dma_map_single(NULL, data, count * 4, DMA_FROM_DEVICE); > + > + tmp = SSI_DST_SINGLE_ACCESS0 | > + SSI_DST_MEMORY_PORT | > + SSI_SRC_SINGLE_ACCESS0 | > + SSI_SRC_PERIPHERAL_PORT | > + SSI_DATA_TYPE_S32; > + ssi_outw(tmp, base, SSI_GDD_CSDP_REG(lch)); > + tmp = SSI_DST_AMODE_POSTINC | SSI_SRC_AMODE_CONST | sync; > + ssi_outw(tmp, base, SSI_GDD_CCR_REG(lch)); > + ssi_outw((SSI_BLOCK_IE | SSI_TOUT_IE), base, SSI_GDD_CICR_REG(lch)); > + d_addr = (u32)base + SSI_SSR_BUFFER_CH_REG(port, channel); > + ssi_outl(d_addr, base, SSI_GDD_CSSA_REG(lch)); > + ssi_outl((u32)dma_data, base, SSI_GDD_CDSA_REG(lch)); > + ssi_outw(count, base, SSI_GDD_CEN_REG(lch)); > + > + ssi_outl_or(SSI_GDD_LCH(lch), base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > + ssi_outw_or(SSI_CCR_ENABLE, base, SSI_GDD_CCR_REG(lch)); > + > + return 0; > +} > + > +void ssi_driver_cancel_write_dma(struct ssi_channel *ssi_ch) > +{ > + int lch = ssi_ch->write_data.lch; > + unsigned int port = ssi_ch->ssi_port->port_number; > + unsigned int channel = ssi_ch->channel_number; > + struct ssi_dev *ssi_ctrl = ssi_ch->ssi_port->ssi_controller; > + u32 ccr; > + > + if (lch < 0) > + return; > + > + clk_enable(ssi_ctrl->ssi_clk); > + ccr = ssi_inw(ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); > + if (!(ccr & SSI_CCR_ENABLE)) { > + dev_dbg(&ssi_ch->dev->device, LOG_NAME "Write cancel on not " > + "enabled logical channel %d CCR REG 0x%08X\n", lch, ccr); > + clk_disable(ssi_ctrl->ssi_clk); > + return; > + } > + > + ssi_outw_and(~SSI_CCR_ENABLE, ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); > + ssi_outl_and(~SSI_GDD_LCH(lch), ssi_ctrl->base, > + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > + ssi_outl(SSI_GDD_LCH(lch), ssi_ctrl->base, > + SSI_SYS_GDD_MPU_IRQ_STATUS_REG); > + > + ssi_outl_and(~NOTFULL(channel), ssi_ctrl->base, > + SSI_SST_BUFSTATE_REG(port)); > + > + > + ssi_ch->write_data.addr = NULL; > + ssi_ch->write_data.size = 0; > + ssi_ch->write_data.lch = -1; > + clk_disable(ssi_ctrl->ssi_clk); > + clk_disable(ssi_ctrl->ssi_clk); > +} > + > +void ssi_driver_cancel_read_dma(struct ssi_channel *ssi_ch) > +{ > + int lch = ssi_ch->read_data.lch; > + struct ssi_dev *ssi_ctrl = ssi_ch->ssi_port->ssi_controller; > + unsigned int port = ssi_ch->ssi_port->port_number; > + unsigned int channel = ssi_ch->channel_number; > + u32 reg; > + > + if (lch < 0) > + return; > + > + clk_enable(ssi_ctrl->ssi_clk); > + reg = ssi_inw(ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); > + if (!(reg & SSI_CCR_ENABLE)) { > + dev_dbg(&ssi_ch->dev->device, LOG_NAME "Read cancel on not " > + "enable logical channel %d CCR REG 0x%08X\n", lch, reg); > + clk_disable(ssi_ctrl->ssi_clk); > + return; > + } > + > + ssi_outw_and(~SSI_CCR_ENABLE, ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); > + ssi_outl_and(~SSI_GDD_LCH(lch), ssi_ctrl->base, > + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > + ssi_outl(SSI_GDD_LCH(lch), ssi_ctrl->base, > + SSI_SYS_GDD_MPU_IRQ_STATUS_REG); > + > + ssi_outl_and(~NOTEMPTY(channel), ssi_ctrl->base, > + SSI_SSR_BUFSTATE_REG(port)); > + > + ssi_ch->read_data.addr = NULL; > + ssi_ch->read_data.size = 0; > + ssi_ch->read_data.lch = -1; > + clk_disable(ssi_ctrl->ssi_clk); > + clk_disable(ssi_ctrl->ssi_clk); > +} > + > +static void dma_read_cb(struct ssi_dev *ctrl, unsigned int port, > + unsigned int channel) > +{ > + struct ssi_channel *ch = &ctrl->ssi_port[port - 1].ssi_channel[channel]; > + > + ch->ops.read_done(ch->dev); > +} > + > +static void dma_write_cb(struct ssi_dev *ctrl, unsigned int port, > + unsigned int channel) > +{ > + struct ssi_channel *ch = &ctrl->ssi_port[port - 1].ssi_channel[channel]; > + > + ch->ops.write_done(ch->dev); > +} > + > +static void do_gdd_lch(struct ssi_dev *ssi_ctrl, unsigned int gdd_lch) > +{ > + void __iomem *base = ssi_ctrl->base; > + unsigned int port; > + unsigned int channel; > + u32 sync; > + u32 gdd_csr; > + dma_addr_t dma_h; > + size_t size; > + > + sync = ssi_inw(base, SSI_GDD_CCR_REG(gdd_lch)) & SSI_CCR_SYNC_MASK; > + port = get_sync_port(sync); > + channel = get_sync_channel(sync); > + > + spin_lock(&ssi_ctrl->lock); > + > + ssi_outl_and(~SSI_GDD_LCH(gdd_lch), base, > + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > + gdd_csr = ssi_inw(base, SSI_GDD_CSR_REG(gdd_lch)); > + > + if (!(gdd_csr & SSI_CSR_TOUR)) { > + if (get_sync_type(sync) == SSI_SYNC_READ) { > + dma_h = ssi_inl(base, SSI_GDD_CDSA_REG(gdd_lch)); > + size = ssi_inw(base, SSI_GDD_CEN_REG(gdd_lch)) * 4; > + dma_sync_single(NULL, dma_h, size, DMA_FROM_DEVICE); > + rst_ch_read(ssi_ctrl, port, channel); > + spin_unlock(&ssi_ctrl->lock); > + dma_read_cb(ssi_ctrl, port, channel); > + } else { > + rst_ch_write(ssi_ctrl, port, channel); > + spin_unlock(&ssi_ctrl->lock); > + dma_write_cb(ssi_ctrl, port, channel); > + } > + } else { > + dev_err(&ssi_ctrl->pdev->dev, "Error on GDD transfer " > + "on gdd channel %d port %d channel %d\n", > + gdd_lch, port, channel); > + spin_unlock(&ssi_ctrl->lock); > + ssi_port_event_handler(&ssi_ctrl->ssi_port[port - 1], > + SSI_EVENT_ERROR, NULL); > + } > +} > + > +void do_ssi_gdd_tasklet(unsigned long device) > +{ > + struct ssi_dev *ssi_ctrl = (struct ssi_dev *)device; > + void __iomem *base = ssi_ctrl->base; > + unsigned int gdd_lch = 0; > + u32 status_reg = 0; > + u32 lch_served = 0; > + > + clk_enable(ssi_ctrl->ssi_clk); > + > + status_reg = ssi_inl(base, SSI_SYS_GDD_MPU_IRQ_STATUS_REG); > + > + for (gdd_lch = 0; gdd_lch < SSI_NUM_LCH; gdd_lch++) { > + if (status_reg & SSI_GDD_LCH(gdd_lch)) { > + do_gdd_lch(ssi_ctrl, gdd_lch); > + lch_served |= SSI_GDD_LCH(gdd_lch); > + clk_disable(ssi_ctrl->ssi_clk); > + } > + } > + > + ssi_outl(lch_served, base, SSI_SYS_GDD_MPU_IRQ_STATUS_REG); > + clk_disable(ssi_ctrl->ssi_clk); > + > + enable_irq(ssi_ctrl->gdd_irq); > +} > + > +irqreturn_t ssi_gdd_mpu_handler(int irq, void *ssi_controller) > +{ > + struct ssi_dev *ssi_ctrl = (struct ssi_dev *)ssi_controller; > + > + tasklet_hi_schedule(&ssi_ctrl->ssi_gdd_tasklet); > + disable_irq_nosync(ssi_ctrl->gdd_irq); > + > + return IRQ_HANDLED; > +} > diff --git a/drivers/misc/ssi/ssi_driver_if.c b/drivers/misc/ssi/ssi_driver_if.c > new file mode 100644 > index 0000000..385467e > --- /dev/null > +++ b/drivers/misc/ssi/ssi_driver_if.c > @@ -0,0 +1,335 @@ > +/* > + * ssi_driver_if.c > + * > + * Implements SSI hardware driver interfaces for the upper layers. > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#include "ssi_driver.h" > + > +/** > + * ssi_open - open a ssi device channel. > + * @dev - Reference to the ssi device channel to be openned. > + * > + * Returns 0 on success, -EINVAL on bad parameters, -EBUSY if is already opened. > + */ > +int ssi_open(struct ssi_device *dev) > +{ > + struct ssi_channel *ch; > + struct ssi_port *port; > + struct ssi_dev *ssi_ctrl; > + > + if (!dev || !dev->ch) { > + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); > + return -EINVAL; > + } > + > + ch = dev->ch; > + if (!ch->ops.read_done || !ch->ops.write_done) { > + dev_err(&dev->device, "Trying to open with no callbacks " > + "registered\n"); > + return -EINVAL; > + } > + port = ch->ssi_port; > + ssi_ctrl = port->ssi_controller; > + spin_lock_bh(&ssi_ctrl->lock); > + if (ch->flags & SSI_CH_OPEN) { > + dev_err(&dev->device, "Port %d Channel %d already OPENED\n", > + dev->n_p, dev->n_ch); > + spin_unlock_bh(&ssi_ctrl->lock); > + return -EBUSY; > + } > + clk_enable(ssi_ctrl->ssi_clk); > + ch->flags |= SSI_CH_OPEN; > + ssi_outl_or(SSI_ERROROCCURED | SSI_BREAKDETECTED, ssi_ctrl->base, > + SSI_SYS_MPU_ENABLE_REG(port->port_number, port->n_irq)); > + clk_disable(ssi_ctrl->ssi_clk); > + spin_unlock_bh(&ssi_ctrl->lock); > + > + return 0; > +} > +EXPORT_SYMBOL(ssi_open); > + > +/** > + * ssi_write - write data into the ssi device channel > + * @dev - reference to the ssi device channel to write into. > + * @data - pointer to a 32-bit word data to be written. > + * @count - number of 32-bit word to be written. > + * > + * Return 0 on sucess, a negative value on failure. > + * A success values only indicates that the request has been accepted. > + * Transfer is only completed when the write_done callback is called. > + * > + */ > +int ssi_write(struct ssi_device *dev, u32 *data, unsigned int count) > +{ > + struct ssi_channel *ch; > + int err; > + > + if (unlikely(!dev || !dev->ch || !data || (count <= 0))) { > + dev_err(&dev->device, "Wrong paramenters " > + "ssi_device %p data %p count %d", dev, data, count); > + return -EINVAL; > + } > + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { > + dev_err(&dev->device, "SSI device NOT open\n"); > + return -EINVAL; > + } > + > + ch = dev->ch; > + spin_lock_bh(&ch->ssi_port->ssi_controller->lock); > + ch->write_data.addr = data; > + ch->write_data.size = count; > + > + if (count == 1) > + err = ssi_driver_write_interrupt(ch, data); > + else > + err = ssi_driver_write_dma(ch, data, count); > + > + if (unlikely(err < 0)) { > + ch->write_data.addr = NULL; > + ch->write_data.size = 0; > + } > + spin_unlock_bh(&ch->ssi_port->ssi_controller->lock); > + > + return err; > + > +} > +EXPORT_SYMBOL(ssi_write); > + > +/** > + * ssi_read - read data from the ssi device channel > + * @dev - ssi device channel reference to read data from. > + * @data - pointer to a 32-bit word data to store the data. > + * @count - number of 32-bit word to be stored. > + * > + * Return 0 on sucess, a negative value on failure. > + * A success values only indicates that the request has been accepted. > + * Data is only available in the buffer when the read_done callback is called. > + * > + */ > +int ssi_read(struct ssi_device *dev, u32 *data, unsigned int count) > +{ > + struct ssi_channel *ch; > + int err; > + > + if (unlikely(!dev || !dev->ch || !data || (count <= 0))) { > + dev_err(&dev->device, "Wrong paramenters " > + "ssi_device %p data %p count %d", dev, data, count); > + return -EINVAL; > + } > + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { > + dev_err(&dev->device, "SSI device NOT open\n"); > + return -EINVAL; > + } > + > + ch = dev->ch; > + spin_lock_bh(&ch->ssi_port->ssi_controller->lock); > + ch->read_data.addr = data; > + ch->read_data.size = count; > + > + if (count == 1) > + err = ssi_driver_read_interrupt(ch, data); > + else > + err = ssi_driver_read_dma(ch, data, count); > + > + if (unlikely(err < 0)) { > + ch->read_data.addr = NULL; > + ch->read_data.size = 0; > + } > + spin_unlock_bh(&ch->ssi_port->ssi_controller->lock); > + > + return err; > +} > +EXPORT_SYMBOL(ssi_read); > + > +void __ssi_write_cancel(struct ssi_channel *ch) > +{ > + if (ch->write_data.size == 1) > + ssi_driver_cancel_write_interrupt(ch); > + else if (ch->write_data.size > 1) > + ssi_driver_cancel_write_dma(ch); > + > +} > +/** > + * ssi_write_cancel - Cancel pending write request. > + * @dev - ssi device channel where to cancel the pending write. > + * > + * write_done() callback will not be called after sucess of this function. > + */ > +void ssi_write_cancel(struct ssi_device *dev) > +{ > + if (unlikely(!dev || !dev->ch)) { > + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); > + return; > + } > + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { > + dev_err(&dev->device, "SSI device NOT open\n"); > + return; > + } > + > + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); > + __ssi_write_cancel(dev->ch); > + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); > +} > +EXPORT_SYMBOL(ssi_write_cancel); > + > +void __ssi_read_cancel(struct ssi_channel *ch) > +{ > + if (ch->read_data.size == 1) > + ssi_driver_cancel_read_interrupt(ch); > + else if (ch->read_data.size > 1) > + ssi_driver_cancel_read_dma(ch); > +} > + > +/** > + * ssi_read_cancel - Cancel pending read request. > + * @dev - ssi device channel where to cancel the pending read. > + * > + * read_done() callback will not be called after sucess of this function. > + */ > +void ssi_read_cancel(struct ssi_device *dev) > +{ > + if (unlikely(!dev || !dev->ch)) { > + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); > + return; > + } > + > + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { > + dev_err(&dev->device, "SSI device NOT open\n"); > + return; > + } > + > + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); > + __ssi_read_cancel(dev->ch); > + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); > + > +} > +EXPORT_SYMBOL(ssi_read_cancel); > + > +/** > + * ssi_ioctl - SSI I/O control > + * @dev - ssi device channel reference to apply the I/O control > + * (or port associated to it) > + * @command - SSI I/O control command > + * @arg - parameter associated to the control command. NULL, if no parameter. > + * > + * Return 0 on sucess, a negative value on failure. > + * > + */ > +int ssi_ioctl(struct ssi_device *dev, unsigned int command, void *arg) > +{ > + struct ssi_channel *ch; > + struct ssi_dev *ssi_ctrl; > + void __iomem *base; > + unsigned int port, channel; > + u32 wake; > + int err = 0; > + > + if (unlikely((!dev) || > + (!dev->ch) || > + (!dev->ch->ssi_port) || > + (!dev->ch->ssi_port->ssi_controller)) || > + (!(dev->ch->flags & SSI_CH_OPEN))) { > + pr_err(LOG_NAME "SSI IOCTL Invalid parameter\n"); > + return -EINVAL; > + } > + > + > + ch = dev->ch; > + ssi_ctrl = ch->ssi_port->ssi_controller; > + port = ch->ssi_port->port_number; > + channel = ch->channel_number; > + base = ssi_ctrl->base; > + clk_enable(ssi_ctrl->ssi_clk); > + > + switch (command) { > + case SSI_IOCTL_WAKE_UP: > + /* We only claim once the wake line per channel */ > + wake = ssi_inl(base, SSI_SYS_WAKE_REG(port)); > + if (!(wake & SSI_WAKE(channel))) { > + clk_enable(ssi_ctrl->ssi_clk); > + ssi_outl(SSI_WAKE(channel), base, > + SSI_SYS_SET_WAKE_REG(port)); > + } > + break; > + case SSI_IOCTL_WAKE_DOWN: > + wake = ssi_inl(base, SSI_SYS_WAKE_REG(port)); > + if ((wake & SSI_WAKE(channel))) { > + ssi_outl(SSI_WAKE(channel), base, > + SSI_SYS_CLEAR_WAKE_REG(port)); > + clk_disable(ssi_ctrl->ssi_clk); > + } > + break; > + case SSI_IOCTL_SEND_BREAK: > + ssi_outl(1, base, SSI_SST_BREAK_REG(port)); > + break; > + case SSI_IOCTL_WAKE: > + if (arg == NULL) > + err = -EINVAL; > + else > + *(u32 *)arg = ssi_inl(base, SSI_SYS_WAKE_REG(port)); > + break; > + default: > + err = -ENOIOCTLCMD; > + break; > + } > + > + clk_disable(ssi_ctrl->ssi_clk); > + > + return err; > +} > +EXPORT_SYMBOL(ssi_ioctl); > + > +/** > + * ssi_close - close given ssi device channel > + * @dev - reference to ssi device channel. > + */ > +void ssi_close(struct ssi_device *dev) > +{ > + if (!dev || !dev->ch) { > + pr_err(LOG_NAME "Trying to close wrong SSI device %p\n", dev); > + return; > + } > + > + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); > + if (dev->ch->flags & SSI_CH_OPEN) { > + dev->ch->flags &= ~SSI_CH_OPEN; > + __ssi_write_cancel(dev->ch); > + __ssi_read_cancel(dev->ch); > + } > + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); > + > +} > +EXPORT_SYMBOL(ssi_close); > + > +/** > + * ssi_dev_set_cb - register read_done() and write_done() callbacks. > + * @dev - reference to ssi device channel where callbacks are associated. > + * @r_cb - callback to signal read transfer completed. > + * @w_cb - callback to signal write transfer completed. > + */ > +void ssi_dev_set_cb(struct ssi_device *dev, void (*r_cb)(struct ssi_device *dev) > + , void (*w_cb)(struct ssi_device *dev)) > +{ > + dev->ch->ops.read_done = r_cb; > + dev->ch->ops.write_done = w_cb; > +} > +EXPORT_SYMBOL(ssi_dev_set_cb); > diff --git a/drivers/misc/ssi/ssi_driver_int.c b/drivers/misc/ssi/ssi_driver_int.c > new file mode 100644 > index 0000000..6491e48 > --- /dev/null > +++ b/drivers/misc/ssi/ssi_driver_int.c > @@ -0,0 +1,232 @@ > +/* > + * ssi_driver_int.c > + * > + * Implements SSI interrupt functionality. > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > +#include "ssi_driver.h" > + > +static void reset_ch_read(struct ssi_channel *ch) > +{ > + ch->read_data.addr = NULL; > + ch->read_data.size = 0; > + ch->read_data.lch = -1; > +} > + > +static void reset_ch_write(struct ssi_channel *ch) > +{ > + ch->write_data.addr = NULL; > + ch->write_data.size = 0; > + ch->write_data.lch = -1; > +} > + > +int ssi_driver_write_interrupt(struct ssi_channel *ch, u32 *data) > +{ > + struct ssi_port *p = ch->ssi_port; > + unsigned int port = p->port_number; > + unsigned int channel = ch->channel_number; > + > + clk_enable(p->ssi_controller->ssi_clk); > + ssi_outl_or(SSI_SST_DATAACCEPT(channel), p->ssi_controller->base, > + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > + > + > + return 0; > +} > + > +int ssi_driver_read_interrupt(struct ssi_channel *ch, u32 *data) > +{ > + struct ssi_port *p = ch->ssi_port; > + unsigned int port = p->port_number; > + unsigned int channel = ch->channel_number; > + > + clk_enable(p->ssi_controller->ssi_clk); > + > + ssi_outl_or(SSI_SSR_DATAAVAILABLE(channel), p->ssi_controller->base, > + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > + > + clk_disable(p->ssi_controller->ssi_clk); > + > + return 0; > +} > + > +void ssi_driver_cancel_write_interrupt(struct ssi_channel *ch) > +{ > + struct ssi_port *p = ch->ssi_port; > + unsigned int port = p->port_number; > + unsigned int channel = ch->channel_number; > + void __iomem *base = p->ssi_controller->base; > + u32 enable; > + > + clk_enable(p->ssi_controller->ssi_clk); > + > + enable = ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > + if (!(enable & SSI_SST_DATAACCEPT(channel))) { > + dev_dbg(&ch->dev->device, LOG_NAME "Write cancel on not " > + "enabled channel %d ENABLE REG 0x%08X", channel, enable); > + clk_disable(p->ssi_controller->ssi_clk); > + return; > + } > + ssi_outl_and(~SSI_SST_DATAACCEPT(channel), base, > + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > + ssi_outl_and(~NOTFULL(channel), base, SSI_SST_BUFSTATE_REG(port)); > + reset_ch_write(ch); > + > + clk_disable(p->ssi_controller->ssi_clk); > + clk_disable(p->ssi_controller->ssi_clk); > + > +} > + > +void ssi_driver_cancel_read_interrupt(struct ssi_channel *ch) > +{ > + struct ssi_port *p = ch->ssi_port; > + unsigned int port = p->port_number; > + unsigned int channel = ch->channel_number; > + void __iomem *base = p->ssi_controller->base; > + > + clk_enable(p->ssi_controller->ssi_clk); > + > + ssi_outl_and(~SSI_SSR_DATAAVAILABLE(channel), base, > + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > + ssi_outl_and(~NOTEMPTY(channel), base, SSI_SSR_BUFSTATE_REG(port)); > + reset_ch_read(ch); > + > + clk_disable(p->ssi_controller->ssi_clk); > +} > + > +static void do_channel_tx(struct ssi_channel *ch) > +{ > + struct ssi_dev *ssi_ctrl = ch->ssi_port->ssi_controller; > + void __iomem *base = ssi_ctrl->base; > + unsigned int n_ch; > + unsigned int n_p; > + unsigned int irq; > + > + n_ch = ch->channel_number; > + n_p = ch->ssi_port->port_number; > + irq = ch->ssi_port->n_irq; > + > + spin_lock(&ssi_ctrl->lock); > + > + if (ch->write_data.addr == NULL) { > + ssi_outl_and(~SSI_SST_DATAACCEPT(n_ch), base, > + SSI_SYS_MPU_ENABLE_REG(n_p, irq)); > + reset_ch_write(ch); > + spin_unlock(&ssi_ctrl->lock); > + clk_disable(ssi_ctrl->ssi_clk); > + (*ch->ops.write_done)(ch->dev); > + } else { > + ssi_outl(*(ch->write_data.addr), base, > + SSI_SST_BUFFER_CH_REG(n_p, n_ch)); > + ch->write_data.addr = NULL; > + spin_unlock(&ssi_ctrl->lock); > + } > +} > + > +static void do_channel_rx(struct ssi_channel *ch) > +{ > + struct ssi_dev *ssi_ctrl = ch->ssi_port->ssi_controller; > + void __iomem *base = ch->ssi_port->ssi_controller->base; > + unsigned int n_ch; > + unsigned int n_p; > + unsigned int irq; > + > + n_ch = ch->channel_number; > + n_p = ch->ssi_port->port_number; > + irq = ch->ssi_port->n_irq; > + > + spin_lock(&ssi_ctrl->lock); > + > + *(ch->read_data.addr) = ssi_inl(base, SSI_SSR_BUFFER_CH_REG(n_p, n_ch)); > + > + ssi_outl_and(~SSI_SSR_DATAAVAILABLE(n_ch), base, > + SSI_SYS_MPU_ENABLE_REG(n_p, irq)); > + reset_ch_read(ch); > + > + spin_unlock(&ssi_ctrl->lock); > + > + (*ch->ops.read_done)(ch->dev); > +} > + > +void do_ssi_tasklet(unsigned long ssi_port) > +{ > + struct ssi_port *pport = (struct ssi_port *)ssi_port; > + struct ssi_dev *ssi_ctrl = pport->ssi_controller; > + void __iomem *base = ssi_ctrl->base; > + unsigned int port = pport->port_number; > + unsigned int channel = 0; > + unsigned int irq = pport->n_irq; > + u32 status_reg; > + u32 enable_reg; > + u32 ssr_err_reg; > + u32 channels_served; > + > + clk_enable(ssi_ctrl->ssi_clk); > + > + channels_served = 0; > + status_reg = ssi_inl(base, SSI_SYS_MPU_STATUS_REG(port, irq)); > + enable_reg = ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, irq)); > + > + for (channel = 0; channel < pport->max_ch; channel++) { > + if ((status_reg & SSI_SST_DATAACCEPT(channel)) && > + (enable_reg & SSI_SST_DATAACCEPT(channel))) { > + do_channel_tx(&pport->ssi_channel[channel]); > + channels_served |= SSI_SST_DATAACCEPT(channel); > + } > + > + if ((status_reg & SSI_SSR_DATAAVAILABLE(channel)) && > + (enable_reg & SSI_SSR_DATAAVAILABLE(channel))) { > + do_channel_rx(&pport->ssi_channel[channel]); > + channels_served |= SSI_SSR_DATAAVAILABLE(channel); > + } > + } > + > + if ((status_reg & SSI_BREAKDETECTED) && > + (enable_reg & SSI_BREAKDETECTED)) { > + dev_info(&ssi_ctrl->pdev->dev, > + "Hardware BREAK on port %d\n", port); > + ssi_outl(0, base, SSI_SSR_BREAK_REG(port)); > + ssi_port_event_handler(pport, SSI_EVENT_BREAK_DETECTED, NULL); > + } > + > + if (status_reg & SSI_ERROROCCURED) { > + ssr_err_reg = ssi_inl(base, SSI_SSR_ERROR_REG(port)); > + dev_err(&ssi_ctrl->pdev->dev, "SSI ERROR Port %d: 0x%02x\n", > + port, ssr_err_reg); > + ssi_outl(ssr_err_reg, base, SSI_SSR_ERRORACK_REG(port)); > + ssi_port_event_handler(pport, SSI_EVENT_ERROR, NULL); > + } > + > + ssi_outl((channels_served | SSI_ERROROCCURED | SSI_BREAKDETECTED), base, > + SSI_SYS_MPU_STATUS_REG(port, irq)); > + > + clk_disable(ssi_ctrl->ssi_clk); > + enable_irq(pport->irq); > +} > + > +irqreturn_t ssi_mpu_handler(int irq, void *ssi_port) > +{ > + struct ssi_port *p = (struct ssi_port *)ssi_port; unnecessary cast, remove. > + > + tasklet_hi_schedule(&p->ssi_tasklet); > + disable_irq_nosync(p->irq); > + > + return IRQ_HANDLED; > +} this patch is really huge, I'm not finished with it. Still a lot to review. -- balbi ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC][PATCH 3/5] OMAP SSI driver code 2008-10-07 0:03 ` [RFC][PATCH 3/5] OMAP SSI driver code Felipe Balbi @ 2008-10-07 22:01 ` Felipe Balbi 0 siblings, 0 replies; 14+ messages in thread From: Felipe Balbi @ 2008-10-07 22:01 UTC (permalink / raw) To: Felipe Balbi; +Cc: Carlos Chinea, linux-kernel, linux-omap continuing with this patch On Tue, Oct 07, 2008 at 03:03:09AM +0300, Felipe Balbi wrote: > On Fri, Oct 03, 2008 at 02:52:28PM +0300, Carlos Chinea wrote: > > Description. This seems to repeat in all your patches, take a look at > linux kernel patch format: http://linux.yyz.us/patch-format.html > > > Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> > > --- > > drivers/misc/ssi/Kconfig | 11 + > > drivers/misc/ssi/Makefile | 7 + > > drivers/misc/ssi/ssi_driver.c | 513 +++++++++++++++++++++++++++++++++++++ > > drivers/misc/ssi/ssi_driver.h | 211 +++++++++++++++ > > drivers/misc/ssi/ssi_driver_bus.c | 192 ++++++++++++++ > > drivers/misc/ssi/ssi_driver_dma.c | 406 +++++++++++++++++++++++++++++ > > drivers/misc/ssi/ssi_driver_if.c | 335 ++++++++++++++++++++++++ > > drivers/misc/ssi/ssi_driver_int.c | 232 +++++++++++++++++ > > 8 files changed, 1907 insertions(+), 0 deletions(-) > > create mode 100644 drivers/misc/ssi/Kconfig > > create mode 100644 drivers/misc/ssi/Makefile > > create mode 100644 drivers/misc/ssi/ssi_driver.c > > create mode 100644 drivers/misc/ssi/ssi_driver.h > > create mode 100644 drivers/misc/ssi/ssi_driver_bus.c > > create mode 100644 drivers/misc/ssi/ssi_driver_dma.c > > create mode 100644 drivers/misc/ssi/ssi_driver_if.c > > create mode 100644 drivers/misc/ssi/ssi_driver_int.c > > > > diff --git a/drivers/misc/ssi/Kconfig b/drivers/misc/ssi/Kconfig > > new file mode 100644 > > index 0000000..5e0842c > > --- /dev/null > > +++ b/drivers/misc/ssi/Kconfig > > @@ -0,0 +1,11 @@ > > +# > > +# OMAP SSI HW kernel configuration > > +# > > +config OMAP_SSI > > + tristate "OMAP SSI hardware driver" > > + depends on ARCH_OMAP > > + default n > > + ---help--- > > + If you say Y here, you will enable the OMAP SSI hardware driver. > > + > > + If unsure, say N. > > diff --git a/drivers/misc/ssi/Makefile b/drivers/misc/ssi/Makefile > > new file mode 100644 > > index 0000000..2b74e02 > > --- /dev/null > > +++ b/drivers/misc/ssi/Makefile > > @@ -0,0 +1,7 @@ > > +# > > +# Makefile for SSI drivers > > +# > > +omap_ssi-objs := ssi_driver.o ssi_driver_dma.o ssi_driver_int.o \ > ^ trailing whitespace > > > + ssi_driver_if.o ssi_driver_bus.o > > + > > +obj-$(CONFIG_OMAP_SSI) += omap_ssi.o > > diff --git a/drivers/misc/ssi/ssi_driver.c b/drivers/misc/ssi/ssi_driver.c > > new file mode 100644 > > index 0000000..292e868 > > --- /dev/null > > +++ b/drivers/misc/ssi/ssi_driver.c > > @@ -0,0 +1,513 @@ > > +/* > > + * ssi_driver.c > > + * > > + * Implements SSI module interface, initialization, and PM related functions. > > + * > > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > > + * > > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > > + * > > + * This program is free software; you can redistribute it and/or > > + * modify it under the terms of the GNU General Public License > > + * version 2 as published by the Free Software Foundation. > > + * > > + * This program is distributed in the hope that it will be useful, but > > + * WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > + * General Public License for more details. > > + * > > + * You should have received a copy of the GNU General Public License > > + * along with this program; if not, write to the Free Software > > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > > + * 02110-1301 USA > > + */ > > + > > +#include <linux/err.h> > > +#include "ssi_driver.h" > > + > > +static void ssi_dev_release(struct device *dev) > > +{ > > +} > > + > > +static void __init ssi_undo_reg_dev(struct platform_device *pd, > > + int p, int ch) > > Take a few tabs out of the second line, it'll look better. > > > +{ > > + struct ssi_platform_data *p_data = > > normally we call it pdata, but it's your call in this case > > > + (struct ssi_platform_data *) pd->dev.platform_data; > > unnecessary cast. Remove > > > + struct ssi_port *ssi_p; > > + int port; > > + int channel; > > + > > + for (port = p; port >= 0; port--) { > > + ssi_p = &p_data->ssi_ctrl->ssi_port[port]; > > + for (channel = ch; channel >= 0 ; channel--) > > + device_unregister(&ssi_p->ssi_channel[channel].dev > > + ->device); > > This function should be unnecessary. pdata usage is wrong. > > > + } > > +} > > + > > +static int __init reg_ssi_dev_ch(struct platform_device *pd, > > + unsigned int p, unsigned int ch) > > the following should be probe(). > > > +{ > > + struct ssi_device *dev; > > + struct ssi_platform_data *p_data = > > + (struct ssi_platform_data *) pd->dev.platform_data; > > unnecessary cast, remove. > > > + > > + dev = kzalloc(sizeof(*dev), GFP_KERNEL); > > + if (!dev) > > + return -ENOMEM; > > + dev->n_ctrl = pd->id; > > + dev->n_p = p; > > + dev->n_ch = ch; > > + dev->ch = &p_data->ssi_ctrl->ssi_port[p].ssi_channel[ch]; > > + p_data->ssi_ctrl->ssi_port[p].ssi_channel[ch].dev = dev; > > pdata usage is wrong. It should be used to initialize a few fields in > the device structure and then vanish. > > > + dev->device.bus = &ssi_bus_type; > > + dev->device.parent = &pd->dev; > > + dev->device.release = ssi_dev_release; > > + if (dev->n_ctrl < 0) > > + snprintf(dev->device.bus_id, sizeof(dev->device.bus_id), > > + "omap_ssi-p%u.c%u", p, ch); > > + else > > + snprintf(dev->device.bus_id, sizeof(dev->device.bus_id), > > + "omap_ssi%d-p%u.c%u", dev->n_ctrl, p, ch); > > + > > + return device_register(&dev->device); > > +} > > + > > +static int __init register_ssi_devices(struct platform_device *pd) > > +{ > > + struct ssi_platform_data *p_data = > > + (struct ssi_platform_data *) pd->dev.platform_data; > > unnecessary cast. Remove > > > + unsigned int n_ch = 0; > > + int port; > > + int ch = 0; > > + int err = 0; > > + > > + for (port = 0; ((port < p_data->num_ports) && (err >= 0)); port++) { > > + n_ch = max(p_data->ports[port].tx_ch, > > + p_data->ports[port].rx_ch); > > + for (ch = 0; ((ch < n_ch) && (err >= 0)); ch++) > > + err = reg_ssi_dev_ch(pd, port, ch); > > + } > > + > > + if (err < 0) { > > + port--; > > + ch--; > > + dev_err(&pd->dev, "Error registering ssi device channel " > > + "p%d-c%d : %d\n", port, ch, err); > > + if ((port == 0) && (ch == 0)) > > + return err; > > + else if ((port > 0) && (ch == 0)) > > + ch = n_ch; > > + ssi_undo_reg_dev(pd, port, ch); > > + } > > + return err; > > +} > > + > > +static int __init ssi_softreset(struct ssi_dev *ssi_ctrl) > > +{ > > + int ind = 0; > > + void __iomem *base = ssi_ctrl->base; > > + u32 status; > > + > > + ssi_outl_or(SSI_SOFTRESET, base, SSI_SYS_SYSCONFIG_REG); > > + > > + status = ssi_inl(base, SSI_SYS_SYSSTATUS_REG); > > + while ((!(status & SSI_RESETDONE)) && (ind < SSI_RESETDONE_RETRIES)) { > > + set_current_state(TASK_UNINTERRUPTIBLE); > > + schedule_timeout(msecs_to_jiffies(SSI_RESETDONE_TIMEOUT)); > > + status = ssi_inl(base, SSI_SYS_SYSSTATUS_REG); > > + ind++; > > + } > > + > > + if (ind >= SSI_RESETDONE_RETRIES) > > + return -EIO; > > + > > + /* Reseting GDD */ > > + ssi_outl_or(SSI_SWRESET, base, SSI_GDD_GRST_REG); > > + > > + return 0; > > +} > > + > > +static void __init set_ssi_ports_default( > > + struct platform_device *pd) > > +{ > > + struct ssi_port_pd *cfg = NULL; > > + struct ssi_platform_data *p_data = > > + (struct ssi_platform_data *) pd->dev.platform_data; > > + unsigned int port = 0; > > + void __iomem *base = p_data->ssi_ctrl->base; > > is this really a virtual address?? then can you use > __raw_{read,write}[blw]() and drop ssi-specific read/write functions? > > btw, the base address shouldn't come via pdata, it should come via > struct resource and get ioremap:ed in the driver. Recently we fixed all > the physical/virtual address mess and if we accept this it'll lead to > similar issues. Please fix. > > > + > > + for (port = 1; port <= p_data->num_ports; port++) { > > + cfg = &p_data->ports[port - 1]; > > + ssi_outl(cfg->tx_mode, base, SSI_SST_MODE_REG(port)); > > + ssi_outl(cfg->tx_frame_size, base, SSI_SST_FRAMESIZE_REG(port)); > > + ssi_outl(cfg->divisor, base, SSI_SST_DIVISOR_REG(port)); > > + ssi_outl(cfg->tx_ch, base, SSI_SST_CHANNELS_REG(port)); > > + ssi_outl(cfg->arb_mode, base, SSI_SST_ARBMODE_REG(port)); > > + > > + ssi_outl(cfg->rx_mode, base, SSI_SSR_MODE_REG(port)); > > + ssi_outl(cfg->rx_frame_size, base, SSI_SSR_FRAMESIZE_REG(port)); > > + ssi_outl(cfg->rx_ch, base, SSI_SSR_CHANNELS_REG(port)); > > + ssi_outl(cfg->timeout, base, SSI_SSR_TIMEOUT_REG(port)); > > + } > > +} > > + > > +static int __init ssi_port_channels_init( > > + struct platform_device *pd, unsigned int lport) > > +{ > > + struct ssi_platform_data *p_data = > > + (struct ssi_platform_data *) pd->dev.platform_data; > > + struct ssi_channel *ch; > > + struct ssi_port *port; > > + unsigned int n_ch; > > + unsigned int ch_i; > > + > > + n_ch = max(p_data->ports[lport].tx_ch, p_data->ports[lport].rx_ch); > > + port = &p_data->ssi_ctrl->ssi_port[lport]; > > + for (ch_i = 0; ch_i < n_ch; ch_i++) { > > + ch = &port->ssi_channel[ch_i]; > > + ch->channel_number = ch_i; > > + ch->flags = 0; > > + ch->ssi_port = port; > > + ch->read_data.addr = NULL; > > + ch->read_data.size = 0; > > + ch->read_data.lch = -1; > > + ch->write_data.addr = NULL; > > + ch->write_data.size = 0; > > + ch->write_data.lch = -1; > > + spin_lock_init(&ch->ssi_ch_lock); > > + } > > + > > + return 0; > > +} > > + > > +static int __init ssi_ports_init(struct platform_device *pd) > > +{ > > + struct ssi_platform_data *p_data = > > + (struct ssi_platform_data *) pd->dev.platform_data; > > unnecessary cast. Remove > > > + struct ssi_port *ssi_p = NULL; > > + unsigned int port = 0; > > + unsigned int err = 0; > > + unsigned int n_ports; > > + > > + n_ports = min(p_data->num_ports, (u8)SSI_MAX_PORTS); > > + > > + for (port = 0; ((port < n_ports) && (err >= 0)); port++) { > > + ssi_p = &p_data->ssi_ctrl->ssi_port[port]; > > + ssi_p->port_number = port + 1; > > + ssi_p->ssi_controller = p_data->ssi_ctrl; > > + ssi_p->max_ch = max(p_data->ports[port].tx_ch, > > + p_data->ports[port].rx_ch); > > + ssi_p->max_ch = min(ssi_p->max_ch, (u8)SSI_PORT_MAX_CH); > > + ssi_p->n_irq = p_data->ports[port].n_irq; > > + ssi_p->irq = 0; > > + spin_lock_init(&ssi_p->lock); > > + err = ssi_port_channels_init(pd, port); > > + } > > + > > + return 0; > > +} > > + > > +static int __init ssi_controller_init(struct platform_device *pd) > > +{ > > + struct ssi_platform_data *p_data = > > + (struct ssi_platform_data *) pd->dev.platform_data; > > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > > + int err; > > + > > + ssi_ctrl->id = pd->id; > > + ssi_ctrl->max_p = p_data->num_ports; > > in the header file you have a define for the max_ports and here you use > platform_data to initialize it. Quite weird. Where are you using that > define ? > > > + ssi_ctrl->pdev = pd; > > do not save the entire pdev pointer, save only the dev pointer use > platform_set_drvdata(pdev, ssi_ctrl); > > > + spin_lock_init(&ssi_ctrl->lock); > > + ssi_ctrl->ssi_clk = clk_get(&pd->dev, p_data->clk_name); > > please don't. > > > + if (IS_ERR(ssi_ctrl->ssi_clk)) { > > + dev_err(&pd->dev, "Unable to get SSI clocks"); > > + return PTR_ERR(ssi_ctrl->ssi_clk); > > + } > > + > > + err = ssi_ports_init(pd); > > + if (err < 0) { > > + dev_err(&pd->dev, "Error on ssi_controller initialization"); > > + clk_put(ssi_ctrl->ssi_clk); > > + return -EBUSY; > > + } > > + > > + return 0; > > +} > > + > > +static void ssi_controller_exit(struct platform_device *pd) > > +{ > > + struct ssi_platform_data *p_data = > > + (struct ssi_platform_data *) pd->dev.platform_data; > > unnecessary cast, remove. > > > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > > + > > + p_data->ssi_ctrl = NULL; > > + ssi_ctrl->pdev = NULL; > > + clk_put(ssi_ctrl->ssi_clk); > > + > > unnecessary extra white line, remove. > > > +} > > + > > +static int __init request_ssi_irqs(struct platform_device *pd) > > +{ > > + struct ssi_platform_data *p_data = pd->dev.platform_data; > > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > > + struct ssi_port *ssi_p; > > + struct resource *mpu_irq; > > unnecessary if it's only to get the irq. > > > + int i; > > + int j; > > + int err = 0; > > + > > + for (i = 0; ((i < p_data->num_ports) && (!err)); i++) { > > + mpu_irq = platform_get_resource(pd, IORESOURCE_IRQ, i*2); > > no > > > + if (!mpu_irq) { > > + dev_err(&pd->dev, "SSI misses info for MPU IRQ" > > + " on port %d", i + 1); > > + err = -ENODEV; > > use a goto to create a nice error path, then you remove this else below. > > > + } else { > > + ssi_p = &ssi_ctrl->ssi_port[i]; > > + ssi_p->n_irq = 0; /* We only use one irq line */ > > + ssi_p->irq = mpu_irq->start; > > ssi_p->irq = platform_get_irq(pdev, 0); > also gets rid of the unnecessary mpu_irq. > > > + tasklet_init(&ssi_p->ssi_tasklet, > > + do_ssi_tasklet, (unsigned long)ssi_p); > > + err = request_irq(mpu_irq->start, ssi_mpu_handler, > > + IRQF_DISABLED, mpu_irq->name, ssi_p); > > + if (err < 0) { > > + dev_err(&pd->dev, "FAILED request IRQ (%d)", > > + mpu_irq->start); > > + err = -EBUSY; > > + } > > + } > > + } > > + > > + if (err < 0) { > > + /* Let's free the reserved resources if there are any errors */ > > + for (j = 0; (j > (i-1)); j++) { > > + printk(KERN_DEBUG LOG_NAME "Free resources on port %d", > > + j+1); > > + ssi_p = &ssi_ctrl->ssi_port[i]; > > + tasklet_disable(&ssi_p->ssi_tasklet); > > + free_irq(ssi_p->irq, ssi_p); > > + } > > + } > > + > > + return err; > > +} > > + > > +static int __init request_ssi_gdd_irq(struct platform_device *pd) > > +{ > > + struct ssi_platform_data *p_data = pd->dev.platform_data; > > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > > + struct resource *gdd_irq; > > no > > > + int err = 0; > > + > > + gdd_irq = platform_get_resource(pd, IORESOURCE_IRQ, 4); > > no > > > + if (!gdd_irq) { > > + dev_err(&pd->dev, "SSI device has no info about GDD IRQ"); > > + return -ENODEV; > > + } > > + > > + ssi_ctrl->gdd_irq = gdd_irq->start; > > hell no. Use platform_get_irq() like I said before. > > > + err = request_irq(gdd_irq->start, ssi_gdd_mpu_handler, > > + IRQF_DISABLED, gdd_irq->name, ssi_ctrl); > > + if (err < 0) { > > + dev_err(&pd->dev, "FAILED to request IRQ NUMBER (%d)", > > + gdd_irq->start); > > + return -EBUSY; > > + } > > + tasklet_init(&ssi_ctrl->ssi_gdd_tasklet, do_ssi_gdd_tasklet, > > + (unsigned long)ssi_ctrl); > > + > > + return err; > > +} > > + > > +static void free_ssi_irqs(struct ssi_dev *ssi_ctrl) > > +{ > > + struct ssi_port *ssi_p; > > + int i; > > + > > + for (i = 0; (i < ssi_ctrl->max_p); i++) { > > + ssi_p = &ssi_ctrl->ssi_port[i]; > > + tasklet_disable(&ssi_p->ssi_tasklet); > > + free_irq(ssi_p->irq, ssi_p); > > + } > > +} > > + > > +static void free_ssi_gdd_irq(struct ssi_dev *ssi_ctrl) > > +{ > > + tasklet_disable(&ssi_ctrl->ssi_gdd_tasklet); > > + free_irq(ssi_ctrl->gdd_irq, ssi_ctrl); > > +} > > + > > +static int __init ssi_probe(struct platform_device *pd) > > +{ > > + struct resource *mem, *ioarea; > > + struct ssi_dev *ssi_ctrl = NULL; > > + struct ssi_platform_data *p_data = NULL; > > + u32 revision = 0; > > + int err = 0; > > + > > + > > + if ((pd == NULL) || (pd->dev.platform_data == NULL)) { > > + pr_err(LOG_NAME "No device/platform_data found on " > > + "ssi device\n"); > > + return -ENODEV; > > + } > > aff... pd will never be NULL if this driver is probing. I'd rather: > > struct ssi_platform_data *pdata = pd->dev.platform_data; > > if (!pdata) { > error messages goes here > } > > > + > > + ssi_ctrl = kzalloc(sizeof(*ssi_ctrl), GFP_KERNEL); > > + if (ssi_ctrl == NULL) { > > + dev_err(&pd->dev, "Could not allocate memory for" > > + " struct ssi_dev\n"); > > + return -ENOMEM; > > + } > > + > > + p_data = (struct ssi_platform_data *) pd->dev.platform_data; > > no cast needed. > > > + p_data->ssi_ctrl = ssi_ctrl; > > hell no. platform_set_drvdata(pd, ssi_ctrl); > > > + > > + mem = platform_get_resource(pd, IORESOURCE_MEM, 0); > > + if (!mem) { > > + dev_err(&pd->dev, "SSI device does not have " > > + "SSI IO memory region information\n"); > > + err = -ENODEV; > > + goto rback5; > > + } > > + > > + err = ssi_controller_init(pd); > > + if (err < 0) { > > + dev_err(&pd->dev, "Could not initialize ssi controller:" > > + " %d\n", err); > > + goto rback5; > > + } > > + > > + ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1, > > + pd->dev.bus_id); > > + if (!ioarea) { > > + dev_err(&pd->dev, "Unable to request SSI IO memory " > > + "region\n"); > > + err = -EBUSY; > > + goto rollback4; > > + } > > + > > + ssi_ctrl->base = (void __iomem *)mem->start; > > afff... this is terrible. Use ioremap(); > > > + > > + clk_enable(ssi_ctrl->ssi_clk); > > + > > + err = request_ssi_irqs(pd); > > + if (err < 0) > > + goto rollback1; > > + > > + err = request_ssi_gdd_irq(pd); > > + if (err < 0) > > + goto rollback2; > > + > > + err = ssi_softreset(ssi_ctrl); > > + if (err < 0) > > + goto rollback3; > > + > > + /* Set default PM settings */ > > + ssi_outl((SSI_AUTOIDLE | SSI_SIDLEMODE_SMART | SSI_MIDLEMODE_SMART), > > + ssi_ctrl->base, SSI_SYS_SYSCONFIG_REG); > > + ssi_outl(SSI_CLK_AUTOGATING_ON, ssi_ctrl->base, SSI_GDD_GCR_REG); > > + > > + set_ssi_ports_default(pd); > > + > > + /* Gather info from registers for the driver.(REVISION) */ > > + revision = ssi_inl(ssi_ctrl->base, SSI_SYS_REVISION_REG); > > + dev_info(&pd->dev, "SSI Hardware REVISION %d.%d\n", > > + (revision & SSI_REV_MAJOR) >> 4, (revision & SSI_REV_MINOR)); > > + > > + err = register_ssi_devices(pd); > > + if (err < 0) > > + goto rollback3; > > + > > + clk_disable(ssi_ctrl->ssi_clk); > > + > > + return err; > > + > > +rollback3: > > + free_ssi_gdd_irq(ssi_ctrl); > > +rollback2: > > + free_ssi_irqs(ssi_ctrl); > > +rollback1: > add iounmap() here > > + release_mem_region(mem->start, mem->end - mem->start + 1); > > + clk_disable(ssi_ctrl->ssi_clk); > > +rollback4: > > + ssi_controller_exit(pd); > > +rback5: > > + kfree(ssi_ctrl); > > + return err; > > +} > > + > > +static void __exit close_all_ch(struct ssi_dev *ssi_ctrl) > > +{ > > + struct ssi_port *ssi_p; > > + unsigned int port; > > + unsigned int ch; > > + > > + for (port = 0; port < ssi_ctrl->max_p; port++) { > > + ssi_p = &ssi_ctrl->ssi_port[port]; > > + for (ch = 0; ch < ssi_p->max_ch; ch++) > > + ssi_close(ssi_p->ssi_channel[ch].dev); > > + } > > +} > > + > > +static int __exit ssi_remove(struct platform_device *pd) > > +{ > > + struct resource *mem; > > + struct ssi_platform_data *p_data = > > + (struct ssi_platform_data *) pd->dev.platform_data; > > + struct ssi_dev *ssi_ctrl = p_data->ssi_ctrl; > > This should ssi_ctrl = platform_get_drvdata(pd); > > > + > > + close_all_ch(ssi_ctrl); > > + free_ssi_gdd_irq(ssi_ctrl); > > + free_ssi_irqs(ssi_ctrl); > > + mem = platform_get_resource(pd, IORESOURCE_MEM, 0); > > + if (mem) > > + release_mem_region(mem->start, mem->end - mem->start + 1); > > when you add proper ioremap(), add iounmap here. > > > + ssi_controller_exit(pd); > > + kfree(ssi_ctrl); > > + > > + return 0; > > +} > > + > > +static struct platform_driver ssi_pdriver = { > > + .probe = ssi_probe, > > + .remove = __exit_p(ssi_remove), > > + .driver = { > > + .name = "omap_ssi", > > + .owner = THIS_MODULE, > > + } > > +}; > > + > > +static int __init ssi_driver_init(void) > > +{ > > + int err = 0; > > + > > + pr_info("SSI DRIVER Version " SSI_DRIVER_VERSION "\n"); > > + > > + ssi_bus_init(); > > + err = platform_driver_probe(&ssi_pdriver, ssi_probe); > > + if (err < 0) { > > + pr_err(LOG_NAME "Platform DRIVER register FAILED: %d\n", err); > > + ssi_bus_exit(); > > + return err; > > + } > > + > > + return 0; > > return platform_driver_register(&ssi_pdriver); > should be enough. > > > +} > > + > > +static void __exit ssi_driver_exit(void) > > +{ > > + ssi_bus_exit(); > > + platform_driver_unregister(&ssi_pdriver); > > + > > + pr_info("SSI DRIVER removed\n"); > > +} > > + > > +module_init(ssi_driver_init); > > +module_exit(ssi_driver_exit); > > + > > +MODULE_ALIAS("platform:omap_ssi"); > > +MODULE_AUTHOR(SSI_DRIVER_AUTHOR); > > +MODULE_DESCRIPTION(SSI_DRIVER_DESC); > > +MODULE_LICENSE("GPL"); > > diff --git a/drivers/misc/ssi/ssi_driver.h b/drivers/misc/ssi/ssi_driver.h > > new file mode 100644 > > index 0000000..3c6d849 > > --- /dev/null > > +++ b/drivers/misc/ssi/ssi_driver.h > > @@ -0,0 +1,211 @@ > > +/* > > + * ssi_driver.h > > + * > > + * Header file for the SSI driver low level interface. > > + * > > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > > + * > > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > > + * > > + * This program is free software; you can redistribute it and/or > > + * modify it under the terms of the GNU General Public License > > + * version 2 as published by the Free Software Foundation. > > + * > > + * This program is distributed in the hope that it will be useful, but > > + * WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > + * General Public License for more details. > > + * > > + * You should have received a copy of the GNU General Public License > > + * along with this program; if not, write to the Free Software > > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > > + * 02110-1301 USA > > + */ > > + > > +#ifndef __SSI_DRIVER_H__ > > +#define __SSI_DRIVER_H__ > > + > > +#include <linux/module.h> > > +#include <linux/init.h> > > +#include <linux/clk.h> > > +#include <linux/ioport.h> > > +#include <linux/interrupt.h> > > +#include <linux/irq.h> > > +#include <linux/spinlock.h> > > +#include <linux/io.h> > > +#include <linux/platform_device.h> > > +#include <linux/gpio.h> > > + > > +#include <mach/ssi/ssi_reg_common.h> > > +#include <mach/ssi/ssi_sys_reg.h> > > +#include <mach/ssi/ssi_ssr_reg.h> > > +#include <mach/ssi/ssi_sst_reg.h> > > +#include <mach/ssi/ssi_gdd_reg.h> > > + > > +#include <linux/ssi_driver_if.h> > > + > > +#define SSI_DRIVER_VERSION "1.1-rc2" > > +#define SSI_DRIVER_AUTHOR "Carlos Chinea / Nokia" > > +#define SSI_DRIVER_DESC "Synchronous Serial Interface Driver" > > +#define SSI_DRIVER_LINCESE "GPL" > > +#define SSI_DRIVER_NAME "ssi_driver" > > + > > +#define SSI_DEVICE_NAME "ssi_device" > > these should be in the C-file. Nobody needs to access them > > > + > > +/* 10 ms */ > > +#define SSI_RESETDONE_TIMEOUT 10 > > +/* Let's retry 20 times.=>20x10 ms=200 ms */ > > +#define SSI_RESETDONE_RETRIES 20 > > + > > +/* Channel states */ > > +#define SSI_CH_OPEN 0x01 > > + > > +/* > > + * The number of channels to use by the driver in the ports, or the highest > > + * port channel number (+1) used. (MAX:8) > > + */ > > +#define SSI_PORT_MAX_CH 4 > > +/* Number of logical channel in GDD */ > > +#define SSI_NUM_LCH 8 > > + > > +#define LOG_NAME "OMAP SSI: " > > +#define SSI_PREFIX "ssi:" > > + > > +/* > > + * Callbacks use by the SSI upper layer (SSI protocol) to receive data > > + * from the port channels. > > + */ > > +struct ssi_channel_ops { > > + void (*write_done) (struct ssi_device *dev); > > + void (*read_done) (struct ssi_device *dev); > > +}; > > + > > +struct ssi_data { > > + /* Pointer to the data to be sent/received */ > > + u32 *addr; > > + /* > > + * Number of words to be sent or space reserved for data to be > > + * received. > > + */ > > + unsigned int size; > > + /* Holds GDD logical channed number */ > > + int lch; > > +}; > > + > > +struct ssi_channel { > > + struct ssi_channel_ops ops; > > + struct ssi_data read_data; > > + struct ssi_data write_data; > > + struct ssi_port *ssi_port; > > + u8 flags; > > + u8 channel_number; > > + spinlock_t ssi_ch_lock; > > + struct ssi_device *dev; > > + void *priv; > > +}; > > + > > +/* Holds information related to SSI port */ > > +struct ssi_port { > > + struct ssi_channel ssi_channel[SSI_PORT_MAX_CH]; > > + struct ssi_dev *ssi_controller; > > + u8 port_number; > > + u8 max_ch; > > + u8 n_irq; /* IRQ0 or IRQ1 */ > > + int irq /* Actual IRQ number */; > > + spinlock_t lock; > > + struct tasklet_struct ssi_tasklet; > > +}; > > + > > +/* > > + * Struct definition to hold information about the clocks, ssi controller > > + * and the ssi ports. > > + */ > > +struct ssi_dev { > > + /* Holds reference to PORT 1 (and PORT2 if defined) */ > > + struct ssi_port ssi_port[SSI_MAX_PORTS]; > > + int id; > > + u8 flags; > > + u8 max_p; > > + struct clk *ssi_clk; > > + void __iomem *base; > > + spinlock_t lock; > > + int gdd_irq; > > + struct tasklet_struct ssi_gdd_tasklet; > > + struct platform_device *pdev; > > +}; > > + > > +/* SSI Bus */ > > +struct ssi_port_event { > > + struct ssi_port *ssi_port; > > + unsigned int event; > > + void *priv; > > +}; > > +extern struct bus_type ssi_bus_type; > > + > > +int ssi_port_event_handler(struct ssi_port *p, unsigned int event, void *arg); > > +int ssi_bus_init(void); > > +void ssi_bus_exit(void); > > +/* End SSI Bus */ > > + > > +int ssi_driver_read_interrupt(struct ssi_channel *ssi_channel, u32 *data); > > +int ssi_driver_write_interrupt(struct ssi_channel *ssi_channel, u32 *data); > > +int ssi_driver_read_dma(struct ssi_channel *ssi_channel, u32 *data, > > + unsigned int count); > > +int ssi_driver_write_dma(struct ssi_channel *ssi_channel, u32 *data, > > + unsigned int count); > > + > > +void ssi_driver_cancel_write_interrupt(struct ssi_channel *ch); > > +void ssi_driver_cancel_read_interrupt(struct ssi_channel *ch); > > +void ssi_driver_cancel_write_dma(struct ssi_channel *ch); > > +void ssi_driver_cancel_read_dma(struct ssi_channel *ch); > > + > > +irqreturn_t ssi_mpu_handler(int irq, void *ssi_port); > > +irqreturn_t ssi_gdd_mpu_handler(int irq, void *ssi_controller); > > + > > +void do_ssi_tasklet(unsigned long ssi_port); > > +void do_ssi_gdd_tasklet(unsigned long device); > > + > > +static inline u32 ssi_inl(void __iomem *base, u32 offset) > > +{ > > + return inl(OMAP2_IO_ADDRESS(base + offset)); > > +} > > + > > +static inline void ssi_outl(u32 data, void __iomem *base, u32 offset) > > +{ > > + outl(data, OMAP2_IO_ADDRESS(base + offset)); > > +} > > + > > +static inline void ssi_outl_or(u32 data, void __iomem *base, u32 offset) > > +{ > > + u32 tmp = ssi_inl(base, offset); > > + ssi_outl((tmp | data), base, offset); > > +} > > + > > +static inline void ssi_outl_and(u32 data, void __iomem *base, u32 offset) > > +{ > > + u32 tmp = ssi_inl(base, offset); > > + ssi_outl((tmp & data), base, offset); > > +} > > + > > +static inline u16 ssi_inw(void __iomem *base, u32 offset) > > +{ > > + return inw(OMAP2_IO_ADDRESS(base + offset)); > > +} > > + > > +static inline void ssi_outw(u16 data, void __iomem *base, u32 offset) > > +{ > > + outw(data, OMAP2_IO_ADDRESS(base + offset)); > > +} > > + > > +static inline void ssi_outw_or(u16 data, void __iomem *base, u32 offset) > > +{ > > + u16 tmp = ssi_inw(base, offset); > > + ssi_outw((tmp | data), base, offset); > > +} > > + > > +static inline void ssi_outw_and(u16 data, void __iomem *base, u32 offset) > > +{ > > + u16 tmp = ssi_inw(base, offset); > > + ssi_outw((tmp & data), base, offset); > > +} > > +#endif > > diff --git a/drivers/misc/ssi/ssi_driver_bus.c b/drivers/misc/ssi/ssi_driver_bus.c > > new file mode 100644 > > index 0000000..6a07ee0 > > --- /dev/null > > +++ b/drivers/misc/ssi/ssi_driver_bus.c > > @@ -0,0 +1,192 @@ > > +/* > > + * ssi_driver_bus.c > > + * > > + * Implements SSI bus, device and driver interface. > > + * > > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > > + * > > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > > + * > > + * This program is free software; you can redistribute it and/or > > + * modify it under the terms of the GNU General Public License > > + * version 2 as published by the Free Software Foundation. > > + * > > + * This program is distributed in the hope that it will be useful, but > > + * WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > + * General Public License for more details. > > + * > > + * You should have received a copy of the GNU General Public License > > + * along with this program; if not, write to the Free Software > > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > > + * 02110-1301 USA > > + */ > > +#include <linux/device.h> > > +#include "ssi_driver.h" > > + > > +/* LDM. defintions for the ssi bus, ssi device, and ssi_device driver */ > > +struct bus_type ssi_bus_type; > > + > > +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, > > + char *buf) > > +{ > > + return snprintf(buf, BUS_ID_SIZE + 1, "%s%s\n", SSI_PREFIX, > > + dev->bus_id); > > +} > > + > > +static struct device_attribute ssi_dev_attrs[] = { > > + __ATTR_RO(modalias), > > + __ATTR_NULL, > > +}; > > + > > +static int ssi_bus_uevent(struct device *dev, struct kobj_uevent_env *env) > > +{ > > + add_uevent_var(env, "MODALIAS=%s%s", SSI_PREFIX, dev->bus_id); > > + return 0; > > +} > > + > > +/* NOTE: Function called in interrupt context */ > > +static int ssi_e_handler(struct device_driver *drv, void *p_event) > > +{ > > + struct ssi_port_event *event = (struct ssi_port_event *)p_event; > > + struct ssi_device_driver *ssi_drv = to_ssi_device_driver(drv); > > + struct ssi_port *p = event->ssi_port; > > + > > + BUG_ON(p_event == NULL); > > + > > + if ((ssi_drv->port_event) && > > + (test_bit(event->event, &ssi_drv->event_mask)) && > > + ((p->ssi_controller->id == -1) || > > + (test_bit(p->ssi_controller->id, &ssi_drv->ctrl_mask))) && > > + (ssi_drv->ch_mask[p->port_number - 1] != 0)) { > > + > > + (*ssi_drv->port_event)(p->ssi_controller->id, p->port_number, > > + event->event, event->priv); > > + } > > + > > + return 0; > > +} > > + > > +int ssi_port_event_handler(struct ssi_port *p, unsigned int event, void *arg) > > +{ > > + int err = 0; > > + struct ssi_port_event p_ev = { > > + .ssi_port = p, > > + .event = event, > > + .priv = arg > > + }; > > + > > + BUG_ON(p == NULL); > > + > > + err = bus_for_each_drv(&ssi_bus_type, NULL, &p_ev, ssi_e_handler); > > + > > + return err; > > +} > > + > > +static int ssi_bus_match(struct device *device, struct device_driver *driver) > > +{ > > + struct ssi_device *dev = to_ssi_device(device); > > + struct ssi_device_driver *drv = to_ssi_device_driver(driver); > > + > > + if (!test_bit(dev->n_ctrl, &drv->ctrl_mask)) > > + return 0; > > + > > + if (!test_bit(dev->n_ch, &drv->ch_mask[dev->n_p])) > > + return 0; > > + > > + return 1; > > +} > > + > > +int ssi_bus_unreg_dev(struct device *device, void *p) > > +{ > > + device->release(device); > > + device_unregister(device); > > + > > + return 0; > > +} > > + > > +int __init ssi_bus_init(void) > > +{ > > + remove this extra line > > + return bus_register(&ssi_bus_type); > > + remove this extra line > > +} > > + > > +void ssi_bus_exit(void) > > +{ > > + bus_for_each_dev(&ssi_bus_type, NULL, NULL, ssi_bus_unreg_dev); > > + bus_unregister(&ssi_bus_type); > > +} > > + > > +static int ssi_driver_probe(struct device *dev) > > +{ > > + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); int ret; if (!drv->probe) /* note if you add MODULE_DEVICE_TABLE test here !drv->id_table */ return -ENODEV; I think would be nice to save a reference of driver inside ssi_device, so you would: to_ssi_device(dev)->driver = drv; dev_dbg(dev, "probe\n"); ret = driver->probe(to_ssi_device(dev)); if (ret) to_ssi_device(dev)->driver = NULL return ret; > > +} > > + > > +static int ssi_driver_remove(struct device *dev) > > +{ > > + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); int ret; if (!dev->driver) return 0; drv = to_ssi_device_driver(dev->driver); if (drv->remove) { dev_dbg(dev, "remove\n"); ret = drv->remove(to_ssi_device(dev)); } else { dev->driver = NULL; ret = 0; } if (ret == 0) to_sse_device(dev)->driver = NULL; return ret; > > +} > > + > > +static int ssi_driver_suspend(struct device *dev, pm_message_t mesg) > > +{ > > + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); > > + > > + return drv->suspend(to_ssi_device(dev), mesg); > > +} > > + > > +static int ssi_driver_resume(struct device *dev) > > +{ > > + struct ssi_device_driver *drv = to_ssi_device_driver(dev->driver); > > + > > + return drv->resume(to_ssi_device(dev)); you need to be careful here. Try something like: if (!dev->driver) return 0; drv = to_ssi_device_driver(dev->driver); if (!drv->suspend) return 0; return driver->suspend(to_ssi_device(dev), msg); ditto to suspend > > +} > > + > > +struct bus_type ssi_bus_type = { > > + .name = "ssi", > > + .dev_attrs = ssi_dev_attrs, > > + .match = ssi_bus_match, > > + .uevent = ssi_bus_uevent, > > tabify these > > .name = "ssi", > .dev_attrs = ssi_dev_attrs, > .match = ssi_bus_match, > .uevent = ssi_bus_uevent, add suspend, resume, probe and remove to ssi_bus_type and remove what you have in register_ssi_driver. > > +int register_ssi_driver(struct ssi_device_driver *driver) > > +{ > > + int ret = 0; > > + > > + BUG_ON(driver == NULL); > > don't bug, just return. BUG will oops the kernel. Returning would be > more appropriate since what needs fix is the driver trying to register > not the bus driver. > > > + > > + driver->driver.bus = &ssi_bus_type; --- remove from here > > + if (driver->probe) > > + driver->driver.probe = ssi_driver_probe; > > + if (driver->remove) > > + driver->driver.remove = ssi_driver_remove; > > + if (driver->suspend) > > + driver->driver.suspend = ssi_driver_suspend; > > + if (driver->resume) > > + driver->driver.resume = ssi_driver_resume; > > + --- to here. > > + ret = driver_register(&driver->driver); would be nice to get a success message if ret == 0: if (ret == 0) pr_debug("ssi: driver %s registered\n", driver->driver.name); > > + > > + return ret; > > +} > > +EXPORT_SYMBOL(register_ssi_driver); > > + > > +/** > > + * unregister_ssi_driver - Unregister SSI device driver > > + * @driver - reference to the SSI device driver. > > + */ > > +void unregister_ssi_driver(struct ssi_device_driver *driver) > > +{ > > + BUG_ON(driver == NULL); > > ditto. > > > + > > + driver_unregister(&driver->driver); same here: pr_debug("ssi: driver %s unregistered\n", driver->driver.name); > > +} > > +EXPORT_SYMBOL(unregister_ssi_driver); > > diff --git a/drivers/misc/ssi/ssi_driver_dma.c b/drivers/misc/ssi/ssi_driver_dma.c > > new file mode 100644 > > index 0000000..2524a12 > > --- /dev/null > > +++ b/drivers/misc/ssi/ssi_driver_dma.c > > @@ -0,0 +1,406 @@ > > +/* > > + * ssi_driver_dma.c > > + * > > + * Implements SSI low level interface driver functionality with DMA support. > > + * > > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > > + * > > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > > + * > > + * This program is free software; you can redistribute it and/or > > + * modify it under the terms of the GNU General Public License > > + * version 2 as published by the Free Software Foundation. > > + * > > + * This program is distributed in the hope that it will be useful, but > > + * WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > + * General Public License for more details. > > + * > > + * You should have received a copy of the GNU General Public License > > + * along with this program; if not, write to the Free Software > > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > > + * 02110-1301 USA > > + */ > > +#include <linux/dma-mapping.h> > > +#include "ssi_driver.h" > > + > > +#define SSI_SYNC_WRITE 0 > > +#define SSI_SYNC_READ 1 > ^^ trailing whitespaces > > + the following variables and functions, etc please prepend them with ssi_ to avoid possible namespace conflicts. also, fix the comment style to be kerneldoc style: /** * function name - small description * * @parameter1: small description * @parameter2: small description * @parameterN: small description * * Here you put a long description of the function * and what it really does. Just don't extend too much * and don't translate C-code into english, just give * information on what's the main purpose for that function * and anything you think is really valid to mention. * * Returns 0 on success or negative errno */ of course the Returns blabla will have to change according to what the function really returns: channel number, port number, sync type, etc. > > +static unsigned char sync_table[2][2][8] = { > > + { > > + {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, > > + {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00} > > + }, > > + { > > }, { would look better > > > + {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}, > > + {0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F} > > + } > > +}; > > + > > +static unsigned int get_sync_type(unsigned int sync) > > +{ > > + return (sync & 0x10) ? SSI_SYNC_READ : SSI_SYNC_WRITE; > > +} > > + > > +static unsigned int get_sync_port(unsigned int sync) > > +{ > > + if (((sync >= 0x01) && (sync <= 0x08)) || > > + ((sync >= 0x10) && (sync <= 0x17))) > > + return 1; > > + else if (((sync >= 0x09) && (sync <= 0x0F)) || > > + ((sync >= 0x18) && (sync <= 0x1E))) > > + return 2; > > + else > > + return 3; what's the main idea here? you pass sync and it's return the port number ?? this really looks odd. I wanna understand this better and let's try to find a better solution, shall we ? :-) > > +} > > + > > +static unsigned int get_sync_channel(unsigned int sync) > > +{ > > + if ((sync == 0x00) || (sync == 0x1F)) > > + return 8; > > + > > + if (sync & 0x10) > > + return (sync & 0x0F) % 8; > > + else > > + return (sync - 1) % 8; ditto > > +} > > + > > +static unsigned int get_sync(unsigned int port, > > + unsigned int channel, unsigned int type) > > +{ > > + if ((port == 0) || (port > SSI_MAX_PORTS) || > > + (channel >= SSI_PORT_MAX_CH) || (type > SSI_SYNC_READ)) > > + return 0x00; > > + > > + return sync_table[type][port - 1][channel]; ditto > > +} > > + > > +static void rst_ch_read(struct ssi_dev *ssi_ctrl, > > + unsigned int n_p, unsigned int n_ch) > > +{ > > + struct ssi_channel *ch = > > + &ssi_ctrl->ssi_port[n_p - 1].ssi_channel[n_ch]; > > + > > + ch->read_data.addr = NULL; > > + ch->read_data.size = 0; > > + ch->read_data.lch = -1; > > +} > > + > > +static void rst_ch_write(struct ssi_dev *ssi_ctrl, > > + unsigned int n_p, unsigned int n_ch) > > +{ > > + struct ssi_channel *ch = > > + &ssi_ctrl->ssi_port[n_p - 1].ssi_channel[n_ch]; > > + > > + ch->write_data.addr = NULL; > > + ch->write_data.size = 0; > > + ch->write_data.lch = -1; > > +} > > + > > +/* > > + * get_free_lch - Get a free GDD(DMA)logical channel > > + * @ssi_ctrl- SSI controller of the GDD. > > + * > > + * Needs to be called holding the gdd controller lock > > + */ > > +static unsigned int get_free_lch(struct ssi_dev *ssi_ctrl) > > +{ > > + unsigned int enable_reg; > > + unsigned int lch = 0; > > + > > + enable_reg = ssi_inl(ssi_ctrl->base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > > + while ((lch < SSI_NUM_LCH) && (enable_reg & SSI_GDD_LCH(lch))) > > + lch++; > > + > > + return lch; > > +} > > + > > +/* > > + * ssi_driver_write_dma - Program GDD [DMA] to write data from memory to > > + * the ssi channel buffer. > > + * @ssi_channel - pointer to the ssi_channel to write data to. > > + * @data - 32-bit word pointer to the data. > > + * @size - Number of 32bit words to be transfered. > > + * > > + * ssi_controller lock must be hold before calling this function. s/hold/held > > + */ > > +int ssi_driver_write_dma(struct ssi_channel *ssi_channel, u32 *data, > > + unsigned int size) > > +{ > > + struct ssi_dev *ssi_ctrl = ssi_channel->ssi_port->ssi_controller; > > + void __iomem *base = ssi_ctrl->base; > > + unsigned int port = ssi_channel->ssi_port->port_number; > > + unsigned int channel = ssi_channel->channel_number; > > + unsigned int sync; > > + int lch; > > + dma_addr_t dma_data; > > + u32 s_addr; > > + u16 tmp; > > + > > + if ((size < 1) || (data == NULL)) > > + return -EINVAL; > > + > > + clk_enable(ssi_ctrl->ssi_clk); > > + > > + lch = get_free_lch(ssi_ctrl); > > + if (lch >= SSI_NUM_LCH) { > > + dev_err(&ssi_ctrl->pdev->dev, "No free GDD logical " > > + "channels.\n"); > > + clk_disable(ssi_ctrl->ssi_clk); > > + return -EBUSY; /* No free GDD logical channels. */ > > + } > > + /* NOTE: Gettting a free gdd logical channel and > > + * reserve it must be done atomicaly. */ > > + ssi_channel->write_data.lch = lch; > > + > > + sync = get_sync(port, channel, SSI_SYNC_WRITE); > > + dma_data = dma_map_single(NULL, data, size * 4, DMA_TO_DEVICE); > > + dma_sync_single(NULL, dma_data, size * 4, DMA_TO_DEVICE); > > + > > + tmp = SSI_SRC_SINGLE_ACCESS0 | > > + SSI_SRC_MEMORY_PORT | > > + SSI_DST_SINGLE_ACCESS0 | > > + SSI_DST_PERIPHERAL_PORT | > > + SSI_DATA_TYPE_S32; > > + ssi_outw(tmp, base, SSI_GDD_CSDP_REG(lch)); > > + tmp = SSI_SRC_AMODE_POSTINC | SSI_DST_AMODE_CONST | sync; > > + ssi_outw(tmp, base, SSI_GDD_CCR_REG(lch)); > > + ssi_outw((SSI_BLOCK_IE | SSI_TOUT_IE), base, SSI_GDD_CICR_REG(lch)); > > + s_addr = (u32)base + SSI_SST_BUFFER_CH_REG(port, channel); > > + ssi_outl(s_addr, base, SSI_GDD_CDSA_REG(lch)); > > + ssi_outl(dma_data, base, SSI_GDD_CSSA_REG(lch)); > > + ssi_outw(size, base, SSI_GDD_CEN_REG(lch)); > > + ssi_outl_or(SSI_GDD_LCH(lch), base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > > + ssi_outw_or(SSI_CCR_ENABLE, base, SSI_GDD_CCR_REG(lch)); a bit of spacing up here will increase readability. > > + > > + return 0; > > +} > > + > > +/* > > + * ssi_driver_read_dma - Program GDD [DMA] to write data to memory from > > + * the ssi channel buffer. > > + * @ssi_channel - pointer to the ssi_channel to read data from. > > + * @data - 32-bit word pointer where to store the incoming data. > > + * @size - Number of 32bit words to be transfered to the buffer. > > + * > > + * ssi_controller lock must be hold before calling this function. > > + */ > > +int ssi_driver_read_dma(struct ssi_channel *ssi_channel, u32 *data, > > + unsigned int count) > > +{ > > + struct ssi_dev *ssi_ctrl = ssi_channel->ssi_port->ssi_controller; > > + void __iomem *base = ssi_ctrl->base; > > + unsigned int port = ssi_channel->ssi_port->port_number; > > + unsigned int channel = ssi_channel->channel_number; > > + unsigned int sync; > > + unsigned int lch; > > + dma_addr_t dma_data; > > + u32 d_addr; > > + u16 tmp; > > + > > + clk_enable(ssi_ctrl->ssi_clk); > > + lch = get_free_lch(ssi_ctrl); > > + if (lch >= SSI_NUM_LCH) { > > + dev_err(&ssi_ctrl->pdev->dev, "No free GDD logical " > > + "channels.\n"); > > + clk_disable(ssi_ctrl->ssi_clk); > > + return -EBUSY; /* No free GDD logical channels. */ > > + } > > + /* > > + * NOTE: Gettting a free gdd logical channel and > > + * reserve it must be done atomicaly. > > + */ > > + ssi_channel->read_data.lch = lch; > > + > > + sync = get_sync(port, channel, SSI_SYNC_READ); > > + > > + dma_data = dma_map_single(NULL, data, count * 4, DMA_FROM_DEVICE); > > + > > + tmp = SSI_DST_SINGLE_ACCESS0 | > > + SSI_DST_MEMORY_PORT | > > + SSI_SRC_SINGLE_ACCESS0 | > > + SSI_SRC_PERIPHERAL_PORT | > > + SSI_DATA_TYPE_S32; > > + ssi_outw(tmp, base, SSI_GDD_CSDP_REG(lch)); > > + tmp = SSI_DST_AMODE_POSTINC | SSI_SRC_AMODE_CONST | sync; > > + ssi_outw(tmp, base, SSI_GDD_CCR_REG(lch)); > > + ssi_outw((SSI_BLOCK_IE | SSI_TOUT_IE), base, SSI_GDD_CICR_REG(lch)); > > + d_addr = (u32)base + SSI_SSR_BUFFER_CH_REG(port, channel); don't keep casting the addresses, find a better solution. If you need to keep casting them around between void __iomem * and some unsigned, your code needs attention. Ditto to all other occurrencies of this. > > + ssi_outl(d_addr, base, SSI_GDD_CSSA_REG(lch)); > > + ssi_outl((u32)dma_data, base, SSI_GDD_CDSA_REG(lch)); > > + ssi_outw(count, base, SSI_GDD_CEN_REG(lch)); ditto. > > + > > + ssi_outl_or(SSI_GDD_LCH(lch), base, SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > > + ssi_outw_or(SSI_CCR_ENABLE, base, SSI_GDD_CCR_REG(lch)); > > + > > + return 0; > > +} > > + > > +void ssi_driver_cancel_write_dma(struct ssi_channel *ssi_ch) > > +{ > > + int lch = ssi_ch->write_data.lch; > > + unsigned int port = ssi_ch->ssi_port->port_number; > > + unsigned int channel = ssi_ch->channel_number; > > + struct ssi_dev *ssi_ctrl = ssi_ch->ssi_port->ssi_controller; > > + u32 ccr; > > + > > + if (lch < 0) > > + return; > > + > > + clk_enable(ssi_ctrl->ssi_clk); > > + ccr = ssi_inw(ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); > > + if (!(ccr & SSI_CCR_ENABLE)) { > > + dev_dbg(&ssi_ch->dev->device, LOG_NAME "Write cancel on not " > > + "enabled logical channel %d CCR REG 0x%08X\n", lch, ccr); > > + clk_disable(ssi_ctrl->ssi_clk); > > + return; > > + } > > + > > + ssi_outw_and(~SSI_CCR_ENABLE, ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); > > + ssi_outl_and(~SSI_GDD_LCH(lch), ssi_ctrl->base, > > + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > > + ssi_outl(SSI_GDD_LCH(lch), ssi_ctrl->base, > > + SSI_SYS_GDD_MPU_IRQ_STATUS_REG); > > + > > + ssi_outl_and(~NOTFULL(channel), ssi_ctrl->base, > > + SSI_SST_BUFSTATE_REG(port)); > > + > > + any reason why one blank line isn't enough ? > > + ssi_ch->write_data.addr = NULL; > > + ssi_ch->write_data.size = 0; > > + ssi_ch->write_data.lch = -1; > > + clk_disable(ssi_ctrl->ssi_clk); > > + clk_disable(ssi_ctrl->ssi_clk); clk_disable is duplicated. > > +} > > + > > +void ssi_driver_cancel_read_dma(struct ssi_channel *ssi_ch) > > +{ > > + int lch = ssi_ch->read_data.lch; > > + struct ssi_dev *ssi_ctrl = ssi_ch->ssi_port->ssi_controller; > > + unsigned int port = ssi_ch->ssi_port->port_number; > > + unsigned int channel = ssi_ch->channel_number; > > + u32 reg; > > + > > + if (lch < 0) > > + return; > > + > > + clk_enable(ssi_ctrl->ssi_clk); > > + reg = ssi_inw(ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); > > + if (!(reg & SSI_CCR_ENABLE)) { > > + dev_dbg(&ssi_ch->dev->device, LOG_NAME "Read cancel on not " > > + "enable logical channel %d CCR REG 0x%08X\n", lch, reg); > > + clk_disable(ssi_ctrl->ssi_clk); > > + return; > > + } > > + > > + ssi_outw_and(~SSI_CCR_ENABLE, ssi_ctrl->base, SSI_GDD_CCR_REG(lch)); > > + ssi_outl_and(~SSI_GDD_LCH(lch), ssi_ctrl->base, > > + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > > + ssi_outl(SSI_GDD_LCH(lch), ssi_ctrl->base, > > + SSI_SYS_GDD_MPU_IRQ_STATUS_REG); > > + > > + ssi_outl_and(~NOTEMPTY(channel), ssi_ctrl->base, > > + SSI_SSR_BUFSTATE_REG(port)); > > + > > + ssi_ch->read_data.addr = NULL; > > + ssi_ch->read_data.size = 0; > > + ssi_ch->read_data.lch = -1; > > + clk_disable(ssi_ctrl->ssi_clk); > > + clk_disable(ssi_ctrl->ssi_clk); ditto. > > +} > > + > > +static void dma_read_cb(struct ssi_dev *ctrl, unsigned int port, > > + unsigned int channel) take a few tabs away from the second line. > > +{ > > + struct ssi_channel *ch = &ctrl->ssi_port[port - 1].ssi_channel[channel]; > > + > > + ch->ops.read_done(ch->dev); > > +} > > + > > +static void dma_write_cb(struct ssi_dev *ctrl, unsigned int port, > > + unsigned int channel) take a few tabs away from the second line. > > +{ > > + struct ssi_channel *ch = &ctrl->ssi_port[port - 1].ssi_channel[channel]; > > + > > + ch->ops.write_done(ch->dev); > > +} > > + > > +static void do_gdd_lch(struct ssi_dev *ssi_ctrl, unsigned int gdd_lch) > > +{ > > + void __iomem *base = ssi_ctrl->base; > > + unsigned int port; > > + unsigned int channel; > > + u32 sync; > > + u32 gdd_csr; > > + dma_addr_t dma_h; > > + size_t size; > > + > > + sync = ssi_inw(base, SSI_GDD_CCR_REG(gdd_lch)) & SSI_CCR_SYNC_MASK; > > + port = get_sync_port(sync); > > + channel = get_sync_channel(sync); > > + > > + spin_lock(&ssi_ctrl->lock); > > + > > + ssi_outl_and(~SSI_GDD_LCH(gdd_lch), base, > > + SSI_SYS_GDD_MPU_IRQ_ENABLE_REG); > > + gdd_csr = ssi_inw(base, SSI_GDD_CSR_REG(gdd_lch)); > > + > > + if (!(gdd_csr & SSI_CSR_TOUR)) { > > + if (get_sync_type(sync) == SSI_SYNC_READ) { > > + dma_h = ssi_inl(base, SSI_GDD_CDSA_REG(gdd_lch)); > > + size = ssi_inw(base, SSI_GDD_CEN_REG(gdd_lch)) * 4; > > + dma_sync_single(NULL, dma_h, size, DMA_FROM_DEVICE); > > + rst_ch_read(ssi_ctrl, port, channel); > > + spin_unlock(&ssi_ctrl->lock); > > + dma_read_cb(ssi_ctrl, port, channel); > > + } else { > > + rst_ch_write(ssi_ctrl, port, channel); > > + spin_unlock(&ssi_ctrl->lock); > > + dma_write_cb(ssi_ctrl, port, channel); > > + } > > + } else { > > + dev_err(&ssi_ctrl->pdev->dev, "Error on GDD transfer " > > + "on gdd channel %d port %d channel %d\n", > > + gdd_lch, port, channel); > > + spin_unlock(&ssi_ctrl->lock); > > + ssi_port_event_handler(&ssi_ctrl->ssi_port[port - 1], > > + SSI_EVENT_ERROR, NULL); > > + } > > +} > > + > > +void do_ssi_gdd_tasklet(unsigned long device) > > +{ > > + struct ssi_dev *ssi_ctrl = (struct ssi_dev *)device; > > + void __iomem *base = ssi_ctrl->base; > > + unsigned int gdd_lch = 0; > > + u32 status_reg = 0; > > + u32 lch_served = 0; > > + > > + clk_enable(ssi_ctrl->ssi_clk); > > + > > + status_reg = ssi_inl(base, SSI_SYS_GDD_MPU_IRQ_STATUS_REG); > > + > > + for (gdd_lch = 0; gdd_lch < SSI_NUM_LCH; gdd_lch++) { > > + if (status_reg & SSI_GDD_LCH(gdd_lch)) { > > + do_gdd_lch(ssi_ctrl, gdd_lch); > > + lch_served |= SSI_GDD_LCH(gdd_lch); > > + clk_disable(ssi_ctrl->ssi_clk); clk_disable() will be called as many times as this loop executes and the if condition is met. Fix it. > > + } > > + } > > + > > + ssi_outl(lch_served, base, SSI_SYS_GDD_MPU_IRQ_STATUS_REG); > > + clk_disable(ssi_ctrl->ssi_clk); > > + > > + enable_irq(ssi_ctrl->gdd_irq); > > +} > > + > > +irqreturn_t ssi_gdd_mpu_handler(int irq, void *ssi_controller) > > +{ > > + struct ssi_dev *ssi_ctrl = (struct ssi_dev *)ssi_controller; > > + > > + tasklet_hi_schedule(&ssi_ctrl->ssi_gdd_tasklet); > > + disable_irq_nosync(ssi_ctrl->gdd_irq); > > + > > + return IRQ_HANDLED; > > +} > > diff --git a/drivers/misc/ssi/ssi_driver_if.c b/drivers/misc/ssi/ssi_driver_if.c > > new file mode 100644 > > index 0000000..385467e > > --- /dev/null > > +++ b/drivers/misc/ssi/ssi_driver_if.c > > @@ -0,0 +1,335 @@ > > +/* > > + * ssi_driver_if.c > > + * > > + * Implements SSI hardware driver interfaces for the upper layers. > > + * > > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > > + * > > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > > + * > > + * This program is free software; you can redistribute it and/or > > + * modify it under the terms of the GNU General Public License > > + * version 2 as published by the Free Software Foundation. > > + * > > + * This program is distributed in the hope that it will be useful, but > > + * WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > + * General Public License for more details. > > + * > > + * You should have received a copy of the GNU General Public License > > + * along with this program; if not, write to the Free Software > > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > > + * 02110-1301 USA > > + */ > > + > > +#include "ssi_driver.h" > > + > > +/** > > + * ssi_open - open a ssi device channel. > > + * @dev - Reference to the ssi device channel to be openned. > > + * > > + * Returns 0 on success, -EINVAL on bad parameters, -EBUSY if is already opened. > > + */ > > +int ssi_open(struct ssi_device *dev) > > +{ > > + struct ssi_channel *ch; > > + struct ssi_port *port; > > + struct ssi_dev *ssi_ctrl; > > + > > + if (!dev || !dev->ch) { > > + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); > > + return -EINVAL; > > + } > > + > > + ch = dev->ch; > > + if (!ch->ops.read_done || !ch->ops.write_done) { > > + dev_err(&dev->device, "Trying to open with no callbacks " > > + "registered\n"); > > + return -EINVAL; > > + } > > + port = ch->ssi_port; > > + ssi_ctrl = port->ssi_controller; > > + spin_lock_bh(&ssi_ctrl->lock); > > + if (ch->flags & SSI_CH_OPEN) { > > + dev_err(&dev->device, "Port %d Channel %d already OPENED\n", > > + dev->n_p, dev->n_ch); > > + spin_unlock_bh(&ssi_ctrl->lock); > > + return -EBUSY; > > + } > > + clk_enable(ssi_ctrl->ssi_clk); > > + ch->flags |= SSI_CH_OPEN; > > + ssi_outl_or(SSI_ERROROCCURED | SSI_BREAKDETECTED, ssi_ctrl->base, > > + SSI_SYS_MPU_ENABLE_REG(port->port_number, port->n_irq)); > > + clk_disable(ssi_ctrl->ssi_clk); > > + spin_unlock_bh(&ssi_ctrl->lock); > > + > > + return 0; > > +} > > +EXPORT_SYMBOL(ssi_open); > > + > > +/** > > + * ssi_write - write data into the ssi device channel > > + * @dev - reference to the ssi device channel to write into. > > + * @data - pointer to a 32-bit word data to be written. > > + * @count - number of 32-bit word to be written. > > + * > > + * Return 0 on sucess, a negative value on failure. > > + * A success values only indicates that the request has been accepted. plural or singular ?? > > + * Transfer is only completed when the write_done callback is called. > > + * > > + */ > > +int ssi_write(struct ssi_device *dev, u32 *data, unsigned int count) > > +{ > > + struct ssi_channel *ch; > > + int err; > > + > > + if (unlikely(!dev || !dev->ch || !data || (count <= 0))) { if someone doesn't meet this conditions they deserve to oops I'd say. It would avoid badly written drivers. > > + dev_err(&dev->device, "Wrong paramenters " > > + "ssi_device %p data %p count %d", dev, data, count); > > + return -EINVAL; > > + } > > + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { > > + dev_err(&dev->device, "SSI device NOT open\n"); > > + return -EINVAL; > > + } > > + > > + ch = dev->ch; > > + spin_lock_bh(&ch->ssi_port->ssi_controller->lock); > > + ch->write_data.addr = data; > > + ch->write_data.size = count; how about matching the names ?? > > + > > + if (count == 1) > > + err = ssi_driver_write_interrupt(ch, data); > > + else > > + err = ssi_driver_write_dma(ch, data, count); > > + > > + if (unlikely(err < 0)) { > > + ch->write_data.addr = NULL; > > + ch->write_data.size = 0; > > + } > > + spin_unlock_bh(&ch->ssi_port->ssi_controller->lock); > > + > > + return err; > > + > > +} > > +EXPORT_SYMBOL(ssi_write); > > + > > +/** > > + * ssi_read - read data from the ssi device channel > > + * @dev - ssi device channel reference to read data from. > > + * @data - pointer to a 32-bit word data to store the data. > > + * @count - number of 32-bit word to be stored. > > + * > > + * Return 0 on sucess, a negative value on failure. > > + * A success values only indicates that the request has been accepted. > > + * Data is only available in the buffer when the read_done callback is called. > > + * > > + */ > > +int ssi_read(struct ssi_device *dev, u32 *data, unsigned int count) > > +{ > > + struct ssi_channel *ch; > > + int err; > > + > > + if (unlikely(!dev || !dev->ch || !data || (count <= 0))) { ditto. > > + dev_err(&dev->device, "Wrong paramenters " > > + "ssi_device %p data %p count %d", dev, data, count); > > + return -EINVAL; > > + } > > + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { > > + dev_err(&dev->device, "SSI device NOT open\n"); > > + return -EINVAL; > > + } > > + > > + ch = dev->ch; > > + spin_lock_bh(&ch->ssi_port->ssi_controller->lock); > > + ch->read_data.addr = data; > > + ch->read_data.size = count; how about matching the names ?? > > + > > + if (count == 1) > > + err = ssi_driver_read_interrupt(ch, data); > > + else > > + err = ssi_driver_read_dma(ch, data, count); > > + > > + if (unlikely(err < 0)) { > > + ch->read_data.addr = NULL; > > + ch->read_data.size = 0; > > + } > > + spin_unlock_bh(&ch->ssi_port->ssi_controller->lock); > > + > > + return err; > > +} > > +EXPORT_SYMBOL(ssi_read); > > + > > +void __ssi_write_cancel(struct ssi_channel *ch) > > +{ > > + if (ch->write_data.size == 1) > > + ssi_driver_cancel_write_interrupt(ch); > > + else if (ch->write_data.size > 1) > > + ssi_driver_cancel_write_dma(ch); > > + > > +} > > +/** > > + * ssi_write_cancel - Cancel pending write request. > > + * @dev - ssi device channel where to cancel the pending write. > > + * > > + * write_done() callback will not be called after sucess of this function. > > + */ > > +void ssi_write_cancel(struct ssi_device *dev) > > +{ > > + if (unlikely(!dev || !dev->ch)) { > > + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); > > + return; > > + } > > + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { > > + dev_err(&dev->device, "SSI device NOT open\n"); > > + return; > > + } > > + > > + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); > > + __ssi_write_cancel(dev->ch); > > + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); > > +} > > +EXPORT_SYMBOL(ssi_write_cancel); > > + > > +void __ssi_read_cancel(struct ssi_channel *ch) > > +{ > > + if (ch->read_data.size == 1) > > + ssi_driver_cancel_read_interrupt(ch); > > + else if (ch->read_data.size > 1) > > + ssi_driver_cancel_read_dma(ch); > > +} > > + > > +/** > > + * ssi_read_cancel - Cancel pending read request. > > + * @dev - ssi device channel where to cancel the pending read. > > + * > > + * read_done() callback will not be called after sucess of this function. > > + */ > > +void ssi_read_cancel(struct ssi_device *dev) > > +{ > > + if (unlikely(!dev || !dev->ch)) { > > + pr_err(LOG_NAME "Wrong SSI device %p\n", dev); > > + return; > > + } > > + > > + if (unlikely(!(dev->ch->flags & SSI_CH_OPEN))) { > > + dev_err(&dev->device, "SSI device NOT open\n"); > > + return; > > + } > > + > > + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); > > + __ssi_read_cancel(dev->ch); > > + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); > > + > > +} > > +EXPORT_SYMBOL(ssi_read_cancel); > > + > > +/** > > + * ssi_ioctl - SSI I/O control > > + * @dev - ssi device channel reference to apply the I/O control > > + * (or port associated to it) ^ trailing whitespace and you can remove a few tabs from the second line > > + * @command - SSI I/O control command > > + * @arg - parameter associated to the control command. NULL, if no parameter. > > + * > > + * Return 0 on sucess, a negative value on failure. > > + * > > + */ > > +int ssi_ioctl(struct ssi_device *dev, unsigned int command, void *arg) > > +{ > > + struct ssi_channel *ch; > > + struct ssi_dev *ssi_ctrl; > > + void __iomem *base; > > + unsigned int port, channel; > > + u32 wake; > > + int err = 0; > > + > > + if (unlikely((!dev) || > > + (!dev->ch) || > > + (!dev->ch->ssi_port) || > > + (!dev->ch->ssi_port->ssi_controller)) || > > + (!(dev->ch->flags & SSI_CH_OPEN))) { > > + pr_err(LOG_NAME "SSI IOCTL Invalid parameter\n"); > > + return -EINVAL; > > + } > > + > > + remove one blank line. > > + ch = dev->ch; > > + ssi_ctrl = ch->ssi_port->ssi_controller; > > + port = ch->ssi_port->port_number; > > + channel = ch->channel_number; > > + base = ssi_ctrl->base; > > + clk_enable(ssi_ctrl->ssi_clk); > > + > > + switch (command) { > > + case SSI_IOCTL_WAKE_UP: > > + /* We only claim once the wake line per channel */ > > + wake = ssi_inl(base, SSI_SYS_WAKE_REG(port)); > > + if (!(wake & SSI_WAKE(channel))) { > > + clk_enable(ssi_ctrl->ssi_clk); > > + ssi_outl(SSI_WAKE(channel), base, > > + SSI_SYS_SET_WAKE_REG(port)); > > + } > > + break; > > + case SSI_IOCTL_WAKE_DOWN: > > + wake = ssi_inl(base, SSI_SYS_WAKE_REG(port)); > > + if ((wake & SSI_WAKE(channel))) { > > + ssi_outl(SSI_WAKE(channel), base, > > + SSI_SYS_CLEAR_WAKE_REG(port)); > > + clk_disable(ssi_ctrl->ssi_clk); > > + } > > + break; > > + case SSI_IOCTL_SEND_BREAK: > > + ssi_outl(1, base, SSI_SST_BREAK_REG(port)); > > + break; > > + case SSI_IOCTL_WAKE: > > + if (arg == NULL) > > + err = -EINVAL; > > + else > > + *(u32 *)arg = ssi_inl(base, SSI_SYS_WAKE_REG(port)); > > + break; > > + default: > > + err = -ENOIOCTLCMD; > > + break; > > + } > > + > > + clk_disable(ssi_ctrl->ssi_clk); > > + > > + return err; > > +} > > +EXPORT_SYMBOL(ssi_ioctl); > > + > > +/** > > + * ssi_close - close given ssi device channel > > + * @dev - reference to ssi device channel. > > + */ > > +void ssi_close(struct ssi_device *dev) > > +{ > > + if (!dev || !dev->ch) { > > + pr_err(LOG_NAME "Trying to close wrong SSI device %p\n", dev); > > + return; > > + } > > + > > + spin_lock_bh(&dev->ch->ssi_port->ssi_controller->lock); > > + if (dev->ch->flags & SSI_CH_OPEN) { > > + dev->ch->flags &= ~SSI_CH_OPEN; > > + __ssi_write_cancel(dev->ch); > > + __ssi_read_cancel(dev->ch); > > + } > > + spin_unlock_bh(&dev->ch->ssi_port->ssi_controller->lock); > > + > > +} > > +EXPORT_SYMBOL(ssi_close); > > + > > +/** > > + * ssi_dev_set_cb - register read_done() and write_done() callbacks. > > + * @dev - reference to ssi device channel where callbacks are associated. > > + * @r_cb - callback to signal read transfer completed. > > + * @w_cb - callback to signal write transfer completed. I'd call them read and write. Or maybe have only one 'complete' and it handles read and write inside itself, would have to think a bit more about this. > > + */ > > +void ssi_dev_set_cb(struct ssi_device *dev, void (*r_cb)(struct ssi_device *dev) > > + , void (*w_cb)(struct ssi_device *dev)) > > +{ > > + dev->ch->ops.read_done = r_cb; > > + dev->ch->ops.write_done = w_cb; > > +} > > +EXPORT_SYMBOL(ssi_dev_set_cb); > > diff --git a/drivers/misc/ssi/ssi_driver_int.c b/drivers/misc/ssi/ssi_driver_int.c > > new file mode 100644 > > index 0000000..6491e48 > > --- /dev/null > > +++ b/drivers/misc/ssi/ssi_driver_int.c > > @@ -0,0 +1,232 @@ > > +/* > > + * ssi_driver_int.c > > + * > > + * Implements SSI interrupt functionality. > > + * > > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > > + * > > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > > + * > > + * This program is free software; you can redistribute it and/or > > + * modify it under the terms of the GNU General Public License > > + * version 2 as published by the Free Software Foundation. > > + * > > + * This program is distributed in the hope that it will be useful, but > > + * WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > + * General Public License for more details. > > + * > > + * You should have received a copy of the GNU General Public License > > + * along with this program; if not, write to the Free Software > > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > > + * 02110-1301 USA > > + */ > > +#include "ssi_driver.h" > > + > > +static void reset_ch_read(struct ssi_channel *ch) > > +{ > > + ch->read_data.addr = NULL; > > + ch->read_data.size = 0; > > + ch->read_data.lch = -1; > > +} > > + > > +static void reset_ch_write(struct ssi_channel *ch) > > +{ > > + ch->write_data.addr = NULL; > > + ch->write_data.size = 0; > > + ch->write_data.lch = -1; > > +} > > + > > +int ssi_driver_write_interrupt(struct ssi_channel *ch, u32 *data) > > +{ > > + struct ssi_port *p = ch->ssi_port; > > + unsigned int port = p->port_number; > > + unsigned int channel = ch->channel_number; > > + > > + clk_enable(p->ssi_controller->ssi_clk); > > + ssi_outl_or(SSI_SST_DATAACCEPT(channel), p->ssi_controller->base, > > + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > > + > > + one blank line is enough. > > + return 0; > > +} > > + > > +int ssi_driver_read_interrupt(struct ssi_channel *ch, u32 *data) > > +{ > > + struct ssi_port *p = ch->ssi_port; > > + unsigned int port = p->port_number; > > + unsigned int channel = ch->channel_number; > > + > > + clk_enable(p->ssi_controller->ssi_clk); > > + > > + ssi_outl_or(SSI_SSR_DATAAVAILABLE(channel), p->ssi_controller->base, > > + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > > + > > + clk_disable(p->ssi_controller->ssi_clk); > > + > > + return 0; > > +} > > + > > +void ssi_driver_cancel_write_interrupt(struct ssi_channel *ch) > > +{ > > + struct ssi_port *p = ch->ssi_port; > > + unsigned int port = p->port_number; > > + unsigned int channel = ch->channel_number; > > + void __iomem *base = p->ssi_controller->base; > > + u32 enable; > > + > > + clk_enable(p->ssi_controller->ssi_clk); > > + > > + enable = ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > > + if (!(enable & SSI_SST_DATAACCEPT(channel))) { > > + dev_dbg(&ch->dev->device, LOG_NAME "Write cancel on not " > > + "enabled channel %d ENABLE REG 0x%08X", channel, enable); > > + clk_disable(p->ssi_controller->ssi_clk); > > + return; > > + } > > + ssi_outl_and(~SSI_SST_DATAACCEPT(channel), base, > > + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > > + ssi_outl_and(~NOTFULL(channel), base, SSI_SST_BUFSTATE_REG(port)); > > + reset_ch_write(ch); > > + > > + clk_disable(p->ssi_controller->ssi_clk); > > + clk_disable(p->ssi_controller->ssi_clk); > > + remove this extra line > > +} > > + > > +void ssi_driver_cancel_read_interrupt(struct ssi_channel *ch) > > +{ > > + struct ssi_port *p = ch->ssi_port; > > + unsigned int port = p->port_number; > > + unsigned int channel = ch->channel_number; > > + void __iomem *base = p->ssi_controller->base; > > + > > + clk_enable(p->ssi_controller->ssi_clk); > > + > > + ssi_outl_and(~SSI_SSR_DATAAVAILABLE(channel), base, > > + SSI_SYS_MPU_ENABLE_REG(port, p->n_irq)); > > + ssi_outl_and(~NOTEMPTY(channel), base, SSI_SSR_BUFSTATE_REG(port)); > > + reset_ch_read(ch); > > + > > + clk_disable(p->ssi_controller->ssi_clk); > > +} > > + > > +static void do_channel_tx(struct ssi_channel *ch) > > +{ > > + struct ssi_dev *ssi_ctrl = ch->ssi_port->ssi_controller; > > + void __iomem *base = ssi_ctrl->base; > > + unsigned int n_ch; > > + unsigned int n_p; > > + unsigned int irq; > > + > > + n_ch = ch->channel_number; > > + n_p = ch->ssi_port->port_number; > > + irq = ch->ssi_port->n_irq; > > + > > + spin_lock(&ssi_ctrl->lock); > > + > > + if (ch->write_data.addr == NULL) { > > + ssi_outl_and(~SSI_SST_DATAACCEPT(n_ch), base, > > + SSI_SYS_MPU_ENABLE_REG(n_p, irq)); > > + reset_ch_write(ch); > > + spin_unlock(&ssi_ctrl->lock); > > + clk_disable(ssi_ctrl->ssi_clk); > > + (*ch->ops.write_done)(ch->dev); > > + } else { > > + ssi_outl(*(ch->write_data.addr), base, > > + SSI_SST_BUFFER_CH_REG(n_p, n_ch)); > > + ch->write_data.addr = NULL; > > + spin_unlock(&ssi_ctrl->lock); > > + } > > +} > > + > > +static void do_channel_rx(struct ssi_channel *ch) > > +{ > > + struct ssi_dev *ssi_ctrl = ch->ssi_port->ssi_controller; > > + void __iomem *base = ch->ssi_port->ssi_controller->base; > > + unsigned int n_ch; > > + unsigned int n_p; > > + unsigned int irq; > > + > > + n_ch = ch->channel_number; > > + n_p = ch->ssi_port->port_number; > > + irq = ch->ssi_port->n_irq; > > + > > + spin_lock(&ssi_ctrl->lock); > > + > > + *(ch->read_data.addr) = ssi_inl(base, SSI_SSR_BUFFER_CH_REG(n_p, n_ch)); > > + > > + ssi_outl_and(~SSI_SSR_DATAAVAILABLE(n_ch), base, > > + SSI_SYS_MPU_ENABLE_REG(n_p, irq)); > > + reset_ch_read(ch); > > + > > + spin_unlock(&ssi_ctrl->lock); > > + > > + (*ch->ops.read_done)(ch->dev); > > +} > > + > > +void do_ssi_tasklet(unsigned long ssi_port) > > +{ > > + struct ssi_port *pport = (struct ssi_port *)ssi_port; > > + struct ssi_dev *ssi_ctrl = pport->ssi_controller; > > + void __iomem *base = ssi_ctrl->base; > > + unsigned int port = pport->port_number; > > + unsigned int channel = 0; > > + unsigned int irq = pport->n_irq; > > + u32 status_reg; > > + u32 enable_reg; > > + u32 ssr_err_reg; > > + u32 channels_served; > > + > > + clk_enable(ssi_ctrl->ssi_clk); > > + > > + channels_served = 0; > > + status_reg = ssi_inl(base, SSI_SYS_MPU_STATUS_REG(port, irq)); > > + enable_reg = ssi_inl(base, SSI_SYS_MPU_ENABLE_REG(port, irq)); > > + > > + for (channel = 0; channel < pport->max_ch; channel++) { > > + if ((status_reg & SSI_SST_DATAACCEPT(channel)) && > > + (enable_reg & SSI_SST_DATAACCEPT(channel))) { > > + do_channel_tx(&pport->ssi_channel[channel]); > > + channels_served |= SSI_SST_DATAACCEPT(channel); > > + } > > + > > + if ((status_reg & SSI_SSR_DATAAVAILABLE(channel)) && > > + (enable_reg & SSI_SSR_DATAAVAILABLE(channel))) { > > + do_channel_rx(&pport->ssi_channel[channel]); > > + channels_served |= SSI_SSR_DATAAVAILABLE(channel); > > + } > > + } > > + > > + if ((status_reg & SSI_BREAKDETECTED) && > > + (enable_reg & SSI_BREAKDETECTED)) { > > + dev_info(&ssi_ctrl->pdev->dev, > > + "Hardware BREAK on port %d\n", port); > > + ssi_outl(0, base, SSI_SSR_BREAK_REG(port)); > > + ssi_port_event_handler(pport, SSI_EVENT_BREAK_DETECTED, NULL); > > + } > > + > > + if (status_reg & SSI_ERROROCCURED) { > > + ssr_err_reg = ssi_inl(base, SSI_SSR_ERROR_REG(port)); > > + dev_err(&ssi_ctrl->pdev->dev, "SSI ERROR Port %d: 0x%02x\n", > > + port, ssr_err_reg); > > + ssi_outl(ssr_err_reg, base, SSI_SSR_ERRORACK_REG(port)); > > + ssi_port_event_handler(pport, SSI_EVENT_ERROR, NULL); > > + } > > + > > + ssi_outl((channels_served | SSI_ERROROCCURED | SSI_BREAKDETECTED), base, > > + SSI_SYS_MPU_STATUS_REG(port, irq)); > > + > > + clk_disable(ssi_ctrl->ssi_clk); > > + enable_irq(pport->irq); > > +} I might have more comments on this later. But I'll wait until you come with the new version and these comments fixed to look at it again. -- balbi ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC][PATCH 2/5] OMAP SSI driver interface 2008-10-03 11:52 ` [RFC][PATCH 2/5] OMAP SSI driver interface Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 3/5] OMAP SSI driver code Carlos Chinea @ 2008-10-06 23:29 ` Felipe Balbi 2008-10-07 1:03 ` David Brownell 1 sibling, 1 reply; 14+ messages in thread From: Felipe Balbi @ 2008-10-06 23:29 UTC (permalink / raw) To: Carlos Chinea; +Cc: linux-kernel, linux-omap On Fri, Oct 03, 2008 at 02:52:27PM +0300, Carlos Chinea wrote: add a patch description body here. > Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> > --- > include/linux/ssi_driver_if.h | 137 +++++++++++++++++++++++++++++++++++++++++ > 1 files changed, 137 insertions(+), 0 deletions(-) > create mode 100644 include/linux/ssi_driver_if.h > > diff --git a/include/linux/ssi_driver_if.h b/include/linux/ssi_driver_if.h > new file mode 100644 > index 0000000..3379dd0 > --- /dev/null > +++ b/include/linux/ssi_driver_if.h why wouldn't ssi.h be enough as a header name ? looks much better and much easier to type: #include <linux/ssi.h> > @@ -0,0 +1,137 @@ > +/* > + * ssi_driver_if.h > + * > + * Header for the SSI driver low level interface. > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > +#ifndef __SSI_DRIVER_IF_H__ > +#define __SSI_DRIVER_IF_H__ > + > +#include <linux/device.h> > + > +#define SSI_IOMEM_NAME "SSI_IO_MEM" > +#define SSI_P1_MPU_IRQ0_NAME "SSI_P1_MPU_IRQ0" > +#define SSI_P2_MPU_IRQ0_NAME "SSI_P2_MPU_IRQ0" > +#define SSI_P1_MPU_IRQ1_NAME "SSI_P1_MPU_IRQ1" > +#define SSI_P2_MPU_IRQ1_NAME "SSI_P2_MPU_IRQ1" > +#define SSI_GDD_MPU_IRQ_NAME "GDD_MPU_IRQ" hmm... I wonder you really need these ? Maybe I have to wait until I review the other patches but at least for the irq names, they look weird. Are them used for request_irq() only ? If so, remove them and pass it in the driver. There's no need for such a global definition. > + > +/* IRQ values */ > +#define SSI_P1_MPU_IRQ0 67 > +#define SSI_P2_MPU_IRQ0 68 > +#define SSI_P1_MPU_IRQ1 69 > +#define SSI_P2_MPU_IRQ1 70 > +#define SSI_GDD_MPU_IRQ 71 Most likely this will be platform_specific right ? So pass it to the driver using a struct resource. > + > +/* The number of ports handled by the driver. (MAX:2) */ > +#define SSI_MAX_PORTS 1 > + > +/* > + * Masks used to enable or disable the reception of certain hardware events > + * for the ssi_device_drivers > + */ > +#define SSI_EVENT_CLEAR 0x00 > +#define SSI_EVENT_MASK 0xFF > +#define SSI_EVENT_BREAK_DETECTED_MASK 0x01 > +#define SSI_EVENT_ERROR_MASK 0x02 > + > +#define ANY_SSI_CONTROLLER -1 > +#define ANY_CHANNEL -1 > +#define CHANNEL(channel) (1<<channel) CHANNEL is not generic enough name, use, at least, SSI_CHANNEL and add spaces around that left shift. > + > +enum { > + SSI_EVENT_BREAK_DETECTED = 0, > + SSI_EVENT_ERROR, > +}; > + > +enum { > + SSI_IOCTL_WAKE_UP, > + SSI_IOCTL_WAKE_DOWN, > + SSI_IOCTL_SEND_BREAK, > + SSI_IOCTL_WAKE, > +}; hmm... ioctls, let's if they're really needed later. > + > +/* Forward references */ > +struct ssi_device; > +struct ssi_dev; > +struct ssi_port; > +struct ssi_channel; > + > +struct ssi_port_pd { > + u32 tx_mode; > + u32 tx_frame_size; > + u32 divisor; > + u32 tx_ch; > + u32 arb_mode; > + u32 rx_mode; > + u32 rx_frame_size; > + u32 rx_ch; > + u32 timeout; > + u8 n_irq; > +}; > + > +struct ssi_platform_data { > + unsigned char *clk_name; please don't pass clock names via platform_data. It's ugly and we're having quite a big amount of work trying to find a solution to clean omap drivers. Looks like we're gonna introduce omap_clk_associate() which will associate the user device with the clock structure and introduce a clk alias name (called function name) to avoid the problem we have now with different omap versions (different clock names). > + struct ssi_dev *ssi_ctrl; > + struct ssi_port_pd *ports; > + u8 num_ports; > +}; > + > +struct ssi_device { > + int n_ctrl; > + unsigned int n_p; > + unsigned int n_ch; > + char modalias[BUS_ID_SIZE]; > + struct ssi_channel *ch; > + struct device device; > +}; > + > +#define to_ssi_device(dev) container_of(dev, struct ssi_device, device) > + > +struct ssi_device_driver { > + unsigned long ctrl_mask; > + unsigned long ch_mask[SSI_MAX_PORTS]; > + unsigned long event_mask; > + void (*port_event) (int c_id, unsigned int port, ^ trailing whitespace > + unsigned int event, void *arg); > + int (*probe)(struct ssi_device *dev); and then this will be the only bus not using the MODULE_DEVICE_TABLE, please fix. Introduce a MODULE_DEVICE_TABLE. i2c did it recently and it's quite simple. Most likely this will have a similar implementation. > + int (*remove)(struct ssi_device *dev); > + int (*suspend)(struct ssi_device *dev, > + pm_message_t mesg); > + int (*resume)(struct ssi_device *dev); > + struct device_driver driver; ^ trailing whitespace > +}; > + > +#define to_ssi_device_driver(drv) container_of(drv, \ > + struct ssi_device_driver, \ > + driver) > + > +int register_ssi_driver(struct ssi_device_driver *driver); let's try to keep a consistency with several other register and unregister functions in the kernel and rename this to ssi_register_driver(), likewise to ssi_unregister_driver() > +void unregister_ssi_driver(struct ssi_device_driver *driver); > +int ssi_open(struct ssi_device *dev); > +int ssi_write(struct ssi_device *dev, u32 *data, unsigned int count); > +void ssi_write_cancel(struct ssi_device *dev); > +int ssi_read(struct ssi_device *dev, u32 *data, unsigned int w_count); > +void ssi_read_cancel(struct ssi_device *dev); > +int ssi_ioctl(struct ssi_device *dev, unsigned int command, void *arg); > +void ssi_close(struct ssi_device *dev); > +void ssi_dev_set_cb(struct ssi_device *dev, void (*r_cb)(struct ssi_device *dev) > + , void (*w_cb)(struct ssi_device *dev)); ^ this comma should be in the previous line > +#endif #endif /* __SSI__ blablabla */ btw, a bit of kerneldoc wouldn't hurt. Please document all these structures. -- balbi ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC][PATCH 2/5] OMAP SSI driver interface 2008-10-06 23:29 ` [RFC][PATCH 2/5] OMAP SSI driver interface Felipe Balbi @ 2008-10-07 1:03 ` David Brownell 2008-10-07 11:56 ` Woodruff, Richard 0 siblings, 1 reply; 14+ messages in thread From: David Brownell @ 2008-10-07 1:03 UTC (permalink / raw) To: me; +Cc: Carlos Chinea, linux-kernel, linux-omap On Monday 06 October 2008, Felipe Balbi wrote: > looks much better and much easier to type: #include <linux/ssi.h> Does this work on non-OMAP kernels? If not, <mach/ssi.h> or similar would seem more sensible ... Agreed about there being way too many headers, too. ^ permalink raw reply [flat|nested] 14+ messages in thread
* RE: [RFC][PATCH 2/5] OMAP SSI driver interface 2008-10-07 1:03 ` David Brownell @ 2008-10-07 11:56 ` Woodruff, Richard 0 siblings, 0 replies; 14+ messages in thread From: Woodruff, Richard @ 2008-10-07 11:56 UTC (permalink / raw) To: David Brownell, me@felipebalbi.com Cc: Carlos Chinea, linux-kernel@vger.kernel.org, linux-omap@vger.kernel.org > From: linux-omap-owner@vger.kernel.org [mailto:linux-omap- > owner@vger.kernel.org] On Behalf Of David Brownell > On Monday 06 October 2008, Felipe Balbi wrote: > > looks much better and much easier to type: #include <linux/ssi.h> > > Does this work on non-OMAP kernels? If not, <mach/ssi.h> or > similar would seem more sensible ... Seems like some cousin might be multi vendor via MIPI HSI. http://www.mipi.org/wgoverview.shtml Regards, Richard W. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC][PATCH 1/5] OMAP SSI hardware interface definitions 2008-10-03 11:52 ` [RFC][PATCH 1/5] OMAP SSI hardware interface definitions Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 2/5] OMAP SSI driver interface Carlos Chinea @ 2008-10-06 23:16 ` Felipe Balbi 1 sibling, 0 replies; 14+ messages in thread From: Felipe Balbi @ 2008-10-06 23:16 UTC (permalink / raw) To: Carlos Chinea; +Cc: linux-kernel, linux-omap On Fri, Oct 03, 2008 at 02:52:26PM +0300, Carlos Chinea wrote: > > Signed-off-by: Carlos Chinea <carlos.chinea@nokia.com> > --- > arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h | 145 ++++++++++++++++++++ > .../plat-omap/include/mach/ssi/ssi_reg_common.h | 73 ++++++++++ > arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h | 56 ++++++++ > arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h | 65 +++++++++ > arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h | 107 ++++++++++++++ > 5 files changed, 446 insertions(+), 0 deletions(-) > create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h > create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_reg_common.h > create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h > create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h > create mode 100644 arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h > > diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h b/arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h > new file mode 100644 > index 0000000..5ed91cc > --- /dev/null > +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_gdd_reg.h > @@ -0,0 +1,145 @@ > +/* > + * ssi_gdd_reg.h > + * > + * Hardware defintions for SSI Controller GDD registers. > + * > + * HARDWARE: OMAP 2420, OMAP 3430 This is unnecessary, if we happen to use the same in omap4 we're gonna have to update. Please remove. > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > +#ifndef __SSI_GDD_REG_H__ > +#define __SSI_GDD_REG_H__ > + > +#include "ssi_reg_common.h" > + > +#define SSI_GDD_HW_ID_REG SSI_GDD_REG32(0x0000) this is not a good practice, use the correct offsets for all registers > +#define SSI_GDD_PPORT_ID_REG SSI_GDD_REG32(0x0010) > +#define SSI_GDD_MPORT_ID_REG SSI_GDD_REG32(0x0014) > + > +#define SSI_GDD_PPORT_SR_REG SSI_GDD_REG32(0x0020) > +# define SSI_PPORT_ACTIVE_LCH_NUMBER_MASK 0xFF > + > +#define SSI_GDD_MPORT_SR_REG SSI_GDD_REG32(0x0024) > +# define SSI_MPORT_ACTIVE_LCH_NUMBER_MASK 0xFF > + > +#define SSI_GDD_TEST_REG SSI_GDD_REG32(0x0040) ^ trailing whitespace > +# define SSI_TEST 0x1 > + > +#define SSI_GDD_GCR_REG SSI_GDD_REG32(0x0100) > +# define SSI_CLK_AUTOGATING_ON (1<<3) ^^ add spaces around << > +# define SSI_FREE (1<<2) > +# define SSI_SWITCH_OFF 0x1 > + > +#define SSI_GDD_GRST_REG SSI_GDD_REG32(0x0200) ^ trailing whitespace > +# define SSI_SWRESET 0x1 > + > +#define SSI_GDD_CSDP_BASE 0x0800 > +#define SSI_GDD_CSDP_OFFSET 0x40 > +#define SSI_GDD_CSDP_REG(channel) SSI_GDD_REG16(SSI_GDD_CSDP_BASE +\ ^ trailing whitespace > + (channel*SSI_GDD_CSDP_OFFSET)) ^ add spaces around * > +# define SSI_DST_BURST_EN_MASK 0xC000 > +# define SSI_DST_SINGLE_ACCESS0 0x0 > +# define SSI_DST_SINGLE_ACCESS (0x1<<14) > +# define SSI_DST_BURST_4X32_BIT (0x2<<14) > +# define SSI_DST_BURST_8x32_BIT (0x3<<14) /*NOTE: NOT SUPPORTED */ (1 << 14), (2 << 14) and (3 << 14) (up to 9) is enough. ^ add a space before NOTE > + > +# define SSI_DST_MASK 0x1E00 make all the hex numbers lower case > +# define SSI_DST_MEMORY_PORT (0x8<<9) > +# define SSI_DST_PERIPHERAL_PORT (0x9<<9) > + > +# define SSI_SRC_BURST_EN_MASK 0x0180 > +# define SSI_SRC_SINGLE_ACCESS0 0x0 > +# define SSI_SRC_SINGLE_ACCESS (0x1<<7) > +# define SSI_SRC_BURST_4x32_BIT (0x2<<7) > +# define SSI_SRC_BURST_8x32_BIT (0x3<<7) /*NOTE: NOT SUPPORTED */ > + > +# define SSI_SRC_MASK 0x003C > +# define SSI_SRC_MEMORY_PORT (0x8<<2) > +# define SSI_SRC_PERIPHERAL_PORT (0x9<<2) > + > +# define SSI_DATA_TYPE_MASK 0x0003 > +# define SSI_DATA_TYPE_S32 0x2 > + > +#define SSI_GDD_CCR_BASE 0x0802 ^ trailing whitespace > +#define SSI_GDD_CCR_OFFSET 0x40 ^ trailing whitespace > +#define SSI_GDD_CCR_REG(channel) SSI_GDD_REG16(SSI_GDD_CCR_BASE +\ > + (channel*SSI_GDD_CCR_OFFSET)) ^ missing space > +# define SSI_DST_AMODE_MASK (0x3<<14) > +# define SSI_DST_AMODE_CONST 0x0 > +# define SSI_DST_AMODE_POSTINC (0x1<<12) > + > +# define SSI_SRC_AMODE_MASK (0x3<<12) > +# define SSI_SRC_AMODE_CONST 0x0 > +# define SSI_SRC_AMODE_POSTINC (0x1<<12) > + > +# define SSI_CCR_ENABLE (0x1<<7) > + > +# define SSI_CCR_SYNC_MASK 0x001F > + > +#define SSI_GDD_CICR_BASE 0x0804 ^ trailing whitespace > +#define SSI_GDD_CICR_OFFSET 0x40 > +#define SSI_GDD_CICR_REG(channel) SSI_GDD_REG16(SSI_GDD_CICR_BASE +\ > + (channel*SSI_GDD_CICR_OFFSET)) > +# define SSI_BLOCK_IE (0x1<<5) (1 << 5) > +# define SSI_HALF_IE (0x1<<2) (1 << 2) > +# define SSI_TOUT_IE 0x1 (1 << 0) > + > +#define SSI_GDD_CSR_BASE 0x0806 ^ trailing whitespace > +#define SSI_GDD_CSR_OFFSET 0x40 ^ trailing whitespace > +#define SSI_GDD_CSR_REG(channel) SSI_GDD_REG16(SSI_GDD_CSR_BASE +\ > + (channel*SSI_GDD_CSR_OFFSET)) > +# define SSI_CSR_SYNC (0x1<<6) (1 << 6) > +# define SSI_CSR_BLOCK (0x1<<5) (1 << 5) > +# define SSI_CSR_HALF (0x1<<2) (1 << 2) > +# define SSI_CSR_TOUR 0x1 > + > +#define SSI_GDD_CSSA_BASE 0x0808 ^ trailing whitespace > +#define SSI_GDD_CSSA_OFFSET 0x40 > +#define SSI_GDD_CSSA_REG(channel) SSI_GDD_REG32(SSI_GDD_CSSA_BASE +\ > + (channel*SSI_GDD_CSSA_OFFSET)) ^ missing space > + > +#define SSI_GDD_CDSA_BASE 0x080C ^ trailing whitespace > +#define SSI_GDD_CDSA_OFFSET 0x40 > +#define SSI_GDD_CDSA_REG(channel) SSI_GDD_REG32(SSI_GDD_CDSA_BASE +\ > + (channel*SSI_GDD_CDSA_OFFSET)) ^ missing space > + > +#define SSI_GDD_CEN_BASE 0x0810 > +#define SSI_GDD_CEN_OFFSET 0x40 > +#define SSI_GDD_CEN_REG(channel) SSI_GDD_REG16(SSI_GDD_CEN_BASE +\ > + (channel*SSI_GDD_CEN_OFFSET)) ^ missing space > + > +#define SSI_GDD_CSAC_BASE 0x0818 ^ trailing whitespace > +#define SSI_GDD_CSAC_OFFSET 0x40 > +#define SSI_GDD_CSAC_REG(channel) SSI_GDD_REG16(SSI_GDD_CSAC_BASE +\ ^ trailing whitespace > + (channel*SSI_GDD_CSAC_OFFSET)) ^ missing space > + > +#define SSI_GDD_CDAC_BASE 0x081A ^ trailing whitespace > +#define SSI_GDD_CDAC_OFFSET 0x40 > +#define SSI_GDD_CDAC_REG(channel) SSI_GDD_REG16(SSI_GDD_CDAC_BASE +\ ^ trailing whitespace > + (channel*SSI_GDD_CDAC_OFFSET)) ^ missing space > + > +#define SSI_GDD_CLNK_CTRL_BASE 0x0828 > +#define SSI_GDD_CLNK_CTRL_OFFSET 0x40 > +#define SSI_GDD_CLNK_CTRL_REG(channel) SSI_GDD_REG16(SSI_GDD_CLNK_CTRL_BASE +\ > + (channel*SSI_GDD_CLNK_CTRL_OFFSET)) ^ missing space > +# define SSI_ENABLE_LNK (0x1<<15) (1 << 15) > +# define SSI_STOP_LNK (0x1<<14) (1 << 14) > +# define NEXT_CH_ID_MASK 0xF > + > +#endif > diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_reg_common.h b/arch/arm/plat-omap/include/mach/ssi/ssi_reg_common.h > new file mode 100644 > index 0000000..e66fb43 > --- /dev/null > +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_reg_common.h > @@ -0,0 +1,73 @@ > +/* > + * ssi_reg_common.h > + * > + * Common hardware definitions for SSI. > + * > + * HARDWARE: OMAP 2420, 3430 unnecessary > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifndef __SSI_REG_COMMON_H__ > +#define __SSI_REG_COMMON_H__ > + > +#define SSI_COMMON_BASE_ADDR 0x48050000 > + > +/* SSI system registers */ > +#define SSI_SYS_OFFSET 0x8000 > +#define SSI_SYS_REG32(offset) (SSI_SYS_OFFSET + (offset)) > +/* SSI GDD registers */ > +#define SSI_GDD_OFFSET 0x9000 > +#define SSI_GDD_REG32(offset) (SSI_GDD_OFFSET + (offset)) > +#define SSI_GDD_REG16(offset) (SSI_GDD_OFFSET + (offset)) > + > +/* SSI SST registers */ > +/* General offset of SST port 1. First SST port register.*/ > +#define SSI_SST1_OFFSET 0xA000 > +/* General offset of SST port 2.*/ > +#define SSI_SST2_OFFSET 0xB000 > +/* Offset among the SST ports.*/ > +#define SSI_SST_PORT_OFFSET 0x1000 > +#define SSI_SST_OFFSET(port) (SSI_SST1_OFFSET +\ > + ((port-1)*(SSI_SST_PORT_OFFSET))) ^ ^ missing spaces > +#define SSI_SST_REG(port, offset) (SSI_SST_OFFSET(port) + (offset)) > + > +/* SSI SSR registers */ > +/* General offset of SSR port 1. First SSR port register.*/ > +#define SSI_SSR1_OFFSET 0xA800 > +/* General offset of SSR port 2.*/ > +#define SSI_SSR2_OFFSET 0xB800 > +/* Offset among the SSR ports.*/ > +#define SSI_SSR_PORT_OFFSET 0x1000 > +#define SSI_SSR_OFFSET(port) (SSI_SSR1_OFFSET +\ > + ((port-1)*(SSI_SSR_PORT_OFFSET))) ^ missing space > +#define SSI_SSR_REG(port, offset) (SSI_SSR_OFFSET(port) + (offset)) > + > +#define SSI_IOMEM_BASE_ADDR SSI_COMMON_BASE_ADDR > +#define SSI_IOMEM_SIZE 0x3C00 > + > +/* > + * FIXME: Following definitions to be removed. > + * They are used for checking that the SSI clocks are stable before accessing > + * the SSI registers. > + */ > +#define OMAP_COMMON_BASE 0x48000000 > +#define CM_IDLEST1_CORE_REG 0x4A20 > +#define ST_SSI 1 > +#endif > diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h b/arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h > new file mode 100644 > index 0000000..b272047 > --- /dev/null > +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_ssr_reg.h > @@ -0,0 +1,56 @@ > +/* > + * ssi_sst_reg.h > + * > + * Hardware definitions for SSI controller SSR registers. > + * > + * HARDWARE: OMAP 2420, 3430 unnecessary > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifndef __SSI_SSR_REG_H__ > +#define __SSI_SSR_REG_H__ > + > +#include "ssi_reg_common.h" > + > +#define SSI_SSR_ID_REG(port) SSI_SSR_REG(port, 0x0000) > +#define SSI_SSR_MODE_REG(port) SSI_SSR_REG(port, 0x0004) > +#define SSI_SSR_FRAMESIZE_REG(port) SSI_SSR_REG(port, 0x0008) > +#define SSI_SSR_RXSTATE_REG(port) SSI_SSR_REG(port, 0x000C) ^^ trailing whitespaces > +#define SSI_SSR_BUFSTATE_REG(port) SSI_SSR_REG(port, 0x0010) ^ trailing whitespace > +# define NOTEMPTY(channel) (1<<channel) ^^ missing spaces > +#define SSI_SSR_BREAK_REG(port) SSI_SSR_REG(port, 0x001C) > +#define SSI_SSR_ERROR_REG(port) SSI_SSR_REG(port, 0x0020) > +#define SSI_SSR_ERRORACK_REG(port) SSI_SSR_REG(port, 0x0024) > +#define SSI_SSR_OVERRUN_REG(port) SSI_SSR_REG(port, 0x002C) > +#define SSI_SSR_OVERRUNACK_REG(port) SSI_SSR_REG(port, 0x0030) > +#define SSI_SSR_TIMEOUT_REG(port) SSI_SSR_REG(port, 0x0030) > +# define SSI_TIMEOUT_DEFAULT 0 > +#define SSI_SSR_CHANNELS_REG(port) SSI_SSR_REG(port, 0x0028) > + > +#define SSI_SSR_BUFFER_OFFSET_BASE 0x0080 > +#define SSI_SSR_BUFFER_CH_REG(port, channel) SSI_SSR_REG(port, \ > + (SSI_SSR_BUFFER_OFFSET_BASE +\ > + (channel * 0x04))) > + > +#define SSI_SSR_SWAPBUFFER_OFFSET_BASE 0x00C0 > +#define SSI_SSR_SWAPBUFFER_CH_REG(port, channel) SSI_SSR_REG(port, \ > + (SSI_SSR_SWAPBUFFER_OFFSET_BASE\ > + + (channel * 0x04))) > +#endif > diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h b/arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h > new file mode 100644 > index 0000000..ed15908 > --- /dev/null > +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_sst_reg.h > @@ -0,0 +1,65 @@ > +/* > + * ssi_sst_reg.h > + * > + * Hardware definitions for SSI controller SST registers. > + * > + * HARDWARE: OMAP 2420, 3430 unnecessary > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifndef __SSI_SST_REG_H__ > +#define __SSI_SST_REG_H__ > + > +#include "ssi_reg_common.h" > + > +#define SSI_SST_ID_REG(port) SSI_SST_REG(port, 0x0000) > +#define SSI_SST_MODE_REG(port) SSI_SST_REG(port, 0x0004) > +# define SSI_MODE_VAL_MASK 0x3 > +# define SSI_MODE_SLEEP 0x0 > +# define SSI_MODE_STREAM 0x1 > +# define SSI_MODE_FRAME 0x2 > +# define SSI_MODE_MULTIPOINTS 0x3 > +#define SSI_SST_FRAMESIZE_REG(port) SSI_SST_REG(port, 0x0008) > +# define SSI_FRAMESIZE_DEFAULT 31 > +#define SSI_SST_TXSTATE_REG(port) SSI_SST_REG(port, 0x000C) ^^ trailing whitespaces > +# define TXSTATE_IDLE 0x0 > +#define SSI_SST_BUFSTATE_REG(port) SSI_SST_REG(port, 0x0010) ^ trailing whitespace > +# define NOTFULL(channel) (1<<channel) ^ trailing whitespace ^^ missing spaces > +#define SSI_SST_DIVISOR_REG(port) SSI_SST_REG(port, 0x0018) > +# define SSI_DIVISOR_DEFAULT 1 > + > +#define SSI_SST_BREAK_REG(port) SSI_SST_REG(port, 0x0020) > +#define SSI_SST_CHANNELS_REG(port) SSI_SST_REG(port, 0x0024) > +# define SSI_CHANNELS_DEFAULT 4 > + > +#define SSI_SST_ARBMODE_REG(port) SSI_SST_REG(port, 0x0028) > +# define SSI_ARBMODE_ROUNDROBIN 0x0 > +# define SSI_ARBMODE_PRIORITY 0x1 > + > +#define SSI_SST_BUFFER_OFFSET_BASE 0x0080 > +#define SSI_SST_BUFFER_CH_REG(port, channel) SSI_SST_REG(port, \ > + (SSI_SST_BUFFER_OFFSET_BASE +\ > + (channel * 0x4))) > + > +#define SSI_SST_SWAPBUF_OFFSET_BASE 0x00C0 > +#define SSI_SST_SWAPBUF_CH_REG(port, channel) SSI_SST_REG(port, \ > + (SSI_SST_SWAPBUF_OFFSET_BASE +\ > + (channel * 0x4))) > +#endif > diff --git a/arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h b/arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h > new file mode 100644 > index 0000000..2f1e1f5 > --- /dev/null > +++ b/arch/arm/plat-omap/include/mach/ssi/ssi_sys_reg.h > @@ -0,0 +1,107 @@ > +/* > + * ssi_sys_reg.h > + * > + * Hardware defintions for SSI Controller system registers. > + * > + * HARDWARE: OMAP 2420, 3430 unnecessary > + * > + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. > + * > + * Contact: Carlos Chinea <carlos.chinea@nokia.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA > + * 02110-1301 USA > + */ > + > +#ifndef __SSI_SYS_REG_H__ > +#define __SSI_SYS_REG_H__ > + > +#include "ssi_reg_common.h" > + > +#define SSI_SYS_REVISION_REG SSI_SYS_REG32(0x0000) > +# define SSI_REV_MASK 0x000000FF > +# define SSI_REV_MAJOR 0xF0 > +# define SSI_REV_MINOR 0x0F > + > +#define SSI_SYS_SYSCONFIG_REG SSI_SYS_REG32(0x0010) > +# define SSI_AUTOIDLE 1 > +# define SSI_SOFTRESET (1<<1) add spaces > +# define SSI_SIDLEMODE_FORCE 0 > +# define SSI_SIDLEMODE_NO (1<<3) add spaces > +# define SSI_SIDLEMODE_SMART (1<<4) add spaces > +# define SSI_SIDLEMODE_MASK 0x00000018 > +# define SSI_MIDLEMODE_FORCE 0 > +# define SSI_MIDLEMODE_NO (1<<12) add spaces > +# define SSI_MIDLEMODE_SMART (1<<13) add spaces > +# define SSI_MIDLEMODE_MASK 0x00003000 > + > +#define SSI_SYS_SYSSTATUS_REG SSI_SYS_REG32(0x0014) > +# define SSI_RESETDONE 1 > + > +#define SSI_SYS_MPU_STATUS_BASE 0x0808 > +#define SSI_SYS_MPU_STATUS_PORT_OFFSET 0x10 > +#define SSI_SYS_MPU_STATUS_IRQ_OFFSET 0x2 > +#define SSI_SYS_MPU_STATUS_REG(port, irq) \ > + SSI_SYS_REG32(SSI_SYS_MPU_STATUS_BASE +\ > + (((port-1)*SSI_SYS_MPU_STATUS_PORT_OFFSET) +\ > + (irq*SSI_SYS_MPU_STATUS_IRQ_OFFSET))) > + > +#define SSI_SYS_MPU_ENABLE_BASE 0x080C > +#define SSI_SYS_MPU_ENABLE_PORT_OFFSET 0x10 > +#define SSI_SYS_MPU_ENABLE_IRQ_OFFSET 0x8 > +#define SSI_SYS_MPU_ENABLE_REG(port, irq) \ > + SSI_SYS_REG32(SSI_SYS_MPU_ENABLE_BASE +\ > + (((port-1)*SSI_SYS_MPU_ENABLE_PORT_OFFSET) +\ add spaces > + (irq*SSI_SYS_MPU_ENABLE_IRQ_OFFSET))) add spaces > + > +#define SSI_SYS_DSP_STATUS_BASE 0x0830 > +#define SSI_SYS_DSP_STATUS_PORT_OFFSET 0x10 > +#define SSI_SYS_DSP_STATUS_IRQ_OFFSET 0x8 > +#define SSI_SYS_DSP_STATUS_REG(port, irq) \ > + SSI_SYS_REG32(SSI_SYS_DSP_STATUS_BASE +\ > + (((port-1)*SSI_SYS_DSP_STATUS_PORT_OFFSET) +\ add spaces > + (irq*SSI_SYS_DSP_STATUS_IRQ_OFFSET))) add spaces > + > +#define SSI_SYS_DSP_ENABLE_BASE 0x0834 > +#define SSI_SYS_DSP_ENABLE_PORT_OFFSET 0x10 > +#define SSI_SYS_DSP_ENABLE_IRQ_OFFSET 0x8 > +#define SSI_SYS_DSP_ENABLE_REG(port, irq) \ > + SSI_SYS_REG32(SSI_SYS_DSP_ENABLE_BASE +\ > + (((port-1)*SSI_SYS_DSP_ENABLE_PORT_OFFSET) +\ add spaces > + (irq*SSI_SYS_DSP_ENABLE_IRQ_OFFSET))) add spaces > +# define SSI_SST_DATAACCEPT(channel) (1<<channel) add spaces > +# define SSI_SSR_DATAAVAILABLE(channel) (1<<(channel + 8)) add spaces > +# define SSI_SSR_DATAOVERRUN(channel) (1<<(channel + 16)) add spaces > +# define SSI_ERROROCCURED (1<<24) add spaces > +# define SSI_BREAKDETECTED (1<<25) add spaces > + > +#define SSI_SYS_GDD_MPU_IRQ_STATUS_REG SSI_SYS_REG32(0x0800) > +#define SSI_SYS_GDD_MPU_IRQ_ENABLE_REG SSI_SYS_REG32(0x0804) > +#define SSI_SYS_GDD_DSP_IRQ_STATUS_REG SSI_SYS_REG32(0x0828) ^ trailing whitespace > +#define SSI_SYS_GDD_DSP_IRQ_ENABLE_REG SSI_SYS_REG32(0x082C) ^ trailing whitespace > +# define SSI_GDD_LCH(channel) (1<<channel) > + > +#define SSI_SYS_WAKE_OFFSET 0x10 > +#define SSI_SYS_WAKE_BASE 0x0C00 > +#define SSI_SYS_WAKE_REG(port) SSI_SYS_REG32(SSI_SYS_WAKE_BASE +\ ^ trailing whitespace > + ((port-1)*SSI_SYS_WAKE_OFFSET)) add spaces > +#define SSI_SYS_CLEAR_WAKE_BASE 0x0C04 > +#define SSI_SYS_CLEAR_WAKE_REG(port) SSI_SYS_REG32(SSI_SYS_CLEAR_WAKE_BASE +\ > + ((port-1)*SSI_SYS_WAKE_OFFSET)) add spaces > +#define SSI_SYS_SET_WAKE_BASE 0x0C08 > +#define SSI_SYS_SET_WAKE_REG(port) SSI_SYS_REG32(SSI_SYS_SET_WAKE_BASE +\ > + ((port-1)*SSI_SYS_WAKE_OFFSET)) add spaces > +# define SSI_WAKE(channel) (1<<channel) add spaces > +# define SSI_WAKE_MASK 0xFF lower case -- balbi ^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2008-10-09 16:47 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2008-10-03 11:50 [RFC][PATCH 0/5] OMAP Synchronous Serial Interface (SSI) driver Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 1/5] OMAP SSI hardware interface definitions Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 2/5] OMAP SSI driver interface Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 3/5] OMAP SSI driver code Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 4/5] OMAP SSI integration into misc drivers Carlos Chinea 2008-10-03 11:52 ` [RFC][PATCH 5/5] OMAP SSI API documentation Carlos Chinea 2008-10-09 16:47 ` Felipe Balbi 2008-10-07 0:08 ` [RFC][PATCH 4/5] OMAP SSI integration into misc drivers Felipe Balbi 2008-10-07 0:03 ` [RFC][PATCH 3/5] OMAP SSI driver code Felipe Balbi 2008-10-07 22:01 ` Felipe Balbi 2008-10-06 23:29 ` [RFC][PATCH 2/5] OMAP SSI driver interface Felipe Balbi 2008-10-07 1:03 ` David Brownell 2008-10-07 11:56 ` Woodruff, Richard 2008-10-06 23:16 ` [RFC][PATCH 1/5] OMAP SSI hardware interface definitions Felipe Balbi
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).