Merge branch 'renesas-drivers-2016-10-18-v4.9-rc1/dts-rcar-gen3.rc10' into v4.9-rc1/rcar-3.4.x
* renesas-drivers-2016-10-18-v4.9-rc1/dts-rcar-gen3.rc10: (226 commits)
arm64: dts: r8a7796-salvator-x: Disable sdr104 of SDHI
arm64: dts: r8a7795-salvator-x: Disable sdr104 of SDHI
arm64: dts: r8a7795-es1-salvator-x: Disable sdr104 of SDHI
arm64: dts: r8a7796: Fix phy-mode to rgmii-txid for EthernetAVB
arm64: dts: r8a7796: Delete the DRC module of VSP2 node.
arm64: dts: r8a7796-salvator-x: Reserve Multimedia cma for MMNGR
arm64: dts: r8a7796: Add QoS Driver node at Device Tree
arm64: dts: r8a7796: Add power domains for IPMMU (PMB)
arm64: dts: r8a7796: Support IPMMU (PMB) in IPMMU-VC0
arm64: dts: r8a7796: Hook up SYS-DMAC/Audio-DMAC to IPMMU
arm64: dts: r8a7796: Add IPMMU device nodes
arm64: dts: r8a7796-salvator-x: Add ADSP device support
arm64: dts: r8a7796: Add ADSP device node
arm64: dts: r8a7796: Add iVDP1C and FCP and VCP4 device nodes for UVCS
arm64: dts: r8a7796-salvator-x: Add VSP Manager support
arm64: dts: r8a7796: Convert VSP Driver into VSP Manager
arm64: dts: r8a7796-salvator-x: Add MMNGRBUF driver node
arm64: dts: r8a7796-salvator-x: Add MMNGR support
arm64: dts: r8a7796: Add GSX device node
arm64: dts: r8a7796: Add INTC-EX device node
arm64: dts: r8a7796: Add CAN support
arm64: dts: r8a7796: Add CAN external clock support
arm64: dts: r8a7796: Add MFIS Lock device node
arm64: dts: r8a7796: Add MFIS device node
arm64: dts: r8a7796-salvator-x: Add cpu-supply property in a57_0 node
arm64: dts: r8a7796: Adjust VDD domain voltage value for CA53
arm64: dts: r8a7796: Add OPPs table for cpu devices
arm64: dts: r8a7796: Add the emergency node in thermal-zones
arm64: dts: r8a7796: Create thermal zone to support IPA
arm64: dts: r8a7796: Add thermal sensor device nodes
...
diff --git a/Documentation/devicetree/bindings/display/renesas,du.txt b/Documentation/devicetree/bindings/display/renesas,du.txt
index 0d30e42..8515658 100644
--- a/Documentation/devicetree/bindings/display/renesas,du.txt
+++ b/Documentation/devicetree/bindings/display/renesas,du.txt
@@ -9,6 +9,7 @@
- "renesas,du-r8a7793" for R8A7793 (R-Car M2-N) compatible DU
- "renesas,du-r8a7794" for R8A7794 (R-Car E2) compatible DU
- "renesas,du-r8a7795" for R8A7795 (R-Car H3) compatible DU
+ - "renesas,du-r8a7796" for R8A7796 (R-Car M3) compatible DU
- reg: A list of base address and length of each memory resource, one for
each entry in the reg-names property.
@@ -50,6 +51,7 @@
R8A7793 (M2-N) DPAD LVDS 0 - -
R8A7794 (E2) DPAD 0 DPAD 1 - -
R8A7795 (H3) DPAD HDMI 0 HDMI 1 LVDS
+ R8A7796 (M3) DPAD HDMI LVDS -
Example: R8A7790 (R-Car H2) DU
diff --git a/Documentation/devicetree/bindings/hwlock/rcar-hwspinlock.txt b/Documentation/devicetree/bindings/hwlock/rcar-hwspinlock.txt
new file mode 100644
index 0000000..133673d
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwlock/rcar-hwspinlock.txt
@@ -0,0 +1,24 @@
+DT bindings for the Renesas R-Car Hardware spinlock driver
+----------------------------------------------------------
+
+Required properties :
+- compatible : shall contain only one of the following:
+ - "renesas,mfis-lock-r8a7795", "renesas,mfis-lock";
+ - "renesas,mfis-lock-r8a7796", "renesas,mfis-lock";
+
+- reg : start address and length for MFIS lock registers.
+
+- clocks : clock phandle and specifier pair.
+
+- power-domains : the power domain the MFIS belongs to.
+
+
+Examples:
+
+mfis_lock: mfis-lock {
+ compatible = "renesas,mfis-lock-r8a7795",
+ "renesas,mfis-lock";
+ reg = <0 0xe62600c0 0 0x0020>;
+ clocks = <&cpg CPG_MOD 213>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+};
diff --git a/Documentation/devicetree/bindings/i2c/i2c-sh_mobile.txt b/Documentation/devicetree/bindings/i2c/i2c-sh_mobile.txt
index 214f94c..1a241b5 100644
--- a/Documentation/devicetree/bindings/i2c/i2c-sh_mobile.txt
+++ b/Documentation/devicetree/bindings/i2c/i2c-sh_mobile.txt
@@ -11,6 +11,7 @@
- "renesas,iic-r8a7793" (R-Car M2-N)
- "renesas,iic-r8a7794" (R-Car E2)
- "renesas,iic-r8a7795" (R-Car H3)
+ - "renesas,iic-r8a7796" (R-Car M3)
- "renesas,iic-sh73a0" (SH-Mobile AG5)
- reg : address start and address range size of device
- interrupts : interrupt of device
diff --git a/Documentation/devicetree/bindings/media/rcar-csi2.txt b/Documentation/devicetree/bindings/media/rcar-csi2.txt
new file mode 100644
index 0000000..5d9a23f
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rcar-csi2.txt
@@ -0,0 +1,80 @@
+Renesas R-Car MIPI CSI-2 driver (rcar-csi2)
+-------------------------------------------
+
+The rcar-csi2 device provides MIPI CSI-2 capabilities for the Renesas R-Car
+family of devices. It is to be used in conjunction with the rcar-vin driver which
+provides the video input capabilities.
+
+ - compatible: Must be one or more of the following
+ - "renesas,csi2-r8a7796" for the R8A7796 device
+ - "renesas,csi2-r8a7795" for the R8A7795 device
+ - "renesas,rcar-gen3-csi2" for a generic R-Car Gen3 compatible device.
+
+ When compatible with the generic version nodes must list the
+ SoC-specific version corresponding to the platform first
+ followed by the generic version.
+
+ - reg: the register base and size for the device registers
+ - interrupts: the interrupt for the device
+ - clocks: Reference to the parent clock
+
+The device node should contain two 'port' child nodes with one child 'endpoint'
+node, according to the bindings defined in Documentation/devicetree/bindings/
+media/video-interfaces.txt. Port 0 should connect the device that is the CSI-2
+producer. Port 1 should connect the R-Car VIN device (rcar-vin) consumer.
+
+ - ports:
+ - port@0: sub-node describing a endpoint to the CSI-2 source
+ - port@1: sub-node describing a endpoint to the VIN consumer
+
+Additionally, an alias named csiX will need to be created to specify
+which CSI-2 device this is.
+
+Example:
+
+/* SoC properties */
+
+ aliases {
+ csi40 = &csi40;
+ };
+
+ csi40: csi2@feaa0000 {
+ compatible = "renesas,csi2-r8a7795";
+ reg = <0 0xfeaa0000 0 0x10000>;
+ interrupts = <0 246 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 716>;
+ power-domains = <&cpg>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ reg = <1>;
+ csi40_out: endpoint@1 {
+ remote-endpoint = <&vin0csi40>;
+ };
+ };
+ };
+ };
+
+/* Board properties */
+
+ &csi40 {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ csi40_in: endpoint@0 {
+ clock-lanes = <0>;
+ data-lanes = <1 2 3 4>;
+ remote-endpoint = <&adv7482_1_out>;
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/media/rcar_vin.txt b/Documentation/devicetree/bindings/media/rcar_vin.txt
index 6a4e61c..c57eb39 100644
--- a/Documentation/devicetree/bindings/media/rcar_vin.txt
+++ b/Documentation/devicetree/bindings/media/rcar_vin.txt
@@ -2,10 +2,16 @@
------------------------------------------
The rcar_vin device provides video input capabilities for the Renesas R-Car
-family of devices. The current blocks are always slaves and suppot one input
-channel which can be either RGB, YUYV or BT656.
+family of devices.
+
+On Gen2 the current blocks are always slaves and support one input
+channel which can be either RGB, YUYV or BT656
+
+On Gen3 the current blocks are always slaves and support multiple inputs
+channels which can be ether RGB, YUVU, BT656 or CSI-2.
- compatible: Must be one or more of the following
+ - "renesas,vin-r8a7796" for the R8A7796 device
- "renesas,vin-r8a7795" for the R8A7795 device
- "renesas,vin-r8a7794" for the R8A7794 device
- "renesas,vin-r8a7793" for the R8A7793 device
@@ -28,7 +34,30 @@
Additionally, an alias named vinX will need to be created to specify
which video input device this is.
-The per-board settings:
+On Gen3 additional ports can be specified to describe the CSI-2 group
+hierarchy. Port 0 are used to describe inputs (in per-board settings)
+and VIN group membership. Port 1 are used to describe CSI-2 endpoints.
+Port 2 are used to describe VIN endpoints which are part of the group.
+ - ports:
+ - port@0:
+ - reg 1: sub-node describing a endpoint connected to the VIN
+ group master.
+ - port@1: remote CSI-2 endpoints part of VIN group
+ - reg 0: sub-node describing a endpoint to CSI20
+ - reg 1: sub-node describing a endpoint to CSI21
+ - reg 3: sub-node describing a endpoint to CSI40
+ - reg 4: sub-node describing a endpoint to CSI41
+ - port@2: remote VIN endpoints part of VIN group
+ - reg 0: sub-node describing a endpoint to VIN0
+ - reg 1: sub-node describing a endpoint to VIN1
+ - reg 2: sub-node describing a endpoint to VIN2
+ - reg 3: sub-node describing a endpoint to VIN3
+ - reg 4: sub-node describing a endpoint to VIN4
+ - reg 5: sub-node describing a endpoint to VIN5
+ - reg 6: sub-node describing a endpoint to VIN6
+ - reg 7: sub-node describing a endpoint to VIN7
+
+The per-board settings Gen2:
- port sub-node describing a single endpoint connected to the vin
as described in video-interfaces.txt[1]. Only the first one will
be considered as each vin interface has one input port.
@@ -36,9 +65,17 @@
These settings are used to work out video input format and widths
into the system.
+The per-board settings Gen3:
+-ports:
+ - port@0:
+ - reg 0: sub-node describing a endpoint connected to the VIN
+ private digital input as described in video-interfaces.txt[1].
-Device node example
--------------------
+ These settings are used to work out video input format and widths
+ into the system.
+
+Device node example Gen2
+------------------------
aliases {
vin0 = &vin0;
@@ -52,8 +89,8 @@
status = "disabled";
};
-Board setup example (vin1 composite video input)
-------------------------------------------------
+Board setup example Gen2 (vin1 composite video input)
+-----------------------------------------------------
&i2c2 {
status = "ok";
@@ -92,6 +129,174 @@
};
};
+Device node example Gen3
+------------------------
+aliases {
+ vin0 = &vin0;
+ vin4 = &vin4;
+};
+
+csi21: csi2@fea90000 {
+ ...
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ reg = <1>;
+
+ csi21_out: endpoint@1 {
+ remote-endpoint = <&vin0csi21>;
+ };
+ };
+ };
+};
+
+vin0: video@e6ef0000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef0000 0 0x1000>;
+ interrupts = <0 188 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 811>;
+ power-domains = <&cpg>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ vin0csi: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&vin0out0>;
+ };
+
+ };
+ port@1 {
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ vin0csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21_out>;
+ };
+ };
+ port@2 {
+ reg = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ vin0out0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vin0csi>;
+ };
+ vin0out4: endpoint@4 {
+ reg = <4>;
+ remote-endpoint = <&vin4csi>;
+ };
+ };
+ };
+};
+
+vin4: video@e6ef4000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef4000 0 0x1000>;
+ interrupts = <0 174 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 807>;
+ power-domains = <&cpg>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ vin4csi: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&vin0out4>;
+ };
+ };
+ };
+};
+
+Board setup example Gen3
+- VIN0 and VIN4 part of CSI-2 group
+- VIN4 with local digital input
+-----------------------------------------------------
+
+&i2c2 {
+ ...
+
+ adv7482: composite-in@70 {
+ ...
+ port {
+ adv7482_out: endpoint@1 {
+ clock-lanes = <0>;
+ data-lanes = <1>;
+ remote-endpoint = <&csi21_in>;
+ };
+ };
+ };
+
+ adv7612: composite-in@20 {
+ ...
+ port {
+ adv7612_out: endpoint {
+ bus-width = <8>;
+ remote-endpoint = <&vin4ep0>;
+ };
+ };
+ };
+};
+
+&csi21 {
+ status = "okay";
+
+ ...
+
+ ports {
+ port@0 {
+ reg = <0>;
+
+ csi21_in: endpoint@0 {
+ clock-lanes = <0>;
+ data-lanes = <1>;
+ remote-endpoint = <&adv7482_out>;
+ };
+ };
+ };
+};
+
+&vin0 {
+ status = "okay";
+};
+
+&vin4 {
+ status = "okay";
+
+ ports {
+ port@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ vin4ep0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&adv7612_out>;
+ };
+
+ };
+ };
+};
[1] video-interfaces.txt common video media interface
diff --git a/Documentation/devicetree/bindings/pci/rcar-pci.txt b/Documentation/devicetree/bindings/pci/rcar-pci.txt
index 6cf9969..ca1698b 100644
--- a/Documentation/devicetree/bindings/pci/rcar-pci.txt
+++ b/Documentation/devicetree/bindings/pci/rcar-pci.txt
@@ -6,6 +6,7 @@
"renesas,pcie-r8a7791" for the R8A7791 SoC;
"renesas,pcie-r8a7793" for the R8A7793 SoC;
"renesas,pcie-r8a7795" for the R8A7795 SoC;
+ "renesas,pcie-r8a7796" for the R8A7796 SoC;
"renesas,pcie-rcar-gen2" for a generic R-Car Gen2 compatible device.
When compatible with the generic version, nodes must list the
diff --git a/Documentation/devicetree/bindings/serial/renesas,sci-serial.txt b/Documentation/devicetree/bindings/serial/renesas,sci-serial.txt
index 1e4000d..27acba5 100644
--- a/Documentation/devicetree/bindings/serial/renesas,sci-serial.txt
+++ b/Documentation/devicetree/bindings/serial/renesas,sci-serial.txt
@@ -31,8 +31,8 @@
- "renesas,hscif-r8a7794" for R8A7794 (R-Car E2) HSCIF compatible UART.
- "renesas,scif-r8a7795" for R8A7795 (R-Car H3) SCIF compatible UART.
- "renesas,hscif-r8a7795" for R8A7795 (R-Car H3) HSCIF compatible UART.
- - "renesas,scif-r8a7796" for R8A7796 (R-Car M3-W) SCIF compatible UART.
- - "renesas,hscif-r8a7796" for R8A7796 (R-Car M3-W) HSCIF compatible UART.
+ - "renesas,scif-r8a7796" for R8A7796 (R-Car M3) SCIF compatible UART.
+ - "renesas,hscif-r8a7796" for R8A7796 (R-Car M3) HSCIF compatible UART.
- "renesas,scifa-sh73a0" for SH73A0 (SH-Mobile AG5) SCIFA compatible UART.
- "renesas,scifb-sh73a0" for SH73A0 (SH-Mobile AG5) SCIFB compatible UART.
- "renesas,rcar-gen1-scif" for R-Car Gen1 SCIF compatible UART,
diff --git a/Documentation/devicetree/bindings/spi/sh-msiof.txt b/Documentation/devicetree/bindings/spi/sh-msiof.txt
index aa005c1..c2b6251 100644
--- a/Documentation/devicetree/bindings/spi/sh-msiof.txt
+++ b/Documentation/devicetree/bindings/spi/sh-msiof.txt
@@ -10,6 +10,8 @@
"renesas,msiof-r8a7792" (R-Car V2H)
"renesas,msiof-r8a7793" (R-Car M2-N)
"renesas,msiof-r8a7794" (R-Car E2)
+ "renesas,msiof-r8a7795" (R-Car H3)
+ "renesas,msiof-r8a7796" (R-Car M3)
"renesas,msiof-sh73a0" (SH-Mobile AG5)
- reg : A list of offsets and lengths of the register sets for
the device.
diff --git a/Documentation/devicetree/bindings/thermal/rcar-gen3-thermal.txt b/Documentation/devicetree/bindings/thermal/rcar-gen3-thermal.txt
new file mode 100644
index 0000000..9b38109
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/rcar-gen3-thermal.txt
@@ -0,0 +1,84 @@
+* Renesas R-Car Gen3 Thermal
+
+Required properties:
+- compatible : "renesas,thermal-<soctype>",
+ Examples with soctypes are:
+ - "renesas,thermal-r8a7795" (R-Car H3)
+ - "renesas,thermal-r8a7796" (R-Car M3)
+- reg : Address range of the thermal registers.
+- #thermal-sensor-cells : Please see ./thermal.txt
+
+Option properties:
+
+- interrupts : use interrupt
+
+Example (non interrupt support):
+
+thermal: thermal@0xe6198000 {
+ compatible = "renesas,thermal-r8a7795";
+ reg = <0 0xe6198000 0 0x5c>;
+ };
+
+thermal-zones {
+ cpu_thermal: cpu-thermal {
+ polling-delay-passive = <250>;
+ polling-delay = <1000>;
+
+ thermal-sensors = <&thermal>;
+ };
+};
+
+Example (interrupt support):
+
+thermal: thermal@0xe6198000 {
+ compatible = "renesas,thermal-r8a7795";
+ reg = <0 0xe6198000 0 0x5c>;
+ interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
+ <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
+ <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+thermal-zones {
+ cpu_thermal: cpu-thermal {
+ polling-delay-passive = <250>;
+ polling-delay = <0>;
+
+ thermal-sensors = <&thermal>;
+ };
+};
+
+* Emergency shutdown for R-CAR Gen3
+Emergency shutdown functionality provides the specific cooling mechanism
+for R-CAR Gen3. In case of high temperature(e.g over 100 degrees),
+it has the ability to rapidly cool down the system.
+
+Required property:
+- polling-delay: The maximum number of milliseconds to wait between polls
+ Type: unsigned when checking temperature for emergency shutdown.
+ Size: one cell
+
+- on-temperature: This value indicates the emergency temperature and invokes
+ Type: unsigned emergency shutdown functionality when exceeding this
+ Size: one cell temperature.
+
+- off-temperature: This value indicates the temperature to disable emergency
+ Type: unsigned shutdown.
+ Size: one cell
+
+- status: Should be "disabled" always.
+ Type: string
+
+- target_cpus: This property indicates which CPU will be targeted for shutdown.
+ Type: phandle
+
+thermal-zones {
+ emergency {
+ polling-delay = <1000>; /* milliseconds */
+ on-temperature = <110000>; /* millicelsius */
+ off-temperature = <95000>; /* millicelsius */
+ target_cpus = <&a57_1>,
+ <&a57_2>,
+ <&a57_3>;
+ status = "disabled";
+ };
+};
diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt
index 966885c..0b7d857 100644
--- a/Documentation/devicetree/bindings/usb/usb-xhci.txt
+++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt
@@ -11,6 +11,7 @@
- "renesas,xhci-r8a7791" for r8a7791 SoC
- "renesas,xhci-r8a7793" for r8a7793 SoC
- "renesas,xhci-r8a7795" for r8a7795 SoC
+ - "renesas,xhci-r8a7796" for r8a7796 SoC
- "renesas,rcar-gen2-xhci" for a generic R-Car Gen2 compatible device
- "renesas,rcar-gen3-xhci" for a generic R-Car Gen3 compatible device
- "xhci-platform" (deprecated)
diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index 72f4eac..d9f4673 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -144,6 +144,7 @@
select PM_GENERIC_DOMAINS
select RENESAS_IRQC
select SOC_BUS
+ select RPMSG_VIRTIO
help
This enables support for the ARMv8 based Renesas SoCs.
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index dab2cb0..81beecd 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -84,6 +84,12 @@
CONFIG_CPU_IDLE=y
CONFIG_ARM_CPUIDLE=y
CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPU_FREQ_STAT_DETAILS=y
CONFIG_CPUFREQ_DT=y
CONFIG_ARM_BIG_LITTLE_CPUFREQ=y
CONFIG_ARM_SCPI_CPUFREQ=y
@@ -238,12 +244,14 @@
CONFIG_I2C_UNIPHIER_F=y
CONFIG_I2C_RCAR=y
CONFIG_I2C_CROS_EC_TUNNEL=y
+CONFIG_I2C_SH_MOBILE=y
CONFIG_SPI=y
CONFIG_SPI_MESON_SPIFC=m
CONFIG_SPI_ORION=y
CONFIG_SPI_PL022=y
CONFIG_SPI_QUP=y
-CONFIG_SPI_SPIDEV=m
+CONFIG_SPI_SH_MSIOF=y
+CONFIG_SPI_SPIDEV=y
CONFIG_SPI_S3C64XX=y
CONFIG_SPMI=y
CONFIG_PINCTRL_SINGLE=y
@@ -260,6 +268,7 @@
CONFIG_GPIO_PCA953X=y
CONFIG_GPIO_PCA953X_IRQ=y
CONFIG_GPIO_MAX77620=y
+CONFIG_POWER_AVS=y
CONFIG_POWER_RESET_MSM=y
CONFIG_BATTERY_BQ27XXX=y
CONFIG_POWER_RESET_XGENE=y
@@ -268,10 +277,14 @@
CONFIG_SENSORS_INA2XX=m
CONFIG_SENSORS_ARM_SCPI=y
CONFIG_THERMAL=y
+CONFIG_THERMAL_OF=y
+CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR=y
CONFIG_THERMAL_EMULATION=y
CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y
CONFIG_CPU_THERMAL=y
CONFIG_EXYNOS_THERMAL=y
+CONFIG_RCAR_GEN3_THERMAL=y
+CONFIG_RCAR_THERMAL_EMS_ENABLED=y
CONFIG_WATCHDOG=y
CONFIG_RENESAS_WDT=y
CONFIG_S3C2410_WATCHDOG=y
@@ -284,6 +297,7 @@
CONFIG_REGULATOR=y
CONFIG_MFD_CROS_EC=y
CONFIG_MFD_CROS_EC_I2C=y
+CONFIG_REGULATOR_BD9571MWV=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_REGULATOR_GPIO=y
CONFIG_REGULATOR_HI655X=y
@@ -292,12 +306,28 @@
CONFIG_REGULATOR_QCOM_SMD_RPM=y
CONFIG_REGULATOR_QCOM_SPMI=y
CONFIG_REGULATOR_S2MPS11=y
-CONFIG_DRM=m
+CONFIG_MEDIA_SUPPORT=y
+CONFIG_MEDIA_CAMERA_SUPPORT=y
+CONFIG_MEDIA_CONTROLLER=y
+CONFIG_VIDEO_V4L2_SUBDEV_API=y
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_V4L_MEM2MEM_DRIVERS=y
+CONFIG_VIDEO_RENESAS_FCP=y
+CONFIG_VIDEO_RENESAS_VSP1=y
+CONFIG_DRM=y
CONFIG_DRM_NOUVEAU=m
CONFIG_DRM_TEGRA=m
CONFIG_DRM_PANEL_SIMPLE=m
CONFIG_DRM_I2C_ADV7511=m
CONFIG_DRM_HISI_KIRIN=m
+CONFIG_DRM_RCAR_DU=y
+CONFIG_DRM_RCAR_HDMI=y
+CONFIG_DRM_RCAR_LVDS=y
+CONFIG_DRM_RCAR_VSP=y
+CONFIG_VIDEO_RCAR_VIN=y
+CONFIG_VIDEO_RCAR_CSI2=y
+# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set
+CONFIG_VIDEO_ADV7482=y
CONFIG_FB=y
CONFIG_FB_ARMCLCD=y
CONFIG_BACKLIGHT_GENERIC=m
@@ -325,7 +355,7 @@
CONFIG_USB_OHCI_EXYNOS=y
CONFIG_USB_OHCI_HCD=y
CONFIG_USB_OHCI_HCD_PLATFORM=y
-CONFIG_USB_RENESAS_USBHS=m
+CONFIG_USB_RENESAS_USBHS=y
CONFIG_USB_STORAGE=y
CONFIG_USB_DWC2=y
CONFIG_USB_DWC3=y
@@ -337,7 +367,7 @@
CONFIG_USB_MSM_OTG=y
CONFIG_USB_ULPI=y
CONFIG_USB_GADGET=y
-CONFIG_USB_RENESAS_USBHS_UDC=m
+CONFIG_USB_RENESAS_USBHS_UDC=y
CONFIG_MMC=y
CONFIG_MMC_BLOCK_MINORS=32
CONFIG_MMC_ARMMMCI=y
@@ -386,6 +416,7 @@
CONFIG_XEN_GRANT_DEV_ALLOC=y
CONFIG_COMMON_CLK_SCPI=y
CONFIG_COMMON_CLK_CS2000_CP=y
+CONFIG_COMMON_CLK_5P49V5923A=y
CONFIG_COMMON_CLK_S2MPS11=y
CONFIG_CLK_QORIQ=y
CONFIG_COMMON_CLK_QCOM=y
@@ -413,6 +444,7 @@
CONFIG_ACPI=y
CONFIG_IIO=y
CONFIG_EXYNOS_ADC=y
+CONFIG_PWM_RCAR=y
CONFIG_PWM_SAMSUNG=y
CONFIG_EXT2_FS=y
CONFIG_EXT3_FS=y
diff --git a/drivers/ata/sata_rcar.c b/drivers/ata/sata_rcar.c
index f72d601..59d2dc0 100644
--- a/drivers/ata/sata_rcar.c
+++ b/drivers/ata/sata_rcar.c
@@ -970,9 +970,41 @@ static int sata_rcar_resume(struct device *dev)
struct ata_host *host = dev_get_drvdata(dev);
struct sata_rcar_priv *priv = host->private_data;
void __iomem *base = priv->base;
+ u32 val;
clk_prepare_enable(priv->clk);
+ /* Re-use from sata_rcar_init_controller() */
+ /* reset and setup phy */
+ switch (priv->type) {
+ case RCAR_GEN1_SATA:
+ sata_rcar_gen1_phy_init(priv);
+ break;
+ case RCAR_GEN2_SATA:
+ sata_rcar_gen2_phy_init(priv);
+ break;
+ default:
+ dev_warn(host->dev, "SATA phy is not initialized\n");
+ break;
+ }
+
+ /* SATA-IP reset state */
+ val = ioread32(base + ATAPI_CONTROL1_REG);
+ val |= ATAPI_CONTROL1_RESET;
+ iowrite32(val, base + ATAPI_CONTROL1_REG);
+
+ /* ISM mode, PRD mode, DTEND flag at bit 0 */
+ val = ioread32(base + ATAPI_CONTROL1_REG);
+ val |= ATAPI_CONTROL1_ISM;
+ val |= ATAPI_CONTROL1_DESE;
+ val |= ATAPI_CONTROL1_DTA32M;
+ iowrite32(val, base + ATAPI_CONTROL1_REG);
+
+ /* Release the SATA-IP from the reset state */
+ val = ioread32(base + ATAPI_CONTROL1_REG);
+ val &= ~ATAPI_CONTROL1_RESET;
+ iowrite32(val, base + ATAPI_CONTROL1_REG);
+
/* ack and mask */
iowrite32(0, base + SATAINTSTAT_REG);
iowrite32(0x7ff, base + SATAINTMASK_REG);
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 6a8ac04..d4502a8 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -183,6 +183,13 @@
Adapter driver so that any PWM output can be (mis)used as clock signal
at 50% duty cycle.
+config COMMON_CLK_5P49V5923A
+ tristate "Clock driver for 5P49V5923A programmable clock generator"
+ depends on I2C
+ help
+ If you say yes here you get support for the 5P49V5923A programmable
+ clock generator.
+
config COMMON_CLK_PXA
def_bool COMMON_CLK && ARCH_PXA
---help---
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 925081e..84fb169 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -17,6 +17,7 @@
# hardware specific clock types
# please keep this section sorted lexicographically by file path name
+obj-$(CONFIG_COMMON_CLK_5P49V5923A) += clk-5p49v5923a.o
obj-$(CONFIG_MACH_ASM9260) += clk-asm9260.o
obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o
obj-$(CONFIG_ARCH_AXXIA) += clk-axm5516.o
diff --git a/drivers/clk/clk-5p49v5923a.c b/drivers/clk/clk-5p49v5923a.c
new file mode 100644
index 0000000..0cbe670
--- /dev/null
+++ b/drivers/clk/clk-5p49v5923a.c
@@ -0,0 +1,310 @@
+/*
+ * drivers/gpu/drm/i2c/5p49v5923a.c
+ * This file is programmable clock generator driver.
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * This file is based on the drivers/clk/clk-cs2000-cp.c
+ *
+ * Copyright (C) 2015 Renesas Electronics Corporation
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.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/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/module.h>
+
+#define REF_CLK 1
+#define CLK_MAX 5
+
+#define INPUT_CLK 25000000
+
+#define C5P49_FB_INT_DIV_REG1 0x17
+#define C5P49_FB_INT_DIV_REG0 0x18
+
+/* offset address*/
+#define C5P49_DIV_FRAC_29_22 0x02
+#define C5P49_DIV_FRAC_21_14 0x03
+#define C5P49_DIV_FRAC_13_6 0x04
+#define C5P49_DIV_FRAC_5_0 0x05
+#define C5P49_DIV_INTEGER_11_4 0x0d
+#define C5P49_DIV_INTEGER_3_0 0x0e
+
+#define C5P49_CLK_OE_SHUTDOWN 0x68
+
+#define hw_to_priv(_hw) container_of(_hw, struct clk_5p49_priv, hw)
+#define priv_to_client(priv) (priv->client)
+#define priv_to_dev(priv) (&(priv_to_client(priv)->dev))
+
+struct clk_5p49_priv {
+ struct clk_hw hw;
+ struct i2c_client *client;
+ struct clk *clk_out;
+ unsigned long index;
+ unsigned long clk_rate;
+};
+
+static const struct of_device_id clk_5p49_of_match[] = {
+ { .compatible = "idt,5p49v5923a", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, clk_5p49_of_match);
+
+static const struct i2c_device_id clk_5p49_id[] = {
+ { "5p49v5923a",},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, clk_5p49_id);
+
+#define clk_5p49_read(priv, addr) \
+ i2c_smbus_read_byte_data(priv_to_client(priv), \
+ (addr + (0x10 * priv->index)))
+#define clk_5p49_write(priv, addr, val) \
+ i2c_smbus_write_byte_data(priv_to_client(priv), \
+ (addr + (0x10 * priv->index)), val)
+
+static int clk_5p49_set_rate(struct clk_hw *hw,
+ unsigned long rate, unsigned long parent_rate)
+{
+ return 0;
+}
+
+static void clk_5p49_power(struct clk_hw *hw, bool power)
+{
+ struct clk_5p49_priv *priv = hw_to_priv(hw);
+ u8 reg;
+
+ if (power) {
+ reg = i2c_smbus_read_byte_data(priv->client,
+ C5P49_CLK_OE_SHUTDOWN);
+ reg |= (0x80 >> (priv->index - 1));
+ i2c_smbus_write_byte_data(priv->client,
+ C5P49_CLK_OE_SHUTDOWN, reg);
+ } else {
+ reg = i2c_smbus_read_byte_data(priv->client,
+ C5P49_CLK_OE_SHUTDOWN);
+ reg &= ~(0x80 >> (priv->index - 1));
+ i2c_smbus_write_byte_data(priv->client,
+ C5P49_CLK_OE_SHUTDOWN, reg);
+ }
+}
+
+static int clk_5p49_enable(struct clk_hw *hw)
+{
+ clk_5p49_power(hw, true);
+
+ return 0;
+}
+
+static void clk_5p49_disable(struct clk_hw *hw)
+{
+ clk_5p49_power(hw, false);
+}
+
+static unsigned long clk_5p49_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_5p49_priv *priv = hw_to_priv(hw);
+
+ return priv->clk_rate;
+}
+
+static int clk_5p49_div_calculation(struct clk_hw *hw, unsigned long rate)
+{
+ struct clk_5p49_priv *priv = hw_to_priv(hw);
+ int integ_div, frac_div, div, vco_div, vco_clk;
+ u32 shift_1kHz = 1000;
+ u8 frac_0, frac_1, frac_2, frac_3;
+
+ vco_div = ((i2c_smbus_read_byte_data(priv->client,
+ C5P49_FB_INT_DIV_REG0) & 0xF0) >> 4)
+ + (i2c_smbus_read_byte_data(priv->client,
+ C5P49_FB_INT_DIV_REG1) << 4);
+
+ clk_5p49_power(hw, false);
+
+ vco_clk = INPUT_CLK * vco_div / shift_1kHz;
+ dev_dbg(&priv->client->dev, "vco clock:%d kHz\n", vco_clk);
+
+ vco_clk = (vco_clk / 2);
+ rate = rate / shift_1kHz;
+
+ integ_div = (vco_clk / rate);
+ div = ((vco_clk * shift_1kHz) / rate);
+ frac_div = div - (integ_div * shift_1kHz);
+
+ if (frac_div > 0x3fffffff)
+ return -EINVAL;
+
+ clk_5p49_write(priv, C5P49_DIV_INTEGER_11_4,
+ ((0x0ff0 & (u16)integ_div) >> 4));
+ clk_5p49_write(priv, C5P49_DIV_INTEGER_3_0,
+ ((0x000f & (u16)integ_div) << 4));
+
+ /* spread = 0.01% */
+ frac_div = frac_div - ((div / (100 * 100 / 1)) / 2);
+ frac_div = ((0x1000000 / shift_1kHz) * frac_div);
+ dev_dbg(&priv->client->dev,
+ "integer:0x%x, fraction:0x%x\n",
+ integ_div, frac_div);
+
+ frac_0 = (frac_div & 0x3fc00000) >> 22;
+ frac_1 = (frac_div & 0x003fc000) >> 14;
+ frac_2 = (frac_div & 0x00003fc0) >> 6;
+ frac_3 = (frac_div & 0x0000003f) << 2;
+
+ clk_5p49_write(priv, C5P49_DIV_FRAC_29_22, frac_0);
+ clk_5p49_write(priv, C5P49_DIV_FRAC_21_14, frac_1);
+ clk_5p49_write(priv, C5P49_DIV_FRAC_13_6, frac_2);
+ clk_5p49_write(priv, C5P49_DIV_FRAC_5_0, frac_3);
+
+ clk_5p49_power(hw, true);
+
+ return 0;
+}
+
+static long clk_5p49_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk_5p49_priv *priv = hw_to_priv(hw);
+ int ret;
+
+ priv->clk_rate = 0;
+
+ ret = clk_5p49_div_calculation(hw, rate);
+ if (ret < 0)
+ return ret;
+
+ priv->clk_rate = rate;
+
+ return 0;
+}
+
+static u8 clk_5p49_get_parent(struct clk_hw *hw)
+{
+ return 0;
+}
+
+static const struct clk_ops clk_5p49_ops = {
+ .get_parent = clk_5p49_get_parent,
+ .set_rate = clk_5p49_set_rate,
+ .prepare = clk_5p49_enable,
+ .unprepare = clk_5p49_disable,
+ .recalc_rate = clk_5p49_recalc_rate,
+ .round_rate = clk_5p49_round_rate,
+};
+
+static int clk_5p49_clk_register(struct clk_5p49_priv *priv,
+ struct device_node *np)
+{
+ struct clk_init_data init;
+ struct clk *clk;
+ const char *name;
+ static const char *parent_names[REF_CLK];
+ int ret = 0;
+
+ parent_names[0] = __clk_get_name(of_clk_get(np, 0));
+ name = np->name;
+
+ init.name = name;
+ init.ops = &clk_5p49_ops;
+ init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT;
+ init.parent_names = parent_names;
+ init.num_parents = ARRAY_SIZE(parent_names);
+
+ priv->hw.init = &init;
+
+ clk = clk_register(NULL, &priv->hw);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ ret = of_clk_add_provider(np, of_clk_src_simple_get, clk);
+ if (ret < 0) {
+ clk_unregister(clk);
+ return ret;
+ }
+
+ priv->clk_out = clk;
+
+ return 0;
+}
+
+static int clk_5p49_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct clk_5p49_priv *priv = NULL;
+ struct device *dev = &client->dev;
+ struct device_node *np = dev->of_node, *ch_np;
+ int ret, i, ch = 1; /* ch = 0 reserved.*/
+ u32 probe_cnt = 0;
+
+ for (i = ch; i < CLK_MAX; i++) {
+ char name[20];
+
+ sprintf(name, "5p49v5923a_clk%u", i);
+ ch_np = of_get_child_by_name(np, name);
+ if (!ch_np)
+ continue;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = client;
+ priv->index = i + 1;
+ i2c_set_clientdata(client, priv);
+
+ ret = clk_5p49_clk_register(priv, ch_np);
+ if (ret < 0)
+ return ret;
+ probe_cnt++;
+ }
+
+ if (probe_cnt == 0) {
+ dev_err(dev, "Device tree error.\n");
+ return -EINVAL;
+ }
+
+ dev_info(dev, "Rev.0x%x, probed\n",
+ i2c_smbus_read_byte_data(priv->client, 0x01));
+
+ return 0;
+}
+
+static int clk_5p49_remove(struct i2c_client *client)
+{
+ struct clk_5p49_priv *priv = i2c_get_clientdata(client);
+ struct device *dev = &client->dev;
+ struct device_node *np = dev->of_node;
+
+ of_clk_del_provider(np);
+
+ clk_unregister(priv->clk_out);
+
+ return 0;
+}
+
+static struct i2c_driver clk_5p49_driver = {
+ .driver = {
+ .name = "5p49v5923a",
+ .of_match_table = clk_5p49_of_match,
+ },
+ .probe = clk_5p49_probe,
+ .remove = clk_5p49_remove,
+ .id_table = clk_5p49_id,
+};
+
+module_i2c_driver(clk_5p49_driver);
+
+MODULE_DESCRIPTION("5p49v5923a programmable clock generator driver");
+MODULE_AUTHOR("Koji Matsuoka <koji.matsuoka.xm@renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-cs2000-cp.c b/drivers/clk/clk-cs2000-cp.c
index 021f3da..0a04b05 100644
--- a/drivers/clk/clk-cs2000-cp.c
+++ b/drivers/clk/clk-cs2000-cp.c
@@ -489,9 +489,42 @@ static int cs2000_probe(struct i2c_client *client,
return ret;
}
+#ifdef CONFIG_PM_SLEEP
+static int cs2000_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int cs2000_resume(struct device *dev)
+{
+ struct cs2000_priv *priv = dev_get_drvdata(dev);
+ int rate;
+ int ret;
+
+ /* Setting is referred from cs2000_clk_register */
+ rate = clk_get_rate(priv->ref_clk);
+ ret = __cs2000_set_rate(priv, 0, rate, rate);
+
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct dev_pm_ops cs2000_pm_ops = {
+ .suspend = cs2000_suspend,
+ .resume_early = cs2000_resume,
+};
+#define DEV_PM_OPS (&cs2000_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct i2c_driver cs2000_driver = {
.driver = {
.name = "cs2000-cp",
+ .pm = DEV_PM_OPS,
.of_match_table = cs2000_of_match,
},
.probe = cs2000_probe,
diff --git a/drivers/clk/renesas/rcar-gen3-cpg.c b/drivers/clk/renesas/rcar-gen3-cpg.c
index 2d325c7..982d76d 100644
--- a/drivers/clk/renesas/rcar-gen3-cpg.c
+++ b/drivers/clk/renesas/rcar-gen3-cpg.c
@@ -21,6 +21,7 @@
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include "renesas-cpg-mssr.h"
#include "rcar-gen3-cpg.h"
@@ -39,9 +40,154 @@ static const struct soc_device_attribute r8a7796es10[] __initconst = {
#define CPG_PLL2CR 0x002c
#define CPG_PLL4CR 0x01f4
-/** Modify for Z-clock
- * -----------------------------------------------------------------------------
- * Z Clock & Z2 Clock
+/* Implementation for customized clocks (Z-clk, Z2-clk, PLL0-clk) for CPUFreq */
+#define CPG_PLLECR 0x00D0
+#define CPG_PLLECR_PLL0ST (1 << 8)
+
+
+static const struct soc_device_attribute r8a7795es10[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.0" },
+ { /* sentinel */ }
+};
+
+/* Define for PLL0 clk driver */
+#define CPG_PLL0CR_STC_MASK 0x7f000000
+#define CPG_PLL0CR_STC_SHIFT 24
+
+#ifdef CONFIG_RCAR_Z_CLK_MAX_THRESHOLD
+#define Z_CLK_MAX_THRESHOLD CONFIG_RCAR_Z_CLK_MAX_THRESHOLD
+#else
+#define Z_CLK_MAX_THRESHOLD 1500000000
+#endif
+
+struct cpg_pll0_clk {
+ struct clk_hw hw;
+ void __iomem *reg;
+ void __iomem *pllecr_reg;
+};
+
+#define to_pll0_clk(_hw) container_of(_hw, struct cpg_pll0_clk, hw)
+
+static int cpg_pll0_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct cpg_pll0_clk *pll0_clk = to_pll0_clk(hw);
+ unsigned int stc_val;
+ u32 val;
+ int i;
+
+ /* Start clock issue W/A (for H3 WS1.0) */
+ if (soc_device_match(r8a7795es10))
+ prate *= 2; /* PLL0 output multiplied by 2 */
+ /* End clock issue W/A */
+
+ stc_val = DIV_ROUND_CLOSEST(rate, prate);
+ stc_val = clamp(stc_val, 90U, 120U);/*Lowest value is 1.5G (stc == 90)*/
+ pr_debug("%s(): prate: %lu, rate: %lu, pll0-mult: %d\n",
+ __func__, prate, rate, stc_val);
+
+ stc_val -= 1;
+ val = clk_readl(pll0_clk->reg);
+ val &= ~CPG_PLL0CR_STC_MASK;
+ val |= stc_val << CPG_PLL0CR_STC_SHIFT;
+ clk_writel(val, pll0_clk->reg);
+
+ i = 0;
+ while (!(clk_readl(pll0_clk->pllecr_reg) & CPG_PLLECR_PLL0ST)) {
+ cpu_relax();
+ i++;
+ }
+
+ if (i > 1000)
+ pr_warn("%s(): PLL0: long settled time: %d\n", __func__, i);
+
+ return 0;
+}
+
+static long cpg_pll0_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long prate = *parent_rate;
+ unsigned int mult;
+
+ if (rate < Z_CLK_MAX_THRESHOLD)
+ rate = Z_CLK_MAX_THRESHOLD; /* Set lowest value: 1.5GHz */
+
+ /* Start clock issue W/A (for H3 WS1.0) */
+ if (soc_device_match(r8a7795es10))
+ prate *= 2; /* PLL0 output multiplied by 2 */
+ /* End clock issue W/A */
+
+ mult = DIV_ROUND_CLOSEST(rate, prate);
+ mult = clamp(mult, 90U, 120U); /* 1.5G => (stc == 90)*/
+
+ rate = prate * mult;
+
+ /* Round to closest value at 100MHz unit */
+ rate = 100000000 * DIV_ROUND_CLOSEST(rate, 100000000);
+ pr_debug("%s(): output rate: %lu, parent_rate: %lu, pll0-mult: %d\n",
+ __func__, rate, prate, mult);
+ return rate;
+}
+
+static unsigned long cpg_pll0_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct cpg_pll0_clk *pll0_clk = to_pll0_clk(hw);
+ unsigned int val;
+ unsigned long rate;
+
+ val = (clk_readl(pll0_clk->reg) & CPG_PLL0CR_STC_MASK)
+ >> CPG_PLL0CR_STC_SHIFT;
+
+ rate = (u64)parent_rate * (val + 1);
+
+ /* Start clock issue W/A (for H3 WS1.0) */
+ if (soc_device_match(r8a7795es10))
+ rate *= 2; /* PLL0 output multiplied by 2 */
+ /* End clock issue W/A */
+
+ /* Round to closest value at 100MHz unit */
+ rate = 100000000 * DIV_ROUND_CLOSEST(rate, 100000000);
+ return rate;
+}
+
+static const struct clk_ops cpg_pll0_clk_ops = {
+ .recalc_rate = cpg_pll0_clk_recalc_rate,
+ .round_rate = cpg_pll0_clk_round_rate,
+ .set_rate = cpg_pll0_clk_set_rate,
+};
+
+static struct clk * __init cpg_pll0_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *cpg_base)
+{
+ struct clk_init_data init;
+ struct cpg_pll0_clk *pll0_clk;
+ struct clk *clk;
+
+ pll0_clk = kzalloc(sizeof(*pll0_clk), GFP_KERNEL);
+ if (!pll0_clk)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &cpg_pll0_clk_ops;
+ init.flags = 0;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ pll0_clk->reg = cpg_base + CPG_PLL0CR;
+ pll0_clk->pllecr_reg = cpg_base + CPG_PLLECR;
+ pll0_clk->hw.init = &init;
+
+ clk = clk_register(NULL, &pll0_clk->hw);
+ if (IS_ERR(clk))
+ kfree(pll0_clk);
+
+ return clk;
+}
+
+/* Modify for Z-clock and Z2-clock
*
* Traits of this clock:
* prepare - clk_prepare only ensures that parents are prepared
@@ -56,7 +202,7 @@ static const struct soc_device_attribute r8a7796es10[] __initconst = {
#define CPG_FRQCRC_ZFC_SHIFT 8
#define CPG_FRQCRC_Z2FC_MASK 0x1f
-
+/* Z clk driver code */
struct cpg_z_clk {
struct clk_hw hw;
void __iomem *reg;
@@ -79,24 +225,7 @@ static unsigned long cpg_z_clk_recalc_rate(struct clk_hw *hw,
rate = div_u64((u64)parent_rate * mult + 16, 32);
/* Round to closest value at 100MHz unit */
- rate = 100000000*DIV_ROUND_CLOSEST(rate, 100000000);
- return rate;
-}
-
-static unsigned long cpg_z2_clk_recalc_rate(struct clk_hw *hw,
- unsigned long parent_rate)
-{
- struct cpg_z_clk *zclk = to_z_clk(hw);
- unsigned int mult;
- unsigned int val;
- unsigned long rate;
-
- val = (clk_readl(zclk->reg) & CPG_FRQCRC_Z2FC_MASK);
- mult = 32 - val;
-
- rate = div_u64((u64)parent_rate * mult + 16, 32);
- /* Round to closest value at 100MHz unit */
- rate = 100000000*DIV_ROUND_CLOSEST(rate, 100000000);
+ rate = 100000000 * DIV_ROUND_CLOSEST(rate, 100000000);
return rate;
}
@@ -109,10 +238,25 @@ static long cpg_z_clk_round_rate(struct clk_hw *hw, unsigned long rate,
if (!prate)
prate = 1;
- mult = div_u64((u64)rate * 32 + prate/2, prate);
+ if (rate <= Z_CLK_MAX_THRESHOLD) { /* Focus on changing z-clock */
+ prate = Z_CLK_MAX_THRESHOLD; /* Set parent to: 1.5GHz */
+ mult = div_u64((u64)rate * 32 + prate/2, prate);
+ } else {
+ /* Focus on changing parent. Fix z-clock divider is 32/32 */
+ mult = 32;
+ }
+
mult = clamp(mult, 1U, 32U);
- return *parent_rate / 32 * mult;
+ /* Re-calculate the parent_rate to propagate new rate for it */
+ prate = div_u64((u64)rate * 32 + mult/2, mult);
+ prate = 100000000 * DIV_ROUND_CLOSEST(prate, 100000000);
+ rate = 100000000 * DIV_ROUND_CLOSEST(prate / 32 * mult, 100000000);
+ pr_debug("%s():z-clk mult:%d, re-calculated prate:%lu, return: %lu\n",
+ __func__, mult, prate, rate);
+ *parent_rate = prate;
+
+ return rate;
}
static int cpg_z_clk_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -123,9 +267,16 @@ static int cpg_z_clk_set_rate(struct clk_hw *hw, unsigned long rate,
u32 val, kick;
unsigned int i;
- mult = div_u64((u64)rate * 32 + parent_rate/2, parent_rate);
+ if (rate <= Z_CLK_MAX_THRESHOLD) { /* Focus on changing z-clock */
+ parent_rate = Z_CLK_MAX_THRESHOLD; /* Set parent to: 1.5GHz */
+ mult = div_u64((u64)rate * 32 + parent_rate/2, parent_rate);
+ } else {
+ mult = 32;
+ }
mult = clamp(mult, 1U, 32U);
+ pr_debug("%s(): rate: %lu, set prate to: %lu, z-clk mult: %d\n",
+ __func__, rate, parent_rate, mult);
if (clk_readl(zclk->kick_reg) & CPG_FRQCRB_KICK)
return -EBUSY;
@@ -161,25 +312,12 @@ static int cpg_z_clk_set_rate(struct clk_hw *hw, unsigned long rate,
return -ETIMEDOUT;
}
-static int cpg_z2_clk_set_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long parent_rate)
-{
- pr_info("Do not support Z2 clock changing\n");
- return 0;
-}
-
static const struct clk_ops cpg_z_clk_ops = {
.recalc_rate = cpg_z_clk_recalc_rate,
.round_rate = cpg_z_clk_round_rate,
.set_rate = cpg_z_clk_set_rate,
};
-static const struct clk_ops cpg_z2_clk_ops = {
- .recalc_rate = cpg_z2_clk_recalc_rate,
- .round_rate = cpg_z_clk_round_rate,
- .set_rate = cpg_z2_clk_set_rate,
-};
-
static struct clk * __init cpg_z_clk_register(const char *name,
const char *parent_name,
void __iomem *reg)
@@ -194,7 +332,7 @@ static struct clk * __init cpg_z_clk_register(const char *name,
init.name = name;
init.ops = &cpg_z_clk_ops;
- init.flags = 0;
+ init.flags = CLK_SET_RATE_PARENT;
init.parent_names = &parent_name;
init.num_parents = 1;
@@ -209,6 +347,49 @@ static struct clk * __init cpg_z_clk_register(const char *name,
return clk;
}
+/* Z2 clk driver code */
+static unsigned long cpg_z2_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct cpg_z_clk *zclk = to_z_clk(hw);
+ unsigned int mult;
+ unsigned int val;
+ unsigned long rate;
+
+ val = (clk_readl(zclk->reg) & CPG_FRQCRC_Z2FC_MASK);
+ mult = 32 - val;
+
+ rate = div_u64((u64)parent_rate * mult + 16, 32);
+ /* Round to closest value at 100MHz unit */
+ rate = 100000000 * DIV_ROUND_CLOSEST(rate, 100000000);
+ return rate;
+}
+
+static long cpg_z2_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long prate = *parent_rate;
+ unsigned int mult;
+
+ mult = div_u64((u64)rate * 32 + prate/2, prate);
+ mult = clamp(mult, 1U, 32U);
+
+ return prate / 32 * mult;
+}
+
+static int cpg_z2_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ pr_info("Do not support Z2 clock changing\n");
+ return 0;
+}
+
+static const struct clk_ops cpg_z2_clk_ops = {
+ .recalc_rate = cpg_z2_clk_recalc_rate,
+ .round_rate = cpg_z2_clk_round_rate,
+ .set_rate = cpg_z2_clk_set_rate,
+};
+
static struct clk * __init cpg_z2_clk_register(const char *name,
const char *parent_name,
void __iomem *reg)
@@ -237,7 +418,7 @@ static struct clk * __init cpg_z2_clk_register(const char *name,
return clk;
}
-/** End of modifying for Z-clock */
+/** End of modifying for Z-clock, Z2-clock and PLL0-clock */
/*
* SDn Clock
@@ -479,15 +660,12 @@ struct clk * __init rcar_gen3_cpg_clk_register(struct device *dev,
case CLK_TYPE_GEN3_PLL0:
/*
- * PLL0 is a configurable multiplier clock. Register it as a
- * fixed factor clock for now as there's no generic multiplier
- * clock implementation and we currently have no need to change
- * the multiplier value.
+ * The PLL0 is implemented as customized clock,
+ * it changes the multiplier when cpufreq changes between
+ * normal and override mode.
*/
- value = readl(base + CPG_PLL0CR);
- mult = (((value >> 24) & 0x7f) + 1) * 2;
- break;
-
+ return cpg_pll0_clk_register(core->name,
+ __clk_get_name(parent), base);
case CLK_TYPE_GEN3_PLL1:
mult = cpg_pll_config->pll1_mult;
break;
@@ -500,7 +678,11 @@ struct clk * __init rcar_gen3_cpg_clk_register(struct device *dev,
* the multiplier value.
*/
value = readl(base + CPG_PLL2CR);
- mult = (((value >> 24) & 0x7f) + 1) * 2;
+ mult = ((value >> 24) & 0x7f) + 1;
+ /* Start clock issue W/A (for H3 WS1.0) */
+ if (soc_device_match(r8a7795es10))
+ mult *= 2; /* PLL2 output multiplied by 2 */
+ /* End clock issue W/A */
break;
case CLK_TYPE_GEN3_PLL3:
@@ -516,6 +698,10 @@ struct clk * __init rcar_gen3_cpg_clk_register(struct device *dev,
*/
value = readl(base + CPG_PLL4CR);
mult = (((value >> 24) & 0x7f) + 1) * 2;
+ /* Start clock issue W/A (for H3 WS1.0) */
+ if (soc_device_match(r8a7795es10))
+ mult *= 2; /* PLL4 output multiplied by 2 */
+ /* End clock issue W/A */
break;
case CLK_TYPE_GEN3_SD:
@@ -581,5 +767,6 @@ int __init rcar_gen3_cpg_init(const struct rcar_gen3_cpg_pll_config *config,
{
cpg_pll_config = config;
cpg_clk_extalr = clk_extalr;
+
return 0;
}
diff --git a/drivers/clk/renesas/renesas-cpg-mssr.c b/drivers/clk/renesas/renesas-cpg-mssr.c
index 75b0d89..db75fc4 100644
--- a/drivers/clk/renesas/renesas-cpg-mssr.c
+++ b/drivers/clk/renesas/renesas-cpg-mssr.c
@@ -26,6 +26,7 @@
#include <linux/pm_clock.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
#include <dt-bindings/clock/renesas-cpg-mssr.h>
@@ -38,6 +39,59 @@
#define WARN_DEBUG(x) do { } while (0)
#endif
+#ifdef CONFIG_RCAR_DDR_BACKUP
+static struct hw_register cpg_ip_regs[] = {
+ {"CPGWPCR", 0x904, 32, 0},
+ {"CPGWPR", 0x900, 32, 0},
+ {"FRQCRB", 0x004, 32, 0},
+ {"FRQCRC", 0x0E0, 32, 0},
+ {"PLLECR", 0x0D0, 32, 0},
+ {"PLL0CR", 0x0D8, 32, 0},
+ {"PLL2CR", 0x02C, 32, 0},
+ {"PLL4CR", 0x1F4, 32, 0},
+ {"PLL0STPCR", 0x0F0, 32, 0},
+ {"PLL2STPCR", 0x0F8, 32, 0},
+ {"PLL3STPCR", 0x0FC, 32, 0},
+ {"PLL4STPCR", 0x1F8, 32, 0},
+ {"SD0CKCR", 0x074, 32, 0},
+ {"SD1CKCR", 0x078, 32, 0},
+ {"SD2CKCR", 0x268, 32, 0},
+ {"SD3CKCR", 0x26C, 32, 0},
+ {"RPCCKCR", 0x038, 32, 0},
+ {"CANFDCKCR", 0x244, 32, 0},
+ {"MSOCKCR", 0x014, 32, 0},
+ {"HDMICKCR", 0x250, 32, 0},
+ {"CSI0CKCR", 0x00C, 32, 0},
+ {"CSIREFCKCR", 0X034, 32, 0},
+ {"DVFSCR0", 0x058, 32, 0},
+ {"DVFSCR1", 0x05C, 32, 0},
+ {"FSAPBR", 0x700, 32, 0},
+ {"FSCLKCSR", 0x704, 32, 0},
+ {"FSCNTCHKH0", 0x710, 32, 0},
+ {"FSCNTCHKH1", 0x714, 32, 0},
+ {"FSCNTCHKH2", 0x718, 32, 0},
+ {"FSCNTCHKH3", 0x71C, 32, 0},
+ {"FSCNTCHKH4", 0x720, 32, 0},
+ {"FSCNTCHKH5", 0x724, 32, 0},
+ {"FSCNTCHKH6", 0x728, 32, 0},
+ {"FSCNTCHKL0", 0x730, 32, 0},
+ {"FSCNTCHKL1", 0x734, 32, 0},
+ {"FSCNTCHKL2", 0x738, 32, 0},
+ {"FSCNTCHKL3", 0x73C, 32, 0},
+ {"FSCNTCHKL4", 0x740, 32, 0},
+ {"FSCNTCHKL5", 0x744, 32, 0},
+ {"FSCNTCHKL6", 0x748, 32, 0},
+ {"FSSEQCHKCSR", 0xAF4, 32, 0},
+};
+
+static struct rcar_ip cpg_ip = {
+ .ip_name = "CPG",
+ .base_addr = 0xE6150000,
+ .size = 0xAF8,
+ .reg_count = ARRAY_SIZE(cpg_ip_regs),
+ .ip_reg = cpg_ip_regs,
+};
+#endif /* CONFIG_RCAR_DDR_BACKUP */
/*
* Module Standby and Software Reset register offets.
@@ -574,6 +628,7 @@ static int __init cpg_mssr_probe(struct platform_device *pdev)
priv->num_core_clks = info->num_total_core_clks;
priv->num_mod_clks = info->num_hw_mod_clks;
priv->last_dt_core_clk = info->last_dt_core_clk;
+ platform_set_drvdata(pdev, priv);
for (i = 0; i < nclks; i++)
clks[i] = ERR_PTR(-ENOENT);
@@ -602,9 +657,46 @@ static int __init cpg_mssr_probe(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int cpg_mssr_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct cpg_mssr_priv *priv;
+
+ pr_debug("%s\n", __func__);
+
+ priv = dev_get_drvdata(dev);
+
+ if (!cpg_ip.virt_addr)
+ cpg_ip.virt_addr = priv->base;
+
+ ret = rcar_handle_registers(&cpg_ip, DO_BACKUP);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static int cpg_mssr_resume(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ pr_debug("%s\n", __func__);
+ ret = rcar_handle_registers(&cpg_ip, DO_RESTORE);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(cpg_mssr_pm_ops,
+ cpg_mssr_suspend, cpg_mssr_resume);
+#define DEV_PM_OPS (&cpg_mssr_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver cpg_mssr_driver = {
.driver = {
.name = "renesas-cpg-mssr",
+ .pm = DEV_PM_OPS,
.of_match_table = cpg_mssr_match,
},
};
diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c
index 7126762..29f76a4 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -57,6 +57,8 @@ static const struct of_device_id machines[] __initconst = {
{ .compatible = "renesas,r8a7792", },
{ .compatible = "renesas,r8a7793", },
{ .compatible = "renesas,r8a7794", },
+ { .compatible = "renesas,r8a7795", },
+ { .compatible = "renesas,r8a7796", },
{ .compatible = "renesas,sh73a0", },
{ .compatible = "rockchip,rk2928", },
diff --git a/drivers/cpuidle/cpuidle-arm.c b/drivers/cpuidle/cpuidle-arm.c
index f440d38..ca1b3f3 100644
--- a/drivers/cpuidle/cpuidle-arm.c
+++ b/drivers/cpuidle/cpuidle-arm.c
@@ -12,6 +12,7 @@
#define pr_fmt(fmt) "CPUidle arm: " fmt
#include <linux/cpuidle.h>
+#include <linux/cpufeature.h>
#include <linux/cpumask.h>
#include <linux/cpu_pm.h>
#include <linux/kernel.h>
@@ -19,7 +20,9 @@
#include <linux/of.h>
#include <linux/slab.h>
+#include <asm/cpu.h>
#include <asm/cpuidle.h>
+#include <asm/cputype.h>
#include "dt_idle_states.h"
@@ -64,6 +67,30 @@ static struct cpuidle_driver arm_idle_driver = {
}
};
+#if IS_ENABLED(CONFIG_ARM64)
+static int __init arm_idle_driver_init(struct cpuidle_driver *drv, int part_id)
+{
+ struct cpumask *cpumask;
+ int cpu;
+
+ cpumask = kzalloc(cpumask_size(), GFP_KERNEL);
+ if (!cpumask)
+ return -ENOMEM;
+
+ for_each_possible_cpu(cpu) {
+ struct cpuinfo_arm64 *cpuinfo = &per_cpu(cpu_data, cpu);
+ u32 midr = cpuinfo->reg_midr;
+
+ if (MIDR_PARTNUM(midr) == part_id)
+ cpumask_set_cpu(cpu, cpumask);
+ }
+
+ drv->cpumask = cpumask;
+
+ return 0;
+}
+#endif
+
static const struct of_device_id arm_idle_state_match[] __initconst = {
{ .compatible = "arm,idle-state",
.data = arm_enter_idle_state },
@@ -83,6 +110,13 @@ static int __init arm_idle_init(void)
struct cpuidle_driver *drv = &arm_idle_driver;
struct cpuidle_device *dev;
+#if IS_ENABLED(CONFIG_ARM64)
+ /* Support CPUIdle for Cortex-A57 only */
+ ret = arm_idle_driver_init(drv, ARM_CPU_PART_CORTEX_A57);
+ if (ret)
+ return ret;
+#endif
+
/*
* Initialize idle states data, starting at index 1.
* This driver is DT only, if no DT idle states are detected (ret == 0)
@@ -103,7 +137,7 @@ static int __init arm_idle_init(void)
* Call arch CPU operations in order to initialize
* idle states suspend back-end specific data
*/
- for_each_possible_cpu(cpu) {
+ for_each_cpu(cpu, drv->cpumask) {
ret = arm_cpuidle_init(cpu);
/*
diff --git a/drivers/dma/sh/Kconfig b/drivers/dma/sh/Kconfig
index 6e0685f..55584f1 100644
--- a/drivers/dma/sh/Kconfig
+++ b/drivers/dma/sh/Kconfig
@@ -40,7 +40,7 @@
endif
config RCAR_DMAC
- tristate "Renesas R-Car Gen2 DMA Controller"
+ tristate "Renesas R-Car Gen2/3 DMA Controller"
depends on ARCH_RENESAS || COMPILE_TEST
select RENESAS_DMA
help
diff --git a/drivers/dma/sh/rcar-dmac.c b/drivers/dma/sh/rcar-dmac.c
index 2e441d0..e9d79d7 100644
--- a/drivers/dma/sh/rcar-dmac.c
+++ b/drivers/dma/sh/rcar-dmac.c
@@ -333,6 +333,14 @@ static bool rcar_dmac_chan_is_busy(struct rcar_dmac_chan *chan)
return !!(chcr & (RCAR_DMACHCR_DE | RCAR_DMACHCR_TE));
}
+/* Transfer completed but not yet handled */
+static bool rcar_dmac_last_tx_complete(struct rcar_dmac_chan *chan)
+{
+ u32 chcr = rcar_dmac_chan_read(chan, RCAR_DMACHCR);
+
+ return (chcr & RCAR_DMACHCR_TE) == RCAR_DMACHCR_TE;
+}
+
static void rcar_dmac_chan_start_xfer(struct rcar_dmac_chan *chan)
{
struct rcar_dmac_desc *desc = chan->desc.running;
@@ -735,14 +743,38 @@ static int rcar_dmac_fill_hwdesc(struct rcar_dmac_chan *chan,
/* -----------------------------------------------------------------------------
* Stop and reset
*/
-
-static void rcar_dmac_chan_halt(struct rcar_dmac_chan *chan)
+#define NR_READS_TO_WAIT 5 /* number of times to check if DE = 0 */
+static inline int rcar_dmac_wait_stop(struct rcar_dmac_chan *chan)
{
- u32 chcr = rcar_dmac_chan_read(chan, RCAR_DMACHCR);
+ unsigned int i = 0;
+ do {
+ u32 chcr = rcar_dmac_chan_read(chan, RCAR_DMACHCR);
+
+ if (!(chcr & RCAR_DMACHCR_DE))
+ return 0;
+ cpu_relax();
+ dev_dbg(chan->chan.device->dev, "DMA xfer couldn't be stopped");
+ } while (++i < NR_READS_TO_WAIT);
+
+ return -EBUSY;
+}
+
+/* Called with chan lock held */
+static int rcar_dmac_chan_halt(struct rcar_dmac_chan *chan)
+{
+ u32 chcr;
+ int ret;
+
+ chcr = rcar_dmac_chan_read(chan, RCAR_DMACHCR);
chcr &= ~(RCAR_DMACHCR_DSE | RCAR_DMACHCR_DSIE | RCAR_DMACHCR_IE |
RCAR_DMACHCR_TE | RCAR_DMACHCR_DE);
rcar_dmac_chan_write(chan, RCAR_DMACHCR, chcr);
+ ret = rcar_dmac_wait_stop(chan);
+
+ WARN_ON(ret < 0);
+
+ return ret;
}
static void rcar_dmac_chan_reinit(struct rcar_dmac_chan *chan)
@@ -769,6 +801,26 @@ static void rcar_dmac_chan_reinit(struct rcar_dmac_chan *chan)
}
}
+static int rcar_dmac_chan_pause(struct dma_chan *chan)
+{
+ u32 chcr;
+ int ret;
+ unsigned long flags;
+ struct rcar_dmac_chan *rchan = to_rcar_dmac_chan(chan);
+
+ spin_lock_irqsave(&rchan->lock, flags);
+
+ chcr = rcar_dmac_chan_read(rchan, RCAR_DMACHCR);
+ chcr &= ~RCAR_DMACHCR_DE;
+ rcar_dmac_chan_write(rchan, RCAR_DMACHCR, chcr);
+ ret = rcar_dmac_wait_stop(rchan);
+
+ spin_unlock_irqrestore(&rchan->lock, flags);
+
+ WARN_ON(ret < 0);
+ return ret;
+}
+
static void rcar_dmac_stop(struct rcar_dmac *dmac)
{
rcar_dmac_write(dmac, RCAR_DMAOR, 0);
@@ -1295,6 +1347,10 @@ static enum dma_status rcar_dmac_tx_status(struct dma_chan *chan,
unsigned long flags;
unsigned int residue;
+ /* Interrupt not yet serviced */
+ if (rcar_dmac_last_tx_complete(rchan))
+ return DMA_COMPLETE;
+
status = dma_cookie_status(chan, cookie, txstate);
if (status == DMA_COMPLETE || !txstate)
return status;
@@ -1582,16 +1638,33 @@ static struct dma_chan *rcar_dmac_of_xlate(struct of_phandle_args *dma_spec,
#ifdef CONFIG_PM_SLEEP
static int rcar_dmac_sleep_suspend(struct device *dev)
{
- /*
- * TODO: Wait for the current transfer to complete and stop the device.
- */
+ struct rcar_dmac *dmac = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < dmac->n_channels; ++i) {
+ if (!dmac->channels[i].iomem)
+ continue;
+
+ spin_lock(&dmac->channels[i].lock);
+ pm_runtime_get_sync(dev);
+ rcar_dmac_chan_halt(&dmac->channels[i]);
+ pm_runtime_put(dev);
+ spin_unlock(&dmac->channels[i].lock);
+ }
+
return 0;
}
static int rcar_dmac_sleep_resume(struct device *dev)
{
- /* TODO: Resume transfers, if any. */
- return 0;
+ struct rcar_dmac *dmac = dev_get_drvdata(dev);
+ int ret;
+
+ pm_runtime_get_sync(dev);
+ ret = rcar_dmac_init(dmac);
+ pm_runtime_put(dev);
+
+ return ret;
}
#endif
@@ -1819,6 +1892,7 @@ static int rcar_dmac_probe(struct platform_device *pdev)
engine->device_prep_slave_sg = rcar_dmac_prep_slave_sg;
engine->device_prep_dma_cyclic = rcar_dmac_prep_dma_cyclic;
engine->device_config = rcar_dmac_device_config;
+ engine->device_pause = rcar_dmac_chan_pause;
engine->device_terminate_all = rcar_dmac_chan_terminate_all;
engine->device_tx_status = rcar_dmac_tx_status;
engine->device_issue_pending = rcar_dmac_issue_pending;
diff --git a/drivers/dma/sh/usb-dmac.c b/drivers/dma/sh/usb-dmac.c
index 06ecdc3..52bc399 100644
--- a/drivers/dma/sh/usb-dmac.c
+++ b/drivers/dma/sh/usb-dmac.c
@@ -699,7 +699,20 @@ static int usb_dmac_runtime_resume(struct device *dev)
}
#endif /* CONFIG_PM */
+#ifdef CONFIG_PM_SLEEP
+static int usb_dmac_sleep_suspend(struct device *dev)
+{
+ return usb_dmac_runtime_suspend(dev);
+}
+
+static int usb_dmac_sleep_resume(struct device *dev)
+{
+ return usb_dmac_runtime_resume(dev);
+}
+#endif
+
static const struct dev_pm_ops usb_dmac_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(usb_dmac_sleep_suspend, usb_dmac_sleep_resume)
SET_RUNTIME_PM_OPS(usb_dmac_runtime_suspend, usb_dmac_runtime_resume,
NULL)
};
diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c
index 8263429..178f553 100644
--- a/drivers/firmware/psci.c
+++ b/drivers/firmware/psci.c
@@ -537,6 +537,13 @@ static int __init psci_probe(void)
psci_0_2_set_functions();
psci_init_migrate();
+ /*
+ * psci_init_migrate() might fail to get needed information due to
+ * incomplete firmware support.
+ * FIXME: Let's assume that Trusted OS services (e.g DDR training)
+ * always run in CPU0.
+ */
+ resident_cpu = 0;
if (PSCI_VERSION_MAJOR(ver) >= 1) {
psci_init_cpu_suspend();
diff --git a/drivers/gpio/gpio-rcar.c b/drivers/gpio/gpio-rcar.c
index 2be48f5..ece6fb6 100644
--- a/drivers/gpio/gpio-rcar.c
+++ b/drivers/gpio/gpio-rcar.c
@@ -29,6 +29,7 @@
#include <linux/pm_runtime.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
struct gpio_rcar_priv {
void __iomem *base;
@@ -57,6 +58,244 @@ struct gpio_rcar_priv {
#define RCAR_MAX_GPIO_PER_BANK 32
+#ifdef CONFIG_RCAR_DDR_BACKUP
+static struct hw_register gpio0_ip_regs[] = {
+ {"IOINTSEL", 0x0000, 32, 0},
+ {"INOUTSEL", 0x0004, 32, 0},
+ {"OUTDT", 0x0008, 32, 0},
+ {"POSNEG", 0x0020, 32, 0},
+ {"EDGLEVEL", 0x0024, 32, 0},
+ {"FILONOFF", 0x0028, 32, 0},
+ {"OUTDTSEL", 0x0040, 32, 0},
+ {"OUTDTH", 0x0044, 32, 0},
+ {"OUTDTL", 0x0048, 32, 0},
+ {"BOTHEDGE", 0x004C, 32, 0},
+ {"INTCLR", 0x0014, 32, 0},
+ {"INTMSK", 0x0018, 32, 0},
+ {"MSKCLR", 0x001C, 32, 0},
+ {"INTMSKS", 0x0038, 32, 0},
+ {"MSKCLRS", 0x003C, 32, 0},
+};
+
+static struct rcar_ip gpio0_ip = {
+ .ip_name = "GPIO0",
+ .base_addr = 0xE6050000,
+ .size = 0x50,
+ .reg_count = ARRAY_SIZE(gpio0_ip_regs),
+ .ip_reg = gpio0_ip_regs,
+};
+
+static struct hw_register gpio1_ip_regs[] = {
+ {"IOINTSEL", 0x0000, 32, 0},
+ {"INOUTSEL", 0x0004, 32, 0},
+ {"OUTDT", 0x0008, 32, 0},
+ {"POSNEG", 0x0020, 32, 0},
+ {"EDGLEVEL", 0x0024, 32, 0},
+ {"FILONOFF", 0x0028, 32, 0},
+ {"OUTDTSEL", 0x0040, 32, 0},
+ {"OUTDTH", 0x0044, 32, 0},
+ {"OUTDTL", 0x0048, 32, 0},
+ {"BOTHEDGE", 0x004C, 32, 0},
+ {"INTCLR", 0x0014, 32, 0},
+ {"INTMSK", 0x0018, 32, 0},
+ {"MSKCLR", 0x001C, 32, 0},
+ {"INTMSKS", 0x0038, 32, 0},
+ {"MSKCLRS", 0x003C, 32, 0},
+};
+
+static struct rcar_ip gpio1_ip = {
+ .ip_name = "GPIO1",
+ .base_addr = 0xE6051000,
+ .size = 0x50,
+ .reg_count = ARRAY_SIZE(gpio1_ip_regs),
+ .ip_reg = gpio1_ip_regs,
+};
+
+static struct hw_register gpio2_ip_regs[] = {
+ {"IOINTSEL", 0x0000, 32, 0},
+ {"INOUTSEL", 0x0004, 32, 0},
+ {"OUTDT", 0x0008, 32, 0},
+ {"POSNEG", 0x0020, 32, 0},
+ {"EDGLEVEL", 0x0024, 32, 0},
+ {"FILONOFF", 0x0028, 32, 0},
+ {"OUTDTSEL", 0x0040, 32, 0},
+ {"OUTDTH", 0x0044, 32, 0},
+ {"OUTDTL", 0x0048, 32, 0},
+ {"BOTHEDGE", 0x004C, 32, 0},
+ {"INTCLR", 0x0014, 32, 0},
+ {"INTMSK", 0x0018, 32, 0},
+ {"MSKCLR", 0x001C, 32, 0},
+ {"INTMSKS", 0x0038, 32, 0},
+ {"MSKCLRS", 0x003C, 32, 0},
+};
+
+static struct rcar_ip gpio2_ip = {
+ .ip_name = "GPIO2",
+ .base_addr = 0xE6052000,
+ .size = 0x50,
+ .reg_count = ARRAY_SIZE(gpio2_ip_regs),
+ .ip_reg = gpio2_ip_regs,
+};
+
+static struct hw_register gpio3_ip_regs[] = {
+ {"IOINTSEL", 0x0000, 32, 0},
+ {"INOUTSEL", 0x0004, 32, 0},
+ {"OUTDT", 0x0008, 32, 0},
+ {"POSNEG", 0x0020, 32, 0},
+ {"EDGLEVEL", 0x0024, 32, 0},
+ {"FILONOFF", 0x0028, 32, 0},
+ {"OUTDTSEL", 0x0040, 32, 0},
+ {"OUTDTH", 0x0044, 32, 0},
+ {"OUTDTL", 0x0048, 32, 0},
+ {"BOTHEDGE", 0x004C, 32, 0},
+ {"INTCLR", 0x0014, 32, 0},
+ {"INTMSK", 0x0018, 32, 0},
+ {"MSKCLR", 0x001C, 32, 0},
+ {"INTMSKS", 0x0038, 32, 0},
+ {"MSKCLRS", 0x003C, 32, 0},
+};
+
+static struct rcar_ip gpio3_ip = {
+ .ip_name = "GPIO3",
+ .base_addr = 0xE6053000,
+ .size = 0x50,
+ .reg_count = ARRAY_SIZE(gpio3_ip_regs),
+ .ip_reg = gpio3_ip_regs,
+};
+
+static struct hw_register gpio4_ip_regs[] = {
+ {"IOINTSEL", 0x0000, 32, 0},
+ {"INOUTSEL", 0x0004, 32, 0},
+ {"OUTDT", 0x0008, 32, 0},
+ {"POSNEG", 0x0020, 32, 0},
+ {"EDGLEVEL", 0x0024, 32, 0},
+ {"FILONOFF", 0x0028, 32, 0},
+ {"OUTDTSEL", 0x0040, 32, 0},
+ {"OUTDTH", 0x0044, 32, 0},
+ {"OUTDTL", 0x0048, 32, 0},
+ {"BOTHEDGE", 0x004C, 32, 0},
+ {"INTCLR", 0x0014, 32, 0},
+ {"INTMSK", 0x0018, 32, 0},
+ {"MSKCLR", 0x001C, 32, 0},
+ {"INTMSKS", 0x0038, 32, 0},
+ {"MSKCLRS", 0x003C, 32, 0},
+};
+
+static struct rcar_ip gpio4_ip = {
+ .ip_name = "GPIO4",
+ .base_addr = 0xE6054000,
+ .size = 0x50,
+ .reg_count = ARRAY_SIZE(gpio4_ip_regs),
+ .ip_reg = gpio4_ip_regs,
+};
+
+static struct hw_register gpio5_ip_regs[] = {
+ {"IOINTSEL", 0x0000, 32, 0},
+ {"INOUTSEL", 0x0004, 32, 0},
+ {"OUTDT", 0x0008, 32, 0},
+ {"POSNEG", 0x0020, 32, 0},
+ {"EDGLEVEL", 0x0024, 32, 0},
+ {"FILONOFF", 0x0028, 32, 0},
+ {"OUTDTSEL", 0x0040, 32, 0},
+ {"OUTDTH", 0x0044, 32, 0},
+ {"OUTDTL", 0x0048, 32, 0},
+ {"BOTHEDGE", 0x004C, 32, 0},
+ {"INTCLR", 0x0014, 32, 0},
+ {"INTMSK", 0x0018, 32, 0},
+ {"MSKCLR", 0x001C, 32, 0},
+ {"INTMSKS", 0x0038, 32, 0},
+ {"MSKCLRS", 0x003C, 32, 0},
+};
+
+static struct rcar_ip gpio5_ip = {
+ .ip_name = "GPIO5",
+ .base_addr = 0xE6055000,
+ .size = 0x50,
+ .reg_count = ARRAY_SIZE(gpio5_ip_regs),
+ .ip_reg = gpio5_ip_regs,
+};
+
+static struct hw_register gpio6_ip_regs[] = {
+ {"IOINTSEL", 0x0000, 32, 0},
+ {"INOUTSEL", 0x0004, 32, 0},
+ {"OUTDT", 0x0008, 32, 0},
+ {"POSNEG", 0x0020, 32, 0},
+ {"EDGLEVEL", 0x0024, 32, 0},
+ {"FILONOFF", 0x0028, 32, 0},
+ {"OUTDTSEL", 0x0040, 32, 0},
+ {"OUTDTH", 0x0044, 32, 0},
+ {"OUTDTL", 0x0048, 32, 0},
+ {"BOTHEDGE", 0x004C, 32, 0},
+ {"INTCLR", 0x0014, 32, 0},
+ {"INTMSK", 0x0018, 32, 0},
+ {"MSKCLR", 0x001C, 32, 0},
+ {"INTMSKS", 0x0038, 32, 0},
+ {"MSKCLRS", 0x003C, 32, 0},
+};
+
+static struct rcar_ip gpio6_ip = {
+ .ip_name = "GPIO6",
+ .base_addr = 0xE6055400,
+ .size = 0x50,
+ .reg_count = ARRAY_SIZE(gpio6_ip_regs),
+ .ip_reg = gpio6_ip_regs,
+};
+
+static struct hw_register gpio7_ip_regs[] = {
+ {"IOINTSEL", 0x0000, 32, 0},
+ {"INOUTSEL", 0x0004, 32, 0},
+ {"OUTDT", 0x0008, 32, 0},
+ {"POSNEG", 0x0020, 32, 0},
+ {"EDGLEVEL", 0x0024, 32, 0},
+ {"FILONOFF", 0x0028, 32, 0},
+ {"OUTDTSEL", 0x0040, 32, 0},
+ {"OUTDTH", 0x0044, 32, 0},
+ {"OUTDTL", 0x0048, 32, 0},
+ {"BOTHEDGE", 0x004C, 32, 0},
+ {"INTCLR", 0x0014, 32, 0},
+ {"INTMSK", 0x0018, 32, 0},
+ {"MSKCLR", 0x001C, 32, 0},
+ {"INTMSKS", 0x0038, 32, 0},
+ {"MSKCLRS", 0x003C, 32, 0},
+};
+
+static struct rcar_ip gpio7_ip = {
+ .ip_name = "GPIO7",
+ .base_addr = 0xE6055800,
+ .size = 0x50,
+ .reg_count = ARRAY_SIZE(gpio7_ip_regs),
+ .ip_reg = gpio7_ip_regs,
+};
+
+static struct rcar_ip *ip_info_tbl[] = {
+ &gpio0_ip,
+ &gpio1_ip,
+ &gpio2_ip,
+ &gpio3_ip,
+ &gpio4_ip,
+ &gpio5_ip,
+ &gpio6_ip,
+ &gpio7_ip,
+};
+
+static int gpio_rcar_find_index(struct device_node *nd)
+{
+ u32 addr;
+ int i;
+ int num_ip = ARRAY_SIZE(ip_info_tbl);
+
+ for (i = 0; i < num_ip; i++) {
+ of_property_read_u32_index(nd, "reg", 1, &addr);
+ if (addr == (u32)ip_info_tbl[i]->base_addr) {
+ pr_debug("base addr: 0x%08X index %d\n", addr, i);
+ break;
+ }
+ }
+
+ return ((i < num_ip) ? i : -1);
+}
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+
static inline u32 gpio_rcar_read(struct gpio_rcar_priv *p, int offs)
{
return ioread32(p->base + offs);
@@ -510,11 +749,64 @@ static int gpio_rcar_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int gpio_rcar_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct rcar_ip *gpio_ip;
+ int index = gpio_rcar_find_index(dev->of_node);
+ struct gpio_rcar_priv *p = dev_get_drvdata(dev);
+
+ pr_debug("%s\n", __func__);
+ if (index < 0) {
+ pr_err("%s: Failed to find GPIO index\n", __func__);
+ return index;
+ }
+
+ gpio_ip = ip_info_tbl[index];
+
+ if (!gpio_ip->virt_addr)
+ gpio_ip->virt_addr = p->base;
+
+ ret = rcar_handle_registers(gpio_ip, DO_BACKUP);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static int gpio_rcar_resume(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct rcar_ip *gpio_ip;
+ int index = gpio_rcar_find_index(dev->of_node);
+
+ pr_debug("%s\n", __func__);
+ if (index < 0) {
+ pr_err("%s: Failed to find GPIO index\n", __func__);
+ return index;
+ }
+
+ gpio_ip = ip_info_tbl[index];
+
+ ret = rcar_handle_registers(gpio_ip, DO_RESTORE);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(gpio_rcar_pm_ops,
+ gpio_rcar_suspend, gpio_rcar_resume);
+#define DEV_PM_OPS (&gpio_rcar_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver gpio_rcar_device_driver = {
.probe = gpio_rcar_probe,
.remove = gpio_rcar_remove,
.driver = {
.name = "gpio_rcar",
+ .pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(gpio_rcar_of_table),
}
};
diff --git a/drivers/gpu/drm/bridge/dw-hdmi.c b/drivers/gpu/drm/bridge/dw-hdmi.c
index ab7023e..f30477e 100644
--- a/drivers/gpu/drm/bridge/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/dw-hdmi.c
@@ -1,14 +1,16 @@
/*
+ * DesignWare High-Definition Multimedia Interface (HDMI) driver
+ *
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
+ * Copyright (C) 2013-2015 Mentor Graphics Inc.
* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski at gmx.de>
*
* 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.
*
- * Designware High-Definition Multimedia Interface (HDMI) driver
- *
- * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*/
#include <linux/module.h>
#include <linux/irq.h>
@@ -20,6 +22,7 @@
#include <linux/of_device.h>
#include <linux/spinlock.h>
+#include <drm/drm_atomic_helper.h>
#include <drm/drm_of.h>
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
@@ -31,6 +34,26 @@
#include "dw-hdmi.h"
#include "dw-hdmi-audio.h"
+/* HDMI_IH_I2CM_STAT0 and HDMI_IH_MUTE_I2CM_STAT0 register bits */
+#define HDMI_IH_I2CM_STAT0_ERROR BIT(0)
+#define HDMI_IH_I2CM_STAT0_DONE BIT(1)
+
+/* HDMI_I2CM_OPERATION register bits */
+#define HDMI_I2CM_OPERATION_READ BIT(0)
+#define HDMI_I2CM_OPERATION_READ_EXT BIT(1)
+#define HDMI_I2CM_OPERATION_WRITE BIT(4)
+
+/* HDMI_I2CM_INT register bits */
+#define HDMI_I2CM_INT_DONE_MASK BIT(2)
+#define HDMI_I2CM_INT_DONE_POL BIT(3)
+
+/* HDMI_I2CM_CTLINT register bits */
+#define HDMI_I2CM_CTLINT_ARB_MASK BIT(2)
+#define HDMI_I2CM_CTLINT_ARB_POL BIT(3)
+#define HDMI_I2CM_CTLINT_NAC_MASK BIT(6)
+#define HDMI_I2CM_CTLINT_NAC_POL BIT(7)
+
+
#define HDMI_EDID_LEN 512
#define RGB 0
@@ -39,6 +62,8 @@
#define YCBCR422_8BITS 3
#define XVYCC444 4
+#define TIMEOUT 200 /* time for hot plug detection */
+
enum hdmi_datamap {
RGB444_8B = 0x01,
RGB444_10B = 0x03,
@@ -101,6 +126,17 @@ struct hdmi_data_info {
struct hdmi_vmode video_mode;
};
+struct dw_hdmi_i2c {
+ struct i2c_adapter adap;
+
+ spinlock_t lock;
+ struct completion cmp;
+ u8 stat;
+
+ u8 slave_reg;
+ bool is_regaddr;
+};
+
struct dw_hdmi {
struct drm_connector connector;
struct drm_encoder *encoder;
@@ -112,6 +148,8 @@ struct dw_hdmi {
struct clk *isfr_clk;
struct clk *iahb_clk;
+ struct dw_hdmi_i2c *i2c;
+
struct hdmi_data_info hdmi_data;
const struct dw_hdmi_plat_data *plat_data;
@@ -142,6 +180,11 @@ struct dw_hdmi {
unsigned int audio_n;
bool audio_enable;
+ bool interlaced;
+ bool isfr_use;
+ bool iahb_use;
+ int num;
+
void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
u8 (*read)(struct dw_hdmi *hdmi, int offset);
};
@@ -198,6 +241,247 @@ static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg,
hdmi_modb(hdmi, data << shift, mask, reg);
}
+static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hdmi->i2c->lock, flags);
+
+ if (hdmi->dev_type == RCAR_HDMI) {
+ /* Set Standard Mode speed */
+ hdmi_writeb(hdmi, 0x03, HDMI_I2CM_DIV);
+ } else {
+ /* Set Fast Mode speed */
+ hdmi_writeb(hdmi, 0x0b, HDMI_I2CM_DIV);
+ }
+
+ /* Software reset */
+ hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ);
+
+ /* Set done, not acknowledged and arbitration interrupt polarities */
+ hdmi_writeb(hdmi, HDMI_I2CM_INT_DONE_POL, HDMI_I2CM_INT);
+ hdmi_writeb(hdmi, HDMI_I2CM_CTLINT_NAC_POL | HDMI_I2CM_CTLINT_ARB_POL,
+ HDMI_I2CM_CTLINT);
+
+ /* Clear DONE and ERROR interrupts */
+ hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+ HDMI_IH_I2CM_STAT0);
+
+ /* Mute DONE and ERROR interrupts */
+ hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+ HDMI_IH_MUTE_I2CM_STAT0);
+
+ spin_unlock_irqrestore(&hdmi->i2c->lock, flags);
+}
+
+static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
+ unsigned char *buf, int length)
+{
+ int stat;
+ unsigned long flags;
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+
+ spin_lock_irqsave(&i2c->lock, flags);
+
+ if (!i2c->is_regaddr) {
+ dev_dbg(hdmi->dev, "set read register address to 0\n");
+ i2c->slave_reg = 0x00;
+ i2c->is_regaddr = true;
+ }
+
+ while (length--) {
+ hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+ hdmi_writeb(hdmi,
+ HDMI_I2CM_OPERATION_READ, HDMI_I2CM_OPERATION);
+ i2c->stat = 0;
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ stat = wait_for_completion_interruptible_timeout(&i2c->cmp,
+ HZ / 10);
+ if (!stat)
+ return -ETIMEDOUT;
+ if (stat < 0)
+ return stat;
+
+ spin_lock_irqsave(&i2c->lock, flags);
+
+ /* Check for error condition on the bus */
+ if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR) {
+ spin_unlock_irqrestore(&i2c->lock, flags);
+ return -EIO;
+ }
+
+ *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
+ }
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ return 0;
+}
+
+static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
+ unsigned char *buf, int length)
+{
+ int stat;
+ unsigned long flags;
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+
+ spin_lock_irqsave(&i2c->lock, flags);
+
+ if (!i2c->is_regaddr) {
+ if (length) {
+ /* Use the first write byte as register address */
+ i2c->slave_reg = buf[0];
+ length--;
+ buf++;
+ } else {
+ dev_dbg(hdmi->dev, "set write register address to 0\n");
+ i2c->slave_reg = 0x00;
+ }
+ i2c->is_regaddr = true;
+ }
+
+ while (length--) {
+ hdmi_writeb(hdmi, *buf++, HDMI_I2CM_DATAO);
+ hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+ hdmi_writeb(hdmi,
+ HDMI_I2CM_OPERATION_WRITE, HDMI_I2CM_OPERATION);
+ i2c->stat = 0;
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ stat = wait_for_completion_interruptible_timeout(&i2c->cmp,
+ HZ / 10);
+ if (!stat)
+ return -ETIMEDOUT;
+ if (stat < 0)
+ return stat;
+
+ spin_lock_irqsave(&i2c->lock, flags);
+
+ /* Check for error condition on the bus */
+ if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR) {
+ spin_unlock_irqrestore(&i2c->lock, flags);
+ return -EIO;
+ }
+ }
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ return 0;
+}
+
+static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct dw_hdmi *hdmi = i2c_get_adapdata(adap);
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+
+ int i, ret;
+ u8 addr;
+ unsigned long flags;
+
+ dev_dbg(hdmi->dev, "xfer: num: %d, addr: 0x%x\n", num, msgs[0].addr);
+
+ spin_lock_irqsave(&i2c->lock, flags);
+
+ hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0);
+
+ /* Set slave device address from the first transaction */
+ addr = msgs[0].addr;
+ hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE);
+
+ /* Set slave device register address on transfer */
+ i2c->is_regaddr = false;
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ for (i = 0; i < num; i++) {
+ dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: 0x%x\n",
+ i + 1, num, msgs[i].len, msgs[i].flags);
+
+ if (msgs[i].addr != addr) {
+ dev_warn(hdmi->dev,
+ "unsupported transaction, changed slave address\n");
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ if (msgs[i].len == 0) {
+ dev_dbg(hdmi->dev,
+ "unsupported transaction %d/%d, no data\n",
+ i + 1, num);
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ if (msgs[i].flags & I2C_M_RD)
+ ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf, msgs[i].len);
+ else
+ ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf, msgs[i].len);
+
+ if (ret < 0)
+ break;
+ }
+
+ if (!ret)
+ ret = num;
+
+ spin_lock_irqsave(&i2c->lock, flags);
+
+ /* Mute DONE and ERROR interrupts */
+ hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+ HDMI_IH_MUTE_I2CM_STAT0);
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ return ret;
+}
+
+static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm dw_hdmi_algorithm = {
+ .master_xfer = dw_hdmi_i2c_xfer,
+ .functionality = dw_hdmi_i2c_func,
+};
+
+static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi)
+{
+ struct i2c_adapter *adap;
+ int ret;
+
+ hdmi->i2c = devm_kzalloc(hdmi->dev, sizeof(*hdmi->i2c), GFP_KERNEL);
+ if (!hdmi->i2c)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_init(&hdmi->i2c->lock);
+ init_completion(&hdmi->i2c->cmp);
+
+ adap = &hdmi->i2c->adap;
+ adap->class = I2C_CLASS_DDC;
+ adap->owner = THIS_MODULE;
+ adap->dev.parent = hdmi->dev;
+ adap->algo = &dw_hdmi_algorithm;
+ strlcpy(adap->name, "DesignWare HDMI", sizeof(adap->name));
+ i2c_set_adapdata(adap, hdmi);
+
+ ret = i2c_add_adapter(adap);
+ if (ret) {
+ dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
+ devm_kfree(hdmi->dev, hdmi->i2c);
+ hdmi->i2c = NULL;
+ return ERR_PTR(ret);
+ }
+
+ dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
+
+ return adap;
+}
+
static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts,
unsigned int n)
{
@@ -739,6 +1023,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
const struct dw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg;
const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr;
const struct dw_hdmi_phy_config *phy_config = pdata->phy_config;
+ const struct dw_hdmi_multi_div *multi_div = pdata->multi_div;
if (prep)
return -EINVAL;
@@ -774,6 +1059,13 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
phy_config->mpixelclock)
break;
+ if (hdmi->dev_type == RCAR_HDMI) {
+ for (; multi_div->mpixelclock != (~0UL); multi_div++)
+ if (hdmi->hdmi_data.video_mode.mpixelclock <=
+ multi_div->mpixelclock)
+ break;
+ }
+
if (mpll_config->mpixelclock == ~0UL ||
curr_ctrl->mpixelclock == ~0UL ||
phy_config->mpixelclock == ~0UL) {
@@ -808,21 +1100,27 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
hdmi_phy_test_clear(hdmi, 0);
hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].cpce, 0x06);
- hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].gmp, 0x15);
+ if (hdmi->dev_type != RCAR_HDMI)
+ hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].gmp, 0x15);
/* CURRCTRL */
hdmi_phy_i2c_write(hdmi, curr_ctrl->curr[res_idx], 0x10);
- hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */
- hdmi_phy_i2c_write(hdmi, 0x0006, 0x17);
+ if (hdmi->dev_type == RCAR_HDMI)
+ hdmi_phy_i2c_write(hdmi, multi_div->multi[res_idx], 0x11);
- hdmi_phy_i2c_write(hdmi, phy_config->term, 0x19); /* TXTERM */
- hdmi_phy_i2c_write(hdmi, phy_config->sym_ctr, 0x09); /* CKSYMTXCTRL */
- hdmi_phy_i2c_write(hdmi, phy_config->vlev_ctr, 0x0E); /* VLEVCTRL */
+ if (hdmi->dev_type != RCAR_HDMI) {
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */
+ hdmi_phy_i2c_write(hdmi, 0x0006, 0x17);
- /* REMOVE CLK TERM */
- hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */
-
+ hdmi_phy_i2c_write(hdmi, phy_config->term, 0x19); /* TXTERM */
+ hdmi_phy_i2c_write(hdmi, phy_config->sym_ctr,
+ 0x09); /* CKSYMTXCTRL */
+ hdmi_phy_i2c_write(hdmi, phy_config->vlev_ctr,
+ 0x0E); /* VLEVCTRL */
+ /* REMOVE CLK TERM */
+ hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */
+ }
dw_hdmi_phy_enable_powerdown(hdmi, false);
/* toggle TMDS enable */
@@ -833,7 +1131,8 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
dw_hdmi_phy_gen2_txpwron(hdmi, 1);
dw_hdmi_phy_gen2_pddq(hdmi, 0);
- if (hdmi->dev_type == RK3288_HDMI)
+ if ((hdmi->dev_type == RK3288_HDMI) ||
+ (hdmi->dev_type == RCAR_HDMI))
dw_hdmi_phy_enable_spare(hdmi, 1);
/*Wait for PHY PLL lock */
@@ -1400,17 +1699,60 @@ static void dw_hdmi_bridge_disable(struct drm_bridge *bridge)
hdmi->disabled = true;
dw_hdmi_update_power(hdmi);
dw_hdmi_update_phy_mask(hdmi);
+
+ if (hdmi->dev_type == RCAR_HDMI) {
+ hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
+ hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE,
+ HDMI_PHY_POL0);
+ }
mutex_unlock(&hdmi->mutex);
}
static void dw_hdmi_bridge_enable(struct drm_bridge *bridge)
{
struct dw_hdmi *hdmi = bridge->driver_private;
+ u8 intr_stat, i;
mutex_lock(&hdmi->mutex);
+
+ if (hdmi->dev_type == RCAR_HDMI) {
+ /* Reinitialization for resume */
+ initialize_hdmi_ih_mutes(hdmi);
+ hdmi_init_clk_regenerator(hdmi);
+ hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE,
+ HDMI_PHY_POL0);
+ hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD |
+ HDMI_IH_PHY_STAT0_RX_SENSE,
+ HDMI_IH_PHY_STAT0);
+ hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD |
+ HDMI_IH_PHY_STAT0_RX_SENSE),
+ HDMI_IH_MUTE_PHY_STAT0);
+ dw_hdmi_fb_registered(hdmi);
+ hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD |
+ HDMI_IH_PHY_STAT0_RX_SENSE),
+ HDMI_IH_MUTE_PHY_STAT0);
+ dw_hdmi_i2c_init(hdmi);
+ }
+
hdmi->disabled = false;
dw_hdmi_update_power(hdmi);
dw_hdmi_update_phy_mask(hdmi);
+
+ if (hdmi->dev_type == RCAR_HDMI) {
+ /* Wait for hot plug detection */
+ for (i = 0; i < TIMEOUT; i++) {
+ intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
+ if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
+ dev_dbg(hdmi->dev,
+ "HPD is occurred after %dms\n", i);
+ break;
+ }
+ mdelay(1);
+ }
+ if (i == TIMEOUT)
+ dev_warn(hdmi->dev, "HPD is not occurred\n");
+ }
+
mutex_unlock(&hdmi->mutex);
}
@@ -1512,16 +1854,47 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.mode_set = dw_hdmi_bridge_mode_set,
};
+static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi)
+{
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+ unsigned long flags;
+
+ spin_lock_irqsave(&i2c->lock, flags);
+
+ i2c->stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0);
+ if (!i2c->stat) {
+ spin_unlock_irqrestore(&i2c->lock, flags);
+ return IRQ_NONE;
+ }
+
+ hdmi_writeb(hdmi, i2c->stat, HDMI_IH_I2CM_STAT0);
+ complete(&i2c->cmp);
+
+ dev_dbg(hdmi->dev, "i2cm_stat: 0x%02x, addr: 0x%02x, reg: 0x%02x\n",
+ i2c->stat, hdmi_readb(hdmi, HDMI_I2CM_SLAVE),
+ hdmi_readb(hdmi, HDMI_I2CM_ADDRESS));
+
+ spin_unlock_irqrestore(&i2c->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
{
struct dw_hdmi *hdmi = dev_id;
u8 intr_stat;
+ irqreturn_t ret = IRQ_NONE;
+
+ if (hdmi->i2c)
+ ret = dw_hdmi_i2c_irq(hdmi);
intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
- if (intr_stat)
+ if (intr_stat) {
hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
+ return IRQ_WAKE_THREAD;
+ }
- return intr_stat ? IRQ_WAKE_THREAD : IRQ_NONE;
+ return ret;
}
static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
@@ -1618,6 +1991,9 @@ static int dw_hdmi_register(struct drm_device *drm, struct dw_hdmi *hdmi)
encoder->bridge = bridge;
hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+ if (hdmi->interlaced)
+ hdmi->connector.interlace_allowed = true;
+
drm_connector_helper_add(&hdmi->connector,
&dw_hdmi_connector_helper_funcs);
@@ -1679,6 +2055,11 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
return -EINVAL;
}
+ if (of_property_read_u32(np, "interlaced", &val) == 0)
+ hdmi->interlaced = val;
+ else
+ hdmi->interlaced = false;
+
ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
if (ddc_node) {
hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node);
@@ -1696,30 +2077,59 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
if (IS_ERR(hdmi->regs))
return PTR_ERR(hdmi->regs);
- hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr");
- if (IS_ERR(hdmi->isfr_clk)) {
- ret = PTR_ERR(hdmi->isfr_clk);
- dev_err(hdmi->dev, "Unable to get HDMI isfr clk: %d\n", ret);
- return ret;
+ if (of_property_read_u32(np, "clock-isfr", &val) == 0)
+ hdmi->isfr_use = val;
+ else
+ hdmi->isfr_use = true;
+
+ if (hdmi->isfr_use) {
+ if (of_property_read_u32(np, "hdmi-num", &val) == 0)
+ hdmi->num = val;
+ else
+ hdmi->num = -1;
+
+ if (hdmi->num > 1) {
+ char name[7];
+
+ sprintf(name, "isfr.%u", hdmi->plat_data->index);
+ hdmi->isfr_clk = devm_clk_get(hdmi->dev, name);
+ } else
+ hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr");
+ if (IS_ERR(hdmi->isfr_clk)) {
+ ret = PTR_ERR(hdmi->isfr_clk);
+ dev_err(hdmi->dev,
+ "Unable to get HDMI isfr clk: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(hdmi->isfr_clk);
+ if (ret) {
+ dev_err(hdmi->dev,
+ "Cannot enable HDMI isfr clock: %d\n", ret);
+ return ret;
+ }
}
- ret = clk_prepare_enable(hdmi->isfr_clk);
- if (ret) {
- dev_err(hdmi->dev, "Cannot enable HDMI isfr clock: %d\n", ret);
- return ret;
- }
+ if (of_property_read_u32(np, "clock-iahb", &val) == 0)
+ hdmi->iahb_use = val;
+ else
+ hdmi->iahb_use = true;
- hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb");
- if (IS_ERR(hdmi->iahb_clk)) {
- ret = PTR_ERR(hdmi->iahb_clk);
- dev_err(hdmi->dev, "Unable to get HDMI iahb clk: %d\n", ret);
- goto err_isfr;
- }
+ if (hdmi->iahb_use) {
+ hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb");
+ if (IS_ERR(hdmi->iahb_clk)) {
+ ret = PTR_ERR(hdmi->iahb_clk);
+ dev_err(hdmi->dev,
+ "Unable to get HDMI iahb clk: %d\n", ret);
+ goto err_isfr;
+ }
- ret = clk_prepare_enable(hdmi->iahb_clk);
- if (ret) {
- dev_err(hdmi->dev, "Cannot enable HDMI iahb clock: %d\n", ret);
- goto err_isfr;
+ ret = clk_prepare_enable(hdmi->iahb_clk);
+ if (ret) {
+ dev_err(hdmi->dev,
+ "Cannot enable HDMI iahb clock: %d\n", ret);
+ goto err_isfr;
+ }
}
/* Product and revision IDs */
@@ -1744,6 +2154,13 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
*/
hdmi_init_clk_regenerator(hdmi);
+ /* If DDC bus is not specified, try to register HDMI I2C bus */
+ if (!hdmi->ddc) {
+ hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
+ if (IS_ERR(hdmi->ddc))
+ hdmi->ddc = NULL;
+ }
+
/*
* Configure registers related to HDMI interrupt
* generation before registering IRQ.
@@ -1784,14 +2201,23 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
hdmi->audio = platform_device_register_full(&pdevinfo);
}
+ /* Unmute I2CM interrupts and reset HDMI DDC I2C master controller */
+ if (hdmi->i2c)
+ dw_hdmi_i2c_init(hdmi);
+
dev_set_drvdata(dev, hdmi);
return 0;
err_iahb:
- clk_disable_unprepare(hdmi->iahb_clk);
+ if (hdmi->i2c)
+ i2c_del_adapter(&hdmi->i2c->adap);
+
+ if (hdmi->iahb_use)
+ clk_disable_unprepare(hdmi->iahb_clk);
err_isfr:
- clk_disable_unprepare(hdmi->isfr_clk);
+ if (hdmi->isfr_use)
+ clk_disable_unprepare(hdmi->isfr_clk);
return ret;
}
@@ -1807,15 +2233,23 @@ void dw_hdmi_unbind(struct device *dev, struct device *master, void *data)
/* Disable all interrupts */
hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
- clk_disable_unprepare(hdmi->iahb_clk);
- clk_disable_unprepare(hdmi->isfr_clk);
- i2c_put_adapter(hdmi->ddc);
+ if (hdmi->iahb_use)
+ clk_disable_unprepare(hdmi->iahb_clk);
+
+ if (hdmi->isfr_use)
+ clk_disable_unprepare(hdmi->isfr_clk);
+
+ if (hdmi->i2c)
+ i2c_del_adapter(&hdmi->i2c->adap);
+ else
+ i2c_put_adapter(hdmi->ddc);
}
EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
+MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy at mentor.com>");
MODULE_DESCRIPTION("DW HDMI transmitter driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:dw-hdmi");
diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig
index 4c2fd05..1249314 100644
--- a/drivers/gpu/drm/rcar-du/Kconfig
+++ b/drivers/gpu/drm/rcar-du/Kconfig
@@ -14,12 +14,15 @@
config DRM_RCAR_HDMI
bool "R-Car DU HDMI Encoder Support"
depends on DRM_RCAR_DU
+ depends on OF
+ select DRM_DW_HDMI
help
Enable support for external HDMI encoders.
config DRM_RCAR_LVDS
bool "R-Car DU LVDS Encoder Support"
depends on DRM_RCAR_DU
+ select GPIOLIB
help
Enable support for the R-Car Display Unit embedded LVDS encoders.
diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
index d3b4465..4666821 100644
--- a/drivers/gpu/drm/rcar-du/Makefile
+++ b/drivers/gpu/drm/rcar-du/Makefile
@@ -3,13 +3,13 @@
rcar_du_encoder.o \
rcar_du_group.o \
rcar_du_kms.o \
- rcar_du_lvdscon.o \
rcar_du_plane.o \
rcar_du_vgacon.o
rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI) += rcar_du_hdmienc.o
-rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o
+rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o \
+ rcar_du_lvdscon.o
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index 7316fc7..428e078 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -1,7 +1,7 @@
/*
* rcar_du_crtc.c -- R-Car Display Unit CRTCs
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -13,6 +13,7 @@
#include <linux/clk.h>
#include <linux/mutex.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
@@ -69,7 +70,7 @@ static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 reg,
rcar_du_write(rcdu, rcrtc->mmio_offset + reg, (value & ~clr) | set);
}
-static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
+int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
{
int ret;
@@ -94,7 +95,7 @@ static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
return ret;
}
-static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
+void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
{
rcar_du_group_put(rcrtc->group);
@@ -102,18 +103,92 @@ static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
clk_disable_unprepare(rcrtc->clock);
}
+void rcar_du_crtc_vbk_check(struct rcar_du_crtc *rcrtc)
+{
+ u32 i, timeout = 100, loop = 2;
+ u32 status;
+
+ /* Check next VBK flag */
+ for (i = 0; i < timeout; i++) {
+ status = rcar_du_crtc_read(rcrtc, DSSR);
+ if (status & DSSR_VBK) {
+
+ rcar_du_crtc_write(rcrtc, DSRCR, DSRCR_VBCL);
+ if (--loop == 0)
+ break;
+ }
+ mdelay(1);
+ }
+}
+
/* -----------------------------------------------------------------------------
* Hardware Setup
*/
+static void rcar_du_dpll_divider(struct dpll_info *dpll, unsigned int extclk,
+ unsigned int mode_clock)
+{
+ unsigned long dpllclk;
+ unsigned long diff;
+ unsigned long n, m, fdpll;
+ bool match_flag = false;
+ bool clk_diff_set = true;
+
+ for (n = 39; n < 120; n++) {
+ for (m = 0; m < 4; m++) {
+ for (fdpll = 1; fdpll < 32; fdpll++) {
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0))
+ /* 1/2 (FRQSEL=1) for duty rate 50% */
+ dpllclk = extclk * (n + 1) / (m + 1)
+ / (fdpll + 1) / 2;
+ else
+ dpllclk = extclk * (n + 1) / (m + 1)
+ / (fdpll + 1);
+ if (dpllclk >= 400000000)
+ continue;
+
+ diff = abs((long)dpllclk - (long)mode_clock);
+ if (clk_diff_set ||
+ ((diff == 0) || (dpll->diff > diff))) {
+ dpll->diff = diff;
+ dpll->n = n;
+ dpll->m = m;
+ dpll->fdpll = fdpll;
+ dpll->dpllclk = dpllclk;
+
+ if (clk_diff_set)
+ clk_diff_set = false;
+
+ if (diff == 0) {
+ match_flag = true;
+ break;
+ }
+ }
+ }
+ if (match_flag)
+ break;
+ }
+ if (match_flag)
+ break;
+ }
+}
+
static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
{
const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode;
+ struct rcar_du_device *rcdu = rcrtc->group->dev;
unsigned long mode_clock = mode->clock * 1000;
unsigned long clk;
u32 value;
u32 escr;
u32 div;
+ u32 dpll_reg = 0;
+ struct dpll_info *dpll;
+
+ dpll = kzalloc(sizeof(*dpll), GFP_KERNEL);
+ if (dpll == NULL)
+ return;
/* Compute the clock divisor and select the internal or external dot
* clock based on the requested frequency.
@@ -129,7 +204,17 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
unsigned long rate;
u32 extdiv;
+ clk_set_rate(rcrtc->extclock, mode_clock);
extclk = clk_get_rate(rcrtc->extclock);
+
+ if (rcdu->info->dpll_ch & (0x01 << rcrtc->index)) {
+ rcar_du_dpll_divider(dpll, extclk, mode_clock);
+ extclk = dpll->dpllclk;
+ dev_dbg(rcrtc->group->dev->dev,
+ "dpllclk:%d, fdpll:%d, n:%d, m:%d, diff:%d\n",
+ dpll->dpllclk, dpll->fdpll, dpll->n, dpll->m,
+ dpll->diff);
+ }
extdiv = DIV_ROUND_CLOSEST(extclk, mode_clock);
extdiv = clamp(extdiv, 1U, 64U) - 1;
@@ -140,17 +225,44 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
abs((long)rate - (long)mode_clock)) {
dev_dbg(rcrtc->group->dev->dev,
"crtc%u: using external clock\n", rcrtc->index);
- escr = extdiv | ESCR_DCLKSEL_DCLKIN;
+ if (rcdu->info->dpll_ch & (0x01 << rcrtc->index)) {
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0))
+ escr = ESCR_DCLKSEL_DCLKIN | 0x01;
+ else
+ escr = ESCR_DCLKSEL_DCLKIN;
+ dpll_reg = DPLLCR_CODE | DPLLCR_M(dpll->m) |
+ DPLLCR_FDPLL(dpll->fdpll) |
+ DPLLCR_CLKE | DPLLCR_N(dpll->n) |
+ DPLLCR_STBY;
+
+ if (rcrtc->index == DU_CH_1)
+ dpll_reg |= (DPLLCR_PLCS1 |
+ DPLLCR_INCS_DPLL01_DOTCLKIN13);
+ if (rcrtc->index == DU_CH_2) {
+ dpll_reg |= (DPLLCR_PLCS0 |
+ DPLLCR_INCS_DPLL01_DOTCLKIN02);
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0))
+ dpll_reg |= (0x01 << 21);
+ }
+ rcar_du_group_write(rcrtc->group, DPLLCR,
+ dpll_reg);
+ } else
+ escr = extdiv | ESCR_DCLKSEL_DCLKIN;
}
}
+ kfree(dpll);
+
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? ESCR2 : ESCR,
escr);
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? OTAR2 : OTAR, 0);
/* Signal polarities */
- value = ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : DSMR_VSL)
- | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : DSMR_HSL)
+ value = ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? DSMR_VSL : 0)
+ | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? DSMR_HSL : 0)
+ | ((mode->flags & DRM_MODE_FLAG_INTERLACE) ? DSMR_ODEV : 0)
| DSMR_DIPM_DISP | DSMR_CSPM;
rcar_du_crtc_write(rcrtc, DSMR, value);
@@ -172,7 +284,7 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
mode->crtc_vsync_start - 1);
rcar_du_crtc_write(rcrtc, VCR, mode->crtc_vtotal - 1);
- rcar_du_crtc_write(rcrtc, DESR, mode->htotal - mode->hsync_start);
+ rcar_du_crtc_write(rcrtc, DESR, mode->htotal - mode->hsync_start - 1);
rcar_du_crtc_write(rcrtc, DEWR, mode->hdisplay);
}
@@ -282,12 +394,12 @@ static void rcar_du_crtc_update_planes(struct rcar_du_crtc *rcrtc)
rcrtc->group->dptsr_planes = dptsr_planes;
if (rcrtc->group->used_crtcs)
- rcar_du_group_restart(rcrtc->group);
+ rcar_du_group_restart(rcrtc->group, rcrtc);
}
/* Restart the group if plane sources have changed. */
if (rcrtc->group->need_restart)
- rcar_du_group_restart(rcrtc->group);
+ rcar_du_group_restart(rcrtc->group, rcrtc);
mutex_unlock(&rcrtc->group->lock);
@@ -380,7 +492,7 @@ static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
(interlaced ? DSYSR_SCM_INT_VIDEO : 0) |
DSYSR_TVM_MASTER);
- rcar_du_group_start_stop(rcrtc->group, true);
+ rcar_du_group_start_stop(rcrtc->group, true, rcrtc);
/* Enable the VSP compositor. */
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
@@ -428,17 +540,32 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
*/
rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH);
- rcar_du_group_start_stop(rcrtc->group, false);
+ rcar_du_group_start_stop(rcrtc->group, false, rcrtc);
+
+ rcrtc->started = false;
+}
+
+void rcar_du_crtc_remove_suspend(struct rcar_du_crtc *rcrtc)
+{
+ if (!rcrtc->started)
+ return;
+
+ rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0);
+
+ rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH);
+
+ rcar_du_group_start_stop(rcrtc->group, false, rcrtc);
+
+ rcar_du_crtc_put(rcrtc);
rcrtc->started = false;
}
void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc)
{
- if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
- rcar_du_vsp_disable(rcrtc);
-
+ rcrtc->suspend = true;
rcar_du_crtc_stop(rcrtc);
+ rcrtc->suspend = false;
rcar_du_crtc_put(rcrtc);
}
@@ -453,9 +580,7 @@ void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc)
rcar_du_crtc_start(rcrtc);
/* Commit the planes state. */
- if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE)) {
- rcar_du_vsp_enable(rcrtc);
- } else {
+ if (!rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE)) {
for (i = 0; i < rcrtc->group->num_planes; ++i) {
struct rcar_du_plane *plane = &rcrtc->group->planes[i];
@@ -582,6 +707,18 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
int irq;
int ret;
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSPDL_SOURCE)) {
+ if (index == rcdu->info->vspdl_pair_ch)
+ rcrtc->lif_index = 1;
+ else
+ rcrtc->lif_index = 0;
+
+ if ((rcrtc->lif_index == 1) && (rcrtc->vsp->num_brs == 0))
+ return 0;
+ } else {
+ rcrtc->lif_index = 0;
+ }
+
/* Get the CRTC clock and the optional external clock. */
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_IRQ_CLOCK)) {
sprintf(clk_name, "du.%u", index);
@@ -610,10 +747,19 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
rcrtc->group = rgrp;
rcrtc->mmio_offset = mmio_offsets[index];
rcrtc->index = index;
+ rcrtc->lvds_ch = -1;
+ rcrtc->suspend = false;
- if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE))
- primary = &rcrtc->vsp->planes[0].plane;
- else
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) {
+ unsigned int primary_num;
+
+ primary_num = rcrtc->vsp->num_planes - rcrtc->vsp->num_brs;
+
+ if (rcrtc->lif_index == 1)
+ primary = &rcrtc->vsp->planes[primary_num].plane;
+ else
+ primary = &rcrtc->vsp->planes[0].plane;
+ } else
primary = &rgrp->planes[index % 2].plane;
ret = drm_crtc_init_with_planes(rcdu->ddev, crtc, primary,
@@ -648,6 +794,12 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
return ret;
}
+ ret = RCAR_PRR_INIT();
+ if (ret) {
+ dev_dbg(rcdu->dev, "product register init fail.\n");
+ return ret;
+ }
+
return 0;
}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
index 6f08b7e..fdd9f46 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
@@ -1,7 +1,7 @@
/*
* rcar_du_crtc.h -- R-Car Display Unit CRTCs
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -52,6 +52,18 @@ struct rcar_du_crtc {
struct rcar_du_group *group;
struct rcar_du_vsp *vsp;
+
+ int lvds_ch;
+ bool suspend;
+ unsigned int lif_index;
+};
+
+struct dpll_info {
+ unsigned int dpllclk;
+ unsigned int diff;
+ unsigned int fdpll;
+ unsigned int n;
+ unsigned int m;
};
#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc)
@@ -61,6 +73,8 @@ enum rcar_du_output {
RCAR_DU_OUTPUT_DPAD1,
RCAR_DU_OUTPUT_LVDS0,
RCAR_DU_OUTPUT_LVDS1,
+ RCAR_DU_OUTPUT_HDMI0,
+ RCAR_DU_OUTPUT_HDMI1,
RCAR_DU_OUTPUT_TCON,
RCAR_DU_OUTPUT_MAX,
};
@@ -68,9 +82,13 @@ enum rcar_du_output {
int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index);
void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable);
void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc);
+void rcar_du_crtc_remove_suspend(struct rcar_du_crtc *rcrtc);
void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc);
void rcar_du_crtc_route_output(struct drm_crtc *crtc,
enum rcar_du_output output);
+void rcar_du_crtc_vbk_check(struct rcar_du_crtc *rcrtc);
+int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc);
+void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc);
#endif /* __RCAR_DU_CRTC_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
index 73c971e..13d2ccf 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
@@ -1,7 +1,7 @@
/*
* rcar_du_drv.c -- R-Car Display Unit DRM driver
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -20,16 +20,27 @@
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/wait.h>
+#include <linux/sys_soc.h>
#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
+#include <drm/rcar_du_drm.h>
+
+#include <media/vsp1.h>
#include "rcar_du_crtc.h"
+#include "rcar_du_encoder.h"
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_regs.h"
+#include "rcar_du_vsp.h"
+#include "rcar_du_hdmienc.h"
+#include "rcar_du_lvdsenc.h"
/* -----------------------------------------------------------------------------
* Device Information
@@ -108,6 +119,7 @@ static const struct rcar_du_device_info rcar_du_r8a7791_info = {
},
},
.num_lvds = 1,
+ .vsp_num = 4,
};
static const struct rcar_du_device_info rcar_du_r8a7794_info = {
@@ -133,21 +145,32 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
.num_lvds = 0,
};
-static const struct rcar_du_device_info rcar_du_r8a7795_info = {
+static const struct rcar_du_device_info rcar_du_r8a7795_es1x_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS
- | RCAR_DU_FEATURE_VSP1_SOURCE,
+ | RCAR_DU_FEATURE_VSP1_SOURCE
+ | RCAR_DU_FEATURE_DIDSR2_REG,
.num_crtcs = 4,
.routes = {
- /* R8A7795 has one RGB output, one LVDS output and two
- * (currently unsupported) HDMI outputs.
+ /* R8A7795 has one RGB output, two HDMI outputs and one
+ * LVDS output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(3),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 0,
},
+ [RCAR_DU_OUTPUT_HDMI0] = {
+ .possible_crtcs = BIT(1),
+ .encoder_type = RCAR_DU_ENCODER_HDMI,
+ .port = 1,
+ },
+ [RCAR_DU_OUTPUT_HDMI1] = {
+ .possible_crtcs = BIT(2),
+ .encoder_type = RCAR_DU_ENCODER_HDMI,
+ .port = 2,
+ },
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_LVDS,
@@ -155,6 +178,90 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
},
},
.num_lvds = 1,
+ .dpll_ch = BIT(1) | BIT(2),
+ .vsp_num = 5,
+};
+
+static const struct rcar_du_device_info rcar_du_r8a7795_info = {
+ .gen = 3,
+ .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
+ | RCAR_DU_FEATURE_EXT_CTRL_REGS
+ | RCAR_DU_FEATURE_VSP1_SOURCE
+ | RCAR_DU_FEATURE_DIDSR2_REG
+ | RCAR_DU_FEATURE_VSPDL_SOURCE,
+ .num_crtcs = 4,
+ .routes = {
+ /* R8A7795 has one RGB output, two HDMI outputs and one
+ * LVDS output.
+ */
+ [RCAR_DU_OUTPUT_DPAD0] = {
+ .possible_crtcs = BIT(3),
+ .encoder_type = DRM_MODE_ENCODER_NONE,
+ .port = 0,
+ },
+ [RCAR_DU_OUTPUT_HDMI0] = {
+ .possible_crtcs = BIT(1),
+ .encoder_type = RCAR_DU_ENCODER_HDMI,
+ .port = 1,
+ },
+ [RCAR_DU_OUTPUT_HDMI1] = {
+ .possible_crtcs = BIT(2),
+ .encoder_type = RCAR_DU_ENCODER_HDMI,
+ .port = 2,
+ },
+ [RCAR_DU_OUTPUT_LVDS0] = {
+ .possible_crtcs = BIT(0),
+ .encoder_type = DRM_MODE_ENCODER_LVDS,
+ .port = 3,
+ },
+ },
+ .num_lvds = 1,
+ .dpll_ch = BIT(1) | BIT(2),
+ .vsp_num = 5,
+ .vspdl_pair_ch = DU_CH_3,
+};
+
+static const struct rcar_du_device_info rcar_du_r8a7796_info = {
+ .gen = 3,
+ .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
+ | RCAR_DU_FEATURE_EXT_CTRL_REGS
+ | RCAR_DU_FEATURE_VSP1_SOURCE,
+ .num_crtcs = 3,
+ .routes = {
+ /* R8A7796 has one RGB output, one LVDS output and one
+ * HDMI outputs.
+ */
+ [RCAR_DU_OUTPUT_DPAD0] = {
+ .possible_crtcs = BIT(2),
+ .encoder_type = DRM_MODE_ENCODER_NONE,
+ .port = 0,
+ },
+ [RCAR_DU_OUTPUT_HDMI0] = {
+ .possible_crtcs = BIT(1),
+ .encoder_type = RCAR_DU_ENCODER_HDMI,
+ .port = 1,
+ },
+ [RCAR_DU_OUTPUT_LVDS0] = {
+ .possible_crtcs = BIT(0),
+ .encoder_type = DRM_MODE_ENCODER_LVDS,
+ .port = 2,
+ },
+ },
+ .num_lvds = 1,
+ .dpll_ch = BIT(1),
+ .vsp_num = 5,
+};
+
+/* H3 WS1.0 */
+static const struct soc_device_attribute r8a7795es10[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.0" },
+ { },
+};
+
+/* H3 WS1.1 */
+static const struct soc_device_attribute r8a7795es11[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.1" },
+ { },
};
static const struct of_device_id rcar_du_of_table[] = {
@@ -164,6 +271,7 @@ static const struct of_device_id rcar_du_of_table[] = {
{ .compatible = "renesas,du-r8a7793", .data = &rcar_du_r8a7791_info },
{ .compatible = "renesas,du-r8a7794", .data = &rcar_du_r8a7794_info },
{ .compatible = "renesas,du-r8a7795", .data = &rcar_du_r8a7795_info },
+ { .compatible = "renesas,du-r8a7796", .data = &rcar_du_r8a7796_info },
{ }
};
@@ -177,7 +285,8 @@ static void rcar_du_lastclose(struct drm_device *dev)
{
struct rcar_du_device *rcdu = dev->dev_private;
- drm_fbdev_cma_restore_mode(rcdu->fbdev);
+ if (dev->irq_enabled)
+ drm_fbdev_cma_restore_mode(rcdu->fbdev);
}
static int rcar_du_enable_vblank(struct drm_device *dev, unsigned int pipe)
@@ -196,6 +305,13 @@ static void rcar_du_disable_vblank(struct drm_device *dev, unsigned int pipe)
rcar_du_crtc_enable_vblank(&rcdu->crtcs[pipe], false);
}
+static const struct drm_ioctl_desc rcar_du_ioctls[] = {
+ DRM_IOCTL_DEF_DRV(DRM_RCAR_DU_SET_VMUTE, rcar_du_set_vmute,
+ DRM_UNLOCKED | DRM_CONTROL_ALLOW),
+ DRM_IOCTL_DEF_DRV(DRM_RCAR_DU_SCRSHOT, rcar_du_vsp_write_back,
+ DRM_UNLOCKED | DRM_CONTROL_ALLOW),
+};
+
static const struct file_operations rcar_du_fops = {
.owner = THIS_MODULE,
.open = drm_open,
@@ -237,30 +353,85 @@ static struct drm_driver rcar_du_driver = {
.date = "20130110",
.major = 1,
.minor = 0,
+ .ioctls = rcar_du_ioctls,
+ .num_ioctls = ARRAY_SIZE(rcar_du_ioctls),
};
/* -----------------------------------------------------------------------------
* Power management
*/
-
#ifdef CONFIG_PM_SLEEP
static int rcar_du_pm_suspend(struct device *dev)
{
struct rcar_du_device *rcdu = dev_get_drvdata(dev);
-
+ int i;
+#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI)
+ struct drm_encoder *encoder;
+#endif
drm_kms_helper_poll_disable(rcdu->ddev);
- /* TODO Suspend the CRTC */
+#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI)
+ list_for_each_entry(encoder,
+ &rcdu->ddev->mode_config.encoder_list, head) {
+ if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS)
+ rcar_du_hdmienc_suspend(encoder);
+ }
+#endif
+#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
+ for (i = 0; i < rcdu->info->num_lvds; ++i) {
+ if (rcdu->lvds[i])
+ rcar_du_lvdsenc_stop_suspend(rcdu->lvds[i]);
+ }
+#endif
+ for (i = 0; i < rcdu->num_crtcs; ++i) {
+ if (rcdu->crtcs[i].started) {
+ rcar_du_crtc_suspend(&rcdu->crtcs[i]);
+ clk_set_rate(rcdu->crtcs[i].extclock, 0);
+ rcdu->crtcs[i].group->used_crtcs = 0;
+ }
+ }
return 0;
}
static int rcar_du_pm_resume(struct device *dev)
{
struct rcar_du_device *rcdu = dev_get_drvdata(dev);
+ struct drm_encoder *encoder;
+ int i;
- /* TODO Resume the CRTC */
+ encoder = NULL;
+ for (i = 0; i < rcdu->num_crtcs; ++i)
+ rcar_du_crtc_resume(&rcdu->crtcs[i]);
+
+#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI)
+ list_for_each_entry(encoder,
+ &rcdu->ddev->mode_config.encoder_list, head) {
+ if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS)
+ rcar_du_hdmienc_resume(encoder);
+ }
+#endif
+#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
+ for (i = 0; i < rcdu->num_crtcs; ++i) {
+ if (rcdu->crtcs[i].lvds_ch >= 0)
+ rcar_du_lvdsenc_start(
+ rcdu->lvds[rcdu->crtcs[i].lvds_ch],
+ &rcdu->crtcs[i]);
+ }
+#endif
+
+ for (i = 0; i < rcdu->num_crtcs; ++i) {
+ struct drm_crtc *crtc = &rcdu->crtcs[i].crtc;
+
+ if (rcdu->crtcs[i].started) {
+ rcar_du_group_restart(rcdu->crtcs[i].group,
+ &rcdu->crtcs[i]);
+ rcar_du_async_commit(rcdu->ddev, crtc);
+ }
+ }
drm_kms_helper_poll_enable(rcdu->ddev);
+ drm_fbdev_cma_hotplug_event(rcdu->fbdev);
+
return 0;
}
#endif
@@ -272,20 +443,53 @@ static const struct dev_pm_ops rcar_du_pm_ops = {
/* -----------------------------------------------------------------------------
* Platform driver
*/
+static void rcar_du_remove_suspend(struct rcar_du_device *rcdu)
+{
+ int i;
+#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI)
+ struct drm_encoder *encoder;
+
+ list_for_each_entry(encoder,
+ &rcdu->ddev->mode_config.encoder_list, head) {
+ if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS)
+ rcar_du_hdmienc_disable(encoder);
+ }
+#endif
+
+#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
+ for (i = 0; i < rcdu->info->num_lvds; ++i) {
+ if (rcdu->lvds[i])
+ rcar_du_lvdsenc_stop_suspend(rcdu->lvds[i]);
+ }
+#endif
+ for (i = 0; i < rcdu->num_crtcs; ++i) {
+ if (rcdu->crtcs[i].started)
+ rcar_du_crtc_remove_suspend(&rcdu->crtcs[i]);
+ }
+}
static int rcar_du_remove(struct platform_device *pdev)
{
struct rcar_du_device *rcdu = platform_get_drvdata(pdev);
struct drm_device *ddev = rcdu->ddev;
+ int i;
+
+ ddev->irq_enabled = 0;
+
+ for (i = 0; i < rcdu->num_crtcs; ++i) {
+ if (rcdu->crtcs[i].started)
+ drm_crtc_vblank_off(&rcdu->crtcs[i].crtc);
+ }
drm_dev_unregister(ddev);
+ rcar_du_remove_suspend(rcdu);
+
if (rcdu->fbdev)
drm_fbdev_cma_fini(rcdu->fbdev);
drm_kms_helper_poll_fini(ddev);
drm_mode_config_cleanup(ddev);
- drm_vblank_cleanup(ddev);
drm_dev_unref(ddev);
@@ -315,6 +519,9 @@ static int rcar_du_probe(struct platform_device *pdev)
rcdu->dev = &pdev->dev;
rcdu->info = of_match_device(rcar_du_of_table, rcdu->dev)->data;
+ if (soc_device_match(r8a7795es10) || soc_device_match(r8a7795es11))
+ rcdu->info = &rcar_du_r8a7795_es1x_info;
+
ddev = drm_dev_alloc(&rcar_du_driver, &pdev->dev);
if (IS_ERR(ddev))
return PTR_ERR(ddev);
@@ -322,8 +529,6 @@ static int rcar_du_probe(struct platform_device *pdev)
rcdu->ddev = ddev;
ddev->dev_private = rcdu;
- platform_set_drvdata(pdev, rcdu);
-
/* I/O resources */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
rcdu->mmio = devm_ioremap_resource(&pdev->dev, mem);
@@ -342,6 +547,7 @@ static int rcar_du_probe(struct platform_device *pdev)
/* DRM/KMS objects */
ret = rcar_du_modeset_init(rcdu);
if (ret < 0) {
+ platform_set_drvdata(pdev, rcdu);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev,
"failed to initialize DRM/KMS (%d)\n", ret);
@@ -357,6 +563,8 @@ static int rcar_du_probe(struct platform_device *pdev)
if (ret)
goto error;
+ platform_set_drvdata(pdev, rcdu);
+
DRM_INFO("Device %s probed\n", dev_name(&pdev->dev));
return 0;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
index c843c31..9108f4b 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
@@ -1,7 +1,7 @@
/*
* rcar_du_drv.h -- R-Car Display Unit DRM driver
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -31,6 +31,8 @@ struct rcar_du_lvdsenc;
#define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK (1 << 0) /* Per-CRTC IRQ and clock */
#define RCAR_DU_FEATURE_EXT_CTRL_REGS (1 << 1) /* Has extended control registers */
#define RCAR_DU_FEATURE_VSP1_SOURCE (1 << 2) /* Has inputs from VSP1 */
+#define RCAR_DU_FEATURE_DIDSR2_REG (1 << 3) /* Has DIDSR2 register */
+#define RCAR_DU_FEATURE_VSPDL_SOURCE (1 << 4) /* Has VSPDL from VSP2 */
#define RCAR_DU_QUIRK_ALIGN_128B (1 << 0) /* Align pitches to 128 bytes */
#define RCAR_DU_QUIRK_LVDS_LANES (1 << 1) /* LVDS lanes 1 and 3 inverted */
@@ -67,12 +69,15 @@ struct rcar_du_device_info {
unsigned int num_crtcs;
struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
unsigned int num_lvds;
+ unsigned int dpll_ch;
+ unsigned int vsp_num;
+ unsigned int vspdl_pair_ch;
};
#define RCAR_DU_MAX_CRTCS 4
#define RCAR_DU_MAX_GROUPS DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2)
#define RCAR_DU_MAX_LVDS 2
-#define RCAR_DU_MAX_VSPS 4
+#define RCAR_DU_MAX_VSPS 5
struct rcar_du_device {
struct device *dev;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
index ab8645c..6eaba60 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
@@ -1,7 +1,7 @@
/*
* rcar_du_encoder.c -- R-Car Display Unit Encoder
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -106,7 +106,8 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
enum rcar_du_encoder_type type,
enum rcar_du_output output,
struct device_node *enc_node,
- struct device_node *con_node)
+ struct device_node *con_node,
+ const char *device_name)
{
struct rcar_du_encoder *renc;
struct drm_encoder *encoder;
@@ -150,8 +151,12 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
break;
}
+ renc->device_name = device_name;
+
if (type == RCAR_DU_ENCODER_HDMI) {
ret = rcar_du_hdmienc_init(rcdu, renc, enc_node);
+ if (of_device_is_compatible(enc_node, "rockchip,rcar-dw-hdmi"))
+ goto done;
if (ret < 0)
goto done;
} else {
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
index 7fc10a9..26d589b 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
@@ -1,7 +1,7 @@
/*
* rcar_du_encoder.h -- R-Car Display Unit Encoder
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -33,6 +33,7 @@ struct rcar_du_encoder {
enum rcar_du_output output;
struct rcar_du_hdmienc *hdmi;
struct rcar_du_lvdsenc *lvds;
+ const char *device_name;
};
#define to_rcar_encoder(e) \
@@ -52,6 +53,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
enum rcar_du_encoder_type type,
enum rcar_du_output output,
struct device_node *enc_node,
- struct device_node *con_node);
+ struct device_node *con_node,
+ const char *device_name);
#endif /* __RCAR_DU_ENCODER_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c
index 33b2fc5..9eaed53 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_group.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c
@@ -1,7 +1,7 @@
/*
* rcar_du_group.c -- R-Car Display Unit Channels Pair
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -108,13 +108,30 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp)
/* Configure input dot clock routing. We currently hardcode the
* configuration to routing DOTCLKINn to DUn.
*/
- rcar_du_group_write(rgrp, DIDSR, DIDSR_CODE |
+ if (rcdu->info->gen < 3) {
+ rcar_du_group_write(rgrp, DIDSR, DIDSR_CODE |
DIDSR_LCDS_DCLKIN(2) |
DIDSR_LCDS_DCLKIN(1) |
DIDSR_LCDS_DCLKIN(0) |
DIDSR_PDCS_CLK(2, 0) |
DIDSR_PDCS_CLK(1, 0) |
DIDSR_PDCS_CLK(0, 0));
+ } else {
+ if (rgrp->index == 0) {
+ rcar_du_group_write(rgrp,
+ DIDSR, DIDSR_CODE |
+ DIDSR_LCDS0_DCLKIN |
+ DIDSR_PDCS_CLK(1, 0) |
+ DIDSR_PDCS_CLK(0, 0));
+ } else if ((rgrp->index == 1) &&
+ rcar_du_has(rgrp->dev,
+ RCAR_DU_FEATURE_DIDSR2_REG)) {
+ rcar_du_group_write(rgrp,
+ DIDSR, DIDSR_CODE |
+ DIDSR_PDCS_CLK(1, 0) |
+ DIDSR_PDCS_CLK(0, 0));
+ }
+ }
}
if (rcdu->info->gen >= 3)
@@ -164,14 +181,30 @@ void rcar_du_group_put(struct rcar_du_group *rgrp)
--rgrp->use_count;
}
-static void __rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start)
+static void __rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start,
+ struct rcar_du_crtc *rcrtc)
{
+ struct rcar_du_device *rcdu = rgrp->dev;
+
+ if (!start) {
+ rcar_du_group_write(rgrp, DSYSR,
+ (rcar_du_group_read(rgrp, DSYSR) &
+ ~(DSYSR_DRES | DSYSR_DEN)));
+
+ /* Wait until access to VSP stops after setting both
+ * the DEN and DRES bits in DSYSRm.
+ */
+ if (rcdu->info->gen >= 3)
+ rcar_du_crtc_vbk_check(rcrtc);
+ }
+
rcar_du_group_write(rgrp, DSYSR,
(rcar_du_group_read(rgrp, DSYSR) & ~(DSYSR_DRES | DSYSR_DEN)) |
(start ? DSYSR_DEN : DSYSR_DRES));
}
-void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start)
+void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start,
+ struct rcar_du_crtc *rcrtc)
{
/* Many of the configuration bits are only updated when the display
* reset (DRES) bit in DSYSR is set to 1, disabling *both* CRTCs. Some
@@ -186,20 +219,21 @@ void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start)
*/
if (start) {
if (rgrp->used_crtcs++ != 0)
- __rcar_du_group_start_stop(rgrp, false);
- __rcar_du_group_start_stop(rgrp, true);
+ __rcar_du_group_start_stop(rgrp, false, rcrtc);
+ __rcar_du_group_start_stop(rgrp, true, rcrtc);
} else {
if (--rgrp->used_crtcs == 0)
- __rcar_du_group_start_stop(rgrp, false);
+ __rcar_du_group_start_stop(rgrp, false, rcrtc);
}
}
-void rcar_du_group_restart(struct rcar_du_group *rgrp)
+void rcar_du_group_restart(struct rcar_du_group *rgrp,
+ struct rcar_du_crtc *rcrtc)
{
rgrp->need_restart = false;
- __rcar_du_group_start_stop(rgrp, false);
- __rcar_du_group_start_stop(rgrp, true);
+ __rcar_du_group_start_stop(rgrp, false, rcrtc);
+ __rcar_du_group_start_stop(rgrp, true, rcrtc);
}
int rcar_du_set_dpad0_vsp1_routing(struct rcar_du_device *rcdu)
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.h b/drivers/gpu/drm/rcar-du/rcar_du_group.h
index 5e3adc6..e7391c0 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_group.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_group.h
@@ -1,7 +1,7 @@
/*
* rcar_du_group.c -- R-Car Display Unit Planes and CRTCs Group
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -56,8 +56,10 @@ void rcar_du_group_write(struct rcar_du_group *rgrp, u32 reg, u32 data);
int rcar_du_group_get(struct rcar_du_group *rgrp);
void rcar_du_group_put(struct rcar_du_group *rgrp);
-void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start);
-void rcar_du_group_restart(struct rcar_du_group *rgrp);
+void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start,
+ struct rcar_du_crtc *rcrtc);
+void rcar_du_group_restart(struct rcar_du_group *rgrp,
+ struct rcar_du_crtc *rcrtc);
int rcar_du_group_set_routing(struct rcar_du_group *rgrp);
int rcar_du_set_dpad0_vsp1_routing(struct rcar_du_device *rcdu);
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c
index e03004f..1e75e39 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c
@@ -1,7 +1,7 @@
/*
* R-Car Display Unit HDMI Encoder
*
- * Copyright (C) 2014 Renesas Electronics Corporation
+ * Copyright (C) 2014-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -13,10 +13,13 @@
#include <linux/slab.h>
+#include <drm/bridge/dw_hdmi.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
+#include <linux/of_platform.h>
+
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
#include "rcar_du_hdmienc.h"
@@ -24,14 +27,43 @@
struct rcar_du_hdmienc {
struct rcar_du_encoder *renc;
+ struct device *dev;
bool enabled;
+ unsigned int index;
};
#define to_rcar_hdmienc(e) (to_rcar_encoder(e)->hdmi)
-static void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
+void rcar_du_hdmienc_suspend(struct drm_encoder *encoder)
+{
+ const struct drm_bridge_funcs *bfuncs = encoder->bridge->funcs;
+
+ if ((bfuncs) && (bfuncs->disable))
+ bfuncs->disable(encoder->bridge);
+}
+
+#define SMSTPCR7 0xE615014C
+
+void rcar_du_hdmienc_resume(struct drm_encoder *encoder)
+{
+ void __iomem *smstpcr = ioremap_nocache(SMSTPCR7, 4);
+ const struct drm_bridge_funcs *bfuncs = encoder->bridge->funcs;
+ u32 temp;
+
+ temp = readl(smstpcr);
+ writel(temp & ~(0x3 << 28), smstpcr);
+
+ if ((bfuncs) && (bfuncs->enable))
+ bfuncs->enable(encoder->bridge);
+}
+
+void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
+ const struct drm_bridge_funcs *bfuncs = encoder->bridge->funcs;
+
+ if ((bfuncs) && (bfuncs->disable))
+ bfuncs->disable(encoder->bridge);
if (hdmienc->renc->lvds)
rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc,
@@ -40,13 +72,16 @@ static void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
hdmienc->enabled = false;
}
-static void rcar_du_hdmienc_enable(struct drm_encoder *encoder)
+void rcar_du_hdmienc_enable(struct drm_encoder *encoder)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
+ const struct drm_bridge_funcs *bfuncs = encoder->bridge->funcs;
if (hdmienc->renc->lvds)
rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc,
true);
+ if ((bfuncs) && (bfuncs->enable))
+ bfuncs->enable(encoder->bridge);
hdmienc->enabled = true;
}
@@ -56,21 +91,30 @@ static int rcar_du_hdmienc_atomic_check(struct drm_encoder *encoder,
struct drm_connector_state *conn_state)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
+ const struct drm_bridge_funcs *bfuncs = encoder->bridge->funcs;
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+ const struct drm_display_mode *mode = &crtc_state->mode;
+ int ret = 0;
if (hdmienc->renc->lvds)
rcar_du_lvdsenc_atomic_check(hdmienc->renc->lvds,
adjusted_mode);
- return 0;
+ if ((bfuncs) && (bfuncs->mode_fixup))
+ ret = bfuncs->mode_fixup(encoder->bridge, mode,
+ adjusted_mode) ? 0 : -EINVAL;
+ return ret;
}
-
static void rcar_du_hdmienc_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
+ const struct drm_bridge_funcs *bfuncs = encoder->bridge->funcs;
+
+ if ((bfuncs) && (bfuncs->mode_set))
+ bfuncs->mode_set(encoder->bridge, mode, adjusted_mode);
rcar_du_crtc_route_output(encoder->crtc, hdmienc->renc->output);
}
@@ -96,22 +140,199 @@ static const struct drm_encoder_funcs encoder_funcs = {
.destroy = rcar_du_hdmienc_cleanup,
};
+static const struct dw_hdmi_mpll_config rcar_du_hdmienc_mpll_cfg[] = {
+ {
+ 44900000, {
+ { 0x0003, 0x0000},
+ { 0x0003, 0x0000},
+ { 0x0003, 0x0000}
+ },
+ }, {
+ 90000000, {
+ { 0x0002, 0x0000},
+ { 0x0002, 0x0000},
+ { 0x0002, 0x0000}
+ },
+ }, {
+ 182750000, {
+ { 0x0001, 0x0000},
+ { 0x0001, 0x0000},
+ { 0x0001, 0x0000}
+ },
+ }, {
+ 297000000, {
+ { 0x0000, 0x0000},
+ { 0x0000, 0x0000},
+ { 0x0000, 0x0000}
+ },
+ }, {
+ ~0UL, {
+ { 0xFFFF, 0xFFFF },
+ { 0xFFFF, 0xFFFF },
+ { 0xFFFF, 0xFFFF },
+ },
+ }
+};
+static const struct dw_hdmi_curr_ctrl rcar_du_hdmienc_cur_ctr[] = {
+ /* pixelclk bpp8 bpp10 bpp12 */
+ {
+ 35500000, { 0x0344, 0x0000, 0x0000 },
+ }, {
+ 44900000, { 0x0285, 0x0000, 0x0000 },
+ }, {
+ 71000000, { 0x1184, 0x0000, 0x0000 },
+ }, {
+ 90000000, { 0x1144, 0x0000, 0x0000 },
+ }, {
+ 140250000, { 0x20c4, 0x0000, 0x0000 },
+ }, {
+ 182750000, { 0x2084, 0x0000, 0x0000 },
+ }, {
+ 297000000, { 0x0084, 0x0000, 0x0000 },
+ }, {
+ ~0UL, { 0x0000, 0x0000, 0x0000 },
+ }
+};
+
+static const struct dw_hdmi_multi_div rcar_du_hdmienc_multi_div[] = {
+ /* pixelclk bpp8 bpp10 bpp12 */
+ {
+ 35500000, { 0x0328, 0x0000, 0x0000 },
+ }, {
+ 44900000, { 0x0128, 0x0000, 0x0000 },
+ }, {
+ 71000000, { 0x0314, 0x0000, 0x0000 },
+ }, {
+ 90000000, { 0x0114, 0x0000, 0x0000 },
+ }, {
+ 140250000, { 0x030a, 0x0000, 0x0000 },
+ }, {
+ 182750000, { 0x010a, 0x0000, 0x0000 },
+ }, {
+ 281250000, { 0x0305, 0x0000, 0x0000 },
+ }, {
+ 297000000, { 0x0105, 0x0000, 0x0000 },
+ }, {
+ ~0UL, { 0x0000, 0x0000, 0x0000 },
+ }
+};
+
+static const struct dw_hdmi_phy_config rcar_du_hdmienc_phy_config[] = {
+ /*pixelclk symbol term vlev*/
+ { 74250000, 0x8009, 0x0004, 0x0272},
+ { 148500000, 0x802b, 0x0004, 0x028d},
+ { 297000000, 0x8039, 0x0005, 0x028d},
+ { ~0UL, 0x0000, 0x0000, 0x0000}
+};
+
+static enum drm_mode_status
+rcar_du_hdmienc_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ if ((mode->hdisplay > 3840) || (mode->vdisplay > 2160))
+ return MODE_BAD;
+
+ if (((mode->hdisplay == 3840) && (mode->vdisplay == 2160))
+ && (mode->vrefresh > 30))
+ return MODE_BAD;
+
+ if (mode->clock > 297000)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+static const struct dw_hdmi_plat_data rcar_du_hdmienc_hdmi0_drv_data = {
+ .mode_valid = rcar_du_hdmienc_mode_valid,
+ .mpll_cfg = rcar_du_hdmienc_mpll_cfg,
+ .cur_ctr = rcar_du_hdmienc_cur_ctr,
+ .multi_div = rcar_du_hdmienc_multi_div,
+ .phy_config = rcar_du_hdmienc_phy_config,
+ .dev_type = RCAR_HDMI,
+ .index = 0,
+};
+
+static const struct dw_hdmi_plat_data rcar_du_hdmienc_hdmi1_drv_data = {
+ .mode_valid = rcar_du_hdmienc_mode_valid,
+ .mpll_cfg = rcar_du_hdmienc_mpll_cfg,
+ .cur_ctr = rcar_du_hdmienc_cur_ctr,
+ .multi_div = rcar_du_hdmienc_multi_div,
+ .phy_config = rcar_du_hdmienc_phy_config,
+ .dev_type = RCAR_HDMI,
+ .index = 1,
+};
+
+static const struct of_device_id rcar_du_hdmienc_dt_ids[] = {
+ {
+ .data = &rcar_du_hdmienc_hdmi0_drv_data
+ },
+ {
+ .data = &rcar_du_hdmienc_hdmi1_drv_data
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rcar_du_hdmienc_dt_ids);
+
int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc, struct device_node *np)
{
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
- struct drm_bridge *bridge;
struct rcar_du_hdmienc *hdmienc;
- int ret;
+ struct resource *iores;
+ struct platform_device *pdev;
+ const struct dw_hdmi_plat_data *plat_data;
+ int ret, irq;
+ bool dw_hdmi_use = false;
+ struct drm_bridge *bridge = NULL;
hdmienc = devm_kzalloc(rcdu->dev, sizeof(*hdmienc), GFP_KERNEL);
if (hdmienc == NULL)
return -ENOMEM;
- /* Locate drm bridge from the hdmi encoder DT node */
- bridge = of_drm_find_bridge(np);
- if (!bridge)
- return -EPROBE_DEFER;
+ if (strcmp(renc->device_name, "rockchip,rcar-dw-hdmi") == 0)
+ dw_hdmi_use = true;
+
+ if (dw_hdmi_use) {
+ if (renc->output == RCAR_DU_OUTPUT_HDMI0)
+ hdmienc->index = 0;
+ else if (renc->output == RCAR_DU_OUTPUT_HDMI1)
+ hdmienc->index = 1;
+ else
+ return -EINVAL;
+
+ np = of_parse_phandle(rcdu->dev->of_node, "hdmi",
+ hdmienc->index);
+ if (!np) {
+ dev_err(rcdu->dev, "hdmi node not found\n");
+ return -ENXIO;
+ }
+
+ pdev = of_find_device_by_node(np);
+ of_node_put(np);
+ if (!pdev)
+ return -ENXIO;
+
+ plat_data = rcar_du_hdmienc_dt_ids[hdmienc->index].data;
+ hdmienc->dev = &pdev->dev;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!iores)
+ return -ENXIO;
+
+ } else {
+ /* Locate the DRM bridge from the HDMI encoder DT node. */
+ bridge = of_drm_find_bridge(np);
+ if (!bridge) {
+ dev_dbg(rcdu->dev,
+ "can't get bridge for %s, deferring probe\n",
+ of_node_full_name(np));
+ return -EPROBE_DEFER;
+ }
+ }
ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
@@ -124,13 +345,21 @@ int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
hdmienc->renc = renc;
/* Link drm_bridge to encoder */
- bridge->encoder = encoder;
- encoder->bridge = bridge;
+ if (bridge) {
+ bridge->encoder = encoder;
+ encoder->bridge = bridge;
+ }
- ret = drm_bridge_attach(rcdu->ddev, bridge);
- if (ret) {
- drm_encoder_cleanup(encoder);
- return ret;
+ if (dw_hdmi_use)
+ ret = dw_hdmi_bind(rcdu->dev, NULL, rcdu->ddev, encoder,
+ iores, irq, plat_data);
+
+ if (bridge) {
+ ret = drm_bridge_attach(rcdu->ddev, bridge);
+ if (ret) {
+ drm_encoder_cleanup(encoder);
+ return ret;
+ }
}
return 0;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h
index 2ff0128..14f5c33 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h
@@ -1,7 +1,7 @@
/*
* R-Car Display Unit HDMI Encoder
*
- * Copyright (C) 2014 Renesas Electronics Corporation
+ * Copyright (C) 2014-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -23,6 +23,10 @@ struct rcar_du_encoder;
#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI)
int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc, struct device_node *np);
+void rcar_du_hdmienc_disable(struct drm_encoder *encoder);
+void rcar_du_hdmienc_enable(struct drm_encoder *encoder);
+void rcar_du_hdmienc_suspend(struct drm_encoder *encoder);
+void rcar_du_hdmienc_resume(struct drm_encoder *encoder);
#else
static inline int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc,
@@ -30,6 +34,18 @@ static inline int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
{
return -ENOSYS;
}
+static inline void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
+{
+}
+static inline void rcar_du_hdmienc_enable(struct drm_encoder *encoder)
+{
+}
+static inline void rcar_du_hdmienc_suspend(struct drm_encoder *encoder)
+{
+}
+static inline void rcar_du_hdmienc_resume(struct drm_encoder *encoder)
+{
+}
#endif
#endif /* __RCAR_DU_HDMIENC_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
index bd9c3bb..d1583a9 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
@@ -1,7 +1,7 @@
/*
* rcar_du_kms.c -- R-Car Display Unit Mode Setting
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -95,6 +95,48 @@ static const struct rcar_du_format_info rcar_du_format_infos[] = {
.planes = 2,
.pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
.edf = PnDDCR4_EDF_NONE,
+ }, {
+ .fourcc = DRM_FORMAT_NV61,
+ .bpp = 16,
+ .planes = 2,
+ .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
+ .edf = PnDDCR4_EDF_NONE,
+ },
+};
+
+static const struct rcar_du_format_info rcar_vsp_format_infos[] = {
+ {
+ .fourcc = DRM_FORMAT_RGB332,
+ .bpp = 8,
+ .planes = 1,
+ }, {
+ .fourcc = DRM_FORMAT_ARGB4444,
+ .bpp = 16,
+ .planes = 1,
+ }, {
+ .fourcc = DRM_FORMAT_XRGB4444,
+ .bpp = 16,
+ .planes = 1,
+ }, {
+ .fourcc = DRM_FORMAT_BGR888,
+ .bpp = 24,
+ .planes = 1,
+ }, {
+ .fourcc = DRM_FORMAT_RGB888,
+ .bpp = 24,
+ .planes = 1,
+ }, {
+ .fourcc = DRM_FORMAT_BGRA8888,
+ .bpp = 32,
+ .planes = 1,
+ }, {
+ .fourcc = DRM_FORMAT_BGRX8888,
+ .bpp = 32,
+ .planes = 1,
+ }, {
+ .fourcc = DRM_FORMAT_YVYU,
+ .bpp = 16,
+ .planes = 1,
},
/* The following formats are not supported on Gen2 and thus have no
* associated .pnmr or .edf settings.
@@ -142,6 +184,18 @@ const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc)
return NULL;
}
+const struct rcar_du_format_info *rcar_vsp_format_info(u32 fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(rcar_vsp_format_infos); ++i) {
+ if (rcar_vsp_format_infos[i].fourcc == fourcc)
+ return &rcar_vsp_format_infos[i];
+ }
+
+ return NULL;
+}
+
/* -----------------------------------------------------------------------------
* Frame buffer
*/
@@ -178,6 +232,15 @@ rcar_du_fb_create(struct drm_device *dev, struct drm_file *file_priv,
unsigned int i;
format = rcar_du_format_info(mode_cmd->pixel_format);
+
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE) &&
+ (format == NULL)) {
+ format = rcar_vsp_format_info(mode_cmd->pixel_format);
+ }
+
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE) && (format != NULL))
+ goto done;
+
if (format == NULL) {
dev_dbg(dev->dev, "unsupported pixel format %08x\n",
mode_cmd->pixel_format);
@@ -210,7 +273,7 @@ rcar_du_fb_create(struct drm_device *dev, struct drm_file *file_priv,
return ERR_PTR(-EINVAL);
}
}
-
+done:
return drm_fb_cma_create(dev, file_priv, mode_cmd);
}
@@ -279,7 +342,6 @@ static void rcar_du_atomic_work(struct work_struct *work)
{
struct rcar_du_commit *commit =
container_of(work, struct rcar_du_commit, work);
-
rcar_du_atomic_complete(commit);
}
@@ -342,6 +404,34 @@ static int rcar_du_atomic_commit(struct drm_device *dev,
return ret;
}
+int rcar_du_async_commit(struct drm_device *dev, struct drm_crtc *crtc)
+{
+ int ret;
+ struct drm_atomic_state *state;
+ struct drm_crtc_state *crtc_state;
+
+ state = drm_atomic_state_alloc(dev);
+ if (!state)
+ return -ENOMEM;
+
+ crtc_state = drm_atomic_helper_crtc_duplicate_state(crtc);
+ if (!crtc_state)
+ return -ENOMEM;
+
+ state->crtcs->state = crtc_state;
+ state->crtcs->ptr = crtc;
+ crtc_state->state = state;
+ crtc_state->active = true;
+
+ ret = drm_atomic_commit(state);
+ if (ret != 0) {
+ drm_atomic_helper_crtc_destroy_state(crtc, crtc_state);
+ return ret;
+ }
+
+ return 0;
+}
+
/* -----------------------------------------------------------------------------
* Initialization
*/
@@ -363,6 +453,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
} encoders[] = {
{ "adi,adv7123", RCAR_DU_ENCODER_VGA },
{ "adi,adv7511w", RCAR_DU_ENCODER_HDMI },
+ { "rockchip,rcar-dw-hdmi", RCAR_DU_ENCODER_HDMI },
{ "thine,thc63lvdm83d", RCAR_DU_ENCODER_LVDS },
};
@@ -373,6 +464,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
struct device_node *entity_ep_node;
struct device_node *entity;
int ret;
+ const char *enc_name = NULL;
/*
* Locate the connected entity and infer its type from the number of
@@ -424,6 +516,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
if (of_device_is_compatible(encoder,
encoders[i].compatible)) {
enc_type = encoders[i].type;
+ enc_name = encoders[i].compatible;
break;
}
}
@@ -444,7 +537,8 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
connector = entity;
}
- ret = rcar_du_encoder_init(rcdu, enc_type, output, encoder, connector);
+ ret = rcar_du_encoder_init(rcdu, enc_type, output, encoder, connector,
+ enc_name);
of_node_put(encoder);
of_node_put(connector);
@@ -549,8 +643,8 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
- dev->mode_config.max_width = 4095;
- dev->mode_config.max_height = 2047;
+ dev->mode_config.max_width = 4096;
+ dev->mode_config.max_height = 2160;
dev->mode_config.funcs = &rcar_du_mode_config_funcs;
rcdu->num_crtcs = rcdu->info->num_crtcs;
@@ -591,15 +685,26 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
/* Initialize the compositors. */
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) {
for (i = 0; i < rcdu->num_crtcs; ++i) {
- struct rcar_du_vsp *vsp = &rcdu->vsps[i];
+ struct rcar_du_vsp *vsp;
+ int vsp_index = i;
+ bool init = true;
- vsp->index = i;
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSPDL_SOURCE) &&
+ (i == rcdu->info->vspdl_pair_ch)) {
+ vsp_index = 0;
+ init = false;
+ }
+
+ vsp = &rcdu->vsps[vsp_index];
+ vsp->index = vsp_index;
vsp->dev = rcdu;
rcdu->crtcs[i].vsp = vsp;
- ret = rcar_du_vsp_init(vsp);
- if (ret < 0)
- return ret;
+ if (init) {
+ ret = rcar_du_vsp_init(vsp);
+ if (ret < 0)
+ return ret;
+ }
}
}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.h b/drivers/gpu/drm/rcar-du/rcar_du_kms.h
index 07951d5..ccccc71 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_kms.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.h
@@ -1,7 +1,7 @@
/*
* rcar_du_kms.h -- R-Car Display Unit Mode Setting
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -30,10 +30,12 @@ struct rcar_du_format_info {
};
const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc);
+const struct rcar_du_format_info *rcar_vsp_format_info(u32 fourcc);
int rcar_du_modeset_init(struct rcar_du_device *rcdu);
int rcar_du_dumb_create(struct drm_file *file, struct drm_device *dev,
struct drm_mode_create_dumb *args);
+int rcar_du_async_commit(struct drm_device *dev, struct drm_crtc *crtc);
#endif /* __RCAR_DU_KMS_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
index d4881ee..07f9e3e 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
@@ -1,7 +1,7 @@
/*
* rcar_du_lvdscon.h -- R-Car Display Unit LVDS Connector
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -17,8 +17,17 @@
struct rcar_du_device;
struct rcar_du_encoder;
+#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc,
struct device_node *np);
+#else
+static inline int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
+ struct rcar_du_encoder *renc,
+ struct device_node *np)
+{
+ return -ENOSYS;
+}
+#endif
#endif /* __RCAR_DU_LVDSCON_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
index 116740a..08ee27e 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
@@ -1,7 +1,7 @@
/*
* rcar_du_lvdsenc.c -- R-Car Display Unit LVDS Encoder
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -14,6 +14,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
+#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
@@ -32,6 +33,7 @@ struct rcar_du_lvdsenc {
enum rcar_lvds_input input;
enum rcar_lvds_mode mode;
+ int gpio_pd;
};
static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
@@ -105,6 +107,13 @@ static void rcar_du_lvdsenc_start_gen3(struct rcar_du_lvdsenc *lvds,
rcar_lvds_write(lvds, LVDPLLCR, pllcr);
+ /* Turn all the channels on. */
+ rcar_lvds_write(lvds, LVDCR1,
+ LVDCR1_CHSTBY_GEN3(3) | LVDCR1_CHSTBY_GEN3(2) |
+ LVDCR1_CHSTBY_GEN3(1) | LVDCR1_CHSTBY_GEN3(0) |
+ LVDCR1_CLKSTBY_GEN3);
+
+
/* Turn the PLL on, set it to LVDS normal mode, wait for the startup
* delay and turn the output on.
*/
@@ -118,23 +127,29 @@ static void rcar_du_lvdsenc_start_gen3(struct rcar_du_lvdsenc *lvds,
lvdcr0 |= LVDCR0_LVRES;
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-
- /* Turn all the channels on. */
- rcar_lvds_write(lvds, LVDCR1,
- LVDCR1_CHSTBY_GEN3(3) | LVDCR1_CHSTBY_GEN3(2) |
- LVDCR1_CHSTBY_GEN3(1) | LVDCR1_CHSTBY_GEN3(0) |
- LVDCR1_CLKSTBY_GEN3);
}
-static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
+int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
struct rcar_du_crtc *rcrtc)
{
u32 lvdhcr;
int ret;
+ void __iomem *srstclr7_reg;
+ u32 srstclr7_lvds = 0;
if (lvds->enabled)
return 0;
+ /* software reset release */
+ if (lvds->index == 0)
+ srstclr7_lvds |= SRCR7_LVDS;
+ srstclr7_reg = ioremap_nocache(SRSTCLR7, 0x04);
+ writel_relaxed(srstclr7_lvds, srstclr7_reg);
+ iounmap(srstclr7_reg);
+
+ if (gpio_is_valid(lvds->gpio_pd))
+ gpio_set_value(lvds->gpio_pd, 1);
+
ret = clk_prepare_enable(lvds->clock);
if (ret < 0)
return ret;
@@ -165,15 +180,19 @@ static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
else
rcar_du_lvdsenc_start_gen3(lvds, rcrtc);
+ rcrtc->lvds_ch = lvds->index;
lvds->enabled = true;
return 0;
}
-static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
+int rcar_du_lvdsenc_stop_suspend(struct rcar_du_lvdsenc *lvds)
{
+ void __iomem *srcr7_reg;
+ u32 srcr7_lvds = 0;
+
if (!lvds->enabled)
- return;
+ return -1;
rcar_lvds_write(lvds, LVDCR0, 0);
rcar_lvds_write(lvds, LVDCR1, 0);
@@ -181,6 +200,36 @@ static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
clk_disable_unprepare(lvds->clock);
lvds->enabled = false;
+
+ if (gpio_is_valid(lvds->gpio_pd))
+ gpio_set_value(lvds->gpio_pd, 0);
+
+ if (lvds->index == 0)
+ srcr7_lvds |= SRCR7_LVDS;
+ srcr7_reg = ioremap_nocache(SRCR7, 0x04);
+ writel_relaxed(readl_relaxed(srcr7_reg) | srcr7_lvds, srcr7_reg);
+ iounmap(srcr7_reg);
+
+ return 0;
+}
+
+static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
+{
+ int ret;
+ unsigned int i;
+
+ if (!lvds->enabled)
+ return;
+
+ ret = rcar_du_lvdsenc_stop_suspend(lvds);
+ if (ret < 0)
+ return;
+
+ for (i = 0; i < lvds->dev->num_crtcs; ++i)
+ if (lvds->index == lvds->dev->crtcs[i].lvds_ch)
+ lvds->dev->crtcs[i].lvds_ch = -1;
+
+ return;
}
int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc,
@@ -245,6 +294,7 @@ int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
struct rcar_du_lvdsenc *lvds;
unsigned int i;
int ret;
+ char name[16];
for (i = 0; i < rcdu->info->num_lvds; ++i) {
lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
@@ -257,12 +307,20 @@ int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
lvds->index = i;
lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
lvds->enabled = false;
+ /* Get optional backlight GPIO */
+ lvds->gpio_pd = of_get_named_gpio(rcdu->dev->of_node,
+ "backlight-gpios", 0);
ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
if (ret < 0)
return ret;
rcdu->lvds[i] = lvds;
+
+ sprintf(name, "lvds%u", i);
+ if (gpio_is_valid(lvds->gpio_pd))
+ devm_gpio_request_one(&pdev->dev, lvds->gpio_pd,
+ GPIOF_OUT_INIT_LOW, name);
}
return 0;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
index 7218ac8..e18e632 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
@@ -1,7 +1,7 @@
/*
* rcar_du_lvdsenc.h -- R-Car Display Unit LVDS Encoder
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -41,6 +41,9 @@ int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
struct drm_crtc *crtc, bool enable);
void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
struct drm_display_mode *mode);
+int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
+ struct rcar_du_crtc *rcrtc);
+int rcar_du_lvdsenc_stop_suspend(struct rcar_du_lvdsenc *lvds);
#else
static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
{
@@ -59,6 +62,15 @@ static inline void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
struct drm_display_mode *mode)
{
}
+static inline int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
+ struct rcar_du_crtc *rcrtc)
+{
+ return 0;
+}
+static inline int rcar_du_lvdsenc_stop_suspend(struct rcar_du_lvdsenc *lvds)
+{
+ return 0;
+}
#endif
#endif /* __RCAR_DU_LVDSENC_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_plane.h b/drivers/gpu/drm/rcar-du/rcar_du_plane.h
index 8b91dd3..c1de338 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_plane.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.h
@@ -1,7 +1,7 @@
/*
* rcar_du_plane.h -- R-Car Display Unit Planes
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -34,6 +34,11 @@ enum rcar_du_plane_source {
RCAR_DU_PLANE_VSPD1,
};
+#define DU_CH_0 0
+#define DU_CH_1 1
+#define DU_CH_2 2
+#define DU_CH_3 3
+
struct rcar_du_plane {
struct drm_plane plane;
struct rcar_du_group *group;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
index fedb016..3c3c798 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_regs.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
@@ -1,7 +1,7 @@
/*
* rcar_du_regs.h -- R-Car Display Unit Registers Definitions
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -255,6 +255,8 @@
#define DIDSR 0x20028
#define DIDSR_CODE (0x7790 << 16)
+#define DIDSR_LCDS0_DCLKIN (0 << 9)
+#define DIDSR_LCDS0_LVDSIF (1 << 9)
#define DIDSR_LCDS_DCLKIN(n) (0 << (8 + (n) * 2))
#define DIDSR_LCDS_LVDS0(n) (2 << (8 + (n) * 2))
#define DIDSR_LCDS_LVDS1(n) (3 << (8 + (n) * 2))
@@ -277,6 +279,25 @@
#define DEFR10_TSEL_H3_TCON1 (0 << 1) /* DEFR102 register only (DU2/DU3) */
#define DEFR10_DEFE10 (1 << 0)
+#define DPLLCR 0x20044
+#define DPLLCR_CODE (0x95 << 24)
+#define DPLLCR_PLCS1 (1 << 23)
+#define DPLLCR_PLCS0 (1 << 20)
+#define DPLLCR_CLKE (1 << 18)
+#define DPLLCR_FDPLL(n) ((n) << 12) /* n=0 Setting prohibited */
+/* H'00 to H'26, H'78 to H'7F: Setting prohibited.*/
+#define DPLLCR_N(n) ((n) << 5)
+#define DPLLCR_M(n) ((n) << 3)
+#define DPLLCR_STBY (1 << 2)
+#define DPLLCR_INCS_DPLL01_DOTCLKIN02 (0 << 0)
+#define DPLLCR_INCS_DPLL01_DOTCLKIN13 (1 << 1)
+
+#define DPLLC2R 0x20048
+#define DPLLC2R_CODE (0x95 << 24)
+#define DPLLC2R_SELC (1 << 12)
+#define DPLLC2R_M(n) ((n) << 8)
+#define DPLLC2R_FDPLL(n) ((n) << 0) /* n=0 Setting prohibited */
+
/* -----------------------------------------------------------------------------
* Display Timing Generation Registers
*/
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c
index 8d6125c..e81dfbe 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c
@@ -1,7 +1,7 @@
/*
* rcar_du_vgacon.c -- R-Car Display Unit VGA Connector
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -23,7 +23,7 @@
static int rcar_du_vga_connector_get_modes(struct drm_connector *connector)
{
- return 0;
+ return drm_add_modes_noedid(connector, 1024, 768);
}
static const struct drm_connector_helper_funcs connector_helper_funcs = {
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vsp.c b/drivers/gpu/drm/rcar-du/rcar_du_vsp.c
index 851c2e7..c7024e5 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_vsp.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_vsp.c
@@ -1,7 +1,7 @@
/*
- * rcar_du_vsp.h -- R-Car Display Unit VSP-Based Compositor
+ * rcar_du_vsp.c -- R-Car Display Unit VSP-Based Compositor
*
- * Copyright (C) 2015 Renesas Electronics Corporation
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -12,12 +12,14 @@
*/
#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_plane_helper.h>
+#include <drm/rcar_du_drm.h>
#include <linux/dma-mapping.h>
#include <linux/of_platform.h>
@@ -68,22 +70,23 @@ void rcar_du_vsp_enable(struct rcar_du_crtc *crtc)
*/
crtc->group->need_restart = true;
- vsp1_du_setup_lif(crtc->vsp->vsp, mode->hdisplay, mode->vdisplay);
+ vsp1_du_setup_lif(crtc->vsp->vsp, mode->hdisplay, mode->vdisplay,
+ crtc->lif_index, 0);
}
void rcar_du_vsp_disable(struct rcar_du_crtc *crtc)
{
- vsp1_du_setup_lif(crtc->vsp->vsp, 0, 0);
+ vsp1_du_setup_lif(crtc->vsp->vsp, 0, 0, 0, crtc->suspend);
}
void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc)
{
- vsp1_du_atomic_begin(crtc->vsp->vsp);
+ vsp1_du_atomic_begin(crtc->vsp->vsp, crtc->lif_index);
}
void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc)
{
- vsp1_du_atomic_flush(crtc->vsp->vsp);
+ vsp1_du_atomic_flush(crtc->vsp->vsp, crtc->lif_index);
}
/* Keep the two tables in sync. */
@@ -145,6 +148,28 @@ static const u32 formats_v4l2[] = {
V4L2_PIX_FMT_YVU444M,
};
+static const u32 formats_xlate[][2] = {
+ { DRM_FORMAT_RGB332, V4L2_PIX_FMT_RGB332 },
+ { DRM_FORMAT_ARGB4444, V4L2_PIX_FMT_ARGB444 },
+ { DRM_FORMAT_XRGB4444, V4L2_PIX_FMT_XRGB444 },
+ { DRM_FORMAT_ARGB1555, V4L2_PIX_FMT_ARGB555 },
+ { DRM_FORMAT_XRGB1555, V4L2_PIX_FMT_XRGB555 },
+ { DRM_FORMAT_RGB565, V4L2_PIX_FMT_RGB565 },
+ { DRM_FORMAT_BGR888, V4L2_PIX_FMT_RGB24 },
+ { DRM_FORMAT_RGB888, V4L2_PIX_FMT_BGR24 },
+ { DRM_FORMAT_BGRA8888, V4L2_PIX_FMT_ARGB32 },
+ { DRM_FORMAT_BGRX8888, V4L2_PIX_FMT_XRGB32 },
+ { DRM_FORMAT_ARGB8888, V4L2_PIX_FMT_ABGR32 },
+ { DRM_FORMAT_XRGB8888, V4L2_PIX_FMT_XBGR32 },
+ { DRM_FORMAT_UYVY, V4L2_PIX_FMT_UYVY },
+ { DRM_FORMAT_YUYV, V4L2_PIX_FMT_YUYV },
+ { DRM_FORMAT_YVYU, V4L2_PIX_FMT_YVYU },
+ { DRM_FORMAT_NV12, V4L2_PIX_FMT_NV12M },
+ { DRM_FORMAT_NV21, V4L2_PIX_FMT_NV21M },
+ { DRM_FORMAT_NV16, V4L2_PIX_FMT_NV16M },
+ { DRM_FORMAT_NV61, V4L2_PIX_FMT_NV61M },
+};
+
static void rcar_du_vsp_plane_setup(struct rcar_du_vsp_plane *plane)
{
struct rcar_du_vsp_plane_state *state =
@@ -158,6 +183,12 @@ static void rcar_du_vsp_plane_setup(struct rcar_du_vsp_plane *plane)
};
unsigned int i;
+ if (plane->plane.state->crtc->mode.flags
+ & DRM_MODE_FLAG_INTERLACE)
+ cfg.interlaced = true;
+ else
+ cfg.interlaced = false;
+
cfg.src.left = state->state.src_x >> 16;
cfg.src.top = state->state.src_y >> 16;
cfg.src.width = state->state.src_w >> 16;
@@ -249,6 +280,7 @@ static int rcar_du_vsp_plane_atomic_check(struct drm_plane *plane,
struct rcar_du_vsp_plane_state *rstate = to_rcar_vsp_plane_state(state);
struct rcar_du_vsp_plane *rplane = to_rcar_vsp_plane(plane);
struct rcar_du_device *rcdu = rplane->vsp->dev;
+ int hdisplay, vdisplay;
if (!state->fb || !state->crtc) {
rstate->format = NULL;
@@ -261,7 +293,25 @@ static int rcar_du_vsp_plane_atomic_check(struct drm_plane *plane,
return -EINVAL;
}
+ hdisplay = state->crtc->mode.hdisplay;
+ vdisplay = state->crtc->mode.vdisplay;
+
+ if ((state->plane->type == DRM_PLANE_TYPE_OVERLAY) &&
+ (((state->crtc_w + state->crtc_x) > hdisplay) ||
+ ((state->crtc_h + state->crtc_y) > vdisplay))) {
+ dev_err(rcdu->dev,
+ "%s: specify (%dx%d) + (%d, %d) < (%dx%d).\n",
+ __func__, state->crtc_w, state->crtc_h, state->crtc_x,
+ state->crtc_y, hdisplay, vdisplay);
+ return -EINVAL;
+ }
+
rstate->format = rcar_du_format_info(state->fb->pixel_format);
+
+ if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE) &&
+ (rstate->format == NULL))
+ rstate->format = rcar_vsp_format_info(state->fb->pixel_format);
+
if (rstate->format == NULL) {
dev_dbg(rcdu->dev, "%s: unsupported format %08x\n", __func__,
state->fb->pixel_format);
@@ -366,6 +416,107 @@ static int rcar_du_vsp_plane_atomic_get_property(struct drm_plane *plane,
return 0;
}
+int rcar_du_vsp_write_back(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ int ret;
+ struct rcar_du_screen_shot *sh = (struct rcar_du_screen_shot *)data;
+ struct drm_mode_object *obj;
+ struct drm_crtc *crtc;
+ struct rcar_du_crtc *rcrtc;
+ struct rcar_du_device *rcdu;
+ const struct drm_display_mode *mode;
+ u32 pixelformat, bpp;
+ unsigned int pitch;
+ dma_addr_t mem[3];
+
+ obj = drm_mode_object_find(dev, sh->crtc_id, DRM_MODE_OBJECT_CRTC);
+ if (!obj)
+ return -EINVAL;
+
+ crtc = obj_to_crtc(obj);
+ rcrtc = to_rcar_crtc(crtc);
+ rcdu = rcrtc->group->dev;
+ mode = &rcrtc->crtc.state->adjusted_mode;
+
+ switch (sh->fmt) {
+ case DRM_FORMAT_RGB565:
+ bpp = 16;
+ pixelformat = V4L2_PIX_FMT_RGB565;
+ break;
+ case DRM_FORMAT_ARGB1555:
+ bpp = 16;
+ pixelformat = V4L2_PIX_FMT_ARGB555;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ bpp = 32;
+ pixelformat = V4L2_PIX_FMT_ABGR32;
+ break;
+ default:
+ dev_err(rcdu->dev, "specified format is not supported.\n");
+ return -EINVAL;
+ }
+
+ pitch = mode->hdisplay * bpp / 8;
+
+ mem[0] = sh->buff;
+ mem[1] = 0;
+ mem[2] = 0;
+
+ if ((sh->width != (mode->hdisplay)) ||
+ (sh->height != (mode->vdisplay)))
+ return -EINVAL;
+
+ if ((pitch * mode->vdisplay) > sh->buff_len)
+ return -EINVAL;
+
+ vsp1_du_setup_wb(rcrtc->vsp->vsp, pixelformat, pitch, mem,
+ rcrtc->lif_index);
+ vsp1_du_wait_wb(rcrtc->vsp->vsp, 2, rcrtc->lif_index);
+
+ ret = rcar_du_async_commit(dev, crtc);
+ if (ret != 0)
+ return ret;
+
+ vsp1_du_wait_wb(rcrtc->vsp->vsp, 1, rcrtc->lif_index);
+
+ ret = rcar_du_async_commit(dev, crtc);
+ if (ret != 0)
+ return ret;
+
+ vsp1_du_wait_wb(rcrtc->vsp->vsp, 0, rcrtc->lif_index);
+
+ return ret;
+}
+
+int rcar_du_set_vmute(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct rcar_du_vmute *vmute =
+ (struct rcar_du_vmute *)data;
+ struct drm_mode_object *obj;
+ struct drm_crtc *crtc;
+ struct rcar_du_crtc *rcrtc;
+ int ret = 0;
+
+ dev_dbg(dev->dev, "CRTC[%d], display:%s\n",
+ vmute->crtc_id, vmute->on ? "off":"on");
+
+ obj = drm_mode_object_find(dev, vmute->crtc_id,
+ DRM_MODE_OBJECT_CRTC);
+ if (!obj)
+ return -EINVAL;
+
+ crtc = obj_to_crtc(obj);
+ rcrtc = to_rcar_crtc(crtc);
+
+ vsp1_du_if_set_mute(rcrtc->vsp->vsp, vmute->on, rcrtc->lif_index);
+
+ ret = rcar_du_async_commit(dev, crtc);
+
+ return ret;
+}
+
static const struct drm_plane_funcs rcar_du_vsp_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
@@ -378,6 +529,28 @@ static const struct drm_plane_funcs rcar_du_vsp_plane_funcs = {
.atomic_get_property = rcar_du_vsp_plane_atomic_get_property,
};
+static const uint32_t formats[] = {
+ DRM_FORMAT_RGB332,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_XRGB4444,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV61,
+};
+
int rcar_du_vsp_init(struct rcar_du_vsp *vsp)
{
struct rcar_du_device *rcdu = vsp->dev;
@@ -385,6 +558,7 @@ int rcar_du_vsp_init(struct rcar_du_vsp *vsp)
struct device_node *np;
unsigned int i;
int ret;
+ unsigned long possible_crtcs, share_num;
/* Find the VSP device and initialize it. */
np = of_parse_phandle(rcdu->dev->of_node, "vsps", vsp->index);
@@ -393,6 +567,8 @@ int rcar_du_vsp_init(struct rcar_du_vsp *vsp)
return -ENXIO;
}
+ of_property_read_u32(np, "renesas,#brs", &vsp->num_brs);
+
pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pdev)
@@ -407,23 +583,41 @@ int rcar_du_vsp_init(struct rcar_du_vsp *vsp)
/* The VSP2D (Gen3) has 5 RPFs, but the VSP1D (Gen2) is limited to
* 4 RPFs.
*/
- vsp->num_planes = rcdu->info->gen >= 3 ? 5 : 4;
+ vsp->num_planes = rcdu->info->vsp_num;
vsp->planes = devm_kcalloc(rcdu->dev, vsp->num_planes,
sizeof(*vsp->planes), GFP_KERNEL);
if (!vsp->planes)
return -ENOMEM;
+ share_num = vsp->num_planes - vsp->num_brs;
+
for (i = 0; i < vsp->num_planes; ++i) {
- enum drm_plane_type type = i ? DRM_PLANE_TYPE_OVERLAY
- : DRM_PLANE_TYPE_PRIMARY;
struct rcar_du_vsp_plane *plane = &vsp->planes[i];
+ enum drm_plane_type type;
+
+ if ((rcar_du_has(rcdu, RCAR_DU_FEATURE_VSPDL_SOURCE)) &&
+ (vsp->index == 0)) {
+
+ if (i < share_num)
+ possible_crtcs = 1 << vsp->index;
+ else
+ possible_crtcs =
+ 1 << rcdu->info->vspdl_pair_ch;
+
+ type = (i == 0) || (i == share_num) ?
+ DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY;
+ } else {
+ possible_crtcs = 1 << vsp->index;
+ type = i ? DRM_PLANE_TYPE_OVERLAY
+ : DRM_PLANE_TYPE_PRIMARY;
+ }
plane->vsp = vsp;
plane->index = i;
ret = drm_universal_plane_init(rcdu->ddev, &plane->plane,
- 1 << vsp->index,
+ possible_crtcs,
&rcar_du_vsp_plane_funcs,
formats_kms,
ARRAY_SIZE(formats_kms), type,
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vsp.h b/drivers/gpu/drm/rcar-du/rcar_du_vsp.h
index bbb4161..3fd9cef 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_vsp.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_vsp.h
@@ -1,7 +1,7 @@
/*
* rcar_du_vsp.h -- R-Car Display Unit VSP-Based Compositor
*
- * Copyright (C) 2015 Renesas Electronics Corporation
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -32,6 +32,7 @@ struct rcar_du_vsp {
struct rcar_du_device *dev;
struct rcar_du_vsp_plane *planes;
unsigned int num_planes;
+ unsigned int num_brs;
};
static inline struct rcar_du_vsp_plane *to_rcar_vsp_plane(struct drm_plane *p)
@@ -69,12 +70,20 @@ void rcar_du_vsp_enable(struct rcar_du_crtc *crtc);
void rcar_du_vsp_disable(struct rcar_du_crtc *crtc);
void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc);
void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc);
+int rcar_du_set_vmute(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+int rcar_du_vsp_write_back(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
#else
static inline int rcar_du_vsp_init(struct rcar_du_vsp *vsp) { return 0; };
static inline void rcar_du_vsp_enable(struct rcar_du_crtc *crtc) { };
static inline void rcar_du_vsp_disable(struct rcar_du_crtc *crtc) { };
static inline void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc) { };
static inline void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc) { };
+static inline int rcar_du_set_vmute(struct drm_device *dev, void *data,
+ struct drm_file *file_priv) { return 0; };
+static inline int rcar_du_vsp_write_back(struct drm_device *dev, void *data,
+ struct drm_file *file_priv) { return 0; };
#endif
#endif /* __RCAR_DU_VSP_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h b/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h
index d7d294b..3c019d1 100644
--- a/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h
+++ b/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h
@@ -1,7 +1,7 @@
/*
* rcar_lvds_regs.h -- R-Car LVDS Interface Registers Definitions
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -76,4 +76,8 @@
#define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4))
#define LVDCHCR_CHSEL_MASK(n) (3 << ((n) * 4))
+#define SRCR7 0xE61501CC
+#define SRSTCLR7 0xE615095C
+#define SRCR7_LVDS (1 << 27)
+
#endif /* __RCAR_LVDS_REGS_H__ */
diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig
index 73a4016..f0c0a35 100644
--- a/drivers/hwspinlock/Kconfig
+++ b/drivers/hwspinlock/Kconfig
@@ -53,4 +53,17 @@
If unsure, say N.
+config HWSPINLOCK_RCAR
+ bool "R-Car Hardware Spinlock functionality"
+ depends on ARCH_RENESAS
+ select HWSPINLOCK
+ default y
+ help
+ Say y here to support the R-Car Hardware Spinlock functionality, which
+ provides a synchronisation mechanism for the various processor on the
+ SoC.
+ This function is implemented with MFIS device.
+
+ If unsure, say N.
+
endmenu
diff --git a/drivers/hwspinlock/Makefile b/drivers/hwspinlock/Makefile
index 6b59cb5a..4ee4001 100644
--- a/drivers/hwspinlock/Makefile
+++ b/drivers/hwspinlock/Makefile
@@ -6,4 +6,5 @@
obj-$(CONFIG_HWSPINLOCK_OMAP) += omap_hwspinlock.o
obj-$(CONFIG_HWSPINLOCK_QCOM) += qcom_hwspinlock.o
obj-$(CONFIG_HWSPINLOCK_SIRF) += sirf_hwspinlock.o
+obj-$(CONFIG_HWSPINLOCK_RCAR) += rcar_hwspinlock.o
obj-$(CONFIG_HSEM_U8500) += u8500_hsem.o
diff --git a/drivers/hwspinlock/rcar_hwspinlock.c b/drivers/hwspinlock/rcar_hwspinlock.c
new file mode 100644
index 0000000..35ba8c1
--- /dev/null
+++ b/drivers/hwspinlock/rcar_hwspinlock.c
@@ -0,0 +1,156 @@
+/*
+ * rcar_hwspinlock.c
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/hwspinlock.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#include "hwspinlock_internal.h"
+
+#define RCAR_HWSPINLOCK_NUM (8)
+
+static int rcar_hwspinlock_trylock(struct hwspinlock *lock)
+{
+ void __iomem *addr = lock->priv;
+
+ return !ioread32(addr);
+}
+
+static void rcar_hwspinlock_unlock(struct hwspinlock *lock)
+{
+ void __iomem *addr = lock->priv;
+
+ iowrite32(0, addr);
+}
+
+static const struct hwspinlock_ops rcar_hwspinlock_ops = {
+ .trylock = rcar_hwspinlock_trylock,
+ .unlock = rcar_hwspinlock_unlock,
+};
+
+static const struct of_device_id rcar_hwspinlock_of_match[] = {
+ { .compatible = "renesas,mfis-lock" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rcar_hwspinlock_of_match);
+
+static int rcar_hwspinlock_probe(struct platform_device *pdev)
+{
+ int idx;
+ int ret = 0;
+ u32 __iomem *addr;
+ struct hwspinlock_device *bank;
+ struct hwspinlock *lock;
+ struct resource *res = NULL;
+ struct clk *clock;
+
+ /* enable MFIS clock */
+ clock = of_clk_get(pdev->dev.of_node, 0);
+ if (!clock) {
+ dev_err(&pdev->dev, "Failed to get clock.\n");
+ ret = PTR_ERR(clock);
+ goto out;
+ }
+ clk_prepare_enable(clock);
+
+ pm_runtime_enable(&pdev->dev);
+
+ /* map MFIS register */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ addr = (u32 __iomem *)devm_ioremap_nocache(&pdev->dev,
+ res->start, resource_size(res));
+ if (IS_ERR(addr)) {
+ dev_err(&pdev->dev, "Failed to remap MFIS Lock register.\n");
+ ret = PTR_ERR(addr);
+ goto clk_disable;
+ }
+
+ /* create hwspinlock control info */
+ bank = devm_kzalloc(&pdev->dev,
+ sizeof(*bank) + sizeof(*lock) * RCAR_HWSPINLOCK_NUM,
+ GFP_KERNEL);
+ if (!bank) {
+ dev_err(&pdev->dev, "Failed to allocate memory.\n");
+ ret = PTR_ERR(bank);
+ goto clk_disable;
+ }
+
+ for (idx = 0; idx < RCAR_HWSPINLOCK_NUM; idx++) {
+ lock = &bank->lock[idx];
+ lock->priv = &addr[idx];
+ }
+ platform_set_drvdata(pdev, bank);
+
+ /* register hwspinlock */
+ ret = hwspin_lock_register(bank, &pdev->dev, &rcar_hwspinlock_ops,
+ 0, RCAR_HWSPINLOCK_NUM);
+ if (!ret)
+ goto out;
+
+clk_disable:
+ if (clock)
+ clk_disable_unprepare(clock);
+
+out:
+ return ret;
+}
+
+static int rcar_hwspinlock_remove(struct platform_device *pdev)
+{
+ int ret;
+ struct clk *clock = NULL;
+
+ ret = hwspin_lock_unregister(platform_get_drvdata(pdev));
+ if (ret) {
+ dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ pm_runtime_disable(&pdev->dev);
+ clock = of_clk_get(pdev->dev.of_node, 0);
+ if (clock)
+ clk_disable_unprepare(clock);
+
+ return 0;
+}
+
+static struct platform_driver rcar_hwspinlock_driver = {
+ .probe = rcar_hwspinlock_probe,
+ .remove = rcar_hwspinlock_remove,
+ .driver = {
+ .name = "rcar_hwspinlock",
+ .of_match_table = rcar_hwspinlock_of_match,
+ },
+};
+
+static int __init rcar_hwspinlock_init(void)
+{
+ return platform_driver_register(&rcar_hwspinlock_driver);
+}
+core_initcall(rcar_hwspinlock_init);
+
+static void __exit rcar_hwspinlock_exit(void)
+{
+ platform_driver_unregister(&rcar_hwspinlock_driver);
+}
+module_exit(rcar_hwspinlock_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c
index 726615e..95c5085 100644
--- a/drivers/i2c/busses/i2c-rcar.c
+++ b/drivers/i2c/busses/i2c-rcar.c
@@ -33,6 +33,7 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
/* register offsets */
#define ICSCR 0x00 /* slave ctrl */
@@ -147,6 +148,240 @@ struct rcar_i2c_priv {
#define LOOP_TIMEOUT 1024
+#ifdef CONFIG_RCAR_DDR_BACKUP
+static struct hw_register i2c0_ip_regs[] = {
+ {"ICSCR", 0x00, 32, 0},
+ {"ICMCR", 0x04, 32, 0},
+ {"ICSIER", 0x10, 32, 0},
+ {"ICMIER", 0x14, 32, 0},
+ {"ICCCR", 0x18, 32, 0},
+ {"ICSAR", 0x1C, 32, 0},
+ {"ICMAR", 0x20, 32, 0},
+ {"ICCCR2", 0x28, 32, 0},
+ {"ICMPR", 0x2C, 32, 0},
+ {"ICHPR", 0x30, 32, 0},
+ {"ICLPR", 0x34, 32, 0},
+ {"ICDMAER", 0x3C, 32, 0},
+ {"ICFBSCR", 0x38, 32, 0},
+};
+
+static struct rcar_ip i2c0_ip = {
+ .ip_name = "I2C0",
+ .base_addr = 0xE6500000,
+ .size = 0x40,
+ .reg_count = ARRAY_SIZE(i2c0_ip_regs),
+ .ip_reg = i2c0_ip_regs,
+};
+
+static struct hw_register i2c1_ip_regs[] = {
+ {"ICSCR", 0x00, 32, 0},
+ {"ICMCR", 0x04, 32, 0},
+ {"ICSIER", 0x10, 32, 0},
+ {"ICMIER", 0x14, 32, 0},
+ {"ICCCR", 0x18, 32, 0},
+ {"ICSAR", 0x1C, 32, 0},
+ {"ICMAR", 0x20, 32, 0},
+ {"ICCCR2", 0x28, 32, 0},
+ {"ICMPR", 0x2C, 32, 0},
+ {"ICHPR", 0x30, 32, 0},
+ {"ICLPR", 0x34, 32, 0},
+ {"ICDMAER", 0x3C, 32, 0},
+ {"ICFBSCR", 0x38, 32, 0},
+};
+
+static struct rcar_ip i2c1_ip = {
+ .ip_name = "I2C1",
+ .base_addr = 0xE6508000,
+ .size = 0x40,
+ .reg_count = ARRAY_SIZE(i2c1_ip_regs),
+ .ip_reg = i2c1_ip_regs,
+};
+
+static struct hw_register i2c2_ip_regs[] = {
+ {"ICSCR", 0x00, 32, 0},
+ {"ICMCR", 0x04, 32, 0},
+ {"ICSIER", 0x10, 32, 0},
+ {"ICMIER", 0x14, 32, 0},
+ {"ICCCR", 0x18, 32, 0},
+ {"ICSAR", 0x1C, 32, 0},
+ {"ICMAR", 0x20, 32, 0},
+ {"ICCCR2", 0x28, 32, 0},
+ {"ICMPR", 0x2C, 32, 0},
+ {"ICHPR", 0x30, 32, 0},
+ {"ICLPR", 0x34, 32, 0},
+ {"ICDMAER", 0x3C, 32, 0},
+ {"ICFBSCR", 0x38, 32, 0},
+};
+
+static struct rcar_ip i2c2_ip = {
+ .ip_name = "I2C2",
+ .base_addr = 0xE6510000,
+ .size = 0x40,
+ .reg_count = ARRAY_SIZE(i2c2_ip_regs),
+ .ip_reg = i2c2_ip_regs,
+};
+
+static struct hw_register i2c3_ip_regs[] = {
+ {"ICSCR", 0x00, 32, 0},
+ {"ICMCR", 0x04, 32, 0},
+ {"ICSIER", 0x10, 32, 0},
+ {"ICMIER", 0x14, 32, 0},
+ {"ICCCR", 0x18, 32, 0},
+ {"ICSAR", 0x1C, 32, 0},
+ {"ICMAR", 0x20, 32, 0},
+ {"ICCCR2", 0x28, 32, 0},
+ {"ICMPR", 0x2C, 32, 0},
+ {"ICHPR", 0x30, 32, 0},
+ {"ICLPR", 0x34, 32, 0},
+ {"ICDMAER", 0x3C, 32, 0},
+ {"ICFBSCR", 0x38, 32, 0},
+};
+
+static struct rcar_ip i2c3_ip = {
+ .ip_name = "I2C3",
+ .base_addr = 0xE66D0000,
+ .size = 0x40,
+ .reg_count = ARRAY_SIZE(i2c3_ip_regs),
+ .ip_reg = i2c3_ip_regs,
+};
+
+static struct hw_register i2c4_ip_regs[] = {
+ {"ICSCR", 0x00, 32, 0},
+ {"ICMCR", 0x04, 32, 0},
+ {"ICSIER", 0x10, 32, 0},
+ {"ICMIER", 0x14, 32, 0},
+ {"ICCCR", 0x18, 32, 0},
+ {"ICSAR", 0x1C, 32, 0},
+ {"ICMAR", 0x20, 32, 0},
+ {"ICCCR2", 0x28, 32, 0},
+ {"ICMPR", 0x2C, 32, 0},
+ {"ICHPR", 0x30, 32, 0},
+ {"ICLPR", 0x34, 32, 0},
+ {"ICDMAER", 0x3C, 32, 0},
+ {"ICFBSCR", 0x38, 32, 0},
+};
+
+static struct rcar_ip i2c4_ip = {
+ .ip_name = "I2C4",
+ .base_addr = 0xE66D8000,
+ .size = 0x40,
+ .reg_count = ARRAY_SIZE(i2c4_ip_regs),
+ .ip_reg = i2c4_ip_regs,
+};
+
+static struct hw_register i2c5_ip_regs[] = {
+ {"ICSCR", 0x00, 32, 0},
+ {"ICMCR", 0x04, 32, 0},
+ {"ICSIER", 0x10, 32, 0},
+ {"ICMIER", 0x14, 32, 0},
+ {"ICCCR", 0x18, 32, 0},
+ {"ICSAR", 0x1C, 32, 0},
+ {"ICMAR", 0x20, 32, 0},
+ {"ICCCR2", 0x28, 32, 0},
+ {"ICMPR", 0x2C, 32, 0},
+ {"ICHPR", 0x30, 32, 0},
+ {"ICLPR", 0x34, 32, 0},
+ {"ICDMAER", 0x3C, 32, 0},
+ {"ICFBSCR", 0x38, 32, 0},
+};
+
+static struct rcar_ip i2c5_ip = {
+ .ip_name = "I2C5",
+ .base_addr = 0xE66E0000,
+ .size = 0x40,
+ .reg_count = ARRAY_SIZE(i2c5_ip_regs),
+ .ip_reg = i2c5_ip_regs,
+};
+
+static struct hw_register i2c6_ip_regs[] = {
+ {"ICSCR", 0x00, 32, 0},
+ {"ICMCR", 0x04, 32, 0},
+ {"ICSIER", 0x10, 32, 0},
+ {"ICMIER", 0x14, 32, 0},
+ {"ICCCR", 0x18, 32, 0},
+ {"ICSAR", 0x1C, 32, 0},
+ {"ICMAR", 0x20, 32, 0},
+ {"ICCCR2", 0x28, 32, 0},
+ {"ICMPR", 0x2C, 32, 0},
+ {"ICHPR", 0x30, 32, 0},
+ {"ICLPR", 0x34, 32, 0},
+ {"ICDMAER", 0x3C, 32, 0},
+ {"ICFBSCR", 0x38, 32, 0},
+};
+
+static struct rcar_ip i2c6_ip = {
+ .ip_name = "I2C6",
+ .base_addr = 0xE66E8000,
+ .size = 0x40,
+ .reg_count = ARRAY_SIZE(i2c6_ip_regs),
+ .ip_reg = i2c6_ip_regs,
+};
+
+struct i2c_ip_info {
+ const char *name;
+ struct rcar_ip *ip;
+};
+
+static struct i2c_ip_info ip_info_tbl[] = {
+ {"e6500000.i2c", &i2c0_ip},
+ {"e6508000.i2c", &i2c1_ip},
+ {"e6510000.i2c", &i2c2_ip},
+ {"e66d0000.i2c", &i2c3_ip},
+ {"e66d8000.i2c", &i2c4_ip},
+ {"e66e0000.i2c", &i2c5_ip},
+ {"e66e8000.i2c", &i2c6_ip},
+ {NULL, NULL},
+};
+
+static struct rcar_ip *rcar_i2c_get_ip(const char *name)
+{
+ struct i2c_ip_info *ip_info = ip_info_tbl;
+ struct rcar_ip *ip = NULL;
+
+ while (ip_info->name) {
+ if (!strcmp(ip_info->name, name)) {
+ ip = ip_info->ip;
+ break;
+ }
+ ip_info++;
+ }
+
+ return ip;
+}
+
+static int rcar_i2c_save_regs(struct platform_device *pdev)
+{
+ struct rcar_ip *ip = rcar_i2c_get_ip(pdev->name);
+ int ret = -ENODEV;
+
+ if (ip) {
+ struct rcar_i2c_priv *priv = platform_get_drvdata(pdev);
+
+ if (!ip->virt_addr)
+ ip->virt_addr = priv->io;
+
+ ret = rcar_handle_registers(ip, DO_BACKUP);
+ pr_debug("%s: Backup %s register\n", __func__, ip->ip_name);
+ } else
+ pr_err("%s: Failed to find i2c-rcar\n", __func__);
+
+ return ret;
+}
+
+static int rcar_i2c_restore_regs(struct platform_device *pdev)
+{
+ struct rcar_ip *ip = rcar_i2c_get_ip(pdev->name);
+ int ret = -ENODEV;
+
+ if (ip) {
+ ret = rcar_handle_registers(ip, DO_RESTORE);
+ pr_debug("%s: Restore %s register\n", __func__, ip->ip_name);
+ } else
+ pr_err("%s: Failed to find i2c-rcar\n", __func__);
+
+ return ret;
+}
+#endif /* CONFIG_RCAR_DDR_BACKUP */
static void rcar_i2c_write(struct rcar_i2c_priv *priv, int reg, u32 val)
{
@@ -904,9 +1139,47 @@ static int rcar_i2c_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int rcar_i2c_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct platform_device *pdev = to_platform_device(dev);
+
+ pm_runtime_get_sync(dev);
+ ret = rcar_i2c_save_regs(pdev);
+ pm_runtime_put(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static int rcar_i2c_resume(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct platform_device *pdev = to_platform_device(dev);
+
+ pm_runtime_get_sync(dev);
+ ret = rcar_i2c_restore_regs(pdev);
+ pm_runtime_put(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static const struct dev_pm_ops rcar_i2c_pm_ops = {
+ .suspend = rcar_i2c_suspend,
+ .resume_early = rcar_i2c_resume,
+};
+
+#define DEV_PM_OPS (&rcar_i2c_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver rcar_i2c_driver = {
.driver = {
.name = "i2c-rcar",
+ .pm = DEV_PM_OPS,
.of_match_table = rcar_i2c_dt_ids,
},
.probe = rcar_i2c_probe,
diff --git a/drivers/i2c/busses/i2c-sh_mobile.c b/drivers/i2c/busses/i2c-sh_mobile.c
index 192f36f0..df33426 100644
--- a/drivers/i2c/busses/i2c-sh_mobile.c
+++ b/drivers/i2c/busses/i2c-sh_mobile.c
@@ -34,6 +34,7 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
/* Transmit operation: */
/* */
@@ -153,6 +154,26 @@ struct sh_mobile_dt_config {
void (*setup)(struct sh_mobile_i2c_data *pd);
};
+#ifdef CONFIG_RCAR_DDR_BACKUP
+static struct hw_register i2c_dvfs_ip_regs[] = {
+ {"ICDR", 0x00, 8, 0},
+ {"ICCR", 0x04, 8, 0},
+ {"ICIC", 0x0C, 8, 0},
+ {"ICCL", 0x10, 8, 0},
+ {"ICCH", 0x14, 8, 0},
+ {"ICTC", 0x28, 8, 0},
+ {"ICVCON", 0x6C, 8, 0},
+};
+
+static struct rcar_ip i2c_dvfs_ip = {
+ .ip_name = "I2C_DVFS",
+ .base_addr = 0xE60B0000,
+ .size = 0x70,
+ .reg_count = ARRAY_SIZE(i2c_dvfs_ip_regs),
+ .ip_reg = i2c_dvfs_ip_regs,
+};
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+
#define IIC_FLAG_HAS_ICIC67 (1 << 0)
#define STANDARD_MODE 100000
@@ -836,6 +857,7 @@ static const struct of_device_id sh_mobile_i2c_dt_ids[] = {
{ .compatible = "renesas,iic-r8a7793", .data = &fast_clock_dt_config },
{ .compatible = "renesas,iic-r8a7794", .data = &fast_clock_dt_config },
{ .compatible = "renesas,iic-r8a7795", .data = &fast_clock_dt_config },
+ { .compatible = "renesas,iic-r8a7796", .data = &fast_clock_dt_config },
{ .compatible = "renesas,iic-sh73a0", .data = &fast_clock_dt_config },
{},
};
@@ -1011,9 +1033,42 @@ static int sh_mobile_i2c_runtime_nop(struct device *dev)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int sh_mobile_i2c_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct sh_mobile_i2c_data *i2c_data = dev_get_drvdata(dev);
+
+ if (!i2c_dvfs_ip.virt_addr)
+ i2c_dvfs_ip.virt_addr = i2c_data->reg;
+ pm_runtime_get_sync(dev);
+ ret = rcar_handle_registers(&i2c_dvfs_ip, DO_BACKUP);
+ pm_runtime_put(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static int sh_mobile_i2c_resume(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ pm_runtime_get_sync(dev);
+ ret = rcar_handle_registers(&i2c_dvfs_ip, DO_RESTORE);
+ pm_runtime_put(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+#else
+#define sh_mobile_i2c_suspend NULL
+#define sh_mobile_i2c_resume NULL
+#endif /* CONFIG_PM_SLEEP */
+
static const struct dev_pm_ops sh_mobile_i2c_dev_pm_ops = {
- .runtime_suspend = sh_mobile_i2c_runtime_nop,
- .runtime_resume = sh_mobile_i2c_runtime_nop,
+ SET_SYSTEM_SLEEP_PM_OPS(sh_mobile_i2c_suspend, sh_mobile_i2c_resume)
+ SET_RUNTIME_PM_OPS(sh_mobile_i2c_runtime_nop, sh_mobile_i2c_runtime_nop,
+ NULL)
};
static struct platform_driver sh_mobile_i2c_driver = {
@@ -1030,7 +1085,7 @@ static int __init sh_mobile_i2c_adap_init(void)
{
return platform_driver_register(&sh_mobile_i2c_driver);
}
-subsys_initcall(sh_mobile_i2c_adap_init);
+subsys_initcall_sync(sh_mobile_i2c_adap_init);
static void __exit sh_mobile_i2c_adap_exit(void)
{
diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c
index 2b92116..2f516ea 100644
--- a/drivers/iommu/ipmmu-vmsa.c
+++ b/drivers/iommu/ipmmu-vmsa.c
@@ -41,6 +41,14 @@ struct ipmmu_features {
bool twobit_imttbcr_sl0;
};
+#ifdef CONFIG_RCAR_DDR_BACKUP
+struct hw_register {
+ char *reg_name;
+ unsigned int reg_offset;
+ unsigned int reg_data;
+};
+#endif
+
struct ipmmu_vmsa_device {
struct device *dev;
void __iomem *base;
@@ -52,6 +60,9 @@ struct ipmmu_vmsa_device {
spinlock_t lock; /* Protects ctx and domains[] */
DECLARE_BITMAP(ctx, IPMMU_CTX_MAX);
struct ipmmu_vmsa_domain *domains[IPMMU_CTX_MAX];
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct hw_register *reg_backup[IPMMU_CTX_MAX];
+#endif
struct dma_iommu_mapping *mapping;
};
@@ -74,6 +85,10 @@ struct ipmmu_vmsa_archdata {
unsigned int num_utlbs;
struct device *dev;
struct list_head list;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ unsigned int *utlbs_val;
+ unsigned int *asids_val;
+#endif
};
static DEFINE_SPINLOCK(ipmmu_devices_lock);
@@ -215,6 +230,7 @@ static void set_archdata(struct device *dev, struct ipmmu_vmsa_archdata *p)
#define IMPMBD(n) (0x02c0 + ((n) * 4))
#define IMUCTR(n) (0x0300 + ((n) * 16))
+#define IMUCTR2(n) (0x0600 + ((n) * 16))
#define IMUCTR_FIXADDEN (1 << 31)
#define IMUCTR_FIXADD_MASK (0xff << 16)
#define IMUCTR_FIXADD_SHIFT 16
@@ -225,11 +241,114 @@ static void set_archdata(struct device *dev, struct ipmmu_vmsa_archdata *p)
#define IMUCTR_MMUEN (1 << 0)
#define IMUASID(n) (0x0308 + ((n) * 16))
+#define IMUASID2(n) (0x0608 + ((n) * 16))
#define IMUASID_ASID8_MASK (0xff << 8)
#define IMUASID_ASID8_SHIFT 8
#define IMUASID_ASID0_MASK (0xff << 0)
#define IMUASID_ASID0_SHIFT 0
+#ifdef CONFIG_RCAR_DDR_BACKUP
+#define HW_REGISTER_BACKUP_SIZE ARRAY_SIZE(root_pgtable0_reg)
+static struct hw_register root_pgtable0_reg[] = {
+ {"IMTTLBR0", IMTTLBR0, 0},
+ {"IMTTUBR0", IMTTUBR0, 0},
+ {"IMTTBCR", IMTTBCR, 0},
+ {"IMTTLBR1", IMTTLBR1, 0},
+ {"IMTTUBR1", IMTTUBR1, 0},
+ {"IMMAIR0", IMMAIR0, 0},
+ {"IMMAIR1", IMMAIR1, 0},
+ {"IMCTR", IMCTR, 0},
+};
+
+static struct hw_register root_pgtable1_reg[] = {
+ {"IMTTLBR0", IMTTLBR0, 0},
+ {"IMTTUBR0", IMTTUBR0, 0},
+ {"IMTTBCR", IMTTBCR, 0},
+ {"IMTTLBR1", IMTTLBR1, 0},
+ {"IMTTUBR1", IMTTUBR1, 0},
+ {"IMMAIR0", IMMAIR0, 0},
+ {"IMMAIR1", IMMAIR1, 0},
+ {"IMCTR", IMCTR, 0},
+};
+
+static struct hw_register root_pgtable2_reg[] = {
+ {"IMTTLBR0", IMTTLBR0, 0},
+ {"IMTTUBR0", IMTTUBR0, 0},
+ {"IMTTBCR", IMTTBCR, 0},
+ {"IMTTLBR1", IMTTLBR1, 0},
+ {"IMTTUBR1", IMTTUBR1, 0},
+ {"IMMAIR0", IMMAIR0, 0},
+ {"IMMAIR1", IMMAIR1, 0},
+ {"IMCTR", IMCTR, 0},
+};
+
+static struct hw_register root_pgtable3_reg[] = {
+ {"IMTTLBR0", IMTTLBR0, 0},
+ {"IMTTUBR0", IMTTUBR0, 0},
+ {"IMTTBCR", IMTTBCR, 0},
+ {"IMTTLBR1", IMTTLBR1, 0},
+ {"IMTTUBR1", IMTTUBR1, 0},
+ {"IMMAIR0", IMMAIR0, 0},
+ {"IMMAIR1", IMMAIR1, 0},
+ {"IMCTR", IMCTR, 0},
+};
+
+static struct hw_register root_pgtable4_reg[] = {
+ {"IMTTLBR0", IMTTLBR0, 0},
+ {"IMTTUBR0", IMTTUBR0, 0},
+ {"IMTTBCR", IMTTBCR, 0},
+ {"IMTTLBR1", IMTTLBR1, 0},
+ {"IMTTUBR1", IMTTUBR1, 0},
+ {"IMMAIR0", IMMAIR0, 0},
+ {"IMMAIR1", IMMAIR1, 0},
+ {"IMCTR", IMCTR, 0},
+};
+
+static struct hw_register root_pgtable5_reg[] = {
+ {"IMTTLBR0", IMTTLBR0, 0},
+ {"IMTTUBR0", IMTTUBR0, 0},
+ {"IMTTBCR", IMTTBCR, 0},
+ {"IMTTLBR1", IMTTLBR1, 0},
+ {"IMTTUBR1", IMTTUBR1, 0},
+ {"IMMAIR0", IMMAIR0, 0},
+ {"IMMAIR1", IMMAIR1, 0},
+ {"IMCTR", IMCTR, 0},
+};
+
+static struct hw_register root_pgtable6_reg[] = {
+ {"IMTTLBR0", IMTTLBR0, 0},
+ {"IMTTUBR0", IMTTUBR0, 0},
+ {"IMTTBCR", IMTTBCR, 0},
+ {"IMTTLBR1", IMTTLBR1, 0},
+ {"IMTTUBR1", IMTTUBR1, 0},
+ {"IMMAIR0", IMMAIR0, 0},
+ {"IMMAIR1", IMMAIR1, 0},
+ {"IMCTR", IMCTR, 0},
+};
+
+static struct hw_register root_pgtable7_reg[] = {
+ {"IMTTLBR0", IMTTLBR0, 0},
+ {"IMTTUBR0", IMTTUBR0, 0},
+ {"IMTTBCR", IMTTBCR, 0},
+ {"IMTTLBR1", IMTTLBR1, 0},
+ {"IMTTUBR1", IMTTUBR1, 0},
+ {"IMMAIR0", IMMAIR0, 0},
+ {"IMMAIR1", IMMAIR1, 0},
+ {"IMCTR", IMCTR, 0},
+};
+
+static struct hw_register *root_pgtable[IPMMU_CTX_MAX] = {
+ root_pgtable0_reg,
+ root_pgtable1_reg,
+ root_pgtable2_reg,
+ root_pgtable3_reg,
+ root_pgtable4_reg,
+ root_pgtable5_reg,
+ root_pgtable6_reg,
+ root_pgtable7_reg,
+};
+
+#endif
/* -----------------------------------------------------------------------------
* Root device handling
*/
@@ -334,6 +453,7 @@ static void ipmmu_utlb_enable(struct ipmmu_vmsa_domain *domain,
unsigned int utlb)
{
struct ipmmu_vmsa_device *mmu = domain->mmu;
+ unsigned int offset;
/*
* TODO: Reference-count the microTLB as several bus masters can be
@@ -341,9 +461,12 @@ static void ipmmu_utlb_enable(struct ipmmu_vmsa_domain *domain,
*/
/* TODO: What should we set the ASID to ? */
- ipmmu_write(mmu, IMUASID(utlb), 0);
+ offset = (utlb < 32) ? IMUASID(utlb) : IMUASID2(utlb - 32);
+ ipmmu_write(mmu, offset, 0);
+
/* TODO: Do we need to flush the microTLB ? */
- ipmmu_write(mmu, IMUCTR(utlb),
+ offset = (utlb < 32) ? IMUCTR(utlb) : IMUCTR2(utlb - 32);
+ ipmmu_write(mmu, offset,
IMUCTR_TTSEL_MMU(domain->context_id) | IMUCTR_FLUSH |
IMUCTR_MMUEN);
}
@@ -355,8 +478,10 @@ static void ipmmu_utlb_disable(struct ipmmu_vmsa_domain *domain,
unsigned int utlb)
{
struct ipmmu_vmsa_device *mmu = domain->mmu;
+ unsigned int offset;
- ipmmu_write(mmu, IMUCTR(utlb), 0);
+ offset = (utlb < 32) ? IMUCTR(utlb) : IMUCTR2(utlb - 32);
+ ipmmu_write(mmu, offset, 0);
}
static void ipmmu_tlb_flush_all(void *cookie)
@@ -445,6 +570,9 @@ static int ipmmu_domain_init_context(struct ipmmu_vmsa_domain *domain)
}
domain->context_id = ret;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ domain->root->reg_backup[ret] = root_pgtable[ret];
+#endif
/* TTBR0 */
ttbr = domain->cfg.arm_lpae_s1_cfg.ttbr[0];
@@ -516,6 +644,11 @@ static void ipmmu_domain_destroy_context(struct ipmmu_vmsa_domain *domain)
*/
ipmmu_ctx_write2(domain, IMCTR, IMCTR_FLUSH);
ipmmu_tlb_sync(domain);
+
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ domain->root->reg_backup[domain->context_id] = NULL;
+#endif
+
ipmmu_domain_free_context(domain->root, domain->context_id);
}
@@ -625,6 +758,95 @@ static void ipmmu_domain_free(struct iommu_domain *io_domain)
kfree(domain);
}
+/* Hack to identity map address ranges needed by the DMAC hardware */
+static struct {
+ phys_addr_t base;
+ size_t size;
+} dmac_workaround[] = {
+ {
+ .base = 0xe6e60000, /* SCIF0 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6e68000, /* SCIF1 */
+ .size = SZ_4K,
+ },
+ /* DMA transfer of SCIF2 is not supported */
+ {
+ .base = 0xe6c50000, /* SCIF3 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6c40000, /* SCIF4 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6f30000, /* SCIF5 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6540000, /* HSCIF0 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6550000, /* HSCIF1 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6560000, /* HSCIF2 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe66a0000, /* HSCIF3 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe66b0000, /* HSCIF4 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6e90000, /* MSIOF0 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6ea0000, /* MSIOF1 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6c00000, /* MSIOF2 */
+ .size = SZ_4K,
+ },
+ {
+ .base = 0xe6c10000, /* MSIOF3 */
+ .size = SZ_4K,
+ },
+};
+
+static void ipmmu_workaround(struct iommu_domain *io_domain,
+ struct device *dev)
+{
+ int k;
+
+ /* only apply workaround for DMA controllers */
+ if (!strstr(dev_name(dev), "dma-controller"))
+ return;
+
+ dev_info(dev, "Adding iommu workaround\n");
+
+ for (k = 0; k < ARRAY_SIZE(dmac_workaround); k++) {
+ phys_addr_t phys_addr;
+
+ phys_addr = iommu_iova_to_phys(io_domain,
+ dmac_workaround[k].base);
+ if (phys_addr)
+ continue;
+
+ iommu_map(io_domain, dmac_workaround[k].base,
+ dmac_workaround[k].base, dmac_workaround[k].size,
+ IOMMU_READ | IOMMU_WRITE);
+ }
+}
+
static int ipmmu_attach_device(struct iommu_domain *io_domain,
struct device *dev)
{
@@ -678,6 +900,8 @@ static int ipmmu_attach_device(struct iommu_domain *io_domain,
if (ret < 0)
return ret;
+ ipmmu_workaround(io_domain, dev);
+
for (i = 0; i < archdata->num_utlbs; ++i)
ipmmu_utlb_enable(domain, archdata->utlbs[i]);
@@ -794,6 +1018,9 @@ static int ipmmu_init_platform_device(struct device *dev)
struct ipmmu_vmsa_archdata *archdata;
struct ipmmu_vmsa_device *mmu;
unsigned int *utlbs;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ unsigned int *utlbs_val, *asids_val;
+#endif
unsigned int i;
int num_utlbs;
int ret = -ENODEV;
@@ -809,6 +1036,15 @@ static int ipmmu_init_platform_device(struct device *dev)
if (!utlbs)
return -ENOMEM;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ utlbs_val = kcalloc(num_utlbs, sizeof(*utlbs_val), GFP_KERNEL);
+ if (!utlbs_val)
+ return -ENOMEM;
+ asids_val = kcalloc(num_utlbs, sizeof(*asids_val), GFP_KERNEL);
+ if (!asids_val)
+ return -ENOMEM;
+#endif
+
spin_lock(&ipmmu_devices_lock);
list_for_each_entry(mmu, &ipmmu_devices, list) {
@@ -842,6 +1078,10 @@ static int ipmmu_init_platform_device(struct device *dev)
archdata->mmu = mmu;
archdata->utlbs = utlbs;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ archdata->utlbs_val = utlbs_val;
+ archdata->asids_val = asids_val;
+#endif
archdata->num_utlbs = num_utlbs;
archdata->dev = dev;
set_archdata(dev, archdata);
@@ -940,6 +1180,10 @@ static void ipmmu_remove_device(struct device *dev)
iommu_group_remove_device(dev);
kfree(archdata->utlbs);
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ kfree(archdata->utlbs_val);
+ kfree(archdata->asids_val);
+#endif
kfree(archdata);
set_archdata(dev, NULL);
@@ -1230,9 +1474,195 @@ static int ipmmu_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_RCAR_DDR_BACKUP
+static int ipmmu_utlbs_backup(struct ipmmu_vmsa_device *mmu)
+{
+ unsigned int i;
+ struct ipmmu_vmsa_device *slave_mmu = NULL;
+ struct ipmmu_vmsa_archdata *slave_dev = NULL;
+
+ pr_debug("%s: Handle UTLB backup\n", dev_name(mmu->dev));
+
+ spin_lock(&ipmmu_slave_devices_lock);
+
+ list_for_each_entry(slave_dev, &ipmmu_slave_devices, list) {
+ slave_mmu = slave_dev->mmu;
+
+ if (slave_mmu != mmu)
+ continue;
+
+ for (i = 0; i < slave_dev->num_utlbs; ++i) {
+ slave_dev->utlbs_val[i] =
+ ipmmu_read(slave_mmu,
+ IMUCTR(slave_dev->utlbs[i]));
+ slave_dev->asids_val[i] =
+ ipmmu_read(slave_mmu,
+ IMUASID(slave_dev->utlbs[i]));
+ pr_debug("%d: Backup UTLB[%d]: 0x%x, ASID[%d]: %d\n",
+ i, slave_dev->utlbs[i], slave_dev->utlbs_val[i],
+ slave_dev->utlbs[i],
+ slave_dev->asids_val[i]);
+ }
+ }
+
+ spin_unlock(&ipmmu_slave_devices_lock);
+
+ return 0;
+}
+
+static int ipmmu_utlbs_restore(struct ipmmu_vmsa_device *mmu)
+{
+ unsigned int i;
+ struct ipmmu_vmsa_device *slave_mmu = NULL;
+ struct ipmmu_vmsa_archdata *slave_dev = NULL;
+
+ pr_debug("%s: Handle UTLB restore\n", dev_name(mmu->dev));
+
+ spin_lock(&ipmmu_slave_devices_lock);
+
+ list_for_each_entry(slave_dev, &ipmmu_slave_devices, list) {
+ slave_mmu = slave_dev->mmu;
+
+ if (slave_mmu != mmu)
+ continue;
+
+ for (i = 0; i < slave_dev->num_utlbs; ++i) {
+ ipmmu_write(slave_mmu, IMUASID(slave_dev->utlbs[i]),
+ slave_dev->asids_val[i]);
+ ipmmu_write(slave_mmu,
+ IMUCTR(slave_dev->utlbs[i]),
+ (slave_dev->utlbs_val[i] | IMUCTR_FLUSH));
+ pr_debug("%d: Restore UTLB[%d]: 0x%x, ASID[%d]: %d\n",
+ i, slave_dev->utlbs[i],
+ ipmmu_read(slave_mmu,
+ IMUCTR(slave_dev->utlbs[i])),
+ slave_dev->utlbs[i],
+ ipmmu_read(slave_mmu,
+ IMUASID(slave_dev->utlbs[i])));
+ }
+ }
+
+ spin_unlock(&ipmmu_slave_devices_lock);
+
+ return 0;
+}
+
+static int ipmmu_domain_backup_context(struct ipmmu_vmsa_domain *domain)
+{
+ struct ipmmu_vmsa_device *mmu = domain->root;
+ struct hw_register *reg = mmu->reg_backup[domain->context_id];
+ unsigned int i;
+
+ pr_info("%s: Handle domain context backup\n", dev_name(mmu->dev));
+
+ for (i = 0; i < HW_REGISTER_BACKUP_SIZE; i++) {
+ reg[i].reg_data = ipmmu_ctx_read(domain, reg[i].reg_offset);
+
+ pr_info("%s: reg_data 0x%x, reg_offset 0x%x\n",
+ reg[i].reg_name,
+ reg[i].reg_data,
+ reg[i].reg_offset);
+ }
+
+ return 0;
+}
+
+static int ipmmu_domain_restore_context(struct ipmmu_vmsa_domain *domain)
+{
+ struct ipmmu_vmsa_device *mmu = domain->root;
+ struct hw_register *reg = mmu->reg_backup[domain->context_id];
+ unsigned int i;
+
+ pr_info("%s: Handle domain context restore\n", dev_name(mmu->dev));
+
+ for (i = 0; i < HW_REGISTER_BACKUP_SIZE; i++) {
+ if (reg[i].reg_offset != IMCTR) {
+ ipmmu_ctx_write(domain,
+ reg[i].reg_offset,
+ reg[i].reg_data);
+
+ pr_info("%s: reg_data 0x%x, reg_offset 0x%x\n",
+ reg[i].reg_name,
+ ipmmu_ctx_read(domain, reg[i].reg_offset),
+ reg[i].reg_offset);
+
+ } else {
+ ipmmu_ctx_write2(domain,
+ reg[i].reg_offset,
+ reg[i].reg_data | IMCTR_FLUSH);
+
+ pr_info("%s: reg_data 0x%x, reg_offset 0x%x\n",
+ reg[i].reg_name,
+ ipmmu_ctx_read(domain,
+ reg[i].reg_offset),
+ reg[i].reg_offset);
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static int ipmmu_suspend(struct device *dev)
+{
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ int ctx;
+ unsigned int i;
+ struct ipmmu_vmsa_device *mmu = dev_get_drvdata(dev);
+
+ pr_debug("%s: %s\n", __func__, dev_name(dev));
+
+ /* Only backup UTLB in IPMMU cache devices*/
+ if (!ipmmu_is_root(mmu))
+ ipmmu_utlbs_backup(mmu);
+
+ ctx = find_first_zero_bit(mmu->ctx, mmu->num_ctx);
+
+ for (i = 0; i < ctx; i++) {
+ pr_info("Handle ctx %d\n", i);
+ ipmmu_domain_backup_context(mmu->domains[i]);
+ }
+#endif
+
+ return 0;
+}
+
+static int ipmmu_resume(struct device *dev)
+{
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ int ctx;
+ unsigned int i;
+ struct ipmmu_vmsa_device *mmu = dev_get_drvdata(dev);
+
+ pr_debug("%s: %s\n", __func__, dev_name(dev));
+
+ ctx = find_first_zero_bit(mmu->ctx, mmu->num_ctx);
+
+ for (i = 0; i < ctx; i++) {
+ pr_info("Handle ctx %d\n", i);
+ ipmmu_domain_restore_context(mmu->domains[i]);
+ }
+
+ /* Only backup UTLB in IPMMU cache devices*/
+ if (!ipmmu_is_root(mmu))
+ ipmmu_utlbs_restore(mmu);
+#endif
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ipmmu_pm_ops,
+ ipmmu_suspend, ipmmu_resume);
+#define DEV_PM_OPS (&ipmmu_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver ipmmu_driver = {
.driver = {
.name = "ipmmu-vmsa",
+ .pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(ipmmu_of_ids),
},
.probe = ipmmu_probe,
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 2669b4b..51f89a7 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -204,6 +204,16 @@
To compile this driver as a module, choose M here: the
module will be called adv7183.
+config VIDEO_ADV7482
+ tristate "Analog Devices ADV7482 decoder"
+ depends on VIDEO_V4L2 && I2C
+ ---help---
+ V4l2 subdevice driver for the Analog Devices
+ ADV7482 video decoder.
+
+ To compile this driver as a module, choose M here: the
+ module will be called adv7482.
+
config VIDEO_ADV7604
tristate "Analog Devices ADV7604 decoder"
depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 92773b2..0140e60 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -26,6 +26,7 @@
obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o
obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o
obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o
+obj-$(CONFIG_VIDEO_ADV7482) += adv7482.o
obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o
obj-$(CONFIG_VIDEO_ADV7842) += adv7842.o
obj-$(CONFIG_VIDEO_AD9389B) += ad9389b.o
diff --git a/drivers/media/i2c/adv7482.c b/drivers/media/i2c/adv7482.c
new file mode 100644
index 0000000..f5a9311
--- /dev/null
+++ b/drivers/media/i2c/adv7482.c
@@ -0,0 +1,2414 @@
+/*
+ * drivers/media/i2c/adv7482.c
+ * This file is Analog Devices ADV7482 HDMI receiver driver.
+ *
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-of.h>
+#include <linux/mutex.h>
+#include <media/soc_camera.h>
+
+#include <linux/delay.h> /* for msleep() */
+
+#define DRIVER_NAME "adv7482"
+
+/****************************************/
+/* ADV7482 I2C slave address definition */
+/****************************************/
+#define ADV7482_I2C_IO 0x70 /* IO Map */
+#define ADV7482_I2C_DPLL 0x26 /* DPLL Map */
+#define ADV7482_I2C_CP 0x22 /* CP Map */
+#define ADV7482_I2C_HDMI 0x34 /* HDMI Map */
+#define ADV7482_I2C_EDID 0x36 /* EDID Map */
+#define ADV7482_I2C_REPEATER 0x32 /* HDMI RX Repeater Map */
+#define ADV7482_I2C_INFOFRAME 0x31 /* HDMI RX InfoFrame Map */
+#define ADV7482_I2C_CEC 0x41 /* CEC Map */
+#define ADV7482_I2C_SDP 0x79 /* SDP Map */
+#define ADV7482_I2C_TXB 0x48 /* CSI-TXB Map */
+#define ADV7482_I2C_TXA 0x4A /* CSI-TXA Map */
+#define ADV7482_I2C_WAIT 0xFE /* Wait x mesec */
+#define ADV7482_I2C_EOR 0xFF /* End Mark */
+
+/****************************************/
+/* ADV7482 IO register definition */
+/****************************************/
+
+/* Revision */
+#define ADV7482_IO_RD_INFO1_REG 0xDF /* chip version register */
+#define ADV7482_IO_RD_INFO2_REG 0xE0 /* chip version register */
+
+#define ADV7482_IO_CP_DATAPATH_REG 0x03 /* datapath ctrl */
+#define ADV7482_IO_CP_COLORSPACE_REG 0x04
+#define ADV7482_IO_CP_VID_STD_REG 0x05 /* Video Standard */
+
+/* Power Management */
+#define ADV7482_IO_PWR_MAN_REG 0x0C /* Power management register */
+#define ADV7482_IO_PWR_ON 0xE0 /* Power on */
+#define ADV7482_IO_PWR_OFF 0x00 /* Power down */
+
+#define ADV7482_HDMI_DDC_PWRDN 0x73 /* Power DDC pads control register */
+#define ADV7482_HDMI_DDC_PWR_ON 0x00 /* Power on */
+#define ADV7482_HDMI_DDC_PWR_OFF 0x01 /* Power down */
+
+#define ADV7482_IO_CP_VID_STD_480I 0x40
+#define ADV7482_IO_CP_VID_STD_576I 0x41
+#define ADV7482_IO_CP_VID_STD_480P 0x4A
+#define ADV7482_IO_CP_VID_STD_576P 0x4B
+#define ADV7482_IO_CP_VID_STD_720P 0x53
+#define ADV7482_IO_CP_VID_STD_1080I 0x54
+#define ADV7482_IO_CP_VID_STD_1080P 0x5E
+#define ADV7482_IO_CP_VID_STD_SVGA56 0x80
+#define ADV7482_IO_CP_VID_STD_SVGA60 0x81
+#define ADV7482_IO_CP_VID_STD_SVGA72 0x82
+#define ADV7482_IO_CP_VID_STD_SVGA75 0x83
+#define ADV7482_IO_CP_VID_STD_SVGA85 0x84
+#define ADV7482_IO_CP_VID_STD_SXGA60 0x85
+#define ADV7482_IO_CP_VID_STD_SXGA75 0x86
+#define ADV7482_IO_CP_VID_STD_VGA60 0x88
+#define ADV7482_IO_CP_VID_STD_VGA72 0x89
+#define ADV7482_IO_CP_VID_STD_VGA75 0x8A
+#define ADV7482_IO_CP_VID_STD_VGA85 0x8B
+#define ADV7482_IO_CP_VID_STD_XGA60 0x8C
+#define ADV7482_IO_CP_VID_STD_XGA70 0x8D
+#define ADV7482_IO_CP_VID_STD_XGA75 0x8E
+#define ADV7482_IO_CP_VID_STD_XGA85 0x8F
+#define ADV7482_IO_CP_VID_STD_UXGA60 0x96
+
+#define ADV7482_IO_CSI4_EN_ENABLE 0x80
+#define ADV7482_IO_CSI4_EN_DISABLE 0x00
+
+#define ADV7482_IO_CSI1_EN_ENABLE 0x40
+#define ADV7482_IO_CSI1_EN_DISABLE 0x00
+
+/****************************************/
+/* ADV7482 CP register definition */
+/****************************************/
+
+/* Contrast Control */
+#define ADV7482_CP_CON_REG 0x3a /* Contrast (unsigned) */
+#define ADV7482_CP_CON_MIN 0 /* Minimum contrast */
+#define ADV7482_CP_CON_DEF 128 /* Default */
+#define ADV7482_CP_CON_MAX 255 /* Maximum contrast */
+
+/* Saturation Control */
+#define ADV7482_CP_SAT_REG 0x3b /* Saturation (unsigned) */
+#define ADV7482_CP_SAT_MIN 0 /* Minimum saturation */
+#define ADV7482_CP_SAT_DEF 128 /* Default */
+#define ADV7482_CP_SAT_MAX 255 /* Maximum saturation */
+
+/* Brightness Control */
+#define ADV7482_CP_BRI_REG 0x3c /* Brightness (signed) */
+#define ADV7482_CP_BRI_MIN -128 /* Luma is -512d */
+#define ADV7482_CP_BRI_DEF 0 /* Luma is 0 */
+#define ADV7482_CP_BRI_MAX 127 /* Luma is 508d */
+
+/* Hue Control */
+#define ADV7482_CP_HUE_REG 0x3d /* Hue (unsigned) */
+#define ADV7482_CP_HUE_MIN 0 /* -90 degree */
+#define ADV7482_CP_HUE_DEF 0 /* -90 degree */
+#define ADV7482_CP_HUE_MAX 255 /* +90 degree */
+
+/* Video adjustment register */
+#define ADV7482_CP_VID_ADJ_REG 0x3e
+/* Video adjustment mask */
+#define ADV7482_CP_VID_ADJ_MASK 0x7F
+/* Enable color controls */
+#define ADV7482_CP_VID_ADJ_ENABLE 0x80
+
+/****************************************/
+/* ADV7482 HDMI register definition */
+/****************************************/
+
+/* HDMI status register */
+#define ADV7482_HDMI_STATUS1_REG 0x07
+/* VERT_FILTER_LOCKED flag */
+#define ADV7482_HDMI_VF_LOCKED_FLG 0x80
+/* DE_REGEN_FILTER_LOCKED flag */
+#define ADV7482_HDMI_DERF_LOCKED_FLG 0x20
+/* LINE_WIDTH[12:8] mask */
+#define ADV7482_HDMI_LWIDTH_MSBS_MASK 0x1F
+
+/* LINE_WIDTH[7:0] register */
+#define ADV7482_HDMI_LWIDTH_REG 0x08
+
+/* FIELD0_HEIGHT[12:8] register */
+#define ADV7482_HDMI_F0HEIGHT_MSBS_REG 0x09
+/* FIELD0_HEIGHT[12:8] mask */
+#define ADV7482_HDMI_F0HEIGHT_MSBS_MASK 0x1F
+
+/* FIELD0_HEIGHT[7:0] register */
+#define ADV7482_HDMI_F0HEIGHT_LSBS_REG 0x0A
+
+/* HDMI status register */
+#define ADV7482_HDMI_STATUS2_REG 0x0B
+/* DEEP_COLOR_MODE[1:0] mask */
+#define ADV7482_HDMI_DCM_MASK 0xC0
+/* HDMI_INTERLACED flag */
+#define ADV7482_HDMI_IP_FLAG 0x20
+/* FIELD1_HEIGHT[12:8] mask */
+#define ADV7482_HDMI_F1HEIGHT_MSBS_MASK 0x1F
+
+/* FIELD1_HEIGHT[7:0] register */
+#define ADV7482_HDMI_F1HEIGHT_REG 0x0C
+
+struct adv7482_sdp_main_info {
+ u8 status_reg_10;
+};
+
+/****************************************/
+/* ADV7482 SDP register definition */
+/****************************************/
+
+#define ADV7482_SDP_MAIN_MAP 0x00
+#define ADV7482_SDP_SUB_MAP1 0x20
+#define ADV7482_SDP_SUB_MAP2 0x40
+
+#define ADV7482_SDP_NO_RO_MAIN_MAP 0x00
+#define ADV7482_SDP_RO_MAIN_MAP 0x01
+#define ADV7482_SDP_RO_SUB_MAP1 0x02
+#define ADV7482_SDP_RO_SUB_MAP2 0x03
+
+#define ADV7482_SDP_MAIN_MAP_RW \
+ (ADV7482_SDP_MAIN_MAP | ADV7482_SDP_NO_RO_MAIN_MAP)
+
+#define ADV7482_SDP_STD_AD_PAL_BG_NTSC_J_SECAM 0x0
+#define ADV7482_SDP_STD_AD_PAL_BG_NTSC_J_SECAM_PED 0x1
+#define ADV7482_SDP_STD_AD_PAL_N_NTSC_J_SECAM 0x2
+#define ADV7482_SDP_STD_AD_PAL_N_NTSC_M_SECAM 0x3
+#define ADV7482_SDP_STD_NTSC_J 0x4
+#define ADV7482_SDP_STD_NTSC_M 0x5
+#define ADV7482_SDP_STD_PAL60 0x6
+#define ADV7482_SDP_STD_NTSC_443 0x7
+#define ADV7482_SDP_STD_PAL_BG 0x8
+#define ADV7482_SDP_STD_PAL_N 0x9
+#define ADV7482_SDP_STD_PAL_M 0xa
+#define ADV7482_SDP_STD_PAL_M_PED 0xb
+#define ADV7482_SDP_STD_PAL_COMB_N 0xc
+#define ADV7482_SDP_STD_PAL_COMB_N_PED 0xd
+#define ADV7482_SDP_STD_PAL_SECAM 0xe
+#define ADV7482_SDP_STD_PAL_SECAM_PED 0xf
+
+#define ADV7482_SDP_REG_INPUT_CONTROL 0x00
+#define ADV7482_SDP_INPUT_CONTROL_INSEL_MASK 0x0f
+
+#define ADV7482_SDP_REG_INPUT_VIDSEL 0x02
+
+#define ADV7482_SDP_REG_CTRL 0x0e
+
+#define ADV7482_SDP_REG_PWR_MAN 0x0f
+#define ADV7482_SDP_PWR_MAN_ON 0x00
+#define ADV7482_SDP_PWR_MAN_OFF 0x20
+#define ADV7482_SDP_PWR_MAN_RES 0x80
+
+/* Contrast */
+#define ADV7482_SDP_REG_CON 0x08 /*Unsigned */
+#define ADV7482_SDP_CON_MIN 0
+#define ADV7482_SDP_CON_DEF 128
+#define ADV7482_SDP_CON_MAX 255
+/* Brightness*/
+#define ADV7482_SDP_REG_BRI 0x0a /*Signed */
+#define ADV7482_SDP_BRI_MIN -128
+#define ADV7482_SDP_BRI_DEF 0
+#define ADV7482_SDP_BRI_MAX 127
+/* Hue */
+#define ADV7482_SDP_REG_HUE 0x0b /*Signed, inverted */
+#define ADV7482_SDP_HUE_MIN -127
+#define ADV7482_SDP_HUE_DEF 0
+#define ADV7482_SDP_HUE_MAX 128
+
+/* Saturation */
+#define ADV7482_SDP_REG_SD_SAT_CB 0xe3
+#define ADV7482_SDP_REG_SD_SAT_CR 0xe4
+#define ADV7482_SDP_SAT_MIN 0
+#define ADV7482_SDP_SAT_DEF 128
+#define ADV7482_SDP_SAT_MAX 255
+
+#define ADV7482_SDP_INPUT_CVBS_AIN1 0x00
+#define ADV7482_SDP_INPUT_CVBS_AIN2 0x01
+#define ADV7482_SDP_INPUT_CVBS_AIN3 0x02
+#define ADV7482_SDP_INPUT_CVBS_AIN4 0x03
+#define ADV7482_SDP_INPUT_CVBS_AIN5 0x04
+#define ADV7482_SDP_INPUT_CVBS_AIN6 0x05
+#define ADV7482_SDP_INPUT_CVBS_AIN7 0x06
+#define ADV7482_SDP_INPUT_CVBS_AIN8 0x07
+#define ADV7482_SDP_INPUT_SVIDEO_AIN1_AIN2 0x08
+#define ADV7482_SDP_INPUT_SVIDEO_AIN3_AIN4 0x09
+#define ADV7482_SDP_INPUT_SVIDEO_AIN5_AIN6 0x0a
+#define ADV7482_SDP_INPUT_SVIDEO_AIN7_AIN8 0x0b
+#define ADV7482_SDP_INPUT_YPRPB_AIN1_AIN2_AIN3 0x0c
+#define ADV7482_SDP_INPUT_YPRPB_AIN4_AIN5_AIN6 0x0d
+#define ADV7482_SDP_INPUT_DIFF_CVBS_AIN1_AIN2 0x0e
+#define ADV7482_SDP_INPUT_DIFF_CVBS_AIN3_AIN4 0x0f
+#define ADV7482_SDP_INPUT_DIFF_CVBS_AIN5_AIN6 0x10
+#define ADV7482_SDP_INPUT_DIFF_CVBS_AIN7_AIN8 0x11
+
+#define ADV7482_SDP_R_REG_10 0x10
+#define ADV7482_SDP_R_REG_10_IN_LOCK 0x01
+#define ADV7482_SDP_R_REG_10_FSC_LOCK 0x04
+
+#define ADV7482_SDP_R_REG_10_AUTOD_MASK 0x70
+#define ADV7482_SDP_R_REG_10_AUTOD_NTSM_M_J 0x00
+#define ADV7482_SDP_R_REG_10_AUTOD_NTSC_4_43 0x10
+#define ADV7482_SDP_R_REG_10_AUTOD_PAL_M 0x20
+#define ADV7482_SDP_R_REG_10_AUTOD_PAL_60 0x30
+#define ADV7482_SDP_R_REG_10_AUTOD_PAL_B_G 0x40
+#define ADV7482_SDP_R_REG_10_AUTOD_SECAM 0x50
+#define ADV7482_SDP_R_REG_10_AUTOD_PAL_COMB 0x60
+#define ADV7482_SDP_R_REG_10_AUTOD_SECAM_525 0x70
+
+#define ADV7482_MAX_WIDTH 1920
+#define ADV7482_MAX_HEIGHT 1080
+
+/****************************************/
+/* ADV7482 structure definition */
+/****************************************/
+
+/* Structure for register values */
+struct adv7482_reg_value {
+ u8 addr; /* i2c slave address */
+ u8 reg; /* sub (register) address */
+ u8 value; /* register value */
+};
+
+#define END_REGISTER_TABLE \
+{ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+
+/* Register default values */
+
+static const struct adv7482_reg_value adv7482_sw_reset[] = {
+
+ {ADV7482_I2C_IO, 0xFF, 0xFF}, /* SW reset */
+ {ADV7482_I2C_WAIT, 0x00, 0x05}, /* delay 5 */
+ {ADV7482_I2C_IO, 0x01, 0x76}, /* ADI Required Write */
+ {ADV7482_I2C_IO, 0xF2, 0x01}, /* Enable I2C Read Auto-Increment */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_set_slave_address[] = {
+
+ /* I2C Slave Address settings */
+ {ADV7482_I2C_IO, 0xF3, ADV7482_I2C_DPLL * 2}, /* DPLL Map */
+ {ADV7482_I2C_IO, 0xF4, ADV7482_I2C_CP * 2}, /* CP Map */
+ {ADV7482_I2C_IO, 0xF5, ADV7482_I2C_HDMI * 2}, /* HDMI Map */
+ {ADV7482_I2C_IO, 0xF6, ADV7482_I2C_EDID * 2}, /* EDID Map */
+ {ADV7482_I2C_IO, 0xF7, ADV7482_I2C_REPEATER * 2},
+ /* HDMI RX Repeater Map */
+ {ADV7482_I2C_IO, 0xF8, ADV7482_I2C_INFOFRAME * 2},
+ /* HDMI RX InfoFrame Map */
+ {ADV7482_I2C_IO, 0xFA, ADV7482_I2C_CEC * 2}, /* CEC Map */
+ {ADV7482_I2C_IO, 0xFB, ADV7482_I2C_SDP * 2}, /* SDP Map */
+ {ADV7482_I2C_IO, 0xFC, ADV7482_I2C_TXB * 2}, /* CSI-TXB Map */
+ {ADV7482_I2C_IO, 0xFD, ADV7482_I2C_TXA * 2}, /* CSI-TXA Map */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+/* Supported Formats For Script Below */
+/* - 01-29 HDMI to MIPI TxA CSI 4-Lane - RGB888: */
+static const struct adv7482_reg_value adv7482_init_txa_4lane[] = {
+ /* I2C Slave Address settings */
+ {ADV7482_I2C_IO, 0xF3, ADV7482_I2C_DPLL * 2}, /* DPLL Map */
+ {ADV7482_I2C_IO, 0xF4, ADV7482_I2C_CP * 2}, /* CP Map */
+ {ADV7482_I2C_IO, 0xF5, ADV7482_I2C_HDMI * 2}, /* HDMI Map */
+ {ADV7482_I2C_IO, 0xF6, ADV7482_I2C_EDID * 2}, /* EDID Map */
+ {ADV7482_I2C_IO, 0xF7, ADV7482_I2C_REPEATER * 2},
+ /* HDMI RX Repeater Map */
+ {ADV7482_I2C_IO, 0xF8, ADV7482_I2C_INFOFRAME * 2},
+ /* HDMI RX InfoFrame Map */
+ {ADV7482_I2C_IO, 0xFA, ADV7482_I2C_CEC * 2}, /* CEC Map */
+ {ADV7482_I2C_IO, 0xFB, ADV7482_I2C_SDP * 2}, /* SDP Map */
+ {ADV7482_I2C_IO, 0xFC, ADV7482_I2C_TXB * 2}, /* CSI-TXB Map */
+ {ADV7482_I2C_IO, 0xFD, ADV7482_I2C_TXA * 2}, /* CSI-TXA Map */
+
+ /* Disable chip powerdown & Enable HDMI Rx block */
+ {ADV7482_I2C_IO, 0x00, 0x40},
+
+ {ADV7482_I2C_REPEATER, 0x40, 0x83}, /* Enable HDCP 1.1 */
+
+ {ADV7482_I2C_HDMI, 0x00, 0x08}, /* Foreground Channel = A */
+ {ADV7482_I2C_HDMI, 0x98, 0xFF}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x99, 0xA3}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x9A, 0x00}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x9B, 0x0A}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x9D, 0x40}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0xCB, 0x09}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x3D, 0x10}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x3E, 0x7B}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x3F, 0x5E}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x4E, 0xFE}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x4F, 0x18}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x57, 0xA3}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x58, 0x04}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0x85, 0x10}, /* ADI Required Write */
+
+ {ADV7482_I2C_HDMI, 0x83, 0x00}, /* Enable All Terminations */
+ {ADV7482_I2C_HDMI, 0xA3, 0x01}, /* ADI Required Write */
+ {ADV7482_I2C_HDMI, 0xBE, 0x00}, /* ADI Required Write */
+
+ {ADV7482_I2C_HDMI, 0x6C, 0x01}, /* HPA Manual Enable */
+ {ADV7482_I2C_HDMI, 0xF8, 0x01}, /* HPA Asserted */
+ {ADV7482_I2C_HDMI, 0x0F, 0x00}, /* Audio Mute Speed Set to Fastest */
+ /* (Smallest Step Size) */
+
+ /* EDID */
+ /* Header information(0-19th byte) */
+ {ADV7482_I2C_EDID, 0x00, 0x00}, /* Fixed header pattern */
+ {ADV7482_I2C_EDID, 0x01, 0xFF},
+ {ADV7482_I2C_EDID, 0x02, 0xFF},
+ {ADV7482_I2C_EDID, 0x03, 0xFF},
+ {ADV7482_I2C_EDID, 0x04, 0xFF},
+ {ADV7482_I2C_EDID, 0x05, 0xFF},
+ {ADV7482_I2C_EDID, 0x06, 0xFF},
+ {ADV7482_I2C_EDID, 0x07, 0x00},
+ {ADV7482_I2C_EDID, 0x08, 0x00}, /* Manufacturer ID */
+ {ADV7482_I2C_EDID, 0x09, 0x00},
+ {ADV7482_I2C_EDID, 0x0A, 0x00}, /* Manufacturer product code */
+ {ADV7482_I2C_EDID, 0x0B, 0x00},
+ {ADV7482_I2C_EDID, 0x0C, 0x00}, /* Serial number */
+ {ADV7482_I2C_EDID, 0x0D, 0x00},
+ {ADV7482_I2C_EDID, 0x0E, 0x00},
+ {ADV7482_I2C_EDID, 0x0F, 0x00},
+ {ADV7482_I2C_EDID, 0x10, 0x01}, /* Week of manufacture */
+ {ADV7482_I2C_EDID, 0x11, 0x0C}, /* Year of manufacture */
+ {ADV7482_I2C_EDID, 0x12, 0x01}, /* EDID version */
+ {ADV7482_I2C_EDID, 0x13, 0x03},
+ /* Basic display parameters(20-24th byte) */
+ {ADV7482_I2C_EDID, 0x14, 0x80}, /* Video input parameters bitmap */
+ {ADV7482_I2C_EDID, 0x15, 0x50}, /* Maximum horizontal image size */
+ {ADV7482_I2C_EDID, 0x16, 0x2D}, /* Maximum vertical image size */
+ {ADV7482_I2C_EDID, 0x17, 0x78}, /* Display gamma */
+ {ADV7482_I2C_EDID, 0x18, 0x0A}, /* Supported features bitmap */
+ /* Chromaticity coordinates(25-34th byte) */
+ {ADV7482_I2C_EDID, 0x19, 0x0D},
+ {ADV7482_I2C_EDID, 0x1A, 0xC9},
+ {ADV7482_I2C_EDID, 0x1B, 0xA0},
+ {ADV7482_I2C_EDID, 0x1C, 0x57},
+ {ADV7482_I2C_EDID, 0x1D, 0x47},
+ {ADV7482_I2C_EDID, 0x1E, 0x98},
+ {ADV7482_I2C_EDID, 0x1F, 0x27},
+ {ADV7482_I2C_EDID, 0x20, 0x12},
+ {ADV7482_I2C_EDID, 0x21, 0x48},
+ {ADV7482_I2C_EDID, 0x22, 0x4C},
+ /* Established timing bitmap(35-37th byte) */
+ {ADV7482_I2C_EDID, 0x23, 0x20},
+ {ADV7482_I2C_EDID, 0x24, 0x00},
+ {ADV7482_I2C_EDID, 0x25, 0x00},
+ /* Standard timing information(38-53th byte) */
+ /* Because they are unused, in this field, all values are 0101h. */
+ {ADV7482_I2C_EDID, 0x26, 0x01},
+ {ADV7482_I2C_EDID, 0x27, 0x01},
+ {ADV7482_I2C_EDID, 0x28, 0x01},
+ {ADV7482_I2C_EDID, 0x29, 0x01},
+ {ADV7482_I2C_EDID, 0x2A, 0x01},
+ {ADV7482_I2C_EDID, 0x2B, 0x01},
+ {ADV7482_I2C_EDID, 0x2C, 0x01},
+ {ADV7482_I2C_EDID, 0x2D, 0x01},
+ {ADV7482_I2C_EDID, 0x2E, 0x01},
+ {ADV7482_I2C_EDID, 0x2F, 0x01},
+ {ADV7482_I2C_EDID, 0x30, 0x01},
+ {ADV7482_I2C_EDID, 0x31, 0x01},
+ {ADV7482_I2C_EDID, 0x32, 0x01},
+ {ADV7482_I2C_EDID, 0x33, 0x01},
+ {ADV7482_I2C_EDID, 0x34, 0x01},
+ {ADV7482_I2C_EDID, 0x35, 0x01},
+ /* Descriptor blocks of Descriptor 1(54-71th byte) */
+ {ADV7482_I2C_EDID, 0x36, 0x02},
+ {ADV7482_I2C_EDID, 0x37, 0x3A},
+ {ADV7482_I2C_EDID, 0x38, 0x80},
+ {ADV7482_I2C_EDID, 0x39, 0x18},
+ {ADV7482_I2C_EDID, 0x3A, 0x71},
+ {ADV7482_I2C_EDID, 0x3B, 0x38},
+ {ADV7482_I2C_EDID, 0x3C, 0x2D},
+ {ADV7482_I2C_EDID, 0x3D, 0x40},
+ {ADV7482_I2C_EDID, 0x3E, 0x58},
+ {ADV7482_I2C_EDID, 0x3F, 0x2C},
+ {ADV7482_I2C_EDID, 0x40, 0x45},
+ {ADV7482_I2C_EDID, 0x41, 0x00},
+ {ADV7482_I2C_EDID, 0x42, 0xDC},
+ {ADV7482_I2C_EDID, 0x43, 0x0B},
+ {ADV7482_I2C_EDID, 0x44, 0x11},
+ {ADV7482_I2C_EDID, 0x45, 0x00},
+ {ADV7482_I2C_EDID, 0x46, 0x00},
+ {ADV7482_I2C_EDID, 0x47, 0x1E},
+ /* Descriptor blocks of Descriptor 2(72-89th byte) */
+ {ADV7482_I2C_EDID, 0x48, 0x8C},
+ {ADV7482_I2C_EDID, 0x49, 0x0A},
+ {ADV7482_I2C_EDID, 0x4A, 0xD0},
+ {ADV7482_I2C_EDID, 0x4B, 0x8A},
+ {ADV7482_I2C_EDID, 0x4C, 0x20},
+ {ADV7482_I2C_EDID, 0x4D, 0xE0},
+ {ADV7482_I2C_EDID, 0x4E, 0x2D},
+ {ADV7482_I2C_EDID, 0x4F, 0x10},
+ {ADV7482_I2C_EDID, 0x50, 0x10},
+ {ADV7482_I2C_EDID, 0x51, 0x3E},
+ {ADV7482_I2C_EDID, 0x52, 0x96},
+ {ADV7482_I2C_EDID, 0x53, 0x00},
+ {ADV7482_I2C_EDID, 0x54, 0x58},
+ {ADV7482_I2C_EDID, 0x55, 0xC2},
+ {ADV7482_I2C_EDID, 0x56, 0x21},
+ {ADV7482_I2C_EDID, 0x57, 0x00},
+ {ADV7482_I2C_EDID, 0x58, 0x00},
+ {ADV7482_I2C_EDID, 0x59, 0x18},
+ /* Descriptor blocks of Descriptor 3(90-107th byte) */
+ {ADV7482_I2C_EDID, 0x5A, 0x00},
+ {ADV7482_I2C_EDID, 0x5B, 0x00},
+ {ADV7482_I2C_EDID, 0x5C, 0x00},
+ {ADV7482_I2C_EDID, 0x5D, 0x7C},
+ {ADV7482_I2C_EDID, 0x5E, 0x00},
+ {ADV7482_I2C_EDID, 0x5F, 0x4D},
+ {ADV7482_I2C_EDID, 0x60, 0x59},
+ {ADV7482_I2C_EDID, 0x61, 0x20},
+ {ADV7482_I2C_EDID, 0x62, 0x48},
+ {ADV7482_I2C_EDID, 0x63, 0x44},
+ {ADV7482_I2C_EDID, 0x64, 0x54},
+ {ADV7482_I2C_EDID, 0x65, 0x56},
+ {ADV7482_I2C_EDID, 0x66, 0x0A},
+ {ADV7482_I2C_EDID, 0x67, 0x20},
+ {ADV7482_I2C_EDID, 0x68, 0x20},
+ {ADV7482_I2C_EDID, 0x69, 0x20},
+ {ADV7482_I2C_EDID, 0x6A, 0x20},
+ {ADV7482_I2C_EDID, 0x6B, 0x20},
+ /* Descriptor blocks of Descriptor 4(108-125th byte) */
+ {ADV7482_I2C_EDID, 0x6C, 0x00},
+ {ADV7482_I2C_EDID, 0x6D, 0x00},
+ {ADV7482_I2C_EDID, 0x6E, 0x00},
+ {ADV7482_I2C_EDID, 0x6F, 0x00},
+ {ADV7482_I2C_EDID, 0x70, 0x00},
+ {ADV7482_I2C_EDID, 0x71, 0x00},
+ {ADV7482_I2C_EDID, 0x72, 0x00},
+ {ADV7482_I2C_EDID, 0x73, 0x0A},
+ {ADV7482_I2C_EDID, 0x74, 0x2E},
+ {ADV7482_I2C_EDID, 0x75, 0x06},
+ {ADV7482_I2C_EDID, 0x76, 0x00},
+ {ADV7482_I2C_EDID, 0x77, 0x0A},
+ {ADV7482_I2C_EDID, 0x78, 0x20},
+ {ADV7482_I2C_EDID, 0x79, 0x20},
+ {ADV7482_I2C_EDID, 0x7A, 0x20},
+ {ADV7482_I2C_EDID, 0x7B, 0x20},
+ {ADV7482_I2C_EDID, 0x7C, 0x20},
+ {ADV7482_I2C_EDID, 0x7D, 0x20},
+ /* Number of extensions to follow(126th byte) */
+ {ADV7482_I2C_EDID, 0x7E, 0x01}, /* Extension enable */
+ /* Checksum(127th byte) */
+ {ADV7482_I2C_EDID, 0x7F, 0x75},
+
+ /* CEA EDID Timing Extension Version 3 */
+ {ADV7482_I2C_EDID, 0x80, 0x02}, /* CEA 861 Externsion Block */
+ {ADV7482_I2C_EDID, 0x81, 0x03},
+ {ADV7482_I2C_EDID, 0x82, 0x1E},
+ {ADV7482_I2C_EDID, 0x83, 0x40},
+ {ADV7482_I2C_EDID, 0x84, 0x83},
+ {ADV7482_I2C_EDID, 0x85, 0x7F},
+ {ADV7482_I2C_EDID, 0x86, 0x40},
+ {ADV7482_I2C_EDID, 0x87, 0x05},
+ {ADV7482_I2C_EDID, 0x88, 0x40},
+ {ADV7482_I2C_EDID, 0x89, 0x48},
+ {ADV7482_I2C_EDID, 0x8A, 0x10}, /* 1920x1080p @ 59.94/60 Hz */
+ {ADV7482_I2C_EDID, 0x8B, 0x05}, /* 1920x1080i @ 59.94/60 Hz */
+ {ADV7482_I2C_EDID, 0x8C, 0x04}, /* 1280x720p @ 59.94/60 Hz */
+ {ADV7482_I2C_EDID, 0x8D, 0x01}, /* 640x480p @ 59.94/60 Hz */
+ {ADV7482_I2C_EDID, 0x8E, 0x02}, /* 720x480p @ 59.94/60 Hz */
+ {ADV7482_I2C_EDID, 0x8F, 0x06}, /* 720(1440)x480i @ 59.94/60 Hz */
+ {ADV7482_I2C_EDID, 0x90, 0x15}, /* 720(1440)x576i @ 50 Hz */
+ {ADV7482_I2C_EDID, 0x91, 0x11}, /* 720x576p @ 50 Hz */
+ {ADV7482_I2C_EDID, 0x92, 0x00},
+ {ADV7482_I2C_EDID, 0x93, 0x00},
+ {ADV7482_I2C_EDID, 0x94, 0x00},
+ {ADV7482_I2C_EDID, 0x95, 0x00},
+ {ADV7482_I2C_EDID, 0x96, 0x00},
+ {ADV7482_I2C_EDID, 0x97, 0x00},
+ {ADV7482_I2C_EDID, 0x98, 0x00},
+ {ADV7482_I2C_EDID, 0x99, 0x00},
+ {ADV7482_I2C_EDID, 0x9A, 0x00},
+ {ADV7482_I2C_EDID, 0x9B, 0x00},
+ {ADV7482_I2C_EDID, 0x9C, 0x00},
+ {ADV7482_I2C_EDID, 0x9D, 0x00},
+ {ADV7482_I2C_EDID, 0x9E, 0x00},
+ {ADV7482_I2C_EDID, 0x9F, 0x00},
+ {ADV7482_I2C_EDID, 0xA0, 0x00},
+ {ADV7482_I2C_EDID, 0xA1, 0x00},
+ {ADV7482_I2C_EDID, 0xA2, 0x00},
+ {ADV7482_I2C_EDID, 0xA3, 0x00},
+ {ADV7482_I2C_EDID, 0xA4, 0x00},
+ {ADV7482_I2C_EDID, 0xA5, 0x00},
+ {ADV7482_I2C_EDID, 0xA6, 0x00},
+ {ADV7482_I2C_EDID, 0xA7, 0x00},
+ {ADV7482_I2C_EDID, 0xA8, 0x00},
+ {ADV7482_I2C_EDID, 0xA9, 0x00},
+ {ADV7482_I2C_EDID, 0xAA, 0x00},
+ {ADV7482_I2C_EDID, 0xAB, 0x00},
+ {ADV7482_I2C_EDID, 0xAC, 0x00},
+ {ADV7482_I2C_EDID, 0xAD, 0x00},
+ {ADV7482_I2C_EDID, 0xAE, 0x00},
+ {ADV7482_I2C_EDID, 0xAF, 0x00},
+ {ADV7482_I2C_EDID, 0xB0, 0x00},
+ {ADV7482_I2C_EDID, 0xB1, 0x00},
+ {ADV7482_I2C_EDID, 0xB2, 0x00},
+ {ADV7482_I2C_EDID, 0xB3, 0x00},
+ {ADV7482_I2C_EDID, 0xB4, 0x00},
+ {ADV7482_I2C_EDID, 0xB5, 0x00},
+ {ADV7482_I2C_EDID, 0xB6, 0x00},
+ {ADV7482_I2C_EDID, 0xB7, 0x00},
+ {ADV7482_I2C_EDID, 0xB8, 0x00},
+ {ADV7482_I2C_EDID, 0xB9, 0x00},
+ {ADV7482_I2C_EDID, 0xBA, 0x20},
+ {ADV7482_I2C_EDID, 0xBB, 0x20},
+ {ADV7482_I2C_EDID, 0xBC, 0x20},
+ {ADV7482_I2C_EDID, 0xBD, 0x20},
+ {ADV7482_I2C_EDID, 0xBE, 0x20},
+ {ADV7482_I2C_EDID, 0xBF, 0x20},
+ {ADV7482_I2C_EDID, 0xC0, 0x20},
+ {ADV7482_I2C_EDID, 0xC1, 0x20},
+ {ADV7482_I2C_EDID, 0xC2, 0x20},
+ {ADV7482_I2C_EDID, 0xC3, 0x20},
+ {ADV7482_I2C_EDID, 0xC4, 0x20},
+ {ADV7482_I2C_EDID, 0xC5, 0x20},
+ {ADV7482_I2C_EDID, 0xC6, 0x00},
+ {ADV7482_I2C_EDID, 0xC7, 0x00},
+ {ADV7482_I2C_EDID, 0xC8, 0x00},
+ {ADV7482_I2C_EDID, 0xC9, 0xFF},
+ {ADV7482_I2C_EDID, 0xCA, 0x00},
+ {ADV7482_I2C_EDID, 0xCB, 0x0A},
+ {ADV7482_I2C_EDID, 0xCC, 0x20},
+ {ADV7482_I2C_EDID, 0xCD, 0x20},
+ {ADV7482_I2C_EDID, 0xCE, 0x20},
+ {ADV7482_I2C_EDID, 0xCF, 0x20},
+ {ADV7482_I2C_EDID, 0xD0, 0x20},
+ {ADV7482_I2C_EDID, 0xD1, 0x20},
+ {ADV7482_I2C_EDID, 0xD2, 0x20},
+ {ADV7482_I2C_EDID, 0xD3, 0x20},
+ {ADV7482_I2C_EDID, 0xD4, 0x20},
+ {ADV7482_I2C_EDID, 0xD5, 0x20},
+ {ADV7482_I2C_EDID, 0xD6, 0x20},
+ {ADV7482_I2C_EDID, 0xD7, 0x20},
+ {ADV7482_I2C_EDID, 0xD8, 0x00},
+ {ADV7482_I2C_EDID, 0xD9, 0x00},
+ {ADV7482_I2C_EDID, 0xDA, 0x00},
+ {ADV7482_I2C_EDID, 0xDB, 0x1B},
+ {ADV7482_I2C_EDID, 0xDC, 0x00},
+ {ADV7482_I2C_EDID, 0xDD, 0x0A},
+ {ADV7482_I2C_EDID, 0xDE, 0x20},
+ {ADV7482_I2C_EDID, 0xDF, 0x20},
+ {ADV7482_I2C_EDID, 0xE0, 0x20},
+ {ADV7482_I2C_EDID, 0xE1, 0x20},
+ {ADV7482_I2C_EDID, 0xE2, 0x20},
+ {ADV7482_I2C_EDID, 0xE3, 0x20},
+ {ADV7482_I2C_EDID, 0xE4, 0x20},
+ {ADV7482_I2C_EDID, 0xE5, 0x20},
+ {ADV7482_I2C_EDID, 0xE6, 0x20},
+ {ADV7482_I2C_EDID, 0xE7, 0x20},
+ {ADV7482_I2C_EDID, 0xE8, 0x20},
+ {ADV7482_I2C_EDID, 0xE9, 0x20},
+ {ADV7482_I2C_EDID, 0xEA, 0x00},
+ {ADV7482_I2C_EDID, 0xEB, 0x00},
+ {ADV7482_I2C_EDID, 0xEC, 0x00},
+ {ADV7482_I2C_EDID, 0xED, 0x00},
+ {ADV7482_I2C_EDID, 0xEE, 0x00},
+ {ADV7482_I2C_EDID, 0xEF, 0x00},
+ {ADV7482_I2C_EDID, 0xF0, 0x00},
+ {ADV7482_I2C_EDID, 0xF1, 0x00},
+ {ADV7482_I2C_EDID, 0xF2, 0x00},
+ {ADV7482_I2C_EDID, 0xF3, 0x00},
+ {ADV7482_I2C_EDID, 0xF4, 0x00},
+ {ADV7482_I2C_EDID, 0xF5, 0x00},
+ {ADV7482_I2C_EDID, 0xF6, 0x00},
+ {ADV7482_I2C_EDID, 0xF7, 0x00},
+ {ADV7482_I2C_EDID, 0xF8, 0x00},
+ {ADV7482_I2C_EDID, 0xF9, 0x00},
+ {ADV7482_I2C_EDID, 0xFA, 0x00},
+ {ADV7482_I2C_EDID, 0xFB, 0x00},
+ {ADV7482_I2C_EDID, 0xFC, 0x00},
+ {ADV7482_I2C_EDID, 0xFD, 0x00},
+ {ADV7482_I2C_EDID, 0xFE, 0x00},
+ {ADV7482_I2C_EDID, 0xFF, 0xD8}, /* Set the Most Significant Bit */
+
+ {ADV7482_I2C_REPEATER, 0x70, 0x10},
+ /* EDID image, in 128-byte blocks. */
+ {ADV7482_I2C_REPEATER, 0x74, 0x01},
+ /* Manual enable active for E-EDID on Port A */
+
+ {ADV7482_I2C_IO, 0x04, 0x02}, /* RGB Out of CP */
+ {ADV7482_I2C_IO, 0x12, 0xF0},
+ /* CSC Depends on ip Packets - SDR 444 */
+ {ADV7482_I2C_IO, 0x17, 0x80},
+ /* Luma & Chroma Values Can Reach 254d */
+ {ADV7482_I2C_IO, 0x03, 0x86}, /* CP-Insert_AV_Code */
+
+ {ADV7482_I2C_CP, 0x7C, 0x00}, /* ADI Required Write */
+
+ {ADV7482_I2C_IO, 0x0C, 0xE0}, /* Enable LLC_DLL & Double LLC Timing */
+ {ADV7482_I2C_IO, 0x0E, 0xDD}, /* LLC/PIX/SPI PINS TRISTATED AUD */
+ /* Outputs Enabled */
+ {ADV7482_I2C_IO, 0x10, 0xA0}, /* Enable 4-lane CSI Tx & Pixel Port */
+
+ {ADV7482_I2C_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */
+ {ADV7482_I2C_TXA, 0x00, 0xA4}, /* Set Auto DPHY Timing */
+ {ADV7482_I2C_TXA, 0xDB, 0x10}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xD6, 0x07}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xC4, 0x0A}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0x71, 0x33}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0x72, 0x11}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xF0, 0x00}, /* i2c_dphy_pwdn - 1'b0 */
+
+ {ADV7482_I2C_TXA, 0x31, 0x82}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0x1E, 0x40}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xDA, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
+ {ADV7482_I2C_WAIT, 0x00, 0x02}, /* delay 2 */
+ {ADV7482_I2C_TXA, 0x00, 0x24 }, /* Power-up CSI-TX */
+ {ADV7482_I2C_WAIT, 0x00, 0x01}, /* delay 1 */
+ {ADV7482_I2C_TXA, 0xC1, 0x2B}, /* ADI Required Write */
+ {ADV7482_I2C_WAIT, 0x00, 0x01}, /* delay 1 */
+ {ADV7482_I2C_TXA, 0x31, 0x80}, /* ADI Required Write */
+
+ /* for debug */
+#ifdef REL_DGB_FORCE_TO_SEND_COLORBAR
+ {ADV7482_I2C_CP, 0x37, 0x81}, /* Output Colorbars Pattern */
+#endif
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+/* 02-01 Analog CVBS to MIPI TX-B CSI 1-Lane - */
+/* Autodetect CVBS Single Ended In Ain 1 - MIPI Out */
+static const struct adv7482_reg_value adv7482_init_txb_1lane[] = {
+
+ {ADV7482_I2C_IO, 0x00, 0x30},
+ /* Disable chip powerdown - powerdown Rx */
+ {ADV7482_I2C_IO, 0xF2, 0x01}, /* Enable I2C Read Auto-Increment */
+
+ /* I2C Slave Address settings */
+ {ADV7482_I2C_IO, 0xF3, ADV7482_I2C_DPLL * 2}, /* DPLL Map */
+ {ADV7482_I2C_IO, 0xF4, ADV7482_I2C_CP * 2}, /* CP Map */
+ {ADV7482_I2C_IO, 0xF5, ADV7482_I2C_HDMI * 2}, /* HDMI Map */
+ {ADV7482_I2C_IO, 0xF6, ADV7482_I2C_EDID * 2}, /* EDID Map */
+ {ADV7482_I2C_IO, 0xF7, ADV7482_I2C_REPEATER * 2},
+ /* HDMI RX Repeater Map */
+ {ADV7482_I2C_IO, 0xF8, ADV7482_I2C_INFOFRAME * 2},
+ /* HDMI RX InfoFrame Map */
+ {ADV7482_I2C_IO, 0xFA, ADV7482_I2C_CEC * 2}, /* CEC Map */
+ {ADV7482_I2C_IO, 0xFB, ADV7482_I2C_SDP * 2}, /* SDP Map */
+ {ADV7482_I2C_IO, 0xFC, ADV7482_I2C_TXB * 2}, /* CSI-TXB Map */
+ {ADV7482_I2C_IO, 0xFD, ADV7482_I2C_TXA * 2}, /* CSI-TXA Map */
+
+ {ADV7482_I2C_IO, 0x0E, 0xFF}, /* LLC/PIX/AUD/SPI PINS TRISTATED */
+
+ {ADV7482_I2C_SDP, ADV7482_SDP_REG_PWR_MAN, ADV7482_SDP_PWR_MAN_ON},
+ /* Exit Power Down Mode */
+ {ADV7482_I2C_SDP, 0x52, 0xCD}, /* ADI Required Write */
+ {ADV7482_I2C_SDP, ADV7482_SDP_REG_INPUT_CONTROL,
+ ADV7482_SDP_INPUT_CVBS_AIN8}, /* INSEL = CVBS in on Ain 8 */
+ {ADV7482_I2C_SDP, ADV7482_SDP_REG_CTRL, 0x80},
+ /* ADI Required Write */
+ {ADV7482_I2C_SDP, 0x9C, 0x00}, /* ADI Required Write */
+ {ADV7482_I2C_SDP, 0x9C, 0xFF}, /* ADI Required Write */
+ {ADV7482_I2C_SDP, ADV7482_SDP_REG_CTRL, ADV7482_SDP_MAIN_MAP_RW},
+ /* ADI Required Write */
+
+ /* ADI recommended writes for improved video quality */
+ {ADV7482_I2C_SDP, 0x80, 0x51}, /* ADI Required Write */
+ {ADV7482_I2C_SDP, 0x81, 0x51}, /* ADI Required Write */
+ {ADV7482_I2C_SDP, 0x82, 0x68}, /* ADI Required Write */
+
+ {ADV7482_I2C_SDP, 0x03, 0x42},
+ /* Tri-S Output Drivers, PwrDwn 656 pads */
+ {ADV7482_I2C_SDP, 0x04, 0xB5}, /* ITU-R BT.656-4 compatible */
+ {ADV7482_I2C_SDP, 0x13, 0x00}, /* ADI Required Write */
+
+ {ADV7482_I2C_SDP, 0x17, 0x41}, /* Select SH1 */
+ {ADV7482_I2C_SDP, 0x31, 0x12}, /* ADI Required Write */
+ {ADV7482_I2C_SDP, 0xE6, 0x4F},
+ /* Set V bit end position manually in NTSC mode */
+
+#ifdef REL_DGB_FORCE_TO_SEND_COLORBAR
+ {ADV7482_I2C_SDP, 0x0C, 0x01}, /* ColorBar */
+ {ADV7482_I2C_SDP, 0x14, 0x01}, /* ColorBar */
+#endif
+ /* Enable 1-Lane MIPI Tx, */
+ /* enable pixel output and route SD through Pixel port */
+ {ADV7482_I2C_IO, 0x10, 0x70},
+
+ {ADV7482_I2C_TXB, 0x00, 0x81}, /* Enable 1-lane MIPI */
+ {ADV7482_I2C_TXB, 0x00, 0xA1}, /* Set Auto DPHY Timing */
+#if 0 /* If CVBS input only is used, please enable this code. */
+ {ADV7482_I2C_TXA, 0xF0, 0x00}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xD6, 0x07}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xC0, 0x3C}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xC3, 0x3C}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xC6, 0x3C}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xC9, 0x3C}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xCC, 0x3C}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xD5, 0x03}, /* ADI Required Write */
+#endif
+ {ADV7482_I2C_TXB, 0xD2, 0x40}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0xC4, 0x0A}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0x71, 0x33}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0x72, 0x11}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0xF0, 0x00}, /* i2c_dphy_pwdn - 1'b0 */
+ {ADV7482_I2C_TXB, 0x31, 0x82}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0x1E, 0x40}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0xDA, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
+
+ {ADV7482_I2C_WAIT, 0x00, 0x02}, /* delay 2 */
+ {ADV7482_I2C_TXB, 0x00, 0x21 }, /* Power-up CSI-TX */
+ {ADV7482_I2C_WAIT, 0x00, 0x01}, /* delay 1 */
+ {ADV7482_I2C_TXB, 0xC1, 0x2B}, /* ADI Required Write */
+ {ADV7482_I2C_WAIT, 0x00, 0x01}, /* delay 1 */
+ {ADV7482_I2C_TXB, 0x31, 0x80}, /* ADI Required Write */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_power_up_txa_4lane[] = {
+
+ {ADV7482_I2C_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */
+ {ADV7482_I2C_TXA, 0x00, 0xA4}, /* Set Auto DPHY Timing */
+
+ {ADV7482_I2C_TXA, 0x31, 0x82}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0x1E, 0x40}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0xDA, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
+ {ADV7482_I2C_WAIT, 0x00, 0x02}, /* delay 2 */
+ {ADV7482_I2C_TXA, 0x00, 0x24 }, /* Power-up CSI-TX */
+ {ADV7482_I2C_WAIT, 0x00, 0x01}, /* delay 1 */
+ {ADV7482_I2C_TXA, 0xC1, 0x2B}, /* ADI Required Write */
+ {ADV7482_I2C_WAIT, 0x00, 0x01}, /* delay 1 */
+ {ADV7482_I2C_TXA, 0x31, 0x80}, /* ADI Required Write */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_power_down_txa_4lane[] = {
+
+ {ADV7482_I2C_TXA, 0x31, 0x82}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0x1E, 0x00}, /* ADI Required Write */
+ {ADV7482_I2C_TXA, 0x00, 0x84}, /* Enable 4-lane MIPI */
+ {ADV7482_I2C_TXA, 0xDA, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
+ {ADV7482_I2C_TXA, 0xC1, 0x3B}, /* ADI Required Write */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_power_up_txb_1lane[] = {
+
+ {ADV7482_I2C_TXB, 0x00, 0x81}, /* Enable 1-lane MIPI */
+ {ADV7482_I2C_TXB, 0x00, 0xA1}, /* Set Auto DPHY Timing */
+
+ {ADV7482_I2C_TXB, 0x31, 0x82}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0x1E, 0x40}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0xDA, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
+ {ADV7482_I2C_WAIT, 0x00, 0x02}, /* delay 2 */
+ {ADV7482_I2C_TXB, 0x00, 0x21 }, /* Power-up CSI-TX */
+ {ADV7482_I2C_WAIT, 0x00, 0x01}, /* delay 1 */
+ {ADV7482_I2C_TXB, 0xC1, 0x2B}, /* ADI Required Write */
+ {ADV7482_I2C_WAIT, 0x00, 0x01}, /* delay 1 */
+ {ADV7482_I2C_TXB, 0x31, 0x80}, /* ADI Required Write */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_power_down_txb_1lane[] = {
+
+ {ADV7482_I2C_TXB, 0x31, 0x82}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0x1E, 0x00}, /* ADI Required Write */
+ {ADV7482_I2C_TXB, 0x00, 0x81}, /* Enable 4-lane MIPI */
+ {ADV7482_I2C_TXB, 0xDA, 0x01}, /* i2c_mipi_pll_en - 1'b1 */
+ {ADV7482_I2C_TXB, 0xC1, 0x3B}, /* ADI Required Write */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_power_up_hdmi_rx[] = {
+
+ /* Disable chip powerdown & Enable HDMI Rx block */
+ {ADV7482_I2C_IO, 0x00, 0x40},
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_power_down_hdmi_rx[] = {
+
+ {ADV7482_I2C_IO, 0x00, 0x30}, /* Disable chip powerdown */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_enable_csi4_csi1[] = {
+
+ {ADV7482_I2C_IO, 0x10, 0xE0}, /* Enable 4-lane CSI Tx & Pixel Port */
+
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_set_virtual_channel0[] = {
+
+ {ADV7482_I2C_TXB, 0x0D, 0x00}, /* Set virtual channel 0 */
+ {ADV7482_I2C_TXA, 0x0D, 0x00}, /* Set virtual channel 0 */
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_set_virtual_channel1[] = {
+
+ {ADV7482_I2C_TXB, 0x0D, 0x40}, /* Set virtual channel 1 */
+ {ADV7482_I2C_TXA, 0x0D, 0x40}, /* Set virtual channel 1 */
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_set_virtual_channel2[] = {
+
+ {ADV7482_I2C_TXB, 0x0D, 0x80}, /* Set virtual channel 2 */
+ {ADV7482_I2C_TXA, 0x0D, 0x80}, /* Set virtual channel 2 */
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+static const struct adv7482_reg_value adv7482_set_virtual_channel3[] = {
+
+ {ADV7482_I2C_TXB, 0x0D, 0xC0}, /* Set virtual channel 3 */
+ {ADV7482_I2C_TXA, 0x0D, 0xC0}, /* Set virtual channel 3 */
+ {ADV7482_I2C_EOR, 0xFF, 0xFF} /* End of register table */
+};
+
+enum decoder_input_interface {
+ DECODER_INPUT_INTERFACE_RGB888,
+ DECODER_INPUT_INTERFACE_YCBCR422,
+};
+
+/**
+ * struct adv7482_link_config - Describes adv7482 hardware configuration
+ * @input_colorspace: The input colorspace (RGB, YUV444, YUV422)
+ */
+struct adv7482_link_config {
+ enum decoder_input_interface input_interface;
+ struct adv7482_reg_value *regs;
+
+ struct adv7482_reg_value *power_up;
+ struct adv7482_reg_value *power_down;
+
+ int (*init_device)(void *);
+ int (*init_controls)(void *);
+ int (*s_power)(void *);
+ int (*s_ctrl)(void *);
+ int (*enum_mbus_code)(void *);
+ int (*set_pad_format)(void *);
+ int (*get_pad_format)(void *);
+ int (*s_std)(void *);
+ int (*querystd)(void *);
+ int (*g_input_status)(void *);
+ int (*s_routing)(void *);
+ int (*g_mbus_config)(void *);
+
+ struct device *dev;
+ bool sw_reset;
+ bool hdmi_in;
+ bool sdp_in;
+ int vc_ch;
+};
+
+struct adv7482_state {
+ struct v4l2_ctrl_handler ctrl_hdl;
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+ /* mutual excl. when accessing chip */
+ struct mutex mutex;
+ int irq;
+ v4l2_std_id curr_norm;
+ bool streaming;
+ const struct adv7482_color_format *cfmt;
+ u32 width;
+ u32 height;
+
+ struct i2c_client *client;
+ unsigned int register_page;
+ struct i2c_client *csi_client;
+ enum v4l2_field field;
+
+ struct device *dev;
+ struct adv7482_link_config mipi_csi2_link[2];
+};
+
+/*****************************************************************************/
+/* Private functions */
+/*****************************************************************************/
+
+#define to_adv7482_sd(_ctrl) (&container_of(_ctrl->handler, \
+ struct adv7482_state, \
+ ctrl_hdl)->sd)
+
+static inline struct adv7482_state *to_state(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct adv7482_state, sd);
+}
+
+/*
+ * adv7482_write_registers() - Write adv7482 device registers
+ * @client: pointer to i2c_client structure
+ * @regs: pointer to adv7482_reg_value structure
+ *
+ * Write the specified adv7482 register's values.
+ */
+static int adv7482_write_registers(struct i2c_client *client,
+ const struct adv7482_reg_value *regs)
+{
+ struct i2c_msg msg;
+ u8 data_buf[2];
+ int ret = -EINVAL;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg.flags = 0;
+ msg.len = 2;
+ msg.buf = &data_buf[0];
+
+ while (regs->addr != ADV7482_I2C_EOR) {
+
+ if (regs->addr == ADV7482_I2C_WAIT)
+ msleep(regs->value);
+ else {
+ msg.addr = regs->addr;
+ data_buf[0] = regs->reg;
+ data_buf[1] = regs->value;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret < 0)
+ break;
+ }
+ regs++;
+ }
+
+ return (ret < 0) ? ret : 0;
+}
+
+/*
+ * adv7482_write_register() - Write adv7482 device register
+ * @client: pointer to i2c_client structure
+ * @addr: i2c slave address of adv7482 device
+ * @reg: adv7482 device register address
+ * @value: the value to be written
+ *
+ * Write the specified adv7482 register's value.
+ */
+static int adv7482_write_register(struct i2c_client *client,
+ u8 addr, u8 reg, u8 value)
+{
+ struct adv7482_reg_value regs[2];
+ int ret;
+
+ regs[0].addr = addr;
+ regs[0].reg = reg;
+ regs[0].value = value;
+ regs[1].addr = ADV7482_I2C_EOR;
+ regs[1].reg = 0xFF;
+ regs[1].value = 0xFF;
+
+ ret = adv7482_write_registers(client, regs);
+
+ return ret;
+}
+
+/*
+ * adv7482_read_register() - Read adv7482 device register
+ * @client: pointer to i2c_client structure
+ * @addr: i2c slave address of adv7482 device
+ * @reg: adv7482 device register address
+ * @value: pointer to the value
+ *
+ * Read the specified adv7482 register's value.
+ */
+static int adv7482_read_register(struct i2c_client *client,
+ u8 addr, u8 reg, u8 *value)
+{
+ struct i2c_msg msg[2];
+ u8 reg_buf, data_buf;
+ int ret;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg[0].addr = addr;
+ msg[0].flags = 0;
+ msg[0].len = 1;
+ msg[0].buf = ®_buf;
+ msg[1].addr = addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = 1;
+ msg[1].buf = &data_buf;
+
+ reg_buf = reg;
+
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret < 0)
+ return ret;
+
+ *value = data_buf;
+
+ return (ret < 0) ? ret : 0;
+}
+
+static int adv7482_read_sdp_main_info(struct i2c_client *client,
+ struct adv7482_sdp_main_info *info)
+{
+ int ret;
+ u8 value;
+
+ ret = adv7482_write_register(client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_CTRL, ADV7482_SDP_RO_MAIN_MAP);
+ if (ret < 0)
+ return ret;
+
+ /* status_reg_10 */
+ ret = adv7482_read_register(client, ADV7482_I2C_SDP,
+ ADV7482_SDP_R_REG_10, &value);
+ if (ret < 0)
+ return ret;
+
+ info->status_reg_10 = value;
+
+ return ret;
+}
+
+static int adv7482_set_video_standard(struct adv7482_state *state,
+ unsigned int std)
+{
+ int ret;
+ u8 value;
+
+ ret = adv7482_read_register(state->client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_INPUT_VIDSEL, &value);
+ if (ret < 0)
+ return ret;
+
+ value &= 0xf;
+ value |= std << 4;
+
+ return adv7482_write_register(state->client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_INPUT_VIDSEL, value);
+}
+
+static v4l2_std_id adv7482_std_to_v4l2(u8 status_reg_10)
+{
+ /* in case V4L2_IN_ST_NO_SIGNAL */
+ if (!(status_reg_10 & ADV7482_SDP_R_REG_10_IN_LOCK))
+ return V4L2_STD_UNKNOWN;
+
+ switch (status_reg_10 & ADV7482_SDP_R_REG_10_AUTOD_MASK) {
+ case ADV7482_SDP_R_REG_10_AUTOD_NTSM_M_J:
+ return V4L2_STD_NTSC;
+ case ADV7482_SDP_R_REG_10_AUTOD_NTSC_4_43:
+ return V4L2_STD_NTSC_443;
+ case ADV7482_SDP_R_REG_10_AUTOD_PAL_M:
+ return V4L2_STD_PAL_M;
+ case ADV7482_SDP_R_REG_10_AUTOD_PAL_60:
+ return V4L2_STD_PAL_60;
+ case ADV7482_SDP_R_REG_10_AUTOD_PAL_B_G:
+ return V4L2_STD_PAL;
+ case ADV7482_SDP_R_REG_10_AUTOD_SECAM:
+ return V4L2_STD_SECAM;
+ case ADV7482_SDP_R_REG_10_AUTOD_PAL_COMB:
+ return V4L2_STD_PAL_Nc | V4L2_STD_PAL_N;
+ case ADV7482_SDP_R_REG_10_AUTOD_SECAM_525:
+ return V4L2_STD_SECAM;
+ default:
+ return V4L2_STD_UNKNOWN;
+ }
+}
+
+static int v4l2_std_to_adv7482(v4l2_std_id std)
+{
+ if (std == V4L2_STD_PAL_60)
+ return ADV7482_SDP_STD_PAL60;
+ if (std == V4L2_STD_NTSC_443)
+ return ADV7482_SDP_STD_NTSC_443;
+ if (std == V4L2_STD_PAL_N)
+ return ADV7482_SDP_STD_PAL_N;
+ if (std == V4L2_STD_PAL_M)
+ return ADV7482_SDP_STD_PAL_M;
+ if (std == V4L2_STD_PAL_Nc)
+ return ADV7482_SDP_STD_PAL_COMB_N;
+ if (std & V4L2_STD_PAL)
+ return ADV7482_SDP_STD_PAL_BG;
+ if (std & V4L2_STD_NTSC)
+ return ADV7482_SDP_STD_NTSC_M;
+ if (std & V4L2_STD_SECAM)
+ return ADV7482_SDP_STD_PAL_SECAM;
+
+ return -EINVAL;
+}
+
+static u32 adv7482_status_to_v4l2(u8 status_reg_10)
+{
+ if (!(status_reg_10 & ADV7482_SDP_R_REG_10_IN_LOCK))
+ return V4L2_IN_ST_NO_SIGNAL;
+
+ return 0;
+}
+
+static int __adv7482_status(struct adv7482_state *state, u32 *status,
+ v4l2_std_id *std)
+{
+ int ret;
+ u8 status_reg_10;
+ struct adv7482_sdp_main_info sdp_info;
+
+ ret = adv7482_read_sdp_main_info(state->client, &sdp_info);
+ if (ret < 0)
+ return ret;
+
+ status_reg_10 = sdp_info.status_reg_10;
+
+ if (status)
+ *status = adv7482_status_to_v4l2(status_reg_10);
+ if (std)
+ *std = adv7482_std_to_v4l2(status_reg_10);
+
+ return 0;
+}
+
+/*
+ * adv7482_get_vid_info() - Get video information
+ * @sd: pointer to standard V4L2 sub-device structure
+ * @progressive: progressive or interlace
+ * @width: line width
+ * @height: lines per frame
+ *
+ * Get video information.
+ */
+static int adv7482_get_vid_info(struct v4l2_subdev *sd, u8 *progressive,
+ u32 *width, u32 *height, u8 *signal)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ u8 hdmi_int;
+ u8 msb;
+ u8 lsb;
+ int ret;
+
+ if (signal)
+ *signal = 0;
+
+ /* decide line width */
+ ret = adv7482_read_register(client, ADV7482_I2C_HDMI,
+ ADV7482_HDMI_STATUS1_REG, &msb);
+ if (ret < 0)
+ return ret;
+
+ if (!(msb & ADV7482_HDMI_VF_LOCKED_FLG) ||
+ !(msb & ADV7482_HDMI_DERF_LOCKED_FLG))
+ return -EIO;
+
+ if (signal)
+ *signal = 1;
+
+ /* decide interlaced or progressive */
+ ret = adv7482_read_register(client, ADV7482_I2C_HDMI,
+ ADV7482_HDMI_STATUS2_REG, &hdmi_int);
+ if (ret < 0)
+ return ret;
+
+ *progressive = 1;
+ if ((hdmi_int & ADV7482_HDMI_IP_FLAG) != 0)
+ *progressive = 0;
+
+ ret = adv7482_read_register(client, ADV7482_I2C_HDMI,
+ ADV7482_HDMI_LWIDTH_REG, &lsb);
+ if (ret < 0)
+ return ret;
+
+ *width = (u32)(ADV7482_HDMI_LWIDTH_MSBS_MASK & msb);
+ *width = (lsb | (*width << 8));
+
+ /* decide lines per frame */
+ ret = adv7482_read_register(client, ADV7482_I2C_HDMI,
+ ADV7482_HDMI_F0HEIGHT_MSBS_REG, &msb);
+ if (ret < 0)
+ return ret;
+
+ ret = adv7482_read_register(client, ADV7482_I2C_HDMI,
+ ADV7482_HDMI_F0HEIGHT_LSBS_REG, &lsb);
+ if (ret < 0)
+ return ret;
+
+ *height = (u32)(ADV7482_HDMI_F0HEIGHT_MSBS_MASK & msb);
+ *height = (lsb | (*height << 8));
+ if (!(*progressive))
+ *height = *height * 2;
+
+ if (*width == 0 || *height == 0)
+ return -EIO;
+
+ return 0;
+}
+
+static int adv7482_set_vid_info(struct v4l2_subdev *sd)
+{
+ struct adv7482_state *state = to_state(sd);
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+ u8 progressive;
+ u8 signal;
+ u8 val = ADV7482_IO_CP_VID_STD_480P;
+ u32 width;
+ u32 height;
+ int ret;
+
+ /* Get video information */
+ ret = adv7482_get_vid_info(sd, &progressive, &width, &height, &signal);
+ if (ret < 0) {
+ width = ADV7482_MAX_WIDTH;
+ height = ADV7482_MAX_HEIGHT;
+ progressive = 1;
+ } else {
+ if ((width == 640) &&
+ (height == 480) && (progressive == 1)) {
+ val = ADV7482_IO_CP_VID_STD_VGA60;
+ dev_info(state->dev,
+ "Changed active resolution to 640x480p\n");
+ } else if ((width == 720) &&
+ (height == 480) && (progressive == 1)) {
+ val = ADV7482_IO_CP_VID_STD_480P;
+ dev_info(state->dev,
+ "Changed active resolution to 720x480p\n");
+ } else if ((width == 720) &&
+ (height == 576) && (progressive == 1)) {
+ val = ADV7482_IO_CP_VID_STD_576P;
+ dev_info(state->dev,
+ "Changed active resolution to 720x576p\n");
+ } else if ((width == 1280) &&
+ (height == 720) && (progressive == 1)) {
+ val = ADV7482_IO_CP_VID_STD_720P;
+ dev_info(state->dev,
+ "Changed active resolution to 1280x720p\n");
+ } else if ((width == 1920) &&
+ (height == 1080) && (progressive == 1)) {
+ val = ADV7482_IO_CP_VID_STD_1080P;
+ dev_info(state->dev,
+ "Changed active resolution to 1920x1080p\n");
+ } else if ((width == 1920) &&
+ (height == 1080) && (progressive == 0)) {
+ val = ADV7482_IO_CP_VID_STD_1080I;
+ dev_info(state->dev,
+ "Changed active resolution to 1920x1080i\n");
+ } else {
+ dev_err(state->dev,
+ "Not support resolution %dx%d%c\n",
+ width, height, (progressive) ? 'p' : 'i');
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * The resolution of 720p, 1080i and 1080p is Hsync width of 40 pixel
+ * clock cycles. These resolutions must be shifted horizontally to
+ * the left in active video mode.
+ */
+ if ((val == ADV7482_IO_CP_VID_STD_1080I) ||
+ (val == ADV7482_IO_CP_VID_STD_1080P)) {
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8B, 0x43);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8C, 0xD4);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8B, 0x4F);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8D, 0xD4);
+ } else if (val == ADV7482_IO_CP_VID_STD_720P) {
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8B, 0x43);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8C, 0xD8);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8B, 0x4F);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8D, 0xD8);
+ } else {
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8B, 0x40);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8C, 0x00);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8B, 0x40);
+ ret = adv7482_write_register(client,
+ ADV7482_I2C_CP, 0x8D, 0x00);
+ }
+
+ ret = adv7482_write_register(client, ADV7482_I2C_IO,
+ ADV7482_IO_CP_VID_STD_REG, val);
+
+ return ret;
+}
+
+/*****************************************************************************/
+/* V4L2 decoder i/f handler for v4l2_subdev_core_ops */
+/*****************************************************************************/
+
+/*
+ * adv7482_querystd() - V4L2 decoder i/f handler for querystd
+ * @sd: ptr to v4l2_subdev struct
+ * @std: standard input video id
+ *
+ * Obtains the video standard input id
+ */
+static int adv7482_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)
+{
+ struct adv7482_state *state = to_state(sd);
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+ int err = mutex_lock_interruptible(&state->mutex);
+
+ if (err)
+ return err;
+
+ if (config->input_interface != DECODER_INPUT_INTERFACE_YCBCR422) {
+ *std = V4L2_STD_ATSC;
+ goto unlock;
+ }
+
+ if (state->streaming) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ err = adv7482_set_video_standard(state,
+ ADV7482_SDP_STD_AD_PAL_BG_NTSC_J_SECAM);
+ if (err)
+ goto unlock;
+
+ msleep(100);
+ err = __adv7482_status(state, NULL, std);
+ if (err)
+ goto unlock;
+
+ err = v4l2_std_to_adv7482(state->curr_norm);
+ if (err < 0)
+ goto unlock;
+
+ err = adv7482_set_video_standard(state, err);
+
+unlock:
+ mutex_unlock(&state->mutex);
+
+ return err;
+}
+
+/*
+ * adv7482_g_input_status() - V4L2 decoder i/f handler for g_input_status
+ * @sd: ptr to v4l2_subdev struct
+ * @status: video input status flag
+ *
+ * Obtains the video input status flags.
+ */
+static int adv7482_g_input_status(struct v4l2_subdev *sd, u32 *status)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct adv7482_state *state = to_state(sd);
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+ u8 status1 = 0;
+ int ret = mutex_lock_interruptible(&state->mutex);
+
+ if (ret)
+ return ret;
+
+ if (config->input_interface == DECODER_INPUT_INTERFACE_YCBCR422)
+ ret = __adv7482_status(state, status, NULL);
+ else {
+ ret = adv7482_read_register(client, ADV7482_I2C_HDMI,
+ ADV7482_HDMI_STATUS1_REG, &status1);
+ if (ret < 0)
+ goto out;
+
+ if (!(status1 & ADV7482_HDMI_VF_LOCKED_FLG))
+ *status = V4L2_IN_ST_NO_SIGNAL;
+ else if (!(status1 & ADV7482_HDMI_DERF_LOCKED_FLG))
+ *status = V4L2_IN_ST_NO_SIGNAL;
+ else
+ *status = 0;
+ }
+
+ ret = 0;
+out:
+ mutex_unlock(&state->mutex);
+ return ret;
+}
+
+static int adv7482_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct adv7482_state *state = to_state(sd);
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+
+ if (code->index != 0)
+ return -EINVAL;
+
+ if (config->input_interface == DECODER_INPUT_INTERFACE_YCBCR422)
+ code->code = MEDIA_BUS_FMT_YUYV8_2X8;
+ else
+ code->code = MEDIA_BUS_FMT_RGB888_1X24;
+
+ return 0;
+}
+
+static int adv7482_mbus_fmt(struct v4l2_subdev *sd,
+ struct v4l2_mbus_framefmt *fmt)
+{
+ struct adv7482_state *state = to_state(sd);
+
+ u8 progressive;
+ u8 signal;
+ u32 width;
+ u32 height;
+ int ret;
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+ u8 status_reg_10;
+ struct adv7482_sdp_main_info sdp_info;
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+ if (config->input_interface == DECODER_INPUT_INTERFACE_YCBCR422) {
+ ret = __adv7482_status(state, NULL, &state->curr_norm);
+ if (ret < 0)
+ return ret;
+
+ fmt->code = MEDIA_BUS_FMT_YUYV8_2X8;
+ fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ fmt->width = 720;
+ fmt->height = state->curr_norm & V4L2_STD_525_60
+ ? 480 : 576;
+
+ /* Get video information */
+ ret = adv7482_read_sdp_main_info(client, &sdp_info);
+ if (ret < 0)
+ return ret;
+
+ status_reg_10 = sdp_info.status_reg_10;
+
+ if ((status_reg_10 & ADV7482_SDP_R_REG_10_IN_LOCK) &&
+ (status_reg_10 & ADV7482_SDP_R_REG_10_FSC_LOCK)
+ && (((status_reg_10 &
+ ADV7482_SDP_R_REG_10_AUTOD_PAL_M) ==
+ ADV7482_SDP_R_REG_10_AUTOD_PAL_M) ||
+ ((status_reg_10 &
+ ADV7482_SDP_R_REG_10_AUTOD_PAL_60) ==
+ ADV7482_SDP_R_REG_10_AUTOD_PAL_60) ||
+ ((status_reg_10 &
+ ADV7482_SDP_R_REG_10_AUTOD_PAL_B_G) ==
+ ADV7482_SDP_R_REG_10_AUTOD_PAL_B_G) ||
+ ((status_reg_10 &
+ ADV7482_SDP_R_REG_10_AUTOD_PAL_COMB) ==
+ ADV7482_SDP_R_REG_10_AUTOD_PAL_COMB)))
+ dev_info(state->dev,
+ "Detected the PAL video input signal\n");
+ else if ((status_reg_10 & ADV7482_SDP_R_REG_10_IN_LOCK)
+ && (status_reg_10 &
+ ADV7482_SDP_R_REG_10_FSC_LOCK) &&
+ (((status_reg_10 &
+ ADV7482_SDP_R_REG_10_AUTOD_NTSC_4_43) ==
+ ADV7482_SDP_R_REG_10_AUTOD_NTSC_4_43) ||
+ ((status_reg_10 &
+ ADV7482_SDP_R_REG_10_AUTOD_MASK) ==
+ ADV7482_SDP_R_REG_10_AUTOD_NTSM_M_J)))
+ dev_info(state->dev,
+ "Detected the NTSC video input signal\n");
+ else
+ dev_info(state->dev,
+ "Not detect any video input signal\n");
+
+ state->width = fmt->width;
+ state->height = fmt->height;
+ state->field = V4L2_FIELD_INTERLACED;
+
+ } else {
+ fmt->code = MEDIA_BUS_FMT_RGB888_1X24;
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
+
+ /* Get video information */
+ ret = adv7482_get_vid_info(sd, &progressive,
+ &width, &height, &signal);
+ if (ret < 0) {
+ width = ADV7482_MAX_WIDTH;
+ height = ADV7482_MAX_HEIGHT;
+ progressive = 1;
+ }
+
+ if (signal)
+ dev_info(state->dev,
+ "Detected the HDMI video input signal (%dx%d%c)\n",
+ width, height, (progressive) ? 'p' : 'i');
+ else
+ dev_info(state->dev,
+ "Not detect any video input signal\n");
+
+ state->width = width;
+ state->height = height;
+ state->field =
+ (progressive) ? V4L2_FIELD_NONE : V4L2_FIELD_INTERLACED;
+
+ fmt->width = state->width;
+ fmt->height = state->height;
+ }
+
+ return 0;
+}
+
+static int adv7482_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm)
+{
+ *norm = V4L2_STD_ALL;
+ return 0;
+}
+
+static int adv7482_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm)
+{
+ struct adv7482_state *state = to_state(sd);
+
+ *norm = state->curr_norm;
+
+ return 0;
+}
+
+static int adv7482_program_std(struct adv7482_state *state)
+{
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+ int ret;
+
+ if (config->input_interface != DECODER_INPUT_INTERFACE_YCBCR422)
+ return -EINVAL;
+
+ ret = v4l2_std_to_adv7482(state->curr_norm);
+ if (ret < 0)
+ return ret;
+
+ ret = adv7482_set_video_standard(state, ret);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int adv7482_s_std(struct v4l2_subdev *sd, v4l2_std_id std)
+{
+ struct adv7482_state *state = to_state(sd);
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+ int ret = mutex_lock_interruptible(&state->mutex);
+
+ if (ret)
+ return ret;
+
+ if (config->input_interface != DECODER_INPUT_INTERFACE_YCBCR422) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Make sure we can support this std */
+ ret = v4l2_std_to_adv7482(std);
+ if (ret < 0)
+ goto out;
+
+ state->curr_norm = std;
+
+ ret = adv7482_program_std(state);
+out:
+ mutex_unlock(&state->mutex);
+ return ret;
+}
+
+static int adv7482_set_field_mode(struct adv7482_state *state)
+{
+ /* FIXME */
+
+ return 0;
+}
+
+static int adv7482_set_power(struct adv7482_state *state, bool on)
+{
+ u8 val;
+ int ret;
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+
+ if (config->input_interface == DECODER_INPUT_INTERFACE_YCBCR422) {
+ ret = adv7482_read_register(state->client, ADV7482_I2C_TXB,
+ 0x1E, &val);
+ if (ret < 0)
+ return ret;
+
+ if (on && ((val & 0x40) == 0)) {
+ /* Power up */
+ ret = adv7482_write_registers(state->client,
+ adv7482_power_up_txb_1lane);
+ if (ret < 0)
+ goto fail;
+ } else {
+ /* Power down */
+ ret = adv7482_write_registers(state->client,
+ adv7482_power_down_txb_1lane);
+ if (ret < 0)
+ goto fail;
+ }
+ } else {
+ /* set active resolution */
+ ret = adv7482_set_vid_info(&state->sd);
+ ret = adv7482_read_register(state->client, ADV7482_I2C_TXA,
+ 0x1E, &val);
+ if (ret < 0)
+ return ret;
+
+ if (on && ((val & 0x40) == 0)) {
+ /* Power up */
+ ret = adv7482_write_registers(state->client,
+ adv7482_power_up_txa_4lane);
+ if (ret < 0)
+ goto fail;
+ } else {
+ /* Power down */
+ ret = adv7482_write_registers(state->client,
+ adv7482_power_down_txa_4lane);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+
+ return 0;
+fail:
+ pr_info("%s: Failed set power operation, ret = %d\n", __func__, ret);
+ return ret;
+}
+
+static int adv7482_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct adv7482_state *state = to_state(sd);
+ int ret;
+
+ ret = mutex_lock_interruptible(&state->mutex);
+ if (ret)
+ return ret;
+
+ ret = adv7482_set_power(state, enable);
+ if (ret)
+ goto out;
+
+ state->streaming = enable;
+
+out:
+ mutex_unlock(&state->mutex);
+ return ret;
+}
+
+static int adv7482_get_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct adv7482_state *state = to_state(sd);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ format->format = *v4l2_subdev_get_try_format(sd, cfg, 0);
+ else {
+ adv7482_mbus_fmt(sd, &format->format);
+ format->format.field = state->field;
+ }
+
+ return 0;
+}
+
+static int adv7482_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct adv7482_state *state = to_state(sd);
+ struct v4l2_mbus_framefmt *framefmt;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+ framefmt = &format->format;
+ if (state->field != format->format.field) {
+ state->field = format->format.field;
+ adv7482_set_field_mode(state);
+ }
+ } else {
+ adv7482_mbus_fmt(sd, &format->format);
+
+ if (format->format.field == V4L2_FIELD_ANY)
+ format->format.field = state->field;
+
+ return 0;
+ }
+
+ return adv7482_mbus_fmt(sd, framefmt);
+}
+
+/*
+ * adv7482_g_mbus_config() - V4L2 decoder i/f handler for g_mbus_config
+ * @sd: pointer to standard V4L2 sub-device structure
+ * @cfg: pointer to V4L2 mbus_config structure
+ *
+ * Get mbus configuration.
+ */
+static int adv7482_g_mbus_config(struct v4l2_subdev *sd,
+ struct v4l2_mbus_config *cfg)
+{
+ struct adv7482_state *state = to_state(sd);
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+
+ if (config->input_interface == DECODER_INPUT_INTERFACE_YCBCR422)
+ cfg->flags = V4L2_MBUS_CSI2_1_LANE |
+ V4L2_MBUS_CSI2_CHANNEL_0 |
+ V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
+ else
+ cfg->flags = V4L2_MBUS_CSI2_LANES |
+ V4L2_MBUS_CSI2_CHANNELS |
+ V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
+
+ cfg->type = V4L2_MBUS_CSI2;
+
+ return 0;
+}
+
+/*****************************************************************************/
+/* V4L2 decoder i/f handler for v4l2_ctrl_ops */
+/*****************************************************************************/
+static int adv7482_cp_s_ctrl(struct v4l2_ctrl *ctrl, struct i2c_client *client)
+{
+ u8 val;
+ int ret;
+
+ /* Enable video adjustment first */
+ ret = adv7482_read_register(client, ADV7482_I2C_CP,
+ ADV7482_CP_VID_ADJ_REG, &val);
+ if (ret < 0)
+ return ret;
+ val |= ADV7482_CP_VID_ADJ_ENABLE;
+ ret = adv7482_write_register(client, ADV7482_I2C_CP,
+ ADV7482_CP_VID_ADJ_REG, val);
+ if (ret < 0)
+ return ret;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ if ((ctrl->val < ADV7482_CP_BRI_MIN) ||
+ (ctrl->val > ADV7482_CP_BRI_MAX))
+ ret = -ERANGE;
+ else
+ ret = adv7482_write_register(client, ADV7482_I2C_CP,
+ ADV7482_CP_BRI_REG, ctrl->val);
+ break;
+ case V4L2_CID_HUE:
+ if ((ctrl->val < ADV7482_CP_HUE_MIN) ||
+ (ctrl->val > ADV7482_CP_HUE_MAX))
+ ret = -ERANGE;
+ else
+ ret = adv7482_write_register(client, ADV7482_I2C_CP,
+ ADV7482_CP_HUE_REG, ctrl->val);
+ break;
+ case V4L2_CID_CONTRAST:
+ if ((ctrl->val < ADV7482_CP_CON_MIN) ||
+ (ctrl->val > ADV7482_CP_CON_MAX))
+ ret = -ERANGE;
+ else
+ ret = adv7482_write_register(client, ADV7482_I2C_CP,
+ ADV7482_CP_CON_REG, ctrl->val);
+ break;
+ case V4L2_CID_SATURATION:
+ if ((ctrl->val < ADV7482_CP_SAT_MIN) ||
+ (ctrl->val > ADV7482_CP_SAT_MAX))
+ ret = -ERANGE;
+ else
+ ret = adv7482_write_register(client, ADV7482_I2C_CP,
+ ADV7482_CP_SAT_REG, ctrl->val);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int adv7482_sdp_s_ctrl(struct v4l2_ctrl *ctrl, struct i2c_client *client)
+{
+ int ret;
+
+ ret = adv7482_write_register(client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_CTRL, ADV7482_SDP_MAIN_MAP_RW);
+ if (ret < 0)
+ return ret;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ if ((ctrl->val < ADV7482_SDP_BRI_MIN) ||
+ (ctrl->val > ADV7482_SDP_BRI_MAX))
+ ret = -ERANGE;
+ else
+ ret = adv7482_write_register(client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_BRI, ctrl->val);
+ break;
+ case V4L2_CID_HUE:
+ if ((ctrl->val < ADV7482_SDP_HUE_MIN) ||
+ (ctrl->val > ADV7482_SDP_HUE_MAX))
+ ret = -ERANGE;
+ else
+ /*Hue is inverted according to HSL chart */
+ ret = adv7482_write_register(client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_HUE, -ctrl->val);
+ break;
+ case V4L2_CID_CONTRAST:
+ if ((ctrl->val < ADV7482_SDP_CON_MIN) ||
+ (ctrl->val > ADV7482_SDP_CON_MAX))
+ ret = -ERANGE;
+ else
+ ret = adv7482_write_register(client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_CON, ctrl->val);
+ break;
+ case V4L2_CID_SATURATION:
+ /*
+ *This could be V4L2_CID_BLUE_BALANCE/V4L2_CID_RED_BALANCE
+ *Let's not confuse the user, everybody understands saturation
+ */
+ if ((ctrl->val < ADV7482_SDP_SAT_MIN) ||
+ (ctrl->val > ADV7482_SDP_SAT_MAX))
+ ret = -ERANGE;
+ else {
+ ret = adv7482_write_register(client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_SD_SAT_CB, ctrl->val);
+ if (ret < 0)
+ break;
+
+ ret = adv7482_write_register(client, ADV7482_I2C_SDP,
+ ADV7482_SDP_REG_SD_SAT_CR, ctrl->val);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/*
+ * adv7482_s_ctrl() - V4L2 decoder i/f handler for s_ctrl
+ * @ctrl: pointer to standard V4L2 control structure
+ *
+ * Set a control in ADV7482 decoder device.
+ */
+static int adv7482_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *sd = to_adv7482_sd(ctrl);
+ struct adv7482_state *state = to_state(sd);
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ int ret = mutex_lock_interruptible(&state->mutex);
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+
+ if (ret)
+ return ret;
+
+ if (config->input_interface == DECODER_INPUT_INTERFACE_YCBCR422)
+ ret = adv7482_sdp_s_ctrl(ctrl, client);
+ else
+ ret = adv7482_cp_s_ctrl(ctrl, client);
+
+ mutex_unlock(&state->mutex);
+
+ return ret;
+}
+
+static const struct v4l2_subdev_video_ops adv7482_video_ops = {
+ .g_std = adv7482_g_std,
+ .s_std = adv7482_s_std,
+ .querystd = adv7482_querystd,
+ .g_input_status = adv7482_g_input_status,
+ .g_tvnorms = adv7482_g_tvnorms,
+ .g_mbus_config = adv7482_g_mbus_config,
+ .s_stream = adv7482_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops adv7482_pad_ops = {
+ .enum_mbus_code = adv7482_enum_mbus_code,
+ .set_fmt = adv7482_set_pad_format,
+ .get_fmt = adv7482_get_pad_format,
+};
+
+static const struct v4l2_subdev_ops adv7482_ops = {
+ .video = &adv7482_video_ops,
+ .pad = &adv7482_pad_ops,
+};
+
+static const struct v4l2_ctrl_ops adv7482_ctrl_ops = {
+ .s_ctrl = adv7482_s_ctrl,
+};
+
+/*
+ * adv7482_init_controls() - Init controls
+ * @state: pointer to private state structure
+ *
+ * Init ADV7482 supported control handler.
+ */
+static int adv7482_cp_init_controls(struct adv7482_state *state)
+{
+ v4l2_ctrl_handler_init(&state->ctrl_hdl, 4);
+
+ v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7482_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, ADV7482_CP_BRI_MIN,
+ ADV7482_CP_BRI_MAX, 1, ADV7482_CP_BRI_DEF);
+ v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7482_ctrl_ops,
+ V4L2_CID_CONTRAST, ADV7482_CP_CON_MIN,
+ ADV7482_CP_CON_MAX, 1, ADV7482_CP_CON_DEF);
+ v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7482_ctrl_ops,
+ V4L2_CID_SATURATION, ADV7482_CP_SAT_MIN,
+ ADV7482_CP_SAT_MAX, 1, ADV7482_CP_SAT_DEF);
+ v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7482_ctrl_ops,
+ V4L2_CID_HUE, ADV7482_CP_HUE_MIN,
+ ADV7482_CP_HUE_MAX, 1, ADV7482_CP_HUE_DEF);
+ state->sd.ctrl_handler = &state->ctrl_hdl;
+ if (state->ctrl_hdl.error) {
+ int err = state->ctrl_hdl.error;
+
+ v4l2_ctrl_handler_free(&state->ctrl_hdl);
+ return err;
+ }
+ v4l2_ctrl_handler_setup(&state->ctrl_hdl);
+
+ return 0;
+}
+
+static int adv7482_sdp_init_controls(struct adv7482_state *state)
+{
+ v4l2_ctrl_handler_init(&state->ctrl_hdl, 4);
+
+ v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7482_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, ADV7482_SDP_BRI_MIN,
+ ADV7482_SDP_BRI_MAX, 1, ADV7482_SDP_BRI_DEF);
+ v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7482_ctrl_ops,
+ V4L2_CID_CONTRAST, ADV7482_SDP_CON_MIN,
+ ADV7482_SDP_CON_MAX, 1, ADV7482_SDP_CON_DEF);
+ v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7482_ctrl_ops,
+ V4L2_CID_SATURATION, ADV7482_SDP_SAT_MIN,
+ ADV7482_SDP_SAT_MAX, 1, ADV7482_SDP_SAT_DEF);
+ v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7482_ctrl_ops,
+ V4L2_CID_HUE, ADV7482_SDP_HUE_MIN,
+ ADV7482_SDP_HUE_MAX, 1, ADV7482_SDP_HUE_DEF);
+
+ state->sd.ctrl_handler = &state->ctrl_hdl;
+ if (state->ctrl_hdl.error) {
+ int err = state->ctrl_hdl.error;
+
+ v4l2_ctrl_handler_free(&state->ctrl_hdl);
+ return err;
+ }
+ v4l2_ctrl_handler_setup(&state->ctrl_hdl);
+
+ return 0;
+}
+
+static void adv7482_exit_controls(struct adv7482_state *state)
+{
+ v4l2_ctrl_handler_free(&state->ctrl_hdl);
+}
+
+/*****************************************************************************/
+/* i2c driver interface handlers */
+/*****************************************************************************/
+
+static int adv7482_parse_dt(struct device_node *np,
+ struct adv7482_link_config *config)
+{
+ struct v4l2_of_endpoint bus_cfg;
+ struct device_node *endpoint;
+ const char *str;
+ int ret;
+ int ch;
+
+ /* Parse the endpoint. */
+ endpoint = of_graph_get_next_endpoint(np, NULL);
+ if (!endpoint)
+ return -EINVAL;
+
+ v4l2_of_parse_endpoint(endpoint, &bus_cfg);
+ of_node_put(endpoint);
+
+ /* check input-interface */
+ ret = of_property_read_string(np, "adi,input-interface", &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "rgb888")) {
+ config->input_interface = DECODER_INPUT_INTERFACE_RGB888;
+ config->regs =
+ (struct adv7482_reg_value *)adv7482_init_txa_4lane;
+ config->power_up =
+ (struct adv7482_reg_value *)adv7482_power_up_txa_4lane;
+ config->power_down =
+ (struct adv7482_reg_value *)adv7482_power_down_txa_4lane;
+ config->init_controls =
+ (int (*)(void *))adv7482_cp_init_controls;
+ } else {
+ config->input_interface = DECODER_INPUT_INTERFACE_YCBCR422;
+ config->regs =
+ (struct adv7482_reg_value *)adv7482_init_txb_1lane;
+ config->power_up =
+ (struct adv7482_reg_value *)adv7482_power_up_txb_1lane;
+ config->power_down =
+ (struct adv7482_reg_value *)adv7482_power_down_txb_1lane;
+ config->init_controls =
+ (int (*)(void *))adv7482_sdp_init_controls;
+ }
+
+ ret = of_property_read_string(np, "adi,input-hdmi", &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "on"))
+ config->hdmi_in = 1;
+ else
+ config->hdmi_in = 0;
+
+ ret = of_property_read_string(np, "adi,input-sdp", &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "on"))
+ config->sdp_in = 1;
+ else
+ config->sdp_in = 0;
+
+ ret = of_property_read_string(np, "adi,sw-reset", &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "on"))
+ config->sw_reset = 1;
+ else
+ config->sw_reset = 0;
+
+ ret = of_property_read_u32(np, "adi,virtual-channel", &ch);
+ if (ret < 0)
+ return ret;
+
+ if ((ch < 0) || (ch > 3))
+ return -EINVAL;
+
+ config->vc_ch = ch;
+
+ config->init_device = NULL;
+ config->s_power = NULL;
+ config->s_ctrl = NULL;
+ config->enum_mbus_code = NULL;
+ config->set_pad_format = NULL;
+ config->get_pad_format = NULL;
+ config->s_std = NULL;
+ config->querystd = NULL;
+ config->g_input_status = NULL;
+ config->s_routing = NULL;
+ config->g_mbus_config = NULL;
+
+ return 0;
+}
+
+/*
+ * adv7482_probe - Probe a ADV7482 device
+ * @client: pointer to i2c_client structure
+ * @id: pointer to i2c_device_id structure
+ *
+ * Initialize the ADV7482 device
+ */
+static int adv7482_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct adv7482_state *state;
+ struct adv7482_link_config link_config;
+ struct device *dev = &client->dev;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ /* Check if the adapter supports the needed features */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EIO;
+
+ v4l_info(client, "chip found @ 0x%02x (%s)\n",
+ client->addr << 1, client->adapter->name);
+
+ state = kzalloc(sizeof(struct adv7482_state), GFP_KERNEL);
+ if (state == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ state->client = client;
+ state->irq = client->irq;
+
+ ret = adv7482_parse_dt(dev->of_node, &link_config);
+ if (ret) {
+ dev_err(&client->dev, "adv7482 parse error\n");
+ return ret;
+ }
+
+ state->mipi_csi2_link[0].input_interface = link_config.input_interface;
+
+ mutex_init(&state->mutex);
+ state->curr_norm = V4L2_STD_NTSC;
+ sd = &state->sd;
+ state->width = ADV7482_MAX_WIDTH;
+ state->height = ADV7482_MAX_HEIGHT;
+ state->field = V4L2_FIELD_NONE;
+
+ v4l2_i2c_subdev_init(sd, client, &adv7482_ops);
+
+ sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ state->dev = dev;
+ state->mipi_csi2_link[0].dev = dev;
+
+ /* SW reset ADV7482 to its default values */
+ if (link_config.sw_reset) {
+ ret = adv7482_write_registers(client, adv7482_sw_reset);
+ if (ret < 0)
+ goto err_unreg_subdev;
+
+ /* check rd_info */
+ {
+ u8 msb;
+ u8 lsb;
+
+ ret = adv7482_read_register(client, ADV7482_I2C_IO,
+ ADV7482_IO_RD_INFO1_REG, &lsb);
+ if (ret < 0)
+ return ret;
+
+ ret = adv7482_read_register(client, ADV7482_I2C_IO,
+ ADV7482_IO_RD_INFO2_REG, &msb);
+ if (ret < 0)
+ return ret;
+
+ v4l_info(client, "adv7482 revision is %02x%02x\n",
+ lsb, msb);
+ }
+ }
+
+ if (link_config.hdmi_in) {
+ ret = adv7482_write_registers(client,
+ adv7482_init_txa_4lane);
+ if (ret < 0)
+ goto err_unreg_subdev;
+
+ /* Power down */
+ ret = adv7482_write_registers(client,
+ adv7482_power_down_txa_4lane);
+ if (ret < 0)
+ goto err_unreg_subdev;
+
+ v4l_info(client, "adv7482 txa power down\n");
+ } else
+ v4l_info(client, "adv7482 hdmi_in is disabled.\n");
+
+ /* Initializes ADV7482 to its default values */
+ if (link_config.sdp_in) {
+ ret = adv7482_write_registers(client,
+ adv7482_init_txb_1lane);
+ if (ret < 0)
+ goto err_unreg_subdev;
+
+ /* Power down */
+ ret = adv7482_write_registers(client,
+ adv7482_power_down_txb_1lane);
+ if (ret < 0)
+ goto err_unreg_subdev;
+
+ v4l_info(client, "adv7482 txb power down\n");
+ } else
+ v4l_info(client, "adv7482 sdp_in is disabled.\n");
+
+ if (link_config.sdp_in && link_config.hdmi_in) {
+ /* Power up hdmi rx */
+ ret = adv7482_write_registers(client,
+ adv7482_power_up_hdmi_rx);
+ if (ret < 0)
+ goto err_unreg_subdev;
+
+ /* Enable csi4 and sci1 */
+ ret = adv7482_write_registers(client,
+ adv7482_enable_csi4_csi1);
+ if (ret < 0)
+ goto err_unreg_subdev;
+
+ v4l_info(client, "adv7482 enable csi1 and csi4\n");
+ }
+
+ if (link_config.input_interface == DECODER_INPUT_INTERFACE_YCBCR422)
+ ret = adv7482_sdp_init_controls(state);
+ else
+ ret = adv7482_cp_init_controls(state);
+ if (ret)
+ goto err_unreg_subdev;
+
+ /* Setting virtual channel for ADV7482 */
+ if (link_config.vc_ch == 0)
+ ret = adv7482_write_registers(client,
+ adv7482_set_virtual_channel0);
+ else if (link_config.vc_ch == 1)
+ ret = adv7482_write_registers(client,
+ adv7482_set_virtual_channel1);
+ else if (link_config.vc_ch == 2)
+ ret = adv7482_write_registers(client,
+ adv7482_set_virtual_channel2);
+ else if (link_config.vc_ch == 3)
+ ret = adv7482_write_registers(client,
+ adv7482_set_virtual_channel3);
+ if (ret < 0)
+ goto err_unreg_subdev;
+
+ state->pad.flags = MEDIA_PAD_FL_SOURCE;
+ sd->entity.flags |= MEDIA_ENT_F_ATV_DECODER;
+ ret = media_entity_pads_init(&sd->entity, 1, &state->pad);
+ if (ret)
+ goto err_free_ctrl;
+
+ ret = v4l2_async_register_subdev(sd);
+ if (ret)
+ goto err_free_ctrl;
+
+ return 0;
+
+err_free_ctrl:
+ adv7482_exit_controls(state);
+
+err_unreg_subdev:
+ mutex_destroy(&state->mutex);
+ v4l2_device_unregister_subdev(sd);
+ kfree(state);
+err:
+ dev_err(&client->dev, ": Failed to probe: %d\n", ret);
+ return ret;
+}
+
+/*
+ * adv7482_remove - Remove ADV7482 device support
+ * @client: pointer to i2c_client structure
+ *
+ * Reset the ADV7482 device
+ */
+static int adv7482_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct adv7482_state *state = to_state(sd);
+
+ v4l2_async_unregister_subdev(sd);
+
+ media_entity_cleanup(&sd->entity);
+ adv7482_exit_controls(state);
+
+ mutex_destroy(&state->mutex);
+ kfree(to_state(sd));
+ return 0;
+}
+
+static const struct i2c_device_id adv7482_id[] = {
+ {DRIVER_NAME, 0},
+ {},
+};
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * adv7482_suspend - Suspend ADV7482 device
+ * @client: pointer to i2c_client structure
+ * @state: power management state
+ *
+ * Power down the ADV7482 device
+ */
+static int adv7482_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret;
+
+ ret = adv7482_write_register(client, ADV7482_I2C_IO,
+ ADV7482_IO_PWR_MAN_REG, ADV7482_IO_PWR_OFF);
+
+ return ret;
+}
+
+/*
+ * adv7482_resume - Resume ADV7482 device
+ * @client: pointer to i2c_client structure
+ *
+ * Power on and initialize the ADV7482 device
+ */
+static int adv7482_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct adv7482_link_config link_config;
+ int ret;
+
+ ret = adv7482_write_register(client, ADV7482_I2C_IO,
+ ADV7482_IO_PWR_MAN_REG, ADV7482_IO_PWR_ON);
+ if (ret < 0)
+ return ret;
+
+ ret = adv7482_parse_dt(dev->of_node, &link_config);
+ if (ret)
+ return ret;
+
+ /* Initializes ADV7482 to its default values */
+ ret = adv7482_write_registers(client, link_config.regs);
+
+ /* Power down */
+ ret = adv7482_write_registers(client, link_config.power_down);
+
+ if (link_config.sdp_in && link_config.hdmi_in) {
+ /* Power up hdmi rx */
+ ret = adv7482_write_registers(client,
+ adv7482_power_up_hdmi_rx);
+ if (ret < 0)
+ return ret;
+
+ /* Enable csi4 and sci1 */
+ ret = adv7482_write_registers(client,
+ adv7482_enable_csi4_csi1);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(adv7482_pm_ops, adv7482_suspend, adv7482_resume);
+#define ADV7482_PM_OPS (&adv7482_pm_ops)
+
+#else
+#define ADV7482_PM_OPS NULL
+#endif
+
+MODULE_DEVICE_TABLE(i2c, adv7482_id);
+
+static const struct of_device_id adv7482_of_ids[] = {
+ { .compatible = "adi,adv7482", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adv7482_of_ids);
+
+static struct i2c_driver adv7482_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = ADV7482_PM_OPS,
+ .of_match_table = adv7482_of_ids,
+ },
+ .probe = adv7482_probe,
+ .remove = adv7482_remove,
+ .id_table = adv7482_id,
+};
+
+module_i2c_driver(adv7482_driver);
+
+MODULE_DESCRIPTION("HDMI Receiver ADV7482 video decoder driver");
+MODULE_ALIAS("platform:adv7482");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Koji Matsuoka <koji.matsuoka.xm@renesas.com>");
diff --git a/drivers/media/media-entity.c b/drivers/media/media-entity.c
index c68239e..4d03ea7 100644
--- a/drivers/media/media-entity.c
+++ b/drivers/media/media-entity.c
@@ -242,6 +242,35 @@ EXPORT_SYMBOL_GPL(media_entity_pads_init);
* Graph traversal
*/
+/**
+ * media_entity_has_route - Check if two entity pads are connected internally
+ * @entity: The entity
+ * @pad0: The first pad index
+ * @pad1: The second pad index
+ *
+ * This function can be used to check whether two pads of an entity are
+ * connected internally in the entity.
+ *
+ * The caller must hold entity->source->parent->mutex.
+ *
+ * Return: true if the pads are connected internally and false otherwise.
+ */
+bool media_entity_has_route(struct media_entity *entity, unsigned int pad0,
+ unsigned int pad1)
+{
+ if (pad0 >= entity->num_pads || pad1 >= entity->num_pads)
+ return false;
+
+ if (pad0 == pad1)
+ return true;
+
+ if (!entity->ops || !entity->ops->has_route)
+ return true;
+
+ return entity->ops->has_route(entity, pad0, pad1);
+}
+EXPORT_SYMBOL_GPL(media_entity_has_route);
+
static struct media_entity *
media_entity_other(struct media_entity *entity, struct media_link *link)
{
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index e9fc288..ba2b892 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -328,6 +328,27 @@
To compile this driver as a module, choose M here: the module
will be called vsp1.
+config VIDEO_RENESAS_DEBUG
+ bool "Renesas VSP1 underrun debug messages"
+ depends on VIDEO_RENESAS_VSP1
+ ---help---
+ Enable debug underrun messages on Renesas VSP1 Video Processing
+ Engine driver.
+ If you set to enable, When an underrun occurred, a debug underrun
+ message is output.
+
+config VIDEO_RENESAS_VSP_ALPHA_BIT_ARGB1555
+ int "Renesas VSP alpha bit function of ARGB1555"
+ depends on VIDEO_RENESAS_VSP1
+ range 0 1
+ default 0
+ help
+ Choose this option if you select A bit function of ARGB1555.
+ If you set value to 0, pixel alpha blending is performed
+ when the value of A is 0.
+ If you set value to 1, pixel alpha blending is performed
+ when the value of A is 1.
+
config VIDEO_TI_VPE
tristate "TI VPE (Video Processing Engine) driver"
depends on VIDEO_DEV && VIDEO_V4L2
diff --git a/drivers/media/platform/rcar-vin/Kconfig b/drivers/media/platform/rcar-vin/Kconfig
index 111d2a1..64a55e7 100644
--- a/drivers/media/platform/rcar-vin/Kconfig
+++ b/drivers/media/platform/rcar-vin/Kconfig
@@ -5,7 +5,27 @@
select VIDEOBUF2_DMA_CONTIG
---help---
Support for Renesas R-Car Video Input (VIN) driver.
- Supports R-Car Gen2 SoCs.
+ Supports R-Car Gen2 and Gen3 SoCs.
To compile this driver as a module, choose M here: the
module will be called rcar-vin.
+
+config VIDEO_RCAR_VIN_DEBUG
+ bool "Renesas VIN overflow debug messages"
+ depends on VIDEO_RCAR_VIN
+ ---help---
+ Enable debug overflow messages on R-Car Video
+ Input driver.
+ If you set to enable, When an overflow occurred,
+ a debug overflow message is output.
+
+config VIDEO_RCAR_CSI2
+ tristate "R-Car MIPI CSI-2 Interface driver"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF
+ depends on ARCH_RENESAS || COMPILE_TEST
+ ---help---
+ Support for Renesas R-Car MIPI CSI-2 interface driver.
+ Supports R-Car Gen3 SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rcar-csi2.
diff --git a/drivers/media/platform/rcar-vin/Makefile b/drivers/media/platform/rcar-vin/Makefile
index 48c5632..d159605 100644
--- a/drivers/media/platform/rcar-vin/Makefile
+++ b/drivers/media/platform/rcar-vin/Makefile
@@ -1,3 +1,5 @@
-rcar-vin-objs = rcar-core.o rcar-dma.o rcar-v4l2.o
+rcar-vin-objs = rcar-core.o rcar-dma.o rcar-v4l2.o rcar-group.o
obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin.o
+
+obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
index 098a0b1..0fa2ed4 100644
--- a/drivers/media/platform/rcar-vin/rcar-core.c
+++ b/drivers/media/platform/rcar-vin/rcar-core.c
@@ -26,36 +26,132 @@
#include "rcar-vin.h"
/* -----------------------------------------------------------------------------
- * Async notifier
+ * Subdevice group helpers
+ */
+
+#define rvin_group_call_func(v, f, args...) \
+ (v->slave.v4l2_dev ? vin_to_group(v)->f(&v->slave, ##args) : -ENODEV)
+
+int rvin_subdev_get(struct rvin_dev *vin)
+{
+ int i, num = 0;
+
+ for (i = 0; i < RVIN_INPUT_MAX; i++) {
+ vin->inputs[i].type = RVIN_INPUT_NONE;
+ vin->inputs[i].hint = false;
+ }
+
+ /* Get inputs from CSI2 group */
+ if (vin->slave.v4l2_dev)
+ num = rvin_group_call_func(vin, get, vin->inputs);
+
+ /* Add local digital input */
+ if (num < RVIN_INPUT_MAX && vin->digital.subdev) {
+ vin->inputs[num].type = RVIN_INPUT_DIGITAL;
+ strncpy(vin->inputs[num].name, "Digital", RVIN_INPUT_NAME_SIZE);
+ vin->inputs[num].sink_idx =
+ sd_to_pad_idx(vin->digital.subdev, MEDIA_PAD_FL_SINK);
+ vin->inputs[num].source_idx =
+ sd_to_pad_idx(vin->digital.subdev, MEDIA_PAD_FL_SOURCE);
+ /* If last input was digital we want it again */
+ if (vin->current_input == RVIN_INPUT_DIGITAL)
+ vin->inputs[num].hint = true;
+ }
+
+ /* Make sure we have at least one input */
+ if (vin->inputs[0].type == RVIN_INPUT_NONE) {
+ vin_err(vin, "No inputs for channel with current selection\n");
+ return -EBUSY;
+ }
+ vin->current_input = 0;
+
+ /* Search for hint and prefer digital over CSI2 run over all elements */
+ for (i = 0; i < RVIN_INPUT_MAX; i++)
+ if (vin->inputs[i].hint)
+ vin->current_input = i;
+
+ return 0;
+}
+
+int rvin_subdev_put(struct rvin_dev *vin)
+{
+ /* Store what type of input we used */
+ vin->current_input = vin->inputs[vin->current_input].type;
+
+ if (vin->slave.v4l2_dev)
+ rvin_group_call_func(vin, put);
+
+ return 0;
+}
+
+int rvin_subdev_set_input(struct rvin_dev *vin, struct rvin_input_item *item)
+{
+ if (rvin_input_is_csi(vin))
+ return rvin_group_call_func(vin, set_input, item);
+
+ if (vin->digital.subdev)
+ return 0;
+
+ return -EBUSY;
+}
+
+int rvin_subdev_get_code(struct rvin_dev *vin, u32 *code)
+{
+ if (rvin_input_is_csi(vin))
+ return rvin_group_call_func(vin, get_code, code);
+
+ *code = vin->digital.code;
+ return 0;
+}
+
+int rvin_subdev_get_mbus_cfg(struct rvin_dev *vin,
+ struct v4l2_mbus_config *mbus_cfg)
+{
+ if (rvin_input_is_csi(vin))
+ return rvin_group_call_func(vin, get_mbus_cfg, mbus_cfg);
+
+ *mbus_cfg = vin->digital.mbus_cfg;
+ return 0;
+}
+
+struct v4l2_subdev_pad_config*
+rvin_subdev_alloc_pad_config(struct rvin_dev *vin)
+{
+ struct v4l2_subdev_pad_config *cfg;
+
+ if (rvin_input_is_csi(vin)) {
+ if (rvin_group_call_func(vin, alloc_pad_config, &cfg))
+ return NULL;
+ return cfg;
+ }
+
+ return v4l2_subdev_alloc_pad_config(vin->digital.subdev);
+}
+
+int rvin_subdev_ctrl_add_handler(struct rvin_dev *vin)
+{
+ int ret;
+
+ v4l2_ctrl_handler_free(&vin->ctrl_handler);
+
+ ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16);
+ if (ret < 0)
+ return ret;
+
+ if (rvin_input_is_csi(vin))
+ return rvin_group_call_func(vin, ctrl_add_handler,
+ &vin->ctrl_handler);
+
+ return v4l2_ctrl_add_handler(&vin->ctrl_handler,
+ vin->digital.subdev->ctrl_handler, NULL);
+}
+
+/* -----------------------------------------------------------------------------
+ * Async notifier for local Digital
*/
#define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier)
-static bool rvin_mbus_supported(struct rvin_graph_entity *entity)
-{
- struct v4l2_subdev *sd = entity->subdev;
- struct v4l2_subdev_mbus_code_enum code = {
- .which = V4L2_SUBDEV_FORMAT_ACTIVE,
- };
-
- code.index = 0;
- while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) {
- code.index++;
- switch (code.code) {
- case MEDIA_BUS_FMT_YUYV8_1X16:
- case MEDIA_BUS_FMT_UYVY8_2X8:
- case MEDIA_BUS_FMT_UYVY10_2X10:
- case MEDIA_BUS_FMT_RGB888_1X24:
- entity->code = code.code;
- return true;
- default:
- break;
- }
- }
-
- return false;
-}
-
static int rvin_digital_notify_complete(struct v4l2_async_notifier *notifier)
{
struct rvin_dev *vin = notifier_to_vin(notifier);
@@ -77,7 +173,7 @@ static int rvin_digital_notify_complete(struct v4l2_async_notifier *notifier)
return ret;
}
- return rvin_v4l2_probe(vin);
+ return 0;
}
static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier,
@@ -88,7 +184,6 @@ static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier,
if (vin->digital.subdev == subdev) {
vin_dbg(vin, "unbind digital subdev %s\n", subdev->name);
- rvin_v4l2_remove(vin);
vin->digital.subdev = NULL;
return;
}
@@ -146,7 +241,7 @@ static int rvin_digitial_parse_v4l2(struct rvin_dev *vin,
return 0;
}
-static int rvin_digital_graph_parse(struct rvin_dev *vin)
+static int rvin_digital_get(struct rvin_dev *vin)
{
struct device_node *ep, *np;
int ret;
@@ -158,7 +253,8 @@ static int rvin_digital_graph_parse(struct rvin_dev *vin)
* Port 0 id 0 is local digital input, try to get it.
* Not all instances can or will have this, that is OK
*/
- ep = of_graph_get_endpoint_by_regs(vin->dev->of_node, 0, 0);
+ ep = of_graph_get_endpoint_by_regs(vin->dev->of_node, RVIN_PORT_LOCAL,
+ 0);
if (!ep)
return 0;
@@ -186,13 +282,18 @@ static int rvin_digital_graph_init(struct rvin_dev *vin)
struct v4l2_async_subdev **subdevs = NULL;
int ret;
- ret = rvin_digital_graph_parse(vin);
+ ret = rvin_digital_get(vin);
if (ret)
return ret;
if (!vin->digital.asd.match.of.node) {
vin_dbg(vin, "No digital subdevice found\n");
- return -ENODEV;
+
+ /* OK for Gen3 where we can be part of a subdevice group */
+ if (vin->chip == RCAR_H3 || vin->chip == RCAR_M3)
+ return 0;
+
+ return -EINVAL;
}
/* Register the subdevices notifier. */
@@ -200,7 +301,7 @@ static int rvin_digital_graph_init(struct rvin_dev *vin)
if (subdevs == NULL)
return -ENOMEM;
- subdevs[0] = &vin->digital.asd;
+ subdevs[0] = &vin->digital.asd;
vin_dbg(vin, "Found digital subdevice %s\n",
of_node_full_name(subdevs[0]->match.of.node));
@@ -225,23 +326,50 @@ static int rvin_digital_graph_init(struct rvin_dev *vin)
*/
static const struct of_device_id rvin_of_id_table[] = {
+ { .compatible = "renesas,vin-r8a7796", .data = (void *)RCAR_M3 },
+ { .compatible = "renesas,vin-r8a7795", .data = (void *)RCAR_H3 },
{ .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 },
{ .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 },
{ .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 },
{ .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
{ .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
{ .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
+ { .compatible = "renesas,rcar-gen3-vin", .data = (void *)RCAR_GEN3 },
{ .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 },
{ },
};
MODULE_DEVICE_TABLE(of, rvin_of_id_table);
+static int rvin_probe_channel(struct platform_device *pdev,
+ struct rvin_dev *vin)
+{
+ struct resource *mem;
+ int irq, ret;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (mem == NULL)
+ return -EINVAL;
+
+ vin->base = devm_ioremap_resource(vin->dev, mem);
+ if (IS_ERR(vin->base))
+ return PTR_ERR(vin->base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0)
+ return irq;
+
+ ret = rvin_dma_probe(vin, irq);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int rcar_vin_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct rvin_dev *vin;
- struct resource *mem;
- int irq, ret;
+ int ret;
vin = devm_kzalloc(&pdev->dev, sizeof(*vin), GFP_KERNEL);
if (!vin)
@@ -254,34 +382,76 @@ static int rcar_vin_probe(struct platform_device *pdev)
vin->dev = &pdev->dev;
vin->chip = (enum chip_id)match->data;
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (mem == NULL)
- return -EINVAL;
+ /* Prefer digital input */
+ vin->current_input = RVIN_INPUT_DIGITAL;
- vin->base = devm_ioremap_resource(vin->dev, mem);
- if (IS_ERR(vin->base))
- return PTR_ERR(vin->base);
-
- irq = platform_get_irq(pdev, 0);
- if (irq < 0)
- return irq;
-
- ret = rvin_dma_probe(vin, irq);
+ /* Initialize the top-level structure */
+ ret = v4l2_device_register(vin->dev, &vin->v4l2_dev);
if (ret)
return ret;
- ret = rvin_digital_graph_init(vin);
- if (ret < 0)
- goto error;
+ ret = rvin_probe_channel(pdev, vin);
+ if (ret)
+ goto err_register;
pm_suspend_ignore_children(&pdev->dev, true);
pm_runtime_enable(&pdev->dev);
+ if (vin->chip == RCAR_H3 || vin->chip == RCAR_M3) {
+ vin->api = rvin_group_probe(&pdev->dev, &vin->v4l2_dev);
+
+ if (strcmp(dev_name(vin->v4l2_dev.dev),
+ "e6ef0000.video") == 0)
+ vin->index = RCAR_VIDEO_0;
+ else if (strcmp(dev_name(vin->v4l2_dev.dev),
+ "e6ef1000.video") == 0)
+ vin->index = RCAR_VIDEO_1;
+ else if (strcmp(dev_name(vin->v4l2_dev.dev),
+ "e6ef2000.video") == 0)
+ vin->index = RCAR_VIDEO_2;
+ else if (strcmp(dev_name(vin->v4l2_dev.dev),
+ "e6ef3000.video") == 0)
+ vin->index = RCAR_VIDEO_3;
+ else if (strcmp(dev_name(vin->v4l2_dev.dev),
+ "e6ef4000.video") == 0)
+ vin->index = RCAR_VIDEO_4;
+ else if (strcmp(dev_name(vin->v4l2_dev.dev),
+ "e6ef5000.video") == 0)
+ vin->index = RCAR_VIDEO_5;
+ else if (strcmp(dev_name(vin->v4l2_dev.dev),
+ "e6ef6000.video") == 0)
+ vin->index = RCAR_VIDEO_6;
+ else if (strcmp(dev_name(vin->v4l2_dev.dev),
+ "e6ef7000.video") == 0)
+ vin->index = RCAR_VIDEO_7;
+ else
+ vin->index = RCAR_VIN_CH_NONE;
+
+ } else
+ vin->api = NULL;
+
+ ret = rvin_digital_graph_init(vin);
+ if (ret < 0)
+ goto err_dma;
+
+ ret = rvin_v4l2_probe(vin);
+ if (ret)
+ goto err_dma;
+
platform_set_drvdata(pdev, vin);
+ ret = rvin_subdev_probe(vin);
+ if (ret)
+ goto err_subdev;
+
return 0;
-error:
+
+err_subdev:
+ rvin_v4l2_remove(vin);
+err_dma:
rvin_dma_remove(vin);
+err_register:
+ v4l2_device_unregister(&vin->v4l2_dev);
return ret;
}
@@ -292,16 +462,57 @@ static int rcar_vin_remove(struct platform_device *pdev)
pm_runtime_disable(&pdev->dev);
+ rvin_subdev_remove(vin);
+
+ rvin_v4l2_remove(vin);
+
v4l2_async_notifier_unregister(&vin->notifier);
+ if (vin->api)
+ rvin_group_remove(vin->api);
+
rvin_dma_remove(vin);
+ v4l2_device_unregister(&vin->v4l2_dev);
+
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int rcar_vin_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int rcar_vin_resume(struct device *dev)
+{
+ int ifmd_val = -1;
+ struct rvin_dev *vin = dev_get_drvdata(dev);
+
+ if (vin->index == 0)
+ ifmd_val = 0;
+
+ if (vin->index == 4)
+ ifmd_val = 1;
+
+ if (ifmd_val >= 0)
+ rvin_set_ifmd(vin, ifmd_val);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_vin_pm_ops,
+ rcar_vin_suspend, rcar_vin_resume);
+#define DEV_PM_OPS (&rcar_vin_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver rcar_vin_driver = {
.driver = {
.name = "rcar-vin",
+ .pm = DEV_PM_OPS,
.of_match_table = rvin_of_id_table,
},
.probe = rcar_vin_probe,
diff --git a/drivers/media/platform/rcar-vin/rcar-csi2.c b/drivers/media/platform/rcar-vin/rcar-csi2.c
new file mode 100644
index 0000000..2d4018e
--- /dev/null
+++ b/drivers/media/platform/rcar-vin/rcar-csi2.c
@@ -0,0 +1,608 @@
+/*
+ * Driver for Renesas R-Car MIPI CSI-2
+ *
+ * Copyright (C) 2016 Renesas Electronics Corp.
+ *
+ * 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/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/sys_soc.h>
+
+#include <media/v4l2-of.h>
+#include <media/v4l2-subdev.h>
+
+enum chip_id {
+ RCAR_H3_WS20,
+ RCAR_GEN3,
+};
+
+/* Register offsets */
+#define TREF_REG 0x00 /* Control Timing Select */
+#define SRST_REG 0x04 /* Software Reset */
+#define PHYCNT_REG 0x08 /* PHY Operation Control */
+#define CHKSUM_REG 0x0C /* Checksum Control */
+#define VCDT_REG 0x10 /* Channel Data Type Select */
+#define VCDT2_REG 0x14 /* Channel Data Type Select 2 */
+#define FRDT_REG 0x18 /* Frame Data Type Select */
+#define FLD_REG 0x1C /* Field Detection Control */
+#define ASTBY_REG 0x20 /* Automatic Standby Control */
+#define LNGDT0_REG 0x28 /* Long Data Type Setting 0 */
+#define LNGDT1_REG 0x2C /* Long Data Type Setting 1 */
+#define INTEN_REG 0x30 /* Interrupt Enable */
+#define INTCLOSE_REG 0x34 /* Interrupt Source Mask */
+#define INTSTATE_REG 0x38 /* Interrupt Status Monitor */
+#define INTERRSTATE_REG 0x3C /* Interrupt Error Status Monitor */
+#define SHPDAT_REG 0x40 /* Short Packet Data */
+#define SHPCNT_REG 0x44 /* Short Packet Count */
+#define LINKCNT_REG 0x48 /* LINK Operation Control */
+#define LSWAP_REG 0x4C /* Lane Swap */
+#define PHTW_REG 0x50 /* PHY Test Interface Write */
+#define PHTC_REG 0x58 /* PHY Test Interface Clear */
+#define PHYPLL_REG 0x68 /* PHY Frequency Control */
+#define PHEERM_REG 0x74 /* PHY ESC Error Monitor */
+#define PHCLM_REG 0x78 /* PHY Clock Lane Monitor */
+#define PHDLM_REG 0x7C /* PHY Data Lane Monitor */
+#define CSI0CLKFCPR_REG 0x254/* CSI0CLK Frequency Configuration Preset */
+
+/* Control Timing Select bits */
+#define TREF_TREF (1 << 0)
+
+/* Software Reset bits */
+#define SRST_SRST (1 << 0)
+
+/* PHY Operation Control bits */
+#define PHYCNT_SHUTDOWNZ (1 << 17)
+#define PHYCNT_RSTZ (1 << 16)
+#define PHYCNT_ENABLECLK (1 << 4)
+#define PHYCNT_ENABLE_3 (1 << 3)
+#define PHYCNT_ENABLE_2 (1 << 2)
+#define PHYCNT_ENABLE_1 (1 << 1)
+#define PHYCNT_ENABLE_0 (1 << 0)
+
+/* Checksum Control bits */
+#define CHKSUM_ECC_EN (1 << 1)
+#define CHKSUM_CRC_EN (1 << 0)
+
+/*
+ * Channel Data Type Select bits
+ * VCDT[0-15]: Channel 1 VCDT[16-31]: Channel 2
+ * VCDT2[0-15]: Channel 3 VCDT2[16-31]: Channel 4
+ */
+#define VCDT_VCDTN_EN (1 << 15)
+#define VCDT_SEL_VC(n) ((n & 0x3) << 8)
+#define VCDT_SEL_DTN_ON (1 << 6)
+#define VCDT_SEL_DT(n) ((n & 0x1f) << 0)
+
+/* Field Detection Control bits */
+#define FLD_FLD_NUM(n) ((n & 0xff) << 16)
+#define FLD_FLD_EN4 (1 << 3)
+#define FLD_FLD_EN3 (1 << 2)
+#define FLD_FLD_EN2 (1 << 1)
+#define FLD_FLD_EN (1 << 0)
+
+/* LINK Operation Control bits */
+#define LINKCNT_MONITOR_EN (1 << 31)
+#define LINKCNT_REG_MONI_PACT_EN (1 << 25)
+#define LINKCNT_ICLK_NONSTOP (1 << 24)
+
+/* Lane Swap bits */
+#define LSWAP_L3SEL(n) ((n & 0x3) << 6)
+#define LSWAP_L2SEL(n) ((n & 0x3) << 4)
+#define LSWAP_L1SEL(n) ((n & 0x3) << 2)
+#define LSWAP_L0SEL(n) ((n & 0x3) << 0)
+
+/* PHY Test Interface Clear bits */
+#define PHTC_TESTCLR (1 << 0)
+
+/* PHY Frequency Control bits */
+#define PHYPLL_HSFREQRANGE_80MBPS (0x00 << 16)
+#define PHYPLL_HSFREQRANGE_90MBPS (0x10 << 16)
+#define PHYPLL_HSFREQRANGE_100MBPS (0x20 << 16)
+#define PHYPLL_HSFREQRANGE_110MBPS (0x30 << 16)
+#define PHYPLL_HSFREQRANGE_120MBPS (0x01 << 16)
+#define PHYPLL_HSFREQRANGE_130MBPS (0x11 << 16)
+#define PHYPLL_HSFREQRANGE_140MBPS (0x21 << 16)
+#define PHYPLL_HSFREQRANGE_150MBPS (0x31 << 16)
+#define PHYPLL_HSFREQRANGE_160MBPS (0x02 << 16)
+#define PHYPLL_HSFREQRANGE_170MBPS (0x12 << 16)
+#define PHYPLL_HSFREQRANGE_180MBPS (0x22 << 16)
+#define PHYPLL_HSFREQRANGE_190MBPS (0x32 << 16)
+#define PHYPLL_HSFREQRANGE_205MBPS (0x03 << 16)
+#define PHYPLL_HSFREQRANGE_220MBPS (0x13 << 16)
+#define PHYPLL_HSFREQRANGE_235MBPS (0x23 << 16)
+#define PHYPLL_HSFREQRANGE_250MBPS (0x33 << 16)
+#define PHYPLL_HSFREQRANGE_275MBPS (0x04 << 16)
+#define PHYPLL_HSFREQRANGE_300MBPS (0x14 << 16)
+#define PHYPLL_HSFREQRANGE_325MBPS (0x05 << 16)
+#define PHYPLL_HSFREQRANGE_350MBPS (0x15 << 16)
+#define PHYPLL_HSFREQRANGE_400MBPS (0x25 << 16)
+#define PHYPLL_HSFREQRANGE_450MBPS (0x06 << 16)
+#define PHYPLL_HSFREQRANGE_500MBPS (0x16 << 16)
+#define PHYPLL_HSFREQRANGE_550MBPS (0x07 << 16)
+#define PHYPLL_HSFREQRANGE_600MBPS (0x17 << 16)
+#define PHYPLL_HSFREQRANGE_650MBPS (0x08 << 16)
+#define PHYPLL_HSFREQRANGE_700MBPS (0x18 << 16)
+#define PHYPLL_HSFREQRANGE_750MBPS (0x09 << 16)
+#define PHYPLL_HSFREQRANGE_800MBPS (0x19 << 16)
+#define PHYPLL_HSFREQRANGE_850MBPS (0x29 << 16)
+#define PHYPLL_HSFREQRANGE_900MBPS (0x39 << 16)
+#define PHYPLL_HSFREQRANGE_950MBPS (0x0A << 16)
+#define PHYPLL_HSFREQRANGE_1000MBPS (0x1A << 16)
+#define PHYPLL_HSFREQRANGE_1050MBPS (0x2A << 16)
+#define PHYPLL_HSFREQRANGE_1100MBPS (0x3A << 16)
+#define PHYPLL_HSFREQRANGE_1150MBPS (0x0B << 16)
+#define PHYPLL_HSFREQRANGE_1200MBPS (0x1B << 16)
+#define PHYPLL_HSFREQRANGE_1250MBPS (0x2B << 16)
+#define PHYPLL_HSFREQRANGE_1300MBPS (0x3B << 16)
+#define PHYPLL_HSFREQRANGE_1350MBPS (0x0C << 16)
+#define PHYPLL_HSFREQRANGE_1400MBPS (0x1C << 16)
+#define PHYPLL_HSFREQRANGE_1450MBPS (0x2C << 16)
+#define PHYPLL_HSFREQRANGE_1500MBPS (0x3C << 16)
+
+/* CSI0CLK frequency configuration bit */
+#define CSI0CLKFREQRANGE(n) ((n & 0x3f) << 16)
+
+struct rcar_csi2 {
+ struct device *dev;
+ void __iomem *base;
+ spinlock_t lock;
+
+ unsigned short lanes;
+ unsigned char swap[4];
+
+ struct v4l2_subdev subdev;
+ struct v4l2_mbus_framefmt mf;
+
+ u32 vc_num;
+ enum chip_id chip;
+};
+
+#define csi_dbg(p, fmt, arg...) dev_dbg(p->dev, fmt, ##arg)
+#define csi_info(p, fmt, arg...) dev_info(p->dev, fmt, ##arg)
+#define csi_warn(p, fmt, arg...) dev_warn(p->dev, fmt, ##arg)
+#define csi_err(p, fmt, arg...) dev_err(p->dev, fmt, ##arg)
+
+static irqreturn_t rcar_csi2_irq(int irq, void *data)
+{
+ struct rcar_csi2 *priv = data;
+ u32 int_status;
+ unsigned int handled = 0;
+
+ spin_lock(&priv->lock);
+
+ int_status = ioread32(priv->base + INTSTATE_REG);
+ if (!int_status)
+ goto done;
+
+ /* ack interrupts */
+ iowrite32(int_status, priv->base + INTSTATE_REG);
+ handled = 1;
+
+done:
+ spin_unlock(&priv->lock);
+
+ return IRQ_RETVAL(handled);
+
+}
+
+static void rcar_csi2_reset(struct rcar_csi2 *priv)
+{
+ iowrite32(SRST_SRST, priv->base + SRST_REG);
+ udelay(5);
+ iowrite32(0, priv->base + SRST_REG);
+}
+
+static void rcar_csi2_wait_phy_start(struct rcar_csi2 *priv)
+{
+ int timeout;
+
+ /* Read the PHY clock lane monitor register (PHCLM). */
+ for (timeout = 100; timeout > 0; timeout--) {
+ if (ioread32(priv->base + PHCLM_REG) & 0x01) {
+ csi_dbg(priv, "Detected the PHY clock lane\n");
+ break;
+ }
+ msleep(20);
+ }
+ if (!timeout)
+ csi_err(priv, "Timeout of reading the PHY clock lane\n");
+
+
+ /* Read the PHY data lane monitor register (PHDLM). */
+ for (timeout = 100; timeout > 0; timeout--) {
+ if (ioread32(priv->base + PHDLM_REG) & 0x01) {
+ csi_dbg(priv, "Detected the PHY data lane\n");
+ break;
+ }
+ msleep(20);
+ }
+ if (!timeout)
+ csi_err(priv, "Timeout of reading the PHY data lane\n");
+
+}
+
+static int rcar_csi2_start(struct rcar_csi2 *priv)
+{
+ u32 fld, phycnt, phypll, vcdt, vcdt2, tmp, pixels;
+ int i;
+
+ csi_dbg(priv, "Input size (%dx%d%c)\n", priv->mf.width, priv->mf.height,
+ priv->mf.field == V4L2_FIELD_NONE ? 'p' : 'i');
+
+ vcdt = vcdt2 = 0;
+ for (i = 0; i < priv->vc_num; i++) {
+ tmp = VCDT_SEL_VC(i) | VCDT_VCDTN_EN | VCDT_SEL_DTN_ON;
+
+ switch (priv->mf.code) {
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ /* 24 == RGB888 */
+ tmp |= 0x24;
+ break;
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV10_2X10:
+ /* 1E == YUV422 8-bit */
+ tmp |= 0x1e;
+ break;
+ default:
+ csi_warn(priv,
+ "Unknown media bus format, try it anyway\n");
+ break;
+ }
+
+ /* Store in correct reg and offset */
+ if (i < 2)
+ vcdt |= tmp << ((i % 2) * 16);
+ else
+ vcdt2 |= tmp << ((i % 2) * 16);
+ }
+
+ switch (priv->lanes) {
+ case 1:
+ fld = FLD_FLD_NUM(1) | FLD_FLD_EN4 | FLD_FLD_EN3 |
+ FLD_FLD_EN2 | FLD_FLD_EN;
+ phycnt = PHYCNT_ENABLECLK | PHYCNT_ENABLE_0;
+ phypll = PHYPLL_HSFREQRANGE_400MBPS;
+ break;
+ case 4:
+ fld = FLD_FLD_NUM(2) | FLD_FLD_EN4 | FLD_FLD_EN3 |
+ FLD_FLD_EN2 | FLD_FLD_EN;
+ phycnt = PHYCNT_ENABLECLK | PHYCNT_ENABLE_3 |
+ PHYCNT_ENABLE_2 | PHYCNT_ENABLE_1 | PHYCNT_ENABLE_0;
+
+ /* Calculate MBPS per lane, assume 32 bits per pixel at 60Hz */
+ pixels = (priv->mf.width * priv->mf.height);
+ if (pixels <= 640 * 480)
+ phypll = PHYPLL_HSFREQRANGE_100MBPS;
+ else if (pixels <= 720 * 576)
+ phypll = PHYPLL_HSFREQRANGE_190MBPS;
+ else if (pixels <= 1280 * 720)
+ phypll = PHYPLL_HSFREQRANGE_450MBPS;
+ else if (pixels <= 1920 * 1080) {
+ if (priv->mf.field == V4L2_FIELD_NONE)
+ phypll = PHYPLL_HSFREQRANGE_900MBPS;
+ else
+ phypll = PHYPLL_HSFREQRANGE_450MBPS;
+ }
+ else
+ goto error;
+
+ break;
+ default:
+ goto error;
+ }
+
+ /* Init */
+ iowrite32(TREF_TREF, priv->base + TREF_REG);
+ rcar_csi2_reset(priv);
+ iowrite32(0, priv->base + PHTC_REG);
+
+ /* Configure */
+ iowrite32(fld, priv->base + FLD_REG);
+ iowrite32(vcdt, priv->base + VCDT_REG);
+ iowrite32(vcdt2, priv->base + VCDT2_REG);
+ iowrite32(LSWAP_L0SEL(priv->swap[0]) | LSWAP_L1SEL(priv->swap[1]) |
+ LSWAP_L2SEL(priv->swap[2]) | LSWAP_L3SEL(priv->swap[3]),
+ priv->base + LSWAP_REG);
+
+ if (priv->chip == RCAR_H3_WS20) {
+ /* Set PHY Test Interface Write Register for external
+ * reference resistor is unnecessary in R-Car H3(WS2.0)
+ */
+ iowrite32(0x012701e2, priv->base + PHTW_REG);
+ iowrite32(0x010101e3, priv->base + PHTW_REG);
+ iowrite32(0x010101e4, priv->base + PHTW_REG);
+ iowrite32(0x01100104, priv->base + PHTW_REG);
+ }
+
+ /* Start */
+ iowrite32(phypll, priv->base + PHYPLL_REG);
+
+ /* Set CSI0CLK Frequency Configuration Preset Register for external
+ * reference resistor is unnecessary in R-Car H3(WS2.0)
+ */
+ if (priv->chip == RCAR_H3_WS20)
+ iowrite32(CSI0CLKFREQRANGE(32), priv->base + CSI0CLKFCPR_REG);
+
+ iowrite32(phycnt, priv->base + PHYCNT_REG);
+ iowrite32(LINKCNT_MONITOR_EN | LINKCNT_REG_MONI_PACT_EN |
+ LINKCNT_ICLK_NONSTOP, priv->base + LINKCNT_REG);
+ iowrite32(phycnt | PHYCNT_SHUTDOWNZ, priv->base + PHYCNT_REG);
+ iowrite32(phycnt | PHYCNT_SHUTDOWNZ | PHYCNT_RSTZ,
+ priv->base + PHYCNT_REG);
+
+ rcar_csi2_wait_phy_start(priv);
+
+ return 0;
+error:
+ csi_err(priv, "Unsupported resolution (%dx%d%c)\n",
+ priv->mf.width, priv->mf.height,
+ priv->mf.field == V4L2_FIELD_NONE ? 'p' : 'i');
+
+ return -EINVAL;
+}
+
+static void rcar_csi2_stop(struct rcar_csi2 *priv)
+{
+ iowrite32(0, priv->base + PHYCNT_REG);
+
+ rcar_csi2_reset(priv);
+}
+
+static int rcar_csi2_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct rcar_csi2 *priv = container_of(sd, struct rcar_csi2, subdev);
+
+ if (enable)
+ return rcar_csi2_start(priv);
+
+ rcar_csi2_stop(priv);
+
+ return 0;
+}
+
+static int rcar_csi2_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct rcar_csi2 *priv = container_of(sd, struct rcar_csi2, subdev);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ priv->mf = format->format;
+
+ return 0;
+}
+
+static int rcar_csi2_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct rcar_csi2 *priv = container_of(sd, struct rcar_csi2, subdev);
+
+ if (on)
+ pm_runtime_get_sync(priv->dev);
+ else
+ pm_runtime_put_sync(priv->dev);
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops rcar_csi2_video_ops = {
+ .s_stream = rcar_csi2_s_stream,
+};
+
+static struct v4l2_subdev_core_ops rcar_csi2_subdev_core_ops = {
+ .s_power = rcar_csi2_s_power,
+};
+
+static const struct v4l2_subdev_pad_ops rcar_csi2_pad_ops = {
+ .set_fmt = rcar_csi2_set_pad_format,
+};
+
+static struct v4l2_subdev_ops rcar_csi2_subdev_ops = {
+ .video = &rcar_csi2_video_ops,
+ .core = &rcar_csi2_subdev_core_ops,
+ .pad = &rcar_csi2_pad_ops,
+};
+
+/* -----------------------------------------------------------------------------
+ * Platform Device Driver
+ */
+/* H3 WS2.0 */
+static const struct soc_device_attribute r8a7795es20[] = {
+ { .soc_id = "r8a7795", .revision = "ES2.0" },
+ { },
+};
+
+static const struct of_device_id rcar_csi2_of_table[] = {
+ { .compatible = "renesas,csi2-r8a7796", .data = (void *)RCAR_GEN3 },
+ { .compatible = "renesas,csi2-r8a7795", .data = (void *)RCAR_GEN3 },
+ { .compatible = "renesas,rcar-gen3-csi2", .data = (void *)RCAR_GEN3 },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rcar_csi2_of_table);
+
+static int rcar_csi2_parse_dt(struct rcar_csi2 *priv)
+{
+ struct v4l2_of_endpoint v4l2_ep;
+ struct device_node *ep;
+ int i, n, ret;
+ u32 vc_num;
+
+ ep = of_graph_get_endpoint_by_regs(priv->dev->of_node, 0, 0);
+ if (!ep)
+ return -EINVAL;
+
+ ret = v4l2_of_parse_endpoint(ep, &v4l2_ep);
+ of_node_put(ep);
+ if (ret) {
+ csi_err(priv, "Could not parse v4l2 endpoint\n");
+ return -EINVAL;
+ }
+
+ if (v4l2_ep.bus_type != V4L2_MBUS_CSI2) {
+ csi_err(priv, "Unsupported media bus type for %s\n",
+ of_node_full_name(ep));
+ return -EINVAL;
+ }
+
+ switch (v4l2_ep.bus.mipi_csi2.num_data_lanes) {
+ case 1:
+ case 4:
+ priv->lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
+ break;
+ default:
+ csi_err(priv, "Unsupported number of lanes\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 4; i++)
+ priv->swap[i] = i;
+
+ for (i = 0; i < priv->lanes; i++) {
+ /* Check for valid lane number */
+ if (v4l2_ep.bus.mipi_csi2.data_lanes[i] < 1 ||
+ v4l2_ep.bus.mipi_csi2.data_lanes[i] > 4) {
+ csi_err(priv, "data lanes must be in 1-4 range\n");
+ return -EINVAL;
+ }
+
+ /* Use lane numbers 0-3 internally */
+ priv->swap[i] = v4l2_ep.bus.mipi_csi2.data_lanes[i] - 1;
+
+
+ }
+
+ /* Make sure there are no duplicates */
+ for (i = 0; i < priv->lanes; i++) {
+ for (n = i + 1; n < priv->lanes; n++) {
+ if (priv->swap[i] == priv->swap[n]) {
+ csi_err(priv,
+ "Requested swapping not possible\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (!of_property_read_u32(ep, "virtual-channel-number", &vc_num))
+ priv->vc_num = vc_num;
+
+ return 0;
+}
+
+static int rcar_csi2_probe_resources(struct rcar_csi2 *priv,
+ struct platform_device *pdev)
+{
+ struct resource *mem;
+ int irq;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem)
+ return -ENODEV;
+
+ priv->base = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (!irq)
+ return -ENODEV;
+
+ return devm_request_irq(&pdev->dev, irq, rcar_csi2_irq, IRQF_SHARED,
+ dev_name(&pdev->dev), priv);
+}
+
+static int rcar_csi2_probe(struct platform_device *pdev)
+{
+ struct rcar_csi2 *priv;
+ const struct of_device_id *match;
+ int ret;
+ u32 vc_num;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct rcar_csi2), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ match = of_match_device(of_match_ptr(rcar_csi2_of_table), &pdev->dev);
+ if (!match)
+ return -ENODEV;
+
+ priv->dev = &pdev->dev;
+ spin_lock_init(&priv->lock);
+
+ priv->vc_num = 0;
+ priv->chip = (enum chip_id)match->data;
+
+ if (soc_device_match(r8a7795es20))
+ priv->chip = RCAR_H3_WS20;
+
+ ret = rcar_csi2_parse_dt(priv);
+ if (ret)
+ return ret;
+
+ ret = rcar_csi2_probe_resources(priv, pdev);
+ if (ret) {
+ csi_err(priv, "Failed to get resources\n");
+ return ret;
+ }
+
+ vc_num = priv->vc_num;
+ platform_set_drvdata(pdev, priv);
+
+retry:
+ priv->subdev.owner = THIS_MODULE;
+ priv->subdev.dev = &pdev->dev;
+ v4l2_subdev_init(&priv->subdev, &rcar_csi2_subdev_ops);
+ v4l2_set_subdevdata(&priv->subdev, &pdev->dev);
+ snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "rcar_csi2.%s",
+ dev_name(&pdev->dev));
+
+ ret = v4l2_async_register_subdev(&priv->subdev);
+ if (ret < 0)
+ return ret;
+
+ vc_num--;
+ if (vc_num > 0)
+ goto retry;
+
+ pm_runtime_enable(&pdev->dev);
+
+ csi_info(priv, "%d lanes found. virtual channel number %d use\n",
+ priv->lanes, priv->vc_num);
+
+ return 0;
+}
+
+static int rcar_csi2_remove(struct platform_device *pdev)
+{
+ struct rcar_csi2 *priv = platform_get_drvdata(pdev);
+
+ v4l2_async_unregister_subdev(&priv->subdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver __refdata rcar_csi2_pdrv = {
+ .remove = rcar_csi2_remove,
+ .probe = rcar_csi2_probe,
+ .driver = {
+ .name = "rcar-csi2",
+ .of_match_table = of_match_ptr(rcar_csi2_of_table),
+ },
+};
+
+module_platform_driver(rcar_csi2_pdrv);
+
+MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>");
+MODULE_DESCRIPTION("Renesas R-Car MIPI CSI-2 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c
index 9ccd5ff..10978d1 100644
--- a/drivers/media/platform/rcar-vin/rcar-dma.c
+++ b/drivers/media/platform/rcar-vin/rcar-dma.c
@@ -14,8 +14,14 @@
* option) any later version.
*/
+#ifdef CONFIG_VIDEO_RCAR_VIN_DEBUG
+#define DEBUG
+#endif
+
#include <linux/delay.h>
#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
#include <media/videobuf2-dma-contig.h>
@@ -33,21 +39,28 @@
#define VNELPRC_REG 0x10 /* Video n End Line Pre-Clip Register */
#define VNSPPRC_REG 0x14 /* Video n Start Pixel Pre-Clip Register */
#define VNEPPRC_REG 0x18 /* Video n End Pixel Pre-Clip Register */
-#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */
-#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */
-#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */
-#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */
#define VNIS_REG 0x2C /* Video n Image Stride Register */
#define VNMB_REG(m) (0x30 + ((m) << 2)) /* Video n Memory Base m Register */
#define VNIE_REG 0x40 /* Video n Interrupt Enable Register */
#define VNINTS_REG 0x44 /* Video n Interrupt Status Register */
#define VNSI_REG 0x48 /* Video n Scanline Interrupt Register */
#define VNMTC_REG 0x4C /* Video n Memory Transfer Control Register */
-#define VNYS_REG 0x50 /* Video n Y Scale Register */
-#define VNXS_REG 0x54 /* Video n X Scale Register */
#define VNDMR_REG 0x58 /* Video n Data Mode Register */
#define VNDMR2_REG 0x5C /* Video n Data Mode Register 2 */
#define VNUVAOF_REG 0x60 /* Video n UV Address Offset Register */
+#define VNUDS_CTRL_REG 0x80 /* Scaling Control Registers */
+#define VNUDS_SCALE_REG 0x84 /* Scaling Factor Register */
+#define VNUDS_PASS_BWIDTH_REG 0x90 /* Passband Registers */
+#define VNUDS_IPC_REG 0x98 /* 2D IPC Setting Register */
+#define VNUDS_CLIP_SIZE_REG 0xA4 /* UDS Output Size Clipping Register */
+
+/* Register offsets specific for Gen2 */
+#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */
+#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */
+#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */
+#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */
+#define VNYS_REG 0x50 /* Video n Y Scale Register */
+#define VNXS_REG 0x54 /* Video n X Scale Register */
#define VNC1A_REG 0x80 /* Video n Coefficient Set C1A Register */
#define VNC1B_REG 0x84 /* Video n Coefficient Set C1B Register */
#define VNC1C_REG 0x88 /* Video n Coefficient Set C1C Register */
@@ -73,9 +86,13 @@
#define VNC8B_REG 0xF4 /* Video n Coefficient Set C8B Register */
#define VNC8C_REG 0xF8 /* Video n Coefficient Set C8C Register */
+/* Register offsets specific for Gen3 */
+#define VNCSI_IFMD_REG 0x20 /* Video n CSI2 Interface Mode Register */
/* Register bit fields for R-Car VIN */
/* Video n Main Control Register bits */
+#define VNMC_DPINE (1 << 27) /* Gen3 specific */
+#define VNMC_SCLE (1 << 26) /* Gen3 specific */
#define VNMC_FOC (1 << 21)
#define VNMC_YCAL (1 << 19)
#define VNMC_INF_YUV8_BT656 (0 << 16)
@@ -106,12 +123,19 @@
/* Video n Interrupt Enable Register bits */
#define VNIE_FIE (1 << 4)
#define VNIE_EFE (1 << 1)
+#define VNIE_FOE (1 << 0)
+
+/* Video n Interrupt Status Register bits */
+#define VNINTS_FIS (1 << 4)
+#define VNINTS_EFS (1 << 1)
+#define VNINTS_FOS (1 << 0)
/* Video n Data Mode Register bits */
#define VNDMR_EXRGB (1 << 8)
#define VNDMR_BPSM (1 << 4)
#define VNDMR_DTMD_YCSEP (1 << 1)
-#define VNDMR_DTMD_ARGB1555 (1 << 0)
+#define VNDMR_DTMD_ARGB (1 << 0)
+#define VNDMR_DTMD_YCSEP_YCBCR420 (3 << 0)
/* Video n Data Mode Register 2 bits */
#define VNDMR2_VPS (1 << 30)
@@ -119,6 +143,47 @@
#define VNDMR2_FTEV (1 << 17)
#define VNDMR2_VLV(n) ((n & 0xf) << 12)
+/* Video n CSI2 Interface Mode Register (Gen3) */
+#define VNCSI_IFMD_DES1 (1 << 26)
+#define VNCSI_IFMD_DES0 (1 << 25)
+#define VNCSI_IFMD_CSI_CHSEL(n) ((n & 0xf) << 0)
+
+/* Video n UDS Control Register bits */
+#define VNUDS_CTRL_AMD (1 << 30)
+#define VNUDS_CTRL_BC (1 << 20)
+#define VNUDS_CTRL_TDIPC (1 << 1)
+
+#define VIN_UT_IRQ 0x01
+
+static int vin_debug;
+module_param_named(debug, vin_debug, int, 0600);
+static int overflow_video[RCAR_VIDEO_MAX];
+module_param_array(overflow_video, int, NULL, 0600);
+
+#ifdef CONFIG_VIDEO_RCAR_VIN_DEBUG
+#define VIN_IRQ_DEBUG(fmt, args...) \
+ do { \
+ if (unlikely(vin_debug & VIN_UT_IRQ)) \
+ vin_ut_debug_printk(__func__, fmt, ##args); \
+ } while (0)
+#else
+#define VIN_IRQ_DEBUG(fmt, args...)
+#endif
+
+void vin_ut_debug_printk(const char *function_name, const char *format, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, format);
+ vaf.fmt = format;
+ vaf.va = &args;
+
+ pr_debug("[" DRV_NAME ":%s] %pV", function_name, &vaf);
+
+ va_end(args);
+}
+
static void rvin_write(struct rvin_dev *vin, u32 value, u32 offset)
{
iowrite32(value, vin->base + offset);
@@ -129,12 +194,54 @@ static u32 rvin_read(struct rvin_dev *vin, u32 offset)
return ioread32(vin->base + offset);
}
+int rvin_is_scaling(struct rvin_dev *vin)
+{
+ if (vin->chip == RCAR_H3 || vin->chip == RCAR_M3) {
+ if ((vin->crop.width != vin->format.width) ||
+ (vin->crop.height != vin->format.height))
+ return 1;
+ }
+
+ return 0;
+}
+
+int rvin_set_ifmd(struct rvin_dev *vin, int val)
+{
+ u32 ifmd;
+
+ if (vin->chip != RCAR_H3 && vin->chip != RCAR_M3)
+ return 0;
+
+ if ((vin->index != 0) && (vin->index != 4))
+ return 0;
+
+ pm_runtime_get_sync(vin->v4l2_dev.dev);
+
+ ifmd = VNCSI_IFMD_DES1 | VNCSI_IFMD_DES0 |
+ VNCSI_IFMD_CSI_CHSEL(val);
+
+ rvin_write(vin, ifmd, VNCSI_IFMD_REG);
+
+ vin_dbg(vin, "Set IFMD 0x%x\n", ifmd);
+
+ pm_runtime_put(vin->v4l2_dev.dev);
+
+ return 0;
+}
+
static int rvin_setup(struct rvin_dev *vin)
{
- u32 vnmc, dmr, dmr2, interrupts;
v4l2_std_id std;
+ u32 code, vnmc, dmr, dmr2, interrupts;
+ struct v4l2_mbus_config mbus_cfg;
bool progressive = false, output_is_yuv = false, input_is_yuv = false;
+ if (rvin_subdev_get_mbus_cfg(vin, &mbus_cfg))
+ return -EINVAL;
+
+ if (rvin_subdev_get_code(vin, &code))
+ return -EINVAL;
+
switch (vin->format.field) {
case V4L2_FIELD_TOP:
vnmc = VNMC_IM_ODD;
@@ -146,7 +253,7 @@ static int rvin_setup(struct rvin_dev *vin)
/* Default to TB */
vnmc = VNMC_IM_FULL;
/* Use BT if video standard can be read and is 60 Hz format */
- if (!v4l2_subdev_call(vin_to_source(vin), video, g_std, &std)) {
+ if (!rvin_subdev_call_local(vin, video, g_std, &std)) {
if (std & V4L2_STD_525_60)
vnmc = VNMC_IM_FULL | VNMC_FOC;
}
@@ -174,7 +281,7 @@ static int rvin_setup(struct rvin_dev *vin)
/*
* Input interface
*/
- switch (vin->digital.code) {
+ switch (code) {
case MEDIA_BUS_FMT_YUYV8_1X16:
/* BT.601/BT.1358 16bit YCbCr422 */
vnmc |= VNMC_INF_YUV16;
@@ -182,7 +289,7 @@ static int rvin_setup(struct rvin_dev *vin)
break;
case MEDIA_BUS_FMT_UYVY8_2X8:
/* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */
- vnmc |= vin->digital.mbus_cfg.type == V4L2_MBUS_BT656 ?
+ vnmc |= mbus_cfg.type == V4L2_MBUS_BT656 ?
VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601;
input_is_yuv = true;
break;
@@ -191,7 +298,7 @@ static int rvin_setup(struct rvin_dev *vin)
break;
case MEDIA_BUS_FMT_UYVY10_2X10:
/* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */
- vnmc |= vin->digital.mbus_cfg.type == V4L2_MBUS_BT656 ?
+ vnmc |= mbus_cfg.type == V4L2_MBUS_BT656 ?
VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601;
input_is_yuv = true;
break;
@@ -200,20 +307,30 @@ static int rvin_setup(struct rvin_dev *vin)
}
/* Enable VSYNC Field Toogle mode after one VSYNC input */
- dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
+ if (vin->chip == RCAR_H3 || vin->chip == RCAR_M3)
+ dmr2 = VNDMR2_FTEV;
+ else
+ dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
/* Hsync Signal Polarity Select */
- if (!(vin->digital.mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
+ if (!(mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
dmr2 |= VNDMR2_HPS;
/* Vsync Signal Polarity Select */
- if (!(vin->digital.mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW))
+ if (!(mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW))
dmr2 |= VNDMR2_VPS;
/*
* Output format
*/
switch (vin->format.pixelformat) {
+ case V4L2_PIX_FMT_NV12:
+ rvin_write(vin,
+ ALIGN(vin->format.width * vin->format.height, 0x80),
+ VNUVAOF_REG);
+ dmr = VNDMR_DTMD_YCSEP_YCBCR420;
+ output_is_yuv = true;
+ break;
case V4L2_PIX_FMT_NV16:
rvin_write(vin,
ALIGN(vin->format.width * vin->format.height, 0x80),
@@ -229,12 +346,15 @@ static int rvin_setup(struct rvin_dev *vin)
dmr = 0;
output_is_yuv = true;
break;
- case V4L2_PIX_FMT_XRGB555:
- dmr = VNDMR_DTMD_ARGB1555;
+ case V4L2_PIX_FMT_ARGB555:
+ dmr = VNDMR_DTMD_ARGB;
break;
case V4L2_PIX_FMT_RGB565:
dmr = 0;
break;
+ case V4L2_PIX_FMT_ABGR32:
+ dmr = VNDMR_EXRGB | VNDMR_DTMD_ARGB;
+ break;
case V4L2_PIX_FMT_XBGR32:
/* Note: not supported on M1 */
dmr = VNDMR_EXRGB;
@@ -252,9 +372,27 @@ static int rvin_setup(struct rvin_dev *vin)
if (input_is_yuv == output_is_yuv)
vnmc |= VNMC_BPS;
+ if (vin->chip == RCAR_H3 || vin->chip == RCAR_M3) {
+ /* Select between CSI-2 and Digital input */
+ if (mbus_cfg.type == V4L2_MBUS_CSI2)
+ vnmc &= ~VNMC_DPINE;
+ else
+ vnmc |= VNMC_DPINE;
+ }
+
+ if ((vin->format.pixelformat != V4L2_PIX_FMT_NV12) &&
+ (rvin_is_scaling(vin)))
+ vnmc |= VNMC_SCLE;
+
/* Progressive or interlaced mode */
interrupts = progressive ? VNIE_FIE : VNIE_EFE;
+ /* Enable Overflow */
+ if (vin_debug) {
+ vin_dbg(vin, "Enable Overflow\n");
+ interrupts |= VNIE_FOE;
+ }
+
/* Ack interrupts */
rvin_write(vin, interrupts, VNINTS_REG);
/* Enable interrupts */
@@ -307,6 +445,13 @@ static void rvin_capture_stop(struct rvin_dev *vin)
{
rvin_capture_off(vin);
+ if (vin->chip == RCAR_H3 || vin->chip == RCAR_M3) {
+ u32 vnmc;
+
+ vnmc = rvin_read(vin, VNMC_REG);
+ rvin_write(vin, vnmc & ~VNMC_SCLE, VNMC_REG);
+ }
+
/* Disable module */
rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG);
}
@@ -753,28 +898,10 @@ static void rvin_set_coeff(struct rvin_dev *vin, unsigned short xs)
rvin_write(vin, p_set->coeff_set[23], VNC8C_REG);
}
-void rvin_crop_scale_comp(struct rvin_dev *vin)
+static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin)
{
u32 xs, ys;
- /* Set Start/End Pixel/Line Pre-Clip */
- rvin_write(vin, vin->crop.left, VNSPPRC_REG);
- rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG);
- switch (vin->format.field) {
- case V4L2_FIELD_INTERLACED:
- case V4L2_FIELD_INTERLACED_TB:
- case V4L2_FIELD_INTERLACED_BT:
- rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG);
- rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1,
- VNELPRC_REG);
- break;
- default:
- rvin_write(vin, vin->crop.top, VNSLPRC_REG);
- rvin_write(vin, vin->crop.top + vin->crop.height - 1,
- VNELPRC_REG);
- break;
- }
-
/* Set scaling coefficient */
ys = 0;
if (vin->crop.height != vin->compose.height)
@@ -812,11 +939,6 @@ void rvin_crop_scale_comp(struct rvin_dev *vin)
break;
}
- if (vin->format.pixelformat == V4L2_PIX_FMT_NV16)
- rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG);
- else
- rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG);
-
vin_dbg(vin,
"Pre-Clip: %ux%u@%u:%u YS: %d XS: %d Post-Clip: %ux%u@%u:%u\n",
vin->crop.width, vin->crop.height, vin->crop.left,
@@ -824,6 +946,110 @@ void rvin_crop_scale_comp(struct rvin_dev *vin)
0, 0);
}
+struct rcar_vin_uds_regs {
+ unsigned long ctrl;
+ unsigned long scale;
+ unsigned long pass_bwidth;
+ unsigned long clip_size;
+};
+
+static unsigned long rvin_get_bwidth(unsigned long ratio)
+{
+ unsigned long bwidth;
+ unsigned long mant, frac;
+
+ mant = (ratio & 0xF000) >> 12;
+ frac = ratio & 0x0FFF;
+ if (mant)
+ bwidth = 64 * 4096 * mant / (4096 * mant + frac);
+ else
+ bwidth = 64;
+
+ return bwidth;
+}
+
+static unsigned long rvin_compute_ratio(unsigned int input,
+ unsigned int output)
+{
+ return ((input * 4096 / output) == 0x10000) ?
+ 0xFFFF : (input * 4096 / output);
+}
+
+int rvin_crop_scale_comp_gen3(struct rvin_dev *vin)
+{
+ struct rcar_vin_uds_regs regs;
+ unsigned long ratio_h, ratio_v;
+ unsigned long bwidth_h, bwidth_v;
+ unsigned long ctrl;
+ unsigned long clip_size;
+ u32 vnmc;
+
+ ratio_h = rvin_compute_ratio(vin->crop.width, vin->format.width);
+ ratio_v = rvin_compute_ratio(vin->crop.height, vin->format.height);
+
+ if ((ratio_h > 0x10000) || (ratio_v > 0x10000))
+ dev_warn(vin->dev, "Scaling rate parameter error\n");
+
+ bwidth_h = rvin_get_bwidth(ratio_h);
+ bwidth_v = rvin_get_bwidth(ratio_v);
+
+ ctrl = VNUDS_CTRL_AMD;
+
+ if (vin->format.field == V4L2_FIELD_NONE)
+ clip_size = (vin->format.width << 16) |
+ (vin->format.height);
+ else
+ clip_size = (vin->format.width << 16) |
+ (vin->format.height / 2);
+
+ regs.ctrl = ctrl;
+ regs.scale = (ratio_h << 16) | ratio_v;
+ regs.pass_bwidth = (bwidth_h << 16) | bwidth_v;
+ regs.clip_size = clip_size;
+
+ vnmc = rvin_read(vin, VNMC_REG);
+ rvin_write(vin, vnmc | VNMC_SCLE, VNMC_REG);
+ rvin_write(vin, regs.ctrl, VNUDS_CTRL_REG);
+ rvin_write(vin, regs.scale, VNUDS_SCALE_REG);
+ rvin_write(vin, regs.pass_bwidth, VNUDS_PASS_BWIDTH_REG);
+ rvin_write(vin, regs.clip_size, VNUDS_CLIP_SIZE_REG);
+ rvin_write(vin, vnmc, VNMC_REG);
+
+ return 0;
+}
+
+void rvin_crop_scale_comp(struct rvin_dev *vin)
+{
+ /* Set Start/End Pixel/Line Pre-Clip */
+ rvin_write(vin, vin->crop.left, VNSPPRC_REG);
+ rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG);
+ switch (vin->format.field) {
+ case V4L2_FIELD_INTERLACED:
+ case V4L2_FIELD_INTERLACED_TB:
+ case V4L2_FIELD_INTERLACED_BT:
+ rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG);
+ rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1,
+ VNELPRC_REG);
+ break;
+ default:
+ rvin_write(vin, vin->crop.top, VNSLPRC_REG);
+ rvin_write(vin, vin->crop.top + vin->crop.height - 1,
+ VNELPRC_REG);
+ break;
+ }
+
+ if (vin->chip != RCAR_H3 && vin->chip != RCAR_M3)
+ rvin_crop_scale_comp_gen2(vin);
+ else
+ rvin_crop_scale_comp_gen3(vin);
+
+ if ((vin->format.pixelformat == V4L2_PIX_FMT_NV16) ||
+ (vin->format.pixelformat == V4L2_PIX_FMT_NV12))
+ rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG);
+ else
+ rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG);
+}
+
void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix,
u32 width, u32 height)
{
@@ -895,6 +1121,7 @@ static irqreturn_t rvin_irq(int irq, void *data)
int slot;
unsigned int sequence, handled = 0;
unsigned long flags;
+ int vin_ovr_cnt = 0;
spin_lock_irqsave(&vin->qlock, flags);
@@ -905,6 +1132,13 @@ static irqreturn_t rvin_irq(int irq, void *data)
rvin_ack_interrupt(vin);
handled = 1;
+ /* overflow occurs */
+ if (vin_debug && (int_status & VNINTS_FOS)) {
+ vin_ovr_cnt = ++overflow_video[vin->index];
+ VIN_IRQ_DEBUG("overflow occurrs num[%d] at VIN (%s)\n",
+ vin_ovr_cnt, dev_name(vin->v4l2_dev.dev));
+ }
+
/* Nothing to do if capture status is 'STOPPED' */
if (vin->state == STOPPED) {
vin_dbg(vin, "IRQ while state stopped\n");
@@ -1000,6 +1234,16 @@ static int rvin_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
{
struct rvin_dev *vin = vb2_get_drv_priv(vq);
+ if (rvin_is_scaling(vin) && (vin->format.width % 32)) {
+ vin_err(vin, "Scaling parameter error\n");
+ return -EINVAL;
+ }
+
+ if (!rvin_is_scaling(vin) && (vin->format.width % 16)) {
+ vin_err(vin, "Image stride parameter error\n");
+ return -EINVAL;
+ }
+
/* Make sure the image size is large enough. */
if (*nplanes)
return sizes[0] < vin->format.sizeimage ? -EINVAL : 0;
@@ -1050,12 +1294,10 @@ static void rvin_buffer_queue(struct vb2_buffer *vb)
static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct rvin_dev *vin = vb2_get_drv_priv(vq);
- struct v4l2_subdev *sd;
unsigned long flags;
int ret;
- sd = vin_to_source(vin);
- v4l2_subdev_call(sd, video, s_stream, 1);
+ rvin_subdev_call(vin, video, s_stream, 1);
spin_lock_irqsave(&vin->qlock, flags);
@@ -1080,7 +1322,7 @@ static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count)
/* Return all buffers if something went wrong */
if (ret) {
return_all_buffers(vin, VB2_BUF_STATE_QUEUED);
- v4l2_subdev_call(sd, video, s_stream, 0);
+ rvin_subdev_call(vin, video, s_stream, 0);
}
spin_unlock_irqrestore(&vin->qlock, flags);
@@ -1091,7 +1333,6 @@ static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count)
static void rvin_stop_streaming(struct vb2_queue *vq)
{
struct rvin_dev *vin = vb2_get_drv_priv(vq);
- struct v4l2_subdev *sd;
unsigned long flags;
int retries = 0;
@@ -1130,8 +1371,7 @@ static void rvin_stop_streaming(struct vb2_queue *vq)
spin_unlock_irqrestore(&vin->qlock, flags);
- sd = vin_to_source(vin);
- v4l2_subdev_call(sd, video, s_stream, 0);
+ rvin_subdev_call(vin, video, s_stream, 0);
/* disable interrupts */
rvin_disable_interrupts(vin);
@@ -1150,8 +1390,6 @@ static const struct vb2_ops rvin_qops = {
void rvin_dma_remove(struct rvin_dev *vin)
{
mutex_destroy(&vin->lock);
-
- v4l2_device_unregister(&vin->v4l2_dev);
}
int rvin_dma_probe(struct rvin_dev *vin, int irq)
@@ -1159,11 +1397,6 @@ int rvin_dma_probe(struct rvin_dev *vin, int irq)
struct vb2_queue *q = &vin->queue;
int i, ret;
- /* Initialize the top-level structure */
- ret = v4l2_device_register(vin->dev, &vin->v4l2_dev);
- if (ret)
- return ret;
-
mutex_init(&vin->lock);
INIT_LIST_HEAD(&vin->buf_list);
@@ -1176,7 +1409,7 @@ int rvin_dma_probe(struct rvin_dev *vin, int irq)
/* buffer queue */
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF;
+ q->io_modes = VB2_MMAP | VB2_READ | VB2_USERPTR | VB2_DMABUF;
q->lock = &vin->lock;
q->drv_priv = vin;
q->buf_struct_size = sizeof(struct rvin_buffer);
@@ -1200,9 +1433,77 @@ int rvin_dma_probe(struct rvin_dev *vin, int irq)
goto error;
}
+ vin_debug = 0;
+
return 0;
error:
rvin_dma_remove(vin);
return ret;
}
+
+/* -----------------------------------------------------------------------------
+ * Salve Subdevice
+ */
+
+static int rvin_subdev_s_gpio(struct v4l2_subdev *sd, u32 val)
+{
+ struct rvin_dev *vin = container_of(sd, struct rvin_dev, slave);
+ u32 ifmd;
+
+ if (vin->chip != RCAR_H3 && vin->chip != RCAR_M3)
+ return 0;
+
+ pm_runtime_get_sync(vin->v4l2_dev.dev);
+
+ /*
+ * Undocumented feature: Writing to VNCSI_IFMD_REG will go
+ * through and on read back look correct but won't have
+ * any effect if VNMC_REG is not first set to 0.
+ */
+ rvin_write(vin, 0, VNMC_REG);
+
+ ifmd = VNCSI_IFMD_DES1 | VNCSI_IFMD_DES0 |
+ VNCSI_IFMD_CSI_CHSEL(val);
+
+ rvin_write(vin, ifmd, VNCSI_IFMD_REG);
+
+ vin_dbg(vin, "Set IFMD 0x%x\n", ifmd);
+
+ pm_runtime_put(vin->v4l2_dev.dev);
+
+ return 0;
+}
+
+static struct v4l2_subdev_core_ops rvin_subdev_core_ops = {
+ .s_gpio = rvin_subdev_s_gpio,
+};
+
+static struct v4l2_subdev_ops rvin_subdev_ops = {
+ .core = &rvin_subdev_core_ops,
+};
+
+int rvin_subdev_probe(struct rvin_dev *vin)
+{
+ vin->slave.v4l2_dev = NULL;
+
+ if (vin->chip != RCAR_H3 && vin->chip != RCAR_M3)
+ return 0;
+
+ vin->slave.owner = THIS_MODULE;
+ vin->slave.dev = vin->dev;
+ v4l2_subdev_init(&vin->slave, &rvin_subdev_ops);
+ v4l2_set_subdevdata(&vin->slave, vin->dev);
+ snprintf(vin->slave.name, V4L2_SUBDEV_NAME_SIZE, "rcar-vin-slave.%s",
+ dev_name(vin->dev));
+
+ return v4l2_async_register_subdev(&vin->slave);
+}
+
+void rvin_subdev_remove(struct rvin_dev *vin)
+{
+ if (vin->chip != RCAR_H3 && vin->chip != RCAR_M3)
+ return;
+
+ v4l2_async_unregister_subdev(&vin->slave);
+}
diff --git a/drivers/media/platform/rcar-vin/rcar-group.c b/drivers/media/platform/rcar-vin/rcar-group.c
new file mode 100644
index 0000000..2b98e4c
--- /dev/null
+++ b/drivers/media/platform/rcar-vin/rcar-group.c
@@ -0,0 +1,1359 @@
+/*
+ * Driver for Renesas R-Car VIN
+ *
+ * Copyright (C) 2016 Renesas Electronics Corp.
+ *
+ * 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/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/soc/renesas/rcar_prr.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-of.h>
+
+#include "rcar-group.h"
+#include "rcar-vin.h"
+
+/* Max chsel supported by HW */
+#define RVIN_CHSEL_MAX 5
+#define RVIN_H3_WS11_CHSEL_MAX 6
+#define RVIN_VCSEL_MAX 4
+
+enum rvin_csi_id {
+ RVIN_CSI20_VC0,
+ RVIN_CSI20_VC1,
+ RVIN_CSI20_VC2,
+ RVIN_CSI20_VC3,
+ RVIN_CSI21_VC0,
+ RVIN_CSI21_VC1,
+ RVIN_CSI21_VC2,
+ RVIN_CSI21_VC3,
+ RVIN_CSI40_VC0,
+ RVIN_CSI40_VC1,
+ RVIN_CSI40_VC2,
+ RVIN_CSI40_VC3,
+ RVIN_CSI41_VC0,
+ RVIN_CSI41_VC1,
+ RVIN_CSI41_VC2,
+ RVIN_CSI41_VC3,
+ RVIN_CSI_MAX,
+ RVIN_CSI_ERROR,
+};
+
+enum rvin_chan_id {
+ RVIN_CHAN0,
+ RVIN_CHAN1,
+ RVIN_CHAN2,
+ RVIN_CHAN3,
+ RVIN_CHAN4,
+ RVIN_CHAN5,
+ RVIN_CHAN6,
+ RVIN_CHAN7,
+ RVIN_CHAN_MAX,
+ RVIN_CHAN_ERROR,
+};
+
+struct rvin_group_map_item {
+ enum rvin_csi_id csi;
+ const char *name;
+};
+
+static const struct rvin_group_map_item
+rvin_group_h3_ws11_map[RVIN_CHAN_MAX][RVIN_H3_WS11_CHSEL_MAX] = {
+ {
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 0" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 1" },
+ { .csi = RVIN_CSI21_VC0, .name = "CSI21/VC0 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 4" },
+ { .csi = RVIN_CSI21_VC0, .name = "CSI21/VC0 chsel1: 5" },
+ }, {
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 0" },
+ { .csi = RVIN_CSI21_VC0, .name = "CSI21/VC0 chsel1: 1" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel1: 4" },
+ { .csi = RVIN_CSI21_VC1, .name = "CSI21/VC1 chsel1: 5" },
+ }, {
+ { .csi = RVIN_CSI21_VC0, .name = "CSI21/VC0 chsel1: 0" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 1" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC2, .name = "CSI40/VC2 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC2, .name = "CSI20/VC2 chsel1: 4" },
+ { .csi = RVIN_CSI21_VC2, .name = "CSI21/VC2 chsel1: 5" },
+ }, {
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel1: 0" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel1: 1" },
+ { .csi = RVIN_CSI21_VC1, .name = "CSI21/VC1 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC3, .name = "CSI40/VC3 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC3, .name = "CSI20/VC3 chsel1: 4" },
+ { .csi = RVIN_CSI21_VC3, .name = "CSI21/VC3 chsel1: 5" },
+ }, {
+ { .csi = RVIN_CSI41_VC0, .name = "CSI41/VC0 chsel2: 0" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 1" },
+ { .csi = RVIN_CSI21_VC0, .name = "CSI21/VC0 chsel2: 2" },
+ { .csi = RVIN_CSI41_VC0, .name = "CSI41/VC0 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 4" },
+ { .csi = RVIN_CSI21_VC0, .name = "CSI21/VC0 chsel2: 5" },
+ }, {
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 0" },
+ { .csi = RVIN_CSI21_VC0, .name = "CSI21/VC0 chsel2: 1" },
+ { .csi = RVIN_CSI41_VC0, .name = "CSI41/VC0 chsel2: 2" },
+ { .csi = RVIN_CSI41_VC1, .name = "CSI41/VC1 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel2: 4" },
+ { .csi = RVIN_CSI21_VC1, .name = "CSI21/VC1 chsel2: 5" },
+ }, {
+ { .csi = RVIN_CSI21_VC0, .name = "CSI21/VC0 chsel2: 0" },
+ { .csi = RVIN_CSI41_VC0, .name = "CSI41/VC0 chsel2: 1" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 2" },
+ { .csi = RVIN_CSI41_VC2, .name = "CSI41/VC2 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC2, .name = "CSI20/VC2 chsel2: 4" },
+ { .csi = RVIN_CSI21_VC2, .name = "CSI21/VC2 chsel2: 5" },
+ }, {
+ { .csi = RVIN_CSI41_VC1, .name = "CSI41/VC1 chsel2: 0" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel2: 1" },
+ { .csi = RVIN_CSI21_VC1, .name = "CSI21/VC1 chsel2: 2" },
+ { .csi = RVIN_CSI41_VC3, .name = "CSI41/VC3 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC3, .name = "CSI20/VC3 chsel2: 4" },
+ { .csi = RVIN_CSI21_VC3, .name = "CSI21/VC3 chsel2: 5" },
+ },
+};
+
+static const struct rvin_group_map_item
+rvin_group_h3_map[RVIN_CHAN_MAX][RVIN_CHSEL_MAX] = {
+ {
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 0" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 1" },
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 4" },
+ }, {
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 0" },
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel1: 1" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel1: 4" },
+ }, {
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel1: 0" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 1" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC2, .name = "CSI40/VC2 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC2, .name = "CSI20/VC2 chsel1: 4" },
+ }, {
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel1: 0" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel1: 1" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC3, .name = "CSI40/VC3 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC3, .name = "CSI20/VC3 chsel1: 4" },
+ }, {
+ { .csi = RVIN_CSI41_VC0, .name = "CSI41/VC0 chsel2: 0" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 1" },
+ { .csi = RVIN_CSI41_VC1, .name = "CSI41/VC1 chsel2: 2" },
+ { .csi = RVIN_CSI41_VC0, .name = "CSI41/VC0 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 4" },
+ }, {
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 0" },
+ { .csi = RVIN_CSI41_VC1, .name = "CSI41/VC1 chsel2: 1" },
+ { .csi = RVIN_CSI41_VC0, .name = "CSI41/VC0 chsel2: 2" },
+ { .csi = RVIN_CSI41_VC1, .name = "CSI41/VC1 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel2: 4" },
+ }, {
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel2: 0" },
+ { .csi = RVIN_CSI41_VC0, .name = "CSI41/VC0 chsel2: 1" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 2" },
+ { .csi = RVIN_CSI41_VC2, .name = "CSI41/VC2 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC2, .name = "CSI20/VC2 chsel2: 4" },
+ }, {
+ { .csi = RVIN_CSI41_VC1, .name = "CSI41/VC1 chsel2: 0" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel2: 1" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel2: 2" },
+ { .csi = RVIN_CSI41_VC3, .name = "CSI41/VC3 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC3, .name = "CSI20/VC3 chsel2: 4" },
+ },
+};
+
+static const struct rvin_group_map_item
+rvin_group_m3_map[RVIN_CHAN_MAX][RVIN_CHSEL_MAX] = {
+ {
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 0" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 1" },
+ { .csi = RVIN_CSI_ERROR, .name = "No operate" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 4" },
+ }, {
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 0" },
+ { .csi = RVIN_CSI_ERROR, .name = "No operate" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel1: 4" },
+ }, {
+ { .csi = RVIN_CSI_ERROR, .name = "No operate" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel1: 1" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel1: 2" },
+ { .csi = RVIN_CSI40_VC2, .name = "CSI40/VC2 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC2, .name = "CSI20/VC2 chsel1: 4" },
+ }, {
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel1: 0" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel1: 1" },
+ { .csi = RVIN_CSI_ERROR, .name = "No operate" },
+ { .csi = RVIN_CSI40_VC3, .name = "CSI40/VC3 chsel1: 3" },
+ { .csi = RVIN_CSI20_VC3, .name = "CSI20/VC3 chsel1: 4" },
+ }, {
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel2: 0" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 1" },
+ { .csi = RVIN_CSI_ERROR, .name = "No operate" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 4" },
+ }, {
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 0" },
+ { .csi = RVIN_CSI_ERROR, .name = "No operate" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel2: 2" },
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel2: 4" },
+ }, {
+ { .csi = RVIN_CSI_ERROR, .name = "No operate" },
+ { .csi = RVIN_CSI40_VC0, .name = "CSI40/VC0 chsel2: 1" },
+ { .csi = RVIN_CSI20_VC0, .name = "CSI20/VC0 chsel2: 2" },
+ { .csi = RVIN_CSI40_VC2, .name = "CSI40/VC2 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC2, .name = "CSI20/VC2 chsel2: 4" },
+ }, {
+ { .csi = RVIN_CSI40_VC1, .name = "CSI40/VC1 chsel2: 0" },
+ { .csi = RVIN_CSI20_VC1, .name = "CSI20/VC1 chsel2: 1" },
+ { .csi = RVIN_CSI_ERROR, .name = "No operate" },
+ { .csi = RVIN_CSI40_VC3, .name = "CSI40/VC3 chsel2: 3" },
+ { .csi = RVIN_CSI20_VC3, .name = "CSI20/VC3 chsel2: 4" },
+ },
+};
+
+struct rvin_group {
+ struct device *dev;
+ struct v4l2_device *v4l2_dev;
+ struct mutex lock;
+
+ struct rvin_group_api api;
+
+ struct v4l2_async_notifier notifier;
+
+ struct rvin_graph_entity bridge[RVIN_CSI_MAX];
+ struct rvin_graph_entity source[RVIN_CSI_MAX];
+ int stream[RVIN_CSI_MAX];
+ int power[RVIN_CSI_MAX];
+
+ struct rvin_graph_entity chan[RVIN_CHAN_MAX];
+ int users[RVIN_CHAN_MAX];
+
+ int chsel1;
+ int chsel2;
+};
+
+#define grp_dbg(d, fmt, arg...) dev_dbg(d->dev, fmt, ##arg)
+#define grp_info(d, fmt, arg...) dev_info(d->dev, fmt, ##arg)
+#define grp_err(d, fmt, arg...) dev_err(d->dev, fmt, ##arg)
+
+/* -----------------------------------------------------------------------------
+ * Group API - Helpers
+ */
+
+static enum rvin_chan_id sd_to_chan(struct rvin_group *grp,
+ struct v4l2_subdev *sd)
+{
+ enum rvin_chan_id chan = RVIN_CHAN_ERROR;
+ int i;
+
+ for (i = 0; i < RVIN_CHAN_MAX; i++) {
+ if (grp->chan[i].subdev == sd) {
+ chan = i;
+ break;
+ }
+ }
+
+ /* Something is wrong, subdevice can't be resolved to channel id */
+ BUG_ON(chan == RVIN_CHAN_ERROR);
+
+ return chan;
+}
+
+static enum rvin_chan_id chan_to_master(struct rvin_group *grp,
+ enum rvin_chan_id chan)
+{
+ enum rvin_chan_id master;
+
+ switch (chan) {
+ case RVIN_CHAN0:
+ case RVIN_CHAN1:
+ case RVIN_CHAN2:
+ case RVIN_CHAN3:
+ master = RVIN_CHAN0;
+ break;
+ case RVIN_CHAN4:
+ case RVIN_CHAN5:
+ case RVIN_CHAN6:
+ case RVIN_CHAN7:
+ master = RVIN_CHAN4;
+ break;
+ default:
+ master = RVIN_CHAN_ERROR;
+ break;
+ }
+
+ /* Something is wrong, subdevice can't be resolved to channel id */
+ BUG_ON(master == RVIN_CHAN_ERROR);
+
+ return master;
+}
+
+static enum rvin_csi_id rvin_group_get_csi(struct rvin_group *grp,
+ struct v4l2_subdev *sd, int chsel)
+{
+ struct rvin_dev *vin =
+ container_of(sd->v4l2_dev, struct rvin_dev, v4l2_dev);
+ enum rvin_chan_id chan;
+ enum rvin_csi_id csi = RVIN_CSI_ERROR;
+ int chsel_max;
+
+ if ((vin->chip == RCAR_H3) && (RCAR_PRR_IS_PRODUCT(H3)
+ && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)))
+ chsel_max = RVIN_H3_WS11_CHSEL_MAX;
+ else
+ chsel_max = RVIN_CHSEL_MAX;
+
+ if (chsel < 0 || chsel > chsel_max)
+ return RVIN_CSI_ERROR;
+
+ chan = sd_to_chan(grp, sd);
+
+ if ((vin->chip == RCAR_H3) && (RCAR_PRR_IS_PRODUCT(H3)
+ && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)))
+ csi = rvin_group_h3_ws11_map[chan][chsel].csi;
+ else if (vin->chip == RCAR_H3)
+ csi = rvin_group_h3_map[chan][chsel].csi;
+ else if (vin->chip == RCAR_M3)
+ csi = rvin_group_m3_map[chan][chsel].csi;
+
+ /* Not all CSI source might be available */
+ if (!grp->bridge[csi].subdev || !grp->source[csi].subdev)
+ return RVIN_CSI_ERROR;
+
+ return csi;
+}
+
+static int rvin_group_chsel_get(struct rvin_group *grp, struct v4l2_subdev *sd)
+{
+ enum rvin_chan_id master;
+
+ master = chan_to_master(grp, sd_to_chan(grp, sd));
+
+ if (master == RVIN_CHAN0)
+ return grp->chsel1;
+
+ return grp->chsel2;
+}
+
+static void rvin_group_chsel_set(struct rvin_group *grp, struct v4l2_subdev *sd,
+ int chsel)
+{
+ enum rvin_chan_id master;
+
+ master = chan_to_master(grp, sd_to_chan(grp, sd));
+
+ if (master == RVIN_CHAN0)
+ grp->chsel1 = chsel;
+ else
+ grp->chsel2 = chsel;
+}
+
+static enum rvin_csi_id sd_to_csi(struct rvin_group *grp,
+ struct v4l2_subdev *sd)
+{
+ return rvin_group_get_csi(grp, sd, rvin_group_chsel_get(grp, sd));
+}
+
+/* -----------------------------------------------------------------------------
+ * Group API - logic
+ */
+
+static int rvin_group_get_sink_idx(struct media_entity *entity, int source_idx)
+{
+ unsigned int i;
+
+ /* Iterates through the pads to find a connected sink pad. */
+ for (i = 0; i < entity->num_pads; ++i) {
+ struct media_pad *sink = &entity->pads[i];
+
+ if (!(sink->flags & MEDIA_PAD_FL_SINK))
+ continue;
+
+ if (sink->index == source_idx)
+ continue;
+
+ if (media_entity_has_route(entity, sink->index, source_idx))
+ return sink->index;
+ }
+
+ /* If not found return the first pad, guaranteed to be a sink pad. */
+ return 0;
+}
+
+static int rvin_group_get(struct v4l2_subdev *sd,
+ struct rvin_input_item *inputs)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ struct rvin_dev *vin =
+ container_of(sd->v4l2_dev, struct rvin_dev, v4l2_dev);
+ enum rvin_chan_id chan, master;
+ enum rvin_csi_id csi;
+ int i, num = 0;
+ int chsel_max;
+
+ mutex_lock(&grp->lock);
+
+ chan = sd_to_chan(grp, sd);
+
+ /* If subgroup master is not present channel is useless */
+ master = chan_to_master(grp, chan);
+ if (!grp->chan[master].subdev) {
+ grp_err(grp, "chan%d: No group master found\n", chan);
+ goto out;
+ }
+
+ /* Make sure channel is usable with current chsel */
+ if (sd_to_csi(grp, sd) == RVIN_CSI_ERROR) {
+ grp_info(grp, "chan%d: Unavailable with current chsel\n", chan);
+ goto out;
+ }
+
+ if ((vin->chip == RCAR_H3) && (RCAR_PRR_IS_PRODUCT(H3)
+ && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)))
+ chsel_max = RVIN_H3_WS11_CHSEL_MAX;
+ else
+ chsel_max = RVIN_CHSEL_MAX;
+
+ /* Create list of valid inputs */
+ for (i = 0; i < chsel_max; i++) {
+ csi = rvin_group_get_csi(grp, sd, i);
+ if (rvin_group_get_csi(grp, sd, i) != RVIN_CSI_ERROR) {
+ inputs[num].type = RVIN_INPUT_CSI2;
+ inputs[num].chsel = i;
+ inputs[num].hint = rvin_group_chsel_get(grp, sd) == i;
+ inputs[num].source_idx = grp->source[csi].source_idx;
+ inputs[num].sink_idx =
+ rvin_group_get_sink_idx(
+ &grp->source[csi].subdev->entity,
+ inputs[num].source_idx);
+ if ((vin->chip == RCAR_H3) && (RCAR_PRR_IS_PRODUCT(H3)
+ && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)))
+ strlcpy(inputs[num].name,
+ rvin_group_h3_ws11_map[chan][i].name,
+ RVIN_INPUT_NAME_SIZE);
+ else if (vin->chip == RCAR_H3)
+ strlcpy(inputs[num].name,
+ rvin_group_h3_map[chan][i].name,
+ RVIN_INPUT_NAME_SIZE);
+ else if (vin->chip == RCAR_M3)
+ strlcpy(inputs[num].name,
+ rvin_group_m3_map[chan][i].name,
+ RVIN_INPUT_NAME_SIZE);
+ grp_dbg(grp, "chan%d: %s source pad: %d sink pad: %d\n",
+ chan, inputs[num].name, inputs[num].source_idx,
+ inputs[num].sink_idx);
+ num++;
+ }
+ }
+
+ grp->users[chan]++;
+out:
+ mutex_unlock(&grp->lock);
+
+ return num;
+}
+
+static int rvin_group_put(struct v4l2_subdev *sd)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+
+ mutex_lock(&grp->lock);
+ grp->users[sd_to_chan(grp, sd)]--;
+ mutex_unlock(&grp->lock);
+
+ return 0;
+}
+
+static int rvin_group_set_input(struct v4l2_subdev *sd,
+ struct rvin_input_item *item)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_chan_id chan, master;
+ int i, chsel, ret = 0;
+
+ chan = sd_to_chan(grp, sd);
+ chsel = item->chsel;
+
+ mutex_lock(&grp->lock);
+
+ /* No need to set chsel if it's already set */
+ if (chsel == rvin_group_chsel_get(grp, sd))
+ goto out;
+
+ /* Do not allow a chsel that is not usable for channel */
+ if (rvin_group_get_csi(grp, sd, chsel) == RVIN_CSI_ERROR) {
+ grp_err(grp, "chan%d: Invalid chsel\n", chan);
+ goto out;
+ }
+
+ /* If subgroup master is not present we can't write the chsel */
+ master = chan_to_master(grp, chan);
+ if (!grp->chan[master].subdev) {
+ grp_err(grp, "chan%d: No group master found\n", chan);
+ goto out;
+ }
+
+ /*
+ * Check that all needed resurces are free. We don't want to
+ * change input selection if somone else uses our subgroup or
+ * if there are another user of our channel.
+ */
+ for (i = 0; i < RVIN_CHAN_MAX; i++) {
+
+ /* Only look in our sub group */
+ if (master != chan_to_master(grp, i))
+ continue;
+
+ /* Need to be only user of channel and subgroup to set hsel */
+ if ((i == chan && grp->users[i] != 1) ||
+ (i != chan && grp->users[i] != 0)) {
+ grp_info(grp, "chan%d: %s in use, can't set chsel\n",
+ chan, i == chan ? "Channel" : "Group");
+ ret = -EBUSY;
+ goto out;
+ }
+ }
+
+ ret = v4l2_subdev_call(grp->chan[master].subdev, core, s_gpio, chsel);
+ rvin_group_chsel_set(grp, sd, chsel);
+out:
+ mutex_unlock(&grp->lock);
+ return ret;
+}
+
+static int rvin_group_get_code(struct v4l2_subdev *sd, u32 *code)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ *code = grp->source[csi].code;
+
+ return 0;
+}
+
+static int rvin_group_get_mbus_cfg(struct v4l2_subdev *sd,
+ struct v4l2_mbus_config *mbus_cfg)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ *mbus_cfg = grp->source[csi].mbus_cfg;
+
+ return 0;
+}
+
+static int rvin_group_ctrl_add_handler(struct v4l2_subdev *sd,
+ struct v4l2_ctrl_handler *hdl)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_ctrl_add_handler(hdl, grp->source[csi].subdev->ctrl_handler,
+ NULL);
+}
+
+static int rvin_group_alloc_pad_config(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config **cfg)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ *cfg = v4l2_subdev_alloc_pad_config(grp->source[csi].subdev);
+
+ return 0;
+}
+
+static int rvin_group_g_tvnorms_input(struct v4l2_subdev *sd,
+ struct rvin_input_item *item,
+ v4l2_std_id *std)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = rvin_group_get_csi(grp, sd, item->chsel);
+
+ if (csi == RVIN_CSI_ERROR)
+ return -EINVAL;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video,
+ g_tvnorms, std);
+}
+
+static int rvin_group_g_input_status(struct v4l2_subdev *sd,
+ struct rvin_input_item *item, u32 *status)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = rvin_group_get_csi(grp, sd, item->chsel);
+
+ if (csi == RVIN_CSI_ERROR)
+ return -EINVAL;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video,
+ g_input_status, status);
+}
+
+static int rvin_group_dv_timings_cap(struct v4l2_subdev *sd,
+ struct rvin_input_item *item,
+ struct v4l2_dv_timings_cap *cap)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = rvin_group_get_csi(grp, sd, item->chsel);
+
+ if (csi == RVIN_CSI_ERROR)
+ return -EINVAL;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, pad,
+ dv_timings_cap, cap);
+}
+
+static int rvin_group_enum_dv_timings(struct v4l2_subdev *sd,
+ struct rvin_input_item *item,
+ struct v4l2_enum_dv_timings *timings)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = rvin_group_get_csi(grp, sd, item->chsel);
+
+ if (csi == RVIN_CSI_ERROR)
+ return -EINVAL;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, pad,
+ enum_dv_timings, timings);
+}
+
+static const struct rvin_group_input_ops rvin_input_ops = {
+ .g_tvnorms = &rvin_group_g_tvnorms_input,
+ .g_input_status = &rvin_group_g_input_status,
+ .dv_timings_cap = &rvin_group_dv_timings_cap,
+ .enum_dv_timings = &rvin_group_enum_dv_timings,
+};
+
+/* -----------------------------------------------------------------------------
+ * Basic group subdev operations
+ */
+
+static int rvin_group_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+ int ret = 0;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ mutex_lock(&grp->lock);
+ /* If we already are powerd just increment the usage */
+ if ((on && grp->power[csi] != 0) || (!on && grp->power[csi] != 1))
+ goto unlock;
+
+ /* Important to start bridge fist, it needs a quiet bus to start */
+ ret = v4l2_subdev_call(grp->bridge[csi].subdev, core, s_power, on);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ goto unlock_err;
+ ret = v4l2_subdev_call(grp->source[csi].subdev, core, s_power, on);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ goto unlock_err;
+
+ grp_dbg(grp, "csi%d: power: %d bridge: %s source: %s\n", csi,
+ on, grp->bridge[csi].subdev->name,
+ grp->source[csi].subdev->name);
+unlock:
+ grp->power[csi] += on ? 1 : -1;
+unlock_err:
+ mutex_unlock(&grp->lock);
+ return ret;
+}
+
+static const struct v4l2_subdev_core_ops rvin_group_core_ops = {
+ .s_power = &rvin_group_s_power,
+};
+
+static int rvin_group_g_std(struct v4l2_subdev *sd, v4l2_std_id *std)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video, g_std, std);
+}
+
+static int rvin_group_s_std(struct v4l2_subdev *sd, v4l2_std_id std)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video, s_std, std);
+}
+
+static int rvin_group_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video, querystd, std);
+}
+
+static int rvin_group_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *tvnorms)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video, g_tvnorms,
+ tvnorms);
+}
+
+static int rvin_group_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+ int ret = 0;
+
+ mutex_lock(&grp->lock);
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR) {
+ ret = -ENODEV;
+ goto unlock_err;
+ }
+
+ /* If we already are streaming just increment the usage */
+ if ((enable && grp->stream[csi] != 0) ||
+ (!enable && grp->stream[csi] != 1))
+ goto unlock;
+
+ /* Important to start bridge fist, it needs a quiet bus to start */
+ ret = v4l2_subdev_call(grp->bridge[csi].subdev, video, s_stream,
+ enable);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ goto unlock_err;
+ ret = v4l2_subdev_call(grp->source[csi].subdev, video, s_stream,
+ enable);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ goto unlock_err;
+
+ grp_dbg(grp, "csi%d: stream: %d bridge: %s source %s\n", csi,
+ enable, grp->bridge[csi].subdev->name,
+ grp->source[csi].subdev->name);
+unlock:
+ grp->stream[csi] += enable ? 1 : -1;
+unlock_err:
+ mutex_unlock(&grp->lock);
+ return ret;
+}
+
+static int rvin_group_g_dv_timings(struct v4l2_subdev *sd,
+ struct v4l2_dv_timings *timings)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video,
+ g_dv_timings, timings);
+}
+
+static int rvin_group_s_dv_timings(struct v4l2_subdev *sd,
+ struct v4l2_dv_timings *timings)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video,
+ s_dv_timings, timings);
+}
+
+static int rvin_group_query_dv_timings(struct v4l2_subdev *sd,
+ struct v4l2_dv_timings *timings)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, video,
+ query_dv_timings, timings);
+}
+
+static const struct v4l2_subdev_video_ops rvin_group_video_ops = {
+ .g_std = rvin_group_g_std,
+ .s_std = rvin_group_s_std,
+ .querystd = rvin_group_querystd,
+ .g_tvnorms = rvin_group_g_tvnorms,
+ .s_stream = rvin_group_s_stream,
+ .g_dv_timings = rvin_group_g_dv_timings,
+ .s_dv_timings = rvin_group_s_dv_timings,
+ .query_dv_timings = rvin_group_query_dv_timings,
+};
+
+static int rvin_group_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *pad_cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ return v4l2_subdev_call(grp->source[csi].subdev, pad, get_fmt,
+ pad_cfg, fmt);
+}
+
+static int rvin_group_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *pad_cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+ enum rvin_csi_id csi;
+ int ret;
+
+ csi = sd_to_csi(grp, sd);
+ if (csi == RVIN_CSI_ERROR)
+ return -ENODEV;
+
+ /* First the source and then inform the bridge about the format. */
+ ret = v4l2_subdev_call(grp->source[csi].subdev, pad, set_fmt,
+ pad_cfg, fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ return ret;
+ return v4l2_subdev_call(grp->bridge[csi].subdev, pad, set_fmt,
+ NULL, fmt);
+}
+
+static const struct v4l2_subdev_pad_ops rvin_group_pad_ops = {
+ .get_fmt = rvin_group_get_fmt,
+ .set_fmt = rvin_group_set_fmt,
+};
+
+static const struct v4l2_subdev_ops rvin_group_ops = {
+ .core = &rvin_group_core_ops,
+ .video = &rvin_group_video_ops,
+ .pad = &rvin_group_pad_ops,
+};
+
+/* -----------------------------------------------------------------------------
+ * Async notifier
+ */
+
+#define notifier_to_grp(n) container_of(n, struct rvin_group, notifier)
+
+static void __verify_source_pad(struct rvin_graph_entity *entity)
+{
+ if (entity->source_idx >= entity->subdev->entity.num_pads)
+ goto use_default;
+ if (entity->subdev->entity.pads[entity->source_idx].flags !=
+ MEDIA_PAD_FL_SOURCE)
+ goto use_default;
+ return;
+use_default:
+ entity->source_idx = sd_to_pad_idx(entity->subdev, MEDIA_PAD_FL_SOURCE);
+}
+
+
+static int rvin_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct rvin_group *grp = notifier_to_grp(notifier);
+ int i;
+
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+ if (!grp->source[i].subdev)
+ continue;
+
+ __verify_source_pad(&grp->source[i]);
+
+ if (!rvin_mbus_supported(&grp->source[i])) {
+ grp_err(grp,
+ "Unsupported media bus format for %s pad %d\n",
+ grp->source[i].subdev->name,
+ grp->source[i].source_idx);
+ return -EINVAL;
+ }
+
+ grp_dbg(grp, "Found media bus format for %s pad %d: %d\n",
+ grp->source[i].subdev->name, grp->source[i].source_idx,
+ grp->source[i].code);
+ }
+ return 0;
+}
+
+static void rvin_graph_notify_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rvin_group *grp = notifier_to_grp(notifier);
+ bool matched = false;
+ int i;
+
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+ if (grp->bridge[i].subdev == subdev) {
+ grp_dbg(grp, "unbind bridge subdev %s\n", subdev->name);
+ grp->bridge[i].subdev = NULL;
+ matched = true;
+ }
+ if (grp->source[i].subdev == subdev) {
+ grp_dbg(grp, "unbind source subdev %s\n", subdev->name);
+ grp->source[i].subdev = NULL;
+ matched = true;
+ }
+ }
+
+ for (i = 0; i < RVIN_CHAN_MAX; i++) {
+ if (grp->chan[i].subdev == subdev) {
+ grp_dbg(grp, "unbind chan subdev %s\n", subdev->name);
+ grp->chan[i].subdev = NULL;
+ matched = true;
+ }
+ }
+
+ if (!matched)
+ grp_err(grp, "no entity for subdev %s to unbind\n", subdev->name);
+}
+
+static int rvin_graph_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rvin_group *grp = notifier_to_grp(notifier);
+ bool matched = false;
+ int i, j;
+
+ v4l2_set_subdev_hostdata(subdev, grp);
+
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+ if (grp->bridge[i].asd.match.of.node == subdev->dev->of_node) {
+ grp_dbg(grp, "bound bridge subdev %s\n", subdev->name);
+ grp->bridge[i].subdev = subdev;
+
+ for (j = i; j < (i + RVIN_VCSEL_MAX); j++)
+ grp->bridge[j].subdev = subdev;
+
+ matched = true;
+ }
+ if (grp->source[i].asd.match.of.node == subdev->dev->of_node) {
+ grp_dbg(grp, "bound source subdev %s\n", subdev->name);
+ grp->source[i].subdev = subdev;
+ matched = true;
+ }
+ }
+
+ for (i = 0; i < RVIN_CHAN_MAX; i++) {
+ if (grp->chan[i].asd.match.of.node == subdev->dev->of_node) {
+ grp_dbg(grp, "bound chan subdev %s\n", subdev->name);
+ grp->chan[i].subdev = subdev;
+
+ /* Write initial chsel if binding subgroup master */
+ if (i == RVIN_CHAN0)
+ v4l2_subdev_call(subdev, core, s_gpio,
+ grp->chsel1);
+ if (i == RVIN_CHAN4)
+ v4l2_subdev_call(subdev, core, s_gpio,
+ grp->chsel2);
+
+ matched = true;
+ }
+ }
+
+ if (matched)
+ return 0;
+
+ grp_err(grp, "no entity for subdev %s to bind\n", subdev->name);
+ return -EINVAL;
+}
+
+static int rvin_parse_v4l2_endpoint(struct rvin_group *grp,
+ struct device_node *ep,
+ struct v4l2_mbus_config *mbus_cfg)
+{
+ struct v4l2_of_endpoint v4l2_ep;
+ int ret;
+
+ ret = v4l2_of_parse_endpoint(ep, &v4l2_ep);
+ if (ret) {
+ grp_err(grp, "Could not parse v4l2 endpoint\n");
+ return -EINVAL;
+ }
+
+ if (v4l2_ep.bus_type != V4L2_MBUS_CSI2) {
+ grp_err(grp, "Unsupported media bus type for %s\n",
+ of_node_full_name(ep));
+ return -EINVAL;
+ }
+
+ mbus_cfg->type = v4l2_ep.bus_type;
+ mbus_cfg->flags = v4l2_ep.bus.mipi_csi2.flags;
+
+ return 0;
+}
+
+static int rvin_get_csi_source(struct rvin_group *grp, int id)
+{
+ struct device_node *ep, *np, *rp, *bridge = NULL, *source = NULL;
+ struct v4l2_mbus_config mbus_cfg;
+ struct of_endpoint endpoint;
+ int ret;
+ u32 souce_cnt = 0, loop_cnt = 0;
+
+ grp->bridge[id].asd.match.of.node = NULL;
+ grp->bridge[id].subdev = NULL;
+ grp->source[id].asd.match.of.node = NULL;
+ grp->source[id].subdev = NULL;
+
+ /* Not all indexes will be defined, this is OK */
+ ep = of_graph_get_endpoint_by_regs(grp->dev->of_node, RVIN_PORT_CSI,
+ id);
+ if (!ep)
+ return 0;
+
+ /* Get bridge */
+ bridge = of_graph_get_remote_port_parent(ep);
+ of_node_put(ep);
+ if (!bridge) {
+ grp_err(grp, "No bridge found for endpoint '%s'\n",
+ of_node_full_name(ep));
+ return -EINVAL;
+ }
+
+ /* Not all bridges are available, this is OK */
+ if (!of_device_is_available(bridge)) {
+ of_node_put(bridge);
+ return 0;
+ }
+
+ /* Get source */
+ for_each_endpoint_of_node(bridge, ep) {
+ np = of_graph_get_remote_port_parent(ep);
+ if (!np) {
+ grp_err(grp, "No remote found for endpoint '%s'\n",
+ of_node_full_name(ep));
+ of_node_put(bridge);
+ of_node_put(ep);
+ return -EINVAL;
+ }
+
+ if (grp->dev->of_node == np) {
+ /* Ignore loop-back */
+ } else if (!of_device_is_available(np)) {
+ /* Not all sources are available, this is OK */
+ } else if (source) {
+ souce_cnt++;
+
+ if (souce_cnt == loop_cnt) {
+ grp_dbg(grp, "Multiple source for %s\n",
+ of_node_full_name(source));
+
+ ret = rvin_parse_v4l2_endpoint(grp, ep,
+ &mbus_cfg);
+ if (ret) {
+ of_node_put(bridge);
+ of_node_put(ep);
+ of_node_put(np);
+ return ret;
+ }
+ source = np;
+ grp->source[id].mbus_cfg = mbus_cfg;
+ grp->source[id].asd.match.of.node = source;
+ grp->source[id].asd.match_type =
+ V4L2_ASYNC_MATCH_OF;
+ }
+ } else {
+ /* Get endpoint information */
+ rp = of_graph_get_remote_port(ep);
+ of_graph_parse_endpoint(rp, &endpoint);
+ of_node_put(rp);
+
+ ret = rvin_parse_v4l2_endpoint(grp, ep, &mbus_cfg);
+ if (ret) {
+ of_node_put(bridge);
+ of_node_put(ep);
+ of_node_put(np);
+ return ret;
+ }
+ source = np;
+ grp->source[id].mbus_cfg = mbus_cfg;
+ grp->source[id].source_idx = endpoint.id;
+ grp->source[id].asd.match.of.node = source;
+ grp->source[id].asd.match_type = V4L2_ASYNC_MATCH_OF;
+
+ /* Multiple source registration */
+ if ((id % 4) == 1)
+ loop_cnt = 1;
+ else if ((id % 4) == 2)
+ loop_cnt = 2;
+ else if ((id % 4) == 3)
+ loop_cnt = 3;
+ else
+ loop_cnt = 0;
+ }
+
+ of_node_put(np);
+ }
+ of_node_put(bridge);
+
+ grp->bridge[id].asd.match.of.node = bridge;
+ grp->bridge[id].asd.match_type = V4L2_ASYNC_MATCH_OF;
+
+ grp_dbg(grp, "csi%d: bridge: %s source: %s pad: %d", id,
+ of_node_full_name(grp->bridge[id].asd.match.of.node),
+ of_node_full_name(grp->source[id].asd.match.of.node),
+ grp->source[id].source_idx);
+
+ return ret;
+}
+
+static int rvin_get_remote_channels(struct rvin_group *grp, int id)
+{
+ struct device_node *ep, *remote;
+ int ret = 0;
+
+ grp->chan[id].asd.match.of.node = NULL;
+ grp->chan[id].subdev = NULL;
+
+ /* Not all indexes will be defined, this is OK */
+ ep = of_graph_get_endpoint_by_regs(grp->dev->of_node, RVIN_PORT_REMOTE,
+ id);
+ if (!ep)
+ return 0;
+
+ /* Find remote subdevice */
+ remote = of_graph_get_remote_port_parent(ep);
+ if (!remote) {
+ grp_err(grp, "No remote parent for endpoint '%s'\n",
+ of_node_full_name(ep));
+ ret = -EINVAL;
+ goto out_ep;
+ }
+
+ /* Not all remotes will be available, this is OK */
+ if (!of_device_is_available(remote)) {
+ ret = 0;
+ goto out_remote;
+ }
+
+ grp->chan[id].asd.match.of.node = remote;
+ grp->chan[id].asd.match_type = V4L2_ASYNC_MATCH_OF;
+
+ grp_dbg(grp, "chan%d: node: '%s'\n", id,
+ of_node_full_name(grp->chan[id].asd.match.of.node));
+
+out_remote:
+ of_node_put(remote);
+out_ep:
+ of_node_put(ep);
+
+ return ret;
+}
+
+static int __node_add(struct v4l2_async_subdev **subdev, int num,
+ struct rvin_graph_entity *entity)
+{
+ int i;
+
+ if (!entity->asd.match.of.node)
+ return 0;
+
+ for (i = 0; i < num; i++) {
+
+ if (!subdev[i]) {
+ subdev[i] = &entity->asd;
+ return 1;
+ }
+
+ if (subdev[i]->match.of.node == entity->asd.match.of.node)
+ break;
+ }
+
+ return 0;
+}
+
+static int rvin_graph_init(struct rvin_group *grp)
+{
+ struct v4l2_async_subdev **subdevs = NULL;
+ int i, ret, found = 0, matched = 0;
+
+ /* Try to get CSI2 sources */
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+ ret = rvin_get_csi_source(grp, i);
+ if (ret)
+ return ret;
+ if (grp->bridge[i].asd.match.of.node &&
+ grp->source[i].asd.match.of.node)
+ found += 2;
+ }
+
+ /* Try to get slave channels */
+ for (i = 0; i < RVIN_CHAN_MAX; i++) {
+ ret = rvin_get_remote_channels(grp, i);
+ if (ret)
+ return ret;
+ if (grp->chan[i].asd.match.of.node)
+ found++;
+ }
+
+ if (!found)
+ return -ENODEV;
+
+ /* Register the subdevices notifier. */
+ subdevs = devm_kzalloc(grp->dev, sizeof(*subdevs) * found, GFP_KERNEL);
+ if (subdevs == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+ matched += __node_add(subdevs, found, &grp->bridge[i]);
+ matched += __node_add(subdevs, found, &grp->source[i]);
+ }
+ for (i = 0; i < RVIN_CHAN_MAX; i++)
+ matched += __node_add(subdevs, found, &grp->chan[i]);
+
+ grp_dbg(grp, "found %d group subdevice(s) %d are unique\n", found, matched);
+
+ grp->notifier.num_subdevs = matched;
+ grp->notifier.subdevs = subdevs;
+ grp->notifier.bound = rvin_graph_notify_bound;
+ grp->notifier.unbind = rvin_graph_notify_unbind;
+ grp->notifier.complete = rvin_graph_notify_complete;
+
+ ret = v4l2_async_notifier_register(grp->v4l2_dev, &grp->notifier);
+ if (ret < 0) {
+ grp_err(grp, "Notifier registration failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Base
+ */
+
+struct rvin_group_api *rvin_group_probe(struct device *dev,
+ struct v4l2_device *v4l2_dev)
+{
+ struct rvin_group *grp;
+ int i, ret;
+
+ grp = devm_kzalloc(dev, sizeof(*grp), GFP_KERNEL);
+ if (!grp)
+ return NULL;
+
+ grp->dev = dev;
+ grp->v4l2_dev = v4l2_dev;
+ grp->chsel1 = 0;
+ grp->chsel2 = 1;
+
+ ret = RCAR_PRR_INIT();
+ if (ret) {
+ devm_kfree(dev, grp);
+ grp_dbg(grp, "product register init fail.\n");
+ return NULL;
+ }
+
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+ grp->power[i] = 0;
+ grp->stream[i] = 0;
+ }
+
+ for (i = 0; i < RVIN_CHAN_MAX; i++)
+ grp->users[i] = 0;
+
+ ret = rvin_graph_init(grp);
+ if (ret) {
+ devm_kfree(dev, grp);
+ return NULL;
+ }
+
+ mutex_init(&grp->lock);
+
+ grp->api.ops = &rvin_group_ops;
+ grp->api.input_ops = &rvin_input_ops;
+
+ grp->api.get = rvin_group_get;
+ grp->api.put = rvin_group_put;
+ grp->api.set_input = rvin_group_set_input;
+ grp->api.get_code = rvin_group_get_code;
+ grp->api.get_mbus_cfg = rvin_group_get_mbus_cfg;
+ grp->api.ctrl_add_handler = rvin_group_ctrl_add_handler;
+ grp->api.alloc_pad_config = rvin_group_alloc_pad_config;
+
+ return &grp->api;
+}
+
+int rvin_group_remove(struct rvin_group_api *api)
+{
+ struct rvin_group *grp = container_of(api, struct rvin_group, api);
+
+ v4l2_async_notifier_unregister(&grp->notifier);
+
+ mutex_destroy(&grp->lock);
+
+ return 0;
+}
diff --git a/drivers/media/platform/rcar-vin/rcar-group.h b/drivers/media/platform/rcar-vin/rcar-group.h
new file mode 100644
index 0000000..9171aeb
--- /dev/null
+++ b/drivers/media/platform/rcar-vin/rcar-group.h
@@ -0,0 +1,168 @@
+/*
+ * Driver for Renesas R-Car VIN
+ *
+ * Copyright (C) 2016 Renesas Electronics Corp.
+ *
+ * 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.
+ */
+
+#ifndef __RCAR_GROUP__
+#define __RCAR_GROUP__
+
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+
+#define RVIN_PORT_LOCAL 0
+#define RVIN_PORT_CSI 1
+#define RVIN_PORT_REMOTE 2
+
+enum rvin_input_type {
+ RVIN_INPUT_NONE,
+ RVIN_INPUT_DIGITAL,
+ RVIN_INPUT_CSI2,
+};
+
+/* Max number of inputs supported */
+#define RVIN_INPUT_MAX 7
+#define RVIN_INPUT_NAME_SIZE 32
+
+/**
+ * struct rvin_input_item - One possible input for the channel
+ * @name: User-friendly name of the input
+ * @type: Type of the input or RVIN_INPUT_NONE if not available
+ * @chsel: The chsel value needed to select this input
+ * @sink_idx: Sink pad number from the subdevice associated with the input
+ * @source_idx: Source pad number from the subdevice associated with the input
+ */
+struct rvin_input_item {
+ char name[RVIN_INPUT_NAME_SIZE];
+ enum rvin_input_type type;
+ int chsel;
+ bool hint;
+ int sink_idx;
+ int source_idx;
+};
+
+/**
+ * struct rvin_graph_entity - Video endpoint from async framework
+ * @asd: sub-device descriptor for async framework
+ * @subdev: subdevice matched using async framework
+ * @code: Media bus format from source
+ * @mbus_cfg: Media bus format from DT
+ * @source_idx: Source pad on remote device
+ */
+struct rvin_graph_entity {
+ struct v4l2_async_subdev asd;
+ struct v4l2_subdev *subdev;
+
+ u32 code;
+ struct v4l2_mbus_config mbus_cfg;
+
+ unsigned int source_idx;
+};
+
+static inline int rvin_mbus_supported(struct rvin_graph_entity *entity)
+{
+ struct v4l2_subdev *sd = entity->subdev;
+ struct v4l2_subdev_mbus_code_enum code = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ code.index = 0;
+ code.pad = entity->source_idx;
+
+ while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) {
+ code.index++;
+ switch (code.code) {
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY10_2X10:
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ entity->code = code.code;
+ return true;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Older versions where looking for the wrong media bus format.
+ * It where looking for a YUVY format but then treated it as a
+ * UYVY format. This was not noticed since atlest one subdevice
+ * used for testing (adv7180) reported a YUVY media bus format
+ * but provided UYVY data. There might be other unknown subdevices
+ * which also do this, to not break compatibility try to use them
+ * in legacy mode.
+ */
+ code.index = 0;
+ while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) {
+ code.index++;
+ switch (code.code) {
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ entity->code = MEDIA_BUS_FMT_UYVY8_2X8;
+ return true;
+ case MEDIA_BUS_FMT_YUYV10_2X10:
+ entity->code = MEDIA_BUS_FMT_UYVY10_2X10;
+ return true;
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+static inline int sd_to_pad_idx(struct v4l2_subdev *sd, int flag)
+{
+ int pad_idx;
+#if defined(CONFIG_MEDIA_CONTROLLER)
+ for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
+ if (sd->entity.pads[pad_idx].flags == flag)
+ break;
+
+ if (pad_idx >= sd->entity.num_pads)
+ pad_idx = 0;
+#else
+ pad_idx = 0;
+#endif
+ return pad_idx;
+}
+
+struct rvin_group_input_ops {
+ int (*g_input_status)(struct v4l2_subdev *sd,
+ struct rvin_input_item *item, u32 *status);
+ int (*g_tvnorms)(struct v4l2_subdev *sd,
+ struct rvin_input_item *item, v4l2_std_id *std);
+ int (*dv_timings_cap)(struct v4l2_subdev *sd,
+ struct rvin_input_item *item,
+ struct v4l2_dv_timings_cap *cap);
+ int (*enum_dv_timings)(struct v4l2_subdev *sd,
+ struct rvin_input_item *item,
+ struct v4l2_enum_dv_timings *timings);
+};
+
+struct rvin_group_api {
+ int (*get)(struct v4l2_subdev *sd, struct rvin_input_item *inputs);
+ int (*put)(struct v4l2_subdev *sd);
+ int (*set_input)(struct v4l2_subdev *sd, struct rvin_input_item *item);
+ int (*get_code)(struct v4l2_subdev *sd, u32 *code);
+ int (*get_mbus_cfg)(struct v4l2_subdev *sd,
+ struct v4l2_mbus_config *mbus_cfg);
+
+ int (*ctrl_add_handler)(struct v4l2_subdev *sd,
+ struct v4l2_ctrl_handler *hdl);
+ int (*alloc_pad_config)(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config **cfg);
+
+ const struct v4l2_subdev_ops *ops;
+ const struct rvin_group_input_ops *input_ops;
+};
+
+struct rvin_group_api *rvin_group_probe(struct device *dev,
+ struct v4l2_device *v4l2_dev);
+int rvin_group_remove(struct rvin_group_api *grp);
+
+#endif
diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c
index 2bbe6d4..928ed14 100644
--- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
+++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
@@ -25,6 +25,8 @@
#define RVIN_DEFAULT_FORMAT V4L2_PIX_FMT_YUYV
#define RVIN_MAX_WIDTH 2048
#define RVIN_MAX_HEIGHT 2048
+#define RVIN_MAX_WIDTH_GEN3 4096
+#define RVIN_MAX_HEIGHT_GEN3 4096
/* -----------------------------------------------------------------------------
* Format Conversions
@@ -32,6 +34,10 @@
static const struct rvin_video_format rvin_formats[] = {
{
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .bpp = 1,
+ },
+ {
.fourcc = V4L2_PIX_FMT_NV16,
.bpp = 1,
},
@@ -48,10 +54,14 @@ static const struct rvin_video_format rvin_formats[] = {
.bpp = 2,
},
{
- .fourcc = V4L2_PIX_FMT_XRGB555,
+ .fourcc = V4L2_PIX_FMT_ARGB555,
.bpp = 2,
},
{
+ .fourcc = V4L2_PIX_FMT_ABGR32,
+ .bpp = 4,
+ },
+ {
.fourcc = V4L2_PIX_FMT_XBGR32,
.bpp = 4,
},
@@ -85,6 +95,9 @@ static u32 rvin_format_sizeimage(struct v4l2_pix_format *pix)
if (pix->pixelformat == V4L2_PIX_FMT_NV16)
return pix->bytesperline * pix->height * 2;
+ if (pix->pixelformat == V4L2_PIX_FMT_NV12)
+ return pix->bytesperline * pix->height * 3 / 2;
+
return pix->bytesperline * pix->height;
}
@@ -92,99 +105,37 @@ static u32 rvin_format_sizeimage(struct v4l2_pix_format *pix)
* V4L2
*/
-static void rvin_reset_crop_compose(struct rvin_dev *vin)
-{
- vin->crop.top = vin->crop.left = 0;
- vin->crop.width = vin->source.width;
- vin->crop.height = vin->source.height;
-
- vin->compose.top = vin->compose.left = 0;
- vin->compose.width = vin->format.width;
- vin->compose.height = vin->format.height;
-}
-
-static int rvin_reset_format(struct rvin_dev *vin)
-{
- struct v4l2_subdev_format fmt = {
- .which = V4L2_SUBDEV_FORMAT_ACTIVE,
- };
- struct v4l2_mbus_framefmt *mf = &fmt.format;
- int ret;
-
- fmt.pad = vin->src_pad_idx;
-
- ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt);
- if (ret)
- return ret;
-
- vin->format.width = mf->width;
- vin->format.height = mf->height;
- vin->format.colorspace = mf->colorspace;
- vin->format.field = mf->field;
-
- /*
- * If the subdevice uses ALTERNATE field mode and G_STD is
- * implemented use the VIN HW to combine the two fields to
- * one INTERLACED frame. The ALTERNATE field mode can still
- * be requested in S_FMT and be respected, this is just the
- * default which is applied at probing or when S_STD is called.
- */
- if (vin->format.field == V4L2_FIELD_ALTERNATE &&
- v4l2_subdev_has_op(vin_to_source(vin), video, g_std))
- vin->format.field = V4L2_FIELD_INTERLACED;
-
- switch (vin->format.field) {
- case V4L2_FIELD_TOP:
- case V4L2_FIELD_BOTTOM:
- case V4L2_FIELD_ALTERNATE:
- vin->format.height /= 2;
- break;
- case V4L2_FIELD_NONE:
- case V4L2_FIELD_INTERLACED_TB:
- case V4L2_FIELD_INTERLACED_BT:
- case V4L2_FIELD_INTERLACED:
- break;
- default:
- vin->format.field = V4L2_FIELD_NONE;
- break;
- }
-
- rvin_reset_crop_compose(vin);
-
- return 0;
-}
-
static int __rvin_try_format_source(struct rvin_dev *vin,
u32 which,
struct v4l2_pix_format *pix,
struct rvin_source_fmt *source)
{
- struct v4l2_subdev *sd;
struct v4l2_subdev_pad_config *pad_cfg;
struct v4l2_subdev_format format = {
.which = which,
};
+ u32 code;
enum v4l2_field field;
int ret;
- sd = vin_to_source(vin);
+ ret = rvin_subdev_get_code(vin, &code);
+ if (ret)
+ return -EINVAL;
- v4l2_fill_mbus_format(&format.format, pix, vin->digital.code);
+ v4l2_fill_mbus_format(&format.format, pix, code);
- pad_cfg = v4l2_subdev_alloc_pad_config(sd);
+ pad_cfg = rvin_subdev_alloc_pad_config(vin);
if (pad_cfg == NULL)
return -ENOMEM;
- format.pad = vin->src_pad_idx;
+ format.pad = vin->inputs[vin->current_input].source_idx;
field = pix->field;
- ret = v4l2_subdev_call(sd, pad, set_fmt, pad_cfg, &format);
+ ret = rvin_subdev_call(vin, pad, set_fmt, pad_cfg, &format);
if (ret < 0 && ret != -ENOIOCTLCMD)
goto done;
- v4l2_fill_pix_format(pix, &format.format);
-
pix->field = field;
source->width = pix->width;
@@ -204,7 +155,9 @@ static int __rvin_try_format(struct rvin_dev *vin,
struct rvin_source_fmt *source)
{
const struct rvin_video_format *info;
- u32 rwidth, rheight, walign;
+ u32 rwidth, rheight, walign, max_width, max_height;
+ int ret;
+ v4l2_std_id std;
/* Requested */
rwidth = pix->width;
@@ -244,7 +197,17 @@ static int __rvin_try_format(struct rvin_dev *vin,
case V4L2_FIELD_NONE:
case V4L2_FIELD_INTERLACED_TB:
case V4L2_FIELD_INTERLACED_BT:
+ break;
case V4L2_FIELD_INTERLACED:
+ ret = rvin_subdev_call(vin, video, querystd, &std);
+ if (ret == -ENOIOCTLCMD)
+ pix->field = V4L2_FIELD_NONE;
+ else if (ret < 0)
+ goto out;
+ else
+ pix->field = std & V4L2_STD_625_50 ?
+ V4L2_FIELD_INTERLACED_TB :
+ V4L2_FIELD_INTERLACED_BT;
break;
default:
pix->field = V4L2_FIELD_NONE;
@@ -255,12 +218,39 @@ static int __rvin_try_format(struct rvin_dev *vin,
if (source->width != rwidth || source->height != rheight)
rvin_scale_try(vin, pix, rwidth, rheight);
- /* HW limit width to a multiple of 32 (2^5) for NV16 else 2 (2^1) */
- walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1;
+ /* At the time of capturing NV16/NV12 format, user has to specify
+ * the width of the multiple of 32 for H/W specification.
+ */
+ if (((pix->pixelformat == V4L2_PIX_FMT_NV16) ||
+ (pix->pixelformat == V4L2_PIX_FMT_NV12)) &&
+ (pix->width % 32)) {
+ vin_err(vin,
+ "specify width of 32 multiple in separate format.\n");
+ return -EINVAL;
+ }
+
+ /* HW limit width to a multiple of 32 (2^5) for NV16/12 else 2 (2^1) */
+ if ((pix->pixelformat == V4L2_PIX_FMT_NV12) ||
+ (pix->pixelformat == V4L2_PIX_FMT_NV16))
+ walign = 5;
+ else if ((pix->pixelformat == V4L2_PIX_FMT_YUYV) ||
+ (pix->pixelformat == V4L2_PIX_FMT_UYVY))
+ walign = 1;
+ else
+ walign = 0;
/* Limit to VIN capabilities */
- v4l_bound_align_image(&pix->width, 2, RVIN_MAX_WIDTH, walign,
- &pix->height, 4, RVIN_MAX_HEIGHT, 2, 0);
+ if (vin->chip == RCAR_H3 || vin->chip == RCAR_M3) {
+ max_width = RVIN_MAX_WIDTH_GEN3;
+ max_height = RVIN_MAX_HEIGHT_GEN3;
+ } else {
+ max_width = RVIN_MAX_WIDTH;
+ max_height = RVIN_MAX_HEIGHT;
+ }
+
+ /* Limit to VIN capabilities */
+ v4l_bound_align_image(&pix->width, 5, max_width, walign,
+ &pix->height, 2, max_height, 0, 0);
pix->bytesperline = max_t(u32, pix->bytesperline,
rvin_format_bytesperline(pix));
@@ -272,11 +262,26 @@ static int __rvin_try_format(struct rvin_dev *vin,
return -EINVAL;
}
+ if ((vin->chip != RCAR_H3 && vin->chip != RCAR_M3) &&
+ (pix->pixelformat == V4L2_PIX_FMT_NV12)) {
+ vin_err(vin, "pixel format NV12 is supported from GEN3\n");
+ return -EINVAL;
+ }
+
+ if ((vin->chip != RCAR_H3 && vin->chip != RCAR_M3 &&
+ vin->chip != RCAR_GEN2) &&
+ (pix->pixelformat == V4L2_PIX_FMT_NV12)) {
+ vin_err(vin, "pixel format ARGB8888 is supported from GEN2\n");
+ return -EINVAL;
+ }
+
vin_dbg(vin, "Requested %ux%u Got %ux%u bpl: %d size: %d\n",
rwidth, rheight, pix->width, pix->height,
pix->bytesperline, pix->sizeimage);
return 0;
+out:
+ return ret;
}
static int rvin_querycap(struct file *file, void *priv,
@@ -301,10 +306,8 @@ static int rvin_try_fmt_vid_cap(struct file *file, void *priv,
&source);
}
-static int rvin_s_fmt_vid_cap(struct file *file, void *priv,
- struct v4l2_format *f)
+static int __rvin_s_fmt_vid_cap(struct rvin_dev *vin, struct v4l2_format *f)
{
- struct rvin_dev *vin = video_drvdata(file);
struct rvin_source_fmt source;
int ret;
@@ -321,11 +324,17 @@ static int rvin_s_fmt_vid_cap(struct file *file, void *priv,
vin->format = f->fmt.pix;
- rvin_reset_crop_compose(vin);
-
return 0;
}
+static int rvin_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rvin_dev *vin = video_drvdata(file);
+
+ return __rvin_s_fmt_vid_cap(vin, f);
+}
+
static int rvin_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
@@ -457,79 +466,217 @@ static int rvin_cropcap(struct file *file, void *priv,
struct v4l2_cropcap *crop)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_mbus_framefmt *mf = &fmt.format;
+ int ret;
if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
- return v4l2_subdev_call(sd, video, g_pixelaspect, &crop->pixelaspect);
+ /* Changing the standard will change the width/height */
+ ret = rvin_subdev_call(vin, pad, get_fmt, NULL, &fmt);
+ if (ret) {
+ vin_err(vin, "Failed to get initial format\n");
+ return ret;
+ }
+
+ crop->bounds.left = 0;
+ crop->bounds.top = 0;
+ crop->bounds.width = mf->width;
+ crop->bounds.height = mf->height;
+
+ /* default cropping rectangle */
+ crop->defrect = crop->bounds;
+ crop->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ return 0;
+}
+
+static int rvin_attach_subdevices(struct rvin_dev *vin)
+{
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_mbus_framefmt *mf = &fmt.format;
+ struct v4l2_format f;
+ int ret;
+
+ ret = rvin_subdev_set_input(vin, &vin->inputs[vin->current_input]);
+ if (ret)
+ return ret;
+
+ ret = rvin_subdev_call(vin, core, s_power, 1);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ return ret;
+
+ vin->vdev.tvnorms = 0;
+ ret = rvin_subdev_call(vin, video, g_tvnorms, &vin->vdev.tvnorms);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ goto error;
+
+ /* Add controlls */
+ ret = rvin_subdev_ctrl_add_handler(vin);
+ if (ret < 0)
+ goto error;
+
+ v4l2_ctrl_handler_setup(&vin->ctrl_handler);
+
+ fmt.pad = vin->inputs[vin->current_input].source_idx;
+
+ /* Try to improve our guess of a reasonable window format */
+ ret = rvin_subdev_call(vin, pad, get_fmt, NULL, &fmt);
+ if (ret)
+ goto error;
+
+ /* Set default format */
+ vin->format.width = mf->width;
+ vin->format.height = mf->height;
+ vin->format.colorspace = mf->colorspace;
+ vin->format.field = mf->field;
+ vin->format.pixelformat = RVIN_DEFAULT_FORMAT;
+
+ /* Set initial crop and compose */
+ vin->crop.top = vin->crop.left = 0;
+ vin->crop.width = mf->width;
+ vin->crop.height = mf->height;
+
+ vin->compose.top = vin->compose.left = 0;
+ vin->compose.width = mf->width;
+ vin->compose.height = mf->height;
+
+ f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ f.fmt.pix = vin->format;
+ ret = __rvin_s_fmt_vid_cap(vin, &f);
+ if (ret)
+ goto error;
+
+ return 0;
+error:
+ rvin_subdev_call(vin, core, s_power, 0);
+ return ret;
+}
+
+static void rvin_detach_subdevices(struct rvin_dev *vin)
+{
+ rvin_subdev_call(vin, core, s_power, 0);
}
static int rvin_enum_input(struct file *file, void *priv,
struct v4l2_input *i)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
+ struct v4l2_dv_timings_cap cap;
+ struct rvin_input_item *item;
int ret;
- if (i->index != 0)
+ if (i->index > RVIN_INPUT_MAX ||
+ vin->inputs[i->index].type == RVIN_INPUT_NONE)
return -EINVAL;
- ret = v4l2_subdev_call(sd, video, g_input_status, &i->status);
+ item = &vin->inputs[i->index];
+
+ ret = rvin_subdev_call_input(vin, i->index, video,
+ g_input_status, &i->status);
+
if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
return ret;
i->type = V4L2_INPUT_TYPE_CAMERA;
- i->std = vin->vdev.tvnorms;
+ strlcpy(i->name, item->name, sizeof(i->name));
- if (v4l2_subdev_has_op(sd, pad, dv_timings_cap))
+ /* Test if pad supports dv_timings_cap */
+ cap.pad = vin->inputs[i->index].sink_idx;
+ ret = rvin_subdev_call_input(vin, i->index, pad, dv_timings_cap, &cap);
+ if (ret) {
+ i->capabilities = V4L2_IN_CAP_STD;
+ ret = rvin_subdev_call_input(vin,
+ vin->current_input,
+ video, g_tvnorms, &i->std);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ return ret;
+
+ } else {
i->capabilities = V4L2_IN_CAP_DV_TIMINGS;
-
- strlcpy(i->name, "Camera", sizeof(i->name));
+ i->std = 0;
+ }
return 0;
}
static int rvin_g_input(struct file *file, void *priv, unsigned int *i)
{
- *i = 0;
+ struct rvin_dev *vin = video_drvdata(file);
+
+ *i = vin->current_input;
return 0;
}
static int rvin_s_input(struct file *file, void *priv, unsigned int i)
{
- if (i > 0)
+ struct rvin_dev *vin = video_drvdata(file);
+ int ret;
+
+ if (i > RVIN_INPUT_MAX || vin->inputs[i].type == RVIN_INPUT_NONE)
return -EINVAL;
- return 0;
+
+ rvin_detach_subdevices(vin);
+
+ ret = rvin_subdev_set_input(vin, &vin->inputs[i]);
+ if (!ret)
+ vin->current_input = i;
+
+ /* Power on new subdevice */
+ return rvin_attach_subdevices(vin);
}
static int rvin_querystd(struct file *file, void *priv, v4l2_std_id *a)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
- return v4l2_subdev_call(sd, video, querystd, a);
+ return rvin_subdev_call(vin, video, querystd, a);
}
static int rvin_s_std(struct file *file, void *priv, v4l2_std_id a)
{
struct rvin_dev *vin = video_drvdata(file);
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_mbus_framefmt *mf = &fmt.format;
int ret;
- ret = v4l2_subdev_call(vin_to_source(vin), video, s_std, a);
+ ret = rvin_subdev_call(vin, video, s_std, a);
if (ret < 0)
return ret;
/* Changing the standard will change the width/height */
- return rvin_reset_format(vin);
+ ret = rvin_subdev_call(vin, pad, get_fmt, NULL, &fmt);
+ if (ret) {
+ vin_err(vin, "Failed to get initial format\n");
+ return ret;
+ }
+
+ vin->format.width = mf->width;
+ vin->format.height = mf->height;
+
+ vin->crop.top = vin->crop.left = 0;
+ vin->crop.width = mf->width;
+ vin->crop.height = mf->height;
+
+ vin->compose.top = vin->compose.left = 0;
+ vin->compose.width = mf->width;
+ vin->compose.height = mf->height;
+
+ return 0;
}
static int rvin_g_std(struct file *file, void *priv, v4l2_std_id *a)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
- return v4l2_subdev_call(sd, video, g_std, a);
+ return rvin_subdev_call(vin, video, g_std, a);
}
static int rvin_subscribe_event(struct v4l2_fh *fh,
@@ -546,15 +693,15 @@ static int rvin_enum_dv_timings(struct file *file, void *priv_fh,
struct v4l2_enum_dv_timings *timings)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
- int pad, ret;
+ int input, ret;
- pad = timings->pad;
- timings->pad = vin->sink_pad_idx;
+ input = timings->pad;
- ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings);
+ timings->pad = vin->inputs[input].sink_idx;
- timings->pad = pad;
+ ret = rvin_subdev_call_input(vin, input, pad, enum_dv_timings, timings);
+
+ timings->pad = input;
return ret;
}
@@ -563,52 +710,48 @@ static int rvin_s_dv_timings(struct file *file, void *priv_fh,
struct v4l2_dv_timings *timings)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
- int ret;
+ int err;
- ret = v4l2_subdev_call(sd, video, s_dv_timings, timings);
- if (ret)
- return ret;
-
- vin->source.width = timings->bt.width;
- vin->source.height = timings->bt.height;
- vin->format.width = timings->bt.width;
- vin->format.height = timings->bt.height;
-
- return 0;
+ err = rvin_subdev_call(vin, video, s_dv_timings, timings);
+ if (!err) {
+ vin->source.width = timings->bt.width;
+ vin->source.height = timings->bt.height;
+ vin->format.width = timings->bt.width;
+ vin->format.height = timings->bt.height;
+ }
+ return err;
}
static int rvin_g_dv_timings(struct file *file, void *priv_fh,
struct v4l2_dv_timings *timings)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
- return v4l2_subdev_call(sd, video, g_dv_timings, timings);
+ return rvin_subdev_call(vin, video, g_dv_timings, timings);
}
static int rvin_query_dv_timings(struct file *file, void *priv_fh,
struct v4l2_dv_timings *timings)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
- return v4l2_subdev_call(sd, video, query_dv_timings, timings);
+ return rvin_subdev_call(vin, video, query_dv_timings, timings);
}
static int rvin_dv_timings_cap(struct file *file, void *priv_fh,
struct v4l2_dv_timings_cap *cap)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
- int pad, ret;
+ int input, ret;
- pad = cap->pad;
- cap->pad = vin->sink_pad_idx;
+ input = cap->pad;
- ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap);
+ cap->pad = vin->inputs[input].sink_idx;
- cap->pad = pad;
+ ret = rvin_subdev_call_input(vin, input, pad,
+ dv_timings_cap, cap);
+
+ cap->pad = input;
return ret;
}
@@ -616,7 +759,6 @@ static int rvin_dv_timings_cap(struct file *file, void *priv_fh,
static int rvin_g_edid(struct file *file, void *fh, struct v4l2_edid *edid)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
int input, ret;
if (edid->pad)
@@ -625,7 +767,7 @@ static int rvin_g_edid(struct file *file, void *fh, struct v4l2_edid *edid)
input = edid->pad;
edid->pad = vin->sink_pad_idx;
- ret = v4l2_subdev_call(sd, pad, get_edid, edid);
+ ret = rvin_subdev_call_local(vin, pad, get_edid, edid);
edid->pad = input;
@@ -635,7 +777,6 @@ static int rvin_g_edid(struct file *file, void *fh, struct v4l2_edid *edid)
static int rvin_s_edid(struct file *file, void *fh, struct v4l2_edid *edid)
{
struct rvin_dev *vin = video_drvdata(file);
- struct v4l2_subdev *sd = vin_to_source(vin);
int input, ret;
if (edid->pad)
@@ -644,7 +785,7 @@ static int rvin_s_edid(struct file *file, void *fh, struct v4l2_edid *edid)
input = edid->pad;
edid->pad = vin->sink_pad_idx;
- ret = v4l2_subdev_call(sd, pad, set_edid, edid);
+ ret = rvin_subdev_call_local(vin, pad, set_edid, edid);
edid->pad = input;
@@ -699,80 +840,6 @@ static const struct v4l2_ioctl_ops rvin_ioctl_ops = {
* File Operations
*/
-static int rvin_power_on(struct rvin_dev *vin)
-{
- int ret;
- struct v4l2_subdev *sd = vin_to_source(vin);
-
- pm_runtime_get_sync(vin->v4l2_dev.dev);
-
- ret = v4l2_subdev_call(sd, core, s_power, 1);
- if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
- return ret;
- return 0;
-}
-
-static int rvin_power_off(struct rvin_dev *vin)
-{
- int ret;
- struct v4l2_subdev *sd = vin_to_source(vin);
-
- ret = v4l2_subdev_call(sd, core, s_power, 0);
-
- pm_runtime_put(vin->v4l2_dev.dev);
-
- if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
- return ret;
-
- return 0;
-}
-
-static int rvin_initialize_device(struct file *file)
-{
- struct rvin_dev *vin = video_drvdata(file);
- int ret;
-
- struct v4l2_format f = {
- .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
- .fmt.pix = {
- .width = vin->format.width,
- .height = vin->format.height,
- .field = vin->format.field,
- .colorspace = vin->format.colorspace,
- .pixelformat = vin->format.pixelformat,
- },
- };
-
- ret = rvin_power_on(vin);
- if (ret < 0)
- return ret;
-
- pm_runtime_enable(&vin->vdev.dev);
- ret = pm_runtime_resume(&vin->vdev.dev);
- if (ret < 0 && ret != -ENOSYS)
- goto eresume;
-
- /*
- * Try to configure with default parameters. Notice: this is the
- * very first open, so, we cannot race against other calls,
- * apart from someone else calling open() simultaneously, but
- * .host_lock is protecting us against it.
- */
- ret = rvin_s_fmt_vid_cap(file, NULL, &f);
- if (ret < 0)
- goto esfmt;
-
- v4l2_ctrl_handler_setup(&vin->ctrl_handler);
-
- return 0;
-esfmt:
- pm_runtime_disable(&vin->vdev.dev);
-eresume:
- rvin_power_off(vin);
-
- return ret;
-}
-
static int rvin_open(struct file *file)
{
struct rvin_dev *vin = video_drvdata(file);
@@ -784,17 +851,30 @@ static int rvin_open(struct file *file)
ret = v4l2_fh_open(file);
if (ret)
- goto unlock;
+ goto err_out;
- if (!v4l2_fh_is_singular_file(file))
- goto unlock;
+ ret = rvin_subdev_get(vin);
+ if (ret)
+ goto err_open;
- if (rvin_initialize_device(file)) {
- v4l2_fh_release(file);
- ret = -ENODEV;
+ if (v4l2_fh_is_singular_file(file)) {
+ pm_runtime_get_sync(vin->dev);
+ ret = rvin_attach_subdevices(vin);
+ if (ret) {
+ vin_err(vin, "Error attaching subdevices\n");
+ goto err_get;
+ }
}
-unlock:
+ mutex_unlock(&vin->lock);
+
+ return 0;
+err_get:
+ pm_runtime_put(vin->dev);
+ rvin_subdev_put(vin);
+err_open:
+ v4l2_fh_release(file);
+err_out:
mutex_unlock(&vin->lock);
return ret;
}
@@ -818,11 +898,12 @@ static int rvin_release(struct file *file)
* Then de-initialize hw module.
*/
if (fh_singular) {
- pm_runtime_suspend(&vin->vdev.dev);
- pm_runtime_disable(&vin->vdev.dev);
- rvin_power_off(vin);
+ rvin_detach_subdevices(vin);
+ pm_runtime_put(vin->dev);
}
+ rvin_subdev_put(vin);
+
mutex_unlock(&vin->lock);
return ret;
@@ -868,41 +949,10 @@ static void rvin_notify(struct v4l2_subdev *sd,
int rvin_v4l2_probe(struct rvin_dev *vin)
{
struct video_device *vdev = &vin->vdev;
- struct v4l2_subdev *sd = vin_to_source(vin);
- int pad_idx, ret;
-
- v4l2_set_subdev_hostdata(sd, vin);
+ int ret;
vin->v4l2_dev.notify = rvin_notify;
- ret = v4l2_subdev_call(sd, video, g_tvnorms, &vin->vdev.tvnorms);
- if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
- return ret;
-
- if (vin->vdev.tvnorms == 0) {
- /* Disable the STD API if there are no tvnorms defined */
- v4l2_disable_ioctl(&vin->vdev, VIDIOC_G_STD);
- v4l2_disable_ioctl(&vin->vdev, VIDIOC_S_STD);
- v4l2_disable_ioctl(&vin->vdev, VIDIOC_QUERYSTD);
- v4l2_disable_ioctl(&vin->vdev, VIDIOC_ENUMSTD);
- }
-
- /* Add the controls */
- /*
- * Currently the subdev with the largest number of controls (13) is
- * ov6550. So let's pick 16 as a hint for the control handler. Note
- * that this is a hint only: too large and you waste some memory, too
- * small and there is a (very) small performance hit when looking up
- * controls in the internal hash.
- */
- ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16);
- if (ret < 0)
- return ret;
-
- ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, sd->ctrl_handler, NULL);
- if (ret < 0)
- return ret;
-
/* video node */
vdev->fops = &rvin_fops;
vdev->v4l2_dev = &vin->v4l2_dev;
@@ -915,25 +965,6 @@ int rvin_v4l2_probe(struct rvin_dev *vin)
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE;
- vin->src_pad_idx = 0;
- for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
- if (sd->entity.pads[pad_idx].flags == MEDIA_PAD_FL_SOURCE)
- break;
- if (pad_idx >= sd->entity.num_pads)
- return -EINVAL;
-
- vin->src_pad_idx = pad_idx;
-
- vin->sink_pad_idx = 0;
- for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
- if (sd->entity.pads[pad_idx].flags == MEDIA_PAD_FL_SINK) {
- vin->sink_pad_idx = pad_idx;
- break;
- }
-
- vin->format.pixelformat = RVIN_DEFAULT_FORMAT;
- rvin_reset_format(vin);
-
ret = video_register_device(&vin->vdev, VFL_TYPE_GRABBER, -1);
if (ret) {
vin_err(vin, "Failed to register video device\n");
diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h
index 727e215..e129054 100644
--- a/drivers/media/platform/rcar-vin/rcar-vin.h
+++ b/drivers/media/platform/rcar-vin/rcar-vin.h
@@ -23,6 +23,10 @@
#include <media/v4l2-device.h>
#include <media/videobuf2-v4l2.h>
+#include "rcar-group.h"
+
+#define DRV_NAME "rcar-vin"
+
/* Number of HW buffers */
#define HW_BUFFER_NUM 3
@@ -30,9 +34,12 @@
#define HW_BUFFER_MASK 0x7f
enum chip_id {
+ RCAR_M3,
+ RCAR_H3,
+ RCAR_GEN3,
+ RCAR_GEN2,
RCAR_H1,
RCAR_M1,
- RCAR_GEN2,
};
/**
@@ -68,19 +75,17 @@ struct rvin_video_format {
u8 bpp;
};
-/**
- * struct rvin_graph_entity - Video endpoint from async framework
- * @asd: sub-device descriptor for async framework
- * @subdev: subdevice matched using async framework
- * @code: Media bus format from source
- * @mbus_cfg: Media bus format from DT
- */
-struct rvin_graph_entity {
- struct v4l2_async_subdev asd;
- struct v4l2_subdev *subdev;
-
- u32 code;
- struct v4l2_mbus_config mbus_cfg;
+enum gen3_vin_ch {
+ RCAR_VIN_CH_NONE = -1,
+ RCAR_VIDEO_0,
+ RCAR_VIDEO_1,
+ RCAR_VIDEO_2,
+ RCAR_VIDEO_3,
+ RCAR_VIDEO_4,
+ RCAR_VIDEO_5,
+ RCAR_VIDEO_6,
+ RCAR_VIDEO_7,
+ RCAR_VIDEO_MAX,
};
/**
@@ -113,6 +118,12 @@ struct rvin_graph_entity {
*
* @crop: active cropping
* @compose: active composing
+ *
+ * @slave: subdevice used to register with the group master
+ * @api: group api controller (only used on master channel)
+ *
+ * @current_input: currently used input in @inputs
+ * @inputs: list of valid inputs sources
*/
struct rvin_dev {
struct device *dev;
@@ -142,9 +153,15 @@ struct rvin_dev {
struct v4l2_rect crop;
struct v4l2_rect compose;
-};
-#define vin_to_source(vin) vin->digital.subdev
+ struct v4l2_subdev slave;
+ struct rvin_group_api *api;
+
+ int current_input;
+ struct rvin_input_item inputs[RVIN_INPUT_MAX];
+
+ unsigned int index;
+};
/* Debug */
#define vin_dbg(d, fmt, arg...) dev_dbg(d->dev, fmt, ##arg)
@@ -152,9 +169,13 @@ struct rvin_dev {
#define vin_warn(d, fmt, arg...) dev_warn(d->dev, fmt, ##arg)
#define vin_err(d, fmt, arg...) dev_err(d->dev, fmt, ##arg)
+int rvin_set_ifmd(struct rvin_dev *vin, int val);
int rvin_dma_probe(struct rvin_dev *vin, int irq);
void rvin_dma_remove(struct rvin_dev *vin);
+int rvin_subdev_probe(struct rvin_dev *vin);
+void rvin_subdev_remove(struct rvin_dev *vin);
+
int rvin_v4l2_probe(struct rvin_dev *vin);
void rvin_v4l2_remove(struct rvin_dev *vin);
@@ -165,4 +186,39 @@ void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix,
u32 width, u32 height);
void rvin_crop_scale_comp(struct rvin_dev *vin);
+/* Subdevice group helpers */
+#define rvin_input_is_csi(v) (v->inputs[v->current_input].type == \
+ RVIN_INPUT_CSI2)
+#define vin_to_group(v) container_of(v->slave.v4l2_dev, struct rvin_dev, \
+ v4l2_dev)->api
+#define rvin_subdev_call_local(v, o, f, args...) \
+ (v->digital.subdev ? \
+ v4l2_subdev_call(v->digital.subdev, o, f, ##args) : -ENODEV)
+#define rvin_subdev_call_group(v, o, f, args...) \
+ (!(v)->slave.v4l2_dev ? -ENODEV : \
+ (vin_to_group(v)->ops->o && vin_to_group(v)->ops->o->f) ? \
+ vin_to_group(v)->ops->o->f(&v->slave, ##args) : -ENOIOCTLCMD)
+#define rvin_subdev_call_group_input(v, i, f, args...) \
+ (!(v)->slave.v4l2_dev ? -ENODEV : \
+ (vin_to_group(v)->input_ops->f ? \
+ vin_to_group(v)->input_ops->f(&v->slave, i, ##args) : -ENOIOCTLCMD))
+#define rvin_subdev_call(v, o, f, args...) \
+ (rvin_input_is_csi(v) ? rvin_subdev_call_group(v, o, f, ##args) :\
+ rvin_subdev_call_local(v, o, f, ##args))
+#define rvin_subdev_call_input(v, i, o, f, args...) \
+ (v->inputs[i].type == RVIN_INPUT_CSI2 ? \
+ rvin_subdev_call_group_input(v, &v->inputs[i], f, ##args) : \
+ rvin_subdev_call_local(v, o, f, ##args))
+
+int rvin_subdev_get(struct rvin_dev *vin);
+int rvin_subdev_put(struct rvin_dev *vin);
+int rvin_subdev_set_input(struct rvin_dev *vin, struct rvin_input_item *item);
+
+int rvin_subdev_get_code(struct rvin_dev *vin, u32 *code);
+int rvin_subdev_get_mbus_cfg(struct rvin_dev *vin,
+ struct v4l2_mbus_config *mbus_cfg);
+
+int rvin_subdev_ctrl_add_handler(struct rvin_dev *vin);
+struct v4l2_subdev_pad_config *rvin_subdev_alloc_pad_config(struct rvin_dev
+ *vin);
#endif
diff --git a/drivers/media/platform/vsp1/Makefile b/drivers/media/platform/vsp1/Makefile
index a33afc3..7e2258c 100644
--- a/drivers/media/platform/vsp1/Makefile
+++ b/drivers/media/platform/vsp1/Makefile
@@ -4,6 +4,6 @@
vsp1-y += vsp1_clu.o vsp1_hsit.o vsp1_lut.o
vsp1-y += vsp1_bru.o vsp1_sru.o vsp1_uds.o
vsp1-y += vsp1_hgo.o vsp1_hgt.o vsp1_histo.o
-vsp1-y += vsp1_lif.o
+vsp1-y += vsp1_lif.o vsp1_brs.o
obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1.o
diff --git a/drivers/media/platform/vsp1/vsp1.h b/drivers/media/platform/vsp1/vsp1.h
index 85387a6..d8a32a3 100644
--- a/drivers/media/platform/vsp1/vsp1.h
+++ b/drivers/media/platform/vsp1/vsp1.h
@@ -1,7 +1,7 @@
/*
* vsp1.h -- R-Car VSP1 Driver
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -31,6 +31,7 @@ struct vsp1_drm;
struct vsp1_entity;
struct vsp1_platform_data;
struct vsp1_bru;
+struct vsp1_brs;
struct vsp1_clu;
struct vsp1_hgo;
struct vsp1_hgt;
@@ -44,6 +45,7 @@ struct vsp1_uds;
#define VSP1_MAX_RPF 5
#define VSP1_MAX_UDS 3
#define VSP1_MAX_WPF 4
+#define VSP1_MAX_LIF 2
#define VSP1_HAS_LIF (1 << 0)
#define VSP1_HAS_LUT (1 << 1)
@@ -54,6 +56,7 @@ struct vsp1_uds;
#define VSP1_HAS_WPF_HFLIP (1 << 6)
#define VSP1_HAS_HGO (1 << 7)
#define VSP1_HAS_HGT (1 << 8)
+#define VSP1_HAS_BRS (1 << 9)
struct vsp1_device_info {
u32 version;
@@ -65,6 +68,7 @@ struct vsp1_device_info {
unsigned int wpf_count;
unsigned int num_bru_inputs;
bool uapi;
+ bool header_mode;
};
struct vsp1_device {
@@ -76,12 +80,13 @@ struct vsp1_device {
struct rcar_fcp_device *fcp;
struct vsp1_bru *bru;
+ struct vsp1_brs *brs;
struct vsp1_clu *clu;
struct vsp1_hgo *hgo;
struct vsp1_hgt *hgt;
struct vsp1_hsit *hsi;
struct vsp1_hsit *hst;
- struct vsp1_lif *lif;
+ struct vsp1_lif *lif[VSP1_MAX_LIF];
struct vsp1_lut *lut;
struct vsp1_rwpf *rpf[VSP1_MAX_RPF];
struct vsp1_sru *sru;
@@ -95,11 +100,21 @@ struct vsp1_device {
struct media_device media_dev;
struct media_entity_operations media_ops;
+ unsigned int num_brs_inputs;
+
+ bool auto_fld_mode;
+
struct vsp1_drm *drm;
+ int index;
+
+ dma_addr_t dl_addr;
+ unsigned int dl_body;
};
+int vsp1_gen3_vspdl_check(struct vsp1_device *vsp1);
int vsp1_device_get(struct vsp1_device *vsp1);
void vsp1_device_put(struct vsp1_device *vsp1);
+void vsp1_underrun_workaround(struct vsp1_device *vsp1, bool reset);
int vsp1_reset_wpf(struct vsp1_device *vsp1, unsigned int index);
diff --git a/drivers/media/platform/vsp1/vsp1_brs.c b/drivers/media/platform/vsp1/vsp1_brs.c
new file mode 100644
index 0000000..e9811a7
--- /dev/null
+++ b/drivers/media/platform/vsp1/vsp1_brs.c
@@ -0,0 +1,448 @@
+/*
+ * vsp1_brs.c -- R-Car VSP1 Blend ROP Sub Unit
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * This file is based on the drivers/media/platform/vsp1/vsp1_bru.c
+ *
+ * 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/device.h>
+#include <linux/gfp.h>
+
+#include <media/v4l2-subdev.h>
+
+#include "vsp1.h"
+#include "vsp1_brs.h"
+#include "vsp1_dl.h"
+#include "vsp1_pipe.h"
+#include "vsp1_rwpf.h"
+#include "vsp1_video.h"
+
+#define BRS_MIN_SIZE 1U
+#define BRS_MAX_SIZE 8190U
+
+/* -----------------------------------------------------------------------------
+ * Device Access
+ */
+
+static inline void vsp1_brs_write(struct vsp1_brs *brs, struct vsp1_dl_list *dl,
+ u32 reg, u32 data)
+{
+ vsp1_dl_list_write(dl, reg, data);
+}
+
+/* -----------------------------------------------------------------------------
+ * Controls
+ */
+
+static int brs_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vsp1_brs *brs =
+ container_of(ctrl->handler, struct vsp1_brs, ctrls);
+
+ switch (ctrl->id) {
+ case V4L2_CID_BG_COLOR:
+ brs->bgcolor = ctrl->val;
+ break;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops brs_ctrl_ops = {
+ .s_ctrl = brs_s_ctrl,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Subdevice Operations
+ */
+
+/*
+ * The BRS can't perform format conversion, all sink and source formats must be
+ * identical. We pick the format on the first sink pad (pad 0) and propagate it
+ * to all other pads.
+ */
+
+static int brs_enum_mbus_code(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ static const unsigned int codes[] = {
+ MEDIA_BUS_FMT_ARGB8888_1X32,
+ MEDIA_BUS_FMT_AYUV8_1X32,
+ };
+
+ return vsp1_subdev_enum_mbus_code(subdev, cfg, code, codes,
+ ARRAY_SIZE(codes));
+}
+
+static int brs_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ if (fse->index)
+ return -EINVAL;
+
+ if (fse->code != MEDIA_BUS_FMT_ARGB8888_1X32 &&
+ fse->code != MEDIA_BUS_FMT_AYUV8_1X32)
+ return -EINVAL;
+
+ fse->min_width = BRS_MIN_SIZE;
+ fse->max_width = BRS_MAX_SIZE;
+ fse->min_height = BRS_MIN_SIZE;
+ fse->max_height = BRS_MAX_SIZE;
+
+ return 0;
+}
+
+static struct v4l2_rect *brs_get_compose(struct vsp1_brs *brs,
+ struct v4l2_subdev_pad_config *cfg,
+ unsigned int pad)
+{
+ return v4l2_subdev_get_try_compose(&brs->entity.subdev, cfg, pad);
+}
+
+static void brs_try_format(struct vsp1_brs *brs,
+ struct v4l2_subdev_pad_config *config,
+ unsigned int pad, struct v4l2_mbus_framefmt *fmt)
+{
+ struct v4l2_mbus_framefmt *format;
+
+ switch (pad) {
+ case BRS_PAD_SINK(0):
+ /* Default to YUV if the requested format is not supported. */
+ if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 &&
+ fmt->code != MEDIA_BUS_FMT_AYUV8_1X32)
+ fmt->code = MEDIA_BUS_FMT_AYUV8_1X32;
+ break;
+
+ default:
+ /* The BRS can't perform format conversion. */
+ format = vsp1_entity_get_pad_format(&brs->entity, config,
+ BRS_PAD_SINK(0));
+ fmt->code = format->code;
+ break;
+ }
+
+ fmt->width = clamp(fmt->width, BRS_MIN_SIZE, BRS_MAX_SIZE);
+ fmt->height = clamp(fmt->height, BRS_MIN_SIZE, BRS_MAX_SIZE);
+ fmt->field = V4L2_FIELD_NONE;
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
+}
+
+static int brs_set_format(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vsp1_brs *brs = to_brs(subdev);
+ struct v4l2_subdev_pad_config *config;
+ struct v4l2_mbus_framefmt *format;
+ int ret = 0;
+
+ mutex_lock(&brs->entity.lock);
+
+ config = vsp1_entity_get_pad_config(&brs->entity, cfg, fmt->which);
+ if (!config) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ brs_try_format(brs, config, fmt->pad, &fmt->format);
+
+ format = vsp1_entity_get_pad_format(&brs->entity, config, fmt->pad);
+ *format = fmt->format;
+
+ /* Reset the compose rectangle */
+ if (fmt->pad != brs->entity.source_pad) {
+ struct v4l2_rect *compose;
+
+ compose = brs_get_compose(brs, config, fmt->pad);
+ compose->left = 0;
+ compose->top = 0;
+ compose->width = format->width;
+ compose->height = format->height;
+ }
+
+ /* Propagate the format code to all pads */
+ if (fmt->pad == BRS_PAD_SINK(0)) {
+ unsigned int i;
+
+ for (i = 0; i <= brs->entity.source_pad; ++i) {
+ format = vsp1_entity_get_pad_format(&brs->entity,
+ config, i);
+ format->code = fmt->format.code;
+ }
+ }
+
+done:
+ mutex_unlock(&brs->entity.lock);
+ return ret;
+}
+
+static int brs_get_selection(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_selection *sel)
+{
+ struct vsp1_brs *brs = to_brs(subdev);
+ struct v4l2_subdev_pad_config *config;
+
+ if (sel->pad == brs->entity.source_pad)
+ return -EINVAL;
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ sel->r.left = 0;
+ sel->r.top = 0;
+ sel->r.width = BRS_MAX_SIZE;
+ sel->r.height = BRS_MAX_SIZE;
+ return 0;
+
+ case V4L2_SEL_TGT_COMPOSE:
+ config = vsp1_entity_get_pad_config(&brs->entity, cfg,
+ sel->which);
+ if (!config)
+ return -EINVAL;
+
+ mutex_lock(&brs->entity.lock);
+ sel->r = *brs_get_compose(brs, config, sel->pad);
+ mutex_unlock(&brs->entity.lock);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int brs_set_selection(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_selection *sel)
+{
+ struct vsp1_brs *brs = to_brs(subdev);
+ struct v4l2_subdev_pad_config *config;
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_rect *compose;
+ int ret = 0;
+
+ if (sel->pad == brs->entity.source_pad)
+ return -EINVAL;
+
+ if (sel->target != V4L2_SEL_TGT_COMPOSE)
+ return -EINVAL;
+
+ mutex_lock(&brs->entity.lock);
+
+ config = vsp1_entity_get_pad_config(&brs->entity, cfg, sel->which);
+ if (!config) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /*
+ * The compose rectangle top left corner must be inside the output
+ * frame.
+ */
+ format = vsp1_entity_get_pad_format(&brs->entity, config,
+ brs->entity.source_pad);
+ sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1);
+ sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1);
+
+ /* Scaling isn't supported, the compose rectangle size must be identical
+ * to the sink format size.
+ */
+ format = vsp1_entity_get_pad_format(&brs->entity, config, sel->pad);
+ sel->r.width = format->width;
+ sel->r.height = format->height;
+
+ compose = brs_get_compose(brs, config, sel->pad);
+ *compose = sel->r;
+
+done:
+ mutex_unlock(&brs->entity.lock);
+ return ret;
+}
+
+static const struct v4l2_subdev_pad_ops brs_pad_ops = {
+ .init_cfg = vsp1_entity_init_cfg,
+ .enum_mbus_code = brs_enum_mbus_code,
+ .enum_frame_size = brs_enum_frame_size,
+ .get_fmt = vsp1_subdev_get_pad_format,
+ .set_fmt = brs_set_format,
+ .get_selection = brs_get_selection,
+ .set_selection = brs_set_selection,
+};
+
+static const struct v4l2_subdev_ops brs_ops = {
+ .pad = &brs_pad_ops,
+};
+
+/* -----------------------------------------------------------------------------
+ * VSP1 Entity Operations
+ */
+
+static void brs_configure(struct vsp1_entity *entity,
+ struct vsp1_pipeline *pipe,
+ struct vsp1_dl_list *dl,
+ enum vsp1_entity_params params)
+{
+ struct vsp1_brs *brs = to_brs(&entity->subdev);
+ struct v4l2_mbus_framefmt *format;
+ unsigned int flags;
+ unsigned int i;
+
+ if (params != VSP1_ENTITY_PARAMS_INIT)
+ return;
+
+ format = vsp1_entity_get_pad_format(&brs->entity, brs->entity.config,
+ brs->entity.source_pad);
+
+ if (pipe->vmute_flag) {
+ vsp1_brs_write(brs, dl, VI6_BRS_INCTRL, 0);
+ vsp1_brs_write(brs, dl, VI6_BRS_VIRRPF_SIZE,
+ (format->width << VI6_BRS_VIRRPF_SIZE_HSIZE_SHIFT) |
+ (format->height << VI6_BRS_VIRRPF_SIZE_VSIZE_SHIFT));
+ vsp1_brs_write(brs, dl, VI6_BRS_VIRRPF_LOC, 0);
+ vsp1_brs_write(brs, dl, VI6_BRS_VIRRPF_COL, (0xFF << 24));
+
+ for (i = 0; i < brs->entity.source_pad; ++i) {
+ vsp1_brs_write(brs, dl, VI6_BRS_BLD(i),
+ VI6_BRS_BLD_CCMDX_255_SRC_A |
+ VI6_BRS_BLD_CCMDY_SRC_A |
+ VI6_BRS_BLD_ACMDX_255_SRC_A |
+ VI6_BRS_BLD_ACMDY_COEFY |
+ VI6_BRS_BLD_COEFY_MASK);
+ }
+
+ return;
+ }
+
+ /* The hardware is extremely flexible but we have no userspace API to
+ * expose all the parameters, nor is it clear whether we would have use
+ * cases for all the supported modes. Let's just harcode the parameters
+ * to sane default values for now.
+ */
+
+ /* Disable dithering and enable color data normalization unless the
+ * format at the pipeline output is premultiplied.
+ */
+ flags = pipe->output ? pipe->output->format.flags : 0;
+ vsp1_brs_write(brs, dl, VI6_BRS_INCTRL,
+ flags & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA ?
+ 0 : VI6_BRS_INCTRL_NRM);
+
+ /* Set the background position to cover the whole output image and
+ * configure its color.
+ */
+ vsp1_brs_write(brs, dl, VI6_BRS_VIRRPF_SIZE,
+ (format->width << VI6_BRS_VIRRPF_SIZE_HSIZE_SHIFT) |
+ (format->height << VI6_BRS_VIRRPF_SIZE_VSIZE_SHIFT));
+ vsp1_brs_write(brs, dl, VI6_BRS_VIRRPF_LOC, 0);
+
+ vsp1_brs_write(brs, dl, VI6_BRS_VIRRPF_COL, brs->bgcolor |
+ (0xff << VI6_BRS_VIRRPF_COL_A_SHIFT));
+
+ for (i = 0; i < brs->entity.source_pad; ++i) {
+ bool premultiplied = false;
+ u32 ctrl = 0;
+
+ /* Configure all Blend/ROP units corresponding to an enabled BRS
+ * input for alpha blending. Blend/ROP units corresponding to
+ * disabled BRS inputs are used in ROP NOP mode to ignore the
+ * SRC input.
+ */
+ if (brs->inputs[i].rpf) {
+ ctrl |= VI6_BRS_CTRL_RBC;
+
+ premultiplied = brs->inputs[i].rpf->format.flags
+ & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA;
+ } else {
+ ctrl |= VI6_BRS_CTRL_CROP(VI6_ROP_NOP)
+ | VI6_BRS_CTRL_AROP(VI6_ROP_NOP);
+ }
+
+ /* Select the virtual RPF as the Blend/ROP unit A DST input to
+ * serve as a background color.
+ */
+ if (i == 0)
+ ctrl |= VI6_BRS_CTRL_DSTSEL_VRPF;
+
+ /* Route BRS inputs 0 to 3 as SRC inputs to Blend/ROP units A to
+ * D in that order. The Blend/ROP unit B SRC is hardwired to the
+ * ROP unit output, the corresponding register bits must be set
+ * to 0.
+ */
+ if (i != 1)
+ ctrl |= VI6_BRS_CTRL_SRCSEL_BRSIN(i);
+
+ vsp1_brs_write(brs, dl, VI6_BRS_CTRL(i), ctrl);
+
+ /* Harcode the blending formula to
+ *
+ * DSTc = DSTc * (1 - SRCa) + SRCc * SRCa
+ * DSTa = DSTa * (1 - SRCa) + SRCa
+ *
+ * when the SRC input isn't premultiplied, and to
+ *
+ * DSTc = DSTc * (1 - SRCa) + SRCc
+ * DSTa = DSTa * (1 - SRCa) + SRCa
+ *
+ * otherwise.
+ */
+ vsp1_brs_write(brs, dl, VI6_BRS_BLD(i),
+ VI6_BRS_BLD_CCMDX_255_SRC_A |
+ (premultiplied ? VI6_BRS_BLD_CCMDY_COEFY :
+ VI6_BRS_BLD_CCMDY_SRC_A) |
+ VI6_BRS_BLD_ACMDX_255_SRC_A |
+ VI6_BRS_BLD_ACMDY_COEFY |
+ (0xff << VI6_BRS_BLD_COEFY_SHIFT));
+ }
+}
+
+static const struct vsp1_entity_operations brs_entity_ops = {
+ .configure = brs_configure,
+};
+
+/* -----------------------------------------------------------------------------
+ * Initialization and Cleanup
+ */
+
+struct vsp1_brs *vsp1_brs_create(struct vsp1_device *vsp1)
+{
+ struct vsp1_brs *brs;
+ int ret;
+
+ brs = devm_kzalloc(vsp1->dev, sizeof(*brs), GFP_KERNEL);
+ if (brs == NULL)
+ return ERR_PTR(-ENOMEM);
+
+ brs->entity.ops = &brs_entity_ops;
+ brs->entity.type = VSP1_ENTITY_BRS;
+
+ ret = vsp1_entity_init(vsp1, &brs->entity, "brs",
+ vsp1->info->rpf_count + 1, &brs_ops,
+ MEDIA_ENT_F_PROC_VIDEO_COMPOSER);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ /* Initialize the control handler. */
+ v4l2_ctrl_handler_init(&brs->ctrls, 1);
+ v4l2_ctrl_new_std(&brs->ctrls, &brs_ctrl_ops, V4L2_CID_BG_COLOR,
+ 0, 0xffffff, 1, 0);
+
+ brs->bgcolor = 0;
+
+ brs->entity.subdev.ctrl_handler = &brs->ctrls;
+
+ if (brs->ctrls.error) {
+ dev_err(vsp1->dev, "brs: failed to initialize controls\n");
+ ret = brs->ctrls.error;
+ vsp1_entity_destroy(&brs->entity);
+ return ERR_PTR(ret);
+ }
+
+ return brs;
+}
diff --git a/drivers/media/platform/vsp1/vsp1_brs.h b/drivers/media/platform/vsp1/vsp1_brs.h
new file mode 100644
index 0000000..b29bf55
--- /dev/null
+++ b/drivers/media/platform/vsp1/vsp1_brs.h
@@ -0,0 +1,46 @@
+/*
+ * vsp1_brs.h -- R-Car VSP1 Blend ROP Sub Unit
+ *
+ * Copyright (C) 2016 Renesas Corporation
+ *
+ * This file is based on the drivers/media/platform/vsp1/vsp1_bru.h
+ *
+ * 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.
+ */
+#ifndef __VSP1_BRS_H__
+#define __VSP1_BRS_H__
+
+#include <media/media-entity.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+
+#include "vsp1_entity.h"
+
+struct vsp1_device;
+struct vsp1_rwpf;
+
+#define BRS_PAD_SINK(n) (n)
+
+struct vsp1_brs {
+ struct vsp1_entity entity;
+
+ struct v4l2_ctrl_handler ctrls;
+
+ struct {
+ struct vsp1_rwpf *rpf;
+ } inputs[VSP1_MAX_RPF];
+
+ u32 bgcolor;
+};
+
+static inline struct vsp1_brs *to_brs(struct v4l2_subdev *subdev)
+{
+ return container_of(subdev, struct vsp1_brs, entity.subdev);
+}
+
+struct vsp1_brs *vsp1_brs_create(struct vsp1_device *vsp1);
+
+#endif /* __VSP1_BRS_H__ */
diff --git a/drivers/media/platform/vsp1/vsp1_bru.c b/drivers/media/platform/vsp1/vsp1_bru.c
index ee8355c..8b3164a 100644
--- a/drivers/media/platform/vsp1/vsp1_bru.c
+++ b/drivers/media/platform/vsp1/vsp1_bru.c
@@ -1,7 +1,7 @@
/*
* vsp1_bru.c -- R-Car VSP1 Blend ROP Unit
*
- * Copyright (C) 2013 Renesas Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -300,6 +300,26 @@ static void bru_configure(struct vsp1_entity *entity,
format = vsp1_entity_get_pad_format(&bru->entity, bru->entity.config,
bru->entity.source_pad);
+ if (pipe->vmute_flag) {
+ vsp1_bru_write(bru, dl, VI6_BRU_INCTRL, 0);
+ vsp1_bru_write(bru, dl, VI6_BRU_VIRRPF_SIZE,
+ (format->width << VI6_BRU_VIRRPF_SIZE_HSIZE_SHIFT) |
+ (format->height << VI6_BRU_VIRRPF_SIZE_VSIZE_SHIFT));
+ vsp1_bru_write(bru, dl, VI6_BRU_VIRRPF_LOC, 0);
+ vsp1_bru_write(bru, dl, VI6_BRU_VIRRPF_COL, (0xFF << 24));
+
+ for (i = 0; i < bru->entity.source_pad; ++i) {
+ vsp1_bru_write(bru, dl, VI6_BRU_BLD(i),
+ VI6_BRU_BLD_CCMDX_255_SRC_A |
+ VI6_BRU_BLD_CCMDY_SRC_A |
+ VI6_BRU_BLD_ACMDX_255_SRC_A |
+ VI6_BRU_BLD_ACMDY_COEFY |
+ VI6_BRU_BLD_COEFY_MASK);
+ }
+
+ return;
+ }
+
/* The hardware is extremely flexible but we have no userspace API to
* expose all the parameters, nor is it clear whether we would have use
* cases for all the supported modes. Let's just harcode the parameters
diff --git a/drivers/media/platform/vsp1/vsp1_dl.c b/drivers/media/platform/vsp1/vsp1_dl.c
index ad545af..77ae01c 100644
--- a/drivers/media/platform/vsp1/vsp1_dl.c
+++ b/drivers/media/platform/vsp1/vsp1_dl.c
@@ -1,7 +1,7 @@
/*
* vsp1_dl.h -- R-Car VSP1 Display List
*
- * Copyright (C) 2015 Renesas Corporation
+ * Copyright (C) 2015-2016 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -15,12 +15,18 @@
#include <linux/dma-mapping.h>
#include <linux/gfp.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include <linux/workqueue.h>
+#include <media/rcar-fcp.h>
+
#include "vsp1.h"
#include "vsp1_dl.h"
+#include "vsp1_pipe.h"
+#include "vsp1_rwpf.h"
#define VSP1_DL_NUM_ENTRIES 256
+#define VSP1_DL_EXT_OFFSET 0x1000
#define VSP1_DLH_INT_ENABLE (1 << 1)
#define VSP1_DLH_AUTO_START (1 << 0)
@@ -35,6 +41,24 @@ struct vsp1_dl_header {
struct vsp1_dl_header_list lists[8];
u32 next_header;
u32 flags;
+ /* if (VI6_DL_EXT_CTRL.EXT) */
+ u32 zero_bits;
+ /* zero_bits:6 + pre_ext_dl_exec:1 + */
+ /* post_ext_dl_exec:1 + zero_bits:8 + pre_ext_dl_num_cmd:16 */
+ u32 pre_post_num;
+ u32 pre_ext_dl_plist;
+ /* zero_bits:16 + post_ext_dl_num_cmd:16 */
+ u32 post_ext_dl_num_cmd;
+ u32 post_ext_dl_p_list;
+} __attribute__((__packed__));
+
+struct vsp1_ext_dl_body {
+ u32 ext_dl_cmd[2];
+ u32 ext_dl_data[2];
+} __attribute__((__packed__));
+
+struct vsp1_ext_addr {
+ u32 addr;
} __attribute__((__packed__));
struct vsp1_dl_entry {
@@ -68,6 +92,10 @@ struct vsp1_dl_body {
* @dlm: the display list manager
* @header: display list header, NULL for headerless lists
* @dma: DMA address for the header
+ * @ext_body: display list extended body
+ * @ext_dma: DMA address for extended body
+ * @src_dst_addr: display list (Auto-FLD) source/destination address
+ * @ext_addr_dma: DMA address for display list (Auto-FLD)
* @body0: first display list body
* @fragments: list of extra display list bodies
* @chain: entry in the display list partition chain
@@ -79,6 +107,12 @@ struct vsp1_dl_list {
struct vsp1_dl_header *header;
dma_addr_t dma;
+ struct vsp1_ext_dl_body *ext_body;
+ dma_addr_t ext_dma;
+
+ struct vsp1_ext_addr *src_dst_addr;
+ dma_addr_t ext_addr_dma;
+
struct vsp1_dl_body body0;
struct list_head fragments;
@@ -133,12 +167,13 @@ static int vsp1_dl_body_init(struct vsp1_device *vsp1,
size_t extra_size)
{
size_t size = num_entries * sizeof(*dlb->entries) + extra_size;
+ struct device *fcp = rcar_fcp_get_device(vsp1->fcp);
dlb->vsp1 = vsp1;
dlb->size = size;
- dlb->entries = dma_alloc_wc(vsp1->dev, dlb->size, &dlb->dma,
- GFP_KERNEL);
+ dlb->entries = dma_alloc_wc(fcp ? fcp : vsp1->dev, dlb->size +
+ (VSP1_DL_EXT_OFFSET * 2), &dlb->dma, GFP_KERNEL);
if (!dlb->entries)
return -ENOMEM;
@@ -150,7 +185,10 @@ static int vsp1_dl_body_init(struct vsp1_device *vsp1,
*/
static void vsp1_dl_body_cleanup(struct vsp1_dl_body *dlb)
{
- dma_free_wc(dlb->vsp1->dev, dlb->size, dlb->entries, dlb->dma);
+ struct device *fcp = rcar_fcp_get_device(dlb->vsp1->fcp);
+
+ dma_free_wc(fcp ? fcp : dlb->vsp1->dev, dlb->size +
+ (VSP1_DL_EXT_OFFSET * 2), dlb->entries, dlb->dma);
}
/**
@@ -227,6 +265,102 @@ void vsp1_dl_fragment_write(struct vsp1_dl_body *dlb, u32 reg, u32 data)
* Display List Transaction Management
*/
+void vsp1_dl_set_addr_auto_fld(struct vsp1_dl_list *dl, struct vsp1_rwpf *rpf)
+{
+ const struct vsp1_format_info *fmtinfo = rpf->fmtinfo;
+ const struct v4l2_rect *crop;
+ u32 y_top_index, y_bot_index;
+ u32 u_top_index, u_bot_index;
+ u32 v_top_index, v_bot_index;
+ dma_addr_t y_top_addr, y_bot_addr;
+ dma_addr_t u_top_addr, u_bot_addr;
+ dma_addr_t v_top_addr, v_bot_addr;
+ u32 width, stride;
+
+ crop = vsp1_rwpf_get_crop(rpf, rpf->entity.config);
+ width = ALIGN(crop->width, 16);
+ stride = width * fmtinfo->bpp[0] / 8;
+
+ y_top_index = rpf->entity.index * 8;
+ y_bot_index = rpf->entity.index * 8 + 1;
+ u_top_index = rpf->entity.index * 8 + 2;
+ u_bot_index = rpf->entity.index * 8 + 3;
+ v_top_index = rpf->entity.index * 8 + 4;
+ v_bot_index = rpf->entity.index * 8 + 5;
+
+ switch (rpf->fmtinfo->fourcc) {
+ case V4L2_PIX_FMT_YUV420M:
+ y_top_addr = rpf->mem.addr[0] + rpf->offsets[0];
+ y_bot_addr = rpf->mem.addr[0] + rpf->offsets[0] + stride;
+ u_top_addr = rpf->mem.addr[1] + rpf->offsets[1];
+ u_bot_addr = rpf->mem.addr[1] + rpf->offsets[1] + stride / 2;
+ v_top_addr = rpf->mem.addr[2] + rpf->offsets[1];
+ v_bot_addr = rpf->mem.addr[2] + rpf->offsets[1] + stride / 2;
+ break;
+
+ case V4L2_PIX_FMT_YUV422M:
+ y_top_addr = rpf->mem.addr[0] + rpf->offsets[0];
+ y_bot_addr = rpf->mem.addr[0] + rpf->offsets[0] + stride * 2;
+ u_top_addr = rpf->mem.addr[1] + rpf->offsets[1];
+ u_bot_addr = rpf->mem.addr[1] + rpf->offsets[1] + stride;
+ v_top_addr = rpf->mem.addr[2] + rpf->offsets[1];
+ v_bot_addr = rpf->mem.addr[2] + rpf->offsets[1] + stride;
+ break;
+
+ case V4L2_PIX_FMT_YUV444M:
+ y_top_addr = rpf->mem.addr[0] + rpf->offsets[0];
+ y_bot_addr = rpf->mem.addr[0] + rpf->offsets[0] + stride * 3;
+ u_top_addr = rpf->mem.addr[1] + rpf->offsets[1];
+ u_bot_addr = rpf->mem.addr[1] + rpf->offsets[1] + stride * 3;
+ v_top_addr = rpf->mem.addr[2] + rpf->offsets[1];
+ v_bot_addr = rpf->mem.addr[2] + rpf->offsets[1] + stride * 3;
+ break;
+
+ case V4L2_PIX_FMT_YVU420M:
+ y_top_addr = rpf->mem.addr[0] + rpf->offsets[0];
+ y_bot_addr = rpf->mem.addr[0] + rpf->offsets[0] + stride;
+ u_top_addr = rpf->mem.addr[2] + rpf->offsets[1];
+ u_bot_addr = rpf->mem.addr[2] + rpf->offsets[1] + stride / 2;
+ v_top_addr = rpf->mem.addr[1] + rpf->offsets[1];
+ v_bot_addr = rpf->mem.addr[1] + rpf->offsets[1] + stride / 2;
+ break;
+
+ case V4L2_PIX_FMT_YVU422M:
+ y_top_addr = rpf->mem.addr[0] + rpf->offsets[0];
+ y_bot_addr = rpf->mem.addr[0] + rpf->offsets[0] + stride * 2;
+ u_top_addr = rpf->mem.addr[2] + rpf->offsets[1];
+ u_bot_addr = rpf->mem.addr[2] + rpf->offsets[1] + stride;
+ v_top_addr = rpf->mem.addr[1] + rpf->offsets[1];
+ v_bot_addr = rpf->mem.addr[1] + rpf->offsets[1] + stride;
+ break;
+
+ case V4L2_PIX_FMT_YVU444M:
+ y_top_addr = rpf->mem.addr[0] + rpf->offsets[0];
+ y_bot_addr = rpf->mem.addr[0] + rpf->offsets[0] + stride * 3;
+ u_top_addr = rpf->mem.addr[2] + rpf->offsets[1];
+ u_bot_addr = rpf->mem.addr[2] + rpf->offsets[1] + stride * 3;
+ v_top_addr = rpf->mem.addr[1] + rpf->offsets[1];
+ v_bot_addr = rpf->mem.addr[1] + rpf->offsets[1] + stride * 3;
+ break;
+
+ default:
+ y_top_addr = rpf->mem.addr[0] + rpf->offsets[0];
+ y_bot_addr = rpf->mem.addr[0] + rpf->offsets[0] + stride;
+ u_top_addr = rpf->mem.addr[1] + rpf->offsets[1];
+ u_bot_addr = rpf->mem.addr[1] + rpf->offsets[1] + stride;
+ v_top_addr = rpf->mem.addr[2] + rpf->offsets[1];
+ v_bot_addr = rpf->mem.addr[2] + rpf->offsets[1] + stride;
+ break;
+ }
+
+ dl->src_dst_addr[y_top_index].addr = y_top_addr;
+ dl->src_dst_addr[y_bot_index].addr = y_bot_addr;
+ dl->src_dst_addr[u_top_index].addr = u_top_addr;
+ dl->src_dst_addr[u_bot_index].addr = u_bot_addr;
+ dl->src_dst_addr[v_top_index].addr = v_top_addr;
+ dl->src_dst_addr[v_bot_index].addr = v_bot_addr;
+}
+
static struct vsp1_dl_list *vsp1_dl_list_alloc(struct vsp1_dl_manager *dlm)
{
struct vsp1_dl_list *dl;
@@ -263,6 +397,16 @@ static struct vsp1_dl_list *vsp1_dl_list_alloc(struct vsp1_dl_manager *dlm)
dl->header = ((void *)dl->body0.entries) + header_offset;
dl->dma = dl->body0.dma + header_offset;
+ dl->ext_body = ((void *)dl->body0.entries) +
+ header_offset + VSP1_DL_EXT_OFFSET;
+ dl->ext_dma = dl->body0.dma + header_offset +
+ VSP1_DL_EXT_OFFSET;
+
+ dl->src_dst_addr = ((void *)dl->body0.entries) +
+ header_offset + (VSP1_DL_EXT_OFFSET * 2);
+ dl->ext_addr_dma = dl->body0.dma + header_offset +
+ (VSP1_DL_EXT_OFFSET * 2);
+
memset(dl->header, 0, sizeof(*dl->header));
dl->header->lists[0].addr = dl->body0.dma;
}
@@ -472,6 +616,30 @@ static void vsp1_dl_list_fill_header(struct vsp1_dl_list *dl, bool is_last)
dl->header->flags = VSP1_DLH_AUTO_START;
} else {
dl->header->flags = VSP1_DLH_INT_ENABLE;
+ if (dl->dlm->vsp1->info->header_mode) {
+ dl->header->next_header = dl->dma;
+ dl->header->flags |= VSP1_DLH_AUTO_START;
+ }
+
+ if (dl->dlm->vsp1->auto_fld_mode) {
+ /* Set extended display list header */
+ /* pre_ext_dl_exec = 1, pre_ext_dl_num_cmd = 1 */
+ dl->header->pre_post_num = (1 << 25) | (0x01);
+ dl->header->pre_ext_dl_plist = dl->ext_dma;
+ dl->header->post_ext_dl_num_cmd = 0;
+ dl->header->post_ext_dl_p_list = 0;
+
+ /* Set extended display list (Auto-FLD) */
+ /* Set opecode */
+ dl->ext_body->ext_dl_cmd[0] = 0x00000003;
+ /* RPF[0]-[4] address is updated */
+ dl->ext_body->ext_dl_cmd[1] = 0x001f0001;
+
+ /* Set pointer of source/destination address */
+ dl->ext_body->ext_dl_data[0] = dl->ext_addr_dma;
+ /* Should be set to 0 */
+ dl->ext_body->ext_dl_data[1] = 0;
+ }
}
}
@@ -507,7 +675,13 @@ void vsp1_dl_list_commit(struct vsp1_dl_list *dl)
*/
vsp1_write(vsp1, VI6_DL_HDR_ADDR(dlm->index), dl->dma);
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0))
+ vsp1->dl_addr = dl->dma;
+
dlm->active = dl;
+ __vsp1_dl_list_put(dl);
+
goto done;
}
@@ -531,6 +705,13 @@ void vsp1_dl_list_commit(struct vsp1_dl_list *dl)
vsp1_write(vsp1, VI6_DL_BODY_SIZE, VI6_DL_BODY_SIZE_UPD |
(dl->body0.num_entries * sizeof(*dl->header->lists)));
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) {
+ vsp1->dl_addr = dl->body0.dma;
+ vsp1->dl_body = VI6_DL_BODY_SIZE_UPD |
+ (dl->body0.num_entries * sizeof(*dl->header->lists));
+ }
+
__vsp1_dl_list_put(dlm->queued);
dlm->queued = dl;
@@ -600,6 +781,13 @@ void vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm)
(dl->body0.num_entries *
sizeof(*dl->header->lists)));
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) {
+ vsp1->dl_addr = dl->body0.dma;
+ vsp1->dl_body = VI6_DL_BODY_SIZE_UPD |
+ (dl->body0.num_entries *
+ sizeof(*dl->header->lists));
+ }
dlm->queued = dl;
dlm->pending = NULL;
}
@@ -609,20 +797,26 @@ void vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm)
}
/* Hardware Setup */
-void vsp1_dlm_setup(struct vsp1_device *vsp1)
+void vsp1_dlm_setup(struct vsp1_device *vsp1, unsigned int lif_index)
{
u32 ctrl = (256 << VI6_DL_CTRL_AR_WAIT_SHIFT)
| VI6_DL_CTRL_DC2 | VI6_DL_CTRL_DC1 | VI6_DL_CTRL_DC0
| VI6_DL_CTRL_DLE;
+ if ((vsp1->info->header_mode) && (vsp1->auto_fld_mode)) {
+ vsp1_write(vsp1, VI6_DL_EXT_CTRL(lif_index),
+ (0x02 << VI6_DL_EXT_CTRL_POLINT_SHIFT)
+ | VI6_DL_EXT_CTRL_DLPRI | VI6_DL_EXT_CTRL_EXT);
+ }
+
/* The DRM pipeline operates with display lists in Continuous Frame
* Mode, all other pipelines use manual start.
*/
- if (vsp1->drm)
+ if ((vsp1->drm) && (!vsp1->info->header_mode))
ctrl |= VI6_DL_CTRL_CFM0 | VI6_DL_CTRL_NH0;
vsp1_write(vsp1, VI6_DL_CTRL, ctrl);
- vsp1_write(vsp1, VI6_DL_SWAP, VI6_DL_SWAP_LWS);
+ vsp1_write(vsp1, VI6_DL_SWAP(lif_index), VI6_DL_SWAP_LWS);
}
void vsp1_dlm_reset(struct vsp1_dl_manager *dlm)
@@ -682,13 +876,14 @@ struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1,
{
struct vsp1_dl_manager *dlm;
unsigned int i;
+ int ret;
dlm = devm_kzalloc(vsp1->dev, sizeof(*dlm), GFP_KERNEL);
if (!dlm)
return NULL;
dlm->index = index;
- dlm->mode = index == 0 && !vsp1->info->uapi
+ dlm->mode = index == 0 && !vsp1->info->uapi && !vsp1->info->header_mode
? VSP1_DL_MODE_HEADERLESS : VSP1_DL_MODE_HEADER;
dlm->vsp1 = vsp1;
@@ -707,18 +902,28 @@ struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1,
list_add_tail(&dl->list, &dlm->free);
}
+ ret = RCAR_PRR_INIT();
+ if (ret) {
+ dev_dbg(dlm->vsp1->dev, "product register init fail.\n");
+ return NULL;
+ }
+
return dlm;
}
void vsp1_dlm_destroy(struct vsp1_dl_manager *dlm)
{
struct vsp1_dl_list *dl, *next;
+ struct vsp1_device *vsp1 = dlm->vsp1;
if (!dlm)
return;
cancel_work_sync(&dlm->gc_work);
+ if (vsp1->info->header_mode)
+ return;
+
list_for_each_entry_safe(dl, next, &dlm->free, list) {
list_del(&dl->list);
vsp1_dl_list_free(dl);
diff --git a/drivers/media/platform/vsp1/vsp1_dl.h b/drivers/media/platform/vsp1/vsp1_dl.h
index 7131aa3..39c084a 100644
--- a/drivers/media/platform/vsp1/vsp1_dl.h
+++ b/drivers/media/platform/vsp1/vsp1_dl.h
@@ -1,7 +1,7 @@
/*
* vsp1_dl.h -- R-Car VSP1 Display List
*
- * Copyright (C) 2015 Renesas Corporation
+ * Copyright (C) 2015-2016 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -20,7 +20,7 @@ struct vsp1_dl_fragment;
struct vsp1_dl_list;
struct vsp1_dl_manager;
-void vsp1_dlm_setup(struct vsp1_device *vsp1);
+void vsp1_dlm_setup(struct vsp1_device *vsp1, unsigned int lif_index);
struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1,
unsigned int index,
@@ -31,6 +31,7 @@ void vsp1_dlm_irq_display_start(struct vsp1_dl_manager *dlm);
void vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm);
struct vsp1_dl_list *vsp1_dl_list_get(struct vsp1_dl_manager *dlm);
+void vsp1_dl_set_addr_auto_fld(struct vsp1_dl_list *dl, struct vsp1_rwpf *rpf);
void vsp1_dl_list_put(struct vsp1_dl_list *dl);
void vsp1_dl_list_write(struct vsp1_dl_list *dl, u32 reg, u32 data);
void vsp1_dl_list_commit(struct vsp1_dl_list *dl);
diff --git a/drivers/media/platform/vsp1/vsp1_drm.c b/drivers/media/platform/vsp1/vsp1_drm.c
index 0daf5f2..1b3e949 100644
--- a/drivers/media/platform/vsp1/vsp1_drm.c
+++ b/drivers/media/platform/vsp1/vsp1_drm.c
@@ -1,7 +1,7 @@
/*
* vsp1_drm.c -- R-Car VSP1 DRM API
*
- * Copyright (C) 2015 Renesas Electronics Corporation
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -14,6 +14,7 @@
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include <media/media-entity.h>
#include <media/rcar-fcp.h>
@@ -22,6 +23,7 @@
#include "vsp1.h"
#include "vsp1_bru.h"
+#include "vsp1_brs.h"
#include "vsp1_dl.h"
#include "vsp1_drm.h"
#include "vsp1_lif.h"
@@ -33,9 +35,9 @@
* Interrupt Handling
*/
-void vsp1_drm_display_start(struct vsp1_device *vsp1)
+void vsp1_drm_display_start(struct vsp1_device *vsp1, unsigned int lif_index)
{
- vsp1_dlm_irq_display_start(vsp1->drm->pipe.output->dlm);
+ vsp1_dlm_irq_display_start(vsp1->drm->pipe[lif_index].output->dlm);
}
/* -----------------------------------------------------------------------------
@@ -53,6 +55,20 @@ int vsp1_du_init(struct device *dev)
}
EXPORT_SYMBOL_GPL(vsp1_du_init);
+int vsp1_du_if_set_mute(struct device *dev, bool on, unsigned int lif_index)
+{
+ struct vsp1_device *vsp1 = dev_get_drvdata(dev);
+ struct vsp1_pipeline *pipe = &vsp1->drm->pipe[lif_index];
+
+ if (on)
+ pipe->vmute_flag = true;
+ else
+ pipe->vmute_flag = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vsp1_du_if_set_mute);
+
/**
* vsp1_du_setup_lif - Setup the output part of the VSP pipeline
* @dev: the VSP device
@@ -72,17 +88,38 @@ EXPORT_SYMBOL_GPL(vsp1_du_init);
* Return 0 on success or a negative error code on failure.
*/
int vsp1_du_setup_lif(struct device *dev, unsigned int width,
- unsigned int height)
+ unsigned int height, unsigned int lif_index,
+ bool suspend)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
- struct vsp1_pipeline *pipe = &vsp1->drm->pipe;
+ struct vsp1_pipeline *pipe = &vsp1->drm->pipe[lif_index];
struct vsp1_bru *bru = vsp1->bru;
+ struct vsp1_brs *brs = vsp1->brs;
+ unsigned int init_bru_num, end_bru_num;
+ unsigned int init_brs_num, end_brs_num;
struct v4l2_subdev_format format;
unsigned int i;
int ret;
- dev_dbg(vsp1->dev, "%s: configuring LIF with format %ux%u\n",
- __func__, width, height);
+ dev_dbg(vsp1->dev, "%s: configuring LIF%d with format %ux%u\n",
+ __func__, lif_index, width, height);
+
+ if (vsp1_gen3_vspdl_check(vsp1)) {
+ if (!vsp1->brs || !vsp1->lif[1])
+ return -ENXIO;
+
+ init_bru_num = 0;
+ init_brs_num = vsp1->info->rpf_count -
+ vsp1->num_brs_inputs;
+ end_bru_num = vsp1->info->rpf_count -
+ vsp1->num_brs_inputs;
+ end_brs_num = vsp1->info->rpf_count;
+ } else {
+ init_bru_num = 0;
+ init_brs_num = 0;
+ end_bru_num = bru->entity.source_pad;
+ end_brs_num = 0;
+ }
if (width == 0 || height == 0) {
/* Zero width or height means the CRTC is being disabled, stop
@@ -94,10 +131,20 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int width,
media_entity_pipeline_stop(&pipe->output->entity.subdev.entity);
- for (i = 0; i < bru->entity.source_pad; ++i) {
- vsp1->drm->inputs[i].enabled = false;
- bru->inputs[i].rpf = NULL;
- pipe->inputs[i] = NULL;
+ if (!suspend) {
+ if (lif_index == 1) {
+ for (i = init_brs_num; i < end_brs_num; ++i) {
+ vsp1->drm->inputs[i].enabled = false;
+ brs->inputs[i].rpf = NULL;
+ pipe->inputs[i] = NULL;
+ }
+ } else {
+ for (i = init_bru_num; i < end_bru_num; ++i) {
+ vsp1->drm->inputs[i].enabled = false;
+ bru->inputs[i].rpf = NULL;
+ pipe->inputs[i] = NULL;
+ }
+ }
}
pipe->num_inputs = 0;
@@ -116,16 +163,67 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int width,
memset(&format, 0, sizeof(format));
format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
- for (i = 0; i < bru->entity.source_pad; ++i) {
- format.pad = i;
+ if (lif_index == 1) {
+ for (i = init_brs_num; i < end_brs_num; ++i) {
+ format.pad = i;
- format.format.width = width;
- format.format.height = height;
- format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32;
- format.format.field = V4L2_FIELD_NONE;
+ format.format.width = width;
+ format.format.height = height;
+ format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32;
+ format.format.field = V4L2_FIELD_NONE;
- ret = v4l2_subdev_call(&bru->entity.subdev, pad,
- set_fmt, NULL, &format);
+ ret = v4l2_subdev_call(&brs->entity.subdev, pad,
+ set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(vsp1->dev,
+ "%s: set format %ux%u (%x) on BRS pad %u\n",
+ __func__, format.format.width,
+ format.format.height,
+ format.format.code, i);
+ }
+ format.pad = brs->entity.source_pad;
+ } else {
+ for (i = init_bru_num; i < end_bru_num; ++i) {
+ format.pad = i;
+
+ format.format.width = width;
+ format.format.height = height;
+ format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32;
+ format.format.field = V4L2_FIELD_NONE;
+
+ ret = v4l2_subdev_call(&bru->entity.subdev, pad,
+ set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(vsp1->dev,
+ "%s: set format %ux%u (%x) on BRU pad %u\n",
+ __func__, format.format.width,
+ format.format.height,
+ format.format.code, i);
+ }
+ format.pad = bru->entity.source_pad;
+ }
+
+ format.format.width = width;
+ format.format.height = height;
+ format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32;
+ format.format.field = V4L2_FIELD_NONE;
+
+ if (lif_index == 1) {
+ ret = v4l2_subdev_call(&brs->entity.subdev, pad, set_fmt, NULL,
+ &format);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on BRS pad %u\n",
+ __func__, format.format.width, format.format.height,
+ format.format.code, i);
+ } else {
+ ret = v4l2_subdev_call(&bru->entity.subdev, pad, set_fmt, NULL,
+ &format);
if (ret < 0)
return ret;
@@ -134,50 +232,35 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int width,
format.format.code, i);
}
- format.pad = bru->entity.source_pad;
- format.format.width = width;
- format.format.height = height;
- format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32;
- format.format.field = V4L2_FIELD_NONE;
-
- ret = v4l2_subdev_call(&bru->entity.subdev, pad, set_fmt, NULL,
- &format);
- if (ret < 0)
- return ret;
-
- dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on BRU pad %u\n",
- __func__, format.format.width, format.format.height,
- format.format.code, i);
-
format.pad = RWPF_PAD_SINK;
- ret = v4l2_subdev_call(&vsp1->wpf[0]->entity.subdev, pad, set_fmt, NULL,
- &format);
+ ret = v4l2_subdev_call(&vsp1->wpf[lif_index]->entity.subdev,
+ pad, set_fmt, NULL, &format);
if (ret < 0)
return ret;
- dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on WPF0 sink\n",
+ dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on WPF%d sink\n",
__func__, format.format.width, format.format.height,
- format.format.code);
+ format.format.code, lif_index);
format.pad = RWPF_PAD_SOURCE;
- ret = v4l2_subdev_call(&vsp1->wpf[0]->entity.subdev, pad, get_fmt, NULL,
- &format);
+ ret = v4l2_subdev_call(&vsp1->wpf[lif_index]->entity.subdev,
+ pad, get_fmt, NULL, &format);
if (ret < 0)
return ret;
- dev_dbg(vsp1->dev, "%s: got format %ux%u (%x) on WPF0 source\n",
+ dev_dbg(vsp1->dev, "%s: got format %ux%u (%x) on WPF%d source\n",
__func__, format.format.width, format.format.height,
- format.format.code);
+ format.format.code, lif_index);
format.pad = LIF_PAD_SINK;
- ret = v4l2_subdev_call(&vsp1->lif->entity.subdev, pad, set_fmt, NULL,
- &format);
+ ret = v4l2_subdev_call(&vsp1->lif[lif_index]->entity.subdev,
+ pad, set_fmt, NULL, &format);
if (ret < 0)
return ret;
- dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on LIF sink\n",
+ dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on LIF%d sink\n",
__func__, format.format.width, format.format.height,
- format.format.code);
+ format.format.code, lif_index);
/* Verify that the format at the output of the pipeline matches the
* requested frame size and media bus code.
@@ -216,10 +299,10 @@ EXPORT_SYMBOL_GPL(vsp1_du_setup_lif);
* vsp1_du_atomic_begin - Prepare for an atomic update
* @dev: the VSP device
*/
-void vsp1_du_atomic_begin(struct device *dev)
+void vsp1_du_atomic_begin(struct device *dev, unsigned int lif_index)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
- struct vsp1_pipeline *pipe = &vsp1->drm->pipe;
+ struct vsp1_pipeline *pipe = &vsp1->drm->pipe[lif_index];
vsp1->drm->num_inputs = pipe->num_inputs;
@@ -299,8 +382,22 @@ int vsp1_du_atomic_update(struct device *dev, unsigned int rpf_index,
rpf->fmtinfo = fmtinfo;
rpf->format.num_planes = fmtinfo->planes;
rpf->format.plane_fmt[0].bytesperline = cfg->pitch;
- rpf->format.plane_fmt[1].bytesperline = cfg->pitch;
+ if ((rpf->fmtinfo->fourcc == V4L2_PIX_FMT_YUV420M) ||
+ (rpf->fmtinfo->fourcc == V4L2_PIX_FMT_YVU420M) ||
+ (rpf->fmtinfo->fourcc == V4L2_PIX_FMT_YUV422M) ||
+ (rpf->fmtinfo->fourcc == V4L2_PIX_FMT_YVU422M))
+ rpf->format.plane_fmt[1].bytesperline = cfg->pitch / 2;
+ else
+ rpf->format.plane_fmt[1].bytesperline = cfg->pitch;
rpf->alpha = cfg->alpha;
+ rpf->interlaced = cfg->interlaced;
+
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0) && rpf->interlaced) {
+ dev_err(vsp1->dev,
+ "Interlaced mode is not supported.\n");
+ return -EINVAL;
+ }
rpf->mem.addr[0] = cfg->mem[0];
rpf->mem.addr[1] = cfg->mem[1];
@@ -321,7 +418,9 @@ static int vsp1_du_setup_rpf_pipe(struct vsp1_device *vsp1,
struct v4l2_subdev_selection sel;
struct v4l2_subdev_format format;
const struct v4l2_rect *crop;
+ struct v4l2_subdev *subdev;
int ret;
+ bool brs_use = false;
/* Configure the format on the RPF sink pad and propagate it up to the
* BRU sink pad.
@@ -387,28 +486,40 @@ static int vsp1_du_setup_rpf_pipe(struct vsp1_device *vsp1,
/* BRU sink, propagate the format from the RPF source. */
format.pad = bru_input;
- ret = v4l2_subdev_call(&vsp1->bru->entity.subdev, pad, set_fmt, NULL,
+ if (bru_input >=
+ (vsp1->info->rpf_count - vsp1->num_brs_inputs))
+ brs_use = true;
+
+ if (vsp1_gen3_vspdl_check(vsp1)) {
+ if (brs_use)
+ subdev = &vsp1->brs->entity.subdev;
+ else
+ subdev = &vsp1->bru->entity.subdev;
+ } else
+ subdev = &vsp1->bru->entity.subdev;
+
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL,
&format);
if (ret < 0)
return ret;
- dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on BRU pad %u\n",
+ dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on %s pad %u\n",
__func__, format.format.width, format.format.height,
- format.format.code, format.pad);
+ format.format.code, (brs_use ? "BRS" : "BRU"), format.pad);
sel.pad = bru_input;
sel.target = V4L2_SEL_TGT_COMPOSE;
sel.r = vsp1->drm->inputs[rpf->entity.index].compose;
- ret = v4l2_subdev_call(&vsp1->bru->entity.subdev, pad, set_selection,
+ ret = v4l2_subdev_call(subdev, pad, set_selection,
NULL, &sel);
if (ret < 0)
return ret;
dev_dbg(vsp1->dev,
- "%s: set selection (%u,%u)/%ux%u on BRU pad %u\n",
+ "%s: set selection (%u,%u)/%ux%u on %s pad %u\n",
__func__, sel.r.left, sel.r.top, sel.r.width, sel.r.height,
- sel.pad);
+ (brs_use ? "BRS" : "BRU"), sel.pad);
return 0;
}
@@ -422,20 +533,36 @@ static unsigned int rpf_zpos(struct vsp1_device *vsp1, struct vsp1_rwpf *rpf)
* vsp1_du_atomic_flush - Commit an atomic update
* @dev: the VSP device
*/
-void vsp1_du_atomic_flush(struct device *dev)
+void vsp1_du_atomic_flush(struct device *dev, unsigned int lif_index)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
- struct vsp1_pipeline *pipe = &vsp1->drm->pipe;
+ struct vsp1_pipeline *pipe = &vsp1->drm->pipe[lif_index];
struct vsp1_rwpf *inputs[VSP1_MAX_RPF] = { NULL, };
struct vsp1_entity *entity;
unsigned long flags;
unsigned int i;
+ unsigned int init_rpf, end_rpf;
int ret;
/* Count the number of enabled inputs and sort them by Z-order. */
pipe->num_inputs = 0;
- for (i = 0; i < vsp1->info->rpf_count; ++i) {
+ if (vsp1_gen3_vspdl_check(vsp1)) {
+ if (lif_index == 1) {
+ init_rpf = vsp1->info->rpf_count -
+ vsp1->num_brs_inputs;
+ end_rpf = vsp1->info->rpf_count;
+ } else {
+ init_rpf = 0;
+ end_rpf = vsp1->info->rpf_count -
+ vsp1->num_brs_inputs;
+ }
+ } else {
+ init_rpf = 0;
+ end_rpf = vsp1->info->rpf_count;
+ }
+
+ for (i = init_rpf; i < end_rpf; ++i) {
struct vsp1_rwpf *rpf = vsp1->rpf[i];
unsigned int j;
@@ -448,29 +575,42 @@ void vsp1_du_atomic_flush(struct device *dev)
/* Insert the RPF in the sorted RPFs array. */
for (j = pipe->num_inputs++; j > 0; --j) {
- if (rpf_zpos(vsp1, inputs[j-1]) <= rpf_zpos(vsp1, rpf))
+ if (rpf_zpos(vsp1, inputs[j-1+init_rpf]) <=
+ rpf_zpos(vsp1, rpf))
break;
- inputs[j] = inputs[j-1];
+ inputs[j+init_rpf] = inputs[j-1+init_rpf];
}
- inputs[j] = rpf;
+ inputs[j+init_rpf] = rpf;
}
+ if (!vsp1_gen3_vspdl_check(vsp1))
+ end_rpf = vsp1->info->num_bru_inputs;
+
/* Setup the RPF input pipeline for every enabled input. */
- for (i = 0; i < vsp1->info->num_bru_inputs; ++i) {
+ for (i = init_rpf; i < end_rpf; ++i) {
struct vsp1_rwpf *rpf = inputs[i];
+ bool brs_use = false;
if (!rpf) {
vsp1->bru->inputs[i].rpf = NULL;
+ if ((lif_index == 1) && (vsp1->brs))
+ vsp1->brs->inputs[i].rpf = NULL;
continue;
}
vsp1->bru->inputs[i].rpf = rpf;
+ if ((lif_index == 1) && (vsp1->brs)) {
+ vsp1->brs->inputs[i].rpf = rpf;
+ rpf->brs_input = 2;
+ brs_use = true;
+ }
rpf->bru_input = i;
rpf->entity.sink_pad = i;
- dev_dbg(vsp1->dev, "%s: connecting RPF.%u to BRU:%u\n",
- __func__, rpf->entity.index, i);
+ dev_dbg(vsp1->dev, "%s: connecting RPF.%u to %s:%u\n",
+ __func__, rpf->entity.index,
+ (brs_use ? "BRS" : "BRU"), i);
ret = vsp1_du_setup_rpf_pipe(vsp1, rpf, i);
if (ret < 0)
@@ -509,13 +649,14 @@ void vsp1_du_atomic_flush(struct device *dev)
/* Start or stop the pipeline if needed. */
if (!vsp1->drm->num_inputs && pipe->num_inputs) {
- vsp1_write(vsp1, VI6_DISP_IRQ_STA, 0);
- vsp1_write(vsp1, VI6_DISP_IRQ_ENB, VI6_DISP_IRQ_ENB_DSTE);
+ vsp1_write(vsp1, VI6_DISP_IRQ_STA(lif_index), 0);
+ vsp1_write(vsp1, VI6_DISP_IRQ_ENB(lif_index),
+ VI6_DISP_IRQ_ENB_DSTE);
spin_lock_irqsave(&pipe->irqlock, flags);
vsp1_pipeline_run(pipe);
spin_unlock_irqrestore(&pipe->irqlock, flags);
} else if (vsp1->drm->num_inputs && !pipe->num_inputs) {
- vsp1_write(vsp1, VI6_DISP_IRQ_ENB, 0);
+ vsp1_write(vsp1, VI6_DISP_IRQ_ENB(lif_index), 0);
vsp1_pipeline_stop(pipe);
}
}
@@ -543,6 +684,58 @@ void vsp1_du_unmap_sg(struct device *dev, struct sg_table *sgt)
}
EXPORT_SYMBOL_GPL(vsp1_du_unmap_sg);
+int vsp1_du_setup_wb(struct device *dev, u32 pixelformat, unsigned int pitch,
+ dma_addr_t mem[2], unsigned int lif_index)
+{
+ struct vsp1_device *vsp1 = dev_get_drvdata(dev);
+ struct vsp1_pipeline *pipe = &vsp1->drm->pipe[lif_index];
+ struct vsp1_rwpf *wpf = pipe->output;
+ const struct vsp1_format_info *fmtinfo;
+ struct vsp1_rwpf *rpf = pipe->inputs[0];
+ unsigned long flags;
+ int i;
+
+ fmtinfo = vsp1_get_format_info(vsp1, pixelformat);
+ if (!fmtinfo) {
+ dev_err(vsp1->dev, "Unsupport pixel format %08x for RPF\n",
+ pixelformat);
+ return -EINVAL;
+ }
+
+ if (rpf->interlaced) {
+ dev_err(vsp1->dev, "Prohibited in interlaced mode\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&pipe->irqlock, flags);
+
+ wpf->fmtinfo = fmtinfo;
+ wpf->format.num_planes = fmtinfo->planes;
+ wpf->format.plane_fmt[0].bytesperline = pitch;
+ wpf->format.plane_fmt[1].bytesperline = pitch;
+
+ for (i = 0; i < wpf->format.num_planes; ++i)
+ wpf->buf_addr[i] = mem[i];
+
+ pipe->output->write_back = 3;
+
+ spin_unlock_irqrestore(&pipe->irqlock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vsp1_du_setup_wb);
+
+void vsp1_du_wait_wb(struct device *dev, u32 count, unsigned int lif_index)
+{
+ int ret;
+ struct vsp1_device *vsp1 = dev_get_drvdata(dev);
+ struct vsp1_pipeline *pipe = &vsp1->drm->pipe[lif_index];
+
+ ret = wait_event_interruptible(pipe->event_wait,
+ (pipe->output->write_back == count));
+}
+EXPORT_SYMBOL_GPL(vsp1_du_wait_wb);
+
/* -----------------------------------------------------------------------------
* Initialization
*/
@@ -551,15 +744,34 @@ int vsp1_drm_create_links(struct vsp1_device *vsp1)
{
const u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE;
unsigned int i;
+ unsigned int init_bru_num, end_bru_num;
+ unsigned int init_brs_num, end_brs_num;
int ret;
/* VSPD instances require a BRU to perform composition and a LIF to
* output to the DU.
*/
- if (!vsp1->bru || !vsp1->lif)
+ if (!vsp1->bru || !vsp1->lif[0])
return -ENXIO;
- for (i = 0; i < vsp1->info->rpf_count; ++i) {
+ if (vsp1_gen3_vspdl_check(vsp1)) {
+ if (!vsp1->brs || !vsp1->lif[1])
+ return -ENXIO;
+
+ init_bru_num = 0;
+ init_brs_num = vsp1->info->rpf_count -
+ vsp1->num_brs_inputs;
+ end_bru_num = vsp1->info->rpf_count -
+ vsp1->num_brs_inputs;
+ end_brs_num = vsp1->info->rpf_count;
+ } else {
+ init_bru_num = 0;
+ init_brs_num = 0;
+ end_bru_num = vsp1->info->rpf_count;
+ end_brs_num = 0;
+ }
+
+ for (i = init_bru_num; i < end_bru_num; ++i) {
struct vsp1_rwpf *rpf = vsp1->rpf[i];
ret = media_create_pad_link(&rpf->entity.subdev.entity,
@@ -568,11 +780,23 @@ int vsp1_drm_create_links(struct vsp1_device *vsp1)
i, flags);
if (ret < 0)
return ret;
-
rpf->entity.sink = &vsp1->bru->entity.subdev.entity;
rpf->entity.sink_pad = i;
}
+ for (i = init_brs_num; i < end_brs_num; ++i) {
+ struct vsp1_rwpf *rpf = vsp1->rpf[i];
+
+ ret = media_create_pad_link(&rpf->entity.subdev.entity,
+ RWPF_PAD_SOURCE,
+ &vsp1->brs->entity.subdev.entity,
+ i, flags);
+ if (ret < 0)
+ return ret;
+ rpf->entity.sink = &vsp1->brs->entity.subdev.entity;
+ rpf->entity.sink_pad = i;
+ }
+
ret = media_create_pad_link(&vsp1->bru->entity.subdev.entity,
vsp1->bru->entity.source_pad,
&vsp1->wpf[0]->entity.subdev.entity,
@@ -585,41 +809,99 @@ int vsp1_drm_create_links(struct vsp1_device *vsp1)
ret = media_create_pad_link(&vsp1->wpf[0]->entity.subdev.entity,
RWPF_PAD_SOURCE,
- &vsp1->lif->entity.subdev.entity,
+ &vsp1->lif[0]->entity.subdev.entity,
LIF_PAD_SINK, flags);
if (ret < 0)
return ret;
+ if (vsp1_gen3_vspdl_check(vsp1)) {
+ ret = media_create_pad_link(
+ &vsp1->brs->entity.subdev.entity,
+ vsp1->brs->entity.source_pad,
+ &vsp1->wpf[1]->entity.subdev.entity,
+ RWPF_PAD_SINK, flags);
+ if (ret < 0)
+ return ret;
+
+ vsp1->brs->entity.sink = &vsp1->wpf[1]->entity.subdev.entity;
+ vsp1->brs->entity.sink_pad = RWPF_PAD_SINK;
+
+ ret = media_create_pad_link(
+ &vsp1->wpf[1]->entity.subdev.entity,
+ RWPF_PAD_SOURCE,
+ &vsp1->lif[1]->entity.subdev.entity,
+ LIF_PAD_SINK, flags);
+ if (ret < 0)
+ return ret;
+ }
+
return 0;
}
int vsp1_drm_init(struct vsp1_device *vsp1)
{
struct vsp1_pipeline *pipe;
- unsigned int i;
+ unsigned int i, j, lif_cnt = 1;
+ int ret, rpf_share = -1;
vsp1->drm = devm_kzalloc(vsp1->dev, sizeof(*vsp1->drm), GFP_KERNEL);
if (!vsp1->drm)
return -ENOMEM;
- pipe = &vsp1->drm->pipe;
-
- vsp1_pipeline_init(pipe);
-
- /* The DRM pipeline is static, add entities manually. */
- for (i = 0; i < vsp1->info->rpf_count; ++i) {
- struct vsp1_rwpf *input = vsp1->rpf[i];
-
- list_add_tail(&input->entity.list_pipe, &pipe->entities);
+ if (vsp1_gen3_vspdl_check(vsp1)) {
+ lif_cnt = 2;
+ rpf_share = vsp1->info->rpf_count - vsp1->num_brs_inputs;
}
- list_add_tail(&vsp1->bru->entity.list_pipe, &pipe->entities);
- list_add_tail(&vsp1->wpf[0]->entity.list_pipe, &pipe->entities);
- list_add_tail(&vsp1->lif->entity.list_pipe, &pipe->entities);
+ for (i = 0; i < lif_cnt; i++) {
- pipe->bru = &vsp1->bru->entity;
- pipe->lif = &vsp1->lif->entity;
- pipe->output = vsp1->wpf[0];
+ pipe = &vsp1->drm->pipe[i];
+
+ vsp1_pipeline_init(pipe);
+
+ if (i == 1) {
+ list_add_tail(&vsp1->brs->entity.list_pipe,
+ &pipe->entities);
+ pipe->brs = &vsp1->brs->entity;
+ } else {
+ list_add_tail(&vsp1->bru->entity.list_pipe,
+ &pipe->entities);
+ pipe->bru = &vsp1->bru->entity;
+ }
+
+ /* The DRM pipeline is static, add entities manually. */
+ for (j = 0; j < vsp1->info->rpf_count; ++j) {
+ struct vsp1_rwpf *input = vsp1->rpf[j];
+
+ if (rpf_share < 0)
+ list_add_tail(&input->entity.list_pipe,
+ &pipe->entities);
+ else {
+ if ((i == 0) && (j < rpf_share))
+ list_add_tail(&input->entity.list_pipe,
+ &pipe->entities);
+ if ((i == 1) && (j >= rpf_share))
+ list_add_tail(&input->entity.list_pipe,
+ &pipe->entities);
+ }
+ }
+
+ list_add_tail(&vsp1->wpf[i]->entity.list_pipe,
+ &pipe->entities);
+ list_add_tail(&vsp1->lif[i]->entity.list_pipe,
+ &pipe->entities);
+
+ pipe->lif = &vsp1->lif[i]->entity;
+ pipe->output = vsp1->wpf[i];
+ pipe->output->write_back = 0;
+ init_waitqueue_head(&pipe->event_wait);
+ }
+
+ ret = RCAR_PRR_INIT();
+ if (ret) {
+ dev_dbg(vsp1->dev, "product register init fail.\n");
+ return ret;
+ }
return 0;
}
diff --git a/drivers/media/platform/vsp1/vsp1_drm.h b/drivers/media/platform/vsp1/vsp1_drm.h
index 9e28ab9..1bcee33 100644
--- a/drivers/media/platform/vsp1/vsp1_drm.h
+++ b/drivers/media/platform/vsp1/vsp1_drm.h
@@ -1,7 +1,7 @@
/*
* vsp1_drm.h -- R-Car VSP1 DRM/KMS Interface
*
- * Copyright (C) 2015 Renesas Electronics Corporation
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -25,7 +25,7 @@
* position for every input
*/
struct vsp1_drm {
- struct vsp1_pipeline pipe;
+ struct vsp1_pipeline pipe[VSP1_MAX_LIF];
unsigned int num_inputs;
struct {
bool enabled;
@@ -39,6 +39,6 @@ int vsp1_drm_init(struct vsp1_device *vsp1);
void vsp1_drm_cleanup(struct vsp1_device *vsp1);
int vsp1_drm_create_links(struct vsp1_device *vsp1);
-void vsp1_drm_display_start(struct vsp1_device *vsp1);
+void vsp1_drm_display_start(struct vsp1_device *vsp1, unsigned int lif_index);
#endif /* __VSP1_DRM_H__ */
diff --git a/drivers/media/platform/vsp1/vsp1_drv.c b/drivers/media/platform/vsp1/vsp1_drv.c
index 3b08497..8bec172 100644
--- a/drivers/media/platform/vsp1/vsp1_drv.c
+++ b/drivers/media/platform/vsp1/vsp1_drv.c
@@ -1,7 +1,7 @@
/*
* vsp1_drv.c -- R-Car VSP1 Driver
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -11,6 +11,10 @@
* (at your option) any later version.
*/
+#ifdef CONFIG_VIDEO_RENESAS_DEBUG
+#define DEBUG
+#endif
+
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
@@ -21,12 +25,14 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/videodev2.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include <media/rcar-fcp.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_bru.h"
+#include "vsp1_brs.h"
#include "vsp1_clu.h"
#include "vsp1_dl.h"
#include "vsp1_drm.h"
@@ -41,40 +47,190 @@
#include "vsp1_uds.h"
#include "vsp1_video.h"
+#define VSP1_UT_IRQ 0x01
+
+static unsigned int vsp1_debug; /* 1 to enable debug output */
+module_param_named(debug, vsp1_debug, int, 0600);
+static int underrun_vspd[4];
+module_param_array(underrun_vspd, int, NULL, 0600);
+
+#ifdef CONFIG_VIDEO_RENESAS_DEBUG
+#define VSP1_IRQ_DEBUG(fmt, args...) \
+ do { \
+ if (unlikely(vsp1_debug & VSP1_UT_IRQ)) \
+ vsp1_ut_debug_printk(__func__, fmt, ##args); \
+ } while (0)
+#else
+#define VSP1_IRQ_DEBUG(fmt, args...)
+#endif
+
+void vsp1_ut_debug_printk(const char *function_name, const char *format, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, format);
+ vaf.fmt = format;
+ vaf.va = &args;
+
+ pr_debug("[vsp1 :%s] %pV", function_name, &vaf);
+
+ va_end(args);
+}
+
+#define SRCR7_REG 0xe61501cc
+#define FCPVD0_REG 0xfea27000
+#define FCPVD1_REG 0xfea2f000
+#define FCPVD2_REG 0xfea37000
+#define FCPVD3_REG 0xfea3f000
+
+#define FCP_RST_REG 0x0010
+#define FCP_RST_SOFTRST 0x00000001
+#define FCP_RST_WORKAROUND 0x00000010
+
+#define FCP_STA_REG 0x0018
+#define FCP_STA_ACT 0x00000001
+
+static void __iomem *fcpv_reg[4];
+static const unsigned int fcpvd_offset[] = {
+ FCPVD0_REG, FCPVD1_REG, FCPVD2_REG, FCPVD3_REG
+};
+
+int vsp1_gen3_vspdl_check(struct vsp1_device *vsp1)
+{
+ if (((vsp1->version & VI6_IP_VERSION_MODEL_MASK) ==
+ VI6_IP_VERSION_MODEL_VSPDL_H3) && (vsp1->index == 0))
+ return true;
+
+ return false;
+}
+
+static int vsp1_gen3_vspd_check(struct vsp1_device *vsp1)
+{
+ if ((vsp1->version & VI6_IP_VERSION_MODEL_MASK) ==
+ VI6_IP_VERSION_MODEL_VSPD_GEN3)
+ return true;
+
+ if ((vsp1->version & VI6_IP_VERSION_MODEL_MASK) ==
+ VI6_IP_VERSION_MODEL_VSPDL_H3)
+ return true;
+
+ return false;
+}
+
+void vsp1_underrun_workaround(struct vsp1_device *vsp1, bool reset)
+{
+ unsigned int timeout = 0;
+
+ /* 1. Disable clock stop of VSP */
+ vsp1_write(vsp1, VI6_CLK_CTRL0, VI6_CLK_CTRL0_WORKAROUND);
+ vsp1_write(vsp1, VI6_CLK_CTRL1, VI6_CLK_CTRL1_WORKAROUND);
+ vsp1_write(vsp1, VI6_CLK_DCSWT, VI6_CLK_DCSWT_WORKAROUND1);
+ vsp1_write(vsp1, VI6_CLK_DCSM0, VI6_CLK_DCSM0_WORKAROUND);
+ vsp1_write(vsp1, VI6_CLK_DCSM1, VI6_CLK_DCSM1_WORKAROUND);
+
+ /* 2. Stop operation of VSP except bus access with module reset */
+ vsp1_write(vsp1, VI6_MRESET_ENB0, VI6_MRESET_ENB0_WORKAROUND1);
+ vsp1_write(vsp1, VI6_MRESET_ENB1, VI6_MRESET_ENB1_WORKAROUND);
+ vsp1_write(vsp1, VI6_MRESET, VI6_MRESET_WORKAROUND);
+
+ /* 3. Stop operation of FCPV with software reset */
+ iowrite32(FCP_RST_SOFTRST, fcpv_reg[vsp1->index] + FCP_RST_REG);
+
+ /* 4. Wait until FCP_STA.ACT become 0. */
+ while (1) {
+ if ((ioread32(fcpv_reg[vsp1->index] + FCP_STA_REG) &
+ FCP_STA_ACT) != FCP_STA_ACT)
+ break;
+
+ if (timeout == 100)
+ break;
+
+ timeout++;
+ udelay(1);
+ }
+
+ /* 5. Initialize the whole FCPV with module reset */
+ iowrite32(FCP_RST_WORKAROUND, fcpv_reg[vsp1->index] + FCP_RST_REG);
+
+ /* 6. Stop the whole operation of VSP with module reset */
+ /* (note that register setting is not cleared) */
+ vsp1_write(vsp1, VI6_MRESET_ENB0, VI6_MRESET_ENB0_WORKAROUND2);
+ vsp1_write(vsp1, VI6_MRESET_ENB1, VI6_MRESET_ENB1_WORKAROUND);
+ vsp1_write(vsp1, VI6_MRESET, VI6_MRESET_WORKAROUND);
+
+ /* 7. Enable clock stop of VSP */
+ vsp1_write(vsp1, VI6_CLK_CTRL0, 0);
+ vsp1_write(vsp1, VI6_CLK_CTRL1, 0);
+ vsp1_write(vsp1, VI6_CLK_DCSWT, VI6_CLK_DCSWT_WORKAROUND2);
+ vsp1_write(vsp1, VI6_CLK_DCSM0, 0);
+ vsp1_write(vsp1, VI6_CLK_DCSM1, 0);
+
+ /* 8. Restart VSPD */
+ if (!reset) {
+ vsp1_write(vsp1, VI6_DL_HDR_ADDR(0), vsp1->dl_addr);
+ /* Necessary when headerless display list */
+ if (!vsp1->info->header_mode)
+ vsp1_write(vsp1, VI6_DL_BODY_SIZE, vsp1->dl_body);
+ vsp1_write(vsp1, VI6_CMD(0), VI6_CMD_STRCMD);
+ }
+}
+
/* -----------------------------------------------------------------------------
* Interrupt Handling
*/
-
static irqreturn_t vsp1_irq_handler(int irq, void *data)
{
- u32 mask = VI6_WFP_IRQ_STA_DFE | VI6_WFP_IRQ_STA_FRE;
+ u32 mask = VI6_WFP_IRQ_STA_DFE | VI6_WFP_IRQ_STA_FRE
+ | VI6_WFP_IRQ_STA_UND;
struct vsp1_device *vsp1 = data;
irqreturn_t ret = IRQ_NONE;
unsigned int i;
u32 status;
+ unsigned int vsp_und_cnt = 0;
+ bool underrun = false;
for (i = 0; i < vsp1->info->wpf_count; ++i) {
struct vsp1_rwpf *wpf = vsp1->wpf[i];
+ struct vsp1_pipeline *pipe;
if (wpf == NULL)
continue;
+ pipe = to_vsp1_pipeline(&wpf->entity.subdev.entity);
status = vsp1_read(vsp1, VI6_WPF_IRQ_STA(i));
vsp1_write(vsp1, VI6_WPF_IRQ_STA(i), ~status & mask);
+ if (vsp1_debug && (status & VI6_WFP_IRQ_STA_UND) &&
+ vsp1_gen3_vspd_check(vsp1)) {
+ vsp_und_cnt = ++underrun_vspd[vsp1->index];
+ VSP1_IRQ_DEBUG(
+ "Underrun occurred num[%d] at VSPD (%s)\n",
+ vsp_und_cnt, dev_name(vsp1->dev));
+ }
+
if (status & VI6_WFP_IRQ_STA_DFE) {
- vsp1_pipeline_frame_end(wpf->pipe);
+ vsp1_pipeline_frame_end(pipe);
+ ret = IRQ_HANDLED;
+ }
+ if (status & VI6_WFP_IRQ_STA_UND)
+ underrun = true;
+ }
+
+ for (i = 0; i < vsp1->info->wpf_count; ++i) {
+ status = vsp1_read(vsp1, VI6_DISP_IRQ_STA(i));
+ vsp1_write(vsp1, VI6_DISP_IRQ_STA(i),
+ ~status & VI6_DISP_IRQ_STA_DST);
+
+ if (status & VI6_DISP_IRQ_STA_DST) {
+ vsp1_drm_display_start(vsp1, i);
ret = IRQ_HANDLED;
}
}
- status = vsp1_read(vsp1, VI6_DISP_IRQ_STA);
- vsp1_write(vsp1, VI6_DISP_IRQ_STA, ~status & VI6_DISP_IRQ_STA_DST);
-
- if (status & VI6_DISP_IRQ_STA_DST) {
- vsp1_drm_display_start(vsp1);
- ret = IRQ_HANDLED;
- }
+ if ((RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) &&
+ underrun && vsp1_gen3_vspd_check(vsp1))
+ vsp1_underrun_workaround(vsp1, false);
return ret;
}
@@ -172,10 +328,19 @@ static int vsp1_uapi_create_links(struct vsp1_device *vsp1)
return ret;
}
- if (vsp1->lif) {
+ if (vsp1->lif[0]) {
ret = media_create_pad_link(&vsp1->wpf[0]->entity.subdev.entity,
RWPF_PAD_SOURCE,
- &vsp1->lif->entity.subdev.entity,
+ &vsp1->lif[0]->entity.subdev.entity,
+ LIF_PAD_SINK, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (vsp1->lif[1]) {
+ ret = media_create_pad_link(&vsp1->wpf[1]->entity.subdev.entity,
+ RWPF_PAD_SOURCE,
+ &vsp1->lif[1]->entity.subdev.entity,
LIF_PAD_SINK, 0);
if (ret < 0)
return ret;
@@ -277,6 +442,16 @@ static int vsp1_create_entities(struct vsp1_device *vsp1)
list_add_tail(&vsp1->bru->entity.list_dev, &vsp1->entities);
}
+ if (vsp1->info->features & VSP1_HAS_BRS) {
+ vsp1->brs = vsp1_brs_create(vsp1);
+ if (IS_ERR(vsp1->brs)) {
+ ret = PTR_ERR(vsp1->brs);
+ goto done;
+ }
+
+ list_add_tail(&vsp1->brs->entity.list_dev, &vsp1->entities);
+ }
+
if (vsp1->info->features & VSP1_HAS_CLU) {
vsp1->clu = vsp1_clu_create(vsp1);
if (IS_ERR(vsp1->clu)) {
@@ -330,13 +505,27 @@ static int vsp1_create_entities(struct vsp1_device *vsp1)
* enabled skip the LIF, even when present.
*/
if (vsp1->info->features & VSP1_HAS_LIF && !vsp1->info->uapi) {
- vsp1->lif = vsp1_lif_create(vsp1);
- if (IS_ERR(vsp1->lif)) {
- ret = PTR_ERR(vsp1->lif);
- goto done;
- }
+ if (vsp1_gen3_vspdl_check(vsp1)) {
+ for (i = 0; i < 2; i++) {
+ vsp1->lif[i] = vsp1_lif_create(vsp1, i);
+ if (IS_ERR(vsp1->lif[i])) {
+ ret = PTR_ERR(vsp1->lif[i]);
+ goto done;
+ }
- list_add_tail(&vsp1->lif->entity.list_dev, &vsp1->entities);
+ list_add_tail(&vsp1->lif[i]->entity.list_dev,
+ &vsp1->entities);
+ }
+ } else {
+ vsp1->lif[0] = vsp1_lif_create(vsp1, i);
+ if (IS_ERR(vsp1->lif[0])) {
+ ret = PTR_ERR(vsp1->lif[0]);
+ goto done;
+ }
+
+ list_add_tail(&vsp1->lif[0]->entity.list_dev,
+ &vsp1->entities);
+ }
}
if (vsp1->info->features & VSP1_HAS_LUT) {
@@ -466,7 +655,12 @@ int vsp1_reset_wpf(struct vsp1_device *vsp1, unsigned int index)
if (!(status & VI6_STATUS_SYS_ACT(index)))
return 0;
- vsp1_write(vsp1, VI6_SRESET, VI6_SRESET_SRTS(index));
+ if ((RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) &&
+ vsp1_gen3_vspd_check(vsp1))
+ vsp1_underrun_workaround(vsp1, true);
+ else
+ vsp1_write(vsp1, VI6_SRESET, VI6_SRESET_SRTS(index));
+
for (timeout = 10; timeout > 0; --timeout) {
status = vsp1_read(vsp1, VI6_STATUS);
if (!(status & VI6_STATUS_SYS_ACT(index)))
@@ -510,13 +704,19 @@ static int vsp1_device_init(struct vsp1_device *vsp1)
vsp1_write(vsp1, VI6_DPR_HST_ROUTE, VI6_DPR_NODE_UNUSED);
vsp1_write(vsp1, VI6_DPR_HSI_ROUTE, VI6_DPR_NODE_UNUSED);
vsp1_write(vsp1, VI6_DPR_BRU_ROUTE, VI6_DPR_NODE_UNUSED);
+ vsp1_write(vsp1, VI6_DPR_BRS_ROUTE, VI6_DPR_NODE_UNUSED);
vsp1_write(vsp1, VI6_DPR_HGO_SMPPT, (7 << VI6_DPR_SMPPT_TGW_SHIFT) |
(VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT));
vsp1_write(vsp1, VI6_DPR_HGT_SMPPT, (7 << VI6_DPR_SMPPT_TGW_SHIFT) |
(VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT));
- vsp1_dlm_setup(vsp1);
+ for (i = 0; i < vsp1->info->wpf_count; ++i) {
+ if ((i == 1) && (!vsp1_gen3_vspdl_check(vsp1)))
+ break;
+
+ vsp1_dlm_setup(vsp1, i);
+ }
return 0;
}
@@ -530,10 +730,23 @@ static int vsp1_device_init(struct vsp1_device *vsp1)
*/
int vsp1_device_get(struct vsp1_device *vsp1)
{
- int ret;
+ int ret = 0;
+
+ ret = rcar_fcp_enable(vsp1->fcp);
+ if (ret < 0)
+ return ret;
ret = pm_runtime_get_sync(vsp1->dev);
- return ret < 0 ? ret : 0;
+ if (ret < 0)
+ return ret;
+
+ if (vsp1->info) {
+ ret = vsp1_device_init(vsp1);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
}
/*
@@ -545,6 +758,7 @@ int vsp1_device_get(struct vsp1_device *vsp1)
void vsp1_device_put(struct vsp1_device *vsp1)
{
pm_runtime_put_sync(vsp1->dev);
+ rcar_fcp_disable(vsp1->fcp);
}
/* -----------------------------------------------------------------------------
@@ -556,7 +770,7 @@ static int __maybe_unused vsp1_pm_suspend(struct device *dev)
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
vsp1_pipelines_suspend(vsp1);
- pm_runtime_force_suspend(vsp1->dev);
+ vsp1_device_put(vsp1);
return 0;
}
@@ -564,39 +778,23 @@ static int __maybe_unused vsp1_pm_suspend(struct device *dev)
static int __maybe_unused vsp1_pm_resume(struct device *dev)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
+ int ret = 0;
- pm_runtime_force_resume(vsp1->dev);
+ ret = rcar_fcp_enable(vsp1->fcp);
+ if (ret < 0)
+ return ret;
+
+ ret = pm_runtime_get_sync(vsp1->dev);
+ if (ret < 0)
+ return ret;
+
vsp1_pipelines_resume(vsp1);
return 0;
}
-static int __maybe_unused vsp1_pm_runtime_suspend(struct device *dev)
-{
- struct vsp1_device *vsp1 = dev_get_drvdata(dev);
-
- rcar_fcp_disable(vsp1->fcp);
-
- return 0;
-}
-
-static int __maybe_unused vsp1_pm_runtime_resume(struct device *dev)
-{
- struct vsp1_device *vsp1 = dev_get_drvdata(dev);
- int ret;
-
- if (vsp1->info) {
- ret = vsp1_device_init(vsp1);
- if (ret < 0)
- return ret;
- }
-
- return rcar_fcp_enable(vsp1->fcp);
-}
-
static const struct dev_pm_ops vsp1_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(vsp1_pm_suspend, vsp1_pm_resume)
- SET_RUNTIME_PM_OPS(vsp1_pm_runtime_suspend, vsp1_pm_runtime_resume, NULL)
};
/* -----------------------------------------------------------------------------
@@ -709,6 +907,17 @@ static const struct vsp1_device_info vsp1_device_infos[] = {
.rpf_count = 5,
.wpf_count = 2,
.num_bru_inputs = 5,
+ .header_mode = true,
+ }, {
+ .version = VI6_IP_VERSION_MODEL_VSPDL_H3,
+ .model = "VSP2-D",
+ .gen = 3,
+ .features = VSP1_HAS_BRU | VSP1_HAS_LIF | VSP1_HAS_WPF_VFLIP
+ | VSP1_HAS_BRS,
+ .rpf_count = 5,
+ .wpf_count = 2,
+ .num_bru_inputs = 5,
+ .header_mode = true,
},
};
@@ -762,6 +971,10 @@ static int vsp1_probe(struct platform_device *pdev)
}
}
+ /* Get using number of BRS module */
+ of_property_read_u32(pdev->dev.of_node, "renesas,#brs",
+ &vsp1->num_brs_inputs);
+
/* Configure device parameters based on the version register. */
pm_runtime_enable(&pdev->dev);
@@ -789,6 +1002,27 @@ static int vsp1_probe(struct platform_device *pdev)
dev_dbg(&pdev->dev, "IP version 0x%08x\n", vsp1->version);
+ ret = RCAR_PRR_INIT();
+ if (ret) {
+ dev_dbg(vsp1->dev, "product register init fail.\n");
+ return ret;
+ }
+
+ if (vsp1->info->header_mode && !(RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)))
+ vsp1->auto_fld_mode = true;
+ else
+ vsp1->auto_fld_mode = false;
+
+ if (strcmp(dev_name(vsp1->dev), "fea20000.vsp") == 0)
+ vsp1->index = 0;
+ else if (strcmp(dev_name(vsp1->dev), "fea28000.vsp") == 0)
+ vsp1->index = 1;
+ else if (strcmp(dev_name(vsp1->dev), "fea30000.vsp") == 0)
+ vsp1->index = 2;
+ else if (strcmp(dev_name(vsp1->dev), "fea38000.vsp") == 0)
+ vsp1->index = 3;
+
/* Instanciate entities */
ret = vsp1_create_entities(vsp1);
if (ret < 0) {
@@ -796,6 +1030,10 @@ static int vsp1_probe(struct platform_device *pdev)
goto done;
}
+ if ((RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) &&
+ vsp1_gen3_vspd_check(vsp1))
+ fcpv_reg[vsp1->index] =
+ ioremap(fcpvd_offset[vsp1->index], 0x20);
done:
if (ret)
pm_runtime_disable(&pdev->dev);
@@ -807,11 +1045,16 @@ static int vsp1_remove(struct platform_device *pdev)
{
struct vsp1_device *vsp1 = platform_get_drvdata(pdev);
+ vsp1_device_put(vsp1);
vsp1_destroy_entities(vsp1);
rcar_fcp_put(vsp1->fcp);
pm_runtime_disable(&pdev->dev);
+ if ((RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) &&
+ vsp1_gen3_vspd_check(vsp1))
+ iounmap(fcpv_reg[vsp1->index]);
+
return 0;
}
diff --git a/drivers/media/platform/vsp1/vsp1_entity.c b/drivers/media/platform/vsp1/vsp1_entity.c
index 5263387..a64da29 100644
--- a/drivers/media/platform/vsp1/vsp1_entity.c
+++ b/drivers/media/platform/vsp1/vsp1_entity.c
@@ -1,7 +1,7 @@
/*
* vsp1_entity.c -- R-Car VSP1 Base Entity
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -36,6 +36,8 @@ void vsp1_entity_route_setup(struct vsp1_entity *entity,
{
struct vsp1_entity *source;
struct vsp1_entity *sink;
+ struct vsp1_device *vsp1 = pipe->output->entity.vsp1;
+ u32 route_data;
if (entity->type == VSP1_ENTITY_HGO) {
u32 smppt;
@@ -68,8 +70,15 @@ void vsp1_entity_route_setup(struct vsp1_entity *entity,
return;
sink = media_entity_to_vsp1_entity(source->sink);
- vsp1_dl_list_write(dl, source->route->reg,
- sink->route->inputs[source->sink_pad]);
+
+ route_data = sink->route->inputs[source->sink_pad];
+ if (entity->type == VSP1_ENTITY_BRS)
+ route_data = sink->route->inputs[source->sink_pad] |
+ VI6_DPR_ROUTE_BRSSEL_BRS;
+
+ dev_dbg(vsp1->dev, "type:%d, reg:0x%x, route_data:0x%x\n",
+ entity->type, source->route->reg, route_data);
+ vsp1_dl_list_write(dl, source->route->reg, route_data);
}
/* -----------------------------------------------------------------------------
@@ -449,12 +458,18 @@ static const struct vsp1_route vsp1_routes[] = {
{ VI6_DPR_NODE_BRU_IN(0), VI6_DPR_NODE_BRU_IN(1),
VI6_DPR_NODE_BRU_IN(2), VI6_DPR_NODE_BRU_IN(3),
VI6_DPR_NODE_BRU_IN(4) }, VI6_DPR_NODE_BRU_OUT },
+ { VSP1_ENTITY_BRS, 0, VI6_DPR_BRS_ROUTE,
+ { 0, 0, 0, VI6_DPR_NODE_BRS_IN(0), VI6_DPR_NODE_BRS_IN(1), },
+ VI6_DPR_NODE_BRS_OUT },
VSP1_ENTITY_ROUTE(CLU),
{ VSP1_ENTITY_HGO, 0, 0, { 0, }, 0 },
{ VSP1_ENTITY_HGT, 0, 0, { 0, }, 0 },
VSP1_ENTITY_ROUTE(HSI),
VSP1_ENTITY_ROUTE(HST),
- { VSP1_ENTITY_LIF, 0, 0, { VI6_DPR_NODE_LIF, }, VI6_DPR_NODE_LIF },
+ { VSP1_ENTITY_LIF, 0, 0,
+ { VI6_DPR_NODE_LIF(0), }, VI6_DPR_NODE_LIF(0) },
+ { VSP1_ENTITY_LIF, 1, 0,
+ { VI6_DPR_NODE_LIF(1), }, VI6_DPR_NODE_LIF(1) },
VSP1_ENTITY_ROUTE(LUT),
VSP1_ENTITY_ROUTE_RPF(0),
VSP1_ENTITY_ROUTE_RPF(1),
diff --git a/drivers/media/platform/vsp1/vsp1_entity.h b/drivers/media/platform/vsp1/vsp1_entity.h
index c169a06..d76cf16 100644
--- a/drivers/media/platform/vsp1/vsp1_entity.h
+++ b/drivers/media/platform/vsp1/vsp1_entity.h
@@ -35,6 +35,7 @@ enum vsp1_entity_type {
VSP1_ENTITY_SRU,
VSP1_ENTITY_UDS,
VSP1_ENTITY_WPF,
+ VSP1_ENTITY_BRS,
};
/**
diff --git a/drivers/media/platform/vsp1/vsp1_lif.c b/drivers/media/platform/vsp1/vsp1_lif.c
index e32acae..69bcf7e 100644
--- a/drivers/media/platform/vsp1/vsp1_lif.c
+++ b/drivers/media/platform/vsp1/vsp1_lif.c
@@ -1,7 +1,7 @@
/*
* vsp1_lif.c -- R-Car VSP1 LCD Controller Interface
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -134,9 +134,9 @@ static void lif_configure(struct vsp1_entity *entity,
{
const struct v4l2_mbus_framefmt *format;
struct vsp1_lif *lif = to_lif(&entity->subdev);
- unsigned int hbth = 1300;
- unsigned int obth = 400;
- unsigned int lbth = 200;
+ unsigned int hbth = 0;
+ unsigned int obth = 3000;
+ unsigned int lbth = 0;
if (params != VSP1_ENTITY_PARAMS_INIT)
return;
@@ -146,11 +146,11 @@ static void lif_configure(struct vsp1_entity *entity,
obth = min(obth, (format->width + 1) / 2 * format->height - 4);
- vsp1_lif_write(lif, dl, VI6_LIF_CSBTH,
+ vsp1_lif_write(lif, dl, VI6_LIF_CSBTH(lif->entity.index),
(hbth << VI6_LIF_CSBTH_HBTH_SHIFT) |
(lbth << VI6_LIF_CSBTH_LBTH_SHIFT));
- vsp1_lif_write(lif, dl, VI6_LIF_CTRL,
+ vsp1_lif_write(lif, dl, VI6_LIF_CTRL(lif->entity.index),
(obth << VI6_LIF_CTRL_OBTH_SHIFT) |
(format->code == 0 ? VI6_LIF_CTRL_CFMT : 0) |
VI6_LIF_CTRL_REQSEL | VI6_LIF_CTRL_LIF_EN);
@@ -164,7 +164,8 @@ static const struct vsp1_entity_operations lif_entity_ops = {
* Initialization and Cleanup
*/
-struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1)
+struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1,
+ unsigned int lif_index)
{
struct vsp1_lif *lif;
int ret;
@@ -175,6 +176,7 @@ struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1)
lif->entity.ops = &lif_entity_ops;
lif->entity.type = VSP1_ENTITY_LIF;
+ lif->entity.index = lif_index;
/* The LIF is never exposed to userspace, but media entity registration
* requires a function to be set. Use PROC_VIDEO_PIXEL_FORMATTER just to
diff --git a/drivers/media/platform/vsp1/vsp1_lif.h b/drivers/media/platform/vsp1/vsp1_lif.h
index 7b35879..c2d11f2 100644
--- a/drivers/media/platform/vsp1/vsp1_lif.h
+++ b/drivers/media/platform/vsp1/vsp1_lif.h
@@ -1,7 +1,7 @@
/*
* vsp1_lif.h -- R-Car VSP1 LCD Controller Interface
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -32,6 +32,7 @@ static inline struct vsp1_lif *to_lif(struct v4l2_subdev *subdev)
return container_of(subdev, struct vsp1_lif, entity.subdev);
}
-struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1);
+struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1,
+ unsigned int lif_index);
#endif /* __VSP1_LIF_H__ */
diff --git a/drivers/media/platform/vsp1/vsp1_pipe.c b/drivers/media/platform/vsp1/vsp1_pipe.c
index 7327192..20af576 100644
--- a/drivers/media/platform/vsp1/vsp1_pipe.c
+++ b/drivers/media/platform/vsp1/vsp1_pipe.c
@@ -1,7 +1,7 @@
/*
* vsp1_pipe.c -- R-Car VSP1 Pipeline
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -21,6 +21,7 @@
#include "vsp1.h"
#include "vsp1_bru.h"
+#include "vsp1_brs.h"
#include "vsp1_dl.h"
#include "vsp1_entity.h"
#include "vsp1_hgo.h"
@@ -119,7 +120,7 @@ static const struct vsp1_format_info vsp1_video_formats[] = {
{ V4L2_PIX_FMT_YVU420M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
- 3, { 8, 8, 8 }, false, true, 2, 2, false },
+ 3, { 8, 8, 8 }, false, false, 2, 2, false },
{ V4L2_PIX_FMT_YUV422M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
@@ -127,7 +128,7 @@ static const struct vsp1_format_info vsp1_video_formats[] = {
{ V4L2_PIX_FMT_YVU422M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
- 3, { 8, 8, 8 }, false, true, 2, 1, false },
+ 3, { 8, 8, 8 }, false, false, 2, 1, false },
{ V4L2_PIX_FMT_YUV444M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
@@ -135,7 +136,7 @@ static const struct vsp1_format_info vsp1_video_formats[] = {
{ V4L2_PIX_FMT_YVU444M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
- 3, { 8, 8, 8 }, false, true, 1, 1, false },
+ 3, { 8, 8, 8 }, false, false, 1, 1, false },
};
/**
@@ -180,6 +181,13 @@ void vsp1_pipeline_reset(struct vsp1_pipeline *pipe)
bru->inputs[i].rpf = NULL;
}
+ if (pipe->brs) {
+ struct vsp1_brs *brs = to_brs(&pipe->brs->subdev);
+
+ for (i = 0; i < ARRAY_SIZE(brs->inputs); ++i)
+ brs->inputs[i].rpf = NULL;
+ }
+
for (i = 0; i < ARRAY_SIZE(pipe->inputs); ++i) {
if (pipe->inputs[i]) {
pipe->inputs[i]->pipe = NULL;
@@ -209,6 +217,7 @@ void vsp1_pipeline_reset(struct vsp1_pipeline *pipe)
pipe->buffers_ready = 0;
pipe->num_inputs = 0;
pipe->bru = NULL;
+ pipe->brs = NULL;
pipe->hgo = NULL;
pipe->hgt = NULL;
pipe->lif = NULL;
@@ -224,6 +233,7 @@ void vsp1_pipeline_init(struct vsp1_pipeline *pipe)
INIT_LIST_HEAD(&pipe->entities);
pipe->state = VSP1_PIPELINE_STOPPED;
+ pipe->vmute_flag = false;
}
/* Must be called with the pipe irqlock held. */
@@ -315,9 +325,18 @@ bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe)
void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe)
{
+ unsigned long flags;
+
if (pipe == NULL)
return;
+ spin_lock_irqsave(&pipe->irqlock, flags);
+ if (pipe->output->write_back != 0) {
+ pipe->output->write_back--;
+ wake_up_interruptible(&pipe->event_wait);
+ }
+ spin_unlock_irqrestore(&pipe->irqlock, flags);
+
vsp1_dlm_irq_frame_end(pipe->output->dlm);
if (pipe->hgo)
diff --git a/drivers/media/platform/vsp1/vsp1_pipe.h b/drivers/media/platform/vsp1/vsp1_pipe.h
index f181949..305e2ae 100644
--- a/drivers/media/platform/vsp1/vsp1_pipe.h
+++ b/drivers/media/platform/vsp1/vsp1_pipe.h
@@ -1,7 +1,7 @@
/*
* vsp1_pipe.h -- R-Car VSP1 Pipeline
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -102,6 +102,7 @@ struct vsp1_pipeline {
struct vsp1_rwpf *inputs[VSP1_MAX_RPF];
struct vsp1_rwpf *output;
struct vsp1_entity *bru;
+ struct vsp1_entity *brs;
struct vsp1_entity *hgo;
struct vsp1_entity *hgt;
struct vsp1_entity *lif;
@@ -109,6 +110,7 @@ struct vsp1_pipeline {
struct vsp1_entity *uds_input;
struct list_head entities;
+ wait_queue_head_t event_wait;
struct vsp1_dl_list *dl;
@@ -116,8 +118,18 @@ struct vsp1_pipeline {
unsigned int partitions;
struct v4l2_rect partition;
unsigned int current_partition;
+
+ bool vmute_flag;
};
+static inline struct vsp1_pipeline *to_vsp1_pipeline(struct media_entity *e)
+{
+ if (likely(e->pipe))
+ return container_of(e->pipe, struct vsp1_pipeline, pipe);
+ else
+ return NULL;
+}
+
void vsp1_pipeline_reset(struct vsp1_pipeline *pipe);
void vsp1_pipeline_init(struct vsp1_pipeline *pipe);
diff --git a/drivers/media/platform/vsp1/vsp1_regs.h b/drivers/media/platform/vsp1/vsp1_regs.h
index cd3e32a..7dcbd63 100644
--- a/drivers/media/platform/vsp1/vsp1_regs.h
+++ b/drivers/media/platform/vsp1/vsp1_regs.h
@@ -1,7 +1,7 @@
/*
* vsp1_regs.h -- R-Car VSP1 Registers Definitions
*
- * Copyright (C) 2013 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -20,32 +20,58 @@
#define VI6_CMD(n) (0x0000 + (n) * 4)
#define VI6_CMD_STRCMD (1 << 0)
+#define VI6_CLK_CTRL0 0x0010
+#define VI6_CLK_CTRL0_WORKAROUND 0x10010F1F
+
+#define VI6_CLK_CTRL1 0x0014
+#define VI6_CLK_CTRL1_WORKAROUND 0xFF00FFFF
+
#define VI6_CLK_DCSWT 0x0018
#define VI6_CLK_DCSWT_CSTPW_MASK (0xff << 8)
#define VI6_CLK_DCSWT_CSTPW_SHIFT 8
#define VI6_CLK_DCSWT_CSTRW_MASK (0xff << 0)
#define VI6_CLK_DCSWT_CSTRW_SHIFT 0
+#define VI6_CLK_DCSWT_WORKAROUND1 0x00130808
+#define VI6_CLK_DCSWT_WORKAROUND2 0x00000808
+
+#define VI6_CLK_DCSM0 0x001C
+#define VI6_CLK_DCSM0_WORKAROUND 0x1FFF0F1F
+
+#define VI6_CLK_DCSM1 0x0020
+#define VI6_CLK_DCSM1_WORKAROUND 0xFF00FFFF
#define VI6_SRESET 0x0028
#define VI6_SRESET_SRTS(n) (1 << (n))
+#define VI6_MRESET_ENB0 0x002C
+#define VI6_MRESET_ENB0_WORKAROUND1 0x0000001F
+#define VI6_MRESET_ENB0_WORKAROUND2 0x30000F1F
+
+#define VI6_MRESET_ENB1 0x0030
+#define VI6_MRESET_ENB1_WORKAROUND 0xFF00FFFF
+
+#define VI6_MRESET 0x0034
+#define VI6_MRESET_WORKAROUND 0x00000001
+
#define VI6_STATUS 0x0038
#define VI6_STATUS_SYS_ACT(n) (1 << ((n) + 8))
#define VI6_WPF_IRQ_ENB(n) (0x0048 + (n) * 12)
+#define VI6_WFP_IRQ_ENB_UNDE (1 << 16)
#define VI6_WFP_IRQ_ENB_DFEE (1 << 1)
#define VI6_WFP_IRQ_ENB_FREE (1 << 0)
#define VI6_WPF_IRQ_STA(n) (0x004c + (n) * 12)
+#define VI6_WFP_IRQ_STA_UND (1 << 16)
#define VI6_WFP_IRQ_STA_DFE (1 << 1)
#define VI6_WFP_IRQ_STA_FRE (1 << 0)
-#define VI6_DISP_IRQ_ENB 0x0078
+#define VI6_DISP_IRQ_ENB(n) (0x0078 + (n) * 60)
#define VI6_DISP_IRQ_ENB_DSTE (1 << 8)
#define VI6_DISP_IRQ_ENB_MAEE (1 << 5)
#define VI6_DISP_IRQ_ENB_LNEE(n) (1 << (n))
-#define VI6_DISP_IRQ_STA 0x007c
+#define VI6_DISP_IRQ_STA(n) (0x007c + (n) * 60)
#define VI6_DISP_IRQ_STA_DST (1 << 8)
#define VI6_DISP_IRQ_STA_MAE (1 << 5)
#define VI6_DISP_IRQ_STA_LNE(n) (1 << (n))
@@ -69,12 +95,12 @@
#define VI6_DL_HDR_ADDR(n) (0x0104 + (n) * 4)
-#define VI6_DL_SWAP 0x0114
+#define VI6_DL_SWAP(n) (0x0114 + (n) * 56)
#define VI6_DL_SWAP_LWS (1 << 2)
#define VI6_DL_SWAP_WDS (1 << 1)
#define VI6_DL_SWAP_BTS (1 << 0)
-#define VI6_DL_EXT_CTRL 0x011c
+#define VI6_DL_EXT_CTRL(n) (0x011c + (n) * 36)
#define VI6_DL_EXT_CTRL_NWE (1 << 16)
#define VI6_DL_EXT_CTRL_POLINT_MASK (0x3f << 8)
#define VI6_DL_EXT_CTRL_POLINT_SHIFT 8
@@ -302,7 +328,7 @@
#define VI6_WPF_DSTM_ADDR_C0 0x1028
#define VI6_WPF_DSTM_ADDR_C1 0x102c
-#define VI6_WPF_WRBCK_CTRL 0x1034
+#define VI6_WPF_WRBCK_CTRL(n) (0x1034 + ((n) * 0x100))
#define VI6_WPF_WRBCK_CTRL_WBMD (1 << 0)
/* -----------------------------------------------------------------------------
@@ -321,6 +347,10 @@
#define VI6_DPR_HST_ROUTE 0x2044
#define VI6_DPR_HSI_ROUTE 0x2048
#define VI6_DPR_BRU_ROUTE 0x204c
+#define VI6_DPR_BRS_ROUTE 0x2050
+#define VI6_DPR_ROUTE_BRSSEL_MASK (0x01 << 28)
+#define VI6_DPR_ROUTE_BRSSEL_ILV (0x00 << 28)
+#define VI6_DPR_ROUTE_BRSSEL_BRS (0x01 << 28)
#define VI6_DPR_ROUTE_FXA_MASK (0xff << 16)
#define VI6_DPR_ROUTE_FXA_SHIFT 16
#define VI6_DPR_ROUTE_FP_MASK (0x3f << 8)
@@ -340,11 +370,13 @@
#define VI6_DPR_NODE_UDS(n) (17 + (n))
#define VI6_DPR_NODE_LUT 22
#define VI6_DPR_NODE_BRU_IN(n) (((n) <= 3) ? 23 + (n) : 49)
+#define VI6_DPR_NODE_BRS_IN(n) (38 + (n))
#define VI6_DPR_NODE_BRU_OUT 27
#define VI6_DPR_NODE_CLU 29
#define VI6_DPR_NODE_HST 30
#define VI6_DPR_NODE_HSI 31
-#define VI6_DPR_NODE_LIF 55
+#define VI6_DPR_NODE_BRS_OUT 40
+#define VI6_DPR_NODE_LIF(n) (54 + (n))
#define VI6_DPR_NODE_WPF(n) (56 + (n))
#define VI6_DPR_NODE_UNUSED 63
@@ -650,17 +682,101 @@
#define VI6_HGT_REGRST_RCLEA (1 << 0)
/* -----------------------------------------------------------------------------
+ * BRS Control Registers
+ */
+
+#define VI6_BRS_INCTRL 0x3900
+#define VI6_BRS_INCTRL_NRM (1 << 28)
+#define VI6_BRS_INCTRL_DnON (1 << (16 + (n)))
+#define VI6_BRS_INCTRL_DITHn_OFF (0 << ((n) * 4))
+#define VI6_BRS_INCTRL_DITHn_18BPP (1 << ((n) * 4))
+#define VI6_BRS_INCTRL_DITHn_16BPP (2 << ((n) * 4))
+#define VI6_BRS_INCTRL_DITHn_15BPP (3 << ((n) * 4))
+#define VI6_BRS_INCTRL_DITHn_12BPP (4 << ((n) * 4))
+#define VI6_BRS_INCTRL_DITHn_8BPP (5 << ((n) * 4))
+#define VI6_BRS_INCTRL_DITHn_MASK (7 << ((n) * 4))
+#define VI6_BRS_INCTRL_DITHn_SHIFT ((n) * 4)
+
+#define VI6_BRS_VIRRPF_SIZE 0x3904
+#define VI6_BRS_VIRRPF_SIZE_HSIZE_MASK (0x1fff << 16)
+#define VI6_BRS_VIRRPF_SIZE_HSIZE_SHIFT 16
+#define VI6_BRS_VIRRPF_SIZE_VSIZE_MASK (0x1fff << 0)
+#define VI6_BRS_VIRRPF_SIZE_VSIZE_SHIFT 0
+
+#define VI6_BRS_VIRRPF_LOC 0x3908
+#define VI6_BRS_VIRRPF_LOC_HCOORD_MASK (0x1fff << 16)
+#define VI6_BRS_VIRRPF_LOC_HCOORD_SHIFT 16
+#define VI6_BRS_VIRRPF_LOC_VCOORD_MASK (0x1fff << 0)
+#define VI6_BRS_VIRRPF_LOC_VCOORD_SHIFT 0
+
+#define VI6_BRS_VIRRPF_COL 0x390c
+#define VI6_BRS_VIRRPF_COL_A_MASK (0xff << 24)
+#define VI6_BRS_VIRRPF_COL_A_SHIFT 24
+#define VI6_BRS_VIRRPF_COL_RCR_MASK (0xff << 16)
+#define VI6_BRS_VIRRPF_COL_RCR_SHIFT 16
+#define VI6_BRS_VIRRPF_COL_GY_MASK (0xff << 8)
+#define VI6_BRS_VIRRPF_COL_GY_SHIFT 8
+#define VI6_BRS_VIRRPF_COL_BCB_MASK (0xff << 0)
+#define VI6_BRS_VIRRPF_COL_BCB_SHIFT 0
+
+#define VI6_BRS_CTRL(n) (0x3910 + (n) * 8)
+#define VI6_BRS_CTRL_RBC (1 << 31)
+#define VI6_BRS_CTRL_DSTSEL_BRSIN(n) ((n) << 20)
+#define VI6_BRS_CTRL_DSTSEL_VRPF (4 << 20)
+#define VI6_BRS_CTRL_DSTSEL_MASK (7 << 20)
+#define VI6_BRS_CTRL_SRCSEL_BRSIN(n) ((n) << 16)
+#define VI6_BRS_CTRL_SRCSEL_VRPF (4 << 16)
+#define VI6_BRS_CTRL_SRCSEL_MASK (7 << 16)
+#define VI6_BRS_CTRL_CROP(rop) ((rop) << 4)
+#define VI6_BRS_CTRL_CROP_MASK (0xf << 4)
+#define VI6_BRS_CTRL_AROP(rop) ((rop) << 0)
+#define VI6_BRS_CTRL_AROP_MASK (0xf << 0)
+
+#define VI6_BRS_BLD(n) (0x3914 + (n) * 8)
+#define VI6_BRS_BLD_CBES (1 << 31)
+#define VI6_BRS_BLD_CCMDX_DST_A (0 << 28)
+#define VI6_BRS_BLD_CCMDX_255_DST_A (1 << 28)
+#define VI6_BRS_BLD_CCMDX_SRC_A (2 << 28)
+#define VI6_BRS_BLD_CCMDX_255_SRC_A (3 << 28)
+#define VI6_BRS_BLD_CCMDX_COEFX (4 << 28)
+#define VI6_BRS_BLD_CCMDX_MASK (7 << 28)
+#define VI6_BRS_BLD_CCMDY_DST_A (0 << 24)
+#define VI6_BRS_BLD_CCMDY_255_DST_A (1 << 24)
+#define VI6_BRS_BLD_CCMDY_SRC_A (2 << 24)
+#define VI6_BRS_BLD_CCMDY_255_SRC_A (3 << 24)
+#define VI6_BRS_BLD_CCMDY_COEFY (4 << 24)
+#define VI6_BRS_BLD_CCMDY_MASK (7 << 24)
+#define VI6_BRS_BLD_CCMDY_SHIFT 24
+#define VI6_BRS_BLD_ABES (1 << 23)
+#define VI6_BRS_BLD_ACMDX_DST_A (0 << 20)
+#define VI6_BRS_BLD_ACMDX_255_DST_A (1 << 20)
+#define VI6_BRS_BLD_ACMDX_SRC_A (2 << 20)
+#define VI6_BRS_BLD_ACMDX_255_SRC_A (3 << 20)
+#define VI6_BRS_BLD_ACMDX_COEFX (4 << 20)
+#define VI6_BRS_BLD_ACMDX_MASK (7 << 20)
+#define VI6_BRS_BLD_ACMDY_DST_A (0 << 16)
+#define VI6_BRS_BLD_ACMDY_255_DST_A (1 << 16)
+#define VI6_BRS_BLD_ACMDY_SRC_A (2 << 16)
+#define VI6_BRS_BLD_ACMDY_255_SRC_A (3 << 16)
+#define VI6_BRS_BLD_ACMDY_COEFY (4 << 16)
+#define VI6_BRS_BLD_ACMDY_MASK (7 << 16)
+#define VI6_BRS_BLD_COEFX_MASK (0xff << 8)
+#define VI6_BRS_BLD_COEFX_SHIFT 8
+#define VI6_BRS_BLD_COEFY_MASK (0xff << 0)
+#define VI6_BRS_BLD_COEFY_SHIFT 0
+
+/* -----------------------------------------------------------------------------
* LIF Control Registers
*/
-#define VI6_LIF_CTRL 0x3b00
+#define VI6_LIF_CTRL(n) (0x3b00 - ((n) * 0x100))
#define VI6_LIF_CTRL_OBTH_MASK (0x7ff << 16)
#define VI6_LIF_CTRL_OBTH_SHIFT 16
#define VI6_LIF_CTRL_CFMT (1 << 4)
#define VI6_LIF_CTRL_REQSEL (1 << 1)
#define VI6_LIF_CTRL_LIF_EN (1 << 0)
-#define VI6_LIF_CSBTH 0x3b04
+#define VI6_LIF_CSBTH(n) (0x3b04 - ((n) * 0x100))
#define VI6_LIF_CSBTH_HBTH_MASK (0x7ff << 16)
#define VI6_LIF_CSBTH_HBTH_SHIFT 16
#define VI6_LIF_CSBTH_LBTH_MASK (0x7ff << 0)
@@ -689,6 +805,7 @@
#define VI6_IP_VERSION_MODEL_VSPBD_GEN3 (0x15 << 8)
#define VI6_IP_VERSION_MODEL_VSPBC_GEN3 (0x16 << 8)
#define VI6_IP_VERSION_MODEL_VSPD_GEN3 (0x17 << 8)
+#define VI6_IP_VERSION_MODEL_VSPDL_H3 (0x19 << 8)
#define VI6_IP_VERSION_SOC_MASK (0xff << 0)
#define VI6_IP_VERSION_SOC_H (0x01 << 0)
#define VI6_IP_VERSION_SOC_M (0x02 << 0)
diff --git a/drivers/media/platform/vsp1/vsp1_rpf.c b/drivers/media/platform/vsp1/vsp1_rpf.c
index 03d7160..f70757f 100644
--- a/drivers/media/platform/vsp1/vsp1_rpf.c
+++ b/drivers/media/platform/vsp1/vsp1_rpf.c
@@ -1,7 +1,7 @@
/*
* vsp1_rpf.c -- R-Car VSP1 Read Pixel Formatter
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -51,6 +51,7 @@ static void rpf_configure(struct vsp1_entity *entity,
struct vsp1_dl_list *dl,
enum vsp1_entity_params params)
{
+ struct vsp1_device *vsp1 = entity->vsp1;
struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev);
const struct vsp1_format_info *fmtinfo = rpf->fmtinfo;
const struct v4l2_pix_format_mplane *format = &rpf->format;
@@ -60,6 +61,16 @@ static void rpf_configure(struct vsp1_entity *entity,
unsigned int top = 0;
u32 pstride;
u32 infmt;
+ u32 alph_sel = 0;
+ u32 i;
+ u32 crop_width, crop_height, crop_x, crop_y;
+
+ if (pipe->vmute_flag) {
+ for (i = 0; i < vsp1->info->rpf_count; ++i)
+ vsp1_rpf_write(rpf, dl, VI6_DPR_RPF_ROUTE(i),
+ VI6_DPR_NODE_UNUSED);
+ return;
+ }
if (params == VSP1_ENTITY_PARAMS_RUNTIME) {
vsp1_rpf_write(rpf, dl, VI6_RPF_VRTCOL_SET,
@@ -72,8 +83,8 @@ static void rpf_configure(struct vsp1_entity *entity,
}
if (params == VSP1_ENTITY_PARAMS_PARTITION) {
- unsigned int offsets[2];
struct v4l2_rect crop;
+ u32 fourcc;
/*
* Source size and crop offsets.
@@ -113,29 +124,85 @@ static void rpf_configure(struct vsp1_entity *entity,
/ output->width;
}
- vsp1_rpf_write(rpf, dl, VI6_RPF_SRC_BSIZE,
- (crop.width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) |
- (crop.height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT));
- vsp1_rpf_write(rpf, dl, VI6_RPF_SRC_ESIZE,
- (crop.width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) |
- (crop.height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT));
+ crop_width = crop.width;
+ crop_x = crop.left;
+ fourcc = rpf->fmtinfo->fourcc;
- offsets[0] = crop.top * format->plane_fmt[0].bytesperline
- + crop.left * fmtinfo->bpp[0] / 8;
+ if (rpf->interlaced) {
+ crop_height = crop.height / 2;
+ crop_y = crop.top / 2;
+
+ if ((fourcc == V4L2_PIX_FMT_UYVY) ||
+ (fourcc == V4L2_PIX_FMT_VYUY) ||
+ (fourcc == V4L2_PIX_FMT_YUYV) ||
+ (fourcc == V4L2_PIX_FMT_YVYU)) {
+ crop_width = round_down(crop_width, 2);
+ crop_x = round_down(crop_x, 2);
+ } else if ((fourcc == V4L2_PIX_FMT_NV12M) ||
+ (fourcc == V4L2_PIX_FMT_NV21M)) {
+ crop_width = round_down(crop_width, 2);
+ crop_height = round_down(crop_height, 2);
+ crop_x = round_down(crop_x, 2);
+ crop_y = round_down(crop_y, 2);
+ } else if ((fourcc == V4L2_PIX_FMT_NV16M) ||
+ (fourcc == V4L2_PIX_FMT_NV61M)) {
+ crop_width = round_down(crop_width, 2);
+ crop_x = round_down(crop_x, 2);
+ } else if ((fourcc == V4L2_PIX_FMT_YUV420M) ||
+ (fourcc == V4L2_PIX_FMT_YUV444M) ||
+ (fourcc == V4L2_PIX_FMT_YVU420M) ||
+ (fourcc == V4L2_PIX_FMT_YVU444M)) {
+ crop_width = round_down(crop_width, 2);
+ crop_height = round_down(crop_height, 2);
+ } else if ((fourcc == V4L2_PIX_FMT_YUV422M) ||
+ (fourcc == V4L2_PIX_FMT_YVU422M)) {
+ crop_width = round_down(crop_width, 2);
+ crop_height = round_down(crop_height, 2);
+ crop_x = round_down(crop_x, 2);
+ crop_y = round_down(crop_y, 2);
+ }
+ } else {
+ crop_height = crop.height;
+ crop_y = crop.top;
+ }
+
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRC_BSIZE,
+ (crop_width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) |
+ (crop_height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT));
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRC_ESIZE,
+ (crop_width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) |
+ (crop_height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT));
+
+ rpf->offsets[0] = crop_y * format->plane_fmt[0].bytesperline
+ + crop_x * fmtinfo->bpp[0] / 8;
if (format->num_planes > 1)
- offsets[1] = crop.top * format->plane_fmt[1].bytesperline
- + crop.left / fmtinfo->hsub
+ rpf->offsets[1] = crop_y
+ * format->plane_fmt[1].bytesperline
+ + crop_x / fmtinfo->hsub
* fmtinfo->bpp[1] / 8;
else
- offsets[1] = 0;
+ rpf->offsets[1] = 0;
- vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_Y,
- rpf->mem.addr[0] + offsets[0]);
- vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C0,
- rpf->mem.addr[1] + offsets[1]);
- vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C1,
- rpf->mem.addr[2] + offsets[1]);
+ if (vsp1->auto_fld_mode)
+ vsp1_dl_set_addr_auto_fld(dl, rpf);
+ else {
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_Y,
+ rpf->mem.addr[0] + rpf->offsets[0]);
+ if ((fourcc == V4L2_PIX_FMT_YVU420M) ||
+ (fourcc == V4L2_PIX_FMT_YVU422M) ||
+ (fourcc == V4L2_PIX_FMT_YVU444M)) {
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C0,
+ rpf->mem.addr[2] + rpf->offsets[1]);
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C1,
+ rpf->mem.addr[1] + rpf->offsets[1]);
+ } else {
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C0,
+ rpf->mem.addr[1] + rpf->offsets[1]);
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C1,
+ rpf->mem.addr[2] + rpf->offsets[1]);
+ }
+ }
return;
}
@@ -146,7 +213,10 @@ static void rpf_configure(struct vsp1_entity *entity,
pstride |= format->plane_fmt[1].bytesperline
<< VI6_RPF_SRCM_PSTRIDE_C_SHIFT;
- vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_PSTRIDE, pstride);
+ if (rpf->interlaced)
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_PSTRIDE, pstride * 2);
+ else
+ vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_PSTRIDE, pstride);
/* Format */
sink_format = vsp1_entity_get_pad_format(&rpf->entity,
@@ -182,7 +252,23 @@ static void rpf_configure(struct vsp1_entity *entity,
top = compose->top;
}
- vsp1_rpf_write(rpf, dl, VI6_RPF_LOC,
+ if (pipe->brs) {
+ const struct v4l2_rect *compose;
+
+ compose = vsp1_entity_get_pad_selection(pipe->brs,
+ pipe->brs->config,
+ rpf->brs_input,
+ V4L2_SEL_TGT_COMPOSE);
+ left = compose->left;
+ top = compose->top;
+ }
+
+ if (rpf->interlaced)
+ vsp1_rpf_write(rpf, dl, VI6_RPF_LOC,
+ (left << VI6_RPF_LOC_HCOORD_SHIFT) |
+ ((top / 2) << VI6_RPF_LOC_VCOORD_SHIFT));
+ else
+ vsp1_rpf_write(rpf, dl, VI6_RPF_LOC,
(left << VI6_RPF_LOC_HCOORD_SHIFT) |
(top << VI6_RPF_LOC_VCOORD_SHIFT));
@@ -208,20 +294,46 @@ static void rpf_configure(struct vsp1_entity *entity,
*
* In all cases, disable color keying.
*/
- vsp1_rpf_write(rpf, dl, VI6_RPF_ALPH_SEL, VI6_RPF_ALPH_SEL_AEXT_EXT |
- (fmtinfo->alpha ? VI6_RPF_ALPH_SEL_ASEL_PACKED
- : VI6_RPF_ALPH_SEL_ASEL_FIXED));
+ switch (fmtinfo->fourcc) {
+ case V4L2_PIX_FMT_ARGB555:
+ if (CONFIG_VIDEO_RENESAS_VSP_ALPHA_BIT_ARGB1555 == 1)
+ alph_sel = VI6_RPF_ALPH_SEL_ASEL_SELECT |
+ VI6_RPF_ALPH_SEL_AEXT_EXT |
+ VI6_RPF_ALPH_SEL_ALPHA1_MASK |
+ (rpf->alpha &
+ VI6_RPF_ALPH_SEL_ALPHA0_MASK);
+ else
+ alph_sel = VI6_RPF_ALPH_SEL_ASEL_SELECT |
+ VI6_RPF_ALPH_SEL_AEXT_EXT |
+ ((rpf->alpha & 0xff) << 8) |
+ VI6_RPF_ALPH_SEL_ALPHA0_MASK;
+ break;
+ case V4L2_PIX_FMT_ARGB444:
+ alph_sel = VI6_RPF_ALPH_SEL_AEXT_ONE;
+ break;
+ case V4L2_PIX_FMT_ABGR32:
+ case V4L2_PIX_FMT_ARGB32:
+ break;
+ default:
+ alph_sel = VI6_RPF_ALPH_SEL_AEXT_EXT |
+ (fmtinfo->alpha ? VI6_RPF_ALPH_SEL_ASEL_PACKED
+ : VI6_RPF_ALPH_SEL_ASEL_FIXED);
+ break;
+ }
+
+ vsp1_rpf_write(rpf, dl, VI6_RPF_ALPH_SEL, alph_sel);
if (entity->vsp1->info->gen == 3) {
u32 mult;
- if (fmtinfo->alpha) {
+ if ((fmtinfo->alpha) &&
+ (fmtinfo->fourcc != V4L2_PIX_FMT_ARGB555)) {
/* When the input contains an alpha channel enable the
* alpha multiplier. If the input is premultiplied we
* need to multiply both the alpha channel and the pixel
* components by the global alpha value to keep them
* premultiplied. Otherwise multiply the alpha channel
- * only.
+ * only. The alpha multiplier is disabled in ARGB1555.
*/
bool premultiplied = format->flags
& V4L2_PIX_FMT_FLAG_PREMUL_ALPHA;
diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.h b/drivers/media/platform/vsp1/vsp1_rwpf.h
index b4ffc38..26aef3e 100644
--- a/drivers/media/platform/vsp1/vsp1_rwpf.h
+++ b/drivers/media/platform/vsp1/vsp1_rwpf.h
@@ -1,7 +1,7 @@
/*
* vsp1_rwpf.h -- R-Car VSP1 Read and Write Pixel Formatters
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -48,11 +48,13 @@ struct vsp1_rwpf {
struct v4l2_pix_format_mplane format;
const struct vsp1_format_info *fmtinfo;
unsigned int bru_input;
+ unsigned int brs_input;
unsigned int alpha;
u32 mult_alpha;
u32 outfmt;
+ unsigned int offsets[2];
struct {
spinlock_t lock;
@@ -65,6 +67,11 @@ struct vsp1_rwpf {
struct vsp1_rwpf_memory mem;
struct vsp1_dl_manager *dlm;
+
+ bool interlaced;
+
+ int write_back;
+ dma_addr_t buf_addr[3];
};
static inline struct vsp1_rwpf *to_rwpf(struct v4l2_subdev *subdev)
diff --git a/drivers/media/platform/vsp1/vsp1_video.c b/drivers/media/platform/vsp1/vsp1_video.c
index 94b4285..cc4f3e3 100644
--- a/drivers/media/platform/vsp1/vsp1_video.c
+++ b/drivers/media/platform/vsp1/vsp1_video.c
@@ -1,7 +1,7 @@
/*
* vsp1_video.c -- R-Car VSP1 Video Node
*
- * Copyright (C) 2013-2015 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -27,6 +27,8 @@
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-dma-contig.h>
+#include <media/rcar-fcp.h>
+
#include "vsp1.h"
#include "vsp1_bru.h"
#include "vsp1_dl.h"
@@ -1109,6 +1111,7 @@ struct vsp1_video *vsp1_video_create(struct vsp1_device *vsp1,
{
struct vsp1_video *video;
const char *direction;
+ struct device *fcp;
int ret;
video = devm_kzalloc(vsp1->dev, sizeof(*video), GFP_KERNEL);
@@ -1166,7 +1169,8 @@ struct vsp1_video *vsp1_video_create(struct vsp1_device *vsp1,
video->queue.ops = &vsp1_video_queue_qops;
video->queue.mem_ops = &vb2_dma_contig_memops;
video->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
- video->queue.dev = video->vsp1->dev;
+ fcp = rcar_fcp_get_device(vsp1->fcp);
+ video->queue.dev = fcp ? fcp : video->vsp1->dev;
ret = vb2_queue_init(&video->queue);
if (ret < 0) {
dev_err(video->vsp1->dev, "failed to initialize vb2 queue\n");
diff --git a/drivers/media/platform/vsp1/vsp1_wpf.c b/drivers/media/platform/vsp1/vsp1_wpf.c
index 958e7f2..6f26b62 100644
--- a/drivers/media/platform/vsp1/vsp1_wpf.c
+++ b/drivers/media/platform/vsp1/vsp1_wpf.c
@@ -1,7 +1,7 @@
/*
* vsp1_wpf.c -- R-Car VSP1 Write Pixel Formatter
*
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -248,6 +248,20 @@ static void wpf_configure(struct vsp1_entity *entity,
u32 outfmt = 0;
u32 srcrpf = 0;
+ if (pipe->vmute_flag) {
+ vsp1_wpf_write(wpf, dl, VI6_WPF_SRCRPF +
+ (0x100 * wpf->entity.index),
+ VI6_WPF_SRCRPF_VIRACT_MST);
+ vsp1_wpf_write(wpf, dl, VI6_WPF_HSZCLIP +
+ (0x100 * wpf->entity.index), 0);
+ vsp1_wpf_write(wpf, dl, VI6_WPF_VSZCLIP +
+ (0x100 * wpf->entity.index), 0);
+ vsp1_wpf_write(wpf, dl, VI6_DPR_WPF_FPORCH(wpf->entity.index),
+ VI6_DPR_WPF_FPORCH_FP_WPFN);
+
+ return;
+ }
+
if (params == VSP1_ENTITY_PARAMS_RUNTIME) {
const unsigned int mask = BIT(WPF_CTRL_VFLIP)
| BIT(WPF_CTRL_HFLIP);
@@ -384,10 +398,21 @@ static void wpf_configure(struct vsp1_entity *entity,
}
/* Format */
- if (!pipe->lif) {
+ if (!pipe->lif || (wpf->write_back == 2)) {
const struct v4l2_pix_format_mplane *format = &wpf->format;
const struct vsp1_format_info *fmtinfo = wpf->fmtinfo;
+ if (pipe->lif) {
+ vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_Y,
+ wpf->buf_addr[0]);
+ if (format->num_planes > 1)
+ vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_C0,
+ wpf->buf_addr[1]);
+ if (format->num_planes > 2)
+ vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_C1,
+ wpf->buf_addr[2]);
+ }
+
outfmt = fmtinfo->hwfmt << VI6_WPF_OUTFMT_WRFMT_SHIFT;
if (wpf->flip.rotate)
@@ -424,7 +449,12 @@ static void wpf_configure(struct vsp1_entity *entity,
vsp1_dl_list_write(dl, VI6_DPR_WPF_FPORCH(wpf->entity.index),
VI6_DPR_WPF_FPORCH_FP_WPFN);
- vsp1_dl_list_write(dl, VI6_WPF_WRBCK_CTRL, 0);
+ if (pipe->lif && (pipe->output->write_back == 2))
+ vsp1_dl_list_write(dl, VI6_WPF_WRBCK_CTRL(wpf->entity.index),
+ VI6_WPF_WRBCK_CTRL_WBMD);
+ else
+ vsp1_dl_list_write(dl, VI6_WPF_WRBCK_CTRL(wpf->entity.index),
+ 0);
/* Sources. If the pipeline has a single input and BRU is not used,
* configure it as the master layer. Otherwise configure all
@@ -450,7 +480,7 @@ static void wpf_configure(struct vsp1_entity *entity,
/* Enable interrupts */
vsp1_dl_list_write(dl, VI6_WPF_IRQ_STA(wpf->entity.index), 0);
vsp1_dl_list_write(dl, VI6_WPF_IRQ_ENB(wpf->entity.index),
- VI6_WFP_IRQ_ENB_DFEE);
+ VI6_WFP_IRQ_ENB_FREE | VI6_WFP_IRQ_ENB_UNDE);
}
static unsigned int wpf_max_width(struct vsp1_entity *entity,
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5274f50..7ec33c5 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -558,6 +558,16 @@
This provides support for the SDHI SD/SDIO controller found in
SuperH and ARM SH-Mobile SoCs
+config MMC_SDHI_PIO
+ bool "SH-Mobile SDHI PIO transfer mode setting"
+ depends on MMC_SDHI
+ help
+ This setting switches the transfer mode of the PIO and DMA.
+
+ Select Yes or No according to the following.
+ When switching the transfer mode from DMA to PIO, say Y here.
+ When switching the transfer mode from PIO to DMA, say N here.
+
config MMC_CB710
tristate "ENE CB710 MMC/SD Interface support"
depends on PCI
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e2bdaaf..1ac115f 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -37,7 +37,13 @@
obj-$(CONFIG_MMC_TMIO) += tmio_mmc.o
obj-$(CONFIG_MMC_TMIO_CORE) += tmio_mmc_core.o
tmio_mmc_core-y := tmio_mmc_pio.o
+ifeq ($(CONFIG_ARM64),y)
+# if CONFIG_ARM64, we assume it is gen3
+tmio_mmc_core-$(subst m,y,$(CONFIG_MMC_SDHI)) += tmio_mmc_dma_gen3.o
+else
+# if no, We assume it is gen2 or older
tmio_mmc_core-$(subst m,y,$(CONFIG_MMC_SDHI)) += tmio_mmc_dma.o
+endif
obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o
obj-$(CONFIG_MMC_CB710) += cb710-mmc.o
obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c
index 665ea53..a91e598 100644
--- a/drivers/mmc/host/sh_mobile_sdhi.c
+++ b/drivers/mmc/host/sh_mobile_sdhi.c
@@ -1,8 +1,8 @@
/*
* SuperH Mobile SDHI
*
+ * Copyright (C) 2016 Renesas Electronics Corporation
* Copyright (C) 2016 Sang Engineering, Wolfram Sang
- * Copyright (C) 2015-16 Renesas Electronics Corporation
* Copyright (C) 2009 Magnus Damm
*
* This program is free software; you can redistribute it and/or modify
@@ -37,7 +37,7 @@
#include "tmio_mmc.h"
-#define EXT_ACC 0xe4
+#define HOST_MODE 0xe4
#define SDHI_VER_GEN2_SDR50 0x490c
/* very old datasheets said 0x490c for SDR104, too. They are wrong! */
@@ -59,6 +59,9 @@ struct sh_mobile_sdhi_of_data {
enum dma_slave_buswidth dma_buswidth;
dma_addr_t dma_rx_offset;
unsigned bus_shift;
+ unsigned int max_blk_count;
+ unsigned short max_segs;
+ bool sdbuf_64bit;
int scc_offset;
struct sh_mobile_sdhi_scc *taps;
int taps_num;
@@ -107,9 +110,15 @@ static struct sh_mobile_sdhi_scc rcar_gen3_scc_taps[] = {
static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = {
.tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE |
- TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2,
- .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
+ TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2 |
+ TMIO_MMC_CLK_NO_SLEEP,
+ .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ |
+ MMC_CAP_CMD23,
.bus_shift = 2,
+ /* Gen3 SDHI DMAC can handle 0xffffffff blk count, but seg = 1 */
+ .max_blk_count = 0xffffffff,
+ .max_segs = 1,
+ .sdbuf_64bit = true,
.scc_offset = 0x1000,
.taps = rcar_gen3_scc_taps,
.taps_num = ARRAY_SIZE(rcar_gen3_scc_taps),
@@ -171,7 +180,7 @@ static void sh_mobile_sdhi_sdbuf_width(struct tmio_mmc_host *host, int width)
return;
}
- sd_ctrl_write16(host, EXT_ACC, val);
+ sd_ctrl_write16(host, HOST_MODE, val);
}
static int sh_mobile_sdhi_clk_enable(struct tmio_mmc_host *host)
@@ -297,6 +306,7 @@ static int sh_mobile_sdhi_start_signal_voltage_switch(struct mmc_host *mmc,
#define SH_MOBILE_SDHI_SCC_CKSEL 0x006
#define SH_MOBILE_SDHI_SCC_RVSCNTL 0x008
#define SH_MOBILE_SDHI_SCC_RVSREQ 0x00A
+#define SH_MOBILE_SDHI_SCC_TMPPORT2 0x00E
/* Definitions for values the SH_MOBILE_SDHI_SCC_DTCNTL register */
#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN BIT(0)
@@ -309,6 +319,9 @@ static int sh_mobile_sdhi_start_signal_voltage_switch(struct mmc_host *mmc,
#define SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN BIT(1)
/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSREQ register */
#define SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR BIT(2)
+/* Definitions for values the SH_MOBILE_SDHI_SCC_TMPPORT2 register */
+#define SH_MOBILE_SDHI_SCC_TMPPORT2_HS400EN BIT(31)
+#define SH_MOBILE_SDHI_SCC_TMPPORT2_HS400OSEL BIT(4)
static inline u32 sd_scc_read32(struct tmio_mmc_host *host,
struct sh_mobile_sdhi *priv, int addr)
@@ -327,7 +340,10 @@ static unsigned int sh_mobile_sdhi_init_tuning(struct tmio_mmc_host *host)
{
struct sh_mobile_sdhi *priv;
- if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
+ if (!(host->mmc->caps & MMC_CAP_UHS_SDR104) &&
+ !(host->mmc->caps2 & MMC_CAP2_HS200_1_8V_SDR) &&
+ !(host->mmc->caps2 & (MMC_CAP2_HS400_1_8V |
+ MMC_CAP2_HS200_1_8V_SDR)))
return 0;
priv = host_to_priv(host);
@@ -374,6 +390,27 @@ static void sh_mobile_sdhi_prepare_tuning(struct tmio_mmc_host *host,
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap);
}
+static void sh_mobile_sdhi_prepare_hs400_tuning(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ struct sh_mobile_sdhi *priv = host_to_priv(host);
+
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+
+ /* Set HS400 mode */
+ sd_ctrl_write16(host, CTL_SDIF_MODE, 0x0001 |
+ sd_ctrl_read16(host, CTL_SDIF_MODE));
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2,
+ (SH_MOBILE_SDHI_SCC_TMPPORT2_HS400EN |
+ SH_MOBILE_SDHI_SCC_TMPPORT2_HS400OSEL) |
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2));
+
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+}
+
#define SH_MOBILE_SDHI_MAX_TAP 3
static int sh_mobile_sdhi_select_tuning(struct tmio_mmc_host *host)
@@ -389,6 +426,15 @@ static int sh_mobile_sdhi_select_tuning(struct tmio_mmc_host *host)
/* Clear SCC_RVSREQ */
sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0);
+ /* Merge the results */
+ for (i = 0; i < host->tap_num * 2; i++) {
+ if (!test_bit(i, host->taps)) {
+ clear_bit(i % host->tap_num, host->taps);
+ clear_bit((i % host->tap_num) + host->tap_num,
+ host->taps);
+ }
+ }
+
/*
* Find the longest consecutive run of successful probes. If that
* is more than SH_MOBILE_SDHI_MAX_TAP probes long then use the
@@ -438,7 +484,10 @@ static bool sh_mobile_sdhi_check_scc_error(struct tmio_mmc_host *host)
{
struct sh_mobile_sdhi *priv;
- if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
+ if (!(host->mmc->caps & MMC_CAP_UHS_SDR104) &&
+ !(host->mmc->caps2 & MMC_CAP2_HS200_1_8V_SDR) &&
+ !(host->mmc->caps2 & (MMC_CAP2_HS400_1_8V |
+ MMC_CAP2_HS200_1_8V_SDR)))
return 0;
priv = host_to_priv(host);
@@ -460,7 +509,10 @@ static void sh_mobile_sdhi_hw_reset(struct tmio_mmc_host *host)
{
struct sh_mobile_sdhi *priv;
- if (!(host->mmc->caps & MMC_CAP_UHS_SDR104))
+ if (!(host->mmc->caps & MMC_CAP_UHS_SDR104) &&
+ !(host->mmc->caps2 & MMC_CAP2_HS200_1_8V_SDR) &&
+ !(host->mmc->caps2 & (MMC_CAP2_HS400_1_8V |
+ MMC_CAP2_HS200_1_8V_SDR)))
return;
priv = host_to_priv(host);
@@ -473,6 +525,14 @@ static void sh_mobile_sdhi_hw_reset(struct tmio_mmc_host *host)
~SH_MOBILE_SDHI_SCC_CKSEL_DTSEL &
sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL));
+ /* Reset HS400 mode */
+ sd_ctrl_write16(host, CTL_SDIF_MODE, ~0x0001 &
+ sd_ctrl_read16(host, CTL_SDIF_MODE));
+ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2,
+ ~(SH_MOBILE_SDHI_SCC_TMPPORT2_HS400EN |
+ SH_MOBILE_SDHI_SCC_TMPPORT2_HS400OSEL) &
+ sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2));
+
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN |
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
@@ -513,7 +573,7 @@ static int sh_mobile_sdhi_write16_hook(struct tmio_mmc_host *host, int addr)
case CTL_SD_MEM_CARD_OPT:
case CTL_TRANSACTION_CTL:
case CTL_DMA_ENABLE:
- case EXT_ACC:
+ case HOST_MODE:
return sh_mobile_sdhi_wait_idle(host);
}
@@ -540,10 +600,12 @@ static int sh_mobile_sdhi_multi_io_quirk(struct mmc_card *card,
static void sh_mobile_sdhi_enable_dma(struct tmio_mmc_host *host, bool enable)
{
+ int dma_width = host->dma->sdbuf_64bit ? 64 : 32;
+
sd_ctrl_write16(host, CTL_DMA_ENABLE, enable ? 2 : 0);
/* enable 32bit access if DMA mode if possibile */
- sh_mobile_sdhi_sdbuf_width(host, enable ? 32 : 16);
+ sh_mobile_sdhi_sdbuf_width(host, enable ? dma_width : 16);
}
static int sh_mobile_sdhi_probe(struct platform_device *pdev)
@@ -597,6 +659,8 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
mmc_data->capabilities |= of_data->capabilities;
mmc_data->capabilities2 |= of_data->capabilities2;
mmc_data->dma_rx_offset = of_data->dma_rx_offset;
+ mmc_data->max_blk_count = of_data->max_blk_count;
+ mmc_data->max_segs = of_data->max_segs;
dma_priv->dma_buswidth = of_data->dma_buswidth;
host->bus_shift = of_data->bus_shift;
}
@@ -619,10 +683,15 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
host->select_tuning = sh_mobile_sdhi_select_tuning;
host->check_scc_error = sh_mobile_sdhi_check_scc_error;
host->hw_reset = sh_mobile_sdhi_hw_reset;
+ host->prepare_hs400_tuning =
+ sh_mobile_sdhi_prepare_hs400_tuning;
}
/* Orginally registers were 16 bit apart, could be 32 or 64 nowadays */
- if (!host->bus_shift && resource_size(res) > 0x100) /* old way to determine the shift */
+ if (resource_size(res) > 0x400)
+ host->bus_shift = 2;
+ else if (!host->bus_shift &&
+ resource_size(res) > 0x100) /* old way to determine the shift */
host->bus_shift = 1;
if (mmd)
@@ -659,7 +728,10 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev)
if (ret < 0)
goto efree;
- if (host->mmc->caps & MMC_CAP_UHS_SDR104) {
+ if ((host->mmc->caps & MMC_CAP_UHS_SDR104) ||
+ (host->mmc->caps2 & MMC_CAP2_HS200_1_8V_SDR) ||
+ (host->mmc->caps2 & (MMC_CAP2_HS400_1_8V |
+ MMC_CAP2_HS200_1_8V_SDR))) {
host->mmc->caps |= MMC_CAP_HW_RESET;
if (of_id && of_id->data) {
@@ -730,11 +802,13 @@ static int sh_mobile_sdhi_remove(struct platform_device *pdev)
}
static const struct dev_pm_ops tmio_mmc_dev_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
- pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(tmio_mmc_host_runtime_suspend,
tmio_mmc_host_runtime_resume,
NULL)
+#ifdef CONFIG_PM_SLEEP
+ .suspend_late = tmio_mmc_host_suspend,
+ .resume_early = tmio_mmc_host_resume,
+#endif
};
static struct platform_driver sh_mobile_sdhi_driver = {
diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c
index e897e7f..17ac517 100644
--- a/drivers/mmc/host/tmio_mmc.c
+++ b/drivers/mmc/host/tmio_mmc.c
@@ -30,7 +30,7 @@ static int tmio_mmc_suspend(struct device *dev)
const struct mfd_cell *cell = mfd_get_cell(pdev);
int ret;
- ret = pm_runtime_force_suspend(dev);
+ ret = tmio_mmc_host_suspend(dev);
/* Tell MFD core it can disable us now.*/
if (!ret && cell->disable)
@@ -50,7 +50,7 @@ static int tmio_mmc_resume(struct device *dev)
ret = cell->resume(pdev);
if (!ret)
- ret = pm_runtime_force_resume(dev);
+ ret = tmio_mmc_host_resume(dev);
return ret;
}
diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h
index 637581f..e81dbe1 100644
--- a/drivers/mmc/host/tmio_mmc.h
+++ b/drivers/mmc/host/tmio_mmc.h
@@ -19,6 +19,7 @@
#define TMIO_MMC_H
#include <linux/dmaengine.h>
+#include <linux/completion.h>
#include <linux/highmem.h>
#include <linux/mutex.h>
#include <linux/pagemap.h>
@@ -45,6 +46,7 @@
#define CTL_DMA_ENABLE 0xd8
#define CTL_RESET_SD 0xe0
#define CTL_VERSION 0xe2
+#define CTL_SDIF_MODE 0xe6
#define CTL_SDIO_REGS 0x100
#define CTL_CLK_AND_WAIT_CTL 0x138
#define CTL_RESET_SDIO 0x1e0
@@ -104,6 +106,7 @@ struct tmio_mmc_host;
struct tmio_mmc_dma {
enum dma_slave_buswidth dma_buswidth;
+ bool sdbuf_64bit;
bool (*filter)(struct dma_chan *chan, void *arg);
void (*enable)(struct tmio_mmc_host *host, bool enable);
};
@@ -154,6 +157,7 @@ struct tmio_mmc_host {
bool native_hotplug;
bool sdio_irq_enabled;
u32 scc_tappos;
+ struct completion completion;
/* Mandatory callback */
int (*clk_enable)(struct tmio_mmc_host *host);
@@ -180,6 +184,7 @@ struct tmio_mmc_host {
/* Tuning values: 1 for success, 0 for failure */
DECLARE_BITMAP(taps, BITS_PER_BYTE * sizeof(long));
unsigned int tap_num;
+ void (*prepare_hs400_tuning)(struct mmc_host *mmc, struct mmc_ios *ios);
};
struct tmio_mmc_host *tmio_mmc_host_alloc(struct platform_device *pdev);
@@ -244,6 +249,11 @@ int tmio_mmc_host_runtime_suspend(struct device *dev);
int tmio_mmc_host_runtime_resume(struct device *dev);
#endif
+#ifdef CONFIG_PM_SLEEP
+int tmio_mmc_host_suspend(struct device *dev);
+int tmio_mmc_host_resume(struct device *dev);
+#endif
+
static inline u16 sd_ctrl_read16(struct tmio_mmc_host *host, int addr)
{
return readw(host->ctl + (addr << host->bus_shift));
diff --git a/drivers/mmc/host/tmio_mmc_dma_gen3.c b/drivers/mmc/host/tmio_mmc_dma_gen3.c
new file mode 100644
index 0000000..b289dfa
--- /dev/null
+++ b/drivers/mmc/host/tmio_mmc_dma_gen3.c
@@ -0,0 +1,196 @@
+/*
+ * linux/drivers/mmc/tmio_mmc_dma_gen3.c
+ *
+ * Copyright (C) 2015 Renesas Electronics Corporation
+ *
+ * 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.
+ *
+ * R-Car Gen3 DMA function for TMIO MMC implementations
+ */
+
+#include <linux/bug.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mfd/tmio.h>
+#include <linux/mmc/host.h>
+#include <linux/pagemap.h>
+#include <linux/scatterlist.h>
+
+#include "tmio_mmc.h"
+
+#define DM_CM_DTRAN_MODE 0x820
+#define DM_CM_DTRAN_CTRL 0x828
+#define DM_CM_RST 0x830
+#define DM_CM_INFO1 0x840
+#define DM_CM_INFO1_MASK 0x848
+#define DM_CM_INFO2 0x850
+#define DM_CM_INFO2_MASK 0x858
+#define DM_DTRAN_ADDR 0x880
+
+/* DM_CM_DTRAN_MODE */
+#define DTRAN_MODE_CH_NUM_CH0 0 /* "downstream" = for write commands */
+#define DTRAN_MODE_CH_NUM_CH1 BIT(16) /* "uptream" = for read commands */
+#define DTRAN_MODE_BUS_WID_TH (BIT(5) | BIT(4))
+#define DTRAN_MODE_ADDR_MODE BIT(0) /* 1 = Increment address */
+
+/* DM_CM_DTRAN_CTRL */
+#define DTRAN_CTRL_DM_START BIT(0)
+
+/* DM_CM_RST */
+#define RST_DTRANRST1 BIT(9)
+#define RST_DTRANRST0 BIT(8)
+#define RST_RESERVED_BITS GENMASK_ULL(32, 0)
+
+/* DM_CM_INFO1 and DM_CM_INFO1_MASK */
+#define INFO1_CLEAR 0
+#define INFO1_DTRANEND1 BIT(17)
+#define INFO1_DTRANEND0 BIT(16)
+
+/* DM_CM_INFO2 and DM_CM_INFO2_MASK */
+#define INFO2_DTRANERR1 BIT(17)
+#define INFO2_DTRANERR0 BIT(16)
+
+/*
+ * Specification of this driver:
+ * - host->chan_{rx,tx} will be used as a flag of enabling/disabling the dma
+ * - Since this SDHI DMAC register set has actual 32-bit and "bus_shift" is 2,
+ * this driver cannot use original sd_ctrl_{write,read}32 functions.
+ */
+
+static void tmio_dm_write(struct tmio_mmc_host *host, int addr, u64 val)
+{
+ writeq(val, host->ctl + addr);
+}
+
+void tmio_mmc_enable_dma(struct tmio_mmc_host *host, bool enable)
+{
+ if (!host->chan_tx || !host->chan_rx)
+ return;
+
+ if (!enable)
+ tmio_dm_write(host, DM_CM_INFO1, INFO1_CLEAR);
+
+ if (host->dma->enable)
+ host->dma->enable(host, enable);
+}
+
+void tmio_mmc_abort_dma(struct tmio_mmc_host *host)
+{
+ u64 val = RST_DTRANRST1 | RST_DTRANRST0;
+
+ dev_dbg(&host->pdev->dev, "%s\n", __func__);
+
+ tmio_mmc_enable_dma(host, false);
+
+ tmio_dm_write(host, DM_CM_RST, RST_RESERVED_BITS & ~val);
+ tmio_dm_write(host, DM_CM_RST, RST_RESERVED_BITS | val);
+
+ tmio_mmc_enable_dma(host, true);
+}
+
+void tmio_mmc_start_dma(struct tmio_mmc_host *host, struct mmc_data *data)
+{
+ struct scatterlist *sg = host->sg_ptr;
+ u32 dtran_mode = DTRAN_MODE_BUS_WID_TH | DTRAN_MODE_ADDR_MODE;
+ enum dma_data_direction dir;
+ int ret;
+ u32 irq_mask;
+
+ if (!host->chan_rx || !host->chan_tx)
+ return;
+
+ /* This DMAC cannot handle if sg_len is not 1 */
+ WARN_ON(host->sg_len > 1);
+
+ dev_dbg(&host->pdev->dev, "%s: %d, %x\n", __func__, host->sg_len,
+ data->flags);
+
+ /* This DMAC cannot handle if buffer is not 8-bytes alignment */
+ if (!IS_ALIGNED(sg->offset, 8)) {
+ host->force_pio = true;
+ tmio_mmc_enable_dma(host, false);
+ return;
+ }
+
+ if (data->flags & MMC_DATA_READ) {
+ dtran_mode |= DTRAN_MODE_CH_NUM_CH1;
+ dir = DMA_FROM_DEVICE;
+ irq_mask = TMIO_STAT_RXRDY;
+ } else {
+ dtran_mode |= DTRAN_MODE_CH_NUM_CH0;
+ dir = DMA_TO_DEVICE;
+ irq_mask = TMIO_STAT_TXRQ;
+ }
+
+ ret = dma_map_sg(&host->pdev->dev, sg, host->sg_len, dir);
+ if (ret < 0) {
+ dev_err(&host->pdev->dev, "%s: dma_map_sg failed\n", __func__);
+ return;
+ }
+
+ tmio_mmc_enable_dma(host, true);
+
+ /* disable PIO irqs to avoid "PIO IRQ in DMA mode!" */
+ tmio_mmc_disable_mmc_irqs(host, irq_mask);
+
+ /* set dma parameters */
+ tmio_dm_write(host, DM_CM_DTRAN_MODE, dtran_mode);
+ tmio_dm_write(host, DM_DTRAN_ADDR, sg->dma_address);
+}
+
+#ifndef CONFIG_MMC_SDHI_PIO
+static void tmio_mmc_issue_tasklet_fn(unsigned long arg)
+{
+ struct tmio_mmc_host *host = (struct tmio_mmc_host *)arg;
+
+ dev_dbg(&host->pdev->dev, "%s\n", __func__);
+
+ tmio_mmc_enable_mmc_irqs(host, TMIO_STAT_DATAEND);
+
+ /* start the DMAC */
+ tmio_dm_write(host, DM_CM_DTRAN_CTRL, DTRAN_CTRL_DM_START);
+}
+
+static void tmio_mmc_complete_tasklet_fn(unsigned long arg)
+{
+ struct tmio_mmc_host *host = (struct tmio_mmc_host *)arg;
+ enum dma_data_direction dir;
+
+ dev_dbg(&host->pdev->dev, "%s: %p\n", __func__, host->data);
+
+ if (!host->data)
+ return;
+
+ if (host->data->flags & MMC_DATA_READ)
+ dir = DMA_FROM_DEVICE;
+ else
+ dir = DMA_TO_DEVICE;
+
+ tmio_mmc_enable_dma(host, false);
+ dma_unmap_sg(&host->pdev->dev, host->sg_ptr, host->sg_len, dir);
+ tmio_mmc_do_data_irq(host);
+}
+#endif
+
+void tmio_mmc_request_dma(struct tmio_mmc_host *host,
+ struct tmio_mmc_data *pdata)
+{
+#ifndef CONFIG_MMC_SDHI_PIO
+ /* Each value is set to non-zero to assume "enabling" each DMA */
+ host->chan_rx = host->chan_tx = (void *)0xdeadbeaf;
+
+ tasklet_init(&host->dma_complete, tmio_mmc_complete_tasklet_fn,
+ (unsigned long)host);
+ tasklet_init(&host->dma_issue, tmio_mmc_issue_tasklet_fn,
+ (unsigned long)host);
+#endif
+}
+
+void tmio_mmc_release_dma(struct tmio_mmc_host *host)
+{
+ /* Each value is set to zero to assume "disabling" each DMA */
+ host->chan_rx = host->chan_tx = NULL;
+}
diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c
index a0f05eb..8b1f403 100644
--- a/drivers/mmc/host/tmio_mmc_pio.c
+++ b/drivers/mmc/host/tmio_mmc_pio.c
@@ -1,8 +1,8 @@
/*
* linux/drivers/mmc/host/tmio_mmc_pio.c
*
+ * Copyright (C) 2016 Renesas Electronics Corporation
* Copyright (C) 2016 Sang Engineering, Wolfram Sang
- * Copyright (C) 2015-16 Renesas Electronics Corporation
* Copyright (C) 2011 Guennadi Liakhovetski
* Copyright (C) 2007 Ian Molton
* Copyright (C) 2004 Ian Molton
@@ -161,8 +161,9 @@ static void tmio_mmc_clk_start(struct tmio_mmc_host *host)
msleep(host->pdata->flags & TMIO_MMC_MIN_RCAR2 ? 1 : 10);
if (host->pdata->flags & TMIO_MMC_HAVE_HIGH_REG) {
- sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0100);
- msleep(10);
+ sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, CLK_CTL_SCLKEN);
+ if (!(host->pdata->flags & TMIO_MMC_CLK_NO_SLEEP))
+ msleep(10);
}
}
@@ -170,7 +171,8 @@ static void tmio_mmc_clk_stop(struct tmio_mmc_host *host)
{
if (host->pdata->flags & TMIO_MMC_HAVE_HIGH_REG) {
sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0000);
- msleep(10);
+ if (!(host->pdata->flags & TMIO_MMC_CLK_NO_SLEEP))
+ msleep(10);
}
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN &
@@ -197,8 +199,13 @@ static void tmio_mmc_set_clock(struct tmio_mmc_host *host,
clock <<= 1;
/* 1/1 clock is option */
- if ((host->pdata->flags & TMIO_MMC_CLK_ACTUAL) && ((clk >> 22) & 0x1))
- clk |= 0xff;
+ if ((host->pdata->flags & TMIO_MMC_CLK_ACTUAL) &&
+ ((clk >> 22) & 0x1)) {
+ if (!(host->mmc->ios.timing == MMC_TIMING_MMC_HS400))
+ clk |= 0xff;
+ else
+ clk &= ~0xff;
+ }
if (host->set_clk_div)
host->set_clk_div(host->pdev, (clk >> 22) & 1);
@@ -231,6 +238,7 @@ static void tmio_mmc_reset_work(struct work_struct *work)
delayed_reset_work.work);
struct mmc_request *mrq;
unsigned long flags;
+ u16 clk;
spin_lock_irqsave(&host->lock, flags);
mrq = host->mrq;
@@ -264,7 +272,9 @@ static void tmio_mmc_reset_work(struct work_struct *work)
spin_unlock_irqrestore(&host->lock, flags);
+ clk = sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL) & 0x0ff;
tmio_mmc_reset(host);
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, clk | 0x100);
/* Ready for new calls */
host->mrq = NULL;
@@ -278,6 +288,7 @@ static void tmio_mmc_finish_request(struct tmio_mmc_host *host)
{
struct mmc_request *mrq;
unsigned long flags;
+ struct mmc_command *cmd = host->cmd;
spin_lock_irqsave(&host->lock, flags);
@@ -302,6 +313,12 @@ static void tmio_mmc_finish_request(struct tmio_mmc_host *host)
if (host->check_scc_error)
host->check_scc_error(host);
+ if (cmd == mrq->sbc) {
+ /* finish SET_BLOCK_COUNT request */
+ complete(&host->completion);
+ return;
+ }
+
mmc_request_done(host->mmc, mrq);
}
@@ -333,9 +350,18 @@ static int tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command
u32 irq_mask = TMIO_MASK_CMD;
/* CMD12 is handled by hardware */
- if (cmd->opcode == MMC_STOP_TRANSMISSION && !cmd->arg) {
- sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x001);
- return 0;
+ if (!(host->pdata->flags & TMIO_MMC_MIN_RCAR2)) {
+ if (cmd->opcode == MMC_STOP_TRANSMISSION && !cmd->arg) {
+ sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x001);
+ return 0;
+ }
+ } else if (cmd->opcode == MMC_STOP_TRANSMISSION && !cmd->arg) {
+ u32 status = sd_ctrl_read16_and_16_as_32(host, CTL_STATUS);
+
+ if (status & TMIO_STAT_CMD_BUSY) {
+ sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x001);
+ return 0;
+ }
}
switch (mmc_resp_type(cmd)) {
@@ -369,7 +395,8 @@ static int tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command
* multiple block transfer
*/
if ((host->pdata->flags & TMIO_MMC_HAVE_CMD12_CTRL) &&
- (cmd->opcode == SD_IO_RW_EXTENDED))
+ ((cmd->opcode == SD_IO_RW_EXTENDED) ||
+ host->mrq->sbc))
c |= NO_CMD12_ISSUE;
}
if (data->flags & MMC_DATA_READ)
@@ -827,6 +854,7 @@ static void tmio_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
struct tmio_mmc_host *host = mmc_priv(mmc);
unsigned long flags;
int ret;
+ u32 opcode;
spin_lock_irqsave(&host->lock, flags);
@@ -846,6 +874,36 @@ static void tmio_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
spin_unlock_irqrestore(&host->lock, flags);
+ if (mrq->sbc) {
+ init_completion(&host->completion);
+ ret = tmio_mmc_start_command(host, mrq->sbc);
+ if (ret)
+ goto fail;
+ ret = wait_for_completion_timeout(&host->completion,
+ msecs_to_jiffies(CMDREQ_TIMEOUT));
+ if (ret < 0)
+ goto fail;
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ goto fail;
+ }
+ host->last_req_ts = jiffies;
+ host->mrq = mrq;
+ if (((host->check_scc_error && host->check_scc_error(host)) ||
+ (mrq->cmd->error == -EILSEQ)) && host->mmc->card) {
+ if (mmc_card_mmc(host->mmc->card))
+ opcode = MMC_SEND_TUNING_BLOCK_HS200;
+ else
+ opcode = MMC_SEND_TUNING_BLOCK;
+ /* Start retuning */
+ ret = tmio_mmc_execute_tuning(mmc, opcode);
+ if (ret)
+ goto fail;
+ /* Restore request */
+ host->mrq = mrq;
+ }
+ }
+
if (mrq->data) {
ret = tmio_mmc_start_data(host, mrq->data);
if (ret)
@@ -949,6 +1007,11 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
struct device *dev = &host->pdev->dev;
unsigned long flags;
+ /* HS400 Register setting */
+ if (ios->timing == MMC_TIMING_MMC_HS400)
+ if (host->prepare_hs400_tuning)
+ host->prepare_hs400_tuning(mmc, ios);
+
mutex_lock(&host->ios_lock);
spin_lock_irqsave(&host->lock, flags);
@@ -1142,10 +1205,10 @@ int tmio_mmc_host_probe(struct tmio_mmc_host *_host,
mmc->caps |= MMC_CAP_4_BIT_DATA | pdata->capabilities;
mmc->caps2 |= pdata->capabilities2;
- mmc->max_segs = 32;
+ mmc->max_segs = pdata->max_segs ? pdata->max_segs : 32;
mmc->max_blk_size = 512;
- mmc->max_blk_count = (PAGE_SIZE / mmc->max_blk_size) *
- mmc->max_segs;
+ mmc->max_blk_count = pdata->max_blk_count ? :
+ (PAGE_SIZE / mmc->max_blk_size) * mmc->max_segs;
mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
mmc->max_seg_size = mmc->max_req_size;
@@ -1311,4 +1374,23 @@ int tmio_mmc_host_runtime_resume(struct device *dev)
EXPORT_SYMBOL(tmio_mmc_host_runtime_resume);
#endif
+#ifdef CONFIG_PM_SLEEP
+int tmio_mmc_host_suspend(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+
+ tmio_mmc_hw_reset(mmc);
+
+ return 0;
+}
+EXPORT_SYMBOL(tmio_mmc_host_suspend);
+
+int tmio_mmc_host_resume(struct device *dev)
+{
+ /* Empty for now */
+ return 0;
+}
+EXPORT_SYMBOL(tmio_mmc_host_resume);
+#endif /* CONFIG_PM_SLEEP */
+
MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h
index f110966..9a8dd874 100644
--- a/drivers/net/ethernet/renesas/ravb.h
+++ b/drivers/net/ethernet/renesas/ravb.h
@@ -76,6 +76,7 @@ enum ravb_reg {
CDAR20 = 0x0060,
CDAR21 = 0x0064,
ESR = 0x0088,
+ APSR = 0x008C, /* R-Car Gen3 only */
RCR = 0x0090,
RQC0 = 0x0094,
RQC1 = 0x0098,
@@ -83,6 +84,7 @@ enum ravb_reg {
RQC3 = 0x00A0,
RQC4 = 0x00A4,
RPC = 0x00B0,
+ RTC = 0x00B4, /* R-Car Gen3 only */
UFCW = 0x00BC,
UFCS = 0x00C0,
UFCV0 = 0x00C4,
@@ -128,11 +130,52 @@ enum ravb_reg {
SFP29 = 0x0174,
SFP30 = 0x0178,
SFP31 = 0x017C,
+ SFV0 = 0x01B8, /* R-Car Gen3 only */
+ SFV1 = 0x01BC, /* R-Car Gen3 only */
SFM0 = 0x01C0,
SFM1 = 0x01C4,
+ SFL = 0x01C8, /* R-Car Gen3 only */
+ PCRC = 0x01CC, /* R-Car Gen3 only */
+ CIAR0 = 0x0200, /* R-Car Gen3 only */
+ CIAR1 = 0x0204, /* R-Car Gen3 only */
+ CIAR2 = 0x0208, /* R-Car Gen3 only */
+ CIAR3 = 0x020C, /* R-Car Gen3 only */
+ CIAR4 = 0x0210, /* R-Car Gen3 only */
+ CIAR5 = 0x0214, /* R-Car Gen3 only */
+ CIAR6 = 0x0218, /* R-Car Gen3 only */
+ CIAR7 = 0x021C, /* R-Car Gen3 only */
+ CIAR8 = 0x0220, /* R-Car Gen3 only */
+ CIAR9 = 0x0224, /* R-Car Gen3 only */
+ CIAR10 = 0x0228, /* R-Car Gen3 only */
+ CIAR11 = 0x022C, /* R-Car Gen3 only */
+ CIAR12 = 0x0230, /* R-Car Gen3 only */
+ CIAR13 = 0x0234, /* R-Car Gen3 only */
+ CIAR14 = 0x0238, /* R-Car Gen3 only */
+ CIAR15 = 0x023C, /* R-Car Gen3 only */
+ CIAR16 = 0x0240, /* R-Car Gen3 only */
+ CIAR17 = 0x0244, /* R-Car Gen3 only */
+ LIAR0 = 0x0280, /* R-Car Gen3 only */
+ LIAR1 = 0x0284, /* R-Car Gen3 only */
+ LIAR2 = 0x0288, /* R-Car Gen3 only */
+ LIAR3 = 0x028C, /* R-Car Gen3 only */
+ LIAR4 = 0x0290, /* R-Car Gen3 only */
+ LIAR5 = 0x0294, /* R-Car Gen3 only */
+ LIAR6 = 0x0298, /* R-Car Gen3 only */
+ LIAR7 = 0x029C, /* R-Car Gen3 only */
+ LIAR8 = 0x02A0, /* R-Car Gen3 only */
+ LIAR9 = 0x02A4, /* R-Car Gen3 only */
+ LIAR10 = 0x02A8, /* R-Car Gen3 only */
+ LIAR11 = 0x02AC, /* R-Car Gen3 only */
+ LIAR12 = 0x02B0, /* R-Car Gen3 only */
+ LIAR13 = 0x02B4, /* R-Car Gen3 only */
+ LIAR14 = 0x02B8, /* R-Car Gen3 only */
+ LIAR15 = 0x02BC, /* R-Car Gen3 only */
+ LIAR16 = 0x02C0, /* R-Car Gen3 only */
+ LIAR17 = 0x02C4, /* R-Car Gen3 only */
TGC = 0x0300,
TCCR = 0x0304,
TSR = 0x0308,
+ MFA = 0x030C,
TFA0 = 0x0310,
TFA1 = 0x0314,
TFA2 = 0x0318,
@@ -158,6 +201,8 @@ enum ravb_reg {
TIS = 0x037C,
ISS = 0x0380,
CIE = 0x0384, /* R-Car Gen3 only */
+ RIC3 = 0x0388, /* R-Car Gen3 only */
+ RIS3 = 0x038C, /* R-Car Gen3 only */
GCCR = 0x0390,
GMTT = 0x0394,
GPTC = 0x0398,
@@ -171,15 +216,46 @@ enum ravb_reg {
GCT0 = 0x03B8,
GCT1 = 0x03BC,
GCT2 = 0x03C0,
+ GSR = 0x03C4, /* R-Car Gen3 only */
GIE = 0x03CC, /* R-Car Gen3 only */
GID = 0x03D0, /* R-Car Gen3 only */
+ GIL = 0x03D4, /* R-Car Gen3 only */
+ GACP = 0x03DC, /* R-Car Gen3 only */
+ GPTF0 = 0x03E0, /* R-Car Gen3 only */
+ GPTF1 = 0x03E4, /* R-Car Gen3 only */
+ GPTF2 = 0x03E8, /* R-Car Gen3 only */
+ GPTF3 = 0x03EC, /* R-Car Gen3 only */
+ GCAT0 = 0x0400, /* R-Car Gen3 only */
+ GCAT1 = 0x0404, /* R-Car Gen3 only */
+ GCAT2 = 0x0408, /* R-Car Gen3 only */
+ GCAT3 = 0x040C, /* R-Car Gen3 only */
+ GCAT4 = 0x0410, /* R-Car Gen3 only */
+ GCAT5 = 0x0414, /* R-Car Gen3 only */
+ GCAT6 = 0x0418, /* R-Car Gen3 only */
+ GCAT7 = 0x041C, /* R-Car Gen3 only */
+ GCAT8 = 0x0420, /* R-Car Gen3 only */
+ GCAT9 = 0x0424, /* R-Car Gen3 only */
+ GCAT10 = 0x0428, /* R-Car Gen3 only */
+ GCAT11 = 0x042C, /* R-Car Gen3 only */
+ GCAT12 = 0x0430, /* R-Car Gen3 only */
+ GCAT13 = 0x0434, /* R-Car Gen3 only */
+ GCAT14 = 0x0438, /* R-Car Gen3 only */
+ GCAT15 = 0x043C, /* R-Car Gen3 only */
DIL = 0x0440, /* R-Car Gen3 only */
+ EIL = 0x0444, /* R-Car Gen3 only */
+ TIL = 0x0448, /* R-Car Gen3 only */
+ DIE = 0x0450, /* R-Car Gen3 only */
+ DID = 0x0454, /* R-Car Gen3 only */
+ EIE = 0x0458, /* R-Car Gen3 only */
+ EID = 0x045C, /* R-Car Gen3 only */
RIE0 = 0x0460, /* R-Car Gen3 only */
RID0 = 0x0464, /* R-Car Gen3 only */
RIE2 = 0x0470, /* R-Car Gen3 only */
RID2 = 0x0474, /* R-Car Gen3 only */
TIE = 0x0478, /* R-Car Gen3 only */
TID = 0x047c, /* R-Car Gen3 only */
+ RIE3 = 0x0488, /* R-Car Gen3 only */
+ RID3 = 0x048C, /* R-Car Gen3 only */
/* E-MAC registers */
ECMR = 0x0500,
@@ -189,9 +265,12 @@ enum ravb_reg {
PIR = 0x0520,
PSR = 0x0528,
PIPR = 0x052c,
+ APR = 0x0554, /* R-Car Gen3 only */
MPR = 0x0558,
PFTCR = 0x055c,
PFRCR = 0x0560,
+ PFTTLR = 0x0564, /* R-Car Gen3 only */
+ PFTTCR = 0x0568, /* R-Car Gen3 only */
GECMR = 0x05b0,
MAHR = 0x05c0,
MALR = 0x05c8,
@@ -248,6 +327,15 @@ enum ESR_BIT {
ESR_EIL = 0x00001000,
};
+/* APSR */
+enum APSR_BIT {
+ APSR_MEMS = 0x00000002,
+ APSR_CMSW = 0x00000010,
+ APSR_DM = 0x00006000,
+ APSR_DM_RDM = 0x00002000,
+ APSR_DM_TDM = 0x00004000,
+};
+
/* RCR */
enum RCR_BIT {
RCR_EFFS = 0x00000001,
@@ -319,6 +407,14 @@ enum RTC_BIT {
RTC_MFL1 = 0x0FFF0000,
};
+/* SFL */
+enum SFL_BIT {
+ SFL_LC = 0x0000001F,
+ SFL_LC_SFM = 0x0000001D,
+ SFL_LC_SFO = 0x0000001E,
+ SFL_LC_LOADABLE = 0x0000001F,
+};
+
/* TGC */
enum TGC_BIT {
TGC_TSM0 = 0x00000001,
@@ -947,7 +1043,6 @@ enum RAVB_QUEUE {
#define RX_QUEUE_OFFSET 4
#define NUM_RX_QUEUE 2
#define NUM_TX_QUEUE 2
-#define NUM_TX_DESC 2 /* TX descriptors per packet */
struct ravb_tstamp_skb {
struct list_head list;
@@ -1023,6 +1118,7 @@ struct ravb_private {
unsigned no_avb_link:1;
unsigned avb_link_active_low:1;
+ int num_tx_desc; /* TX descriptors per packet */
};
static inline u32 ravb_read(struct net_device *ndev, enum ravb_reg reg)
diff --git a/drivers/net/ethernet/renesas/ravb_main.c b/drivers/net/ethernet/renesas/ravb_main.c
index 630536b..8ad1246 100644
--- a/drivers/net/ethernet/renesas/ravb_main.c
+++ b/drivers/net/ethernet/renesas/ravb_main.c
@@ -31,6 +31,9 @@
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include <asm/div64.h>
@@ -185,6 +188,10 @@ static void ravb_ring_free(struct net_device *ndev, int q)
struct ravb_private *priv = netdev_priv(ndev);
int ring_size;
int i;
+ int num_tx_desc = priv->num_tx_desc;
+ struct ravb_ex_rx_desc *rx_desc;
+ struct ravb_tx_desc *tx_desc;
+ u32 size;
/* Free RX skb ringbuffer */
if (priv->rx_skb[q]) {
@@ -207,6 +214,16 @@ static void ravb_ring_free(struct net_device *ndev, int q)
priv->tx_align[q] = NULL;
if (priv->rx_ring[q]) {
+ for (i = 0; i < priv->num_rx_ring[q]; i++) {
+ rx_desc = &priv->rx_ring[q][i];
+ if (rx_desc->dptr != 0) {
+ dma_unmap_single(ndev->dev.parent,
+ le32_to_cpu(rx_desc->dptr),
+ PKT_BUF_SZ,
+ DMA_FROM_DEVICE);
+ rx_desc->dptr = 0;
+ }
+ }
ring_size = sizeof(struct ravb_ex_rx_desc) *
(priv->num_rx_ring[q] + 1);
dma_free_coherent(ndev->dev.parent, ring_size, priv->rx_ring[q],
@@ -215,8 +232,18 @@ static void ravb_ring_free(struct net_device *ndev, int q)
}
if (priv->tx_ring[q]) {
+ for (i = 0; i < priv->num_tx_ring[q]; i++) {
+ tx_desc = &priv->tx_ring[q][i];
+ size = le16_to_cpu(tx_desc->ds_tagl) & TX_DS;
+ if (tx_desc->dptr != 0) {
+ dma_unmap_single(ndev->dev.parent,
+ le32_to_cpu(tx_desc->dptr),
+ size, DMA_TO_DEVICE);
+ tx_desc->dptr = 0;
+ }
+ }
ring_size = sizeof(struct ravb_tx_desc) *
- (priv->num_tx_ring[q] * NUM_TX_DESC + 1);
+ (priv->num_tx_ring[q] * num_tx_desc + 1);
dma_free_coherent(ndev->dev.parent, ring_size, priv->tx_ring[q],
priv->tx_desc_dma[q]);
priv->tx_ring[q] = NULL;
@@ -230,9 +257,10 @@ static void ravb_ring_format(struct net_device *ndev, int q)
struct ravb_ex_rx_desc *rx_desc;
struct ravb_tx_desc *tx_desc;
struct ravb_desc *desc;
+ int num_tx_desc = priv->num_tx_desc;
int rx_ring_size = sizeof(*rx_desc) * priv->num_rx_ring[q];
int tx_ring_size = sizeof(*tx_desc) * priv->num_tx_ring[q] *
- NUM_TX_DESC;
+ num_tx_desc;
dma_addr_t dma_addr;
int i;
@@ -267,8 +295,10 @@ static void ravb_ring_format(struct net_device *ndev, int q)
for (i = 0, tx_desc = priv->tx_ring[q]; i < priv->num_tx_ring[q];
i++, tx_desc++) {
tx_desc->die_dt = DT_EEMPTY;
- tx_desc++;
- tx_desc->die_dt = DT_EEMPTY;
+ if (num_tx_desc >= 2) {
+ tx_desc++;
+ tx_desc->die_dt = DT_EEMPTY;
+ }
}
tx_desc->dptr = cpu_to_le32((u32)priv->tx_desc_dma[q]);
tx_desc->die_dt = DT_LINKFIX; /* type */
@@ -291,6 +321,7 @@ static int ravb_ring_init(struct net_device *ndev, int q)
struct sk_buff *skb;
int ring_size;
int i;
+ int num_tx_desc = priv->num_tx_desc;
/* Allocate RX and TX skb rings */
priv->rx_skb[q] = kcalloc(priv->num_rx_ring[q],
@@ -326,7 +357,7 @@ static int ravb_ring_init(struct net_device *ndev, int q)
/* Allocate all TX descriptors. */
ring_size = sizeof(struct ravb_tx_desc) *
- (priv->num_tx_ring[q] * NUM_TX_DESC + 1);
+ (priv->num_tx_ring[q] * num_tx_desc + 1);
priv->tx_ring[q] = dma_alloc_coherent(ndev->dev.parent, ring_size,
&priv->tx_desc_dma[q],
GFP_KERNEL);
@@ -440,10 +471,11 @@ static int ravb_tx_free(struct net_device *ndev, int q)
int free_num = 0;
int entry;
u32 size;
+ int num_tx_desc = priv->num_tx_desc;
for (; priv->cur_tx[q] - priv->dirty_tx[q] > 0; priv->dirty_tx[q]++) {
entry = priv->dirty_tx[q] % (priv->num_tx_ring[q] *
- NUM_TX_DESC);
+ num_tx_desc);
desc = &priv->tx_ring[q][entry];
if (desc->die_dt != DT_FEMPTY)
break;
@@ -451,12 +483,12 @@ static int ravb_tx_free(struct net_device *ndev, int q)
dma_rmb();
size = le16_to_cpu(desc->ds_tagl) & TX_DS;
/* Free the original skb. */
- if (priv->tx_skb[q][entry / NUM_TX_DESC]) {
+ if (priv->tx_skb[q][entry / num_tx_desc]) {
dma_unmap_single(ndev->dev.parent, le32_to_cpu(desc->dptr),
size, DMA_TO_DEVICE);
/* Last packet descriptor? */
- if (entry % NUM_TX_DESC == NUM_TX_DESC - 1) {
- entry /= NUM_TX_DESC;
+ if (entry % num_tx_desc == num_tx_desc - 1) {
+ entry /= num_tx_desc;
dev_kfree_skb_any(priv->tx_skb[q][entry]);
priv->tx_skb[q][entry] = NULL;
stats->tx_packets++;
@@ -928,11 +960,9 @@ static int ravb_poll(struct napi_struct *napi, int budget)
priv->rx_over_errors += priv->stats[RAVB_NC].rx_over_errors;
if (priv->rx_over_errors != ndev->stats.rx_over_errors) {
ndev->stats.rx_over_errors = priv->rx_over_errors;
- netif_err(priv, rx_err, ndev, "Receive Descriptor Empty\n");
}
if (priv->rx_fifo_errors != ndev->stats.rx_fifo_errors) {
ndev->stats.rx_fifo_errors = priv->rx_fifo_errors;
- netif_err(priv, rx_err, ndev, "Receive FIFO Overflow\n");
}
out:
return budget - quota;
@@ -1015,16 +1045,40 @@ static int ravb_phy_init(struct net_device *ndev)
* at this time.
*/
if (priv->chip_id == RCAR_GEN3) {
- int err;
+ struct platform_device *pdev = priv->pdev;
+ struct device *dev = &pdev->dev;
+ enum of_gpio_flags flags;
+ int err, gpio;
- err = phy_set_max_speed(phydev, SPEED_100);
- if (err) {
- netdev_err(ndev, "failed to limit PHY to 100Mbit/s\n");
- phy_disconnect(phydev);
+ err = RCAR_PRR_INIT();
+ if (err)
return err;
+
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS10) == 0)) {
+ err = phy_set_max_speed(phydev, SPEED_100);
+ if (err) {
+ netdev_err(ndev, "failed to limit PHY to 100Mbit/s\n");
+ phy_disconnect(phydev);
+ return err;
+ }
+ netdev_info(ndev, "limited PHY to 100Mbit/s\n");
}
- netdev_info(ndev, "limited PHY to 100Mbit/s\n");
+ gpio = of_get_named_gpio_flags(np, "phy-int-gpio", 0, &flags);
+ if (gpio_is_valid(gpio)) {
+ err = devm_gpio_request_one(dev, gpio, GPIOF_DIR_IN,
+ "phy-int-gpio");
+ if (err == 0 && (phydev->irq == gpio_to_irq(gpio))) {
+ if (flags & OF_GPIO_ACTIVE_LOW)
+ irq_set_irq_type(phydev->irq,
+ IRQ_TYPE_LEVEL_LOW);
+ else
+ irq_set_irq_type(phydev->irq,
+ IRQ_TYPE_LEVEL_HIGH);
+ }
+ }
+
}
/* 10BASE is not supported */
@@ -1483,41 +1537,56 @@ static netdev_tx_t ravb_start_xmit(struct sk_buff *skb, struct net_device *ndev)
void *buffer;
u32 entry;
u32 len;
+ int num_tx_desc = priv->num_tx_desc;
spin_lock_irqsave(&priv->lock, flags);
if (priv->cur_tx[q] - priv->dirty_tx[q] > (priv->num_tx_ring[q] - 1) *
- NUM_TX_DESC) {
+ num_tx_desc) {
netif_err(priv, tx_queued, ndev,
"still transmitting with the full ring!\n");
netif_stop_subqueue(ndev, q);
spin_unlock_irqrestore(&priv->lock, flags);
return NETDEV_TX_BUSY;
}
- entry = priv->cur_tx[q] % (priv->num_tx_ring[q] * NUM_TX_DESC);
- priv->tx_skb[q][entry / NUM_TX_DESC] = skb;
+ entry = priv->cur_tx[q] % (priv->num_tx_ring[q] * num_tx_desc);
+ priv->tx_skb[q][entry / num_tx_desc] = skb;
if (skb_put_padto(skb, ETH_ZLEN))
goto drop;
- buffer = PTR_ALIGN(priv->tx_align[q], DPTR_ALIGN) +
- entry / NUM_TX_DESC * DPTR_ALIGN;
- len = PTR_ALIGN(skb->data, DPTR_ALIGN) - skb->data;
- memcpy(buffer, skb->data, len);
- dma_addr = dma_map_single(ndev->dev.parent, buffer, len, DMA_TO_DEVICE);
- if (dma_mapping_error(ndev->dev.parent, dma_addr))
- goto drop;
+ if (num_tx_desc >= 2) {
+ buffer = PTR_ALIGN(priv->tx_align[q], DPTR_ALIGN) +
+ entry / num_tx_desc * DPTR_ALIGN;
+ len = PTR_ALIGN(skb->data, DPTR_ALIGN) - skb->data;
+ /* quick fix */
+ if (len == 0)
+ len = 4;
+ memcpy(buffer, skb->data, len);
+ dma_addr = dma_map_single(ndev->dev.parent, buffer, len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(ndev->dev.parent, dma_addr))
+ goto drop;
- desc = &priv->tx_ring[q][entry];
- desc->ds_tagl = cpu_to_le16(len);
- desc->dptr = cpu_to_le32(dma_addr);
+ desc = &priv->tx_ring[q][entry];
+ desc->ds_tagl = cpu_to_le16(len);
+ desc->dptr = cpu_to_le32(dma_addr);
- buffer = skb->data + len;
- len = skb->len - len;
- dma_addr = dma_map_single(ndev->dev.parent, buffer, len, DMA_TO_DEVICE);
- if (dma_mapping_error(ndev->dev.parent, dma_addr))
- goto unmap;
+ buffer = skb->data + len;
+ len = skb->len - len;
+ dma_addr = dma_map_single(ndev->dev.parent, buffer, len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(ndev->dev.parent, dma_addr))
+ goto unmap;
- desc++;
+ desc++;
+ } else {
+ desc = &priv->tx_ring[q][entry];
+ len = skb->len;
+ dma_addr = dma_map_single(ndev->dev.parent, skb->data, skb->len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(ndev->dev.parent, dma_addr))
+ goto drop;
+ }
desc->ds_tagl = cpu_to_le16(len);
desc->dptr = cpu_to_le32(dma_addr);
@@ -1525,9 +1594,11 @@ static netdev_tx_t ravb_start_xmit(struct sk_buff *skb, struct net_device *ndev)
if (q == RAVB_NC) {
ts_skb = kmalloc(sizeof(*ts_skb), GFP_ATOMIC);
if (!ts_skb) {
- desc--;
- dma_unmap_single(ndev->dev.parent, dma_addr, len,
- DMA_TO_DEVICE);
+ if (num_tx_desc >= 2) {
+ desc--;
+ dma_unmap_single(ndev->dev.parent, dma_addr,
+ len, DMA_TO_DEVICE);
+ }
goto unmap;
}
ts_skb->skb = skb;
@@ -1544,15 +1615,19 @@ static netdev_tx_t ravb_start_xmit(struct sk_buff *skb, struct net_device *ndev)
skb_tx_timestamp(skb);
/* Descriptor type must be set after all the above writes */
dma_wmb();
- desc->die_dt = DT_FEND;
- desc--;
- desc->die_dt = DT_FSTART;
+ if (num_tx_desc >= 2) {
+ desc->die_dt = DT_FEND;
+ desc--;
+ desc->die_dt = DT_FSTART;
+ } else {
+ desc->die_dt = DT_FSINGLE;
+ }
ravb_modify(ndev, TCCR, TCCR_TSRQ0 << q, TCCR_TSRQ0 << q);
- priv->cur_tx[q] += NUM_TX_DESC;
+ priv->cur_tx[q] += num_tx_desc;
if (priv->cur_tx[q] - priv->dirty_tx[q] >
- (priv->num_tx_ring[q] - 1) * NUM_TX_DESC && !ravb_tx_free(ndev, q))
+ (priv->num_tx_ring[q] - 1) * num_tx_desc && !ravb_tx_free(ndev, q))
netif_stop_subqueue(ndev, q);
exit:
@@ -1565,7 +1640,7 @@ static netdev_tx_t ravb_start_xmit(struct sk_buff *skb, struct net_device *ndev)
le16_to_cpu(desc->ds_tagl), DMA_TO_DEVICE);
drop:
dev_kfree_skb_any(skb);
- priv->tx_skb[q][entry / NUM_TX_DESC] = NULL;
+ priv->tx_skb[q][entry / num_tx_desc] = NULL;
goto exit;
}
@@ -1833,6 +1908,7 @@ static const struct of_device_id ravb_match_table[] = {
{ .compatible = "renesas,etheravb-r8a7794", .data = (void *)RCAR_GEN2 },
{ .compatible = "renesas,etheravb-rcar-gen2", .data = (void *)RCAR_GEN2 },
{ .compatible = "renesas,etheravb-r8a7795", .data = (void *)RCAR_GEN3 },
+ { .compatible = "renesas,etheravb-r8a7796", .data = (void *)RCAR_GEN3 },
{ .compatible = "renesas,etheravb-rcar-gen3", .data = (void *)RCAR_GEN3 },
{ }
};
@@ -1887,6 +1963,29 @@ static void ravb_set_config_mode(struct net_device *ndev)
}
}
+static void ravb_set_delay_mode(struct net_device *ndev)
+{
+ struct ravb_private *priv = netdev_priv(ndev);
+
+ if (priv->chip_id != RCAR_GEN2) {
+ switch (priv->phy_interface) {
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ ravb_modify(ndev, APSR, APSR_DM, APSR_DM_RDM |
+ APSR_DM_TDM);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ ravb_modify(ndev, APSR, APSR_DM, APSR_DM_RDM);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ ravb_modify(ndev, APSR, APSR_DM, APSR_DM_TDM);
+ break;
+ default:
+ ravb_modify(ndev, APSR, APSR_DM, 0);
+ break;
+ }
+ }
+}
+
static int ravb_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -1984,6 +2083,11 @@ static int ravb_probe(struct platform_device *pdev)
priv->chip_id = chip_id;
+ if (chip_id == RCAR_GEN2)
+ priv->num_tx_desc = 2;
+ else
+ priv->num_tx_desc = 1;
+
/* Set function */
ndev->netdev_ops = &ravb_netdev_ops;
ndev->ethtool_ops = &ravb_ethtool_ops;
@@ -1999,6 +2103,9 @@ static int ravb_probe(struct platform_device *pdev)
/* Request GTI loading */
ravb_modify(ndev, GCCR, GCCR_LTI, GCCR_LTI);
+ /* Set APSR */
+ ravb_set_delay_mode(ndev);
+
/* Allocate descriptor base address table */
priv->desc_bat_size = sizeof(struct ravb_desc) * DBAT_ENTRY_NUM;
priv->desc_bat = dma_alloc_coherent(ndev->dev.parent, priv->desc_bat_size,
@@ -2103,6 +2210,7 @@ static int ravb_remove(struct platform_device *pdev)
static int __maybe_unused ravb_suspend(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
+ struct ravb_private *priv = netdev_priv(ndev);
int ret = 0;
if (netif_running(ndev)) {
@@ -2110,6 +2218,9 @@ static int __maybe_unused ravb_suspend(struct device *dev)
ret = ravb_close(ndev);
}
+ if (priv->chip_id != RCAR_GEN2)
+ ravb_ptp_stop(ndev);
+
return ret;
}
@@ -2117,6 +2228,7 @@ static int __maybe_unused ravb_resume(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
struct ravb_private *priv = netdev_priv(ndev);
+ struct platform_device *pdev = priv->pdev;
int ret = 0;
/* All register have been reset to default values.
@@ -2135,9 +2247,15 @@ static int __maybe_unused ravb_resume(struct device *dev)
/* Request GTI loading */
ravb_modify(ndev, GCCR, GCCR_LTI, GCCR_LTI);
+ /* Set APSR */
+ ravb_set_delay_mode(ndev);
+
/* Restore descriptor base address table */
ravb_write(ndev, priv->desc_bat_dma, DBAT);
+ if (priv->chip_id != RCAR_GEN2)
+ ravb_ptp_init(ndev, pdev);
+
if (netif_running(ndev)) {
ret = ravb_open(ndev);
if (ret < 0)
diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c
index 62700d1..8e3f92f 100644
--- a/drivers/pci/host/pcie-rcar.c
+++ b/drivers/pci/host/pcie-rcar.c
@@ -30,6 +30,7 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
#define PCIECAR 0x000010
#define PCIECCTLR 0x000018
@@ -43,6 +44,7 @@
/* Transfer control */
#define PCIETCTLR 0x02000
+#define DL_DOWN (1 << 3)
#define CFINIT 1
#define PCIETSTR 0x02004
#define DATA_LINK_ACTIVE 1
@@ -91,6 +93,13 @@
#define MACCTLR 0x011058
#define SPEED_CHANGE (1 << 24)
#define SCRAMBLE_DISABLE (1 << 27)
+#define PMSR 0x01105c
+#define L1FAEG (1 << 31)
+#define PM_ENTER_L1RX (1 << 23)
+#define PMSTATE (7 << 16)
+#define PMSTATE_L1 (3 << 16)
+#define PMCTLR 0x011060
+#define L1_INIT (1 << 31)
#define MACS2R 0x011078
#define MACCGSPSETR 0x011084
#define SPCNGRSN (1 << 31)
@@ -139,6 +148,7 @@ static inline struct rcar_msi *to_rcar_msi(struct msi_controller *chip)
return container_of(chip, struct rcar_msi, chip);
}
+
/* Structure representing the PCIe interface */
struct rcar_pcie {
struct device *dev;
@@ -150,6 +160,150 @@ struct rcar_pcie {
struct rcar_msi msi;
};
+static int rcar_pcie_wait_for_dl(struct rcar_pcie *pcie);
+
+#ifdef CONFIG_RCAR_DDR_BACKUP
+
+#define PCIE_BACKUP_REGS(pcie_ip_regs) \
+struct hw_register (pcie_ip_regs)[] = { \
+/* PCIEC transfer control registers */ \
+ {"PCIETCTLR", 0x2000, 32, 0}, \
+ {"PCIEINTER", 0x200C, 32, 0}, \
+ {"PCIEERRFER", 0x2024, 32, 0}, \
+ {"PCIETIER", 0x2030, 32, 0}, \
+ {"PCIEPMSCIER", 0x2038, 32, 0}, \
+ {"PCIEMSIALR", 0x2048, 32, 0}, \
+ {"PCIEMSIAUR", 0x204C, 32, 0}, \
+ {"PCIEMSIIER", 0x2050, 32, 0}, \
+ {"PCIEPRAR0", 0x2080, 32, 0}, \
+ {"PCIEPRAR1", 0x2084, 32, 0}, \
+ {"PCIEPRAR2", 0x2088, 32, 0}, \
+ {"PCIEPRAR3", 0x208C, 32, 0}, \
+ {"PCIEPRAR4", 0x2090, 32, 0}, \
+ {"PCIEPRAR5", 0x2094, 32, 0}, \
+ \
+ /* Local address registers */ \
+ {"PCIELAR0", 0x2200, 32, 0}, \
+ {"PCIELAMR0", 0x2208, 32, 0}, \
+ {"PCIELAR1", 0x2220, 32, 0}, \
+ {"PCIELAMR1", 0x2228, 32, 0}, \
+ {"PCIELAR2", 0x2240, 32, 0}, \
+ {"PCIELAMR2", 0x2248, 32, 0}, \
+ {"PCIELAR3", 0x2260, 32, 0}, \
+ {"PCIELAMR3", 0x2268, 32, 0}, \
+ {"PCIELAR4", 0x2280, 32, 0}, \
+ {"PCIELAMR4", 0x2288, 32, 0}, \
+ {"PCIELAR5", 0x22A0, 32, 0}, \
+ {"PCIELAMR5", 0x22A8, 32, 0}, \
+ \
+ /* PCIEC address registers */ \
+ {"PCIEPALR0", 0x3400, 32, 0}, \
+ {"PCIEPAUR0", 0x3404, 32, 0}, \
+ {"PCIEPAMR0", 0x3408, 32, 0}, \
+ {"PCIEPTCTLR0", 0x340C, 32, 0}, \
+ {"PCIEPALR1", 0x3420, 32, 0}, \
+ {"PCIEPAUR1", 0x3424, 32, 0}, \
+ {"PCIEPAMR1", 0x3428, 32, 0}, \
+ {"PCIEPTCTLR1", 0x342C, 32, 0}, \
+ {"PCIEPALR2", 0x3440, 32, 0}, \
+ {"PCIEPAUR2", 0x3444, 32, 0}, \
+ {"PCIEPAMR2", 0x3448, 32, 0}, \
+ {"PCIEPTCTLR2", 0x344C, 32, 0}, \
+ {"PCIEPALR3", 0x3460, 32, 0}, \
+ {"PCIEPAUR3", 0x3464, 32, 0}, \
+ {"PCIEPAMR3", 0x3468, 32, 0}, \
+ {"PCIEPTCTLR3", 0x346C, 32, 0}, \
+}
+
+static PCIE_BACKUP_REGS(pcie0_ip_regs);
+static PCIE_BACKUP_REGS(pcie1_ip_regs);
+
+static struct rcar_ip pcie0_ip = {
+ .ip_name = "pcie0",
+ .base_addr = 0xFE000000,
+ .size = 0x3470,
+ .reg_count = ARRAY_SIZE(pcie0_ip_regs),
+ .ip_reg = pcie0_ip_regs,
+};
+
+static struct rcar_ip pcie1_ip = {
+ .ip_name = "pcie1",
+ .base_addr = 0xEE800000,
+ .size = 0x3470,
+ .reg_count = ARRAY_SIZE(pcie1_ip_regs),
+ .ip_reg = pcie1_ip_regs,
+};
+
+struct ip_info {
+ const char *name;
+ struct rcar_ip *ip;
+};
+
+static struct ip_info ip_info_tbl[] = {
+ {"fe000000.pcie", &pcie0_ip },
+ {"ee800000.pcie", &pcie1_ip },
+ {NULL, NULL},
+};
+
+static struct rcar_ip *rcar_pcie_get_ip(const char *name)
+{
+ struct ip_info *ip_info = ip_info_tbl;
+ struct rcar_ip *ip = NULL;
+
+ while (ip_info->name) {
+ if (!strcmp(ip_info->name, name)) {
+ ip = ip_info->ip;
+ break;
+ }
+ ip_info++;
+ }
+
+ return ip;
+}
+
+static int rcar_pcie_save_regs(struct device *dev)
+{
+ struct rcar_ip *ip = rcar_pcie_get_ip(dev_name(dev));
+ struct rcar_pcie *pcie = NULL;
+ int ret;
+
+ if (ip) {
+ if (!ip->virt_addr) {
+ pcie = dev_get_drvdata(dev);
+ ip->virt_addr = pcie->base;
+ }
+
+ ret = rcar_handle_registers(ip, DO_BACKUP);
+ if (ret)
+ pr_err("%s: %s: BACKUP failed, ret=%d\n",
+ __func__, dev_name(dev), ret);
+ } else
+ pr_err("%s: Failed to find backup of dev: %s\n\n",
+ __func__, dev_name(dev));
+
+ return 0;
+}
+
+static int rcar_pcie_restore_regs(struct device *dev)
+{
+ struct rcar_ip *ip = rcar_pcie_get_ip(dev_name(dev));
+ int ret = -ENODEV;
+
+ if (ip) {
+ ret = rcar_handle_registers(ip, DO_RESTORE);
+ if (ret)
+ pr_err("%s: %s: RESTORE failed, ret=%d\n",
+ __func__, dev_name(dev), ret);
+
+ } else
+ pr_err("%s: Failed to find backup of dev: %s\n\n",
+ __func__, dev_name(dev));
+
+ return 0;
+}
+
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+
static void rcar_pci_write_reg(struct rcar_pcie *pcie, unsigned long val,
unsigned long reg)
{
@@ -191,6 +345,7 @@ static int rcar_pcie_config_access(struct rcar_pcie *pcie,
unsigned int devfn, int where, u32 *data)
{
int dev, func, reg, index;
+ u32 val;
dev = PCI_SLOT(devfn);
func = PCI_FUNC(devfn);
@@ -232,6 +387,29 @@ static int rcar_pcie_config_access(struct rcar_pcie *pcie,
if (pcie->root_bus_nr < 0)
return PCIBIOS_DEVICE_NOT_FOUND;
+ /*
+ * If we are not in L1 link state and we have received PM_ENTER_L1 DLLP,
+ * transition to L1 link state. The HW will handle coming of of L1.
+ */
+ val = rcar_pci_read_reg(pcie, PMSR);
+
+ if ((val == 0) || (rcar_pci_read_reg(pcie, PCIETCTLR) & DL_DOWN)) {
+ /* Wait PCI Express link is re-initialized */
+ rcar_pci_write_reg(pcie, CFINIT, PCIETCTLR);
+ rcar_pcie_wait_for_dl(pcie);
+ }
+
+ if ((val & PM_ENTER_L1RX) && ((val & PMSTATE) != PMSTATE_L1)) {
+ rcar_pci_write_reg(pcie, L1_INIT, PMCTLR);
+
+ /* Wait until we are in L1 */
+ while (!(val & L1FAEG))
+ val = rcar_pci_read_reg(pcie, PMSR);
+
+ /* Clear flags indicating link has transitioned to L1 */
+ rcar_pci_write_reg(pcie, L1FAEG | PM_ENTER_L1RX, PMSR);
+ }
+
/* Clear errors */
rcar_pci_write_reg(pcie, rcar_pci_read_reg(pcie, PCIEERRFR), PCIEERRFR);
@@ -472,7 +650,7 @@ static int rcar_pcie_enable(struct rcar_pcie *pcie)
return -ENODEV;
}
- pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci);
+ pci_fixup_irqs_local(bus, pci_common_swizzle, of_irq_parse_and_map_pci);
pci_bus_size_bridges(bus);
pci_bus_assign_resources(bus);
@@ -536,7 +714,7 @@ static int rcar_pcie_wait_for_dl(struct rcar_pcie *pcie)
if ((rcar_pci_read_reg(pcie, PCIETSTR) & DATA_LINK_ACTIVE))
return 0;
- msleep(5);
+ mdelay(5);
}
return -ETIMEDOUT;
@@ -915,15 +1093,6 @@ static int rcar_pcie_get_resources(struct rcar_pcie *pcie)
if (IS_ERR(pcie->base))
return PTR_ERR(pcie->base);
- pcie->clk = devm_clk_get(dev, "pcie");
- if (IS_ERR(pcie->clk)) {
- dev_err(dev, "cannot get platform clock\n");
- return PTR_ERR(pcie->clk);
- }
- err = clk_prepare_enable(pcie->clk);
- if (err)
- return err;
-
pcie->bus_clk = devm_clk_get(dev, "pcie_bus");
if (IS_ERR(pcie->bus_clk)) {
dev_err(dev, "cannot get pcie bus clock\n");
@@ -955,7 +1124,6 @@ static int rcar_pcie_get_resources(struct rcar_pcie *pcie)
err_map_reg:
clk_disable_unprepare(pcie->bus_clk);
fail_clk:
- clk_disable_unprepare(pcie->clk);
return err;
}
@@ -1078,6 +1246,7 @@ static const struct of_device_id rcar_pcie_of_match[] = {
{ .compatible = "renesas,pcie-r8a7791",
.data = rcar_pcie_hw_init_gen2 },
{ .compatible = "renesas,pcie-r8a7795", .data = rcar_pcie_hw_init },
+ { .compatible = "renesas,pcie-r8a7796", .data = rcar_pcie_hw_init },
{},
};
@@ -1133,11 +1302,19 @@ static int rcar_pcie_probe(struct platform_device *pdev)
return -ENOMEM;
pcie->dev = dev;
+ platform_set_drvdata(pdev, pcie);
INIT_LIST_HEAD(&pcie->resources);
rcar_pcie_parse_request_of_pci_ranges(pcie);
+ pm_runtime_enable(pcie->dev);
+ err = pm_runtime_get_sync(pcie->dev);
+ if (err < 0) {
+ dev_err(pcie->dev, "pm_runtime_get_sync failed\n");
+ goto err_pm_disable;
+ }
+
err = rcar_pcie_get_resources(pcie);
if (err < 0) {
dev_err(dev, "failed to request resources: %d\n", err);
@@ -1153,18 +1330,11 @@ static int rcar_pcie_probe(struct platform_device *pdev)
return -EINVAL;
hw_init_fn = of_id->data;
- pm_runtime_enable(dev);
- err = pm_runtime_get_sync(dev);
- if (err < 0) {
- dev_err(dev, "pm_runtime_get_sync failed\n");
- goto err_pm_disable;
- }
-
/* Failure to get a link might just be that no cards are inserted */
err = hw_init_fn(pcie);
if (err) {
dev_info(dev, "PCIe link down\n");
- err = 0;
+ err = -ENODEV;
goto err_pm_put;
}
@@ -1195,12 +1365,80 @@ static int rcar_pcie_probe(struct platform_device *pdev)
return err;
}
+#ifdef CONFIG_PM_SLEEP
+static int rcar_pcie_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ ret = rcar_pcie_save_regs(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+
+ return ret;
+}
+
+static int rcar_pcie_resume(struct device *dev)
+{
+ struct rcar_pcie *pcie = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (pcie) {
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ rcar_pcie_restore_regs(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ ret = rcar_pcie_hw_init(pcie);
+ if (ret)
+ pr_debug("%s: %s: re-init hw fail, ret=%d\n",
+ __func__, dev_name(dev), ret);
+ } else
+ pr_warn("%s: %s: pcie NULL\n", __func__, dev_name(dev));
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_pcie_pm_ops,
+ rcar_pcie_suspend,
+ rcar_pcie_resume);
+
+#define DEV_PM_OPS (&rcar_pcie_pm_ops)
+#else /* CONFIG_PM_SLEEP */
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver rcar_pcie_driver = {
.driver = {
.name = "rcar-pcie",
+ .pm = DEV_PM_OPS,
.of_match_table = rcar_pcie_of_match,
.suppress_bind_attrs = true,
},
.probe = rcar_pcie_probe,
};
builtin_platform_driver(rcar_pcie_driver);
+
+static int rcar_pcie_pci_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct device *dev = data;
+
+ switch (action) {
+ case BUS_NOTIFY_BOUND_DRIVER:
+ /* Force the DMA mask to lower 32-bits */
+ dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block device_nb = {
+ .notifier_call = rcar_pcie_pci_notifier,
+};
+
+static int __init register_rcar_pcie_pci_notifier(void)
+{
+ return bus_register_notifier(&pci_bus_type, &device_nb);
+}
+
+arch_initcall(register_rcar_pcie_pci_notifier);
diff --git a/drivers/pci/pcie/pme.c b/drivers/pci/pcie/pme.c
index 884bad5..1ae4c73 100644
--- a/drivers/pci/pcie/pme.c
+++ b/drivers/pci/pcie/pme.c
@@ -10,6 +10,7 @@
* for more details.
*/
+#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/errno.h>
@@ -448,6 +449,17 @@ static int pcie_pme_resume(struct pcie_device *srv)
return 0;
}
+/**
+ * pcie_pme_remove - Prepare PCIe PME service device for removal.
+ * @srv - PCIe service device to remove.
+ */
+static void pcie_pme_remove(struct pcie_device *srv)
+{
+ pcie_pme_suspend(srv);
+ free_irq(srv->irq, srv);
+ kfree(get_service_data(srv));
+}
+
static struct pcie_port_service_driver pcie_pme_driver = {
.name = "pcie_pme",
.port_type = PCI_EXP_TYPE_ROOT_PORT,
@@ -456,6 +468,7 @@ static struct pcie_port_service_driver pcie_pme_driver = {
.probe = pcie_pme_probe,
.suspend = pcie_pme_suspend,
.resume = pcie_pme_resume,
+ .remove = pcie_pme_remove,
};
/**
@@ -465,4 +478,5 @@ static int __init pcie_pme_service_init(void)
{
return pcie_port_service_register(&pcie_pme_driver);
}
-device_initcall(pcie_pme_service_init);
+
+module_init(pcie_pme_service_init);
diff --git a/drivers/pci/setup-irq.c b/drivers/pci/setup-irq.c
index 95c225b..90ea8fa 100644
--- a/drivers/pci/setup-irq.c
+++ b/drivers/pci/setup-irq.c
@@ -22,7 +22,8 @@ void __weak pcibios_update_irq(struct pci_dev *dev, int irq)
pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq);
}
-static void pdev_fixup_irq(struct pci_dev *dev,
+static void pdev_fixup_irq(int domain_nr,
+ struct pci_dev *dev,
u8 (*swizzle)(struct pci_dev *, u8 *),
int (*map_irq)(const struct pci_dev *, u8, u8))
{
@@ -48,8 +49,15 @@ static void pdev_fixup_irq(struct pci_dev *dev,
if (irq == -1)
irq = 0;
}
- dev->irq = irq;
+ /* Since pci_fixup_irqs() can be called more than once due to multiple
+ * host controllers, and we scan all PCI devices, not just those
+ * attached to this controller, make sure we don't clobber dev->irq
+ * that has nothing to do with this domain.
+ */
+ if (domain_nr >= 0 && dev->bus->domain_nr != domain_nr)
+ return;
+ dev->irq = irq;
dev_dbg(&dev->dev, "fixup irq: got %d\n", dev->irq);
/* Always tell the device, so the driver knows what is
@@ -63,6 +71,17 @@ void pci_fixup_irqs(u8 (*swizzle)(struct pci_dev *, u8 *),
struct pci_dev *dev = NULL;
for_each_pci_dev(dev)
- pdev_fixup_irq(dev, swizzle, map_irq);
+ pdev_fixup_irq(-1, dev, swizzle, map_irq);
}
EXPORT_SYMBOL_GPL(pci_fixup_irqs);
+
+void pci_fixup_irqs_local(struct pci_bus *bus,
+ u8 (*swizzle)(struct pci_dev *, u8 *),
+ int (*map_irq)(const struct pci_dev *, u8, u8))
+{
+ struct pci_dev *dev = NULL;
+
+ for_each_pci_dev(dev)
+ pdev_fixup_irq(bus->domain_nr, dev, swizzle, map_irq);
+}
+EXPORT_SYMBOL_GPL(pci_fixup_irqs_local);
diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c
index 80f5bcc..baf1189 100644
--- a/drivers/phy/phy-rcar-gen3-usb2.c
+++ b/drivers/phy/phy-rcar-gen3-usb2.c
@@ -20,6 +20,7 @@
#include <linux/of_address.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>
@@ -417,6 +418,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
if (IS_ERR(channel->base))
return PTR_ERR(channel->base);
+ pm_runtime_enable(dev);
/* call request_irq for OTG */
irq = platform_get_irq(pdev, 0);
if (irq >= 0) {
@@ -478,16 +480,45 @@ static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev)
if (channel->has_otg)
device_remove_file(&pdev->dev, &dev_attr_otg_inputs);
+ pm_runtime_disable(&pdev->dev);
return 0;
-};
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rcar_gen3_phy_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int rcar_gen3_phy_resume(struct device *dev)
+{
+ int ret = 0;
+
+ if (to_phy(dev))
+ ret = rcar_gen3_phy_usb2_init(to_phy(dev));
+ else {
+ pr_warn("%s: No phy dev\n", __func__);
+ ret = -ENODEV;
+ }
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_gen3_phy_pm_ops,
+ rcar_gen3_phy_suspend, rcar_gen3_phy_resume);
+#define DEV_PM_OPS (&rcar_gen3_phy_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
static struct platform_driver rcar_gen3_phy_usb2_driver = {
.driver = {
.name = "phy_rcar_gen3_usb2",
+ .pm = DEV_PM_OPS,
.of_match_table = rcar_gen3_phy_usb2_match_table,
},
.probe = rcar_gen3_phy_usb2_probe,
- .remove = rcar_gen3_phy_usb2_remove,
+ .remove = rcar_gen3_phy_usb2_remove,
};
module_platform_driver(rcar_gen3_phy_usb2_driver);
diff --git a/drivers/pinctrl/sh-pfc/core.c b/drivers/pinctrl/sh-pfc/core.c
index f3a8897..897d093 100644
--- a/drivers/pinctrl/sh-pfc/core.c
+++ b/drivers/pinctrl/sh-pfc/core.c
@@ -25,9 +25,94 @@
#include <linux/pinctrl/machine.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
#include "core.h"
+#ifdef CONFIG_RCAR_DDR_BACKUP
+static struct hw_register pfc_ip_regs[] = {
+ {"PMMR", 0x0000, 32, 0},
+ {"GPSR0", 0x0100, 32, 0},
+ {"GPSR1", 0x0104, 32, 0},
+ {"GPSR2", 0x0108, 32, 0},
+ {"GPSR3", 0x010C, 32, 0},
+ {"GPSR4", 0x0110, 32, 0},
+ {"GPSR5", 0x0114, 32, 0},
+ {"GPSR6", 0x0118, 32, 0},
+ {"GPSR7", 0x011C, 32, 0},
+ {"IPSR0", 0x0200, 32, 0},
+ {"IPSR1", 0x0204, 32, 0},
+ {"IPSR2", 0x0208, 32, 0},
+ {"IPSR3", 0x020C, 32, 0},
+ {"IPSR4", 0x0210, 32, 0},
+ {"IPSR5", 0x0214, 32, 0},
+ {"IPSR6", 0x0218, 32, 0},
+ {"IPSR7", 0x021C, 32, 0},
+ {"IPSR8", 0x0220, 32, 0},
+ {"IPSR9", 0x0224, 32, 0},
+ {"IPSR10", 0x0228, 32, 0},
+ {"IPSR11", 0x022C, 32, 0},
+ {"IPSR12", 0x0230, 32, 0},
+ {"IPSR13", 0x0234, 32, 0},
+ {"IPSR14", 0x0238, 32, 0},
+ {"IPSR15", 0x023C, 32, 0},
+ {"IPSR16", 0x0240, 32, 0},
+ {"IPSR17", 0x0244, 32, 0},
+ {"DRVCTRL0", 0x0300, 32, 0},
+ {"DRVCTRL1", 0x0304, 32, 0},
+ {"DRVCTRL2", 0x0308, 32, 0},
+ {"DRVCTRL3", 0x030C, 32, 0},
+ {"DRVCTRL4", 0x0310, 32, 0},
+ {"DRVCTRL5", 0x0314, 32, 0},
+ {"DRVCTRL6", 0x0318, 32, 0},
+ {"DRVCTRL7", 0x031C, 32, 0},
+ {"DRVCTRL8", 0x0320, 32, 0},
+ {"DRVCTRL9", 0x0324, 32, 0},
+ {"DRVCTRL10", 0x0328, 32, 0},
+ {"DRVCTRL11", 0x032C, 32, 0},
+ {"DRVCTRL12", 0x0330, 32, 0},
+ {"DRVCTRL13", 0x0334, 32, 0},
+ {"DRVCTRL14", 0x0338, 32, 0},
+ {"DRVCTRL15", 0x033C, 32, 0},
+ {"DRVCTRL16", 0x0340, 32, 0},
+ {"DRVCTRL17", 0x0344, 32, 0},
+ {"DRVCTRL18", 0x0348, 32, 0},
+ {"DRVCTRL19", 0x034C, 32, 0},
+ {"DRVCTRL20", 0x0350, 32, 0},
+ {"DRVCTRL21", 0x0354, 32, 0},
+ {"DRVCTRL22", 0x0358, 32, 0},
+ {"DRVCTRL23", 0x035C, 32, 0},
+ {"DRVCTRL24", 0x0360, 32, 0},
+ {"PCCTRL0", 0x0380, 32, 0},
+ {"TDSELCTRL0", 0x03C0, 32, 0},
+ {"PUEN0", 0x0400, 32, 0},
+ {"PUEN1", 0x0404, 32, 0},
+ {"PUEN2", 0x0408, 32, 0},
+ {"PUEN3", 0x040C, 32, 0},
+ {"PUEN4", 0x0410, 32, 0},
+ {"PUEN5", 0x0414, 32, 0},
+ {"PUEN6", 0x0418, 32, 0},
+ {"PUD0", 0x0440, 32, 0},
+ {"PUD1", 0x0444, 32, 0},
+ {"PUD2", 0x0448, 32, 0},
+ {"PUD3", 0x044C, 32, 0},
+ {"PUD4", 0x0450, 32, 0},
+ {"PUD5", 0x0454, 32, 0},
+ {"PUD6", 0x0458, 32, 0},
+ {"MOD_SEL0", 0x0500, 32, 0},
+ {"MOD_SEL1", 0x0504, 32, 0},
+ {"MOD_SEL2", 0x0508, 32, 0},
+};
+
+static struct rcar_ip pfc_ip = {
+ .ip_name = "PFC",
+ .base_addr = 0xE6060000,
+ .size = 0x50C,
+ .reg_count = ARRAY_SIZE(pfc_ip_regs),
+ .ip_reg = pfc_ip_regs,
+};
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+
static int sh_pfc_map_resources(struct sh_pfc *pfc,
struct platform_device *pdev)
{
@@ -651,11 +736,46 @@ static const struct platform_device_id sh_pfc_id_table[] = {
{ },
};
+#ifdef CONFIG_PM_SLEEP
+static int sh_pfc_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ pr_debug("%s\n", __func__);
+
+ if (!pfc_ip.virt_addr)
+ ret = rcar_handle_registers(&pfc_ip, DO_IOREMAP);
+ if (ret)
+ return ret;
+
+ ret = rcar_handle_registers(&pfc_ip, DO_BACKUP);
+#endif
+ return ret;
+}
+
+static int sh_pfc_resume(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ pr_debug("%s\n", __func__);
+ ret = rcar_handle_registers(&pfc_ip, DO_RESTORE);
+#endif
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(sh_pfc_pm_ops,
+ sh_pfc_suspend, sh_pfc_resume);
+#define DEV_PM_OPS (&sh_pfc_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver sh_pfc_driver = {
.probe = sh_pfc_probe,
.id_table = sh_pfc_id_table,
.driver = {
.name = DRV_NAME,
+ .pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(sh_pfc_of_table),
},
};
diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c
index 1c85ecc..0bc47fd 100644
--- a/drivers/pwm/pwm-rcar.c
+++ b/drivers/pwm/pwm-rcar.c
@@ -17,8 +17,10 @@
#include <linux/pm_runtime.h>
#include <linux/pwm.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
#define RCAR_PWM_MAX_DIVISION 24
+#define RCAR_PWM_MIN_CYCLE 2
#define RCAR_PWM_MAX_CYCLE 1023
#define RCAR_PWMCR 0x00
@@ -35,6 +37,138 @@
#define RCAR_PWMCNT_PH0_MASK 0x000003ff
#define RCAR_PWMCNT_PH0_SHIFT 0
+#ifdef CONFIG_RCAR_DDR_BACKUP
+/* PWM0 */
+static struct hw_register pwm0_ip_regs[] = {
+ {"PWMCNT", 0x0004, 32, 0},
+ {"PWMCR", 0x0000, 32, 0},
+};
+
+static struct rcar_ip pwm0_ip = {
+ .ip_name = "PWM0",
+ .base_addr = 0xE6E30000,
+ .size = 0x08,
+ .reg_count = ARRAY_SIZE(pwm0_ip_regs),
+ .ip_reg = pwm0_ip_regs,
+};
+
+/* PWM1 */
+static struct hw_register pwm1_ip_regs[] = {
+ {"PWMCNT", 0x0004, 32, 0},
+ {"PWMCR", 0x0000, 32, 0},
+};
+
+static struct rcar_ip pwm1_ip = {
+ .ip_name = "PWM1",
+ .base_addr = 0xE6E31000,
+ .size = 0x08,
+ .reg_count = ARRAY_SIZE(pwm1_ip_regs),
+ .ip_reg = pwm1_ip_regs,
+};
+
+/* PWM2 */
+static struct hw_register pwm2_ip_regs[] = {
+ {"PWMCNT", 0x0004, 32, 0},
+ {"PWMCR", 0x0000, 32, 0},
+};
+
+static struct rcar_ip pwm2_ip = {
+ .ip_name = "PWM2",
+ .base_addr = 0xE6E32000,
+ .size = 0x08,
+ .reg_count = ARRAY_SIZE(pwm2_ip_regs),
+ .ip_reg = pwm2_ip_regs,
+};
+
+/* PWM3 */
+static struct hw_register pwm3_ip_regs[] = {
+ {"PWMCNT", 0x0004, 32, 0},
+ {"PWMCR", 0x0000, 32, 0},
+};
+
+static struct rcar_ip pwm3_ip = {
+ .ip_name = "PWM3",
+ .base_addr = 0xE6E34000,
+ .size = 0x08,
+ .reg_count = ARRAY_SIZE(pwm3_ip_regs),
+ .ip_reg = pwm3_ip_regs,
+};
+
+/* PWM4 */
+static struct hw_register pwm4_ip_regs[] = {
+ {"PWMCNT", 0x0004, 32, 0},
+ {"PWMCR", 0x0000, 32, 0},
+};
+
+static struct rcar_ip pwm4_ip = {
+ .ip_name = "PWM4",
+ .base_addr = 0xE6E34000,
+ .size = 0x08,
+ .reg_count = ARRAY_SIZE(pwm4_ip_regs),
+ .ip_reg = pwm4_ip_regs,
+};
+
+/* PWM5 */
+static struct hw_register pwm5_ip_regs[] = {
+ {"PWMCNT", 0x0004, 32, 0},
+ {"PWMCR", 0x0000, 32, 0},
+};
+
+static struct rcar_ip pwm5_ip = {
+ .ip_name = "PWM5",
+ .base_addr = 0xE6E35000,
+ .size = 0x08,
+ .reg_count = ARRAY_SIZE(pwm5_ip_regs),
+ .ip_reg = pwm5_ip_regs,
+};
+
+/* PWM6 */
+static struct hw_register pwm6_ip_regs[] = {
+ {"PWMCNT", 0x0004, 32, 0},
+ {"PWMCR", 0x0000, 32, 0},
+};
+
+static struct rcar_ip pwm6_ip = {
+ .ip_name = "PWM6",
+ .base_addr = 0xE6E36000,
+ .size = 0x08,
+ .reg_count = ARRAY_SIZE(pwm6_ip_regs),
+ .ip_reg = pwm6_ip_regs,
+};
+
+struct pwm_ip_info {
+ const char *name;
+ struct rcar_ip *ip;
+};
+
+static struct pwm_ip_info ip_info_tbl[] = {
+ {"e6e30000.pwm", &pwm0_ip},
+ {"e6e31000.pwm", &pwm1_ip},
+ {"e6e32000.pwm", &pwm2_ip},
+ {"e6e33000.pwm", &pwm3_ip},
+ {"e6e34000.pwm", &pwm4_ip},
+ {"e6e35000.pwm", &pwm5_ip},
+ {"e6e36000.pwm", &pwm6_ip},
+ {NULL, NULL},
+};
+
+static struct rcar_ip *rcar_pwm_get_ip(const char *name)
+{
+ struct pwm_ip_info *ip_info = ip_info_tbl;
+ struct rcar_ip *ip = NULL;
+
+ while (ip_info->name) {
+ if (!strcmp(ip_info->name, name)) {
+ ip = ip_info->ip;
+ break;
+ }
+ ip_info++;
+ }
+
+ return ip;
+}
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+
struct rcar_pwm_chip {
struct pwm_chip chip;
void __iomem *base;
@@ -71,12 +205,17 @@ static void rcar_pwm_update(struct rcar_pwm_chip *rp, u32 mask, u32 data,
static int rcar_pwm_get_clock_division(struct rcar_pwm_chip *rp, int period_ns)
{
unsigned long clk_rate = clk_get_rate(rp->clk);
- unsigned long long max; /* max cycle / nanoseconds */
+ unsigned long long min, max; /* min, max cycle / nanoseconds */
unsigned int div;
if (clk_rate == 0)
return -EINVAL;
+ min = (unsigned long long)NSEC_PER_SEC * RCAR_PWM_MIN_CYCLE;
+ do_div(min, clk_rate);
+ if (period_ns < min)
+ return -ERANGE;
+
for (div = 0; div <= RCAR_PWM_MAX_DIVISION; div++) {
max = (unsigned long long)NSEC_PER_SEC * RCAR_PWM_MAX_CYCLE *
(1 << div);
@@ -134,16 +273,12 @@ static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns,
static int rcar_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
-
- return clk_prepare_enable(rp->clk);
+ return pm_runtime_get_sync(chip->dev);
}
static void rcar_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
-
- clk_disable_unprepare(rp->clk);
+ pm_runtime_put(chip->dev);
}
static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -157,7 +292,7 @@ static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
return div;
/* Let the core driver set pwm->period if disabled and duty_ns == 0 */
- if (!pwm_is_enabled(pwm) && !duty_ns)
+ if (!pwm_is_enabled(pwm) && !duty_ns && !pwm->state.duty_cycle)
return 0;
rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR);
@@ -258,11 +393,62 @@ static const struct of_device_id rcar_pwm_of_table[] = {
};
MODULE_DEVICE_TABLE(of, rcar_pwm_of_table);
+#ifdef CONFIG_PM_SLEEP
+static int rcar_pwm_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct platform_device *pdev = to_platform_device(dev);
+ struct rcar_ip *ip = rcar_pwm_get_ip(pdev->name);
+
+ if (ip) {
+ struct rcar_pwm_chip *pwm = platform_get_drvdata(pdev);
+
+ if (!ip->virt_addr)
+ ip->virt_addr = pwm->base;
+ pm_runtime_get_sync(dev);
+ ret = rcar_handle_registers(ip, DO_BACKUP);
+ pm_runtime_put(dev);
+ } else {
+ pr_err("%s: Failed to find PWM device\n", __func__);
+ ret = -ENODEV;
+ }
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static int rcar_pwm_resume(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct platform_device *pdev = to_platform_device(dev);
+ struct rcar_ip *ip = rcar_pwm_get_ip(pdev->name);
+
+ if (ip) {
+ pm_runtime_get_sync(dev);
+ ret = rcar_handle_registers(ip, DO_RESTORE);
+ pm_runtime_put(dev);
+ } else {
+ pr_err("%s: Failed to find PWM device\n", __func__);
+ ret = -ENODEV;
+ }
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_pwm_pm_ops,
+ rcar_pwm_suspend, rcar_pwm_resume);
+#define DEV_PM_OPS (&rcar_pwm_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver rcar_pwm_driver = {
.probe = rcar_pwm_probe,
.remove = rcar_pwm_remove,
.driver = {
.name = "pwm-rcar",
+ .pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(rcar_pwm_of_table),
}
};
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 936f7cc..70d5ba2 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -163,6 +163,13 @@
BCM590xx PMUs. This will enable support for the software
controllable LDO/Switching regulators.
+config REGULATOR_BD9571MWV
+ tristate "ROHM BD9571MWV PMIC regulator"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ This driver supports the voltage regulators of BD9571MWV-M.
+
config REGULATOR_DA903X
tristate "Dialog Semiconductor DA9030/DA9034 regulators"
depends on PMIC_DA903X
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 2142a5d..eb179d6 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -23,6 +23,7 @@
obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o
obj-$(CONFIG_REGULATOR_AXP20X) += axp20x-regulator.o
obj-$(CONFIG_REGULATOR_BCM590XX) += bcm590xx-regulator.o
+obj-$(CONFIG_REGULATOR_BD9571MWV) += bd9571mwv.o
obj-$(CONFIG_REGULATOR_DA903X) += da903x.o
obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o
obj-$(CONFIG_REGULATOR_DA9055) += da9055-regulator.o
diff --git a/drivers/regulator/bd9571mwv.c b/drivers/regulator/bd9571mwv.c
new file mode 100644
index 0000000..4cff812
--- /dev/null
+++ b/drivers/regulator/bd9571mwv.c
@@ -0,0 +1,140 @@
+/*
+ * Power Management IC for BD9571MWV-M.
+ *
+ * Copyright (C) 2015 Renesas Electronics Corporation.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+struct bd9571mwv {
+ struct regulator_dev *rdev;
+ struct regmap *regmap;
+};
+
+static const struct regmap_config bd9571mwv_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static struct regulator_ops bd9571mwv_ops = {
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_time_sel = regulator_set_voltage_time_sel,
+ .list_voltage = regulator_list_voltage_linear,
+};
+
+/* Default limits measured in millivolts and milliamps */
+#define BD9571MWV_MIN_MV 600
+#define BD9571MWV_MAX_MV 1100
+#define BD9571MWV_STEP_MV 10
+#define BD9571MWV_SLEWRATE 10000
+
+/* Define Register */
+#define BD9571_DVFS_SETVID 0x54
+#define BD9571_DVFS_SETVID_MASK 0x7F
+
+static const struct regulator_desc bd9571mwv_reg = {
+ .name = "BD9571MWV",
+ .id = 0,
+ .ops = &bd9571mwv_ops,
+ .type = REGULATOR_VOLTAGE,
+ .n_voltages = BD9571MWV_MAX_MV / BD9571MWV_STEP_MV + 1,
+ .min_uV = BD9571MWV_MIN_MV * 1000,
+ .uV_step = BD9571MWV_STEP_MV * 1000,
+ .ramp_delay = BD9571MWV_SLEWRATE,
+ .vsel_reg = BD9571_DVFS_SETVID,
+ .vsel_mask = BD9571_DVFS_SETVID_MASK,
+ .linear_min_sel = BD9571MWV_MIN_MV / BD9571MWV_STEP_MV,
+ .owner = THIS_MODULE,
+};
+
+/*
+ * I2C driver interface functions
+ */
+static int bd9571mwv_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct bd9571mwv *chip;
+ struct device *dev = &i2c->dev;
+ struct regulator_dev *rdev = NULL;
+ struct regulator_config config = { };
+ int error;
+
+ chip = devm_kzalloc(&i2c->dev, sizeof(struct bd9571mwv), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->regmap = devm_regmap_init_i2c(i2c, &bd9571mwv_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ error = PTR_ERR(chip->regmap);
+ dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
+ error);
+ return error;
+ }
+
+ config.dev = &i2c->dev;
+ config.init_data = of_get_regulator_init_data(dev,
+ dev->of_node, &bd9571mwv_reg);
+ config.driver_data = chip;
+ config.regmap = chip->regmap;
+ config.of_node = dev->of_node;
+
+ rdev = devm_regulator_register(&i2c->dev, &bd9571mwv_reg, &config);
+ if (IS_ERR(rdev)) {
+ dev_err(&i2c->dev, "Failed to register BD9571MWV\n");
+ return PTR_ERR(rdev);
+ }
+
+ chip->rdev = rdev;
+
+ i2c_set_clientdata(i2c, chip);
+
+ dev_info(dev, "bd9571mwv probed\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id bd9571mwv_dt_ids[] = {
+ { .compatible = "rohm,bd9571mwv" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bd9571mwv_dt_ids);
+#endif
+
+static const struct i2c_device_id bd9571mwv_i2c_id[] = {
+ {"bd9571mwv", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, bd9571mwv_i2c_id);
+
+static struct i2c_driver bd9571mwv_regulator_driver = {
+ .driver = {
+ .name = "bd9571mwv",
+ .of_match_table = of_match_ptr(bd9571mwv_dt_ids),
+ },
+ .probe = bd9571mwv_i2c_probe,
+ .id_table = bd9571mwv_i2c_id,
+};
+
+module_i2c_driver(bd9571mwv_regulator_driver);
+
+MODULE_AUTHOR("Keita Kobayashi <keita.kobayashi.ym@renesas.com");
+MODULE_DESCRIPTION("Power Management IC for BD9571MWV-M");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c
index 3090b0d..e3f986d 100644
--- a/drivers/rpmsg/virtio_rpmsg_bus.c
+++ b/drivers/rpmsg/virtio_rpmsg_bus.c
@@ -212,6 +212,22 @@ static void __ept_release(struct kref *kref)
kfree(ept);
}
+static inline dma_addr_t msg_dma_address(struct virtproc_info *vrp, void *msg)
+{
+ unsigned long offset = msg - vrp->rbufs;
+
+ return vrp->bufs_dma + offset;
+}
+
+static inline void rpmsg_msg_sg_init(struct virtproc_info *vrp,
+ struct scatterlist *sg,
+ void *msg, unsigned int len)
+{
+ sg_init_table(sg, 1);
+ sg_dma_address(sg) = msg_dma_address(vrp, msg);
+ sg_dma_len(sg) = len;
+}
+
/* for more info, see below documentation of rpmsg_create_ept() */
static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp,
struct rpmsg_device *rpdev,
@@ -604,12 +620,12 @@ static int rpmsg_send_offchannel_raw(struct rpmsg_device *rpdev,
msg, sizeof(*msg) + msg->len, true);
#endif
- sg_init_one(&sg, msg, sizeof(*msg) + len);
+ rpmsg_msg_sg_init(vrp, &sg, msg, sizeof(*msg) + len);
mutex_lock(&vrp->tx_lock);
/* add message to the remote processor's virtqueue */
- err = virtqueue_add_outbuf(vrp->svq, &sg, 1, msg, GFP_KERNEL);
+ err = dma_virtqueue_add_outbuf(vrp->svq, &sg, 1, msg, GFP_KERNEL);
if (err) {
/*
* need to reclaim the buffer here, otherwise it's lost
@@ -729,10 +745,10 @@ static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
dev_warn(dev, "msg received with no recipient\n");
/* publish the real size of the buffer */
- sg_init_one(&sg, msg, RPMSG_BUF_SIZE);
+ rpmsg_msg_sg_init(vrp, &sg, msg, RPMSG_BUF_SIZE);
/* add the buffer back to the remote processor's virtqueue */
- err = virtqueue_add_inbuf(vrp->rvq, &sg, 1, msg, GFP_KERNEL);
+ err = dma_virtqueue_add_inbuf(vrp->rvq, &sg, 1, msg, GFP_KERNEL);
if (err < 0) {
dev_err(dev, "failed to add a virtqueue buffer: %d\n", err);
return err;
@@ -911,9 +927,9 @@ static int rpmsg_probe(struct virtio_device *vdev)
struct scatterlist sg;
void *cpu_addr = vrp->rbufs + i * RPMSG_BUF_SIZE;
- sg_init_one(&sg, cpu_addr, RPMSG_BUF_SIZE);
+ rpmsg_msg_sg_init(vrp, &sg, cpu_addr, RPMSG_BUF_SIZE);
- err = virtqueue_add_inbuf(vrp->rvq, &sg, 1, cpu_addr,
+ err = dma_virtqueue_add_inbuf(vrp->rvq, &sg, 1, cpu_addr,
GFP_KERNEL);
WARN_ON(err); /* sanity check; this can't really happen */
}
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index e6e90e8..b4629e9 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -12,5 +12,6 @@
source "drivers/soc/ti/Kconfig"
source "drivers/soc/ux500/Kconfig"
source "drivers/soc/versatile/Kconfig"
+source "drivers/soc/renesas/Kconfig"
endmenu
diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig
new file mode 100644
index 0000000..a9bdbbe
--- /dev/null
+++ b/drivers/soc/renesas/Kconfig
@@ -0,0 +1,18 @@
+#
+# Renesas SoC drivers
+#
+config RCAR_THERMAL_EMS_ENABLED
+ tristate "Renesas R-Car Gen3 Enable Emergency Shutdown"
+ depends on RCAR_GEN3_THERMAL
+ help
+ Enable this option if you want to have support for Emergency Shutdown
+ in R-Car Gen3.
+
+config RCAR_DDR_BACKUP
+ bool "Renesas R-Car DDR backup/restore function"
+ depends on SUSPEND
+ default y
+ help
+ This enables DDR backup function for R-Car Gen3.
+ It supports to backup/restore module register during suspend/resume
+ sequence respectively when system enters S2RAM.
diff --git a/drivers/soc/renesas/Makefile b/drivers/soc/renesas/Makefile
index 1169aba..c3baa3b 100644
--- a/drivers/soc/renesas/Makefile
+++ b/drivers/soc/renesas/Makefile
@@ -10,3 +10,10 @@
obj-$(CONFIG_ARCH_R8A7794) += rcar-sysc.o r8a7794-sysc.o
obj-$(CONFIG_ARCH_R8A7795) += rcar-sysc.o r8a7795-sysc.o
obj-$(CONFIG_ARCH_R8A7796) += rcar-sysc.o r8a7796-sysc.o
+
+obj-$(CONFIG_ARCH_R8A7795) += rcar-avs.o
+obj-$(CONFIG_ARCH_R8A7796) += rcar-avs.o
+# EMS for R-Car Gen3
+obj-$(CONFIG_ARCH_R8A7795) += rcar_ems_ctrl.o
+obj-$(CONFIG_ARCH_R8A7796) += rcar_ems_ctrl.o
+obj-$(CONFIG_RCAR_DDR_BACKUP) += s2ram_ddr_backup.o
diff --git a/drivers/soc/renesas/rcar-avs.c b/drivers/soc/renesas/rcar-avs.c
new file mode 100644
index 0000000..4b673c5
--- /dev/null
+++ b/drivers/soc/renesas/rcar-avs.c
@@ -0,0 +1,104 @@
+/*
+ * Renesas R-Car AVS Support
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * 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
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+
+#ifdef CONFIG_POWER_AVS
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+/* Change the default opp_table pattern in device tree.
+ * Set opp_pattern_num is default.
+ */
+
+int change_default_opp_pattern(unsigned int opp_pattern_num)
+{
+ struct device_node *cpu_node = NULL;
+
+ __be32 *list, *pp_val;
+ int size;
+ struct property *pp;
+
+ for_each_node_with_property(cpu_node, "operating-points-v2") {
+ pp = of_find_property(cpu_node, "operating-points-v2", &size);
+ if (!pp || !pp->value)
+ return -ENOENT;
+
+ pp_val = pp->value;
+ size = size / sizeof(*pp_val);
+ if (size > opp_pattern_num) {
+ list = kzalloc(sizeof(*pp_val), GFP_KERNEL);
+ if (!list) {
+ pr_debug("%s(): kzalloc fail, return -ENOMEM\n",
+ __func__);
+ return -ENOMEM;
+ }
+ *list = *(pp_val + opp_pattern_num);
+ pp->value = list;
+ }
+ pp->length = sizeof(*list); /* opp fw only accept 1 opp_tb */
+
+ pr_info("rcar-cpufreq: %s is running with: %s\n",
+ of_node_full_name(cpu_node),
+ of_node_full_name(of_find_node_by_phandle(
+ be32_to_cpup(pp->value))));
+ }
+
+ return 0;
+}
+
+/* Get AVS value */
+#define ADVFS_BASE 0xE60A0000
+#define KSEN_ADJCNTS (ADVFS_BASE + 0x13C)
+#define VOLCOND_MASK_0_3 0x0f /* VOLCOND[3:0] */
+
+#define AVS_TABLE_NUM 7
+
+unsigned int get_avs_value(void)
+{
+ unsigned int ret;
+ void __iomem *ksen_adjcnts = ioremap_nocache(KSEN_ADJCNTS, 4);
+ u32 ksen_adjcnts_value = ioread32(ksen_adjcnts);
+
+ ksen_adjcnts_value &= VOLCOND_MASK_0_3;
+ if (ksen_adjcnts_value >= 0 && ksen_adjcnts_value < AVS_TABLE_NUM) {
+ ret = ksen_adjcnts_value;
+ } else {
+ ret = 0;
+ pr_debug("rcar-cpufreq: hw get invalid avs value, use avs_tb0\n");
+ }
+ pr_info("rcar-cpufreq: use avs value: %d\n", ksen_adjcnts_value);
+ iounmap(ksen_adjcnts);
+
+ return ret;
+}
+#endif /* CONFIG_POWER_AVS */
+
+int __init rcar_avs_init(void)
+{
+#ifdef CONFIG_POWER_AVS
+ int avs_val = get_avs_value();
+
+ change_default_opp_pattern(avs_val);
+#endif /* CONFIG_POWER_AVS */
+ return 0;
+}
+
+subsys_initcall(rcar_avs_init);
+
+MODULE_AUTHOR("Renesas Electronics Corporation");
+MODULE_DESCRIPTION("R-Car AVS module");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/renesas/rcar_ems_ctrl.c b/drivers/soc/renesas/rcar_ems_ctrl.c
new file mode 100644
index 0000000..8295365
--- /dev/null
+++ b/drivers/soc/renesas/rcar_ems_ctrl.c
@@ -0,0 +1,308 @@
+/*
+ * R-Car Gen3 Emergency shutdown for thermal management
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation.
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
+#include <linux/thermal.h>
+
+#include <linux/soc/renesas/rcar_ems_ctrl.h>
+
+#define EMS_THERMAL_ZONE_MAX 10
+
+static void rcar_ems_monitor(struct work_struct *ws);
+static DECLARE_DELAYED_WORK(rcar_ems_monitor_work, rcar_ems_monitor);
+
+static RAW_NOTIFIER_HEAD(rcar_ems_chain);
+
+static int ems_mode = RCAR_EMS_MODE_OFF;
+static int ems_mode_on_temp;
+static int ems_mode_off_temp;
+static int ems_poll;
+
+static int thermal_zone_num;
+static struct thermal_zone_device *thermal_zone[EMS_THERMAL_ZONE_MAX];
+
+static int rcar_ems_notify(unsigned long state, void *p)
+{
+ return raw_notifier_call_chain(&rcar_ems_chain, state, p);
+}
+
+int register_rcar_ems_notifier(struct notifier_block *nb)
+{
+ return raw_notifier_chain_register(&rcar_ems_chain, nb);
+}
+EXPORT_SYMBOL(register_rcar_ems_notifier);
+
+void unregister_rcar_ems_notifier(struct notifier_block *nb)
+{
+ raw_notifier_chain_unregister(&rcar_ems_chain, nb);
+}
+EXPORT_SYMBOL(unregister_rcar_ems_notifier);
+
+static void rcar_ems_monitor(struct work_struct *ws)
+{
+ int i, ret;
+ int temp, max_temp;
+
+ max_temp = INT_MIN;
+ for (i = 0; i < thermal_zone_num; i++) {
+ if (thermal_zone[i]) {
+ ret = thermal_zone_get_temp(
+ thermal_zone[i], &temp);
+ if (!ret) {
+ if (max_temp < temp)
+ max_temp = temp;
+ }
+ }
+ }
+
+ if (max_temp == INT_MIN)
+ goto skip;
+
+ if (ems_mode == RCAR_EMS_MODE_OFF) {
+ if (max_temp >= ems_mode_on_temp) {
+ ems_mode = RCAR_EMS_MODE_ON;
+ rcar_ems_notify(RCAR_EMS_MODE_ON,
+ (void *)(long)max_temp);
+ }
+ } else {
+ if (max_temp <= ems_mode_off_temp) {
+ ems_mode = RCAR_EMS_MODE_OFF;
+ rcar_ems_notify(RCAR_EMS_MODE_OFF,
+ (void *)(long)max_temp);
+ }
+ }
+
+skip:
+ schedule_delayed_work(&rcar_ems_monitor_work, ems_poll);
+
+}
+
+
+int rcar_ems_get_mode(void)
+{
+ return ems_mode;
+}
+EXPORT_SYMBOL(rcar_ems_get_mode);
+
+static int __init rcar_ems_ctrl_init(void)
+{
+ struct device_node *np, *c;
+ struct thermal_zone_device *zone;
+ u32 value;
+
+ if (!IS_ENABLED(CONFIG_RCAR_THERMAL_EMS_ENABLED)) {
+ pr_err("thermal emergency: disabled\n");
+ return 0;
+ }
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np)
+ return 0;
+
+ for_each_child_of_node(np, c) {
+ if (!strcmp(c->name, "emergency")) {
+ if (!of_property_read_u32(c,
+ "polling-delay", &value))
+ ems_poll = msecs_to_jiffies(value);
+
+ if (!of_property_read_u32(c,
+ "on-temperature", &value))
+ ems_mode_on_temp = value;
+
+ if (!of_property_read_u32(c,
+ "off-temperature", &value))
+ ems_mode_off_temp = value;
+ } else {
+ zone = thermal_zone_get_zone_by_name(c->name);
+ if (IS_ERR(zone))
+ continue;
+
+ if (thermal_zone_num < EMS_THERMAL_ZONE_MAX) {
+ thermal_zone[thermal_zone_num] = zone;
+ thermal_zone_num++;
+ }
+ }
+ }
+ of_node_put(np);
+
+ if (thermal_zone_num == 0) {
+ pr_err("thermal emergency: not find thermal_zone\n");
+ return 0;
+ }
+
+ if (ems_poll == 0 ||
+ ems_mode_on_temp == 0 || ems_mode_off_temp == 0) {
+ pr_err("thermal emergency: not set value\n");
+ return 0;
+ }
+
+ schedule_delayed_work(&rcar_ems_monitor_work, ems_poll);
+
+ pr_info("thermal emergency: set temperature to %d celsius\n",
+ ems_mode_on_temp / 1000);
+
+ return 0;
+}
+
+static void __exit rcar_ems_ctrl_exit(void)
+{
+ cancel_delayed_work_sync(&rcar_ems_monitor_work);
+}
+
+late_initcall(rcar_ems_ctrl_init)
+module_exit(rcar_ems_ctrl_exit)
+
+
+/* emergency cpu shutdown function */
+static struct cpumask target_cpus;
+static struct cpumask freq_scaled_cpus;
+
+static int rcar_ems_cpufreq_notifier_call(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct cpufreq_policy *policy = data;
+ int mode;
+
+ if (!cpumask_test_cpu(policy->cpu, &freq_scaled_cpus))
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case CPUFREQ_ADJUST:
+ mode = rcar_ems_get_mode();
+ if (mode == RCAR_EMS_MODE_ON) {
+ cpufreq_verify_within_limits(policy,
+ policy->cpuinfo.min_freq,
+ policy->cpuinfo.min_freq);
+ }
+ break;
+
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int rcar_ems_thermal_notifier_call(struct notifier_block *nb,
+ unsigned long state, void *val)
+{
+ long temp = (long)val;
+ int cpu;
+
+ pr_info("thermal emergency notifier: state=%ld (temp=%ld)\n",
+ state, temp);
+
+ switch (state) {
+ case RCAR_EMS_MODE_ON:
+ for_each_cpu(cpu, &target_cpus) {
+ if (cpu_online(cpu))
+ cpu_down(cpu);
+ }
+ break;
+
+ case RCAR_EMS_MODE_OFF:
+ for_each_cpu(cpu, &target_cpus) {
+ if (!cpu_online(cpu))
+ cpu_up(cpu);
+ }
+ break;
+
+ default:
+ return NOTIFY_DONE;
+ }
+
+ cpufreq_update_policy(cpumask_any(&freq_scaled_cpus));
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block ems_thermal_notifier_block = {
+ .notifier_call = rcar_ems_thermal_notifier_call,
+};
+static struct notifier_block ems_cpufreq_notifier_block = {
+ .notifier_call = rcar_ems_cpufreq_notifier_call,
+};
+
+static int __init rcar_ems_cpu_shutdown_init(void)
+{
+ int cpu;
+ struct device_node *cpu_node, *ems_node, *tmp_node;
+ int total_target_cpu, i;
+
+ if (!IS_ENABLED(CONFIG_RCAR_THERMAL_EMS_ENABLED))
+ return 0;
+
+ cpumask_clear(&target_cpus);
+ cpumask_clear(&freq_scaled_cpus);
+
+ ems_node = of_find_node_by_name(NULL, "emergency");
+
+ if (!ems_node)
+ return 0;
+
+ total_target_cpu = of_count_phandle_with_args(ems_node,
+ "target_cpus", 0);
+
+ for_each_online_cpu(cpu) {
+ tmp_node = of_get_cpu_node(cpu, NULL);
+ if (!of_device_is_compatible(tmp_node, "arm,cortex-a57"))
+ continue;
+ for (i = 0; i < total_target_cpu; i++) {
+ cpu_node = of_parse_phandle(ems_node, "target_cpus", i);
+ if (tmp_node == cpu_node) {
+ cpumask_set_cpu(cpu, &target_cpus);
+ break;
+ }
+ }
+
+ if (i == total_target_cpu)
+ cpumask_set_cpu(cpu, &freq_scaled_cpus);
+ }
+
+ if (cpumask_weight(&target_cpus) == 0) {
+ pr_err("thermal emergency: shutdown cpu none\n");
+ return 0;
+ }
+
+ register_rcar_ems_notifier(&ems_thermal_notifier_block);
+ cpufreq_register_notifier(&ems_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+
+ pr_info("thermal emergency: shutdown target cpus %*pbl\n",
+ cpumask_pr_args(&target_cpus));
+ pr_info("thermal emergency: freq scaled target cpus %*pbl\n",
+ cpumask_pr_args(&freq_scaled_cpus));
+
+ return 0;
+}
+
+static void __exit rcar_ems_cpu_shutdown_exit(void)
+{
+ unregister_rcar_ems_notifier(&ems_thermal_notifier_block);
+ cpufreq_unregister_notifier(&ems_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+}
+
+late_initcall(rcar_ems_cpu_shutdown_init);
+module_exit(rcar_ems_cpu_shutdown_exit);
diff --git a/drivers/soc/renesas/s2ram_ddr_backup.c b/drivers/soc/renesas/s2ram_ddr_backup.c
new file mode 100644
index 0000000..818e78b
--- /dev/null
+++ b/drivers/soc/renesas/s2ram_ddr_backup.c
@@ -0,0 +1,327 @@
+/*
+ * S2RAM supports for DDR Power-Supply Backup/Restore Function
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation.
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/sizes.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
+#include <linux/syscore_ops.h>
+#include <linux/io.h>
+
+/* Special code for RWDT write access*/
+#define RWTCNT_CODE 0x5a5a0000
+#define RWTCSRA_CODE 0xa5a5a500
+
+/* INTC-EX */
+static struct hw_register intc_ex_ip_regs[] = {
+ {"CONFIG_00", 0x0180, 32, 0},
+ {"CONFIG_01", 0x0184, 32, 0},
+ {"CONFIG_02", 0x0188, 32, 0},
+ {"CONFIG_03", 0x018C, 32, 0},
+ {"CONFIG_04", 0x0190, 32, 0},
+ {"CONFIG_05", 0x0194, 32, 0},
+};
+
+static struct rcar_ip intc_ex_ip = {
+ .ip_name = "INTC-SYS",
+ .base_addr = 0xE61C0000,
+ .size = 0x198,
+ .reg_count = ARRAY_SIZE(intc_ex_ip_regs),
+ .ip_reg = intc_ex_ip_regs,
+};
+
+/* SYSC */
+static struct hw_register sysc_ip_regs[] = {
+ {"SYSCIER", 0x00C, 32, 0},
+ {"SYSCIMR", 0x010, 32, 0},
+};
+
+static struct rcar_ip sysc_ip = {
+ .ip_name = "SYSC",
+ .base_addr = 0xE6180000,
+ .size = 0x14,
+ .reg_count = ARRAY_SIZE(sysc_ip_regs),
+ .ip_reg = sysc_ip_regs,
+};
+
+static struct rcar_ip *common_ips[] = {
+ &intc_ex_ip,
+ &sysc_ip,
+ NULL,
+};
+
+static unsigned int read_reg(unsigned int size, void __iomem *addr)
+{
+ unsigned int ret = 0;
+
+ switch (size) {
+ case 8:
+ ret = readb_relaxed(addr);
+ break;
+ case 16:
+ ret = readw_relaxed(addr);
+ break;
+ case 32:
+ ret = readl_relaxed(addr);
+ break;
+ default:
+ pr_debug("%s: Wrong access size\n", __func__);
+ break;
+ }
+
+ return ret;
+}
+
+static void write_reg(unsigned int size, unsigned int value,
+ void __iomem *addr)
+{
+ switch (size) {
+ case 8:
+ writeb_relaxed(value, addr);
+ break;
+ case 16:
+ writew_relaxed(value, addr);
+ break;
+ case 32:
+ writel_relaxed(value, addr);
+ break;
+ default:
+ pr_debug("%s: Wrong access size\n", __func__);
+ break;
+ }
+}
+
+static int _do_ioremap(struct rcar_ip *ip)
+{
+ ip->virt_addr = ioremap_nocache(ip->base_addr, ip->size);
+
+ if (ip->virt_addr == NULL) {
+ pr_debug("s2ram ioremap: Could not remap IP register\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+static int _do_backup(struct rcar_ip *ip)
+{
+ struct hw_register *ip_reg;
+ void __iomem *virt_addr = NULL;
+ int reg_count = 0;
+ unsigned int j;
+
+ if (ip->virt_addr == NULL) {
+ pr_debug("s2ram backup: Registers have not been mapped\n");
+ return -EINVAL;
+ }
+
+ ip_reg = ip->ip_reg;
+ reg_count = ip->reg_count;
+ virt_addr = ip->virt_addr;
+
+ pr_debug("s2ram backup: Working with %s, size 0x%x, virt_addr 0x%p\n",
+ ip->ip_name, ip->size, ip->virt_addr);
+
+ for (j = 0; j < reg_count; j++) {
+ pr_debug("%-20s, access_size 0x%-2x, offset 0x%-4x, value 0x%x\n",
+ ip_reg[j].reg_name, ip_reg[j].access_size,
+ ip_reg[j].reg_offset, ip_reg[j].reg_value);
+
+ ip_reg[j].reg_value = read_reg(ip_reg[j].access_size,
+ virt_addr + ip_reg[j].reg_offset);
+
+ pr_debug("%-20s, access_size 0x%-2x, offset 0x%-4x, value 0x%x\n",
+ ip_reg[j].reg_name, ip_reg[j].access_size,
+ ip_reg[j].reg_offset, ip_reg[j].reg_value);
+ }
+
+ return 0;
+}
+
+static int _do_restore(struct rcar_ip *ip)
+{
+ struct hw_register *ip_reg;
+ void __iomem *virt_addr = NULL;
+ int reg_count = 0;
+ unsigned int j;
+
+ if (ip->virt_addr == NULL) {
+ pr_debug("s2ram restore: Registers have not been mapped\n");
+ return -EINVAL;
+ }
+
+ ip_reg = ip->ip_reg;
+ reg_count = ip->reg_count;
+ virt_addr = ip->virt_addr;
+
+ pr_debug("s2ram restore: Working with %s, size 0x%x, virt_addr 0x%p\n",
+ ip->ip_name, ip->size, ip->virt_addr);
+
+ if (strcmp(ip->ip_name, "PFC") && strcmp(ip->ip_name, "RWDT")) {
+ for (j = 0; j < reg_count; j++) {
+ pr_debug("%-20s, access_size 0x%-2x, offset 0x%-4x, value 0x%x\n",
+ ip_reg[j].reg_name,
+ ip_reg[j].access_size,
+ ip_reg[j].reg_offset,
+ ip_reg[j].reg_value);
+
+ write_reg(ip_reg[j].access_size, ip_reg[j].reg_value,
+ virt_addr + ip_reg[j].reg_offset);
+
+ pr_debug("%-20s, access_size 0x%-2x, offset 0x%-4x, value 0x%x\n",
+ ip_reg[j].reg_name,
+ ip_reg[j].access_size,
+ ip_reg[j].reg_offset,
+ read_reg(ip_reg[j].access_size, virt_addr +
+ ip_reg[j].reg_offset));
+ }
+ } else if (!strcmp(ip->ip_name, "RWDT")) {
+ /* For RWDT registers, need special way to write */
+ for (j = 0; j < reg_count; j++) {
+ pr_debug("%-20s, access_size 0x%-2x, offset 0x%-4x, value 0x%x\n",
+ ip_reg[j].reg_name,
+ ip_reg[j].access_size,
+ ip_reg[j].reg_offset,
+ ip_reg[j].reg_value);
+
+ /* Only two registers are backup/restored.
+ * If offset is zero, it is RWTCNT register
+ * and RWTCNT_CODE is used for writing.
+ * Otherwise, RWTCSRA_CODE is used for RWTCSRA register.
+ */
+ writel_relaxed(ip_reg[j].reg_value |
+ (!ip_reg[j].reg_offset ? RWTCNT_CODE
+ : RWTCSRA_CODE),
+ virt_addr + ip_reg[j].reg_offset);
+
+ pr_debug("%-20s, access_size 0x%-2x, offset 0x%-4x, value 0x%x\n",
+ ip_reg[j].reg_name,
+ ip_reg[j].access_size,
+ ip_reg[j].reg_offset,
+ read_reg(ip_reg[j].access_size, virt_addr +
+ ip_reg[j].reg_offset));
+ }
+ } else {
+ /* For PFC registers, need to unlock before writing */
+ for (j = 0; j < reg_count; j++) {
+ pr_debug("%-20s, access_size 0x%-2x, offset 0x%-4x, value 0x%x\n",
+ ip_reg[j].reg_name,
+ ip_reg[j].access_size,
+ ip_reg[j].reg_offset,
+ ip_reg[j].reg_value);
+
+ writel_relaxed(~ip_reg[j].reg_value,
+ virt_addr + ip_reg[0].reg_offset);
+ writel_relaxed(ip_reg[j].reg_value,
+ virt_addr +
+ ip_reg[j].reg_offset);
+ pr_debug("%-20s, access_size 0x%-2x, offset 0x%-4x, value 0x%x\n",
+ ip_reg[j].reg_name,
+ ip_reg[j].access_size,
+ ip_reg[j].reg_offset,
+ readl_relaxed(virt_addr +
+ ip_reg[j].reg_offset));
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Handle backup/restore of IP register
+ * ip: IP to be processed
+ * handling: Flag of processing
+ * DO_IOREMAP: ioremap
+ * DO_BACKUP: backup
+ * DO_RESTORE: restore
+ */
+int rcar_handle_registers(struct rcar_ip *ip, unsigned int handling)
+{
+ int ret = 0;
+
+ switch (handling) {
+ case DO_IOREMAP:
+ ret = _do_ioremap(ip);
+ break;
+ case DO_BACKUP:
+ ret = _do_backup(ip);
+ break;
+ case DO_RESTORE:
+ ret = _do_restore(ip);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(rcar_handle_registers);
+
+/*
+ * Handle backup/restore of IPs
+ * ip: Point to address of IP list to be processed
+ * handling: Flag of processing
+ * DO_IOREMAP: ioremap
+ * DO_BACKUP: backup
+ * DO_RESTORE: restore
+ */
+int rcar_handle_ips(struct rcar_ip **ip, unsigned int handling)
+{
+ struct rcar_ip *working_ip;
+ unsigned int i = 0;
+ unsigned int ret = 0;
+
+ while (ip[i] != NULL) {
+ working_ip = ip[i];
+ ret = rcar_handle_registers(working_ip, handling);
+ i++;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(rcar_handle_ips);
+
+#ifdef CONFIG_PM_SLEEP
+static int ddr_backup_suspend(void)
+{
+ pr_debug("%s\n", __func__);
+
+ return rcar_handle_ips(common_ips, DO_BACKUP);
+}
+
+static void ddr_backup_resume(void)
+{
+ pr_debug("%s\n", __func__);
+
+ rcar_handle_ips(common_ips, DO_RESTORE);
+}
+
+static struct syscore_ops ddr_backup_syscore_ops = {
+ .suspend = ddr_backup_suspend,
+ .resume = ddr_backup_resume,
+};
+
+static int ddr_backup_init(void)
+{
+ int ret;
+
+ /* Map register for all common IPs */
+ ret = rcar_handle_ips(common_ips, DO_IOREMAP);
+
+ register_syscore_ops(&ddr_backup_syscore_ops);
+
+ return ret;
+}
+core_initcall(ddr_backup_init);
+
+#endif /* CONFIG_PM_SLEEP */
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index b799547..fb418d5 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -570,6 +570,26 @@
help
SPI driver for SuperH and SH Mobile MSIOF blocks.
+config SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG
+ bool "Transfer Synchronization Debug support for MSIOF"
+ depends on SPI_SH_MSIOF
+ default n
+ help
+ In data transfer, the slave needs to have completed
+ a transfer preparation before the master.
+ As a test environment, it was to be able to put a sleep wait
+ before the data transfer of the master.
+
+config SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG_MSLEEP
+ int "Master of sleep latency (msec time)"
+ default 1
+ depends on SPI_SH_MSIOF && SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG
+ help
+ Select Sleep latency of the previous data transfer
+ at the time of master mode.
+ Examples:
+ N => N msec
+
config SPI_SH
tristate "SuperH SPI controller"
depends on SUPERH || COMPILE_TEST
diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index 1de3a77..9b9deb1 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -1,6 +1,7 @@
/*
* SuperH MSIOF SPI Master Interface
*
+ * Copyright (C) 2016 Renesas Electronics Corporation
* Copyright (c) 2009 Magnus Damm
* Copyright (C) 2014 Glider bvba
*
@@ -27,19 +28,24 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/sh_dma.h>
+#include <linux/sys_soc.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
#include <linux/spi/sh_msiof.h>
#include <linux/spi/spi.h>
#include <asm/unaligned.h>
-
struct sh_msiof_chipdata {
u16 tx_fifo_size;
u16 rx_fifo_size;
u16 master_flags;
};
+#ifdef CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG
+#define TRANSFAR_SYNC_DELAY (CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG_MSLEEP)
+#endif /* CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG */
+
struct sh_msiof_spi_priv {
struct spi_master *master;
void __iomem *mapbase;
@@ -47,8 +53,10 @@ struct sh_msiof_spi_priv {
struct platform_device *pdev;
struct sh_msiof_spi_info *info;
struct completion done;
+ struct completion done_dma_tx, done_dma_rx;
unsigned int tx_fifo_size;
unsigned int rx_fifo_size;
+ int mode;
void *tx_dma_page;
void *rx_dma_page;
dma_addr_t tx_dma_addr;
@@ -81,6 +89,7 @@ struct sh_msiof_spi_priv {
#define MDR1_SYNCMD_LR 0x30000000 /* L/R mode */
#define MDR1_SYNCAC_SHIFT 25 /* Sync Polarity (1 = Active-low) */
#define MDR1_BITLSB_SHIFT 24 /* MSB/LSB First (1 = LSB first) */
+#define MDR1_DTDL_MASK 0x00700000 /* Data Pin Bit Delay Mask */
#define MDR1_DTDL_SHIFT 20 /* Data Pin Bit Delay for MSIOF_SYNC */
#define MDR1_SYNCDL_SHIFT 16 /* Frame Sync Signal Timing Delay */
#define MDR1_FLD_MASK 0x0000000c /* Frame Sync Signal Interval (0-3) */
@@ -179,6 +188,172 @@ struct sh_msiof_spi_priv {
#define IER_RFUDFE 0x00000010 /* Receive FIFO Underflow Enable */
#define IER_RFOVFE 0x00000008 /* Receive FIFO Overflow Enable */
+static const struct soc_device_attribute r8a7795es10[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.0" },
+ { },
+};
+
+static const struct soc_device_attribute r8a7795es11[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.1" },
+ { },
+};
+
+
+static int msiof_rcar_is_gen3(struct device *dev)
+{
+ struct device_node *node = dev->of_node;
+
+ return of_device_is_compatible(node, "renesas,msiof-r8a7795") ||
+ of_device_is_compatible(node, "renesas,msiof-r8a7796");
+}
+
+#ifdef CONFIG_RCAR_DDR_BACKUP
+static struct hw_register msiof0_ip_regs[] = {
+ {"SITMDR1", 0x00, 32, 0},
+ {"SITMDR2", 0x04, 32, 0},
+ {"SITMDR3", 0x08, 32, 0},
+ {"SIRMDR1", 0x10, 32, 0},
+ {"SIRMDR2", 0x14, 32, 0},
+ {"SIRMDR3", 0x18, 32, 0},
+ {"SITSCR", 0x20, 32, 0},
+ {"SICTR", 0x28, 32, 0},
+ {"SIFCTR", 0x30, 32, 0},
+ {"SIIER", 0x44, 32, 0},
+};
+
+static struct rcar_ip msiof0_ip = {
+ .ip_name = "MSIOF0",
+ .base_addr = 0xE6E90000,
+ .size = 0x48,
+ .reg_count = ARRAY_SIZE(msiof0_ip_regs),
+ .ip_reg = msiof0_ip_regs,
+};
+
+static struct hw_register msiof1_ip_regs[] = {
+ {"SITMDR1", 0x00, 32, 0},
+ {"SITMDR2", 0x04, 32, 0},
+ {"SITMDR3", 0x08, 32, 0},
+ {"SIRMDR1", 0x10, 32, 0},
+ {"SIRMDR2", 0x14, 32, 0},
+ {"SIRMDR3", 0x18, 32, 0},
+ {"SITSCR", 0x20, 32, 0},
+ {"SICTR", 0x28, 32, 0},
+ {"SIFCTR", 0x30, 32, 0},
+ {"SIIER", 0x44, 32, 0},
+};
+
+static struct rcar_ip msiof1_ip = {
+ .ip_name = "MSIOF1",
+ .base_addr = 0xE6EA0000,
+ .size = 0x48,
+ .reg_count = ARRAY_SIZE(msiof1_ip_regs),
+ .ip_reg = msiof1_ip_regs,
+};
+
+static struct hw_register msiof2_ip_regs[] = {
+ {"SITMDR1", 0x00, 32, 0},
+ {"SITMDR2", 0x04, 32, 0},
+ {"SITMDR3", 0x08, 32, 0},
+ {"SIRMDR1", 0x10, 32, 0},
+ {"SIRMDR2", 0x14, 32, 0},
+ {"SIRMDR3", 0x18, 32, 0},
+ {"SITSCR", 0x20, 32, 0},
+ {"SICTR", 0x28, 32, 0},
+ {"SIFCTR", 0x30, 32, 0},
+ {"SIIER", 0x44, 32, 0},
+};
+
+static struct rcar_ip msiof2_ip = {
+ .ip_name = "MSIO2",
+ .base_addr = 0xE6C00000,
+ .size = 0x48,
+ .reg_count = ARRAY_SIZE(msiof2_ip_regs),
+ .ip_reg = msiof2_ip_regs,
+};
+
+static struct hw_register msiof3_ip_regs[] = {
+ {"SITMDR1", 0x00, 32, 0},
+ {"SITMDR2", 0x04, 32, 0},
+ {"SITMDR3", 0x08, 32, 0},
+ {"SIRMDR1", 0x10, 32, 0},
+ {"SIRMDR2", 0x14, 32, 0},
+ {"SIRMDR3", 0x18, 32, 0},
+ {"SITSCR", 0x20, 32, 0},
+ {"SICTR", 0x28, 32, 0},
+ {"SIFCTR", 0x30, 32, 0},
+ {"SIIER", 0x44, 32, 0},
+};
+
+static struct rcar_ip msiof3_ip = {
+ .ip_name = "MSIOF3",
+ .base_addr = 0xE6C10000,
+ .size = 0x48,
+ .reg_count = ARRAY_SIZE(msiof3_ip_regs),
+ .ip_reg = msiof3_ip_regs,
+};
+
+struct msiof_ip_info {
+ const char *name;
+ struct rcar_ip *ip;
+};
+
+static struct msiof_ip_info ip_info_tbl[] = {
+ {"e6e90000.spi", &msiof0_ip},
+ {"e6ea0000.spi", &msiof1_ip},
+ {"e6c00000.spi", &msiof2_ip},
+ {"e6c10000.spi", &msiof3_ip},
+ {NULL, NULL},
+};
+
+static struct rcar_ip *msiof_get_ip(const char *name)
+{
+ struct msiof_ip_info *ip_info = ip_info_tbl;
+ struct rcar_ip *ip = NULL;
+
+ while (ip_info->name) {
+ if (!strcmp(ip_info->name, name)) {
+ ip = ip_info->ip;
+ break;
+ }
+ ip_info++;
+ }
+
+ return ip;
+}
+
+static int msiof_save_regs(struct platform_device *pdev)
+{
+ struct rcar_ip *ip = msiof_get_ip(pdev->name);
+ int ret = -ENODEV;
+
+ if (ip) {
+ struct sh_msiof_spi_priv *priv = platform_get_drvdata(pdev);
+
+ if (!ip->virt_addr)
+ ip->virt_addr = priv->mapbase;
+
+ ret = rcar_handle_registers(ip, DO_BACKUP);
+ pr_debug("%s: Backup %s register\n", __func__, ip->ip_name);
+ } else
+ pr_err("%s: Failed to find MSIOF device\n", __func__);
+
+ return ret;
+}
+
+static int msiof_restore_regs(struct platform_device *pdev)
+{
+ struct rcar_ip *ip = msiof_get_ip(pdev->name);
+ int ret = -ENODEV;
+
+ if (ip) {
+ ret = rcar_handle_registers(ip, DO_RESTORE);
+ pr_debug("%s: Restore %s register\n", __func__, ip->ip_name);
+ } else
+ pr_err("%s: Failed to find MSIOF device\n", __func__);
+
+ return ret;
+}
+#endif /* CONFIG_RCAR_DDR_BACKUP*/
static u32 sh_msiof_read(struct sh_msiof_spi_priv *p, int reg_offs)
{
@@ -337,7 +512,34 @@ static void sh_msiof_spi_set_pin_regs(struct sh_msiof_spi_priv *p,
tmp |= !cs_high << MDR1_SYNCAC_SHIFT;
tmp |= lsb_first << MDR1_BITLSB_SHIFT;
tmp |= sh_msiof_spi_get_dtdl_and_syncdl(p);
- sh_msiof_write(p, TMDR1, tmp | MDR1_TRMD | TMDR1_PCON);
+ if (soc_device_match(r8a7795es10)) {
+ if (p->mode == SPI_MSIOF_MASTER) {
+ tmp &= ~MDR1_DTDL_MASK;
+ tmp |= 0 << MDR1_DTDL_SHIFT;
+ }
+ }
+ if (soc_device_match(r8a7795es11)) {
+ if (p->mode == SPI_MSIOF_MASTER) {
+ tmp &= ~MDR1_DTDL_MASK;
+ tmp |= 1 << MDR1_DTDL_SHIFT;
+ }
+ }
+ if (p->mode == SPI_MSIOF_MASTER)
+ sh_msiof_write(p, TMDR1, tmp | MDR1_TRMD | TMDR1_PCON);
+ else
+ sh_msiof_write(p, TMDR1, tmp | TMDR1_PCON);
+ if (soc_device_match(r8a7795es10)) {
+ if (p->mode == SPI_MSIOF_MASTER) {
+ tmp &= ~MDR1_DTDL_MASK;
+ tmp |= 2 << MDR1_DTDL_SHIFT;
+ }
+ }
+ if (soc_device_match(r8a7795es11)) {
+ if (p->mode == SPI_MSIOF_MASTER) {
+ tmp &= ~MDR1_DTDL_MASK;
+ tmp |= 1 << MDR1_DTDL_SHIFT;
+ }
+ }
if (p->master->flags & SPI_MASTER_MUST_TX) {
/* These bits are reserved if RX needs TX */
tmp &= ~0x0000ffff;
@@ -345,8 +547,18 @@ static void sh_msiof_spi_set_pin_regs(struct sh_msiof_spi_priv *p,
sh_msiof_write(p, RMDR1, tmp);
tmp = 0;
- tmp |= CTR_TSCKIZ_SCK | cpol << CTR_TSCKIZ_POL_SHIFT;
- tmp |= CTR_RSCKIZ_SCK | cpol << CTR_RSCKIZ_POL_SHIFT;
+ if (soc_device_match(r8a7795es11)) {
+ if (p->mode == SPI_MSIOF_MASTER) {
+ tmp |= 0 << CTR_TSCKIZ_POL_SHIFT;
+ tmp |= 0 << CTR_RSCKIZ_POL_SHIFT;
+ } else {
+ tmp |= CTR_TSCKIZ_SCK | cpol << CTR_TSCKIZ_POL_SHIFT;
+ tmp |= CTR_RSCKIZ_SCK | cpol << CTR_RSCKIZ_POL_SHIFT;
+ }
+ } else {
+ tmp |= CTR_TSCKIZ_SCK | cpol << CTR_TSCKIZ_POL_SHIFT;
+ tmp |= CTR_RSCKIZ_SCK | cpol << CTR_RSCKIZ_POL_SHIFT;
+ }
edge = cpol ^ !cpha;
@@ -564,17 +776,18 @@ static int sh_msiof_prepare_message(struct spi_master *master,
static int sh_msiof_spi_start(struct sh_msiof_spi_priv *p, void *rx_buf)
{
- int ret;
+ int ret = 0;
/* setup clock and rx/tx signals */
- ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
+ if (p->mode == SPI_MSIOF_MASTER)
+ ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TSCKE);
if (rx_buf && !ret)
ret = sh_msiof_modify_ctr_wait(p, 0, CTR_RXE);
if (!ret)
ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TXE);
/* start by setting frame bit */
- if (!ret)
+ if (!ret && p->mode == SPI_MSIOF_MASTER)
ret = sh_msiof_modify_ctr_wait(p, 0, CTR_TFSE);
return ret;
@@ -582,15 +795,16 @@ static int sh_msiof_spi_start(struct sh_msiof_spi_priv *p, void *rx_buf)
static int sh_msiof_spi_stop(struct sh_msiof_spi_priv *p, void *rx_buf)
{
- int ret;
+ int ret = 0;
/* shut down frame, rx/tx and clock signals */
- ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
+ if (p->mode == SPI_MSIOF_MASTER)
+ ret = sh_msiof_modify_ctr_wait(p, CTR_TFSE, 0);
if (!ret)
ret = sh_msiof_modify_ctr_wait(p, CTR_TXE, 0);
if (rx_buf && !ret)
ret = sh_msiof_modify_ctr_wait(p, CTR_RXE, 0);
- if (!ret)
+ if (!ret && p->mode == SPI_MSIOF_MASTER)
ret = sh_msiof_modify_ctr_wait(p, CTR_TSCKE, 0);
return ret;
@@ -606,6 +820,9 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
{
int fifo_shift;
int ret;
+ unsigned long timeout;
+
+ timeout = (p->mode == SPI_MSIOF_MASTER) ? HZ : MAX_SCHEDULE_TIMEOUT;
/* limit maximum word transfer to rx/tx fifo size */
if (tx_buf)
@@ -636,7 +853,7 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
}
/* wait for tx fifo to be emptied / rx fifo to be filled */
- if (!wait_for_completion_timeout(&p->done, HZ)) {
+ if (!wait_for_completion_timeout(&p->done, timeout)) {
dev_err(&p->pdev->dev, "PIO timeout\n");
ret = -ETIMEDOUT;
goto stop_reset;
@@ -665,12 +882,18 @@ static int sh_msiof_spi_txrx_once(struct sh_msiof_spi_priv *p,
return ret;
}
-static void sh_msiof_dma_complete(void *arg)
+static void sh_msiof_tx_dma_complete(void *arg)
{
struct sh_msiof_spi_priv *p = arg;
- sh_msiof_write(p, IER, 0);
- complete(&p->done);
+ complete(&p->done_dma_tx);
+}
+
+static void sh_msiof_rx_dma_complete(void *arg)
+{
+ struct sh_msiof_spi_priv *p = arg;
+
+ complete(&p->done_dma_rx);
}
static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx,
@@ -680,6 +903,9 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx,
struct dma_async_tx_descriptor *desc_tx = NULL, *desc_rx = NULL;
dma_cookie_t cookie;
int ret;
+ unsigned long timeout;
+
+ timeout = (p->mode == SPI_MSIOF_MASTER) ? HZ : MAX_SCHEDULE_TIMEOUT;
/* First prepare and submit the DMA request(s), as this may fail */
if (rx) {
@@ -690,7 +916,7 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx,
if (!desc_rx)
return -EAGAIN;
- desc_rx->callback = sh_msiof_dma_complete;
+ desc_rx->callback = sh_msiof_rx_dma_complete;
desc_rx->callback_param = p;
cookie = dmaengine_submit(desc_rx);
if (dma_submit_error(cookie))
@@ -709,13 +935,8 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx,
goto no_dma_tx;
}
- if (rx) {
- /* No callback */
- desc_tx->callback = NULL;
- } else {
- desc_tx->callback = sh_msiof_dma_complete;
- desc_tx->callback_param = p;
- }
+ desc_tx->callback = sh_msiof_tx_dma_complete;
+ desc_tx->callback_param = p;
cookie = dmaengine_submit(desc_tx);
if (dma_submit_error(cookie)) {
ret = cookie;
@@ -732,6 +953,8 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx,
sh_msiof_write(p, IER, ier_bits);
reinit_completion(&p->done);
+ reinit_completion(&p->done_dma_tx);
+ reinit_completion(&p->done_dma_rx);
/* Now start DMA */
if (rx)
@@ -745,12 +968,37 @@ static int sh_msiof_dma_once(struct sh_msiof_spi_priv *p, const void *tx,
goto stop_dma;
}
- /* wait for tx fifo to be emptied / rx fifo to be filled */
- if (!wait_for_completion_timeout(&p->done, HZ)) {
- dev_err(&p->pdev->dev, "DMA timeout\n");
- ret = -ETIMEDOUT;
- goto stop_reset;
+ /* wait for Tx/Rx DMA completion */
+ if (tx) {
+ ret = wait_for_completion_timeout(&p->done_dma_tx, timeout);
+ if (!ret) {
+ dev_err(&p->pdev->dev, "Tx DMA timeout\n");
+ ret = -ETIMEDOUT;
+ goto stop_reset;
+ }
+ if (!rx) {
+ ier_bits = IER_TEOFE;
+ sh_msiof_write(p, IER, ier_bits);
+
+ /* wait for tx fifo to be emptied */
+ if (!wait_for_completion_timeout(&p->done, timeout)) {
+ dev_err(&p->pdev->dev,
+ "Tx fifo to be emptied timeout\n");
+ ret = -ETIMEDOUT;
+ goto stop_reset;
+ }
+ }
}
+ if (rx) {
+ ret = wait_for_completion_timeout(&p->done_dma_rx, timeout);
+ if (!ret) {
+ dev_err(&p->pdev->dev, "Rx DMA timeout\n");
+ ret = -ETIMEDOUT;
+ goto stop_reset;
+ }
+ }
+
+ sh_msiof_write(p, IER, 0);
/* clear status bits */
sh_msiof_reset_str(p);
@@ -843,7 +1091,8 @@ static int sh_msiof_transfer_one(struct spi_master *master,
int ret;
/* setup clocks (clock already enabled in chipselect()) */
- sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk), t->speed_hz);
+ if (p->mode == SPI_MSIOF_MASTER)
+ sh_msiof_spi_set_clk_regs(p, clk_get_rate(p->clk), t->speed_hz);
while (master->dma_tx && len > 15) {
/*
@@ -862,7 +1111,7 @@ static int sh_msiof_transfer_one(struct spi_master *master,
break;
copy32 = copy_bswap32;
} else if (bits <= 16) {
- if (l & 1)
+ if (l & 3)
break;
copy32 = copy_wswap32;
} else {
@@ -872,6 +1121,11 @@ static int sh_msiof_transfer_one(struct spi_master *master,
if (tx_buf)
copy32(p->tx_dma_page, tx_buf, l / 4);
+#ifdef CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG
+ if (p->mode == SPI_MSIOF_MASTER)
+ msleep(TRANSFAR_SYNC_DELAY);
+#endif /* CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG */
+
ret = sh_msiof_dma_once(p, tx_buf, rx_buf, l);
if (ret == -EAGAIN) {
pr_warn_once("%s %s: DMA not available, falling back to PIO\n",
@@ -945,6 +1199,12 @@ static int sh_msiof_transfer_one(struct spi_master *master,
words = len / bytes_per_word;
while (words > 0) {
+
+#ifdef CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG
+ if (p->mode == SPI_MSIOF_MASTER)
+ msleep(TRANSFAR_SYNC_DELAY);
+#endif /* CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG */
+
n = sh_msiof_spi_txrx_once(p, tx_fifo, rx_fifo, tx_buf, rx_buf,
words, bits);
if (n < 0)
@@ -980,6 +1240,8 @@ static const struct of_device_id sh_msiof_match[] = {
{ .compatible = "renesas,msiof-r8a7792", .data = &r8a779x_data },
{ .compatible = "renesas,msiof-r8a7793", .data = &r8a779x_data },
{ .compatible = "renesas,msiof-r8a7794", .data = &r8a779x_data },
+ { .compatible = "renesas,msiof-r8a7795", .data = &r8a779x_data },
+ { .compatible = "renesas,msiof-r8a7796", .data = &r8a779x_data },
{},
};
MODULE_DEVICE_TABLE(of, sh_msiof_match);
@@ -1004,6 +1266,11 @@ static struct sh_msiof_spi_info *sh_msiof_spi_parse_dt(struct device *dev)
of_property_read_u32(np, "renesas,dtdl", &info->dtdl);
of_property_read_u32(np, "renesas,syncdl", &info->syncdl);
+ if (of_property_read_bool(np, "slave"))
+ info->mode = SPI_MSIOF_SLAVE;
+ else
+ info->mode = SPI_MSIOF_MASTER;
+
info->num_chipselect = num_cs;
return info;
@@ -1076,10 +1343,7 @@ static int sh_msiof_request_dma(struct sh_msiof_spi_priv *p)
return 0;
}
- /* The DMA engine uses the second register set, if present */
- res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- if (!res)
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
master = p->master;
master->dma_tx = sh_msiof_request_dma_chan(dev, DMA_MEM_TO_DEV,
@@ -1157,6 +1421,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
const struct sh_msiof_chipdata *chipdata;
const struct of_device_id *of_id;
struct sh_msiof_spi_priv *p;
+ struct clk *ref_clk;
+ u32 clk_rate = 0;
int i;
int ret;
@@ -1187,6 +1453,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
}
init_completion(&p->done);
+ init_completion(&p->done_dma_tx);
+ init_completion(&p->done_dma_rx);
p->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(p->clk)) {
@@ -1226,6 +1494,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
p->tx_fifo_size = p->info->tx_fifo_override;
if (p->info->rx_fifo_override)
p->rx_fifo_size = p->info->rx_fifo_override;
+ p->mode = p->info->mode;
/* init master code */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
@@ -1250,6 +1519,17 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
goto err2;
}
+ if (msiof_rcar_is_gen3(&master->dev)) {
+ ref_clk = devm_clk_get(&pdev->dev, "msiof_ref_clk");
+ if (!IS_ERR(ref_clk))
+ clk_rate = clk_get_rate(ref_clk);
+ if (clk_rate) {
+ clk_prepare_enable(p->clk);
+ clk_set_rate(p->clk, clk_rate);
+ clk_disable_unprepare(p->clk);
+ }
+ }
+
return 0;
err2:
@@ -1275,12 +1555,47 @@ static const struct platform_device_id spi_driver_ids[] = {
};
MODULE_DEVICE_TABLE(platform, spi_driver_ids);
+#ifdef CONFIG_PM_SLEEP
+static int sh_msiof_spi_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct platform_device *pdev = to_platform_device(dev);
+
+ pm_runtime_get_sync(dev);
+ ret = msiof_save_regs(pdev);
+ pm_runtime_put(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static int sh_msiof_spi_resume(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct platform_device *pdev = to_platform_device(dev);
+
+ pm_runtime_get_sync(dev);
+ ret = msiof_restore_regs(pdev);
+ pm_runtime_put(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(sh_msiof_spi_pm_ops,
+ sh_msiof_spi_suspend, sh_msiof_spi_resume);
+#define DEV_PM_OPS (&sh_msiof_spi_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver sh_msiof_spi_drv = {
.probe = sh_msiof_spi_probe,
.remove = sh_msiof_spi_remove,
.id_table = spi_driver_ids,
.driver = {
.name = "spi_sh_msiof",
+ .pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(sh_msiof_match),
},
};
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index 2e05046..928c674 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -4,6 +4,7 @@
* Copyright (C) 2006 SWAPP
* Andrea Paterniani <a.paterniani@swapp-eng.it>
* Copyright (C) 2007 David Brownell (simplification, cleanup)
+ * Copyright (C) 2015 Renesas Electronics Corporation
*
* 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
@@ -696,6 +697,7 @@ static struct class *spidev_class;
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "rohm,dh2228fv" },
{ .compatible = "lineartechnology,ltc2488" },
+ { .compatible = "renesas,sh-msiof" },
{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index a13541b..92db596 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -243,6 +243,15 @@
Enable this to plug the R-Car thermal sensor driver into the Linux
thermal framework.
+config RCAR_GEN3_THERMAL
+ tristate "Renesas R-Car Gen3 thermal driver"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ select SOC_BUS
+ depends on HAS_IOMEM
+ help
+ Enable this to plug the R-Car Gen3 thermal sensor driver into the Linux
+ thermal framework.
+
config KIRKWOOD_THERMAL
tristate "Temperature sensor on Marvell Kirkwood SoCs"
depends on MACH_KIRKWOOD || COMPILE_TEST
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index c92eb22..1216fb3 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -30,6 +30,7 @@
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
+obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o
obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o
obj-y += samsung/
obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o
diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c
new file mode 100644
index 0000000..2e5816b
--- /dev/null
+++ b/drivers/thermal/rcar_gen3_thermal.c
@@ -0,0 +1,626 @@
+/*
+ * R-Car Gen3 THS/CIVM thermal sensor driver
+ * Based on drivers/thermal/rcar_thermal.c
+ *
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation.
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
+#include <linux/sys_soc.h>
+#include <linux/thermal.h>
+
+/* Register offset */
+#define REG_GEN3_CTSR 0x20
+#define REG_GEN3_THCTR 0x20
+#define REG_GEN3_IRQSTR 0x04
+#define REG_GEN3_IRQMSK 0x08
+#define REG_GEN3_IRQCTL 0x0C
+#define REG_GEN3_IRQEN 0x10
+#define REG_GEN3_IRQTEMP1 0x14
+#define REG_GEN3_IRQTEMP2 0x18
+#define REG_GEN3_IRQTEMP3 0x1C
+#define REG_GEN3_TEMP 0x28
+#define REG_GEN3_THCODE1 0x50
+#define REG_GEN3_THCODE2 0x54
+#define REG_GEN3_THCODE3 0x58
+
+#define PTAT_BASE 0xE6198000
+#define REG_GEN3_PTAT1 0x5C
+#define REG_GEN3_PTAT2 0x60
+#define REG_GEN3_PTAT3 0x64
+#define PTAT_SIZE REG_GEN3_PTAT3
+
+/* CTSR bit */
+#define PONM1 (0x1 << 8) /* For H3 WS1.x */
+#define AOUT (0x1 << 7)
+#define THBGR (0x1 << 5)
+#define VMEN (0x1 << 4)
+#define VMST (0x1 << 1)
+#define THSST (0x1 << 0)
+
+/* THCTR bit */
+#define PONM2 (0x1 << 6) /* For H3 WS2.0 and M3 WS1.0 */
+
+#define CTEMP_MASK 0xFFF
+
+#define IRQ_TEMP1_BIT (0x1 << 0)
+#define IRQ_TEMP2_BIT (0x1 << 1)
+#define IRQ_TEMP3_BIT (0x1 << 2)
+#define IRQ_TEMPD1_BIT (0x1 << 3)
+#define IRQ_TEMPD2_BIT (0x1 << 4)
+#define IRQ_TEMPD3_BIT (0x1 << 5)
+
+#define MCELSIUS(temp) ((temp) * 1000)
+#define TEMP_IRQ_SHIFT(tsc_id) (0x1 << tsc_id)
+#define TEMPD_IRQ_SHIFT(tsc_id) (0x1 << (tsc_id + 3))
+#define GEN3_FUSE_MASK 0xFFF
+
+/* Attribute structs describing Salvator-X revisions */
+/* H3 WS1.0 and WS1.1 */
+static const struct soc_device_attribute r8a7795es1[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.*" },
+ {}
+};
+
+/* M3 WS1.0 */
+static const struct soc_device_attribute r8a7796es10[] = {
+ { .soc_id = "r8a7796", .revision = "ES1.0" },
+ {}
+};
+
+/* Equation coefficients for thermal calculation formula.*/
+struct equation_coefs {
+ long a1;
+ long b1;
+ long a2;
+ long b2;
+};
+
+
+struct fuse_factors {
+ int thcode_1;
+ int thcode_2;
+ int thcode_3;
+ int ptat_1;
+ int ptat_2;
+ int ptat_3;
+};
+
+struct rcar_thermal_priv {
+ void __iomem *base;
+ struct device *dev;
+ struct thermal_zone_device *zone;
+ struct delayed_work work;
+ struct fuse_factors factor;
+ struct equation_coefs coef;
+ spinlock_t lock;
+ int id;
+ int irq;
+ const struct rcar_thermal_data *data;
+};
+
+struct rcar_thermal_data {
+ int (*thermal_init)(struct rcar_thermal_priv *priv);
+};
+
+#define rcar_priv_to_dev(priv) ((priv)->dev)
+#define rcar_has_irq_support(priv) ((priv)->irq)
+
+/* Temperature calculation */
+#define CODETSD(x) ((x) * 1000)
+#define TJ_1 96000L
+#define TJ_3 (-41000L)
+
+#define rcar_thermal_read(p, r) _rcar_thermal_read(p, r)
+static u32 _rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg)
+{
+ return ioread32(priv->base + reg);
+}
+
+#define rcar_thermal_write(p, r, d) _rcar_thermal_write(p, r, d)
+static void _rcar_thermal_write(struct rcar_thermal_priv *priv,
+ u32 reg, u32 data)
+{
+ iowrite32(data, priv->base + reg);
+}
+
+static int round_temp(int temp)
+{
+ int tmp1, tmp2;
+ int result = 0;
+
+ tmp1 = abs(temp) % 1000;
+ tmp2 = abs(temp) / 1000;
+
+ if (tmp1 < 250)
+ result = CODETSD(tmp2);
+ else if (tmp1 < 750 && tmp1 >= 250)
+ result = CODETSD(tmp2) + 500;
+ else
+ result = CODETSD(tmp2) + 1000;
+
+ return ((temp < 0) ? (result * (-1)) : result);
+}
+
+static int thermal_read_fuse_factor(struct rcar_thermal_priv *priv)
+{
+ void __iomem *ptat_base;
+
+ ptat_base = ioremap_nocache(PTAT_BASE, PTAT_SIZE);
+ if (!ptat_base) {
+ dev_err(rcar_priv_to_dev(priv), "Cannot map FUSE register\n");
+ return -ENOMEM;
+ }
+
+ /* For H3 WS1.0, H3 WS1.1 and M3 ES1.0
+ * these registers have not been programmed yet.
+ * We will use fixed value as temporary solution.
+ */
+ if (soc_device_match(r8a7795es1)
+ || soc_device_match(r8a7796es10)) {
+ priv->factor.ptat_1 = 2351;
+ priv->factor.ptat_2 = 1509;
+ priv->factor.ptat_3 = 435;
+ switch (priv->id) {
+ case 0:
+ priv->factor.thcode_1 = 3248;
+ priv->factor.thcode_2 = 2800;
+ priv->factor.thcode_3 = 2221;
+ break;
+ case 1:
+ priv->factor.thcode_1 = 3245;
+ priv->factor.thcode_2 = 2795;
+ priv->factor.thcode_3 = 2216;
+ break;
+ case 2:
+ priv->factor.thcode_1 = 3250;
+ priv->factor.thcode_2 = 2805;
+ priv->factor.thcode_3 = 2237;
+ break;
+ }
+ } else {
+ priv->factor.thcode_1 = rcar_thermal_read(priv,
+ REG_GEN3_THCODE1)
+ & GEN3_FUSE_MASK;
+ priv->factor.thcode_2 = rcar_thermal_read(priv,
+ REG_GEN3_THCODE2)
+ & GEN3_FUSE_MASK;
+ priv->factor.thcode_3 = rcar_thermal_read(priv,
+ REG_GEN3_THCODE3)
+ & GEN3_FUSE_MASK;
+ priv->factor.ptat_1 = ioread32(ptat_base + REG_GEN3_PTAT1)
+ & GEN3_FUSE_MASK;
+ priv->factor.ptat_2 = ioread32(ptat_base + REG_GEN3_PTAT2)
+ & GEN3_FUSE_MASK;
+ priv->factor.ptat_3 = ioread32(ptat_base + REG_GEN3_PTAT3)
+ & GEN3_FUSE_MASK;
+ }
+
+ iounmap(ptat_base);
+
+ return 0;
+}
+
+static void thermal_coefficient_calculation(struct rcar_thermal_priv *priv)
+{
+ int tj_2 = 0;
+ long a1, b1;
+ long a2, b2;
+ long a1_num, a1_den;
+ long a2_num, a2_den;
+
+ tj_2 = (CODETSD((priv->factor.ptat_2 - priv->factor.ptat_3) * 137)
+ / (priv->factor.ptat_1 - priv->factor.ptat_3)) - CODETSD(41);
+
+ /*
+ * The following code is to calculate coefficients.
+ */
+ /* Coefficient a1 and b1 */
+ a1_num = CODETSD(priv->factor.thcode_2 - priv->factor.thcode_3);
+ a1_den = tj_2 - TJ_3;
+ a1 = (10000 * a1_num) / a1_den;
+ b1 = (10000 * priv->factor.thcode_3) - ((a1 * TJ_3) / 1000);
+
+ /* Coefficient a2 and b2 */
+ a2_num = CODETSD(priv->factor.thcode_2 - priv->factor.thcode_1);
+ a2_den = tj_2 - TJ_1;
+ a2 = (10000 * a2_num) / a2_den;
+ b2 = (10000 * priv->factor.thcode_1) - ((a2 * TJ_1) / 1000);
+
+ priv->coef.a1 = DIV_ROUND_CLOSEST(a1, 10);
+ priv->coef.b1 = DIV_ROUND_CLOSEST(b1, 10);
+ priv->coef.a2 = DIV_ROUND_CLOSEST(a2, 10);
+ priv->coef.b2 = DIV_ROUND_CLOSEST(b2, 10);
+}
+
+int thermal_temp_converter(struct equation_coefs coef,
+ int temp_code)
+{
+ int temp, temp1, temp2;
+
+ temp1 = MCELSIUS((CODETSD(temp_code) - coef.b1)) / coef.a1;
+ temp2 = MCELSIUS((CODETSD(temp_code) - coef.b2)) / coef.a2;
+ temp = (temp1 + temp2) / 2;
+
+ return round_temp(temp);
+}
+
+int thermal_celsius_to_temp(struct equation_coefs coef,
+ int ctemp)
+{
+ int temp_code, temp1, temp2;
+
+ temp1 = (ctemp * coef.a1 / 1000 + coef.b1) / 1000;
+ temp2 = (ctemp * coef.a2 / 1000 + coef.b2) / 1000;
+ temp_code = (temp1 + temp2) / 2;
+
+ return temp_code;
+}
+
+/*
+ * Zone device functions
+ */
+static int rcar_gen3_thermal_update_temp(struct rcar_thermal_priv *priv)
+{
+ u32 ctemp;
+ unsigned long flags;
+ int temp_cel, temp_code;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ ctemp = rcar_thermal_read(priv, REG_GEN3_TEMP) & CTEMP_MASK;
+ if (rcar_has_irq_support(priv)) {
+ temp_cel = thermal_temp_converter(priv->coef, ctemp);
+
+ /* set the interrupts to exceed the temperature */
+ temp_code = thermal_celsius_to_temp(priv->coef,
+ temp_cel + MCELSIUS(1));
+ rcar_thermal_write(priv, REG_GEN3_IRQTEMP1, temp_code);
+
+ /* set the interrupts to fall below the temperature */
+ temp_code = thermal_celsius_to_temp(priv->coef,
+ temp_cel - MCELSIUS(1));
+ rcar_thermal_write(priv, REG_GEN3_IRQTEMP2, temp_code);
+ }
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int rcar_gen3_thermal_get_temp(void *devdata, int *temp)
+{
+ struct rcar_thermal_priv *priv = devdata;
+ int ctemp;
+ unsigned long flags;
+ u32 ctemp_code;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ ctemp_code = rcar_thermal_read(priv, REG_GEN3_TEMP) & CTEMP_MASK;
+ ctemp = thermal_temp_converter(priv->coef, ctemp_code);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if ((ctemp < MCELSIUS(-40)) || (ctemp > MCELSIUS(125))) {
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ dev_dbg(dev, "Temperature is not measured correctly!\n");
+
+ return -EIO;
+ }
+
+ *temp = ctemp;
+
+ return 0;
+}
+
+static int rcar_gen3_r8a7796_thermal_init(struct rcar_thermal_priv *priv)
+{
+ unsigned long flags;
+ unsigned long reg_val;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ reg_val = rcar_thermal_read(priv, REG_GEN3_THCTR);
+ reg_val &= ~PONM2;
+ rcar_thermal_write(priv, REG_GEN3_THCTR, reg_val);
+ udelay(1000);
+ rcar_thermal_write(priv, REG_GEN3_IRQCTL, 0x3F);
+ rcar_thermal_write(priv, REG_GEN3_IRQEN,
+ IRQ_TEMP1_BIT | IRQ_TEMPD2_BIT);
+ reg_val = rcar_thermal_read(priv, REG_GEN3_THCTR);
+ reg_val |= THSST;
+ rcar_thermal_write(priv, REG_GEN3_THCTR, reg_val);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int rcar_gen3_r8a7795_thermal_init(struct rcar_thermal_priv *priv)
+{
+ unsigned long flags;
+
+ if (soc_device_match(r8a7795es1)) {
+ spin_lock_irqsave(&priv->lock, flags);
+
+ rcar_thermal_write(priv, REG_GEN3_CTSR, THBGR);
+ rcar_thermal_write(priv, REG_GEN3_CTSR, 0x0);
+
+ udelay(1000);
+
+ rcar_thermal_write(priv, REG_GEN3_CTSR, PONM1);
+ rcar_thermal_write(priv, REG_GEN3_IRQCTL, 0x3F);
+ rcar_thermal_write(priv, REG_GEN3_IRQEN,
+ IRQ_TEMP1_BIT | IRQ_TEMPD2_BIT);
+ rcar_thermal_write(priv, REG_GEN3_CTSR,
+ PONM1 | AOUT | THBGR | VMEN);
+ udelay(100);
+
+ rcar_thermal_write(priv, REG_GEN3_CTSR,
+ PONM1 | AOUT | THBGR | VMEN | VMST | THSST);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ } else
+ /* H3 WS2.0 has the same init flow with M3 WS1.0 */
+ rcar_gen3_r8a7796_thermal_init(priv);
+
+ return 0;
+}
+
+/*
+ * Interrupt
+ */
+#define rcar_thermal_irq_enable(p) _rcar_thermal_irq_ctrl(p, 1)
+#define rcar_thermal_irq_disable(p) _rcar_thermal_irq_ctrl(p, 0)
+static void _rcar_thermal_irq_ctrl(struct rcar_thermal_priv *priv, int enable)
+{
+ unsigned long flags;
+
+ if (!rcar_has_irq_support(priv))
+ return;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ rcar_thermal_write(priv, REG_GEN3_IRQMSK,
+ enable ? (IRQ_TEMP1_BIT | IRQ_TEMPD2_BIT) : 0);
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static void rcar_gen3_thermal_work(struct work_struct *work)
+{
+ struct rcar_thermal_priv *priv;
+
+ priv = container_of(work, struct rcar_thermal_priv, work.work);
+
+ rcar_gen3_thermal_update_temp(priv);
+ thermal_zone_device_update(priv->zone, THERMAL_EVENT_UNSPECIFIED);
+
+ rcar_thermal_irq_enable(priv);
+}
+
+static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data)
+{
+ struct rcar_thermal_priv *priv = data;
+ unsigned long flags;
+ int status;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ status = rcar_thermal_read(priv, REG_GEN3_IRQSTR);
+ rcar_thermal_write(priv, REG_GEN3_IRQSTR, 0);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (status == 0)
+ return IRQ_NONE;
+
+ if (status & (IRQ_TEMP1_BIT | IRQ_TEMPD2_BIT)) {
+ rcar_thermal_irq_disable(priv);
+ schedule_delayed_work(&priv->work, 0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = {
+ .get_temp = rcar_gen3_thermal_get_temp,
+};
+
+/*
+ * Platform functions
+ */
+static int rcar_gen3_thermal_remove(struct platform_device *pdev)
+{
+ struct rcar_thermal_priv *priv = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ rcar_thermal_irq_disable(priv);
+ thermal_zone_of_sensor_unregister(dev, priv->zone);
+
+ pm_runtime_put(dev);
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
+static const struct rcar_thermal_data r8a7795_data = {
+ .thermal_init = rcar_gen3_r8a7795_thermal_init,
+};
+
+static const struct rcar_thermal_data r8a7796_data = {
+ .thermal_init = rcar_gen3_r8a7796_thermal_init,
+};
+
+static const struct of_device_id rcar_thermal_dt_ids[] = {
+ { .compatible = "renesas,thermal-r8a7795", .data = &r8a7795_data},
+ { .compatible = "renesas,thermal-r8a7796", .data = &r8a7796_data},
+ {},
+};
+MODULE_DEVICE_TABLE(of, rcar_thermal_dt_ids);
+
+static int rcar_gen3_thermal_probe(struct platform_device *pdev)
+{
+ struct rcar_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct resource *res, *irq;
+ int ret = -ENODEV;
+ int idle;
+ struct device_node *tz_nd, *tmp_nd;
+ int i, irq_cnt;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ priv->dev = dev;
+
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+
+ priv->data = of_device_get_match_data(dev);
+ if (!priv->data)
+ goto error_unregister;
+
+ irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ priv->irq = 0;
+ if (irq) {
+ priv->irq = 1;
+ for_each_node_with_property(tz_nd, "polling-delay") {
+ tmp_nd = of_parse_phandle(tz_nd,
+ "thermal-sensors", 0);
+ if (tmp_nd && !strcmp(tmp_nd->full_name,
+ dev->of_node->full_name)) {
+ of_property_read_u32(tz_nd, "polling-delay",
+ &idle);
+ (idle > 0) ? (priv->irq = 0) :
+ (priv->irq = 1);
+ break;
+ }
+ }
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ goto error_unregister;
+
+ priv->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto error_unregister;
+ }
+
+ spin_lock_init(&priv->lock);
+ INIT_DELAYED_WORK(&priv->work, rcar_gen3_thermal_work);
+
+ priv->id = of_alias_get_id(dev->of_node, "tsc");
+
+ priv->zone = devm_thermal_zone_of_sensor_register(dev, 0, priv,
+ &rcar_gen3_tz_of_ops);
+
+ if (IS_ERR(priv->zone)) {
+ dev_err(dev, "Can't register thermal zone\n");
+ ret = PTR_ERR(priv->zone);
+ priv->zone = NULL;
+ goto error_unregister;
+ }
+
+ priv->data->thermal_init(priv);
+ ret = thermal_read_fuse_factor(priv);
+ if (ret)
+ goto error_unregister;
+ thermal_coefficient_calculation(priv);
+ ret = rcar_gen3_thermal_update_temp(priv);
+
+ if (ret < 0)
+ goto error_unregister;
+
+ rcar_thermal_irq_enable(priv);
+
+ /* Interrupt */
+ if (rcar_has_irq_support(priv)) {
+ irq_cnt = platform_irq_count(pdev);
+ for (i = 0; i < irq_cnt; i++) {
+ irq = platform_get_resource(pdev, IORESOURCE_IRQ, i);
+ ret = devm_request_irq(dev, irq->start,
+ rcar_gen3_thermal_irq,
+ IRQF_SHARED,
+ dev_name(dev), priv);
+ if (ret) {
+ dev_err(dev, "IRQ request failed\n ");
+ goto error_unregister;
+ }
+ }
+ }
+
+ dev_info(dev, "Thermal sensor probed\n");
+
+ return 0;
+
+error_unregister:
+ rcar_gen3_thermal_remove(pdev);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rcar_gen3_thermal_suspend(struct device *dev)
+{
+ struct rcar_thermal_priv *priv = dev_get_drvdata(dev);
+
+ pr_debug("%s\n", __func__);
+ rcar_thermal_irq_disable(priv);
+
+ return 0;
+}
+
+static int rcar_gen3_thermal_resume(struct device *dev)
+{
+ struct rcar_thermal_priv *priv = dev_get_drvdata(dev);
+
+ pr_debug("%s\n", __func__);
+ priv->data->thermal_init(priv);
+ rcar_thermal_irq_enable(priv);
+ rcar_gen3_thermal_update_temp(priv);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops,
+ rcar_gen3_thermal_suspend,
+ rcar_gen3_thermal_resume);
+
+#define DEV_PM_OPS (&rcar_gen3_thermal_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct platform_driver rcar_gen3_thermal_driver = {
+ .driver = {
+ .name = "rcar_gen3_thermal",
+ .pm = DEV_PM_OPS,
+ .of_match_table = rcar_thermal_dt_ids,
+ },
+ .probe = rcar_gen3_thermal_probe,
+ .remove = rcar_gen3_thermal_remove,
+};
+module_platform_driver(rcar_gen3_thermal_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("R-Car Gen3 THS/CIVM driver");
+MODULE_AUTHOR("Renesas Electronics Corporation");
diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c
index 4b26252..9ffde39 100644
--- a/drivers/tty/serial/sh-sci.c
+++ b/drivers/tty/serial/sh-sci.c
@@ -47,6 +47,7 @@
#include <linux/serial_sci.h>
#include <linux/sh_dma.h>
#include <linux/slab.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
#include <linux/string.h>
#include <linux/sysrq.h>
#include <linux/timer.h>
@@ -454,6 +455,307 @@ static const struct plat_sci_reg sci_regmap[SCIx_NR_REGTYPES][SCIx_NR_REGS] = {
#define sci_getreg(up, offset) (sci_regmap[to_sci_port(up)->cfg->regtype] + offset)
+#ifdef CONFIG_RCAR_DDR_BACKUP
+/* SCIF0 save/restore regs */
+static struct hw_register scif0_ip_regs[] = {
+ {"SCSMR", 0x0000, 16, 0},
+ {"SCBRR", 0x0004, 8, 0},
+ {"SCFCR", 0x0018, 16, 0},
+ {"SCSPTR", 0x0020, 16, 0},
+ {"DL", 0x0030, 16, 0},
+ {"CKS", 0x0034, 16, 0},
+ {"SCSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip scif0_ip = {
+ .ip_name = "SCIF0",
+ .base_addr = 0xE6E60000,
+ .size = 0x38,
+ .reg_count = ARRAY_SIZE(scif0_ip_regs),
+ .ip_reg = scif0_ip_regs,
+};
+
+/* SCIF1 save/restore regs */
+static struct hw_register scif1_ip_regs[] = {
+ {"SCSMR", 0x0000, 16, 0},
+ {"SCBRR", 0x0004, 8, 0},
+ {"SCFCR", 0x0018, 16, 0},
+ {"SCSPTR", 0x0020, 16, 0},
+ {"DL", 0x0030, 16, 0},
+ {"CKS", 0x0034, 16, 0},
+ {"SCSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip scif1_ip = {
+ .ip_name = "SCIF1",
+ .base_addr = 0xE6E68000,
+ .size = 0x38,
+ .reg_count = ARRAY_SIZE(scif1_ip_regs),
+ .ip_reg = scif1_ip_regs,
+};
+
+/* SCIF2 save/restore regs */
+static struct hw_register scif2_ip_regs[] = {
+ {"SCSMR", 0x0000, 16, 0},
+ {"SCBRR", 0x0004, 8, 0},
+ {"SCFCR", 0x0018, 16, 0},
+ {"SCSPTR", 0x0020, 16, 0},
+ {"DL", 0x0030, 16, 0},
+ {"CKS", 0x0034, 16, 0},
+ {"SCSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip scif2_ip = {
+ .ip_name = "SCIF2",
+ .base_addr = 0xE6E88000,
+ .size = 0x38,
+ .reg_count = ARRAY_SIZE(scif2_ip_regs),
+ .ip_reg = scif2_ip_regs,
+};
+
+/* SCIF3 save/restore regs */
+static struct hw_register scif3_ip_regs[] = {
+ {"SCSMR", 0x0000, 16, 0},
+ {"SCBRR", 0x0004, 8, 0},
+ {"SCFCR", 0x0018, 16, 0},
+ {"SCSPTR", 0x0020, 16, 0},
+ {"DL", 0x0030, 16, 0},
+ {"CKS", 0x0034, 16, 0},
+ {"SCSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip scif3_ip = {
+ .ip_name = "SCIF3",
+ .base_addr = 0xE6C50000,
+ .size = 0x38,
+ .reg_count = ARRAY_SIZE(scif3_ip_regs),
+ .ip_reg = scif3_ip_regs,
+};
+
+/* SCIF4 save/restore regs */
+static struct hw_register scif4_ip_regs[] = {
+ {"SCSMR", 0x0000, 16, 0},
+ {"SCBRR", 0x0004, 8, 0},
+ {"SCFCR", 0x0018, 16, 0},
+ {"SCSPTR", 0x0020, 16, 0},
+ {"DL", 0x0030, 16, 0},
+ {"CKS", 0x0034, 16, 0},
+ {"SCSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip scif4_ip = {
+ .ip_name = "SCIF4",
+ .base_addr = 0xE6C40000,
+ .size = 0x38,
+ .reg_count = ARRAY_SIZE(scif4_ip_regs),
+ .ip_reg = scif4_ip_regs,
+};
+
+/* SCIF5 save/restore regs */
+static struct hw_register scif5_ip_regs[] = {
+ {"SCSMR", 0x0000, 16, 0},
+ {"SCBRR", 0x0004, 8, 0},
+ {"SCFCR", 0x0018, 16, 0},
+ {"SCSPTR", 0x0020, 16, 0},
+ {"DL", 0x0030, 16, 0},
+ {"CKS", 0x0034, 16, 0},
+ {"SCSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip scif5_ip = {
+ .ip_name = "SCIF5",
+ .base_addr = 0xE6F30000,
+ .size = 0x38,
+ .reg_count = ARRAY_SIZE(scif5_ip_regs),
+ .ip_reg = scif5_ip_regs,
+};
+
+/* HSCIF0 save/restore regs */
+static struct hw_register hscif0_ip_regs[] = {
+ {"HSSMR", 0x0000, 16, 0},
+ {"HSBRR", 0x0004, 8, 0},
+ {"HSFCR", 0x0018, 16, 0},
+ {"HSSPTR", 0x0020, 16, 0},
+ {"HSSRR", 0x0040, 16, 0},
+ {"HSRTGR", 0x0050, 16, 0},
+ {"HSRTRGR", 0x0054, 16, 0},
+ {"HSTTRGR", 0x0058, 16, 0},
+ {"HSSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip hscif0_ip = {
+ .ip_name = "HSCIF0",
+ .base_addr = 0xE6540000,
+ .size = 0x60,
+ .reg_count = ARRAY_SIZE(hscif0_ip_regs),
+ .ip_reg = hscif0_ip_regs,
+};
+
+/* HSCIF1 save/restore regs */
+static struct hw_register hscif1_ip_regs[] = {
+ {"HSSMR", 0x0000, 16, 0},
+ {"HSBRR", 0x0004, 8, 0},
+ {"HSFCR", 0x0018, 16, 0},
+ {"HSSPTR", 0x0020, 16, 0},
+ {"HSSRR", 0x0040, 16, 0},
+ {"HSRTGR", 0x0050, 16, 0},
+ {"HSRTRGR", 0x0054, 16, 0},
+ {"HSTTRGR", 0x0058, 16, 0},
+ {"HSSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip hscif1_ip = {
+ .ip_name = "HSCIF1",
+ .base_addr = 0xE6550000,
+ .size = 0x60,
+ .reg_count = ARRAY_SIZE(hscif1_ip_regs),
+ .ip_reg = hscif1_ip_regs,
+};
+
+/* HSCIF2 save/restore regs */
+static struct hw_register hscif2_ip_regs[] = {
+ {"HSSMR", 0x0000, 16, 0},
+ {"HSBRR", 0x0004, 8, 0},
+ {"HSFCR", 0x0018, 16, 0},
+ {"HSSPTR", 0x0020, 16, 0},
+ {"HSSRR", 0x0040, 16, 0},
+ {"HSRTGR", 0x0050, 16, 0},
+ {"HSRTRGR", 0x0054, 16, 0},
+ {"HSTTRGR", 0x0058, 16, 0},
+ {"HSSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip hscif2_ip = {
+ .ip_name = "HSCIF2",
+ .base_addr = 0xE6560000,
+ .size = 0x60,
+ .reg_count = ARRAY_SIZE(hscif2_ip_regs),
+ .ip_reg = hscif2_ip_regs,
+};
+
+/* HSCIF3 save/restore regs */
+static struct hw_register hscif3_ip_regs[] = {
+ {"HSSMR", 0x0000, 16, 0},
+ {"HSBRR", 0x0004, 8, 0},
+ {"HSFCR", 0x0018, 16, 0},
+ {"HSSPTR", 0x0020, 16, 0},
+ {"HSSRR", 0x0040, 16, 0},
+ {"HSRTGR", 0x0050, 16, 0},
+ {"HSRTRGR", 0x0054, 16, 0},
+ {"HSTTRGR", 0x0058, 16, 0},
+ {"HSSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip hscif3_ip = {
+ .ip_name = "HSCIF3",
+ .base_addr = 0xE66A0000,
+ .size = 0x60,
+ .reg_count = ARRAY_SIZE(hscif3_ip_regs),
+ .ip_reg = hscif3_ip_regs,
+};
+
+/* HSCIF4 save/restore regs */
+static struct hw_register hscif4_ip_regs[] = {
+ {"HSSMR", 0x0000, 16, 0},
+ {"HSBRR", 0x0004, 8, 0},
+ {"HSFCR", 0x0018, 16, 0},
+ {"HSSPTR", 0x0020, 16, 0},
+ {"HSSRR", 0x0040, 16, 0},
+ {"HSRTGR", 0x0050, 16, 0},
+ {"HSRTRGR", 0x0054, 16, 0},
+ {"HSTTRGR", 0x0058, 16, 0},
+ {"HSSCR", 0x0008, 16, 0},
+};
+
+static struct rcar_ip hscif4_ip = {
+ .ip_name = "HSCIF4",
+ .base_addr = 0xE66B0000,
+ .size = 0x60,
+ .reg_count = ARRAY_SIZE(hscif4_ip_regs),
+ .ip_reg = hscif4_ip_regs,
+};
+
+struct h_scif_ip_info {
+ const char *name;
+ struct rcar_ip *ip;
+};
+
+static struct h_scif_ip_info ip_info_tbl[] = {
+ /* SCIF */
+ {"e6e60000.serial", &scif0_ip},
+ {"e6e68000.serial", &scif1_ip},
+ {"e6e88000.serial", &scif2_ip},
+ {"e6c50000.serial", &scif3_ip},
+ {"e6c40000.serial", &scif4_ip},
+ {"e6c30000.serial", &scif5_ip},
+ /* HSCIF */
+ {"e6540000.serial", &hscif0_ip},
+ {"e6550000.serial", &hscif1_ip},
+ {"e6560000.serial", &hscif2_ip},
+ {"e66a0000.serial", &hscif3_ip},
+ {"e66b0000.serial", &hscif4_ip},
+ {NULL, NULL},
+};
+
+static struct rcar_ip *sci_get_ip(const char *name)
+{
+ struct h_scif_ip_info *ip_info = ip_info_tbl;
+ struct rcar_ip *ip = NULL;
+
+ while (ip_info->name) {
+ if (!strcmp(ip_info->name, name)) {
+ ip = ip_info->ip;
+ break;
+ }
+ ip_info++;
+ }
+
+ return ip;
+}
+
+static int sci_save_regs(struct device *dev)
+{
+ struct sci_port *sport = dev_get_drvdata(dev);
+ struct platform_device *pdev;
+ struct rcar_ip *h_scif_ip;
+ int ret = -ENODEV;
+
+ pdev = container_of(dev, struct platform_device, dev);
+ h_scif_ip = sci_get_ip(pdev->name);
+
+ if (h_scif_ip) {
+ if (!h_scif_ip->virt_addr)
+ h_scif_ip->virt_addr = sport->port.membase;
+
+ ret = rcar_handle_registers(h_scif_ip, DO_BACKUP);
+ pr_debug("%s: Backup %s registers\n",
+ __func__, h_scif_ip->ip_name);
+ } else
+ pr_err("%s: No serial found\n", __func__);
+
+ return ret;
+}
+
+static int sci_restore_regs(struct device *dev)
+{
+ struct platform_device *pdev;
+ struct rcar_ip *h_scif_ip;
+ int ret = -ENODEV;
+
+ pdev = container_of(dev, struct platform_device, dev);
+ h_scif_ip = sci_get_ip(pdev->name);
+
+ if (h_scif_ip) {
+ ret = rcar_handle_registers(h_scif_ip, DO_RESTORE);
+ pr_debug("%s: Restore %s registers\n",
+ __func__, h_scif_ip->ip_name);
+ } else
+ pr_err("%s: No serial found\n", __func__);
+
+ return ret;
+}
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+
/*
* The "offset" here is rather misleading, in that it refers to an enum
* value relative to the port mapping rather than the fixed offset
@@ -1142,11 +1444,8 @@ static int sci_dma_rx_push(struct sci_port *s, void *buf, size_t count)
int copied;
copied = tty_insert_flip_string(tport, buf, count);
- if (copied < count) {
- dev_warn(port->dev, "Rx overrun: dropping %zu bytes\n",
- count - copied);
+ if (copied < count)
port->icount.buf_overrun++;
- }
port->icount.rx += copied;
@@ -1161,8 +1460,6 @@ static int sci_dma_rx_find_active(struct sci_port *s)
if (s->active_rx == s->cookie_rx[i])
return i;
- dev_err(s->port.dev, "%s: Rx cookie %d not found!\n", __func__,
- s->active_rx);
return -1;
}
@@ -1223,9 +1520,9 @@ static void sci_dma_rx_complete(void *arg)
dma_async_issue_pending(chan);
+ spin_unlock_irqrestore(&port->lock, flags);
dev_dbg(port->dev, "%s: cookie %d #%d, new active cookie %d\n",
__func__, s->cookie_rx[active], active, s->active_rx);
- spin_unlock_irqrestore(&port->lock, flags);
return;
fail:
@@ -1248,8 +1545,11 @@ static void sci_tx_dma_release(struct sci_port *s, bool enable_pio)
dma_unmap_single(chan->device->dev, s->tx_dma_addr, UART_XMIT_SIZE,
DMA_TO_DEVICE);
dma_release_channel(chan);
- if (enable_pio)
+ if (enable_pio) {
+ spin_lock_irqsave(&port->lock, flags);
sci_start_tx(port);
+ spin_unlock_irqrestore(&port->lock, flags);
+ }
}
static void sci_submit_rx(struct sci_port *s)
@@ -1273,8 +1573,6 @@ static void sci_submit_rx(struct sci_port *s)
if (dma_submit_error(s->cookie_rx[i]))
goto fail;
- dev_dbg(s->port.dev, "%s(): cookie %d to #%d\n", __func__,
- s->cookie_rx[i], i);
}
s->active_rx = s->cookie_rx[0];
@@ -1288,7 +1586,6 @@ static void sci_submit_rx(struct sci_port *s)
for (i = 0; i < 2; i++)
s->cookie_rx[i] = -EINVAL;
s->active_rx = -EINVAL;
- dev_warn(s->port.dev, "Failed to re-start Rx DMA, using PIO\n");
sci_rx_dma_release(s, true);
}
@@ -1358,10 +1655,10 @@ static void rx_timer_fn(unsigned long arg)
int active, count;
u16 scr;
- spin_lock_irqsave(&port->lock, flags);
-
dev_dbg(port->dev, "DMA Rx timed out\n");
+ spin_lock_irqsave(&port->lock, flags);
+
active = sci_dma_rx_find_active(s);
if (active < 0) {
spin_unlock_irqrestore(&port->lock, flags);
@@ -1370,9 +1667,9 @@ static void rx_timer_fn(unsigned long arg)
status = dmaengine_tx_status(s->chan_rx, s->active_rx, &state);
if (status == DMA_COMPLETE) {
+ spin_unlock_irqrestore(&port->lock, flags);
dev_dbg(port->dev, "Cookie %d #%d has already completed\n",
s->active_rx, active);
- spin_unlock_irqrestore(&port->lock, flags);
/* Let packet complete handler take care of the packet */
return;
@@ -1396,8 +1693,6 @@ static void rx_timer_fn(unsigned long arg)
/* Handle incomplete DMA receive */
dmaengine_terminate_all(s->chan_rx);
read = sg_dma_len(&s->sg_rx[active]) - state.residue;
- dev_dbg(port->dev, "Read %u bytes with cookie %d\n", read,
- s->active_rx);
if (read) {
count = sci_dma_rx_push(s, s->rx_buf[active], read);
@@ -1469,8 +1764,7 @@ static void sci_request_dma(struct uart_port *port)
dev_dbg(port->dev, "%s: port %d\n", __func__, port->line);
- if (!port->dev->of_node &&
- (s->cfg->dma_slave_tx <= 0 || s->cfg->dma_slave_rx <= 0))
+ if (s->cfg->dma_slave_tx <= 0 || s->cfg->dma_slave_rx <= 0)
return;
s->cookie_tx = -EINVAL;
@@ -1944,6 +2238,7 @@ static void sci_enable_ms(struct uart_port *port)
static void sci_break_ctl(struct uart_port *port, int break_state)
{
unsigned short scscr, scsptr;
+ unsigned long flags;
/* check wheter the port has SCSPTR */
if (!sci_getreg(port, SCSPTR)->size) {
@@ -1954,6 +2249,7 @@ static void sci_break_ctl(struct uart_port *port, int break_state)
return;
}
+ spin_lock_irqsave(&port->lock, flags);
scsptr = serial_port_in(port, SCSPTR);
scscr = serial_port_in(port, SCSCR);
@@ -1967,6 +2263,7 @@ static void sci_break_ctl(struct uart_port *port, int break_state)
serial_port_out(port, SCSPTR, scsptr);
serial_port_out(port, SCSCR, scscr);
+ spin_unlock_irqrestore(&port->lock, flags);
}
static int sci_startup(struct uart_port *port)
@@ -2178,6 +2475,7 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
int min_err = INT_MAX, err;
unsigned long max_freq = 0;
int best_clk = -1;
+ unsigned long flags;
if ((termios->c_cflag & CSIZE) == CS7)
smr_val |= SCSMR_CHR;
@@ -2287,6 +2585,8 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
serial_port_out(port, SCCKS, sccks);
}
+ spin_lock_irqsave(&port->lock, flags);
+
sci_reset(port);
uart_update_timeout(port, termios->c_cflag, baud);
@@ -2304,9 +2604,6 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
case 27: smr_val |= SCSMR_SRC_27; break;
}
smr_val |= cks;
- dev_dbg(port->dev,
- "SCR 0x%x SMR 0x%x BRR %u CKS 0x%x DL %u SRR %u\n",
- scr_val, smr_val, brr, sccks, dl, srr);
serial_port_out(port, SCSCR, scr_val);
serial_port_out(port, SCSMR, smr_val);
serial_port_out(port, SCBRR, brr);
@@ -2320,7 +2617,6 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
scr_val = s->cfg->scscr & (SCSCR_CKE1 | SCSCR_CKE0);
smr_val |= serial_port_in(port, SCSMR) &
(SCSMR_CKEDG | SCSMR_SRC_MASK | SCSMR_CKS);
- dev_dbg(port->dev, "SCR 0x%x SMR 0x%x\n", scr_val, smr_val);
serial_port_out(port, SCSCR, scr_val);
serial_port_out(port, SCSMR, smr_val);
}
@@ -2352,7 +2648,6 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
}
scr_val |= s->cfg->scscr & ~(SCSCR_CKE1 | SCSCR_CKE0);
- dev_dbg(port->dev, "SCSCR 0x%x\n", scr_val);
serial_port_out(port, SCSCR, scr_val);
if ((srr + 1 == 5) &&
(port->type == PORT_SCIFA || port->type == PORT_SCIFB)) {
@@ -2401,8 +2696,6 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
bits++;
s->rx_timeout = DIV_ROUND_UP((s->buf_len_rx * 2 * bits * HZ) /
(baud / 10), 10);
- dev_dbg(port->dev, "DMA Rx t-out %ums, tty t-out %u jiffies\n",
- s->rx_timeout * 1000 / HZ, port->timeout);
if (s->rx_timeout < msecs_to_jiffies(20))
s->rx_timeout = msecs_to_jiffies(20);
}
@@ -2411,6 +2704,8 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
if ((termios->c_cflag & CREAD) != 0)
sci_start_rx(port);
+ spin_unlock_irqrestore(&port->lock, flags);
+
sci_port_disable(s);
if (UART_ENABLE_MS(port, termios->c_cflag))
@@ -2979,7 +3274,8 @@ sci_parse_dt(struct platform_device *pdev, unsigned int *dev_id)
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match;
struct plat_sci_port *p;
- int id;
+ int id, index;
+ struct of_phandle_args dma_spec;
if (!IS_ENABLED(CONFIG_OF) || !np)
return NULL;
@@ -3009,6 +3305,20 @@ sci_parse_dt(struct platform_device *pdev, unsigned int *dev_id)
if (of_find_property(np, "uart-has-rtscts", NULL))
p->capabilities |= SCIx_HAVE_RTSCTS;
+ index = of_property_match_string(np, "dma-names", "tx");
+ if (index >= 0)
+ index = of_parse_phandle_with_args(np, "dmas", "#dma-cells",
+ index, &dma_spec);
+ if (index >= 0)
+ p->dma_slave_tx = dma_spec.args[0];
+
+ index = of_property_match_string(np, "dma-names", "rx");
+ if (index >= 0)
+ index = of_parse_phandle_with_args(np, "dmas", "#dma-cells",
+ index, &dma_spec);
+ if (index >= 0)
+ p->dma_slave_rx = dma_spec.args[0];
+
return p;
}
@@ -3101,21 +3411,35 @@ static int sci_probe(struct platform_device *dev)
static __maybe_unused int sci_suspend(struct device *dev)
{
struct sci_port *sport = dev_get_drvdata(dev);
+ int ret = 0;
- if (sport)
+ if (sport) {
+ pm_runtime_get_sync(sport->port.dev);
uart_suspend_port(&sci_uart_driver, &sport->port);
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ ret = sci_save_regs(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ pm_runtime_put(sport->port.dev);
+ }
- return 0;
+ return ret;
}
static __maybe_unused int sci_resume(struct device *dev)
{
struct sci_port *sport = dev_get_drvdata(dev);
+ int ret = 0;
- if (sport)
+ if (sport) {
+ pm_runtime_get_sync(sport->port.dev);
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ ret = sci_restore_regs(dev);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
uart_resume_port(&sci_uart_driver, &sport->port);
+ pm_runtime_put(sport->port.dev);
+ }
- return 0;
+ return ret;
}
static SIMPLE_DEV_PM_OPS(sci_dev_pm_ops, sci_suspend, sci_resume);
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 1700908..86612ac 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -72,7 +72,7 @@
static const char hcd_name [] = "ohci_hcd";
#define STATECHANGE_DELAY msecs_to_jiffies(300)
-#define IO_WATCHDOG_DELAY msecs_to_jiffies(250)
+#define IO_WATCHDOG_DELAY msecs_to_jiffies(275)
#include "ohci.h"
#include "pci-quirks.h"
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index ed56bf9..6a07fc3 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -95,7 +95,6 @@ static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = {
};
static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = {
- .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V2,
.init_quirk = xhci_rcar_init_quirk,
.plat_start = xhci_rcar_start,
};
@@ -124,6 +123,9 @@ static const struct of_device_id usb_xhci_of_match[] = {
.compatible = "renesas,xhci-r8a7795",
.data = &xhci_plat_renesas_rcar_gen3,
}, {
+ .compatible = "renesas,xhci-r8a7796",
+ .data = &xhci_plat_renesas_rcar_gen3,
+ }, {
.compatible = "renesas,rcar-gen2-xhci",
.data = &xhci_plat_renesas_rcar_gen2,
}, {
@@ -142,7 +144,9 @@ static int xhci_plat_probe(struct platform_device *pdev)
struct xhci_hcd *xhci;
struct resource *res;
struct usb_hcd *hcd;
+#if (!IS_ENABLED(CONFIG_USB_XHCI_RCAR))
struct clk *clk;
+#endif /* CONFIG_USB_XHCI_RCAR */
int ret;
int irq;
@@ -188,6 +192,10 @@ static int xhci_plat_probe(struct platform_device *pdev)
* Not all platforms have a clk so it is not an error if the
* clock does not exists.
*/
+#if IS_ENABLED(CONFIG_USB_XHCI_RCAR)
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_get_sync(&pdev->dev);
+#else
clk = devm_clk_get(&pdev->dev, NULL);
if (!IS_ERR(clk)) {
ret = clk_prepare_enable(clk);
@@ -197,6 +205,7 @@ static int xhci_plat_probe(struct platform_device *pdev)
ret = -EPROBE_DEFER;
goto put_hcd;
}
+#endif /* CONFIG_USB_XHCI_RCAR */
xhci = hcd_to_xhci(hcd);
match = of_match_node(usb_xhci_of_match, pdev->dev.of_node);
@@ -211,7 +220,9 @@ static int xhci_plat_probe(struct platform_device *pdev)
device_wakeup_enable(hcd->self.controller);
+#if (!IS_ENABLED(CONFIG_USB_XHCI_RCAR))
xhci->clk = clk;
+#endif /* CONFIG_USB_XHCI_RCAR */
xhci->main_hcd = hcd;
xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev,
dev_name(&pdev->dev), hcd);
@@ -259,8 +270,13 @@ static int xhci_plat_probe(struct platform_device *pdev)
usb_put_hcd(xhci->shared_hcd);
disable_clk:
+#if IS_ENABLED(CONFIG_USB_XHCI_RCAR)
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+#else
if (!IS_ERR(clk))
clk_disable_unprepare(clk);
+#endif /* CONFIG_USB_XHCI_RCAR */
put_hcd:
usb_put_hcd(hcd);
@@ -272,7 +288,9 @@ static int xhci_plat_remove(struct platform_device *dev)
{
struct usb_hcd *hcd = platform_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+#if (!IS_ENABLED(CONFIG_USB_XHCI_RCAR))
struct clk *clk = xhci->clk;
+#endif /* CONFIG_USB_XHCI_RCAR */
usb_remove_hcd(xhci->shared_hcd);
usb_phy_shutdown(hcd->usb_phy);
@@ -280,9 +298,15 @@ static int xhci_plat_remove(struct platform_device *dev)
usb_remove_hcd(hcd);
usb_put_hcd(xhci->shared_hcd);
+ usb_put_hcd(hcd);
+
+#if IS_ENABLED(CONFIG_USB_XHCI_RCAR)
+ pm_runtime_put_sync(&dev->dev);
+ pm_runtime_disable(&dev->dev);
+#else
if (!IS_ERR(clk))
clk_disable_unprepare(clk);
- usb_put_hcd(hcd);
+#endif /* CONFIG_USB_XHCI_RCAR */
return 0;
}
@@ -292,7 +316,7 @@ static int xhci_plat_suspend(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-
+ int ret = 0;
/*
* xhci_suspend() needs `do_wakeup` to know whether host is allowed
* to do wakeup during suspend. Since xhci_plat_suspend is currently
@@ -301,7 +325,15 @@ static int xhci_plat_suspend(struct device *dev)
* reconsider this when xhci_plat_suspend enlarges its scope, e.g.,
* also applies to runtime suspend.
*/
- return xhci_suspend(xhci, device_may_wakeup(dev));
+ ret = xhci_suspend(xhci, device_may_wakeup(dev));
+ if (ret)
+ pr_err("%s: xhci_suspend failed, ret: %d\n", __func__, ret);
+
+#if IS_ENABLED(CONFIG_USB_XHCI_RCAR)
+ pm_runtime_put_sync(dev);
+#endif /* CONFIG_USB_XHCI_RCAR */
+
+ return ret;
}
static int xhci_plat_resume(struct device *dev)
@@ -309,6 +341,13 @@ static int xhci_plat_resume(struct device *dev)
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+#if IS_ENABLED(CONFIG_USB_XHCI_RCAR)
+ pm_runtime_get_sync(dev);
+
+ hcd->driver->reset(hcd);
+ hcd->driver->start(hcd);
+#endif /* CONFIG_USB_XHCI_RCAR */
+
return xhci_resume(xhci, 0);
}
diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c
index 0e4535e..d37773b 100644
--- a/drivers/usb/host/xhci-rcar.c
+++ b/drivers/usb/host/xhci-rcar.c
@@ -13,12 +13,16 @@
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/usb/phy.h>
+#include <linux/sys_soc.h>
#include "xhci.h"
#include "xhci-plat.h"
#include "xhci-rcar.h"
/*
+* - The V3 firmware is possible to use on r8a7795 es2.0 or later and
+* r8a7796 es1.1 or later.
+* - The V2 firmware is possible to use on r8a7795 es1.x and r8a7796 es1.0.
* - The V2 firmware is possible to use on R-Car Gen2. However, the V2 causes
* performance degradation. So, this driver continues to use the V1 if R-Car
* Gen2.
@@ -26,6 +30,7 @@
*/
MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1);
MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V2);
+MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V3);
/*** Register Offset ***/
#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */
@@ -64,6 +69,17 @@ MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V2);
#define RCAR_USB3_RX_POL_VAL BIT(21)
#define RCAR_USB3_TX_POL_VAL BIT(4)
+static const struct soc_device_attribute r8a7795es1x[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.*" },
+ { },
+};
+
+static const struct soc_device_attribute r8a7796es10[] = {
+ { .soc_id = "r8a7796", .revision = "ES1.0" },
+ { },
+};
+
+
static void xhci_rcar_start_gen2(struct usb_hcd *hcd)
{
/* LCLK Select */
@@ -92,6 +108,7 @@ static int xhci_rcar_is_gen3(struct device *dev)
struct device_node *node = dev->of_node;
return of_device_is_compatible(node, "renesas,xhci-r8a7795") ||
+ of_device_is_compatible(node, "renesas,xhci-r8a7796") ||
of_device_is_compatible(node, "renesas,rcar-gen3-xhci");
}
@@ -120,7 +137,18 @@ static int xhci_rcar_download_firmware(struct usb_hcd *hcd)
u32 data, val, temp;
/* request R-Car USB3.0 firmware */
- retval = request_firmware(&fw, priv->firmware_name, dev);
+ if (xhci_rcar_is_gen3(hcd->self.controller)) {
+ if (soc_device_match(r8a7795es1x))
+ retval = request_firmware(&fw,
+ XHCI_RCAR_FIRMWARE_NAME_V2, dev);
+ else if (soc_device_match(r8a7796es10))
+ retval = request_firmware(&fw,
+ XHCI_RCAR_FIRMWARE_NAME_V2, dev);
+ else
+ retval = request_firmware(&fw,
+ XHCI_RCAR_FIRMWARE_NAME_V3, dev);
+ } else
+ retval = request_firmware(&fw, priv->firmware_name, dev);
if (retval)
return retval;
@@ -192,5 +220,7 @@ int xhci_rcar_init_quirk(struct usb_hcd *hcd)
xhci_rcar_is_gen3(hcd->self.controller))
xhci->quirks |= XHCI_NO_64BIT_SUPPORT;
+ xhci->quirks |= XHCI_SLOW_SUSPEND;
+
return xhci_rcar_download_firmware(hcd);
}
diff --git a/drivers/usb/host/xhci-rcar.h b/drivers/usb/host/xhci-rcar.h
index 2941a25..d2ffe20 100644
--- a/drivers/usb/host/xhci-rcar.h
+++ b/drivers/usb/host/xhci-rcar.h
@@ -13,6 +13,7 @@
#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem"
#define XHCI_RCAR_FIRMWARE_NAME_V2 "r8a779x_usb3_v2.dlmem"
+#define XHCI_RCAR_FIRMWARE_NAME_V3 "r8a779x_usb3_v3.dlmem"
#if IS_ENABLED(CONFIG_USB_XHCI_RCAR)
void xhci_rcar_start(struct usb_hcd *hcd);
diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c
index 012a37a..4948740 100644
--- a/drivers/usb/renesas_usbhs/common.c
+++ b/drivers/usb/renesas_usbhs/common.c
@@ -735,7 +735,10 @@ static int usbhsc_suspend(struct device *dev)
{
struct usbhs_priv *priv = dev_get_drvdata(dev);
struct usbhs_mod *mod = usbhs_mod_get_current(priv);
+ struct renesas_usbhs_platform_info *info = dev_get_platdata(dev);
+ struct renesas_usbhs_driver_callback *dfunc = &info->driver_callback;
+ dfunc->notify_hotplug = NULL;
if (mod) {
usbhs_mod_call(priv, stop, priv);
usbhs_mod_change(priv, -1);
@@ -751,11 +754,14 @@ static int usbhsc_resume(struct device *dev)
{
struct usbhs_priv *priv = dev_get_drvdata(dev);
struct platform_device *pdev = usbhs_priv_to_pdev(priv);
+ struct renesas_usbhs_platform_info *info = dev_get_platdata(dev);
+ struct renesas_usbhs_driver_callback *dfunc = &info->driver_callback;
if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL))
usbhsc_power_ctrl(priv, 1);
usbhs_platform_call(priv, phy_reset, pdev);
+ dfunc->notify_hotplug = usbhsc_drvcllbck_notify_hotplug;
usbhsc_drvcllbck_notify_hotplug(pdev);
@@ -775,10 +781,9 @@ static int usbhsc_runtime_nop(struct device *dev)
}
static const struct dev_pm_ops usbhsc_pm_ops = {
- .suspend = usbhsc_suspend,
- .resume = usbhsc_resume,
- .runtime_suspend = usbhsc_runtime_nop,
- .runtime_resume = usbhsc_runtime_nop,
+ SET_SYSTEM_SLEEP_PM_OPS(usbhsc_suspend, usbhsc_resume)
+ SET_RUNTIME_PM_OPS(usbhsc_runtime_nop, usbhsc_runtime_nop,
+ NULL)
};
static struct platform_driver renesas_usbhs_driver = {
diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c
index 1d70add..d544b33 100644
--- a/drivers/usb/renesas_usbhs/rcar3.c
+++ b/drivers/usb/renesas_usbhs/rcar3.c
@@ -9,6 +9,7 @@
*
*/
+#include <linux/delay.h>
#include <linux/io.h>
#include "common.h"
#include "rcar3.h"
@@ -35,10 +36,13 @@ static int usbhs_rcar3_power_ctrl(struct platform_device *pdev,
usbhs_write32(priv, UGCTRL2, UGCTRL2_RESERVED_3 | UGCTRL2_USB0SEL_OTG);
- if (enable)
+ if (enable) {
usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM);
- else
+ /* The controller on R-Car Gen3 needs to wait up to 45 usec */
+ udelay(45);
+ } else {
usbhs_bset(priv, LPSTS, LPSTS_SUSPM, 0);
+ }
return 0;
}
diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c
index ed9c9ee..7fd180a 100644
--- a/drivers/virtio/virtio_ring.c
+++ b/drivers/virtio/virtio_ring.c
@@ -257,13 +257,42 @@ static struct vring_desc *alloc_indirect(struct virtqueue *_vq,
return desc;
}
+static inline int vring_desc_set(struct virtqueue *_vq,
+ struct vring_desc *desc,
+ struct scatterlist *sg,
+ unsigned int flags,
+ enum dma_data_direction direction,
+ bool dma)
+{
+ int ret = 0;
+ struct vring_virtqueue *vq = to_vvq(_vq);
+ dma_addr_t addr;
+
+ if (dma)
+ addr = sg_dma_address(sg);
+ else {
+ addr = vring_map_one_sg(vq, sg, direction);
+ ret = vring_mapping_error(vq, addr);
+ if (ret)
+ return ret;
+ }
+
+ desc->flags = cpu_to_virtio16(_vq->vdev, flags);
+ desc->addr = cpu_to_virtio64(_vq->vdev, addr);
+ desc->len = cpu_to_virtio32(_vq->vdev,
+ dma ? sg_dma_len(sg) : sg->length);
+
+ return ret;
+}
+
static inline int virtqueue_add(struct virtqueue *_vq,
struct scatterlist *sgs[],
unsigned int total_sg,
unsigned int out_sgs,
unsigned int in_sgs,
void *data,
- gfp_t gfp)
+ gfp_t gfp,
+ bool dma)
{
struct vring_virtqueue *vq = to_vvq(_vq);
struct scatterlist *sg;
@@ -301,7 +330,7 @@ static inline int virtqueue_add(struct virtqueue *_vq,
/* If the host supports indirect descriptor tables, and we have multiple
* buffers, then go indirect. FIXME: tune this threshold */
- if (vq->indirect && total_sg > 1 && vq->vq.num_free)
+ if (!dma && vq->indirect && total_sg > 1 && vq->vq.num_free)
desc = alloc_indirect(_vq, total_sg, gfp);
else
desc = NULL;
@@ -335,26 +364,23 @@ static inline int virtqueue_add(struct virtqueue *_vq,
for (n = 0; n < out_sgs; n++) {
for (sg = sgs[n]; sg; sg = sg_next(sg)) {
- dma_addr_t addr = vring_map_one_sg(vq, sg, DMA_TO_DEVICE);
- if (vring_mapping_error(vq, addr))
+ int ret = vring_desc_set(_vq, desc + i, sg,
+ VRING_DESC_F_NEXT, DMA_TO_DEVICE, dma);
+ if (ret)
goto unmap_release;
- desc[i].flags = cpu_to_virtio16(_vq->vdev, VRING_DESC_F_NEXT);
- desc[i].addr = cpu_to_virtio64(_vq->vdev, addr);
- desc[i].len = cpu_to_virtio32(_vq->vdev, sg->length);
prev = i;
i = virtio16_to_cpu(_vq->vdev, desc[i].next);
}
}
for (; n < (out_sgs + in_sgs); n++) {
for (sg = sgs[n]; sg; sg = sg_next(sg)) {
- dma_addr_t addr = vring_map_one_sg(vq, sg, DMA_FROM_DEVICE);
- if (vring_mapping_error(vq, addr))
+ int ret = vring_desc_set(_vq, desc + i, sg,
+ VRING_DESC_F_NEXT | VRING_DESC_F_WRITE,
+ DMA_FROM_DEVICE, dma);
+ if (ret)
goto unmap_release;
- desc[i].flags = cpu_to_virtio16(_vq->vdev, VRING_DESC_F_NEXT | VRING_DESC_F_WRITE);
- desc[i].addr = cpu_to_virtio64(_vq->vdev, addr);
- desc[i].len = cpu_to_virtio32(_vq->vdev, sg->length);
prev = i;
i = virtio16_to_cpu(_vq->vdev, desc[i].next);
}
@@ -461,7 +487,8 @@ int virtqueue_add_sgs(struct virtqueue *_vq,
for (sg = sgs[i]; sg; sg = sg_next(sg))
total_sg++;
}
- return virtqueue_add(_vq, sgs, total_sg, out_sgs, in_sgs, data, gfp);
+ return virtqueue_add(_vq, sgs, total_sg, out_sgs, in_sgs, data, gfp,
+ false);
}
EXPORT_SYMBOL_GPL(virtqueue_add_sgs);
@@ -483,10 +510,19 @@ int virtqueue_add_outbuf(struct virtqueue *vq,
void *data,
gfp_t gfp)
{
- return virtqueue_add(vq, &sg, num, 1, 0, data, gfp);
+ return virtqueue_add(vq, &sg, num, 1, 0, data, gfp, false);
}
EXPORT_SYMBOL_GPL(virtqueue_add_outbuf);
+int dma_virtqueue_add_outbuf(struct virtqueue *vq,
+ struct scatterlist *sg, unsigned int num,
+ void *data,
+ gfp_t gfp)
+{
+ return virtqueue_add(vq, &sg, num, 1, 0, data, gfp, true);
+}
+EXPORT_SYMBOL_GPL(dma_virtqueue_add_outbuf);
+
/**
* virtqueue_add_inbuf - expose input buffers to other end
* @vq: the struct virtqueue we're talking about.
@@ -505,10 +541,19 @@ int virtqueue_add_inbuf(struct virtqueue *vq,
void *data,
gfp_t gfp)
{
- return virtqueue_add(vq, &sg, num, 0, 1, data, gfp);
+ return virtqueue_add(vq, &sg, num, 0, 1, data, gfp, false);
}
EXPORT_SYMBOL_GPL(virtqueue_add_inbuf);
+int dma_virtqueue_add_inbuf(struct virtqueue *vq,
+ struct scatterlist *sg, unsigned int num,
+ void *data,
+ gfp_t gfp)
+{
+ return virtqueue_add(vq, &sg, num, 0, 1, data, gfp, true);
+}
+EXPORT_SYMBOL_GPL(dma_virtqueue_add_inbuf);
+
/**
* virtqueue_kick_prepare - first half of split virtqueue_kick call.
* @vq: the struct virtqueue
diff --git a/drivers/watchdog/renesas_wdt.c b/drivers/watchdog/renesas_wdt.c
index cf61c92..96cbabf 100644
--- a/drivers/watchdog/renesas_wdt.c
+++ b/drivers/watchdog/renesas_wdt.c
@@ -16,6 +16,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/soc/renesas/s2ram_ddr_backup.h>
#include <linux/watchdog.h>
#define RWTCNT 0
@@ -26,6 +27,21 @@
#define RWDT_DEFAULT_TIMEOUT 60U
+#ifdef CONFIG_RCAR_DDR_BACKUP
+static struct hw_register rwdt_ip_regs[] = {
+ {"RWTCNT", 0x00, 16, 0},
+ {"RWTCSRA", 0x04, 8, 0},
+};
+
+static struct rcar_ip rwdt_ip = {
+ .ip_name = "RWDT",
+ .base_addr = 0xE6020000,
+ .size = 0x08,
+ .reg_count = ARRAY_SIZE(rwdt_ip_regs),
+ .ip_reg = rwdt_ip_regs,
+};
+#endif /* CONFIG_RCAR_DDR_BACKUP*/
+
static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024 };
static bool nowayout = WATCHDOG_NOWAYOUT;
@@ -60,6 +76,15 @@ static int rwdt_init_timeout(struct watchdog_device *wdev)
return 0;
}
+static int rwdt_set_timeout(struct watchdog_device *wdev,
+ unsigned int new_timeout)
+{
+ wdev->timeout = new_timeout;
+ rwdt_init_timeout(wdev);
+
+ return 0;
+}
+
static int rwdt_start(struct watchdog_device *wdev)
{
struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
@@ -105,6 +130,7 @@ static const struct watchdog_ops rwdt_ops = {
.start = rwdt_start,
.stop = rwdt_stop,
.ping = rwdt_init_timeout,
+ .set_timeout = rwdt_set_timeout,
.get_timeleft = rwdt_get_timeleft,
};
@@ -198,9 +224,41 @@ static const struct of_device_id rwdt_ids[] = {
};
MODULE_DEVICE_TABLE(of, rwdt_ids);
+#ifdef CONFIG_PM_SLEEP
+static int rwdt_suspend(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ struct rwdt_priv *priv = dev_get_drvdata(dev);
+
+ if (!rwdt_ip.virt_addr)
+ rwdt_ip.virt_addr = priv->base;
+
+ ret = rcar_handle_registers(&rwdt_ip, DO_BACKUP);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static int rwdt_resume(struct device *dev)
+{
+ int ret = 0;
+#ifdef CONFIG_RCAR_DDR_BACKUP
+ ret = rcar_handle_registers(&rwdt_ip, DO_RESTORE);
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(rwdt_pm_ops,
+ rwdt_suspend, rwdt_resume);
+#define DEV_PM_OPS (&rwdt_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver rwdt_driver = {
.driver = {
.name = "renesas_wdt",
+ .pm = DEV_PM_OPS,
.of_match_table = rwdt_ids,
},
.probe = rwdt_probe,
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index bae79f3..3c3442a 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
* Copyright (C) 2011 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +26,7 @@ enum dw_hdmi_devtype {
IMX6Q_HDMI,
IMX6DL_HDMI,
RK3288_HDMI,
+ RCAR_HDMI,
};
struct dw_hdmi_mpll_config {
@@ -40,6 +42,11 @@ struct dw_hdmi_curr_ctrl {
u16 curr[DW_HDMI_RES_MAX];
};
+struct dw_hdmi_multi_div {
+ unsigned long mpixelclock;
+ u16 multi[DW_HDMI_RES_MAX];
+};
+
struct dw_hdmi_phy_config {
unsigned long mpixelclock;
u16 sym_ctr; /*clock symbol and transmitter control*/
@@ -51,9 +58,11 @@ struct dw_hdmi_plat_data {
enum dw_hdmi_devtype dev_type;
const struct dw_hdmi_mpll_config *mpll_cfg;
const struct dw_hdmi_curr_ctrl *cur_ctr;
+ const struct dw_hdmi_multi_div *multi_div;
const struct dw_hdmi_phy_config *phy_config;
enum drm_mode_status (*mode_valid)(struct drm_connector *connector,
struct drm_display_mode *mode);
+ u32 index;
};
void dw_hdmi_unbind(struct device *dev, struct device *master, void *data);
diff --git a/include/linux/mfd/tmio.h b/include/linux/mfd/tmio.h
index 7a26286..2f2cc7b 100644
--- a/include/linux/mfd/tmio.h
+++ b/include/linux/mfd/tmio.h
@@ -99,6 +99,9 @@
*/
#define TMIO_MMC_SDIO_STATUS_QUIRK (1 << 8)
+/* The start or stop of SD clock don't wait 10msec. */
+#define TMIO_MMC_CLK_NO_SLEEP (1 << 9)
+
/*
* Some controllers allows to set SDx actual clock
*/
@@ -125,6 +128,8 @@ struct tmio_mmc_data {
unsigned int cd_gpio;
int alignment_shift;
dma_addr_t dma_rx_offset;
+ unsigned int max_blk_count;
+ unsigned short max_segs;
void (*set_pwr)(struct platform_device *host, int state);
void (*set_clk_div)(struct platform_device *host, int state);
};
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 0e49f70..3892e35 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1139,6 +1139,9 @@ void pdev_enable_device(struct pci_dev *);
int pci_enable_resources(struct pci_dev *, int mask);
void pci_fixup_irqs(u8 (*)(struct pci_dev *, u8 *),
int (*)(const struct pci_dev *, u8, u8));
+void pci_fixup_irqs_local(struct pci_bus *,
+ u8 (*)(struct pci_dev *, u8 *),
+ int (*)(const struct pci_dev *, u8, u8));
struct resource *pci_find_resource(struct pci_dev *dev, struct resource *res);
#define HAVE_PCI_REQ_REGIONS 2
int __must_check pci_request_regions(struct pci_dev *, const char *);
diff --git a/include/linux/soc/renesas/rcar_ems_ctrl.h b/include/linux/soc/renesas/rcar_ems_ctrl.h
new file mode 100644
index 0000000..57ef8ad
--- /dev/null
+++ b/include/linux/soc/renesas/rcar_ems_ctrl.h
@@ -0,0 +1,11 @@
+#ifndef __RCAR_EMS_CTRL_H__
+#define __RCAR_EMS_CTRL_H__
+
+#define RCAR_EMS_MODE_OFF 0
+#define RCAR_EMS_MODE_ON 1
+
+extern int register_rcar_ems_notifier(struct notifier_block *nb);
+extern void unregister_rcar_ems_notifier(struct notifier_block *nb);
+extern int rcar_ems_get_mode(void);
+
+#endif /* __RCAR_EMS_CTRL_H__ */
diff --git a/include/linux/soc/renesas/s2ram_ddr_backup.h b/include/linux/soc/renesas/s2ram_ddr_backup.h
new file mode 100644
index 0000000..f6b74df
--- /dev/null
+++ b/include/linux/soc/renesas/s2ram_ddr_backup.h
@@ -0,0 +1,45 @@
+#ifndef S2RAM_DDR_BACKUP_H
+#define S2RAM_DDR_BACKUP_H
+
+#include <linux/types.h>
+
+enum {
+ DO_IOREMAP,
+ DO_BACKUP,
+ DO_RESTORE,
+};
+
+struct hw_register {
+ char *reg_name;
+ unsigned int reg_offset;
+ unsigned int access_size;
+ unsigned int reg_value;
+};
+
+struct rcar_ip {
+ char *ip_name;
+ void __iomem *virt_addr;
+ phys_addr_t base_addr;
+ unsigned int size;
+ unsigned int reg_count;
+ struct hw_register *ip_reg;
+};
+
+#ifdef CONFIG_RCAR_DDR_BACKUP
+int rcar_handle_registers(struct rcar_ip *ip, unsigned int handling);
+int rcar_handle_ips(struct rcar_ip **ip, unsigned int handling);
+#else
+static inline int rcar_handle_registers(struct rcar_ip *ip,
+ unsigned int handling)
+{
+ return 0;
+}
+
+static inline int rcar_handle_ips(struct rcar_ip **ip,
+ unsigned int handling)
+{
+ return 0;
+}
+#endif /* CONFIG_RCAR_DDR_BACKUP */
+
+#endif /* S2RAM_DDR_BACKUP_H */
diff --git a/include/linux/spi/sh_msiof.h b/include/linux/spi/sh_msiof.h
index b087a85..f723aa4 100644
--- a/include/linux/spi/sh_msiof.h
+++ b/include/linux/spi/sh_msiof.h
@@ -1,10 +1,16 @@
#ifndef __SPI_SH_MSIOF_H__
#define __SPI_SH_MSIOF_H__
+enum {
+ SPI_MSIOF_MASTER,
+ SPI_MSIOF_SLAVE,
+};
+
struct sh_msiof_spi_info {
int tx_fifo_override;
int rx_fifo_override;
u16 num_chipselect;
+ int mode;
unsigned int dma_tx_id;
unsigned int dma_rx_id;
u32 dtdl;
diff --git a/include/linux/virtio.h b/include/linux/virtio.h
index d5eb547..64f45b7 100644
--- a/include/linux/virtio.h
+++ b/include/linux/virtio.h
@@ -34,6 +34,16 @@ struct virtqueue {
void *priv;
};
+int dma_virtqueue_add_outbuf(struct virtqueue *vq,
+ struct scatterlist sg[], unsigned int num,
+ void *data,
+ gfp_t gfp);
+
+int dma_virtqueue_add_inbuf(struct virtqueue *vq,
+ struct scatterlist sg[], unsigned int num,
+ void *data,
+ gfp_t gfp);
+
int virtqueue_add_outbuf(struct virtqueue *vq,
struct scatterlist sg[], unsigned int num,
void *data,
diff --git a/include/media/media-entity.h b/include/media/media-entity.h
index b2203ee..5fb3f06 100644
--- a/include/media/media-entity.h
+++ b/include/media/media-entity.h
@@ -181,6 +181,9 @@ struct media_pad {
* @link_validate: Return whether a link is valid from the entity point of
* view. The media_entity_pipeline_start() function
* validates all links by calling this operation. Optional.
+ * @has_route: Return whether a route exists inside the entity between
+ * two given pads. Optional. If the operation isn't
+ * implemented all pads will be considered as connected.
*
* .. note::
*
@@ -192,6 +195,8 @@ struct media_entity_operations {
const struct media_pad *local,
const struct media_pad *remote, u32 flags);
int (*link_validate)(struct media_link *link);
+ bool (*has_route)(struct media_entity *entity, unsigned int pad0,
+ unsigned int pad1);
};
/**
@@ -846,6 +851,9 @@ void media_entity_graph_walk_cleanup(struct media_entity_graph *graph);
*/
void media_entity_put(struct media_entity *entity);
+bool media_entity_has_route(struct media_entity *entity, unsigned int sink,
+ unsigned int source);
+
/**
* media_entity_graph_walk_start - Start walking the media graph at a
* given entity
diff --git a/include/media/vsp1.h b/include/media/vsp1.h
index 8d3d07a..7459546 100644
--- a/include/media/vsp1.h
+++ b/include/media/vsp1.h
@@ -1,7 +1,7 @@
/*
* vsp1.h -- R-Car VSP1 API
*
- * Copyright (C) 2015 Renesas Electronics Corporation
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -22,7 +22,8 @@ struct device;
int vsp1_du_init(struct device *dev);
int vsp1_du_setup_lif(struct device *dev, unsigned int width,
- unsigned int height);
+ unsigned int height, unsigned int lif_index,
+ bool suspend);
struct vsp1_du_atomic_config {
u32 pixelformat;
@@ -32,13 +33,19 @@ struct vsp1_du_atomic_config {
struct v4l2_rect dst;
unsigned int alpha;
unsigned int zpos;
+ bool interlaced;
};
-void vsp1_du_atomic_begin(struct device *dev);
+void vsp1_du_atomic_begin(struct device *dev, unsigned int lif_index);
int vsp1_du_atomic_update(struct device *dev, unsigned int rpf,
const struct vsp1_du_atomic_config *cfg);
-void vsp1_du_atomic_flush(struct device *dev);
+void vsp1_du_atomic_flush(struct device *dev, unsigned int lif_index);
int vsp1_du_map_sg(struct device *dev, struct sg_table *sgt);
void vsp1_du_unmap_sg(struct device *dev, struct sg_table *sgt);
+int vsp1_du_if_set_mute(struct device *dev, bool on, unsigned int lif_index);
+int vsp1_du_setup_wb(struct device *dev, u32 pixelformat, unsigned int pitch,
+ dma_addr_t mem[2], unsigned int lif_index);
+void vsp1_du_wait_wb(struct device *dev, u32 count, unsigned int lif_index);
+
#endif /* __MEDIA_VSP1_H__ */
diff --git a/include/uapi/drm/Kbuild b/include/uapi/drm/Kbuild
index 9355dd8..061983a 100644
--- a/include/uapi/drm/Kbuild
+++ b/include/uapi/drm/Kbuild
@@ -12,6 +12,7 @@
header-y += qxl_drm.h
header-y += r128_drm.h
header-y += radeon_drm.h
+header-y += rcar_du_drm.h
header-y += savage_drm.h
header-y += sis_drm.h
header-y += tegra_drm.h
diff --git a/include/uapi/drm/rcar_du_drm.h b/include/uapi/drm/rcar_du_drm.h
new file mode 100644
index 0000000..43420db
--- /dev/null
+++ b/include/uapi/drm/rcar_du_drm.h
@@ -0,0 +1,42 @@
+/*
+ * rcar_du_drm.h -- R-Car Display Unit DRM driver
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * 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.
+ */
+
+#ifndef __RCAR_DU_DRM_H__
+#define __RCAR_DU_DRM_H__
+
+struct rcar_du_vmute {
+ int crtc_id; /* CRTCs ID */
+ int on; /* Vmute function ON/OFF */
+};
+
+/* DRM_IOCTL_RCAR_DU_SET_SCRSHOT:VSPD screen shot */
+struct rcar_du_screen_shot {
+ unsigned long buff;
+ unsigned int buff_len;
+ unsigned int crtc_id;
+ unsigned int fmt;
+ unsigned int width;
+ unsigned int height;
+};
+
+/* rcar-du + vspd specific ioctls */
+#define DRM_RCAR_DU_SET_VMUTE 0
+#define DRM_RCAR_DU_SCRSHOT 4
+
+#define DRM_IOCTL_DRM_RCAR_DU_SET_VMUTE \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_RCAR_DU_SET_VMUTE, \
+ struct rcar_du_vmute)
+
+#define DRM_IOCTL_DRM_RCAR_DU_SCRSHOT \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_RCAR_DU_SCRSHOT, \
+ struct rcar_du_screen_shot)
+
+#endif /* __RCAR_DU_DRM_H__ */
diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c
index 1cb3930..e5b80f5 100644
--- a/sound/soc/generic/simple-card-utils.c
+++ b/sound/soc/generic/simple-card-utils.c
@@ -195,9 +195,6 @@ EXPORT_SYMBOL_GPL(asoc_simple_card_init_dai);
int asoc_simple_card_canonicalize_dailink(struct snd_soc_dai_link *dai_link)
{
- if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name)
- return -EINVAL;
-
/* Assumes platform == cpu */
if (!dai_link->platform_of_node)
dai_link->platform_of_node = dai_link->cpu_of_node;
diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c
index f181410..940012d 100644
--- a/sound/soc/sh/rcar/core.c
+++ b/sound/soc/sh/rcar/core.c
@@ -306,7 +306,7 @@ u32 rsnd_get_adinr_bit(struct rsnd_mod *mod, struct rsnd_dai_stream *io)
*/
u32 rsnd_get_dalign(struct rsnd_mod *mod, struct rsnd_dai_stream *io)
{
- struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io);
+ struct rsnd_mod *ssiu = rsnd_io_to_mod_ssiu(io);
struct rsnd_mod *target;
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
u32 val = 0x76543210;
@@ -315,11 +315,11 @@ u32 rsnd_get_dalign(struct rsnd_mod *mod, struct rsnd_dai_stream *io)
if (rsnd_io_is_play(io)) {
struct rsnd_mod *src = rsnd_io_to_mod_src(io);
- target = src ? src : ssi;
+ target = src ? src : ssiu;
} else {
struct rsnd_mod *cmd = rsnd_io_to_mod_cmd(io);
- target = cmd ? cmd : ssi;
+ target = cmd ? cmd : ssiu;
}
mask <<= runtime->channels * 4;
@@ -1251,9 +1251,37 @@ static int rsnd_remove(struct platform_device *pdev)
return ret;
}
+#ifdef CONFIG_PM_SLEEP
+static int rsnd_suspend(struct device *dev)
+{
+ struct rsnd_priv *priv = dev_get_drvdata(dev);
+
+ rsnd_adg_remove(priv);
+
+ return 0;
+}
+
+static int rsnd_resume(struct device *dev)
+{
+ struct rsnd_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = rsnd_adg_probe(priv);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(rsnd_pm_ops,
+ rsnd_suspend, rsnd_resume);
+#define DEV_PM_OPS (&rsnd_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver rsnd_driver = {
.driver = {
.name = "rcar_sound",
+ .pm = DEV_PM_OPS,
.of_match_table = rsnd_of_match,
},
.probe = rsnd_probe,
diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h
index a8f61d7..8ca1705 100644
--- a/sound/soc/sh/rcar/rsnd.h
+++ b/sound/soc/sh/rcar/rsnd.h
@@ -382,6 +382,7 @@ struct rsnd_dai_stream {
};
#define rsnd_io_to_mod(io, i) ((i) < RSND_MOD_MAX ? (io)->mod[(i)] : NULL)
#define rsnd_io_to_mod_ssi(io) rsnd_io_to_mod((io), RSND_MOD_SSI)
+#define rsnd_io_to_mod_ssiu(io) rsnd_io_to_mod((io), RSND_MOD_SSIU)
#define rsnd_io_to_mod_ssip(io) rsnd_io_to_mod((io), RSND_MOD_SSIP)
#define rsnd_io_to_mod_src(io) rsnd_io_to_mod((io), RSND_MOD_SRC)
#define rsnd_io_to_mod_ctu(io) rsnd_io_to_mod((io), RSND_MOD_CTU)
diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c
index 969a516..1e9ca57 100644
--- a/sound/soc/sh/rcar/src.c
+++ b/sound/soc/sh/rcar/src.c
@@ -189,6 +189,7 @@ static void rsnd_src_set_convert_rate(struct rsnd_dai_stream *io,
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
struct device *dev = rsnd_priv_to_dev(priv);
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+ int use_src = 0;
u32 fin, fout;
u32 ifscr, fsrate, adinr;
u32 cr, route;
@@ -214,6 +215,8 @@ static void rsnd_src_set_convert_rate(struct rsnd_dai_stream *io,
return;
}
+ use_src = (fin != fout) | rsnd_src_sync_is_enabled(mod);
+
/*
* SRC_ADINR
*/
@@ -225,7 +228,7 @@ static void rsnd_src_set_convert_rate(struct rsnd_dai_stream *io,
*/
ifscr = 0;
fsrate = 0;
- if (fin != fout) {
+ if (use_src) {
u64 n;
ifscr = 1;
@@ -239,7 +242,7 @@ static void rsnd_src_set_convert_rate(struct rsnd_dai_stream *io,
*/
cr = 0x00011110;
route = 0x0;
- if (fin != fout) {
+ if (use_src) {
route = 0x1;
if (rsnd_src_sync_is_enabled(mod)) {