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 the DW MIPI D-PHY and both include a reference
platform driver and the option to use platform data. The optional sysfs
interface is used to re-configure the controller and the phy for
prototyping purposes.
This series also documents the dt-bindings needed for the platform drivers.
This will enable future SoCs to use this standard approach to Synopsys
DesignWare CSI-2 and D-PHY and hopefully create a more clean environment.
This series are following an old series submitted on v4.19.rc1 and are
result of all the feedback received, please check the changelog per patch.
This was applied in: https://git.linuxtv.org/media_tree.git
Luis Oliveira (6):
dt-bindings: media: Document bindings for DW MIPI CSI-2 Host
media: platform: dwc: Add MIPI CSI-2 controller driver
media: platform: dwc: Add MIPI CSI-2 platform data
dt-bindings: phy: Document the Synopsys MIPI DPHY Rx bindings
media: platform: dwc: Add DW MIPI DPHY Rx driver
media: platform: dwc: Add platform data support to D-Phy
.../devicetree/bindings/media/snps,dw-csi.txt | 41 ++
.../devicetree/bindings/phy/snps,dw-dphy-rx.txt | 29 +
MAINTAINERS | 11 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/dwc/Kconfig | 41 ++
drivers/media/platform/dwc/Makefile | 15 +
drivers/media/platform/dwc/dw-csi-plat.c | 516 +++++++++++++++++
drivers/media/platform/dwc/dw-csi-plat.h | 97 ++++
drivers/media/platform/dwc/dw-csi-sysfs.c | 624 +++++++++++++++++++++
drivers/media/platform/dwc/dw-dphy-plat.c | 224 ++++++++
drivers/media/platform/dwc/dw-dphy-rx.c | 611 ++++++++++++++++++++
drivers/media/platform/dwc/dw-dphy-rx.h | 212 +++++++
drivers/media/platform/dwc/dw-dphy-sysfs.c | 232 ++++++++
drivers/media/platform/dwc/dw-mipi-csi.c | 569 +++++++++++++++++++
drivers/media/platform/dwc/dw-mipi-csi.h | 287 ++++++++++
include/media/dwc/dw-csi-data.h | 26 +
include/media/dwc/dw-dphy-data.h | 32 ++
include/media/dwc/dw-mipi-csi-pltfrm.h | 104 ++++
19 files changed, 3674 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
create mode 100644 Documentation/devicetree/bindings/phy/snps,dw-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-csi-sysfs.c
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-dphy-sysfs.c
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-csi-data.h
create mode 100644 include/media/dwc/dw-dphy-data.h
create mode 100644 include/media/dwc/dw-mipi-csi-pltfrm.h
--
2.7.4
From: Luis Oliveira <[email protected]>
Add bindings for Synopsys DesignWare MIPI CSI-2 host.
Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v3-v4
- remove "plat" from the block name @rob @laurent
- remove "phy-names" when single-entry @rob
- remove "snps,output-type" -> went to the driver config @laurent
.../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
new file mode 100644
index 0000000..613b7f9
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
@@ -0,0 +1,41 @@
+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"
+- reg : physical base address and size of the device memory
+ mapped registers;
+- interrupts : DW CSI-2 Host interrupts
+- 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)
+
+The per-board settings:
+ - port sub-node describing a single endpoint connected to the camera as
+ described in video-interfaces.txt[1].
+
+Example:
+
+ csi2: csi2@3000 {
+ compatible = "snps,dw-csi";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = < 0x03000 0x7FF>;
+ phys = <&mipi_dphy_rx>;
+ resets = <&dw_rst 1>;
+ interrupts = <2>;
+
+ port@0 {
+ reg = <0>;
+ csi_ep1: endpoint {
+ remote-endpoint = <&camera_1>;
+ data-lanes = <1 2>;
+ };
+ };
+ };
--
2.7.4
This allows the driver loading via platform data which makes the driver
not device tree dependent.
Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v3-v4
- not present on v3, allows configuration using pdata
MAINTAINERS | 1 +
drivers/media/platform/dwc/dw-csi-plat.c | 105 +++++++++++++++++++++----------
include/media/dwc/dw-csi-data.h | 26 ++++++++
3 files changed, 100 insertions(+), 32 deletions(-)
create mode 100644 include/media/dwc/dw-csi-data.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 6ffe4fd..2df1f7c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15193,6 +15193,7 @@ L: [email protected]
T: git git://linuxtv.org/media_tree.git
S: Maintained
F: drivers/media/platform/dwc
+F: include/media/dwc/dw-csi-data.h
F: Documentation/devicetree/bindings/media/snps,dw-csi.txt
SYNOPSYS DESIGNWARE DMAC DRIVER
diff --git a/drivers/media/platform/dwc/dw-csi-plat.c b/drivers/media/platform/dwc/dw-csi-plat.c
index 9828d55..c7117c6 100644
--- a/drivers/media/platform/dwc/dw-csi-plat.c
+++ b/drivers/media/platform/dwc/dw-csi-plat.c
@@ -8,6 +8,7 @@
* Author: Luis Oliveira <[email protected]>
*/
+#include <media/dwc/dw-csi-data.h>
#include <media/dwc/dw-dphy-data.h>
#include "dw-csi-plat.h"
@@ -312,12 +313,16 @@ static const struct of_device_id dw_mipi_csi_of_match[];
static int dw_csi_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id = NULL;
+ struct dw_csih_pdata *pdata;
struct device *dev = &pdev->dev;
struct resource *res = NULL;
struct dw_csi *csi;
struct v4l2_subdev *sd;
int ret;
+ if (!IS_ENABLED(CONFIG_OF))
+ pdata = pdev->dev.platform_data;
+
dev_vdbg(dev, "Probing started\n");
/* Resource allocation */
@@ -329,18 +334,29 @@ static int dw_csi_probe(struct platform_device *pdev)
spin_lock_init(&csi->slock);
csi->dev = dev;
- of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
- if (!of_id)
- return -EINVAL;
+ if (dev->of_node) {
+ of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
+ if (!of_id)
+ return -EINVAL;
- ret = dw_mipi_csi_parse_dt(pdev, csi);
- if (ret < 0)
- return ret;
+ ret = dw_mipi_csi_parse_dt(pdev, csi);
+ if (ret < 0)
+ return ret;
- csi->phy = devm_of_phy_get(dev, dev->of_node, NULL);
- if (IS_ERR(csi->phy)) {
- dev_err(dev, "No DPHY available\n");
- return PTR_ERR(csi->phy);
+ csi->phy = devm_of_phy_get(dev, dev->of_node, NULL);
+ if (IS_ERR(csi->phy)) {
+ dev_err(dev, "No DPHY available\n");
+ return PTR_ERR(csi->phy);
+ }
+ } else {
+ csi->phy = devm_phy_get(dev, phys[pdata->id].name);
+ if (IS_ERR(csi->phy)) {
+ dev_err(dev, "No '%s' DPHY available\n",
+ phys[pdata->id].name);
+ return PTR_ERR(csi->phy);
+ }
+ dev_vdbg(dev, "got D-PHY %s with id %d\n", phys[pdata->id].name,
+ csi->phy->id);
}
/* Registers mapping */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -370,7 +386,10 @@ static int dw_csi_probe(struct platform_device *pdev)
dw_mipi_csi_irq1, IRQF_SHARED,
dev_name(dev), csi);
if (ret) {
- dev_err(dev, "irq csi %s failed\n", of_id->name);
+ if (dev->of_node)
+ dev_err(dev, "irq csi %s failed\n", of_id->name);
+ else
+ dev_err(dev, "irq csi %d failed\n", pdata->id);
goto end;
}
@@ -379,25 +398,44 @@ static int dw_csi_probe(struct platform_device *pdev)
v4l2_subdev_init(sd, &dw_mipi_csi_subdev_ops);
csi->sd.owner = THIS_MODULE;
- snprintf(sd->name, sizeof(sd->name), "%s.%d",
- "dw-csi", csi->index);
+ if (dev->of_node) {
+ snprintf(sd->name, sizeof(sd->name), "%s.%d",
+ "dw-csi", csi->index);
- csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ } else {
+ strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
+ strlcpy(csi->v4l2_dev.name, dev_name(dev),
+ sizeof(csi->v4l2_dev.name));
+ }
csi->fmt = &dw_mipi_csi_formats[0];
csi->format.code = dw_mipi_csi_formats[0].mbus_code;
sd->entity.function = MEDIA_ENT_F_IO_V4L;
- csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
- ret = media_entity_pads_init(&csi->sd.entity,
- CSI_PADS_NUM, csi->pads);
- dev_vdbg(dev, "v4l2.name: %s\n", csi->v4l2_dev.name);
+ if (dev->of_node) {
+ csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
- if (ret < 0) {
- dev_err(dev, "media entity init failed\n");
- goto end;
+ ret = media_entity_pads_init(&csi->sd.entity,
+ CSI_PADS_NUM, csi->pads);
+ if (ret < 0) {
+ dev_err(dev, "media entity init failed\n");
+ goto end;
+ }
+ } else {
+ csi->hw.num_lanes = pdata->lanes;
+ csi->hw.pclk = pdata->pclk;
+ csi->hw.fps = pdata->fps;
+ csi->hw.dphy_freq = pdata->hs_freq;
+
+ ret = v4l2_device_register(NULL, &csi->v4l2_dev);
+ if (ret) {
+ dev_err(dev, "failed to register v4l2 device\n");
+ goto end;
+ }
}
+ dev_vdbg(dev, "v4l2.name: %s\n", csi->v4l2_dev.name);
v4l2_set_subdevdata(&csi->sd, pdev);
platform_set_drvdata(pdev, &csi->sd);
@@ -405,6 +443,7 @@ static int dw_csi_probe(struct platform_device *pdev)
if (csi->rst)
reset_control_deassert(csi->rst);
+
#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
dw_csi_create_capabilities_sysfs(pdev);
#endif
@@ -418,11 +457,12 @@ static int dw_csi_probe(struct platform_device *pdev)
phy_init(csi->phy);
return 0;
-
end:
+#if IS_ENABLED(CONFIG_OF)
media_entity_cleanup(&csi->sd.entity);
+ return ret;
+#endif
v4l2_device_unregister(csi->vdev.v4l2_dev);
-
return ret;
}
@@ -436,35 +476,36 @@ static int dw_csi_remove(struct platform_device *pdev)
struct v4l2_subdev *sd = platform_get_drvdata(pdev);
struct dw_csi *mipi_csi = sd_to_mipi_csi_dev(sd);
- dev_dbg(&pdev->dev, "Removing MIPI CSI-2 module\n");
+ dev_dbg(&pdev->dev, "Removing DW MIPI CSI-2 Host module\n");
if (mipi_csi->rst)
reset_control_assert(mipi_csi->rst);
+#if IS_ENABLED(CONFIG_OF)
media_entity_cleanup(&mipi_csi->sd.entity);
-
+#else
+ v4l2_device_unregister(mipi_csi->vdev.v4l2_dev);
+#endif
return 0;
}
-/**
- * @short of_device_id structure
- */
+#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id dw_mipi_csi_of_match[] = {
{ .compatible = "snps,dw-csi" },
{},
};
MODULE_DEVICE_TABLE(of, dw_mipi_csi_of_match);
+#endif
-/**
- * @short Platform driver structure
- */
static struct platform_driver dw_mipi_csi_driver = {
.remove = dw_csi_remove,
.probe = dw_csi_probe,
.driver = {
.name = "dw-csi",
.owner = THIS_MODULE,
+#if IS_ENABLED(CONFIG_OF)
.of_match_table = of_match_ptr(dw_mipi_csi_of_match),
+#endif
},
};
diff --git a/include/media/dwc/dw-csi-data.h b/include/media/dwc/dw-csi-data.h
new file mode 100644
index 0000000..87942ab
--- /dev/null
+++ b/include/media/dwc/dw-csi-data.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI CSI-2 platform data
+ *
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#include <linux/kernel.h>
+#include <media/dwc/dw-mipi-csi-pltfrm.h>
+
+struct dw_csih_pdata {
+ u8 eotp_enabled;
+ u32 hs_freq;
+ u32 lanes;
+ u32 pclk;
+ u32 fps;
+ u32 bpp;
+ u8 id;
+};
+
+static const struct pdata_names csis[] = {
+ { .name = "dw-csi.0", },
+ { .name = "dw-csi.1", },
+};
--
2.7.4
Add the Synopsys MIPI CSI-2 controller driver. This
controller driver is divided in platform functions and core functions.
This way it serves as platform for future DesignWare drivers.
Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v3-v4
- fix v4l2_fwnode_endpoint bad initialization @eugen
- removed extra lines and fixed coding style issues
MAINTAINERS | 8 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/dwc/Kconfig | 19 +
drivers/media/platform/dwc/Makefile | 9 +
drivers/media/platform/dwc/dw-csi-plat.c | 475 +++++++++++++++++++++++
drivers/media/platform/dwc/dw-csi-plat.h | 97 +++++
drivers/media/platform/dwc/dw-csi-sysfs.c | 624 ++++++++++++++++++++++++++++++
drivers/media/platform/dwc/dw-mipi-csi.c | 569 +++++++++++++++++++++++++++
drivers/media/platform/dwc/dw-mipi-csi.h | 287 ++++++++++++++
include/media/dwc/dw-mipi-csi-pltfrm.h | 104 +++++
11 files changed, 2195 insertions(+)
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-csi-sysfs.c
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 16a97ba..6ffe4fd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15187,6 +15187,14 @@ S: Maintained
F: drivers/dma/dwi-axi-dmac/
F: Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.txt
+SYNOPSYS DESIGNWARE MIPI DPHY CSI-2 HOST DRIVER
+M: Luis Oliveira <[email protected]>
+L: [email protected]
+T: git git://linuxtv.org/media_tree.git
+S: Maintained
+F: drivers/media/platform/dwc
+F: Documentation/devicetree/bindings/media/snps,dw-csi.txt
+
SYNOPSYS DESIGNWARE DMAC DRIVER
M: Viresh Kumar <[email protected]>
R: Andy Shevchenko <[email protected]>
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 8a19654..b6fb139 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -147,6 +147,7 @@ source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
source "drivers/media/platform/sunxi/sun6i-csi/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 7cbbd92..4807caf 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -101,3 +101,5 @@ obj-y += meson/
obj-y += cros-ec-cec/
obj-$(CONFIG_VIDEO_SUN6I_CSI) += sunxi/sun6i-csi/
+
+obj-y += dwc/
diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
new file mode 100644
index 0000000..508ac21
--- /dev/null
+++ b/drivers/media/platform/dwc/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Synopsys DWC Platform drivers
+# Drivers here are currently for MIPI CSI-2 support
+
+config DWC_MIPI_CSI2_HOST
+ tristate "Synopsys DesignWare CSI-2 Host Controller support"
+ select VIDEO_DEV
+ select VIDEO_V4L2
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ help
+ This selects the DesignWare MIPI CSI-2 host controller support. This
+ controller gives access to control a CSI-2 receiver acting as a V4L2
+ subdevice.
+
+ If you have a controller with this interface, say Y.
+
+ If unsure, say N.
diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
new file mode 100644
index 0000000..057f137
--- /dev/null
+++ b/drivers/media/platform/dwc/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for Synopsys DWC Platform drivers
+#
+dw-csi-objs := dw-csi-plat.o dw-mipi-csi.o
+ifeq ($(CONFIG_DWC_MIPI_TC_DPHY_GEN3),y)
+ dw-csi-objs += dw-csi-sysfs.o
+endif
+obj-$(CONFIG_DWC_MIPI_CSI2_HOST) += dw-csi.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..9828d55
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-csi-plat.c
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI CSI-2 Host controller driver.
+ * Platform driver
+ *
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#include <media/dwc/dw-dphy-data.h>
+
+#include "dw-csi-plat.h"
+
+const struct mipi_dt csi_dt[] = {
+ {
+ .hex = CSI_2_YUV420_8,
+ .name = "YUV420_8bits",
+ }, {
+ .hex = CSI_2_YUV420_10,
+ .name = "YUV420_10bits",
+ }, {
+ .hex = CSI_2_YUV420_8_LEG,
+ .name = "YUV420_8bits_LEGACY",
+ }, {
+ .hex = CSI_2_YUV420_8_SHIFT,
+ .name = "YUV420_8bits_SHIFT",
+ }, {
+ .hex = CSI_2_YUV420_10_SHIFT,
+ .name = "YUV420_10bits_SHIFT",
+ }, {
+ .hex = CSI_2_YUV422_8,
+ .name = "YUV442_8bits",
+ }, {
+ .hex = CSI_2_YUV422_10,
+ .name = "YUV442_10bits",
+ }, {
+ .hex = CSI_2_RGB444,
+ .name = "RGB444",
+ }, {
+ .hex = CSI_2_RGB555,
+ .name = "RGB555",
+ }, {
+ .hex = CSI_2_RGB565,
+ .name = "RGB565",
+ }, {
+ .hex = CSI_2_RGB666,
+ .name = "RGB666",
+ }, {
+ .hex = CSI_2_RGB888,
+ .name = "RGB888",
+ }, {
+ .hex = CSI_2_RAW6,
+ .name = "RAW6",
+ }, {
+ .hex = CSI_2_RAW7,
+ .name = "RAW7",
+ }, {
+ .hex = CSI_2_RAW8,
+ .name = "RAW8",
+ }, {
+ .hex = CSI_2_RAW10,
+ .name = "RAW10",
+ }, {
+ .hex = CSI_2_RAW12,
+ .name = "RAW12",
+ }, {
+ .hex = CSI_2_RAW14,
+ .name = "RAW14",
+ }, {
+ .hex = CSI_2_RAW16,
+ .name = "RAW16",
+ },
+};
+
+static 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].mbus_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;
+
+ if (code->index != 0)
+ return -EINVAL;
+
+ code->code = dw_mipi_csi_formats[code->index].mbus_code;
+ return 0;
+}
+
+static struct mipi_fmt *
+dw_mipi_csi_try_format(struct v4l2_mbus_framefmt *mf)
+{
+ struct mipi_fmt *fmt;
+
+ fmt = find_dw_mipi_csi_format(mf);
+ if (!fmt)
+ fmt = &dw_mipi_csi_formats[0];
+
+ mf->code = fmt->mbus_code;
+
+ return fmt;
+}
+
+static struct v4l2_mbus_framefmt *
+dw_mipi_csi_get_format(struct dw_csi *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;
+ dev_dbg(dev->dev,
+ "%s got v4l2_mbus_pixelcode. 0x%x\n", __func__,
+ dev->format.code);
+ dev_dbg(dev->dev,
+ "%s got width. 0x%x\n", __func__,
+ dev->format.width);
+ dev_dbg(dev->dev,
+ "%s got height. 0x%x\n", __func__,
+ dev->format.height);
+ 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 dw_csi *dev = sd_to_mipi_csi_dev(sd);
+ struct mipi_fmt *dev_fmt;
+ struct v4l2_mbus_framefmt *mf = dw_mipi_csi_get_format(dev, cfg,
+ fmt->which);
+ int i;
+
+ 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;
+ dev->fmt->mbus_code = mf->code;
+ dw_mipi_csi_set_ipi_fmt(dev);
+ }
+
+ if (fmt->format.width > 0 && fmt->format.height > 0) {
+ dw_mipi_csi_fill_timings(dev, fmt);
+ } else {
+ dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
+ __func__, fmt->format.width);
+ dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
+ __func__, fmt->format.height);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(csi_dt); i++)
+ if (csi_dt[i].hex == dev->ipi_dt) {
+ dev_vdbg(dev->dev, "Using data type %s\n",
+ csi_dt[i].name);
+ }
+ 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 dw_csi *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 dw_csi *dev = sd_to_mipi_csi_dev(sd);
+
+ dev_vdbg(dev->dev, "%s: on=%d\n", __func__, on);
+
+ 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);
+ /* reset data type */
+ dev->ipi_dt = 0x0;
+ }
+ return 0;
+}
+
+static int
+dw_mipi_csi_log_status(struct v4l2_subdev *sd)
+{
+ struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
+
+ dw_mipi_csi_dump(dev);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
+static int
+dw_mipi_csi_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
+{
+ struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
+
+ dev_vdbg(dev->dev, "%s: reg=%llu\n", __func__, reg->reg);
+ reg->val = dw_mipi_csi_read(dev, reg->reg);
+
+ return 0;
+}
+#endif
+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 = MEDIA_BUS_FMT_RGB888_1X24;
+ 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,
+ .log_status = dw_mipi_csi_log_status,
+#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
+ .g_register = dw_mipi_csi_g_register,
+#endif
+};
+
+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 dw_csi *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 dw_csi *dev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
+ int ret = 0;
+
+ if (of_property_read_u32(node, "snps,output-type",
+ &dev->hw.output))
+ dev->hw.output = 2;
+
+ node = of_graph_get_next_endpoint(node, NULL);
+ if (!node) {
+ dev_err(&pdev->dev, "No port node at %pOF\n",
+ pdev->dev.of_node);
+ return -EINVAL;
+ }
+ /* Get port node and validate MIPI-CSI channel id. */
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &ep);
+ if (ret)
+ goto err;
+
+ dev->index = ep.base.port - 1;
+ if (dev->index >= CSI_MAX_ENTITIES) {
+ ret = -ENXIO;
+ goto err;
+ }
+ dev->hw.num_lanes = ep.bus.mipi_csi2.num_data_lanes;
+err:
+ of_node_put(node);
+ return ret;
+}
+
+static const struct of_device_id dw_mipi_csi_of_match[];
+
+static int dw_csi_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id = NULL;
+ struct device *dev = &pdev->dev;
+ struct resource *res = NULL;
+ struct dw_csi *csi;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ dev_vdbg(dev, "Probing started\n");
+
+ /* Resource allocation */
+ csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
+ if (!csi)
+ return -ENOMEM;
+
+ mutex_init(&csi->lock);
+ spin_lock_init(&csi->slock);
+ csi->dev = dev;
+
+ of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
+ if (!of_id)
+ return -EINVAL;
+
+ ret = dw_mipi_csi_parse_dt(pdev, csi);
+ if (ret < 0)
+ return ret;
+
+ csi->phy = devm_of_phy_get(dev, dev->of_node, NULL);
+ if (IS_ERR(csi->phy)) {
+ dev_err(dev, "No DPHY available\n");
+ return PTR_ERR(csi->phy);
+ }
+ /* Registers mapping */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENXIO;
+
+ csi->base_address = devm_ioremap_resource(dev, res);
+ if (IS_ERR(csi->base_address)) {
+ dev_err(dev, "Base address not set.\n");
+ return PTR_ERR(csi->base_address);
+ }
+
+ csi->ctrl_irq_number = platform_get_irq(pdev, 0);
+ if (csi->ctrl_irq_number < 0) {
+ dev_err(dev, "irq number %d not set.\n", csi->ctrl_irq_number);
+ ret = csi->ctrl_irq_number;
+ goto end;
+ }
+
+ csi->rst = devm_reset_control_get_optional_shared(dev, NULL);
+ if (IS_ERR(csi->rst)) {
+ dev_err(dev, "error getting reset control %d\n", ret);
+ return PTR_ERR(csi->rst);
+ }
+
+ ret = devm_request_irq(dev, csi->ctrl_irq_number,
+ dw_mipi_csi_irq1, IRQF_SHARED,
+ dev_name(dev), csi);
+ if (ret) {
+ dev_err(dev, "irq csi %s failed\n", of_id->name);
+
+ goto end;
+ }
+
+ sd = &csi->sd;
+ v4l2_subdev_init(sd, &dw_mipi_csi_subdev_ops);
+ csi->sd.owner = THIS_MODULE;
+
+ snprintf(sd->name, sizeof(sd->name), "%s.%d",
+ "dw-csi", csi->index);
+
+ csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ csi->fmt = &dw_mipi_csi_formats[0];
+ csi->format.code = dw_mipi_csi_formats[0].mbus_code;
+
+ sd->entity.function = MEDIA_ENT_F_IO_V4L;
+ csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&csi->sd.entity,
+ CSI_PADS_NUM, csi->pads);
+
+ dev_vdbg(dev, "v4l2.name: %s\n", csi->v4l2_dev.name);
+
+ if (ret < 0) {
+ dev_err(dev, "media entity init failed\n");
+ goto end;
+ }
+
+ v4l2_set_subdevdata(&csi->sd, pdev);
+ platform_set_drvdata(pdev, &csi->sd);
+ dev_set_drvdata(dev, sd);
+
+ if (csi->rst)
+ reset_control_deassert(csi->rst);
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ dw_csi_create_capabilities_sysfs(pdev);
+#endif
+ dw_mipi_csi_get_version(csi);
+ dw_mipi_csi_specific_mappings(csi);
+ dw_mipi_csi_mask_irq_power_off(csi);
+
+ dev_info(dev, "DW MIPI CSI-2 Host registered successfully HW v%u.%u\n",
+ csi->hw_version_major, csi->hw_version_minor);
+
+ phy_init(csi->phy);
+
+ return 0;
+
+end:
+ media_entity_cleanup(&csi->sd.entity);
+ v4l2_device_unregister(csi->vdev.v4l2_dev);
+
+ return ret;
+}
+
+/**
+ * @short Exit routine - Exit point of the driver
+ * @param[in] pdev pointer to the platform device structure
+ * @return 0 on success
+ */
+static int dw_csi_remove(struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *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" },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, dw_mipi_csi_of_match);
+
+/**
+ * @short Platform driver structure
+ */
+static struct platform_driver dw_mipi_csi_driver = {
+ .remove = dw_csi_remove,
+ .probe = dw_csi_probe,
+ .driver = {
+ .name = "dw-csi",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(dw_mipi_csi_of_match),
+ },
+};
+
+module_platform_driver(dw_mipi_csi_driver);
+
+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..e322592
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-csi-plat.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 Synopsys, Inc.
+ *
+ * Synopsys DesignWare MIPI CSI-2 Host controller driver.
+ * Supported bus formats
+ *
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#ifndef _DW_CSI_PLAT_H__
+#define _DW_CSI_PLAT_H__
+
+#include "dw-mipi-csi.h"
+
+/* Video formats supported by the MIPI CSI-2 */
+struct mipi_fmt dw_mipi_csi_formats[] = {
+ {
+ /* RAW 8 */
+ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .depth = 8,
+ }, {
+ /* RAW 10 */
+ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .depth = 10,
+ }, {
+ /* RAW 12 */
+ .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .depth = 12,
+ }, {
+ /* RAW 14 */
+ .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14,
+ .depth = 14,
+ }, {
+ /* RAW 16 */
+ .mbus_code = MEDIA_BUS_FMT_SBGGR16_1X16,
+ .depth = 16,
+ }, {
+ /* RGB 666 */
+ .mbus_code = MEDIA_BUS_FMT_RGB666_1X18,
+ .depth = 18,
+ }, {
+ /* RGB 565 */
+ .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE,
+ .depth = 16,
+ }, {
+ /* BGR 565 */
+ .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE,
+ .depth = 16,
+ }, {
+ /* RGB 555 */
+ .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
+ .depth = 16,
+ }, {
+ /* BGR 555 */
+ .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
+ .depth = 16,
+ }, {
+ /* RGB 444 */
+ .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE,
+ .depth = 16,
+ }, {
+ /* RGB 444 */
+ .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE,
+ .depth = 16,
+ }, {
+ /* RGB 888 */
+ .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_LE,
+ .depth = 24,
+ }, {
+ /* BGR 888 */
+ .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_BE,
+ .depth = 24,
+ }, {
+ /* BGR 888 */
+ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+ .depth = 24,
+ }, {
+ /* YUV 422 8-bit */
+ .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
+ .depth = 16,
+ }, {
+ /* YUV 422 10-bit */
+ .mbus_code = MEDIA_BUS_FMT_VYUY10_2X10,
+ .depth = 20,
+ }, {
+ /* YUV 420 8-bit LEGACY */
+ .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
+ .depth = 8,
+ }, {
+ /* YUV 420 10-bit */
+ .mbus_code = MEDIA_BUS_FMT_Y10_1X10,
+ .depth = 10,
+ },
+};
+
+#endif /* _DW_CSI_PLAT_H__ */
diff --git a/drivers/media/platform/dwc/dw-csi-sysfs.c b/drivers/media/platform/dwc/dw-csi-sysfs.c
new file mode 100644
index 0000000..e8d2bb9
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-csi-sysfs.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI CSI-2 Host controller driver.
+ * SysFS components for the platform driver
+ *
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#include "dw-mipi-csi.h"
+
+static ssize_t core_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 dw_csi *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 dw_csi *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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%d\n", csi_dev->hw.num_lanes);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t core_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 dw_csi *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 data_type_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 dw_csi *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 0x%lx\n", dt);
+ csi_dev->ipi_dt = dt;
+
+ return count;
+}
+
+static ssize_t data_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%x\n", csi_dev->ipi_dt);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t hsa_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long hsa;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 16, &hsa);
+ if (ret < 0)
+ return ret;
+
+ if (hsa > 0xFFF) {
+ dev_err(dev, "Invalid HSA time %lx\n", hsa);
+ return count;
+ }
+
+ dev_info(dev, "HSA time 0x%lx\n", hsa);
+ csi_dev->hw.hsa = hsa;
+
+ return count;
+}
+
+static ssize_t hsa_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%x\n", csi_dev->hw.hsa);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t hbp_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long hbp;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 16, &hbp);
+ if (ret < 0)
+ return ret;
+
+ if (hbp > 0xFFF) {
+ dev_err(dev, "Invalid HBP time %lx\n", hbp);
+ return count;
+ }
+
+ dev_info(dev, "HBP time 0x%lx\n", hbp);
+ csi_dev->hw.hbp = hbp;
+
+ return count;
+}
+
+static ssize_t hbp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%x\n", csi_dev->hw.hbp);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t hsd_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long hsd;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 16, &hsd);
+ if (ret < 0)
+ return ret;
+
+ if (hsd > 0xFF) {
+ dev_err(dev, "Invalid HSD time %lx\n", hsd);
+ return count;
+ }
+
+ dev_info(dev, "HSD time 0x%lx\n", hsd);
+ csi_dev->hw.hsd = hsd;
+
+ return count;
+}
+
+static ssize_t hsd_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%x\n", csi_dev->hw.hsd);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t vsa_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long vsa;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 16, &vsa);
+ if (ret < 0)
+ return ret;
+
+ if (vsa > 0x3FF) {
+ dev_err(dev, "Invalid VSA period %lx\n", vsa);
+ return count;
+ }
+
+ dev_info(dev, "VSA period 0x%lx\n", vsa);
+ csi_dev->hw.vsa = vsa;
+
+ return count;
+}
+
+static ssize_t vsa_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%x\n", csi_dev->hw.vsa);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t vbp_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long vbp;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 16, &vbp);
+ if (ret < 0)
+ return ret;
+
+ if (vbp > 0x2FF) {
+ dev_err(dev, "Invalid VBP period %lx\n", vbp);
+ return count;
+ }
+
+ dev_info(dev, "VBP period 0x%lx\n", vbp);
+ csi_dev->hw.vbp = vbp;
+
+ return count;
+}
+
+static ssize_t vbp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%x\n", csi_dev->hw.vbp);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t vfp_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long vfp;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 16, &vfp);
+ if (ret < 0)
+ return ret;
+
+ if (vfp > 0x3ff) {
+ dev_err(dev, "Invalid VFP period %lx\n", vfp);
+ return count;
+ }
+
+ dev_info(dev, "VFP period 0x%lx\n", vfp);
+ csi_dev->hw.vfp = vfp;
+
+ return count;
+}
+
+static ssize_t vfp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%x\n", csi_dev->hw.vfp);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t virtual_channel_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long virtual_ch;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 10, &virtual_ch);
+ if (ret < 0)
+ return ret;
+
+ if ((signed int)virtual_ch < 0 || (signed int)virtual_ch > 8) {
+ dev_err(dev, "Invalid Virtual Channel %lu\n", virtual_ch);
+ return count;
+ }
+
+ dev_info(dev, "Virtual Channel %lu\n", virtual_ch);
+ csi_dev->hw.virtual_ch = virtual_ch;
+
+ return count;
+}
+
+static ssize_t virtual_channel_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%d\n", csi_dev->hw.virtual_ch);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t ipi_color_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long ipi_color_mode;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 10, &ipi_color_mode);
+ if (ret < 0)
+ return ret;
+
+ if ((signed int)ipi_color_mode < 0 || (signed int)ipi_color_mode > 1) {
+ dev_err(dev,
+ "Wrong Color Mode %lu, (48 bits -> 0 or 16 bits -> 1\n",
+ ipi_color_mode);
+ return count;
+ }
+
+ dev_info(dev, "IPI Color mode %lu\n", ipi_color_mode);
+ csi_dev->hw.ipi_color_mode = ipi_color_mode;
+
+ return count;
+}
+
+static ssize_t ipi_color_mode_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_color_mode);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t ipi_auto_flush_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long ipi_auto_flush;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 10, &ipi_auto_flush);
+ if (ret < 0)
+ return ret;
+
+ if ((signed int)ipi_auto_flush < 0 || (signed int)ipi_auto_flush > 1) {
+ dev_err(dev,
+ "Invalid Auto Flush Mode %lu, (No -> 0 or Yes -> 1\n",
+ ipi_auto_flush);
+ return count;
+ }
+
+ dev_info(dev, "IPI Auto Flush %lu\n", ipi_auto_flush);
+ csi_dev->hw.ipi_auto_flush = ipi_auto_flush;
+
+ return count;
+}
+
+static ssize_t ipi_auto_flush_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_auto_flush);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t ipi_timings_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long ipi_mode;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 10, &ipi_mode);
+ if (ret < 0)
+ return ret;
+
+ if ((signed int)ipi_mode < 0 || (signed int)ipi_mode > 1) {
+ dev_err(dev,
+ "Invalid Timing Source %lu (Camera:0|Controller:1)\n",
+ ipi_mode);
+ return count;
+ }
+
+ dev_info(dev, "IPI Color mode %lu\n", ipi_mode);
+ csi_dev->hw.ipi_mode = ipi_mode;
+
+ return count;
+}
+
+static ssize_t ipi_timings_mode_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_mode);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static ssize_t output_type_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned long output;
+
+ struct platform_device *pdev = to_platform_device(dev);
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ ret = kstrtoul(buf, 10, &output);
+ if (ret < 0)
+ return ret;
+
+ if ((signed int)output < 0 || (signed int)output > 1) {
+ dev_err(dev,
+ "Invalid Core output %lu to be used \
+ (IPI-> 0 or IDI->1 or BOTH- 2\n",
+ output);
+ return count;
+ }
+
+ dev_info(dev, "IPI Color mode %lu\n", output);
+ csi_dev->hw.output = output;
+
+ return count;
+}
+
+static ssize_t output_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
+
+ char buffer[10];
+
+ snprintf(buffer, 10, "%d\n", csi_dev->hw.output);
+
+ return strlcpy(buf, buffer, PAGE_SIZE);
+}
+
+static DEVICE_ATTR_RO(core_version);
+static DEVICE_ATTR_RO(core_reset);
+static DEVICE_ATTR_RW(n_lanes);
+static DEVICE_ATTR_RW(data_type);
+static DEVICE_ATTR_RW(hsa);
+static DEVICE_ATTR_RW(hbp);
+static DEVICE_ATTR_RW(hsd);
+static DEVICE_ATTR_RW(vsa);
+static DEVICE_ATTR_RW(vbp);
+static DEVICE_ATTR_RW(vfp);
+static DEVICE_ATTR_RW(virtual_channel);
+static DEVICE_ATTR_RW(ipi_color_mode);
+static DEVICE_ATTR_RW(ipi_auto_flush);
+static DEVICE_ATTR_RW(ipi_timings_mode);
+static DEVICE_ATTR_RW(output_type);
+
+int dw_csi_create_capabilities_sysfs(struct platform_device *pdev)
+{
+ device_create_file(&pdev->dev, &dev_attr_core_version);
+ device_create_file(&pdev->dev, &dev_attr_core_reset);
+ device_create_file(&pdev->dev, &dev_attr_n_lanes);
+ device_create_file(&pdev->dev, &dev_attr_data_type);
+ device_create_file(&pdev->dev, &dev_attr_hsa);
+ device_create_file(&pdev->dev, &dev_attr_hbp);
+ device_create_file(&pdev->dev, &dev_attr_hsd);
+ device_create_file(&pdev->dev, &dev_attr_vsa);
+ device_create_file(&pdev->dev, &dev_attr_vbp);
+ device_create_file(&pdev->dev, &dev_attr_vfp);
+ device_create_file(&pdev->dev, &dev_attr_virtual_channel);
+ device_create_file(&pdev->dev, &dev_attr_ipi_color_mode);
+ device_create_file(&pdev->dev, &dev_attr_ipi_auto_flush);
+ device_create_file(&pdev->dev, &dev_attr_ipi_timings_mode);
+ device_create_file(&pdev->dev, &dev_attr_output_type);
+
+ return 0;
+}
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..d01418d
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-mipi-csi.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI CSI-2 Host controller driver
+ * Core MIPI CSI-2 functions
+ *
+ * Author: 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),
+};
+
+#define dw_print(VAR) \
+ dev_info(csi_dev->dev, "%s: 0x%x: %X\n", "#VAR#",\
+ VAR, dw_mipi_csi_read(csi_dev, VAR))
+
+void dw_mipi_csi_write_part(struct dw_csi *dev, u32 address, u32 data,
+ u8 shift, u8 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 dw_csi *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 dw_csi *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_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 dw_csi *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,
+ GENMASK(31, 0));
+
+ /* common */
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY_FATAL,
+ GENMASK(8, 0));
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT_FATAL,
+ GENMASK(1, 0));
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY, GENMASK(23, 0));
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_LINE, GENMASK(23, 0));
+ dw_mipi_csi_write(csi_dev, reg.MASK_INT_IPI, GENMASK(5, 0));
+
+ /* only for version 1.40 */
+ if (csi_dev->hw_version_minor == 40) {
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_BNDRY_FRAME_FATAL,
+ GENMASK(31, 0));
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_SEQ_FRAME_FATAL,
+ GENMASK(31, 0));
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_CRC_FRAME_FATAL,
+ GENMASK(31, 0));
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_PLD_CRC_FATAL,
+ GENMASK(31, 0));
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_DATA_ID, GENMASK(31, 0));
+ dw_mipi_csi_write(csi_dev,
+ reg.MSK_ECC_CORRECT, GENMASK(31, 0));
+ }
+ }
+ return 0;
+}
+
+void dw_mipi_csi_set_ipi_fmt(struct dw_csi *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);
+ switch (csi_dev->ipi_dt) {
+ case CSI_2_YUV420_8:
+ case CSI_2_YUV420_8_LEG:
+ case CSI_2_YUV420_8_SHIFT:
+ break;
+ case CSI_2_YUV420_10:
+ case CSI_2_YUV420_10_SHIFT:
+ break;
+ }
+ } else {
+ switch (csi_dev->fmt->mbus_code) {
+ /* RGB 666 */
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ csi_dev->ipi_dt = CSI_2_RGB666;
+ break;
+ /* RGB 565 */
+ case MEDIA_BUS_FMT_RGB565_2X8_BE:
+ case MEDIA_BUS_FMT_RGB565_2X8_LE:
+ csi_dev->ipi_dt = CSI_2_RGB565;
+ break;
+ /* RGB 555 */
+ case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
+ case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
+ csi_dev->ipi_dt = CSI_2_RGB555;
+ break;
+ /* RGB 444 */
+ case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
+ case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
+ csi_dev->ipi_dt = CSI_2_RGB444;
+ break;
+ /* RGB 888 */
+ break;
+ case MEDIA_BUS_FMT_RGB888_2X12_LE:
+ case MEDIA_BUS_FMT_RGB888_2X12_BE:
+ csi_dev->ipi_dt = CSI_2_RGB888;
+ break;
+ /* RAW 10 */
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
+ csi_dev->ipi_dt = CSI_2_RAW10;
+ break;
+ /* RAW 12 */
+ case MEDIA_BUS_FMT_SBGGR12_1X12:
+ csi_dev->ipi_dt = CSI_2_RAW12;
+ break;
+ /* RAW 14 */
+ case MEDIA_BUS_FMT_SBGGR14_1X14:
+ csi_dev->ipi_dt = CSI_2_RAW14;
+ break;
+ /* RAW 16 */
+ case MEDIA_BUS_FMT_SBGGR16_1X16:
+ csi_dev->ipi_dt = CSI_2_RAW16;
+ break;
+ /* RAW 8 */
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ csi_dev->ipi_dt = CSI_2_RAW8;
+ break;
+ /* YUV 422 8-bit */
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ csi_dev->ipi_dt = CSI_2_RAW8;
+ break;
+ /* YUV 422 10-bit */
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ csi_dev->ipi_dt = CSI_2_YUV422_8;
+ break;
+ /* YUV 420 8-bit LEGACY */
+ case MEDIA_BUS_FMT_Y8_1X8:
+ csi_dev->ipi_dt = CSI_2_RAW8;
+ break;
+ /* YUV 420 10-bit */
+ case MEDIA_BUS_FMT_Y10_1X10:
+ csi_dev->ipi_dt = CSI_2_RAW8;
+ break;
+ default:
+ break;
+ }
+ dw_mipi_csi_write(csi_dev, reg.IPI_DATA_TYPE, csi_dev->ipi_dt);
+ }
+ dev_info(dev, "Selected IPI Data Type 0x%X\n", csi_dev->ipi_dt);
+}
+
+void dw_mipi_csi_fill_timings(struct dw_csi *dev,
+ struct v4l2_subdev_format *fmt)
+{
+ /* expected values */
+ dev->hw.virtual_ch = 0;
+ dev->hw.ipi_color_mode = COLOR48;
+ dev->hw.ipi_auto_flush = 1;
+ dev->hw.ipi_mode = CAMERA_TIMING;
+ dev->hw.ipi_cut_through = CTINACTIVE;
+ dev->hw.ipi_adv_features = LINE_EVENT_SELECTION(EVSELAUTO);
+ dev->hw.htotal = fmt->format.width + dev->hw.hsa +
+ dev->hw.hbp + dev->hw.hsd;
+ dev->hw.vactive = fmt->format.height;
+ dev->hw.output = 2;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+ dev_dbg(dev->dev, "*********** timings *********\n");
+ dev_dbg(dev->dev, "Horizontal Sync Active: %d\n", dev->hw.hsa);
+ dev_dbg(dev->dev, "Horizontal Back Porch: %d\n", dev->hw.hbp);
+ dev_dbg(dev->dev, "Horizontal Width: %d\n", fmt->format.width);
+ dev_dbg(dev->dev, "Horizontal Total: %d\n", dev->hw.htotal);
+ dev_dbg(dev->dev, "Vertical Sync Active: %d\n", dev->hw.vsa);
+ dev_dbg(dev->dev, "Vertical Back Porch: %d\n", dev->hw.vbp);
+ dev_dbg(dev->dev, "Vertical Front Porch: %d\n", dev->hw.vfp);
+ dev_dbg(dev->dev, "Vertical Active: %d\n", dev->hw.vactive);
+ }
+}
+
+void dw_mipi_csi_start(struct dw_csi *csi_dev)
+{
+ struct device *dev = csi_dev->dev;
+
+ dw_mipi_csi_write(csi_dev, reg.N_LANES, (csi_dev->hw.num_lanes - 1));
+ dev_info(dev, "number of 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,
+ csi_dev->hw.ipi_adv_features);
+ 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);
+ if (csi_dev->hw.ipi_mode == CAMERA_TIMING) {
+ dw_mipi_csi_write(csi_dev,
+ reg.IPI_ADV_FEATURES,
+ LINE_EVENT_SELECTION(EVSELPROG) |
+ EN_VIDEO |
+ EN_LINE_START |
+ EN_NULL |
+ EN_BLANKING |
+ EN_EMBEDDED);
+ }
+ 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_MODE,
+ csi_dev->hw.ipi_cut_through,
+ 16, 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);
+
+ dev_vdbg(dev, "*********** config *********\n");
+ dev_vdbg(dev, "IPI enable: %s\n",
+ csi_dev->hw.output ? "YES" : "NO");
+ dev_vdbg(dev, "video mode transmission type: %s timming\n",
+ csi_dev->hw.ipi_mode ? "controller" : "camera");
+ dev_vdbg(dev, "Color Mode: %s\n",
+ csi_dev->hw.ipi_color_mode ? "16 bits" : "48 bits");
+ dev_vdbg(dev, "Cut Through Mode: %s\n",
+ csi_dev->hw.ipi_cut_through ? "enable" : "disable");
+ dev_vdbg(dev, "Virtual Channel: %d\n",
+ csi_dev->hw.virtual_ch);
+ dev_vdbg(dev, "Auto-flush: %d\n",
+ csi_dev->hw.ipi_auto_flush);
+ dw_mipi_csi_write(csi_dev, reg.IPI_SOFTRSTN, 1);
+
+ if (csi_dev->hw.ipi_mode == AUTO_TIMING)
+ phy_power_on(csi_dev->phy);
+
+ 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);
+ 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);
+ }
+ phy_power_on(csi_dev->phy);
+}
+
+int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev)
+{
+ struct device *dev = csi_dev->dev;
+ u32 global_int_status, i_sts;
+ unsigned long flags;
+
+ spin_lock_irqsave(&csi_dev->slock, flags);
+ global_int_status = dw_mipi_csi_read(csi_dev, reg.INTERRUPT);
+
+ if (global_int_status & csi_int.PHY_FATAL) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PHY_FATAL);
+ dev_err_ratelimited(dev, "int %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, "int %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, "int %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, "int %08X: PHY: %08X\n",
+ reg.INT_PHY, i_sts);
+ }
+
+ if (global_int_status & csi_int.PKT &&
+ csi_dev->hw_version_major == 1 &&
+ csi_dev->hw_version_minor <= 30) {
+ i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PKT);
+ dev_err_ratelimited(dev, "int %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, "int %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, "int %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,
+ "int %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,
+ "int %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,
+ "int %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,
+ "int %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, "int %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, "int %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 dw_csi *csi_dev)
+{
+ u32 hw_version;
+
+ hw_version = dw_mipi_csi_read(csi_dev, reg.VERSION);
+ csi_dev->hw_version_major = (u8)((hw_version >> 24) - '0');
+ csi_dev->hw_version_minor = (u8)((hw_version >> 16) - '0');
+ csi_dev->hw_version_minor = csi_dev->hw_version_minor * 10;
+ csi_dev->hw_version_minor += (u8)((hw_version >> 8) - '0');
+}
+
+int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev)
+{
+ struct device *dev = csi_dev->dev;
+
+ if (csi_dev->hw_version_major == 1) {
+ if (csi_dev->hw_version_minor == 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) {
+ /*
+ * 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;
+}
+
+void dw_mipi_csi_dump(struct dw_csi *csi_dev)
+{
+ dw_print(reg.VERSION);
+ dw_print(reg.N_LANES);
+ dw_print(reg.CTRL_RESETN);
+ dw_print(reg.INTERRUPT);
+ dw_print(reg.DATA_IDS_1);
+ dw_print(reg.DATA_IDS_2);
+ dw_print(reg.IPI_MODE);
+ dw_print(reg.IPI_VCID);
+ dw_print(reg.IPI_DATA_TYPE);
+ dw_print(reg.IPI_MEM_FLUSH);
+ dw_print(reg.IPI_HSA_TIME);
+ dw_print(reg.IPI_HBP_TIME);
+ dw_print(reg.IPI_HSD_TIME);
+ dw_print(reg.IPI_HLINE_TIME);
+ dw_print(reg.IPI_SOFTRSTN);
+ dw_print(reg.IPI_ADV_FEATURES);
+ dw_print(reg.IPI_VSA_LINES);
+ dw_print(reg.IPI_VBP_LINES);
+ dw_print(reg.IPI_VFP_LINES);
+ dw_print(reg.IPI_VACTIVE_LINES);
+ dw_print(reg.IPI_DATA_TYPE);
+ dw_print(reg.VERSION);
+ dw_print(reg.IPI_ADV_FEATURES);
+}
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..6df3688
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-mipi-csi.h
@@ -0,0 +1,287 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI CSI-2 Host controller driver
+ *
+ * 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/module.h>
+#include <linux/io.h>
+#include <linux/phy/phy.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/ratelimit.h>
+#include <linux/reset.h>
+#include <linux/videodev2.h>
+#include <linux/wait.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/dwc/dw-mipi-csi-pltfrm.h>
+
+/* Advanced features */
+#define IPI_DT_OVERWRITE BIT(0)
+#define DATA_TYPE_OVERWRITE(dt) (((dt) & GENMASK(5, 0)) << 8)
+#define LINE_EVENT_SELECTION(n) ((n) << 16)
+
+enum line_event {
+ EVSELAUTO = 0,
+ EVSELPROG = 1,
+};
+
+#define EN_VIDEO BIT(17)
+#define EN_LINE_START BIT(18)
+#define EN_NULL BIT(19)
+#define EN_BLANKING BIT(20)
+#define EN_EMBEDDED BIT(21)
+#define IPI_SYNC_EVENT_MODE(n) ((n) << 24)
+
+enum sync_event {
+ SYNCEVFSN = 0,
+ SYNCEVFS = 1,
+};
+
+/* 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,
+ CSI_2_RAW16 = 0x2E,
+ CSI_2_RAW20 = 0x2F,
+ USER_DEFINED_1 = 0x30,
+ USER_DEFINED_2 = 0x31,
+ USER_DEFINED_3 = 0x32,
+ USER_DEFINED_4 = 0x33,
+ USER_DEFINED_5 = 0x34,
+ USER_DEFINED_6 = 0x35,
+ USER_DEFINED_7 = 0x36,
+ USER_DEFINED_8 = 0x37,
+};
+
+/* DWC MIPI CSI-2 output types */
+enum output {
+ IPI_OUT = 0,
+ IDI_OUT = 1,
+ BOTH_OUT = 2
+};
+
+/* IPI color components */
+enum color_mode {
+ COLOR48 = 0,
+ COLOR16 = 1
+};
+
+/* IPI cut through */
+enum cut_through {
+ CTINACTIVE = 0,
+ CTACTIVE = 1
+};
+
+/* IPI output types */
+enum ipi_output {
+ CAMERA_TIMING = 0,
+ AUTO_TIMING = 1
+};
+
+/* Format template */
+struct mipi_fmt {
+ u32 mbus_code;
+ u8 depth;
+};
+
+struct mipi_dt {
+ u32 hex;
+ char *name;
+};
+
+/* CSI specific configuration */
+struct csi_data {
+ u32 num_lanes;
+ u32 dphy_freq;
+ u32 pclk;
+ u32 fps;
+ u32 bpp;
+ u32 output;
+ u32 ipi_mode;
+ u32 ipi_adv_features;
+ u32 ipi_cut_through;
+ u32 ipi_color_mode;
+ u32 ipi_auto_flush;
+ u32 virtual_ch;
+ u32 hsa;
+ u32 hbp;
+ u32 hsd;
+ u32 htotal;
+ u32 vsa;
+ u32 vbp;
+ u32 vfp;
+ u32 vactive;
+};
+
+/* Structure to embed device driver information */
+struct dw_csi {
+ struct v4l2_subdev sd;
+ struct video_device vdev;
+ struct v4l2_device v4l2_dev;
+ struct device *dev;
+ struct media_pad pads[CSI_PADS_NUM];
+ struct mipi_fmt *fmt;
+ struct v4l2_mbus_framefmt format;
+ void __iomem *base_address;
+ void __iomem *demo;
+ void __iomem *csc;
+ int ctrl_irq_number;
+ int demosaic_irq;
+ struct csi_data hw;
+ struct reset_control *rst;
+ struct phy *phy;
+ struct dw_csih_pdata *config;
+ struct mutex lock; /* protect resources sharing */
+ spinlock_t slock; /* interrupt handling lock */
+ u8 ipi_dt;
+ u8 index;
+ u8 hw_version_major;
+ u16 hw_version_minor;
+};
+
+static inline struct dw_csi *sd_to_mipi_csi_dev(struct v4l2_subdev *sdev)
+{
+ return container_of(sdev, struct dw_csi, sd);
+}
+
+void dw_mipi_csi_reset(struct dw_csi *csi_dev);
+int dw_mipi_csi_mask_irq_power_off(struct dw_csi *csi_dev);
+int dw_mipi_csi_hw_stdby(struct dw_csi *csi_dev);
+void dw_mipi_csi_set_ipi_fmt(struct dw_csi *csi_dev);
+void dw_mipi_csi_start(struct dw_csi *csi_dev);
+int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev);
+void dw_mipi_csi_get_version(struct dw_csi *csi_dev);
+int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev);
+void dw_mipi_csi_fill_timings(struct dw_csi *dev,
+ struct v4l2_subdev_format *fmt);
+void dw_mipi_csi_dump(struct dw_csi *csi_dev);
+
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+int dw_csi_create_capabilities_sysfs(struct platform_device *pdev);
+#endif
+
+static inline void dw_mipi_csi_write(struct dw_csi *dev,
+ u32 address, u32 data)
+{
+ writel(data, dev->base_address + address);
+}
+
+static inline u32 dw_mipi_csi_read(struct dw_csi *dev, u32 address)
+{
+ return readl(dev->base_address + address);
+}
+
+#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..948db4e
--- /dev/null
+++ b/include/media/dwc/dw-mipi-csi-pltfrm.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI CSI-2 Host media entities
+ *
+ * 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
+
+/* 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)
+
+struct pdata_names {
+ char *name;
+};
+
+enum video_dev_pads {
+ VIDEO_DEV_SD_PAD_SINK_VIF1,
+ VIDEO_DEV_SD_PAD_SINK_VIF2,
+ VIDEO_DEV_SD_PAD_SOURCE_DMA,
+ VIDEO_DEV_SD_PADS_NUM,
+};
+
+enum vif_pads {
+ VIF_PAD_SINK_CSI,
+ VIF_PAD_SOURCE_DMA,
+ VIF_PADS_NUM,
+};
+
+enum mipi_csi_pads {
+ CSI_PAD_SINK,
+ CSI_PAD_SOURCE,
+ CSI_PADS_NUM,
+};
+
+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.7.4
Add device-tree bindings documentation for SNPS DesignWare MIPI D-PHY in
RX mode.
Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v3-v4
- @Laurent I know I told you I could remove the snps,dphy-frequency on V3 but
it is really useful for me here. I removed all other the proprietary
properties except this one. Do you still think it must be removed?
- Frequency units @Rob
.../devicetree/bindings/phy/snps,dw-dphy-rx.txt | 29 ++++++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
diff --git a/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt b/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
new file mode 100644
index 0000000..50603e6
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
@@ -0,0 +1,29 @@
+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,dw-dphy-rx".
+- #phy-cells : Must be 1.
+- bus-width : Size of the test interface data bus (8 bits->8 or
+ 12bits->12).
+- snps,dphy-frequency : Frequency at which D-PHY should start, configurable.
+ Check Synopsys databook. (-kHz)
+- reg : Test interface register. This correspondes to the
+ physical base address of the controller and size of
+ the device memory mapped registers; Check Synopsys
+ databook.
+
+Example:
+
+ mipi_dphy_rx1: dphy@d00003040 {
+ compatible = "snps,dw-dphy-rx";
+ #phy-cells = <1>;
+ bus-width = <12>;
+ snps,dphy-frequency = <300000>;
+ reg = <0xd0003040 0x20>;
+ };
--
2.7.4
This patch allows the configuration via platform data. This allows
the driver loading not devicetree dependent.
Signed-off-by: Luis Oliveira <[email protected]>
---
Changelog
v3-v4
- This patch was not in the v3. This enables pdata to configure the driver
drivers/media/platform/dwc/dw-dphy-plat.c | 87 ++++++++++++++++++++++---------
drivers/media/platform/dwc/dw-dphy-rx.c | 19 ++++---
2 files changed, 72 insertions(+), 34 deletions(-)
diff --git a/drivers/media/platform/dwc/dw-dphy-plat.c b/drivers/media/platform/dwc/dw-dphy-plat.c
index 1f5113d..34d9caf 100644
--- a/drivers/media/platform/dwc/dw-dphy-plat.c
+++ b/drivers/media/platform/dwc/dw-dphy-plat.c
@@ -23,38 +23,50 @@ static struct phy_ops dw_dphy_ops = {
static struct phy_provider *phy_provider;
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
static u8 get_config_8l(struct device *dev, struct dw_dphy_rx *dphy)
{
- dphy->config_8l = of_get_gpio(dev->of_node, 0);
- if (!gpio_is_valid(dphy->config_8l)) {
- dev_warn(dev,
- "failed to parse 8l config, default is 0\n");
- dphy->config_8l = 0;
- }
+ if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
+ dphy->config_8l = of_get_gpio(dev->of_node, 0);
+ if (!gpio_is_valid(dphy->config_8l)) {
+ dev_warn(dev,
+ "failed to parse 8l config, default is 0\n");
+ dphy->config_8l = 0;
+ }
+ } else {
+ struct dw_phy_pdata *pdata = dev->platform_data;
+ dphy->config_8l = pdata->config_8l;
+ }
return dphy->config_8l;
}
-
+#endif
static int get_resources(struct device *dev, struct dw_dphy_rx *dphy)
{
int ret = 0;
- if (of_property_read_u32(dev->of_node, "snps,dphy-frequency",
- &dphy->dphy_freq)) {
- dev_err(dev, "failed to find dphy frequency\n");
- ret = -EINVAL;
- }
-
- if (of_property_read_u32(dev->of_node, "bus-width",
- &dphy->dphy_te_len)) {
- dev_err(dev, "failed to find dphy te length\n");
- ret = -EINVAL;
- }
+ if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
+ if (of_property_read_u32(dev->of_node, "snps,dphy-frequency",
+ &dphy->dphy_freq)) {
+ dev_err(dev, "failed to find dphy frequency\n");
+ ret = -EINVAL;
+ }
+ if (of_property_read_u32(dev->of_node, "bus-width",
+ &dphy->dphy_te_len)) {
+ dev_err(dev, "failed to find dphy te length\n");
+ ret = -EINVAL;
+ }
+ if (of_property_read_u32(dev->of_node, "snps,phy_type",
+ &dphy->phy_type)) {
+ dev_err(dev, "failed to find dphy type\n");
+ ret = -EINVAL;
+ }
+ } else {
+ struct dw_phy_pdata *pdata = dev->platform_data;
- if (of_property_read_u32(dev->of_node, "snps,phy_type",
- &dphy->phy_type)) {
- dev_err(dev, "failed to find dphy te length\n");
- ret = -EINVAL;
+ dphy->dphy_freq = pdata->dphy_frequency;
+ dphy->dphy_te_len = pdata->dphy_te_len;
+ dphy->dphy_gen = pdata->dphy_gen;
}
dev_set_drvdata(dev, dphy);
@@ -63,20 +75,39 @@ static int get_resources(struct device *dev, struct dw_dphy_rx *dphy)
static int phy_register(struct device *dev)
{
- if (dev->of_node) {
+ int ret = 0;
+
+ if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
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);
+ ret = PTR_ERR(phy_provider);
}
+ } else {
+ struct dw_phy_pdata *pdata = dev->platform_data;
+ struct dw_dphy_rx *dphy = dev_get_drvdata(dev);
+
+ ret = phy_create_lookup(dphy->phy,
+ phys[pdata->id].name,
+ csis[pdata->id].name);
+ if (ret)
+ dev_err(dev, "Failed to create dphy lookup\n");
+ else
+ dev_warn(dev,
+ "Created dphy lookup [%s] --> [%s]\n",
+ phys[pdata->id].name, csis[pdata->id].name);
}
- return 0;
+ return ret;
}
static void phy_unregister(struct device *dev)
{
- devm_of_phy_provider_unregister(dev, phy_provider);
+ if (!dev->of_node) {
+ struct dw_dphy_rx *dphy = dev_get_drvdata(dev);
+
+ phy_remove_lookup(dphy->phy, "dw-dphy", "dw-csi");
+ }
}
static int dw_dphy_rx_probe(struct platform_device *pdev)
@@ -166,18 +197,22 @@ static int dw_dphy_rx_remove(struct platform_device *pdev)
return 0;
}
+#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id dw_dphy_rx_of_match[] = {
{ .compatible = "snps,dw-dphy-rx" },
{},
};
MODULE_DEVICE_TABLE(of, dw_dphy_rx_of_match);
+#endif
static struct platform_driver dw_dphy_rx_driver = {
.probe = dw_dphy_rx_probe,
.remove = dw_dphy_rx_remove,
.driver = {
+#if IS_ENABLED(CONFIG_OF)
.of_match_table = of_match_ptr(dw_dphy_rx_of_match),
+#endif
.name = "dw-dphy",
.owner = THIS_MODULE,
}
diff --git a/drivers/media/platform/dwc/dw-dphy-rx.c b/drivers/media/platform/dwc/dw-dphy-rx.c
index f57a814..a69cb3e 100644
--- a/drivers/media/platform/dwc/dw-dphy-rx.c
+++ b/drivers/media/platform/dwc/dw-dphy-rx.c
@@ -71,15 +71,18 @@ u8 dw_dphy_setup_config(struct dw_dphy_rx *dphy)
dev_vdbg(&dphy->phy->dev, "CONFIG 4L\n");
return CTRL_4_LANES;
}
-
- ret = gpio_request(dphy->config_8l, "config");
- if (ret < 0) {
- dev_vdbg(&dphy->phy->dev,
- "could not acquire config gpio (err=%d)\n", ret);
- return ret;
+ if (IS_ENABLED(CONFIG_OF)) {
+ ret = gpio_request(dphy->config_8l, "config");
+ if (ret < 0) {
+ dev_vdbg(&dphy->phy->dev,
+ "could not acquire config (err=%d)\n", ret);
+ return ret;
+ }
+ ret = gpio_get_value(dphy->config_8l);
+ gpio_free(dphy->config_8l);
+ } else {
+ ret = dphy->config_8l;
}
- ret = gpio_get_value(dphy->config_8l);
- gpio_free(dphy->config_8l);
dev_vdbg(&dphy->phy->dev,
"Booting in [%s] mode\n",
--
2.7.4
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
v3-v4
- This patch suffered from some changes, but still the D-PHY is configured
on boot-up either from dt or by pdata (check header dw-dphy-data.h)
MAINTAINERS | 2 +
drivers/media/platform/dwc/Kconfig | 24 +-
drivers/media/platform/dwc/Makefile | 6 +
drivers/media/platform/dwc/dw-dphy-plat.c | 189 +++++++++
drivers/media/platform/dwc/dw-dphy-rx.c | 608 +++++++++++++++++++++++++++++
drivers/media/platform/dwc/dw-dphy-rx.h | 212 ++++++++++
drivers/media/platform/dwc/dw-dphy-sysfs.c | 232 +++++++++++
include/media/dwc/dw-dphy-data.h | 32 ++
8 files changed, 1304 insertions(+), 1 deletion(-)
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-dphy-sysfs.c
create mode 100644 include/media/dwc/dw-dphy-data.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 2df1f7c..9dec1a1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15193,8 +15193,10 @@ L: [email protected]
T: git git://linuxtv.org/media_tree.git
S: Maintained
F: drivers/media/platform/dwc
+F: include/media/dwc/dw-dphy-data.h
F: include/media/dwc/dw-csi-data.h
F: Documentation/devicetree/bindings/media/snps,dw-csi.txt
+F: Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
SYNOPSYS DESIGNWARE DMAC DRIVER
M: Viresh Kumar <[email protected]>
diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
index 508ac21..b42219d 100644
--- a/drivers/media/platform/dwc/Kconfig
+++ b/drivers/media/platform/dwc/Kconfig
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
#
# Synopsys DWC Platform drivers
-# Drivers here are currently for MIPI CSI-2 support
+# Drivers here are currently for MIPI CSI-2 and MIPI DPHY support
config DWC_MIPI_CSI2_HOST
tristate "Synopsys DesignWare CSI-2 Host Controller support"
@@ -17,3 +17,25 @@ config DWC_MIPI_CSI2_HOST
If you have a controller with this interface, say Y.
If unsure, say N.
+
+config DWC_MIPI_DPHY_GEN3
+ tristate "DesignWare platform support using a Gen3 D-PHY"
+ select GENERIC_PHY
+ help
+ Synopsys MIPI D-PHY Generation 3 reference driver. This driver supports
+ all Generation 3 D-PHYs. Choose Y or M if you have a platform with this
+ block.
+
+ If unsure, say N.
+
+if DWC_MIPI_DPHY_GEN3
+
+config DWC_MIPI_TC_DPHY_GEN3
+ bool "Platform support using a Synopsys Test Chip"
+ help
+ Synopsys Test Chip is for prototyping purposes. This enables extra
+ features that exist only in prototyping and/or for debug purposes.
+
+ If unsure, say N.
+
+endif # DWC_MIPI_DPHY_GEN3
diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
index 057f137..9af4dfb 100644
--- a/drivers/media/platform/dwc/Makefile
+++ b/drivers/media/platform/dwc/Makefile
@@ -7,3 +7,9 @@ ifeq ($(CONFIG_DWC_MIPI_TC_DPHY_GEN3),y)
dw-csi-objs += dw-csi-sysfs.o
endif
obj-$(CONFIG_DWC_MIPI_CSI2_HOST) += dw-csi.o
+
+dw-dphy-objs := dw-dphy-plat.o dw-dphy-rx.o
+ifeq ($(CONFIG_DWC_MIPI_TC_DPHY_GEN3),y)
+ dw-dphy-objs += dw-dphy-sysfs.o
+endif
+obj-$(CONFIG_DWC_MIPI_DPHY_GEN3) += dw-dphy.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..1f5113d
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-dphy-plat.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI D-PHY controller driver.
+ * Platform driver
+ *
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#include <media/dwc/dw-dphy-data.h>
+#include <media/dwc/dw-csi-data.h>
+
+#include "dw-dphy-rx.h"
+
+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 struct phy_provider *phy_provider;
+
+static u8 get_config_8l(struct device *dev, struct dw_dphy_rx *dphy)
+{
+ dphy->config_8l = of_get_gpio(dev->of_node, 0);
+ if (!gpio_is_valid(dphy->config_8l)) {
+ dev_warn(dev,
+ "failed to parse 8l config, default is 0\n");
+ dphy->config_8l = 0;
+ }
+
+ return dphy->config_8l;
+}
+
+static int get_resources(struct device *dev, struct dw_dphy_rx *dphy)
+{
+ int ret = 0;
+
+ if (of_property_read_u32(dev->of_node, "snps,dphy-frequency",
+ &dphy->dphy_freq)) {
+ dev_err(dev, "failed to find dphy frequency\n");
+ ret = -EINVAL;
+ }
+
+ if (of_property_read_u32(dev->of_node, "bus-width",
+ &dphy->dphy_te_len)) {
+ dev_err(dev, "failed to find dphy te length\n");
+ ret = -EINVAL;
+ }
+
+ if (of_property_read_u32(dev->of_node, "snps,phy_type",
+ &dphy->phy_type)) {
+ dev_err(dev, "failed to find dphy te length\n");
+ ret = -EINVAL;
+ }
+ dev_set_drvdata(dev, dphy);
+
+ return ret;
+}
+
+static int phy_register(struct device *dev)
+{
+ if (dev->of_node) {
+ 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);
+ }
+ }
+ return 0;
+}
+
+static void phy_unregister(struct device *dev)
+{
+ devm_of_phy_provider_unregister(dev, phy_provider);
+}
+
+static int dw_dphy_rx_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dw_dphy_rx *dphy;
+ struct resource *res;
+
+ dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
+ if (!dphy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dphy->base_address = devm_ioremap(&pdev->dev,
+ res->start, resource_size(res));
+ if (IS_ERR(dphy->base_address)) {
+ dev_err(&pdev->dev, "error requesting base address\n");
+ return PTR_ERR(dphy->base_address);
+ }
+
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+
+ dphy->dphy1_if_addr = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dphy->dphy1_if_addr)) {
+ dev_err(&pdev->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(&pdev->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(&pdev->dev,
+ res->start, resource_size(res));
+
+ if (IS_ERR(dphy->dphy2_if_addr)) {
+ dev_err(&pdev->dev,
+ "error requesting dphy 2 if regbank\n");
+ return PTR_ERR(dphy->dphy2_if_addr);
+ }
+ dphy->config_8l = get_config_8l(&pdev->dev, dphy);
+ }
+#endif
+ if (get_resources(dev, dphy)) {
+ dev_err(dev, "failed to parse PHY resources\n");
+ return -EINVAL;
+ }
+
+ dphy->phy = devm_phy_create(dev, NULL, &dw_dphy_ops);
+ if (IS_ERR(dphy->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(dphy->phy);
+ }
+
+ platform_set_drvdata(pdev, dphy);
+ phy_set_drvdata(dphy->phy, dphy);
+
+ if (phy_register(dev)) {
+ dev_err(dev, "failed to register PHY\n");
+ return -EINVAL;
+ }
+
+ dphy->lp_time = 1000; /* 1000 ns */
+ dphy->lanes_config = dw_dphy_setup_config(dphy);
+
+ dev_info(dev, "Probing dphy finished\n");
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ dw_dphy_create_capabilities_sysfs(pdev);
+#endif
+
+ return 0;
+}
+
+static int dw_dphy_rx_remove(struct platform_device *pdev)
+{
+ phy_unregister(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id dw_dphy_rx_of_match[] = {
+ { .compatible = "snps,dw-dphy-rx" },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, dw_dphy_rx_of_match);
+
+static struct platform_driver dw_dphy_rx_driver = {
+ .probe = dw_dphy_rx_probe,
+ .remove = dw_dphy_rx_remove,
+ .driver = {
+ .of_match_table = of_match_ptr(dw_dphy_rx_of_match),
+ .name = "dw-dphy",
+ .owner = THIS_MODULE,
+ }
+};
+module_platform_driver(dw_dphy_rx_driver);
+
+MODULE_DESCRIPTION("Synopsys DesignWare 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..f57a814
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-dphy-rx.c
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI D-PHY controller driver
+ * Core functions
+ *
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#include "dw-dphy-rx.h"
+
+struct range_dphy_gen2 {
+ u32 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 {
+ u32 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)
+{
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ int ret;
+
+ if (dphy->max_lanes == CTRL_4_LANES) {
+ dev_vdbg(&dphy->phy->dev, "CONFIG 4L\n");
+ return CTRL_4_LANES;
+ }
+
+ ret = gpio_request(dphy->config_8l, "config");
+ if (ret < 0) {
+ dev_vdbg(&dphy->phy->dev,
+ "could not acquire config gpio (err=%d)\n", ret);
+ return ret;
+ }
+ ret = gpio_get_value(dphy->config_8l);
+ gpio_free(dphy->config_8l);
+
+ dev_vdbg(&dphy->phy->dev,
+ "Booting in [%s] mode\n",
+ ret == CTRL_8_LANES ? "8L" : "4+4L");
+ return ret;
+
+#endif /* CONFIG_DWC_MIPI_TC_DPHY_GEN3 */
+ return CTRL_4_LANES;
+}
+
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+void dw_dphy_if_write(struct dw_dphy_rx *dphy, u32 address, u32 data)
+{
+ writel(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, u32 address)
+{
+ u32 if1 = 0, if2 = 0;
+
+ if1 = readl(dphy->dphy1_if_addr + address);
+
+ if (dphy->lanes_config == CTRL_4_LANES)
+ goto end;
+
+ if (dphy->lanes_config == DPHYID)
+ goto end;
+
+ if2 = readl(dphy->dphy2_if_addr + address);
+
+ if (if1 != if2)
+ dev_vdbg(&dphy->phy->dev,
+ "Values read different for each interface\n");
+end:
+ return if1;
+}
+#endif
+
+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, u32 address)
+{
+ int 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;
+end:
+ return dphy1;
+}
+
+void dw_dphy_write_msk(struct dw_dphy_rx *dev, u32 address, u32 data, u8 shift,
+ u8 width)
+{
+ u32 temp = dw_dphy_read(dev, address);
+ u32 mask = (1 << width) - 1;
+
+ 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;
+}
+
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+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);
+}
+#endif
+
+static void dw_dphy_gen3_12bit_tc_power_up(struct dw_dphy_rx *dphy)
+{
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, GLUELOGIC);
+#endif
+ 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);
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RESET);
+ dw_dphy_if_write(dphy, DPHYGLUEIFTESTER, RX_PHY);
+#endif
+}
+
+static void dw_dphy_gen3_8bit_tc_power_up(struct dw_dphy_rx *dphy)
+{
+ u32 input_freq = dphy->dphy_freq / 1000;
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ 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);
+#endif
+ 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);
+ dev_vdbg(&dphy->phy->dev, "DPHY power up.\n");
+ 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;
+
+ dev_vdbg(&dphy->phy->dev, "12bit: 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->phy_type) {
+ dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_1, 0x01);
+ dw_dphy_te_write(dphy, RX_RX_STARTUP_OVR_0, 0x80);
+ }
+
+ if (dphy->phy_type || 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;
+
+ dev_vdbg(&dphy->phy->dev, "8bit: 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);
+
+ dev_vdbg(&dphy->phy->dev, "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) {
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ dw_dphy_if_init(dphy);
+#endif
+ 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;
+}
+
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+int dw_dphy_if_set_idelay(struct dw_dphy_rx *dphy, u8 dly, u8 cells)
+{
+ u32 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) {
+ dev_vdbg(&dphy->phy->dev,
+ "odelay config failed, set %d get %d", dly, val);
+ return -EINVAL;
+ }
+
+ 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:
+ dev_err(&dphy->phy->dev, "Lane Value not recognized\n");
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+int dw_dphy_init(struct phy *phy)
+{
+ struct dw_dphy_rx *dphy = phy_get_drvdata(phy);
+
+ dev_vdbg(&dphy->phy->dev, "Init DPHY.\n");
+
+ 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);
+ dev_vdbg(&dphy->phy->dev,
+ "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..2e8d68c
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-dphy-rx.h
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI D-PHY controller driver
+ *
+ * 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/phy/phy-mipi-dphy.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,
+ GEN2,
+ GEN3,
+};
+
+enum dphy_interface_length {
+ BIT8 = 8,
+ BIT12 = 12,
+};
+
+enum tst_ctrl0 {
+ PHY_TESTCLR,
+ PHY_TESTCLK,
+};
+
+enum tst_ctrl1 {
+ PHY_TESTDIN = 0,
+ PHY_TESTDOUT = 8,
+ PHY_TESTEN = 16,
+};
+
+enum lanes_config_values {
+ CTRL_4_LANES,
+ CTRL_8_LANES,
+};
+
+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,
+};
+
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+/* Testchip 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
+
+#define DPHY_DEFAULT_FREQ 300000
+
+enum glueiftester {
+ RESET = 0x0,
+ TX_PHY = 0x1,
+ RX_PHY = 0x2,
+ GLUELOGIC = 0x4,
+};
+#endif
+
+/**
+ * struct phy specifies associated phy component
+ * struct cfg to pass mipi dphy specific configurations
+ * @lanes_config lanes configuration
+ * @dphy_freq operating frequency of the d-phy (mbps)
+ * @phy_type dphy can be of two types, passed here
+ * @dphy_gen dphy can be of three generations, passed here
+ * @dphy_te_len bus width
+ * @max_lanes maximum number of lanes
+ * @lp_time time in low-power
+ * @base_address memmory address of dphy test interface
+ * @dphy1_if_addr gluelogic dphy 1 memmory address of interface
+ * @dphy2_if_addr gluelogic dphy 2 memmory address of interface
+ * @config_8l eight lanes configuration
+ */
+
+struct dw_dphy_rx {
+ struct phy *phy;
+ struct phy_configure_opts_mipi_dphy *cfg;
+ u32 lanes_config;
+ u32 dphy_freq;
+ u32 phy_type;
+ u32 dphy_gen;
+ u32 dphy_te_len;
+ u32 max_lanes;
+ u32 lp_time;
+ void __iomem *base_address;
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ void __iomem *dphy1_if_addr;
+ void __iomem *dphy2_if_addr;
+ u8 config_8l;
+ u8 (*get_config_8l)(struct device *dev, struct dw_dphy_rx *dphy);
+#endif
+ u8 (*phy_register)(struct device *dev);
+ void (*phy_unregister)(struct device *dev);
+};
+
+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);
+void dw_dphy_write(struct dw_dphy_rx *dphy, u32 address, u32 data);
+u32 dw_dphy_read(struct dw_dphy_rx *dphy, u32 address);
+int dw_dphy_te_read(struct dw_dphy_rx *dphy, u32 addr);
+
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+u32 dw_dphy_if_read(struct dw_dphy_rx *dphy, u32 address);
+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);
+int dw_dphy_create_capabilities_sysfs(struct platform_device *pdev);
+
+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);
+}
+#endif /*IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)*/
+
+static inline 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 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__*/
diff --git a/drivers/media/platform/dwc/dw-dphy-sysfs.c b/drivers/media/platform/dwc/dw-dphy-sysfs.c
new file mode 100644
index 0000000..416dec8
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-dphy-sysfs.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI D-PHY controller driver.
+ * SysFS components for the platform driver
+ *
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#include "dw-dphy-rx.h"
+
+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_vdbg(dev, "Data Rate %lu Mbps\n", freq);
+ dphy->dphy_freq = freq;
+
+ 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,
+ sizeof(buffer),
+ "Freq %d\n", dphy->dphy_freq);
+
+ 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;
+ u8 addr, payload;
+ int ret;
+
+ ret = kstrtoul(buf, 32, &val);
+ if (ret < 0)
+ return ret;
+
+ payload = (u16)val;
+ addr = (u16)(val >> 16);
+
+ dev_vdbg(dev, "addr 0x%lX\n", val);
+ dev_vdbg(dev, "payload: 0x%X\n", addr);
+ dev_vdbg(dev, "Addr [0x%x] -> 0x%x\n", (unsigned int)addr,
+ dw_dphy_te_read(dphy, addr));
+
+ return count;
+}
+
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+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,
+ sizeof(buffer), "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);
+ unsigned long val;
+ u8 lane, delay;
+ int ret;
+
+ ret = kstrtoul(buf, 16, &val);
+ if (ret < 0)
+ return ret;
+
+ lane = (u8)val;
+ delay = (u8)(val >> 8);
+
+ dev_vdbg(dev, "Lanes %u\n", lane);
+ dev_vdbg(dev, "Delay %u\n", delay);
+
+ dw_dphy_if_set_idelay_lane(dphy, delay, lane);
+
+ return count;
+}
+#endif
+
+static ssize_t len_config_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 length;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &length);
+ if (ret < 0)
+ return ret;
+
+ if (length == BIT8)
+ dev_vdbg(dev, "Configured for 8-bit interface\n");
+ else if (length == BIT12)
+ dev_vdbg(dev, "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, sizeof(buffer), "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)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_dphy_rx *dphy = platform_get_drvdata(pdev);
+ unsigned long lp_time;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &lp_time);
+ if (ret < 0)
+ return ret;
+
+ if (lp_time > 1 && lp_time < 10000) {
+ dphy->lp_time = lp_time;
+ } else {
+ dev_vdbg(dev, "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, sizeof(buffer), "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);
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+static DEVICE_ATTR_RW(idelay);
+#endif
+static DEVICE_ATTR_RW(len_config);
+static DEVICE_ATTR_RW(dw_dphy_g118_settle);
+
+int dw_dphy_create_capabilities_sysfs(struct platform_device *pdev)
+{
+ 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);
+#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
+ device_create_file(&pdev->dev, &dev_attr_idelay);
+#endif
+ device_create_file(&pdev->dev, &dev_attr_len_config);
+ device_create_file(&pdev->dev, &dev_attr_dw_dphy_g118_settle);
+ return 0;
+}
diff --git a/include/media/dwc/dw-dphy-data.h b/include/media/dwc/dw-dphy-data.h
new file mode 100644
index 0000000..c8d6c0f
--- /dev/null
+++ b/include/media/dwc/dw-dphy-data.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
+ *
+ * Synopsys DesignWare MIPI D-PHY platform data
+ *
+ * Author: Luis Oliveira <[email protected]>
+ */
+
+#include <linux/phy/phy.h>
+#include <linux/kernel.h>
+#include <media/dwc/dw-mipi-csi-pltfrm.h>
+
+struct dw_phy_pdata {
+ u32 dphy_frequency;
+ u8 dphy_te_len;
+ u32 config_8l;
+ u8 dphy_gen;
+ u8 phy_type;
+ u8 id;
+};
+
+static const struct pdata_names phys[] = {
+ { .name = "phy-dw-dphy.0.0", },
+ { .name = "phy-dw-dphy.1.1", },
+};
+
+struct dw_dphy_rx;
+
+struct plat_dw_dphy {
+ int (*get_resources)(struct device *dev, struct dw_dphy_rx *dphy);
+};
--
2.7.4
Hi Luis,
Thank you for the patchset.
On Tue, Jun 11, 2019 at 09:20:50PM +0200, Luis Oliveira wrote:
> From: Luis Oliveira <[email protected]>
>
> Add bindings for Synopsys DesignWare MIPI CSI-2 host.
>
> Signed-off-by: Luis Oliveira <[email protected]>
> ---
> Changelog
> v3-v4
> - remove "plat" from the block name @rob @laurent
> - remove "phy-names" when single-entry @rob
> - remove "snps,output-type" -> went to the driver config @laurent
>
> .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> 1 file changed, 41 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
>
> diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> new file mode 100644
> index 0000000..613b7f9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> @@ -0,0 +1,41 @@
> +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"
> +- reg : physical base address and size of the device memory
> + mapped registers;
> +- interrupts : DW CSI-2 Host interrupts
> +- 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)
> +
> +The per-board settings:
> + - port sub-node describing a single endpoint connected to the camera as
> + described in video-interfaces.txt[1].
Which endpoint properties in video-interfaces.txt are relevant for the
hardware? Which values may they have?
> +
> +Example:
> +
> + csi2: csi2@3000 {
> + compatible = "snps,dw-csi";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = < 0x03000 0x7FF>;
reg = <0x03000 0x7FF>;
> + phys = <&mipi_dphy_rx>;
> + resets = <&dw_rst 1>;
> + interrupts = <2>;
> +
> + port@0 {
> + reg = <0>;
You can drop "@0" and the reg property.
> + csi_ep1: endpoint {
> + remote-endpoint = <&camera_1>;
> + data-lanes = <1 2>;
> + };
> + };
> + };
--
Kind regards,
Sakari Ailus
Hi Sakari,
Thank you for your feedback.
I have my comments inline.
From: Sakari Ailus <[email protected]>
Date: Fri, Jun 28, 2019 at 15:13:26
> Hi Luis,
>
> Thank you for the patchset.
>
> On Tue, Jun 11, 2019 at 09:20:50PM +0200, Luis Oliveira wrote:
> > From: Luis Oliveira <[email protected]>
> >
> > Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> >
> > Signed-off-by: Luis Oliveira <[email protected]>
> > ---
> > Changelog
> > v3-v4
> > - remove "plat" from the block name @rob @laurent
> > - remove "phy-names" when single-entry @rob
> > - remove "snps,output-type" -> went to the driver config @laurent
> >
> > .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> > 1 file changed, 41 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> >
> > diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > new file mode 100644
> > index 0000000..613b7f9
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > @@ -0,0 +1,41 @@
> > +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"
> > +- reg : physical base address and size of the device memory
> > + mapped registers;
> > +- interrupts : DW CSI-2 Host interrupts
> > +- 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)
> > +
> > +The per-board settings:
> > + - port sub-node describing a single endpoint connected to the camera as
> > + described in video-interfaces.txt[1].
>
> Which endpoint properties in video-interfaces.txt are relevant for the
> hardware? Which values may they have?
>
Currently I'm using only two properties "data-lanes" and "bus-width", but
I have plans to add blanking info also.
I will add more info.
> > +
> > +Example:
> > +
> > + csi2: csi2@3000 {
> > + compatible = "snps,dw-csi";
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + reg = < 0x03000 0x7FF>;
>
> reg = <0x03000 0x7FF>;
Yes, I've missed that.
>
> > + phys = <&mipi_dphy_rx>;
> > + resets = <&dw_rst 1>;
> > + interrupts = <2>;
> > +
> > + port@0 {
> > + reg = <0>;
>
> You can drop "@0" and the reg property.
Ok thank you.
>
> > + csi_ep1: endpoint {
> > + remote-endpoint = <&camera_1>;
> > + data-lanes = <1 2>;
> > + };
> > + };
> > + };
>
> --
> Kind regards,
>
> Sakari Ailus
Best regards,
Luis
On Tue, Jun 11, 2019 at 09:20:53PM +0200, 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
> v3-v4
> - @Laurent I know I told you I could remove the snps,dphy-frequency on V3 but
> it is really useful for me here. I removed all other the proprietary
> properties except this one. Do you still think it must be removed?
> - Frequency units @Rob
Frequency units means append '-khz' to the property name. That also
makes the 'frequency' part redundant, so maybe name it more around what
the frequency is. The frequency for what?
Rob
>
> .../devicetree/bindings/phy/snps,dw-dphy-rx.txt | 29 ++++++++++++++++++++++
> 1 file changed, 29 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
>
> diff --git a/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt b/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
> new file mode 100644
> index 0000000..50603e6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
> @@ -0,0 +1,29 @@
> +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,dw-dphy-rx".
> +- #phy-cells : Must be 1.
> +- bus-width : Size of the test interface data bus (8 bits->8 or
> + 12bits->12).
> +- snps,dphy-frequency : Frequency at which D-PHY should start, configurable.
> + Check Synopsys databook. (-kHz)
> +- reg : Test interface register. This correspondes to the
> + physical base address of the controller and size of
> + the device memory mapped registers; Check Synopsys
> + databook.
> +
> +Example:
> +
> + mipi_dphy_rx1: dphy@d00003040 {
> + compatible = "snps,dw-dphy-rx";
> + #phy-cells = <1>;
> + bus-width = <12>;
> + snps,dphy-frequency = <300000>;
> + reg = <0xd0003040 0x20>;
> + };
> --
> 2.7.4
>
On 11.06.2019 22:20, Luis Oliveira wrote:
> From: Luis Oliveira <[email protected]>
>
> Add bindings for Synopsys DesignWare MIPI CSI-2 host.
>
> Signed-off-by: Luis Oliveira <[email protected]>
> ---
> Changelog
> v3-v4
> - remove "plat" from the block name @rob @laurent
> - remove "phy-names" when single-entry @rob
> - remove "snps,output-type" -> went to the driver config @laurent
>
> .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> 1 file changed, 41 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
>
> diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> new file mode 100644
> index 0000000..613b7f9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> @@ -0,0 +1,41 @@
> +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"
> +- reg : physical base address and size of the device memory
> + mapped registers;
> +- interrupts : DW CSI-2 Host interrupts
> +- 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)
> +
> +The per-board settings:
> + - port sub-node describing a single endpoint connected to the camera as
> + described in video-interfaces.txt[1].
> +
> +Example:
> +
> + csi2: csi2@3000 {
> + compatible = "snps,dw-csi";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = < 0x03000 0x7FF>;
> + phys = <&mipi_dphy_rx>;
> + resets = <&dw_rst 1>;
> + interrupts = <2>;
> +
> + port@0 {
> + reg = <0>;
> + csi_ep1: endpoint {
> + remote-endpoint = <&camera_1>;
> + data-lanes = <1 2>;
> + };
Hello Luis,
Which is the output port (endpoint) : how to connect the output of
csi2host to another node ?
I mean, the second port of this block, or, how is the data taken from
csi2host ?
Thanks,
Eugen
> + };
> + };
>
From: Rob Herring <[email protected]>
Date: Tue, Jul 09, 2019 at 15:20:00
> On Tue, Jun 11, 2019 at 09:20:53PM +0200, 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
> > v3-v4
> > - @Laurent I know I told you I could remove the snps,dphy-frequency on V3 but
> > it is really useful for me here. I removed all other the proprietary
> > properties except this one. Do you still think it must be removed?
> > - Frequency units @Rob
>
> Frequency units means append '-khz' to the property name. That also
> makes the 'frequency' part redundant, so maybe name it more around what
> the frequency is. The frequency for what?
>
> Rob
>
This is the D-PHY high speed frequency configuration that corresponds to
the high speed data transfer of the bus.
Basically is the transmission speed per lane.
Do you think I can use "link-frequencies" (present in
video-interfaces.txt) with one frequency?
> >
> > .../devicetree/bindings/phy/snps,dw-dphy-rx.txt | 29 ++++++++++++++++++++++
> > 1 file changed, 29 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
> >
> > diff --git a/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt b/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
> > new file mode 100644
> > index 0000000..50603e6
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
> > @@ -0,0 +1,29 @@
> > +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,dw-dphy-rx".
> > +- #phy-cells : Must be 1.
> > +- bus-width : Size of the test interface data bus (8 bits->8 or
> > + 12bits->12).
> > +- snps,dphy-frequency : Frequency at which D-PHY should start, configurable.
> > + Check Synopsys databook. (-kHz)
> > +- reg : Test interface register. This correspondes to the
> > + physical base address of the controller and size of
> > + the device memory mapped registers; Check Synopsys
> > + databook.
> > +
> > +Example:
> > +
> > + mipi_dphy_rx1: dphy@d00003040 {
> > + compatible = "snps,dw-dphy-rx";
> > + #phy-cells = <1>;
> > + bus-width = <12>;
> > + snps,dphy-frequency = <300000>;
> > + reg = <0xd0003040 0x20>;
> > + };
> > --
> > 2.7.4
> >
Thank you Rob,
Luis
Hi Eugen,
From: [email protected] <[email protected]>
Date: Tue, Jul 09, 2019 at 15:33:50
>
>
> On 11.06.2019 22:20, Luis Oliveira wrote:
> > From: Luis Oliveira <[email protected]>
> >
> > Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> >
> > Signed-off-by: Luis Oliveira <[email protected]>
> > ---
> > Changelog
> > v3-v4
> > - remove "plat" from the block name @rob @laurent
> > - remove "phy-names" when single-entry @rob
> > - remove "snps,output-type" -> went to the driver config @laurent
> >
> > .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> > 1 file changed, 41 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> >
> > diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > new file mode 100644
> > index 0000000..613b7f9
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > @@ -0,0 +1,41 @@
> > +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"
> > +- reg : physical base address and size of the device memory
> > + mapped registers;
> > +- interrupts : DW CSI-2 Host interrupts
> > +- 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)
> > +
> > +The per-board settings:
> > + - port sub-node describing a single endpoint connected to the camera as
> > + described in video-interfaces.txt[1].
> > +
> > +Example:
> > +
> > + csi2: csi2@3000 {
> > + compatible = "snps,dw-csi";
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + reg = < 0x03000 0x7FF>;
> > + phys = <&mipi_dphy_rx>;
> > + resets = <&dw_rst 1>;
> > + interrupts = <2>;
> > +
> > + port@0 {
> > + reg = <0>;
> > + csi_ep1: endpoint {
> > + remote-endpoint = <&camera_1>;
> > + data-lanes = <1 2>;
> > + };
>
> Hello Luis,
>
> Which is the output port (endpoint) : how to connect the output of
> csi2host to another node ?
> I mean, the second port of this block, or, how is the data taken from
> csi2host ?
>
I understand your question, I think you guessed this is not the complete
pipeline (I have a top driver that interacts with this one).
I was not planning to submit it, do you think I should?
The behavior is very similar with this one
./drivers/media/platform/exynos4-is/media-dev.c
> Thanks,
>
> Eugen
>
> > + };
> > + };
> >
Thanks,
Luis
Hi Luis,
On Mon, Jul 08, 2019 at 03:21:50PM +0000, Luis de Oliveira wrote:
> Hi Sakari,
>
> Thank you for your feedback.
> I have my comments inline.
>
> From: Sakari Ailus <[email protected]>
> Date: Fri, Jun 28, 2019 at 15:13:26
>
> > Hi Luis,
> >
> > Thank you for the patchset.
> >
> > On Tue, Jun 11, 2019 at 09:20:50PM +0200, Luis Oliveira wrote:
> > > From: Luis Oliveira <[email protected]>
> > >
> > > Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> > >
> > > Signed-off-by: Luis Oliveira <[email protected]>
> > > ---
> > > Changelog
> > > v3-v4
> > > - remove "plat" from the block name @rob @laurent
> > > - remove "phy-names" when single-entry @rob
> > > - remove "snps,output-type" -> went to the driver config @laurent
> > >
> > > .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> > > 1 file changed, 41 insertions(+)
> > > create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > >
> > > diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > new file mode 100644
> > > index 0000000..613b7f9
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > @@ -0,0 +1,41 @@
> > > +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"
> > > +- reg : physical base address and size of the device memory
> > > + mapped registers;
> > > +- interrupts : DW CSI-2 Host interrupts
> > > +- 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)
> > > +
> > > +The per-board settings:
> > > + - port sub-node describing a single endpoint connected to the camera as
> > > + described in video-interfaces.txt[1].
> >
> > Which endpoint properties in video-interfaces.txt are relevant for the
> > hardware? Which values may they have?
> >
>
> Currently I'm using only two properties "data-lanes" and "bus-width", but
> I have plans to add blanking info also.
> I will add more info.
Isn't blanking defined by what the transmitter seneds? Or do you have
hardware limitations on the receiver side?
I've only heard of one such case before, and it was a very old parallel
receiver.
If you have a CSI-2 receiver, bus-width isn't relevant --- it's for paralle
interfaces only. Please add data-lanes to required endpoint properties.
--
Regards,
Sakari Ailus
On 09.07.2019 20:08, Luis de Oliveira wrote:
>
> Hi Eugen,
>
>
> From: [email protected] <[email protected]>
> Date: Tue, Jul 09, 2019 at 15:33:50
>
>>
>>
>> On 11.06.2019 22:20, Luis Oliveira wrote:
>>> From: Luis Oliveira <[email protected]>
>>>
>>> Add bindings for Synopsys DesignWare MIPI CSI-2 host.
>>>
>>> Signed-off-by: Luis Oliveira <[email protected]>
>>> ---
>>> Changelog
>>> v3-v4
>>> - remove "plat" from the block name @rob @laurent
>>> - remove "phy-names" when single-entry @rob
>>> - remove "snps,output-type" -> went to the driver config @laurent
>>>
>>> .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
>>> 1 file changed, 41 insertions(+)
>>> create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
>>>
>>> diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
>>> new file mode 100644
>>> index 0000000..613b7f9
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
>>> @@ -0,0 +1,41 @@
>>> +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"
>>> +- reg : physical base address and size of the device memory
>>> + mapped registers;
>>> +- interrupts : DW CSI-2 Host interrupts
>>> +- 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)
>>> +
>>> +The per-board settings:
>>> + - port sub-node describing a single endpoint connected to the camera as
>>> + described in video-interfaces.txt[1].
>>> +
>>> +Example:
>>> +
>>> + csi2: csi2@3000 {
>>> + compatible = "snps,dw-csi";
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> + reg = < 0x03000 0x7FF>;
>>> + phys = <&mipi_dphy_rx>;
>>> + resets = <&dw_rst 1>;
>>> + interrupts = <2>;
>>> +
>>> + port@0 {
>>> + reg = <0>;
>>> + csi_ep1: endpoint {
>>> + remote-endpoint = <&camera_1>;
>>> + data-lanes = <1 2>;
>>> + };
>>
>> Hello Luis,
>>
>> Which is the output port (endpoint) : how to connect the output of
>> csi2host to another node ?
>> I mean, the second port of this block, or, how is the data taken from
>> csi2host ?
>>
>
> I understand your question, I think you guessed this is not the complete
> pipeline (I have a top driver that interacts with this one).
> I was not planning to submit it, do you think I should?
Yes please, you can have the patch with subject DO NOT MERGE if you do
not want that patch to be included in the kernel and just for reference.
but it would help me in understanding your setup
Thanks !
>
> The behavior is very similar with this one
> ./drivers/media/platform/exynos4-is/media-dev.c
>
>
>> Thanks,
>>
>> Eugen
>>
>>> + };
>>> + };
>>>
>
> Thanks,
> Luis
>
Hello Luis,
A few questions inline:
On 11.06.2019 22:20, Luis Oliveira wrote:
> Add the Synopsys MIPI CSI-2 controller driver. This
> controller driver is divided in platform functions and core functions.
> This way it serves as platform for future DesignWare drivers.
>
> Signed-off-by: Luis Oliveira <[email protected]>
> ---
> Changelog
> v3-v4
> - fix v4l2_fwnode_endpoint bad initialization @eugen
> - removed extra lines and fixed coding style issues
>
> MAINTAINERS | 8 +
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/dwc/Kconfig | 19 +
> drivers/media/platform/dwc/Makefile | 9 +
> drivers/media/platform/dwc/dw-csi-plat.c | 475 +++++++++++++++++++++++
> drivers/media/platform/dwc/dw-csi-plat.h | 97 +++++
> drivers/media/platform/dwc/dw-csi-sysfs.c | 624 ++++++++++++++++++++++++++++++
> drivers/media/platform/dwc/dw-mipi-csi.c | 569 +++++++++++++++++++++++++++
> drivers/media/platform/dwc/dw-mipi-csi.h | 287 ++++++++++++++
> include/media/dwc/dw-mipi-csi-pltfrm.h | 104 +++++
> 11 files changed, 2195 insertions(+)
> 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-csi-sysfs.c
> 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 16a97ba..6ffe4fd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15187,6 +15187,14 @@ S: Maintained
> F: drivers/dma/dwi-axi-dmac/
> F: Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.txt
>
> +SYNOPSYS DESIGNWARE MIPI DPHY CSI-2 HOST DRIVER
> +M: Luis Oliveira <[email protected]>
> +L: [email protected]
> +T: git git://linuxtv.org/media_tree.git
> +S: Maintained
> +F: drivers/media/platform/dwc
> +F: Documentation/devicetree/bindings/media/snps,dw-csi.txt
> +
> SYNOPSYS DESIGNWARE DMAC DRIVER
> M: Viresh Kumar <[email protected]>
> R: Andy Shevchenko <[email protected]>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 8a19654..b6fb139 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -147,6 +147,7 @@ source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> source "drivers/media/platform/sunxi/sun6i-csi/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 7cbbd92..4807caf 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -101,3 +101,5 @@ obj-y += meson/
> obj-y += cros-ec-cec/
>
> obj-$(CONFIG_VIDEO_SUN6I_CSI) += sunxi/sun6i-csi/
> +
> +obj-y += dwc/
> diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
> new file mode 100644
> index 0000000..508ac21
> --- /dev/null
> +++ b/drivers/media/platform/dwc/Kconfig
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Synopsys DWC Platform drivers
> +# Drivers here are currently for MIPI CSI-2 support
> +
> +config DWC_MIPI_CSI2_HOST
> + tristate "Synopsys DesignWare CSI-2 Host Controller support"
> + select VIDEO_DEV
> + select VIDEO_V4L2
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + help
> + This selects the DesignWare MIPI CSI-2 host controller support. This
> + controller gives access to control a CSI-2 receiver acting as a V4L2
> + subdevice.
> +
> + If you have a controller with this interface, say Y.
> +
> + If unsure, say N.
> diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
> new file mode 100644
> index 0000000..057f137
> --- /dev/null
> +++ b/drivers/media/platform/dwc/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for Synopsys DWC Platform drivers
> +#
> +dw-csi-objs := dw-csi-plat.o dw-mipi-csi.o
> +ifeq ($(CONFIG_DWC_MIPI_TC_DPHY_GEN3),y)
> + dw-csi-objs += dw-csi-sysfs.o
> +endif
> +obj-$(CONFIG_DWC_MIPI_CSI2_HOST) += dw-csi.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..9828d55
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-plat.c
> @@ -0,0 +1,475 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * Platform driver
> + *
> + * Author: Luis Oliveira <[email protected]>
> + */
> +
> +#include <media/dwc/dw-dphy-data.h>
> +
> +#include "dw-csi-plat.h"
> +
> +const struct mipi_dt csi_dt[] = {
> + {
> + .hex = CSI_2_YUV420_8,
> + .name = "YUV420_8bits",
> + }, {
> + .hex = CSI_2_YUV420_10,
> + .name = "YUV420_10bits",
> + }, {
> + .hex = CSI_2_YUV420_8_LEG,
> + .name = "YUV420_8bits_LEGACY",
> + }, {
> + .hex = CSI_2_YUV420_8_SHIFT,
> + .name = "YUV420_8bits_SHIFT",
> + }, {
> + .hex = CSI_2_YUV420_10_SHIFT,
> + .name = "YUV420_10bits_SHIFT",
> + }, {
> + .hex = CSI_2_YUV422_8,
> + .name = "YUV442_8bits",
> + }, {
> + .hex = CSI_2_YUV422_10,
> + .name = "YUV442_10bits",
> + }, {
> + .hex = CSI_2_RGB444,
> + .name = "RGB444",
> + }, {
> + .hex = CSI_2_RGB555,
> + .name = "RGB555",
> + }, {
> + .hex = CSI_2_RGB565,
> + .name = "RGB565",
> + }, {
> + .hex = CSI_2_RGB666,
> + .name = "RGB666",
> + }, {
> + .hex = CSI_2_RGB888,
> + .name = "RGB888",
> + }, {
> + .hex = CSI_2_RAW6,
> + .name = "RAW6",
> + }, {
> + .hex = CSI_2_RAW7,
> + .name = "RAW7",
> + }, {
> + .hex = CSI_2_RAW8,
> + .name = "RAW8",
> + }, {
> + .hex = CSI_2_RAW10,
> + .name = "RAW10",
> + }, {
> + .hex = CSI_2_RAW12,
> + .name = "RAW12",
> + }, {
> + .hex = CSI_2_RAW14,
> + .name = "RAW14",
> + }, {
> + .hex = CSI_2_RAW16,
> + .name = "RAW16",
> + },
> +};
> +
> +static 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].mbus_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;
> +
> + if (code->index != 0)
> + return -EINVAL;
What is the purpose of checking if index >= array_size, if any value
different from 0 will return an error anyway ?
> +
> + code->code = dw_mipi_csi_formats[code->index].mbus_code;
> + return 0;
> +}
> +
> +static struct mipi_fmt *
> +dw_mipi_csi_try_format(struct v4l2_mbus_framefmt *mf)
> +{
> + struct mipi_fmt *fmt;
> +
> + fmt = find_dw_mipi_csi_format(mf);
> + if (!fmt)
> + fmt = &dw_mipi_csi_formats[0];
> +
> + mf->code = fmt->mbus_code;
> +
> + return fmt;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +dw_mipi_csi_get_format(struct dw_csi *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;
> + dev_dbg(dev->dev,
> + "%s got v4l2_mbus_pixelcode. 0x%x\n", __func__,
> + dev->format.code);
> + dev_dbg(dev->dev,
> + "%s got width. 0x%x\n", __func__,
> + dev->format.width);
> + dev_dbg(dev->dev,
> + "%s got height. 0x%x\n", __func__,
> + dev->format.height);
> + 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 dw_csi *dev = sd_to_mipi_csi_dev(sd);
> + struct mipi_fmt *dev_fmt;
> + struct v4l2_mbus_framefmt *mf = dw_mipi_csi_get_format(dev, cfg,
> + fmt->which);
> + int i;
> +
> + 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;
> + dev->fmt->mbus_code = mf->code;
> + dw_mipi_csi_set_ipi_fmt(dev);
> + }
> +
> + if (fmt->format.width > 0 && fmt->format.height > 0) {
> + dw_mipi_csi_fill_timings(dev, fmt);
> + } else {
> + dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
> + __func__, fmt->format.width);
> + dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
> + __func__, fmt->format.height);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(csi_dt); i++)
> + if (csi_dt[i].hex == dev->ipi_dt) {
> + dev_vdbg(dev->dev, "Using data type %s\n",
> + csi_dt[i].name);
> + }
> + 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 dw_csi *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 dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dev_vdbg(dev->dev, "%s: on=%d\n", __func__, on);
> +
> + 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);
> + /* reset data type */
> + dev->ipi_dt = 0x0;
> + }
> + return 0;
> +}
> +
> +static int
> +dw_mipi_csi_log_status(struct v4l2_subdev *sd)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dw_mipi_csi_dump(dev);
> +
> + return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
> +static int
> +dw_mipi_csi_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dev_vdbg(dev->dev, "%s: reg=%llu\n", __func__, reg->reg);
> + reg->val = dw_mipi_csi_read(dev, reg->reg);
> +
> + return 0;
> +}
> +#endif
> +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 = MEDIA_BUS_FMT_RGB888_1X24;
> + 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,
> + .log_status = dw_mipi_csi_log_status,
> +#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
> + .g_register = dw_mipi_csi_g_register,
> +#endif
> +};
> +
> +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)
This naming 'irq1' is pretty unfortunate, do you agree ?
> +{
> + struct dw_csi *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 dw_csi *dev)
> +{
> + struct device_node *node = pdev->dev.of_node;
> + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> + int ret = 0;
> +
> + if (of_property_read_u32(node, "snps,output-type",
> + &dev->hw.output))
> + dev->hw.output = 2;
> +
> + node = of_graph_get_next_endpoint(node, NULL);
> + if (!node) {
> + dev_err(&pdev->dev, "No port node at %pOF\n",
> + pdev->dev.of_node);
> + return -EINVAL;
> + }
> + /* Get port node and validate MIPI-CSI channel id. */
> + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &ep);
> + if (ret)
> + goto err;
> +
> + dev->index = ep.base.port - 1;
> + if (dev->index >= CSI_MAX_ENTITIES) {
> + ret = -ENXIO;
> + goto err;
> + }
> + dev->hw.num_lanes = ep.bus.mipi_csi2.num_data_lanes;
> +err:
> + of_node_put(node);
> + return ret;
> +}
> +
> +static const struct of_device_id dw_mipi_csi_of_match[];
> +
> +static int dw_csi_probe(struct platform_device *pdev)
> +{
> + const struct of_device_id *of_id = NULL;
> + struct device *dev = &pdev->dev;
> + struct resource *res = NULL;
> + struct dw_csi *csi;
> + struct v4l2_subdev *sd;
> + int ret;
> +
> + dev_vdbg(dev, "Probing started\n");
> +
> + /* Resource allocation */
> + csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
> + if (!csi)
> + return -ENOMEM;
> +
> + mutex_init(&csi->lock);
> + spin_lock_init(&csi->slock);
> + csi->dev = dev;
> +
> + of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
> + if (!of_id)
> + return -EINVAL;
> +
> + ret = dw_mipi_csi_parse_dt(pdev, csi);
> + if (ret < 0)
> + return ret;
> +
> + csi->phy = devm_of_phy_get(dev, dev->of_node, NULL);
> + if (IS_ERR(csi->phy)) {
> + dev_err(dev, "No DPHY available\n");
> + return PTR_ERR(csi->phy);
> + }
> + /* Registers mapping */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res)
> + return -ENXIO;
> +
> + csi->base_address = devm_ioremap_resource(dev, res);
> + if (IS_ERR(csi->base_address)) {
> + dev_err(dev, "Base address not set.\n");
> + return PTR_ERR(csi->base_address);
> + }
> +
> + csi->ctrl_irq_number = platform_get_irq(pdev, 0);
> + if (csi->ctrl_irq_number < 0) {
> + dev_err(dev, "irq number %d not set.\n", csi->ctrl_irq_number);
> + ret = csi->ctrl_irq_number;
> + goto end;
> + }
> +
> + csi->rst = devm_reset_control_get_optional_shared(dev, NULL);
> + if (IS_ERR(csi->rst)) {
> + dev_err(dev, "error getting reset control %d\n", ret);
this is optional ? in case it does not exist, there is no error ?
> + return PTR_ERR(csi->rst);
> + }
> +
> + ret = devm_request_irq(dev, csi->ctrl_irq_number,
> + dw_mipi_csi_irq1, IRQF_SHARED,
> + dev_name(dev), csi);
> + if (ret) {
> + dev_err(dev, "irq csi %s failed\n", of_id->name);
> +
> + goto end;
if this fails and you go to end, media entity cleanup is called, but it
was never initialized ? does it matter ?
> + }
> +
> + sd = &csi->sd;
> + v4l2_subdev_init(sd, &dw_mipi_csi_subdev_ops);
> + csi->sd.owner = THIS_MODULE;
> +
> + snprintf(sd->name, sizeof(sd->name), "%s.%d",
> + "dw-csi", csi->index);
> +
> + csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + csi->fmt = &dw_mipi_csi_formats[0];
> + csi->format.code = dw_mipi_csi_formats[0].mbus_code;
> +
> + sd->entity.function = MEDIA_ENT_F_IO_V4L;
> + csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> + csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> + ret = media_entity_pads_init(&csi->sd.entity,
> + CSI_PADS_NUM, csi->pads);
> +
> + dev_vdbg(dev, "v4l2.name: %s\n", csi->v4l2_dev.name);
> +
> + if (ret < 0) {
> + dev_err(dev, "media entity init failed\n");
> + goto end;
> + }
> +
> + v4l2_set_subdevdata(&csi->sd, pdev);
> + platform_set_drvdata(pdev, &csi->sd);
> + dev_set_drvdata(dev, sd);
> +
> + if (csi->rst)
> + reset_control_deassert(csi->rst);
> +#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
> + dw_csi_create_capabilities_sysfs(pdev);
> +#endif
> + dw_mipi_csi_get_version(csi);
> + dw_mipi_csi_specific_mappings(csi);
> + dw_mipi_csi_mask_irq_power_off(csi);
> +
> + dev_info(dev, "DW MIPI CSI-2 Host registered successfully HW v%u.%u\n",
> + csi->hw_version_major, csi->hw_version_minor);
> +
> + phy_init(csi->phy);
> +
> + return 0;
> +
> +end:
> + media_entity_cleanup(&csi->sd.entity);
> + v4l2_device_unregister(csi->vdev.v4l2_dev);
> +
> + return ret;
> +}
> +
> +/**
> + * @short Exit routine - Exit point of the driver
> + * @param[in] pdev pointer to the platform device structure
> + * @return 0 on success
> + */
> +static int dw_csi_remove(struct platform_device *pdev)
> +{
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *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" },
> + {},
> +};
> +
> +MODULE_DEVICE_TABLE(of, dw_mipi_csi_of_match);
> +
> +/**
> + * @short Platform driver structure
> + */
> +static struct platform_driver dw_mipi_csi_driver = {
> + .remove = dw_csi_remove,
> + .probe = dw_csi_probe,
> + .driver = {
> + .name = "dw-csi",
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(dw_mipi_csi_of_match),
> + },
> +};
> +
> +module_platform_driver(dw_mipi_csi_driver);
> +
> +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..e322592
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-plat.h
> @@ -0,0 +1,97 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018 Synopsys, Inc.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * Supported bus formats
> + *
> + * Author: Luis Oliveira <[email protected]>
> + */
> +
> +#ifndef _DW_CSI_PLAT_H__
> +#define _DW_CSI_PLAT_H__
> +
> +#include "dw-mipi-csi.h"
> +
> +/* Video formats supported by the MIPI CSI-2 */
> +struct mipi_fmt dw_mipi_csi_formats[] = {
> + {
> + /* RAW 8 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .depth = 8,
> + }, {
> + /* RAW 10 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .depth = 10,
> + }, {
> + /* RAW 12 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .depth = 12,
> + }, {
> + /* RAW 14 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .depth = 14,
> + }, {
> + /* RAW 16 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR16_1X16,
> + .depth = 16,
> + }, {
> + /* RGB 666 */
> + .mbus_code = MEDIA_BUS_FMT_RGB666_1X18,
> + .depth = 18,
> + }, {
> + /* RGB 565 */
> + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE,
> + .depth = 16,
> + }, {
> + /* BGR 565 */
> + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE,
> + .depth = 16,
> + }, {
> + /* RGB 555 */
> + .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
> + .depth = 16,
> + }, {
> + /* BGR 555 */
> + .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
> + .depth = 16,
> + }, {
> + /* RGB 444 */
> + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE,
> + .depth = 16,
> + }, {
> + /* RGB 444 */
> + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE,
> + .depth = 16,
> + }, {
> + /* RGB 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_LE,
> + .depth = 24,
> + }, {
> + /* BGR 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_BE,
> + .depth = 24,
> + }, {
> + /* BGR 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
> + .depth = 24,
> + }, {
> + /* YUV 422 8-bit */
> + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .depth = 16,
> + }, {
> + /* YUV 422 10-bit */
> + .mbus_code = MEDIA_BUS_FMT_VYUY10_2X10,
> + .depth = 20,
> + }, {
> + /* YUV 420 8-bit LEGACY */
> + .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> + .depth = 8,
> + }, {
> + /* YUV 420 10-bit */
> + .mbus_code = MEDIA_BUS_FMT_Y10_1X10,
> + .depth = 10,
> + },
> +};
> +
> +#endif /* _DW_CSI_PLAT_H__ */
> diff --git a/drivers/media/platform/dwc/dw-csi-sysfs.c b/drivers/media/platform/dwc/dw-csi-sysfs.c
> new file mode 100644
> index 0000000..e8d2bb9
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-sysfs.c
> @@ -0,0 +1,624 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * SysFS components for the platform driver
> + *
> + * Author: Luis Oliveira <[email protected]>
> + */
> +
> +#include "dw-mipi-csi.h"
> +
> +static ssize_t core_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 dw_csi *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 dw_csi *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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.num_lanes);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t core_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 dw_csi *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 data_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &dt);
> + if (ret < 0)
> + return ret;
> +
> + if (dt < 0x18 || dt > 0x2F) {
These aren't some top/start defines in your list ?
> + dev_err(dev, "Invalid data type %lx\n", dt);
> + return count;
> + }
> +
> + dev_info(dev, "Data type 0x%lx\n", dt);
> + csi_dev->ipi_dt = dt;
> +
> + return count;
> +}
> +
> +static ssize_t data_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->ipi_dt);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hsa_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hsa;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hsa);
> + if (ret < 0)
> + return ret;
> +
> + if (hsa > 0xFFF) {
> + dev_err(dev, "Invalid HSA time %lx\n", hsa);
> + return count;
> + }
> +
> + dev_info(dev, "HSA time 0x%lx\n", hsa);
> + csi_dev->hw.hsa = hsa;
> +
> + return count;
> +}
> +
> +static ssize_t hsa_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hsa);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hbp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hbp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hbp);
> + if (ret < 0)
> + return ret;
> +
> + if (hbp > 0xFFF) {
> + dev_err(dev, "Invalid HBP time %lx\n", hbp);
> + return count;
> + }
> +
> + dev_info(dev, "HBP time 0x%lx\n", hbp);
> + csi_dev->hw.hbp = hbp;
> +
> + return count;
> +}
> +
> +static ssize_t hbp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hbp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hsd_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hsd;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hsd);
> + if (ret < 0)
> + return ret;
> +
> + if (hsd > 0xFF) {
> + dev_err(dev, "Invalid HSD time %lx\n", hsd);
> + return count;
> + }
> +
> + dev_info(dev, "HSD time 0x%lx\n", hsd);
> + csi_dev->hw.hsd = hsd;
> +
> + return count;
> +}
> +
> +static ssize_t hsd_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hsd);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vsa_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vsa;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vsa);
> + if (ret < 0)
> + return ret;
> +
> + if (vsa > 0x3FF) {
> + dev_err(dev, "Invalid VSA period %lx\n", vsa);
> + return count;
> + }
> +
> + dev_info(dev, "VSA period 0x%lx\n", vsa);
> + csi_dev->hw.vsa = vsa;
> +
> + return count;
> +}
> +
> +static ssize_t vsa_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vsa);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vbp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vbp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vbp);
> + if (ret < 0)
> + return ret;
> +
> + if (vbp > 0x2FF) {
> + dev_err(dev, "Invalid VBP period %lx\n", vbp);
> + return count;
> + }
> +
> + dev_info(dev, "VBP period 0x%lx\n", vbp);
> + csi_dev->hw.vbp = vbp;
> +
> + return count;
> +}
> +
> +static ssize_t vbp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vbp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vfp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vfp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vfp);
> + if (ret < 0)
> + return ret;
> +
> + if (vfp > 0x3ff) {
> + dev_err(dev, "Invalid VFP period %lx\n", vfp);
> + return count;
> + }
> +
> + dev_info(dev, "VFP period 0x%lx\n", vfp);
> + csi_dev->hw.vfp = vfp;
> +
> + return count;
> +}
> +
> +static ssize_t vfp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vfp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t virtual_channel_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long virtual_ch;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &virtual_ch);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)virtual_ch < 0 || (signed int)virtual_ch > 8) {
> + dev_err(dev, "Invalid Virtual Channel %lu\n", virtual_ch);
> + return count;
> + }
> +
> + dev_info(dev, "Virtual Channel %lu\n", virtual_ch);
> + csi_dev->hw.virtual_ch = virtual_ch;
> +
> + return count;
> +}
> +
> +static ssize_t virtual_channel_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.virtual_ch);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_color_mode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_color_mode;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_color_mode);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_color_mode < 0 || (signed int)ipi_color_mode > 1) {
> + dev_err(dev,
> + "Wrong Color Mode %lu, (48 bits -> 0 or 16 bits -> 1\n",
> + ipi_color_mode);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", ipi_color_mode);
> + csi_dev->hw.ipi_color_mode = ipi_color_mode;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_color_mode_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_color_mode);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_auto_flush_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_auto_flush;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_auto_flush);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_auto_flush < 0 || (signed int)ipi_auto_flush > 1) {
> + dev_err(dev,
> + "Invalid Auto Flush Mode %lu, (No -> 0 or Yes -> 1\n",
> + ipi_auto_flush);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Auto Flush %lu\n", ipi_auto_flush);
> + csi_dev->hw.ipi_auto_flush = ipi_auto_flush;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_auto_flush_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_auto_flush);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_timings_mode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_mode;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_mode);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_mode < 0 || (signed int)ipi_mode > 1) {
> + dev_err(dev,
> + "Invalid Timing Source %lu (Camera:0|Controller:1)\n",
> + ipi_mode);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", ipi_mode);
> + csi_dev->hw.ipi_mode = ipi_mode;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_timings_mode_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_mode);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t output_type_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long output;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &output);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)output < 0 || (signed int)output > 1) {
> + dev_err(dev,
> + "Invalid Core output %lu to be used \
> + (IPI-> 0 or IDI->1 or BOTH- 2\n",
> + output);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", output);
> + csi_dev->hw.output = output;
> +
> + return count;
> +}
> +
> +static ssize_t output_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.output);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static DEVICE_ATTR_RO(core_version);
> +static DEVICE_ATTR_RO(core_reset);
> +static DEVICE_ATTR_RW(n_lanes);
> +static DEVICE_ATTR_RW(data_type);
> +static DEVICE_ATTR_RW(hsa);
> +static DEVICE_ATTR_RW(hbp);
> +static DEVICE_ATTR_RW(hsd);
> +static DEVICE_ATTR_RW(vsa);
> +static DEVICE_ATTR_RW(vbp);
> +static DEVICE_ATTR_RW(vfp);
> +static DEVICE_ATTR_RW(virtual_channel);
> +static DEVICE_ATTR_RW(ipi_color_mode);
> +static DEVICE_ATTR_RW(ipi_auto_flush);
> +static DEVICE_ATTR_RW(ipi_timings_mode);
> +static DEVICE_ATTR_RW(output_type);
> +
> +int dw_csi_create_capabilities_sysfs(struct platform_device *pdev)
> +{
> + device_create_file(&pdev->dev, &dev_attr_core_version);
> + device_create_file(&pdev->dev, &dev_attr_core_reset);
> + device_create_file(&pdev->dev, &dev_attr_n_lanes);
> + device_create_file(&pdev->dev, &dev_attr_data_type);
> + device_create_file(&pdev->dev, &dev_attr_hsa);
> + device_create_file(&pdev->dev, &dev_attr_hbp);
> + device_create_file(&pdev->dev, &dev_attr_hsd);
> + device_create_file(&pdev->dev, &dev_attr_vsa);
> + device_create_file(&pdev->dev, &dev_attr_vbp);
> + device_create_file(&pdev->dev, &dev_attr_vfp);
> + device_create_file(&pdev->dev, &dev_attr_virtual_channel);
> + device_create_file(&pdev->dev, &dev_attr_ipi_color_mode);
> + device_create_file(&pdev->dev, &dev_attr_ipi_auto_flush);
> + device_create_file(&pdev->dev, &dev_attr_ipi_timings_mode);
> + device_create_file(&pdev->dev, &dev_attr_output_type);
> +
> + return 0;
> +}
> 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..d01418d
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-mipi-csi.c
> @@ -0,0 +1,569 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver
> + * Core MIPI CSI-2 functions
> + *
> + * Author: 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),
> +};
> +
> +#define dw_print(VAR) \
> + dev_info(csi_dev->dev, "%s: 0x%x: %X\n", "#VAR#",\
> + VAR, dw_mipi_csi_read(csi_dev, VAR))
> +
> +void dw_mipi_csi_write_part(struct dw_csi *dev, u32 address, u32 data,
> + u8 shift, u8 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 dw_csi *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 dw_csi *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_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 dw_csi *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,
> + GENMASK(31, 0));
> +
> + /* common */
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY_FATAL,
> + GENMASK(8, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT_FATAL,
> + GENMASK(1, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY, GENMASK(23, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_LINE, GENMASK(23, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_IPI, GENMASK(5, 0));
> +
> + /* only for version 1.40 */
> + if (csi_dev->hw_version_minor == 40) {
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_BNDRY_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_SEQ_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_CRC_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_PLD_CRC_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_DATA_ID, GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_ECC_CORRECT, GENMASK(31, 0));
> + }
> + }
> + return 0;
> +}
> +
> +void dw_mipi_csi_set_ipi_fmt(struct dw_csi *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);
> + switch (csi_dev->ipi_dt) {
> + case CSI_2_YUV420_8:
> + case CSI_2_YUV420_8_LEG:
> + case CSI_2_YUV420_8_SHIFT:
> + break;
> + case CSI_2_YUV420_10:
> + case CSI_2_YUV420_10_SHIFT:
> + break;
> + }
any reason for this switch instruction? does basically nothing.
> + } else {
> + switch (csi_dev->fmt->mbus_code) {
> + /* RGB 666 */
> + case MEDIA_BUS_FMT_RGB666_1X18:
> + csi_dev->ipi_dt = CSI_2_RGB666;
> + break;
> + /* RGB 565 */
> + case MEDIA_BUS_FMT_RGB565_2X8_BE:
> + case MEDIA_BUS_FMT_RGB565_2X8_LE:
> + csi_dev->ipi_dt = CSI_2_RGB565;
> + break;
> + /* RGB 555 */
> + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
> + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
> + csi_dev->ipi_dt = CSI_2_RGB555;
> + break;
> + /* RGB 444 */
> + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
> + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
> + csi_dev->ipi_dt = CSI_2_RGB444;
> + break;
> + /* RGB 888 */
> + break;
> + case MEDIA_BUS_FMT_RGB888_2X12_LE:
> + case MEDIA_BUS_FMT_RGB888_2X12_BE:
> + csi_dev->ipi_dt = CSI_2_RGB888;
> + break;
> + /* RAW 10 */
> + case MEDIA_BUS_FMT_SBGGR10_1X10:
> + case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
> + csi_dev->ipi_dt = CSI_2_RAW10;
> + break;
> + /* RAW 12 */
> + case MEDIA_BUS_FMT_SBGGR12_1X12:
> + csi_dev->ipi_dt = CSI_2_RAW12;
> + break;
> + /* RAW 14 */
> + case MEDIA_BUS_FMT_SBGGR14_1X14:
> + csi_dev->ipi_dt = CSI_2_RAW14;
> + break;
> + /* RAW 16 */
> + case MEDIA_BUS_FMT_SBGGR16_1X16:
> + csi_dev->ipi_dt = CSI_2_RAW16;
> + break;
> + /* RAW 8 */
> + case MEDIA_BUS_FMT_SBGGR8_1X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 422 8-bit */
> + case MEDIA_BUS_FMT_YVYU8_2X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 422 10-bit */
> + case MEDIA_BUS_FMT_VYUY8_1X16:
> + csi_dev->ipi_dt = CSI_2_YUV422_8;
> + break;
> + /* YUV 420 8-bit LEGACY */
> + case MEDIA_BUS_FMT_Y8_1X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 420 10-bit */
> + case MEDIA_BUS_FMT_Y10_1X10:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + default:
> + break;
> + }
> + dw_mipi_csi_write(csi_dev, reg.IPI_DATA_TYPE, csi_dev->ipi_dt);
> + }
> + dev_info(dev, "Selected IPI Data Type 0x%X\n", csi_dev->ipi_dt);
> +}
> +
> +void dw_mipi_csi_fill_timings(struct dw_csi *dev,
> + struct v4l2_subdev_format *fmt)
> +{
> + /* expected values */
> + dev->hw.virtual_ch = 0;
> + dev->hw.ipi_color_mode = COLOR48;
> + dev->hw.ipi_auto_flush = 1;
> + dev->hw.ipi_mode = CAMERA_TIMING;
> + dev->hw.ipi_cut_through = CTINACTIVE;
> + dev->hw.ipi_adv_features = LINE_EVENT_SELECTION(EVSELAUTO);
> + dev->hw.htotal = fmt->format.width + dev->hw.hsa +
> + dev->hw.hbp + dev->hw.hsd;
> + dev->hw.vactive = fmt->format.height;
> + dev->hw.output = 2;
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> + dev_dbg(dev->dev, "*********** timings *********\n");
> + dev_dbg(dev->dev, "Horizontal Sync Active: %d\n", dev->hw.hsa);
> + dev_dbg(dev->dev, "Horizontal Back Porch: %d\n", dev->hw.hbp);
> + dev_dbg(dev->dev, "Horizontal Width: %d\n", fmt->format.width);
> + dev_dbg(dev->dev, "Horizontal Total: %d\n", dev->hw.htotal);
> + dev_dbg(dev->dev, "Vertical Sync Active: %d\n", dev->hw.vsa);
> + dev_dbg(dev->dev, "Vertical Back Porch: %d\n", dev->hw.vbp);
> + dev_dbg(dev->dev, "Vertical Front Porch: %d\n", dev->hw.vfp);
> + dev_dbg(dev->dev, "Vertical Active: %d\n", dev->hw.vactive);
> + }
> +}
> +
> +void dw_mipi_csi_start(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> +
> + dw_mipi_csi_write(csi_dev, reg.N_LANES, (csi_dev->hw.num_lanes - 1));
> + dev_info(dev, "number of 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,
> + csi_dev->hw.ipi_adv_features);
> + 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);
> + if (csi_dev->hw.ipi_mode == CAMERA_TIMING) {
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_ADV_FEATURES,
> + LINE_EVENT_SELECTION(EVSELPROG) |
> + EN_VIDEO |
> + EN_LINE_START |
> + EN_NULL |
> + EN_BLANKING |
> + EN_EMBEDDED);
> + }
> + 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_MODE,
> + csi_dev->hw.ipi_cut_through,
> + 16, 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);
> +
> + dev_vdbg(dev, "*********** config *********\n");
> + dev_vdbg(dev, "IPI enable: %s\n",
> + csi_dev->hw.output ? "YES" : "NO");
> + dev_vdbg(dev, "video mode transmission type: %s timming\n",
> + csi_dev->hw.ipi_mode ? "controller" : "camera");
> + dev_vdbg(dev, "Color Mode: %s\n",
> + csi_dev->hw.ipi_color_mode ? "16 bits" : "48 bits");
> + dev_vdbg(dev, "Cut Through Mode: %s\n",
> + csi_dev->hw.ipi_cut_through ? "enable" : "disable");
> + dev_vdbg(dev, "Virtual Channel: %d\n",
> + csi_dev->hw.virtual_ch);
> + dev_vdbg(dev, "Auto-flush: %d\n",
> + csi_dev->hw.ipi_auto_flush);
> + dw_mipi_csi_write(csi_dev, reg.IPI_SOFTRSTN, 1);
> +
> + if (csi_dev->hw.ipi_mode == AUTO_TIMING)
> + phy_power_on(csi_dev->phy);
> +
> + 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);
> + 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);
> + }
> + phy_power_on(csi_dev->phy);
> +}
> +
> +int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> + u32 global_int_status, i_sts;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&csi_dev->slock, flags);
> + global_int_status = dw_mipi_csi_read(csi_dev, reg.INTERRUPT);
> +
> + if (global_int_status & csi_int.PHY_FATAL) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PHY_FATAL);
> + dev_err_ratelimited(dev, "int %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, "int %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, "int %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, "int %08X: PHY: %08X\n",
> + reg.INT_PHY, i_sts);
> + }
> +
> + if (global_int_status & csi_int.PKT &&
> + csi_dev->hw_version_major == 1 &&
> + csi_dev->hw_version_minor <= 30) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PKT);
> + dev_err_ratelimited(dev, "int %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, "int %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, "int %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,
> + "int %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,
> + "int %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,
> + "int %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,
> + "int %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, "int %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, "int %08X: ST_ECC_CORRECT: %08X\n",
> + reg.ST_ECC_CORRECT, i_sts);
> + }
> +
> + spin_unlock_irqrestore(&csi_dev->slock, flags);
> +
> + return 1;
any reason for this return 1 ?
I see the code does not use this return value and it looks hardcoded.
> +}
> +
> +void dw_mipi_csi_get_version(struct dw_csi *csi_dev)
> +{
> + u32 hw_version;
> +
> + hw_version = dw_mipi_csi_read(csi_dev, reg.VERSION);
> + csi_dev->hw_version_major = (u8)((hw_version >> 24) - '0');
> + csi_dev->hw_version_minor = (u8)((hw_version >> 16) - '0');
> + csi_dev->hw_version_minor = csi_dev->hw_version_minor * 10;
> + csi_dev->hw_version_minor += (u8)((hw_version >> 8) - '0');
> +}
> +
> +int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> +
> + if (csi_dev->hw_version_major == 1) {
> + if (csi_dev->hw_version_minor == 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) {
> + /*
> + * 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;
any reason why this function returns anything ? unused and only 0 is the
possible return value
> +}
> +
> +void dw_mipi_csi_dump(struct dw_csi *csi_dev)
> +{
> + dw_print(reg.VERSION);
> + dw_print(reg.N_LANES);
> + dw_print(reg.CTRL_RESETN);
> + dw_print(reg.INTERRUPT);
> + dw_print(reg.DATA_IDS_1);
> + dw_print(reg.DATA_IDS_2);
> + dw_print(reg.IPI_MODE);
> + dw_print(reg.IPI_VCID);
> + dw_print(reg.IPI_DATA_TYPE);
> + dw_print(reg.IPI_MEM_FLUSH);
> + dw_print(reg.IPI_HSA_TIME);
> + dw_print(reg.IPI_HBP_TIME);
> + dw_print(reg.IPI_HSD_TIME);
> + dw_print(reg.IPI_HLINE_TIME);
> + dw_print(reg.IPI_SOFTRSTN);
> + dw_print(reg.IPI_ADV_FEATURES);
> + dw_print(reg.IPI_VSA_LINES);
> + dw_print(reg.IPI_VBP_LINES);
> + dw_print(reg.IPI_VFP_LINES);
> + dw_print(reg.IPI_VACTIVE_LINES);
> + dw_print(reg.IPI_DATA_TYPE);
> + dw_print(reg.VERSION);
> + dw_print(reg.IPI_ADV_FEATURES);
> +}
> 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..6df3688
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-mipi-csi.h
> @@ -0,0 +1,287 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver
> + *
> + * 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/module.h>
> +#include <linux/io.h>
> +#include <linux/phy/phy.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/ratelimit.h>
> +#include <linux/reset.h>
> +#include <linux/videodev2.h>
> +#include <linux/wait.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/dwc/dw-mipi-csi-pltfrm.h>
> +
> +/* Advanced features */
> +#define IPI_DT_OVERWRITE BIT(0)
> +#define DATA_TYPE_OVERWRITE(dt) (((dt) & GENMASK(5, 0)) << 8)
> +#define LINE_EVENT_SELECTION(n) ((n) << 16)
> +
> +enum line_event {
> + EVSELAUTO = 0,
> + EVSELPROG = 1,
> +};
> +
> +#define EN_VIDEO BIT(17)
> +#define EN_LINE_START BIT(18)
> +#define EN_NULL BIT(19)
> +#define EN_BLANKING BIT(20)
> +#define EN_EMBEDDED BIT(21)
> +#define IPI_SYNC_EVENT_MODE(n) ((n) << 24)
> +
> +enum sync_event {
> + SYNCEVFSN = 0,
> + SYNCEVFS = 1,
> +};
> +
> +/* 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,
> + CSI_2_RAW16 = 0x2E,
> + CSI_2_RAW20 = 0x2F,
> + USER_DEFINED_1 = 0x30,
> + USER_DEFINED_2 = 0x31,
> + USER_DEFINED_3 = 0x32,
> + USER_DEFINED_4 = 0x33,
> + USER_DEFINED_5 = 0x34,
> + USER_DEFINED_6 = 0x35,
> + USER_DEFINED_7 = 0x36,
> + USER_DEFINED_8 = 0x37,
> +};
These are csi2 standard defines... would be good to have them available
in a more generic header to not redefine them if they are needed elsewhere ?
> +
> +/* DWC MIPI CSI-2 output types */
> +enum output {
> + IPI_OUT = 0,
> + IDI_OUT = 1,
> + BOTH_OUT = 2
> +};
> +
> +/* IPI color components */
> +enum color_mode {
> + COLOR48 = 0,
> + COLOR16 = 1
> +};
> +
> +/* IPI cut through */
> +enum cut_through {
> + CTINACTIVE = 0,
> + CTACTIVE = 1
> +};
> +
> +/* IPI output types */
> +enum ipi_output {
> + CAMERA_TIMING = 0,
> + AUTO_TIMING = 1
> +};
> +
> +/* Format template */
> +struct mipi_fmt {
> + u32 mbus_code;
> + u8 depth;
> +};
> +
> +struct mipi_dt {
> + u32 hex;
> + char *name;
> +};
> +
> +/* CSI specific configuration */
> +struct csi_data {
> + u32 num_lanes;
> + u32 dphy_freq;
> + u32 pclk;
> + u32 fps;
> + u32 bpp;
> + u32 output;
> + u32 ipi_mode;
> + u32 ipi_adv_features;
> + u32 ipi_cut_through;
> + u32 ipi_color_mode;
> + u32 ipi_auto_flush;
> + u32 virtual_ch;
> + u32 hsa;
> + u32 hbp;
> + u32 hsd;
> + u32 htotal;
> + u32 vsa;
> + u32 vbp;
> + u32 vfp;
> + u32 vactive;
> +};
> +
> +/* Structure to embed device driver information */
> +struct dw_csi {
> + struct v4l2_subdev sd;
> + struct video_device vdev;
> + struct v4l2_device v4l2_dev;
> + struct device *dev;
> + struct media_pad pads[CSI_PADS_NUM];
> + struct mipi_fmt *fmt;
> + struct v4l2_mbus_framefmt format;
> + void __iomem *base_address;
> + void __iomem *demo;
> + void __iomem *csc;
> + int ctrl_irq_number;
> + int demosaic_irq;
> + struct csi_data hw;
> + struct reset_control *rst;
> + struct phy *phy;
> + struct dw_csih_pdata *config;
> + struct mutex lock; /* protect resources sharing */
> + spinlock_t slock; /* interrupt handling lock */
> + u8 ipi_dt;
> + u8 index;
> + u8 hw_version_major;
> + u16 hw_version_minor;
> +};
> +
> +static inline struct dw_csi *sd_to_mipi_csi_dev(struct v4l2_subdev *sdev)
> +{
> + return container_of(sdev, struct dw_csi, sd);
> +}
> +
> +void dw_mipi_csi_reset(struct dw_csi *csi_dev);
> +int dw_mipi_csi_mask_irq_power_off(struct dw_csi *csi_dev);
> +int dw_mipi_csi_hw_stdby(struct dw_csi *csi_dev);
> +void dw_mipi_csi_set_ipi_fmt(struct dw_csi *csi_dev);
> +void dw_mipi_csi_start(struct dw_csi *csi_dev);
> +int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev);
> +void dw_mipi_csi_get_version(struct dw_csi *csi_dev);
> +int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev);
> +void dw_mipi_csi_fill_timings(struct dw_csi *dev,
> + struct v4l2_subdev_format *fmt);
> +void dw_mipi_csi_dump(struct dw_csi *csi_dev);
> +
> +#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
> +int dw_csi_create_capabilities_sysfs(struct platform_device *pdev);
> +#endif
> +
> +static inline void dw_mipi_csi_write(struct dw_csi *dev,
> + u32 address, u32 data)
> +{
> + writel(data, dev->base_address + address);
> +}
> +
> +static inline u32 dw_mipi_csi_read(struct dw_csi *dev, u32 address)
> +{
> + return readl(dev->base_address + address);
> +}
> +
> +#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..948db4e
> --- /dev/null
> +++ b/include/media/dwc/dw-mipi-csi-pltfrm.h
> @@ -0,0 +1,104 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host media entities
> + *
> + * 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
> +
> +/* 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)
> +
> +struct pdata_names {
> + char *name;
> +};
> +
> +enum video_dev_pads {
> + VIDEO_DEV_SD_PAD_SINK_VIF1,
> + VIDEO_DEV_SD_PAD_SINK_VIF2,
> + VIDEO_DEV_SD_PAD_SOURCE_DMA,
> + VIDEO_DEV_SD_PADS_NUM,
> +};
> +
> +enum vif_pads {
> + VIF_PAD_SINK_CSI,
> + VIF_PAD_SOURCE_DMA,
> + VIF_PADS_NUM,
> +};
> +
> +enum mipi_csi_pads {
> + CSI_PAD_SINK,
> + CSI_PAD_SOURCE,
> + CSI_PADS_NUM,
> +};
> +
> +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_ */
Would be useful to prefix these with 'dw_' to be sure they do not step
over some other symbols when included ?
>
Hi Sakari,
From: Sakari Ailus <[email protected]>
Date: Tue, Jul 09, 2019 at 19:25:00
> Hi Luis,
>
> On Mon, Jul 08, 2019 at 03:21:50PM +0000, Luis de Oliveira wrote:
> > Hi Sakari,
> >
> > Thank you for your feedback.
> > I have my comments inline.
> >
> > From: Sakari Ailus <[email protected]>
> > Date: Fri, Jun 28, 2019 at 15:13:26
> >
> > > Hi Luis,
> > >
> > > Thank you for the patchset.
> > >
> > > On Tue, Jun 11, 2019 at 09:20:50PM +0200, Luis Oliveira wrote:
> > > > From: Luis Oliveira <[email protected]>
> > > >
> > > > Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> > > >
> > > > Signed-off-by: Luis Oliveira <[email protected]>
> > > > ---
> > > > Changelog
> > > > v3-v4
> > > > - remove "plat" from the block name @rob @laurent
> > > > - remove "phy-names" when single-entry @rob
> > > > - remove "snps,output-type" -> went to the driver config @laurent
> > > >
> > > > .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> > > > 1 file changed, 41 insertions(+)
> > > > create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > >
> > > > diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > new file mode 100644
> > > > index 0000000..613b7f9
> > > > --- /dev/null
> > > > +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > @@ -0,0 +1,41 @@
> > > > +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"
> > > > +- reg : physical base address and size of the device memory
> > > > + mapped registers;
> > > > +- interrupts : DW CSI-2 Host interrupts
> > > > +- 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)
> > > > +
> > > > +The per-board settings:
> > > > + - port sub-node describing a single endpoint connected to the camera as
> > > > + described in video-interfaces.txt[1].
> > >
> > > Which endpoint properties in video-interfaces.txt are relevant for the
> > > hardware? Which values may they have?
> > >
> >
> > Currently I'm using only two properties "data-lanes" and "bus-width", but
> > I have plans to add blanking info also.
> > I will add more info.
>
> Isn't blanking defined by what the transmitter seneds? Or do you have
> hardware limitations on the receiver side?
>
When we use this IP in prototyping we configure blanking at the receiver
side.
Some cameras don't have blanking configuration capabilities so we
configure it on the RX side.
> I've only heard of one such case before, and it was a very old parallel
> receiver.
>
> If you have a CSI-2 receiver, bus-width isn't relevant --- it's for paralle
> interfaces only. Please add data-lanes to required endpoint properties.
>
I used bus-width property in the Synopsys IPI (Image Pixel Interface)
that enables direct video stream access.
This interface is an output that can be 16-bit or 48-bit, that's why I
used bus-width property.
> --
> Regards,
>
> Sakari Ailus
Thank you,
Luis
Hi Eugen,
From: [email protected] <[email protected]>
Date: Wed, Jul 10, 2019 at 07:53:02
>
>
> On 09.07.2019 20:08, Luis de Oliveira wrote:
>
> >
> > Hi Eugen,
> >
> >
> > From: [email protected] <[email protected]>
> > Date: Tue, Jul 09, 2019 at 15:33:50
> >
> >>
> >>
> >> On 11.06.2019 22:20, Luis Oliveira wrote:
> >>> From: Luis Oliveira <[email protected]>
> >>>
> >>> Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> >>>
> >>> Signed-off-by: Luis Oliveira <[email protected]>
> >>> ---
> >>> Changelog
> >>> v3-v4
> >>> - remove "plat" from the block name @rob @laurent
> >>> - remove "phy-names" when single-entry @rob
> >>> - remove "snps,output-type" -> went to the driver config @laurent
> >>>
> >>> .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> >>> 1 file changed, 41 insertions(+)
> >>> create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> >>>
> >>> diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> >>> new file mode 100644
> >>> index 0000000..613b7f9
> >>> --- /dev/null
> >>> +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> >>> @@ -0,0 +1,41 @@
> >>> +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"
> >>> +- reg : physical base address and size of the device memory
> >>> + mapped registers;
> >>> +- interrupts : DW CSI-2 Host interrupts
> >>> +- 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)
> >>> +
> >>> +The per-board settings:
> >>> + - port sub-node describing a single endpoint connected to the camera as
> >>> + described in video-interfaces.txt[1].
> >>> +
> >>> +Example:
> >>> +
> >>> + csi2: csi2@3000 {
> >>> + compatible = "snps,dw-csi";
> >>> + #address-cells = <1>;
> >>> + #size-cells = <0>;
> >>> + reg = < 0x03000 0x7FF>;
> >>> + phys = <&mipi_dphy_rx>;
> >>> + resets = <&dw_rst 1>;
> >>> + interrupts = <2>;
> >>> +
> >>> + port@0 {
> >>> + reg = <0>;
> >>> + csi_ep1: endpoint {
> >>> + remote-endpoint = <&camera_1>;
> >>> + data-lanes = <1 2>;
> >>> + };
> >>
> >> Hello Luis,
> >>
> >> Which is the output port (endpoint) : how to connect the output of
> >> csi2host to another node ?
> >> I mean, the second port of this block, or, how is the data taken from
> >> csi2host ?
> >>
> >
> > I understand your question, I think you guessed this is not the complete
> > pipeline (I have a top driver that interacts with this one).
> > I was not planning to submit it, do you think I should?
>
> Yes please, you can have the patch with subject DO NOT MERGE if you do
> not want that patch to be included in the kernel and just for reference.
> but it would help me in understanding your setup
>
> Thanks !
>
Ok, thank you. I will included it next.
> >
> > The behavior is very similar with this one
> > ./drivers/media/platform/exynos4-is/media-dev.c
> >
> >
> >> Thanks,
> >>
> >> Eugen
> >>
> >>> + };
> >>> + };
> >>>
> >
> > Thanks,
> > Luis
> >
On Tue, Jul 9, 2019 at 10:29 AM Luis de Oliveira
<[email protected]> wrote:
>
> From: Rob Herring <[email protected]>
> Date: Tue, Jul 09, 2019 at 15:20:00
>
> > On Tue, Jun 11, 2019 at 09:20:53PM +0200, 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
> > > v3-v4
> > > - @Laurent I know I told you I could remove the snps,dphy-frequency on V3 but
> > > it is really useful for me here. I removed all other the proprietary
> > > properties except this one. Do you still think it must be removed?
> > > - Frequency units @Rob
> >
> > Frequency units means append '-khz' to the property name. That also
> > makes the 'frequency' part redundant, so maybe name it more around what
> > the frequency is. The frequency for what?
> >
> > Rob
> >
>
> This is the D-PHY high speed frequency configuration that corresponds to
> the high speed data transfer of the bus.
> Basically is the transmission speed per lane.
> Do you think I can use "link-frequencies" (present in
> video-interfaces.txt) with one frequency?
Seems reasonable.
Rob
Hi Eugen,
My answers,
From: [email protected] <[email protected]>
Date: Wed, Jul 10, 2019 at 09:59:13
> Hello Luis,
>
> A few questions inline:
>
> On 11.06.2019 22:20, Luis Oliveira wrote:
> > Add the Synopsys MIPI CSI-2 controller driver. This
> > controller driver is divided in platform functions and core functions.
> > This way it serves as platform for future DesignWare drivers.
> >
> > Signed-off-by: Luis Oliveira <[email protected]>
> > ---
> > Changelog
> > v3-v4
> > - fix v4l2_fwnode_endpoint bad initialization @eugen
> > - removed extra lines and fixed coding style issues
> >
> > MAINTAINERS | 8 +
> > drivers/media/platform/Kconfig | 1 +
> > drivers/media/platform/Makefile | 2 +
> > drivers/media/platform/dwc/Kconfig | 19 +
> > drivers/media/platform/dwc/Makefile | 9 +
> > drivers/media/platform/dwc/dw-csi-plat.c | 475 +++++++++++++++++++++++
> > drivers/media/platform/dwc/dw-csi-plat.h | 97 +++++
> > drivers/media/platform/dwc/dw-csi-sysfs.c | 624 ++++++++++++++++++++++++++++++
> > drivers/media/platform/dwc/dw-mipi-csi.c | 569 +++++++++++++++++++++++++++
> > drivers/media/platform/dwc/dw-mipi-csi.h | 287 ++++++++++++++
> > include/media/dwc/dw-mipi-csi-pltfrm.h | 104 +++++
> > 11 files changed, 2195 insertions(+)
> > 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-csi-sysfs.c
> > 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 16a97ba..6ffe4fd 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -15187,6 +15187,14 @@ S: Maintained
> > F: drivers/dma/dwi-axi-dmac/
> > F: Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.txt
> >
> > +SYNOPSYS DESIGNWARE MIPI DPHY CSI-2 HOST DRIVER
> > +M: Luis Oliveira <[email protected]>
> > +L: [email protected]
> > +T: git git://linuxtv.org/media_tree.git
> > +S: Maintained
> > +F: drivers/media/platform/dwc
> > +F: Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > +
> > SYNOPSYS DESIGNWARE DMAC DRIVER
> > M: Viresh Kumar <[email protected]>
> > R: Andy Shevchenko <[email protected]>
> > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> > index 8a19654..b6fb139 100644
> > --- a/drivers/media/platform/Kconfig
> > +++ b/drivers/media/platform/Kconfig
> > @@ -147,6 +147,7 @@ source "drivers/media/platform/xilinx/Kconfig"
> > source "drivers/media/platform/rcar-vin/Kconfig"
> > source "drivers/media/platform/atmel/Kconfig"
> > source "drivers/media/platform/sunxi/sun6i-csi/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 7cbbd92..4807caf 100644
> > --- a/drivers/media/platform/Makefile
> > +++ b/drivers/media/platform/Makefile
> > @@ -101,3 +101,5 @@ obj-y += meson/
> > obj-y += cros-ec-cec/
> >
> > obj-$(CONFIG_VIDEO_SUN6I_CSI) += sunxi/sun6i-csi/
> > +
> > +obj-y += dwc/
> > diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
> > new file mode 100644
> > index 0000000..508ac21
> > --- /dev/null
> > +++ b/drivers/media/platform/dwc/Kconfig
> > @@ -0,0 +1,19 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +#
> > +# Synopsys DWC Platform drivers
> > +# Drivers here are currently for MIPI CSI-2 support
> > +
> > +config DWC_MIPI_CSI2_HOST
> > + tristate "Synopsys DesignWare CSI-2 Host Controller support"
> > + select VIDEO_DEV
> > + select VIDEO_V4L2
> > + select VIDEO_V4L2_SUBDEV_API
> > + select V4L2_FWNODE
> > + help
> > + This selects the DesignWare MIPI CSI-2 host controller support. This
> > + controller gives access to control a CSI-2 receiver acting as a V4L2
> > + subdevice.
> > +
> > + If you have a controller with this interface, say Y.
> > +
> > + If unsure, say N.
> > diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
> > new file mode 100644
> > index 0000000..057f137
> > --- /dev/null
> > +++ b/drivers/media/platform/dwc/Makefile
> > @@ -0,0 +1,9 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +#
> > +# Makefile for Synopsys DWC Platform drivers
> > +#
> > +dw-csi-objs := dw-csi-plat.o dw-mipi-csi.o
> > +ifeq ($(CONFIG_DWC_MIPI_TC_DPHY_GEN3),y)
> > + dw-csi-objs += dw-csi-sysfs.o
> > +endif
> > +obj-$(CONFIG_DWC_MIPI_CSI2_HOST) += dw-csi.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..9828d55
> > --- /dev/null
> > +++ b/drivers/media/platform/dwc/dw-csi-plat.c
> > @@ -0,0 +1,475 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> > + *
> > + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> > + * Platform driver
> > + *
> > + * Author: Luis Oliveira <[email protected]>
> > + */
> > +
> > +#include <media/dwc/dw-dphy-data.h>
> > +
> > +#include "dw-csi-plat.h"
> > +
> > +const struct mipi_dt csi_dt[] = {
> > + {
> > + .hex = CSI_2_YUV420_8,
> > + .name = "YUV420_8bits",
> > + }, {
> > + .hex = CSI_2_YUV420_10,
> > + .name = "YUV420_10bits",
> > + }, {
> > + .hex = CSI_2_YUV420_8_LEG,
> > + .name = "YUV420_8bits_LEGACY",
> > + }, {
> > + .hex = CSI_2_YUV420_8_SHIFT,
> > + .name = "YUV420_8bits_SHIFT",
> > + }, {
> > + .hex = CSI_2_YUV420_10_SHIFT,
> > + .name = "YUV420_10bits_SHIFT",
> > + }, {
> > + .hex = CSI_2_YUV422_8,
> > + .name = "YUV442_8bits",
> > + }, {
> > + .hex = CSI_2_YUV422_10,
> > + .name = "YUV442_10bits",
> > + }, {
> > + .hex = CSI_2_RGB444,
> > + .name = "RGB444",
> > + }, {
> > + .hex = CSI_2_RGB555,
> > + .name = "RGB555",
> > + }, {
> > + .hex = CSI_2_RGB565,
> > + .name = "RGB565",
> > + }, {
> > + .hex = CSI_2_RGB666,
> > + .name = "RGB666",
> > + }, {
> > + .hex = CSI_2_RGB888,
> > + .name = "RGB888",
> > + }, {
> > + .hex = CSI_2_RAW6,
> > + .name = "RAW6",
> > + }, {
> > + .hex = CSI_2_RAW7,
> > + .name = "RAW7",
> > + }, {
> > + .hex = CSI_2_RAW8,
> > + .name = "RAW8",
> > + }, {
> > + .hex = CSI_2_RAW10,
> > + .name = "RAW10",
> > + }, {
> > + .hex = CSI_2_RAW12,
> > + .name = "RAW12",
> > + }, {
> > + .hex = CSI_2_RAW14,
> > + .name = "RAW14",
> > + }, {
> > + .hex = CSI_2_RAW16,
> > + .name = "RAW16",
> > + },
> > +};
> > +
> > +static 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].mbus_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;
> > +
> > + if (code->index != 0)
> > + return -EINVAL;
>
> What is the purpose of checking if index >= array_size, if any value
> different from 0 will return an error anyway ?
>
You are right, I added this !=0 later and forgot to remove the >=
array_size.
> > +
> > + code->code = dw_mipi_csi_formats[code->index].mbus_code;
> > + return 0;
> > +}
> > +
> > +static struct mipi_fmt *
> > +dw_mipi_csi_try_format(struct v4l2_mbus_framefmt *mf)
> > +{
> > + struct mipi_fmt *fmt;
> > +
> > + fmt = find_dw_mipi_csi_format(mf);
> > + if (!fmt)
> > + fmt = &dw_mipi_csi_formats[0];
> > +
> > + mf->code = fmt->mbus_code;
> > +
> > + return fmt;
> > +}
> > +
> > +static struct v4l2_mbus_framefmt *
> > +dw_mipi_csi_get_format(struct dw_csi *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;
> > + dev_dbg(dev->dev,
> > + "%s got v4l2_mbus_pixelcode. 0x%x\n", __func__,
> > + dev->format.code);
> > + dev_dbg(dev->dev,
> > + "%s got width. 0x%x\n", __func__,
> > + dev->format.width);
> > + dev_dbg(dev->dev,
> > + "%s got height. 0x%x\n", __func__,
> > + dev->format.height);
> > + 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 dw_csi *dev = sd_to_mipi_csi_dev(sd);
> > + struct mipi_fmt *dev_fmt;
> > + struct v4l2_mbus_framefmt *mf = dw_mipi_csi_get_format(dev, cfg,
> > + fmt->which);
> > + int i;
> > +
> > + 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;
> > + dev->fmt->mbus_code = mf->code;
> > + dw_mipi_csi_set_ipi_fmt(dev);
> > + }
> > +
> > + if (fmt->format.width > 0 && fmt->format.height > 0) {
> > + dw_mipi_csi_fill_timings(dev, fmt);
> > + } else {
> > + dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
> > + __func__, fmt->format.width);
> > + dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
> > + __func__, fmt->format.height);
> > + return -EINVAL;
> > + }
> > +
> > + for (i = 0; i < ARRAY_SIZE(csi_dt); i++)
> > + if (csi_dt[i].hex == dev->ipi_dt) {
> > + dev_vdbg(dev->dev, "Using data type %s\n",
> > + csi_dt[i].name);
> > + }
> > + 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 dw_csi *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 dw_csi *dev = sd_to_mipi_csi_dev(sd);
> > +
> > + dev_vdbg(dev->dev, "%s: on=%d\n", __func__, on);
> > +
> > + 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);
> > + /* reset data type */
> > + dev->ipi_dt = 0x0;
> > + }
> > + return 0;
> > +}
> > +
> > +static int
> > +dw_mipi_csi_log_status(struct v4l2_subdev *sd)
> > +{
> > + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> > +
> > + dw_mipi_csi_dump(dev);
> > +
> > + return 0;
> > +}
> > +
> > +#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
> > +static int
> > +dw_mipi_csi_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
> > +{
> > + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> > +
> > + dev_vdbg(dev->dev, "%s: reg=%llu\n", __func__, reg->reg);
> > + reg->val = dw_mipi_csi_read(dev, reg->reg);
> > +
> > + return 0;
> > +}
> > +#endif
> > +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 = MEDIA_BUS_FMT_RGB888_1X24;
> > + 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,
> > + .log_status = dw_mipi_csi_log_status,
> > +#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
> > + .g_register = dw_mipi_csi_g_register,
> > +#endif
> > +};
> > +
> > +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)
>
> This naming 'irq1' is pretty unfortunate, do you agree ?
>
Yep, I will remove the 1.
> > +{
> > + struct dw_csi *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 dw_csi *dev)
> > +{
> > + struct device_node *node = pdev->dev.of_node;
> > + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> > + int ret = 0;
> > +
> > + if (of_property_read_u32(node, "snps,output-type",
> > + &dev->hw.output))
> > + dev->hw.output = 2;
> > +
> > + node = of_graph_get_next_endpoint(node, NULL);
> > + if (!node) {
> > + dev_err(&pdev->dev, "No port node at %pOF\n",
> > + pdev->dev.of_node);
> > + return -EINVAL;
> > + }
> > + /* Get port node and validate MIPI-CSI channel id. */
> > + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &ep);
> > + if (ret)
> > + goto err;
> > +
> > + dev->index = ep.base.port - 1;
> > + if (dev->index >= CSI_MAX_ENTITIES) {
> > + ret = -ENXIO;
> > + goto err;
> > + }
> > + dev->hw.num_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > +err:
> > + of_node_put(node);
> > + return ret;
> > +}
> > +
> > +static const struct of_device_id dw_mipi_csi_of_match[];
> > +
> > +static int dw_csi_probe(struct platform_device *pdev)
> > +{
> > + const struct of_device_id *of_id = NULL;
> > + struct device *dev = &pdev->dev;
> > + struct resource *res = NULL;
> > + struct dw_csi *csi;
> > + struct v4l2_subdev *sd;
> > + int ret;
> > +
> > + dev_vdbg(dev, "Probing started\n");
> > +
> > + /* Resource allocation */
> > + csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
> > + if (!csi)
> > + return -ENOMEM;
> > +
> > + mutex_init(&csi->lock);
> > + spin_lock_init(&csi->slock);
> > + csi->dev = dev;
> > +
> > + of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
> > + if (!of_id)
> > + return -EINVAL;
> > +
> > + ret = dw_mipi_csi_parse_dt(pdev, csi);
> > + if (ret < 0)
> > + return ret;
> > +
> > + csi->phy = devm_of_phy_get(dev, dev->of_node, NULL);
> > + if (IS_ERR(csi->phy)) {
> > + dev_err(dev, "No DPHY available\n");
> > + return PTR_ERR(csi->phy);
> > + }
> > + /* Registers mapping */
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (!res)
> > + return -ENXIO;
> > +
> > + csi->base_address = devm_ioremap_resource(dev, res);
> > + if (IS_ERR(csi->base_address)) {
> > + dev_err(dev, "Base address not set.\n");
> > + return PTR_ERR(csi->base_address);
> > + }
> > +
> > + csi->ctrl_irq_number = platform_get_irq(pdev, 0);
> > + if (csi->ctrl_irq_number < 0) {
> > + dev_err(dev, "irq number %d not set.\n", csi->ctrl_irq_number);
> > + ret = csi->ctrl_irq_number;
> > + goto end;
> > + }
> > +
> > + csi->rst = devm_reset_control_get_optional_shared(dev, NULL);
> > + if (IS_ERR(csi->rst)) {
> > + dev_err(dev, "error getting reset control %d\n", ret);
>
> this is optional ? in case it does not exist, there is no error ?
>
Let me see if I understand. Should I do like this?
ret = PTR_ERR(csi->rst);
goto error_label;
> > + return PTR_ERR(csi->rst);
> > + }
> > +
> > + ret = devm_request_irq(dev, csi->ctrl_irq_number,
> > + dw_mipi_csi_irq1, IRQF_SHARED,
> > + dev_name(dev), csi);
> > + if (ret) {
> > + dev_err(dev, "irq csi %s failed\n", of_id->name);
> > +
> > + goto end;
>
> if this fails and you go to end, media entity cleanup is called, but it
> was never initialized ? does it matter ?
I think maybe I can use another label that just returns.
>
> > + }
> > +
> > + sd = &csi->sd;
> > + v4l2_subdev_init(sd, &dw_mipi_csi_subdev_ops);
> > + csi->sd.owner = THIS_MODULE;
> > +
> > + snprintf(sd->name, sizeof(sd->name), "%s.%d",
> > + "dw-csi", csi->index);
> > +
> > + csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > + csi->fmt = &dw_mipi_csi_formats[0];
> > + csi->format.code = dw_mipi_csi_formats[0].mbus_code;
> > +
> > + sd->entity.function = MEDIA_ENT_F_IO_V4L;
> > + csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> > + csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> > + ret = media_entity_pads_init(&csi->sd.entity,
> > + CSI_PADS_NUM, csi->pads);
> > +
> > + dev_vdbg(dev, "v4l2.name: %s\n", csi->v4l2_dev.name);
> > +
> > + if (ret < 0) {
> > + dev_err(dev, "media entity init failed\n");
> > + goto end;
> > + }
> > +
> > + v4l2_set_subdevdata(&csi->sd, pdev);
> > + platform_set_drvdata(pdev, &csi->sd);
> > + dev_set_drvdata(dev, sd);
> > +
> > + if (csi->rst)
> > + reset_control_deassert(csi->rst);
> > +#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
> > + dw_csi_create_capabilities_sysfs(pdev);
> > +#endif
> > + dw_mipi_csi_get_version(csi);
> > + dw_mipi_csi_specific_mappings(csi);
> > + dw_mipi_csi_mask_irq_power_off(csi);
> > +
> > + dev_info(dev, "DW MIPI CSI-2 Host registered successfully HW v%u.%u\n",
> > + csi->hw_version_major, csi->hw_version_minor);
> > +
> > + phy_init(csi->phy);
> > +
> > + return 0;
> > +
> > +end:
> > + media_entity_cleanup(&csi->sd.entity);
> > + v4l2_device_unregister(csi->vdev.v4l2_dev);
> > +
> > + return ret;
> > +}
> > +
> > +/**
> > + * @short Exit routine - Exit point of the driver
> > + * @param[in] pdev pointer to the platform device structure
> > + * @return 0 on success
> > + */
> > +static int dw_csi_remove(struct platform_device *pdev)
> > +{
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *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" },
> > + {},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, dw_mipi_csi_of_match);
> > +
> > +/**
> > + * @short Platform driver structure
> > + */
> > +static struct platform_driver dw_mipi_csi_driver = {
> > + .remove = dw_csi_remove,
> > + .probe = dw_csi_probe,
> > + .driver = {
> > + .name = "dw-csi",
> > + .owner = THIS_MODULE,
> > + .of_match_table = of_match_ptr(dw_mipi_csi_of_match),
> > + },
> > +};
> > +
> > +module_platform_driver(dw_mipi_csi_driver);
> > +
> > +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..e322592
> > --- /dev/null
> > +++ b/drivers/media/platform/dwc/dw-csi-plat.h
> > @@ -0,0 +1,97 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2018 Synopsys, Inc.
> > + *
> > + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> > + * Supported bus formats
> > + *
> > + * Author: Luis Oliveira <[email protected]>
> > + */
> > +
> > +#ifndef _DW_CSI_PLAT_H__
> > +#define _DW_CSI_PLAT_H__
> > +
> > +#include "dw-mipi-csi.h"
> > +
> > +/* Video formats supported by the MIPI CSI-2 */
> > +struct mipi_fmt dw_mipi_csi_formats[] = {
> > + {
> > + /* RAW 8 */
> > + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> > + .depth = 8,
> > + }, {
> > + /* RAW 10 */
> > + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> > + .depth = 10,
> > + }, {
> > + /* RAW 12 */
> > + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> > + .depth = 12,
> > + }, {
> > + /* RAW 14 */
> > + .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14,
> > + .depth = 14,
> > + }, {
> > + /* RAW 16 */
> > + .mbus_code = MEDIA_BUS_FMT_SBGGR16_1X16,
> > + .depth = 16,
> > + }, {
> > + /* RGB 666 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB666_1X18,
> > + .depth = 18,
> > + }, {
> > + /* RGB 565 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE,
> > + .depth = 16,
> > + }, {
> > + /* BGR 565 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE,
> > + .depth = 16,
> > + }, {
> > + /* RGB 555 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
> > + .depth = 16,
> > + }, {
> > + /* BGR 555 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
> > + .depth = 16,
> > + }, {
> > + /* RGB 444 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE,
> > + .depth = 16,
> > + }, {
> > + /* RGB 444 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE,
> > + .depth = 16,
> > + }, {
> > + /* RGB 888 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_LE,
> > + .depth = 24,
> > + }, {
> > + /* BGR 888 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_BE,
> > + .depth = 24,
> > + }, {
> > + /* BGR 888 */
> > + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
> > + .depth = 24,
> > + }, {
> > + /* YUV 422 8-bit */
> > + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
> > + .depth = 16,
> > + }, {
> > + /* YUV 422 10-bit */
> > + .mbus_code = MEDIA_BUS_FMT_VYUY10_2X10,
> > + .depth = 20,
> > + }, {
> > + /* YUV 420 8-bit LEGACY */
> > + .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> > + .depth = 8,
> > + }, {
> > + /* YUV 420 10-bit */
> > + .mbus_code = MEDIA_BUS_FMT_Y10_1X10,
> > + .depth = 10,
> > + },
> > +};
> > +
> > +#endif /* _DW_CSI_PLAT_H__ */
> > diff --git a/drivers/media/platform/dwc/dw-csi-sysfs.c b/drivers/media/platform/dwc/dw-csi-sysfs.c
> > new file mode 100644
> > index 0000000..e8d2bb9
> > --- /dev/null
> > +++ b/drivers/media/platform/dwc/dw-csi-sysfs.c
> > @@ -0,0 +1,624 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> > + *
> > + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> > + * SysFS components for the platform driver
> > + *
> > + * Author: Luis Oliveira <[email protected]>
> > + */
> > +
> > +#include "dw-mipi-csi.h"
> > +
> > +static ssize_t core_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 dw_csi *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 dw_csi *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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%d\n", csi_dev->hw.num_lanes);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t core_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 dw_csi *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 data_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 16, &dt);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (dt < 0x18 || dt > 0x2F) {
>
> These aren't some top/start defines in your list ?
>
Yes, good point.
> > + dev_err(dev, "Invalid data type %lx\n", dt);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "Data type 0x%lx\n", dt);
> > + csi_dev->ipi_dt = dt;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t data_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%x\n", csi_dev->ipi_dt);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t hsa_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long hsa;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 16, &hsa);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (hsa > 0xFFF) {
> > + dev_err(dev, "Invalid HSA time %lx\n", hsa);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "HSA time 0x%lx\n", hsa);
> > + csi_dev->hw.hsa = hsa;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t hsa_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%x\n", csi_dev->hw.hsa);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t hbp_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long hbp;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 16, &hbp);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (hbp > 0xFFF) {
> > + dev_err(dev, "Invalid HBP time %lx\n", hbp);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "HBP time 0x%lx\n", hbp);
> > + csi_dev->hw.hbp = hbp;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t hbp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%x\n", csi_dev->hw.hbp);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t hsd_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long hsd;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 16, &hsd);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (hsd > 0xFF) {
> > + dev_err(dev, "Invalid HSD time %lx\n", hsd);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "HSD time 0x%lx\n", hsd);
> > + csi_dev->hw.hsd = hsd;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t hsd_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%x\n", csi_dev->hw.hsd);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t vsa_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long vsa;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 16, &vsa);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (vsa > 0x3FF) {
> > + dev_err(dev, "Invalid VSA period %lx\n", vsa);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "VSA period 0x%lx\n", vsa);
> > + csi_dev->hw.vsa = vsa;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t vsa_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%x\n", csi_dev->hw.vsa);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t vbp_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long vbp;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 16, &vbp);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (vbp > 0x2FF) {
> > + dev_err(dev, "Invalid VBP period %lx\n", vbp);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "VBP period 0x%lx\n", vbp);
> > + csi_dev->hw.vbp = vbp;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t vbp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%x\n", csi_dev->hw.vbp);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t vfp_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long vfp;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 16, &vfp);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (vfp > 0x3ff) {
> > + dev_err(dev, "Invalid VFP period %lx\n", vfp);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "VFP period 0x%lx\n", vfp);
> > + csi_dev->hw.vfp = vfp;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t vfp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%x\n", csi_dev->hw.vfp);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t virtual_channel_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long virtual_ch;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 10, &virtual_ch);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if ((signed int)virtual_ch < 0 || (signed int)virtual_ch > 8) {
> > + dev_err(dev, "Invalid Virtual Channel %lu\n", virtual_ch);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "Virtual Channel %lu\n", virtual_ch);
> > + csi_dev->hw.virtual_ch = virtual_ch;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t virtual_channel_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%d\n", csi_dev->hw.virtual_ch);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t ipi_color_mode_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long ipi_color_mode;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 10, &ipi_color_mode);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if ((signed int)ipi_color_mode < 0 || (signed int)ipi_color_mode > 1) {
> > + dev_err(dev,
> > + "Wrong Color Mode %lu, (48 bits -> 0 or 16 bits -> 1\n",
> > + ipi_color_mode);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "IPI Color mode %lu\n", ipi_color_mode);
> > + csi_dev->hw.ipi_color_mode = ipi_color_mode;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t ipi_color_mode_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_color_mode);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t ipi_auto_flush_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long ipi_auto_flush;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 10, &ipi_auto_flush);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if ((signed int)ipi_auto_flush < 0 || (signed int)ipi_auto_flush > 1) {
> > + dev_err(dev,
> > + "Invalid Auto Flush Mode %lu, (No -> 0 or Yes -> 1\n",
> > + ipi_auto_flush);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "IPI Auto Flush %lu\n", ipi_auto_flush);
> > + csi_dev->hw.ipi_auto_flush = ipi_auto_flush;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t ipi_auto_flush_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_auto_flush);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t ipi_timings_mode_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long ipi_mode;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 10, &ipi_mode);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if ((signed int)ipi_mode < 0 || (signed int)ipi_mode > 1) {
> > + dev_err(dev,
> > + "Invalid Timing Source %lu (Camera:0|Controller:1)\n",
> > + ipi_mode);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "IPI Color mode %lu\n", ipi_mode);
> > + csi_dev->hw.ipi_mode = ipi_mode;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t ipi_timings_mode_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_mode);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static ssize_t output_type_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long output;
> > +
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 10, &output);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if ((signed int)output < 0 || (signed int)output > 1) {
> > + dev_err(dev,
> > + "Invalid Core output %lu to be used \
> > + (IPI-> 0 or IDI->1 or BOTH- 2\n",
> > + output);
> > + return count;
> > + }
> > +
> > + dev_info(dev, "IPI Color mode %lu\n", output);
> > + csi_dev->hw.output = output;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t output_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + char buffer[10];
> > +
> > + snprintf(buffer, 10, "%d\n", csi_dev->hw.output);
> > +
> > + return strlcpy(buf, buffer, PAGE_SIZE);
> > +}
> > +
> > +static DEVICE_ATTR_RO(core_version);
> > +static DEVICE_ATTR_RO(core_reset);
> > +static DEVICE_ATTR_RW(n_lanes);
> > +static DEVICE_ATTR_RW(data_type);
> > +static DEVICE_ATTR_RW(hsa);
> > +static DEVICE_ATTR_RW(hbp);
> > +static DEVICE_ATTR_RW(hsd);
> > +static DEVICE_ATTR_RW(vsa);
> > +static DEVICE_ATTR_RW(vbp);
> > +static DEVICE_ATTR_RW(vfp);
> > +static DEVICE_ATTR_RW(virtual_channel);
> > +static DEVICE_ATTR_RW(ipi_color_mode);
> > +static DEVICE_ATTR_RW(ipi_auto_flush);
> > +static DEVICE_ATTR_RW(ipi_timings_mode);
> > +static DEVICE_ATTR_RW(output_type);
> > +
> > +int dw_csi_create_capabilities_sysfs(struct platform_device *pdev)
> > +{
> > + device_create_file(&pdev->dev, &dev_attr_core_version);
> > + device_create_file(&pdev->dev, &dev_attr_core_reset);
> > + device_create_file(&pdev->dev, &dev_attr_n_lanes);
> > + device_create_file(&pdev->dev, &dev_attr_data_type);
> > + device_create_file(&pdev->dev, &dev_attr_hsa);
> > + device_create_file(&pdev->dev, &dev_attr_hbp);
> > + device_create_file(&pdev->dev, &dev_attr_hsd);
> > + device_create_file(&pdev->dev, &dev_attr_vsa);
> > + device_create_file(&pdev->dev, &dev_attr_vbp);
> > + device_create_file(&pdev->dev, &dev_attr_vfp);
> > + device_create_file(&pdev->dev, &dev_attr_virtual_channel);
> > + device_create_file(&pdev->dev, &dev_attr_ipi_color_mode);
> > + device_create_file(&pdev->dev, &dev_attr_ipi_auto_flush);
> > + device_create_file(&pdev->dev, &dev_attr_ipi_timings_mode);
> > + device_create_file(&pdev->dev, &dev_attr_output_type);
> > +
> > + return 0;
> > +}
> > 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..d01418d
> > --- /dev/null
> > +++ b/drivers/media/platform/dwc/dw-mipi-csi.c
> > @@ -0,0 +1,569 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> > + *
> > + * Synopsys DesignWare MIPI CSI-2 Host controller driver
> > + * Core MIPI CSI-2 functions
> > + *
> > + * Author: 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),
> > +};
> > +
> > +#define dw_print(VAR) \
> > + dev_info(csi_dev->dev, "%s: 0x%x: %X\n", "#VAR#",\
> > + VAR, dw_mipi_csi_read(csi_dev, VAR))
> > +
> > +void dw_mipi_csi_write_part(struct dw_csi *dev, u32 address, u32 data,
> > + u8 shift, u8 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 dw_csi *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 dw_csi *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_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 dw_csi *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,
> > + GENMASK(31, 0));
> > +
> > + /* common */
> > + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY_FATAL,
> > + GENMASK(8, 0));
> > + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT_FATAL,
> > + GENMASK(1, 0));
> > + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY, GENMASK(23, 0));
> > + dw_mipi_csi_write(csi_dev, reg.MASK_INT_LINE, GENMASK(23, 0));
> > + dw_mipi_csi_write(csi_dev, reg.MASK_INT_IPI, GENMASK(5, 0));
> > +
> > + /* only for version 1.40 */
> > + if (csi_dev->hw_version_minor == 40) {
> > + dw_mipi_csi_write(csi_dev,
> > + reg.MSK_BNDRY_FRAME_FATAL,
> > + GENMASK(31, 0));
> > + dw_mipi_csi_write(csi_dev,
> > + reg.MSK_SEQ_FRAME_FATAL,
> > + GENMASK(31, 0));
> > + dw_mipi_csi_write(csi_dev,
> > + reg.MSK_CRC_FRAME_FATAL,
> > + GENMASK(31, 0));
> > + dw_mipi_csi_write(csi_dev,
> > + reg.MSK_PLD_CRC_FATAL,
> > + GENMASK(31, 0));
> > + dw_mipi_csi_write(csi_dev,
> > + reg.MSK_DATA_ID, GENMASK(31, 0));
> > + dw_mipi_csi_write(csi_dev,
> > + reg.MSK_ECC_CORRECT, GENMASK(31, 0));
> > + }
> > + }
> > + return 0;
> > +}
> > +
> > +void dw_mipi_csi_set_ipi_fmt(struct dw_csi *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);
> > + switch (csi_dev->ipi_dt) {
> > + case CSI_2_YUV420_8:
> > + case CSI_2_YUV420_8_LEG:
> > + case CSI_2_YUV420_8_SHIFT:
> > + break;
> > + case CSI_2_YUV420_10:
> > + case CSI_2_YUV420_10_SHIFT:
> > + break;
> > + }
>
> any reason for this switch instruction? does basically nothing.
>
Oversight. I had prints here that were removed, now it is just silly.
> > + } else {
> > + switch (csi_dev->fmt->mbus_code) {
> > + /* RGB 666 */
> > + case MEDIA_BUS_FMT_RGB666_1X18:
> > + csi_dev->ipi_dt = CSI_2_RGB666;
> > + break;
> > + /* RGB 565 */
> > + case MEDIA_BUS_FMT_RGB565_2X8_BE:
> > + case MEDIA_BUS_FMT_RGB565_2X8_LE:
> > + csi_dev->ipi_dt = CSI_2_RGB565;
> > + break;
> > + /* RGB 555 */
> > + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
> > + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
> > + csi_dev->ipi_dt = CSI_2_RGB555;
> > + break;
> > + /* RGB 444 */
> > + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
> > + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
> > + csi_dev->ipi_dt = CSI_2_RGB444;
> > + break;
> > + /* RGB 888 */
> > + break;
> > + case MEDIA_BUS_FMT_RGB888_2X12_LE:
> > + case MEDIA_BUS_FMT_RGB888_2X12_BE:
> > + csi_dev->ipi_dt = CSI_2_RGB888;
> > + break;
> > + /* RAW 10 */
> > + case MEDIA_BUS_FMT_SBGGR10_1X10:
> > + case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
> > + csi_dev->ipi_dt = CSI_2_RAW10;
> > + break;
> > + /* RAW 12 */
> > + case MEDIA_BUS_FMT_SBGGR12_1X12:
> > + csi_dev->ipi_dt = CSI_2_RAW12;
> > + break;
> > + /* RAW 14 */
> > + case MEDIA_BUS_FMT_SBGGR14_1X14:
> > + csi_dev->ipi_dt = CSI_2_RAW14;
> > + break;
> > + /* RAW 16 */
> > + case MEDIA_BUS_FMT_SBGGR16_1X16:
> > + csi_dev->ipi_dt = CSI_2_RAW16;
> > + break;
> > + /* RAW 8 */
> > + case MEDIA_BUS_FMT_SBGGR8_1X8:
> > + csi_dev->ipi_dt = CSI_2_RAW8;
> > + break;
> > + /* YUV 422 8-bit */
> > + case MEDIA_BUS_FMT_YVYU8_2X8:
> > + csi_dev->ipi_dt = CSI_2_RAW8;
> > + break;
> > + /* YUV 422 10-bit */
> > + case MEDIA_BUS_FMT_VYUY8_1X16:
> > + csi_dev->ipi_dt = CSI_2_YUV422_8;
> > + break;
> > + /* YUV 420 8-bit LEGACY */
> > + case MEDIA_BUS_FMT_Y8_1X8:
> > + csi_dev->ipi_dt = CSI_2_RAW8;
> > + break;
> > + /* YUV 420 10-bit */
> > + case MEDIA_BUS_FMT_Y10_1X10:
> > + csi_dev->ipi_dt = CSI_2_RAW8;
> > + break;
> > + default:
> > + break;
> > + }
> > + dw_mipi_csi_write(csi_dev, reg.IPI_DATA_TYPE, csi_dev->ipi_dt);
> > + }
> > + dev_info(dev, "Selected IPI Data Type 0x%X\n", csi_dev->ipi_dt);
> > +}
> > +
> > +void dw_mipi_csi_fill_timings(struct dw_csi *dev,
> > + struct v4l2_subdev_format *fmt)
> > +{
> > + /* expected values */
> > + dev->hw.virtual_ch = 0;
> > + dev->hw.ipi_color_mode = COLOR48;
> > + dev->hw.ipi_auto_flush = 1;
> > + dev->hw.ipi_mode = CAMERA_TIMING;
> > + dev->hw.ipi_cut_through = CTINACTIVE;
> > + dev->hw.ipi_adv_features = LINE_EVENT_SELECTION(EVSELAUTO);
> > + dev->hw.htotal = fmt->format.width + dev->hw.hsa +
> > + dev->hw.hbp + dev->hw.hsd;
> > + dev->hw.vactive = fmt->format.height;
> > + dev->hw.output = 2;
> > +
> > + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > + dev_dbg(dev->dev, "*********** timings *********\n");
> > + dev_dbg(dev->dev, "Horizontal Sync Active: %d\n", dev->hw.hsa);
> > + dev_dbg(dev->dev, "Horizontal Back Porch: %d\n", dev->hw.hbp);
> > + dev_dbg(dev->dev, "Horizontal Width: %d\n", fmt->format.width);
> > + dev_dbg(dev->dev, "Horizontal Total: %d\n", dev->hw.htotal);
> > + dev_dbg(dev->dev, "Vertical Sync Active: %d\n", dev->hw.vsa);
> > + dev_dbg(dev->dev, "Vertical Back Porch: %d\n", dev->hw.vbp);
> > + dev_dbg(dev->dev, "Vertical Front Porch: %d\n", dev->hw.vfp);
> > + dev_dbg(dev->dev, "Vertical Active: %d\n", dev->hw.vactive);
> > + }
> > +}
> > +
> > +void dw_mipi_csi_start(struct dw_csi *csi_dev)
> > +{
> > + struct device *dev = csi_dev->dev;
> > +
> > + dw_mipi_csi_write(csi_dev, reg.N_LANES, (csi_dev->hw.num_lanes - 1));
> > + dev_info(dev, "number of 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,
> > + csi_dev->hw.ipi_adv_features);
> > + 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);
> > + if (csi_dev->hw.ipi_mode == CAMERA_TIMING) {
> > + dw_mipi_csi_write(csi_dev,
> > + reg.IPI_ADV_FEATURES,
> > + LINE_EVENT_SELECTION(EVSELPROG) |
> > + EN_VIDEO |
> > + EN_LINE_START |
> > + EN_NULL |
> > + EN_BLANKING |
> > + EN_EMBEDDED);
> > + }
> > + 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_MODE,
> > + csi_dev->hw.ipi_cut_through,
> > + 16, 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);
> > +
> > + dev_vdbg(dev, "*********** config *********\n");
> > + dev_vdbg(dev, "IPI enable: %s\n",
> > + csi_dev->hw.output ? "YES" : "NO");
> > + dev_vdbg(dev, "video mode transmission type: %s timming\n",
> > + csi_dev->hw.ipi_mode ? "controller" : "camera");
> > + dev_vdbg(dev, "Color Mode: %s\n",
> > + csi_dev->hw.ipi_color_mode ? "16 bits" : "48 bits");
> > + dev_vdbg(dev, "Cut Through Mode: %s\n",
> > + csi_dev->hw.ipi_cut_through ? "enable" : "disable");
> > + dev_vdbg(dev, "Virtual Channel: %d\n",
> > + csi_dev->hw.virtual_ch);
> > + dev_vdbg(dev, "Auto-flush: %d\n",
> > + csi_dev->hw.ipi_auto_flush);
> > + dw_mipi_csi_write(csi_dev, reg.IPI_SOFTRSTN, 1);
> > +
> > + if (csi_dev->hw.ipi_mode == AUTO_TIMING)
> > + phy_power_on(csi_dev->phy);
> > +
> > + 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);
> > + 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);
> > + }
> > + phy_power_on(csi_dev->phy);
> > +}
> > +
> > +int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev)
> > +{
> > + struct device *dev = csi_dev->dev;
> > + u32 global_int_status, i_sts;
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&csi_dev->slock, flags);
> > + global_int_status = dw_mipi_csi_read(csi_dev, reg.INTERRUPT);
> > +
> > + if (global_int_status & csi_int.PHY_FATAL) {
> > + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PHY_FATAL);
> > + dev_err_ratelimited(dev, "int %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, "int %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, "int %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, "int %08X: PHY: %08X\n",
> > + reg.INT_PHY, i_sts);
> > + }
> > +
> > + if (global_int_status & csi_int.PKT &&
> > + csi_dev->hw_version_major == 1 &&
> > + csi_dev->hw_version_minor <= 30) {
> > + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PKT);
> > + dev_err_ratelimited(dev, "int %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, "int %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, "int %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,
> > + "int %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,
> > + "int %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,
> > + "int %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,
> > + "int %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, "int %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, "int %08X: ST_ECC_CORRECT: %08X\n",
> > + reg.ST_ECC_CORRECT, i_sts);
> > + }
> > +
> > + spin_unlock_irqrestore(&csi_dev->slock, flags);
> > +
> > + return 1;
>
> any reason for this return 1 ?
>
> I see the code does not use this return value and it looks hardcoded.
Another oversight. I used to check the return value of this handler but I
don’t anymore. Will be returning 0.
>
> > +}
> > +
> > +void dw_mipi_csi_get_version(struct dw_csi *csi_dev)
> > +{
> > + u32 hw_version;
> > +
> > + hw_version = dw_mipi_csi_read(csi_dev, reg.VERSION);
> > + csi_dev->hw_version_major = (u8)((hw_version >> 24) - '0');
> > + csi_dev->hw_version_minor = (u8)((hw_version >> 16) - '0');
> > + csi_dev->hw_version_minor = csi_dev->hw_version_minor * 10;
> > + csi_dev->hw_version_minor += (u8)((hw_version >> 8) - '0');
> > +}
> > +
> > +int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev)
> > +{
> > + struct device *dev = csi_dev->dev;
> > +
> > + if (csi_dev->hw_version_major == 1) {
> > + if (csi_dev->hw_version_minor == 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) {
> > + /*
> > + * 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;
>
> any reason why this function returns anything ? unused and only 0 is the
> possible return value
No reason. Will be removed.
>
> > +}
> > +
> > +void dw_mipi_csi_dump(struct dw_csi *csi_dev)
> > +{
> > + dw_print(reg.VERSION);
> > + dw_print(reg.N_LANES);
> > + dw_print(reg.CTRL_RESETN);
> > + dw_print(reg.INTERRUPT);
> > + dw_print(reg.DATA_IDS_1);
> > + dw_print(reg.DATA_IDS_2);
> > + dw_print(reg.IPI_MODE);
> > + dw_print(reg.IPI_VCID);
> > + dw_print(reg.IPI_DATA_TYPE);
> > + dw_print(reg.IPI_MEM_FLUSH);
> > + dw_print(reg.IPI_HSA_TIME);
> > + dw_print(reg.IPI_HBP_TIME);
> > + dw_print(reg.IPI_HSD_TIME);
> > + dw_print(reg.IPI_HLINE_TIME);
> > + dw_print(reg.IPI_SOFTRSTN);
> > + dw_print(reg.IPI_ADV_FEATURES);
> > + dw_print(reg.IPI_VSA_LINES);
> > + dw_print(reg.IPI_VBP_LINES);
> > + dw_print(reg.IPI_VFP_LINES);
> > + dw_print(reg.IPI_VACTIVE_LINES);
> > + dw_print(reg.IPI_DATA_TYPE);
> > + dw_print(reg.VERSION);
> > + dw_print(reg.IPI_ADV_FEATURES);
> > +}
> > 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..6df3688
> > --- /dev/null
> > +++ b/drivers/media/platform/dwc/dw-mipi-csi.h
> > @@ -0,0 +1,287 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> > + *
> > + * Synopsys DesignWare MIPI CSI-2 Host controller driver
> > + *
> > + * 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/module.h>
> > +#include <linux/io.h>
> > +#include <linux/phy/phy.h>
> > +#include <linux/delay.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/of.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/ratelimit.h>
> > +#include <linux/reset.h>
> > +#include <linux/videodev2.h>
> > +#include <linux/wait.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/dwc/dw-mipi-csi-pltfrm.h>
> > +
> > +/* Advanced features */
> > +#define IPI_DT_OVERWRITE BIT(0)
> > +#define DATA_TYPE_OVERWRITE(dt) (((dt) & GENMASK(5, 0)) << 8)
> > +#define LINE_EVENT_SELECTION(n) ((n) << 16)
> > +
> > +enum line_event {
> > + EVSELAUTO = 0,
> > + EVSELPROG = 1,
> > +};
> > +
> > +#define EN_VIDEO BIT(17)
> > +#define EN_LINE_START BIT(18)
> > +#define EN_NULL BIT(19)
> > +#define EN_BLANKING BIT(20)
> > +#define EN_EMBEDDED BIT(21)
> > +#define IPI_SYNC_EVENT_MODE(n) ((n) << 24)
> > +
> > +enum sync_event {
> > + SYNCEVFSN = 0,
> > + SYNCEVFS = 1,
> > +};
> > +
> > +/* 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,
> > + CSI_2_RAW16 = 0x2E,
> > + CSI_2_RAW20 = 0x2F,
> > + USER_DEFINED_1 = 0x30,
> > + USER_DEFINED_2 = 0x31,
> > + USER_DEFINED_3 = 0x32,
> > + USER_DEFINED_4 = 0x33,
> > + USER_DEFINED_5 = 0x34,
> > + USER_DEFINED_6 = 0x35,
> > + USER_DEFINED_7 = 0x36,
> > + USER_DEFINED_8 = 0x37,
> > +};
>
> These are csi2 standard defines... would be good to have them available
> in a more generic header to not redefine them if they are needed elsewhere ?
>
Suggesting somewhere in ./include/media/...? Please elaborate on that.
> > +
> > +/* DWC MIPI CSI-2 output types */
> > +enum output {
> > + IPI_OUT = 0,
> > + IDI_OUT = 1,
> > + BOTH_OUT = 2
> > +};
> > +
> > +/* IPI color components */
> > +enum color_mode {
> > + COLOR48 = 0,
> > + COLOR16 = 1
> > +};
> > +
> > +/* IPI cut through */
> > +enum cut_through {
> > + CTINACTIVE = 0,
> > + CTACTIVE = 1
> > +};
> > +
> > +/* IPI output types */
> > +enum ipi_output {
> > + CAMERA_TIMING = 0,
> > + AUTO_TIMING = 1
> > +};
> > +
> > +/* Format template */
> > +struct mipi_fmt {
> > + u32 mbus_code;
> > + u8 depth;
> > +};
> > +
> > +struct mipi_dt {
> > + u32 hex;
> > + char *name;
> > +};
> > +
> > +/* CSI specific configuration */
> > +struct csi_data {
> > + u32 num_lanes;
> > + u32 dphy_freq;
> > + u32 pclk;
> > + u32 fps;
> > + u32 bpp;
> > + u32 output;
> > + u32 ipi_mode;
> > + u32 ipi_adv_features;
> > + u32 ipi_cut_through;
> > + u32 ipi_color_mode;
> > + u32 ipi_auto_flush;
> > + u32 virtual_ch;
> > + u32 hsa;
> > + u32 hbp;
> > + u32 hsd;
> > + u32 htotal;
> > + u32 vsa;
> > + u32 vbp;
> > + u32 vfp;
> > + u32 vactive;
> > +};
> > +
> > +/* Structure to embed device driver information */
> > +struct dw_csi {
> > + struct v4l2_subdev sd;
> > + struct video_device vdev;
> > + struct v4l2_device v4l2_dev;
> > + struct device *dev;
> > + struct media_pad pads[CSI_PADS_NUM];
> > + struct mipi_fmt *fmt;
> > + struct v4l2_mbus_framefmt format;
> > + void __iomem *base_address;
> > + void __iomem *demo;
> > + void __iomem *csc;
> > + int ctrl_irq_number;
> > + int demosaic_irq;
> > + struct csi_data hw;
> > + struct reset_control *rst;
> > + struct phy *phy;
> > + struct dw_csih_pdata *config;
> > + struct mutex lock; /* protect resources sharing */
> > + spinlock_t slock; /* interrupt handling lock */
> > + u8 ipi_dt;
> > + u8 index;
> > + u8 hw_version_major;
> > + u16 hw_version_minor;
> > +};
> > +
> > +static inline struct dw_csi *sd_to_mipi_csi_dev(struct v4l2_subdev *sdev)
> > +{
> > + return container_of(sdev, struct dw_csi, sd);
> > +}
> > +
> > +void dw_mipi_csi_reset(struct dw_csi *csi_dev);
> > +int dw_mipi_csi_mask_irq_power_off(struct dw_csi *csi_dev);
> > +int dw_mipi_csi_hw_stdby(struct dw_csi *csi_dev);
> > +void dw_mipi_csi_set_ipi_fmt(struct dw_csi *csi_dev);
> > +void dw_mipi_csi_start(struct dw_csi *csi_dev);
> > +int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev);
> > +void dw_mipi_csi_get_version(struct dw_csi *csi_dev);
> > +int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev);
> > +void dw_mipi_csi_fill_timings(struct dw_csi *dev,
> > + struct v4l2_subdev_format *fmt);
> > +void dw_mipi_csi_dump(struct dw_csi *csi_dev);
> > +
> > +#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
> > +int dw_csi_create_capabilities_sysfs(struct platform_device *pdev);
> > +#endif
> > +
> > +static inline void dw_mipi_csi_write(struct dw_csi *dev,
> > + u32 address, u32 data)
> > +{
> > + writel(data, dev->base_address + address);
> > +}
> > +
> > +static inline u32 dw_mipi_csi_read(struct dw_csi *dev, u32 address)
> > +{
> > + return readl(dev->base_address + address);
> > +}
> > +
> > +#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..948db4e
> > --- /dev/null
> > +++ b/include/media/dwc/dw-mipi-csi-pltfrm.h
> > @@ -0,0 +1,104 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> > + *
> > + * Synopsys DesignWare MIPI CSI-2 Host media entities
> > + *
> > + * 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
> > +
> > +/* 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)
> > +
> > +struct pdata_names {
> > + char *name;
> > +};
> > +
> > +enum video_dev_pads {
> > + VIDEO_DEV_SD_PAD_SINK_VIF1,
> > + VIDEO_DEV_SD_PAD_SINK_VIF2,
> > + VIDEO_DEV_SD_PAD_SOURCE_DMA,
> > + VIDEO_DEV_SD_PADS_NUM,
> > +};
> > +
> > +enum vif_pads {
> > + VIF_PAD_SINK_CSI,
> > + VIF_PAD_SOURCE_DMA,
> > + VIF_PADS_NUM,
> > +};
> > +
> > +enum mipi_csi_pads {
> > + CSI_PAD_SINK,
> > + CSI_PAD_SOURCE,
> > + CSI_PADS_NUM,
> > +};
> > +
> > +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_ */
>
> Would be useful to prefix these with 'dw_' to be sure they do not step
> over some other symbols when included ?
> >
I will fix this also.
Thank you Eugen,
Luis
Hi Luis,
On Wed, Jul 10, 2019 at 10:20:55AM +0000, Luis de Oliveira wrote:
> Hi Sakari,
>
> From: Sakari Ailus <[email protected]>
> Date: Tue, Jul 09, 2019 at 19:25:00
>
> > Hi Luis,
> >
> > On Mon, Jul 08, 2019 at 03:21:50PM +0000, Luis de Oliveira wrote:
> > > Hi Sakari,
> > >
> > > Thank you for your feedback.
> > > I have my comments inline.
> > >
> > > From: Sakari Ailus <[email protected]>
> > > Date: Fri, Jun 28, 2019 at 15:13:26
> > >
> > > > Hi Luis,
> > > >
> > > > Thank you for the patchset.
> > > >
> > > > On Tue, Jun 11, 2019 at 09:20:50PM +0200, Luis Oliveira wrote:
> > > > > From: Luis Oliveira <[email protected]>
> > > > >
> > > > > Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> > > > >
> > > > > Signed-off-by: Luis Oliveira <[email protected]>
> > > > > ---
> > > > > Changelog
> > > > > v3-v4
> > > > > - remove "plat" from the block name @rob @laurent
> > > > > - remove "phy-names" when single-entry @rob
> > > > > - remove "snps,output-type" -> went to the driver config @laurent
> > > > >
> > > > > .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> > > > > 1 file changed, 41 insertions(+)
> > > > > create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > >
> > > > > diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > > new file mode 100644
> > > > > index 0000000..613b7f9
> > > > > --- /dev/null
> > > > > +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > > @@ -0,0 +1,41 @@
> > > > > +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"
> > > > > +- reg : physical base address and size of the device memory
> > > > > + mapped registers;
> > > > > +- interrupts : DW CSI-2 Host interrupts
> > > > > +- 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)
> > > > > +
> > > > > +The per-board settings:
> > > > > + - port sub-node describing a single endpoint connected to the camera as
> > > > > + described in video-interfaces.txt[1].
> > > >
> > > > Which endpoint properties in video-interfaces.txt are relevant for the
> > > > hardware? Which values may they have?
> > > >
> > >
> > > Currently I'm using only two properties "data-lanes" and "bus-width", but
> > > I have plans to add blanking info also.
> > > I will add more info.
> >
> > Isn't blanking defined by what the transmitter seneds? Or do you have
> > hardware limitations on the receiver side?
> >
>
> When we use this IP in prototyping we configure blanking at the receiver
> side.
> Some cameras don't have blanking configuration capabilities so we
> configure it on the RX side.
I haven't come across a CSI-2 connected camera without some kind of
blanking configuration capabilities. Even if there was one, you couldn't
configure blanking from the receiver side.
Please document that the data-lanes property is required, and which values
are possible.
>
> > I've only heard of one such case before, and it was a very old parallel
> > receiver.
> >
> > If you have a CSI-2 receiver, bus-width isn't relevant --- it's for paralle
> > interfaces only. Please add data-lanes to required endpoint properties.
> >
>
> I used bus-width property in the Synopsys IPI (Image Pixel Interface)
> that enables direct video stream access.
> This interface is an output that can be 16-bit or 48-bit, that's why I
> used bus-width property.
Does this device write the image data to system memory, or is it another
device? If there's another one, then you should probably have another port
to describe that connection.
--
Kind regards,
Sakari Ailus
Hi Sakari,
Thank you for the review, my answers inline.
> From: Sakari Ailus <[email protected]>
> Date: Thu, Jul 25, 2019 at 21:02:11
>
> Hi Luis,
>
> On Wed, Jul 10, 2019 at 10:20:55AM +0000, Luis de Oliveira wrote:
> > Hi Sakari,
> >
> > From: Sakari Ailus <[email protected]>
> > Date: Tue, Jul 09, 2019 at 19:25:00
> >
> > > Hi Luis,
> > >
> > > On Mon, Jul 08, 2019 at 03:21:50PM +0000, Luis de Oliveira wrote:
> > > > Hi Sakari,
> > > >
> > > > Thank you for your feedback.
> > > > I have my comments inline.
> > > >
> > > > From: Sakari Ailus <[email protected]>
> > > > Date: Fri, Jun 28, 2019 at 15:13:26
> > > >
> > > > > Hi Luis,
> > > > >
> > > > > Thank you for the patchset.
> > > > >
> > > > > On Tue, Jun 11, 2019 at 09:20:50PM +0200, Luis Oliveira wrote:
> > > > > > From: Luis Oliveira <[email protected]>
> > > > > >
> > > > > > Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> > > > > >
> > > > > > Signed-off-by: Luis Oliveira <[email protected]>
> > > > > > ---
> > > > > > Changelog
> > > > > > v3-v4
> > > > > > - remove "plat" from the block name @rob @laurent
> > > > > > - remove "phy-names" when single-entry @rob
> > > > > > - remove "snps,output-type" -> went to the driver config @laurent
> > > > > >
> > > > > > .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> > > > > > 1 file changed, 41 insertions(+)
> > > > > > create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > > >
> > > > > > diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > > > new file mode 100644
> > > > > > index 0000000..613b7f9
> > > > > > --- /dev/null
> > > > > > +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > > > @@ -0,0 +1,41 @@
> > > > > > +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"
> > > > > > +- reg : physical base address and size of the device memory
> > > > > > + mapped registers;
> > > > > > +- interrupts : DW CSI-2 Host interrupts
> > > > > > +- 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)
> > > > > > +
> > > > > > +The per-board settings:
> > > > > > + - port sub-node describing a single endpoint connected to the camera as
> > > > > > + described in video-interfaces.txt[1].
> > > > >
> > > > > Which endpoint properties in video-interfaces.txt are relevant for the
> > > > > hardware? Which values may they have?
> > > > >
> > > >
> > > > Currently I'm using only two properties "data-lanes" and "bus-width", but
> > > > I have plans to add blanking info also.
> > > > I will add more info.
> > >
> > > Isn't blanking defined by what the transmitter seneds? Or do you have
> > > hardware limitations on the receiver side?
> > >
> >
> > When we use this IP in prototyping we configure blanking at the receiver
> > side.
> > Some cameras don't have blanking configuration capabilities so we
> > configure it on the RX side.
>
> I haven't come across a CSI-2 connected camera without some kind of
> blanking configuration capabilities. Even if there was one, you couldn't
> configure blanking from the receiver side.
>
> Please document that the data-lanes property is required, and which values
> are possible.
>
Ok, I will add the data-lanes property to the Documentation.
> >
> > > I've only heard of one such case before, and it was a very old parallel
> > > receiver.
> > >
> > > If you have a CSI-2 receiver, bus-width isn't relevant --- it's for paralle
> > > interfaces only. Please add data-lanes to required endpoint properties.
> > >
> >
> > I used bus-width property in the Synopsys IPI (Image Pixel Interface)
> > that enables direct video stream access.
> > This interface is an output that can be 16-bit or 48-bit, that's why I
> > used bus-width property.
>
> Does this device write the image data to system memory, or is it another
> device? If there's another one, then you should probably have another port
> to describe that connection.
>
Yes, it is another. I can add that connection port also.
> --
> Kind regards,
>
> Sakari Ailus
Thank you,
Luis
On 26.07.2019 12:50, Luis de Oliveira wrote:
> Hi Sakari,
>
> Thank you for the review, my answers inline.
>
>> From: Sakari Ailus <[email protected]>
>> Date: Thu, Jul 25, 2019 at 21:02:11
>>
>> Hi Luis,
>>
>> On Wed, Jul 10, 2019 at 10:20:55AM +0000, Luis de Oliveira wrote:
>>> Hi Sakari,
>>>
>>> From: Sakari Ailus <[email protected]>
>>> Date: Tue, Jul 09, 2019 at 19:25:00
>>>
>>>> Hi Luis,
>>>>
>>>> On Mon, Jul 08, 2019 at 03:21:50PM +0000, Luis de Oliveira wrote:
>>>>> Hi Sakari,
>>>>>
>>>>> Thank you for your feedback.
>>>>> I have my comments inline.
>>>>>
>>>>> From: Sakari Ailus <[email protected]>
>>>>> Date: Fri, Jun 28, 2019 at 15:13:26
>>>>>
>>>>>> Hi Luis,
>>>>>>
>>>>>> Thank you for the patchset.
>>>>>>
>>>>>> On Tue, Jun 11, 2019 at 09:20:50PM +0200, Luis Oliveira wrote:
>>>>>>> From: Luis Oliveira <[email protected]>
>>>>>>>
>>>>>>> Add bindings for Synopsys DesignWare MIPI CSI-2 host.
>>>>>>>
>>>>>>> Signed-off-by: Luis Oliveira <[email protected]>
>>>>>>> ---
>>>>>>> Changelog
>>>>>>> v3-v4
>>>>>>> - remove "plat" from the block name @rob @laurent
>>>>>>> - remove "phy-names" when single-entry @rob
>>>>>>> - remove "snps,output-type" -> went to the driver config @laurent
>>>>>>>
>>>>>>> .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
>>>>>>> 1 file changed, 41 insertions(+)
>>>>>>> create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
>>>>>>>
>>>>>>> diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
>>>>>>> new file mode 100644
>>>>>>> index 0000000..613b7f9
>>>>>>> --- /dev/null
>>>>>>> +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
>>>>>>> @@ -0,0 +1,41 @@
>>>>>>> +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"
>>>>>>> +- reg : physical base address and size of the device memory
>>>>>>> + mapped registers;
>>>>>>> +- interrupts : DW CSI-2 Host interrupts
>>>>>>> +- 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)
>>>>>>> +
>>>>>>> +The per-board settings:
>>>>>>> + - port sub-node describing a single endpoint connected to the camera as
>>>>>>> + described in video-interfaces.txt[1].
>>>>>>
>>>>>> Which endpoint properties in video-interfaces.txt are relevant for the
>>>>>> hardware? Which values may they have?
>>>>>>
>>>>>
>>>>> Currently I'm using only two properties "data-lanes" and "bus-width", but
>>>>> I have plans to add blanking info also.
>>>>> I will add more info.
>>>>
>>>> Isn't blanking defined by what the transmitter seneds? Or do you have
>>>> hardware limitations on the receiver side?
>>>>
>>>
>>> When we use this IP in prototyping we configure blanking at the receiver
>>> side.
>>> Some cameras don't have blanking configuration capabilities so we
>>> configure it on the RX side.
>>
>> I haven't come across a CSI-2 connected camera without some kind of
>> blanking configuration capabilities. Even if there was one, you couldn't
>> configure blanking from the receiver side.
>>
>> Please document that the data-lanes property is required, and which values
>> are possible.
>>
>
> Ok, I will add the data-lanes property to the Documentation.
>
>>>
>>>> I've only heard of one such case before, and it was a very old parallel
>>>> receiver.
>>>>
>>>> If you have a CSI-2 receiver, bus-width isn't relevant --- it's for paralle
>>>> interfaces only. Please add data-lanes to required endpoint properties.
>>>>
>>>
>>> I used bus-width property in the Synopsys IPI (Image Pixel Interface)
>>> that enables direct video stream access.
>>> This interface is an output that can be 16-bit or 48-bit, that's why I
>>> used bus-width property.
>>
>> Does this device write the image data to system memory, or is it another
>> device? If there's another one, then you should probably have another port
>> to describe that connection.
>>
>
> Yes, it is another. I can add that connection port also.
Yes please. It is interesting for me to see how to connect the output of
this device to another media device, and how to specify the bus in this
situation.
Thanks,
Eugen
>
>
>> --
>> Kind regards,
>>
>> Sakari Ailus
>
> Thank you,
>
> Luis
>
>
Hi Luis,
On Tue, Jun 11, 2019 at 09:20:51PM +0200, Luis Oliveira wrote:
> Add the Synopsys MIPI CSI-2 controller driver. This
> controller driver is divided in platform functions and core functions.
> This way it serves as platform for future DesignWare drivers.
>
> Signed-off-by: Luis Oliveira <[email protected]>
> ---
> Changelog
> v3-v4
> - fix v4l2_fwnode_endpoint bad initialization @eugen
> - removed extra lines and fixed coding style issues
>
> MAINTAINERS | 8 +
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/dwc/Kconfig | 19 +
> drivers/media/platform/dwc/Makefile | 9 +
> drivers/media/platform/dwc/dw-csi-plat.c | 475 +++++++++++++++++++++++
> drivers/media/platform/dwc/dw-csi-plat.h | 97 +++++
> drivers/media/platform/dwc/dw-csi-sysfs.c | 624 ++++++++++++++++++++++++++++++
> drivers/media/platform/dwc/dw-mipi-csi.c | 569 +++++++++++++++++++++++++++
> drivers/media/platform/dwc/dw-mipi-csi.h | 287 ++++++++++++++
> include/media/dwc/dw-mipi-csi-pltfrm.h | 104 +++++
> 11 files changed, 2195 insertions(+)
> 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-csi-sysfs.c
> 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 16a97ba..6ffe4fd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15187,6 +15187,14 @@ S: Maintained
> F: drivers/dma/dwi-axi-dmac/
> F: Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.txt
>
> +SYNOPSYS DESIGNWARE MIPI DPHY CSI-2 HOST DRIVER
> +M: Luis Oliveira <[email protected]>
> +L: [email protected]
> +T: git git://linuxtv.org/media_tree.git
> +S: Maintained
> +F: drivers/media/platform/dwc
> +F: Documentation/devicetree/bindings/media/snps,dw-csi.txt
> +
> SYNOPSYS DESIGNWARE DMAC DRIVER
> M: Viresh Kumar <[email protected]>
> R: Andy Shevchenko <[email protected]>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 8a19654..b6fb139 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -147,6 +147,7 @@ source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> source "drivers/media/platform/sunxi/sun6i-csi/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 7cbbd92..4807caf 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -101,3 +101,5 @@ obj-y += meson/
> obj-y += cros-ec-cec/
>
> obj-$(CONFIG_VIDEO_SUN6I_CSI) += sunxi/sun6i-csi/
> +
> +obj-y += dwc/
> diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
> new file mode 100644
> index 0000000..508ac21
> --- /dev/null
> +++ b/drivers/media/platform/dwc/Kconfig
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Synopsys DWC Platform drivers
> +# Drivers here are currently for MIPI CSI-2 support
> +
> +config DWC_MIPI_CSI2_HOST
> + tristate "Synopsys DesignWare CSI-2 Host Controller support"
> + select VIDEO_DEV
> + select VIDEO_V4L2
> + select VIDEO_V4L2_SUBDEV_API
Please depend on the above instead.
> + select V4L2_FWNODE
> + help
> + This selects the DesignWare MIPI CSI-2 host controller support. This
> + controller gives access to control a CSI-2 receiver acting as a V4L2
> + subdevice.
> +
> + If you have a controller with this interface, say Y.
> +
> + If unsure, say N.
You might mention the module name.
> diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
> new file mode 100644
> index 0000000..057f137
> --- /dev/null
> +++ b/drivers/media/platform/dwc/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for Synopsys DWC Platform drivers
> +#
> +dw-csi-objs := dw-csi-plat.o dw-mipi-csi.o
> +ifeq ($(CONFIG_DWC_MIPI_TC_DPHY_GEN3),y)
> + dw-csi-objs += dw-csi-sysfs.o
> +endif
> +obj-$(CONFIG_DWC_MIPI_CSI2_HOST) += dw-csi.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..9828d55
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-plat.c
> @@ -0,0 +1,475 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * Platform driver
> + *
> + * Author: Luis Oliveira <[email protected]>
> + */
> +
> +#include <media/dwc/dw-dphy-data.h>
> +
> +#include "dw-csi-plat.h"
> +
> +const struct mipi_dt csi_dt[] = {
Make this static or use a common prefix that somehow resembles the name
name of the driver.
> + {
> + .hex = CSI_2_YUV420_8,
> + .name = "YUV420_8bits",
> + }, {
> + .hex = CSI_2_YUV420_10,
> + .name = "YUV420_10bits",
> + }, {
> + .hex = CSI_2_YUV420_8_LEG,
> + .name = "YUV420_8bits_LEGACY",
> + }, {
> + .hex = CSI_2_YUV420_8_SHIFT,
> + .name = "YUV420_8bits_SHIFT",
> + }, {
> + .hex = CSI_2_YUV420_10_SHIFT,
> + .name = "YUV420_10bits_SHIFT",
> + }, {
> + .hex = CSI_2_YUV422_8,
> + .name = "YUV442_8bits",
> + }, {
> + .hex = CSI_2_YUV422_10,
> + .name = "YUV442_10bits",
> + }, {
> + .hex = CSI_2_RGB444,
> + .name = "RGB444",
> + }, {
> + .hex = CSI_2_RGB555,
> + .name = "RGB555",
> + }, {
> + .hex = CSI_2_RGB565,
> + .name = "RGB565",
> + }, {
> + .hex = CSI_2_RGB666,
> + .name = "RGB666",
> + }, {
> + .hex = CSI_2_RGB888,
> + .name = "RGB888",
> + }, {
> + .hex = CSI_2_RAW6,
> + .name = "RAW6",
> + }, {
> + .hex = CSI_2_RAW7,
> + .name = "RAW7",
> + }, {
> + .hex = CSI_2_RAW8,
> + .name = "RAW8",
> + }, {
> + .hex = CSI_2_RAW10,
> + .name = "RAW10",
> + }, {
> + .hex = CSI_2_RAW12,
> + .name = "RAW12",
> + }, {
> + .hex = CSI_2_RAW14,
> + .name = "RAW14",
> + }, {
> + .hex = CSI_2_RAW16,
> + .name = "RAW16",
> + },
> +};
> +
> +static 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].mbus_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;
> +
> + if (code->index != 0)
> + return -EINVAL;
One of the two conditions makes no sense. Is it the latter?
> +
> + code->code = dw_mipi_csi_formats[code->index].mbus_code;
> + return 0;
> +}
> +
> +static struct mipi_fmt *
> +dw_mipi_csi_try_format(struct v4l2_mbus_framefmt *mf)
> +{
> + struct mipi_fmt *fmt;
> +
> + fmt = find_dw_mipi_csi_format(mf);
> + if (!fmt)
> + fmt = &dw_mipi_csi_formats[0];
> +
You could return &dw_mipi_csi_formats[0] in find_dw_mipi_csi_format() and
omit this function.
> + mf->code = fmt->mbus_code;
> +
> + return fmt;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +dw_mipi_csi_get_format(struct dw_csi *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;
> + dev_dbg(dev->dev,
> + "%s got v4l2_mbus_pixelcode. 0x%x\n", __func__,
> + dev->format.code);
> + dev_dbg(dev->dev,
> + "%s got width. 0x%x\n", __func__,
> + dev->format.width);
> + dev_dbg(dev->dev,
> + "%s got height. 0x%x\n", __func__,
> + dev->format.height);
I'd just omit these debug prints in a driver. But adding them to the
framework might make sense. We don't have a lot of debug prints dealing
with user parameters in there. OTOH the common test programs largely do the
same already.
> + 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 dw_csi *dev = sd_to_mipi_csi_dev(sd);
> + struct mipi_fmt *dev_fmt;
> + struct v4l2_mbus_framefmt *mf = dw_mipi_csi_get_format(dev, cfg,
> + fmt->which);
> + int i;
unsigned int
> +
> + dev_fmt = dw_mipi_csi_try_format(&fmt->format);
> +
> + if (dev_fmt) {
Can dev_fmt be NULL?
> + *mf = fmt->format;
> + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
> + dev->fmt = dev_fmt;
> + dev->fmt->mbus_code = mf->code;
> + dw_mipi_csi_set_ipi_fmt(dev);
> + }
> +
> + if (fmt->format.width > 0 && fmt->format.height > 0) {
> + dw_mipi_csi_fill_timings(dev, fmt);
> + } else {
> + dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
> + __func__, fmt->format.width);
> + dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
> + __func__, fmt->format.height);
Instead of returning an error here, a driver needs to adjust them to make
them valid.
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(csi_dt); i++)
> + if (csi_dt[i].hex == dev->ipi_dt) {
> + dev_vdbg(dev->dev, "Using data type %s\n",
> + csi_dt[i].name);
> + }
Braces not needed here.
> + 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 dw_csi *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 dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dev_vdbg(dev->dev, "%s: on=%d\n", __func__, on);
> +
> + 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);
> + /* reset data type */
> + dev->ipi_dt = 0x0;
> + }
> + return 0;
> +}
> +
> +static int
> +dw_mipi_csi_log_status(struct v4l2_subdev *sd)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dw_mipi_csi_dump(dev);
> +
> + return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
> +static int
> +dw_mipi_csi_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dev_vdbg(dev->dev, "%s: reg=%llu\n", __func__, reg->reg);
> + reg->val = dw_mipi_csi_read(dev, reg->reg);
> +
> + return 0;
> +}
> +#endif
> +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 = MEDIA_BUS_FMT_RGB888_1X24;
> + 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,
> + .log_status = dw_mipi_csi_log_status,
> +#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
> + .g_register = dw_mipi_csi_g_register,
> +#endif
> +};
> +
> +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 dw_csi *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 dw_csi *dev)
> +{
> + struct device_node *node = pdev->dev.of_node;
> + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> + int ret = 0;
ret is always assigned, no need to do that here.
> +
> + if (of_property_read_u32(node, "snps,output-type",
> + &dev->hw.output))
> + dev->hw.output = 2;
What does this do?
> +
> + node = of_graph_get_next_endpoint(node, NULL);
> + if (!node) {
> + dev_err(&pdev->dev, "No port node at %pOF\n",
> + pdev->dev.of_node);
> + return -EINVAL;
> + }
> + /* Get port node and validate MIPI-CSI channel id. */
> + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &ep);
> + if (ret)
> + goto err;
> +
> + dev->index = ep.base.port - 1;
Why? Wouldn't index be -1 here?
> + if (dev->index >= CSI_MAX_ENTITIES) {
> + ret = -ENXIO;
> + goto err;
> + }
> + dev->hw.num_lanes = ep.bus.mipi_csi2.num_data_lanes;
A newline would be nice here.
> +err:
> + of_node_put(node);
And here.
> + return ret;
> +}
> +
> +static const struct of_device_id dw_mipi_csi_of_match[];
Could you move the declaration here?
> +
> +static int dw_csi_probe(struct platform_device *pdev)
> +{
> + const struct of_device_id *of_id = NULL;
> + struct device *dev = &pdev->dev;
> + struct resource *res = NULL;
> + struct dw_csi *csi;
> + struct v4l2_subdev *sd;
> + int ret;
> +
> + dev_vdbg(dev, "Probing started\n");
I'd guess that might be needed at development time, but please remove it
now.
> +
> + /* Resource allocation */
> + csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
> + if (!csi)
> + return -ENOMEM;
> +
> + mutex_init(&csi->lock);
Remember to do mutex_destroy() on this in error handling and driver's
remove() function.
> + spin_lock_init(&csi->slock);
> + csi->dev = dev;
> +
> + of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
> + if (!of_id)
> + return -EINVAL;
> +
> + ret = dw_mipi_csi_parse_dt(pdev, csi);
> + if (ret < 0)
> + return ret;
> +
> + csi->phy = devm_of_phy_get(dev, dev->of_node, NULL);
> + if (IS_ERR(csi->phy)) {
> + dev_err(dev, "No DPHY available\n");
> + return PTR_ERR(csi->phy);
> + }
> + /* Registers mapping */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res)
> + return -ENXIO;
> +
> + csi->base_address = devm_ioremap_resource(dev, res);
> + if (IS_ERR(csi->base_address)) {
> + dev_err(dev, "Base address not set.\n");
> + return PTR_ERR(csi->base_address);
> + }
> +
> + csi->ctrl_irq_number = platform_get_irq(pdev, 0);
> + if (csi->ctrl_irq_number < 0) {
> + dev_err(dev, "irq number %d not set.\n", csi->ctrl_irq_number);
> + ret = csi->ctrl_irq_number;
> + goto end;
> + }
> +
> + csi->rst = devm_reset_control_get_optional_shared(dev, NULL);
> + if (IS_ERR(csi->rst)) {
> + dev_err(dev, "error getting reset control %d\n", ret);
> + return PTR_ERR(csi->rst);
> + }
> +
> + ret = devm_request_irq(dev, csi->ctrl_irq_number,
> + dw_mipi_csi_irq1, IRQF_SHARED,
> + dev_name(dev), csi);
> + if (ret) {
> + dev_err(dev, "irq csi %s failed\n", of_id->name);
> +
> + goto end;
> + }
> +
> + sd = &csi->sd;
> + v4l2_subdev_init(sd, &dw_mipi_csi_subdev_ops);
> + csi->sd.owner = THIS_MODULE;
> +
> + snprintf(sd->name, sizeof(sd->name), "%s.%d",
> + "dw-csi", csi->index);
> +
> + csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + csi->fmt = &dw_mipi_csi_formats[0];
> + csi->format.code = dw_mipi_csi_formats[0].mbus_code;
> +
> + sd->entity.function = MEDIA_ENT_F_IO_V4L;
> + csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> + csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> + ret = media_entity_pads_init(&csi->sd.entity,
> + CSI_PADS_NUM, csi->pads);
> +
> + dev_vdbg(dev, "v4l2.name: %s\n", csi->v4l2_dev.name);
Please remove.
> +
> + if (ret < 0) {
> + dev_err(dev, "media entity init failed\n");
> + goto end;
> + }
> +
> + v4l2_set_subdevdata(&csi->sd, pdev);
> + platform_set_drvdata(pdev, &csi->sd);
> + dev_set_drvdata(dev, sd);
> +
> + if (csi->rst)
> + reset_control_deassert(csi->rst);
> +#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
> + dw_csi_create_capabilities_sysfs(pdev);
> +#endif
> + dw_mipi_csi_get_version(csi);
> + dw_mipi_csi_specific_mappings(csi);
> + dw_mipi_csi_mask_irq_power_off(csi);
> +
> + dev_info(dev, "DW MIPI CSI-2 Host registered successfully HW v%u.%u\n",
> + csi->hw_version_major, csi->hw_version_minor);
> +
> + phy_init(csi->phy);
> +
> + return 0;
> +
> +end:
> + media_entity_cleanup(&csi->sd.entity);
> + v4l2_device_unregister(csi->vdev.v4l2_dev);
> +
> + return ret;
> +}
> +
> +/**
> + * @short Exit routine - Exit point of the driver
> + * @param[in] pdev pointer to the platform device structure
> + * @return 0 on success
> + */
> +static int dw_csi_remove(struct platform_device *pdev)
> +{
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *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" },
> + {},
> +};
> +
> +MODULE_DEVICE_TABLE(of, dw_mipi_csi_of_match);
> +
> +/**
> + * @short Platform driver structure
> + */
> +static struct platform_driver dw_mipi_csi_driver = {
> + .remove = dw_csi_remove,
> + .probe = dw_csi_probe,
> + .driver = {
> + .name = "dw-csi",
> + .owner = THIS_MODULE,
You can drop the owner field.
> + .of_match_table = of_match_ptr(dw_mipi_csi_of_match),
> + },
> +};
> +
> +module_platform_driver(dw_mipi_csi_driver);
> +
> +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..e322592
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-plat.h
> @@ -0,0 +1,97 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018 Synopsys, Inc.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * Supported bus formats
> + *
> + * Author: Luis Oliveira <[email protected]>
> + */
> +
> +#ifndef _DW_CSI_PLAT_H__
> +#define _DW_CSI_PLAT_H__
> +
> +#include "dw-mipi-csi.h"
> +
> +/* Video formats supported by the MIPI CSI-2 */
> +struct mipi_fmt dw_mipi_csi_formats[] = {
Please move to the .c file. If you need this in multiple files, please use
a forward declaration. This also should be const.
> + {
> + /* RAW 8 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .depth = 8,
> + }, {
> + /* RAW 10 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .depth = 10,
> + }, {
> + /* RAW 12 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .depth = 12,
> + }, {
> + /* RAW 14 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .depth = 14,
> + }, {
> + /* RAW 16 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR16_1X16,
> + .depth = 16,
> + }, {
> + /* RGB 666 */
> + .mbus_code = MEDIA_BUS_FMT_RGB666_1X18,
> + .depth = 18,
> + }, {
> + /* RGB 565 */
> + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE,
> + .depth = 16,
> + }, {
> + /* BGR 565 */
> + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE,
> + .depth = 16,
> + }, {
> + /* RGB 555 */
> + .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
> + .depth = 16,
> + }, {
> + /* BGR 555 */
> + .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
> + .depth = 16,
> + }, {
> + /* RGB 444 */
> + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE,
> + .depth = 16,
> + }, {
> + /* RGB 444 */
> + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE,
> + .depth = 16,
> + }, {
> + /* RGB 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_LE,
> + .depth = 24,
> + }, {
> + /* BGR 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_BE,
> + .depth = 24,
> + }, {
> + /* BGR 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
> + .depth = 24,
> + }, {
> + /* YUV 422 8-bit */
> + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .depth = 16,
> + }, {
> + /* YUV 422 10-bit */
> + .mbus_code = MEDIA_BUS_FMT_VYUY10_2X10,
> + .depth = 20,
> + }, {
> + /* YUV 420 8-bit LEGACY */
> + .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> + .depth = 8,
> + }, {
> + /* YUV 420 10-bit */
> + .mbus_code = MEDIA_BUS_FMT_Y10_1X10,
> + .depth = 10,
> + },
> +};
> +
> +#endif /* _DW_CSI_PLAT_H__ */
> diff --git a/drivers/media/platform/dwc/dw-csi-sysfs.c b/drivers/media/platform/dwc/dw-csi-sysfs.c
> new file mode 100644
> index 0000000..e8d2bb9
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-sysfs.c
> @@ -0,0 +1,624 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * SysFS components for the platform driver
> + *
> + * Author: Luis Oliveira <[email protected]>
> + */
> +
> +#include "dw-mipi-csi.h"
> +
> +static ssize_t core_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 dw_csi *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 dw_csi *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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.num_lanes);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t core_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 dw_csi *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 data_type_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 dw_csi *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 0x%lx\n", dt);
> + csi_dev->ipi_dt = dt;
> +
> + return count;
> +}
> +
> +static ssize_t data_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->ipi_dt);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hsa_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hsa;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hsa);
> + if (ret < 0)
> + return ret;
> +
> + if (hsa > 0xFFF) {
> + dev_err(dev, "Invalid HSA time %lx\n", hsa);
> + return count;
> + }
> +
> + dev_info(dev, "HSA time 0x%lx\n", hsa);
> + csi_dev->hw.hsa = hsa;
> +
> + return count;
> +}
> +
> +static ssize_t hsa_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hsa);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hbp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hbp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hbp);
> + if (ret < 0)
> + return ret;
> +
> + if (hbp > 0xFFF) {
> + dev_err(dev, "Invalid HBP time %lx\n", hbp);
> + return count;
> + }
> +
> + dev_info(dev, "HBP time 0x%lx\n", hbp);
> + csi_dev->hw.hbp = hbp;
> +
> + return count;
> +}
> +
> +static ssize_t hbp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hbp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hsd_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hsd;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hsd);
> + if (ret < 0)
> + return ret;
> +
> + if (hsd > 0xFF) {
> + dev_err(dev, "Invalid HSD time %lx\n", hsd);
> + return count;
> + }
> +
> + dev_info(dev, "HSD time 0x%lx\n", hsd);
> + csi_dev->hw.hsd = hsd;
> +
> + return count;
> +}
> +
> +static ssize_t hsd_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hsd);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vsa_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vsa;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vsa);
> + if (ret < 0)
> + return ret;
> +
> + if (vsa > 0x3FF) {
> + dev_err(dev, "Invalid VSA period %lx\n", vsa);
> + return count;
> + }
> +
> + dev_info(dev, "VSA period 0x%lx\n", vsa);
> + csi_dev->hw.vsa = vsa;
> +
> + return count;
> +}
> +
> +static ssize_t vsa_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vsa);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vbp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vbp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vbp);
> + if (ret < 0)
> + return ret;
> +
> + if (vbp > 0x2FF) {
> + dev_err(dev, "Invalid VBP period %lx\n", vbp);
> + return count;
> + }
> +
> + dev_info(dev, "VBP period 0x%lx\n", vbp);
> + csi_dev->hw.vbp = vbp;
> +
> + return count;
> +}
> +
> +static ssize_t vbp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vbp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vfp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vfp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vfp);
> + if (ret < 0)
> + return ret;
> +
> + if (vfp > 0x3ff) {
> + dev_err(dev, "Invalid VFP period %lx\n", vfp);
> + return count;
> + }
> +
> + dev_info(dev, "VFP period 0x%lx\n", vfp);
> + csi_dev->hw.vfp = vfp;
> +
> + return count;
> +}
> +
> +static ssize_t vfp_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vfp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t virtual_channel_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long virtual_ch;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &virtual_ch);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)virtual_ch < 0 || (signed int)virtual_ch > 8) {
> + dev_err(dev, "Invalid Virtual Channel %lu\n", virtual_ch);
> + return count;
> + }
> +
> + dev_info(dev, "Virtual Channel %lu\n", virtual_ch);
> + csi_dev->hw.virtual_ch = virtual_ch;
> +
> + return count;
> +}
> +
> +static ssize_t virtual_channel_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.virtual_ch);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_color_mode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_color_mode;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_color_mode);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_color_mode < 0 || (signed int)ipi_color_mode > 1) {
> + dev_err(dev,
> + "Wrong Color Mode %lu, (48 bits -> 0 or 16 bits -> 1\n",
> + ipi_color_mode);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", ipi_color_mode);
> + csi_dev->hw.ipi_color_mode = ipi_color_mode;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_color_mode_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_color_mode);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_auto_flush_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_auto_flush;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_auto_flush);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_auto_flush < 0 || (signed int)ipi_auto_flush > 1) {
> + dev_err(dev,
> + "Invalid Auto Flush Mode %lu, (No -> 0 or Yes -> 1\n",
> + ipi_auto_flush);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Auto Flush %lu\n", ipi_auto_flush);
> + csi_dev->hw.ipi_auto_flush = ipi_auto_flush;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_auto_flush_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_auto_flush);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_timings_mode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_mode;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_mode);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_mode < 0 || (signed int)ipi_mode > 1) {
> + dev_err(dev,
> + "Invalid Timing Source %lu (Camera:0|Controller:1)\n",
> + ipi_mode);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", ipi_mode);
> + csi_dev->hw.ipi_mode = ipi_mode;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_timings_mode_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_mode);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t output_type_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long output;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &output);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)output < 0 || (signed int)output > 1) {
> + dev_err(dev,
> + "Invalid Core output %lu to be used \
> + (IPI-> 0 or IDI->1 or BOTH- 2\n",
> + output);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", output);
> + csi_dev->hw.output = output;
> +
> + return count;
> +}
> +
> +static ssize_t output_type_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 dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.output);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static DEVICE_ATTR_RO(core_version);
> +static DEVICE_ATTR_RO(core_reset);
> +static DEVICE_ATTR_RW(n_lanes);
> +static DEVICE_ATTR_RW(data_type);
> +static DEVICE_ATTR_RW(hsa);
> +static DEVICE_ATTR_RW(hbp);
> +static DEVICE_ATTR_RW(hsd);
> +static DEVICE_ATTR_RW(vsa);
> +static DEVICE_ATTR_RW(vbp);
> +static DEVICE_ATTR_RW(vfp);
> +static DEVICE_ATTR_RW(virtual_channel);
> +static DEVICE_ATTR_RW(ipi_color_mode);
> +static DEVICE_ATTR_RW(ipi_auto_flush);
> +static DEVICE_ATTR_RW(ipi_timings_mode);
> +static DEVICE_ATTR_RW(output_type);
> +
> +int dw_csi_create_capabilities_sysfs(struct platform_device *pdev)
> +{
> + device_create_file(&pdev->dev, &dev_attr_core_version);
> + device_create_file(&pdev->dev, &dev_attr_core_reset);
> + device_create_file(&pdev->dev, &dev_attr_n_lanes);
> + device_create_file(&pdev->dev, &dev_attr_data_type);
> + device_create_file(&pdev->dev, &dev_attr_hsa);
> + device_create_file(&pdev->dev, &dev_attr_hbp);
> + device_create_file(&pdev->dev, &dev_attr_hsd);
> + device_create_file(&pdev->dev, &dev_attr_vsa);
> + device_create_file(&pdev->dev, &dev_attr_vbp);
> + device_create_file(&pdev->dev, &dev_attr_vfp);
> + device_create_file(&pdev->dev, &dev_attr_virtual_channel);
> + device_create_file(&pdev->dev, &dev_attr_ipi_color_mode);
> + device_create_file(&pdev->dev, &dev_attr_ipi_auto_flush);
> + device_create_file(&pdev->dev, &dev_attr_ipi_timings_mode);
> + device_create_file(&pdev->dev, &dev_attr_output_type);
> +
> + return 0;
> +}
For configuring the above, we do have (or at least will have for some, such
as VC) proper V4L2 APIs. Please use them, and postpone e.g. VC
configurability until we do.
> 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..d01418d
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-mipi-csi.c
> @@ -0,0 +1,569 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver
> + * Core MIPI CSI-2 functions
> + *
> + * Author: Luis Oliveira <[email protected]>
> + */
> +
> +#include "dw-mipi-csi.h"
> +
> +static struct R_CSI2 reg = {
const. Same below.
> + .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,
Instead I'd add #defines for the register addresses: this is static data
after all.
> +};
> +
> +struct interrupt_type csi_int = {
> + .PHY_FATAL = BIT(0),
> + .PKT_FATAL = BIT(1),
> + .PHY = BIT(16),
> +};
> +
> +#define dw_print(VAR) \
> + dev_info(csi_dev->dev, "%s: 0x%x: %X\n", "#VAR#",\
csi_dev should be an argument for the macro. Also, use parentheses around
the macro arguments.
It'd be better to define this also next to the only user.
> + VAR, dw_mipi_csi_read(csi_dev, VAR))
> +
> +void dw_mipi_csi_write_part(struct dw_csi *dev, u32 address, u32 data,
> + u8 shift, u8 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 dw_csi *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 dw_csi *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_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 dw_csi *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,
> + GENMASK(31, 0));
> +
> + /* common */
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY_FATAL,
> + GENMASK(8, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT_FATAL,
> + GENMASK(1, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY, GENMASK(23, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_LINE, GENMASK(23, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_IPI, GENMASK(5, 0));
> +
> + /* only for version 1.40 */
> + if (csi_dev->hw_version_minor == 40) {
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_BNDRY_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_SEQ_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_CRC_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_PLD_CRC_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_DATA_ID, GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_ECC_CORRECT, GENMASK(31, 0));
> + }
> + }
> + return 0;
> +}
> +
> +void dw_mipi_csi_set_ipi_fmt(struct dw_csi *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);
> + switch (csi_dev->ipi_dt) {
> + case CSI_2_YUV420_8:
> + case CSI_2_YUV420_8_LEG:
> + case CSI_2_YUV420_8_SHIFT:
> + break;
> + case CSI_2_YUV420_10:
> + case CSI_2_YUV420_10_SHIFT:
> + break;
This doesn't do anything.
> + }
> + } else {
> + switch (csi_dev->fmt->mbus_code) {
> + /* RGB 666 */
> + case MEDIA_BUS_FMT_RGB666_1X18:
> + csi_dev->ipi_dt = CSI_2_RGB666;
> + break;
Indentation. Same below.
> + /* RGB 565 */
> + case MEDIA_BUS_FMT_RGB565_2X8_BE:
> + case MEDIA_BUS_FMT_RGB565_2X8_LE:
> + csi_dev->ipi_dt = CSI_2_RGB565;
> + break;
> + /* RGB 555 */
> + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
> + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
> + csi_dev->ipi_dt = CSI_2_RGB555;
> + break;
> + /* RGB 444 */
> + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
> + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
> + csi_dev->ipi_dt = CSI_2_RGB444;
> + break;
> + /* RGB 888 */
> + break;
> + case MEDIA_BUS_FMT_RGB888_2X12_LE:
> + case MEDIA_BUS_FMT_RGB888_2X12_BE:
> + csi_dev->ipi_dt = CSI_2_RGB888;
> + break;
> + /* RAW 10 */
> + case MEDIA_BUS_FMT_SBGGR10_1X10:
> + case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
> + csi_dev->ipi_dt = CSI_2_RAW10;
> + break;
> + /* RAW 12 */
> + case MEDIA_BUS_FMT_SBGGR12_1X12:
> + csi_dev->ipi_dt = CSI_2_RAW12;
> + break;
> + /* RAW 14 */
> + case MEDIA_BUS_FMT_SBGGR14_1X14:
> + csi_dev->ipi_dt = CSI_2_RAW14;
> + break;
> + /* RAW 16 */
> + case MEDIA_BUS_FMT_SBGGR16_1X16:
> + csi_dev->ipi_dt = CSI_2_RAW16;
> + break;
> + /* RAW 8 */
> + case MEDIA_BUS_FMT_SBGGR8_1X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 422 8-bit */
> + case MEDIA_BUS_FMT_YVYU8_2X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 422 10-bit */
> + case MEDIA_BUS_FMT_VYUY8_1X16:
> + csi_dev->ipi_dt = CSI_2_YUV422_8;
> + break;
> + /* YUV 420 8-bit LEGACY */
> + case MEDIA_BUS_FMT_Y8_1X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 420 10-bit */
> + case MEDIA_BUS_FMT_Y10_1X10:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + default:
> + break;
> + }
> + dw_mipi_csi_write(csi_dev, reg.IPI_DATA_TYPE, csi_dev->ipi_dt);
> + }
> + dev_info(dev, "Selected IPI Data Type 0x%X\n", csi_dev->ipi_dt);
Looks like dev_dbg() would be more appropriate.
> +}
> +
> +void dw_mipi_csi_fill_timings(struct dw_csi *dev,
> + struct v4l2_subdev_format *fmt)
> +{
> + /* expected values */
> + dev->hw.virtual_ch = 0;
> + dev->hw.ipi_color_mode = COLOR48;
> + dev->hw.ipi_auto_flush = 1;
> + dev->hw.ipi_mode = CAMERA_TIMING;
> + dev->hw.ipi_cut_through = CTINACTIVE;
> + dev->hw.ipi_adv_features = LINE_EVENT_SELECTION(EVSELAUTO);
> + dev->hw.htotal = fmt->format.width + dev->hw.hsa +
> + dev->hw.hbp + dev->hw.hsd;
> + dev->hw.vactive = fmt->format.height;
> + dev->hw.output = 2;
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> + dev_dbg(dev->dev, "*********** timings *********\n");
> + dev_dbg(dev->dev, "Horizontal Sync Active: %d\n", dev->hw.hsa);
> + dev_dbg(dev->dev, "Horizontal Back Porch: %d\n", dev->hw.hbp);
> + dev_dbg(dev->dev, "Horizontal Width: %d\n", fmt->format.width);
> + dev_dbg(dev->dev, "Horizontal Total: %d\n", dev->hw.htotal);
> + dev_dbg(dev->dev, "Vertical Sync Active: %d\n", dev->hw.vsa);
> + dev_dbg(dev->dev, "Vertical Back Porch: %d\n", dev->hw.vbp);
> + dev_dbg(dev->dev, "Vertical Front Porch: %d\n", dev->hw.vfp);
> + dev_dbg(dev->dev, "Vertical Active: %d\n", dev->hw.vactive);
> + }
> +}
> +
> +void dw_mipi_csi_start(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> +
> + dw_mipi_csi_write(csi_dev, reg.N_LANES, (csi_dev->hw.num_lanes - 1));
> + dev_info(dev, "number of lanes: %d\n", csi_dev->hw.num_lanes);
Ditto.
> +
> + /* 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,
> + csi_dev->hw.ipi_adv_features);
> + 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);
> + if (csi_dev->hw.ipi_mode == CAMERA_TIMING) {
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_ADV_FEATURES,
> + LINE_EVENT_SELECTION(EVSELPROG) |
> + EN_VIDEO |
> + EN_LINE_START |
> + EN_NULL |
> + EN_BLANKING |
> + EN_EMBEDDED);
> + }
No need for braces.
> + 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_MODE,
> + csi_dev->hw.ipi_cut_through,
> + 16, 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);
> +
> + dev_vdbg(dev, "*********** config *********\n");
> + dev_vdbg(dev, "IPI enable: %s\n",
> + csi_dev->hw.output ? "YES" : "NO");
> + dev_vdbg(dev, "video mode transmission type: %s timming\n",
> + csi_dev->hw.ipi_mode ? "controller" : "camera");
> + dev_vdbg(dev, "Color Mode: %s\n",
> + csi_dev->hw.ipi_color_mode ? "16 bits" : "48 bits");
> + dev_vdbg(dev, "Cut Through Mode: %s\n",
> + csi_dev->hw.ipi_cut_through ? "enable" : "disable");
> + dev_vdbg(dev, "Virtual Channel: %d\n",
> + csi_dev->hw.virtual_ch);
> + dev_vdbg(dev, "Auto-flush: %d\n",
> + csi_dev->hw.ipi_auto_flush);
> + dw_mipi_csi_write(csi_dev, reg.IPI_SOFTRSTN, 1);
> +
> + if (csi_dev->hw.ipi_mode == AUTO_TIMING)
> + phy_power_on(csi_dev->phy);
> +
> + 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);
> + 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);
> + }
> + phy_power_on(csi_dev->phy);
> +}
> +
> +int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> + u32 global_int_status, i_sts;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&csi_dev->slock, flags);
> + global_int_status = dw_mipi_csi_read(csi_dev, reg.INTERRUPT);
> +
> + if (global_int_status & csi_int.PHY_FATAL) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PHY_FATAL);
> + dev_err_ratelimited(dev, "int %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, "int %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, "int %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, "int %08X: PHY: %08X\n",
> + reg.INT_PHY, i_sts);
> + }
> +
> + if (global_int_status & csi_int.PKT &&
> + csi_dev->hw_version_major == 1 &&
> + csi_dev->hw_version_minor <= 30) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PKT);
> + dev_err_ratelimited(dev, "int %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, "int %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, "int %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,
> + "int %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,
> + "int %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,
> + "int %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,
> + "int %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, "int %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, "int %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 dw_csi *csi_dev)
> +{
> + u32 hw_version;
> +
> + hw_version = dw_mipi_csi_read(csi_dev, reg.VERSION);
> + csi_dev->hw_version_major = (u8)((hw_version >> 24) - '0');
> + csi_dev->hw_version_minor = (u8)((hw_version >> 16) - '0');
> + csi_dev->hw_version_minor = csi_dev->hw_version_minor * 10;
> + csi_dev->hw_version_minor += (u8)((hw_version >> 8) - '0');
> +}
> +
> +int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> +
> + if (csi_dev->hw_version_major == 1) {
> + if (csi_dev->hw_version_minor == 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) {
> + /*
> + * 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;
Please take this into account in the functions accessing the registers
instead.
Note that here reg is global, not specific to a given device.
> +
> + /* 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;
> +}
> +
> +void dw_mipi_csi_dump(struct dw_csi *csi_dev)
> +{
> + dw_print(reg.VERSION);
> + dw_print(reg.N_LANES);
> + dw_print(reg.CTRL_RESETN);
> + dw_print(reg.INTERRUPT);
> + dw_print(reg.DATA_IDS_1);
> + dw_print(reg.DATA_IDS_2);
> + dw_print(reg.IPI_MODE);
> + dw_print(reg.IPI_VCID);
> + dw_print(reg.IPI_DATA_TYPE);
> + dw_print(reg.IPI_MEM_FLUSH);
> + dw_print(reg.IPI_HSA_TIME);
> + dw_print(reg.IPI_HBP_TIME);
> + dw_print(reg.IPI_HSD_TIME);
> + dw_print(reg.IPI_HLINE_TIME);
> + dw_print(reg.IPI_SOFTRSTN);
> + dw_print(reg.IPI_ADV_FEATURES);
> + dw_print(reg.IPI_VSA_LINES);
> + dw_print(reg.IPI_VBP_LINES);
> + dw_print(reg.IPI_VFP_LINES);
> + dw_print(reg.IPI_VACTIVE_LINES);
> + dw_print(reg.IPI_DATA_TYPE);
> + dw_print(reg.VERSION);
> + dw_print(reg.IPI_ADV_FEATURES);
> +}
> 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..6df3688
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-mipi-csi.h
> @@ -0,0 +1,287 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver
> + *
> + * 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/module.h>
> +#include <linux/io.h>
> +#include <linux/phy/phy.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/ratelimit.h>
> +#include <linux/reset.h>
> +#include <linux/videodev2.h>
> +#include <linux/wait.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/dwc/dw-mipi-csi-pltfrm.h>
> +
> +/* Advanced features */
> +#define IPI_DT_OVERWRITE BIT(0)
> +#define DATA_TYPE_OVERWRITE(dt) (((dt) & GENMASK(5, 0)) << 8)
> +#define LINE_EVENT_SELECTION(n) ((n) << 16)
> +
> +enum line_event {
> + EVSELAUTO = 0,
> + EVSELPROG = 1,
> +};
> +
> +#define EN_VIDEO BIT(17)
> +#define EN_LINE_START BIT(18)
> +#define EN_NULL BIT(19)
> +#define EN_BLANKING BIT(20)
> +#define EN_EMBEDDED BIT(21)
Please align the macro expansions to the same column.
> +#define IPI_SYNC_EVENT_MODE(n) ((n) << 24)
> +
> +enum sync_event {
> + SYNCEVFSN = 0,
> + SYNCEVFS = 1,
> +};
> +
> +/* DW MIPI CSI-2 register addresses*/
> +
> +struct R_CSI2 {
> + u16 VERSION;
Please use lower case names for structs and struct fields.
> + 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,
> + CSI_2_RAW16 = 0x2E,
> + CSI_2_RAW20 = 0x2F,
> + USER_DEFINED_1 = 0x30,
> + USER_DEFINED_2 = 0x31,
> + USER_DEFINED_3 = 0x32,
> + USER_DEFINED_4 = 0x33,
> + USER_DEFINED_5 = 0x34,
> + USER_DEFINED_6 = 0x35,
> + USER_DEFINED_7 = 0x36,
> + USER_DEFINED_8 = 0x37,
> +};
> +
> +/* DWC MIPI CSI-2 output types */
> +enum output {
> + IPI_OUT = 0,
> + IDI_OUT = 1,
> + BOTH_OUT = 2
> +};
> +
> +/* IPI color components */
> +enum color_mode {
> + COLOR48 = 0,
> + COLOR16 = 1
> +};
> +
> +/* IPI cut through */
> +enum cut_through {
> + CTINACTIVE = 0,
> + CTACTIVE = 1
> +};
> +
> +/* IPI output types */
> +enum ipi_output {
> + CAMERA_TIMING = 0,
> + AUTO_TIMING = 1
> +};
> +
> +/* Format template */
> +struct mipi_fmt {
> + u32 mbus_code;
> + u8 depth;
> +};
> +
> +struct mipi_dt {
> + u32 hex;
> + char *name;
> +};
> +
> +/* CSI specific configuration */
> +struct csi_data {
> + u32 num_lanes;
> + u32 dphy_freq;
> + u32 pclk;
> + u32 fps;
> + u32 bpp;
> + u32 output;
> + u32 ipi_mode;
> + u32 ipi_adv_features;
> + u32 ipi_cut_through;
> + u32 ipi_color_mode;
> + u32 ipi_auto_flush;
> + u32 virtual_ch;
> + u32 hsa;
> + u32 hbp;
> + u32 hsd;
> + u32 htotal;
> + u32 vsa;
> + u32 vbp;
> + u32 vfp;
> + u32 vactive;
> +};
> +
> +/* Structure to embed device driver information */
> +struct dw_csi {
> + struct v4l2_subdev sd;
> + struct video_device vdev;
> + struct v4l2_device v4l2_dev;
> + struct device *dev;
> + struct media_pad pads[CSI_PADS_NUM];
> + struct mipi_fmt *fmt;
> + struct v4l2_mbus_framefmt format;
> + void __iomem *base_address;
> + void __iomem *demo;
> + void __iomem *csc;
> + int ctrl_irq_number;
> + int demosaic_irq;
> + struct csi_data hw;
> + struct reset_control *rst;
> + struct phy *phy;
> + struct dw_csih_pdata *config;
> + struct mutex lock; /* protect resources sharing */
> + spinlock_t slock; /* interrupt handling lock */
> + u8 ipi_dt;
> + u8 index;
> + u8 hw_version_major;
> + u16 hw_version_minor;
> +};
> +
> +static inline struct dw_csi *sd_to_mipi_csi_dev(struct v4l2_subdev *sdev)
> +{
> + return container_of(sdev, struct dw_csi, sd);
> +}
> +
> +void dw_mipi_csi_reset(struct dw_csi *csi_dev);
> +int dw_mipi_csi_mask_irq_power_off(struct dw_csi *csi_dev);
> +int dw_mipi_csi_hw_stdby(struct dw_csi *csi_dev);
> +void dw_mipi_csi_set_ipi_fmt(struct dw_csi *csi_dev);
> +void dw_mipi_csi_start(struct dw_csi *csi_dev);
> +int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev);
> +void dw_mipi_csi_get_version(struct dw_csi *csi_dev);
> +int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev);
> +void dw_mipi_csi_fill_timings(struct dw_csi *dev,
> + struct v4l2_subdev_format *fmt);
> +void dw_mipi_csi_dump(struct dw_csi *csi_dev);
> +
> +#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
> +int dw_csi_create_capabilities_sysfs(struct platform_device *pdev);
> +#endif
> +
> +static inline void dw_mipi_csi_write(struct dw_csi *dev,
> + u32 address, u32 data)
> +{
> + writel(data, dev->base_address + address);
> +}
> +
> +static inline u32 dw_mipi_csi_read(struct dw_csi *dev, u32 address)
> +{
> + return readl(dev->base_address + address);
> +}
> +
> +#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..948db4e
> --- /dev/null
> +++ b/include/media/dwc/dw-mipi-csi-pltfrm.h
> @@ -0,0 +1,104 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host media entities
> + *
> + * 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
> +
> +/* 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)
> +
> +struct pdata_names {
> + char *name;
> +};
> +
> +enum video_dev_pads {
> + VIDEO_DEV_SD_PAD_SINK_VIF1,
> + VIDEO_DEV_SD_PAD_SINK_VIF2,
> + VIDEO_DEV_SD_PAD_SOURCE_DMA,
> + VIDEO_DEV_SD_PADS_NUM,
These are unused; please remove.
> +};
> +
> +enum vif_pads {
> + VIF_PAD_SINK_CSI,
> + VIF_PAD_SOURCE_DMA,
> + VIF_PADS_NUM,
> +};
> +
> +enum mipi_csi_pads {
> + CSI_PAD_SINK,
> + CSI_PAD_SOURCE,
> + CSI_PADS_NUM,
> +};
> +
> +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;
> +};
Some of the above appears to be unused. Such as the pipeline ops. Please
remove what is unused by the driver.
> +
> +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)) \
Same here.
> +
> +#endif /* __DW_MIPI_CSI_PLTFRM_INCLUDES_H_ */
--
Regards,
Sakari Ailus
Hi Luis,
On Tue, Jun 11, 2019 at 09:20:52PM +0200, Luis Oliveira wrote:
> This allows the driver loading via platform data which makes the driver
> not device tree dependent.
>
> Signed-off-by: Luis Oliveira <[email protected]>
Platform data? I thought we ceased to add support for platform data long
time ago.
--
Regards,
Sakari Ailus
Hi Luis,
On Tue, Jun 11, 2019 at 09:20:53PM +0200, 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
> v3-v4
> - @Laurent I know I told you I could remove the snps,dphy-frequency on V3 but
> it is really useful for me here. I removed all other the proprietary
> properties except this one. Do you still think it must be removed?
Yes. DT is the wrong place for runtime configuration. You get that
information using the V4L2_CID_LINK_FREQ control on the upstream
sub-device.
> - Frequency units @Rob
>
> .../devicetree/bindings/phy/snps,dw-dphy-rx.txt | 29 ++++++++++++++++++++++
> 1 file changed, 29 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
>
> diff --git a/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt b/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
> new file mode 100644
> index 0000000..50603e6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/snps,dw-dphy-rx.txt
> @@ -0,0 +1,29 @@
> +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,dw-dphy-rx".
> +- #phy-cells : Must be 1.
> +- bus-width : Size of the test interface data bus (8 bits->8 or
> + 12bits->12).
Hmm. This seems like runtime configuration as well.
> +- snps,dphy-frequency : Frequency at which D-PHY should start, configurable.
> + Check Synopsys databook. (-kHz)
> +- reg : Test interface register. This correspondes to the
> + physical base address of the controller and size of
> + the device memory mapped registers; Check Synopsys
> + databook.
Is this just for testing purposes or not?
> +
> +Example:
> +
> + mipi_dphy_rx1: dphy@d00003040 {
> + compatible = "snps,dw-dphy-rx";
> + #phy-cells = <1>;
> + bus-width = <12>;
> + snps,dphy-frequency = <300000>;
> + reg = <0xd0003040 0x20>;
> + };
--
Regards,
Sakari Ailus
On Fri, Aug 09, 2019 at 05:42:52PM +0300, Sakari Ailus wrote:
> > +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,dw-dphy-rx".
> > +- #phy-cells : Must be 1.
> > +- bus-width : Size of the test interface data bus (8 bits->8 or
> > + 12bits->12).
>
> Hmm. This seems like runtime configuration as well.
And it's for the parallel busses.
I'd like to see the bindings that have the other interface described as
well if there are parameters that affect device configuration there.
--
Sakari Ailus
On Fri, Aug 09, 2019 at 05:42:52PM +0300, Sakari Ailus wrote:
> Hi Luis,
>
> On Tue, Jun 11, 2019 at 09:20:53PM +0200, 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
> > v3-v4
> > - @Laurent I know I told you I could remove the snps,dphy-frequency on V3 but
> > it is really useful for me here. I removed all other the proprietary
> > properties except this one. Do you still think it must be removed?
>
> Yes. DT is the wrong place for runtime configuration. You get that
> information using the V4L2_CID_LINK_FREQ control on the upstream
> sub-device.
And the PHY driver itself gets that in its configuration (struct
phy_configure_opts_mipi_dphy through the configure op).
--
Sakari Ailus
On Fri, Aug 9, 2019 at 5:38 PM Sakari Ailus <[email protected]> wrote:
> On Tue, Jun 11, 2019 at 09:20:51PM +0200, Luis Oliveira wrote:
> > Add the Synopsys MIPI CSI-2 controller driver. This
> > controller driver is divided in platform functions and core functions.
> > This way it serves as platform for future DesignWare drivers.
> > +const struct mipi_dt csi_dt[] = {
>
> Make this static or use a common prefix that somehow resembles the name
> name of the driver.
>
> > + {
> > + .hex = CSI_2_YUV420_8,
> > + .name = "YUV420_8bits",
> > + }, {
> > + .hex = CSI_2_YUV420_10,
> > + .name = "YUV420_10bits",
> > + }, {
> > + .hex = CSI_2_YUV420_8_LEG,
> > + .name = "YUV420_8bits_LEGACY",
> > + }, {
> > + .hex = CSI_2_YUV420_8_SHIFT,
> > + .name = "YUV420_8bits_SHIFT",
> > + }, {
> > + .hex = CSI_2_YUV420_10_SHIFT,
> > + .name = "YUV420_10bits_SHIFT",
> > + }, {
> > + .hex = CSI_2_YUV422_8,
> > + .name = "YUV442_8bits",
> > + }, {
> > + .hex = CSI_2_YUV422_10,
> > + .name = "YUV442_10bits",
> > + }, {
> > + .hex = CSI_2_RGB444,
> > + .name = "RGB444",
> > + }, {
> > + .hex = CSI_2_RGB555,
> > + .name = "RGB555",
> > + }, {
> > + .hex = CSI_2_RGB565,
> > + .name = "RGB565",
> > + }, {
> > + .hex = CSI_2_RGB666,
> > + .name = "RGB666",
> > + }, {
> > + .hex = CSI_2_RGB888,
> > + .name = "RGB888",
> > + }, {
> > + .hex = CSI_2_RAW6,
> > + .name = "RAW6",
> > + }, {
> > + .hex = CSI_2_RAW7,
> > + .name = "RAW7",
> > + }, {
> > + .hex = CSI_2_RAW8,
> > + .name = "RAW8",
> > + }, {
> > + .hex = CSI_2_RAW10,
> > + .name = "RAW10",
> > + }, {
> > + .hex = CSI_2_RAW12,
> > + .name = "RAW12",
> > + }, {
> > + .hex = CSI_2_RAW14,
> > + .name = "RAW14",
> > + }, {
> > + .hex = CSI_2_RAW16,
> > + .name = "RAW16",
> > + },
> > +};
One may utilize __stringify() macro and do somelike
#define CSI_FMT_DESC(fmt) \
{ .hex = CSI_2_##fmt, .name = __stringify(fmt), }
And do
CSI_FMT_DESC(RAW16),
etc.
> > + return cfg ? v4l2_subdev_get_try_format(&dev->sd,
> > + cfg,
> > + 0) : NULL;
This indentation looks ugly.
I would rather put this on one line.
> > + dev_dbg(dev->dev,
> > + "%s got v4l2_mbus_pixelcode. 0x%x\n", __func__,
> > + dev->format.code);
> > + dev_dbg(dev->dev,
> > + "%s got width. 0x%x\n", __func__,
> > + dev->format.width);
> > + dev_dbg(dev->dev,
> > + "%s got height. 0x%x\n", __func__,
> > + dev->format.height);
__func__ is usually redundant (if Dynamic Debug in use it can be
switched at run-time).
> I'd just omit these debug prints in a driver. But adding them to the
> framework might make sense. We don't have a lot of debug prints dealing
> with user parameters in there. OTOH the common test programs largely do the
> same already.
I would rather see tracepoints instead of debug prints if we are
talking about generic solution for entire framework.
>
> > + return &dev->format;
> > +}
> > + struct mipi_fmt *dev_fmt;
This is simple bad name. We have dev_fmt() macro. I would rather avoid
potential collisions.
> > + struct v4l2_mbus_framefmt *mf;
> > +
> > + mf = dw_mipi_csi_get_format(dev, cfg, fmt->which);
> > + if (!mf)
> > + return -EINVAL;
Can't you rather return an error pointer in this and similar cases?
> > + dev_vdbg(dev->dev, "%s: on=%d\n", __func__, on);
This is noise. If you would like to debug Function Tracer is a good start.
> > + of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
> > + if (!of_id)
> > + return -EINVAL;
Is it possible to have this asserted?
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (!res)
> > + return -ENXIO;
Redundant. Below does the check for you.
> > +
> > + csi->base_address = devm_ioremap_resource(dev, res);
> > + if (IS_ERR(csi->base_address)) {
> > + dev_err(dev, "Base address not set.\n");
Redundant. Above does print an error message for you.
> > + return PTR_ERR(csi->base_address);
> > + }
Moreover, use devm_platform_ioremap_resource() instead of both.
> > + csi->ctrl_irq_number = platform_get_irq(pdev, 0);
> > + if (csi->ctrl_irq_number < 0) {
> > + dev_err(dev, "irq number %d not set.\n", csi->ctrl_irq_number);
Redundant since this cycle (v5.4).
> > + ret = csi->ctrl_irq_number;
Better to do the opposite
ret = platform_get_irq();
if (ret)
goto end;
... = ret;
> > + goto end;
> > + }
> > + ret = devm_request_irq(dev, csi->ctrl_irq_number,
> > + dw_mipi_csi_irq1, IRQF_SHARED,
> > + dev_name(dev), csi);
> > + if (ret) {
> > + dev_err(dev, "irq csi %s failed\n", of_id->name);
> > +
> > + goto end;
> > + }
devm_*irq() might be a bad idea. Is it race free in your driver?
> > +static const struct of_device_id dw_mipi_csi_of_match[] = {
> > + { .compatible = "snps,dw-csi" },
> > + {},
Better without comma. Terminator may terminate even at compile time.
> > +};
> > +static ssize_t core_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 dw_csi *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);
Oh, can't you simple without any temprorary useless buffers?
sprintf(buf, ...)?
(Yes, note _absence_ of *n* there)
> > +}
> > +static ssize_t n_lanes_store(struct device *dev, struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + int ret;
> > + unsigned long lanes;
> > +
More blank lines! We need them!
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > +
> > + ret = kstrtoul(buf, 10, &lanes);
> > + if (ret < 0)
> > + return ret;
Can it return positive number?
> > + dev_info(dev, "Lanes %lu\n", lanes);
Noise.
The user gets it, why to spam kernel log???
> > + csi_dev->hw.num_lanes = lanes;
> > +
> > + return count;
> > +}
I told once, can repeat again. Synopsys perhaps needs better reviews
inside company. Each time I see the code, it repeats same mistakes
over and over. Have you, guys, do something about it?
--
With Best Regards,
Andy Shevchenko
Hi Sakari, Andy,
From: Andy Shevchenko <[email protected]>
Date: Sat, Aug 10, 2019 at 14:09:21
> On Fri, Aug 9, 2019 at 5:38 PM Sakari Ailus <[email protected]> wrote:
> > On Tue, Jun 11, 2019 at 09:20:51PM +0200, Luis Oliveira wrote:
> > > Add the Synopsys MIPI CSI-2 controller driver. This
> > > controller driver is divided in platform functions and core functions.
> > > This way it serves as platform for future DesignWare drivers.
>
> > > +const struct mipi_dt csi_dt[] = {
> >
> > Make this static or use a common prefix that somehow resembles the name
> > name of the driver.
I will do it.
> >
> > > + {
> > > + .hex = CSI_2_YUV420_8,
> > > + .name = "YUV420_8bits",
> > > + }, {
> > > + .hex = CSI_2_YUV420_10,
> > > + .name = "YUV420_10bits",
> > > + }, {
> > > + .hex = CSI_2_YUV420_8_LEG,
> > > + .name = "YUV420_8bits_LEGACY",
> > > + }, {
> > > + .hex = CSI_2_YUV420_8_SHIFT,
> > > + .name = "YUV420_8bits_SHIFT",
> > > + }, {
> > > + .hex = CSI_2_YUV420_10_SHIFT,
> > > + .name = "YUV420_10bits_SHIFT",
> > > + }, {
> > > + .hex = CSI_2_YUV422_8,
> > > + .name = "YUV442_8bits",
> > > + }, {
> > > + .hex = CSI_2_YUV422_10,
> > > + .name = "YUV442_10bits",
> > > + }, {
> > > + .hex = CSI_2_RGB444,
> > > + .name = "RGB444",
> > > + }, {
> > > + .hex = CSI_2_RGB555,
> > > + .name = "RGB555",
> > > + }, {
> > > + .hex = CSI_2_RGB565,
> > > + .name = "RGB565",
> > > + }, {
> > > + .hex = CSI_2_RGB666,
> > > + .name = "RGB666",
> > > + }, {
> > > + .hex = CSI_2_RGB888,
> > > + .name = "RGB888",
> > > + }, {
> > > + .hex = CSI_2_RAW6,
> > > + .name = "RAW6",
> > > + }, {
> > > + .hex = CSI_2_RAW7,
> > > + .name = "RAW7",
> > > + }, {
> > > + .hex = CSI_2_RAW8,
> > > + .name = "RAW8",
> > > + }, {
> > > + .hex = CSI_2_RAW10,
> > > + .name = "RAW10",
> > > + }, {
> > > + .hex = CSI_2_RAW12,
> > > + .name = "RAW12",
> > > + }, {
> > > + .hex = CSI_2_RAW14,
> > > + .name = "RAW14",
> > > + }, {
> > > + .hex = CSI_2_RAW16,
> > > + .name = "RAW16",
> > > + },
> > > +};
>
> One may utilize __stringify() macro and do somelike
>
> #define CSI_FMT_DESC(fmt) \
> { .hex = CSI_2_##fmt, .name = __stringify(fmt), }
>
> And do
>
> CSI_FMT_DESC(RAW16),
>
> etc.
>
Great, thanks!
> > > + return cfg ? v4l2_subdev_get_try_format(&dev->sd,
> > > + cfg,
> > > + 0) : NULL;
>
> This indentation looks ugly.
> I would rather put this on one line.
>
> > > + dev_dbg(dev->dev,
> > > + "%s got v4l2_mbus_pixelcode. 0x%x\n", __func__,
> > > + dev->format.code);
> > > + dev_dbg(dev->dev,
> > > + "%s got width. 0x%x\n", __func__,
> > > + dev->format.width);
> > > + dev_dbg(dev->dev,
> > > + "%s got height. 0x%x\n", __func__,
> > > + dev->format.height);
>
> __func__ is usually redundant (if Dynamic Debug in use it can be
> switched at run-time).
>
That's true, I don't need it.
> > I'd just omit these debug prints in a driver. But adding them to the
> > framework might make sense. We don't have a lot of debug prints dealing
> > with user parameters in there. OTOH the common test programs largely do the
> > same already.
>
> I would rather see tracepoints instead of debug prints if we are
> talking about generic solution for entire framework.
>
I will check that.
> >
> > > + return &dev->format;
> > > +}
>
> > > + struct mipi_fmt *dev_fmt;
>
> This is simple bad name. We have dev_fmt() macro. I would rather avoid
> potential collisions.
True, I will change the name.
>
> > > + struct v4l2_mbus_framefmt *mf;
> > > +
> > > + mf = dw_mipi_csi_get_format(dev, cfg, fmt->which);
> > > + if (!mf)
> > > + return -EINVAL;
>
> Can't you rather return an error pointer in this and similar cases?
>
Yes, ofc.
> > > + dev_vdbg(dev->dev, "%s: on=%d\n", __func__, on);
>
> This is noise. If you would like to debug Function Tracer is a good start.
>
Ok.
> > > + of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
> > > + if (!of_id)
> > > + return -EINVAL;
>
> Is it possible to have this asserted?
>
I will remove it.
> > > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>
> > > + if (!res)
> > > + return -ENXIO;
>
> Redundant. Below does the check for you.
>
Yep, thanks.
> > > +
> > > + csi->base_address = devm_ioremap_resource(dev, res);
> > > + if (IS_ERR(csi->base_address)) {
>
> > > + dev_err(dev, "Base address not set.\n");
>
> Redundant. Above does print an error message for you.
>
Ok.
> > > + return PTR_ERR(csi->base_address);
> > > + }
>
> Moreover, use devm_platform_ioremap_resource() instead of both.
>
Nice, thanks.
> > > + csi->ctrl_irq_number = platform_get_irq(pdev, 0);
> > > + if (csi->ctrl_irq_number < 0) {
>
> > > + dev_err(dev, "irq number %d not set.\n", csi->ctrl_irq_number);
>
> Redundant since this cycle (v5.4).
>
Ok,
> > > + ret = csi->ctrl_irq_number;
>
> Better to do the opposite
>
> ret = platform_get_irq();
> if (ret)
> goto end;
> ... = ret;
>
> > > + goto end;
> > > + }
>
> > > + ret = devm_request_irq(dev, csi->ctrl_irq_number,
> > > + dw_mipi_csi_irq1, IRQF_SHARED,
> > > + dev_name(dev), csi);
> > > + if (ret) {
> > > + dev_err(dev, "irq csi %s failed\n", of_id->name);
> > > +
> > > + goto end;
> > > + }
>
> devm_*irq() might be a bad idea. Is it race free in your driver?
>
I never thought about it like that. Should I use request_irq and
free_irq?
> > > +static const struct of_device_id dw_mipi_csi_of_match[] = {
> > > + { .compatible = "snps,dw-csi" },
>
> > > + {},
>
> Better without comma. Terminator may terminate even at compile time.
>
Ok.
> > > +};
>
> > > +static ssize_t core_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 dw_csi *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);
>
> Oh, can't you simple without any temprorary useless buffers?
> sprintf(buf, ...)?
> (Yes, note _absence_ of *n* there)
You are right.
>
> > > +}
>
> > > +static ssize_t n_lanes_store(struct device *dev, struct device_attribute *attr,
> > > + const char *buf, size_t count)
> > > +{
> > > + int ret;
> > > + unsigned long lanes;
>
> > > +
>
> More blank lines! We need them!
>
Ok.
> > > + struct platform_device *pdev = to_platform_device(dev);
> > > + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> > > + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> > > +
> > > + ret = kstrtoul(buf, 10, &lanes);
> > > + if (ret < 0)
> > > + return ret;
>
> Can it return positive number?
>
> > > + dev_info(dev, "Lanes %lu\n", lanes);
>
> Noise.
> The user gets it, why to spam kernel log???
>
Ok.
> > > + csi_dev->hw.num_lanes = lanes;
> > > +
> > > + return count;
> > > +}
>
> I told once, can repeat again. Synopsys perhaps needs better reviews
> inside company. Each time I see the code, it repeats same mistakes
> over and over. Have you, guys, do something about it?
We are working on it. It will get better, sorry.
>
> --
> With Best Regards,
> Andy Shevchenko
Thanks,
Luis
On Mon, Aug 12, 2019 at 12:45 PM Luis de Oliveira
<[email protected]> wrote:
> From: Andy Shevchenko <[email protected]>
> Date: Sat, Aug 10, 2019 at 14:09:21
> > On Fri, Aug 9, 2019 at 5:38 PM Sakari Ailus <[email protected]> wrote:
> > > On Tue, Jun 11, 2019 at 09:20:51PM +0200, Luis Oliveira wrote:
> > > > + of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
> > > > + if (!of_id)
> > > > + return -EINVAL;
> >
> > Is it possible to have this asserted?
> >
>
> I will remove it.
But please double check that is really the case.
> > > > + ret = devm_request_irq(dev, csi->ctrl_irq_number,
> > > > + dw_mipi_csi_irq1, IRQF_SHARED,
> > > > + dev_name(dev), csi);
> > > > + if (ret) {
> > > > + dev_err(dev, "irq csi %s failed\n", of_id->name);
> > > > +
> > > > + goto end;
> > > > + }
> >
> > devm_*irq() might be a bad idea. Is it race free in your driver?
> >
>
> I never thought about it like that. Should I use request_irq and
> free_irq?
It's you, author of the driver, who knows it better :-)
> > I told once, can repeat again. Synopsys perhaps needs better reviews
> > inside company. Each time I see the code, it repeats same mistakes
> > over and over. Have you, guys, do something about it?
>
> We are working on it. It will get better, sorry.
Thanks! Hope to see a progress!
--
With Best Regards,
Andy Shevchenko