2018-09-20 11:19:24

by Luis de Oliveira

[permalink] [raw]
Subject: [V2, 0/5] platform: dwc: Add of DesignWare MIPI CSI-2 Host

This adds support for Synopsys MIPI CSI-2 Host and MIPI D-PHY.
The patch series include support for initialization/configuration of the
DW MIPI CSI-2 controller and DW MIPI D-PHY and both include a reference
platform driver.

This will enable future SoCs to use this standard approach and possibly
create a more clean environment.

This series also documents the dt-bindings needed for the platform drivers.

This was applied in: https://git.linuxtv.org/media_tree.git

Luis Oliveira (5):
media: platform: Add a DesignWare folder to have Synopsys drivers
Documentation: dt-bindings: Document the Synopsys MIPI DPHY Rx
bindings
media: platform: dwc: Add DW MIPI DPHY Rx platform
Documentation: dt-bindings: Document bindings for DW MIPI CSI-2 Host
media: platform: dwc: Add MIPI CSI-2 controller driver

.../devicetree/bindings/media/snps,dw-csi-plat.txt | 74 +++
.../devicetree/bindings/phy/snps,dphy-rx.txt | 36 ++
MAINTAINERS | 11 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 3 +
drivers/media/platform/dwc/Kconfig | 43 ++
drivers/media/platform/dwc/Makefile | 4 +
drivers/media/platform/dwc/dw-csi-plat.c | 508 ++++++++++++++++++
drivers/media/platform/dwc/dw-csi-plat.h | 76 +++
drivers/media/platform/dwc/dw-dphy-plat.c | 365 +++++++++++++
drivers/media/platform/dwc/dw-dphy-rx.c | 594 +++++++++++++++++++++
drivers/media/platform/dwc/dw-dphy-rx.h | 176 ++++++
drivers/media/platform/dwc/dw-mipi-csi.c | 490 +++++++++++++++++
drivers/media/platform/dwc/dw-mipi-csi.h | 202 +++++++
include/media/dwc/dw-mipi-csi-pltfrm.h | 101 ++++
15 files changed, 2684 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
create mode 100644 Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
create mode 100644 drivers/media/platform/dwc/Kconfig
create mode 100644 drivers/media/platform/dwc/Makefile
create mode 100644 drivers/media/platform/dwc/dw-csi-plat.c
create mode 100644 drivers/media/platform/dwc/dw-csi-plat.h
create mode 100644 drivers/media/platform/dwc/dw-dphy-plat.c
create mode 100644 drivers/media/platform/dwc/dw-dphy-rx.c
create mode 100644 drivers/media/platform/dwc/dw-dphy-rx.h
create mode 100644 drivers/media/platform/dwc/dw-mipi-csi.c
create mode 100644 drivers/media/platform/dwc/dw-mipi-csi.h
create mode 100644 include/media/dwc/dw-mipi-csi-pltfrm.h

--
2.9.3



2018-09-20 11:20:15

by Luis de Oliveira

[permalink] [raw]
Subject: [V2, 2/5] Documentation: dt-bindings: Document the Synopsys MIPI DPHY Rx bindings

Add device-tree bindings documentation for SNPS DesignWare MIPI D-PHY in
RX mode.

Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v2:
- no changes

.../devicetree/bindings/phy/snps,dphy-rx.txt | 36 ++++++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/snps,dphy-rx.txt

diff --git a/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
new file mode 100644
index 0000000..9079f4a
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
@@ -0,0 +1,36 @@
+Synopsys DesignWare MIPI Rx D-PHY block details
+
+Description
+-----------
+
+The Synopsys MIPI D-PHY controller supports MIPI-DPHY in receiver mode.
+Please refer to phy-bindings.txt for more information.
+
+Required properties:
+- compatible : Shall be "snps,dphy-rx".
+- #phy-cells : Must be 1.
+- snps,dphy-frequency : Output frequency of the D-PHY.
+- snps,dphy-te-len : Size of the communication interface (8 bits->8 or 12bits->12).
+- reg : Physical base address and size of the device memory mapped
+ registers;
+
+Optional properties:
+- snps,compat-mode : Compatibility mode control
+
+The per-board settings:
+- gpios : Synopsys testchip used as reference uses this to change setup
+ configurations.
+
+Example:
+
+ mipi_dphy_rx1: dphy@3040 {
+ compatible = "snps,dphy-rx";
+ #phy-cells = <1>;
+ snps,dphy-frequency = <300000>;
+ snps,dphy-te-len = <12>;
+ snps,compat-mode = <1>;
+ reg = < 0x03040 0x20
+ 0x08000 0x100
+ 0x09000 0x100>;
+ };
+
--
2.9.3


2018-09-20 11:20:33

by Luis de Oliveira

[permalink] [raw]
Subject: [V2, 3/5] media: platform: dwc: Add DW MIPI DPHY Rx platform

Add of Synopsys MIPI D-PHY in RX mode support.
Separated in the implementation are platform dependent probing functions.

Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v2:
- fix uninitialization warning
- fix SDPX License to match: Documentation/process/license-rules.rst

drivers/media/platform/dwc/Kconfig | 32 ++
drivers/media/platform/dwc/Makefile | 2 +
drivers/media/platform/dwc/dw-dphy-plat.c | 365 ++++++++++++++++++
drivers/media/platform/dwc/dw-dphy-rx.c | 594 ++++++++++++++++++++++++++++++
drivers/media/platform/dwc/dw-dphy-rx.h | 176 +++++++++
5 files changed, 1169 insertions(+)
create mode 100644 drivers/media/platform/dwc/dw-dphy-plat.c
create mode 100644 drivers/media/platform/dwc/dw-dphy-rx.c
create mode 100644 drivers/media/platform/dwc/dw-dphy-rx.h

diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
index e69de29..a573297 100644
--- a/drivers/media/platform/dwc/Kconfig
+++ b/drivers/media/platform/dwc/Kconfig
@@ -0,0 +1,32 @@
+#
+# Synopsys DWC Platform drivers
+# Most drivers here are currently for MIPI CSI-2 and MIPI DPHY support
+
+config DWC_MIPI_CSI2_HOST
+ bool "Synopsys Designware CSI-2 Host Controller and DPHY-RX support"
+ select VIDEO_DEV
+ select VIDEO_V4L2
+ select VIDEO_V4L2_SUBDEV_API
+ select VIDEOBUF2_VMALLOC
+ select VIDEOBUF2_DMA_CONTIG
+ select GENERIC_PHY
+ select VIDEO_OV5647
+ help
+ This selects the CSI-2 host controller support.
+
+ If you have a controller with this interface, say Y.
+
+ If unsure, say N.
+
+if DWC_MIPI_CSI2_HOST
+
+config DWC_MIPI_TC_DPHY_G128
+ tristate "DesignWare platform support using a G128 Test Chip"
+ depends on DWC_MIPI_CSI2_HOST
+ help
+ Synopsys Test Chip is a MIPI D-PHY for prototyping purposes.
+
+ If unsure, say N.
+
+endif # DWC_MIPI_CSI2_HOST
+
diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
index e69de29..8be6f68 100644
--- a/drivers/media/platform/dwc/Makefile
+++ b/drivers/media/platform/dwc/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_DWC_MIPI_TC_DPHY_G128) += dw-dphy-platfrm.o
+dw-dphy-platfrm-objs := dw-dphy-plat.o dw-dphy-rx.o
diff --git a/drivers/media/platform/dwc/dw-dphy-plat.c b/drivers/media/platform/dwc/dw-dphy-plat.c
new file mode 100644
index 0000000..8e298f5
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-dphy-plat.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * dw-dphy-plat.c
+ *
+ * Copyright(c) 2018-present, Synopsys, Inc. and/or its affiliates.
+ * Luis Oliveira <[email protected]>
+ *
+ */
+
+#include "dw-dphy-rx.h"
+
+static struct phy *dw_dphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct dw_dphy_rx *dphy = dev_get_drvdata(dev);
+
+ return dphy->phy;
+}
+
+static ssize_t dphy_reset_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+ char buffer[15];
+
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
+ usleep_range(100, 200);
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t dphy_freq_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ unsigned long freq;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+
+ ret = kstrtoul(buf, 10, &freq);
+ if (ret < 0)
+ return ret;
+
+ if (freq > 2500) {
+ dev_info(dev, "Freq must be under 2500 Mhz\n");
+ return count;
+ }
+ if (freq < 80) {
+ dev_info(dev, "Freq must be over 80 Mhz\n");
+ return count;
+ }
+
+ dev_info(dev, "Data Rate %lu Mbps\n", freq);
+ dphy->dphy_freq = freq * 1000;
+
+ return count;
+
+}
+
+static ssize_t dphy_freq_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+ char buffer[15];
+
+ snprintf(buffer, 15, "Freq %d\n", dphy->dphy_freq / 1000);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t dphy_addr_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+ unsigned long val;
+ int ret;
+ u8 addr, payload;
+
+ ret = kstrtoul(buf, 32, &val);
+ if (ret < 0)
+ return ret;
+
+ payload = (u16)val;
+ addr = (u16)(val >> 16);
+
+ dev_info(dev, "addr 0x%lX\n", val);
+ dev_info(dev, "payload: 0x%X\n", addr);
+
+ dev_info(dev,
+ "Addr [0x%x] -> 0x%x\n", (unsigned int)addr,
+ dw_dphy_te_read(dphy, addr));
+
+ return count;
+}
+
+static ssize_t idelay_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+ char buffer[15];
+
+ snprintf(buffer, 15, "idelay %d\n", dw_dphy_if_get_idelay(dphy));
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t idelay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+ int ret;
+ unsigned long val;
+ u8 lane, delay;
+
+ ret = kstrtoul(buf, 16, &val);
+ if (ret < 0)
+ return ret;
+
+ lane = (u8)val;
+ delay = (u8)(val >> 8);
+
+ dev_dbg(dev, "Lanes %u\n", lane);
+ dev_dbg(dev, "Delay %u\n", delay);
+
+ dw_dphy_if_set_idelay_lane(dphy, delay, lane);
+
+ return count;
+}
+
+static ssize_t len_config_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long length;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+
+ ret = kstrtoul(buf, 10, &length);
+ if (ret < 0)
+ return ret;
+
+ if (length == BIT8)
+ pr_info("Configured for 8-bit interface\n");
+ else if (length == BIT12)
+ pr_info("Configured for 12-bit interface\n");
+ else
+ return count;
+
+ dphy->dphy_te_len = length;
+
+ return count;
+
+}
+
+static ssize_t len_config_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+ char buffer[20];
+
+ snprintf(buffer, 20, "Length %d\n", dphy->dphy_te_len);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t dw_dphy_g118_settle_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ unsigned long lp_time;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+
+ ret = kstrtoul(buf, 10, &lp_time);
+ if (ret < 0)
+ return ret;
+
+ if ((lp_time > 1) && (lp_time < 10000))
+ dphy->lp_time = lp_time;
+ else {
+ pr_info("Invalid Value configuring for 1000 ns\n");
+ dphy->lp_time = 1000;
+ }
+
+ dphy->lp_time = lp_time;
+
+ return count;
+
+}
+
+static ssize_t dw_dphy_g118_settle_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+ char buffer[10];
+
+ snprintf(buffer, 10, "Settle %d ns\n", dphy->lp_time);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static DEVICE_ATTR_RO(dphy_reset);
+static DEVICE_ATTR_RW(dphy_freq);
+static DEVICE_ATTR_WO(dphy_addr);
+static DEVICE_ATTR_RW(idelay);
+static DEVICE_ATTR_RW(len_config);
+static DEVICE_ATTR_RW(dw_dphy_g118_settle);
+
+static struct phy_ops dw_dphy_ops = {
+ .init = dw_dphy_init,
+ .reset = dw_dphy_reset,
+ .power_on = dw_dphy_power_on,
+ .power_off = dw_dphy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int dw_dphy_rx_probe(struct platform_device *pdev)
+{
+ struct dw_dphy_rx *dphy;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ struct phy *phy;
+
+ dphy = devm_kzalloc(dev, sizeof(*dphy), GFP_KERNEL);
+ if (!dphy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dphy->base_address = devm_ioremap(dev, res->start, resource_size(res));
+ if (IS_ERR(dphy->base_address)) {
+ dev_err(dev, "error requesting base address\n");
+ return PTR_ERR(dphy->base_address);
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ dphy->dphy1_if_addr = devm_ioremap_resource(dev, res);
+ if (IS_ERR(dphy->dphy1_if_addr)) {
+ dev_err(dev, "error requesting dphy 1 if regbank\n");
+ return PTR_ERR(dphy->dphy1_if_addr);
+ }
+
+ dphy->max_lanes =
+ dw_dphy_if_read_msk(dphy, DPHYID, DPHY_ID_LANE_SUPPORT, 4);
+
+ dphy->dphy_gen = dw_dphy_if_read_msk(dphy, DPHYID, DPHY_ID_GEN, 4);
+ dev_info(dev, "DPHY GEN %s with maximum %s lanes\n",
+ dphy->dphy_gen == GEN3 ? "3" : "2",
+ dphy->max_lanes == CTRL_8_LANES ? "8" : "4");
+
+ if (dphy->max_lanes == CTRL_8_LANES) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ dphy->dphy2_if_addr =
+ devm_ioremap(dev, res->start, resource_size(res));
+
+ if (IS_ERR(dphy->dphy2_if_addr)) {
+ dev_err(dev, "error requesting dphy 2 if regbank\n");
+ return PTR_ERR(dphy->dphy2_if_addr);
+ }
+
+ dphy->config_gpio = of_get_gpio(dev->of_node, 0);
+ if (!gpio_is_valid(dphy->config_gpio)) {
+ dev_err(dev, "failed to parse config gpio\n");
+ return dphy->config_gpio;
+ }
+ }
+
+ if (of_property_read_u32(dev->of_node,
+ "snps,dphy-frequency",
+ &dphy->dphy_freq)) {
+ dev_err(dev, "failed to find dphy frequency\n");
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32(dev->of_node,
+ "snps,dphy-te-len",
+ &dphy->dphy_te_len)) {
+ dev_err(dev, "failed to find dphy te length\n");
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32(dev->of_node,
+ "snps,compat-mode",
+ &dphy->compat_mode)) {
+ dev_err(dev, "failed to find compat mode\n");
+ return -EINVAL;
+ }
+
+ dev_set_drvdata(dev, dphy);
+ spin_lock_init(&dphy->slock);
+
+ phy = devm_phy_create(dev, NULL, &dw_dphy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ dphy->phy = phy;
+ phy_set_drvdata(phy, dphy);
+
+ phy_provider = devm_of_phy_provider_register(dev, dw_dphy_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "error getting phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ dphy->lp_time = 1000; /* 1000 ns */
+ dphy->lanes_config = dw_dphy_setup_config(dphy);
+ dev_dbg(dev, "rx-dphy created\n");
+
+ device_create_file(&pdev->dev, &dev_attr_dphy_reset);
+ device_create_file(&pdev->dev, &dev_attr_dphy_freq);
+ device_create_file(&pdev->dev, &dev_attr_dphy_addr);
+ device_create_file(&pdev->dev, &dev_attr_idelay);
+ device_create_file(&pdev->dev, &dev_attr_len_config);
+ device_create_file(&pdev->dev, &dev_attr_dw_dphy_g118_settle);
+
+ return 0;
+}
+
+static const struct of_device_id dw_dphy_rx_of_match[] = {
+ { .compatible = "snps,dphy-rx" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, dw_dphy_rx_of_match);
+
+static struct platform_driver dw_dphy_rx_driver = {
+ .probe = dw_dphy_rx_probe,
+ .driver = {
+ .of_match_table = dw_dphy_rx_of_match,
+ .name = "snps-dphy-rx",
+ .owner = THIS_MODULE,
+ }
+};
+module_platform_driver(dw_dphy_rx_driver);
+
+MODULE_DESCRIPTION("SNPS MIPI DPHY Rx driver");
+MODULE_AUTHOR("Luis Oliveira <[email protected]>");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/media/platform/dwc/dw-dphy-rx.c b/drivers/media/platform/dwc/dw-dphy-rx.c
new file mode 100644
index 0000000..4100ea5
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-dphy-rx.c
@@ -0,0 +1,594 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Synopsys MIPI D-PHY driver
+ *
+ * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+ * Author: Luis Oliveira <[email protected]>
+ *
+ */
+
+#include "dw-dphy-rx.h"
+
+struct range_dphy_gen2 {
+ u64 freq;
+ u8 hsfregrange;
+};
+
+struct range_dphy_gen2 range_gen2[] = {
+ { 80, 0x00}, { 90, 0x10}, { 100, 0x20},
+ { 110, 0x30}, { 120, 0x01}, { 130, 0x11},
+ { 140, 0x21}, { 150, 0x31}, { 160, 0x02},
+ { 170, 0x12}, { 180, 0x22}, { 190, 0x32},
+ { 205, 0x03}, { 220, 0x13}, { 235, 0x23},
+ { 250, 0x33}, { 275, 0x04}, { 300, 0x14},
+ { 325, 0x05}, { 350, 0x15}, { 400, 0x25},
+ { 450, 0x06}, { 500, 0x16}, { 550, 0x07},
+ { 600, 0x17}, { 650, 0x08}, { 700, 0x18},
+ { 750, 0x09}, { 800, 0x19}, { 850, 0x29},
+ { 900, 0x39}, { 950, 0x0A}, {1000, 0x1A},
+ {1050, 0x2A}, {1100, 0x3A}, {1150, 0x0B},
+ {1200, 0x1B}, {1250, 0x2B}, {1300, 0x3B},
+ {1350, 0x0C}, {1400, 0x1C}, {1450, 0x2C},
+ {1500, 0x3C}, {1550, 0x0D}, {1600, 0x1D},
+ {1650, 0x2D}, {1700, 0x0E}, {1750, 0x1E},
+ {1800, 0x2E}, {1850, 0x3E}, {1900, 0x0F},
+ {1950, 0x1F}, {2000, 0x2F},
+};
+
+struct range_dphy_gen3 {
+ u64 freq;
+ u8 hsfregrange;
+ u32 osc_freq_target;
+};
+
+struct range_dphy_gen3 range_gen3[] = {
+
+ { 80, 0x00, 0x1B6}, { 90, 0x10, 0x1B6}, { 100, 0x20, 0x1B6},
+ { 110, 0x30, 0x1B6}, { 120, 0x01, 0x1B6}, { 130, 0x11, 0x1B6},
+ { 140, 0x21, 0x1B6}, { 150, 0x31, 0x1B6}, { 160, 0x02, 0x1B6},
+ { 170, 0x12, 0x1B6}, { 180, 0x22, 0x1B6}, { 190, 0x32, 0x1B6},
+ { 205, 0x03, 0x1B6}, { 220, 0x13, 0x1B6}, { 235, 0x23, 0x1B6},
+ { 250, 0x33, 0x1B6}, { 275, 0x04, 0x1B6}, { 300, 0x14, 0x1B6},
+ { 325, 0x25, 0x1B6}, { 350, 0x35, 0x1B6}, { 400, 0x05, 0x1B6},
+ { 450, 0x16, 0x1B6}, { 500, 0x26, 0x1B6}, { 550, 0x37, 0x1B6},
+ { 600, 0x07, 0x1B6}, { 650, 0x18, 0x1B6}, { 700, 0x28, 0x1B6},
+ { 750, 0x39, 0x1B6}, { 800, 0x09, 0x1B6}, { 850, 0x19, 0x1B6},
+ { 900, 0x29, 0x1B6}, { 950, 0x3A, 0x1B6}, {1000, 0x0A, 0x1B6},
+ {1050, 0x1A, 0x1B6}, {1100, 0x2A, 0x1B6}, {1150, 0x3B, 0x1B6},
+ {1200, 0x0B, 0x1B6}, {1250, 0x1B, 0x1B6}, {1300, 0x2B, 0x1B6},
+ {1350, 0x3C, 0x1B6}, {1400, 0x0C, 0x1B6}, {1450, 0x1C, 0x1B6},
+ {1500, 0x2C, 0x1B6}, {1550, 0x3D, 0x10F}, {1600, 0x0D, 0x118},
+ {1650, 0x1D, 0x121}, {1700, 0x2E, 0x12A}, {1750, 0x3E, 0x132},
+ {1800, 0x0E, 0x13B}, {1850, 0x1E, 0x144}, {1900, 0x2F, 0x14D},
+ {1950, 0x3F, 0x155}, {2000, 0x0F, 0x15E}, {2050, 0x40, 0x167},
+ {2100, 0x41, 0x170}, {2150, 0x42, 0x178}, {2200, 0x43, 0x181},
+ {2250, 0x44, 0x18A}, {2300, 0x45, 0x193}, {2350, 0x46, 0x19B},
+ {2400, 0x47, 0x1A4}, {2450, 0x48, 0x1AD}, {2500, 0x49, 0x1B6}
+};
+
+u8 dw_dphy_setup_config(struct dw_dphy_rx *dphy)
+{
+ u8 ret;
+ int setup_config;
+
+ if (dphy->max_lanes == CTRL_4_LANES)
+ return CTRL_4_LANES;
+
+ ret = gpio_request(dphy->config_gpio, "config");
+ if (ret < 0) {
+ pr_err("could not acquire config gpio (err=%d)\n", ret);
+ return ret;
+ }
+
+ setup_config = gpio_get_value(dphy->config_gpio);
+ pr_debug("CONFIG %s\n", setup_config == CTRL_8_LANES ? "8L" : "4+4L");
+ gpio_free(dphy->config_gpio);
+
+ return setup_config;
+}
+void dw_dphy_if_write(struct dw_dphy_rx *dphy, u32 address, u32 data)
+{
+ iowrite32(data, dphy->dphy1_if_addr + address);
+
+ if (dphy->lanes_config == CTRL_4_LANES)
+ return;
+
+ iowrite32(data, dphy->dphy2_if_addr + address);
+}
+
+u32 dw_dphy_if_read(struct dw_dphy_rx *dphy, u64 address)
+{
+ u32 if1 = 0, if2 = 0;
+
+ if1 = ioread32(dphy->dphy1_if_addr + address);
+
+ if (dphy->lanes_config == CTRL_4_LANES)
+ goto end;
+
+ if (dphy->lanes_config == DPHYID)
+ goto end;
+
+ if2 = ioread32(dphy->dphy2_if_addr + address);
+
+ if (if1 != if2)
+ pr_err("Values read different for each interface\n");
+
+end:
+ return if1;
+}
+
+void dw_dphy_write(struct dw_dphy_rx *dphy, u32 address, u32 data)
+{
+ iowrite32(data, dphy->base_address + address);
+
+ if (dphy->lanes_config == CTRL_4_LANES)
+ return;
+
+ if (address == R_CSI2_DPHY_TST_CTRL0)
+ iowrite32(data, dphy->base_address + R_CSI2_DPHY2_TST_CTRL0);
+ else if (address == R_CSI2_DPHY_TST_CTRL1)
+ iowrite32(data, dphy->base_address + R_CSI2_DPHY2_TST_CTRL1);
+}
+
+u32 dw_dphy_read(struct dw_dphy_rx *dphy, u64 address)
+{
+ u32 dphy1 = 0, dphy2 = 0;
+
+ dphy1 = ioread32(dphy->base_address + address);
+
+ if (dphy->lanes_config == CTRL_4_LANES)
+ goto end;
+
+ if (address == R_CSI2_DPHY_TST_CTRL0)
+ dphy2 = ioread32(dphy->base_address + R_CSI2_DPHY2_TST_CTRL0);
+ else if (address == R_CSI2_DPHY_TST_CTRL1)
+ dphy2 = ioread32(dphy->base_address + R_CSI2_DPHY2_TST_CTRL1);
+ else
+ return -ENODEV;
+
+ if (dphy1 != dphy2)
+ pr_err("Values read different for each dphy\n");
+
+end:
+ return dphy1;
+}
+
+void dw_dphy_write_msk(struct dw_dphy_rx *dev,
+ u64 address, u64 data, u8 shift, u8 width)
+{
+ u32 mask = (1 << width) - 1;
+ u32 temp = dw_dphy_read(dev, address);
+
+ temp &= ~(mask << shift);
+ temp |= (data & mask) << shift;
+ dw_dphy_write(dev, address, temp);
+}
+
+static void dw_dphy_te_12b_write(struct dw_dphy_rx *dphy, u16 addr, u8 data)
+{
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, PHY_TESTDIN, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy,
+ R_CSI2_DPHY_TST_CTRL1, (u8) (addr >> 8), PHY_TESTDIN, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy,
+ R_CSI2_DPHY_TST_CTRL1, (u8) addr, PHY_TESTDIN, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy,
+ R_CSI2_DPHY_TST_CTRL1, (u8) data, PHY_TESTDIN, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+}
+
+static void dw_dphy_te_8b_write(struct dw_dphy_rx *dphy, u8 addr, u8 data)
+{
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write(dphy, R_CSI2_DPHY_TST_CTRL1, addr);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
+ dw_dphy_write(dphy, R_CSI2_DPHY_TST_CTRL1, data);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+}
+
+static void dw_dphy_te_write(struct dw_dphy_rx *dphy, u16 addr, u8 data)
+{
+
+ if (dphy->dphy_te_len == BIT12)
+ dw_dphy_te_12b_write(dphy, addr, data);
+ else
+ dw_dphy_te_8b_write(dphy, addr, data);
+}
+
+static int dw_dphy_te_12b_read(struct dw_dphy_rx *dphy, u32 addr)
+{
+ u8 ret;
+
+ dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, PHY_TESTDIN, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy,
+ R_CSI2_DPHY_TST_CTRL1, (u8) (addr >> 8), PHY_TESTDIN, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy,
+ R_CSI2_DPHY_TST_CTRL1, (u8) addr, PHY_TESTDIN, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, 0, PHY_TESTDIN);
+ ret = dw_dphy_read_msk(dphy, R_CSI2_DPHY_TST_CTRL1, PHY_TESTDOUT, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
+ dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
+
+ return ret;
+}
+
+static int dw_dphy_te_8b_read(struct dw_dphy_rx *dphy, u32 addr)
+{
+ u8 ret;
+
+ dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, addr, PHY_TESTDIN, 8);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTDIN, 8);
+ ret = dw_dphy_read_msk(dphy, R_CSI2_DPHY_TST_CTRL1, PHY_TESTDOUT, 8);
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
+ dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
+
+ return ret;
+}
+
+int dw_dphy_te_read(struct dw_dphy_rx *dphy, u32 addr)
+{
+ int ret;
+
+ if (dphy->dphy_te_len == BIT12)
+ ret = dw_dphy_te_12b_read(dphy, addr);
+ else
+ ret = dw_dphy_te_8b_read(dphy, addr);
+
+ return ret;
+}
+
+static void dw_dphy_if_init(struct dw_dphy_rx *dphy)
+{
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, TX_PHY);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
+ dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
+ dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
+ dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
+ dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
+ dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
+ dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
+}
+
+static void dw_dphy_gen3_12bit_tc_power_up(struct dw_dphy_rx *dphy)
+{
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
+ dw_dphy_te_write(dphy, CFGCLKFREQRANGE_TX, 0x1C);
+
+ /* CLKSEL | UPDATEPLL | SHADOW_CLEAR | SHADOW_CTRL | FORCEPLL */
+ dw_dphy_te_write(dphy, BYPASS, 0x3F);
+
+ /* IO_DS3 | IO_DS2 | IO_DS1 | IO_DS0 */
+ if (dphy->dphy_freq > 1500)
+ dw_dphy_te_write(dphy, IO_DS, 0x0F);
+
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
+}
+
+static void dw_dphy_gen3_8bit_tc_power_up(struct dw_dphy_rx *dphy)
+{
+ u32 input_freq = dphy->dphy_freq / 1000;
+
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
+ dw_dphy_te_write(dphy, CFGCLKFREQRANGE_RX, 0x1C);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
+ dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX0_MSB, 0x03);
+ dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX0_LSB, 0x02);
+ dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX1_MSB, 0x03);
+ dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX1_LSB, 0x02);
+ dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX2_MSB, 0x03);
+ dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX2_LSB, 0x02);
+ dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX3_MSB, 0x03);
+ dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX3_LSB, 0x02);
+ dw_dphy_te_write(dphy, BANDGAP_CTRL, 0x80);
+
+ if (input_freq < 2000)
+ dw_dphy_te_write(dphy, HS_RX_CTRL_LANE0, 0xC0);
+
+ if (input_freq < 1000) {
+ dw_dphy_te_write(dphy, HS_RX_CTRL_LANE1, 0xC0);
+ dw_dphy_te_write(dphy, HS_RX_CTRL_LANE2, 0xC0);
+ dw_dphy_te_write(dphy, HS_RX_CTRL_LANE3, 0xC0);
+ }
+}
+
+int dw_dphy_g118_settle(struct dw_dphy_rx *dphy)
+{
+ u32 input_freq, total_settle, settle_time, byte_clk, lp_time;
+
+ lp_time = dphy->lp_time;
+ input_freq = dphy->dphy_freq / 1000;
+
+ settle_time = (8 * (1000000/(input_freq))) + 115000;
+ byte_clk = (8000000/(input_freq));
+ total_settle = (settle_time + lp_time * 1000) / byte_clk;
+
+ if (total_settle > 0xFF)
+ total_settle = 0xFF;
+
+ return total_settle;
+}
+
+static void dw_dphy_pwr_down(struct dw_dphy_rx *dphy)
+{
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
+
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
+ if (dphy->lanes_config == CTRL_8_LANES)
+ dw_dphy_write_msk(dphy,
+ R_CSI2_DPHY2_TST_CTRL0, 0, PHY_TESTCLK, 1);
+
+ dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
+}
+
+static void dw_dphy_pwr_up(struct dw_dphy_rx *dphy)
+{
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
+ if (dphy->lanes_config == CTRL_8_LANES)
+ dw_dphy_write_msk(dphy,
+ R_CSI2_DPHY2_TST_CTRL0, 1, PHY_TESTCLK, 1);
+
+ dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
+}
+
+static int dw_dphy_gen3_12bit_configure(struct dw_dphy_rx *dphy)
+{
+ u32 input_freq = dphy->dphy_freq;
+ u8 range = 0;
+
+ pr_debug("PHY GEN 3 Freq: %u\n", input_freq);
+ for (range = 0; (range < ARRAY_SIZE(range_gen3) - 1) &&
+ ((input_freq / 1000) > range_gen3[range].freq); range++)
+ ;
+
+ dw_dphy_gen3_12bit_tc_power_up(dphy);
+
+ dw_dphy_te_write(dphy, RX_SYS_1, range_gen3[range].hsfregrange);
+ dw_dphy_te_write(dphy, RX_SYS_0, 0x20);
+ dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_2,
+ (u8) range_gen3[range].osc_freq_target);
+ dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_3,
+ (u8) (range_gen3[range].osc_freq_target >> 8));
+ dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_4, 0x01);
+
+ if (dphy->compat_mode) {
+ dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_1, 0x01);
+ dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_0, 0x80);
+ }
+
+ if ((dphy->compat_mode) || (input_freq <= 1500))
+ dw_dphy_te_write(dphy, RX_SYS_7, 0x38);
+
+ return 0;
+}
+
+static int dw_dphy_gen3_8bit_configure(struct dw_dphy_rx *dphy)
+{
+ u32 input_freq = dphy->dphy_freq;
+ u8 data;
+ u8 range = 0;
+
+ pr_debug("PHY GEN 3 Freq: %u\n", input_freq);
+ for (range = 0; (range < ARRAY_SIZE(range_gen3) - 1) &&
+ ((input_freq / 1000) > range_gen3[range].freq); range++)
+ ;
+
+ dw_dphy_te_write(dphy, RX_SKEW_CAL, dw_dphy_g118_settle(dphy));
+ data = 1<<7 | range_gen3[range].hsfregrange;
+ dw_dphy_te_write(dphy, HSFREQRANGE_8BIT, data);
+ dw_dphy_gen3_8bit_tc_power_up(dphy);
+
+ return 0;
+}
+
+static int dw_dphy_gen2_configure(struct dw_dphy_rx *dphy)
+{
+ u32 input_freq = dphy->dphy_freq;
+ u8 data;
+ u8 range = 0;
+
+ /* provide an initial active-high test clear pulse in TESTCLR */
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
+ dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
+
+ pr_debug("PHY GEN 2 Freq: %u\n", input_freq);
+ for (range = 0; (range < ARRAY_SIZE(range_gen2) - 1) &&
+ ((input_freq / 1000) > range_gen2[range].freq); range++)
+ ;
+
+ data = range_gen2[range].hsfregrange << 1;
+ dw_dphy_te_write(dphy, HSFREQRANGE_8BIT, data);
+
+ return 0;
+}
+
+static int dw_dphy_configure(struct dw_dphy_rx *dphy)
+{
+ dw_dphy_pwr_down(dphy);
+
+ if (dphy->dphy_gen == GEN3) {
+ dw_dphy_if_init(dphy);
+
+ if (dphy->dphy_te_len == BIT12)
+ dw_dphy_gen3_12bit_configure(dphy);
+ else
+ dw_dphy_gen3_8bit_configure(dphy);
+ } else
+ dw_dphy_gen2_configure(dphy);
+
+ dw_dphy_pwr_up(dphy);
+
+ return 0;
+}
+
+int dw_dphy_if_set_idelay(struct dw_dphy_rx *dphy, u8 dly, u8 cells)
+{
+ uint32_t val = 0;
+
+ dw_dphy_if_write(dphy, IDLYCFG, 0);
+
+ dw_dphy_if_write(dphy, IDLYSEL, cells);
+ dw_dphy_if_write(dphy, IDLYCNTINVAL, dly);
+
+ /* Pulse Value Set */
+ dw_dphy_if_write(dphy, IDLYCFG, 1);
+ usleep_range(10, 20);
+ dw_dphy_if_write(dphy, IDLYCFG, 0);
+
+ /* Pulse IDELAY CTRL Reset */
+ dw_dphy_if_write(dphy, DPHY1REGRSTN, 0);
+ usleep_range(10, 20);
+ dw_dphy_if_write(dphy, DPHY1REGRSTN, 1);
+
+ /* Get Value*/
+ val = dw_dphy_if_read(dphy, IDLYCNTOUTVAL);
+
+ if (val != dly) {
+ pr_info("odelay config failed, set %d get %d", dly, val);
+ return -1;
+ }
+
+ return 0;
+}
+
+int dw_dphy_if_get_idelay(struct dw_dphy_rx *dphy)
+{
+ return dw_dphy_if_read(dphy, IDLYCNTOUTVAL);
+}
+
+int dw_dphy_if_set_idelay_lane(struct dw_dphy_rx *dphy, u8 dly, u8 lane)
+{
+ int cell;
+
+ switch (lane) {
+ case 0:
+ for (cell = 3; cell <= 10; cell++)
+ dw_dphy_if_set_idelay(dphy, dly, cell);
+ break;
+ case 1:
+ for (cell = 14; cell <= 21; cell++)
+ dw_dphy_if_set_idelay(dphy, dly, cell);
+ break;
+ case 2:
+ for (cell = 24; cell <= 31; cell++)
+ dw_dphy_if_set_idelay(dphy, dly, cell);
+ break;
+ case 3:
+ for (cell = 34; cell <= 41; cell++)
+ dw_dphy_if_set_idelay(dphy, dly, cell);
+ break;
+ case 4: /* ALL */
+ dw_dphy_if_set_idelay(dphy, dly, 0x7F);
+ break;
+ default:
+ pr_err("Lane Value not recognized\n");
+ return -1;
+ }
+ return 0;
+}
+
+int dw_dphy_init(struct phy *phy)
+{
+ struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
+
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
+ dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
+
+ return 0;
+}
+
+static int dw_dphy_set_phy_state(struct dw_dphy_rx *dphy, u32 on)
+{
+ u8 hs_freq;
+
+ dphy->lanes_config = dw_dphy_setup_config(dphy);
+
+ if (dphy->dphy_te_len == BIT12)
+ hs_freq = RX_SYS_1;
+ else
+ hs_freq = HSFREQRANGE_8BIT;
+
+ if (on) {
+ dw_dphy_configure(dphy);
+ pr_debug("HS Code: 0X%x\n", dw_dphy_te_read(dphy, hs_freq));
+ } else {
+ dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
+ }
+
+ return 0;
+}
+
+int dw_dphy_power_on(struct phy *phy)
+{
+ struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
+
+ return dw_dphy_set_phy_state(dphy, 1);
+}
+
+int dw_dphy_power_off(struct phy *phy)
+{
+ struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
+
+ return dw_dphy_set_phy_state(dphy, 0);
+}
+
+int dw_dphy_reset(struct phy *phy)
+{
+ struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
+
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
+ usleep_range(100, 200);
+ dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
+
+ return 0;
+}
diff --git a/drivers/media/platform/dwc/dw-dphy-rx.h b/drivers/media/platform/dwc/dw-dphy-rx.h
new file mode 100644
index 0000000..5375685
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-dphy-rx.h
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Synopsys MIPI D-PHY driver
+ *
+ * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#ifndef __PHY_SNPS_DPHY_RX_H__
+#define __PHY_SNPS_DPHY_RX_H__
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+/* DPHY interface register bank*/
+#define R_CSI2_DPHY_SHUTDOWNZ 0x0
+#define R_CSI2_DPHY_RSTZ 0x4
+#define R_CSI2_DPHY_RX 0x8
+#define R_CSI2_DPHY_STOPSTATE 0xC
+#define R_CSI2_DPHY_TST_CTRL0 0x10
+#define R_CSI2_DPHY_TST_CTRL1 0x14
+#define R_CSI2_DPHY2_TST_CTRL0 0x18
+#define R_CSI2_DPHY2_TST_CTRL1 0x1C
+
+enum dphy_id_mask {
+ DPHY_ID_LANE_SUPPORT = 0,
+ DPHY_ID_IF = 4,
+ DPHY_ID_GEN = 8,
+};
+
+enum dphy_gen_values {
+ GEN1 = 0,
+ GEN2 = 1,
+ GEN3 = 2,
+};
+
+enum dphy_interface_length {
+ BIT8 = 8,
+ BIT12 = 12,
+};
+
+enum tst_ctrl0 {
+ PHY_TESTCLR = 0,
+ PHY_TESTCLK = 1,
+};
+
+enum tst_ctrl1 {
+ PHY_TESTDIN = 0,
+ PHY_TESTDOUT = 8,
+ PHY_TESTEN = 16,
+};
+
+enum lanes_config_values {
+ CTRL_4_LANES = 0,
+ CTRL_8_LANES = 1,
+};
+
+enum dphy_tc {
+ CFGCLKFREQRANGE_TX = 0x02,
+ CFGCLKFREQRANGE_RX = 0x05,
+ BYPASS = 0x20,
+ IO_DS = 0x30,
+};
+
+enum dphy_8bit_interface_addr {
+ BANDGAP_CTRL = 0x24,
+ HS_RX_CTRL_LANE0 = 0x42,
+ HSFREQRANGE_8BIT = 0x44,
+ OSC_FREQ_TARGET_RX0_LSB = 0x4e,
+ OSC_FREQ_TARGET_RX0_MSB = 0x4f,
+ HS_RX_CTRL_LANE1 = 0x52,
+ OSC_FREQ_TARGET_RX1_LSB = 0x5e,
+ OSC_FREQ_TARGET_RX1_MSB = 0x5f,
+ RX_SKEW_CAL = 0x7e,
+ HS_RX_CTRL_LANE2 = 0x82,
+ OSC_FREQ_TARGET_RX2_LSB = 0x8e,
+ OSC_FREQ_TARGET_RX2_MSB = 0x8f,
+ HS_RX_CTRL_LANE3 = 0x92,
+ OSC_FREQ_TARGET_RX3_LSB = 0x9e,
+ OSC_FREQ_TARGET_RX3_MSB = 0x9f,
+};
+
+enum dphy_12bit_interface_addr {
+ RX_SYS_0 = 0x01,
+ RX_SYS_1 = 0x02,
+ RX_SYS_7 = 0x08,
+ RX_RX_STARTUP_OVR_0 = 0xe0,
+ RX_RX_STARTUP_OVR_1 = 0xe1,
+ RX_RX_STARTUP_OVR_2 = 0xe2,
+ RX_RX_STARTUP_OVR_3 = 0xe3,
+ RX_RX_STARTUP_OVR_4 = 0xe4,
+};
+
+/* Gen3 interface register bank*/
+#define IDLYCFG 0x00
+#define IDLYSEL 0x04
+#define IDLYCNTINVAL 0x08
+#define IDLYCNTOUTVAL 0x0c
+#define DPHY1REGRSTN 0x10
+#define DPHYZCALSTAT 0x14
+#define DPHYZCALCTRL 0x18
+#define DPHYLANE0STAT 0x1c
+#define DPHYLANE1STAT 0x20
+#define DPHYLANE2STAT 0x24
+#define DPHYLANE3STAT 0x28
+#define DPHYCLKSTAT 0x2c
+#define DPHYZCLKCTRL 0x30
+#define TCGENPURPOSOUT 0x34
+#define TCGENPURPOSIN 0x38
+#define DPHYGENERICOUT 0x3c
+#define DPHYGENERICIN 0x40
+#define DPHYGLUEIFTESTER 0x44
+#define DPHYID 0x100
+
+enum glueiftester {
+ GLUELOGIC = 0x4,
+ RX_PHY = 0x2,
+ TX_PHY = 0x1,
+ RESET = 0x0,
+};
+
+struct dw_dphy_rx {
+ spinlock_t slock;
+ struct phy *phy;
+ uint32_t dphy_freq;
+ uint32_t dphy_gen;
+ uint32_t dphy_te_len;
+ uint32_t lanes_config;
+ uint32_t max_lanes;
+ uint32_t compat_mode;
+ uint32_t lp_time;
+
+ void __iomem *base_address; /* test interface */
+ void __iomem *dphy1_if_addr; /* gluelogic phy 1 */
+ void __iomem *dphy2_if_addr; /* gluelogic phy 2 */
+
+ int config_gpio;
+ uint8_t setup_config;
+};
+
+int dw_dphy_init(struct phy *phy);
+int dw_dphy_reset(struct phy *phy);
+int dw_dphy_power_off(struct phy *phy);
+int dw_dphy_power_on(struct phy *phy);
+
+u8 dw_dphy_setup_config(struct dw_dphy_rx *dphy);
+u32 dw_dphy_if_read(struct dw_dphy_rx *dphy, u64 address);
+void dw_dphy_write(struct dw_dphy_rx *dphy, u32 address, u32 data);
+u32 dw_dphy_read(struct dw_dphy_rx *dphy, u64 address);
+int dw_dphy_te_read(struct dw_dphy_rx *dphy, u32 addr);
+int dw_dphy_if_get_idelay(struct dw_dphy_rx *dphy);
+int dw_dphy_if_set_idelay_lane(struct dw_dphy_rx *dphy, u8 dly, u8 lane);
+
+static inline
+u32 dw_dphy_if_read_msk(struct dw_dphy_rx *dphy,
+ u32 address, u8 shift, u8 width)
+{
+ return (dw_dphy_if_read(dphy, address) >> shift) & ((1 << width) - 1);
+}
+
+static inline
+u32 dw_dphy_read_msk(struct dw_dphy_rx *dev, u32 address, u8 shift, u8 width)
+{
+ return (dw_dphy_read(dev, address) >> shift) & ((1 << width) - 1);
+}
+#endif /*__PHY_SNPS_DPHY_RX_H__*/
--
2.9.3


2018-09-20 11:21:09

by Luis de Oliveira

[permalink] [raw]
Subject: [V2, 1/5] media: platform: Add a DesignWare folder to have Synopsys drivers

This patch has the intention of make the patch series more clear by creating
a dwc folder.

Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v2:
- Fix Kbuild error with no Makefile present

drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 3 +++
drivers/media/platform/dwc/Kconfig | 0
drivers/media/platform/dwc/Makefile | 0
4 files changed, 4 insertions(+)
create mode 100644 drivers/media/platform/dwc/Kconfig
create mode 100644 drivers/media/platform/dwc/Makefile

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index f627587..f627a27 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -137,6 +137,7 @@ source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/dwc/Kconfig"

config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 6ab6200..def2f33 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -98,3 +98,6 @@ obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
obj-y += meson/

obj-y += cros-ec-cec/
+
+obj-y += dwc/
+
diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
new file mode 100644
index 0000000..e69de29
diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
new file mode 100644
index 0000000..e69de29
--
2.9.3


2018-09-20 11:21:45

by Luis de Oliveira

[permalink] [raw]
Subject: [V2, 5/5] media: platform: dwc: Add MIPI CSI-2 controller driver

Add the Synopsys MIPI CSI-2 controller driver. This
controller driver is divided in platform dependent functions
and core functions. It also includes a platform for future
DesignWare drivers.

Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v2:
- fix SDPX License to match: Documentation/process/license-rules.rst

MAINTAINERS | 11 +
drivers/media/platform/dwc/Kconfig | 11 +
drivers/media/platform/dwc/Makefile | 2 +
drivers/media/platform/dwc/dw-csi-plat.c | 508 +++++++++++++++++++++++++++++++
drivers/media/platform/dwc/dw-csi-plat.h | 76 +++++
drivers/media/platform/dwc/dw-mipi-csi.c | 490 +++++++++++++++++++++++++++++
drivers/media/platform/dwc/dw-mipi-csi.h | 202 ++++++++++++
include/media/dwc/dw-mipi-csi-pltfrm.h | 101 ++++++
8 files changed, 1401 insertions(+)
create mode 100644 drivers/media/platform/dwc/dw-csi-plat.c
create mode 100644 drivers/media/platform/dwc/dw-csi-plat.h
create mode 100644 drivers/media/platform/dwc/dw-mipi-csi.c
create mode 100644 drivers/media/platform/dwc/dw-mipi-csi.h
create mode 100644 include/media/dwc/dw-mipi-csi-pltfrm.h

diff --git a/MAINTAINERS b/MAINTAINERS
index da2e509..fd5f1fc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14032,6 +14032,16 @@ S: Maintained
F: drivers/dma/dwi-axi-dmac/
F: Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.txt

+SYNOPSYS DESIGNWARE MIPI CSI-2 HOST VIDEO PLATFORM
+M: Luis Oliveira <[email protected]>
+L: [email protected]
+T: git git://linuxtv.org/media_tree.git
+S: Maintained
+F: drivers/media/platform/dwc
+F: include/media/dwc/dw-mipi-csi-pltfrm.h
+F: Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
+F: Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
+
SYNOPSYS DESIGNWARE DMAC DRIVER
M: Viresh Kumar <[email protected]>
R: Andy Shevchenko <[email protected]>
@@ -16217,3 +16227,4 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
S: Buried alive in reporters
F: *
F: */
+
diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
index a573297..4b971a6 100644
--- a/drivers/media/platform/dwc/Kconfig
+++ b/drivers/media/platform/dwc/Kconfig
@@ -20,6 +20,17 @@ config DWC_MIPI_CSI2_HOST

if DWC_MIPI_CSI2_HOST

+config DWC_MIPI_CSI2_HOST_PLATFORM
+ tristate "Platform bus based CSI-2 Host Controller support"
+ depends on HAS_DMA
+ help
+ This selects the CSI-2 host controller support. Select this if
+ you have an CSI-2 Host controller on Platform bus.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
config DWC_MIPI_TC_DPHY_G128
tristate "DesignWare platform support using a G128 Test Chip"
depends on DWC_MIPI_CSI2_HOST
diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
index 8be6f68..e19cede 100644
--- a/drivers/media/platform/dwc/Makefile
+++ b/drivers/media/platform/dwc/Makefile
@@ -1,2 +1,4 @@
+obj-$(CONFIG_DWC_MIPI_CSI2_HOST_PLATFORM) += dw-csi-platfrm.o
+dw-csi-platfrm-objs := dw-csi-plat.o dw-mipi-csi.o
obj-$(CONFIG_DWC_MIPI_TC_DPHY_G128) += dw-dphy-platfrm.o
dw-dphy-platfrm-objs := dw-dphy-plat.o dw-dphy-rx.o
diff --git a/drivers/media/platform/dwc/dw-csi-plat.c b/drivers/media/platform/dwc/dw-csi-plat.c
new file mode 100644
index 0000000..012830e
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-csi-plat.c
@@ -0,0 +1,508 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * dw-csi-plat.c
+ *
+ * Copyright(c) 2018-present, Synopsys, Inc. and/or its affiliates.
+ * Luis Oliveira <[email protected]>
+ *
+ */
+
+#include "dw-csi-plat.h"
+
+static const struct mipi_fmt *
+find_dw_mipi_csi_format(struct v4l2_mbus_framefmt *mf)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(dw_mipi_csi_formats); i++)
+ if (mf->code == dw_mipi_csi_formats[i].code)
+ return &dw_mipi_csi_formats[i];
+ return NULL;
+}
+
+static int dw_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index >= ARRAY_SIZE(dw_mipi_csi_formats))
+ return -EINVAL;
+
+ code->code = dw_mipi_csi_formats[code->index].code;
+ return 0;
+}
+
+static struct mipi_fmt const *
+dw_mipi_csi_try_format(struct v4l2_mbus_framefmt *mf)
+{
+ struct mipi_fmt const *fmt;
+
+ fmt = find_dw_mipi_csi_format(mf);
+ if (fmt == NULL)
+ fmt = &dw_mipi_csi_formats[0];
+
+ mf->code = fmt->code;
+ return fmt;
+}
+
+static struct v4l2_mbus_framefmt *
+dw_mipi_csi_get_format(struct mipi_csi_dev *dev,
+ struct v4l2_subdev_pad_config *cfg,
+ enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return cfg ? v4l2_subdev_get_try_format(&dev->sd, cfg,
+ 0) : NULL;
+
+ return &dev->format;
+}
+
+static int
+dw_mipi_csi_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mipi_csi_dev *dev = sd_to_mipi_csi_dev(sd);
+ struct mipi_fmt const *dev_fmt;
+ struct v4l2_mbus_framefmt *mf;
+ unsigned int i = 0;
+ const struct v4l2_bt_timings *bt_r = &v4l2_dv_timings_presets[0].bt;
+
+ mf = dw_mipi_csi_get_format(dev, cfg, fmt->which);
+
+ dev_fmt = dw_mipi_csi_try_format(&fmt->format);
+ if (dev_fmt) {
+ *mf = fmt->format;
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ dev->fmt = dev_fmt;
+ dw_mipi_csi_set_ipi_fmt(dev);
+ }
+ while (v4l2_dv_timings_presets[i].bt.width) {
+ const struct v4l2_bt_timings *bt =
+ &v4l2_dv_timings_presets[i].bt;
+ if (mf->width == bt->width && mf->height == bt->width) {
+ dw_mipi_csi_fill_timings(dev, bt);
+ return 0;
+ }
+ i++;
+ }
+
+ dw_mipi_csi_fill_timings(dev, bt_r);
+ return 0;
+
+}
+
+static int
+dw_mipi_csi_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mipi_csi_dev *dev = sd_to_mipi_csi_dev(sd);
+ struct v4l2_mbus_framefmt *mf;
+
+ mf = dw_mipi_csi_get_format(dev, cfg, fmt->which);
+ if (!mf)
+ return -EINVAL;
+
+ mutex_lock(&dev->lock);
+ fmt->format = *mf;
+ mutex_unlock(&dev->lock);
+ return 0;
+}
+
+static int
+dw_mipi_csi_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct mipi_csi_dev *dev = sd_to_mipi_csi_dev(sd);
+
+ if (on) {
+ dw_mipi_csi_hw_stdby(dev);
+ dw_mipi_csi_start(dev);
+ } else {
+ phy_power_off(dev->phy);
+ dw_mipi_csi_mask_irq_power_off(dev);
+ }
+ return 0;
+}
+
+static int
+dw_mipi_csi_init_cfg(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg)
+{
+ struct v4l2_mbus_framefmt *format =
+ v4l2_subdev_get_try_format(sd, cfg, 0);
+
+ format->colorspace = V4L2_COLORSPACE_SRGB;
+ format->code = dw_mipi_csi_formats[0].code;
+ format->width = MIN_WIDTH;
+ format->height = MIN_HEIGHT;
+ format->field = V4L2_FIELD_NONE;
+
+ return 0;
+}
+
+static struct v4l2_subdev_core_ops dw_mipi_csi_core_ops = {
+ .s_power = dw_mipi_csi_s_power,
+};
+
+static struct v4l2_subdev_pad_ops dw_mipi_csi_pad_ops = {
+ .init_cfg = dw_mipi_csi_init_cfg,
+ .enum_mbus_code = dw_mipi_csi_enum_mbus_code,
+ .get_fmt = dw_mipi_csi_get_fmt,
+ .set_fmt = dw_mipi_csi_set_fmt,
+};
+
+static struct v4l2_subdev_ops dw_mipi_csi_subdev_ops = {
+ .core = &dw_mipi_csi_core_ops,
+ .pad = &dw_mipi_csi_pad_ops,
+};
+
+static irqreturn_t dw_mipi_csi_irq1(int irq, void *dev_id)
+{
+ struct mipi_csi_dev *csi_dev = dev_id;
+
+ dw_mipi_csi_irq_handler(csi_dev);
+
+ return IRQ_HANDLED;
+}
+
+static int
+dw_mipi_csi_parse_dt(struct platform_device *pdev, struct mipi_csi_dev *dev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct v4l2_fwnode_endpoint endpoint;
+ int ret;
+
+ ret = of_property_read_u32(node, "snps,output-type", &dev->hw.output);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't read output-type\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(node, "snps,ipi-mode", &dev->hw.ipi_mode);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't read ipi-mode\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(node, "snps,ipi-auto-flush",
+ &dev->hw.ipi_auto_flush);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't read ipi-auto-flush\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(node, "snps,ipi-color-mode",
+ &dev->hw.ipi_color_mode);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't read ipi-color-mode\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(node, "snps,virtual-channel",
+ &dev->hw.virtual_ch);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't read virtual-channel\n");
+ return ret;
+ }
+
+ node = of_graph_get_next_endpoint(node, NULL);
+ if (!node) {
+ dev_err(&pdev->dev, "No port node at %s\n",
+ pdev->dev.of_node->full_name);
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &endpoint);
+ if (ret)
+ goto err;
+
+ dev->index = endpoint.base.port - 1;
+ if (dev->index >= CSI_MAX_ENTITIES) {
+ ret = -ENXIO;
+ goto err;
+ }
+ dev->hw.num_lanes = endpoint.bus.mipi_csi2.num_data_lanes;
+
+err:
+ of_node_put(node);
+ return ret;
+}
+
+static ssize_t csih_version_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct mipi_csi_dev *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "v.%d.%d*\n", csi_dev->hw_version_major,
+ csi_dev->hw_version_minor);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t n_lanes_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long lanes;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct mipi_csi_dev *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 10, &lanes);
+ if (ret < 0)
+ return ret;
+
+ if (lanes > 8) {
+ dev_err(dev, "Invalid number of lanes %lu\n", lanes);
+ return count;
+ }
+
+ dev_info(dev, "Lanes %lu\n", lanes);
+ csi_dev->hw.num_lanes = lanes;
+
+ return count;
+}
+static ssize_t n_lanes_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct mipi_csi_dev *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "Lanes %d\n", csi_dev->hw.num_lanes);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t csih_reset_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct mipi_csi_dev *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ /* Reset Controller and DPHY */
+ phy_reset(csi_dev->phy);
+ dw_mipi_csi_reset(csi_dev);
+
+ snprintf(buffer, 10, "Reset\n");
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t dt_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long dt;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct mipi_csi_dev *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 16, &dt);
+ if (ret < 0)
+ return ret;
+
+ if ((dt < 0x18) || (dt > 0x2F)) {
+ dev_err(dev, "Invalid data type %lx\n", dt);
+ return count;
+ }
+
+ dev_info(dev, "Data type %lx\n", dt);
+ csi_dev->ipi_dt = dt;
+
+ return count;
+}
+
+static ssize_t dt_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct mipi_csi_dev *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "DT %x\n", csi_dev->ipi_dt);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static DEVICE_ATTR_RO(csih_version);
+static DEVICE_ATTR_RO(csih_reset);
+static DEVICE_ATTR_RW(n_lanes);
+static DEVICE_ATTR_RW(dt);
+
+static const struct of_device_id dw_mipi_csi_of_match[];
+
+static int csi_plat_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id;
+ struct device *dev = &pdev->dev;
+ struct resource *res = NULL;
+ struct mipi_csi_dev *mipi_csi;
+ int ret = -ENOMEM;
+
+ mipi_csi = devm_kzalloc(dev, sizeof(*mipi_csi), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ mutex_init(&mipi_csi->lock);
+ spin_lock_init(&mipi_csi->slock);
+ mipi_csi->dev = dev;
+
+ of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
+ if (WARN_ON(of_id == NULL))
+ return -EINVAL;
+
+ ret = dw_mipi_csi_parse_dt(pdev, mipi_csi);
+ if (ret < 0)
+ return ret;
+
+ mipi_csi->phy = devm_of_phy_get(dev, dev->of_node, NULL);
+ if (IS_ERR(mipi_csi->phy)) {
+ dev_err(dev, "No DPHY available\n");
+ return PTR_ERR(mipi_csi->phy);
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mipi_csi->base_address = devm_ioremap_resource(dev, res);
+
+ if (IS_ERR(mipi_csi->base_address)) {
+ dev_err(dev, "Base address not set.\n");
+ return PTR_ERR(mipi_csi->base_address);
+ }
+
+ mipi_csi->ctrl_irq_number = platform_get_irq(pdev, 0);
+ if (mipi_csi->ctrl_irq_number <= 0) {
+ dev_err(dev, "IRQ number not set.\n");
+ return mipi_csi->ctrl_irq_number;
+ }
+
+ mipi_csi->rst = devm_reset_control_get_optional_shared(dev, NULL);
+ if (IS_ERR(mipi_csi->rst)) {
+ ret = PTR_ERR(mipi_csi->rst);
+ dev_err(dev, "error getting reset control %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_request_irq(dev, mipi_csi->ctrl_irq_number,
+ dw_mipi_csi_irq1, IRQF_SHARED,
+ dev_name(dev), mipi_csi);
+ if (ret) {
+ dev_err(dev, "IRQ failed\n");
+ goto end;
+ }
+
+ v4l2_subdev_init(&mipi_csi->sd, &dw_mipi_csi_subdev_ops);
+ mipi_csi->sd.owner = THIS_MODULE;
+ snprintf(mipi_csi->sd.name, sizeof(mipi_csi->sd.name), "%s.%d",
+ CSI_HOST_NAME, mipi_csi->index);
+ mipi_csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ mipi_csi->fmt = &dw_mipi_csi_formats[0];
+
+ mipi_csi->format.code = dw_mipi_csi_formats[0].code;
+ mipi_csi->format.width = MIN_WIDTH;
+ mipi_csi->format.height = MIN_HEIGHT;
+
+ mipi_csi->sd.entity.function = MEDIA_ENT_F_IO_V4L;
+ mipi_csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ mipi_csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&mipi_csi->sd.entity,
+ CSI_PADS_NUM, mipi_csi->pads);
+
+ if (ret < 0) {
+ dev_err(dev, "Media Entity init failed\n");
+ goto entity_cleanup;
+ }
+
+ v4l2_set_subdevdata(&mipi_csi->sd, pdev);
+
+ platform_set_drvdata(pdev, &mipi_csi->sd);
+
+ device_create_file(&pdev->dev, &dev_attr_csih_version);
+ device_create_file(&pdev->dev, &dev_attr_csih_reset);
+ device_create_file(&pdev->dev, &dev_attr_n_lanes);
+ device_create_file(&pdev->dev, &dev_attr_dt);
+
+ if (mipi_csi->rst)
+ reset_control_deassert(mipi_csi->rst);
+
+ dw_mipi_csi_get_version(mipi_csi);
+ dw_mipi_csi_specific_mappings(mipi_csi);
+ dw_mipi_csi_mask_irq_power_off(mipi_csi);
+
+ dev_info(dev, "DW MIPI CSI-2 Host registered successfully HW v%u.%u\n",
+ mipi_csi->hw_version_major, mipi_csi->hw_version_minor);
+ return 0;
+
+entity_cleanup:
+ media_entity_cleanup(&mipi_csi->sd.entity);
+end:
+ return ret;
+}
+
+/**
+ * @short Exit routine - Exit point of the driver
+ * @param[in] pdev pointer to the platform device structure
+ * @return 0 on success and a negative number on failure
+ * Refer to Linux errors.
+ */
+static int csi_plat_remove(struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct mipi_csi_dev *mipi_csi = sd_to_mipi_csi_dev(sd);
+
+ dev_dbg(&pdev->dev, "Removing MIPI CSI-2 module\n");
+
+ if (mipi_csi->rst)
+ reset_control_assert(mipi_csi->rst);
+
+ media_entity_cleanup(&mipi_csi->sd.entity);
+
+ return 0;
+}
+
+/**
+ * @short of_device_id structure
+ */
+static const struct of_device_id dw_mipi_csi_of_match[] = {
+ {
+ .compatible = "snps,dw-csi-plat"},
+ { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, dw_mipi_csi_of_match);
+
+/**
+ * @short Platform driver structure
+ */
+static struct platform_driver __refdata dw_mipi_csi_pdrv = {
+ .remove = csi_plat_remove,
+ .probe = csi_plat_probe,
+ .driver = {
+ .name = CSI_HOST_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = dw_mipi_csi_of_match,
+ },
+};
+
+module_platform_driver(dw_mipi_csi_pdrv);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Luis Oliveira <[email protected]>");
+MODULE_DESCRIPTION("Synopsys DesignWare MIPI CSI-2 Host Platform driver");
diff --git a/drivers/media/platform/dwc/dw-csi-plat.h b/drivers/media/platform/dwc/dw-csi-plat.h
new file mode 100644
index 0000000..d504f5d
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-csi-plat.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * dw-csi-plat.h
+ *
+ * Copyright(c) 2018-present, Synopsys, Inc. and/or its affiliates.
+ * Luis Oliveira <[email protected]>
+ *
+ */
+
+#ifndef _DW_CSI_PLAT_H__
+#define _DW_CSI_PLAT_H__
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/ratelimit.h>
+#include <linux/reset.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <linux/wait.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#include "dw-mipi-csi.h"
+
+#define CSI_HOST_NAME "dw-mipi-csi"
+
+/* Video formats supported by the MIPI CSI-2 */
+const struct mipi_fmt dw_mipi_csi_formats[] = {
+ {
+ /* RAW 8 */
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .depth = 8,
+ },
+ {
+ /* RAW 10 */
+ .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE,
+ .depth = 10,
+ },
+ {
+ /* RGB 565 */
+ .code = MEDIA_BUS_FMT_RGB565_2X8_BE,
+ .depth = 16,
+ },
+ {
+ /* BGR 565 */
+ .code = MEDIA_BUS_FMT_RGB565_2X8_LE,
+ .depth = 16,
+ },
+ {
+ /* RGB 888 */
+ .code = MEDIA_BUS_FMT_RGB888_2X12_LE,
+ .depth = 24,
+ },
+ {
+ /* BGR 888 */
+ .code = MEDIA_BUS_FMT_RGB888_2X12_BE,
+ .depth = 24,
+ },
+};
+
+static inline struct mipi_csi_dev *sd_to_mipi_csi_dev(struct v4l2_subdev *sdev)
+{
+ return container_of(sdev, struct mipi_csi_dev, sd);
+}
+
+#endif /* _DW_CSI_PLAT_H__ */
diff --git a/drivers/media/platform/dwc/dw-mipi-csi.c b/drivers/media/platform/dwc/dw-mipi-csi.c
new file mode 100644
index 0000000..578998a
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-mipi-csi.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * dw-mipi-csi.c
+ *
+ * Copyright(c) 2018-present, Synopsys, Inc. and/or its affiliates.
+ * Luis Oliveira <[email protected]>
+ *
+ */
+
+#include "dw-mipi-csi.h"
+
+static struct R_CSI2 reg = {
+ .VERSION = 0x00,
+ .N_LANES = 0x04,
+ .CTRL_RESETN = 0x08,
+ .INTERRUPT = 0x0C,
+ .DATA_IDS_1 = 0x10,
+ .DATA_IDS_2 = 0x14,
+ .IPI_MODE = 0x80,
+ .IPI_VCID = 0x84,
+ .IPI_DATA_TYPE = 0x88,
+ .IPI_MEM_FLUSH = 0x8C,
+ .IPI_HSA_TIME = 0x90,
+ .IPI_HBP_TIME = 0x94,
+ .IPI_HSD_TIME = 0x98,
+ .IPI_HLINE_TIME = 0x9C,
+ .IPI_SOFTRSTN = 0xA0,
+ .IPI_ADV_FEATURES = 0xAC,
+ .IPI_VSA_LINES = 0xB0,
+ .IPI_VBP_LINES = 0xB4,
+ .IPI_VFP_LINES = 0xB8,
+ .IPI_VACTIVE_LINES = 0xBC,
+ .INT_PHY_FATAL = 0xe0,
+ .MASK_INT_PHY_FATAL = 0xe4,
+ .FORCE_INT_PHY_FATAL = 0xe8,
+ .INT_PKT_FATAL = 0xf0,
+ .MASK_INT_PKT_FATAL = 0xf4,
+ .FORCE_INT_PKT_FATAL = 0xf8,
+ .INT_PHY = 0x110,
+ .MASK_INT_PHY = 0x114,
+ .FORCE_INT_PHY = 0x118,
+ .INT_LINE = 0x130,
+ .MASK_INT_LINE = 0x134,
+ .FORCE_INT_LINE = 0x138,
+ .INT_IPI = 0x140,
+ .MASK_INT_IPI = 0x144,
+ .FORCE_INT_IPI = 0x148,
+};
+struct interrupt_type csi_int = {
+ .PHY_FATAL = BIT(0),
+ .PKT_FATAL = BIT(1),
+ .PHY = BIT(16),
+};
+static void dw_mipi_csi_write(struct mipi_csi_dev *dev,
+ unsigned int address, unsigned int data)
+{
+ iowrite32(data, dev->base_address + address);
+}
+
+static u32 dw_mipi_csi_read(struct mipi_csi_dev *dev, unsigned long address)
+{
+ return ioread32(dev->base_address + address);
+}
+
+void dw_mipi_csi_write_part(struct mipi_csi_dev *dev,
+ unsigned long address, unsigned long data,
+ unsigned char shift, unsigned char width)
+{
+ u32 mask = (1 << width) - 1;
+ u32 temp = dw_mipi_csi_read(dev, address);
+
+ temp &= ~(mask << shift);
+ temp |= (data & mask) << shift;
+ dw_mipi_csi_write(dev, address, temp);
+}
+
+void dw_mipi_csi_reset(struct mipi_csi_dev *csi_dev)
+{
+ dw_mipi_csi_write(csi_dev, reg.CTRL_RESETN, 0);
+ usleep_range(100, 200);
+ dw_mipi_csi_write(csi_dev, reg.CTRL_RESETN, 1);
+}
+
+int dw_mipi_csi_mask_irq_power_off(struct mipi_csi_dev *csi_dev)
+{
+ if ((csi_dev->hw_version_major) == 1) {
+
+ /* set only one lane (lane 0) as active (ON) */
+ dw_mipi_csi_write(csi_dev, reg.N_LANES, 0);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY_FATAL, 0);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT_FATAL, 0);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY, 0);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT, 0);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_LINE, 0);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_IPI, 0);
+
+ /* only for version 1.30 */
+ if ((csi_dev->hw_version_minor) == 30)
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_FRAME_FATAL, 0);
+
+ dw_mipi_csi_write(csi_dev, reg.CTRL_RESETN, 0);
+
+ /* only for version 1.40 */
+ if ((csi_dev->hw_version_minor) == 40) {
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_BNDRY_FRAME_FATAL, 0);
+ dw_mipi_csi_write(csi_dev, reg.MSK_SEQ_FRAME_FATAL, 0);
+ dw_mipi_csi_write(csi_dev, reg.MSK_CRC_FRAME_FATAL, 0);
+ dw_mipi_csi_write(csi_dev, reg.MSK_PLD_CRC_FATAL, 0);
+ dw_mipi_csi_write(csi_dev, reg.MSK_DATA_ID, 0);
+ dw_mipi_csi_write(csi_dev, reg.MSK_ECC_CORRECT, 0);
+ }
+ }
+
+ return 0;
+}
+
+int dw_mipi_csi_hw_stdby(struct mipi_csi_dev *csi_dev)
+{
+ if ((csi_dev->hw_version_major) == 1) {
+
+ /* set only one lane (lane 0) as active (ON) */
+ dw_mipi_csi_reset(csi_dev);
+ dw_mipi_csi_write(csi_dev, reg.N_LANES, 0);
+ phy_init(csi_dev->phy);
+
+ /* only for version 1.30 */
+ if ((csi_dev->hw_version_minor) == 30)
+ dw_mipi_csi_write(csi_dev,
+ reg.MASK_INT_FRAME_FATAL, 0xFFFFFFFF);
+
+ /* common */
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY_FATAL, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT_FATAL, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_LINE, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_IPI, 0xFFFFFFFF);
+
+ /* only for version 1.40 */
+ if ((csi_dev->hw_version_minor) == 40) {
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_BNDRY_FRAME_FATAL, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_SEQ_FRAME_FATAL, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_CRC_FRAME_FATAL, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_PLD_CRC_FATAL, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev, reg.MSK_DATA_ID, 0xFFFFFFFF);
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_ECC_CORRECT, 0xFFFFFFFF);
+ }
+ }
+ return 0;
+}
+
+void dw_mipi_csi_set_ipi_fmt(struct mipi_csi_dev *csi_dev)
+{
+ struct device *dev = csi_dev->dev;
+
+ if (csi_dev->ipi_dt)
+ dw_mipi_csi_write(csi_dev, reg.IPI_DATA_TYPE, csi_dev->ipi_dt);
+ else {
+ switch (csi_dev->fmt->code) {
+ case MEDIA_BUS_FMT_RGB565_2X8_BE:
+ case MEDIA_BUS_FMT_RGB565_2X8_LE:
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_DATA_TYPE, CSI_2_RGB565);
+ dev_dbg(dev, "DT: RGB 565");
+ break;
+
+ case MEDIA_BUS_FMT_RGB888_2X12_LE:
+ case MEDIA_BUS_FMT_RGB888_2X12_BE:
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_DATA_TYPE, CSI_2_RGB888);
+ dev_dbg(dev, "DT: RGB 888");
+ break;
+ case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_DATA_TYPE, CSI_2_RAW10);
+ dev_dbg(dev, "DT: RAW 10");
+ break;
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_DATA_TYPE, CSI_2_RAW8);
+ dev_dbg(dev, "DT: RAW 8");
+ break;
+ default:
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_DATA_TYPE, CSI_2_RGB565);
+ dev_dbg(dev, "Error");
+ break;
+ }
+ }
+}
+
+void dw_mipi_csi_fill_timings(struct mipi_csi_dev *dev,
+ const struct v4l2_bt_timings *bt)
+{
+ if (bt == NULL)
+ return;
+
+ dev->hw.hsa = bt->hsync;
+ dev->hw.hbp = bt->hbackporch;
+ dev->hw.hsd = bt->hsync;
+ dev->hw.htotal = bt->height + bt->vfrontporch +
+ bt->vsync + bt->vbackporch;
+ dev->hw.vsa = bt->vsync;
+ dev->hw.vbp = bt->vbackporch;
+ dev->hw.vfp = bt->vfrontporch;
+ dev->hw.vactive = bt->height;
+}
+
+void dw_mipi_csi_start(struct mipi_csi_dev *csi_dev)
+{
+ const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[0].bt;
+ struct device *dev = csi_dev->dev;
+
+ dw_mipi_csi_fill_timings(csi_dev, bt);
+ dw_mipi_csi_write(csi_dev, reg.N_LANES, (csi_dev->hw.num_lanes - 1));
+ dev_dbg(dev, "N Lanes: %d\n", csi_dev->hw.num_lanes);
+
+ /* IPI Related Configuration */
+ if ((csi_dev->hw.output == IPI_OUT)
+ || (csi_dev->hw.output == BOTH_OUT)) {
+
+ if (csi_dev->hw_version_major >= 1) {
+ if (csi_dev->hw_version_minor >= 20)
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_ADV_FEATURES, 0x30000);
+
+ if (csi_dev->hw_version_minor >= 30)
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_SOFTRSTN, 0x1);
+ }
+ /* address | data, | shift | width */
+ dw_mipi_csi_write_part(csi_dev, reg.IPI_MODE, 1, 24, 1);
+ dw_mipi_csi_write_part(csi_dev,
+ reg.IPI_MODE,
+ csi_dev->hw.ipi_mode,
+ 0, 1);
+
+ dw_mipi_csi_write_part(csi_dev,
+ reg.IPI_MODE,
+ csi_dev->hw.ipi_color_mode,
+ 8, 1);
+
+ dw_mipi_csi_write_part(csi_dev,
+ reg.IPI_VCID,
+ csi_dev->hw.virtual_ch,
+ 0, 2);
+
+ dw_mipi_csi_write_part(csi_dev,
+ reg.IPI_MEM_FLUSH,
+ csi_dev->hw.ipi_auto_flush,
+ 8, 1);
+
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_HSA_TIME, csi_dev->hw.hsa);
+
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_HBP_TIME, csi_dev->hw.hbp);
+
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_HSD_TIME, csi_dev->hw.hsd);
+
+ dev_dbg(dev, "IPI enable\n");
+ dev_dbg(dev, "IPI MODE: %d\n", csi_dev->hw.ipi_mode);
+ dev_dbg(dev, "Color Mode: %d\n", csi_dev->hw.ipi_color_mode);
+ dev_dbg(dev, "Virtual Channel: %d\n", csi_dev->hw.virtual_ch);
+ dev_dbg(dev, "Auto-flush: %d\n", csi_dev->hw.ipi_auto_flush);
+ dev_dbg(dev, "HSA: %d\n", csi_dev->hw.hsa);
+ dev_dbg(dev, "HBP: %d\n", csi_dev->hw.hbp);
+ dev_dbg(dev, "HSD: %d\n", csi_dev->hw.hsd);
+
+ if (csi_dev->hw.ipi_mode == AUTO_TIMING) {
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_HLINE_TIME, csi_dev->hw.htotal);
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_VSA_LINES, csi_dev->hw.vsa);
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_VBP_LINES, csi_dev->hw.vbp);
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_VFP_LINES, csi_dev->hw.vfp);
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_VACTIVE_LINES, csi_dev->hw.vactive);
+ dev_dbg(dev,
+ "Horizontal Total: %d\n", csi_dev->hw.htotal);
+ dev_dbg(dev,
+ "Vertical Sync Active: %d\n", csi_dev->hw.vsa);
+ dev_dbg(dev,
+ "Vertical Back Porch: %d\n", csi_dev->hw.vbp);
+ dev_dbg(dev,
+ "Vertical Front Porch: %d\n", csi_dev->hw.vfp);
+ dev_dbg(dev,
+ "Vertical Active: %d\n", csi_dev->hw.vactive);
+ }
+ }
+ phy_power_on(csi_dev->phy);
+}
+
+int dw_mipi_csi_irq_handler(struct mipi_csi_dev *csi_dev)
+{
+ struct device *dev = csi_dev->dev;
+ u32 global_int_status, i_sts;
+ unsigned long flags;
+
+ global_int_status = dw_mipi_csi_read(csi_dev, reg.INTERRUPT);
+ spin_lock_irqsave(&csi_dev->slock, flags);
+
+ if (global_int_status & csi_int.PHY_FATAL) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PHY_FATAL);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: PHY FATAL: %08X\n",
+ reg.INT_PHY_FATAL, i_sts);
+ }
+
+ if (global_int_status & csi_int.PKT_FATAL) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PKT_FATAL);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: PKT FATAL: %08X\n",
+ reg.INT_PKT_FATAL, i_sts);
+ }
+
+ if ((global_int_status & csi_int.FRAME_FATAL)
+ && ((csi_dev->hw_version_major) == 1)
+ && ((csi_dev->hw_version_minor) == 30)) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_FRAME_FATAL);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: FRAME FATAL: %08X\n",
+ reg.INT_FRAME_FATAL, i_sts);
+ }
+
+ if (global_int_status & csi_int.PHY) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PHY);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: PHY: %08X\n",
+ reg.INT_PHY, i_sts);
+ }
+
+ if (global_int_status & csi_int.PKT) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PKT);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: PKT: %08X\n",
+ reg.INT_PKT, i_sts);
+ }
+
+ if (global_int_status & csi_int.LINE) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_LINE);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: LINE: %08X\n",
+ reg.INT_LINE, i_sts);
+ }
+
+ if (global_int_status & csi_int.IPI) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_IPI);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: IPI: %08X\n",
+ reg.INT_IPI, i_sts);
+ }
+
+ if (global_int_status & csi_int.BNDRY_FRAME_FATAL) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.ST_BNDRY_FRAME_FATAL);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: ST_BNDRY_FRAME_FATAL: %08X\n",
+ reg.ST_BNDRY_FRAME_FATAL, i_sts);
+ }
+
+ if (global_int_status & csi_int.SEQ_FRAME_FATAL) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.ST_SEQ_FRAME_FATAL);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: ST_SEQ_FRAME_FATAL: %08X\n",
+ reg.ST_SEQ_FRAME_FATAL, i_sts);
+ }
+
+ if (global_int_status & csi_int.CRC_FRAME_FATAL) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.ST_CRC_FRAME_FATAL);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: ST_CRC_FRAME_FATAL: %08X\n",
+ reg.ST_CRC_FRAME_FATAL, i_sts);
+ }
+
+ if (global_int_status & csi_int.PLD_CRC_FATAL) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.ST_PLD_CRC_FATAL);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: ST_PLD_CRC_FATAL: %08X\n",
+ reg.ST_PLD_CRC_FATAL, i_sts);
+ }
+
+ if (global_int_status & csi_int.DATA_ID) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.ST_DATA_ID);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: ST_DATA_ID: %08X\n",
+ reg.ST_DATA_ID, i_sts);
+ }
+
+ if (global_int_status & csi_int.ECC_CORRECTED) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.ST_ECC_CORRECT);
+ dev_err_ratelimited(dev,
+ "interrupt %08X: ST_ECC_CORRECT: %08X\n",
+ reg.ST_ECC_CORRECT, i_sts);
+ }
+
+ spin_unlock_irqrestore(&csi_dev->slock, flags);
+
+ return 1;
+}
+
+void dw_mipi_csi_get_version(struct mipi_csi_dev *csi_dev)
+{
+ uint32_t hw_version;
+
+ hw_version = dw_mipi_csi_read(csi_dev, reg.VERSION);
+ csi_dev->hw_version_major = (uint8_t) ((hw_version >> 24) - '0');
+ csi_dev->hw_version_minor = (uint8_t) ((hw_version >> 16) - '0');
+ csi_dev->hw_version_minor = csi_dev->hw_version_minor * 10;
+ csi_dev->hw_version_minor += (uint8_t) ((hw_version >> 8) - '0');
+}
+
+int dw_mipi_csi_specific_mappings(struct mipi_csi_dev *csi_dev)
+{
+ struct device *dev = csi_dev->dev;
+
+ if ((csi_dev->hw_version_major) == 1)
+ if ((csi_dev->hw_version_minor) == 30) {
+
+ dev_dbg(dev, "We are version 30");
+ /*
+ * Hardware registers that were
+ * exclusive to version < 1.40
+ */
+ reg.INT_FRAME_FATAL = 0x100;
+ reg.MASK_INT_FRAME_FATAL = 0x104;
+ reg.FORCE_INT_FRAME_FATAL = 0x108;
+ reg.INT_PKT = 0x120;
+ reg.MASK_INT_PKT = 0x124;
+ reg.FORCE_INT_PKT = 0x128;
+
+ /* interrupt source present until this release */
+ csi_int.PKT = BIT(17);
+ csi_int.LINE = BIT(18);
+ csi_int.IPI = BIT(19);
+ csi_int.FRAME_FATAL = BIT(2);
+
+ } else if ((csi_dev->hw_version_minor) == 40) {
+ dev_dbg(dev, "We are version 40");
+ /*
+ * HW registers that were added
+ * to version 1.40
+ */
+ reg.ST_BNDRY_FRAME_FATAL = 0x280;
+ reg.MSK_BNDRY_FRAME_FATAL = 0x284;
+ reg.FORCE_BNDRY_FRAME_FATAL = 0x288;
+ reg.ST_SEQ_FRAME_FATAL = 0x290;
+ reg.MSK_SEQ_FRAME_FATAL = 0x294;
+ reg.FORCE_SEQ_FRAME_FATAL = 0x298;
+ reg.ST_CRC_FRAME_FATAL = 0x2a0;
+ reg.MSK_CRC_FRAME_FATAL = 0x2a4;
+ reg.FORCE_CRC_FRAME_FATAL = 0x2a8;
+ reg.ST_PLD_CRC_FATAL = 0x2b0;
+ reg.MSK_PLD_CRC_FATAL = 0x2b4;
+ reg.FORCE_PLD_CRC_FATAL = 0x2b8;
+ reg.ST_DATA_ID = 0x2c0;
+ reg.MSK_DATA_ID = 0x2c4;
+ reg.FORCE_DATA_ID = 0x2c8;
+ reg.ST_ECC_CORRECT = 0x2d0;
+ reg.MSK_ECC_CORRECT = 0x2d4;
+ reg.FORCE_ECC_CORRECT = 0x2d8;
+ reg.DATA_IDS_VC_1 = 0x0;
+ reg.DATA_IDS_VC_2 = 0x0;
+ reg.VC_EXTENSION = 0x0;
+
+ /* interrupts map were changed */
+ csi_int.LINE = BIT(17);
+ csi_int.IPI = BIT(18);
+ csi_int.BNDRY_FRAME_FATAL = BIT(2);
+ csi_int.SEQ_FRAME_FATAL = BIT(3);
+ csi_int.CRC_FRAME_FATAL = BIT(4);
+ csi_int.PLD_CRC_FATAL = BIT(5);
+ csi_int.DATA_ID = BIT(6);
+ csi_int.ECC_CORRECTED = BIT(7);
+
+ } else
+ dev_info(dev, "Version minor not supported.");
+ else
+ dev_info(dev, "Version major not supported.");
+
+ return 0;
+}
diff --git a/drivers/media/platform/dwc/dw-mipi-csi.h b/drivers/media/platform/dwc/dw-mipi-csi.h
new file mode 100644
index 0000000..2119027
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-mipi-csi.h
@@ -0,0 +1,202 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * DWC MIPI CSI-2 Host device driver
+ *
+ * Copyright (C) 2018 Synopsys, Inc. All rights reserved.
+ * Author: Luis Oliveira <[email protected]>
+ *
+ */
+
+#ifndef _DW_MIPI_CSI_H__
+#define _DW_MIPI_CSI_H__
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/phy/phy.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/dwc/dw-mipi-csi-pltfrm.h>
+
+/* DW MIPI CSI-2 register addresses*/
+
+struct R_CSI2 {
+ u16 VERSION;
+ u16 N_LANES;
+ u16 CTRL_RESETN;
+ u16 INTERRUPT;
+ u16 DATA_IDS_1;
+ u16 DATA_IDS_2;
+ u16 DATA_IDS_VC_1;
+ u16 DATA_IDS_VC_2;
+ u16 IPI_MODE;
+ u16 IPI_VCID;
+ u16 IPI_DATA_TYPE;
+ u16 IPI_MEM_FLUSH;
+ u16 IPI_HSA_TIME;
+ u16 IPI_HBP_TIME;
+ u16 IPI_HSD_TIME;
+ u16 IPI_HLINE_TIME;
+ u16 IPI_SOFTRSTN;
+ u16 IPI_ADV_FEATURES;
+ u16 IPI_VSA_LINES;
+ u16 IPI_VBP_LINES;
+ u16 IPI_VFP_LINES;
+ u16 IPI_VACTIVE_LINES;
+ u16 VC_EXTENSION;
+ u16 INT_PHY_FATAL;
+ u16 MASK_INT_PHY_FATAL;
+ u16 FORCE_INT_PHY_FATAL;
+ u16 INT_PKT_FATAL;
+ u16 MASK_INT_PKT_FATAL;
+ u16 FORCE_INT_PKT_FATAL;
+ u16 INT_FRAME_FATAL;
+ u16 MASK_INT_FRAME_FATAL;
+ u16 FORCE_INT_FRAME_FATAL;
+ u16 INT_PHY;
+ u16 MASK_INT_PHY;
+ u16 FORCE_INT_PHY;
+ u16 INT_PKT;
+ u16 MASK_INT_PKT;
+ u16 FORCE_INT_PKT;
+ u16 INT_LINE;
+ u16 MASK_INT_LINE;
+ u16 FORCE_INT_LINE;
+ u16 INT_IPI;
+ u16 MASK_INT_IPI;
+ u16 FORCE_INT_IPI;
+ u16 ST_BNDRY_FRAME_FATAL;
+ u16 MSK_BNDRY_FRAME_FATAL;
+ u16 FORCE_BNDRY_FRAME_FATAL;
+ u16 ST_SEQ_FRAME_FATAL;
+ u16 MSK_SEQ_FRAME_FATAL;
+ u16 FORCE_SEQ_FRAME_FATAL;
+ u16 ST_CRC_FRAME_FATAL;
+ u16 MSK_CRC_FRAME_FATAL;
+ u16 FORCE_CRC_FRAME_FATAL;
+ u16 ST_PLD_CRC_FATAL;
+ u16 MSK_PLD_CRC_FATAL;
+ u16 FORCE_PLD_CRC_FATAL;
+ u16 ST_DATA_ID;
+ u16 MSK_DATA_ID;
+ u16 FORCE_DATA_ID;
+ u16 ST_ECC_CORRECT;
+ u16 MSK_ECC_CORRECT;
+ u16 FORCE_ECC_CORRECT;
+};
+/* Interrupt Masks */
+struct interrupt_type {
+ u32 PHY_FATAL;
+ u32 PKT_FATAL;
+ u32 FRAME_FATAL;
+ u32 PHY;
+ u32 PKT;
+ u32 LINE;
+ u32 IPI;
+ u32 BNDRY_FRAME_FATAL;
+ u32 SEQ_FRAME_FATAL;
+ u32 CRC_FRAME_FATAL;
+ u32 PLD_CRC_FATAL;
+ u32 DATA_ID;
+ u32 ECC_CORRECTED;
+};
+
+/* IPI Data Types */
+enum data_type {
+ CSI_2_YUV420_8 = 0x18,
+ CSI_2_YUV420_10 = 0x19,
+ CSI_2_YUV420_8_LEG = 0x1A,
+ CSI_2_YUV420_8_SHIFT = 0x1C,
+ CSI_2_YUV420_10_SHIFT = 0x1D,
+ CSI_2_YUV422_8 = 0x1E,
+ CSI_2_YUV422_10 = 0x1F,
+ CSI_2_RGB444 = 0x20,
+ CSI_2_RGB555 = 0x21,
+ CSI_2_RGB565 = 0x22,
+ CSI_2_RGB666 = 0x23,
+ CSI_2_RGB888 = 0x24,
+ CSI_2_RAW6 = 0x28,
+ CSI_2_RAW7 = 0x29,
+ CSI_2_RAW8 = 0x2A,
+ CSI_2_RAW10 = 0x2B,
+ CSI_2_RAW12 = 0x2C,
+ CSI_2_RAW14 = 0x2D,
+};
+
+/* DWC MIPI CSI-2 output types */
+enum output {
+ IPI_OUT = 0,
+ IDI_OUT = 1,
+ BOTH_OUT = 2
+};
+
+/* IPI output types */
+enum ipi_output {
+ CAMERA_TIMING = 0,
+ AUTO_TIMING = 1
+};
+
+/* Format template */
+struct mipi_fmt {
+ u32 code;
+ u8 depth;
+};
+
+/* CSI specific configuration */
+struct csi_hw {
+
+ uint32_t num_lanes;
+ uint32_t output;
+ uint32_t ipi_mode;
+ uint32_t ipi_color_mode;
+ uint32_t ipi_auto_flush;
+ uint32_t virtual_ch;
+ uint32_t hsa;
+ uint32_t hbp;
+ uint32_t hsd;
+ uint32_t htotal;
+ uint32_t vsa;
+ uint32_t vbp;
+ uint32_t vfp;
+ uint32_t vactive;
+};
+
+/* Structure to embed device driver information */
+struct mipi_csi_dev {
+ struct v4l2_subdev sd;
+ struct video_device vdev;
+ struct device *dev;
+
+ struct mutex lock;
+ spinlock_t slock;
+ struct media_pad pads[CSI_PADS_NUM];
+ u8 index;
+
+ /* Store current format */
+ const struct mipi_fmt *fmt;
+ struct v4l2_mbus_framefmt format;
+
+ /* Device Tree Information */
+ void __iomem *base_address;
+ uint32_t ctrl_irq_number;
+
+ struct csi_hw hw;
+ struct phy *phy;
+ struct reset_control *rst;
+
+ u8 ipi_dt;
+ u8 hw_version_major;
+ u16 hw_version_minor;
+};
+
+void dw_mipi_csi_reset(struct mipi_csi_dev *csi_dev);
+int dw_mipi_csi_mask_irq_power_off(struct mipi_csi_dev *csi_dev);
+int dw_mipi_csi_hw_stdby(struct mipi_csi_dev *csi_dev);
+void dw_mipi_csi_set_ipi_fmt(struct mipi_csi_dev *csi_dev);
+void dw_mipi_csi_start(struct mipi_csi_dev *csi_dev);
+int dw_mipi_csi_irq_handler(struct mipi_csi_dev *csi_dev);
+void dw_mipi_csi_get_version(struct mipi_csi_dev *csi_dev);
+int dw_mipi_csi_specific_mappings(struct mipi_csi_dev *csi_dev);
+void dw_mipi_csi_fill_timings(struct mipi_csi_dev *dev,
+ const struct v4l2_bt_timings *bt);
+
+#endif /*_DW_MIPI_CSI_H__ */
diff --git a/include/media/dwc/dw-mipi-csi-pltfrm.h b/include/media/dwc/dw-mipi-csi-pltfrm.h
new file mode 100644
index 0000000..a7cc494
--- /dev/null
+++ b/include/media/dwc/dw-mipi-csi-pltfrm.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * CSI-2 Video platform video library
+ *
+ * Copyright (C) 2018 Synopsys, Inc. All rights reserved.
+ * Author: Luis Oliveira <[email protected]>
+ *
+ */
+
+#ifndef __DW_MIPI_CSI_PLTFRM_INCLUDES_H_
+#define __DW_MIPI_CSI_PLTFRM_INCLUDES_H_
+
+#include <media/media-entity.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-mediabus.h>
+#include <media/v4l2-subdev.h>
+
+#define MAX_WIDTH 3280
+#define MAX_HEIGHT 1852
+#define MIN_WIDTH 640
+#define MIN_HEIGHT 480
+
+/* The subdevices' group IDs. */
+#define GRP_ID_SENSOR (10)
+#define GRP_ID_CSI (20)
+#define GRP_ID_VIF (30)
+#define GRP_ID_VIDEODEV (40)
+
+#define CSI_MAX_ENTITIES (2)
+#define VIF_MAX_ENTITIES (2)
+#define PLAT_MAX_SENSORS (2)
+
+enum video_dev_pads {
+ VIDEO_DEV_SD_PAD_SINK_VIF1 = 0,
+ VIDEO_DEV_SD_PAD_SINK_VIF2 = 1,
+ VIDEO_DEV_SD_PAD_SOURCE_DMA = 2,
+ VIDEO_DEV_SD_PADS_NUM = 3,
+};
+enum vif_pads {
+ VIF_PAD_SINK_CSI = 0,
+ VIF_PAD_SOURCE_DMA = 1,
+ VIF_PADS_NUM = 2,
+};
+
+enum mipi_csi_pads {
+ CSI_PAD_SINK = 0,
+ CSI_PAD_SOURCE = 1,
+ CSI_PADS_NUM = 2,
+};
+
+struct plat_csi_source_info {
+ u16 flags;
+ u16 mux_id;
+};
+
+struct plat_csi_fmt {
+ char *name;
+ u32 mbus_code;
+ u32 fourcc;
+ u8 depth;
+};
+
+struct plat_csi_media_pipeline;
+
+/*
+ * Media pipeline operations to be called from within a video node, i.e. the
+ * last entity within the pipeline. Implemented by related media device driver.
+ */
+struct plat_csi_media_pipeline_ops {
+ int (*prepare)(struct plat_csi_media_pipeline *p,
+ struct media_entity *me);
+ int (*unprepare)(struct plat_csi_media_pipeline *p);
+ int (*open)(struct plat_csi_media_pipeline *p, struct media_entity *me,
+ bool resume);
+ int (*close)(struct plat_csi_media_pipeline *p);
+ int (*set_stream)(struct plat_csi_media_pipeline *p, bool state);
+ int (*set_format)(struct plat_csi_media_pipeline *p,
+ struct v4l2_subdev_format *fmt);
+};
+
+struct plat_csi_video_entity {
+ struct video_device vdev;
+ struct plat_csi_media_pipeline *pipe;
+};
+
+struct plat_csi_media_pipeline {
+ struct media_pipeline mp;
+ const struct plat_csi_media_pipeline_ops *ops;
+};
+
+static inline struct plat_csi_video_entity
+*vdev_to_plat_csi_video_entity(struct video_device *vdev)
+{
+ return container_of(vdev, struct plat_csi_video_entity, vdev);
+}
+
+#define plat_csi_pipeline_call(ent, op, args...) \
+ (!(ent) ? -ENOENT : (((ent)->pipe->ops && (ent)->pipe->ops->op) ? \
+ (ent)->pipe->ops->op(((ent)->pipe), ##args) : -ENOIOCTLCMD)) \
+
+#endif /* __DW_MIPI_CSI_PLTFRM_INCLUDES_H_ */
--
2.9.3


2018-09-20 11:21:54

by Luis de Oliveira

[permalink] [raw]
Subject: [V2, 4/5] Documentation: dt-bindings: Document bindings for DW MIPI CSI-2 Host

Add bindings for Synopsys DesignWare MIPI CSI-2 host.

Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v2:
- no changes

.../devicetree/bindings/media/snps,dw-csi-plat.txt | 74 ++++++++++++++++++++++
1 file changed, 74 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt

diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt b/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
new file mode 100644
index 0000000..028f5eb
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
@@ -0,0 +1,74 @@
+Synopsys DesignWare CSI-2 Host controller
+
+Description
+-----------
+
+This HW block is used to receive image coming from an MIPI CSI-2 compatible
+camera.
+
+Required properties:
+- compatible: shall be "snps,dw-csi-plat"
+- reg : physical base address and size of the device memory mapped
+ registers;
+- interrupts : CSI-2 Host interrupt
+- snps,output-type : Core output to be used (IPI-> 0 or IDI->1 or BOTH->2) These
+ values choose which of the Core outputs will be used, it
+ can be Image Data Interface or Image Pixel Interface.
+- phys : List of one PHY specifier (as defined in
+ Documentation/devicetree/bindings/phy/phy-bindings.txt).
+ This PHY is a MIPI DPHY working in RX mode.
+- resets : Reference to a reset controller (optional)
+
+Optional properties(if in IPI mode):
+- snps,ipi-mode : Mode to be used when in IPI(Camera -> 0 or Controller -> 1)
+ This property defines if the controller will use the video
+ timings available
+ in the video stream or if it will use pre-defined ones.
+- snps,ipi-color-mode : Bus depth to be used in IPI (48 bits -> 0 or 16 bits -> 1)
+ This property defines the width of the IPI bus.
+- snps,ipi-auto-flush : Data auto-flush (1 -> Yes or 0 -> No). This property defines
+ if the data is automatically flushed in each vsync or if
+ this process is done manually
+- snps,virtual-channel : Virtual channel where data is present when in IPI mode. This
+ property chooses the virtual channel which IPI will use to
+ retrieve the video stream.
+
+The per-board settings:
+ - port sub-node describing a single endpoint connected to the camera as
+ described in video-interfaces.txt[1].
+
+Example:
+
+ csi2_1: csi2@3000 {
+ compatible = "snps,dw-csi-plat";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = < 0x03000 0x7FF>;
+ interrupts = <2>;
+ output-type = <2>;
+ resets = <&dw_rst 1>;
+ phys = <&mipi_dphy_rx1 0>;
+ phy-names = "csi2-dphy";
+
+ /* IPI optional Configurations */
+ snps,ipi-mode = <0>;
+ snps,ipi-color-mode = <0>;
+ snps,ipi-auto-flush = <1>;
+ snps,virtual-channel = <0>;
+
+ /* CSI-2 per-board settings */
+ port@1 {
+ reg = <1>;
+ csi1_ep1: endpoint {
+ remote-endpoint = <&camera_1>;
+ data-lanes = <1 2>;
+ };
+ };
+ port@2 {
+ csi1_ep2: endpoint {
+ remote-endpoint = <&vif1_ep>;
+ };
+ };
+ };
+
+
--
2.9.3


2018-09-20 12:26:25

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [V2, 1/5] media: platform: Add a DesignWare folder to have Synopsys drivers

Hi Luis,

Thank you for the patch.


On Thursday, 20 September 2018 14:16:39 EEST Luis Oliveira wrote:
> This patch has the intention of make the patch series more clear by creating
> a dwc folder.
>
> Signed-off-by: Luis Oliveira <[email protected]>

No issue with the content, but you should fold this in patch 3/5, it doesn't
deserve a patch on its own.

> ---
> Changelog
> v2:
> - Fix Kbuild error with no Makefile present
>
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 3 +++
> drivers/media/platform/dwc/Kconfig | 0
> drivers/media/platform/dwc/Makefile | 0
> 4 files changed, 4 insertions(+)
> create mode 100644 drivers/media/platform/dwc/Kconfig
> create mode 100644 drivers/media/platform/dwc/Makefile
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index f627587..f627a27 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -137,6 +137,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/dwc/Kconfig"
>
> config VIDEO_TI_CAL
> tristate "TI CAL (Camera Adaptation Layer) driver"
> diff --git a/drivers/media/platform/Makefile
> b/drivers/media/platform/Makefile index 6ab6200..def2f33 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -98,3 +98,6 @@ obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
> obj-y += meson/
>
> obj-y += cros-ec-cec/
> +
> +obj-y += dwc/
> +
> diff --git a/drivers/media/platform/dwc/Kconfig
> b/drivers/media/platform/dwc/Kconfig new file mode 100644
> index 0000000..e69de29
> diff --git a/drivers/media/platform/dwc/Makefile
> b/drivers/media/platform/dwc/Makefile new file mode 100644
> index 0000000..e69de29

--
Regards,

Laurent Pinchart




2018-09-20 12:30:35

by Luis de Oliveira

[permalink] [raw]
Subject: Re: [V2, 1/5] media: platform: Add a DesignWare folder to have Synopsys drivers

On 20-Sep-18 13:26, Laurent Pinchart wrote:
> Hi Luis,
>
> Thank you for the patch.
>
>
> On Thursday, 20 September 2018 14:16:39 EEST Luis Oliveira wrote:
>> This patch has the intention of make the patch series more clear by creating
>> a dwc folder.
>>
>> Signed-off-by: Luis Oliveira <[email protected]>
>
> No issue with the content, but you should fold this in patch 3/5, it doesn't
> deserve a patch on its own.
>

Thank you, I will do that in V3.

>> ---
>> Changelog
>> v2:
>> - Fix Kbuild error with no Makefile present
>>
>> drivers/media/platform/Kconfig | 1 +
>> drivers/media/platform/Makefile | 3 +++
>> drivers/media/platform/dwc/Kconfig | 0
>> drivers/media/platform/dwc/Makefile | 0
>> 4 files changed, 4 insertions(+)
>> create mode 100644 drivers/media/platform/dwc/Kconfig
>> create mode 100644 drivers/media/platform/dwc/Makefile
>>
>> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
>> index f627587..f627a27 100644
>> --- a/drivers/media/platform/Kconfig
>> +++ b/drivers/media/platform/Kconfig
>> @@ -137,6 +137,7 @@ source "drivers/media/platform/am437x/Kconfig"
>> source "drivers/media/platform/xilinx/Kconfig"
>> source "drivers/media/platform/rcar-vin/Kconfig"
>> source "drivers/media/platform/atmel/Kconfig"
>> +source "drivers/media/platform/dwc/Kconfig"
>>
>> config VIDEO_TI_CAL
>> tristate "TI CAL (Camera Adaptation Layer) driver"
>> diff --git a/drivers/media/platform/Makefile
>> b/drivers/media/platform/Makefile index 6ab6200..def2f33 100644
>> --- a/drivers/media/platform/Makefile
>> +++ b/drivers/media/platform/Makefile
>> @@ -98,3 +98,6 @@ obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
>> obj-y += meson/
>>
>> obj-y += cros-ec-cec/
>> +
>> +obj-y += dwc/
>> +
>> diff --git a/drivers/media/platform/dwc/Kconfig
>> b/drivers/media/platform/dwc/Kconfig new file mode 100644
>> index 0000000..e69de29
>> diff --git a/drivers/media/platform/dwc/Makefile
>> b/drivers/media/platform/dwc/Makefile new file mode 100644
>> index 0000000..e69de29
>


2018-09-20 13:12:59

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [V2, 2/5] Documentation: dt-bindings: Document the Synopsys MIPI DPHY Rx bindings

Hi Louis,

Thank you for the patch.

On Thursday, 20 September 2018 14:16:40 EEST Luis Oliveira wrote:
> Add device-tree bindings documentation for SNPS DesignWare MIPI D-PHY in
> RX mode.
>
> Signed-off-by: Luis Oliveira <[email protected]>
> ---
> Changelog
> v2:
> - no changes
>
> .../devicetree/bindings/phy/snps,dphy-rx.txt | 36 +++++++++++++++++++
> 1 file changed, 36 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
>
> diff --git a/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
> b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt new file mode
> 100644
> index 0000000..9079f4a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
> @@ -0,0 +1,36 @@
> +Synopsys DesignWare MIPI Rx D-PHY block details
> +
> +Description
> +-----------
> +
> +The Synopsys MIPI D-PHY controller supports MIPI-DPHY in receiver mode.
> +Please refer to phy-bindings.txt for more information.
> +
> +Required properties:
> +- compatible : Shall be "snps,dphy-rx".
> +- #phy-cells : Must be 1.
> +- snps,dphy-frequency : Output frequency of the D-PHY.

If that's the frequency of the clock on the output side of the RX PHY, doesn't
it depend on the frequency on the CSI-2 (or other) bus ? Can't it vary ? Why
do you need to have it in DT ?

> +- snps,dphy-te-len : Size of the communication interface (8 bits->8 or
> 12bits->12).

We have similar properties in various bindings, such as bus-width in video-
interfaces.txt. Couldn't we use a more standard name ?

> +- reg : Physical base address and size of the device memory mapped
> + registers;

The example below shows three ranges. Could you document the ranges that are
expected ?

> +Optional properties:
> +- snps,compat-mode : Compatibility mode control

What is this ?

> +The per-board settings:
> +- gpios : Synopsys testchip used as reference uses this to change
setup
> + configurations.

Here too, what is this for ?

> +Example:
> +
> + mipi_dphy_rx1: dphy@3040 {
> + compatible = "snps,dphy-rx";
> + #phy-cells = <1>;
> + snps,dphy-frequency = <300000>;
> + snps,dphy-te-len = <12>;
> + snps,compat-mode = <1>;
> + reg = < 0x03040 0x20
> + 0x08000 0x100
> + 0x09000 0x100>;

The base addresses are pretty low, what kind of bus does this sit on ?

> + };
> +

--
Regards,

Laurent Pinchart




2018-09-20 13:21:11

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [V2, 3/5] media: platform: dwc: Add DW MIPI DPHY Rx platform

Hi Louis,

Thank you for the patch.

On Thursday, 20 September 2018 14:16:41 EEST Luis Oliveira wrote:
> Add of Synopsys MIPI D-PHY in RX mode support.
> Separated in the implementation are platform dependent probing functions.
>
> Signed-off-by: Luis Oliveira <[email protected]>
> ---
> Changelog
> v2:
> - fix uninitialization warning
> - fix SDPX License to match: Documentation/process/license-rules.rst
>
> drivers/media/platform/dwc/Kconfig | 32 ++
> drivers/media/platform/dwc/Makefile | 2 +
> drivers/media/platform/dwc/dw-dphy-plat.c | 365 ++++++++++++++++++
> drivers/media/platform/dwc/dw-dphy-rx.c | 594 +++++++++++++++++++++++++++
> drivers/media/platform/dwc/dw-dphy-rx.h | 176 +++++++++

Is there really a need to split this in two source files ? It smells of OS
abstraction layers.

> 5 files changed, 1169 insertions(+)
> create mode 100644 drivers/media/platform/dwc/dw-dphy-plat.c
> create mode 100644 drivers/media/platform/dwc/dw-dphy-rx.c
> create mode 100644 drivers/media/platform/dwc/dw-dphy-rx.h

As this is a PHY driver, shouldn't it be located in drivers/phy/ ?

> diff --git a/drivers/media/platform/dwc/Kconfig
> b/drivers/media/platform/dwc/Kconfig index e69de29..a573297 100644
> --- a/drivers/media/platform/dwc/Kconfig
> +++ b/drivers/media/platform/dwc/Kconfig
> @@ -0,0 +1,32 @@
> +#
> +# Synopsys DWC Platform drivers
> +# Most drivers here are currently for MIPI CSI-2 and MIPI DPHY support
> +
> +config DWC_MIPI_CSI2_HOST
> + bool "Synopsys Designware CSI-2 Host Controller and DPHY-RX support"

There's no CSI-2 host controller in this patch.

> + select VIDEO_DEV
> + select VIDEO_V4L2
> + select VIDEO_V4L2_SUBDEV_API
> + select VIDEOBUF2_VMALLOC
> + select VIDEOBUF2_DMA_CONTIG

All these don't seem to be needed.

> + select GENERIC_PHY
> + select VIDEO_OV5647

And I'm pretty sure you can use the PHY without an OV5647 sensor :-)

> + help
> + This selects the CSI-2 host controller support.
> +
> + If you have a controller with this interface, say Y.
> +
> + If unsure, say N.
> +
> +if DWC_MIPI_CSI2_HOST
> +
> +config DWC_MIPI_TC_DPHY_G128
> + tristate "DesignWare platform support using a G128 Test Chip"
> + depends on DWC_MIPI_CSI2_HOST
> + help
> + Synopsys Test Chip is a MIPI D-PHY for prototyping purposes.
> +
> + If unsure, say N.
> +
> +endif # DWC_MIPI_CSI2_HOST
> +
> diff --git a/drivers/media/platform/dwc/Makefile
> b/drivers/media/platform/dwc/Makefile index e69de29..8be6f68 100644
> --- a/drivers/media/platform/dwc/Makefile
> +++ b/drivers/media/platform/dwc/Makefile
> @@ -0,0 +1,2 @@
> +obj-$(CONFIG_DWC_MIPI_TC_DPHY_G128) += dw-dphy-platfrm.o
> +dw-dphy-platfrm-objs := dw-dphy-plat.o dw-dphy-rx.o
> diff --git a/drivers/media/platform/dwc/dw-dphy-plat.c
> b/drivers/media/platform/dwc/dw-dphy-plat.c new file mode 100644
> index 0000000..8e298f5
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-dphy-plat.c
> @@ -0,0 +1,365 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * dw-dphy-plat.c
> + *
> + * Copyright(c) 2018-present, Synopsys, Inc. and/or its affiliates.

I don't think that -present is right. The copyright notice should list the
years explicitly.

> + * Luis Oliveira <[email protected]>
> + *
> + */
> +
> +#include "dw-dphy-rx.h"
> +
> +static struct phy *dw_dphy_xlate(struct device *dev,
> + struct of_phandle_args *args)
> +{
> + struct dw_dphy_rx *dphy = dev_get_drvdata(dev);
> +
> + return dphy->phy;
> +}
> +
> +static ssize_t dphy_reset_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> + char buffer[15];
> +
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
> + usleep_range(100, 200);
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t dphy_freq_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + int ret;
> + unsigned long freq;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> +
> + ret = kstrtoul(buf, 10, &freq);
> + if (ret < 0)
> + return ret;
> +
> + if (freq > 2500) {
> + dev_info(dev, "Freq must be under 2500 Mhz\n");
> + return count;
> + }
> + if (freq < 80) {
> + dev_info(dev, "Freq must be over 80 Mhz\n");
> + return count;
> + }
> +
> + dev_info(dev, "Data Rate %lu Mbps\n", freq);
> + dphy->dphy_freq = freq * 1000;
> +
> + return count;
> +
> +}
> +
> +static ssize_t dphy_freq_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> + char buffer[15];
> +
> + snprintf(buffer, 15, "Freq %d\n", dphy->dphy_freq / 1000);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t dphy_addr_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> + unsigned long val;
> + int ret;
> + u8 addr, payload;
> +
> + ret = kstrtoul(buf, 32, &val);
> + if (ret < 0)
> + return ret;
> +
> + payload = (u16)val;
> + addr = (u16)(val >> 16);
> +
> + dev_info(dev, "addr 0x%lX\n", val);
> + dev_info(dev, "payload: 0x%X\n", addr);
> +
> + dev_info(dev,
> + "Addr [0x%x] -> 0x%x\n", (unsigned int)addr,
> + dw_dphy_te_read(dphy, addr));
> +
> + return count;
> +}
> +
> +static ssize_t idelay_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> + char buffer[15];
> +
> + snprintf(buffer, 15, "idelay %d\n", dw_dphy_if_get_idelay(dphy));
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t idelay_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> + int ret;
> + unsigned long val;
> + u8 lane, delay;
> +
> + ret = kstrtoul(buf, 16, &val);
> + if (ret < 0)
> + return ret;
> +
> + lane = (u8)val;
> + delay = (u8)(val >> 8);
> +
> + dev_dbg(dev, "Lanes %u\n", lane);
> + dev_dbg(dev, "Delay %u\n", delay);
> +
> + dw_dphy_if_set_idelay_lane(dphy, delay, lane);
> +
> + return count;
> +}
> +
> +static ssize_t len_config_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long length;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> +
> + ret = kstrtoul(buf, 10, &length);
> + if (ret < 0)
> + return ret;
> +
> + if (length == BIT8)
> + pr_info("Configured for 8-bit interface\n");
> + else if (length == BIT12)
> + pr_info("Configured for 12-bit interface\n");
> + else
> + return count;
> +
> + dphy->dphy_te_len = length;
> +
> + return count;
> +
> +}
> +
> +static ssize_t len_config_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> + char buffer[20];
> +
> + snprintf(buffer, 20, "Length %d\n", dphy->dphy_te_len);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t dw_dphy_g118_settle_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + int ret;
> + unsigned long lp_time;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> +
> + ret = kstrtoul(buf, 10, &lp_time);
> + if (ret < 0)
> + return ret;
> +
> + if ((lp_time > 1) && (lp_time < 10000))
> + dphy->lp_time = lp_time;
> + else {
> + pr_info("Invalid Value configuring for 1000 ns\n");
> + dphy->lp_time = 1000;
> + }
> +
> + dphy->lp_time = lp_time;
> +
> + return count;
> +
> +}
> +
> +static ssize_t dw_dphy_g118_settle_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
> + char buffer[10];
> +
> + snprintf(buffer, 10, "Settle %d ns\n", dphy->lp_time);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static DEVICE_ATTR_RO(dphy_reset);
> +static DEVICE_ATTR_RW(dphy_freq);
> +static DEVICE_ATTR_WO(dphy_addr);
> +static DEVICE_ATTR_RW(idelay);
> +static DEVICE_ATTR_RW(len_config);
> +static DEVICE_ATTR_RW(dw_dphy_g118_settle);

If you want to expose an API through sysfs it should be properly documented. I
don't think this is needed though, the PHY should be completely transparent
for userspace, it should be handled fully within the kernel.

> +static struct phy_ops dw_dphy_ops = {
> + .init = dw_dphy_init,
> + .reset = dw_dphy_reset,
> + .power_on = dw_dphy_power_on,
> + .power_off = dw_dphy_power_off,
> + .owner = THIS_MODULE,
> +};
> +
> +static int dw_dphy_rx_probe(struct platform_device *pdev)
> +{
> + struct dw_dphy_rx *dphy;
> + struct device *dev = &pdev->dev;
> + struct resource *res;
> + struct phy_provider *phy_provider;
> + struct phy *phy;
> +
> + dphy = devm_kzalloc(dev, sizeof(*dphy), GFP_KERNEL);
> + if (!dphy)
> + return -ENOMEM;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + dphy->base_address = devm_ioremap(dev, res->start, resource_size(res));
> + if (IS_ERR(dphy->base_address)) {
> + dev_err(dev, "error requesting base address\n");
> + return PTR_ERR(dphy->base_address);
> + }
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> + dphy->dphy1_if_addr = devm_ioremap_resource(dev, res);
> + if (IS_ERR(dphy->dphy1_if_addr)) {
> + dev_err(dev, "error requesting dphy 1 if regbank\n");
> + return PTR_ERR(dphy->dphy1_if_addr);
> + }
> +
> + dphy->max_lanes =
> + dw_dphy_if_read_msk(dphy, DPHYID, DPHY_ID_LANE_SUPPORT, 4);
> +
> + dphy->dphy_gen = dw_dphy_if_read_msk(dphy, DPHYID, DPHY_ID_GEN, 4);
> + dev_info(dev, "DPHY GEN %s with maximum %s lanes\n",
> + dphy->dphy_gen == GEN3 ? "3" : "2",
> + dphy->max_lanes == CTRL_8_LANES ? "8" : "4");
> +
> + if (dphy->max_lanes == CTRL_8_LANES) {
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> + dphy->dphy2_if_addr =
> + devm_ioremap(dev, res->start, resource_size(res));
> +
> + if (IS_ERR(dphy->dphy2_if_addr)) {
> + dev_err(dev, "error requesting dphy 2 if regbank\n");
> + return PTR_ERR(dphy->dphy2_if_addr);
> + }
> +
> + dphy->config_gpio = of_get_gpio(dev->of_node, 0);
> + if (!gpio_is_valid(dphy->config_gpio)) {
> + dev_err(dev, "failed to parse config gpio\n");
> + return dphy->config_gpio;
> + }
> + }
> +
> + if (of_property_read_u32(dev->of_node,
> + "snps,dphy-frequency",
> + &dphy->dphy_freq)) {
> + dev_err(dev, "failed to find dphy frequency\n");
> + return -EINVAL;
> + }
> +
> + if (of_property_read_u32(dev->of_node,
> + "snps,dphy-te-len",
> + &dphy->dphy_te_len)) {
> + dev_err(dev, "failed to find dphy te length\n");
> + return -EINVAL;
> + }
> +
> + if (of_property_read_u32(dev->of_node,
> + "snps,compat-mode",
> + &dphy->compat_mode)) {
> + dev_err(dev, "failed to find compat mode\n");
> + return -EINVAL;
> + }
> +
> + dev_set_drvdata(dev, dphy);
> + spin_lock_init(&dphy->slock);
> +
> + phy = devm_phy_create(dev, NULL, &dw_dphy_ops);
> + if (IS_ERR(phy)) {
> + dev_err(dev, "failed to create PHY\n");
> + return PTR_ERR(phy);
> + }
> +
> + dphy->phy = phy;
> + phy_set_drvdata(phy, dphy);
> +
> + phy_provider = devm_of_phy_provider_register(dev, dw_dphy_xlate);
> + if (IS_ERR(phy_provider)) {
> + dev_err(dev, "error getting phy provider\n");
> + return PTR_ERR(phy_provider);
> + }
> +
> + dphy->lp_time = 1000; /* 1000 ns */
> + dphy->lanes_config = dw_dphy_setup_config(dphy);
> + dev_dbg(dev, "rx-dphy created\n");
> +
> + device_create_file(&pdev->dev, &dev_attr_dphy_reset);
> + device_create_file(&pdev->dev, &dev_attr_dphy_freq);
> + device_create_file(&pdev->dev, &dev_attr_dphy_addr);
> + device_create_file(&pdev->dev, &dev_attr_idelay);
> + device_create_file(&pdev->dev, &dev_attr_len_config);
> + device_create_file(&pdev->dev, &dev_attr_dw_dphy_g118_settle);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id dw_dphy_rx_of_match[] = {
> + { .compatible = "snps,dphy-rx" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, dw_dphy_rx_of_match);
> +
> +static struct platform_driver dw_dphy_rx_driver = {
> + .probe = dw_dphy_rx_probe,
> + .driver = {
> + .of_match_table = dw_dphy_rx_of_match,
> + .name = "snps-dphy-rx",
> + .owner = THIS_MODULE,
> + }
> +};
> +module_platform_driver(dw_dphy_rx_driver);
> +
> +MODULE_DESCRIPTION("SNPS MIPI DPHY Rx driver");
> +MODULE_AUTHOR("Luis Oliveira <[email protected]>");
> +MODULE_LICENSE("GPL v2");

"GPL v2" means just that, while the SPDX header mentions GPL-2.0+. You can
pick your license of choice (or rather your employer should, as it seems they
own the copyright on the code based on the copyright header above), but SPDX
and MODULE_LICENSE should match.

> diff --git a/drivers/media/platform/dwc/dw-dphy-rx.c
> b/drivers/media/platform/dwc/dw-dphy-rx.c new file mode 100644
> index 0000000..4100ea5
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-dphy-rx.c
> @@ -0,0 +1,594 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Synopsys MIPI D-PHY driver
> + *
> + * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
> + * Author: Luis Oliveira <[email protected]>
> + *
> + */
> +
> +#include "dw-dphy-rx.h"
> +
> +struct range_dphy_gen2 {
> + u64 freq;
> + u8 hsfregrange;
> +};
> +
> +struct range_dphy_gen2 range_gen2[] = {
> + { 80, 0x00}, { 90, 0x10}, { 100, 0x20},
> + { 110, 0x30}, { 120, 0x01}, { 130, 0x11},
> + { 140, 0x21}, { 150, 0x31}, { 160, 0x02},
> + { 170, 0x12}, { 180, 0x22}, { 190, 0x32},
> + { 205, 0x03}, { 220, 0x13}, { 235, 0x23},
> + { 250, 0x33}, { 275, 0x04}, { 300, 0x14},
> + { 325, 0x05}, { 350, 0x15}, { 400, 0x25},
> + { 450, 0x06}, { 500, 0x16}, { 550, 0x07},
> + { 600, 0x17}, { 650, 0x08}, { 700, 0x18},
> + { 750, 0x09}, { 800, 0x19}, { 850, 0x29},
> + { 900, 0x39}, { 950, 0x0A}, {1000, 0x1A},
> + {1050, 0x2A}, {1100, 0x3A}, {1150, 0x0B},
> + {1200, 0x1B}, {1250, 0x2B}, {1300, 0x3B},
> + {1350, 0x0C}, {1400, 0x1C}, {1450, 0x2C},
> + {1500, 0x3C}, {1550, 0x0D}, {1600, 0x1D},
> + {1650, 0x2D}, {1700, 0x0E}, {1750, 0x1E},
> + {1800, 0x2E}, {1850, 0x3E}, {1900, 0x0F},
> + {1950, 0x1F}, {2000, 0x2F},
> +};
> +
> +struct range_dphy_gen3 {
> + u64 freq;
> + u8 hsfregrange;
> + u32 osc_freq_target;
> +};
> +
> +struct range_dphy_gen3 range_gen3[] = {
> +
> + { 80, 0x00, 0x1B6}, { 90, 0x10, 0x1B6}, { 100, 0x20, 0x1B6},
> + { 110, 0x30, 0x1B6}, { 120, 0x01, 0x1B6}, { 130, 0x11, 0x1B6},
> + { 140, 0x21, 0x1B6}, { 150, 0x31, 0x1B6}, { 160, 0x02, 0x1B6},
> + { 170, 0x12, 0x1B6}, { 180, 0x22, 0x1B6}, { 190, 0x32, 0x1B6},
> + { 205, 0x03, 0x1B6}, { 220, 0x13, 0x1B6}, { 235, 0x23, 0x1B6},
> + { 250, 0x33, 0x1B6}, { 275, 0x04, 0x1B6}, { 300, 0x14, 0x1B6},
> + { 325, 0x25, 0x1B6}, { 350, 0x35, 0x1B6}, { 400, 0x05, 0x1B6},
> + { 450, 0x16, 0x1B6}, { 500, 0x26, 0x1B6}, { 550, 0x37, 0x1B6},
> + { 600, 0x07, 0x1B6}, { 650, 0x18, 0x1B6}, { 700, 0x28, 0x1B6},
> + { 750, 0x39, 0x1B6}, { 800, 0x09, 0x1B6}, { 850, 0x19, 0x1B6},
> + { 900, 0x29, 0x1B6}, { 950, 0x3A, 0x1B6}, {1000, 0x0A, 0x1B6},
> + {1050, 0x1A, 0x1B6}, {1100, 0x2A, 0x1B6}, {1150, 0x3B, 0x1B6},
> + {1200, 0x0B, 0x1B6}, {1250, 0x1B, 0x1B6}, {1300, 0x2B, 0x1B6},
> + {1350, 0x3C, 0x1B6}, {1400, 0x0C, 0x1B6}, {1450, 0x1C, 0x1B6},
> + {1500, 0x2C, 0x1B6}, {1550, 0x3D, 0x10F}, {1600, 0x0D, 0x118},
> + {1650, 0x1D, 0x121}, {1700, 0x2E, 0x12A}, {1750, 0x3E, 0x132},
> + {1800, 0x0E, 0x13B}, {1850, 0x1E, 0x144}, {1900, 0x2F, 0x14D},
> + {1950, 0x3F, 0x155}, {2000, 0x0F, 0x15E}, {2050, 0x40, 0x167},
> + {2100, 0x41, 0x170}, {2150, 0x42, 0x178}, {2200, 0x43, 0x181},
> + {2250, 0x44, 0x18A}, {2300, 0x45, 0x193}, {2350, 0x46, 0x19B},
> + {2400, 0x47, 0x1A4}, {2450, 0x48, 0x1AD}, {2500, 0x49, 0x1B6}
> +};
> +
> +u8 dw_dphy_setup_config(struct dw_dphy_rx *dphy)
> +{
> + u8 ret;
> + int setup_config;
> +
> + if (dphy->max_lanes == CTRL_4_LANES)
> + return CTRL_4_LANES;
> +
> + ret = gpio_request(dphy->config_gpio, "config");
> + if (ret < 0) {
> + pr_err("could not acquire config gpio (err=%d)\n", ret);
> + return ret;
> + }
> +
> + setup_config = gpio_get_value(dphy->config_gpio);
> + pr_debug("CONFIG %s\n", setup_config == CTRL_8_LANES ? "8L" : "4+4L");
> + gpio_free(dphy->config_gpio);
> +
> + return setup_config;
> +}
> +void dw_dphy_if_write(struct dw_dphy_rx *dphy, u32 address, u32 data)
> +{
> + iowrite32(data, dphy->dphy1_if_addr + address);
> +
> + if (dphy->lanes_config == CTRL_4_LANES)
> + return;
> +
> + iowrite32(data, dphy->dphy2_if_addr + address);
> +}
> +
> +u32 dw_dphy_if_read(struct dw_dphy_rx *dphy, u64 address)
> +{
> + u32 if1 = 0, if2 = 0;
> +
> + if1 = ioread32(dphy->dphy1_if_addr + address);
> +
> + if (dphy->lanes_config == CTRL_4_LANES)
> + goto end;
> +
> + if (dphy->lanes_config == DPHYID)
> + goto end;
> +
> + if2 = ioread32(dphy->dphy2_if_addr + address);
> +
> + if (if1 != if2)
> + pr_err("Values read different for each interface\n");
> +
> +end:
> + return if1;
> +}
> +
> +void dw_dphy_write(struct dw_dphy_rx *dphy, u32 address, u32 data)
> +{
> + iowrite32(data, dphy->base_address + address);
> +
> + if (dphy->lanes_config == CTRL_4_LANES)
> + return;
> +
> + if (address == R_CSI2_DPHY_TST_CTRL0)
> + iowrite32(data, dphy->base_address + R_CSI2_DPHY2_TST_CTRL0);
> + else if (address == R_CSI2_DPHY_TST_CTRL1)
> + iowrite32(data, dphy->base_address + R_CSI2_DPHY2_TST_CTRL1);
> +}
> +
> +u32 dw_dphy_read(struct dw_dphy_rx *dphy, u64 address)
> +{
> + u32 dphy1 = 0, dphy2 = 0;
> +
> + dphy1 = ioread32(dphy->base_address + address);
> +
> + if (dphy->lanes_config == CTRL_4_LANES)
> + goto end;
> +
> + if (address == R_CSI2_DPHY_TST_CTRL0)
> + dphy2 = ioread32(dphy->base_address + R_CSI2_DPHY2_TST_CTRL0);
> + else if (address == R_CSI2_DPHY_TST_CTRL1)
> + dphy2 = ioread32(dphy->base_address + R_CSI2_DPHY2_TST_CTRL1);
> + else
> + return -ENODEV;
> +
> + if (dphy1 != dphy2)
> + pr_err("Values read different for each dphy\n");
> +
> +end:
> + return dphy1;
> +}
> +
> +void dw_dphy_write_msk(struct dw_dphy_rx *dev,
> + u64 address, u64 data, u8 shift, u8 width)
> +{
> + u32 mask = (1 << width) - 1;
> + u32 temp = dw_dphy_read(dev, address);
> +
> + temp &= ~(mask << shift);
> + temp |= (data & mask) << shift;
> + dw_dphy_write(dev, address, temp);
> +}
> +
> +static void dw_dphy_te_12b_write(struct dw_dphy_rx *dphy, u16 addr, u8
> data) +{
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, PHY_TESTDIN, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy,
> + R_CSI2_DPHY_TST_CTRL1, (u8) (addr >> 8), PHY_TESTDIN, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy,
> + R_CSI2_DPHY_TST_CTRL1, (u8) addr, PHY_TESTDIN, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy,
> + R_CSI2_DPHY_TST_CTRL1, (u8) data, PHY_TESTDIN, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> +}
> +
> +static void dw_dphy_te_8b_write(struct dw_dphy_rx *dphy, u8 addr, u8 data)
> +{
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write(dphy, R_CSI2_DPHY_TST_CTRL1, addr);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
> + dw_dphy_write(dphy, R_CSI2_DPHY_TST_CTRL1, data);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> +}
> +
> +static void dw_dphy_te_write(struct dw_dphy_rx *dphy, u16 addr, u8 data)
> +{
> +
> + if (dphy->dphy_te_len == BIT12)
> + dw_dphy_te_12b_write(dphy, addr, data);
> + else
> + dw_dphy_te_8b_write(dphy, addr, data);
> +}
> +
> +static int dw_dphy_te_12b_read(struct dw_dphy_rx *dphy, u32 addr)
> +{
> + u8 ret;
> +
> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, PHY_TESTDIN, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy,
> + R_CSI2_DPHY_TST_CTRL1, (u8) (addr >> 8), PHY_TESTDIN, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy,
> + R_CSI2_DPHY_TST_CTRL1, (u8) addr, PHY_TESTDIN, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, 0, PHY_TESTDIN);
> + ret = dw_dphy_read_msk(dphy, R_CSI2_DPHY_TST_CTRL1, PHY_TESTDOUT, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
> +
> + return ret;
> +}
> +
> +static int dw_dphy_te_8b_read(struct dw_dphy_rx *dphy, u32 addr)
> +{
> + u8 ret;
> +
> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, addr, PHY_TESTDIN, 8);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTDIN, 8);
> + ret = dw_dphy_read_msk(dphy, R_CSI2_DPHY_TST_CTRL1, PHY_TESTDOUT, 8);
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
> +
> + return ret;
> +}
> +
> +int dw_dphy_te_read(struct dw_dphy_rx *dphy, u32 addr)
> +{
> + int ret;
> +
> + if (dphy->dphy_te_len == BIT12)
> + ret = dw_dphy_te_12b_read(dphy, addr);
> + else
> + ret = dw_dphy_te_8b_read(dphy, addr);
> +
> + return ret;
> +}
> +
> +static void dw_dphy_if_init(struct dw_dphy_rx *dphy)
> +{
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, TX_PHY);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
> +}
> +
> +static void dw_dphy_gen3_12bit_tc_power_up(struct dw_dphy_rx *dphy)
> +{
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
> + dw_dphy_te_write(dphy, CFGCLKFREQRANGE_TX, 0x1C);
> +
> + /* CLKSEL | UPDATEPLL | SHADOW_CLEAR | SHADOW_CTRL | FORCEPLL */
> + dw_dphy_te_write(dphy, BYPASS, 0x3F);
> +
> + /* IO_DS3 | IO_DS2 | IO_DS1 | IO_DS0 */
> + if (dphy->dphy_freq > 1500)
> + dw_dphy_te_write(dphy, IO_DS, 0x0F);
> +
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
> +}
> +
> +static void dw_dphy_gen3_8bit_tc_power_up(struct dw_dphy_rx *dphy)
> +{
> + u32 input_freq = dphy->dphy_freq / 1000;
> +
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
> + dw_dphy_te_write(dphy, CFGCLKFREQRANGE_RX, 0x1C);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX0_MSB, 0x03);
> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX0_LSB, 0x02);
> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX1_MSB, 0x03);
> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX1_LSB, 0x02);
> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX2_MSB, 0x03);
> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX2_LSB, 0x02);
> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX3_MSB, 0x03);
> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX3_LSB, 0x02);
> + dw_dphy_te_write(dphy, BANDGAP_CTRL, 0x80);
> +
> + if (input_freq < 2000)
> + dw_dphy_te_write(dphy, HS_RX_CTRL_LANE0, 0xC0);
> +
> + if (input_freq < 1000) {
> + dw_dphy_te_write(dphy, HS_RX_CTRL_LANE1, 0xC0);
> + dw_dphy_te_write(dphy, HS_RX_CTRL_LANE2, 0xC0);
> + dw_dphy_te_write(dphy, HS_RX_CTRL_LANE3, 0xC0);
> + }
> +}
> +
> +int dw_dphy_g118_settle(struct dw_dphy_rx *dphy)
> +{
> + u32 input_freq, total_settle, settle_time, byte_clk, lp_time;
> +
> + lp_time = dphy->lp_time;
> + input_freq = dphy->dphy_freq / 1000;
> +
> + settle_time = (8 * (1000000/(input_freq))) + 115000;
> + byte_clk = (8000000/(input_freq));
> + total_settle = (settle_time + lp_time * 1000) / byte_clk;
> +
> + if (total_settle > 0xFF)
> + total_settle = 0xFF;
> +
> + return total_settle;
> +}
> +
> +static void dw_dphy_pwr_down(struct dw_dphy_rx *dphy)
> +{
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
> +
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
> + if (dphy->lanes_config == CTRL_8_LANES)
> + dw_dphy_write_msk(dphy,
> + R_CSI2_DPHY2_TST_CTRL0, 0, PHY_TESTCLK, 1);
> +
> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
> +}
> +
> +static void dw_dphy_pwr_up(struct dw_dphy_rx *dphy)
> +{
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
> + if (dphy->lanes_config == CTRL_8_LANES)
> + dw_dphy_write_msk(dphy,
> + R_CSI2_DPHY2_TST_CTRL0, 1, PHY_TESTCLK, 1);
> +
> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
> +}
> +
> +static int dw_dphy_gen3_12bit_configure(struct dw_dphy_rx *dphy)
> +{
> + u32 input_freq = dphy->dphy_freq;
> + u8 range = 0;
> +
> + pr_debug("PHY GEN 3 Freq: %u\n", input_freq);
> + for (range = 0; (range < ARRAY_SIZE(range_gen3) - 1) &&
> + ((input_freq / 1000) > range_gen3[range].freq); range++)
> + ;
> +
> + dw_dphy_gen3_12bit_tc_power_up(dphy);
> +
> + dw_dphy_te_write(dphy, RX_SYS_1, range_gen3[range].hsfregrange);
> + dw_dphy_te_write(dphy, RX_SYS_0, 0x20);
> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_2,
> + (u8) range_gen3[range].osc_freq_target);
> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_3,
> + (u8) (range_gen3[range].osc_freq_target >> 8));
> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_4, 0x01);
> +
> + if (dphy->compat_mode) {
> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_1, 0x01);
> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_0, 0x80);
> + }
> +
> + if ((dphy->compat_mode) || (input_freq <= 1500))
> + dw_dphy_te_write(dphy, RX_SYS_7, 0x38);
> +
> + return 0;
> +}
> +
> +static int dw_dphy_gen3_8bit_configure(struct dw_dphy_rx *dphy)
> +{
> + u32 input_freq = dphy->dphy_freq;
> + u8 data;
> + u8 range = 0;
> +
> + pr_debug("PHY GEN 3 Freq: %u\n", input_freq);
> + for (range = 0; (range < ARRAY_SIZE(range_gen3) - 1) &&
> + ((input_freq / 1000) > range_gen3[range].freq); range++)
> + ;
> +
> + dw_dphy_te_write(dphy, RX_SKEW_CAL, dw_dphy_g118_settle(dphy));
> + data = 1<<7 | range_gen3[range].hsfregrange;
> + dw_dphy_te_write(dphy, HSFREQRANGE_8BIT, data);
> + dw_dphy_gen3_8bit_tc_power_up(dphy);
> +
> + return 0;
> +}
> +
> +static int dw_dphy_gen2_configure(struct dw_dphy_rx *dphy)
> +{
> + u32 input_freq = dphy->dphy_freq;
> + u8 data;
> + u8 range = 0;
> +
> + /* provide an initial active-high test clear pulse in TESTCLR */
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
> +
> + pr_debug("PHY GEN 2 Freq: %u\n", input_freq);
> + for (range = 0; (range < ARRAY_SIZE(range_gen2) - 1) &&
> + ((input_freq / 1000) > range_gen2[range].freq); range++)
> + ;
> +
> + data = range_gen2[range].hsfregrange << 1;
> + dw_dphy_te_write(dphy, HSFREQRANGE_8BIT, data);
> +
> + return 0;
> +}
> +
> +static int dw_dphy_configure(struct dw_dphy_rx *dphy)
> +{
> + dw_dphy_pwr_down(dphy);
> +
> + if (dphy->dphy_gen == GEN3) {
> + dw_dphy_if_init(dphy);
> +
> + if (dphy->dphy_te_len == BIT12)
> + dw_dphy_gen3_12bit_configure(dphy);
> + else
> + dw_dphy_gen3_8bit_configure(dphy);
> + } else
> + dw_dphy_gen2_configure(dphy);
> +
> + dw_dphy_pwr_up(dphy);
> +
> + return 0;
> +}
> +
> +int dw_dphy_if_set_idelay(struct dw_dphy_rx *dphy, u8 dly, u8 cells)
> +{
> + uint32_t val = 0;
> +
> + dw_dphy_if_write(dphy, IDLYCFG, 0);
> +
> + dw_dphy_if_write(dphy, IDLYSEL, cells);
> + dw_dphy_if_write(dphy, IDLYCNTINVAL, dly);
> +
> + /* Pulse Value Set */
> + dw_dphy_if_write(dphy, IDLYCFG, 1);
> + usleep_range(10, 20);
> + dw_dphy_if_write(dphy, IDLYCFG, 0);
> +
> + /* Pulse IDELAY CTRL Reset */
> + dw_dphy_if_write(dphy, DPHY1REGRSTN, 0);
> + usleep_range(10, 20);
> + dw_dphy_if_write(dphy, DPHY1REGRSTN, 1);
> +
> + /* Get Value*/
> + val = dw_dphy_if_read(dphy, IDLYCNTOUTVAL);
> +
> + if (val != dly) {
> + pr_info("odelay config failed, set %d get %d", dly, val);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +int dw_dphy_if_get_idelay(struct dw_dphy_rx *dphy)
> +{
> + return dw_dphy_if_read(dphy, IDLYCNTOUTVAL);
> +}
> +
> +int dw_dphy_if_set_idelay_lane(struct dw_dphy_rx *dphy, u8 dly, u8 lane)
> +{
> + int cell;
> +
> + switch (lane) {
> + case 0:
> + for (cell = 3; cell <= 10; cell++)
> + dw_dphy_if_set_idelay(dphy, dly, cell);
> + break;
> + case 1:
> + for (cell = 14; cell <= 21; cell++)
> + dw_dphy_if_set_idelay(dphy, dly, cell);
> + break;
> + case 2:
> + for (cell = 24; cell <= 31; cell++)
> + dw_dphy_if_set_idelay(dphy, dly, cell);
> + break;
> + case 3:
> + for (cell = 34; cell <= 41; cell++)
> + dw_dphy_if_set_idelay(dphy, dly, cell);
> + break;
> + case 4: /* ALL */
> + dw_dphy_if_set_idelay(dphy, dly, 0x7F);
> + break;
> + default:
> + pr_err("Lane Value not recognized\n");
> + return -1;
> + }
> + return 0;
> +}
> +
> +int dw_dphy_init(struct phy *phy)
> +{
> + struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
> +
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
> +
> + return 0;
> +}
> +
> +static int dw_dphy_set_phy_state(struct dw_dphy_rx *dphy, u32 on)
> +{
> + u8 hs_freq;
> +
> + dphy->lanes_config = dw_dphy_setup_config(dphy);
> +
> + if (dphy->dphy_te_len == BIT12)
> + hs_freq = RX_SYS_1;
> + else
> + hs_freq = HSFREQRANGE_8BIT;
> +
> + if (on) {
> + dw_dphy_configure(dphy);
> + pr_debug("HS Code: 0X%x\n", dw_dphy_te_read(dphy, hs_freq));
> + } else {
> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
> + }
> +
> + return 0;
> +}
> +
> +int dw_dphy_power_on(struct phy *phy)
> +{
> + struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
> +
> + return dw_dphy_set_phy_state(dphy, 1);
> +}
> +
> +int dw_dphy_power_off(struct phy *phy)
> +{
> + struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
> +
> + return dw_dphy_set_phy_state(dphy, 0);
> +}
> +
> +int dw_dphy_reset(struct phy *phy)
> +{
> + struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
> +
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
> + usleep_range(100, 200);
> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
> +
> + return 0;
> +}
> diff --git a/drivers/media/platform/dwc/dw-dphy-rx.h
> b/drivers/media/platform/dwc/dw-dphy-rx.h new file mode 100644
> index 0000000..5375685
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-dphy-rx.h
> @@ -0,0 +1,176 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Synopsys MIPI D-PHY driver
> + *
> + * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
> + * Author: Luis Oliveira <[email protected]>
> + */
> +
> +#ifndef __PHY_SNPS_DPHY_RX_H__
> +#define __PHY_SNPS_DPHY_RX_H__
> +
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/gpio.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/spinlock.h>
> +
> +/* DPHY interface register bank*/
> +#define R_CSI2_DPHY_SHUTDOWNZ 0x0
> +#define R_CSI2_DPHY_RSTZ 0x4
> +#define R_CSI2_DPHY_RX 0x8
> +#define R_CSI2_DPHY_STOPSTATE 0xC
> +#define R_CSI2_DPHY_TST_CTRL0 0x10
> +#define R_CSI2_DPHY_TST_CTRL1 0x14
> +#define R_CSI2_DPHY2_TST_CTRL0 0x18
> +#define R_CSI2_DPHY2_TST_CTRL1 0x1C
> +
> +enum dphy_id_mask {
> + DPHY_ID_LANE_SUPPORT = 0,
> + DPHY_ID_IF = 4,
> + DPHY_ID_GEN = 8,
> +};
> +
> +enum dphy_gen_values {
> + GEN1 = 0,
> + GEN2 = 1,
> + GEN3 = 2,
> +};
> +
> +enum dphy_interface_length {
> + BIT8 = 8,
> + BIT12 = 12,
> +};
> +
> +enum tst_ctrl0 {
> + PHY_TESTCLR = 0,
> + PHY_TESTCLK = 1,
> +};
> +
> +enum tst_ctrl1 {
> + PHY_TESTDIN = 0,
> + PHY_TESTDOUT = 8,
> + PHY_TESTEN = 16,
> +};
> +
> +enum lanes_config_values {
> + CTRL_4_LANES = 0,
> + CTRL_8_LANES = 1,
> +};
> +
> +enum dphy_tc {
> + CFGCLKFREQRANGE_TX = 0x02,
> + CFGCLKFREQRANGE_RX = 0x05,
> + BYPASS = 0x20,
> + IO_DS = 0x30,
> +};
> +
> +enum dphy_8bit_interface_addr {
> + BANDGAP_CTRL = 0x24,
> + HS_RX_CTRL_LANE0 = 0x42,
> + HSFREQRANGE_8BIT = 0x44,
> + OSC_FREQ_TARGET_RX0_LSB = 0x4e,
> + OSC_FREQ_TARGET_RX0_MSB = 0x4f,
> + HS_RX_CTRL_LANE1 = 0x52,
> + OSC_FREQ_TARGET_RX1_LSB = 0x5e,
> + OSC_FREQ_TARGET_RX1_MSB = 0x5f,
> + RX_SKEW_CAL = 0x7e,
> + HS_RX_CTRL_LANE2 = 0x82,
> + OSC_FREQ_TARGET_RX2_LSB = 0x8e,
> + OSC_FREQ_TARGET_RX2_MSB = 0x8f,
> + HS_RX_CTRL_LANE3 = 0x92,
> + OSC_FREQ_TARGET_RX3_LSB = 0x9e,
> + OSC_FREQ_TARGET_RX3_MSB = 0x9f,
> +};
> +
> +enum dphy_12bit_interface_addr {
> + RX_SYS_0 = 0x01,
> + RX_SYS_1 = 0x02,
> + RX_SYS_7 = 0x08,
> + RX_RX_STARTUP_OVR_0 = 0xe0,
> + RX_RX_STARTUP_OVR_1 = 0xe1,
> + RX_RX_STARTUP_OVR_2 = 0xe2,
> + RX_RX_STARTUP_OVR_3 = 0xe3,
> + RX_RX_STARTUP_OVR_4 = 0xe4,
> +};
> +
> +/* Gen3 interface register bank*/
> +#define IDLYCFG 0x00
> +#define IDLYSEL 0x04
> +#define IDLYCNTINVAL 0x08
> +#define IDLYCNTOUTVAL 0x0c
> +#define DPHY1REGRSTN 0x10
> +#define DPHYZCALSTAT 0x14
> +#define DPHYZCALCTRL 0x18
> +#define DPHYLANE0STAT 0x1c
> +#define DPHYLANE1STAT 0x20
> +#define DPHYLANE2STAT 0x24
> +#define DPHYLANE3STAT 0x28
> +#define DPHYCLKSTAT 0x2c
> +#define DPHYZCLKCTRL 0x30
> +#define TCGENPURPOSOUT 0x34
> +#define TCGENPURPOSIN 0x38
> +#define DPHYGENERICOUT 0x3c
> +#define DPHYGENERICIN 0x40
> +#define DPHYGLUEIFTESTER 0x44
> +#define DPHYID 0x100
> +
> +enum glueiftester {
> + GLUELOGIC = 0x4,
> + RX_PHY = 0x2,
> + TX_PHY = 0x1,
> + RESET = 0x0,
> +};
> +
> +struct dw_dphy_rx {
> + spinlock_t slock;
> + struct phy *phy;
> + uint32_t dphy_freq;
> + uint32_t dphy_gen;
> + uint32_t dphy_te_len;
> + uint32_t lanes_config;
> + uint32_t max_lanes;
> + uint32_t compat_mode;
> + uint32_t lp_time;
> +
> + void __iomem *base_address; /* test interface */
> + void __iomem *dphy1_if_addr; /* gluelogic phy 1 */
> + void __iomem *dphy2_if_addr; /* gluelogic phy 2 */
> +
> + int config_gpio;
> + uint8_t setup_config;
> +};
> +
> +int dw_dphy_init(struct phy *phy);
> +int dw_dphy_reset(struct phy *phy);
> +int dw_dphy_power_off(struct phy *phy);
> +int dw_dphy_power_on(struct phy *phy);
> +
> +u8 dw_dphy_setup_config(struct dw_dphy_rx *dphy);
> +u32 dw_dphy_if_read(struct dw_dphy_rx *dphy, u64 address);
> +void dw_dphy_write(struct dw_dphy_rx *dphy, u32 address, u32 data);
> +u32 dw_dphy_read(struct dw_dphy_rx *dphy, u64 address);
> +int dw_dphy_te_read(struct dw_dphy_rx *dphy, u32 addr);
> +int dw_dphy_if_get_idelay(struct dw_dphy_rx *dphy);
> +int dw_dphy_if_set_idelay_lane(struct dw_dphy_rx *dphy, u8 dly, u8 lane);
> +
> +static inline
> +u32 dw_dphy_if_read_msk(struct dw_dphy_rx *dphy,
> + u32 address, u8 shift, u8 width)
> +{
> + return (dw_dphy_if_read(dphy, address) >> shift) & ((1 << width) - 1);
> +}
> +
> +static inline
> +u32 dw_dphy_read_msk(struct dw_dphy_rx *dev, u32 address, u8 shift, u8
> width) +{
> + return (dw_dphy_read(dev, address) >> shift) & ((1 << width) - 1);
> +}
> +#endif /*__PHY_SNPS_DPHY_RX_H__*/


--
Regards,

Laurent Pinchart




2018-09-20 13:25:15

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [V2, 4/5] Documentation: dt-bindings: Document bindings for DW MIPI CSI-2 Host

Hi Luis,

Thank you for the patch.

On Thursday, 20 September 2018 14:16:42 EEST Luis Oliveira wrote:
> Add bindings for Synopsys DesignWare MIPI CSI-2 host.
>
> Signed-off-by: Luis Oliveira <[email protected]>
> ---
> Changelog
> v2:
> - no changes
>
> .../devicetree/bindings/media/snps,dw-csi-plat.txt | 74 +++++++++++++++++++
> 1 file changed, 74 insertions(+)
> create mode 100644
> Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
>
> diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
> b/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt new file
> mode 100644
> index 0000000..028f5eb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
> @@ -0,0 +1,74 @@
> +Synopsys DesignWare CSI-2 Host controller
> +
> +Description
> +-----------
> +
> +This HW block is used to receive image coming from an MIPI CSI-2 compatible
> +camera.
> +
> +Required properties:
> +- compatible: shall be "snps,dw-csi-plat"
> +- reg : physical base address and size of the device memory mapped
> + registers;
> +- interrupts : CSI-2 Host interrupt
> +- snps,output-type : Core output to be used (IPI-> 0 or IDI->1 or BOTH-
>2)
> These
> + values choose which of the Core outputs will be used, it
> + can be Image Data Interface or Image Pixel Interface.
> +- phys : List of one PHY specifier (as defined in
> + Documentation/devicetree/bindings/phy/phy-bindings.txt).
> + This PHY is a MIPI DPHY working in RX mode.
> +- resets : Reference to a reset controller (optional)
> +
> +Optional properties(if in IPI mode):
> +- snps,ipi-mode : Mode to be used when in IPI(Camera -> 0 or Controller -
>
> 1)
> + This property defines if the controller will use the video
> + timings available
> + in the video stream or if it will use pre-defined ones.

How does one select this ?

> +- snps,ipi-color-mode : Bus depth to be used in IPI (48 bits -> 0 or 16
> bits -> 1)
> + This property defines the width of the IPI bus.

How about using the standard bus-width property in the endpoint of the output
port ?

> +- snps,ipi-auto-flush : Data auto-flush (1 -> Yes or 0 -> No). This
> property defines
> + if the data is automatically flushed in each vsync
> or if
> + this process is done manually

This seems like a configuration option, not a hardware property. I don't think
it belongs to DT.

> +- snps,virtual-channel : Virtual channel where data is present when in IPI
> mode. This
> + property chooses the virtual channel which IPI will use to
> + retrieve the video stream.

The virtual channel doesn't belong to DT, it should be queried from the sensor
at runtime (and when a sensor can send multiple data streams, that should even
be configurable).

> +The per-board settings:
> + - port sub-node describing a single endpoint connected to the camera as
> + described in video-interfaces.txt[1].

You need to explicitly list all the ports for this device, with their number
and function.

> +Example:
> +
> + csi2_1: csi2@3000 {
> + compatible = "snps,dw-csi-plat";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = < 0x03000 0x7FF>;
> + interrupts = <2>;
> + output-type = <2>;
> + resets = <&dw_rst 1>;
> + phys = <&mipi_dphy_rx1 0>;
> + phy-names = "csi2-dphy";
> +
> + /* IPI optional Configurations */
> + snps,ipi-mode = <0>;
> + snps,ipi-color-mode = <0>;
> + snps,ipi-auto-flush = <1>;
> + snps,virtual-channel = <0>;
> +
> + /* CSI-2 per-board settings */
> + port@1 {
> + reg = <1>;
> + csi1_ep1: endpoint {
> + remote-endpoint = <&camera_1>;
> + data-lanes = <1 2>;
> + };
> + };
> + port@2 {
> + csi1_ep2: endpoint {
> + remote-endpoint = <&vif1_ep>;
> + };
> + };
> + };
> +
> +

Extra blank lines.

--
Regards,

Laurent Pinchart




2018-09-20 14:27:24

by Luis de Oliveira

[permalink] [raw]
Subject: Re: [V2, 2/5] Documentation: dt-bindings: Document the Synopsys MIPI DPHY Rx bindings

On 20-Sep-18 14:11, Laurent Pinchart wrote:
> Hi Louis,
>
> Thank you for the patch.
>

Hi Laurent, thank you for your review, my answers are inline.

> On Thursday, 20 September 2018 14:16:40 EEST Luis Oliveira wrote:
>> Add device-tree bindings documentation for SNPS DesignWare MIPI D-PHY in
>> RX mode.
>>
>> Signed-off-by: Luis Oliveira <[email protected]>
>> ---
>> Changelog
>> v2:
>> - no changes
>>
>> .../devicetree/bindings/phy/snps,dphy-rx.txt | 36 +++++++++++++++++++
>> 1 file changed, 36 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
>>
>> diff --git a/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
>> b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt new file mode
>> 100644
>> index 0000000..9079f4a
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
>> @@ -0,0 +1,36 @@
>> +Synopsys DesignWare MIPI Rx D-PHY block details
>> +
>> +Description
>> +-----------
>> +
>> +The Synopsys MIPI D-PHY controller supports MIPI-DPHY in receiver mode.
>> +Please refer to phy-bindings.txt for more information.
>> +
>> +Required properties:
>> +- compatible : Shall be "snps,dphy-rx".
>> +- #phy-cells : Must be 1.
>> +- snps,dphy-frequency : Output frequency of the D-PHY.
>
> If that's the frequency of the clock on the output side of the RX PHY, doesn't
> it depend on the frequency on the CSI-2 (or other) bus ? Can't it vary ? Why
> do you need to have it in DT ?
>

But you are right, I will move it to the CSI-2 block.
The reason for it to be on the DT is that my use case have a camera with fixed
frequency connected, but of course I can change it after.

>> +- snps,dphy-te-len : Size of the communication interface (8 bits->8 or
>> 12bits->12).
>
> We have similar properties in various bindings, such as bus-width in video-
> interfaces.txt. Couldn't we use a more standard name ?

I have read the bus-width property but I don't this is the same.
This is not a video quite a video interface. Our D-PHY is configured using an
control interface, which is called "test interface" and can have 8-bit or 12-bit.

>
>> +- reg : Physical base address and size of the device memory mapped
>> + registers;
>
> The example below shows three ranges. Could you document the ranges that are
> expected ?
>

The three ranges is optional, it can be only two.
- The first is the interface from which we communicate with the d-phy. A small
window the for the referenced above "test interface".
- The second and third reg are regions for configuration for the d-phy before
the d-phy bring up:
a few examples:
- 4+4 data lanes max, 1 clk
- 8 data lanes max, 1clk
- 4+4 data lanes, 2 clk

>> +Optional properties:
>> +- snps,compat-mode : Compatibility mode control
>
> What is this ?

This toggles a mode for a specific d-phy that we can't auto-detect. So when we
use it we activate the compatibility mode for it. I can remove it if you think
it's best.

>
>> +The per-board settings:
>> +- gpios : Synopsys testchip used as reference uses this to change
> setup
>> + configurations.
>
> Here too, what is this for ?

Most of our d-phys have a wrapper around that can be controlled by a gpio-driver
that can halt the d-phy.

>
>> +Example:
>> +
>> + mipi_dphy_rx1: dphy@3040 {
>> + compatible = "snps,dphy-rx";
>> + #phy-cells = <1>;
>> + snps,dphy-frequency = <300000>;
>> + snps,dphy-te-len = <12>;
>> + snps,compat-mode = <1>;
>> + reg = < 0x03040 0x20
>> + 0x08000 0x100
>> + 0x09000 0x100>;
>
> The base addresses are pretty low, what kind of bus does this sit on ?
>

It sits on top of a bus like this for my configuration.

dx_mb {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x00000000 0x0 0xD0000000 0x10000000>;
interrupt-parent = <&he_intc>;
...

>> + };
>> +
>


2018-09-20 15:46:46

by Luis de Oliveira

[permalink] [raw]
Subject: Re: [V2, 4/5] Documentation: dt-bindings: Document bindings for DW MIPI CSI-2 Host

On 20-Sep-18 14:24, Laurent Pinchart wrote:
> Hi Luis,
>
> Thank you for the patch.
>

Hi Laurent, thank you for your review.
My answers inline.

> On Thursday, 20 September 2018 14:16:42 EEST Luis Oliveira wrote:
>> Add bindings for Synopsys DesignWare MIPI CSI-2 host.
>>
>> Signed-off-by: Luis Oliveira <[email protected]>
>> ---
>> Changelog
>> v2:
>> - no changes
>>
>> .../devicetree/bindings/media/snps,dw-csi-plat.txt | 74 +++++++++++++++++++
>> 1 file changed, 74 insertions(+)
>> create mode 100644
>> Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
>>
>> diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
>> b/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt new file
>> mode 100644
>> index 0000000..028f5eb
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/snps,dw-csi-plat.txt
>> @@ -0,0 +1,74 @@
>> +Synopsys DesignWare CSI-2 Host controller
>> +
>> +Description
>> +-----------
>> +
>> +This HW block is used to receive image coming from an MIPI CSI-2 compatible
>> +camera.
>> +
>> +Required properties:
>> +- compatible: shall be "snps,dw-csi-plat"
>> +- reg : physical base address and size of the device memory mapped
>> + registers;
>> +- interrupts : CSI-2 Host interrupt
>> +- snps,output-type : Core output to be used (IPI-> 0 or IDI->1 or BOTH-
>> 2)
>> These
>> + values choose which of the Core outputs will be used, it
>> + can be Image Data Interface or Image Pixel Interface.
>> +- phys : List of one PHY specifier (as defined in
>> + Documentation/devicetree/bindings/phy/phy-bindings.txt).
>> + This PHY is a MIPI DPHY working in RX mode.
>> +- resets : Reference to a reset controller (optional)
>> +
>> +Optional properties(if in IPI mode):
>> +- snps,ipi-mode : Mode to be used when in IPI(Camera -> 0 or Controller -
>>
>> 1)
>> + This property defines if the controller will use the video
>> + timings available
>> + in the video stream or if it will use pre-defined ones.
>
> How does one select this ?
>

This is a hardware setting for timings in IPI (Image Pixel Interface - Synopsys
specific interface), default is Controller mode - 1.

This can be selected in the DT or after by the driver.

>> +- snps,ipi-color-mode : Bus depth to be used in IPI (48 bits -> 0 or 16
>> bits -> 1)
>> + This property defines the width of the IPI bus.
>
> How about using the standard bus-width property in the endpoint of the output
> port ?
>

Here I think It makes sense, I will use your suggestion.

>> +- snps,ipi-auto-flush : Data auto-flush (1 -> Yes or 0 -> No). This
>> property defines
>> + if the data is automatically flushed in each vsync
>> or if
>> + this process is done manually
>
> This seems like a configuration option, not a hardware property. I don't think
> it belongs to DT.
>
I see your point,

>> +- snps,virtual-channel : Virtual channel where data is present when in IPI
>> mode. This
>> + property chooses the virtual channel which IPI will use to
>> + retrieve the video stream.
>
> The virtual channel doesn't belong to DT, it should be queried from the sensor
> at runtime (and when a sensor can send multiple data streams, that should even
> be configurable).
>

I will do that.

I made this properties for my tests with cameras, thinking of SoCs that have
fixed HW configurations. But I can remove all this fields from the DT because
all of them can be made after in the driver.

>> +The per-board settings:
>> + - port sub-node describing a single endpoint connected to the camera as
>> + described in video-interfaces.txt[1].
>
> You need to explicitly list all the ports for this device, with their number
> and function.
>

Ok.

>> +Example:
>> +
>> + csi2_1: csi2@3000 {
>> + compatible = "snps,dw-csi-plat";
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> + reg = < 0x03000 0x7FF>;
>> + interrupts = <2>;
>> + output-type = <2>;
>> + resets = <&dw_rst 1>;
>> + phys = <&mipi_dphy_rx1 0>;
>> + phy-names = "csi2-dphy";
>> +
>> + /* IPI optional Configurations */
>> + snps,ipi-mode = <0>;
>> + snps,ipi-color-mode = <0>;
>> + snps,ipi-auto-flush = <1>;
>> + snps,virtual-channel = <0>;
>> +
>> + /* CSI-2 per-board settings */
>> + port@1 {
>> + reg = <1>;
>> + csi1_ep1: endpoint {
>> + remote-endpoint = <&camera_1>;
>> + data-lanes = <1 2>;
>> + };
>> + };
>> + port@2 {
>> + csi1_ep2: endpoint {
>> + remote-endpoint = <&vif1_ep>;
>> + };
>> + };
>> + };
>> +
>> +
>
> Extra blank lines.

Thanks.
>


2018-09-21 14:37:48

by Maxime Ripard

[permalink] [raw]
Subject: Re: [V2, 0/5] platform: dwc: Add of DesignWare MIPI CSI-2 Host

Hi Luis,

On Thu, Sep 20, 2018 at 01:16:38PM +0200, Luis Oliveira wrote:
> This adds support for Synopsys MIPI CSI-2 Host and MIPI D-PHY.
> The patch series include support for initialization/configuration of the
> DW MIPI CSI-2 controller and DW MIPI D-PHY and both include a reference
> platform driver.
>
> This will enable future SoCs to use this standard approach and possibly
> create a more clean environment.
>
> This series also documents the dt-bindings needed for the platform drivers.
>
> This was applied in: https://git.linuxtv.org/media_tree.git

I'm currently working on some MIPI D-PHY support through the generic
phy framework that could benefit your patches.

https://lwn.net/Articles/764173/

Feel free to comment on that serie if you have any particular
constraints or if you believe that some issues should be addressed.

Thanks!
Maxime

--
Maxime Ripard, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com


Attachments:
(No filename) (978.00 B)
signature.asc (849.00 B)
Download all attachments

2018-09-21 15:25:55

by Luis de Oliveira

[permalink] [raw]
Subject: Re: [V2, 3/5] media: platform: dwc: Add DW MIPI DPHY Rx platform

On 20-Sep-18 14:20, Laurent Pinchart wrote:
> Hi Louis,
>
> Thank you for the patch.

Hi Laurent,
My answers inline.

>
> On Thursday, 20 September 2018 14:16:41 EEST Luis Oliveira wrote:
>> Add of Synopsys MIPI D-PHY in RX mode support.
>> Separated in the implementation are platform dependent probing functions.
>>
>> Signed-off-by: Luis Oliveira <[email protected]>
>> ---
>> Changelog
>> v2:
>> - fix uninitialization warning
>> - fix SDPX License to match: Documentation/process/license-rules.rst
>>
>> drivers/media/platform/dwc/Kconfig | 32 ++
>> drivers/media/platform/dwc/Makefile | 2 +
>> drivers/media/platform/dwc/dw-dphy-plat.c | 365 ++++++++++++++++++
>> drivers/media/platform/dwc/dw-dphy-rx.c | 594 +++++++++++++++++++++++++++
>> drivers/media/platform/dwc/dw-dphy-rx.h | 176 +++++++++
>
> Is there really a need to split this in two source files ? It smells of OS
> abstraction layers.
>
The purpose of the division is to separate d-phy configuration from platform
dependent configuration. We will add PCI support in the future.

>> 5 files changed, 1169 insertions(+)
>> create mode 100644 drivers/media/platform/dwc/dw-dphy-plat.c
>> create mode 100644 drivers/media/platform/dwc/dw-dphy-rx.c
>> create mode 100644 drivers/media/platform/dwc/dw-dphy-rx.h
>
> As this is a PHY driver, shouldn't it be located in drivers/phy/ ?
>
You are right I suppose, I will move it.

>> diff --git a/drivers/media/platform/dwc/Kconfig
>> b/drivers/media/platform/dwc/Kconfig index e69de29..a573297 100644
>> --- a/drivers/media/platform/dwc/Kconfig
>> +++ b/drivers/media/platform/dwc/Kconfig
>> @@ -0,0 +1,32 @@
>> +#
>> +# Synopsys DWC Platform drivers
>> +# Most drivers here are currently for MIPI CSI-2 and MIPI DPHY support
>> +
>> +config DWC_MIPI_CSI2_HOST
>> + bool "Synopsys Designware CSI-2 Host Controller and DPHY-RX support"
>
> There's no CSI-2 host controller in this patch.
>

This was initially a place holder for the

>> + select VIDEO_DEV
>> + select VIDEO_V4L2
>> + select VIDEO_V4L2_SUBDEV_API
>> + select VIDEOBUF2_VMALLOC
>> + select VIDEOBUF2_DMA_CONTIG
>
> All these don't seem to be needed.
>
>> + select GENERIC_PHY
>> + select VIDEO_OV5647
>
> And I'm pretty sure you can use the PHY without an OV5647 sensor :-)

Yes. This is an error, I will fix it
>
>> + help
>> + This selects the CSI-2 host controller support.
>> +
>> + If you have a controller with this interface, say Y.
>> +
>> + If unsure, say N.
>> +
>> +if DWC_MIPI_CSI2_HOST
>> +
>> +config DWC_MIPI_TC_DPHY_G128
>> + tristate "DesignWare platform support using a G128 Test Chip"
>> + depends on DWC_MIPI_CSI2_HOST
>> + help
>> + Synopsys Test Chip is a MIPI D-PHY for prototyping purposes.
>> +
>> + If unsure, say N.
>> +
>> +endif # DWC_MIPI_CSI2_HOST
>> +
>> diff --git a/drivers/media/platform/dwc/Makefile
>> b/drivers/media/platform/dwc/Makefile index e69de29..8be6f68 100644
>> --- a/drivers/media/platform/dwc/Makefile
>> +++ b/drivers/media/platform/dwc/Makefile
>> @@ -0,0 +1,2 @@
>> +obj-$(CONFIG_DWC_MIPI_TC_DPHY_G128) += dw-dphy-platfrm.o
>> +dw-dphy-platfrm-objs := dw-dphy-plat.o dw-dphy-rx.o
>> diff --git a/drivers/media/platform/dwc/dw-dphy-plat.c
>> b/drivers/media/platform/dwc/dw-dphy-plat.c new file mode 100644
>> index 0000000..8e298f5
>> --- /dev/null
>> +++ b/drivers/media/platform/dwc/dw-dphy-plat.c
>> @@ -0,0 +1,365 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * dw-dphy-plat.c
>> + *
>> + * Copyright(c) 2018-present, Synopsys, Inc. and/or its affiliates.
>
> I don't think that -present is right. The copyright notice should list the
> years explicitly.
>

Ok, I will check that

>> + * Luis Oliveira <[email protected]>
>> + *
>> + */
>> +
>> +#include "dw-dphy-rx.h"
>> +
>> +static struct phy *dw_dphy_xlate(struct device *dev,
>> + struct of_phandle_args *args)
>> +{
>> + struct dw_dphy_rx *dphy = dev_get_drvdata(dev);
>> +
>> + return dphy->phy;
>> +}
>> +
>> +static ssize_t dphy_reset_show(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> + char buffer[15];
>> +
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
>> + usleep_range(100, 200);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
>> +
>> + return strlcpy(buf, buffer, PAGE_SIZE);
>> +}
>> +
>> +static ssize_t dphy_freq_store(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf,
>> + size_t count)
>> +{
>> + int ret;
>> + unsigned long freq;
>> +
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> +
>> + ret = kstrtoul(buf, 10, &freq);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (freq > 2500) {
>> + dev_info(dev, "Freq must be under 2500 Mhz\n");
>> + return count;
>> + }
>> + if (freq < 80) {
>> + dev_info(dev, "Freq must be over 80 Mhz\n");
>> + return count;
>> + }
>> +
>> + dev_info(dev, "Data Rate %lu Mbps\n", freq);
>> + dphy->dphy_freq = freq * 1000;
>> +
>> + return count;
>> +
>> +}
>> +
>> +static ssize_t dphy_freq_show(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> + char buffer[15];
>> +
>> + snprintf(buffer, 15, "Freq %d\n", dphy->dphy_freq / 1000);
>> +
>> + return strlcpy(buf, buffer, PAGE_SIZE);
>> +}
>> +
>> +static ssize_t dphy_addr_store(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf,
>> + size_t count)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> + unsigned long val;
>> + int ret;
>> + u8 addr, payload;
>> +
>> + ret = kstrtoul(buf, 32, &val);
>> + if (ret < 0)
>> + return ret;
>> +
>> + payload = (u16)val;
>> + addr = (u16)(val >> 16);
>> +
>> + dev_info(dev, "addr 0x%lX\n", val);
>> + dev_info(dev, "payload: 0x%X\n", addr);
>> +
>> + dev_info(dev,
>> + "Addr [0x%x] -> 0x%x\n", (unsigned int)addr,
>> + dw_dphy_te_read(dphy, addr));
>> +
>> + return count;
>> +}
>> +
>> +static ssize_t idelay_show(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> + char buffer[15];
>> +
>> + snprintf(buffer, 15, "idelay %d\n", dw_dphy_if_get_idelay(dphy));
>> +
>> + return strlcpy(buf, buffer, PAGE_SIZE);
>> +}
>> +
>> +static ssize_t idelay_store(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf,
>> + size_t count)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> + int ret;
>> + unsigned long val;
>> + u8 lane, delay;
>> +
>> + ret = kstrtoul(buf, 16, &val);
>> + if (ret < 0)
>> + return ret;
>> +
>> + lane = (u8)val;
>> + delay = (u8)(val >> 8);
>> +
>> + dev_dbg(dev, "Lanes %u\n", lane);
>> + dev_dbg(dev, "Delay %u\n", delay);
>> +
>> + dw_dphy_if_set_idelay_lane(dphy, delay, lane);
>> +
>> + return count;
>> +}
>> +
>> +static ssize_t len_config_store(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + int ret;
>> + unsigned long length;
>> +
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> +
>> + ret = kstrtoul(buf, 10, &length);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (length == BIT8)
>> + pr_info("Configured for 8-bit interface\n");
>> + else if (length == BIT12)
>> + pr_info("Configured for 12-bit interface\n");
>> + else
>> + return count;
>> +
>> + dphy->dphy_te_len = length;
>> +
>> + return count;
>> +
>> +}
>> +
>> +static ssize_t len_config_show(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> + char buffer[20];
>> +
>> + snprintf(buffer, 20, "Length %d\n", dphy->dphy_te_len);
>> +
>> + return strlcpy(buf, buffer, PAGE_SIZE);
>> +}
>> +
>> +static ssize_t dw_dphy_g118_settle_store(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf,
>> + size_t count)
>> +{
>> + int ret;
>> + unsigned long lp_time;
>> +
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> +
>> + ret = kstrtoul(buf, 10, &lp_time);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if ((lp_time > 1) && (lp_time < 10000))
>> + dphy->lp_time = lp_time;
>> + else {
>> + pr_info("Invalid Value configuring for 1000 ns\n");
>> + dphy->lp_time = 1000;
>> + }
>> +
>> + dphy->lp_time = lp_time;
>> +
>> + return count;
>> +
>> +}
>> +
>> +static ssize_t dw_dphy_g118_settle_show(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct platform_device *pdev = to_platform_device(dev);
>> + struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
>> + char buffer[10];
>> +
>> + snprintf(buffer, 10, "Settle %d ns\n", dphy->lp_time);
>> +
>> + return strlcpy(buf, buffer, PAGE_SIZE);
>> +}
>> +
>> +static DEVICE_ATTR_RO(dphy_reset);
>> +static DEVICE_ATTR_RW(dphy_freq);
>> +static DEVICE_ATTR_WO(dphy_addr);
>> +static DEVICE_ATTR_RW(idelay);
>> +static DEVICE_ATTR_RW(len_config);
>> +static DEVICE_ATTR_RW(dw_dphy_g118_settle);
>
> If you want to expose an API through sysfs it should be properly documented. I
> don't think this is needed though, the PHY should be completely transparent
> for userspace, it should be handled fully within the kernel.
>

We have clients that use this. This is very useful for debug but I will try to
come up with a better solution.

>> +static struct phy_ops dw_dphy_ops = {
>> + .init = dw_dphy_init,
>> + .reset = dw_dphy_reset,
>> + .power_on = dw_dphy_power_on,
>> + .power_off = dw_dphy_power_off,
>> + .owner = THIS_MODULE,
>> +};
>> +
>> +static int dw_dphy_rx_probe(struct platform_device *pdev)
>> +{
>> + struct dw_dphy_rx *dphy;
>> + struct device *dev = &pdev->dev;
>> + struct resource *res;
>> + struct phy_provider *phy_provider;
>> + struct phy *phy;
>> +
>> + dphy = devm_kzalloc(dev, sizeof(*dphy), GFP_KERNEL);
>> + if (!dphy)
>> + return -ENOMEM;
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + dphy->base_address = devm_ioremap(dev, res->start, resource_size(res));
>> + if (IS_ERR(dphy->base_address)) {
>> + dev_err(dev, "error requesting base address\n");
>> + return PTR_ERR(dphy->base_address);
>> + }
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> + dphy->dphy1_if_addr = devm_ioremap_resource(dev, res);
>> + if (IS_ERR(dphy->dphy1_if_addr)) {
>> + dev_err(dev, "error requesting dphy 1 if regbank\n");
>> + return PTR_ERR(dphy->dphy1_if_addr);
>> + }
>> +
>> + dphy->max_lanes =
>> + dw_dphy_if_read_msk(dphy, DPHYID, DPHY_ID_LANE_SUPPORT, 4);
>> +
>> + dphy->dphy_gen = dw_dphy_if_read_msk(dphy, DPHYID, DPHY_ID_GEN, 4);
>> + dev_info(dev, "DPHY GEN %s with maximum %s lanes\n",
>> + dphy->dphy_gen == GEN3 ? "3" : "2",
>> + dphy->max_lanes == CTRL_8_LANES ? "8" : "4");
>> +
>> + if (dphy->max_lanes == CTRL_8_LANES) {
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
>> + dphy->dphy2_if_addr =
>> + devm_ioremap(dev, res->start, resource_size(res));
>> +
>> + if (IS_ERR(dphy->dphy2_if_addr)) {
>> + dev_err(dev, "error requesting dphy 2 if regbank\n");
>> + return PTR_ERR(dphy->dphy2_if_addr);
>> + }
>> +
>> + dphy->config_gpio = of_get_gpio(dev->of_node, 0);
>> + if (!gpio_is_valid(dphy->config_gpio)) {
>> + dev_err(dev, "failed to parse config gpio\n");
>> + return dphy->config_gpio;
>> + }
>> + }
>> +
>> + if (of_property_read_u32(dev->of_node,
>> + "snps,dphy-frequency",
>> + &dphy->dphy_freq)) {
>> + dev_err(dev, "failed to find dphy frequency\n");
>> + return -EINVAL;
>> + }
>> +
>> + if (of_property_read_u32(dev->of_node,
>> + "snps,dphy-te-len",
>> + &dphy->dphy_te_len)) {
>> + dev_err(dev, "failed to find dphy te length\n");
>> + return -EINVAL;
>> + }
>> +
>> + if (of_property_read_u32(dev->of_node,
>> + "snps,compat-mode",
>> + &dphy->compat_mode)) {
>> + dev_err(dev, "failed to find compat mode\n");
>> + return -EINVAL;
>> + }
>> +
>> + dev_set_drvdata(dev, dphy);
>> + spin_lock_init(&dphy->slock);
>> +
>> + phy = devm_phy_create(dev, NULL, &dw_dphy_ops);
>> + if (IS_ERR(phy)) {
>> + dev_err(dev, "failed to create PHY\n");
>> + return PTR_ERR(phy);
>> + }
>> +
>> + dphy->phy = phy;
>> + phy_set_drvdata(phy, dphy);
>> +
>> + phy_provider = devm_of_phy_provider_register(dev, dw_dphy_xlate);
>> + if (IS_ERR(phy_provider)) {
>> + dev_err(dev, "error getting phy provider\n");
>> + return PTR_ERR(phy_provider);
>> + }
>> +
>> + dphy->lp_time = 1000; /* 1000 ns */
>> + dphy->lanes_config = dw_dphy_setup_config(dphy);
>> + dev_dbg(dev, "rx-dphy created\n");
>> +
>> + device_create_file(&pdev->dev, &dev_attr_dphy_reset);
>> + device_create_file(&pdev->dev, &dev_attr_dphy_freq);
>> + device_create_file(&pdev->dev, &dev_attr_dphy_addr);
>> + device_create_file(&pdev->dev, &dev_attr_idelay);
>> + device_create_file(&pdev->dev, &dev_attr_len_config);
>> + device_create_file(&pdev->dev, &dev_attr_dw_dphy_g118_settle);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct of_device_id dw_dphy_rx_of_match[] = {
>> + { .compatible = "snps,dphy-rx" },
>> + { },
>> +};
>> +MODULE_DEVICE_TABLE(of, dw_dphy_rx_of_match);
>> +
>> +static struct platform_driver dw_dphy_rx_driver = {
>> + .probe = dw_dphy_rx_probe,
>> + .driver = {
>> + .of_match_table = dw_dphy_rx_of_match,
>> + .name = "snps-dphy-rx",
>> + .owner = THIS_MODULE,
>> + }
>> +};
>> +module_platform_driver(dw_dphy_rx_driver);
>> +
>> +MODULE_DESCRIPTION("SNPS MIPI DPHY Rx driver");
>> +MODULE_AUTHOR("Luis Oliveira <[email protected]>");
>> +MODULE_LICENSE("GPL v2");
>
> "GPL v2" means just that, while the SPDX header mentions GPL-2.0+. You can
> pick your license of choice (or rather your employer should, as it seems they
> own the copyright on the code based on the copyright header above), but SPDX
> and MODULE_LICENSE should match.
>

Ok, thank you.

>> diff --git a/drivers/media/platform/dwc/dw-dphy-rx.c
>> b/drivers/media/platform/dwc/dw-dphy-rx.c new file mode 100644
>> index 0000000..4100ea5
>> --- /dev/null
>> +++ b/drivers/media/platform/dwc/dw-dphy-rx.c
>> @@ -0,0 +1,594 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Synopsys MIPI D-PHY driver
>> + *
>> + * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
>> + * Author: Luis Oliveira <[email protected]>
>> + *
>> + */
>> +
>> +#include "dw-dphy-rx.h"
>> +
>> +struct range_dphy_gen2 {
>> + u64 freq;
>> + u8 hsfregrange;
>> +};
>> +
>> +struct range_dphy_gen2 range_gen2[] = {
>> + { 80, 0x00}, { 90, 0x10}, { 100, 0x20},
>> + { 110, 0x30}, { 120, 0x01}, { 130, 0x11},
>> + { 140, 0x21}, { 150, 0x31}, { 160, 0x02},
>> + { 170, 0x12}, { 180, 0x22}, { 190, 0x32},
>> + { 205, 0x03}, { 220, 0x13}, { 235, 0x23},
>> + { 250, 0x33}, { 275, 0x04}, { 300, 0x14},
>> + { 325, 0x05}, { 350, 0x15}, { 400, 0x25},
>> + { 450, 0x06}, { 500, 0x16}, { 550, 0x07},
>> + { 600, 0x17}, { 650, 0x08}, { 700, 0x18},
>> + { 750, 0x09}, { 800, 0x19}, { 850, 0x29},
>> + { 900, 0x39}, { 950, 0x0A}, {1000, 0x1A},
>> + {1050, 0x2A}, {1100, 0x3A}, {1150, 0x0B},
>> + {1200, 0x1B}, {1250, 0x2B}, {1300, 0x3B},
>> + {1350, 0x0C}, {1400, 0x1C}, {1450, 0x2C},
>> + {1500, 0x3C}, {1550, 0x0D}, {1600, 0x1D},
>> + {1650, 0x2D}, {1700, 0x0E}, {1750, 0x1E},
>> + {1800, 0x2E}, {1850, 0x3E}, {1900, 0x0F},
>> + {1950, 0x1F}, {2000, 0x2F},
>> +};
>> +
>> +struct range_dphy_gen3 {
>> + u64 freq;
>> + u8 hsfregrange;
>> + u32 osc_freq_target;
>> +};
>> +
>> +struct range_dphy_gen3 range_gen3[] = {
>> +
>> + { 80, 0x00, 0x1B6}, { 90, 0x10, 0x1B6}, { 100, 0x20, 0x1B6},
>> + { 110, 0x30, 0x1B6}, { 120, 0x01, 0x1B6}, { 130, 0x11, 0x1B6},
>> + { 140, 0x21, 0x1B6}, { 150, 0x31, 0x1B6}, { 160, 0x02, 0x1B6},
>> + { 170, 0x12, 0x1B6}, { 180, 0x22, 0x1B6}, { 190, 0x32, 0x1B6},
>> + { 205, 0x03, 0x1B6}, { 220, 0x13, 0x1B6}, { 235, 0x23, 0x1B6},
>> + { 250, 0x33, 0x1B6}, { 275, 0x04, 0x1B6}, { 300, 0x14, 0x1B6},
>> + { 325, 0x25, 0x1B6}, { 350, 0x35, 0x1B6}, { 400, 0x05, 0x1B6},
>> + { 450, 0x16, 0x1B6}, { 500, 0x26, 0x1B6}, { 550, 0x37, 0x1B6},
>> + { 600, 0x07, 0x1B6}, { 650, 0x18, 0x1B6}, { 700, 0x28, 0x1B6},
>> + { 750, 0x39, 0x1B6}, { 800, 0x09, 0x1B6}, { 850, 0x19, 0x1B6},
>> + { 900, 0x29, 0x1B6}, { 950, 0x3A, 0x1B6}, {1000, 0x0A, 0x1B6},
>> + {1050, 0x1A, 0x1B6}, {1100, 0x2A, 0x1B6}, {1150, 0x3B, 0x1B6},
>> + {1200, 0x0B, 0x1B6}, {1250, 0x1B, 0x1B6}, {1300, 0x2B, 0x1B6},
>> + {1350, 0x3C, 0x1B6}, {1400, 0x0C, 0x1B6}, {1450, 0x1C, 0x1B6},
>> + {1500, 0x2C, 0x1B6}, {1550, 0x3D, 0x10F}, {1600, 0x0D, 0x118},
>> + {1650, 0x1D, 0x121}, {1700, 0x2E, 0x12A}, {1750, 0x3E, 0x132},
>> + {1800, 0x0E, 0x13B}, {1850, 0x1E, 0x144}, {1900, 0x2F, 0x14D},
>> + {1950, 0x3F, 0x155}, {2000, 0x0F, 0x15E}, {2050, 0x40, 0x167},
>> + {2100, 0x41, 0x170}, {2150, 0x42, 0x178}, {2200, 0x43, 0x181},
>> + {2250, 0x44, 0x18A}, {2300, 0x45, 0x193}, {2350, 0x46, 0x19B},
>> + {2400, 0x47, 0x1A4}, {2450, 0x48, 0x1AD}, {2500, 0x49, 0x1B6}
>> +};
>> +
>> +u8 dw_dphy_setup_config(struct dw_dphy_rx *dphy)
>> +{
>> + u8 ret;
>> + int setup_config;
>> +
>> + if (dphy->max_lanes == CTRL_4_LANES)
>> + return CTRL_4_LANES;
>> +
>> + ret = gpio_request(dphy->config_gpio, "config");
>> + if (ret < 0) {
>> + pr_err("could not acquire config gpio (err=%d)\n", ret);
>> + return ret;
>> + }
>> +
>> + setup_config = gpio_get_value(dphy->config_gpio);
>> + pr_debug("CONFIG %s\n", setup_config == CTRL_8_LANES ? "8L" : "4+4L");
>> + gpio_free(dphy->config_gpio);
>> +
>> + return setup_config;
>> +}
>> +void dw_dphy_if_write(struct dw_dphy_rx *dphy, u32 address, u32 data)
>> +{
>> + iowrite32(data, dphy->dphy1_if_addr + address);
>> +
>> + if (dphy->lanes_config == CTRL_4_LANES)
>> + return;
>> +
>> + iowrite32(data, dphy->dphy2_if_addr + address);
>> +}
>> +
>> +u32 dw_dphy_if_read(struct dw_dphy_rx *dphy, u64 address)
>> +{
>> + u32 if1 = 0, if2 = 0;
>> +
>> + if1 = ioread32(dphy->dphy1_if_addr + address);
>> +
>> + if (dphy->lanes_config == CTRL_4_LANES)
>> + goto end;
>> +
>> + if (dphy->lanes_config == DPHYID)
>> + goto end;
>> +
>> + if2 = ioread32(dphy->dphy2_if_addr + address);
>> +
>> + if (if1 != if2)
>> + pr_err("Values read different for each interface\n");
>> +
>> +end:
>> + return if1;
>> +}
>> +
>> +void dw_dphy_write(struct dw_dphy_rx *dphy, u32 address, u32 data)
>> +{
>> + iowrite32(data, dphy->base_address + address);
>> +
>> + if (dphy->lanes_config == CTRL_4_LANES)
>> + return;
>> +
>> + if (address == R_CSI2_DPHY_TST_CTRL0)
>> + iowrite32(data, dphy->base_address + R_CSI2_DPHY2_TST_CTRL0);
>> + else if (address == R_CSI2_DPHY_TST_CTRL1)
>> + iowrite32(data, dphy->base_address + R_CSI2_DPHY2_TST_CTRL1);
>> +}
>> +
>> +u32 dw_dphy_read(struct dw_dphy_rx *dphy, u64 address)
>> +{
>> + u32 dphy1 = 0, dphy2 = 0;
>> +
>> + dphy1 = ioread32(dphy->base_address + address);
>> +
>> + if (dphy->lanes_config == CTRL_4_LANES)
>> + goto end;
>> +
>> + if (address == R_CSI2_DPHY_TST_CTRL0)
>> + dphy2 = ioread32(dphy->base_address + R_CSI2_DPHY2_TST_CTRL0);
>> + else if (address == R_CSI2_DPHY_TST_CTRL1)
>> + dphy2 = ioread32(dphy->base_address + R_CSI2_DPHY2_TST_CTRL1);
>> + else
>> + return -ENODEV;
>> +
>> + if (dphy1 != dphy2)
>> + pr_err("Values read different for each dphy\n");
>> +
>> +end:
>> + return dphy1;
>> +}
>> +
>> +void dw_dphy_write_msk(struct dw_dphy_rx *dev,
>> + u64 address, u64 data, u8 shift, u8 width)
>> +{
>> + u32 mask = (1 << width) - 1;
>> + u32 temp = dw_dphy_read(dev, address);
>> +
>> + temp &= ~(mask << shift);
>> + temp |= (data & mask) << shift;
>> + dw_dphy_write(dev, address, temp);
>> +}
>> +
>> +static void dw_dphy_te_12b_write(struct dw_dphy_rx *dphy, u16 addr, u8
>> data) +{
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, PHY_TESTDIN, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy,
>> + R_CSI2_DPHY_TST_CTRL1, (u8) (addr >> 8), PHY_TESTDIN, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy,
>> + R_CSI2_DPHY_TST_CTRL1, (u8) addr, PHY_TESTDIN, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy,
>> + R_CSI2_DPHY_TST_CTRL1, (u8) data, PHY_TESTDIN, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> +}
>> +
>> +static void dw_dphy_te_8b_write(struct dw_dphy_rx *dphy, u8 addr, u8 data)
>> +{
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_TST_CTRL1, addr);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_TST_CTRL1, data);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> +}
>> +
>> +static void dw_dphy_te_write(struct dw_dphy_rx *dphy, u16 addr, u8 data)
>> +{
>> +
>> + if (dphy->dphy_te_len == BIT12)
>> + dw_dphy_te_12b_write(dphy, addr, data);
>> + else
>> + dw_dphy_te_8b_write(dphy, addr, data);
>> +}
>> +
>> +static int dw_dphy_te_12b_read(struct dw_dphy_rx *dphy, u32 addr)
>> +{
>> + u8 ret;
>> +
>> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, PHY_TESTDIN, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy,
>> + R_CSI2_DPHY_TST_CTRL1, (u8) (addr >> 8), PHY_TESTDIN, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy,
>> + R_CSI2_DPHY_TST_CTRL1, (u8) addr, PHY_TESTDIN, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0x00, 0, PHY_TESTDIN);
>> + ret = dw_dphy_read_msk(dphy, R_CSI2_DPHY_TST_CTRL1, PHY_TESTDOUT, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
>> +
>> + return ret;
>> +}
>> +
>> +static int dw_dphy_te_8b_read(struct dw_dphy_rx *dphy, u32 addr)
>> +{
>> + u8 ret;
>> +
>> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 1, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, addr, PHY_TESTDIN, 8);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTEN, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL1, 0, PHY_TESTDIN, 8);
>> + ret = dw_dphy_read_msk(dphy, R_CSI2_DPHY_TST_CTRL1, PHY_TESTDOUT, 8);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
>> +
>> + return ret;
>> +}
>> +
>> +int dw_dphy_te_read(struct dw_dphy_rx *dphy, u32 addr)
>> +{
>> + int ret;
>> +
>> + if (dphy->dphy_te_len == BIT12)
>> + ret = dw_dphy_te_12b_read(dphy, addr);
>> + else
>> + ret = dw_dphy_te_8b_read(dphy, addr);
>> +
>> + return ret;
>> +}
>> +
>> +static void dw_dphy_if_init(struct dw_dphy_rx *dphy)
>> +{
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, TX_PHY);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
>> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
>> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
>> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
>> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
>> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 0);
>> + dw_dphy_if_write(dphy, DPHYZCALCTRL, 1);
>> +}
>> +
>> +static void dw_dphy_gen3_12bit_tc_power_up(struct dw_dphy_rx *dphy)
>> +{
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
>> + dw_dphy_te_write(dphy, CFGCLKFREQRANGE_TX, 0x1C);
>> +
>> + /* CLKSEL | UPDATEPLL | SHADOW_CLEAR | SHADOW_CTRL | FORCEPLL */
>> + dw_dphy_te_write(dphy, BYPASS, 0x3F);
>> +
>> + /* IO_DS3 | IO_DS2 | IO_DS1 | IO_DS0 */
>> + if (dphy->dphy_freq > 1500)
>> + dw_dphy_te_write(dphy, IO_DS, 0x0F);
>> +
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
>> +}
>> +
>> +static void dw_dphy_gen3_8bit_tc_power_up(struct dw_dphy_rx *dphy)
>> +{
>> + u32 input_freq = dphy->dphy_freq / 1000;
>> +
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
>> + dw_dphy_te_write(dphy, CFGCLKFREQRANGE_RX, 0x1C);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
>> + dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
>> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX0_MSB, 0x03);
>> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX0_LSB, 0x02);
>> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX1_MSB, 0x03);
>> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX1_LSB, 0x02);
>> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX2_MSB, 0x03);
>> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX2_LSB, 0x02);
>> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX3_MSB, 0x03);
>> + dw_dphy_te_write(dphy, OSC_FREQ_TARGET_RX3_LSB, 0x02);
>> + dw_dphy_te_write(dphy, BANDGAP_CTRL, 0x80);
>> +
>> + if (input_freq < 2000)
>> + dw_dphy_te_write(dphy, HS_RX_CTRL_LANE0, 0xC0);
>> +
>> + if (input_freq < 1000) {
>> + dw_dphy_te_write(dphy, HS_RX_CTRL_LANE1, 0xC0);
>> + dw_dphy_te_write(dphy, HS_RX_CTRL_LANE2, 0xC0);
>> + dw_dphy_te_write(dphy, HS_RX_CTRL_LANE3, 0xC0);
>> + }
>> +}
>> +
>> +int dw_dphy_g118_settle(struct dw_dphy_rx *dphy)
>> +{
>> + u32 input_freq, total_settle, settle_time, byte_clk, lp_time;
>> +
>> + lp_time = dphy->lp_time;
>> + input_freq = dphy->dphy_freq / 1000;
>> +
>> + settle_time = (8 * (1000000/(input_freq))) + 115000;
>> + byte_clk = (8000000/(input_freq));
>> + total_settle = (settle_time + lp_time * 1000) / byte_clk;
>> +
>> + if (total_settle > 0xFF)
>> + total_settle = 0xFF;
>> +
>> + return total_settle;
>> +}
>> +
>> +static void dw_dphy_pwr_down(struct dw_dphy_rx *dphy)
>> +{
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
>> +
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> + if (dphy->lanes_config == CTRL_8_LANES)
>> + dw_dphy_write_msk(dphy,
>> + R_CSI2_DPHY2_TST_CTRL0, 0, PHY_TESTCLK, 1);
>> +
>> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
>> +}
>> +
>> +static void dw_dphy_pwr_up(struct dw_dphy_rx *dphy)
>> +{
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> + if (dphy->lanes_config == CTRL_8_LANES)
>> + dw_dphy_write_msk(dphy,
>> + R_CSI2_DPHY2_TST_CTRL0, 1, PHY_TESTCLK, 1);
>> +
>> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 1);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
>> +}
>> +
>> +static int dw_dphy_gen3_12bit_configure(struct dw_dphy_rx *dphy)
>> +{
>> + u32 input_freq = dphy->dphy_freq;
>> + u8 range = 0;
>> +
>> + pr_debug("PHY GEN 3 Freq: %u\n", input_freq);
>> + for (range = 0; (range < ARRAY_SIZE(range_gen3) - 1) &&
>> + ((input_freq / 1000) > range_gen3[range].freq); range++)
>> + ;
>> +
>> + dw_dphy_gen3_12bit_tc_power_up(dphy);
>> +
>> + dw_dphy_te_write(dphy, RX_SYS_1, range_gen3[range].hsfregrange);
>> + dw_dphy_te_write(dphy, RX_SYS_0, 0x20);
>> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_2,
>> + (u8) range_gen3[range].osc_freq_target);
>> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_3,
>> + (u8) (range_gen3[range].osc_freq_target >> 8));
>> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_4, 0x01);
>> +
>> + if (dphy->compat_mode) {
>> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_1, 0x01);
>> + dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_0, 0x80);
>> + }
>> +
>> + if ((dphy->compat_mode) || (input_freq <= 1500))
>> + dw_dphy_te_write(dphy, RX_SYS_7, 0x38);
>> +
>> + return 0;
>> +}
>> +
>> +static int dw_dphy_gen3_8bit_configure(struct dw_dphy_rx *dphy)
>> +{
>> + u32 input_freq = dphy->dphy_freq;
>> + u8 data;
>> + u8 range = 0;
>> +
>> + pr_debug("PHY GEN 3 Freq: %u\n", input_freq);
>> + for (range = 0; (range < ARRAY_SIZE(range_gen3) - 1) &&
>> + ((input_freq / 1000) > range_gen3[range].freq); range++)
>> + ;
>> +
>> + dw_dphy_te_write(dphy, RX_SKEW_CAL, dw_dphy_g118_settle(dphy));
>> + data = 1<<7 | range_gen3[range].hsfregrange;
>> + dw_dphy_te_write(dphy, HSFREQRANGE_8BIT, data);
>> + dw_dphy_gen3_8bit_tc_power_up(dphy);
>> +
>> + return 0;
>> +}
>> +
>> +static int dw_dphy_gen2_configure(struct dw_dphy_rx *dphy)
>> +{
>> + u32 input_freq = dphy->dphy_freq;
>> + u8 data;
>> + u8 range = 0;
>> +
>> + /* provide an initial active-high test clear pulse in TESTCLR */
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 1, PHY_TESTCLR, 1);
>> + dw_dphy_write_msk(dphy, R_CSI2_DPHY_TST_CTRL0, 0, PHY_TESTCLR, 1);
>> +
>> + pr_debug("PHY GEN 2 Freq: %u\n", input_freq);
>> + for (range = 0; (range < ARRAY_SIZE(range_gen2) - 1) &&
>> + ((input_freq / 1000) > range_gen2[range].freq); range++)
>> + ;
>> +
>> + data = range_gen2[range].hsfregrange << 1;
>> + dw_dphy_te_write(dphy, HSFREQRANGE_8BIT, data);
>> +
>> + return 0;
>> +}
>> +
>> +static int dw_dphy_configure(struct dw_dphy_rx *dphy)
>> +{
>> + dw_dphy_pwr_down(dphy);
>> +
>> + if (dphy->dphy_gen == GEN3) {
>> + dw_dphy_if_init(dphy);
>> +
>> + if (dphy->dphy_te_len == BIT12)
>> + dw_dphy_gen3_12bit_configure(dphy);
>> + else
>> + dw_dphy_gen3_8bit_configure(dphy);
>> + } else
>> + dw_dphy_gen2_configure(dphy);
>> +
>> + dw_dphy_pwr_up(dphy);
>> +
>> + return 0;
>> +}
>> +
>> +int dw_dphy_if_set_idelay(struct dw_dphy_rx *dphy, u8 dly, u8 cells)
>> +{
>> + uint32_t val = 0;
>> +
>> + dw_dphy_if_write(dphy, IDLYCFG, 0);
>> +
>> + dw_dphy_if_write(dphy, IDLYSEL, cells);
>> + dw_dphy_if_write(dphy, IDLYCNTINVAL, dly);
>> +
>> + /* Pulse Value Set */
>> + dw_dphy_if_write(dphy, IDLYCFG, 1);
>> + usleep_range(10, 20);
>> + dw_dphy_if_write(dphy, IDLYCFG, 0);
>> +
>> + /* Pulse IDELAY CTRL Reset */
>> + dw_dphy_if_write(dphy, DPHY1REGRSTN, 0);
>> + usleep_range(10, 20);
>> + dw_dphy_if_write(dphy, DPHY1REGRSTN, 1);
>> +
>> + /* Get Value*/
>> + val = dw_dphy_if_read(dphy, IDLYCNTOUTVAL);
>> +
>> + if (val != dly) {
>> + pr_info("odelay config failed, set %d get %d", dly, val);
>> + return -1;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +int dw_dphy_if_get_idelay(struct dw_dphy_rx *dphy)
>> +{
>> + return dw_dphy_if_read(dphy, IDLYCNTOUTVAL);
>> +}
>> +
>> +int dw_dphy_if_set_idelay_lane(struct dw_dphy_rx *dphy, u8 dly, u8 lane)
>> +{
>> + int cell;
>> +
>> + switch (lane) {
>> + case 0:
>> + for (cell = 3; cell <= 10; cell++)
>> + dw_dphy_if_set_idelay(dphy, dly, cell);
>> + break;
>> + case 1:
>> + for (cell = 14; cell <= 21; cell++)
>> + dw_dphy_if_set_idelay(dphy, dly, cell);
>> + break;
>> + case 2:
>> + for (cell = 24; cell <= 31; cell++)
>> + dw_dphy_if_set_idelay(dphy, dly, cell);
>> + break;
>> + case 3:
>> + for (cell = 34; cell <= 41; cell++)
>> + dw_dphy_if_set_idelay(dphy, dly, cell);
>> + break;
>> + case 4: /* ALL */
>> + dw_dphy_if_set_idelay(dphy, dly, 0x7F);
>> + break;
>> + default:
>> + pr_err("Lane Value not recognized\n");
>> + return -1;
>> + }
>> + return 0;
>> +}
>> +
>> +int dw_dphy_init(struct phy *phy)
>> +{
>> + struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
>> +
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
>> +
>> + return 0;
>> +}
>> +
>> +static int dw_dphy_set_phy_state(struct dw_dphy_rx *dphy, u32 on)
>> +{
>> + u8 hs_freq;
>> +
>> + dphy->lanes_config = dw_dphy_setup_config(dphy);
>> +
>> + if (dphy->dphy_te_len == BIT12)
>> + hs_freq = RX_SYS_1;
>> + else
>> + hs_freq = HSFREQRANGE_8BIT;
>> +
>> + if (on) {
>> + dw_dphy_configure(dphy);
>> + pr_debug("HS Code: 0X%x\n", dw_dphy_te_read(dphy, hs_freq));
>> + } else {
>> + dw_dphy_write(dphy, R_CSI2_DPHY_SHUTDOWNZ, 0);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +int dw_dphy_power_on(struct phy *phy)
>> +{
>> + struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
>> +
>> + return dw_dphy_set_phy_state(dphy, 1);
>> +}
>> +
>> +int dw_dphy_power_off(struct phy *phy)
>> +{
>> + struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
>> +
>> + return dw_dphy_set_phy_state(dphy, 0);
>> +}
>> +
>> +int dw_dphy_reset(struct phy *phy)
>> +{
>> + struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
>> +
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 0);
>> + usleep_range(100, 200);
>> + dw_dphy_write(dphy, R_CSI2_DPHY_RSTZ, 1);
>> +
>> + return 0;
>> +}
>> diff --git a/drivers/media/platform/dwc/dw-dphy-rx.h
>> b/drivers/media/platform/dwc/dw-dphy-rx.h new file mode 100644
>> index 0000000..5375685
>> --- /dev/null
>> +++ b/drivers/media/platform/dwc/dw-dphy-rx.h
>> @@ -0,0 +1,176 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Synopsys MIPI D-PHY driver
>> + *
>> + * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
>> + * Author: Luis Oliveira <[email protected]>
>> + */
>> +
>> +#ifndef __PHY_SNPS_DPHY_RX_H__
>> +#define __PHY_SNPS_DPHY_RX_H__
>> +
>> +#include <linux/debugfs.h>
>> +#include <linux/delay.h>
>> +#include <linux/gpio.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/phy/phy.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/spinlock.h>
>> +
>> +/* DPHY interface register bank*/
>> +#define R_CSI2_DPHY_SHUTDOWNZ 0x0
>> +#define R_CSI2_DPHY_RSTZ 0x4
>> +#define R_CSI2_DPHY_RX 0x8
>> +#define R_CSI2_DPHY_STOPSTATE 0xC
>> +#define R_CSI2_DPHY_TST_CTRL0 0x10
>> +#define R_CSI2_DPHY_TST_CTRL1 0x14
>> +#define R_CSI2_DPHY2_TST_CTRL0 0x18
>> +#define R_CSI2_DPHY2_TST_CTRL1 0x1C
>> +
>> +enum dphy_id_mask {
>> + DPHY_ID_LANE_SUPPORT = 0,
>> + DPHY_ID_IF = 4,
>> + DPHY_ID_GEN = 8,
>> +};
>> +
>> +enum dphy_gen_values {
>> + GEN1 = 0,
>> + GEN2 = 1,
>> + GEN3 = 2,
>> +};
>> +
>> +enum dphy_interface_length {
>> + BIT8 = 8,
>> + BIT12 = 12,
>> +};
>> +
>> +enum tst_ctrl0 {
>> + PHY_TESTCLR = 0,
>> + PHY_TESTCLK = 1,
>> +};
>> +
>> +enum tst_ctrl1 {
>> + PHY_TESTDIN = 0,
>> + PHY_TESTDOUT = 8,
>> + PHY_TESTEN = 16,
>> +};
>> +
>> +enum lanes_config_values {
>> + CTRL_4_LANES = 0,
>> + CTRL_8_LANES = 1,
>> +};
>> +
>> +enum dphy_tc {
>> + CFGCLKFREQRANGE_TX = 0x02,
>> + CFGCLKFREQRANGE_RX = 0x05,
>> + BYPASS = 0x20,
>> + IO_DS = 0x30,
>> +};
>> +
>> +enum dphy_8bit_interface_addr {
>> + BANDGAP_CTRL = 0x24,
>> + HS_RX_CTRL_LANE0 = 0x42,
>> + HSFREQRANGE_8BIT = 0x44,
>> + OSC_FREQ_TARGET_RX0_LSB = 0x4e,
>> + OSC_FREQ_TARGET_RX0_MSB = 0x4f,
>> + HS_RX_CTRL_LANE1 = 0x52,
>> + OSC_FREQ_TARGET_RX1_LSB = 0x5e,
>> + OSC_FREQ_TARGET_RX1_MSB = 0x5f,
>> + RX_SKEW_CAL = 0x7e,
>> + HS_RX_CTRL_LANE2 = 0x82,
>> + OSC_FREQ_TARGET_RX2_LSB = 0x8e,
>> + OSC_FREQ_TARGET_RX2_MSB = 0x8f,
>> + HS_RX_CTRL_LANE3 = 0x92,
>> + OSC_FREQ_TARGET_RX3_LSB = 0x9e,
>> + OSC_FREQ_TARGET_RX3_MSB = 0x9f,
>> +};
>> +
>> +enum dphy_12bit_interface_addr {
>> + RX_SYS_0 = 0x01,
>> + RX_SYS_1 = 0x02,
>> + RX_SYS_7 = 0x08,
>> + RX_RX_STARTUP_OVR_0 = 0xe0,
>> + RX_RX_STARTUP_OVR_1 = 0xe1,
>> + RX_RX_STARTUP_OVR_2 = 0xe2,
>> + RX_RX_STARTUP_OVR_3 = 0xe3,
>> + RX_RX_STARTUP_OVR_4 = 0xe4,
>> +};
>> +
>> +/* Gen3 interface register bank*/
>> +#define IDLYCFG 0x00
>> +#define IDLYSEL 0x04
>> +#define IDLYCNTINVAL 0x08
>> +#define IDLYCNTOUTVAL 0x0c
>> +#define DPHY1REGRSTN 0x10
>> +#define DPHYZCALSTAT 0x14
>> +#define DPHYZCALCTRL 0x18
>> +#define DPHYLANE0STAT 0x1c
>> +#define DPHYLANE1STAT 0x20
>> +#define DPHYLANE2STAT 0x24
>> +#define DPHYLANE3STAT 0x28
>> +#define DPHYCLKSTAT 0x2c
>> +#define DPHYZCLKCTRL 0x30
>> +#define TCGENPURPOSOUT 0x34
>> +#define TCGENPURPOSIN 0x38
>> +#define DPHYGENERICOUT 0x3c
>> +#define DPHYGENERICIN 0x40
>> +#define DPHYGLUEIFTESTER 0x44
>> +#define DPHYID 0x100
>> +
>> +enum glueiftester {
>> + GLUELOGIC = 0x4,
>> + RX_PHY = 0x2,
>> + TX_PHY = 0x1,
>> + RESET = 0x0,
>> +};
>> +
>> +struct dw_dphy_rx {
>> + spinlock_t slock;
>> + struct phy *phy;
>> + uint32_t dphy_freq;
>> + uint32_t dphy_gen;
>> + uint32_t dphy_te_len;
>> + uint32_t lanes_config;
>> + uint32_t max_lanes;
>> + uint32_t compat_mode;
>> + uint32_t lp_time;
>> +
>> + void __iomem *base_address; /* test interface */
>> + void __iomem *dphy1_if_addr; /* gluelogic phy 1 */
>> + void __iomem *dphy2_if_addr; /* gluelogic phy 2 */
>> +
>> + int config_gpio;
>> + uint8_t setup_config;
>> +};
>> +
>> +int dw_dphy_init(struct phy *phy);
>> +int dw_dphy_reset(struct phy *phy);
>> +int dw_dphy_power_off(struct phy *phy);
>> +int dw_dphy_power_on(struct phy *phy);
>> +
>> +u8 dw_dphy_setup_config(struct dw_dphy_rx *dphy);
>> +u32 dw_dphy_if_read(struct dw_dphy_rx *dphy, u64 address);
>> +void dw_dphy_write(struct dw_dphy_rx *dphy, u32 address, u32 data);
>> +u32 dw_dphy_read(struct dw_dphy_rx *dphy, u64 address);
>> +int dw_dphy_te_read(struct dw_dphy_rx *dphy, u32 addr);
>> +int dw_dphy_if_get_idelay(struct dw_dphy_rx *dphy);
>> +int dw_dphy_if_set_idelay_lane(struct dw_dphy_rx *dphy, u8 dly, u8 lane);
>> +
>> +static inline
>> +u32 dw_dphy_if_read_msk(struct dw_dphy_rx *dphy,
>> + u32 address, u8 shift, u8 width)
>> +{
>> + return (dw_dphy_if_read(dphy, address) >> shift) & ((1 << width) - 1);
>> +}
>> +
>> +static inline
>> +u32 dw_dphy_read_msk(struct dw_dphy_rx *dev, u32 address, u8 shift, u8
>> width) +{
>> + return (dw_dphy_read(dev, address) >> shift) & ((1 << width) - 1);
>> +}
>> +#endif /*__PHY_SNPS_DPHY_RX_H__*/
>
>


2018-09-21 15:28:10

by Luis de Oliveira

[permalink] [raw]
Subject: Re: [V2, 0/5] platform: dwc: Add of DesignWare MIPI CSI-2 Host

On 21-Sep-18 15:37, Maxime Ripard wrote:
> Hi Luis,
>
> On Thu, Sep 20, 2018 at 01:16:38PM +0200, Luis Oliveira wrote:
>> This adds support for Synopsys MIPI CSI-2 Host and MIPI D-PHY.
>> The patch series include support for initialization/configuration of the
>> DW MIPI CSI-2 controller and DW MIPI D-PHY and both include a reference
>> platform driver.
>>
>> This will enable future SoCs to use this standard approach and possibly
>> create a more clean environment.
>>
>> This series also documents the dt-bindings needed for the platform drivers.
>>
>> This was applied in: https://git.linuxtv.org/media_tree.git
>
> I'm currently working on some MIPI D-PHY support through the generic
> phy framework that could benefit your patches.
>
> https://lwn.net/Articles/764173/
>
> Feel free to comment on that serie if you have any particular
> constraints or if you believe that some issues should be addressed.

Hi Maxime,

I will check that, thanks!

>
> Thanks!
> Maxime
>


2018-10-12 16:46:25

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [V2, 2/5] Documentation: dt-bindings: Document the Synopsys MIPI DPHY Rx bindings

On Thu, Sep 20, 2018 at 01:16:40PM +0200, Luis Oliveira wrote:
> Add device-tree bindings documentation for SNPS DesignWare MIPI D-PHY in
> RX mode.

"dt-bindings: phy: ..." for the subject.

>
> Signed-off-by: Luis Oliveira <[email protected]>
> ---
> Changelog
> v2:
> - no changes
>
> .../devicetree/bindings/phy/snps,dphy-rx.txt | 36 ++++++++++++++++++++++
> 1 file changed, 36 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
>
> diff --git a/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
> new file mode 100644
> index 0000000..9079f4a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
> @@ -0,0 +1,36 @@
> +Synopsys DesignWare MIPI Rx D-PHY block details
> +
> +Description
> +-----------
> +
> +The Synopsys MIPI D-PHY controller supports MIPI-DPHY in receiver mode.
> +Please refer to phy-bindings.txt for more information.
> +
> +Required properties:
> +- compatible : Shall be "snps,dphy-rx".
> +- #phy-cells : Must be 1.
> +- snps,dphy-frequency : Output frequency of the D-PHY.
> +- snps,dphy-te-len : Size of the communication interface (8 bits->8 or 12bits->12).
> +- reg : Physical base address and size of the device memory mapped
> + registers;
> +
> +Optional properties:
> +- snps,compat-mode : Compatibility mode control

type? values?

> +
> +The per-board settings:
> +- gpios : Synopsys testchip used as reference uses this to change setup
> + configurations.

Preferred to be named (e.g. foo-gpios). How many? What are their
functions?

> +
> +Example:
> +
> + mipi_dphy_rx1: dphy@3040 {
> + compatible = "snps,dphy-rx";
> + #phy-cells = <1>;
> + snps,dphy-frequency = <300000>;
> + snps,dphy-te-len = <12>;
> + snps,compat-mode = <1>;
> + reg = < 0x03040 0x20
> + 0x08000 0x100
> + 0x09000 0x100>;
> + };
> +
> --
> 2.9.3
>

2018-10-12 16:48:50

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [V2, 2/5] Documentation: dt-bindings: Document the Synopsys MIPI DPHY Rx bindings

On Fri, Oct 12, 2018 at 11:45:48AM -0500, Rob Herring wrote:
> On Thu, Sep 20, 2018 at 01:16:40PM +0200, Luis Oliveira wrote:
> > Add device-tree bindings documentation for SNPS DesignWare MIPI D-PHY in
> > RX mode.
>
> "dt-bindings: phy: ..." for the subject.
>
> >
> > Signed-off-by: Luis Oliveira <[email protected]>

Also, checkpatch.pl complains the author and S-o-b emails don't match.

Rob

2018-10-15 16:22:11

by Luis de Oliveira

[permalink] [raw]
Subject: Re: [V2, 2/5] Documentation: dt-bindings: Document the Synopsys MIPI DPHY Rx bindings

Hi Rob,

On 12-Oct-18 17:45, Rob Herring wrote:
> On Thu, Sep 20, 2018 at 01:16:40PM +0200, Luis Oliveira wrote:
>> Add device-tree bindings documentation for SNPS DesignWare MIPI D-PHY in
>> RX mode.
>
> "dt-bindings: phy: ..." for the subject.
>
Yes, you are right.

>>
>> Signed-off-by: Luis Oliveira <[email protected]>
>> ---
>> Changelog
>> v2:
>> - no changes
>>
>> .../devicetree/bindings/phy/snps,dphy-rx.txt | 36 ++++++++++++++++++++++
>> 1 file changed, 36 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
>>
>> diff --git a/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
>> new file mode 100644
>> index 0000000..9079f4a
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/phy/snps,dphy-rx.txt
>> @@ -0,0 +1,36 @@
>> +Synopsys DesignWare MIPI Rx D-PHY block details
>> +
>> +Description
>> +-----------
>> +
>> +The Synopsys MIPI D-PHY controller supports MIPI-DPHY in receiver mode.
>> +Please refer to phy-bindings.txt for more information.
>> +
>> +Required properties:
>> +- compatible : Shall be "snps,dphy-rx".
>> +- #phy-cells : Must be 1.
>> +- snps,dphy-frequency : Output frequency of the D-PHY.
>> +- snps,dphy-te-len : Size of the communication interface (8 bits->8 or 12bits->12).
>> +- reg : Physical base address and size of the device memory mapped
>> + registers;
>> +
>> +Optional properties:
>> +- snps,compat-mode : Compatibility mode control
>
> type? values?
>

I will remove this in V3.

>> +
>> +The per-board settings:
>> +- gpios : Synopsys testchip used as reference uses this to change setup
>> + configurations.
>
> Preferred to be named (e.g. foo-gpios). How many? What are their
> functions?
>

Ok, thanks for reviewing this.

>> +
>> +Example:
>> +
>> + mipi_dphy_rx1: dphy@3040 {
>> + compatible = "snps,dphy-rx";
>> + #phy-cells = <1>;
>> + snps,dphy-frequency = <300000>;
>> + snps,dphy-te-len = <12>;
>> + snps,compat-mode = <1>;
>> + reg = < 0x03040 0x20
>> + 0x08000 0x100
>> + 0x09000 0x100>;
>> + };
>> +
>> --
>> 2.9.3
>>