* [PATCH 0/7] media: add imx93 mipi/controller csi support
@ 2025-07-01 22:06 Frank Li
2025-07-01 22:06 ` [PATCH 1/7] dt-bindings: media: add DW MIPI CSI-2 Host support Frank Li
` (6 more replies)
0 siblings, 7 replies; 20+ messages in thread
From: Frank Li @ 2025-07-01 22:06 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li, Luis Oliveira, Guoniu.zhou, Jindong Yue,
Robby Cai, Eugen Hristev
Add camera support for imx93. MIPI CSI2 use Sysnosys designware MIPI CSI2
controller.
binding doc: dt-bindings: media: add DW MIPI CSI-2 Host support
drivers: media: nxp: add designware MIPI CSI2 controller driver
i.MX93 provide DWC CSI2 DPHY wrapper layer, which module as standard phy
driver. The controller register lay in imx-blk-ctrl.
binding doc: dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support
phy: freescale: add imx93 MIPI CSI2 DPHY support
Dts part:
common part: dts: arm64: imx93: add camera related nodes
board level depend on AP1302 sensor driver, which have not upstreamed yet.
Leave here just converince for reviewer to do test. Please skip review below
2 patches
[NOT MERGE]media: i2c: add AP1302 driver from community
[NOT MERGE]arm64: dts: imx93-11x11-evk: add camera related nodes
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
Eugen Hristev (1):
dt-bindings: media: add DW MIPI CSI-2 Host support
Frank Li (4):
dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support
pmdomain: imx93-blk-ctrl: populate child devices
phy: freescale: add imx93 MIPI CSI2 DPHY support
[NOT MERGE] arm64: dts: imx93-11x11-evk: add camera related nodes
Guoniu.zhou (2):
media: nxp: add DesignWare MIPI CSI2 controller driver
[NOT MERGE]media: i2c: add AP1302 driver from community
.../bindings/media/snps,dw-mipi-csi2-v150.yaml | 161 ++
.../bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml | 28 +
MAINTAINERS | 2 +
arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts | 139 +
drivers/media/i2c/Kconfig | 13 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/ap1302.c | 2920 ++++++++++++++++++++
drivers/media/platform/nxp/Kconfig | 11 +
drivers/media/platform/nxp/Makefile | 1 +
drivers/media/platform/nxp/dwc-mipi-csi2.c | 1675 +++++++++++
drivers/phy/freescale/Kconfig | 10 +
drivers/phy/freescale/Makefile | 1 +
drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c | 306 ++
drivers/pmdomain/imx/imx93-blk-ctrl.c | 3 +-
14 files changed, 5270 insertions(+), 1 deletion(-)
---
base-commit: 778df2ca2914864b0876d8c9d7383a5f4293f43c
change-id: 20250606-95_cam-b74c094ad1fb
Best regards,
---
Frank Li <Frank.Li@nxp.com>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 1/7] dt-bindings: media: add DW MIPI CSI-2 Host support
2025-07-01 22:06 [PATCH 0/7] media: add imx93 mipi/controller csi support Frank Li
@ 2025-07-01 22:06 ` Frank Li
2025-07-02 6:38 ` Krzysztof Kozlowski
2025-07-01 22:06 ` [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support Frank Li
` (5 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Frank Li @ 2025-07-01 22:06 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li, Luis Oliveira, Eugen Hristev
From: Eugen Hristev <eugen.hristev@linaro.org>
Add bindings for Synopsys DesignWare MIPI CSI-2 host, which used at i.MX93
and i.MX95 platform.
Signed-off-by: Luis Oliveira <lolivei@synopsys.com>
Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
This is continue previous thread
https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20221216143717.1002015-2-eugen.hristev@microchip.com/#3023663
change in v1 (compared to previous post)
- add reg-names
- remove clk surfix
- add imx93,dw-csi compatible string
- add dphys subnode name
- use compatible string snps,dw-mipi-csi2-v150 to avoid use general fallback
compatible string.
---
.../bindings/media/snps,dw-mipi-csi2-v150.yaml | 161 +++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 162 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml b/Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml
new file mode 100644
index 0000000000000..2a93bd72498f8
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml
@@ -0,0 +1,161 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/snps,dw-mipi-csi2-v150.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Synopsys DesignWare CSI-2 Host controller (csi2host)
+
+maintainers:
+ - Frank Li <Frank.Li@nxp.com>
+ - Eugen Hristev <eugen.hristev@microchip.com>
+
+description:
+ CSI2HOST is used to receive image coming from an MIPI CSI-2 compatible
+ camera. It will convert the incoming CSI-2 stream into a dedicated
+ interface called the Synopsys IDI (Image Data Interface).
+ This interface is a 32-bit SoC internal only, and can be assimilated
+ with a CSI-2 interface.
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - enum:
+ - fsl,imx93-mipi-csi2
+ - const: snps,dw-mipi-csi2-v150
+ - const: snps,dw-mipi-csi2-v150
+
+ reg:
+ items:
+ - description: MIPI CSI-2 core register
+
+ reg-names:
+ items:
+ - const: core
+
+ clocks:
+ maxItems: 2
+
+ clock-names:
+ items:
+ - const: per
+ - const: pixel
+
+ phys:
+ maxItems: 1
+ description: MIPI D-PHY
+
+ phy-names:
+ items:
+ - const: rx
+
+ resets:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description:
+ Input port node, single endpoint describing the input port.
+
+ properties:
+ endpoint:
+ $ref: video-interfaces.yaml#
+ unevaluatedProperties: false
+ description: Endpoint connected to input device
+
+ properties:
+ bus-type:
+ const: 4
+
+ data-lanes:
+ minItems: 1
+ maxItems: 4
+ items:
+ maximum: 4
+
+ clock-lanes:
+ maxItems: 1
+
+ remote-endpoint: true
+
+ port@1:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description:
+ Output port node, single endpoint describing the output port.
+
+ properties:
+ endpoint:
+ unevaluatedProperties: false
+ $ref: video-interfaces.yaml#
+ description: Endpoint connected to output device
+
+ properties:
+ bus-type:
+ const: 4
+
+ remote-endpoint: true
+
+ required:
+ - port@0
+ - port@1
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - ports
+
+examples:
+ - |
+ csi@3000 {
+ compatible = "snps,dw-mipi-csi2-v150";
+ reg = <0x03000 0x1000>;
+ reg-names = "core";
+ phys = <&mipi_dphy_rx 0>;
+ phy-names = "rx";
+ resets = <&dw_rst 1>;
+ interrupts = <2>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ endpoint {
+ bus-type = <4>; /* MIPI CSI2 D-PHY */
+ remote-endpoint = <&camera_1>;
+ data-lanes = <1 2>;
+ clock-lanes = <0>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ endpoint {
+ remote-endpoint = <&idi_receiver>;
+ bus-type = <4>;
+ };
+ };
+ };
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 01d2aa62045c7..e208c91f1405f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15021,6 +15021,7 @@ F: Documentation/devicetree/bindings/media/fsl,imx93-parallel-csi.yaml
F: Documentation/devicetree/bindings/media/nxp,imx-mipi-csi2.yaml
F: Documentation/devicetree/bindings/media/nxp,imx7-csi.yaml
F: Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml
+F: Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml
F: drivers/media/platform/nxp/imx-mipi-csis.c
F: drivers/media/platform/nxp/imx-parallel-csi.c
F: drivers/media/platform/nxp/imx7-media-csi.c
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support
2025-07-01 22:06 [PATCH 0/7] media: add imx93 mipi/controller csi support Frank Li
2025-07-01 22:06 ` [PATCH 1/7] dt-bindings: media: add DW MIPI CSI-2 Host support Frank Li
@ 2025-07-01 22:06 ` Frank Li
2025-07-02 6:40 ` Krzysztof Kozlowski
2025-07-02 6:42 ` Krzysztof Kozlowski
2025-07-01 22:06 ` [PATCH 3/7] pmdomain: imx93-blk-ctrl: populate child devices Frank Li
` (4 subsequent siblings)
6 siblings, 2 replies; 20+ messages in thread
From: Frank Li @ 2025-07-01 22:06 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li
Add child node dphy-rx, which export phys interface to MIPI CSI2
controller.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
imx media blk ctrl combined many small glue logic registers for display
, camera, power, clk and reset.
There are some discussion at
https://lore.kernel.org/all/4414669.ejJDZkT8p0@steina-w/
to define whole schema for this.
But there are some difficults to cover whole once.
Plan use two steps to complete it
- add all camera related property and subnode
- add all display related property and subnode
---
.../bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml | 28 ++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/Documentation/devicetree/bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml b/Documentation/devicetree/bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml
index b3554e7f9e76d..708cd00f2b896 100644
--- a/Documentation/devicetree/bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml
+++ b/Documentation/devicetree/bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml
@@ -46,6 +46,34 @@ properties:
- const: csi
- const: dsi
+ dphy-rx:
+ type: object
+ properties:
+ compatible:
+ enum:
+ - fsl,imx93-dphy-rx
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: cfg
+
+ '#phy-cells':
+ const: 0
+
+ power-domains:
+ maxItems: 1
+
+ required:
+ - compatible
+ - clocks
+ - clock-names
+ - '#phy-cells'
+
+ additionalProperties: false
+
required:
- compatible
- reg
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 3/7] pmdomain: imx93-blk-ctrl: populate child devices
2025-07-01 22:06 [PATCH 0/7] media: add imx93 mipi/controller csi support Frank Li
2025-07-01 22:06 ` [PATCH 1/7] dt-bindings: media: add DW MIPI CSI-2 Host support Frank Li
2025-07-01 22:06 ` [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support Frank Li
@ 2025-07-01 22:06 ` Frank Li
2025-07-02 5:15 ` Alexander Stein
2025-07-01 22:06 ` [PATCH 4/7] phy: freescale: add imx93 MIPI CSI2 DPHY support Frank Li
` (3 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Frank Li @ 2025-07-01 22:06 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li
imx93-blk-ctrl is miscellaneous devices, which include reset, clock, MIPI
CSI2 PHY and DSI's miscellaneous logic. Call of_platform_populate() to
probe child nodes.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/pmdomain/imx/imx93-blk-ctrl.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/pmdomain/imx/imx93-blk-ctrl.c b/drivers/pmdomain/imx/imx93-blk-ctrl.c
index 0e2ba8ec55d75..ba2dd8bd143d1 100644
--- a/drivers/pmdomain/imx/imx93-blk-ctrl.c
+++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c
@@ -7,6 +7,7 @@
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
@@ -297,7 +298,7 @@ static int imx93_blk_ctrl_probe(struct platform_device *pdev)
dev_set_drvdata(dev, bc);
- return 0;
+ return of_platform_populate(dev->of_node, NULL, NULL, dev);
cleanup_pds:
for (i--; i >= 0; i--)
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 4/7] phy: freescale: add imx93 MIPI CSI2 DPHY support
2025-07-01 22:06 [PATCH 0/7] media: add imx93 mipi/controller csi support Frank Li
` (2 preceding siblings ...)
2025-07-01 22:06 ` [PATCH 3/7] pmdomain: imx93-blk-ctrl: populate child devices Frank Li
@ 2025-07-01 22:06 ` Frank Li
2025-07-02 5:50 ` Alexander Stein
2025-07-01 22:06 ` [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver Frank Li
` (2 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Frank Li @ 2025-07-01 22:06 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li, Guoniu.zhou, Jindong Yue
Add driver i.MX93 MIPI DPHY controller, which is wrapper for Synosys MIPI
CSI2 DPHY module.
Base on
https://github.com/nxp-imx/linux-imx/blob/lf-6.12.y/drivers/phy/freescale/phy-fsl-imx9-dphy-rx.c
Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
Signed-off-by: Jindong Yue <jindong.yue@nxp.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
drivers/phy/freescale/Kconfig | 10 +
drivers/phy/freescale/Makefile | 1 +
drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c | 306 ++++++++++++++++++++++++++
3 files changed, 317 insertions(+)
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index 81f53564ee156..cb34e151e86c4 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -44,6 +44,16 @@ config PHY_FSL_IMX8QM_HSIO
Enable this to add support for the HSIO PHY as found on
i.MX8QM family of SOCs.
+config PHY_FSL_IMX93_DPHY_RX
+ tristate "Freescale i.MX9 DPHY Rx"
+ depends on OF && HAS_IOMEM
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ select REGMAP_MMIO
+ help
+ Enable this to add support for the Synopsys DW DPHY Rx as found
+ on NXP's i.MX9 family.
+
config PHY_FSL_SAMSUNG_HDMI_PHY
tristate "Samsung HDMI PHY support"
depends on OF && HAS_IOMEM && COMMON_CLK
diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
index 658eac7d0a622..8e122a07695f0 100644
--- a/drivers/phy/freescale/Makefile
+++ b/drivers/phy/freescale/Makefile
@@ -4,5 +4,6 @@ obj-$(CONFIG_PHY_MIXEL_LVDS_PHY) += phy-fsl-imx8qm-lvds-phy.o
obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o
obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) += phy-fsl-imx8m-pcie.o
obj-$(CONFIG_PHY_FSL_IMX8QM_HSIO) += phy-fsl-imx8qm-hsio.o
+obj-$(CONFIG_PHY_FSL_IMX93_DPHY_RX) += phy-fsl-imx93-dphy-rx.o
obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o
obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) += phy-fsl-samsung-hdmi.o
diff --git a/drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c b/drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c
new file mode 100644
index 0000000000000..f5155ae68c50f
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define IMX93_BLK_CSI 0x48
+#define IMX93_BLK_CSI_CFGCLKFREQRANGE GENMASK(5, 0)
+#define IMX93_BLK_CSI_HSFREQRANGE GENMASK(14, 8)
+
+struct fsl_csi2_phy_drv_data {
+ u32 max_lanes;
+ u32 max_data_rate; /* Mbps */
+};
+
+struct fsl_csi2_phy {
+ struct device *dev;
+ struct regmap *dphy_regmap;
+ struct clk *cfg_clk;
+
+ const struct fsl_csi2_phy_drv_data *drv_data;
+
+ u16 hsfreqrange;
+ u16 cfgclkfreqrange;
+ u16 ddlfreq;
+};
+
+struct dphy_mbps_hsfreqrange_map {
+ u16 mbps;
+ u16 hsfreqrange;
+ u16 ddlfreq;
+};
+
+/*
+ * Data rate to high speed frequency range map table
+ */
+static const struct dphy_mbps_hsfreqrange_map hsfreqrange_table[] = {
+ { .mbps = 80, .hsfreqrange = 0x00, .ddlfreq = 489 },
+ { .mbps = 90, .hsfreqrange = 0x10, .ddlfreq = 489 },
+ { .mbps = 100, .hsfreqrange = 0x20, .ddlfreq = 489 },
+ { .mbps = 110, .hsfreqrange = 0x30, .ddlfreq = 489 },
+ { .mbps = 120, .hsfreqrange = 0x01, .ddlfreq = 489 },
+ { .mbps = 130, .hsfreqrange = 0x11, .ddlfreq = 489 },
+ { .mbps = 140, .hsfreqrange = 0x21, .ddlfreq = 489 },
+ { .mbps = 150, .hsfreqrange = 0x31, .ddlfreq = 489 },
+ { .mbps = 160, .hsfreqrange = 0x02, .ddlfreq = 489 },
+ { .mbps = 170, .hsfreqrange = 0x12, .ddlfreq = 489 },
+ { .mbps = 180, .hsfreqrange = 0x22, .ddlfreq = 489 },
+ { .mbps = 190, .hsfreqrange = 0x32, .ddlfreq = 489 },
+ { .mbps = 205, .hsfreqrange = 0x03, .ddlfreq = 489 },
+ { .mbps = 220, .hsfreqrange = 0x13, .ddlfreq = 489 },
+ { .mbps = 235, .hsfreqrange = 0x23, .ddlfreq = 489 },
+ { .mbps = 250, .hsfreqrange = 0x33, .ddlfreq = 489 },
+ { .mbps = 275, .hsfreqrange = 0x04, .ddlfreq = 489 },
+ { .mbps = 300, .hsfreqrange = 0x14, .ddlfreq = 489 },
+ { .mbps = 325, .hsfreqrange = 0x25, .ddlfreq = 489 },
+ { .mbps = 350, .hsfreqrange = 0x35, .ddlfreq = 489 },
+ { .mbps = 400, .hsfreqrange = 0x05, .ddlfreq = 489 },
+ { .mbps = 450, .hsfreqrange = 0x16, .ddlfreq = 489 },
+ { .mbps = 500, .hsfreqrange = 0x26, .ddlfreq = 489 },
+ { .mbps = 550, .hsfreqrange = 0x37, .ddlfreq = 489 },
+ { .mbps = 600, .hsfreqrange = 0x07, .ddlfreq = 489 },
+ { .mbps = 650, .hsfreqrange = 0x18, .ddlfreq = 489 },
+ { .mbps = 700, .hsfreqrange = 0x28, .ddlfreq = 489 },
+ { .mbps = 750, .hsfreqrange = 0x39, .ddlfreq = 489 },
+ { .mbps = 800, .hsfreqrange = 0x09, .ddlfreq = 489 },
+ { .mbps = 850, .hsfreqrange = 0x19, .ddlfreq = 489 },
+ { .mbps = 900, .hsfreqrange = 0x29, .ddlfreq = 489 },
+ { .mbps = 950, .hsfreqrange = 0x3a, .ddlfreq = 489 },
+ { .mbps = 1000, .hsfreqrange = 0x0a, .ddlfreq = 489 },
+ { .mbps = 1050, .hsfreqrange = 0x1a, .ddlfreq = 489 },
+ { .mbps = 1100, .hsfreqrange = 0x2a, .ddlfreq = 489 },
+ { .mbps = 1150, .hsfreqrange = 0x3b, .ddlfreq = 489 },
+ { .mbps = 1200, .hsfreqrange = 0x0b, .ddlfreq = 489 },
+ { .mbps = 1250, .hsfreqrange = 0x1b, .ddlfreq = 489 },
+ { .mbps = 1300, .hsfreqrange = 0x2b, .ddlfreq = 489 },
+ { .mbps = 1350, .hsfreqrange = 0x3c, .ddlfreq = 489 },
+ { .mbps = 1400, .hsfreqrange = 0x0c, .ddlfreq = 489 },
+ { .mbps = 1450, .hsfreqrange = 0x1c, .ddlfreq = 489 },
+ { .mbps = 1500, .hsfreqrange = 0x2c, .ddlfreq = 489 },
+ { .mbps = 1550, .hsfreqrange = 0x3d, .ddlfreq = 303 },
+ { .mbps = 1600, .hsfreqrange = 0x0d, .ddlfreq = 313 },
+ { .mbps = 1650, .hsfreqrange = 0x1d, .ddlfreq = 323 },
+ { .mbps = 1700, .hsfreqrange = 0x2e, .ddlfreq = 333 },
+ { .mbps = 1750, .hsfreqrange = 0x3e, .ddlfreq = 342 },
+ { .mbps = 1800, .hsfreqrange = 0x0e, .ddlfreq = 352 },
+ { .mbps = 1850, .hsfreqrange = 0x1e, .ddlfreq = 362 },
+ { .mbps = 1900, .hsfreqrange = 0x1f, .ddlfreq = 372 },
+ { .mbps = 1950, .hsfreqrange = 0x3f, .ddlfreq = 381 },
+ { .mbps = 2000, .hsfreqrange = 0x0f, .ddlfreq = 391 },
+ { .mbps = 2050, .hsfreqrange = 0x40, .ddlfreq = 401 },
+ { .mbps = 2100, .hsfreqrange = 0x41, .ddlfreq = 411 },
+ { .mbps = 2150, .hsfreqrange = 0x42, .ddlfreq = 411 },
+ { .mbps = 2200, .hsfreqrange = 0x43, .ddlfreq = 411 },
+ { .mbps = 2250, .hsfreqrange = 0x44, .ddlfreq = 411 },
+ { .mbps = 2300, .hsfreqrange = 0x45, .ddlfreq = 411 },
+ { .mbps = 2350, .hsfreqrange = 0x46, .ddlfreq = 411 },
+ { .mbps = 2400, .hsfreqrange = 0x47, .ddlfreq = 411 },
+ { .mbps = 2450, .hsfreqrange = 0x48, .ddlfreq = 411 },
+ { .mbps = 2500, .hsfreqrange = 0x49, .ddlfreq = 411 },
+ { /* sentinel */ },
+};
+
+static int fsl_csi2_phy_init(struct phy *phy)
+{
+ struct fsl_csi2_phy *priv = phy_get_drvdata(phy);
+
+ return pm_runtime_get_sync(priv->dev);
+}
+
+static int fsl_csi2_phy_exit(struct phy *phy)
+{
+ struct fsl_csi2_phy *priv = phy_get_drvdata(phy);
+
+ return pm_runtime_put(priv->dev);
+}
+
+static int fsl_csi2_phy_power_on(struct phy *phy)
+{
+ struct fsl_csi2_phy *priv = phy_get_drvdata(phy);
+
+ regmap_update_bits(priv->dphy_regmap, IMX93_BLK_CSI,
+ IMX93_BLK_CSI_CFGCLKFREQRANGE,
+ FIELD_PREP(IMX93_BLK_CSI_CFGCLKFREQRANGE, priv->cfgclkfreqrange));
+
+ regmap_update_bits(priv->dphy_regmap, IMX93_BLK_CSI,
+ IMX93_BLK_CSI_HSFREQRANGE,
+ FIELD_PREP(IMX93_BLK_CSI_HSFREQRANGE, priv->hsfreqrange));
+
+ return 0;
+}
+
+static int set_freqrange_by_mpbs(struct fsl_csi2_phy *priv, u64 mbps)
+{
+ const struct dphy_mbps_hsfreqrange_map *prev_value = NULL;
+ const struct dphy_mbps_hsfreqrange_map *value;
+
+ for (value = hsfreqrange_table; value->mbps; value++) {
+ if (value->mbps >= mbps)
+ break;
+ prev_value = value;
+ }
+
+ if (prev_value &&
+ ((mbps - prev_value->mbps) <= (value->mbps - mbps)))
+ value = prev_value;
+
+ if (!value->mbps) {
+ pr_err("Unsupported PHY speed (%llu Mbps)", mbps);
+ return -ERANGE;
+ }
+
+ priv->hsfreqrange = value->hsfreqrange;
+ priv->ddlfreq = value->ddlfreq;
+
+ return 0;
+}
+
+static int fsl_csi2_phy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct fsl_csi2_phy *priv = phy_get_drvdata(phy);
+ const struct fsl_csi2_phy_drv_data *drv_data = priv->drv_data;
+ struct phy_configure_opts_mipi_dphy *config = &opts->mipi_dphy;
+ struct device *dev = priv->dev;
+ u64 data_rate_mbps;
+ int ret;
+
+ if (config->lanes > drv_data->max_lanes) {
+ dev_err(dev, "The number of lanes has exceeded the maximum value\n");
+ return -EINVAL;
+ }
+
+ data_rate_mbps = div_u64(config->hs_clk_rate, 1000 * 1000);
+ if (data_rate_mbps < 80 ||
+ data_rate_mbps > drv_data->max_data_rate) {
+ dev_err(dev, "Out-of-bound lane rate %llu\n", data_rate_mbps);
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "Number of lanes: %d, data rate=%llu(Mbps)\n",
+ config->lanes, data_rate_mbps);
+
+ ret = set_freqrange_by_mpbs(priv, data_rate_mbps);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct phy_ops fsl_csi2_phy_ops = {
+ .init = fsl_csi2_phy_init,
+ .exit = fsl_csi2_phy_exit,
+ .power_on = fsl_csi2_phy_power_on,
+ .configure = fsl_csi2_phy_configure,
+ .owner = THIS_MODULE,
+};
+
+static const struct fsl_csi2_phy_drv_data imx93_dphy_drvdata = {
+ .max_lanes = 2,
+ .max_data_rate = 1500,
+};
+
+static int fsl_csi2_runtime_suspend(struct device *dev)
+{
+ struct fsl_csi2_phy *priv = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(priv->cfg_clk);
+
+ return 0;
+}
+
+static int fsl_csi2_runtime_resume(struct device *dev)
+{
+ struct fsl_csi2_phy *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(priv->cfg_clk);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(fsl_csi2_pm_ops, fsl_csi2_runtime_suspend,
+ fsl_csi2_runtime_resume, NULL);
+
+static const struct of_device_id fsl_csi2_phy_of_match[] = {
+ { .compatible = "fsl,imx93-dphy-rx", .data = &imx93_dphy_drvdata},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_csi2_phy_of_match);
+
+static int fsl_csi2_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ struct fsl_csi2_phy *priv;
+ unsigned long cfg_rate;
+ struct phy *phy;
+
+ if (!dev->parent || !dev->parent->of_node)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->drv_data = of_device_get_match_data(dev);
+
+ platform_set_drvdata(pdev, priv);
+
+ priv->dphy_regmap = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(priv->dphy_regmap))
+ dev_err_probe(dev, -ENODEV, "Failed to DPHY regmap\n");
+
+ priv->cfg_clk = devm_clk_get(dev, "cfg");
+ if (IS_ERR(priv->cfg_clk))
+ dev_err_probe(dev, PTR_ERR(priv->cfg_clk), "Failed to get DPHY config clock\n");
+
+ /* cfgclkfreqrange[5:0] = round[(cfg_clk(MHz) - 17) * 4] */
+ cfg_rate = clk_get_rate(priv->cfg_clk);
+ if (!cfg_rate)
+ dev_err_probe(dev, -EINVAL, "Failed to get PHY config clock rate\n");
+
+ priv->cfgclkfreqrange = (div_u64(cfg_rate, 1000 * 1000) - 17) * 4;
+
+ phy = devm_phy_create(dev, np, &fsl_csi2_phy_ops);
+ if (IS_ERR(phy))
+ return dev_err_probe(dev, -ENODEV, "Failed to create PHY\n");
+
+ phy_set_drvdata(phy, priv);
+
+ pm_runtime_set_suspended(dev);
+ devm_pm_runtime_enable(dev);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver fsl_csi2_phy_driver = {
+ .probe = fsl_csi2_phy_probe,
+ .driver = {
+ .name = "imx-mipi-dphy-rx",
+ .pm = pm_ptr(&fsl_csi2_pm_ops),
+ .of_match_table = fsl_csi2_phy_of_match,
+ }
+};
+module_platform_driver(fsl_csi2_phy_driver);
+
+MODULE_DESCRIPTION("i.MX9 Synopsys DesignWare MIPI DPHY Rx wrapper driver");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver
2025-07-01 22:06 [PATCH 0/7] media: add imx93 mipi/controller csi support Frank Li
` (3 preceding siblings ...)
2025-07-01 22:06 ` [PATCH 4/7] phy: freescale: add imx93 MIPI CSI2 DPHY support Frank Li
@ 2025-07-01 22:06 ` Frank Li
2025-07-02 6:04 ` Alexander Stein
` (2 more replies)
2025-07-01 22:06 ` [PATCH NOT MERGE 6/7] arm64: dts: imx93-11x11-evk: add camera related nodes Frank Li
2025-07-01 22:06 ` [PATCH NOT MERGE 7/7] media: i2c: add AP1302 driver from community Frank Li
6 siblings, 3 replies; 20+ messages in thread
From: Frank Li @ 2025-07-01 22:06 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li, Guoniu.zhou
From: "Guoniu.zhou" <guoniu.zhou@nxp.com>
Add V4L2 subdev driver for DesignWare MIPI CSI2 controller.
Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
MAINTAINERS | 1 +
drivers/media/platform/nxp/Kconfig | 11 +
drivers/media/platform/nxp/Makefile | 1 +
drivers/media/platform/nxp/dwc-mipi-csi2.c | 1675 ++++++++++++++++++++++++++++
4 files changed, 1688 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e208c91f1405f..f67db23cb9d6f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15022,6 +15022,7 @@ F: Documentation/devicetree/bindings/media/nxp,imx-mipi-csi2.yaml
F: Documentation/devicetree/bindings/media/nxp,imx7-csi.yaml
F: Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml
F: Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml
+F: drivers/media/platform/nxp/dwc-mipi-csi2.c
F: drivers/media/platform/nxp/imx-mipi-csis.c
F: drivers/media/platform/nxp/imx-parallel-csi.c
F: drivers/media/platform/nxp/imx7-media-csi.c
diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig
index 5df6f97d16f29..b7a4aed706443 100644
--- a/drivers/media/platform/nxp/Kconfig
+++ b/drivers/media/platform/nxp/Kconfig
@@ -4,6 +4,17 @@
comment "NXP media platform drivers"
+config VIDEO_DWC_MIPI_CSIS
+ tristate "DesignWare Cores MIPI CSI-2 receiver found on i.MX93"
+ depends on ARCH_MXC || COMPILE_TEST
+ depends on VIDEO_DEV
+ select MEDIA_CONTROLLER
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
+ help
+ Video4Linux2 sub-device driver for the DesignWare Cores MIPI
+ CSI-2 receiver used on i.MX93.
+
config VIDEO_IMX7_CSI
tristate "NXP CSI Bridge driver"
depends on ARCH_MXC || COMPILE_TEST
diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile
index 9a9b2a1d6886c..a0a025c169aca 100644
--- a/drivers/media/platform/nxp/Makefile
+++ b/drivers/media/platform/nxp/Makefile
@@ -4,6 +4,7 @@ obj-y += dw100/
obj-y += imx-jpeg/
obj-y += imx8-isi/
+obj-$(CONFIG_VIDEO_DWC_MIPI_CSIS) += dwc-mipi-csi2.o
obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o
obj-$(CONFIG_VIDEO_IMX_PARALLEL_CSI) += imx-parallel-csi.o
diff --git a/drivers/media/platform/nxp/dwc-mipi-csi2.c b/drivers/media/platform/nxp/dwc-mipi-csi2.c
new file mode 100644
index 0000000000000..cd57c06b95848
--- /dev/null
+++ b/drivers/media/platform/nxp/dwc-mipi-csi2.c
@@ -0,0 +1,1675 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+/* MIPI CSI-2 Host Controller Registers Define */
+
+/* Core Version */
+#define CSI2RX_VERSION 0x0
+
+/* Number of Lanes */
+#define CSI2RX_N_LANES 0x4
+#define CSI2RX_N_LANES_N_LANES(x) FIELD_PREP(GENMASK(2, 0), (x) - 1)
+
+/* Logic Reset */
+#define CSI2RX_HOST_RESETN 0x8
+#define CSI2RX_HOST_RESETN_ENABLE BIT(0)
+
+/* Main Interrupt Status */
+#define CSI2RX_INT_ST_MAIN 0xc
+#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY BIT(0)
+#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT BIT(1)
+#define CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL BIT(2)
+#define CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME BIT(3)
+#define CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME BIT(4)
+#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC BIT(5)
+#define CSI2RX_INT_ST_MAIN_ERR_DID BIT(6)
+#define CSI2RX_INT_ST_MAIN_ERR_ECC BIT(7)
+#define CSI2RX_INT_ST_MAIN_ERR_PHY BIT(16)
+#define CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI BIT(18)
+
+/* Data monitor */
+#define CSI2RX_DATA_IDS_1_DT 0x10
+#define CSI2RX_DATA_IDS_1_DT_DATA_ID0(x) FIELD_PREP(GENMASK(5, 0), (x))
+#define CSI2RX_DATA_IDS_1_DT_DATA_ID1(x) FIELD_PREP(GENMASK(13, 8), (x))
+#define CSI2RX_DATA_IDS_1_DT_DATA_ID2(x) FIELD_PREP(GENMASK(21, 16), (x))
+#define CSI2RX_DATA_IDS_1_DT_DATA_ID3(x) FIELD_PREP(GENMASK(29, 24), (x))
+
+#define CSI2RX_DATA_IDS_2_DT 0x14
+#define CSI2RX_DATA_IDS_2_DT_DATA_ID4(x) FIELD_PREP(GENMASK(5, 0), (x))
+#define CSI2RX_DATA_IDS_2_DT_DATA_ID5(x) FIELD_PREP(GENMASK(13, 8), (x))
+#define CSI2RX_DATA_IDS_2_DT_DATA_ID6(x) FIELD_PREP(GENMASK(21, 16), (x))
+#define CSI2RX_DATA_IDS_2_DT_DATA_ID7(x) FIELD_PREP(GENMASK(29, 24), (x))
+
+#define CSI2RX_DATA_IDS_1_VC 0x30
+#define CSI2RX_DATA_IDS_1_VC_DATA_ID0(x) FIELD_PREP(GENMASK(3, 0), (x))
+#define CSI2RX_DATA_IDS_1_VC_DATA_ID1(x) FIELD_PREP(GENMASK(11, 8), (x))
+#define CSI2RX_DATA_IDS_1_VC_DATA_ID2(x) FIELD_PREP(GENMASK(19, 16), (x))
+#define CSI2RX_DATA_IDS_1_VC_DATA_ID3(x) FIELD_PREP(GENMASK(27, 24), (x))
+
+#define CSI2RX_DATA_IDS_2_VC 0x34
+#define CSI2RX_DATA_IDS_2_VC_DATA_ID4(x) FIELD_PREP(GENMASK(3, 0), (x))
+#define CSI2RX_DATA_IDS_2_VC_DATA_ID5(x) FIELD_PREP(GENMASK(11, 8), (x))
+#define CSI2RX_DATA_IDS_2_VC_DATA_ID6(x) FIELD_PREP(GENMASK(19, 16), (x))
+#define CSI2RX_DATA_IDS_2_VC_DATA_ID7(x) FIELD_PREP(GENMASK(27, 24), (x))
+
+/* PHY Shutdown */
+#define CSI2RX_DPHY_SHUTDOWNZ 0x40
+#define CSI2RX_DPHY_SHUTDOWNZ_ENABLE BIT(0)
+
+/* DPHY Reset */
+#define CSI2RX_DPHY_RSTZ 0x44
+#define CSI2RX_DPHY_RSTZ_ENABLE BIT(0)
+
+/* RX PHY Status */
+#define CSI2RX_DPHY_RX_STATUS 0x48
+#define CSI2RX_DPHY_RX_STATUS_DATA_LANE0_ULP BIT(0)
+#define CSI2RX_DPHY_RX_STATUS_DATA_LANE1_ULP BIT(1)
+#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_ULP BIT(16)
+#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS BIT(17)
+
+/* STOP STATE PHY Status */
+#define CSI2RX_DPHY_STOPSTATE 0x4c
+#define CSI2RX_DPHY_STOPSTATE_DATA_LANE0 BIT(0)
+#define CSI2RX_DPHY_STOPSTATE_DATA_LANE1 BIT(1)
+#define CSI2RX_DPHY_STOPSTATE_DATA_LANE2 BIT(2)
+#define CSI2RX_DPHY_STOPSTATE_DATA_LANE3 BIT(3)
+#define CSI2RX_DPHY_STOPSTATE_CLK_LANE BIT(16)
+
+/* DPHY Test and Control Interface 1 */
+#define CSI2RX_DPHY_TEST_CTRL0 0x50
+#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLR BIT(0)
+#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN BIT(1)
+
+/* DPHY Test and Control Interface 2 */
+#define CSI2RX_DPHY_TEST_CTRL1 0x54
+#define CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(x) FIELD_PREP(GENMASK(7, 0), (x))
+#define CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(x) FIELD_GET(GENMASK(15, 8), (x))
+#define CSI2RX_DPHY_TEST_CTRL1_TEST_EN BIT(16)
+
+/* Pattern Generator vertical Resolution */
+#define CSI2RX_PPI_PG_PATTERN_VRES 0x60
+#define CSI2RX_PPI_PG_PATTERN_VRES_VRES(x) FIELD_PREP(GENMASK(15, 0), (x))
+
+/* Pattern Generator horizontal Resolution */
+#define CSI2RX_PPI_PG_PATTERN_HRES 0x64
+#define CSI2RX_PPI_PG_PATTERN_HRES_HRES(x) FIELD_PREP(GENMASK(15, 0), (x))
+
+/* Pattern Generator */
+#define CSI2RX_PPI_PG_CONFIG 0x68
+#define CSI2RX_PPI_PG_CONFIG_PG_MODE(x) FIELD_PREP(1, (x))
+#define CSI2RX_PPI_PG_CONFIG_DATA_TYPE(x) FIELD_PREP(GENMASK(13, 8), (x))
+#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN(x) FIELD_PREP(GENMASK(15, 14), (x))
+#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX(x) FIELD_PREP(GENMASK(17, 16), (x))
+#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX_2_EN BIT(18)
+
+/* Pattern Generator Enable */
+#define CSI2RX_PPI_PG_ENABLE 0x6c
+#define CSI2RX_PPI_PG_ENABLE_EN BIT(0)
+
+/* Pattern Generator Status */
+#define CSI2RX_PPI_PG_STATUS 0x70
+#define CSI2RX_PPI_PG_STATUS_ACTIVE BIT(0)
+
+/* IPI Mode */
+#define CSI2RX_IPI_MODE 0x80
+#define CSI2RX_IPI_MODE_CONTROLLER BIT(1)
+#define CSI2RX_IPI_MODE_COLOR_MODE16 BIT(8)
+#define CSI2RX_IPI_MODE_CUT_THROUGH BIT(16)
+#define CSI2RX_IPI_MODE_ENABLE BIT(24)
+
+/* IPI Virtual Channel */
+#define CSI2RX_IPI_VCID 0x84
+#define CSI2RX_IPI_VCID_VC(x) FIELD_PREP(GENMASK(1, 0), (x))
+#define CSI2RX_IPI_VCID_VC_0_1(x) FIELD_PREP(GENMASK(3, 2), (x))
+#define CSI2RX_IPI_VCID_VC_2 BIT(4)
+
+/* IPI Data Type */
+#define CSI2RX_IPI_DATA_TYPE 0x88
+#define CSI2RX_IPI_DATA_TYPE_DT(x) FIELD_PREP(GENMASK(5, 0), (x))
+#define CSI2RX_IPI_DATA_TYPE_EMB_DATA_EN BIT(8)
+
+/* IPI Flush Memory */
+#define CSI2RX_IPI_MEM_FLUSH 0x8c
+#define CSI2RX_IPI_MEM_FLUSH_AUTO BIT(8)
+
+/* IPI HSA */
+#define CSI2RX_IPI_HSA_TIME 0x90
+#define CSI2RX_IPI_HSA_TIME_VAL(x) FIELD_PREP(GENMASK(11, 0), (x))
+
+/* IPI HBP */
+#define CSI2RX_IPI_HBP_TIME 0x94
+#define CSI2RX_IPI_HBP_TIME_VAL(x) FIELD_PREP(GENMASK(11, 0), (x))
+
+/* IPI HSD */
+#define CSI2RX_IPI_HSD_TIME 0x98
+#define CSI2RX_IPI_HSD_TIME_VAL(x) FIELD_PREP(GENMASK(11, 0), (x))
+
+/* IPI HLINE */
+#define CSI2RX_IPI_HLINE_TIME 0x9C
+#define CSI2RX_IPI_HLINE_TIME_VAL(x) FIELD_PREP(GENMASK(14, 0), (x))
+
+/* IPI Soft Reset */
+#define CSI2RX_IPI_SOFTRSTN 0xa0
+
+/* IPI Advanced Features */
+#define CSI2RX_IPI_ADV_FEATURES 0xac
+#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE_EN BIT(0)
+#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE(x) FIELD_PREP(GENMASK(13, 8), (x))
+#define CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL BIT(16)
+#define CSI2RX_IPI_ADV_FEATURES_SYNC_VIDEO_PKT BIT(17)
+#define CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT BIT(18)
+#define CSI2RX_IPI_ADV_FEATURES_SYNC_NULL_PKT BIT(19)
+#define CSI2RX_IPI_ADV_FEATURES_SYNC_BLANKING_PKT BIT(20)
+#define CSI2RX_IPI_ADV_FEATURES_SYNC_EMBEDDED_PKT BIT(21)
+#define CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE BIT(24)
+
+/* IPI VSA */
+#define CSI2RX_IPI_VSA_LINES 0xb0
+#define CSI2RX_IPI_VSA_LINES_VAL(x) FIELD_PREP(GENMASK(9, 0), (x))
+
+/* IPI VBP */
+#define CSI2RX_IPI_VBP_LINES 0xb4
+#define CSI2RX_IPI_VBP_LINES_VAL(x) FIELD_PREP(GENMASK(9, 0), (x))
+
+/* IPI VFP */
+#define CSI2RX_IPI_VFP_LINES 0xb8
+#define CSI2RX_IPI_VFP_LINES_VAL(x) FIELD_PREP(GENMASK(9, 0), (x))
+
+/* IPI VACTIVE */
+#define CSI2RX_IPI_VACTIVE_LINES 0xbc
+#define CSI2RX_IPI_VACTIVE_LINES_VAL(x) FIELD_PREP(GENMASK(13, 0), (x))
+
+/* Fatal Interruption Caused by PHY */
+#define CSI2RX_INT_ST_DPHY_FATAL 0xe0
+#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE0 BIT(0)
+#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE1 BIT(1)
+
+/* Mask for Fatal Interruption Caused by PHY */
+#define CSI2RX_INT_MSK_DPHY_FATAL 0xe4
+#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE0 BIT(0)
+#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE1 BIT(1)
+
+/* Force for Fatal Interruption Caused by PHY */
+#define CSI2RX_INT_FORCE_DPHY_FATAL 0xe8
+
+/* Fatal Interruption Caused During Packet Construction */
+#define CSI2RX_INT_ST_PKT_FATAL 0xf0
+#define CSI2RX_INT_ST_PKT_FATAL_ERR_ECC BIT(0)
+#define CSI2RX_INT_ST_PKT_FATAL_ERR_PAYLOAD BIT(1)
+
+/* Mask for Fatal Interruption Caused During Packet Construction */
+#define CSI2RX_INT_MSK_PKT_FATAL 0xf4
+#define CSI2RX_INT_MSK_PKT_FATAL_ERR_ECC BIT(0)
+#define CSI2RX_INT_MSK_PKT_FATAL_ERR_PAYLOAD BIT(1)
+
+/* Force for Fatal Interruption Caused During Packet Construction */
+#define CSI2RX_INT_FORCE_PKT_FATAL 0xf8
+
+/* Interruption Caused by PHY */
+#define CSI2RX_INT_ST_DPHY 0x110
+#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE0 BIT(0)
+#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE1 BIT(1)
+#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE0 BIT(16)
+#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE1 BIT(17)
+
+/* Mask for Interruption Caused by PHY */
+#define CSI2RX_INT_MSK_DPHY 0x114
+#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE0 BIT(0)
+#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE1 BIT(1)
+#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE0 BIT(16)
+#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE1 BIT(17)
+
+/* Force for Interruption Caused by PHY */
+#define CSI2RX_INT_FORCE_DPHY 0x118
+
+/* Fatal Interruption Caused by IPI Interface */
+#define CSI2RX_INT_ST_IPI_FATAL 0x140
+#define CSI2RX_INT_ST_IPI_FATAL_ERR_IFFIFO_UNDERFLOW BIT(0)
+#define CSI2RX_INT_ST_IPI_FATAL_ERR_IFFIFO_OVERFLOW BIT(1)
+#define CSI2RX_INT_ST_IPI_FATAL_ERR_FRAME_SYNC BIT(2)
+#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3)
+#define CSI2RX_INT_ST_IPI_FATAL_ERR_HLINE_TIME BIT(4)
+#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5)
+#define CSI2RX_INT_ST_IPI_FATAL_ERR_PD_FIFO_OVERFLOW BIT(6)
+
+/* Mask for Fatal Interruption Caused by IPI Interface */
+#define CSI2RX_INT_MSK_IPI_FATAL 0x144
+#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IFFIFO_UNDERFLOW BIT(0)
+#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IFFIFO_OVERFLOW BIT(1)
+#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FRAME_SYNC BIT(2)
+#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3)
+#define CSI2RX_INT_MSK_IPI_FATAL_ERR_HLINE_TIME BIT(4)
+#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5)
+#define CSI2RX_INT_MSK_IPI_FATAL_ERR_PD_FIFO_OVERFLOW BIT(6)
+
+/* Force for Fatal Interruption Caused by IPI Interface */
+#define CSI2RX_INT_FORCE_IPI_FATAL 0x148
+
+/* Data De-Scrambling */
+#define CSI2RX_SCRAMBLING 0x300
+
+/* De-scrambler Seed for Lane 1 */
+#define CSI2RX_SCRAMBLING_SEED1 0x304
+
+/* De-scrambler Seed for Lane 2 */
+#define CSI2RX_SCRAMBLING_SEED2 0x308
+
+#define DWC_CSI2RX_PAD_SINK 0
+#define DWC_CSI2RX_PAD_SOURCE 1
+#define DWC_CSI2RX_PADS_NUM 2
+
+#define DWC_CSI2RX_DEF_MBUS_CODE MEDIA_BUS_FMT_UYVY8_1X16
+#define DWC_CSI2RX_DEF_PIX_WIDTH 1920U
+#define DWC_CSI2RX_DEF_PIX_HEIGHT 1080U
+#define DWC_CSI2RX_MAX_PIX_WIDTH 0xffff
+#define DWC_CSI2RX_MAX_PIX_HEIGHT 0xffff
+
+struct dwc_csi_event {
+ u32 mask;
+ const char * const name;
+ unsigned int counter;
+};
+
+static const struct dwc_csi_event dwc_events[] = {
+ { CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI, "IPI Interface Fatal Error" },
+ { CSI2RX_INT_ST_MAIN_ERR_PHY, "PHY Error" },
+ { CSI2RX_INT_ST_MAIN_ERR_ECC, "Header Single Bit Error" },
+ { CSI2RX_INT_ST_MAIN_ERR_DID, "Data ID Error" },
+ { CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC, "Payload CRC Fatal Error" },
+ { CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME, "Frame CRC Fatal Error" },
+ { CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME, "Frame Sequence Fatal Error" },
+ { CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL, "Frame Boundaries Fatal Error" },
+ { CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT, "Packet Construction Fatal Error" },
+ { CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY, "PHY Fatal Error" },
+};
+
+#define DWC_NUM_EVENTS ARRAY_SIZE(dwc_events)
+#define DWC_EVENT_MASK 0x500ff
+
+struct dwc_csi_pix_format {
+ u32 code;
+ u32 output;
+ u32 data_type;
+ u8 width;
+};
+
+struct dwc_csi_device {
+ struct device *dev;
+ void __iomem *regs;
+ struct phy *phy;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct v4l2_subdev sd;
+ struct v4l2_async_notifier notifier;
+ struct v4l2_subdev *source_sd;
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct media_pad pads[DWC_CSI2RX_PADS_NUM];
+ u16 remote_pad;
+
+ struct v4l2_mbus_config_mipi_csi2 bus;
+ u32 cfgclkfreqrange;
+ u32 hsfreqrange;
+ u64 enabled_streams;
+
+ /* Use driver mutex lock for the ctrl lock */
+ struct mutex lock;
+
+ struct dwc_csi_event events[DWC_NUM_EVENTS];
+ const struct dwc_csi_pix_format *csi_fmt;
+
+ /* Used for pattern generator */
+ bool pg_enable;
+ enum {
+ PATTERN_DISABLED,
+ PATTERN_VERTICAL,
+ PATTERN_HORIZONTAL,
+ } pg_pattern;
+};
+
+/* List of supported pixel formats for the subdev */
+static const struct dwc_csi_pix_format dwc_csi_formats[] = {
+ /* YUV formats */
+ {
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .output = MEDIA_BUS_FMT_UYVY8_1X16,
+ .data_type = MIPI_CSI2_DT_YUV422_8B,
+ .width = 16,
+ },
+ /* RGB formats */
+ {
+ .code = MEDIA_BUS_FMT_RGB565_1X16,
+ .output = MEDIA_BUS_FMT_RGB565_1X16,
+ .data_type = MIPI_CSI2_DT_RGB565,
+ .width = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_BGR888_1X24,
+ .output = MEDIA_BUS_FMT_RGB888_1X24,
+ .data_type = MIPI_CSI2_DT_RGB888,
+ .width = 24,
+ },
+ /* RAW (Bayer and greyscale) formats. */
+ {
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .output = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .data_type = MIPI_CSI2_DT_RAW8,
+ .width = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .output = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .data_type = MIPI_CSI2_DT_RAW8,
+ .width = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .output = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .data_type = MIPI_CSI2_DT_RAW8,
+ .width = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .output = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .data_type = MIPI_CSI2_DT_RAW8,
+ .width = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_Y8_1X8,
+ .output = MEDIA_BUS_FMT_Y8_1X8,
+ .data_type = MIPI_CSI2_DT_RAW8,
+ .width = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .output = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .data_type = MIPI_CSI2_DT_RAW10,
+ .width = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .output = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .data_type = MIPI_CSI2_DT_RAW10,
+ .width = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .output = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .data_type = MIPI_CSI2_DT_RAW10,
+ .width = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .output = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .data_type = MIPI_CSI2_DT_RAW10,
+ .width = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_Y10_1X10,
+ .output = MEDIA_BUS_FMT_Y10_1X10,
+ .data_type = MIPI_CSI2_DT_RAW10,
+ .width = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .output = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .data_type = MIPI_CSI2_DT_RAW12,
+ .width = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .output = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .data_type = MIPI_CSI2_DT_RAW12,
+ .width = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .output = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .data_type = MIPI_CSI2_DT_RAW12,
+ .width = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .output = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .data_type = MIPI_CSI2_DT_RAW12,
+ .width = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_Y12_1X12,
+ .output = MEDIA_BUS_FMT_Y12_1X12,
+ .data_type = MIPI_CSI2_DT_RAW12,
+ .width = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR14_1X14,
+ .output = MEDIA_BUS_FMT_SBGGR14_1X14,
+ .data_type = MIPI_CSI2_DT_RAW14,
+ .width = 14,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG14_1X14,
+ .output = MEDIA_BUS_FMT_SGBRG14_1X14,
+ .data_type = MIPI_CSI2_DT_RAW14,
+ .width = 14,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG14_1X14,
+ .output = MEDIA_BUS_FMT_SGRBG14_1X14,
+ .data_type = MIPI_CSI2_DT_RAW14,
+ .width = 14,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB14_1X14,
+ .output = MEDIA_BUS_FMT_SRGGB14_1X14,
+ .data_type = MIPI_CSI2_DT_RAW14,
+ .width = 14,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR16_1X16,
+ .output = MEDIA_BUS_FMT_SBGGR16_1X16,
+ .data_type = MIPI_CSI2_DT_RAW16,
+ .width = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG16_1X16,
+ .output = MEDIA_BUS_FMT_SGBRG16_1X16,
+ .data_type = MIPI_CSI2_DT_RAW16,
+ .width = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG16_1X16,
+ .output = MEDIA_BUS_FMT_SGRBG16_1X16,
+ .data_type = MIPI_CSI2_DT_RAW16,
+ .width = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB16_1X16,
+ .output = MEDIA_BUS_FMT_SRGGB16_1X16,
+ .data_type = MIPI_CSI2_DT_RAW16,
+ .width = 16,
+ }
+};
+
+static const struct v4l2_mbus_framefmt dwc_csi_default_fmt = {
+ .code = DWC_CSI2RX_DEF_MBUS_CODE,
+ .width = DWC_CSI2RX_DEF_PIX_WIDTH,
+ .height = DWC_CSI2RX_DEF_PIX_HEIGHT,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SMPTE170M,
+ .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
+ .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
+ .quantization = V4L2_QUANTIZATION_LIM_RANGE,
+};
+
+static const struct dwc_csi_pix_format *find_csi_format(u32 code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dwc_csi_formats); i++)
+ if (code == dwc_csi_formats[i].code)
+ return &dwc_csi_formats[i];
+
+ return &dwc_csi_formats[0];
+}
+
+static inline void dwc_csi_write(struct dwc_csi_device *csidev, unsigned int offset, u32 val)
+{
+ writel(val, csidev->regs + offset);
+}
+
+static inline u32 dwc_csi_read(struct dwc_csi_device *csidev, unsigned int offset)
+{
+ return readl(csidev->regs + offset);
+}
+
+/*
+ * DWC MIPI CSI-2 Host Controller Hardware operation
+ */
+static int dwc_csi_device_pg_enable(struct dwc_csi_device *csidev)
+{
+ const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt;
+ struct v4l2_subdev *sd = &csidev->sd;
+ struct v4l2_subdev_state *state;
+ struct v4l2_mbus_framefmt *fmt;
+ u32 val;
+
+ if (!csidev->pg_enable)
+ return 0;
+
+ if (!csi_fmt) {
+ dev_err(csidev->dev, "CSI pixel format is NULL\n");
+ return -EINVAL;
+ }
+
+ if (csi_fmt->data_type != MIPI_CSI2_DT_RGB888) {
+ dev_err(csidev->dev, "Pattern generator only support RGB888\n");
+ return -EINVAL;
+ }
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+ /* Pattern generator create data stream only according to stream 0 */
+ fmt = v4l2_subdev_state_get_format(state, DWC_CSI2RX_PAD_SINK, 0);
+
+ val = CSI2RX_PPI_PG_PATTERN_HRES_HRES(fmt->width);
+ dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_HRES, val);
+
+ val = CSI2RX_PPI_PG_PATTERN_VRES_VRES(fmt->height);
+ dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_VRES, val);
+
+ val = CSI2RX_PPI_PG_CONFIG_DATA_TYPE(csi_fmt->data_type);
+ val |= CSI2RX_PPI_PG_CONFIG_VIR_CHAN(0);
+ val |= CSI2RX_PPI_PG_CONFIG_PG_MODE(csidev->pg_pattern);
+ dwc_csi_write(csidev, CSI2RX_PPI_PG_CONFIG, val);
+
+ /*
+ * Select line start packets to construct vertical
+ * timing information for IPI interface
+ */
+ val = CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE;
+ val |= CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT;
+ val |= CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL;
+ dwc_csi_write(csidev, CSI2RX_IPI_ADV_FEATURES, val);
+
+ val = CSI2RX_PPI_PG_ENABLE_EN;
+ dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, val);
+
+ v4l2_subdev_unlock_state(state);
+
+ return 0;
+}
+
+static void dwc_csi_device_pg_disable(struct dwc_csi_device *csidev)
+{
+ dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, 0);
+}
+
+static void dwc_csi_ipi_enable(struct dwc_csi_device *csidev)
+{
+ u32 val;
+
+ /* Memory is automatically flushed at each Frame Start */
+ val = CSI2RX_IPI_MEM_FLUSH_AUTO;
+ dwc_csi_write(csidev, CSI2RX_IPI_MEM_FLUSH, val);
+
+ /* Enable IPI */
+ val = dwc_csi_read(csidev, CSI2RX_IPI_MODE);
+ val |= CSI2RX_IPI_MODE_ENABLE;
+ dwc_csi_write(csidev, CSI2RX_IPI_MODE, val);
+}
+
+static void dwc_csi_ipi_disable(struct dwc_csi_device *csidev)
+{
+ dwc_csi_write(csidev, CSI2RX_IPI_MODE, 0);
+}
+
+static void dwc_csi_device_ipi_config(struct dwc_csi_device *csidev)
+{
+ const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt;
+ u32 val;
+
+ /* Do IPI soft reset */
+ dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x0);
+ dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x1);
+
+ /* Select virtual channel and data type to be processed by IPI */
+ val = CSI2RX_IPI_DATA_TYPE_DT(csi_fmt->data_type);
+ dwc_csi_write(csidev, CSI2RX_IPI_DATA_TYPE, val);
+
+ /* Set virtual channel 0 as default */
+ val = CSI2RX_IPI_VCID_VC(0);
+ dwc_csi_write(csidev, CSI2RX_IPI_VCID, val);
+
+ /*
+ * Select IPI camera timing mode and allow the pixel stream
+ * to be non-continuous when pixel interface FIFO is empty
+ */
+ val = dwc_csi_read(csidev, CSI2RX_IPI_MODE);
+ val &= ~CSI2RX_IPI_MODE_CONTROLLER;
+ val &= ~CSI2RX_IPI_MODE_COLOR_MODE16;
+ val |= CSI2RX_IPI_MODE_CUT_THROUGH;
+ dwc_csi_write(csidev, CSI2RX_IPI_MODE, val);
+}
+
+static void dwc_csi_device_reset(struct dwc_csi_device *csidev)
+{
+ /* Reset mipi csi host, active low */
+ dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0);
+ dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 1);
+}
+
+static void dwc_csi_device_startup(struct dwc_csi_device *csidev)
+{
+ /* Release DWC_mipi_csi2_host from reset */
+ dwc_csi_device_reset(csidev);
+
+ phy_init(csidev->phy);
+
+ phy_reset(csidev->phy);
+}
+
+static int dwc_csi_get_dphy_configuration(struct dwc_csi_device *csidev,
+ union phy_configure_opts *opts)
+{
+ struct phy_configure_opts_mipi_dphy *cfg = &opts->mipi_dphy;
+ struct v4l2_subdev *source = csidev->source_sd;
+ s64 link_freq;
+
+ link_freq = v4l2_get_link_freq(source->ctrl_handler,
+ csidev->csi_fmt->width,
+ csidev->bus.num_data_lanes * 2);
+ if (link_freq < 0) {
+ dev_err(csidev->dev, "Unable to obtain link frequency: %d\n",
+ (int)link_freq);
+ return link_freq;
+ }
+
+ memset(cfg, 0x0, sizeof(*cfg));
+ cfg->hs_clk_rate = link_freq * 2;
+ cfg->lanes = csidev->bus.num_data_lanes;
+
+ return 0;
+}
+
+static void dwc_csi_dphy_prep(struct dwc_csi_device *csidev)
+{
+ u32 val;
+
+ /* Release synopsis DPHY test codes from reset */
+ dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0);
+ dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0);
+
+ val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
+ val &= ~CSI2RX_DPHY_TEST_CTRL0_TEST_CLR;
+ dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
+
+ /*
+ * ndelay is not necessary have MMIO operation, need dummy read to make
+ * sure above write reach target.
+ */
+ dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
+ /* Wait for at least 15ns */
+ ndelay(15);
+
+ val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
+ val |= CSI2RX_DPHY_TEST_CTRL0_TEST_CLR;
+ dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
+}
+
+static void dwc_csi_dphy_release_reset(struct dwc_csi_device *csidev)
+{
+ /* Release PHY from reset */
+ dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1);
+ /*
+ * ndelay is not necessary have MMIO operation, need dummy read to make
+ * sure above write reach target.
+ */
+ dwc_csi_read(csidev, CSI2RX_DPHY_SHUTDOWNZ);
+ ndelay(5);
+ dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1);
+ dwc_csi_read(csidev, CSI2RX_DPHY_RSTZ);
+ ndelay(5);
+}
+
+static int dwc_csi_device_init(struct dwc_csi_device *csidev)
+{
+ struct device *dev = csidev->dev;
+ union phy_configure_opts opts;
+ u32 phy_stopstate;
+ u32 val;
+ int ret;
+
+ ret = dwc_csi_get_dphy_configuration(csidev, &opts);
+ if (ret)
+ return ret;
+
+ ret = phy_set_mode(csidev->phy, PHY_MODE_MIPI_DPHY);
+ if (ret)
+ return ret;
+
+ ret = phy_configure(csidev->phy, &opts);
+ if (ret)
+ return ret;
+
+ dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0);
+ dwc_csi_dphy_prep(csidev);
+ dwc_csi_write(csidev, CSI2RX_N_LANES, CSI2RX_N_LANES_N_LANES(opts.mipi_dphy.lanes));
+ ret = phy_power_on(csidev->phy);
+ dwc_csi_dphy_release_reset(csidev);
+ dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0x1);
+
+ if (ret)
+ return ret;
+
+ /* Check if lanes are in stop state */
+ phy_stopstate = CSI2RX_DPHY_STOPSTATE_CLK_LANE;
+ phy_stopstate |= GENMASK(csidev->bus.num_data_lanes - 1, 0);
+ ret = readl_poll_timeout(csidev->regs + CSI2RX_DPHY_STOPSTATE,
+ val, (val & phy_stopstate) != phy_stopstate,
+ 10, 10000);
+ if (ret) {
+ dev_err(dev, "Lanes are not in stop state(%#x)\n", val);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dwc_csi_device_hs_rx_start(struct dwc_csi_device *csidev)
+{
+ dwc_csi_ipi_enable(csidev);
+}
+
+static int dwc_csi_device_hs_rx_stop(struct dwc_csi_device *csidev)
+{
+ struct device *dev = csidev->dev;
+ u32 val;
+
+ phy_power_off(csidev->phy);
+ phy_exit(csidev->phy);
+ dwc_csi_ipi_disable(csidev);
+
+ /* Check clock lanes are not in High Speed Mode */
+ val = dwc_csi_read(csidev, CSI2RX_DPHY_RX_STATUS);
+ if (val & CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS) {
+ dev_err(dev, "Clock lanes are still in HS mode\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void dwc_csi_device_enable_interrupts(struct dwc_csi_device *csidev, bool on)
+{
+ /* Define errors to be enabled */
+ dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY_FATAL, on ? 0x3 : 0);
+ dwc_csi_write(csidev, CSI2RX_INT_MSK_PKT_FATAL, on ? 0x3 : 0);
+ dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY, on ? 0x30003 : 0);
+ dwc_csi_write(csidev, CSI2RX_INT_MSK_IPI_FATAL, on ? 0x7f : 0);
+}
+
+static void dwc_csi_clear_counters(struct dwc_csi_device *csidev)
+{
+ unsigned int i;
+
+ for (i = 0; i < DWC_NUM_EVENTS; ++i)
+ csidev->events[i].counter = 0;
+}
+
+static void dwc_csi_log_counters(struct dwc_csi_device *csidev)
+{
+ unsigned int i;
+ int counter;
+
+ for (i = 0; i < DWC_NUM_EVENTS; ++i) {
+ counter = csidev->events[i].counter;
+ if (counter > 0)
+ dev_info(csidev->dev, "%s events: %d\n",
+ csidev->events[i].name,
+ counter);
+ }
+}
+
+static void dwc_csi_dump_regs(struct dwc_csi_device *csidev)
+{
+#define DWC_MIPI_CSIS_DEBUG_REG(name) {name, #name}
+ static const struct {
+ u32 offset;
+ const char * const name;
+ } registers[] = {
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_VERSION),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_N_LANES),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_HOST_RESETN),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_MAIN),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_1_DT),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_2_DT),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_1_VC),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_2_VC),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_SHUTDOWNZ),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RSTZ),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RX_STATUS),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_STOPSTATE),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL0),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL1),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_VRES),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_HRES),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_CONFIG),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_ENABLE),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_STATUS),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MODE),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_VCID),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_DATA_TYPE),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MEM_FLUSH),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_SOFTRSTN),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_ADV_FEATURES),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_PKT_FATAL),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL),
+ DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_IPI_FATAL),
+ };
+
+ unsigned int i;
+ u32 cfg;
+
+ dev_dbg(csidev->dev, "--- REGISTERS ---\n");
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ cfg = dwc_csi_read(csidev, registers[i].offset);
+ dev_dbg(csidev->dev, "%14s[0x%02x]: 0x%08x\n",
+ registers[i].name, registers[i].offset, cfg);
+ }
+}
+
+/*
+ * V4L2 subdev operations
+ */
+
+static inline struct dwc_csi_device *
+sd_to_dwc_csi_device(struct v4l2_subdev *sdev)
+{
+ return container_of(sdev, struct dwc_csi_device, sd);
+}
+
+static int __dwc_csi_subdev_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_krouting *routing)
+{
+ int ret;
+
+ if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
+ return -EINVAL;
+
+ ret = v4l2_subdev_routing_validate(sd, routing,
+ V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+ if (ret)
+ return ret;
+
+ return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
+ &dwc_csi_default_fmt);
+}
+
+static int dwc_csi_subdev_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ struct v4l2_subdev_route routes[] = {
+ {
+ .sink_pad = DWC_CSI2RX_PAD_SINK,
+ .sink_stream = 0,
+ .source_pad = DWC_CSI2RX_PAD_SOURCE,
+ .source_stream = 0,
+ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+ },
+ };
+
+ struct v4l2_subdev_krouting routing = {
+ .num_routes = ARRAY_SIZE(routes),
+ .routes = routes,
+ };
+
+ return __dwc_csi_subdev_set_routing(sd, sd_state, &routing);
+}
+
+static int dwc_csi_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ /*
+ * The CSIS can't transcode in any way, the source format is identical
+ * to the sink format.
+ */
+ if (code->pad == DWC_CSI2RX_PAD_SOURCE) {
+ struct v4l2_mbus_framefmt *fmt;
+
+ if (code->index > 0)
+ return -EINVAL;
+
+ fmt = v4l2_subdev_state_get_format(sd_state, code->pad,
+ code->stream);
+ code->code = fmt->code;
+ return 0;
+ }
+
+ if (code->index >= ARRAY_SIZE(dwc_csi_formats))
+ return -EINVAL;
+
+ code->code = dwc_csi_formats[code->index].code;
+
+ return 0;
+}
+
+static int dwc_csi_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *sdformat)
+{
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+ struct dwc_csi_pix_format const *csi_fmt;
+ struct v4l2_mbus_framefmt *fmt;
+ unsigned int align;
+
+ /*
+ * The CSIS can't transcode in any way, the source format can't be
+ * modified.
+ */
+ if (sdformat->pad == DWC_CSI2RX_PAD_SOURCE)
+ return v4l2_subdev_get_fmt(sd, sd_state, sdformat);
+
+ /*
+ * Validate the media bus code and clamp and align the size.
+ *
+ * The total number of bits per line must be a multiple of 8. We thus
+ * need to align the width for formats that are not multiples of 8
+ * bits.
+ */
+ csi_fmt = find_csi_format(sdformat->format.code);
+
+ switch (csi_fmt->width % 8) {
+ case 0:
+ align = 0;
+ break;
+ case 4:
+ align = 1;
+ break;
+ case 2:
+ case 6:
+ align = 2;
+ break;
+ default:
+ /* 1, 3, 5, 7 */
+ align = 3;
+ break;
+ }
+
+ v4l_bound_align_image(&sdformat->format.width, 1,
+ DWC_CSI2RX_MAX_PIX_WIDTH, align,
+ &sdformat->format.height, 1,
+ DWC_CSI2RX_MAX_PIX_HEIGHT, 0, 0);
+
+ fmt = v4l2_subdev_state_get_format(sd_state, sdformat->pad,
+ sdformat->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ *fmt = sdformat->format;
+
+ /* Set default code if user set an invalid value */
+ fmt->code = csi_fmt->code;
+
+ /* Propagate the format from sink stream to source stream */
+ fmt = v4l2_subdev_state_get_opposite_stream_format(sd_state, sdformat->pad,
+ sdformat->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ *fmt = sdformat->format;
+ /* The format on the source pad might change due to unpacking. */
+ fmt->code = csi_fmt->output;
+
+ /* Store the CSIS format descriptor for active formats. */
+ if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ csidev->csi_fmt = csi_fmt;
+
+ return 0;
+}
+
+static int dwc_csi_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
+ struct v4l2_mbus_frame_desc *fd)
+{
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+ struct v4l2_mbus_frame_desc source_fd;
+ struct v4l2_subdev_route *route;
+ struct v4l2_subdev_state *state;
+ int ret;
+
+ if (pad != DWC_CSI2RX_PAD_SOURCE)
+ return -EINVAL;
+
+ memset(fd, 0, sizeof(*fd));
+
+ ret = v4l2_subdev_call(csidev->source_sd, pad, get_frame_desc,
+ csidev->remote_pad, &source_fd);
+ if (ret < 0) {
+ dev_info(csidev->dev,
+ "Remote sub-device on pad %d should implement .get_frame_desc! Forcing VC = 0 and DT = %x\n",
+ pad, csidev->csi_fmt->data_type);
+ fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+ fd->num_entries = 1;
+ fd->entry[0].pixelcode = csidev->csi_fmt->code;
+ fd->entry[0].bus.csi2.vc = 0;
+ fd->entry[0].bus.csi2.dt = csidev->csi_fmt->data_type;
+
+ return 0;
+ }
+
+ fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ for_each_active_route(&state->routing, route) {
+ struct v4l2_mbus_frame_desc_entry *entry = NULL;
+ unsigned int i;
+
+ if (route->source_pad != pad)
+ continue;
+
+ for (i = 0; i < source_fd.num_entries; ++i) {
+ if (source_fd.entry[i].stream == route->sink_stream) {
+ entry = &source_fd.entry[i];
+ break;
+ }
+ }
+
+ if (!entry) {
+ dev_err(csidev->dev,
+ "Failed to find stream from source frames desc\n");
+ ret = -EPIPE;
+ goto out_unlock;
+ }
+
+ fd->entry[fd->num_entries].stream = route->source_stream;
+ fd->entry[fd->num_entries].flags = entry->flags;
+ fd->entry[fd->num_entries].length = entry->length;
+ fd->entry[fd->num_entries].pixelcode = entry->pixelcode;
+ fd->entry[fd->num_entries].bus.csi2.vc = entry->bus.csi2.vc;
+ fd->entry[fd->num_entries].bus.csi2.dt = entry->bus.csi2.dt;
+
+ fd->num_entries++;
+ }
+
+out_unlock:
+ v4l2_subdev_unlock_state(state);
+ return ret;
+}
+
+static int dwc_csi_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ enum v4l2_subdev_format_whence which,
+ struct v4l2_subdev_krouting *routing)
+{
+ if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+ media_entity_is_streaming(&sd->entity))
+ return -EBUSY;
+
+ return __dwc_csi_subdev_set_routing(sd, state, routing);
+}
+
+static int dwc_csi_start_stream(struct dwc_csi_device *csidev)
+{
+ int ret;
+
+ dwc_csi_device_startup(csidev);
+
+ ret = dwc_csi_device_init(csidev);
+ if (ret)
+ return ret;
+
+ dwc_csi_device_ipi_config(csidev);
+
+ ret = dwc_csi_device_pg_enable(csidev);
+ if (ret)
+ return ret;
+
+ dwc_csi_device_hs_rx_start(csidev);
+
+ dwc_csi_device_enable_interrupts(csidev, true);
+
+ return 0;
+}
+
+static void dwc_csi_stop_stream(struct dwc_csi_device *csidev)
+{
+ dwc_csi_device_enable_interrupts(csidev, false);
+ dwc_csi_device_hs_rx_stop(csidev);
+ dwc_csi_device_pg_disable(csidev);
+}
+
+static int dwc_csi_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+ u64 sink_streams;
+ int ret;
+
+ if (!csidev->source_sd) {
+ dev_err(csidev->dev, "Sensor don't link with CSIS pad\n");
+ return -EPIPE;
+ }
+
+ if (!csidev->enabled_streams) {
+ ret = pm_runtime_resume_and_get(csidev->dev);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_ctrl_handler_setup(&csidev->ctrl_handler);
+ if (ret < 0)
+ goto err_runtime_put;
+
+ dwc_csi_clear_counters(csidev);
+
+ ret = dwc_csi_start_stream(csidev);
+ if (ret < 0)
+ goto err_runtime_put;
+
+ dwc_csi_dump_regs(csidev);
+ dwc_csi_log_counters(csidev);
+ }
+
+ sink_streams = v4l2_subdev_state_xlate_streams(state, DWC_CSI2RX_PAD_SOURCE,
+ DWC_CSI2RX_PAD_SINK,
+ &streams_mask);
+
+ dev_dbg(csidev->dev, "remote sd: %s pad: %u, sink_stream:0x%llx\n",
+ csidev->source_sd->name, csidev->remote_pad, sink_streams);
+
+ ret = v4l2_subdev_enable_streams(csidev->source_sd, csidev->remote_pad,
+ sink_streams);
+ if (ret)
+ return ret;
+
+ csidev->enabled_streams |= streams_mask;
+
+ return 0;
+
+err_runtime_put:
+ pm_runtime_put(csidev->dev);
+ return ret;
+}
+
+static int dwc_csi_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+ u64 sink_streams;
+ int ret;
+
+ sink_streams = v4l2_subdev_state_xlate_streams(state, DWC_CSI2RX_PAD_SOURCE,
+ DWC_CSI2RX_PAD_SINK,
+ &streams_mask);
+
+ ret = v4l2_subdev_disable_streams(csidev->source_sd, csidev->remote_pad,
+ sink_streams);
+ if (ret)
+ return ret;
+
+ csidev->enabled_streams &= ~streams_mask;
+
+ if (!csidev->enabled_streams) {
+ dwc_csi_stop_stream(csidev);
+ dwc_csi_log_counters(csidev);
+ pm_runtime_put(csidev->dev);
+ }
+
+ return 0;
+}
+
+static int dwc_csi_subdev_log_status(struct v4l2_subdev *sd)
+{
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+
+ dwc_csi_log_counters(csidev);
+ return 0;
+}
+
+static const struct v4l2_subdev_core_ops dwc_csi_subdev_core_ops = {
+ .log_status = dwc_csi_subdev_log_status,
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_pad_ops dwc_csi_subdev_pad_ops = {
+ .enum_mbus_code = dwc_csi_subdev_enum_mbus_code,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = dwc_csi_subdev_set_fmt,
+ .get_frame_desc = dwc_csi_get_frame_desc,
+ .set_routing = dwc_csi_set_routing,
+ .enable_streams = dwc_csi_enable_streams,
+ .disable_streams = dwc_csi_disable_streams,
+};
+
+static const struct v4l2_subdev_ops dwc_csi_subdev_ops = {
+ .core = &dwc_csi_subdev_core_ops,
+ .pad = &dwc_csi_subdev_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops dwc_csi_internal_ops = {
+ .init_state = dwc_csi_subdev_init_state,
+};
+
+/*
+ * Media entity operations
+ */
+
+static int dwc_csi_link_setup(struct media_entity *entity,
+ const struct media_pad *local_pad,
+ const struct media_pad *remote_pad, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+ struct v4l2_subdev *remote_sd;
+
+ dev_dbg(csidev->dev, "link setup %s -> %s", remote_pad->entity->name,
+ local_pad->entity->name);
+
+ /* We only care about the link to the source. */
+ if (!(local_pad->flags & MEDIA_PAD_FL_SINK))
+ return 0;
+
+ remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
+
+ if (flags & MEDIA_LNK_FL_ENABLED) {
+ if (csidev->source_sd)
+ return -EBUSY;
+
+ csidev->source_sd = remote_sd;
+ csidev->remote_pad = remote_pad->index;
+ } else {
+ csidev->source_sd = NULL;
+ }
+
+ return 0;
+}
+
+static int dwc_csi_link_validate(struct media_link *link)
+{
+ struct media_pad *sink_pad = link->sink;
+ struct v4l2_subdev *sink_sd;
+ struct dwc_csi_device *csidev;
+
+ sink_sd = media_entity_to_v4l2_subdev(sink_pad->entity);
+ csidev = sd_to_dwc_csi_device(sink_sd);
+
+ dev_dbg(csidev->dev, "entity name:%s pad index=%d\n",
+ sink_sd->name, sink_pad->index);
+
+ /*
+ * Skip link validate when pattern enabled since the source
+ * data will be from CSI pattern generator, not sensor.
+ */
+ if (csidev->pg_enable && sink_pad->index == DWC_CSI2RX_PAD_SINK)
+ return 0;
+
+ return v4l2_subdev_link_validate(link);
+}
+
+static const struct media_entity_operations dwc_csi_entity_ops = {
+ .link_setup = dwc_csi_link_setup,
+ .link_validate = dwc_csi_link_validate,
+ .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+};
+
+/*
+ * Async subdev notifier
+ */
+
+static inline struct dwc_csi_device *
+notifier_to_dwc_csi_device(struct v4l2_async_notifier *n)
+{
+ return container_of(n, struct dwc_csi_device, notifier);
+}
+
+static int dwc_csi_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_connection *asd)
+{
+ struct dwc_csi_device *csidev = notifier_to_dwc_csi_device(notifier);
+ struct media_pad *sink = &csidev->sd.entity.pads[DWC_CSI2RX_PAD_SINK];
+
+ return v4l2_create_fwnode_links_to_pad(sd, sink, 0);
+}
+
+static const struct v4l2_async_notifier_operations dwc_csi_notify_ops = {
+ .bound = dwc_csi_notify_bound,
+};
+
+static int dwc_csi_async_register(struct dwc_csi_device *csidev)
+{
+ struct v4l2_fwnode_endpoint vep = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY,
+ };
+ struct v4l2_async_connection *asd;
+ struct fwnode_handle *ep;
+ unsigned int i;
+ int ret;
+
+ v4l2_async_subdev_nf_init(&csidev->notifier, &csidev->sd);
+
+ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csidev->dev), 0, 0,
+ FWNODE_GRAPH_ENDPOINT_NEXT);
+ if (!ep)
+ return -ENOTCONN;
+
+ ret = v4l2_fwnode_endpoint_parse(ep, &vep);
+ if (ret)
+ goto err_parse;
+
+ for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) {
+ if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) {
+ dev_err(csidev->dev,
+ "data lanes reordering is not supported");
+ ret = -EINVAL;
+ goto err_parse;
+ }
+ }
+
+ csidev->bus = vep.bus.mipi_csi2;
+
+ dev_dbg(csidev->dev, "data lanes: %d\n", csidev->bus.num_data_lanes);
+ dev_dbg(csidev->dev, "flags: 0x%08x\n", csidev->bus.flags);
+
+ asd = v4l2_async_nf_add_fwnode_remote(&csidev->notifier, ep,
+ struct v4l2_async_connection);
+ if (IS_ERR(asd)) {
+ ret = PTR_ERR(asd);
+ goto err_parse;
+ }
+
+ fwnode_handle_put(ep);
+
+ csidev->notifier.ops = &dwc_csi_notify_ops;
+
+ ret = v4l2_async_nf_register(&csidev->notifier);
+ if (ret)
+ goto err_notifier_clean;
+
+ ret = v4l2_async_register_subdev(&csidev->sd);
+ if (ret)
+ goto err_unreg_notifier;
+
+ return ret;
+
+err_unreg_notifier:
+ v4l2_async_nf_unregister(&csidev->notifier);
+err_notifier_clean:
+ v4l2_async_nf_cleanup(&csidev->notifier);
+err_parse:
+ fwnode_handle_put(ep);
+ return ret;
+}
+
+/*
+ * Pattern Generator Controller operations
+ */
+
+static const char * const test_pattern_menu[] = {
+ "Disabled",
+ "Vertical Color Bars",
+ "Horizontal Color Bars",
+};
+
+static inline struct dwc_csi_device *ctrl_to_csidev(struct v4l2_ctrl *ctrl)
+{
+ return container_of(ctrl->handler, struct dwc_csi_device, ctrl_handler);
+}
+
+static int dwc_csi_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct dwc_csi_device *csidev = ctrl_to_csidev(ctrl);
+ int ret = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_TEST_PATTERN:
+ /* Pattern index start from 0 */
+ csidev->pg_pattern = ctrl->val - 1;
+ csidev->pg_enable = (ctrl->val) ? true : false;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops dwc_csi_ctrl_ops = {
+ .s_ctrl = dwc_csi_s_ctrl,
+};
+
+static int dwc_csi_controls_init(struct dwc_csi_device *csidev)
+{
+ struct v4l2_ctrl_handler *handler = &csidev->ctrl_handler;
+ int ret;
+
+ v4l2_ctrl_handler_init(handler, 1);
+
+ /* Use driver mutex lock for the ctrl lock */
+ handler->lock = &csidev->lock;
+
+ v4l2_ctrl_new_std_menu_items(handler, &dwc_csi_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(test_pattern_menu) - 1,
+ 0, 0, test_pattern_menu);
+
+ if (handler->error) {
+ ret = handler->error;
+ v4l2_ctrl_handler_free(handler);
+ return ret;
+ }
+
+ csidev->sd.ctrl_handler = handler;
+ return 0;
+}
+
+static void dwc_csi_controls_cleanup(void *data)
+{
+ struct dwc_csi_device *csidev = data;
+
+ v4l2_ctrl_handler_free(&csidev->ctrl_handler);
+}
+
+/*
+ * Suspend/resume
+ */
+
+static int dwc_csi_system_suspend(struct device *dev)
+{
+ return pm_runtime_force_suspend(dev);
+}
+
+static int dwc_csi_system_resume(struct device *dev)
+{
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret < 0) {
+ dev_err(dev, "force resume %s failed!\n", dev_name(dev));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dwc_csi_runtime_suspend(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+
+ clk_bulk_disable_unprepare(csidev->num_clks, csidev->clks);
+
+ return 0;
+}
+
+static int dwc_csi_runtime_resume(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+
+ return clk_bulk_prepare_enable(csidev->num_clks, csidev->clks);
+}
+
+static const struct dev_pm_ops dwc_csi_device_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(dwc_csi_system_suspend, dwc_csi_system_resume)
+ SET_RUNTIME_PM_OPS(dwc_csi_runtime_suspend, dwc_csi_runtime_resume, NULL)
+};
+
+static irqreturn_t dwc_csi_irq_handler(int irq, void *priv)
+{
+ struct dwc_csi_device *csidev = priv;
+ u32 status;
+ int i;
+
+ /* Hardware auto clean after read */
+ status = dwc_csi_read(csidev, CSI2RX_INT_ST_MAIN);
+
+ if (status & DWC_EVENT_MASK) {
+ for (i = 0; i < DWC_NUM_EVENTS; ++i) {
+ struct dwc_csi_event *event = &csidev->events[i];
+
+ if (status & event->mask)
+ event->counter++;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static inline void dwc_csi_param_init(struct dwc_csi_device *csidev)
+{
+ csidev->csi_fmt = &dwc_csi_formats[0];
+}
+
+static int dwc_csi_subdev_init(struct dwc_csi_device *csidev)
+{
+ struct v4l2_subdev *sd = &csidev->sd;
+ int ret;
+
+ v4l2_subdev_init(sd, &dwc_csi_subdev_ops);
+ sd->owner = THIS_MODULE;
+ snprintf(sd->name, sizeof(sd->name), "csidev-%s", dev_name(csidev->dev));
+ sd->internal_ops = &dwc_csi_internal_ops;
+
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+ V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_STREAMS;
+ sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+ sd->entity.ops = &dwc_csi_entity_ops;
+
+ sd->dev = csidev->dev;
+
+ csidev->pads[DWC_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ csidev->pads[DWC_CSI2RX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&csidev->sd.entity, DWC_CSI2RX_PADS_NUM,
+ csidev->pads);
+ if (ret) {
+ dev_err(csidev->dev, "Failed to init pads\n");
+ return ret;
+ }
+
+ ret = v4l2_subdev_init_finalize(sd);
+ if (ret)
+ media_entity_cleanup(&sd->entity);
+
+ return ret;
+}
+
+static void dwc_csi_subdev_cleanup(void *data)
+{
+ struct dwc_csi_device *csidev = data;
+
+ v4l2_subdev_cleanup(&csidev->sd);
+ media_entity_cleanup(&csidev->sd.entity);
+}
+
+static int dwc_csi_device_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dwc_csi_device *csidev;
+ int irq;
+ int ret;
+
+ csidev = devm_kzalloc(dev, sizeof(*csidev), GFP_KERNEL);
+ if (!csidev)
+ return -ENOMEM;
+
+ mutex_init(&csidev->lock);
+
+ csidev->dev = dev;
+ memcpy(csidev->events, dwc_events, sizeof(dwc_events));
+
+ csidev->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(csidev->regs)) {
+ dev_err(dev, "Failed to get DWC csi2 register map\n");
+ return PTR_ERR(csidev->regs);
+ }
+
+ csidev->phy = devm_phy_get(dev, "rx");
+ if (IS_ERR(csidev->phy))
+ return dev_err_probe(dev, PTR_ERR(csidev->phy),
+ "Failed to get DPHY Rx\n");
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "Failed to get IRQ\n");
+
+ ret = devm_request_irq(dev, irq, dwc_csi_irq_handler, 0,
+ dev_name(dev), csidev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+ csidev->num_clks = devm_clk_bulk_get_all(dev, &csidev->clks);
+
+ dwc_csi_param_init(csidev);
+
+ ret = dwc_csi_subdev_init(csidev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to initialize subdev\n");
+
+ ret = devm_add_action_or_reset(dev, dwc_csi_subdev_cleanup, csidev);
+ if (ret)
+ return ret;
+
+ ret = dwc_csi_controls_init(csidev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialize controls\n");
+
+ ret = devm_add_action_or_reset(dev, dwc_csi_controls_cleanup, csidev);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, &csidev->sd);
+
+ ret = dwc_csi_async_register(csidev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Async register failed\n");
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static void dwc_csi_device_remove(struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
+
+ v4l2_async_nf_unregister(&csidev->notifier);
+ v4l2_async_nf_cleanup(&csidev->notifier);
+ v4l2_async_unregister_subdev(&csidev->sd);
+
+ pm_runtime_disable(&pdev->dev);
+
+ fwnode_handle_put(csidev->sd.fwnode);
+ mutex_destroy(&csidev->lock);
+
+ pm_runtime_set_suspended(&pdev->dev);
+}
+
+static const struct of_device_id dwc_csi_device_of_match[] = {
+ { .compatible = "snps,dw-mipi-csi2-v150" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, dwc_csi_device_of_match);
+
+static struct platform_driver dwc_csi_device_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "dwc-mipi-csi2",
+ .of_match_table = dwc_csi_device_of_match,
+ .pm = &dwc_csi_device_pm_ops,
+ },
+ .probe = dwc_csi_device_probe,
+ .remove = dwc_csi_device_remove,
+};
+
+module_platform_driver(dwc_csi_device_driver);
+
+MODULE_DESCRIPTION("DesignWare Core MIPI CSI2 driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform: dwc-mipi-csi2");
+MODULE_AUTHOR("NXP Semiconductor, Inc.");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH NOT MERGE 6/7] arm64: dts: imx93-11x11-evk: add camera related nodes
2025-07-01 22:06 [PATCH 0/7] media: add imx93 mipi/controller csi support Frank Li
` (4 preceding siblings ...)
2025-07-01 22:06 ` [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver Frank Li
@ 2025-07-01 22:06 ` Frank Li
2025-07-01 22:06 ` [PATCH NOT MERGE 7/7] media: i2c: add AP1302 driver from community Frank Li
6 siblings, 0 replies; 20+ messages in thread
From: Frank Li @ 2025-07-01 22:06 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li
this just help verify camera censor work at imx93. onnn,ap1302 have not
upstream yet.
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts | 139 ++++++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts b/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts
index 8491eb53120e6..25646364d6953 100644
--- a/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx93-11x11-evk.dts
@@ -107,6 +107,42 @@ reg_usdhc2_vmmc: regulator-usdhc2 {
enable-active-high;
};
+ reg_dvdd_1v2: regulator-dvdd {
+ compatible = "regulator-fixed";
+ regulator-name = "DVDD_1V2";
+ gpio = <&adp5585_isp 7 GPIO_ACTIVE_HIGH>;
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ enable-active-high;
+ };
+
+ reg_vddio_1v8: regulator-vddo {
+ compatible = "regulator-fixed";
+ regulator-name = "VDDIO_1V8";
+ gpio = <&adp5585_isp 6 GPIO_ACTIVE_HIGH>;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ enable-active-high;
+ };
+
+ reg_avdd_2v8: regulator-avdd {
+ compatible = "regulator-fixed";
+ regulator-name = "AVDD_2V8";
+ gpio = <&adp5585_isp 8 GPIO_ACTIVE_HIGH>;
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <2800000>;
+ enable-active-high;
+ };
+
+ reg_hmisc_vddio: regulator-hmisc-vddio {
+ compatible = "regulator-fixed";
+ regulator-name = "HMISC_VDDIO_1V8";
+ gpio = <&adp5585_isp 10 GPIO_ACTIVE_HIGH>;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ enable-active-high;
+ };
+
backlight_lvds: backlight-lvds {
compatible = "pwm-backlight";
pwms = <&adp5585 0 100000 0>;
@@ -201,6 +237,10 @@ &cm33 {
status = "okay";
};
+&dphy_rx {
+ status = "okay";
+};
+
&eqos {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&pinctrl_eqos>;
@@ -256,6 +296,23 @@ &flexcan2 {
status = "okay";
};
+&isi {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ isi_in: endpoint {
+ remote-endpoint = <&mipi_csi_out>;
+ };
+ };
+ };
+};
+
&lpi2c1 {
clock-frequency = <400000>;
pinctrl-names = "default";
@@ -409,6 +466,57 @@ adp5585_isp: io-expander@34 {
#pwm-cells = <3>;
};
+ ap1302: ap1302_mipi@3c {
+ compatible = "onnn,ap1302";
+ reg = <0x3c>;
+ clocks = <&clk IMX93_CLK_24M>;
+ reset-gpios = <&adp5585 0 GPIO_ACTIVE_LOW>;
+ isp_en-gpios = <&adp5585_isp 2 GPIO_ACTIVE_HIGH>;
+ orientation = <2>;
+ rotation = <0>;
+ DVDD-supply = <®_dvdd_1v2>;
+ VDDIO_HMISC-supply = <®_hmisc_vddio>;
+ VDDIO_SMISC-supply = <®_hmisc_vddio>;
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ };
+
+ port@1 {
+ reg = <1>;
+ };
+
+ port@2 {
+ reg = <2>;
+
+ isp_out: endpoint {
+ remote-endpoint = <&mipi_csi_in>;
+ data-lanes = <1 2>;
+ };
+ };
+ };
+
+ sensors {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ onnn,model = "onnn,ar0144";
+
+ sensor@0 {
+ reg = <0>;
+
+ vdd-supply = <®_dvdd_1v2>;
+ vddio-supply = <®_vddio_1v8>;
+ vaa-supply = <®_avdd_2v8>;
+ };
+ };
+ };
+
ptn5110: tcpc@50 {
compatible = "nxp,ptn5110", "tcpci";
reg = <0x50>;
@@ -495,6 +603,10 @@ &lpuart5 {
status = "okay";
};
+&media_blk_ctrl {
+ status = "okay";
+};
+
&micfil {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&pinctrl_pdm>;
@@ -505,6 +617,33 @@ &micfil {
status = "okay";
};
+&mipi_csi {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ mipi_csi_in: endpoint {
+ remote-endpoint = <&isp_out>;
+ data-lanes = <1 2>;
+ clock-lanes = <0>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ mipi_csi_out: endpoint {
+ remote-endpoint = <&isi_in>;
+ };
+ };
+ };
+};
+
&mu1 {
status = "okay";
};
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH NOT MERGE 7/7] media: i2c: add AP1302 driver from community
2025-07-01 22:06 [PATCH 0/7] media: add imx93 mipi/controller csi support Frank Li
` (5 preceding siblings ...)
2025-07-01 22:06 ` [PATCH NOT MERGE 6/7] arm64: dts: imx93-11x11-evk: add camera related nodes Frank Li
@ 2025-07-01 22:06 ` Frank Li
6 siblings, 0 replies; 20+ messages in thread
From: Frank Li @ 2025-07-01 22:06 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li, Guoniu.zhou, Robby Cai
From: "Guoniu.zhou" <guoniu.zhou@nxp.com>
Just for help verify previous mipi csi work. this part upstream later time.
Add AP1302 driver from community but still not be accepted and the
activity in community has stopped, so I just use the base and make
work with our AP1302 device for iMX95 platform.
Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
Reviewed-by: Robby Cai <robby.cai@nxp.com>
---
This one just for convenice to to test whole patch.
formal upstream version post:
https://lore.kernel.org/imx/20250623-ap1302-v3-0-c9ca5b791494@nxp.com/T/#m9ecad9fcbfd1ac1c59b3aa5737e3860a64db2eb4
---
drivers/media/i2c/Kconfig | 13 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/ap1302.c | 2920 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 2934 insertions(+)
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index e68202954a8fd..1a8bb283e4879 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -664,6 +664,19 @@ config VIDEO_OV9734
To compile this driver as a module, choose M here: the
module's name is ov9734.
+config VIDEO_AP1302
+ tristate "ON Semiconductor AP130X ISP"
+ depends on I2C && VIDEO_DEV
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+
+ help
+ This is a Video4Linux2 sensor-level driver for the ON Semiconductor
+ AP1302 ISP.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ap1302.
+
config VIDEO_RDACM20
tristate "IMI RDACM20 camera support"
select VIDEO_MAX9271_LIB
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 5873d29433ee5..4519f98e242d9 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -162,3 +162,4 @@ obj-$(CONFIG_VIDEO_VP27SMPX) += vp27smpx.o
obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o
obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
+obj-$(CONFIG_VIDEO_AP1302) += ap1302.o
diff --git a/drivers/media/i2c/ap1302.c b/drivers/media/i2c/ap1302.c
new file mode 100644
index 0000000000000..56a156603fae2
--- /dev/null
+++ b/drivers/media/i2c/ap1302.c
@@ -0,0 +1,2920 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the AP1302 external camera ISP from ON Semiconductor
+ *
+ * Copyright (C) 2021, Witekio, Inc.
+ * Copyright (C) 2021, Xilinx, Inc.
+ * Copyright (C) 2021, Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/media.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <media/media-entity.h>
+#include <media/mipi-csi2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#define DRIVER_NAME "ap1302"
+
+#define AP1302_FW_WINDOW_SIZE 0x2000
+#define AP1302_FW_WINDOW_OFFSET 0x8000
+
+#define AP1302_MIN_WIDTH 24U
+#define AP1302_MIN_HEIGHT 16U
+#define AP1302_MAX_WIDTH 4224U
+#define AP1302_MAX_HEIGHT 4092U
+
+#define AP1302_REG_16BIT(n) ((2 << 24) | (n))
+#define AP1302_REG_32BIT(n) ((4 << 24) | (n))
+#define AP1302_REG_SIZE(n) ((n) >> 24)
+#define AP1302_REG_ADDR(n) ((n) & 0x0000ffff)
+#define AP1302_REG_PAGE(n) ((n) & 0x00ff0000)
+#define AP1302_REG_PAGE_MASK 0x00ff0000
+
+/* Info Registers */
+#define AP1302_CHIP_VERSION AP1302_REG_16BIT(0x0000)
+#define AP1302_CHIP_ID 0x0265
+#define AP1302_FRAME_CNT AP1302_REG_16BIT(0x0002)
+#define AP1302_ERROR AP1302_REG_16BIT(0x0006)
+#define AP1302_ERR_FILE AP1302_REG_32BIT(0x0008)
+#define AP1302_ERR_LINE AP1302_REG_16BIT(0x000c)
+#define AP1302_SIPM_ERR_0 AP1302_REG_16BIT(0x0014)
+#define AP1302_SIPM_ERR_1 AP1302_REG_16BIT(0x0016)
+#define AP1302_CHIP_REV AP1302_REG_16BIT(0x0050)
+#define AP1302_CON_BUF(n) AP1302_REG_16BIT(0x0a2c + (n))
+#define AP1302_CON_BUF_SIZE 512
+
+/* Control Registers */
+#define AP1302_DZ_TGT_FCT AP1302_REG_16BIT(0x1010)
+#define AP1302_SFX_MODE AP1302_REG_16BIT(0x1016)
+#define AP1302_SFX_MODE_SFX_NORMAL (0U << 0)
+#define AP1302_SFX_MODE_SFX_ALIEN (1U << 0)
+#define AP1302_SFX_MODE_SFX_ANTIQUE (2U << 0)
+#define AP1302_SFX_MODE_SFX_BW (3U << 0)
+#define AP1302_SFX_MODE_SFX_EMBOSS (4U << 0)
+#define AP1302_SFX_MODE_SFX_EMBOSS_COLORED (5U << 0)
+#define AP1302_SFX_MODE_SFX_GRAYSCALE (6U << 0)
+#define AP1302_SFX_MODE_SFX_NEGATIVE (7U << 0)
+#define AP1302_SFX_MODE_SFX_BLUISH (8U << 0)
+#define AP1302_SFX_MODE_SFX_GREENISH (9U << 0)
+#define AP1302_SFX_MODE_SFX_REDISH (10U << 0)
+#define AP1302_SFX_MODE_SFX_POSTERIZE1 (11U << 0)
+#define AP1302_SFX_MODE_SFX_POSTERIZE2 (12U << 0)
+#define AP1302_SFX_MODE_SFX_SEPIA1 (13U << 0)
+#define AP1302_SFX_MODE_SFX_SEPIA2 (14U << 0)
+#define AP1302_SFX_MODE_SFX_SKETCH (15U << 0)
+#define AP1302_SFX_MODE_SFX_SOLARIZE (16U << 0)
+#define AP1302_SFX_MODE_SFX_FOGGY (17U << 0)
+#define AP1302_BUBBLE_OUT_FMT AP1302_REG_16BIT(0x1164)
+#define AP1302_BUBBLE_OUT_FMT_FT_YUV (3U << 4)
+#define AP1302_BUBBLE_OUT_FMT_FT_RGB (4U << 4)
+#define AP1302_BUBBLE_OUT_FMT_FT_YUV_JFIF (5U << 4)
+#define AP1302_BUBBLE_OUT_FMT_FST_RGB_888 (0U << 0)
+#define AP1302_BUBBLE_OUT_FMT_FST_RGB_565 (1U << 0)
+#define AP1302_BUBBLE_OUT_FMT_FST_RGB_555M (2U << 0)
+#define AP1302_BUBBLE_OUT_FMT_FST_RGB_555L (3U << 0)
+#define AP1302_BUBBLE_OUT_FMT_FST_YUV_422 (0U << 0)
+#define AP1302_BUBBLE_OUT_FMT_FST_YUV_420 (1U << 0)
+#define AP1302_BUBBLE_OUT_FMT_FST_YUV_400 (2U << 0)
+#define AP1302_ATOMIC AP1302_REG_16BIT(0x1184)
+#define AP1302_ATOMIC_MODE BIT(2)
+#define AP1302_ATOMIC_FINISH BIT(1)
+#define AP1302_ATOMIC_RECORD BIT(0)
+
+/*
+ * Preview Context Registers (preview_*). AP1302 supports 3 "contexts"
+ * (Preview, Snapshot, Video). These can be programmed for different size,
+ * format, FPS, etc. There is no functional difference between the contexts,
+ * so the only potential benefit of using them is reduced number of register
+ * writes when switching output modes (if your concern is atomicity, see
+ * "atomic" register).
+ * So there's virtually no benefit in using contexts for this driver and it
+ * would significantly increase complexity. Let's use preview context only.
+ */
+#define AP1302_PREVIEW_WIDTH AP1302_REG_16BIT(0x2000)
+#define AP1302_PREVIEW_HEIGHT AP1302_REG_16BIT(0x2002)
+#define AP1302_PREVIEW_ROI_X0 AP1302_REG_16BIT(0x2004)
+#define AP1302_PREVIEW_ROI_Y0 AP1302_REG_16BIT(0x2006)
+#define AP1302_PREVIEW_ROI_X1 AP1302_REG_16BIT(0x2008)
+#define AP1302_PREVIEW_ROI_Y1 AP1302_REG_16BIT(0x200a)
+#define AP1302_PREVIEW_OUT_FMT AP1302_REG_16BIT(0x2012)
+#define AP1302_PREVIEW_OUT_FMT_IPIPE_BYPASS BIT(13)
+#define AP1302_PREVIEW_OUT_FMT_SS BIT(12)
+#define AP1302_PREVIEW_OUT_FMT_FAKE_EN BIT(11)
+#define AP1302_PREVIEW_OUT_FMT_ST_EN BIT(10)
+#define AP1302_PREVIEW_OUT_FMT_IIS_NONE (0U << 8)
+#define AP1302_PREVIEW_OUT_FMT_IIS_POST_VIEW (1U << 8)
+#define AP1302_PREVIEW_OUT_FMT_IIS_VIDEO (2U << 8)
+#define AP1302_PREVIEW_OUT_FMT_IIS_BUBBLE (3U << 8)
+#define AP1302_PREVIEW_OUT_FMT_FT_JPEG_422 (0U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_JPEG_420 (1U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_YUV (3U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_RGB (4U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_YUV_JFIF (5U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_RAW8 (8U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_RAW10 (9U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_RAW12 (10U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_RAW16 (11U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_DNG8 (12U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_DNG10 (13U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_DNG12 (14U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FT_DNG16 (15U << 4)
+#define AP1302_PREVIEW_OUT_FMT_FST_JPEG_ROTATE BIT(2)
+#define AP1302_PREVIEW_OUT_FMT_FST_JPEG_SCAN (0U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_JPEG_JFIF (1U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_JPEG_EXIF (2U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RGB_888 (0U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RGB_565 (1U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RGB_555M (2U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RGB_555L (3U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_YUV_422 (0U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_YUV_420 (1U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_YUV_400 (2U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_SENSOR (0U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_CAPTURE (1U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_CP (2U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_BPC (3U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_IHDR (4U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_PP (5U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_DENSH (6U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_PM (7U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_GC (8U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_CURVE (9U << 0)
+#define AP1302_PREVIEW_OUT_FMT_FST_RAW_CCONV (10U << 0)
+#define AP1302_PREVIEW_S1_SENSOR_MODE AP1302_REG_16BIT(0x202e)
+#define AP1302_PREVIEW_HINF_CTRL AP1302_REG_16BIT(0x2030)
+#define AP1302_PREVIEW_HINF_CTRL_BT656_LE BIT(15)
+#define AP1302_PREVIEW_HINF_CTRL_BT656_16BIT BIT(14)
+#define AP1302_PREVIEW_HINF_CTRL_MUX_DELAY(n) ((n) << 8)
+#define AP1302_PREVIEW_HINF_CTRL_LV_POL BIT(7)
+#define AP1302_PREVIEW_HINF_CTRL_FV_POL BIT(6)
+#define AP1302_PREVIEW_HINF_CTRL_MIPI_CONT_CLK BIT(5)
+#define AP1302_PREVIEW_HINF_CTRL_SPOOF BIT(4)
+#define AP1302_PREVIEW_HINF_CTRL_MIPI_MODE BIT(3)
+#define AP1302_PREVIEW_HINF_CTRL_MIPI_LANES(n) ((n) << 0)
+
+/* IQ Registers */
+#define AP1302_AE_CTRL AP1302_REG_16BIT(0x5002)
+#define AP1302_AE_CTRL_STATS_SEL BIT(11)
+#define AP1302_AE_CTRL_IMM BIT(10)
+#define AP1302_AE_CTRL_ROUND_ISO BIT(9)
+#define AP1302_AE_CTRL_UROI_FACE BIT(7)
+#define AP1302_AE_CTRL_UROI_LOCK BIT(6)
+#define AP1302_AE_CTRL_UROI_BOUND BIT(5)
+#define AP1302_AE_CTRL_IMM1 BIT(4)
+#define AP1302_AE_CTRL_MANUAL_EXP_TIME_GAIN (0U << 0)
+#define AP1302_AE_CTRL_MANUAL_BV_EXP_TIME (1U << 0)
+#define AP1302_AE_CTRL_MANUAL_BV_GAIN (2U << 0)
+#define AP1302_AE_CTRL_MANUAL_BV_ISO (3U << 0)
+#define AP1302_AE_CTRL_AUTO_BV_EXP_TIME (9U << 0)
+#define AP1302_AE_CTRL_AUTO_BV_GAIN (10U << 0)
+#define AP1302_AE_CTRL_AUTO_BV_ISO (11U << 0)
+#define AP1302_AE_CTRL_FULL_AUTO (12U << 0)
+#define AP1302_AE_CTRL_MODE_MASK 0x000f
+#define AP1302_AE_MANUAL_GAIN AP1302_REG_16BIT(0x5006)
+#define AP1302_AE_BV_OFF AP1302_REG_16BIT(0x5014)
+#define AP1302_AE_MET AP1302_REG_16BIT(0x503E)
+#define AP1302_AWB_CTRL AP1302_REG_16BIT(0x5100)
+#define AP1302_AWB_CTRL_RECALC BIT(13)
+#define AP1302_AWB_CTRL_POSTGAIN BIT(12)
+#define AP1302_AWB_CTRL_UNGAIN BIT(11)
+#define AP1302_AWB_CTRL_CLIP BIT(10)
+#define AP1302_AWB_CTRL_SKY BIT(9)
+#define AP1302_AWB_CTRL_FLASH BIT(8)
+#define AP1302_AWB_CTRL_FACE_OFF (0U << 6)
+#define AP1302_AWB_CTRL_FACE_IGNORE (1U << 6)
+#define AP1302_AWB_CTRL_FACE_CONSTRAINED (2U << 6)
+#define AP1302_AWB_CTRL_FACE_ONLY (3U << 6)
+#define AP1302_AWB_CTRL_IMM BIT(5)
+#define AP1302_AWB_CTRL_IMM1 BIT(4)
+#define AP1302_AWB_CTRL_MODE_OFF (0U << 0)
+#define AP1302_AWB_CTRL_MODE_HORIZON (1U << 0)
+#define AP1302_AWB_CTRL_MODE_A (2U << 0)
+#define AP1302_AWB_CTRL_MODE_CWF (3U << 0)
+#define AP1302_AWB_CTRL_MODE_D50 (4U << 0)
+#define AP1302_AWB_CTRL_MODE_D65 (5U << 0)
+#define AP1302_AWB_CTRL_MODE_D75 (6U << 0)
+#define AP1302_AWB_CTRL_MODE_MANUAL (7U << 0)
+#define AP1302_AWB_CTRL_MODE_MEASURE (8U << 0)
+#define AP1302_AWB_CTRL_MODE_AUTO (15U << 0)
+#define AP1302_AWB_CTRL_MODE_MASK 0x000f
+#define AP1302_FLICK_CTRL AP1302_REG_16BIT(0x5440)
+#define AP1302_FLICK_CTRL_FREQ(n) ((n) << 8)
+#define AP1302_FLICK_CTRL_ETC_IHDR_UP BIT(6)
+#define AP1302_FLICK_CTRL_ETC_DIS BIT(5)
+#define AP1302_FLICK_CTRL_FRC_OVERRIDE_MAX_ET BIT(4)
+#define AP1302_FLICK_CTRL_FRC_OVERRIDE_UPPER_ET BIT(3)
+#define AP1302_FLICK_CTRL_FRC_EN BIT(2)
+#define AP1302_FLICK_CTRL_MODE_DISABLED (0U << 0)
+#define AP1302_FLICK_CTRL_MODE_MANUAL (1U << 0)
+#define AP1302_FLICK_CTRL_MODE_AUTO (2U << 0)
+#define AP1302_SCENE_CTRL AP1302_REG_16BIT(0x5454)
+#define AP1302_SCENE_CTRL_MODE_NORMAL (0U << 0)
+#define AP1302_SCENE_CTRL_MODE_PORTRAIT (1U << 0)
+#define AP1302_SCENE_CTRL_MODE_LANDSCAPE (2U << 0)
+#define AP1302_SCENE_CTRL_MODE_SPORT (3U << 0)
+#define AP1302_SCENE_CTRL_MODE_CLOSE_UP (4U << 0)
+#define AP1302_SCENE_CTRL_MODE_NIGHT (5U << 0)
+#define AP1302_SCENE_CTRL_MODE_TWILIGHT (6U << 0)
+#define AP1302_SCENE_CTRL_MODE_BACKLIGHT (7U << 0)
+#define AP1302_SCENE_CTRL_MODE_HIGH_SENSITIVE (8U << 0)
+#define AP1302_SCENE_CTRL_MODE_NIGHT_PORTRAIT (9U << 0)
+#define AP1302_SCENE_CTRL_MODE_BEACH (10U << 0)
+#define AP1302_SCENE_CTRL_MODE_DOCUMENT (11U << 0)
+#define AP1302_SCENE_CTRL_MODE_PARTY (12U << 0)
+#define AP1302_SCENE_CTRL_MODE_FIREWORKS (13U << 0)
+#define AP1302_SCENE_CTRL_MODE_SUNSET (14U << 0)
+#define AP1302_SCENE_CTRL_MODE_AUTO (0xffU << 0)
+
+/* System Registers */
+#define AP1302_BOOTDATA_STAGE AP1302_REG_16BIT(0x6002)
+#define AP1302_WARNING(n) AP1302_REG_16BIT(0x6004 + (n) * 2)
+#define AP1302_SENSOR_SELECT AP1302_REG_16BIT(0x600c)
+#define AP1302_SENSOR_SELECT_TP_MODE(n) ((n) << 8)
+#define AP1302_SENSOR_SELECT_PATTERN_ON BIT(7)
+#define AP1302_SENSOR_SELECT_MODE_3D_ON BIT(6)
+#define AP1302_SENSOR_SELECT_CLOCK BIT(5)
+#define AP1302_SENSOR_SELECT_SINF_MIPI BIT(4)
+#define AP1302_SENSOR_SELECT_YUV BIT(2)
+#define AP1302_SENSOR_SELECT_SENSOR_TP (0U << 0)
+#define AP1302_SENSOR_SELECT_SENSOR(n) (((n) + 1) << 0)
+#define AP1302_SYS_START AP1302_REG_16BIT(0x601a)
+#define AP1302_SYS_START_PLL_LOCK BIT(15)
+#define AP1302_SYS_START_LOAD_OTP BIT(12)
+#define AP1302_SYS_START_RESTART_ERROR BIT(11)
+#define AP1302_SYS_START_STALL_STATUS BIT(9)
+#define AP1302_SYS_START_STALL_EN BIT(8)
+#define AP1302_SYS_START_STALL_MODE_FRAME (0U << 6)
+#define AP1302_SYS_START_STALL_MODE_DISABLED (1U << 6)
+#define AP1302_SYS_START_STALL_MODE_POWER_DOWN (2U << 6)
+#define AP1302_SYS_START_GO BIT(4)
+#define AP1302_SYS_START_PATCH_FUN BIT(1)
+#define AP1302_SYS_START_PLL_INIT BIT(0)
+#define AP1302_DMA_SRC AP1302_REG_32BIT(0x60a0)
+#define AP1302_DMA_DST AP1302_REG_32BIT(0x60a4)
+#define AP1302_DMA_SIP_SIPM(n) ((n) << 26)
+#define AP1302_DMA_SIP_DATA_16_BIT BIT(25)
+#define AP1302_DMA_SIP_ADDR_16_BIT BIT(24)
+#define AP1302_DMA_SIP_ID(n) ((n) << 17)
+#define AP1302_DMA_SIP_REG(n) ((n) << 0)
+#define AP1302_DMA_SIZE AP1302_REG_32BIT(0x60a8)
+#define AP1302_DMA_CTRL AP1302_REG_16BIT(0x60ac)
+#define AP1302_DMA_CTRL_SCH_NORMAL (0 << 12)
+#define AP1302_DMA_CTRL_SCH_NEXT (1 << 12)
+#define AP1302_DMA_CTRL_SCH_NOW (2 << 12)
+#define AP1302_DMA_CTRL_DST_REG (0 << 8)
+#define AP1302_DMA_CTRL_DST_SRAM (1 << 8)
+#define AP1302_DMA_CTRL_DST_SPI (2 << 8)
+#define AP1302_DMA_CTRL_DST_SIP (3 << 8)
+#define AP1302_DMA_CTRL_SRC_REG (0 << 4)
+#define AP1302_DMA_CTRL_SRC_SRAM (1 << 4)
+#define AP1302_DMA_CTRL_SRC_SPI (2 << 4)
+#define AP1302_DMA_CTRL_SRC_SIP (3 << 4)
+#define AP1302_DMA_CTRL_MODE_32_BIT BIT(3)
+#define AP1302_DMA_CTRL_MODE_MASK (7 << 0)
+#define AP1302_DMA_CTRL_MODE_IDLE (0 << 0)
+#define AP1302_DMA_CTRL_MODE_SET (1 << 0)
+#define AP1302_DMA_CTRL_MODE_COPY (2 << 0)
+#define AP1302_DMA_CTRL_MODE_MAP (3 << 0)
+#define AP1302_DMA_CTRL_MODE_UNPACK (4 << 0)
+#define AP1302_DMA_CTRL_MODE_OTP_READ (5 << 0)
+#define AP1302_DMA_CTRL_MODE_SIP_PROBE (6 << 0)
+
+#define AP1302_BRIGHTNESS AP1302_REG_16BIT(0x7000)
+#define AP1302_CONTRAST AP1302_REG_16BIT(0x7002)
+#define AP1302_SATURATION AP1302_REG_16BIT(0x7006)
+#define AP1302_GAMMA AP1302_REG_16BIT(0x700A)
+
+/* Misc Registers */
+#define AP1302_REG_ADV_START 0xe000
+#define AP1302_ADVANCED_BASE AP1302_REG_32BIT(0xf038)
+#define AP1302_SIP_CRC AP1302_REG_16BIT(0xf052)
+
+/* Advanced System Registers */
+#define AP1302_ADV_IRQ_SYS_INTE AP1302_REG_32BIT(0x00230000)
+#define AP1302_ADV_IRQ_SYS_INTE_TEST_COUNT BIT(25)
+#define AP1302_ADV_IRQ_SYS_INTE_HINF_1 BIT(24)
+#define AP1302_ADV_IRQ_SYS_INTE_HINF_0 BIT(23)
+#define AP1302_ADV_IRQ_SYS_INTE_SINF_B_MIPI_L (7U << 20)
+#define AP1302_ADV_IRQ_SYS_INTE_SINF_B_MIPI BIT(19)
+#define AP1302_ADV_IRQ_SYS_INTE_SINF_A_MIPI_L (15U << 14)
+#define AP1302_ADV_IRQ_SYS_INTE_SINF_A_MIPI BIT(13)
+#define AP1302_ADV_IRQ_SYS_INTE_SINF BIT(12)
+#define AP1302_ADV_IRQ_SYS_INTE_IPIPE_S BIT(11)
+#define AP1302_ADV_IRQ_SYS_INTE_IPIPE_B BIT(10)
+#define AP1302_ADV_IRQ_SYS_INTE_IPIPE_A BIT(9)
+#define AP1302_ADV_IRQ_SYS_INTE_IP BIT(8)
+#define AP1302_ADV_IRQ_SYS_INTE_TIMER BIT(7)
+#define AP1302_ADV_IRQ_SYS_INTE_SIPM (3U << 6)
+#define AP1302_ADV_IRQ_SYS_INTE_SIPS_ADR_RANGE BIT(5)
+#define AP1302_ADV_IRQ_SYS_INTE_SIPS_DIRECT_WRITE BIT(4)
+#define AP1302_ADV_IRQ_SYS_INTE_SIPS_FIFO_WRITE BIT(3)
+#define AP1302_ADV_IRQ_SYS_INTE_SPI BIT(2)
+#define AP1302_ADV_IRQ_SYS_INTE_GPIO_CNT BIT(1)
+#define AP1302_ADV_IRQ_SYS_INTE_GPIO_PIN BIT(0)
+
+/* Advanced Slave MIPI Registers */
+#define AP1302_ADV_SINF_MIPI_INTERNAL_p_LANE_n_STAT(p, n) \
+ AP1302_REG_32BIT(0x00420008 + (p) * 0x50000 + (n) * 0x20)
+#define AP1302_LANE_ERR_LP_VAL(n) (((n) >> 30) & 3)
+#define AP1302_LANE_ERR_STATE(n) (((n) >> 24) & 0xf)
+#define AP1302_LANE_ERR BIT(18)
+#define AP1302_LANE_ABORT BIT(17)
+#define AP1302_LANE_LP_VAL(n) (((n) >> 6) & 3)
+#define AP1302_LANE_STATE(n) ((n) & 0xf)
+#define AP1302_LANE_STATE_STOP_S 0x0
+#define AP1302_LANE_STATE_HS_REQ_S 0x1
+#define AP1302_LANE_STATE_LP_REQ_S 0x2
+#define AP1302_LANE_STATE_HS_S 0x3
+#define AP1302_LANE_STATE_LP_S 0x4
+#define AP1302_LANE_STATE_ESC_REQ_S 0x5
+#define AP1302_LANE_STATE_TURN_REQ_S 0x6
+#define AP1302_LANE_STATE_ESC_S 0x7
+#define AP1302_LANE_STATE_ESC_0 0x8
+#define AP1302_LANE_STATE_ESC_1 0x9
+#define AP1302_LANE_STATE_TURN_S 0xa
+#define AP1302_LANE_STATE_TURN_MARK 0xb
+#define AP1302_LANE_STATE_ERROR_S 0xc
+
+#define AP1302_ADV_CAPTURE_A_FV_CNT AP1302_REG_32BIT(0x00490040)
+
+struct ap1302_device;
+
+enum {
+ AP1302_PAD_SINK_0,
+ AP1302_PAD_SINK_1,
+ AP1302_PAD_SOURCE,
+ AP1302_PAD_MAX,
+};
+
+struct ap1302_format_info {
+ unsigned int code;
+ u16 out_fmt;
+ u32 data_type;
+};
+
+struct ap1302_format {
+ struct v4l2_mbus_framefmt format;
+ const struct ap1302_format_info *info;
+};
+
+struct ap1302_size {
+ unsigned int width;
+ unsigned int height;
+};
+
+struct ap1302_sensor_supply {
+ const char *name;
+ unsigned int post_delay_us;
+};
+
+static const struct ap1302_sensor_supply ap1302_supplies[] = {
+ { .name = "DVDD", .post_delay_us = 2000, },
+ { .name = "VDDIO_HMISC", .post_delay_us = 2000, },
+ { .name = "VDDIO_SMISC", .post_delay_us = 2000, },
+};
+
+#define AP1302_NUM_SUPPLIES ARRAY_SIZE(ap1302_supplies)
+
+struct ap1302_sensor_info {
+ const char *model;
+ const char *name;
+ unsigned int i2c_addr;
+ struct ap1302_size resolution;
+ u32 format;
+ const struct ap1302_sensor_supply *supplies;
+};
+
+struct ap1302_sensor {
+ struct ap1302_device *ap1302;
+ unsigned int index;
+
+ struct device_node *of_node;
+ struct device *dev;
+ unsigned int num_supplies;
+ struct regulator_bulk_data *supplies;
+
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+};
+
+static inline struct ap1302_sensor *to_ap1302_sensor(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct ap1302_sensor, sd);
+}
+
+struct ap1302_device {
+ struct device *dev;
+ struct i2c_client *client;
+
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *standby_gpio;
+ struct gpio_desc *isp_en_gpio;
+ struct clk *clock;
+ struct regmap *regmap16;
+ struct regmap *regmap32;
+ u32 reg_page;
+
+ const struct firmware *fw;
+
+ struct v4l2_fwnode_endpoint bus_cfg;
+
+ struct mutex lock; /* Protects formats */
+
+ struct v4l2_subdev sd;
+ struct media_pad pads[AP1302_PAD_MAX];
+ unsigned int width_factor;
+ bool streaming;
+
+ struct v4l2_ctrl_handler ctrls;
+
+ const struct ap1302_sensor_info *sensor_info;
+ struct ap1302_sensor sensors[2];
+
+ struct regulator_bulk_data supplies[AP1302_NUM_SUPPLIES];
+
+ struct {
+ struct dentry *dir;
+ struct mutex lock;
+ u32 sipm_addr;
+ } debugfs;
+};
+
+static inline struct ap1302_device *to_ap1302(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct ap1302_device, sd);
+}
+
+struct ap1302_firmware_header {
+ u32 crc;
+ u32 checksum;
+ u32 pll_init_size;
+ u32 total_size;
+} __packed;
+
+static const struct ap1302_format_info supported_video_formats[] = {
+ {
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .out_fmt = AP1302_PREVIEW_OUT_FMT_FT_YUV_JFIF
+ | AP1302_PREVIEW_OUT_FMT_FST_YUV_422,
+ .data_type = MIPI_CSI2_DT_YUV422_8B,
+ }, {
+ .code = MEDIA_BUS_FMT_UYYVYY8_0_5X24,
+ .out_fmt = AP1302_PREVIEW_OUT_FMT_FT_YUV_JFIF
+ | AP1302_PREVIEW_OUT_FMT_FST_YUV_420,
+ .data_type = MIPI_CSI2_DT_YUV420_8B,
+ },
+};
+
+/* -----------------------------------------------------------------------------
+ * Sensor Info
+ */
+
+static const struct ap1302_sensor_info ap1302_sensor_info[] = {
+ {
+ .model = "onnn,ar0144",
+ .name = "ar0144",
+ .i2c_addr = 0x10,
+ .resolution = { 1280, 800 },
+ .format = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .supplies = (const struct ap1302_sensor_supply[]) {
+ { "vaa", 100 },
+ { "vddio", 100 },
+ { "vdd", 0 },
+ { NULL, 0 },
+ },
+ }, {
+ .model = "onnn,ar0330",
+ .name = "ar0330",
+ .i2c_addr = 0x10,
+ .resolution = { 2304, 1536 },
+ .format = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .supplies = (const struct ap1302_sensor_supply[]) {
+ { "vddpll", 0 },
+ { "vaa", 0 },
+ { "vdd", 0 },
+ { "vddio", 0 },
+ { NULL, 0 },
+ },
+ }, {
+ .model = "onnn,ar1335",
+ .name = "ar1335",
+ .i2c_addr = 0x36,
+ .resolution = { 4208, 3120 },
+ .format = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .supplies = (const struct ap1302_sensor_supply[]) {
+ { "vaa", 0 },
+ { "vddio", 0 },
+ { "vdd", 0 },
+ { NULL, 0 },
+ },
+ },
+};
+
+static const struct ap1302_sensor_info ap1302_sensor_info_tpg = {
+ .model = "",
+ .name = "tpg",
+ .resolution = { 1920, 1080 },
+};
+
+/* -----------------------------------------------------------------------------
+ * Register Configuration
+ */
+
+static const struct regmap_config ap1302_reg16_config = {
+ .name = "val_16bits",
+ .reg_bits = 16,
+ .val_bits = 16,
+ .reg_stride = 2,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_NONE,
+};
+
+static const struct regmap_config ap1302_reg32_config = {
+ .name = "val_32bits",
+ .reg_bits = 16,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_NONE,
+};
+
+static int __ap1302_write(struct ap1302_device *ap1302, u32 reg, u32 val)
+{
+ unsigned int size = AP1302_REG_SIZE(reg);
+ u16 addr = AP1302_REG_ADDR(reg);
+ int ret;
+
+ switch (size) {
+ case 2:
+ ret = regmap_write(ap1302->regmap16, addr, val);
+ break;
+ case 4:
+ ret = regmap_write(ap1302->regmap32, addr, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(ap1302->dev, "%s: register 0x%04x %s failed: %d\n",
+ __func__, addr, "write", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ap1302_write(struct ap1302_device *ap1302, u32 reg, u32 val,
+ int *err)
+{
+ u32 page = AP1302_REG_PAGE(reg);
+ int ret;
+
+ if (err && *err)
+ return *err;
+
+ if (page) {
+ if (ap1302->reg_page != page) {
+ ret = __ap1302_write(ap1302, AP1302_ADVANCED_BASE,
+ page);
+ if (ret < 0)
+ goto done;
+
+ ap1302->reg_page = page;
+ }
+
+ reg &= ~AP1302_REG_PAGE_MASK;
+ reg += AP1302_REG_ADV_START;
+ }
+
+ ret = __ap1302_write(ap1302, reg, val);
+
+done:
+ if (err && ret)
+ *err = ret;
+
+ return ret;
+}
+
+static int __ap1302_read(struct ap1302_device *ap1302, u32 reg, u32 *val)
+{
+ unsigned int size = AP1302_REG_SIZE(reg);
+ u16 addr = AP1302_REG_ADDR(reg);
+ int ret;
+
+ switch (size) {
+ case 2:
+ ret = regmap_read(ap1302->regmap16, addr, val);
+ break;
+ case 4:
+ ret = regmap_read(ap1302->regmap32, addr, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(ap1302->dev, "%s: register 0x%04x %s failed: %d\n",
+ __func__, addr, "read", ret);
+ return ret;
+ }
+
+ dev_dbg(ap1302->dev, "%s: R0x%04x = 0x%0*x\n", __func__,
+ addr, size * 2, *val);
+
+ return 0;
+}
+
+static int ap1302_read(struct ap1302_device *ap1302, u32 reg, u32 *val)
+{
+ u32 page = AP1302_REG_PAGE(reg);
+ int ret;
+
+ if (page) {
+ if (ap1302->reg_page != page) {
+ ret = __ap1302_write(ap1302, AP1302_ADVANCED_BASE,
+ page);
+ if (ret < 0)
+ return ret;
+
+ ap1302->reg_page = page;
+ }
+
+ reg &= ~AP1302_REG_PAGE_MASK;
+ reg += AP1302_REG_ADV_START;
+ }
+
+ return __ap1302_read(ap1302, reg, val);
+}
+
+/* -----------------------------------------------------------------------------
+ * Sensor Registers Access
+ *
+ * Read and write sensor registers through the AP1302 DMA interface.
+ */
+
+static int ap1302_dma_wait_idle(struct ap1302_device *ap1302)
+{
+ unsigned int i;
+ u32 ctrl;
+ int ret;
+
+ for (i = 50; i > 0; i--) {
+ ret = ap1302_read(ap1302, AP1302_DMA_CTRL, &ctrl);
+ if (ret < 0)
+ return ret;
+
+ if ((ctrl & AP1302_DMA_CTRL_MODE_MASK) ==
+ AP1302_DMA_CTRL_MODE_IDLE)
+ break;
+
+ usleep_range(1000, 1500);
+ }
+
+ if (!i) {
+ dev_err(ap1302->dev, "DMA timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int ap1302_sipm_read(struct ap1302_device *ap1302, unsigned int port,
+ u32 reg, u32 *val)
+{
+ unsigned int size = AP1302_REG_SIZE(reg);
+ u32 src;
+ int ret;
+
+ if (size > 2)
+ return -EINVAL;
+
+ ret = ap1302_dma_wait_idle(ap1302);
+ if (ret < 0)
+ return ret;
+
+ ap1302_write(ap1302, AP1302_DMA_SIZE, size, &ret);
+ src = AP1302_DMA_SIP_SIPM(port)
+ | (size == 2 ? AP1302_DMA_SIP_DATA_16_BIT : 0)
+ | AP1302_DMA_SIP_ADDR_16_BIT
+ | AP1302_DMA_SIP_ID(ap1302->sensor_info->i2c_addr)
+ | AP1302_DMA_SIP_REG(AP1302_REG_ADDR(reg));
+ ap1302_write(ap1302, AP1302_DMA_SRC, src, &ret);
+
+ /*
+ * Use the AP1302_DMA_DST register as both the destination address, and
+ * the scratch pad to store the read value.
+ */
+ ap1302_write(ap1302, AP1302_DMA_DST, AP1302_REG_ADDR(AP1302_DMA_DST),
+ &ret);
+
+ ap1302_write(ap1302, AP1302_DMA_CTRL,
+ AP1302_DMA_CTRL_SCH_NORMAL |
+ AP1302_DMA_CTRL_DST_REG |
+ AP1302_DMA_CTRL_SRC_SIP |
+ AP1302_DMA_CTRL_MODE_COPY, &ret);
+ if (ret < 0)
+ return ret;
+
+ ret = ap1302_dma_wait_idle(ap1302);
+ if (ret < 0)
+ return ret;
+
+ ret = ap1302_read(ap1302, AP1302_DMA_DST, val);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The value is stored in big-endian at the DMA_DST address. The regmap
+ * uses big-endian, so 8-bit values are stored in bits 31:24 and 16-bit
+ * values in bits 23:16.
+ */
+ *val >>= 32 - size * 8;
+
+ return 0;
+}
+
+static int ap1302_sipm_write(struct ap1302_device *ap1302, unsigned int port,
+ u32 reg, u32 val)
+{
+ unsigned int size = AP1302_REG_SIZE(reg);
+ u32 dst;
+ int ret;
+
+ if (size > 2)
+ return -EINVAL;
+
+ ret = ap1302_dma_wait_idle(ap1302);
+ if (ret < 0)
+ return ret;
+
+ ap1302_write(ap1302, AP1302_DMA_SIZE, size, &ret);
+
+ /*
+ * Use the AP1302_DMA_SRC register as both the source address, and the
+ * scratch pad to store the write value.
+ *
+ * As the AP1302 uses big endian, to store the value at address DMA_SRC
+ * it must be written in the high order bits of the registers. However,
+ * 8-bit values seem to be incorrectly handled by the AP1302, which
+ * expects them to be stored at DMA_SRC + 1 instead of DMA_SRC. The
+ * value is thus unconditionally shifted by 16 bits, unlike for DMA
+ * reads.
+ */
+ ap1302_write(ap1302, AP1302_DMA_SRC,
+ (val << 16) | AP1302_REG_ADDR(AP1302_DMA_SRC), &ret);
+ if (ret < 0)
+ return ret;
+
+ dst = AP1302_DMA_SIP_SIPM(port)
+ | (size == 2 ? AP1302_DMA_SIP_DATA_16_BIT : 0)
+ | AP1302_DMA_SIP_ADDR_16_BIT
+ | AP1302_DMA_SIP_ID(ap1302->sensor_info->i2c_addr)
+ | AP1302_DMA_SIP_REG(AP1302_REG_ADDR(reg));
+ ap1302_write(ap1302, AP1302_DMA_DST, dst, &ret);
+
+ ap1302_write(ap1302, AP1302_DMA_CTRL,
+ AP1302_DMA_CTRL_SCH_NORMAL |
+ AP1302_DMA_CTRL_DST_SIP |
+ AP1302_DMA_CTRL_SRC_REG |
+ AP1302_DMA_CTRL_MODE_COPY, &ret);
+ if (ret < 0)
+ return ret;
+
+ ret = ap1302_dma_wait_idle(ap1302);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Debugfs
+ */
+
+static int ap1302_sipm_addr_get(void *arg, u64 *val)
+{
+ struct ap1302_device *ap1302 = arg;
+
+ mutex_lock(&ap1302->debugfs.lock);
+ *val = ap1302->debugfs.sipm_addr;
+ mutex_unlock(&ap1302->debugfs.lock);
+
+ return 0;
+}
+
+static int ap1302_sipm_addr_set(void *arg, u64 val)
+{
+ struct ap1302_device *ap1302 = arg;
+
+ if (val & ~0x8700ffff)
+ return -EINVAL;
+
+ switch ((val >> 24) & 7) {
+ case 1:
+ case 2:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&ap1302->debugfs.lock);
+ ap1302->debugfs.sipm_addr = val;
+ mutex_unlock(&ap1302->debugfs.lock);
+
+ return 0;
+}
+
+static int ap1302_sipm_data_get(void *arg, u64 *val)
+{
+ struct ap1302_device *ap1302 = arg;
+ u32 value;
+ u32 addr;
+ int ret;
+
+ mutex_lock(&ap1302->debugfs.lock);
+
+ addr = ap1302->debugfs.sipm_addr;
+ if (!addr) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = ap1302_sipm_read(ap1302, addr >> 30, addr & ~BIT(31),
+ &value);
+ if (!ret)
+ *val = value;
+
+unlock:
+ mutex_unlock(&ap1302->debugfs.lock);
+
+ return ret;
+}
+
+static int ap1302_sipm_data_set(void *arg, u64 val)
+{
+ struct ap1302_device *ap1302 = arg;
+ u32 addr;
+ int ret;
+
+ mutex_lock(&ap1302->debugfs.lock);
+
+ addr = ap1302->debugfs.sipm_addr;
+ if (!addr) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = ap1302_sipm_write(ap1302, addr >> 30, addr & ~BIT(31),
+ val);
+
+unlock:
+ mutex_unlock(&ap1302->debugfs.lock);
+
+ return ret;
+}
+
+/*
+ * The sipm_addr and sipm_data attributes expose access to the sensor I2C bus.
+ *
+ * To read or write a register, sipm_addr has to first be written with the
+ * register address. The address is a 32-bit integer formatted as follows.
+ *
+ * I000 0SSS 0000 0000 RRRR RRRR RRRR RRRR
+ *
+ * I: SIPM index (0 or 1)
+ * S: Size (1: 8-bit, 2: 16-bit)
+ * R: Register address (16-bit)
+ *
+ * The sipm_data attribute can then be read to read the register value, or
+ * written to write it.
+ */
+
+DEFINE_DEBUGFS_ATTRIBUTE(ap1302_sipm_addr_fops, ap1302_sipm_addr_get,
+ ap1302_sipm_addr_set, "0x%08llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(ap1302_sipm_data_fops, ap1302_sipm_data_get,
+ ap1302_sipm_data_set, "0x%08llx\n");
+
+static void ap1302_debugfs_init(struct ap1302_device *ap1302)
+{
+ struct dentry *dir;
+ char name[16];
+
+ mutex_init(&ap1302->debugfs.lock);
+
+ snprintf(name, sizeof(name), "ap1302.%s", dev_name(ap1302->dev));
+
+ dir = debugfs_create_dir(name, NULL);
+ if (IS_ERR(dir))
+ return;
+
+ ap1302->debugfs.dir = dir;
+
+ debugfs_create_file_unsafe("sipm_addr", 0600, ap1302->debugfs.dir,
+ ap1302, &ap1302_sipm_addr_fops);
+ debugfs_create_file_unsafe("sipm_data", 0600, ap1302->debugfs.dir,
+ ap1302, &ap1302_sipm_data_fops);
+}
+
+static void ap1302_debugfs_cleanup(struct ap1302_device *ap1302)
+{
+ debugfs_remove_recursive(ap1302->debugfs.dir);
+ mutex_destroy(&ap1302->debugfs.lock);
+}
+
+/* -----------------------------------------------------------------------------
+ * Power Handling
+ */
+
+static int ap1302_power_on_sensors(struct ap1302_device *ap1302)
+{
+ struct ap1302_sensor *sensor;
+ unsigned int i, j;
+ int ret;
+
+ if (!ap1302->sensor_info->supplies)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ap1302->sensors); ++i) {
+ sensor = &ap1302->sensors[i];
+ ret = 0;
+
+ for (j = 0; j < sensor->num_supplies; ++j) {
+ unsigned int delay;
+
+ /*
+ * We can't use regulator_bulk_enable() as it would
+ * enable all supplies in parallel, breaking the sensor
+ * power sequencing constraints.
+ */
+ ret = regulator_enable(sensor->supplies[j].consumer);
+ if (ret < 0) {
+ dev_err(ap1302->dev,
+ "Failed to enable supply %u for sensor %u\n",
+ j, i);
+ goto error;
+ }
+
+ delay = ap1302->sensor_info->supplies[j].post_delay_us;
+ usleep_range(delay, delay + 100);
+ }
+ }
+
+ return 0;
+
+error:
+ for (; j > 0; --j)
+ regulator_disable(sensor->supplies[j - 1].consumer);
+
+ for (; i > 0; --i) {
+ sensor = &ap1302->sensors[i - 1];
+ regulator_bulk_disable(sensor->num_supplies, sensor->supplies);
+ }
+
+ return ret;
+}
+
+static void ap1302_power_off_sensors(struct ap1302_device *ap1302)
+{
+ unsigned int i;
+
+ if (!ap1302->sensor_info->supplies)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(ap1302->sensors); ++i) {
+ struct ap1302_sensor *sensor = &ap1302->sensors[i];
+
+ regulator_bulk_disable(sensor->num_supplies, sensor->supplies);
+ }
+}
+
+static int ap1302_power_on(struct ap1302_device *ap1302)
+{
+ int ret;
+ int i;
+
+ /* 0. RESET was asserted when getting the GPIO. */
+
+ /* 1. Assert STANDBY. */
+ if (ap1302->standby_gpio) {
+ gpiod_set_value_cansleep(ap1302->standby_gpio, 1);
+ usleep_range(200, 1000);
+ }
+
+ /* 2. Power up the regulators. To be implemented. */
+ for (i = 0; i < AP1302_NUM_SUPPLIES; ++i) {
+ unsigned int delay;
+
+ ret = regulator_enable(ap1302->supplies[i].consumer);
+ if (ret < 0) {
+ dev_err(ap1302->dev, "enabel regulator fail\n");
+ return ret;
+ }
+
+ delay = ap1302_supplies[i].post_delay_us;
+ usleep_range(delay, delay + 100);
+ }
+
+ /* 3. De-assert STANDBY. */
+ if (ap1302->standby_gpio) {
+ gpiod_set_value_cansleep(ap1302->standby_gpio, 0);
+ usleep_range(200, 1000);
+ }
+
+ /* 4. Turn the clock on. */
+ ret = clk_prepare_enable(ap1302->clock);
+ if (ret < 0) {
+ dev_err(ap1302->dev, "Failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ /* 5. De-assert RESET. */
+ gpiod_set_value_cansleep(ap1302->reset_gpio, 0);
+
+ /*
+ * 6. Wait for the AP1302 to initialize. The datasheet doesn't specify
+ * how long this takes.
+ */
+ usleep_range(10000, 11000);
+
+ return 0;
+}
+
+static void ap1302_power_off(struct ap1302_device *ap1302)
+{
+ /* 1. Assert RESET. */
+ gpiod_set_value_cansleep(ap1302->reset_gpio, 1);
+
+ /* 2. Turn the clock off. */
+ clk_disable_unprepare(ap1302->clock);
+
+ /* 3. Assert STANDBY. */
+ if (ap1302->standby_gpio) {
+ gpiod_set_value_cansleep(ap1302->standby_gpio, 1);
+ usleep_range(200, 1000);
+ }
+
+ /* 4. Power down the regulators. To be implemented. */
+ regulator_bulk_disable(AP1302_NUM_SUPPLIES, ap1302->supplies);
+
+ /* 5. De-assert STANDBY. */
+ if (ap1302->standby_gpio) {
+ usleep_range(200, 1000);
+ gpiod_set_value_cansleep(ap1302->standby_gpio, 0);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware Configuration
+ */
+
+static int ap1302_dump_console(struct ap1302_device *ap1302)
+{
+ u8 *buffer;
+ u8 *endp;
+ u8 *p;
+ int ret;
+
+ buffer = kmalloc(AP1302_CON_BUF_SIZE + 1, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ ret = regmap_raw_read(ap1302->regmap16, AP1302_CON_BUF(0), buffer,
+ AP1302_CON_BUF_SIZE);
+ if (ret < 0) {
+ dev_err(ap1302->dev, "Failed to read console buffer: %d\n",
+ ret);
+ goto done;
+ }
+
+ print_hex_dump(KERN_INFO, "console ", DUMP_PREFIX_OFFSET, 16, 1, buffer,
+ AP1302_CON_BUF_SIZE, true);
+
+ buffer[AP1302_CON_BUF_SIZE] = '\0';
+
+ for (p = buffer; p < buffer + AP1302_CON_BUF_SIZE && *p; p = endp + 1) {
+ endp = strchrnul(p, '\n');
+ *endp = '\0';
+
+ pr_info("console %s\n", p);
+ }
+
+ ret = 0;
+
+done:
+ kfree(buffer);
+ return ret;
+}
+
+static const struct ap1302_format_info *find_info_by_code(u32 code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(supported_video_formats); i++) {
+ if (supported_video_formats[i].code == code)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(supported_video_formats))
+ return &supported_video_formats[0];
+
+ return &supported_video_formats[i];
+}
+
+static int ap1302_configure(struct ap1302_device *ap1302)
+{
+ struct v4l2_subdev *sd = &ap1302->sd;
+ struct v4l2_subdev_state *state;
+ const struct v4l2_mbus_framefmt *format;
+ const struct ap1302_format_info *info;
+ unsigned int data_lanes = ap1302->bus_cfg.bus.mipi_csi2.num_data_lanes;
+ int ret = 0;
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ format = v4l2_subdev_state_get_format(state, AP1302_PAD_SOURCE);
+
+ info = find_info_by_code(format->code);
+
+ ap1302_write(ap1302, AP1302_PREVIEW_HINF_CTRL,
+ AP1302_PREVIEW_HINF_CTRL_SPOOF |
+ AP1302_PREVIEW_HINF_CTRL_MIPI_LANES(data_lanes), &ret);
+
+ ap1302_write(ap1302, AP1302_PREVIEW_WIDTH,
+ format->width / ap1302->width_factor, &ret);
+ ap1302_write(ap1302, AP1302_PREVIEW_HEIGHT,
+ format->height, &ret);
+ ap1302_write(ap1302, AP1302_PREVIEW_OUT_FMT,
+ info->out_fmt, &ret);
+
+ if (ret < 0)
+ goto unlock_state;
+
+ __v4l2_ctrl_handler_setup(&ap1302->ctrls);
+
+unlock_state:
+ v4l2_subdev_unlock_state(state);
+ return ret;
+}
+
+static int ap1302_stall(struct ap1302_device *ap1302, bool stall)
+{
+ int ret = 0;
+
+ if (stall) {
+ ap1302_write(ap1302, AP1302_SYS_START,
+ AP1302_SYS_START_PLL_LOCK |
+ AP1302_SYS_START_STALL_MODE_DISABLED, &ret);
+ ap1302_write(ap1302, AP1302_SYS_START,
+ AP1302_SYS_START_PLL_LOCK |
+ AP1302_SYS_START_STALL_EN |
+ AP1302_SYS_START_STALL_MODE_DISABLED, &ret);
+ if (ret < 0)
+ return ret;
+
+ msleep(200);
+ return ret;
+ } else {
+ return ap1302_write(ap1302, AP1302_SYS_START,
+ AP1302_SYS_START_PLL_LOCK |
+ AP1302_SYS_START_STALL_STATUS |
+ AP1302_SYS_START_STALL_EN |
+ AP1302_SYS_START_STALL_MODE_DISABLED, NULL);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Controls
+ */
+
+static u16 ap1302_wb_values[] = {
+ AP1302_AWB_CTRL_MODE_OFF, /* V4L2_WHITE_BALANCE_MANUAL */
+ AP1302_AWB_CTRL_MODE_AUTO, /* V4L2_WHITE_BALANCE_AUTO */
+ AP1302_AWB_CTRL_MODE_A, /* V4L2_WHITE_BALANCE_INCANDESCENT */
+ AP1302_AWB_CTRL_MODE_D50, /* V4L2_WHITE_BALANCE_FLUORESCENT */
+ AP1302_AWB_CTRL_MODE_D65, /* V4L2_WHITE_BALANCE_FLUORESCENT_H */
+ AP1302_AWB_CTRL_MODE_HORIZON, /* V4L2_WHITE_BALANCE_HORIZON */
+ AP1302_AWB_CTRL_MODE_D65, /* V4L2_WHITE_BALANCE_DAYLIGHT */
+ AP1302_AWB_CTRL_MODE_AUTO, /* V4L2_WHITE_BALANCE_FLASH */
+ AP1302_AWB_CTRL_MODE_D75, /* V4L2_WHITE_BALANCE_CLOUDY */
+ AP1302_AWB_CTRL_MODE_D75, /* V4L2_WHITE_BALANCE_SHADE */
+};
+
+static inline struct ap1302_device *ctrl_to_sd(struct v4l2_ctrl *ctrl)
+{
+ return container_of(ctrl->handler, struct ap1302_device, ctrls);
+}
+
+static int ap1302_set_wb_mode(struct ap1302_device *ap1302, s32 mode)
+{
+ u32 val;
+ int ret;
+
+ ret = ap1302_read(ap1302, AP1302_AWB_CTRL, &val);
+ if (ret)
+ return ret;
+ val &= ~AP1302_AWB_CTRL_MODE_MASK;
+ val |= ap1302_wb_values[mode];
+
+ if (mode == V4L2_WHITE_BALANCE_FLASH)
+ val |= AP1302_AWB_CTRL_FLASH;
+ else
+ val &= ~AP1302_AWB_CTRL_FLASH;
+
+ return ap1302_write(ap1302, AP1302_AWB_CTRL, val, NULL);
+}
+
+static int ap1302_set_exposure(struct ap1302_device *ap1302, s32 mode)
+{
+ u32 val;
+ int ret;
+
+ ret = ap1302_read(ap1302, AP1302_AE_CTRL, &val);
+ if (ret)
+ return ret;
+
+ val &= ~AP1302_AE_CTRL_MODE_MASK;
+ val |= mode;
+
+ return ap1302_write(ap1302, AP1302_AE_CTRL, val, NULL);
+}
+
+static int ap1302_set_exp_met(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_AE_MET, val, NULL);
+}
+
+static int ap1302_set_gain(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_AE_MANUAL_GAIN, val, NULL);
+}
+
+static int ap1302_set_contrast(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_CONTRAST, val, NULL);
+}
+
+static int ap1302_set_brightness(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_BRIGHTNESS, val, NULL);
+}
+
+static int ap1302_set_saturation(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_SATURATION, val, NULL);
+}
+
+static int ap1302_set_gamma(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_GAMMA, val, NULL);
+}
+
+static int ap1302_set_zoom(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_DZ_TGT_FCT, val, NULL);
+}
+
+static u16 ap1302_sfx_values[] = {
+ AP1302_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_NONE */
+ AP1302_SFX_MODE_SFX_BW, /* V4L2_COLORFX_BW */
+ AP1302_SFX_MODE_SFX_SEPIA1, /* V4L2_COLORFX_SEPIA */
+ AP1302_SFX_MODE_SFX_NEGATIVE, /* V4L2_COLORFX_NEGATIVE */
+ AP1302_SFX_MODE_SFX_EMBOSS, /* V4L2_COLORFX_EMBOSS */
+ AP1302_SFX_MODE_SFX_SKETCH, /* V4L2_COLORFX_SKETCH */
+ AP1302_SFX_MODE_SFX_BLUISH, /* V4L2_COLORFX_SKY_BLUE */
+ AP1302_SFX_MODE_SFX_GREENISH, /* V4L2_COLORFX_GRASS_GREEN */
+ AP1302_SFX_MODE_SFX_REDISH, /* V4L2_COLORFX_SKIN_WHITEN */
+ AP1302_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_VIVID */
+ AP1302_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_AQUA */
+ AP1302_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_ART_FREEZE */
+ AP1302_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_SILHOUETTE */
+ AP1302_SFX_MODE_SFX_SOLARIZE, /* V4L2_COLORFX_SOLARIZATION */
+ AP1302_SFX_MODE_SFX_ANTIQUE, /* V4L2_COLORFX_ANTIQUE */
+ AP1302_SFX_MODE_SFX_NORMAL, /* V4L2_COLORFX_SET_CBCR */
+};
+
+static int ap1302_set_special_effect(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_SFX_MODE, ap1302_sfx_values[val],
+ NULL);
+}
+
+static u16 ap1302_scene_mode_values[] = {
+ AP1302_SCENE_CTRL_MODE_NORMAL, /* V4L2_SCENE_MODE_NONE */
+ AP1302_SCENE_CTRL_MODE_BACKLIGHT, /* V4L2_SCENE_MODE_BACKLIGHT */
+ AP1302_SCENE_CTRL_MODE_BEACH, /* V4L2_SCENE_MODE_BEACH_SNOW */
+ AP1302_SCENE_CTRL_MODE_TWILIGHT, /* V4L2_SCENE_MODE_CANDLE_LIGHT */
+ AP1302_SCENE_CTRL_MODE_NORMAL, /* V4L2_SCENE_MODE_DAWN_DUSK */
+ AP1302_SCENE_CTRL_MODE_NORMAL, /* V4L2_SCENE_MODE_FALL_COLORS */
+ AP1302_SCENE_CTRL_MODE_FIREWORKS, /* V4L2_SCENE_MODE_FIREWORKS */
+ AP1302_SCENE_CTRL_MODE_LANDSCAPE, /* V4L2_SCENE_MODE_LANDSCAPE */
+ AP1302_SCENE_CTRL_MODE_NIGHT, /* V4L2_SCENE_MODE_NIGHT */
+ AP1302_SCENE_CTRL_MODE_PARTY, /* V4L2_SCENE_MODE_PARTY_INDOOR */
+ AP1302_SCENE_CTRL_MODE_PORTRAIT, /* V4L2_SCENE_MODE_PORTRAIT */
+ AP1302_SCENE_CTRL_MODE_SPORT, /* V4L2_SCENE_MODE_SPORTS */
+ AP1302_SCENE_CTRL_MODE_SUNSET, /* V4L2_SCENE_MODE_SUNSET */
+ AP1302_SCENE_CTRL_MODE_DOCUMENT, /* V4L2_SCENE_MODE_TEXT */
+};
+
+static int ap1302_set_scene_mode(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_SCENE_CTRL,
+ ap1302_scene_mode_values[val], NULL);
+}
+
+static const u16 ap1302_flicker_values[] = {
+ AP1302_FLICK_CTRL_MODE_DISABLED,
+ AP1302_FLICK_CTRL_FREQ(50) | AP1302_FLICK_CTRL_MODE_MANUAL,
+ AP1302_FLICK_CTRL_FREQ(60) | AP1302_FLICK_CTRL_MODE_MANUAL,
+ AP1302_FLICK_CTRL_MODE_AUTO,
+};
+
+static int ap1302_set_flicker_freq(struct ap1302_device *ap1302, s32 val)
+{
+ return ap1302_write(ap1302, AP1302_FLICK_CTRL,
+ ap1302_flicker_values[val], NULL);
+}
+
+static int ap1302_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct ap1302_device *ap1302 = ctrl_to_sd(ctrl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE:
+ return ap1302_set_wb_mode(ap1302, ctrl->val);
+
+ case V4L2_CID_EXPOSURE:
+ return ap1302_set_exposure(ap1302, ctrl->val);
+
+ case V4L2_CID_EXPOSURE_METERING:
+ return ap1302_set_exp_met(ap1302, ctrl->val);
+
+ case V4L2_CID_GAIN:
+ return ap1302_set_gain(ap1302, ctrl->val);
+
+ case V4L2_CID_GAMMA:
+ return ap1302_set_gamma(ap1302, ctrl->val);
+
+ case V4L2_CID_CONTRAST:
+ return ap1302_set_contrast(ap1302, ctrl->val);
+
+ case V4L2_CID_BRIGHTNESS:
+ return ap1302_set_brightness(ap1302, ctrl->val);
+
+ case V4L2_CID_SATURATION:
+ return ap1302_set_saturation(ap1302, ctrl->val);
+
+ case V4L2_CID_ZOOM_ABSOLUTE:
+ return ap1302_set_zoom(ap1302, ctrl->val);
+
+ case V4L2_CID_COLORFX:
+ return ap1302_set_special_effect(ap1302, ctrl->val);
+
+ case V4L2_CID_SCENE_MODE:
+ return ap1302_set_scene_mode(ap1302, ctrl->val);
+
+ case V4L2_CID_POWER_LINE_FREQUENCY:
+ return ap1302_set_flicker_freq(ap1302, ctrl->val);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const s64 ap1302_link_freqs[] = {
+ 445000000,
+};
+
+static int ap1302_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct ap1302_device *ap1302 = ctrl_to_sd(ctrl);
+ int i;
+ u32 val;
+
+ switch (ctrl->id) {
+ case V4L2_CID_LINK_FREQ:
+ ap1302_read(ap1302, AP1302_REG_16BIT(0x0068), &val);
+ for (i = 0; i < ARRAY_SIZE(ap1302_link_freqs); i++) {
+ if (ap1302_link_freqs[i] == (val / 2) * 1000000)
+ break;
+ }
+ WARN_ON(i == ARRAY_SIZE(ap1302_link_freqs));
+ __v4l2_ctrl_s_ctrl(ctrl, i);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops ap1302_ctrl_ops = {
+ .s_ctrl = ap1302_s_ctrl,
+ .g_volatile_ctrl = ap1302_g_volatile_ctrl,
+};
+
+static const struct v4l2_ctrl_config ap1302_ctrls[] = {
+ {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
+ .min = 0,
+ .max = 9,
+ .def = 1,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_GAMMA,
+ .name = "Gamma",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0x0100,
+ .max = 0xFFFF,
+ .step = 0x100,
+ .def = 0x1000,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_CONTRAST,
+ .name = "Contrast",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0x100,
+ .max = 0xFFFF,
+ .step = 0x100,
+ .def = 0x100,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_BRIGHTNESS,
+ .name = "Brightness",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0x100,
+ .max = 0xFFFF,
+ .step = 0x100,
+ .def = 0x100,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_SATURATION,
+ .name = "Saturation",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0x0100,
+ .max = 0xFFFF,
+ .step = 0x100,
+ .def = 0x1000,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_EXPOSURE,
+ .name = "Exposure",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0x0,
+ .max = 0xC,
+ .step = 1,
+ .def = 0xC,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_EXPOSURE_METERING,
+ .name = "Exposure Metering",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0x0,
+ .max = 0x3,
+ .step = 1,
+ .def = 0x1,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_GAIN,
+ .name = "Gain",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0x0100,
+ .max = 0xFFFF,
+ .step = 0x100,
+ .def = 0x100,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_ZOOM_ABSOLUTE,
+ .min = 0x0100,
+ .max = 0x1000,
+ .step = 1,
+ .def = 0x0100,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_COLORFX,
+ .min = 0,
+ .max = 15,
+ .def = 0,
+ .menu_skip_mask = BIT(15) | BIT(12) | BIT(11) | BIT(10) | BIT(9),
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_SCENE_MODE,
+ .min = 0,
+ .max = 13,
+ .def = 0,
+ .menu_skip_mask = BIT(5) | BIT(4),
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_POWER_LINE_FREQUENCY,
+ .min = 0,
+ .max = 3,
+ .def = 3,
+ }, {
+ .ops = &ap1302_ctrl_ops,
+ .id = V4L2_CID_LINK_FREQ,
+ .min = 0,
+ .max = ARRAY_SIZE(ap1302_link_freqs) - 1,
+ .def = 0,
+ .qmenu_int = ap1302_link_freqs,
+ },
+};
+
+static int ap1302_ctrls_init(struct ap1302_device *ap1302)
+{
+ struct v4l2_fwnode_device_properties props;
+ unsigned int i;
+ int ret;
+
+ ret = v4l2_ctrl_handler_init(&ap1302->ctrls, ARRAY_SIZE(ap1302_ctrls));
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(ap1302_ctrls); i++)
+ v4l2_ctrl_new_custom(&ap1302->ctrls, &ap1302_ctrls[i], NULL);
+
+ if (ap1302->ctrls.error) {
+ ret = ap1302->ctrls.error;
+ goto free_ctrls;
+ }
+
+ ret = v4l2_fwnode_device_parse(ap1302->dev, &props);
+ if (ret)
+ goto free_ctrls;
+
+ ret = v4l2_ctrl_new_fwnode_properties(&ap1302->ctrls,
+ &ap1302_ctrl_ops, &props);
+ if (ret)
+ goto free_ctrls;
+
+ /* Use same lock for controls as for everything else. */
+ ap1302->ctrls.lock = &ap1302->lock;
+ ap1302->sd.ctrl_handler = &ap1302->ctrls;
+
+ return 0;
+
+free_ctrls:
+ v4l2_ctrl_handler_free(&ap1302->ctrls);
+ return ret;
+}
+
+static void ap1302_ctrls_cleanup(struct ap1302_device *ap1302)
+{
+ v4l2_ctrl_handler_free(&ap1302->ctrls);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Subdev Operations
+ */
+
+static int ap1302_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+ const struct ap1302_sensor_info *info = ap1302->sensor_info;
+ unsigned int pad;
+
+ for (pad = 0; pad < AP1302_PAD_MAX; ++pad) {
+ struct v4l2_mbus_framefmt *format =
+ v4l2_subdev_state_get_format(state, pad);
+
+ format->width = info->resolution.width;
+ format->height = info->resolution.height;
+
+ /*
+ * The source pad combines images side by side in multi-sensor
+ * setup.
+ */
+ if (pad == AP1302_PAD_SOURCE) {
+ format->width *= ap1302->width_factor;
+ format->code = supported_video_formats[0].code;
+ } else {
+ format->code = info->format;
+ }
+
+ format->field = V4L2_FIELD_NONE;
+ format->colorspace = V4L2_COLORSPACE_SRGB;
+ }
+
+ return 0;
+}
+
+static int ap1302_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+
+ if (code->pad != AP1302_PAD_SOURCE) {
+ /*
+ * On the sink pads, only the format produced by the sensor is
+ * supported.
+ */
+ if (code->index)
+ return -EINVAL;
+
+ code->code = ap1302->sensor_info->format;
+ } else {
+ /* On the source pad, multiple formats are supported. */
+ if (code->index >= ARRAY_SIZE(supported_video_formats))
+ return -EINVAL;
+
+ code->code = supported_video_formats[code->index].code;
+ }
+
+ return 0;
+}
+
+static int ap1302_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+
+ if (fse->index)
+ return -EINVAL;
+
+ if (fse->pad != AP1302_PAD_SOURCE) {
+ /*
+ * On the sink pads, only the size produced by the sensor is
+ * supported.
+ */
+ if (fse->code != ap1302->sensor_info->format)
+ return -EINVAL;
+
+ fse->min_width = ap1302->sensor_info->resolution.width;
+ fse->min_height = ap1302->sensor_info->resolution.height;
+ fse->max_width = ap1302->sensor_info->resolution.width;
+ fse->max_height = ap1302->sensor_info->resolution.height;
+ } else {
+ /*
+ * On the source pad, the AP1302 can freely scale within the
+ * scaler's limits.
+ */
+ if (fse->index > ARRAY_SIZE(supported_video_formats))
+ return -EINVAL;
+
+ fse->min_width = AP1302_MIN_WIDTH * ap1302->width_factor;
+ fse->min_height = AP1302_MIN_HEIGHT;
+ fse->max_width = AP1302_MAX_WIDTH;
+ fse->max_height = AP1302_MAX_HEIGHT;
+ }
+
+ return 0;
+}
+
+static int ap1302_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+ const struct ap1302_format_info *info = NULL;
+ struct v4l2_mbus_framefmt *format;
+ unsigned int i;
+
+ /* Formats on the sink pads can't be changed. */
+ if (fmt->pad != AP1302_PAD_SOURCE)
+ return v4l2_subdev_get_fmt(sd, state, fmt);
+
+ format = v4l2_subdev_state_get_format(state, fmt->pad, fmt->stream);
+
+ /* Validate the media bus code, default to the first supported value. */
+ for (i = 0; i < ARRAY_SIZE(supported_video_formats); i++) {
+ if (supported_video_formats[i].code == fmt->format.code) {
+ info = &supported_video_formats[i];
+ break;
+ }
+ }
+
+ if (!info)
+ info = &supported_video_formats[0];
+
+ /*
+ * Clamp the size. The width must be a multiple of 4 (or 8 in the
+ * dual-sensor case) and the height a multiple of 2.
+ */
+ fmt->format.width = clamp(ALIGN_DOWN(fmt->format.width,
+ 4 * ap1302->width_factor),
+ AP1302_MIN_WIDTH * ap1302->width_factor,
+ AP1302_MAX_WIDTH);
+ fmt->format.height = clamp(ALIGN_DOWN(fmt->format.height, 2),
+ AP1302_MIN_HEIGHT, AP1302_MAX_HEIGHT);
+
+ mutex_lock(&ap1302->lock);
+
+ format->width = fmt->format.width;
+ format->height = fmt->format.height;
+ format->code = info->code;
+
+ mutex_unlock(&ap1302->lock);
+
+ fmt->format = *format;
+
+ return 0;
+}
+
+static int ap1302_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+ const struct ap1302_size *resolution = &ap1302->sensor_info->resolution;
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_NATIVE_SIZE:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP:
+ sel->r.left = 0;
+ sel->r.top = 0;
+ sel->r.width = resolution->width * ap1302->width_factor;
+ sel->r.height = resolution->height;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ap1302_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
+ struct v4l2_mbus_frame_desc *fd)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+ struct v4l2_subdev_state *state;
+ const struct v4l2_mbus_framefmt *format;
+ const struct ap1302_format_info *info;
+
+ if (pad != AP1302_PAD_SOURCE || !fd)
+ return -EINVAL;
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ format = v4l2_subdev_state_get_format(state, AP1302_PAD_SOURCE);
+
+ info = find_info_by_code(format->code);
+
+ memset(fd, 0x0, sizeof(*fd));
+
+ mutex_lock(&ap1302->lock);
+
+ fd->entry[0].flags = 0;
+ fd->entry[0].pixelcode = info->code;
+ fd->entry[0].bus.csi2.vc = 0;
+ fd->entry[0].bus.csi2.dt = info->data_type;
+
+ mutex_unlock(&ap1302->lock);
+
+ fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+ fd->num_entries = 1;
+
+ v4l2_subdev_unlock_state(state);
+ return 0;
+}
+
+static int ap1302_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+ int ret = 0;
+
+ mutex_lock(&ap1302->lock);
+
+ if (enable == ap1302->streaming)
+ goto done;
+
+ if (enable) {
+ ret = ap1302_configure(ap1302);
+ if (ret < 0)
+ goto done;
+
+ ret = ap1302_stall(ap1302, false);
+ if (!ret)
+ ap1302->streaming = true;
+ } else {
+ ret = ap1302_stall(ap1302, true);
+ if (!ret)
+ ap1302->streaming = false;
+ }
+
+done:
+ mutex_unlock(&ap1302->lock);
+
+ if (ret < 0)
+ dev_err(ap1302->dev, "Failed to %s stream: %d\n",
+ enable ? "start" : "stop", ret);
+
+ return ret;
+}
+
+static const char * const ap1302_warnings[] = {
+ "HINF_BANDWIDTH",
+ "FLICKER_DETECTION",
+ "FACED_NE",
+ "SMILED_NE",
+ "HINF_OVERRUN",
+ NULL,
+ "FRAME_TOO_SMALL",
+ "MISSING_PHASES",
+ "SPOOF_UNDERRUN",
+ "JPEG_NOLAST",
+ "NO_IN_FREQ_SPEC",
+ "SINF0",
+ "SINF1",
+ "CAPTURE0",
+ "CAPTURE1",
+ "ISR_UNHANDLED",
+ "INTERLEAVE_SPOOF",
+ "INTERLEAVE_BUF",
+ "COORD_OUT_OF_RANGE",
+ "ICP_CLOCKING",
+ "SENSOR_CLOCKING",
+ "SENSOR_NO_IHDR",
+ "DIVIDE_BY_ZERO",
+ "INT0_UNDERRUN",
+ "INT1_UNDERRUN",
+ "SCRATCHPAD_TOO_BIG",
+ "OTP_RECORD_READ",
+ "NO_LSC_IN_OTP",
+ "GPIO_INT_LOST",
+ "NO_PDAF_DATA",
+ "FAR_PDAF_ACCESS_SKIP",
+ "PDAF_ERROR",
+ "ATM_TVI_BOUNDS",
+ "SIPM_0_RTY",
+ "SIPM_1_TRY",
+ "SIPM_0_NO_ACK",
+ "SIPM_1_NO_ACK",
+ "SMILE_DIS",
+ "DVS_DIS",
+ "TEST_DIS",
+ "SENSOR_LV2LV",
+ "SENSOR_FV2FV",
+ "FRAME_LOST",
+};
+
+static const char * const ap1302_lane_states[] = {
+ "stop_s",
+ "hs_req_s",
+ "lp_req_s",
+ "hs_s",
+ "lp_s",
+ "esc_req_s",
+ "turn_req_s",
+ "esc_s",
+ "esc_0",
+ "esc_1",
+ "turn_s",
+ "turn_mark",
+ "error_s",
+};
+
+#define NUM_LANES 4
+static void ap1302_log_lane_state(struct ap1302_sensor *sensor,
+ unsigned int index)
+{
+ static const char * const lp_states[] = {
+ "00", "10", "01", "11",
+ };
+ unsigned int counts[NUM_LANES][ARRAY_SIZE(ap1302_lane_states)];
+ unsigned int samples = 0;
+ unsigned int lane;
+ unsigned int i;
+ u32 first[NUM_LANES] = { 0, };
+ u32 last[NUM_LANES] = { 0, };
+ int ret;
+
+ memset(counts, 0, sizeof(counts));
+
+ for (i = 0; i < 1000; ++i) {
+ u32 values[NUM_LANES];
+
+ /*
+ * Read the state of all lanes and skip read errors and invalid
+ * values.
+ */
+ for (lane = 0; lane < NUM_LANES; ++lane) {
+ ret = ap1302_read(sensor->ap1302,
+ AP1302_ADV_SINF_MIPI_INTERNAL_p_LANE_n_STAT(index, lane),
+ &values[lane]);
+ if (ret < 0)
+ break;
+
+ if (AP1302_LANE_STATE(values[lane]) >=
+ ARRAY_SIZE(ap1302_lane_states)) {
+ ret = -EINVAL;
+ break;
+ }
+ }
+
+ if (ret < 0)
+ continue;
+
+ /* Accumulate the samples and save the first and last states. */
+ for (lane = 0; lane < NUM_LANES; ++lane)
+ counts[lane][AP1302_LANE_STATE(values[lane])]++;
+
+ if (!samples)
+ memcpy(first, values, sizeof(first));
+ memcpy(last, values, sizeof(last));
+
+ samples++;
+ }
+
+ if (!samples)
+ return;
+
+ /*
+ * Print the LP state from the first sample, the error state from the
+ * last sample, and the states accumulators for each lane.
+ */
+ for (lane = 0; lane < NUM_LANES; ++lane) {
+ u32 state = last[lane];
+ char error_msg[25] = "";
+
+ if (state & (AP1302_LANE_ERR | AP1302_LANE_ABORT)) {
+ unsigned int err = AP1302_LANE_ERR_STATE(state);
+ const char *err_state = NULL;
+
+ err_state = err < ARRAY_SIZE(ap1302_lane_states)
+ ? ap1302_lane_states[err] : "INVALID";
+
+ snprintf(error_msg, sizeof(error_msg), "ERR (%s%s) %s LP%s",
+ state & AP1302_LANE_ERR ? "E" : "",
+ state & AP1302_LANE_ABORT ? "A" : "",
+ err_state,
+ lp_states[AP1302_LANE_ERR_LP_VAL(state)]);
+ }
+
+ dev_info(sensor->ap1302->dev, "SINF%u L%u state: LP%s %s",
+ index, lane, lp_states[AP1302_LANE_LP_VAL(first[lane])],
+ error_msg);
+
+ for (i = 0; i < ARRAY_SIZE(ap1302_lane_states); ++i) {
+ if (counts[lane][i])
+ pr_cont(" %s:%u",
+ ap1302_lane_states[i],
+ counts[lane][i]);
+ }
+ pr_cont("\n");
+ }
+
+ /* Reset the error flags. */
+ for (lane = 0; lane < NUM_LANES; ++lane)
+ ap1302_write(sensor->ap1302,
+ AP1302_ADV_SINF_MIPI_INTERNAL_p_LANE_n_STAT(index, lane),
+ AP1302_LANE_ERR | AP1302_LANE_ABORT, NULL);
+}
+
+static int ap1302_log_status(struct v4l2_subdev *sd)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+ u16 frame_count_icp;
+ u16 frame_count_brac;
+ u16 frame_count_hinf;
+ u32 warning[4];
+ u32 error[3];
+ unsigned int i;
+ u32 value;
+ int ret;
+
+ /* Dump the console buffer. */
+ ret = ap1302_dump_console(ap1302);
+ if (ret < 0)
+ return ret;
+
+ /* Print errors. */
+ ret = ap1302_read(ap1302, AP1302_ERROR, &error[0]);
+ if (ret < 0)
+ return ret;
+
+ ret = ap1302_read(ap1302, AP1302_ERR_FILE, &error[1]);
+ if (ret < 0)
+ return ret;
+
+ ret = ap1302_read(ap1302, AP1302_ERR_LINE, &error[2]);
+ if (ret < 0)
+ return ret;
+
+ dev_info(ap1302->dev, "ERROR: 0x%04x (file 0x%08x:%u)\n",
+ error[0], error[1], error[2]);
+
+ ret = ap1302_read(ap1302, AP1302_SIPM_ERR_0, &error[0]);
+ if (ret < 0)
+ return ret;
+
+ ret = ap1302_read(ap1302, AP1302_SIPM_ERR_1, &error[1]);
+ if (ret < 0)
+ return ret;
+
+ dev_info(ap1302->dev, "SIPM_ERR [0] 0x%04x [1] 0x%04x\n",
+ error[0], error[1]);
+
+ /* Print warnings. */
+ for (i = 0; i < ARRAY_SIZE(warning); ++i) {
+ ret = ap1302_read(ap1302, AP1302_WARNING(i), &warning[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ dev_info(ap1302->dev,
+ "WARNING [0] 0x%04x [1] 0x%04x [2] 0x%04x [3] 0x%04x\n",
+ warning[0], warning[1], warning[2], warning[3]);
+
+ for (i = 0; i < ARRAY_SIZE(ap1302_warnings); ++i) {
+ if ((warning[i / 16] & BIT(i % 16)) &&
+ ap1302_warnings[i])
+ dev_info(ap1302->dev, "- WARN_%s\n",
+ ap1302_warnings[i]);
+ }
+
+ /* Print the frame counter. */
+ ret = ap1302_read(ap1302, AP1302_FRAME_CNT, &value);
+ if (ret < 0)
+ return ret;
+
+ frame_count_hinf = value >> 8;
+ frame_count_brac = value & 0xff;
+
+ ret = ap1302_read(ap1302, AP1302_ADV_CAPTURE_A_FV_CNT, &value);
+ if (ret < 0)
+ return ret;
+
+ frame_count_icp = value & 0xffff;
+
+ dev_info(ap1302->dev, "Frame counters: ICP %u, HINF %u, BRAC %u\n",
+ frame_count_icp, frame_count_hinf, frame_count_brac);
+
+ /* Sample the lane state. */
+ for (i = 0; i < ARRAY_SIZE(ap1302->sensors); ++i) {
+ struct ap1302_sensor *sensor = &ap1302->sensors[i];
+
+ if (!sensor->ap1302)
+ continue;
+
+ ap1302_log_lane_state(sensor, i);
+ }
+
+ return 0;
+}
+
+static int ap1302_subdev_registered(struct v4l2_subdev *sd)
+{
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(ap1302->sensors); ++i) {
+ struct ap1302_sensor *sensor = &ap1302->sensors[i];
+
+ if (!sensor->dev)
+ continue;
+
+ dev_dbg(ap1302->dev, "registering sensor %u\n", i);
+
+ ret = v4l2_device_register_subdev(sd->v4l2_dev, &sensor->sd);
+ if (ret)
+ return ret;
+
+ ret = media_create_pad_link(&sensor->sd.entity, 0,
+ &sd->entity, i,
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct media_entity_operations ap1302_media_ops = {
+ .link_validate = v4l2_subdev_link_validate
+};
+
+static const struct v4l2_subdev_pad_ops ap1302_pad_ops = {
+ .enum_mbus_code = ap1302_enum_mbus_code,
+ .enum_frame_size = ap1302_enum_frame_size,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = ap1302_set_fmt,
+ .get_selection = ap1302_get_selection,
+ .set_selection = ap1302_get_selection,
+ .get_frame_desc = ap1302_get_frame_desc,
+};
+
+static const struct v4l2_subdev_video_ops ap1302_video_ops = {
+ .s_stream = ap1302_s_stream,
+};
+
+static const struct v4l2_subdev_core_ops ap1302_core_ops = {
+ .log_status = ap1302_log_status,
+};
+
+static const struct v4l2_subdev_ops ap1302_subdev_ops = {
+ .core = &ap1302_core_ops,
+ .video = &ap1302_video_ops,
+ .pad = &ap1302_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops ap1302_subdev_internal_ops = {
+ .registered = ap1302_subdev_registered,
+ .init_state = ap1302_init_state,
+};
+
+/* -----------------------------------------------------------------------------
+ * Sensor
+ */
+
+static int ap1302_sensor_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct ap1302_sensor *sensor = to_ap1302_sensor(sd);
+ const struct ap1302_sensor_info *info = sensor->ap1302->sensor_info;
+
+ if (code->index)
+ return -EINVAL;
+
+ code->code = info->format;
+
+ return 0;
+}
+
+static int ap1302_sensor_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct ap1302_sensor *sensor = to_ap1302_sensor(sd);
+ const struct ap1302_sensor_info *info = sensor->ap1302->sensor_info;
+
+ if (fse->index)
+ return -EINVAL;
+
+ if (fse->code != info->format)
+ return -EINVAL;
+
+ fse->min_width = info->resolution.width;
+ fse->min_height = info->resolution.height;
+ fse->max_width = info->resolution.width;
+ fse->max_height = info->resolution.height;
+
+ return 0;
+}
+
+static int ap1302_sensor_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct ap1302_sensor *sensor = to_ap1302_sensor(sd);
+ const struct ap1302_sensor_info *info = sensor->ap1302->sensor_info;
+
+ memset(&fmt->format, 0, sizeof(fmt->format));
+
+ fmt->format.width = info->resolution.width;
+ fmt->format.height = info->resolution.height;
+ fmt->format.field = V4L2_FIELD_NONE;
+ fmt->format.code = info->format;
+ fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops ap1302_sensor_pad_ops = {
+ .enum_mbus_code = ap1302_sensor_enum_mbus_code,
+ .enum_frame_size = ap1302_sensor_enum_frame_size,
+ .get_fmt = ap1302_sensor_get_fmt,
+ .set_fmt = ap1302_sensor_get_fmt,
+};
+
+static const struct v4l2_subdev_ops ap1302_sensor_subdev_ops = {
+ .pad = &ap1302_sensor_pad_ops,
+};
+
+static int ap1302_sensor_parse_of(struct ap1302_device *ap1302,
+ struct device_node *node)
+{
+ struct ap1302_sensor *sensor;
+ u32 reg;
+ int ret;
+
+ /* Retrieve the sensor index from the reg property. */
+ ret = of_property_read_u32(node, "reg", ®);
+ if (ret < 0) {
+ dev_warn(ap1302->dev,
+ "'reg' property missing in sensor node\n");
+ return -EINVAL;
+ }
+
+ if (reg >= ARRAY_SIZE(ap1302->sensors)) {
+ dev_warn(ap1302->dev, "Out-of-bounds 'reg' value %u\n",
+ reg);
+ return -EINVAL;
+ }
+
+ sensor = &ap1302->sensors[reg];
+ if (sensor->ap1302) {
+ dev_warn(ap1302->dev, "Duplicate entry for sensor %u\n", reg);
+ return -EINVAL;
+ }
+
+ sensor->ap1302 = ap1302;
+ sensor->of_node = of_node_get(node);
+
+ return 0;
+}
+
+static void ap1302_sensor_dev_release(struct device *dev)
+{
+ of_node_put(dev->of_node);
+ kfree(dev);
+}
+
+static int ap1302_sensor_init(struct ap1302_sensor *sensor, unsigned int index)
+{
+ struct ap1302_device *ap1302 = sensor->ap1302;
+ struct v4l2_subdev *sd = &sensor->sd;
+ unsigned int i;
+ int ret;
+
+ sensor->index = index;
+
+ /*
+ * Register a device for the sensor, to support usage of the regulator
+ * API.
+ */
+ sensor->dev = kzalloc(sizeof(*sensor->dev), GFP_KERNEL);
+ if (!sensor->dev)
+ return -ENOMEM;
+
+ sensor->dev->parent = ap1302->dev;
+ sensor->dev->of_node = of_node_get(sensor->of_node);
+ sensor->dev->release = &ap1302_sensor_dev_release;
+ dev_set_name(sensor->dev, "%s-%s.%u", dev_name(ap1302->dev),
+ ap1302->sensor_info->name, index);
+
+ ret = device_register(sensor->dev);
+ if (ret < 0) {
+ dev_err(ap1302->dev,
+ "Failed to register device for sensor %u\n", index);
+ goto error;
+ }
+
+ /* Retrieve the power supplies for the sensor, if any. */
+ if (ap1302->sensor_info->supplies) {
+ const struct ap1302_sensor_supply *supplies =
+ ap1302->sensor_info->supplies;
+ unsigned int num_supplies;
+
+ for (num_supplies = 0; supplies[num_supplies].name;)
+ ++num_supplies;
+
+ sensor->supplies = devm_kcalloc(ap1302->dev, num_supplies,
+ sizeof(*sensor->supplies),
+ GFP_KERNEL);
+ if (!sensor->supplies) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ for (i = 0; i < num_supplies; ++i)
+ sensor->supplies[i].supply = supplies[i].name;
+
+ ret = regulator_bulk_get(sensor->dev, num_supplies,
+ sensor->supplies);
+ if (ret < 0) {
+ dev_err(ap1302->dev,
+ "Failed to get supplies for sensor %u\n",
+ index);
+ goto error;
+ }
+
+ sensor->num_supplies = i;
+ }
+
+ sd->dev = sensor->dev;
+ v4l2_subdev_init(sd, &ap1302_sensor_subdev_ops);
+
+ snprintf(sd->name, sizeof(sd->name), "%s %u",
+ ap1302->sensor_info->name, index);
+
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+ sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad);
+ if (ret < 0) {
+ dev_err(ap1302->dev,
+ "failed to initialize media entity for sensor %u: %d\n",
+ index, ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return ret;
+}
+
+static void ap1302_sensor_cleanup(struct ap1302_sensor *sensor)
+{
+ media_entity_cleanup(&sensor->sd.entity);
+
+ if (sensor->num_supplies)
+ regulator_bulk_free(sensor->num_supplies, sensor->supplies);
+
+ if (device_is_registered(sensor->dev))
+ device_unregister(sensor->dev);
+ else
+ put_device(sensor->dev);
+
+ of_node_put(sensor->of_node);
+}
+
+/* -----------------------------------------------------------------------------
+ * Boot & Firmware Handling
+ */
+
+static int ap1302_request_firmware(struct ap1302_device *ap1302)
+{
+ static const char * const suffixes[] = {
+ "",
+ "_single",
+ "_dual",
+ };
+
+ const struct ap1302_firmware_header *fw_hdr;
+ unsigned int num_sensors;
+ unsigned int fw_size;
+ unsigned int i;
+ char name[64];
+ int ret;
+
+ for (i = 0, num_sensors = 0; i < ARRAY_SIZE(ap1302->sensors); ++i) {
+ if (ap1302->sensors[i].dev)
+ num_sensors++;
+ }
+
+ ret = snprintf(name, sizeof(name), "ap1302_%s%s_fw.bin",
+ ap1302->sensor_info->name, suffixes[num_sensors]);
+ if (ret >= sizeof(name)) {
+ dev_err(ap1302->dev, "Firmware name too long\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(ap1302->dev, "Requesting firmware %s\n", name);
+
+ ret = request_firmware(&ap1302->fw, name, ap1302->dev);
+ if (ret) {
+ dev_err(ap1302->dev, "Failed to request firmware: %d\n", ret);
+ return ret;
+ }
+
+ if (ap1302->fw->size < sizeof(*fw_hdr)) {
+ dev_err(ap1302->dev, "Invalid firmware: too small\n");
+ return -EINVAL;
+ }
+
+ /*
+ * The firmware binary contains a header defined by the
+ * ap1302_firmware_header structure. The firmware itself (also referred
+ * to as bootdata) follows the header. Perform sanity checks to ensure
+ * the firmware is valid.
+ */
+ fw_hdr = (const struct ap1302_firmware_header *)ap1302->fw->data;
+ fw_size = ap1302->fw->size - sizeof(*fw_hdr);
+
+ if (fw_hdr->pll_init_size > fw_size) {
+ dev_err(ap1302->dev,
+ "Invalid firmware: PLL init size too large\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * ap1302_write_fw_window() - Write a piece of firmware to the AP1302
+ * @win_pos: Firmware load window current position
+ * @buf: Firmware data buffer
+ * @len: Firmware data length
+ *
+ * The firmware is loaded through a window in the registers space. Writes are
+ * sequential starting at address 0x8000, and must wrap around when reaching
+ * 0x9fff. This function write the firmware data stored in @buf to the AP1302,
+ * keeping track of the window position in the @win_pos argument.
+ */
+static int ap1302_write_fw_window(struct ap1302_device *ap1302, const u8 *buf,
+ u32 len, unsigned int *win_pos)
+{
+ while (len > 0) {
+ unsigned int write_addr;
+ unsigned int write_size;
+ int ret;
+
+ /*
+ * Write at most len bytes, from the current position to the
+ * end of the window.
+ */
+ write_addr = *win_pos + AP1302_FW_WINDOW_OFFSET;
+ write_size = min(len, AP1302_FW_WINDOW_SIZE - *win_pos);
+
+ ret = regmap_raw_write(ap1302->regmap16, write_addr, buf,
+ write_size);
+ if (ret)
+ return ret;
+
+ buf += write_size;
+ len -= write_size;
+
+ *win_pos += write_size;
+ if (*win_pos >= AP1302_FW_WINDOW_SIZE)
+ *win_pos = 0;
+ }
+
+ return 0;
+}
+
+static int ap1302_load_firmware(struct ap1302_device *ap1302)
+{
+ const struct ap1302_firmware_header *fw_hdr;
+ unsigned int fw_size;
+ const u8 *fw_data;
+ unsigned int win_pos = 0;
+ int ret;
+
+ fw_hdr = (const struct ap1302_firmware_header *)ap1302->fw->data;
+ fw_data = (u8 *)&fw_hdr[1];
+ fw_size = ap1302->fw->size - sizeof(*fw_hdr);
+
+ /* Clear the CRC register. */
+ ret = ap1302_write(ap1302, AP1302_SIP_CRC, 0xffff, NULL);
+ if (ret)
+ return ret;
+
+ /*
+ * Load the PLL initialization settings, set the bootdata stage to 2 to
+ * apply the basic_init_hp settings, and wait 1ms for the PLL to lock.
+ */
+ ret = ap1302_write_fw_window(ap1302, fw_data, fw_hdr->pll_init_size,
+ &win_pos);
+ if (ret)
+ return ret;
+
+ ret = ap1302_write(ap1302, AP1302_BOOTDATA_STAGE, 0x0002, NULL);
+ if (ret)
+ return ret;
+
+ usleep_range(1000, 2000);
+
+ /* Load the rest of the bootdata content and verify the CRC. */
+ ret = ap1302_write_fw_window(ap1302, fw_data + fw_hdr->pll_init_size,
+ fw_size - fw_hdr->pll_init_size, &win_pos);
+ if (ret)
+ return ret;
+
+ msleep(40);
+
+#if 0 // disable CRC check for tempory
+
+ ret = ap1302_read(ap1302, AP1302_SIP_CRC, &crc);
+ if (ret)
+ return ret;
+
+ if (crc != fw_hdr->crc) {
+ dev_warn(ap1302->dev,
+ "CRC mismatch: expected 0x%04x, got 0x%04x\n",
+ fw_hdr->crc, crc);
+ return -EAGAIN;
+ }
+#endif
+
+ /*
+ * Write 0xffff to the bootdata_stage register to indicate to the
+ * AP1302 that the whole bootdata content has been loaded.
+ */
+ ret = ap1302_write(ap1302, AP1302_BOOTDATA_STAGE, 0xffff, NULL);
+ if (ret)
+ return ret;
+
+ /* The AP1302 starts outputting frames right after boot, stop it. */
+ ret = ap1302_stall(ap1302, true);
+ if (!ret)
+ ap1302->streaming = false;
+
+ return ret;
+}
+
+static int ap1302_detect_chip(struct ap1302_device *ap1302)
+{
+ unsigned int version;
+ unsigned int revision;
+ int ret;
+
+ ret = ap1302_read(ap1302, AP1302_CHIP_VERSION, &version);
+ if (ret)
+ return ret;
+
+ ret = ap1302_read(ap1302, AP1302_CHIP_REV, &revision);
+ if (ret)
+ return ret;
+
+ if (version != AP1302_CHIP_ID) {
+ dev_err(ap1302->dev,
+ "Invalid chip version, expected 0x%04x, got 0x%04x\n",
+ AP1302_CHIP_ID, version);
+ return -EINVAL;
+ }
+
+ dev_info(ap1302->dev, "AP1302 revision %u.%u.%u detected\n",
+ (revision & 0xf000) >> 12, (revision & 0x0f00) >> 8,
+ revision & 0x00ff);
+
+ return 0;
+}
+
+static int ap1302_hw_init(struct ap1302_device *ap1302)
+{
+ unsigned int retries;
+ int ret;
+
+ /* Request and validate the firmware. */
+ ret = ap1302_request_firmware(ap1302);
+ if (ret)
+ return ret;
+
+ /*
+ * Power the sensors first, as the firmware will access them once it
+ * gets loaded.
+ */
+ ret = ap1302_power_on_sensors(ap1302);
+ if (ret < 0)
+ goto error_firmware;
+
+#define MAX_FW_LOAD_RETRIES 3
+ /*
+ * Load the firmware, retrying in case of CRC errors. The AP1302 is
+ * reset with a full power cycle between each attempt.
+ */
+ for (retries = 0; retries < MAX_FW_LOAD_RETRIES; ++retries) {
+ ret = ap1302_power_on(ap1302);
+ if (ret < 0)
+ goto error_power_sensors;
+
+ ret = ap1302_detect_chip(ap1302);
+ if (ret)
+ goto error_power;
+
+ ret = ap1302_load_firmware(ap1302);
+ if (!ret)
+ break;
+
+ if (ret != -EAGAIN)
+ goto error_power;
+
+ ap1302_power_off(ap1302);
+ }
+
+ if (retries == MAX_FW_LOAD_RETRIES) {
+ dev_err(ap1302->dev,
+ "Firmware load retries exceeded, aborting\n");
+ ret = -ETIMEDOUT;
+ goto error_power_sensors;
+ }
+
+ return 0;
+
+error_power:
+ ap1302_power_off(ap1302);
+error_power_sensors:
+ ap1302_power_off_sensors(ap1302);
+error_firmware:
+ release_firmware(ap1302->fw);
+
+ return ret;
+}
+
+static void ap1302_hw_cleanup(struct ap1302_device *ap1302)
+{
+ ap1302_power_off(ap1302);
+ ap1302_power_off_sensors(ap1302);
+}
+
+/* -----------------------------------------------------------------------------
+ * Probe & Remove
+ */
+
+static int ap1302_config_v4l2(struct ap1302_device *ap1302)
+{
+ struct v4l2_subdev *sd;
+ unsigned int i;
+ int ret;
+
+ sd = &ap1302->sd;
+ sd->dev = ap1302->dev;
+ v4l2_i2c_subdev_init(sd, ap1302->client, &ap1302_subdev_ops);
+
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
+ sd->internal_ops = &ap1302_subdev_internal_ops;
+ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP;
+ sd->entity.ops = &ap1302_media_ops;
+
+ for (i = 0; i < ARRAY_SIZE(ap1302->pads); ++i)
+ ap1302->pads[i].flags = i == AP1302_PAD_SOURCE
+ ? MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK;
+
+ ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(ap1302->pads),
+ ap1302->pads);
+ if (ret < 0) {
+ dev_err(ap1302->dev, "media_entity_init failed %d\n", ret);
+ return ret;
+ }
+
+ ret = v4l2_subdev_init_finalize(sd);
+ if (ret) {
+ dev_err(ap1302->dev, "subdev finalize init failure\n");
+ goto error_media;
+ }
+
+ ret = ap1302_ctrls_init(ap1302);
+ if (ret < 0)
+ goto error_media;
+
+ ret = v4l2_async_register_subdev(sd);
+ if (ret < 0) {
+ dev_err(ap1302->dev, "v4l2_async_register_subdev failed %d\n", ret);
+ goto error_ctrls;
+ }
+
+ return 0;
+
+error_ctrls:
+ ap1302_ctrls_cleanup(ap1302);
+error_media:
+ media_entity_cleanup(&sd->entity);
+ return ret;
+}
+
+static int ap1302_parse_of(struct ap1302_device *ap1302)
+{
+ struct device_node *sensors;
+ struct device_node *node;
+ struct fwnode_handle *ep;
+ unsigned int num_sensors = 0;
+ const char *model;
+ unsigned int i;
+ int ret;
+
+ /* Clock */
+ ap1302->clock = devm_clk_get(ap1302->dev, NULL);
+ if (IS_ERR(ap1302->clock)) {
+ dev_err(ap1302->dev, "Failed to get clock: %ld\n",
+ PTR_ERR(ap1302->clock));
+ return PTR_ERR(ap1302->clock);
+ }
+
+ /* GPIOs */
+ ap1302->reset_gpio = devm_gpiod_get(ap1302->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ap1302->reset_gpio)) {
+ dev_err(ap1302->dev, "Can't get reset GPIO: %ld\n",
+ PTR_ERR(ap1302->reset_gpio));
+ return PTR_ERR(ap1302->reset_gpio);
+ }
+
+ ap1302->standby_gpio = devm_gpiod_get_optional(ap1302->dev, "standby",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ap1302->standby_gpio)) {
+ dev_err(ap1302->dev, "Can't get standby GPIO: %ld\n",
+ PTR_ERR(ap1302->standby_gpio));
+ return PTR_ERR(ap1302->standby_gpio);
+ }
+
+ ap1302->isp_en_gpio = devm_gpiod_get_optional(ap1302->dev, "isp_en",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ap1302->isp_en_gpio)) {
+ dev_err(ap1302->dev, "Can't get ISP enable GPIO: %ld\n",
+ PTR_ERR(ap1302->isp_en_gpio));
+ return PTR_ERR(ap1302->isp_en_gpio);
+ }
+ gpiod_set_value_cansleep(ap1302->isp_en_gpio, 1);
+
+ // has issue?
+ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(ap1302->dev),
+ AP1302_PAD_SOURCE, 0,
+ FWNODE_GRAPH_ENDPOINT_NEXT);
+ if (!ep) {
+ dev_err(ap1302->dev, "no sink port found");
+ return -EINVAL;
+ }
+
+ ap1302->bus_cfg.bus_type = V4L2_MBUS_CSI2_DPHY;
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &ap1302->bus_cfg);
+ if (ret < 0) {
+ dev_err(ap1302->dev, "Failed to parse bus configuration\n");
+ return ret;
+ }
+
+ /* Sensors */
+ sensors = of_get_child_by_name(dev_of_node(ap1302->dev), "sensors");
+ if (!sensors) {
+ dev_err(ap1302->dev, "'sensors' child node not found\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_string(sensors, "onnn,model", &model);
+ if (ret < 0) {
+ /*
+ * If no sensor is connected, we can still support operation
+ * with the test pattern generator.
+ */
+ ap1302->sensor_info = &ap1302_sensor_info_tpg;
+ ap1302->width_factor = 1;
+ ret = 0;
+ goto done;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ap1302_sensor_info); ++i) {
+ const struct ap1302_sensor_info *info =
+ &ap1302_sensor_info[i];
+
+ if (!strcmp(info->model, model)) {
+ ap1302->sensor_info = info;
+ break;
+ }
+ }
+
+ if (!ap1302->sensor_info) {
+ dev_warn(ap1302->dev, "Unsupported sensor model %s\n", model);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ for_each_child_of_node(sensors, node) {
+ if (of_node_name_eq(node, "sensor")) {
+ if (!ap1302_sensor_parse_of(ap1302, node))
+ num_sensors++;
+ }
+ }
+
+ if (!num_sensors) {
+ dev_err(ap1302->dev, "No sensor found\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ap1302->width_factor = num_sensors;
+
+done:
+ of_node_put(sensors);
+ return ret;
+}
+
+static void ap1302_cleanup(struct ap1302_device *ap1302)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ap1302->sensors); ++i) {
+ struct ap1302_sensor *sensor = &ap1302->sensors[i];
+
+ if (!sensor->ap1302)
+ continue;
+
+ ap1302_sensor_cleanup(sensor);
+ }
+
+ v4l2_fwnode_endpoint_free(&ap1302->bus_cfg);
+
+ mutex_destroy(&ap1302->lock);
+}
+
+static int ap1302_probe(struct i2c_client *client)
+{
+ struct ap1302_device *ap1302;
+ unsigned int i;
+ int ret;
+
+ ap1302 = devm_kzalloc(&client->dev, sizeof(*ap1302), GFP_KERNEL);
+ if (!ap1302)
+ return -ENOMEM;
+
+ ap1302->dev = &client->dev;
+ ap1302->client = client;
+
+ mutex_init(&ap1302->lock);
+
+ ap1302->regmap16 = devm_regmap_init_i2c(client, &ap1302_reg16_config);
+ if (IS_ERR(ap1302->regmap16)) {
+ dev_err(ap1302->dev, "regmap16 init failed: %ld\n",
+ PTR_ERR(ap1302->regmap16));
+ ret = -ENODEV;
+ goto error;
+ }
+
+ ap1302->regmap32 = devm_regmap_init_i2c(client, &ap1302_reg32_config);
+ if (IS_ERR(ap1302->regmap32)) {
+ dev_err(ap1302->dev, "regmap32 init failed: %ld\n",
+ PTR_ERR(ap1302->regmap32));
+ ret = -ENODEV;
+ goto error;
+ }
+
+ ret = ap1302_parse_of(ap1302);
+ if (ret < 0)
+ goto error;
+
+ for (i = 0; i < ARRAY_SIZE(ap1302->sensors); ++i) {
+ struct ap1302_sensor *sensor = &ap1302->sensors[i];
+
+ if (!sensor->ap1302)
+ continue;
+
+ ret = ap1302_sensor_init(sensor, i);
+ if (ret < 0)
+ goto error;
+ }
+
+ for (i = 0; i < AP1302_NUM_SUPPLIES; ++i)
+ ap1302->supplies[i].supply = ap1302_supplies[i].name;
+
+ ret = devm_regulator_bulk_get(&client->dev, AP1302_NUM_SUPPLIES,
+ ap1302->supplies);
+ if (ret < 0)
+ return ret;
+
+ ret = ap1302_hw_init(ap1302);
+ if (ret)
+ goto error;
+
+ ap1302_debugfs_init(ap1302);
+
+ ret = ap1302_config_v4l2(ap1302);
+ if (ret)
+ goto error_hw_cleanup;
+
+ dev_dbg(ap1302->dev, "%d: successfully\n", __LINE__);
+ return 0;
+
+error_hw_cleanup:
+ ap1302_hw_cleanup(ap1302);
+error:
+ ap1302_cleanup(ap1302);
+ return ret;
+}
+
+static void ap1302_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct ap1302_device *ap1302 = to_ap1302(sd);
+
+ ap1302_debugfs_cleanup(ap1302);
+
+ ap1302_hw_cleanup(ap1302);
+
+ release_firmware(ap1302->fw);
+
+ v4l2_async_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+
+ ap1302_ctrls_cleanup(ap1302);
+
+ ap1302_cleanup(ap1302);
+}
+
+static const struct of_device_id ap1302_of_id_table[] = {
+ { .compatible = "onnn,ap1302" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ap1302_of_id_table);
+
+static struct i2c_driver ap1302_i2c_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = ap1302_of_id_table,
+ },
+ .probe = ap1302_probe,
+ .remove = ap1302_remove,
+};
+
+module_i2c_driver(ap1302_i2c_driver);
+
+MODULE_AUTHOR("Florian Rebaudo <frebaudo@witekio.com>");
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_AUTHOR("Anil Kumar M <anil.mamidala@xilinx.com>");
+
+MODULE_DESCRIPTION("ON Semiconductor AP1302 ISP driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 3/7] pmdomain: imx93-blk-ctrl: populate child devices
2025-07-01 22:06 ` [PATCH 3/7] pmdomain: imx93-blk-ctrl: populate child devices Frank Li
@ 2025-07-02 5:15 ` Alexander Stein
0 siblings, 0 replies; 20+ messages in thread
From: Alexander Stein @ 2025-07-02 5:15 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel,
Frank Li
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li
Hi,
thanks for the patch.
Am Mittwoch, 2. Juli 2025, 00:06:08 CEST schrieb Frank Li:
> imx93-blk-ctrl is miscellaneous devices, which include reset, clock, MIPI
> CSI2 PHY and DSI's miscellaneous logic. Call of_platform_populate() to
> probe child nodes.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> drivers/pmdomain/imx/imx93-blk-ctrl.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/pmdomain/imx/imx93-blk-ctrl.c b/drivers/pmdomain/imx/imx93-blk-ctrl.c
> index 0e2ba8ec55d75..ba2dd8bd143d1 100644
> --- a/drivers/pmdomain/imx/imx93-blk-ctrl.c
> +++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c
> @@ -7,6 +7,7 @@
> #include <linux/device.h>
> #include <linux/module.h>
> #include <linux/of.h>
> +#include <linux/of_platform.h>
> #include <linux/platform_device.h>
> #include <linux/pm_domain.h>
> #include <linux/pm_runtime.h>
> @@ -297,7 +298,7 @@ static int imx93_blk_ctrl_probe(struct platform_device *pdev)
>
> dev_set_drvdata(dev, bc);
>
> - return 0;
> + return of_platform_populate(dev->of_node, NULL, NULL, dev);
Please use devm_ same as in [1], which you Sob'ed as well.
[1] https://lore.kernel.org/all/20250304154929.1785200-4-alexander.stein@ew.tq-group.com/
>
> cleanup_pds:
> for (i--; i >= 0; i--)
>
>
--
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
http://www.tq-group.com/
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 4/7] phy: freescale: add imx93 MIPI CSI2 DPHY support
2025-07-01 22:06 ` [PATCH 4/7] phy: freescale: add imx93 MIPI CSI2 DPHY support Frank Li
@ 2025-07-02 5:50 ` Alexander Stein
0 siblings, 0 replies; 20+ messages in thread
From: Alexander Stein @ 2025-07-02 5:50 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel,
Frank Li
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li, Guoniu.zhou, Jindong Yue
Hi,
thanks for the patch.
Am Mittwoch, 2. Juli 2025, 00:06:09 CEST schrieb Frank Li:
> Add driver i.MX93 MIPI DPHY controller, which is wrapper for Synosys MIPI
> CSI2 DPHY module.
>
> Base on
> https://github.com/nxp-imx/linux-imx/blob/lf-6.12.y/drivers/phy/freescale/phy-fsl-imx9-dphy-rx.c
>
> Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
> Signed-off-by: Jindong Yue <jindong.yue@nxp.com>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> drivers/phy/freescale/Kconfig | 10 +
> drivers/phy/freescale/Makefile | 1 +
> drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c | 306 ++++++++++++++++++++++++++
> 3 files changed, 317 insertions(+)
>
> diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
> index 81f53564ee156..cb34e151e86c4 100644
> --- a/drivers/phy/freescale/Kconfig
> +++ b/drivers/phy/freescale/Kconfig
> @@ -44,6 +44,16 @@ config PHY_FSL_IMX8QM_HSIO
> Enable this to add support for the HSIO PHY as found on
> i.MX8QM family of SOCs.
>
> +config PHY_FSL_IMX93_DPHY_RX
> + tristate "Freescale i.MX9 DPHY Rx"
> + depends on OF && HAS_IOMEM
> + select GENERIC_PHY
> + select GENERIC_PHY_MIPI_DPHY
> + select REGMAP_MMIO
> + help
> + Enable this to add support for the Synopsys DW DPHY Rx as found
> + on NXP's i.MX9 family.
> +
> config PHY_FSL_SAMSUNG_HDMI_PHY
> tristate "Samsung HDMI PHY support"
> depends on OF && HAS_IOMEM && COMMON_CLK
> diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
> index 658eac7d0a622..8e122a07695f0 100644
> --- a/drivers/phy/freescale/Makefile
> +++ b/drivers/phy/freescale/Makefile
> @@ -4,5 +4,6 @@ obj-$(CONFIG_PHY_MIXEL_LVDS_PHY) += phy-fsl-imx8qm-lvds-phy.o
> obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o
> obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) += phy-fsl-imx8m-pcie.o
> obj-$(CONFIG_PHY_FSL_IMX8QM_HSIO) += phy-fsl-imx8qm-hsio.o
> +obj-$(CONFIG_PHY_FSL_IMX93_DPHY_RX) += phy-fsl-imx93-dphy-rx.o
> obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o
> obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) += phy-fsl-samsung-hdmi.o
> diff --git a/drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c b/drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c
> new file mode 100644
> index 0000000000000..f5155ae68c50f
> --- /dev/null
> +++ b/drivers/phy/freescale/phy-fsl-imx93-dphy-rx.c
> @@ -0,0 +1,306 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2025 NXP
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/phy/phy.h>
> +#include <linux/phy/phy-mipi-dphy.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#define IMX93_BLK_CSI 0x48
> +#define IMX93_BLK_CSI_CFGCLKFREQRANGE GENMASK(5, 0)
> +#define IMX93_BLK_CSI_HSFREQRANGE GENMASK(14, 8)
> +
> +struct fsl_csi2_phy_drv_data {
> + u32 max_lanes;
> + u32 max_data_rate; /* Mbps */
> +};
> +
> +struct fsl_csi2_phy {
> + struct device *dev;
> + struct regmap *dphy_regmap;
> + struct clk *cfg_clk;
> +
> + const struct fsl_csi2_phy_drv_data *drv_data;
> +
> + u16 hsfreqrange;
> + u16 cfgclkfreqrange;
> + u16 ddlfreq;
> +};
> +
> +struct dphy_mbps_hsfreqrange_map {
> + u16 mbps;
> + u16 hsfreqrange;
> + u16 ddlfreq;
> +};
> +
> +/*
> + * Data rate to high speed frequency range map table
> + */
> +static const struct dphy_mbps_hsfreqrange_map hsfreqrange_table[] = {
> + { .mbps = 80, .hsfreqrange = 0x00, .ddlfreq = 489 },
> + { .mbps = 90, .hsfreqrange = 0x10, .ddlfreq = 489 },
> + { .mbps = 100, .hsfreqrange = 0x20, .ddlfreq = 489 },
> + { .mbps = 110, .hsfreqrange = 0x30, .ddlfreq = 489 },
> + { .mbps = 120, .hsfreqrange = 0x01, .ddlfreq = 489 },
> + { .mbps = 130, .hsfreqrange = 0x11, .ddlfreq = 489 },
> + { .mbps = 140, .hsfreqrange = 0x21, .ddlfreq = 489 },
> + { .mbps = 150, .hsfreqrange = 0x31, .ddlfreq = 489 },
> + { .mbps = 160, .hsfreqrange = 0x02, .ddlfreq = 489 },
> + { .mbps = 170, .hsfreqrange = 0x12, .ddlfreq = 489 },
> + { .mbps = 180, .hsfreqrange = 0x22, .ddlfreq = 489 },
> + { .mbps = 190, .hsfreqrange = 0x32, .ddlfreq = 489 },
> + { .mbps = 205, .hsfreqrange = 0x03, .ddlfreq = 489 },
> + { .mbps = 220, .hsfreqrange = 0x13, .ddlfreq = 489 },
> + { .mbps = 235, .hsfreqrange = 0x23, .ddlfreq = 489 },
> + { .mbps = 250, .hsfreqrange = 0x33, .ddlfreq = 489 },
> + { .mbps = 275, .hsfreqrange = 0x04, .ddlfreq = 489 },
> + { .mbps = 300, .hsfreqrange = 0x14, .ddlfreq = 489 },
> + { .mbps = 325, .hsfreqrange = 0x25, .ddlfreq = 489 },
> + { .mbps = 350, .hsfreqrange = 0x35, .ddlfreq = 489 },
> + { .mbps = 400, .hsfreqrange = 0x05, .ddlfreq = 489 },
> + { .mbps = 450, .hsfreqrange = 0x16, .ddlfreq = 489 },
> + { .mbps = 500, .hsfreqrange = 0x26, .ddlfreq = 489 },
> + { .mbps = 550, .hsfreqrange = 0x37, .ddlfreq = 489 },
> + { .mbps = 600, .hsfreqrange = 0x07, .ddlfreq = 489 },
> + { .mbps = 650, .hsfreqrange = 0x18, .ddlfreq = 489 },
> + { .mbps = 700, .hsfreqrange = 0x28, .ddlfreq = 489 },
> + { .mbps = 750, .hsfreqrange = 0x39, .ddlfreq = 489 },
> + { .mbps = 800, .hsfreqrange = 0x09, .ddlfreq = 489 },
> + { .mbps = 850, .hsfreqrange = 0x19, .ddlfreq = 489 },
> + { .mbps = 900, .hsfreqrange = 0x29, .ddlfreq = 489 },
> + { .mbps = 950, .hsfreqrange = 0x3a, .ddlfreq = 489 },
> + { .mbps = 1000, .hsfreqrange = 0x0a, .ddlfreq = 489 },
> + { .mbps = 1050, .hsfreqrange = 0x1a, .ddlfreq = 489 },
> + { .mbps = 1100, .hsfreqrange = 0x2a, .ddlfreq = 489 },
> + { .mbps = 1150, .hsfreqrange = 0x3b, .ddlfreq = 489 },
> + { .mbps = 1200, .hsfreqrange = 0x0b, .ddlfreq = 489 },
> + { .mbps = 1250, .hsfreqrange = 0x1b, .ddlfreq = 489 },
> + { .mbps = 1300, .hsfreqrange = 0x2b, .ddlfreq = 489 },
> + { .mbps = 1350, .hsfreqrange = 0x3c, .ddlfreq = 489 },
> + { .mbps = 1400, .hsfreqrange = 0x0c, .ddlfreq = 489 },
> + { .mbps = 1450, .hsfreqrange = 0x1c, .ddlfreq = 489 },
> + { .mbps = 1500, .hsfreqrange = 0x2c, .ddlfreq = 489 },
> + { .mbps = 1550, .hsfreqrange = 0x3d, .ddlfreq = 303 },
> + { .mbps = 1600, .hsfreqrange = 0x0d, .ddlfreq = 313 },
> + { .mbps = 1650, .hsfreqrange = 0x1d, .ddlfreq = 323 },
> + { .mbps = 1700, .hsfreqrange = 0x2e, .ddlfreq = 333 },
> + { .mbps = 1750, .hsfreqrange = 0x3e, .ddlfreq = 342 },
> + { .mbps = 1800, .hsfreqrange = 0x0e, .ddlfreq = 352 },
> + { .mbps = 1850, .hsfreqrange = 0x1e, .ddlfreq = 362 },
> + { .mbps = 1900, .hsfreqrange = 0x1f, .ddlfreq = 372 },
> + { .mbps = 1950, .hsfreqrange = 0x3f, .ddlfreq = 381 },
> + { .mbps = 2000, .hsfreqrange = 0x0f, .ddlfreq = 391 },
> + { .mbps = 2050, .hsfreqrange = 0x40, .ddlfreq = 401 },
> + { .mbps = 2100, .hsfreqrange = 0x41, .ddlfreq = 411 },
> + { .mbps = 2150, .hsfreqrange = 0x42, .ddlfreq = 411 },
> + { .mbps = 2200, .hsfreqrange = 0x43, .ddlfreq = 411 },
> + { .mbps = 2250, .hsfreqrange = 0x44, .ddlfreq = 411 },
> + { .mbps = 2300, .hsfreqrange = 0x45, .ddlfreq = 411 },
> + { .mbps = 2350, .hsfreqrange = 0x46, .ddlfreq = 411 },
> + { .mbps = 2400, .hsfreqrange = 0x47, .ddlfreq = 411 },
> + { .mbps = 2450, .hsfreqrange = 0x48, .ddlfreq = 411 },
> + { .mbps = 2500, .hsfreqrange = 0x49, .ddlfreq = 411 },
> + { /* sentinel */ },
> +};
> +
> +static int fsl_csi2_phy_init(struct phy *phy)
> +{
> + struct fsl_csi2_phy *priv = phy_get_drvdata(phy);
> +
> + return pm_runtime_get_sync(priv->dev);
> +}
> +
> +static int fsl_csi2_phy_exit(struct phy *phy)
> +{
> + struct fsl_csi2_phy *priv = phy_get_drvdata(phy);
> +
> + return pm_runtime_put(priv->dev);
> +}
> +
> +static int fsl_csi2_phy_power_on(struct phy *phy)
> +{
> + struct fsl_csi2_phy *priv = phy_get_drvdata(phy);
> +
> + regmap_update_bits(priv->dphy_regmap, IMX93_BLK_CSI,
> + IMX93_BLK_CSI_CFGCLKFREQRANGE,
> + FIELD_PREP(IMX93_BLK_CSI_CFGCLKFREQRANGE, priv->cfgclkfreqrange));
> +
> + regmap_update_bits(priv->dphy_regmap, IMX93_BLK_CSI,
> + IMX93_BLK_CSI_HSFREQRANGE,
> + FIELD_PREP(IMX93_BLK_CSI_HSFREQRANGE, priv->hsfreqrange));
> +
> + return 0;
> +}
> +
> +static int set_freqrange_by_mpbs(struct fsl_csi2_phy *priv, u64 mbps)
> +{
> + const struct dphy_mbps_hsfreqrange_map *prev_value = NULL;
> + const struct dphy_mbps_hsfreqrange_map *value;
> +
> + for (value = hsfreqrange_table; value->mbps; value++) {
> + if (value->mbps >= mbps)
> + break;
> + prev_value = value;
> + }
> +
> + if (prev_value &&
> + ((mbps - prev_value->mbps) <= (value->mbps - mbps)))
> + value = prev_value;
> +
> + if (!value->mbps) {
> + pr_err("Unsupported PHY speed (%llu Mbps)", mbps);
> + return -ERANGE;
> + }
> +
> + priv->hsfreqrange = value->hsfreqrange;
> + priv->ddlfreq = value->ddlfreq;
I'm wondering if it's worth storing a pointer to the table entry instead.
> +
> + return 0;
> +}
> +
> +static int fsl_csi2_phy_configure(struct phy *phy, union phy_configure_opts *opts)
> +{
> + struct fsl_csi2_phy *priv = phy_get_drvdata(phy);
> + const struct fsl_csi2_phy_drv_data *drv_data = priv->drv_data;
> + struct phy_configure_opts_mipi_dphy *config = &opts->mipi_dphy;
> + struct device *dev = priv->dev;
> + u64 data_rate_mbps;
> + int ret;
> +
> + if (config->lanes > drv_data->max_lanes) {
> + dev_err(dev, "The number of lanes has exceeded the maximum value\n");
> + return -EINVAL;
> + }
> +
> + data_rate_mbps = div_u64(config->hs_clk_rate, 1000 * 1000);
> + if (data_rate_mbps < 80 ||
> + data_rate_mbps > drv_data->max_data_rate) {
> + dev_err(dev, "Out-of-bound lane rate %llu\n", data_rate_mbps);
> + return -EINVAL;
> + }
> +
> + dev_dbg(dev, "Number of lanes: %d, data rate=%llu(Mbps)\n",
> + config->lanes, data_rate_mbps);
> +
> + ret = set_freqrange_by_mpbs(priv, data_rate_mbps);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static const struct phy_ops fsl_csi2_phy_ops = {
> + .init = fsl_csi2_phy_init,
> + .exit = fsl_csi2_phy_exit,
> + .power_on = fsl_csi2_phy_power_on,
> + .configure = fsl_csi2_phy_configure,
> + .owner = THIS_MODULE,
> +};
> +
> +static const struct fsl_csi2_phy_drv_data imx93_dphy_drvdata = {
> + .max_lanes = 2,
> + .max_data_rate = 1500,
> +};
> +
> +static int fsl_csi2_runtime_suspend(struct device *dev)
> +{
> + struct fsl_csi2_phy *priv = dev_get_drvdata(dev);
> +
> + clk_disable_unprepare(priv->cfg_clk);
> +
> + return 0;
> +}
> +
> +static int fsl_csi2_runtime_resume(struct device *dev)
> +{
> + struct fsl_csi2_phy *priv = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = clk_prepare_enable(priv->cfg_clk);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static DEFINE_RUNTIME_DEV_PM_OPS(fsl_csi2_pm_ops, fsl_csi2_runtime_suspend,
> + fsl_csi2_runtime_resume, NULL);
> +
> +static const struct of_device_id fsl_csi2_phy_of_match[] = {
> + { .compatible = "fsl,imx93-dphy-rx", .data = &imx93_dphy_drvdata},
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, fsl_csi2_phy_of_match);
> +
> +static int fsl_csi2_phy_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + struct phy_provider *phy_provider;
> + struct fsl_csi2_phy *priv;
> + unsigned long cfg_rate;
> + struct phy *phy;
> +
> + if (!dev->parent || !dev->parent->of_node)
> + return -ENODEV;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->dev = dev;
> + priv->drv_data = of_device_get_match_data(dev);
> +
> + platform_set_drvdata(pdev, priv);
> +
> + priv->dphy_regmap = syscon_node_to_regmap(dev->parent->of_node);
> + if (IS_ERR(priv->dphy_regmap))
> + dev_err_probe(dev, -ENODEV, "Failed to DPHY regmap\n");
> +
> + priv->cfg_clk = devm_clk_get(dev, "cfg");
> + if (IS_ERR(priv->cfg_clk))
> + dev_err_probe(dev, PTR_ERR(priv->cfg_clk), "Failed to get DPHY config clock\n");
> +
> + /* cfgclkfreqrange[5:0] = round[(cfg_clk(MHz) - 17) * 4] */
Please move this comment directly above the calculation below.
Best regards,
Alexander
> + cfg_rate = clk_get_rate(priv->cfg_clk);
> + if (!cfg_rate)
> + dev_err_probe(dev, -EINVAL, "Failed to get PHY config clock rate\n");
> +
> + priv->cfgclkfreqrange = (div_u64(cfg_rate, 1000 * 1000) - 17) * 4;
> +
> + phy = devm_phy_create(dev, np, &fsl_csi2_phy_ops);
> + if (IS_ERR(phy))
> + return dev_err_probe(dev, -ENODEV, "Failed to create PHY\n");
> +
> + phy_set_drvdata(phy, priv);
> +
> + pm_runtime_set_suspended(dev);
> + devm_pm_runtime_enable(dev);
> +
> + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
> +
> + return PTR_ERR_OR_ZERO(phy_provider);
> +}
> +
> +static struct platform_driver fsl_csi2_phy_driver = {
> + .probe = fsl_csi2_phy_probe,
> + .driver = {
> + .name = "imx-mipi-dphy-rx",
> + .pm = pm_ptr(&fsl_csi2_pm_ops),
> + .of_match_table = fsl_csi2_phy_of_match,
> + }
> +};
> +module_platform_driver(fsl_csi2_phy_driver);
> +
> +MODULE_DESCRIPTION("i.MX9 Synopsys DesignWare MIPI DPHY Rx wrapper driver");
> +MODULE_AUTHOR("NXP Semiconductor");
> +MODULE_LICENSE("GPL");
>
>
--
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
http://www.tq-group.com/
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver
2025-07-01 22:06 ` [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver Frank Li
@ 2025-07-02 6:04 ` Alexander Stein
2025-07-02 6:35 ` Krzysztof Kozlowski
2025-07-02 9:38 ` Laurent Pinchart
2 siblings, 0 replies; 20+ messages in thread
From: Alexander Stein @ 2025-07-02 6:04 UTC (permalink / raw)
To: Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel,
Frank Li
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Frank Li, Guoniu.zhou
Hi,
thanks for the patch.
Am Mittwoch, 2. Juli 2025, 00:06:10 CEST schrieb Frank Li:
> From: "Guoniu.zhou" <guoniu.zhou@nxp.com>
>
> Add V4L2 subdev driver for DesignWare MIPI CSI2 controller.
>
> Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> MAINTAINERS | 1 +
> drivers/media/platform/nxp/Kconfig | 11 +
> drivers/media/platform/nxp/Makefile | 1 +
> drivers/media/platform/nxp/dwc-mipi-csi2.c | 1675 ++++++++++++++++++++++++++++
> 4 files changed, 1688 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e208c91f1405f..f67db23cb9d6f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15022,6 +15022,7 @@ F: Documentation/devicetree/bindings/media/nxp,imx-mipi-csi2.yaml
> F: Documentation/devicetree/bindings/media/nxp,imx7-csi.yaml
> F: Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml
> F: Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml
> +F: drivers/media/platform/nxp/dwc-mipi-csi2.c
> F: drivers/media/platform/nxp/imx-mipi-csis.c
> F: drivers/media/platform/nxp/imx-parallel-csi.c
> F: drivers/media/platform/nxp/imx7-media-csi.c
> diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig
> index 5df6f97d16f29..b7a4aed706443 100644
> --- a/drivers/media/platform/nxp/Kconfig
> +++ b/drivers/media/platform/nxp/Kconfig
> @@ -4,6 +4,17 @@
>
> comment "NXP media platform drivers"
>
> +config VIDEO_DWC_MIPI_CSIS
> + tristate "DesignWare Cores MIPI CSI-2 receiver found on i.MX93"
> + depends on ARCH_MXC || COMPILE_TEST
> + depends on VIDEO_DEV
> + select MEDIA_CONTROLLER
> + select V4L2_FWNODE
> + select VIDEO_V4L2_SUBDEV_API
> + help
> + Video4Linux2 sub-device driver for the DesignWare Cores MIPI
> + CSI-2 receiver used on i.MX93.
> +
> config VIDEO_IMX7_CSI
> tristate "NXP CSI Bridge driver"
> depends on ARCH_MXC || COMPILE_TEST
> diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile
> index 9a9b2a1d6886c..a0a025c169aca 100644
> --- a/drivers/media/platform/nxp/Makefile
> +++ b/drivers/media/platform/nxp/Makefile
> @@ -4,6 +4,7 @@ obj-y += dw100/
> obj-y += imx-jpeg/
> obj-y += imx8-isi/
>
> +obj-$(CONFIG_VIDEO_DWC_MIPI_CSIS) += dwc-mipi-csi2.o
> obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
> obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o
> obj-$(CONFIG_VIDEO_IMX_PARALLEL_CSI) += imx-parallel-csi.o
> diff --git a/drivers/media/platform/nxp/dwc-mipi-csi2.c b/drivers/media/platform/nxp/dwc-mipi-csi2.c
> new file mode 100644
> index 0000000000000..cd57c06b95848
> --- /dev/null
> +++ b/drivers/media/platform/nxp/dwc-mipi-csi2.c
> @@ -0,0 +1,1675 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2025 NXP
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/errno.h>
> +#include <linux/iopoll.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* MIPI CSI-2 Host Controller Registers Define */
> +
> +/* Core Version */
> +#define CSI2RX_VERSION 0x0
> +
> +/* Number of Lanes */
> +#define CSI2RX_N_LANES 0x4
> +#define CSI2RX_N_LANES_N_LANES(x) FIELD_PREP(GENMASK(2, 0), (x) - 1)
> +
> +/* Logic Reset */
> +#define CSI2RX_HOST_RESETN 0x8
> +#define CSI2RX_HOST_RESETN_ENABLE BIT(0)
> +
> +/* Main Interrupt Status */
> +#define CSI2RX_INT_ST_MAIN 0xc
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY BIT(0)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT BIT(1)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL BIT(2)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME BIT(3)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME BIT(4)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC BIT(5)
> +#define CSI2RX_INT_ST_MAIN_ERR_DID BIT(6)
> +#define CSI2RX_INT_ST_MAIN_ERR_ECC BIT(7)
> +#define CSI2RX_INT_ST_MAIN_ERR_PHY BIT(16)
> +#define CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI BIT(18)
> +
> +/* Data monitor */
> +#define CSI2RX_DATA_IDS_1_DT 0x10
> +#define CSI2RX_DATA_IDS_1_DT_DATA_ID0(x) FIELD_PREP(GENMASK(5, 0), (x))
> +#define CSI2RX_DATA_IDS_1_DT_DATA_ID1(x) FIELD_PREP(GENMASK(13, 8), (x))
> +#define CSI2RX_DATA_IDS_1_DT_DATA_ID2(x) FIELD_PREP(GENMASK(21, 16), (x))
> +#define CSI2RX_DATA_IDS_1_DT_DATA_ID3(x) FIELD_PREP(GENMASK(29, 24), (x))
> +
> +#define CSI2RX_DATA_IDS_2_DT 0x14
> +#define CSI2RX_DATA_IDS_2_DT_DATA_ID4(x) FIELD_PREP(GENMASK(5, 0), (x))
> +#define CSI2RX_DATA_IDS_2_DT_DATA_ID5(x) FIELD_PREP(GENMASK(13, 8), (x))
> +#define CSI2RX_DATA_IDS_2_DT_DATA_ID6(x) FIELD_PREP(GENMASK(21, 16), (x))
> +#define CSI2RX_DATA_IDS_2_DT_DATA_ID7(x) FIELD_PREP(GENMASK(29, 24), (x))
> +
> +#define CSI2RX_DATA_IDS_1_VC 0x30
> +#define CSI2RX_DATA_IDS_1_VC_DATA_ID0(x) FIELD_PREP(GENMASK(3, 0), (x))
> +#define CSI2RX_DATA_IDS_1_VC_DATA_ID1(x) FIELD_PREP(GENMASK(11, 8), (x))
> +#define CSI2RX_DATA_IDS_1_VC_DATA_ID2(x) FIELD_PREP(GENMASK(19, 16), (x))
> +#define CSI2RX_DATA_IDS_1_VC_DATA_ID3(x) FIELD_PREP(GENMASK(27, 24), (x))
> +
> +#define CSI2RX_DATA_IDS_2_VC 0x34
> +#define CSI2RX_DATA_IDS_2_VC_DATA_ID4(x) FIELD_PREP(GENMASK(3, 0), (x))
> +#define CSI2RX_DATA_IDS_2_VC_DATA_ID5(x) FIELD_PREP(GENMASK(11, 8), (x))
> +#define CSI2RX_DATA_IDS_2_VC_DATA_ID6(x) FIELD_PREP(GENMASK(19, 16), (x))
> +#define CSI2RX_DATA_IDS_2_VC_DATA_ID7(x) FIELD_PREP(GENMASK(27, 24), (x))
> +
> +/* PHY Shutdown */
> +#define CSI2RX_DPHY_SHUTDOWNZ 0x40
> +#define CSI2RX_DPHY_SHUTDOWNZ_ENABLE BIT(0)
> +
> +/* DPHY Reset */
> +#define CSI2RX_DPHY_RSTZ 0x44
> +#define CSI2RX_DPHY_RSTZ_ENABLE BIT(0)
> +
> +/* RX PHY Status */
> +#define CSI2RX_DPHY_RX_STATUS 0x48
> +#define CSI2RX_DPHY_RX_STATUS_DATA_LANE0_ULP BIT(0)
> +#define CSI2RX_DPHY_RX_STATUS_DATA_LANE1_ULP BIT(1)
> +#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_ULP BIT(16)
> +#define CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS BIT(17)
> +
> +/* STOP STATE PHY Status */
> +#define CSI2RX_DPHY_STOPSTATE 0x4c
> +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE0 BIT(0)
> +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE1 BIT(1)
> +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE2 BIT(2)
> +#define CSI2RX_DPHY_STOPSTATE_DATA_LANE3 BIT(3)
> +#define CSI2RX_DPHY_STOPSTATE_CLK_LANE BIT(16)
> +
> +/* DPHY Test and Control Interface 1 */
> +#define CSI2RX_DPHY_TEST_CTRL0 0x50
> +#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLR BIT(0)
> +#define CSI2RX_DPHY_TEST_CTRL0_TEST_CLKEN BIT(1)
> +
> +/* DPHY Test and Control Interface 2 */
> +#define CSI2RX_DPHY_TEST_CTRL1 0x54
> +#define CSI2RX_DPHY_TEST_CTRL1_TEST_DIN(x) FIELD_PREP(GENMASK(7, 0), (x))
> +#define CSI2RX_DPHY_TEST_CTRL1_TEST_DOUT(x) FIELD_GET(GENMASK(15, 8), (x))
> +#define CSI2RX_DPHY_TEST_CTRL1_TEST_EN BIT(16)
> +
> +/* Pattern Generator vertical Resolution */
> +#define CSI2RX_PPI_PG_PATTERN_VRES 0x60
> +#define CSI2RX_PPI_PG_PATTERN_VRES_VRES(x) FIELD_PREP(GENMASK(15, 0), (x))
> +
> +/* Pattern Generator horizontal Resolution */
> +#define CSI2RX_PPI_PG_PATTERN_HRES 0x64
> +#define CSI2RX_PPI_PG_PATTERN_HRES_HRES(x) FIELD_PREP(GENMASK(15, 0), (x))
> +
> +/* Pattern Generator */
> +#define CSI2RX_PPI_PG_CONFIG 0x68
> +#define CSI2RX_PPI_PG_CONFIG_PG_MODE(x) FIELD_PREP(1, (x))
> +#define CSI2RX_PPI_PG_CONFIG_DATA_TYPE(x) FIELD_PREP(GENMASK(13, 8), (x))
> +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN(x) FIELD_PREP(GENMASK(15, 14), (x))
> +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX(x) FIELD_PREP(GENMASK(17, 16), (x))
> +#define CSI2RX_PPI_PG_CONFIG_VIR_CHAN_EX_2_EN BIT(18)
> +
> +/* Pattern Generator Enable */
> +#define CSI2RX_PPI_PG_ENABLE 0x6c
> +#define CSI2RX_PPI_PG_ENABLE_EN BIT(0)
> +
> +/* Pattern Generator Status */
> +#define CSI2RX_PPI_PG_STATUS 0x70
> +#define CSI2RX_PPI_PG_STATUS_ACTIVE BIT(0)
> +
> +/* IPI Mode */
> +#define CSI2RX_IPI_MODE 0x80
> +#define CSI2RX_IPI_MODE_CONTROLLER BIT(1)
> +#define CSI2RX_IPI_MODE_COLOR_MODE16 BIT(8)
> +#define CSI2RX_IPI_MODE_CUT_THROUGH BIT(16)
> +#define CSI2RX_IPI_MODE_ENABLE BIT(24)
> +
> +/* IPI Virtual Channel */
> +#define CSI2RX_IPI_VCID 0x84
> +#define CSI2RX_IPI_VCID_VC(x) FIELD_PREP(GENMASK(1, 0), (x))
> +#define CSI2RX_IPI_VCID_VC_0_1(x) FIELD_PREP(GENMASK(3, 2), (x))
> +#define CSI2RX_IPI_VCID_VC_2 BIT(4)
> +
> +/* IPI Data Type */
> +#define CSI2RX_IPI_DATA_TYPE 0x88
> +#define CSI2RX_IPI_DATA_TYPE_DT(x) FIELD_PREP(GENMASK(5, 0), (x))
> +#define CSI2RX_IPI_DATA_TYPE_EMB_DATA_EN BIT(8)
> +
> +/* IPI Flush Memory */
> +#define CSI2RX_IPI_MEM_FLUSH 0x8c
> +#define CSI2RX_IPI_MEM_FLUSH_AUTO BIT(8)
> +
> +/* IPI HSA */
> +#define CSI2RX_IPI_HSA_TIME 0x90
> +#define CSI2RX_IPI_HSA_TIME_VAL(x) FIELD_PREP(GENMASK(11, 0), (x))
> +
> +/* IPI HBP */
> +#define CSI2RX_IPI_HBP_TIME 0x94
> +#define CSI2RX_IPI_HBP_TIME_VAL(x) FIELD_PREP(GENMASK(11, 0), (x))
> +
> +/* IPI HSD */
> +#define CSI2RX_IPI_HSD_TIME 0x98
> +#define CSI2RX_IPI_HSD_TIME_VAL(x) FIELD_PREP(GENMASK(11, 0), (x))
> +
> +/* IPI HLINE */
> +#define CSI2RX_IPI_HLINE_TIME 0x9C
> +#define CSI2RX_IPI_HLINE_TIME_VAL(x) FIELD_PREP(GENMASK(14, 0), (x))
> +
> +/* IPI Soft Reset */
> +#define CSI2RX_IPI_SOFTRSTN 0xa0
> +
> +/* IPI Advanced Features */
> +#define CSI2RX_IPI_ADV_FEATURES 0xac
> +#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE_EN BIT(0)
> +#define CSI2RX_IPI_ADV_FEATURES_DT_OVER_WRITE(x) FIELD_PREP(GENMASK(13, 8), (x))
> +#define CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL BIT(16)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_VIDEO_PKT BIT(17)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT BIT(18)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_NULL_PKT BIT(19)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_BLANKING_PKT BIT(20)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_EMBEDDED_PKT BIT(21)
> +#define CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE BIT(24)
> +
> +/* IPI VSA */
> +#define CSI2RX_IPI_VSA_LINES 0xb0
> +#define CSI2RX_IPI_VSA_LINES_VAL(x) FIELD_PREP(GENMASK(9, 0), (x))
> +
> +/* IPI VBP */
> +#define CSI2RX_IPI_VBP_LINES 0xb4
> +#define CSI2RX_IPI_VBP_LINES_VAL(x) FIELD_PREP(GENMASK(9, 0), (x))
> +
> +/* IPI VFP */
> +#define CSI2RX_IPI_VFP_LINES 0xb8
> +#define CSI2RX_IPI_VFP_LINES_VAL(x) FIELD_PREP(GENMASK(9, 0), (x))
> +
> +/* IPI VACTIVE */
> +#define CSI2RX_IPI_VACTIVE_LINES 0xbc
> +#define CSI2RX_IPI_VACTIVE_LINES_VAL(x) FIELD_PREP(GENMASK(13, 0), (x))
> +
> +/* Fatal Interruption Caused by PHY */
> +#define CSI2RX_INT_ST_DPHY_FATAL 0xe0
> +#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE0 BIT(0)
> +#define CSI2RX_INT_ST_DPHY_FATAL_ERR_SOT_LANE1 BIT(1)
> +
> +/* Mask for Fatal Interruption Caused by PHY */
> +#define CSI2RX_INT_MSK_DPHY_FATAL 0xe4
> +#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE0 BIT(0)
> +#define CSI2RX_INT_MSK_DPHY_FATAL_ERR_SOT_LANE1 BIT(1)
> +
> +/* Force for Fatal Interruption Caused by PHY */
> +#define CSI2RX_INT_FORCE_DPHY_FATAL 0xe8
> +
> +/* Fatal Interruption Caused During Packet Construction */
> +#define CSI2RX_INT_ST_PKT_FATAL 0xf0
> +#define CSI2RX_INT_ST_PKT_FATAL_ERR_ECC BIT(0)
> +#define CSI2RX_INT_ST_PKT_FATAL_ERR_PAYLOAD BIT(1)
> +
> +/* Mask for Fatal Interruption Caused During Packet Construction */
> +#define CSI2RX_INT_MSK_PKT_FATAL 0xf4
> +#define CSI2RX_INT_MSK_PKT_FATAL_ERR_ECC BIT(0)
> +#define CSI2RX_INT_MSK_PKT_FATAL_ERR_PAYLOAD BIT(1)
> +
> +/* Force for Fatal Interruption Caused During Packet Construction */
> +#define CSI2RX_INT_FORCE_PKT_FATAL 0xf8
> +
> +/* Interruption Caused by PHY */
> +#define CSI2RX_INT_ST_DPHY 0x110
> +#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE0 BIT(0)
> +#define CSI2RX_INT_ST_DPHY_ERR_SOT_LANE1 BIT(1)
> +#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE0 BIT(16)
> +#define CSI2RX_INT_ST_DPHY_ERR_ESC_LANE1 BIT(17)
> +
> +/* Mask for Interruption Caused by PHY */
> +#define CSI2RX_INT_MSK_DPHY 0x114
> +#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE0 BIT(0)
> +#define CSI2RX_INT_MSK_DPHY_SOT_ERR_LANE1 BIT(1)
> +#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE0 BIT(16)
> +#define CSI2RX_INT_MSK_DPHY_ESC_ERR_LANE1 BIT(17)
> +
> +/* Force for Interruption Caused by PHY */
> +#define CSI2RX_INT_FORCE_DPHY 0x118
> +
> +/* Fatal Interruption Caused by IPI Interface */
> +#define CSI2RX_INT_ST_IPI_FATAL 0x140
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_IFFIFO_UNDERFLOW BIT(0)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_IFFIFO_OVERFLOW BIT(1)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FRAME_SYNC BIT(2)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_HLINE_TIME BIT(4)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5)
> +#define CSI2RX_INT_ST_IPI_FATAL_ERR_PD_FIFO_OVERFLOW BIT(6)
> +
> +/* Mask for Fatal Interruption Caused by IPI Interface */
> +#define CSI2RX_INT_MSK_IPI_FATAL 0x144
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IFFIFO_UNDERFLOW BIT(0)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_IFFIFO_OVERFLOW BIT(1)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FRAME_SYNC BIT(2)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_HLINE_TIME BIT(4)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5)
> +#define CSI2RX_INT_MSK_IPI_FATAL_ERR_PD_FIFO_OVERFLOW BIT(6)
> +
> +/* Force for Fatal Interruption Caused by IPI Interface */
> +#define CSI2RX_INT_FORCE_IPI_FATAL 0x148
> +
> +/* Data De-Scrambling */
> +#define CSI2RX_SCRAMBLING 0x300
> +
> +/* De-scrambler Seed for Lane 1 */
> +#define CSI2RX_SCRAMBLING_SEED1 0x304
> +
> +/* De-scrambler Seed for Lane 2 */
> +#define CSI2RX_SCRAMBLING_SEED2 0x308
> +
> +#define DWC_CSI2RX_PAD_SINK 0
> +#define DWC_CSI2RX_PAD_SOURCE 1
> +#define DWC_CSI2RX_PADS_NUM 2
> +
> +#define DWC_CSI2RX_DEF_MBUS_CODE MEDIA_BUS_FMT_UYVY8_1X16
> +#define DWC_CSI2RX_DEF_PIX_WIDTH 1920U
> +#define DWC_CSI2RX_DEF_PIX_HEIGHT 1080U
> +#define DWC_CSI2RX_MAX_PIX_WIDTH 0xffff
> +#define DWC_CSI2RX_MAX_PIX_HEIGHT 0xffff
> +
> +struct dwc_csi_event {
> + u32 mask;
> + const char * const name;
> + unsigned int counter;
> +};
> +
> +static const struct dwc_csi_event dwc_events[] = {
> + { CSI2RX_INT_ST_MAIN_FATAL_ERR_IPI, "IPI Interface Fatal Error" },
> + { CSI2RX_INT_ST_MAIN_ERR_PHY, "PHY Error" },
> + { CSI2RX_INT_ST_MAIN_ERR_ECC, "Header Single Bit Error" },
> + { CSI2RX_INT_ST_MAIN_ERR_DID, "Data ID Error" },
> + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PLD_CRC, "Payload CRC Fatal Error" },
> + { CSI2RX_INT_ST_MAIN_FATAL_ERR_CRC_FRAME, "Frame CRC Fatal Error" },
> + { CSI2RX_INT_ST_MAIN_FATAL_ERR_SEQ_FRAME, "Frame Sequence Fatal Error" },
> + { CSI2RX_INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL, "Frame Boundaries Fatal Error" },
> + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PKT, "Packet Construction Fatal Error" },
> + { CSI2RX_INT_ST_MAIN_FATAL_ERR_PHY, "PHY Fatal Error" },
> +};
> +
> +#define DWC_NUM_EVENTS ARRAY_SIZE(dwc_events)
> +#define DWC_EVENT_MASK 0x500ff
> +
> +struct dwc_csi_pix_format {
> + u32 code;
> + u32 output;
> + u32 data_type;
> + u8 width;
> +};
> +
> +struct dwc_csi_device {
> + struct device *dev;
> + void __iomem *regs;
> + struct phy *phy;
> + struct clk_bulk_data *clks;
> + int num_clks;
> + struct v4l2_subdev sd;
> + struct v4l2_async_notifier notifier;
> + struct v4l2_subdev *source_sd;
> + struct v4l2_ctrl_handler ctrl_handler;
> + struct media_pad pads[DWC_CSI2RX_PADS_NUM];
> + u16 remote_pad;
> +
> + struct v4l2_mbus_config_mipi_csi2 bus;
> + u32 cfgclkfreqrange;
> + u32 hsfreqrange;
> + u64 enabled_streams;
> +
> + /* Use driver mutex lock for the ctrl lock */
> + struct mutex lock;
> +
> + struct dwc_csi_event events[DWC_NUM_EVENTS];
> + const struct dwc_csi_pix_format *csi_fmt;
> +
> + /* Used for pattern generator */
> + bool pg_enable;
> + enum {
> + PATTERN_DISABLED,
> + PATTERN_VERTICAL,
> + PATTERN_HORIZONTAL,
> + } pg_pattern;
> +};
> +
> +/* List of supported pixel formats for the subdev */
> +static const struct dwc_csi_pix_format dwc_csi_formats[] = {
> + /* YUV formats */
> + {
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .output = MEDIA_BUS_FMT_UYVY8_1X16,
> + .data_type = MIPI_CSI2_DT_YUV422_8B,
> + .width = 16,
> + },
> + /* RGB formats */
> + {
> + .code = MEDIA_BUS_FMT_RGB565_1X16,
> + .output = MEDIA_BUS_FMT_RGB565_1X16,
> + .data_type = MIPI_CSI2_DT_RGB565,
> + .width = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_BGR888_1X24,
> + .output = MEDIA_BUS_FMT_RGB888_1X24,
> + .data_type = MIPI_CSI2_DT_RGB888,
> + .width = 24,
> + },
> + /* RAW (Bayer and greyscale) formats. */
> + {
> + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .output = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .data_type = MIPI_CSI2_DT_RAW8,
> + .width = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .output = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .data_type = MIPI_CSI2_DT_RAW8,
> + .width = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .output = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .data_type = MIPI_CSI2_DT_RAW8,
> + .width = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .output = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .data_type = MIPI_CSI2_DT_RAW8,
> + .width = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_Y8_1X8,
> + .output = MEDIA_BUS_FMT_Y8_1X8,
> + .data_type = MIPI_CSI2_DT_RAW8,
> + .width = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .output = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .data_type = MIPI_CSI2_DT_RAW10,
> + .width = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .output = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .data_type = MIPI_CSI2_DT_RAW10,
> + .width = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .output = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .data_type = MIPI_CSI2_DT_RAW10,
> + .width = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .output = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .data_type = MIPI_CSI2_DT_RAW10,
> + .width = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_Y10_1X10,
> + .output = MEDIA_BUS_FMT_Y10_1X10,
> + .data_type = MIPI_CSI2_DT_RAW10,
> + .width = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .output = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .data_type = MIPI_CSI2_DT_RAW12,
> + .width = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> + .output = MEDIA_BUS_FMT_SGBRG12_1X12,
> + .data_type = MIPI_CSI2_DT_RAW12,
> + .width = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> + .output = MEDIA_BUS_FMT_SGRBG12_1X12,
> + .data_type = MIPI_CSI2_DT_RAW12,
> + .width = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> + .output = MEDIA_BUS_FMT_SRGGB12_1X12,
> + .data_type = MIPI_CSI2_DT_RAW12,
> + .width = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_Y12_1X12,
> + .output = MEDIA_BUS_FMT_Y12_1X12,
> + .data_type = MIPI_CSI2_DT_RAW12,
> + .width = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .output = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .data_type = MIPI_CSI2_DT_RAW14,
> + .width = 14,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> + .output = MEDIA_BUS_FMT_SGBRG14_1X14,
> + .data_type = MIPI_CSI2_DT_RAW14,
> + .width = 14,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> + .output = MEDIA_BUS_FMT_SGRBG14_1X14,
> + .data_type = MIPI_CSI2_DT_RAW14,
> + .width = 14,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> + .output = MEDIA_BUS_FMT_SRGGB14_1X14,
> + .data_type = MIPI_CSI2_DT_RAW14,
> + .width = 14,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR16_1X16,
> + .output = MEDIA_BUS_FMT_SBGGR16_1X16,
> + .data_type = MIPI_CSI2_DT_RAW16,
> + .width = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG16_1X16,
> + .output = MEDIA_BUS_FMT_SGBRG16_1X16,
> + .data_type = MIPI_CSI2_DT_RAW16,
> + .width = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG16_1X16,
> + .output = MEDIA_BUS_FMT_SGRBG16_1X16,
> + .data_type = MIPI_CSI2_DT_RAW16,
> + .width = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB16_1X16,
> + .output = MEDIA_BUS_FMT_SRGGB16_1X16,
> + .data_type = MIPI_CSI2_DT_RAW16,
> + .width = 16,
> + }
> +};
> +
> +static const struct v4l2_mbus_framefmt dwc_csi_default_fmt = {
> + .code = DWC_CSI2RX_DEF_MBUS_CODE,
> + .width = DWC_CSI2RX_DEF_PIX_WIDTH,
> + .height = DWC_CSI2RX_DEF_PIX_HEIGHT,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SMPTE170M,
> + .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
> + .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M),
> + .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +};
> +
> +static const struct dwc_csi_pix_format *find_csi_format(u32 code)
> +{
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(dwc_csi_formats); i++)
> + if (code == dwc_csi_formats[i].code)
> + return &dwc_csi_formats[i];
> +
> + return &dwc_csi_formats[0];
> +}
> +
> +static inline void dwc_csi_write(struct dwc_csi_device *csidev, unsigned int offset, u32 val)
> +{
> + writel(val, csidev->regs + offset);
> +}
> +
> +static inline u32 dwc_csi_read(struct dwc_csi_device *csidev, unsigned int offset)
> +{
> + return readl(csidev->regs + offset);
> +}
Please don't add simple wrappers around writel/readl. Instead consider
using regmap-mmio. This also adds the possibility to have register access
tracing.
> +
> +/*
> + * DWC MIPI CSI-2 Host Controller Hardware operation
> + */
> +static int dwc_csi_device_pg_enable(struct dwc_csi_device *csidev)
> +{
> + const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt;
> + struct v4l2_subdev *sd = &csidev->sd;
> + struct v4l2_subdev_state *state;
> + struct v4l2_mbus_framefmt *fmt;
> + u32 val;
> +
> + if (!csidev->pg_enable)
> + return 0;
> +
> + if (!csi_fmt) {
> + dev_err(csidev->dev, "CSI pixel format is NULL\n");
> + return -EINVAL;
> + }
> +
> + if (csi_fmt->data_type != MIPI_CSI2_DT_RGB888) {
> + dev_err(csidev->dev, "Pattern generator only support RGB888\n");
s/support/supports/
> + return -EINVAL;
> + }
> +
> + state = v4l2_subdev_lock_and_get_active_state(sd);
> + /* Pattern generator create data stream only according to stream 0 */
s/create/creates/
> + fmt = v4l2_subdev_state_get_format(state, DWC_CSI2RX_PAD_SINK, 0);
> +
> + val = CSI2RX_PPI_PG_PATTERN_HRES_HRES(fmt->width);
> + dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_HRES, val);
> +
> + val = CSI2RX_PPI_PG_PATTERN_VRES_VRES(fmt->height);
> + dwc_csi_write(csidev, CSI2RX_PPI_PG_PATTERN_VRES, val);
> +
> + val = CSI2RX_PPI_PG_CONFIG_DATA_TYPE(csi_fmt->data_type);
> + val |= CSI2RX_PPI_PG_CONFIG_VIR_CHAN(0);
> + val |= CSI2RX_PPI_PG_CONFIG_PG_MODE(csidev->pg_pattern);
> + dwc_csi_write(csidev, CSI2RX_PPI_PG_CONFIG, val);
> +
> + /*
> + * Select line start packets to construct vertical
> + * timing information for IPI interface
> + */
> + val = CSI2RX_IPI_ADV_FEATURES_SYNC_EVENT_MODE;
> + val |= CSI2RX_IPI_ADV_FEATURES_SYNC_LS_PKT;
> + val |= CSI2RX_IPI_ADV_FEATURES_LINE_EVENT_SEL;
> + dwc_csi_write(csidev, CSI2RX_IPI_ADV_FEATURES, val);
> +
> + val = CSI2RX_PPI_PG_ENABLE_EN;
> + dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, val);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return 0;
> +}
> +
> +static void dwc_csi_device_pg_disable(struct dwc_csi_device *csidev)
> +{
> + dwc_csi_write(csidev, CSI2RX_PPI_PG_ENABLE, 0);
> +}
> +
> +static void dwc_csi_ipi_enable(struct dwc_csi_device *csidev)
> +{
> + u32 val;
> +
> + /* Memory is automatically flushed at each Frame Start */
> + val = CSI2RX_IPI_MEM_FLUSH_AUTO;
> + dwc_csi_write(csidev, CSI2RX_IPI_MEM_FLUSH, val);
> +
> + /* Enable IPI */
> + val = dwc_csi_read(csidev, CSI2RX_IPI_MODE);
> + val |= CSI2RX_IPI_MODE_ENABLE;
> + dwc_csi_write(csidev, CSI2RX_IPI_MODE, val);
> +}
> +
> +static void dwc_csi_ipi_disable(struct dwc_csi_device *csidev)
> +{
> + dwc_csi_write(csidev, CSI2RX_IPI_MODE, 0);
> +}
> +
> +static void dwc_csi_device_ipi_config(struct dwc_csi_device *csidev)
> +{
> + const struct dwc_csi_pix_format *csi_fmt = csidev->csi_fmt;
> + u32 val;
> +
> + /* Do IPI soft reset */
> + dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x0);
> + dwc_csi_write(csidev, CSI2RX_IPI_SOFTRSTN, 0x1);
Use dwc_csi_device_reset()
> +
> + /* Select virtual channel and data type to be processed by IPI */
> + val = CSI2RX_IPI_DATA_TYPE_DT(csi_fmt->data_type);
> + dwc_csi_write(csidev, CSI2RX_IPI_DATA_TYPE, val);
> +
> + /* Set virtual channel 0 as default */
> + val = CSI2RX_IPI_VCID_VC(0);
> + dwc_csi_write(csidev, CSI2RX_IPI_VCID, val);
> +
> + /*
> + * Select IPI camera timing mode and allow the pixel stream
> + * to be non-continuous when pixel interface FIFO is empty
> + */
> + val = dwc_csi_read(csidev, CSI2RX_IPI_MODE);
> + val &= ~CSI2RX_IPI_MODE_CONTROLLER;
> + val &= ~CSI2RX_IPI_MODE_COLOR_MODE16;
So this enables 48 Bit interface, instead of 16 Bit. What does that
mean given that IPI is an internal IP block?
> + val |= CSI2RX_IPI_MODE_CUT_THROUGH;
> + dwc_csi_write(csidev, CSI2RX_IPI_MODE, val);
> +}
> +
> +static void dwc_csi_device_reset(struct dwc_csi_device *csidev)
> +{
> + /* Reset mipi csi host, active low */
> + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0);
> + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 1);
> +}
> +
> +static void dwc_csi_device_startup(struct dwc_csi_device *csidev)
> +{
> + /* Release DWC_mipi_csi2_host from reset */
> + dwc_csi_device_reset(csidev);
> +
> + phy_init(csidev->phy);
> +
> + phy_reset(csidev->phy);
> +}
> +
> +static int dwc_csi_get_dphy_configuration(struct dwc_csi_device *csidev,
> + union phy_configure_opts *opts)
> +{
> + struct phy_configure_opts_mipi_dphy *cfg = &opts->mipi_dphy;
> + struct v4l2_subdev *source = csidev->source_sd;
> + s64 link_freq;
> +
> + link_freq = v4l2_get_link_freq(source->ctrl_handler,
> + csidev->csi_fmt->width,
> + csidev->bus.num_data_lanes * 2);
> + if (link_freq < 0) {
> + dev_err(csidev->dev, "Unable to obtain link frequency: %d\n",
> + (int)link_freq);
> + return link_freq;
> + }
> +
> + memset(cfg, 0x0, sizeof(*cfg));
> + cfg->hs_clk_rate = link_freq * 2;
> + cfg->lanes = csidev->bus.num_data_lanes;
> +
> + return 0;
> +}
> +
> +static void dwc_csi_dphy_prep(struct dwc_csi_device *csidev)
> +{
> + u32 val;
> +
> + /* Release synopsis DPHY test codes from reset */
> + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x0);
> + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x0);
> +
> + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
> + val &= ~CSI2RX_DPHY_TEST_CTRL0_TEST_CLR;
> + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +
> + /*
> + * ndelay is not necessary have MMIO operation, need dummy read to make
> + * sure above write reach target.
> + */
> + dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
> + /* Wait for at least 15ns */
> + ndelay(15);
What does the comment mean? You are reading that register twice, see below.
If you write to that register, you may need to wait 15ns before reading back.
But reading back, waiting and reading back, doesn't make sense to me.
> +
> + val = dwc_csi_read(csidev, CSI2RX_DPHY_TEST_CTRL0);
> + val |= CSI2RX_DPHY_TEST_CTRL0_TEST_CLR;
> + dwc_csi_write(csidev, CSI2RX_DPHY_TEST_CTRL0, val);
> +}
> +
> +static void dwc_csi_dphy_release_reset(struct dwc_csi_device *csidev)
> +{
> + /* Release PHY from reset */
> + dwc_csi_write(csidev, CSI2RX_DPHY_SHUTDOWNZ, 0x1);
> + /*
> + * ndelay is not necessary have MMIO operation, need dummy read to make
> + * sure above write reach target.
> + */
> + dwc_csi_read(csidev, CSI2RX_DPHY_SHUTDOWNZ);
> + ndelay(5);
> + dwc_csi_write(csidev, CSI2RX_DPHY_RSTZ, 0x1);
> + dwc_csi_read(csidev, CSI2RX_DPHY_RSTZ);
> + ndelay(5);
> +}
> +
> +static int dwc_csi_device_init(struct dwc_csi_device *csidev)
> +{
> + struct device *dev = csidev->dev;
> + union phy_configure_opts opts;
> + u32 phy_stopstate;
> + u32 val;
> + int ret;
> +
> + ret = dwc_csi_get_dphy_configuration(csidev, &opts);
> + if (ret)
> + return ret;
> +
> + ret = phy_set_mode(csidev->phy, PHY_MODE_MIPI_DPHY);
> + if (ret)
> + return ret;
> +
> + ret = phy_configure(csidev->phy, &opts);
> + if (ret)
> + return ret;
> +
> + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0);
> + dwc_csi_dphy_prep(csidev);
> + dwc_csi_write(csidev, CSI2RX_N_LANES, CSI2RX_N_LANES_N_LANES(opts.mipi_dphy.lanes));
> + ret = phy_power_on(csidev->phy);
> + dwc_csi_dphy_release_reset(csidev);
> + dwc_csi_write(csidev, CSI2RX_HOST_RESETN, 0x1);
> +
> + if (ret)
> + return ret;
> +
> + /* Check if lanes are in stop state */
> + phy_stopstate = CSI2RX_DPHY_STOPSTATE_CLK_LANE;
> + phy_stopstate |= GENMASK(csidev->bus.num_data_lanes - 1, 0);
> + ret = readl_poll_timeout(csidev->regs + CSI2RX_DPHY_STOPSTATE,
> + val, (val & phy_stopstate) != phy_stopstate,
> + 10, 10000);
> + if (ret) {
> + dev_err(dev, "Lanes are not in stop state(%#x)\n", val);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void dwc_csi_device_hs_rx_start(struct dwc_csi_device *csidev)
> +{
> + dwc_csi_ipi_enable(csidev);
> +}
> +
> +static int dwc_csi_device_hs_rx_stop(struct dwc_csi_device *csidev)
> +{
> + struct device *dev = csidev->dev;
> + u32 val;
> +
> + phy_power_off(csidev->phy);
> + phy_exit(csidev->phy);
> + dwc_csi_ipi_disable(csidev);
> +
> + /* Check clock lanes are not in High Speed Mode */
> + val = dwc_csi_read(csidev, CSI2RX_DPHY_RX_STATUS);
> + if (val & CSI2RX_DPHY_RX_STATUS_CLK_LANE_HS) {
> + dev_err(dev, "Clock lanes are still in HS mode\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static void dwc_csi_device_enable_interrupts(struct dwc_csi_device *csidev, bool on)
> +{
> + /* Define errors to be enabled */
> + dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY_FATAL, on ? 0x3 : 0);
> + dwc_csi_write(csidev, CSI2RX_INT_MSK_PKT_FATAL, on ? 0x3 : 0);
> + dwc_csi_write(csidev, CSI2RX_INT_MSK_DPHY, on ? 0x30003 : 0);
> + dwc_csi_write(csidev, CSI2RX_INT_MSK_IPI_FATAL, on ? 0x7f : 0);
> +}
> +
> +static void dwc_csi_clear_counters(struct dwc_csi_device *csidev)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < DWC_NUM_EVENTS; ++i)
> + csidev->events[i].counter = 0;
> +}
> +
> +static void dwc_csi_log_counters(struct dwc_csi_device *csidev)
> +{
> + unsigned int i;
> + int counter;
> +
> + for (i = 0; i < DWC_NUM_EVENTS; ++i) {
> + counter = csidev->events[i].counter;
> + if (counter > 0)
> + dev_info(csidev->dev, "%s events: %d\n",
> + csidev->events[i].name,
> + counter);
> + }
> +}
> +
> +static void dwc_csi_dump_regs(struct dwc_csi_device *csidev)
> +{
> +#define DWC_MIPI_CSIS_DEBUG_REG(name) {name, #name}
> + static const struct {
> + u32 offset;
> + const char * const name;
> + } registers[] = {
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_VERSION),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_N_LANES),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_HOST_RESETN),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_MAIN),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_1_DT),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_2_DT),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_1_VC),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DATA_IDS_2_VC),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_SHUTDOWNZ),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RSTZ),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_RX_STATUS),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_STOPSTATE),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL0),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_DPHY_TEST_CTRL1),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_VRES),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_PATTERN_HRES),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_CONFIG),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_ENABLE),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_PPI_PG_STATUS),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MODE),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_VCID),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_DATA_TYPE),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_MEM_FLUSH),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_SOFTRSTN),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_IPI_ADV_FEATURES),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_PKT_FATAL),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_DPHY_FATAL),
> + DWC_MIPI_CSIS_DEBUG_REG(CSI2RX_INT_ST_IPI_FATAL),
> + };
> +
> + unsigned int i;
> + u32 cfg;
> +
> + dev_dbg(csidev->dev, "--- REGISTERS ---\n");
> +
> + for (i = 0; i < ARRAY_SIZE(registers); i++) {
> + cfg = dwc_csi_read(csidev, registers[i].offset);
> + dev_dbg(csidev->dev, "%14s[0x%02x]: 0x%08x\n",
> + registers[i].name, registers[i].offset, cfg);
> + }
> +}
> +
> +/*
> + * V4L2 subdev operations
> + */
> +
> +static inline struct dwc_csi_device *
> +sd_to_dwc_csi_device(struct v4l2_subdev *sdev)
> +{
> + return container_of(sdev, struct dwc_csi_device, sd);
> +}
> +
> +static int __dwc_csi_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + int ret;
> +
> + if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
> + return -EINVAL;
> +
> + ret = v4l2_subdev_routing_validate(sd, routing,
> + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> + if (ret)
> + return ret;
> +
> + return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
> + &dwc_csi_default_fmt);
> +}
> +
> +static int dwc_csi_subdev_init_state(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state)
> +{
> + struct v4l2_subdev_route routes[] = {
> + {
> + .sink_pad = DWC_CSI2RX_PAD_SINK,
> + .sink_stream = 0,
> + .source_pad = DWC_CSI2RX_PAD_SOURCE,
> + .source_stream = 0,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + },
> + };
> +
> + struct v4l2_subdev_krouting routing = {
> + .num_routes = ARRAY_SIZE(routes),
> + .routes = routes,
> + };
> +
> + return __dwc_csi_subdev_set_routing(sd, sd_state, &routing);
> +}
> +
> +static int dwc_csi_subdev_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + /*
> + * The CSIS can't transcode in any way, the source format is identical
> + * to the sink format.
> + */
> + if (code->pad == DWC_CSI2RX_PAD_SOURCE) {
> + struct v4l2_mbus_framefmt *fmt;
> +
> + if (code->index > 0)
> + return -EINVAL;
> +
> + fmt = v4l2_subdev_state_get_format(sd_state, code->pad,
> + code->stream);
> + code->code = fmt->code;
> + return 0;
> + }
> +
> + if (code->index >= ARRAY_SIZE(dwc_csi_formats))
> + return -EINVAL;
> +
> + code->code = dwc_csi_formats[code->index].code;
> +
> + return 0;
> +}
> +
> +static int dwc_csi_subdev_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *sdformat)
> +{
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> + struct dwc_csi_pix_format const *csi_fmt;
> + struct v4l2_mbus_framefmt *fmt;
> + unsigned int align;
> +
> + /*
> + * The CSIS can't transcode in any way, the source format can't be
> + * modified.
> + */
> + if (sdformat->pad == DWC_CSI2RX_PAD_SOURCE)
> + return v4l2_subdev_get_fmt(sd, sd_state, sdformat);
> +
> + /*
> + * Validate the media bus code and clamp and align the size.
> + *
> + * The total number of bits per line must be a multiple of 8. We thus
> + * need to align the width for formats that are not multiples of 8
> + * bits.
> + */
> + csi_fmt = find_csi_format(sdformat->format.code);
> +
> + switch (csi_fmt->width % 8) {
> + case 0:
> + align = 0;
> + break;
> + case 4:
> + align = 1;
> + break;
> + case 2:
> + case 6:
> + align = 2;
> + break;
> + default:
> + /* 1, 3, 5, 7 */
> + align = 3;
> + break;
> + }
> +
> + v4l_bound_align_image(&sdformat->format.width, 1,
> + DWC_CSI2RX_MAX_PIX_WIDTH, align,
> + &sdformat->format.height, 1,
> + DWC_CSI2RX_MAX_PIX_HEIGHT, 0, 0);
> +
> + fmt = v4l2_subdev_state_get_format(sd_state, sdformat->pad,
> + sdformat->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = sdformat->format;
> +
> + /* Set default code if user set an invalid value */
> + fmt->code = csi_fmt->code;
> +
> + /* Propagate the format from sink stream to source stream */
> + fmt = v4l2_subdev_state_get_opposite_stream_format(sd_state, sdformat->pad,
> + sdformat->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = sdformat->format;
> + /* The format on the source pad might change due to unpacking. */
> + fmt->code = csi_fmt->output;
> +
> + /* Store the CSIS format descriptor for active formats. */
> + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE)
> + csidev->csi_fmt = csi_fmt;
> +
> + return 0;
> +}
> +
> +static int dwc_csi_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
> + struct v4l2_mbus_frame_desc *fd)
> +{
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> + struct v4l2_mbus_frame_desc source_fd;
> + struct v4l2_subdev_route *route;
> + struct v4l2_subdev_state *state;
> + int ret;
> +
> + if (pad != DWC_CSI2RX_PAD_SOURCE)
> + return -EINVAL;
> +
> + memset(fd, 0, sizeof(*fd));
> +
> + ret = v4l2_subdev_call(csidev->source_sd, pad, get_frame_desc,
> + csidev->remote_pad, &source_fd);
> + if (ret < 0) {
> + dev_info(csidev->dev,
> + "Remote sub-device on pad %d should implement .get_frame_desc! Forcing VC = 0 and DT = %x\n",
> + pad, csidev->csi_fmt->data_type);
Is this dev_info or dev_warn? Maybe Laurent or other V4L folks can give
some hints.
> + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> + fd->num_entries = 1;
> + fd->entry[0].pixelcode = csidev->csi_fmt->code;
> + fd->entry[0].bus.csi2.vc = 0;
> + fd->entry[0].bus.csi2.dt = csidev->csi_fmt->data_type;
> +
> + return 0;
> + }
> +
> + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> +
> + state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> + for_each_active_route(&state->routing, route) {
> + struct v4l2_mbus_frame_desc_entry *entry = NULL;
> + unsigned int i;
> +
> + if (route->source_pad != pad)
> + continue;
> +
> + for (i = 0; i < source_fd.num_entries; ++i) {
> + if (source_fd.entry[i].stream == route->sink_stream) {
> + entry = &source_fd.entry[i];
> + break;
> + }
> + }
> +
> + if (!entry) {
> + dev_err(csidev->dev,
> + "Failed to find stream from source frames desc\n");
> + ret = -EPIPE;
> + goto out_unlock;
> + }
> +
> + fd->entry[fd->num_entries].stream = route->source_stream;
> + fd->entry[fd->num_entries].flags = entry->flags;
> + fd->entry[fd->num_entries].length = entry->length;
> + fd->entry[fd->num_entries].pixelcode = entry->pixelcode;
> + fd->entry[fd->num_entries].bus.csi2.vc = entry->bus.csi2.vc;
> + fd->entry[fd->num_entries].bus.csi2.dt = entry->bus.csi2.dt;
> +
> + fd->num_entries++;
> + }
> +
> +out_unlock:
> + v4l2_subdev_unlock_state(state);
> + return ret;
> +}
> +
> +static int dwc_csi_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> + media_entity_is_streaming(&sd->entity))
> + return -EBUSY;
> +
> + return __dwc_csi_subdev_set_routing(sd, state, routing);
> +}
> +
> +static int dwc_csi_start_stream(struct dwc_csi_device *csidev)
> +{
> + int ret;
> +
> + dwc_csi_device_startup(csidev);
> +
> + ret = dwc_csi_device_init(csidev);
> + if (ret)
> + return ret;
> +
> + dwc_csi_device_ipi_config(csidev);
> +
> + ret = dwc_csi_device_pg_enable(csidev);
> + if (ret)
> + return ret;
> +
> + dwc_csi_device_hs_rx_start(csidev);
> +
> + dwc_csi_device_enable_interrupts(csidev, true);
> +
> + return 0;
> +}
> +
> +static void dwc_csi_stop_stream(struct dwc_csi_device *csidev)
> +{
> + dwc_csi_device_enable_interrupts(csidev, false);
> + dwc_csi_device_hs_rx_stop(csidev);
> + dwc_csi_device_pg_disable(csidev);
> +}
> +
> +static int dwc_csi_enable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state, u32 pad,
> + u64 streams_mask)
> +{
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> + u64 sink_streams;
> + int ret;
> +
> + if (!csidev->source_sd) {
> + dev_err(csidev->dev, "Sensor don't link with CSIS pad\n");
s/don't/doesn't/
> + return -EPIPE;
> + }
> +
> + if (!csidev->enabled_streams) {
> + ret = pm_runtime_resume_and_get(csidev->dev);
> + if (ret < 0)
> + return ret;
> +
> + ret = v4l2_ctrl_handler_setup(&csidev->ctrl_handler);
> + if (ret < 0)
> + goto err_runtime_put;
> +
> + dwc_csi_clear_counters(csidev);
> +
> + ret = dwc_csi_start_stream(csidev);
> + if (ret < 0)
> + goto err_runtime_put;
> +
> + dwc_csi_dump_regs(csidev);
> + dwc_csi_log_counters(csidev);
Are you sure you want to dump all the registers every time you start
a stream?
> + }
> +
> + sink_streams = v4l2_subdev_state_xlate_streams(state, DWC_CSI2RX_PAD_SOURCE,
> + DWC_CSI2RX_PAD_SINK,
> + &streams_mask);
> +
> + dev_dbg(csidev->dev, "remote sd: %s pad: %u, sink_stream:0x%llx\n",
> + csidev->source_sd->name, csidev->remote_pad, sink_streams);
> +
> + ret = v4l2_subdev_enable_streams(csidev->source_sd, csidev->remote_pad,
> + sink_streams);
> + if (ret)
> + return ret;
> +
> + csidev->enabled_streams |= streams_mask;
> +
> + return 0;
> +
> +err_runtime_put:
> + pm_runtime_put(csidev->dev);
> + return ret;
> +}
> +
> +static int dwc_csi_disable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state, u32 pad,
> + u64 streams_mask)
> +{
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> + u64 sink_streams;
> + int ret;
> +
> + sink_streams = v4l2_subdev_state_xlate_streams(state, DWC_CSI2RX_PAD_SOURCE,
> + DWC_CSI2RX_PAD_SINK,
> + &streams_mask);
> +
> + ret = v4l2_subdev_disable_streams(csidev->source_sd, csidev->remote_pad,
> + sink_streams);
> + if (ret)
> + return ret;
> +
> + csidev->enabled_streams &= ~streams_mask;
> +
> + if (!csidev->enabled_streams) {
> + dwc_csi_stop_stream(csidev);
> + dwc_csi_log_counters(csidev);
> + pm_runtime_put(csidev->dev);
> + }
> +
> + return 0;
> +}
> +
> +static int dwc_csi_subdev_log_status(struct v4l2_subdev *sd)
> +{
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +
> + dwc_csi_log_counters(csidev);
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_core_ops dwc_csi_subdev_core_ops = {
> + .log_status = dwc_csi_subdev_log_status,
> + .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> + .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> +};
> +
> +static const struct v4l2_subdev_pad_ops dwc_csi_subdev_pad_ops = {
> + .enum_mbus_code = dwc_csi_subdev_enum_mbus_code,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = dwc_csi_subdev_set_fmt,
> + .get_frame_desc = dwc_csi_get_frame_desc,
> + .set_routing = dwc_csi_set_routing,
> + .enable_streams = dwc_csi_enable_streams,
> + .disable_streams = dwc_csi_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops dwc_csi_subdev_ops = {
> + .core = &dwc_csi_subdev_core_ops,
> + .pad = &dwc_csi_subdev_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops dwc_csi_internal_ops = {
> + .init_state = dwc_csi_subdev_init_state,
> +};
> +
> +/*
> + * Media entity operations
> + */
> +
> +static int dwc_csi_link_setup(struct media_entity *entity,
> + const struct media_pad *local_pad,
> + const struct media_pad *remote_pad, u32 flags)
> +{
> + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> + struct v4l2_subdev *remote_sd;
> +
> + dev_dbg(csidev->dev, "link setup %s -> %s", remote_pad->entity->name,
> + local_pad->entity->name);
> +
> + /* We only care about the link to the source. */
> + if (!(local_pad->flags & MEDIA_PAD_FL_SINK))
> + return 0;
> +
> + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> +
> + if (flags & MEDIA_LNK_FL_ENABLED) {
> + if (csidev->source_sd)
> + return -EBUSY;
> +
> + csidev->source_sd = remote_sd;
> + csidev->remote_pad = remote_pad->index;
> + } else {
> + csidev->source_sd = NULL;
> + }
> +
> + return 0;
> +}
> +
> +static int dwc_csi_link_validate(struct media_link *link)
> +{
> + struct media_pad *sink_pad = link->sink;
> + struct v4l2_subdev *sink_sd;
> + struct dwc_csi_device *csidev;
> +
> + sink_sd = media_entity_to_v4l2_subdev(sink_pad->entity);
> + csidev = sd_to_dwc_csi_device(sink_sd);
> +
> + dev_dbg(csidev->dev, "entity name:%s pad index=%d\n",
> + sink_sd->name, sink_pad->index);
> +
> + /*
> + * Skip link validate when pattern enabled since the source
> + * data will be from CSI pattern generator, not sensor.
> + */
> + if (csidev->pg_enable && sink_pad->index == DWC_CSI2RX_PAD_SINK)
> + return 0;
> +
> + return v4l2_subdev_link_validate(link);
> +}
> +
> +static const struct media_entity_operations dwc_csi_entity_ops = {
> + .link_setup = dwc_csi_link_setup,
> + .link_validate = dwc_csi_link_validate,
> + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> +};
> +
> +/*
> + * Async subdev notifier
> + */
> +
> +static inline struct dwc_csi_device *
> +notifier_to_dwc_csi_device(struct v4l2_async_notifier *n)
> +{
> + return container_of(n, struct dwc_csi_device, notifier);
> +}
> +
> +static int dwc_csi_notify_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *sd,
> + struct v4l2_async_connection *asd)
> +{
> + struct dwc_csi_device *csidev = notifier_to_dwc_csi_device(notifier);
> + struct media_pad *sink = &csidev->sd.entity.pads[DWC_CSI2RX_PAD_SINK];
> +
> + return v4l2_create_fwnode_links_to_pad(sd, sink, 0);
> +}
> +
> +static const struct v4l2_async_notifier_operations dwc_csi_notify_ops = {
> + .bound = dwc_csi_notify_bound,
> +};
> +
> +static int dwc_csi_async_register(struct dwc_csi_device *csidev)
> +{
> + struct v4l2_fwnode_endpoint vep = {
> + .bus_type = V4L2_MBUS_CSI2_DPHY,
> + };
> + struct v4l2_async_connection *asd;
> + struct fwnode_handle *ep;
> + unsigned int i;
> + int ret;
> +
> + v4l2_async_subdev_nf_init(&csidev->notifier, &csidev->sd);
> +
> + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csidev->dev), 0, 0,
> + FWNODE_GRAPH_ENDPOINT_NEXT);
> + if (!ep)
> + return -ENOTCONN;
> +
> + ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> + if (ret)
> + goto err_parse;
> +
> + for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) {
> + if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) {
> + dev_err(csidev->dev,
> + "data lanes reordering is not supported");
> + ret = -EINVAL;
> + goto err_parse;
> + }
> + }
> +
> + csidev->bus = vep.bus.mipi_csi2;
> +
> + dev_dbg(csidev->dev, "data lanes: %d\n", csidev->bus.num_data_lanes);
> + dev_dbg(csidev->dev, "flags: 0x%08x\n", csidev->bus.flags);
> +
> + asd = v4l2_async_nf_add_fwnode_remote(&csidev->notifier, ep,
> + struct v4l2_async_connection);
> + if (IS_ERR(asd)) {
> + ret = PTR_ERR(asd);
> + goto err_parse;
> + }
> +
> + fwnode_handle_put(ep);
> +
> + csidev->notifier.ops = &dwc_csi_notify_ops;
> +
> + ret = v4l2_async_nf_register(&csidev->notifier);
> + if (ret)
> + goto err_notifier_clean;
> +
> + ret = v4l2_async_register_subdev(&csidev->sd);
> + if (ret)
> + goto err_unreg_notifier;
> +
> + return ret;
> +
> +err_unreg_notifier:
> + v4l2_async_nf_unregister(&csidev->notifier);
> +err_notifier_clean:
> + v4l2_async_nf_cleanup(&csidev->notifier);
> +err_parse:
> + fwnode_handle_put(ep);
> + return ret;
> +}
> +
> +/*
> + * Pattern Generator Controller operations
> + */
> +
> +static const char * const test_pattern_menu[] = {
> + "Disabled",
> + "Vertical Color Bars",
> + "Horizontal Color Bars",
> +};
> +
> +static inline struct dwc_csi_device *ctrl_to_csidev(struct v4l2_ctrl *ctrl)
> +{
> + return container_of(ctrl->handler, struct dwc_csi_device, ctrl_handler);
> +}
> +
> +static int dwc_csi_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct dwc_csi_device *csidev = ctrl_to_csidev(ctrl);
> + int ret = 0;
> +
> + switch (ctrl->id) {
> + case V4L2_CID_TEST_PATTERN:
> + /* Pattern index start from 0 */
> + csidev->pg_pattern = ctrl->val - 1;
ctrl->val is s32 which not supposed to wrap. IIRC this is undefined behavior.
Just set pg_pattern if strl->val is non-zero.
You only active test pattern when stream is started. Does it make sense to
allow setting this control only while streaming is disabled?
Thanks, I'll give it a try soon.
Best regards
Alexander
> + csidev->pg_enable = (ctrl->val) ? true : false;
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops dwc_csi_ctrl_ops = {
> + .s_ctrl = dwc_csi_s_ctrl,
> +};
> +
> +static int dwc_csi_controls_init(struct dwc_csi_device *csidev)
> +{
> + struct v4l2_ctrl_handler *handler = &csidev->ctrl_handler;
> + int ret;
> +
> + v4l2_ctrl_handler_init(handler, 1);
> +
> + /* Use driver mutex lock for the ctrl lock */
> + handler->lock = &csidev->lock;
> +
> + v4l2_ctrl_new_std_menu_items(handler, &dwc_csi_ctrl_ops,
> + V4L2_CID_TEST_PATTERN,
> + ARRAY_SIZE(test_pattern_menu) - 1,
> + 0, 0, test_pattern_menu);
> +
> + if (handler->error) {
> + ret = handler->error;
> + v4l2_ctrl_handler_free(handler);
> + return ret;
> + }
> +
> + csidev->sd.ctrl_handler = handler;
> + return 0;
> +}
> +
> +static void dwc_csi_controls_cleanup(void *data)
> +{
> + struct dwc_csi_device *csidev = data;
> +
> + v4l2_ctrl_handler_free(&csidev->ctrl_handler);
> +}
> +
> +/*
> + * Suspend/resume
> + */
> +
> +static int dwc_csi_system_suspend(struct device *dev)
> +{
> + return pm_runtime_force_suspend(dev);
> +}
> +
> +static int dwc_csi_system_resume(struct device *dev)
> +{
> + int ret;
> +
> + ret = pm_runtime_force_resume(dev);
> + if (ret < 0) {
> + dev_err(dev, "force resume %s failed!\n", dev_name(dev));
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int dwc_csi_runtime_suspend(struct device *dev)
> +{
> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +
> + clk_bulk_disable_unprepare(csidev->num_clks, csidev->clks);
> +
> + return 0;
> +}
> +
> +static int dwc_csi_runtime_resume(struct device *dev)
> +{
> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +
> + return clk_bulk_prepare_enable(csidev->num_clks, csidev->clks);
> +}
> +
> +static const struct dev_pm_ops dwc_csi_device_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(dwc_csi_system_suspend, dwc_csi_system_resume)
> + SET_RUNTIME_PM_OPS(dwc_csi_runtime_suspend, dwc_csi_runtime_resume, NULL)
> +};
> +
> +static irqreturn_t dwc_csi_irq_handler(int irq, void *priv)
> +{
> + struct dwc_csi_device *csidev = priv;
> + u32 status;
> + int i;
> +
> + /* Hardware auto clean after read */
> + status = dwc_csi_read(csidev, CSI2RX_INT_ST_MAIN);
> +
> + if (status & DWC_EVENT_MASK) {
> + for (i = 0; i < DWC_NUM_EVENTS; ++i) {
> + struct dwc_csi_event *event = &csidev->events[i];
> +
> + if (status & event->mask)
> + event->counter++;
> + }
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static inline void dwc_csi_param_init(struct dwc_csi_device *csidev)
> +{
> + csidev->csi_fmt = &dwc_csi_formats[0];
> +}
> +
> +static int dwc_csi_subdev_init(struct dwc_csi_device *csidev)
> +{
> + struct v4l2_subdev *sd = &csidev->sd;
> + int ret;
> +
> + v4l2_subdev_init(sd, &dwc_csi_subdev_ops);
> + sd->owner = THIS_MODULE;
> + snprintf(sd->name, sizeof(sd->name), "csidev-%s", dev_name(csidev->dev));
> + sd->internal_ops = &dwc_csi_internal_ops;
> +
> + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> + V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_STREAMS;
> + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + sd->entity.ops = &dwc_csi_entity_ops;
> +
> + sd->dev = csidev->dev;
> +
> + csidev->pads[DWC_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> + csidev->pads[DWC_CSI2RX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&csidev->sd.entity, DWC_CSI2RX_PADS_NUM,
> + csidev->pads);
> + if (ret) {
> + dev_err(csidev->dev, "Failed to init pads\n");
> + return ret;
> + }
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret)
> + media_entity_cleanup(&sd->entity);
> +
> + return ret;
> +}
> +
> +static void dwc_csi_subdev_cleanup(void *data)
> +{
> + struct dwc_csi_device *csidev = data;
> +
> + v4l2_subdev_cleanup(&csidev->sd);
> + media_entity_cleanup(&csidev->sd.entity);
> +}
> +
> +static int dwc_csi_device_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct dwc_csi_device *csidev;
> + int irq;
> + int ret;
> +
> + csidev = devm_kzalloc(dev, sizeof(*csidev), GFP_KERNEL);
> + if (!csidev)
> + return -ENOMEM;
> +
> + mutex_init(&csidev->lock);
> +
> + csidev->dev = dev;
> + memcpy(csidev->events, dwc_events, sizeof(dwc_events));
> +
> + csidev->regs = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(csidev->regs)) {
> + dev_err(dev, "Failed to get DWC csi2 register map\n");
> + return PTR_ERR(csidev->regs);
> + }
> +
> + csidev->phy = devm_phy_get(dev, "rx");
> + if (IS_ERR(csidev->phy))
> + return dev_err_probe(dev, PTR_ERR(csidev->phy),
> + "Failed to get DPHY Rx\n");
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return dev_err_probe(dev, irq, "Failed to get IRQ\n");
> +
> + ret = devm_request_irq(dev, irq, dwc_csi_irq_handler, 0,
> + dev_name(dev), csidev);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to request IRQ\n");
> +
> + csidev->num_clks = devm_clk_bulk_get_all(dev, &csidev->clks);
> +
> + dwc_csi_param_init(csidev);
> +
> + ret = dwc_csi_subdev_init(csidev);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to initialize subdev\n");
> +
> + ret = devm_add_action_or_reset(dev, dwc_csi_subdev_cleanup, csidev);
> + if (ret)
> + return ret;
> +
> + ret = dwc_csi_controls_init(csidev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to initialize controls\n");
> +
> + ret = devm_add_action_or_reset(dev, dwc_csi_controls_cleanup, csidev);
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, &csidev->sd);
> +
> + ret = dwc_csi_async_register(csidev);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Async register failed\n");
> +
> + pm_runtime_enable(dev);
> +
> + return 0;
> +}
> +
> +static void dwc_csi_device_remove(struct platform_device *pdev)
> +{
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dwc_csi_device *csidev = sd_to_dwc_csi_device(sd);
> +
> + v4l2_async_nf_unregister(&csidev->notifier);
> + v4l2_async_nf_cleanup(&csidev->notifier);
> + v4l2_async_unregister_subdev(&csidev->sd);
> +
> + pm_runtime_disable(&pdev->dev);
> +
> + fwnode_handle_put(csidev->sd.fwnode);
> + mutex_destroy(&csidev->lock);
> +
> + pm_runtime_set_suspended(&pdev->dev);
> +}
> +
> +static const struct of_device_id dwc_csi_device_of_match[] = {
> + { .compatible = "snps,dw-mipi-csi2-v150" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, dwc_csi_device_of_match);
> +
> +static struct platform_driver dwc_csi_device_driver = {
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "dwc-mipi-csi2",
> + .of_match_table = dwc_csi_device_of_match,
> + .pm = &dwc_csi_device_pm_ops,
> + },
> + .probe = dwc_csi_device_probe,
> + .remove = dwc_csi_device_remove,
> +};
> +
> +module_platform_driver(dwc_csi_device_driver);
> +
> +MODULE_DESCRIPTION("DesignWare Core MIPI CSI2 driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform: dwc-mipi-csi2");
> +MODULE_AUTHOR("NXP Semiconductor, Inc.");
>
>
--
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
http://www.tq-group.com/
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver
2025-07-01 22:06 ` [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver Frank Li
2025-07-02 6:04 ` Alexander Stein
@ 2025-07-02 6:35 ` Krzysztof Kozlowski
2025-07-02 9:38 ` Laurent Pinchart
2 siblings, 0 replies; 20+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-02 6:35 UTC (permalink / raw)
To: Frank Li, Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Guoniu.zhou
On 02/07/2025 00:06, Frank Li wrote:
> +
> +static const struct of_device_id dwc_csi_device_of_match[] = {
> + { .compatible = "snps,dw-mipi-csi2-v150" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, dwc_csi_device_of_match);
> +
> +static struct platform_driver dwc_csi_device_driver = {
> + .driver = {
> + .owner = THIS_MODULE,
That's 12 or 15 year old downstream code. Drop.
> + .name = "dwc-mipi-csi2",
> + .of_match_table = dwc_csi_device_of_match,
> + .pm = &dwc_csi_device_pm_ops,
> + },
> + .probe = dwc_csi_device_probe,
> + .remove = dwc_csi_device_remove,
> +};
> +
> +module_platform_driver(dwc_csi_device_driver);
> +
> +MODULE_DESCRIPTION("DesignWare Core MIPI CSI2 driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform: dwc-mipi-csi2");
No, drop.
> +MODULE_AUTHOR("NXP Semiconductor, Inc.");
>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/7] dt-bindings: media: add DW MIPI CSI-2 Host support
2025-07-01 22:06 ` [PATCH 1/7] dt-bindings: media: add DW MIPI CSI-2 Host support Frank Li
@ 2025-07-02 6:38 ` Krzysztof Kozlowski
0 siblings, 0 replies; 20+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-02 6:38 UTC (permalink / raw)
To: Frank Li, Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy, Luis Oliveira
On 02/07/2025 00:06, Frank Li wrote:
> From: Eugen Hristev <eugen.hristev@linaro.org>
Linaro?
>
> Add bindings for Synopsys DesignWare MIPI CSI-2 host, which used at i.MX93
> and i.MX95 platform.
>
> Signed-off-by: Luis Oliveira <lolivei@synopsys.com>
> Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
Microchip?
Sorry, these two must match.
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> This is continue previous thread
> https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20221216143717.1002015-2-eugen.hristev@microchip.com/#3023663
>
> change in v1 (compared to previous post)
> - add reg-names
> - remove clk surfix
> - add imx93,dw-csi compatible string
> - add dphys subnode name
> - use compatible string snps,dw-mipi-csi2-v150 to avoid use general fallback
> compatible string.
> ---
> .../bindings/media/snps,dw-mipi-csi2-v150.yaml | 161 +++++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 162 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml b/Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml
> new file mode 100644
> index 0000000000000..2a93bd72498f8
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/snps,dw-mipi-csi2-v150.yaml
> @@ -0,0 +1,161 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/snps,dw-mipi-csi2-v150.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Synopsys DesignWare CSI-2 Host controller (csi2host)
> +
> +maintainers:
> + - Frank Li <Frank.Li@nxp.com>
> + - Eugen Hristev <eugen.hristev@microchip.com>
For sure does not work. Also, needs some sort of Ack from Eugen now.
> +
> +description:
> + CSI2HOST is used to receive image coming from an MIPI CSI-2 compatible
> + camera. It will convert the incoming CSI-2 stream into a dedicated
> + interface called the Synopsys IDI (Image Data Interface).
> + This interface is a 32-bit SoC internal only, and can be assimilated
> + with a CSI-2 interface.
> +
> +properties:
> + compatible:
> + oneOf:
> + - items:
> + - enum:
> + - fsl,imx93-mipi-csi2
> + - const: snps,dw-mipi-csi2-v150
> + - const: snps,dw-mipi-csi2-v150
Same comment as before, you need SoC compatible. Drop the last one. You
cannot use DW in a DW soc, can you?
> +
> + reg:
> + items:
> + - description: MIPI CSI-2 core register
> +
> + reg-names:
> + items:
> + - const: core
> +
> + clocks:
> + maxItems: 2
> +
> + clock-names:
> + items:
> + - const: per
> + - const: pixel
> +
> + phys:
> + maxItems: 1
> + description: MIPI D-PHY
> +
> + phy-names:
> + items:
> + - const: rx
> +
> + resets:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + power-domains:
> + maxItems: 1
> +
> + ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> +
> + properties:
> + port@0:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + unevaluatedProperties: false
> + description:
> + Input port node, single endpoint describing the input port.
> +
> + properties:
> + endpoint:
> + $ref: video-interfaces.yaml#
> + unevaluatedProperties: false
> + description: Endpoint connected to input device
> +
> + properties:
> + bus-type:
> + const: 4
> +
> + data-lanes:
> + minItems: 1
> + maxItems: 4
> + items:
> + maximum: 4
> +
> + clock-lanes:
> + maxItems: 1
> +
> + remote-endpoint: true
> +
> + port@1:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + unevaluatedProperties: false
> + description:
> + Output port node, single endpoint describing the output port.
> +
> + properties:
> + endpoint:
> + unevaluatedProperties: false
> + $ref: video-interfaces.yaml#
> + description: Endpoint connected to output device
> +
> + properties:
> + bus-type:
> + const: 4
> +
> + remote-endpoint: true
> +
> + required:
> + - port@0
> + - port@1
> +
> +additionalProperties: false
This goes after required: block.
> +
>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support
2025-07-01 22:06 ` [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support Frank Li
@ 2025-07-02 6:40 ` Krzysztof Kozlowski
2025-07-02 6:42 ` Krzysztof Kozlowski
1 sibling, 0 replies; 20+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-02 6:40 UTC (permalink / raw)
To: Frank Li, Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy
On 02/07/2025 00:06, Frank Li wrote:
> Add child node dphy-rx, which export phys interface to MIPI CSI2
> controller.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> imx media blk ctrl combined many small glue logic registers for display
> , camera, power, clk and reset.
>
> There are some discussion at
> https://lore.kernel.org/all/4414669.ejJDZkT8p0@steina-w/
> to define whole schema for this.
>
> But there are some difficults to cover whole once.
>
> Plan use two steps to complete it
> - add all camera related property and subnode
> - add all display related property and subnode
> ---
> .../bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml | 28 ++++++++++++++++++++++
> 1 file changed, 28 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml b/Documentation/devicetree/bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml
> index b3554e7f9e76d..708cd00f2b896 100644
> --- a/Documentation/devicetree/bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml
> +++ b/Documentation/devicetree/bindings/soc/imx/fsl,imx93-media-blk-ctrl.yaml
> @@ -46,6 +46,34 @@ properties:
> - const: csi
> - const: dsi
>
> + dphy-rx:
> + type: object
> + properties:
> + compatible:
> + enum:
> + - fsl,imx93-dphy-rx
> +
> + clocks:
> + maxItems: 1
> +
> + clock-names:
> + items:
> + - const: cfg
> +
> + '#phy-cells':
> + const: 0
> +
> + power-domains:
> + maxItems: 1
This looks wrong. How a parent device can take one power domain and
child take a different one? If that's the same, then it is redundant,
because it is part of parent's power domain.
I have also some doubts about clock. Rarely syscon children have their
own resources which would make this entire node redundant.
Provide complete picture on this in commit msg.
> +
> + required:
> + - compatible
> + - clocks
> + - clock-names
> + - '#phy-cells'
> +
> + additionalProperties: false
Please place it after type:.
You also need to update the example.
> +
> required:
> - compatible
> - reg
>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support
2025-07-01 22:06 ` [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support Frank Li
2025-07-02 6:40 ` Krzysztof Kozlowski
@ 2025-07-02 6:42 ` Krzysztof Kozlowski
2025-07-02 18:02 ` Frank Li
1 sibling, 1 reply; 20+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-02 6:42 UTC (permalink / raw)
To: Frank Li, Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media, devicetree, linux-kernel, imx, linux-arm-kernel,
linux-phy
On 02/07/2025 00:06, Frank Li wrote:
> Add child node dphy-rx, which export phys interface to MIPI CSI2
> controller.
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> imx media blk ctrl combined many small glue logic registers for display
> , camera, power, clk and reset.
>
> There are some discussion at
> https://lore.kernel.org/all/4414669.ejJDZkT8p0@steina-w/
> to define whole schema for this.
>
> But there are some difficults to cover whole once.
>
> Plan use two steps to complete it
> - add all camera related property and subnode
> - add all display related property and subnode
And what was the comment there?
"Binding is supposed to be complete. We have several examples when people
added children one-by-one, everytime with different reasoning about
child addressing."
NAK. At least try to read previous discussion.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver
2025-07-01 22:06 ` [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver Frank Li
2025-07-02 6:04 ` Alexander Stein
2025-07-02 6:35 ` Krzysztof Kozlowski
@ 2025-07-02 9:38 ` Laurent Pinchart
2025-07-02 15:55 ` Frank Li
2 siblings, 1 reply; 20+ messages in thread
From: Laurent Pinchart @ 2025-07-02 9:38 UTC (permalink / raw)
To: Frank Li
Cc: Rui Miguel Silva, Martin Kepplinger, Purism Kernel Team,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Eugen Hristev, Shawn Guo, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Peng Fan, Alice Yuan,
Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel, linux-media,
devicetree, linux-kernel, imx, linux-arm-kernel, linux-phy,
Guoniu.zhou
Hi Frank,
Thank you for the patch.
On Tue, Jul 01, 2025 at 06:06:10PM -0400, Frank Li wrote:
> From: "Guoniu.zhou" <guoniu.zhou@nxp.com>
>
> Add V4L2 subdev driver for DesignWare MIPI CSI2 controller.
This seems to be at least the third instance of a driver for the CSI-2
receiver, the first two being
drivers/media/platform/raspberrypi/rp1-cfe/dphy.c and the second one
drivers/media/platform/renesas/rcar-csi2.c (the latter seems to support
multiple CSI-2 receivers). drivers/staging/media/imx/imx6-mipi-csi2.c
seem related too, likely for an old version of the IP.
Could we please try to avoid code duplication ?
> Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> MAINTAINERS | 1 +
> drivers/media/platform/nxp/Kconfig | 11 +
> drivers/media/platform/nxp/Makefile | 1 +
> drivers/media/platform/nxp/dwc-mipi-csi2.c | 1675 ++++++++++++++++++++++++++++
This should go to drivers/media/platform/synopsys/
> 4 files changed, 1688 insertions(+)
[snip]
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver
2025-07-02 9:38 ` Laurent Pinchart
@ 2025-07-02 15:55 ` Frank Li
2025-07-04 2:04 ` Frank Li
0 siblings, 1 reply; 20+ messages in thread
From: Frank Li @ 2025-07-02 15:55 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Rui Miguel Silva, Martin Kepplinger, Purism Kernel Team,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Eugen Hristev, Shawn Guo, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Peng Fan, Alice Yuan,
Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel, linux-media,
devicetree, linux-kernel, imx, linux-arm-kernel, linux-phy,
Guoniu.zhou
On Wed, Jul 02, 2025 at 12:38:06PM +0300, Laurent Pinchart wrote:
> Hi Frank,
>
> Thank you for the patch.
>
> On Tue, Jul 01, 2025 at 06:06:10PM -0400, Frank Li wrote:
> > From: "Guoniu.zhou" <guoniu.zhou@nxp.com>
> >
> > Add V4L2 subdev driver for DesignWare MIPI CSI2 controller.
>
> This seems to be at least the third instance of a driver for the CSI-2
> receiver, the first two being
when I read spec, I known it should be used at other SoC. But I just jump
to into this area, not easy to find who use it easily.
> drivers/media/platform/raspberrypi/rp1-cfe/dphy.c and the second one
I think this one is not good abstraction. it should be phy driver, which
use "test" interface to community MIPI phy. I think it'd better create
a bus driver, like test_if, DPHY should be child devices of this test_if.
like
csi2@000 {
...
phys = <&dphy>;
dphy: dphy {
compatible = "...";
}
}
The tough problem is that dwc phy have one kind of combo phy, which
under both dsi and csi controller's test_if.
CSI need config such combophy by DSI test_if. I still have not idea
how to descript this type hardware yet.
> drivers/media/platform/renesas/rcar-csi2.c (the latter seems to support
> multiple CSI-2 receivers).
This is one mixed PHY and controller to one drivers, which is not problem
when use one big MMIO space.
We can create dwc mipi csi common library, let each vendor driver to link
it (dwc pci/usb did this). The issue is that I have not their hardware to
test it.
> drivers/staging/media/imx/imx6-mipi-csi2.c
> seem related too, likely for an old version of the IP.
After we create common dw mipi csi library, we can move this to there.
Frank
>
> Could we please try to avoid code duplication ?
>
> > Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
> > Signed-off-by: Frank Li <Frank.Li@nxp.com>
> > ---
> > MAINTAINERS | 1 +
> > drivers/media/platform/nxp/Kconfig | 11 +
> > drivers/media/platform/nxp/Makefile | 1 +
> > drivers/media/platform/nxp/dwc-mipi-csi2.c | 1675 ++++++++++++++++++++++++++++
>
> This should go to drivers/media/platform/synopsys/
>
> > 4 files changed, 1688 insertions(+)
>
> [snip]
>
> --
> Regards,
>
> Laurent Pinchart
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support
2025-07-02 6:42 ` Krzysztof Kozlowski
@ 2025-07-02 18:02 ` Frank Li
2025-07-02 20:12 ` Krzysztof Kozlowski
0 siblings, 1 reply; 20+ messages in thread
From: Frank Li @ 2025-07-02 18:02 UTC (permalink / raw)
To: Krzysztof Kozlowski, Rui Miguel Silva, Laurent Pinchart,
Martin Kepplinger, Purism Kernel Team, Mauro Carvalho Chehab,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Eugen Hristev,
Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Peng Fan, Alice Yuan, Vinod Koul, Kishon Vijay Abraham I,
Philipp Zabel
Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, imx@lists.linux.dev,
linux-arm-kernel@lists.infradead.org,
linux-phy@lists.infradead.org
>
> On 02/07/2025 00:06, Frank Li wrote:
> > Add child node dphy-rx, which export phys interface to MIPI CSI2
> > controller.
> >
> > Signed-off-by: Frank Li <Frank.Li@nxp.com>
> > ---
> > imx media blk ctrl combined many small glue logic registers for display
> > , camera, power, clk and reset.
> >
> > There are some discussion at
> >
> https://lore.k/
> ernel.org%2Fall%2F4414669.ejJDZkT8p0%40steina-
> w%2F&data=05%7C02%7CFrank.Li%40nxp.com%7C7ade0a318b37496d628
> 608ddb93398af%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C
> 638870353425054747%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGk
> iOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIj
> oyfQ%3D%3D%7C0%7C%7C%7C&sdata=1gHgVYaxpYxqwBoJVSRJKzbtG3WY
> E%2Beb1BqkaGEctUA%3D&reserved=0
Sorry, I have to use stupid outlook reply this email because my git send email
always report failure, not sure if company block it. I am trying to fix it.
> > to define whole schema for this.
> >
> > But there are some difficults to cover whole once.
> >
> > Plan use two steps to complete it
> > - add all camera related property and subnode
> > - add all display related property and subnode
> And what was the comment there?
>
> "Binding is supposed to be complete. We have several examples when people
> added children one-by-one, everytime with different reasoning about
> child addressing."
>
> NAK. At least try to read previous discussion.
I read previous your comments, otherwise, I can't put this in cover letter.
"to define whole schema for this".
I understand what your point. But I met some difficult and want to discuss
with you and provided implement path for this.
unlike other individual ip (like UART), there are clearly abstraction for
reg clock ...
CSR bindle some miscellaneous control for difference IPs. Some is easy to
abstract, such reset, clock ... Some is not. It is hard to ensure full
subnodes with good, workable abstraction to for all devices, which used
this module once.
For example: driver need touch a bit of this CSR, but we missed at begin.
We need find new interfaces, some time it is disaster for whole subnode
binding. We don't want to wrong use a reset interface to work around driver
need touch a bits of CSR.
So I try to upstream "confidence" part gradually.
Frank
>
> Best regards,
> Krzysztof
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support
2025-07-02 18:02 ` Frank Li
@ 2025-07-02 20:12 ` Krzysztof Kozlowski
0 siblings, 0 replies; 20+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-02 20:12 UTC (permalink / raw)
To: Frank Li, Rui Miguel Silva, Laurent Pinchart, Martin Kepplinger,
Purism Kernel Team, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Eugen Hristev, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Peng Fan,
Alice Yuan, Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel
Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, imx@lists.linux.dev,
linux-arm-kernel@lists.infradead.org,
linux-phy@lists.infradead.org
On 02/07/2025 20:02, Frank Li wrote:
> For example: driver need touch a bit of this CSR, but we missed at begin.
> We need find new interfaces, some time it is disaster for whole subnode
> binding. We don't want to wrong use a reset interface to work around driver
> need touch a bits of CSR.
>
> So I try to upstream "confidence" part gradually.
No, you cannot, because you add incompatible style of children. By
adding piece by piece, without looking at big picture, you add different
solutions. And that was one of the points before.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver
2025-07-02 15:55 ` Frank Li
@ 2025-07-04 2:04 ` Frank Li
0 siblings, 0 replies; 20+ messages in thread
From: Frank Li @ 2025-07-04 2:04 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Rui Miguel Silva, Martin Kepplinger, Purism Kernel Team,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Eugen Hristev, Shawn Guo, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Peng Fan, Alice Yuan,
Vinod Koul, Kishon Vijay Abraham I, Philipp Zabel, linux-media,
devicetree, linux-kernel, imx, linux-arm-kernel, linux-phy,
Guoniu.zhou
On Wed, Jul 02, 2025 at 11:55:35AM -0400, Frank Li wrote:
> On Wed, Jul 02, 2025 at 12:38:06PM +0300, Laurent Pinchart wrote:
> > Hi Frank,
> >
> > Thank you for the patch.
> >
> > On Tue, Jul 01, 2025 at 06:06:10PM -0400, Frank Li wrote:
> > > From: "Guoniu.zhou" <guoniu.zhou@nxp.com>
> > >
> > > Add V4L2 subdev driver for DesignWare MIPI CSI2 controller.
> >
> > This seems to be at least the third instance of a driver for the CSI-2
> > receiver, the first two being
>
> when I read spec, I known it should be used at other SoC. But I just jump
> to into this area, not easy to find who use it easily.
>
> > drivers/media/platform/raspberrypi/rp1-cfe/dphy.c and the second one
>
> I think this one is not good abstraction. it should be phy driver, which
> use "test" interface to community MIPI phy. I think it'd better create
> a bus driver, like test_if, DPHY should be child devices of this test_if.
>
> like
> csi2@000 {
> ...
> phys = <&dphy>;
>
> dphy: dphy {
> compatible = "...";
> }
> }
>
> The tough problem is that dwc phy have one kind of combo phy, which
> under both dsi and csi controller's test_if.
>
> CSI need config such combophy by DSI test_if. I still have not idea
> how to descript this type hardware yet.
>
> > drivers/media/platform/renesas/rcar-csi2.c (the latter seems to support
> > multiple CSI-2 receivers).
>
Are you sure it is use dwc IP? I check regiser layout only few is the same
#define V4H_N_LANES_REG 0x0004
#define V4H_CSI2_RESETN_REG 0x0008
#define V4H_PHY_MODE_REG 0x001c
#define V4H_PHY_MODE_DPHY 0
#define V4H_PHY_MODE_CPHY 1
#define V4H_PHY_SHUTDOWNZ_REG 0x0040
#define V4H_DPHY_RSTZ_REG 0x0044
Anything I missed?
Frank
> This is one mixed PHY and controller to one drivers, which is not problem
> when use one big MMIO space.
>
> We can create dwc mipi csi common library, let each vendor driver to link
> it (dwc pci/usb did this). The issue is that I have not their hardware to
> test it.
>
> > drivers/staging/media/imx/imx6-mipi-csi2.c
> > seem related too, likely for an old version of the IP.
>
> After we create common dw mipi csi library, we can move this to there.
>
> Frank
>
> >
> > Could we please try to avoid code duplication ?
> >
> > > Signed-off-by: Guoniu.zhou <guoniu.zhou@nxp.com>
> > > Signed-off-by: Frank Li <Frank.Li@nxp.com>
> > > ---
> > > MAINTAINERS | 1 +
> > > drivers/media/platform/nxp/Kconfig | 11 +
> > > drivers/media/platform/nxp/Makefile | 1 +
> > > drivers/media/platform/nxp/dwc-mipi-csi2.c | 1675 ++++++++++++++++++++++++++++
> >
> > This should go to drivers/media/platform/synopsys/
> >
> > > 4 files changed, 1688 insertions(+)
> >
> > [snip]
> >
> > --
> > Regards,
> >
> > Laurent Pinchart
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2025-07-04 2:05 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-01 22:06 [PATCH 0/7] media: add imx93 mipi/controller csi support Frank Li
2025-07-01 22:06 ` [PATCH 1/7] dt-bindings: media: add DW MIPI CSI-2 Host support Frank Li
2025-07-02 6:38 ` Krzysztof Kozlowski
2025-07-01 22:06 ` [PATCH 2/7] dt-bindings: soc: imx-blk-ctrl: add MIPI CSI2 dphy support Frank Li
2025-07-02 6:40 ` Krzysztof Kozlowski
2025-07-02 6:42 ` Krzysztof Kozlowski
2025-07-02 18:02 ` Frank Li
2025-07-02 20:12 ` Krzysztof Kozlowski
2025-07-01 22:06 ` [PATCH 3/7] pmdomain: imx93-blk-ctrl: populate child devices Frank Li
2025-07-02 5:15 ` Alexander Stein
2025-07-01 22:06 ` [PATCH 4/7] phy: freescale: add imx93 MIPI CSI2 DPHY support Frank Li
2025-07-02 5:50 ` Alexander Stein
2025-07-01 22:06 ` [PATCH 5/7] media: nxp: add DesignWare MIPI CSI2 controller driver Frank Li
2025-07-02 6:04 ` Alexander Stein
2025-07-02 6:35 ` Krzysztof Kozlowski
2025-07-02 9:38 ` Laurent Pinchart
2025-07-02 15:55 ` Frank Li
2025-07-04 2:04 ` Frank Li
2025-07-01 22:06 ` [PATCH NOT MERGE 6/7] arm64: dts: imx93-11x11-evk: add camera related nodes Frank Li
2025-07-01 22:06 ` [PATCH NOT MERGE 7/7] media: i2c: add AP1302 driver from community Frank Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).