Merge branch 'renesas-drivers-2016-01-19-v4.4/dts-rcar-gen3' into v4.4/rcar-3.2.x
* renesas-drivers-2016-01-19-v4.4/dts-rcar-gen3: (78 commits)
arm64: renesas: r8a7795: Use polling mode for thermal driver
arm64: dts: r8a7795: Add UHS-I(SDR104) support
arm64: dts: r8a7795: Delete IO voltate setting
arm64: dts: salvator-x: Add a voltage setting of MMC by pinctrl
arm64: dts: salvator-x: Add a voltage setting of SDHI by pinctrl
arm64: dts: salvator-x: Add restrictions of 100Mbps for H3 WS1.1 EthernetAVB
arm64: renesas: r8a7795: Add sustainable-power for thermal zone 0 & 1
arm64: renesas: r8a7795: Create thermal zone to support IPA
arm64: renesas: r8a7795: Add cpu-supply to regulator node
arm64: renesas: salvator-x: Add regulator for DVFS
arm64: renesas: salvator-x: Enable I2C for DVFS device
arm64: renesas: r8a7795: Add I2C for DVFS core to dtsi
arm64: renesas: r8a7795: Add OPPs table for cpu devices
arm64: dts: r8a7795: Fix iVDP1C and VCP4 node
arm64: dts: salvator-x: enable usb3.0 host channel 0
arm64: dts: r8a7795: Add USB3.0 host device nodes
arm64: dts: r8a7795: add HS-USB device node
arm64: dts: r8a7795: add usb2_phy device nodes
arm64: dts: salvator-x: Add programmable clock generator node
arm64: dts: salvator-x: Add eMMC support to 8-bit bus width
arm64: dts: r8a7795: Add eMMC support to 8-bit bus width
arm64: dts: r8a7795: Fix the power domain of VSP node
arm64: dts: salvator-x: Add VSPM I/F driver node
arm64: dts: salvator-x: Add MMNGR driver node
arm64: dts: salvator-x: Add ADSP support
arm64: dts: r8a7795: Add ADSP node
arm64: dts: r8a7795: Add iVDP1C node
arm64: dts: r8a7795: Add VCP4 node
arm64: dts: r8a7795: Add FDP1 node
arm64: dts: r8a7795: Convert VSP Driver into VSP Manager
...
diff --git a/Documentation/devicetree/bindings/media/rcar_vin.txt b/Documentation/devicetree/bindings/media/rcar_vin.txt
index 9dafe6b..619193c 100644
--- a/Documentation/devicetree/bindings/media/rcar_vin.txt
+++ b/Documentation/devicetree/bindings/media/rcar_vin.txt
@@ -6,6 +6,7 @@
channel which can be either RGB, YUYV or BT656.
- compatible: Must be one of the following
+ - "renesas,vin-r8a7795" for the R8A7795 device
- "renesas,vin-r8a7794" for the R8A7794 device
- "renesas,vin-r8a7793" for the R8A7793 device
- "renesas,vin-r8a7791" for the R8A7791 device
diff --git a/Documentation/devicetree/bindings/mmc/tmio_mmc.txt b/Documentation/devicetree/bindings/mmc/tmio_mmc.txt
index 400b640..d9b3d11 100644
--- a/Documentation/devicetree/bindings/mmc/tmio_mmc.txt
+++ b/Documentation/devicetree/bindings/mmc/tmio_mmc.txt
@@ -22,6 +22,8 @@
"renesas,sdhi-r8a7792" - SDHI IP on R8A7792 SoC
"renesas,sdhi-r8a7793" - SDHI IP on R8A7793 SoC
"renesas,sdhi-r8a7794" - SDHI IP on R8A7794 SoC
+ "renesas,sdhi-r8a7795" - SDHI IP on R8A7795 SoC
+ "renesas,mmc-r8a7795" - MMC IP on R8A7795 SoC
Optional properties:
- toshiba,mmc-wrprotect-disable: write-protect detection is unavailable
diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt
index 2390e4e..eaf7e9b 100644
--- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt
+++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt
@@ -7,33 +7,26 @@
- compatible: "renesas,usb2-phy-r8a7795" if the device is a part of an R8A7795
SoC.
- reg: offset and length of the partial USB 2.0 Host register block.
-- reg-names: must be "usb2_host".
- clocks: clock phandle and specifier pair(s).
- #phy-cells: see phy-bindings.txt in the same directory, must be <0>.
Optional properties:
To use a USB channel where USB 2.0 Host and HSUSB (USB 2.0 Peripheral) are
-combined, the device tree node should set HSUSB properties to reg and reg-names
-properties. This is because HSUSB has registers to select USB 2.0 host or
-peripheral at that channel:
-- reg: offset and length of the partial HSUSB register block.
-- reg-names: must be "hsusb".
+combined, the device tree node should set interrupt properties to use the
+channel as USB OTG:
- interrupts: interrupt specifier for the PHY.
Example (R-Car H3):
usb-phy@ee080200 {
compatible = "renesas,usb2-phy-r8a7795";
- reg = <0 0xee080200 0 0x700>, <0 0xe6590100 0 0x100>;
- reg-names = "usb2_host", "hsusb";
+ reg = <0 0xee080200 0 0x700>;
interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&mstp7_clks R8A7795_CLK_EHCI0>,
- <&mstp7_clks R8A7795_CLK_HSUSB>;
+ clocks = <&mstp7_clks R8A7795_CLK_EHCI0>;
};
usb-phy@ee0a0200 {
compatible = "renesas,usb2-phy-r8a7795";
reg = <0 0xee0a0200 0 0x700>;
- reg-names = "usb2_host";
clocks = <&mstp7_clks R8A7795_CLK_EHCI0>;
};
diff --git a/Documentation/devicetree/bindings/spi/sh-msiof.txt b/Documentation/devicetree/bindings/spi/sh-msiof.txt
index aa005c1..e4ba96c 100644
--- a/Documentation/devicetree/bindings/spi/sh-msiof.txt
+++ b/Documentation/devicetree/bindings/spi/sh-msiof.txt
@@ -10,6 +10,7 @@
"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-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..f5c9430
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/rcar-gen3-thermal.txt
@@ -0,0 +1,48 @@
+* Renesas R-Car Gen3 Thermal
+
+Required properties:
+- compatible : "renesas,thermal-<soctype>",
+ "renesas,rcar-gen3-thermal" (with thermal-zone always)
+ Examples with soctypes are:
+ - "renesas,thermal-r8a7795" (R-Car H3)
+- 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",
+ "renesas,rcar-gen3-thermal",
+ 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",
+ "renesas,rcar-gen3-thermal",
+ reg = <0 0xe6198000 0 0x5c>;
+ interrupts = <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+thermal-zones {
+ cpu_thermal: cpu-thermal {
+ polling-delay-passive = <250>;
+ polling-delay = <0>;
+
+ thermal-sensors = <&thermal>;
+ };
+};
diff --git a/Documentation/devicetree/bindings/watchdog/renesas-rwdt.txt b/Documentation/devicetree/bindings/watchdog/renesas-rwdt.txt
new file mode 100644
index 0000000..fd050d5
--- /dev/null
+++ b/Documentation/devicetree/bindings/watchdog/renesas-rwdt.txt
@@ -0,0 +1,18 @@
+Renesas RCLK Watchdog Timer (RWDT) Controller
+
+Required properties:
+- compatible : Should be "renesas,rwdt-r8a7795";
+- reg : Should contain WDT registers location and length
+- clocks : the clock feeding the watchdog timer.
+
+Optional properties:
+- timeout-sec : Contains the watchdog timeout in seconds
+
+Examples:
+
+ wdt0: wdt@e6020000 {
+ compatible = "renesas,rwdt-r8a7795";
+ reg = <0 0xe6020000 0 0x0c>;
+ clocks = <&cpg CPG_MOD 402>;
+ timeout-sec = <60>;
+ };
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 18ca9fb..671ab54 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -54,8 +54,8 @@
CONFIG_ARCH_ZYNQMP=y
CONFIG_PCI=y
CONFIG_PCI_MSI=y
-CONFIG_PCI_HOST_GENERIC=y
-CONFIG_PCI_XGENE=y
+# CONFIG_PCI_RCAR_GEN2 is not set
+CONFIG_PCI_RCAR_GEN2_PCIE=y
CONFIG_SMP=y
CONFIG_SCHED_MC=y
CONFIG_PREEMPT=y
@@ -67,6 +67,13 @@
CONFIG_COMPAT=y
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_STAT_DETAILS=y
+CONFIG_CPUFREQ_DT=y
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_UNIX=y
@@ -135,21 +142,50 @@
CONFIG_VIRTIO_CONSOLE=y
# CONFIG_HW_RANDOM is not set
CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=y
CONFIG_I2C_QUP=y
CONFIG_I2C_RCAR=y
+CONFIG_I2C_SH_MOBILE=y
CONFIG_SPI=y
CONFIG_SPI_PL022=y
+CONFIG_SPI_SH_MSIOF=y
+CONFIG_SPI_SPIDEV=y
CONFIG_SPI_QUP=y
CONFIG_PINCTRL_MSM8916=y
CONFIG_GPIO_PL061=y
CONFIG_GPIO_RCAR=y
CONFIG_GPIO_XGENE=y
+CONFIG_POWER_AVS=y
CONFIG_POWER_RESET_XGENE=y
CONFIG_POWER_RESET_SYSCON=y
# CONFIG_HWMON is not set
+CONFIG_THERMAL=y
+CONFIG_THERMAL_OF=y
+CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR=y
+CONFIG_RCAR_GEN3_THERMAL=y
CONFIG_REGULATOR=y
+CONFIG_REGULATOR_BD9571MWV=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_GPIO=y
CONFIG_REGULATOR_QCOM_SMD_RPM=y
+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_SOC_CAMERA=y
+CONFIG_SOC_CAMERA_PLATFORM=y
+CONFIG_VIDEO_RCAR_VIN=y
+CONFIG_VIDEO_RCAR_CSI2=y
+CONFIG_V4L_MEM2MEM_DRIVERS=y
+CONFIG_VIDEO_RENESAS_VSP1=y
+# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set
+CONFIG_VIDEO_ADV7482=y
+CONFIG_DRM=y
+CONFIG_DRM_RCAR_DU=y
+CONFIG_DRM_RCAR_HDMI=y
+CONFIG_DRM_RCAR_LVDS=y
+CONFIG_DRM_RCAR_VSP=y
CONFIG_FB=y
CONFIG_FB_ARMCLCD=y
CONFIG_FRAMEBUFFER_CONSOLE=y
@@ -175,6 +211,8 @@
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_TEGRA=y
CONFIG_MMC_SPI=y
+CONFIG_MMC_TMIO_CORE=y
+CONFIG_MMC_SDHI=y
CONFIG_MMC_DW=y
CONFIG_MMC_DW_IDMAC=y
CONFIG_MMC_DW_PLTFM=y
@@ -195,14 +233,20 @@
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BALLOON=y
CONFIG_VIRTIO_MMIO=y
+CONFIG_STAGING=y
+CONFIG_STAGING_BOARD=y
CONFIG_COMMON_CLK_CS2000_CP=y
+CONFIG_COMMON_CLK_5P49V5923A=y
CONFIG_COMMON_CLK_QCOM=y
CONFIG_MSM_GCC_8916=y
CONFIG_HWSPINLOCK_QCOM=y
# CONFIG_IOMMU_SUPPORT is not set
+CONFIG_PWM=y
+CONFIG_PWM_RCAR=y
CONFIG_QCOM_SMEM=y
CONFIG_QCOM_SMD=y
CONFIG_QCOM_SMD_RPM=y
+CONFIG_PHY_RCAR_GEN3_USB2=y
CONFIG_PHY_XGENE=y
CONFIG_EXT2_FS=y
CONFIG_EXT3_FS=y
diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c
index 93ed14c..f6a9ad5 100644
--- a/drivers/base/power/common.c
+++ b/drivers/base/power/common.c
@@ -146,7 +146,7 @@
if (dev->pm_domain == pd)
return;
- WARN(device_is_bound(dev),
+ WARN(pd && device_is_bound(dev),
"PM domains can only be changed for unbound devices\n");
dev->pm_domain = pd;
device_pm_check_callbacks(dev);
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index ee54e84..3fb04c3 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -1690,100 +1690,63 @@
EXPORT_SYMBOL_GPL(regmap_raw_write);
/**
- * regmap_field_write(): Write a value to a single register field
- *
- * @field: Register field to write to
- * @val: Value to be written
- *
- * A value of zero will be returned on success, a negative errno will
- * be returned in error cases.
- */
-int regmap_field_write(struct regmap_field *field, unsigned int val)
-{
- return regmap_update_bits(field->regmap, field->reg,
- field->mask, val << field->shift);
-}
-EXPORT_SYMBOL_GPL(regmap_field_write);
-
-/**
- * regmap_field_update_bits(): Perform a read/modify/write cycle
- * on the register field
+ * regmap_field_update_bits_base():
+ * Perform a read/modify/write cycle on the register field
+ * with change, async, force option
*
* @field: Register field to write to
* @mask: Bitmask to change
* @val: Value to be written
+ * @change: Boolean indicating if a write was done
+ * @async: Boolean indicating asynchronously
+ * @force: Boolean indicating use force update
*
* A value of zero will be returned on success, a negative errno will
* be returned in error cases.
*/
-int regmap_field_update_bits(struct regmap_field *field, unsigned int mask, unsigned int val)
+int regmap_field_update_bits_base(struct regmap_field *field,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force)
{
mask = (mask << field->shift) & field->mask;
- return regmap_update_bits(field->regmap, field->reg,
- mask, val << field->shift);
+ return regmap_update_bits_base(field->regmap, field->reg,
+ mask, val << field->shift,
+ change, async, force);
}
-EXPORT_SYMBOL_GPL(regmap_field_update_bits);
+EXPORT_SYMBOL_GPL(regmap_field_update_bits_base);
/**
- * regmap_fields_write(): Write a value to a single register field with port ID
- *
- * @field: Register field to write to
- * @id: port ID
- * @val: Value to be written
- *
- * A value of zero will be returned on success, a negative errno will
- * be returned in error cases.
- */
-int regmap_fields_write(struct regmap_field *field, unsigned int id,
- unsigned int val)
-{
- if (id >= field->id_size)
- return -EINVAL;
-
- return regmap_update_bits(field->regmap,
- field->reg + (field->id_offset * id),
- field->mask, val << field->shift);
-}
-EXPORT_SYMBOL_GPL(regmap_fields_write);
-
-int regmap_fields_force_write(struct regmap_field *field, unsigned int id,
- unsigned int val)
-{
- if (id >= field->id_size)
- return -EINVAL;
-
- return regmap_write_bits(field->regmap,
- field->reg + (field->id_offset * id),
- field->mask, val << field->shift);
-}
-EXPORT_SYMBOL_GPL(regmap_fields_force_write);
-
-/**
- * regmap_fields_update_bits(): Perform a read/modify/write cycle
- * on the register field
+ * regmap_fields_update_bits_base():
+ * Perform a read/modify/write cycle on the register field
+ * with change, async, force option
*
* @field: Register field to write to
* @id: port ID
* @mask: Bitmask to change
* @val: Value to be written
+ * @change: Boolean indicating if a write was done
+ * @async: Boolean indicating asynchronously
+ * @force: Boolean indicating use force update
*
* A value of zero will be returned on success, a negative errno will
* be returned in error cases.
*/
-int regmap_fields_update_bits(struct regmap_field *field, unsigned int id,
- unsigned int mask, unsigned int val)
+int regmap_fields_update_bits_base(struct regmap_field *field, unsigned int id,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force)
{
if (id >= field->id_size)
return -EINVAL;
mask = (mask << field->shift) & field->mask;
- return regmap_update_bits(field->regmap,
- field->reg + (field->id_offset * id),
- mask, val << field->shift);
+ return regmap_update_bits_base(field->regmap,
+ field->reg + (field->id_offset * id),
+ mask, val << field->shift,
+ change, async, force);
}
-EXPORT_SYMBOL_GPL(regmap_fields_update_bits);
+EXPORT_SYMBOL_GPL(regmap_fields_update_bits_base);
/*
* regmap_bulk_write(): Write multiple registers to the device
@@ -2648,76 +2611,36 @@
}
/**
- * regmap_update_bits: Perform a read/modify/write cycle on the register map
+ * regmap_update_bits_base:
+ * Perform a read/modify/write cycle on the
+ * register map with change, async, force option
*
* @map: Register map to update
* @reg: Register to update
* @mask: Bitmask to change
* @val: New value for bitmask
+ * @change: Boolean indicating if a write was done
+ * @async: Boolean indicating asynchronously
+ * @force: Boolean indicating use force update
*
- * Returns zero for success, a negative number on error.
- */
-int regmap_update_bits(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val)
-{
- int ret;
-
- map->lock(map->lock_arg);
- ret = _regmap_update_bits(map, reg, mask, val, NULL, false);
- map->unlock(map->lock_arg);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(regmap_update_bits);
-
-/**
- * regmap_write_bits: Perform a read/modify/write cycle on the register map
- *
- * @map: Register map to update
- * @reg: Register to update
- * @mask: Bitmask to change
- * @val: New value for bitmask
- *
- * Returns zero for success, a negative number on error.
- */
-int regmap_write_bits(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val)
-{
- int ret;
-
- map->lock(map->lock_arg);
- ret = _regmap_update_bits(map, reg, mask, val, NULL, true);
- map->unlock(map->lock_arg);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(regmap_write_bits);
-
-/**
- * regmap_update_bits_async: Perform a read/modify/write cycle on the register
- * map asynchronously
- *
- * @map: Register map to update
- * @reg: Register to update
- * @mask: Bitmask to change
- * @val: New value for bitmask
- *
+ * if async was true,
* With most buses the read must be done synchronously so this is most
* useful for devices with a cache which do not need to interact with
* the hardware to determine the current register value.
*
* Returns zero for success, a negative number on error.
*/
-int regmap_update_bits_async(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val)
+int regmap_update_bits_base(struct regmap *map, unsigned int reg,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force)
{
int ret;
map->lock(map->lock_arg);
- map->async = true;
+ map->async = async;
- ret = _regmap_update_bits(map, reg, mask, val, NULL, false);
+ ret = _regmap_update_bits(map, reg, mask, val, change, force);
map->async = false;
@@ -2725,69 +2648,7 @@
return ret;
}
-EXPORT_SYMBOL_GPL(regmap_update_bits_async);
-
-/**
- * regmap_update_bits_check: Perform a read/modify/write cycle on the
- * register map and report if updated
- *
- * @map: Register map to update
- * @reg: Register to update
- * @mask: Bitmask to change
- * @val: New value for bitmask
- * @change: Boolean indicating if a write was done
- *
- * Returns zero for success, a negative number on error.
- */
-int regmap_update_bits_check(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val,
- bool *change)
-{
- int ret;
-
- map->lock(map->lock_arg);
- ret = _regmap_update_bits(map, reg, mask, val, change, false);
- map->unlock(map->lock_arg);
- return ret;
-}
-EXPORT_SYMBOL_GPL(regmap_update_bits_check);
-
-/**
- * regmap_update_bits_check_async: Perform a read/modify/write cycle on the
- * register map asynchronously and report if
- * updated
- *
- * @map: Register map to update
- * @reg: Register to update
- * @mask: Bitmask to change
- * @val: New value for bitmask
- * @change: Boolean indicating if a write was done
- *
- * With most buses the read must be done synchronously so this is most
- * useful for devices with a cache which do not need to interact with
- * the hardware to determine the current register value.
- *
- * Returns zero for success, a negative number on error.
- */
-int regmap_update_bits_check_async(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val,
- bool *change)
-{
- int ret;
-
- map->lock(map->lock_arg);
-
- map->async = true;
-
- ret = _regmap_update_bits(map, reg, mask, val, change, false);
-
- map->async = false;
-
- map->unlock(map->lock_arg);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(regmap_update_bits_check_async);
+EXPORT_SYMBOL_GPL(regmap_update_bits_base);
void regmap_async_complete_cb(struct regmap_async *async, int ret)
{
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index eca8e01..346c068 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -187,6 +187,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 b038e36..15722ff 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/directory 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..684d544
--- /dev/null
+++ b/drivers/clk/clk-5p49v5923a.c
@@ -0,0 +1,331 @@
+/*
+ * 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int clk_5p49_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int clk_5p49_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(clk_5p49_pm_ops,
+ clk_5p49_suspend, clk_5p49_resume);
+#define DEV_PM_OPS (&clk_5p49_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct i2c_driver clk_5p49_driver = {
+ .driver = {
+ .name = "5p49v5923a",
+ .pm = DEV_PM_OPS,
+ .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 7379de8..d279d3e 100644
--- a/drivers/clk/clk-cs2000-cp.c
+++ b/drivers/clk/clk-cs2000-cp.c
@@ -493,9 +493,30 @@
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)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(cs2000_pm_ops,
+ cs2000_suspend, 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/shmobile/r8a7795-cpg-mssr.c b/drivers/clk/shmobile/r8a7795-cpg-mssr.c
index c824bf5..4fbf800 100644
--- a/drivers/clk/shmobile/r8a7795-cpg-mssr.c
+++ b/drivers/clk/shmobile/r8a7795-cpg-mssr.c
@@ -121,8 +121,8 @@
DEF_BASE("z2", R8A7795_CLK_Z2, CLK_TYPE_GEN3_Z2, CLK_PLL2),
DEF_DIV6P1("mso", R8A7795_CLK_MSO, CLK_PLL1_DIV4, 0x014),
- DEF_DIV6P1("csi0", R8A7795_CLK_CSI0, CLK_PLL1_DIV2, 0x00C),
- DEF_DIV6P1("hdmi", R8A7795_CLK_HDMI, CLK_PLL1_DIV2, 0x250),
+ DEF_DIV6P1("csi0", R8A7795_CLK_CSI0, CLK_PLL1_DIV4, 0x00C),
+ DEF_DIV6P1("hdmi", R8A7795_CLK_HDMI, CLK_PLL1_DIV4, 0x250),
};
static const struct mssr_mod_clk r8a7795_mod_clks[] __initconst = {
@@ -154,6 +154,8 @@
DEF_MOD("sdhi0", 314, R8A7795_CLK_SD0),
DEF_MOD("pcie1", 318, R8A7795_CLK_S3D1),
DEF_MOD("pcie0", 319, R8A7795_CLK_S3D1),
+ DEF_MOD("usb3-if1", 327, R8A7795_CLK_S3D1),
+ DEF_MOD("usb3-if0", 328, R8A7795_CLK_S3D1),
DEF_MOD("rwdt", 402, R8A7795_CLK_R),
DEF_MOD("intc-ap", 408, R8A7795_CLK_S3D1),
DEF_MOD("audmac0", 502, R8A7795_CLK_S3D4),
@@ -165,6 +167,7 @@
DEF_MOD("hscif1", 519, R8A7795_CLK_S3D1),
DEF_MOD("hscif0", 520, R8A7795_CLK_S3D1),
DEF_MOD("thermal", 522, R8A7795_CLK_CP),
+ DEF_MOD("pwm", 523, R8A7795_CLK_S3D4),
DEF_MOD("fcpvd3", 600, R8A7795_CLK_S2D1),
DEF_MOD("fcpvd2", 601, R8A7795_CLK_S2D1),
DEF_MOD("fcpvd1", 602, R8A7795_CLK_S2D1),
@@ -224,6 +227,7 @@
DEF_MOD("gpio0", 912, R8A7795_CLK_CP),
DEF_MOD("i2c6", 918, R8A7795_CLK_S3D2),
DEF_MOD("i2c5", 919, R8A7795_CLK_S3D2),
+ DEF_MOD("i2c-dvfs", 926, R8A7795_CLK_CP),
DEF_MOD("i2c4", 927, R8A7795_CLK_S3D2),
DEF_MOD("i2c3", 928, R8A7795_CLK_S3D2),
DEF_MOD("i2c2", 929, R8A7795_CLK_S3D2),
@@ -271,28 +275,15 @@
#define CPG_SD3CKCR 0x026c
#define CPG_RCKCR 0x0240
-/** Modify for Z-clock
- * -----------------------------------------------------------------------------
- * Z Clock & Z2 Clock
- *
- * Traits of this clock:
- * prepare - clk_prepare only ensures that parents are prepared
- * enable - clk_enable only ensures that parents are enabled
- * rate - rate is adjustable. clk->rate = parent->rate * mult / 32
- * parent - fixed parent. No clk_set_parent support
- */
-#define CPG_FRQCRB 0x00000004
-#define CPG_FRQCRB_KICK BIT(31)
-#define CPG_FRQCRC 0x000000e0
-#define CPG_FRQCRC_ZFC_MASK (0x1f << 8)
-#define CPG_FRQCRC_ZFC_SHIFT 8
-#define CPG_FRQCRC_Z2FC_MASK 0x1f
+/* Implementation for customized clocks (Z-clk, Z2-clk, PLL0-clk) for CPUFreq */
+#define CPG_PLLECR 0x00D0
+#define CPG_PLLECR_PLL0ST (1 << 8)
#define GEN3_PRR 0xFFF00044 /* Product register */
#define PRODUCT_ID_MASK (0x7f << 8) /* R-Car H3: PRODUCT[14:8] bits */
#define RCAR_H3_PRODUCT_ID (0x4f << 8) /* 0b1001111 */
#define PRODUCT_VERSION_MASK 0xff /* R-Car H3: CUT[7:0] bits*/
-#define PRODUCT_VERSION_WS1_0 0
+#define PRODUCT_VERSION_WS1_0 0 /* WS1.0: 0b0000000 */
int check_product_version(u32 product_bits)
{
@@ -314,6 +305,165 @@
return ret;
}
+/* 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 */
+ if (check_product_version(
+ RCAR_H3_PRODUCT_ID | PRODUCT_VERSION_WS1_0) == 0) {
+ prate *= 2; /* Because PLL0 output is not divided for 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 */
+ if (check_product_version(
+ RCAR_H3_PRODUCT_ID | PRODUCT_VERSION_WS1_0) == 0) {
+ prate *= 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 */
+ if (check_product_version(
+ RCAR_H3_PRODUCT_ID|PRODUCT_VERSION_WS1_0) == 0) {
+ rate *= 2; /* Don't divide PLL0 output for 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
+ * enable - clk_enable only ensures that parents are enabled
+ * rate - rate is adjustable. clk->rate = parent->rate * mult / 32
+ * parent - fixed parent. No clk_set_parent support
+ */
+#define CPG_FRQCRB 0x00000004
+#define CPG_FRQCRB_KICK BIT(31)
+#define CPG_FRQCRC 0x000000e0
+#define CPG_FRQCRC_ZFC_MASK (0x1f << 8)
+#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;
@@ -336,24 +486,7 @@
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;
}
@@ -366,10 +499,24 @@
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);
+ pr_debug("%s():z-clk mult:%d, re-calculated prate:%lu, return: %lu\n",
+ __func__, mult, prate, prate / 32 * mult);
+ *parent_rate = prate;
+
+ return prate / 32 * mult;
}
static int cpg_z_clk_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -380,9 +527,16 @@
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;
@@ -418,25 +572,12 @@
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)
@@ -451,7 +592,7 @@
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;
@@ -466,6 +607,49 @@
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)
@@ -494,7 +678,7 @@
return clk;
}
-/** End of modifying for Z-clock */
+/** End of modifying for Z-clock, Z2-clock and PLL0-clock */
/* -----------------------------------------------------------------------------
* SDn Clock
@@ -786,20 +970,20 @@
static const struct cpg_pll_config cpg_pll_configs[16] __initconst = {
/* EXTAL div PLL1 mult PLL3 mult */
{ 1, 192, 192, },
- { 1, 192, 128, },
- { 0, /* Prohibited setting */ },
+ { 1, 192, 168, },
+ { 1, 192, 144, },
{ 1, 192, 192, },
{ 1, 160, 160, },
- { 1, 160, 106, },
- { 0, /* Prohibited setting */ },
+ { 1, 160, 140, },
+ { 1, 160, 120, },
{ 1, 160, 160, },
{ 1, 128, 128, },
- { 1, 128, 84, },
- { 0, /* Prohibited setting */ },
+ { 1, 128, 112, },
+ { 1, 128, 96, },
{ 1, 128, 128, },
{ 2, 192, 192, },
- { 2, 192, 128, },
- { 0, /* Prohibited setting */ },
+ { 2, 192, 168, },
+ { 2, 192, 144, },
{ 2, 192, 192, },
};
@@ -828,21 +1012,12 @@
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;
- /* Start clock issue W/A */
- if (!check_product_version(
- RCAR_H3_PRODUCT_ID|PRODUCT_VERSION_WS1_0)) {
- mult *= 2; /* Don't divide PLL0 output for 2 */
- }
- /* End clock issue W/A */
- 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;
@@ -855,10 +1030,10 @@
* the multiplier value.
*/
value = readl(base + CPG_PLL2CR);
- mult = (((value >> 24) & 0x7f) + 1) * 2;
- /* Start clock issue W/A */
- if (!check_product_version(
- RCAR_H3_PRODUCT_ID|PRODUCT_VERSION_WS1_0)) {
+ mult = ((value >> 24) & 0x7f) + 1;
+ /* Start clock issue W/A (for H3 WS1.0) */
+ if (check_product_version(
+ RCAR_H3_PRODUCT_ID | PRODUCT_VERSION_WS1_0) == 0) {
mult *= 2; /* Don't divide PLL2 output for 2 */
}
/* End clock issue W/A */
diff --git a/drivers/clk/shmobile/renesas-cpg-mssr.c b/drivers/clk/shmobile/renesas-cpg-mssr.c
index 9a4d888..c7bec26 100644
--- a/drivers/clk/shmobile/renesas-cpg-mssr.c
+++ b/drivers/clk/shmobile/renesas-cpg-mssr.c
@@ -578,9 +578,30 @@
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int cpg_mssr_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int cpg_mssr_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+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/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 0031069..6847b49 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -102,6 +102,15 @@
help
Internal configuration node for common cpufreq on Samsung SoC
+config ARM_RCAR_CPUFREQ
+ bool "Renesas R-Car Gen3 CPUfreq driver"
+ depends on ARCH_RENESAS
+ default y
+ help
+ This enables CPUfreq driver for R-Car Gen3.
+ It needs to be enabled to control for both
+ CPU frequency and voltage scaling.
+
config ARM_S3C24XX_CPUFREQ
bool "CPUfreq driver for Samsung S3C24XX series CPUs (EXPERIMENTAL)"
depends on ARCH_S3C24XX
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 9e63fb1..4d854c6 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -61,6 +61,7 @@
obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o
obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o
obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o
+obj-$(CONFIG_ARM_RCAR_CPUFREQ) += rcar-cpufreq.o
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o
obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o
diff --git a/drivers/cpufreq/rcar-cpufreq.c b/drivers/cpufreq/rcar-cpufreq.c
new file mode 100644
index 0000000..4cb3907
--- /dev/null
+++ b/drivers/cpufreq/rcar-cpufreq.c
@@ -0,0 +1,106 @@
+/*
+ * Renesas R-Car CPUFreq Support
+ *
+ * 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
+ * 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>
+#include <linux/platform_device.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_debug("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_debug("rcar-cpufreq: use avs value: %d\n", ksen_adjcnts_value);
+ iounmap(ksen_adjcnts);
+
+ return ret;
+}
+#endif /* CONFIG_POWER_AVS */
+
+int __init rcar_cpufreq_init(void)
+{
+#ifdef CONFIG_POWER_AVS
+ int avs_val = get_avs_value();
+
+ change_default_opp_pattern(avs_val);
+#endif /* CONFIG_POWER_AVS */
+ platform_device_register_simple("cpufreq-dt", -1, NULL, 0);
+ return 0;
+}
+
+module_init(rcar_cpufreq_init);
+
+MODULE_AUTHOR("Renesas Electronics Corporation");
+MODULE_DESCRIPTION("R-Car CPUFreq driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c
index f25cd79..b26c537 100644
--- a/drivers/firmware/psci.c
+++ b/drivers/firmware/psci.c
@@ -365,6 +365,13 @@
psci_0_2_set_functions();
psci_init_migrate();
+ /*
+ * psci_init_migrate() might fail to get needed information due to
+ * incomplete firmware support.
+ * TODO: 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 cf41440..f1cf84d 100644
--- a/drivers/gpio/gpio-rcar.c
+++ b/drivers/gpio/gpio-rcar.c
@@ -494,11 +494,32 @@
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int gpio_rcar_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int gpio_rcar_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+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 b0aac473..ab266b9 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
@@ -101,6 +124,17 @@
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 +146,8 @@
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 +178,10 @@
unsigned int audio_n;
bool audio_enable;
+ 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 +238,242 @@
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);
+
+ /* 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 +1015,7 @@
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 +1051,13 @@
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 +1092,27 @@
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 +1123,8 @@
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 */
@@ -1015,13 +1306,23 @@
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
- inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
- HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH :
- HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW;
+ if (hdmi->dev_type == RCAR_HDMI) {
+ inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
+ HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW :
+ HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH;
- inv_val |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
- HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH :
- HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW;
+ inv_val |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
+ HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW :
+ HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH;
+ } else {
+ inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
+ HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW;
+
+ inv_val |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
+ HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW;
+ }
inv_val |= (vmode->mdataenablepolarity ?
HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH :
@@ -1549,16 +1850,47 @@
.mode_fixup = dw_hdmi_bridge_mode_fixup,
};
+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)
@@ -1738,30 +2070,68 @@
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;
+ }
+
+ if (of_property_read_u32(np, "hdmi-ifclk", &val) == 0) {
+ ret = clk_set_rate(hdmi->isfr_clk, val);
+ if (ret) {
+ dev_err(hdmi->dev,
+ "Cannot set HDMI isfr clock rate: %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 */
@@ -1786,6 +2156,13 @@
*/
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.
@@ -1826,14 +2203,23 @@
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;
}
@@ -1852,15 +2238,23 @@
hdmi->connector.funcs->destroy(&hdmi->connector);
hdmi->encoder->funcs->destroy(hdmi->encoder);
- 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 971ab01..8609e43 100644
--- a/drivers/gpu/drm/rcar-du/Kconfig
+++ b/drivers/gpu/drm/rcar-du/Kconfig
@@ -15,6 +15,7 @@
config DRM_RCAR_HDMI
bool "R-Car DU HDMI Encoder Support"
depends on DRM_RCAR_DU
+ select DRM_DW_HDMI
help
Enable support for external HDMI encoders.
diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
index 827711e..7074a98 100644
--- a/drivers/gpu/drm/rcar-du/Makefile
+++ b/drivers/gpu/drm/rcar-du/Makefile
@@ -3,13 +3,14 @@
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_hdmicon.o \
rcar_du_hdmienc.o
-rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o
+
+rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdscon.o \
+ rcar_du_lvdsenc.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 e6eca8e..1dfb3a4 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>
@@ -106,14 +107,70 @@
* 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 +186,17 @@
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,7 +207,32 @@
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;
}
}
@@ -453,6 +545,20 @@
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->started = false;
+}
+
void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc)
{
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
@@ -649,6 +755,7 @@
rcrtc->mmio_offset = mmio_offsets[index];
rcrtc->index = index;
rcrtc->enabled = false;
+ rcrtc->lvds_ch = -1;
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE))
primary = &rcrtc->vsp->planes[0].plane;
@@ -687,6 +794,12 @@
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 c7ab467..064fb48 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)
*
@@ -54,6 +54,15 @@
struct rcar_du_group *group;
struct rcar_du_vsp *vsp;
+ int lvds_ch;
+};
+
+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)
@@ -63,6 +72,8 @@
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,
};
@@ -72,6 +83,7 @@
void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
struct drm_file *file);
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,
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
index 03fc576..344c7fb 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)
*
@@ -23,12 +23,20 @@
#include <drm/drmP.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_drv.h"
+#include "rcar_du_encoder.h"
+#include "rcar_du_hdmicon.h"
+#include "rcar_du_hdmienc.h"
#include "rcar_du_kms.h"
+#include "rcar_du_lvdsenc.h"
#include "rcar_du_regs.h"
/* -----------------------------------------------------------------------------
@@ -55,6 +63,7 @@
},
},
.num_lvds = 0,
+ .dpll_ch = 0,
};
static const struct rcar_du_device_info rcar_du_r8a7790_info = {
@@ -84,6 +93,7 @@
},
},
.num_lvds = 2,
+ .dpll_ch = 0,
};
/* M2-W (r8a7791) and M2-N (r8a7793) are identical */
@@ -108,6 +118,7 @@
},
},
.num_lvds = 1,
+ .dpll_ch = 0,
};
static const struct rcar_du_device_info rcar_du_r8a7794_info = {
@@ -131,6 +142,7 @@
},
},
.num_lvds = 0,
+ .dpll_ch = 0,
};
static const struct rcar_du_device_info rcar_du_r8a7795_info = {
@@ -141,13 +153,23 @@
.num_crtcs = 4,
.routes = {
/* R8A7795 has one RGB output, one LVDS output and two
- * (currently unsupported) HDMI outputs.
+ * HDMI outputs.
*/
[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 = DRM_MODE_ENCODER_TMDS,
+ .port = 1,
+ },
+ [RCAR_DU_OUTPUT_HDMI1] = {
+ .possible_crtcs = BIT(2),
+ .encoder_type = DRM_MODE_ENCODER_TMDS,
+ .port = 2,
+ },
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_LVDS,
@@ -155,6 +177,7 @@
},
},
.num_lvds = 1,
+ .dpll_ch = BIT(1) | BIT(2),
};
static const struct of_device_id rcar_du_of_table[] = {
@@ -172,7 +195,6 @@
/* -----------------------------------------------------------------------------
* DRM operations
*/
-
static void rcar_du_preclose(struct drm_device *dev, struct drm_file *file)
{
struct rcar_du_device *rcdu = dev->dev_private;
@@ -205,6 +227,37 @@
rcar_du_crtc_enable_vblank(&rcdu->crtcs[pipe], false);
}
+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;
+
+ 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);
+
+ return 0;
+}
+
+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),
+};
+
+
static const struct file_operations rcar_du_fops = {
.owner = THIS_MODULE,
.open = drm_open,
@@ -247,19 +300,42 @@
.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_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_suspend(&rcdu->crtcs[i]);
+ }
return 0;
}
@@ -267,9 +343,32 @@
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) {
+ if (!rcdu->crtcs[i].started)
+ rcar_du_crtc_resume(&rcdu->crtcs[i]);
+ }
+
+#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
+
+#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_enable(encoder);
+ }
+#endif
drm_kms_helper_poll_enable(rcdu->ddev);
return 0;
}
@@ -282,18 +381,52 @@
/* -----------------------------------------------------------------------------
* 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;
+#endif
+
+#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_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;
mutex_lock(&ddev->mode_config.mutex);
drm_connector_unplug_all(ddev);
mutex_unlock(&ddev->mode_config.mutex);
+ 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);
@@ -339,8 +472,6 @@
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);
@@ -361,6 +492,7 @@
/* DRM/KMS objects */
ret = rcar_du_modeset_init(rcdu);
if (ret < 0) {
+ platform_set_drvdata(pdev, rcdu);
dev_err(&pdev->dev, "failed to initialize DRM/KMS (%d)\n", ret);
goto error;
}
@@ -385,6 +517,8 @@
if (ret < 0)
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 ed35467..ebf3a55 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)
*
@@ -67,6 +67,7 @@
unsigned int num_crtcs;
struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
unsigned int num_lvds;
+ unsigned int dpll_ch;
};
#define RCAR_DU_MAX_CRTCS 4
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
index 4e939e4..bf95bef 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)
*
@@ -119,7 +119,8 @@
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;
@@ -163,8 +164,12 @@
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 719b6f2a..2766dd3 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)
*
@@ -34,6 +34,7 @@
enum rcar_du_output output;
struct rcar_du_hdmienc *hdmi;
struct rcar_du_lvdsenc *lvds;
+ const char *device_name;
};
#define to_rcar_encoder(e) \
@@ -56,6 +57,7 @@
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_hdmienc.c b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c
index 461662d..40b315d 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,11 +13,14 @@
#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 <drm/drm_encoder_slave.h>
+#include <linux/of_platform.h>
+
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
#include "rcar_du_hdmienc.h"
@@ -27,17 +30,23 @@
struct rcar_du_encoder *renc;
struct device *dev;
bool enabled;
+ unsigned int index;
};
#define to_rcar_hdmienc(e) (to_rcar_encoder(e)->hdmi)
#define to_slave_funcs(e) (to_rcar_encoder(e)->slave.slave_funcs)
-static void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
+void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
- if (sfuncs->dpms)
+ const struct drm_bridge_funcs *bfuncs = encoder->bridge->funcs;
+
+ if ((bfuncs) && (bfuncs->post_disable))
+ bfuncs->post_disable(encoder->bridge);
+
+ if ((sfuncs) && (sfuncs->dpms))
sfuncs->dpms(encoder, DRM_MODE_DPMS_OFF);
if (hdmienc->renc->lvds)
@@ -47,16 +56,20 @@
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_encoder_slave_funcs *sfuncs = to_slave_funcs(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 (sfuncs->dpms)
+ if ((bfuncs) && (bfuncs->enable))
+ bfuncs->enable(encoder->bridge);
+
+ if ((sfuncs) && (sfuncs->dpms))
sfuncs->dpms(encoder, DRM_MODE_DPMS_ON);
hdmienc->enabled = true;
@@ -68,17 +81,26 @@
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(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);
- if (sfuncs->mode_fixup == NULL)
+ if ((sfuncs) && (sfuncs->mode_fixup == NULL))
return 0;
- return sfuncs->mode_fixup(encoder, mode, adjusted_mode) ? 0 : -EINVAL;
+ if ((sfuncs) && (sfuncs->mode_fixup))
+ ret = sfuncs->mode_fixup(encoder, mode,
+ adjusted_mode) ? 0 : -EINVAL;
+
+ 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,
@@ -87,10 +109,14 @@
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
+ const struct drm_bridge_funcs *bfuncs = encoder->bridge->funcs;
- if (sfuncs->mode_set)
+ if ((sfuncs) && (sfuncs->mode_set))
sfuncs->mode_set(encoder, mode, adjusted_mode);
+ if ((bfuncs) && (bfuncs->mode_set))
+ bfuncs->mode_set(encoder->bridge, mode, adjusted_mode);
+
rcar_du_crtc_route_output(encoder->crtc, hdmienc->renc->output);
}
@@ -116,6 +142,139 @@
.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)
{
@@ -123,37 +282,71 @@
struct drm_i2c_encoder_driver *driver;
struct i2c_client *i2c_slave;
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;
hdmienc = devm_kzalloc(rcdu->dev, sizeof(*hdmienc), GFP_KERNEL);
if (hdmienc == NULL)
return -ENOMEM;
- /* Locate the slave I2C device and driver. */
- i2c_slave = of_find_i2c_device_by_node(np);
- if (!i2c_slave || !i2c_get_clientdata(i2c_slave)) {
- dev_dbg(rcdu->dev,
- "can't get I2C slave for %s, deferring probe\n",
- of_node_full_name(np));
- 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 slave I2C device and driver. */
+ i2c_slave = of_find_i2c_device_by_node(np);
+ if (!i2c_slave || !i2c_get_clientdata(i2c_slave))
+ return -EPROBE_DEFER;
+
+ hdmienc->dev = &i2c_slave->dev;
+
+ if (hdmienc->dev->driver == NULL) {
+ ret = -EPROBE_DEFER;
+ goto error;
+ }
+
+ /* Initialize the slave encoder. */
+ driver = to_drm_i2c_encoder_driver
+ (to_i2c_driver(hdmienc->dev->driver));
+ ret = driver->encoder_init(i2c_slave, rcdu->ddev, &renc->slave);
+ if (ret < 0)
+ goto error;
}
- hdmienc->dev = &i2c_slave->dev;
-
- if (hdmienc->dev->driver == NULL) {
- dev_dbg(rcdu->dev,
- "I2C slave %s not probed yet, deferring probe\n",
- dev_name(hdmienc->dev));
- ret = -EPROBE_DEFER;
- goto error;
- }
-
- /* Initialize the slave encoder. */
- driver = to_drm_i2c_encoder_driver(to_i2c_driver(hdmienc->dev->driver));
- ret = driver->encoder_init(i2c_slave, rcdu->ddev, &renc->slave);
- if (ret < 0)
- goto error;
-
ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
if (ret < 0)
@@ -164,7 +357,11 @@
renc->hdmi = hdmienc;
hdmienc->renc = renc;
- return 0;
+ if (dw_hdmi_use)
+ ret = dw_hdmi_bind(rcdu->dev, NULL, rcdu->ddev, encoder,
+ iores, irq, plat_data);
+
+ return ret;
error:
put_device(hdmienc->dev);
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h
index 2ff0128..ad32651 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,8 @@
#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);
#else
static inline int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc,
@@ -30,6 +32,12 @@
{
return -ENOSYS;
}
+static inline void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
+{
+}
+static inline void rcar_du_hdmienc_enable(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 7d65251..c52ee77 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)
*
@@ -96,6 +96,40 @@
.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,
+ }, {
+ .fourcc = DRM_FORMAT_ARGB4444,
+ .bpp = 16,
+ }, {
+ .fourcc = DRM_FORMAT_XRGB4444,
+ .bpp = 16,
+ }, {
+ .fourcc = DRM_FORMAT_BGR888,
+ .bpp = 24,
+ }, {
+ .fourcc = DRM_FORMAT_RGB888,
+ .bpp = 24,
+ }, {
+ .fourcc = DRM_FORMAT_BGRA8888,
+ .bpp = 32,
+ }, {
+ .fourcc = DRM_FORMAT_BGRX8888,
+ .bpp = 32,
+ }, {
+ .fourcc = DRM_FORMAT_YVYU,
+ .bpp = 16,
},
};
@@ -111,6 +145,18 @@
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
*/
@@ -146,6 +192,15 @@
unsigned int bpp;
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);
@@ -179,6 +234,7 @@
}
}
+done:
return drm_fb_cma_create(dev, file_priv, mode_cmd);
}
@@ -329,6 +385,7 @@
} 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 },
};
@@ -339,6 +396,7 @@
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
@@ -390,6 +448,7 @@
if (of_device_is_compatible(encoder,
encoders[i].compatible)) {
enc_type = encoders[i].type;
+ enc_name = encoders[i].compatible;
break;
}
}
@@ -410,7 +469,8 @@
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);
@@ -520,8 +580,8 @@
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;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.h b/drivers/gpu/drm/rcar-du/rcar_du_kms.h
index 07951d5..14c81ba 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-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
@@ -30,6 +30,7 @@
};
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);
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
index d4881ee..3d653cb 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-2015 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 ef3a503..421554a 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>
@@ -31,6 +32,7 @@
bool enabled;
enum rcar_lvds_input input;
+ int gpio_pd;
};
static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
@@ -125,7 +127,7 @@
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;
@@ -134,6 +136,9 @@
if (lvds->enabled)
return 0;
+ 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;
@@ -164,15 +169,16 @@
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)
{
if (!lvds->enabled)
- return;
+ return -1;
rcar_lvds_write(lvds, LVDCR0, 0);
rcar_lvds_write(lvds, LVDCR1, 0);
@@ -180,6 +186,30 @@
clk_disable_unprepare(lvds->clock);
lvds->enabled = false;
+
+ 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;
+
+ if (gpio_is_valid(lvds->gpio_pd))
+ gpio_set_value(lvds->gpio_pd, 0);
+
+ return;
}
int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc,
@@ -238,6 +268,7 @@
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);
@@ -250,12 +281,18 @@
lvds->index = i;
lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
lvds->enabled = false;
+ lvds->gpio_pd = of_get_named_gpio(rcdu->dev->of_node,
+ "backlight-pin", 0);
ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
if (ret < 0)
return ret;
rcdu->lvds[i] = lvds;
+
+ sprintf(name, "lvds%u", i);
+ 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 dfdba74..9fcfcee 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)
*
@@ -32,6 +32,9 @@
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)
{
@@ -46,6 +49,15 @@
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 b18b7b2..a627c56 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 @@
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 d2f6606..2fc1455 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)
*
@@ -276,6 +276,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 a03bab1..1279c98 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-2015 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 drm_add_modes_noedid(connector, 1280, 768);
+ 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 24acee1..268f29f 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_vsp.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_vsp.c
@@ -99,7 +99,6 @@
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_UYVY,
- DRM_FORMAT_VYUY,
DRM_FORMAT_YUYV,
DRM_FORMAT_YVYU,
DRM_FORMAT_NV12,
@@ -117,12 +116,11 @@
V4L2_PIX_FMT_RGB565,
V4L2_PIX_FMT_RGB24,
V4L2_PIX_FMT_BGR24,
- V4L2_PIX_FMT_ARGB32,
- V4L2_PIX_FMT_XRGB32,
V4L2_PIX_FMT_ABGR32,
V4L2_PIX_FMT_XBGR32,
+ V4L2_PIX_FMT_ARGB32,
+ V4L2_PIX_FMT_XRGB32,
V4L2_PIX_FMT_UYVY,
- V4L2_PIX_FMT_VYUY,
V4L2_PIX_FMT_YUYV,
V4L2_PIX_FMT_YVYU,
V4L2_PIX_FMT_NV12M,
@@ -171,7 +169,7 @@
WARN_ON(!pixelformat);
vsp1_du_atomic_update(plane->vsp->vsp, plane->index, pixelformat,
- fb->pitches[0], paddr, &src, &dst);
+ fb->pitches[0], paddr, &src, &dst, state->alpha);
}
static int rcar_du_vsp_plane_atomic_check(struct drm_plane *plane,
@@ -193,6 +191,11 @@
}
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);
@@ -211,7 +214,7 @@
rcar_du_vsp_plane_setup(rplane);
else
vsp1_du_atomic_update(rplane->vsp->vsp, rplane->index, 0, 0, 0,
- NULL, NULL);
+ NULL, NULL, 255);
}
static const struct drm_plane_helper_funcs rcar_du_vsp_plane_helper_funcs = {
diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c
index 1abeadc..6c32d90 100644
--- a/drivers/i2c/busses/i2c-rcar.c
+++ b/drivers/i2c/busses/i2c-rcar.c
@@ -680,9 +680,30 @@
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int rcar_i2c_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int rcar_i2c_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_i2c_pm_ops,
+ rcar_i2c_suspend, 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 7d2bd3e..522d79a 100644
--- a/drivers/i2c/busses/i2c-sh_mobile.c
+++ b/drivers/i2c/busses/i2c-sh_mobile.c
@@ -1013,7 +1013,27 @@
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int sh_mobile_i2c_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int sh_mobile_i2c_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+#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 = {
+ .suspend = sh_mobile_i2c_suspend,
+ .resume = sh_mobile_i2c_resume,
.runtime_suspend = sh_mobile_i2c_runtime_nop,
.runtime_resume = sh_mobile_i2c_runtime_nop,
};
@@ -1032,7 +1052,7 @@
{
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/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 993dc50..19acc7d 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -204,6 +204,18 @@
To compile this driver as a module, choose M here: the
module will be called adv7183.
+config VIDEO_ADV7482
+ tristate "Analog Devices ADV7482 HDMI receiver and video decoder"
+ depends on VIDEO_V4L2 && I2C
+ ---help---
+ Support for the Analog Devices ADV7482 HDMI receiver.
+ Support for the Analog Devices ADV7482 video decoder.
+ Video capture data is the HD digital signal that is
+ received by the Analog Devices ADV7482 HDMI receiver.
+
+ 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 94f2c99..a39ee28 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -25,6 +25,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..a59ab90
--- /dev/null
+++ b/drivers/media/i2c/adv7482.c
@@ -0,0 +1,2118 @@
+/*
+ * 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 <media/v4l2-ioctl.h>
+#include <linux/videodev2.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) */
+
+ {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 autodetect;
+ bool powered;
+ 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 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 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_reg_10 < 0)
+ return (int)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)
+ /* when we are interrupt driven we know the state */
+ if (!state->autodetect)
+ *std = state->curr_norm;
+ else
+ err = __adv7482_status(state, NULL, std);
+ else
+ *std = V4L2_STD_ATSC;
+
+ 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 < 0)
+ dev_info(state->dev,
+ "Not detect any 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_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;
+}
+
+/*
+ * adv7482_cropcap() - V4L2 decoder i/f handler for cropcap
+ * @sd: pointer to standard V4L2 sub-device structure
+ * @a: pointer to standard V4L2 cropcap structure
+ *
+ * Gets cropping limits, default cropping rectangle and pixel aspect.
+ */
+static int adv7482_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
+{
+ struct adv7482_state *state = to_state(sd);
+ u8 progressive;
+ u32 width;
+ u32 height;
+ int ret;
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+
+ /* cropping limits */
+ a->bounds.left = 0;
+ a->bounds.top = 0;
+
+ if (config->input_interface == DECODER_INPUT_INTERFACE_YCBCR422) {
+ ret = __adv7482_status(state, NULL, &state->curr_norm);
+ if (ret < 0)
+ return ret;
+
+ a->bounds.width = 720;
+ a->bounds.height = state->curr_norm & V4L2_STD_525_60
+ ? 480 : 576;
+ } else {
+ /* Get video information */
+ ret = adv7482_get_vid_info(sd, &progressive,
+ &width, &height, NULL);
+ if (ret < 0) {
+ a->bounds.width = ADV7482_MAX_WIDTH;
+ a->bounds.height = ADV7482_MAX_HEIGHT;
+ } else {
+ a->bounds.width = width;
+ a->bounds.height = height;
+ }
+ }
+
+ /* default cropping rectangle */
+ a->defrect = a->bounds;
+
+ /* does not support scaling */
+ a->pixelaspect.numerator = 1;
+ a->pixelaspect.denominator = 1;
+ a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ return 0;
+}
+
+/*
+ * adv7482_g_crop() - V4L2 decoder i/f handler for g_crop
+ * @sd: pointer to standard V4L2 sub-device structure
+ * @a: pointer to standard V4L2 cropcap structure
+ *
+ * Gets current cropping rectangle.
+ */
+static int adv7482_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
+{
+ struct adv7482_state *state = to_state(sd);
+ u8 progressive;
+ u32 width;
+ u32 height;
+ int ret;
+
+ struct adv7482_link_config *config = &state->mipi_csi2_link[0];
+
+ a->c.left = 0;
+ a->c.top = 0;
+
+ if (config->input_interface == DECODER_INPUT_INTERFACE_YCBCR422) {
+ ret = __adv7482_status(state, NULL, &state->curr_norm);
+ if (ret < 0)
+ return ret;
+
+ a->c.width = 720;
+ a->c.height = state->curr_norm & V4L2_STD_525_60
+ ? 480 : 576;
+ } else {
+ /* Get video information */
+ ret = adv7482_get_vid_info(sd, &progressive,
+ &width, &height, NULL);
+ if (ret < 0) {
+ a->c.width = ADV7482_MAX_WIDTH;
+ a->c.height = ADV7482_MAX_HEIGHT;
+ } else {
+ a->c.width = width;
+ a->c.height = height;
+ }
+ }
+
+ a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ return 0;
+}
+
+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_power(struct v4l2_subdev *sd, int on)
+{
+ struct adv7482_state *state = to_state(sd);
+ int ret;
+
+ ret = mutex_lock_interruptible(&state->mutex);
+ if (ret)
+ return ret;
+
+ ret = adv7482_set_power(state, on);
+ if (ret == 0)
+ state->powered = on;
+
+ 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_power(state, false);
+ adv7482_set_field_mode(state);
+ adv7482_set_power(state, true);
+ }
+ } 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_core_ops adv7482_core_ops = {
+ .queryctrl = v4l2_subdev_queryctrl,
+ .g_ctrl = v4l2_subdev_g_ctrl,
+ .s_ctrl = v4l2_subdev_s_ctrl,
+ .g_ext_ctrls = v4l2_subdev_g_ext_ctrls,
+ .s_ext_ctrls = v4l2_subdev_s_ext_ctrls,
+ .try_ext_ctrls = v4l2_subdev_try_ext_ctrls,
+ .querymenu = v4l2_subdev_querymenu,
+ .s_power = adv7482_s_power,
+};
+
+static const struct v4l2_subdev_video_ops adv7482_video_ops = {
+ .querystd = adv7482_querystd,
+ .g_input_status = adv7482_g_input_status,
+ .cropcap = adv7482_cropcap,
+ .g_crop = adv7482_g_crop,
+ .g_mbus_config = adv7482_g_mbus_config,
+};
+
+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 = {
+ .core = &adv7482_core_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->autodetect = true;
+ state->powered = true;
+ 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 AVD7482 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 AVD7482 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_T_V4L2_SUBDEV_DECODER;
+ ret = media_entity_init(&sd->entity, 1, &state->pad, 0);
+ 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 AVD7482 to its default values */
+ ret = adv7482_write_registers(client, link_config.regs);
+
+ 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/platform/Kconfig b/drivers/media/platform/Kconfig
index 0c53805..77d1195 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -254,6 +254,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/soc_camera/Kconfig b/drivers/media/platform/soc_camera/Kconfig
index f2776cd..f1c3646 100644
--- a/drivers/media/platform/soc_camera/Kconfig
+++ b/drivers/media/platform/soc_camera/Kconfig
@@ -43,6 +43,22 @@
---help---
This is a v4l2 driver for the R-Car VIN Interface
+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_DEV && SOC_CAMERA && HAVE_CLK
+ depends on ARCH_R8A7795 || COMPILE_TEST
+ ---help---
+ This is a v4l2 driver for the R-Car CSI-2 Interface
+
config VIDEO_SH_MOBILE_CSI2
tristate "SuperH Mobile MIPI CSI-2 Interface driver"
depends on VIDEO_DEV && SOC_CAMERA && HAVE_CLK
diff --git a/drivers/media/platform/soc_camera/Makefile b/drivers/media/platform/soc_camera/Makefile
index 2826382..8bcd617 100644
--- a/drivers/media/platform/soc_camera/Makefile
+++ b/drivers/media/platform/soc_camera/Makefile
@@ -13,4 +13,5 @@
obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o
obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o
obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o
+obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar_csi2.o
obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar_vin.o
diff --git a/drivers/media/platform/soc_camera/rcar_csi2.c b/drivers/media/platform/soc_camera/rcar_csi2.c
new file mode 100644
index 0000000..888ecc8
--- /dev/null
+++ b/drivers/media/platform/soc_camera/rcar_csi2.c
@@ -0,0 +1,706 @@
+/*
+ * drivers/media/platform/soc_camera/rcar_csi2.c
+ * This file is the driver for the R-Car MIPI CSI-2 unit.
+ *
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
+ *
+ * This file is based on the drivers/media/platform/soc_camera/sh_mobile_csi2.c
+ *
+ * Driver for the SH-Mobile MIPI CSI-2 unit
+ *
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * 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/delay.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <linux/module.h>
+
+#include <media/rcar_csi2.h>
+#include <media/soc_camera.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mediabus.h>
+#include <media/v4l2-subdev.h>
+
+#include <media/v4l2-of.h>
+
+#define DRV_NAME "rcar_csi2"
+#define CONNECT_SLAVE_NAME "adv7482"
+#define VC_MAX_CHANNEL 4
+
+#define RCAR_CSI2_TREF 0x00
+#define RCAR_CSI2_SRST 0x04
+#define RCAR_CSI2_PHYCNT 0x08
+#define RCAR_CSI2_CHKSUM 0x0C
+#define RCAR_CSI2_VCDT 0x10
+
+#define RCAR_CSI2_VCDT2 0x14 /* Channel Data Type Select */
+#define RCAR_CSI2_FRDT 0x18 /* Frame Data Type Select */
+#define RCAR_CSI2_FLD 0x1C /* Field Detection Control */
+#define RCAR_CSI2_ASTBY 0x20 /* Automatic standby control */
+#define RCAR_CSI2_LNGDT0 0x28
+#define RCAR_CSI2_LNGDT1 0x2C
+#define RCAR_CSI2_INTEN 0x30
+#define RCAR_CSI2_INTCLOSE 0x34
+#define RCAR_CSI2_INTSTATE 0x38
+#define RCAR_CSI2_INTERRSTATE 0x3C
+
+#define RCAR_CSI2_SHPDAT 0x40
+#define RCAR_CSI2_SHPCNT 0x44
+
+#define RCAR_CSI2_LINKCNT 0x48
+#define RCAR_CSI2_LSWAP 0x4C
+#define RCAR_CSI2_PHTC 0x58
+#define RCAR_CSI2_PHYPLL 0x68
+
+#define RCAR_CSI2_PHEERM 0x74
+#define RCAR_CSI2_PHCLM 0x78
+#define RCAR_CSI2_PHDLM 0x7C
+
+#define RCAR_CSI2_PHYCNT_SHUTDOWNZ (1 << 17)
+#define RCAR_CSI2_PHYCNT_RSTZ (1 << 16)
+#define RCAR_CSI2_PHYCNT_ENABLECLK (1 << 4)
+#define RCAR_CSI2_PHYCNT_ENABLE_3 (1 << 3)
+#define RCAR_CSI2_PHYCNT_ENABLE_2 (1 << 2)
+#define RCAR_CSI2_PHYCNT_ENABLE_1 (1 << 1)
+#define RCAR_CSI2_PHYCNT_ENABLE_0 (1 << 0)
+
+#define RCAR_CSI2_VCDT_VCDTN_EN (1 << 15)
+#define RCAR_CSI2_VCDT_SEL_VCN (1 << 8)
+#define RCAR_CSI2_VCDT_SEL_DTN_ON (1 << 6)
+#define RCAR_CSI2_VCDT_SEL_DTN (1 << 0)
+
+#define RCAR_CSI2_LINKCNT_MONITOR_EN (1 << 31)
+#define RCAR_CSI2_LINKCNT_REG_MONI_PACT_EN (1 << 25)
+
+#define RCAR_CSI2_LSWAP_L3SEL_PLANE0 (0 << 6)
+#define RCAR_CSI2_LSWAP_L3SEL_PLANE1 (1 << 6)
+#define RCAR_CSI2_LSWAP_L3SEL_PLANE2 (2 << 6)
+#define RCAR_CSI2_LSWAP_L3SEL_PLANE3 (3 << 6)
+
+#define RCAR_CSI2_LSWAP_L2SEL_PLANE0 (0 << 4)
+#define RCAR_CSI2_LSWAP_L2SEL_PLANE1 (1 << 4)
+#define RCAR_CSI2_LSWAP_L2SEL_PLANE2 (2 << 4)
+#define RCAR_CSI2_LSWAP_L2SEL_PLANE3 (3 << 4)
+
+#define RCAR_CSI2_LSWAP_L1SEL_PLANE0 (0 << 2)
+#define RCAR_CSI2_LSWAP_L1SEL_PLANE1 (1 << 2)
+#define RCAR_CSI2_LSWAP_L1SEL_PLANE2 (2 << 2)
+#define RCAR_CSI2_LSWAP_L1SEL_PLANE3 (3 << 2)
+
+#define RCAR_CSI2_LSWAP_L0SEL_PLANE0 (0 << 0)
+#define RCAR_CSI2_LSWAP_L0SEL_PLANE1 (1 << 0)
+#define RCAR_CSI2_LSWAP_L0SEL_PLANE2 (2 << 0)
+#define RCAR_CSI2_LSWAP_L0SEL_PLANE3 (3 << 0)
+
+#define RCAR_CSI2_PHTC_TESTCLR (1 << 0)
+
+/* interrupt status registers */
+#define RCAR_CSI2_INTSTATE_EBD_CH1 (1 << 29)
+#define RCAR_CSI2_INTSTATE_LESS_THAN_WC (1 << 28)
+#define RCAR_CSI2_INTSTATE_AFIFO_OF (1 << 27)
+#define RCAR_CSI2_INTSTATE_VD4_START (1 << 26)
+#define RCAR_CSI2_INTSTATE_VD4_END (1 << 25)
+#define RCAR_CSI2_INTSTATE_VD3_START (1 << 24)
+#define RCAR_CSI2_INTSTATE_VD3_END (1 << 23)
+#define RCAR_CSI2_INTSTATE_VD2_START (1 << 22)
+#define RCAR_CSI2_INTSTATE_VD2_END (1 << 21)
+#define RCAR_CSI2_INTSTATE_VD1_START (1 << 20)
+#define RCAR_CSI2_INTSTATE_VD1_END (1 << 19)
+#define RCAR_CSI2_INTSTATE_SHP (1 << 18)
+#define RCAR_CSI2_INTSTATE_FSFE (1 << 17)
+#define RCAR_CSI2_INTSTATE_LNP (1 << 16)
+#define RCAR_CSI2_INTSTATE_CRC_ERR (1 << 15)
+#define RCAR_CSI2_INTSTATE_HD_WC_ZERO (1 << 14)
+#define RCAR_CSI2_INTSTATE_FRM_SEQ_ERR1 (1 << 13)
+#define RCAR_CSI2_INTSTATE_FRM_SEQ_ERR2 (1 << 12)
+#define RCAR_CSI2_INTSTATE_ECC_ERR (1 << 11)
+#define RCAR_CSI2_INTSTATE_ECC_CRCT_ERR (1 << 10)
+#define RCAR_CSI2_INTSTATE_LPDT_START (1 << 9)
+#define RCAR_CSI2_INTSTATE_LPDT_END (1 << 8)
+#define RCAR_CSI2_INTSTATE_ULPS_START (1 << 7)
+#define RCAR_CSI2_INTSTATE_ULPS_END (1 << 6)
+#define RCAR_CSI2_INTSTATE_RESERVED (1 << 5)
+#define RCAR_CSI2_INTSTATE_ERRSOTHS (1 << 4)
+#define RCAR_CSI2_INTSTATE_ERRSOTSYNCCHS (1 << 3)
+#define RCAR_CSI2_INTSTATE_ERRESC (1 << 2)
+#define RCAR_CSI2_INTSTATE_ERRSYNCESC (1 << 1)
+#define RCAR_CSI2_INTSTATE_ERRCONTROL (1 << 0)
+
+/* monitoring registers of interrupt error status */
+#define RCAR_CSI2_INTSTATE_ECC_ERR (1 << 11)
+#define RCAR_CSI2_INTSTATE_ECC_CRCT_ERR (1 << 10)
+#define RCAR_CSI2_INTSTATE_LPDT_START (1 << 9)
+#define RCAR_CSI2_INTSTATE_LPDT_END (1 << 8)
+#define RCAR_CSI2_INTSTATE_ULPS_START (1 << 7)
+#define RCAR_CSI2_INTSTATE_ULPS_END (1 << 6)
+#define RCAR_CSI2_INTSTATE_RESERVED (1 << 5)
+#define RCAR_CSI2_INTSTATE_ERRSOTHS (1 << 4)
+#define RCAR_CSI2_INTSTATE_ERRSOTSYNCCHS (1 << 3)
+#define RCAR_CSI2_INTSTATE_ERRESC (1 << 2)
+#define RCAR_CSI2_INTSTATE_ERRSYNCESC (1 << 1)
+#define RCAR_CSI2_INTSTATE_ERRCONTROL (1 << 0)
+
+enum chip_id {
+ RCAR_GEN3,
+ RCAR_GEN2,
+};
+
+enum decoder_input_interface {
+ DECODER_INPUT_INTERFACE_RGB888,
+ DECODER_INPUT_INTERFACE_YCBCR422,
+ DECODER_INPUT_INTERFACE_NONE,
+};
+
+/**
+ * struct rcar_csi2_link_config - Describes rcar_csi2 hardware configuration
+ * @input_colorspace: The input colorspace (RGB, YUV444, YUV422)
+ */
+struct rcar_csi2_link_config {
+ enum decoder_input_interface input_interface;
+ unsigned char lanes;
+ unsigned long vcdt;
+ unsigned long vcdt2;
+};
+
+#define INIT_RCAR_CSI2_LINK_CONFIG(m) \
+{ \
+ m.input_interface = DECODER_INPUT_INTERFACE_NONE; \
+ m.lanes = 0; \
+}
+
+struct rcar_csi_irq_counter_log {
+ unsigned long crc_err;
+};
+
+struct rcar_csi2 {
+ struct v4l2_subdev subdev;
+ struct v4l2_mbus_framefmt *mf;
+ unsigned int irq;
+ unsigned long mipi_flags;
+ void __iomem *base;
+ struct platform_device *pdev;
+ struct rcar_csi2_client_config *client;
+ unsigned long vcdt;
+ unsigned long vcdt2;
+
+ unsigned int field;
+ unsigned int code;
+ unsigned int lanes;
+ spinlock_t lock;
+};
+
+#define RCAR_CSI_80MBPS 0
+#define RCAR_CSI_90MBPS 1
+#define RCAR_CSI_100MBPS 2
+#define RCAR_CSI_110MBPS 3
+#define RCAR_CSI_120MBPS 4
+#define RCAR_CSI_130MBPS 5
+#define RCAR_CSI_140MBPS 6
+#define RCAR_CSI_150MBPS 7
+#define RCAR_CSI_160MBPS 8
+#define RCAR_CSI_170MBPS 9
+#define RCAR_CSI_180MBPS 10
+#define RCAR_CSI_190MBPS 11
+#define RCAR_CSI_205MBPS 12
+#define RCAR_CSI_220MBPS 13
+#define RCAR_CSI_235MBPS 14
+#define RCAR_CSI_250MBPS 15
+#define RCAR_CSI_275MBPS 16
+#define RCAR_CSI_300MBPS 17
+#define RCAR_CSI_325MBPS 18
+#define RCAR_CSI_350MBPS 19
+#define RCAR_CSI_400MBPS 20
+#define RCAR_CSI_450MBPS 21
+#define RCAR_CSI_500MBPS 22
+#define RCAR_CSI_550MBPS 23
+#define RCAR_CSI_600MBPS 24
+#define RCAR_CSI_650MBPS 25
+#define RCAR_CSI_700MBPS 26
+#define RCAR_CSI_750MBPS 27
+#define RCAR_CSI_800MBPS 28
+#define RCAR_CSI_850MBPS 29
+#define RCAR_CSI_900MBPS 30
+#define RCAR_CSI_950MBPS 31
+#define RCAR_CSI_1000MBPS 32
+#define RCAR_CSI_1050MBPS 33
+#define RCAR_CSI_1100MBPS 34
+#define RCAR_CSI_1150MBPS 35
+#define RCAR_CSI_1200MBPS 36
+#define RCAR_CSI_1250MBPS 37
+#define RCAR_CSI_1300MBPS 38
+#define RCAR_CSI_1350MBPS 39
+#define RCAR_CSI_1400MBPS 40
+#define RCAR_CSI_1450MBPS 41
+#define RCAR_CSI_1500MBPS 42
+
+static int rcar_csi2_set_phy_freq(struct rcar_csi2 *priv)
+{
+ const uint32_t const hs_freq_range[43] = {
+ 0x00, 0x10, 0x20, 0x30, 0x01, /* 0-4 */
+ 0x11, 0x21, 0x31, 0x02, 0x12, /* 5-9 */
+ 0x22, 0x32, 0x03, 0x13, 0x23, /* 10-14 */
+ 0x33, 0x04, 0x14, 0x05, 0x15, /* 15-19 */
+ 0x25, 0x06, 0x16, 0x07, 0x17, /* 20-24 */
+ 0x08, 0x18, 0x09, 0x19, 0x29, /* 25-29 */
+ 0x39, 0x0A, 0x1A, 0x2A, 0x3A, /* 30-34 */
+ 0x0B, 0x1B, 0x2B, 0x3B, 0x0C, /* 35-39 */
+ 0x1C, 0x2C, 0x3C /* 40-42 */
+ };
+ uint32_t bps_per_lane = RCAR_CSI_190MBPS;
+
+ dev_dbg(&priv->pdev->dev, "Input size (%dx%d%c)\n",
+ priv->mf->width, priv->mf->height,
+ (priv->mf->field == V4L2_FIELD_NONE) ? 'p' : 'i');
+
+ switch (priv->lanes) {
+ case 1:
+ bps_per_lane = RCAR_CSI_400MBPS;
+ break;
+ case 4:
+ if (priv->mf->field == V4L2_FIELD_NONE) {
+ if ((priv->mf->width == 1920) &&
+ (priv->mf->height == 1080))
+ bps_per_lane = RCAR_CSI_900MBPS;
+ else if ((priv->mf->width == 1280) &&
+ (priv->mf->height == 720))
+ bps_per_lane = RCAR_CSI_450MBPS;
+ else if ((priv->mf->width == 720) &&
+ (priv->mf->height == 480))
+ bps_per_lane = RCAR_CSI_190MBPS;
+ else if ((priv->mf->width == 720) &&
+ (priv->mf->height == 576))
+ bps_per_lane = RCAR_CSI_190MBPS;
+ else if ((priv->mf->width == 640) &&
+ (priv->mf->height == 480))
+ bps_per_lane = RCAR_CSI_100MBPS;
+ else
+ goto error;
+ } else {
+ if ((priv->mf->width == 1920) &&
+ (priv->mf->height == 1080))
+ bps_per_lane = RCAR_CSI_450MBPS;
+ else
+ goto error;
+ }
+ break;
+ default:
+ dev_err(&priv->pdev->dev, "ERROR: lanes is invalid (%d)\n",
+ priv->lanes);
+ return -EINVAL;
+ }
+
+ dev_dbg(&priv->pdev->dev, "bps_per_lane (%d)\n", bps_per_lane);
+
+ iowrite32((hs_freq_range[bps_per_lane] << 16),
+ priv->base + RCAR_CSI2_PHYPLL);
+ return 0;
+
+error:
+ dev_err(&priv->pdev->dev, "Not support resolution (%dx%d%c)\n",
+ priv->mf->width, priv->mf->height,
+ (priv->mf->field == V4L2_FIELD_NONE) ? 'p' : 'i');
+ return -EINVAL;
+}
+
+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 + RCAR_CSI2_INTSTATE);
+ if (!int_status)
+ goto done;
+
+ /* ack interrupts */
+ iowrite32(int_status, priv->base + RCAR_CSI2_INTSTATE);
+ handled = 1;
+
+done:
+ spin_unlock(&priv->lock);
+
+ return IRQ_RETVAL(handled);
+
+}
+
+static void rcar_csi2_hwdeinit(struct rcar_csi2 *priv)
+{
+ iowrite32(0, priv->base + RCAR_CSI2_PHYCNT);
+
+ /* reset CSI2 hardware */
+ iowrite32(0x00000001, priv->base + RCAR_CSI2_SRST);
+ udelay(5);
+ iowrite32(0x00000000, priv->base + RCAR_CSI2_SRST);
+}
+
+static int rcar_csi2_hwinit(struct rcar_csi2 *priv)
+{
+ int ret;
+ __u32 tmp = 0x10; /* Enable MIPI CSI clock lane */
+
+ /* Reflect registers immediately */
+ iowrite32(0x00000001, priv->base + RCAR_CSI2_TREF);
+ /* reset CSI2 hardware */
+ iowrite32(0x00000001, priv->base + RCAR_CSI2_SRST);
+ udelay(5);
+ iowrite32(0x00000000, priv->base + RCAR_CSI2_SRST);
+
+ iowrite32(0x00000000, priv->base + RCAR_CSI2_PHTC);
+
+ /* setting HS reception frequency */
+ {
+ switch (priv->lanes) {
+ case 1:
+ /* First field number setting */
+ iowrite32(0x0001000f, priv->base + RCAR_CSI2_FLD);
+ tmp |= 0x1;
+ break;
+ case 4:
+ /* First field number setting */
+ iowrite32(0x0002000f, priv->base + RCAR_CSI2_FLD);
+ tmp |= 0xF;
+ break;
+ default:
+ dev_err(&priv->pdev->dev,
+ "ERROR: lanes is invalid (%d)\n",
+ priv->lanes);
+ return -EINVAL;
+ }
+
+ /* set PHY frequency */
+ ret = rcar_csi2_set_phy_freq(priv);
+ if (ret < 0)
+ return ret;
+
+ /* Enable lanes */
+ iowrite32(tmp, priv->base + RCAR_CSI2_PHYCNT);
+
+ iowrite32(tmp | RCAR_CSI2_PHYCNT_SHUTDOWNZ,
+ priv->base + RCAR_CSI2_PHYCNT);
+ iowrite32(tmp | (RCAR_CSI2_PHYCNT_SHUTDOWNZ |
+ RCAR_CSI2_PHYCNT_RSTZ),
+ priv->base + RCAR_CSI2_PHYCNT);
+ }
+
+ iowrite32(0x00000003, priv->base + RCAR_CSI2_CHKSUM);
+ iowrite32(priv->vcdt, priv->base + RCAR_CSI2_VCDT);
+ iowrite32(priv->vcdt2, priv->base + RCAR_CSI2_VCDT2);
+ iowrite32(0x00010000, priv->base + RCAR_CSI2_FRDT);
+ udelay(10);
+ iowrite32(0x83000000, priv->base + RCAR_CSI2_LINKCNT);
+ iowrite32(0x000000e4, priv->base + RCAR_CSI2_LSWAP);
+
+ dev_dbg(&priv->pdev->dev, "CSI2 VCDT: 0x%x\n",
+ ioread32(priv->base + RCAR_CSI2_VCDT));
+ dev_dbg(&priv->pdev->dev, "CSI2 VCDT2: 0x%x\n",
+ ioread32(priv->base + RCAR_CSI2_VCDT2));
+
+ /* wait until video decoder power off */
+ msleep(10);
+ {
+ int timeout = 100;
+
+ /* Read the PHY clock lane monitor register (PHCLM). */
+ while (!(ioread32(priv->base + RCAR_CSI2_PHCLM) & 0x01)
+ && timeout) {
+ timeout--;
+ }
+ if (timeout == 0)
+ dev_err(&priv->pdev->dev,
+ "Timeout of reading the PHY clock lane\n");
+ else
+ dev_dbg(&priv->pdev->dev,
+ "Detected the PHY clock lane\n");
+
+ timeout = 100;
+
+ /* Read the PHY data lane monitor register (PHDLM). */
+ while (!(ioread32(priv->base + RCAR_CSI2_PHDLM) & 0x01)
+ && timeout) {
+ timeout--;
+ }
+ if (timeout == 0)
+ dev_err(&priv->pdev->dev,
+ "Timeout of reading the PHY data lane\n");
+ else
+ dev_dbg(&priv->pdev->dev,
+ "Detected the PHY data lane\n");
+ }
+
+ 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);
+ struct v4l2_subdev *tmp_sd;
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_mbus_framefmt *mf = &fmt.format;
+ int ret = 0;
+
+ if (on) {
+ v4l2_device_for_each_subdev(tmp_sd, sd->v4l2_dev) {
+ if (strncmp(tmp_sd->name, CONNECT_SLAVE_NAME,
+ sizeof(CONNECT_SLAVE_NAME) - 1) == 0) {
+ v4l2_subdev_call(tmp_sd, pad, get_fmt,
+ NULL, &fmt);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ priv->mf = mf;
+ pm_runtime_get_sync(&priv->pdev->dev);
+ ret = rcar_csi2_hwinit(priv);
+ if (ret < 0)
+ return ret;
+ } else {
+ rcar_csi2_hwdeinit(priv);
+ pm_runtime_put_sync(&priv->pdev->dev);
+ }
+
+ return ret;
+}
+
+static struct v4l2_subdev_core_ops rcar_csi2_subdev_core_ops = {
+ .s_power = rcar_csi2_s_power,
+};
+
+static struct v4l2_subdev_ops rcar_csi2_subdev_ops = {
+ .core = &rcar_csi2_subdev_core_ops,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id rcar_csi2_of_table[] = {
+ { .compatible = "renesas,csi2-r8a7795", .data = (void *)RCAR_GEN3 },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rcar_csi2_of_table);
+#endif
+
+static struct platform_device_id rcar_csi2_id_table[] = {
+ { "r8a7795-csi2", RCAR_GEN3 },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, rcar_csi2_id_table);
+
+static int rcar_csi2_parse_dt(struct device_node *np,
+ struct rcar_csi2_link_config *config)
+{
+ struct v4l2_of_endpoint bus_cfg;
+ struct device_node *endpoint;
+ struct device_node *vc_np, *vc_ch;
+ const char *str;
+ char csi_name[9];
+ int ret;
+ int i, 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);
+
+ config->lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
+
+ ret = of_property_read_string(np, "adi,input-interface", &str);
+ if (ret < 0)
+ return ret;
+
+ vc_np = of_get_child_by_name(np, "virtual,channel");
+
+ config->vcdt = 0;
+ config->vcdt2 = 0;
+ for (i = 0; i < VC_MAX_CHANNEL; i++) {
+ sprintf(csi_name, "csi2_vc%d", i);
+
+ vc_ch = of_get_child_by_name(vc_np, csi_name);
+ if (!vc_ch)
+ continue;
+ ret = of_property_read_string(vc_ch, "data,type", &str);
+ if (ret < 0)
+ return ret;
+ ret = of_property_read_u32(vc_ch, "receive,vc", &ch);
+ if (ret < 0)
+ return ret;
+
+ if (i < 2) {
+ if (!strcmp(str, "rgb888"))
+ config->vcdt |= (0x24 << (i * 16));
+ else if (!strcmp(str, "ycbcr422"))
+ config->vcdt |= (0x1e << (i * 16));
+ else
+ config->vcdt |= 0;
+
+ config->vcdt |= (ch << (8 + (i * 16)));
+ config->vcdt |= (RCAR_CSI2_VCDT_VCDTN_EN << (i * 16)) |
+ (RCAR_CSI2_VCDT_SEL_DTN_ON << (i * 16));
+ }
+ if (i >= 2) {
+ int j = (i - 2);
+
+ if (!strcmp(str, "rgb888"))
+ config->vcdt2 |= (0x24 << (j * 16));
+ else if (!strcmp(str, "ycbcr422"))
+ config->vcdt2 |= (0x1e << (j * 16));
+ else
+ config->vcdt2 |= 0;
+
+ config->vcdt2 |= (ch << (8 + (j * 16)));
+ config->vcdt2 |= (RCAR_CSI2_VCDT_VCDTN_EN << (j * 16)) |
+ (RCAR_CSI2_VCDT_SEL_DTN_ON << (j * 16));
+ }
+ }
+
+ return 0;
+}
+
+static int rcar_csi2_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ unsigned int irq;
+ int ret;
+ struct rcar_csi2 *priv;
+ /* Platform data specify the PHY, lanes, ECC, CRC */
+ struct rcar_csi2_pdata *pdata;
+ struct rcar_csi2_link_config link_config;
+
+ dev_dbg(&pdev->dev, "CSI2 probed.\n");
+
+ INIT_RCAR_CSI2_LINK_CONFIG(link_config);
+
+ if (pdev->dev.of_node) {
+ ret = rcar_csi2_parse_dt(pdev->dev.of_node, &link_config);
+ if (ret)
+ return ret;
+
+ if (link_config.lanes == 4)
+ dev_info(&pdev->dev,
+ "Detected rgb888 in rcar_csi2_parse_dt\n");
+ else
+ dev_info(&pdev->dev,
+ "Detected YCbCr422 in rcar_csi2_parse_dt\n");
+ } else {
+ pdata = pdev->dev.platform_data;
+ if (!pdata)
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct rcar_csi2), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ /* Interrupt unused so far */
+ irq = platform_get_irq(pdev, 0);
+
+ if (!res || (int)irq <= 0) {
+ dev_err(&pdev->dev, "Not enough CSI2 platform resources.\n");
+ return -ENODEV;
+ }
+
+ priv->irq = irq;
+
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ ret = devm_request_irq(&pdev->dev, irq, rcar_csi2_irq, IRQF_SHARED,
+ dev_name(&pdev->dev), priv);
+ if (ret)
+ return ret;
+
+ priv->pdev = pdev;
+ priv->subdev.owner = THIS_MODULE;
+ priv->subdev.dev = &pdev->dev;
+ priv->lanes = link_config.lanes;
+ priv->vcdt = link_config.vcdt;
+ priv->vcdt2 = link_config.vcdt2;
+
+ platform_set_drvdata(pdev, &priv->subdev);
+
+ 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;
+
+ spin_lock_init(&priv->lock);
+
+ pm_runtime_enable(&pdev->dev);
+
+ dev_dbg(&pdev->dev, "CSI2 probed.\n");
+
+ return 0;
+}
+
+static int rcar_csi2_remove(struct platform_device *pdev)
+{
+ struct v4l2_subdev *subdev = platform_get_drvdata(pdev);
+ struct rcar_csi2 *priv = container_of(subdev, struct rcar_csi2, subdev);
+
+ v4l2_async_unregister_subdev(&priv->subdev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rcar_csi2_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int rcar_csi2_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_csi2_pm_ops,
+ rcar_csi2_suspend, rcar_csi2_resume);
+#define DEV_PM_OPS (&rcar_csi2_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct platform_driver __refdata rcar_csi2_pdrv = {
+ .remove = rcar_csi2_remove,
+ .probe = rcar_csi2_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .pm = DEV_PM_OPS,
+ .of_match_table = of_match_ptr(rcar_csi2_of_table),
+ },
+ .id_table = rcar_csi2_id_table,
+};
+
+module_platform_driver(rcar_csi2_pdrv);
+
+MODULE_DESCRIPTION("Renesas R-Car MIPI CSI-2 driver");
+MODULE_AUTHOR("Koji Matsuoka <koji.matsuoka.xm@renesas.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:rcar-csi2");
diff --git a/drivers/media/platform/soc_camera/rcar_vin.c b/drivers/media/platform/soc_camera/rcar_vin.c
index b7fd695..cde52ce 100644
--- a/drivers/media/platform/soc_camera/rcar_vin.c
+++ b/drivers/media/platform/soc_camera/rcar_vin.c
@@ -1,6 +1,7 @@
/*
* SoC-camera host driver for Renesas R-Car VIN unit
*
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
* Copyright (C) 2011-2013 Renesas Solutions Corp.
* Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com>
*
@@ -14,6 +15,10 @@
* option) any later version.
*/
+#ifdef CONFIG_VIDEO_RCAR_VIN_DEBUG
+#define DEBUG
+#endif
+
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
@@ -25,6 +30,7 @@
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/videodev2.h>
+#include <linux/list.h>
#include <media/soc_camera.h>
#include <media/drv-intf/soc_mediabus.h>
@@ -36,6 +42,8 @@
#include <media/v4l2-subdev.h>
#include <media/videobuf2-dma-contig.h>
+#include <media/rcar_csi2.h>
+
#include "soc_scale_crop.h"
#define DRV_NAME "rcar_vin"
@@ -90,6 +98,8 @@
/* Register bit fields for R-Car VIN */
/* Video n Main Control Register bits */
+#define VNMC_DPINE (1 << 27)
+#define VNMC_SCLE (1 << 26)
#define VNMC_FOC (1 << 21)
#define VNMC_YCAL (1 << 19)
#define VNMC_INF_YUV8_BT656 (0 << 16)
@@ -98,6 +108,7 @@
#define VNMC_INF_YUV10_BT601 (3 << 16)
#define VNMC_INF_YUV16 (5 << 16)
#define VNMC_INF_RGB888 (6 << 16)
+#define VNMC_INF_MASK (7 << 16)
#define VNMC_VUP (1 << 10)
#define VNMC_IM_ODD (0 << 3)
#define VNMC_IM_ODD_EVEN (1 << 3)
@@ -119,12 +130,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)
@@ -132,8 +150,25 @@
#define VNDMR2_FTEV (1 << 17)
#define VNDMR2_VLV(n) ((n & 0xf) << 12)
-#define VIN_MAX_WIDTH 2048
-#define VIN_MAX_HEIGHT 2048
+/* setting CSI2 on R-Car Gen3*/
+#define VNCSI_IFMD_REG 0x20 /* Video n CSI2 Interface Mode Register */
+
+#define VNCSI_IFMD_DES2 (1 << 27) /* CSI40/CSI41 Input Data */
+#define VNCSI_IFMD_DES1 (1 << 26) /* CSI20 Input Data) */
+#define VNCSI_IFMD_DES0 (1 << 25) /* CSI21 Input Data) */
+
+#define VNCSI_IFMD_CSI_CHSEL(n) (n << 0)
+
+/* UDS */
+#define VNUDS_CTRL_REG 0x80 /* Scaling Control Registers */
+#define VNUDS_CTRL_AMD (1 << 30)
+#define VNUDS_CTRL_BC (1 << 20)
+#define VNUDS_CTRL_TDIPC (1 << 1)
+
+#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 */
#define TIMEOUT_MS 100
@@ -141,14 +176,143 @@
#define RCAR_VIN_VSYNC_ACTIVE_LOW (1 << 1)
#define RCAR_VIN_BT601 (1 << 2)
#define RCAR_VIN_BT656 (1 << 3)
+#define RCAR_VIN_CSI2 (1 << 4)
+
+static int ifmd0_reg_match[6];
+static int ifmd4_reg_match[6];
+static int ifmd0_init = true;
+static int ifmd4_init = true;
enum chip_id {
+ RCAR_GEN3,
RCAR_GEN2,
RCAR_H1,
RCAR_M1,
RCAR_E1,
};
+enum csi2_ch {
+ RCAR_CSI_CH_NONE = -1,
+ RCAR_CSI40,
+ RCAR_CSI20,
+ RCAR_CSI41,
+ RCAR_CSI21,
+ RCAR_CSI_MAX,
+};
+
+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,
+};
+
+enum virtual_ch {
+ RCAR_VIRTUAL_NONE = -1,
+ RCAR_VIRTUAL_CH0,
+ RCAR_VIRTUAL_CH1,
+ RCAR_VIRTUAL_CH2,
+ RCAR_VIRTUAL_CH3,
+ RCAR_VIRTUAL_MAX,
+};
+
+struct vin_gen3_virtual_sel {
+ enum csi2_ch csi2_ch;
+ enum virtual_ch vc;
+};
+
+struct vin_gen3_ifmd {
+ unsigned int set_reg;
+ struct vin_gen3_virtual_sel v_sel[8];
+};
+
+static const struct vin_gen3_ifmd vin_vc_ifmd[] = {
+ { 0x0000,
+ {
+ {RCAR_CSI40, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI40, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI41, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI41, RCAR_VIRTUAL_CH1},
+ }
+ },
+ { 0x0001,
+ {
+ {RCAR_CSI20, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI40, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI41, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH1},
+ }
+ },
+ { 0x0002,
+ {
+ {RCAR_CSI21, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI40, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI41, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH1},
+ }
+ },
+ { 0x0003,
+ {
+ {RCAR_CSI40, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI40, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI40, RCAR_VIRTUAL_CH2},
+ {RCAR_CSI40, RCAR_VIRTUAL_CH3},
+ {RCAR_CSI41, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI41, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI41, RCAR_VIRTUAL_CH2},
+ {RCAR_CSI41, RCAR_VIRTUAL_CH3},
+ }
+ },
+ { 0x0004,
+ {
+ {RCAR_CSI20, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH2},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH3},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH2},
+ {RCAR_CSI20, RCAR_VIRTUAL_CH3},
+ }
+ },
+ { 0x0005,
+ {
+ {RCAR_CSI21, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH2},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH3},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH0},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH1},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH2},
+ {RCAR_CSI21, RCAR_VIRTUAL_CH3},
+ }
+ },
+};
+
+enum csi2_fmt {
+ RCAR_CSI_FMT_NONE = -1,
+ RCAR_CSI_RGB888,
+ RCAR_CSI_YCBCR422,
+};
+
struct vin_coeff {
unsigned short xs_value;
u32 coeff_set[24];
@@ -473,6 +637,19 @@
STOPPING,
};
+struct rcar_vin_async_client {
+ struct v4l2_async_subdev *sensor;
+ struct v4l2_async_notifier notifier;
+ struct platform_device *pdev;
+ struct list_head list; /* needed for clean up */
+};
+
+struct soc_of_info {
+ struct soc_camera_async_subdev sasd;
+ struct rcar_vin_async_client sasc;
+ struct v4l2_async_subdev *subdev;
+};
+
struct rcar_vin_priv {
void __iomem *base;
spinlock_t lock;
@@ -491,6 +668,21 @@
bool request_to_stop;
struct completion capture_stop;
enum chip_id chip;
+ unsigned int max_width;
+ unsigned int max_height;
+ bool error_flag;
+ enum csi2_ch csi_ch;
+ enum csi2_fmt csi_fmt;
+ enum virtual_ch vc;
+ bool csi_sync;
+
+ struct rcar_vin_async_client *async_client;
+ /* Asynchronous CSI2 linking */
+ struct v4l2_subdev *csi2_sd;
+ /* Synchronous probing compatibility */
+ struct platform_device *csi2_pdev;
+
+ unsigned int index;
};
#define is_continuous_transfer(priv) (priv->vb_count > MAX_BUFFER_NUM)
@@ -525,6 +717,69 @@
const struct soc_mbus_pixelfmt *extra_fmt;
};
+#define VIN_UT_IRQ 0x01
+
+static unsigned 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 rcar_vin_cpg_enable_for_ifmd(unsigned int ch, bool enable)
+{
+ void __iomem *smstpcr8;
+
+ smstpcr8 = ioremap(0xE6150990, 0x04);
+
+ if (enable) {
+ if (ch < RCAR_VIDEO_4)
+ iowrite32((ioread32(smstpcr8) & 0xFFFFF7FF), smstpcr8);
+ else
+ iowrite32((ioread32(smstpcr8) & 0xFFFFFF7F), smstpcr8);
+ } else {
+ if (ch < RCAR_VIDEO_4)
+ iowrite32((ioread32(smstpcr8) | 0x00000800), smstpcr8);
+ else
+ iowrite32((ioread32(smstpcr8) | 0x00000080), smstpcr8);
+ }
+
+ iounmap(smstpcr8);
+}
+
+static inline int is_scaling(struct rcar_vin_cam *cam)
+{
+ struct v4l2_rect *cam_subrect = &cam->subrect;
+
+ if ((cam_subrect->width != cam->out_width) ||
+ (cam_subrect->height != cam->out_height))
+ return 1;
+
+ return 0;
+}
+
/*
* .queue_setup() is called to check whether the driver can accept the requested
* number of buffers and to fill in plane sizes for the current frame format if
@@ -538,6 +793,18 @@
struct soc_camera_device *icd = soc_camera_from_vb2q(vq);
struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
struct rcar_vin_priv *priv = ici->priv;
+ struct rcar_vin_cam *cam = icd->host_priv;
+
+ if (priv->chip == RCAR_GEN3) {
+ if (is_scaling(cam) && (cam->out_width % 32)) {
+ dev_err(icd->parent, "Scaling parameter error\n");
+ return -EINVAL;
+ }
+ if (!is_scaling(cam) && (cam->out_width % 16)) {
+ dev_err(icd->parent, "Image stride parameter error\n");
+ return -EINVAL;
+ }
+ }
alloc_ctxs[0] = priv->alloc_ctx;
@@ -627,8 +894,19 @@
/* output format */
switch (icd->current_fmt->host_fmt->fourcc) {
+ case V4L2_PIX_FMT_NV12:
+ if (priv->chip == RCAR_GEN3) {
+ iowrite32(ALIGN((cam->out_width * cam->out_height),
+ 0x80), priv->base + VNUVAOF_REG);
+ dmr = VNDMR_DTMD_YCSEP_YCBCR420;
+ output_is_yuv = true;
+ } else {
+ dev_warn(icd->parent, "Not support format\n");
+ return -EINVAL;
+ }
+ break;
case V4L2_PIX_FMT_NV16:
- iowrite32(ALIGN(cam->width * cam->height, 0x80),
+ iowrite32(ALIGN((cam->out_width * cam->out_height), 0x80),
priv->base + VNUVAOF_REG);
dmr = VNDMR_DTMD_YCSEP;
output_is_yuv = true;
@@ -641,18 +919,27 @@
dmr = 0;
output_is_yuv = true;
break;
- case V4L2_PIX_FMT_RGB555X:
- 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_RGB32:
- if (priv->chip == RCAR_GEN2 || priv->chip == RCAR_H1 ||
+ if (priv->chip == RCAR_GEN3 ||
+ priv->chip == RCAR_GEN2 || priv->chip == RCAR_H1 ||
priv->chip == RCAR_E1) {
dmr = VNDMR_EXRGB;
break;
}
+ case V4L2_PIX_FMT_ARGB32:
+ if (priv->chip == RCAR_GEN3)
+ dmr = VNDMR_EXRGB | VNDMR_DTMD_ARGB;
+ else {
+ dev_err(icd->parent, "Not support format\n");
+ return -EINVAL;
+ }
+ break;
default:
dev_warn(icd->parent, "Invalid fourcc format (0x%x)\n",
icd->current_fmt->host_fmt->fourcc);
@@ -666,9 +953,24 @@
if (input_is_yuv == output_is_yuv)
vnmc |= VNMC_BPS;
+ if (priv->chip == RCAR_GEN3) {
+ if (priv->pdata_flags & RCAR_VIN_CSI2)
+ vnmc &= ~VNMC_DPINE;
+ else
+ vnmc |= VNMC_DPINE;
+
+ if ((icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_NV12)
+ && is_scaling(cam))
+ vnmc |= VNMC_SCLE;
+ }
+
/* progressive or interlaced mode */
interrupts = progressive ? VNIE_FIE : VNIE_EFE;
+ /* Enable Overflow */
+ if (vin_debug)
+ interrupts |= VNIE_FOE;
+
/* ack interrupts */
iowrite32(interrupts, priv->base + VNINTS_REG);
/* enable interrupts */
@@ -863,16 +1165,25 @@
bool can_run = false, hw_stopped;
int slot;
unsigned int handled = 0;
+ int vin_ovr_cnt = 0;
spin_lock(&priv->lock);
int_status = ioread32(priv->base + VNINTS_REG);
if (!int_status)
goto done;
+
/* ack interrupts */
iowrite32(int_status, priv->base + VNINTS_REG);
handled = 1;
+ /* overflow occurs */
+ if (vin_debug && (int_status & VNINTS_FOS)) {
+ vin_ovr_cnt = ++overflow_video[priv->index];
+ VIN_IRQ_DEBUG("overflow occurrs num[%d] at VIN (%s)\n",
+ vin_ovr_cnt, dev_name(priv->ici.v4l2_dev.dev));
+ }
+
/* nothing to do if capture status is 'STOPPED' */
if (priv->state == STOPPED)
goto done;
@@ -923,6 +1234,21 @@
return IRQ_RETVAL(handled);
}
+static struct v4l2_subdev *find_csi2(struct rcar_vin_priv *pcdev)
+{
+ struct v4l2_subdev *sd;
+ char name[] = "rcar_csi2";
+
+ v4l2_device_for_each_subdev(sd, &pcdev->ici.v4l2_dev) {
+ if (!strncmp(name, sd->name, sizeof(name) - 1)) {
+ pcdev->csi2_sd = sd;
+ return sd;
+ }
+ }
+
+ return NULL;
+}
+
static int rcar_vin_add_device(struct soc_camera_device *icd)
{
struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
@@ -934,8 +1260,40 @@
pm_runtime_get_sync(ici->v4l2_dev.dev);
- dev_dbg(icd->parent, "R-Car VIN driver attached to camera %d\n",
- icd->devnum);
+ if (priv->chip == RCAR_GEN3) {
+ struct v4l2_subdev *csi2_sd = find_csi2(priv);
+ int ret;
+
+ if (csi2_sd) {
+ csi2_sd->grp_id = soc_camera_grp_id(icd);
+ v4l2_set_subdev_hostdata(csi2_sd, icd);
+
+ ret = v4l2_subdev_call(csi2_sd, core, s_power, 1);
+ priv->csi_sync = true;
+
+ if (ret < 0 && ret != -EINVAL)
+ priv->csi_sync = false;
+
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ return ret;
+ }
+ /*
+ * -ENODEV is special:
+ * either csi2_sd == NULL or the CSI-2 driver
+ * has not found this soc-camera device among its clients
+ */
+ if (csi2_sd && ret == -ENODEV)
+ csi2_sd->grp_id = 0;
+
+ dev_dbg(icd->parent,
+ "R-Car VIN/CSI-2 driver attached to camera %d\n",
+ icd->devnum);
+
+ } else
+ dev_dbg(icd->parent, "R-Car VIN driver attached to camera %d\n",
+ icd->devnum);
+
+ priv->error_flag = false;
return 0;
}
@@ -945,6 +1303,7 @@
struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
struct rcar_vin_priv *priv = ici->priv;
struct vb2_v4l2_buffer *vbuf;
+ struct v4l2_subdev *csi2_sd = find_csi2(priv);
int i;
/* disable capture, disable interrupts */
@@ -954,6 +1313,7 @@
priv->state = STOPPED;
priv->request_to_stop = false;
+ priv->error_flag = false;
/* make sure active buffer is cancelled */
spin_lock_irq(&priv->lock);
@@ -968,10 +1328,78 @@
pm_runtime_put(ici->v4l2_dev.dev);
+ if ((csi2_sd) && (priv->csi_sync))
+ v4l2_subdev_call(csi2_sd, core, s_power, 0);
+
dev_dbg(icd->parent, "R-Car VIN driver detached from camera %d\n",
icd->devnum);
}
+struct rcar_vin_uds_regs {
+ unsigned long ctrl;
+ unsigned long scale;
+ unsigned long pass_bwidth;
+ unsigned long clip_size;
+};
+
+static unsigned long rcar_vin_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 rcar_vin_compute_ratio(unsigned int input,
+ unsigned int output)
+{
+ if (output > input)
+ return input * 4096 / output;
+ else
+ return (input - 1) * 4096 / (output - 1);
+}
+
+int rcar_vin_uds_set(struct rcar_vin_priv *priv, struct rcar_vin_cam *cam)
+{
+ 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;
+ struct v4l2_rect *cam_subrect = &cam->subrect;
+ u32 vnmc;
+
+ ratio_h = rcar_vin_compute_ratio(cam_subrect->width, cam->out_width);
+ ratio_v = rcar_vin_compute_ratio(cam_subrect->height, cam->out_height);
+
+ bwidth_h = rcar_vin_get_bwidth(ratio_h);
+ bwidth_v = rcar_vin_get_bwidth(ratio_v);
+
+ ctrl = VNUDS_CTRL_AMD;
+ clip_size = (cam->out_width << 16) | cam->out_height;
+
+ regs.ctrl = ctrl;
+ regs.scale = (ratio_h << 16) | ratio_v;
+ regs.pass_bwidth = (bwidth_h << 16) | bwidth_v;
+ regs.clip_size = clip_size;
+
+ vnmc = ioread32(priv->base + VNMC_REG);
+ iowrite32(vnmc | VNMC_SCLE, priv->base + VNMC_REG);
+ iowrite32(regs.ctrl, priv->base + VNUDS_CTRL_REG);
+ iowrite32(regs.scale, priv->base + VNUDS_SCALE_REG);
+ iowrite32(regs.pass_bwidth, priv->base + VNUDS_PASS_BWIDTH_REG);
+ iowrite32(regs.clip_size, priv->base + VNUDS_CLIP_SIZE_REG);
+
+ return 0;
+}
+
static void set_coeff(struct rcar_vin_priv *priv, unsigned short xs)
{
int i;
@@ -1036,6 +1464,7 @@
unsigned char dsize = 0;
struct v4l2_rect *cam_subrect = &cam->subrect;
u32 value;
+ int ret = 0;
dev_dbg(icd->parent, "Crop %ux%u@%u:%u\n",
icd->user_width, icd->user_height, cam->vin_left, cam->vin_top);
@@ -1072,49 +1501,69 @@
break;
}
- /* Set scaling coefficient */
- value = 0;
- if (cam_subrect->height != cam->out_height)
- value = (4096 * cam_subrect->height) / cam->out_height;
- dev_dbg(icd->parent, "YS Value: %x\n", value);
- iowrite32(value, priv->base + VNYS_REG);
+ if (priv->chip == RCAR_GEN3) {
+ if ((icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_NV12)
+ && is_scaling(cam)) {
+ ret = rcar_vin_uds_set(priv, cam);
+ if (ret < 0)
+ return ret;
+ }
+ if (is_scaling(cam) ||
+ (icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_NV16) ||
+ (icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_NV12))
+ iowrite32(ALIGN(cam->out_width, 0x20),
+ priv->base + VNIS_REG);
+ else
+ iowrite32(ALIGN(cam->out_width, 0x10),
+ priv->base + VNIS_REG);
+ } else {
+ /* Set scaling coefficient */
+ value = 0;
+ if (cam_subrect->height != cam->out_height)
+ value = (4096 * cam_subrect->height) / cam->out_height;
+ dev_dbg(icd->parent, "YS Value: %x\n", value);
+ iowrite32(value, priv->base + VNYS_REG);
- value = 0;
- if (cam_subrect->width != cam->out_width)
- value = (4096 * cam_subrect->width) / cam->out_width;
+ value = 0;
+ if (cam_subrect->width != cam->out_width)
+ value = (4096 * cam_subrect->width) / cam->out_width;
- /* Horizontal upscaling is up to double size */
- if (0 < value && value < 2048)
- value = 2048;
+ /* Horizontal upscaling is up to double size */
+ if (value < 2048)
+ value = 2048;
- dev_dbg(icd->parent, "XS Value: %x\n", value);
- iowrite32(value, priv->base + VNXS_REG);
+ dev_dbg(icd->parent, "XS Value: %x\n", value);
+ iowrite32(value, priv->base + VNXS_REG);
- /* Horizontal upscaling is carried out by scaling down from double size */
- if (value < 4096)
- value *= 2;
+ /* Horizontal upscaling is carried out */
+ /* by scaling down from double size */
+ if (value < 4096)
+ value *= 2;
- set_coeff(priv, value);
+ set_coeff(priv, value);
- /* Set Start/End Pixel/Line Post-Clip */
- iowrite32(0, priv->base + VNSPPOC_REG);
- iowrite32(0, priv->base + VNSLPOC_REG);
- iowrite32((cam->out_width - 1) << dsize, priv->base + VNEPPOC_REG);
- switch (priv->field) {
- case V4L2_FIELD_INTERLACED:
- case V4L2_FIELD_INTERLACED_TB:
- case V4L2_FIELD_INTERLACED_BT:
- iowrite32(cam->out_height / 2 - 1,
- priv->base + VNELPOC_REG);
- break;
- default:
- iowrite32(cam->out_height - 1, priv->base + VNELPOC_REG);
- break;
+ /* Set Start/End Pixel/Line Post-Clip */
+ iowrite32(0, priv->base + VNSPPOC_REG);
+ iowrite32(0, priv->base + VNSLPOC_REG);
+ iowrite32((cam->out_width - 1) << dsize,
+ priv->base + VNEPPOC_REG);
+ switch (priv->field) {
+ case V4L2_FIELD_INTERLACED:
+ case V4L2_FIELD_INTERLACED_TB:
+ case V4L2_FIELD_INTERLACED_BT:
+ iowrite32(cam->out_height / 2 - 1,
+ priv->base + VNELPOC_REG);
+ break;
+ default:
+ iowrite32(cam->out_height - 1,
+ priv->base + VNELPOC_REG);
+ break;
+ }
+
+ iowrite32(ALIGN(cam->out_width, 0x10), priv->base + VNIS_REG);
}
- iowrite32(ALIGN(cam->out_width, 0x10), priv->base + VNIS_REG);
-
- return 0;
+ return ret;
}
static void capture_stop_preserve(struct rcar_vin_priv *priv, u32 *vnmc)
@@ -1203,7 +1652,17 @@
if (ret < 0 && ret != -ENOIOCTLCMD)
return ret;
- val = VNDMR2_FTEV | VNDMR2_VLV(1);
+ if (priv->chip == RCAR_GEN3) {
+ if (cfg.type == V4L2_MBUS_CSI2)
+ vnmc &= ~VNMC_DPINE;
+ else
+ vnmc |= VNMC_DPINE;
+ }
+
+ if (priv->chip == RCAR_GEN3)
+ val = VNDMR2_FTEV;
+ else
+ val = VNDMR2_FTEV | VNDMR2_VLV(1);
if (!(common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW))
val |= VNDMR2_VPS;
if (!(common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
@@ -1256,6 +1715,14 @@
static const struct soc_mbus_pixelfmt rcar_vin_formats[] = {
{
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .name = "NV12",
+ .bits_per_sample = 8,
+ .packing = SOC_MBUS_PACKING_1_5X8,
+ .order = SOC_MBUS_ORDER_LE,
+ .layout = SOC_MBUS_LAYOUT_PLANAR_2Y_C,
+ },
+ {
.fourcc = V4L2_PIX_FMT_NV16,
.name = "NV16",
.bits_per_sample = 8,
@@ -1288,7 +1755,7 @@
.layout = SOC_MBUS_LAYOUT_PACKED,
},
{
- .fourcc = V4L2_PIX_FMT_RGB555X,
+ .fourcc = V4L2_PIX_FMT_ARGB555,
.name = "ARGB1555",
.bits_per_sample = 16,
.packing = SOC_MBUS_PACKING_NONE,
@@ -1303,6 +1770,14 @@
.order = SOC_MBUS_ORDER_LE,
.layout = SOC_MBUS_LAYOUT_PACKED,
},
+ {
+ .fourcc = V4L2_PIX_FMT_ARGB32,
+ .name = "ARGB8888",
+ .bits_per_sample = 32,
+ .packing = SOC_MBUS_PACKING_NONE,
+ .order = SOC_MBUS_ORDER_LE,
+ .layout = SOC_MBUS_LAYOUT_PACKED,
+ },
};
static int rcar_vin_get_formats(struct soc_camera_device *icd, unsigned int idx,
@@ -1313,6 +1788,8 @@
int ret, k, n;
int formats = 0;
struct rcar_vin_cam *cam;
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct rcar_vin_priv *priv = ici->priv;
struct v4l2_subdev_mbus_code_enum code = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
.index = idx,
@@ -1363,8 +1840,8 @@
* 1280x960, 640x480, 320x240
*/
for (shift = 0; shift < 3; shift++) {
- if (mf->width <= VIN_MAX_WIDTH &&
- mf->height <= VIN_MAX_HEIGHT)
+ if (mf->width <= priv->max_width &&
+ mf->height <= priv->max_height)
break;
mf->width = 1280 >> shift;
@@ -1455,6 +1932,294 @@
icd->host_priv = NULL;
}
+static bool rcar_vin_is_smaller(const struct v4l2_rect *r1,
+ const struct v4l2_rect *r2)
+{
+ return r1->width < r2->width || r1->height < r2->height;
+}
+
+static bool rcar_vin_is_inside(const struct v4l2_rect *r1,
+ const struct v4l2_rect *r2)
+{
+ return r1->left > r2->left || r1->top > r2->top ||
+ r1->left + r1->width < r2->left + r2->width ||
+ r1->top + r1->height < r2->top + r2->height;
+}
+
+static void rcar_vin_update_subrect(struct v4l2_rect *rect,
+ struct v4l2_rect *subrect)
+{
+ if (rect->width < subrect->width)
+ subrect->width = rect->width;
+
+ if (rect->height < subrect->height)
+ subrect->height = rect->height;
+
+ if (rect->left > subrect->left)
+ subrect->left = rect->left;
+ else if (rect->left + rect->width <
+ subrect->left + subrect->width)
+ subrect->left = rect->left + rect->width -
+ subrect->width;
+
+ if (rect->top > subrect->top)
+ subrect->top = rect->top;
+ else if (rect->top + rect->height <
+ subrect->top + subrect->height)
+ subrect->top = rect->top + rect->height -
+ subrect->height;
+}
+
+/* Iterative set_fmt, also updates cached client crop on success */
+static int rcar_vin_client_set_fmt(struct soc_camera_device *icd,
+ struct v4l2_rect *rect, struct v4l2_rect *subrect,
+ unsigned int max_width, unsigned int max_height,
+ struct v4l2_subdev_format *format, bool host_can_scale)
+{
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ struct device *dev = icd->parent;
+ struct v4l2_mbus_framefmt *mf = &format->format;
+ unsigned int width = mf->width, height = mf->height, tmp_w, tmp_h;
+ struct v4l2_cropcap cap;
+ bool host_1to1;
+ int ret;
+
+ ret = v4l2_device_call_until_err(sd->v4l2_dev,
+ soc_camera_grp_id(icd), pad,
+ set_fmt, NULL, format);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "camera scaled to %ux%u\n", mf->width, mf->height);
+
+ if (width == mf->width && height == mf->height) {
+ /* Perfect! The client has done it all. */
+ host_1to1 = true;
+ goto update_cache;
+ }
+
+ host_1to1 = false;
+ if (!host_can_scale)
+ goto update_cache;
+
+ cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ ret = v4l2_subdev_call(sd, video, cropcap, &cap);
+ if (ret < 0)
+ return ret;
+
+ if (max_width > cap.bounds.width)
+ max_width = cap.bounds.width;
+ if (max_height > cap.bounds.height)
+ max_height = cap.bounds.height;
+
+ /* Camera set a format, but geometry is not precise, try to improve */
+ tmp_w = mf->width;
+ tmp_h = mf->height;
+
+ /* width <= max_width && height <= max_height - guaranteed by try_fmt */
+ while ((width > tmp_w || height > tmp_h) &&
+ tmp_w < max_width && tmp_h < max_height) {
+ tmp_w = min(2 * tmp_w, max_width);
+ tmp_h = min(2 * tmp_h, max_height);
+ mf->width = tmp_w;
+ mf->height = tmp_h;
+ ret = v4l2_device_call_until_err(sd->v4l2_dev,
+ soc_camera_grp_id(icd), pad,
+ set_fmt, NULL, format);
+ dev_dbg(dev, "Camera scaled to %ux%u\n",
+ mf->width, mf->height);
+ if (ret < 0) {
+ /* This shouldn't happen */
+ dev_err(dev, "Client failed to set format: %d\n", ret);
+ return ret;
+ }
+ }
+
+update_cache:
+ /* Update cache */
+ ret = soc_camera_client_g_rect(sd, rect);
+ if (ret < 0)
+ return ret;
+
+ if (!host_1to1)
+ rcar_vin_update_subrect(rect, subrect);
+
+ return 0;
+}
+
+int rcar_vin_client_scale(struct soc_camera_device *icd,
+ struct v4l2_rect *rect, struct v4l2_rect *subrect,
+ struct v4l2_mbus_framefmt *mf,
+ unsigned int *width, unsigned int *height,
+ bool host_can_scale, unsigned int shift)
+{
+ struct device *dev = icd->parent;
+ struct v4l2_subdev_format fmt_tmp = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ .format = *mf,
+ };
+ struct v4l2_mbus_framefmt *mf_tmp = &fmt_tmp.format;
+ unsigned int scale_h, scale_v;
+ int ret;
+
+ /*
+ * 5. Apply iterative camera S_FMT for camera user window (also updates
+ * client crop cache and the imaginary sub-rectangle).
+ */
+ ret = rcar_vin_client_set_fmt(icd, rect, subrect, *width, *height,
+ &fmt_tmp, host_can_scale);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "5: camera scaled to %ux%u\n",
+ mf_tmp->width, mf_tmp->height);
+
+ /* 6. Retrieve camera output window (g_fmt) */
+
+ /* unneeded - it is already in "mf_tmp" */
+
+ /* 7. Calculate new client scales. */
+ scale_h = soc_camera_calc_scale(rect->width, shift, mf_tmp->width);
+ scale_v = soc_camera_calc_scale(rect->height, shift, mf_tmp->height);
+
+ mf->width = mf_tmp->width;
+ mf->height = mf_tmp->height;
+ mf->colorspace = mf_tmp->colorspace;
+
+ /*
+ * 8. Calculate new host crop - apply camera scales to previously
+ * updated "effective" crop.
+ */
+ *width = soc_camera_shift_scale(subrect->width, shift, scale_h);
+ *height = soc_camera_shift_scale(subrect->height, shift, scale_v);
+
+ dev_dbg(dev, "8: new client sub-window %ux%u\n", *width, *height);
+
+ return 0;
+}
+
+int rcar_vin_client_s_crop(struct v4l2_subdev *sd,
+ struct v4l2_crop *crop, struct v4l2_crop *cam_crop,
+ struct v4l2_rect *target_rect,
+ struct v4l2_rect *subrect)
+{
+ struct v4l2_rect *rect = &crop->c, *cam_rect = &cam_crop->c;
+ struct device *dev = sd->v4l2_dev->dev;
+ struct v4l2_cropcap cap;
+ int ret;
+ unsigned int width, height;
+
+ v4l2_subdev_call(sd, video, s_crop, crop);
+ ret = soc_camera_client_g_rect(sd, cam_rect);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Now cam_crop contains the current camera input rectangle, and it must
+ * be within camera cropcap bounds
+ */
+ if (!memcmp(rect, cam_rect, sizeof(*rect))) {
+ /* Even if camera S_CROP failed, but camera rectangle matches */
+ dev_dbg(dev, "Camera S_CROP successful for %dx%d@%d:%d\n",
+ rect->width, rect->height, rect->left, rect->top);
+ *target_rect = *cam_rect;
+ *subrect = *rect;
+ return 0;
+ }
+
+ /* Try to fix cropping, that camera hasn't managed to set */
+ dev_dbg(dev, "Fix camera S_CROP for %dx%d@%d:%d to %dx%d@%d:%d\n",
+ cam_rect->width, cam_rect->height,
+ cam_rect->left, cam_rect->top,
+ rect->width, rect->height, rect->left, rect->top);
+
+ /* We need sensor maximum rectangle */
+ ret = v4l2_subdev_call(sd, video, cropcap, &cap);
+ if (ret < 0)
+ return ret;
+
+ /* Put user requested rectangle within sensor bounds */
+ soc_camera_limit_side(&rect->left, &rect->width, cap.bounds.left, 2,
+ cap.bounds.width);
+ soc_camera_limit_side(&rect->top, &rect->height, cap.bounds.top, 4,
+ cap.bounds.height);
+
+ /*
+ * Popular special case - some cameras can only handle fixed sizes like
+ * QVGA, VGA,... Take care to avoid infinite loop.
+ */
+ width = max_t(unsigned int, cam_rect->width, 2);
+ height = max_t(unsigned int, cam_rect->height, 2);
+
+ /*
+ * Loop as long as sensor is not covering the requested rectangle and
+ * is still within its bounds
+ */
+ while (!ret && (rcar_vin_is_smaller(cam_rect, rect) ||
+ rcar_vin_is_inside(cam_rect, rect)) &&
+ (cap.bounds.width > width || cap.bounds.height > height)) {
+
+ width *= 2;
+ height *= 2;
+
+ cam_rect->width = width;
+ cam_rect->height = height;
+
+ /*
+ * We do not know what capabilities the camera has to set up
+ * left and top borders. We could try to be smarter in iterating
+ * them, e.g., if camera current left is to the right of the
+ * target left, set it to the middle point between the current
+ * left and minimum left. But that would add too much
+ * complexity: we would have to iterate each border separately.
+ * Instead we just drop to the left and top bounds.
+ */
+ if (cam_rect->left > rect->left)
+ cam_rect->left = cap.bounds.left;
+
+ if (cam_rect->left + cam_rect->width < rect->left + rect->width)
+ cam_rect->width = rect->left + rect->width -
+ cam_rect->left;
+
+ if (cam_rect->top > rect->top)
+ cam_rect->top = cap.bounds.top;
+
+ if (cam_rect->top + cam_rect->height < rect->top + rect->height)
+ cam_rect->height = rect->top + rect->height -
+ cam_rect->top;
+
+ v4l2_subdev_call(sd, video, s_crop, cam_crop);
+ ret = soc_camera_client_g_rect(sd, cam_rect);
+ dev_dbg(dev, "Camera S_CROP %d for %dx%d@%d:%d\n", ret,
+ cam_rect->width, cam_rect->height,
+ cam_rect->left, cam_rect->top);
+ }
+
+ /* S_CROP must not modify the rectangle */
+ if (rcar_vin_is_smaller(cam_rect, rect) ||
+ rcar_vin_is_inside(cam_rect, rect)) {
+ /*
+ * The camera failed to configure a suitable cropping,
+ * we cannot use the current rectangle, set to max
+ */
+ *cam_rect = cap.bounds;
+ v4l2_subdev_call(sd, video, s_crop, cam_crop);
+ ret = soc_camera_client_g_rect(sd, cam_rect);
+ dev_dbg(dev, "Camera S_CROP %d for max %dx%d@%d:%d\n", ret,
+ cam_rect->width, cam_rect->height,
+ cam_rect->left, cam_rect->top);
+ }
+
+ if (!ret) {
+ *target_rect = *cam_rect;
+ *subrect = *rect;
+ rcar_vin_update_subrect(target_rect, subrect);
+ }
+
+ return ret;
+}
+
static int rcar_vin_set_crop(struct soc_camera_device *icd,
const struct v4l2_crop *a)
{
@@ -1482,7 +2247,7 @@
dev_dbg(dev, "VNMC_REG 0x%x\n", vnmc);
/* Apply iterative camera S_CROP for new input window. */
- ret = soc_camera_client_s_crop(sd, &a_writable, &cam_crop,
+ ret = rcar_vin_client_s_crop(sd, &a_writable, &cam_crop,
&cam->rect, &cam->subrect);
if (ret < 0)
return ret;
@@ -1498,26 +2263,21 @@
if (ret < 0)
return ret;
- if (mf->width > VIN_MAX_WIDTH || mf->height > VIN_MAX_HEIGHT)
+ if (mf->width > priv->max_width || mf->height > priv->max_height)
return -EINVAL;
/* Cache camera output window */
cam->width = mf->width;
cam->height = mf->height;
- icd->user_width = cam->width;
- icd->user_height = cam->height;
-
- cam->vin_left = rect->left & ~1;
- cam->vin_top = rect->top & ~1;
+ cam->vin_left = rect->left;
+ cam->vin_top = rect->top;
/* Use VIN cropping to crop to the new window. */
ret = rcar_vin_set_rect(icd);
if (ret < 0)
return ret;
- cam->subrect = *rect;
-
dev_dbg(dev, "VIN cropped to %ux%u@%u:%u\n",
icd->user_width, icd->user_height,
cam->vin_left, cam->vin_top);
@@ -1568,6 +2328,20 @@
dev_dbg(dev, "S_FMT(pix=0x%x, %ux%u)\n",
pixfmt, pix->width, pix->height);
+ /* At the time of NV16 capture format, the user has to specify */
+ /* the width of the multiple of 32 for H/W specification. */
+ if (priv->error_flag == false)
+ priv->error_flag = true;
+ else {
+ if (((pixfmt == V4L2_PIX_FMT_NV16) ||
+ (pixfmt == V4L2_PIX_FMT_NV12)) &&
+ (pix->width & 0x1F)) {
+ dev_dbg(icd->parent,
+ "specify width of 32 multiple in separate format.\n");
+ return -EINVAL;
+ }
+ }
+
switch (pix->field) {
default:
pix->field = V4L2_FIELD_NONE;
@@ -1610,12 +2384,15 @@
case V4L2_PIX_FMT_RGB32:
can_scale = priv->chip != RCAR_E1;
break;
+ case V4L2_PIX_FMT_ARGB32:
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_RGB565:
- case V4L2_PIX_FMT_RGB555X:
+ case V4L2_PIX_FMT_ARGB555:
+ case V4L2_PIX_FMT_NV16:
can_scale = true;
break;
+ case V4L2_PIX_FMT_NV12:
default:
can_scale = false;
break;
@@ -1623,7 +2400,7 @@
dev_dbg(dev, "request camera output %ux%u\n", mf.width, mf.height);
- ret = soc_camera_client_scale(icd, &cam->rect, &cam->subrect,
+ ret = rcar_vin_client_scale(icd, &cam->rect, &cam->subrect,
&mf, &vin_sub_width, &vin_sub_height,
can_scale, 12);
@@ -1681,6 +2458,8 @@
struct v4l2_format *f)
{
const struct soc_camera_format_xlate *xlate;
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct rcar_vin_priv *priv = ici->priv;
struct v4l2_pix_format *pix = &f->fmt.pix;
struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
struct v4l2_subdev_pad_config pad_cfg;
@@ -1702,9 +2481,16 @@
pix->colorspace = icd->colorspace;
}
- /* FIXME: calculate using depth and bus width */
- v4l_bound_align_image(&pix->width, 2, VIN_MAX_WIDTH, 1,
- &pix->height, 4, VIN_MAX_HEIGHT, 2, 0);
+ /* When performing a YCbCr-422 format output, even if it performs */
+ /* odd number clipping by pixel post clip processing, */
+ /* it is outputted to a memory per even pixels. */
+ if ((pixfmt == V4L2_PIX_FMT_NV16) || (pixfmt == V4L2_PIX_FMT_NV12) ||
+ (pixfmt == V4L2_PIX_FMT_YUYV) || (pixfmt == V4L2_PIX_FMT_UYVY))
+ v4l_bound_align_image(&pix->width, 5, priv->max_width, 1,
+ &pix->height, 2, priv->max_height, 0, 0);
+ else
+ v4l_bound_align_image(&pix->width, 5, priv->max_width, 0,
+ &pix->height, 2, priv->max_height, 0, 0);
width = pix->width;
height = pix->height;
@@ -1725,11 +2511,19 @@
if (ret < 0)
return ret;
- /* Adjust only if VIN cannot scale */
- if (pix->width > mf->width * 2)
- pix->width = mf->width * 2;
- if (pix->height > mf->height * 3)
- pix->height = mf->height * 3;
+ if (priv->chip == RCAR_GEN3) {
+ /* Adjust max scaling size for Gen3 */
+ if (pix->width > 4096)
+ pix->width = priv->max_width;
+ if (pix->height > 4096)
+ pix->height = priv->max_height;
+ } else {
+ /* Adjust only if VIN cannot scale */
+ if (pix->width > mf->width * 2)
+ pix->width = mf->width * 2;
+ if (pix->height > mf->height * 3)
+ pix->height = mf->height * 3;
+ }
pix->field = mf->field;
pix->colorspace = mf->colorspace;
@@ -1743,8 +2537,8 @@
* requested a bigger rectangle, it will not return a
* smaller one.
*/
- mf->width = VIN_MAX_WIDTH;
- mf->height = VIN_MAX_HEIGHT;
+ mf->width = priv->max_width;
+ mf->height = priv->max_height;
ret = v4l2_device_call_until_err(sd->v4l2_dev,
soc_camera_grp_id(icd),
pad, set_fmt, &pad_cfg,
@@ -1800,6 +2594,39 @@
return vb2_queue_init(vq);
}
+static int rcar_vin_get_selection(struct soc_camera_device *icd,
+ struct v4l2_selection *sel)
+{
+ /* TODO */
+ return 0;
+}
+
+static int rcar_vin_cropcap(struct soc_camera_device *icd,
+ struct v4l2_cropcap *crop)
+{
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_mbus_framefmt *mf = &fmt.format;
+ int ret;
+
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
+ if (ret < 0)
+ 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 struct soc_camera_host_ops rcar_vin_host_ops = {
.owner = THIS_MODULE,
.add = rcar_vin_add_device,
@@ -1814,10 +2641,13 @@
.querycap = rcar_vin_querycap,
.set_bus_param = rcar_vin_set_bus_param,
.init_videobuf2 = rcar_vin_init_videobuf2,
+ .get_selection = rcar_vin_get_selection,
+ .cropcap = rcar_vin_cropcap,
};
#ifdef CONFIG_OF
static const struct of_device_id rcar_vin_of_table[] = {
+ { .compatible = "renesas,vin-r8a7795", .data = (void *)RCAR_GEN3 },
{ .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 },
{ .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 },
{ .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 },
@@ -1829,6 +2659,184 @@
MODULE_DEVICE_TABLE(of, rcar_vin_of_table);
#endif
+#define MAP_MAX_NUM 32
+static DECLARE_BITMAP(device_map, MAP_MAX_NUM);
+static DEFINE_MUTEX(list_lock);
+
+static int rcar_vin_dyn_pdev(struct soc_camera_desc *sdesc,
+ struct rcar_vin_async_client *sasc)
+{
+ struct platform_device *pdev;
+ int ret, i;
+
+ mutex_lock(&list_lock);
+ i = find_first_zero_bit(device_map, MAP_MAX_NUM);
+ if (i < MAP_MAX_NUM)
+ set_bit(i, device_map);
+ mutex_unlock(&list_lock);
+ if (i >= MAP_MAX_NUM)
+ return -ENOMEM;
+
+ pdev = platform_device_alloc("soc-camera-pdrv", ((2 * i) + 1));
+ if (!pdev)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(pdev, sdesc, sizeof(*sdesc));
+ if (ret < 0) {
+ platform_device_put(pdev);
+ return ret;
+ }
+
+ sasc->pdev = pdev;
+
+ return 0;
+}
+
+static int rcar_vin_async_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ /* None. */
+ return 0;
+}
+
+static void rcar_vin_async_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ /* None. */
+}
+
+static int rcar_vin_async_probe(struct soc_camera_host *ici,
+ struct soc_camera_device *icd)
+{
+ struct soc_camera_desc *sdesc = to_soc_camera_desc(icd);
+ struct soc_camera_host_desc *shd = &sdesc->host_desc;
+ struct device *control = NULL;
+ int ret;
+
+ ret = v4l2_ctrl_handler_init(&icd->ctrl_handler, 16);
+ if (ret < 0)
+ return ret;
+
+ if (shd->module_name)
+ ret = request_module(shd->module_name);
+
+ ret = shd->add_device(icd);
+
+ control = to_soc_camera_control(icd);
+ if (!control || !control->driver || !dev_get_drvdata(control) ||
+ !try_module_get(control->driver->owner)) {
+ shd->del_device(icd);
+ ret = -ENODEV;
+ }
+
+ return ret;
+}
+
+static int rcar_vin_async_complete(struct v4l2_async_notifier *notifier)
+{
+ struct rcar_vin_async_client *sasc = container_of(notifier,
+ struct rcar_vin_async_client, notifier);
+ struct soc_camera_device *icd = platform_get_drvdata(sasc->pdev);
+
+ if (to_soc_camera_control(icd)) {
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ int ret;
+
+ mutex_lock(&list_lock);
+ ret = rcar_vin_async_probe(ici, icd);
+ mutex_unlock(&list_lock);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct soc_camera_device *rcar_vin_add_pdev(
+ struct rcar_vin_async_client *sasc)
+{
+ struct platform_device *pdev = sasc->pdev;
+ int ret;
+
+ ret = platform_device_add(pdev);
+
+ if (ret < 0 || !pdev->dev.driver)
+ return NULL;
+
+ return platform_get_drvdata(pdev);
+}
+
+static int rcar_vin_soc_of_bind(struct rcar_vin_priv *priv,
+ struct soc_camera_host *ici,
+ struct device_node *ep,
+ struct device_node *remote)
+{
+ struct soc_camera_device *icd;
+ struct soc_camera_desc sdesc = {.host_desc.bus_id = ici->nr,};
+ struct rcar_vin_async_client *sasc;
+ struct soc_of_info *info;
+ struct i2c_client *client;
+ char clk_name[V4L2_SUBDEV_NAME_SIZE];
+ int ret;
+
+ /* allocate a new subdev and add match info to it */
+ info = devm_kzalloc(ici->v4l2_dev.dev, sizeof(struct soc_of_info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->sasd.asd.match.of.node = remote;
+ info->sasd.asd.match_type = V4L2_ASYNC_MATCH_OF;
+ info->subdev = &info->sasd.asd;
+
+ /* Or shall this be managed by the soc-camera device? */
+ sasc = &info->sasc;
+
+ ret = rcar_vin_dyn_pdev(&sdesc, sasc);
+ if (ret < 0)
+ goto eallocpdev;
+
+ sasc->sensor = &info->sasd.asd;
+
+ icd = rcar_vin_add_pdev(sasc);
+ if (!icd) {
+ ret = -ENOMEM;
+ goto eaddpdev;
+ }
+
+ sasc->notifier.subdevs = &info->subdev;
+ sasc->notifier.num_subdevs = 1;
+ sasc->notifier.bound = rcar_vin_async_bound;
+ sasc->notifier.unbind = rcar_vin_async_unbind;
+ sasc->notifier.complete = rcar_vin_async_complete;
+
+ priv->async_client = sasc;
+
+ client = of_find_i2c_device_by_node(remote);
+
+ if (client)
+ snprintf(clk_name, sizeof(clk_name), "%d-%04x",
+ client->adapter->nr, client->addr);
+ else
+ snprintf(clk_name, sizeof(clk_name), "of-%s",
+ of_node_full_name(remote));
+
+ ret = v4l2_async_notifier_register(&ici->v4l2_dev, &sasc->notifier);
+ if (!ret)
+ return 0;
+
+ platform_device_del(sasc->pdev);
+eaddpdev:
+ platform_device_put(sasc->pdev);
+eallocpdev:
+ devm_kfree(ici->v4l2_dev.dev, info);
+ dev_err(ici->v4l2_dev.dev, "group probe failed: %d\n", ret);
+
+ return ret;
+}
+
static int rcar_vin_probe(struct platform_device *pdev)
{
const struct of_device_id *match = NULL;
@@ -1838,6 +2846,11 @@
struct resource *mem;
unsigned int pdata_flags;
int irq, ret;
+ const char *str;
+ unsigned int i;
+ struct device_node *epn = NULL, *ren = NULL;
+ bool csi_use = false;
+ int vc, num;
match = of_match_device(of_match_ptr(rcar_vin_of_table), &pdev->dev);
@@ -1847,6 +2860,32 @@
return -EINVAL;
}
+ for (i = 0; ; i++) {
+ epn = of_graph_get_next_endpoint(pdev->dev.of_node,
+ epn);
+ if (!epn)
+ break;
+
+ ren = of_graph_get_remote_port(epn);
+ if (!ren) {
+ dev_notice(&pdev->dev, "no remote for %s\n",
+ of_node_full_name(epn));
+ continue;
+ }
+
+ /* so we now have a remote node to connect */
+ dev_dbg(&pdev->dev, "node name:%s\n",
+ of_node_full_name(ren->parent));
+
+ if (strcmp(ren->parent->name, "csi2") == 0)
+ csi_use = true;
+
+ of_node_put(ren);
+
+ if (i)
+ break;
+ }
+
ret = v4l2_of_parse_endpoint(np, &ep);
if (ret) {
dev_err(&pdev->dev, "could not parse endpoint\n");
@@ -1855,6 +2894,8 @@
if (ep.bus_type == V4L2_MBUS_BT656)
pdata_flags = RCAR_VIN_BT656;
+ else if (ep.bus_type == V4L2_MBUS_CSI2)
+ pdata_flags = RCAR_VIN_BT656 | RCAR_VIN_CSI2;
else {
pdata_flags = 0;
if (ep.bus.parallel.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
@@ -1897,6 +2938,164 @@
priv->ici.v4l2_dev.dev = &pdev->dev;
priv->ici.drv_name = dev_name(&pdev->dev);
priv->ici.ops = &rcar_vin_host_ops;
+ priv->csi_sync = false;
+
+ if (priv->chip <= RCAR_GEN3) {
+ priv->max_width = 4096;
+ priv->max_height = 4096;
+ } else {
+ priv->max_width = 2048;
+ priv->max_height = 2048;
+ }
+
+ if (priv->chip == RCAR_GEN3) {
+ u32 ifmd = 0;
+ bool match_flag = false;
+
+ if (strcmp(dev_name(priv->ici.v4l2_dev.dev),
+ "e6ef0000.video") == 0)
+ priv->index = RCAR_VIDEO_0;
+ else if (strcmp(dev_name(priv->ici.v4l2_dev.dev),
+ "e6ef1000.video") == 0)
+ priv->index = RCAR_VIDEO_1;
+ else if (strcmp(dev_name(priv->ici.v4l2_dev.dev),
+ "e6ef2000.video") == 0)
+ priv->index = RCAR_VIDEO_2;
+ else if (strcmp(dev_name(priv->ici.v4l2_dev.dev),
+ "e6ef3000.video") == 0)
+ priv->index = RCAR_VIDEO_3;
+ else if (strcmp(dev_name(priv->ici.v4l2_dev.dev),
+ "e6ef4000.video") == 0)
+ priv->index = RCAR_VIDEO_4;
+ else if (strcmp(dev_name(priv->ici.v4l2_dev.dev),
+ "e6ef5000.video") == 0)
+ priv->index = RCAR_VIDEO_5;
+ else if (strcmp(dev_name(priv->ici.v4l2_dev.dev),
+ "e6ef6000.video") == 0)
+ priv->index = RCAR_VIDEO_6;
+ else if (strcmp(dev_name(priv->ici.v4l2_dev.dev),
+ "e6ef7000.video") == 0)
+ priv->index = RCAR_VIDEO_7;
+ else
+ priv->index = RCAR_VIN_CH_NONE;
+
+ ret = of_property_read_string(np, "csi,select", &str);
+ if (ret) {
+ dev_err(&pdev->dev, "could not parse csi,select\n");
+ return ret;
+ }
+
+ if (strcmp(str, "csi40") == 0)
+ priv->csi_ch = RCAR_CSI40;
+ else if (strcmp(str, "csi20") == 0)
+ priv->csi_ch = RCAR_CSI20;
+ else if (strcmp(str, "csi41") == 0)
+ priv->csi_ch = RCAR_CSI41;
+ else if (strcmp(str, "csi21") == 0)
+ priv->csi_ch = RCAR_CSI21;
+ else
+ priv->csi_ch = RCAR_CSI_CH_NONE;
+
+ ret = of_property_read_u32(np, "virtual,channel", &vc);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "could not parse virtual,channel\n");
+ return ret;
+ }
+
+ if (vc == 0)
+ priv->vc = RCAR_VIRTUAL_CH0;
+ else if (vc == 1)
+ priv->vc = RCAR_VIRTUAL_CH1;
+ else if (vc == 2)
+ priv->vc = RCAR_VIRTUAL_CH2;
+ else if (vc == 3)
+ priv->vc = RCAR_VIRTUAL_CH3;
+ else
+ priv->vc = RCAR_VIRTUAL_NONE;
+
+ dev_dbg(&pdev->dev, "csi_ch:%d, vc:%d\n",
+ priv->csi_ch, priv->vc);
+
+ num = sizeof(vin_vc_ifmd) / sizeof(struct vin_gen3_ifmd);
+ for (i = 0; i < num; i++) {
+ if ((vin_vc_ifmd[i].v_sel[priv->index].csi2_ch
+ == priv->csi_ch) &&
+ (vin_vc_ifmd[i].v_sel[priv->index].vc
+ == priv->vc)) {
+ if (priv->index < RCAR_VIDEO_4) {
+ if (ifmd0_init) {
+ ifmd0_reg_match[i] = true;
+ match_flag = true;
+ } else if (ifmd0_reg_match[i])
+ match_flag = true;
+ } else {
+ if (ifmd4_init) {
+ ifmd4_reg_match[i] = true;
+ match_flag = true;
+ } else if (ifmd4_reg_match[i])
+ match_flag = true;
+ }
+ } else {
+ if (priv->index < RCAR_VIDEO_4)
+ ifmd0_reg_match[i] = false;
+ else
+ ifmd4_reg_match[i] = false;
+ }
+ }
+ if (priv->index < RCAR_VIDEO_4)
+ ifmd0_init = false;
+ else
+ ifmd4_init = false;
+
+ if (!match_flag) {
+ dev_err(&pdev->dev,
+ "Not match, virtual channel pattern error.\n");
+ return -EINVAL;
+ }
+
+ ifmd = VNCSI_IFMD_DES2 | VNCSI_IFMD_DES1 | VNCSI_IFMD_DES0;
+
+ rcar_vin_cpg_enable_for_ifmd(priv->index, true);
+
+ if (priv->index < RCAR_VIDEO_4) {
+ void __iomem *ifmd0_mem;
+ int i, num;
+
+ num = sizeof(vin_vc_ifmd) /
+ sizeof(struct vin_gen3_ifmd);
+
+ for (i = 0; i < num; i++) {
+ if (ifmd0_reg_match[i]) {
+ ifmd |= vin_vc_ifmd[i].set_reg;
+ break;
+ }
+ }
+
+ ifmd0_mem = ioremap(0xe6ef0000 + VNCSI_IFMD_REG, 0x04);
+ iowrite32(ifmd, ifmd0_mem);
+ iounmap(ifmd0_mem);
+ } else {
+ void __iomem *ifmd4_mem;
+ int i, num;
+
+ num = sizeof(vin_vc_ifmd) /
+ sizeof(struct vin_gen3_ifmd);
+
+ for (i = 0; i < num; i++) {
+ if (ifmd4_reg_match[i]) {
+ ifmd |= vin_vc_ifmd[i].set_reg;
+ break;
+ }
+ }
+
+ ifmd4_mem = ioremap(0xe6ef4000 + VNCSI_IFMD_REG, 0x04);
+ iowrite32(ifmd, ifmd4_mem);
+ iounmap(ifmd4_mem);
+ }
+
+ rcar_vin_cpg_enable_for_ifmd(priv->index, false);
+ }
priv->pdata_flags = pdata_flags;
if (!match) {
@@ -1919,6 +3118,14 @@
if (ret)
goto cleanup;
+ if (csi_use) {
+ ret = rcar_vin_soc_of_bind(priv, &priv->ici, epn, ren->parent);
+ if (ret)
+ goto cleanup;
+ }
+
+ vin_debug = 0;
+
return 0;
cleanup:
@@ -1934,6 +3141,11 @@
struct rcar_vin_priv *priv = container_of(soc_host,
struct rcar_vin_priv, ici);
+ platform_device_del(priv->async_client->pdev);
+ platform_device_put(priv->async_client->pdev);
+
+ v4l2_async_notifier_unregister(&priv->async_client->notifier);
+
soc_camera_host_unregister(soc_host);
pm_runtime_disable(&pdev->dev);
vb2_dma_contig_cleanup_ctx(priv->alloc_ctx);
@@ -1941,11 +3153,32 @@
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)
+{
+ /* Empty function for now */
+ 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 = {
.probe = rcar_vin_probe,
.remove = rcar_vin_remove,
.driver = {
.name = DRV_NAME,
+ .pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(rcar_vin_of_table),
},
};
@@ -1955,3 +3188,4 @@
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:rcar_vin");
MODULE_DESCRIPTION("Renesas R-Car VIN camera host driver");
+MODULE_AUTHOR("Koji Matsuoka <koji.matsuoka.xm@renesas.com>");
diff --git a/drivers/media/platform/soc_camera/soc_scale_crop.c b/drivers/media/platform/soc_camera/soc_scale_crop.c
index bda29bc..673491a 100644
--- a/drivers/media/platform/soc_camera/soc_scale_crop.c
+++ b/drivers/media/platform/soc_camera/soc_scale_crop.c
@@ -1,6 +1,7 @@
/*
* soc-camera generic scaling-cropping manipulation functions
*
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
* Copyright (C) 2013 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*
* This program is free software; you can redistribute it and/or modify
@@ -405,3 +406,5 @@
mf->height = soc_camera_shift_scale(rect->height, shift, scale_v);
}
EXPORT_SYMBOL(soc_camera_calc_client_output);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/vsp1/vsp1.h b/drivers/media/platform/vsp1/vsp1.h
index 910d6b8..06cbab6 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)
*
@@ -55,6 +55,7 @@
unsigned int wpf_count;
unsigned int num_bru_inputs;
bool uapi;
+ bool fcpvd;
};
struct vsp1_device {
@@ -63,6 +64,7 @@
void __iomem *mmio;
struct clk *clock;
+ struct clk *fcpvd_clock;
struct mutex lock;
int ref_count;
@@ -87,10 +89,15 @@
struct vsp1_drm *drm;
bool use_dl;
+ int index;
+
+ dma_addr_t dl_addr;
+ unsigned int dl_body;
};
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_dl.c b/drivers/media/platform/vsp1/vsp1_dl.c
index a4dcccf..be890e3 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)
*
@@ -14,6 +14,7 @@
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/gfp.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include "vsp1.h"
#include "vsp1_dl.h"
@@ -171,6 +172,12 @@
vsp1_write(vsp1, VI6_DL_BODY_SIZE, VI6_DL_BODY_SIZE_UPD |
(list->reg_count * 8));
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) {
+ dl->vsp1->dl_addr = list->dma;
+ dl->vsp1->dl_body = VI6_DL_BODY_SIZE_UPD |
+ (list->reg_count * 8);
+ }
vsp1_dl_free_list(dl->lists.queued);
dl->lists.queued = list;
@@ -230,7 +237,12 @@
vsp1_write(vsp1, VI6_DL_HDR_ADDR(0), list->dma);
vsp1_write(vsp1, VI6_DL_BODY_SIZE, VI6_DL_BODY_SIZE_UPD |
(list->reg_count * 8));
-
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) {
+ dl->vsp1->dl_addr = list->dma;
+ dl->vsp1->dl_body = VI6_DL_BODY_SIZE_UPD |
+ (list->reg_count * 8);
+ }
dl->lists.queued = list;
dl->lists.pending = NULL;
}
@@ -267,6 +279,7 @@
{
struct vsp1_dl *dl;
unsigned int i;
+ int ret;
dl = kzalloc(sizeof(*dl), GFP_KERNEL);
if (!dl)
@@ -294,6 +307,12 @@
list->body = dl->mem + VSP1_DL_BODY_SIZE * i;
}
+ ret = RCAR_PRR_INIT();
+ if (ret) {
+ dev_dbg(dl->vsp1->dev, "product register init fail.\n");
+ return NULL;
+ }
+
return dl;
}
diff --git a/drivers/media/platform/vsp1/vsp1_drm.c b/drivers/media/platform/vsp1/vsp1_drm.c
index 302d02a..4f27df6 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)
*
@@ -55,6 +55,20 @@
}
EXPORT_SYMBOL_GPL(vsp1_du_init);
+int vsp1_du_if_set_mute(struct device *dev, bool on)
+{
+ struct vsp1_device *vsp1 = dev_get_drvdata(dev);
+ struct vsp1_pipeline *pipe = &vsp1->drm->pipe;
+
+ if (on)
+ vsp1_pipeline_stop(pipe);
+ else
+ vsp1_pipeline_run(pipe);
+
+ 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
@@ -273,7 +287,7 @@
int vsp1_du_atomic_update(struct device *dev, unsigned int rpf_index,
u32 pixelformat, unsigned int pitch,
dma_addr_t mem[2], const struct v4l2_rect *src,
- const struct v4l2_rect *dst)
+ const struct v4l2_rect *dst, u8 alpha)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_pipeline *pipe = &vsp1->drm->pipe;
@@ -289,6 +303,7 @@
return -EINVAL;
rpf = vsp1->rpf[rpf_index];
+ rpf->alpha->cur.val = alpha;
if (pixelformat == 0) {
dev_dbg(vsp1->dev, "%s: RPF%u: disable requested\n", __func__,
diff --git a/drivers/media/platform/vsp1/vsp1_drv.c b/drivers/media/platform/vsp1/vsp1_drv.c
index 7ecdaba..3fe711a 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>
@@ -20,6 +24,7 @@
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/videodev2.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include <media/v4l2-subdev.h>
@@ -35,17 +40,124 @@
#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
+};
+
+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) {
+ /* Necessary when headerless display list */
+ vsp1_write(vsp1, VI6_DL_HDR_ADDR(0), vsp1->dl_addr);
+ 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;
for (i = 0; i < vsp1->info->wpf_count; ++i) {
struct vsp1_rwpf *wpf = vsp1->wpf[i];
@@ -58,6 +170,14 @@
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)) {
+ 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_FRE) {
vsp1_pipeline_frame_end(pipe);
ret = IRQ_HANDLED;
@@ -79,6 +199,11 @@
ret = IRQ_HANDLED;
}
+ if ((RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) &&
+ (status & VI6_WFP_IRQ_STA_UND))
+ vsp1_underrun_workaround(vsp1, false);
+
return ret;
}
@@ -200,6 +325,8 @@
struct vsp1_entity *entity, *_entity;
struct vsp1_video *video, *_video;
+ v4l2_device_unregister(&vsp1->v4l2_dev);
+
list_for_each_entry_safe(entity, _entity, &vsp1->entities, list_dev) {
list_del(&entity->list_dev);
vsp1_entity_destroy(entity);
@@ -210,7 +337,6 @@
vsp1_video_cleanup(video);
}
- v4l2_device_unregister(&vsp1->v4l2_dev);
media_device_unregister(&vsp1->media_dev);
if (!vsp1->info->uapi)
@@ -411,7 +537,12 @@
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_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)))
@@ -487,9 +618,19 @@
if (ret < 0)
goto done;
+ if (vsp1->info->fcpvd) {
+ ret = clk_prepare_enable(vsp1->fcpvd_clock);
+ if (ret < 0) {
+ clk_disable_unprepare(vsp1->clock);
+ goto done;
+ }
+ }
+
ret = vsp1_device_init(vsp1);
if (ret < 0) {
clk_disable_unprepare(vsp1->clock);
+ if (vsp1->info->fcpvd)
+ clk_disable_unprepare(vsp1->fcpvd_clock);
goto done;
}
@@ -509,11 +650,16 @@
*/
void vsp1_device_put(struct vsp1_device *vsp1)
{
+ if (vsp1->ref_count == 0)
+ return;
+
mutex_lock(&vsp1->lock);
- if (--vsp1->ref_count == 0)
+ if (--vsp1->ref_count == 0) {
clk_disable_unprepare(vsp1->clock);
-
+ if (vsp1->info->fcpvd)
+ clk_disable_unprepare(vsp1->fcpvd_clock);
+ }
mutex_unlock(&vsp1->lock);
}
@@ -534,6 +680,8 @@
vsp1_pipelines_suspend(vsp1);
clk_disable_unprepare(vsp1->clock);
+ if (vsp1->info->fcpvd)
+ clk_disable_unprepare(vsp1->fcpvd_clock);
return 0;
}
@@ -548,6 +696,8 @@
return 0;
clk_prepare_enable(vsp1->clock);
+ if (vsp1->info->fcpvd)
+ clk_prepare_enable(vsp1->fcpvd_clock);
vsp1_pipelines_resume(vsp1);
@@ -572,6 +722,7 @@
.wpf_count = 4,
.num_bru_inputs = 4,
.uapi = true,
+ .fcpvd = false,
}, {
.version = VI6_IP_VERSION_MODEL_VSPR_H2,
.features = VSP1_HAS_BRU | VSP1_HAS_SRU,
@@ -580,6 +731,7 @@
.wpf_count = 4,
.num_bru_inputs = 4,
.uapi = true,
+ .fcpvd = false,
}, {
.version = VI6_IP_VERSION_MODEL_VSPD_GEN2,
.features = VSP1_HAS_BRU | VSP1_HAS_LIF | VSP1_HAS_LUT,
@@ -588,6 +740,7 @@
.wpf_count = 4,
.num_bru_inputs = 4,
.uapi = true,
+ .fcpvd = false,
}, {
.version = VI6_IP_VERSION_MODEL_VSPS_M2,
.features = VSP1_HAS_BRU | VSP1_HAS_LUT | VSP1_HAS_SRU,
@@ -596,6 +749,7 @@
.wpf_count = 4,
.num_bru_inputs = 4,
.uapi = true,
+ .fcpvd = false,
}, {
.version = VI6_IP_VERSION_MODEL_VSPI_GEN3,
.features = VSP1_HAS_LUT | VSP1_HAS_SRU,
@@ -603,6 +757,7 @@
.uds_count = 1,
.wpf_count = 1,
.uapi = true,
+ .fcpvd = false,
}, {
.version = VI6_IP_VERSION_MODEL_VSPBD_GEN3,
.features = VSP1_HAS_BRU,
@@ -610,6 +765,7 @@
.wpf_count = 1,
.num_bru_inputs = 5,
.uapi = true,
+ .fcpvd = false,
}, {
.version = VI6_IP_VERSION_MODEL_VSPBC_GEN3,
.features = VSP1_HAS_BRU | VSP1_HAS_LUT,
@@ -617,12 +773,15 @@
.wpf_count = 1,
.num_bru_inputs = 5,
.uapi = true,
+ .fcpvd = false,
}, {
.version = VI6_IP_VERSION_MODEL_VSPD_GEN3,
.features = VSP1_HAS_BRU | VSP1_HAS_LIF | VSP1_HAS_LUT,
.rpf_count = 5,
.wpf_count = 2,
.num_bru_inputs = 5,
+ .uapi = false,
+ .fcpvd = true,
},
};
@@ -650,7 +809,7 @@
if (IS_ERR(vsp1->mmio))
return PTR_ERR(vsp1->mmio);
- vsp1->clock = devm_clk_get(&pdev->dev, NULL);
+ vsp1->clock = of_clk_get(vsp1->dev->of_node, 0);
if (IS_ERR(vsp1->clock)) {
dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(vsp1->clock);
@@ -692,6 +851,14 @@
dev_dbg(&pdev->dev, "IP version 0x%08x\n", version);
+ if (vsp1->info->fcpvd) {
+ vsp1->fcpvd_clock = of_clk_get(vsp1->dev->of_node, 1);
+ if (IS_ERR(vsp1->fcpvd_clock)) {
+ dev_err(&pdev->dev, "failed to get fcpvd clock\n");
+ return PTR_ERR(vsp1->fcpvd_clock);
+ }
+ }
+
/* Instanciate entities */
ret = vsp1_create_entities(vsp1);
if (ret < 0) {
@@ -699,8 +866,28 @@
return ret;
}
+ 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;
+
platform_set_drvdata(pdev, vsp1);
+ ret = RCAR_PRR_INIT();
+ if (ret) {
+ dev_dbg(vsp1->dev, "product register init fail.\n");
+ return ret;
+ }
+
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0))
+ fcpv_reg[vsp1->index] =
+ ioremap(fcpvd_offset[vsp1->index], 0x20);
+
return 0;
}
@@ -708,8 +895,13 @@
{
struct vsp1_device *vsp1 = platform_get_drvdata(pdev);
+ vsp1_device_put(vsp1);
vsp1_destroy_entities(vsp1);
+ if (RCAR_PRR_IS_PRODUCT(H3) &&
+ (RCAR_PRR_CHK_CUT(H3, WS11) <= 0))
+ iounmap(fcpv_reg[vsp1->index]);
+
return 0;
}
diff --git a/drivers/media/platform/vsp1/vsp1_lif.c b/drivers/media/platform/vsp1/vsp1_lif.c
index 433853c..b8af06f 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)
*
@@ -20,7 +20,7 @@
#include "vsp1_lif.h"
#define LIF_MIN_SIZE 2U
-#define LIF_MAX_SIZE 2048U
+#define LIF_MAX_SIZE 4096U
/* -----------------------------------------------------------------------------
* Device Access
@@ -39,9 +39,9 @@
{
const struct v4l2_mbus_framefmt *format;
struct vsp1_lif *lif = to_lif(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 (!enable) {
vsp1_write(lif->entity.vsp1, VI6_LIF_CTRL, 0);
diff --git a/drivers/media/platform/vsp1/vsp1_pipe.c b/drivers/media/platform/vsp1/vsp1_pipe.c
index 96f0e7d..69124ac 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)
*
@@ -63,17 +63,17 @@
VI6_FMT_RGB_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 24, 0, 0 }, false, false, 1, 1, false },
- { V4L2_PIX_FMT_ABGR32, MEDIA_BUS_FMT_ARGB8888_1X32,
+ { V4L2_PIX_FMT_ARGB32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, true },
- { V4L2_PIX_FMT_XBGR32, MEDIA_BUS_FMT_ARGB8888_1X32,
+ { V4L2_PIX_FMT_XRGB32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
- { V4L2_PIX_FMT_ARGB32, MEDIA_BUS_FMT_ARGB8888_1X32,
+ { V4L2_PIX_FMT_ABGR32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 32, 0, 0 }, false, false, 1, 1, true },
- { V4L2_PIX_FMT_XRGB32, MEDIA_BUS_FMT_ARGB8888_1X32,
+ { V4L2_PIX_FMT_XBGR32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
diff --git a/drivers/media/platform/vsp1/vsp1_regs.h b/drivers/media/platform/vsp1/vsp1_regs.h
index 069216f..9cb74cc 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,23 +20,49 @@
#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)
diff --git a/drivers/media/platform/vsp1/vsp1_rpf.c b/drivers/media/platform/vsp1/vsp1_rpf.c
index 5bc1d15..c2665c5 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)
*
@@ -76,6 +76,7 @@
const struct v4l2_rect *crop = &rpf->crop;
u32 pstride;
u32 infmt;
+ u32 alph_sel, laya;
int ret;
ret = vsp1_entity_set_streaming(&rpf->entity, enable);
@@ -147,14 +148,34 @@
* alpha value set through the V4L2_CID_ALPHA_COMPONENT control
* otherwise. Disable color keying.
*/
- vsp1_rpf_write(rpf, 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 = (2 << 28) | (1 << 18) |
+ (0xFF << 8) | (rpf->alpha->cur.val & 0xFF);
+ else
+ alph_sel = (2 << 28) | (1 << 18) |
+ ((rpf->alpha->cur.val & 0xFF) << 8) | 0xFF;
+ laya = 0;
+ break;
+ case V4L2_PIX_FMT_ARGB32:
+ case V4L2_PIX_FMT_ABGR32:
+ alph_sel = (1 << 18);
+ laya = 0;
+ break;
+ case V4L2_PIX_FMT_ARGB444:
+ alph_sel = (0 << 28) | (2 << 18);
+ laya = 0;
+ break;
+ default:
+ alph_sel = (4 << 28) | (1 << 18);
+ laya = (rpf->alpha->cur.val & 0xFF) << 24;
+ break;
+ }
+ vsp1_rpf_write(rpf, VI6_RPF_ALPH_SEL, alph_sel);
if (vsp1->info->uapi)
mutex_lock(rpf->ctrls.lock);
- vsp1_rpf_write(rpf, VI6_RPF_VRTCOL_SET,
- rpf->alpha->cur.val << VI6_RPF_VRTCOL_SET_LAYA_SHIFT);
+ vsp1_rpf_write(rpf, VI6_RPF_VRTCOL_SET, laya);
vsp1_pipeline_propagate_alpha(pipe, &rpf->entity, rpf->alpha->cur.val);
if (vsp1->info->uapi)
mutex_unlock(rpf->ctrls.lock);
diff --git a/drivers/media/platform/vsp1/vsp1_wpf.c b/drivers/media/platform/vsp1/vsp1_wpf.c
index c78d4af..c90528b 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)
*
@@ -19,8 +19,8 @@
#include "vsp1_rwpf.h"
#include "vsp1_video.h"
-#define WPF_MAX_WIDTH 2048
-#define WPF_MAX_HEIGHT 2048
+#define WPF_MAX_WIDTH 4096
+#define WPF_MAX_HEIGHT 4096
/* -----------------------------------------------------------------------------
* Device Access
@@ -170,7 +170,7 @@
/* Enable interrupts */
vsp1_write(vsp1, VI6_WPF_IRQ_STA(wpf->entity.index), 0);
vsp1_write(vsp1, VI6_WPF_IRQ_ENB(wpf->entity.index),
- VI6_WFP_IRQ_ENB_FREE);
+ VI6_WFP_IRQ_ENB_FREE | VI6_WFP_IRQ_ENB_UNDE);
return 0;
}
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 1526b8a..dd1499b 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -560,7 +560,7 @@
config MMC_SDHI
tristate "SH-Mobile SDHI SD/SDIO controller support"
- depends on SUPERH || ARM
+ depends on SUPERH || ARM || ARM64
depends on SUPERH || ARCH_SHMOBILE || COMPILE_TEST
select MMC_TMIO_CORE
help
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 3595f83..3aa3587f 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 354f4f3..e48748f 100644
--- a/drivers/mmc/host/sh_mobile_sdhi.c
+++ b/drivers/mmc/host/sh_mobile_sdhi.c
@@ -1,6 +1,7 @@
/*
* SuperH Mobile SDHI
*
+ * Copyright (C) 2015 Renesas Electronics Corporation
* Copyright (C) 2009 Magnus Damm
*
* This program is free software; you can redistribute it and/or modify
@@ -30,19 +31,31 @@
#include <linux/mfd/tmio.h>
#include <linux/sh_dma.h>
#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
#include "tmio_mmc.h"
-#define EXT_ACC 0xe4
+#define HOST_MODE 0xe4
#define host_to_priv(host) container_of((host)->pdata, struct sh_mobile_sdhi, mmc_data)
+struct sh_mobile_sdhi_scc {
+ unsigned long clk; /* clock for SDR104 */
+ u32 tap; /* sampling clock position for SDR104 */
+};
+
struct sh_mobile_sdhi_of_data {
unsigned long tmio_flags;
unsigned long capabilities;
unsigned long capabilities2;
enum dma_slave_buswidth dma_buswidth;
dma_addr_t dma_rx_offset;
+ unsigned int max_blk_count;
+ unsigned short max_segs;
+ bool sdbuf_64bit;
+ int scc_offset;
+ struct sh_mobile_sdhi_scc *taps;
+ int taps_num;
};
static const struct sh_mobile_sdhi_of_data sh_mobile_sdhi_of_cfg[] = {
@@ -57,12 +70,48 @@
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
};
+/* Definitions for sampling clocks */
+static struct sh_mobile_sdhi_scc rcar_gen2_scc_taps[] = {
+ {
+ .clk = 156000000,
+ .tap = 0x00000703,
+ },
+ {
+ .clk = 0,
+ .tap = 0x00000300,
+ },
+};
+
static const struct sh_mobile_sdhi_of_data of_rcar_gen2_compatible = {
.tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE |
TMIO_MMC_CLK_ACTUAL,
.capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ,
.dma_buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dma_rx_offset = 0x2000,
+ .scc_offset = 0x0300,
+ .taps = rcar_gen2_scc_taps,
+ .taps_num = ARRAY_SIZE(rcar_gen2_scc_taps),
+};
+
+/* Definitions for sampling clocks */
+static struct sh_mobile_sdhi_scc rcar_gen3_scc_taps[] = {
+ {
+ .clk = 0,
+ .tap = 0x00000300,
+ },
+};
+
+static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = {
+ .tmio_flags = TMIO_MMC_CLK_NO_SLEEP | TMIO_MMC_HAS_IDLE_WAIT |
+ TMIO_MMC_WRPROTECT_DISABLE | TMIO_MMC_CLK_ACTUAL,
+ .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_CMD23,
+ /* 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),
};
static const struct of_device_id sh_mobile_sdhi_of_match[] = {
@@ -78,6 +127,8 @@
{ .compatible = "renesas,sdhi-r8a7792", .data = &of_rcar_gen2_compatible, },
{ .compatible = "renesas,sdhi-r8a7793", .data = &of_rcar_gen2_compatible, },
{ .compatible = "renesas,sdhi-r8a7794", .data = &of_rcar_gen2_compatible, },
+ { .compatible = "renesas,sdhi-r8a7795", .data = &of_rcar_gen3_compatible, },
+ { .compatible = "renesas,mmc-r8a7795", .data = &of_rcar_gen3_compatible, },
{},
};
MODULE_DEVICE_TABLE(of, sh_mobile_sdhi_of_match);
@@ -86,6 +137,8 @@
struct clk *clk;
struct tmio_mmc_data mmc_data;
struct tmio_mmc_dma dma_priv;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pinctrl_3v3, *pinctrl_1v8;
};
static void sh_mobile_sdhi_sdbuf_width(struct tmio_mmc_host *host, int width)
@@ -103,12 +156,21 @@
case 0xCB0D:
val = (width == 32) ? 0x0000 : 0x0001;
break;
+ case 0xCC10:
+ case 0xCD10:
+ if (width == 64)
+ val = 0x0000;
+ else if (width == 32)
+ val = 0x0101;
+ else /* width = 16 */
+ val = 0x0001;
+ break;
default:
/* nothing to do */
return;
}
- sd_ctrl_write16(host, EXT_ACC, val);
+ sd_ctrl_write16(host, HOST_MODE, val);
}
static int sh_mobile_sdhi_clk_enable(struct platform_device *pdev, unsigned int *f)
@@ -136,6 +198,316 @@
clk_disable_unprepare(priv->clk);
}
+static void sh_mobile_sdhi_set_clk_div(struct platform_device *pdev,
+ int state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+
+ if (state) {
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~0x0100 &
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, 0x00ff);
+ }
+}
+
+#define SH_MOBILE_SDHI_DAT0 0x0080
+static int sh_mobile_sdhi_card_busy(struct tmio_mmc_host *host)
+{
+ u16 dat0;
+
+ /* check to see DAT[3:0] */
+ dat0 = sd_ctrl_read16(host, CTL_STATUS2);
+ return !(dat0 & SH_MOBILE_SDHI_DAT0);
+}
+
+#define SH_MOBILE_SDHI_SIGNAL_180V 0
+#define SH_MOBILE_SDHI_SIGNAL_330V 1
+
+static int sh_mobile_sdhi_set_ioctrl(struct tmio_mmc_host *host, int state)
+{
+ struct platform_device *pdev = host->pdev;
+ struct sh_mobile_sdhi *priv =
+ container_of(host->pdata, struct sh_mobile_sdhi, mmc_data);
+ struct pinctrl_state *pstate;
+ int ret;
+
+ if (state == SH_MOBILE_SDHI_SIGNAL_330V) {
+ pstate = priv->pinctrl_3v3;
+ } else if (state == SH_MOBILE_SDHI_SIGNAL_180V) {
+ pstate = priv->pinctrl_1v8;
+ } else {
+ dev_err(&pdev->dev, "update_ioctrl: unknown state\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (!pstate) {
+ ret = -EIO;
+ goto err;
+ }
+
+ ret = pinctrl_select_state(priv->pinctrl, pstate);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ return ret;
+}
+
+static int sh_mobile_sdhi_start_signal_voltage_switch(
+ struct tmio_mmc_host *host, unsigned char signal_voltage)
+{
+ struct mmc_host *mmc = host->mmc;
+ int ret;
+
+ if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) {
+ /* Enable 3.3V Signal */
+ if (!IS_ERR(mmc->supply.vqmmc)) {
+ /* ioctrl */
+ ret = sh_mobile_sdhi_set_ioctrl(host,
+ SH_MOBILE_SDHI_SIGNAL_330V);
+ if (ret) {
+ dev_err(&host->pdev->dev,
+ "3.3V pin function control failed\n");
+ return -EIO;
+ }
+
+ ret = regulator_set_voltage(mmc->supply.vqmmc,
+ 3300000, 3300000);
+ if (ret) {
+ dev_warn(&host->pdev->dev,
+ "3.3V signalling voltage failed\n");
+ return -EIO;
+ }
+ } else {
+ return -EIO;
+ }
+ usleep_range(5000, 10000);
+ } else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) {
+ /* Enable 1.8V Signal */
+ if (!IS_ERR(mmc->supply.vqmmc)) {
+ ret = regulator_set_voltage(mmc->supply.vqmmc,
+ 1800000, 1800000);
+ if (ret) {
+ dev_warn(&host->pdev->dev,
+ "1.8V signalling voltage failed\n");
+ return -EIO;
+ }
+ /* ioctrl */
+ ret = sh_mobile_sdhi_set_ioctrl(host,
+ SH_MOBILE_SDHI_SIGNAL_180V);
+ if (ret) {
+ dev_err(&host->pdev->dev,
+ "1.8V pin function control failed\n");
+ return -EIO;
+ }
+ } else {
+ return -EIO;
+ }
+ /* Wait for 5ms */
+ usleep_range(5000, 10000);
+ } else {
+ /* No signal voltage switch required */
+ }
+
+ return 0;
+}
+
+/* SCC registers */
+#define SH_MOBILE_SDHI_SCC_DTCNTL 0x000
+#define SH_MOBILE_SDHI_SCC_TAPSET 0x002
+#define SH_MOBILE_SDHI_SCC_DT2FF 0x004
+#define SH_MOBILE_SDHI_SCC_CKSEL 0x006
+#define SH_MOBILE_SDHI_SCC_RVSCNTL 0x008
+#define SH_MOBILE_SDHI_SCC_RVSREQ 0x00A
+
+/* Definitions for values the SH_MOBILE_SDHI_SCC_DTCNTL register */
+#define SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN (1 << 0)
+/* Definitions for values the SH_MOBILE_SDHI_SCC_CKSEL register */
+#define SH_MOBILE_SDHI_SCC_CKSEL_DTSEL (1 << 0)
+/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSCNTL register */
+#define SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN (1 << 0)
+/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSREQ register */
+#define SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR (1 << 2)
+
+static inline u32 sd_scc_read32(struct tmio_mmc_host *host, int addr)
+{
+ struct platform_device *pdev = host->pdev;
+ const struct of_device_id *of_id =
+ of_match_device(sh_mobile_sdhi_of_match, &pdev->dev);
+ const struct sh_mobile_sdhi_of_data *of_data = of_id->data;
+
+ return readl(host->ctl + of_data->scc_offset +
+ (addr << host->bus_shift));
+}
+
+static inline void sd_scc_write32(struct tmio_mmc_host *host, int addr,
+ u32 val)
+{
+ struct platform_device *pdev = host->pdev;
+ const struct of_device_id *of_id =
+ of_match_device(sh_mobile_sdhi_of_match, &pdev->dev);
+ const struct sh_mobile_sdhi_of_data *of_data = of_id->data;
+
+ writel(val, host->ctl + of_data->scc_offset +
+ (addr << host->bus_shift));
+}
+
+static bool sh_mobile_sdhi_inquiry_tuning(struct tmio_mmc_host *host)
+{
+ /* SDHI should be tuning only SDR104 and HS200 */
+ if (host->mmc->ios.timing == MMC_TIMING_UHS_SDR104 ||
+ host->mmc->ios.timing == MMC_TIMING_MMC_HS200)
+ return true;
+
+ return false;
+}
+
+static void sh_mobile_sdhi_init_tuning(struct tmio_mmc_host *host,
+ unsigned long *num)
+{
+ /* set sampling clock selection range */
+ if (host->scc_tapnum)
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_DTCNTL,
+ host->scc_tapnum << 16);
+
+ /* Initialize SCC */
+ sd_ctrl_write32(host, CTL_STATUS, 0x00000000);
+
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_DTCNTL,
+ SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN |
+ sd_scc_read32(host, SH_MOBILE_SDHI_SCC_DTCNTL));
+
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~0x0100 &
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_CKSEL,
+ SH_MOBILE_SDHI_SCC_CKSEL_DTSEL |
+ sd_scc_read32(host, SH_MOBILE_SDHI_SCC_CKSEL));
+
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, 0x0100 |
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSCNTL,
+ ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
+ sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL));
+
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_DT2FF, host->scc_tappos);
+
+ /* Read TAPNUM */
+ *num = (sd_scc_read32(host, SH_MOBILE_SDHI_SCC_DTCNTL) >> 16) & 0xff;
+}
+
+static int sh_mobile_sdhi_prepare_tuning(struct tmio_mmc_host *host,
+ unsigned long tap)
+{
+ /* Set sampling clock position */
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_TAPSET, tap);
+
+ return 0;
+}
+
+#define SH_MOBILE_SDHI_MAX_TAP 3
+static int sh_mobile_sdhi_select_tuning(struct tmio_mmc_host *host,
+ unsigned long *tap)
+{
+ unsigned long tap_num; /* total number of taps */
+ unsigned long tap_cnt; /* counter of tuning success */
+ unsigned long tap_set; /* tap position */
+ unsigned long tap_start; /* start position of tuning success */
+ unsigned long tap_end; /* end position of tuning success */
+ unsigned long ntap; /* temporary counter of tuning success */
+ unsigned long i;
+
+ /* Clear SCC_RVSREQ */
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSREQ, 0);
+
+ /* Select SCC */
+ tap_num = (sd_scc_read32(host,
+ SH_MOBILE_SDHI_SCC_DTCNTL) >> 16) & 0xff;
+
+ tap_cnt = 0;
+ ntap = 0;
+ tap_start = 0;
+ tap_end = 0;
+ for (i = 0; i < tap_num * 2; i++) {
+ if (tap[i] == 0)
+ ntap++;
+ else {
+ if (ntap > tap_cnt) {
+ tap_start = i - ntap;
+ tap_end = i - 1;
+ tap_cnt = ntap;
+ }
+ ntap = 0;
+ }
+ }
+
+ if (ntap > tap_cnt) {
+ tap_start = i - ntap;
+ tap_end = i - 1;
+ tap_cnt = ntap;
+ }
+
+ if (tap_cnt >= SH_MOBILE_SDHI_MAX_TAP)
+ tap_set = (tap_start + tap_end) / 2 % tap_num;
+ else
+ return -EIO;
+
+ /* Set SCC */
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_TAPSET, tap_set);
+
+ /* Enable auto re-tuning */
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSCNTL,
+ SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN |
+ sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL));
+
+ return 0;
+}
+
+static bool sh_mobile_sdhi_retuning(struct tmio_mmc_host *host)
+{
+ /* Check SCC error */
+ if (sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL) &
+ SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &&
+ sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSREQ) &
+ SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR) {
+ /* Clear SCC error */
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSREQ, 0);
+ return true;
+ }
+ return false;
+}
+
+static void sh_mobile_sdhi_hw_reset(struct tmio_mmc_host *host)
+{
+ struct tmio_mmc_data *pdata = host->pdata;
+
+ if (pdata->flags & TMIO_MMC_HAS_UHS_SCC) {
+ /* Reset SCC */
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~0x0100 &
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_CKSEL,
+ ~SH_MOBILE_SDHI_SCC_CKSEL_DTSEL &
+ sd_scc_read32(host, SH_MOBILE_SDHI_SCC_CKSEL));
+
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, 0x0100 |
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSCNTL,
+ ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
+ sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL));
+
+ sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSCNTL,
+ ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN &
+ sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL));
+ }
+}
+
static int sh_mobile_sdhi_wait_idle(struct tmio_mmc_host *host)
{
int timeout = 1000;
@@ -163,6 +535,7 @@
case CTL_SD_MEM_CARD_OPT:
case CTL_TRANSACTION_CTL:
case CTL_DMA_ENABLE:
+ case HOST_MODE:
return sh_mobile_sdhi_wait_idle(host);
}
@@ -189,10 +562,12 @@
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)
@@ -204,9 +579,12 @@
struct tmio_mmc_data *mmd = pdev->dev.platform_data;
struct tmio_mmc_host *host;
struct resource *res;
- int irq, ret, i = 0;
+ struct device_node *np = pdev->dev.of_node;
+ int irq, ret, i;
bool multiplexed_isr = true;
struct tmio_mmc_dma *dma_priv;
+ int clk_rate;
+ u32 num, tapnum = 0, tappos;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
@@ -228,6 +606,58 @@
goto eprobe;
}
+ if (np && !of_property_read_u32(np, "renesas,clk-rate", &clk_rate)) {
+ if (clk_rate) {
+ clk_prepare_enable(priv->clk);
+ ret = clk_set_rate(priv->clk, clk_rate);
+ if (ret < 0)
+ dev_err(&pdev->dev,
+ "cannot set clock rate: %d\n", ret);
+
+ clk_disable_unprepare(priv->clk);
+ }
+ }
+
+ if (np && !of_property_read_u32(np, "renesas,mmc-scc-tapnum", &num))
+ tapnum = num;
+
+ priv->pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (!IS_ERR(priv->pinctrl)) {
+ const char *p;
+ struct pinctrl_state *pstate;
+
+ num = of_property_count_strings(np, "pinctrl-names");
+ if (num < 1) {
+ dev_err(&pdev->dev,
+ "not find pinctrl for voltage switch\n");
+ ret = -ENODEV;
+ goto eprobe;
+ }
+
+ for (i = 0; i < num; i++) {
+ ret = of_property_read_string_index(np, "pinctrl-names",
+ i, &p);
+ if (ret)
+ continue;
+
+ pstate = pinctrl_lookup_state(priv->pinctrl, p);
+ if (IS_ERR(pstate))
+ continue;
+
+ if (!strcmp(p, "3v3"))
+ priv->pinctrl_3v3 = pstate;
+ else if (!strcmp(p, "1v8"))
+ priv->pinctrl_1v8 = pstate;
+ }
+
+ if (!priv->pinctrl_3v3 && !priv->pinctrl_1v8) {
+ dev_err(&pdev->dev,
+ "not find pinctrl state for voltage switch\n");
+ ret = -ENODEV;
+ goto eprobe;
+ }
+ }
+
host = tmio_mmc_host_alloc(pdev);
if (!host) {
ret = -ENOMEM;
@@ -238,9 +668,22 @@
host->write16_hook = sh_mobile_sdhi_write16_hook;
host->clk_enable = sh_mobile_sdhi_clk_enable;
host->clk_disable = sh_mobile_sdhi_clk_disable;
+ host->card_busy = sh_mobile_sdhi_card_busy;
host->multi_io_quirk = sh_mobile_sdhi_multi_io_quirk;
- /* SD control register space size is 0x100, 0x200 for bus_shift=1 */
- if (resource_size(res) > 0x100)
+ host->set_clk_div = sh_mobile_sdhi_set_clk_div;
+ host->start_signal_voltage_switch =
+ sh_mobile_sdhi_start_signal_voltage_switch;
+ host->inquiry_tuning = sh_mobile_sdhi_inquiry_tuning;
+ host->init_tuning = sh_mobile_sdhi_init_tuning;
+ host->prepare_tuning = sh_mobile_sdhi_prepare_tuning;
+ host->select_tuning = sh_mobile_sdhi_select_tuning;
+ host->retuning = sh_mobile_sdhi_retuning;
+ host->hw_reset = sh_mobile_sdhi_hw_reset;
+ host->scc_tapnum = tapnum;
+ /* SD control register space size */
+ if (resource_size(res) > 0x400) /* 0x400 for bus_shift=2 */
+ host->bus_shift = 2;
+ else if (resource_size(res) > 0x100) /* 0x100, 0x200 for bus_shift=1 */
host->bus_shift = 1;
else
host->bus_shift = 0;
@@ -277,11 +720,39 @@
if (of_id && of_id->data) {
const struct sh_mobile_sdhi_of_data *of_data = of_id->data;
+ const struct sh_mobile_sdhi_scc *taps = of_data->taps;
mmc_data->flags |= of_data->tmio_flags;
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;
+ dma_priv->sdbuf_64bit = of_data->sdbuf_64bit;
+ if (np && !of_property_read_u32(np, "renesas,mmc-scc-tappos",
+ &tappos)) {
+ host->scc_tappos = tappos;
+ } else {
+ for (i = 0, taps = of_data->taps;
+ i < of_data->taps_num; i++, taps++) {
+ if (taps->clk == 0 || taps->clk == clk_rate) {
+ host->scc_tappos = taps->tap;
+ break;
+ }
+ }
+ if (taps->clk != 0 && taps->clk != clk_rate)
+ dev_warn(&host->pdev->dev, "Unknown clock rate for SDR104 and HS200\n");
+ }
+ }
+
+ if (of_find_property(np, "sd-uhs-sdr50", NULL))
+ mmc_data->capabilities |= MMC_CAP_UHS_SDR50;
+ if (of_find_property(np, "sd-uhs-sdr104", NULL))
+ mmc_data->capabilities |= MMC_CAP_UHS_SDR104;
+
+ if (mmc_data->capabilities & MMC_CAP_UHS_SDR104) {
+ mmc_data->capabilities |= MMC_CAP_HW_RESET;
+ mmc_data->flags |= TMIO_MMC_HAS_UHS_SCC;
}
ret = tmio_mmc_host_probe(host, mmc_data);
@@ -326,6 +797,7 @@
}
if (multiplexed_isr) {
+ i = 0;
while (1) {
irq = platform_get_irq(pdev, i);
if (irq < 0)
@@ -370,8 +842,8 @@
}
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_SYSTEM_SLEEP_PM_OPS(tmio_mmc_host_suspend,
+ tmio_mmc_host_resume)
SET_RUNTIME_PM_OPS(tmio_mmc_host_runtime_suspend,
tmio_mmc_host_runtime_resume,
NULL)
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 @@
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 @@
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 4a597f5a..2710344 100644
--- a/drivers/mmc/host/tmio_mmc.h
+++ b/drivers/mmc/host/tmio_mmc.h
@@ -1,6 +1,7 @@
/*
* linux/drivers/mmc/host/tmio_mmc.h
*
+ * Copyright (C) 2015 Renesas Electronics Corporation
* Copyright (C) 2007 Ian Molton
* Copyright (C) 2004 Ian Molton
*
@@ -17,6 +18,7 @@
#define TMIO_MMC_H
#include <linux/dmaengine.h>
+#include <linux/completion.h>
#include <linux/highmem.h>
#include <linux/mmc/tmio.h>
#include <linux/mutex.h>
@@ -44,6 +46,7 @@
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);
};
@@ -93,12 +96,25 @@
struct mutex ios_lock; /* protect set_ios() context */
bool native_hotplug;
bool sdio_irq_enabled;
+ u32 scc_tapnum;
+ u32 scc_tappos;
+ bool done_tuning;
+ struct completion completion;
int (*write16_hook)(struct tmio_mmc_host *host, int addr);
int (*clk_enable)(struct platform_device *pdev, unsigned int *f);
void (*clk_disable)(struct platform_device *pdev);
int (*multi_io_quirk)(struct mmc_card *card,
unsigned int direction, int blk_size);
+ int (*card_busy)(struct tmio_mmc_host *host);
+ int (*start_signal_voltage_switch)(struct tmio_mmc_host *host,
+ unsigned char signal_voltage);
+ bool (*inquiry_tuning)(struct tmio_mmc_host *host);
+ void (*init_tuning)(struct tmio_mmc_host *host, unsigned long *num);
+ int (*prepare_tuning)(struct tmio_mmc_host *host, unsigned long tap);
+ int (*select_tuning)(struct tmio_mmc_host *host, unsigned long *tap);
+ bool (*retuning)(struct tmio_mmc_host *host);
+ void (*hw_reset)(struct tmio_mmc_host *host);
};
struct tmio_mmc_host *tmio_mmc_host_alloc(struct platform_device *pdev);
@@ -166,6 +182,11 @@
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..fbf0d02
--- /dev/null
+++ b/drivers/mmc/host/tmio_mmc_dma_gen3.c
@@ -0,0 +1,190 @@
+/*
+ * 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/mmc/tmio.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;
+
+ /* 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);
+}
+
+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);
+}
+
+void tmio_mmc_request_dma(struct tmio_mmc_host *host,
+ struct tmio_mmc_data *pdata)
+{
+ /* 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);
+}
+
+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 a10fde4..a959fc8 100644
--- a/drivers/mmc/host/tmio_mmc_pio.c
+++ b/drivers/mmc/host/tmio_mmc_pio.c
@@ -1,6 +1,7 @@
/*
* linux/drivers/mmc/host/tmio_mmc_pio.c
*
+ * Copyright (C) 2015-2016 Renesas Electronics Corporation
* Copyright (C) 2011 Guennadi Liakhovetski
* Copyright (C) 2007 Ian Molton
* Copyright (C) 2004 Ian Molton
@@ -34,6 +35,7 @@
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/mfd/tmio.h>
+#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/slot-gpio.h>
@@ -51,6 +53,12 @@
#include "tmio_mmc.h"
+static int tmio_mmc_execute_tuning(struct mmc_host *mmc, u32 opcode);
+static int tmio_mmc_start_data(struct tmio_mmc_host *host,
+ struct mmc_data *data);
+static int tmio_mmc_start_command(struct tmio_mmc_host *host,
+ struct mmc_command *cmd);
+
void tmio_mmc_enable_mmc_irqs(struct tmio_mmc_host *host, u32 i)
{
host->sdcard_irq_mask &= ~(i & TMIO_MASK_IRQ);
@@ -158,7 +166,8 @@
u32 clk = 0, clock;
if (new_clock) {
- for (clock = host->mmc->f_min, clk = 0x80000080;
+ clk = 0x80000080;
+ for (clock = host->mmc->f_min;
new_clock >= (clock<<1); clk >>= 1)
clock <<= 1;
@@ -180,24 +189,28 @@
/* implicit BUG_ON(!res) */
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, ~0x0100 &
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
- msleep(10);
+ if (!(host->pdata->flags & TMIO_MMC_CLK_NO_SLEEP))
+ msleep(10);
}
static void tmio_mmc_clk_start(struct tmio_mmc_host *host)
{
sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, 0x0100 |
sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
- msleep(10);
+ if (!(host->pdata->flags & TMIO_MMC_CLK_NO_SLEEP))
+ msleep(10);
/* implicit BUG_ON(!res) */
if (host->pdata->flags & TMIO_MMC_HAVE_HIGH_REG) {
sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0100);
- msleep(10);
+ if (!(host->pdata->flags & TMIO_MMC_CLK_NO_SLEEP))
+ msleep(10);
}
}
@@ -221,6 +234,7 @@
delayed_reset_work.work);
struct mmc_request *mrq;
unsigned long flags;
+ u16 clk;
spin_lock_irqsave(&host->lock, flags);
mrq = host->mrq;
@@ -254,7 +268,9 @@
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;
@@ -271,6 +287,8 @@
{
struct mmc_request *mrq;
unsigned long flags;
+ bool result;
+ struct mmc_command *cmd = host->cmd;
spin_lock_irqsave(&host->lock, flags);
@@ -284,7 +302,9 @@
host->data = NULL;
host->force_pio = false;
- cancel_delayed_work(&host->delayed_reset_work);
+ if (!(host->inquiry_tuning && host->inquiry_tuning(host) &&
+ !host->done_tuning) || cmd != mrq->sbc)
+ cancel_delayed_work(&host->delayed_reset_work);
host->mrq = NULL;
spin_unlock_irqrestore(&host->lock, flags);
@@ -292,6 +312,29 @@
if (mrq->cmd->error || (mrq->data && mrq->data->error))
tmio_mmc_abort_dma(host);
+ if (host->inquiry_tuning && host->inquiry_tuning(host) &&
+ !host->done_tuning) {
+ /* call retuning() to clear SCC error bit */
+ if (host->retuning)
+ host->retuning(host);
+ /* finish processing tuning request */
+ complete(&host->completion);
+ return;
+ }
+
+ /* Check retuning */
+ if (host->retuning && host->done_tuning) {
+ result = host->retuning(host);
+ if (result || (mrq->cmd->error == -EILSEQ))
+ host->done_tuning = false;
+ }
+
+ if (cmd == mrq->sbc) {
+ /* finish SET_BLOCK_COUNT request */
+ complete(&host->completion);
+ return;
+ }
+
mmc_request_done(host->mmc, mrq);
pm_runtime_mark_last_busy(mmc_dev(host->mmc));
@@ -305,6 +348,165 @@
tmio_mmc_finish_request(host);
}
+#define TMIO_MMC_MAX_TUNING_LOOP 40
+
+static int tmio_mmc_execute_tuning(struct mmc_host *mmc, u32 opcode)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ struct mmc_ios *ios = &mmc->ios;
+
+ unsigned long timeout, val, num;
+ unsigned long *tap;
+ int tuning_loop_counter = TMIO_MMC_MAX_TUNING_LOOP;
+ int ret, timeleft;
+
+ struct mmc_request mrq = {NULL};
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+ u8 *data_buf;
+ unsigned int tm = CMDREQ_TIMEOUT;
+ unsigned long flags;
+ u8 data_size = 64;
+
+ if (ios->timing != MMC_TIMING_UHS_SDR50 &&
+ ios->timing != MMC_TIMING_UHS_SDR104 &&
+ ios->timing != MMC_TIMING_MMC_HS200 &&
+ ios->timing != MMC_TIMING_MMC_HS400)
+ return 0;
+
+ if ((host->inquiry_tuning && !host->inquiry_tuning(host)) ||
+ host->done_tuning)
+ return 0;
+
+ host->init_tuning(host, &num);
+
+ tap = kmalloc(num * 2, GFP_KERNEL);
+ if (tap == NULL) {
+ ret = -ENOMEM;
+ goto err_tap;
+ }
+
+ if (ios->timing == MMC_TIMING_MMC_HS200)
+ data_size = 128;
+
+ data_buf = kmalloc(data_size, GFP_KERNEL);
+ if (data_buf == NULL) {
+ ret = -ENOMEM;
+ goto err_data;
+ }
+
+ val = 0;
+
+ /*
+ * Issue CMD19 repeatedly till Execute Tuning is set to 0 or the number
+ * of loops reaches 40 times or a timeout of 150ms occurs.
+ */
+ timeout = 150;
+ do {
+ if (host->prepare_tuning)
+ host->prepare_tuning(host, val % num);
+
+ if (!tuning_loop_counter && !timeout)
+ break;
+
+ /*
+ * In response to CMD19, the card sends 64 bytes of tuning
+ * block to the Host Controller. So we set the block size
+ * to 64 here.
+ */
+
+ spin_lock_irqsave(&host->lock, flags);
+ init_completion(&host->completion);
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ cmd.opcode = opcode;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+ cmd.retries = 0;
+ cmd.error = 0;
+
+ data.blksz = data_size;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+ data.error = 0;
+
+ sg_init_one(&sg, data_buf, data_size);
+
+ host->mrq = &mrq;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ ret = tmio_mmc_start_data(host, mrq.data);
+ if (ret)
+ goto out;
+
+ ret = tmio_mmc_start_command(host, mrq.cmd);
+ if (ret)
+ goto out;
+
+ timeleft = wait_for_completion_timeout(&host->completion,
+ msecs_to_jiffies(tm));
+ if (timeleft < 0) {
+ ret = timeleft;
+ goto out;
+ }
+
+ if (!timeleft) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ /* Check CRC error */
+ if (cmd.error && cmd.error != -EILSEQ) {
+ ret = cmd.error;
+ goto out;
+ }
+ if (data.error && data.error != -EILSEQ) {
+ ret = data.error;
+ goto out;
+ }
+
+ tap[val] = (cmd.error | data.error);
+
+ val++;
+ tuning_loop_counter--;
+ timeout--;
+ mdelay(1);
+ } while ((val < (num * 2)) && (tuning_loop_counter || timeout));
+
+ /*
+ * The Host Driver has exhausted the maximum number of loops allowed,
+ * so use fixed sampling frequency.
+ */
+ if (tuning_loop_counter || timeout) {
+ if (host->select_tuning) {
+ ret = host->select_tuning(host, tap);
+ if (ret < 0)
+ goto out;
+ }
+ host->done_tuning = true;
+ } else {
+ dev_warn(&host->pdev->dev, ": Tuning procedure failed\n");
+ ret = -EIO;
+ goto out;
+ }
+
+out:
+ kfree(data_buf);
+err_data:
+ kfree(tap);
+err_tap:
+ if (ret < 0 && host->hw_reset)
+ host->hw_reset(host);
+
+ return ret;
+
+}
+
/* These are the bitmasks the tmio chip requires to implement the MMC response
* types. Note that R1 and R6 are the same in this scheme. */
#define APP_CMD 0x0040
@@ -334,6 +536,8 @@
switch (mmc_resp_type(cmd)) {
case MMC_RSP_NONE: c |= RESP_NONE; break;
case MMC_RSP_R1: c |= RESP_R1; break;
+ case MMC_RSP_R1 & ~MMC_RSP_CRC:
+ c |= RESP_R1; break;
case MMC_RSP_R1B: c |= RESP_R1B; break;
case MMC_RSP_R2: c |= RESP_R2; break;
case MMC_RSP_R3: c |= RESP_R3; break;
@@ -360,7 +564,8 @@
* 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)
@@ -517,7 +722,7 @@
schedule_work(&host->done);
}
-static void tmio_mmc_data_irq(struct tmio_mmc_host *host)
+static void tmio_mmc_data_irq(struct tmio_mmc_host *host, unsigned int stat)
{
struct mmc_data *data;
spin_lock(&host->lock);
@@ -526,6 +731,9 @@
if (!data)
goto out;
+ if (stat & TMIO_STAT_CRCFAIL || stat & TMIO_STAT_STOPBIT_ERR ||
+ stat & TMIO_STAT_TXUNDERRUN)
+ data->error = -EILSEQ;
if (host->chan_tx && (data->flags & MMC_DATA_WRITE) && !host->force_pio) {
u32 status = sd_ctrl_read32(host, CTL_STATUS);
bool done = false;
@@ -574,8 +782,6 @@
goto out;
}
- host->cmd = NULL;
-
/* This controller is sicker than the PXA one. Not only do we need to
* drop the top 8 bits of the first response word, we also need to
* modify the order of the response for short response command types.
@@ -595,14 +801,16 @@
if (stat & TMIO_STAT_CMDTIMEOUT)
cmd->error = -ETIMEDOUT;
- else if (stat & TMIO_STAT_CRCFAIL && cmd->flags & MMC_RSP_CRC)
+ else if ((stat & TMIO_STAT_CRCFAIL && cmd->flags & MMC_RSP_CRC) ||
+ stat & TMIO_STAT_STOPBIT_ERR ||
+ stat & TMIO_STAT_CMD_IDX_ERR)
cmd->error = -EILSEQ;
/* If there is data to handle we enable data IRQs here, and
* we will ultimatley finish the request in the data_end handler.
* If theres no data or we encountered an error, finish now.
*/
- if (host->data && !cmd->error) {
+ if (host->data && (!cmd->error || cmd->error == -EILSEQ)) {
if (host->data->flags & MMC_DATA_READ) {
if (host->force_pio || !host->chan_rx)
tmio_mmc_enable_mmc_irqs(host, TMIO_MASK_READOP);
@@ -688,7 +896,7 @@
/* Data transfer completion */
if (ireg & TMIO_STAT_DATAEND) {
tmio_mmc_ack_mmc_irqs(host, TMIO_STAT_DATAEND);
- tmio_mmc_data_irq(host);
+ tmio_mmc_data_irq(host, status);
return true;
}
@@ -790,6 +998,7 @@
struct tmio_mmc_host *host = mmc_priv(mmc);
unsigned long flags;
int ret;
+ u32 opcode;
spin_lock_irqsave(&host->lock, flags);
@@ -811,6 +1020,50 @@
pm_runtime_get_sync(mmc_dev(mmc));
+ if (host->inquiry_tuning && host->inquiry_tuning(host) &&
+ !host->done_tuning) {
+ 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->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->inquiry_tuning && host->inquiry_tuning(host) &&
+ !host->done_tuning) {
+ 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)
@@ -900,12 +1153,21 @@
static void tmio_mmc_set_bus_width(struct tmio_mmc_host *host,
unsigned char bus_width)
{
+ sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, ~0xa000 &
+ sd_ctrl_read16(host, CTL_SD_MEM_CARD_OPT));
+
switch (bus_width) {
case MMC_BUS_WIDTH_1:
- sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x80e0);
+ sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x8000 |
+ sd_ctrl_read16(host, CTL_SD_MEM_CARD_OPT));
break;
case MMC_BUS_WIDTH_4:
- sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x00e0);
+ sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x0000 |
+ sd_ctrl_read16(host, CTL_SD_MEM_CARD_OPT));
+ break;
+ case MMC_BUS_WIDTH_8:
+ sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x2000 |
+ sd_ctrl_read16(host, CTL_SD_MEM_CARD_OPT));
break;
}
}
@@ -1013,6 +1275,45 @@
return blk_size;
}
+static int tmio_mmc_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ int ret;
+
+ if (host->start_signal_voltage_switch) {
+ ret = host->start_signal_voltage_switch(host,
+ ios->signal_voltage);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tmio_mmc_card_busy(struct mmc_host *mmc)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ int ret;
+
+ if (host->card_busy) {
+ ret = host->card_busy(host);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static void tmio_mmc_hw_reset(struct mmc_host *mmc)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+
+ if (host->hw_reset)
+ host->hw_reset(host);
+
+ host->done_tuning = false;
+}
+
static const struct mmc_host_ops tmio_mmc_ops = {
.request = tmio_mmc_request,
.set_ios = tmio_mmc_set_ios,
@@ -1020,6 +1321,11 @@
.get_cd = mmc_gpio_get_cd,
.enable_sdio_irq = tmio_mmc_enable_sdio_irq,
.multi_io_quirk = tmio_multi_io_quirk,
+ .start_signal_voltage_switch
+ = tmio_mmc_start_signal_voltage_switch,
+ .card_busy = tmio_mmc_card_busy,
+ .execute_tuning = tmio_mmc_execute_tuning,
+ .hw_reset = tmio_mmc_hw_reset,
};
static int tmio_mmc_init_ocr(struct tmio_mmc_host *host)
@@ -1120,13 +1426,14 @@
mmc->ops = &tmio_mmc_ops;
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_CACHE_SIZE / mmc->max_blk_size) *
- mmc->max_segs;
+ mmc->max_blk_count = pdata->max_blk_count ? :
+ (PAGE_CACHE_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;
+ _host->done_tuning = false;
_host->native_hotplug = !(pdata->flags & TMIO_MMC_USE_GPIO_CD ||
mmc->caps & MMC_CAP_NEEDS_POLL ||
mmc->caps & MMC_CAP_NONREMOVABLE ||
@@ -1274,4 +1581,20 @@
EXPORT_SYMBOL(tmio_mmc_host_runtime_resume);
#endif
+#ifdef CONFIG_PM_SLEEP
+int tmio_mmc_host_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+EXPORT_SYMBOL(tmio_mmc_host_suspend);
+
+int tmio_mmc_host_resume(struct device *dev)
+{
+ /* Empty function 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 9fbe92a..6455bdd 100644
--- a/drivers/net/ethernet/renesas/ravb.h
+++ b/drivers/net/ethernet/renesas/ravb.h
@@ -76,6 +76,7 @@
CDAR20 = 0x0060,
CDAR21 = 0x0064,
ESR = 0x0088,
+ APSR = 0x008C,
RCR = 0x0090,
RQC0 = 0x0094,
RQC1 = 0x0098,
@@ -83,6 +84,7 @@
RQC3 = 0x00A0,
RQC4 = 0x00A4,
RPC = 0x00B0,
+ RTC = 0x00B4,
UFCW = 0x00BC,
UFCS = 0x00C0,
UFCV0 = 0x00C4,
@@ -128,11 +130,52 @@
SFP29 = 0x0174,
SFP30 = 0x0178,
SFP31 = 0x017C,
+ SFV0 = 0x01B8,
+ SFV1 = 0x01BC,
SFM0 = 0x01C0,
SFM1 = 0x01C4,
+ SFL = 0x01C8,
+ PCRC = 0x01CC,
+ CIAR0 = 0x0200,
+ CIAR1 = 0x0204,
+ CIAR2 = 0x0208,
+ CIAR3 = 0x020C,
+ CIAR4 = 0x0210,
+ CIAR5 = 0x0214,
+ CIAR6 = 0x0218,
+ CIAR7 = 0x021C,
+ CIAR8 = 0x0220,
+ CIAR9 = 0x0224,
+ CIAR10 = 0x0228,
+ CIAR11 = 0x022C,
+ CIAR12 = 0x0230,
+ CIAR13 = 0x0234,
+ CIAR14 = 0x0238,
+ CIAR15 = 0x023C,
+ CIAR16 = 0x0240,
+ CIAR17 = 0x0244,
+ LIAR0 = 0x0280,
+ LIAR1 = 0x0284,
+ LIAR2 = 0x0288,
+ LIAR3 = 0x028C,
+ LIAR4 = 0x0290,
+ LIAR5 = 0x0294,
+ LIAR6 = 0x0298,
+ LIAR7 = 0x029C,
+ LIAR8 = 0x02A0,
+ LIAR9 = 0x02A4,
+ LIAR10 = 0x02A8,
+ LIAR11 = 0x02AC,
+ LIAR12 = 0x02B0,
+ LIAR13 = 0x02B4,
+ LIAR14 = 0x02B8,
+ LIAR15 = 0x02BC,
+ LIAR16 = 0x02C0,
+ LIAR17 = 0x02C4,
TGC = 0x0300,
TCCR = 0x0304,
TSR = 0x0308,
+ MFA = 0x030C,
TFA0 = 0x0310,
TFA1 = 0x0314,
TFA2 = 0x0318,
@@ -157,6 +200,9 @@
TIC = 0x0378,
TIS = 0x037C,
ISS = 0x0380,
+ CIE = 0x0384,
+ RIC3 = 0x0388,
+ RIS3 = 0x038C,
GCCR = 0x0390,
GMTT = 0x0394,
GPTC = 0x0398,
@@ -170,6 +216,48 @@
GCT0 = 0x03B8,
GCT1 = 0x03BC,
GCT2 = 0x03C0,
+ GSR = 0x03C4,
+ GIE = 0x03CC,
+ GID = 0x03D0,
+ GIL = 0x03D4,
+ GACP = 0x03DC,
+ GPTF0 = 0x03E0,
+ GPTF1 = 0x03E4,
+ GPTF2 = 0x03E8,
+ GPTF3 = 0x03EC,
+ GCAT0 = 0x0400,
+ GCAT1 = 0x0404,
+ GCAT2 = 0x0408,
+ GCAT3 = 0x040C,
+ GCAT4 = 0x0410,
+ GCAT5 = 0x0414,
+ GCAT6 = 0x0418,
+ GCAT7 = 0x041C,
+ GCAT8 = 0x0420,
+ GCAT9 = 0x0424,
+ GCAT10 = 0x0428,
+ GCAT11 = 0x042C,
+ GCAT12 = 0x0430,
+ GCAT13 = 0x0434,
+ GCAT14 = 0x0438,
+ GCAT15 = 0x043C,
+ DIL = 0x0440,
+ EIL = 0x0444,
+ TIL = 0x0448,
+ DIE = 0x0450,
+ DID = 0x0454,
+ EIE = 0x0458,
+ EID = 0x045C,
+ RIE0 = 0x0460,
+ RID0 = 0x0464,
+ RIE1 = 0x0468,
+ RID1 = 0x046c,
+ RIE2 = 0x0470,
+ RID2 = 0x0474,
+ TIE = 0x0478,
+ TID = 0x047c,
+ RIE3 = 0x0488,
+ RID3 = 0x048C,
/* E-MAC registers */
ECMR = 0x0500,
@@ -179,9 +267,12 @@
PIR = 0x0520,
PSR = 0x0528,
PIPR = 0x052c,
+ APR = 0x0554,
MPR = 0x0558,
PFTCR = 0x055c,
PFRCR = 0x0560,
+ PFTTLR = 0x0564,
+ PFTTCR = 0x0568,
GECMR = 0x05b0,
MAHR = 0x05c0,
MALR = 0x05c8,
@@ -309,6 +400,13 @@
RTC_MFL1 = 0x0FFF0000,
};
+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,
@@ -556,6 +654,16 @@
ISS_DPS15 = 0x80000000,
};
+/* CIE */
+enum CIE_BIT {
+ CIE_CRIE = 0x00000001, /* Common Receive Interrupt Enable */
+ CIE_CTIE = 0x00000100, /* Common Transmit Interrupt Enable */
+ CIE_RQFM = 0x00010000, /* Reception Queue Full Mode */
+ CIE_CL0M = 0x00020000, /* Common Line 0 Mode */
+ CIE_RFWL = 0x00040000, /* Rx-FIFO Warning interrupt Line */
+ CIE_RFFL = 0x00080000, /* Rx-FIFO Full interrupt Line */
+};
+
/* GCCR */
enum GCCR_BIT {
GCCR_TCR = 0x00000003,
@@ -592,6 +700,18 @@
GIS_PTMF = 0x00000004,
};
+/* GIx */
+#define RAVB_GIx_ALL 0xffff03ff
+
+/* RIx0 */
+#define RAVB_RIx0_ALL 0x0003ffff
+
+/* RIx2 */
+#define RAVB_RIx2_ALL 0x8003ffff
+
+/* TIx */
+#define RAVB_TIx_ALL 0x000fffff
+
/* ECMR */
enum ECMR_BIT {
ECMR_PRM = 0x00000001,
@@ -745,7 +865,6 @@
#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;
@@ -820,6 +939,9 @@
unsigned no_avb_link:1;
unsigned avb_link_active_low:1;
+ int rx_irqs[NUM_RX_QUEUE];
+ int tx_irqs[NUM_TX_QUEUE];
+ 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 c9794bf..5851405 100644
--- a/drivers/net/ethernet/renesas/ravb_main.c
+++ b/drivers/net/ethernet/renesas/ravb_main.c
@@ -1,6 +1,6 @@
/* Renesas Ethernet AVB device driver
*
- * Copyright (C) 2014-2015 Renesas Electronics Corporation
+ * Copyright (C) 2014-2016 Renesas Electronics Corporation
* Copyright (C) 2015 Renesas Solutions Corp.
* Copyright (C) 2015 Cogent Embedded, Inc. <source@cogentembedded.com>
*
@@ -33,6 +33,7 @@
#include <linux/spinlock.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include <asm/div64.h>
@@ -44,6 +45,16 @@
NETIF_MSG_RX_ERR | \
NETIF_MSG_TX_ERR)
+static const char *ravb_rx_irqs[NUM_RX_QUEUE] = {
+ "ch0", /* RAVB_BE */
+ "ch1", /* RAVB_NC */
+};
+
+static const char *ravb_tx_irqs[NUM_TX_QUEUE] = {
+ "ch18", /* RAVB_BE */
+ "ch19", /* RAVB_NC */
+};
+
int ravb_wait(struct net_device *ndev, enum ravb_reg reg, u32 mask, u32 value)
{
int i;
@@ -184,6 +195,7 @@
struct ravb_private *priv = netdev_priv(ndev);
int ring_size;
int i;
+ int num_tx_desc = priv->num_tx_desc;
/* Free RX skb ringbuffer */
if (priv->rx_skb[q]) {
@@ -215,7 +227,7 @@
if (priv->tx_ring[q]) {
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;
@@ -229,9 +241,10 @@
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;
@@ -245,10 +258,10 @@
for (i = 0; i < priv->num_rx_ring[q]; i++) {
/* RX descriptor */
rx_desc = &priv->rx_ring[q][i];
- /* The size of the buffer should be on 16-byte boundary. */
- rx_desc->ds_cc = cpu_to_le16(ALIGN(PKT_BUF_SZ, 16));
- dma_addr = dma_map_single(ndev->dev.parent, priv->rx_skb[q][i]->data,
- ALIGN(PKT_BUF_SZ, 16),
+ rx_desc->ds_cc = cpu_to_le16(PKT_BUF_SZ);
+ dma_addr = dma_map_single(ndev->dev.parent,
+ priv->rx_skb[q][i]->data,
+ PKT_BUF_SZ,
DMA_FROM_DEVICE);
/* We just set the data size to 0 for a failed mapping which
* should prevent DMA from happening...
@@ -267,8 +280,10 @@
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 +306,7 @@
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 +342,7 @@
/* 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);
@@ -362,8 +378,6 @@
ravb_write(ndev,
(ndev->dev_addr[4] << 8) | (ndev->dev_addr[5]), MALR);
- ravb_write(ndev, 1, MPR);
-
/* E-MAC status register clear */
ravb_write(ndev, ECSR_ICD | ECSR_MPD, ECSR);
@@ -375,6 +389,7 @@
static int ravb_dmac_init(struct net_device *ndev)
{
int error;
+ struct ravb_private *priv = netdev_priv(ndev);
/* Set CONFIG mode */
error = ravb_config(ndev);
@@ -401,7 +416,8 @@
#endif
/* Set AVB RX */
- ravb_write(ndev, RCR_EFFS | RCR_ENCF | RCR_ETS0 | 0x18000000, RCR);
+ ravb_write(ndev,
+ RCR_EFFS | RCR_ENCF | RCR_ETS0 | RCR_ESF | 0x18000000, RCR);
/* Set FIFO size */
ravb_write(ndev, TGC_TQP_AVBMODE1 | 0x00222200, TGC);
@@ -410,14 +426,30 @@
ravb_write(ndev, TCCR_TFEN, TCCR);
/* Interrupt init: */
- /* Frame receive */
- ravb_write(ndev, RIC0_FRE0 | RIC0_FRE1, RIC0);
- /* Disable FIFO full warning */
- ravb_write(ndev, 0, RIC1);
- /* Receive FIFO full error, descriptor empty */
- ravb_write(ndev, RIC2_QFE0 | RIC2_QFE1 | RIC2_RFFE, RIC2);
- /* Frame transmitted, timestamp FIFO updated */
- ravb_write(ndev, TIC_FTE0 | TIC_FTE1 | TIC_TFUE, TIC);
+ if (priv->chip_id == RCAR_GEN2) {
+ /* Frame receive */
+ ravb_write(ndev, RIC0_FRE0 | RIC0_FRE1, RIC0);
+ /* Disable FIFO full warning */
+ ravb_write(ndev, 0, RIC1);
+ /* Receive FIFO full error, descriptor empty */
+ ravb_write(ndev, RIC2_QFE0 | RIC2_QFE1 | RIC2_RFFE, RIC2);
+ /* Frame transmitted, timestamp FIFO updated */
+ ravb_write(ndev, TIC_FTE0 | TIC_FTE1 | TIC_TFUE, TIC);
+ } else {
+ /* Clear CIE.CTIE, CIE.CRIE, DIL.DPLx */
+ ravb_write(ndev, 0, CIE);
+ ravb_write(ndev, 0, DIL);
+ /* Set queue specific interrupt */
+ ravb_write(ndev, CIE_CRIE | CIE_CTIE | CIE_CL0M, CIE);
+ /* Frame receive */
+ ravb_write(ndev, RIC0_FRE0 | RIC0_FRE1, RIE0);
+ /* Disable FIFO full warning */
+ ravb_write(ndev, 0, RID1);
+ /* Receive FIFO full error, descriptor empty */
+ ravb_write(ndev, RIC2_QFE0 | RIC2_QFE1 | RIC2_RFFE, RIE2);
+ /* Frame transmitted, timestamp FIFO updated */
+ ravb_write(ndev, TIC_FTE0 | TIC_FTE1 | TIC_TFUE, TIE);
+ }
/* Setting the control will start the AVB-DMAC process. */
ravb_write(ndev, (ravb_read(ndev, CCC) & ~CCC_OPC) | CCC_OPC_OPERATION,
@@ -435,10 +467,11 @@
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;
@@ -446,12 +479,12 @@
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++;
@@ -550,8 +583,9 @@
skb = priv->rx_skb[q][entry];
priv->rx_skb[q][entry] = NULL;
- dma_unmap_single(ndev->dev.parent, le32_to_cpu(desc->dptr),
- ALIGN(PKT_BUF_SZ, 16),
+ dma_unmap_single(ndev->dev.parent,
+ le32_to_cpu(desc->dptr),
+ PKT_BUF_SZ,
DMA_FROM_DEVICE);
get_ts &= (q == RAVB_NC) ?
RAVB_RXTSTAMP_TYPE_V2_L2_EVENT :
@@ -581,8 +615,7 @@
for (; priv->cur_rx[q] - priv->dirty_rx[q] > 0; priv->dirty_rx[q]++) {
entry = priv->dirty_rx[q] % priv->num_rx_ring[q];
desc = &priv->rx_ring[q][entry];
- /* The size of the buffer should be on 16-byte boundary. */
- desc->ds_cc = cpu_to_le16(ALIGN(PKT_BUF_SZ, 16));
+ desc->ds_cc = cpu_to_le16(PKT_BUF_SZ);
if (!priv->rx_skb[q][entry]) {
skb = netdev_alloc_skb(ndev,
@@ -653,7 +686,7 @@
}
/* E-MAC interrupt handler */
-static void ravb_emac_interrupt(struct net_device *ndev)
+static void _ravb_emac_interrupt(struct net_device *ndev)
{
struct ravb_private *priv = netdev_priv(ndev);
u32 ecsr, psr;
@@ -679,6 +712,18 @@
}
}
+static irqreturn_t ravb_emac_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = dev_id;
+ struct ravb_private *priv = netdev_priv(ndev);
+
+ spin_lock(&priv->lock);
+ _ravb_emac_interrupt(ndev);
+ mmiowb();
+ spin_unlock(&priv->lock);
+ return IRQ_HANDLED;
+}
+
/* Error interrupt handler */
static void ravb_error_interrupt(struct net_device *ndev)
{
@@ -689,7 +734,10 @@
ravb_write(ndev, ~EIS_QFS, EIS);
if (eis & EIS_QFS) {
ris2 = ravb_read(ndev, RIS2);
- ravb_write(ndev, ~(RIS2_QFF0 | RIS2_RFFF), RIS2);
+ if (priv->chip_id == RCAR_GEN2)
+ ravb_write(ndev, ~(RIS2_QFF0 | RIS2_RFFF), RIS2);
+ else
+ ravb_write(ndev, RIS2_QFF0 | RIS2_RFFF, RID2);
/* Receive Descriptor Empty int */
if (ris2 & RIS2_QFF0)
@@ -757,7 +805,7 @@
/* E-MAC status summary */
if (iss & ISS_MS) {
- ravb_emac_interrupt(ndev);
+ _ravb_emac_interrupt(ndev);
result = IRQ_HANDLED;
}
@@ -775,6 +823,83 @@
return result;
}
+/* Descriptor IRQ/ Error/ Management interrupt handler */
+static irqreturn_t ravb_multi_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = dev_id;
+ struct ravb_private *priv = netdev_priv(ndev);
+ irqreturn_t result = IRQ_NONE;
+ u32 iss, tis;
+
+ spin_lock(&priv->lock);
+ /* Get interrupt status */
+ iss = ravb_read(ndev, ISS);
+
+ /* Timestamp updated */
+ tis = ravb_read(ndev, TIS);
+ if (tis & TIS_TFUF) {
+ ravb_write(ndev, ~TIS_TFUF, TIS);
+ ravb_get_tx_tstamp(ndev);
+ result = IRQ_HANDLED;
+ }
+
+ /* Error status summary */
+ if (iss & ISS_ES) {
+ ravb_error_interrupt(ndev);
+ result = IRQ_HANDLED;
+ }
+
+ /* Management */
+ if (iss & ISS_CGIS)
+ result = ravb_ptp_interrupt(ndev);
+
+ mmiowb();
+ spin_unlock(&priv->lock);
+ return result;
+}
+
+static irqreturn_t ravb_dmaq_interrupt(int irq, void *dev_id, int ravb_queue)
+{
+ struct net_device *ndev = dev_id;
+ struct ravb_private *priv = netdev_priv(ndev);
+ irqreturn_t result = IRQ_NONE;
+ u32 ris0, ric0, tis, tic;
+ int q = ravb_queue;
+
+ spin_lock(&priv->lock);
+
+ ris0 = ravb_read(ndev, RIS0);
+ ric0 = ravb_read(ndev, RIC0);
+ tis = ravb_read(ndev, TIS);
+ tic = ravb_read(ndev, TIC);
+
+ /* Best effort queue RX/TX */
+ if (((ris0 & ric0) & BIT(q)) ||
+ ((tis & tic) & BIT(q))) {
+ if (napi_schedule_prep(&priv->napi[q])) {
+ /* Mask RX and TX interrupts */
+ ravb_write(ndev, BIT(q), RID0);
+ ravb_write(ndev, BIT(q), TID);
+ __napi_schedule(&priv->napi[q]);
+ }
+ result = IRQ_HANDLED;
+ }
+
+ mmiowb();
+ spin_unlock(&priv->lock);
+ return result;
+}
+
+static irqreturn_t ravb_be_interrupt(int irq, void *dev_id)
+{
+ return ravb_dmaq_interrupt(irq, dev_id, RAVB_BE);
+}
+
+static irqreturn_t ravb_nc_interrupt(int irq, void *dev_id)
+{
+ return ravb_dmaq_interrupt(irq, dev_id, RAVB_NC);
+}
+
static int ravb_poll(struct napi_struct *napi, int budget)
{
struct net_device *ndev = napi->dev;
@@ -814,8 +939,13 @@
/* Re-enable RX/TX interrupts */
spin_lock_irqsave(&priv->lock, flags);
- ravb_write(ndev, ravb_read(ndev, RIC0) | mask, RIC0);
- ravb_write(ndev, ravb_read(ndev, TIC) | mask, TIC);
+ if (priv->chip_id == RCAR_GEN2) {
+ ravb_write(ndev, ravb_read(ndev, RIC0) | mask, RIC0);
+ ravb_write(ndev, ravb_read(ndev, TIC) | mask, TIC);
+ } else {
+ ravb_write(ndev, mask, RIE0);
+ ravb_write(ndev, mask, TIE);
+ }
mmiowb();
spin_unlock_irqrestore(&priv->lock, flags);
@@ -916,11 +1046,19 @@
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");
}
gpio = of_get_named_gpio_flags(np, "phy-int-gpio", 0, &flags);
@@ -937,7 +1075,6 @@
}
}
- netdev_info(ndev, "limited PHY to 100Mbit/s\n");
}
/* 10BASE is not supported */
@@ -1239,30 +1376,79 @@
{
struct ravb_private *priv = netdev_priv(ndev);
int error;
+ struct platform_device *pdev = priv->pdev;
+ struct device *dev = &pdev->dev;
+ char *name;
napi_enable(&priv->napi[RAVB_BE]);
napi_enable(&priv->napi[RAVB_NC]);
- error = request_irq(ndev->irq, ravb_interrupt, IRQF_SHARED, ndev->name,
- ndev);
- if (error) {
- netdev_err(ndev, "cannot request IRQ\n");
- goto out_napi_off;
- }
-
- if (priv->chip_id == RCAR_GEN3) {
- error = request_irq(priv->emac_irq, ravb_interrupt,
- IRQF_SHARED, ndev->name, ndev);
+ if (priv->chip_id == RCAR_GEN2) {
+ error = request_irq(ndev->irq, ravb_interrupt, IRQF_SHARED,
+ ndev->name, ndev);
if (error) {
netdev_err(ndev, "cannot request IRQ\n");
+ goto out_napi_off;
+ }
+ } else {
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s:ch22:multi",
+ ndev->name);
+ error = request_irq(ndev->irq, ravb_multi_interrupt,
+ IRQF_SHARED, name, ndev);
+ if (error) {
+ netdev_err(ndev, "cannot request IRQ %s\n", name);
+ goto out_napi_off;
+ }
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s:ch24:emac",
+ ndev->name);
+ error = request_irq(priv->emac_irq, ravb_emac_interrupt,
+ IRQF_SHARED, name, ndev);
+ if (error) {
+ netdev_err(ndev, "cannot request IRQ %s\n", name);
goto out_free_irq;
}
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s:ch0:rx_be",
+ ndev->name);
+ error = request_irq(priv->rx_irqs[RAVB_BE], ravb_be_interrupt,
+ IRQF_SHARED, name, ndev);
+ if (error) {
+ netdev_err(ndev, "cannot request IRQ %s\n", name);
+ goto out_free_irq2;
+ }
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s:ch18:tx_be",
+ ndev->name);
+ error = request_irq(priv->tx_irqs[RAVB_BE], ravb_be_interrupt,
+ IRQF_SHARED, name, ndev);
+ if (error) {
+ netdev_err(ndev, "cannot request IRQ %s\n", name);
+ goto out_free_irq3;
+ }
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s:ch1:rx_nc",
+ ndev->name);
+ error = request_irq(priv->rx_irqs[RAVB_NC], ravb_nc_interrupt,
+ IRQF_SHARED, name, ndev);
+ if (error) {
+ netdev_err(ndev, "cannot request IRQ %s\n", name);
+ goto out_free_irq4;
+ }
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s:ch19:tx_nc",
+ ndev->name);
+ error = request_irq(priv->tx_irqs[RAVB_NC], ravb_nc_interrupt,
+ IRQF_SHARED, name, ndev);
+ if (error) {
+ netdev_err(ndev, "cannot request IRQ %s\n", name);
+ goto out_free_irq5;
+ }
}
/* Device init */
error = ravb_dmac_init(ndev);
- if (error)
- goto out_free_irq2;
+ if (error) {
+ if (priv->chip_id == RCAR_GEN2)
+ goto out_free_irq;
+ else
+ goto out_free_irq6;
+ }
ravb_emac_init(ndev);
/* Initialise PTP Clock driver */
@@ -1280,11 +1466,20 @@
out_ptp_stop:
/* Stop PTP Clock driver */
- if (priv->chip_id == RCAR_GEN2)
+ if (priv->chip_id == RCAR_GEN2) {
ravb_ptp_stop(ndev);
+ goto out_free_irq;
+ }
+out_free_irq6:
+ free_irq(priv->tx_irqs[RAVB_NC], ndev);
+out_free_irq5:
+ free_irq(priv->rx_irqs[RAVB_NC], ndev);
+out_free_irq4:
+ free_irq(priv->tx_irqs[RAVB_BE], ndev);
+out_free_irq3:
+ free_irq(priv->rx_irqs[RAVB_BE], ndev);
out_free_irq2:
- if (priv->chip_id == RCAR_GEN3)
- free_irq(priv->emac_irq, ndev);
+ free_irq(priv->emac_irq, ndev);
out_free_irq:
free_irq(ndev->irq, ndev);
out_napi_off:
@@ -1347,41 +1542,56 @@
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);
@@ -1389,9 +1599,11 @@
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;
@@ -1408,15 +1620,19 @@
/* 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_write(ndev, ravb_read(ndev, TCCR) | (TCCR_TSRQ0 << q), TCCR);
- 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:
@@ -1429,7 +1645,7 @@
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;
}
@@ -1509,9 +1725,15 @@
netif_tx_stop_all_queues(ndev);
/* Disable interrupts by clearing the interrupt masks. */
- ravb_write(ndev, 0, RIC0);
- ravb_write(ndev, 0, RIC2);
- ravb_write(ndev, 0, TIC);
+ if (priv->chip_id == RCAR_GEN2) {
+ ravb_write(ndev, 0, RIC0);
+ ravb_write(ndev, 0, RIC2);
+ ravb_write(ndev, 0, TIC);
+ } else {
+ ravb_write(ndev, RAVB_RIx0_ALL, RID0);
+ ravb_write(ndev, RAVB_RIx2_ALL, RID2);
+ ravb_write(ndev, RAVB_TIx_ALL, TID);
+ }
/* Stop PTP Clock driver */
if (priv->chip_id == RCAR_GEN2)
@@ -1536,6 +1758,13 @@
}
free_irq(ndev->irq, ndev);
+ if (priv->chip_id != RCAR_GEN2) {
+ free_irq(priv->tx_irqs[RAVB_NC], ndev);
+ free_irq(priv->rx_irqs[RAVB_NC], ndev);
+ free_irq(priv->tx_irqs[RAVB_BE], ndev);
+ free_irq(priv->rx_irqs[RAVB_BE], ndev);
+ free_irq(priv->emac_irq, ndev);
+ }
napi_disable(&priv->napi[RAVB_NC]);
napi_disable(&priv->napi[RAVB_BE]);
@@ -1743,6 +1972,7 @@
struct net_device *ndev;
int error, irq, q;
struct resource *res;
+ int i;
if (!np) {
dev_err(&pdev->dev,
@@ -1813,19 +2043,39 @@
goto out_release;
}
priv->emac_irq = irq;
+ for (i = 0; i < NUM_RX_QUEUE; i++) {
+ irq = platform_get_irq_byname(pdev, ravb_rx_irqs[i]);
+ if (irq < 0) {
+ error = irq;
+ goto out_release;
+ }
+ priv->rx_irqs[i] = irq;
+ }
+ for (i = 0; i < NUM_TX_QUEUE; i++) {
+ irq = platform_get_irq_byname(pdev, ravb_tx_irqs[i]);
+ if (irq < 0) {
+ error = irq;
+ goto out_release;
+ }
+ priv->tx_irqs[i] = irq;
+ }
}
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;
- /* Set AVB config mode */
+ /* Set AVB config mode and CSEL value */
if (chip_id == RCAR_GEN2) {
ravb_write(ndev, (ravb_read(ndev, CCC) & ~CCC_OPC) |
CCC_OPC_CONFIG, CCC);
- /* Set CSEL value */
ravb_write(ndev, (ravb_read(ndev, CCC) & ~CCC_CSEL) |
CCC_CSEL_HPB, CCC);
} else {
@@ -1833,10 +2083,6 @@
CCC_OPC_CONFIG | CCC_GAC | CCC_CSEL_HPB, CCC);
}
- /* Set CSEL value */
- ravb_write(ndev, (ravb_read(ndev, CCC) & ~CCC_CSEL) | CCC_CSEL_HPB,
- CCC);
-
/* Set GTI value */
error = ravb_set_gti(ndev);
if (error)
@@ -1947,6 +2193,66 @@
}
#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
+static int 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)) {
+ netif_device_detach(ndev);
+ ret = ravb_close(ndev);
+ if (priv->chip_id != RCAR_GEN2)
+ ravb_ptp_stop(ndev);
+ }
+
+ return ret;
+}
+
+static int 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;
+
+ if (netif_running(ndev)) {
+ /* Set AVB config mode */
+ if (priv->chip_id == RCAR_GEN2) {
+ ravb_write(ndev, (ravb_read(ndev, CCC) & ~CCC_OPC) |
+ CCC_OPC_CONFIG, CCC);
+ } else {
+ ravb_write(ndev, (ravb_read(ndev, CCC) & ~CCC_OPC) |
+ CCC_OPC_CONFIG | CCC_GAC, CCC);
+ }
+
+ /* Set CSEL value */
+ ravb_write(ndev, (ravb_read(ndev, CCC) & ~CCC_CSEL) |
+ CCC_CSEL_HPB, CCC);
+
+ /* Set GTI value */
+ ravb_set_gti(ndev);
+
+ /* Request GTI loading */
+ ravb_write(ndev, ravb_read(ndev, GCCR) | GCCR_LTI, GCCR);
+
+ /* Set DBAT value */
+ ravb_write(ndev, priv->desc_bat_dma, DBAT);
+
+ if (priv->chip_id != RCAR_GEN2)
+ ravb_ptp_init(ndev, pdev);
+
+ ret = ravb_open(ndev);
+ if (ret < 0)
+ return ret;
+ netif_device_attach(ndev);
+ }
+
+ return ret;
+}
+#endif
+
static int ravb_runtime_nop(struct device *dev)
{
/* Runtime PM callback shared between ->runtime_suspend()
@@ -1960,8 +2266,8 @@
}
static const struct dev_pm_ops ravb_dev_pm_ops = {
- .runtime_suspend = ravb_runtime_nop,
- .runtime_resume = ravb_runtime_nop,
+ SET_SYSTEM_SLEEP_PM_OPS(ravb_suspend, ravb_resume)
+ SET_RUNTIME_PM_OPS(ravb_runtime_nop, ravb_runtime_nop, NULL)
};
#define RAVB_PM_OPS (&ravb_dev_pm_ops)
diff --git a/drivers/net/ethernet/renesas/ravb_ptp.c b/drivers/net/ethernet/renesas/ravb_ptp.c
index 7a8ce92..a789c7c 100644
--- a/drivers/net/ethernet/renesas/ravb_ptp.c
+++ b/drivers/net/ethernet/renesas/ravb_ptp.c
@@ -196,11 +196,15 @@
spin_lock_irqsave(&priv->lock, flags);
gic = ravb_read(ndev, GIC);
- if (on)
- gic |= GIC_PTCE;
- else
- gic &= ~GIC_PTCE;
- ravb_write(ndev, gic, GIC);
+ if (priv->chip_id == RCAR_GEN2) {
+ if (on)
+ gic |= GIC_PTCE;
+ else
+ gic &= ~GIC_PTCE;
+ ravb_write(ndev, gic, GIC);
+ } else {
+ ravb_write(ndev, GIC_PTCE, (on) ? GIE : GID);
+ }
mmiowb();
spin_unlock_irqrestore(&priv->lock, flags);
@@ -216,7 +220,7 @@
struct ravb_ptp_perout *perout;
unsigned long flags;
int error = 0;
- u32 gic;
+ u32 gic = 0;
if (req->index)
return -EINVAL;
@@ -248,9 +252,13 @@
error = ravb_ptp_update_compare(priv, (u32)start_ns);
if (!error) {
/* Unmask interrupt */
- gic = ravb_read(ndev, GIC);
- gic |= GIC_PTME;
- ravb_write(ndev, gic, GIC);
+ if (priv->chip_id == RCAR_GEN2) {
+ gic = ravb_read(ndev, GIC);
+ gic |= GIC_PTME;
+ ravb_write(ndev, gic, GIC);
+ } else {
+ ravb_write(ndev, GIC_PTME, GIE);
+ }
}
} else {
spin_lock_irqsave(&priv->lock, flags);
@@ -259,9 +267,13 @@
perout->period = 0;
/* Mask interrupt */
- gic = ravb_read(ndev, GIC);
- gic &= ~GIC_PTME;
- ravb_write(ndev, gic, GIC);
+ if (priv->chip_id == RCAR_GEN2) {
+ gic = ravb_read(ndev, GIC);
+ gic &= ~GIC_PTME;
+ ravb_write(ndev, gic, GIC);
+ } else {
+ ravb_write(ndev, GIC_PTME, GID);
+ }
}
mmiowb();
spin_unlock_irqrestore(&priv->lock, flags);
@@ -352,7 +364,11 @@
{
struct ravb_private *priv = netdev_priv(ndev);
- ravb_write(ndev, 0, GIC);
+ if (priv->chip_id == RCAR_GEN2)
+ ravb_write(ndev, 0, GIC);
+ else
+ ravb_write(ndev, RAVB_GIx_ALL, GID);
+
ravb_write(ndev, 0, GIS);
ptp_clock_unregister(priv->ptp.clock);
diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c
index 4edb518..de147fe 100644
--- a/drivers/pci/host/pcie-rcar.c
+++ b/drivers/pci/host/pcie-rcar.c
@@ -84,6 +84,14 @@
#define MACSR 0x011054
#define MACCTLR 0x011058
#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)
+
/* R-Car H1 PHY */
#define H1_PCIEPHYADRR 0x04000c
@@ -181,6 +189,7 @@
unsigned int devfn, int where, u32 *data)
{
int dev, func, reg, index;
+ u32 val;
dev = PCI_SLOT(devfn);
func = PCI_FUNC(devfn);
@@ -222,6 +231,22 @@
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 & 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);
@@ -1069,9 +1094,32 @@
return err;
}
+#ifdef CONFIG_PM_SLEEP
+static int rcar_pcie_suspend(struct device *dev)
+{
+ /* Empty functino for now */
+ return 0;
+}
+
+static int rcar_pcie_resume(struct device *dev)
+{
+ /* Empty function for now */
+ 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
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver rcar_pcie_driver = {
.driver = {
.name = DRV_NAME,
+ .pm = DEV_PM_OPS,
.of_match_table = rcar_pcie_of_match,
.suppress_bind_attrs = true,
},
@@ -1079,6 +1127,34 @@
};
module_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);
+
MODULE_AUTHOR("Phil Edworthy <phil.edworthy@renesas.com>");
MODULE_DESCRIPTION("Renesas R-Car PCIe driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c
index ef332ef..70a9297 100644
--- a/drivers/phy/phy-rcar-gen3-usb2.c
+++ b/drivers/phy/phy-rcar-gen3-usb2.c
@@ -74,20 +74,6 @@
#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */
#define USB2_ADPCTRL_DRVVBUS BIT(4)
-/******* HSUSB registers (original offset is +0x100) *******/
-#define HSUSB_LPSTS 0x02
-#define HSUSB_UGCTRL2 0x84
-
-/* Low Power Status register (LPSTS) */
-#define HSUSB_LPSTS_SUSPM 0x4000
-
-/* USB General control register 2 (UGCTRL2) */
-#define HSUSB_UGCTRL2_MASK 0x00000031 /* bit[31:6] should be 0 */
-#define HSUSB_UGCTRL2_USB0SEL 0x00000030
-#define HSUSB_UGCTRL2_USB0SEL_HOST 0x00000010
-#define HSUSB_UGCTRL2_USB0SEL_HS_USB 0x00000020
-#define HSUSB_UGCTRL2_USB0SEL_OTG 0x00000030
-
struct rcar_gen3_data {
void __iomem *base;
struct clk *clk;
@@ -95,8 +81,8 @@
struct rcar_gen3_chan {
struct rcar_gen3_data usb2;
- struct rcar_gen3_data hsusb;
struct phy *phy;
+ bool has_otg;
};
static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host)
@@ -202,24 +188,15 @@
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
void __iomem *usb2_base = channel->usb2.base;
- void __iomem *hsusb_base = channel->hsusb.base;
- u32 val;
/* Initialize USB2 part */
writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE);
writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET);
writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET);
- /* Initialize HSUSB part */
- if (hsusb_base) {
- val = readl(hsusb_base + HSUSB_UGCTRL2);
- val = (val & ~HSUSB_UGCTRL2_USB0SEL) |
- HSUSB_UGCTRL2_USB0SEL_OTG;
- writel(val & HSUSB_UGCTRL2_MASK, hsusb_base + HSUSB_UGCTRL2);
-
- /* Initialize otg part */
+ /* Initialize otg part */
+ if (channel->has_otg)
rcar_gen3_init_otg(channel);
- }
return 0;
}
@@ -237,7 +214,6 @@
{
struct rcar_gen3_chan *channel = phy_get_drvdata(p);
void __iomem *usb2_base = channel->usb2.base;
- void __iomem *hsusb_base = channel->hsusb.base;
u32 val;
val = readl(usb2_base + USB2_USBCTR);
@@ -246,33 +222,6 @@
val &= ~USB2_USBCTR_PLL_RST;
writel(val, usb2_base + USB2_USBCTR);
- /*
- * TODO: To reduce power consuming, this driver should set the SUSPM
- * after the PHY detects ID pin as peripheral.
- */
- if (hsusb_base) {
- /* Power on HSUSB PHY */
- val = readw(hsusb_base + HSUSB_LPSTS);
- val |= HSUSB_LPSTS_SUSPM;
- writew(val, hsusb_base + HSUSB_LPSTS);
- }
-
- return 0;
-}
-
-static int rcar_gen3_phy_usb2_power_off(struct phy *p)
-{
- struct rcar_gen3_chan *channel = phy_get_drvdata(p);
- void __iomem *hsusb_base = channel->hsusb.base;
- u32 val;
-
- if (hsusb_base) {
- /* Power off HSUSB PHY */
- val = readw(hsusb_base + HSUSB_LPSTS);
- val &= ~HSUSB_LPSTS_SUSPM;
- writew(val, hsusb_base + HSUSB_LPSTS);
- }
-
return 0;
}
@@ -280,7 +229,6 @@
.init = rcar_gen3_phy_usb2_init,
.exit = rcar_gen3_phy_usb2_exit,
.power_on = rcar_gen3_phy_usb2_power_on,
- .power_off = rcar_gen3_phy_usb2_power_off,
.owner = THIS_MODULE,
};
@@ -313,6 +261,7 @@
struct rcar_gen3_chan *channel;
struct phy_provider *provider;
struct resource *res;
+ int irq;
if (!dev->of_node) {
dev_err(dev, "This driver needs device tree\n");
@@ -323,29 +272,19 @@
if (!channel)
return -ENOMEM;
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2_host");
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
channel->usb2.base = devm_ioremap_resource(dev, res);
if (IS_ERR(channel->usb2.base))
return PTR_ERR(channel->usb2.base);
- /* "hsusb" memory resource is optional */
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hsusb");
-
- /* To avoid error message by devm_ioremap_resource() */
- if (res) {
- int irq;
-
- channel->hsusb.base = devm_ioremap_resource(dev, res);
- if (IS_ERR(channel->hsusb.base))
- channel->hsusb.base = NULL;
- /* call request_irq for OTG */
- irq = platform_get_irq(pdev, 0);
- if (irq >= 0)
- irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq,
- IRQF_SHARED, dev_name(dev),
- channel);
+ /* call request_irq for OTG */
+ irq = platform_get_irq(pdev, 0);
+ if (irq >= 0) {
+ irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq,
+ IRQF_SHARED, dev_name(dev), channel);
if (irq < 0)
dev_err(dev, "No irq handler (%d)\n", irq);
+ channel->has_otg = true;
}
/* devm_phy_create() will call pm_runtime_enable(dev); */
@@ -364,9 +303,30 @@
return PTR_ERR_OR_ZERO(provider);
}
+#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)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+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,
diff --git a/drivers/pinctrl/sh-pfc/core.c b/drivers/pinctrl/sh-pfc/core.c
index 181ea98..3942773 100644
--- a/drivers/pinctrl/sh-pfc/core.c
+++ b/drivers/pinctrl/sh-pfc/core.c
@@ -634,12 +634,37 @@
};
MODULE_DEVICE_TABLE(platform, sh_pfc_id_table);
+#ifdef CONFIG_PM_SLEEP
+static int sh_pfc_suspend(struct device *dev)
+{
+#ifdef CONFIG_PINCTRL_PFC_R8A7795
+ /* Empty function for now */
+#endif
+ return 0;
+}
+
+static int sh_pfc_resume(struct device *dev)
+{
+#ifdef CONFIG_PINCTRL_PFC_R8A7795
+ /* Empty function for now */
+#endif
+ return 0;
+}
+
+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,
.remove = sh_pfc_remove,
.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/pinctrl/sh-pfc/pfc-r8a7795.c b/drivers/pinctrl/sh-pfc/pfc-r8a7795.c
index d2250d2..1cc2b78 100644
--- a/drivers/pinctrl/sh-pfc/pfc-r8a7795.c
+++ b/drivers/pinctrl/sh-pfc/pfc-r8a7795.c
@@ -8,17 +8,22 @@
* the Free Software Foundation; version 2 of the License.
*/
+#include <linux/io.h>
#include <linux/kernel.h>
#include "core.h"
#include "sh_pfc.h"
+/*
+* All pins assigned to GPIO bank 3 and 4 can be used for SD interfaces in
+* which case they support both 3.3V and 1.8V signalling.
+*/
#define CPU_ALL_PORT(fn, sfx) \
PORT_GP_16(0, fn, sfx), \
PORT_GP_28(1, fn, sfx), \
PORT_GP_15(2, fn, sfx), \
- PORT_GP_16(3, fn, sfx), \
- PORT_GP_18(4, fn, sfx), \
+ PORT_GP_CFG_16(3, fn, sfx, SH_PFC_PIN_CFG_IO_VOLTAGE), \
+ PORT_GP_CFG_18(4, fn, sfx, SH_PFC_PIN_CFG_IO_VOLTAGE), \
PORT_GP_26(5, fn, sfx), \
PORT_GP_32(6, fn, sfx), \
PORT_GP_4(7, fn, sfx)
@@ -4379,6 +4384,67 @@
"vin5_clk",
};
+#define POCCTRL0 0x380
+#define PIN2POCCTRL0_SHIFT(a) ({ \
+ int _gp = (a) >> 5; \
+ int _bit = (a) & 0x1f; \
+ ((_gp == 3) && (_bit < 12)) ? _bit : \
+ ((_gp == 4) && (_bit < 18)) ? _bit + 12 : -1; \
+})
+
+static int r8a7795_get_io_voltage(struct sh_pfc *pfc, unsigned int pin)
+{
+ void __iomem *reg;
+ u32 data, mask;
+ int shift;
+
+ /* Bits in POCCTRL0 are numbered in opposite order to pins */
+ shift = PIN2POCCTRL0_SHIFT(pin);
+
+ if (WARN(shift < 0, "invalid pin %#x", pin))
+ return -EINVAL;
+
+ reg = pfc->windows->virt + POCCTRL0;
+ data = ioread32(reg);
+
+ mask = 0x1 << shift;
+
+ return (data & mask) ? 3300 : 1800;
+}
+
+static int r8a7795_set_io_voltage(struct sh_pfc *pfc, unsigned int pin, u16 mV)
+{
+ void __iomem *reg;
+ u32 data, mask;
+ int shift;
+
+ /* Bits in POCCTRL0 are numbered in opposite order to pins */
+ shift = PIN2POCCTRL0_SHIFT(pin);
+
+ if (WARN(shift < 0, "invalid pin %#x", pin))
+ return -EINVAL;
+
+ if (mV != 1800 && mV != 3300)
+ return -EINVAL;
+
+ reg = pfc->windows->virt + POCCTRL0;
+ data = ioread32(reg);
+
+ mask = 0x1 << shift;
+
+ if (mV == 3300)
+ data |= mask;
+ else
+ data &= ~mask;
+
+
+ iowrite32(~data, pfc->windows->virt +
+ (pfc->info->unlock_reg - pfc->windows->phys));
+ iowrite32(data, reg);
+
+ return 0;
+}
+
static const struct sh_pfc_function pinmux_functions[] = {
SH_PFC_FUNCTION(audio_clk),
SH_PFC_FUNCTION(avb),
@@ -4981,8 +5047,14 @@
{ },
};
+static const struct sh_pfc_soc_operations pinmux_ops = {
+ .get_io_voltage = r8a7795_get_io_voltage,
+ .set_io_voltage = r8a7795_set_io_voltage,
+};
+
const struct sh_pfc_soc_info r8a7795_pinmux_info = {
.name = "r8a77950_pfc",
+ .ops = &pinmux_ops,
.unlock_reg = 0xe6060000, /* PMMR */
.function = { PINMUX_FUNCTION_BEGIN, PINMUX_FUNCTION_END },
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 8cf0dae..9a93740 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -305,7 +305,7 @@
config PWM_RCAR
tristate "Renesas R-Car PWM support"
- depends on ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || COMPILE_TEST
+ depends on ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || ARCH_RENESAS || COMPILE_TEST
depends on HAS_IOMEM
help
This driver exposes the PWM Timer controller found in Renesas
diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c
index 7b8ac06..00c8fd1 100644
--- a/drivers/pwm/pwm-rcar.c
+++ b/drivers/pwm/pwm-rcar.c
@@ -19,6 +19,7 @@
#include <linux/slab.h>
#define RCAR_PWM_MAX_DIVISION 24
+#define RCAR_PWM_MIN_CYCLE 2
#define RCAR_PWM_MAX_CYCLE 1023
#define RCAR_PWMCR 0x00
@@ -71,12 +72,17 @@
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);
@@ -157,7 +163,7 @@
return div;
/* Let the core driver set pwm->period if disabled and duty_ns == 0 */
- if (!test_bit(PWMF_ENABLED, &pwm->flags) && !duty_ns)
+ if (!test_bit(PWMF_ENABLED, &pwm->flags) && !duty_ns && !pwm->duty_cycle)
return 0;
rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR);
@@ -258,11 +264,32 @@
};
MODULE_DEVICE_TABLE(of, rcar_pwm_of_table);
+#ifdef CONFIG_PM_SLEEP
+static int rcar_pwm_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int rcar_pwm_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+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 8155e80..158c391 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -154,6 +154,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 980b194..a31804a 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -22,6 +22,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/soc/renesas/rcar_pm_sysc.c b/drivers/soc/renesas/rcar_pm_sysc.c
index 656dee46..f85104e 100644
--- a/drivers/soc/renesas/rcar_pm_sysc.c
+++ b/drivers/soc/renesas/rcar_pm_sysc.c
@@ -192,7 +192,7 @@
if (ret != -EAGAIN)
break;
}
- udelay(SYSCISR_DELAY_US);
+ udelay(SYSCSR_DELAY_US);
}
spin_unlock_irqrestore(&sysc_lock, sysc_lock_flags);
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 7706416..d73ce13 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -505,6 +505,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 a7934ab..dd4d071 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) 2014-2016 Renesas Electronics Corporation
* Copyright (c) 2009 Magnus Damm
* Copyright (C) 2014 Glider bvba
*
@@ -28,11 +29,14 @@
#include <linux/pm_runtime.h>
#include <linux/sh_dma.h>
+#include <linux/soc/renesas/rcar_prr.h>
#include <linux/spi/sh_msiof.h>
#include <linux/spi/spi.h>
#include <asm/unaligned.h>
+#undef HZ
+#define HZ MAX_SCHEDULE_TIMEOUT
struct sh_msiof_chipdata {
u16 tx_fifo_size;
@@ -40,6 +44,9 @@
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;
@@ -48,8 +55,10 @@
const struct sh_msiof_chipdata *chipdata;
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;
@@ -82,6 +91,7 @@
#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) */
@@ -335,7 +345,34 @@
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 (RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS10) == 0)) {
+ if (p->mode == SPI_MSIOF_MASTER) {
+ tmp &= ~MDR1_DTDL_MASK;
+ tmp |= 0 << MDR1_DTDL_SHIFT;
+ }
+ }
+ if (RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS11) == 0)) {
+ 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 (RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS10) == 0)) {
+ if (p->mode == SPI_MSIOF_MASTER) {
+ tmp &= ~MDR1_DTDL_MASK;
+ tmp |= 2 << MDR1_DTDL_SHIFT;
+ }
+ }
+ if (RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS11) == 0)) {
+ if (p->mode == SPI_MSIOF_MASTER) {
+ tmp &= ~MDR1_DTDL_MASK;
+ tmp |= 1 << MDR1_DTDL_SHIFT;
+ }
+ }
if (p->chipdata->master_flags & SPI_MASTER_MUST_TX) {
/* These bits are reserved if RX needs TX */
tmp &= ~0x0000ffff;
@@ -343,8 +380,18 @@
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 (RCAR_PRR_IS_PRODUCT(H3) && (RCAR_PRR_CHK_CUT(H3, WS11) <= 0)) {
+ 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;
@@ -562,17 +609,18 @@
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;
@@ -580,15 +628,16 @@
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;
@@ -663,12 +712,18 @@
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,
@@ -688,7 +743,7 @@
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))
@@ -707,13 +762,8 @@
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;
@@ -730,6 +780,8 @@
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)
@@ -743,12 +795,37 @@
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, HZ);
+ 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, HZ)) {
+ 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, HZ);
+ 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);
@@ -841,7 +918,8 @@
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) {
/*
@@ -860,7 +938,7 @@
break;
copy32 = copy_bswap32;
} else if (bits <= 16) {
- if (l & 1)
+ if (l & 3)
break;
copy32 = copy_wswap32;
} else {
@@ -870,6 +948,11 @@
if (tx_buf)
copy32(p->tx_dma_page, tx_buf, l / 4);
+#ifdef CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG
+ if (p->mode == 0)
+ 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",
@@ -943,6 +1026,12 @@
words = len / bytes_per_word;
while (words > 0) {
+
+#ifdef CONFIG_SPI_SH_MSIOF_TRANSFER_SYNC_DEBUG
+ if (p->mode == 0)
+ 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)
@@ -978,6 +1067,7 @@
{ .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 },
{},
};
MODULE_DEVICE_TABLE(of, sh_msiof_match);
@@ -1002,6 +1092,11 @@
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;
@@ -1074,10 +1169,7 @@
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,
@@ -1184,6 +1276,8 @@
}
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)) {
@@ -1223,6 +1317,7 @@
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;
@@ -1247,6 +1342,12 @@
goto err2;
}
+ ret = RCAR_PRR_INIT();
+ if (ret) {
+ dev_err(&pdev->dev, "rcar workaround init error.\n");
+ goto err2;
+ }
+
return 0;
err2:
@@ -1272,12 +1373,33 @@
};
MODULE_DEVICE_TABLE(platform, spi_driver_ids);
+#ifdef CONFIG_PM_SLEEP
+static int sh_msiof_spi_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int sh_msiof_spi_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+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 e3c19f3..b016a5a 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
@@ -695,6 +696,7 @@
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/staging/board/Makefile b/drivers/staging/board/Makefile
index 6842745..7a6fa98 100644
--- a/drivers/staging/board/Makefile
+++ b/drivers/staging/board/Makefile
@@ -1,3 +1,4 @@
obj-y := board.o
obj-$(CONFIG_ARCH_EMEV2) += kzm9d.o
obj-$(CONFIG_ARCH_R8A7740) += armadillo800eva.o
+obj-$(CONFIG_ARCH_R8A7795) += salvator-x.o
diff --git a/drivers/staging/board/salvator-x.c b/drivers/staging/board/salvator-x.c
new file mode 100644
index 0000000..313cc94
--- /dev/null
+++ b/drivers/staging/board/salvator-x.c
@@ -0,0 +1,67 @@
+/*
+ * Staging board support for Salvator-X.
+ *
+ * Copyright (C) 2015 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; 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/cma.h>
+#include <linux/dma-contiguous.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include "board.h"
+#include "../../../mm/cma.h"
+
+struct cma *find_largest_nondefault_cma(void)
+{
+ unsigned long largest_size;
+ int k, largest_idx;
+
+ largest_size = 0;
+ largest_idx = -ENOENT;
+
+ for (k = 0; k < cma_area_count; k++) {
+ if (&cma_areas[k] == dma_contiguous_default_area)
+ continue;
+
+ if (cma_get_size(&cma_areas[k]) > largest_size) {
+ largest_size = cma_get_size(&cma_areas[k]);
+ largest_idx = k;
+ }
+ }
+
+ if (largest_idx != -ENOENT)
+ return &cma_areas[largest_idx];
+
+ return NULL;
+}
+
+struct cma *rcar_gen3_dma_contiguous;
+EXPORT_SYMBOL(rcar_gen3_dma_contiguous);
+
+static void __init salvator_x_board_staging_init(void)
+{
+ phys_addr_t cma_base;
+ unsigned long cma_size;
+
+ rcar_gen3_dma_contiguous = find_largest_nondefault_cma();
+
+ if (rcar_gen3_dma_contiguous) {
+ cma_base = cma_get_base(rcar_gen3_dma_contiguous);
+ cma_size = cma_get_size(rcar_gen3_dma_contiguous) / SZ_1M;
+
+ pr_info("%s: Located CMA at %pa, size %ld MiB\n",
+ __func__, &cma_base, cma_size);
+ }
+}
+
+board_staging("renesas,salvator-x", salvator_x_board_staging_init);
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 68487c2..4f5a22e 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -232,6 +232,16 @@
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
+ depends on HAS_IOMEM
+ select CPU_THERMAL
+ select THERMAL_GOV_POWER_ALLOCATOR
+ 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 44736f6..2af1557 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -31,6 +31,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..82abe2d
--- /dev/null
+++ b/drivers/thermal/rcar_gen3_thermal.c
@@ -0,0 +1,638 @@
+/*
+ * R-Car Gen3 THS/CIVM thermal sensor driver
+ * Based on drivers/thermal/rcar_thermal.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 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/thermal.h>
+
+/* Register offset */
+#define REG_GEN3_CTSR 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_FTHCODEH 0x48
+#define REG_GEN3_FTHCODET 0x4C
+#define REG_GEN3_FTHCODEL 0x50
+#define REG_GEN3_FPTATH 0x54
+#define REG_GEN3_FPTATT 0x58
+#define REG_GEN3_FPTATL 0x5C
+
+/* CTSR bit */
+#define PONSEQSTOP (0x1 << 27)
+#define PONM (0x1 << 8)
+#define AOUT (0x1 << 7)
+#define THBGR (0x1 << 5)
+#define VMEN (0x1 << 4)
+#define VMST (0x1 << 1)
+#define THSST (0x1 << 0)
+
+#define CTEMP_MASK 0xFFF
+
+#define POWERON 0
+
+#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
+
+#define RCAR_H3_WS10 0x4F00
+#define RCAR_H3_WS11 0x4F01
+
+/* Product register */
+#define GEN3_PRR 0xFFF00044
+#define GEN3_PRR_MASK 0x4FFF
+
+/* Quadratic and linear equation config
+ * Default is using quadratic equation.
+ * To switch to linear formula calculation,
+ * please comment out APPLY_QUADRATIC_EQUATION macro.
+*/
+/* #define APPLY_QUADRATIC_EQUATION */
+
+#ifdef APPLY_QUADRATIC_EQUATION
+/* This struct is for quadratic equation.
+ * y = ax^2 + bx + c
+*/
+struct equation_coefs {
+ long a;
+ long b;
+ long c;
+};
+#else
+/* This struct is for linear equation.
+ * y = a1*x + b1
+ * y = a2*x + b2
+*/
+struct equation_coefs {
+ long a1;
+ long b1;
+ long a2;
+ long b2;
+};
+#endif /* APPLY_QUADRATIC_EQUATION */
+
+struct fuse_factors {
+ int fthcode_h;
+ int fthcode_t;
+ int fthcode_l;
+ int fptat_h;
+ int fptat_t;
+ int fptat_l;
+};
+
+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;
+ u32 ctemp;
+};
+
+#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_H 96000L
+#define TJ_L (-41000L)
+#define PW2(x) ((x)*(x))
+
+#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 void thermal_read_fuse_factor(struct rcar_thermal_priv *priv)
+{
+ u32 lsi_id;
+ void __iomem *product_register = ioremap_nocache(GEN3_PRR, 4);
+
+ /* For H3 WS1.0 and H3 WS1.1,
+ * these registers have not been programmed yet.
+ * We will use fixed value as temporary solution.
+ */
+ lsi_id = ioread32(product_register) & GEN3_PRR_MASK;
+ if ((lsi_id != RCAR_H3_WS10) && (lsi_id != RCAR_H3_WS11)) {
+ priv->factor.fthcode_h = rcar_thermal_read(priv,
+ REG_GEN3_FTHCODEH)
+ & GEN3_FUSE_MASK;
+ priv->factor.fthcode_t = rcar_thermal_read(priv,
+ REG_GEN3_FTHCODET)
+ & GEN3_FUSE_MASK;
+ priv->factor.fthcode_l = rcar_thermal_read(priv,
+ REG_GEN3_FTHCODEL)
+ & GEN3_FUSE_MASK;
+ priv->factor.fptat_h = rcar_thermal_read(priv, REG_GEN3_FPTATH)
+ & GEN3_FUSE_MASK;
+ priv->factor.fptat_t = rcar_thermal_read(priv, REG_GEN3_FPTATT)
+ & GEN3_FUSE_MASK;
+ priv->factor.fptat_l = rcar_thermal_read(priv, REG_GEN3_FPTATL)
+ & GEN3_FUSE_MASK;
+ } else {
+ priv->factor.fptat_h = 2351;
+ priv->factor.fptat_t = 1509;
+ priv->factor.fptat_l = 435;
+ switch (priv->id) {
+ case 0:
+ priv->factor.fthcode_h = 3248;
+ priv->factor.fthcode_t = 2800;
+ priv->factor.fthcode_l = 2221;
+ break;
+ case 1:
+ priv->factor.fthcode_h = 3245;
+ priv->factor.fthcode_t = 2795;
+ priv->factor.fthcode_l = 2216;
+ break;
+ case 2:
+ priv->factor.fthcode_h = 3250;
+ priv->factor.fthcode_t = 2805;
+ priv->factor.fthcode_l = 2237;
+ break;
+ }
+ }
+ iounmap(product_register);
+}
+
+#ifdef APPLY_QUADRATIC_EQUATION
+static void _quadratic_coef_calc(struct rcar_thermal_priv *priv)
+{
+ long tj_t = 0;
+ long a, b, c;
+ long num_a, num_a1, num_a2;
+ long den_a, den_a1, den_a2;
+ long num_b1, num_b2, num_b, den_b;
+ long para_c1, para_c2, para_c3;
+
+ tj_t = (CODETSD((priv->factor.fptat_t - priv->factor.fptat_l) * 137)
+ / (priv->factor.fptat_h - priv->factor.fptat_l)) - CODETSD(41);
+
+ /*
+ * The following code is to calculate coefficients
+ * for quadratic equation.
+ */
+ /* Coefficient a */
+ num_a1 = (CODETSD(priv->factor.fthcode_t)
+ - CODETSD(priv->factor.fthcode_l)) * (TJ_H - TJ_L);
+ num_a2 = (CODETSD(priv->factor.fthcode_h)
+ - CODETSD(priv->factor.fthcode_l)) * (tj_t - TJ_L);
+ num_a = num_a1 - num_a2;
+ den_a1 = (PW2(tj_t) - PW2(TJ_L)) * (TJ_H - TJ_L);
+ den_a2 = (PW2(TJ_H) - PW2(TJ_L)) * (tj_t - TJ_L);
+ den_a = (den_a1 - den_a2) / 1000;
+ a = (100000 * num_a) / den_a;
+
+ /* Coefficient b */
+ num_b1 = (CODETSD(priv->factor.fthcode_t)
+ - CODETSD(priv->factor.fthcode_l))
+ * (TJ_H - TJ_L);
+ num_b2 = ((PW2(tj_t) - PW2(TJ_L)) * (TJ_H - TJ_L) * a) / 1000;
+ num_b = 100000 * num_b1 - num_b2;
+ den_b = ((tj_t - TJ_L) * (TJ_H - TJ_L));
+ b = num_b / den_b;
+
+ /* Coefficient c */
+ para_c1 = 100000 * priv->factor.fthcode_l;
+ para_c2 = (PW2(TJ_L) * a) / PW2(1000);
+ para_c3 = (TJ_L * b) / 1000;
+ c = para_c1 - para_c2 - para_c3;
+
+ priv->coef.a = a;
+ priv->coef.b = b;
+ priv->coef.c = c;
+}
+#else
+static void _linear_coef_calc(struct rcar_thermal_priv *priv)
+{
+ int tj_t = 0;
+ long a1, b1;
+ long a2, b2;
+ long a1_num, a1_den;
+ long a2_num, a2_den;
+
+ tj_t = (CODETSD((priv->factor.fptat_t - priv->factor.fptat_l) * 137)
+ / (priv->factor.fptat_h - priv->factor.fptat_l)) - CODETSD(41);
+
+ /*
+ * The following code is to calculate coefficients for linear equation.
+ */
+ /* Coefficient a1 and b1 */
+ a1_num = CODETSD(priv->factor.fthcode_t - priv->factor.fthcode_l);
+ a1_den = tj_t - TJ_L;
+ a1 = (10000 * a1_num) / a1_den;
+ b1 = (10000 * priv->factor.fthcode_l) - ((a1 * TJ_L) / 1000);
+
+ /* Coefficient a2 and b2 */
+ a2_num = CODETSD(priv->factor.fthcode_t - priv->factor.fthcode_h);
+ a2_den = tj_t - TJ_H;
+ a2 = (10000 * a2_num) / a2_den;
+ b2 = (10000 * priv->factor.fthcode_h) - ((a2 * TJ_H) / 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);
+}
+#endif /* APPLY_QUADRATIC_EQUATION */
+
+static void thermal_coefficient_calculation(struct rcar_thermal_priv *priv)
+{
+#ifdef APPLY_QUADRATIC_EQUATION
+ _quadratic_coef_calc(priv);
+#else
+ _linear_coef_calc(priv);
+#endif /* APPLY_QUADRATIC_EQUATION */
+}
+
+#ifdef APPLY_QUADRATIC_EQUATION
+int _quadratic_temp_converter(struct equation_coefs coef, int temp_code)
+{
+ int temp, temp1, temp2;
+ long delta;
+
+ /* Multiply with 100000 to sync with coef a, coef b and coef c. */
+ delta = coef.b * coef.b - 4 * coef.a * (coef.c - 100000 * temp_code);
+
+ /* Multiply temp with 1000 to convert to Mili-Celsius */
+ temp1 = (CODETSD(-coef.b) + int_sqrt(1000000 * delta)) / 2;
+ temp1 = temp1 / coef.a;
+ temp2 = (CODETSD(-coef.b) - int_sqrt(1000000 * delta)) / 2;
+ temp2 = temp2 / coef.a;
+
+ if (temp1 > -45000000)
+ temp = temp1;
+ else
+ temp = temp2;
+
+ return round_temp(temp);
+}
+#else
+int _linear_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);
+}
+#endif /* APPLY_QUADRATIC_EQUATION */
+
+int thermal_temp_converter(struct equation_coefs coef,
+ int temp_code)
+{
+ int ctemp = 0;
+#ifdef APPLY_QUADRATIC_EQUATION
+ ctemp = _quadratic_temp_converter(coef, temp_code);
+#else
+ ctemp = _linear_temp_converter(coef, temp_code);
+#endif /* APPLY_QUADRATIC_EQUATION */
+
+ return ctemp;
+}
+
+/*
+ * Zone device functions
+ */
+static int rcar_gen3_thermal_update_temp(struct rcar_thermal_priv *priv)
+{
+ u32 ctemp;
+ int i;
+ unsigned long flags;
+ u32 reg = REG_GEN3_IRQTEMP1 + (priv->id * 4);
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ for (i = 0; i < 256; i++) {
+ ctemp = rcar_thermal_read(priv, REG_GEN3_TEMP) & CTEMP_MASK;
+ if (rcar_has_irq_support(priv)) {
+ rcar_thermal_write(priv, reg, ctemp);
+ if (rcar_thermal_read(priv, REG_GEN3_IRQSTR) != 0)
+ break;
+ } else
+ break;
+
+ udelay(150);
+ }
+
+ priv->ctemp = ctemp;
+ 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;
+
+ rcar_gen3_thermal_update_temp(priv);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ ctemp = thermal_temp_converter(priv->coef, priv->ctemp);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if ((ctemp < MCELSIUS(-40)) || (ctemp > MCELSIUS(125))) {
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ dev_err(dev, "Temperature is not measured correclty!\n");
+
+ return -EIO;
+ }
+
+ *temp = ctemp;
+
+ return 0;
+}
+
+static int rcar_gen3_thermal_init(struct rcar_thermal_priv *priv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ rcar_thermal_write(priv, REG_GEN3_CTSR, 0x0);
+
+ rcar_thermal_write(priv, REG_GEN3_CTSR,
+ PONM | AOUT | THBGR | VMEN);
+ udelay(100);
+ rcar_thermal_write(priv, REG_GEN3_CTSR,
+ PONM | AOUT | THBGR | VMEN | VMST | THSST);
+ udelay(1000);
+
+ rcar_thermal_write(priv, REG_GEN3_IRQCTL, 0x3F);
+ rcar_thermal_write(priv, REG_GEN3_IRQEN, TEMP_IRQ_SHIFT(priv->id) |
+ TEMPD_IRQ_SHIFT(priv->id));
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ 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 ? (TEMP_IRQ_SHIFT(priv->id) |
+ TEMPD_IRQ_SHIFT(priv->id)) : 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);
+
+ thermal_zone_device_update(priv->zone, THERMAL_DEVICE_EVENT_NONE);
+
+ 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 & TEMP_IRQ_SHIFT(priv->id)) ||
+ (status & TEMPD_IRQ_SHIFT(priv->id))) {
+ rcar_thermal_irq_disable(priv);
+ schedule_delayed_work(&priv->work,
+ msecs_to_jiffies(300));
+ }
+
+ 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_device_unregister(priv->zone);
+
+ pm_runtime_put(dev);
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
+static const struct of_device_id rcar_thermal_dt_ids[] = {
+ { .compatible = "renesas,rcar-gen3-thermal"},
+ {},
+};
+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;
+
+ 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);
+
+ 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 = thermal_zone_of_sensor_register(dev, 0, priv,
+ &rcar_gen3_tz_of_ops);
+
+
+ rcar_gen3_thermal_init(priv);
+ thermal_read_fuse_factor(priv);
+ thermal_coefficient_calculation(priv);
+ ret = rcar_gen3_thermal_update_temp(priv);
+
+ if (ret < 0)
+ goto error_unregister;
+
+ if (IS_ERR(priv->zone)) {
+ dev_err(dev, "Can't register thermal zone\n");
+ ret = PTR_ERR(priv->zone);
+ goto error_unregister;
+ }
+
+ rcar_thermal_irq_enable(priv);
+
+ /* Interrupt */
+ if (irq) {
+ ret = devm_request_irq(dev, irq->start,
+ rcar_gen3_thermal_irq, 0,
+ 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)
+{
+ /* Empty functino for now */
+ return 0;
+}
+
+static int rcar_gen3_thermal_resume(struct device *dev)
+{
+ /* Empty function for now */
+ 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 4678d8f..7aa25d4 100644
--- a/drivers/tty/serial/sh-sci.c
+++ b/drivers/tty/serial/sh-sci.c
@@ -1459,8 +1459,7 @@
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;
@@ -1833,11 +1832,17 @@
static unsigned int sci_get_mctrl(struct uart_port *port)
{
+ struct sci_port *s = to_sci_port(port);
+ unsigned int mctrl = TIOCM_DSR | TIOCM_CAR;
+
/*
* CTS/RTS is handled in hardware when supported, while nothing
* else is wired up. Keep it simple and simply assert DSR/CAR.
*/
- return TIOCM_DSR | TIOCM_CAR;
+ if (s->cfg->capabilities & SCIx_HAVE_RTSCTS)
+ mctrl |= TIOCM_CTS;
+
+ return mctrl;
}
static void sci_break_ctl(struct uart_port *port, int break_state)
@@ -2863,7 +2868,8 @@
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;
@@ -2889,6 +2895,22 @@
p->type = SCI_OF_TYPE(match->data);
p->regtype = SCI_OF_REGTYPE(match->data);
p->scscr = SCSCR_RE | SCSCR_TE;
+ if (of_property_read_bool(np, "ctsrts"))
+ 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;
}
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 770b6b0..afbbaa0 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -1,6 +1,7 @@
/*
* xhci-plat.c - xHCI host controller driver platform Bus Glue.
*
+ * Copyright (C) 2015 Renesas Electronics Corporation
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com
* Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*
@@ -39,12 +40,18 @@
static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci)
{
+ struct usb_hcd *hcd = xhci->main_hcd;
+
/*
* As of now platform drivers don't provide MSI support so we ensure
* here that the generic code does not try to make a pci_dev from our
* dev struct in order to setup MSI
*/
xhci->quirks |= XHCI_PLAT;
+
+ if (xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN2) ||
+ xhci_plat_type_is(hcd, XHCI_PLAT_TYPE_RENESAS_RCAR_GEN3))
+ xhci->quirks |= XHCI_NO_64BIT;
}
/* called during probe() after chip reset completes */
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 26a44c0..f652625 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1,6 +1,7 @@
/*
* xHCI host controller driver
*
+ * Copyright (C) 2015 Renesas Electronics Corporation
* Copyright (C) 2008 Intel Corp.
*
* Author: Sarah Sharp
@@ -4945,6 +4946,7 @@
/* Set dma_mask and coherent_dma_mask to 64-bits,
* if xHC supports 64-bit addressing */
if (HCC_64BIT_ADDR(xhci->hcc_params) &&
+ !(xhci->quirks & XHCI_NO_64BIT) &&
!dma_set_mask(dev, DMA_BIT_MASK(64))) {
xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n");
dma_set_coherent_mask(dev, DMA_BIT_MASK(64));
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 9be7348..04ce102 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -2,6 +2,7 @@
/*
* xHCI host controller driver
*
+ * Copyright (C) 2015 Renesas Electronics Corporation
* Copyright (C) 2008 Intel Corp.
*
* Author: Sarah Sharp
@@ -1631,6 +1632,7 @@
#define XHCI_BROKEN_STREAMS (1 << 19)
#define XHCI_PME_STUCK_QUIRK (1 << 20)
#define XHCI_MTK_HOST (1 << 21)
+#define XHCI_NO_64BIT (1 << 22)
unsigned int num_active_eps;
unsigned int limit_active_eps;
/* There are two roothubs to keep track of bus suspend info for */
diff --git a/drivers/usb/renesas_usbhs/Makefile b/drivers/usb/renesas_usbhs/Makefile
index 9e47f47..d787d05 100644
--- a/drivers/usb/renesas_usbhs/Makefile
+++ b/drivers/usb/renesas_usbhs/Makefile
@@ -4,7 +4,7 @@
obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs.o
-renesas_usbhs-y := common.o mod.o pipe.o fifo.o rcar2.o
+renesas_usbhs-y := common.o mod.o pipe.o fifo.o rcar2.o rcar3.o
ifneq ($(CONFIG_USB_RENESAS_USBHS_HCD),)
renesas_usbhs-y += mod_host.o
diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c
index 5af9ca5..baeb7d2 100644
--- a/drivers/usb/renesas_usbhs/common.c
+++ b/drivers/usb/renesas_usbhs/common.c
@@ -25,6 +25,7 @@
#include <linux/sysfs.h>
#include "common.h"
#include "rcar2.h"
+#include "rcar3.h"
/*
* image of renesas_usbhs
@@ -477,18 +478,16 @@
.data = (void *)USBHS_TYPE_RCAR_GEN2,
},
{
- /* Gen3 is compatible with Gen2 */
.compatible = "renesas,usbhs-r8a7795",
- .data = (void *)USBHS_TYPE_RCAR_GEN2,
+ .data = (void *)USBHS_TYPE_RCAR_GEN3,
},
{
.compatible = "renesas,rcar-gen2-usbhs",
.data = (void *)USBHS_TYPE_RCAR_GEN2,
},
{
- /* Gen3 is compatible with Gen2 */
.compatible = "renesas,rcar-gen3-usbhs",
- .data = (void *)USBHS_TYPE_RCAR_GEN2,
+ .data = (void *)USBHS_TYPE_RCAR_GEN3,
},
{ },
};
@@ -578,6 +577,13 @@
priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_new_pipe);
}
break;
+ case USBHS_TYPE_RCAR_GEN3:
+ priv->pfunc = usbhs_rcar3_ops;
+ if (!priv->dparam.pipe_configs) {
+ priv->dparam.pipe_configs = usbhsc_new_pipe;
+ priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_new_pipe);
+ }
+ break;
default:
if (!info->platform_callback.get_id) {
dev_err(&pdev->dev, "no platform callbacks");
diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c
index 657f967..664b263 100644
--- a/drivers/usb/renesas_usbhs/mod_gadget.c
+++ b/drivers/usb/renesas_usbhs/mod_gadget.c
@@ -561,7 +561,7 @@
if (!pkt)
break;
- usbhsg_queue_pop(uep, usbhsg_pkt_to_ureq(pkt), -ECONNRESET);
+ usbhsg_queue_pop(uep, usbhsg_pkt_to_ureq(pkt), -ESHUTDOWN);
}
usbhs_pipe_disable(pipe);
diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c
new file mode 100644
index 0000000..38b01f2
--- /dev/null
+++ b/drivers/usb/renesas_usbhs/rcar3.c
@@ -0,0 +1,54 @@
+/*
+ * Renesas USB driver R-Car Gen. 3 initialization and power control
+ *
+ * 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.
+ *
+ */
+
+#include <linux/io.h>
+#include "common.h"
+#include "rcar3.h"
+
+#define LPSTS 0x102
+#define UGCTRL2 0x184 /* 32-bit register */
+
+/* Low Power Status register (LPSTS) */
+#define LPSTS_SUSPM 0x4000
+
+/* USB General control register 2 (UGCTRL2), bit[31:6] should be 0 */
+#define UGCTRL2_RESERVED_3 0x00000001 /* bit[3:0] should be B'0001 */
+#define UGCTRL2_USB0SEL_OTG 0x00000030
+
+void usbhs_write32(struct usbhs_priv *priv, u32 reg, u32 data)
+{
+ iowrite32(data, priv->base + reg);
+}
+
+static int usbhs_rcar3_power_ctrl(struct platform_device *pdev,
+ void __iomem *base, int enable)
+{
+ struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev);
+
+ usbhs_write32(priv, UGCTRL2, UGCTRL2_RESERVED_3 | UGCTRL2_USB0SEL_OTG);
+
+ if (enable)
+ usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM);
+ else
+ usbhs_bset(priv, LPSTS, LPSTS_SUSPM, 0);
+
+ return 0;
+}
+
+static int usbhs_rcar3_get_id(struct platform_device *pdev)
+{
+ return USBHS_GADGET;
+}
+
+const struct renesas_usbhs_platform_callback usbhs_rcar3_ops = {
+ .power_ctrl = usbhs_rcar3_power_ctrl,
+ .get_id = usbhs_rcar3_get_id,
+};
diff --git a/drivers/usb/renesas_usbhs/rcar3.h b/drivers/usb/renesas_usbhs/rcar3.h
new file mode 100644
index 0000000..5f850b2
--- /dev/null
+++ b/drivers/usb/renesas_usbhs/rcar3.h
@@ -0,0 +1,3 @@
+#include "common.h"
+
+extern const struct renesas_usbhs_platform_callback usbhs_rcar3_ops;
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 4f0e7be..e698d43 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -636,6 +636,14 @@
To compile this driver as a module, choose M here: the
module will be called atlas7_wdt.
+config RENESAS_RWDT
+ tristate "Renesas RWDT Watchdog"
+ depends on ARCH_SHMOBILE || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ This driver adds watchdog support for the integrated watchdog in the
+ Renesas RCar and other SH Mobile SoCs.
+
# AVR32 Architecture
config AT32AP700X_WDT
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index f566753..1524585 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -72,6 +72,7 @@
obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o
obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o
obj-$(CONFIG_ATLAS7_WATCHDOG) += atlas7_wdt.o
+obj-$(CONFIG_RENESAS_RWDT) += renesas_rwdt.o
# AVR32 Architecture
obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
diff --git a/drivers/watchdog/renesas_rwdt.c b/drivers/watchdog/renesas_rwdt.c
new file mode 100644
index 0000000..e925eea
--- /dev/null
+++ b/drivers/watchdog/renesas_rwdt.c
@@ -0,0 +1,251 @@
+/*
+ * Watchdog driver for Renesas RWDT watchdog
+ *
+ * Copyright (C) 2015-16 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>
+ * Copyright (C) 2015-16 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.
+ */
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/pm_runtime.h>
+
+#define RWTCNT 0
+#define RWTCSRA 4
+#define RWTCSRA_WOVF BIT(4)
+#define RWTCSRA_WRFLG BIT(5)
+#define RWTCSRA_TME BIT(7)
+
+#define RWDT_DEFAULT_TIMEOUT 60
+
+static const unsigned clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024 };
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, S_IRUGO);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct rwdt_priv {
+ void __iomem *base;
+ struct watchdog_device wdev;
+ struct clk *clk;
+ unsigned clks_per_sec;
+ u8 cks;
+};
+
+static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned reg)
+{
+ if (reg == RWTCNT)
+ val |= 0x5a5a0000;
+ else
+ val |= 0xa5a5a500;
+
+ writel_relaxed(val, priv->base + reg);
+}
+
+static int rwdt_init_timeout(struct watchdog_device *wdev)
+{
+ struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ rwdt_write(priv, 65536 - wdev->timeout * priv->clks_per_sec, RWTCNT);
+
+ return 0;
+}
+
+static int rwdt_set_timeout(struct watchdog_device *wdev, unsigned 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);
+
+ pm_runtime_get_sync(wdev->parent);
+
+ rwdt_write(priv, priv->cks, RWTCSRA);
+ rwdt_init_timeout(wdev);
+
+ while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
+ cpu_relax();
+
+ rwdt_write(priv, priv->cks | RWTCSRA_TME, RWTCSRA);
+
+ return 0;
+}
+
+static int rwdt_stop(struct watchdog_device *wdev)
+{
+ struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ rwdt_write(priv, priv->cks, RWTCSRA);
+ pm_runtime_put(wdev->parent);
+
+ return 0;
+}
+
+static int rwdt_restart_handler(struct watchdog_device *wdev)
+{
+ struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ rwdt_start(wdev);
+ rwdt_write(priv, 0xffff, RWTCNT);
+
+ return 0;
+}
+
+static const struct watchdog_info rwdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+ .identity = "Renesas RWDT Watchdog",
+};
+
+static const struct watchdog_ops rwdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rwdt_start,
+ .stop = rwdt_stop,
+ .ping = rwdt_init_timeout,
+ .set_timeout = rwdt_set_timeout,
+ .restart = rwdt_restart_handler,
+};
+
+static int rwdt_probe(struct platform_device *pdev)
+{
+ struct rwdt_priv *priv;
+ struct resource *res;
+ unsigned long rate;
+ unsigned clks_per_sec;
+ int ret, i;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_get_sync(&pdev->dev);
+
+ rate = clk_get_rate(priv->clk);
+ if (!rate) {
+ ret = -ENOENT;
+ goto err_clk;
+ }
+
+ for (i = ARRAY_SIZE(clk_divs); i >= 0; i--) {
+ clks_per_sec = rate / clk_divs[i];
+ if (clks_per_sec) {
+ priv->clks_per_sec = clks_per_sec;
+ priv->cks = i;
+ break;
+ }
+ }
+
+ if (!clks_per_sec) {
+ dev_err(&pdev->dev, "Can't find suitable clock divider!\n");
+ ret = -ERANGE;
+ goto err_clk;
+ }
+
+ priv->wdev.info = &rwdt_ident,
+ priv->wdev.ops = &rwdt_ops,
+ priv->wdev.parent = &pdev->dev;
+ priv->wdev.min_timeout = 1;
+ priv->wdev.max_timeout = 65536 / clks_per_sec;
+ priv->wdev.timeout = min_t(unsigned, priv->wdev.max_timeout, RWDT_DEFAULT_TIMEOUT);
+
+ platform_set_drvdata(pdev, priv);
+ watchdog_set_drvdata(&priv->wdev, priv);
+ watchdog_set_nowayout(&priv->wdev, nowayout);
+ watchdog_set_restart_priority(&priv->wdev, 192);
+
+ /* This overrides the default timeout only if DT configuration was found */
+ ret = watchdog_init_timeout(&priv->wdev, 0, &pdev->dev);
+ if (ret)
+ dev_warn(&pdev->dev, "Specified timeout value invalid, using default\n");
+
+ ret = watchdog_register_device(&priv->wdev);
+ if (ret < 0)
+ goto err_register;
+
+ pm_runtime_put(&pdev->dev);
+ return 0;
+
+err_register:
+err_clk:
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static int rwdt_remove(struct platform_device *pdev)
+{
+ struct rwdt_priv *priv = platform_get_drvdata(pdev);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ watchdog_unregister_device(&priv->wdev);
+ return 0;
+}
+
+/*
+ * This driver does also fit for RCar Gen2 (r8a779[0-4]) RWDT. However, for SMP
+ * to work there, one also needs a RESET (RST) driver which does not exist yet
+ * due to HW issues. This needs to be solved before adding compatibles here.
+ */
+static const struct of_device_id rwdt_ids[] = {
+ { .compatible = "renesas,rwdt-r8a7795", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rwdt_ids);
+
+#ifdef CONFIG_PM_SLEEP
+static int rwdt_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int rwdt_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+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_rwdt",
+ .pm = DEV_PM_OPS,
+ .of_match_table = rwdt_ids,
+ },
+ .probe = rwdt_probe,
+ .remove = rwdt_remove,
+};
+module_platform_driver(rwdt_driver);
+
+MODULE_DESCRIPTION("Renesas RWDT Watchdog Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>");
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 @@
IMX6Q_HDMI,
IMX6DL_HDMI,
RK3288_HDMI,
+ RCAR_HDMI,
};
struct dw_hdmi_mpll_config {
@@ -40,6 +42,11 @@
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 @@
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 24b86d5..30d518d 100644
--- a/include/linux/mfd/tmio.h
+++ b/include/linux/mfd/tmio.h
@@ -95,11 +95,17 @@
*/
#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
*/
#define TMIO_MMC_CLK_ACTUAL (1 << 10)
+/* Some controllers have UHS-I sampling clock controller */
+#define TMIO_MMC_HAS_UHS_SCC (1 << 11)
+
int tmio_core_mmc_enable(void __iomem *cnf, int shift, unsigned long base);
int tmio_core_mmc_resume(void __iomem *cnf, int shift, unsigned long base);
void tmio_core_mmc_pwr(void __iomem *cnf, int shift, int state);
@@ -121,6 +127,8 @@
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/regmap.h b/include/linux/regmap.h
index 1839434..5315ff0 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -65,6 +65,33 @@
unsigned int delay_us;
};
+#define regmap_update_bits(map, reg, mask, val) \
+ regmap_update_bits_base(map, reg, mask, val, NULL, false, false)
+#define regmap_update_bits_async(map, reg, mask, val)\
+ regmap_update_bits_base(map, reg, mask, val, NULL, true, false)
+#define regmap_update_bits_check(map, reg, mask, val, change)\
+ regmap_update_bits_base(map, reg, mask, val, change, false, false)
+#define regmap_update_bits_check_async(map, reg, mask, val, change)\
+ regmap_update_bits_base(map, reg, mask, val, change, true, false)
+
+#define regmap_field_write(field, val) \
+ regmap_field_update_bits_base(field, ~0, val, NULL, false, false)
+#define regmap_field_force_write(field, val) \
+ regmap_field_update_bits_base(field, ~0, val, NULL, false, true)
+#define regmap_field_update_bits(field, mask, val)\
+ regmap_field_update_bits_base(field, mask, val, NULL, false, false)
+#define regmap_field_force_update_bits(field, mask, val) \
+ regmap_field_update_bits_base(field, mask, val, NULL, false, true)
+
+#define regmap_fields_write(field, id, val) \
+ regmap_fields_update_bits_base(field, id, ~0, val, NULL, false, false)
+#define regmap_fields_force_write(field, id, val) \
+ regmap_fields_update_bits_base(field, id, ~0, val, NULL, false, true)
+#define regmap_fields_update_bits(field, id, mask, val)\
+ regmap_fields_update_bits_base(field, id, mask, val, NULL, false, false)
+#define regmap_fields_force_update_bits(field, id, mask, val) \
+ regmap_fields_update_bits_base(field, id, mask, val, NULL, false, true)
+
#ifdef CONFIG_REGMAP
enum regmap_endian {
@@ -691,18 +718,9 @@
void *val, size_t val_len);
int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val,
size_t val_count);
-int regmap_update_bits(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val);
-int regmap_write_bits(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val);
-int regmap_update_bits_async(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val);
-int regmap_update_bits_check(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val,
- bool *change);
-int regmap_update_bits_check_async(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val,
- bool *change);
+int regmap_update_bits_base(struct regmap *map, unsigned int reg,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force);
int regmap_get_val_bytes(struct regmap *map);
int regmap_get_max_register(struct regmap *map);
int regmap_get_reg_stride(struct regmap *map);
@@ -770,18 +788,14 @@
void devm_regmap_field_free(struct device *dev, struct regmap_field *field);
int regmap_field_read(struct regmap_field *field, unsigned int *val);
-int regmap_field_write(struct regmap_field *field, unsigned int val);
-int regmap_field_update_bits(struct regmap_field *field,
- unsigned int mask, unsigned int val);
-
-int regmap_fields_write(struct regmap_field *field, unsigned int id,
- unsigned int val);
-int regmap_fields_force_write(struct regmap_field *field, unsigned int id,
- unsigned int val);
+int regmap_field_update_bits_base(struct regmap_field *field,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force);
int regmap_fields_read(struct regmap_field *field, unsigned int id,
unsigned int *val);
-int regmap_fields_update_bits(struct regmap_field *field, unsigned int id,
- unsigned int mask, unsigned int val);
+int regmap_fields_update_bits_base(struct regmap_field *field, unsigned int id,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force);
/**
* Description of an IRQ for the generic regmap irq_chip.
@@ -937,42 +951,26 @@
return -EINVAL;
}
-static inline int regmap_update_bits(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val)
+static inline int regmap_update_bits_base(struct regmap *map, unsigned int reg,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force)
{
WARN_ONCE(1, "regmap API is disabled");
return -EINVAL;
}
-static inline int regmap_write_bits(struct regmap *map, unsigned int reg,
- unsigned int mask, unsigned int val)
+static inline int regmap_field_update_bits_base(struct regmap_field *field,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force)
{
WARN_ONCE(1, "regmap API is disabled");
return -EINVAL;
}
-static inline int regmap_update_bits_async(struct regmap *map,
- unsigned int reg,
- unsigned int mask, unsigned int val)
-{
- WARN_ONCE(1, "regmap API is disabled");
- return -EINVAL;
-}
-
-static inline int regmap_update_bits_check(struct regmap *map,
- unsigned int reg,
- unsigned int mask, unsigned int val,
- bool *change)
-{
- WARN_ONCE(1, "regmap API is disabled");
- return -EINVAL;
-}
-
-static inline int regmap_update_bits_check_async(struct regmap *map,
- unsigned int reg,
- unsigned int mask,
- unsigned int val,
- bool *change)
+static inline int regmap_fields_update_bits_base(struct regmap_field *field,
+ unsigned int id,
+ unsigned int mask, unsigned int val,
+ bool *change, bool async, bool force)
{
WARN_ONCE(1, "regmap API is disabled");
return -EINVAL;
diff --git a/include/linux/soc/renesas/rcar_prr.h b/include/linux/soc/renesas/rcar_prr.h
new file mode 100644
index 0000000..59ec0f1
--- /dev/null
+++ b/include/linux/soc/renesas/rcar_prr.h
@@ -0,0 +1,61 @@
+/*
+ * Renesas R-Car Product Register (PRR) helpers
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Gereral Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ */
+
+#ifndef __SOC_RCAR_PRR_H
+#define __SOC_RCAR_PRR_H
+
+#include <linux/types.h>
+#include <linux/io.h>
+
+#define PRR (0xfff00044ul) /* Product Register */
+
+/* PRR PRODUCT for RCAR */
+#define PRR_PRODUCT_RCAR_H3 (0x4f00ul)
+#define PRR_PRODUCT_MASK (0x7f00ul)
+
+/* PRR PRODUCT and CUT for RCAR */
+#define PRR_PRODUCT_CUT_RCAR_H3_WS10 (PRR_PRODUCT_RCAR_H3 | 0x00ul)
+#define PRR_PRODUCT_CUT_RCAR_H3_WS11 (PRR_PRODUCT_RCAR_H3 | 0x01ul)
+#define PRR_PRODUCT_CUT_MASK (PRR_PRODUCT_MASK | 0xfful)
+
+#define RCAR_PRR_INIT() rcar_prr_init()
+
+#define RCAR_PRR_IS_PRODUCT(a) \
+ rcar_prr_compare_product(PRR_PRODUCT_RCAR_##a)
+
+#define RCAR_PRR_CHK_CUT(a, b) \
+ rcar_prr_check_product_cut(PRR_PRODUCT_CUT_RCAR_##a##_##b)
+
+static u32 rcar_prr = 0xffffffff;
+
+static inline int rcar_prr_compare_product(u32 id)
+{
+ return (rcar_prr & PRR_PRODUCT_MASK) == (id & PRR_PRODUCT_MASK);
+}
+
+static inline int rcar_prr_check_product_cut(u32 id)
+{
+ return (rcar_prr & PRR_PRODUCT_CUT_MASK) - (id & PRR_PRODUCT_CUT_MASK);
+}
+
+static inline int rcar_prr_init(void)
+{
+ void __iomem *reg;
+
+ reg = ioremap(PRR, 0x04);
+ if (!reg)
+ return -ENOMEM;
+
+ rcar_prr = ioread32(reg);
+ iounmap(reg);
+
+ return 0;
+}
+#endif /* __SOC_RCAR_PRR_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/usb/renesas_usbhs.h b/include/linux/usb/renesas_usbhs.h
index 4db191f..00a47d0 100644
--- a/include/linux/usb/renesas_usbhs.h
+++ b/include/linux/usb/renesas_usbhs.h
@@ -184,6 +184,7 @@
};
#define USBHS_TYPE_RCAR_GEN2 1
+#define USBHS_TYPE_RCAR_GEN3 2
/*
* option:
diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h
index 0e32bc7..ca73c50 100644
--- a/include/linux/workqueue.h
+++ b/include/linux/workqueue.h
@@ -311,6 +311,7 @@
__WQ_DRAINING = 1 << 16, /* internal: workqueue is draining */
__WQ_ORDERED = 1 << 17, /* internal: workqueue is ordered */
+ __WQ_LEGACY = 1 << 18, /* internal: create*_workqueue() */
WQ_MAX_ACTIVE = 512, /* I like 512, better ideas? */
WQ_MAX_UNBOUND_PER_CPU = 4, /* 4 * #cpus for unbound wq */
@@ -411,12 +412,12 @@
alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)
#define create_workqueue(name) \
- alloc_workqueue("%s", WQ_MEM_RECLAIM, 1, (name))
+ alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))
#define create_freezable_workqueue(name) \
- alloc_workqueue("%s", WQ_FREEZABLE | WQ_UNBOUND | WQ_MEM_RECLAIM, \
- 1, (name))
+ alloc_workqueue("%s", __WQ_LEGACY | WQ_FREEZABLE | WQ_UNBOUND | \
+ WQ_MEM_RECLAIM, 1, (name))
#define create_singlethread_workqueue(name) \
- alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, name)
+ alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)
extern void destroy_workqueue(struct workqueue_struct *wq);
diff --git a/include/media/rcar_csi2.h b/include/media/rcar_csi2.h
new file mode 100644
index 0000000..1a040fa
--- /dev/null
+++ b/include/media/rcar_csi2.h
@@ -0,0 +1,66 @@
+/*
+ * include/media/rcar_csi2.h
+ * This file is the driver header
+ * for the Renesas R-Car MIPI CSI-2 unit.
+ *
+ * Copyright (C) 2015 Renesas Electronics Corporation
+ *
+ * This file is based on the include/media/sh_mobile_csi2.h
+ *
+ * Driver header for the SH-Mobile MIPI CSI-2 unit
+ *
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * 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.
+ */
+
+#ifndef RCAR_MIPI_CSI
+#define RCAR_MIPI_CSI
+
+#include <linux/list.h>
+
+enum rcar_csi2_phy {
+ RCAR_CSI2_PHY_CSI40, /* CSI0 */
+ RCAR_CSI2_PHY_CSI20, /* CSI1 */
+ RCAR_CSI2_PHY_CSI41, /* CSI2 */
+ RCAR_CSI2_PHY_CSI21, /* CSI3 */
+};
+
+enum rcar_csi2_link {
+ RCAR_CSI2_LINK_CSI40,
+ RCAR_CSI2_LINK_CSI20,
+ RCAR_CSI2_LINK_CSI41,
+ RCAR_CSI2_LINK_CSI21,
+};
+
+enum rcar_csi2_type {
+ RCAR_CSI2_CSI4X,
+ RCAR_CSI2_CSI2X,
+};
+
+#define RCAR_CSI2_CRC (1 << 0)
+#define RCAR_CSI2_ECC (1 << 1)
+
+struct platform_device;
+
+struct rcar_csi2_client_config {
+ enum rcar_csi2_phy phy;
+ enum rcar_csi2_link link;
+ unsigned char lanes; /* bitmask[3:0] */
+ unsigned char channel; /* 0..3 */
+ struct platform_device *pdev; /* client platform device */
+ const char *name; /* async matching: client name */
+};
+
+struct v4l2_device;
+
+struct rcar_csi2_pdata {
+ enum rcar_csi2_type type;
+ unsigned int flags;
+ struct rcar_csi2_client_config *clients;
+ int num_clients;
+};
+
+#endif
diff --git a/include/media/vsp1.h b/include/media/vsp1.h
index cc54175..a1d4f2a 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)
*
@@ -27,7 +27,8 @@
int vsp1_du_atomic_update(struct device *dev, unsigned int rpf, u32 pixelformat,
unsigned int pitch, dma_addr_t mem[2],
const struct v4l2_rect *src,
- const struct v4l2_rect *dst);
+ const struct v4l2_rect *dst, u8 alpha);
int vsp1_du_atomic_flush(struct device *dev);
+int vsp1_du_if_set_mute(struct device *dev, bool on);
#endif /* __MEDIA_VSP1_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..8ac0296
--- /dev/null
+++ b/include/uapi/drm/rcar_du_drm.h
@@ -0,0 +1,27 @@
+/*
+ * 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 */
+};
+
+/* rcar-du + vspd specific ioctls */
+#define DRM_RCAR_DU_SET_VMUTE 0
+
+#define DRM_IOCTL_DRM_RCAR_DU_SET_VMUTE \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_RCAR_DU_SET_VMUTE, \
+ struct rcar_du_vmute)
+
+#endif /* __RCAR_DU_DRM_H__ */
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index 61a0264..dc7faad 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -2355,7 +2355,8 @@
WARN_ONCE(current->flags & PF_MEMALLOC,
"workqueue: PF_MEMALLOC task %d(%s) is flushing !WQ_MEM_RECLAIM %s:%pf",
current->pid, current->comm, target_wq->name, target_func);
- WARN_ONCE(worker && (worker->current_pwq->wq->flags & WQ_MEM_RECLAIM),
+ WARN_ONCE(worker && ((worker->current_pwq->wq->flags &
+ (WQ_MEM_RECLAIM | __WQ_LEGACY)) == WQ_MEM_RECLAIM),
"workqueue: WQ_MEM_RECLAIM %s:%pf is flushing !WQ_MEM_RECLAIM %s:%pf",
worker->current_pwq->wq->name, worker->current_func,
target_wq->name, target_func);
diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c
index 647f69d..92b67e2 100644
--- a/sound/soc/codecs/ak4613.c
+++ b/sound/soc/codecs/ak4613.c
@@ -444,7 +444,14 @@
return regcache_sync(regmap);
}
+static int ak4613_suspend(struct snd_soc_codec *codec)
+{
+ /* Empty function for now */
+ return 0;
+}
+
static struct snd_soc_codec_driver soc_codec_dev_ak4613 = {
+ .suspend = ak4613_suspend,
.resume = ak4613_resume,
.set_bias_level = ak4613_set_bias_level,
.controls = ak4613_snd_controls,
diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c
index 6d3ef36..d74e1cc 100644
--- a/sound/soc/sh/rcar/adg.c
+++ b/sound/soc/sh/rcar/adg.c
@@ -518,13 +518,8 @@
return -ENOMEM;
}
- /*
- * ADG is special module.
- * Use ADG mod without rsnd_mod_init() to make debug easy
- * for rsnd_write/rsnd_read
- */
- adg->mod.ops = &adg_ops;
- adg->mod.priv = priv;
+ rsnd_mod_init(priv, &adg->mod, &adg_ops,
+ NULL, NULL, 0, 0);
rsnd_adg_get_clkin(priv, adg);
rsnd_adg_get_clkout(priv, adg);
diff --git a/sound/soc/sh/rcar/cmd.c b/sound/soc/sh/rcar/cmd.c
index cd1f064..abb5eaa 100644
--- a/sound/soc/sh/rcar/cmd.c
+++ b/sound/soc/sh/rcar/cmd.c
@@ -29,7 +29,6 @@
{
struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io);
struct rsnd_mod *mix = rsnd_io_to_mod_mix(io);
- struct rsnd_mod *src = rsnd_io_to_mod_src(io);
struct device *dev = rsnd_priv_to_dev(priv);
u32 data;
@@ -38,6 +37,8 @@
if (mix) {
struct rsnd_dai *rdai;
+ struct rsnd_mod *src;
+ struct rsnd_dai_stream *tio;
int i;
u32 path[] = {
[0] = 0,
@@ -55,16 +56,20 @@
*/
data = 0;
for_each_rsnd_dai(rdai, priv, i) {
- io = &rdai->playback;
- if (mix == rsnd_io_to_mod_mix(io))
+ tio = &rdai->playback;
+ src = rsnd_io_to_mod_src(tio);
+ if (mix == rsnd_io_to_mod_mix(tio))
data |= path[rsnd_mod_id(src)];
- io = &rdai->capture;
- if (mix == rsnd_io_to_mod_mix(io))
+ tio = &rdai->capture;
+ src = rsnd_io_to_mod_src(tio);
+ if (mix == rsnd_io_to_mod_mix(tio))
data |= path[rsnd_mod_id(src)];
}
} else {
+ struct rsnd_mod *src = rsnd_io_to_mod_src(io);
+
u32 path[] = {
[0] = 0x30000,
[1] = 0x30001,
@@ -152,7 +157,8 @@
for_each_rsnd_cmd(cmd, priv, i) {
ret = rsnd_mod_init(priv, rsnd_mod_get(cmd),
- &rsnd_cmd_ops, NULL, RSND_MOD_CMD, i);
+ &rsnd_cmd_ops, NULL,
+ rsnd_mod_get_status, RSND_MOD_CMD, i);
if (ret)
return ret;
}
diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c
index 02b4b08..d9b98f2 100644
--- a/sound/soc/sh/rcar/core.c
+++ b/sound/soc/sh/rcar/core.c
@@ -138,12 +138,22 @@
return mod->ops->dma_req(io, mod);
}
+u32 *rsnd_mod_get_status(struct rsnd_dai_stream *io,
+ struct rsnd_mod *mod,
+ enum rsnd_mod_type type)
+{
+ return &mod->status;
+}
+
int rsnd_mod_init(struct rsnd_priv *priv,
struct rsnd_mod *mod,
- struct rsnd_mod_ops *ops,
- struct clk *clk,
- enum rsnd_mod_type type,
- int id)
+ struct rsnd_mod_ops *ops,
+ struct clk *clk,
+ u32* (*get_status)(struct rsnd_dai_stream *io,
+ struct rsnd_mod *mod,
+ enum rsnd_mod_type type),
+ enum rsnd_mod_type type,
+ int id)
{
int ret = clk_prepare(clk);
@@ -155,6 +165,7 @@
mod->type = type;
mod->clk = clk;
mod->priv = priv;
+ mod->get_status = get_status;
return ret;
}
@@ -163,6 +174,7 @@
{
if (mod->clk)
clk_unprepare(mod->clk);
+ mod->clk = NULL;
}
void rsnd_mod_interrupt(struct rsnd_mod *mod,
@@ -218,7 +230,7 @@
int chan = runtime->channels;
/* Multi channel Mode */
- if (rsnd_ssi_multi_slaves(io))
+ if (rsnd_ssi_multi_slaves_runtime(io))
chan /= rsnd_get_slot_num(io);
/* TDM Extend Mode needs 8ch */
@@ -324,31 +336,73 @@
struct rsnd_priv *priv = rsnd_mod_to_priv(mod); \
struct rsnd_mod *mod = (io)->mod[idx]; \
struct device *dev = rsnd_priv_to_dev(priv); \
- u32 *status = (io)->mod_status + idx; \
+ u32 *status = mod->get_status(io, mod, idx); \
u32 mask = 0xF << __rsnd_mod_shift_##func; \
u8 val = (*status >> __rsnd_mod_shift_##func) & 0xF; \
u8 add = ((val + __rsnd_mod_add_##func) & 0xF); \
int ret = 0; \
int call = (val == __rsnd_mod_call_##func) && (mod)->ops->func; \
- *status = (*status & ~mask) + \
- (add << __rsnd_mod_shift_##func); \
+ if (add == 0xF) \
+ call = 0; \
+ else \
+ *status = (*status & ~mask) + \
+ (add << __rsnd_mod_shift_##func); \
dev_dbg(dev, "%s[%d]\t0x%08x %s\n", \
rsnd_mod_name(mod), rsnd_mod_id(mod), \
*status, call ? #func : ""); \
if (call) \
ret = (mod)->ops->func(mod, io, param); \
+ if (ret) \
+ dev_dbg(dev, "%s[%d] : rsnd_mod_call error %d\n", \
+ rsnd_mod_name(mod), rsnd_mod_id(mod), ret); \
ret; \
})
+static enum rsnd_mod_type rsnd_mod_sequence[][RSND_MOD_MAX] = {
+ {
+ /* CAPTURE */
+ RSND_MOD_AUDMAPP,
+ RSND_MOD_AUDMA,
+ RSND_MOD_DVC,
+ RSND_MOD_MIX,
+ RSND_MOD_CTU,
+ RSND_MOD_CMD,
+ RSND_MOD_SRC,
+ RSND_MOD_SSIU,
+ RSND_MOD_SSIM3,
+ RSND_MOD_SSIM2,
+ RSND_MOD_SSIM1,
+ RSND_MOD_SSIP,
+ RSND_MOD_SSI,
+ }, {
+ /* PLAYBACK */
+ RSND_MOD_AUDMAPP,
+ RSND_MOD_AUDMA,
+ RSND_MOD_SSIM3,
+ RSND_MOD_SSIM2,
+ RSND_MOD_SSIM1,
+ RSND_MOD_SSIP,
+ RSND_MOD_SSI,
+ RSND_MOD_SSIU,
+ RSND_MOD_DVC,
+ RSND_MOD_MIX,
+ RSND_MOD_CTU,
+ RSND_MOD_CMD,
+ RSND_MOD_SRC,
+ },
+};
+
#define rsnd_dai_call(fn, io, param...) \
({ \
struct rsnd_mod *mod; \
+ int type, is_play = rsnd_io_is_play(io); \
int ret = 0, i; \
for (i = 0; i < RSND_MOD_MAX; i++) { \
- mod = (io)->mod[i]; \
+ type = rsnd_mod_sequence[is_play][i]; \
+ mod = (io)->mod[type]; \
if (!mod) \
continue; \
- ret |= rsnd_mod_call(i, io, fn, param); \
+ ret |= rsnd_mod_call(type, io, fn, param); \
} \
ret; \
})
@@ -363,6 +417,9 @@
if (!mod)
return -EIO;
+ if (io->mod[type] == mod)
+ return 0;
+
if (io->mod[type])
return -EINVAL;
@@ -511,9 +568,16 @@
ret = rsnd_dai_call(start, io, priv);
if (ret < 0)
goto dai_trigger_end;
+
+ ret = rsnd_dai_call(irq, io, priv, 1);
+ if (ret < 0)
+ goto dai_trigger_end;
+
break;
case SNDRV_PCM_TRIGGER_STOP:
- ret = rsnd_dai_call(stop, io, priv);
+ ret = rsnd_dai_call(irq, io, priv, 0);
+
+ ret |= rsnd_dai_call(stop, io, priv);
ret |= rsnd_dai_call(quit, io, priv);
@@ -923,7 +987,7 @@
int ch_size,
u32 max)
{
- if (ch_size > RSND_DVC_CHANNELS)
+ if (ch_size > RSND_MAX_CHANNELS)
return -EINVAL;
_cfg->cfg.max = max;
@@ -1169,9 +1233,30 @@
return ret;
}
+#ifdef CONFIG_PM_SLEEP
+static int rsnd_suspend(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static int rsnd_resume(struct device *dev)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rsnd_pm_ops,
+ rsnd_suspend, rsnd_resume);
+#define DEV_PM_OPS (&rsnd_pm_ops)
+#else
+#define DEV_PM_OPS NUL
+#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/ctu.c b/sound/soc/sh/rcar/ctu.c
index d53a225..a10d0f7 100644
--- a/sound/soc/sh/rcar/ctu.c
+++ b/sound/soc/sh/rcar/ctu.c
@@ -24,11 +24,17 @@
i++)
#define rsnd_ctu_get(priv, id) ((struct rsnd_ctu *)(priv->ctu) + id)
-#define rsnd_ctu_initialize_lock(mod) __rsnd_ctu_initialize_lock(mod, 1)
-#define rsnd_ctu_initialize_unlock(mod) __rsnd_ctu_initialize_lock(mod, 0)
-static void __rsnd_ctu_initialize_lock(struct rsnd_mod *mod, u32 enable)
+
+static void rsnd_ctu_activation(struct rsnd_mod *mod)
{
- rsnd_mod_write(mod, CTU_CTUIR, enable);
+ rsnd_mod_write(mod, CTU_SWRSR, 0);
+ rsnd_mod_write(mod, CTU_SWRSR, 1);
+}
+
+static void rsnd_ctu_halt(struct rsnd_mod *mod)
+{
+ rsnd_mod_write(mod, CTU_CTUIR, 1);
+ rsnd_mod_write(mod, CTU_SWRSR, 0);
}
static int rsnd_ctu_probe_(struct rsnd_mod *mod,
@@ -38,17 +44,63 @@
return rsnd_cmd_attach(io, rsnd_mod_id(mod) / 4);
}
+static void rsnd_ctu_value_init(struct rsnd_dai_stream *io,
+ struct rsnd_mod *mod)
+{
+ rsnd_mod_write(mod, CTU_CTUIR, 1);
+
+ rsnd_mod_write(mod, CTU_ADINR, rsnd_get_adinr_chan(mod, io));
+
+ rsnd_mod_write(mod, CTU_CPMDR, 0);
+ rsnd_mod_write(mod, CTU_SCMDR, 0);
+ rsnd_mod_write(mod, CTU_SV00R, 0);
+ rsnd_mod_write(mod, CTU_SV01R, 0);
+ rsnd_mod_write(mod, CTU_SV02R, 0);
+ rsnd_mod_write(mod, CTU_SV03R, 0);
+ rsnd_mod_write(mod, CTU_SV04R, 0);
+ rsnd_mod_write(mod, CTU_SV05R, 0);
+ rsnd_mod_write(mod, CTU_SV06R, 0);
+ rsnd_mod_write(mod, CTU_SV07R, 0);
+
+ rsnd_mod_write(mod, CTU_SV10R, 0);
+ rsnd_mod_write(mod, CTU_SV11R, 0);
+ rsnd_mod_write(mod, CTU_SV12R, 0);
+ rsnd_mod_write(mod, CTU_SV13R, 0);
+ rsnd_mod_write(mod, CTU_SV14R, 0);
+ rsnd_mod_write(mod, CTU_SV15R, 0);
+ rsnd_mod_write(mod, CTU_SV16R, 0);
+ rsnd_mod_write(mod, CTU_SV17R, 0);
+
+ rsnd_mod_write(mod, CTU_SV20R, 0);
+ rsnd_mod_write(mod, CTU_SV21R, 0);
+ rsnd_mod_write(mod, CTU_SV22R, 0);
+ rsnd_mod_write(mod, CTU_SV23R, 0);
+ rsnd_mod_write(mod, CTU_SV24R, 0);
+ rsnd_mod_write(mod, CTU_SV25R, 0);
+ rsnd_mod_write(mod, CTU_SV26R, 0);
+ rsnd_mod_write(mod, CTU_SV27R, 0);
+
+ rsnd_mod_write(mod, CTU_SV30R, 0);
+ rsnd_mod_write(mod, CTU_SV31R, 0);
+ rsnd_mod_write(mod, CTU_SV32R, 0);
+ rsnd_mod_write(mod, CTU_SV33R, 0);
+ rsnd_mod_write(mod, CTU_SV34R, 0);
+ rsnd_mod_write(mod, CTU_SV35R, 0);
+ rsnd_mod_write(mod, CTU_SV36R, 0);
+ rsnd_mod_write(mod, CTU_SV37R, 0);
+
+ rsnd_mod_write(mod, CTU_CTUIR, 0);
+}
+
static int rsnd_ctu_init(struct rsnd_mod *mod,
struct rsnd_dai_stream *io,
struct rsnd_priv *priv)
{
rsnd_mod_power_on(mod);
- rsnd_ctu_initialize_lock(mod);
+ rsnd_ctu_activation(mod);
- rsnd_mod_write(mod, CTU_ADINR, rsnd_get_adinr_chan(mod, io));
-
- rsnd_ctu_initialize_unlock(mod);
+ rsnd_ctu_value_init(io, mod);
return 0;
}
@@ -57,6 +109,8 @@
struct rsnd_dai_stream *io,
struct rsnd_priv *priv)
{
+ rsnd_ctu_halt(mod);
+
rsnd_mod_power_off(mod);
return 0;
@@ -129,7 +183,7 @@
}
ret = rsnd_mod_init(priv, rsnd_mod_get(ctu), &rsnd_ctu_ops,
- clk, RSND_MOD_CTU, i);
+ clk, rsnd_mod_get_status, RSND_MOD_CTU, i);
if (ret)
goto rsnd_ctu_probe_done;
diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c
index 418e6fd..7658e8f 100644
--- a/sound/soc/sh/rcar/dma.c
+++ b/sound/soc/sh/rcar/dma.c
@@ -622,15 +622,13 @@
}
}
-struct rsnd_mod *rsnd_dma_attach(struct rsnd_dai_stream *io,
- struct rsnd_mod *mod, int id)
+int rsnd_dma_attach(struct rsnd_dai_stream *io, struct rsnd_mod *mod,
+ struct rsnd_mod **dma_mod, int id)
{
- struct rsnd_mod *dma_mod;
struct rsnd_mod *mod_from = NULL;
struct rsnd_mod *mod_to = NULL;
struct rsnd_priv *priv = rsnd_io_to_priv(io);
struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv);
- struct rsnd_dma *dma;
struct device *dev = rsnd_priv_to_dev(priv);
struct rsnd_mod_ops *ops;
enum rsnd_mod_type type;
@@ -646,17 +644,10 @@
* rsnd_rdai_continuance_probe()
*/
if (!dmac)
- return ERR_PTR(-EAGAIN);
-
- dma = devm_kzalloc(dev, sizeof(*dma), GFP_KERNEL);
- if (!dma)
- return ERR_PTR(-ENOMEM);
+ return -EAGAIN;
rsnd_dma_of_path(mod, io, is_play, &mod_from, &mod_to);
- dma->src_addr = rsnd_dma_addr(io, mod_from, is_play, 1);
- dma->dst_addr = rsnd_dma_addr(io, mod_to, is_play, 0);
-
/* for Gen2 */
if (mod_from && mod_to) {
ops = &rsnd_dmapp_ops;
@@ -678,27 +669,38 @@
type = RSND_MOD_AUDMA;
}
- dma_mod = rsnd_mod_get(dma);
+ if (!(*dma_mod)) {
+ struct rsnd_dma *dma;
- ret = rsnd_mod_init(priv, dma_mod,
- ops, NULL, type, dma_id);
+ dma = devm_kzalloc(dev, sizeof(*dma), GFP_KERNEL);
+ if (!dma)
+ return -ENOMEM;
+
+ *dma_mod = rsnd_mod_get(dma);
+
+ dma->src_addr = rsnd_dma_addr(io, mod_from, is_play, 1);
+ dma->dst_addr = rsnd_dma_addr(io, mod_to, is_play, 0);
+
+ ret = rsnd_mod_init(priv, *dma_mod, ops, NULL,
+ rsnd_mod_get_status, type, dma_id);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "%s[%d] %s[%d] -> %s[%d]\n",
+ rsnd_mod_name(*dma_mod), rsnd_mod_id(*dma_mod),
+ rsnd_mod_name(mod_from), rsnd_mod_id(mod_from),
+ rsnd_mod_name(mod_to), rsnd_mod_id(mod_to));
+
+ ret = attach(io, dma, id, mod_from, mod_to);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = rsnd_dai_connect(*dma_mod, io, type);
if (ret < 0)
- return ERR_PTR(ret);
+ return ret;
- dev_dbg(dev, "%s[%d] %s[%d] -> %s[%d]\n",
- rsnd_mod_name(dma_mod), rsnd_mod_id(dma_mod),
- rsnd_mod_name(mod_from), rsnd_mod_id(mod_from),
- rsnd_mod_name(mod_to), rsnd_mod_id(mod_to));
-
- ret = attach(io, dma, id, mod_from, mod_to);
- if (ret < 0)
- return ERR_PTR(ret);
-
- ret = rsnd_dai_connect(dma_mod, io, type);
- if (ret < 0)
- return ERR_PTR(ret);
-
- return rsnd_mod_get(dma);
+ return 0;
}
int rsnd_dma_probe(struct rsnd_priv *priv)
diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c
index d45ffe4..d757f13 100644
--- a/sound/soc/sh/rcar/dvc.c
+++ b/sound/soc/sh/rcar/dvc.c
@@ -83,15 +83,15 @@
struct rsnd_mod *mod)
{
struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod);
- u32 val[RSND_DVC_CHANNELS];
+ u32 val[RSND_MAX_CHANNELS];
int i;
/* Enable Ramp */
if (dvc->ren.val)
- for (i = 0; i < RSND_DVC_CHANNELS; i++)
+ for (i = 0; i < RSND_MAX_CHANNELS; i++)
val[i] = dvc->volume.cfg.max;
else
- for (i = 0; i < RSND_DVC_CHANNELS; i++)
+ for (i = 0; i < RSND_MAX_CHANNELS; i++)
val[i] = dvc->volume.val[i];
/* Enable Digital Volume */
@@ -373,7 +373,7 @@
}
ret = rsnd_mod_init(priv, rsnd_mod_get(dvc), &rsnd_dvc_ops,
- clk, RSND_MOD_DVC, i);
+ clk, rsnd_mod_get_status, RSND_MOD_DVC, i);
if (ret)
goto rsnd_dvc_probe_done;
diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c
index ea24247..46c0ba7 100644
--- a/sound/soc/sh/rcar/gen.c
+++ b/sound/soc/sh/rcar/gen.c
@@ -104,23 +104,6 @@
if (!rsnd_is_accessible_reg(priv, gen, reg))
return;
- regmap_fields_write(gen->regs[reg], rsnd_mod_id(mod), data);
-
- dev_dbg(dev, "w %s[%d] - %-18s (%4d) : %08x\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod),
- rsnd_reg_name(gen, reg), reg, data);
-}
-
-void rsnd_force_write(struct rsnd_priv *priv,
- struct rsnd_mod *mod,
- enum rsnd_reg reg, u32 data)
-{
- struct device *dev = rsnd_priv_to_dev(priv);
- struct rsnd_gen *gen = rsnd_priv_to_gen(priv);
-
- if (!rsnd_is_accessible_reg(priv, gen, reg))
- return;
-
regmap_fields_force_write(gen->regs[reg], rsnd_mod_id(mod), data);
dev_dbg(dev, "w %s[%d] - %-18s (%4d) : %08x\n",
@@ -137,8 +120,8 @@
if (!rsnd_is_accessible_reg(priv, gen, reg))
return;
- regmap_fields_update_bits(gen->regs[reg], rsnd_mod_id(mod),
- mask, data);
+ regmap_fields_force_update_bits(gen->regs[reg],
+ rsnd_mod_id(mod), mask, data);
dev_dbg(dev, "b %s[%d] - %-18s (%4d) : %08x/%08x\n",
rsnd_mod_name(mod), rsnd_mod_id(mod),
@@ -260,8 +243,43 @@
RSND_GEN_M_REG(SRC_SRCCR, 0x224, 0x40),
RSND_GEN_M_REG(SRC_BSDSR, 0x22c, 0x40),
RSND_GEN_M_REG(SRC_BSISR, 0x238, 0x40),
+ RSND_GEN_M_REG(CTU_SWRSR, 0x500, 0x100),
RSND_GEN_M_REG(CTU_CTUIR, 0x504, 0x100),
RSND_GEN_M_REG(CTU_ADINR, 0x508, 0x100),
+ RSND_GEN_M_REG(CTU_CPMDR, 0x510, 0x100),
+ RSND_GEN_M_REG(CTU_SCMDR, 0x514, 0x100),
+ RSND_GEN_M_REG(CTU_SV00R, 0x518, 0x100),
+ RSND_GEN_M_REG(CTU_SV01R, 0x51c, 0x100),
+ RSND_GEN_M_REG(CTU_SV02R, 0x520, 0x100),
+ RSND_GEN_M_REG(CTU_SV03R, 0x524, 0x100),
+ RSND_GEN_M_REG(CTU_SV04R, 0x528, 0x100),
+ RSND_GEN_M_REG(CTU_SV05R, 0x52c, 0x100),
+ RSND_GEN_M_REG(CTU_SV06R, 0x530, 0x100),
+ RSND_GEN_M_REG(CTU_SV07R, 0x534, 0x100),
+ RSND_GEN_M_REG(CTU_SV10R, 0x538, 0x100),
+ RSND_GEN_M_REG(CTU_SV11R, 0x53c, 0x100),
+ RSND_GEN_M_REG(CTU_SV12R, 0x540, 0x100),
+ RSND_GEN_M_REG(CTU_SV13R, 0x544, 0x100),
+ RSND_GEN_M_REG(CTU_SV14R, 0x548, 0x100),
+ RSND_GEN_M_REG(CTU_SV15R, 0x54c, 0x100),
+ RSND_GEN_M_REG(CTU_SV16R, 0x550, 0x100),
+ RSND_GEN_M_REG(CTU_SV17R, 0x554, 0x100),
+ RSND_GEN_M_REG(CTU_SV20R, 0x558, 0x100),
+ RSND_GEN_M_REG(CTU_SV21R, 0x55c, 0x100),
+ RSND_GEN_M_REG(CTU_SV22R, 0x560, 0x100),
+ RSND_GEN_M_REG(CTU_SV23R, 0x564, 0x100),
+ RSND_GEN_M_REG(CTU_SV24R, 0x568, 0x100),
+ RSND_GEN_M_REG(CTU_SV25R, 0x56c, 0x100),
+ RSND_GEN_M_REG(CTU_SV26R, 0x570, 0x100),
+ RSND_GEN_M_REG(CTU_SV27R, 0x574, 0x100),
+ RSND_GEN_M_REG(CTU_SV30R, 0x578, 0x100),
+ RSND_GEN_M_REG(CTU_SV31R, 0x57c, 0x100),
+ RSND_GEN_M_REG(CTU_SV32R, 0x580, 0x100),
+ RSND_GEN_M_REG(CTU_SV33R, 0x584, 0x100),
+ RSND_GEN_M_REG(CTU_SV34R, 0x588, 0x100),
+ RSND_GEN_M_REG(CTU_SV35R, 0x58c, 0x100),
+ RSND_GEN_M_REG(CTU_SV36R, 0x590, 0x100),
+ RSND_GEN_M_REG(CTU_SV37R, 0x594, 0x100),
RSND_GEN_M_REG(MIX_SWRSR, 0xd00, 0x40),
RSND_GEN_M_REG(MIX_MIXIR, 0xd04, 0x40),
RSND_GEN_M_REG(MIX_ADINR, 0xd08, 0x40),
diff --git a/sound/soc/sh/rcar/mix.c b/sound/soc/sh/rcar/mix.c
index 65542b6..e0e337a 100644
--- a/sound/soc/sh/rcar/mix.c
+++ b/sound/soc/sh/rcar/mix.c
@@ -172,7 +172,7 @@
}
ret = rsnd_mod_init(priv, rsnd_mod_get(mix), &rsnd_mix_ops,
- clk, RSND_MOD_MIX, i);
+ clk, rsnd_mod_get_status, RSND_MOD_MIX, i);
if (ret)
goto rsnd_mix_probe_done;
diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h
index 317dd79..305cc08 100644
--- a/sound/soc/sh/rcar/rsnd.h
+++ b/sound/soc/sh/rcar/rsnd.h
@@ -86,8 +86,43 @@
RSND_REG_CMD_BUSIF_DALIGN, /* Gen2 only */
RSND_REG_CMD_ROUTE_SLCT,
RSND_REG_CMDOUT_TIMSEL, /* Gen2 only */
+ RSND_REG_CTU_SWRSR,
RSND_REG_CTU_CTUIR,
RSND_REG_CTU_ADINR,
+ RSND_REG_CTU_CPMDR,
+ RSND_REG_CTU_SCMDR,
+ RSND_REG_CTU_SV00R,
+ RSND_REG_CTU_SV01R,
+ RSND_REG_CTU_SV02R,
+ RSND_REG_CTU_SV03R,
+ RSND_REG_CTU_SV04R,
+ RSND_REG_CTU_SV05R,
+ RSND_REG_CTU_SV06R,
+ RSND_REG_CTU_SV07R,
+ RSND_REG_CTU_SV10R,
+ RSND_REG_CTU_SV11R,
+ RSND_REG_CTU_SV12R,
+ RSND_REG_CTU_SV13R,
+ RSND_REG_CTU_SV14R,
+ RSND_REG_CTU_SV15R,
+ RSND_REG_CTU_SV16R,
+ RSND_REG_CTU_SV17R,
+ RSND_REG_CTU_SV20R,
+ RSND_REG_CTU_SV21R,
+ RSND_REG_CTU_SV22R,
+ RSND_REG_CTU_SV23R,
+ RSND_REG_CTU_SV24R,
+ RSND_REG_CTU_SV25R,
+ RSND_REG_CTU_SV26R,
+ RSND_REG_CTU_SV27R,
+ RSND_REG_CTU_SV30R,
+ RSND_REG_CTU_SV31R,
+ RSND_REG_CTU_SV32R,
+ RSND_REG_CTU_SV33R,
+ RSND_REG_CTU_SV34R,
+ RSND_REG_CTU_SV35R,
+ RSND_REG_CTU_SV36R,
+ RSND_REG_CTU_SV37R,
RSND_REG_MIX_SWRSR,
RSND_REG_MIX_MIXIR,
RSND_REG_MIX_ADINR,
@@ -147,8 +182,6 @@
rsnd_read(rsnd_mod_to_priv(m), m, RSND_REG_##r)
#define rsnd_mod_write(m, r, d) \
rsnd_write(rsnd_mod_to_priv(m), m, RSND_REG_##r, d)
-#define rsnd_mod_force_write(m, r, d) \
- rsnd_force_write(rsnd_mod_to_priv(m), m, RSND_REG_##r, d)
#define rsnd_mod_bset(m, r, s, d) \
rsnd_bset(rsnd_mod_to_priv(m), m, RSND_REG_##r, s, d)
@@ -166,8 +199,8 @@
/*
* R-Car DMA
*/
-struct rsnd_mod *rsnd_dma_attach(struct rsnd_dai_stream *io,
- struct rsnd_mod *mod, int id);
+int rsnd_dma_attach(struct rsnd_dai_stream *io,
+ struct rsnd_mod *mod, struct rsnd_mod **dma_mod, int id);
int rsnd_dma_probe(struct rsnd_priv *priv);
struct dma_chan *rsnd_dma_request_channel(struct device_node *of_node,
struct rsnd_mod *mod, char *name);
@@ -214,6 +247,9 @@
int (*stop)(struct rsnd_mod *mod,
struct rsnd_dai_stream *io,
struct rsnd_priv *priv);
+ int (*irq)(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct rsnd_priv *priv, int enable);
int (*pcm_new)(struct rsnd_mod *mod,
struct rsnd_dai_stream *io,
struct snd_soc_pcm_runtime *rtd);
@@ -233,47 +269,54 @@
struct rsnd_mod_ops *ops;
struct rsnd_priv *priv;
struct clk *clk;
+ u32 *(*get_status)(struct rsnd_dai_stream *io,
+ struct rsnd_mod *mod,
+ enum rsnd_mod_type type);
+ u32 status;
};
/*
* status
*
- * 0xH0000CBA
+ * 0xH0000CB0
*
- * A 0: probe 1: remove
* B 0: init 1: quit
* C 0: start 1: stop
*
* H is always called (see __rsnd_mod_call)
+ * H 0: probe 1: remove
* H 0: pcm_new
* H 0: fallback
* H 0: hw_params
*/
-#define __rsnd_mod_shift_probe 0
-#define __rsnd_mod_shift_remove 0
#define __rsnd_mod_shift_init 4
#define __rsnd_mod_shift_quit 4
#define __rsnd_mod_shift_start 8
#define __rsnd_mod_shift_stop 8
+#define __rsnd_mod_shift_probe 28 /* always called */
+#define __rsnd_mod_shift_remove 28 /* always called */
+#define __rsnd_mod_shift_irq 28 /* always called */
#define __rsnd_mod_shift_pcm_new 28 /* always called */
#define __rsnd_mod_shift_fallback 28 /* always called */
#define __rsnd_mod_shift_hw_params 28 /* always called */
-#define __rsnd_mod_add_probe 1
-#define __rsnd_mod_add_remove -1
+#define __rsnd_mod_add_probe 0
+#define __rsnd_mod_add_remove 0
#define __rsnd_mod_add_init 1
#define __rsnd_mod_add_quit -1
#define __rsnd_mod_add_start 1
#define __rsnd_mod_add_stop -1
+#define __rsnd_mod_add_irq 0
#define __rsnd_mod_add_pcm_new 0
#define __rsnd_mod_add_fallback 0
#define __rsnd_mod_add_hw_params 0
#define __rsnd_mod_call_probe 0
-#define __rsnd_mod_call_remove 1
+#define __rsnd_mod_call_remove 0
#define __rsnd_mod_call_init 0
#define __rsnd_mod_call_quit 1
#define __rsnd_mod_call_start 0
#define __rsnd_mod_call_stop 1
+#define __rsnd_mod_call_irq 0
#define __rsnd_mod_call_pcm_new 0
#define __rsnd_mod_call_fallback 0
#define __rsnd_mod_call_hw_params 0
@@ -286,10 +329,13 @@
int rsnd_mod_init(struct rsnd_priv *priv,
struct rsnd_mod *mod,
- struct rsnd_mod_ops *ops,
- struct clk *clk,
- enum rsnd_mod_type type,
- int id);
+ struct rsnd_mod_ops *ops,
+ struct clk *clk,
+ u32* (*get_status)(struct rsnd_dai_stream *io,
+ struct rsnd_mod *mod,
+ enum rsnd_mod_type type),
+ enum rsnd_mod_type type,
+ int id);
void rsnd_mod_quit(struct rsnd_mod *mod);
char *rsnd_mod_name(struct rsnd_mod *mod);
struct dma_chan *rsnd_mod_dma_req(struct rsnd_dai_stream *io,
@@ -297,6 +343,10 @@
void rsnd_mod_interrupt(struct rsnd_mod *mod,
void (*callback)(struct rsnd_mod *mod,
struct rsnd_dai_stream *io));
+u32 *rsnd_mod_get_status(struct rsnd_dai_stream *io,
+ struct rsnd_mod *mod,
+ enum rsnd_mod_type type);
+
void rsnd_parse_connect_common(struct rsnd_dai *rdai,
struct rsnd_mod* (*mod_get)(struct rsnd_priv *priv, int id),
struct device_node *node,
@@ -319,7 +369,7 @@
struct rsnd_mod *mod[RSND_MOD_MAX];
struct rsnd_dai_path_info *info; /* rcar_snd.h */
struct rsnd_dai *rdai;
- u32 mod_status[RSND_MOD_MAX];
+ u32 parent_ssi_status;
int byte_pos;
int period_pos;
int byte_per_period;
@@ -498,10 +548,10 @@
struct snd_kcontrol *kctrl;
};
-#define RSND_DVC_CHANNELS 8
+#define RSND_MAX_CHANNELS 8
struct rsnd_kctrl_cfg_m {
struct rsnd_kctrl_cfg cfg;
- u32 val[RSND_DVC_CHANNELS];
+ u32 val[RSND_MAX_CHANNELS];
};
struct rsnd_kctrl_cfg_s {
@@ -547,7 +597,7 @@
struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id);
int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod);
int rsnd_ssi_use_busif(struct rsnd_dai_stream *io);
-u32 rsnd_ssi_multi_slaves(struct rsnd_dai_stream *io);
+u32 rsnd_ssi_multi_slaves_runtime(struct rsnd_dai_stream *io);
#define rsnd_ssi_is_pin_sharing(io) \
__rsnd_ssi_is_pin_sharing(rsnd_io_to_mod_ssi(io))
diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c
index 5eda056..03c6314 100644
--- a/sound/soc/sh/rcar/src.c
+++ b/sound/soc/sh/rcar/src.c
@@ -25,7 +25,6 @@
struct rsnd_kctrl_cfg_s sen; /* sync convert enable */
struct rsnd_kctrl_cfg_s sync; /* sync convert */
u32 convert_rate; /* sampling rate convert */
- int err;
int irq;
};
@@ -250,6 +249,8 @@
break;
}
+ rsnd_mod_write(mod, SRC_ROUTE_MODE0, route);
+
rsnd_mod_write(mod, SRC_SRCIR, 1); /* initialize */
rsnd_mod_write(mod, SRC_ADINR, adinr);
rsnd_mod_write(mod, SRC_IFSCR, ifscr);
@@ -259,7 +260,6 @@
rsnd_mod_write(mod, SRC_BSISR, bsisr);
rsnd_mod_write(mod, SRC_SRCIR, 0); /* cancel initialize */
- rsnd_mod_write(mod, SRC_ROUTE_MODE0, route);
rsnd_mod_write(mod, SRC_I_BUSIF_MODE, 1);
rsnd_mod_write(mod, SRC_O_BUSIF_MODE, 1);
rsnd_mod_write(mod, SRC_BUSIF_DALIGN, rsnd_get_dalign(mod, io));
@@ -272,9 +272,10 @@
rsnd_adg_set_convert_timing_gen2(mod, io);
}
-#define rsnd_src_irq_enable(mod) rsnd_src_irq_ctrol(mod, 1)
-#define rsnd_src_irq_disable(mod) rsnd_src_irq_ctrol(mod, 0)
-static void rsnd_src_irq_ctrol(struct rsnd_mod *mod, int enable)
+static int rsnd_src_irq(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct rsnd_priv *priv,
+ int enable)
{
struct rsnd_src *src = rsnd_mod_to_src(mod);
u32 sys_int_val, int_val, sys_int_mask;
@@ -306,6 +307,8 @@
rsnd_mod_write(mod, SRC_INT_ENABLE0, int_val);
rsnd_mod_bset(mod, SCU_SYS_INT_EN0, sys_int_mask, sys_int_val);
rsnd_mod_bset(mod, SCU_SYS_INT_EN1, sys_int_mask, sys_int_val);
+
+ return 0;
}
static void rsnd_src_status_clear(struct rsnd_mod *mod)
@@ -316,7 +319,7 @@
rsnd_mod_bset(mod, SCU_SYS_STATUS1, val, val);
}
-static bool rsnd_src_record_error(struct rsnd_mod *mod)
+static bool rsnd_src_error_occurred(struct rsnd_mod *mod)
{
struct rsnd_src *src = rsnd_mod_to_src(mod);
u32 val0, val1;
@@ -333,12 +336,8 @@
val0 = val0 & 0xffff;
if ((rsnd_mod_read(mod, SCU_SYS_STATUS0) & val0) ||
- (rsnd_mod_read(mod, SCU_SYS_STATUS1) & val1)) {
- struct rsnd_src *src = rsnd_mod_to_src(mod);
-
- src->err++;
+ (rsnd_mod_read(mod, SCU_SYS_STATUS1) & val1))
ret = true;
- }
return ret;
}
@@ -367,11 +366,7 @@
struct rsnd_dai_stream *io,
struct rsnd_priv *priv)
{
- /*
- * stop SRC output only
- * see rsnd_src_quit
- */
- rsnd_mod_write(mod, SRC_CTRL, 0x01);
+ rsnd_mod_write(mod, SRC_CTRL, 0);
return 0;
}
@@ -390,10 +385,6 @@
rsnd_src_status_clear(mod);
- rsnd_src_irq_enable(mod);
-
- src->err = 0;
-
/* reset sync convert_rate */
src->sync.val = 0;
@@ -405,21 +396,11 @@
struct rsnd_priv *priv)
{
struct rsnd_src *src = rsnd_mod_to_src(mod);
- struct device *dev = rsnd_priv_to_dev(priv);
-
- rsnd_src_irq_disable(mod);
-
- /* stop both out/in */
- rsnd_mod_write(mod, SRC_CTRL, 0);
rsnd_src_halt(mod);
rsnd_mod_power_off(mod);
- if (src->err)
- dev_warn(dev, "%s[%d] under/over flow err = %d\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod), src->err);
-
src->convert_rate = 0;
/* reset sync convert_rate */
@@ -432,8 +413,7 @@
struct rsnd_dai_stream *io)
{
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
- struct rsnd_src *src = rsnd_mod_to_src(mod);
- struct device *dev = rsnd_priv_to_dev(priv);
+ bool stop = false;
spin_lock(&priv->lock);
@@ -441,26 +421,16 @@
if (!rsnd_io_is_working(io))
goto rsnd_src_interrupt_out;
- if (rsnd_src_record_error(mod)) {
-
- dev_dbg(dev, "%s[%d] restart\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod));
-
- rsnd_src_stop(mod, io, priv);
- rsnd_src_start(mod, io, priv);
- }
-
- if (src->err > 1024) {
- rsnd_src_irq_disable(mod);
-
- dev_warn(dev, "no more %s[%d] restart\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod));
- }
+ if (rsnd_src_error_occurred(mod))
+ stop = true;
rsnd_src_status_clear(mod);
rsnd_src_interrupt_out:
spin_unlock(&priv->lock);
+
+ if (stop)
+ snd_pcm_stop_xrun(io->substream);
}
static irqreturn_t rsnd_src_interrupt(int irq, void *data)
@@ -485,7 +455,7 @@
/*
* IRQ is not supported on non-DT
* see
- * rsnd_src_irq_enable()
+ * rsnd_src_irq()
*/
ret = devm_request_irq(dev, irq,
rsnd_src_interrupt,
@@ -495,9 +465,7 @@
return ret;
}
- src->dma = rsnd_dma_attach(io, mod, 0);
- if (IS_ERR(src->dma))
- return PTR_ERR(src->dma);
+ ret = rsnd_dma_attach(io, mod, &src->dma, 0);
return ret;
}
@@ -557,6 +525,7 @@
.quit = rsnd_src_quit,
.start = rsnd_src_start,
.stop = rsnd_src_stop,
+ .irq = rsnd_src_irq,
.hw_params = rsnd_src_hw_params,
.pcm_new = rsnd_src_pcm_new,
};
@@ -622,7 +591,8 @@
}
ret = rsnd_mod_init(priv, rsnd_mod_get(src),
- &rsnd_src_ops, clk, RSND_MOD_SRC, i);
+ &rsnd_src_ops, clk, rsnd_mod_get_status,
+ RSND_MOD_SRC, i);
if (ret)
goto rsnd_src_probe_done;
diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c
index 7ee89da..0979db8 100644
--- a/sound/soc/sh/rcar/ssi.c
+++ b/sound/soc/sh/rcar/ssi.c
@@ -64,7 +64,6 @@
#define SSI_NAME "ssi"
struct rsnd_ssi {
- struct rsnd_ssi *parent;
struct rsnd_mod mod;
struct rsnd_mod *dma;
@@ -75,7 +74,6 @@
u32 wsr;
int chan;
int rate;
- int err;
int irq;
unsigned int usrcnt;
};
@@ -96,7 +94,10 @@
#define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod)
#define rsnd_ssi_mode_flags(p) ((p)->flags)
#define rsnd_ssi_is_parent(ssi, io) ((ssi) == rsnd_io_to_mod_ssip(io))
-#define rsnd_ssi_is_multi_slave(ssi, io) ((mod) != rsnd_io_to_mod_ssi(io))
+#define rsnd_ssi_is_multi_slave(mod, io) \
+ (rsnd_ssi_multi_slaves(io) & (1 << rsnd_mod_id(mod)))
+#define rsnd_ssi_is_run_mods(mod, io) \
+ (rsnd_ssi_run_mods(io) & (1 << rsnd_mod_id(mod)))
int rsnd_ssi_use_busif(struct rsnd_dai_stream *io)
{
@@ -141,43 +142,13 @@
udelay(50);
}
- dev_warn(dev, "status check failed\n");
+ dev_warn(dev, "%s[%d] status check failed\n",
+ rsnd_mod_name(mod), rsnd_mod_id(mod));
}
-static int rsnd_ssi_irq_enable(struct rsnd_mod *ssi_mod)
-{
- struct rsnd_priv *priv = rsnd_mod_to_priv(ssi_mod);
-
- if (rsnd_is_gen1(priv))
- return 0;
-
- /* enable SSI interrupt if Gen2 */
- rsnd_mod_write(ssi_mod, SSI_INT_ENABLE,
- rsnd_ssi_is_dma_mode(ssi_mod) ?
- 0x0e000000 : 0x0f000000);
-
- return 0;
-}
-
-static int rsnd_ssi_irq_disable(struct rsnd_mod *ssi_mod)
-{
- struct rsnd_priv *priv = rsnd_mod_to_priv(ssi_mod);
-
- if (rsnd_is_gen1(priv))
- return 0;
-
- /* disable SSI interrupt if Gen2 */
- rsnd_mod_write(ssi_mod, SSI_INT_ENABLE, 0x00000000);
-
- return 0;
-}
-
-u32 rsnd_ssi_multi_slaves(struct rsnd_dai_stream *io)
+static u32 rsnd_ssi_multi_slaves(struct rsnd_dai_stream *io)
{
struct rsnd_mod *mod;
- struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
- struct rsnd_priv *priv = rsnd_io_to_priv(io);
- struct device *dev = rsnd_priv_to_dev(priv);
enum rsnd_mod_type types[] = {
RSND_MOD_SSIM1,
RSND_MOD_SSIM2,
@@ -185,16 +156,6 @@
};
int i, mask;
- switch (runtime->channels) {
- case 2: /* Multi channel is not needed for Stereo */
- return 0;
- case 6:
- break;
- default:
- dev_err(dev, "unsupported channel\n");
- return 0;
- }
-
mask = 0;
for (i = 0; i < ARRAY_SIZE(types); i++) {
mod = rsnd_io_to_mod(io, types[i]);
@@ -207,14 +168,35 @@
return mask;
}
-static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi,
+static u32 rsnd_ssi_run_mods(struct rsnd_dai_stream *io)
+{
+ struct rsnd_mod *ssi_mod = rsnd_io_to_mod_ssi(io);
+ struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io);
+
+ return rsnd_ssi_multi_slaves_runtime(io) |
+ 1 << rsnd_mod_id(ssi_mod) |
+ 1 << rsnd_mod_id(ssi_parent_mod);
+}
+
+u32 rsnd_ssi_multi_slaves_runtime(struct rsnd_dai_stream *io)
+{
+ struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+ u32 mask = rsnd_ssi_multi_slaves(io);
+
+ if (mask && (runtime->channels >= 6))
+ return mask;
+
+ return 0;
+}
+
+static int rsnd_ssi_master_clk_start(struct rsnd_mod *mod,
struct rsnd_dai_stream *io)
{
struct rsnd_priv *priv = rsnd_io_to_priv(io);
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
struct device *dev = rsnd_priv_to_dev(priv);
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
- struct rsnd_mod *mod = rsnd_mod_get(ssi);
+ struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io);
int slots = rsnd_get_slot_width(io);
int j, ret;
@@ -274,11 +256,11 @@
return -EIO;
}
-static void rsnd_ssi_master_clk_stop(struct rsnd_ssi *ssi,
+static void rsnd_ssi_master_clk_stop(struct rsnd_mod *mod,
struct rsnd_dai_stream *io)
{
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
- struct rsnd_mod *mod = rsnd_mod_get(ssi);
+ struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io);
if (!rsnd_rdai_is_clk_master(rdai))
@@ -296,11 +278,12 @@
rsnd_adg_ssi_clk_stop(mod);
}
-static int rsnd_ssi_config_init(struct rsnd_ssi *ssi,
+static void rsnd_ssi_config_init(struct rsnd_mod *mod,
struct rsnd_dai_stream *io)
{
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
+ struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
u32 cr_own;
u32 cr_mode;
u32 wsr;
@@ -332,11 +315,9 @@
case 32:
cr_own |= DWL_24;
break;
- default:
- return -EINVAL;
}
- if (rsnd_ssi_is_dma_mode(rsnd_mod_get(ssi))) {
+ if (rsnd_ssi_is_dma_mode(mod)) {
cr_mode = UIEN | OIEN | /* over/under run */
DMEN; /* DMA : enable DMA */
} else {
@@ -357,8 +338,16 @@
ssi->cr_own = cr_own;
ssi->cr_mode = cr_mode;
ssi->wsr = wsr;
+}
- return 0;
+static void rsnd_ssi_register_setup(struct rsnd_mod *mod)
+{
+ struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+
+ rsnd_mod_write(mod, SSIWSR, ssi->wsr);
+ rsnd_mod_write(mod, SSICR, ssi->cr_own |
+ ssi->cr_clk |
+ ssi->cr_mode); /* without EN */
}
/*
@@ -371,28 +360,25 @@
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
int ret;
+ if (!rsnd_ssi_is_run_mods(mod, io))
+ return 0;
+
ssi->usrcnt++;
rsnd_mod_power_on(mod);
- ret = rsnd_ssi_master_clk_start(ssi, io);
+ ret = rsnd_ssi_master_clk_start(mod, io);
if (ret < 0)
return ret;
- if (rsnd_ssi_is_parent(mod, io))
- return 0;
+ if (!rsnd_ssi_is_parent(mod, io))
+ rsnd_ssi_config_init(mod, io);
- ret = rsnd_ssi_config_init(ssi, io);
- if (ret < 0)
- return ret;
-
- ssi->err = -1; /* ignore 1st error */
+ rsnd_ssi_register_setup(mod);
/* clear error status */
rsnd_ssi_status_clear(mod);
- rsnd_ssi_irq_enable(mod);
-
return 0;
}
@@ -403,25 +389,19 @@
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
struct device *dev = rsnd_priv_to_dev(priv);
+ if (!rsnd_ssi_is_run_mods(mod, io))
+ return 0;
+
if (!ssi->usrcnt) {
dev_err(dev, "%s[%d] usrcnt error\n",
rsnd_mod_name(mod), rsnd_mod_id(mod));
return -EIO;
}
- if (!rsnd_ssi_is_parent(mod, io)) {
- if (ssi->err > 0)
- dev_warn(dev, "%s[%d] under/over flow err = %d\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod),
- ssi->err);
-
+ if (!rsnd_ssi_is_parent(mod, io))
ssi->cr_own = 0;
- ssi->err = 0;
- rsnd_ssi_irq_disable(mod);
- }
-
- rsnd_ssi_master_clk_stop(ssi, io);
+ rsnd_ssi_master_clk_stop(mod, io);
rsnd_mod_power_off(mod);
@@ -456,62 +436,44 @@
return 0;
}
-static u32 rsnd_ssi_record_error(struct rsnd_ssi *ssi)
+static int rsnd_ssi_start(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct rsnd_priv *priv)
{
- struct rsnd_mod *mod = rsnd_mod_get(ssi);
- u32 status = rsnd_ssi_status_get(mod);
-
- /* under/over flow error */
- if (status & (UIRQ | OIRQ))
- ssi->err++;
-
- return status;
-}
-
-static int __rsnd_ssi_start(struct rsnd_mod *mod,
- struct rsnd_dai_stream *io,
- struct rsnd_priv *priv)
-{
- struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
- u32 cr;
-
- cr = ssi->cr_own |
- ssi->cr_clk |
- ssi->cr_mode;
+ if (!rsnd_ssi_is_run_mods(mod, io))
+ return 0;
/*
* EN will be set via SSIU :: SSI_CONTROL
* if Multi channel mode
*/
- if (!rsnd_ssi_multi_slaves(io))
- cr |= EN;
+ if (rsnd_ssi_multi_slaves_runtime(io))
+ return 0;
- rsnd_mod_write(mod, SSICR, cr);
- rsnd_mod_write(mod, SSIWSR, ssi->wsr);
+ rsnd_mod_bset(mod, SSICR, EN, EN);
return 0;
}
-static int rsnd_ssi_start(struct rsnd_mod *mod,
- struct rsnd_dai_stream *io,
- struct rsnd_priv *priv)
-{
- /*
- * no limit to start
- * see also
- * rsnd_ssi_stop
- * rsnd_ssi_interrupt
- */
- return __rsnd_ssi_start(mod, io, priv);
-}
-
-static int __rsnd_ssi_stop(struct rsnd_mod *mod,
- struct rsnd_dai_stream *io,
- struct rsnd_priv *priv)
+static int rsnd_ssi_stop(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct rsnd_priv *priv)
{
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
u32 cr;
+ if (!rsnd_ssi_is_run_mods(mod, io))
+ return 0;
+
+ /*
+ * don't stop if not last user
+ * see also
+ * rsnd_ssi_start
+ * rsnd_ssi_interrupt
+ */
+ if (ssi->usrcnt > 1)
+ return 0;
+
/*
* disable all IRQ,
* and, wait all data was sent
@@ -532,33 +494,38 @@
return 0;
}
-static int rsnd_ssi_stop(struct rsnd_mod *mod,
- struct rsnd_dai_stream *io,
- struct rsnd_priv *priv)
+static int rsnd_ssi_irq(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct rsnd_priv *priv,
+ int enable)
{
- struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+ u32 val = 0;
- /*
- * don't stop if not last user
- * see also
- * rsnd_ssi_start
- * rsnd_ssi_interrupt
- */
- if (ssi->usrcnt > 1)
+ if (rsnd_is_gen1(priv))
return 0;
- return __rsnd_ssi_stop(mod, io, priv);
+ if (rsnd_ssi_is_parent(mod, io))
+ return 0;
+
+ if (!rsnd_ssi_is_run_mods(mod, io))
+ return 0;
+
+ if (enable)
+ val = rsnd_ssi_is_dma_mode(mod) ? 0x0e000000 : 0x0f000000;
+
+ rsnd_mod_write(mod, SSI_INT_ENABLE, val);
+
+ return 0;
}
static void __rsnd_ssi_interrupt(struct rsnd_mod *mod,
struct rsnd_dai_stream *io)
{
- struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
- struct device *dev = rsnd_priv_to_dev(priv);
int is_dma = rsnd_ssi_is_dma_mode(mod);
u32 status;
bool elapsed = false;
+ bool stop = false;
spin_lock(&priv->lock);
@@ -566,7 +533,7 @@
if (!rsnd_io_is_working(io))
goto rsnd_ssi_interrupt_out;
- status = rsnd_ssi_record_error(ssi);
+ status = rsnd_ssi_status_get(mod);
/* PIO only */
if (!is_dma && (status & DIRQ)) {
@@ -588,23 +555,8 @@
}
/* DMA only */
- if (is_dma && (status & (UIRQ | OIRQ))) {
- /*
- * restart SSI
- */
- dev_dbg(dev, "%s[%d] restart\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod));
-
- __rsnd_ssi_stop(mod, io, priv);
- __rsnd_ssi_start(mod, io, priv);
- }
-
- if (ssi->err > 1024) {
- rsnd_ssi_irq_disable(mod);
-
- dev_warn(dev, "no more %s[%d] restart\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod));
- }
+ if (is_dma && (status & (UIRQ | OIRQ)))
+ stop = true;
rsnd_ssi_status_clear(mod);
rsnd_ssi_interrupt_out:
@@ -612,6 +564,10 @@
if (elapsed)
rsnd_dai_period_elapsed(io);
+
+ if (stop)
+ snd_pcm_stop_xrun(io->substream);
+
}
static irqreturn_t rsnd_ssi_interrupt(int irq, void *data)
@@ -627,12 +583,17 @@
* SSI PIO
*/
static void rsnd_ssi_parent_attach(struct rsnd_mod *mod,
- struct rsnd_dai_stream *io,
- struct rsnd_priv *priv)
+ struct rsnd_dai_stream *io)
{
+ struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
+ struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
+
if (!__rsnd_ssi_is_pin_sharing(mod))
return;
+ if (!rsnd_rdai_is_clk_master(rdai))
+ return;
+
switch (rsnd_mod_id(mod)) {
case 1:
case 2:
@@ -647,6 +608,20 @@
}
}
+static int rsnd_ssi_pcm_new(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ /*
+ * rsnd_rdai_is_clk_master() will be enabled after set_fmt,
+ * and, pcm_new will be called after it.
+ * This function reuse pcm_new at this point.
+ */
+ rsnd_ssi_parent_attach(mod, io);
+
+ return 0;
+}
+
static int rsnd_ssi_common_probe(struct rsnd_mod *mod,
struct rsnd_dai_stream *io,
struct rsnd_priv *priv)
@@ -662,7 +637,10 @@
if (rsnd_ssi_is_multi_slave(mod, io))
return 0;
- rsnd_ssi_parent_attach(mod, io, priv);
+ /*
+ * It can't judge ssi parent at this point
+ * see rsnd_ssi_pcm_new()
+ */
ret = rsnd_ssiu_attach(io, mod);
if (ret < 0)
@@ -683,6 +661,8 @@
.quit = rsnd_ssi_quit,
.start = rsnd_ssi_start,
.stop = rsnd_ssi_stop,
+ .irq = rsnd_ssi_irq,
+ .pcm_new = rsnd_ssi_pcm_new,
.hw_params = rsnd_ssi_hw_params,
};
@@ -705,9 +685,8 @@
if (ret)
return ret;
- ssi->dma = rsnd_dma_attach(io, mod, dma_id);
- if (IS_ERR(ssi->dma))
- return PTR_ERR(ssi->dma);
+ /* SSI probe might be called many times in MUX multi path */
+ ret = rsnd_dma_attach(io, mod, &ssi->dma, dma_id);
return ret;
}
@@ -772,6 +751,8 @@
.quit = rsnd_ssi_quit,
.start = rsnd_ssi_start,
.stop = rsnd_ssi_stop,
+ .irq = rsnd_ssi_irq,
+ .pcm_new = rsnd_ssi_pcm_new,
.fallback = rsnd_ssi_fallback,
.hw_params = rsnd_ssi_hw_params,
};
@@ -858,6 +839,41 @@
return !!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_CLK_PIN_SHARE);
}
+static u32 *rsnd_ssi_get_status(struct rsnd_dai_stream *io,
+ struct rsnd_mod *mod,
+ enum rsnd_mod_type type)
+{
+ /*
+ * SSIP (= SSI parent) needs to be special, otherwise,
+ * 2nd SSI might doesn't start. see also rsnd_mod_call()
+ *
+ * We can't include parent SSI status on SSI, because we don't know
+ * how many SSI requests parent SSI. Thus, it is localed on "io" now.
+ * ex) trouble case
+ * Playback: SSI0
+ * Capture : SSI1 (needs SSI0)
+ *
+ * 1) start Capture -> SSI0/SSI1 are started.
+ * 2) start Playback -> SSI0 doesn't work, because it is already
+ * marked as "started" on 1)
+ *
+ * OTOH, using each mod's status is good for MUX case.
+ * It doesn't need to start in 2nd start
+ * ex)
+ * IO-0: SRC0 -> CTU1 -+-> MUX -> DVC -> SSIU -> SSI0
+ * |
+ * IO-1: SRC1 -> CTU2 -+
+ *
+ * 1) start IO-0 -> start SSI0
+ * 2) start IO-1 -> SSI0 doesn't need to start, because it is
+ * already started on 1)
+ */
+ if (type == RSND_MOD_SSIP)
+ return &io->parent_ssi_status;
+
+ return rsnd_mod_get_status(io, mod, type);
+}
+
int rsnd_ssi_probe(struct rsnd_priv *priv)
{
struct device_node *node;
@@ -920,7 +936,7 @@
ops = &rsnd_ssi_dma_ops;
ret = rsnd_mod_init(priv, rsnd_mod_get(ssi), ops, clk,
- RSND_MOD_SSI, i);
+ rsnd_ssi_get_status, RSND_MOD_SSI, i);
if (ret)
goto rsnd_ssi_probe_done;
diff --git a/sound/soc/sh/rcar/ssiu.c b/sound/soc/sh/rcar/ssiu.c
index 06d7282..1b8ea0e 100644
--- a/sound/soc/sh/rcar/ssiu.c
+++ b/sound/soc/sh/rcar/ssiu.c
@@ -27,7 +27,7 @@
struct rsnd_priv *priv)
{
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
- u32 multi_ssi_slaves = rsnd_ssi_multi_slaves(io);
+ u32 multi_ssi_slaves = rsnd_ssi_multi_slaves_runtime(io);
int use_busif = rsnd_ssi_use_busif(io);
int id = rsnd_mod_id(mod);
u32 mask1, val1;
@@ -136,7 +136,7 @@
rsnd_mod_write(mod, SSI_CTRL, 0x1);
- if (rsnd_ssi_multi_slaves(io))
+ if (rsnd_ssi_multi_slaves_runtime(io))
rsnd_mod_write(mod, SSI_CONTROL, 0x1);
return 0;
@@ -151,7 +151,7 @@
rsnd_mod_write(mod, SSI_CTRL, 0);
- if (rsnd_ssi_multi_slaves(io))
+ if (rsnd_ssi_multi_slaves_runtime(io))
rsnd_mod_write(mod, SSI_CONTROL, 0);
return 0;
@@ -206,7 +206,8 @@
for_each_rsnd_ssiu(ssiu, priv, i) {
ret = rsnd_mod_init(priv, rsnd_mod_get(ssiu),
- ops, NULL, RSND_MOD_SSIU, i);
+ ops, NULL, rsnd_mod_get_status,
+ RSND_MOD_SSIU, i);
if (ret)
return ret;
}