summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/sysfs-bus-iio-vf6107
-rw-r--r--Documentation/devicetree/bindings/arm/freescale/fsl,vf610-ddrmc.txt23
-rw-r--r--Documentation/devicetree/bindings/fb/fsl-dcu-fb.txt67
-rw-r--r--Documentation/devicetree/bindings/iio/adc/vf610-adc.txt14
-rw-r--r--Documentation/devicetree/bindings/input/touchscreen/colibri-vf50-ts.txt34
-rw-r--r--Documentation/devicetree/bindings/mtd/vf610-nfc.txt45
-rw-r--r--MAINTAINERS6
-rw-r--r--arch/arm/boot/dts/Makefile2
-rw-r--r--arch/arm/boot/dts/vf-colibri-dual-eth.dtsi91
-rw-r--r--arch/arm/boot/dts/vf-colibri-eval-v3.dtsi179
-rw-r--r--arch/arm/boot/dts/vf-colibri.dtsi205
-rw-r--r--arch/arm/boot/dts/vf500-colibri-dual-eth.dts17
-rw-r--r--arch/arm/boot/dts/vf500-colibri-eval-v3.dts4
-rw-r--r--arch/arm/boot/dts/vf500-colibri.dtsi45
-rw-r--r--arch/arm/boot/dts/vf500.dtsi10
-rw-r--r--arch/arm/boot/dts/vf610-colibri-dual-eth.dts17
-rw-r--r--arch/arm/boot/dts/vf610-colibri.dtsi84
-rw-r--r--arch/arm/boot/dts/vf610-cosmic.dts2
-rw-r--r--arch/arm/boot/dts/vf610-pinfunc.h2
-rw-r--r--arch/arm/boot/dts/vf610-twr.dts111
-rw-r--r--arch/arm/boot/dts/vfxxx.dtsi200
-rw-r--r--arch/arm/configs/colibri_vf_defconfig317
-rw-r--r--arch/arm/mach-imx/Makefile5
-rw-r--r--arch/arm/mach-imx/clk-gate2.c7
-rw-r--r--arch/arm/mach-imx/clk-vf610.c115
-rw-r--r--arch/arm/mach-imx/clk.h13
-rw-r--r--arch/arm/mach-imx/common.h11
-rw-r--r--arch/arm/mach-imx/mach-vf610.c8
-rw-r--r--arch/arm/mach-imx/pm-vf610.c661
-rw-r--r--arch/arm/mach-imx/suspend-vf610.S448
-rw-r--r--arch/arm/mach-imx/vf610_sema4.c314
-rw-r--r--drivers/clocksource/vf_pit_timer.c30
-rw-r--r--drivers/dma/fsl-edma.c92
-rw-r--r--drivers/gpio/gpio-vf610.c197
-rw-r--r--drivers/iio/adc/vf610_adc.c228
-rw-r--r--drivers/input/touchscreen/Kconfig19
-rw-r--r--drivers/input/touchscreen/Makefile2
-rw-r--r--drivers/input/touchscreen/colibri-vf50-ts.c411
-rw-r--r--drivers/input/touchscreen/fusion_F0710A.c503
-rw-r--r--drivers/input/touchscreen/fusion_F0710A.h87
-rw-r--r--drivers/irqchip/Kconfig1
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-nvic.c28
-rw-r--r--drivers/irqchip/irq-vf610-gpc.c123
-rw-r--r--drivers/irqchip/irq-vf610-mscm-ir.c151
-rw-r--r--drivers/mmc/host/sdhci-esdhc-imx.c2
-rw-r--r--drivers/mmc/host/sdhci-pltfm.c36
-rw-r--r--drivers/mmc/host/sdhci-pltfm.h2
-rw-r--r--drivers/mtd/nand/Kconfig11
-rw-r--r--drivers/mtd/nand/Makefile1
-rw-r--r--drivers/mtd/nand/nand_base.c41
-rw-r--r--drivers/mtd/nand/vf610_nfc.c889
-rw-r--r--drivers/pinctrl/freescale/pinctrl-imx.c62
-rw-r--r--drivers/pinctrl/freescale/pinctrl-imx.h3
-rw-r--r--drivers/pinctrl/freescale/pinctrl-vf610.c6
-rw-r--r--drivers/pwm/pwm-fsl-ftm.c58
-rw-r--r--drivers/soc/Kconfig1
-rw-r--r--drivers/soc/Makefile1
-rw-r--r--drivers/soc/fsl/Kconfig9
-rw-r--r--drivers/soc/fsl/Makefile1
-rw-r--r--drivers/soc/fsl/soc-vf610.c168
-rw-r--r--drivers/spi/spi-fsl-dspi.c19
-rw-r--r--drivers/tty/serial/fsl_lpuart.c208
-rw-r--r--drivers/usb/chipidea/ci.h6
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c6
-rw-r--r--drivers/usb/chipidea/core.c83
-rw-r--r--drivers/video/backlight/gpio_backlight.c10
-rw-r--r--drivers/video/fbdev/Kconfig11
-rw-r--r--drivers/video/fbdev/Makefile1
-rw-r--r--drivers/video/fbdev/fsl-dcu-fb.c1275
-rw-r--r--drivers/video/logo/Kconfig4
-rw-r--r--drivers/video/logo/Makefile2
-rw-r--r--drivers/video/logo/logo.c4
-rw-r--r--include/dt-bindings/clock/vf610-clock.h6
-rw-r--r--include/linux/input/fusion_F0710A.h20
-rw-r--r--include/linux/irq.h2
-rw-r--r--include/linux/irqdomain.h8
-rw-r--r--include/linux/linux_logo.h1
-rw-r--r--include/linux/mtd/nand.h5
-rw-r--r--include/linux/usb/chipidea.h1
-rw-r--r--include/linux/vf610_mscm.h13
-rw-r--r--include/linux/vf610_sema4.h33
-rw-r--r--kernel/irq/chip.c28
-rw-r--r--kernel/irq/generic-chip.c5
-rw-r--r--kernel/irq/irqdomain.c21
-rw-r--r--sound/soc/codecs/wm9712.c4
-rw-r--r--sound/soc/fsl/Kconfig10
-rw-r--r--sound/soc/fsl/Makefile3
-rw-r--r--sound/soc/fsl/fsl_sai.h7
-rw-r--r--sound/soc/fsl/fsl_sai_ac97.c1332
-rw-r--r--sound/soc/fsl/fsl_sai_clk.c246
-rw-r--r--sound/soc/fsl/fsl_sai_wm9712.c130
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 = <&reg_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 = <&ethphy0>;
@@ -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);