diff options
author | Sebastian Reichel <sre@ring0.de> | 2013-09-13 23:43:12 +0200 |
---|---|---|
committer | Sebastian Reichel <sre@ring0.de> | 2013-09-13 23:43:12 +0200 |
commit | 784e256ca93b69f5f7d231250f9c1dd81fb60e90 (patch) | |
tree | f1a6b65d3a5808d88bc617cc1174eaa03b6f7d13 | |
parent | 2dde1ae8236a6429284b7853edd232a6fab64691 (diff) | |
download | linux-ssi-cleaned.tar.bz2 |
big updatessi-cleaned
-rw-r--r-- | Documentation/devicetree/bindings/hsi/omap_ssi.txt | 73 | ||||
-rw-r--r-- | arch/arm/boot/dts/omap3-n900.dts | 151 | ||||
-rw-r--r-- | arch/arm/boot/dts/omap34xx.dtsi | 30 | ||||
-rw-r--r-- | arch/arm/mach-omap2/Makefile | 4 | ||||
-rw-r--r-- | arch/arm/mach-omap2/board-rx51-peripherals.c | 7 | ||||
-rw-r--r-- | arch/arm/mach-omap2/omap_hwmod_3xxx_data.c | 56 | ||||
-rw-r--r-- | arch/arm/mach-omap2/ssi.c | 82 | ||||
-rw-r--r-- | drivers/hsi/controllers/Kconfig | 5 | ||||
-rw-r--r-- | drivers/hsi/controllers/Makefile | 1 | ||||
-rw-r--r-- | drivers/hsi/controllers/omap_ssi.c | 1637 | ||||
-rw-r--r-- | drivers/hsi/controllers/omap_ssi.h | 167 | ||||
-rw-r--r-- | drivers/hsi/controllers/omap_ssi_port.c | 1330 | ||||
-rw-r--r-- | drivers/hsi/controllers/omap_ssi_regs.h (renamed from include/linux/platform_data/hsi-omap-ssi.h) | 30 |
13 files changed, 1915 insertions, 1658 deletions
diff --git a/Documentation/devicetree/bindings/hsi/omap_ssi.txt b/Documentation/devicetree/bindings/hsi/omap_ssi.txt new file mode 100644 index 000000000000..e3597ebd76c7 --- /dev/null +++ b/Documentation/devicetree/bindings/hsi/omap_ssi.txt @@ -0,0 +1,73 @@ +OMAP SSI controller bindings + +Required properties: +- compatible: Should be set to the following value + ti,omap3-ssi (applicable to OMAP34xx devices) +- ti,hwmods: Name of the hwmod associated to the controller, which + is "ssi". +- reg: Contains SSI register address range (base address and + length). +- reg-names: Contains the names of the address ranges. It's + expected, that "sys" and "gdd" address ranges are + provided. +- interrupts: Contains the interrupt information for the controller. +- interrupt-names: Contains the names of the interrupts. It's expected, + that "gdd_mpu" is provided. +- ranges Required as an empty node +- #address-cells Should be set to <1> +- #size-cells Should be set to <1> + +Each port is represented as a sub-node of the ti,omap3-ssi device. + +Required Port sub-node properties: +- compatible: Should be set to the following value + ti,omap3-ssi-port (applicable to OMAP34xx devices) +- reg: Contains port's register address range (base address + and length). +- reg-names: Contains the names of the address ranges. It's + expected, that "tx" and "rx" address ranges are + provided. +- interrupt-parent Should be a phandle for the interrupt controller +- interrupts: Contains the interrupt information for the port. +- interrupt-names: Contains the names of the interrupts. It's expected, + that "mpu_irq0" and "mpu_irq1" are provided. +- ti,ssi-cawake-gpio: Defines which GPIO pin is used to signify CAWAKE + events for the port. This is an optional board-specific + property. If it's missing the port will not be + enabled. + +Example for Nokia N900: + +ssi-controller@48058000 { + compatible = "ti,omap3-ssi"; + ti,hwmods = "ssi"; + + reg = <0x48058000 0x1000>, + <0x48059000 0x1000>; + reg-names = "sys", + "gdd"; + + interrupts = <55>; + interrupt-names = "gdd_mpu"; + + #address-cells = <1>; + #size-cells = <1>; + ranges; + + ssi-port@0 { + compatible = "ti,omap3-ssi-port"; + + reg = <0x4805a000 0x800>, + <0x4805a800 0x800>; + reg-names = "tx", + "rx"; + + interrupt-parent = <&intc>; + interrupts = <51>, + <52>; + interrupt-names = "mpu_irq0", + "mpu_irq1"; + + ti,ssi-cawake-gpio = <&gpio5 23 GPIO_ACTIVE_HIGH>; /* 151 */ + } +} diff --git a/arch/arm/boot/dts/omap3-n900.dts b/arch/arm/boot/dts/omap3-n900.dts index 2cb18a0528f2..6ae19bf4260c 100644 --- a/arch/arm/boot/dts/omap3-n900.dts +++ b/arch/arm/boot/dts/omap3-n900.dts @@ -28,7 +28,86 @@ }; +&omap3_pmx_core { + pinctrl-names = "default"; + + uart1_pins: pinmux_uart1_pins { + pinctrl-single,pins = < + 0x152 (PIN_INPUT | MUX_MODE0) /* uart1_rx */ + 0x14c (PIN_OUTPUT |MUX_MODE0) /* uart1_tx */ + >; + }; + + uart2_pins: pinmux_uart2_pins { + pinctrl-single,pins = < + 0x14a (PIN_INPUT | MUX_MODE0) /* uart2_rx */ + 0x148 (PIN_OUTPUT | MUX_MODE0) /* uart2_tx */ + >; + }; + + uart3_pins: pinmux_uart3_pins { + pinctrl-single,pins = < + 0x16e (PIN_INPUT | MUX_MODE0) /* uart3_rx */ + 0x170 (PIN_OUTPUT | MUX_MODE0) /* uart3_tx */ + >; + }; + + i2c1_pins: pinmux_i2c1_pins { + pinctrl-single,pins = < + 0x18a (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c1_scl */ + 0x18c (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c1_sda */ + >; + }; + + i2c2_pins: pinmux_i2c2_pins { + pinctrl-single,pins = < + 0x18e (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c2_scl */ + 0x190 (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c2_sda */ + >; + }; + + i2c3_pins: pinmux_i2c3_pins { + pinctrl-single,pins = < + 0x192 (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c3_scl */ + 0x194 (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c3_sda */ + >; + }; + + mmc1_pins: pinmux_mmc1_pins { + pinctrl-single,pins = < + 0x114 (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc1_clk */ + 0x116 (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc1_cmd */ + 0x118 (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc1_dat0 */ + 0x11a (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc1_dat1 */ + 0x11c (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc1_dat2 */ + 0x11e (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc1_dat3 */ + 0x120 (PIN_INPUT | MUX_MODE0) /* sdmmc1_dat4 */ + 0x122 (PIN_INPUT | MUX_MODE0) /* sdmmc1_dat5 */ + 0x124 (PIN_INPUT | MUX_MODE0) /* sdmmc1_dat6 */ + 0x126 (PIN_INPUT | MUX_MODE0) /* sdmmc1_dat7 */ + >; + }; + + mmc2_pins: pinmux_mmc2_pins { + pinctrl-single,pins = < + 0x128 (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc2_clk */ + 0x12a (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc2_cmd */ + 0x12c (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc2_dat0 */ + 0x12e (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc2_dat1 */ + 0x130 (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc2_dat2 */ + 0x132 (PIN_INPUT_PULLUP | MUX_MODE0) /* sdmmc2_dat3 */ + 0x134 (PIN_INPUT | MUX_MODE0) /* sdmmc2_dat4 */ + 0x136 (PIN_INPUT | MUX_MODE0) /* sdmmc2_dat5 */ + 0x138 (PIN_INPUT | MUX_MODE0) /* sdmmc2_dat6 */ + 0x13a (PIN_INPUT | MUX_MODE0) /* sdmmc2_dat7 */ + >; + }; +}; + &i2c1 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <2200000>; twl: twl@48 { @@ -39,6 +118,7 @@ }; #include "twl4030.dtsi" +#include "twl4030_omap3.dtsi" &twl_gpio { ti,pullups = <0x0>; @@ -46,19 +126,34 @@ }; &i2c2 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_pins>; + clock-frequency = <400000>; }; &i2c3 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c3_pins>; + clock-frequency = <100000>; }; + &mmc1 { - status = "disabled"; + pinctrl-names = "default"; + pinctrl-0 = <&mmc1_pins>; + vmmc-supply = <&vmmc1>; + vmmc_aux-supply = <&vsim>; + bus-width = <8>; }; &mmc2 { - status = "disabled"; + pinctrl-names = "default"; + pinctrl-0 = <&mmc2_pins>; + vmmc-supply = <&vmmc2>; + vmmc_aux-supply = <&vsim>; + bus-width = <8>; }; &mmc3 { @@ -93,4 +188,56 @@ &ssi_port1 { ti,ssi-cawake-gpio = <&gpio5 23 GPIO_ACTIVE_HIGH>; /* 151 */ + + clients { + hsi_char: ssi-client@0 { + compatible = "hsi,hsi-char"; + }; + + ssi_protocol: ssi-client@1 { + compatible = "hsi,ssi-protocol"; + + tx { + mode = "frame"; + channels = <4>; + speed = <55000>; + }; + + rx { + arb_mode = "arb_rr"; + mode = "frame"; + channels = <4>; + }; + }; + + cmt_speech: ssi-client@2 { + compatible = "hsi,cmt-speech"; + }; + }; +}; + + +&ssi_port2 { + status = "disabled"; +}; + +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; +}; + +&uart2 { + pinctrl-names = "default"; + pinctrl-0 = <&uart2_pins>; +}; + +/* +&uart3 { + status = "disabled"; +}; +*/ + +&uart3 { + pinctrl-names = "default"; + pinctrl-0 = <&uart3_pins>; }; diff --git a/arch/arm/boot/dts/omap34xx.dtsi b/arch/arm/boot/dts/omap34xx.dtsi index fdc9a26244a6..393b7a712bc5 100644 --- a/arch/arm/boot/dts/omap34xx.dtsi +++ b/arch/arm/boot/dts/omap34xx.dtsi @@ -27,7 +27,7 @@ }; ocp { - ssi: ssi@48058000 { + ssi: ssi-controller@48058000 { compatible = "ti,omap3-ssi"; ti,hwmods = "ssi"; @@ -36,32 +36,42 @@ reg-names = "sys", "gdd"; - interrupts = <71>; + interrupts = <55>; interrupt-names = "gdd_mpu"; - ssi_port1: ssi-port@4805a000 { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + ssi_port1: ssi-port@0 { + compatible = "ti,omap3-ssi-port"; + reg = <0x4805a000 0x800>, <0x4805a800 0x800>; reg-names = "tx", "rx"; - interrupts = <67>, - <68>; + interrupt-parent = <&intc>; + interrupts = <51>, + <52>; interrupt-names = "mpu_irq0", "mpu_irq1"; - } + }; + + ssi_port2: ssi-port@1 { + compatible = "ti,omap3-ssi-port"; - ssi_port2: ssi-port@4805b000 { reg = <0x4805b000 0x800>, <0x4805b800 0x800>; reg-names = "tx", "rx"; - interrupts = <69>, - <70>; + interrupt-parent = <&intc>; + interrupts = <53>, + <54>; interrupt-names = "mpu_irq0", "mpu_irq1"; - } + }; }; }; }; diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index ace860ded36f..d4f671547c37 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -222,10 +222,6 @@ ifneq ($(CONFIG_DRM_OMAP),) obj-y += drm.o endif -# Synchronous Serial Interface (SSI) -omap-ssi-$(CONFIG_OMAP_SSI) := ssi.o -obj-y += $(omap-ssi-m) $(omap-ssi-y) - # Specific board support obj-$(CONFIG_MACH_OMAP_GENERIC) += board-generic.o obj-$(CONFIG_MACH_OMAP_H4) += board-h4.o diff --git a/arch/arm/mach-omap2/board-rx51-peripherals.c b/arch/arm/mach-omap2/board-rx51-peripherals.c index e2ca155f5801..32110a09a17d 100644 --- a/arch/arm/mach-omap2/board-rx51-peripherals.c +++ b/arch/arm/mach-omap2/board-rx51-peripherals.c @@ -27,7 +27,6 @@ #include <linux/power/isp1704_charger.h> #include <linux/platform_data/spi-omap2-mcspi.h> #include <linux/platform_data/mtd-onenand-omap2.h> -#include <linux/platform_data/hsi-omap-ssi.h> #include <asm/system_info.h> @@ -268,11 +267,6 @@ static struct spi_board_info rx51_peripherals_spi_board_info[] __initdata = { }, }; -static struct omap_ssi_board_config ssi_board_config = { - .num_ports = 1, - .cawake_gpio = { RX51_CAWAKE_GPIO }, -}; - static struct platform_device rx51_battery_device = { .name = "rx51-battery", .id = -1, @@ -1303,7 +1297,6 @@ void __init rx51_peripherals_init(void) if (partition) omap_hsmmc_init(mmc); - omap_ssi_config(&ssi_board_config); rx51_charger_init(); rx51_init_twl4030_hwmon(); } diff --git a/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c b/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c index 74006c4a8524..b4b9af36b7a2 100644 --- a/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c +++ b/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c @@ -3716,65 +3716,10 @@ static struct omap_hwmod_class omap34xx_ssi_hwmod_class = { .sysc = &omap34xx_ssi_sysc, }; -static struct omap_hwmod_irq_info omap34xx_ssi_irqs[] = { - { .name = "ssi_p1_mpu_irq0", .irq = 67 }, - { .name = "ssi_p1_mpu_irq1", .irq = 68 }, - { .name = "ssi_p2_mpu_irq0", .irq = 69 }, - { .name = "ssi_p2_mpu_irq1", .irq = 70 }, - { .name = "ssi_gdd_mpu", .irq = 71 }, - { .irq = -1 }, -}; - -static struct omap_hwmod_addr_space omap34xx_ssi_addrs[] = { - { - .name = "sys", - .pa_start = 0x48058000, - .pa_end = 0x48058fff, - .flags = ADDR_TYPE_RT, - }, - { - /* generic distributed DMA */ - .name = "gdd", - .pa_start = 0x48059000, - .pa_end = 0x48059fff, - .flags = ADDR_TYPE_RT, - }, - { - /* port 1: synchronous serial transmitter */ - .name = "p1_sst", - .pa_start = 0x4805a000, - .pa_end = 0x4805a7ff, - .flags = ADDR_TYPE_RT, - }, - { - /* port 1: synchronous serial receiver */ - .name = "p1_ssr", - .pa_start = 0x4805a800, - .pa_end = 0x4805afff, - .flags = ADDR_TYPE_RT, - }, - { - /* port 2: synchronous serial transmitter */ - .name = "p2_sst", - .pa_start = 0x4805b000, - .pa_end = 0x4805b7ff, - .flags = ADDR_TYPE_RT, - }, - { - /* port 2: synchronous serial receiver */ - .name = "p2_ssr", - .pa_start = 0x4805b800, - .pa_end = 0x4805bfff, - .flags = ADDR_TYPE_RT, - }, - {} -}; - static struct omap_hwmod omap34xx_ssi_hwmod = { .name = "ssi", .class = &omap34xx_ssi_hwmod_class, .clkdm_name = "l3_init_clkdm", - .mpu_irqs = omap34xx_ssi_irqs, .main_clk = "ssi_ssr_fck", .prcm = { .omap2 = { @@ -3792,7 +3737,6 @@ static struct omap_hwmod_ocp_if omap34xx_l3__ssi = { .master = &omap3xxx_l3_main_hwmod, .slave = &omap34xx_ssi_hwmod, .clk = "ssi_ick", - .addr = omap34xx_ssi_addrs, .user = OCP_USER_MPU | OCP_USER_SDMA, }; diff --git a/arch/arm/mach-omap2/ssi.c b/arch/arm/mach-omap2/ssi.c deleted file mode 100644 index a9d6eee1abba..000000000000 --- a/arch/arm/mach-omap2/ssi.c +++ /dev/null @@ -1,82 +0,0 @@ -/* - * linux/arch/arm/mach-omap2/ssi.c - * - * Copyright (C) 2010 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/kernel.h> -#include <linux/init.h> -#include <linux/err.h> -#include <linux/gpio.h> -#include <linux/platform_device.h> -#include <linux/platform_data/hsi-omap-ssi.h> -#include "omap_hwmod.h" -#include "omap_device.h" -#include "omap-pm.h" - -static struct omap_ssi_platform_data ssi_pdata = { - .num_ports = SSI_NUM_PORTS, - .cawake_gpio = {0}, - .get_dev_context_loss_count = omap_pm_get_dev_context_loss_count, -}; - -int __init omap_ssi_config(struct omap_ssi_board_config *ssi_config) -{ - unsigned int port, offset, cawake_gpio; - int err; - - ssi_pdata.num_ports = ssi_config->num_ports; - - for (port = 0, offset = 7; port < ssi_config->num_ports; port++, offset += 5) { - cawake_gpio = ssi_config->cawake_gpio[port]; - if (!cawake_gpio) - continue; /* Nothing to do */ - err = gpio_request(cawake_gpio, "cawake"); - if (err < 0) - goto rback; - gpio_direction_input(cawake_gpio); - - ssi_pdata.cawake_gpio[port] = ssi_config->cawake_gpio[port]; - } - - return 0; - -rback: - pr_err("omap-ssi: Request cawake (gpio%d) failed\n", cawake_gpio); - while (port > 0) - gpio_free(ssi_config->cawake_gpio[--port]); - - return err; -} - -static int __init omap_ssi_init(void) -{ - struct omap_hwmod *oh; - struct platform_device *pdev; - - oh = omap_hwmod_lookup("ssi"); - if (!oh) - return -EINVAL; - - pdev = omap_device_build("omap_ssi", 0, oh, &ssi_pdata, sizeof(struct omap_ssi_platform_data)); - WARN(IS_ERR(pdev), "Can't build omap_device for omap_ssi\n"); - - return 0; -} -subsys_initcall(omap_ssi_init); diff --git a/drivers/hsi/controllers/Kconfig b/drivers/hsi/controllers/Kconfig index 3efe0f027c7a..e4bdf7946225 100644 --- a/drivers/hsi/controllers/Kconfig +++ b/drivers/hsi/controllers/Kconfig @@ -14,6 +14,11 @@ config OMAP_SSI If unsure, say N. +config OMAP_SSI_PORT + tristate + default m if OMAP_SSI=m + default y if OMAP_SSI=y + if OMAP_SSI config OMAP_SSI_CONFIG diff --git a/drivers/hsi/controllers/Makefile b/drivers/hsi/controllers/Makefile index c4ba2c2c2bda..d2665cf9c545 100644 --- a/drivers/hsi/controllers/Makefile +++ b/drivers/hsi/controllers/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_OMAP_SSI) += omap_ssi.o +obj-$(CONFIG_OMAP_SSI_PORT) += omap_ssi_port.o diff --git a/drivers/hsi/controllers/omap_ssi.c b/drivers/hsi/controllers/omap_ssi.c index edac3d30e756..923d59d5a5e5 100644 --- a/drivers/hsi/controllers/omap_ssi.c +++ b/drivers/hsi/controllers/omap_ssi.c @@ -1,9 +1,7 @@ -/* - * omap_ssi.c - * - * Implements the OMAP SSI driver. +/* OMAP SSI driver. * * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * Copyright (C) 2013 Sebastian Reichel * * Contact: Carlos Chinea <carlos.chinea@nokia.com> * @@ -21,6 +19,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ + #include <linux/compiler.h> #include <linux/err.h> #include <linux/ioport.h> @@ -39,388 +38,28 @@ #include <linux/spinlock.h> #include <linux/debugfs.h> #include <linux/pm_runtime.h> - +#include <linux/of_platform.h> #include <linux/hsi/hsi.h> -#include <linux/platform_data/hsi-omap-ssi.h> +#include "omap_ssi_regs.h" +#include "omap_ssi.h" -#define SSI_MAX_CHANNELS 8 -#define SSI_MAX_GDD_LCH 8 -#define SSI_BYTES_TO_FRAMES(x) ((((x) - 1) >> 2) + 1) - -/** - * struct gdd_trn - GDD transaction data - * @msg: Pointer to the HSI message being served - * @sg: Pointer to the current sg entry being served - */ -struct gdd_trn { - struct hsi_msg *msg; - struct scatterlist *sg; -}; - -/** - * struct omap_ssm_ctx - OMAP synchronous serial module (TX/RX) context - * @mode: Bit transmission mode - * @channels: Number of channels - * @framesize: Frame size in bits - * @timeout: RX frame timeout - * @divisor: TX divider - * @arb_mode: Arbitration mode for TX frame (Round robin, priority) - */ -struct omap_ssm_ctx { - u32 mode; - u32 channels; - u32 frame_size; - union { - u32 timeout; /* Rx Only */ - struct { - u32 arb_mode; - u32 divisor; - }; /* Tx only */ - }; -}; - -/** - * struct omap_ssi_port - OMAP SSI port data - * @dev: device associated to the port (HSI port) - * @sst_dma: SSI transmitter physical base address - * @ssr_dma: SSI receiver physical base address - * @sst_base: SSI transmitter base address - * @ssr_base: SSI receiver base address - * @wk_lock: spin lock to serialize access to the wake lines - * @lock: Spin lock to serialize access to the SSI port - * @channels: Current number of channels configured (1,2,4 or 8) - * @txqueue: TX message queues - * @rxqueue: RX message queues - * @brkqueue: Queue of incoming HWBREAK requests (FRAME mode) - * @irq: IRQ number - * @wake_irq: IRQ number for incoming wake line (-1 if none) - * @wake_gpio: GPIO number for incoming wake line (-1 if none) - * @pio_tasklet: Bottom half for PIO transfers and events - * @wake_tasklet: Bottom half for incoming wake events - * @wkin_cken: Keep track of clock references due to the incoming wake line - * @wk_refcount: Reference count for output wake line - * @sys_mpu_enable: Context for the interrupt enable register for irq 0 - * @sst: Context for the synchronous serial transmitter - * @ssr: Context for the synchronous serial receiver - */ -struct omap_ssi_port { - struct device *dev; - dma_addr_t sst_dma; - dma_addr_t ssr_dma; - void __iomem *sst_base; - void __iomem *ssr_base; - spinlock_t wk_lock; - spinlock_t lock; - unsigned int channels; - struct list_head txqueue[SSI_MAX_CHANNELS]; - struct list_head rxqueue[SSI_MAX_CHANNELS]; - struct list_head brkqueue; - unsigned int irq; - int wake_irq; - int wake_gpio; - struct tasklet_struct pio_tasklet; - struct tasklet_struct wake_tasklet; - unsigned int wktest:1; /* FIXME: HACK to be removed */ - unsigned int wkin_cken:1; /* Workaround */ - int wk_refcount; - /* OMAP SSI port context */ - u32 sys_mpu_enable; /* We use only one irq */ - struct omap_ssm_ctx sst; - struct omap_ssm_ctx ssr; -}; - -/** - * struct omap_ssi_controller - OMAP SSI controller data - * @dev: device associated to the controller (HSI controller) - * @sys: SSI I/O base address - * @gdd: GDD I/O base address - * @ick: SSI interconnect clock - * @fck: SSI functional clock - * @ck_refcount: References count for clocks - * @gdd_irq: IRQ line for GDD - * @gdd_tasklet: bottom half for DMA transfers - * @gdd_trn: Array of GDD transaction data for ongoing GDD transfers - * @lock: lock to serialize access to GDD - * @ck_lock: lock to serialize access to the clocks - * @loss_count: To follow if we need to restore context or not - * @max_speed: Maximum TX speed (Kb/s) set by the clients. - * @sysconfig: SSI controller saved context - * @gdd_gcr: SSI GDD saved context - * @get_loss: Pointer to omap_pm_get_dev_context_loss_count, if any - * @port: Array of pointers of the ports of the controller - * @dir: Debugfs SSI root directory - */ -struct omap_ssi_controller { - struct device *dev; - void __iomem *sys; - void __iomem *gdd; - struct clk *ick; - struct clk *fck; - int ck_refcount; - unsigned int gdd_irq; - struct tasklet_struct gdd_tasklet; - struct gdd_trn gdd_trn[SSI_MAX_GDD_LCH]; - spinlock_t lock; - spinlock_t ck_lock; - unsigned long fck_rate; - u32 loss_count; - u32 max_speed; - /* OMAP SSI Controller context */ - u32 sysconfig; - u32 gdd_gcr; - int (*get_loss)(struct device *dev); - struct omap_ssi_port **port; -#ifdef CONFIG_DEBUG_FS - struct dentry *dir; -#endif -}; - -static inline unsigned int ssi_wakein(struct hsi_port *port) -{ - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - return gpio_get_value(omap_port->wake_gpio); -} - -static int ssi_for_each_port(struct hsi_controller *ssi, void *data, - int (*fn)(struct omap_ssi_port *p, void *data)) -{ - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - unsigned int i = 0; - int err = 0; - - for (i = 0; ((i < ssi->num_ports) && !err); i++) - err = (*fn)(omap_ssi->port[i], data); - - return err; -} - -static int ssi_set_port_mode(struct omap_ssi_port *omap_port, void *data) -{ - u32 *mode = data; - - __raw_writel(*mode, omap_port->sst_base + SSI_SST_MODE_REG); - __raw_writel(*mode, omap_port->ssr_base + SSI_SSR_MODE_REG); - /* OCP barrier */ - *mode = __raw_readl(omap_port->ssr_base + SSI_SSR_MODE_REG); - - return 0; -} - -static inline void ssi_set_mode(struct hsi_controller *ssi, u32 mode) -{ - ssi_for_each_port(ssi, &mode, ssi_set_port_mode); -} - -static int ssi_restore_port_mode(struct omap_ssi_port *omap_port, - void *data __maybe_unused) -{ - u32 mode; - - __raw_writel(omap_port->sst.mode, - omap_port->sst_base + SSI_SST_MODE_REG); - __raw_writel(omap_port->ssr.mode, - omap_port->ssr_base + SSI_SSR_MODE_REG); - /* OCP barrier */ - mode = __raw_readl(omap_port->ssr_base + SSI_SSR_MODE_REG); - - return 0; -} - -static int ssi_restore_divisor(struct omap_ssi_port *omap_port, - void *data __maybe_unused) -{ - __raw_writel(omap_port->sst.divisor, - omap_port->sst_base + SSI_SST_DIVISOR_REG); - - return 0; -} - -static int ssi_restore_port_ctx(struct omap_ssi_port *omap_port, - void *data __maybe_unused) -{ - struct hsi_port *port = to_hsi_port(omap_port->dev); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - void __iomem *base = omap_port->sst_base; - - __raw_writel(omap_port->sys_mpu_enable, - omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - /* SST context */ - __raw_writel(omap_port->sst.frame_size, base + SSI_SST_FRAMESIZE_REG); - __raw_writel(omap_port->sst.channels, base + SSI_SST_CHANNELS_REG); - __raw_writel(omap_port->sst.arb_mode, base + SSI_SST_ARBMODE_REG); - /* SSR context */ - base = omap_port->ssr_base; - __raw_writel(omap_port->ssr.frame_size, base + SSI_SSR_FRAMESIZE_REG); - __raw_writel(omap_port->ssr.channels, base + SSI_SSR_CHANNELS_REG); - __raw_writel(omap_port->ssr.timeout, base + SSI_SSR_TIMEOUT_REG); - - return 0; -} - -static int ssi_save_port_ctx(struct omap_ssi_port *omap_port, - void *data __maybe_unused) -{ - struct hsi_port *port = to_hsi_port(omap_port->dev); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - - omap_port->sys_mpu_enable = __raw_readl(omap_ssi->sys + - SSI_MPU_ENABLE_REG(port->num, 0)); - - return 0; -} - -static int ssi_clk_enable(struct hsi_controller *ssi) -{ - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct device *pdev = ssi->device.parent; - int err = 0; - - spin_lock_bh(&omap_ssi->ck_lock); - - if (omap_ssi->ck_refcount++) - goto out; - - err = pm_runtime_get_sync(pdev); - if (unlikely(err < 0)) - goto out; - - if ((omap_ssi->get_loss) && (omap_ssi->loss_count == - (*omap_ssi->get_loss)(ssi->device.parent))) - goto mode; /* We always need to restore the mode & TX divisor */ - - __raw_writel(omap_ssi->sysconfig, omap_ssi->sys + SSI_SYSCONFIG_REG); - __raw_writel(omap_ssi->gdd_gcr, omap_ssi->gdd + SSI_GDD_GCR_REG); - - ssi_for_each_port(ssi, NULL, ssi_restore_port_ctx); -mode: - ssi_for_each_port(ssi, NULL, ssi_restore_divisor); - ssi_for_each_port(ssi, NULL, ssi_restore_port_mode); -out: - spin_unlock_bh(&omap_ssi->ck_lock); - - return err; -} - -static void ssi_clk_disable(struct hsi_controller *ssi) -{ - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct device *pdev = ssi->device.parent; - - spin_lock_bh(&omap_ssi->ck_lock); - WARN_ON(omap_ssi->ck_refcount <= 0); - if (--omap_ssi->ck_refcount) - goto out; - - ssi_set_mode(ssi, SSI_MODE_SLEEP); - - if (omap_ssi->get_loss) - omap_ssi->loss_count = - (*omap_ssi->get_loss)(ssi->device.parent); - - ssi_for_each_port(ssi, NULL, ssi_save_port_ctx); - - spin_unlock_bh(&omap_ssi->ck_lock); - - pm_runtime_put_sync(pdev); - return; - -out: - spin_unlock_bh(&omap_ssi->ck_lock); -} +/* TODO: HACK: define omap_pm_get_dev_context_loss_count, because it is defined + * in arch/arm/mach-omap2/omap-pm.h and thus not available via #include */ +int omap_pm_get_dev_context_loss_count(struct device *dev); #ifdef CONFIG_DEBUG_FS static int ssi_debug_show(struct seq_file *m, void *p __maybe_unused) { struct hsi_controller *ssi = m->private; struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - void __iomem *sys = omap_ssi->sys; + void __iomem *sys = omap_ssi->sys; - ssi_clk_enable(ssi); - seq_printf(m, "REVISION\t: 0x%08x\n", - __raw_readl(sys + SSI_REVISION_REG)); - seq_printf(m, "SYSCONFIG\t: 0x%08x\n", - __raw_readl(sys + SSI_SYSCONFIG_REG)); - seq_printf(m, "SYSSTATUS\t: 0x%08x\n", - __raw_readl(sys + SSI_SYSSTATUS_REG)); - ssi_clk_disable(ssi); - - return 0; -} - -static int ssi_debug_port_show(struct seq_file *m, void *p __maybe_unused) -{ - struct hsi_port *port = m->private; - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - void __iomem *base = omap_ssi->sys; - unsigned int ch; - - ssi_clk_enable(ssi); - if (omap_port->wake_irq > 0) - seq_printf(m, "CAWAKE\t\t: %d\n", ssi_wakein(port)); - seq_printf(m, "WAKE\t\t: 0x%08x\n", - __raw_readl(base + SSI_WAKE_REG(port->num))); - seq_printf(m, "MPU_ENABLE_IRQ%d\t: 0x%08x\n", 0, - __raw_readl(base + SSI_MPU_ENABLE_REG(port->num, 0))); - seq_printf(m, "MPU_STATUS_IRQ%d\t: 0x%08x\n", 0, - __raw_readl(base + SSI_MPU_STATUS_REG(port->num, 0))); - /* SST */ - base = omap_port->sst_base; - seq_printf(m, "\nSST\n===\n"); - seq_printf(m, "ID SST\t\t: 0x%08x\n", - __raw_readl(base + SSI_SST_ID_REG)); - seq_printf(m, "MODE\t\t: 0x%08x\n", - __raw_readl(base + SSI_SST_MODE_REG)); - seq_printf(m, "FRAMESIZE\t: 0x%08x\n", - __raw_readl(base + SSI_SST_FRAMESIZE_REG)); - seq_printf(m, "DIVISOR\t\t: 0x%08x\n", - __raw_readl(base + SSI_SST_DIVISOR_REG)); - seq_printf(m, "CHANNELS\t: 0x%08x\n", - __raw_readl(base + SSI_SST_CHANNELS_REG)); - seq_printf(m, "ARBMODE\t\t: 0x%08x\n", - __raw_readl(base + SSI_SST_ARBMODE_REG)); - seq_printf(m, "TXSTATE\t\t: 0x%08x\n", - __raw_readl(base + SSI_SST_TXSTATE_REG)); - seq_printf(m, "BUFSTATE\t: 0x%08x\n", - __raw_readl(base + SSI_SST_BUFSTATE_REG)); - seq_printf(m, "BREAK\t\t: 0x%08x\n", - __raw_readl(base + SSI_SST_BREAK_REG)); - for (ch = 0; ch < omap_port->channels; ch++) { - seq_printf(m, "BUFFER_CH%d\t: 0x%08x\n", ch, - __raw_readl(base + SSI_SST_BUFFER_CH_REG(ch))); - } - /* SSR */ - base = omap_port->ssr_base; - seq_printf(m, "\nSSR\n===\n"); - seq_printf(m, "ID SSR\t\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_ID_REG)); - seq_printf(m, "MODE\t\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_MODE_REG)); - seq_printf(m, "FRAMESIZE\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_FRAMESIZE_REG)); - seq_printf(m, "CHANNELS\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_CHANNELS_REG)); - seq_printf(m, "TIMEOUT\t\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_TIMEOUT_REG)); - seq_printf(m, "RXSTATE\t\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_RXSTATE_REG)); - seq_printf(m, "BUFSTATE\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_BUFSTATE_REG)); - seq_printf(m, "BREAK\t\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_BREAK_REG)); - seq_printf(m, "ERROR\t\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_ERROR_REG)); - seq_printf(m, "ERRORACK\t: 0x%08x\n", - __raw_readl(base + SSI_SSR_ERRORACK_REG)); - for (ch = 0; ch < omap_port->channels; ch++) { - seq_printf(m, "BUFFER_CH%d\t: 0x%08x\n", ch, - __raw_readl(base + SSI_SSR_BUFFER_CH_REG(ch))); - } - ssi_clk_disable(ssi); + pm_runtime_get_sync(ssi->device.parent); + seq_printf(m, "REVISION\t: 0x%08x\n", readl(sys + SSI_REVISION_REG)); + seq_printf(m, "SYSCONFIG\t: 0x%08x\n", readl(sys + SSI_SYSCONFIG_REG)); + seq_printf(m, "SYSSTATUS\t: 0x%08x\n", readl(sys + SSI_SYSSTATUS_REG)); + pm_runtime_put_sync(ssi->device.parent); return 0; } @@ -429,79 +68,52 @@ static int ssi_debug_gdd_show(struct seq_file *m, void *p __maybe_unused) { struct hsi_controller *ssi = m->private; struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - void __iomem *gdd = omap_ssi->gdd; + void __iomem *gdd = omap_ssi->gdd; + void __iomem *sys = omap_ssi->sys; int lch; - ssi_clk_enable(ssi); + pm_runtime_get_sync(ssi->device.parent); + seq_printf(m, "GDD_MPU_STATUS\t: 0x%08x\n", - __raw_readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_STATUS_REG)); + readl(sys + SSI_GDD_MPU_IRQ_STATUS_REG)); seq_printf(m, "GDD_MPU_ENABLE\t: 0x%08x\n\n", - __raw_readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG)); + readl(sys + SSI_GDD_MPU_IRQ_ENABLE_REG)); seq_printf(m, "HW_ID\t\t: 0x%08x\n", - __raw_readl(gdd + SSI_GDD_HW_ID_REG)); + readl(gdd + SSI_GDD_HW_ID_REG)); seq_printf(m, "PPORT_ID\t: 0x%08x\n", - __raw_readl(gdd + SSI_GDD_PPORT_ID_REG)); + readl(gdd + SSI_GDD_PPORT_ID_REG)); seq_printf(m, "MPORT_ID\t: 0x%08x\n", - __raw_readl(gdd + SSI_GDD_MPORT_ID_REG)); + readl(gdd + SSI_GDD_MPORT_ID_REG)); seq_printf(m, "TEST\t\t: 0x%08x\n", - __raw_readl(gdd + SSI_GDD_TEST_REG)); + readl(gdd + SSI_GDD_TEST_REG)); seq_printf(m, "GCR\t\t: 0x%08x\n", - __raw_readl(gdd + SSI_GDD_GCR_REG)); + readl(gdd + SSI_GDD_GCR_REG)); for (lch = 0; lch < SSI_MAX_GDD_LCH; lch++) { seq_printf(m, "\nGDD LCH %d\n=========\n", lch); seq_printf(m, "CSDP\t\t: 0x%04x\n", - __raw_readw(gdd + SSI_GDD_CSDP_REG(lch))); + readw(gdd + SSI_GDD_CSDP_REG(lch))); seq_printf(m, "CCR\t\t: 0x%04x\n", - __raw_readw(gdd + SSI_GDD_CCR_REG(lch))); + readw(gdd + SSI_GDD_CCR_REG(lch))); seq_printf(m, "CICR\t\t: 0x%04x\n", - __raw_readw(gdd + SSI_GDD_CICR_REG(lch))); + readw(gdd + SSI_GDD_CICR_REG(lch))); seq_printf(m, "CSR\t\t: 0x%04x\n", - __raw_readw(gdd + SSI_GDD_CSR_REG(lch))); + readw(gdd + SSI_GDD_CSR_REG(lch))); seq_printf(m, "CSSA\t\t: 0x%08x\n", - __raw_readl(gdd + SSI_GDD_CSSA_REG(lch))); + readl(gdd + SSI_GDD_CSSA_REG(lch))); seq_printf(m, "CDSA\t\t: 0x%08x\n", - __raw_readl(gdd + SSI_GDD_CDSA_REG(lch))); + readl(gdd + SSI_GDD_CDSA_REG(lch))); seq_printf(m, "CEN\t\t: 0x%04x\n", - __raw_readw(gdd + SSI_GDD_CEN_REG(lch))); + readw(gdd + SSI_GDD_CEN_REG(lch))); seq_printf(m, "CSAC\t\t: 0x%04x\n", - __raw_readw(gdd + SSI_GDD_CSAC_REG(lch))); + readw(gdd + SSI_GDD_CSAC_REG(lch))); seq_printf(m, "CDAC\t\t: 0x%04x\n", - __raw_readw(gdd + SSI_GDD_CDAC_REG(lch))); + readw(gdd + SSI_GDD_CDAC_REG(lch))); seq_printf(m, "CLNK_CTRL\t: 0x%04x\n", - __raw_readw(gdd + SSI_GDD_CLNK_CTRL_REG(lch))); + readw(gdd + SSI_GDD_CLNK_CTRL_REG(lch))); } - ssi_clk_disable(ssi); - - return 0; -} - -static int ssi_div_get(void *data, u64 *val) -{ - struct hsi_port *port = data; - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - - ssi_clk_enable(ssi); - *val = __raw_readl(omap_port->sst_base + SSI_SST_DIVISOR_REG); - ssi_clk_disable(ssi); - return 0; -} - -static int ssi_div_set(void *data, u64 val) -{ - struct hsi_port *port = data; - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - - if (val > 127) - return -EINVAL; - - ssi_clk_enable(ssi); - __raw_writel(val, omap_port->sst_base + SSI_SST_DIVISOR_REG); - omap_port->sst.divisor = val; - ssi_clk_disable(ssi); + pm_runtime_put_sync(ssi->device.parent); return 0; } @@ -511,11 +123,6 @@ static int ssi_regs_open(struct inode *inode, struct file *file) return single_open(file, ssi_debug_show, inode->i_private); } -static int ssi_port_regs_open(struct inode *inode, struct file *file) -{ - return single_open(file, ssi_debug_port_show, inode->i_private); -} - static int ssi_gdd_regs_open(struct inode *inode, struct file *file) { return single_open(file, ssi_debug_gdd_show, inode->i_private); @@ -528,13 +135,6 @@ static const struct file_operations ssi_regs_fops = { .release = single_release, }; -static const struct file_operations ssi_port_regs_fops = { - .open = ssi_port_regs_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - static const struct file_operations ssi_gdd_regs_fops = { .open = ssi_gdd_regs_open, .read = seq_read, @@ -542,32 +142,10 @@ static const struct file_operations ssi_gdd_regs_fops = { .release = single_release, }; -DEFINE_SIMPLE_ATTRIBUTE(ssi_sst_div_fops, ssi_div_get, ssi_div_set, "%llu\n"); - -static int __init ssi_debug_add_port(struct omap_ssi_port *omap_port, - void *data) -{ - struct hsi_port *port = to_hsi_port(omap_port->dev); - struct dentry *dir = data; - - dir = debugfs_create_dir(dev_name(omap_port->dev), dir); - if (IS_ERR(dir)) - return PTR_ERR(dir); - debugfs_create_file("regs", S_IRUGO, dir, port, &ssi_port_regs_fops); - dir = debugfs_create_dir("sst", dir); - if (IS_ERR(dir)) - return PTR_ERR(dir); - debugfs_create_file("divisor", S_IRUGO | S_IWUSR, dir, port, - &ssi_sst_div_fops); - - return 0; -} - static int __init ssi_debug_add_ctrl(struct hsi_controller *ssi) { struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); struct dentry *dir; - int err; /* SSI controller */ omap_ssi->dir = debugfs_create_dir(dev_name(&ssi->device), NULL); @@ -581,10 +159,6 @@ static int __init ssi_debug_add_ctrl(struct hsi_controller *ssi) if (IS_ERR(dir)) goto rback; debugfs_create_file("regs", S_IRUGO, dir, ssi, &ssi_gdd_regs_fops); - /* SSI ports */ - err = ssi_for_each_port(ssi, omap_ssi->dir, ssi_debug_add_port); - if (err < 0) - goto rback; return 0; rback: @@ -601,173 +175,6 @@ static void ssi_debug_remove_ctrl(struct hsi_controller *ssi) } #endif /* CONFIG_DEBUG_FS */ -static int ssi_claim_lch(struct hsi_msg *msg) -{ - - struct hsi_port *port = hsi_get_port(msg->cl); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - int lch; - - for (lch = 0; lch < SSI_MAX_GDD_LCH; lch++) - if (!omap_ssi->gdd_trn[lch].msg) { - omap_ssi->gdd_trn[lch].msg = msg; - omap_ssi->gdd_trn[lch].sg = msg->sgt.sgl; - return lch; - } - - return -EBUSY; -} - -static int ssi_start_pio(struct hsi_msg *msg) -{ - struct hsi_port *port = hsi_get_port(msg->cl); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - u32 val; - - ssi_clk_enable(ssi); - if (msg->ttype == HSI_MSG_WRITE) { - val = SSI_DATAACCEPT(msg->channel); - ssi_clk_enable(ssi); /* Hold clocks for pio writes */ - } else { - val = SSI_DATAAVAILABLE(msg->channel) | SSI_ERROROCCURED; - } - dev_dbg(&port->device, "Single %s transfer\n", - msg->ttype ? "write" : "read"); - val |= __raw_readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - __raw_writel(val, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - ssi_clk_disable(ssi); - msg->actual_len = 0; - msg->status = HSI_STATUS_PROCEEDING; - - return 0; -} - -static int ssi_start_dma(struct hsi_msg *msg, int lch) -{ - struct hsi_port *port = hsi_get_port(msg->cl); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - void __iomem *gdd = omap_ssi->gdd; - int err; - u16 csdp; - u16 ccr; - u32 s_addr; - u32 d_addr; - u32 tmp; - - if (msg->ttype == HSI_MSG_READ) { - err = dma_map_sg(&ssi->device, msg->sgt.sgl, msg->sgt.nents, - DMA_FROM_DEVICE); - if (err < 0) { - dev_dbg(&ssi->device, "DMA map SG failed !\n"); - return err; - } - csdp = SSI_DST_BURST_4x32_BIT | SSI_DST_MEMORY_PORT | - SSI_SRC_SINGLE_ACCESS0 | SSI_SRC_PERIPHERAL_PORT | - SSI_DATA_TYPE_S32; - ccr = msg->channel + 0x10 + (port->num * 8); /* Sync */ - ccr |= SSI_DST_AMODE_POSTINC | SSI_SRC_AMODE_CONST | - SSI_CCR_ENABLE; - s_addr = omap_port->ssr_dma + - SSI_SSR_BUFFER_CH_REG(msg->channel); - d_addr = sg_dma_address(msg->sgt.sgl); - } else { - err = dma_map_sg(&ssi->device, msg->sgt.sgl, msg->sgt.nents, - DMA_TO_DEVICE); - if (err < 0) { - dev_dbg(&ssi->device, "DMA map SG failed !\n"); - return err; - } - csdp = SSI_SRC_BURST_4x32_BIT | SSI_SRC_MEMORY_PORT | - SSI_DST_SINGLE_ACCESS0 | SSI_DST_PERIPHERAL_PORT | - SSI_DATA_TYPE_S32; - ccr = (msg->channel + 1 + (port->num * 8)) & 0xf; /* Sync */ - ccr |= SSI_SRC_AMODE_POSTINC | SSI_DST_AMODE_CONST | - SSI_CCR_ENABLE; - s_addr = sg_dma_address(msg->sgt.sgl); - d_addr = omap_port->sst_dma + - SSI_SST_BUFFER_CH_REG(msg->channel); - } - dev_dbg(&ssi->device, "lch %d cdsp %08x ccr %04x s_addr %08x" - " d_addr %08x\n", lch, csdp, ccr, s_addr, d_addr); - ssi_clk_enable(ssi); /* Hold clocks during the transfer */ - __raw_writew(csdp, gdd + SSI_GDD_CSDP_REG(lch)); - __raw_writew(SSI_BLOCK_IE | SSI_TOUT_IE, gdd + SSI_GDD_CICR_REG(lch)); - __raw_writel(d_addr, gdd + SSI_GDD_CDSA_REG(lch)); - __raw_writel(s_addr, gdd + SSI_GDD_CSSA_REG(lch)); - __raw_writew(SSI_BYTES_TO_FRAMES(msg->sgt.sgl->length), - gdd + SSI_GDD_CEN_REG(lch)); - - spin_lock_bh(&omap_ssi->lock); - tmp = __raw_readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); - tmp |= SSI_GDD_LCH(lch); - __raw_writel(tmp, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); - spin_unlock_bh(&omap_ssi->lock); - __raw_writew(ccr, gdd + SSI_GDD_CCR_REG(lch)); - msg->status = HSI_STATUS_PROCEEDING; - - return 0; -} - -static int ssi_start_transfer(struct list_head *queue) -{ - struct hsi_msg *msg; - int lch = -1; - - if (list_empty(queue)) - return 0; - msg = list_first_entry(queue, struct hsi_msg, link); - if (msg->status != HSI_STATUS_QUEUED) - return 0; - if ((msg->sgt.nents) && (msg->sgt.sgl->length > sizeof(u32))) - lch = ssi_claim_lch(msg); - if (lch >= 0) - return ssi_start_dma(msg, lch); - else - return ssi_start_pio(msg); -} - -static void ssi_transfer(struct omap_ssi_port *omap_port, - struct list_head *queue) -{ - struct hsi_msg *msg; - int err = -1; - - spin_lock_bh(&omap_port->lock); - while (err < 0) { - err = ssi_start_transfer(queue); - if (err < 0) { - msg = list_first_entry(queue, struct hsi_msg, link); - msg->status = HSI_STATUS_ERROR; - msg->actual_len = 0; - list_del(&msg->link); - spin_unlock_bh(&omap_port->lock); - msg->complete(msg); - spin_lock_bh(&omap_port->lock); - } - } - spin_unlock_bh(&omap_port->lock); -} - -static u32 ssi_calculate_div(struct hsi_controller *ssi) -{ - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - u32 tx_fckrate = (u32) omap_ssi->fck_rate; - - /* / 2 : SSI TX clock is always half of the SSI functional clock */ - tx_fckrate >>= 1; - /* Round down when tx_fckrate % omap_ssi->max_speed == 0 */ - tx_fckrate--; - dev_dbg(&ssi->device, "TX div %d for fck_rate %lu Khz speed %d Kb/s\n", - tx_fckrate / omap_ssi->max_speed, omap_ssi->fck_rate, - omap_ssi->max_speed); - - return tx_fckrate / omap_ssi->max_speed; -} - /* * FIXME: Horrible HACK needed until we remove the useless wakeline test * in the CMT. To be removed !!!! @@ -781,549 +188,17 @@ void ssi_waketest(struct hsi_client *cl, unsigned int enable) omap_port->wktest = !!enable; if (omap_port->wktest) { - ssi_clk_enable(ssi); - __raw_writel(SSI_WAKE(0), + pm_runtime_get_sync(ssi->device.parent); + writel_relaxed(SSI_WAKE(0), omap_ssi->sys + SSI_SET_WAKE_REG(port->num)); } else { - __raw_writel(SSI_WAKE(0), + writel_relaxed(SSI_WAKE(0), omap_ssi->sys + SSI_CLEAR_WAKE_REG(port->num)); - ssi_clk_disable(ssi); + pm_runtime_put_sync(ssi->device.parent); } } EXPORT_SYMBOL_GPL(ssi_waketest); -static void ssi_error(struct hsi_port *port) -{ - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct hsi_msg *msg; - unsigned int i; - u32 err; - u32 val; - u32 tmp; - - /* ACK error */ - err = __raw_readl(omap_port->ssr_base + SSI_SSR_ERROR_REG); - dev_err(&port->device, "SSI error: 0x%02x\n", err); - if (!err) { - dev_dbg(&port->device, "spurious SSI error ignored!\n"); - return; - } - spin_lock(&omap_ssi->lock); - /* Cancel all GDD read transfers */ - for (i = 0, val = 0; i < SSI_MAX_GDD_LCH; i++) { - msg = omap_ssi->gdd_trn[i].msg; - if ((msg) && (msg->ttype == HSI_MSG_READ)) { - __raw_writew(0, omap_ssi->gdd + SSI_GDD_CCR_REG(i)); - val |= (1 << i); - omap_ssi->gdd_trn[i].msg = NULL; - } - } - tmp = __raw_readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); - tmp &= ~val; - __raw_writel(tmp, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); - spin_unlock(&omap_ssi->lock); - /* Cancel all PIO read transfers */ - spin_lock(&omap_port->lock); - tmp = __raw_readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - tmp &= 0xfeff00ff; /* Disable error & all dataavailable interrupts */ - __raw_writel(tmp, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - /* ACK error */ - __raw_writel(err, omap_port->ssr_base + SSI_SSR_ERRORACK_REG); - __raw_writel(SSI_ERROROCCURED, - omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); - /* Signal the error all current pending read requests */ - for (i = 0; i < omap_port->channels; i++) { - if (list_empty(&omap_port->rxqueue[i])) - continue; - msg = list_first_entry(&omap_port->rxqueue[i], struct hsi_msg, - link); - list_del(&msg->link); - msg->status = HSI_STATUS_ERROR; - spin_unlock(&omap_port->lock); - msg->complete(msg); - /* Now restart queued reads if any */ - ssi_transfer(omap_port, &omap_port->rxqueue[i]); - spin_lock(&omap_port->lock); - } - spin_unlock(&omap_port->lock); -} - -static void ssi_break_complete(struct hsi_port *port) -{ - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct hsi_msg *msg; - struct hsi_msg *tmp; - u32 val; - - dev_dbg(&port->device, "HWBREAK received\n"); - - spin_lock(&omap_port->lock); - val = __raw_readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - val &= ~SSI_BREAKDETECTED; - __raw_writel(val, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - __raw_writel(0, omap_port->ssr_base + SSI_SSR_BREAK_REG); - __raw_writel(SSI_BREAKDETECTED, - omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); - spin_unlock(&omap_port->lock); - - list_for_each_entry_safe(msg, tmp, &omap_port->brkqueue, link) { - msg->status = HSI_STATUS_COMPLETED; - spin_lock(&omap_port->lock); - list_del(&msg->link); - spin_unlock(&omap_port->lock); - msg->complete(msg); - } - -} - -static int ssi_async_break(struct hsi_msg *msg) -{ - struct hsi_port *port = hsi_get_port(msg->cl); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - int err = 0; - u32 tmp; - - ssi_clk_enable(ssi); - if (msg->ttype == HSI_MSG_WRITE) { - if (omap_port->sst.mode != SSI_MODE_FRAME) { - err = -EINVAL; - goto out; - } - __raw_writel(1, omap_port->sst_base + SSI_SST_BREAK_REG); - msg->status = HSI_STATUS_COMPLETED; - msg->complete(msg); - } else { - if (omap_port->ssr.mode != SSI_MODE_FRAME) { - err = -EINVAL; - goto out; - } - spin_lock_bh(&omap_port->lock); - tmp = __raw_readl(omap_ssi->sys + - SSI_MPU_ENABLE_REG(port->num, 0)); - __raw_writel(tmp | SSI_BREAKDETECTED, - omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - msg->status = HSI_STATUS_PROCEEDING; - list_add_tail(&msg->link, &omap_port->brkqueue); - spin_unlock_bh(&omap_port->lock); - } -out: - ssi_clk_disable(ssi); - - return err; -} - -static int ssi_async(struct hsi_msg *msg) -{ - struct hsi_port *port = hsi_get_port(msg->cl); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct list_head *queue; - int err = 0; - - BUG_ON(!msg); - - if (msg->sgt.nents > 1) - return -ENOSYS; /* TODO: Add sg support */ - - if (msg->break_frame) - return ssi_async_break(msg); - - if (msg->ttype) { - BUG_ON(msg->channel >= omap_port->sst.channels); - queue = &omap_port->txqueue[msg->channel]; - } else { - BUG_ON(msg->channel >= omap_port->ssr.channels); - queue = &omap_port->rxqueue[msg->channel]; - } - msg->status = HSI_STATUS_QUEUED; - spin_lock_bh(&omap_port->lock); - list_add_tail(&msg->link, queue); - err = ssi_start_transfer(queue); - if (err < 0) { - list_del(&msg->link); - msg->status = HSI_STATUS_ERROR; - } - spin_unlock_bh(&omap_port->lock); - dev_dbg(&port->device, "msg status %d ttype %d ch %d\n", - msg->status, msg->ttype, msg->channel); - - return err; -} - -static void ssi_flush_queue(struct list_head *queue, struct hsi_client *cl) -{ - struct list_head *node, *tmp; - struct hsi_msg *msg; - - list_for_each_safe(node, tmp, queue) { - msg = list_entry(node, struct hsi_msg, link); - if ((cl) && (cl != msg->cl)) - continue; - list_del(node); - pr_debug("flush queue: ch %d, msg %p len %d type %d ctxt %p\n", - msg->channel, msg, msg->sgt.sgl->length, - msg->ttype, msg->context); - if (msg->destructor) - msg->destructor(msg); - else - hsi_free_msg(msg); - } -} - -static int ssi_setup(struct hsi_client *cl) -{ - struct hsi_port *port = to_hsi_port(cl->device.parent); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - void __iomem *sst = omap_port->sst_base; - void __iomem *ssr = omap_port->ssr_base; - u32 div; - u32 val; - int err = 0; - - ssi_clk_enable(ssi); - spin_lock_bh(&omap_port->lock); - if (cl->tx_cfg.speed) - omap_ssi->max_speed = cl->tx_cfg.speed; - div = ssi_calculate_div(ssi); - if (div > SSI_MAX_DIVISOR) { - dev_err(&cl->device, "Invalid TX speed %d Mb/s (div %d)\n", - cl->tx_cfg.speed, div); - err = -EINVAL; - goto out; - } - /* Set TX/RX module to sleep to stop TX/RX during cfg update */ - __raw_writel(SSI_MODE_SLEEP, sst + SSI_SST_MODE_REG); - __raw_writel(SSI_MODE_SLEEP, ssr + SSI_SSR_MODE_REG); - /* Flush posted write */ - val = __raw_readl(ssr + SSI_SSR_MODE_REG); - /* TX */ - __raw_writel(31, sst + SSI_SST_FRAMESIZE_REG); - __raw_writel(div, sst + SSI_SST_DIVISOR_REG); - __raw_writel(cl->tx_cfg.channels, sst + SSI_SST_CHANNELS_REG); - __raw_writel(cl->tx_cfg.arb_mode, sst + SSI_SST_ARBMODE_REG); - __raw_writel(cl->tx_cfg.mode, sst + SSI_SST_MODE_REG); - /* RX */ - __raw_writel(31, ssr + SSI_SSR_FRAMESIZE_REG); - __raw_writel(cl->rx_cfg.channels, ssr + SSI_SSR_CHANNELS_REG); - __raw_writel(0, ssr + SSI_SSR_TIMEOUT_REG); - /* Cleanup the break queue if we leave FRAME mode */ - if ((omap_port->ssr.mode == SSI_MODE_FRAME) && - (cl->rx_cfg.mode != SSI_MODE_FRAME)) - ssi_flush_queue(&omap_port->brkqueue, cl); - __raw_writel(cl->rx_cfg.mode, ssr + SSI_SSR_MODE_REG); - omap_port->channels = max(cl->rx_cfg.channels, cl->tx_cfg.channels); - /* Shadow registering for OFF mode */ - /* SST */ - omap_port->sst.divisor = div; - omap_port->sst.frame_size = 31; - omap_port->sst.channels = cl->tx_cfg.channels; - omap_port->sst.arb_mode = cl->tx_cfg.arb_mode; - omap_port->sst.mode = cl->tx_cfg.mode; - /* SSR */ - omap_port->ssr.frame_size = 31; - omap_port->ssr.timeout = 0; - omap_port->ssr.channels = cl->rx_cfg.channels; - omap_port->ssr.mode = cl->rx_cfg.mode; -out: - spin_unlock_bh(&omap_port->lock); - ssi_clk_disable(ssi); - - return err; -} - -static void ssi_cleanup_queues(struct hsi_client *cl) -{ - struct hsi_port *port = hsi_get_port(cl); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct hsi_msg *msg; - unsigned int i; - u32 rxbufstate = 0; - u32 txbufstate = 0; - u32 status = SSI_ERROROCCURED; - u32 tmp; - - ssi_flush_queue(&omap_port->brkqueue, cl); - if (list_empty(&omap_port->brkqueue)) - status |= SSI_BREAKDETECTED; - - for (i = 0; i < omap_port->channels; i++) { - if (list_empty(&omap_port->txqueue[i])) - continue; - msg = list_first_entry(&omap_port->txqueue[i], struct hsi_msg, - link); - if ((msg->cl == cl) && (msg->status == HSI_STATUS_PROCEEDING)) { - txbufstate |= (1 << i); - status |= SSI_DATAACCEPT(i); - /* Release the clocks writes, also GDD ones */ - ssi_clk_disable(ssi); - } - ssi_flush_queue(&omap_port->txqueue[i], cl); - } - for (i = 0; i < omap_port->channels; i++) { - if (list_empty(&omap_port->rxqueue[i])) - continue; - msg = list_first_entry(&omap_port->rxqueue[i], struct hsi_msg, - link); - if ((msg->cl == cl) && (msg->status == HSI_STATUS_PROCEEDING)) { - rxbufstate |= (1 << i); - status |= SSI_DATAAVAILABLE(i); - } - ssi_flush_queue(&omap_port->rxqueue[i], cl); - /* Check if we keep the error detection interrupt armed */ - if (!list_empty(&omap_port->rxqueue[i])) - status &= ~SSI_ERROROCCURED; - } - /* Cleanup write buffers */ - tmp = __raw_readl(omap_port->sst_base + SSI_SST_BUFSTATE_REG); - tmp &= ~txbufstate; - __raw_writel(tmp, omap_port->sst_base + SSI_SST_BUFSTATE_REG); - /* Cleanup read buffers */ - tmp = __raw_readl(omap_port->ssr_base + SSI_SSR_BUFSTATE_REG); - tmp &= ~rxbufstate; - __raw_writel(tmp, omap_port->ssr_base + SSI_SSR_BUFSTATE_REG); - /* Disarm and ack pending interrupts */ - tmp = __raw_readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - tmp &= ~status; - __raw_writel(tmp, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - __raw_writel(status, omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); -} - -static void ssi_cleanup_gdd(struct hsi_controller *ssi, struct hsi_client *cl) -{ - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct hsi_msg *msg; - unsigned int i; - u32 val = 0; - u32 tmp; - - for (i = 0; i < SSI_MAX_GDD_LCH; i++) { - msg = omap_ssi->gdd_trn[i].msg; - if ((!msg) || (msg->cl != cl)) - continue; - __raw_writew(0, omap_ssi->gdd + SSI_GDD_CCR_REG(i)); - val |= (1 << i); - /* - * Clock references for write will be handled in - * ssi_cleanup_queues - */ - if (msg->ttype == HSI_MSG_READ) - ssi_clk_disable(ssi); - omap_ssi->gdd_trn[i].msg = NULL; - } - tmp = __raw_readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); - tmp &= ~val; - __raw_writel(tmp, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); - __raw_writel(val, omap_ssi->sys + SSI_GDD_MPU_IRQ_STATUS_REG); -} - -static int ssi_release(struct hsi_client *cl) -{ - struct hsi_port *port = hsi_get_port(cl); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - - spin_lock_bh(&omap_port->lock); - ssi_clk_enable(ssi); - /* Stop all the pending DMA requests for that client */ - ssi_cleanup_gdd(ssi, cl); - /* Now cleanup all the queues */ - ssi_cleanup_queues(cl); - ssi_clk_disable(ssi); - /* If it is the last client of the port, do extra checks and cleanup */ - if (port->claimed <= 1) { - /* - * Drop the clock reference for the incoming wake line - * if it is still kept high by the other side. - */ - if (omap_port->wkin_cken) { - ssi_clk_disable(ssi); - omap_port->wkin_cken = 0; - } - ssi_clk_enable(ssi); - /* Stop any SSI TX/RX without a client */ - ssi_set_mode(ssi, SSI_MODE_SLEEP); - omap_port->sst.mode = SSI_MODE_SLEEP; - omap_port->ssr.mode = SSI_MODE_SLEEP; - ssi_clk_disable(ssi); - WARN_ON(omap_port->wk_refcount != 0); - WARN_ON(omap_ssi->ck_refcount != 0); - } - spin_unlock_bh(&omap_port->lock); - - return 0; -} - -static int ssi_flush(struct hsi_client *cl) -{ - struct hsi_port *port = hsi_get_port(cl); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct hsi_msg *msg; - void __iomem *sst = omap_port->sst_base; - void __iomem *ssr = omap_port->ssr_base; - unsigned int i; - u32 err; - - ssi_clk_enable(ssi); - spin_lock_bh(&omap_port->lock); - /* Stop all DMA transfers */ - for (i = 0; i < SSI_MAX_GDD_LCH; i++) { - msg = omap_ssi->gdd_trn[i].msg; - if (!msg || (port != hsi_get_port(msg->cl))) - continue; - __raw_writew(0, omap_ssi->gdd + SSI_GDD_CCR_REG(i)); - if (msg->ttype == HSI_MSG_READ) - ssi_clk_disable(ssi); - omap_ssi->gdd_trn[i].msg = NULL; - } - /* Flush all SST buffers */ - __raw_writel(0, sst + SSI_SST_BUFSTATE_REG); - __raw_writel(0, sst + SSI_SST_TXSTATE_REG); - /* Flush all SSR buffers */ - __raw_writel(0, ssr + SSI_SSR_RXSTATE_REG); - __raw_writel(0, ssr + SSI_SSR_BUFSTATE_REG); - /* Flush all errors */ - err = __raw_readl(ssr + SSI_SSR_ERROR_REG); - __raw_writel(err, ssr + SSI_SSR_ERRORACK_REG); - /* Flush break */ - __raw_writel(0, ssr + SSI_SSR_BREAK_REG); - /* Clear interrupts */ - __raw_writel(0, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - __raw_writel(0xffffff00, - omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); - __raw_writel(0, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); - __raw_writel(0xff, omap_ssi->sys + SSI_GDD_MPU_IRQ_STATUS_REG); - /* Dequeue all pending requests */ - for (i = 0; i < omap_port->channels; i++) { - /* Release write clocks */ - if (!list_empty(&omap_port->txqueue[i])) - ssi_clk_disable(ssi); - ssi_flush_queue(&omap_port->txqueue[i], NULL); - ssi_flush_queue(&omap_port->rxqueue[i], NULL); - } - ssi_flush_queue(&omap_port->brkqueue, NULL); - spin_unlock_bh(&omap_port->lock); - ssi_clk_disable(ssi); - - return 0; -} - -static int ssi_start_tx(struct hsi_client *cl) -{ - struct hsi_port *port = hsi_get_port(cl); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - - dev_dbg(&port->device, "Wake out high %d\n", omap_port->wk_refcount); - - spin_lock_bh(&omap_port->wk_lock); - if (omap_port->wk_refcount++) { - spin_unlock_bh(&omap_port->wk_lock); - return 0; - } - ssi_clk_enable(ssi); /* Grab clocks */ - __raw_writel(SSI_WAKE(0), omap_ssi->sys + SSI_SET_WAKE_REG(port->num)); - spin_unlock_bh(&omap_port->wk_lock); - - return 0; -} - -static int ssi_stop_tx(struct hsi_client *cl) -{ - struct hsi_port *port = hsi_get_port(cl); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - - dev_dbg(&port->device, "Wake out low %d\n", omap_port->wk_refcount); - - spin_lock_bh(&omap_port->wk_lock); - BUG_ON(!omap_port->wk_refcount); - if (--omap_port->wk_refcount) { - spin_unlock_bh(&omap_port->wk_lock); - return 0; - } - __raw_writel(SSI_WAKE(0), - omap_ssi->sys + SSI_CLEAR_WAKE_REG(port->num)); - ssi_clk_disable(ssi); /* Release clocks */ - spin_unlock_bh(&omap_port->wk_lock); - - return 0; -} - -static void ssi_pio_complete(struct hsi_port *port, struct list_head *queue) -{ - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct hsi_msg *msg; - u32 *buf; - u32 reg; - u32 val; - - spin_lock(&omap_port->lock); - msg = list_first_entry(queue, struct hsi_msg, link); - if ((!msg->sgt.nents) || (!msg->sgt.sgl->length)) { - msg->actual_len = 0; - msg->status = HSI_STATUS_PENDING; - } - if (msg->ttype == HSI_MSG_WRITE) - val = SSI_DATAACCEPT(msg->channel); - else - val = SSI_DATAAVAILABLE(msg->channel); - if (msg->status == HSI_STATUS_PROCEEDING) { - buf = sg_virt(msg->sgt.sgl) + msg->actual_len; - if (msg->ttype == HSI_MSG_WRITE) - __raw_writel(*buf, omap_port->sst_base + - SSI_SST_BUFFER_CH_REG(msg->channel)); - else - *buf = __raw_readl(omap_port->ssr_base + - SSI_SSR_BUFFER_CH_REG(msg->channel)); - dev_dbg(&port->device, "ch %d ttype %d 0x%08x\n", msg->channel, - msg->ttype, *buf); - msg->actual_len += sizeof(*buf); - if (msg->actual_len >= msg->sgt.sgl->length) - msg->status = HSI_STATUS_COMPLETED; - /* - * Wait for the last written frame to be really sent before - * we call the complete callback - */ - if ((msg->status == HSI_STATUS_PROCEEDING) || - ((msg->status == HSI_STATUS_COMPLETED) && - (msg->ttype == HSI_MSG_WRITE))) { - __raw_writel(val, omap_ssi->sys + - SSI_MPU_STATUS_REG(port->num, 0)); - spin_unlock(&omap_port->lock); - - return; - } - - } - /* Transfer completed at this point */ - reg = __raw_readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - if (msg->ttype == HSI_MSG_WRITE) - ssi_clk_disable(ssi); /* Release clocks for write transfer */ - reg &= ~val; - __raw_writel(reg, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - __raw_writel(val, omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); - list_del(&msg->link); - spin_unlock(&omap_port->lock); - msg->complete(msg); - ssi_transfer(omap_port, queue); -} - static void ssi_gdd_complete(struct hsi_controller *ssi, unsigned int lch) { struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); @@ -1336,21 +211,21 @@ static void ssi_gdd_complete(struct hsi_controller *ssi, unsigned int lch) spin_lock(&omap_ssi->lock); - val = __raw_readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + val = readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); val &= ~SSI_GDD_LCH(lch); - __raw_writel(val, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + writel_relaxed(val, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); if (msg->ttype == HSI_MSG_READ) { dir = DMA_FROM_DEVICE; val = SSI_DATAAVAILABLE(msg->channel); - ssi_clk_disable(ssi); + pm_runtime_put_sync(ssi->device.parent); } else { dir = DMA_TO_DEVICE; val = SSI_DATAACCEPT(msg->channel); /* Keep clocks reference for write pio event */ } dma_unmap_sg(&ssi->device, msg->sgt.sgl, msg->sgt.nents, dir); - csr = __raw_readw(omap_ssi->gdd + SSI_GDD_CSR_REG(lch)); + csr = readw(omap_ssi->gdd + SSI_GDD_CSR_REG(lch)); omap_ssi->gdd_trn[lch].msg = NULL; /* release GDD lch */ dev_dbg(&port->device, "DMA completed ch %d ttype %d\n", msg->channel, msg->ttype); @@ -1365,8 +240,8 @@ static void ssi_gdd_complete(struct hsi_controller *ssi, unsigned int lch) return; } spin_lock(&omap_port->lock); - val |= __raw_readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); - __raw_writel(val, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + val |= readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + writel_relaxed(val, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); spin_unlock(&omap_port->lock); msg->status = HSI_STATUS_COMPLETED; @@ -1381,16 +256,18 @@ static void ssi_gdd_tasklet(unsigned long dev) unsigned int lch; u32 status_reg; - ssi_clk_enable(ssi); + pm_runtime_get_sync(ssi->device.parent); - status_reg = __raw_readl(sys + SSI_GDD_MPU_IRQ_STATUS_REG); + status_reg = readl(sys + SSI_GDD_MPU_IRQ_STATUS_REG); for (lch = 0; lch < SSI_MAX_GDD_LCH; lch++) { if (status_reg & SSI_GDD_LCH(lch)) ssi_gdd_complete(ssi, lch); } - __raw_writel(status_reg, sys + SSI_GDD_MPU_IRQ_STATUS_REG); - status_reg = __raw_readl(sys + SSI_GDD_MPU_IRQ_STATUS_REG); - ssi_clk_disable(ssi); + writel_relaxed(status_reg, sys + SSI_GDD_MPU_IRQ_STATUS_REG); + status_reg = readl(sys + SSI_GDD_MPU_IRQ_STATUS_REG); + + pm_runtime_put_sync(ssi->device.parent); + if (status_reg) tasklet_hi_schedule(&omap_ssi->gdd_tasklet); else @@ -1408,185 +285,7 @@ static irqreturn_t ssi_gdd_isr(int irq, void *ssi) return IRQ_HANDLED; } -static void ssi_pio_tasklet(unsigned long ssi_port) -{ - struct hsi_port *port = (struct hsi_port *)ssi_port; - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - void __iomem *sys = omap_ssi->sys; - unsigned int ch; - u32 status_reg; - - ssi_clk_enable(ssi); - status_reg = __raw_readl(sys + SSI_MPU_STATUS_REG(port->num, 0)); - status_reg &= __raw_readl(sys + SSI_MPU_ENABLE_REG(port->num, 0)); - - for (ch = 0; ch < omap_port->channels; ch++) { - if (status_reg & SSI_DATAACCEPT(ch)) - ssi_pio_complete(port, &omap_port->txqueue[ch]); - if (status_reg & SSI_DATAAVAILABLE(ch)) - ssi_pio_complete(port, &omap_port->rxqueue[ch]); - } - if (status_reg & SSI_BREAKDETECTED) - ssi_break_complete(port); - if (status_reg & SSI_ERROROCCURED) - ssi_error(port); - - status_reg = __raw_readl(sys + SSI_MPU_STATUS_REG(port->num, 0)); - status_reg &= __raw_readl(sys + SSI_MPU_ENABLE_REG(port->num, 0)); - ssi_clk_disable(ssi); - - if (status_reg) - tasklet_hi_schedule(&omap_port->pio_tasklet); - else - enable_irq(omap_port->irq); -} - -static irqreturn_t ssi_pio_isr(int irq, void *port) -{ - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - - tasklet_hi_schedule(&omap_port->pio_tasklet); - disable_irq_nosync(irq); - - return IRQ_HANDLED; -} - -static void ssi_wake_tasklet(unsigned long ssi_port) -{ - struct hsi_port *port = (struct hsi_port *)ssi_port; - struct hsi_controller *ssi = to_hsi_controller(port->device.parent); - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - - if (ssi_wakein(port)) { - /** - * We can have a quick High-Low-High transition in the line. - * In such a case if we have long interrupt latencies, - * we can miss the low event or get twice a high event. - * This workaround will avoid breaking the clock reference - * count when such a situation ocurrs. - */ - spin_lock(&omap_port->lock); - if (!omap_port->wkin_cken) { - omap_port->wkin_cken = 1; - ssi_clk_enable(ssi); - } - spin_unlock(&omap_port->lock); - dev_dbg(&ssi->device, "Wake in high\n"); - if (omap_port->wktest) { /* FIXME: HACK ! To be removed */ - __raw_writel(SSI_WAKE(0), - omap_ssi->sys + SSI_SET_WAKE_REG(port->num)); - } - hsi_event(port, HSI_EVENT_START_RX); - } else { - dev_dbg(&ssi->device, "Wake in low\n"); - if (omap_port->wktest) { /* FIXME: HACK ! To be removed */ - __raw_writel(SSI_WAKE(0), - omap_ssi->sys + SSI_CLEAR_WAKE_REG(port->num)); - } - hsi_event(port, HSI_EVENT_STOP_RX); - spin_lock(&omap_port->lock); - if (omap_port->wkin_cken) { - ssi_clk_disable(ssi); - omap_port->wkin_cken = 0; - } - spin_unlock(&omap_port->lock); - } -} - -static irqreturn_t ssi_wake_isr(int irq __maybe_unused, void *ssi_port) -{ - struct omap_ssi_port *omap_port = hsi_port_drvdata(ssi_port); - - tasklet_hi_schedule(&omap_port->wake_tasklet); - - return IRQ_HANDLED; -} - -static int __init ssi_port_irq(struct hsi_port *port, - struct platform_device *pd) -{ - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - struct resource *irq; - int err; - char irq_name[25]; - - sprintf(irq_name, "ssi_p%d_mpu_irq0", port->num+1); - irq = platform_get_resource_byname(pd, IORESOURCE_IRQ, irq_name); - if (!irq) { - dev_err(&port->device, "Port IRQ resource missing\n"); - return -ENXIO; - } - omap_port->irq = irq->start; - tasklet_init(&omap_port->pio_tasklet, ssi_pio_tasklet, - (unsigned long)port); - err = devm_request_irq(&pd->dev, omap_port->irq, ssi_pio_isr, - IRQF_DISABLED, irq->name, port); - if (err < 0) - dev_err(&port->device, "Request IRQ %d failed (%d)\n", - omap_port->irq, err); - return err; -} - -static int __init ssi_wake_irq(struct hsi_port *port, - struct platform_device *pd) -{ - struct omap_ssi_platform_data *omap_ssi_pdata = pd->dev.platform_data; - struct omap_ssi_port *omap_port = hsi_port_drvdata(port); - char irq_name[23]; - int cawake_gpio; - int cawake_irq; - int err; - - if (port->num >= omap_ssi_pdata->num_ports) { - dev_err(&port->device, "Wake in IRQ resource missing"); - return -ENXIO; - } - - sprintf(irq_name, "ssi_p%d_cawake", port->num+1); - - cawake_gpio = omap_ssi_pdata->cawake_gpio[port->num]; - - if (cawake_gpio == -1) { - omap_port->wake_gpio = -1; - omap_port->wake_irq = -1; - return 0; - } - - cawake_irq = gpio_to_irq(cawake_gpio); - - omap_port->wake_gpio = cawake_gpio; - omap_port->wake_irq = cawake_irq; - tasklet_init(&omap_port->wake_tasklet, ssi_wake_tasklet, - (unsigned long)port); - err = devm_request_irq(&pd->dev, cawake_irq, ssi_wake_isr, - IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - irq_name, port); - if (err < 0) - dev_err(&port->device, "Request Wake in IRQ %d failed %d\n", - cawake_irq, err); - err = enable_irq_wake(cawake_irq); - if (err < 0) - dev_err(&port->device, "Enable wake on the wakeline in irq %d" - " failed %d\n", cawake_irq, err); - - return err; -} - -static void __init ssi_queues_init(struct omap_ssi_port *omap_port) -{ - unsigned int ch; - - for (ch = 0; ch < SSI_MAX_CHANNELS; ch++) { - INIT_LIST_HEAD(&omap_port->txqueue[ch]); - INIT_LIST_HEAD(&omap_port->rxqueue[ch]); - } - INIT_LIST_HEAD(&omap_port->brkqueue); -} - -static int __init ssi_get_iomem(struct platform_device *pd, +int __init ssi_get_iomem(struct platform_device *pd, const char *name, void __iomem **pbase, dma_addr_t *phy) { struct resource *mem; @@ -1618,100 +317,16 @@ static int __init ssi_get_iomem(struct platform_device *pd, return 0; } -static int __init ssi_ports_init(struct hsi_controller *ssi, - struct platform_device *pd) -{ - struct hsi_port *port; - struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - struct omap_ssi_port *omap_port; - unsigned int i; - int err; - char mem_sst_name[16]; - char mem_ssr_name[16]; - - omap_ssi->port = devm_kzalloc(&pd->dev, - sizeof(omap_port) * ssi->num_ports, GFP_KERNEL); - if (!omap_ssi->port) - return -ENOMEM; - - for (i = 0; i < ssi->num_ports; i++) { - port = ssi->port[i]; - omap_port = devm_kzalloc(&pd->dev, sizeof(*omap_port), - GFP_KERNEL); - if (!omap_port) - return -ENOMEM; - port->async = ssi_async; - port->setup = ssi_setup; - port->flush = ssi_flush; - port->start_tx = ssi_start_tx; - port->stop_tx = ssi_stop_tx; - port->release = ssi_release; - hsi_port_set_drvdata(port, omap_port); - - sprintf(mem_sst_name, "p%d_sst", i+1); - sprintf(mem_ssr_name, "p%d_ssr", i+1); - - /* Get SST base addresses*/ - err = ssi_get_iomem(pd, mem_sst_name, &omap_port->sst_base, - &omap_port->sst_dma); - if (err < 0) - return err; - /* Get SSR base addresses */ - err = ssi_get_iomem(pd, mem_ssr_name, &omap_port->ssr_base, - &omap_port->ssr_dma); - if (err < 0) - return err; - err = ssi_port_irq(port, pd); - if (err < 0) - return err; - err = ssi_wake_irq(port, pd); - if (err < 0) - return err; - ssi_queues_init(omap_port); - spin_lock_init(&omap_port->lock); - spin_lock_init(&omap_port->wk_lock); - omap_port->dev = &port->device; - omap_ssi->port[i] = omap_port; - } - - return 0; -} - -static void ssi_ports_exit(struct hsi_controller *ssi) -{ - struct omap_ssi_port *omap_port; - unsigned int i; - - for (i = 0; i < ssi->num_ports; i++) { - omap_port = hsi_port_drvdata(ssi->port[i]); - tasklet_kill(&omap_port->wake_tasklet); - tasklet_kill(&omap_port->pio_tasklet); - } -} - static unsigned long ssi_get_clk_rate(struct hsi_controller *ssi) { - struct device *pdev = ssi->device.parent; - struct clk *clk; - unsigned long rate; - - clk = clk_get(pdev, "ssi_ssr_fck"); - if (IS_ERR(clk)) { - dev_err(pdev, "clock get ssi_ssr_fck failed %li\n", PTR_ERR(clk)); - return 0; - } - - rate = clk_get_rate(clk); - - clk_put(clk); - + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + unsigned long rate = clk_get_rate(omap_ssi->fck); return rate; } static int __init ssi_add_controller(struct hsi_controller *ssi, struct platform_device *pd) { - struct omap_ssi_platform_data *omap_ssi_pdata = pd->dev.platform_data; struct omap_ssi_controller *omap_ssi; struct resource *irq; int err; @@ -1733,7 +348,7 @@ static int __init ssi_add_controller(struct hsi_controller *ssi, err = ssi_get_iomem(pd, "gdd", &omap_ssi->gdd, NULL); if (err < 0) return err; - irq = platform_get_resource_byname(pd, IORESOURCE_IRQ, "ssi_gdd_mpu"); + irq = platform_get_resource_byname(pd, IORESOURCE_IRQ, "gdd_mpu"); if (!irq) { dev_err(&pd->dev, "GDD IRQ resource missing\n"); return -ENXIO; @@ -1742,19 +357,28 @@ static int __init ssi_add_controller(struct hsi_controller *ssi, tasklet_init(&omap_ssi->gdd_tasklet, ssi_gdd_tasklet, (unsigned long)ssi); err = devm_request_irq(&pd->dev, omap_ssi->gdd_irq, ssi_gdd_isr, - IRQF_DISABLED, irq->name, ssi); + 0, irq->name, ssi); if (err < 0) { dev_err(&ssi->device, "Request GDD IRQ %d failed (%d)", omap_ssi->gdd_irq, err); return err; } - err = ssi_ports_init(ssi, pd); - if (err < 0) - return err; - omap_ssi->get_loss = omap_ssi_pdata->get_dev_context_loss_count; + + omap_ssi->port = devm_kzalloc(&pd->dev, + sizeof(struct omap_ssi_port*) * ssi->num_ports, GFP_KERNEL); + if (!omap_ssi->port) + return -ENOMEM; + + omap_ssi->fck = devm_clk_get(&pd->dev, "ssi_ssr_fck"); + if(IS_ERR(omap_ssi->fck)) { + dev_err(&pd->dev, "Could not acquire clock \"ssi_ssr_fck\": %li\n", PTR_ERR(omap_ssi->fck)); + return -ENODEV; + } + + omap_ssi->get_loss = omap_pm_get_dev_context_loss_count; + omap_ssi->max_speed = UINT_MAX; spin_lock_init(&omap_ssi->lock); - spin_lock_init(&omap_ssi->ck_lock); err = hsi_register_controller(ssi); return err; @@ -1767,35 +391,35 @@ static int __init ssi_hw_init(struct hsi_controller *ssi) u32 val; int err; - err = ssi_clk_enable(ssi); + err = pm_runtime_get_sync(ssi->device.parent); if (err < 0) { - dev_err(&ssi->device, "Failed to enable the clocks %d\n", err); + dev_err(&ssi->device, "runtime PM failed %d\n", err); return err; } /* Reseting SSI controller */ - __raw_writel(SSI_SOFTRESET, omap_ssi->sys + SSI_SYSCONFIG_REG); - val = __raw_readl(omap_ssi->sys + SSI_SYSSTATUS_REG); + writel_relaxed(SSI_SOFTRESET, omap_ssi->sys + SSI_SYSCONFIG_REG); + val = readl(omap_ssi->sys + SSI_SYSSTATUS_REG); for (i = 0; ((i < 20) && !(val & SSI_RESETDONE)); i++) { msleep(20); - val = __raw_readl(omap_ssi->sys + SSI_SYSSTATUS_REG); + val = readl(omap_ssi->sys + SSI_SYSSTATUS_REG); } if (!(val & SSI_RESETDONE)) { dev_err(&ssi->device, "SSI HW reset failed\n"); - ssi_clk_disable(ssi); + pm_runtime_put_sync(ssi->device.parent); return -EIO; } /* Reseting GDD */ - __raw_writel(SSI_SWRESET, omap_ssi->gdd + SSI_GDD_GRST_REG); + writel_relaxed(SSI_SWRESET, omap_ssi->gdd + SSI_GDD_GRST_REG); /* Get FCK rate */ - omap_ssi->fck_rate = ssi_get_clk_rate(ssi) / 1000; /* KHz */ + omap_ssi->fck_rate = DIV_ROUND_CLOSEST(ssi_get_clk_rate(ssi), 1000); /* KHz */ dev_dbg(&ssi->device, "SSI fck rate %lu KHz\n", omap_ssi->fck_rate); /* Set default PM settings */ val = SSI_AUTOIDLE | SSI_SIDLEMODE_SMART | SSI_MIDLEMODE_SMART; - __raw_writel(val, omap_ssi->sys + SSI_SYSCONFIG_REG); + writel_relaxed(val, omap_ssi->sys + SSI_SYSCONFIG_REG); omap_ssi->sysconfig = val; - __raw_writel(SSI_CLK_AUTOGATING_ON, omap_ssi->sys + SSI_GDD_GCR_REG); + writel_relaxed(SSI_CLK_AUTOGATING_ON, omap_ssi->sys + SSI_GDD_GCR_REG); omap_ssi->gdd_gcr = SSI_CLK_AUTOGATING_ON; - ssi_clk_disable(ssi); + pm_runtime_put_sync(ssi->device.parent); return 0; } @@ -1804,31 +428,50 @@ static void ssi_remove_controller(struct hsi_controller *ssi) { struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); - ssi_ports_exit(ssi); tasklet_kill(&omap_ssi->gdd_tasklet); hsi_unregister_controller(ssi); } +static inline int ssi_of_get_available_child_count(const struct device_node *np) +{ + struct device_node *child; + int num = 0; + + for_each_child_of_node(np, child) + if (of_device_is_available(child)) + num++; + + return num; +} + static int __init ssi_probe(struct platform_device *pd) { - struct omap_ssi_platform_data *omap_ssi_pdata = pd->dev.platform_data; + struct device_node *np = pd->dev.of_node; struct hsi_controller *ssi; int err; + int num_ports; - if (!omap_ssi_pdata) { - dev_err(&pd->dev, "No OMAP SSI platform data\n"); + if(!np) { + dev_err(&pd->dev, "missing device tree data\n"); return -EINVAL; } - ssi = hsi_alloc_controller(omap_ssi_pdata->num_ports, GFP_KERNEL); + + num_ports = ssi_of_get_available_child_count(np); + + ssi = hsi_alloc_controller(num_ports, GFP_KERNEL); if (!ssi) { dev_err(&pd->dev, "No memory for controller\n"); return -ENOMEM; } + platform_set_drvdata(pd, ssi); - pm_runtime_enable(&pd->dev); + err = ssi_add_controller(ssi, pd); if (err < 0) goto out1; + + pm_runtime_enable(&pd->dev); + err = ssi_hw_init(ssi); if (err < 0) goto out2; @@ -1837,12 +480,19 @@ static int __init ssi_probe(struct platform_device *pd) if (err < 0) goto out2; #endif + + err = of_platform_populate(pd->dev.of_node, NULL, NULL, &pd->dev); + if (err) { + dev_err(&pd->dev, "failed to create ssi controller ports (err=%d)\n", err); + return -ENODEV; + } + + dev_info(&pd->dev, "ssi controller initialized (%d ports)!\n", num_ports); return err; out2: ssi_remove_controller(ssi); out1: platform_set_drvdata(pd, NULL); - pm_runtime_disable(&pd->dev); return err; @@ -1863,11 +513,64 @@ static int __exit ssi_remove(struct platform_device *pd) return 0; } +#ifdef CONFIG_PM_RUNTIME +static int omap_ssi_runtime_suspend(struct device *dev) +{ + struct hsi_controller *ssi = dev_get_drvdata(dev); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + + dev_dbg(dev, "runtime suspend!\n"); + + if (omap_ssi->get_loss) + omap_ssi->loss_count = + (*omap_ssi->get_loss)(ssi->device.parent); + + return 0; +} + +static int omap_ssi_runtime_resume(struct device *dev) +{ + struct hsi_controller *ssi = dev_get_drvdata(dev); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + + dev_dbg(dev, "runtime resume!\n"); + + if ((omap_ssi->get_loss) && (omap_ssi->loss_count == + (*omap_ssi->get_loss)(ssi->device.parent))) + return 0; + + writel_relaxed(omap_ssi->gdd_gcr, omap_ssi->gdd + SSI_GDD_GCR_REG); + + return 0; +} + +static const struct dev_pm_ops omap_ssi_pm_ops = { + SET_RUNTIME_PM_OPS(omap_ssi_runtime_suspend, omap_ssi_runtime_resume, + NULL) +}; + +#define DEV_PM_OPS (&omap_ssi_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id omap_ssi_of_match[] = { + { .compatible = "ti,omap3-ssi", }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_ssi_of_match); +#else +#define omap_ssi_of_match NULL +#endif + static struct platform_driver ssi_pdriver = { .remove = __exit_p(ssi_remove), .driver = { .name = "omap_ssi", .owner = THIS_MODULE, + .pm = DEV_PM_OPS, + .of_match_table = omap_ssi_of_match, }, }; diff --git a/drivers/hsi/controllers/omap_ssi.h b/drivers/hsi/controllers/omap_ssi.h new file mode 100644 index 000000000000..da92b4316a7b --- /dev/null +++ b/drivers/hsi/controllers/omap_ssi.h @@ -0,0 +1,167 @@ +/* OMAP SSI internal interface. + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * Copyright (C) 2013 Sebastian Reichel + * + * 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 __LINUX_HSI_OMAP_SSI_H__ +#define __LINUX_HSI_OMAP_SSI_H__ + +#include <linux/device.h> +#include <linux/hsi/hsi.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> + +#define SSI_MAX_CHANNELS 8 +#define SSI_MAX_GDD_LCH 8 +#define SSI_BYTES_TO_FRAMES(x) ((((x) - 1) >> 2) + 1) + +/** + * struct omap_ssm_ctx - OMAP synchronous serial module (TX/RX) context + * @mode: Bit transmission mode + * @channels: Number of channels + * @framesize: Frame size in bits + * @timeout: RX frame timeout + * @divisor: TX divider + * @arb_mode: Arbitration mode for TX frame (Round robin, priority) + */ +struct omap_ssm_ctx { + u32 mode; + u32 channels; + u32 frame_size; + union { + u32 timeout; /* Rx Only */ + struct { + u32 arb_mode; + u32 divisor; + }; /* Tx only */ + }; +}; + +/** + * struct omap_ssi_port - OMAP SSI port data + * @dev: device associated to the port (HSI port) + * @pdev: platform device associated to the port + * @sst_dma: SSI transmitter physical base address + * @ssr_dma: SSI receiver physical base address + * @sst_base: SSI transmitter base address + * @ssr_base: SSI receiver base address + * @wk_lock: spin lock to serialize access to the wake lines + * @lock: Spin lock to serialize access to the SSI port + * @channels: Current number of channels configured (1,2,4 or 8) + * @txqueue: TX message queues + * @rxqueue: RX message queues + * @brkqueue: Queue of incoming HWBREAK requests (FRAME mode) + * @irq: IRQ number + * @wake_irq: IRQ number for incoming wake line (-1 if none) + * @wake_gpio: GPIO number for incoming wake line (-1 if none) + * @pio_tasklet: Bottom half for PIO transfers and events + * @wake_tasklet: Bottom half for incoming wake events + * @wkin_cken: Keep track of clock references due to the incoming wake line + * @wk_refcount: Reference count for output wake line + * @sys_mpu_enable: Context for the interrupt enable register for irq 0 + * @sst: Context for the synchronous serial transmitter + * @ssr: Context for the synchronous serial receiver + */ +struct omap_ssi_port { + struct device *dev; + struct device *pdev; + dma_addr_t sst_dma; + dma_addr_t ssr_dma; + void __iomem *sst_base; + void __iomem *ssr_base; + spinlock_t wk_lock; + spinlock_t lock; + unsigned int channels; + struct list_head txqueue[SSI_MAX_CHANNELS]; + struct list_head rxqueue[SSI_MAX_CHANNELS]; + struct list_head brkqueue; + unsigned int irq; + int wake_irq; + int wake_gpio; + struct tasklet_struct pio_tasklet; + struct tasklet_struct wake_tasklet; + bool wktest:1; /* FIXME: HACK to be removed */ + bool wkin_cken:1; /* Workaround */ + unsigned int wk_refcount; + /* OMAP SSI port context */ + u32 sys_mpu_enable; /* We use only one irq */ + struct omap_ssm_ctx sst; + struct omap_ssm_ctx ssr; + u32 loss_count; + u32 port_id; +#ifdef CONFIG_DEBUG_FS + struct dentry *dir; +#endif +}; + +/** + * struct gdd_trn - GDD transaction data + * @msg: Pointer to the HSI message being served + * @sg: Pointer to the current sg entry being served + */ +struct gdd_trn { + struct hsi_msg *msg; + struct scatterlist *sg; +}; + +/** + * struct omap_ssi_controller - OMAP SSI controller data + * @dev: device associated to the controller (HSI controller) + * @sys: SSI I/O base address + * @gdd: GDD I/O base address + * @fck: SSI functional clock + * @gdd_irq: IRQ line for GDD + * @gdd_tasklet: bottom half for DMA transfers + * @gdd_trn: Array of GDD transaction data for ongoing GDD transfers + * @lock: lock to serialize access to GDD + * @loss_count: To follow if we need to restore context or not + * @max_speed: Maximum TX speed (Kb/s) set by the clients. + * @sysconfig: SSI controller saved context + * @gdd_gcr: SSI GDD saved context + * @get_loss: Pointer to omap_pm_get_dev_context_loss_count, if any + * @port: Array of pointers of the ports of the controller + * @dir: Debugfs SSI root directory + */ +struct omap_ssi_controller { + struct device *dev; + void __iomem *sys; + void __iomem *gdd; + struct clk *fck; // TODO: unused? + unsigned int gdd_irq; + struct tasklet_struct gdd_tasklet; + struct gdd_trn gdd_trn[SSI_MAX_GDD_LCH]; + spinlock_t lock; + unsigned long fck_rate; + u32 loss_count; + u32 max_speed; + /* OMAP SSI Controller context */ + u32 sysconfig; + u32 gdd_gcr; + int (*get_loss)(struct device *dev); + struct omap_ssi_port **port; +#ifdef CONFIG_DEBUG_FS + struct dentry *dir; +#endif +}; + +int __init ssi_get_iomem(struct platform_device *pd, + const char *name, void __iomem **pbase, dma_addr_t *phy); + +#endif /* __LINUX_HSI_OMAP_SSI_H__ */ diff --git a/drivers/hsi/controllers/omap_ssi_port.c b/drivers/hsi/controllers/omap_ssi_port.c new file mode 100644 index 000000000000..6659dec513fd --- /dev/null +++ b/drivers/hsi/controllers/omap_ssi_port.c @@ -0,0 +1,1330 @@ +/* OMAP SSI port driver. + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * Copyright (C) 2013 Sebastian Reichel + * + * 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/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/pm_runtime.h> + +#include <linux/of_gpio.h> +#include <linux/debugfs.h> + +#include "omap_ssi_regs.h" +#include "omap_ssi.h" + +static inline unsigned int ssi_wakein(struct hsi_port *port) +{ + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + return gpio_get_value(omap_port->wake_gpio); +} + +#ifdef CONFIG_DEBUG_FS +static void ssi_debug_remove_port(struct hsi_port *port) +{ + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + + debugfs_remove_recursive(omap_port->dir); +} + +static int ssi_debug_port_show(struct seq_file *m, void *p __maybe_unused) +{ + struct hsi_port *port = m->private; + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + void __iomem *base = omap_ssi->sys; + unsigned int ch; + + pm_runtime_get_sync(omap_port->pdev); + if (omap_port->wake_irq > 0) + seq_printf(m, "CAWAKE\t\t: %d\n", ssi_wakein(port)); + seq_printf(m, "WAKE\t\t: 0x%08x\n", + readl(base + SSI_WAKE_REG(port->num))); + seq_printf(m, "MPU_ENABLE_IRQ%d\t: 0x%08x\n", 0, + readl(base + SSI_MPU_ENABLE_REG(port->num, 0))); + seq_printf(m, "MPU_STATUS_IRQ%d\t: 0x%08x\n", 0, + readl(base + SSI_MPU_STATUS_REG(port->num, 0))); + /* SST */ + base = omap_port->sst_base; + seq_printf(m, "\nSST\n===\n"); + seq_printf(m, "ID SST\t\t: 0x%08x\n", + readl(base + SSI_SST_ID_REG)); + seq_printf(m, "MODE\t\t: 0x%08x\n", + readl(base + SSI_SST_MODE_REG)); + seq_printf(m, "FRAMESIZE\t: 0x%08x\n", + readl(base + SSI_SST_FRAMESIZE_REG)); + seq_printf(m, "DIVISOR\t\t: 0x%08x\n", + readl(base + SSI_SST_DIVISOR_REG)); + seq_printf(m, "CHANNELS\t: 0x%08x\n", + readl(base + SSI_SST_CHANNELS_REG)); + seq_printf(m, "ARBMODE\t\t: 0x%08x\n", + readl(base + SSI_SST_ARBMODE_REG)); + seq_printf(m, "TXSTATE\t\t: 0x%08x\n", + readl(base + SSI_SST_TXSTATE_REG)); + seq_printf(m, "BUFSTATE\t: 0x%08x\n", + readl(base + SSI_SST_BUFSTATE_REG)); + seq_printf(m, "BREAK\t\t: 0x%08x\n", + readl(base + SSI_SST_BREAK_REG)); + for (ch = 0; ch < omap_port->channels; ch++) { + seq_printf(m, "BUFFER_CH%d\t: 0x%08x\n", ch, + readl(base + SSI_SST_BUFFER_CH_REG(ch))); + } + /* SSR */ + base = omap_port->ssr_base; + seq_printf(m, "\nSSR\n===\n"); + seq_printf(m, "ID SSR\t\t: 0x%08x\n", + readl(base + SSI_SSR_ID_REG)); + seq_printf(m, "MODE\t\t: 0x%08x\n", + readl(base + SSI_SSR_MODE_REG)); + seq_printf(m, "FRAMESIZE\t: 0x%08x\n", + readl(base + SSI_SSR_FRAMESIZE_REG)); + seq_printf(m, "CHANNELS\t: 0x%08x\n", + readl(base + SSI_SSR_CHANNELS_REG)); + seq_printf(m, "TIMEOUT\t\t: 0x%08x\n", + readl(base + SSI_SSR_TIMEOUT_REG)); + seq_printf(m, "RXSTATE\t\t: 0x%08x\n", + readl(base + SSI_SSR_RXSTATE_REG)); + seq_printf(m, "BUFSTATE\t: 0x%08x\n", + readl(base + SSI_SSR_BUFSTATE_REG)); + seq_printf(m, "BREAK\t\t: 0x%08x\n", + readl(base + SSI_SSR_BREAK_REG)); + seq_printf(m, "ERROR\t\t: 0x%08x\n", + readl(base + SSI_SSR_ERROR_REG)); + seq_printf(m, "ERRORACK\t: 0x%08x\n", + readl(base + SSI_SSR_ERRORACK_REG)); + for (ch = 0; ch < omap_port->channels; ch++) { + seq_printf(m, "BUFFER_CH%d\t: 0x%08x\n", ch, + readl(base + SSI_SSR_BUFFER_CH_REG(ch))); + } + pm_runtime_put_sync(omap_port->pdev); + + return 0; +} + +static int ssi_port_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, ssi_debug_port_show, inode->i_private); +} + +static const struct file_operations ssi_port_regs_fops = { + .open = ssi_port_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int ssi_div_get(void *data, u64 *val) +{ + struct hsi_port *port = data; + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + + pm_runtime_get_sync(omap_port->pdev); + *val = readl(omap_port->sst_base + SSI_SST_DIVISOR_REG); + pm_runtime_put_sync(omap_port->pdev); + + return 0; +} + +static int ssi_div_set(void *data, u64 val) +{ + struct hsi_port *port = data; + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + + if (val > 127) + return -EINVAL; + + pm_runtime_get_sync(omap_port->pdev); + writel(val, omap_port->sst_base + SSI_SST_DIVISOR_REG); + omap_port->sst.divisor = val; + pm_runtime_put_sync(omap_port->pdev); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(ssi_sst_div_fops, ssi_div_get, ssi_div_set, "%llu\n"); + +static int __init ssi_debug_add_port(struct omap_ssi_port *omap_port, + struct dentry *dir) +{ + struct hsi_port *port = to_hsi_port(omap_port->dev); + + dir = debugfs_create_dir(dev_name(omap_port->dev), dir); + if (IS_ERR(dir)) + return PTR_ERR(dir); + omap_port->dir = dir; + debugfs_create_file("regs", S_IRUGO, dir, port, &ssi_port_regs_fops); + dir = debugfs_create_dir("sst", dir); + if (IS_ERR(dir)) + return PTR_ERR(dir); + debugfs_create_file("divisor", S_IRUGO | S_IWUSR, dir, port, + &ssi_sst_div_fops); + + return 0; +} +#endif + +static int ssi_claim_lch(struct hsi_msg *msg) +{ + + struct hsi_port *port = hsi_get_port(msg->cl); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + int lch; + + for (lch = 0; lch < SSI_MAX_GDD_LCH; lch++) + if (!omap_ssi->gdd_trn[lch].msg) { + omap_ssi->gdd_trn[lch].msg = msg; + omap_ssi->gdd_trn[lch].sg = msg->sgt.sgl; + return lch; + } + + return -EBUSY; +} + +static int ssi_start_dma(struct hsi_msg *msg, int lch) +{ + struct hsi_port *port = hsi_get_port(msg->cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + void __iomem *gdd = omap_ssi->gdd; + int err; + u16 csdp; + u16 ccr; + u32 s_addr; + u32 d_addr; + u32 tmp; + + if (msg->ttype == HSI_MSG_READ) { + err = dma_map_sg(&ssi->device, msg->sgt.sgl, msg->sgt.nents, + DMA_FROM_DEVICE); + if (err < 0) { + dev_dbg(&ssi->device, "DMA map SG failed !\n"); + return err; + } + csdp = SSI_DST_BURST_4x32_BIT | SSI_DST_MEMORY_PORT | + SSI_SRC_SINGLE_ACCESS0 | SSI_SRC_PERIPHERAL_PORT | + SSI_DATA_TYPE_S32; + ccr = msg->channel + 0x10 + (port->num * 8); /* Sync */ + ccr |= SSI_DST_AMODE_POSTINC | SSI_SRC_AMODE_CONST | + SSI_CCR_ENABLE; + s_addr = omap_port->ssr_dma + + SSI_SSR_BUFFER_CH_REG(msg->channel); + d_addr = sg_dma_address(msg->sgt.sgl); + } else { + err = dma_map_sg(&ssi->device, msg->sgt.sgl, msg->sgt.nents, + DMA_TO_DEVICE); + if (err < 0) { + dev_dbg(&ssi->device, "DMA map SG failed !\n"); + return err; + } + csdp = SSI_SRC_BURST_4x32_BIT | SSI_SRC_MEMORY_PORT | + SSI_DST_SINGLE_ACCESS0 | SSI_DST_PERIPHERAL_PORT | + SSI_DATA_TYPE_S32; + ccr = (msg->channel + 1 + (port->num * 8)) & 0xf; /* Sync */ + ccr |= SSI_SRC_AMODE_POSTINC | SSI_DST_AMODE_CONST | + SSI_CCR_ENABLE; + s_addr = sg_dma_address(msg->sgt.sgl); + d_addr = omap_port->sst_dma + + SSI_SST_BUFFER_CH_REG(msg->channel); + } + dev_dbg(&ssi->device, "lch %d cdsp %08x ccr %04x s_addr %08x" + " d_addr %08x\n", lch, csdp, ccr, s_addr, d_addr); + pm_runtime_get_sync(omap_port->pdev); /* Hold clocks during the transfer */ + writew_relaxed(csdp, gdd + SSI_GDD_CSDP_REG(lch)); + writew_relaxed(SSI_BLOCK_IE | SSI_TOUT_IE, gdd + SSI_GDD_CICR_REG(lch)); + writel_relaxed(d_addr, gdd + SSI_GDD_CDSA_REG(lch)); + writel_relaxed(s_addr, gdd + SSI_GDD_CSSA_REG(lch)); + writew_relaxed(SSI_BYTES_TO_FRAMES(msg->sgt.sgl->length), + gdd + SSI_GDD_CEN_REG(lch)); + + spin_lock_bh(&omap_ssi->lock); + tmp = readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + tmp |= SSI_GDD_LCH(lch); + writel_relaxed(tmp, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + spin_unlock_bh(&omap_ssi->lock); + writew(ccr, gdd + SSI_GDD_CCR_REG(lch)); + msg->status = HSI_STATUS_PROCEEDING; + + return 0; +} + +static int ssi_start_pio(struct hsi_msg *msg) +{ + struct hsi_port *port = hsi_get_port(msg->cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + u32 val; + + pm_runtime_get_sync(omap_port->pdev); + if (msg->ttype == HSI_MSG_WRITE) { + val = SSI_DATAACCEPT(msg->channel); + pm_runtime_get_sync(omap_port->pdev); /* Hold clocks for pio writes */ + } else { + val = SSI_DATAAVAILABLE(msg->channel) | SSI_ERROROCCURED; + } + dev_dbg(&port->device, "Single %s transfer\n", + msg->ttype ? "write" : "read"); + val |= readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + writel(val, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + pm_runtime_put_sync(omap_port->pdev); + msg->actual_len = 0; + msg->status = HSI_STATUS_PROCEEDING; + + return 0; +} + +static int ssi_start_transfer(struct list_head *queue) +{ + struct hsi_msg *msg; + int lch = -1; + + if (list_empty(queue)) + return 0; + msg = list_first_entry(queue, struct hsi_msg, link); + if (msg->status != HSI_STATUS_QUEUED) + return 0; + if ((msg->sgt.nents) && (msg->sgt.sgl->length > sizeof(u32))) + lch = ssi_claim_lch(msg); + if (lch >= 0) + return ssi_start_dma(msg, lch); + else + return ssi_start_pio(msg); +} + +static int ssi_async_break(struct hsi_msg *msg) +{ + struct hsi_port *port = hsi_get_port(msg->cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + int err = 0; + u32 tmp; + + pm_runtime_get_sync(omap_port->pdev); + if (msg->ttype == HSI_MSG_WRITE) { + if (omap_port->sst.mode != SSI_MODE_FRAME) { + err = -EINVAL; + goto out; + } + writel(1, omap_port->sst_base + SSI_SST_BREAK_REG); + msg->status = HSI_STATUS_COMPLETED; + msg->complete(msg); + } else { + if (omap_port->ssr.mode != SSI_MODE_FRAME) { + err = -EINVAL; + goto out; + } + spin_lock_bh(&omap_port->lock); + tmp = readl(omap_ssi->sys + + SSI_MPU_ENABLE_REG(port->num, 0)); + writel(tmp | SSI_BREAKDETECTED, + omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + msg->status = HSI_STATUS_PROCEEDING; + list_add_tail(&msg->link, &omap_port->brkqueue); + spin_unlock_bh(&omap_port->lock); + } +out: + pm_runtime_put_sync(omap_port->pdev); + + return err; +} + +static int ssi_async(struct hsi_msg *msg) +{ + struct hsi_port *port = hsi_get_port(msg->cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct list_head *queue; + int err = 0; + + BUG_ON(!msg); + + if (msg->sgt.nents > 1) + return -ENOSYS; /* TODO: Add sg support */ + + if (msg->break_frame) + return ssi_async_break(msg); + + if (msg->ttype) { + BUG_ON(msg->channel >= omap_port->sst.channels); + queue = &omap_port->txqueue[msg->channel]; + } else { + BUG_ON(msg->channel >= omap_port->ssr.channels); + queue = &omap_port->rxqueue[msg->channel]; + } + msg->status = HSI_STATUS_QUEUED; + spin_lock_bh(&omap_port->lock); + list_add_tail(&msg->link, queue); + err = ssi_start_transfer(queue); + if (err < 0) { + list_del(&msg->link); + msg->status = HSI_STATUS_ERROR; + } + spin_unlock_bh(&omap_port->lock); + dev_dbg(&port->device, "msg status %d ttype %d ch %d\n", + msg->status, msg->ttype, msg->channel); + + return err; +} + +static u32 ssi_calculate_div(struct hsi_controller *ssi) +{ + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + u32 tx_fckrate = (u32) omap_ssi->fck_rate; + + /* / 2 : SSI TX clock is always half of the SSI functional clock */ + tx_fckrate >>= 1; + /* Round down when tx_fckrate % omap_ssi->max_speed == 0 */ + tx_fckrate--; + dev_dbg(&ssi->device, "TX div %d for fck_rate %lu Khz speed %d Kb/s\n", + tx_fckrate / omap_ssi->max_speed, omap_ssi->fck_rate, + omap_ssi->max_speed); + + return tx_fckrate / omap_ssi->max_speed; +} + +static void ssi_flush_queue(struct list_head *queue, struct hsi_client *cl) +{ + struct list_head *node, *tmp; + struct hsi_msg *msg; + + list_for_each_safe(node, tmp, queue) { + msg = list_entry(node, struct hsi_msg, link); + if ((cl) && (cl != msg->cl)) + continue; + list_del(node); + pr_debug("flush queue: ch %d, msg %p len %d type %d ctxt %p\n", + msg->channel, msg, msg->sgt.sgl->length, + msg->ttype, msg->context); + if (msg->destructor) + msg->destructor(msg); + else + hsi_free_msg(msg); + } +} + +static int ssi_setup(struct hsi_client *cl) +{ + struct hsi_port *port = to_hsi_port(cl->device.parent); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + void __iomem *sst = omap_port->sst_base; + void __iomem *ssr = omap_port->ssr_base; + u32 div; + u32 val; + int err = 0; + + pm_runtime_get_sync(omap_port->pdev); + spin_lock_bh(&omap_port->lock); + if (cl->tx_cfg.speed) + omap_ssi->max_speed = cl->tx_cfg.speed; + div = ssi_calculate_div(ssi); + if (div > SSI_MAX_DIVISOR) { + dev_err(&cl->device, "Invalid TX speed %d Mb/s (div %d)\n", + cl->tx_cfg.speed, div); + err = -EINVAL; + goto out; + } + /* Set TX/RX module to sleep to stop TX/RX during cfg update */ + writel_relaxed(SSI_MODE_SLEEP, sst + SSI_SST_MODE_REG); + writel_relaxed(SSI_MODE_SLEEP, ssr + SSI_SSR_MODE_REG); + /* Flush posted write */ + val = readl(ssr + SSI_SSR_MODE_REG); + /* TX */ + writel_relaxed(31, sst + SSI_SST_FRAMESIZE_REG); + writel_relaxed(div, sst + SSI_SST_DIVISOR_REG); + writel_relaxed(cl->tx_cfg.channels, sst + SSI_SST_CHANNELS_REG); + writel_relaxed(cl->tx_cfg.arb_mode, sst + SSI_SST_ARBMODE_REG); + writel_relaxed(cl->tx_cfg.mode, sst + SSI_SST_MODE_REG); + /* RX */ + writel_relaxed(31, ssr + SSI_SSR_FRAMESIZE_REG); + writel_relaxed(cl->rx_cfg.channels, ssr + SSI_SSR_CHANNELS_REG); + writel_relaxed(0, ssr + SSI_SSR_TIMEOUT_REG); + /* Cleanup the break queue if we leave FRAME mode */ + if ((omap_port->ssr.mode == SSI_MODE_FRAME) && + (cl->rx_cfg.mode != SSI_MODE_FRAME)) + ssi_flush_queue(&omap_port->brkqueue, cl); + writel_relaxed(cl->rx_cfg.mode, ssr + SSI_SSR_MODE_REG); + omap_port->channels = max(cl->rx_cfg.channels, cl->tx_cfg.channels); + /* Shadow registering for OFF mode */ + /* SST */ + omap_port->sst.divisor = div; + omap_port->sst.frame_size = 31; + omap_port->sst.channels = cl->tx_cfg.channels; + omap_port->sst.arb_mode = cl->tx_cfg.arb_mode; + omap_port->sst.mode = cl->tx_cfg.mode; + /* SSR */ + omap_port->ssr.frame_size = 31; + omap_port->ssr.timeout = 0; + omap_port->ssr.channels = cl->rx_cfg.channels; + omap_port->ssr.mode = cl->rx_cfg.mode; +out: + spin_unlock_bh(&omap_port->lock); + pm_runtime_put_sync(omap_port->pdev); + + return err; +} + +static int ssi_flush(struct hsi_client *cl) +{ + struct hsi_port *port = hsi_get_port(cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + struct hsi_msg *msg; + void __iomem *sst = omap_port->sst_base; + void __iomem *ssr = omap_port->ssr_base; + unsigned int i; + u32 err; + + pm_runtime_get_sync(omap_port->pdev); + spin_lock_bh(&omap_port->lock); + /* Stop all DMA transfers */ + for (i = 0; i < SSI_MAX_GDD_LCH; i++) { + msg = omap_ssi->gdd_trn[i].msg; + if (!msg || (port != hsi_get_port(msg->cl))) + continue; + writew_relaxed(0, omap_ssi->gdd + SSI_GDD_CCR_REG(i)); + if (msg->ttype == HSI_MSG_READ) + pm_runtime_put_sync(omap_port->pdev); + omap_ssi->gdd_trn[i].msg = NULL; + } + /* Flush all SST buffers */ + writel_relaxed(0, sst + SSI_SST_BUFSTATE_REG); + writel_relaxed(0, sst + SSI_SST_TXSTATE_REG); + /* Flush all SSR buffers */ + writel_relaxed(0, ssr + SSI_SSR_RXSTATE_REG); + writel_relaxed(0, ssr + SSI_SSR_BUFSTATE_REG); + /* Flush all errors */ + err = readl(ssr + SSI_SSR_ERROR_REG); + writel_relaxed(err, ssr + SSI_SSR_ERRORACK_REG); + /* Flush break */ + writel_relaxed(0, ssr + SSI_SSR_BREAK_REG); + /* Clear interrupts */ + writel_relaxed(0, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + writel_relaxed(0xffffff00, + omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); + writel_relaxed(0, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + writel(0xff, omap_ssi->sys + SSI_GDD_MPU_IRQ_STATUS_REG); + /* Dequeue all pending requests */ + for (i = 0; i < omap_port->channels; i++) { + /* Release write clocks */ + if (!list_empty(&omap_port->txqueue[i])) + pm_runtime_put_sync(omap_port->pdev); + ssi_flush_queue(&omap_port->txqueue[i], NULL); + ssi_flush_queue(&omap_port->rxqueue[i], NULL); + } + ssi_flush_queue(&omap_port->brkqueue, NULL); + spin_unlock_bh(&omap_port->lock); + pm_runtime_put_sync(omap_port->pdev); + + return 0; +} + +static int ssi_start_tx(struct hsi_client *cl) +{ + struct hsi_port *port = hsi_get_port(cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + + dev_dbg(&port->device, "Wake out high %d\n", omap_port->wk_refcount); + + spin_lock_bh(&omap_port->wk_lock); + if (omap_port->wk_refcount++) { + spin_unlock_bh(&omap_port->wk_lock); + return 0; + } + pm_runtime_get_sync(omap_port->pdev); /* Grab clocks */ + writel(SSI_WAKE(0), omap_ssi->sys + SSI_SET_WAKE_REG(port->num)); + spin_unlock_bh(&omap_port->wk_lock); + + return 0; +} + +static int ssi_stop_tx(struct hsi_client *cl) +{ + struct hsi_port *port = hsi_get_port(cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + + dev_dbg(&port->device, "Wake out low %d\n", omap_port->wk_refcount); + + spin_lock_bh(&omap_port->wk_lock); + BUG_ON(!omap_port->wk_refcount); + if (--omap_port->wk_refcount) { + spin_unlock_bh(&omap_port->wk_lock); + return 0; + } + writel(SSI_WAKE(0), + omap_ssi->sys + SSI_CLEAR_WAKE_REG(port->num)); + pm_runtime_put_sync(omap_port->pdev); /* Release clocks */ + spin_unlock_bh(&omap_port->wk_lock); + + return 0; +} + +static void ssi_transfer(struct omap_ssi_port *omap_port, + struct list_head *queue) +{ + struct hsi_msg *msg; + int err = -1; + + spin_lock_bh(&omap_port->lock); + while (err < 0) { + err = ssi_start_transfer(queue); + if (err < 0) { + msg = list_first_entry(queue, struct hsi_msg, link); + msg->status = HSI_STATUS_ERROR; + msg->actual_len = 0; + list_del(&msg->link); + spin_unlock_bh(&omap_port->lock); + msg->complete(msg); + spin_lock_bh(&omap_port->lock); + } + } + spin_unlock_bh(&omap_port->lock); +} + +static void ssi_cleanup_queues(struct hsi_client *cl) +{ + struct hsi_port *port = hsi_get_port(cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + struct hsi_msg *msg; + unsigned int i; + u32 rxbufstate = 0; + u32 txbufstate = 0; + u32 status = SSI_ERROROCCURED; + u32 tmp; + + ssi_flush_queue(&omap_port->brkqueue, cl); + if (list_empty(&omap_port->brkqueue)) + status |= SSI_BREAKDETECTED; + + for (i = 0; i < omap_port->channels; i++) { + if (list_empty(&omap_port->txqueue[i])) + continue; + msg = list_first_entry(&omap_port->txqueue[i], struct hsi_msg, + link); + if ((msg->cl == cl) && (msg->status == HSI_STATUS_PROCEEDING)) { + txbufstate |= (1 << i); + status |= SSI_DATAACCEPT(i); + /* Release the clocks writes, also GDD ones */ + pm_runtime_put_sync(omap_port->pdev); + } + ssi_flush_queue(&omap_port->txqueue[i], cl); + } + for (i = 0; i < omap_port->channels; i++) { + if (list_empty(&omap_port->rxqueue[i])) + continue; + msg = list_first_entry(&omap_port->rxqueue[i], struct hsi_msg, + link); + if ((msg->cl == cl) && (msg->status == HSI_STATUS_PROCEEDING)) { + rxbufstate |= (1 << i); + status |= SSI_DATAAVAILABLE(i); + } + ssi_flush_queue(&omap_port->rxqueue[i], cl); + /* Check if we keep the error detection interrupt armed */ + if (!list_empty(&omap_port->rxqueue[i])) + status &= ~SSI_ERROROCCURED; + } + /* Cleanup write buffers */ + tmp = readl(omap_port->sst_base + SSI_SST_BUFSTATE_REG); + tmp &= ~txbufstate; + writel_relaxed(tmp, omap_port->sst_base + SSI_SST_BUFSTATE_REG); + /* Cleanup read buffers */ + tmp = readl(omap_port->ssr_base + SSI_SSR_BUFSTATE_REG); + tmp &= ~rxbufstate; + writel_relaxed(tmp, omap_port->ssr_base + SSI_SSR_BUFSTATE_REG); + /* Disarm and ack pending interrupts */ + tmp = readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + tmp &= ~status; + writel_relaxed(tmp, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + writel_relaxed(status, omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); +} + +static void ssi_cleanup_gdd(struct hsi_controller *ssi, struct hsi_client *cl) +{ + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + struct hsi_port *port = hsi_get_port(cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_msg *msg; + unsigned int i; + u32 val = 0; + u32 tmp; + + for (i = 0; i < SSI_MAX_GDD_LCH; i++) { + msg = omap_ssi->gdd_trn[i].msg; + if ((!msg) || (msg->cl != cl)) + continue; + writew_relaxed(0, omap_ssi->gdd + SSI_GDD_CCR_REG(i)); + val |= (1 << i); + /* + * Clock references for write will be handled in + * ssi_cleanup_queues + */ + if (msg->ttype == HSI_MSG_READ) + pm_runtime_put_sync(omap_port->pdev); + omap_ssi->gdd_trn[i].msg = NULL; + } + tmp = readl_relaxed(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + tmp &= ~val; + writel_relaxed(tmp, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + writel(val, omap_ssi->sys + SSI_GDD_MPU_IRQ_STATUS_REG); +} + +static int ssi_set_port_mode(struct omap_ssi_port *omap_port, u32 mode) +{ + writel(mode, omap_port->sst_base + SSI_SST_MODE_REG); + writel(mode, omap_port->ssr_base + SSI_SSR_MODE_REG); + /* OCP barrier */ + mode = readl(omap_port->ssr_base + SSI_SSR_MODE_REG); + + return 0; +} + +static int ssi_release(struct hsi_client *cl) +{ + struct hsi_port *port = hsi_get_port(cl); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + + spin_lock_bh(&omap_port->lock); + pm_runtime_get_sync(omap_port->pdev); + /* Stop all the pending DMA requests for that client */ + ssi_cleanup_gdd(ssi, cl); + /* Now cleanup all the queues */ + ssi_cleanup_queues(cl); + pm_runtime_put_sync(omap_port->pdev); + /* If it is the last client of the port, do extra checks and cleanup */ + if (port->claimed <= 1) { + /* + * Drop the clock reference for the incoming wake line + * if it is still kept high by the other side. + */ + if (omap_port->wkin_cken) { + pm_runtime_put_sync(omap_port->pdev); + omap_port->wkin_cken = 0; + } + pm_runtime_get_sync(omap_port->pdev); + /* Stop any SSI TX/RX without a client */ + ssi_set_port_mode(omap_port, SSI_MODE_SLEEP); + omap_port->sst.mode = SSI_MODE_SLEEP; + omap_port->ssr.mode = SSI_MODE_SLEEP; + pm_runtime_put_sync(omap_port->pdev); + WARN_ON(omap_port->wk_refcount != 0); + } + spin_unlock_bh(&omap_port->lock); + + return 0; +} + + + +static void ssi_error(struct hsi_port *port) +{ + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + struct hsi_msg *msg; + unsigned int i; + u32 err; + u32 val; + u32 tmp; + + /* ACK error */ + err = readl(omap_port->ssr_base + SSI_SSR_ERROR_REG); + dev_err(&port->device, "SSI error: 0x%02x\n", err); + if (!err) { + dev_dbg(&port->device, "spurious SSI error ignored!\n"); + return; + } + spin_lock(&omap_ssi->lock); + /* Cancel all GDD read transfers */ + for (i = 0, val = 0; i < SSI_MAX_GDD_LCH; i++) { + msg = omap_ssi->gdd_trn[i].msg; + if ((msg) && (msg->ttype == HSI_MSG_READ)) { + writew_relaxed(0, omap_ssi->gdd + SSI_GDD_CCR_REG(i)); + val |= (1 << i); + omap_ssi->gdd_trn[i].msg = NULL; + } + } + tmp = readl(omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + tmp &= ~val; + writel_relaxed(tmp, omap_ssi->sys + SSI_GDD_MPU_IRQ_ENABLE_REG); + spin_unlock(&omap_ssi->lock); + /* Cancel all PIO read transfers */ + spin_lock(&omap_port->lock); + tmp = readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + tmp &= 0xfeff00ff; /* Disable error & all dataavailable interrupts */ + writel_relaxed(tmp, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + /* ACK error */ + writel_relaxed(err, omap_port->ssr_base + SSI_SSR_ERRORACK_REG); + writel_relaxed(SSI_ERROROCCURED, + omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); + /* Signal the error all current pending read requests */ + for (i = 0; i < omap_port->channels; i++) { + if (list_empty(&omap_port->rxqueue[i])) + continue; + msg = list_first_entry(&omap_port->rxqueue[i], struct hsi_msg, + link); + list_del(&msg->link); + msg->status = HSI_STATUS_ERROR; + spin_unlock(&omap_port->lock); + msg->complete(msg); + /* Now restart queued reads if any */ + ssi_transfer(omap_port, &omap_port->rxqueue[i]); + spin_lock(&omap_port->lock); + } + spin_unlock(&omap_port->lock); +} + +static void ssi_break_complete(struct hsi_port *port) +{ + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + struct hsi_msg *msg; + struct hsi_msg *tmp; + u32 val; + + dev_dbg(&port->device, "HWBREAK received\n"); + + spin_lock(&omap_port->lock); + val = readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + val &= ~SSI_BREAKDETECTED; + writel_relaxed(val, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + writel_relaxed(0, omap_port->ssr_base + SSI_SSR_BREAK_REG); + writel(SSI_BREAKDETECTED, + omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); + spin_unlock(&omap_port->lock); + + list_for_each_entry_safe(msg, tmp, &omap_port->brkqueue, link) { + msg->status = HSI_STATUS_COMPLETED; + spin_lock(&omap_port->lock); + list_del(&msg->link); + spin_unlock(&omap_port->lock); + msg->complete(msg); + } + +} + +static void ssi_pio_complete(struct hsi_port *port, struct list_head *queue) +{ + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_msg *msg; + u32 *buf; + u32 reg; + u32 val; + + spin_lock(&omap_port->lock); + msg = list_first_entry(queue, struct hsi_msg, link); + if ((!msg->sgt.nents) || (!msg->sgt.sgl->length)) { + msg->actual_len = 0; + msg->status = HSI_STATUS_PENDING; + } + if (msg->ttype == HSI_MSG_WRITE) + val = SSI_DATAACCEPT(msg->channel); + else + val = SSI_DATAAVAILABLE(msg->channel); + if (msg->status == HSI_STATUS_PROCEEDING) { + buf = sg_virt(msg->sgt.sgl) + msg->actual_len; + if (msg->ttype == HSI_MSG_WRITE) + writel(*buf, omap_port->sst_base + + SSI_SST_BUFFER_CH_REG(msg->channel)); + else + *buf = readl(omap_port->ssr_base + + SSI_SSR_BUFFER_CH_REG(msg->channel)); + dev_dbg(&port->device, "ch %d ttype %d 0x%08x\n", msg->channel, + msg->ttype, *buf); + msg->actual_len += sizeof(*buf); + if (msg->actual_len >= msg->sgt.sgl->length) + msg->status = HSI_STATUS_COMPLETED; + /* + * Wait for the last written frame to be really sent before + * we call the complete callback + */ + if ((msg->status == HSI_STATUS_PROCEEDING) || + ((msg->status == HSI_STATUS_COMPLETED) && + (msg->ttype == HSI_MSG_WRITE))) { + writel(val, omap_ssi->sys + + SSI_MPU_STATUS_REG(port->num, 0)); + spin_unlock(&omap_port->lock); + + return; + } + + } + /* Transfer completed at this point */ + reg = readl(omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + if (msg->ttype == HSI_MSG_WRITE) + pm_runtime_put_sync(omap_port->pdev); /* Release clocks for write transfer */ + reg &= ~val; + writel_relaxed(reg, omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + writel_relaxed(val, omap_ssi->sys + SSI_MPU_STATUS_REG(port->num, 0)); + list_del(&msg->link); + spin_unlock(&omap_port->lock); + msg->complete(msg); + ssi_transfer(omap_port, queue); +} + +static void ssi_pio_tasklet(unsigned long ssi_port) +{ + struct hsi_port *port = (struct hsi_port *)ssi_port; + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + void __iomem *sys = omap_ssi->sys; + unsigned int ch; + u32 status_reg; + + pm_runtime_get_sync(omap_port->pdev); + status_reg = readl(sys + SSI_MPU_STATUS_REG(port->num, 0)); + status_reg &= readl(sys + SSI_MPU_ENABLE_REG(port->num, 0)); + + for (ch = 0; ch < omap_port->channels; ch++) { + if (status_reg & SSI_DATAACCEPT(ch)) + ssi_pio_complete(port, &omap_port->txqueue[ch]); + if (status_reg & SSI_DATAAVAILABLE(ch)) + ssi_pio_complete(port, &omap_port->rxqueue[ch]); + } + if (status_reg & SSI_BREAKDETECTED) + ssi_break_complete(port); + if (status_reg & SSI_ERROROCCURED) + ssi_error(port); + + status_reg = readl(sys + SSI_MPU_STATUS_REG(port->num, 0)); + status_reg &= readl(sys + SSI_MPU_ENABLE_REG(port->num, 0)); + pm_runtime_put_sync(omap_port->pdev); + + if (status_reg) + tasklet_hi_schedule(&omap_port->pio_tasklet); + else + enable_irq(omap_port->irq); +} + +static irqreturn_t ssi_pio_isr(int irq, void *port) +{ + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + + tasklet_hi_schedule(&omap_port->pio_tasklet); + disable_irq_nosync(irq); + + return IRQ_HANDLED; +} + +static void ssi_wake_tasklet(unsigned long ssi_port) +{ + struct hsi_port *port = (struct hsi_port *)ssi_port; + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + + if (ssi_wakein(port)) { + /** + * We can have a quick High-Low-High transition in the line. + * In such a case if we have long interrupt latencies, + * we can miss the low event or get twice a high event. + * This workaround will avoid breaking the clock reference + * count when such a situation ocurrs. + */ + spin_lock(&omap_port->lock); + if (!omap_port->wkin_cken) { + omap_port->wkin_cken = 1; + pm_runtime_get_sync(omap_port->pdev); + } + spin_unlock(&omap_port->lock); + dev_dbg(&ssi->device, "Wake in high\n"); + if (omap_port->wktest) { /* FIXME: HACK ! To be removed */ + writel(SSI_WAKE(0), + omap_ssi->sys + SSI_SET_WAKE_REG(port->num)); + } + hsi_event(port, HSI_EVENT_START_RX); + } else { + dev_dbg(&ssi->device, "Wake in low\n"); + if (omap_port->wktest) { /* FIXME: HACK ! To be removed */ + writel(SSI_WAKE(0), + omap_ssi->sys + SSI_CLEAR_WAKE_REG(port->num)); + } + hsi_event(port, HSI_EVENT_STOP_RX); + spin_lock(&omap_port->lock); + if (omap_port->wkin_cken) { + pm_runtime_put_sync(omap_port->pdev); + omap_port->wkin_cken = 0; + } + spin_unlock(&omap_port->lock); + } +} + +static irqreturn_t ssi_wake_isr(int irq __maybe_unused, void *ssi_port) +{ + struct omap_ssi_port *omap_port = hsi_port_drvdata(ssi_port); + + tasklet_hi_schedule(&omap_port->wake_tasklet); + + return IRQ_HANDLED; +} + +static int __init ssi_port_irq(struct hsi_port *port, + struct platform_device *pd) +{ + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct resource *irq; + int err; + + irq = platform_get_resource_byname(pd, IORESOURCE_IRQ, "mpu_irq0"); + if (!irq) { + dev_err(&port->device, "Port IRQ resource missing\n"); + return -ENXIO; + } + omap_port->irq = irq->start; + tasklet_init(&omap_port->pio_tasklet, ssi_pio_tasklet, + (unsigned long)port); + err = devm_request_irq(&pd->dev, omap_port->irq, ssi_pio_isr, + 0, irq->name, port); + if (err < 0) + dev_err(&port->device, "Request IRQ %d failed (%d)\n", + omap_port->irq, err); + return err; +} + +static int __init ssi_wake_irq(struct hsi_port *port, + struct platform_device *pd) +{ + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + int cawake_irq; + int err; + + if (omap_port->wake_gpio == -1) { + omap_port->wake_irq = -1; + return 0; + } + + cawake_irq = gpio_to_irq(omap_port->wake_gpio); + + omap_port->wake_irq = cawake_irq; + tasklet_init(&omap_port->wake_tasklet, ssi_wake_tasklet, + (unsigned long)port); + err = devm_request_irq(&pd->dev, cawake_irq, ssi_wake_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "cawake", port); + if (err < 0) + dev_err(&port->device, "Request Wake in IRQ %d failed %d\n", + cawake_irq, err); + err = enable_irq_wake(cawake_irq); + if (err < 0) + dev_err(&port->device, "Enable wake on the wakeline in irq %d" + " failed %d\n", cawake_irq, err); + + return err; +} + +static void __init ssi_queues_init(struct omap_ssi_port *omap_port) +{ + unsigned int ch; + + for (ch = 0; ch < SSI_MAX_CHANNELS; ch++) { + INIT_LIST_HEAD(&omap_port->txqueue[ch]); + INIT_LIST_HEAD(&omap_port->rxqueue[ch]); + } + INIT_LIST_HEAD(&omap_port->brkqueue); +} + +static int __init ssi_port_init(struct platform_device *pd, u32 cawake_gpio) +{ + struct hsi_port *port; + struct omap_ssi_port *omap_port; + struct hsi_controller *ssi = dev_get_drvdata(pd->dev.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + u32 port_id; + int err; + + dev_dbg(&pd->dev, "init ssi port...\n"); + + err = devm_gpio_request_one(&pd->dev, cawake_gpio, GPIOF_DIR_IN, "cawake"); + if (err) { + dev_err(&pd->dev, "could not request cawake gpio (err=%d)!\n", err); + return -ENODEV; + } + + omap_port = devm_kzalloc(&pd->dev, sizeof(*omap_port), GFP_KERNEL); + if (!omap_port) + return -ENOMEM; + omap_port->wake_gpio = cawake_gpio; + omap_port->pdev = &pd->dev; + + if (!ssi->port || !omap_ssi->port) { + dev_err(&pd->dev, "ssi controller not initialized!\n"); + return -ENODEV; + } + + /* get id of first uninitialized port in controller */ + for (port_id = 0; port_id < ssi->num_ports && omap_ssi->port[port_id]; port_id++); + if (port_id >= ssi->num_ports) { + dev_err(&pd->dev, "port id out of range!\n"); + return -ENODEV; + } + omap_port->port_id = port_id; + + /* initialize HSI port */ + port = ssi->port[port_id]; + port->async = ssi_async; + port->setup = ssi_setup; + port->flush = ssi_flush; + port->start_tx = ssi_start_tx; + port->stop_tx = ssi_stop_tx; + port->release = ssi_release; + hsi_port_set_drvdata(port, omap_port); + omap_ssi->port[port_id] = omap_port; + + platform_set_drvdata(pd, port); + + err = ssi_get_iomem(pd, "tx", &omap_port->sst_base, &omap_port->sst_dma); + if (err < 0) + return err; + err = ssi_get_iomem(pd, "rx", &omap_port->ssr_base, &omap_port->ssr_dma); + if (err < 0) + return err; + + err = ssi_port_irq(port, pd); + if (err < 0) + return err; + err = ssi_wake_irq(port, pd); + if (err < 0) + return err; + + ssi_queues_init(omap_port); + spin_lock_init(&omap_port->lock); + spin_lock_init(&omap_port->wk_lock); + omap_port->dev = &port->device; + + pm_runtime_enable(omap_port->pdev); + +#ifdef CONFIG_DEBUG_FS + err = ssi_debug_add_port(omap_port, omap_ssi->dir); + if (err < 0) { + pm_runtime_disable(omap_port->pdev); + return err; + } +#endif + + dev_info(&pd->dev, "ssi port %u successfully initialized (cawake=%d)\n", port_id, cawake_gpio); + + return 0; +} + +static int __init ssi_port_probe(struct platform_device *pd) +{ + struct device_node *np = pd->dev.of_node; + u32 cawake_gpio = 0; + int ret; + + if(!np) { + dev_err(&pd->dev, "missing device tree data\n"); + return -EINVAL; + } + + cawake_gpio = of_get_named_gpio(np, "ti,ssi-cawake-gpio", 0); + if(cawake_gpio < 0) { + dev_err(&pd->dev, "DT data is missing cawake gpio (err=%d)\n", cawake_gpio); + return -ENXIO; + } + + ret = ssi_port_init(pd, cawake_gpio); + if (ret < 0) + return ret; + + return 0; +} + +static int __exit ssi_port_remove(struct platform_device *pd) +{ + struct hsi_port *port = platform_get_drvdata(pd); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + +#ifdef CONFIG_DEBUG_FS + ssi_debug_remove_port(port); +#endif + + tasklet_kill(&omap_port->wake_tasklet); + tasklet_kill(&omap_port->pio_tasklet); + + port->async = NULL; + port->setup = NULL; + port->flush = NULL; + port->start_tx = NULL; + port->stop_tx = NULL; + port->release = NULL; + hsi_port_set_drvdata(port, NULL); + + omap_ssi->port[omap_port->port_id] = NULL; + platform_set_drvdata(pd, NULL); + pm_runtime_disable(&pd->dev); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int ssi_save_port_ctx(struct omap_ssi_port *omap_port) +{ + struct hsi_port *port = to_hsi_port(omap_port->dev); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + + omap_port->sys_mpu_enable = readl(omap_ssi->sys + + SSI_MPU_ENABLE_REG(port->num, 0)); + + return 0; +} + +static int ssi_restore_port_ctx(struct omap_ssi_port *omap_port) +{ + struct hsi_port *port = to_hsi_port(omap_port->dev); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + void __iomem *base; + + writel_relaxed(omap_port->sys_mpu_enable, + omap_ssi->sys + SSI_MPU_ENABLE_REG(port->num, 0)); + + /* SST context */ + base = omap_port->sst_base; + writel_relaxed(omap_port->sst.frame_size, base + SSI_SST_FRAMESIZE_REG); + writel_relaxed(omap_port->sst.channels, base + SSI_SST_CHANNELS_REG); + writel_relaxed(omap_port->sst.arb_mode, base + SSI_SST_ARBMODE_REG); + + /* SSR context */ + base = omap_port->ssr_base; + writel_relaxed(omap_port->ssr.frame_size, base + SSI_SSR_FRAMESIZE_REG); + writel_relaxed(omap_port->ssr.channels, base + SSI_SSR_CHANNELS_REG); + writel_relaxed(omap_port->ssr.timeout, base + SSI_SSR_TIMEOUT_REG); + + return 0; +} + +static int ssi_restore_port_mode(struct omap_ssi_port *omap_port) +{ + u32 mode; + + writel_relaxed(omap_port->sst.mode, + omap_port->sst_base + SSI_SST_MODE_REG); + writel_relaxed(omap_port->ssr.mode, + omap_port->ssr_base + SSI_SSR_MODE_REG); + /* OCP barrier */ + mode = readl(omap_port->ssr_base + SSI_SSR_MODE_REG); + + return 0; +} + +static int ssi_restore_divisor(struct omap_ssi_port *omap_port) +{ + writel_relaxed(omap_port->sst.divisor, + omap_port->sst_base + SSI_SST_DIVISOR_REG); + + return 0; +} + +static int omap_ssi_port_runtime_suspend(struct device *dev) +{ + struct hsi_port *port = dev_get_drvdata(dev); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + + dev_dbg(dev, "port runtime suspend!\n"); + + ssi_set_port_mode(omap_port, SSI_MODE_SLEEP); + if (omap_ssi->get_loss) + omap_port->loss_count = + (*omap_ssi->get_loss)(ssi->device.parent); + ssi_save_port_ctx(omap_port); + + return 0; +} + +static int omap_ssi_port_runtime_resume(struct device *dev) +{ + struct hsi_port *port = dev_get_drvdata(dev); + struct omap_ssi_port *omap_port = hsi_port_drvdata(port); + struct hsi_controller *ssi = to_hsi_controller(port->device.parent); + struct omap_ssi_controller *omap_ssi = hsi_controller_drvdata(ssi); + + dev_dbg(dev, "port runtime resume!\n"); + + if ((omap_ssi->get_loss) && (omap_port->loss_count == + (*omap_ssi->get_loss)(ssi->device.parent))) + goto mode; /* We always need to restore the mode & TX divisor */ + + ssi_restore_port_ctx(omap_port); + +mode: + ssi_restore_divisor(omap_port); + ssi_restore_port_mode(omap_port); + + return 0; +} + +static const struct dev_pm_ops omap_ssi_port_pm_ops = { + SET_RUNTIME_PM_OPS(omap_ssi_port_runtime_suspend, omap_ssi_port_runtime_resume, + NULL) +}; + +#define DEV_PM_OPS (&omap_ssi_port_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + + +#ifdef CONFIG_OF +static const struct of_device_id omap_ssi_port_of_match[] = { + { .compatible = "ti,omap3-ssi-port", }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_ssi_port_of_match); +#else +#define omap_ssi_port_of_match NULL +#endif + +static struct platform_driver ssi_port_pdriver = { + .remove = __exit_p(ssi_port_remove), + .driver = { + .name = "omap_ssi_port", + .owner = THIS_MODULE, + .of_match_table = omap_ssi_port_of_match, + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver_probe(ssi_port_pdriver, ssi_port_probe); + +MODULE_ALIAS("platform:omap_ssi_port"); +MODULE_AUTHOR("Carlos Chinea <carlos.chinea@nokia.com>"); +MODULE_DESCRIPTION("Synchronous Serial Interface Port Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/hsi-omap-ssi.h b/drivers/hsi/controllers/omap_ssi_regs.h index 7c3801af45b7..b1743ba91434 100644 --- a/include/linux/platform_data/hsi-omap-ssi.h +++ b/drivers/hsi/controllers/omap_ssi_regs.h @@ -169,34 +169,4 @@ # define SSI_STOP_LNK (1 << 14) # define SSI_NEXT_CH_ID_MASK 0xf -/** - * struct omap_ssi_platform_data - OMAP SSI platform data - * @num_ports: Number of ports on the controller - * @ctxt_loss_count: Pointer to omap_pm_get_dev_context_loss_count - */ -struct omap_ssi_platform_data { - unsigned int num_ports; - int cawake_gpio[SSI_NUM_PORTS]; - int (*get_dev_context_loss_count)(struct device *dev); -}; - -/** - * struct omap_ssi_config - SSI board configuration - * @num_ports: Number of ports in use - * @cawake_line: Array of cawake gpio lines - */ -struct omap_ssi_board_config { - unsigned int num_ports; - int cawake_gpio[SSI_NUM_PORTS]; -}; - -#ifdef CONFIG_OMAP_SSI_CONFIG -extern int omap_ssi_config(struct omap_ssi_board_config *ssi_config); -#else -static inline int omap_ssi_config(struct omap_ssi_board_config *ssi_config) -{ - return 0; -} -#endif /* CONFIG_OMAP_SSI_CONFIG */ - #endif /* __OMAP_SSI_REGS_H__ */ |