diff options
92 files changed, 9479 insertions, 253 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-vf610 b/Documentation/ABI/testing/sysfs-bus-iio-vf610 new file mode 100644 index 000000000000..ecbc1f4af921 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-vf610 @@ -0,0 +1,7 @@ +What: /sys/bus/iio/devices/iio:deviceX/conversion_mode +KernelVersion: 4.2 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the hardware conversion mode used. The three + available modes are "normal", "high-speed" and "low-power", + where the last is the default mode. diff --git a/Documentation/devicetree/bindings/arm/freescale/fsl,vf610-ddrmc.txt b/Documentation/devicetree/bindings/arm/freescale/fsl,vf610-ddrmc.txt new file mode 100644 index 000000000000..56a71d6bb423 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/freescale/fsl,vf610-ddrmc.txt @@ -0,0 +1,23 @@ +Freescale Vybrid LPDDR2/DDR3 SDRAM Memory Controller + +The memory controller supports high performance applications for 16-bit or +8-bit DDR2, or LPDDR SDRAM memories. + +Required properties: +- compatible: "fsl,vf610-ddrmc" +- reg: the register range of the DDRMC registers +- clocks: DDRMC main clock to clock memory and access registers. +- clock-names: Must contain "ddrc", matching entry in the clocks property. +- fsl,has-cke-reset-pulls: + States whether pull-down/up are populated on DDR CKE/RESET + signals to allow using DDR self-refresh modes (see Vybrid + Hardware Development Guide for details). + +Example: + ddrmc: ddrmc@400ae000 { + compatible = "fsl,vf610-ddrmc"; + reg = <0x400ae000 0x1000>; + clocks = <&clks VF610_CLK_DDRMC>; + clock-names = "ddrc"; + fsl,has-cke-reset-pulls; + } diff --git a/Documentation/devicetree/bindings/fb/fsl-dcu-fb.txt b/Documentation/devicetree/bindings/fb/fsl-dcu-fb.txt new file mode 100644 index 000000000000..d0d50dd724d1 --- /dev/null +++ b/Documentation/devicetree/bindings/fb/fsl-dcu-fb.txt @@ -0,0 +1,67 @@ +* Freescale Display Control Unit (DCU) + +Required properties: +- compatible: Should be "fsl,vf610-dcu". Supported chips include + Vybrid VF610. +- reg: Address and length of the register set for DCU. +- interrupts: Should contain DCU interrupts. +- clocks: From common clock binding: handle to DCU clock. +- clock-names: From common clock binding: Shall be "dcu". +- tcon-controller: The phandle of TCON controller. +- display: The phandle to display node. + +For the DCU initialization, we read data from TCON node. +Required properties for TCON: +- compatible: Should be "fsl,vf610-tcon". Supported chips include + Vybrid VF610. +- reg: Address and length of the register set for TCON. +- clocks: From common clock binding: handle to TCON clock. +- clock-names: From common clock binding: Shall be "tcon". + +* display node + +Required properties: +- bits-per-pixel: <24> for RGB888. + +Required sub-node: +- display-timings: Refer to binding doc display-timing.txt for details. + +Examples: + +dcu0: dcu@40058000 { + compatible = "fsl,vf610-dcu"; + reg = <0x40058000 0x1200>; + interrupts = <0 30 0x04>; + clocks = <&clks VF610_CLK_DCU0>; + clock-names = "dcu"; + tcon-controller = <&tcon0>; + display = <&display>; + + display: display@0 { + bits-per-pixel = <24>; + + display-timings { + native-mode = <&timing0>; + timing0: nl4827hc19 { + clock-frequency = <10870000>; + hactive = <480>; + vactive = <272>; + hback-porch = <2>; + hfront-porch = <2>; + vback-porch = <1>; + vfront-porch = <1>; + hsync-len = <41>; + vsync-len = <2>; + hsync-active = <1>; + vsync-active = <1>; + }; + }; + }; +}; + +tcon0: tcon@4003d000 { + compatible = "fsl,vf610-tcon"; + reg = <0x4003d000 0x1000>; + clocks = <&clks VF610_CLK_TCON0>; + clock-names = "tcon"; +}; diff --git a/Documentation/devicetree/bindings/iio/adc/vf610-adc.txt b/Documentation/devicetree/bindings/iio/adc/vf610-adc.txt index 1a4a43d5c9ea..1aad0514e647 100644 --- a/Documentation/devicetree/bindings/iio/adc/vf610-adc.txt +++ b/Documentation/devicetree/bindings/iio/adc/vf610-adc.txt @@ -11,6 +11,18 @@ Required properties: - clock-names: Must contain "adc", matching entry in the clocks property. - vref-supply: The regulator supply ADC reference voltage. +Recommended properties: +- fsl,adck-max-frequency: Maximum frequencies according to datasheets operating + requirements. Three values are required, depending on conversion mode: + - Frequency in normal mode (ADLPC=0, ADHSC=0) + - Frequency in high-speed mode (ADLPC=0, ADHSC=1) + - Frequency in low-power mode (ADLPC=1, ADHSC=0) +- min-sample-time: Minimum sampling time in nanoseconds. This value has + to be chosen according to the conversion mode and the connected analog + source resistance (R_as) and capacitance (C_as). Refer the datasheet's + operating requirements. A safe default across a wide range of R_as and + C_as as well as conversion modes is 1000ns. + Example: adc0: adc@4003b000 { compatible = "fsl,vf610-adc"; @@ -18,5 +30,7 @@ adc0: adc@4003b000 { interrupts = <0 53 0x04>; clocks = <&clks VF610_CLK_ADC0>; clock-names = "adc"; + fsl,adck-max-frequency = <30000000>, <40000000>, + <20000000>; vref-supply = <®_vcc_3v3_mcu>; }; diff --git a/Documentation/devicetree/bindings/input/touchscreen/colibri-vf50-ts.txt b/Documentation/devicetree/bindings/input/touchscreen/colibri-vf50-ts.txt new file mode 100644 index 000000000000..caad52a2fd8d --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/colibri-vf50-ts.txt @@ -0,0 +1,34 @@ +* Toradex Colibri VF50 Touchscreen driver + +Required Properties: +- compatible must be toradex,vf50-touchctrl +- io-channels: adc channels being used by the Colibri VF50 module +- xp-gpios: FET gate driver for input of X+ +- xm-gpios: FET gate driver for input of X- +- yp-gpios: FET gate driver for input of Y+ +- ym-gpios: FET gate driver for input of Y- +- pen-detect-gpios: GPIO for pen detect irq +- pen-pullup-gpios: GPIO for pen pullup +- pinctrl-names: "idle", "default", "gpios" +- pinctrl-0: pinctrl node for idle state gpio pinmux +- pinctrl-1: pinctrl node for touch detection state pinmux +- pinctrl-2: pinctrl node for gpios functioning as FET gate drivers + +Example: + + touchctrl: vf50_touchctrl { + compatible = "toradex,vf50-touchctrl"; + io-channels = <&adc1 0>,<&adc0 0>, + <&adc0 1>,<&adc1 2>; + xp-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; + xm-gpios = <&gpio2 29 GPIO_ACTIVE_HIGH>; + yp-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; + ym-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; + pen-detect-gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>; + pen-pullup-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>; + pinctrl-names = "idle","default","gpios"; + pinctrl-0 = <&pinctrl_touchctrl_idle>; + pinctrl-1 = <&pinctrl_touchctrl_default>; + pinctrl-2 = <&pinctrl_touchctrl_gpios>; + status = "disabled"; + }; diff --git a/Documentation/devicetree/bindings/mtd/vf610-nfc.txt b/Documentation/devicetree/bindings/mtd/vf610-nfc.txt new file mode 100644 index 000000000000..cae5f259abd8 --- /dev/null +++ b/Documentation/devicetree/bindings/mtd/vf610-nfc.txt @@ -0,0 +1,45 @@ +Freescale's NAND flash controller (NFC) + +This variant of the Freescale NAND flash controller (NFC) can be found on +Vybrid (vf610), MPC5125, MCF54418 and Kinetis K70. + +Required properties: +- compatible: Should be set to "fsl,vf610-nfc" +- reg: address range of the NFC +- interrupts: interrupt of the NFC +- nand-bus-width: see nand.txt +- nand-ecc-mode: see nand.txt +- nand-on-flash-bbt: see nand.txt +- assigned-clocks: main clock from the SoC, for Vybrid <&clks VF610_CLK_NFC>; +- assigned-clock-rates: The NAND bus timing is derived from this clock + rate and should not exceed maximum timing for any NAND memory chip + in a board stuffing. Typical NAND memory timings derived from this + clock are found in the SoC hardware reference manual. Furthermore, + there might be restrictions on maximum rates when using hardware ECC. + +- #address-cells, #size-cells : Must be present if the device has sub-nodes + representing partitions. + +Required properties for hardware ECC: +- nand-ecc-strength: supported strengths are 24 and 32 bit (see nand.txt) +- nand-ecc-step-size: step size equals page size, currently only 2k pages are + supported + +Example: + + nfc: nand@400e0000 { + compatible = "fsl,vf610-nfc"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x400e0000 0x4000>; + interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_NFC>; + clock-names = "nfc"; + assigned-clocks = <&clks VF610_CLK_NFC>; + assigned-clock-rates = <33000000>; + nand-bus-width = <8>; + nand-ecc-mode = "hw"; + nand-ecc-strength = <32>; + nand-ecc-step-size = <2048>; + nand-on-flash-bbt; + }; diff --git a/MAINTAINERS b/MAINTAINERS index d8afd2953678..27978d8e2c15 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10530,6 +10530,12 @@ S: Maintained F: Documentation/fb/uvesafb.txt F: drivers/video/fbdev/uvesafb.* +VF610 NAND DRIVER +M: Stefan Agner <stefan@agner.ch> +L: linux-mtd@lists.infradead.org +S: Supported +F: drivers/mtd/nand/vf610_nfc.c + VFAT/FAT/MSDOS FILESYSTEM M: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> S: Maintained diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 992736b5229b..ff9f6564d057 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -319,6 +319,8 @@ dtb-$(CONFIG_SOC_LS1021A) += \ dtb-$(CONFIG_SOC_VF610) += \ vf500-colibri-eval-v3.dtb \ vf610-colibri-eval-v3.dtb \ + vf500-colibri-dual-eth.dtb \ + vf610-colibri-dual-eth.dtb \ vf610-cosmic.dtb \ vf610-twr.dtb dtb-$(CONFIG_ARCH_MXS) += \ diff --git a/arch/arm/boot/dts/vf-colibri-dual-eth.dtsi b/arch/arm/boot/dts/vf-colibri-dual-eth.dtsi new file mode 100644 index 000000000000..8fed7304276a --- /dev/null +++ b/arch/arm/boot/dts/vf-colibri-dual-eth.dtsi @@ -0,0 +1,91 @@ +/* + * Copyright 2014 Toradex AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/ { + chosen { + bootargs = "console=ttyLP0,115200"; + stdout-path = "serial0:115200n8"; + }; + + aliases { + ethernet0 = &fec0; + ethernet1 = &fec1; + }; +}; + +&fec0 { + phy-mode = "rmii"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_fec0>; + status = "okay"; +}; + +&fec1 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&uart2 { + status = "okay"; +}; + +&uart3 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_uart3>; + status = "okay"; +}; + +&uart4 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_uart4>; + status = "okay"; +}; + +&iomuxc { + vf610-colibri { + pinctrl_fec0: fec0grp { + fsl,pins = < + VF610_PAD_PTA9__RMII_CLKOUT 0x30d2 + VF610_PAD_PTC0__ENET_RMII0_MDC 0x30d2 + VF610_PAD_PTC1__ENET_RMII0_MDIO 0x30d3 + VF610_PAD_PTC2__ENET_RMII0_CRS 0x30d1 + VF610_PAD_PTC3__ENET_RMII0_RXD1 0x30d1 + VF610_PAD_PTC4__ENET_RMII0_RXD0 0x30d1 + VF610_PAD_PTC5__ENET_RMII0_RXER 0x30d1 + VF610_PAD_PTC6__ENET_RMII0_TXD1 0x30d2 + VF610_PAD_PTC7__ENET_RMII0_TXD0 0x30d2 + VF610_PAD_PTC8__ENET_RMII0_TXEN 0x30d2 + /* Disable pads multiplexed with PTC7/PTC6 */ + VF610_PAD_PTB0__GPIO_22 0x0000 + VF610_PAD_PTB9__GPIO_31 0x0000 + >; + }; + + pinctrl_uart3: uart3grp { + fsl,pins = < + VF610_PAD_PTA20__UART3_TX 0x21a2 + VF610_PAD_PTA21__UART3_RX 0x21a1 + >; + }; + + pinctrl_uart4: uart4grp { + fsl,pins = < + VF610_PAD_PTA28__UART4_TX 0x21a2 + VF610_PAD_PTA29__UART4_RX 0x21a1 + >; + }; + }; +}; diff --git a/arch/arm/boot/dts/vf-colibri-eval-v3.dtsi b/arch/arm/boot/dts/vf-colibri-eval-v3.dtsi index 606753eb72c8..a1adb069cf44 100644 --- a/arch/arm/boot/dts/vf-colibri-eval-v3.dtsi +++ b/arch/arm/boot/dts/vf-colibri-eval-v3.dtsi @@ -1,5 +1,5 @@ /* - * Copyright 2014 Toradex AG + * Copyright 2014-2015 Toradex AG * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -7,9 +7,12 @@ * (at your option) any later version. */ +#include <dt-bindings/input/input.h> + / { chosen { bootargs = "console=ttyLP0,115200"; + stdout-path = "serial0:115200n8"; }; clk16m: clk16m { @@ -18,11 +21,23 @@ clock-frequency = <16000000>; }; - regulators { - compatible = "simple-bus"; - #address-cells = <1>; - #size-cells = <0>; + bl_on { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_gpio_bl_on>; + compatible = "gpio-backlight"; + gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>; + default-on; + }; + + extcon_usbc_det: usbc_det { + compatible = "linux,extcon-usb-gpio"; + debounce = <25>; + id-gpio = <&gpio3 6 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usbc_det>; + }; + regulators { sys_5v0_reg: regulator@0 { compatible = "regulator-fixed"; reg = <0>; @@ -45,6 +60,20 @@ vin-supply = <&sys_5v0_reg>; }; }; + + gpio-keys { + compatible = "gpio-keys"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_gpiokeys>; + + power { + label = "Wake-Up"; + gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; + linux,code = <KEY_WAKEUP>; + debounce-interval = <10>; + gpio-key,wakeup; + }; + }; }; &bl { @@ -53,6 +82,111 @@ status = "okay"; }; +&dcu0 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_dcu0_1>; + display = <&display>; + status = "okay"; + + display: display@0 { + bits-per-pixel = <16>; + + display-timings { + native-mode = <&timing_vga>; + /* Standard VGA timing */ + timing_vga: 640x480 { + clock-frequency = <25175000>; + hactive = <640>; + vactive = <480>; + hback-porch = <40>; + hfront-porch = <24>; + vback-porch = <32>; + vfront-porch = <11>; + hsync-len = <96>; + vsync-len = <2>; + hsync-active = <0>; + vsync-active = <0>; + pixelclk-active = <0>; + }; + /* WVGA Timing, e.g. EDT ET070080DH6 */ + timing_wvga: 800x480 { + clock-frequency = <33260000>; + hactive = <800>; + vactive = <480>; + hback-porch = <216>; + hfront-porch = <40>; + vback-porch = <35>; + vfront-porch = <10>; + hsync-len = <128>; + vsync-len = <2>; + hsync-active = <0>; + vsync-active = <0>; + pixelclk-active = <0>; + }; + /* WVGA Timing, TouchRevolution Fusion 7" */ + timing_wvga2: 800x480pixclkact { + clock-frequency = <33260000>; + hactive = <800>; + vactive = <480>; + hback-porch = <216>; + hfront-porch = <40>; + vback-porch = <35>; + vfront-porch = <10>; + hsync-len = <128>; + vsync-len = <2>; + hsync-active = <0>; + vsync-active = <0>; + pixelclk-active = <1>; + }; + /* Standard SVGA timing */ + timing_svga: 800x600 { + clock-frequency = <40000000>; + hactive = <800>; + vactive = <600>; + hback-porch = <88>; + hfront-porch = <40>; + vback-porch = <23>; + vfront-porch = <1>; + hsync-len = <128>; + vsync-len = <4>; + hsync-active = <1>; + vsync-active = <1>; + pixelclk-active = <0>; + }; + /* TouchRevolution Fusion 10"/CLAA101NC05 10.1 inch */ + timing_wsvga: 1024x600 { + clock-frequency = <48000000>; + hactive = <1024>; + vactive = <600>; + hback-porch = <104>; + hfront-porch = <43>; + vback-porch = <24>; + vfront-porch = <20>; + hsync-len = <5>; + vsync-len = <5>; + hsync-active = <0>; + vsync-active = <0>; + pixelclk-active = <0>; + }; + /* Standard XGA timing */ + timing_xga: 1024x768 { + clock-frequency = <65000000>; + hactive = <1024>; + vactive = <768>; + hback-porch = <160>; + hfront-porch = <24>; + vback-porch = <29>; + vfront-porch = <3>; + hsync-len = <136>; + vsync-len = <6>; + hsync-active = <0>; + vsync-active = <0>; + pixelclk-active = <0>; + }; + }; + }; +}; + &dspi1 { status = "okay"; @@ -85,6 +219,20 @@ &i2c0 { status = "okay"; + /* TouchRevolution Fusion 7 and 10 multi-touch controller */ + touch: touchrevf0710a@10 { + compatible = "touchrevolution,fusion-f0710a"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_gpiotouch>; + reg = <0x10>; + /* SODIMM 28, Pen down interrupt */ + gpios = <&gpio0 30 GPIO_ACTIVE_HIGH + /* SODIMM 30, Reset interrupt */ + &gpio0 23 GPIO_ACTIVE_LOW + >; + status = "disabled"; + }; + /* M41T0M6 real time clock on carrier board */ rtc: m41t0m6@68 { compatible = "st,m41t00"; @@ -100,6 +248,10 @@ status = "okay"; }; +&tcon0 { + status = "okay"; +}; + &uart0 { status = "okay"; }; @@ -112,6 +264,10 @@ status = "okay"; }; +&usbdev0 { + extcon = <&extcon_usbc_det>; +}; + &usbh1 { vbus-supply = <&usbh_vbus_reg>; }; @@ -123,5 +279,18 @@ VF610_PAD_PTB21__GPIO_43 0x22ed >; }; + + pinctrl_gpiokeys: gpiokeys { + fsl,pins = < + VF610_PAD_PTB19__GPIO_41 0x218d + >; + }; + + pinctrl_gpiotouch: touchgpios { + fsl,pins = < + VF610_PAD_PTB8__GPIO_30 0x6f + VF610_PAD_PTB1__GPIO_23 0x4f + >; + }; }; }; diff --git a/arch/arm/boot/dts/vf-colibri.dtsi b/arch/arm/boot/dts/vf-colibri.dtsi index fbef0828e930..783ea2a062b5 100644 --- a/arch/arm/boot/dts/vf-colibri.dtsi +++ b/arch/arm/boot/dts/vf-colibri.dtsi @@ -13,14 +13,51 @@ pwms = <&pwm0 0 5000000 0>; status = "disabled"; }; + + regulators { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + sys_3v3_reg: regulator@100 { + compatible = "regulator-fixed"; + reg = <100>; + regulator-name = "3V3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + sys_3v3_avdd_reg: regulator@101 { + compatible = "regulator-fixed"; + reg = <101>; + regulator-name = "AVDD"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + }; }; &adc0 { status = "okay"; + vref-supply = <&sys_3v3_avdd_reg>; }; &adc1 { status = "okay"; + vref-supply = <&sys_3v3_avdd_reg>; +}; + +&can0 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_flexcan0>; + status = "disabled"; +}; + +&can1 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_flexcan1>; + status = "disabled"; }; &dspi1 { @@ -42,6 +79,7 @@ &fec1 { phy-mode = "rmii"; + phy-supply = <&sys_3v3_reg>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_fec1>; }; @@ -52,14 +90,34 @@ pinctrl-0 = <&pinctrl_i2c0>; }; +&nfc { + assigned-clocks = <&clks VF610_CLK_NFC>; + assigned-clock-rates = <33000000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_nfc>; + status = "okay"; + + nand@0 { + compatible = "fsl,vf610-nfc-nandcs"; + reg = <0>; + #address-cells = <1>; + #size-cells = <1>; + nand-bus-width = <8>; + nand-ecc-mode = "hw"; + nand-ecc-strength = <32>; + nand-ecc-step-size = <2048>; + nand-on-flash-bbt; + }; +}; + &pwm0 { pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_pwm0>; + pinctrl-0 = <&pinctrl_pwm0_a &pinctrl_pwm0_c>; }; &pwm1 { pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_pwm1>; + pinctrl-0 = <&pinctrl_pwm1_b &pinctrl_pwm1_d>; }; &uart0 { @@ -79,6 +137,7 @@ &usbdev0 { disable-over-current; + dr_mode = "otg"; status = "okay"; }; @@ -105,11 +164,96 @@ &iomuxc { vf610-colibri { - pinctrl_gpio_ext: gpio_ext { + pinctrl_additionalgpio: additionalgpios { + fsl,pins = < + VF610_PAD_PTA12__GPIO_5 0x22ed + VF610_PAD_PTA17__GPIO_7 0x22ed + VF610_PAD_PTA20__GPIO_10 0x22ed + VF610_PAD_PTA21__GPIO_11 0x22ed + VF610_PAD_PTA30__GPIO_20 0x22ed + VF610_PAD_PTA31__GPIO_21 0x22ed + VF610_PAD_PTB6__GPIO_28 0x22ed + VF610_PAD_PTB7__GPIO_29 0x22ed + VF610_PAD_PTB12__GPIO_34 0x22ed + VF610_PAD_PTB13__GPIO_35 0x22ed + VF610_PAD_PTB16__GPIO_38 0x22ed + VF610_PAD_PTB17__GPIO_39 0x22ed + VF610_PAD_PTB18__GPIO_40 0x22ed + VF610_PAD_PTB21__GPIO_43 0x22ed + VF610_PAD_PTB22__GPIO_44 0x22ed + VF610_PAD_PTC1__GPIO_46 0x22ed + VF610_PAD_PTC2__GPIO_47 0x22ed + VF610_PAD_PTC3__GPIO_48 0x22ed + VF610_PAD_PTC4__GPIO_49 0x22ed + VF610_PAD_PTC5__GPIO_50 0x22ed + VF610_PAD_PTC6__GPIO_51 0x22ed + VF610_PAD_PTC7__GPIO_52 0x22ed + VF610_PAD_PTC8__GPIO_53 0x22ed + VF610_PAD_PTD31__GPIO_63 0x22ed + VF610_PAD_PTD30__GPIO_64 0x22ed + VF610_PAD_PTD29__GPIO_65 0x22ed + VF610_PAD_PTD28__GPIO_66 0x22ed + VF610_PAD_PTD27__GPIO_67 0x22ed + VF610_PAD_PTD26__GPIO_68 0x22ed + VF610_PAD_PTD25__GPIO_69 0x22ed + VF610_PAD_PTD24__GPIO_70 0x22ed + VF610_PAD_PTD9__GPIO_88 0x22ed + VF610_PAD_PTD10__GPIO_89 0x22ed + VF610_PAD_PTD11__GPIO_90 0x22ed + VF610_PAD_PTD12__GPIO_91 0x22ed + VF610_PAD_PTD13__GPIO_92 0x22ed + VF610_PAD_PTB23__GPIO_93 0x22ed + VF610_PAD_PTB26__GPIO_96 0x22ed + VF610_PAD_PTB28__GPIO_98 0x22ed + VF610_PAD_PTC30__GPIO_103 0x22ed + VF610_PAD_PTA7__GPIO_134 0x22ed + >; + }; + + pinctrl_flexcan0: can0grp { + fsl,pins = < + VF610_PAD_PTB14__CAN0_RX 0x31F1 + VF610_PAD_PTB15__CAN0_TX 0x31F2 + >; + }; + + pinctrl_flexcan1: can1grp { fsl,pins = < - VF610_PAD_PTD10__GPIO_89 0x22ed /* EXT_IO_0 */ - VF610_PAD_PTD9__GPIO_88 0x22ed /* EXT_IO_1 */ - VF610_PAD_PTD26__GPIO_68 0x22ed /* EXT_IO_2 */ + VF610_PAD_PTB16__CAN1_RX 0x31F1 + VF610_PAD_PTB17__CAN1_TX 0x31F2 + >; + }; + + pinctrl_dcu0_1: dcu0grp_1 { + fsl,pins = < + VF610_PAD_PTE0__DCU0_HSYNC 0x1902 + VF610_PAD_PTE1__DCU0_VSYNC 0x1902 + VF610_PAD_PTE2__DCU0_PCLK 0x1902 + VF610_PAD_PTE4__DCU0_DE 0x1902 + VF610_PAD_PTE5__DCU0_R0 0x1902 + VF610_PAD_PTE6__DCU0_R1 0x1902 + VF610_PAD_PTE7__DCU0_R2 0x1902 + VF610_PAD_PTE8__DCU0_R3 0x1902 + VF610_PAD_PTE9__DCU0_R4 0x1902 + VF610_PAD_PTE10__DCU0_R5 0x1902 + VF610_PAD_PTE11__DCU0_R6 0x1902 + VF610_PAD_PTE12__DCU0_R7 0x1902 + VF610_PAD_PTE13__DCU0_G0 0x1902 + VF610_PAD_PTE14__DCU0_G1 0x1902 + VF610_PAD_PTE15__DCU0_G2 0x1902 + VF610_PAD_PTE16__DCU0_G3 0x1902 + VF610_PAD_PTE17__DCU0_G4 0x1902 + VF610_PAD_PTE18__DCU0_G5 0x1902 + VF610_PAD_PTE19__DCU0_G6 0x1902 + VF610_PAD_PTE20__DCU0_G7 0x1902 + VF610_PAD_PTE21__DCU0_B0 0x1902 + VF610_PAD_PTE22__DCU0_B1 0x1902 + VF610_PAD_PTE23__DCU0_B2 0x1902 + VF610_PAD_PTE24__DCU0_B3 0x1902 + VF610_PAD_PTE25__DCU0_B4 0x1902 + VF610_PAD_PTE26__DCU0_B5 0x1902 + VF610_PAD_PTE27__DCU0_B6 0x1902 + VF610_PAD_PTE28__DCU0_B7 0x1902 >; }; @@ -140,7 +284,7 @@ VF610_PAD_PTC9__ENET_RMII1_MDC 0x30d2 VF610_PAD_PTC10__ENET_RMII1_MDIO 0x30d3 VF610_PAD_PTC11__ENET_RMII1_CRS 0x30d1 - VF610_PAD_PTC12__ENET_RMII_RXD1 0x30d1 + VF610_PAD_PTC12__ENET_RMII1_RXD1 0x30d1 VF610_PAD_PTC13__ENET_RMII1_RXD0 0x30d1 VF610_PAD_PTC14__ENET_RMII1_RXER 0x30d1 VF610_PAD_PTC15__ENET_RMII1_TXD1 0x30d2 @@ -149,6 +293,12 @@ >; }; + pinctrl_gpio_bl_on: gpio_bl_on { + fsl,pins = < + VF610_PAD_PTC0__GPIO_45 0x22ef + >; + }; + pinctrl_i2c0: i2c0grp { fsl,pins = < VF610_PAD_PTB14__I2C0_SCL 0x37ff @@ -156,16 +306,45 @@ >; }; - pinctrl_pwm0: pwm0grp { + pinctrl_nfc: nfcgrp { + fsl,pins = < + VF610_PAD_PTD23__NF_IO7 0x28df + VF610_PAD_PTD22__NF_IO6 0x28df + VF610_PAD_PTD21__NF_IO5 0x28df + VF610_PAD_PTD20__NF_IO4 0x28df + VF610_PAD_PTD19__NF_IO3 0x28df + VF610_PAD_PTD18__NF_IO2 0x28df + VF610_PAD_PTD17__NF_IO1 0x28df + VF610_PAD_PTD16__NF_IO0 0x28df + VF610_PAD_PTB24__NF_WE_B 0x28c2 + VF610_PAD_PTB25__NF_CE0_B 0x28c2 + VF610_PAD_PTB27__NF_RE_B 0x28c2 + VF610_PAD_PTC26__NF_RB_B 0x283d + VF610_PAD_PTC27__NF_ALE 0x28c2 + VF610_PAD_PTC28__NF_CLE 0x28c2 + >; + }; + + pinctrl_pwm0_a: pwm0agrp { fsl,pins = < VF610_PAD_PTB0__FTM0_CH0 0x1182 + >; + }; + + pinctrl_pwm0_c: pwm0cgrp { + fsl,pins = < VF610_PAD_PTB1__FTM0_CH1 0x1182 >; }; - pinctrl_pwm1: pwm1grp { + pinctrl_pwm1_b: pwm1bgrp { fsl,pins = < VF610_PAD_PTB8__FTM1_CH0 0x1182 + >; + }; + + pinctrl_pwm1_d: pwm1dgrp { + fsl,pins = < VF610_PAD_PTB9__FTM1_CH1 0x1182 >; }; @@ -174,6 +353,8 @@ fsl,pins = < VF610_PAD_PTB10__UART0_TX 0x21a2 VF610_PAD_PTB11__UART0_RX 0x21a1 + VF610_PAD_PTB12__UART0_RTS 0x21a2 + VF610_PAD_PTB13__UART0_CTS 0x21a1 >; }; @@ -193,6 +374,12 @@ >; }; + pinctrl_usbc_det: gpio_usbc_det { + fsl,pins = < + VF610_PAD_PTC29__GPIO_102 0x22ed + >; + }; + pinctrl_usbh1_reg: gpio_usb_vbus { fsl,pins = < VF610_PAD_PTD4__GPIO_83 0x22ed diff --git a/arch/arm/boot/dts/vf500-colibri-dual-eth.dts b/arch/arm/boot/dts/vf500-colibri-dual-eth.dts new file mode 100644 index 000000000000..24990e241e0e --- /dev/null +++ b/arch/arm/boot/dts/vf500-colibri-dual-eth.dts @@ -0,0 +1,17 @@ +/* + * Copyright 2014 Toradex AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/dts-v1/; +#include "vf500-colibri.dtsi" +#include "vf-colibri-dual-eth.dtsi" + +/ { + model = "Toradex Colibri VF50 on Dual Ethernet Board"; + compatible = "toradex,vf500-colibri_vf50-on-dual-eth-board", "toradex,vf500-colibri_vf50", "fsl,vf500"; +}; diff --git a/arch/arm/boot/dts/vf500-colibri-eval-v3.dts b/arch/arm/boot/dts/vf500-colibri-eval-v3.dts index 7fc782c4fc52..c5efb576570c 100644 --- a/arch/arm/boot/dts/vf500-colibri-eval-v3.dts +++ b/arch/arm/boot/dts/vf500-colibri-eval-v3.dts @@ -15,3 +15,7 @@ model = "Toradex Colibri VF50 on Colibri Evaluation Board"; compatible = "toradex,vf500-colibri_vf50-on-eval", "toradex,vf500-colibri_vf50", "fsl,vf500"; }; + +&touchctrl { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/vf500-colibri.dtsi b/arch/arm/boot/dts/vf500-colibri.dtsi index cee34a32f25b..fe733e52bccd 100644 --- a/arch/arm/boot/dts/vf500-colibri.dtsi +++ b/arch/arm/boot/dts/vf500-colibri.dtsi @@ -17,4 +17,49 @@ memory { reg = <0x80000000 0x8000000>; }; + + touchctrl: vf50_touchctrl { + compatible = "toradex,vf50-touchctrl"; + io-channels = <&adc1 0>,<&adc0 0>, + <&adc0 1>,<&adc1 2>; + xp-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; + xm-gpios = <&gpio2 29 GPIO_ACTIVE_HIGH>; + yp-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; + ym-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; + pen-detect-gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>; + pinctrl-names = "idle","default","gpios"; + pinctrl-0 = <&pinctrl_touchctrl_idle>; + pinctrl-1 = <&pinctrl_touchctrl_default>; + pinctrl-2 = <&pinctrl_touchctrl_gpios>; + status = "disabled"; + }; +}; + +&iomuxc { + vf610-colibri { + pinctrl_touchctrl_idle: touchctrl_idle { + fsl,pins = < + VF610_PAD_PTA18__GPIO_8 0x006d + VF610_PAD_PTA19__GPIO_9 0x006c + >; + }; + + pinctrl_touchctrl_default: touchctrl_default { + fsl,pins = < + VF610_PAD_PTA18__ADC0_SE0 0x0040 + VF610_PAD_PTA19__ADC0_SE1 0x0040 + VF610_PAD_PTA16__ADC1_SE0 0x0040 + VF610_PAD_PTB2__ADC1_SE2 0x0040 + >; + }; + + pinctrl_touchctrl_gpios: touchctrl_gpios { + fsl,pins = < + VF610_PAD_PTA23__GPIO_13 0x22e9 + VF610_PAD_PTB23__GPIO_93 0x22e9 + VF610_PAD_PTA22__GPIO_12 0x22e9 + VF610_PAD_PTA11__GPIO_4 0x22e9 + >; + }; + }; }; diff --git a/arch/arm/boot/dts/vf500.dtsi b/arch/arm/boot/dts/vf500.dtsi index e976d2fa1527..f2be079aae51 100644 --- a/arch/arm/boot/dts/vf500.dtsi +++ b/arch/arm/boot/dts/vf500.dtsi @@ -43,6 +43,16 @@ clocks = <&clks VF610_CLK_PLATFORM_BUS>; }; }; + + aips-bus@40080000 { + + pmu@40089000 { + compatible = "arm,cortex-a5-pmu"; + interrupts = <7 IRQ_TYPE_LEVEL_HIGH>; + interrupt-affinity = <&a5_cpu>; + }; + }; + }; }; diff --git a/arch/arm/boot/dts/vf610-colibri-dual-eth.dts b/arch/arm/boot/dts/vf610-colibri-dual-eth.dts new file mode 100644 index 000000000000..a2eff553956c --- /dev/null +++ b/arch/arm/boot/dts/vf610-colibri-dual-eth.dts @@ -0,0 +1,17 @@ +/* + * Copyright 2014 Toradex AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/dts-v1/; +#include "vf610-colibri.dtsi" +#include "vf-colibri-dual-eth.dtsi" + +/ { + model = "Toradex Colibri VF61 on Dual Ethernet Board"; + compatible = "toradex,vf610-colibri_vf61-on-dual-eth-board", "toradex,vf610-colibri_vf61", "fsl,vf610"; +}; diff --git a/arch/arm/boot/dts/vf610-colibri.dtsi b/arch/arm/boot/dts/vf610-colibri.dtsi index 19fe045b8334..4c94254e4a60 100644 --- a/arch/arm/boot/dts/vf610-colibri.dtsi +++ b/arch/arm/boot/dts/vf610-colibri.dtsi @@ -17,9 +17,89 @@ memory { reg = <0x80000000 0x10000000>; }; + + sound { + compatible = "fsl,fsl-sai-audio-wm9712"; + fsl,ac97-controller = <&sai2>; + + fsl,model = "Colibri VF61 AC97 Audio"; + + fsl,audio-routing = + "Headphone", "HPOUTL", + "Headphone", "HPOUTR", + "LINEINL", "LineIn", + "LINEINR", "LineIn", + "MIC1", "Mic"; + }; +}; + +&sai0 { + compatible = "fsl,vf610-sai-clk"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_sai0>; + status = "okay"; +}; + +&sai2 { + compatible = "fsl,vf610-sai-ac97"; + #sound-dai-cells = <0>; + + pinctrl-names = "default", "ac97-running", "ac97-reset", + "ac97-warm-reset"; + pinctrl-0 = <&pinctrl_sai2_ac97_running>; + pinctrl-1 = <&pinctrl_sai2_ac97_running>; + pinctrl-2 = <&pinctrl_sai2_ac97_reset>; + pinctrl-3 = <&pinctrl_sai2_ac97_reset>; + ac97-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH &gpio0 8 GPIO_ACTIVE_HIGH + &gpio0 13 GPIO_ACTIVE_HIGH>; + status = "okay"; }; &L2 { - arm,data-latency = <2 1 2>; - arm,tag-latency = <3 2 3>; + arm,data-latency = <3 3 3>; + arm,tag-latency = <2 2 2>; +}; + +&iomuxc { + vf610-colibri { + pinctrl_sai0: sai0grp_1 { + fsl,pins = < + VF610_PAD_PTB23__SAI0_TX_BCLK 0x31C3 + >; + }; + pinctrl_sai2_ac97_reset: sai2grp_1 { + fsl,pins = < + /* Pen-down */ + VF610_PAD_PTA11__GPIO_4 0x22ed + /* AC97 SData Out (test mode selection) */ + VF610_PAD_PTA18__GPIO_8 0x22ed + /* AC97 Sync (warm reset) */ + VF610_PAD_PTA19__GPIO_9 0x22ed + /* AC97 Reset (cold reset) */ + VF610_PAD_PTA23__GPIO_13 0x22eb + >; + }; + + pinctrl_sai2_ac97_running: sai2grp_2 { + fsl,pins = < + /* AC97 Bit clock */ + VF610_PAD_PTA16__SAI2_TX_BCLK 0x31C3 + + /* AC97 SData Out */ + VF610_PAD_PTA18__SAI2_TX_DATA 0x31C2 + + /* AC97 Sync */ + VF610_PAD_PTA19__SAI2_TX_SYNC 0x31C3 + + /* AC97 SData In */ + VF610_PAD_PTA22__SAI2_RX_DATA 0x0041 + + /* AC97 Reset (cold reset, keep output buffer on) */ + VF610_PAD_PTA23__GPIO_13 0x22eb + + /* GenIRQ */ + VF610_PAD_PTB2__GPIO_24 0x22ed + >; + }; + }; }; diff --git a/arch/arm/boot/dts/vf610-cosmic.dts b/arch/arm/boot/dts/vf610-cosmic.dts index fd8758b639f5..5447f2594659 100644 --- a/arch/arm/boot/dts/vf610-cosmic.dts +++ b/arch/arm/boot/dts/vf610-cosmic.dts @@ -68,7 +68,7 @@ VF610_PAD_PTC9__ENET_RMII1_MDC 0x30d2 VF610_PAD_PTC10__ENET_RMII1_MDIO 0x30d3 VF610_PAD_PTC11__ENET_RMII1_CRS 0x30d1 - VF610_PAD_PTC12__ENET_RMII_RXD1 0x30d1 + VF610_PAD_PTC12__ENET_RMII1_RXD1 0x30d1 VF610_PAD_PTC13__ENET_RMII1_RXD0 0x30d1 VF610_PAD_PTC14__ENET_RMII1_RXER 0x30d1 VF610_PAD_PTC15__ENET_RMII1_TXD1 0x30d2 diff --git a/arch/arm/boot/dts/vf610-pinfunc.h b/arch/arm/boot/dts/vf610-pinfunc.h index 1ee681f7ce2f..fcad7132c871 100644 --- a/arch/arm/boot/dts/vf610-pinfunc.h +++ b/arch/arm/boot/dts/vf610-pinfunc.h @@ -369,7 +369,7 @@ #define VF610_PAD_PTC11__MLB_DATA 0x0E0 0x358 ALT6 0x1 #define VF610_PAD_PTC11__DEBUG_OUT 0x0E0 0x000 ALT7 0x0 #define VF610_PAD_PTC12__GPIO_57 0x0E4 0x000 ALT0 0x0 -#define VF610_PAD_PTC12__ENET_RMII_RXD1 0x0E4 0x000 ALT1 0x0 +#define VF610_PAD_PTC12__ENET_RMII1_RXD1 0x0E4 0x000 ALT1 0x0 #define VF610_PAD_PTC12__ESAI_SDO1 0x0E4 0x318 ALT3 0x1 #define VF610_PAD_PTC12__SAI2_TX_BCLK 0x0E4 0x370 ALT5 0x1 #define VF610_PAD_PTC12__DEBUG_OUT3 0x0E4 0x000 ALT7 0x0 diff --git a/arch/arm/boot/dts/vf610-twr.dts b/arch/arm/boot/dts/vf610-twr.dts index f64fddce3e2a..c139dc1f2b6e 100644 --- a/arch/arm/boot/dts/vf610-twr.dts +++ b/arch/arm/boot/dts/vf610-twr.dts @@ -127,6 +127,34 @@ status = "okay"; }; +&dcu0 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_dcu0_1>; + display = <&display>; + status = "okay"; + + display: display@0 { + bits-per-pixel = <24>; + + display-timings { + native-mode = <&timing0>; + timing0: nl4827hc19 { + clock-frequency = <10870000>; + hactive = <480>; + vactive = <272>; + hback-porch = <2>; + hfront-porch = <2>; + vback-porch = <1>; + vfront-porch = <1>; + hsync-len = <41>; + vsync-len = <2>; + hsync-active = <1>; + vsync-active = <1>; + }; + }; + }; +}; + &fec0 { phy-mode = "rmii"; phy-handle = <ðphy0>; @@ -180,6 +208,39 @@ >; }; + pinctrl_dcu0_1: dcu0grp_1 { + fsl,pins = < + VF610_PAD_PTE0__DCU0_HSYNC 0x42 + VF610_PAD_PTE1__DCU0_VSYNC 0x42 + VF610_PAD_PTE2__DCU0_PCLK 0x42 + VF610_PAD_PTE4__DCU0_DE 0x42 + VF610_PAD_PTE5__DCU0_R0 0x42 + VF610_PAD_PTE6__DCU0_R1 0x42 + VF610_PAD_PTE7__DCU0_R2 0x42 + VF610_PAD_PTE8__DCU0_R3 0x42 + VF610_PAD_PTE9__DCU0_R4 0x42 + VF610_PAD_PTE10__DCU0_R5 0x42 + VF610_PAD_PTE11__DCU0_R6 0x42 + VF610_PAD_PTE12__DCU0_R7 0x42 + VF610_PAD_PTE13__DCU0_G0 0x42 + VF610_PAD_PTE14__DCU0_G1 0x42 + VF610_PAD_PTE15__DCU0_G2 0x42 + VF610_PAD_PTE16__DCU0_G3 0x42 + VF610_PAD_PTE17__DCU0_G4 0x42 + VF610_PAD_PTE18__DCU0_G5 0x42 + VF610_PAD_PTE19__DCU0_G6 0x42 + VF610_PAD_PTE20__DCU0_G7 0x42 + VF610_PAD_PTE21__DCU0_B0 0x42 + VF610_PAD_PTE22__DCU0_B1 0x42 + VF610_PAD_PTE23__DCU0_B2 0x42 + VF610_PAD_PTE24__DCU0_B3 0x42 + VF610_PAD_PTE25__DCU0_B4 0x42 + VF610_PAD_PTE26__DCU0_B5 0x42 + VF610_PAD_PTE27__DCU0_B6 0x42 + VF610_PAD_PTE28__DCU0_B7 0x42 + >; + }; + pinctrl_dspi0: dspi0grp { fsl,pins = < VF610_PAD_PTB19__DSPI0_CS0 0x1182 @@ -221,7 +282,7 @@ VF610_PAD_PTC9__ENET_RMII1_MDC 0x30d2 VF610_PAD_PTC10__ENET_RMII1_MDIO 0x30d3 VF610_PAD_PTC11__ENET_RMII1_CRS 0x30d1 - VF610_PAD_PTC12__ENET_RMII_RXD1 0x30d1 + VF610_PAD_PTC12__ENET_RMII1_RXD1 0x30d1 VF610_PAD_PTC13__ENET_RMII1_RXD0 0x30d1 VF610_PAD_PTC14__ENET_RMII1_RXER 0x30d1 VF610_PAD_PTC15__ENET_RMII1_TXD1 0x30d2 @@ -287,6 +348,54 @@ status = "okay"; }; +&iomuxc { + vf610-twr { + pinctrl_nfc_1: nfcgrp_1 { + fsl,pins = < + VF610_PAD_PTD31__NF_IO15 0x28df + VF610_PAD_PTD30__NF_IO14 0x28df + VF610_PAD_PTD29__NF_IO13 0x28df + VF610_PAD_PTD28__NF_IO12 0x28df + VF610_PAD_PTD27__NF_IO11 0x28df + VF610_PAD_PTD26__NF_IO10 0x28df + VF610_PAD_PTD25__NF_IO9 0x28df + VF610_PAD_PTD24__NF_IO8 0x28df + VF610_PAD_PTD23__NF_IO7 0x28df + VF610_PAD_PTD22__NF_IO6 0x28df + VF610_PAD_PTD21__NF_IO5 0x28df + VF610_PAD_PTD20__NF_IO4 0x28df + VF610_PAD_PTD19__NF_IO3 0x28df + VF610_PAD_PTD18__NF_IO2 0x28df + VF610_PAD_PTD17__NF_IO1 0x28df + VF610_PAD_PTD16__NF_IO0 0x28df + VF610_PAD_PTB24__NF_WE_B 0x28c2 + VF610_PAD_PTB25__NF_CE0_B 0x28c2 + VF610_PAD_PTB27__NF_RE_B 0x28c2 + VF610_PAD_PTC26__NF_RB_B 0x283d + VF610_PAD_PTC27__NF_ALE 0x28c2 + VF610_PAD_PTC28__NF_CLE 0x28c2 + >; + }; + }; +}; + +&nfc { + assigned-clocks = <&clks VF610_CLK_NFC>; + assigned-clock-rates = <33000000>; + nand-bus-width = <16>; + nand-ecc-mode = "hw"; + nand-ecc-step-size = <2048>; + nand-ecc-strength = <24>; + nand-on-flash-bbt; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_nfc_1>; + status = "okay"; +}; + +&tcon0 { + status = "okay"; +}; + &uart1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_uart1>; diff --git a/arch/arm/boot/dts/vfxxx.dtsi b/arch/arm/boot/dts/vfxxx.dtsi index 4aa335166be7..eb4217273c0e 100644 --- a/arch/arm/boot/dts/vfxxx.dtsi +++ b/arch/arm/boot/dts/vfxxx.dtsi @@ -54,8 +54,36 @@ #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; - interrupt-parent = <&mscm_ir>; + interrupt-parent = <&gpc>; ranges; + ocotp-cfg = <&ocotp 0x410 0x420>; + rom-revision = <&ocrom 0x80>; + + ocrom: ocrom { + compatible = "fsl,vf610-ocrom", "syscon"; + reg = <0x00000000 0x18000>; + }; + + ocram0: sram@3f000000 { + compatible = "mmio-sram"; + reg = <0x3f000000 0x40000>; + }; + + ocram1: sram@3f040000 { + compatible = "mmio-sram"; + reg = <0x3f040000 0x40000>; + }; + + gfxram0: sram@3f400000 { + compatible = "mmio-sram"; + reg = <0x3f400000 0x80000>; + }; + + /* used by L2 cache */ + gfxram1: sram@3f480000 { + compatible = "mmio-sram"; + reg = <0x3f480000 0x80000>; + }; aips0: aips-bus@40000000 { compatible = "fsl,aips-bus", "simple-bus"; @@ -70,10 +98,22 @@ mscm_ir: interrupt-controller@40001800 { compatible = "fsl,vf610-mscm-ir"; + #address-cells = <1>; + #size-cells = <1>; reg = <0x40001800 0x400>; fsl,cpucfg = <&mscm_cpucfg>; interrupt-controller; #interrupt-cells = <2>; + + cpu2cpu@40001800 { + reg = <0x40001800 0x40>; + interrupt-parent = <&mscm_ir>; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>, + <1 IRQ_TYPE_LEVEL_HIGH>, + <2 IRQ_TYPE_LEVEL_HIGH>, + <3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "int0", "int1", "int2", "int3"; + }; }; edma0: dma-controller@40018000 { @@ -92,6 +132,12 @@ status = "disabled"; }; + sema4: semaphore-controller@4001d000 { + compatible = "fsl,vf610-sema4"; + reg = <0x4001d000 0x1000>; + interrupts = <4 IRQ_TYPE_LEVEL_HIGH>; + }; + can0: flexcan@40020000 { compatible = "fsl,vf610-flexcan"; reg = <0x40020000 0x4000>; @@ -174,12 +220,28 @@ status = "disabled"; }; + sai0: sai@4002f000 { + compatible = "fsl,vf610-sai"; + reg = <0x4002f000 0x1000>; + interrupts = <84 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_SAI0>, + <&clks VF610_CLK_SAI0_DIV>, + <&clks 0>, <&clks 0>; + clock-names = "bus", "mclk1", "mclk2", "mclk3"; + dma-names = "tx", "rx"; + dmas = <&edma0 0 17>, + <&edma0 0 16>; + status = "disabled"; + }; + sai2: sai@40031000 { compatible = "fsl,vf610-sai"; reg = <0x40031000 0x1000>; interrupts = <86 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clks VF610_CLK_SAI2>; - clock-names = "sai"; + clocks = <&clks VF610_CLK_SAI2>, + <&clks VF610_CLK_SAI2_DIV>, + <&clks 0>, <&clks 0>; + clock-names = "bus", "mclk1", "mclk2", "mclk3"; dma-names = "tx", "rx"; dmas = <&edma0 0 21>, <&edma0 0 20>; @@ -226,6 +288,17 @@ interrupts = <53 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks VF610_CLK_ADC0>; clock-names = "adc"; + #io-channel-cells = <1>; + status = "disabled"; + fsl,adck-max-frequency = <30000000>, <40000000>, + <20000000>; + }; + + tcon0: tcon@4003d000 { + compatible = "fsl,vf610-tcon"; + reg = <0x4003d000 0x1000>; + clocks = <&clks VF610_CLK_TCON0>; + clock-names = "tcon"; status = "disabled"; }; @@ -264,6 +337,7 @@ interrupt-controller; #interrupt-cells = <2>; gpio-ranges = <&iomuxc 0 0 32>; + fsl,gpio-wakeup = <&wakeup 22 0 8>; /* PTB0...PTB7 */ }; gpio1: gpio@4004a000 { @@ -275,6 +349,10 @@ interrupt-controller; #interrupt-cells = <2>; gpio-ranges = <&iomuxc 0 32 32>; + fsl,gpio-wakeup = <&wakeup 1 8 2>, /* PTB11, PTB12 (NMI)*/ + <&wakeup 4 10 1>, /* PTB14 */ + <&wakeup 6 11 1>, /* PTB16 */ + <&wakeup 9 12 2>; /* PTB19, PTB20 */ }; gpio2: gpio@4004b000 { @@ -297,6 +375,9 @@ interrupt-controller; #interrupt-cells = <2>; gpio-ranges = <&iomuxc 0 96 32>; + fsl,gpio-wakeup = <&wakeup 1 14 1>, /* PTB27 */ + <&wakeup 7 15 1>, /* PTC30 */ + <&wakeup 29 16 1>; /* PTE20 */ }; gpio4: gpio@4004d000 { @@ -315,6 +396,11 @@ reg = <0x40050000 0x400>; }; + scsc: scsc@40052000 { + compatible = "fsl,vf610-scsc"; + reg = <0x40052000 0x1000>; + }; + usbphy0: usbphy@40050800 { compatible = "fsl,vf610-usbphy"; reg = <0x40050800 0x400>; @@ -333,6 +419,16 @@ status = "disabled"; }; + dcu0: dcu@40058000 { + compatible = "fsl,vf610-dcu"; + reg = <0x40058000 0x1200>; + interrupts = <30 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_DCU0>; + clock-names = "dcu"; + tcon-controller = <&tcon0>; + status = "disabled"; + }; + i2c0: i2c@40066000 { #address-cells = <1>; #size-cells = <0>; @@ -347,6 +443,27 @@ status = "disabled"; }; + i2c1: i2c@40067000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "fsl,vf610-i2c"; + reg = <0x40067000 0x1000>; + interrupts = <72 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_I2C1>; + clock-names = "ipg"; + dmas = <&edma0 0 52>, + <&edma0 0 53>; + dma-names = "rx","tx"; + status = "disabled"; + }; + + wakeup: wkpu@4006a000 { + compatible = "fsl,vf610-wkpu"; + reg = <0x4006a000 0x1000>; + interrupts = <92 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_WKPU>; + }; + clks: ccm@4006b000 { compatible = "fsl,vf610-ccm"; reg = <0x4006b000 0x1000>; @@ -379,6 +496,14 @@ reg = <0x4006e000 0x1000>; interrupts = <96 IRQ_TYPE_LEVEL_HIGH>; }; + + gpc: gpc@4006c000 { + compatible = "fsl,vf610-gpc"; + reg = <0x4006c000 0x1000>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&mscm_ir>; + }; }; aips1: aips-bus@40080000 { @@ -403,6 +528,11 @@ status = "disabled"; }; + ocotp: ocotp@400a5000 { + compatible = "fsl,vf610-ocotp", "syscon"; + reg = <0x400a5000 0x1000>; + }; + snvs0: snvs@400a7000 { compatible = "fsl,sec-v4.0-mon", "simple-bus"; #address-cells = <1>; @@ -436,12 +566,33 @@ status = "disabled"; }; + ddrmc: ddrmc@400ae000 { + compatible = "fsl,vf610-ddrmc"; + reg = <0x400ae000 0x400>; + clocks = <&clks VF610_CLK_DDRMC>; + clock-names = "ddrc"; + }; + adc1: adc@400bb000 { compatible = "fsl,vf610-adc"; reg = <0x400bb000 0x1000>; interrupts = <54 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks VF610_CLK_ADC1>; clock-names = "adc"; + #io-channel-cells = <1>; + status = "disabled"; + fsl,adck-max-frequency = <30000000>, <40000000>, + <20000000>; + }; + + esdhc0: esdhc@400b1000 { + compatible = "fsl,imx53-esdhc"; + reg = <0x400b1000 0x1000>; + interrupts = <27 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_IPG_BUS>, + <&clks VF610_CLK_PLATFORM_BUS>, + <&clks VF610_CLK_ESDHC0>; + clock-names = "ipg", "ahb", "per"; status = "disabled"; }; @@ -520,6 +671,49 @@ status = "disabled"; }; + nfc: nand@400e0000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "fsl,vf610-nfc"; + reg = <0x400e0000 0x4000>; + interrupts = <83 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_NFC>; + clock-names = "nfc"; + status = "disabled"; + }; + + i2c2: i2c@400e6000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "fsl,vf610-i2c"; + reg = <0x400e6000 0x1000>; + interrupts = <73 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_I2C2>; + clock-names = "ipg"; + dmas = <&edma0 1 36>, + <&edma0 1 37>; + dma-names = "rx","tx"; + status = "disabled"; + }; + + i2c3: i2c@400e7000 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "fsl,vf610-i2c"; + reg = <0x400e7000 0x1000>; + interrupts = <74 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks VF610_CLK_I2C3>; + clock-names = "ipg"; + dmas = <&edma0 1 38>, + <&edma0 1 39>; + dma-names = "rx","tx"; + status = "disabled"; + }; + }; + + adc_hwmon: iio_hwmon { + compatible = "iio-hwmon"; + io-channels = <&adc0 16>, <&adc1 16>; }; }; }; diff --git a/arch/arm/configs/colibri_vf_defconfig b/arch/arm/configs/colibri_vf_defconfig new file mode 100644 index 000000000000..45f2421a5373 --- /dev/null +++ b/arch/arm/configs/colibri_vf_defconfig @@ -0,0 +1,317 @@ +CONFIG_KERNEL_LZO=y +CONFIG_SYSVIPC=y +CONFIG_FHANDLE=y +CONFIG_IRQ_DOMAIN_DEBUG=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=16 +CONFIG_CGROUPS=y +CONFIG_NAMESPACES=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +CONFIG_KALLSYMS_ALL=y +CONFIG_EMBEDDED=y +CONFIG_PERF_EVENTS=y +# CONFIG_SLUB_DEBUG is not set +# CONFIG_COMPAT_BRK is not set +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +# CONFIG_BLK_DEV_BSG is not set +CONFIG_ARCH_MXC=y +CONFIG_SOC_VF610=y +CONFIG_SWP_EMULATE=y +CONFIG_VMSPLIT_2G=y +CONFIG_PREEMPT_VOLUNTARY=y +CONFIG_AEABI=y +CONFIG_CMA=y +CONFIG_KEXEC=y +# CONFIG_ATAGS_PROC is not set +CONFIG_CPU_IDLE=y +# CONFIG_CPU_IDLE_GOV_LADDER is not set +CONFIG_VFP=y +CONFIG_NEON=y +CONFIG_KERNEL_MODE_NEON=y +CONFIG_BINFMT_MISC=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_NET_IPGRE_DEMUX=m +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +CONFIG_IPV6=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_NAT=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_NFACCT=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_NFT_CHAIN_NAT_IPV4=y +CONFIG_NFT_MASQ_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_NAT=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_NF_TABLES_BRIDGE=y +CONFIG_L2TP=m +CONFIG_BRIDGE=y +# CONFIG_BRIDGE_IGMP_SNOOPING is not set +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_CAN=m +CONFIG_CAN_FLEXCAN=m +CONFIG_CAN_MCP251X=m +CONFIG_CFG80211=m +CONFIG_MAC80211=m +CONFIG_RFKILL=y +CONFIG_RFKILL_INPUT=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_STANDALONE is not set +CONFIG_DMA_CMA=y +CONFIG_CONNECTOR=y +CONFIG_MTD=y +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_JEDECPROBE=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_NAND=y +CONFIG_MTD_NAND_VF610_NFC=y +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_FASTMAP=y +CONFIG_MTD_UBI_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_SCSI_SCAN_ASYNC=y +# CONFIG_SCSI_LOWLEVEL is not set +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_CADENCE is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CIRRUS is not set +# CONFIG_NET_VENDOR_FARADAY is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +CONFIG_MICREL_PHY=y +CONFIG_PPP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_MPPE=m +CONFIG_PPTP=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_USB_NET_DRIVERS=m +CONFIG_USB_USBNET=m +# CONFIG_USB_NET_CDC_NCM is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +CONFIG_USB_ZD1201=m +CONFIG_RT2X00=m +CONFIG_RT2800USB=m +# CONFIG_RT2800USB_RT35XX is not set +CONFIG_RTL8192CU=m +# CONFIG_RTLWIFI_DEBUG is not set +CONFIG_INPUT_POLLDEV=y +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=y +# CONFIG_MOUSE_PS2 is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_FUSION_F0710A=m +CONFIG_TOUCHSCREEN_WM97XX=y +# CONFIG_TOUCHSCREEN_WM9705 is not set +# CONFIG_TOUCHSCREEN_WM9713 is not set +CONFIG_TOUCHSCREEN_COLIBRI_VF50=y +# CONFIG_SERIO is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_DEVKMEM is not set +CONFIG_SERIAL_FSL_LPUART=y +CONFIG_SERIAL_FSL_LPUART_CONSOLE=y +CONFIG_HW_RANDOM=y +# CONFIG_I2C_COMPAT is not set +CONFIG_I2C_CHARDEV=y +# CONFIG_I2C_HELPER_AUTO is not set +CONFIG_I2C_IMX=y +CONFIG_SPI=y +CONFIG_SPI_FSL_DSPI=y +CONFIG_SPI_SPIDEV=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_GPIO=y +CONFIG_POWER_RESET_GPIO_RESTART=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_SENSORS_IIO_HWMON=y +CONFIG_WATCHDOG=y +CONFIG_IMX2_WDT=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_ANATOP=y +CONFIG_REGULATOR_GPIO=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_RC_SUPPORT=y +# CONFIG_RC_MAP is not set +# CONFIG_RC_DECODERS is not set +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_VIDEO_CLASS=m +# CONFIG_USB_GSPCA is not set +CONFIG_FB=y +CONFIG_FB_FSL_DCU=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_GENERIC is not set +CONFIG_BACKLIGHT_PWM=y +CONFIG_BACKLIGHT_GPIO=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=y +# CONFIG_SND_DRIVERS is not set +# CONFIG_SND_ARM is not set +# CONFIG_SND_SPI is not set +# CONFIG_SND_USB is not set +CONFIG_SND_SOC=y +CONFIG_SND_SOC_FSL_SAI=y +CONFIG_SND_IMX_SOC=y +CONFIG_SND_SOC_FSL_SAI_WM9712=y +CONFIG_HIDRAW=y +CONFIG_HID_MULTITOUCH=m +CONFIG_USB_HIDDEV=y +CONFIG_USB=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_ACM=m +CONFIG_USB_STORAGE=y +CONFIG_USB_CHIPIDEA=y +CONFIG_USB_CHIPIDEA_UDC=y +CONFIG_USB_CHIPIDEA_HOST=y +CONFIG_USB_SERIAL=y +CONFIG_USB_SERIAL_CONSOLE=y +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_FTDI_SIO=y +CONFIG_USB_SERIAL_PL2303=y +CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_MXS_PHY=y +CONFIG_USB_GADGET=y +CONFIG_USB_FSL_USB2=y +CONFIG_USB_CONFIGFS=m +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_MMC=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_ESDHC_IMX=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PWM=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_DS1307=y +CONFIG_RTC_DRV_SNVS=y +CONFIG_DMADEVICES=y +# CONFIG_MX3_IPU is not set +CONFIG_FSL_EDMA=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_EXTCON=y +CONFIG_EXTCON_USB_GPIO=y +CONFIG_IIO=y +CONFIG_IIO_TRIGGER=y +CONFIG_VF610_ADC=y +CONFIG_PWM=y +CONFIG_PWM_FSL_FTM=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_AUTOFS4_FS=y +CONFIG_FUSE_FS=y +CONFIG_CUSE=y +CONFIG_OVERLAY_FS=y +CONFIG_VFAT_FS=y +CONFIG_NTFS_FS=y +CONFIG_NTFS_RW=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_UBIFS_FS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_CIFS=y +CONFIG_NLS_DEFAULT="cp437" +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_UTF8=y +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_FS=y +CONFIG_LOCKUP_DETECTOR=y +CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=10 +# CONFIG_SCHED_DEBUG is not set +CONFIG_STACKTRACE=y +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_FTRACE is not set +# CONFIG_ARM_UNWIND is not set +CONFIG_SECURITYFS=y +# CONFIG_CRYPTO_ANSI_CPRNG is not set +# CONFIG_CRYPTO_HW is not set +CONFIG_CRC_T10DIF=y +CONFIG_XZ_DEC=y +CONFIG_FONTS=y +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 3244cf1d2773..19fbc41731d7 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -94,14 +94,17 @@ obj-$(CONFIG_SOC_IMX6SX) += clk-imx6sx.o mach-imx6sx.o ifeq ($(CONFIG_SUSPEND),y) AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o +AFLAGS_suspend-vf610.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_VF610) += suspend-vf610.o endif obj-$(CONFIG_SOC_IMX6) += pm-imx6.o +obj-$(CONFIG_SOC_VF610) += pm-vf610.o obj-$(CONFIG_SOC_IMX50) += mach-imx50.o obj-$(CONFIG_SOC_IMX51) += mach-imx51.o obj-$(CONFIG_SOC_IMX53) += mach-imx53.o -obj-$(CONFIG_SOC_VF610) += clk-vf610.o mach-vf610.o +obj-$(CONFIG_SOC_VF610) += clk-vf610.o mach-vf610.o vf610_sema4.o obj-$(CONFIG_SOC_LS1021A) += mach-ls1021a.o diff --git a/arch/arm/mach-imx/clk-gate2.c b/arch/arm/mach-imx/clk-gate2.c index 8935bff99fe7..db44a198a0d9 100644 --- a/arch/arm/mach-imx/clk-gate2.c +++ b/arch/arm/mach-imx/clk-gate2.c @@ -31,6 +31,7 @@ struct clk_gate2 { struct clk_hw hw; void __iomem *reg; u8 bit_idx; + u8 cgr_val; u8 flags; spinlock_t *lock; unsigned int *share_count; @@ -50,7 +51,8 @@ static int clk_gate2_enable(struct clk_hw *hw) goto out; reg = readl(gate->reg); - reg |= 3 << gate->bit_idx; + reg &= ~(3 << gate->bit_idx); + reg |= gate->cgr_val << gate->bit_idx; writel(reg, gate->reg); out: @@ -125,7 +127,7 @@ static struct clk_ops clk_gate2_ops = { struct clk *clk_register_gate2(struct device *dev, const char *name, const char *parent_name, unsigned long flags, - void __iomem *reg, u8 bit_idx, + void __iomem *reg, u8 bit_idx, u8 cgr_val, u8 clk_gate2_flags, spinlock_t *lock, unsigned int *share_count) { @@ -140,6 +142,7 @@ struct clk *clk_register_gate2(struct device *dev, const char *name, /* struct clk_gate2 assignments */ gate->reg = reg; gate->bit_idx = bit_idx; + gate->cgr_val = cgr_val; gate->flags = clk_gate2_flags; gate->lock = lock; gate->share_count = share_count; diff --git a/arch/arm/mach-imx/clk-vf610.c b/arch/arm/mach-imx/clk-vf610.c index 61876ed6e11e..6e739454ff89 100644 --- a/arch/arm/mach-imx/clk-vf610.c +++ b/arch/arm/mach-imx/clk-vf610.c @@ -10,9 +10,11 @@ #include <linux/of_address.h> #include <linux/clk.h> +#include <linux/syscore_ops.h> #include <dt-bindings/clock/vf610-clock.h> #include "clk.h" +#include "common.h" #define CCM_CCR (ccm_base + 0x00) #define CCM_CSR (ccm_base + 0x04) @@ -40,6 +42,7 @@ #define CCM_CCGR9 (ccm_base + 0x64) #define CCM_CCGR10 (ccm_base + 0x68) #define CCM_CCGR11 (ccm_base + 0x6c) +#define CCM_CCGRx(x) (CCM_CCGR0 + (x) * 4) #define CCM_CMEOR0 (ccm_base + 0x70) #define CCM_CMEOR1 (ccm_base + 0x74) #define CCM_CMEOR2 (ccm_base + 0x78) @@ -115,9 +118,25 @@ static struct clk_div_table pll4_audio_div_table[] = { static struct clk *clk[VF610_CLK_END]; static struct clk_onecell_data clk_data; +static u32 anadig_pll3_ctrl; +static u32 anadig_pll4_ctrl; +static u32 anadig_pll5_ctrl; +static u32 anadig_pll6_ctrl; +static u32 anadig_pll7_ctrl; +static u32 ccpgr0; +static u32 cscmr1; +static u32 cscmr2; +static u32 cscdr1; +static u32 cscdr2; +static u32 cscdr3; +static u32 ccgr[12]; + static unsigned int const clks_init_on[] __initconst = { VF610_CLK_SYS_BUS, VF610_CLK_DDR_SEL, + VF610_CLK_DAP, + VF610_CLK_DDRMC, + VF610_CLK_WKPU, }; static struct clk * __init vf610_get_fixed_clock( @@ -131,6 +150,57 @@ static struct clk * __init vf610_get_fixed_clock( return clk; }; +static int vf610_clk_suspend(void) +{ + int i; + + anadig_pll3_ctrl = readl_relaxed(PLL3_CTRL); + anadig_pll4_ctrl = readl_relaxed(PLL4_CTRL); + anadig_pll5_ctrl = readl_relaxed(PLL5_CTRL); + anadig_pll6_ctrl = readl_relaxed(PLL6_CTRL); + anadig_pll7_ctrl = readl_relaxed(PLL7_CTRL); + + ccpgr0 = readl_relaxed(CCM_CCPGR0); + cscmr1 = readl_relaxed(CCM_CSCMR1); + cscmr2 = readl_relaxed(CCM_CSCMR2); + + cscdr1 = readl_relaxed(CCM_CSCDR1); + cscdr2 = readl_relaxed(CCM_CSCDR2); + cscdr3 = readl_relaxed(CCM_CSCDR3); + + for (i = 0; i < 12; i++) + ccgr[i] = readl_relaxed(CCM_CCGRx(i)); + + return 0; +} + +static void vf610_clk_resume(void) +{ + int i; + + writel_relaxed(anadig_pll3_ctrl, PLL3_CTRL); + writel_relaxed(anadig_pll4_ctrl, PLL4_CTRL); + writel_relaxed(anadig_pll5_ctrl, PLL5_CTRL); + writel_relaxed(anadig_pll6_ctrl, PLL6_CTRL); + writel_relaxed(anadig_pll7_ctrl, PLL7_CTRL); + + writel_relaxed(ccpgr0, CCM_CCPGR0); + writel_relaxed(cscmr1, CCM_CSCMR1); + writel_relaxed(cscmr2, CCM_CSCMR2); + + writel_relaxed(cscdr1, CCM_CSCDR1); + writel_relaxed(cscdr2, CCM_CSCDR2); + writel_relaxed(cscdr3, CCM_CSCDR3); + + for (i = 0; i < 12; i++) + writel_relaxed(ccgr[i], CCM_CCGRx(i)); +} + +static struct syscore_ops vf610_clk_syscore_ops = { + .suspend = vf610_clk_suspend, + .resume = vf610_clk_resume, +}; + static void __init vf610_clocks_init(struct device_node *ccm_node) { struct device_node *np; @@ -159,6 +229,8 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) ccm_base = of_iomap(np, 0); BUG_ON(!ccm_base); + vf610_pm_set_ccm_base(ccm_base); + clk[VF610_CLK_SLOW_CLK_SEL] = imx_clk_mux("slow_clk_sel", CCM_CCSR, 4, 1, slow_sels, ARRAY_SIZE(slow_sels)); clk[VF610_CLK_FASK_CLK_SEL] = imx_clk_mux("fast_clk_sel", CCM_CCSR, 5, 1, fast_sels, ARRAY_SIZE(fast_sels)); @@ -232,6 +304,8 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk[VF610_CLK_PLL4_MAIN_DIV] = clk_register_divider_table(NULL, "pll4_audio_div", "pll4_audio", 0, CCM_CACRR, 6, 3, 0, pll4_audio_div_table, &imx_ccm_lock); clk[VF610_CLK_PLL6_MAIN_DIV] = imx_clk_divider("pll6_video_div", "pll6_video", CCM_CACRR, 21, 1); + clk[VF610_CLK_DDRMC] = imx_clk_gate2_cgr("ddrmc", "ddr_sel", CCM_CCGR6, CCM_CCGRx_CGn(14), 0x2); + clk[VF610_CLK_USBPHY0] = imx_clk_gate("usbphy0", "pll3_usb_otg", PLL3_CTRL, 6); clk[VF610_CLK_USBPHY1] = imx_clk_gate("usbphy1", "pll7_usb_host", PLL7_CTRL, 6); @@ -263,15 +337,19 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk[VF610_CLK_PIT] = imx_clk_gate2("pit", "ipg_bus", CCM_CCGR1, CCM_CCGRx_CGn(7)); - clk[VF610_CLK_UART0] = imx_clk_gate2("uart0", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(7)); - clk[VF610_CLK_UART1] = imx_clk_gate2("uart1", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(8)); - clk[VF610_CLK_UART2] = imx_clk_gate2("uart2", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(9)); - clk[VF610_CLK_UART3] = imx_clk_gate2("uart3", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(10)); - clk[VF610_CLK_UART4] = imx_clk_gate2("uart4", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(9)); - clk[VF610_CLK_UART5] = imx_clk_gate2("uart5", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(10)); + clk[VF610_CLK_UART0] = imx_clk_gate2_cgr("uart0", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(7), 0x2); + clk[VF610_CLK_UART1] = imx_clk_gate2_cgr("uart1", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(8), 0x2); + clk[VF610_CLK_UART2] = imx_clk_gate2_cgr("uart2", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(9), 0x2); + clk[VF610_CLK_UART3] = imx_clk_gate2_cgr("uart3", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(10), 0x2); + clk[VF610_CLK_UART4] = imx_clk_gate2_cgr("uart4", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(9), 0x2); + clk[VF610_CLK_UART5] = imx_clk_gate2_cgr("uart5", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(10), 0x2); clk[VF610_CLK_I2C0] = imx_clk_gate2("i2c0", "ipg_bus", CCM_CCGR4, CCM_CCGRx_CGn(6)); clk[VF610_CLK_I2C1] = imx_clk_gate2("i2c1", "ipg_bus", CCM_CCGR4, CCM_CCGRx_CGn(7)); + clk[VF610_CLK_I2C2] = imx_clk_gate2("i2c2", "ipg_bus", CCM_CCGR10, CCM_CCGRx_CGn(6)); + clk[VF610_CLK_I2C3] = imx_clk_gate2("i2c3", "ipg_bus", CCM_CCGR10, CCM_CCGRx_CGn(7)); + + clk[VF610_CLK_WKPU] = imx_clk_gate2_cgr("wkpu", "ipg_bus", CCM_CCGR4, CCM_CCGRx_CGn(10), 0x2); clk[VF610_CLK_DSPI0] = imx_clk_gate2("dspi0", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(12)); clk[VF610_CLK_DSPI1] = imx_clk_gate2("dspi1", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(13)); @@ -324,6 +402,8 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk[VF610_CLK_DCU1_DIV] = imx_clk_divider("dcu1_div", "dcu1_en", CCM_CSCDR3, 20, 3); clk[VF610_CLK_DCU1] = imx_clk_gate2("dcu1", "dcu1_div", CCM_CCGR9, CCM_CCGRx_CGn(8)); + clk[VF610_CLK_TCON0] = imx_clk_gate2("tcon0", "platform_bus", CCM_CCGR1, CCM_CCGRx_CGn(13)); + clk[VF610_CLK_ESAI_SEL] = imx_clk_mux("esai_sel", CCM_CSCMR1, 20, 2, esai_sels, 4); clk[VF610_CLK_ESAI_EN] = imx_clk_gate("esai_en", "esai_sel", CCM_CSCDR2, 30); clk[VF610_CLK_ESAI_DIV] = imx_clk_divider("esai_div", "esai_en", CCM_CSCDR2, 24, 4); @@ -332,22 +412,22 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk[VF610_CLK_SAI0_SEL] = imx_clk_mux("sai0_sel", CCM_CSCMR1, 0, 2, sai_sels, 4); clk[VF610_CLK_SAI0_EN] = imx_clk_gate("sai0_en", "sai0_sel", CCM_CSCDR1, 16); clk[VF610_CLK_SAI0_DIV] = imx_clk_divider("sai0_div", "sai0_en", CCM_CSCDR1, 0, 4); - clk[VF610_CLK_SAI0] = imx_clk_gate2("sai0", "sai0_div", CCM_CCGR0, CCM_CCGRx_CGn(15)); + clk[VF610_CLK_SAI0] = imx_clk_gate2("sai0", "ipg_bus", CCM_CCGR0, CCM_CCGRx_CGn(15)); clk[VF610_CLK_SAI1_SEL] = imx_clk_mux("sai1_sel", CCM_CSCMR1, 2, 2, sai_sels, 4); clk[VF610_CLK_SAI1_EN] = imx_clk_gate("sai1_en", "sai1_sel", CCM_CSCDR1, 17); clk[VF610_CLK_SAI1_DIV] = imx_clk_divider("sai1_div", "sai1_en", CCM_CSCDR1, 4, 4); - clk[VF610_CLK_SAI1] = imx_clk_gate2("sai1", "sai1_div", CCM_CCGR1, CCM_CCGRx_CGn(0)); + clk[VF610_CLK_SAI1] = imx_clk_gate2("sai1", "ipg_bus", CCM_CCGR1, CCM_CCGRx_CGn(0)); clk[VF610_CLK_SAI2_SEL] = imx_clk_mux("sai2_sel", CCM_CSCMR1, 4, 2, sai_sels, 4); clk[VF610_CLK_SAI2_EN] = imx_clk_gate("sai2_en", "sai2_sel", CCM_CSCDR1, 18); clk[VF610_CLK_SAI2_DIV] = imx_clk_divider("sai2_div", "sai2_en", CCM_CSCDR1, 8, 4); - clk[VF610_CLK_SAI2] = imx_clk_gate2("sai2", "sai2_div", CCM_CCGR1, CCM_CCGRx_CGn(1)); + clk[VF610_CLK_SAI2] = imx_clk_gate2("sai2", "ipg_bus", CCM_CCGR1, CCM_CCGRx_CGn(1)); clk[VF610_CLK_SAI3_SEL] = imx_clk_mux("sai3_sel", CCM_CSCMR1, 6, 2, sai_sels, 4); clk[VF610_CLK_SAI3_EN] = imx_clk_gate("sai3_en", "sai3_sel", CCM_CSCDR1, 19); clk[VF610_CLK_SAI3_DIV] = imx_clk_divider("sai3_div", "sai3_en", CCM_CSCDR1, 12, 4); - clk[VF610_CLK_SAI3] = imx_clk_gate2("sai3", "sai3_div", CCM_CCGR1, CCM_CCGRx_CGn(2)); + clk[VF610_CLK_SAI3] = imx_clk_gate2("sai3", "ipg_bus", CCM_CCGR1, CCM_CCGRx_CGn(2)); clk[VF610_CLK_NFC_SEL] = imx_clk_mux("nfc_sel", CCM_CSCMR1, 12, 2, nfc_sels, 4); clk[VF610_CLK_NFC_EN] = imx_clk_gate("nfc_en", "nfc_sel", CCM_CSCDR2, 9); @@ -383,9 +463,12 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk[VF610_CLK_DMAMUX3] = imx_clk_gate2("dmamux3", "platform_bus", CCM_CCGR6, CCM_CCGRx_CGn(2)); clk[VF610_CLK_SNVS] = imx_clk_gate2("snvs-rtc", "ipg_bus", CCM_CCGR6, CCM_CCGRx_CGn(7)); + clk[VF610_CLK_DAP] = imx_clk_gate("dap", "platform_bus", CCM_CCSR, 24); imx_check_clocks(clk, ARRAY_SIZE(clk)); + clk_set_parent(clk[VF610_CLK_ENET_SEL], clk[VF610_CLK_ENET_50M]); + clk_set_parent(clk[VF610_CLK_QSPI0_SEL], clk[VF610_CLK_PLL1_PFD4]); clk_set_rate(clk[VF610_CLK_QSPI0_X4_DIV], clk_get_rate(clk[VF610_CLK_QSPI0_SEL]) / 2); clk_set_rate(clk[VF610_CLK_QSPI0_X2_DIV], clk_get_rate(clk[VF610_CLK_QSPI0_X4_DIV]) / 2); @@ -396,17 +479,23 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) clk_set_rate(clk[VF610_CLK_QSPI1_X2_DIV], clk_get_rate(clk[VF610_CLK_QSPI1_X4_DIV]) / 2); clk_set_rate(clk[VF610_CLK_QSPI1_X1_DIV], clk_get_rate(clk[VF610_CLK_QSPI1_X2_DIV]) / 2); - clk_set_parent(clk[VF610_CLK_SAI0_SEL], clk[VF610_CLK_AUDIO_EXT]); - clk_set_parent(clk[VF610_CLK_SAI1_SEL], clk[VF610_CLK_AUDIO_EXT]); - clk_set_parent(clk[VF610_CLK_SAI2_SEL], clk[VF610_CLK_AUDIO_EXT]); + clk_set_parent(clk[VF610_CLK_SAI0_SEL], clk[VF610_CLK_PLL4_MAIN_DIV]); + clk_set_parent(clk[VF610_CLK_SAI1_SEL], clk[VF610_CLK_PLL4_MAIN_DIV]); + clk_set_parent(clk[VF610_CLK_SAI2_SEL], clk[VF610_CLK_PLL4_MAIN_DIV]); clk_set_parent(clk[VF610_CLK_SAI3_SEL], clk[VF610_CLK_AUDIO_EXT]); + clk_set_rate(clk[VF610_CLK_PLL4_MAIN_DIV], 147456000); + + clk_set_parent(clk[VF610_CLK_DCU0_SEL], clk[VF610_CLK_PLL1_PFD2]); for (i = 0; i < ARRAY_SIZE(clks_init_on); i++) clk_prepare_enable(clk[clks_init_on[i]]); + register_syscore_ops(&vf610_clk_syscore_ops); + /* Add the clocks to provider list */ clk_data.clks = clk; clk_data.clk_num = ARRAY_SIZE(clk); of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); } CLK_OF_DECLARE(vf610, "fsl,vf610-ccm", vf610_clocks_init); + diff --git a/arch/arm/mach-imx/clk.h b/arch/arm/mach-imx/clk.h index 6a07903a28bc..8aec27129ec7 100644 --- a/arch/arm/mach-imx/clk.h +++ b/arch/arm/mach-imx/clk.h @@ -30,7 +30,7 @@ struct clk *imx_clk_pllv3(enum imx_pllv3_type type, const char *name, struct clk *clk_register_gate2(struct device *dev, const char *name, const char *parent_name, unsigned long flags, - void __iomem *reg, u8 bit_idx, + void __iomem *reg, u8 bit_idx, u8 cgr_val, u8 clk_gate_flags, spinlock_t *lock, unsigned int *share_count); @@ -44,7 +44,7 @@ static inline struct clk *imx_clk_gate2(const char *name, const char *parent, void __iomem *reg, u8 shift) { return clk_register_gate2(NULL, name, parent, CLK_SET_RATE_PARENT, reg, - shift, 0, &imx_ccm_lock, NULL); + shift, 0x3, 0, &imx_ccm_lock, NULL); } static inline struct clk *imx_clk_gate2_shared(const char *name, @@ -52,7 +52,14 @@ static inline struct clk *imx_clk_gate2_shared(const char *name, unsigned int *share_count) { return clk_register_gate2(NULL, name, parent, CLK_SET_RATE_PARENT, reg, - shift, 0, &imx_ccm_lock, share_count); + shift, 0x3, 0, &imx_ccm_lock, share_count); +} + +static inline struct clk *imx_clk_gate2_cgr(const char *name, + const char *parent, void __iomem *reg, u8 shift, u8 cgr_val) +{ + return clk_register_gate2(NULL, name, parent, CLK_SET_RATE_PARENT, reg, + shift, cgr_val, 0, &imx_ccm_lock, NULL); } struct clk *imx_clk_pfd(const char *name, const char *parent_name, diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 0f04e30b726d..f37f1938965a 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -79,6 +79,13 @@ enum mxc_cpu_pwr_mode { STOP_POWER_OFF, /* STOP + SRPG */ }; +enum vf610_cpu_pwr_mode { + VF610_RUN, + VF610_LP_RUN, + VF610_STOP, + VF610_LP_STOP, +}; + enum mx3_cpu_pwr_mode { MX3_RUN, MX3_WAIT, @@ -122,9 +129,11 @@ int imx_cpu_kill(unsigned int cpu); #ifdef CONFIG_SUSPEND void v7_cpu_resume(void); void imx6_suspend(void __iomem *ocram_vbase); +void vf610_suspend(void __iomem *ocram_vbase); #else static inline void v7_cpu_resume(void) {} static inline void imx6_suspend(void __iomem *ocram_vbase) {} +static inline void vf610_suspend(void __iomem *ocram_vbase) {} #endif void imx6q_pm_init(void); @@ -132,6 +141,8 @@ void imx6dl_pm_init(void); void imx6sl_pm_init(void); void imx6sx_pm_init(void); void imx6q_pm_set_ccm_base(void __iomem *base); +void vf610_pm_init(void); +void vf610_pm_set_ccm_base(void __iomem *base); #ifdef CONFIG_PM void imx51_pm_init(void); diff --git a/arch/arm/mach-imx/mach-vf610.c b/arch/arm/mach-imx/mach-vf610.c index 2e7c75b66fe0..1ba7738170c6 100644 --- a/arch/arm/mach-imx/mach-vf610.c +++ b/arch/arm/mach-imx/mach-vf610.c @@ -11,6 +11,13 @@ #include <linux/irqchip.h> #include <asm/mach/arch.h> #include <asm/hardware/cache-l2x0.h> +#include "common.h" + +static void __init vf610_init_machine(void) +{ + of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); + vf610_pm_init(); +} static const char * const vf610_dt_compat[] __initconst = { "fsl,vf500", @@ -23,5 +30,6 @@ static const char * const vf610_dt_compat[] __initconst = { DT_MACHINE_START(VYBRID_VF610, "Freescale Vybrid VF5xx/VF6xx (Device Tree)") .l2c_aux_val = 0, .l2c_aux_mask = ~0, + .init_machine = vf610_init_machine, .dt_compat = vf610_dt_compat, MACHINE_END diff --git a/arch/arm/mach-imx/pm-vf610.c b/arch/arm/mach-imx/pm-vf610.c new file mode 100644 index 000000000000..7192924e009f --- /dev/null +++ b/arch/arm/mach-imx/pm-vf610.c @@ -0,0 +1,661 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2014 Toradex AG + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifdef DEBUG +#define pr_pmdebug(fmt, ...) pr_info("PM: VF610: " fmt "\n", ##__VA_ARGS__) +#else +#define pr_pmdebug(fmt, ...) +#endif +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/genalloc.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/suspend.h> +#include <linux/clk.h> +#include <asm/cacheflush.h> +#include <asm/fncpy.h> +#include <asm/proc-fns.h> +#include <asm/suspend.h> +#include <asm/tlb.h> + +#include "common.h" + +#define DDRMC_PHY_OFFSET 0x400 + +#define CCR 0x0 +#define BM_CCR_FIRC_EN (0x1 << 16) +#define BM_CCR_FXOSC_EN (0x1 << 12) + +#define CCSR 0x8 +#define BM_CCSR_DDRC_CLK_SEL (0x1 << 6) +#define BM_CCSR_FAST_CLK_SEL (0x1 << 5) +#define BM_CCSR_SLOW_CLK_SEL (0x1 << 4) +#define BM_CCSR_SYS_CLK_SEL_MASK (0x7 << 0) + +#define CACRR 0xc + +#define CLPCR 0x2c +#define BM_CLPCR_ARM_CLK_DIS_ON_LPM (0x1 << 5) +#define BM_CLPCR_SBYOS (0x1 << 6) +#define BM_CLPCR_DIS_REF_OSC (0x1 << 7) +#define BM_CLPCR_ANADIG_STOP_MODE (0x1 << 8) +#define BM_CLPCR_FXOSC_BYPSEN (0x1 << 10) +#define BM_CLPCR_FXOSC_PWRDWN (0x1 << 11) +#define BM_CLPCR_MASK_CORE0_WFI (0x1 << 22) +#define BM_CLPCR_MASK_CORE1_WFI (0x1 << 23) +#define BM_CLPCR_MASK_SCU_IDLE (0x1 << 24) +#define BM_CLPCR_MASK_L2CC_IDLE (0x1 << 25) + +#define CGPR 0x64 +#define BM_CGPR_INT_MEM_CLK_LPM (0x1 << 17) + +#define GPC_PGCR 0x0 +#define BM_PGCR_DS_STOP (0x1 << 7) +#define BM_PGCR_DS_LPSTOP (0x1 << 6) +#define BM_PGCR_WB_STOP (0x1 << 4) +#define BM_PGCR_HP_OFF (0x1 << 3) +#define BM_PGCR_PG_PD1 (0x1 << 0) + +#define GPC_LPMR 0x40 +#define BM_LPMR_RUN 0x0 +#define BM_LPMR_STOP 0x2 + +#define ANATOP_PLL1_CTRL 0x270 +#define ANATOP_PLL2_CTRL 0x30 +#define ANATOP_PLL2_PFD 0x100 +#define BM_PLL_POWERDOWN (0x1 << 12) +#define BM_PLL_ENABLE (0x1 << 13) +#define BM_PLL_BYPASS (0x1 << 16) +#define BM_PLL_LOCK (0x1 << 31) +#define BM_PLL_PFD2_CLKGATE (0x1 << 15) +#define BM_PLL_USB_POWER (0x1 << 12) +#define BM_PLL_EN_USB_CLKS (0x1 << 6) + +#define VF610_SUSPEND_OCRAM_SIZE 0x4000 +#define VF610_DDRMC_IO_NUM 94 +#define VF610_IOMUX_DDR_IO_NUM 48 +#define VF610_ANATOP_IO_NUM 2 + +static void __iomem *ccm_base; +static void __iomem *suspend_ocram_base; +static void (*vf610_suspend_in_ocram_fn)(void __iomem *ocram_vbase); +static bool has_cke_reset_pulls; + +#ifdef DEBUG +static void __iomem *uart_membase; +static unsigned long uart_clk; +#endif + +static const u32 vf610_iomuxc_ddr_io_offset[] __initconst = { + 0x220, 0x224, 0x228, 0x22c, 0x230, 0x234, 0x238, 0x23c, + 0x240, 0x244, 0x248, 0x24c, 0x250, 0x254, 0x258, 0x25c, + 0x260, 0x264, 0x268, 0x26c, 0x270, 0x274, 0x278, 0x27c, + 0x280, 0x284, 0x288, 0x28c, 0x290, 0x294, 0x298, 0x29c, + 0x2a0, 0x2a4, 0x2a8, 0x2ac, 0x2b0, 0x2b4, 0x2b8, 0x2bc, + 0x2c0, 0x2c4, 0x2c8, 0x2cc, 0x2d0, 0x2d4, 0x2d8, 0x21c, +}; + + +static const u32 vf610_ddrmc_io_offset[] __initconst = { + 0x00, 0x08, 0x28, 0x2c, 0x30, 0x34, 0x38, + 0x40, 0x44, 0x48, 0x50, 0x54, 0x58, 0x5c, + 0x60, 0x64, 0x68, 0x70, 0x74, 0x78, 0x7c, + 0x84, 0x88, 0x98, 0x9c, 0xa4, 0xc0, + 0x108, 0x10c, 0x114, 0x118, 0x120, 0x124, + 0x128, 0x12c, 0x130, 0x134, 0x138, 0x13c, + 0x148, 0x15c, 0x160, 0x164, 0x16c, 0x180, + 0x184, 0x188, 0x18c, 0x198, 0x1a4, 0x1a8, + 0x1b8, 0x1d4, 0x1d8, 0x1e0, 0x1e4, 0x1e8, + 0x1ec, 0x1f0, 0x1f8, 0x210, 0x224, 0x228, + 0x22c, 0x230, 0x23c, 0x240, 0x244, 0x248, + 0x24c, 0x250, 0x25c, 0x268, 0x26c, 0x278, + 0x268 +}; + +static const u32 vf610_ddrmc_phy_io_offset[] __initconst = { + 0x00, 0x04, 0x08, 0x0c, 0x10, + 0x40, 0x44, 0x48, 0x4c, 0x50, + 0x80, 0x84, 0x88, 0x8c, 0x90, + 0xc4, 0xc8, 0xd0 +}; + +/* + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * vf610_suspend code + * PM_INFO structure(vf610_cpu_pm_info) + * ======================== low address ======================= + */ + +struct vf610_pm_base { + phys_addr_t pbase; + void __iomem *vbase; +}; + +struct vf610_pm_socdata { + const char *anatop_compat; + const char *scsc_compat; + const char *wkpu_compat; + const char *ccm_compat; + const char *gpc_compat; + const char *src_compat; + const char *ddrmc_compat; + const char *iomuxc_compat; +}; + +static const struct vf610_pm_socdata vf610_pm_data __initconst = { + .anatop_compat = "fsl,vf610-anatop", + .scsc_compat = "fsl,vf610-scsc", + .wkpu_compat = "fsl,vf610-wkpu", + .ccm_compat = "fsl,vf610-ccm", + .gpc_compat = "fsl,vf610-gpc", + .src_compat = "fsl,vf610-src", + .ddrmc_compat = "fsl,vf610-ddrmc", + .iomuxc_compat = "fsl,vf610-iomuxc", +}; + +/* + * This structure is for passing necessary data for low level ocram + * suspend code(arch/arm/mach-imx/suspend-vf610.S), if this struct + * definition is changed, the offset definition in + * arch/arm/mach-imx/suspend-vf610.S must be also changed accordingly, + * otherwise, the suspend to ocram function will be broken! + */ +struct vf610_cpu_pm_info { + phys_addr_t pbase; /* The physical address of pm_info. */ + phys_addr_t resume_addr; /* The physical resume address for asm code */ + u32 cpu_type; /* Currently not used, leave it for alignment */ + u32 pm_info_size; /* Size of pm_info. */ + struct vf610_pm_base anatop_base; + struct vf610_pm_base scsc_base; + struct vf610_pm_base wkpu_base; + struct vf610_pm_base ccm_base; + struct vf610_pm_base gpc_base; + struct vf610_pm_base src_base; + struct vf610_pm_base ddrmc_base; + struct vf610_pm_base iomuxc_base; + struct vf610_pm_base l2_base; + u32 ccm_cacrr; + u32 ccm_ccsr; + u32 ddrmc_io_num; /* Number of MMDC IOs which need saved/restored. */ + u32 ddrmc_io_val[VF610_DDRMC_IO_NUM][2]; /* To save offset and value */ + u32 iomux_ddr_io_num; + u32 iomux_ddr_io_val[VF610_IOMUX_DDR_IO_NUM][2]; +} __aligned(8); + +#ifdef DEBUG +static void vf610_uart_reinit(unsigned long int rate, unsigned long int baud) +{ + u8 tmp, c2; + u16 sbr, brfa; + + /* UART_C2 */ + c2 = __raw_readb(uart_membase + 0x3); + __raw_writeb(0, uart_membase + 0x3); + + sbr = (u16) (rate / (baud * 16)); + brfa = (rate / baud) - (sbr * 16); + + tmp = ((sbr & 0x1f00) >> 8); + __raw_writeb(tmp, uart_membase + 0x0); + tmp = sbr & 0x00ff; + __raw_writeb(tmp, uart_membase + 0x1); + + /* UART_C4 */ + __raw_writeb(brfa & 0xf, uart_membase + 0xa); + + __raw_writeb(c2, uart_membase + 0x3); +} +#else +#define vf610_uart_reinit(rate, baud) +#endif + +static void vf610_set(void __iomem *pll_base, u32 mask) +{ + writel(readl(pll_base) | mask, pll_base); +} + +static void vf610_clr(void __iomem *pll_base, u32 mask) +{ + writel(readl(pll_base) & ~mask, pll_base); +} + +int vf610_set_lpm(enum vf610_cpu_pwr_mode mode) +{ + u32 ccr = readl_relaxed(ccm_base + CCR); + u32 ccsr = readl_relaxed(ccm_base + CCSR); + u32 cacrr = readl_relaxed(ccm_base + CACRR); + u32 cclpcr = 0; + u32 gpc_pgcr = 0; + + struct vf610_cpu_pm_info *pm_info = suspend_ocram_base; + void __iomem *gpc_base = pm_info->gpc_base.vbase; + void __iomem *anatop = pm_info->anatop_base.vbase; + + switch (mode) { + case VF610_LP_STOP: + /* Store clock settings */ + pm_info->ccm_ccsr = ccsr; + pm_info->ccm_cacrr = cacrr; + + ccr |= BM_CCR_FIRC_EN; + writel_relaxed(ccr, ccm_base + CCR); + + cclpcr |= BM_CLPCR_ANADIG_STOP_MODE; + cclpcr |= BM_CLPCR_SBYOS; + + cclpcr |= BM_CLPCR_MASK_SCU_IDLE; + cclpcr |= BM_CLPCR_MASK_L2CC_IDLE; + cclpcr |= BM_CLPCR_MASK_CORE1_WFI; + writel_relaxed(cclpcr, ccm_base + CLPCR); + + gpc_pgcr |= BM_PGCR_DS_STOP; + gpc_pgcr |= BM_PGCR_DS_LPSTOP; + gpc_pgcr |= BM_PGCR_WB_STOP; + gpc_pgcr |= BM_PGCR_HP_OFF; + gpc_pgcr |= BM_PGCR_PG_PD1; + writel_relaxed(gpc_pgcr, gpc_base + GPC_PGCR); + break; + case VF610_STOP: + cclpcr &= ~BM_CLPCR_ANADIG_STOP_MODE; + cclpcr |= BM_CLPCR_ARM_CLK_DIS_ON_LPM; + cclpcr |= BM_CLPCR_SBYOS; + writel_relaxed(cclpcr, ccm_base + CLPCR); + + gpc_pgcr |= BM_PGCR_DS_STOP; + gpc_pgcr |= BM_PGCR_HP_OFF; + writel_relaxed(gpc_pgcr, gpc_base + GPC_PGCR); + + /* fall-through */ + case VF610_LP_RUN: + /* Store clock settings */ + pm_info->ccm_ccsr = ccsr; + pm_info->ccm_cacrr = cacrr; + + ccr |= BM_CCR_FIRC_EN; + writel_relaxed(ccr, ccm_base + CCR); + + /* Enable PLL2 for DDR clock */ + vf610_set(anatop + ANATOP_PLL2_CTRL, BM_PLL_ENABLE); + vf610_clr(anatop + ANATOP_PLL2_CTRL, BM_PLL_POWERDOWN); + vf610_clr(anatop + ANATOP_PLL2_CTRL, BM_PLL_BYPASS); + while (!(readl(anatop + ANATOP_PLL2_CTRL) & BM_PLL_LOCK)); + vf610_clr(anatop + ANATOP_PLL2_PFD, BM_PLL_PFD2_CLKGATE); + + /* Switch internal OSC's */ + ccsr &= ~BM_CCSR_FAST_CLK_SEL; + ccsr &= ~BM_CCSR_SLOW_CLK_SEL; + + /* Select PLL2 as DDR clock */ + ccsr &= ~BM_CCSR_DDRC_CLK_SEL; + writel_relaxed(ccsr, ccm_base + CCSR); + + ccsr &= ~BM_CCSR_SYS_CLK_SEL_MASK; + writel_relaxed(ccsr, ccm_base + CCSR); + vf610_uart_reinit(4000000UL, 115200); + + vf610_set(anatop + ANATOP_PLL1_CTRL, BM_PLL_BYPASS); + writel_relaxed(BM_LPMR_STOP, gpc_base + GPC_LPMR); + break; + case VF610_RUN: + writel_relaxed(BM_LPMR_RUN, gpc_base + GPC_LPMR); + + vf610_clr(anatop + ANATOP_PLL1_CTRL, BM_PLL_BYPASS); + while(!(readl(anatop + ANATOP_PLL1_CTRL) & BM_PLL_LOCK)); + + /* Restore clock settings */ + writel(pm_info->ccm_ccsr, ccm_base + CCSR); + + vf610_uart_reinit(uart_clk, 115200); + pr_pmdebug("resuming, uart_reinit done"); + + /* Disable PLL2 if not needed */ + if (pm_info->ccm_ccsr & BM_CCSR_DDRC_CLK_SEL) + vf610_set(anatop + ANATOP_PLL2_CTRL, BM_PLL_POWERDOWN); + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vf610_suspend_finish(unsigned long val) +{ + if (!vf610_suspend_in_ocram_fn) { + cpu_do_idle(); + } else { + /* + * call low level suspend function in ocram, + * as we need to float DDR IO. + */ + local_flush_tlb_all(); + flush_cache_all(); + outer_flush_all(); + vf610_suspend_in_ocram_fn(suspend_ocram_base); + } + + return 0; +} + +static int vf610_pm_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_STANDBY: + vf610_set_lpm(VF610_STOP); + flush_cache_all(); + + /* zzZZZzzz */ + cpu_do_idle(); + + vf610_set_lpm(VF610_RUN); + break; + case PM_SUSPEND_MEM: + vf610_set_lpm(VF610_LP_STOP); + + cpu_suspend(0, vf610_suspend_finish); + outer_resume(); + + vf610_set_lpm(VF610_RUN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vf610_pm_valid(suspend_state_t state) +{ + return (state == PM_SUSPEND_STANDBY || + (state == PM_SUSPEND_MEM && has_cke_reset_pulls)); +} + +static const struct platform_suspend_ops vf610_pm_ops = { + .enter = vf610_pm_enter, + .valid = vf610_pm_valid, +}; + +void __init vf610_pm_set_ccm_base(void __iomem *base) +{ + ccm_base = base; +} + +static int __init imx_pm_get_base(struct vf610_pm_base *base, + const char *compat) +{ + struct device_node *node; + struct resource res; + int ret = 0; + + node = of_find_compatible_node(NULL, NULL, compat); + if (!node) { + ret = -ENODEV; + goto out; + } + + ret = of_address_to_resource(node, 0, &res); + if (ret) + goto put_node; + + base->pbase = res.start; + base->vbase = ioremap(res.start, resource_size(&res)); + + if (!base->vbase) + ret = -ENOMEM; + +put_node: + of_node_put(node); +out: + return ret; +} + +#ifdef DEBUG +static int __init vf610_uart_init(void) +{ + struct device_node *dn; + const char *name; + struct clk *clk; + int ret; + + name = of_get_property(of_chosen, "stdout-path", NULL); + if (name == NULL) + return -ENODEV; + + dn = of_find_node_by_path(name); + if (!dn) + return -ENODEV; + + clk = of_clk_get(dn, 0); + + if (!clk) { + ret = PTR_ERR(clk); + goto put_node; + } + + uart_clk = clk_get_rate(clk); + + uart_membase = of_iomap(dn, 0); + if (!clk) { + ret = -ENOMEM; + goto put_node; + } + + ret = 0; + +put_node: + of_node_put(dn); + return ret; +} +#endif + +static int __init vf610_suspend_init(const struct vf610_pm_socdata *socdata) +{ + phys_addr_t ocram_pbase; + struct device_node *node; + struct platform_device *pdev; + struct vf610_cpu_pm_info *pm_info; + struct gen_pool *ocram_pool; + unsigned long ocram_base; + int ret = 0, reg = 0; + int i; + +#ifdef DEBUG + ret = vf610_uart_init(); + if (ret < 0) + return ret; +#endif + + node = of_find_compatible_node(NULL, NULL, socdata->ddrmc_compat); + if (node) { + has_cke_reset_pulls = + of_property_read_bool(node, "fsl,has-cke-reset-pulls"); + + of_node_put(node); + } + + if (has_cke_reset_pulls) + pr_info("PM: CKE/RESET pulls available, enable Suspend-to-RAM\n"); + else + pr_info("PM: No CKE/RESET pulls, disable Suspend-to-RAM\n"); + + suspend_set_ops(&vf610_pm_ops); + + node = of_find_compatible_node(NULL, NULL, "mmio-sram"); + if (!node) { + pr_warn("%s: failed to find ocram node!\n", __func__); + return -ENODEV; + } + + pdev = of_find_device_by_node(node); + if (!pdev) { + pr_warn("%s: failed to find ocram device!\n", __func__); + ret = -ENODEV; + goto put_node; + } + + ocram_pool = dev_get_gen_pool(&pdev->dev); + if (!ocram_pool) { + pr_warn("%s: ocram pool unavailable!\n", __func__); + ret = -ENODEV; + goto put_node; + } + + ocram_base = gen_pool_alloc(ocram_pool, VF610_SUSPEND_OCRAM_SIZE); + if (!ocram_base) { + pr_warn("%s: unable to alloc ocram!\n", __func__); + ret = -ENOMEM; + goto put_node; + } + + ocram_pbase = gen_pool_virt_to_phys(ocram_pool, ocram_base); + + suspend_ocram_base = __arm_ioremap_exec(ocram_pbase, + VF610_SUSPEND_OCRAM_SIZE, false); + + pm_info = suspend_ocram_base; + pm_info->pbase = ocram_pbase; + pm_info->resume_addr = virt_to_phys(cpu_resume); + pm_info->pm_info_size = sizeof(*pm_info); + + ret = imx_pm_get_base(&pm_info->anatop_base, socdata->anatop_compat); + if (ret) { + pr_warn("%s: failed to get anatop base %d!\n", __func__, ret); + goto put_node; + } + + ret = imx_pm_get_base(&pm_info->scsc_base, socdata->scsc_compat); + if (ret) { + pr_warn("%s: failed to get scsc base %d!\n", __func__, ret); + goto scsc_map_failed; + } + + ret = imx_pm_get_base(&pm_info->ccm_base, socdata->ccm_compat); + if (ret) { + pr_warn("%s: failed to get ccm base %d!\n", __func__, ret); + goto ccm_map_failed; + } + + ret = imx_pm_get_base(&pm_info->gpc_base, socdata->gpc_compat); + if (ret) { + pr_warn("%s: failed to get gpc base %d!\n", __func__, ret); + goto gpc_map_failed; + } + + ret = imx_pm_get_base(&pm_info->src_base, socdata->src_compat); + if (ret) { + pr_warn("%s: failed to get src base %d!\n", __func__, ret); + goto src_map_failed; + } + + ret = imx_pm_get_base(&pm_info->ddrmc_base, socdata->ddrmc_compat); + if (ret) { + pr_warn("%s: failed to get ddrmc base %d!\n", __func__, ret); + goto ddrmc_map_failed; + } + + ret = imx_pm_get_base(&pm_info->iomuxc_base, socdata->iomuxc_compat); + if (ret) { + pr_warn("%s: failed to get iomuxc base %d!\n", __func__, ret); + goto iomuxc_map_failed; + } + + ret = imx_pm_get_base(&pm_info->l2_base, "arm,pl310-cache"); + if (ret && ret != -ENODEV) { + pr_warn("%s: failed to get pl310-cache base %d!\n", + __func__, ret); + goto pl310_cache_map_failed; + } + + pm_info->ddrmc_io_num = VF610_DDRMC_IO_NUM; + + /* Store DDRMC registers */ + for (i = 0; i < ARRAY_SIZE(vf610_ddrmc_io_offset); i++, reg++) { + pm_info->ddrmc_io_val[reg][0] = vf610_ddrmc_io_offset[i]; + pm_info->ddrmc_io_val[reg][1] = + readl_relaxed(pm_info->ddrmc_base.vbase + + vf610_ddrmc_io_offset[i]); + } + + /* Store DDRMC PHY registers */ + for (i = 0; i < ARRAY_SIZE(vf610_ddrmc_phy_io_offset); i++, reg++) { + pm_info->ddrmc_io_val[reg][0] = vf610_ddrmc_phy_io_offset[i] + + DDRMC_PHY_OFFSET; + pm_info->ddrmc_io_val[reg][1] = + readl_relaxed(pm_info->ddrmc_base.vbase + + DDRMC_PHY_OFFSET + vf610_ddrmc_phy_io_offset[i]); + } + + /* Store IOMUX DDR pad registers */ + pm_info->iomux_ddr_io_num = VF610_IOMUX_DDR_IO_NUM; + for (i = 0; i < ARRAY_SIZE(vf610_iomuxc_ddr_io_offset); i++) { + pm_info->iomux_ddr_io_val[i][0] = vf610_iomuxc_ddr_io_offset[i]; + pm_info->iomux_ddr_io_val[i][1] = + readl_relaxed(pm_info->iomuxc_base.vbase + + vf610_iomuxc_ddr_io_offset[i]); + } + + vf610_suspend_in_ocram_fn = fncpy( + suspend_ocram_base + sizeof(*pm_info), + &vf610_suspend, + VF610_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); + + goto put_node; + +pl310_cache_map_failed: + iounmap(&pm_info->iomuxc_base.vbase); +iomuxc_map_failed: + iounmap(&pm_info->ddrmc_base.vbase); +ddrmc_map_failed: + iounmap(&pm_info->src_base.vbase); +src_map_failed: + iounmap(&pm_info->gpc_base.vbase); +gpc_map_failed: + iounmap(&pm_info->ccm_base.vbase); +ccm_map_failed: + iounmap(&pm_info->scsc_base.vbase); +scsc_map_failed: + iounmap(&pm_info->anatop_base.vbase); +put_node: + of_node_put(node); + + return ret; +} + +void __init vf610_pm_init(void) +{ + int ret; + + WARN_ON(!ccm_base); + + if (IS_ENABLED(CONFIG_SUSPEND)) { + ret = vf610_suspend_init(&vf610_pm_data); + if (ret) + pr_warn("%s: No DDR LPM support with suspend %d!\n", + __func__, ret); + } +} + diff --git a/arch/arm/mach-imx/suspend-vf610.S b/arch/arm/mach-imx/suspend-vf610.S new file mode 100644 index 000000000000..595dd4e2c74c --- /dev/null +++ b/arch/arm/mach-imx/suspend-vf610.S @@ -0,0 +1,448 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * Copyright 2015 Toradex AG + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/asm-offsets.h> +#include <asm/hardware/cache-l2x0.h> + +/* + * ==================== low level suspend ==================== + * + * Better to follow below rules to use ARM registers: + * r0: pm_info structure address; + * r1 ~ r4: for saving pm_info members; + * r5 ~ r10: free registers; + * r11: io base address. + * + * suspend ocram space layout: + * ======================== high address ====================== + * . + * . + * . + * ^ + * ^ + * ^ + * vf610_suspend code + * PM_INFO structure(vf610_cpu_pm_info) + * ======================== low address ======================= + */ + +/* + * Below offsets are based on struct vf610_cpu_pm_info + * which defined in arch/arm/mach-imx/pm-vf610.c, this + * structure contains necessary pm info for low level + * suspend related code. + */ +#define PM_INFO_PBASE_OFFSET 0x0 +#define PM_INFO_RESUME_ADDR_OFFSET 0x4 +#define PM_INFO_CPU_TYPE_OFFSET 0x8 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0xC +#define PM_INFO_VF610_ANATOP_P_OFFSET 0x10 +#define PM_INFO_VF610_ANATOP_V_OFFSET 0x14 +#define PM_INFO_VF610_SCSC_P_OFFSET 0x18 +#define PM_INFO_VF610_SCSC_V_OFFSET 0x1C +#define PM_INFO_VF610_WKPU_P_OFFSET 0x20 +#define PM_INFO_VF610_WKPU_V_OFFSET 0x24 +#define PM_INFO_VF610_CCM_P_OFFSET 0x28 +#define PM_INFO_VF610_CCM_V_OFFSET 0x2C +#define PM_INFO_VF610_GPC_P_OFFSET 0x30 +#define PM_INFO_VF610_GPC_V_OFFSET 0x34 +#define PM_INFO_VF610_SRC_P_OFFSET 0x38 +#define PM_INFO_VF610_SRC_V_OFFSET 0x3C +#define PM_INFO_VF610_DDRMC_P_OFFSET 0x40 +#define PM_INFO_VF610_DDRMC_V_OFFSET 0x44 +#define PM_INFO_VF610_IOMUXC_P_OFFSET 0x48 +#define PM_INFO_VF610_IOMUXC_V_OFFSET 0x4c +#define PM_INFO_VF610_L2_P_OFFSET 0x50 +#define PM_INFO_VF610_L2_V_OFFSET 0x54 +#define PM_INFO_CCM_CACRR 0x58 +#define PM_INFO_CCM_CCSR 0x5c +#define PM_INFO_DDRMC_IO_NUM_OFFSET 0x60 +#define PM_INFO_DDRMC_IO_VAL_OFFSET 0x64 +#define PM_INFO_IOMUXC_DDR_IO_NUM_OFFSET (0x64 + 94 * 2 * 4) +#define PM_INFO_IOMUXC_DDR_IO_VAL_OFFSET (0x68 + 94 * 2 * 4) + +#define VF610_ANADIG_PLL2_CTRL 0x30 + +#define VF610_ANADIG_MISC0 0x150 +#define VF610_ANADIG_MISC0_CLK_24M_IRC_XTAL_SEL (0x1 < 13) + +#define VF610_ANADIG_PLL1_CTRL 0x270 + +#define VF610_ANADIG_POWERDOWN (1 << 12) +#define VF610_ANADIG_ENABLE (1 << 13) +#define VF610_ANADIG_BYPASS (1 << 16) +#define VF610_ANADIG_LOCK (1 << 31) + +#define VF610_SCSC_SIRC 0x0 +#define VF610_SCSC_SIRC_SIRC_EN (0x1 << 0) +#define VF610_SCSC_SOSC 0x4 +#define VF610_SCSC_SOSC_SOSC_EN (0x1 << 0) + +#define VF610_GPC_PGCR 0x0 +#define VF610_GPC_LPMR 0x40 + +#define VF610_CCM_CCR 0x00 +#define VF610_CCM_CCR_FXOSC_EN (0x1 << 12) + +#define VF610_CCM_CCSR 0x08 +#define VF610_CCM_CCSR_DDRC_CLK_SEL (0x1 << 6) +#define VF610_CCM_CCSR_FAST_CLK_SEL (0x1 << 5) + +#define VF610_CCM_CACRR 0x0C + +#define VF610_CCM_CLPCR 0x2C +#define VF610_CCM_CLPCR_DIS_REF_OSC (0x1 << 7) +#define VF610_CCM_CLPCR_FXOSC_PWRDWN (0x1 << 11) + +#define VF610_CCM_CCGR0 0x40 +#define VF610_CCM_CCGR2 0x48 +#define VF610_CCM_CCGR3 0x4C +#define VF610_CCM_CCGR4 0x50 +#define VF610_CCM_CCGR6 0x58 + +#define VF610_SRC_GPR0 0x20 +#define VF610_SRC_GPR1 0x24 +#define VF610_SRC_MISC2 0x54 + +#define VF610_DDRMC_CR00 0x0 +#define VF610_DDRMC_CR00_START (0x1 << 0) + +#define VF610_DDRMC_CR33 0x84 +#define VF610_DDRMC_CR33_PWUP_SREF_EX (0x1 << 0) + +#define VF610_DDRMC_CR34 0x88 + +#define VF610_DDRMC_CR35 0x8C +#define VF610_DDRMC_CR35_LP_CMD(cmd) ((cmd) << 8) + +#define VF610_DDRMC_CR80 0x140 +#define VF610_DDRMC_CR80_LP_COMPLETE (0x1 << 9) +#define VF610_DDRMC_CR80_INIT_COMPLETE (0x1 << 8) +#define VF610_DDRMC_CR81 0x144 + + .align 3 + + /* + * Take DDR RAM out of Low-Power mode + */ + .macro resume_ddrmc ddrmc_base + + /* Clear low power complete flag... */ + ldr r6, =VF610_DDRMC_CR80_LP_COMPLETE + str r6, [\ddrmc_base, #VF610_DDRMC_CR81] + + ldr r6, [\ddrmc_base, #VF610_DDRMC_CR35] + orr r6, r6, #VF610_DDRMC_CR35_LP_CMD(0x9) + str r6, [\ddrmc_base, #VF610_DDRMC_CR35] + +1: + ldr r5, [\ddrmc_base, #VF610_DDRMC_CR80] + ands r5, r5, #VF610_DDRMC_CR80_LP_COMPLETE + beq 1b + + .endm + + .macro enable_syspll pll_base + + ldr r5, [\pll_base] + orr r5, r5, #VF610_ANADIG_ENABLE + bic r5, r5, #VF610_ANADIG_POWERDOWN + bic r5, r5, #VF610_ANADIG_BYPASS + str r5, [\pll_base] + +1: + ldr r5, [\pll_base] + tst r5, #VF610_ANADIG_LOCK + beq 1b + + .endm + +ENTRY(vf610_suspend) + ldr r1, [r0, #PM_INFO_PBASE_OFFSET] + ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + ldr r3, [r0, #PM_INFO_CPU_TYPE_OFFSET] + ldr r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + + /* + * make sure TLB contain the addr we want, + * as we will access them after MMDC IO floated. + */ + + ldr r11, [r0, #PM_INFO_VF610_DDRMC_V_OFFSET] + ldr r6, [r11, #0x0] + ldr r11, [r0, #PM_INFO_VF610_GPC_V_OFFSET] + ldr r6, [r11, #0x0] + ldr r11, [r0, #PM_INFO_VF610_SRC_V_OFFSET] + ldr r6, [r11, #0x0] + ldr r11, [r0, #PM_INFO_VF610_CCM_V_OFFSET] + ldr r6, [r11, #0x0] + + ldr r11, [r0, #PM_INFO_VF610_SRC_V_OFFSET] + + /* Disable DDR RESET */ + ldr r6, [r11, #VF610_SRC_MISC2] + orr r6, r6, #0x1 + str r6, [r11, #VF610_SRC_MISC2] + + /* Set ENTRY/ARGUMENT register */ + ldr r6, =vf610_suspend + ldr r7, =resume + sub r7, r7, r6 + add r8, r1, r4 + add r9, r8, r7 + str r9, [r11, #VF610_SRC_GPR0] + str r1, [r11, #VF610_SRC_GPR1] + + /* Put memory in self refresh... */ + ldr r11, [r0, #PM_INFO_VF610_DDRMC_V_OFFSET] + + ldr r6, =VF610_DDRMC_CR80_LP_COMPLETE + str r6, [r11, #VF610_DDRMC_CR81] + + ldr r6, [r11, #VF610_DDRMC_CR35] + orr r6, r6, #VF610_DDRMC_CR35_LP_CMD(0xA) + str r6, [r11, #VF610_DDRMC_CR35] + +ddrmc_cmd_complete: + /* A Unfixed module seems to hang at this read.... */ + ldr r5, [r11, #VF610_DDRMC_CR80] + ands r5, r5, #VF610_DDRMC_CR80_LP_COMPLETE + beq ddrmc_cmd_complete + + /* switch to internal FIRC */ + ldr r11, [r0, #PM_INFO_VF610_CCM_V_OFFSET] + ldr r5, [r11, #VF610_CCM_CCSR] + bic r5, r5, #0x30 /* FAST_/SLOW_CLK_SEL */ + str r5, [r11, #VF610_CCM_CCSR] + bic r5, r5, #0x07 /* SYS_CLK_SEL */ + str r5, [r11, #VF610_CCM_CCSR] + + /* LP-Mode: STOP */ + ldr r11, [r0, #PM_INFO_VF610_GPC_V_OFFSET] + ldr r6, =0x02 + str r6, [r11, #VF610_GPC_LPMR] + + /* Zzz, enter stop mode */ + wfi + nop + nop + nop + nop + + /* If we get here, there is already an interrupt pending. Restore... */ + ldr r6, =0x00 + str r6, [r11, #VF610_GPC_LPMR] + + /* Get previous CCSR/CACRR settings */ + ldr r11, [r0, #PM_INFO_VF610_CCM_V_OFFSET] + ldr r5, [r0, #PM_INFO_CCM_CCSR] + str r5, [r11, #VF610_CCM_CCSR] + + ldr r5, [r0, #PM_INFO_CCM_CACRR] + str r5, [r11, #VF610_CCM_CACRR] + + ldr r11, [r0, #PM_INFO_VF610_DDRMC_V_OFFSET] + resume_ddrmc r11 + + ret lr + +/* Resume path if CPU uses the SRC_GPR0 (PERSISTENT_ENTRY0) */ +resume: + /* invalidate L1 I-cache first */ + mov r6, #0x0 + mcr p15, 0, r6, c7, c5, 0 + mcr p15, 0, r6, c7, c5, 6 + + /* enable the Icache and branch prediction */ + mov r6, #0x1800 + mcr p15, 0, r6, c1, c0, 0 + isb + + ldr r11, [r0, #PM_INFO_VF610_CCM_P_OFFSET] + + ldr r5, [r11, #VF610_CCM_CCSR] + orr r5, r5, #(1 << 13) + str r5, [r11, #VF610_CCM_CCSR] + + /* enable UART0 */ + ldr r5, [r11, #VF610_CCM_CCGR0] + orr r5, r5, #0xC000 + str r5, [r11, #VF610_CCM_CCGR0] + + /* enable IOMUX, PORT A-E */ + ldr r5, [r11, #VF610_CCM_CCGR2] + ldr r6, =0xFFF0000 + orr r5, r5, r6 + str r5, [r11, #VF610_CCM_CCGR2] + + /* enable ANADIG and SCSM */ + ldr r5, [r11, #VF610_CCM_CCGR3] + orr r5, r5, #0x33 + str r5, [r11, #VF610_CCM_CCGR3] + + /* enable GPC, CCM and WKUP */ + ldr r5, [r11, #VF610_CCM_CCGR4] + orr r5, r5, #0x3f00000 + str r5, [r11, #VF610_CCM_CCGR4] + + /* enable mmdc */ + ldr r5, [r11, #VF610_CCM_CCGR6] + orr r5, r5, #0x30000000 + str r5, [r11, #VF610_CCM_CCGR6] + + /* Mux UART0 */ + ldr r5,=0x1021a2 + ldr r6,=0x40048080 + str r5, [r6, #0x0] + ldr r5,=0x1021a1 + ldr r6,=0x40048084 + str r5, [r6, #0x0] + + /* Set IOMUX for DDR pads */ + ldr r11, [r0, #PM_INFO_VF610_IOMUXC_P_OFFSET] + + ldr r6, [r0, #PM_INFO_IOMUXC_DDR_IO_NUM_OFFSET] + ldr r7, =PM_INFO_IOMUXC_DDR_IO_VAL_OFFSET + add r7, r7, r0 + +loop_iomuxc_ddr_restore: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r11, r8] + subs r6, r6, #0x1 + bne loop_iomuxc_ddr_restore + + + /* Enable slow oscilators */ + ldr r11, [r0, #PM_INFO_VF610_SCSC_P_OFFSET] + + ldr r5, [r11, #VF610_SCSC_SOSC] + orr r5, r5, #VF610_SCSC_SOSC_SOSC_EN + str r5, [r11, #VF610_SCSC_SOSC] + + ldr r5, [r11, #VF610_SCSC_SIRC] + orr r5, r5, #VF610_SCSC_SIRC_SIRC_EN + str r5, [r11, #VF610_SCSC_SIRC] + + /* Enable fast osciallator */ + ldr r11, [r0, #PM_INFO_VF610_CCM_P_OFFSET] + + ldr r5, [r11, #VF610_CCM_CLPCR] + bic r5, r5, #VF610_CCM_CLPCR_DIS_REF_OSC + bic r5, r5, #VF610_CCM_CLPCR_FXOSC_PWRDWN + str r5, [r11, #VF610_CCM_CLPCR] + + ldr r5, [r11, #VF610_CCM_CCR] + orr r5, r5, #VF610_CCM_CCR_FXOSC_EN + str r5, [r11, #VF610_CCM_CCR] + + ldr r5, [r11, #VF610_CCM_CCSR] + orr r5, r5, #VF610_CCM_CCSR_FAST_CLK_SEL + str r5, [r11, #VF610_CCM_CCSR] + + ldr r11, [r0, #PM_INFO_VF610_ANATOP_P_OFFSET] + + /* Select external FXOSC */ + ldr r5, [r11, #VF610_ANADIG_MISC0] + bic r5, r5, #VF610_ANADIG_MISC0_CLK_24M_IRC_XTAL_SEL + str r5, [r11, #VF610_ANADIG_MISC0] + + /* pll1 enable */ + add r6, r11, #VF610_ANADIG_PLL1_CTRL + enable_syspll r6 + + /* enable pll2 only if required for DDR */ + ldr r5, [r0, #PM_INFO_CCM_CCSR] + tst r5, #VF610_CCM_CCSR_DDRC_CLK_SEL + bne switch_sysclk + + /* pll2 enable */ + add r6, r11, #VF610_ANADIG_PLL2_CTRL + enable_syspll r6 + +switch_sysclk: + + /* Enable PFD and switch to fast clock */ + ldr r11, [r0, #PM_INFO_VF610_CCM_P_OFFSET] + + /* Get previous CCSR/CACRR settings */ + ldr r5, [r0, #PM_INFO_CCM_CCSR] + str r5, [r11, #VF610_CCM_CCSR] + + ldr r5, [r0, #PM_INFO_CCM_CACRR] + str r5, [r11, #VF610_CCM_CACRR] + + /* Restore memory configuration */ + ldr r11, [r0, #PM_INFO_VF610_DDRMC_P_OFFSET] + + ldr r6, [r0, #PM_INFO_DDRMC_IO_NUM_OFFSET] + ldr r7, =PM_INFO_DDRMC_IO_VAL_OFFSET + add r7, r7, r0 + + /* Clear start bit of first memory register, do not start yet... */ + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + bic r9, r9, #VF610_DDRMC_CR00_START + str r9, [r11, r8] + subs r6, r6, #0x1 + +loop_ddrmc_restore: + ldr r8, [r7], #0x4 + ldr r9, [r7], #0x4 + str r9, [r11, r8] + subs r6, r6, #0x1 + bne loop_ddrmc_restore + + /* Set PWUP_SREF_EX to avoid a full memory initialization */ + ldr r6, [r11, #VF610_DDRMC_CR33] + orr r6, r6, #VF610_DDRMC_CR33_PWUP_SREF_EX + str r6, [r11, #VF610_DDRMC_CR33] + + /* Start initialization */ + ldr r6, =VF610_DDRMC_CR80_INIT_COMPLETE + str r6, [r11, #VF610_DDRMC_CR81] + + ldr r6, [r11, #VF610_DDRMC_CR00] + orr r6, r6, #VF610_DDRMC_CR00_START + str r6, [r11, #VF610_DDRMC_CR00] + +ddrmc_initializing: + ldr r5, [r11, #VF610_DDRMC_CR80] + ands r5, r5, #VF610_DDRMC_CR80_INIT_COMPLETE + beq ddrmc_initializing + + resume_ddrmc r11 + + /* LP-Mode: RUN */ + ldr r11, [r0, #PM_INFO_VF610_GPC_P_OFFSET] + ldr r5, =0x0 + str r5, [r11, #VF610_GPC_LPMR] + + /* Enable SNVS */ + ldr r3, [r0, #PM_INFO_VF610_CCM_P_OFFSET] + ldr r4, [r3, #VF610_CCM_CCGR6] + orr r4, r4, #0x0000C000 + str r4, [r3, #VF610_CCM_CCGR6] + + /* Enable SNVS access (RTC) */ + ldr r11, =0x400a7000 + ldr r4, =0x80000100 + str r4, [r11, #0x4] + + /* get physical resume address from pm_info. */ + ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] + + ret lr +ENDPROC(vf610_suspend) + diff --git a/arch/arm/mach-imx/vf610_sema4.c b/arch/arm/mach-imx/vf610_sema4.c new file mode 100644 index 000000000000..9b4d7a74a8ab --- /dev/null +++ b/arch/arm/mach-imx/vf610_sema4.c @@ -0,0 +1,314 @@ +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/sched.h> + +#include <linux/mm.h> +#include <linux/version.h> +#include <linux/kdev_t.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include "hardware.h" +#include <linux/string.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <asm/io.h> +#include <linux/debugfs.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> + +#include <linux/vf610_sema4.h> +// ************************************ Local Data ************************************************* + +#define NUM_GATES 16 +#define MASK_FROM_GATE(gate_num) ((u32)(1 << (NUM_GATES*2 - 1 - idx[gate_num]))) + +#define THIS_CORE (0) +#define LOCK_VALUE (THIS_CORE + 1) + +#define SEMA4_CP0INE (0x40) +#define SEMA4_CP0NTF (0x80) + +static MVF_SEMA4* gates[NUM_GATES]; +static void __iomem *sema4_base; + +// account for the way the bits are set / returned in CP0INE and CP0NTF +static const int idx[16] = {3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12}; + +// debugfs +#define DEBUGFS_DIR "mvf_sema4" +static struct dentry *debugfs_dir; + +// ************************************ Interrupt handler ************************************************* + +static irqreturn_t sema4_irq_handler(int irq, void *dev_id) +{ + int gate_num; + + u32 cp0ntf = readl(sema4_base + SEMA4_CP0NTF); + + for(gate_num=0; gate_num<NUM_GATES; gate_num++) + { + // interrupt from this gate? + if(cp0ntf & MASK_FROM_GATE(gate_num)) + { + // grab the gate to stop the interrupts + writeb(LOCK_VALUE, sema4_base + gate_num); + + // make sure there's a gate assigned + if(gates[gate_num]) + { + //if(gates[gate_num]->use_interrupts) { + // wake up whoever was aiting + wake_up_interruptible(&(gates[gate_num]->wait_queue)); + // bump stats + gates[gate_num]->interrupts++; + //} + } + } + } + + return IRQ_HANDLED; +} + +// ************************************ Utility functions ************************************************* + +int mvf_sema4_assign(int gate_num, MVF_SEMA4** sema4_p) +{ + u32 cp0ine; + unsigned long irq_flags; + char debugfs_gatedir_name[4]; + struct dentry *debugfs_gate_dir; + + if((gate_num < 0) || (gate_num >= NUM_GATES)) + return -EINVAL; + + if(gates[gate_num]) + return -EBUSY; + + *sema4_p = (MVF_SEMA4 *)kmalloc(sizeof(MVF_SEMA4), GFP_KERNEL); + if(*sema4_p == NULL) + return -ENOMEM; + memset(*sema4_p, 0, sizeof(MVF_SEMA4)); + + gates[gate_num] = *sema4_p; + (*sema4_p)->gate_num = gate_num; + + init_waitqueue_head(&((*sema4_p)->wait_queue)); + local_irq_save(irq_flags); + cp0ine = readl(sema4_base + SEMA4_CP0INE); + cp0ine |= MASK_FROM_GATE(gate_num); + writel(cp0ine, sema4_base + SEMA4_CP0INE); + local_irq_restore(irq_flags); + + // debugfs + sprintf(debugfs_gatedir_name, "%d", gate_num); + debugfs_gate_dir = debugfs_create_dir(debugfs_gatedir_name, debugfs_dir); + debugfs_create_u32("attempts", S_IRUGO | S_IWUGO, debugfs_gate_dir, &(*sema4_p)->attempts); + debugfs_create_u32("interrupts", S_IRUGO | S_IWUGO, debugfs_gate_dir, &(*sema4_p)->interrupts); + debugfs_create_u32("failures", S_IRUGO | S_IWUGO, debugfs_gate_dir, &(*sema4_p)->failures); + debugfs_create_u64("total_latency_us", S_IRUGO | S_IWUGO, debugfs_gate_dir, &(*sema4_p)->total_latency_us); + debugfs_create_u32("worst_latency_us", S_IRUGO | S_IWUGO, debugfs_gate_dir, &(*sema4_p)->worst_latency_us); + + return 0; +} +EXPORT_SYMBOL(mvf_sema4_assign); + +int mvf_sema4_deassign(MVF_SEMA4 *sema4) +{ + u32 cp0ine; + unsigned long irq_flags; + + int gate_num; + if(!sema4) + return -EINVAL; + gate_num = sema4->gate_num; + + local_irq_save(irq_flags); + cp0ine = readl(sema4_base + SEMA4_CP0INE); + cp0ine &= ~MASK_FROM_GATE(gate_num); + writel(cp0ine, sema4_base + SEMA4_CP0INE); + local_irq_restore(irq_flags); + + kfree(sema4); + gates[gate_num] = NULL; + + return 0; +} +EXPORT_SYMBOL(mvf_sema4_deassign); + +static long delta_time(struct timeval *start) { + + struct timeval now; + long now_us, start_us; + + do_gettimeofday(&now); + + now_us = (now.tv_sec * 1000000) + now.tv_usec; + start_us = (start->tv_sec * 1000000) + start->tv_usec; + + return now_us > start_us ? now_us - start_us : 0; +} + +static void add_latency_stat(MVF_SEMA4 *sema4) { + + long latency = delta_time(&sema4->request_time); + + sema4->total_latency_us += latency; + sema4->worst_latency_us = sema4->worst_latency_us < latency ? latency : sema4->worst_latency_us; +} + +int mvf_sema4_lock(MVF_SEMA4 *sema4, unsigned int timeout_us, bool use_interrupts) +{ + int retval; + int gate_num; + if(!sema4) + return -EINVAL; + gate_num = sema4->gate_num; + + // bump stats + gates[gate_num]->attempts++; + do_gettimeofday(&gates[gate_num]->request_time); + + // try to grab it + writeb(LOCK_VALUE, sema4_base + gate_num); + if(readb(sema4_base + gate_num) == LOCK_VALUE) { + add_latency_stat(gates[gate_num]); + return 0; + } + + // no timeout, fail + if(!timeout_us) { + gates[gate_num]->failures++; + return -EBUSY; + } + + // spin lock? + if(!use_interrupts) { + while(readb(sema4_base + gate_num) != LOCK_VALUE) { + + if((timeout_us != 0xffffffff) && (delta_time(&gates[gate_num]->request_time) > timeout_us)) { + gates[gate_num]->failures++; + return -EBUSY; + } + + writeb(LOCK_VALUE, sema4_base + gate_num); + } + add_latency_stat(gates[gate_num]); + return 0; + } + + // wait forever? + if(timeout_us == 0xffffffff) + { + if(wait_event_interruptible(sema4->wait_queue, (readb(sema4_base + gate_num) == LOCK_VALUE))) { + gates[gate_num]->failures++; + return -ERESTARTSYS; + } + } + else + { + // return: 0 = timeout, >0 = woke up with that many jiffies left, <0 = error + retval = wait_event_interruptible_timeout(sema4->wait_queue, + (readb(sema4_base + gate_num) == LOCK_VALUE), + usecs_to_jiffies(timeout_us)); + if(retval == 0) { + gates[gate_num]->failures++; + return -ETIME; + } + else if(retval < 0) { + gates[gate_num]->failures++; + return retval; + } + } + + add_latency_stat(gates[gate_num]); + return 0; +} +EXPORT_SYMBOL(mvf_sema4_lock); + +int mvf_sema4_unlock(MVF_SEMA4 *sema4) +{ + if(!sema4) + return -EINVAL; + + // unlock it + writeb(0, sema4_base + sema4->gate_num); + + return 0; +} +EXPORT_SYMBOL(mvf_sema4_unlock); + +// return 0 on success (meaning it is set to us) +int mvf_sema4_test(MVF_SEMA4 *sema4) +{ + if(!sema4) + return -EINVAL; + + return (readb(sema4_base + sema4->gate_num)) == LOCK_VALUE ? 0 : 1; +} +EXPORT_SYMBOL(mvf_sema4_test); + +static int vf610_sema4_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *iores; + int irq, ret, i; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sema4_base = devm_ioremap_resource(dev, iores); + if (IS_ERR(sema4_base)) + return PTR_ERR(sema4_base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq specified\n"); + return irq; + } + + ret = devm_request_irq(dev, irq, sema4_irq_handler, 0, "SEMA4", NULL); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u\n", irq); + return ret; + } + + // clear the gates table + for (i = 0; i < NUM_GATES; i++) + gates[i] = NULL; + + writel_relaxed(0, sema4_base + SEMA4_CP0INE); + + // debugfs + debugfs_dir = debugfs_create_dir(DEBUGFS_DIR, NULL); + + return 0; +} + +static const struct of_device_id vf610_sema4_dt_ids[] = { + { .compatible = "fsl,vf610-sema4" }, + { /* sentinel */ } +}; + +static struct platform_driver vf610_sema4_compat_driver = { + .driver = { + .name = "vf610-sema4", + .owner = THIS_MODULE, + .of_match_table = vf610_sema4_dt_ids, + }, + .probe = vf610_sema4_probe, +}; + +static int __init vf610_sema4_compat_init(void) +{ + return platform_driver_register(&vf610_sema4_compat_driver); +} +device_initcall(vf610_sema4_compat_init); + +MODULE_DESCRIPTION("Freescale SEMA4 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/vf_pit_timer.c b/drivers/clocksource/vf_pit_timer.c index b45ac6229b57..6608ad7ec278 100644 --- a/drivers/clocksource/vf_pit_timer.c +++ b/drivers/clocksource/vf_pit_timer.c @@ -36,6 +36,7 @@ static void __iomem *clksrc_base; static void __iomem *clkevt_base; static unsigned long cycle_per_jiffy; +static void __iomem *timer_base; static inline void pit_timer_enable(void) { @@ -57,12 +58,17 @@ static u64 pit_read_sched_clock(void) return ~__raw_readl(clksrc_base + PITCVAL); } -static int __init pit_clocksource_init(unsigned long rate) +static void pit_load_and_start_clocksource(int clksrc_ldval) { - /* set the max load value and start the clock source counter */ __raw_writel(0, clksrc_base + PITTCTRL); - __raw_writel(~0UL, clksrc_base + PITLDVAL); + __raw_writel(clksrc_ldval, clksrc_base + PITLDVAL); __raw_writel(PITTCTRL_TEN, clksrc_base + PITTCTRL); +} + +static int __init pit_clocksource_init(unsigned long rate) +{ + /* set the max load value and start the clock source counter */ + pit_load_and_start_clocksource(~0UL); sched_clock_register(pit_read_sched_clock, 32, rate); return clocksource_mmio_init(clksrc_base + PITCVAL, "vf-pit", rate, @@ -122,12 +128,29 @@ static irqreturn_t pit_timer_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } +static unsigned int src_ldval; + +static void pit_resume(struct clock_event_device *evt) +{ + /* Enable the PIT module on resume */ + __raw_writel(~PITMCR_MDIS, timer_base + PITMCR); + + pit_load_and_start_clocksource(src_ldval); +} + +static void pit_suspend(struct clock_event_device *evt) +{ + src_ldval = __raw_readl(clksrc_base + PITLDVAL); +} + static struct clock_event_device clockevent_pit = { .name = "VF pit timer", .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, .set_mode = pit_set_mode, .set_next_event = pit_set_next_event, .rating = 300, + .resume = pit_resume, + .suspend = pit_suspend, }; static struct irqaction pit_timer_irq = { @@ -162,7 +185,6 @@ static int __init pit_clockevent_init(unsigned long rate, int irq) static void __init pit_timer_init(struct device_node *np) { struct clk *pit_clk; - void __iomem *timer_base; unsigned long clk_rate; int irq; diff --git a/drivers/dma/fsl-edma.c b/drivers/dma/fsl-edma.c index 09e2842d15ec..cf8b06cd0c83 100644 --- a/drivers/dma/fsl-edma.c +++ b/drivers/dma/fsl-edma.c @@ -116,6 +116,10 @@ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | \ BIT(DMA_SLAVE_BUSWIDTH_8_BYTES) +enum fsl_edma_pm_state { + RUNNING = 0, + SUSPENDED, +}; struct fsl_edma_hw_tcd { __le32 saddr; @@ -147,6 +151,9 @@ struct fsl_edma_slave_config { struct fsl_edma_chan { struct virt_dma_chan vchan; enum dma_status status; + enum fsl_edma_pm_state pm_state; + bool idle; + u32 slave_id; struct fsl_edma_engine *edma; struct fsl_edma_desc *edesc; struct fsl_edma_slave_config fsc; @@ -298,6 +305,7 @@ static int fsl_edma_terminate_all(struct dma_chan *chan) spin_lock_irqsave(&fsl_chan->vchan.lock, flags); fsl_edma_disable_request(fsl_chan); fsl_chan->edesc = NULL; + fsl_chan->idle = true; vchan_get_all_descriptors(&fsl_chan->vchan, &head); spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); vchan_dma_desc_free_list(&fsl_chan->vchan, &head); @@ -313,6 +321,7 @@ static int fsl_edma_pause(struct dma_chan *chan) if (fsl_chan->edesc) { fsl_edma_disable_request(fsl_chan); fsl_chan->status = DMA_PAUSED; + fsl_chan->idle = true; } spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); return 0; @@ -327,6 +336,7 @@ static int fsl_edma_resume(struct dma_chan *chan) if (fsl_chan->edesc) { fsl_edma_enable_request(fsl_chan); fsl_chan->status = DMA_IN_PROGRESS; + fsl_chan->idle = false; } spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); return 0; @@ -648,6 +658,7 @@ static void fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan) fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd); fsl_edma_enable_request(fsl_chan); fsl_chan->status = DMA_IN_PROGRESS; + fsl_chan->idle = false; } static irqreturn_t fsl_edma_tx_handler(int irq, void *dev_id) @@ -676,6 +687,7 @@ static irqreturn_t fsl_edma_tx_handler(int irq, void *dev_id) vchan_cookie_complete(&fsl_chan->edesc->vdesc); fsl_chan->edesc = NULL; fsl_chan->status = DMA_COMPLETE; + fsl_chan->idle = true; } else { vchan_cyclic_callback(&fsl_chan->edesc->vdesc); } @@ -704,6 +716,7 @@ static irqreturn_t fsl_edma_err_handler(int irq, void *dev_id) edma_writeb(fsl_edma, EDMA_CERR_CERR(ch), fsl_edma->membase + EDMA_CERR); fsl_edma->chans[ch].status = DMA_ERROR; + fsl_edma->chans[ch].idle = true; } } return IRQ_HANDLED; @@ -724,6 +737,12 @@ static void fsl_edma_issue_pending(struct dma_chan *chan) spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + if (unlikely(fsl_chan->pm_state != RUNNING)) { + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + /* cannot submit due to suspend */ + return; + } + if (vchan_issue_pending(&fsl_chan->vchan) && !fsl_chan->edesc) fsl_edma_xfer_desc(fsl_chan); @@ -735,6 +754,7 @@ static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec, { struct fsl_edma_engine *fsl_edma = ofdma->of_dma_data; struct dma_chan *chan, *_chan; + struct fsl_edma_chan *fsl_chan; unsigned long chans_per_mux = fsl_edma->n_chans / DMAMUX_NR; if (dma_spec->args_count != 2) @@ -748,8 +768,10 @@ static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec, chan = dma_get_slave_channel(chan); if (chan) { chan->device->privatecnt++; - fsl_edma_chan_mux(to_fsl_edma_chan(chan), - dma_spec->args[1], true); + fsl_chan = to_fsl_edma_chan(chan); + fsl_chan->slave_id = dma_spec->args[1]; + fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, + true); mutex_unlock(&fsl_edma->fsl_edma_mutex); return chan; } @@ -881,10 +903,6 @@ static int fsl_edma_probe(struct platform_device *pdev) } - ret = fsl_edma_irq_init(pdev, fsl_edma); - if (ret) - return ret; - fsl_edma->big_endian = of_property_read_bool(np, "big-endian"); INIT_LIST_HEAD(&fsl_edma->dma_dev.channels); @@ -892,7 +910,9 @@ static int fsl_edma_probe(struct platform_device *pdev) struct fsl_edma_chan *fsl_chan = &fsl_edma->chans[i]; fsl_chan->edma = fsl_edma; - + fsl_chan->pm_state = RUNNING; + fsl_chan->slave_id = 0; + fsl_chan->idle = true; fsl_chan->vchan.desc_free = fsl_edma_free_desc; vchan_init(&fsl_chan->vchan, &fsl_edma->dma_dev); @@ -900,6 +920,11 @@ static int fsl_edma_probe(struct platform_device *pdev) fsl_edma_chan_mux(fsl_chan, 0, false); } + edma_writel(fsl_edma, ~0, fsl_edma->membase + EDMA_INTR); + ret = fsl_edma_irq_init(pdev, fsl_edma); + if (ret) + return ret; + dma_cap_set(DMA_PRIVATE, fsl_edma->dma_dev.cap_mask); dma_cap_set(DMA_SLAVE, fsl_edma->dma_dev.cap_mask); dma_cap_set(DMA_CYCLIC, fsl_edma->dma_dev.cap_mask); @@ -958,6 +983,58 @@ static int fsl_edma_remove(struct platform_device *pdev) return 0; } +static int fsl_edma_suspend_late(struct device *dev) +{ + struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev); + struct fsl_edma_chan *fsl_chan; + unsigned long flags; + int i; + + for (i = 0; i < fsl_edma->n_chans; i++) { + fsl_chan = &fsl_edma->chans[i]; + spin_lock_irqsave(&fsl_chan->vchan.lock, flags); + /* Make sure chan is idle or can not into suspend. */ + if (unlikely(!fsl_chan->idle)) + goto out; + fsl_chan->pm_state = SUSPENDED; + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + } + return 0; + +out: + for (; i >= 0; i--) { + fsl_chan = &fsl_edma->chans[i]; + fsl_chan->pm_state = RUNNING; + spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); + } + return -EBUSY; +} + +static int fsl_edma_resume_early(struct device *dev) +{ + struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev); + struct fsl_edma_chan *fsl_chan; + int i; + + for (i = 0; i < fsl_edma->n_chans; i++) { + fsl_chan = &fsl_edma->chans[i]; + fsl_chan->pm_state = RUNNING; + edma_writew(fsl_edma, 0x0, fsl_edma->membase + EDMA_TCD_CSR(i)); + if (fsl_chan->slave_id != 0) + fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, true); + } + + edma_writel(fsl_edma, EDMA_CR_ERGA | EDMA_CR_ERCA, + fsl_edma->membase + EDMA_CR); + + return 0; +} + +static const struct dev_pm_ops fsl_edma_pm_ops = { + .suspend_late = fsl_edma_suspend_late, + .resume_early = fsl_edma_resume_early, +}; + static const struct of_device_id fsl_edma_dt_ids[] = { { .compatible = "fsl,vf610-edma", }, { /* sentinel */ } @@ -968,6 +1045,7 @@ static struct platform_driver fsl_edma_driver = { .driver = { .name = "fsl-edma", .of_match_table = fsl_edma_dt_ids, + .pm = &fsl_edma_pm_ops, }, .probe = fsl_edma_probe, .remove = fsl_edma_remove, diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index 7bd9f209ffa8..bd6b30d181f0 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -36,7 +36,10 @@ struct vf610_gpio_port { void __iomem *base; void __iomem *gpio_base; u8 irqc[VF610_GPIO_PER_PORT]; + struct platform_device *pdev_wkpu; + s8 fsl_wakeup[VF610_GPIO_PER_PORT]; int irq; + u32 state; }; #define GPIO_PDOR 0x00 @@ -60,11 +63,26 @@ struct vf610_gpio_port { #define PORT_INT_EITHER_EDGE 0xb #define PORT_INT_LOGIC_ONE 0xc +#define WKPU_WISR 0x14 +#define WKPU_IRER 0x18 +#define WKPU_WRER 0x1c +#define WKPU_WIREER 0x28 +#define WKPU_WIFEER 0x2c +#define WKPU_WIFER 0x30 +#define WKPU_WIPUER 0x34 + +static struct irq_chip vf610_gpio_irq_chip; + static const struct of_device_id vf610_gpio_dt_ids[] = { { .compatible = "fsl,vf610-gpio" }, { /* sentinel */ } }; +static const struct of_device_id vf610_wkpu_dt_ids[] = { + { .compatible = "fsl,vf610-wkpu" }, + { /* sentinel */ } +}; + static inline void vf610_gpio_writel(u32 val, void __iomem *reg) { writel_relaxed(val, reg); @@ -149,8 +167,26 @@ static void vf610_gpio_irq_ack(struct irq_data *d) static int vf610_gpio_irq_set_type(struct irq_data *d, u32 type) { struct vf610_gpio_port *port = irq_data_get_irq_chip_data(d); + s8 wkpu_gpio = port->fsl_wakeup[d->hwirq]; u8 irqc; + if (wkpu_gpio >= 0) { + void __iomem *base = platform_get_drvdata(port->pdev_wkpu); + u32 wireer, wifeer; + u32 mask = 1 << wkpu_gpio; + + wireer = vf610_gpio_readl(base + WKPU_WIREER) & ~mask; + wifeer = vf610_gpio_readl(base + WKPU_WIFEER) & ~mask; + + if (type & IRQ_TYPE_EDGE_RISING) + wireer |= mask; + if (type & IRQ_TYPE_EDGE_FALLING) + wifeer |= mask; + + vf610_gpio_writel(wireer, base + WKPU_WIREER); + vf610_gpio_writel(wifeer, base + WKPU_WIFEER); + } + switch (type) { case IRQ_TYPE_EDGE_RISING: irqc = PORT_INT_RISING_EDGE; @@ -173,6 +209,11 @@ static int vf610_gpio_irq_set_type(struct irq_data *d, u32 type) port->irqc[d->hwirq] = irqc; + if (type & IRQ_TYPE_LEVEL_MASK) + __irq_set_handler_locked(d->irq, handle_level_irq); + else + __irq_set_handler_locked(d->irq, handle_edge_irq); + return 0; } @@ -196,6 +237,29 @@ static void vf610_gpio_irq_unmask(struct irq_data *d) static int vf610_gpio_irq_set_wake(struct irq_data *d, u32 enable) { struct vf610_gpio_port *port = irq_data_get_irq_chip_data(d); + s8 wkpu_gpio = port->fsl_wakeup[d->hwirq]; + + if (wkpu_gpio >= 0) { + void __iomem *base = NULL; + u32 wrer, irer; + + base = platform_get_drvdata(port->pdev_wkpu); + + /* WKPU wakeup flag for LPSTOPx modes... */ + wrer = vf610_gpio_readl(base + WKPU_WRER); + irer = vf610_gpio_readl(base + WKPU_IRER); + + if (enable) { + wrer |= 1 << wkpu_gpio; + irer |= 1 << wkpu_gpio; + } else { + wrer &= ~(1 << wkpu_gpio); + irer &= ~(1 << wkpu_gpio); + } + + vf610_gpio_writel(wrer, base + WKPU_WRER); + vf610_gpio_writel(irer, base + WKPU_IRER); + } if (enable) enable_irq_wake(port->irq); @@ -214,6 +278,77 @@ static struct irq_chip vf610_gpio_irq_chip = { .irq_set_wake = vf610_gpio_irq_set_wake, }; +static int __maybe_unused vf610_gpio_suspend(struct device *dev) +{ + struct vf610_gpio_port *port = dev_get_drvdata(dev); + + port->state = vf610_gpio_readl(port->gpio_base + GPIO_PDOR); + + /* + * There is no need to store Port state since we maintain the state + * alread in the irqc array + */ + + return 0; +} + +static int __maybe_unused vf610_gpio_resume(struct device *dev) +{ + struct vf610_gpio_port *port = dev_get_drvdata(dev); + int i; + + vf610_gpio_writel(port->state, port->gpio_base + GPIO_PDOR); + + for (i = 0; i < port->gc.ngpio; i++) { + u32 irqc = port->irqc[i] << PORT_PCR_IRQC_OFFSET; + + vf610_gpio_writel(irqc, port->base + PORT_PCR(i)); + } + + return 0; +} + +static const struct dev_pm_ops vf610_gpio_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(vf610_gpio_suspend, vf610_gpio_resume) +}; + +static int vf610_gpio_wkpu(struct device_node *np, struct vf610_gpio_port *port) +{ + struct platform_device *pdev = NULL; + struct of_phandle_args arg; + int i, ret; + + for (i = 0; i < VF610_GPIO_PER_PORT; i++) + port->fsl_wakeup[i] = -1; + + for (i = 0;;i++) { + int gpioid, wakeupid, cnt; + + ret = of_parse_phandle_with_fixed_args(np, "fsl,gpio-wakeup", + 3, i, &arg); + + if (ret == -ENOENT) + break; + + if (!pdev) + pdev = of_find_device_by_node(arg.np); + of_node_put(arg.np); + if (!pdev) + return -EPROBE_DEFER; + + gpioid = arg.args[0]; + wakeupid = arg.args[1]; + cnt = arg.args[2]; + + while (cnt-- && gpioid < VF610_GPIO_PER_PORT) + port->fsl_wakeup[gpioid++] = wakeupid++; + } + + port->pdev_wkpu = pdev; + + return 0; +} + static int vf610_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -241,6 +376,10 @@ static int vf610_gpio_probe(struct platform_device *pdev) if (port->irq < 0) return port->irq; + ret = vf610_gpio_wkpu(np, port); + if (ret < 0) + return ret; + gc = &port->gc; gc->of_node = np; gc->dev = dev; @@ -263,7 +402,7 @@ static int vf610_gpio_probe(struct platform_device *pdev) vf610_gpio_writel(~0, port->base + PORT_ISFR); ret = gpiochip_irqchip_add(gc, &vf610_gpio_irq_chip, 0, - handle_simple_irq, IRQ_TYPE_NONE); + handle_edge_irq, IRQ_TYPE_NONE); if (ret) { dev_err(dev, "failed to add irqchip\n"); gpiochip_remove(gc); @@ -271,6 +410,7 @@ static int vf610_gpio_probe(struct platform_device *pdev) } gpiochip_set_chained_irqchip(gc, &vf610_gpio_irq_chip, port->irq, vf610_gpio_irq_handler); + platform_set_drvdata(pdev, port); return 0; } @@ -278,6 +418,7 @@ static int vf610_gpio_probe(struct platform_device *pdev) static struct platform_driver vf610_gpio_driver = { .driver = { .name = "gpio-vf610", + .pm = &vf610_gpio_pm_ops, .of_match_table = vf610_gpio_dt_ids, }, .probe = vf610_gpio_probe, @@ -289,6 +430,60 @@ static int __init gpio_vf610_init(void) } device_initcall(gpio_vf610_init); +static irqreturn_t vf610_wkpu_irq(int irq, void *data) +{ + void __iomem *base = data; + u32 wisr; + + wisr = vf610_gpio_readl(base + WKPU_WISR); + vf610_gpio_writel(wisr, base + WKPU_WISR); + pr_debug("%s, WKPU interrupt received, flags %08x\n", __func__, wisr); + + return 0; +} + +static int vf610_wkpu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *iores; + void __iomem *base; + int irq, err; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, iores); + + if (IS_ERR(base)) + return PTR_ERR(base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(dev, irq, vf610_wkpu_irq, 0, "wkpu-vf610", base); + if (err) { + dev_err(dev, "Error requesting IRQ!\n"); + return err; + } + + platform_set_drvdata(pdev, base); + + return 0; +} + +static struct platform_driver vf610_wkpu_driver = { + .driver = { + .name = "wkpu-vf610", + .of_match_table = vf610_wkpu_dt_ids, + }, + .probe = vf610_wkpu_probe, +}; + +static int __init vf610_wkpu_init(void) +{ + return platform_driver_register(&vf610_wkpu_driver); +} +device_initcall(vf610_wkpu_init); + MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>"); MODULE_DESCRIPTION("Freescale VF610 GPIO"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c index 56292ae4538d..f4df2a709f05 100644 --- a/drivers/iio/adc/vf610_adc.c +++ b/drivers/iio/adc/vf610_adc.c @@ -68,6 +68,9 @@ #define VF610_ADC_CLK_DIV8 0x60 #define VF610_ADC_CLK_MASK 0x60 #define VF610_ADC_ADLSMP_LONG 0x10 +#define VF610_ADC_ADSTS_SHORT 0x100 +#define VF610_ADC_ADSTS_NORMAL 0x200 +#define VF610_ADC_ADSTS_LONG 0x300 #define VF610_ADC_ADSTS_MASK 0x300 #define VF610_ADC_ADLPC_EN 0x80 #define VF610_ADC_ADHSC_EN 0x400 @@ -98,6 +101,8 @@ #define VF610_ADC_CALF 0x2 #define VF610_ADC_TIMEOUT msecs_to_jiffies(100) +#define DEFAULT_SAMPLE_TIME 1000 + enum clk_sel { VF610_ADCIOC_BUSCLK_SET, VF610_ADCIOC_ALTCLK_SET, @@ -118,15 +123,34 @@ enum average_sel { VF610_ADC_SAMPLE_32, }; +enum conversion_mode_sel { + VF610_ADC_CONV_NORMAL, + VF610_ADC_CONV_HIGH_SPEED, + VF610_ADC_CONV_LOW_POWER, +}; + +enum lst_adder_sel { + VF610_ADCK_CYCLES_3, + VF610_ADCK_CYCLES_5, + VF610_ADCK_CYCLES_7, + VF610_ADCK_CYCLES_9, + VF610_ADCK_CYCLES_13, + VF610_ADCK_CYCLES_17, + VF610_ADCK_CYCLES_21, + VF610_ADCK_CYCLES_25, +}; + struct vf610_adc_feature { enum clk_sel clk_sel; enum vol_ref vol_ref; + enum conversion_mode_sel conv_mode; int clk_div; int sample_rate; int res_mode; + u32 lst_adder_index; + u32 default_sample_time; - bool lpm; bool calibration; bool ovwren; }; @@ -139,6 +163,8 @@ struct vf610_adc { u32 vref_uv; u32 value; struct regulator *vref; + + u32 max_adck_rate[3]; struct vf610_adc_feature adc_feature; u32 sample_freq_avail[5]; @@ -147,47 +173,40 @@ struct vf610_adc { }; static const u32 vf610_hw_avgs[] = { 1, 4, 8, 16, 32 }; - -#define VF610_ADC_CHAN(_idx, _chan_type) { \ - .type = (_chan_type), \ - .indexed = 1, \ - .channel = (_idx), \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ - BIT(IIO_CHAN_INFO_SAMP_FREQ), \ -} - -#define VF610_ADC_TEMPERATURE_CHAN(_idx, _chan_type) { \ - .type = (_chan_type), \ - .channel = (_idx), \ - .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ -} - -static const struct iio_chan_spec vf610_adc_iio_channels[] = { - VF610_ADC_CHAN(0, IIO_VOLTAGE), - VF610_ADC_CHAN(1, IIO_VOLTAGE), - VF610_ADC_CHAN(2, IIO_VOLTAGE), - VF610_ADC_CHAN(3, IIO_VOLTAGE), - VF610_ADC_CHAN(4, IIO_VOLTAGE), - VF610_ADC_CHAN(5, IIO_VOLTAGE), - VF610_ADC_CHAN(6, IIO_VOLTAGE), - VF610_ADC_CHAN(7, IIO_VOLTAGE), - VF610_ADC_CHAN(8, IIO_VOLTAGE), - VF610_ADC_CHAN(9, IIO_VOLTAGE), - VF610_ADC_CHAN(10, IIO_VOLTAGE), - VF610_ADC_CHAN(11, IIO_VOLTAGE), - VF610_ADC_CHAN(12, IIO_VOLTAGE), - VF610_ADC_CHAN(13, IIO_VOLTAGE), - VF610_ADC_CHAN(14, IIO_VOLTAGE), - VF610_ADC_CHAN(15, IIO_VOLTAGE), - VF610_ADC_TEMPERATURE_CHAN(26, IIO_TEMP), - /* sentinel */ -}; +static const u32 vf610_lst_adder[] = { 3, 5, 7, 9, 13, 17, 21, 25 }; static inline void vf610_adc_calculate_rates(struct vf610_adc *info) { + struct vf610_adc_feature *adc_feature = &info->adc_feature; unsigned long adck_rate, ipg_rate = clk_get_rate(info->clk); - int i; + u32 adck_period, lst_addr_min; + int divisor, i; + + adck_rate = info->max_adck_rate[adc_feature->conv_mode]; + + if (adck_rate) { + /* calculate clk divider which is within specification */ + divisor = ipg_rate / adck_rate; + adc_feature->clk_div = 1 << fls(divisor + 1); + } else { + /* fall-back value using a safe divisor */ + adc_feature->clk_div = 8; + } + + adck_rate = ipg_rate / info->adc_feature.clk_div; + + /* + * Determine the long sample time adder value to be used based + * on the default minimum sample time provided. + */ + adck_period = NSEC_PER_SEC / adck_rate; + lst_addr_min = adc_feature->default_sample_time / adck_period; + for (i = 0; i < ARRAY_SIZE(vf610_lst_adder); i++) { + if (vf610_lst_adder[i] > lst_addr_min) { + adc_feature->lst_adder_index = i; + break; + } + } /* * Calculate ADC sample frequencies @@ -198,12 +217,12 @@ static inline void vf610_adc_calculate_rates(struct vf610_adc *info) * SFCAdder: fixed to 6 ADCK cycles * AverageNum: 1, 4, 8, 16, 32 samples for hardware average. * BCT (Base Conversion Time): fixed to 25 ADCK cycles for 12 bit mode - * LSTAdder(Long Sample Time): fixed to 3 ADCK cycles + * LSTAdder(Long Sample Time): 3, 5, 7, 9, 13, 17, 21, 25 ADCK cycles */ - adck_rate = ipg_rate / info->adc_feature.clk_div; for (i = 0; i < ARRAY_SIZE(vf610_hw_avgs); i++) info->sample_freq_avail[i] = - adck_rate / (6 + vf610_hw_avgs[i] * (25 + 3)); + adck_rate / (6 + vf610_hw_avgs[i] * + (25 + vf610_lst_adder[adc_feature->lst_adder_index])); } static inline void vf610_adc_cfg_init(struct vf610_adc *info) @@ -219,10 +238,8 @@ static inline void vf610_adc_cfg_init(struct vf610_adc *info) adc_feature->res_mode = 12; adc_feature->sample_rate = 1; - adc_feature->lpm = true; - /* Use a save ADCK which is below 20MHz on all devices */ - adc_feature->clk_div = 8; + adc_feature->conv_mode = VF610_ADC_CONV_LOW_POWER; vf610_adc_calculate_rates(info); } @@ -304,10 +321,12 @@ static void vf610_adc_cfg_set(struct vf610_adc *info) cfg_data = readl(info->regs + VF610_REG_ADC_CFG); cfg_data &= ~VF610_ADC_ADLPC_EN; - if (adc_feature->lpm) + if (adc_feature->conv_mode == VF610_ADC_CONV_LOW_POWER) cfg_data |= VF610_ADC_ADLPC_EN; cfg_data &= ~VF610_ADC_ADHSC_EN; + if (adc_feature->conv_mode == VF610_ADC_CONV_HIGH_SPEED) + cfg_data |= VF610_ADC_ADHSC_EN; writel(cfg_data, info->regs + VF610_REG_ADC_CFG); } @@ -363,8 +382,40 @@ static void vf610_adc_sample_set(struct vf610_adc *info) break; } - /* Use the short sample mode */ - cfg_data &= ~(VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_MASK); + /* + * Set ADLSMP and ADSTS based on the Long Sample Time Adder value + * determined. + */ + switch (adc_feature->lst_adder_index) { + case VF610_ADCK_CYCLES_3: + break; + case VF610_ADCK_CYCLES_5: + cfg_data |= VF610_ADC_ADSTS_SHORT; + break; + case VF610_ADCK_CYCLES_7: + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + case VF610_ADCK_CYCLES_9: + cfg_data |= VF610_ADC_ADSTS_LONG; + break; + case VF610_ADCK_CYCLES_13: + cfg_data |= VF610_ADC_ADLSMP_LONG; + break; + case VF610_ADCK_CYCLES_17: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_SHORT; + break; + case VF610_ADCK_CYCLES_21: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + case VF610_ADCK_CYCLES_25: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + default: + dev_err(info->dev, "error in sample time select\n"); + } /* update hardware average selection */ cfg_data &= ~VF610_ADC_AVGS_MASK; @@ -409,6 +460,81 @@ static void vf610_adc_hw_init(struct vf610_adc *info) vf610_adc_cfg_set(info); } +static int vf610_set_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct vf610_adc *info = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + info->adc_feature.conv_mode = mode; + vf610_adc_calculate_rates(info); + vf610_adc_hw_init(info); + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static int vf610_get_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct vf610_adc *info = iio_priv(indio_dev); + + return info->adc_feature.conv_mode; +} + +static const char * const vf610_conv_modes[] = { "normal", "high-speed", + "low-power" }; + +static const struct iio_enum vf610_conversion_mode = { + .items = vf610_conv_modes, + .num_items = ARRAY_SIZE(vf610_conv_modes), + .get = vf610_get_conversion_mode, + .set = vf610_set_conversion_mode, +}; + +static const struct iio_chan_spec_ext_info vf610_ext_info[] = { + IIO_ENUM("conversion_mode", IIO_SHARED_BY_DIR, &vf610_conversion_mode), + {}, +}; + +#define VF610_ADC_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .ext_info = vf610_ext_info, \ +} + +#define VF610_ADC_TEMPERATURE_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ +} + +static const struct iio_chan_spec vf610_adc_iio_channels[] = { + VF610_ADC_CHAN(0, IIO_VOLTAGE), + VF610_ADC_CHAN(1, IIO_VOLTAGE), + VF610_ADC_CHAN(2, IIO_VOLTAGE), + VF610_ADC_CHAN(3, IIO_VOLTAGE), + VF610_ADC_CHAN(4, IIO_VOLTAGE), + VF610_ADC_CHAN(5, IIO_VOLTAGE), + VF610_ADC_CHAN(6, IIO_VOLTAGE), + VF610_ADC_CHAN(7, IIO_VOLTAGE), + VF610_ADC_CHAN(8, IIO_VOLTAGE), + VF610_ADC_CHAN(9, IIO_VOLTAGE), + VF610_ADC_CHAN(10, IIO_VOLTAGE), + VF610_ADC_CHAN(11, IIO_VOLTAGE), + VF610_ADC_CHAN(12, IIO_VOLTAGE), + VF610_ADC_CHAN(13, IIO_VOLTAGE), + VF610_ADC_CHAN(14, IIO_VOLTAGE), + VF610_ADC_CHAN(15, IIO_VOLTAGE), + VF610_ADC_TEMPERATURE_CHAN(26, IIO_TEMP), + /* sentinel */ +}; + static int vf610_adc_read_data(struct vf610_adc *info) { int result; @@ -651,6 +777,14 @@ static int vf610_adc_probe(struct platform_device *pdev) info->vref_uv = regulator_get_voltage(info->vref); + of_property_read_u32_array(pdev->dev.of_node, "fsl,adck-max-frequency", + info->max_adck_rate, 3); + + ret = of_property_read_u32(pdev->dev.of_node, "min-sample-time", + &info->adc_feature.default_sample_time); + if (ret) + info->adc_feature.default_sample_time = DEFAULT_SAMPLE_TIME; + platform_set_drvdata(pdev, indio_dev); init_completion(&info->completion); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 80f6386709bf..c901680d23d7 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -594,6 +594,13 @@ config TOUCHSCREEN_MIGOR To compile this driver as a module, choose M here: the module will be called migor_ts. +config TOUCHSCREEN_FUSION_F0710A + tristate "TouchRevolution Fusion F0710A Touchscreens" + depends on I2C + help + Say Y here if you want to support the multi-touch input driver for + the TouchRevolution Fusion 7 and 10 panels. + config TOUCHSCREEN_TOUCHRIGHT tristate "Touchright serial touchscreen" select SERIO @@ -1027,4 +1034,16 @@ config TOUCHSCREEN_ZFORCE To compile this driver as a module, choose M here: the module will be called zforce_ts. +config TOUCHSCREEN_COLIBRI_VF50 + tristate "Toradex Colibri on board touchscreen driver" + depends on IIO && VF610_ADC + help + Say Y here if you have a Colibri VF50 and plan to use + the on-board provided 4-wire touchscreen driver. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called colibri_vf50_ts. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 44deea743d02..dce1b68d5dcb 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -84,3 +84,5 @@ obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_SX8654) += sx8654.o obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o +obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o +obj-$(CONFIG_TOUCHSCREEN_FUSION_F0710A) += fusion_F0710A.o diff --git a/drivers/input/touchscreen/colibri-vf50-ts.c b/drivers/input/touchscreen/colibri-vf50-ts.c new file mode 100644 index 000000000000..74c5dd4311a7 --- /dev/null +++ b/drivers/input/touchscreen/colibri-vf50-ts.c @@ -0,0 +1,411 @@ +/* Copyright 2015 Toradex AG + * + * Toradex Colibri VF50 Touchscreen driver + * + * Originally authored by Stefan Agner for 3.0 kernel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/iio/consumer.h> +#include <linux/iio/types.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> + +#define DRIVER_NAME "colibri-vf50-ts" +#define DRV_VERSION "1.0" + +#define VF_ADC_MAX ((1 << 12) - 1) + +#define COLI_TOUCH_MIN_DELAY_US 1000 +#define COLI_TOUCH_MAX_DELAY_US 2000 + +static int min_pressure = 200; + +struct vf50_touch_device { + struct platform_device *pdev; + struct input_dev *ts_input; + struct iio_channel *channels; + struct gpio_desc *gpio_xp; + struct gpio_desc *gpio_xm; + struct gpio_desc *gpio_yp; + struct gpio_desc *gpio_ym; + struct gpio_desc *gpio_pen_detect; + int pen_irq; + bool stop_touchscreen; +}; + +/* + * Enables given plates and measures touch parameters using ADC + */ +static int adc_ts_measure(struct iio_channel *channel, + struct gpio_desc *plate_p, struct gpio_desc *plate_m) +{ + int i, value = 0, val = 0; + int ret; + + gpiod_set_value(plate_p, 1); + gpiod_set_value(plate_m, 1); + + /* Use hrtimer sleep since msleep sleeps 10ms+ */ + usleep_range(COLI_TOUCH_MIN_DELAY_US, COLI_TOUCH_MAX_DELAY_US); + + for (i = 0; i < 5; i++) { + ret = iio_read_channel_raw(channel, &val); + if (ret < 0) { + value = ret; + goto error_iio_read; + } + + value += val; + } + + value /= 5; + +error_iio_read: + gpiod_set_value(plate_p, 0); + gpiod_set_value(plate_m, 0); + + return value; +} + +/* + * Enable touch detection using falling edge detection on XM + */ +static void vf50_ts_enable_touch_detection(struct vf50_touch_device *vf50_ts) +{ + /* Enable plate YM (needs to be strong GND, high active) */ + gpiod_set_value(vf50_ts->gpio_ym, 1); + + /* + * Let the platform mux to idle state in order to enable + * Pull-up on GPIO + */ + pinctrl_pm_select_idle_state(&vf50_ts->pdev->dev); +} + +/* + * ADC touch screen sampling bottom half irq handler + */ +static irqreturn_t vf50_ts_irq_bh(int irq, void *private) +{ + struct vf50_touch_device *vf50_ts = (struct vf50_touch_device *)private; + struct device *dev = &vf50_ts->pdev->dev; + int val_x, val_y, val_z1, val_z2, val_p = 0; + bool discard_val_on_start = true; + + /* Disable the touch detection plates */ + gpiod_set_value(vf50_ts->gpio_ym, 0); + + /* Let the platform mux to default state in order to mux as ADC */ + pinctrl_pm_select_default_state(dev); + + while (!vf50_ts->stop_touchscreen) { + /* X-Direction */ + val_x = adc_ts_measure(&vf50_ts->channels[0], + vf50_ts->gpio_xp, vf50_ts->gpio_xm); + if (val_x < 0) + break; + + /* Y-Direction */ + val_y = adc_ts_measure(&vf50_ts->channels[1], + vf50_ts->gpio_yp, vf50_ts->gpio_ym); + if (val_y < 0) + break; + + /* + * Touch pressure + * Measure on XP/YM + */ + val_z1 = adc_ts_measure(&vf50_ts->channels[2], + vf50_ts->gpio_yp, vf50_ts->gpio_xm); + if (val_z1 < 0) + break; + val_z2 = adc_ts_measure(&vf50_ts->channels[3], + vf50_ts->gpio_yp, vf50_ts->gpio_xm); + if (val_z2 < 0) + break; + + /* + * According to datasheet of our touchscreen, + * resistance on X axis is 400~1200.. + */ + + /* Validate signal (avoid calculation using noise) */ + if (val_z1 > 64 && val_x > 64) { + /* + * Calculate resistance between the plates + * lower resistance means higher pressure + */ + int r_x = (1000 * val_x) / VF_ADC_MAX; + + val_p = (r_x * val_z2) / val_z1 - r_x; + + } else { + val_p = 2000; + } + + val_p = 2000 - val_p; + dev_dbg(dev, "Measured values: x: %d, y: %d, z1: %d, z2: %d, " + "p: %d\n", val_x, val_y, val_z1, val_z2, val_p); + + /* + * If touch pressure is too low, stop measuring and reenable + * touch detection + */ + if (val_p < min_pressure || val_p > 2000) + break; + + /* + * The pressure may not be enough for the first x and the + * second y measurement, but, the pressure is ok when the + * driver is doing the third and fourth measurement. To + * take care of this, we drop the first measurement always. + */ + if (discard_val_on_start) { + discard_val_on_start = false; + } else { + /* + * Report touch position and sleep for + * next measurement + */ + input_report_abs(vf50_ts->ts_input, + ABS_X, VF_ADC_MAX - val_x); + input_report_abs(vf50_ts->ts_input, + ABS_Y, VF_ADC_MAX - val_y); + input_report_abs(vf50_ts->ts_input, + ABS_PRESSURE, val_p); + input_report_key(vf50_ts->ts_input, BTN_TOUCH, 1); + input_sync(vf50_ts->ts_input); + } + + msleep(10); + } + + /* Report no more touch, reenable touch detection */ + input_report_abs(vf50_ts->ts_input, ABS_PRESSURE, 0); + input_report_key(vf50_ts->ts_input, BTN_TOUCH, 0); + input_sync(vf50_ts->ts_input); + + vf50_ts_enable_touch_detection(vf50_ts); + + /* Wait the pull-up to be stable on high */ + msleep(10); + + return IRQ_HANDLED; +} + +static int vf50_ts_open(struct input_dev *dev_input) +{ + int ret; + struct vf50_touch_device *touchdev = input_get_drvdata(dev_input); + struct device *dev = &touchdev->pdev->dev; + + dev_dbg(dev, "Input device %s opened, starting touch detection\n", + dev_input->name); + + touchdev->stop_touchscreen = false; + + ret = gpiod_direction_output(touchdev->gpio_xp, 0); + if (ret) { + dev_err(dev, "Could not set gpio xp as output %d\n", ret); + return ret; + } + + ret = gpiod_direction_output(touchdev->gpio_xm, 0); + if (ret) { + dev_err(dev, "Could not set gpio xm as output %d\n", ret); + return ret; + } + + ret = gpiod_direction_output(touchdev->gpio_yp, 0); + if (ret) { + dev_err(dev, "Could not set gpio yp as output %d\n", ret); + return ret; + } + + ret = gpiod_direction_output(touchdev->gpio_ym, 0); + if (ret) { + dev_err(dev, "Could not set gpio ym as output %d\n", ret); + return ret; + } + + ret = gpiod_direction_input(touchdev->gpio_pen_detect); + if (ret) { + dev_err(dev, + "Could not set gpio pen detect as input %d\n", ret); + return ret; + } + + /* Mux detection before request IRQ, wait for pull-up to settle */ + vf50_ts_enable_touch_detection(touchdev); + msleep(10); + + return 0; +} + +static void vf50_ts_close(struct input_dev *dev_input) +{ + struct vf50_touch_device *touchdev = input_get_drvdata(dev_input); + struct device *dev = &touchdev->pdev->dev; + + touchdev->stop_touchscreen = true; + + dev_dbg(dev, "Input device %s closed, disable touch detection\n", + dev_input->name); +} + +static inline int vf50_ts_get_gpiod(struct device *dev, + struct gpio_desc **gpio_d, const char *con_id) +{ + int ret; + + *gpio_d = devm_gpiod_get(dev, con_id); + if (IS_ERR(*gpio_d)) { + ret = PTR_ERR(*gpio_d); + dev_err(dev, "Could not get gpio_%s %d\n", con_id, ret); + return ret; + } + + return 0; +} + +static int vf50_ts_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct vf50_touch_device *touchdev; + struct input_dev *input; + struct iio_channel *channels; + + channels = iio_channel_get_all(dev); + if (IS_ERR(channels)) + return PTR_ERR(channels); + + touchdev = devm_kzalloc(dev, sizeof(*touchdev), GFP_KERNEL); + if (touchdev == NULL) { + ret = -ENOMEM; + goto error_release_channels; + } + + input = devm_input_allocate_device(dev); + if (!input) { + dev_err(dev, "Failed to allocate TS input device\n"); + ret = -ENOMEM; + goto error_release_channels; + } + + platform_set_drvdata(pdev, touchdev); + + touchdev->pdev = pdev; + touchdev->channels = channels; + + input->name = DRIVER_NAME; + input->id.bustype = BUS_HOST; + input->dev.parent = dev; + input->open = vf50_ts_open; + input->close = vf50_ts_close; + + _set_bit(EV_ABS, input->evbit); + _set_bit(EV_KEY, input->evbit); + _set_bit(BTN_TOUCH, input->keybit); + input_set_abs_params(input, ABS_X, 0, VF_ADC_MAX, 0, 0); + input_set_abs_params(input, ABS_Y, 0, VF_ADC_MAX, 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, VF_ADC_MAX, 0, 0); + + touchdev->ts_input = input; + input_set_drvdata(input, touchdev); + ret = input_register_device(input); + if (ret) { + dev_err(dev, "Failed to register input device\n"); + goto error_release_channels; + } + + ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_xp, "xp"); + if (ret) + goto error_release_channels; + + ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_xm, "xm"); + if (ret) + goto error_release_channels; + + ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_yp, "yp"); + if (ret) + goto error_release_channels; + + ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_ym, "ym"); + if (ret) + goto error_release_channels; + + ret = vf50_ts_get_gpiod(dev, &touchdev->gpio_pen_detect, + "pen-detect"); + if (ret) + goto error_release_channels; + + touchdev->pen_irq = gpiod_to_irq(touchdev->gpio_pen_detect); + if (touchdev->pen_irq < 0) { + ret = touchdev->pen_irq; + dev_err(dev, "Unable to get IRQ for GPIO\n"); + goto error_release_channels; + } + + ret = devm_request_threaded_irq(dev, touchdev->pen_irq, NULL, + vf50_ts_irq_bh, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "vf50 touch", touchdev); + if (ret < 0) { + dev_err(dev, "Unable to request IRQ %d\n", touchdev->pen_irq); + goto error_release_channels; + } + + return 0; + +error_release_channels: + iio_channel_release_all(channels); + return ret; +} + +static int vf50_ts_remove(struct platform_device *pdev) +{ + struct vf50_touch_device *touchdev = platform_get_drvdata(pdev); + + iio_channel_release_all(touchdev->channels); + + return 0; +} + +static const struct of_device_id vf50_touch_of_match[] = { + { .compatible = "toradex,vf50-touchctrl", }, + { } +}; +MODULE_DEVICE_TABLE(of, vf50_touch_of_match); + +static struct platform_driver __refdata vf50_touch_driver = { + .driver = { + .name = "toradex,vf50_touchctrl", + .owner = THIS_MODULE, + .of_match_table = vf50_touch_of_match, + }, + .probe = vf50_ts_probe, + .remove = vf50_ts_remove, +}; + +module_platform_driver(vf50_touch_driver); + +module_param(min_pressure, int, 0600); +MODULE_PARM_DESC(min_pressure, "Minimum pressure for touch detection"); +MODULE_AUTHOR("Sanchayan Maity"); +MODULE_DESCRIPTION("Colibri VF50 Touchscreen driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRV_VERSION); diff --git a/drivers/input/touchscreen/fusion_F0710A.c b/drivers/input/touchscreen/fusion_F0710A.c new file mode 100644 index 000000000000..edecf98d968e --- /dev/null +++ b/drivers/input/touchscreen/fusion_F0710A.c @@ -0,0 +1,503 @@ +/* + * "fusion_F0710A" touchscreen driver + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <asm/irq.h> +#include <linux/gpio.h> +#include <linux/input/fusion_F0710A.h> +#include <linux/input/mt.h> +#include <linux/slab.h> +#include <linux/of_gpio.h> + + +#include "fusion_F0710A.h" + +#define DRV_NAME "fusion_F0710A" +#define MAX_TOUCHES 2 + +static struct fusion_F0710A_data fusion_F0710A; + +static unsigned short normal_i2c[] = { fusion_F0710A_I2C_SLAVE_ADDR, I2C_CLIENT_END }; + +static int fusion_F0710A_write_u8(u8 addr, u8 data) +{ + return i2c_smbus_write_byte_data(fusion_F0710A.client, addr, data); +} + +static int fusion_F0710A_read_u8(u8 addr) +{ + return i2c_smbus_read_byte_data(fusion_F0710A.client, addr); +} + +static int fusion_F0710A_read_block(u8 addr, u8 len, u8 *data) +{ + u8 msgbuf0[1] = { addr }; + u16 slave = fusion_F0710A.client->addr; + u16 flags = fusion_F0710A.client->flags; + struct i2c_msg msg[2] = { { slave, flags, 1, msgbuf0 }, + { slave, flags | I2C_M_RD, len, data } + }; + + return i2c_transfer(fusion_F0710A.client->adapter, msg, ARRAY_SIZE(msg)); +} + +static int fusion_F0710A_register_input(void) +{ + int ret; + struct input_dev *dev; + + dev = fusion_F0710A.input = input_allocate_device(); + if (dev == NULL) + return -ENOMEM; + + dev->name = "fusion_F0710A"; + + set_bit(EV_KEY, dev->evbit); + set_bit(EV_ABS, dev->evbit); + set_bit(EV_SYN, dev->evbit); + set_bit(BTN_TOUCH, dev->keybit); + + input_mt_init_slots(dev, MAX_TOUCHES, 0); + input_set_abs_params(dev, ABS_MT_POSITION_X, 0, fusion_F0710A.info.xres-1, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, fusion_F0710A.info.yres-1, 0, 0); + input_set_abs_params(dev, ABS_MT_PRESSURE, 0, 255, 0, 0); + + input_set_abs_params(dev, ABS_X, 0, fusion_F0710A.info.xres-1, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, fusion_F0710A.info.yres-1, 0, 0); + input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0); + + ret = input_register_device(dev); + if (ret < 0) + goto bail1; + + return 0; + +bail1: + input_free_device(dev); + return ret; +} + +#define WC_RETRY_COUNT 3 +static int fusion_F0710A_write_complete(void) +{ + int ret, i; + + for(i=0; i<WC_RETRY_COUNT; i++) + { + ret = fusion_F0710A_write_u8(fusion_F0710A_SCAN_COMPLETE, 0); + if(ret == 0) + break; + else + dev_err(&fusion_F0710A.client->dev, "Write complete failed(%d): %d\n", i, ret); + } + + return ret; +} + +#define DATA_START fusion_F0710A_DATA_INFO +#define DATA_END fusion_F0710A_SEC_TIDTS +#define DATA_LEN (DATA_END - DATA_START + 1) +#define DATA_OFF(x) ((x) - DATA_START) + +static int fusion_F0710A_read_sensor(void) +{ + int ret; + u8 data[DATA_LEN]; + +#define DATA(x) (data[DATA_OFF(x)]) + /* To ensure data coherency, read the sensor with a single transaction. */ + ret = fusion_F0710A_read_block(DATA_START, DATA_LEN, data); + if (ret < 0) { + dev_err(&fusion_F0710A.client->dev, + "Read block failed: %d\n", ret); + + return ret; + } + + fusion_F0710A.f_num = DATA(fusion_F0710A_DATA_INFO)&0x03; + + fusion_F0710A.y1 = DATA(fusion_F0710A_POS_X1_HI) << 8; + fusion_F0710A.y1 |= DATA(fusion_F0710A_POS_X1_LO); + fusion_F0710A.x1 = DATA(fusion_F0710A_POS_Y1_HI) << 8; + fusion_F0710A.x1 |= DATA(fusion_F0710A_POS_Y1_LO); + fusion_F0710A.z1 = DATA(fusion_F0710A_FIR_PRESS); + fusion_F0710A.tip1 = DATA(fusion_F0710A_FIR_TIDTS)&0x0f; + fusion_F0710A.tid1 = (DATA(fusion_F0710A_FIR_TIDTS)&0xf0)>>4; + + + fusion_F0710A.y2 = DATA(fusion_F0710A_POS_X2_HI) << 8; + fusion_F0710A.y2 |= DATA(fusion_F0710A_POS_X2_LO); + fusion_F0710A.x2 = DATA(fusion_F0710A_POS_Y2_HI) << 8; + fusion_F0710A.x2 |= DATA(fusion_F0710A_POS_Y2_LO); + fusion_F0710A.z2 = DATA(fusion_F0710A_SEC_PRESS); + fusion_F0710A.tip2 = DATA(fusion_F0710A_SEC_TIDTS)&0x0f; + fusion_F0710A.tid2 =(DATA(fusion_F0710A_SEC_TIDTS)&0xf0)>>4; +#undef DATA + + return 0; +} + +#define val_cut_max(x, max, reverse) \ +do \ +{ \ + if(x > max) \ + x = max; \ + if(reverse) \ + x = (max) - (x); \ +} \ +while(0) + +static void fusion_F0710A_wq(struct work_struct *work) +{ + struct input_dev *dev = fusion_F0710A.input; + + if (fusion_F0710A_read_sensor() < 0) + goto restore_irq; + +#ifdef DEBUG + printk(KERN_DEBUG "tip1, tid1, x1, y1, z1 (%x,%x,%d,%d,%d); tip2, tid2, x2, y2, z2 (%x,%x,%d,%d,%d)\n", + fusion_F0710A.tip1, fusion_F0710A.tid1, fusion_F0710A.x1, fusion_F0710A.y1, fusion_F0710A.z1, + fusion_F0710A.tip2, fusion_F0710A.tid2, fusion_F0710A.x2, fusion_F0710A.y2, fusion_F0710A.z2); +#endif /* DEBUG */ + + val_cut_max(fusion_F0710A.x1, fusion_F0710A.info.xres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.y1, fusion_F0710A.info.yres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.x2, fusion_F0710A.info.xres-1, fusion_F0710A.info.xy_reverse); + val_cut_max(fusion_F0710A.y2, fusion_F0710A.info.yres-1, fusion_F0710A.info.xy_reverse); + + if (fusion_F0710A.tid1) { + input_mt_slot(dev, fusion_F0710A.tid1 - 1); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, fusion_F0710A.tip1); + if (fusion_F0710A.tip1) { + input_report_abs(dev, ABS_MT_POSITION_X, fusion_F0710A.x1); + input_report_abs(dev, ABS_MT_POSITION_Y, fusion_F0710A.y1); + input_report_abs(dev, ABS_MT_PRESSURE, fusion_F0710A.z1); + } + } + + if (fusion_F0710A.tid2) { + input_mt_slot(dev, fusion_F0710A.tid2 - 1); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, fusion_F0710A.tip2); + if (fusion_F0710A.tip2) { + input_report_abs(dev, ABS_MT_POSITION_X, fusion_F0710A.x2); + input_report_abs(dev, ABS_MT_POSITION_Y, fusion_F0710A.y2); + input_report_abs(dev, ABS_MT_PRESSURE, fusion_F0710A.z2); + } + } + + input_mt_report_pointer_emulation(dev, false); + input_sync(dev); + +restore_irq: + enable_irq(fusion_F0710A.client->irq); + + /* Clear fusion_F0710A interrupt */ + fusion_F0710A_write_complete(); +} +static DECLARE_WORK(fusion_F0710A_work, fusion_F0710A_wq); + +static irqreturn_t fusion_F0710A_interrupt(int irq, void *dev_id) +{ + disable_irq_nosync(fusion_F0710A.client->irq); + + queue_work(fusion_F0710A.workq, &fusion_F0710A_work); + + return IRQ_HANDLED; +} + +const static u8* g_ver_product[4] = { + "10Z8", "70Z7", "43Z6", "" +}; + +static int of_fusion_F0710A_get_pins(struct device_node *np, + unsigned int *int_pin, unsigned int *reset_pin) +{ + if (of_gpio_count(np) < 2) + return -ENODEV; + + *int_pin = of_get_gpio(np, 0); + *reset_pin = of_get_gpio(np, 1); + + if (!gpio_is_valid(*int_pin) || !gpio_is_valid(*reset_pin)) { + pr_err("%s: invalid GPIO pins, int=%d/reset=%d\n", + np->full_name, *int_pin, *reset_pin); + return -ENODEV; + } + + return 0; +} + +static int fusion_F0710A_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct device_node *np = i2c->dev.of_node; + struct fusion_f0710a_init_data *pdata = i2c->dev.platform_data; + int ret; + u8 ver_product, ver_id; + u32 version; + + if (np != NULL) { + pdata = i2c->dev.platform_data = + devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) { + dev_err(&i2c->dev, "No platform data for Fusion driver\n"); + return -ENODEV; + } + /* the dtb did the pinmuxing for us */ + pdata->pinmux_fusion_pins = NULL; + ret = of_fusion_F0710A_get_pins(i2c->dev.of_node, + &pdata->gpio_int, &pdata->gpio_reset); + if (ret) + return ret; + } + else if (pdata == NULL) { + dev_err(&i2c->dev, "No platform data for Fusion driver\n"); + return -ENODEV; + } + + /* Request pinmuxing, if necessary */ + if (pdata->pinmux_fusion_pins != NULL) { + ret = pdata->pinmux_fusion_pins(); + if (ret < 0) { + dev_err(&i2c->dev, "muxing GPIOs failed\n"); + return -ENODEV; + } + } + + if ((gpio_request(pdata->gpio_int, "Fusion pen down interrupt") == 0) && + (gpio_direction_input(pdata->gpio_int) == 0)) { + gpio_export(pdata->gpio_int, 0); + } else { + dev_warn(&i2c->dev, "Could not obtain GPIO for Fusion pen down\n"); + return -ENODEV; + } + + if ((gpio_request(pdata->gpio_reset, "Fusion reset") == 0) && + (gpio_direction_output(pdata->gpio_reset, 1) == 0)) { + + /* Generate a 0 => 1 edge explicitly, and wait for startup... */ + gpio_set_value(pdata->gpio_reset, 0); + msleep(10); + gpio_set_value(pdata->gpio_reset, 1); + /* Wait for startup (up to 125ms according to datasheet) */ + msleep(125); + + gpio_export(pdata->gpio_reset, 0); + } else { + dev_warn(&i2c->dev, "Could not obtain GPIO for Fusion reset\n"); + ret = -ENODEV; + goto bail0; + } + + /* Use Pen Down GPIO as sampling interrupt */ + i2c->irq = gpio_to_irq(pdata->gpio_int); + irq_set_irq_type(i2c->irq, IRQ_TYPE_LEVEL_HIGH); + + if(!i2c->irq) + { + dev_err(&i2c->dev, "fusion_F0710A irq < 0 \n"); + ret = -ENOMEM; + goto bail1; + } + + /* Attach the I2C client */ + fusion_F0710A.client = i2c; + i2c_set_clientdata(i2c, &fusion_F0710A); + + dev_info(&i2c->dev, "Touchscreen registered with bus id (%d) with slave address 0x%x\n", + i2c_adapter_id(fusion_F0710A.client->adapter), fusion_F0710A.client->addr); + + /* Read out a lot of registers */ + ret = fusion_F0710A_read_u8(fusion_F0710A_VIESION_INFO_LO); + if (ret < 0) { + dev_err(&i2c->dev, "query failed: %d\n", ret); + goto bail1; + } + ver_product = (((u8)ret) & 0xc0) >> 6; + version = (10 + ((((u32)ret)&0x30) >> 4)) * 100000; + version += (((u32)ret)&0xf) * 1000; + /* Read out a lot of registers */ + ret = fusion_F0710A_read_u8(fusion_F0710A_VIESION_INFO); + if (ret < 0) { + dev_err(&i2c->dev, "query failed: %d\n", ret); + goto bail1; + } + ver_id = ((u8)(ret) & 0x6) >> 1; + version += ((((u32)ret) & 0xf8) >> 3) * 10; + version += (((u32)ret) & 0x1) + 1; /* 0 is build 1, 1 is build 2 */ + dev_info(&i2c->dev, "version product %s(%d)\n", g_ver_product[ver_product] ,ver_product); + dev_info(&i2c->dev, "version id %s(%d)\n", ver_id ? "1.4" : "1.0", ver_id); + dev_info(&i2c->dev, "version series (%d)\n", version); + + switch(ver_product) + { + case fusion_F0710A_VIESION_07: /* 7 inch */ + fusion_F0710A.info.xres = fusion_F0710A07_XMAX; + fusion_F0710A.info.yres = fusion_F0710A07_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A07_REV; + break; + case fusion_F0710A_VIESION_43: /* 4.3 inch */ + fusion_F0710A.info.xres = fusion_F0710A43_XMAX; + fusion_F0710A.info.yres = fusion_F0710A43_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A43_REV; + break; + default: /* fusion_F0710A_VIESION_10 10 inch */ + fusion_F0710A.info.xres = fusion_F0710A10_XMAX; + fusion_F0710A.info.yres = fusion_F0710A10_YMAX; + fusion_F0710A.info.xy_reverse = fusion_F0710A10_REV; + break; + } + + /* Register the input device. */ + ret = fusion_F0710A_register_input(); + if (ret < 0) { + dev_err(&i2c->dev, "can't register input: %d\n", ret); + goto bail1; + } + + /* Create a worker thread */ + fusion_F0710A.workq = create_singlethread_workqueue(DRV_NAME); + if (fusion_F0710A.workq == NULL) { + dev_err(&i2c->dev, "can't create work queue\n"); + ret = -ENOMEM; + goto bail2; + } + + + /* Register for the interrupt and enable it. Our handler will + * start getting invoked after this call. */ + ret = request_irq(i2c->irq, fusion_F0710A_interrupt, IRQF_TRIGGER_RISING, + i2c->name, &fusion_F0710A); + if (ret < 0) { + dev_err(&i2c->dev, "can't get irq %d: %d\n", i2c->irq, ret); + goto bail3; + } + /* clear the irq first */ + ret = fusion_F0710A_write_u8(fusion_F0710A_SCAN_COMPLETE, 0); + if (ret < 0) { + dev_err(&i2c->dev, "Clear irq failed: %d\n", ret); + goto bail4; + } + + return 0; + +bail4: + free_irq(i2c->irq, &fusion_F0710A); + +bail3: + destroy_workqueue(fusion_F0710A.workq); + fusion_F0710A.workq = NULL; + +bail2: + input_unregister_device(fusion_F0710A.input); +bail1: + gpio_free(pdata->gpio_reset); +bail0: + gpio_free(pdata->gpio_int); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int fusion_F0710A_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + disable_irq(i2c->irq); + flush_workqueue(fusion_F0710A.workq); + + return 0; +} + +static int fusion_F0710A_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + enable_irq(i2c->irq); + + return 0; +} +#endif + +static int fusion_F0710A_remove(struct i2c_client *i2c) +{ + struct fusion_f0710a_init_data *pdata = i2c->dev.platform_data; + + gpio_free(pdata->gpio_int); + gpio_free(pdata->gpio_reset); + destroy_workqueue(fusion_F0710A.workq); + free_irq(i2c->irq, &fusion_F0710A); + input_unregister_device(fusion_F0710A.input); + i2c_set_clientdata(i2c, NULL); + + dev_info(&i2c->dev, "driver removed\n"); + + return 0; +} + +static struct i2c_device_id fusion_F0710A_id[] = { + {"fusion_F0710A", 0}, + {}, +}; + + +static const struct of_device_id fusion_F0710A_dt_ids[] = { + { + .compatible = "touchrevolution,fusion-f0710a", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, fusion_F0710A_dt_ids); + +static const struct dev_pm_ops fusion_F0710A_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fusion_F0710A_suspend, fusion_F0710A_resume) +}; + +static struct i2c_driver fusion_F0710A_i2c_drv = { + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + .pm = &fusion_F0710A_pm_ops, + .of_match_table = fusion_F0710A_dt_ids, + }, + .probe = fusion_F0710A_probe, + .remove = fusion_F0710A_remove, + .id_table = fusion_F0710A_id, + .address_list = normal_i2c, +}; + +static int __init fusion_F0710A_init( void ) +{ + int ret; + + memset(&fusion_F0710A, 0, sizeof(fusion_F0710A)); + + /* Probe for fusion_F0710A on I2C. */ + ret = i2c_add_driver(&fusion_F0710A_i2c_drv); + if (ret < 0) { + printk(KERN_WARNING DRV_NAME " can't add i2c driver: %d\n", ret); + } + + return ret; +} + +static void __exit fusion_F0710A_exit( void ) +{ + i2c_del_driver(&fusion_F0710A_i2c_drv); +} +module_init(fusion_F0710A_init); +module_exit(fusion_F0710A_exit); + +MODULE_DESCRIPTION("fusion_F0710A Touchscreen Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/touchscreen/fusion_F0710A.h b/drivers/input/touchscreen/fusion_F0710A.h new file mode 100644 index 000000000000..85f8210345a9 --- /dev/null +++ b/drivers/input/touchscreen/fusion_F0710A.h @@ -0,0 +1,87 @@ +/* + * "fusion_F0710A" touchscreen driver + * + * 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. + */ + +/* I2C slave address */ +#define fusion_F0710A_I2C_SLAVE_ADDR 0x10 + +/* I2C registers */ +#define fusion_F0710A_DATA_INFO 0x00 + +/* First Point*/ +#define fusion_F0710A_POS_X1_HI 0x01 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_X1_LO 0x02 /* 16-bit register, LSB */ +#define fusion_F0710A_POS_Y1_HI 0x03 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_Y1_LO 0x04 /* 16-bit register, LSB */ +#define fusion_F0710A_FIR_PRESS 0X05 +#define fusion_F0710A_FIR_TIDTS 0X06 + +/* Second Point */ +#define fusion_F0710A_POS_X2_HI 0x07 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_X2_LO 0x08 /* 16-bit register, LSB */ +#define fusion_F0710A_POS_Y2_HI 0x09 /* 16-bit register, MSB */ +#define fusion_F0710A_POS_Y2_LO 0x0A /* 16-bit register, LSB */ +#define fusion_F0710A_SEC_PRESS 0x0B +#define fusion_F0710A_SEC_TIDTS 0x0C + +#define fusion_F0710A_VIESION_INFO_LO 0X0E +#define fusion_F0710A_VIESION_INFO 0X0F + +#define fusion_F0710A_RESET 0x10 +#define fusion_F0710A_SCAN_COMPLETE 0x11 + + +#define fusion_F0710A_VIESION_10 0 +#define fusion_F0710A_VIESION_07 1 +#define fusion_F0710A_VIESION_43 2 + +/* fusion_F0710A 10 inch panel */ +#define fusion_F0710A10_XMAX 2275 +#define fusion_F0710A10_YMAX 1275 +#define fusion_F0710A10_REV 1 + +/* fusion_F0710A 7 inch panel */ +#define fusion_F0710A07_XMAX 1500 +#define fusion_F0710A07_YMAX 900 +#define fusion_F0710A07_REV 0 + +/* fusion_F0710A 4.3 inch panel */ +#define fusion_F0710A43_XMAX 900 +#define fusion_F0710A43_YMAX 500 +#define fusion_F0710A43_REV 0 + +#define fusion_F0710A_SAVE_PT1 0x1 +#define fusion_F0710A_SAVE_PT2 0x2 + + + +/* fusion_F0710A touch screen information */ +struct fusion_F0710A_info { + int xres; /* x resolution */ + int yres; /* y resolution */ + int xy_reverse; /* if need reverse in the x,y value x=xres-1-x, y=yres-1-y*/ +}; + +struct fusion_F0710A_data { + struct fusion_F0710A_info info; + struct i2c_client *client; + struct workqueue_struct *workq; + struct input_dev *input; + u16 x1; + u16 y1; + u8 z1; + u8 tip1; + u8 tid1; + u16 x2; + u16 y2; + u8 z2; + u8 tip2; + u8 tid2; + u8 f_num; + u8 save_points; +}; + diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 6de62a96e79c..99b9a9792975 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -30,6 +30,7 @@ config ARM_GIC_V3_ITS config ARM_NVIC bool select IRQ_DOMAIN + select IRQ_DOMAIN_HIERARCHY select GENERIC_IRQ_CHIP config ARM_VIC diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index dda4927e47a6..e17ec6c715cb 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o +obj-$(CONFIG_SOC_VF610) += irq-vf610-gpc.o obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o diff --git a/drivers/irqchip/irq-nvic.c b/drivers/irqchip/irq-nvic.c index 4ff0805fca01..5fac9100f6cb 100644 --- a/drivers/irqchip/irq-nvic.c +++ b/drivers/irqchip/irq-nvic.c @@ -49,6 +49,31 @@ nvic_handle_irq(irq_hw_number_t hwirq, struct pt_regs *regs) handle_IRQ(irq, regs); } +static int nvic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + int i, ret; + irq_hw_number_t hwirq; + unsigned int type = IRQ_TYPE_NONE; + struct of_phandle_args *irq_data = arg; + + ret = irq_domain_xlate_onecell(domain, irq_data->np, irq_data->args, + irq_data->args_count, &hwirq, &type); + if (ret) + return ret; + + for (i = 0; i < nr_irqs; i++) + irq_map_generic_chip(domain, virq + i, hwirq + i); + + return 0; +} + +static const struct irq_domain_ops nvic_irq_domain_ops = { + .xlate = irq_domain_xlate_onecell, + .alloc = nvic_irq_domain_alloc, + .free = irq_domain_free_irqs_top, +}; + static int __init nvic_of_init(struct device_node *node, struct device_node *parent) { @@ -70,7 +95,8 @@ static int __init nvic_of_init(struct device_node *node, irqs = NVIC_MAX_IRQ; nvic_irq_domain = - irq_domain_add_linear(node, irqs, &irq_generic_chip_ops, NULL); + irq_domain_add_linear(node, irqs, &nvic_irq_domain_ops, NULL); + if (!nvic_irq_domain) { pr_warn("Failed to allocate irq domain\n"); return -ENOMEM; diff --git a/drivers/irqchip/irq-vf610-gpc.c b/drivers/irqchip/irq-vf610-gpc.c new file mode 100644 index 000000000000..5527e103f9b0 --- /dev/null +++ b/drivers/irqchip/irq-vf610-gpc.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2015 Toradex AG + * Author: Stefan Agner <stefan@agner.ch> + * + * 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. + * + * + * The GPC (General Power Controller) irqchip driver takes care of the + * interrupt wakeup functionality. + * + * o All peripheral interrupts of the Vybrid SoC can be used as wakeup + * source from STOP mode. In LPSTOP mode however, the GPC is unpowered + * too and cannot be used to as a wakeup source. The WKPU (Wakeup Unit) + * is responsible for wakeups from LPSTOP modes. + */ + +#include <linux/cpu_pm.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/mfd/syscon.h> +#include <dt-bindings/interrupt-controller/arm-gic.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/regmap.h> + +#include "irqchip.h" + +#define IMR_NUM 4 +#define VF610_GPC_IMR1 0x044 +#define VF610_GPC_MAX_IRQS (IMR_NUM * 32) + +static void __iomem *gpc_base; + +static int vf610_gpc_irq_set_wake(struct irq_data *d, unsigned int on) +{ + unsigned int idx = d->hwirq / 32; + void __iomem *reg_imr = gpc_base + VF610_GPC_IMR1 + (idx * 4); + u32 mask = 1 << d->hwirq % 32; + + if (on) + writel_relaxed(readl_relaxed(reg_imr) & ~mask, reg_imr); + else + writel_relaxed(readl_relaxed(reg_imr) | mask, reg_imr); + + /* + * Do *not* call into the parent, as the GIC doesn't have any + * wake-up facility... + */ + return 0; +} + +static struct irq_chip vf610_gpc_chip = { + .name = "vf610-gpc", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_enable = irq_chip_enable_parent, + .irq_disable = irq_chip_disable_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_wake = vf610_gpc_irq_set_wake, +}; + +static int vf610_gpc_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *data) +{ + struct of_phandle_args *args = data; + struct of_phandle_args parent_args; + irq_hw_number_t hwirq; + int i; + + if (args->args_count != 2) + return -EINVAL; + + hwirq = args->args[0]; + for (i = 0; i < nr_irqs; i++) + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, + &vf610_gpc_chip, NULL); + + parent_args = *args; + parent_args.np = domain->parent->of_node; + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_args); +} + +static const struct irq_domain_ops gpc_irq_domain_ops = { + .xlate = irq_domain_xlate_twocell, + .alloc = vf610_gpc_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +static int __init vf610_gpc_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *domain, *domain_parent; + int i; + + domain_parent = irq_find_host(parent); + if (!domain_parent) { + pr_err("vf610_gpc: interrupt-parent not found\n"); + return -EINVAL; + } + + gpc_base = of_io_request_and_map(node, 0, "gpc"); + if (WARN_ON(!gpc_base)) + return -ENOMEM; + + domain = irq_domain_add_hierarchy(domain_parent, 0, VF610_GPC_MAX_IRQS, + node, &gpc_irq_domain_ops, NULL); + if (!domain) { + iounmap(gpc_base); + return -ENOMEM; + } + + /* Initially mask all interrupts for wakeup */ + for (i = 0; i < IMR_NUM; i++) + writel_relaxed(~0, gpc_base + VF610_GPC_IMR1 + i * 4); + + return 0; +} +IRQCHIP_DECLARE(vf610_gpc, "fsl,vf610-gpc", vf610_gpc_of_init); diff --git a/drivers/irqchip/irq-vf610-mscm-ir.c b/drivers/irqchip/irq-vf610-mscm-ir.c index 9521057d4744..8fe2cacd6eca 100644 --- a/drivers/irqchip/irq-vf610-mscm-ir.c +++ b/drivers/irqchip/irq-vf610-mscm-ir.c @@ -23,14 +23,17 @@ * variants of Vybrid. */ +#include <linux/bitops.h> #include <linux/cpu_pm.h> #include <linux/io.h> +#include <linux/interrupt.h> #include <linux/irq.h> #include <linux/irqdomain.h> #include <linux/mfd/syscon.h> #include <dt-bindings/interrupt-controller/arm-gic.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_irq.h> #include <linux/slab.h> #include <linux/regmap.h> @@ -38,19 +41,42 @@ #define MSCM_CPxNUM 0x4 +#define MSCM_IRCP0IR 0x0 +#define MSCM_IRCP1IR 0x4 +#define MSCM_IRCPnIR(n) ((n) * 0x4 + MSCM_IRCP0IR) +#define MSCM_IRCPnIR_INT(n) (0x1 << (n)) +#define MSCM_IRCPGIR 0x20 + +#define MSCM_INTID_MASK 0x3 +#define MSCM_INTID(n) ((n) & MSCM_INTID_MASK) +#define MSCM_CPUTL(n) (((n) == 0 ? 1 : 2) << 16) + #define MSCM_IRSPRC(n) (0x80 + 2 * (n)) #define MSCM_IRSPRC_CPEN_MASK 0x3 #define MSCM_IRSPRC_NUM 112 +#define MSCM_CPU2CPU_NUM 4 + struct vf610_mscm_ir_chip_data { void __iomem *mscm_ir_base; - u16 cpu_mask; + u16 cpu_id; u16 saved_irsprc[MSCM_IRSPRC_NUM]; + bool is_nvic; + struct device_node *cpu2cpu_node; +}; + +struct mscm_cpu2cpu_irq_data { + int intid; + int irq; + irq_handler_t handler; + void *priv; }; static struct vf610_mscm_ir_chip_data *mscm_ir_data; +static struct mscm_cpu2cpu_irq_data cpu2cpu_irq_data[MSCM_CPU2CPU_NUM]; + static inline void vf610_mscm_ir_save(struct vf610_mscm_ir_chip_data *data) { int i; @@ -94,24 +120,23 @@ static void vf610_mscm_ir_enable(struct irq_data *data) u16 irsprc; irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); - irsprc &= MSCM_IRSPRC_CPEN_MASK; - - WARN_ON(irsprc & ~chip_data->cpu_mask); - - writew_relaxed(chip_data->cpu_mask, + writew_relaxed(irsprc | BIT(chip_data->cpu_id), chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); - irq_chip_unmask_parent(data); + irq_chip_enable_parent(data); } static void vf610_mscm_ir_disable(struct irq_data *data) { irq_hw_number_t hwirq = data->hwirq; struct vf610_mscm_ir_chip_data *chip_data = data->chip_data; + u16 irsprc; - writew_relaxed(0x0, chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); + irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); + writew_relaxed(irsprc & ~BIT(chip_data->cpu_id), + chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); - irq_chip_mask_parent(data); + irq_chip_disable_parent(data); } static struct irq_chip vf610_mscm_ir_irq_chip = { @@ -143,10 +168,17 @@ static int vf610_mscm_ir_domain_alloc(struct irq_domain *domain, unsigned int vi domain->host_data); gic_data.np = domain->parent->of_node; - gic_data.args_count = 3; - gic_data.args[0] = GIC_SPI; - gic_data.args[1] = irq_data->args[0]; - gic_data.args[2] = irq_data->args[1]; + + if (mscm_ir_data->is_nvic) { + gic_data.args_count = 1; + gic_data.args[0] = irq_data->args[0]; + } else { + gic_data.args_count = 3; + gic_data.args[0] = GIC_SPI; + gic_data.args[1] = irq_data->args[0]; + gic_data.args[2] = irq_data->args[1]; + } + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_data); } @@ -156,6 +188,91 @@ static const struct irq_domain_ops mscm_irq_domain_ops = { .free = irq_domain_free_irqs_common, }; + +static irqreturn_t mscm_cpu2cpu_irq_handler(int irq, void *dev_id) +{ + irqreturn_t ret; + struct mscm_cpu2cpu_irq_data *data = dev_id; + void __iomem *mscm_base = mscm_ir_data->mscm_ir_base; + int cpu_id = mscm_ir_data->cpu_id; + + + ret = data->handler(data->intid, data->priv); + if (ret == IRQ_HANDLED) + writel(MSCM_IRCPnIR_INT(data->intid), mscm_base + MSCM_IRCPnIR(cpu_id)); + + return ret; +} + +int mscm_request_cpu2cpu_irq(unsigned int intid, irq_handler_t handler, + const char *name, void *priv) +{ + int irq; + struct mscm_cpu2cpu_irq_data *data; + + if (intid >= MSCM_CPU2CPU_NUM) + return -EINVAL; + + irq = of_irq_get(mscm_ir_data->cpu2cpu_node, intid); + if (irq < 0) + return irq; + + data = &cpu2cpu_irq_data[intid]; + data->intid = intid; + data->irq = irq; + data->handler = handler; + data->priv = priv; + + return request_irq(irq, mscm_cpu2cpu_irq_handler, 0, name, data); +} +EXPORT_SYMBOL(mscm_request_cpu2cpu_irq); + +void mscm_free_cpu2cpu_irq(unsigned int intid, void *priv) +{ + struct mscm_cpu2cpu_irq_data *data; + + if (intid >= MSCM_CPU2CPU_NUM) + return; + + data = &cpu2cpu_irq_data[intid]; + + if (data->irq < 0) + return; + + free_irq(data->irq, data); +} +EXPORT_SYMBOL(mscm_free_cpu2cpu_irq); + +void mscm_trigger_cpu2cpu_irq(unsigned int intid, int cpuid) +{ + void __iomem *mscm_base = mscm_ir_data->mscm_ir_base; + + writel(MSCM_INTID(intid) | MSCM_CPUTL(cpuid), mscm_base + MSCM_IRCPGIR); +} +EXPORT_SYMBOL(mscm_trigger_cpu2cpu_irq); + +void mscm_enable_cpu2cpu_irq(unsigned int intid) +{ + struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid]; + + if (intid >= MSCM_CPU2CPU_NUM) + return; + + enable_irq(data->irq); +} +EXPORT_SYMBOL(mscm_enable_cpu2cpu_irq); + +void mscm_disable_cpu2cpu_irq(unsigned int intid) +{ + struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid]; + + if (intid >= MSCM_CPU2CPU_NUM) + return; + + disable_irq(data->irq); +} +EXPORT_SYMBOL(mscm_disable_cpu2cpu_irq); + static int __init vf610_mscm_ir_of_init(struct device_node *node, struct device_node *parent) { @@ -188,8 +305,7 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node, goto out_unmap; } - regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid); - mscm_ir_data->cpu_mask = 0x1 << cpuid; + mscm_ir_data->cpu_id = regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid); domain = irq_domain_add_hierarchy(domain_parent, 0, MSCM_IRSPRC_NUM, node, @@ -199,8 +315,13 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node, goto out_unmap; } + if (of_device_is_compatible(domain->parent->of_node, "arm,armv7m-nvic")) + mscm_ir_data->is_nvic = true; + cpu_pm_register_notifier(&mscm_ir_notifier_block); + mscm_ir_data->cpu2cpu_node = of_get_child_by_name(node, "cpu2cpu"); + return 0; out_unmap: diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 461698b038f7..cf05cfcc279d 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -1170,7 +1170,7 @@ static int sdhci_esdhc_runtime_resume(struct device *dev) #endif static const struct dev_pm_ops sdhci_esdhc_pmops = { - SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_pltfm_resume) + SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_rpm_suspend, sdhci_pltfm_rpm_resume) SET_RUNTIME_PM_OPS(sdhci_esdhc_runtime_suspend, sdhci_esdhc_runtime_resume, NULL) }; diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c index a207f5aaf62f..38c03cd66052 100644 --- a/drivers/mmc/host/sdhci-pltfm.c +++ b/drivers/mmc/host/sdhci-pltfm.c @@ -31,6 +31,7 @@ #include <linux/err.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/pm_runtime.h> #ifdef CONFIG_PPC #include <asm/machdep.h> #endif @@ -256,6 +257,41 @@ const struct dev_pm_ops sdhci_pltfm_pmops = { .resume = sdhci_pltfm_resume, }; EXPORT_SYMBOL_GPL(sdhci_pltfm_pmops); + +int sdhci_pltfm_rpm_suspend(struct device *dev) +{ + int ret; + struct sdhci_host *host = dev_get_drvdata(dev); + + pm_runtime_get_sync(dev); + ret = sdhci_suspend_host(host); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + return pm_runtime_force_suspend(dev); +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_rpm_suspend); + +int sdhci_pltfm_rpm_resume(struct device *dev) +{ + int ret; + struct sdhci_host *host = dev_get_drvdata(dev); + + ret = pm_runtime_force_resume(dev); + + if (ret) + return ret; + + pm_runtime_get_sync(dev); + ret = sdhci_resume_host(host); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_rpm_resume); #endif /* CONFIG_PM */ static int __init sdhci_pltfm_drv_init(void) diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h index 04bc2481e5c3..ac5f6ea9b55f 100644 --- a/drivers/mmc/host/sdhci-pltfm.h +++ b/drivers/mmc/host/sdhci-pltfm.h @@ -114,6 +114,8 @@ static inline void *sdhci_pltfm_priv(struct sdhci_pltfm_host *host) extern int sdhci_pltfm_suspend(struct device *dev); extern int sdhci_pltfm_resume(struct device *dev); extern const struct dev_pm_ops sdhci_pltfm_pmops; +extern int sdhci_pltfm_rpm_suspend(struct device *dev); +extern int sdhci_pltfm_rpm_resume(struct device *dev); #define SDHCI_PLTFM_PMOPS (&sdhci_pltfm_pmops) #else #define SDHCI_PLTFM_PMOPS NULL diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 5897d8d8fa5a..b3c57c2e141d 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -455,6 +455,17 @@ config MTD_NAND_MPC5121_NFC This enables the driver for the NAND flash controller on the MPC5121 SoC. +config MTD_NAND_VF610_NFC + tristate "Support for Freescale NFC for VF610/MPC5125" + depends on (SOC_VF610 || COMPILE_TEST) + help + Enables support for NAND Flash Controller on some Freescale + processors like the VF610, MPC5125, MCF54418 or Kinetis K70. + The driver supports a maximum 2k page size. With 2k pages and + 64 bytes or more of OOB, hardware ECC with up to 32-bit error + correction is supported. Hardware ECC is only enabled through + device tree. + config MTD_NAND_MXC tristate "MXC NAND support" depends on ARCH_MXC diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 582bbd05aff7..e97ca7bef832 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o obj-$(CONFIG_MTD_NAND_NUC900) += nuc900_nand.o obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o +obj-$(CONFIG_MTD_NAND_VF610_NFC) += vf610_nfc.o obj-$(CONFIG_MTD_NAND_RICOH) += r852.o obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/ diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index c2e1232cd45c..c6cf775ee431 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -48,6 +48,7 @@ #include <linux/leds.h> #include <linux/io.h> #include <linux/mtd/partitions.h> +#include <linux/of_mtd.h> /* Define default oob placement schemes for large and small page devices */ static struct nand_ecclayout nand_oob_8 = { @@ -3798,6 +3799,39 @@ ident_done: return type; } +static int nand_dt_init(struct mtd_info *mtd, struct nand_chip *chip, + struct device_node *dn) +{ + int ecc_mode, ecc_strength, ecc_step; + + if (of_get_nand_bus_width(dn) == 16) + chip->options |= NAND_BUSWIDTH_16; + + if (of_get_nand_on_flash_bbt(dn)) + chip->bbt_options |= NAND_BBT_USE_FLASH; + + ecc_mode = of_get_nand_ecc_mode(dn); + ecc_strength = of_get_nand_ecc_strength(dn); + ecc_step = of_get_nand_ecc_step_size(dn); + + if ((ecc_step >= 0 && !(ecc_strength >= 0)) || + (!(ecc_step >= 0) && ecc_strength >= 0)) { + pr_err("must set both strength and step size in DT\n"); + return -EINVAL; + } + + if (ecc_mode >= 0) + chip->ecc.mode = ecc_mode; + + if (ecc_strength >= 0) + chip->ecc.strength = ecc_strength; + + if (ecc_step > 0) + chip->ecc.size = ecc_step; + + return 0; +} + /** * nand_scan_ident - [NAND Interface] Scan for the NAND device * @mtd: MTD device structure @@ -3815,6 +3849,13 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips, int i, nand_maf_id, nand_dev_id; struct nand_chip *chip = mtd->priv; struct nand_flash_dev *type; + int ret; + + if (chip->dn) { + ret = nand_dt_init(mtd, chip, chip->dn); + if (ret) + return ret; + } /* Set the default functions */ nand_set_defaults(chip, chip->options & NAND_BUSWIDTH_16); diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c new file mode 100644 index 000000000000..6cf81654fb81 --- /dev/null +++ b/drivers/mtd/nand/vf610_nfc.c @@ -0,0 +1,889 @@ +/* + * Copyright 2009-2015 Freescale Semiconductor, Inc. and others + * + * Description: MPC5125, VF610, MCF54418 and Kinetis K70 Nand driver. + * Jason ported to M54418TWR and MVFA5 (VF610). + * Authors: Stefan Agner <stefan.agner@toradex.com> + * Bill Pringlemeir <bpringlemeir@nbsps.com> + * Shaohui Xie <b21989@freescale.com> + * Jason Jin <Jason.jin@freescale.com> + * + * Based on original driver mpc5121_nfc.c. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Limitations: + * - Untested on MPC5125 and M54418. + * - DMA and pipelining not used. + * - 2K pages or less. + * - HW ECC: Only 2K page with 64+ OOB. + * - HW ECC: Only 24 and 32-bit error correction implemented. + */ + +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/of_mtd.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define DRV_NAME "vf610_nfc" + +/* Register Offsets */ +#define NFC_FLASH_CMD1 0x3F00 +#define NFC_FLASH_CMD2 0x3F04 +#define NFC_COL_ADDR 0x3F08 +#define NFC_ROW_ADDR 0x3F0c +#define NFC_ROW_ADDR_INC 0x3F14 +#define NFC_FLASH_STATUS1 0x3F18 +#define NFC_FLASH_STATUS2 0x3F1c +#define NFC_CACHE_SWAP 0x3F28 +#define NFC_SECTOR_SIZE 0x3F2c +#define NFC_FLASH_CONFIG 0x3F30 +#define NFC_IRQ_STATUS 0x3F38 + +/* Addresses for NFC MAIN RAM BUFFER areas */ +#define NFC_MAIN_AREA(n) ((n) * 0x1000) + +#define PAGE_2K 0x0800 +#define OOB_64 0x0040 +#define OOB_MAX 0x0100 + +/* + * NFC_CMD2[CODE] values. See section: + * - 31.4.7 Flash Command Code Description, Vybrid manual + * - 23.8.6 Flash Command Sequencer, MPC5125 manual + * + * Briefly these are bitmasks of controller cycles. + */ +#define READ_PAGE_CMD_CODE 0x7EE0 +#define READ_ONFI_PARAM_CMD_CODE 0x4860 +#define PROGRAM_PAGE_CMD_CODE 0x7FC0 +#define ERASE_CMD_CODE 0x4EC0 +#define READ_ID_CMD_CODE 0x4804 +#define RESET_CMD_CODE 0x4040 +#define STATUS_READ_CMD_CODE 0x4068 + +/* NFC ECC mode define */ +#define ECC_BYPASS 0 +#define ECC_45_BYTE 6 +#define ECC_60_BYTE 7 + +/*** Register Mask and bit definitions */ + +/* NFC_FLASH_CMD1 Field */ +#define CMD_BYTE2_MASK 0xFF000000 +#define CMD_BYTE2_SHIFT 24 + +/* NFC_FLASH_CM2 Field */ +#define CMD_BYTE1_MASK 0xFF000000 +#define CMD_BYTE1_SHIFT 24 +#define CMD_CODE_MASK 0x00FFFF00 +#define CMD_CODE_SHIFT 8 +#define BUFNO_MASK 0x00000006 +#define BUFNO_SHIFT 1 +#define START_BIT BIT(0) + +/* NFC_COL_ADDR Field */ +#define COL_ADDR_MASK 0x0000FFFF +#define COL_ADDR_SHIFT 0 + +/* NFC_ROW_ADDR Field */ +#define ROW_ADDR_MASK 0x00FFFFFF +#define ROW_ADDR_SHIFT 0 +#define ROW_ADDR_CHIP_SEL_RB_MASK 0xF0000000 +#define ROW_ADDR_CHIP_SEL_RB_SHIFT 28 +#define ROW_ADDR_CHIP_SEL_MASK 0x0F000000 +#define ROW_ADDR_CHIP_SEL_SHIFT 24 + +/* NFC_FLASH_STATUS2 Field */ +#define STATUS_BYTE1_MASK 0x000000FF + +/* NFC_FLASH_CONFIG Field */ +#define CONFIG_ECC_SRAM_ADDR_MASK 0x7FC00000 +#define CONFIG_ECC_SRAM_ADDR_SHIFT 22 +#define CONFIG_ECC_SRAM_REQ_BIT BIT(21) +#define CONFIG_DMA_REQ_BIT BIT(20) +#define CONFIG_ECC_MODE_MASK 0x000E0000 +#define CONFIG_ECC_MODE_SHIFT 17 +#define CONFIG_FAST_FLASH_BIT BIT(16) +#define CONFIG_16BIT BIT(7) +#define CONFIG_BOOT_MODE_BIT BIT(6) +#define CONFIG_ADDR_AUTO_INCR_BIT BIT(5) +#define CONFIG_BUFNO_AUTO_INCR_BIT BIT(4) +#define CONFIG_PAGE_CNT_MASK 0xF +#define CONFIG_PAGE_CNT_SHIFT 0 + +/* NFC_IRQ_STATUS Field */ +#define IDLE_IRQ_BIT BIT(29) +#define IDLE_EN_BIT BIT(20) +#define CMD_DONE_CLEAR_BIT BIT(18) +#define IDLE_CLEAR_BIT BIT(17) + +/* + * ECC status - seems to consume 8 bytes (double word). The documented + * status byte is located in the lowest byte of the second word (which is + * the 4th or 7th byte depending on endianness). + * Calculate an offset to store the ECC status at the end of the buffer. + */ +#define ECC_SRAM_ADDR (PAGE_2K + OOB_MAX - 8) + +#define ECC_STATUS 0x4 +#define ECC_STATUS_MASK 0x80 +#define ECC_STATUS_ERR_COUNT 0x3F + +enum vf610_nfc_alt_buf { + ALT_BUF_DATA = 0, + ALT_BUF_ID = 1, + ALT_BUF_STAT = 2, + ALT_BUF_ONFI = 3, +}; + +enum vf610_nfc_variant { + NFC_VFC610 = 1, +}; + +struct vf610_nfc { + struct mtd_info mtd; + struct nand_chip chip; + struct device *dev; + void __iomem *regs; + struct completion cmd_done; + uint buf_offset; + int write_sz; + /* Status and ID are in alternate locations. */ + enum vf610_nfc_alt_buf alt_buf; + enum vf610_nfc_variant variant; + struct clk *clk; + bool use_hw_ecc; + u32 ecc_mode; +}; + +#define mtd_to_nfc(_mtd) container_of(_mtd, struct vf610_nfc, mtd) + +static struct nand_ecclayout vf610_nfc_ecc45 = { + .eccbytes = 45, + .eccpos = {19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63}, + .oobfree = { + {.offset = 2, + .length = 17} } +}; + +static struct nand_ecclayout vf610_nfc_ecc60 = { + .eccbytes = 60, + .eccpos = { 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63 }, + .oobfree = { + {.offset = 2, + .length = 2} } +}; + +static inline u32 vf610_nfc_read(struct vf610_nfc *nfc, uint reg) +{ + return readl(nfc->regs + reg); +} + +static inline void vf610_nfc_write(struct vf610_nfc *nfc, uint reg, u32 val) +{ + writel(val, nfc->regs + reg); +} + +static inline void vf610_nfc_set(struct vf610_nfc *nfc, uint reg, u32 bits) +{ + vf610_nfc_write(nfc, reg, vf610_nfc_read(nfc, reg) | bits); +} + +static inline void vf610_nfc_clear(struct vf610_nfc *nfc, uint reg, u32 bits) +{ + vf610_nfc_write(nfc, reg, vf610_nfc_read(nfc, reg) & ~bits); +} + +static inline void vf610_nfc_set_field(struct vf610_nfc *nfc, u32 reg, + u32 mask, u32 shift, u32 val) +{ + vf610_nfc_write(nfc, reg, + (vf610_nfc_read(nfc, reg) & (~mask)) | val << shift); +} + +static inline void vf610_nfc_memcpy(void *dst, const void __iomem *src, + size_t n) +{ + /* + * Use this accessor for the internal SRAM buffers. On the ARM + * Freescale Vybrid SoC it's known that the driver can treat + * the SRAM buffer as if it's memory. Other platform might need + * to treat the buffers differently. + * + * For the time being, use memcpy + */ + memcpy(dst, src, n); +} + +/* Clear flags for upcoming command */ +static inline void vf610_nfc_clear_status(struct vf610_nfc *nfc) +{ + u32 tmp = vf610_nfc_read(nfc, NFC_IRQ_STATUS); + + tmp |= CMD_DONE_CLEAR_BIT | IDLE_CLEAR_BIT; + vf610_nfc_write(nfc, NFC_IRQ_STATUS, tmp); +} + +static void vf610_nfc_done(struct vf610_nfc *nfc) +{ + unsigned long timeout = msecs_to_jiffies(100); + + /* + * Barrier is needed after this write. This write need + * to be done before reading the next register the first + * time. + * vf610_nfc_set implicates such a barrier by using writel + * to write to the register. + */ + vf610_nfc_set(nfc, NFC_IRQ_STATUS, IDLE_EN_BIT); + vf610_nfc_set(nfc, NFC_FLASH_CMD2, START_BIT); + + if (!wait_for_completion_timeout(&nfc->cmd_done, timeout)) + dev_warn(nfc->dev, "Timeout while waiting for BUSY.\n"); + + vf610_nfc_clear_status(nfc); +} + +static u8 vf610_nfc_get_id(struct vf610_nfc *nfc, int col) +{ + u32 flash_id; + + if (col < 4) { + flash_id = vf610_nfc_read(nfc, NFC_FLASH_STATUS1); + flash_id >>= (3 - col) * 8; + } else { + flash_id = vf610_nfc_read(nfc, NFC_FLASH_STATUS2); + flash_id >>= 24; + } + + return flash_id & 0xff; +} + +static u8 vf610_nfc_get_status(struct vf610_nfc *nfc) +{ + return vf610_nfc_read(nfc, NFC_FLASH_STATUS2) & STATUS_BYTE1_MASK; +} + +static void vf610_nfc_send_command(struct vf610_nfc *nfc, u32 cmd_byte1, + u32 cmd_code) +{ + u32 tmp; + + vf610_nfc_clear_status(nfc); + + tmp = vf610_nfc_read(nfc, NFC_FLASH_CMD2); + tmp &= ~(CMD_BYTE1_MASK | CMD_CODE_MASK | BUFNO_MASK); + tmp |= cmd_byte1 << CMD_BYTE1_SHIFT; + tmp |= cmd_code << CMD_CODE_SHIFT; + vf610_nfc_write(nfc, NFC_FLASH_CMD2, tmp); +} + +static void vf610_nfc_send_commands(struct vf610_nfc *nfc, u32 cmd_byte1, + u32 cmd_byte2, u32 cmd_code) +{ + u32 tmp; + + vf610_nfc_send_command(nfc, cmd_byte1, cmd_code); + + tmp = vf610_nfc_read(nfc, NFC_FLASH_CMD1); + tmp &= ~CMD_BYTE2_MASK; + tmp |= cmd_byte2 << CMD_BYTE2_SHIFT; + vf610_nfc_write(nfc, NFC_FLASH_CMD1, tmp); +} + +static irqreturn_t vf610_nfc_irq(int irq, void *data) +{ + struct mtd_info *mtd = data; + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + + vf610_nfc_clear(nfc, NFC_IRQ_STATUS, IDLE_EN_BIT); + complete(&nfc->cmd_done); + + return IRQ_HANDLED; +} + +static void vf610_nfc_addr_cycle(struct vf610_nfc *nfc, int column, int page) +{ + if (column != -1) { + if (nfc->chip.options & NAND_BUSWIDTH_16) + column = column / 2; + vf610_nfc_set_field(nfc, NFC_COL_ADDR, COL_ADDR_MASK, + COL_ADDR_SHIFT, column); + } + if (page != -1) + vf610_nfc_set_field(nfc, NFC_ROW_ADDR, ROW_ADDR_MASK, + ROW_ADDR_SHIFT, page); +} + +static inline void vf610_nfc_ecc_mode(struct vf610_nfc *nfc, int ecc_mode) +{ + vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, + CONFIG_ECC_MODE_MASK, + CONFIG_ECC_MODE_SHIFT, ecc_mode); +} + +static inline void vf610_nfc_transfer_size(struct vf610_nfc *nfc, int size) +{ + vf610_nfc_write(nfc, NFC_SECTOR_SIZE, size); +} + +static void vf610_nfc_command(struct mtd_info *mtd, unsigned command, + int column, int page) +{ + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + int trfr_sz = nfc->chip.options & NAND_BUSWIDTH_16 ? 1 : 0; + + nfc->buf_offset = max(column, 0); + nfc->alt_buf = ALT_BUF_DATA; + + switch (command) { + case NAND_CMD_SEQIN: + /* Use valid column/page from preread... */ + vf610_nfc_addr_cycle(nfc, column, page); + nfc->buf_offset = 0; + + /* + * SEQIN => data => PAGEPROG sequence is done by the controller + * hence we do not need to issue the command here... + */ + return; + case NAND_CMD_PAGEPROG: + trfr_sz += nfc->write_sz; + + if (nfc->use_hw_ecc) + vf610_nfc_ecc_mode(nfc, nfc->ecc_mode); + else + vf610_nfc_ecc_mode(nfc, ECC_BYPASS); + + vf610_nfc_transfer_size(nfc, trfr_sz); + vf610_nfc_send_commands(nfc, NAND_CMD_SEQIN, + command, PROGRAM_PAGE_CMD_CODE); + break; + + case NAND_CMD_RESET: + vf610_nfc_transfer_size(nfc, 0); + vf610_nfc_send_command(nfc, command, RESET_CMD_CODE); + break; + + case NAND_CMD_READOOB: + trfr_sz += mtd->oobsize; + column = mtd->writesize; + vf610_nfc_transfer_size(nfc, trfr_sz); + vf610_nfc_send_commands(nfc, NAND_CMD_READ0, + NAND_CMD_READSTART, READ_PAGE_CMD_CODE); + vf610_nfc_addr_cycle(nfc, column, page); + vf610_nfc_ecc_mode(nfc, ECC_BYPASS); + break; + + case NAND_CMD_READ0: + trfr_sz += mtd->writesize + mtd->oobsize; + vf610_nfc_transfer_size(nfc, trfr_sz); + vf610_nfc_ecc_mode(nfc, nfc->ecc_mode); + vf610_nfc_send_commands(nfc, NAND_CMD_READ0, + NAND_CMD_READSTART, READ_PAGE_CMD_CODE); + vf610_nfc_addr_cycle(nfc, column, page); + break; + + case NAND_CMD_PARAM: + nfc->alt_buf = ALT_BUF_ONFI; + trfr_sz = 3 * sizeof(struct nand_onfi_params); + vf610_nfc_transfer_size(nfc, trfr_sz); + vf610_nfc_send_command(nfc, command, READ_ONFI_PARAM_CMD_CODE); + vf610_nfc_set_field(nfc, NFC_ROW_ADDR, ROW_ADDR_MASK, + ROW_ADDR_SHIFT, column); + vf610_nfc_ecc_mode(nfc, ECC_BYPASS); + break; + + case NAND_CMD_ERASE1: + vf610_nfc_transfer_size(nfc, 0); + vf610_nfc_send_commands(nfc, command, + NAND_CMD_ERASE2, ERASE_CMD_CODE); + vf610_nfc_addr_cycle(nfc, column, page); + break; + + case NAND_CMD_READID: + nfc->alt_buf = ALT_BUF_ID; + nfc->buf_offset = 0; + vf610_nfc_transfer_size(nfc, 0); + vf610_nfc_send_command(nfc, command, READ_ID_CMD_CODE); + vf610_nfc_set_field(nfc, NFC_ROW_ADDR, ROW_ADDR_MASK, + ROW_ADDR_SHIFT, column); + break; + + case NAND_CMD_STATUS: + nfc->alt_buf = ALT_BUF_STAT; + vf610_nfc_transfer_size(nfc, 0); + vf610_nfc_send_command(nfc, command, STATUS_READ_CMD_CODE); + break; + default: + return; + } + + vf610_nfc_done(nfc); + + nfc->use_hw_ecc = false; + nfc->write_sz = 0; +} + +static void vf610_nfc_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + uint c = nfc->buf_offset; + + /* Alternate buffers are only supported through read_byte */ + WARN_ON(nfc->alt_buf); + + vf610_nfc_memcpy(buf, nfc->regs + NFC_MAIN_AREA(0) + c, len); + + nfc->buf_offset += len; +} + +static void vf610_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, + int len) +{ + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + uint c = nfc->buf_offset; + uint l; + + l = min_t(uint, len, mtd->writesize + mtd->oobsize - c); + vf610_nfc_memcpy(nfc->regs + NFC_MAIN_AREA(0) + c, buf, l); + + nfc->write_sz += l; + nfc->buf_offset += l; +} + +static uint8_t vf610_nfc_read_byte(struct mtd_info *mtd) +{ + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + u8 tmp; + uint c = nfc->buf_offset; + + switch (nfc->alt_buf) { + case ALT_BUF_ID: + tmp = vf610_nfc_get_id(nfc, c); + break; + case ALT_BUF_STAT: + tmp = vf610_nfc_get_status(nfc); + break; +#ifdef __LITTLE_ENDIAN + case ALT_BUF_ONFI: + /* Reverse byte since the controller uses big endianness */ + c = nfc->buf_offset ^ 0x3; + /* fall-through */ +#endif + default: + tmp = *((u8 *)(nfc->regs + NFC_MAIN_AREA(0) + c)); + break; + } + nfc->buf_offset++; + return tmp; +} + +static u16 vf610_nfc_read_word(struct mtd_info *mtd) +{ + u16 tmp; + + vf610_nfc_read_buf(mtd, (u_char *)&tmp, sizeof(tmp)); + return tmp; +} + +/* If not provided, upper layers apply a fixed delay. */ +static int vf610_nfc_dev_ready(struct mtd_info *mtd) +{ + /* NFC handles R/B internally; always ready. */ + return 1; +} + +/* + * This function supports Vybrid only (MPC5125 would have full RB and four CS) + */ +static void vf610_nfc_select_chip(struct mtd_info *mtd, int chip) +{ + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + u32 tmp = vf610_nfc_read(nfc, NFC_ROW_ADDR); + + /* Vybrid only (MPC5125 would have full RB and four CS) */ + if (nfc->variant != NFC_VFC610) + return; + + tmp &= ~(ROW_ADDR_CHIP_SEL_RB_MASK | ROW_ADDR_CHIP_SEL_MASK); + + if (chip >= 0) { + tmp |= 1 << ROW_ADDR_CHIP_SEL_RB_SHIFT; + tmp |= BIT(chip) << ROW_ADDR_CHIP_SEL_SHIFT; + } + + vf610_nfc_write(nfc, NFC_ROW_ADDR, tmp); +} + +/* Count the number of 0's in buff up to max_bits */ +static inline int count_written_bits(uint8_t *buff, int size, int max_bits) +{ + uint32_t *buff32 = (uint32_t *)buff; + int k, written_bits = 0; + + for (k = 0; k < (size / 4); k++) { + written_bits += hweight32(~buff32[k]); + if (unlikely(written_bits > max_bits)) + break; + } + + return written_bits; +} + +static inline int vf610_nfc_correct_data(struct mtd_info *mtd, uint8_t *dat, + uint8_t *oob, int page) +{ + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + u32 ecc_status_off = NFC_MAIN_AREA(0) + ECC_SRAM_ADDR + ECC_STATUS; + u8 ecc_status; + u8 ecc_count; + int flips; + int flips_threshold = nfc->chip.ecc.strength / 2; + + ecc_status = vf610_nfc_read(nfc, ecc_status_off) & 0xff; + ecc_count = ecc_status & ECC_STATUS_ERR_COUNT; + + if (!(ecc_status & ECC_STATUS_MASK)) + return ecc_count; + + /* Read OOB without ECC unit enabled */ + vf610_nfc_command(mtd, NAND_CMD_READOOB, 0, page); + vf610_nfc_read_buf(mtd, oob, mtd->oobsize); + + /* + * On an erased page, bit count (including OOB) should be zero or + * at least less then half of the ECC strength. + */ + flips = count_written_bits(dat, nfc->chip.ecc.size, flips_threshold); + flips += count_written_bits(oob, mtd->oobsize, flips_threshold); + + if (unlikely(flips > flips_threshold)) + return -EINVAL; + + /* Erased page. */ + memset(dat, 0xff, nfc->chip.ecc.size); + memset(oob, 0xff, mtd->oobsize); + return flips; +} + +static int vf610_nfc_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf, int oob_required, int page) +{ + int eccsize = chip->ecc.size; + int stat; + + vf610_nfc_read_buf(mtd, buf, eccsize); + if (oob_required) + vf610_nfc_read_buf(mtd, chip->oob_poi, mtd->oobsize); + + stat = vf610_nfc_correct_data(mtd, buf, chip->oob_poi, page); + + if (stat < 0) { + mtd->ecc_stats.failed++; + return 0; + } else { + mtd->ecc_stats.corrected += stat; + return stat; + } +} + +static int vf610_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf, int oob_required) +{ + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + + vf610_nfc_write_buf(mtd, buf, mtd->writesize); + if (oob_required) + vf610_nfc_write_buf(mtd, chip->oob_poi, mtd->oobsize); + + /* Always write whole page including OOB due to HW ECC */ + nfc->use_hw_ecc = true; + nfc->write_sz = mtd->writesize + mtd->oobsize; + + return 0; +} + +static const struct of_device_id vf610_nfc_dt_ids[] = { + { .compatible = "fsl,vf610-nfc", .data = (void *)NFC_VFC610 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vf610_nfc_dt_ids); + +static void vf610_nfc_preinit_controller(struct vf610_nfc *nfc) +{ + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT); + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_ADDR_AUTO_INCR_BIT); + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_BUFNO_AUTO_INCR_BIT); + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_BOOT_MODE_BIT); + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_DMA_REQ_BIT); + vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_FAST_FLASH_BIT); + + /* Disable virtual pages, only one elementary transfer unit */ + vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, CONFIG_PAGE_CNT_MASK, + CONFIG_PAGE_CNT_SHIFT, 1); +} + +static void vf610_nfc_init_controller(struct vf610_nfc *nfc) +{ + if (nfc->chip.options & NAND_BUSWIDTH_16) + vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT); + else + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT); + + if (nfc->chip.ecc.mode == NAND_ECC_HW) { + /* Set ECC status offset in SRAM */ + vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, + CONFIG_ECC_SRAM_ADDR_MASK, + CONFIG_ECC_SRAM_ADDR_SHIFT, + ECC_SRAM_ADDR >> 3); + + /* Enable ECC status in SRAM */ + vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_ECC_SRAM_REQ_BIT); + } +} + +static int vf610_nfc_probe(struct platform_device *pdev) +{ + struct vf610_nfc *nfc; + struct resource *res; + struct mtd_info *mtd; + struct nand_chip *chip; + struct device_node *child; + const struct of_device_id *of_id; + int err = 0; + int irq; + + nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = &pdev->dev; + mtd = &nfc->mtd; + chip = &nfc->chip; + + mtd->priv = chip; + mtd->owner = THIS_MODULE; + mtd->dev.parent = nfc->dev; + mtd->name = DRV_NAME; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nfc->regs = devm_ioremap_resource(nfc->dev, res); + if (IS_ERR(nfc->regs)) + return PTR_ERR(nfc->regs); + + nfc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(nfc->clk)) + return PTR_ERR(nfc->clk); + + err = clk_prepare_enable(nfc->clk); + if (err) { + dev_err(nfc->dev, "Unable to enable clock!\n"); + return err; + } + + of_id = of_match_device(vf610_nfc_dt_ids, &pdev->dev); + nfc->variant = (enum vf610_nfc_variant)of_id->data; + + for_each_available_child_of_node(nfc->dev->of_node, child) { + if (of_device_is_compatible(child, "fsl,vf610-nfc-nandcs")) { + + if (chip->dn) { + dev_err(nfc->dev, + "Only one NAND chip supported!\n"); + err = -EINVAL; + goto error; + } + + chip->dn = child; + } + } + + if (!chip->dn) { + dev_err(nfc->dev, "NAND chip sub-node missing!\n"); + err = -ENODEV; + goto err_clk; + } + + chip->dev_ready = vf610_nfc_dev_ready; + chip->cmdfunc = vf610_nfc_command; + chip->read_byte = vf610_nfc_read_byte; + chip->read_word = vf610_nfc_read_word; + chip->read_buf = vf610_nfc_read_buf; + chip->write_buf = vf610_nfc_write_buf; + chip->select_chip = vf610_nfc_select_chip; + + chip->options |= NAND_NO_SUBPAGE_WRITE; + + init_completion(&nfc->cmd_done); + + err = devm_request_irq(nfc->dev, irq, vf610_nfc_irq, 0, DRV_NAME, mtd); + if (err) { + dev_err(nfc->dev, "Error requesting IRQ!\n"); + goto error; + } + + vf610_nfc_preinit_controller(nfc); + + /* first scan to find the device and get the page size */ + if (nand_scan_ident(mtd, 1, NULL)) { + err = -ENXIO; + goto error; + } + + vf610_nfc_init_controller(nfc); + + /* Bad block options. */ + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + /* Single buffer only, max 256 OOB minus ECC status */ + if (mtd->writesize + mtd->oobsize > PAGE_2K + OOB_MAX - 8) { + dev_err(nfc->dev, "Unsupported flash page size\n"); + err = -ENXIO; + goto error; + } + + if (chip->ecc.mode == NAND_ECC_HW) { + if (mtd->writesize != PAGE_2K && mtd->oobsize < 64) { + dev_err(nfc->dev, "Unsupported flash with hwecc\n"); + err = -ENXIO; + goto error; + } + + if (chip->ecc.size != mtd->writesize) { + dev_err(nfc->dev, "Step size needs to be page size\n"); + err = -ENXIO; + goto error; + } + + /* Only 64 byte ECC layouts known */ + if (mtd->oobsize > 64) + mtd->oobsize = 64; + + if (chip->ecc.strength == 32) { + nfc->ecc_mode = ECC_60_BYTE; + chip->ecc.bytes = 60; + chip->ecc.layout = &vf610_nfc_ecc60; + } else if (chip->ecc.strength == 24) { + nfc->ecc_mode = ECC_45_BYTE; + chip->ecc.bytes = 45; + chip->ecc.layout = &vf610_nfc_ecc45; + } else { + dev_err(nfc->dev, "Unsupported ECC strength\n"); + err = -ENXIO; + goto error; + } + + /* propagate ecc.layout to mtd_info */ + mtd->ecclayout = chip->ecc.layout; + chip->ecc.read_page = vf610_nfc_read_page; + chip->ecc.write_page = vf610_nfc_write_page; + + chip->ecc.size = PAGE_2K; + } + + /* second phase scan */ + if (nand_scan_tail(mtd)) { + err = -ENXIO; + goto error; + } + + platform_set_drvdata(pdev, mtd); + + /* Register device in MTD */ + return mtd_device_parse_register(mtd, NULL, + &(struct mtd_part_parser_data){ + .of_node = chip->dn, + }, + NULL, 0); + +error: + of_node_put(chip->dn); +err_clk: + clk_disable_unprepare(nfc->clk); + return err; +} + +static int vf610_nfc_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + + nand_release(mtd); + clk_disable_unprepare(nfc->clk); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int vf610_nfc_suspend(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + + clk_disable_unprepare(nfc->clk); + return 0; +} + +static int vf610_nfc_resume(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + + pinctrl_pm_select_default_state(dev); + + clk_prepare_enable(nfc->clk); + + vf610_nfc_preinit_controller(nfc); + vf610_nfc_init_controller(nfc); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(vf610_nfc_pm_ops, vf610_nfc_suspend, vf610_nfc_resume); + +static struct platform_driver vf610_nfc_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = vf610_nfc_dt_ids, + .pm = &vf610_nfc_pm_ops, + }, + .probe = vf610_nfc_probe, + .remove = vf610_nfc_remove, +}; + +module_platform_driver(vf610_nfc_driver); + +MODULE_AUTHOR("Stefan Agner <stefan.agner@toradex.com>"); +MODULE_DESCRIPTION("Freescale VF610/MPC5125 NFC MTD NAND driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pinctrl/freescale/pinctrl-imx.c b/drivers/pinctrl/freescale/pinctrl-imx.c index e261f1cf85c6..fa46971379cf 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx.c +++ b/drivers/pinctrl/freescale/pinctrl-imx.c @@ -23,6 +23,7 @@ #include <linux/pinctrl/pinctrl.h> #include <linux/pinctrl/pinmux.h> #include <linux/slab.h> +#include <linux/syscore_ops.h> #include "../core.h" #include "pinctrl-imx.h" @@ -40,6 +41,8 @@ struct imx_pinctrl { struct pinctrl_dev *pctl; void __iomem *base; const struct imx_pinctrl_soc_info *info; + u32 *mux_regs; + u32 *input_regs; }; static const inline struct imx_pin_group *imx_pinctrl_find_group_by_name( @@ -643,6 +646,53 @@ static int imx_pinctrl_probe_dt(struct platform_device *pdev, return 0; } +static int __maybe_unused imx_pinctrl_suspend(struct device *dev) +{ + struct imx_pinctrl *ipctl = dev_get_drvdata(dev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + int i; + + for (i = 0; i < info->npins; i++) { + const struct imx_pin_reg *pin_reg = &info->pin_regs[i]; + if (pin_reg->mux_reg == -1) + continue; + + ipctl->mux_regs[i] = readl(ipctl->base + pin_reg->mux_reg); + } + + for (i = 0; i < info->ninput_regs; i++) + ipctl->input_regs[i] = readl(ipctl->base + + info->input_regs_offset + i * sizeof(u32 *)); + + return 0; +} + +static int __maybe_unused imx_pinctrl_resume(struct device *dev) +{ + struct imx_pinctrl *ipctl = dev_get_drvdata(dev); + const struct imx_pinctrl_soc_info *info = ipctl->info; + const struct imx_pin_reg *pin_reg; + int i; + + for (i = 0; i < info->npins; i++) { + pin_reg = &info->pin_regs[i]; + if (pin_reg->mux_reg == -1) + continue; + + writel(ipctl->mux_regs[i], ipctl->base + pin_reg->mux_reg); + } + + for (i = 0; i < info->ninput_regs; i++) + writel(ipctl->input_regs[i], ipctl->base + + info->input_regs_offset + i * sizeof(u32 *)); + + return 0; +} + +const struct dev_pm_ops imx_pinctrl_dev_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_pinctrl_suspend, imx_pinctrl_resume) +}; + int imx_pinctrl_probe(struct platform_device *pdev, struct imx_pinctrl_soc_info *info) { @@ -671,6 +721,18 @@ int imx_pinctrl_probe(struct platform_device *pdev, info->pin_regs[i].conf_reg = -1; } +#ifdef CONFIG_PM_SLEEP + ipctl->mux_regs = devm_kzalloc(&pdev->dev, sizeof(u32 *) * + info->npins, GFP_KERNEL); + if (!ipctl->mux_regs) + return -ENOMEM; + + ipctl->input_regs = devm_kzalloc(&pdev->dev, sizeof(u32 *) * + info->ninput_regs, GFP_KERNEL); + if (!ipctl->input_regs) + return -ENOMEM; +#endif + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ipctl->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(ipctl->base)) diff --git a/drivers/pinctrl/freescale/pinctrl-imx.h b/drivers/pinctrl/freescale/pinctrl-imx.h index 49e55d39f7c8..6da680675bd9 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx.h +++ b/drivers/pinctrl/freescale/pinctrl-imx.h @@ -80,6 +80,8 @@ struct imx_pinctrl_soc_info { unsigned int ngroups; struct imx_pmx_func *functions; unsigned int nfunctions; + unsigned int input_regs_offset; + unsigned int ninput_regs; unsigned int flags; }; @@ -97,4 +99,5 @@ struct imx_pinctrl_soc_info { int imx_pinctrl_probe(struct platform_device *pdev, struct imx_pinctrl_soc_info *info); int imx_pinctrl_remove(struct platform_device *pdev); +extern const struct dev_pm_ops imx_pinctrl_dev_pm_ops; #endif /* __DRIVERS_PINCTRL_IMX_H */ diff --git a/drivers/pinctrl/freescale/pinctrl-vf610.c b/drivers/pinctrl/freescale/pinctrl-vf610.c index 37a037543d29..3d5148e267fa 100644 --- a/drivers/pinctrl/freescale/pinctrl-vf610.c +++ b/drivers/pinctrl/freescale/pinctrl-vf610.c @@ -19,6 +19,9 @@ #include "pinctrl-imx.h" +#define VF610_INPUT_REG_CNT 49 +#define VF610_INPUT_REG_BASE 0x2ec + enum vf610_pads { VF610_PAD_PTA6 = 0, VF610_PAD_PTA8 = 1, @@ -299,6 +302,8 @@ static const struct pinctrl_pin_desc vf610_pinctrl_pads[] = { static struct imx_pinctrl_soc_info vf610_pinctrl_info = { .pins = vf610_pinctrl_pads, .npins = ARRAY_SIZE(vf610_pinctrl_pads), + .input_regs_offset = VF610_INPUT_REG_BASE, + .ninput_regs = VF610_INPUT_REG_CNT, .flags = SHARE_MUX_CONF_REG, }; @@ -316,6 +321,7 @@ static struct platform_driver vf610_pinctrl_driver = { .driver = { .name = "vf610-pinctrl", .of_match_table = vf610_pinctrl_of_match, + .pm = &imx_pinctrl_dev_pm_ops, }, .probe = vf610_pinctrl_probe, .remove = imx_pinctrl_remove, diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c index f9dfc8b6407a..5e1dd9605070 100644 --- a/drivers/pwm/pwm-fsl-ftm.c +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -80,7 +80,6 @@ struct fsl_pwm_chip { struct mutex lock; - unsigned int use_count; unsigned int cnt_select; unsigned int clk_ps; @@ -300,9 +299,6 @@ static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc) { int ret; - if (fpc->use_count++ != 0) - return 0; - /* select counter clock source */ regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, FTM_SC_CLK(fpc->cnt_select)); @@ -334,25 +330,6 @@ static int fsl_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) return ret; } -static void fsl_counter_clock_disable(struct fsl_pwm_chip *fpc) -{ - /* - * already disabled, do nothing - */ - if (fpc->use_count == 0) - return; - - /* there are still users, so can't disable yet */ - if (--fpc->use_count > 0) - return; - - /* no users left, disable PWM counter clock */ - regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, 0); - - clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); - clk_disable_unprepare(fpc->clk[fpc->cnt_select]); -} - static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct fsl_pwm_chip *fpc = to_fsl_chip(chip); @@ -362,7 +339,8 @@ static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), BIT(pwm->hwpwm)); - fsl_counter_clock_disable(fpc); + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); + clk_disable_unprepare(fpc->clk[fpc->cnt_select]); regmap_read(fpc->regmap, FTM_OUTMASK, &val); if ((val & 0xFF) == 0xFF) @@ -492,17 +470,24 @@ static int fsl_pwm_remove(struct platform_device *pdev) static int fsl_pwm_suspend(struct device *dev) { struct fsl_pwm_chip *fpc = dev_get_drvdata(dev); - u32 val; + int i; regcache_cache_only(fpc->regmap, true); regcache_mark_dirty(fpc->regmap); - /* read from cache */ - regmap_read(fpc->regmap, FTM_OUTMASK, &val); - if ((val & 0xFF) != 0xFF) { + for (i = 0; i < fpc->chip.npwm; i++) { + struct pwm_device *pwm = &fpc->chip.pwms[i]; + + if (!test_bit(PWMF_REQUESTED, &pwm->flags)) + continue; + + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]); + + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + continue; + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); clk_disable_unprepare(fpc->clk[fpc->cnt_select]); - clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]); } return 0; @@ -511,12 +496,19 @@ static int fsl_pwm_suspend(struct device *dev) static int fsl_pwm_resume(struct device *dev) { struct fsl_pwm_chip *fpc = dev_get_drvdata(dev); - u32 val; + int i; + + for (i = 0; i < fpc->chip.npwm; i++) { + struct pwm_device *pwm = &fpc->chip.pwms[i]; + + if (!test_bit(PWMF_REQUESTED, &pwm->flags)) + continue; - /* read from cache */ - regmap_read(fpc->regmap, FTM_OUTMASK, &val); - if ((val & 0xFF) != 0xFF) { clk_prepare_enable(fpc->clk[FSL_PWM_CLK_SYS]); + + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + continue; + clk_prepare_enable(fpc->clk[fpc->cnt_select]); clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); } diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index d8bde82f0370..8b4dd2be3bdb 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -1,5 +1,6 @@ menu "SOC (System On Chip) specific Drivers" +source "drivers/soc/fsl/Kconfig" source "drivers/soc/mediatek/Kconfig" source "drivers/soc/qcom/Kconfig" source "drivers/soc/ti/Kconfig" diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 70042b259744..142676ea2996 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -2,6 +2,7 @@ # Makefile for the Linux Kernel SOC specific device drivers. # +obj-$(CONFIG_SOC_VF610) += fsl/ obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/ obj-$(CONFIG_ARCH_QCOM) += qcom/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig new file mode 100644 index 000000000000..b1461aff4065 --- /dev/null +++ b/drivers/soc/fsl/Kconfig @@ -0,0 +1,9 @@ +# +# Freescale SoC drivers + +config SOC_VF610 + bool "SoC bus device for the Freescale Vybrid platform" + select SOC_BUS + help + Include support for the SoC bus on the Freescale Vybrid platform + providing some sysfs information about the module variant.
\ No newline at end of file diff --git a/drivers/soc/fsl/Makefile b/drivers/soc/fsl/Makefile new file mode 100644 index 000000000000..5fccbbad7f58 --- /dev/null +++ b/drivers/soc/fsl/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SOC_VF610) += soc-vf610.o diff --git a/drivers/soc/fsl/soc-vf610.c b/drivers/soc/fsl/soc-vf610.c new file mode 100644 index 000000000000..6425cfb16e1b --- /dev/null +++ b/drivers/soc/fsl/soc-vf610.c @@ -0,0 +1,168 @@ +/* + * Copyright 2015 Toradex AG + * + * Author: Sanchayan Maity <sanchayan.maity@toradex.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. + * + */ + +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/random.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> + +#define DRIVER_NAME "vf610-soc-bus" + +#define MSCM_CPxCOUNT_OFFSET 0x0000002C +#define MSCM_CPxCFG1_OFFSET 0x00000014 + +static struct soc_device_attribute *soc_dev_attr; +static struct soc_device *soc_dev; + +static int vf610_soc_probe(struct platform_device *pdev) +{ + struct regmap *ocotp_regmap, *mscm_regmap, *rom_regmap; + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct device_node *soc_node; + struct of_phandle_args pargs; + char soc_type[] = "xx0"; + u32 cfg0_offset, cfg1_offset, rom_rev_offset; + u32 soc_id1, soc_id2, rom_rev; + u32 cpxcount, cpxcfg1; + u64 soc_id; + int ret; + + mscm_regmap = syscon_node_to_regmap(node); + if (IS_ERR(mscm_regmap)) { + dev_err(dev, "regmap lookup for mscm failed\n"); + return PTR_ERR(mscm_regmap); + } + + soc_node = of_find_node_by_path("/soc"); + + ret = of_parse_phandle_with_fixed_args(soc_node, + "ocotp-cfg", 2, 0, &pargs); + if (ret) { + dev_err(dev, "lookup failed for ocotp-cfg node %d\n", ret); + return ret; + } + + ocotp_regmap = syscon_node_to_regmap(pargs.np); + if (IS_ERR(ocotp_regmap)) { + of_node_put(pargs.np); + dev_err(dev, "regmap lookup for ocotp failed\n"); + return PTR_ERR(ocotp_regmap); + } + + cfg0_offset = pargs.args[0]; + cfg1_offset = pargs.args[1]; + of_node_put(pargs.np); + + ret = of_parse_phandle_with_fixed_args(soc_node, + "rom-revision", 1, 0, &pargs); + if (ret) { + dev_err(dev, "lookup failed for rom-revision node %d\n", ret); + return ret; + } + + rom_regmap = syscon_node_to_regmap(pargs.np); + if (IS_ERR(rom_regmap)) { + of_node_put(pargs.np); + dev_err(dev, "regmap lookup for ocrom failed\n"); + return PTR_ERR(rom_regmap); + } + + rom_rev_offset = pargs.args[0]; + of_node_put(pargs.np); + + ret = regmap_read(ocotp_regmap, cfg0_offset, &soc_id1); + if (ret) + return -ENODEV; + + ret = regmap_read(ocotp_regmap, cfg1_offset, &soc_id2); + if (ret) + return -ENODEV; + + soc_id = (u64) soc_id1 << 32 | soc_id2; + add_device_randomness(&soc_id, sizeof(soc_id)); + + ret = regmap_read(mscm_regmap, MSCM_CPxCOUNT_OFFSET, &cpxcount); + if (ret) + return -ENODEV; + + ret = regmap_read(mscm_regmap, MSCM_CPxCFG1_OFFSET, &cpxcfg1); + if (ret) + return -ENODEV; + + soc_type[0] = cpxcount ? '6' : '5'; /* Dual Core => VF6x0 */ + soc_type[1] = cpxcfg1 ? '1' : '0'; /* L2 Cache => VFx10 */ + + ret = regmap_read(rom_regmap, rom_rev_offset, &rom_rev); + if (ret) + return -ENODEV; + + soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); + if (!soc_dev_attr) + return -ENOMEM; + + soc_dev_attr->machine = kasprintf(GFP_KERNEL, "Freescale Vybrid"); + soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%016llx", soc_id); + soc_dev_attr->family = kasprintf(GFP_KERNEL, "Freescale Vybrid VF%s", + soc_type); + soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%08x", rom_rev); + + soc_dev = soc_device_register(soc_dev_attr); + if (IS_ERR(soc_dev)) { + kfree(soc_dev_attr->revision); + kfree(soc_dev_attr->family); + kfree(soc_dev_attr->soc_id); + kfree(soc_dev_attr->machine); + kfree(soc_dev_attr); + return -ENODEV; + } + + return 0; +} + +static int vf610_soc_remove(struct platform_device *pdev) +{ + if (soc_dev_attr) { + kfree(soc_dev_attr->revision); + kfree(soc_dev_attr->family); + kfree(soc_dev_attr->soc_id); + kfree(soc_dev_attr->machine); + kfree(soc_dev_attr); + } + + if (soc_dev) + soc_device_unregister(soc_dev); + + return 0; +} + +static const struct of_device_id vf610_soc_bus_match[] = { + { .compatible = "fsl,vf610-mscm-cpucfg", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vf610_soc_bus_match); + +static struct platform_driver vf610_soc_driver = { + .probe = vf610_soc_probe, + .remove = vf610_soc_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = vf610_soc_bus_match, + }, +}; + +module_platform_driver(vf610_soc_driver); + +MODULE_DESCRIPTION("Freescale VF610 SoC bus driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index 5fe54cda309f..b21c3c01df6d 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -24,6 +24,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> +#include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -137,7 +138,7 @@ static inline int is_double_byte_mode(struct fsl_dspi *dspi) { unsigned int val; - regmap_read(dspi->regmap, SPI_CTAR(dspi->cs), &val); + regmap_read(dspi->regmap, SPI_CTAR(0), &val); return ((val & SPI_FRAME_BITS_MASK) == SPI_FRAME_BITS(8)) ? 0 : 1; } @@ -230,7 +231,7 @@ static int dspi_transfer_write(struct fsl_dspi *dspi) */ if (tx_word && (dspi->len == 1)) { dspi->dataflags |= TRAN_STATE_WORD_ODD_NUM; - regmap_update_bits(dspi->regmap, SPI_CTAR(dspi->cs), + regmap_update_bits(dspi->regmap, SPI_CTAR(0), SPI_FRAME_BITS_MASK, SPI_FRAME_BITS(8)); tx_word = 0; } @@ -249,7 +250,7 @@ static int dspi_transfer_write(struct fsl_dspi *dspi) dspi_pushr = SPI_PUSHR_TXDATA(d16) | SPI_PUSHR_PCS(dspi->cs) | - SPI_PUSHR_CTAS(dspi->cs) | + SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; dspi->len -= 2; @@ -264,7 +265,7 @@ static int dspi_transfer_write(struct fsl_dspi *dspi) dspi_pushr = SPI_PUSHR_TXDATA(d8) | SPI_PUSHR_PCS(dspi->cs) | - SPI_PUSHR_CTAS(dspi->cs) | + SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; dspi->len--; @@ -364,10 +365,10 @@ static int dspi_transfer_one_message(struct spi_master *master, regmap_update_bits(dspi->regmap, SPI_MCR, SPI_MCR_CLR_TXF | SPI_MCR_CLR_RXF, SPI_MCR_CLR_TXF | SPI_MCR_CLR_RXF); - regmap_write(dspi->regmap, SPI_CTAR(dspi->cs), + regmap_write(dspi->regmap, SPI_CTAR(0), dspi->cur_chip->ctar_val); if (transfer->speed_hz) - regmap_write(dspi->regmap, SPI_CTAR(dspi->cs), + regmap_write(dspi->regmap, SPI_CTAR(0), dspi->cur_chip->ctar_val); regmap_write(dspi->regmap, SPI_RSER, SPI_RSER_EOQFE); @@ -468,7 +469,7 @@ static irqreturn_t dspi_interrupt(int irq, void *dev_id) if (!dspi->len) { if (dspi->dataflags & TRAN_STATE_WORD_ODD_NUM) - regmap_update_bits(dspi->regmap, SPI_CTAR(dspi->cs), + regmap_update_bits(dspi->regmap, SPI_CTAR(0), SPI_FRAME_BITS_MASK, SPI_FRAME_BITS(16)); dspi->waitflags = 1; @@ -494,6 +495,8 @@ static int dspi_suspend(struct device *dev) spi_master_suspend(master); clk_disable_unprepare(dspi->clk); + pinctrl_pm_select_sleep_state(dev); + return 0; } @@ -502,6 +505,8 @@ static int dspi_resume(struct device *dev) struct spi_master *master = dev_get_drvdata(dev); struct fsl_dspi *dspi = spi_master_get_devdata(master); + pinctrl_pm_select_default_state(dev); + clk_prepare_enable(dspi->clk); spi_master_resume(master); diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index 08ce76f4f261..be6c011b642d 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -230,6 +230,9 @@ #define DEV_NAME "ttyLP" #define UART_NR 6 +static bool nodma = true; +module_param(nodma, bool, S_IRUGO); + struct lpuart_port { struct uart_port port; struct clk *clk; @@ -342,38 +345,19 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport, FSL_UART_RX_DMA_BUFFER_SIZE, DMA_TO_DEVICE); } -static void lpuart_pio_tx(struct lpuart_port *sport) -{ - struct circ_buf *xmit = &sport->port.state->xmit; - unsigned long flags; - - spin_lock_irqsave(&sport->port.lock, flags); - - while (!uart_circ_empty(xmit) && - readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size) { - writeb(xmit->buf[xmit->tail], sport->port.membase + UARTDR); - xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); - sport->port.icount.tx++; - } - - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) - uart_write_wakeup(&sport->port); - - if (uart_circ_empty(xmit)) - writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS, - sport->port.membase + UARTCR5); - - spin_unlock_irqrestore(&sport->port.lock, flags); -} - -static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count) +static int lpuart_dma_tx(struct lpuart_port *sport) { struct circ_buf *xmit = &sport->port.state->xmit; + unsigned long count = CIRC_CNT_TO_END(xmit->head, + xmit->tail, UART_XMIT_SIZE); dma_addr_t tx_bus_addr; + if (!count) + return 0; + dma_sync_single_for_device(sport->port.dev, sport->dma_tx_buf_bus, UART_XMIT_SIZE, DMA_TO_DEVICE); - sport->dma_tx_bytes = count & ~(sport->txfifo_size - 1); + sport->dma_tx_bytes = count; tx_bus_addr = sport->dma_tx_buf_bus + xmit->tail; sport->dma_tx_desc = dmaengine_prep_slave_single(sport->dma_tx_chan, tx_bus_addr, sport->dma_tx_bytes, @@ -393,25 +377,6 @@ static int lpuart_dma_tx(struct lpuart_port *sport, unsigned long count) return 0; } -static void lpuart_prepare_tx(struct lpuart_port *sport) -{ - struct circ_buf *xmit = &sport->port.state->xmit; - unsigned long count = CIRC_CNT_TO_END(xmit->head, - xmit->tail, UART_XMIT_SIZE); - - if (!count) - return; - - if (count < sport->txfifo_size) - writeb(readb(sport->port.membase + UARTCR5) & ~UARTCR5_TDMAS, - sport->port.membase + UARTCR5); - else { - writeb(readb(sport->port.membase + UARTCR5) | UARTCR5_TDMAS, - sport->port.membase + UARTCR5); - lpuart_dma_tx(sport, count); - } -} - static void lpuart_dma_tx_complete(void *arg) { struct lpuart_port *sport = arg; @@ -428,7 +393,7 @@ static void lpuart_dma_tx_complete(void *arg) if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&sport->port); - lpuart_prepare_tx(sport); + lpuart_dma_tx(sport); spin_unlock_irqrestore(&sport->port.lock, flags); } @@ -483,9 +448,8 @@ static void lpuart_dma_rx_complete(void *arg) spin_unlock_irqrestore(&sport->port.lock, flags); } -static void lpuart_timer_func(unsigned long data) +static void lpuart_dma_rx_terminate(struct lpuart_port *sport) { - struct lpuart_port *sport = (struct lpuart_port *)data; struct tty_port *port = &sport->port.state->port; struct dma_tx_state state; unsigned long flags; @@ -510,6 +474,11 @@ static void lpuart_timer_func(unsigned long data) spin_unlock_irqrestore(&sport->port.lock, flags); } +static void lpuart_timer_func(unsigned long data) +{ + lpuart_dma_rx_terminate((struct lpuart_port *)data); +} + static inline void lpuart_prepare_rx(struct lpuart_port *sport) { unsigned long flags; @@ -581,7 +550,7 @@ static void lpuart_start_tx(struct uart_port *port) if (sport->lpuart_dma_tx_use) { if (!uart_circ_empty(xmit) && !sport->dma_tx_in_progress) - lpuart_prepare_tx(sport); + lpuart_dma_tx(sport); } else { if (readb(port->membase + UARTSR1) & UARTSR1_TDRE) lpuart_transmit_buffer(sport); @@ -778,10 +747,8 @@ static irqreturn_t lpuart_int(int irq, void *dev_id) lpuart_rxint(irq, dev_id); } if (sts & UARTSR1_TDRE && !(crdma & UARTCR5_TDMAS)) { - if (sport->lpuart_dma_tx_use) - lpuart_pio_tx(sport); - else - lpuart_txint(irq, dev_id); + BUG_ON(sport->lpuart_dma_tx_use); + lpuart_txint(irq, dev_id); } return IRQ_HANDLED; @@ -810,8 +777,18 @@ static irqreturn_t lpuart32_int(int irq, void *dev_id) /* return TIOCSER_TEMT when transmitter is not busy */ static unsigned int lpuart_tx_empty(struct uart_port *port) { - return (readb(port->membase + UARTSR1) & UARTSR1_TC) ? - TIOCSER_TEMT : 0; + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); + unsigned char sr1 = readb(port->membase + UARTSR1); + unsigned char sfifo = readb(port->membase + UARTSFIFO); + + if (sport->dma_tx_in_progress) + return 0; + + if (sr1 & UARTSR1_TC && sfifo & UARTSFIFO_TXEMPT) + return TIOCSER_TEMT; + + return 0; } static unsigned int lpuart32_tx_empty(struct uart_port *port) @@ -820,6 +797,52 @@ static unsigned int lpuart32_tx_empty(struct uart_port *port) TIOCSER_TEMT : 0; } +static int lpuart_config_rs485(struct uart_port *port, + struct serial_rs485 *rs485) +{ + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); + u8 modem = readb(sport->port.membase + UARTMODEM) & + ~(UARTMODEM_TXRTSPOL | UARTMODEM_TXRTSE); + writeb(modem, sport->port.membase + UARTMODEM); + + if (rs485->flags & SER_RS485_ENABLED) { + /* Enable auto RS-485 RTS mode */ + modem |= UARTMODEM_TXRTSE; + + /* + * RTS needs to be logic HIGH either during transer _or_ after + * transfer, other variants are not supported by the hardware. + */ + + if (!(rs485->flags & (SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND))) + rs485->flags |= SER_RS485_RTS_ON_SEND; + + if (rs485->flags & SER_RS485_RTS_ON_SEND && + rs485->flags & SER_RS485_RTS_AFTER_SEND) + + rs485->flags &= ~SER_RS485_RTS_AFTER_SEND; + + /* + * The hardware defaults to RTS logic HIGH while transfer. + * Switch polarity in case RTS shall be logic HIGH + * after transfer. + * Note: UART is assumed to be active high. + */ + if (rs485->flags & SER_RS485_RTS_ON_SEND) + modem &= ~UARTMODEM_TXRTSPOL; + else if (rs485->flags & SER_RS485_RTS_AFTER_SEND) + modem |= UARTMODEM_TXRTSPOL; + } + + /* Store the new configuration */ + sport->port.rs485 = *rs485; + + writeb(modem, sport->port.membase + UARTMODEM); + return 0; +} + static unsigned int lpuart_get_mctrl(struct uart_port *port) { unsigned int temp = 0; @@ -852,18 +875,23 @@ static unsigned int lpuart32_get_mctrl(struct uart_port *port) static void lpuart_set_mctrl(struct uart_port *port, unsigned int mctrl) { + struct lpuart_port *sport = container_of(port, + struct lpuart_port, port); unsigned char temp; - temp = readb(port->membase + UARTMODEM) & + /* Make sure RXRTSE bit is not set when RS485 is enabled */ + if (!(sport->port.rs485.flags & SER_RS485_ENABLED)) { + temp = readb(sport->port.membase + UARTMODEM) & ~(UARTMODEM_RXRTSE | UARTMODEM_TXCTSE); - if (mctrl & TIOCM_RTS) - temp |= UARTMODEM_RXRTSE; + if (mctrl & TIOCM_RTS) + temp |= UARTMODEM_RXRTSE; - if (mctrl & TIOCM_CTS) - temp |= UARTMODEM_TXCTSE; + if (mctrl & TIOCM_CTS) + temp |= UARTMODEM_TXCTSE; - writeb(temp, port->membase + UARTMODEM); + writeb(temp, sport->port.membase + UARTMODEM); + } } static void lpuart32_set_mctrl(struct uart_port *port, unsigned int mctrl) @@ -921,14 +949,17 @@ static void lpuart_setup_watermark(struct lpuart_port *sport) writeb(val | UARTPFIFO_TXFE | UARTPFIFO_RXFE, sport->port.membase + UARTPFIFO); - /* explicitly clear RDRF */ - readb(sport->port.membase + UARTSR1); - /* flush Tx and Rx FIFO */ writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH, sport->port.membase + UARTCFIFO); - writeb(0, sport->port.membase + UARTTWFIFO); + /* explicitly clear RDRF */ + if (readb(sport->port.membase + UARTSR1) & UARTSR1_RDRF) { + readb(sport->port.membase + UARTDR); + writeb(UARTSFIFO_RXUF, sport->port.membase + UARTSFIFO); + } + + writeb(sport->txfifo_size / 2, sport->port.membase + UARTTWFIFO); writeb(1, sport->port.membase + UARTRWFIFO); /* Restore cr2 */ @@ -981,7 +1012,7 @@ static int lpuart_dma_tx_request(struct uart_port *port) dma_buf = sport->port.state->xmit.buf; dma_tx_sconfig.dst_addr = sport->port.mapbase + UARTDR; dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; - dma_tx_sconfig.dst_maxburst = sport->txfifo_size; + dma_tx_sconfig.dst_maxburst = 1; dma_tx_sconfig.direction = DMA_MEM_TO_DEV; ret = dmaengine_slave_config(sport->dma_tx_chan, &dma_tx_sconfig); @@ -1240,6 +1271,12 @@ lpuart_set_termios(struct uart_port *port, struct ktermios *termios, cr1 |= UARTCR1_M; } + /* When auto RS-485 RTS mode is enabled, + * hardware flow control need to be disabled. + */ + if (sport->port.rs485.flags & SER_RS485_ENABLED) + termios->c_cflag &= ~CRTSCTS; + if (termios->c_cflag & CRTSCTS) { modem |= (UARTMODEM_RXRTSE | UARTMODEM_TXCTSE); } else { @@ -1761,6 +1798,18 @@ static struct uart_driver lpuart_reg = { .cons = LPUART_CONSOLE, }; +static struct dma_chan *lpuart_request_dma_chan(struct lpuart_port *sport, + const char *name) +{ + struct dma_chan *chan; + + chan = dma_request_slave_channel(sport->port.dev, name); + if (!chan) + dev_info(sport->port.dev, "DMA %s channel request failed, " + "operating without %s DMA\n", name, name); + return chan; +} + static int lpuart_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -1798,6 +1847,8 @@ static int lpuart_probe(struct platform_device *pdev) sport->port.ops = &lpuart_pops; sport->port.flags = UPF_BOOT_AUTOCONF; + sport->port.rs485_config = lpuart_config_rs485; + sport->clk = devm_clk_get(&pdev->dev, "ipg"); if (IS_ERR(sport->clk)) { ret = PTR_ERR(sport->clk); @@ -1828,15 +1879,16 @@ static int lpuart_probe(struct platform_device *pdev) return ret; } - sport->dma_tx_chan = dma_request_slave_channel(sport->port.dev, "tx"); - if (!sport->dma_tx_chan) - dev_info(sport->port.dev, "DMA tx channel request failed, " - "operating without tx DMA\n"); + if (!nodma) { + sport->dma_tx_chan = lpuart_request_dma_chan(sport, "tx"); + sport->dma_rx_chan = lpuart_request_dma_chan(sport, "rx"); + } - sport->dma_rx_chan = dma_request_slave_channel(sport->port.dev, "rx"); - if (!sport->dma_rx_chan) - dev_info(sport->port.dev, "DMA rx channel request failed, " - "operating without rx DMA\n"); + if (of_property_read_bool(np, "linux,rs485-enabled-at-boot-time")) { + sport->port.rs485.flags |= SER_RS485_ENABLED; + sport->port.rs485.flags |= SER_RS485_RTS_ON_SEND; + writeb(UARTMODEM_TXRTSE, sport->port.membase + UARTMODEM); + } return 0; } @@ -1876,7 +1928,12 @@ static int lpuart_suspend(struct device *dev) writeb(temp, sport->port.membase + UARTCR2); } + if (sport->dma_rx_in_progress) + lpuart_dma_rx_terminate(sport); + uart_suspend_port(&lpuart_reg, &sport->port); + if (sport->port.suspended && !sport->port.irq_wake) + clk_disable_unprepare(sport->clk); return 0; } @@ -1886,6 +1943,9 @@ static int lpuart_resume(struct device *dev) struct lpuart_port *sport = dev_get_drvdata(dev); unsigned long temp; + if (sport->port.suspended && !sport->port.irq_wake) + clk_prepare_enable(sport->clk); + if (sport->lpuart32) { lpuart32_setup_watermark(sport); temp = lpuart32_read(sport->port.membase + UARTCTRL); diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 6d6200e37b71..ba14c6c3b1aa 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -18,6 +18,7 @@ #include <linux/usb.h> #include <linux/usb/gadget.h> #include <linux/usb/otg-fsm.h> +#include <linux/extcon.h> /****************************************************************************** * DEFINE @@ -246,6 +247,11 @@ struct ci_hdrc { bool in_lpm; bool wakeup_int; enum ci_revision rev; + + struct extcon_specific_cable_nb extcon_vbus_dev; + struct extcon_specific_cable_nb extcon_id_dev; + struct notifier_block vbus_nb; + struct notifier_block id_nb; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 846ceb91ec14..df1ad5edbb5f 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -281,9 +281,9 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) &pdata); if (IS_ERR(data->ci_pdev)) { ret = PTR_ERR(data->ci_pdev); - dev_err(&pdev->dev, - "Can't register ci_hdrc platform device, err=%d\n", - ret); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "ci_hdrc_add_device failed, err=%d\n", ret); goto err_clk; } diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 3ad48e1c0c57..86ef48e1ee7b 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -560,6 +560,17 @@ static irqreturn_t ci_irq(int irq, void *data) static int ci_get_platdata(struct device *dev, struct ci_hdrc_platform_data *platdata) { + if (of_property_read_bool(dev->of_node, "extcon")) { + platdata->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(platdata->edev)) { + if (PTR_ERR(platdata->edev) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_err(dev, "couldn't get extcon device: %ld\n", + PTR_ERR(platdata->edev)); + } + platdata->flags |= CI_HDRC_DUAL_ROLE_NOT_OTG; + } + if (!platdata->phy_mode) platdata->phy_mode = of_usb_get_phy_mode(dev->of_node); @@ -676,6 +687,42 @@ static void ci_get_otg_capable(struct ci_hdrc *ci) } } +static int ci_id_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc *ci = container_of(nb, struct ci_hdrc, id_nb); + + pm_runtime_get_sync(ci->dev); + + ci_role_stop(ci); + + hw_wait_phy_stable(); + + if (ci_role_start(ci, event?CI_ROLE_HOST:CI_ROLE_GADGET)) + dev_err(ci->dev, "can't start %s role\n", ci_role(ci)->name); + + pm_runtime_put_sync(ci->dev); + + return NOTIFY_DONE; +} + +static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc *ci = container_of(nb, struct ci_hdrc, vbus_nb); + + pm_runtime_get_sync(ci->dev); + + if (event) + usb_gadget_vbus_connect(&ci->gadget); + else + usb_gadget_vbus_disconnect(&ci->gadget); + + pm_runtime_put_sync(ci->dev); + + return NOTIFY_DONE; +} + static int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -712,6 +759,21 @@ static int ci_hdrc_probe(struct platform_device *pdev) return -ENODEV; } + if (ci->platdata->edev) { + ci->vbus_nb.notifier_call = ci_vbus_notifier; + ret = extcon_register_interest(&ci->extcon_vbus_dev, + ci->platdata->edev->name, "USB", + &ci->vbus_nb); + if (ret < 0) + dev_err(dev, "failed to register notifier for USB aka VBUS\n"); + ci->id_nb.notifier_call = ci_id_notifier; + ret = extcon_register_interest(&ci->extcon_id_dev, + ci->platdata->edev->name, "USB-HOST", + &ci->id_nb); + if (ret < 0) + dev_err(dev, "failed to register notifier for USB-HOST aka ID\n"); + } + if (ci->platdata->phy) { ci->phy = ci->platdata->phy; } else if (ci->platdata->usb_phy) { @@ -737,7 +799,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) ret = ci_usb_phy_init(ci); if (ret) { dev_err(dev, "unable to init phy: %d\n", ret); - return ret; + goto extcon_cleanup; } ci->hw_bank.phys = res->start; @@ -790,7 +852,11 @@ static int ci_hdrc_probe(struct platform_device *pdev) * role switch, the defalt role is gadget, and the * user can switch it through debugfs. */ - ci->role = CI_ROLE_GADGET; + if ((ci->platdata->edev) && (extcon_get_cable_state( + ci->platdata->edev, "USB-HOST") == true)) + ci->role = CI_ROLE_HOST; + else + ci->role = CI_ROLE_GADGET; } } else { ci->role = ci->roles[CI_ROLE_HOST] @@ -809,6 +875,9 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci_role(ci)->name); goto stop; } + if ((ci->role == CI_ROLE_GADGET) && (ci->platdata->edev) && + (extcon_get_cable_state(ci->platdata->edev, "USB") == true)) + usb_gadget_vbus_connect(&ci->gadget); } platform_set_drvdata(pdev, ci); @@ -836,6 +905,11 @@ static int ci_hdrc_probe(struct platform_device *pdev) stop: ci_role_destroy(ci); +extcon_cleanup: + if (ci->extcon_vbus_dev.edev) + extcon_unregister_interest(&ci->extcon_vbus_dev); + if (ci->extcon_id_dev.edev) + extcon_unregister_interest(&ci->extcon_id_dev); deinit_phy: ci_usb_phy_exit(ci); @@ -846,6 +920,11 @@ static int ci_hdrc_remove(struct platform_device *pdev) { struct ci_hdrc *ci = platform_get_drvdata(pdev); + if (ci->extcon_vbus_dev.edev) + extcon_unregister_interest(&ci->extcon_vbus_dev); + if (ci->extcon_id_dev.edev) + extcon_unregister_interest(&ci->extcon_id_dev); + if (ci->supports_runtime_pm) { pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); diff --git a/drivers/video/backlight/gpio_backlight.c b/drivers/video/backlight/gpio_backlight.c index 439feb2389a8..575eee741bd3 100644 --- a/drivers/video/backlight/gpio_backlight.c +++ b/drivers/video/backlight/gpio_backlight.c @@ -89,6 +89,7 @@ static int gpio_backlight_probe(struct platform_device *pdev) struct backlight_device *bl; struct gpio_backlight *gbl; struct device_node *np = pdev->dev.of_node; + unsigned long flags = GPIOF_DIR_OUT; int ret; if (!pdata && !np) { @@ -114,9 +115,12 @@ static int gpio_backlight_probe(struct platform_device *pdev) gbl->def_value = pdata->def_value; } - ret = devm_gpio_request_one(gbl->dev, gbl->gpio, GPIOF_DIR_OUT | - (gbl->active ? GPIOF_INIT_LOW - : GPIOF_INIT_HIGH), + if (gbl->active) + flags |= gbl->def_value ? GPIOF_INIT_HIGH : GPIOF_INIT_LOW; + else + flags |= gbl->def_value ? GPIOF_INIT_LOW : GPIOF_INIT_HIGH; + + ret = devm_gpio_request_one(gbl->dev, gbl->gpio, flags, pdata ? pdata->name : "backlight"); if (ret < 0) { dev_err(&pdev->dev, "unable to request GPIO\n"); diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig index d1e1e1704da1..35da2b0a4fcf 100644 --- a/drivers/video/fbdev/Kconfig +++ b/drivers/video/fbdev/Kconfig @@ -1957,6 +1957,17 @@ config FB_FSL_DIU ---help--- Framebuffer driver for the Freescale SoC DIU +config FB_FSL_DCU + tristate "Freescale DCU framebuffer support" + depends on FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + ---help--- + Framebuffer driver for the Freescale SoC DCU + config FB_W100 tristate "W100 frame buffer support" depends on FB && ARCH_PXA diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile index 1979afffccfe..471695d7b051 100644 --- a/drivers/video/fbdev/Makefile +++ b/drivers/video/fbdev/Makefile @@ -110,6 +110,7 @@ obj-$(CONFIG_FB_IMX) += imxfb.o obj-$(CONFIG_FB_S3C) += s3c-fb.o obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o obj-$(CONFIG_FB_FSL_DIU) += fsl-diu-fb.o +obj-$(CONFIG_FB_FSL_DCU) += fsl-dcu-fb.o obj-$(CONFIG_FB_COBALT) += cobalt_lcdfb.o obj-$(CONFIG_FB_IBM_GXT4500) += gxt4500.o obj-$(CONFIG_FB_PS3) += ps3fb.o diff --git a/drivers/video/fbdev/fsl-dcu-fb.c b/drivers/video/fbdev/fsl-dcu-fb.c new file mode 100644 index 000000000000..a1da36f131f0 --- /dev/null +++ b/drivers/video/fbdev/fsl-dcu-fb.c @@ -0,0 +1,1275 @@ +/* + * Copyright 2012-2013 Freescale Semiconductor, Inc. + * + * Freescale DCU framebuffer device driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/completion.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/fb.h> +#include <linux/clk.h> +#include <linux/of_platform.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> +#include <linux/pm_runtime.h> +#include <linux/console.h> + +#define DRIVER_NAME "fsl-dcu-fb" + +#define DCU_DCU_MODE 0x0010 +#define DCU_MODE_BLEND_ITER(x) ((x) << 20) +#define DCU_MODE_RASTER_EN (1 << 14) +#define DCU_MODE_DCU_MODE(x) (x) +#define DCU_MODE_DCU_MODE_MASK 0x03 +#define DCU_MODE_OFF 0 +#define DCU_MODE_NORMAL 1 +#define DCU_MODE_TEST 2 +#define DCU_MODE_COLORBAR 3 + +#define DCU_BGND 0x0014 +#define DCU_BGND_R(x) ((x) << 16) +#define DCU_BGND_G(x) ((x) << 8) +#define DCU_BGND_B(x) (x) + +#define DCU_DISP_SIZE 0x0018 +#define DCU_DISP_SIZE_DELTA_Y(x) ((x) << 16) +#define DCU_DISP_SIZE_DELTA_X(x) (x) + +#define DCU_HSYN_PARA 0x001c +#define DCU_HSYN_PARA_BP(x) ((x) << 22) +#define DCU_HSYN_PARA_PW(x) ((x) << 11) +#define DCU_HSYN_PARA_FP(x) (x) + +#define DCU_VSYN_PARA 0x0020 +#define DCU_VSYN_PARA_BP(x) ((x) << 22) +#define DCU_VSYN_PARA_PW(x) ((x) << 11) +#define DCU_VSYN_PARA_FP(x) (x) + +#define DCU_SYN_POL 0x0024 +#define DCU_SYN_POL_INV_PXCK_FALL (1 << 6) +#define DCU_SYN_POL_NEG_REMAIN (1 << 5) +#define DCU_SYN_POL_INV_VS_LOW (1 << 1) +#define DCU_SYN_POL_INV_HS_LOW (1) + +#define DCU_THRESHOLD 0x0028 +#define DCU_THRESHOLD_LS_BF_VS(x) ((x) << 16) +#define DCU_THRESHOLD_OUT_BUF_HIGH(x) ((x) << 8) +#define DCU_THRESHOLD_OUT_BUF_LOW(x) (x) + +#define DCU_INT_STATUS 0x002C +#define DCU_INT_STATUS_UNDRUN (1 << 1) +#define DCU_INT_STATUS_VSYNC (1 << 0) + +#define DCU_INT_MASK 0x0030 +#define DCU_INT_MASK_UNDRUN (1 << 1) +#define DCU_INT_MASK_VSYNC (1 << 0) + +#define DCU_DIV_RATIO 0x0054 + +#define DCU_UPDATE_MODE 0x00cc +#define DCU_UPDATE_MODE_MODE (1 << 31) +#define DCU_UPDATE_MODE_READREG (1 << 30) + +#define DCU_CTRLDESCLN_1(x) (0x200 + (x) * 0x40) +#define DCU_CTRLDESCLN_1_HEIGHT(x) ((x) << 16) +#define DCU_CTRLDESCLN_1_WIDTH(x) (x) + +#define DCU_CTRLDESCLN_2(x) (0x204 + (x) * 0x40) +#define DCU_CTRLDESCLN_2_POSY(x) ((x) << 16) +#define DCU_CTRLDESCLN_2_POSX(x) (x) + +#define DCU_CTRLDESCLN_3(x) (0x208 + (x) * 0x40) + +#define DCU_CTRLDESCLN_4(x) (0x20c + (x) * 0x40) +#define DCU_CTRLDESCLN_4_EN (1 << 31) +#define DCU_CTRLDESCLN_4_TILE_EN (1 << 30) +#define DCU_CTRLDESCLN_4_DATA_SEL_CLUT (1 << 29) +#define DCU_CTRLDESCLN_4_SAFETY_EN (1 << 28) +#define DCU_CTRLDESCLN_4_TRANS(x) ((x) << 20) +#define DCU_CTRLDESCLN_4_BPP(x) ((x) << 16) +#define DCU_CTRLDESCLN_4_RLE_EN (1 << 15) +#define DCU_CTRLDESCLN_4_LUOFFS(x) ((x) << 4) +#define DCU_CTRLDESCLN_4_BB_ON (1 << 2) +#define DCU_CTRLDESCLN_4_AB(x) (x) + +#define DCU_CTRLDESCLN_5(x) (0x210 + (x) * 0x40) +#define DCU_CTRLDESCLN_5_CKMAX_R(x) ((x) << 16) +#define DCU_CTRLDESCLN_5_CKMAX_G(x) ((x) << 8) +#define DCU_CTRLDESCLN_5_CKMAX_B(x) (x) + +#define DCU_CTRLDESCLN_6(x) (0x214 + (x) * 0x40) +#define DCU_CTRLDESCLN_6_CKMIN_R(x) ((x) << 16) +#define DCU_CTRLDESCLN_6_CKMIN_G(x) ((x) << 8) +#define DCU_CTRLDESCLN_6_CKMIN_B(x) (x) + +#define DCU_CTRLDESCLN_7(x) (0x218 + (x) * 0x40) +#define DCU_CTRLDESCLN_7_TILE_VER(x) ((x) << 16) +#define DCU_CTRLDESCLN_7_TILE_HOR(x) (x) + +#define DCU_CTRLDESCLN_8(x) (0x21c + (x) * 0x40) +#define DCU_CTRLDESCLN_8_FG_FCOLOR(x) (x) + +#define DCU_CTRLDESCLN_9(x) (0x220 + (x) * 0x40) +#define DCU_CTRLDESCLN_9_BG_BCOLOR(x) (x) + +#define DCU_TOTAL_LAYER_NUM 64 +#define DCU_LAYER_NUM_MAX 6 + +#define BPP_16_RGB565 4 +#define BPP_24_RGB888 5 +#define BPP_32_ARGB8888 6 + +#define TCON_CTRL1 0x0000 +#define TCON_BYPASS_ENABLE (1 << 29) + +#define MFB_SET_ALPHA _IOW('M', 0, __u8) +#define MFB_GET_ALPHA _IOR('M', 0, __u8) +#define MFB_SET_LAYER _IOW('M', 4, struct layer_display_offset) +#define MFB_GET_LAYER _IOR('M', 4, struct layer_display_offset) + +struct dcu_fb_data { + struct fb_info *fsl_dcu_info[DCU_LAYER_NUM_MAX]; + struct device *dev; + void __iomem *reg_base; + unsigned int irq; + struct clk *clk; + struct fb_videomode *mode_db; + int modecnt; + struct fb_videomode native_mode; + u32 bits_per_pixel; + bool pixclockpol; + struct completion vsync_wait; +}; + +struct layer_display_offset { + int x_layer_d; + int y_layer_d; +}; + +struct mfb_info { + int index; + char *id; + unsigned long pseudo_palette[16]; + unsigned char alpha; + unsigned char blend; + unsigned int count; + int x_layer_d; /* layer display x offset to physical screen */ + int y_layer_d; /* layer display y offset to physical screen */ + struct dcu_fb_data *parent; +}; + +enum mfb_index { + LAYER0 = 0, + LAYER1, + LAYER2, + LAYER3, + LAYER4, + LAYER5, +}; + +static struct mfb_info mfb_template[] = { + { + .index = LAYER0, + .id = "Layer0", + .alpha = 0xff, + .blend = 0, + .count = 0, + .x_layer_d = 0, + .y_layer_d = 0, + }, + { + .index = LAYER1, + .id = "Layer1", + .alpha = 0xff, + .blend = 0, + .count = 0, + .x_layer_d = 50, + .y_layer_d = 50, + }, + { + .index = LAYER2, + .id = "Layer2", + .alpha = 0xff, + .blend = 0, + .count = 0, + .x_layer_d = 100, + .y_layer_d = 100, + }, + { + .index = LAYER3, + .id = "Layer3", + .alpha = 0xff, + .blend = 0, + .count = 0, + .x_layer_d = 150, + .y_layer_d = 150, + }, + { + .index = LAYER4, + .id = "Layer4", + .alpha = 0xff, + .blend = 0, + .count = 0, + .x_layer_d = 200, + .y_layer_d = 200, + }, + { + .index = LAYER5, + .id = "Layer5", + .alpha = 0xff, + .blend = 0, + .count = 0, + .x_layer_d = 250, + .y_layer_d = 250, + }, +}; + +static inline unsigned int fsl_dcu_get_offset(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + int pixel_offset; + + pixel_offset = (var->yoffset * var->xres_virtual) + var->xoffset; + return info->fix.smem_start + + (pixel_offset * (var->bits_per_pixel >> 3)); +} + +static int enable_panel(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + unsigned int bpp; + unsigned int addr; + + writel(DCU_CTRLDESCLN_1_HEIGHT(var->yres) | + DCU_CTRLDESCLN_1_WIDTH(var->xres), + dcufb->reg_base + DCU_CTRLDESCLN_1(mfbi->index)); + writel(DCU_CTRLDESCLN_2_POSY(mfbi->y_layer_d) | + DCU_CTRLDESCLN_2_POSX(mfbi->x_layer_d), + dcufb->reg_base + DCU_CTRLDESCLN_2(mfbi->index)); + + addr = fsl_dcu_get_offset(info); + writel(addr, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index)); + + switch (var->bits_per_pixel) { + case 16: + bpp = BPP_16_RGB565; + break; + case 24: + bpp = BPP_24_RGB888; + break; + case 32: + bpp = BPP_32_ARGB8888; + break; + default: + dev_err(dcufb->dev, "unsupported color depth: %u\n", + var->bits_per_pixel); + return -EINVAL; + } + + writel(DCU_CTRLDESCLN_4_EN | + DCU_CTRLDESCLN_4_TRANS(mfbi->alpha) | + DCU_CTRLDESCLN_4_BPP(bpp) | + DCU_CTRLDESCLN_4_AB(mfbi->blend), + dcufb->reg_base + DCU_CTRLDESCLN_4(mfbi->index)); + + writel(DCU_CTRLDESCLN_5_CKMAX_R(0xff) | + DCU_CTRLDESCLN_5_CKMAX_G(0xff) | + DCU_CTRLDESCLN_5_CKMAX_B(0xff), + dcufb->reg_base + DCU_CTRLDESCLN_5(mfbi->index)); + writel(DCU_CTRLDESCLN_6_CKMIN_R(0) | + DCU_CTRLDESCLN_6_CKMIN_G(0) | + DCU_CTRLDESCLN_6_CKMIN_B(0), + dcufb->reg_base + DCU_CTRLDESCLN_6(mfbi->index)); + + writel(DCU_CTRLDESCLN_7_TILE_VER(0) | DCU_CTRLDESCLN_7_TILE_HOR(0), + dcufb->reg_base + DCU_CTRLDESCLN_7(mfbi->index)); + + writel(DCU_CTRLDESCLN_8_FG_FCOLOR(0), + dcufb->reg_base + DCU_CTRLDESCLN_8(mfbi->index)); + writel(DCU_CTRLDESCLN_9_BG_BCOLOR(0), + dcufb->reg_base + DCU_CTRLDESCLN_9(mfbi->index)); + + writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE); + + /* Wait until transfer is complete and switch to automatic updates */ + while (readl(dcufb->reg_base + DCU_UPDATE_MODE) & DCU_UPDATE_MODE_READREG); + writel(DCU_UPDATE_MODE_MODE, dcufb->reg_base + DCU_UPDATE_MODE); + + return 0; +} + +static int disable_panel(struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + + writel(DCU_CTRLDESCLN_1_HEIGHT(0) | + DCU_CTRLDESCLN_1_WIDTH(0), + dcufb->reg_base + DCU_CTRLDESCLN_1(mfbi->index)); + writel(DCU_CTRLDESCLN_2_POSY(0) | DCU_CTRLDESCLN_2_POSX(0), + dcufb->reg_base + DCU_CTRLDESCLN_2(mfbi->index)); + + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_4(mfbi->index)); + + writel(DCU_CTRLDESCLN_5_CKMAX_R(0) | + DCU_CTRLDESCLN_5_CKMAX_G(0) | + DCU_CTRLDESCLN_5_CKMAX_B(0), + dcufb->reg_base + DCU_CTRLDESCLN_5(mfbi->index)); + writel(DCU_CTRLDESCLN_6_CKMIN_R(0) | + DCU_CTRLDESCLN_6_CKMIN_G(0) | + DCU_CTRLDESCLN_6_CKMIN_B(0), + dcufb->reg_base + DCU_CTRLDESCLN_6(mfbi->index)); + + writel(DCU_CTRLDESCLN_7_TILE_VER(0) | DCU_CTRLDESCLN_7_TILE_HOR(0), + dcufb->reg_base + DCU_CTRLDESCLN_7(mfbi->index)); + + writel(DCU_CTRLDESCLN_8_FG_FCOLOR(0), + dcufb->reg_base + DCU_CTRLDESCLN_8(mfbi->index)); + writel(DCU_CTRLDESCLN_9_BG_BCOLOR(0), + dcufb->reg_base + DCU_CTRLDESCLN_9(mfbi->index)); + + /* Clear Mode flag and schedule one transfer using READREG */ + writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE); + return 0; +} + +static void enable_controller(struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + unsigned int dcu_mode; + + dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE); + dcu_mode &= ~DCU_MODE_DCU_MODE_MASK; + writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_NORMAL), + dcufb->reg_base + DCU_DCU_MODE); +} + +static void disable_controller(struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + unsigned int dcu_mode; + + dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE); + dcu_mode &= ~DCU_MODE_DCU_MODE_MASK; + writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_OFF), + dcufb->reg_base + DCU_DCU_MODE); +} + +static int fsl_dcu_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + + if (var->grayscale || var->rotate || var->nonstd) + return -EINVAL; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xoffset + info->var.xres > info->var.xres_virtual) + var->xoffset = info->var.xres_virtual - info->var.xres; + + if (var->yoffset + info->var.yres > info->var.yres_virtual) + var->yoffset = info->var.yres_virtual - info->var.yres; + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + default: + dev_err(dcufb->dev, "unsupported color depth: %u\n", + var->bits_per_pixel); + return -EINVAL; + } + + return 0; +} + +static int fsl_dcu_calc_div(struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + unsigned long long div; + div = (unsigned long long)(clk_get_rate(dcufb->clk) / 1000); + div *= info->var.pixclock; + do_div(div, 1000000000); + + return div; +} + +static void update_controller(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + unsigned int div, pol = 0; + + div = fsl_dcu_calc_div(info); + writel((div - 1), dcufb->reg_base + DCU_DIV_RATIO); + + writel(DCU_DISP_SIZE_DELTA_Y(var->yres) | + DCU_DISP_SIZE_DELTA_X(var->xres / 16), + dcufb->reg_base + DCU_DISP_SIZE); + + /* Horizontal and vertical sync parameters */ + writel(DCU_HSYN_PARA_BP(var->left_margin) | + DCU_HSYN_PARA_PW(var->hsync_len) | + DCU_HSYN_PARA_FP(var->right_margin), + dcufb->reg_base + DCU_HSYN_PARA); + + writel(DCU_VSYN_PARA_BP(var->upper_margin) | + DCU_VSYN_PARA_PW(var->vsync_len) | + DCU_VSYN_PARA_FP(var->lower_margin), + dcufb->reg_base + DCU_VSYN_PARA); + + /* + * pixclockpol = 0 => display samples data on falling edge => 0 + * pixclockpol = 1 => display samples data on rising edge => 1 (default) + */ + if (dcufb->pixclockpol) + pol |= DCU_SYN_POL_INV_PXCK_FALL; + + /* hsync:0 => active low => HS_LOW */ + if (!(var->sync & FB_SYNC_HOR_HIGH_ACT)) + pol |= DCU_SYN_POL_INV_HS_LOW; + + /* vsync:0 => active low => VS_LOW */ + if (!(var->sync & FB_SYNC_VERT_HIGH_ACT)) + pol |= DCU_SYN_POL_INV_VS_LOW; + + writel(pol, dcufb->reg_base + DCU_SYN_POL); + + writel(DCU_BGND_R(0) | DCU_BGND_G(0) | DCU_BGND_B(0), + dcufb->reg_base + DCU_BGND); + + writel(DCU_MODE_BLEND_ITER(DCU_LAYER_NUM_MAX) | DCU_MODE_RASTER_EN, + dcufb->reg_base + DCU_DCU_MODE); + + writel(DCU_THRESHOLD_LS_BF_VS(0x3) | DCU_THRESHOLD_OUT_BUF_HIGH(0x78) | + DCU_THRESHOLD_OUT_BUF_LOW(0), dcufb->reg_base + DCU_THRESHOLD); +} + +static int map_video_memory(struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + u32 smem_len = info->fix.line_length * info->var.yres_virtual; + + info->fix.smem_len = smem_len; + + info->screen_base = dma_alloc_writecombine(info->device, + info->fix.smem_len, (dma_addr_t *)&info->fix.smem_start, + GFP_KERNEL); + if (!info->screen_base) { + dev_err(dcufb->dev, "unable to allocate fb memory\n"); + return -ENOMEM; + } + + memset(info->screen_base, 0, info->fix.smem_len); + + return 0; +} + +static void unmap_video_memory(struct fb_info *info) +{ + if (!info->screen_base) + return; + + dma_free_writecombine(info->device, info->fix.smem_len, + info->screen_base, info->fix.smem_start); + + info->screen_base = NULL; + info->fix.smem_start = 0; + info->fix.smem_len = 0; +} + +static int fsl_dcu_set_par(struct fb_info *info) +{ + unsigned long len; + struct fb_var_screeninfo *var = &info->var; + struct fb_fix_screeninfo *fix = &info->fix; + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + len = info->var.yres_virtual * info->fix.line_length; + if (len != info->fix.smem_len) { + if (info->fix.smem_start) + unmap_video_memory(info); + + if (map_video_memory(info)) { + dev_err(dcufb->dev, "unable to allocate fb memory\n"); + return -ENOMEM; + } + } + + /* Only layer 0 could update LCD controller */ + if (mfbi->index == LAYER0) { + update_controller(info); + enable_controller(info); + } + + enable_panel(info); + return 0; +} + +static inline __u32 CNVT_TOHW(__u32 val, __u32 width) +{ + return ((val<<width) + 0x7FFF - val) >> 16; +} + +static int fsl_dcu_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + unsigned int val; + int ret = -EINVAL; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + + val = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + pal[regno] = val; + ret = 0; + } + break; + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +static int fsl_dcu_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + unsigned int addr; + + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) + return 0; + + if ((var->xoffset + info->var.xres) > info->var.xres_virtual + || (var->yoffset + info->var.yres) > info->var.yres_virtual) + return -EINVAL; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + + addr = fsl_dcu_get_offset(info); + writel(addr, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index)); + + return 0; +} + +static int fsl_dcu_blank(int blank_mode, struct fb_info *info) +{ + switch (blank_mode) { + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + disable_panel(info); + break; + case FB_BLANK_POWERDOWN: + disable_controller(info); + disable_panel(info); + break; + case FB_BLANK_UNBLANK: + enable_controller(info); + enable_panel(info); + break; + } + + return 0; +} + +static int fsl_dcu_wait_for_vsync(struct dcu_fb_data *dcufb) +{ + unsigned long mask = readl(dcufb->reg_base + DCU_INT_MASK); + int ret; + + /* + * Clear current VSYNC status since that still contains the flag from + * last VSYNC... + */ + writel(DCU_INT_STATUS_VSYNC, dcufb->reg_base + DCU_INT_STATUS); + writel(mask & ~DCU_INT_MASK_VSYNC, dcufb->reg_base + DCU_INT_MASK); + + ret = wait_for_completion_timeout(&dcufb->vsync_wait, HZ / 2); + + if (ret < 0) + return ret; + if (ret == 0) { + dev_warn(dcufb->dev, "wait_for_vsync timed out!\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int fsl_dcu_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + struct layer_display_offset layer_d; + void __user *buf = (void __user *)arg; + unsigned char alpha; + + switch (cmd) { + case MFB_SET_LAYER: + if (copy_from_user(&layer_d, buf, sizeof(layer_d))) + return -EFAULT; + mfbi->x_layer_d = layer_d.x_layer_d; + mfbi->y_layer_d = layer_d.y_layer_d; + fsl_dcu_set_par(info); + break; + case MFB_GET_LAYER: + layer_d.x_layer_d = mfbi->x_layer_d; + layer_d.y_layer_d = mfbi->y_layer_d; + if (copy_to_user(buf, &layer_d, sizeof(layer_d))) + return -EFAULT; + break; + case MFB_GET_ALPHA: + alpha = mfbi->alpha; + if (copy_to_user(buf, &alpha, sizeof(alpha))) + return -EFAULT; + break; + case MFB_SET_ALPHA: + if (copy_from_user(&alpha, buf, sizeof(alpha))) + return -EFAULT; + mfbi->blend = 1; + mfbi->alpha = alpha; + fsl_dcu_set_par(info); + break; + case FBIO_WAITFORVSYNC: + return fsl_dcu_wait_for_vsync(dcufb); + default: + dev_err(dcufb->dev, "unknown ioctl command (0x%08X)\n", cmd); + return -ENOIOCTLCMD; + } + + return 0; +} + +static void reset_layers(struct dcu_fb_data *dcufb) +{ + int i; + + for (i = 0; i < DCU_TOTAL_LAYER_NUM; i++) { + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_1(i)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_2(i)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_3(i)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_4(i)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_5(i)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_6(i)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_7(i)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_8(i)); + writel(0, dcufb->reg_base + DCU_CTRLDESCLN_9(i)); + } +} + +static int fsl_dcu_open(struct fb_info *info, int user) +{ + struct mfb_info *mfbi = info->par; + int ret; + + mfbi->index = info->node; + + if (mfbi->count == 0) { + ret = fsl_dcu_check_var(&info->var, info); + if (ret < 0) + return ret; + + ret = fsl_dcu_set_par(info); + if (ret < 0) + return ret; + } + mfbi->count++; + + return 0; +} + +static int fsl_dcu_release(struct fb_info *info, int user) +{ + struct mfb_info *mfbi = info->par; + int ret = 0; + + mfbi->count--; + if (mfbi->count == 0) + ret = disable_panel(info); + + return ret; +} + +static struct fb_ops fsl_dcu_ops = { + .owner = THIS_MODULE, + .fb_check_var = fsl_dcu_check_var, + .fb_set_par = fsl_dcu_set_par, + .fb_setcolreg = fsl_dcu_setcolreg, + .fb_blank = fsl_dcu_blank, + .fb_pan_display = fsl_dcu_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_ioctl = fsl_dcu_ioctl, + .fb_open = fsl_dcu_open, + .fb_release = fsl_dcu_release, +}; + +static int fsl_dcu_init_modelist(struct dcu_fb_data *dcufb) +{ + struct device_node *np = dcufb->dev->of_node; + struct device_node *display_np; + struct display_timings *timings; + int i; + int ret = 0; + + display_np = of_parse_phandle(np, "display", 0); + if (!display_np) { + dev_err(dcufb->dev, "failed to find display phandle\n"); + return -ENOENT; + } + + ret = of_property_read_u32(display_np, "bits-per-pixel", + &dcufb->bits_per_pixel); + if (ret < 0) { + dev_err(dcufb->dev, "failed to get property bits-per-pixel\n"); + goto put_display_node; + } + + timings = of_get_display_timings(display_np); + if (!timings) { + dev_err(dcufb->dev, "failed to get display timings\n"); + ret = -ENOENT; + goto put_display_node; + } + + dcufb->mode_db = devm_kzalloc(dcufb->dev, sizeof(struct fb_videomode) * + timings->num_timings, GFP_KERNEL); + if (!dcufb->mode_db) { + ret = -ENOMEM; + goto put_display_node; + } + + for (i = 0; i < timings->num_timings; i++) { + struct videomode vm; + + ret = videomode_from_timings(timings, &vm, i); + if (ret < 0) + goto free_dcu_mode_db; + + ret = fb_videomode_from_videomode(&vm, &dcufb->mode_db[i]); + if (ret < 0) + goto free_dcu_mode_db; + + if (i == timings->native_mode) { + fb_videomode_from_videomode(&vm, &dcufb->native_mode); + + /* + * Kernel pixelclk settings are controller centric + * whereas DCU is display centric: + * PIXDATA_NEGEDGE (drive data on falling edge) + * => pixclockpol (display samples data on rising edge) + */ + dcufb->pixclockpol = timings->timings[i]->flags & + DISPLAY_FLAGS_PIXDATA_NEGEDGE; + } + + dcufb->modecnt++; + } + + of_node_put(display_np); + return 0; + +free_dcu_mode_db: + kfree(dcufb->mode_db); + dcufb->mode_db = NULL; +put_display_node: + of_node_put(display_np); + return ret; +} + +static int parse_opt(struct dcu_fb_data *dcufb, char *this_opt, u32 *sync) +{ + if (!strncmp(this_opt, "hsync:", 6)) { + if (simple_strtoul(this_opt+6, NULL, 0)) + *sync |= FB_SYNC_HOR_HIGH_ACT; + } else if (!strncmp(this_opt, "vsync:", 6)) { + if (simple_strtoul(this_opt+6, NULL, 0)) + *sync |= FB_SYNC_VERT_HIGH_ACT; + } else if (!strncmp(this_opt, "pixclockpol:", 12)) + dcufb->pixclockpol = !!simple_strtoul(this_opt+12, NULL, 0); + else + return -EINVAL; + + return 0; +} + +static int fsl_dcu_parse_options(struct dcu_fb_data *dcufb, + struct fb_info *info, char *option) +{ + char *this_opt; + int ret = 0; + u32 sync = 0; + + while ((this_opt = strsep(&option, ",")) != NULL) { + /* Parse driver specific arguments */ + if (parse_opt(dcufb, this_opt, &sync) == 0) + continue; + + /* No valid driver specific argument, has to be mode */ + ret = fb_find_mode(&info->var, info, this_opt, dcufb->mode_db, + dcufb->modecnt, &dcufb->native_mode, + dcufb->bits_per_pixel); + if (ret < 0) + return ret; + } + + /* Overwrite from command line */ + info->var.sync = sync; + return 0; +} + +static int install_framebuffer(struct fb_info *info, char *option) +{ + struct mfb_info *mfbi = info->par; + struct dcu_fb_data *dcufb = mfbi->parent; + struct fb_videomode *mode = &dcufb->native_mode; + int ret; + + info->var.activate = FB_ACTIVATE_NOW; + info->fbops = &fsl_dcu_ops; + info->flags = FBINFO_FLAG_DEFAULT; + info->pseudo_palette = &mfbi->pseudo_palette; + + fb_alloc_cmap(&info->cmap, 16, 0); + + INIT_LIST_HEAD(&info->modelist); + fb_add_videomode(mode, &info->modelist); + fb_videomode_to_var(&info->var, mode); + info->var.bits_per_pixel = dcufb->bits_per_pixel; + + /* Parse DCU option for every layer... */ + ret = fsl_dcu_parse_options(dcufb, info, option); + if (ret < 0) + return ret; + + fsl_dcu_check_var(&info->var, info); + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(dcufb->dev, "failed to register framebuffer device\n"); + return ret; + } + + printk(KERN_INFO "fb%d: fb device registered successfully.\n", + info->node); + return 0; +} + +static void uninstall_framebuffer(struct fb_info *info) +{ + unregister_framebuffer(info); + unmap_video_memory(info); + + if (&info->cmap) + fb_dealloc_cmap(&info->cmap); +} + +static irqreturn_t fsl_dcu_irq(int irq, void *dev_id) +{ + struct dcu_fb_data *dcufb = dev_id; + unsigned int mask = readl(dcufb->reg_base + DCU_INT_MASK); + unsigned int status = readl(dcufb->reg_base + DCU_INT_STATUS); + + if (status & DCU_INT_STATUS_VSYNC) { + mask |= DCU_INT_MASK_VSYNC; + writel(mask, dcufb->reg_base + DCU_INT_MASK); + complete(&dcufb->vsync_wait); + } + + writel(status, dcufb->reg_base + DCU_INT_STATUS); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM_RUNTIME +static int fsl_dcu_runtime_suspend(struct device *dev) +{ + struct dcu_fb_data *dcufb = dev_get_drvdata(dev); + + clk_disable_unprepare(dcufb->clk); + + return 0; +} + +static int fsl_dcu_runtime_resume(struct device *dev) +{ + struct dcu_fb_data *dcufb = dev_get_drvdata(dev); + + clk_prepare_enable(dcufb->clk); + + return 0; +} +#endif + +static int bypass_tcon(struct device_node *np) +{ + struct device_node *tcon_np; + struct platform_device *tcon_pdev; + struct clk *tcon_clk; + struct resource *res; + void __iomem *tcon_reg; + + tcon_np = of_parse_phandle(np, "tcon-controller", 0); + if (!tcon_np) + return -EINVAL; + + tcon_pdev = of_find_device_by_node(tcon_np); + if (!tcon_pdev) + return -EINVAL; + + tcon_clk = devm_clk_get(&tcon_pdev->dev, "tcon"); + if (IS_ERR(tcon_clk)) + return PTR_ERR(tcon_clk); + clk_prepare_enable(tcon_clk); + + res = platform_get_resource(tcon_pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + tcon_reg = ioremap(res->start, resource_size(res)); + if (IS_ERR(tcon_reg)) + return PTR_ERR(tcon_reg); + + writel(TCON_BYPASS_ENABLE, tcon_reg + TCON_CTRL1); + + iounmap(tcon_reg); + clk_disable_unprepare(tcon_clk); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int fsl_dcu_suspend(struct device *dev) +{ + struct dcu_fb_data *dcufb = dev_get_drvdata(dev); + struct fb_info *fbi = dcufb->fsl_dcu_info[0]; + + console_lock(); + fb_set_suspend(fbi, 1); + console_unlock(); + + disable_controller(dcufb->fsl_dcu_info[0]); + disable_panel(fbi); + clk_disable_unprepare(dcufb->clk); + + return 0; +} + +static int fsl_dcu_resume(struct device *dev) +{ + struct dcu_fb_data *dcufb = dev_get_drvdata(dev); + struct fb_info *fbi = dcufb->fsl_dcu_info[0]; + int ret; + + clk_prepare_enable(dcufb->clk); + + ret = bypass_tcon(dev->of_node); + if (ret) { + dev_err(dev, "could not bypass TCON\n"); + goto failed_bypasstcon; + } + + reset_layers(dcufb); + + fsl_dcu_set_par(fbi); + + console_lock(); + fb_set_suspend(fbi, 0); + console_unlock(); + +failed_bypasstcon: + return ret; +} +#endif + +static int fsl_dcu_probe(struct platform_device *pdev) +{ + struct dcu_fb_data *dcufb; + struct mfb_info *mfbi; + struct resource *res; + int ret = 0; + int i; + char *option = NULL; + + fb_get_options("dcufb", &option); + + if (option != NULL) { + dev_info(&pdev->dev, "using cmd options: %s\n", option); + if (!strcmp(option, "off")) + return -ENODEV; + } + + dcufb = devm_kzalloc(&pdev->dev, + sizeof(struct dcu_fb_data), GFP_KERNEL); + if (!dcufb) + return -ENOMEM; + + /* initialize the vsync wait queue */ + init_completion(&dcufb->vsync_wait); + + dcufb->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, dcufb); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "could not get memory IO resource\n"); + return -ENODEV; + } + + dcufb->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dcufb->reg_base)) { + dev_err(&pdev->dev, "could not ioremap resource\n"); + return PTR_ERR(dcufb->reg_base); + } + + dcufb->irq = platform_get_irq(pdev, 0); + if (!dcufb->irq) { + dev_err(&pdev->dev, "could not get irq\n"); + return -EINVAL; + } + + ret = devm_request_irq(&pdev->dev, dcufb->irq, fsl_dcu_irq, + 0, DRIVER_NAME, dcufb); + if (ret) { + dev_err(&pdev->dev, "could not request irq\n"); + goto failed_ioremap; + } + + /* Put TCON in bypass mode, so the input signals from DCU are passed + * through TCON unchanged */ + ret = bypass_tcon(pdev->dev.of_node); + if (ret) { + dev_err(&pdev->dev, "could not bypass TCON\n"); + goto failed_bypasstcon; + } + + dcufb->clk = devm_clk_get(&pdev->dev, "dcu"); + if (IS_ERR(dcufb->clk)) { + ret = PTR_ERR(dcufb->clk); + dev_err(&pdev->dev, "could not get clock\n"); + goto failed_getclock; + } + clk_prepare_enable(dcufb->clk); + + pm_runtime_enable(dcufb->dev); + pm_runtime_get_sync(dcufb->dev); + + ret = fsl_dcu_init_modelist(dcufb); + if (ret) + goto failed_alloc_framebuffer; + + reset_layers(dcufb); + + for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) { + dcufb->fsl_dcu_info[i] = + framebuffer_alloc(sizeof(struct mfb_info), &pdev->dev); + if (!dcufb->fsl_dcu_info[i]) { + ret = -ENOMEM; + goto failed_alloc_framebuffer; + } + + dcufb->fsl_dcu_info[i]->fix.smem_start = 0; + + mfbi = dcufb->fsl_dcu_info[i]->par; + memcpy(mfbi, &mfb_template[i], sizeof(struct mfb_info)); + mfbi->parent = dcufb; + + ret = install_framebuffer(dcufb->fsl_dcu_info[i], option); + if (ret) { + dev_err(&pdev->dev, + "could not register framebuffer %d\n", i); + goto failed_register_framebuffer; + } + } + + if (option != NULL) { + ret = fsl_dcu_parse_options(dcufb, dcufb->fsl_dcu_info[0], + option); + if (ret < 0) + goto failed_alloc_framebuffer; + } + + return 0; + +failed_register_framebuffer: + for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) { + if (dcufb->fsl_dcu_info[i]) + framebuffer_release(dcufb->fsl_dcu_info[i]); + } +failed_alloc_framebuffer: + pm_runtime_put_sync(dcufb->dev); + pm_runtime_disable(dcufb->dev); +failed_getclock: +failed_bypasstcon: + free_irq(dcufb->irq, dcufb); +failed_ioremap: + return ret; +} + +static int fsl_dcu_remove(struct platform_device *pdev) +{ + struct dcu_fb_data *dcufb = dev_get_drvdata(&pdev->dev); + int i; + + pm_runtime_get_sync(dcufb->dev); + + disable_controller(dcufb->fsl_dcu_info[0]); + + clk_disable_unprepare(dcufb->clk); + free_irq(dcufb->irq, dcufb); + + for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) { + uninstall_framebuffer(dcufb->fsl_dcu_info[i]); + framebuffer_release(dcufb->fsl_dcu_info[i]); + } + + pm_runtime_put_sync(dcufb->dev); + pm_runtime_disable(dcufb->dev); + + return 0; +} + +static const struct dev_pm_ops fsl_dcu_pm_ops = { +#ifdef CONFIG_PM_RUNTIME + SET_RUNTIME_PM_OPS(fsl_dcu_runtime_suspend, + fsl_dcu_runtime_resume, NULL) +#endif + SET_SYSTEM_SLEEP_PM_OPS(fsl_dcu_suspend, fsl_dcu_resume) +}; + +static struct of_device_id fsl_dcu_dt_ids[] = { + { + .compatible = "fsl,vf610-dcu", + }, + {} +}; + +static struct platform_driver fsl_dcu_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = fsl_dcu_dt_ids, + .pm = &fsl_dcu_pm_ops, + }, + .probe = fsl_dcu_probe, + .remove = fsl_dcu_remove, +}; + +module_platform_driver(fsl_dcu_driver); + +MODULE_AUTHOR("Alison Wang"); +MODULE_DESCRIPTION("Freescale DCU framebuffer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig index 0037104d66ac..d2ca63c03524 100644 --- a/drivers/video/logo/Kconfig +++ b/drivers/video/logo/Kconfig @@ -82,4 +82,8 @@ config LOGO_M32R_CLUT224 depends on M32R default y +config LOGO_CUSTOM_CLUT224 + bool "Custom 224-color Linux logo" + default n + endif # LOGO diff --git a/drivers/video/logo/Makefile b/drivers/video/logo/Makefile index 3b437813584c..45d4b5346d07 100644 --- a/drivers/video/logo/Makefile +++ b/drivers/video/logo/Makefile @@ -18,6 +18,8 @@ obj-$(CONFIG_LOGO_M32R_CLUT224) += logo_m32r_clut224.o obj-$(CONFIG_SPU_BASE) += logo_spe_clut224.o +obj-$(CONFIG_LOGO_CUSTOM_CLUT224) += logo_custom_clut224.o + # How to generate logo's # Use logo-cfiles to retrieve list of .c files to be built diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c index 10fbfd8ab963..bef02e84a076 100644 --- a/drivers/video/logo/logo.c +++ b/drivers/video/logo/logo.c @@ -111,6 +111,10 @@ const struct linux_logo * __init_refok fb_find_logo(int depth) /* M32R Linux logo */ logo = &logo_m32r_clut224; #endif +#ifdef CONFIG_LOGO_CUSTOM_CLUT224 + /* Custom Linux logo */ + logo = &logo_custom_clut224; +#endif } return logo; } diff --git a/include/dt-bindings/clock/vf610-clock.h b/include/dt-bindings/clock/vf610-clock.h index 979d24a6799f..87811108e8f0 100644 --- a/include/dt-bindings/clock/vf610-clock.h +++ b/include/dt-bindings/clock/vf610-clock.h @@ -193,6 +193,10 @@ #define VF610_PLL6_BYPASS 180 #define VF610_PLL7_BYPASS 181 #define VF610_CLK_SNVS 182 -#define VF610_CLK_END 183 +#define VF610_CLK_TCON0 183 +#define VF610_CLK_DAP 184 +#define VF610_CLK_DDRMC 185 +#define VF610_CLK_WKPU 186 +#define VF610_CLK_END 187 #endif /* __DT_BINDINGS_CLOCK_VF610_H */ diff --git a/include/linux/input/fusion_F0710A.h b/include/linux/input/fusion_F0710A.h new file mode 100644 index 000000000000..7d152cbdd06e --- /dev/null +++ b/include/linux/input/fusion_F0710A.h @@ -0,0 +1,20 @@ +/* linux/input/fusion_F0710A.h + * + * Platform data for Fusion F0710A driver + * + * Copyright (c) 2013 Toradex AG (stefan.agner@toradex.ch) + * + * For licencing details see kernel-base/COPYING + */ + +#ifndef __LINUX_I2C_FUSION_F0710A_H +#define __LINUX_I2C_FUSION_F0710A_H + +/* Board specific touch screen initial values */ +struct fusion_f0710a_init_data { + int (*pinmux_fusion_pins)(void); + int gpio_int; + int gpio_reset; +}; + +#endif /* __LINUX_I2C_FUSION_F0710A_H */ diff --git a/include/linux/irq.h b/include/linux/irq.h index 3532dca843f4..469f25c3c37c 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -458,6 +458,8 @@ extern void handle_nested_irq(unsigned int irq); extern int irq_chip_compose_msi_msg(struct irq_data *data, struct msi_msg *msg); #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY +extern void irq_chip_enable_parent(struct irq_data *data); +extern void irq_chip_disable_parent(struct irq_data *data); extern void irq_chip_ack_parent(struct irq_data *data); extern int irq_chip_retrigger_hierarchy(struct irq_data *data); extern void irq_chip_mask_parent(struct irq_data *data); diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 676d7306a360..744ac0ec98eb 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -258,6 +258,10 @@ int irq_domain_xlate_onetwocell(struct irq_domain *d, struct device_node *ctrlr, /* V2 interfaces to support hierarchy IRQ domains. */ extern struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain, unsigned int virq); +extern void irq_domain_set_info(struct irq_domain *domain, unsigned int virq, + irq_hw_number_t hwirq, struct irq_chip *chip, + void *chip_data, irq_flow_handler_t handler, + void *handler_data, const char *handler_name); #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY extern struct irq_domain *irq_domain_add_hierarchy(struct irq_domain *parent, unsigned int flags, unsigned int size, @@ -281,10 +285,6 @@ extern int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, irq_hw_number_t hwirq, struct irq_chip *chip, void *chip_data); -extern void irq_domain_set_info(struct irq_domain *domain, unsigned int virq, - irq_hw_number_t hwirq, struct irq_chip *chip, - void *chip_data, irq_flow_handler_t handler, - void *handler_data, const char *handler_name); extern void irq_domain_reset_irq_data(struct irq_data *irq_data); extern void irq_domain_free_irqs_common(struct irq_domain *domain, unsigned int virq, diff --git a/include/linux/linux_logo.h b/include/linux/linux_logo.h index ca5bd91d12e1..2be299513819 100644 --- a/include/linux/linux_logo.h +++ b/include/linux/linux_logo.h @@ -47,6 +47,7 @@ extern const struct linux_logo logo_superh_vga16; extern const struct linux_logo logo_superh_clut224; extern const struct linux_logo logo_m32r_clut224; extern const struct linux_logo logo_spe_clut224; +extern const struct linux_logo logo_custom_clut224; extern const struct linux_logo *fb_find_logo(int depth); #ifdef CONFIG_FB_LOGO_EXTRA diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 12b75f3ba0a0..0e63003861c7 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -26,6 +26,8 @@ struct mtd_info; struct nand_flash_dev; +struct device_node; + /* Scan and identify a NAND device */ extern int nand_scan(struct mtd_info *mtd, int max_chips); /* @@ -542,6 +544,7 @@ struct nand_buffers { * flash device * @IO_ADDR_W: [BOARDSPECIFIC] address to write the 8 I/O lines of the * flash device. + * @dn: [BOARDSPECIFIC] device node describing this instance * @read_byte: [REPLACEABLE] read one byte from the chip * @read_word: [REPLACEABLE] read one word from the chip * @write_byte: [REPLACEABLE] write a single byte to the chip on the @@ -644,6 +647,8 @@ struct nand_chip { void __iomem *IO_ADDR_R; void __iomem *IO_ADDR_W; + struct device_node *dn; + uint8_t (*read_byte)(struct mtd_info *mtd); u16 (*read_word)(struct mtd_info *mtd); void (*write_byte)(struct mtd_info *mtd, uint8_t byte); diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index ab94f78c4dd1..c22f68b9f69f 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -35,6 +35,7 @@ struct ci_hdrc_platform_data { void (*notify_event) (struct ci_hdrc *ci, unsigned event); struct regulator *reg_vbus; bool tpl_support; + struct extcon_dev *edev; }; /* Default offset of capability registers */ diff --git a/include/linux/vf610_mscm.h b/include/linux/vf610_mscm.h new file mode 100644 index 000000000000..e61c71caca87 --- /dev/null +++ b/include/linux/vf610_mscm.h @@ -0,0 +1,13 @@ +#ifndef __VF610_MSCM__ +#define __VF610_MSCM__ + +#include <linux/interrupt.h> + +int mscm_request_cpu2cpu_irq(unsigned int intid, irq_handler_t handler, + const char *name, void *priv); +void mscm_free_cpu2cpu_irq(unsigned int intid, void *priv); +void mscm_trigger_cpu2cpu_irq(unsigned int intid, int cpuid); +void mscm_enable_cpu2cpu_irq(unsigned int intid); +void mscm_disable_cpu2cpu_irq(unsigned int intid); + +#endif /* __VF610_MSCM__ */ diff --git a/include/linux/vf610_sema4.h b/include/linux/vf610_sema4.h new file mode 100644 index 000000000000..d0587aaf370b --- /dev/null +++ b/include/linux/vf610_sema4.h @@ -0,0 +1,33 @@ +#ifndef __MVF_SEMA4__ +#define __MVF_SEMA4__ + +#define MVF_SHMEM_SEMAPHORE_NUMBER (1) +#define MVF_PRINTF_SEMAPHORE_NUMBER (2) +#define MVF_I2C_SEMAPHORE_NUMBER (3) +#define MVF_RESERVED1_SEMAPHORE_NUMBER (4) +#define MVF_RESERVED2_SEMAPHORE_NUMBER (5) + +#ifdef __KERNEL__ + +#include <linux/sched.h> + +typedef struct mvf_sema4_handle_struct { + int gate_num; + wait_queue_head_t wait_queue; + // stats + u32 attempts; + u32 interrupts; + u32 failures; + struct timeval request_time; + u64 total_latency_us; + u32 worst_latency_us; +} MVF_SEMA4; + +int mvf_sema4_assign(int gate_num, MVF_SEMA4** sema4_p); +int mvf_sema4_deassign(MVF_SEMA4 *sema4); +int mvf_sema4_lock(MVF_SEMA4 *sema4, unsigned int timeout_us, bool use_interrupts); +int mvf_sema4_unlock(MVF_SEMA4 *sema4); +int mvf_sema4_test(MVF_SEMA4 *sema4); + +#endif /* __KERNEL__ */ +#endif /* __MVF_SEMA4__ */ diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 94bbd8fee90d..0ec0298ad999 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -876,6 +876,34 @@ void irq_cpu_offline(void) #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY /** + * irq_chip_enable_parent - Enable the parent interrupt (defaults to unmask if + * NULL) + * @data: Pointer to interrupt specific data + */ +void irq_chip_enable_parent(struct irq_data *data) +{ + data = data->parent_data; + if (data->chip->irq_enable) + data->chip->irq_enable(data); + else + data->chip->irq_unmask(data); +} + +/** + * irq_chip_disable_parent - Disable the parent interrupt (defaults to mask if + * NULL) + * @data: Pointer to interrupt specific data + */ +void irq_chip_disable_parent(struct irq_data *data) +{ + data = data->parent_data; + if (data->chip->irq_disable) + data->chip->irq_disable(data); + else + data->chip->irq_mask(data); +} + +/** * irq_chip_ack_parent - Acknowledge the parent interrupt * @data: Pointer to interrupt specific data */ diff --git a/kernel/irq/generic-chip.c b/kernel/irq/generic-chip.c index 61024e8abdef..15b370daf234 100644 --- a/kernel/irq/generic-chip.c +++ b/kernel/irq/generic-chip.c @@ -360,7 +360,7 @@ static struct lock_class_key irq_nested_lock_class; int irq_map_generic_chip(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw_irq) { - struct irq_data *data = irq_get_irq_data(virq); + struct irq_data *data = irq_domain_get_irq_data(d, virq); struct irq_domain_chip_generic *dgc = d->gc; struct irq_chip_generic *gc; struct irq_chip_type *ct; @@ -405,8 +405,7 @@ int irq_map_generic_chip(struct irq_domain *d, unsigned int virq, else data->mask = 1 << idx; - irq_set_chip_and_handler(virq, chip, ct->handler); - irq_set_chip_data(virq, gc); + irq_domain_set_info(d, virq, hw_irq, chip, gc, ct->handler, NULL, NULL); irq_modify_status(virq, dgc->irq_flags_to_clear, dgc->irq_flags_to_set); return 0; } diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 7fac311057b8..41bf6dc49f59 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -1232,6 +1232,27 @@ struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain, return (irq_data && irq_data->domain == domain) ? irq_data : NULL; } +/** + * irq_domain_set_info - Set the complete data for a @virq in @domain + * @domain: Interrupt domain to match + * @virq: IRQ number + * @hwirq: The hardware interrupt number + * @chip: The associated interrupt chip + * @chip_data: The associated interrupt chip data + * @handler: The interrupt flow handler + * @handler_data: The interrupt flow handler data + * @handler_name: The interrupt handler name + */ +void irq_domain_set_info(struct irq_domain *domain, unsigned int virq, + irq_hw_number_t hwirq, struct irq_chip *chip, + void *chip_data, irq_flow_handler_t handler, + void *handler_data, const char *handler_name) +{ + irq_set_chip_and_handler_name(virq, chip, handler, handler_name); + irq_set_chip_data(virq, chip_data); + irq_set_handler_data(virq, handler_data); +} + static void irq_domain_check_hierarchy(struct irq_domain *domain) { } diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 98c9525bd751..b873c67c46b0 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -323,8 +323,6 @@ SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm9712_out3_mux_controls), SND_SOC_DAPM_MUX("Speaker Mux", SND_SOC_NOPM, 0, 0, &wm9712_spk_mux_controls), -SND_SOC_DAPM_MUX("Capture Phone Mux", SND_SOC_NOPM, 0, 0, - &wm9712_capture_phone_mux_controls), SND_SOC_DAPM_MUX("Left Capture Select", SND_SOC_NOPM, 0, 0, &wm9712_capture_selectl_controls), SND_SOC_DAPM_MUX("Right Capture Select", SND_SOC_NOPM, 0, 0, @@ -333,8 +331,6 @@ SND_SOC_DAPM_MUX("Left Mic Select Source", SND_SOC_NOPM, 0, 0, &wm9712_mic_src_controls), SND_SOC_DAPM_MUX("Right Mic Select Source", SND_SOC_NOPM, 0, 0, &wm9712_mic_src_controls), -SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0, - &wm9712_diff_sel_controls), SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_INT_PAGING, 9, 1, &wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls)), diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 19c302b0d763..a6ae57e7d0e2 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -15,6 +15,8 @@ config SND_SOC_FSL_ASRC config SND_SOC_FSL_SAI tristate "Synchronous Audio Interface (SAI) module support" select REGMAP_MMIO + select SND_SOC_AC97_BUS + select SND_SOC_AC97_CODEC select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n select SND_SOC_GENERIC_DMAENGINE_PCM help @@ -217,6 +219,14 @@ config SND_SOC_PHYCORE_AC97 Say Y if you want to add support for SoC audio on Phytec phyCORE and phyCARD boards in AC97 mode +config SND_SOC_FSL_SAI_WM9712 + tristate "SoC Audio support for Freescale SoC's using SAI and WM9712 codec" + select SND_SOC_FSL_SAI + select SND_SOC_WM9712 + help + Say Y or M here if you want to add support for SoC audio on Freescale + SoC using SAI and the WM9712 (or compatible) codec. + config SND_SOC_EUKREA_TLV320 tristate "Eukrea TLV320" depends on ARCH_MXC && I2C diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index d28dc25c9375..43f5da761675 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -13,7 +13,7 @@ obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o # Freescale SSI/DMA/SAI/SPDIF Support snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o -snd-soc-fsl-sai-objs := fsl_sai.o +snd-soc-fsl-sai-objs := fsl_sai.o fsl_sai_clk.o fsl_sai_ac97.o snd-soc-fsl-ssi-y := fsl_ssi.o snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o snd-soc-fsl-spdif-objs := fsl_spdif.o @@ -60,6 +60,7 @@ snd-soc-imx-mc13783-objs := imx-mc13783.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o +obj-$(CONFIG_SND_SOC_FSL_SAI_WM9712) += fsl_sai_wm9712.o obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index 34667209b607..e77d38a7d117 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -47,6 +47,7 @@ /* SAI Transmit/Recieve Control Register */ #define FSL_SAI_CSR_TERE BIT(31) +#define FSL_SAI_CSR_BCE BIT(28) #define FSL_SAI_CSR_FR BIT(25) #define FSL_SAI_CSR_SR BIT(24) #define FSL_SAI_CSR_xF_SHIFT 16 @@ -71,7 +72,9 @@ #define FSL_SAI_CR1_RFW_MASK 0x1f /* SAI Transmit and Recieve Configuration 2 Register */ +#define FSL_SAI_CR2_SYNC_MASK (0x3 << 30) #define FSL_SAI_CR2_SYNC BIT(30) +#define FSL_SAI_CR2_BCS BIT(29) #define FSL_SAI_CR2_MSEL_MASK (0xff << 26) #define FSL_SAI_CR2_MSEL_BUS 0 #define FSL_SAI_CR2_MSEL_MCLK1 BIT(26) @@ -79,6 +82,8 @@ #define FSL_SAI_CR2_MSEL_MCLK3 (BIT(26) | BIT(27)) #define FSL_SAI_CR2_BCP BIT(25) #define FSL_SAI_CR2_BCD_MSTR BIT(24) +#define FSL_SAI_CR2_DIV_MASK 0xff +#define FSL_SAI_CR2_DIV(x) (x & 0xff) /* SAI Transmit and Recieve Configuration 3 Register */ #define FSL_SAI_CR3_TRCE BIT(16) @@ -100,7 +105,7 @@ #define FSL_SAI_CR5_WNW_MASK (0x1f << 24) #define FSL_SAI_CR5_W0W(x) (((x) - 1) << 16) #define FSL_SAI_CR5_W0W_MASK (0x1f << 16) -#define FSL_SAI_CR5_FBT(x) ((x) << 8) +#define FSL_SAI_CR5_FBT(x) ((x - 1) << 8) #define FSL_SAI_CR5_FBT_MASK (0x1f << 8) /* SAI type */ diff --git a/sound/soc/fsl/fsl_sai_ac97.c b/sound/soc/fsl/fsl_sai_ac97.c new file mode 100644 index 000000000000..6eb235f5551b --- /dev/null +++ b/sound/soc/fsl/fsl_sai_ac97.c @@ -0,0 +1,1332 @@ +/* + * Freescale ALSA SoC Digital Audio Interface (SAI) AC97 driver. + * + * Copyright (C) 2013-2015 Toradex, Inc. + * Authors: Stefan Agner, Marcel Ziswiler + * + * This program is free software, you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or(at your + * option) any later version. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <linux/pinctrl/consumer.h> + +#include <sound/ac97_codec.h> +#include <sound/initval.h> + +#include "fsl_sai.h" +#include "imx-pcm.h" + +struct imx_pcm_runtime_data { + unsigned int period; + int periods; + unsigned long offset; + struct snd_pcm_substream *substream; +}; + +#define EDMA_PRIO_HIGH 6 +#define SAI_AC97_DMABUF_SIZE (13 * 4) +#define SAI_AC97_RBUF_COUNT (4) +#define SAI_AC97_RBUF_FRAMES (1024) +#define SAI_AC97_RBUF_SIZE (SAI_AC97_RBUF_FRAMES * SAI_AC97_DMABUF_SIZE) +#define SAI_AC97_RBUF_SIZE_TOT (SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_SIZE) + +static struct fsl_sai_ac97 *info; + +struct fsl_sai_ac97 { + struct platform_device *pdev; + + resource_size_t mapbase; + + struct regmap *regmap; + struct clk *bus_clk; + struct clk *mclk_clk[FSL_SAI_MCLK_MAX]; + + struct dma_chan *dma_tx_chan; + struct dma_chan *dma_rx_chan; + struct dma_async_tx_descriptor *dma_tx_desc; + struct dma_async_tx_descriptor *dma_rx_desc; + + dma_cookie_t dma_tx_cookie; + dma_cookie_t dma_rx_cookie; + + struct snd_dma_buffer rx_buf; + struct snd_dma_buffer tx_buf; + + bool big_endian_regs; + bool big_endian_data; + bool is_dsp_mode; + bool sai_on_imx; + + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + + + struct snd_card *card; + + struct mutex lock; + + int cmdbufid; + unsigned short reg; + unsigned short val; + + struct snd_soc_platform platform; + + atomic_t playing; + atomic_t capturing; + + struct imx_pcm_runtime_data *iprtd_playback; + struct imx_pcm_runtime_data *iprtd_capture; +}; + +#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\ + FSL_SAI_CSR_FEIE) + + +struct ac97_tx { + /* + * Slot 0: TAG + * Bit 15 Codec Ready + * Bit 14:3 Slot Valid (Which of slot 1 to slot 12 contain valid data) + * Bit 2 Zero + * Bit 1:0 Codec ID + */ + unsigned int reserved_2:4; /* Align the 16-bit Tag to 20-bit */ + unsigned int codec_id:2; + unsigned int reserved_1:1; + unsigned int slot_valid:12; + unsigned int valid:1; + unsigned int align_32_0:12; + + /* + * Slot 1: Command Address Port + * Bit(19) Read/Write command (1=read, 0=write) + * Bit(18:12) Control Register Index (64 16-bit locations, + * addressed on even byte boundaries) + * Bit(11:0) Reserved (Stuffed with 0’s) + */ + unsigned int reserved_3:12; + unsigned int cmdindex:7; + unsigned int cmdread:1; + unsigned int align_32_1:12; + + + /* + * Slot 2: Command Data Port + * The command data port is used to deliver 16-bit + * control register write data in the event that + * the current command port operation is a write + * cycle. (as indicated by Slot 1, bit 19) + * Bit(19:4) Control Register Write Data (Completed + * with 0’s if current operation is a read) + * Bit(3:0) Reserved (Completed with 0’s) + */ + unsigned int reserved_4:4; + unsigned int cmddata:16; + unsigned int align_32_2:12; + + unsigned int slots_data[10]; +} __attribute__((__packed__)); + +struct ac97_rx { + /* + * Slot 0: TAG + * Bit 15 Codec Ready + * Bit 14:3 Slot Valid (Which of slot 1 to slot 12 contain valid data) + * Bit 2:0 Zero + */ + unsigned int reserved_2:4; /* Align the 16-bit Tag to 20-bit */ + unsigned int reserved_1:3; + unsigned int slot_valid:12; + unsigned int valid:1; + unsigned int align_32_0:12; + + /* + * Slot 1: Status Address + */ + unsigned int reserved_4:2; + unsigned int slot_req:10; + unsigned int regindex:7; + unsigned int reserved_3:1; + unsigned int align_32_1:12; + + /* + * Slot 2: Status Data + * Bit 19:4 Control Register Read Data (Completed with 0’s if tagged + * “invalid” by AC‘97) + * Bit 3:0 RESERVED (Completed with 0’s) + */ + unsigned int reserved_5:4; + unsigned int cmddata:16; + unsigned int align_32_2:12; + + unsigned int slots_data[10]; +} __attribute__((__packed__)); + +static void fsl_dma_tx_complete(void *arg) +{ + struct fsl_sai_ac97 *sai = arg; + struct ac97_tx *aclink; + struct imx_pcm_runtime_data *iprtd = sai->iprtd_playback; + int i = 0; + struct dma_tx_state state; + enum dma_status status; + int bufid; + + async_tx_ack(sai->dma_tx_desc); + + status = dmaengine_tx_status(sai->dma_tx_chan, sai->dma_tx_cookie, &state); + + /* Calculate the id of the running buffer */ + if (state.residue % SAI_AC97_RBUF_SIZE == 0) + bufid = 4 - (state.residue / SAI_AC97_RBUF_SIZE); + else + bufid = 3 - (state.residue / SAI_AC97_RBUF_SIZE); + + /* Calculate the id of the next free buffer */ + bufid = (bufid + 1) % 4; + + /* First frame of the just completed buffer... */ + aclink = (struct ac97_tx *)(sai->tx_buf.area + (bufid * SAI_AC97_RBUF_SIZE)); + + if (atomic_read(&info->playing)) + { + struct snd_dma_buffer *buf = &iprtd->substream->dma_buffer; + u16 *ptr = (u16 *)(buf->area + iprtd->offset); + + /* Copy samples of the PCM stream into PCM slots 3/4 */ + for (i = 0; i < SAI_AC97_RBUF_FRAMES; i++) { + + aclink->valid = 1; + aclink->slot_valid |= (1 << 9 | 1 << 8); + aclink->slots_data[0] = ptr[i * 2]; + aclink->slots_data[0] <<= 4; + aclink->slots_data[1] = ptr[i * 2 + 1]; + aclink->slots_data[1] <<= 4; + aclink++; + } + + iprtd->offset += SAI_AC97_RBUF_FRAMES * 4; + iprtd->offset %= (SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT); + snd_pcm_period_elapsed(iprtd->substream); + } + else if (aclink->slot_valid & (1 << 9 | 1 << 8)) + { + /* There is nothing playing anymore, clean the samples */ + for (i = 0; i < SAI_AC97_RBUF_FRAMES; i++) { + aclink->valid = 0; + aclink->slot_valid &= ~(1 << 9 | 1 << 8); + aclink->slots_data[0] = 0; + aclink->slots_data[1] = 0; + aclink++; + } + } +} + +static void fsl_dma_rx_complete(void *arg) +{ + struct fsl_sai_ac97 *sai = arg; + struct ac97_rx *aclink; + struct imx_pcm_runtime_data *iprtd = sai->iprtd_capture; + struct dma_tx_state state; + enum dma_status status; + int bufid; + int i; + + async_tx_ack(sai->dma_rx_desc); + + status = dmaengine_tx_status(sai->dma_rx_chan, sai->dma_rx_cookie, &state); + + /* Calculate the id of the running buffer */ + if (state.residue % SAI_AC97_RBUF_SIZE == 0) + bufid = 4 - (state.residue / SAI_AC97_RBUF_SIZE); + else + bufid = 3 - (state.residue / SAI_AC97_RBUF_SIZE); + + /* Calculate the id of the last processed buffer */ + bufid = (bufid + 3) % 4; + + /* First frame of the just completed buffer... */ + aclink = (struct ac97_rx *)(sai->rx_buf.area + (bufid * SAI_AC97_RBUF_SIZE)); + + if (atomic_read(&info->capturing)) + { + struct snd_dma_buffer *buf = &iprtd->substream->dma_buffer; + u16 *ptr = (u16 *)buf->area; + + /* + * Loop through all AC97 frames, but only some might have data: + * Depending on bit rate, the valid flag might not be set for + * all frames (see AC97 VBR specification) + */ + for (i = 0; i < SAI_AC97_RBUF_FRAMES; i++, aclink++) { + if (!aclink->valid) + continue; + + if (aclink->slot_valid & (1 << 9)) { + ptr[iprtd->offset / 2] = aclink->slots_data[0] >> 4; + iprtd->offset+=2; + } + + if (aclink->slot_valid & (1 << 8)) { + ptr[iprtd->offset / 2] = aclink->slots_data[1] >> 4; + iprtd->offset+=2; + } + + iprtd->offset %= (SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT); + } + + snd_pcm_period_elapsed(iprtd->substream); + } +} + +static int vf610_sai_ac97_read_write(struct snd_ac97 *ac97, bool isread, + unsigned short reg, unsigned short *val) +{ + enum dma_status rx_status; + enum dma_status tx_status; + struct dma_tx_state tx_state; + struct dma_tx_state rx_state; + struct ac97_tx *tx_aclink; + struct ac97_rx *rx_aclink; + int rxbufidstart, txbufidstart, txbufid, rxbufid, curbufid; + unsigned long flags; + int ret = 0; + int rxbufmaxcheck = 10; + + /* + * We need to disable interrupts to make sure we insert the message + * before the next AC97 frame has been sent + */ + local_irq_save(flags); + tx_status = dmaengine_tx_status(info->dma_tx_chan, info->dma_tx_cookie, + &tx_state); + rx_status = dmaengine_tx_status(info->dma_rx_chan, info->dma_rx_cookie, + &rx_state); + + /* Calculate next DMA buffer sent out to the AC97 codec */ + rxbufidstart = (SAI_AC97_RBUF_SIZE_TOT - rx_state.residue) / SAI_AC97_DMABUF_SIZE; + rxbufidstart %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES; + txbufidstart = (SAI_AC97_RBUF_SIZE_TOT - tx_state.residue) / SAI_AC97_DMABUF_SIZE; + txbufidstart %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES; + + /* Safety margin, use next buffer in case current buffer is DMA'ed now */ + txbufid = txbufidstart + 1; + txbufid %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES; + tx_aclink = (struct ac97_tx *)(info->tx_buf.area + (txbufid * SAI_AC97_DMABUF_SIZE)); + + /* Put our request into the next AC97 frame */ + tx_aclink->valid = 1; + tx_aclink->slot_valid |= (1 << 11); + + tx_aclink->cmdread = isread; + tx_aclink->cmdindex = reg; + + if (!isread) { + tx_aclink->slot_valid |= (1 << 10); + tx_aclink->cmddata = *val; + } + + local_irq_restore(flags); + + /* Wait at least until TX frame is in FIFO... */ + if (!isread) { + do { + usleep_range(50, 200); + tx_status = dmaengine_tx_status(info->dma_tx_chan, info->dma_tx_cookie, + &tx_state); + curbufid = ((SAI_AC97_RBUF_SIZE_TOT - tx_state.residue) / SAI_AC97_DMABUF_SIZE); + + if (likely(txbufid > txbufidstart) && + (curbufid > txbufid || curbufid < txbufidstart)) + break; + + /* Wrap-around case */ + if (unlikely(txbufid < txbufidstart) && + (curbufid > txbufid && curbufid < txbufidstart)) + break; + } while (true); + goto clear_command; + } + + /* + * Look into every frame starting at the RX frame which was + * last copied by DMA at command insert time. Typically, the + * answer is in RX start frame +4. Factors which sum up to + * this delay are: + * - TX send delay (+1 safety margin, +2 TX FIFO) + * - AC97 codec sends back the answer in the next frame (+1) + * + * TX ring buffer + * |------|------|------|------|------|------|------|------| + * | | | |txbuf |txbuf | | | | + * | | | |start | | | | | + * |------|------|------|------|------|------|------|------| + * + * RX ring buffer + * |------|------|------|------|------|------|------|------| + * | |rxbuf | | | |rxbuf | | | + * | |start | | | | | | | + * |------|------|------|------|------|------|------|------| + * + */ + rxbufid = rxbufidstart; + curbufid = rxbufid; + do { + while (rxbufid == curbufid) + { + /* Wait for frames being transmitted/received... */ + usleep_range(50, 200); + rx_status = dmaengine_tx_status(info->dma_rx_chan, info->dma_rx_cookie, + &rx_state); + curbufid = ((SAI_AC97_RBUF_SIZE_TOT - rx_state.residue) / SAI_AC97_DMABUF_SIZE); + } + + /* Ok, check frames... */ + rx_aclink = (struct ac97_rx *)(info->rx_buf.area + rxbufid * SAI_AC97_DMABUF_SIZE); + if (rx_aclink->slot_valid & (1 << 11 | 1 << 10) && + rx_aclink->regindex == reg) + { + *val = rx_aclink->cmddata; + break; + } + + rxbufmaxcheck--; + rxbufid++; + rxbufid %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES; + } while (rxbufmaxcheck); + + if (!rxbufmaxcheck) { + pr_err("%s: rx timeout, checked buffer %d to %d, current %d\n", + __func__, rxbufidstart, rxbufid, curbufid); + ret = -ETIMEDOUT; + } + +clear_command: + /* Clear sent command... */ + tx_aclink->slot_valid &= ~(1 << 11 | 1 << 10); + tx_aclink->cmdread = 0; + tx_aclink->cmdindex = 0; + tx_aclink->cmddata = 0; + + return ret; +} + +static unsigned short vf610_sai_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + unsigned short val = 0; + int err; + + err = vf610_sai_ac97_read_write(ac97, true, reg, &val); + pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); + + if (err) + pr_err("failed to read register 0x%02x\n", reg); + + return val; +} + +static void vf610_sai_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + int err; + + err = vf610_sai_ac97_read_write(ac97, false, reg, &val); + pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); + + if (err) + pr_err("failed to write register 0x%02x\n", reg); +} + + +static struct snd_ac97_bus_ops fsl_sai_ac97_ops = { + .read = vf610_sai_ac97_read, + .write = vf610_sai_ac97_write, +}; + +static int fsl_sai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + pr_debug("%s, %d\n", __func__, substream->stream); + + return 0; +} + +static void fsl_sai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + pr_debug("%s, %d\n", __func__, substream->stream); +} + +static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { + //.set_sysclk = fsl_sai_set_dai_sysclk, + //.set_fmt = fsl_sai_set_dai_fmt, + //.hw_params = fsl_sai_hw_params, + //.trigger = fsl_sai_trigger, + .startup = fsl_sai_startup, + .shutdown = fsl_sai_shutdown, +}; + +static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai_ac97 *sai = dev_get_drvdata(cpu_dai->dev); + + snd_soc_dai_set_drvdata(cpu_dai, sai); + + return 0; +} + +static struct snd_soc_dai_driver fsl_sai_ac97_dai = { + .name = "fsl-sai-ac97-pcm", + .bus_control = true, + .probe = fsl_sai_dai_probe, + .playback = { + .stream_name = "PCM Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "PCM Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &fsl_sai_pcm_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_component = { + .name = "fsl-sai", +}; + +static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TFR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RDR: + case FSL_SAI_RFR: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TFR: + case FSL_SAI_RFR: + case FSL_SAI_TDR: + case FSL_SAI_RDR: + return true; + default: + return false; + } + +} + +static bool fsl_sai_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_RDR: + return true; + default: + return false; + } +} + +static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TDR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static struct regmap_config fsl_sai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FSL_SAI_RMR, + .precious_reg = fsl_sai_precious_reg, + .readable_reg = fsl_sai_readable_reg, + .volatile_reg = fsl_sai_volatile_reg, + .writeable_reg = fsl_sai_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static struct snd_pcm_hardware snd_sai_ac97_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .buffer_bytes_max = SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT, + .period_bytes_min = SAI_AC97_RBUF_FRAMES * 4, + .period_bytes_max = SAI_AC97_RBUF_FRAMES * 4, + .periods_min = SAI_AC97_RBUF_COUNT, + .periods_max = SAI_AC97_RBUF_COUNT, + .fifo_size = 0, +}; + +static int snd_fsl_sai_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + iprtd->periods = params_periods(params); + iprtd->period = params_period_bytes(params); + iprtd->offset = 0; + + pr_debug("%s: period %d, periods %d\n", __func__, + iprtd->period, iprtd->periods); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int snd_fsl_sai_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + pr_debug("%s:, %p, cmd %d\n", __func__, substream, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + atomic_set(&info->playing, 1); + else + atomic_set(&info->capturing, 1); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + atomic_set(&info->playing, 0); + else + atomic_set(&info->capturing, 0); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t snd_fsl_sai_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + return bytes_to_frames(substream->runtime, iprtd->offset); +} + +static int snd_fsl_sai_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + ret = dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); + + return ret; +} + +static int snd_fsl_sai_pcm_prepare(struct snd_pcm_substream *substream) +{ + pr_debug("%s, %p\n", __func__, substream); + return 0; +} + +static int snd_fsl_sai_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd; + int ret; + + iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); + + if (iprtd == NULL) + return -ENOMEM; + + runtime->private_data = iprtd; + iprtd->substream = substream; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + kfree(iprtd); + return ret; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + atomic_set(&info->playing, 0); + info->iprtd_playback = iprtd; + } else { + atomic_set(&info->capturing, 0); + info->iprtd_capture = iprtd; + } + + snd_soc_set_runtime_hwparams(substream, &snd_sai_ac97_hardware); + + return 0; +} + +static int snd_fsl_sai_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + info->iprtd_playback = NULL; + else + info->iprtd_capture = NULL; + + kfree(iprtd); + + return 0; +} + +static struct snd_pcm_ops fsl_sai_pcm_ops = { + .open = snd_fsl_sai_open, + .close = snd_fsl_sai_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_fsl_sai_pcm_hw_params, + .prepare = snd_fsl_sai_pcm_prepare, + .trigger = snd_fsl_sai_pcm_trigger, + .pointer = snd_fsl_sai_pcm_pointer, + .mmap = snd_fsl_sai_pcm_mmap, +}; + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + /* Allocate for buffers, 16-Bit stereo data.. */ + size_t size = SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + + return 0; +} + +static int fsl_sai_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_card *card = rtd->card->snd_card; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + pr_debug("%s, %p\n", __func__, pcm); + + return 0; +} + +static void fsl_sai_pcm_free(struct snd_pcm *pcm) +{ + pr_debug("%s, %p\n", __func__, pcm); +} + +static struct snd_soc_platform_driver ac97_software_pcm_platform = { + .ops = &fsl_sai_pcm_ops, + .pcm_new = fsl_sai_pcm_new, + .pcm_free = fsl_sai_pcm_free, +}; + + +static int fsl_sai_ac97_prepare_tx_dma(struct fsl_sai_ac97 *sai) +{ + struct dma_slave_config dma_tx_sconfig; + int ret; + + dma_tx_sconfig.dst_addr = sai->mapbase + FSL_SAI_TDR; + dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_tx_sconfig.dst_maxburst = 13; + dma_tx_sconfig.direction = DMA_MEM_TO_DEV; + ret = dmaengine_slave_config(sai->dma_tx_chan, &dma_tx_sconfig); + if (ret < 0) { + dev_err(&sai->pdev->dev, + "DMA slave config failed, err = %d\n", ret); + dma_release_channel(sai->dma_tx_chan); + return ret; + } + sai->dma_tx_desc = dmaengine_prep_dma_cyclic(sai->dma_tx_chan, + sai->tx_buf.addr, SAI_AC97_RBUF_SIZE_TOT, + SAI_AC97_RBUF_SIZE, DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT); + sai->dma_tx_desc->callback = fsl_dma_tx_complete; + sai->dma_tx_desc->callback_param = sai; + + return 0; +}; + +static int fsl_sai_ac97_prepare_rx_dma(struct fsl_sai_ac97 *sai) +{ + struct dma_slave_config dma_rx_sconfig; + int ret; + + dma_rx_sconfig.src_addr = sai->mapbase + FSL_SAI_RDR; + dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_rx_sconfig.src_maxburst = 13; + dma_rx_sconfig.direction = DMA_DEV_TO_MEM; + ret = dmaengine_slave_config(sai->dma_rx_chan, &dma_rx_sconfig); + if (ret < 0) { + dev_err(&sai->pdev->dev, + "DMA slave config failed, err = %d\n", ret); + dma_release_channel(sai->dma_rx_chan); + return ret; + } + sai->dma_rx_desc = dmaengine_prep_dma_cyclic(sai->dma_rx_chan, + sai->rx_buf.addr, SAI_AC97_RBUF_SIZE_TOT, + SAI_AC97_RBUF_SIZE, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + sai->dma_rx_desc->callback = fsl_dma_rx_complete; + sai->dma_rx_desc->callback_param = sai; + + return 0; +}; + +static void fsl_sai_ac97_reset_sai(struct fsl_sai_ac97 *sai) +{ + /* TX */ + /* Issue software reset */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR, + FSL_SAI_CSR_SR); + + udelay(2); + /* Release software reset */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR, 0); + + /* FIFO reset */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_FR, + FSL_SAI_CSR_FR); + + /* RX */ + /* Issue software reset */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR, + FSL_SAI_CSR_SR); + + udelay(2); + /* Release software reset */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR, 0); + + /* FIFO reset */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_FR, + FSL_SAI_CSR_FR); +}; + +static int fsl_sai_ac97_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_sai_ac97 *sai; + struct resource *res; + void __iomem *base; + char tmp[8]; + //int irq + int ret, i; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + info = sai; + sai->pdev = pdev; + + if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx6sx-sai")) + sai->sai_on_imx = true; + + sai->big_endian_regs = of_property_read_bool(np, "big-endian-regs"); + if (sai->big_endian_regs) + fsl_sai_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG; + + sai->big_endian_data = of_property_read_bool(np, "big-endian-data"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sai->mapbase = res->start; + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "bus", base, &fsl_sai_regmap_config); + + /* Compatible with old DTB cases */ + if (IS_ERR(sai->regmap)) + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "sai", base, &fsl_sai_regmap_config); + if (IS_ERR(sai->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(sai->regmap); + } + + /* No error out for old DTB cases but only mark the clock NULL */ + sai->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(sai->bus_clk)) { + dev_err(&pdev->dev, "failed to get bus clock: %ld\n", + PTR_ERR(sai->bus_clk)); + sai->bus_clk = NULL; + } + + ret = clk_prepare_enable(sai->bus_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable bus clk: %d\n", ret); + return ret; + } + + for (i = 0; i < FSL_SAI_MCLK_MAX; i++) { + sprintf(tmp, "mclk%d", i + 1); + sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(sai->mclk_clk[i])) { + dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n", + i + 1, PTR_ERR(sai->mclk_clk[i])); + sai->mclk_clk[i] = NULL; + } + } + + ret = snd_soc_set_ac97_ops_of_reset(&fsl_sai_ac97_ops, pdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to reset AC97 link: %d\n", ret); + goto err_disable_clock; + } + + mutex_init(&info->lock); + + /* clear transmit/receive configuration/status registers */ + regmap_write(sai->regmap, FSL_SAI_TCSR, 0x0); + regmap_write(sai->regmap, FSL_SAI_RCSR, 0x0); + + /* pre allocate DMA buffers */ + sai->tx_buf.dev.type = SNDRV_DMA_TYPE_DEV; + sai->tx_buf.dev.dev = &pdev->dev; + sai->tx_buf.private_data = NULL; + sai->tx_buf.area = dma_alloc_writecombine(&pdev->dev, SAI_AC97_RBUF_SIZE_TOT, + &sai->tx_buf.addr, GFP_KERNEL); + if (!sai->tx_buf.area) { + ret = -ENOMEM; + //goto failed_tx_buf; + return ret; + } + sai->tx_buf.bytes = SAI_AC97_RBUF_SIZE_TOT; + + sai->rx_buf.dev.type = SNDRV_DMA_TYPE_DEV; + sai->rx_buf.dev.dev = &pdev->dev; + sai->rx_buf.private_data = NULL; + sai->rx_buf.area = dma_alloc_writecombine(&pdev->dev, SAI_AC97_RBUF_SIZE_TOT, + &sai->rx_buf.addr, GFP_KERNEL); + if (!sai->rx_buf.area) { + ret = -ENOMEM; + //goto failed_rx_buf; + return ret; + } + sai->rx_buf.bytes = SAI_AC97_RBUF_SIZE_TOT; + + memset(sai->tx_buf.area, 0, SAI_AC97_RBUF_SIZE_TOT); + memset(sai->rx_buf.area, 0, SAI_AC97_RBUF_SIZE_TOT); + + /* 1. Configuration of SAI clock mode */ + + /* + * Issue software reset and FIFO reset for Transmitter and Receiver + * sections before starting configuration. + */ + fsl_sai_ac97_reset_sai(sai); + + /* Configure FIFO watermark. FIFO watermark is used as an indicator for + DMA trigger when read or write data from/to FIFOs. */ + /* Watermark level for all enabled transmit channels of one SAI module. + */ + regmap_write(sai->regmap, FSL_SAI_TCR1, 13); + regmap_write(sai->regmap, FSL_SAI_RCR1, 13); + + /* Configure the clocking mode, bitclock polarity, direction, and + divider. Clocking mode defines synchronous or asynchronous operation + for SAI module. Bitclock polarity configures polarity of the + bitclock. Bitclock direction configures direction of the bitclock. + Bus master has bitclock generated externally, slave has bitclock + generated internally */ + + /* TX */ + /* The transmitter must be configured for asynchronous operation */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC_MASK, 0); + + /* bit clock not swapped */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCS, 0); + + /* Bitclock is active high (drive outputs on rising edge and sample + * inputs on falling edge + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCP, 0); + + /* Bitclock is generated externally (Slave mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCD_MSTR, 0); + + /* RX */ + /* The receiver must be configured for synchronous operation. */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC_MASK, + FSL_SAI_CR2_SYNC); + + /* bit clock not swapped */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCS, 0); + + /* Bitclock is active high (drive outputs on rising edge and sample + * inputs on falling edge + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCP, 0); + + /* Bitclock is generated externally (Slave mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCD_MSTR, 0); + + /* Configure frame size, frame sync width, MSB first, frame sync early, + polarity, and direction + Frame size – configures the number of words in each frame. AC97 + requires 13 words per frame. + Frame sync width – configures the length of the frame sync in number + of bitclock. The sync width cannot be longer than the first word of + the frame. AC97 requires frame sync asserted for first word. */ + + /* Configures number of words in each frame. The value written should be + * one less than the number of words in the frame (part of define!) + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FRSZ_MASK, + FSL_SAI_CR4_FRSZ(13)); + + /* Configures length of the frame sync. The value written should be one + * less than the number of bitclocks. + * AC97 - 16 bits transmitted in first word. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_SYWD_MASK, + FSL_SAI_CR4_SYWD(16)); + + + /* MSB is transmitted first */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_MF, + FSL_SAI_CR4_MF); + + /* Frame sync asserted one bit before the first bit of the frame */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSE, + FSL_SAI_CR4_FSE); + + /* A new AC-link input frame begins with a low to high transition of + * SYNC. Frame sync is active high + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSP, 0); + + /* Frame sync is generated internally (Master mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSD_MSTR, + FSL_SAI_CR4_FSD_MSTR); + + /* RX */ + /* Configures number of words in each frame. The value written should be + * one less than the number of words in the frame (part of define!) + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FRSZ_MASK, + FSL_SAI_CR4_FRSZ(13)); + + /* Configures length of the frame sync. The value written should be one + * less than the number of bitclocks. + * AC97 - 16 bits transmitted in first word. + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_SYWD_MASK, + FSL_SAI_CR4_SYWD(16)); + + + /* MSB is transmitted first */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_MF, + FSL_SAI_CR4_MF); + + /* Frame sync asserted one bit before the first bit of the frame */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSE, + FSL_SAI_CR4_FSE); + + /* A new AC-link input frame begins with a low to high transition of + * SYNC. Frame sync is active high + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSP, 0); + + /* Frame sync is generated internally (Master mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSD_MSTR, + FSL_SAI_CR4_FSD_MSTR); + + /* Configure the Word 0 and next word sizes. + W0W – defines number of bits in the first word in each frame. + WNW – defines number of bits in each word for each word except the + first in the frame. */ + + /* TX */ + /* Number of bits in first word in each frame. AC97 – 16-bit word is + * transmitted. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_W0W_MASK, + FSL_SAI_CR5_W0W(16)); + + /* Number of bits in each word in each frame. AC97 – 20-bit word is + * transmitted. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_WNW_MASK, + FSL_SAI_CR5_WNW(20)); + + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_W0W_MASK, + FSL_SAI_CR5_W0W(16)); + + /* Configures the bit index for the first bit transmitted for each word + * in the frame. The value written must be greater than or equal to the + * word width when configured for MSB First. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_FBT_MASK, + FSL_SAI_CR5_FBT(20)); + + /* RX */ + /* Number of bits in first word in each frame. AC97 – 16-bit word is + * transmitted. + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_W0W_MASK, + FSL_SAI_CR5_W0W(16)); + + /* Number of bits in each word in each frame. AC97 – 20-bit word is + * transmitted. + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_WNW_MASK, + FSL_SAI_CR5_WNW(20)); + + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_W0W_MASK, + FSL_SAI_CR5_W0W(16)); + + /* Configures the bit index for the first bit transmitted for each word + * in the frame. The value written must be greater than or equal to the + * word width when configured for MSB First. + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_FBT_MASK, + FSL_SAI_CR5_FBT(20)); + + + /* Clear the Transmit and Receive Mask registers. */ + regmap_write(sai->regmap, FSL_SAI_TMR, 0); + regmap_write(sai->regmap, FSL_SAI_RMR, 0); + + + sai->dma_tx_chan = dma_request_slave_channel(&pdev->dev, "tx"); + if (!sai->dma_tx_chan) { + dev_err(&pdev->dev, "DMA tx channel request failed!\n"); + return -ENODEV; + } + + sai->dma_rx_chan = dma_request_slave_channel(&pdev->dev, "rx"); + if (!sai->dma_rx_chan) { + dev_err(&pdev->dev, "DMA rx channel request failed!\n"); + return -ENODEV; + } + + /* Enables a data channel for a transmit operation. */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR3, FSL_SAI_CR3_TRCE, + FSL_SAI_CR3_TRCE); + + /* Enables a data channel for a receive operation. */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR3, FSL_SAI_CR3_TRCE, + FSL_SAI_CR3_TRCE); + + + /* In synchronous mode, receiver is enabled only when both transmitter + and receiver are enabled. It is recommended that transmitter is + enabled last and disabled first. */ + /* Enable receiver */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE); + + /* Enable transmitter */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE); + + platform_set_drvdata(pdev, sai); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, + &fsl_sai_ac97_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register Component: %d\n", ret); + goto err_disable_clock; + } + + /* Register our own PCM device, which fills the AC97 frames... */ + snd_soc_add_platform(&pdev->dev, &sai->platform, &ac97_software_pcm_platform); + + /* Start the DMA engine */ + fsl_sai_ac97_prepare_tx_dma(sai); + fsl_sai_ac97_prepare_rx_dma(sai); + + sai->dma_tx_cookie = dmaengine_submit(sai->dma_tx_desc); + dma_async_issue_pending(sai->dma_tx_chan); + + sai->dma_rx_cookie = dmaengine_submit(sai->dma_rx_desc); + dma_async_issue_pending(sai->dma_rx_chan); + + return 0; + +err_disable_clock: + clk_disable_unprepare(sai->bus_clk); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int fsl_sai_ac97_suspend(struct device *dev) +{ + struct fsl_sai_ac97 *sai = dev_get_drvdata(dev); + + /* Disable receiver/transmitter */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, 0x0); + + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, 0x0); + + dmaengine_terminate_all(sai->dma_tx_chan); + dmaengine_terminate_all(sai->dma_rx_chan); + + regcache_cache_only(sai->regmap, true); + + return 0; +} + +static int fsl_sai_ac97_resume(struct device *dev) +{ + struct fsl_sai_ac97 *sai = dev_get_drvdata(dev); + + regcache_mark_dirty(sai->regmap); + regcache_cache_only(sai->regmap, false); + regcache_sync(sai->regmap); + + /* Reset SAI */ + fsl_sai_ac97_reset_sai(sai); + + /* Enable receiver */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE); + + /* Enable transmitter */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE); + + /* Restart the DMA engine */ + fsl_sai_ac97_prepare_tx_dma(sai); + fsl_sai_ac97_prepare_rx_dma(sai); + + sai->dma_tx_cookie = dmaengine_submit(sai->dma_tx_desc); + dma_async_issue_pending(sai->dma_tx_chan); + + sai->dma_rx_cookie = dmaengine_submit(sai->dma_rx_desc); + dma_async_issue_pending(sai->dma_rx_chan); + + return 0; +} +#else +#define fsl_sai_ac97_suspend NULL +#define fsl_sai_ac97_resume NULL +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_sai_ac97_pm = { + .suspend = fsl_sai_ac97_suspend, + .resume = fsl_sai_ac97_resume, +}; +static const struct of_device_id fsl_sai_ac97_ids[] = { + { .compatible = "fsl,vf610-sai-ac97", }, + { /* sentinel */ } +}; + +static struct platform_driver fsl_sai_ac97_driver = { + .probe = fsl_sai_ac97_probe, + .driver = { + .name = "fsl-sai-ac97", + .owner = THIS_MODULE, + .of_match_table = fsl_sai_ac97_ids, + .pm = &fsl_sai_ac97_pm, + }, +}; +module_platform_driver(fsl_sai_ac97_driver); + +MODULE_DESCRIPTION("Freescale SoC SAI AC97 Interface"); +MODULE_AUTHOR("Stefan Agner, Marcel Ziswiler"); +MODULE_ALIAS("platform:fsl-sai-ac97"); +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/fsl/fsl_sai_clk.c b/sound/soc/fsl/fsl_sai_clk.c new file mode 100644 index 000000000000..797e9ea486e3 --- /dev/null +++ b/sound/soc/fsl/fsl_sai_clk.c @@ -0,0 +1,246 @@ +/* + * Freescale ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright 2012-2013 Freescale Semiconductor, Inc. + * + * This program is free software, you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or(at your + * option) any later version. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_sai.h" + +static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TFR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RDR: + case FSL_SAI_RFR: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TFR: + case FSL_SAI_RFR: + case FSL_SAI_TDR: + case FSL_SAI_RDR: + return true; + default: + return false; + } + +} + +static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TDR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static struct regmap_config fsl_sai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FSL_SAI_RMR, + .readable_reg = fsl_sai_readable_reg, + .volatile_reg = fsl_sai_volatile_reg, + .writeable_reg = fsl_sai_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +#ifdef CONFIG_PM_SLEEP +static int fsl_sai_clk_suspend(struct device *dev) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + + /* disable AC97 master clock */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_BCE, 0); + + regcache_cache_only(sai->regmap, true); + + //clk_disable_unprepare(sai->bus_clk); + clk_disable_unprepare(sai->mclk_clk[0]); + + return 0; +} + +static int fsl_sai_clk_resume(struct device *dev) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + + clk_prepare_enable(sai->mclk_clk[0]); + + regcache_mark_dirty(sai->regmap); + regcache_cache_only(sai->regmap, false); + regcache_sync(sai->regmap); + + /* enable AC97 master clock */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_BCE, + FSL_SAI_CSR_BCE); + + return 0; +} +#else +#define fsl_sai_clk_suspend NULL +#define fsl_sai_clk_resume NULL +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_sai_clk_pm = { + .suspend_late = fsl_sai_clk_suspend, + .resume_early = fsl_sai_clk_resume, +}; + +static int fsl_sai_clk_probe(struct platform_device *pdev) +{ + struct fsl_sai *sai; + struct resource *res; + void __iomem *base; + char tmp[8]; + int i; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + sai->pdev = pdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "bus", base, &fsl_sai_regmap_config); + + /* Compatible with old DTB cases */ + if (IS_ERR(sai->regmap)) + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "sai", base, &fsl_sai_regmap_config); + if (IS_ERR(sai->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(sai->regmap); + } + + /* No error out for old DTB cases but only mark the clock NULL */ + sai->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(sai->bus_clk)) { + dev_err(&pdev->dev, "failed to get bus clock: %ld\n", + PTR_ERR(sai->bus_clk)); + sai->bus_clk = NULL; + } + + for (i = 0; i < FSL_SAI_MCLK_MAX; i++) { + sprintf(tmp, "mclk%d", i + 1); + sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(sai->mclk_clk[i])) { + dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n", + i + 1, PTR_ERR(sai->mclk_clk[i])); + sai->mclk_clk[i] = NULL; + } + } + + clk_prepare_enable(sai->mclk_clk[0]); + + /* configure AC97 master clock */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC, + ~FSL_SAI_CR2_SYNC); + + + /* asynchronous aka independent operation */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC_MASK, 0); + + /* Bit clock not swapped */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCS, 0); + + /* Clock selected by CCM_CSCMR1[SAIn_CLK_SEL] */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_MSEL_MASK, + FSL_SAI_CR2_MSEL_MCLK1); + + /* Bitclock is generated internally (master mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCD_MSTR, + FSL_SAI_CR2_BCD_MSTR); + + /* Divide by 6 */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_DIV_MASK, + FSL_SAI_CR2_DIV(2)); + + /* enable AC97 master clock */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_BCE, + FSL_SAI_CSR_BCE); + + platform_set_drvdata(pdev, sai); + + return clk_prepare_enable(sai->bus_clk); +} + +static const struct of_device_id fsl_sai_ids[] = { + { .compatible = "fsl,vf610-sai-clk", }, + { /* sentinel */ } +}; + +static struct platform_driver fsl_sai_driver = { + .probe = fsl_sai_clk_probe, + .driver = { + .name = "fsl-sai-clk", + .owner = THIS_MODULE, + .of_match_table = fsl_sai_ids, + .pm = &fsl_sai_clk_pm, + }, +}; +module_platform_driver(fsl_sai_driver); + +MODULE_DESCRIPTION("Freescale SoC SAI as clock generator"); +MODULE_AUTHOR("Stefan Agner, <stefan.agner@toradex.com>"); +MODULE_ALIAS("platform:fsl-sai-clk"); +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/fsl/fsl_sai_wm9712.c b/sound/soc/fsl/fsl_sai_wm9712.c new file mode 100644 index 000000000000..617c5ab45f64 --- /dev/null +++ b/sound/soc/fsl/fsl_sai_wm9712.c @@ -0,0 +1,130 @@ +/* + * fsl_sai_wm9712.c -- SoC audio for Freescale SAI + * + * Copyright 2014 Stefan Agner, Toradex AG <stefan@agner.ch> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <sound/soc.h> + +#define DRV_NAME "fsl-sai-ac97-dt-driver" + +static struct snd_soc_dai_link fsl_sai_wm9712_dai = { + .name = "AC97 HiFi", + .stream_name = "AC97 HiFi", + .codec_dai_name = "wm9712-hifi", + .codec_name = "wm9712-codec", +}; + +static const struct snd_soc_dapm_widget tegra_wm9712_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_LINE("LineIn", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + + +static struct snd_soc_card snd_soc_card_fsl_sai_wm9712 = { + .name = "Colibri VF61 AC97 Audio", + .owner = THIS_MODULE, + .dai_link = &fsl_sai_wm9712_dai, + .num_links = 1, + + .dapm_widgets = tegra_wm9712_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_wm9712_dapm_widgets), + .fully_routed = true, +}; + +static struct platform_device *codec; + +static int fsl_sai_wm9712_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_card_fsl_sai_wm9712; + int ret; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + codec = platform_device_alloc("wm9712-codec", -1); + if (!codec) + return -ENOMEM; + + ret = platform_device_add(codec); + if (ret) + goto codec_put; + + ret = snd_soc_of_parse_card_name(card, "fsl,model"); + if (ret) + goto codec_unregister; + + ret = snd_soc_of_parse_audio_routing(card, "fsl,audio-routing"); + if (ret) + goto codec_unregister; + + fsl_sai_wm9712_dai.cpu_of_node = of_parse_phandle(np, + "fsl,ac97-controller", 0); + if (!fsl_sai_wm9712_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'fsl,ac97-controller' missing or invalid\n"); + ret = -EINVAL; + goto codec_unregister; + } + + fsl_sai_wm9712_dai.platform_of_node = fsl_sai_wm9712_dai.cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto codec_unregister; + } + + return 0; + +codec_unregister: + platform_device_del(codec); + +codec_put: + platform_device_put(codec); + return ret; +} + +static int fsl_sai_wm9712_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + platform_device_unregister(codec); + + return 0; +} + +static const struct of_device_id fsl_sai_wm9712_of_match[] = { + { .compatible = "fsl,fsl-sai-audio-wm9712", }, + {}, +}; + +static struct platform_driver fsl_sai_wm9712_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = fsl_sai_wm9712_of_match, + }, + .probe = fsl_sai_wm9712_driver_probe, + .remove = fsl_sai_wm9712_driver_remove, +}; +module_platform_driver(fsl_sai_wm9712_driver); + +/* Module information */ +MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>"); +MODULE_DESCRIPTION("ALSA SoC Freescale SAI+WM9712"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, fsl_sai_wm9712_of_match); |