* [PATCH v7 0/5] Add Mediatek ISP3.0
@ 2024-11-21 8:53 Julien Stephan
2024-11-21 8:53 ` [PATCH v7 1/5] dt-bindings: media: add mediatek ISP3.0 sensor interface Julien Stephan
` (4 more replies)
0 siblings, 5 replies; 39+ messages in thread
From: Julien Stephan @ 2024-11-21 8:53 UTC (permalink / raw)
To: Laurent Pinchart, Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Julien Stephan, Louis Kuo, Phi-Bang Nguyen,
Florian Sylvestre, Paul Elder
This series adds the support of the Mediatek ISP3.0 found on some
Mediatek SoCs such as the mt8365. The driver is divided into 2 parts:
* SENINF: the sensor interface
* CAMSV: this driver provides a path to bypass the SoC ISP so that image
data coming from the SENINF can go directly into memory without any
image processing. This allows the use of an external ISP or camera
sensor directly.
The SENINF driver is based on previous work done by Louis Kuo available
as an RFC here: https://lore.kernel.org/all/20200708104023.3225-1-louis.kuo@mediatek.com/
Changes in v7:
- fix several comments from Laurent Pinchart and CK about style issues,
such as: sort Kconfig and Makefile alphabetically, remove unneeded headers,
use 80 char limits ...
- add back I/O accessors around readl/writel
- use enable_streams/disable_streams instead of s_stream
- use v4l2_subdev_init_finalize and don't store active format
- remove mtk_camsv30_regs.h file to merge it inside mtk_camsv30_hw.c
- adding reviewed-by tag from robh and angelo
- implement .has_pad_interdep callback to fix multistream error
- fix mtk_seninf_get_clk_divider to give the correct pad number. This
caused an issue for multi camera
- use hardware FBC (framce buffer control) instead of dummy buffer to
deal with underrruns
- simplify directory architecture and remove isp_30, camsv and seninf
directories
- Link to v6: https://lore.kernel.org/r/20240729-add-mtk-isp-3-0-support-v6-0-c374c9e0c672@baylibre.com
Changes in v6:
- remove unneeded "link" tag from commits
bindings:
- remove labels from example node
- remove complexity for phy and phy-name properties
driver:
- fix some comments from CK :
- remove unneeded variables
- rename irqlock to buf_list_lock for clarity
- remove unneeded lock/unlock around hw_enable/hw_disable
- Link to v5: https://lore.kernel.org/r/20240704-add-mtk-isp-3-0-support-v5-0-bfccccc5ec21@baylibre.com
Changes on v5:
drivers:
- rebase on 6.10-rc1
- fix various comments from all reviews (mostly style issues and minor
code refactor)
- add a function to calculate the clock divider for the master sensor
clock: NOTE: setting this register seems to have no effect at all,
currently checking with mediatek apps engineer (OOO until 17/04)
bindings:
- camsv: update description
- seninf: fix phy definition and example indentation
- use generic name for node example
dts:
- sort nodes by addresses
- use lower case for hexadecimal
Changes in v4:
- fix suspend/resume deadlock
- fix various locking issues reported by Laurent Pinchart on v3
- run LOCKDEP
- add missing include reported by kernel-test-robot for non mediatek arch and COMPILE_TEST=y
- use atomic poll inside mtk_camsv30_setup
- drop second lane support as it was not used
- remove useless members in structs
- fix media entity initialization
- initialize correct pad for camsv video device
- add isp support in mt8365.dtsi
- rebase on 6.7
Changes in v3:
- fix a lot of formatting issues/coding style issues found in camsv/seninf reported by Angelo on v2
- fix camsv/seninf binding file error reported by Rob
Changes in v2:
- renamed clock `cam_seninf` to `camsys`
- renamed clock `top_mux_seninf` to `top_mux`
- moved phy properties from port nodes to top level
- remove patternProperties
- specify power management dependency in the cover letter description to fix
missing include in dt-binding example
- change '$ref' properties on some endpoint nodes from
'$ref: video-interfaces.yaml#' to '$ref: /schemas/graph.yaml#/$defs/endpoint-base'
where applicable
Best
Julien Stephan
Signed-off-by: Julien Stephan <jstephan@baylibre.com>
---
Julien Stephan (1):
arm64: dts: mediatek: mt8365: Add support for camera
Louis Kuo (2):
dt-bindings: media: add mediatek ISP3.0 sensor interface
media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface
Phi-bang Nguyen (2):
dt-bindings: media: add mediatek ISP3.0 camsv
media: platform: mediatek: isp: add mediatek ISP3.0 camsv
.../bindings/media/mediatek,mt8365-camsv.yaml | 109 ++
.../bindings/media/mediatek,mt8365-seninf.yaml | 259 ++++
MAINTAINERS | 9 +
arch/arm64/boot/dts/mediatek/mt8365.dtsi | 125 ++
drivers/media/platform/mediatek/Kconfig | 1 +
drivers/media/platform/mediatek/Makefile | 1 +
drivers/media/platform/mediatek/isp/Kconfig | 35 +
drivers/media/platform/mediatek/isp/Makefile | 9 +
drivers/media/platform/mediatek/isp/mtk_camsv.c | 275 ++++
drivers/media/platform/mediatek/isp/mtk_camsv.h | 170 ++
.../media/platform/mediatek/isp/mtk_camsv30_hw.c | 539 +++++++
.../media/platform/mediatek/isp/mtk_camsv_video.c | 701 +++++++++
drivers/media/platform/mediatek/isp/mtk_seninf.c | 1636 ++++++++++++++++++++
.../media/platform/mediatek/isp/mtk_seninf_reg.h | 114 ++
14 files changed, 3983 insertions(+)
---
base-commit: 00873e6fe91b77e9bbc82012fe6103080066fbfc
change-id: 20240704-add-mtk-isp-3-0-support-a08a978cac36
Best regards,
--
Julien Stephan <jstephan@baylibre.com>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v7 1/5] dt-bindings: media: add mediatek ISP3.0 sensor interface
2024-11-21 8:53 [PATCH v7 0/5] Add Mediatek ISP3.0 Julien Stephan
@ 2024-11-21 8:53 ` Julien Stephan
2024-11-21 8:53 ` [PATCH v7 2/5] dt-bindings: media: add mediatek ISP3.0 camsv Julien Stephan
` (3 subsequent siblings)
4 siblings, 0 replies; 39+ messages in thread
From: Julien Stephan @ 2024-11-21 8:53 UTC (permalink / raw)
To: Laurent Pinchart, Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Julien Stephan, Louis Kuo, Phi-Bang Nguyen
From: Louis Kuo <louis.kuo@mediatek.com>
This adds the bindings, for the mediatek ISP3.0 SENINF module embedded in
some Mediatek SoC, such as the mt8365
Signed-off-by: Louis Kuo <louis.kuo@mediatek.com>
Signed-off-by: Phi-Bang Nguyen <pnguyen@baylibre.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Julien Stephan <jstephan@baylibre.com>
---
.../bindings/media/mediatek,mt8365-seninf.yaml | 259 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 266 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml b/Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8bd78ef424acf1ec207b527b4a84b6a535f37593
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml
@@ -0,0 +1,259 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (c) 2023 MediaTek, BayLibre
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/mediatek,mt8365-seninf.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek Sensor Interface 3.0
+
+maintainers:
+ - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ - Julien Stephan <jstephan@baylibre.com>
+ - Andy Hsieh <andy.hsieh@mediatek.com>
+
+description:
+ The ISP3.0 SENINF is the CSI-2 and parallel camera sensor interface found in
+ multiple MediaTek SoCs. It can support up to three physical CSI-2 input ports,
+ configured in DPHY (2 or 4 data lanes) or CPHY depending on the SoC.
+ On the output side, SENINF can be connected either to CAMSV instance or
+ to the internal ISP. CAMSV is used to bypass the internal ISP processing
+ in order to connect either an external ISP, or a sensor (RAW, YUV).
+
+properties:
+ compatible:
+ const: mediatek,mt8365-seninf
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: Seninf camsys clock
+ - description: Seninf top mux clock
+
+ clock-names:
+ items:
+ - const: camsys
+ - const: top_mux
+
+ phys:
+ minItems: 2
+ maxItems: 2
+ description:
+ phandle to the PHYs connected to CSI0/A, CSI1, CSI0B
+
+ phy-names:
+ description:
+ list of PHYs names
+ minItems: 2
+ maxItems: 2
+ items:
+ enum: [ csi0, csi1, csi0b]
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: CSI0 or CSI0A port
+
+ properties:
+ endpoint:
+ $ref: video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ clock-lanes:
+ maxItems: 1
+ data-lanes:
+ minItems: 1
+ maxItems: 4
+
+ port@1:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: CSI1 port
+
+ properties:
+ endpoint:
+ $ref: video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ clock-lanes:
+ maxItems: 1
+ data-lanes:
+ minItems: 1
+ maxItems: 4
+
+ port@2:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: CSI2 port
+
+ properties:
+ endpoint:
+ $ref: video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ clock-lanes:
+ maxItems: 1
+ data-lanes:
+ minItems: 1
+ maxItems: 4
+
+ port@3:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: CSI0B port
+
+ properties:
+ endpoint:
+ $ref: video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ clock-lanes:
+ maxItems: 1
+ data-lanes:
+ minItems: 1
+ maxItems: 2
+
+ port@4:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: connection point for cam0
+
+ port@5:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: connection point for cam1
+
+ port@6:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: connection point for camsv0
+
+ port@7:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: connection point for camsv1
+
+ port@8:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: connection point for camsv2
+
+ port@9:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: connection point for camsv3
+
+ required:
+ - port@0
+ - port@1
+ - port@2
+ - port@3
+ - port@4
+ - port@5
+ - port@6
+ - port@7
+ - port@8
+ - port@9
+
+required:
+ - compatible
+ - interrupts
+ - clocks
+ - clock-names
+ - power-domains
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/mediatek,mt8365-clk.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/phy/phy.h>
+ #include <dt-bindings/power/mediatek,mt8365-power.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ csi@15040000 {
+ compatible = "mediatek,mt8365-seninf";
+ reg = <0 0x15040000 0 0x6000>;
+ interrupts = <GIC_SPI 210 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&camsys CLK_CAM_SENIF>,
+ <&topckgen CLK_TOP_SENIF_SEL>;
+ clock-names = "camsys", "top_mux";
+
+ power-domains = <&spm MT8365_POWER_DOMAIN_CAM>;
+
+ phys = <&mipi_csi0 PHY_TYPE_DPHY>, <&mipi_csi1>;
+ phy-names = "csi0", "csi1";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ seninf_in1: endpoint {
+ clock-lanes = <2>;
+ data-lanes = <1 3 0 4>;
+ remote-endpoint = <&isp1_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ };
+
+ port@2 {
+ reg = <2>;
+ };
+
+ port@3 {
+ reg = <3>;
+ };
+
+ port@4 {
+ reg = <4>;
+ seninf_camsv1_endpoint: endpoint {
+ remote-endpoint = <&camsv1_endpoint>;
+ };
+ };
+
+ port@5 {
+ reg = <5>;
+ seninf_camsv2_endpoint: endpoint {
+ remote-endpoint = <&camsv2_endpoint>;
+ };
+ };
+
+ port@6 {
+ reg = <6>;
+ };
+
+ port@7 {
+ reg = <7>;
+ };
+
+ port@8 {
+ reg = <8>;
+ };
+
+ port@9 {
+ reg = <9>;
+ };
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 4c7f9e37c5653664a11a2cb36aba942830b5a8d1..242c54c88a4a22fc0cbe5c4fc5d7b0d0f84b329e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14565,6 +14565,13 @@ M: Sean Wang <sean.wang@mediatek.com>
S: Maintained
F: drivers/char/hw_random/mtk-rng.c
+MEDIATEK ISP3.0 DRIVER
+M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+M: Julien Stephan <jstephan@baylibre.com>
+M: Andy Hsieh <andy.hsieh@mediatek.com>
+S: Supported
+F: Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml
+
MEDIATEK SMI DRIVER
M: Yong Wu <yong.wu@mediatek.com>
L: linux-mediatek@lists.infradead.org (moderated for non-subscribers)
--
2.47.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH v7 2/5] dt-bindings: media: add mediatek ISP3.0 camsv
2024-11-21 8:53 [PATCH v7 0/5] Add Mediatek ISP3.0 Julien Stephan
2024-11-21 8:53 ` [PATCH v7 1/5] dt-bindings: media: add mediatek ISP3.0 sensor interface Julien Stephan
@ 2024-11-21 8:53 ` Julien Stephan
2024-11-25 11:24 ` Laurent Pinchart
2024-11-21 8:53 ` [PATCH v7 3/5] media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface Julien Stephan
` (2 subsequent siblings)
4 siblings, 1 reply; 39+ messages in thread
From: Julien Stephan @ 2024-11-21 8:53 UTC (permalink / raw)
To: Laurent Pinchart, Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Julien Stephan, Phi-bang Nguyen
From: Phi-bang Nguyen <pnguyen@baylibre.com>
This adds the bindings, for the ISP3.0 camsv module embedded in
some Mediatek SoC, such as the mt8365
Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Julien Stephan <jstephan@baylibre.com>
---
.../bindings/media/mediatek,mt8365-camsv.yaml | 109 +++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 110 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml b/Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fdd07675645917fbcd692606c836efd07e50ac0c
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
@@ -0,0 +1,109 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (c) 2023 MediaTek, BayLibre
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/mediatek,mt8365-camsv.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek CAMSV 3.0
+
+maintainers:
+ - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ - Julien Stephan <jstephan@baylibre.com>
+ - Andy Hsieh <andy.hsieh@mediatek.com>
+
+description:
+ The CAMSV is a video capture device that includes a DMA engine connected to
+ the SENINF CSI-2 receivers. The number of CAMSVs depend on the SoC model.
+
+properties:
+ compatible:
+ const: mediatek,mt8365-camsv
+
+ reg:
+ items:
+ - description: camsv base
+ - description: img0 base
+ - description: tg base
+
+ interrupts:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: cam clock
+ - description: camtg clock
+ - description: camsv clock
+
+ clock-names:
+ items:
+ - const: cam
+ - const: camtg
+ - const: camsv
+
+ iommus:
+ maxItems: 1
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Connection to the SENINF output
+
+ required:
+ - port@0
+
+required:
+ - compatible
+ - interrupts
+ - clocks
+ - clock-names
+ - power-domains
+ - iommus
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/clock/mediatek,mt8365-clk.h>
+ #include <dt-bindings/memory/mediatek,mt8365-larb-port.h>
+ #include <dt-bindings/power/mediatek,mt8365-power.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ camsv@15050000 {
+ compatible = "mediatek,mt8365-camsv";
+ reg = <0 0x15050000 0 0x0040>,
+ <0 0x15050208 0 0x0020>,
+ <0 0x15050400 0 0x0100>;
+ interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&camsys CLK_CAM>,
+ <&camsys CLK_CAMTG>,
+ <&camsys CLK_CAMSV0>;
+ clock-names = "cam", "camtg", "camsv";
+ iommus = <&iommu M4U_PORT_CAM_IMGO>;
+ power-domains = <&spm MT8365_POWER_DOMAIN_CAM>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ camsv1_endpoint: endpoint {
+ remote-endpoint = <&seninf_camsv1_endpoint>;
+ };
+ };
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 242c54c88a4a22fc0cbe5c4fc5d7b0d0f84b329e..6147629405c8d40b00c4755a4ee27a746b26f782 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14570,6 +14570,7 @@ M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
M: Julien Stephan <jstephan@baylibre.com>
M: Andy Hsieh <andy.hsieh@mediatek.com>
S: Supported
+F: Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
F: Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml
MEDIATEK SMI DRIVER
--
2.47.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH v7 3/5] media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface
2024-11-21 8:53 [PATCH v7 0/5] Add Mediatek ISP3.0 Julien Stephan
2024-11-21 8:53 ` [PATCH v7 1/5] dt-bindings: media: add mediatek ISP3.0 sensor interface Julien Stephan
2024-11-21 8:53 ` [PATCH v7 2/5] dt-bindings: media: add mediatek ISP3.0 camsv Julien Stephan
@ 2024-11-21 8:53 ` Julien Stephan
2024-11-25 17:33 ` Laurent Pinchart
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
2024-11-21 8:53 ` [PATCH v7 5/5] arm64: dts: mediatek: mt8365: Add support for camera Julien Stephan
4 siblings, 1 reply; 39+ messages in thread
From: Julien Stephan @ 2024-11-21 8:53 UTC (permalink / raw)
To: Laurent Pinchart, Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Julien Stephan, Louis Kuo, Phi-bang Nguyen,
Florian Sylvestre
From: Louis Kuo <louis.kuo@mediatek.com>
This will add the mediatek ISP3.0 seninf (sensor interface) driver found
on several Mediatek SoCs such as the mt8365.
Then seninf module has 4 physical CSI-2 inputs. Depending on the soc they
may not be all connected.
Signed-off-by: Louis Kuo <louis.kuo@mediatek.com>
Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Co-developed-by: Julien Stephan <jstephan@baylibre.com>
Signed-off-by: Julien Stephan <jstephan@baylibre.com>
---
MAINTAINERS | 1 +
drivers/media/platform/mediatek/Kconfig | 1 +
drivers/media/platform/mediatek/Makefile | 1 +
drivers/media/platform/mediatek/isp/Kconfig | 17 +
drivers/media/platform/mediatek/isp/Makefile | 4 +
drivers/media/platform/mediatek/isp/mtk_seninf.c | 1636 ++++++++++++++++++++
.../media/platform/mediatek/isp/mtk_seninf_reg.h | 114 ++
7 files changed, 1774 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6147629405c8d40b00c4755a4ee27a746b26f782..9654a7f4e21cddb77bb799add56110f5f27f7a79 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14572,6 +14572,7 @@ M: Andy Hsieh <andy.hsieh@mediatek.com>
S: Supported
F: Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
F: Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml
+F: drivers/media/platform/mediatek/isp/*
MEDIATEK SMI DRIVER
M: Yong Wu <yong.wu@mediatek.com>
diff --git a/drivers/media/platform/mediatek/Kconfig b/drivers/media/platform/mediatek/Kconfig
index 84104e2cd02447790ae5c29953a2e82ca4fdd0a7..a405d57013292c515d2a2db4d43aa1ed8cb21f7b 100644
--- a/drivers/media/platform/mediatek/Kconfig
+++ b/drivers/media/platform/mediatek/Kconfig
@@ -2,6 +2,7 @@
comment "Mediatek media platform drivers"
+source "drivers/media/platform/mediatek/isp/Kconfig"
source "drivers/media/platform/mediatek/jpeg/Kconfig"
source "drivers/media/platform/mediatek/mdp/Kconfig"
source "drivers/media/platform/mediatek/vcodec/Kconfig"
diff --git a/drivers/media/platform/mediatek/Makefile b/drivers/media/platform/mediatek/Makefile
index 38e6ba917fe5cdd932aa6c88221c9a7aa5a7705a..2341a0e373a4e30f0caf823ab67098fde96fc071 100644
--- a/drivers/media/platform/mediatek/Makefile
+++ b/drivers/media/platform/mediatek/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
+obj-y += isp/
obj-y += jpeg/
obj-y += mdp/
obj-y += vcodec/
diff --git a/drivers/media/platform/mediatek/isp/Kconfig b/drivers/media/platform/mediatek/isp/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..2a3cef81d15aa12633ade2f3be0bba36b9af62e1
--- /dev/null
+++ b/drivers/media/platform/mediatek/isp/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config MTK_SENINF30
+ tristate "MediaTek ISP3.0 SENINF driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ select MEDIA_CONTROLLER
+ select PHY_MTK_MIPI_CSI_0_5
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
+ default n
+ help
+ This driver provides a MIPI CSI-2 receiver interface to connect
+ an external camera module with MediaTek ISP3.0. It is able to handle
+ multiple cameras at the same time.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mtk-seninf.
diff --git a/drivers/media/platform/mediatek/isp/Makefile b/drivers/media/platform/mediatek/isp/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..375d720f9ed75e2197bb723bdce9bc0472e62842
--- /dev/null
+++ b/drivers/media/platform/mediatek/isp/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+mtk-seninf-objs += mtk_seninf.o
+obj-$(CONFIG_MTK_SENINF30) += mtk-seninf.o
diff --git a/drivers/media/platform/mediatek/isp/mtk_seninf.c b/drivers/media/platform/mediatek/isp/mtk_seninf.c
new file mode 100644
index 0000000000000000000000000000000000000000..3b040f96bb63dc90db7d17c46f920d5597d936db
--- /dev/null
+++ b/drivers/media/platform/mediatek/isp/mtk_seninf.c
@@ -0,0 +1,1636 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/videodev2.h>
+#include <media/media-device.h>
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+#include "mtk_seninf_reg.h"
+
+#define SENINF_TIMESTAMP_STEP 0x67
+#define SENINF_SETTLE_DELAY 0x15
+#define SENINF_HS_TRAIL_PARAMETER 0x8
+
+#define SENINF_MAX_NUM_INPUTS 4
+#define SENINF_MAX_NUM_OUTPUTS 6
+#define SENINF_MAX_NUM_MUXES 6
+#define SENINF_MAX_NUM_PADS (SENINF_MAX_NUM_INPUTS + \
+ SENINF_MAX_NUM_OUTPUTS)
+
+#define SENINF_DEFAULT_BUS_FMT MEDIA_BUS_FMT_SGRBG10_1X10
+#define SENINF_DEFAULT_WIDTH 1920
+#define SENINF_DEFAULT_HEIGHT 1080
+
+#define SENINF_PAD_10BIT 0
+
+#define SENINF_TEST_MODEL 0
+#define SENINF_NORMAL_MODEL 1
+#define SENINF_ALL_ERR_IRQ_EN 0x7f
+#define SENINF_IRQ_CLR_SEL 0x80000000
+
+#define SENINF_MIPI_SENSOR 0x8
+
+#define MTK_CSI_MAX_LANES 4
+
+/* Port number in the device tree. */
+enum mtk_seninf_port {
+ CSI_PORT_0 = 0, /* 4D1C or 2D1C */
+ CSI_PORT_1, /* 4D1C */
+ CSI_PORT_2, /* 4D1C */
+ CSI_PORT_0B, /* 2D1C */
+};
+
+enum mtk_seninf_id {
+ SENINF_1 = 0,
+ SENINF_2 = 1,
+ SENINF_3 = 2,
+ SENINF_5 = 4,
+};
+
+static const u32 port_to_seninf_id[] = {
+ [CSI_PORT_0] = SENINF_1,
+ [CSI_PORT_1] = SENINF_3,
+ [CSI_PORT_2] = SENINF_5,
+ [CSI_PORT_0B] = SENINF_2,
+};
+
+enum mtk_seninf_phy_mode {
+ SENINF_PHY_MODE_NONE,
+ SENINF_PHY_MODE_4D1C,
+ SENINF_PHY_MODE_2D1C,
+};
+
+enum mtk_seninf_format_flag {
+ MTK_SENINF_FORMAT_BAYER = BIT(0),
+ MTK_SENINF_FORMAT_DPCM = BIT(1),
+ MTK_SENINF_FORMAT_JPEG = BIT(2),
+ MTK_SENINF_FORMAT_INPUT_ONLY = BIT(3),
+};
+
+/**
+ * struct mtk_seninf_conf - Model-specific SENINF parameters
+ * @model: Model description
+ * @nb_inputs: Number of SENINF inputs
+ * @nb_muxes: Number of SENINF MUX (FIFO) instances
+ * @nb_outputs: Number of outputs (to CAM and CAMSV instances)
+ */
+struct mtk_seninf_conf {
+ const char *model;
+ u8 nb_inputs;
+ u8 nb_muxes;
+ u8 nb_outputs;
+};
+
+/**
+ * struct mtk_seninf_format_info - Information about media bus formats
+ * @code: V4L2 media bus code
+ * @flags: Flags describing the format, as a combination of MTK_SENINF_FORMAT_*
+ * @bpp: Bits per pixel
+ */
+struct mtk_seninf_format_info {
+ u32 code;
+ u32 flags;
+ u8 bpp;
+};
+
+/**
+ * struct mtk_seninf_input - SENINF input block
+ * @pad: DT port and media entity pad number
+ * @seninf_id: SENINF hardware instance ID
+ * @base: Memory mapped I/O based address
+ * @seninf: Back pointer to the mtk_seninf
+ * @phy: PHY connected to the input
+ * @phy_mode: PHY operation mode (NONE when the input is not connected)
+ * @bus: CSI-2 bus configuration from DT
+ * @source_sd: Source subdev connected to the input
+ */
+struct mtk_seninf_input {
+ enum mtk_seninf_port pad;
+ enum mtk_seninf_id seninf_id;
+ void __iomem *base;
+ struct mtk_seninf *seninf;
+
+ struct phy *phy;
+ enum mtk_seninf_phy_mode phy_mode;
+
+ struct v4l2_mbus_config_mipi_csi2 bus;
+
+ struct v4l2_subdev *source_sd;
+};
+
+/**
+ * struct mtk_seninf_mux - SENINF MUX channel
+ * @pad: DT port and media entity pad number
+ * @mux_id: MUX hardware instance ID
+ * @base: Memory mapped I/O based address
+ * @seninf: Back pointer to the mtk_seninf
+ */
+struct mtk_seninf_mux {
+ unsigned int pad;
+ unsigned int mux_id;
+ void __iomem *base;
+ struct mtk_seninf *seninf;
+};
+
+/**
+ * struct mtk_seninf - Top-level SENINF device
+ * @dev: The (platform) device
+ * @phy: PHYs at the SENINF inputs
+ * @num_clks: Number of clocks in the clks array
+ * @clks: Clocks
+ * @base: Memory mapped I/O base address
+ * @media_dev: Media controller device
+ * @v4l2_dev: V4L2 device
+ * @subdev: V4L2 subdevice
+ * @pads: Media entity pads
+ * @notifier: V4L2 async notifier for source subdevs
+ * @ctrl_handler: V4L2 controls handler
+ * @source_format: Active format on the source pad
+ * @inputs: Array of SENINF inputs
+ * @muxes: Array of MUXes
+ * @conf: Model-specific SENINF parameters
+ * @is_testmode: Whether or not the test pattern generator is enabled
+ */
+struct mtk_seninf {
+ struct device *dev;
+ struct phy *phy[5];
+ unsigned int num_clks;
+ struct clk_bulk_data *clks;
+ void __iomem *base;
+
+ struct media_device media_dev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_subdev subdev;
+ struct media_pad pads[SENINF_MAX_NUM_PADS];
+ struct v4l2_async_notifier notifier;
+ struct v4l2_ctrl_handler ctrl_handler;
+
+ struct mtk_seninf_input inputs[SENINF_MAX_NUM_INPUTS];
+ struct mtk_seninf_mux muxes[SENINF_MAX_NUM_MUXES];
+
+ const struct mtk_seninf_conf *conf;
+
+ bool is_testmode;
+};
+
+inline struct mtk_seninf *sd_to_mtk_seninf(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct mtk_seninf, subdev);
+}
+
+static inline bool mtk_seninf_pad_is_sink(struct mtk_seninf *priv,
+ unsigned int pad)
+{
+ return pad < priv->conf->nb_inputs;
+}
+
+static inline bool mtk_seninf_pad_is_source(struct mtk_seninf *priv,
+ unsigned int pad)
+{
+ return !mtk_seninf_pad_is_sink(priv, pad);
+}
+
+/* -----------------------------------------------------------------------------
+ * Formats
+ */
+
+static const struct mtk_seninf_format_info mtk_seninf_formats[] = {
+ {
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 10,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 12,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR14_1X14,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 14,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG14_1X14,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 14,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG14_1X14,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 14,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB14_1X14,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 14,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR16_1X16,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG16_1X16,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG16_1X16,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB16_1X16,
+ .flags = MTK_SENINF_FORMAT_BAYER,
+ .bpp = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .bpp = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_VYUY8_1X16,
+ .bpp = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_YUYV8_1X16,
+ .bpp = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_YVYU8_1X16,
+ .bpp = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_JPEG_1X8,
+ .flags = MTK_SENINF_FORMAT_JPEG,
+ .bpp = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8,
+ .flags = MTK_SENINF_FORMAT_JPEG,
+ .bpp = 8,
+ },
+ /* Keep the input-only formats last. */
+ {
+ .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
+ .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
+ .bpp = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8,
+ .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
+ .bpp = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8,
+ .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
+ .bpp = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8,
+ .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
+ .bpp = 8,
+ }
+};
+
+static const struct mtk_seninf_format_info *mtk_seninf_format_info(u32 code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(mtk_seninf_formats); ++i) {
+ if (mtk_seninf_formats[i].code == code)
+ return &mtk_seninf_formats[i];
+ }
+
+ return NULL;
+}
+
+static u32 mtk_seninf_read(struct mtk_seninf *priv, u32 reg)
+{
+ return readl(priv->base + reg);
+}
+
+static void mtk_seninf_write(struct mtk_seninf *priv, u32 reg, u32 value)
+{
+ writel(value, priv->base + reg);
+}
+
+static void __mtk_seninf_update(struct mtk_seninf *priv, u32 reg,
+ u32 mask, u32 value)
+{
+ u32 val = mtk_seninf_read(priv, reg);
+
+ writel((val & ~mask) | (value & mask), priv->base + reg);
+}
+
+#define mtk_seninf_update(priv, reg, field, val) \
+ __mtk_seninf_update(priv, reg, reg##_##field, \
+ FIELD_PREP(reg##_##field, val))
+
+static u32 mtk_seninf_inuput_read(struct mtk_seninf_input *input, u32 reg)
+{
+ return readl(input->base + reg);
+}
+
+static void mtk_seninf_input_write(struct mtk_seninf_input *input, u32 reg,
+ u32 value)
+{
+ writel(value, input->base + reg);
+}
+
+static void __mtk_seninf_input_update(struct mtk_seninf_input *input, u32 reg,
+ u32 mask, u32 value)
+{
+ u32 val = mtk_seninf_inuput_read(input, reg);
+
+ mtk_seninf_input_write(input, reg, (val & ~mask) | (value & mask));
+}
+
+#define mtk_seninf_input_update(input, reg, field, val) \
+ __mtk_seninf_input_update(input, reg, reg##_##field, \
+ FIELD_PREP(reg##_##field, val))
+
+static u32 mtk_seninf_mux_read(struct mtk_seninf_mux *mux, u32 reg)
+{
+ return readl(mux->base + reg);
+}
+
+static void mtk_seninf_mux_write(struct mtk_seninf_mux *mux, u32 reg,
+ u32 value)
+{
+ writel(value, mux->base + reg);
+}
+
+static void __mtk_seninf_mux_update(struct mtk_seninf_mux *mux, u32 reg,
+ u32 mask, u32 value)
+{
+ u32 val = mtk_seninf_mux_read(mux, reg);
+
+ mtk_seninf_mux_write(mux, reg, (val & ~mask) | (value & mask));
+}
+
+#define mtk_seninf_mux_update(mux, reg, field, val) \
+ __mtk_seninf_mux_update(mux, reg, reg##_##field, \
+ FIELD_PREP(reg##_##field, val))
+
+/* -----------------------------------------------------------------------------
+ * Hardware Configuration
+ *
+ * The SENINF is the camera sensor interface. On the input side it contains
+ * input channels (also named SENINF), each made of a CSI-2 receiver, an
+ * interface for parallel sensors, and a test pattern generator. The inputs are
+ * routed through a N:M crossbar switch (TOP MUX) to VC/DT filters with a FIFO
+ * (MUX). The MUX are routed to another N:M crossbar switch (CAM MUX), whose
+ * output is then connected to other IP cores.
+ *
+ * +-------------------------------------------------------+
+ * | SENINF |
+ * | |
+ * +-------+ | +----------+ TOP MUX |
+ * | | | | SENINF | |\ CAM MUX |
+ * | D-PHY | ---> | CSI-2 RX | ---> | | +------------+ |\ |
+ * | | | | TPG | -> | | ---> | MUX (FIFO) | ---> | | ---> CAMSV
+ * +-------+ | +----------+ -> | | +------------+ -> | | |
+ * | |/ -> | | |
+ * | |/ |
+ * | |
+ * ... | ... ... --->
+ * | |
+ * | |
+ * +-------------------------------------------------------+
+ *
+ * The number of PHYs, SENINF and MUX differ between SoCs. MT8167 has a single
+ * MUX and thus no output CAM MUX crossbar switch.
+ */
+
+static void mtk_seninf_csi2_setup_phy(struct mtk_seninf *priv)
+{
+ /* CSI0 */
+ if (priv->inputs[CSI_PORT_0].phy) {
+ const struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_0];
+
+ mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
+ DPHY_MODE, 0 /* 4D1C*/);
+ mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
+ CK_SEL_1, input->bus.clock_lane);
+ mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
+ CK_SEL_2, 2);
+ mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
+ PHY_SENINF_LANE_MUX_CSI0_EN, 1);
+ }
+
+ /* CSI1 */
+ if (priv->inputs[CSI_PORT_1].phy) {
+ const struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_1];
+
+ mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
+ DPHY_MODE, 0 /* 4D1C */);
+ mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
+ CK_SEL_1, input->bus.clock_lane);
+ mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
+ PHY_SENINF_LANE_MUX_CSI1_EN, 1);
+ }
+}
+
+static void mtk_seninf_input_setup_csi2_rx(struct mtk_seninf_input *input)
+{
+ unsigned int lanes[MTK_CSI_MAX_LANES] = { };
+ unsigned int i;
+
+ /*
+ * Configure data lane muxing. In 2D1C mode, lanes 0 to 2 correspond to
+ * CSIx[AB]_L{0,1,2}, and in 4D1C lanes 0 to 5 correspond to
+ * CSIxA_L{0,1,2}, CSIxB_L{0,1,2}.
+ *
+ * The clock lane must be skipped when calculating the index of the
+ * physical data lane. For instance, in 4D1C mode, the sensor clock
+ * lane is typically connected to lane 2 (CSIxA_L2), and the sensor
+ * data lanes 0-3 to lanes 1 (CSIxA_L1), 3 (CSIxB_L0), 0 (CSIxA_L0) and
+ * 4 (CSIxB_L1). The when skipping the clock lane, the data lane
+ * indices become 1, 2, 0 and 3.
+ */
+ for (i = 0; i < input->bus.num_data_lanes; ++i) {
+ lanes[i] = input->bus.data_lanes[i];
+ if (lanes[i] > input->bus.clock_lane)
+ lanes[i]--;
+ }
+
+ mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
+ CSI0_BIST_LN0_MUX, lanes[0]);
+ mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
+ CSI0_BIST_LN1_MUX, lanes[1]);
+ mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
+ CSI0_BIST_LN2_MUX, lanes[2]);
+ mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
+ CSI0_BIST_LN3_MUX, lanes[3]);
+}
+
+static s64 mtk_seninf_get_clk_divider(struct mtk_seninf *priv,
+ int pad_num,
+ u8 bpp, unsigned int num_data_lanes)
+{
+ struct media_entity *entity = &priv->subdev.entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *sd;
+ s64 link_frequency, pixel_clock;
+
+
+ if (!(entity->pads[pad_num].flags & MEDIA_PAD_FL_SINK))
+ return -ENODEV;
+
+ pad = media_pad_remote_pad_first(&entity->pads[pad_num]);
+ if (!pad)
+ return -ENOENT;
+
+ if (!is_media_entity_v4l2_subdev(pad->entity))
+ return -ENOENT;
+
+ sd = media_entity_to_v4l2_subdev(pad->entity);
+ link_frequency = v4l2_get_link_freq(sd->ctrl_handler, bpp,
+ num_data_lanes * 2);
+ pixel_clock = div_u64(link_frequency * 2 * num_data_lanes, bpp);
+ /*
+ * According to datasheet: Sensor master clock = ISP_clock/(CLKCNT +1)
+ * we also have the following constraint:
+ * pixel_clock >= Sensor master clock
+ */
+ return div_u64(clk_get_rate(priv->clks[0].clk), pixel_clock) - 1;
+}
+
+static int mtk_seninf_input_setup_csi2(struct mtk_seninf *priv,
+ struct mtk_seninf_input *input,
+ struct v4l2_subdev_state *state)
+{
+ const struct mtk_seninf_format_info *fmtinfo;
+ const struct v4l2_mbus_framefmt *format;
+ unsigned int num_data_lanes = input->bus.num_data_lanes;
+ unsigned int val = 0;
+ s64 clock_count;
+
+ format = v4l2_subdev_state_get_format(state, input->pad, 0);
+ fmtinfo = mtk_seninf_format_info(format->code);
+
+ /* Configure timestamp */
+ mtk_seninf_input_write(input, SENINF_TG1_TM_STP, SENINF_TIMESTAMP_STEP);
+
+ /* HQ */
+ /*
+ * Configure phase counter. Zero means:
+ * - Sensor master clock: ISP_CLK
+ * - Sensor clock polarity: Rising edge
+ * - Sensor reset deasserted
+ * - Sensor powered up
+ * - Pixel clock inversion disabled
+ * - Sensor master clock polarity disabled
+ * - Phase counter disabled
+ */
+ mtk_seninf_input_write(input, SENINF_TG1_PH_CNT, 0x0);
+
+ clock_count = mtk_seninf_get_clk_divider(priv, input->pad,
+ fmtinfo->bpp,
+ num_data_lanes);
+ if (clock_count < 0)
+ return clock_count;
+
+ clock_count = FIELD_PREP(SENINF_TG1_SEN_CK_CLKCNT, clock_count) | 0x1;
+ mtk_seninf_input_write(input, SENINF_TG1_SEN_CK, clock_count);
+
+ /* First Enable Sensor interface and select pad (0x1a04_0200) */
+ mtk_seninf_input_update(input, SENINF_CTRL, SENINF_EN, 1);
+ mtk_seninf_input_update(input, SENINF_CTRL, PAD2CAM_DATA_SEL,
+ SENINF_PAD_10BIT);
+ mtk_seninf_input_update(input, SENINF_CTRL, SENINF_SRC_SEL, 0);
+ mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_CSI2_IP_EN, 1);
+ mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_NCSI2_IP_EN, 0);
+
+ /* DPCM Enable */
+ if (fmtinfo->flags & MTK_SENINF_FORMAT_DPCM)
+ val = SENINF_CSI2_DPCM_DI_2A_DPCM_EN;
+ else
+ val = SENINF_CSI2_DPCM_DI_30_DPCM_EN;
+ mtk_seninf_input_write(input, SENINF_CSI2_DPCM, val);
+
+ /* Settle delay */
+ mtk_seninf_input_update(input, SENINF_CSI2_LNRD_TIMING,
+ DATA_SETTLE_PARAMETER, SENINF_SETTLE_DELAY);
+
+ /* CSI2 control */
+ val = mtk_seninf_inuput_read(input, SENINF_CSI2_CTL)
+ | (FIELD_PREP(SENINF_CSI2_CTL_ED_SEL,
+ DATA_HEADER_ORDER_DI_WCL_WCH)
+ | SENINF_CSI2_CTL_CLOCK_LANE_EN | (BIT(num_data_lanes) - 1));
+ mtk_seninf_input_write(input, SENINF_CSI2_CTL, val);
+
+ mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
+ BYPASS_LANE_RESYNC, 0);
+ mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
+ CDPHY_SEL, 0);
+ mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
+ CPHY_LANE_RESYNC_CNT, 3);
+ mtk_seninf_input_update(input, SENINF_CSI2_MODE, CSR_CSI2_MODE, 0);
+ mtk_seninf_input_update(input, SENINF_CSI2_MODE, CSR_CSI2_HEADER_LEN,
+ 0);
+ mtk_seninf_input_update(input, SENINF_CSI2_DPHY_SYNC, SYNC_SEQ_MASK_0,
+ 0xff00);
+ mtk_seninf_input_update(input, SENINF_CSI2_DPHY_SYNC, SYNC_SEQ_PAT_0,
+ 0x001d);
+
+ mtk_seninf_input_update(input, SENINF_CSI2_CTL, CLOCK_HS_OPTION, 0);
+ mtk_seninf_input_update(input, SENINF_CSI2_CTL, HSRX_DET_EN, 0);
+ mtk_seninf_input_update(input, SENINF_CSI2_CTL, HS_TRAIL_EN, 1);
+ mtk_seninf_input_update(input, SENINF_CSI2_HS_TRAIL, HS_TRAIL_PARAMETER,
+ SENINF_HS_TRAIL_PARAMETER);
+
+ /* Set debug port to output packet number */
+ mtk_seninf_input_update(input, SENINF_CSI2_DGB_SEL, DEBUG_EN, 1);
+ mtk_seninf_input_update(input, SENINF_CSI2_DGB_SEL, DEBUG_SEL, 0x1a);
+
+ /* HQ */
+ mtk_seninf_input_write(input, SENINF_CSI2_SPARE0, 0xfffffffe);
+
+ /* Reset the CSI2 to commit changes */
+ mtk_seninf_input_update(input, SENINF_CTRL, CSI2_SW_RST, 1);
+ udelay(1);
+ mtk_seninf_input_update(input, SENINF_CTRL, CSI2_SW_RST, 0);
+
+ return 0;
+}
+
+static void mtk_seninf_mux_setup(struct mtk_seninf_mux *mux,
+ struct mtk_seninf_input *input,
+ struct v4l2_subdev_state *state)
+{
+ const struct mtk_seninf_format_info *fmtinfo;
+ const struct v4l2_mbus_framefmt *format;
+ unsigned int pix_sel_ext;
+ unsigned int pix_sel;
+ unsigned int hs_pol = 0;
+ unsigned int vs_pol = 0;
+ unsigned int val;
+ u32 rst_mask;
+
+ format = v4l2_subdev_state_get_format(state, input->pad, 0);
+ fmtinfo = mtk_seninf_format_info(format->code);
+
+ /* Enable mux */
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_MUX_EN, 1);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_SRC_SEL,
+ SENINF_MIPI_SENSOR);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_SRC_SEL_EXT,
+ SENINF_NORMAL_MODEL);
+
+ pix_sel_ext = 0;
+ pix_sel = 1;
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_PIX_SEL_EXT,
+ pix_sel_ext);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_PIX_SEL, pix_sel);
+
+ if (fmtinfo->flags & MTK_SENINF_FORMAT_JPEG) {
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 0);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN,
+ FIFO_FLUSH_EN_JPEG_2_PIXEL_MODE);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN,
+ FIFO_PUSH_EN_JPEG_2_PIXEL_MODE);
+ } else {
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 2);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN,
+ FIFO_FLUSH_EN_NORMAL_MODE);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN,
+ FIFO_PUSH_EN_NORMAL_MODE);
+ }
+
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_POL, hs_pol);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_VSYNC_POL, vs_pol);
+
+ val = mtk_seninf_mux_read(mux, SENINF_MUX_CTRL);
+ rst_mask = SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
+ SENINF_MUX_CTRL_SENINF_MUX_SW_RST;
+
+ mtk_seninf_mux_write(mux, SENINF_MUX_CTRL, val | rst_mask);
+ mtk_seninf_mux_write(mux, SENINF_MUX_CTRL, val & ~rst_mask);
+
+ /* HQ */
+ val = SENINF_FIFO_FULL_SEL;
+
+ /* SPARE field meaning is unknown */
+ val |= 0xc0000;
+ mtk_seninf_mux_write(mux, SENINF_MUX_SPARE, val);
+}
+
+static void mtk_seninf_top_mux_setup(struct mtk_seninf *priv,
+ enum mtk_seninf_id seninf_id,
+ struct mtk_seninf_mux *mux)
+{
+ unsigned int val;
+
+ /*
+ * Use the top mux (from SENINF input to MUX) to configure routing, and
+ * hardcode a 1:1 mapping from the MUX instances to the SENINF outputs.
+ */
+ val = mtk_seninf_read(priv, SENINF_TOP_MUX_CTRL)
+ & ~(0xf << (mux->mux_id * 4));
+ val |= (seninf_id & 0xf) << (mux->mux_id * 4);
+ mtk_seninf_write(priv, SENINF_TOP_MUX_CTRL, val);
+
+ /*
+ * We currently support only seninf version 3.0
+ * where camsv0 and camsv1 are hardwired respectively to
+ * SENINF_CAM2 and SENINF_CAM3 i.e :
+ * - SENINF_TOP_CAM_MUX_CTRL[11:8] = 0
+ * - SENINF_TOP_CAM_MUX_CTRL[15:12] = 1
+ * so we hardcode it here
+ */
+ mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
+ SENINF_CAM2_MUX_SRC_SEL, 0);
+ mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
+ SENINF_CAM3_MUX_SRC_SEL, 1);
+}
+
+static void seninf_enable_test_pattern(struct mtk_seninf *priv,
+ struct v4l2_subdev_state *state)
+{
+ struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_0];
+ struct mtk_seninf_mux *mux = &priv->muxes[0];
+ const struct mtk_seninf_format_info *fmtinfo;
+ const struct v4l2_mbus_framefmt *format;
+ unsigned int val;
+ unsigned int pix_sel_ext;
+ unsigned int pix_sel;
+ unsigned int hs_pol = 0;
+ unsigned int vs_pol = 0;
+ unsigned int seninf = 0;
+ unsigned int tm_size = 0;
+ unsigned int mux_id = mux->mux_id;
+
+ format = v4l2_subdev_state_get_format(state, priv->conf->nb_inputs, 0);
+ fmtinfo = mtk_seninf_format_info(format->code);
+
+ mtk_seninf_update(priv, SENINF_TOP_CTRL, MUX_LP_MODE, 0);
+
+ mtk_seninf_update(priv, SENINF_TOP_CTRL, SENINF_PCLK_EN, 1);
+ mtk_seninf_update(priv, SENINF_TOP_CTRL, SENINF2_PCLK_EN, 1);
+
+ mtk_seninf_input_update(input, SENINF_CTRL, SENINF_EN, 1);
+ mtk_seninf_input_update(input, SENINF_CTRL, SENINF_SRC_SEL, 1);
+ mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_TESTMDL_IP_EN,
+ 1);
+
+ mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_EN, 1);
+ mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_PAT, 0xc);
+ mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_VSYNC, 4);
+ mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_DUMMYPXL, 0x28);
+
+ if (fmtinfo->flags & MTK_SENINF_FORMAT_BAYER)
+ mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_FMT, 0x0);
+ else
+ mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_FMT, 0x1);
+
+ tm_size = FIELD_PREP(SENINF_TG1_TM_SIZE_TM_LINE, format->height + 8);
+ switch (format->code) {
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ tm_size |= FIELD_PREP(SENINF_TG1_TM_SIZE_TM_PXL,
+ format->width * 2);
+ break;
+ default:
+ tm_size |= FIELD_PREP(SENINF_TG1_TM_SIZE_TM_PXL, format->width);
+ break;
+ }
+ mtk_seninf_input_write(input, SENINF_TG1_TM_SIZE, tm_size);
+
+ mtk_seninf_input_write(input, SENINF_TG1_TM_CLK,
+ TEST_MODEL_CLK_DIVIDED_CNT);
+ mtk_seninf_input_write(input, SENINF_TG1_TM_STP, TIME_STAMP_DIVIDER);
+
+ /* Set top mux */
+ val = (mtk_seninf_read(priv, SENINF_TOP_MUX_CTRL)
+ & (~(0xf << (mux_id * 4)))) |
+ ((seninf & 0xf) << (mux_id * 4));
+ mtk_seninf_write(priv, SENINF_TOP_MUX_CTRL, val);
+
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_MUX_EN, 1);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_SRC_SEL_EXT,
+ SENINF_TEST_MODEL);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_SRC_SEL, 1);
+
+ pix_sel_ext = 0;
+ pix_sel = 1;
+
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT,
+ SENINF_PIX_SEL_EXT, pix_sel_ext);
+
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_PIX_SEL, pix_sel);
+
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN, 0x1f);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN, 0x1b);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 2);
+
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_POL, hs_pol);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_VSYNC_POL, vs_pol);
+ mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_MASK, 1);
+
+ mtk_seninf_mux_write(mux, SENINF_MUX_INTEN,
+ SENINF_IRQ_CLR_SEL | SENINF_ALL_ERR_IRQ_EN);
+
+ mtk_seninf_mux_write(mux, SENINF_MUX_CTRL,
+ mtk_seninf_mux_read(mux, SENINF_MUX_CTRL) |
+ SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
+ SENINF_MUX_CTRL_SENINF_MUX_SW_RST);
+ udelay(1);
+ mtk_seninf_mux_write(mux, SENINF_MUX_CTRL,
+ mtk_seninf_mux_read(mux, SENINF_MUX_CTRL) &
+ ~(SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
+ SENINF_MUX_CTRL_SENINF_MUX_SW_RST));
+
+ //check this
+ mtk_seninf_write(priv, SENINF_TOP_CAM_MUX_CTRL, 0x76540010);
+ /*
+ * We currently support only seninf version 3.0
+ * where camsv0 and camsv1 are hardwired respectively to
+ * test pattern is valid only for seninf_1 (id 0) i.e :
+ * - SENINF_TOP_CAM_MUX_CTRL[11:8] = 0
+ * - SENINF_TOP_CAM_MUX_CTRL[15:12] = 0
+ * so we hardcode it here
+ */
+ mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
+ SENINF_CAM2_MUX_SRC_SEL, 0);
+ mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
+ SENINF_CAM3_MUX_SRC_SEL, 0);
+}
+
+static int mtk_seninf_start(struct mtk_seninf *priv,
+ struct v4l2_subdev_state *state,
+ struct mtk_seninf_input *input,
+ struct mtk_seninf_mux *mux)
+{
+ int ret;
+
+ phy_power_on(input->phy);
+
+ mtk_seninf_input_setup_csi2_rx(input);
+ ret = mtk_seninf_input_setup_csi2(priv, input, state);
+ if (ret)
+ return ret;
+
+ mtk_seninf_mux_setup(mux, input, state);
+ mtk_seninf_top_mux_setup(priv, input->seninf_id, mux);
+ return 0;
+}
+
+static void mtk_seninf_stop(struct mtk_seninf *priv,
+ struct mtk_seninf_input *input)
+{
+ unsigned int val;
+
+ /* Disable CSI2(2.5G) first */
+ val = mtk_seninf_inuput_read(input, SENINF_CSI2_CTL);
+ val &= ~(SENINF_CSI2_CTL_CLOCK_LANE_EN |
+ SENINF_CSI2_CTL_DATA_LANE3_EN |
+ SENINF_CSI2_CTL_DATA_LANE2_EN |
+ SENINF_CSI2_CTL_DATA_LANE1_EN |
+ SENINF_CSI2_CTL_DATA_LANE0_EN);
+ mtk_seninf_input_write(input, SENINF_CSI2_CTL, val);
+
+ if (!priv->is_testmode)
+ phy_power_off(input->phy);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Controls
+ */
+
+static int seninf_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct mtk_seninf *priv = container_of(ctrl->handler,
+ struct mtk_seninf, ctrl_handler);
+
+ switch (ctrl->id) {
+ case V4L2_CID_TEST_PATTERN:
+ priv->is_testmode = !!ctrl->val;
+ break;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops seninf_ctrl_ops = {
+ .s_ctrl = seninf_set_ctrl,
+};
+
+static const char *const seninf_test_pattern_menu[] = {
+ "No test pattern",
+ "Static horizontal color bars",
+};
+
+static int seninf_initialize_controls(struct mtk_seninf *priv)
+{
+ struct v4l2_ctrl_handler *handler;
+ int ret;
+
+ handler = &priv->ctrl_handler;
+ ret = v4l2_ctrl_handler_init(handler, 2);
+ if (ret)
+ return ret;
+
+ v4l2_ctrl_new_std_menu_items(handler, &seninf_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(seninf_test_pattern_menu) - 1,
+ 0, 0, seninf_test_pattern_menu);
+
+ priv->is_testmode = false;
+
+ if (handler->error) {
+ ret = handler->error;
+ dev_err(priv->dev,
+ "Failed to init controls(%d)\n", ret);
+ v4l2_ctrl_handler_free(handler);
+ return ret;
+ }
+
+ priv->subdev.ctrl_handler = handler;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Subdev Operations
+ */
+static int seninf_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
+ struct mtk_seninf_input *input;
+ struct mtk_seninf_mux *mux;
+ struct v4l2_subdev *source;
+ u32 sink_pad;
+ int ret;
+
+ /* Stream control can only operate on source pads. */
+ if (pad < priv->conf->nb_inputs ||
+ pad >= priv->conf->nb_inputs + priv->conf->nb_outputs)
+ return -EINVAL;
+
+ /*
+ * Locate the SENINF input and MUX for the source pad.
+ */
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad,
+ 0, &sink_pad, NULL);
+ if (ret) {
+ dev_dbg(priv->dev, "No sink pad routed to source pad %u\n",
+ pad);
+ return ret;
+ }
+
+ input = &priv->inputs[sink_pad];
+ mux = &priv->muxes[pad - priv->conf->nb_inputs];
+
+ ret = pm_runtime_get_sync(priv->dev);
+ if (ret < 0) {
+ dev_err(priv->dev, "Failed to pm_runtime_get_sync: %d\n", ret);
+ pm_runtime_put_noidle(priv->dev);
+ return ret;
+ }
+
+ /* If test mode is enabled, just enable the test pattern generator. */
+ if (priv->is_testmode) {
+ seninf_enable_test_pattern(priv, state);
+ return 0;
+ }
+
+ /* Start the SENINF first and then the source. */
+ ret = mtk_seninf_start(priv, state, input, mux);
+ if (ret) {
+ dev_err(priv->dev, "failed to start seninf: %d\n", ret);
+ return ret;
+ }
+
+ source = input->source_sd;
+ ret = v4l2_subdev_call(source, video, s_stream, 1);
+ if (ret) {
+ dev_err(priv->dev, "failed to start source %s: %d\n",
+ source->entity.name, ret);
+ mtk_seninf_stop(priv, input);
+ pm_runtime_put(priv->dev);
+ }
+
+ return ret;
+}
+
+static int seninf_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
+ struct mtk_seninf_input *input;
+ struct mtk_seninf_mux *mux;
+ struct v4l2_subdev *source;
+ u32 sink_pad;
+ int ret;
+
+ /* Stream control can only operate on source pads. */
+ if (pad < priv->conf->nb_inputs ||
+ pad >= priv->conf->nb_inputs + priv->conf->nb_outputs)
+ return -EINVAL;
+
+ /*
+ * Locate the SENINF input and MUX for the source pad.
+ *
+ */
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad,
+ 0, &sink_pad, NULL);
+ if (ret) {
+ dev_dbg(priv->dev, "No sink pad routed to source pad %u\n",
+ pad);
+ return ret;
+ }
+
+ input = &priv->inputs[sink_pad];
+ mux = &priv->muxes[pad - priv->conf->nb_inputs];
+
+ if (!priv->is_testmode) {
+ source = input->source_sd;
+ ret = v4l2_subdev_call(source, video, s_stream, 0);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to stop source %s: %d\n",
+ source->entity.name, ret);
+ }
+
+ mtk_seninf_stop(priv, input);
+ pm_runtime_put(priv->dev);
+ return ret;
+}
+
+static const struct v4l2_mbus_framefmt mtk_seninf_default_fmt = {
+ .code = SENINF_DEFAULT_BUS_FMT,
+ .width = SENINF_DEFAULT_WIDTH,
+ .height = SENINF_DEFAULT_HEIGHT,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .xfer_func = V4L2_XFER_FUNC_DEFAULT,
+ .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
+ .quantization = V4L2_QUANTIZATION_DEFAULT,
+};
+
+static int __seninf_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_krouting *routing)
+{
+ int ret;
+
+ 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,
+ &mtk_seninf_default_fmt);
+}
+
+static int seninf_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
+ struct v4l2_subdev_route routes[SENINF_MAX_NUM_OUTPUTS] = { };
+ struct v4l2_subdev_krouting routing = {
+ .routes = routes,
+ .num_routes = priv->conf->nb_outputs,
+ };
+ unsigned int i;
+
+ /*
+ * Initialize one route for supported source pads.
+ * It is a single route from the first sink pad to the source pad,
+ * while on SENINF 5.0 the routing table will map sink pads to source
+ * pads connected to CAMSV 1:1 (skipping the first two source pads
+ * connected to the CAM instances).
+ */
+ for (i = 0; i < routing.num_routes; i++) {
+ struct v4l2_subdev_route *route = &routes[i];
+
+ route->sink_pad = i;
+ route->sink_stream = 0;
+ route->source_pad = priv->conf->nb_inputs + i;
+ route->source_stream = 0;
+ route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
+ }
+
+ return __seninf_set_routing(sd, state, &routing);
+}
+
+static int seninf_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ const struct mtk_seninf_format_info *fmtinfo;
+ struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
+
+ if (code->index >= ARRAY_SIZE(mtk_seninf_formats))
+ return -EINVAL;
+
+ fmtinfo = &mtk_seninf_formats[code->index];
+ if (fmtinfo->flags & MTK_SENINF_FORMAT_INPUT_ONLY &&
+ mtk_seninf_pad_is_source(priv, code->pad))
+ return -EINVAL;
+
+ code->code = fmtinfo->code;
+
+ return 0;
+}
+
+static int seninf_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
+ const struct mtk_seninf_format_info *fmtinfo;
+ struct v4l2_mbus_framefmt *format;
+
+ /*
+ * TODO (?): We should disallow setting formats on the source pad
+ * completely, as the SENINF can't perform any processing. This would
+ * however break usage of the test pattern generator, as there would be
+ * no way to configure formats at all when no active input is selected.
+ */
+
+ /*
+ * Default to the first format if the requested media bus code isn't
+ * supported.
+ */
+ fmtinfo = mtk_seninf_format_info(fmt->format.code);
+ if (!fmtinfo) {
+ fmtinfo = &mtk_seninf_formats[0];
+ fmt->format.code = fmtinfo->code;
+ }
+
+ /* Interlaced formats are not supported yet. */
+ fmt->format.field = V4L2_FIELD_NONE;
+
+ /* Store the format. */
+ format = v4l2_subdev_state_get_format(state, fmt->pad, fmt->stream);
+ if (!format)
+ return -EINVAL;
+
+ *format = fmt->format;
+
+ if (mtk_seninf_pad_is_source(priv, fmt->pad))
+ return 0;
+
+ /* Propagate the format to the corresponding source pad. */
+ format = v4l2_subdev_state_get_opposite_stream_format(state, fmt->pad,
+ fmt->stream);
+ if (!format)
+ return -EINVAL;
+
+ *format = fmt->format;
+
+ return 0;
+}
+
+static int seninf_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ enum v4l2_subdev_format_whence which,
+ struct v4l2_subdev_krouting *routing)
+{
+ return __seninf_set_routing(sd, state, routing);
+}
+
+static const struct v4l2_subdev_core_ops seninf_subdev_core_ops = {
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_pad_ops seninf_subdev_pad_ops = {
+ .enum_mbus_code = seninf_enum_mbus_code,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = seninf_set_fmt,
+ .link_validate = v4l2_subdev_link_validate_default,
+ .set_routing = seninf_set_routing,
+ .enable_streams = seninf_enable_streams,
+ .disable_streams = seninf_disable_streams,
+};
+
+static const struct v4l2_subdev_ops seninf_subdev_ops = {
+ .core = &seninf_subdev_core_ops,
+ .pad = &seninf_subdev_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops seninf_subdev_internal_ops = {
+ .init_state = seninf_init_state,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Entity Operations
+ */
+
+static const struct media_entity_operations seninf_media_ops = {
+ .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+ .link_validate = v4l2_subdev_link_validate,
+ .has_pad_interdep = v4l2_subdev_has_pad_interdep,
+};
+
+/* -----------------------------------------------------------------------------
+ * Async Subdev Notifier
+ */
+
+struct mtk_seninf_async_subdev {
+ struct v4l2_async_connection asc;
+ struct mtk_seninf_input *input;
+ unsigned int port;
+};
+
+static int mtk_seninf_fwnode_parse(struct device *dev,
+ unsigned int id)
+
+{
+ static const char * const phy_names[] = {
+ "csi0", "csi1", "csi2", "csi0b" };
+ struct mtk_seninf *priv = dev_get_drvdata(dev);
+ struct fwnode_handle *ep, *fwnode;
+ struct mtk_seninf_input *input;
+ struct mtk_seninf_async_subdev *asd;
+ struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
+ unsigned int port;
+ int ret;
+
+ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), id, 0, 0);
+ if (!ep)
+ return 0;
+
+ fwnode = fwnode_graph_get_remote_endpoint(ep);
+ ret = v4l2_fwnode_endpoint_parse(ep, &vep);
+ if (ret) {
+ dev_err(dev, "Failed to parse %p fw\n", to_of_node(fwnode));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ asd = v4l2_async_nf_add_fwnode(&priv->notifier, fwnode,
+ struct mtk_seninf_async_subdev);
+ if (IS_ERR(asd)) {
+ ret = PTR_ERR(asd);
+ goto out;
+ }
+
+ port = vep.base.port;
+ asd->port = port;
+
+ if (mtk_seninf_pad_is_source(priv, port)) {
+ ret = 0;
+ goto out;
+ }
+
+ input = &priv->inputs[port];
+
+ input->pad = port;
+ input->seninf_id = port_to_seninf_id[port];
+ input->base = priv->base + 0x1000 * input->seninf_id;
+ input->seninf = priv;
+
+ input->bus = vep.bus.mipi_csi2;
+
+ input->phy = devm_phy_get(dev, phy_names[port]);
+ if (IS_ERR(input->phy)) {
+ dev_err(dev, "failed to get phy: %ld\n", PTR_ERR(input->phy));
+ ret = PTR_ERR(input->phy);
+ goto out;
+ }
+ input->phy_mode = SENINF_PHY_MODE_4D1C;
+
+ asd->input = input;
+
+ ret = 0;
+out:
+ fwnode_handle_put(ep);
+ fwnode_handle_put(fwnode);
+ return ret;
+}
+
+static int mtk_seninf_notifier_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_connection *asc)
+{
+ struct mtk_seninf *priv = container_of(notifier, struct mtk_seninf,
+ notifier);
+ struct mtk_seninf_async_subdev *asd =
+ container_of(asc, struct mtk_seninf_async_subdev, asc);
+ struct device_link *link;
+ int ret;
+
+ dev_dbg(priv->dev, "%s bound to SENINF port %u\n", sd->entity.name,
+ asd->port);
+
+ if (mtk_seninf_pad_is_sink(priv, asd->port)) {
+ struct mtk_seninf_input *input = asd->input;
+
+ input->source_sd = sd;
+
+ link = device_link_add(priv->dev, sd->dev,
+ DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
+ if (!link) {
+ dev_err(priv->dev,
+ "Failed to create device link from source %s\n",
+ sd->name);
+ return -EINVAL;
+ }
+
+ ret = v4l2_create_fwnode_links_to_pad(sd,
+ &priv->pads[input->pad],
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ } else {
+ link = device_link_add(sd->dev, priv->dev,
+ DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
+ if (!link) {
+ dev_err(priv->dev,
+ "Failed to create device link to output %s\n",
+ sd->name);
+ return -EINVAL;
+ }
+
+ ret = v4l2_create_fwnode_links_to_pad(&priv->subdev,
+ &sd->entity.pads[0],
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ }
+ if (ret) {
+ dev_err(priv->dev, "Failed to create links between SENINF port %u and %s (%d)\n",
+ asd->port, sd->entity.name, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mtk_seninf_notifier_complete(struct v4l2_async_notifier *notifier)
+{
+ struct mtk_seninf *priv = container_of(notifier, struct mtk_seninf,
+ notifier);
+ int ret;
+
+ ret = v4l2_device_register_subdev_nodes(&priv->v4l2_dev);
+ if (ret) {
+ dev_err(priv->dev, "Failed to register subdev nodes: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_async_notifier_operations mtk_seninf_async_ops = {
+ .bound = mtk_seninf_notifier_bound,
+ .complete = mtk_seninf_notifier_complete,
+};
+
+static int mtk_seninf_media_init(struct mtk_seninf *priv)
+{
+ struct media_device *media_dev = &priv->media_dev;
+ const struct mtk_seninf_conf *conf = priv->conf;
+ unsigned int num_pads = conf->nb_outputs + conf->nb_inputs;
+ struct media_pad *pads = priv->pads;
+ struct device *dev = priv->dev;
+ unsigned int i;
+ int ret;
+
+ media_dev->dev = dev;
+ strscpy(media_dev->model, conf->model, sizeof(media_dev->model));
+ media_dev->hw_revision = 0;
+ media_device_init(media_dev);
+
+ for (i = 0; i < conf->nb_inputs; i++)
+ pads[i].flags = MEDIA_PAD_FL_SINK;
+ for (i = conf->nb_inputs; i < num_pads; i++)
+ pads[i].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&priv->subdev.entity, num_pads, pads);
+ if (ret) {
+ media_device_cleanup(media_dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mtk_seninf_v4l2_async_register(struct mtk_seninf *priv)
+{
+ const struct mtk_seninf_conf *conf = priv->conf;
+ struct device *dev = priv->dev;
+ unsigned int i;
+ int ret;
+
+ v4l2_async_nf_init(&priv->notifier, &priv->v4l2_dev);
+
+ for (i = 0; i < conf->nb_inputs + conf->nb_outputs; ++i) {
+ ret = mtk_seninf_fwnode_parse(dev, i);
+
+ if (ret) {
+ dev_err(dev,
+ "Failed to parse endpoint at port %d: %d\n",
+ i, ret);
+ goto err_clean_notififer;
+ }
+ }
+
+ priv->notifier.ops = &mtk_seninf_async_ops;
+ ret = v4l2_async_nf_register(&priv->notifier);
+ if (ret) {
+ dev_err(dev, "Failed to register async notifier: %d\n", ret);
+ goto err_clean_notififer;
+ }
+ return 0;
+
+err_clean_notififer:
+ v4l2_async_nf_cleanup(&priv->notifier);
+
+ return ret;
+}
+
+static int mtk_seninf_v4l2_register(struct mtk_seninf *priv)
+{
+ struct v4l2_subdev *sd = &priv->subdev;
+ struct device *dev = priv->dev;
+ int ret;
+
+ /* Initialize media device & pads. */
+ ret = mtk_seninf_media_init(priv);
+ if (ret)
+ return ret;
+
+ /* Initialize & register v4l2 device. */
+ priv->v4l2_dev.mdev = &priv->media_dev;
+
+ ret = v4l2_device_register(dev, &priv->v4l2_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register V4L2 device\n");
+ goto err_clean_media;
+ }
+
+ /* Initialize & register subdev. */
+ v4l2_subdev_init(sd, &seninf_subdev_ops);
+ sd->internal_ops = &seninf_subdev_internal_ops;
+ sd->dev = dev;
+ sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+ sd->entity.ops = &seninf_media_ops;
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
+ V4L2_SUBDEV_FL_STREAMS;
+ strscpy(sd->name, dev_name(dev), sizeof(sd->name));
+ ret = seninf_initialize_controls(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to initialize controls\n");
+ goto err_unreg_v4l2;
+ }
+ v4l2_set_subdevdata(sd, priv);
+
+ ret = v4l2_subdev_init_finalize(sd);
+ if (ret)
+ goto err_free_handler;
+
+ ret = v4l2_device_register_subdev(&priv->v4l2_dev, sd);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register subdev\n");
+ goto err_cleanup_subdev;
+ }
+
+ /* Set up async device */
+ ret = mtk_seninf_v4l2_async_register(priv);
+ if (ret) {
+ dev_err_probe(dev, ret,
+ "Failed to register v4l2 async notifier\n");
+ goto err_unreg_subdev;
+ }
+
+ /* Register media device */
+ ret = media_device_register(&priv->media_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register media device\n");
+ goto err_unreg_notifier;
+ }
+
+ return 0;
+
+err_unreg_notifier:
+ v4l2_async_nf_unregister(&priv->notifier);
+err_unreg_subdev:
+ v4l2_device_unregister_subdev(sd);
+err_cleanup_subdev:
+ v4l2_subdev_cleanup(sd);
+err_free_handler:
+ v4l2_ctrl_handler_free(&priv->ctrl_handler);
+err_unreg_v4l2:
+ v4l2_device_unregister(&priv->v4l2_dev);
+err_clean_media:
+ media_entity_cleanup(&sd->entity);
+ media_device_cleanup(&priv->media_dev);
+
+ return ret;
+}
+
+static int seninf_pm_suspend(struct device *dev)
+{
+ struct mtk_seninf *priv = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+
+ return 0;
+}
+
+static int seninf_pm_resume(struct device *dev)
+{
+ struct mtk_seninf *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ mtk_seninf_csi2_setup_phy(priv);
+
+ return 0;
+}
+
+static const struct dev_pm_ops runtime_pm_ops = {
+ SET_RUNTIME_PM_OPS(seninf_pm_suspend, seninf_pm_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static int seninf_probe(struct platform_device *pdev)
+{
+ static const char * const clk_names[] = { "camsys", "top_mux" };
+ struct device *dev = &pdev->dev;
+ struct mtk_seninf *priv;
+ unsigned int i;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->conf = device_get_match_data(dev);
+
+ dev_set_drvdata(dev, priv);
+ priv->dev = dev;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->num_clks = ARRAY_SIZE(clk_names);
+ priv->clks = devm_kcalloc(dev, priv->num_clks,
+ sizeof(*priv->clks), GFP_KERNEL);
+ if (!priv->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < priv->num_clks; ++i)
+ priv->clks[i].id = clk_names[i];
+
+ ret = devm_clk_bulk_get(dev, priv->num_clks, priv->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get seninf clock\n");
+
+ for (i = 0; i < priv->conf->nb_muxes; ++i) {
+ struct mtk_seninf_mux *mux = &priv->muxes[i];
+
+ mux->pad = priv->conf->nb_inputs + i;
+ mux->mux_id = i;
+ mux->base = priv->base + 0x1000 * i;
+ mux->seninf = priv;
+ }
+
+ devm_pm_runtime_enable(dev);
+
+ ret = mtk_seninf_v4l2_register(priv);
+ return ret;
+}
+
+static void seninf_remove(struct platform_device *pdev)
+{
+ struct mtk_seninf *priv = dev_get_drvdata(&pdev->dev);
+
+ media_device_unregister(&priv->media_dev);
+ media_device_cleanup(&priv->media_dev);
+ v4l2_async_nf_unregister(&priv->notifier);
+ v4l2_async_nf_cleanup(&priv->notifier);
+ v4l2_device_unregister_subdev(&priv->subdev);
+ v4l2_subdev_cleanup(&priv->subdev);
+ v4l2_ctrl_handler_free(&priv->ctrl_handler);
+ media_entity_cleanup(&priv->subdev.entity);
+ v4l2_device_unregister(&priv->v4l2_dev);
+}
+
+static const struct mtk_seninf_conf seninf_8365_conf = {
+ .model = "mtk-camsys-3.0",
+ .nb_inputs = 4,
+ .nb_muxes = 6,
+ .nb_outputs = 4,
+};
+
+static const struct of_device_id mtk_seninf_of_match[] = {
+ { .compatible = "mediatek,mt8365-seninf", .data = &seninf_8365_conf },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mtk_seninf_of_match);
+
+static struct platform_driver seninf_pdrv = {
+ .driver = {
+ .name = "mtk-seninf",
+ .pm = &runtime_pm_ops,
+ .of_match_table = mtk_seninf_of_match,
+ },
+ .probe = seninf_probe,
+ .remove = seninf_remove,
+};
+
+module_platform_driver(seninf_pdrv);
+
+MODULE_DESCRIPTION("MTK sensor interface driver");
+MODULE_AUTHOR("Louis Kuo <louis.kuo@mediatek.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h b/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h
new file mode 100644
index 0000000000000000000000000000000000000000..1f13755ab2f0239b0ab7ed200523da5a7b773d1b
--- /dev/null
+++ b/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#ifndef __SENINF_REG_H__
+#define __SENINF_REG_H__
+
+#include <linux/bits.h>
+
+#define SENINF_TOP_CTRL 0x0000
+#define SENINF_TOP_CTRL_MUX_LP_MODE BIT(31)
+#define SENINF_TOP_CTRL_SENINF_PCLK_EN BIT(10)
+#define SENINF_TOP_CTRL_SENINF2_PCLK_EN BIT(11)
+#define SENINF_TOP_MUX_CTRL 0x0008
+#define SENINF_TOP_CAM_MUX_CTRL 0x0010
+#define SENINF_TOP_CAM_MUX_CTRL_SENINF_CAM2_MUX_SRC_SEL GENMASK(11, 8)
+#define SENINF_TOP_CAM_MUX_CTRL_SENINF_CAM3_MUX_SRC_SEL GENMASK(15, 12)
+#define SENINF_TOP_PHY_SENINF_CTL_CSI0 0x001c
+#define SENINF_TOP_PHY_SENINF_CTL_CSI0_DPHY_MODE BIT(0)
+#define SENINF_TOP_PHY_SENINF_CTL_CSI0_CK_SEL_1 GENMASK(10, 8)
+#define SENINF_TOP_PHY_SENINF_CTL_CSI0_CK_SEL_2 GENMASK(13, 12)
+#define SENINF_TOP_PHY_SENINF_CTL_CSI0_PHY_SENINF_LANE_MUX_CSI0_EN BIT(31)
+#define SENINF_TOP_PHY_SENINF_CTL_CSI1 0x0020
+#define SENINF_TOP_PHY_SENINF_CTL_CSI1_DPHY_MODE BIT(0)
+#define SENINF_TOP_PHY_SENINF_CTL_CSI1_CK_SEL_1 GENMASK(10, 8)
+#define SENINF_TOP_PHY_SENINF_CTL_CSI1_PHY_SENINF_LANE_MUX_CSI1_EN BIT(31)
+#define SENINF_CTRL 0x0200
+#define SENINF_CTRL_SENINF_EN BIT(0)
+#define SENINF_CTRL_CSI2_SW_RST BIT(7)
+#define SENINF_CTRL_SENINF_SRC_SEL GENMASK(14, 12)
+#define SENINF_CTRL_PAD2CAM_DATA_SEL GENMASK(30, 28)
+#define SENINF_CTRL_EXT 0x0204
+#define SENINF_CTRL_EXT_SENINF_TESTMDL_IP_EN BIT(1)
+#define SENINF_CTRL_EXT_SENINF_NCSI2_IP_EN BIT(5)
+#define SENINF_CTRL_EXT_SENINF_CSI2_IP_EN BIT(6)
+#define SENINF_TG1_PH_CNT 0x0600
+#define SENINF_TG1_SEN_CK 0x0604
+#define SENINF_TG1_SEN_CK_CLKCNT GENMASK(21, 16)
+#define SENINF_TG1_TM_CTL 0x0608
+#define SENINF_TG1_TM_CTL_TM_EN BIT(0)
+#define SENINF_TG1_TM_CTL_TM_FMT BIT(2)
+#define SENINF_TG1_TM_CTL_TM_PAT GENMASK(7, 4)
+#define SENINF_TG1_TM_CTL_TM_VSYNC GENMASK(15, 8)
+#define SENINF_TG1_TM_CTL_TM_DUMMYPXL GENMASK(23, 16)
+#define SENINF_TG1_TM_SIZE 0x060c
+#define SENINF_TG1_TM_SIZE_TM_LINE GENMASK(29, 16)
+#define SENINF_TG1_TM_SIZE_TM_PXL GENMASK(12, 0)
+#define SENINF_TG1_TM_CLK 0x0610
+#define TEST_MODEL_CLK_DIVIDED_CNT 8
+#define SENINF_TG1_TM_STP 0x0614
+#define TIME_STAMP_DIVIDER 1
+#define MIPI_RX_CON24_CSI0 0x0824
+#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN0_MUX GENMASK(25, 24)
+#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN1_MUX GENMASK(27, 26)
+#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN2_MUX GENMASK(29, 28)
+#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN3_MUX GENMASK(31, 30)
+#define SENINF_CSI2_CTL 0x0a00
+#define SENINF_CSI2_CTL_DATA_LANE0_EN BIT(0)
+#define SENINF_CSI2_CTL_DATA_LANE1_EN BIT(1)
+#define SENINF_CSI2_CTL_DATA_LANE2_EN BIT(2)
+#define SENINF_CSI2_CTL_DATA_LANE3_EN BIT(3)
+#define SENINF_CSI2_CTL_CLOCK_LANE_EN BIT(4)
+#define SENINF_CSI2_CTL_HSRX_DET_EN BIT(7)
+#define SENINF_CSI2_CTL_ED_SEL BIT(16)
+#define DATA_HEADER_ORDER_DI_WCL_WCH 1
+#define SENINF_CSI2_CTL_HS_TRAIL_EN BIT(25)
+#define SENINF_CSI2_CTL_CLOCK_HS_OPTION BIT(27)
+#define SENINF_CSI2_LNRD_TIMING 0x0a08
+#define SENINF_CSI2_LNRD_TIMING_DATA_SETTLE_PARAMETER GENMASK(15, 8)
+#define SENINF_CSI2_DPCM 0x0a0c
+#define SENINF_CSI2_DPCM_DI_30_DPCM_EN BIT(7)
+#define SENINF_CSI2_DPCM_DI_2A_DPCM_EN BIT(15)
+#define SENINF_CSI2_DGB_SEL 0x0a18
+#define SENINF_CSI2_DGB_SEL_DEBUG_SEL GENMASK(7, 0)
+#define SENINF_CSI2_DGB_SEL_DEBUG_EN BIT(31)
+#define SENINF_CSI2_SPARE0 0x0a20
+#define SENINF_CSI2_LNRC_FSM 0x0a28
+#define SENINF_CSI2_HS_TRAIL 0x0a40
+#define SENINF_CSI2_HS_TRAIL_HS_TRAIL_PARAMETER GENMASK(7, 0)
+#define SENINF_CSI2_RESYNC_MERGE_CTL 0x0a74
+#define SENINF_CSI2_RESYNC_MERGE_CTL_CPHY_LANE_RESYNC_CNT GENMASK(2, 0)
+#define SENINF_CSI2_RESYNC_MERGE_CTL_BYPASS_LANE_RESYNC BIT(10)
+#define SENINF_CSI2_RESYNC_MERGE_CTL_CDPHY_SEL BIT(11)
+#define SENINF_CSI2_MODE 0x0ae8
+#define SENINF_CSI2_MODE_CSR_CSI2_MODE GENMASK(7, 0)
+#define SENINF_CSI2_MODE_CSR_CSI2_HEADER_LEN GENMASK(10, 8)
+#define SENINF_CSI2_DPHY_SYNC 0x0b20
+#define SENINF_CSI2_DPHY_SYNC_SYNC_SEQ_MASK_0 GENMASK(15, 0)
+#define SENINF_CSI2_DPHY_SYNC_SYNC_SEQ_PAT_0 GENMASK(31, 16)
+#define SENINF_MUX_CTRL 0x0d00
+#define SENINF_MUX_CTRL_SENINF_MUX_SW_RST BIT(0)
+#define SENINF_MUX_CTRL_SENINF_IRQ_SW_RST BIT(1)
+#define SENINF_MUX_CTRL_SENINF_HSYNC_MASK BIT(7)
+#define SENINF_MUX_CTRL_SENINF_PIX_SEL BIT(8)
+#define SENINF_MUX_CTRL_SENINF_VSYNC_POL BIT(9)
+#define SENINF_MUX_CTRL_SENINF_HSYNC_POL BIT(10)
+#define SENINF_MUX_CTRL_SENINF_SRC_SEL GENMASK(15, 12)
+#define SENINF_MUX_CTRL_FIFO_PUSH_EN GENMASK(21, 16)
+#define FIFO_PUSH_EN_NORMAL_MODE 0x1f
+#define FIFO_PUSH_EN_JPEG_2_PIXEL_MODE 0x1e
+#define SENINF_MUX_CTRL_FIFO_FLUSH_EN GENMASK(28, 22)
+#define FIFO_FLUSH_EN_NORMAL_MODE 0x1b
+#define FIFO_FLUSH_EN_JPEG_2_PIXEL_MODE 0x18
+#define SENINF_MUX_CTRL_FIFO_FULL_WR_EN GENMASK(29, 28)
+#define SENINF_MUX_CTRL_SENINF_MUX_EN BIT(31)
+#define SENINF_MUX_INTEN 0x0d04
+#define SENINF_MUX_SPARE 0x0d2c
+#define SENINF_FIFO_FULL_SEL BIT(13)
+#define SENINF_MUX_CTRL_EXT 0x0d3c
+#define SENINF_MUX_CTRL_EXT_SENINF_SRC_SEL_EXT GENMASK(1, 0)
+#define SENINF_MUX_CTRL_EXT_SENINF_PIX_SEL_EXT BIT(4)
+
+#endif /* __SENINF_REG_H__ */
--
2.47.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 [PATCH v7 0/5] Add Mediatek ISP3.0 Julien Stephan
` (2 preceding siblings ...)
2024-11-21 8:53 ` [PATCH v7 3/5] media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface Julien Stephan
@ 2024-11-21 8:53 ` Julien Stephan
2024-11-22 7:54 ` CK Hu (胡俊光)
` (10 more replies)
2024-11-21 8:53 ` [PATCH v7 5/5] arm64: dts: mediatek: mt8365: Add support for camera Julien Stephan
4 siblings, 11 replies; 39+ messages in thread
From: Julien Stephan @ 2024-11-21 8:53 UTC (permalink / raw)
To: Laurent Pinchart, Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Julien Stephan, Phi-bang Nguyen,
Florian Sylvestre, Paul Elder
From: Phi-bang Nguyen <pnguyen@baylibre.com>
This driver provides a path to bypass the SoC ISP so that image data
coming from the SENINF can go directly into memory without any image
processing. This allows the use of an external ISP.
Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
[Paul Elder fix irq locking]
Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Co-developed-by: Julien Stephan <jstephan@baylibre.com>
Signed-off-by: Julien Stephan <jstephan@baylibre.com>
---
drivers/media/platform/mediatek/isp/Kconfig | 18 +
drivers/media/platform/mediatek/isp/Makefile | 5 +
drivers/media/platform/mediatek/isp/mtk_camsv.c | 275 ++++++++
drivers/media/platform/mediatek/isp/mtk_camsv.h | 170 +++++
.../media/platform/mediatek/isp/mtk_camsv30_hw.c | 539 ++++++++++++++++
.../media/platform/mediatek/isp/mtk_camsv_video.c | 701 +++++++++++++++++++++
6 files changed, 1708 insertions(+)
diff --git a/drivers/media/platform/mediatek/isp/Kconfig b/drivers/media/platform/mediatek/isp/Kconfig
index 2a3cef81d15aa12633ade2f3be0bba36b9af62e1..2b89efc7ba9aa6b85f850bb8ec938cde581f31a2 100644
--- a/drivers/media/platform/mediatek/isp/Kconfig
+++ b/drivers/media/platform/mediatek/isp/Kconfig
@@ -1,4 +1,22 @@
# SPDX-License-Identifier: GPL-2.0-only
+config MTK_CAMSV30
+ tristate "MediaTek ISP3.0 CAMSV driver"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on OF
+ depends on PM
+ select MEDIA_CONTROLLER
+ select MTK_SENINF30
+ select VIDEO_V4L2_SUBDEV_API
+ select VIDEOBUF2_DMA_CONTIG
+ select VIDEOBUF2_VMALLOC
+ default n
+ help
+ This driver provides a path to bypass the SoC ISP so that
+ image data come from the SENINF can go directly into memory
+ without any image processing.
+ To compile this driver as a module, choose M here: the
+ module will be called mtk-camsv30.
+
config MTK_SENINF30
tristate "MediaTek ISP3.0 SENINF driver"
depends on ARCH_MEDIATEK || COMPILE_TEST
diff --git a/drivers/media/platform/mediatek/isp/Makefile b/drivers/media/platform/mediatek/isp/Makefile
index 375d720f9ed75e2197bb723bdce9bc0472e62842..e7205759fe9bc27bd5146c490b93db72deb3767f 100644
--- a/drivers/media/platform/mediatek/isp/Makefile
+++ b/drivers/media/platform/mediatek/isp/Makefile
@@ -1,4 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
+mtk-camsv30-objs += mtk_camsv.o
+mtk-camsv30-objs += mtk_camsv30_hw.o
+mtk-camsv30-objs += mtk_camsv_video.o
+obj-$(CONFIG_MTK_CAMSV30) += mtk-camsv30.o
+
mtk-seninf-objs += mtk_seninf.o
obj-$(CONFIG_MTK_SENINF30) += mtk-seninf.o
diff --git a/drivers/media/platform/mediatek/isp/mtk_camsv.c b/drivers/media/platform/mediatek/isp/mtk_camsv.c
new file mode 100644
index 0000000000000000000000000000000000000000..a02a1c226ee6164db08d18d6927d35ac86eaa8cc
--- /dev/null
+++ b/drivers/media/platform/mediatek/isp/mtk_camsv.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 BayLibre
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-device.h>
+
+#include "mtk_camsv.h"
+
+static inline struct mtk_cam_dev *to_mtk_cam_dev(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct mtk_cam_dev, subdev);
+}
+
+static const u32 mtk_cam_mbus_formats[] = {
+ MEDIA_BUS_FMT_SBGGR8_1X8,
+ MEDIA_BUS_FMT_SGBRG8_1X8,
+ MEDIA_BUS_FMT_SGRBG8_1X8,
+ MEDIA_BUS_FMT_SRGGB8_1X8,
+ MEDIA_BUS_FMT_SBGGR10_1X10,
+ MEDIA_BUS_FMT_SGBRG10_1X10,
+ MEDIA_BUS_FMT_SGRBG10_1X10,
+ MEDIA_BUS_FMT_SRGGB10_1X10,
+ MEDIA_BUS_FMT_SBGGR12_1X12,
+ MEDIA_BUS_FMT_SGBRG12_1X12,
+ MEDIA_BUS_FMT_SGRBG12_1X12,
+ MEDIA_BUS_FMT_SRGGB12_1X12,
+ MEDIA_BUS_FMT_UYVY8_1X16,
+ MEDIA_BUS_FMT_VYUY8_1X16,
+ MEDIA_BUS_FMT_YUYV8_1X16,
+ MEDIA_BUS_FMT_YVYU8_1X16,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Subdev Operations
+ */
+
+static int mtk_cam_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ static const struct v4l2_mbus_framefmt def_format = {
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .width = IMG_MAX_WIDTH,
+ .height = IMG_MAX_HEIGHT,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .xfer_func = V4L2_XFER_FUNC_DEFAULT,
+ .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
+ .quantization = V4L2_QUANTIZATION_DEFAULT,
+ };
+ struct v4l2_mbus_framefmt *format;
+ unsigned int i;
+
+ for (i = 0; i < sd->entity.num_pads; i++) {
+ format = v4l2_subdev_state_get_format(sd_state, i);
+ *format = def_format;
+ }
+
+ return 0;
+}
+
+static int mtk_cam_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index >= ARRAY_SIZE(mtk_cam_mbus_formats))
+ return -EINVAL;
+
+ code->code = mtk_cam_mbus_formats[code->index];
+
+ return 0;
+}
+
+static int mtk_cam_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct v4l2_mbus_framefmt *format;
+ unsigned int i;
+
+ /*
+ * We only support pass-through mode, the format on source pads can't
+ * be modified.
+ */
+ if (fmt->pad != MTK_CAM_CIO_PAD_SENINF)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(mtk_cam_mbus_formats); ++i) {
+ if (mtk_cam_mbus_formats[i] == fmt->format.code)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(mtk_cam_mbus_formats))
+ fmt->format.code = mtk_cam_mbus_formats[0];
+
+ format = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+ format->width = clamp_t(u32, fmt->format.width,
+ IMG_MIN_WIDTH, IMG_MAX_WIDTH);
+ format->height = clamp_t(u32, fmt->format.height,
+ IMG_MIN_HEIGHT, IMG_MAX_HEIGHT);
+ format->code = fmt->format.code;
+
+ fmt->format = *format;
+
+ /* Propagate the format to the source pad. */
+ format = v4l2_subdev_state_get_format(sd_state, MTK_CAM_CIO_PAD_VIDEO);
+ format->width = fmt->format.width;
+ format->height = fmt->format.height;
+ format->code = fmt->format.code;
+
+ return 0;
+}
+
+static int mtk_cam_subdev_registered(struct v4l2_subdev *sd)
+{
+ struct mtk_cam_dev *cam = to_mtk_cam_dev(sd);
+
+ /* Create the video device and link. */
+ return mtk_cam_video_register(cam);
+}
+
+static int camsv_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct mtk_cam_dev *cam = to_mtk_cam_dev(sd);
+ struct v4l2_subdev *seninf;
+ int ret;
+
+ if (!cam->seninf) {
+ cam->seninf = media_pad_remote_pad_first(&cam->subdev_pads[MTK_CAM_CIO_PAD_SENINF]);
+ if (!cam->seninf) {
+ dev_err(cam->dev, "No SENINF connected\n");
+ return -ENOLINK;
+ }
+ }
+
+ seninf = media_entity_to_v4l2_subdev(cam->seninf->entity);
+
+ /* Seninf must stream on first */
+ ret = v4l2_subdev_enable_streams(seninf, cam->seninf->index, BIT(0));
+ if (ret) {
+ dev_err(cam->dev, "failed to stream on %s:%d\n",
+ seninf->entity.name, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int camsv_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct mtk_cam_dev *cam = to_mtk_cam_dev(sd);
+ struct v4l2_subdev *seninf;
+ int ret;
+
+ if (cam->seninf) {
+ seninf = media_entity_to_v4l2_subdev(cam->seninf->entity);
+ ret = v4l2_subdev_disable_streams(seninf, cam->seninf->index,
+ BIT(0));
+ if (ret) {
+ dev_err(cam->dev, "failed to stream off %s:%d\n",
+ sd->entity.name, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops mtk_cam_subdev_pad_ops = {
+ .enum_mbus_code = mtk_cam_enum_mbus_code,
+ .set_fmt = mtk_cam_set_fmt,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .link_validate = v4l2_subdev_link_validate_default,
+ .enable_streams = camsv_enable_streams,
+ .disable_streams = camsv_disable_streams,
+};
+
+static const struct v4l2_subdev_ops mtk_cam_subdev_ops = {
+ .pad = &mtk_cam_subdev_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops mtk_cam_internal_ops = {
+ .init_state = mtk_cam_init_state,
+ .registered = mtk_cam_subdev_registered,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Entity Operations
+ */
+
+static const struct media_entity_operations mtk_cam_media_entity_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+ .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+};
+
+/* -----------------------------------------------------------------------------
+ * Init & Cleanup
+ */
+
+static int mtk_cam_v4l2_register(struct mtk_cam_dev *cam)
+{
+ struct device *dev = cam->dev;
+ int ret;
+
+ cam->subdev_pads[MTK_CAM_CIO_PAD_SENINF].flags = MEDIA_PAD_FL_SINK;
+ cam->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
+
+ /* Initialize subdev pads */
+ ret = media_entity_pads_init(&cam->subdev.entity,
+ ARRAY_SIZE(cam->subdev_pads),
+ cam->subdev_pads);
+ if (ret) {
+ dev_err(dev, "failed to initialize media pads:%d\n", ret);
+ return ret;
+ }
+
+ /* Initialize subdev */
+ v4l2_subdev_init(&cam->subdev, &mtk_cam_subdev_ops);
+
+ cam->subdev.dev = dev;
+ cam->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+ cam->subdev.entity.ops = &mtk_cam_media_entity_ops;
+ cam->subdev.internal_ops = &mtk_cam_internal_ops;
+ cam->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+ strscpy(cam->subdev.name, dev_name(dev), sizeof(cam->subdev.name));
+ v4l2_set_subdevdata(&cam->subdev, cam);
+
+ v4l2_subdev_init_finalize(&cam->subdev);
+
+ ret = v4l2_async_register_subdev(&cam->subdev);
+ if (ret) {
+ dev_err(dev, "failed to initialize subdev:%d\n", ret);
+ media_entity_cleanup(&cam->subdev.entity);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mtk_cam_v4l2_unregister(struct mtk_cam_dev *cam)
+{
+ mtk_cam_video_unregister(&cam->vdev);
+
+ media_entity_cleanup(&cam->subdev.entity);
+ v4l2_async_unregister_subdev(&cam->subdev);
+ v4l2_subdev_cleanup(&cam->subdev);
+}
+
+int mtk_cam_dev_init(struct mtk_cam_dev *cam_dev)
+{
+ int ret;
+
+ mutex_init(&cam_dev->op_lock);
+
+ /* v4l2 sub-device registration */
+ ret = mtk_cam_v4l2_register(cam_dev);
+ if (ret) {
+ mutex_destroy(&cam_dev->op_lock);
+ return ret;
+ }
+
+ return 0;
+}
+
+void mtk_cam_dev_cleanup(struct mtk_cam_dev *cam)
+{
+ mtk_cam_v4l2_unregister(cam);
+ mutex_destroy(&cam->op_lock);
+}
diff --git a/drivers/media/platform/mediatek/isp/mtk_camsv.h b/drivers/media/platform/mediatek/isp/mtk_camsv.h
new file mode 100644
index 0000000000000000000000000000000000000000..de928662c75778ffeae708a7bdac27943af75d94
--- /dev/null
+++ b/drivers/media/platform/mediatek/isp/mtk_camsv.h
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 BayLibre
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#ifndef __MTK_CAMSV_H__
+#define __MTK_CAMSV_H__
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/videodev2.h>
+#include <media/media-entity.h>
+#include <media/v4l2-subdev.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+#include <soc/mediatek/smi.h>
+
+#define IMG_MAX_WIDTH 5376U
+#define IMG_MAX_HEIGHT 4032U
+#define IMG_MIN_WIDTH 80U
+#define IMG_MIN_HEIGHT 60U
+
+#define MTK_CAM_CIO_PAD_SENINF 0U
+#define MTK_CAM_CIO_PAD_VIDEO 1U
+#define MTK_CAM_CIO_NUM_PADS 2U
+
+struct mtk_cam_format_info {
+ u32 code;
+ u32 fourcc;
+ u8 bpp;
+};
+
+struct mtk_cam_dev_buffer {
+ struct vb2_v4l2_buffer v4l2_buf;
+ struct list_head list;
+ dma_addr_t daddr;
+ void *vaddr;
+};
+
+struct mtk_cam_sparams {
+ u32 w_factor;
+ u32 module_en_pak;
+ u32 fmt_sel;
+ u32 pak;
+ u32 imgo_stride;
+};
+
+/**
+ * struct mtk_cam_vdev_desc - MTK camera device descriptor
+ * @num_fmts: the number of supported node formats
+ * @fmts: supported format
+ * @frmsizes: supported V4L2 frame size number
+ */
+struct mtk_cam_vdev_desc {
+ u8 num_fmts;
+ const u32 *fmts;
+ const struct v4l2_frmsizeenum *frmsizes;
+};
+
+/**
+ * struct mtk_cam_video_device - MediaTek video device structure
+ * @desc: The node description of video device
+ * @vdev_pad: The media pad graph object of video device
+ * @vdev: The video device instance
+ * @vbq: A videobuf queue of video device
+ * @vdev_lock: Serializes vb2 queue and video device operations
+ * @format: The V4L2 format of video device
+ * @fmtinfo: Information about the current format
+ */
+struct mtk_cam_video_device {
+ const struct mtk_cam_vdev_desc *desc;
+
+ struct media_pad vdev_pad;
+ struct video_device vdev;
+ struct vb2_queue vbq;
+
+ /* Serializes vb2 queue and video device operations */
+ struct mutex vdev_lock;
+
+ struct v4l2_pix_format_mplane format;
+ const struct mtk_cam_format_info *fmtinfo;
+};
+
+/**
+ * struct mtk_cam_dev - MediaTek camera device structure.
+ * @dev: Pointer to device.
+ * @regs: Base address of CAMSV.
+ * @regs_img0: Base address of CAMSV IMG0.
+ * @regs_tg: Base address of CAMSV TG.
+ * @num_clks: Number of clocks.
+ * @clks: The clocks.
+ * @irq: Irq fired when buffer is ready.
+ * @conf: soc specific driver data.
+ * @pipeline: Media pipeline information.
+ * @subdev: The V4L2 sub-device instance.
+ * @subdev_pads: Media pads of this sub-device.
+ * @vdev: The video device node.
+ * @seninf: Pointer to the seninf pad.
+ * @stream_count: Number of streaming video nodes.
+ * @sequence: Buffer sequence number.
+ * @op_lock: Serializes driver's VB2 callback operations.
+ * @buf_list_lock: Protects the buffer list.
+ * @buf_list: List head for the buffer list.
+ * @hw_functions: Hardware specific functions.
+ */
+struct mtk_cam_dev {
+ struct device *dev;
+ void __iomem *regs;
+ void __iomem *regs_img0;
+ void __iomem *regs_tg;
+
+ unsigned int num_clks;
+ struct clk_bulk_data *clks;
+ unsigned int irq;
+ const struct mtk_cam_conf *conf;
+
+ struct media_pipeline pipeline;
+ struct v4l2_subdev subdev;
+ struct media_pad subdev_pads[MTK_CAM_CIO_NUM_PADS];
+ struct mtk_cam_video_device vdev;
+ struct media_pad *seninf;
+ unsigned int stream_count;
+ unsigned int sequence;
+
+ struct mutex op_lock;
+ spinlock_t buf_list_lock;
+
+ struct list_head buf_list;
+
+ const struct mtk_cam_hw_functions *hw_functions;
+
+};
+
+/**
+ * struct mtk_cam_conf - MediaTek camera configuration structure
+ * @tg_sen_mode: TG sensor mode
+ * @module_en: module enable
+ * @imgo_con: dma control register
+ * @imgo_con2: dma control register 2
+ */
+struct mtk_cam_conf {
+ u32 tg_sen_mode;
+ u32 module_en;
+ u32 imgo_con;
+ u32 imgo_con2;
+};
+
+struct mtk_cam_hw_functions {
+ void (*mtk_cam_setup)(struct mtk_cam_dev *cam_dev, u32 width,
+ u32 height, u32 bpl, u32 mbus_fmt);
+ void (*mtk_cam_update_buffers_add)(struct mtk_cam_dev *cam_dev,
+ struct mtk_cam_dev_buffer *buf);
+ void (*mtk_cam_cmos_vf_hw_enable)(struct mtk_cam_dev *cam_dev);
+ void (*mtk_cam_cmos_vf_hw_disable)(struct mtk_cam_dev *cam_dev);
+ void (*mtk_cam_fbc_init)(struct mtk_cam_dev *cam_dev,
+ unsigned int num_buffers);
+ void (*mtk_cam_fbc_inc)(struct mtk_cam_dev *cam_dev);
+};
+
+int mtk_cam_dev_init(struct mtk_cam_dev *cam_dev);
+void mtk_cam_dev_cleanup(struct mtk_cam_dev *cam_dev);
+int mtk_cam_video_register(struct mtk_cam_dev *cam_dev);
+void mtk_cam_video_unregister(struct mtk_cam_video_device *vdev);
+
+#endif /* __MTK_CAMSV_H__ */
diff --git a/drivers/media/platform/mediatek/isp/mtk_camsv30_hw.c b/drivers/media/platform/mediatek/isp/mtk_camsv30_hw.c
new file mode 100644
index 0000000000000000000000000000000000000000..56c3686770901da9d355f36ee86a9aa7f71aeb1f
--- /dev/null
+++ b/drivers/media/platform/mediatek/isp/mtk_camsv30_hw.c
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 BayLibre
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/iommu.h>
+#include <linux/iopoll.h>
+#include <linux/ktime.h>
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#include "mtk_camsv.h"
+
+/* CAMSV */
+#define CAMSV_MODULE_EN 0x0000
+#define CAMSV_MODULE_EN_IMGO_EN BIT(4)
+#define CAMSV_FMT_SEL 0x0004
+#define CAMSV_INT_EN 0x0008
+#define CAMSV_INT_STATUS 0x000c
+#define CAMSV_SW_CTL 0x0010
+#define CAMSV_IMGO_FBC 0x001C
+#define CAMSV_CLK_EN 0x0020
+#define CAMSV_PAK 0x003c
+
+/* CAMSV_TG */
+#define CAMSV_TG_SEN_MODE 0x0010
+#define CAMSV_TG_VF_CON 0x0014
+#define CAMSV_TG_SEN_GRAB_PXL 0x0018
+#define CAMSV_TG_SEN_GRAB_LIN 0x001c
+#define CAMSV_TG_PATH_CFG 0x0020
+
+/* CAMSV_IMG0 */
+#define CAMSV_IMGO_SV_BASE_ADDR 0x0000
+#define CAMSV_IMGO_SV_XSIZE 0x0008
+#define CAMSV_IMGO_SV_YSIZE 0x000c
+#define CAMSV_IMGO_SV_STRIDE 0x0010
+#define CAMSV_IMGO_SV_CON 0x0014
+#define CAMSV_IMGO_SV_CON2 0x0018
+
+#define CAMSV_TG_SEN_MODE_CMOS_EN BIT(0)
+#define CAMSV_TG_VF_CON_VFDATA_EN BIT(0)
+
+/* CAMSV_CLK_EN bits */
+#define CAMSV_TG_DP_CLK_EN BIT(0)
+#define CAMSV_PAK_DP_CLK_EN BIT(2)
+#define CAMSV_DMA_DP_CLK_EN BIT(15)
+
+/* CAMSV_SW_CTL bits */
+#define CAMSV_IMGO_RST_TRIG BIT(0)
+#define CAMSV_IMGO_RST_ST BIT(1)
+#define CAMSV_SW_RST BIT(2)
+
+/* IRQ BITS */
+#define CAMSV_IRQ_TG_ERR BIT(4)
+#define CAMSV_IRQ_TG_GBERR BIT(5)
+#define CAMSV_IRQ_PASS1_DON BIT(10)
+#define CAMSV_IRQ_IMGO_ERR BIT(16)
+
+/* FBC bits */
+#define CAMSV_IMGO_FBC_RCNT_INC BIT(11)
+#define CAMSV_IMGO_FBC_EN BIT(14)
+#define CAMSV_IMGO_FBC_LOCK_EN BIT(15)
+#define CAMSV_IMGO_FBC_FB_NUM GENMASK(19, 16)
+
+#define INT_ST_MASK_CAMSV (CAMSV_IRQ_PASS1_DON)
+
+#define INT_ST_MASK_CAMSV_ERR \
+ (CAMSV_IRQ_TG_ERR | CAMSV_IRQ_TG_GBERR | CAMSV_IRQ_IMGO_ERR)
+
+#define MTK_CAMSV30_AUTOSUSPEND_DELAY_MS 100
+
+static const struct mtk_cam_conf camsv30_conf = {
+ .tg_sen_mode = 0x00010002U, /* TIME_STP_EN = 1. DBL_DATA_BUS = 1 */
+ .module_en = 0x40000001U, /* enable double buffer and TG */
+ .imgo_con = 0x80000080U, /* DMA FIFO depth and burst */
+ .imgo_con2 = 0x00020002U, /* DMA priority */
+};
+
+static void fmt_to_sparams(u32 mbus_fmt, struct mtk_cam_sparams *sparams)
+{
+ switch (mbus_fmt) {
+ case MEDIA_BUS_FMT_SBGGR12_1X12:
+ case MEDIA_BUS_FMT_SGBRG12_1X12:
+ case MEDIA_BUS_FMT_SGRBG12_1X12:
+ case MEDIA_BUS_FMT_SRGGB12_1X12:
+ sparams->w_factor = 1;
+ sparams->module_en_pak = 0x4;
+ sparams->fmt_sel = 0x2;
+ sparams->pak = 0x5;
+ sparams->imgo_stride = 0X000b0000;
+ break;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ case MEDIA_BUS_FMT_SGBRG10_1X10:
+ case MEDIA_BUS_FMT_SGRBG10_1X10:
+ case MEDIA_BUS_FMT_SRGGB10_1X10:
+ sparams->w_factor = 1;
+ sparams->module_en_pak = 0x4;
+ sparams->fmt_sel = 0x1;
+ sparams->pak = 0x6;
+ sparams->imgo_stride = 0X000b0000;
+ break;
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ case MEDIA_BUS_FMT_SGBRG8_1X8:
+ case MEDIA_BUS_FMT_SGRBG8_1X8:
+ case MEDIA_BUS_FMT_SRGGB8_1X8:
+ sparams->w_factor = 1;
+ sparams->module_en_pak = 0x4;
+ sparams->fmt_sel = 0x0;
+ sparams->pak = 0x7;
+ sparams->imgo_stride = 0X000b0000;
+ break;
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ sparams->w_factor = 2;
+ sparams->module_en_pak = 0x8;
+ sparams->fmt_sel = 0x1000003;
+ sparams->pak = 0x0;
+ sparams->imgo_stride = 0x00090000;
+ break;
+ default:
+ break;
+ }
+}
+
+static u32 mtk_camsv30_read(struct mtk_cam_dev *priv, u32 reg)
+{
+ return readl(priv->regs + reg);
+}
+
+static void mtk_camsv30_write(struct mtk_cam_dev *priv, u32 reg, u32 value)
+{
+ writel(value, priv->regs + reg);
+}
+
+static void mtk_camsv30_img0_write(struct mtk_cam_dev *priv, u32 reg, u32 value)
+{
+ writel(value, priv->regs_img0 + reg);
+}
+
+static u32 mtk_camsv30_tg_read(struct mtk_cam_dev *priv, u32 reg)
+{
+ return readl(priv->regs_tg + reg);
+}
+
+static void mtk_camsv30_tg_write(struct mtk_cam_dev *priv, u32 reg, u32 value)
+{
+ writel(value, priv->regs_tg + reg);
+}
+
+static void mtk_camsv30_update_buffers_add(struct mtk_cam_dev *cam_dev,
+ struct mtk_cam_dev_buffer *buf)
+{
+ mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_BASE_ADDR, buf->daddr);
+}
+
+static void mtk_camsv30_cmos_vf_hw_enable(struct mtk_cam_dev *cam_dev)
+{
+ unsigned int fbc_val;
+ u32 clk_en = CAMSV_TG_DP_CLK_EN | CAMSV_DMA_DP_CLK_EN |
+ CAMSV_PAK_DP_CLK_EN;
+
+ fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
+ fbc_val |= CAMSV_IMGO_FBC_EN;
+ mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
+
+ mtk_camsv30_write(cam_dev, CAMSV_CLK_EN, clk_en);
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_MODE,
+ mtk_camsv30_tg_read(cam_dev, CAMSV_TG_SEN_MODE) |
+ CAMSV_TG_SEN_MODE_CMOS_EN);
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_VF_CON,
+ mtk_camsv30_tg_read(cam_dev, CAMSV_TG_VF_CON) |
+ CAMSV_TG_VF_CON_VFDATA_EN);
+}
+
+static void mtk_camsv30_cmos_vf_hw_disable(struct mtk_cam_dev *cam_dev)
+{
+ unsigned int fbc_val;
+
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_MODE,
+ mtk_camsv30_tg_read(cam_dev, CAMSV_TG_SEN_MODE) &
+ ~CAMSV_TG_SEN_MODE_CMOS_EN);
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_VF_CON,
+ mtk_camsv30_tg_read(cam_dev, CAMSV_TG_VF_CON) &
+ ~CAMSV_TG_VF_CON_VFDATA_EN);
+ fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
+ fbc_val &= ~CAMSV_IMGO_FBC_EN;
+ mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
+}
+
+static void mtk_camsv30_fbc_init(struct mtk_cam_dev *cam_dev,
+ unsigned int num_buffers)
+{
+ unsigned int fbc_val;
+
+ if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
+ dev_err(cam_dev->dev, "failed to get pm_runtime\n");
+ return;
+ }
+
+ fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
+ fbc_val &= ~CAMSV_IMGO_FBC_FB_NUM;
+ fbc_val |= CAMSV_IMGO_FBC_EN;
+ fbc_val |= FIELD_PREP(CAMSV_IMGO_FBC_FB_NUM, num_buffers);
+ mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
+
+ pm_runtime_put_autosuspend(cam_dev->dev);
+}
+
+static void mtk_camsv30_fbc_inc(struct mtk_cam_dev *cam_dev)
+{
+ unsigned int fbc_val;
+
+ if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
+ dev_err(cam_dev->dev, "failed to get pm_runtime\n");
+ return;
+ }
+
+ fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
+ fbc_val |= CAMSV_IMGO_FBC_RCNT_INC;
+ mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
+ fbc_val &= ~CAMSV_IMGO_FBC_RCNT_INC;
+ mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
+
+ pm_runtime_put_autosuspend(cam_dev->dev);
+}
+
+static void mtk_camsv30_setup(struct mtk_cam_dev *cam_dev, u32 w, u32 h,
+ u32 bpl, u32 mbus_fmt)
+{
+ const struct mtk_cam_conf *conf = cam_dev->conf;
+ u32 tmp;
+ struct mtk_cam_sparams sparams;
+
+ fmt_to_sparams(mbus_fmt, &sparams);
+
+ if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
+ dev_err(cam_dev->dev, "failed to get pm_runtime\n");
+ return;
+ }
+
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_MODE, conf->tg_sen_mode);
+
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_GRAB_PXL,
+ (w * sparams.w_factor) << 16U);
+
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_GRAB_LIN, h << 16U);
+
+ /* YUV_U2S_DIS: disable YUV sensor unsigned to signed */
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_PATH_CFG, 0x1000U);
+
+ /* Reset cam */
+ mtk_camsv30_write(cam_dev, CAMSV_SW_CTL, CAMSV_SW_RST);
+ mtk_camsv30_write(cam_dev, CAMSV_SW_CTL, 0x0U);
+ mtk_camsv30_write(cam_dev, CAMSV_SW_CTL, CAMSV_IMGO_RST_TRIG);
+
+ readl_poll_timeout_atomic(cam_dev->regs + CAMSV_SW_CTL, tmp,
+ (tmp == (CAMSV_IMGO_RST_TRIG |
+ CAMSV_IMGO_RST_ST)), 10, 200);
+
+ mtk_camsv30_write(cam_dev, CAMSV_SW_CTL, 0x0U);
+
+ mtk_camsv30_write(cam_dev, CAMSV_INT_EN, INT_ST_MASK_CAMSV);
+
+ mtk_camsv30_write(cam_dev, CAMSV_MODULE_EN,
+ conf->module_en | sparams.module_en_pak);
+ mtk_camsv30_write(cam_dev, CAMSV_FMT_SEL, sparams.fmt_sel);
+ mtk_camsv30_write(cam_dev, CAMSV_PAK, sparams.pak);
+
+ mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_XSIZE, bpl - 1U);
+ mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_YSIZE, h - 1U);
+
+ mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_STRIDE,
+ sparams.imgo_stride | bpl);
+
+ mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_CON, conf->imgo_con);
+ mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_CON2, conf->imgo_con2);
+
+ /* CMOS_EN first */
+ mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_MODE,
+ mtk_camsv30_tg_read(cam_dev, CAMSV_TG_SEN_MODE) |
+ CAMSV_TG_SEN_MODE_CMOS_EN);
+
+ /* finally, CAMSV_MODULE_EN : IMGO_EN */
+ mtk_camsv30_write(cam_dev, CAMSV_MODULE_EN,
+ mtk_camsv30_read(cam_dev, CAMSV_MODULE_EN) |
+ CAMSV_MODULE_EN_IMGO_EN);
+
+ pm_runtime_put_autosuspend(cam_dev->dev);
+}
+
+static irqreturn_t isp_irq_camsv30(int irq, void *data)
+{
+ struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
+ struct mtk_cam_dev_buffer *buf;
+ unsigned int irq_status;
+
+ spin_lock(&cam_dev->buf_list_lock);
+
+ irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
+
+ if (irq_status & INT_ST_MASK_CAMSV_ERR)
+ dev_err(cam_dev->dev, "irq error 0x%lx\n",
+ irq_status & INT_ST_MASK_CAMSV_ERR);
+
+ /* De-queue frame */
+ if (irq_status & CAMSV_IRQ_PASS1_DON) {
+ cam_dev->sequence++;
+
+ buf = list_first_entry_or_null(&cam_dev->buf_list,
+ struct mtk_cam_dev_buffer,
+ list);
+ if (buf) {
+ buf->v4l2_buf.sequence = cam_dev->sequence;
+ buf->v4l2_buf.vb2_buf.timestamp =
+ ktime_get_ns();
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
+ VB2_BUF_STATE_DONE);
+ list_del(&buf->list);
+ }
+
+ buf = list_first_entry_or_null(&cam_dev->buf_list,
+ struct mtk_cam_dev_buffer,
+ list);
+ if (buf)
+ mtk_camsv30_update_buffers_add(cam_dev, buf);
+ }
+
+ spin_unlock(&cam_dev->buf_list_lock);
+
+ return IRQ_HANDLED;
+}
+
+static int mtk_camsv30_runtime_suspend(struct device *dev)
+{
+ struct mtk_cam_dev *cam_dev = dev_get_drvdata(dev);
+ struct vb2_queue *vbq = &cam_dev->vdev.vbq;
+
+ if (vb2_is_streaming(vbq)) {
+ mutex_lock(&cam_dev->op_lock);
+ v4l2_subdev_disable_streams(&cam_dev->subdev,
+ cam_dev->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].index,
+ BIT(0));
+ mutex_unlock(&cam_dev->op_lock);
+ }
+
+ clk_bulk_disable_unprepare(cam_dev->num_clks, cam_dev->clks);
+
+ return 0;
+}
+
+static int mtk_camsv30_runtime_resume(struct device *dev)
+{
+ struct mtk_cam_dev *cam_dev = dev_get_drvdata(dev);
+ struct mtk_cam_video_device *vdev = &cam_dev->vdev;
+ const struct v4l2_pix_format_mplane *fmt = &vdev->format;
+ struct vb2_queue *vbq = &vdev->vbq;
+ struct mtk_cam_dev_buffer *buf, *buf_prev;
+ int ret;
+ unsigned long flags = 0;
+
+ ret = clk_bulk_prepare_enable(cam_dev->num_clks, cam_dev->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable clock:%d\n", ret);
+ return ret;
+ }
+
+ if (vb2_is_streaming(vbq)) {
+ mtk_camsv30_setup(cam_dev, fmt->width, fmt->height,
+ fmt->plane_fmt[0].bytesperline,
+ vdev->fmtinfo->code);
+
+ spin_lock_irqsave(&cam_dev->buf_list_lock, flags);
+ buf = list_first_entry_or_null(&cam_dev->buf_list,
+ struct mtk_cam_dev_buffer,
+ list);
+ if (buf)
+ mtk_camsv30_update_buffers_add(cam_dev, buf);
+
+ spin_unlock_irqrestore(&cam_dev->buf_list_lock, flags);
+ mtk_camsv30_cmos_vf_hw_enable(cam_dev);
+
+
+ /* Stream on the sub-device */
+ mutex_lock(&cam_dev->op_lock);
+ ret = v4l2_subdev_enable_streams(&cam_dev->subdev,
+ cam_dev->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].index,
+ BIT(0));
+
+ if (ret) {
+ cam_dev->stream_count--;
+ if (cam_dev->stream_count == 0)
+ media_pipeline_stop(vdev->vdev.entity.pads);
+ }
+ mutex_unlock(&cam_dev->op_lock);
+
+ if (ret)
+ goto fail_no_stream;
+ }
+
+ return 0;
+
+fail_no_stream:
+ spin_lock_irqsave(&cam_dev->buf_list_lock, flags);
+ list_for_each_entry_safe(buf, buf_prev, &cam_dev->buf_list, list) {
+ buf->daddr = 0ULL;
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&cam_dev->buf_list_lock, flags);
+ return ret;
+}
+
+static const struct mtk_cam_hw_functions mtk_camsv30_hw_functions = {
+ .mtk_cam_setup = mtk_camsv30_setup,
+ .mtk_cam_update_buffers_add = mtk_camsv30_update_buffers_add,
+ .mtk_cam_cmos_vf_hw_enable = mtk_camsv30_cmos_vf_hw_enable,
+ .mtk_cam_cmos_vf_hw_disable = mtk_camsv30_cmos_vf_hw_disable,
+ .mtk_cam_fbc_init = mtk_camsv30_fbc_init,
+ .mtk_cam_fbc_inc = mtk_camsv30_fbc_inc,
+};
+
+static int mtk_camsv30_probe(struct platform_device *pdev)
+{
+ static const char * const clk_names[] = { "cam", "camtg", "camsv"};
+
+ struct mtk_cam_dev *cam_dev;
+ struct device *dev = &pdev->dev;
+ unsigned int i;
+ int ret;
+
+ if (!iommu_present(&platform_bus_type))
+ return -EPROBE_DEFER;
+
+ cam_dev = devm_kzalloc(dev, sizeof(*cam_dev), GFP_KERNEL);
+ if (!cam_dev)
+ return -ENOMEM;
+
+ cam_dev->conf = device_get_match_data(dev);
+ if (!cam_dev->conf)
+ return -ENODEV;
+
+ cam_dev->dev = dev;
+ dev_set_drvdata(dev, cam_dev);
+
+ cam_dev->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(cam_dev->regs))
+ return dev_err_probe(dev, PTR_ERR(cam_dev->regs),
+ "failed to map register base\n");
+
+ cam_dev->regs_img0 = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(cam_dev->regs_img0))
+ return dev_err_probe(dev, PTR_ERR(cam_dev->regs_img0),
+ "failed to map img0 register base\n");
+
+ cam_dev->regs_tg = devm_platform_ioremap_resource(pdev, 2);
+ if (IS_ERR(cam_dev->regs_tg))
+ return dev_err_probe(dev, PTR_ERR(cam_dev->regs_tg),
+ "failed to map TG register base\n");
+
+ cam_dev->num_clks = ARRAY_SIZE(clk_names);
+ cam_dev->clks = devm_kcalloc(dev, cam_dev->num_clks,
+ sizeof(*cam_dev->clks), GFP_KERNEL);
+ if (!cam_dev->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < cam_dev->num_clks; ++i)
+ cam_dev->clks[i].id = clk_names[i];
+
+ ret = devm_clk_bulk_get(dev, cam_dev->num_clks, cam_dev->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get clocks: %i\n",
+ ret);
+
+ cam_dev->irq = platform_get_irq(pdev, 0);
+ ret = devm_request_irq(dev, cam_dev->irq, isp_irq_camsv30, 0,
+ dev_name(dev), cam_dev);
+ if (ret != 0)
+ return dev_err_probe(dev, -ENODEV, "failed to request irq=%d\n",
+ cam_dev->irq);
+
+ cam_dev->hw_functions = &mtk_camsv30_hw_functions;
+
+ spin_lock_init(&cam_dev->buf_list_lock);
+
+ /* initialise runtime power management */
+ pm_runtime_set_autosuspend_delay(dev, MTK_CAMSV30_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_suspended(dev);
+ devm_pm_runtime_enable(dev);
+
+ /* Initialize the v4l2 common part */
+ return mtk_cam_dev_init(cam_dev);
+}
+
+static void mtk_camsv30_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_cam_dev *cam_dev = dev_get_drvdata(dev);
+
+ mtk_cam_dev_cleanup(cam_dev);
+ pm_runtime_put_autosuspend(dev);
+}
+
+static const struct dev_pm_ops mtk_camsv30_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(mtk_camsv30_runtime_suspend,
+ mtk_camsv30_runtime_resume, NULL)
+};
+
+static const struct of_device_id mtk_camsv30_of_ids[] = {
+ { .compatible = "mediatek,mt8365-camsv", .data = &camsv30_conf },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mtk_camsv30_of_ids);
+
+static struct platform_driver mtk_camsv30_driver = {
+ .probe = mtk_camsv30_probe,
+ .remove = mtk_camsv30_remove,
+ .driver = {
+ .name = "mtk-camsv-isp30",
+ .of_match_table = mtk_camsv30_of_ids,
+ .pm = &mtk_camsv30_pm_ops,
+ }
+};
+
+module_platform_driver(mtk_camsv30_driver);
+
+MODULE_DESCRIPTION("MediaTek CAMSV ISP3.0 driver");
+MODULE_AUTHOR("Florian Sylvestre <fsylvestre@baylibre.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/mediatek/isp/mtk_camsv_video.c b/drivers/media/platform/mediatek/isp/mtk_camsv_video.c
new file mode 100644
index 0000000000000000000000000000000000000000..4a5f3431a14563d5ed133270a9907773e8626f9c
--- /dev/null
+++ b/drivers/media/platform/mediatek/isp/mtk_camsv_video.c
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mtk_camsv_video.c - V4L2 video node support
+ *
+ * Copyright (c) 2020 BayLibre
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#include <linux/version.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mediabus.h>
+
+#include "mtk_camsv.h"
+
+static inline struct mtk_cam_video_device *
+file_to_mtk_cam_video_device(struct file *__file)
+{
+ return container_of(video_devdata(__file),
+ struct mtk_cam_video_device, vdev);
+}
+
+static inline struct mtk_cam_video_device *
+vb2_queue_to_mtk_cam_video_device(struct vb2_queue *vq)
+{
+ return container_of(vq, struct mtk_cam_video_device, vbq);
+}
+
+static inline struct mtk_cam_dev_buffer *
+to_mtk_cam_dev_buffer(struct vb2_buffer *buf)
+{
+ return container_of(buf, struct mtk_cam_dev_buffer, v4l2_buf.vb2_buf);
+}
+
+/* -----------------------------------------------------------------------------
+ * Format Information
+ */
+
+static const struct mtk_cam_format_info mtk_cam_format_info[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .bpp = 8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG8,
+ .code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .bpp = 8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .bpp = 8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB8,
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .bpp = 8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .code = MEDIA_BUS_FMT_YUYV8_1X16,
+ .bpp = 16,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .code = MEDIA_BUS_FMT_YVYU8_1X16,
+ .bpp = 16,
+ }, {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .bpp = 16,
+ }, {
+ .fourcc = V4L2_PIX_FMT_VYUY,
+ .code = MEDIA_BUS_FMT_VYUY8_1X16,
+ .bpp = 16,
+ },
+};
+
+static const struct mtk_cam_format_info *
+mtk_cam_format_info_by_fourcc(u32 fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(mtk_cam_format_info); ++i) {
+ const struct mtk_cam_format_info *info =
+ &mtk_cam_format_info[i];
+
+ if (info->fourcc == fourcc)
+ return info;
+ }
+
+ return NULL;
+}
+
+static const struct mtk_cam_format_info *
+mtk_cam_format_info_by_code(u32 code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(mtk_cam_format_info); ++i) {
+ const struct mtk_cam_format_info *info =
+ &mtk_cam_format_info[i];
+
+ if (info->code == code)
+ return info;
+ }
+
+ return NULL;
+}
+
+static bool mtk_cam_dev_find_fmt(const struct mtk_cam_vdev_desc *desc,
+ u32 format)
+{
+ unsigned int i;
+
+ for (i = 0; i < desc->num_fmts; i++) {
+ if (desc->fmts[i] == format)
+ return true;
+ }
+
+ return false;
+}
+
+static void calc_bpl_size_pix_mp(const struct mtk_cam_format_info *fmtinfo,
+ struct v4l2_pix_format_mplane *pix_mp)
+{
+ unsigned int bpl;
+ unsigned int i;
+
+ bpl = ALIGN(DIV_ROUND_UP(pix_mp->width * fmtinfo->bpp, 8), 2);
+
+ for (i = 0; i < pix_mp->num_planes; ++i) {
+ pix_mp->plane_fmt[i].bytesperline = bpl;
+ pix_mp->plane_fmt[i].sizeimage = bpl * pix_mp->height;
+ }
+}
+
+static void mtk_cam_dev_load_default_fmt(struct mtk_cam_dev *cam)
+{
+ struct mtk_cam_video_device *vdev = &cam->vdev;
+ struct v4l2_pix_format_mplane *fmt = &vdev->format;
+
+ fmt->num_planes = 1;
+ fmt->pixelformat = vdev->desc->fmts[0];
+ fmt->width = IMG_MAX_WIDTH;
+ fmt->height = IMG_MAX_HEIGHT;
+
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
+ fmt->field = V4L2_FIELD_NONE;
+ fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
+ fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ vdev->fmtinfo = mtk_cam_format_info_by_fourcc(fmt->pixelformat);
+
+ calc_bpl_size_pix_mp(vdev->fmtinfo, fmt);
+}
+
+/* -----------------------------------------------------------------------------
+ * VB2 Queue Operations
+ */
+
+static int mtk_cam_vb2_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct mtk_cam_video_device *vdev =
+ vb2_queue_to_mtk_cam_video_device(vq);
+ struct mtk_cam_dev *cam = vb2_get_drv_priv(vq);
+ const struct v4l2_pix_format_mplane *fmt = &vdev->format;
+ unsigned int size, default_num_planes, i;
+
+ size = fmt->plane_fmt[0].sizeimage;
+
+ default_num_planes = 1;
+
+ if (*num_planes == 0) {
+ *num_planes = default_num_planes;
+ for (i = 0; i < *num_planes; ++i)
+ sizes[i] = size;
+ } else if (*num_planes != default_num_planes || sizes[0] < size) {
+ return -EINVAL;
+ }
+
+ (*cam->hw_functions->mtk_cam_fbc_init)(cam, *num_buffers);
+
+ return 0;
+}
+
+static int mtk_cam_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+ struct mtk_cam_video_device *vdev =
+ vb2_queue_to_mtk_cam_video_device(vb->vb2_queue);
+ struct mtk_cam_dev *cam = vb2_get_drv_priv(vb->vb2_queue);
+ struct mtk_cam_dev_buffer *buf = to_mtk_cam_dev_buffer(vb);
+ const struct v4l2_pix_format_mplane *fmt = &vdev->format;
+ u32 size;
+ int i;
+
+ for (i = 0; i < vb->num_planes; i++) {
+ size = fmt->plane_fmt[i].sizeimage;
+ if (vb2_plane_size(vb, i) < size) {
+ dev_err(cam->dev, "plane size is too small:%lu<%u\n",
+ vb2_plane_size(vb, i), size);
+ return -EINVAL;
+ }
+ }
+
+ buf->v4l2_buf.field = V4L2_FIELD_NONE;
+
+ for (i = 0; i < vb->num_planes; i++) {
+ size = fmt->plane_fmt[i].sizeimage;
+ vb2_set_plane_payload(vb, i, size);
+ }
+
+ if (!buf->daddr)
+ buf->daddr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ return 0;
+}
+
+static void mtk_cam_vb2_buf_queue(struct vb2_buffer *vb)
+{
+ struct mtk_cam_dev *cam = vb2_get_drv_priv(vb->vb2_queue);
+ struct mtk_cam_dev_buffer *buf = to_mtk_cam_dev_buffer(vb);
+ unsigned long flags;
+
+ /* Add the buffer into the tracking list */
+ spin_lock_irqsave(&cam->buf_list_lock, flags);
+ if (list_empty(&cam->buf_list))
+ (*cam->hw_functions->mtk_cam_update_buffers_add)(cam, buf);
+
+ list_add_tail(&buf->list, &cam->buf_list);
+ (*cam->hw_functions->mtk_cam_fbc_inc)(cam);
+ spin_unlock_irqrestore(&cam->buf_list_lock, flags);
+}
+
+static void mtk_cam_vb2_return_all_buffers(struct mtk_cam_dev *cam,
+ enum vb2_buffer_state state)
+{
+ struct mtk_cam_dev_buffer *buf, *buf_prev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cam->buf_list_lock, flags);
+ list_for_each_entry_safe(buf, buf_prev, &cam->buf_list, list) {
+ buf->daddr = 0ULL;
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state);
+ }
+ spin_unlock_irqrestore(&cam->buf_list_lock, flags);
+}
+
+static void mtk_cam_cmos_vf_enable(struct mtk_cam_dev *cam_dev,
+ bool enable, bool pak_en)
+{
+ if (enable)
+ cam_dev->hw_functions->mtk_cam_cmos_vf_hw_enable(cam_dev);
+ else
+ cam_dev->hw_functions->mtk_cam_cmos_vf_hw_disable(cam_dev);
+}
+
+static int mtk_cam_verify_format(struct mtk_cam_dev *cam)
+{
+ struct mtk_cam_video_device *vdev = &cam->vdev;
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ .pad = MTK_CAM_CIO_PAD_VIDEO,
+ };
+ int ret;
+
+ ret = v4l2_subdev_call(&cam->subdev, pad, get_fmt, NULL, &fmt);
+ if (ret < 0)
+ return ret == -ENOIOCTLCMD ? -EINVAL : ret;
+
+ if (vdev->fmtinfo->code != fmt.format.code ||
+ vdev->format.height != fmt.format.height ||
+ vdev->format.width != fmt.format.width)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int mtk_cam_vb2_start_streaming(struct vb2_queue *vq,
+ unsigned int count)
+{
+ struct mtk_cam_dev *cam = vb2_get_drv_priv(vq);
+ struct mtk_cam_dev_buffer *buf;
+ struct mtk_cam_video_device *vdev =
+ vb2_queue_to_mtk_cam_video_device(vq);
+ struct device *dev = cam->dev;
+ const struct v4l2_pix_format_mplane *fmt = &vdev->format;
+ int ret;
+ unsigned long flags;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret) {
+ dev_err(dev, "failed to get pm_runtime\n");
+ return ret;
+ }
+
+ (*cam->hw_functions->mtk_cam_setup)(cam, fmt->width, fmt->height,
+ fmt->plane_fmt[0].bytesperline, vdev->fmtinfo->code);
+
+ /* Enable CMOS and VF */
+ mtk_cam_cmos_vf_enable(cam, true, true);
+
+ mutex_lock(&cam->op_lock);
+
+ ret = mtk_cam_verify_format(cam);
+ if (ret < 0)
+ goto fail_unlock;
+
+ /* Start streaming of the whole pipeline now*/
+ if (!cam->pipeline.start_count) {
+ ret = media_pipeline_start(vdev->vdev.entity.pads,
+ &cam->pipeline);
+ if (ret) {
+ dev_err(dev, "failed to start pipeline:%d\n", ret);
+ goto fail_unlock;
+ }
+ }
+
+ /* Media links are fixed after media_pipeline_start */
+ cam->stream_count++;
+
+ cam->sequence = (unsigned int)-1;
+
+ /* Stream on the sub-device */
+ ret = v4l2_subdev_enable_streams(&cam->subdev,
+ cam->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].index,
+ BIT(0));
+ if (ret)
+ goto fail_no_stream;
+
+ mutex_unlock(&cam->op_lock);
+
+ /* Adding the buffer into the tracking list */
+ spin_lock_irqsave(&cam->buf_list_lock, flags);
+ buf = list_first_entry_or_null(&cam->buf_list,
+ struct mtk_cam_dev_buffer,
+ list);
+ if (buf)
+ (*cam->hw_functions->mtk_cam_update_buffers_add)(cam, buf);
+ spin_unlock_irqrestore(&cam->buf_list_lock, flags);
+
+ return 0;
+
+fail_no_stream:
+ cam->stream_count--;
+ if (cam->stream_count == 0)
+ media_pipeline_stop(vdev->vdev.entity.pads);
+fail_unlock:
+ mutex_unlock(&cam->op_lock);
+ mtk_cam_vb2_return_all_buffers(cam, VB2_BUF_STATE_QUEUED);
+
+ return ret;
+}
+
+static void mtk_cam_vb2_stop_streaming(struct vb2_queue *vq)
+{
+ struct mtk_cam_dev *cam = vb2_get_drv_priv(vq);
+ struct mtk_cam_video_device *vdev =
+ vb2_queue_to_mtk_cam_video_device(vq);
+
+ /* Disable CMOS and VF */
+ mtk_cam_cmos_vf_enable(cam, false, false);
+
+ mutex_lock(&cam->op_lock);
+
+ v4l2_subdev_disable_streams(&cam->subdev,
+ cam->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].index,
+ BIT(0));
+
+ mtk_cam_vb2_return_all_buffers(cam, VB2_BUF_STATE_ERROR);
+ cam->stream_count--;
+ if (cam->stream_count) {
+ mutex_unlock(&cam->op_lock);
+ return;
+ }
+
+ mutex_unlock(&cam->op_lock);
+
+ media_pipeline_stop(vdev->vdev.entity.pads);
+}
+
+static const struct vb2_ops mtk_cam_vb2_ops = {
+ .queue_setup = mtk_cam_vb2_queue_setup,
+ .buf_prepare = mtk_cam_vb2_buf_prepare,
+ .buf_queue = mtk_cam_vb2_buf_queue,
+ .start_streaming = mtk_cam_vb2_start_streaming,
+ .stop_streaming = mtk_cam_vb2_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 Video IOCTLs
+ */
+
+static int mtk_cam_vidioc_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct mtk_cam_dev *cam = video_drvdata(file);
+
+ strscpy(cap->driver, dev_driver_string(cam->dev), sizeof(cap->driver));
+ strscpy(cap->card, dev_driver_string(cam->dev), sizeof(cap->card));
+
+ return 0;
+}
+
+static int mtk_cam_vidioc_enum_fmt(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
+ const struct mtk_cam_format_info *fmtinfo;
+ unsigned int i;
+
+ /* If mbus_code is not set enumerate all supported formats. */
+ if (!f->mbus_code) {
+ if (f->index >= vdev->desc->num_fmts)
+ return -EINVAL;
+
+ /* f->description is filled in v4l_fill_fmtdesc function */
+ f->pixelformat = vdev->desc->fmts[f->index];
+ f->flags = 0;
+
+ return 0;
+ }
+
+ /*
+ * Otherwise only enumerate supported pixel formats corresponding to
+ * that bus code.
+ */
+ if (f->index)
+ return -EINVAL;
+
+ fmtinfo = mtk_cam_format_info_by_code(f->mbus_code);
+ if (!fmtinfo)
+ return -EINVAL;
+
+ for (i = 0; i < vdev->desc->num_fmts; ++i) {
+ if (vdev->desc->fmts[i] == fmtinfo->fourcc) {
+ f->pixelformat = fmtinfo->fourcc;
+ f->flags = 0;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int mtk_cam_vidioc_g_fmt(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
+
+ f->fmt.pix_mp = vdev->format;
+
+ return 0;
+}
+
+static int mtk_cam_vidioc_try_fmt(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ const struct mtk_cam_format_info *fmtinfo;
+
+ /* Validate pixelformat */
+ if (!mtk_cam_dev_find_fmt(vdev->desc, pix_mp->pixelformat))
+ pix_mp->pixelformat = vdev->desc->fmts[0];
+
+ pix_mp->width = clamp_val(pix_mp->width, IMG_MIN_WIDTH, IMG_MAX_WIDTH);
+ pix_mp->height = clamp_val(pix_mp->height, IMG_MIN_HEIGHT,
+ IMG_MAX_HEIGHT);
+
+ pix_mp->num_planes = 1;
+
+ fmtinfo = mtk_cam_format_info_by_fourcc(pix_mp->pixelformat);
+ calc_bpl_size_pix_mp(fmtinfo, pix_mp);
+
+ /* Constant format fields */
+ pix_mp->colorspace = V4L2_COLORSPACE_SRGB;
+ pix_mp->field = V4L2_FIELD_NONE;
+ pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ pix_mp->quantization = V4L2_QUANTIZATION_DEFAULT;
+ pix_mp->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ return 0;
+}
+
+static int mtk_cam_vidioc_s_fmt(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
+ int ret;
+
+ if (vb2_is_busy(vdev->vdev.queue))
+ return -EBUSY;
+
+ ret = mtk_cam_vidioc_try_fmt(file, fh, f);
+ if (ret)
+ return ret;
+
+ /* Configure to video device */
+ vdev->format = f->fmt.pix_mp;
+ vdev->fmtinfo =
+ mtk_cam_format_info_by_fourcc(f->fmt.pix_mp.pixelformat);
+
+ return 0;
+}
+
+static int mtk_cam_vidioc_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *sizes)
+{
+ struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
+
+ if (sizes->index)
+ return -EINVAL;
+
+ if (!mtk_cam_dev_find_fmt(vdev->desc, sizes->pixel_format))
+ return -EINVAL;
+
+ sizes->type = vdev->desc->frmsizes->type;
+ memcpy(&sizes->stepwise, &vdev->desc->frmsizes->stepwise,
+ sizeof(sizes->stepwise));
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops mtk_cam_v4l2_vcap_ioctl_ops = {
+ .vidioc_querycap = mtk_cam_vidioc_querycap,
+ .vidioc_enum_framesizes = mtk_cam_vidioc_enum_framesizes,
+ .vidioc_enum_fmt_vid_cap = mtk_cam_vidioc_enum_fmt,
+ .vidioc_g_fmt_vid_cap_mplane = mtk_cam_vidioc_g_fmt,
+ .vidioc_s_fmt_vid_cap_mplane = mtk_cam_vidioc_s_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = mtk_cam_vidioc_try_fmt,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations mtk_cam_v4l2_fops = {
+ .unlocked_ioctl = video_ioctl2,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl32 = v4l2_compat_ioctl32,
+#endif
+};
+
+/* -----------------------------------------------------------------------------
+ * Init & Cleanup
+ */
+
+static const u32 stream_out_fmts[] = {
+ /* The 1st entry is the default image format */
+ V4L2_PIX_FMT_SBGGR8,
+ V4L2_PIX_FMT_SGBRG8,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_PIX_FMT_SRGGB8,
+ V4L2_PIX_FMT_UYVY,
+ V4L2_PIX_FMT_VYUY,
+ V4L2_PIX_FMT_YUYV,
+ V4L2_PIX_FMT_YVYU,
+};
+
+static const struct mtk_cam_vdev_desc video_stream = {
+ .fmts = stream_out_fmts,
+ .num_fmts = ARRAY_SIZE(stream_out_fmts),
+ .frmsizes =
+ &(struct v4l2_frmsizeenum){
+ .index = 0,
+ .type = V4L2_FRMSIZE_TYPE_CONTINUOUS,
+ .stepwise = {
+ .max_width = IMG_MAX_WIDTH,
+ .min_width = IMG_MIN_WIDTH,
+ .max_height = IMG_MAX_HEIGHT,
+ .min_height = IMG_MIN_HEIGHT,
+ .step_height = 1,
+ .step_width = 1,
+ },
+ },
+};
+
+int mtk_cam_video_register(struct mtk_cam_dev *cam)
+{
+ struct device *dev = cam->dev;
+ struct mtk_cam_video_device *cam_vdev = &cam->vdev;
+ struct video_device *vdev = &cam_vdev->vdev;
+ struct vb2_queue *vbq = &cam_vdev->vbq;
+ int ret;
+
+ vb2_dma_contig_set_max_seg_size(cam->dev, DMA_BIT_MASK(32));
+
+ cam_vdev->desc = &video_stream;
+
+ /* Initialize mtk_cam_video_device */
+ mtk_cam_dev_load_default_fmt(cam);
+
+ cam_vdev->vdev_pad.flags = MEDIA_PAD_FL_SOURCE;
+
+ /* Initialize media entities */
+ ret = media_entity_pads_init(&vdev->entity, 1, &cam_vdev->vdev_pad);
+ if (ret) {
+ dev_err(dev, "failed to initialize media pad:%d\n", ret);
+ return ret;
+ }
+ cam_vdev->vdev_pad.flags = MEDIA_PAD_FL_SINK;
+
+ vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ vbq->io_modes = VB2_MMAP | VB2_DMABUF;
+ vbq->dev = dev;
+ vbq->ops = &mtk_cam_vb2_ops;
+ vbq->mem_ops = &vb2_dma_contig_memops;
+ vbq->buf_struct_size = sizeof(struct mtk_cam_dev_buffer);
+ /*
+ * TODO: The hardware supports SOF interrupts, switch to a SOF
+ * timestamp source would give better accuracy, but first requires
+ * extending the V4L2 API to support it.
+ */
+ vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
+ | V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
+
+ /* No minimum buffers limitation */
+ vbq->min_queued_buffers = 0;
+ vbq->drv_priv = cam;
+
+ vbq->lock = &cam_vdev->vdev_lock;
+ ret = vb2_queue_init(vbq);
+ if (ret) {
+ dev_err(dev, "failed to init. vb2 queue:%d\n", ret);
+ goto fail_media_clean;
+ }
+
+ /* Initialize vdev */
+ snprintf(vdev->name, sizeof(vdev->name), "%s video stream",
+ dev_name(dev));
+
+ /* Set cap/type/ioctl_ops of the video device */
+ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING
+ | V4L2_CAP_IO_MC;
+ vdev->ioctl_ops = &mtk_cam_v4l2_vcap_ioctl_ops;
+ vdev->fops = &mtk_cam_v4l2_fops;
+ vdev->release = video_device_release_empty;
+ vdev->lock = &cam_vdev->vdev_lock;
+ vdev->v4l2_dev = cam->subdev.v4l2_dev;
+ vdev->queue = &cam_vdev->vbq;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->entity.function = MEDIA_ENT_F_IO_V4L;
+ video_set_drvdata(vdev, cam);
+
+ /* Initialize miscellaneous variables */
+ mutex_init(&cam_vdev->vdev_lock);
+ INIT_LIST_HEAD(&cam->buf_list);
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ dev_err(dev, "failed to register vde:%d\n", ret);
+ goto fail_vb2_rel;
+ }
+
+ /* Create link between the video pad and the subdev pad. */
+ ret = media_create_pad_link(&cam->subdev.entity,
+ MTK_CAM_CIO_PAD_VIDEO,
+ &vdev->entity, 0,
+ MEDIA_LNK_FL_IMMUTABLE
+ | MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ goto fail_vdev_ureg;
+
+ return 0;
+
+fail_vdev_ureg:
+ video_unregister_device(vdev);
+fail_vb2_rel:
+ mutex_destroy(&cam_vdev->vdev_lock);
+ vb2_queue_release(vbq);
+fail_media_clean:
+ media_entity_cleanup(&vdev->entity);
+
+ return ret;
+}
+
+void mtk_cam_video_unregister(struct mtk_cam_video_device *vdev)
+{
+ video_unregister_device(&vdev->vdev);
+ vb2_queue_release(&vdev->vbq);
+ media_entity_cleanup(&vdev->vdev.entity);
+ mutex_destroy(&vdev->vdev_lock);
+ vb2_dma_contig_clear_max_seg_size(&vdev->vdev.dev);
+}
--
2.47.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH v7 5/5] arm64: dts: mediatek: mt8365: Add support for camera
2024-11-21 8:53 [PATCH v7 0/5] Add Mediatek ISP3.0 Julien Stephan
` (3 preceding siblings ...)
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
@ 2024-11-21 8:53 ` Julien Stephan
4 siblings, 0 replies; 39+ messages in thread
From: Julien Stephan @ 2024-11-21 8:53 UTC (permalink / raw)
To: Laurent Pinchart, Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Julien Stephan
Add base support for cameras for mt8365 platforms. This requires nodes
for the sensor interface, camsv, and CSI receivers.
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Julien Stephan <jstephan@baylibre.com>
---
arch/arm64/boot/dts/mediatek/mt8365.dtsi | 125 +++++++++++++++++++++++++++++++
1 file changed, 125 insertions(+)
diff --git a/arch/arm64/boot/dts/mediatek/mt8365.dtsi b/arch/arm64/boot/dts/mediatek/mt8365.dtsi
index 9c91fe8ea0f969770a611f90b593683f93ff3e22..f3aae8d76cbece5779fe0b23139d594c0ea52579 100644
--- a/arch/arm64/boot/dts/mediatek/mt8365.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt8365.dtsi
@@ -12,6 +12,7 @@
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/phy/phy.h>
#include <dt-bindings/power/mediatek,mt8365-power.h>
+#include <dt-bindings/memory/mediatek,mt8365-larb-port.h>
/ {
compatible = "mediatek,mt8365";
@@ -704,6 +705,23 @@ ethernet: ethernet@112a0000 {
status = "disabled";
};
+ mipi_csi0: mipi-csi0@11c10000 {
+ compatible = "mediatek,mt8365-csi-rx";
+ reg = <0 0x11c10000 0 0x2000>;
+ status = "disabled";
+ num-lanes = <4>;
+ #phy-cells = <1>;
+ };
+
+ mipi_csi1: mipi-csi1@11c12000 {
+ compatible = "mediatek,mt8365-csi-rx";
+ reg = <0 0x11c12000 0 0x2000>;
+ phy-type = <PHY_TYPE_DPHY>;
+ status = "disabled";
+ num-lanes = <4>;
+ #phy-cells = <0>;
+ };
+
u3phy: t-phy@11cc0000 {
compatible = "mediatek,mt8365-tphy", "mediatek,generic-tphy-v2";
#address-cells = <1>;
@@ -774,6 +792,113 @@ larb2: larb@15001000 {
mediatek,larb-id = <2>;
};
+ seninf: seninf@15040000 {
+ compatible = "mediatek,mt8365-seninf";
+ reg = <0 0x15040000 0 0x6000>;
+ interrupts = <GIC_SPI 210 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&camsys CLK_CAM_SENIF>,
+ <&topckgen CLK_TOP_SENIF_SEL>;
+ clock-names = "camsys", "top_mux";
+
+ power-domains = <&spm MT8365_POWER_DOMAIN_CAM>;
+
+ phys = <&mipi_csi0 PHY_TYPE_DPHY>, <&mipi_csi1>;
+ phy-names = "csi0", "csi1";
+
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ };
+
+ port@1 {
+ reg = <1>;
+ };
+
+ port@2 {
+ reg = <2>;
+ };
+
+ port@3 {
+ reg = <3>;
+ };
+
+ port@4 {
+ reg = <4>;
+ seninf_camsv1_endpoint: endpoint {
+ remote-endpoint =
+ <&camsv1_endpoint>;
+ };
+ };
+
+ port@5 {
+ reg = <5>;
+ seninf_camsv2_endpoint: endpoint {
+ remote-endpoint =
+ <&camsv2_endpoint>;
+ };
+ };
+ };
+ };
+
+ camsv1: camsv@15050000 {
+ compatible = "mediatek,mt8365-camsv";
+ reg = <0 0x15050000 0 0x0040>,
+ <0 0x15050208 0 0x0020>,
+ <0 0x15050400 0 0x0100>;
+ interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&camsys CLK_CAM>,
+ <&camsys CLK_CAMTG>,
+ <&camsys CLK_CAMSV0>;
+ clock-names = "cam", "camtg", "camsv";
+ iommus = <&iommu M4U_PORT_CAM_IMGO>;
+ mediatek,larb = <&larb2>;
+ power-domains = <&spm MT8365_POWER_DOMAIN_CAM>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ camsv1_endpoint: endpoint {
+ remote-endpoint = <&seninf_camsv1_endpoint>;
+ };
+ };
+ };
+ };
+
+ camsv2: camsv@15050800 {
+ compatible = "mediatek,mt8365-camsv";
+ reg = <0 0x15050800 0 0x0040>,
+ <0 0x15050228 0 0x0020>,
+ <0 0x15050c00 0 0x0100>;
+ interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&camsys CLK_CAM>,
+ <&camsys CLK_CAMTG>,
+ <&camsys CLK_CAMSV1>;
+ clock-names = "cam", "camtg", "camsv";
+ iommus = <&iommu M4U_PORT_CAM_IMGO>;
+ mediatek,larb = <&larb2>;
+ power-domains = <&spm MT8365_POWER_DOMAIN_CAM>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ camsv2_endpoint: endpoint {
+ remote-endpoint = <&seninf_camsv2_endpoint>;
+ };
+ };
+ };
+ };
+
vdecsys: syscon@16000000 {
compatible = "mediatek,mt8365-vdecsys", "syscon";
reg = <0 0x16000000 0 0x1000>;
--
2.47.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
@ 2024-11-22 7:54 ` CK Hu (胡俊光)
2024-11-22 9:16 ` Julien Stephan
2024-11-22 8:41 ` CK Hu (胡俊光)
` (9 subsequent siblings)
10 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-22 7:54 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +static void mtk_cam_cmos_vf_enable(struct mtk_cam_dev *cam_dev,
> + bool enable, bool pak_en)
> +{
> + if (enable)
> + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_enable(cam_dev);
Directly call mtk_camsv30_cmos_vf_hw_enable().
This has discussed in previous version [1].
[1] https://patchwork.kernel.org/project/linux-mediatek/patch/20240729-add-mtk-isp-3-0-support-v6-4-c374c9e0c672@baylibre.com/#25966327
Regards,
CK
> + else
> + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_disable(cam_dev);
> +}
> +
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
2024-11-22 7:54 ` CK Hu (胡俊光)
@ 2024-11-22 8:41 ` CK Hu (胡俊光)
2024-11-22 9:25 ` Julien Stephan
2024-11-22 9:18 ` CK Hu (胡俊光)
` (8 subsequent siblings)
10 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-22 8:41 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +static irqreturn_t isp_irq_camsv30(int irq, void *data)
> +{
> + struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
> + struct mtk_cam_dev_buffer *buf;
> + unsigned int irq_status;
> +
> + spin_lock(&cam_dev->buf_list_lock);
> +
> + irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
> +
> + if (irq_status & INT_ST_MASK_CAMSV_ERR)
> + dev_err(cam_dev->dev, "irq error 0x%lx\n",
> + irq_status & INT_ST_MASK_CAMSV_ERR);
> +
> + /* De-queue frame */
> + if (irq_status & CAMSV_IRQ_PASS1_DON) {
> + cam_dev->sequence++;
> +
> + buf = list_first_entry_or_null(&cam_dev->buf_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + if (buf) {
> + buf->v4l2_buf.sequence = cam_dev->sequence;
> + buf->v4l2_buf.vb2_buf.timestamp =
> + ktime_get_ns();
> + vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
> + VB2_BUF_STATE_DONE);
> + list_del(&buf->list);
> + }
> +
> + buf = list_first_entry_or_null(&cam_dev->buf_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + if (buf)
> + mtk_camsv30_update_buffers_add(cam_dev, buf);
If buf == NULL, so hardware would automatically stop DMA?
I don't know how this hardware work.
Below is my imagine about this hardware.
1. Software use CAMSV_IMGO_FBC_RCNT_INC to increase software buffer index.
2. Hardware has a hardware buffer index. After hardware finish one frame, hardware buffer index increase.
3. After software buffer index increase, hardware start DMA.
4. When hardware buffer index is equal to software buffer index, hardware automatically stop DMA.
Does the hardware work as my imagine?
If hardware could automatically stop DMA, add comment to describe.
If hardware could not automatically stop DMA, software should do something to stop DMA when buf == NULL.
Regards,
CK
> + }
> +
> + spin_unlock(&cam_dev->buf_list_lock);
> +
> + return IRQ_HANDLED;
> +}
> +
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 7:54 ` CK Hu (胡俊光)
@ 2024-11-22 9:16 ` Julien Stephan
2024-11-22 9:28 ` CK Hu (胡俊光)
0 siblings, 1 reply; 39+ messages in thread
From: Julien Stephan @ 2024-11-22 9:16 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
linux-mediatek@lists.infradead.org, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, paul.elder@ideasonboard.com,
linux-arm-kernel@lists.infradead.org, fsylvestre@baylibre.com,
pnguyen@baylibre.com
Le ven. 22 nov. 2024 à 08:54, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
>
> Hi, Julien:
>
> On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > External email : Please do not click links or open attachments until you have verified the sender or the content.
> >
> >
> > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> >
> > This driver provides a path to bypass the SoC ISP so that image data
> > coming from the SENINF can go directly into memory without any image
> > processing. This allows the use of an external ISP.
> >
> > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > [Paul Elder fix irq locking]
> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > ---
>
> [snip]
>
> > +static void mtk_cam_cmos_vf_enable(struct mtk_cam_dev *cam_dev,
> > + bool enable, bool pak_en)
> > +{
> > + if (enable)
> > + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_enable(cam_dev);
>
> Directly call mtk_camsv30_cmos_vf_hw_enable().
> This has discussed in previous version [1].
>
> [1] https://patchwork.kernel.org/project/linux-mediatek/patch/20240729-add-mtk-isp-3-0-support-v6-4-c374c9e0c672@baylibre.com/#25966327
>
Hi CK,
I forgot about that discussion sorry :/
I guess you want me to completely remove the mtk_cam_hw_functions struct?
In that case, what do you prefer:
- keep mtk_camsv30_hw.c and put signatures in mtkcamsv30_hw.h and
include mtk_camsv30_hw.h in mtk_camsv_video.c
- rename mtk_camsv30_hw.c to mtk_camsv_hw.c (and all functions) and
put signatures in mtk_camsv_hw.h
Cheers
Julien
> Regards,
> CK
>
> > + else
> > + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_disable(cam_dev);
> > +}
> > +
>
> >
>
> ************* MEDIATEK Confidentiality Notice ********************
> The information contained in this e-mail message (including any
> attachments) may be confidential, proprietary, privileged, or otherwise
> exempt from disclosure under applicable laws. It is intended to be
> conveyed only to the designated recipient(s). Any use, dissemination,
> distribution, printing, retaining or copying of this e-mail (including its
> attachments) by unintended recipient(s) is strictly prohibited and may
> be unlawful. If you are not an intended recipient of this e-mail, or believe
> that you have received this e-mail in error, please notify the sender
> immediately (by replying to this e-mail), delete any and all copies of
> this e-mail (including any attachments) from your system, and do not
> disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
2024-11-22 7:54 ` CK Hu (胡俊光)
2024-11-22 8:41 ` CK Hu (胡俊光)
@ 2024-11-22 9:18 ` CK Hu (胡俊光)
2024-11-22 9:44 ` Julien Stephan
2024-11-25 6:05 ` CK Hu (胡俊光)
` (7 subsequent siblings)
10 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-22 9:18 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +static void mtk_cam_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> + struct mtk_cam_dev *cam = vb2_get_drv_priv(vb->vb2_queue);
> + struct mtk_cam_dev_buffer *buf = to_mtk_cam_dev_buffer(vb);
> + unsigned long flags;
> +
> + /* Add the buffer into the tracking list */
> + spin_lock_irqsave(&cam->buf_list_lock, flags);
> + if (list_empty(&cam->buf_list))
> + (*cam->hw_functions->mtk_cam_update_buffers_add)(cam, buf);
> +
> + list_add_tail(&buf->list, &cam->buf_list);
> + (*cam->hw_functions->mtk_cam_fbc_inc)(cam);
I think fbc_inc should together with update_buffers_add.
After update_buffers_add then fbc_inc.
So squash fbc_inc into update_buffers_add and drop fbc_inc function.
Regards,
CK
> + spin_unlock_irqrestore(&cam->buf_list_lock, flags);
> +}
> +
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 8:41 ` CK Hu (胡俊光)
@ 2024-11-22 9:25 ` Julien Stephan
2024-11-22 9:49 ` CK Hu (胡俊光)
0 siblings, 1 reply; 39+ messages in thread
From: Julien Stephan @ 2024-11-22 9:25 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
linux-mediatek@lists.infradead.org, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, paul.elder@ideasonboard.com,
linux-arm-kernel@lists.infradead.org, fsylvestre@baylibre.com,
pnguyen@baylibre.com
Le ven. 22 nov. 2024 à 09:41, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
>
> Hi, Julien:
>
> On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > External email : Please do not click links or open attachments until you have verified the sender or the content.
> >
> >
> > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> >
> > This driver provides a path to bypass the SoC ISP so that image data
> > coming from the SENINF can go directly into memory without any image
> > processing. This allows the use of an external ISP.
> >
> > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > [Paul Elder fix irq locking]
> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > ---
>
> [snip]
>
> > +static irqreturn_t isp_irq_camsv30(int irq, void *data)
> > +{
> > + struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
> > + struct mtk_cam_dev_buffer *buf;
> > + unsigned int irq_status;
> > +
> > + spin_lock(&cam_dev->buf_list_lock);
> > +
> > + irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
> > +
> > + if (irq_status & INT_ST_MASK_CAMSV_ERR)
> > + dev_err(cam_dev->dev, "irq error 0x%lx\n",
> > + irq_status & INT_ST_MASK_CAMSV_ERR);
> > +
> > + /* De-queue frame */
> > + if (irq_status & CAMSV_IRQ_PASS1_DON) {
> > + cam_dev->sequence++;
> > +
> > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > + struct mtk_cam_dev_buffer,
> > + list);
> > + if (buf) {
> > + buf->v4l2_buf.sequence = cam_dev->sequence;
> > + buf->v4l2_buf.vb2_buf.timestamp =
> > + ktime_get_ns();
> > + vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
> > + VB2_BUF_STATE_DONE);
> > + list_del(&buf->list);
> > + }
> > +
> > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > + struct mtk_cam_dev_buffer,
> > + list);
> > + if (buf)
> > + mtk_camsv30_update_buffers_add(cam_dev, buf);
>
> If buf == NULL, so hardware would automatically stop DMA?
> I don't know how this hardware work.
> Below is my imagine about this hardware.
>
> 1. Software use CAMSV_IMGO_FBC_RCNT_INC to increase software buffer index.
> 2. Hardware has a hardware buffer index. After hardware finish one frame, hardware buffer index increase.
> 3. After software buffer index increase, hardware start DMA.
> 4. When hardware buffer index is equal to software buffer index, hardware automatically stop DMA.
>
> Does the hardware work as my imagine?
> If hardware could automatically stop DMA, add comment to describe.
> If hardware could not automatically stop DMA, software should do something to stop DMA when buf == NULL.
>
You are right except that dma is not stopped but frames are
automatically dropped by hardware until a new buffer is enqueued and
software uses CAMSV_IMGO_FBC_RCNT_INC to increase the software buffer
index.
What about adding the following comment:
/*
* If there is no user buffer available, hardware will drop automatically
* frames until buf_queue is called
*/
Let me know if that works for you
Cheers
Julien
> Regards,
> CK
>
> > + }
> > +
> > + spin_unlock(&cam_dev->buf_list_lock);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
>
> ************* MEDIATEK Confidentiality Notice ********************
> The information contained in this e-mail message (including any
> attachments) may be confidential, proprietary, privileged, or otherwise
> exempt from disclosure under applicable laws. It is intended to be
> conveyed only to the designated recipient(s). Any use, dissemination,
> distribution, printing, retaining or copying of this e-mail (including its
> attachments) by unintended recipient(s) is strictly prohibited and may
> be unlawful. If you are not an intended recipient of this e-mail, or believe
> that you have received this e-mail in error, please notify the sender
> immediately (by replying to this e-mail), delete any and all copies of
> this e-mail (including any attachments) from your system, and do not
> disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 9:16 ` Julien Stephan
@ 2024-11-22 9:28 ` CK Hu (胡俊光)
2024-11-24 6:11 ` Laurent Pinchart
2024-11-25 8:26 ` CK Hu (胡俊光)
0 siblings, 2 replies; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-22 9:28 UTC (permalink / raw)
To: Julien Stephan
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno,
pnguyen@baylibre.com
Hi, Julien:
On Fri, 2024-11-22 at 10:16 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> Le ven. 22 nov. 2024 à 08:54, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> >
> > Hi, Julien:
> >
> > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > >
> > >
> > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > >
> > > This driver provides a path to bypass the SoC ISP so that image data
> > > coming from the SENINF can go directly into memory without any image
> > > processing. This allows the use of an external ISP.
> > >
> > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > [Paul Elder fix irq locking]
> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > ---
> >
> > [snip]
> >
> > > +static void mtk_cam_cmos_vf_enable(struct mtk_cam_dev *cam_dev,
> > > + bool enable, bool pak_en)
> > > +{
> > > + if (enable)
> > > + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_enable(cam_dev);
> >
> > Directly call mtk_camsv30_cmos_vf_hw_enable().
> > This has discussed in previous version [1].
> >
> > [1] https://urldefense.com/v3/__https://patchwork.kernel.org/project/linux-mediatek/patch/20240729-add-mtk-isp-3-0-support-v6-4-c374c9e0c672@baylibre.com/*25966327__;Iw!!CTRNKA9wMg0ARbw!lydgLydtAuzr-BC5qArz3AEzOM0iSSr6TXifwab1kPvWVJLy0k7rUiasR_goMAF_6XYmPIpErGF6CdLkPQ$
> >
>
> Hi CK,
>
> I forgot about that discussion sorry :/
> I guess you want me to completely remove the mtk_cam_hw_functions struct?
> In that case, what do you prefer:
> - keep mtk_camsv30_hw.c and put signatures in mtkcamsv30_hw.h and
> include mtk_camsv30_hw.h in mtk_camsv_video.c
> - rename mtk_camsv30_hw.c to mtk_camsv_hw.c (and all functions) and
> put signatures in mtk_camsv_hw.h
I prefer the second one.
Regards,
CK
>
> Cheers
> Julien
>
> > Regards,
> > CK
> >
> > > + else
> > > + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_disable(cam_dev);
> > > +}
> > > +
> > >
> >
> > ************* MEDIATEK Confidentiality Notice ********************
> > The information contained in this e-mail message (including any
> > attachments) may be confidential, proprietary, privileged, or otherwise
> > exempt from disclosure under applicable laws. It is intended to be
> > conveyed only to the designated recipient(s). Any use, dissemination,
> > distribution, printing, retaining or copying of this e-mail (including its
> > attachments) by unintended recipient(s) is strictly prohibited and may
> > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > that you have received this e-mail in error, please notify the sender
> > immediately (by replying to this e-mail), delete any and all copies of
> > this e-mail (including any attachments) from your system, and do not
> > disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 9:18 ` CK Hu (胡俊光)
@ 2024-11-22 9:44 ` Julien Stephan
2024-11-25 5:54 ` CK Hu (胡俊光)
0 siblings, 1 reply; 39+ messages in thread
From: Julien Stephan @ 2024-11-22 9:44 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
linux-mediatek@lists.infradead.org, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, paul.elder@ideasonboard.com,
linux-arm-kernel@lists.infradead.org, fsylvestre@baylibre.com,
pnguyen@baylibre.com
Le ven. 22 nov. 2024 à 10:19, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
>
> Hi, Julien:
>
> On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > External email : Please do not click links or open attachments until you have verified the sender or the content.
> >
> >
> > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> >
> > This driver provides a path to bypass the SoC ISP so that image data
> > coming from the SENINF can go directly into memory without any image
> > processing. This allows the use of an external ISP.
> >
> > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > [Paul Elder fix irq locking]
> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > ---
>
> [snip]
>
> > +static void mtk_cam_vb2_buf_queue(struct vb2_buffer *vb)
> > +{
> > + struct mtk_cam_dev *cam = vb2_get_drv_priv(vb->vb2_queue);
> > + struct mtk_cam_dev_buffer *buf = to_mtk_cam_dev_buffer(vb);
> > + unsigned long flags;
> > +
> > + /* Add the buffer into the tracking list */
> > + spin_lock_irqsave(&cam->buf_list_lock, flags);
> > + if (list_empty(&cam->buf_list))
> > + (*cam->hw_functions->mtk_cam_update_buffers_add)(cam, buf);
> > +
> > + list_add_tail(&buf->list, &cam->buf_list);
> > + (*cam->hw_functions->mtk_cam_fbc_inc)(cam);
>
> I think fbc_inc should together with update_buffers_add.
> After update_buffers_add then fbc_inc.
> So squash fbc_inc into update_buffers_add and drop fbc_inc function.
>
No, this is not true.
mtk_cam_update_buffers_add is used to indicate which buffer should be
used for dma write. This is the first entry in the buf list.
mtk_cam_fbc_inc is used to increase the number of available user space buffers.
If the buffer list is not empty and user space calls buf_queue again,
we need to call mtk_cam_fbc_inc to increase the number of available
user buffers, but we don't want to change the buffer for DMA write.
mtk_camsv30_update_buffers_add is called on irq to update the address
to the next buffer (if available).
Maybe the name mtk_camsv30_update_buffers_add is confusing then?
What do you think about:
- mtk_camsv30_update_buffers_add -> mtk_camsv30_update_buffers_address
- mtk_cam_fbc_inc -> mtk_camsv30_buffer_add
Cheers
Julien
> Regards,
> CK
>
> > + spin_unlock_irqrestore(&cam->buf_list_lock, flags);
> > +}
> > +
>
> >
>
> ************* MEDIATEK Confidentiality Notice ********************
> The information contained in this e-mail message (including any
> attachments) may be confidential, proprietary, privileged, or otherwise
> exempt from disclosure under applicable laws. It is intended to be
> conveyed only to the designated recipient(s). Any use, dissemination,
> distribution, printing, retaining or copying of this e-mail (including its
> attachments) by unintended recipient(s) is strictly prohibited and may
> be unlawful. If you are not an intended recipient of this e-mail, or believe
> that you have received this e-mail in error, please notify the sender
> immediately (by replying to this e-mail), delete any and all copies of
> this e-mail (including any attachments) from your system, and do not
> disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 9:25 ` Julien Stephan
@ 2024-11-22 9:49 ` CK Hu (胡俊光)
2024-11-22 9:50 ` Julien Stephan
0 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-22 9:49 UTC (permalink / raw)
To: Julien Stephan
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno,
pnguyen@baylibre.com
On Fri, 2024-11-22 at 10:25 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> Le ven. 22 nov. 2024 à 09:41, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> >
> > Hi, Julien:
> >
> > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > >
> > >
> > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > >
> > > This driver provides a path to bypass the SoC ISP so that image data
> > > coming from the SENINF can go directly into memory without any image
> > > processing. This allows the use of an external ISP.
> > >
> > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > [Paul Elder fix irq locking]
> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > ---
> >
> > [snip]
> >
> > > +static irqreturn_t isp_irq_camsv30(int irq, void *data)
> > > +{
> > > + struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
> > > + struct mtk_cam_dev_buffer *buf;
> > > + unsigned int irq_status;
> > > +
> > > + spin_lock(&cam_dev->buf_list_lock);
> > > +
> > > + irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
> > > +
> > > + if (irq_status & INT_ST_MASK_CAMSV_ERR)
> > > + dev_err(cam_dev->dev, "irq error 0x%lx\n",
> > > + irq_status & INT_ST_MASK_CAMSV_ERR);
> > > +
> > > + /* De-queue frame */
> > > + if (irq_status & CAMSV_IRQ_PASS1_DON) {
> > > + cam_dev->sequence++;
> > > +
> > > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > > + struct mtk_cam_dev_buffer,
> > > + list);
> > > + if (buf) {
> > > + buf->v4l2_buf.sequence = cam_dev->sequence;
> > > + buf->v4l2_buf.vb2_buf.timestamp =
> > > + ktime_get_ns();
> > > + vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
> > > + VB2_BUF_STATE_DONE);
> > > + list_del(&buf->list);
> > > + }
> > > +
> > > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > > + struct mtk_cam_dev_buffer,
> > > + list);
> > > + if (buf)
> > > + mtk_camsv30_update_buffers_add(cam_dev, buf);
> >
> > If buf == NULL, so hardware would automatically stop DMA?
> > I don't know how this hardware work.
> > Below is my imagine about this hardware.
> >
> > 1. Software use CAMSV_IMGO_FBC_RCNT_INC to increase software buffer index.
> > 2. Hardware has a hardware buffer index. After hardware finish one frame, hardware buffer index increase.
> > 3. After software buffer index increase, hardware start DMA.
> > 4. When hardware buffer index is equal to software buffer index, hardware automatically stop DMA.
> >
> > Does the hardware work as my imagine?
> > If hardware could automatically stop DMA, add comment to describe.
> > If hardware could not automatically stop DMA, software should do something to stop DMA when buf == NULL.
> >
>
> You are right except that dma is not stopped but frames are
> automatically dropped by hardware until a new buffer is enqueued and
> software uses CAMSV_IMGO_FBC_RCNT_INC to increase the software buffer
> index.
>
> What about adding the following comment:
>
> /*
> * If there is no user buffer available, hardware will drop automatically
> * frames until buf_queue is called
> */
You say DMA is not stopped. Do you mean hardware still write data into latest buffer which would be dequeued to user space?
I think hardware should not write data into the buffer which has been take away by user space.
I think software should do something to stop DMA. Maybe use mtk_camsv30_cmos_vf_hw_disable() to stop DMA.
Regards,
CK
>
> Let me know if that works for you
>
> Cheers
> Julien
>
> > Regards,
> > CK
> >
> > > + }
> > > +
> > > + spin_unlock(&cam_dev->buf_list_lock);
> > > +
> > > + return IRQ_HANDLED;
> > > +}
> > > +
> >
> > ************* MEDIATEK Confidentiality Notice ********************
> > The information contained in this e-mail message (including any
> > attachments) may be confidential, proprietary, privileged, or otherwise
> > exempt from disclosure under applicable laws. It is intended to be
> > conveyed only to the designated recipient(s). Any use, dissemination,
> > distribution, printing, retaining or copying of this e-mail (including its
> > attachments) by unintended recipient(s) is strictly prohibited and may
> > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > that you have received this e-mail in error, please notify the sender
> > immediately (by replying to this e-mail), delete any and all copies of
> > this e-mail (including any attachments) from your system, and do not
> > disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 9:49 ` CK Hu (胡俊光)
@ 2024-11-22 9:50 ` Julien Stephan
2024-11-22 10:01 ` CK Hu (胡俊光)
0 siblings, 1 reply; 39+ messages in thread
From: Julien Stephan @ 2024-11-22 9:50 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno,
pnguyen@baylibre.com
Le ven. 22 nov. 2024 à 10:49, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
>
> On Fri, 2024-11-22 at 10:25 +0100, Julien Stephan wrote:
> > External email : Please do not click links or open attachments until you have verified the sender or the content.
> >
> >
> > Le ven. 22 nov. 2024 à 09:41, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> > >
> > > Hi, Julien:
> > >
> > > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > > >
> > > >
> > > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > >
> > > > This driver provides a path to bypass the SoC ISP so that image data
> > > > coming from the SENINF can go directly into memory without any image
> > > > processing. This allows the use of an external ISP.
> > > >
> > > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > > [Paul Elder fix irq locking]
> > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > > ---
> > >
> > > [snip]
> > >
> > > > +static irqreturn_t isp_irq_camsv30(int irq, void *data)
> > > > +{
> > > > + struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
> > > > + struct mtk_cam_dev_buffer *buf;
> > > > + unsigned int irq_status;
> > > > +
> > > > + spin_lock(&cam_dev->buf_list_lock);
> > > > +
> > > > + irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
> > > > +
> > > > + if (irq_status & INT_ST_MASK_CAMSV_ERR)
> > > > + dev_err(cam_dev->dev, "irq error 0x%lx\n",
> > > > + irq_status & INT_ST_MASK_CAMSV_ERR);
> > > > +
> > > > + /* De-queue frame */
> > > > + if (irq_status & CAMSV_IRQ_PASS1_DON) {
> > > > + cam_dev->sequence++;
> > > > +
> > > > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > > > + struct mtk_cam_dev_buffer,
> > > > + list);
> > > > + if (buf) {
> > > > + buf->v4l2_buf.sequence = cam_dev->sequence;
> > > > + buf->v4l2_buf.vb2_buf.timestamp =
> > > > + ktime_get_ns();
> > > > + vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
> > > > + VB2_BUF_STATE_DONE);
> > > > + list_del(&buf->list);
> > > > + }
> > > > +
> > > > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > > > + struct mtk_cam_dev_buffer,
> > > > + list);
> > > > + if (buf)
> > > > + mtk_camsv30_update_buffers_add(cam_dev, buf);
> > >
> > > If buf == NULL, so hardware would automatically stop DMA?
> > > I don't know how this hardware work.
> > > Below is my imagine about this hardware.
> > >
> > > 1. Software use CAMSV_IMGO_FBC_RCNT_INC to increase software buffer index.
> > > 2. Hardware has a hardware buffer index. After hardware finish one frame, hardware buffer index increase.
> > > 3. After software buffer index increase, hardware start DMA.
> > > 4. When hardware buffer index is equal to software buffer index, hardware automatically stop DMA.
> > >
> > > Does the hardware work as my imagine?
> > > If hardware could automatically stop DMA, add comment to describe.
> > > If hardware could not automatically stop DMA, software should do something to stop DMA when buf == NULL.
> > >
> >
> > You are right except that dma is not stopped but frames are
> > automatically dropped by hardware until a new buffer is enqueued and
> > software uses CAMSV_IMGO_FBC_RCNT_INC to increase the software buffer
> > index.
> >
> > What about adding the following comment:
> >
> > /*
> > * If there is no user buffer available, hardware will drop automatically
> > * frames until buf_queue is called
> > */
>
> You say DMA is not stopped. Do you mean hardware still write data into latest buffer which would be dequeued to user space?
> I think hardware should not write data into the buffer which has been take away by user space.
> I think software should do something to stop DMA. Maybe use mtk_camsv30_cmos_vf_hw_disable() to stop DMA.
>
No, I said frame is dropped.. It does not write any data.
> Regards,
> CK
>
> >
> > Let me know if that works for you
> >
> > Cheers
> > Julien
> >
> > > Regards,
> > > CK
> > >
> > > > + }
> > > > +
> > > > + spin_unlock(&cam_dev->buf_list_lock);
> > > > +
> > > > + return IRQ_HANDLED;
> > > > +}
> > > > +
> > >
> > > ************* MEDIATEK Confidentiality Notice ********************
> > > The information contained in this e-mail message (including any
> > > attachments) may be confidential, proprietary, privileged, or otherwise
> > > exempt from disclosure under applicable laws. It is intended to be
> > > conveyed only to the designated recipient(s). Any use, dissemination,
> > > distribution, printing, retaining or copying of this e-mail (including its
> > > attachments) by unintended recipient(s) is strictly prohibited and may
> > > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > > that you have received this e-mail in error, please notify the sender
> > > immediately (by replying to this e-mail), delete any and all copies of
> > > this e-mail (including any attachments) from your system, and do not
> > > disclose the content of this e-mail to any other person. Thank you!
>
> ************* MEDIATEK Confidentiality Notice ********************
> The information contained in this e-mail message (including any
> attachments) may be confidential, proprietary, privileged, or otherwise
> exempt from disclosure under applicable laws. It is intended to be
> conveyed only to the designated recipient(s). Any use, dissemination,
> distribution, printing, retaining or copying of this e-mail (including its
> attachments) by unintended recipient(s) is strictly prohibited and may
> be unlawful. If you are not an intended recipient of this e-mail, or believe
> that you have received this e-mail in error, please notify the sender
> immediately (by replying to this e-mail), delete any and all copies of
> this e-mail (including any attachments) from your system, and do not
> disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 9:50 ` Julien Stephan
@ 2024-11-22 10:01 ` CK Hu (胡俊光)
2024-11-22 12:05 ` Julien Stephan
0 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-22 10:01 UTC (permalink / raw)
To: Julien Stephan
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno,
pnguyen@baylibre.com
On Fri, 2024-11-22 at 10:50 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> Le ven. 22 nov. 2024 à 10:49, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> >
> > On Fri, 2024-11-22 at 10:25 +0100, Julien Stephan wrote:
> > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > >
> > >
> > > Le ven. 22 nov. 2024 à 09:41, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> > > >
> > > > Hi, Julien:
> > > >
> > > > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > > > >
> > > > >
> > > > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > >
> > > > > This driver provides a path to bypass the SoC ISP so that image data
> > > > > coming from the SENINF can go directly into memory without any image
> > > > > processing. This allows the use of an external ISP.
> > > > >
> > > > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > > > [Paul Elder fix irq locking]
> > > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > > > ---
> > > >
> > > > [snip]
> > > >
> > > > > +static irqreturn_t isp_irq_camsv30(int irq, void *data)
> > > > > +{
> > > > > + struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
> > > > > + struct mtk_cam_dev_buffer *buf;
> > > > > + unsigned int irq_status;
> > > > > +
> > > > > + spin_lock(&cam_dev->buf_list_lock);
> > > > > +
> > > > > + irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
> > > > > +
> > > > > + if (irq_status & INT_ST_MASK_CAMSV_ERR)
> > > > > + dev_err(cam_dev->dev, "irq error 0x%lx\n",
> > > > > + irq_status & INT_ST_MASK_CAMSV_ERR);
> > > > > +
> > > > > + /* De-queue frame */
> > > > > + if (irq_status & CAMSV_IRQ_PASS1_DON) {
> > > > > + cam_dev->sequence++;
> > > > > +
> > > > > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > > > > + struct mtk_cam_dev_buffer,
> > > > > + list);
> > > > > + if (buf) {
> > > > > + buf->v4l2_buf.sequence = cam_dev->sequence;
> > > > > + buf->v4l2_buf.vb2_buf.timestamp =
> > > > > + ktime_get_ns();
> > > > > + vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
> > > > > + VB2_BUF_STATE_DONE);
> > > > > + list_del(&buf->list);
> > > > > + }
> > > > > +
> > > > > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > > > > + struct mtk_cam_dev_buffer,
> > > > > + list);
> > > > > + if (buf)
> > > > > + mtk_camsv30_update_buffers_add(cam_dev, buf);
> > > >
> > > > If buf == NULL, so hardware would automatically stop DMA?
> > > > I don't know how this hardware work.
> > > > Below is my imagine about this hardware.
> > > >
> > > > 1. Software use CAMSV_IMGO_FBC_RCNT_INC to increase software buffer index.
> > > > 2. Hardware has a hardware buffer index. After hardware finish one frame, hardware buffer index increase.
> > > > 3. After software buffer index increase, hardware start DMA.
> > > > 4. When hardware buffer index is equal to software buffer index, hardware automatically stop DMA.
> > > >
> > > > Does the hardware work as my imagine?
> > > > If hardware could automatically stop DMA, add comment to describe.
> > > > If hardware could not automatically stop DMA, software should do something to stop DMA when buf == NULL.
> > > >
> > >
> > > You are right except that dma is not stopped but frames are
> > > automatically dropped by hardware until a new buffer is enqueued and
> > > software uses CAMSV_IMGO_FBC_RCNT_INC to increase the software buffer
> > > index.
> > >
> > > What about adding the following comment:
> > >
> > > /*
> > > * If there is no user buffer available, hardware will drop automatically
> > > * frames until buf_queue is called
> > > */
> >
> > You say DMA is not stopped. Do you mean hardware still write data into latest buffer which would be dequeued to user space?
> > I think hardware should not write data into the buffer which has been take away by user space.
> > I think software should do something to stop DMA. Maybe use mtk_camsv30_cmos_vf_hw_disable() to stop DMA.
> >
>
> No, I said frame is dropped.. It does not write any data.
OK, for me, DMA mean memory access. Not writing any data is equal to stop DMA for me.
The comment is OK for me now. But it may change after we discuss fbc_inc.
Regards,
CK
>
> > Regards,
> > CK
> >
> > >
> > > Let me know if that works for you
> > >
> > > Cheers
> > > Julien
> > >
> > > > Regards,
> > > > CK
> > > >
> > > > > + }
> > > > > +
> > > > > + spin_unlock(&cam_dev->buf_list_lock);
> > > > > +
> > > > > + return IRQ_HANDLED;
> > > > > +}
> > > > > +
> > > >
> > > > ************* MEDIATEK Confidentiality Notice ********************
> > > > The information contained in this e-mail message (including any
> > > > attachments) may be confidential, proprietary, privileged, or otherwise
> > > > exempt from disclosure under applicable laws. It is intended to be
> > > > conveyed only to the designated recipient(s). Any use, dissemination,
> > > > distribution, printing, retaining or copying of this e-mail (including its
> > > > attachments) by unintended recipient(s) is strictly prohibited and may
> > > > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > > > that you have received this e-mail in error, please notify the sender
> > > > immediately (by replying to this e-mail), delete any and all copies of
> > > > this e-mail (including any attachments) from your system, and do not
> > > > disclose the content of this e-mail to any other person. Thank you!
> >
> > ************* MEDIATEK Confidentiality Notice ********************
> > The information contained in this e-mail message (including any
> > attachments) may be confidential, proprietary, privileged, or otherwise
> > exempt from disclosure under applicable laws. It is intended to be
> > conveyed only to the designated recipient(s). Any use, dissemination,
> > distribution, printing, retaining or copying of this e-mail (including its
> > attachments) by unintended recipient(s) is strictly prohibited and may
> > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > that you have received this e-mail in error, please notify the sender
> > immediately (by replying to this e-mail), delete any and all copies of
> > this e-mail (including any attachments) from your system, and do not
> > disclose the content of this e-mail to any other person. Thank you!
>
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 10:01 ` CK Hu (胡俊光)
@ 2024-11-22 12:05 ` Julien Stephan
0 siblings, 0 replies; 39+ messages in thread
From: Julien Stephan @ 2024-11-22 12:05 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno,
pnguyen@baylibre.com
Le ven. 22 nov. 2024 à 11:01, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
>
> On Fri, 2024-11-22 at 10:50 +0100, Julien Stephan wrote:
> > External email : Please do not click links or open attachments until you have verified the sender or the content.
> >
> >
> > Le ven. 22 nov. 2024 à 10:49, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> > >
> > > On Fri, 2024-11-22 at 10:25 +0100, Julien Stephan wrote:
> > > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > > >
> > > >
> > > > Le ven. 22 nov. 2024 à 09:41, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> > > > >
> > > > > Hi, Julien:
> > > > >
> > > > > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > > > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > > > > >
> > > > > >
> > > > > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > > >
> > > > > > This driver provides a path to bypass the SoC ISP so that image data
> > > > > > coming from the SENINF can go directly into memory without any image
> > > > > > processing. This allows the use of an external ISP.
> > > > > >
> > > > > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > > > > [Paul Elder fix irq locking]
> > > > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > > > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > > > > ---
> > > > >
> > > > > [snip]
> > > > >
> > > > > > +static irqreturn_t isp_irq_camsv30(int irq, void *data)
> > > > > > +{
> > > > > > + struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
> > > > > > + struct mtk_cam_dev_buffer *buf;
> > > > > > + unsigned int irq_status;
> > > > > > +
> > > > > > + spin_lock(&cam_dev->buf_list_lock);
> > > > > > +
> > > > > > + irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
> > > > > > +
> > > > > > + if (irq_status & INT_ST_MASK_CAMSV_ERR)
> > > > > > + dev_err(cam_dev->dev, "irq error 0x%lx\n",
> > > > > > + irq_status & INT_ST_MASK_CAMSV_ERR);
> > > > > > +
> > > > > > + /* De-queue frame */
> > > > > > + if (irq_status & CAMSV_IRQ_PASS1_DON) {
> > > > > > + cam_dev->sequence++;
> > > > > > +
> > > > > > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > > > > > + struct mtk_cam_dev_buffer,
> > > > > > + list);
> > > > > > + if (buf) {
> > > > > > + buf->v4l2_buf.sequence = cam_dev->sequence;
> > > > > > + buf->v4l2_buf.vb2_buf.timestamp =
> > > > > > + ktime_get_ns();
> > > > > > + vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
> > > > > > + VB2_BUF_STATE_DONE);
> > > > > > + list_del(&buf->list);
> > > > > > + }
> > > > > > +
> > > > > > + buf = list_first_entry_or_null(&cam_dev->buf_list,
> > > > > > + struct mtk_cam_dev_buffer,
> > > > > > + list);
> > > > > > + if (buf)
> > > > > > + mtk_camsv30_update_buffers_add(cam_dev, buf);
> > > > >
> > > > > If buf == NULL, so hardware would automatically stop DMA?
> > > > > I don't know how this hardware work.
> > > > > Below is my imagine about this hardware.
> > > > >
> > > > > 1. Software use CAMSV_IMGO_FBC_RCNT_INC to increase software buffer index.
> > > > > 2. Hardware has a hardware buffer index. After hardware finish one frame, hardware buffer index increase.
> > > > > 3. After software buffer index increase, hardware start DMA.
> > > > > 4. When hardware buffer index is equal to software buffer index, hardware automatically stop DMA.
> > > > >
> > > > > Does the hardware work as my imagine?
> > > > > If hardware could automatically stop DMA, add comment to describe.
> > > > > If hardware could not automatically stop DMA, software should do something to stop DMA when buf == NULL.
> > > > >
> > > >
> > > > You are right except that dma is not stopped but frames are
> > > > automatically dropped by hardware until a new buffer is enqueued and
> > > > software uses CAMSV_IMGO_FBC_RCNT_INC to increase the software buffer
> > > > index.
> > > >
> > > > What about adding the following comment:
> > > >
> > > > /*
> > > > * If there is no user buffer available, hardware will drop automatically
> > > > * frames until buf_queue is called
> > > > */
> > >
> > > You say DMA is not stopped. Do you mean hardware still write data into latest buffer which would be dequeued to user space?
> > > I think hardware should not write data into the buffer which has been take away by user space.
> > > I think software should do something to stop DMA. Maybe use mtk_camsv30_cmos_vf_hw_disable() to stop DMA.
> > >
> >
> > No, I said frame is dropped.. It does not write any data.
>
> OK, for me, DMA mean memory access. Not writing any data is equal to stop DMA for me.
> The comment is OK for me now. But it may change after we discuss fbc_inc.
>
OK, sorry for confusion!
Did you notice my previous reply about fbc_inc?
Cheers
Julien
> Regards,
> CK
>
> >
> > > Regards,
> > > CK
> > >
> > > >
> > > > Let me know if that works for you
> > > >
> > > > Cheers
> > > > Julien
> > > >
> > > > > Regards,
> > > > > CK
> > > > >
> > > > > > + }
> > > > > > +
> > > > > > + spin_unlock(&cam_dev->buf_list_lock);
> > > > > > +
> > > > > > + return IRQ_HANDLED;
> > > > > > +}
> > > > > > +
> > > > >
> > > > > ************* MEDIATEK Confidentiality Notice ********************
> > > > > The information contained in this e-mail message (including any
> > > > > attachments) may be confidential, proprietary, privileged, or otherwise
> > > > > exempt from disclosure under applicable laws. It is intended to be
> > > > > conveyed only to the designated recipient(s). Any use, dissemination,
> > > > > distribution, printing, retaining or copying of this e-mail (including its
> > > > > attachments) by unintended recipient(s) is strictly prohibited and may
> > > > > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > > > > that you have received this e-mail in error, please notify the sender
> > > > > immediately (by replying to this e-mail), delete any and all copies of
> > > > > this e-mail (including any attachments) from your system, and do not
> > > > > disclose the content of this e-mail to any other person. Thank you!
> > >
> > > ************* MEDIATEK Confidentiality Notice ********************
> > > The information contained in this e-mail message (including any
> > > attachments) may be confidential, proprietary, privileged, or otherwise
> > > exempt from disclosure under applicable laws. It is intended to be
> > > conveyed only to the designated recipient(s). Any use, dissemination,
> > > distribution, printing, retaining or copying of this e-mail (including its
> > > attachments) by unintended recipient(s) is strictly prohibited and may
> > > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > > that you have received this e-mail in error, please notify the sender
> > > immediately (by replying to this e-mail), delete any and all copies of
> > > this e-mail (including any attachments) from your system, and do not
> > > disclose the content of this e-mail to any other person. Thank you!
> >
> >
>
> ************* MEDIATEK Confidentiality Notice ********************
> The information contained in this e-mail message (including any
> attachments) may be confidential, proprietary, privileged, or otherwise
> exempt from disclosure under applicable laws. It is intended to be
> conveyed only to the designated recipient(s). Any use, dissemination,
> distribution, printing, retaining or copying of this e-mail (including its
> attachments) by unintended recipient(s) is strictly prohibited and may
> be unlawful. If you are not an intended recipient of this e-mail, or believe
> that you have received this e-mail in error, please notify the sender
> immediately (by replying to this e-mail), delete any and all copies of
> this e-mail (including any attachments) from your system, and do not
> disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 9:28 ` CK Hu (胡俊光)
@ 2024-11-24 6:11 ` Laurent Pinchart
2024-11-25 8:26 ` CK Hu (胡俊光)
1 sibling, 0 replies; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-24 6:11 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: Julien Stephan, linux-kernel@vger.kernel.org,
linux-mediatek@lists.infradead.org, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, paul.elder@ideasonboard.com,
mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
krzk+dt@kernel.org, fsylvestre@baylibre.com,
AngeloGioacchino Del Regno, pnguyen@baylibre.com
On Fri, Nov 22, 2024 at 09:28:53AM +0000, CK Hu (胡俊光) wrote:
> On Fri, 2024-11-22 at 10:16 +0100, Julien Stephan wrote:
> > Le ven. 22 nov. 2024 à 08:54, CK Hu (胡俊光) a écrit :
> > > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > > >
> > > >
> > > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > >
> > > > This driver provides a path to bypass the SoC ISP so that image data
> > > > coming from the SENINF can go directly into memory without any image
> > > > processing. This allows the use of an external ISP.
> > > >
> > > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > > [Paul Elder fix irq locking]
> > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > > ---
> > >
> > > [snip]
> > >
> > > > +static void mtk_cam_cmos_vf_enable(struct mtk_cam_dev *cam_dev,
> > > > + bool enable, bool pak_en)
> > > > +{
> > > > + if (enable)
> > > > + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_enable(cam_dev);
> > >
> > > Directly call mtk_camsv30_cmos_vf_hw_enable().
> > > This has discussed in previous version [1].
> > >
> > > [1] https://patchwork.kernel.org/project/linux-mediatek/patch/20240729-add-mtk-isp-3-0-support-v6-4-c374c9e0c672@baylibre.com/
> >
> > Hi CK,
> >
> > I forgot about that discussion sorry :/
> > I guess you want me to completely remove the mtk_cam_hw_functions struct?
> > In that case, what do you prefer:
> > - keep mtk_camsv30_hw.c and put signatures in mtkcamsv30_hw.h and
> > include mtk_camsv30_hw.h in mtk_camsv_video.c
> > - rename mtk_camsv30_hw.c to mtk_camsv_hw.c (and all functions) and
> > put signatures in mtk_camsv_hw.h
>
> I prefer the second one.
If we drop the indirection (which I think is a good idea until we get a
second hardware generation supported by the same driver) I would also go
for the latter.
> > > > + else
> > > > + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_disable(cam_dev);
> > > > +}
> > > > +
> > > >
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 9:44 ` Julien Stephan
@ 2024-11-25 5:54 ` CK Hu (胡俊光)
0 siblings, 0 replies; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-25 5:54 UTC (permalink / raw)
To: Julien Stephan
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno,
pnguyen@baylibre.com
On Fri, 2024-11-22 at 10:44 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> Le ven. 22 nov. 2024 à 10:19, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> >
> > Hi, Julien:
> >
> > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > >
> > >
> > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > >
> > > This driver provides a path to bypass the SoC ISP so that image data
> > > coming from the SENINF can go directly into memory without any image
> > > processing. This allows the use of an external ISP.
> > >
> > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > [Paul Elder fix irq locking]
> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > ---
> >
> > [snip]
> >
> > > +static void mtk_cam_vb2_buf_queue(struct vb2_buffer *vb)
> > > +{
> > > + struct mtk_cam_dev *cam = vb2_get_drv_priv(vb->vb2_queue);
> > > + struct mtk_cam_dev_buffer *buf = to_mtk_cam_dev_buffer(vb);
> > > + unsigned long flags;
> > > +
> > > + /* Add the buffer into the tracking list */
> > > + spin_lock_irqsave(&cam->buf_list_lock, flags);
> > > + if (list_empty(&cam->buf_list))
> > > + (*cam->hw_functions->mtk_cam_update_buffers_add)(cam, buf);
> > > +
> > > + list_add_tail(&buf->list, &cam->buf_list);
> > > + (*cam->hw_functions->mtk_cam_fbc_inc)(cam);
> >
> > I think fbc_inc should together with update_buffers_add.
> > After update_buffers_add then fbc_inc.
> > So squash fbc_inc into update_buffers_add and drop fbc_inc function.
> >
>
> No, this is not true.
> mtk_cam_update_buffers_add is used to indicate which buffer should be
> used for dma write. This is the first entry in the buf list.
> mtk_cam_fbc_inc is used to increase the number of available user space buffers.
>
> If the buffer list is not empty and user space calls buf_queue again,
> we need to call mtk_cam_fbc_inc to increase the number of available
> user buffers, but we don't want to change the buffer for DMA write.
>
> mtk_camsv30_update_buffers_add is called on irq to update the address
> to the next buffer (if available).
I think current design is OK.
I just think it could have more flexible design.
For example, reduce frame rate from 30 fps to 15 fps.
And this is a time sequence to reduce frame rate:
t = -5 ms, set buf1 address and fbc increase. (hardware index = 0, software index = 1)
t = 0 ms,
t = 33 ms, buf1 is done, do not set next buffer. (hardware index = 1, software index = 1)
t = 67 ms, set buf2 addres and fbc increase. (hardware index = 1, software index = 2)
t = 100 ms, buf2 is done, (hardware index = 2, software idex = 2)
If want to keep the 30 fps, when t = 33ms, set buf2 address and fbc increase.
If you want to keep current design, that's OK for me because now has no requirement for such advanced control.
But I have a new question, in the time sequence I list, does it has interrupt when t = 0 (the first vblank) ?
If t = 0 has no interrupt, I think t = 67 also has no interrupt and my design would not work.
It t = 0 has interrupt, I think software should think this buffer is not done.
>
> Maybe the name mtk_camsv30_update_buffers_add is confusing then?
> What do you think about:
> - mtk_camsv30_update_buffers_add -> mtk_camsv30_update_buffers_address
mtk_camsv30_update_buffers_address is better.
> - mtk_cam_fbc_inc -> mtk_camsv30_buffer_add
mtk_cam_fbc_inc is better, it show counter increase.
Regards,
CK
>
> Cheers
> Julien
>
> > Regards,
> > CK
> >
> > > + spin_unlock_irqrestore(&cam->buf_list_lock, flags);
> > > +}
> > > +
> > >
> >
> > ************* MEDIATEK Confidentiality Notice ********************
> > The information contained in this e-mail message (including any
> > attachments) may be confidential, proprietary, privileged, or otherwise
> > exempt from disclosure under applicable laws. It is intended to be
> > conveyed only to the designated recipient(s). Any use, dissemination,
> > distribution, printing, retaining or copying of this e-mail (including its
> > attachments) by unintended recipient(s) is strictly prohibited and may
> > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > that you have received this e-mail in error, please notify the sender
> > immediately (by replying to this e-mail), delete any and all copies of
> > this e-mail (including any attachments) from your system, and do not
> > disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
` (2 preceding siblings ...)
2024-11-22 9:18 ` CK Hu (胡俊光)
@ 2024-11-25 6:05 ` CK Hu (胡俊光)
2024-11-25 6:34 ` CK Hu (胡俊光)
` (6 subsequent siblings)
10 siblings, 0 replies; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-25 6:05 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +static irqreturn_t isp_irq_camsv30(int irq, void *data)
> +{
> + struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
> + struct mtk_cam_dev_buffer *buf;
> + unsigned int irq_status;
> +
> + spin_lock(&cam_dev->buf_list_lock);
spin_lock region should be as small as possible.
Move to where you start to access buf_list.
> +
> + irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
> +
> + if (irq_status & INT_ST_MASK_CAMSV_ERR)
> + dev_err(cam_dev->dev, "irq error 0x%lx\n",
> + irq_status & INT_ST_MASK_CAMSV_ERR);
> +
> + /* De-queue frame */
> + if (irq_status & CAMSV_IRQ_PASS1_DON) {
> + cam_dev->sequence++;
> +
> + buf = list_first_entry_or_null(&cam_dev->buf_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + if (buf) {
> + buf->v4l2_buf.sequence = cam_dev->sequence;
> + buf->v4l2_buf.vb2_buf.timestamp =
> + ktime_get_ns();
> + vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
> + VB2_BUF_STATE_DONE);
These jobs could be done after buffer is deleted from list, and move these jobs out of this spin_lock region.
vb2_buffer_done() looks like does many things. So it's worth to move vb2_buffer_done() out of this spin_lock region.
spin_lock();
processed_buf = list_first_entry_or_null();
if (processed_buf)
list_del(&processed_buf->list);
spin_unlock();
if (processed_buf) {
...
vb2_buffer_done();
}
Regards,
CK
> + list_del(&buf->list);
> + }
> +
> + buf = list_first_entry_or_null(&cam_dev->buf_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + if (buf)
> + mtk_camsv30_update_buffers_add(cam_dev, buf);
> + }
> +
> + spin_unlock(&cam_dev->buf_list_lock);
> +
> + return IRQ_HANDLED;
> +}
> +
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
` (3 preceding siblings ...)
2024-11-25 6:05 ` CK Hu (胡俊光)
@ 2024-11-25 6:34 ` CK Hu (胡俊光)
2024-11-25 6:59 ` CK Hu (胡俊光)
` (5 subsequent siblings)
10 siblings, 0 replies; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-25 6:34 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +/* -----------------------------------------------------------------------------
> + * VB2 Queue Operations
> + */
> +
> +static int mtk_cam_vb2_queue_setup(struct vb2_queue *vq,
> + unsigned int *num_buffers,
> + unsigned int *num_planes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct mtk_cam_video_device *vdev =
> + vb2_queue_to_mtk_cam_video_device(vq);
> + struct mtk_cam_dev *cam = vb2_get_drv_priv(vq);
> + const struct v4l2_pix_format_mplane *fmt = &vdev->format;
> + unsigned int size, default_num_planes, i;
> +
> + size = fmt->plane_fmt[0].sizeimage;
> +
> + default_num_planes = 1;
Make this a constant.
> +
> + if (*num_planes == 0) {
> + *num_planes = default_num_planes;
> + for (i = 0; i < *num_planes; ++i)
> + sizes[i] = size;
*num_planes = 1, so this for-loop could be replaced by:
size[0] = size;
> + } else if (*num_planes != default_num_planes || sizes[0] < size) {
The term 'default_num_planes' would let's think it could support other number of planes.
But it only support one plane. Maybe use 'supported_num_planes'.
Regards,
CK
> + return -EINVAL;
> + }
> +
> + (*cam->hw_functions->mtk_cam_fbc_init)(cam, *num_buffers);
> +
> + return 0;
> +}
> +
>
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
` (4 preceding siblings ...)
2024-11-25 6:34 ` CK Hu (胡俊光)
@ 2024-11-25 6:59 ` CK Hu (胡俊光)
2024-11-25 9:39 ` Laurent Pinchart
2024-11-25 8:14 ` CK Hu (胡俊光)
` (4 subsequent siblings)
10 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-25 6:59 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +static const struct mtk_cam_conf camsv30_conf = {
> + .tg_sen_mode = 0x00010002U, /* TIME_STP_EN = 1. DBL_DATA_BUS = 1 */
> + .module_en = 0x40000001U, /* enable double buffer and TG */
> + .imgo_con = 0x80000080U, /* DMA FIFO depth and burst */
> + .imgo_con2 = 0x00020002U, /* DMA priority */
Now support only one SoC, so it's not necessary make these SoC variable.
They could be constant symbol now.
Regards,
CK
> +};
> +
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
` (5 preceding siblings ...)
2024-11-25 6:59 ` CK Hu (胡俊光)
@ 2024-11-25 8:14 ` CK Hu (胡俊光)
2024-11-25 14:40 ` Julien Stephan
2024-11-25 9:30 ` CK Hu (胡俊光)
` (3 subsequent siblings)
10 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-25 8:14 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +static void mtk_camsv30_update_buffers_add(struct mtk_cam_dev *cam_dev,
> + struct mtk_cam_dev_buffer *buf)
> +{
> + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_BASE_ADDR, buf->daddr);
> +}
> +
[snip]
> +static void mtk_camsv30_fbc_inc(struct mtk_cam_dev *cam_dev)
> +{
> + unsigned int fbc_val;
> +
> + if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
I think this pm_runtime_resume_and_get() is not necessary.
mtk_camsv30_fbc_inc() is called only in mtk_cam_vb2_buf_queue().
But when buf_list is empty, mtk_camsv30_update_buffers_add() is called before this function.
But mtk_camsv30_update_buffers_add() does not call pm_runtime_resume_and_get() and it works normally.
So this function is not necessary to call pm_runtime_resume_and_get().
In other register setting function, please also check this pm function is necessary or not.
Regards,
CK
> + dev_err(cam_dev->dev, "failed to get pm_runtime\n");
> + return;
> + }
> +
> + fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
> + fbc_val |= CAMSV_IMGO_FBC_RCNT_INC;
> + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> + fbc_val &= ~CAMSV_IMGO_FBC_RCNT_INC;
> + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> +
> + pm_runtime_put_autosuspend(cam_dev->dev);
> +}
> +
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-22 9:28 ` CK Hu (胡俊光)
2024-11-24 6:11 ` Laurent Pinchart
@ 2024-11-25 8:26 ` CK Hu (胡俊光)
1 sibling, 0 replies; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-25 8:26 UTC (permalink / raw)
To: Julien Stephan
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno,
pnguyen@baylibre.com
On Fri, 2024-11-22 at 09:28 +0000, CK Hu (胡俊光) wrote:
> Hi, Julien:
>
> On Fri, 2024-11-22 at 10:16 +0100, Julien Stephan wrote:
> > External email : Please do not click links or open attachments until you have verified the sender or the content.
> >
> >
> > Le ven. 22 nov. 2024 à 08:54, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> > >
> > > Hi, Julien:
> > >
> > > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > > External email : Please do not click links or open attachments until you have verified the sender or the content.
> > > >
> > > >
> > > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > >
> > > > This driver provides a path to bypass the SoC ISP so that image data
> > > > coming from the SENINF can go directly into memory without any image
> > > > processing. This allows the use of an external ISP.
> > > >
> > > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > > [Paul Elder fix irq locking]
> > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > > ---
> > >
> > > [snip]
> > >
> > > > +static void mtk_cam_cmos_vf_enable(struct mtk_cam_dev *cam_dev,
> > > > + bool enable, bool pak_en)
> > > > +{
> > > > + if (enable)
> > > > + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_enable(cam_dev);
> > >
> > > Directly call mtk_camsv30_cmos_vf_hw_enable().
> > > This has discussed in previous version [1].
> > >
> > > [1] https://urldefense.com/v3/__https://patchwork.kernel.org/project/linux-mediatek/patch/20240729-add-mtk-isp-3-0-support-v6-4-c374c9e0c672@baylibre.com/*25966327__;Iw!!CTRNKA9wMg0ARbw!lydgLydtAuzr-BC5qArz3AEzOM0iSSr6TXifwab1kPvWVJLy0k7rUiasR_goMAF_6XYmPIpErGF6CdLkPQ$
> > >
> >
> > Hi CK,
> >
> > I forgot about that discussion sorry :/
> > I guess you want me to completely remove the mtk_cam_hw_functions struct?
> > In that case, what do you prefer:
> > - keep mtk_camsv30_hw.c and put signatures in mtkcamsv30_hw.h and
> > include mtk_camsv30_hw.h in mtk_camsv_video.c
> > - rename mtk_camsv30_hw.c to mtk_camsv_hw.c (and all functions) and
> > put signatures in mtk_camsv_hw.h
>
> I prefer the second one.
In addition, I think mtk_cam_cmos_vf_enable() could be removed.
Caller of this function could directly call mtk_camsv30_cmos_vf_hw_enable() and mtk_camsv30_cmos_vf_hw_disable().
Regards,
CK
>
> Regards,
> CK
>
> >
> > Cheers
> > Julien
> >
> > > Regards,
> > > CK
> > >
> > > > + else
> > > > + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_disable(cam_dev);
> > > > +}
> > > > +
> > > >
> > >
> > > ************* MEDIATEK Confidentiality Notice ********************
> > > The information contained in this e-mail message (including any
> > > attachments) may be confidential, proprietary, privileged, or otherwise
> > > exempt from disclosure under applicable laws. It is intended to be
> > > conveyed only to the designated recipient(s). Any use, dissemination,
> > > distribution, printing, retaining or copying of this e-mail (including its
> > > attachments) by unintended recipient(s) is strictly prohibited and may
> > > be unlawful. If you are not an intended recipient of this e-mail, or believe
> > > that you have received this e-mail in error, please notify the sender
> > > immediately (by replying to this e-mail), delete any and all copies of
> > > this e-mail (including any attachments) from your system, and do not
> > > disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
` (6 preceding siblings ...)
2024-11-25 8:14 ` CK Hu (胡俊光)
@ 2024-11-25 9:30 ` CK Hu (胡俊光)
2024-11-25 20:33 ` Laurent Pinchart
` (2 subsequent siblings)
10 siblings, 0 replies; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-25 9:30 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +struct mtk_cam_dev_buffer {
> + struct vb2_v4l2_buffer v4l2_buf;
> + struct list_head list;
> + dma_addr_t daddr;
> + void *vaddr;
vaddr is useless, so drop it.
Regards,
CK
> +};
> +
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-25 6:59 ` CK Hu (胡俊光)
@ 2024-11-25 9:39 ` Laurent Pinchart
2024-11-25 9:56 ` CK Hu (胡俊光)
0 siblings, 1 reply; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-25 9:39 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, krzk+dt@kernel.org,
AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
linux-mediatek@lists.infradead.org, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, paul.elder@ideasonboard.com,
linux-arm-kernel@lists.infradead.org, fsylvestre@baylibre.com
On Mon, Nov 25, 2024 at 06:59:59AM +0000, CK Hu (胡俊光) wrote:
> Hi, Julien:
>
> On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> >
> > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> >
> > This driver provides a path to bypass the SoC ISP so that image data
> > coming from the SENINF can go directly into memory without any image
> > processing. This allows the use of an external ISP.
> >
> > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > [Paul Elder fix irq locking]
> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > ---
>
> [snip]
>
> > +static const struct mtk_cam_conf camsv30_conf = {
> > + .tg_sen_mode = 0x00010002U, /* TIME_STP_EN = 1. DBL_DATA_BUS = 1 */
> > + .module_en = 0x40000001U, /* enable double buffer and TG */
> > + .imgo_con = 0x80000080U, /* DMA FIFO depth and burst */
> > + .imgo_con2 = 0x00020002U, /* DMA priority */
>
> Now support only one SoC, so it's not necessary make these SoC variable.
> They could be constant symbol now.
This I would keep as a mtk_cam_conf structure instance, as I think it
makes it clear what we consider to be model-specific without hindering
readability. I don't have a very strong opinion though.
> > +};
> > +
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-25 9:39 ` Laurent Pinchart
@ 2024-11-25 9:56 ` CK Hu (胡俊光)
2024-11-25 10:22 ` Laurent Pinchart
0 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-25 9:56 UTC (permalink / raw)
To: laurent.pinchart@ideasonboard.com
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, Julien Stephan,
matthias.bgg@gmail.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno
On Mon, 2024-11-25 at 11:39 +0200, Laurent Pinchart wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> On Mon, Nov 25, 2024 at 06:59:59AM +0000, CK Hu (胡俊光) wrote:
> > Hi, Julien:
> >
> > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > >
> > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > >
> > > This driver provides a path to bypass the SoC ISP so that image data
> > > coming from the SENINF can go directly into memory without any image
> > > processing. This allows the use of an external ISP.
> > >
> > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > [Paul Elder fix irq locking]
> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > ---
> >
> > [snip]
> >
> > > +static const struct mtk_cam_conf camsv30_conf = {
> > > + .tg_sen_mode = 0x00010002U, /* TIME_STP_EN = 1. DBL_DATA_BUS = 1 */
> > > + .module_en = 0x40000001U, /* enable double buffer and TG */
> > > + .imgo_con = 0x80000080U, /* DMA FIFO depth and burst */
> > > + .imgo_con2 = 0x00020002U, /* DMA priority */
> >
> > Now support only one SoC, so it's not necessary make these SoC variable.
> > They could be constant symbol now.
>
> This I would keep as a mtk_cam_conf structure instance, as I think it
> makes it clear what we consider to be model-specific without hindering
> readability. I don't have a very strong opinion though.
If this is a configuration table, I would like it to be
{
.time_stp_en = true;
.dbl_data_bus = 1;
.double_buffer_en = true;
.tg = 0x4;
...
}
If next SoC has only one parameter is different, we duplicate all other parameter?
Regards,
CK
>
> > > +};
> > > +
>
> --
> Regards,
>
> Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-25 9:56 ` CK Hu (胡俊光)
@ 2024-11-25 10:22 ` Laurent Pinchart
2024-11-26 1:08 ` CK Hu (胡俊光)
0 siblings, 1 reply; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-25 10:22 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, Julien Stephan,
matthias.bgg@gmail.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno
On Mon, Nov 25, 2024 at 09:56:54AM +0000, CK Hu (胡俊光) wrote:
> On Mon, 2024-11-25 at 11:39 +0200, Laurent Pinchart wrote:
> > On Mon, Nov 25, 2024 at 06:59:59AM +0000, CK Hu (胡俊光) wrote:
> > > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > >
> > > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > >
> > > > This driver provides a path to bypass the SoC ISP so that image data
> > > > coming from the SENINF can go directly into memory without any image
> > > > processing. This allows the use of an external ISP.
> > > >
> > > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > > [Paul Elder fix irq locking]
> > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > > ---
> > >
> > > [snip]
> > >
> > > > +static const struct mtk_cam_conf camsv30_conf = {
> > > > + .tg_sen_mode = 0x00010002U, /* TIME_STP_EN = 1. DBL_DATA_BUS = 1 */
> > > > + .module_en = 0x40000001U, /* enable double buffer and TG */
> > > > + .imgo_con = 0x80000080U, /* DMA FIFO depth and burst */
> > > > + .imgo_con2 = 0x00020002U, /* DMA priority */
> > >
> > > Now support only one SoC, so it's not necessary make these SoC variable.
> > > They could be constant symbol now.
> >
> > This I would keep as a mtk_cam_conf structure instance, as I think it
> > makes it clear what we consider to be model-specific without hindering
> > readability. I don't have a very strong opinion though.
>
> If this is a configuration table, I would like it to be
>
> {
> .time_stp_en = true;
> .dbl_data_bus = 1;
> .double_buffer_en = true;
> .tg = 0x4;
> ...
> }
I like that too, it's more readable than raw register values.
> If next SoC has only one parameter is different, we duplicate all
> other parameter?
That's what we usually do when the amount of parameters is not too
large. When it becomes larger, we sometimes split the configuration data
in multiple chunks. For instance,
static const char * const family_a_clks[] = {
"core",
"io",
};
static sont char * const family_b_clks[] = {
"main",
"ram",
"bus",
};
static const foo_dev_info soc_1_info = {
.has_time_machine = false,
.clks = family_a_clks,
.num_clks = ARRAY_SIZE(family_a_clks),
};
static const foo_dev_info soc_2_info = {
.has_time_machine = false,
.clks = family_b_clks,
.num_clks = ARRAY_SIZE(family_b_clks),
};
static const foo_dev_info soc_3_info = {
.has_time_machine = true,
.clks = family_b_clks,
.num_clks = ARRAY_SIZE(family_b_clks),
};
There's no clear rule, it's on a case-by-case basis.
> > > > +};
> > > > +
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 2/5] dt-bindings: media: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 2/5] dt-bindings: media: add mediatek ISP3.0 camsv Julien Stephan
@ 2024-11-25 11:24 ` Laurent Pinchart
0 siblings, 0 replies; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-25 11:24 UTC (permalink / raw)
To: Julien Stephan
Cc: Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek
Hi Julien,
Thank you for the patch.
On Thu, Nov 21, 2024 at 09:53:16AM +0100, Julien Stephan wrote:
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This adds the bindings, for the ISP3.0 camsv module embedded in
> some Mediatek SoC, such as the mt8365
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
> .../bindings/media/mediatek,mt8365-camsv.yaml | 109 +++++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 110 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml b/Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..fdd07675645917fbcd692606c836efd07e50ac0c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
> @@ -0,0 +1,109 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +# Copyright (c) 2023 MediaTek, BayLibre
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/mediatek,mt8365-camsv.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: MediaTek CAMSV 3.0
> +
> +maintainers:
> + - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> + - Julien Stephan <jstephan@baylibre.com>
> + - Andy Hsieh <andy.hsieh@mediatek.com>
> +
> +description:
> + The CAMSV is a video capture device that includes a DMA engine connected to
> + the SENINF CSI-2 receivers. The number of CAMSVs depend on the SoC model.
> +
> +properties:
> + compatible:
> + const: mediatek,mt8365-camsv
> +
> + reg:
> + items:
> + - description: camsv base
> + - description: img0 base
> + - description: tg base
> +
> + interrupts:
> + maxItems: 1
> +
> + power-domains:
> + maxItems: 1
> +
> + clocks:
> + items:
> + - description: cam clock
> + - description: camtg clock
> + - description: camsv clock
> +
> + clock-names:
> + items:
> + - const: cam
> + - const: camtg
> + - const: camsv
> +
> + iommus:
> + maxItems: 1
> +
> + ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> +
> + properties:
> + port@0:
> + $ref: /schemas/graph.yaml#/properties/port
> + description: Connection to the SENINF output
> +
> + required:
> + - port@0
> +
> +required:
> + - compatible
> + - interrupts
> + - clocks
> + - clock-names
> + - power-domains
> + - iommus
> + - ports
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> + #include <dt-bindings/clock/mediatek,mt8365-clk.h>
I would sort the headers alphabetically.
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> + #include <dt-bindings/memory/mediatek,mt8365-larb-port.h>
> + #include <dt-bindings/power/mediatek,mt8365-power.h>
> +
> + soc {
> + #address-cells = <2>;
> + #size-cells = <2>;
> +
> + camsv@15050000 {
> + compatible = "mediatek,mt8365-camsv";
> + reg = <0 0x15050000 0 0x0040>,
> + <0 0x15050208 0 0x0020>,
> + <0 0x15050400 0 0x0100>;
> + interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>;
> + clocks = <&camsys CLK_CAM>,
> + <&camsys CLK_CAMTG>,
> + <&camsys CLK_CAMSV0>;
> + clock-names = "cam", "camtg", "camsv";
> + iommus = <&iommu M4U_PORT_CAM_IMGO>;
> + power-domains = <&spm MT8365_POWER_DOMAIN_CAM>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + port@0 {
> + reg = <0>;
> + camsv1_endpoint: endpoint {
> + remote-endpoint = <&seninf_camsv1_endpoint>;
> + };
> + };
> + };
> + };
> + };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 242c54c88a4a22fc0cbe5c4fc5d7b0d0f84b329e..6147629405c8d40b00c4755a4ee27a746b26f782 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14570,6 +14570,7 @@ M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> M: Julien Stephan <jstephan@baylibre.com>
> M: Andy Hsieh <andy.hsieh@mediatek.com>
> S: Supported
> +F: Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
> F: Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml
>
> MEDIATEK SMI DRIVER
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-25 8:14 ` CK Hu (胡俊光)
@ 2024-11-25 14:40 ` Julien Stephan
2024-11-25 17:36 ` Laurent Pinchart
0 siblings, 1 reply; 39+ messages in thread
From: Julien Stephan @ 2024-11-25 14:40 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), matthias.bgg@gmail.com,
laurent.pinchart@ideasonboard.com, krzk+dt@kernel.org,
AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
linux-mediatek@lists.infradead.org, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, paul.elder@ideasonboard.com,
linux-arm-kernel@lists.infradead.org, fsylvestre@baylibre.com,
pnguyen@baylibre.com
Le lun. 25 nov. 2024 à 09:14, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
>
> Hi, Julien:
>
> On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > External email : Please do not click links or open attachments until you have verified the sender or the content.
> >
> >
> > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> >
> > This driver provides a path to bypass the SoC ISP so that image data
> > coming from the SENINF can go directly into memory without any image
> > processing. This allows the use of an external ISP.
> >
> > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > [Paul Elder fix irq locking]
> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > ---
>
> [snip]
>
> > +static void mtk_camsv30_update_buffers_add(struct mtk_cam_dev *cam_dev,
> > + struct mtk_cam_dev_buffer *buf)
> > +{
> > + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_BASE_ADDR, buf->daddr);
> > +}
> > +
>
> [snip]
>
> > +static void mtk_camsv30_fbc_inc(struct mtk_cam_dev *cam_dev)
> > +{
> > + unsigned int fbc_val;
> > +
> > + if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
>
> I think this pm_runtime_resume_and_get() is not necessary.
> mtk_camsv30_fbc_inc() is called only in mtk_cam_vb2_buf_queue().
> But when buf_list is empty, mtk_camsv30_update_buffers_add() is called before this function.
> But mtk_camsv30_update_buffers_add() does not call pm_runtime_resume_and_get() and it works normally.
> So this function is not necessary to call pm_runtime_resume_and_get().
Hi CK,
This one is actually needed because .buf_queue can be called before
.start_streaming in case a user requests to prepare buffers before
streaming.
But you are right, if a user requests to alloc buffer before streaming
mtk_camsv_update_buffers_address will be called without pm. Streaming
still works because in start streaming we call
mtk_camsv_update_buffers_address again.
So maybe I should put the pm stuff in mtk_cam_vb2_buf_queue ?
Cheers
Julien
>
> In other register setting function, please also check this pm function is necessary or not.
>
> Regards,
> CK
>
> > + dev_err(cam_dev->dev, "failed to get pm_runtime\n");
> > + return;
> > + }
> > +
> > + fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
> > + fbc_val |= CAMSV_IMGO_FBC_RCNT_INC;
> > + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> > + fbc_val &= ~CAMSV_IMGO_FBC_RCNT_INC;
> > + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> > +
> > + pm_runtime_put_autosuspend(cam_dev->dev);
> > +}
> > +
>
> >
>
> ************* MEDIATEK Confidentiality Notice ********************
> The information contained in this e-mail message (including any
> attachments) may be confidential, proprietary, privileged, or otherwise
> exempt from disclosure under applicable laws. It is intended to be
> conveyed only to the designated recipient(s). Any use, dissemination,
> distribution, printing, retaining or copying of this e-mail (including its
> attachments) by unintended recipient(s) is strictly prohibited and may
> be unlawful. If you are not an intended recipient of this e-mail, or believe
> that you have received this e-mail in error, please notify the sender
> immediately (by replying to this e-mail), delete any and all copies of
> this e-mail (including any attachments) from your system, and do not
> disclose the content of this e-mail to any other person. Thank you!
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 3/5] media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface
2024-11-21 8:53 ` [PATCH v7 3/5] media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface Julien Stephan
@ 2024-11-25 17:33 ` Laurent Pinchart
2024-11-25 19:45 ` Laurent Pinchart
2025-01-22 14:04 ` Julien Stephan
0 siblings, 2 replies; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-25 17:33 UTC (permalink / raw)
To: Julien Stephan
Cc: Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, Louis Kuo, Phi-bang Nguyen,
Florian Sylvestre
Hi Julien,
Thank you for the patch.
On Thu, Nov 21, 2024 at 09:53:17AM +0100, Julien Stephan wrote:
> From: Louis Kuo <louis.kuo@mediatek.com>
>
> This will add the mediatek ISP3.0 seninf (sensor interface) driver found
> on several Mediatek SoCs such as the mt8365.
>
> Then seninf module has 4 physical CSI-2 inputs. Depending on the soc they
> may not be all connected.
>
> Signed-off-by: Louis Kuo <louis.kuo@mediatek.com>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
> MAINTAINERS | 1 +
> drivers/media/platform/mediatek/Kconfig | 1 +
> drivers/media/platform/mediatek/Makefile | 1 +
> drivers/media/platform/mediatek/isp/Kconfig | 17 +
> drivers/media/platform/mediatek/isp/Makefile | 4 +
> drivers/media/platform/mediatek/isp/mtk_seninf.c | 1636 ++++++++++++++++++++
> .../media/platform/mediatek/isp/mtk_seninf_reg.h | 114 ++
> 7 files changed, 1774 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6147629405c8d40b00c4755a4ee27a746b26f782..9654a7f4e21cddb77bb799add56110f5f27f7a79 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14572,6 +14572,7 @@ M: Andy Hsieh <andy.hsieh@mediatek.com>
> S: Supported
> F: Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
> F: Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml
> +F: drivers/media/platform/mediatek/isp/*
>
> MEDIATEK SMI DRIVER
> M: Yong Wu <yong.wu@mediatek.com>
> diff --git a/drivers/media/platform/mediatek/Kconfig b/drivers/media/platform/mediatek/Kconfig
> index 84104e2cd02447790ae5c29953a2e82ca4fdd0a7..a405d57013292c515d2a2db4d43aa1ed8cb21f7b 100644
> --- a/drivers/media/platform/mediatek/Kconfig
> +++ b/drivers/media/platform/mediatek/Kconfig
> @@ -2,6 +2,7 @@
>
> comment "Mediatek media platform drivers"
>
> +source "drivers/media/platform/mediatek/isp/Kconfig"
> source "drivers/media/platform/mediatek/jpeg/Kconfig"
> source "drivers/media/platform/mediatek/mdp/Kconfig"
> source "drivers/media/platform/mediatek/vcodec/Kconfig"
> diff --git a/drivers/media/platform/mediatek/Makefile b/drivers/media/platform/mediatek/Makefile
> index 38e6ba917fe5cdd932aa6c88221c9a7aa5a7705a..2341a0e373a4e30f0caf823ab67098fde96fc071 100644
> --- a/drivers/media/platform/mediatek/Makefile
> +++ b/drivers/media/platform/mediatek/Makefile
> @@ -1,4 +1,5 @@
> # SPDX-License-Identifier: GPL-2.0-only
> +obj-y += isp/
> obj-y += jpeg/
> obj-y += mdp/
> obj-y += vcodec/
> diff --git a/drivers/media/platform/mediatek/isp/Kconfig b/drivers/media/platform/mediatek/isp/Kconfig
> new file mode 100644
> index 0000000000000000000000000000000000000000..2a3cef81d15aa12633ade2f3be0bba36b9af62e1
> --- /dev/null
> +++ b/drivers/media/platform/mediatek/isp/Kconfig
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config MTK_SENINF30
> + tristate "MediaTek ISP3.0 SENINF driver"
> + depends on ARCH_MEDIATEK || COMPILE_TEST
> + depends on OF
> + select MEDIA_CONTROLLER
> + select PHY_MTK_MIPI_CSI_0_5
> + select V4L2_FWNODE
> + select VIDEO_V4L2_SUBDEV_API
> + default n
> + help
> + This driver provides a MIPI CSI-2 receiver interface to connect
> + an external camera module with MediaTek ISP3.0. It is able to handle
> + multiple cameras at the same time.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called mtk-seninf.
> diff --git a/drivers/media/platform/mediatek/isp/Makefile b/drivers/media/platform/mediatek/isp/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..375d720f9ed75e2197bb723bdce9bc0472e62842
> --- /dev/null
> +++ b/drivers/media/platform/mediatek/isp/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +mtk-seninf-objs += mtk_seninf.o
> +obj-$(CONFIG_MTK_SENINF30) += mtk-seninf.o
> diff --git a/drivers/media/platform/mediatek/isp/mtk_seninf.c b/drivers/media/platform/mediatek/isp/mtk_seninf.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..3b040f96bb63dc90db7d17c46f920d5597d936db
> --- /dev/null
> +++ b/drivers/media/platform/mediatek/isp/mtk_seninf.c
> @@ -0,0 +1,1636 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2022 MediaTek Inc.
> + */
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/property.h>
> +#include <linux/videodev2.h>
> +#include <media/media-device.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "mtk_seninf_reg.h"
> +
> +#define SENINF_TIMESTAMP_STEP 0x67
> +#define SENINF_SETTLE_DELAY 0x15
> +#define SENINF_HS_TRAIL_PARAMETER 0x8
> +
> +#define SENINF_MAX_NUM_INPUTS 4
> +#define SENINF_MAX_NUM_OUTPUTS 6
> +#define SENINF_MAX_NUM_MUXES 6
> +#define SENINF_MAX_NUM_PADS (SENINF_MAX_NUM_INPUTS + \
> + SENINF_MAX_NUM_OUTPUTS)
> +
> +#define SENINF_DEFAULT_BUS_FMT MEDIA_BUS_FMT_SGRBG10_1X10
> +#define SENINF_DEFAULT_WIDTH 1920
> +#define SENINF_DEFAULT_HEIGHT 1080
> +
> +#define SENINF_PAD_10BIT 0
> +
> +#define SENINF_TEST_MODEL 0
> +#define SENINF_NORMAL_MODEL 1
> +#define SENINF_ALL_ERR_IRQ_EN 0x7f
> +#define SENINF_IRQ_CLR_SEL 0x80000000
> +
> +#define SENINF_MIPI_SENSOR 0x8
> +
> +#define MTK_CSI_MAX_LANES 4
> +
> +/* Port number in the device tree. */
> +enum mtk_seninf_port {
> + CSI_PORT_0 = 0, /* 4D1C or 2D1C */
> + CSI_PORT_1, /* 4D1C */
> + CSI_PORT_2, /* 4D1C */
> + CSI_PORT_0B, /* 2D1C */
> +};
> +
> +enum mtk_seninf_id {
> + SENINF_1 = 0,
> + SENINF_2 = 1,
> + SENINF_3 = 2,
> + SENINF_5 = 4,
> +};
> +
> +static const u32 port_to_seninf_id[] = {
> + [CSI_PORT_0] = SENINF_1,
> + [CSI_PORT_1] = SENINF_3,
> + [CSI_PORT_2] = SENINF_5,
> + [CSI_PORT_0B] = SENINF_2,
> +};
> +
> +enum mtk_seninf_phy_mode {
> + SENINF_PHY_MODE_NONE,
> + SENINF_PHY_MODE_4D1C,
> + SENINF_PHY_MODE_2D1C,
> +};
> +
> +enum mtk_seninf_format_flag {
> + MTK_SENINF_FORMAT_BAYER = BIT(0),
> + MTK_SENINF_FORMAT_DPCM = BIT(1),
> + MTK_SENINF_FORMAT_JPEG = BIT(2),
> + MTK_SENINF_FORMAT_INPUT_ONLY = BIT(3),
> +};
> +
> +/**
> + * struct mtk_seninf_conf - Model-specific SENINF parameters
> + * @model: Model description
> + * @nb_inputs: Number of SENINF inputs
> + * @nb_muxes: Number of SENINF MUX (FIFO) instances
> + * @nb_outputs: Number of outputs (to CAM and CAMSV instances)
> + */
> +struct mtk_seninf_conf {
> + const char *model;
> + u8 nb_inputs;
> + u8 nb_muxes;
> + u8 nb_outputs;
> +};
> +
> +/**
> + * struct mtk_seninf_format_info - Information about media bus formats
> + * @code: V4L2 media bus code
> + * @flags: Flags describing the format, as a combination of MTK_SENINF_FORMAT_*
> + * @bpp: Bits per pixel
> + */
> +struct mtk_seninf_format_info {
> + u32 code;
> + u32 flags;
> + u8 bpp;
> +};
> +
> +/**
> + * struct mtk_seninf_input - SENINF input block
> + * @pad: DT port and media entity pad number
> + * @seninf_id: SENINF hardware instance ID
> + * @base: Memory mapped I/O based address
> + * @seninf: Back pointer to the mtk_seninf
> + * @phy: PHY connected to the input
> + * @phy_mode: PHY operation mode (NONE when the input is not connected)
> + * @bus: CSI-2 bus configuration from DT
> + * @source_sd: Source subdev connected to the input
> + */
> +struct mtk_seninf_input {
> + enum mtk_seninf_port pad;
> + enum mtk_seninf_id seninf_id;
> + void __iomem *base;
> + struct mtk_seninf *seninf;
> +
> + struct phy *phy;
> + enum mtk_seninf_phy_mode phy_mode;
> +
> + struct v4l2_mbus_config_mipi_csi2 bus;
> +
> + struct v4l2_subdev *source_sd;
> +};
> +
> +/**
> + * struct mtk_seninf_mux - SENINF MUX channel
> + * @pad: DT port and media entity pad number
> + * @mux_id: MUX hardware instance ID
> + * @base: Memory mapped I/O based address
> + * @seninf: Back pointer to the mtk_seninf
> + */
> +struct mtk_seninf_mux {
> + unsigned int pad;
> + unsigned int mux_id;
> + void __iomem *base;
> + struct mtk_seninf *seninf;
> +};
> +
> +/**
> + * struct mtk_seninf - Top-level SENINF device
> + * @dev: The (platform) device
> + * @phy: PHYs at the SENINF inputs
> + * @num_clks: Number of clocks in the clks array
> + * @clks: Clocks
> + * @base: Memory mapped I/O base address
> + * @media_dev: Media controller device
> + * @v4l2_dev: V4L2 device
> + * @subdev: V4L2 subdevice
> + * @pads: Media entity pads
> + * @notifier: V4L2 async notifier for source subdevs
> + * @ctrl_handler: V4L2 controls handler
> + * @source_format: Active format on the source pad
> + * @inputs: Array of SENINF inputs
> + * @muxes: Array of MUXes
> + * @conf: Model-specific SENINF parameters
> + * @is_testmode: Whether or not the test pattern generator is enabled
> + */
> +struct mtk_seninf {
> + struct device *dev;
> + struct phy *phy[5];
> + unsigned int num_clks;
> + struct clk_bulk_data *clks;
> + void __iomem *base;
> +
> + struct media_device media_dev;
> + struct v4l2_device v4l2_dev;
> + struct v4l2_subdev subdev;
> + struct media_pad pads[SENINF_MAX_NUM_PADS];
> + struct v4l2_async_notifier notifier;
> + struct v4l2_ctrl_handler ctrl_handler;
> +
> + struct mtk_seninf_input inputs[SENINF_MAX_NUM_INPUTS];
> + struct mtk_seninf_mux muxes[SENINF_MAX_NUM_MUXES];
> +
> + const struct mtk_seninf_conf *conf;
> +
> + bool is_testmode;
> +};
> +
> +inline struct mtk_seninf *sd_to_mtk_seninf(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct mtk_seninf, subdev);
> +}
> +
> +static inline bool mtk_seninf_pad_is_sink(struct mtk_seninf *priv,
> + unsigned int pad)
> +{
> + return pad < priv->conf->nb_inputs;
> +}
> +
> +static inline bool mtk_seninf_pad_is_source(struct mtk_seninf *priv,
> + unsigned int pad)
> +{
> + return !mtk_seninf_pad_is_sink(priv, pad);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Formats
> + */
> +
> +static const struct mtk_seninf_format_info mtk_seninf_formats[] = {
> + {
> + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 10,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 12,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 14,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 14,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 14,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 14,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR16_1X16,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG16_1X16,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_SGRBG16_1X16,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB16_1X16,
> + .flags = MTK_SENINF_FORMAT_BAYER,
> + .bpp = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .bpp = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .bpp = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> + .bpp = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> + .bpp = 16,
> + }, {
> + .code = MEDIA_BUS_FMT_JPEG_1X8,
> + .flags = MTK_SENINF_FORMAT_JPEG,
> + .bpp = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8,
> + .flags = MTK_SENINF_FORMAT_JPEG,
> + .bpp = 8,
> + },
> + /* Keep the input-only formats last. */
> + {
> + .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
> + .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
> + .bpp = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8,
> + .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
> + .bpp = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8,
> + .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
> + .bpp = 8,
> + }, {
> + .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8,
> + .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
> + .bpp = 8,
> + }
> +};
> +
> +static const struct mtk_seninf_format_info *mtk_seninf_format_info(u32 code)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(mtk_seninf_formats); ++i) {
> + if (mtk_seninf_formats[i].code == code)
> + return &mtk_seninf_formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static u32 mtk_seninf_read(struct mtk_seninf *priv, u32 reg)
> +{
> + return readl(priv->base + reg);
> +}
> +
> +static void mtk_seninf_write(struct mtk_seninf *priv, u32 reg, u32 value)
> +{
> + writel(value, priv->base + reg);
> +}
> +
> +static void __mtk_seninf_update(struct mtk_seninf *priv, u32 reg,
> + u32 mask, u32 value)
> +{
> + u32 val = mtk_seninf_read(priv, reg);
> +
> + writel((val & ~mask) | (value & mask), priv->base + reg);
> +}
> +
> +#define mtk_seninf_update(priv, reg, field, val) \
> + __mtk_seninf_update(priv, reg, reg##_##field, \
> + FIELD_PREP(reg##_##field, val))
> +
> +static u32 mtk_seninf_inuput_read(struct mtk_seninf_input *input, u32 reg)
> +{
> + return readl(input->base + reg);
> +}
> +
> +static void mtk_seninf_input_write(struct mtk_seninf_input *input, u32 reg,
> + u32 value)
> +{
> + writel(value, input->base + reg);
> +}
> +
> +static void __mtk_seninf_input_update(struct mtk_seninf_input *input, u32 reg,
> + u32 mask, u32 value)
> +{
> + u32 val = mtk_seninf_inuput_read(input, reg);
> +
> + mtk_seninf_input_write(input, reg, (val & ~mask) | (value & mask));
> +}
> +
> +#define mtk_seninf_input_update(input, reg, field, val) \
> + __mtk_seninf_input_update(input, reg, reg##_##field, \
> + FIELD_PREP(reg##_##field, val))
> +
> +static u32 mtk_seninf_mux_read(struct mtk_seninf_mux *mux, u32 reg)
> +{
> + return readl(mux->base + reg);
> +}
> +
> +static void mtk_seninf_mux_write(struct mtk_seninf_mux *mux, u32 reg,
> + u32 value)
> +{
> + writel(value, mux->base + reg);
> +}
> +
> +static void __mtk_seninf_mux_update(struct mtk_seninf_mux *mux, u32 reg,
> + u32 mask, u32 value)
> +{
> + u32 val = mtk_seninf_mux_read(mux, reg);
> +
> + mtk_seninf_mux_write(mux, reg, (val & ~mask) | (value & mask));
> +}
> +
> +#define mtk_seninf_mux_update(mux, reg, field, val) \
> + __mtk_seninf_mux_update(mux, reg, reg##_##field, \
> + FIELD_PREP(reg##_##field, val))
> +
> +/* -----------------------------------------------------------------------------
> + * Hardware Configuration
> + *
> + * The SENINF is the camera sensor interface. On the input side it contains
> + * input channels (also named SENINF), each made of a CSI-2 receiver, an
> + * interface for parallel sensors, and a test pattern generator. The inputs are
> + * routed through a N:M crossbar switch (TOP MUX) to VC/DT filters with a FIFO
> + * (MUX). The MUX are routed to another N:M crossbar switch (CAM MUX), whose
> + * output is then connected to other IP cores.
> + *
> + * +-------------------------------------------------------+
> + * | SENINF |
> + * | |
> + * +-------+ | +----------+ TOP MUX |
> + * | | | | SENINF | |\ CAM MUX |
> + * | D-PHY | ---> | CSI-2 RX | ---> | | +------------+ |\ |
> + * | | | | TPG | -> | | ---> | MUX (FIFO) | ---> | | ---> CAMSV
> + * +-------+ | +----------+ -> | | +------------+ -> | | |
> + * | |/ -> | | |
> + * | |/ |
> + * | |
> + * ... | ... ... --->
> + * | |
> + * | |
> + * +-------------------------------------------------------+
> + *
> + * The number of PHYs, SENINF and MUX differ between SoCs. MT8167 has a single
> + * MUX and thus no output CAM MUX crossbar switch.
> + */
> +
> +static void mtk_seninf_csi2_setup_phy(struct mtk_seninf *priv)
> +{
> + /* CSI0 */
> + if (priv->inputs[CSI_PORT_0].phy) {
> + const struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_0];
> +
> + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> + DPHY_MODE, 0 /* 4D1C*/);
s/4D1C/4D1C /
> + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> + CK_SEL_1, input->bus.clock_lane);
> + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> + CK_SEL_2, 2);
> + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> + PHY_SENINF_LANE_MUX_CSI0_EN, 1);
In the review of v6, I wrote that you're reading and writing the same
register 4 times to set 4 different fields. This could be replaced by a
single register access. I seem to recall that this was needed, and that
writing the whole register in one go didn't produce the desired
behaviour, at least for some registers. Is that right ?
It would be nice to improve this where possible, here and everywhere
else in the driver. I won't make it a blocker, but I really dislike this
pattern :-(
> + }
> +
> + /* CSI1 */
> + if (priv->inputs[CSI_PORT_1].phy) {
> + const struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_1];
> +
> + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> + DPHY_MODE, 0 /* 4D1C */);
> + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> + CK_SEL_1, input->bus.clock_lane);
> + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> + PHY_SENINF_LANE_MUX_CSI1_EN, 1);
> + }
> +}
> +
> +static void mtk_seninf_input_setup_csi2_rx(struct mtk_seninf_input *input)
> +{
> + unsigned int lanes[MTK_CSI_MAX_LANES] = { };
> + unsigned int i;
> +
> + /*
> + * Configure data lane muxing. In 2D1C mode, lanes 0 to 2 correspond to
> + * CSIx[AB]_L{0,1,2}, and in 4D1C lanes 0 to 5 correspond to
> + * CSIxA_L{0,1,2}, CSIxB_L{0,1,2}.
> + *
> + * The clock lane must be skipped when calculating the index of the
> + * physical data lane. For instance, in 4D1C mode, the sensor clock
> + * lane is typically connected to lane 2 (CSIxA_L2), and the sensor
> + * data lanes 0-3 to lanes 1 (CSIxA_L1), 3 (CSIxB_L0), 0 (CSIxA_L0) and
> + * 4 (CSIxB_L1). The when skipping the clock lane, the data lane
> + * indices become 1, 2, 0 and 3.
> + */
> + for (i = 0; i < input->bus.num_data_lanes; ++i) {
> + lanes[i] = input->bus.data_lanes[i];
> + if (lanes[i] > input->bus.clock_lane)
> + lanes[i]--;
> + }
> +
> + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> + CSI0_BIST_LN0_MUX, lanes[0]);
> + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> + CSI0_BIST_LN1_MUX, lanes[1]);
> + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> + CSI0_BIST_LN2_MUX, lanes[2]);
> + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> + CSI0_BIST_LN3_MUX, lanes[3]);
> +}
> +
> +static s64 mtk_seninf_get_clk_divider(struct mtk_seninf *priv,
> + int pad_num,
This holds on a single line.
> + u8 bpp, unsigned int num_data_lanes)
> +{
> + struct media_entity *entity = &priv->subdev.entity;
> + struct media_pad *pad;
> + struct v4l2_subdev *sd;
> + s64 link_frequency, pixel_clock;
> +
> +
> + if (!(entity->pads[pad_num].flags & MEDIA_PAD_FL_SINK))
> + return -ENODEV;
> +
> + pad = media_pad_remote_pad_first(&entity->pads[pad_num]);
> + if (!pad)
> + return -ENOENT;
> +
> + if (!is_media_entity_v4l2_subdev(pad->entity))
> + return -ENOENT;
As those conditions that can happen, wouldn't pipeline validation have
failed ? If those conditions can't happen, then
mtk_seninf_input_setup_csi2() and mtk_seninf_start() can become void
functions.
> +
> + sd = media_entity_to_v4l2_subdev(pad->entity);
> + link_frequency = v4l2_get_link_freq(sd->ctrl_handler, bpp,
> + num_data_lanes * 2);
> + pixel_clock = div_u64(link_frequency * 2 * num_data_lanes, bpp);
> + /*
> + * According to datasheet: Sensor master clock = ISP_clock/(CLKCNT +1)
> + * we also have the following constraint:
> + * pixel_clock >= Sensor master clock
> + */
> + return div_u64(clk_get_rate(priv->clks[0].clk), pixel_clock) - 1;
> +}
> +
> +static int mtk_seninf_input_setup_csi2(struct mtk_seninf *priv,
> + struct mtk_seninf_input *input,
> + struct v4l2_subdev_state *state)
> +{
> + const struct mtk_seninf_format_info *fmtinfo;
> + const struct v4l2_mbus_framefmt *format;
> + unsigned int num_data_lanes = input->bus.num_data_lanes;
> + unsigned int val = 0;
> + s64 clock_count;
> +
> + format = v4l2_subdev_state_get_format(state, input->pad, 0);
> + fmtinfo = mtk_seninf_format_info(format->code);
> +
> + /* Configure timestamp */
> + mtk_seninf_input_write(input, SENINF_TG1_TM_STP, SENINF_TIMESTAMP_STEP);
> +
> + /* HQ */
> + /*
> + * Configure phase counter. Zero means:
> + * - Sensor master clock: ISP_CLK
> + * - Sensor clock polarity: Rising edge
> + * - Sensor reset deasserted
> + * - Sensor powered up
> + * - Pixel clock inversion disabled
> + * - Sensor master clock polarity disabled
> + * - Phase counter disabled
> + */
> + mtk_seninf_input_write(input, SENINF_TG1_PH_CNT, 0x0);
> +
> + clock_count = mtk_seninf_get_clk_divider(priv, input->pad,
> + fmtinfo->bpp,
> + num_data_lanes);
> + if (clock_count < 0)
> + return clock_count;
> +
> + clock_count = FIELD_PREP(SENINF_TG1_SEN_CK_CLKCNT, clock_count) | 0x1;
> + mtk_seninf_input_write(input, SENINF_TG1_SEN_CK, clock_count);
> +
> + /* First Enable Sensor interface and select pad (0x1a04_0200) */
> + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_EN, 1);
> + mtk_seninf_input_update(input, SENINF_CTRL, PAD2CAM_DATA_SEL,
> + SENINF_PAD_10BIT);
> + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_SRC_SEL, 0);
> + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_CSI2_IP_EN, 1);
> + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_NCSI2_IP_EN, 0);
> +
> + /* DPCM Enable */
> + if (fmtinfo->flags & MTK_SENINF_FORMAT_DPCM)
> + val = SENINF_CSI2_DPCM_DI_2A_DPCM_EN;
> + else
> + val = SENINF_CSI2_DPCM_DI_30_DPCM_EN;
> + mtk_seninf_input_write(input, SENINF_CSI2_DPCM, val);
> +
> + /* Settle delay */
> + mtk_seninf_input_update(input, SENINF_CSI2_LNRD_TIMING,
> + DATA_SETTLE_PARAMETER, SENINF_SETTLE_DELAY);
> +
> + /* CSI2 control */
> + val = mtk_seninf_inuput_read(input, SENINF_CSI2_CTL)
> + | (FIELD_PREP(SENINF_CSI2_CTL_ED_SEL,
> + DATA_HEADER_ORDER_DI_WCL_WCH)
> + | SENINF_CSI2_CTL_CLOCK_LANE_EN | (BIT(num_data_lanes) - 1));
> + mtk_seninf_input_write(input, SENINF_CSI2_CTL, val);
> +
> + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> + BYPASS_LANE_RESYNC, 0);
> + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> + CDPHY_SEL, 0);
> + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> + CPHY_LANE_RESYNC_CNT, 3);
> + mtk_seninf_input_update(input, SENINF_CSI2_MODE, CSR_CSI2_MODE, 0);
> + mtk_seninf_input_update(input, SENINF_CSI2_MODE, CSR_CSI2_HEADER_LEN,
> + 0);
> + mtk_seninf_input_update(input, SENINF_CSI2_DPHY_SYNC, SYNC_SEQ_MASK_0,
> + 0xff00);
> + mtk_seninf_input_update(input, SENINF_CSI2_DPHY_SYNC, SYNC_SEQ_PAT_0,
> + 0x001d);
> +
> + mtk_seninf_input_update(input, SENINF_CSI2_CTL, CLOCK_HS_OPTION, 0);
> + mtk_seninf_input_update(input, SENINF_CSI2_CTL, HSRX_DET_EN, 0);
> + mtk_seninf_input_update(input, SENINF_CSI2_CTL, HS_TRAIL_EN, 1);
> + mtk_seninf_input_update(input, SENINF_CSI2_HS_TRAIL, HS_TRAIL_PARAMETER,
> + SENINF_HS_TRAIL_PARAMETER);
> +
> + /* Set debug port to output packet number */
> + mtk_seninf_input_update(input, SENINF_CSI2_DGB_SEL, DEBUG_EN, 1);
> + mtk_seninf_input_update(input, SENINF_CSI2_DGB_SEL, DEBUG_SEL, 0x1a);
> +
> + /* HQ */
> + mtk_seninf_input_write(input, SENINF_CSI2_SPARE0, 0xfffffffe);
> +
> + /* Reset the CSI2 to commit changes */
> + mtk_seninf_input_update(input, SENINF_CTRL, CSI2_SW_RST, 1);
> + udelay(1);
> + mtk_seninf_input_update(input, SENINF_CTRL, CSI2_SW_RST, 0);
> +
> + return 0;
> +}
> +
> +static void mtk_seninf_mux_setup(struct mtk_seninf_mux *mux,
> + struct mtk_seninf_input *input,
> + struct v4l2_subdev_state *state)
> +{
> + const struct mtk_seninf_format_info *fmtinfo;
> + const struct v4l2_mbus_framefmt *format;
> + unsigned int pix_sel_ext;
> + unsigned int pix_sel;
> + unsigned int hs_pol = 0;
> + unsigned int vs_pol = 0;
> + unsigned int val;
> + u32 rst_mask;
> +
> + format = v4l2_subdev_state_get_format(state, input->pad, 0);
> + fmtinfo = mtk_seninf_format_info(format->code);
> +
> + /* Enable mux */
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_MUX_EN, 1);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_SRC_SEL,
> + SENINF_MIPI_SENSOR);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_SRC_SEL_EXT,
> + SENINF_NORMAL_MODEL);
> +
> + pix_sel_ext = 0;
> + pix_sel = 1;
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_PIX_SEL_EXT,
> + pix_sel_ext);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_PIX_SEL, pix_sel);
> +
> + if (fmtinfo->flags & MTK_SENINF_FORMAT_JPEG) {
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 0);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN,
> + FIFO_FLUSH_EN_JPEG_2_PIXEL_MODE);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN,
> + FIFO_PUSH_EN_JPEG_2_PIXEL_MODE);
> + } else {
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 2);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN,
> + FIFO_FLUSH_EN_NORMAL_MODE);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN,
> + FIFO_PUSH_EN_NORMAL_MODE);
> + }
> +
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_POL, hs_pol);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_VSYNC_POL, vs_pol);
> +
> + val = mtk_seninf_mux_read(mux, SENINF_MUX_CTRL);
> + rst_mask = SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> + SENINF_MUX_CTRL_SENINF_MUX_SW_RST;
> +
> + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL, val | rst_mask);
> + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL, val & ~rst_mask);
> +
> + /* HQ */
> + val = SENINF_FIFO_FULL_SEL;
> +
> + /* SPARE field meaning is unknown */
> + val |= 0xc0000;
> + mtk_seninf_mux_write(mux, SENINF_MUX_SPARE, val);
> +}
> +
> +static void mtk_seninf_top_mux_setup(struct mtk_seninf *priv,
> + enum mtk_seninf_id seninf_id,
> + struct mtk_seninf_mux *mux)
mux can be const. Please constify pointer arguments when they don't have
to be modified (both from a language point of view, and conceptually,
for instance priv shouldn't be const as you're writing registers in this
function, which modifies the state of the device)..
> +{
> + unsigned int val;
> +
> + /*
> + * Use the top mux (from SENINF input to MUX) to configure routing, and
> + * hardcode a 1:1 mapping from the MUX instances to the SENINF outputs.
> + */
> + val = mtk_seninf_read(priv, SENINF_TOP_MUX_CTRL)
> + & ~(0xf << (mux->mux_id * 4));
> + val |= (seninf_id & 0xf) << (mux->mux_id * 4);
> + mtk_seninf_write(priv, SENINF_TOP_MUX_CTRL, val);
> +
> + /*
> + * We currently support only seninf version 3.0
> + * where camsv0 and camsv1 are hardwired respectively to
> + * SENINF_CAM2 and SENINF_CAM3 i.e :
> + * - SENINF_TOP_CAM_MUX_CTRL[11:8] = 0
> + * - SENINF_TOP_CAM_MUX_CTRL[15:12] = 1
> + * so we hardcode it here
> + */
> + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> + SENINF_CAM2_MUX_SRC_SEL, 0);
> + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> + SENINF_CAM3_MUX_SRC_SEL, 1);
> +}
> +
> +static void seninf_enable_test_pattern(struct mtk_seninf *priv,
> + struct v4l2_subdev_state *state)
> +{
> + struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_0];
> + struct mtk_seninf_mux *mux = &priv->muxes[0];
> + const struct mtk_seninf_format_info *fmtinfo;
> + const struct v4l2_mbus_framefmt *format;
> + unsigned int val;
> + unsigned int pix_sel_ext;
> + unsigned int pix_sel;
> + unsigned int hs_pol = 0;
> + unsigned int vs_pol = 0;
> + unsigned int seninf = 0;
> + unsigned int tm_size = 0;
> + unsigned int mux_id = mux->mux_id;
> +
> + format = v4l2_subdev_state_get_format(state, priv->conf->nb_inputs, 0);
> + fmtinfo = mtk_seninf_format_info(format->code);
> +
> + mtk_seninf_update(priv, SENINF_TOP_CTRL, MUX_LP_MODE, 0);
> +
> + mtk_seninf_update(priv, SENINF_TOP_CTRL, SENINF_PCLK_EN, 1);
> + mtk_seninf_update(priv, SENINF_TOP_CTRL, SENINF2_PCLK_EN, 1);
> +
> + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_EN, 1);
> + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_SRC_SEL, 1);
> + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_TESTMDL_IP_EN,
> + 1);
> +
> + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_EN, 1);
> + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_PAT, 0xc);
> + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_VSYNC, 4);
> + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_DUMMYPXL, 0x28);
> +
> + if (fmtinfo->flags & MTK_SENINF_FORMAT_BAYER)
> + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_FMT, 0x0);
> + else
> + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_FMT, 0x1);
> +
> + tm_size = FIELD_PREP(SENINF_TG1_TM_SIZE_TM_LINE, format->height + 8);
> + switch (format->code) {
> + case MEDIA_BUS_FMT_UYVY8_1X16:
> + case MEDIA_BUS_FMT_VYUY8_1X16:
> + case MEDIA_BUS_FMT_YUYV8_1X16:
> + case MEDIA_BUS_FMT_YVYU8_1X16:
> + tm_size |= FIELD_PREP(SENINF_TG1_TM_SIZE_TM_PXL,
> + format->width * 2);
> + break;
> + default:
> + tm_size |= FIELD_PREP(SENINF_TG1_TM_SIZE_TM_PXL, format->width);
> + break;
> + }
> + mtk_seninf_input_write(input, SENINF_TG1_TM_SIZE, tm_size);
> +
> + mtk_seninf_input_write(input, SENINF_TG1_TM_CLK,
> + TEST_MODEL_CLK_DIVIDED_CNT);
> + mtk_seninf_input_write(input, SENINF_TG1_TM_STP, TIME_STAMP_DIVIDER);
> +
> + /* Set top mux */
> + val = (mtk_seninf_read(priv, SENINF_TOP_MUX_CTRL)
> + & (~(0xf << (mux_id * 4)))) |
> + ((seninf & 0xf) << (mux_id * 4));
> + mtk_seninf_write(priv, SENINF_TOP_MUX_CTRL, val);
> +
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_MUX_EN, 1);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_SRC_SEL_EXT,
> + SENINF_TEST_MODEL);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_SRC_SEL, 1);
> +
> + pix_sel_ext = 0;
> + pix_sel = 1;
> +
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT,
> + SENINF_PIX_SEL_EXT, pix_sel_ext);
> +
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_PIX_SEL, pix_sel);
> +
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN, 0x1f);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN, 0x1b);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 2);
> +
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_POL, hs_pol);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_VSYNC_POL, vs_pol);
> + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_MASK, 1);
> +
> + mtk_seninf_mux_write(mux, SENINF_MUX_INTEN,
> + SENINF_IRQ_CLR_SEL | SENINF_ALL_ERR_IRQ_EN);
> +
> + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL,
> + mtk_seninf_mux_read(mux, SENINF_MUX_CTRL) |
> + SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> + SENINF_MUX_CTRL_SENINF_MUX_SW_RST);
> + udelay(1);
> + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL,
> + mtk_seninf_mux_read(mux, SENINF_MUX_CTRL) &
> + ~(SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> + SENINF_MUX_CTRL_SENINF_MUX_SW_RST));
> +
> + //check this
> + mtk_seninf_write(priv, SENINF_TOP_CAM_MUX_CTRL, 0x76540010);
> + /*
> + * We currently support only seninf version 3.0
> + * where camsv0 and camsv1 are hardwired respectively to
> + * test pattern is valid only for seninf_1 (id 0) i.e :
I'm having trouble parsing this sentence.
> + * - SENINF_TOP_CAM_MUX_CTRL[11:8] = 0
> + * - SENINF_TOP_CAM_MUX_CTRL[15:12] = 0
> + * so we hardcode it here
> + */
> + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> + SENINF_CAM2_MUX_SRC_SEL, 0);
> + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> + SENINF_CAM3_MUX_SRC_SEL, 0);
Here you're reconfiguring the whole TOP_CAM_MUX. Unless I'm mistaken, if
you want to capture from one sensor and use the TPG for the other camsv,
the configuration performed here and in mtk_seninf_top_mux_setup() will
clash. Isn't that a problem ?
> +}
> +
> +static int mtk_seninf_start(struct mtk_seninf *priv,
> + struct v4l2_subdev_state *state,
> + struct mtk_seninf_input *input,
> + struct mtk_seninf_mux *mux)
> +{
> + int ret;
> +
> + phy_power_on(input->phy);
> +
> + mtk_seninf_input_setup_csi2_rx(input);
> + ret = mtk_seninf_input_setup_csi2(priv, input, state);
> + if (ret)
> + return ret;
> +
> + mtk_seninf_mux_setup(mux, input, state);
> + mtk_seninf_top_mux_setup(priv, input->seninf_id, mux);
> + return 0;
> +}
> +
> +static void mtk_seninf_stop(struct mtk_seninf *priv,
> + struct mtk_seninf_input *input)
> +{
> + unsigned int val;
> +
> + /* Disable CSI2(2.5G) first */
> + val = mtk_seninf_inuput_read(input, SENINF_CSI2_CTL);
> + val &= ~(SENINF_CSI2_CTL_CLOCK_LANE_EN |
> + SENINF_CSI2_CTL_DATA_LANE3_EN |
> + SENINF_CSI2_CTL_DATA_LANE2_EN |
> + SENINF_CSI2_CTL_DATA_LANE1_EN |
> + SENINF_CSI2_CTL_DATA_LANE0_EN);
> + mtk_seninf_input_write(input, SENINF_CSI2_CTL, val);
> +
> + if (!priv->is_testmode)
> + phy_power_off(input->phy);
What happens if userspace alls STREAMON with the TPG disabled, then
enables the TPG, and calls STREAMOFF ? It looks like you should disable
changing the TPG control while streaming. You can use
v4l2_subdev_is_streaming() to check if the subdev is streaming, but
you'll need to hold the active state lock.
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 Controls
> + */
> +
> +static int seninf_set_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct mtk_seninf *priv = container_of(ctrl->handler,
> + struct mtk_seninf, ctrl_handler);
> +
> + switch (ctrl->id) {
> + case V4L2_CID_TEST_PATTERN:
> + priv->is_testmode = !!ctrl->val;
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static const struct v4l2_ctrl_ops seninf_ctrl_ops = {
> + .s_ctrl = seninf_set_ctrl,
> +};
> +
> +static const char *const seninf_test_pattern_menu[] = {
> + "No test pattern",
Drivers normally use "Disabled" for this.
> + "Static horizontal color bars",
"Static Horizontal Color Bars",
Not that it would matter too much, but menu entries usually use Camel
Case, so let's be consistent.
> +};
> +
> +static int seninf_initialize_controls(struct mtk_seninf *priv)
> +{
> + struct v4l2_ctrl_handler *handler;
> + int ret;
> +
> + handler = &priv->ctrl_handler;
> + ret = v4l2_ctrl_handler_init(handler, 2);
The driver creates a single control.
> + if (ret)
> + return ret;
> +
> + v4l2_ctrl_new_std_menu_items(handler, &seninf_ctrl_ops,
> + V4L2_CID_TEST_PATTERN,
> + ARRAY_SIZE(seninf_test_pattern_menu) - 1,
> + 0, 0, seninf_test_pattern_menu);
> +
> + priv->is_testmode = false;
> +
> + if (handler->error) {
> + ret = handler->error;
> + dev_err(priv->dev,
> + "Failed to init controls(%d)\n", ret);
> + v4l2_ctrl_handler_free(handler);
> + return ret;
> + }
> +
> + priv->subdev.ctrl_handler = handler;
> +
> + return 0;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 Subdev Operations
> + */
Missing blank line.
> +static int seninf_enable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state, u32 pad,
> + u64 streams_mask)
> +{
> + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> + struct mtk_seninf_input *input;
> + struct mtk_seninf_mux *mux;
> + struct v4l2_subdev *source;
> + u32 sink_pad;
> + int ret;
> +
> + /* Stream control can only operate on source pads. */
> + if (pad < priv->conf->nb_inputs ||
> + pad >= priv->conf->nb_inputs + priv->conf->nb_outputs)
> + return -EINVAL;
> +
> + /*
> + * Locate the SENINF input and MUX for the source pad.
> + */
/* Locate the SENINF input and MUX for the source pad. */
Same below.
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad,
> + 0, &sink_pad, NULL);
> + if (ret) {
> + dev_dbg(priv->dev, "No sink pad routed to source pad %u\n",
> + pad);
> + return ret;
> + }
> +
> + input = &priv->inputs[sink_pad];
> + mux = &priv->muxes[pad - priv->conf->nb_inputs];
> +
> + ret = pm_runtime_get_sync(priv->dev);
Use pm_runtime_resume_and_get() and drop pm_runtime_put_noidle() in the
error path.
> + if (ret < 0) {
> + dev_err(priv->dev, "Failed to pm_runtime_get_sync: %d\n", ret);
> + pm_runtime_put_noidle(priv->dev);
> + return ret;
> + }
> +
> + /* If test mode is enabled, just enable the test pattern generator. */
> + if (priv->is_testmode) {
> + seninf_enable_test_pattern(priv, state);
> + return 0;
> + }
> +
> + /* Start the SENINF first and then the source. */
> + ret = mtk_seninf_start(priv, state, input, mux);
> + if (ret) {
> + dev_err(priv->dev, "failed to start seninf: %d\n", ret);
Missing
pm_runtime_put(priv->dev);
I would move error handling to the bottom of the function, with gotos
and error labels.
> + return ret;
> + }
> +
> + source = input->source_sd;
> + ret = v4l2_subdev_call(source, video, s_stream, 1);
Use v4l2_subdev_enable_streams().
> + if (ret) {
> + dev_err(priv->dev, "failed to start source %s: %d\n",
> + source->entity.name, ret);
> + mtk_seninf_stop(priv, input);
> + pm_runtime_put(priv->dev);
> + }
> +
> + return ret;
> +}
> +
> +static int seninf_disable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state, u32 pad,
> + u64 streams_mask)
> +{
> + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> + struct mtk_seninf_input *input;
> + struct mtk_seninf_mux *mux;
> + struct v4l2_subdev *source;
> + u32 sink_pad;
> + int ret;
> +
> + /* Stream control can only operate on source pads. */
> + if (pad < priv->conf->nb_inputs ||
> + pad >= priv->conf->nb_inputs + priv->conf->nb_outputs)
> + return -EINVAL;
> +
> + /*
> + * Locate the SENINF input and MUX for the source pad.
> + *
> + */
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad,
> + 0, &sink_pad, NULL);
> + if (ret) {
> + dev_dbg(priv->dev, "No sink pad routed to source pad %u\n",
> + pad);
> + return ret;
> + }
> +
> + input = &priv->inputs[sink_pad];
> + mux = &priv->muxes[pad - priv->conf->nb_inputs];
> +
> + if (!priv->is_testmode) {
> + source = input->source_sd;
> + ret = v4l2_subdev_call(source, video, s_stream, 0);
Use v4l2_subdev_disable_streams().
> + if (ret)
> + dev_err(priv->dev,
> + "failed to stop source %s: %d\n",
> + source->entity.name, ret);
> + }
> +
> + mtk_seninf_stop(priv, input);
> + pm_runtime_put(priv->dev);
> + return ret;
> +}
> +
> +static const struct v4l2_mbus_framefmt mtk_seninf_default_fmt = {
> + .code = SENINF_DEFAULT_BUS_FMT,
> + .width = SENINF_DEFAULT_WIDTH,
> + .height = SENINF_DEFAULT_HEIGHT,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .xfer_func = V4L2_XFER_FUNC_DEFAULT,
> + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
> + .quantization = V4L2_QUANTIZATION_DEFAULT,
> +};
> +
> +static int __seninf_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + int ret;
> +
> + 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,
> + &mtk_seninf_default_fmt);
> +}
> +
> +static int seninf_init_state(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> + struct v4l2_subdev_route routes[SENINF_MAX_NUM_OUTPUTS] = { };
> + struct v4l2_subdev_krouting routing = {
> + .routes = routes,
> + .num_routes = priv->conf->nb_outputs,
> + };
> + unsigned int i;
> +
> + /*
> + * Initialize one route for supported source pads.
> + * It is a single route from the first sink pad to the source pad,
> + * while on SENINF 5.0 the routing table will map sink pads to source
> + * pads connected to CAMSV 1:1 (skipping the first two source pads
> + * connected to the CAM instances).
> + */
> + for (i = 0; i < routing.num_routes; i++) {
> + struct v4l2_subdev_route *route = &routes[i];
> +
> + route->sink_pad = i;
> + route->sink_stream = 0;
> + route->source_pad = priv->conf->nb_inputs + i;
> + route->source_stream = 0;
> + route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> + }
> +
> + return __seninf_set_routing(sd, state, &routing);
> +}
> +
> +static int seninf_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + const struct mtk_seninf_format_info *fmtinfo;
> + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> +
> + if (code->index >= ARRAY_SIZE(mtk_seninf_formats))
> + return -EINVAL;
> +
> + fmtinfo = &mtk_seninf_formats[code->index];
> + if (fmtinfo->flags & MTK_SENINF_FORMAT_INPUT_ONLY &&
> + mtk_seninf_pad_is_source(priv, code->pad))
> + return -EINVAL;
> +
> + code->code = fmtinfo->code;
> +
> + return 0;
> +}
> +
> +static int seninf_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> + const struct mtk_seninf_format_info *fmtinfo;
> + struct v4l2_mbus_framefmt *format;
> +
> + /*
> + * TODO (?): We should disallow setting formats on the source pad
> + * completely, as the SENINF can't perform any processing. This would
> + * however break usage of the test pattern generator, as there would be
> + * no way to configure formats at all when no active input is selected.
> + */
As commented in v6, I think this needs to be solved.
> +
> + /*
> + * Default to the first format if the requested media bus code isn't
> + * supported.
> + */
> + fmtinfo = mtk_seninf_format_info(fmt->format.code);
> + if (!fmtinfo) {
> + fmtinfo = &mtk_seninf_formats[0];
> + fmt->format.code = fmtinfo->code;
> + }
> +
> + /* Interlaced formats are not supported yet. */
> + fmt->format.field = V4L2_FIELD_NONE;
> +
> + /* Store the format. */
> + format = v4l2_subdev_state_get_format(state, fmt->pad, fmt->stream);
> + if (!format)
> + return -EINVAL;
> +
> + *format = fmt->format;
> +
> + if (mtk_seninf_pad_is_source(priv, fmt->pad))
> + return 0;
> +
> + /* Propagate the format to the corresponding source pad. */
> + format = v4l2_subdev_state_get_opposite_stream_format(state, fmt->pad,
> + fmt->stream);
> + if (!format)
> + return -EINVAL;
> +
> + *format = fmt->format;
Another comment from v6 that seems to have been lost:
If fmtinfo is one of the INPUT_ONLY formats, the corresponding
DPCM-uncompressed format must be set on the source pad. To facilitate
this, you want need to add a .uncompressed field to the format info
structure to store the corresponding uncompressed format.
> +
> + return 0;
> +}
> +
> +static int seninf_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + return __seninf_set_routing(sd, state, routing);
> +}
> +
> +static const struct v4l2_subdev_core_ops seninf_subdev_core_ops = {
> + .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> + .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> +};
> +
> +static const struct v4l2_subdev_pad_ops seninf_subdev_pad_ops = {
> + .enum_mbus_code = seninf_enum_mbus_code,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = seninf_set_fmt,
> + .link_validate = v4l2_subdev_link_validate_default,
> + .set_routing = seninf_set_routing,
> + .enable_streams = seninf_enable_streams,
> + .disable_streams = seninf_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops seninf_subdev_ops = {
> + .core = &seninf_subdev_core_ops,
> + .pad = &seninf_subdev_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops seninf_subdev_internal_ops = {
> + .init_state = seninf_init_state,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Media Entity Operations
> + */
> +
> +static const struct media_entity_operations seninf_media_ops = {
> + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> + .link_validate = v4l2_subdev_link_validate,
> + .has_pad_interdep = v4l2_subdev_has_pad_interdep,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Async Subdev Notifier
> + */
> +
> +struct mtk_seninf_async_subdev {
> + struct v4l2_async_connection asc;
> + struct mtk_seninf_input *input;
> + unsigned int port;
> +};
> +
> +static int mtk_seninf_fwnode_parse(struct device *dev,
> + unsigned int id)
This holds on a single line.
> +
Extra blank line.
I would move this function below mtk_seninf_async_ops as it's called
directly in the probe path, before the bound and complete callbacks.
> +{
> + static const char * const phy_names[] = {
> + "csi0", "csi1", "csi2", "csi0b" };
static const char * const phy_names[] = {
"csi0", "csi1", "csi2", "csi0b"
};
> + struct mtk_seninf *priv = dev_get_drvdata(dev);
> + struct fwnode_handle *ep, *fwnode;
> + struct mtk_seninf_input *input;
> + struct mtk_seninf_async_subdev *asd;
> + struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> + unsigned int port;
> + int ret;
> +
> + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), id, 0, 0);
> + if (!ep)
> + return 0;
> +
> + fwnode = fwnode_graph_get_remote_endpoint(ep);
> + ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> + if (ret) {
> + dev_err(dev, "Failed to parse %p fw\n", to_of_node(fwnode));
dev_err(dev, "Failed to parse %pfw\n", fwnode);
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + asd = v4l2_async_nf_add_fwnode(&priv->notifier, fwnode,
> + struct mtk_seninf_async_subdev);
> + if (IS_ERR(asd)) {
> + ret = PTR_ERR(asd);
> + goto out;
> + }
> +
> + port = vep.base.port;
> + asd->port = port;
> +
> + if (mtk_seninf_pad_is_source(priv, port)) {
> + ret = 0;
> + goto out;
> + }
> +
> + input = &priv->inputs[port];
> +
> + input->pad = port;
> + input->seninf_id = port_to_seninf_id[port];
> + input->base = priv->base + 0x1000 * input->seninf_id;
> + input->seninf = priv;
> +
> + input->bus = vep.bus.mipi_csi2;
> +
> + input->phy = devm_phy_get(dev, phy_names[port]);
> + if (IS_ERR(input->phy)) {
> + dev_err(dev, "failed to get phy: %ld\n", PTR_ERR(input->phy));
> + ret = PTR_ERR(input->phy);
> + goto out;
> + }
> + input->phy_mode = SENINF_PHY_MODE_4D1C;
> +
> + asd->input = input;
> +
> + ret = 0;
> +out:
> + fwnode_handle_put(ep);
> + fwnode_handle_put(fwnode);
> + return ret;
> +}
> +
> +static int mtk_seninf_notifier_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *sd,
> + struct v4l2_async_connection *asc)
> +{
> + struct mtk_seninf *priv = container_of(notifier, struct mtk_seninf,
> + notifier);
> + struct mtk_seninf_async_subdev *asd =
> + container_of(asc, struct mtk_seninf_async_subdev, asc);
> + struct device_link *link;
> + int ret;
> +
> + dev_dbg(priv->dev, "%s bound to SENINF port %u\n", sd->entity.name,
> + asd->port);
> +
> + if (mtk_seninf_pad_is_sink(priv, asd->port)) {
> + struct mtk_seninf_input *input = asd->input;
> +
> + input->source_sd = sd;
> +
> + link = device_link_add(priv->dev, sd->dev,
> + DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
> + if (!link) {
> + dev_err(priv->dev,
> + "Failed to create device link from source %s\n",
> + sd->name);
> + return -EINVAL;
> + }
> +
> + ret = v4l2_create_fwnode_links_to_pad(sd,
> + &priv->pads[input->pad],
> + MEDIA_LNK_FL_IMMUTABLE |
> + MEDIA_LNK_FL_ENABLED);
> + } else {
> + link = device_link_add(sd->dev, priv->dev,
> + DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
> + if (!link) {
> + dev_err(priv->dev,
> + "Failed to create device link to output %s\n",
> + sd->name);
> + return -EINVAL;
> + }
> +
> + ret = v4l2_create_fwnode_links_to_pad(&priv->subdev,
> + &sd->entity.pads[0],
> + MEDIA_LNK_FL_IMMUTABLE |
> + MEDIA_LNK_FL_ENABLED);
> + }
Add a blank line here.
> + if (ret) {
> + dev_err(priv->dev, "Failed to create links between SENINF port %u and %s (%d)\n",
> + asd->port, sd->entity.name, ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int mtk_seninf_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct mtk_seninf *priv = container_of(notifier, struct mtk_seninf,
> + notifier);
> + int ret;
> +
> + ret = v4l2_device_register_subdev_nodes(&priv->v4l2_dev);
> + if (ret) {
> + dev_err(priv->dev, "Failed to register subdev nodes: %d\n",
> + ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct v4l2_async_notifier_operations mtk_seninf_async_ops = {
> + .bound = mtk_seninf_notifier_bound,
> + .complete = mtk_seninf_notifier_complete,
> +};
> +
> +static int mtk_seninf_media_init(struct mtk_seninf *priv)
> +{
> + struct media_device *media_dev = &priv->media_dev;
> + const struct mtk_seninf_conf *conf = priv->conf;
> + unsigned int num_pads = conf->nb_outputs + conf->nb_inputs;
> + struct media_pad *pads = priv->pads;
> + struct device *dev = priv->dev;
> + unsigned int i;
> + int ret;
> +
> + media_dev->dev = dev;
> + strscpy(media_dev->model, conf->model, sizeof(media_dev->model));
> + media_dev->hw_revision = 0;
> + media_device_init(media_dev);
> +
> + for (i = 0; i < conf->nb_inputs; i++)
> + pads[i].flags = MEDIA_PAD_FL_SINK;
> + for (i = conf->nb_inputs; i < num_pads; i++)
> + pads[i].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&priv->subdev.entity, num_pads, pads);
> + if (ret) {
> + media_device_cleanup(media_dev);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int mtk_seninf_v4l2_async_register(struct mtk_seninf *priv)
> +{
> + const struct mtk_seninf_conf *conf = priv->conf;
> + struct device *dev = priv->dev;
> + unsigned int i;
> + int ret;
> +
> + v4l2_async_nf_init(&priv->notifier, &priv->v4l2_dev);
> +
> + for (i = 0; i < conf->nb_inputs + conf->nb_outputs; ++i) {
> + ret = mtk_seninf_fwnode_parse(dev, i);
> +
> + if (ret) {
> + dev_err(dev,
> + "Failed to parse endpoint at port %d: %d\n",
i is unsigned, so use %u
> + i, ret);
> + goto err_clean_notififer;
> + }
> + }
> +
> + priv->notifier.ops = &mtk_seninf_async_ops;
> + ret = v4l2_async_nf_register(&priv->notifier);
> + if (ret) {
> + dev_err(dev, "Failed to register async notifier: %d\n", ret);
> + goto err_clean_notififer;
> + }
> + return 0;
> +
> +err_clean_notififer:
> + v4l2_async_nf_cleanup(&priv->notifier);
> +
> + return ret;
> +}
> +
/* -----------------------------------------------------------------------------
* Probe & Remove
*/
as we leave the "Async Subdev Notifier" section.
> +static int mtk_seninf_v4l2_register(struct mtk_seninf *priv)
> +{
> + struct v4l2_subdev *sd = &priv->subdev;
> + struct device *dev = priv->dev;
> + int ret;
> +
> + /* Initialize media device & pads. */
> + ret = mtk_seninf_media_init(priv);
This function intializes both the media device and the entity within the
seninf subdev, while the rest of the subdev is initialized below. I
think that makes the code flow more difficult to understand.
The following function could go above, just after seninf_media_ops:
static int seninf_subdev_init(struct mtk_seninf *priv)
{
const unsigned int num_pads = priv->conf->nb_outputs
+ priv->conf->nb_inputs;
struct v4l2_subdev *sd = &priv->subdev;
struct media_pad *pads = priv->pads;
unsigned int i;
int ret;
/* Initialize the entity. */
for (i = 0; i < priv->conf->nb_inputs; i++)
pads[i].flags = MEDIA_PAD_FL_SINK;
for ( ; i < num_pads; i++)
pads[i].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&priv->subdev.entity, num_pads, pads);
if (ret)
return ret;
/* Initialize the subdev and its controls. */
v4l2_subdev_init(sd, &seninf_subdev_ops);
sd->internal_ops = &seninf_subdev_internal_ops;
sd->dev = priv->dev;
sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
sd->entity.ops = &seninf_media_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
V4L2_SUBDEV_FL_STREAMS;
strscpy(sd->name, dev_name(priv->dev), sizeof(sd->name));
v4l2_set_subdevdata(sd, priv);
ret = seninf_initialize_controls(priv);
if (ret) {
dev_err_probe(priv->dev, ret, "Failed to initialize controls\n");
goto err_subdev;
}
ret = v4l2_subdev_init_finalize(sd);
if (ret)
goto err_free_handler;
return 0;
err_free_handler:
v4l2_ctrl_handler_free(&priv->ctrl_handler);
err_subdev:
v4l2_subdev_cleanup(sd);
media_entity_cleanup(&sd->entity);
return ret;
}
I would also add
static void seninf_subdev_cleanup(struct mtk_seninf *priv)
{
struct v4l2_subdev *sd = &priv->subdev;
v4l2_ctrl_handler_free(&priv->ctrl_handler);
v4l2_subdev_cleanup(sd);
media_entity_cleanup(&sd->entity);
}
and use it in error paths below, as well as in .remove().
Then, in this function,
/* Initialize the media_device and v4l2_device. */
media_dev->dev = dev;
strscpy(media_dev->model, conf->model, sizeof(media_dev->model));
media_dev->hw_revision = 0;
media_device_init(media_dev);
priv->v4l2_dev.mdev = &priv->media_dev;
ret = v4l2_device_register(dev, &priv->v4l2_dev);
if (ret) {
dev_err_probe(dev, ret, "Failed to register V4L2 device\n");
goto err_clean_media;
}
/* Initialize and register the SENINF subdev. */
ret = seninf_subdev_init(priv);
...
followed by mtk_seninf_v4l2_async_register() and
media_device_register().
You can drop mtk_seninf_media_init(). I think the result will be
clearer.
> + if (ret)
> + return ret;
> +
> + /* Initialize & register v4l2 device. */
> + priv->v4l2_dev.mdev = &priv->media_dev;
> +
> + ret = v4l2_device_register(dev, &priv->v4l2_dev);
> + if (ret) {
> + dev_err_probe(dev, ret, "Failed to register V4L2 device\n");
> + goto err_clean_media;
> + }
> +
> + /* Initialize & register subdev. */
> + v4l2_subdev_init(sd, &seninf_subdev_ops);
> + sd->internal_ops = &seninf_subdev_internal_ops;
> + sd->dev = dev;
> + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + sd->entity.ops = &seninf_media_ops;
> + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
If "[PATCH v2 0/2] media: i2c: Drop HAS_EVENTS and event handlers" gets
merged first, you can drop V4L2_SUBDEV_FL_HAS_EVENTS.
> + V4L2_SUBDEV_FL_STREAMS;
> + strscpy(sd->name, dev_name(dev), sizeof(sd->name));
> + ret = seninf_initialize_controls(priv);
> + if (ret) {
> + dev_err_probe(dev, ret, "Failed to initialize controls\n");
> + goto err_unreg_v4l2;
> + }
> + v4l2_set_subdevdata(sd, priv);
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret)
> + goto err_free_handler;
> +
> + ret = v4l2_device_register_subdev(&priv->v4l2_dev, sd);
> + if (ret) {
> + dev_err_probe(dev, ret, "Failed to register subdev\n");
> + goto err_cleanup_subdev;
> + }
> +
> + /* Set up async device */
> + ret = mtk_seninf_v4l2_async_register(priv);
> + if (ret) {
> + dev_err_probe(dev, ret,
> + "Failed to register v4l2 async notifier\n");
> + goto err_unreg_subdev;
> + }
> +
> + /* Register media device */
> + ret = media_device_register(&priv->media_dev);
> + if (ret) {
> + dev_err_probe(dev, ret, "Failed to register media device\n");
> + goto err_unreg_notifier;
> + }
> +
> + return 0;
> +
> +err_unreg_notifier:
> + v4l2_async_nf_unregister(&priv->notifier);
> +err_unreg_subdev:
> + v4l2_device_unregister_subdev(sd);
> +err_cleanup_subdev:
> + v4l2_subdev_cleanup(sd);
> +err_free_handler:
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +err_unreg_v4l2:
> + v4l2_device_unregister(&priv->v4l2_dev);
> +err_clean_media:
> + media_entity_cleanup(&sd->entity);
> + media_device_cleanup(&priv->media_dev);
> +
> + return ret;
> +}
> +
> +static int seninf_pm_suspend(struct device *dev)
> +{
> + struct mtk_seninf *priv = dev_get_drvdata(dev);
> +
> + clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> +
> + return 0;
> +}
> +
> +static int seninf_pm_resume(struct device *dev)
> +{
> + struct mtk_seninf *priv = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> + if (ret) {
> + dev_err(dev, "failed to enable clock: %d\n", ret);
s/clock/clocks/
> + return ret;
> + }
> +
> + mtk_seninf_csi2_setup_phy(priv);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops runtime_pm_ops = {
> + SET_RUNTIME_PM_OPS(seninf_pm_suspend, seninf_pm_resume, NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> + pm_runtime_force_resume)
> +};
> +
> +static int seninf_probe(struct platform_device *pdev)
> +{
> + static const char * const clk_names[] = { "camsys", "top_mux" };
> + struct device *dev = &pdev->dev;
> + struct mtk_seninf *priv;
> + unsigned int i;
> + int ret;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->conf = device_get_match_data(dev);
> +
> + dev_set_drvdata(dev, priv);
> + priv->dev = dev;
> +
> + priv->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(priv->base))
> + return PTR_ERR(priv->base);
> +
> + priv->num_clks = ARRAY_SIZE(clk_names);
> + priv->clks = devm_kcalloc(dev, priv->num_clks,
> + sizeof(*priv->clks), GFP_KERNEL);
> + if (!priv->clks)
> + return -ENOMEM;
> +
> + for (i = 0; i < priv->num_clks; ++i)
> + priv->clks[i].id = clk_names[i];
> +
> + ret = devm_clk_bulk_get(dev, priv->num_clks, priv->clks);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get seninf clock\n");
s/clock/clocks/
> +
> + for (i = 0; i < priv->conf->nb_muxes; ++i) {
> + struct mtk_seninf_mux *mux = &priv->muxes[i];
> +
> + mux->pad = priv->conf->nb_inputs + i;
> + mux->mux_id = i;
> + mux->base = priv->base + 0x1000 * i;
> + mux->seninf = priv;
> + }
> +
> + devm_pm_runtime_enable(dev);
> +
> + ret = mtk_seninf_v4l2_register(priv);
> + return ret;
return mtk_seninf_v4l2_register(priv);
> +}
> +
> +static void seninf_remove(struct platform_device *pdev)
> +{
> + struct mtk_seninf *priv = dev_get_drvdata(&pdev->dev);
> +
> + media_device_unregister(&priv->media_dev);
> + media_device_cleanup(&priv->media_dev);
> + v4l2_async_nf_unregister(&priv->notifier);
> + v4l2_async_nf_cleanup(&priv->notifier);
> + v4l2_device_unregister_subdev(&priv->subdev);
> + v4l2_subdev_cleanup(&priv->subdev);
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> + media_entity_cleanup(&priv->subdev.entity);
> + v4l2_device_unregister(&priv->v4l2_dev);
> +}
> +
> +static const struct mtk_seninf_conf seninf_8365_conf = {
> + .model = "mtk-camsys-3.0",
> + .nb_inputs = 4,
> + .nb_muxes = 6,
> + .nb_outputs = 4,
> +};
> +
> +static const struct of_device_id mtk_seninf_of_match[] = {
> + { .compatible = "mediatek,mt8365-seninf", .data = &seninf_8365_conf },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, mtk_seninf_of_match);
> +
> +static struct platform_driver seninf_pdrv = {
> + .driver = {
> + .name = "mtk-seninf",
> + .pm = &runtime_pm_ops,
> + .of_match_table = mtk_seninf_of_match,
> + },
> + .probe = seninf_probe,
> + .remove = seninf_remove,
> +};
> +
> +module_platform_driver(seninf_pdrv);
> +
> +MODULE_DESCRIPTION("MTK sensor interface driver");
> +MODULE_AUTHOR("Louis Kuo <louis.kuo@mediatek.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h b/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..1f13755ab2f0239b0ab7ed200523da5a7b773d1b
> --- /dev/null
> +++ b/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h
> @@ -0,0 +1,114 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2022 MediaTek Inc.
> + */
> +
> +#ifndef __SENINF_REG_H__
> +#define __SENINF_REG_H__
> +
> +#include <linux/bits.h>
> +
> +#define SENINF_TOP_CTRL 0x0000
> +#define SENINF_TOP_CTRL_MUX_LP_MODE BIT(31)
> +#define SENINF_TOP_CTRL_SENINF_PCLK_EN BIT(10)
> +#define SENINF_TOP_CTRL_SENINF2_PCLK_EN BIT(11)
> +#define SENINF_TOP_MUX_CTRL 0x0008
> +#define SENINF_TOP_CAM_MUX_CTRL 0x0010
> +#define SENINF_TOP_CAM_MUX_CTRL_SENINF_CAM2_MUX_SRC_SEL GENMASK(11, 8)
> +#define SENINF_TOP_CAM_MUX_CTRL_SENINF_CAM3_MUX_SRC_SEL GENMASK(15, 12)
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI0 0x001c
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_DPHY_MODE BIT(0)
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_CK_SEL_1 GENMASK(10, 8)
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_CK_SEL_2 GENMASK(13, 12)
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_PHY_SENINF_LANE_MUX_CSI0_EN BIT(31)
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI1 0x0020
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_DPHY_MODE BIT(0)
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_CK_SEL_1 GENMASK(10, 8)
> +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_PHY_SENINF_LANE_MUX_CSI1_EN BIT(31)
> +#define SENINF_CTRL 0x0200
> +#define SENINF_CTRL_SENINF_EN BIT(0)
> +#define SENINF_CTRL_CSI2_SW_RST BIT(7)
> +#define SENINF_CTRL_SENINF_SRC_SEL GENMASK(14, 12)
> +#define SENINF_CTRL_PAD2CAM_DATA_SEL GENMASK(30, 28)
> +#define SENINF_CTRL_EXT 0x0204
> +#define SENINF_CTRL_EXT_SENINF_TESTMDL_IP_EN BIT(1)
> +#define SENINF_CTRL_EXT_SENINF_NCSI2_IP_EN BIT(5)
> +#define SENINF_CTRL_EXT_SENINF_CSI2_IP_EN BIT(6)
> +#define SENINF_TG1_PH_CNT 0x0600
> +#define SENINF_TG1_SEN_CK 0x0604
> +#define SENINF_TG1_SEN_CK_CLKCNT GENMASK(21, 16)
> +#define SENINF_TG1_TM_CTL 0x0608
> +#define SENINF_TG1_TM_CTL_TM_EN BIT(0)
> +#define SENINF_TG1_TM_CTL_TM_FMT BIT(2)
> +#define SENINF_TG1_TM_CTL_TM_PAT GENMASK(7, 4)
> +#define SENINF_TG1_TM_CTL_TM_VSYNC GENMASK(15, 8)
> +#define SENINF_TG1_TM_CTL_TM_DUMMYPXL GENMASK(23, 16)
> +#define SENINF_TG1_TM_SIZE 0x060c
> +#define SENINF_TG1_TM_SIZE_TM_LINE GENMASK(29, 16)
> +#define SENINF_TG1_TM_SIZE_TM_PXL GENMASK(12, 0)
> +#define SENINF_TG1_TM_CLK 0x0610
> +#define TEST_MODEL_CLK_DIVIDED_CNT 8
> +#define SENINF_TG1_TM_STP 0x0614
> +#define TIME_STAMP_DIVIDER 1
> +#define MIPI_RX_CON24_CSI0 0x0824
> +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN0_MUX GENMASK(25, 24)
> +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN1_MUX GENMASK(27, 26)
> +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN2_MUX GENMASK(29, 28)
> +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN3_MUX GENMASK(31, 30)
> +#define SENINF_CSI2_CTL 0x0a00
> +#define SENINF_CSI2_CTL_DATA_LANE0_EN BIT(0)
> +#define SENINF_CSI2_CTL_DATA_LANE1_EN BIT(1)
> +#define SENINF_CSI2_CTL_DATA_LANE2_EN BIT(2)
> +#define SENINF_CSI2_CTL_DATA_LANE3_EN BIT(3)
> +#define SENINF_CSI2_CTL_CLOCK_LANE_EN BIT(4)
> +#define SENINF_CSI2_CTL_HSRX_DET_EN BIT(7)
> +#define SENINF_CSI2_CTL_ED_SEL BIT(16)
> +#define DATA_HEADER_ORDER_DI_WCL_WCH 1
> +#define SENINF_CSI2_CTL_HS_TRAIL_EN BIT(25)
> +#define SENINF_CSI2_CTL_CLOCK_HS_OPTION BIT(27)
> +#define SENINF_CSI2_LNRD_TIMING 0x0a08
> +#define SENINF_CSI2_LNRD_TIMING_DATA_SETTLE_PARAMETER GENMASK(15, 8)
> +#define SENINF_CSI2_DPCM 0x0a0c
> +#define SENINF_CSI2_DPCM_DI_30_DPCM_EN BIT(7)
> +#define SENINF_CSI2_DPCM_DI_2A_DPCM_EN BIT(15)
> +#define SENINF_CSI2_DGB_SEL 0x0a18
> +#define SENINF_CSI2_DGB_SEL_DEBUG_SEL GENMASK(7, 0)
> +#define SENINF_CSI2_DGB_SEL_DEBUG_EN BIT(31)
> +#define SENINF_CSI2_SPARE0 0x0a20
> +#define SENINF_CSI2_LNRC_FSM 0x0a28
> +#define SENINF_CSI2_HS_TRAIL 0x0a40
> +#define SENINF_CSI2_HS_TRAIL_HS_TRAIL_PARAMETER GENMASK(7, 0)
> +#define SENINF_CSI2_RESYNC_MERGE_CTL 0x0a74
> +#define SENINF_CSI2_RESYNC_MERGE_CTL_CPHY_LANE_RESYNC_CNT GENMASK(2, 0)
> +#define SENINF_CSI2_RESYNC_MERGE_CTL_BYPASS_LANE_RESYNC BIT(10)
> +#define SENINF_CSI2_RESYNC_MERGE_CTL_CDPHY_SEL BIT(11)
> +#define SENINF_CSI2_MODE 0x0ae8
> +#define SENINF_CSI2_MODE_CSR_CSI2_MODE GENMASK(7, 0)
> +#define SENINF_CSI2_MODE_CSR_CSI2_HEADER_LEN GENMASK(10, 8)
> +#define SENINF_CSI2_DPHY_SYNC 0x0b20
> +#define SENINF_CSI2_DPHY_SYNC_SYNC_SEQ_MASK_0 GENMASK(15, 0)
> +#define SENINF_CSI2_DPHY_SYNC_SYNC_SEQ_PAT_0 GENMASK(31, 16)
> +#define SENINF_MUX_CTRL 0x0d00
> +#define SENINF_MUX_CTRL_SENINF_MUX_SW_RST BIT(0)
> +#define SENINF_MUX_CTRL_SENINF_IRQ_SW_RST BIT(1)
> +#define SENINF_MUX_CTRL_SENINF_HSYNC_MASK BIT(7)
> +#define SENINF_MUX_CTRL_SENINF_PIX_SEL BIT(8)
> +#define SENINF_MUX_CTRL_SENINF_VSYNC_POL BIT(9)
> +#define SENINF_MUX_CTRL_SENINF_HSYNC_POL BIT(10)
> +#define SENINF_MUX_CTRL_SENINF_SRC_SEL GENMASK(15, 12)
> +#define SENINF_MUX_CTRL_FIFO_PUSH_EN GENMASK(21, 16)
> +#define FIFO_PUSH_EN_NORMAL_MODE 0x1f
> +#define FIFO_PUSH_EN_JPEG_2_PIXEL_MODE 0x1e
> +#define SENINF_MUX_CTRL_FIFO_FLUSH_EN GENMASK(28, 22)
> +#define FIFO_FLUSH_EN_NORMAL_MODE 0x1b
> +#define FIFO_FLUSH_EN_JPEG_2_PIXEL_MODE 0x18
> +#define SENINF_MUX_CTRL_FIFO_FULL_WR_EN GENMASK(29, 28)
> +#define SENINF_MUX_CTRL_SENINF_MUX_EN BIT(31)
> +#define SENINF_MUX_INTEN 0x0d04
> +#define SENINF_MUX_SPARE 0x0d2c
> +#define SENINF_FIFO_FULL_SEL BIT(13)
> +#define SENINF_MUX_CTRL_EXT 0x0d3c
> +#define SENINF_MUX_CTRL_EXT_SENINF_SRC_SEL_EXT GENMASK(1, 0)
> +#define SENINF_MUX_CTRL_EXT_SENINF_PIX_SEL_EXT BIT(4)
> +
> +#endif /* __SENINF_REG_H__ */
>
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-25 14:40 ` Julien Stephan
@ 2024-11-25 17:36 ` Laurent Pinchart
0 siblings, 0 replies; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-25 17:36 UTC (permalink / raw)
To: Julien Stephan
Cc: CK Hu (胡俊光), mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), matthias.bgg@gmail.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno,
linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com
On Mon, Nov 25, 2024 at 03:40:18PM +0100, Julien Stephan wrote:
> Le lun. 25 nov. 2024 à 09:14, CK Hu (胡俊光) <ck.hu@mediatek.com> a écrit :
> > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > >
> > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > >
> > > This driver provides a path to bypass the SoC ISP so that image data
> > > coming from the SENINF can go directly into memory without any image
> > > processing. This allows the use of an external ISP.
> > >
> > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > [Paul Elder fix irq locking]
> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > ---
> >
> > [snip]
> >
> > > +static void mtk_camsv30_update_buffers_add(struct mtk_cam_dev *cam_dev,
> > > + struct mtk_cam_dev_buffer *buf)
> > > +{
> > > + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_BASE_ADDR, buf->daddr);
> > > +}
> > > +
> >
> > [snip]
> >
> > > +static void mtk_camsv30_fbc_inc(struct mtk_cam_dev *cam_dev)
> > > +{
> > > + unsigned int fbc_val;
> > > +
> > > + if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
> >
> > I think this pm_runtime_resume_and_get() is not necessary.
> > mtk_camsv30_fbc_inc() is called only in mtk_cam_vb2_buf_queue().
> > But when buf_list is empty, mtk_camsv30_update_buffers_add() is called before this function.
> > But mtk_camsv30_update_buffers_add() does not call pm_runtime_resume_and_get() and it works normally.
> > So this function is not necessary to call pm_runtime_resume_and_get().
>
> Hi CK,
>
> This one is actually needed because .buf_queue can be called before
> .start_streaming in case a user requests to prepare buffers before
> streaming.
> But you are right, if a user requests to alloc buffer before streaming
> mtk_camsv_update_buffers_address will be called without pm. Streaming
> still works because in start streaming we call
> mtk_camsv_update_buffers_address again.
> So maybe I should put the pm stuff in mtk_cam_vb2_buf_queue ?
mtk_cam_vb2_buf_queue() should only touch the hardware if streaming has
been started, otherwise it shhould just put the buffers in a queue and
return immediately, and the initial hardware setup should be done when
starting streaming. There should be no need to handle runtime PM in this
function or in mtk_cam_vb2_buf_queue().
> > In other register setting function, please also check this pm function is necessary or not.
> >
> > Regards,
> > CK
> >
> > > + dev_err(cam_dev->dev, "failed to get pm_runtime\n");
> > > + return;
> > > + }
> > > +
> > > + fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
> > > + fbc_val |= CAMSV_IMGO_FBC_RCNT_INC;
> > > + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> > > + fbc_val &= ~CAMSV_IMGO_FBC_RCNT_INC;
> > > + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> > > +
> > > + pm_runtime_put_autosuspend(cam_dev->dev);
> > > +}
> > > +
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 3/5] media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface
2024-11-25 17:33 ` Laurent Pinchart
@ 2024-11-25 19:45 ` Laurent Pinchart
2025-01-22 14:04 ` Julien Stephan
1 sibling, 0 replies; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-25 19:45 UTC (permalink / raw)
To: Julien Stephan
Cc: Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, Louis Kuo, Phi-bang Nguyen,
Florian Sylvestre
Another comment.
On Mon, Nov 25, 2024 at 07:33:37PM +0200, Laurent Pinchart wrote:
> Hi Julien,
>
> Thank you for the patch.
>
> On Thu, Nov 21, 2024 at 09:53:17AM +0100, Julien Stephan wrote:
> > From: Louis Kuo <louis.kuo@mediatek.com>
> >
> > This will add the mediatek ISP3.0 seninf (sensor interface) driver found
> > on several Mediatek SoCs such as the mt8365.
> >
> > Then seninf module has 4 physical CSI-2 inputs. Depending on the soc they
> > may not be all connected.
> >
> > Signed-off-by: Louis Kuo <louis.kuo@mediatek.com>
> > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > ---
> > MAINTAINERS | 1 +
> > drivers/media/platform/mediatek/Kconfig | 1 +
> > drivers/media/platform/mediatek/Makefile | 1 +
> > drivers/media/platform/mediatek/isp/Kconfig | 17 +
> > drivers/media/platform/mediatek/isp/Makefile | 4 +
> > drivers/media/platform/mediatek/isp/mtk_seninf.c | 1636 ++++++++++++++++++++
> > .../media/platform/mediatek/isp/mtk_seninf_reg.h | 114 ++
> > 7 files changed, 1774 insertions(+)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 6147629405c8d40b00c4755a4ee27a746b26f782..9654a7f4e21cddb77bb799add56110f5f27f7a79 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -14572,6 +14572,7 @@ M: Andy Hsieh <andy.hsieh@mediatek.com>
> > S: Supported
> > F: Documentation/devicetree/bindings/media/mediatek,mt8365-camsv.yaml
> > F: Documentation/devicetree/bindings/media/mediatek,mt8365-seninf.yaml
> > +F: drivers/media/platform/mediatek/isp/*
> >
> > MEDIATEK SMI DRIVER
> > M: Yong Wu <yong.wu@mediatek.com>
> > diff --git a/drivers/media/platform/mediatek/Kconfig b/drivers/media/platform/mediatek/Kconfig
> > index 84104e2cd02447790ae5c29953a2e82ca4fdd0a7..a405d57013292c515d2a2db4d43aa1ed8cb21f7b 100644
> > --- a/drivers/media/platform/mediatek/Kconfig
> > +++ b/drivers/media/platform/mediatek/Kconfig
> > @@ -2,6 +2,7 @@
> >
> > comment "Mediatek media platform drivers"
> >
> > +source "drivers/media/platform/mediatek/isp/Kconfig"
> > source "drivers/media/platform/mediatek/jpeg/Kconfig"
> > source "drivers/media/platform/mediatek/mdp/Kconfig"
> > source "drivers/media/platform/mediatek/vcodec/Kconfig"
> > diff --git a/drivers/media/platform/mediatek/Makefile b/drivers/media/platform/mediatek/Makefile
> > index 38e6ba917fe5cdd932aa6c88221c9a7aa5a7705a..2341a0e373a4e30f0caf823ab67098fde96fc071 100644
> > --- a/drivers/media/platform/mediatek/Makefile
> > +++ b/drivers/media/platform/mediatek/Makefile
> > @@ -1,4 +1,5 @@
> > # SPDX-License-Identifier: GPL-2.0-only
> > +obj-y += isp/
> > obj-y += jpeg/
> > obj-y += mdp/
> > obj-y += vcodec/
> > diff --git a/drivers/media/platform/mediatek/isp/Kconfig b/drivers/media/platform/mediatek/isp/Kconfig
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..2a3cef81d15aa12633ade2f3be0bba36b9af62e1
> > --- /dev/null
> > +++ b/drivers/media/platform/mediatek/isp/Kconfig
> > @@ -0,0 +1,17 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +config MTK_SENINF30
> > + tristate "MediaTek ISP3.0 SENINF driver"
> > + depends on ARCH_MEDIATEK || COMPILE_TEST
> > + depends on OF
> > + select MEDIA_CONTROLLER
> > + select PHY_MTK_MIPI_CSI_0_5
> > + select V4L2_FWNODE
> > + select VIDEO_V4L2_SUBDEV_API
> > + default n
> > + help
> > + This driver provides a MIPI CSI-2 receiver interface to connect
> > + an external camera module with MediaTek ISP3.0. It is able to handle
> > + multiple cameras at the same time.
> > +
> > + To compile this driver as a module, choose M here: the
> > + module will be called mtk-seninf.
> > diff --git a/drivers/media/platform/mediatek/isp/Makefile b/drivers/media/platform/mediatek/isp/Makefile
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..375d720f9ed75e2197bb723bdce9bc0472e62842
> > --- /dev/null
> > +++ b/drivers/media/platform/mediatek/isp/Makefile
> > @@ -0,0 +1,4 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +mtk-seninf-objs += mtk_seninf.o
> > +obj-$(CONFIG_MTK_SENINF30) += mtk-seninf.o
> > diff --git a/drivers/media/platform/mediatek/isp/mtk_seninf.c b/drivers/media/platform/mediatek/isp/mtk_seninf.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..3b040f96bb63dc90db7d17c46f920d5597d936db
> > --- /dev/null
> > +++ b/drivers/media/platform/mediatek/isp/mtk_seninf.c
> > @@ -0,0 +1,1636 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2022 MediaTek Inc.
> > + */
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/module.h>
> > +#include <linux/phy/phy.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/property.h>
> > +#include <linux/videodev2.h>
> > +#include <media/media-device.h>
> > +#include <media/media-entity.h>
> > +#include <media/v4l2-async.h>
> > +#include <media/v4l2-common.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-event.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +#include "mtk_seninf_reg.h"
> > +
> > +#define SENINF_TIMESTAMP_STEP 0x67
> > +#define SENINF_SETTLE_DELAY 0x15
> > +#define SENINF_HS_TRAIL_PARAMETER 0x8
> > +
> > +#define SENINF_MAX_NUM_INPUTS 4
> > +#define SENINF_MAX_NUM_OUTPUTS 6
> > +#define SENINF_MAX_NUM_MUXES 6
> > +#define SENINF_MAX_NUM_PADS (SENINF_MAX_NUM_INPUTS + \
> > + SENINF_MAX_NUM_OUTPUTS)
> > +
> > +#define SENINF_DEFAULT_BUS_FMT MEDIA_BUS_FMT_SGRBG10_1X10
> > +#define SENINF_DEFAULT_WIDTH 1920
> > +#define SENINF_DEFAULT_HEIGHT 1080
> > +
> > +#define SENINF_PAD_10BIT 0
> > +
> > +#define SENINF_TEST_MODEL 0
> > +#define SENINF_NORMAL_MODEL 1
> > +#define SENINF_ALL_ERR_IRQ_EN 0x7f
> > +#define SENINF_IRQ_CLR_SEL 0x80000000
> > +
> > +#define SENINF_MIPI_SENSOR 0x8
> > +
> > +#define MTK_CSI_MAX_LANES 4
> > +
> > +/* Port number in the device tree. */
> > +enum mtk_seninf_port {
> > + CSI_PORT_0 = 0, /* 4D1C or 2D1C */
> > + CSI_PORT_1, /* 4D1C */
> > + CSI_PORT_2, /* 4D1C */
> > + CSI_PORT_0B, /* 2D1C */
> > +};
> > +
> > +enum mtk_seninf_id {
> > + SENINF_1 = 0,
> > + SENINF_2 = 1,
> > + SENINF_3 = 2,
> > + SENINF_5 = 4,
> > +};
> > +
> > +static const u32 port_to_seninf_id[] = {
> > + [CSI_PORT_0] = SENINF_1,
> > + [CSI_PORT_1] = SENINF_3,
> > + [CSI_PORT_2] = SENINF_5,
> > + [CSI_PORT_0B] = SENINF_2,
> > +};
> > +
> > +enum mtk_seninf_phy_mode {
> > + SENINF_PHY_MODE_NONE,
> > + SENINF_PHY_MODE_4D1C,
> > + SENINF_PHY_MODE_2D1C,
> > +};
> > +
> > +enum mtk_seninf_format_flag {
> > + MTK_SENINF_FORMAT_BAYER = BIT(0),
> > + MTK_SENINF_FORMAT_DPCM = BIT(1),
> > + MTK_SENINF_FORMAT_JPEG = BIT(2),
> > + MTK_SENINF_FORMAT_INPUT_ONLY = BIT(3),
> > +};
> > +
> > +/**
> > + * struct mtk_seninf_conf - Model-specific SENINF parameters
> > + * @model: Model description
> > + * @nb_inputs: Number of SENINF inputs
> > + * @nb_muxes: Number of SENINF MUX (FIFO) instances
> > + * @nb_outputs: Number of outputs (to CAM and CAMSV instances)
> > + */
> > +struct mtk_seninf_conf {
> > + const char *model;
> > + u8 nb_inputs;
> > + u8 nb_muxes;
> > + u8 nb_outputs;
> > +};
> > +
> > +/**
> > + * struct mtk_seninf_format_info - Information about media bus formats
> > + * @code: V4L2 media bus code
> > + * @flags: Flags describing the format, as a combination of MTK_SENINF_FORMAT_*
> > + * @bpp: Bits per pixel
> > + */
> > +struct mtk_seninf_format_info {
> > + u32 code;
> > + u32 flags;
> > + u8 bpp;
> > +};
> > +
> > +/**
> > + * struct mtk_seninf_input - SENINF input block
> > + * @pad: DT port and media entity pad number
> > + * @seninf_id: SENINF hardware instance ID
> > + * @base: Memory mapped I/O based address
> > + * @seninf: Back pointer to the mtk_seninf
> > + * @phy: PHY connected to the input
> > + * @phy_mode: PHY operation mode (NONE when the input is not connected)
> > + * @bus: CSI-2 bus configuration from DT
> > + * @source_sd: Source subdev connected to the input
> > + */
> > +struct mtk_seninf_input {
> > + enum mtk_seninf_port pad;
> > + enum mtk_seninf_id seninf_id;
> > + void __iomem *base;
> > + struct mtk_seninf *seninf;
> > +
> > + struct phy *phy;
> > + enum mtk_seninf_phy_mode phy_mode;
> > +
> > + struct v4l2_mbus_config_mipi_csi2 bus;
> > +
> > + struct v4l2_subdev *source_sd;
> > +};
> > +
> > +/**
> > + * struct mtk_seninf_mux - SENINF MUX channel
> > + * @pad: DT port and media entity pad number
> > + * @mux_id: MUX hardware instance ID
> > + * @base: Memory mapped I/O based address
> > + * @seninf: Back pointer to the mtk_seninf
> > + */
> > +struct mtk_seninf_mux {
> > + unsigned int pad;
> > + unsigned int mux_id;
> > + void __iomem *base;
> > + struct mtk_seninf *seninf;
> > +};
> > +
> > +/**
> > + * struct mtk_seninf - Top-level SENINF device
> > + * @dev: The (platform) device
> > + * @phy: PHYs at the SENINF inputs
> > + * @num_clks: Number of clocks in the clks array
> > + * @clks: Clocks
> > + * @base: Memory mapped I/O base address
> > + * @media_dev: Media controller device
> > + * @v4l2_dev: V4L2 device
> > + * @subdev: V4L2 subdevice
> > + * @pads: Media entity pads
> > + * @notifier: V4L2 async notifier for source subdevs
> > + * @ctrl_handler: V4L2 controls handler
> > + * @source_format: Active format on the source pad
> > + * @inputs: Array of SENINF inputs
> > + * @muxes: Array of MUXes
> > + * @conf: Model-specific SENINF parameters
> > + * @is_testmode: Whether or not the test pattern generator is enabled
> > + */
> > +struct mtk_seninf {
> > + struct device *dev;
> > + struct phy *phy[5];
> > + unsigned int num_clks;
> > + struct clk_bulk_data *clks;
> > + void __iomem *base;
> > +
> > + struct media_device media_dev;
> > + struct v4l2_device v4l2_dev;
> > + struct v4l2_subdev subdev;
> > + struct media_pad pads[SENINF_MAX_NUM_PADS];
> > + struct v4l2_async_notifier notifier;
> > + struct v4l2_ctrl_handler ctrl_handler;
> > +
> > + struct mtk_seninf_input inputs[SENINF_MAX_NUM_INPUTS];
> > + struct mtk_seninf_mux muxes[SENINF_MAX_NUM_MUXES];
> > +
> > + const struct mtk_seninf_conf *conf;
> > +
> > + bool is_testmode;
> > +};
> > +
> > +inline struct mtk_seninf *sd_to_mtk_seninf(struct v4l2_subdev *sd)
> > +{
> > + return container_of(sd, struct mtk_seninf, subdev);
> > +}
> > +
> > +static inline bool mtk_seninf_pad_is_sink(struct mtk_seninf *priv,
> > + unsigned int pad)
> > +{
> > + return pad < priv->conf->nb_inputs;
> > +}
> > +
> > +static inline bool mtk_seninf_pad_is_source(struct mtk_seninf *priv,
> > + unsigned int pad)
> > +{
> > + return !mtk_seninf_pad_is_sink(priv, pad);
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Formats
> > + */
> > +
> > +static const struct mtk_seninf_format_info mtk_seninf_formats[] = {
> > + {
> > + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 8,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 8,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 8,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 8,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 10,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 10,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 10,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 10,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 12,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 12,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 12,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 12,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 14,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 14,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 14,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 14,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SBGGR16_1X16,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 16,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGBRG16_1X16,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 16,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGRBG16_1X16,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 16,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SRGGB16_1X16,
> > + .flags = MTK_SENINF_FORMAT_BAYER,
> > + .bpp = 16,
> > + }, {
> > + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> > + .bpp = 16,
> > + }, {
> > + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> > + .bpp = 16,
> > + }, {
> > + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> > + .bpp = 16,
> > + }, {
> > + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> > + .bpp = 16,
> > + }, {
> > + .code = MEDIA_BUS_FMT_JPEG_1X8,
> > + .flags = MTK_SENINF_FORMAT_JPEG,
> > + .bpp = 8,
> > + }, {
> > + .code = MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8,
> > + .flags = MTK_SENINF_FORMAT_JPEG,
> > + .bpp = 8,
> > + },
> > + /* Keep the input-only formats last. */
> > + {
> > + .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
> > + .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
> > + .bpp = 8,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8,
> > + .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
> > + .bpp = 8,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8,
> > + .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
> > + .bpp = 8,
> > + }, {
> > + .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8,
> > + .flags = MTK_SENINF_FORMAT_DPCM | MTK_SENINF_FORMAT_INPUT_ONLY,
> > + .bpp = 8,
> > + }
> > +};
> > +
> > +static const struct mtk_seninf_format_info *mtk_seninf_format_info(u32 code)
> > +{
> > + unsigned int i;
> > +
> > + for (i = 0; i < ARRAY_SIZE(mtk_seninf_formats); ++i) {
> > + if (mtk_seninf_formats[i].code == code)
> > + return &mtk_seninf_formats[i];
> > + }
> > +
> > + return NULL;
> > +}
> > +
> > +static u32 mtk_seninf_read(struct mtk_seninf *priv, u32 reg)
> > +{
> > + return readl(priv->base + reg);
> > +}
> > +
> > +static void mtk_seninf_write(struct mtk_seninf *priv, u32 reg, u32 value)
> > +{
> > + writel(value, priv->base + reg);
> > +}
> > +
> > +static void __mtk_seninf_update(struct mtk_seninf *priv, u32 reg,
> > + u32 mask, u32 value)
> > +{
> > + u32 val = mtk_seninf_read(priv, reg);
> > +
> > + writel((val & ~mask) | (value & mask), priv->base + reg);
> > +}
> > +
> > +#define mtk_seninf_update(priv, reg, field, val) \
> > + __mtk_seninf_update(priv, reg, reg##_##field, \
> > + FIELD_PREP(reg##_##field, val))
> > +
> > +static u32 mtk_seninf_inuput_read(struct mtk_seninf_input *input, u32 reg)
> > +{
> > + return readl(input->base + reg);
> > +}
> > +
> > +static void mtk_seninf_input_write(struct mtk_seninf_input *input, u32 reg,
> > + u32 value)
> > +{
> > + writel(value, input->base + reg);
> > +}
> > +
> > +static void __mtk_seninf_input_update(struct mtk_seninf_input *input, u32 reg,
> > + u32 mask, u32 value)
> > +{
> > + u32 val = mtk_seninf_inuput_read(input, reg);
> > +
> > + mtk_seninf_input_write(input, reg, (val & ~mask) | (value & mask));
> > +}
> > +
> > +#define mtk_seninf_input_update(input, reg, field, val) \
> > + __mtk_seninf_input_update(input, reg, reg##_##field, \
> > + FIELD_PREP(reg##_##field, val))
> > +
> > +static u32 mtk_seninf_mux_read(struct mtk_seninf_mux *mux, u32 reg)
> > +{
> > + return readl(mux->base + reg);
> > +}
> > +
> > +static void mtk_seninf_mux_write(struct mtk_seninf_mux *mux, u32 reg,
> > + u32 value)
> > +{
> > + writel(value, mux->base + reg);
> > +}
> > +
> > +static void __mtk_seninf_mux_update(struct mtk_seninf_mux *mux, u32 reg,
> > + u32 mask, u32 value)
> > +{
> > + u32 val = mtk_seninf_mux_read(mux, reg);
> > +
> > + mtk_seninf_mux_write(mux, reg, (val & ~mask) | (value & mask));
> > +}
> > +
> > +#define mtk_seninf_mux_update(mux, reg, field, val) \
> > + __mtk_seninf_mux_update(mux, reg, reg##_##field, \
> > + FIELD_PREP(reg##_##field, val))
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Hardware Configuration
> > + *
> > + * The SENINF is the camera sensor interface. On the input side it contains
> > + * input channels (also named SENINF), each made of a CSI-2 receiver, an
> > + * interface for parallel sensors, and a test pattern generator. The inputs are
> > + * routed through a N:M crossbar switch (TOP MUX) to VC/DT filters with a FIFO
> > + * (MUX). The MUX are routed to another N:M crossbar switch (CAM MUX), whose
> > + * output is then connected to other IP cores.
> > + *
> > + * +-------------------------------------------------------+
> > + * | SENINF |
> > + * | |
> > + * +-------+ | +----------+ TOP MUX |
> > + * | | | | SENINF | |\ CAM MUX |
> > + * | D-PHY | ---> | CSI-2 RX | ---> | | +------------+ |\ |
> > + * | | | | TPG | -> | | ---> | MUX (FIFO) | ---> | | ---> CAMSV
> > + * +-------+ | +----------+ -> | | +------------+ -> | | |
> > + * | |/ -> | | |
> > + * | |/ |
> > + * | |
> > + * ... | ... ... --->
> > + * | |
> > + * | |
> > + * +-------------------------------------------------------+
> > + *
> > + * The number of PHYs, SENINF and MUX differ between SoCs. MT8167 has a single
> > + * MUX and thus no output CAM MUX crossbar switch.
> > + */
> > +
> > +static void mtk_seninf_csi2_setup_phy(struct mtk_seninf *priv)
> > +{
> > + /* CSI0 */
> > + if (priv->inputs[CSI_PORT_0].phy) {
> > + const struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_0];
> > +
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> > + DPHY_MODE, 0 /* 4D1C*/);
>
> s/4D1C/4D1C /
>
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> > + CK_SEL_1, input->bus.clock_lane);
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> > + CK_SEL_2, 2);
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> > + PHY_SENINF_LANE_MUX_CSI0_EN, 1);
>
> In the review of v6, I wrote that you're reading and writing the same
> register 4 times to set 4 different fields. This could be replaced by a
> single register access. I seem to recall that this was needed, and that
> writing the whole register in one go didn't produce the desired
> behaviour, at least for some registers. Is that right ?
>
> It would be nice to improve this where possible, here and everywhere
> else in the driver. I won't make it a blocker, but I really dislike this
> pattern :-(
>
> > + }
> > +
> > + /* CSI1 */
> > + if (priv->inputs[CSI_PORT_1].phy) {
> > + const struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_1];
> > +
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> > + DPHY_MODE, 0 /* 4D1C */);
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> > + CK_SEL_1, input->bus.clock_lane);
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> > + PHY_SENINF_LANE_MUX_CSI1_EN, 1);
> > + }
> > +}
> > +
> > +static void mtk_seninf_input_setup_csi2_rx(struct mtk_seninf_input *input)
> > +{
> > + unsigned int lanes[MTK_CSI_MAX_LANES] = { };
> > + unsigned int i;
> > +
> > + /*
> > + * Configure data lane muxing. In 2D1C mode, lanes 0 to 2 correspond to
> > + * CSIx[AB]_L{0,1,2}, and in 4D1C lanes 0 to 5 correspond to
> > + * CSIxA_L{0,1,2}, CSIxB_L{0,1,2}.
> > + *
> > + * The clock lane must be skipped when calculating the index of the
> > + * physical data lane. For instance, in 4D1C mode, the sensor clock
> > + * lane is typically connected to lane 2 (CSIxA_L2), and the sensor
> > + * data lanes 0-3 to lanes 1 (CSIxA_L1), 3 (CSIxB_L0), 0 (CSIxA_L0) and
> > + * 4 (CSIxB_L1). The when skipping the clock lane, the data lane
> > + * indices become 1, 2, 0 and 3.
> > + */
> > + for (i = 0; i < input->bus.num_data_lanes; ++i) {
> > + lanes[i] = input->bus.data_lanes[i];
> > + if (lanes[i] > input->bus.clock_lane)
> > + lanes[i]--;
> > + }
> > +
> > + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> > + CSI0_BIST_LN0_MUX, lanes[0]);
> > + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> > + CSI0_BIST_LN1_MUX, lanes[1]);
> > + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> > + CSI0_BIST_LN2_MUX, lanes[2]);
> > + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> > + CSI0_BIST_LN3_MUX, lanes[3]);
> > +}
> > +
> > +static s64 mtk_seninf_get_clk_divider(struct mtk_seninf *priv,
> > + int pad_num,
>
> This holds on a single line.
>
> > + u8 bpp, unsigned int num_data_lanes)
> > +{
> > + struct media_entity *entity = &priv->subdev.entity;
> > + struct media_pad *pad;
> > + struct v4l2_subdev *sd;
> > + s64 link_frequency, pixel_clock;
> > +
> > +
> > + if (!(entity->pads[pad_num].flags & MEDIA_PAD_FL_SINK))
> > + return -ENODEV;
> > +
> > + pad = media_pad_remote_pad_first(&entity->pads[pad_num]);
> > + if (!pad)
> > + return -ENOENT;
> > +
> > + if (!is_media_entity_v4l2_subdev(pad->entity))
> > + return -ENOENT;
>
> As those conditions that can happen, wouldn't pipeline validation have
> failed ? If those conditions can't happen, then
> mtk_seninf_input_setup_csi2() and mtk_seninf_start() can become void
> functions.
>
> > +
> > + sd = media_entity_to_v4l2_subdev(pad->entity);
> > + link_frequency = v4l2_get_link_freq(sd->ctrl_handler, bpp,
> > + num_data_lanes * 2);
> > + pixel_clock = div_u64(link_frequency * 2 * num_data_lanes, bpp);
> > + /*
> > + * According to datasheet: Sensor master clock = ISP_clock/(CLKCNT +1)
> > + * we also have the following constraint:
> > + * pixel_clock >= Sensor master clock
> > + */
> > + return div_u64(clk_get_rate(priv->clks[0].clk), pixel_clock) - 1;
> > +}
> > +
> > +static int mtk_seninf_input_setup_csi2(struct mtk_seninf *priv,
This function takes the priv pointer for the sole purpose of passing it
to mtk_seninf_get_clk_divider(), which then uses it only to get the
clock rate. I would drop the priv parameter here and in
mtk_seninf_get_clk_divider() and use input->seninf instead, to avoid
giving the impression that the functions perform any operation that is
not specific to the input.
> > + struct mtk_seninf_input *input,
> > + struct v4l2_subdev_state *state)
> > +{
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + const struct v4l2_mbus_framefmt *format;
> > + unsigned int num_data_lanes = input->bus.num_data_lanes;
> > + unsigned int val = 0;
> > + s64 clock_count;
> > +
> > + format = v4l2_subdev_state_get_format(state, input->pad, 0);
> > + fmtinfo = mtk_seninf_format_info(format->code);
> > +
> > + /* Configure timestamp */
> > + mtk_seninf_input_write(input, SENINF_TG1_TM_STP, SENINF_TIMESTAMP_STEP);
> > +
> > + /* HQ */
> > + /*
> > + * Configure phase counter. Zero means:
> > + * - Sensor master clock: ISP_CLK
> > + * - Sensor clock polarity: Rising edge
> > + * - Sensor reset deasserted
> > + * - Sensor powered up
> > + * - Pixel clock inversion disabled
> > + * - Sensor master clock polarity disabled
> > + * - Phase counter disabled
> > + */
> > + mtk_seninf_input_write(input, SENINF_TG1_PH_CNT, 0x0);
> > +
> > + clock_count = mtk_seninf_get_clk_divider(priv, input->pad,
> > + fmtinfo->bpp,
> > + num_data_lanes);
> > + if (clock_count < 0)
> > + return clock_count;
> > +
> > + clock_count = FIELD_PREP(SENINF_TG1_SEN_CK_CLKCNT, clock_count) | 0x1;
> > + mtk_seninf_input_write(input, SENINF_TG1_SEN_CK, clock_count);
> > +
> > + /* First Enable Sensor interface and select pad (0x1a04_0200) */
> > + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CTRL, PAD2CAM_DATA_SEL,
> > + SENINF_PAD_10BIT);
> > + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_SRC_SEL, 0);
> > + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_CSI2_IP_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_NCSI2_IP_EN, 0);
> > +
> > + /* DPCM Enable */
> > + if (fmtinfo->flags & MTK_SENINF_FORMAT_DPCM)
> > + val = SENINF_CSI2_DPCM_DI_2A_DPCM_EN;
> > + else
> > + val = SENINF_CSI2_DPCM_DI_30_DPCM_EN;
> > + mtk_seninf_input_write(input, SENINF_CSI2_DPCM, val);
> > +
> > + /* Settle delay */
> > + mtk_seninf_input_update(input, SENINF_CSI2_LNRD_TIMING,
> > + DATA_SETTLE_PARAMETER, SENINF_SETTLE_DELAY);
> > +
> > + /* CSI2 control */
> > + val = mtk_seninf_inuput_read(input, SENINF_CSI2_CTL)
> > + | (FIELD_PREP(SENINF_CSI2_CTL_ED_SEL,
> > + DATA_HEADER_ORDER_DI_WCL_WCH)
> > + | SENINF_CSI2_CTL_CLOCK_LANE_EN | (BIT(num_data_lanes) - 1));
> > + mtk_seninf_input_write(input, SENINF_CSI2_CTL, val);
> > +
> > + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> > + BYPASS_LANE_RESYNC, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> > + CDPHY_SEL, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> > + CPHY_LANE_RESYNC_CNT, 3);
> > + mtk_seninf_input_update(input, SENINF_CSI2_MODE, CSR_CSI2_MODE, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_MODE, CSR_CSI2_HEADER_LEN,
> > + 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_DPHY_SYNC, SYNC_SEQ_MASK_0,
> > + 0xff00);
> > + mtk_seninf_input_update(input, SENINF_CSI2_DPHY_SYNC, SYNC_SEQ_PAT_0,
> > + 0x001d);
> > +
> > + mtk_seninf_input_update(input, SENINF_CSI2_CTL, CLOCK_HS_OPTION, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_CTL, HSRX_DET_EN, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_CTL, HS_TRAIL_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CSI2_HS_TRAIL, HS_TRAIL_PARAMETER,
> > + SENINF_HS_TRAIL_PARAMETER);
> > +
> > + /* Set debug port to output packet number */
> > + mtk_seninf_input_update(input, SENINF_CSI2_DGB_SEL, DEBUG_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CSI2_DGB_SEL, DEBUG_SEL, 0x1a);
> > +
> > + /* HQ */
> > + mtk_seninf_input_write(input, SENINF_CSI2_SPARE0, 0xfffffffe);
> > +
> > + /* Reset the CSI2 to commit changes */
> > + mtk_seninf_input_update(input, SENINF_CTRL, CSI2_SW_RST, 1);
> > + udelay(1);
> > + mtk_seninf_input_update(input, SENINF_CTRL, CSI2_SW_RST, 0);
> > +
> > + return 0;
> > +}
> > +
> > +static void mtk_seninf_mux_setup(struct mtk_seninf_mux *mux,
> > + struct mtk_seninf_input *input,
> > + struct v4l2_subdev_state *state)
> > +{
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + const struct v4l2_mbus_framefmt *format;
> > + unsigned int pix_sel_ext;
> > + unsigned int pix_sel;
> > + unsigned int hs_pol = 0;
> > + unsigned int vs_pol = 0;
> > + unsigned int val;
> > + u32 rst_mask;
> > +
> > + format = v4l2_subdev_state_get_format(state, input->pad, 0);
> > + fmtinfo = mtk_seninf_format_info(format->code);
> > +
> > + /* Enable mux */
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_MUX_EN, 1);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_SRC_SEL,
> > + SENINF_MIPI_SENSOR);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_SRC_SEL_EXT,
> > + SENINF_NORMAL_MODEL);
> > +
> > + pix_sel_ext = 0;
> > + pix_sel = 1;
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_PIX_SEL_EXT,
> > + pix_sel_ext);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_PIX_SEL, pix_sel);
> > +
> > + if (fmtinfo->flags & MTK_SENINF_FORMAT_JPEG) {
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 0);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN,
> > + FIFO_FLUSH_EN_JPEG_2_PIXEL_MODE);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN,
> > + FIFO_PUSH_EN_JPEG_2_PIXEL_MODE);
> > + } else {
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 2);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN,
> > + FIFO_FLUSH_EN_NORMAL_MODE);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN,
> > + FIFO_PUSH_EN_NORMAL_MODE);
> > + }
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_POL, hs_pol);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_VSYNC_POL, vs_pol);
> > +
> > + val = mtk_seninf_mux_read(mux, SENINF_MUX_CTRL);
> > + rst_mask = SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> > + SENINF_MUX_CTRL_SENINF_MUX_SW_RST;
> > +
> > + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL, val | rst_mask);
> > + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL, val & ~rst_mask);
> > +
> > + /* HQ */
> > + val = SENINF_FIFO_FULL_SEL;
> > +
> > + /* SPARE field meaning is unknown */
> > + val |= 0xc0000;
> > + mtk_seninf_mux_write(mux, SENINF_MUX_SPARE, val);
> > +}
> > +
> > +static void mtk_seninf_top_mux_setup(struct mtk_seninf *priv,
> > + enum mtk_seninf_id seninf_id,
> > + struct mtk_seninf_mux *mux)
>
> mux can be const. Please constify pointer arguments when they don't have
> to be modified (both from a language point of view, and conceptually,
> for instance priv shouldn't be const as you're writing registers in this
> function, which modifies the state of the device)..
>
> > +{
> > + unsigned int val;
> > +
> > + /*
> > + * Use the top mux (from SENINF input to MUX) to configure routing, and
> > + * hardcode a 1:1 mapping from the MUX instances to the SENINF outputs.
> > + */
> > + val = mtk_seninf_read(priv, SENINF_TOP_MUX_CTRL)
> > + & ~(0xf << (mux->mux_id * 4));
> > + val |= (seninf_id & 0xf) << (mux->mux_id * 4);
> > + mtk_seninf_write(priv, SENINF_TOP_MUX_CTRL, val);
> > +
> > + /*
> > + * We currently support only seninf version 3.0
> > + * where camsv0 and camsv1 are hardwired respectively to
> > + * SENINF_CAM2 and SENINF_CAM3 i.e :
> > + * - SENINF_TOP_CAM_MUX_CTRL[11:8] = 0
> > + * - SENINF_TOP_CAM_MUX_CTRL[15:12] = 1
> > + * so we hardcode it here
> > + */
> > + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> > + SENINF_CAM2_MUX_SRC_SEL, 0);
> > + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> > + SENINF_CAM3_MUX_SRC_SEL, 1);
> > +}
> > +
> > +static void seninf_enable_test_pattern(struct mtk_seninf *priv,
> > + struct v4l2_subdev_state *state)
> > +{
> > + struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_0];
> > + struct mtk_seninf_mux *mux = &priv->muxes[0];
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + const struct v4l2_mbus_framefmt *format;
> > + unsigned int val;
> > + unsigned int pix_sel_ext;
> > + unsigned int pix_sel;
> > + unsigned int hs_pol = 0;
> > + unsigned int vs_pol = 0;
> > + unsigned int seninf = 0;
> > + unsigned int tm_size = 0;
> > + unsigned int mux_id = mux->mux_id;
> > +
> > + format = v4l2_subdev_state_get_format(state, priv->conf->nb_inputs, 0);
> > + fmtinfo = mtk_seninf_format_info(format->code);
> > +
> > + mtk_seninf_update(priv, SENINF_TOP_CTRL, MUX_LP_MODE, 0);
> > +
> > + mtk_seninf_update(priv, SENINF_TOP_CTRL, SENINF_PCLK_EN, 1);
> > + mtk_seninf_update(priv, SENINF_TOP_CTRL, SENINF2_PCLK_EN, 1);
> > +
> > + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_SRC_SEL, 1);
> > + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_TESTMDL_IP_EN,
> > + 1);
> > +
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_PAT, 0xc);
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_VSYNC, 4);
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_DUMMYPXL, 0x28);
> > +
> > + if (fmtinfo->flags & MTK_SENINF_FORMAT_BAYER)
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_FMT, 0x0);
> > + else
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_FMT, 0x1);
> > +
> > + tm_size = FIELD_PREP(SENINF_TG1_TM_SIZE_TM_LINE, format->height + 8);
> > + switch (format->code) {
> > + case MEDIA_BUS_FMT_UYVY8_1X16:
> > + case MEDIA_BUS_FMT_VYUY8_1X16:
> > + case MEDIA_BUS_FMT_YUYV8_1X16:
> > + case MEDIA_BUS_FMT_YVYU8_1X16:
> > + tm_size |= FIELD_PREP(SENINF_TG1_TM_SIZE_TM_PXL,
> > + format->width * 2);
> > + break;
> > + default:
> > + tm_size |= FIELD_PREP(SENINF_TG1_TM_SIZE_TM_PXL, format->width);
> > + break;
> > + }
> > + mtk_seninf_input_write(input, SENINF_TG1_TM_SIZE, tm_size);
> > +
> > + mtk_seninf_input_write(input, SENINF_TG1_TM_CLK,
> > + TEST_MODEL_CLK_DIVIDED_CNT);
> > + mtk_seninf_input_write(input, SENINF_TG1_TM_STP, TIME_STAMP_DIVIDER);
> > +
> > + /* Set top mux */
> > + val = (mtk_seninf_read(priv, SENINF_TOP_MUX_CTRL)
> > + & (~(0xf << (mux_id * 4)))) |
> > + ((seninf & 0xf) << (mux_id * 4));
> > + mtk_seninf_write(priv, SENINF_TOP_MUX_CTRL, val);
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_MUX_EN, 1);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_SRC_SEL_EXT,
> > + SENINF_TEST_MODEL);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_SRC_SEL, 1);
> > +
> > + pix_sel_ext = 0;
> > + pix_sel = 1;
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT,
> > + SENINF_PIX_SEL_EXT, pix_sel_ext);
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_PIX_SEL, pix_sel);
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN, 0x1f);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN, 0x1b);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 2);
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_POL, hs_pol);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_VSYNC_POL, vs_pol);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_MASK, 1);
> > +
> > + mtk_seninf_mux_write(mux, SENINF_MUX_INTEN,
> > + SENINF_IRQ_CLR_SEL | SENINF_ALL_ERR_IRQ_EN);
> > +
> > + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL,
> > + mtk_seninf_mux_read(mux, SENINF_MUX_CTRL) |
> > + SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> > + SENINF_MUX_CTRL_SENINF_MUX_SW_RST);
> > + udelay(1);
> > + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL,
> > + mtk_seninf_mux_read(mux, SENINF_MUX_CTRL) &
> > + ~(SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> > + SENINF_MUX_CTRL_SENINF_MUX_SW_RST));
> > +
> > + //check this
> > + mtk_seninf_write(priv, SENINF_TOP_CAM_MUX_CTRL, 0x76540010);
> > + /*
> > + * We currently support only seninf version 3.0
> > + * where camsv0 and camsv1 are hardwired respectively to
> > + * test pattern is valid only for seninf_1 (id 0) i.e :
>
> I'm having trouble parsing this sentence.
>
> > + * - SENINF_TOP_CAM_MUX_CTRL[11:8] = 0
> > + * - SENINF_TOP_CAM_MUX_CTRL[15:12] = 0
> > + * so we hardcode it here
> > + */
> > + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> > + SENINF_CAM2_MUX_SRC_SEL, 0);
> > + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> > + SENINF_CAM3_MUX_SRC_SEL, 0);
>
> Here you're reconfiguring the whole TOP_CAM_MUX. Unless I'm mistaken, if
> you want to capture from one sensor and use the TPG for the other camsv,
> the configuration performed here and in mtk_seninf_top_mux_setup() will
> clash. Isn't that a problem ?
>
> > +}
> > +
> > +static int mtk_seninf_start(struct mtk_seninf *priv,
> > + struct v4l2_subdev_state *state,
> > + struct mtk_seninf_input *input,
> > + struct mtk_seninf_mux *mux)
> > +{
> > + int ret;
> > +
> > + phy_power_on(input->phy);
> > +
> > + mtk_seninf_input_setup_csi2_rx(input);
> > + ret = mtk_seninf_input_setup_csi2(priv, input, state);
> > + if (ret)
> > + return ret;
> > +
> > + mtk_seninf_mux_setup(mux, input, state);
> > + mtk_seninf_top_mux_setup(priv, input->seninf_id, mux);
> > + return 0;
> > +}
> > +
> > +static void mtk_seninf_stop(struct mtk_seninf *priv,
> > + struct mtk_seninf_input *input)
> > +{
> > + unsigned int val;
> > +
> > + /* Disable CSI2(2.5G) first */
> > + val = mtk_seninf_inuput_read(input, SENINF_CSI2_CTL);
> > + val &= ~(SENINF_CSI2_CTL_CLOCK_LANE_EN |
> > + SENINF_CSI2_CTL_DATA_LANE3_EN |
> > + SENINF_CSI2_CTL_DATA_LANE2_EN |
> > + SENINF_CSI2_CTL_DATA_LANE1_EN |
> > + SENINF_CSI2_CTL_DATA_LANE0_EN);
> > + mtk_seninf_input_write(input, SENINF_CSI2_CTL, val);
> > +
> > + if (!priv->is_testmode)
> > + phy_power_off(input->phy);
>
> What happens if userspace alls STREAMON with the TPG disabled, then
> enables the TPG, and calls STREAMOFF ? It looks like you should disable
> changing the TPG control while streaming. You can use
> v4l2_subdev_is_streaming() to check if the subdev is streaming, but
> you'll need to hold the active state lock.
>
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * V4L2 Controls
> > + */
> > +
> > +static int seninf_set_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > + struct mtk_seninf *priv = container_of(ctrl->handler,
> > + struct mtk_seninf, ctrl_handler);
> > +
> > + switch (ctrl->id) {
> > + case V4L2_CID_TEST_PATTERN:
> > + priv->is_testmode = !!ctrl->val;
> > + break;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops seninf_ctrl_ops = {
> > + .s_ctrl = seninf_set_ctrl,
> > +};
> > +
> > +static const char *const seninf_test_pattern_menu[] = {
> > + "No test pattern",
>
> Drivers normally use "Disabled" for this.
>
> > + "Static horizontal color bars",
>
> "Static Horizontal Color Bars",
>
> Not that it would matter too much, but menu entries usually use Camel
> Case, so let's be consistent.
>
> > +};
> > +
> > +static int seninf_initialize_controls(struct mtk_seninf *priv)
> > +{
> > + struct v4l2_ctrl_handler *handler;
> > + int ret;
> > +
> > + handler = &priv->ctrl_handler;
> > + ret = v4l2_ctrl_handler_init(handler, 2);
>
> The driver creates a single control.
>
> > + if (ret)
> > + return ret;
> > +
> > + v4l2_ctrl_new_std_menu_items(handler, &seninf_ctrl_ops,
> > + V4L2_CID_TEST_PATTERN,
> > + ARRAY_SIZE(seninf_test_pattern_menu) - 1,
> > + 0, 0, seninf_test_pattern_menu);
> > +
> > + priv->is_testmode = false;
> > +
> > + if (handler->error) {
> > + ret = handler->error;
> > + dev_err(priv->dev,
> > + "Failed to init controls(%d)\n", ret);
> > + v4l2_ctrl_handler_free(handler);
> > + return ret;
> > + }
> > +
> > + priv->subdev.ctrl_handler = handler;
> > +
> > + return 0;
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * V4L2 Subdev Operations
> > + */
>
> Missing blank line.
>
> > +static int seninf_enable_streams(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state, u32 pad,
> > + u64 streams_mask)
> > +{
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > + struct mtk_seninf_input *input;
> > + struct mtk_seninf_mux *mux;
> > + struct v4l2_subdev *source;
> > + u32 sink_pad;
> > + int ret;
> > +
> > + /* Stream control can only operate on source pads. */
> > + if (pad < priv->conf->nb_inputs ||
> > + pad >= priv->conf->nb_inputs + priv->conf->nb_outputs)
> > + return -EINVAL;
> > +
> > + /*
> > + * Locate the SENINF input and MUX for the source pad.
> > + */
>
> /* Locate the SENINF input and MUX for the source pad. */
>
> Same below.
>
> > +
> > + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad,
> > + 0, &sink_pad, NULL);
> > + if (ret) {
> > + dev_dbg(priv->dev, "No sink pad routed to source pad %u\n",
> > + pad);
> > + return ret;
> > + }
> > +
> > + input = &priv->inputs[sink_pad];
> > + mux = &priv->muxes[pad - priv->conf->nb_inputs];
> > +
> > + ret = pm_runtime_get_sync(priv->dev);
>
> Use pm_runtime_resume_and_get() and drop pm_runtime_put_noidle() in the
> error path.
>
> > + if (ret < 0) {
> > + dev_err(priv->dev, "Failed to pm_runtime_get_sync: %d\n", ret);
> > + pm_runtime_put_noidle(priv->dev);
> > + return ret;
> > + }
> > +
> > + /* If test mode is enabled, just enable the test pattern generator. */
> > + if (priv->is_testmode) {
> > + seninf_enable_test_pattern(priv, state);
> > + return 0;
> > + }
> > +
> > + /* Start the SENINF first and then the source. */
> > + ret = mtk_seninf_start(priv, state, input, mux);
> > + if (ret) {
> > + dev_err(priv->dev, "failed to start seninf: %d\n", ret);
>
> Missing
>
> pm_runtime_put(priv->dev);
>
> I would move error handling to the bottom of the function, with gotos
> and error labels.
>
> > + return ret;
> > + }
> > +
> > + source = input->source_sd;
> > + ret = v4l2_subdev_call(source, video, s_stream, 1);
>
> Use v4l2_subdev_enable_streams().
>
> > + if (ret) {
> > + dev_err(priv->dev, "failed to start source %s: %d\n",
> > + source->entity.name, ret);
> > + mtk_seninf_stop(priv, input);
> > + pm_runtime_put(priv->dev);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int seninf_disable_streams(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state, u32 pad,
> > + u64 streams_mask)
> > +{
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > + struct mtk_seninf_input *input;
> > + struct mtk_seninf_mux *mux;
> > + struct v4l2_subdev *source;
> > + u32 sink_pad;
> > + int ret;
> > +
> > + /* Stream control can only operate on source pads. */
> > + if (pad < priv->conf->nb_inputs ||
> > + pad >= priv->conf->nb_inputs + priv->conf->nb_outputs)
> > + return -EINVAL;
> > +
> > + /*
> > + * Locate the SENINF input and MUX for the source pad.
> > + *
> > + */
> > +
> > + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad,
> > + 0, &sink_pad, NULL);
> > + if (ret) {
> > + dev_dbg(priv->dev, "No sink pad routed to source pad %u\n",
> > + pad);
> > + return ret;
> > + }
> > +
> > + input = &priv->inputs[sink_pad];
> > + mux = &priv->muxes[pad - priv->conf->nb_inputs];
> > +
> > + if (!priv->is_testmode) {
> > + source = input->source_sd;
> > + ret = v4l2_subdev_call(source, video, s_stream, 0);
>
> Use v4l2_subdev_disable_streams().
>
> > + if (ret)
> > + dev_err(priv->dev,
> > + "failed to stop source %s: %d\n",
> > + source->entity.name, ret);
> > + }
> > +
> > + mtk_seninf_stop(priv, input);
> > + pm_runtime_put(priv->dev);
> > + return ret;
> > +}
> > +
> > +static const struct v4l2_mbus_framefmt mtk_seninf_default_fmt = {
> > + .code = SENINF_DEFAULT_BUS_FMT,
> > + .width = SENINF_DEFAULT_WIDTH,
> > + .height = SENINF_DEFAULT_HEIGHT,
> > + .field = V4L2_FIELD_NONE,
> > + .colorspace = V4L2_COLORSPACE_SRGB,
> > + .xfer_func = V4L2_XFER_FUNC_DEFAULT,
> > + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
> > + .quantization = V4L2_QUANTIZATION_DEFAULT,
> > +};
> > +
> > +static int __seninf_set_routing(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_krouting *routing)
> > +{
> > + int ret;
> > +
> > + 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,
> > + &mtk_seninf_default_fmt);
> > +}
> > +
> > +static int seninf_init_state(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state)
> > +{
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > + struct v4l2_subdev_route routes[SENINF_MAX_NUM_OUTPUTS] = { };
> > + struct v4l2_subdev_krouting routing = {
> > + .routes = routes,
> > + .num_routes = priv->conf->nb_outputs,
> > + };
> > + unsigned int i;
> > +
> > + /*
> > + * Initialize one route for supported source pads.
> > + * It is a single route from the first sink pad to the source pad,
> > + * while on SENINF 5.0 the routing table will map sink pads to source
> > + * pads connected to CAMSV 1:1 (skipping the first two source pads
> > + * connected to the CAM instances).
> > + */
> > + for (i = 0; i < routing.num_routes; i++) {
> > + struct v4l2_subdev_route *route = &routes[i];
> > +
> > + route->sink_pad = i;
> > + route->sink_stream = 0;
> > + route->source_pad = priv->conf->nb_inputs + i;
> > + route->source_stream = 0;
> > + route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > + }
> > +
> > + return __seninf_set_routing(sd, state, &routing);
> > +}
> > +
> > +static int seninf_enum_mbus_code(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > +
> > + if (code->index >= ARRAY_SIZE(mtk_seninf_formats))
> > + return -EINVAL;
> > +
> > + fmtinfo = &mtk_seninf_formats[code->index];
> > + if (fmtinfo->flags & MTK_SENINF_FORMAT_INPUT_ONLY &&
> > + mtk_seninf_pad_is_source(priv, code->pad))
> > + return -EINVAL;
> > +
> > + code->code = fmtinfo->code;
> > +
> > + return 0;
> > +}
> > +
> > +static int seninf_set_fmt(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_format *fmt)
> > +{
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + struct v4l2_mbus_framefmt *format;
> > +
> > + /*
> > + * TODO (?): We should disallow setting formats on the source pad
> > + * completely, as the SENINF can't perform any processing. This would
> > + * however break usage of the test pattern generator, as there would be
> > + * no way to configure formats at all when no active input is selected.
> > + */
>
> As commented in v6, I think this needs to be solved.
>
> > +
> > + /*
> > + * Default to the first format if the requested media bus code isn't
> > + * supported.
> > + */
> > + fmtinfo = mtk_seninf_format_info(fmt->format.code);
> > + if (!fmtinfo) {
> > + fmtinfo = &mtk_seninf_formats[0];
> > + fmt->format.code = fmtinfo->code;
> > + }
> > +
> > + /* Interlaced formats are not supported yet. */
> > + fmt->format.field = V4L2_FIELD_NONE;
> > +
> > + /* Store the format. */
> > + format = v4l2_subdev_state_get_format(state, fmt->pad, fmt->stream);
> > + if (!format)
> > + return -EINVAL;
> > +
> > + *format = fmt->format;
> > +
> > + if (mtk_seninf_pad_is_source(priv, fmt->pad))
> > + return 0;
> > +
> > + /* Propagate the format to the corresponding source pad. */
> > + format = v4l2_subdev_state_get_opposite_stream_format(state, fmt->pad,
> > + fmt->stream);
> > + if (!format)
> > + return -EINVAL;
> > +
> > + *format = fmt->format;
>
> Another comment from v6 that seems to have been lost:
>
> If fmtinfo is one of the INPUT_ONLY formats, the corresponding
> DPCM-uncompressed format must be set on the source pad. To facilitate
> this, you want need to add a .uncompressed field to the format info
> structure to store the corresponding uncompressed format.
>
> > +
> > + return 0;
> > +}
> > +
> > +static int seninf_set_routing(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + enum v4l2_subdev_format_whence which,
> > + struct v4l2_subdev_krouting *routing)
> > +{
> > + return __seninf_set_routing(sd, state, routing);
> > +}
> > +
> > +static const struct v4l2_subdev_core_ops seninf_subdev_core_ops = {
> > + .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> > + .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops seninf_subdev_pad_ops = {
> > + .enum_mbus_code = seninf_enum_mbus_code,
> > + .get_fmt = v4l2_subdev_get_fmt,
> > + .set_fmt = seninf_set_fmt,
> > + .link_validate = v4l2_subdev_link_validate_default,
> > + .set_routing = seninf_set_routing,
> > + .enable_streams = seninf_enable_streams,
> > + .disable_streams = seninf_disable_streams,
> > +};
> > +
> > +static const struct v4l2_subdev_ops seninf_subdev_ops = {
> > + .core = &seninf_subdev_core_ops,
> > + .pad = &seninf_subdev_pad_ops,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops seninf_subdev_internal_ops = {
> > + .init_state = seninf_init_state,
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Media Entity Operations
> > + */
> > +
> > +static const struct media_entity_operations seninf_media_ops = {
> > + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> > + .link_validate = v4l2_subdev_link_validate,
> > + .has_pad_interdep = v4l2_subdev_has_pad_interdep,
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Async Subdev Notifier
> > + */
> > +
> > +struct mtk_seninf_async_subdev {
> > + struct v4l2_async_connection asc;
> > + struct mtk_seninf_input *input;
> > + unsigned int port;
> > +};
> > +
> > +static int mtk_seninf_fwnode_parse(struct device *dev,
> > + unsigned int id)
>
> This holds on a single line.
>
> > +
>
>
> Extra blank line.
>
> I would move this function below mtk_seninf_async_ops as it's called
> directly in the probe path, before the bound and complete callbacks.
>
> > +{
> > + static const char * const phy_names[] = {
> > + "csi0", "csi1", "csi2", "csi0b" };
>
> static const char * const phy_names[] = {
> "csi0", "csi1", "csi2", "csi0b"
> };
>
> > + struct mtk_seninf *priv = dev_get_drvdata(dev);
> > + struct fwnode_handle *ep, *fwnode;
> > + struct mtk_seninf_input *input;
> > + struct mtk_seninf_async_subdev *asd;
> > + struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> > + unsigned int port;
> > + int ret;
> > +
> > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), id, 0, 0);
> > + if (!ep)
> > + return 0;
> > +
> > + fwnode = fwnode_graph_get_remote_endpoint(ep);
> > + ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> > + if (ret) {
> > + dev_err(dev, "Failed to parse %p fw\n", to_of_node(fwnode));
>
> dev_err(dev, "Failed to parse %pfw\n", fwnode);
>
> > + ret = -EINVAL;
> > + goto out;
> > + }
> > +
> > + asd = v4l2_async_nf_add_fwnode(&priv->notifier, fwnode,
> > + struct mtk_seninf_async_subdev);
> > + if (IS_ERR(asd)) {
> > + ret = PTR_ERR(asd);
> > + goto out;
> > + }
> > +
> > + port = vep.base.port;
> > + asd->port = port;
> > +
> > + if (mtk_seninf_pad_is_source(priv, port)) {
> > + ret = 0;
> > + goto out;
> > + }
> > +
> > + input = &priv->inputs[port];
> > +
> > + input->pad = port;
> > + input->seninf_id = port_to_seninf_id[port];
> > + input->base = priv->base + 0x1000 * input->seninf_id;
> > + input->seninf = priv;
> > +
> > + input->bus = vep.bus.mipi_csi2;
> > +
> > + input->phy = devm_phy_get(dev, phy_names[port]);
> > + if (IS_ERR(input->phy)) {
> > + dev_err(dev, "failed to get phy: %ld\n", PTR_ERR(input->phy));
> > + ret = PTR_ERR(input->phy);
> > + goto out;
> > + }
> > + input->phy_mode = SENINF_PHY_MODE_4D1C;
> > +
> > + asd->input = input;
> > +
> > + ret = 0;
> > +out:
> > + fwnode_handle_put(ep);
> > + fwnode_handle_put(fwnode);
> > + return ret;
> > +}
> > +
> > +static int mtk_seninf_notifier_bound(struct v4l2_async_notifier *notifier,
> > + struct v4l2_subdev *sd,
> > + struct v4l2_async_connection *asc)
> > +{
> > + struct mtk_seninf *priv = container_of(notifier, struct mtk_seninf,
> > + notifier);
> > + struct mtk_seninf_async_subdev *asd =
> > + container_of(asc, struct mtk_seninf_async_subdev, asc);
> > + struct device_link *link;
> > + int ret;
> > +
> > + dev_dbg(priv->dev, "%s bound to SENINF port %u\n", sd->entity.name,
> > + asd->port);
> > +
> > + if (mtk_seninf_pad_is_sink(priv, asd->port)) {
> > + struct mtk_seninf_input *input = asd->input;
> > +
> > + input->source_sd = sd;
> > +
> > + link = device_link_add(priv->dev, sd->dev,
> > + DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
> > + if (!link) {
> > + dev_err(priv->dev,
> > + "Failed to create device link from source %s\n",
> > + sd->name);
> > + return -EINVAL;
> > + }
> > +
> > + ret = v4l2_create_fwnode_links_to_pad(sd,
> > + &priv->pads[input->pad],
> > + MEDIA_LNK_FL_IMMUTABLE |
> > + MEDIA_LNK_FL_ENABLED);
> > + } else {
> > + link = device_link_add(sd->dev, priv->dev,
> > + DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
> > + if (!link) {
> > + dev_err(priv->dev,
> > + "Failed to create device link to output %s\n",
> > + sd->name);
> > + return -EINVAL;
> > + }
> > +
> > + ret = v4l2_create_fwnode_links_to_pad(&priv->subdev,
> > + &sd->entity.pads[0],
> > + MEDIA_LNK_FL_IMMUTABLE |
> > + MEDIA_LNK_FL_ENABLED);
> > + }
>
> Add a blank line here.
>
> > + if (ret) {
> > + dev_err(priv->dev, "Failed to create links between SENINF port %u and %s (%d)\n",
> > + asd->port, sd->entity.name, ret);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int mtk_seninf_notifier_complete(struct v4l2_async_notifier *notifier)
> > +{
> > + struct mtk_seninf *priv = container_of(notifier, struct mtk_seninf,
> > + notifier);
> > + int ret;
> > +
> > + ret = v4l2_device_register_subdev_nodes(&priv->v4l2_dev);
> > + if (ret) {
> > + dev_err(priv->dev, "Failed to register subdev nodes: %d\n",
> > + ret);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const struct v4l2_async_notifier_operations mtk_seninf_async_ops = {
> > + .bound = mtk_seninf_notifier_bound,
> > + .complete = mtk_seninf_notifier_complete,
> > +};
> > +
> > +static int mtk_seninf_media_init(struct mtk_seninf *priv)
> > +{
> > + struct media_device *media_dev = &priv->media_dev;
> > + const struct mtk_seninf_conf *conf = priv->conf;
> > + unsigned int num_pads = conf->nb_outputs + conf->nb_inputs;
> > + struct media_pad *pads = priv->pads;
> > + struct device *dev = priv->dev;
> > + unsigned int i;
> > + int ret;
> > +
> > + media_dev->dev = dev;
> > + strscpy(media_dev->model, conf->model, sizeof(media_dev->model));
> > + media_dev->hw_revision = 0;
> > + media_device_init(media_dev);
> > +
> > + for (i = 0; i < conf->nb_inputs; i++)
> > + pads[i].flags = MEDIA_PAD_FL_SINK;
> > + for (i = conf->nb_inputs; i < num_pads; i++)
> > + pads[i].flags = MEDIA_PAD_FL_SOURCE;
> > +
> > + ret = media_entity_pads_init(&priv->subdev.entity, num_pads, pads);
> > + if (ret) {
> > + media_device_cleanup(media_dev);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int mtk_seninf_v4l2_async_register(struct mtk_seninf *priv)
> > +{
> > + const struct mtk_seninf_conf *conf = priv->conf;
> > + struct device *dev = priv->dev;
> > + unsigned int i;
> > + int ret;
> > +
> > + v4l2_async_nf_init(&priv->notifier, &priv->v4l2_dev);
> > +
> > + for (i = 0; i < conf->nb_inputs + conf->nb_outputs; ++i) {
> > + ret = mtk_seninf_fwnode_parse(dev, i);
> > +
> > + if (ret) {
> > + dev_err(dev,
> > + "Failed to parse endpoint at port %d: %d\n",
>
> i is unsigned, so use %u
>
> > + i, ret);
> > + goto err_clean_notififer;
> > + }
> > + }
> > +
> > + priv->notifier.ops = &mtk_seninf_async_ops;
> > + ret = v4l2_async_nf_register(&priv->notifier);
> > + if (ret) {
> > + dev_err(dev, "Failed to register async notifier: %d\n", ret);
> > + goto err_clean_notififer;
> > + }
> > + return 0;
> > +
> > +err_clean_notififer:
> > + v4l2_async_nf_cleanup(&priv->notifier);
> > +
> > + return ret;
> > +}
> > +
>
> /* -----------------------------------------------------------------------------
> * Probe & Remove
> */
>
> as we leave the "Async Subdev Notifier" section.
>
> > +static int mtk_seninf_v4l2_register(struct mtk_seninf *priv)
> > +{
> > + struct v4l2_subdev *sd = &priv->subdev;
> > + struct device *dev = priv->dev;
> > + int ret;
> > +
> > + /* Initialize media device & pads. */
> > + ret = mtk_seninf_media_init(priv);
>
> This function intializes both the media device and the entity within the
> seninf subdev, while the rest of the subdev is initialized below. I
> think that makes the code flow more difficult to understand.
>
> The following function could go above, just after seninf_media_ops:
>
> static int seninf_subdev_init(struct mtk_seninf *priv)
> {
> const unsigned int num_pads = priv->conf->nb_outputs
> + priv->conf->nb_inputs;
> struct v4l2_subdev *sd = &priv->subdev;
> struct media_pad *pads = priv->pads;
> unsigned int i;
> int ret;
>
> /* Initialize the entity. */
> for (i = 0; i < priv->conf->nb_inputs; i++)
> pads[i].flags = MEDIA_PAD_FL_SINK;
> for ( ; i < num_pads; i++)
> pads[i].flags = MEDIA_PAD_FL_SOURCE;
>
> ret = media_entity_pads_init(&priv->subdev.entity, num_pads, pads);
> if (ret)
> return ret;
>
> /* Initialize the subdev and its controls. */
> v4l2_subdev_init(sd, &seninf_subdev_ops);
> sd->internal_ops = &seninf_subdev_internal_ops;
> sd->dev = priv->dev;
> sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> sd->entity.ops = &seninf_media_ops;
> sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
> V4L2_SUBDEV_FL_STREAMS;
> strscpy(sd->name, dev_name(priv->dev), sizeof(sd->name));
> v4l2_set_subdevdata(sd, priv);
>
> ret = seninf_initialize_controls(priv);
> if (ret) {
> dev_err_probe(priv->dev, ret, "Failed to initialize controls\n");
> goto err_subdev;
> }
>
> ret = v4l2_subdev_init_finalize(sd);
> if (ret)
> goto err_free_handler;
>
> return 0;
>
> err_free_handler:
> v4l2_ctrl_handler_free(&priv->ctrl_handler);
> err_subdev:
> v4l2_subdev_cleanup(sd);
> media_entity_cleanup(&sd->entity);
> return ret;
> }
>
> I would also add
>
> static void seninf_subdev_cleanup(struct mtk_seninf *priv)
> {
> struct v4l2_subdev *sd = &priv->subdev;
>
> v4l2_ctrl_handler_free(&priv->ctrl_handler);
> v4l2_subdev_cleanup(sd);
> media_entity_cleanup(&sd->entity);
> }
>
> and use it in error paths below, as well as in .remove().
>
> Then, in this function,
>
> /* Initialize the media_device and v4l2_device. */
> media_dev->dev = dev;
> strscpy(media_dev->model, conf->model, sizeof(media_dev->model));
> media_dev->hw_revision = 0;
> media_device_init(media_dev);
>
> priv->v4l2_dev.mdev = &priv->media_dev;
>
> ret = v4l2_device_register(dev, &priv->v4l2_dev);
> if (ret) {
> dev_err_probe(dev, ret, "Failed to register V4L2 device\n");
> goto err_clean_media;
> }
>
> /* Initialize and register the SENINF subdev. */
> ret = seninf_subdev_init(priv);
> ...
>
> followed by mtk_seninf_v4l2_async_register() and
> media_device_register().
>
> You can drop mtk_seninf_media_init(). I think the result will be
> clearer.
>
> > + if (ret)
> > + return ret;
> > +
> > + /* Initialize & register v4l2 device. */
> > + priv->v4l2_dev.mdev = &priv->media_dev;
> > +
> > + ret = v4l2_device_register(dev, &priv->v4l2_dev);
> > + if (ret) {
> > + dev_err_probe(dev, ret, "Failed to register V4L2 device\n");
> > + goto err_clean_media;
> > + }
> > +
> > + /* Initialize & register subdev. */
> > + v4l2_subdev_init(sd, &seninf_subdev_ops);
> > + sd->internal_ops = &seninf_subdev_internal_ops;
> > + sd->dev = dev;
> > + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> > + sd->entity.ops = &seninf_media_ops;
> > + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
>
> If "[PATCH v2 0/2] media: i2c: Drop HAS_EVENTS and event handlers" gets
> merged first, you can drop V4L2_SUBDEV_FL_HAS_EVENTS.
>
> > + V4L2_SUBDEV_FL_STREAMS;
> > + strscpy(sd->name, dev_name(dev), sizeof(sd->name));
> > + ret = seninf_initialize_controls(priv);
> > + if (ret) {
> > + dev_err_probe(dev, ret, "Failed to initialize controls\n");
> > + goto err_unreg_v4l2;
> > + }
> > + v4l2_set_subdevdata(sd, priv);
> > +
> > + ret = v4l2_subdev_init_finalize(sd);
> > + if (ret)
> > + goto err_free_handler;
> > +
> > + ret = v4l2_device_register_subdev(&priv->v4l2_dev, sd);
> > + if (ret) {
> > + dev_err_probe(dev, ret, "Failed to register subdev\n");
> > + goto err_cleanup_subdev;
> > + }
> > +
> > + /* Set up async device */
> > + ret = mtk_seninf_v4l2_async_register(priv);
> > + if (ret) {
> > + dev_err_probe(dev, ret,
> > + "Failed to register v4l2 async notifier\n");
> > + goto err_unreg_subdev;
> > + }
> > +
> > + /* Register media device */
> > + ret = media_device_register(&priv->media_dev);
> > + if (ret) {
> > + dev_err_probe(dev, ret, "Failed to register media device\n");
> > + goto err_unreg_notifier;
> > + }
> > +
> > + return 0;
> > +
> > +err_unreg_notifier:
> > + v4l2_async_nf_unregister(&priv->notifier);
> > +err_unreg_subdev:
> > + v4l2_device_unregister_subdev(sd);
> > +err_cleanup_subdev:
> > + v4l2_subdev_cleanup(sd);
> > +err_free_handler:
> > + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> > +err_unreg_v4l2:
> > + v4l2_device_unregister(&priv->v4l2_dev);
> > +err_clean_media:
> > + media_entity_cleanup(&sd->entity);
> > + media_device_cleanup(&priv->media_dev);
> > +
> > + return ret;
> > +}
> > +
> > +static int seninf_pm_suspend(struct device *dev)
> > +{
> > + struct mtk_seninf *priv = dev_get_drvdata(dev);
> > +
> > + clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> > +
> > + return 0;
> > +}
> > +
> > +static int seninf_pm_resume(struct device *dev)
> > +{
> > + struct mtk_seninf *priv = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> > + if (ret) {
> > + dev_err(dev, "failed to enable clock: %d\n", ret);
>
> s/clock/clocks/
>
> > + return ret;
> > + }
> > +
> > + mtk_seninf_csi2_setup_phy(priv);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dev_pm_ops runtime_pm_ops = {
> > + SET_RUNTIME_PM_OPS(seninf_pm_suspend, seninf_pm_resume, NULL)
> > + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > + pm_runtime_force_resume)
> > +};
> > +
> > +static int seninf_probe(struct platform_device *pdev)
> > +{
> > + static const char * const clk_names[] = { "camsys", "top_mux" };
> > + struct device *dev = &pdev->dev;
> > + struct mtk_seninf *priv;
> > + unsigned int i;
> > + int ret;
> > +
> > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->conf = device_get_match_data(dev);
> > +
> > + dev_set_drvdata(dev, priv);
> > + priv->dev = dev;
> > +
> > + priv->base = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(priv->base))
> > + return PTR_ERR(priv->base);
> > +
> > + priv->num_clks = ARRAY_SIZE(clk_names);
> > + priv->clks = devm_kcalloc(dev, priv->num_clks,
> > + sizeof(*priv->clks), GFP_KERNEL);
> > + if (!priv->clks)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < priv->num_clks; ++i)
> > + priv->clks[i].id = clk_names[i];
> > +
> > + ret = devm_clk_bulk_get(dev, priv->num_clks, priv->clks);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to get seninf clock\n");
>
> s/clock/clocks/
>
> > +
> > + for (i = 0; i < priv->conf->nb_muxes; ++i) {
> > + struct mtk_seninf_mux *mux = &priv->muxes[i];
> > +
> > + mux->pad = priv->conf->nb_inputs + i;
> > + mux->mux_id = i;
> > + mux->base = priv->base + 0x1000 * i;
> > + mux->seninf = priv;
> > + }
> > +
> > + devm_pm_runtime_enable(dev);
> > +
> > + ret = mtk_seninf_v4l2_register(priv);
> > + return ret;
>
> return mtk_seninf_v4l2_register(priv);
>
> > +}
> > +
> > +static void seninf_remove(struct platform_device *pdev)
> > +{
> > + struct mtk_seninf *priv = dev_get_drvdata(&pdev->dev);
> > +
> > + media_device_unregister(&priv->media_dev);
> > + media_device_cleanup(&priv->media_dev);
> > + v4l2_async_nf_unregister(&priv->notifier);
> > + v4l2_async_nf_cleanup(&priv->notifier);
> > + v4l2_device_unregister_subdev(&priv->subdev);
> > + v4l2_subdev_cleanup(&priv->subdev);
> > + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> > + media_entity_cleanup(&priv->subdev.entity);
> > + v4l2_device_unregister(&priv->v4l2_dev);
> > +}
> > +
> > +static const struct mtk_seninf_conf seninf_8365_conf = {
> > + .model = "mtk-camsys-3.0",
> > + .nb_inputs = 4,
> > + .nb_muxes = 6,
> > + .nb_outputs = 4,
> > +};
> > +
> > +static const struct of_device_id mtk_seninf_of_match[] = {
> > + { .compatible = "mediatek,mt8365-seninf", .data = &seninf_8365_conf },
> > + { /* sentinel */ },
> > +};
> > +MODULE_DEVICE_TABLE(of, mtk_seninf_of_match);
> > +
> > +static struct platform_driver seninf_pdrv = {
> > + .driver = {
> > + .name = "mtk-seninf",
> > + .pm = &runtime_pm_ops,
> > + .of_match_table = mtk_seninf_of_match,
> > + },
> > + .probe = seninf_probe,
> > + .remove = seninf_remove,
> > +};
> > +
> > +module_platform_driver(seninf_pdrv);
> > +
> > +MODULE_DESCRIPTION("MTK sensor interface driver");
> > +MODULE_AUTHOR("Louis Kuo <louis.kuo@mediatek.com>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h b/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..1f13755ab2f0239b0ab7ed200523da5a7b773d1b
> > --- /dev/null
> > +++ b/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h
> > @@ -0,0 +1,114 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2022 MediaTek Inc.
> > + */
> > +
> > +#ifndef __SENINF_REG_H__
> > +#define __SENINF_REG_H__
> > +
> > +#include <linux/bits.h>
> > +
> > +#define SENINF_TOP_CTRL 0x0000
> > +#define SENINF_TOP_CTRL_MUX_LP_MODE BIT(31)
> > +#define SENINF_TOP_CTRL_SENINF_PCLK_EN BIT(10)
> > +#define SENINF_TOP_CTRL_SENINF2_PCLK_EN BIT(11)
> > +#define SENINF_TOP_MUX_CTRL 0x0008
> > +#define SENINF_TOP_CAM_MUX_CTRL 0x0010
> > +#define SENINF_TOP_CAM_MUX_CTRL_SENINF_CAM2_MUX_SRC_SEL GENMASK(11, 8)
> > +#define SENINF_TOP_CAM_MUX_CTRL_SENINF_CAM3_MUX_SRC_SEL GENMASK(15, 12)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0 0x001c
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_DPHY_MODE BIT(0)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_CK_SEL_1 GENMASK(10, 8)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_CK_SEL_2 GENMASK(13, 12)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_PHY_SENINF_LANE_MUX_CSI0_EN BIT(31)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI1 0x0020
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_DPHY_MODE BIT(0)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_CK_SEL_1 GENMASK(10, 8)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_PHY_SENINF_LANE_MUX_CSI1_EN BIT(31)
> > +#define SENINF_CTRL 0x0200
> > +#define SENINF_CTRL_SENINF_EN BIT(0)
> > +#define SENINF_CTRL_CSI2_SW_RST BIT(7)
> > +#define SENINF_CTRL_SENINF_SRC_SEL GENMASK(14, 12)
> > +#define SENINF_CTRL_PAD2CAM_DATA_SEL GENMASK(30, 28)
> > +#define SENINF_CTRL_EXT 0x0204
> > +#define SENINF_CTRL_EXT_SENINF_TESTMDL_IP_EN BIT(1)
> > +#define SENINF_CTRL_EXT_SENINF_NCSI2_IP_EN BIT(5)
> > +#define SENINF_CTRL_EXT_SENINF_CSI2_IP_EN BIT(6)
> > +#define SENINF_TG1_PH_CNT 0x0600
> > +#define SENINF_TG1_SEN_CK 0x0604
> > +#define SENINF_TG1_SEN_CK_CLKCNT GENMASK(21, 16)
> > +#define SENINF_TG1_TM_CTL 0x0608
> > +#define SENINF_TG1_TM_CTL_TM_EN BIT(0)
> > +#define SENINF_TG1_TM_CTL_TM_FMT BIT(2)
> > +#define SENINF_TG1_TM_CTL_TM_PAT GENMASK(7, 4)
> > +#define SENINF_TG1_TM_CTL_TM_VSYNC GENMASK(15, 8)
> > +#define SENINF_TG1_TM_CTL_TM_DUMMYPXL GENMASK(23, 16)
> > +#define SENINF_TG1_TM_SIZE 0x060c
> > +#define SENINF_TG1_TM_SIZE_TM_LINE GENMASK(29, 16)
> > +#define SENINF_TG1_TM_SIZE_TM_PXL GENMASK(12, 0)
> > +#define SENINF_TG1_TM_CLK 0x0610
> > +#define TEST_MODEL_CLK_DIVIDED_CNT 8
> > +#define SENINF_TG1_TM_STP 0x0614
> > +#define TIME_STAMP_DIVIDER 1
> > +#define MIPI_RX_CON24_CSI0 0x0824
> > +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN0_MUX GENMASK(25, 24)
> > +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN1_MUX GENMASK(27, 26)
> > +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN2_MUX GENMASK(29, 28)
> > +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN3_MUX GENMASK(31, 30)
> > +#define SENINF_CSI2_CTL 0x0a00
> > +#define SENINF_CSI2_CTL_DATA_LANE0_EN BIT(0)
> > +#define SENINF_CSI2_CTL_DATA_LANE1_EN BIT(1)
> > +#define SENINF_CSI2_CTL_DATA_LANE2_EN BIT(2)
> > +#define SENINF_CSI2_CTL_DATA_LANE3_EN BIT(3)
> > +#define SENINF_CSI2_CTL_CLOCK_LANE_EN BIT(4)
> > +#define SENINF_CSI2_CTL_HSRX_DET_EN BIT(7)
> > +#define SENINF_CSI2_CTL_ED_SEL BIT(16)
> > +#define DATA_HEADER_ORDER_DI_WCL_WCH 1
> > +#define SENINF_CSI2_CTL_HS_TRAIL_EN BIT(25)
> > +#define SENINF_CSI2_CTL_CLOCK_HS_OPTION BIT(27)
> > +#define SENINF_CSI2_LNRD_TIMING 0x0a08
> > +#define SENINF_CSI2_LNRD_TIMING_DATA_SETTLE_PARAMETER GENMASK(15, 8)
> > +#define SENINF_CSI2_DPCM 0x0a0c
> > +#define SENINF_CSI2_DPCM_DI_30_DPCM_EN BIT(7)
> > +#define SENINF_CSI2_DPCM_DI_2A_DPCM_EN BIT(15)
> > +#define SENINF_CSI2_DGB_SEL 0x0a18
> > +#define SENINF_CSI2_DGB_SEL_DEBUG_SEL GENMASK(7, 0)
> > +#define SENINF_CSI2_DGB_SEL_DEBUG_EN BIT(31)
> > +#define SENINF_CSI2_SPARE0 0x0a20
> > +#define SENINF_CSI2_LNRC_FSM 0x0a28
> > +#define SENINF_CSI2_HS_TRAIL 0x0a40
> > +#define SENINF_CSI2_HS_TRAIL_HS_TRAIL_PARAMETER GENMASK(7, 0)
> > +#define SENINF_CSI2_RESYNC_MERGE_CTL 0x0a74
> > +#define SENINF_CSI2_RESYNC_MERGE_CTL_CPHY_LANE_RESYNC_CNT GENMASK(2, 0)
> > +#define SENINF_CSI2_RESYNC_MERGE_CTL_BYPASS_LANE_RESYNC BIT(10)
> > +#define SENINF_CSI2_RESYNC_MERGE_CTL_CDPHY_SEL BIT(11)
> > +#define SENINF_CSI2_MODE 0x0ae8
> > +#define SENINF_CSI2_MODE_CSR_CSI2_MODE GENMASK(7, 0)
> > +#define SENINF_CSI2_MODE_CSR_CSI2_HEADER_LEN GENMASK(10, 8)
> > +#define SENINF_CSI2_DPHY_SYNC 0x0b20
> > +#define SENINF_CSI2_DPHY_SYNC_SYNC_SEQ_MASK_0 GENMASK(15, 0)
> > +#define SENINF_CSI2_DPHY_SYNC_SYNC_SEQ_PAT_0 GENMASK(31, 16)
> > +#define SENINF_MUX_CTRL 0x0d00
> > +#define SENINF_MUX_CTRL_SENINF_MUX_SW_RST BIT(0)
> > +#define SENINF_MUX_CTRL_SENINF_IRQ_SW_RST BIT(1)
> > +#define SENINF_MUX_CTRL_SENINF_HSYNC_MASK BIT(7)
> > +#define SENINF_MUX_CTRL_SENINF_PIX_SEL BIT(8)
> > +#define SENINF_MUX_CTRL_SENINF_VSYNC_POL BIT(9)
> > +#define SENINF_MUX_CTRL_SENINF_HSYNC_POL BIT(10)
> > +#define SENINF_MUX_CTRL_SENINF_SRC_SEL GENMASK(15, 12)
> > +#define SENINF_MUX_CTRL_FIFO_PUSH_EN GENMASK(21, 16)
> > +#define FIFO_PUSH_EN_NORMAL_MODE 0x1f
> > +#define FIFO_PUSH_EN_JPEG_2_PIXEL_MODE 0x1e
> > +#define SENINF_MUX_CTRL_FIFO_FLUSH_EN GENMASK(28, 22)
> > +#define FIFO_FLUSH_EN_NORMAL_MODE 0x1b
> > +#define FIFO_FLUSH_EN_JPEG_2_PIXEL_MODE 0x18
> > +#define SENINF_MUX_CTRL_FIFO_FULL_WR_EN GENMASK(29, 28)
> > +#define SENINF_MUX_CTRL_SENINF_MUX_EN BIT(31)
> > +#define SENINF_MUX_INTEN 0x0d04
> > +#define SENINF_MUX_SPARE 0x0d2c
> > +#define SENINF_FIFO_FULL_SEL BIT(13)
> > +#define SENINF_MUX_CTRL_EXT 0x0d3c
> > +#define SENINF_MUX_CTRL_EXT_SENINF_SRC_SEL_EXT GENMASK(1, 0)
> > +#define SENINF_MUX_CTRL_EXT_SENINF_PIX_SEL_EXT BIT(4)
> > +
> > +#endif /* __SENINF_REG_H__ */
> >
>
> --
> Regards,
>
> Laurent Pinchart
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
` (7 preceding siblings ...)
2024-11-25 9:30 ` CK Hu (胡俊光)
@ 2024-11-25 20:33 ` Laurent Pinchart
2024-11-26 2:07 ` CK Hu (胡俊光)
2024-11-26 3:40 ` CK Hu (胡俊光)
10 siblings, 0 replies; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-25 20:33 UTC (permalink / raw)
To: Julien Stephan
Cc: Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, Florian Sylvestre, Paul Elder
Hi Julien,
Thank you for the patch.
On Thu, Nov 21, 2024 at 09:53:18AM +0100, Julien Stephan wrote:
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
> drivers/media/platform/mediatek/isp/Kconfig | 18 +
> drivers/media/platform/mediatek/isp/Makefile | 5 +
> drivers/media/platform/mediatek/isp/mtk_camsv.c | 275 ++++++++
> drivers/media/platform/mediatek/isp/mtk_camsv.h | 170 +++++
> .../media/platform/mediatek/isp/mtk_camsv30_hw.c | 539 ++++++++++++++++
> .../media/platform/mediatek/isp/mtk_camsv_video.c | 701 +++++++++++++++++++++
> 6 files changed, 1708 insertions(+)
>
> diff --git a/drivers/media/platform/mediatek/isp/Kconfig b/drivers/media/platform/mediatek/isp/Kconfig
> index 2a3cef81d15aa12633ade2f3be0bba36b9af62e1..2b89efc7ba9aa6b85f850bb8ec938cde581f31a2 100644
> --- a/drivers/media/platform/mediatek/isp/Kconfig
> +++ b/drivers/media/platform/mediatek/isp/Kconfig
> @@ -1,4 +1,22 @@
> # SPDX-License-Identifier: GPL-2.0-only
Missing blank line.
> +config MTK_CAMSV30
> + tristate "MediaTek ISP3.0 CAMSV driver"
> + depends on ARCH_MEDIATEK || COMPILE_TEST
> + depends on OF
> + depends on PM
> + select MEDIA_CONTROLLER
> + select MTK_SENINF30
> + select VIDEO_V4L2_SUBDEV_API
Wrong indentation.
> + select VIDEOBUF2_DMA_CONTIG
> + select VIDEOBUF2_VMALLOC
I don't think the latter is needed.
> + default n
> + help
> + This driver provides a path to bypass the SoC ISP so that
> + image data come from the SENINF can go directly into memory
> + without any image processing.
Missing blank line between paragraphs.
> + To compile this driver as a module, choose M here: the
> + module will be called mtk-camsv30.
> +
> config MTK_SENINF30
> tristate "MediaTek ISP3.0 SENINF driver"
> depends on ARCH_MEDIATEK || COMPILE_TEST
> diff --git a/drivers/media/platform/mediatek/isp/Makefile b/drivers/media/platform/mediatek/isp/Makefile
> index 375d720f9ed75e2197bb723bdce9bc0472e62842..e7205759fe9bc27bd5146c490b93db72deb3767f 100644
> --- a/drivers/media/platform/mediatek/isp/Makefile
> +++ b/drivers/media/platform/mediatek/isp/Makefile
> @@ -1,4 +1,9 @@
> # SPDX-License-Identifier: GPL-2.0
>
> +mtk-camsv30-objs += mtk_camsv.o
> +mtk-camsv30-objs += mtk_camsv30_hw.o
> +mtk-camsv30-objs += mtk_camsv_video.o
> +obj-$(CONFIG_MTK_CAMSV30) += mtk-camsv30.o
> +
> mtk-seninf-objs += mtk_seninf.o
> obj-$(CONFIG_MTK_SENINF30) += mtk-seninf.o
> diff --git a/drivers/media/platform/mediatek/isp/mtk_camsv.c b/drivers/media/platform/mediatek/isp/mtk_camsv.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..a02a1c226ee6164db08d18d6927d35ac86eaa8cc
> --- /dev/null
> +++ b/drivers/media/platform/mediatek/isp/mtk_camsv.c
> @@ -0,0 +1,275 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 BayLibre
> + * Copyright (c) 2022 MediaTek Inc.
> + */
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-device.h>
> +
> +#include "mtk_camsv.h"
> +
> +static inline struct mtk_cam_dev *to_mtk_cam_dev(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct mtk_cam_dev, subdev);
> +}
> +
> +static const u32 mtk_cam_mbus_formats[] = {
> + MEDIA_BUS_FMT_SBGGR8_1X8,
> + MEDIA_BUS_FMT_SGBRG8_1X8,
> + MEDIA_BUS_FMT_SGRBG8_1X8,
> + MEDIA_BUS_FMT_SRGGB8_1X8,
> + MEDIA_BUS_FMT_SBGGR10_1X10,
> + MEDIA_BUS_FMT_SGBRG10_1X10,
> + MEDIA_BUS_FMT_SGRBG10_1X10,
> + MEDIA_BUS_FMT_SRGGB10_1X10,
> + MEDIA_BUS_FMT_SBGGR12_1X12,
> + MEDIA_BUS_FMT_SGBRG12_1X12,
> + MEDIA_BUS_FMT_SGRBG12_1X12,
> + MEDIA_BUS_FMT_SRGGB12_1X12,
> + MEDIA_BUS_FMT_UYVY8_1X16,
> + MEDIA_BUS_FMT_VYUY8_1X16,
> + MEDIA_BUS_FMT_YUYV8_1X16,
> + MEDIA_BUS_FMT_YVYU8_1X16,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 Subdev Operations
> + */
> +
> +static int mtk_cam_init_state(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state)
> +{
> + static const struct v4l2_mbus_framefmt def_format = {
> + .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .width = IMG_MAX_WIDTH,
> + .height = IMG_MAX_HEIGHT,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .xfer_func = V4L2_XFER_FUNC_DEFAULT,
> + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
> + .quantization = V4L2_QUANTIZATION_DEFAULT,
> + };
> + struct v4l2_mbus_framefmt *format;
> + unsigned int i;
> +
> + for (i = 0; i < sd->entity.num_pads; i++) {
> + format = v4l2_subdev_state_get_format(sd_state, i);
> + *format = def_format;
> + }
> +
> + return 0;
> +}
> +
> +static int mtk_cam_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index >= ARRAY_SIZE(mtk_cam_mbus_formats))
> + return -EINVAL;
> +
> + code->code = mtk_cam_mbus_formats[code->index];
> +
> + return 0;
> +}
> +
> +static int mtk_cam_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct v4l2_mbus_framefmt *format;
> + unsigned int i;
> +
> + /*
> + * We only support pass-through mode, the format on source pads can't
> + * be modified.
> + */
> + if (fmt->pad != MTK_CAM_CIO_PAD_SENINF)
> + return -EINVAL;
> +
> + for (i = 0; i < ARRAY_SIZE(mtk_cam_mbus_formats); ++i) {
> + if (mtk_cam_mbus_formats[i] == fmt->format.code)
> + break;
> + }
> +
> + if (i == ARRAY_SIZE(mtk_cam_mbus_formats))
> + fmt->format.code = mtk_cam_mbus_formats[0];
> +
> + format = v4l2_subdev_state_get_format(sd_state, fmt->pad);
> + format->width = clamp_t(u32, fmt->format.width,
> + IMG_MIN_WIDTH, IMG_MAX_WIDTH);
> + format->height = clamp_t(u32, fmt->format.height,
> + IMG_MIN_HEIGHT, IMG_MAX_HEIGHT);
> + format->code = fmt->format.code;
> +
> + fmt->format = *format;
> +
> + /* Propagate the format to the source pad. */
> + format = v4l2_subdev_state_get_format(sd_state, MTK_CAM_CIO_PAD_VIDEO);
> + format->width = fmt->format.width;
> + format->height = fmt->format.height;
> + format->code = fmt->format.code;
> +
> + return 0;
> +}
> +
> +static int mtk_cam_subdev_registered(struct v4l2_subdev *sd)
> +{
> + struct mtk_cam_dev *cam = to_mtk_cam_dev(sd);
> +
> + /* Create the video device and link. */
> + return mtk_cam_video_register(cam);
> +}
> +
> +static int camsv_enable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state, u32 pad,
> + u64 streams_mask)
> +{
> + struct mtk_cam_dev *cam = to_mtk_cam_dev(sd);
> + struct v4l2_subdev *seninf;
> + int ret;
> +
> + if (!cam->seninf) {
> + cam->seninf = media_pad_remote_pad_first(&cam->subdev_pads[MTK_CAM_CIO_PAD_SENINF]);
> + if (!cam->seninf) {
> + dev_err(cam->dev, "No SENINF connected\n");
> + return -ENOLINK;
> + }
> + }
> +
> + seninf = media_entity_to_v4l2_subdev(cam->seninf->entity);
> +
> + /* Seninf must stream on first */
> + ret = v4l2_subdev_enable_streams(seninf, cam->seninf->index, BIT(0));
> + if (ret) {
> + dev_err(cam->dev, "failed to stream on %s:%d\n",
> + seninf->entity.name, ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int camsv_disable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state, u32 pad,
> + u64 streams_mask)
> +{
> + struct mtk_cam_dev *cam = to_mtk_cam_dev(sd);
> + struct v4l2_subdev *seninf;
> + int ret;
> +
> + if (cam->seninf) {
> + seninf = media_entity_to_v4l2_subdev(cam->seninf->entity);
> + ret = v4l2_subdev_disable_streams(seninf, cam->seninf->index,
> + BIT(0));
> + if (ret) {
> + dev_err(cam->dev, "failed to stream off %s:%d\n",
> + sd->entity.name, ret);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops mtk_cam_subdev_pad_ops = {
> + .enum_mbus_code = mtk_cam_enum_mbus_code,
> + .set_fmt = mtk_cam_set_fmt,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .link_validate = v4l2_subdev_link_validate_default,
> + .enable_streams = camsv_enable_streams,
> + .disable_streams = camsv_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops mtk_cam_subdev_ops = {
> + .pad = &mtk_cam_subdev_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops mtk_cam_internal_ops = {
> + .init_state = mtk_cam_init_state,
> + .registered = mtk_cam_subdev_registered,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Media Entity Operations
> + */
> +
> +static const struct media_entity_operations mtk_cam_media_entity_ops = {
> + .link_validate = v4l2_subdev_link_validate,
> + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Init & Cleanup
> + */
> +
> +static int mtk_cam_v4l2_register(struct mtk_cam_dev *cam)
> +{
> + struct device *dev = cam->dev;
> + int ret;
> +
> + cam->subdev_pads[MTK_CAM_CIO_PAD_SENINF].flags = MEDIA_PAD_FL_SINK;
> + cam->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].flags = MEDIA_PAD_FL_SOURCE;
> +
> + /* Initialize subdev pads */
> + ret = media_entity_pads_init(&cam->subdev.entity,
> + ARRAY_SIZE(cam->subdev_pads),
> + cam->subdev_pads);
> + if (ret) {
> + dev_err(dev, "failed to initialize media pads:%d\n", ret);
> + return ret;
> + }
> +
> + /* Initialize subdev */
> + v4l2_subdev_init(&cam->subdev, &mtk_cam_subdev_ops);
> +
> + cam->subdev.dev = dev;
> + cam->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> + cam->subdev.entity.ops = &mtk_cam_media_entity_ops;
> + cam->subdev.internal_ops = &mtk_cam_internal_ops;
> + cam->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
> + strscpy(cam->subdev.name, dev_name(dev), sizeof(cam->subdev.name));
> + v4l2_set_subdevdata(&cam->subdev, cam);
> +
> + v4l2_subdev_init_finalize(&cam->subdev);
> +
> + ret = v4l2_async_register_subdev(&cam->subdev);
> + if (ret) {
> + dev_err(dev, "failed to initialize subdev:%d\n", ret);
> + media_entity_cleanup(&cam->subdev.entity);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_cam_v4l2_unregister(struct mtk_cam_dev *cam)
> +{
> + mtk_cam_video_unregister(&cam->vdev);
> +
> + media_entity_cleanup(&cam->subdev.entity);
> + v4l2_async_unregister_subdev(&cam->subdev);
> + v4l2_subdev_cleanup(&cam->subdev);
> +}
> +
> +int mtk_cam_dev_init(struct mtk_cam_dev *cam_dev)
> +{
> + int ret;
> +
> + mutex_init(&cam_dev->op_lock);
> +
> + /* v4l2 sub-device registration */
> + ret = mtk_cam_v4l2_register(cam_dev);
> + if (ret) {
> + mutex_destroy(&cam_dev->op_lock);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +void mtk_cam_dev_cleanup(struct mtk_cam_dev *cam)
> +{
> + mtk_cam_v4l2_unregister(cam);
> + mutex_destroy(&cam->op_lock);
> +}
> diff --git a/drivers/media/platform/mediatek/isp/mtk_camsv.h b/drivers/media/platform/mediatek/isp/mtk_camsv.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..de928662c75778ffeae708a7bdac27943af75d94
> --- /dev/null
> +++ b/drivers/media/platform/mediatek/isp/mtk_camsv.h
> @@ -0,0 +1,170 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2020 BayLibre
> + * Copyright (c) 2022 MediaTek Inc.
> + */
> +
> +#ifndef __MTK_CAMSV_H__
> +#define __MTK_CAMSV_H__
> +
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/videodev2.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-dma-contig.h>
> +#include <media/videobuf2-v4l2.h>
> +#include <soc/mediatek/smi.h>
> +
> +#define IMG_MAX_WIDTH 5376U
> +#define IMG_MAX_HEIGHT 4032U
> +#define IMG_MIN_WIDTH 80U
> +#define IMG_MIN_HEIGHT 60U
> +
> +#define MTK_CAM_CIO_PAD_SENINF 0U
> +#define MTK_CAM_CIO_PAD_VIDEO 1U
> +#define MTK_CAM_CIO_NUM_PADS 2U
> +
> +struct mtk_cam_format_info {
> + u32 code;
> + u32 fourcc;
> + u8 bpp;
> +};
> +
> +struct mtk_cam_dev_buffer {
> + struct vb2_v4l2_buffer v4l2_buf;
> + struct list_head list;
> + dma_addr_t daddr;
> + void *vaddr;
> +};
> +
> +struct mtk_cam_sparams {
> + u32 w_factor;
> + u32 module_en_pak;
> + u32 fmt_sel;
> + u32 pak;
> + u32 imgo_stride;
> +};
> +
> +/**
> + * struct mtk_cam_vdev_desc - MTK camera device descriptor
> + * @num_fmts: the number of supported node formats
> + * @fmts: supported format
> + * @frmsizes: supported V4L2 frame size number
> + */
> +struct mtk_cam_vdev_desc {
> + u8 num_fmts;
> + const u32 *fmts;
> + const struct v4l2_frmsizeenum *frmsizes;
> +};
> +
> +/**
> + * struct mtk_cam_video_device - MediaTek video device structure
> + * @desc: The node description of video device
> + * @vdev_pad: The media pad graph object of video device
> + * @vdev: The video device instance
> + * @vbq: A videobuf queue of video device
> + * @vdev_lock: Serializes vb2 queue and video device operations
> + * @format: The V4L2 format of video device
> + * @fmtinfo: Information about the current format
> + */
> +struct mtk_cam_video_device {
> + const struct mtk_cam_vdev_desc *desc;
> +
> + struct media_pad vdev_pad;
> + struct video_device vdev;
> + struct vb2_queue vbq;
> +
> + /* Serializes vb2 queue and video device operations */
> + struct mutex vdev_lock;
> +
> + struct v4l2_pix_format_mplane format;
> + const struct mtk_cam_format_info *fmtinfo;
> +};
> +
> +/**
> + * struct mtk_cam_dev - MediaTek camera device structure.
> + * @dev: Pointer to device.
> + * @regs: Base address of CAMSV.
> + * @regs_img0: Base address of CAMSV IMG0.
> + * @regs_tg: Base address of CAMSV TG.
> + * @num_clks: Number of clocks.
> + * @clks: The clocks.
> + * @irq: Irq fired when buffer is ready.
> + * @conf: soc specific driver data.
> + * @pipeline: Media pipeline information.
> + * @subdev: The V4L2 sub-device instance.
> + * @subdev_pads: Media pads of this sub-device.
> + * @vdev: The video device node.
> + * @seninf: Pointer to the seninf pad.
> + * @stream_count: Number of streaming video nodes.
> + * @sequence: Buffer sequence number.
> + * @op_lock: Serializes driver's VB2 callback operations.
> + * @buf_list_lock: Protects the buffer list.
> + * @buf_list: List head for the buffer list.
> + * @hw_functions: Hardware specific functions.
> + */
> +struct mtk_cam_dev {
> + struct device *dev;
> + void __iomem *regs;
> + void __iomem *regs_img0;
> + void __iomem *regs_tg;
> +
> + unsigned int num_clks;
> + struct clk_bulk_data *clks;
> + unsigned int irq;
> + const struct mtk_cam_conf *conf;
> +
> + struct media_pipeline pipeline;
> + struct v4l2_subdev subdev;
> + struct media_pad subdev_pads[MTK_CAM_CIO_NUM_PADS];
> + struct mtk_cam_video_device vdev;
> + struct media_pad *seninf;
> + unsigned int stream_count;
> + unsigned int sequence;
> +
> + struct mutex op_lock;
> + spinlock_t buf_list_lock;
> +
> + struct list_head buf_list;
> +
> + const struct mtk_cam_hw_functions *hw_functions;
> +
> +};
> +
> +/**
> + * struct mtk_cam_conf - MediaTek camera configuration structure
> + * @tg_sen_mode: TG sensor mode
> + * @module_en: module enable
> + * @imgo_con: dma control register
> + * @imgo_con2: dma control register 2
> + */
> +struct mtk_cam_conf {
> + u32 tg_sen_mode;
> + u32 module_en;
> + u32 imgo_con;
> + u32 imgo_con2;
> +};
> +
> +struct mtk_cam_hw_functions {
> + void (*mtk_cam_setup)(struct mtk_cam_dev *cam_dev, u32 width,
> + u32 height, u32 bpl, u32 mbus_fmt);
> + void (*mtk_cam_update_buffers_add)(struct mtk_cam_dev *cam_dev,
> + struct mtk_cam_dev_buffer *buf);
> + void (*mtk_cam_cmos_vf_hw_enable)(struct mtk_cam_dev *cam_dev);
> + void (*mtk_cam_cmos_vf_hw_disable)(struct mtk_cam_dev *cam_dev);
> + void (*mtk_cam_fbc_init)(struct mtk_cam_dev *cam_dev,
> + unsigned int num_buffers);
> + void (*mtk_cam_fbc_inc)(struct mtk_cam_dev *cam_dev);
> +};
> +
> +int mtk_cam_dev_init(struct mtk_cam_dev *cam_dev);
> +void mtk_cam_dev_cleanup(struct mtk_cam_dev *cam_dev);
> +int mtk_cam_video_register(struct mtk_cam_dev *cam_dev);
> +void mtk_cam_video_unregister(struct mtk_cam_video_device *vdev);
> +
> +#endif /* __MTK_CAMSV_H__ */
> diff --git a/drivers/media/platform/mediatek/isp/mtk_camsv30_hw.c b/drivers/media/platform/mediatek/isp/mtk_camsv30_hw.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..56c3686770901da9d355f36ee86a9aa7f71aeb1f
> --- /dev/null
> +++ b/drivers/media/platform/mediatek/isp/mtk_camsv30_hw.c
> @@ -0,0 +1,539 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 BayLibre
> + * Copyright (c) 2022 MediaTek Inc.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/iommu.h>
> +#include <linux/iopoll.h>
> +#include <linux/ktime.h>
> +#include <linux/bits.h>
Alphabetical order.
> +#include <linux/module.h>
> +#include <linux/of.h>
Not needed anymore.
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +
> +#include "mtk_camsv.h"
> +
> +/* CAMSV */
> +#define CAMSV_MODULE_EN 0x0000
> +#define CAMSV_MODULE_EN_IMGO_EN BIT(4)
> +#define CAMSV_FMT_SEL 0x0004
> +#define CAMSV_INT_EN 0x0008
> +#define CAMSV_INT_STATUS 0x000c
> +#define CAMSV_SW_CTL 0x0010
> +#define CAMSV_IMGO_FBC 0x001C
> +#define CAMSV_CLK_EN 0x0020
> +#define CAMSV_PAK 0x003c
> +
> +/* CAMSV_TG */
> +#define CAMSV_TG_SEN_MODE 0x0010
> +#define CAMSV_TG_VF_CON 0x0014
> +#define CAMSV_TG_SEN_GRAB_PXL 0x0018
> +#define CAMSV_TG_SEN_GRAB_LIN 0x001c
> +#define CAMSV_TG_PATH_CFG 0x0020
> +
> +/* CAMSV_IMG0 */
> +#define CAMSV_IMGO_SV_BASE_ADDR 0x0000
> +#define CAMSV_IMGO_SV_XSIZE 0x0008
> +#define CAMSV_IMGO_SV_YSIZE 0x000c
> +#define CAMSV_IMGO_SV_STRIDE 0x0010
> +#define CAMSV_IMGO_SV_CON 0x0014
> +#define CAMSV_IMGO_SV_CON2 0x0018
> +
> +#define CAMSV_TG_SEN_MODE_CMOS_EN BIT(0)
> +#define CAMSV_TG_VF_CON_VFDATA_EN BIT(0)
> +
> +/* CAMSV_CLK_EN bits */
> +#define CAMSV_TG_DP_CLK_EN BIT(0)
> +#define CAMSV_PAK_DP_CLK_EN BIT(2)
> +#define CAMSV_DMA_DP_CLK_EN BIT(15)
> +
> +/* CAMSV_SW_CTL bits */
> +#define CAMSV_IMGO_RST_TRIG BIT(0)
> +#define CAMSV_IMGO_RST_ST BIT(1)
> +#define CAMSV_SW_RST BIT(2)
> +
> +/* IRQ BITS */
> +#define CAMSV_IRQ_TG_ERR BIT(4)
> +#define CAMSV_IRQ_TG_GBERR BIT(5)
> +#define CAMSV_IRQ_PASS1_DON BIT(10)
> +#define CAMSV_IRQ_IMGO_ERR BIT(16)
> +
> +/* FBC bits */
> +#define CAMSV_IMGO_FBC_RCNT_INC BIT(11)
> +#define CAMSV_IMGO_FBC_EN BIT(14)
> +#define CAMSV_IMGO_FBC_LOCK_EN BIT(15)
> +#define CAMSV_IMGO_FBC_FB_NUM GENMASK(19, 16)
> +
> +#define INT_ST_MASK_CAMSV (CAMSV_IRQ_PASS1_DON)
> +
> +#define INT_ST_MASK_CAMSV_ERR \
> + (CAMSV_IRQ_TG_ERR | CAMSV_IRQ_TG_GBERR | CAMSV_IRQ_IMGO_ERR)
> +
> +#define MTK_CAMSV30_AUTOSUSPEND_DELAY_MS 100
> +
> +static const struct mtk_cam_conf camsv30_conf = {
> + .tg_sen_mode = 0x00010002U, /* TIME_STP_EN = 1. DBL_DATA_BUS = 1 */
> + .module_en = 0x40000001U, /* enable double buffer and TG */
> + .imgo_con = 0x80000080U, /* DMA FIFO depth and burst */
> + .imgo_con2 = 0x00020002U, /* DMA priority */
> +};
> +
> +static void fmt_to_sparams(u32 mbus_fmt, struct mtk_cam_sparams *sparams)
> +{
> + switch (mbus_fmt) {
> + case MEDIA_BUS_FMT_SBGGR12_1X12:
> + case MEDIA_BUS_FMT_SGBRG12_1X12:
> + case MEDIA_BUS_FMT_SGRBG12_1X12:
> + case MEDIA_BUS_FMT_SRGGB12_1X12:
> + sparams->w_factor = 1;
> + sparams->module_en_pak = 0x4;
> + sparams->fmt_sel = 0x2;
> + sparams->pak = 0x5;
> + sparams->imgo_stride = 0X000b0000;
> + break;
> + case MEDIA_BUS_FMT_SBGGR10_1X10:
> + case MEDIA_BUS_FMT_SGBRG10_1X10:
> + case MEDIA_BUS_FMT_SGRBG10_1X10:
> + case MEDIA_BUS_FMT_SRGGB10_1X10:
> + sparams->w_factor = 1;
> + sparams->module_en_pak = 0x4;
> + sparams->fmt_sel = 0x1;
> + sparams->pak = 0x6;
> + sparams->imgo_stride = 0X000b0000;
> + break;
> + case MEDIA_BUS_FMT_SBGGR8_1X8:
> + case MEDIA_BUS_FMT_SGBRG8_1X8:
> + case MEDIA_BUS_FMT_SGRBG8_1X8:
> + case MEDIA_BUS_FMT_SRGGB8_1X8:
> + sparams->w_factor = 1;
> + sparams->module_en_pak = 0x4;
> + sparams->fmt_sel = 0x0;
> + sparams->pak = 0x7;
> + sparams->imgo_stride = 0X000b0000;
> + break;
> + case MEDIA_BUS_FMT_UYVY8_1X16:
> + case MEDIA_BUS_FMT_VYUY8_1X16:
> + case MEDIA_BUS_FMT_YUYV8_1X16:
> + case MEDIA_BUS_FMT_YVYU8_1X16:
> + sparams->w_factor = 2;
> + sparams->module_en_pak = 0x8;
> + sparams->fmt_sel = 0x1000003;
> + sparams->pak = 0x0;
> + sparams->imgo_stride = 0x00090000;
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static u32 mtk_camsv30_read(struct mtk_cam_dev *priv, u32 reg)
> +{
> + return readl(priv->regs + reg);
> +}
> +
> +static void mtk_camsv30_write(struct mtk_cam_dev *priv, u32 reg, u32 value)
> +{
> + writel(value, priv->regs + reg);
> +}
> +
> +static void mtk_camsv30_img0_write(struct mtk_cam_dev *priv, u32 reg, u32 value)
> +{
> + writel(value, priv->regs_img0 + reg);
> +}
> +
> +static u32 mtk_camsv30_tg_read(struct mtk_cam_dev *priv, u32 reg)
> +{
> + return readl(priv->regs_tg + reg);
> +}
> +
> +static void mtk_camsv30_tg_write(struct mtk_cam_dev *priv, u32 reg, u32 value)
> +{
> + writel(value, priv->regs_tg + reg);
> +}
> +
> +static void mtk_camsv30_update_buffers_add(struct mtk_cam_dev *cam_dev,
> + struct mtk_cam_dev_buffer *buf)
> +{
> + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_BASE_ADDR, buf->daddr);
> +}
> +
> +static void mtk_camsv30_cmos_vf_hw_enable(struct mtk_cam_dev *cam_dev)
> +{
> + unsigned int fbc_val;
> + u32 clk_en = CAMSV_TG_DP_CLK_EN | CAMSV_DMA_DP_CLK_EN |
> + CAMSV_PAK_DP_CLK_EN;
> +
> + fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
> + fbc_val |= CAMSV_IMGO_FBC_EN;
> + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> +
> + mtk_camsv30_write(cam_dev, CAMSV_CLK_EN, clk_en);
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_MODE,
> + mtk_camsv30_tg_read(cam_dev, CAMSV_TG_SEN_MODE) |
> + CAMSV_TG_SEN_MODE_CMOS_EN);
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_VF_CON,
> + mtk_camsv30_tg_read(cam_dev, CAMSV_TG_VF_CON) |
> + CAMSV_TG_VF_CON_VFDATA_EN);
> +}
> +
> +static void mtk_camsv30_cmos_vf_hw_disable(struct mtk_cam_dev *cam_dev)
> +{
> + unsigned int fbc_val;
> +
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_MODE,
> + mtk_camsv30_tg_read(cam_dev, CAMSV_TG_SEN_MODE) &
> + ~CAMSV_TG_SEN_MODE_CMOS_EN);
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_VF_CON,
> + mtk_camsv30_tg_read(cam_dev, CAMSV_TG_VF_CON) &
> + ~CAMSV_TG_VF_CON_VFDATA_EN);
> + fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
> + fbc_val &= ~CAMSV_IMGO_FBC_EN;
> + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> +}
> +
> +static void mtk_camsv30_fbc_init(struct mtk_cam_dev *cam_dev,
> + unsigned int num_buffers)
> +{
> + unsigned int fbc_val;
> +
> + if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
> + dev_err(cam_dev->dev, "failed to get pm_runtime\n");
> + return;
> + }
> +
> + fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
> + fbc_val &= ~CAMSV_IMGO_FBC_FB_NUM;
> + fbc_val |= CAMSV_IMGO_FBC_EN;
> + fbc_val |= FIELD_PREP(CAMSV_IMGO_FBC_FB_NUM, num_buffers);
> + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> +
> + pm_runtime_put_autosuspend(cam_dev->dev);
> +}
> +
> +static void mtk_camsv30_fbc_inc(struct mtk_cam_dev *cam_dev)
> +{
> + unsigned int fbc_val;
> +
> + if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
> + dev_err(cam_dev->dev, "failed to get pm_runtime\n");
> + return;
> + }
> +
> + fbc_val = mtk_camsv30_read(cam_dev, CAMSV_IMGO_FBC);
> + fbc_val |= CAMSV_IMGO_FBC_RCNT_INC;
> + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
> + fbc_val &= ~CAMSV_IMGO_FBC_RCNT_INC;
> + mtk_camsv30_write(cam_dev, CAMSV_IMGO_FBC, fbc_val);
This needs an explanation in a comment, it's not clear at all how it
prevents overwriting memory in case of buffer underruns. It may be best
to document the mechanism in a more prominent location than in this
function, as it's very important. It could be that I don't understand
how this is supposed to work, but the implementations seems extremely
racy.
> +
> + pm_runtime_put_autosuspend(cam_dev->dev);
> +}
> +
> +static void mtk_camsv30_setup(struct mtk_cam_dev *cam_dev, u32 w, u32 h,
> + u32 bpl, u32 mbus_fmt)
> +{
> + const struct mtk_cam_conf *conf = cam_dev->conf;
> + u32 tmp;
> + struct mtk_cam_sparams sparams;
> +
> + fmt_to_sparams(mbus_fmt, &sparams);
> +
> + if (pm_runtime_resume_and_get(cam_dev->dev) < 0) {
> + dev_err(cam_dev->dev, "failed to get pm_runtime\n");
> + return;
> + }
I think this belongs to mtk_cam_vb2_start_streaming(). There you can
return an error if the call fails.
> +
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_MODE, conf->tg_sen_mode);
> +
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_GRAB_PXL,
> + (w * sparams.w_factor) << 16U);
> +
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_GRAB_LIN, h << 16U);
> +
> + /* YUV_U2S_DIS: disable YUV sensor unsigned to signed */
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_PATH_CFG, 0x1000U);
> +
> + /* Reset cam */
> + mtk_camsv30_write(cam_dev, CAMSV_SW_CTL, CAMSV_SW_RST);
> + mtk_camsv30_write(cam_dev, CAMSV_SW_CTL, 0x0U);
> + mtk_camsv30_write(cam_dev, CAMSV_SW_CTL, CAMSV_IMGO_RST_TRIG);
> +
> + readl_poll_timeout_atomic(cam_dev->regs + CAMSV_SW_CTL, tmp,
> + (tmp == (CAMSV_IMGO_RST_TRIG |
> + CAMSV_IMGO_RST_ST)), 10, 200);
> +
> + mtk_camsv30_write(cam_dev, CAMSV_SW_CTL, 0x0U);
> +
> + mtk_camsv30_write(cam_dev, CAMSV_INT_EN, INT_ST_MASK_CAMSV);
> +
> + mtk_camsv30_write(cam_dev, CAMSV_MODULE_EN,
> + conf->module_en | sparams.module_en_pak);
> + mtk_camsv30_write(cam_dev, CAMSV_FMT_SEL, sparams.fmt_sel);
> + mtk_camsv30_write(cam_dev, CAMSV_PAK, sparams.pak);
> +
> + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_XSIZE, bpl - 1U);
> + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_YSIZE, h - 1U);
> +
> + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_STRIDE,
> + sparams.imgo_stride | bpl);
> +
> + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_CON, conf->imgo_con);
> + mtk_camsv30_img0_write(cam_dev, CAMSV_IMGO_SV_CON2, conf->imgo_con2);
> +
> + /* CMOS_EN first */
> + mtk_camsv30_tg_write(cam_dev, CAMSV_TG_SEN_MODE,
> + mtk_camsv30_tg_read(cam_dev, CAMSV_TG_SEN_MODE) |
> + CAMSV_TG_SEN_MODE_CMOS_EN);
> +
> + /* finally, CAMSV_MODULE_EN : IMGO_EN */
> + mtk_camsv30_write(cam_dev, CAMSV_MODULE_EN,
> + mtk_camsv30_read(cam_dev, CAMSV_MODULE_EN) |
> + CAMSV_MODULE_EN_IMGO_EN);
> +
You're missing the mark_last_busy() call. Same in other locations where
you call pm_runtime_put_autosuspend().
> + pm_runtime_put_autosuspend(cam_dev->dev);
This should go to mtk_cam_vb2_stop_streaming(). I'm surprised things
work at all as you don't seem to keep the device resumed while
streaming. Unless that's a byproduct of using autosuspend and resuming
periodically in mtk_camsv30_fbc_inc() ?
> +}
> +
> +static irqreturn_t isp_irq_camsv30(int irq, void *data)
> +{
> + struct mtk_cam_dev *cam_dev = (struct mtk_cam_dev *)data;
> + struct mtk_cam_dev_buffer *buf;
> + unsigned int irq_status;
> +
> + spin_lock(&cam_dev->buf_list_lock);
> +
> + irq_status = mtk_camsv30_read(cam_dev, CAMSV_INT_STATUS);
> +
> + if (irq_status & INT_ST_MASK_CAMSV_ERR)
> + dev_err(cam_dev->dev, "irq error 0x%lx\n",
> + irq_status & INT_ST_MASK_CAMSV_ERR);
> +
> + /* De-queue frame */
> + if (irq_status & CAMSV_IRQ_PASS1_DON) {
> + cam_dev->sequence++;
> +
> + buf = list_first_entry_or_null(&cam_dev->buf_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + if (buf) {
> + buf->v4l2_buf.sequence = cam_dev->sequence;
> + buf->v4l2_buf.vb2_buf.timestamp =
> + ktime_get_ns();
> + vb2_buffer_done(&buf->v4l2_buf.vb2_buf,
> + VB2_BUF_STATE_DONE);
> + list_del(&buf->list);
> + }
> +
> + buf = list_first_entry_or_null(&cam_dev->buf_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + if (buf)
> + mtk_camsv30_update_buffers_add(cam_dev, buf);
> + }
> +
> + spin_unlock(&cam_dev->buf_list_lock);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int mtk_camsv30_runtime_suspend(struct device *dev)
> +{
> + struct mtk_cam_dev *cam_dev = dev_get_drvdata(dev);
> + struct vb2_queue *vbq = &cam_dev->vdev.vbq;
> +
> + if (vb2_is_streaming(vbq)) {
> + mutex_lock(&cam_dev->op_lock);
> + v4l2_subdev_disable_streams(&cam_dev->subdev,
> + cam_dev->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].index,
> + BIT(0));
> + mutex_unlock(&cam_dev->op_lock);
> + }
> +
> + clk_bulk_disable_unprepare(cam_dev->num_clks, cam_dev->clks);
> +
> + return 0;
> +}
> +
> +static int mtk_camsv30_runtime_resume(struct device *dev)
> +{
> + struct mtk_cam_dev *cam_dev = dev_get_drvdata(dev);
> + struct mtk_cam_video_device *vdev = &cam_dev->vdev;
> + const struct v4l2_pix_format_mplane *fmt = &vdev->format;
> + struct vb2_queue *vbq = &vdev->vbq;
> + struct mtk_cam_dev_buffer *buf, *buf_prev;
> + int ret;
> + unsigned long flags = 0;
> +
> + ret = clk_bulk_prepare_enable(cam_dev->num_clks, cam_dev->clks);
> + if (ret) {
> + dev_err(dev, "failed to enable clock:%d\n", ret);
> + return ret;
> + }
> +
> + if (vb2_is_streaming(vbq)) {
> + mtk_camsv30_setup(cam_dev, fmt->width, fmt->height,
> + fmt->plane_fmt[0].bytesperline,
> + vdev->fmtinfo->code);
> +
> + spin_lock_irqsave(&cam_dev->buf_list_lock, flags);
> + buf = list_first_entry_or_null(&cam_dev->buf_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + if (buf)
> + mtk_camsv30_update_buffers_add(cam_dev, buf);
> +
> + spin_unlock_irqrestore(&cam_dev->buf_list_lock, flags);
> + mtk_camsv30_cmos_vf_hw_enable(cam_dev);
> +
> +
Double blank line.
> + /* Stream on the sub-device */
> + mutex_lock(&cam_dev->op_lock);
> + ret = v4l2_subdev_enable_streams(&cam_dev->subdev,
> + cam_dev->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].index,
> + BIT(0));
> +
> + if (ret) {
> + cam_dev->stream_count--;
> + if (cam_dev->stream_count == 0)
> + media_pipeline_stop(vdev->vdev.entity.pads);
> + }
> + mutex_unlock(&cam_dev->op_lock);
> +
> + if (ret)
> + goto fail_no_stream;
Lots of this duplicates logic from mtk_cam_vb2_start_streaming(). I
think you should try to share code if possible.
> + }
> +
> + return 0;
> +
> +fail_no_stream:
> + spin_lock_irqsave(&cam_dev->buf_list_lock, flags);
> + list_for_each_entry_safe(buf, buf_prev, &cam_dev->buf_list, list) {
> + buf->daddr = 0ULL;
> + list_del(&buf->list);
> + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
> + }
> + spin_unlock_irqrestore(&cam_dev->buf_list_lock, flags);
> + return ret;
> +}
> +
> +static const struct mtk_cam_hw_functions mtk_camsv30_hw_functions = {
> + .mtk_cam_setup = mtk_camsv30_setup,
> + .mtk_cam_update_buffers_add = mtk_camsv30_update_buffers_add,
> + .mtk_cam_cmos_vf_hw_enable = mtk_camsv30_cmos_vf_hw_enable,
> + .mtk_cam_cmos_vf_hw_disable = mtk_camsv30_cmos_vf_hw_disable,
> + .mtk_cam_fbc_init = mtk_camsv30_fbc_init,
> + .mtk_cam_fbc_inc = mtk_camsv30_fbc_inc,
> +};
> +
> +static int mtk_camsv30_probe(struct platform_device *pdev)
> +{
> + static const char * const clk_names[] = { "cam", "camtg", "camsv"};
> +
> + struct mtk_cam_dev *cam_dev;
> + struct device *dev = &pdev->dev;
> + unsigned int i;
> + int ret;
> +
> + if (!iommu_present(&platform_bus_type))
> + return -EPROBE_DEFER;
> +
> + cam_dev = devm_kzalloc(dev, sizeof(*cam_dev), GFP_KERNEL);
> + if (!cam_dev)
> + return -ENOMEM;
> +
> + cam_dev->conf = device_get_match_data(dev);
> + if (!cam_dev->conf)
> + return -ENODEV;
> +
> + cam_dev->dev = dev;
> + dev_set_drvdata(dev, cam_dev);
> +
> + cam_dev->regs = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(cam_dev->regs))
> + return dev_err_probe(dev, PTR_ERR(cam_dev->regs),
> + "failed to map register base\n");
> +
> + cam_dev->regs_img0 = devm_platform_ioremap_resource(pdev, 1);
> + if (IS_ERR(cam_dev->regs_img0))
> + return dev_err_probe(dev, PTR_ERR(cam_dev->regs_img0),
> + "failed to map img0 register base\n");
> +
> + cam_dev->regs_tg = devm_platform_ioremap_resource(pdev, 2);
> + if (IS_ERR(cam_dev->regs_tg))
> + return dev_err_probe(dev, PTR_ERR(cam_dev->regs_tg),
> + "failed to map TG register base\n");
> +
> + cam_dev->num_clks = ARRAY_SIZE(clk_names);
> + cam_dev->clks = devm_kcalloc(dev, cam_dev->num_clks,
> + sizeof(*cam_dev->clks), GFP_KERNEL);
> + if (!cam_dev->clks)
> + return -ENOMEM;
> +
> + for (i = 0; i < cam_dev->num_clks; ++i)
> + cam_dev->clks[i].id = clk_names[i];
> +
> + ret = devm_clk_bulk_get(dev, cam_dev->num_clks, cam_dev->clks);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to get clocks: %i\n",
> + ret);
> +
> + cam_dev->irq = platform_get_irq(pdev, 0);
> + ret = devm_request_irq(dev, cam_dev->irq, isp_irq_camsv30, 0,
> + dev_name(dev), cam_dev);
> + if (ret != 0)
if (ret)
> + return dev_err_probe(dev, -ENODEV, "failed to request irq=%d\n",
s/-ENODEV/ret/
> + cam_dev->irq);
> +
> + cam_dev->hw_functions = &mtk_camsv30_hw_functions;
> +
> + spin_lock_init(&cam_dev->buf_list_lock);
> +
> + /* initialise runtime power management */
> + pm_runtime_set_autosuspend_delay(dev, MTK_CAMSV30_AUTOSUSPEND_DELAY_MS);
> + pm_runtime_use_autosuspend(dev);
> + pm_runtime_set_suspended(dev);
Should seninf also use autosuspend then ?
> + devm_pm_runtime_enable(dev);
> +
> + /* Initialize the v4l2 common part */
> + return mtk_cam_dev_init(cam_dev);
> +}
> +
> +static void mtk_camsv30_remove(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct mtk_cam_dev *cam_dev = dev_get_drvdata(dev);
> +
> + mtk_cam_dev_cleanup(cam_dev);
> + pm_runtime_put_autosuspend(dev);
The probe() function didn't increase the PM refcount unconditionally,
you don't need to decrease it here. I think you should disable
autosuspend though (at least it's a common pattern, but I'm not sure
why drivers would need to do that as it seems that the device core
could handle this task easily).
> +}
> +
> +static const struct dev_pm_ops mtk_camsv30_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> + pm_runtime_force_resume)
> + SET_RUNTIME_PM_OPS(mtk_camsv30_runtime_suspend,
> + mtk_camsv30_runtime_resume, NULL)
> +};
> +
> +static const struct of_device_id mtk_camsv30_of_ids[] = {
> + { .compatible = "mediatek,mt8365-camsv", .data = &camsv30_conf },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, mtk_camsv30_of_ids);
> +
> +static struct platform_driver mtk_camsv30_driver = {
> + .probe = mtk_camsv30_probe,
> + .remove = mtk_camsv30_remove,
> + .driver = {
> + .name = "mtk-camsv-isp30",
> + .of_match_table = mtk_camsv30_of_ids,
> + .pm = &mtk_camsv30_pm_ops,
> + }
> +};
> +
> +module_platform_driver(mtk_camsv30_driver);
> +
> +MODULE_DESCRIPTION("MediaTek CAMSV ISP3.0 driver");
> +MODULE_AUTHOR("Florian Sylvestre <fsylvestre@baylibre.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/mediatek/isp/mtk_camsv_video.c b/drivers/media/platform/mediatek/isp/mtk_camsv_video.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..4a5f3431a14563d5ed133270a9907773e8626f9c
> --- /dev/null
> +++ b/drivers/media/platform/mediatek/isp/mtk_camsv_video.c
> @@ -0,0 +1,701 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * mtk_camsv_video.c - V4L2 video node support
> + *
> + * Copyright (c) 2020 BayLibre
> + * Copyright (c) 2022 MediaTek Inc.
> + */
> +
> +#include <linux/version.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mediabus.h>
> +
> +#include "mtk_camsv.h"
> +
> +static inline struct mtk_cam_video_device *
> +file_to_mtk_cam_video_device(struct file *__file)
> +{
> + return container_of(video_devdata(__file),
> + struct mtk_cam_video_device, vdev);
> +}
> +
> +static inline struct mtk_cam_video_device *
> +vb2_queue_to_mtk_cam_video_device(struct vb2_queue *vq)
> +{
> + return container_of(vq, struct mtk_cam_video_device, vbq);
> +}
> +
> +static inline struct mtk_cam_dev_buffer *
> +to_mtk_cam_dev_buffer(struct vb2_buffer *buf)
> +{
> + return container_of(buf, struct mtk_cam_dev_buffer, v4l2_buf.vb2_buf);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Format Information
> + */
> +
> +static const struct mtk_cam_format_info mtk_cam_format_info[] = {
> + {
> + .fourcc = V4L2_PIX_FMT_SBGGR8,
> + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .bpp = 8,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG8,
> + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .bpp = 8,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG8,
> + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .bpp = 8,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB8,
> + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .bpp = 8,
> + }, {
> + .fourcc = V4L2_PIX_FMT_YUYV,
> + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> + .bpp = 16,
> + }, {
> + .fourcc = V4L2_PIX_FMT_YVYU,
> + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> + .bpp = 16,
> + }, {
> + .fourcc = V4L2_PIX_FMT_UYVY,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .bpp = 16,
> + }, {
> + .fourcc = V4L2_PIX_FMT_VYUY,
> + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .bpp = 16,
> + },
> +};
> +
> +static const struct mtk_cam_format_info *
> +mtk_cam_format_info_by_fourcc(u32 fourcc)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(mtk_cam_format_info); ++i) {
> + const struct mtk_cam_format_info *info =
> + &mtk_cam_format_info[i];
> +
> + if (info->fourcc == fourcc)
> + return info;
> + }
> +
> + return NULL;
> +}
> +
> +static const struct mtk_cam_format_info *
> +mtk_cam_format_info_by_code(u32 code)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(mtk_cam_format_info); ++i) {
> + const struct mtk_cam_format_info *info =
> + &mtk_cam_format_info[i];
> +
> + if (info->code == code)
> + return info;
> + }
> +
> + return NULL;
> +}
> +
> +static bool mtk_cam_dev_find_fmt(const struct mtk_cam_vdev_desc *desc,
> + u32 format)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < desc->num_fmts; i++) {
> + if (desc->fmts[i] == format)
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static void calc_bpl_size_pix_mp(const struct mtk_cam_format_info *fmtinfo,
> + struct v4l2_pix_format_mplane *pix_mp)
> +{
> + unsigned int bpl;
> + unsigned int i;
> +
> + bpl = ALIGN(DIV_ROUND_UP(pix_mp->width * fmtinfo->bpp, 8), 2);
> +
> + for (i = 0; i < pix_mp->num_planes; ++i) {
> + pix_mp->plane_fmt[i].bytesperline = bpl;
> + pix_mp->plane_fmt[i].sizeimage = bpl * pix_mp->height;
> + }
> +}
> +
> +static void mtk_cam_dev_load_default_fmt(struct mtk_cam_dev *cam)
> +{
> + struct mtk_cam_video_device *vdev = &cam->vdev;
> + struct v4l2_pix_format_mplane *fmt = &vdev->format;
> +
> + fmt->num_planes = 1;
> + fmt->pixelformat = vdev->desc->fmts[0];
> + fmt->width = IMG_MAX_WIDTH;
> + fmt->height = IMG_MAX_HEIGHT;
> +
> + fmt->colorspace = V4L2_COLORSPACE_SRGB;
> + fmt->field = V4L2_FIELD_NONE;
> + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> + vdev->fmtinfo = mtk_cam_format_info_by_fourcc(fmt->pixelformat);
> +
> + calc_bpl_size_pix_mp(vdev->fmtinfo, fmt);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * VB2 Queue Operations
> + */
> +
> +static int mtk_cam_vb2_queue_setup(struct vb2_queue *vq,
> + unsigned int *num_buffers,
> + unsigned int *num_planes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct mtk_cam_video_device *vdev =
> + vb2_queue_to_mtk_cam_video_device(vq);
> + struct mtk_cam_dev *cam = vb2_get_drv_priv(vq);
> + const struct v4l2_pix_format_mplane *fmt = &vdev->format;
> + unsigned int size, default_num_planes, i;
> +
> + size = fmt->plane_fmt[0].sizeimage;
> +
> + default_num_planes = 1;
> +
> + if (*num_planes == 0) {
> + *num_planes = default_num_planes;
> + for (i = 0; i < *num_planes; ++i)
> + sizes[i] = size;
> + } else if (*num_planes != default_num_planes || sizes[0] < size) {
> + return -EINVAL;
> + }
> +
> + (*cam->hw_functions->mtk_cam_fbc_init)(cam, *num_buffers);
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> + struct mtk_cam_video_device *vdev =
> + vb2_queue_to_mtk_cam_video_device(vb->vb2_queue);
> + struct mtk_cam_dev *cam = vb2_get_drv_priv(vb->vb2_queue);
> + struct mtk_cam_dev_buffer *buf = to_mtk_cam_dev_buffer(vb);
> + const struct v4l2_pix_format_mplane *fmt = &vdev->format;
> + u32 size;
> + int i;
> +
> + for (i = 0; i < vb->num_planes; i++) {
> + size = fmt->plane_fmt[i].sizeimage;
> + if (vb2_plane_size(vb, i) < size) {
> + dev_err(cam->dev, "plane size is too small:%lu<%u\n",
> + vb2_plane_size(vb, i), size);
> + return -EINVAL;
> + }
> + }
> +
> + buf->v4l2_buf.field = V4L2_FIELD_NONE;
> +
> + for (i = 0; i < vb->num_planes; i++) {
> + size = fmt->plane_fmt[i].sizeimage;
> + vb2_set_plane_payload(vb, i, size);
> + }
> +
> + if (!buf->daddr)
> + buf->daddr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> + return 0;
> +}
> +
> +static void mtk_cam_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> + struct mtk_cam_dev *cam = vb2_get_drv_priv(vb->vb2_queue);
> + struct mtk_cam_dev_buffer *buf = to_mtk_cam_dev_buffer(vb);
> + unsigned long flags;
> +
> + /* Add the buffer into the tracking list */
> + spin_lock_irqsave(&cam->buf_list_lock, flags);
> + if (list_empty(&cam->buf_list))
> + (*cam->hw_functions->mtk_cam_update_buffers_add)(cam, buf);
> +
> + list_add_tail(&buf->list, &cam->buf_list);
> + (*cam->hw_functions->mtk_cam_fbc_inc)(cam);
> + spin_unlock_irqrestore(&cam->buf_list_lock, flags);
> +}
> +
> +static void mtk_cam_vb2_return_all_buffers(struct mtk_cam_dev *cam,
> + enum vb2_buffer_state state)
> +{
> + struct mtk_cam_dev_buffer *buf, *buf_prev;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&cam->buf_list_lock, flags);
> + list_for_each_entry_safe(buf, buf_prev, &cam->buf_list, list) {
> + buf->daddr = 0ULL;
> + list_del(&buf->list);
> + vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state);
> + }
> + spin_unlock_irqrestore(&cam->buf_list_lock, flags);
> +}
> +
> +static void mtk_cam_cmos_vf_enable(struct mtk_cam_dev *cam_dev,
> + bool enable, bool pak_en)
> +{
> + if (enable)
> + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_enable(cam_dev);
> + else
> + cam_dev->hw_functions->mtk_cam_cmos_vf_hw_disable(cam_dev);
> +}
> +
> +static int mtk_cam_verify_format(struct mtk_cam_dev *cam)
> +{
> + struct mtk_cam_video_device *vdev = &cam->vdev;
> + struct v4l2_subdev_format fmt = {
> + .which = V4L2_SUBDEV_FORMAT_ACTIVE,
> + .pad = MTK_CAM_CIO_PAD_VIDEO,
> + };
> + int ret;
> +
> + ret = v4l2_subdev_call(&cam->subdev, pad, get_fmt, NULL, &fmt);
> + if (ret < 0)
> + return ret == -ENOIOCTLCMD ? -EINVAL : ret;
> +
> + if (vdev->fmtinfo->code != fmt.format.code ||
> + vdev->format.height != fmt.format.height ||
> + vdev->format.width != fmt.format.width)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vb2_start_streaming(struct vb2_queue *vq,
> + unsigned int count)
> +{
> + struct mtk_cam_dev *cam = vb2_get_drv_priv(vq);
> + struct mtk_cam_dev_buffer *buf;
> + struct mtk_cam_video_device *vdev =
> + vb2_queue_to_mtk_cam_video_device(vq);
> + struct device *dev = cam->dev;
> + const struct v4l2_pix_format_mplane *fmt = &vdev->format;
> + int ret;
> + unsigned long flags;
> +
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret) {
> + dev_err(dev, "failed to get pm_runtime\n");
> + return ret;
> + }
> +
> + (*cam->hw_functions->mtk_cam_setup)(cam, fmt->width, fmt->height,
> + fmt->plane_fmt[0].bytesperline, vdev->fmtinfo->code);
> +
> + /* Enable CMOS and VF */
> + mtk_cam_cmos_vf_enable(cam, true, true);
> +
> + mutex_lock(&cam->op_lock);
It would be simpler to rely on the video device lock or the vb2 queue
lock.
> +
> + ret = mtk_cam_verify_format(cam);
This should be implemented as a .link_validate() operation on the video
device entity. See
https://lore.kernel.org/r/20240826124106.3823-8-laurent.pinchart+renesas@ideasonboard.com
for an example.
> + if (ret < 0)
> + goto fail_unlock;
> +
> + /* Start streaming of the whole pipeline now*/
s/now/now. /
> + if (!cam->pipeline.start_count) {
> + ret = media_pipeline_start(vdev->vdev.entity.pads,
> + &cam->pipeline);
Use video_device_pipeline_start() and video_device_pipeline_stop().
But this doesn't seem to be right. Why are you protecting this call with
a start_count check ? And why do you use stream_count below to check
when to stop ? Are you sure you understand the logic behind all this ?
Have you ever seen cam->stream_count exceed 1 ? If you need help
understanding all this, don't be shy and ask :-)
> + if (ret) {
> + dev_err(dev, "failed to start pipeline:%d\n", ret);
> + goto fail_unlock;
> + }
> + }
> +
> + /* Media links are fixed after media_pipeline_start */
> + cam->stream_count++;
> +
> + cam->sequence = (unsigned int)-1;
> +
> + /* Stream on the sub-device */
> + ret = v4l2_subdev_enable_streams(&cam->subdev,
> + cam->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].index,
> + BIT(0));
> + if (ret)
> + goto fail_no_stream;
> +
> + mutex_unlock(&cam->op_lock);
> +
> + /* Adding the buffer into the tracking list */
> + spin_lock_irqsave(&cam->buf_list_lock, flags);
> + buf = list_first_entry_or_null(&cam->buf_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + if (buf)
> + (*cam->hw_functions->mtk_cam_update_buffers_add)(cam, buf);
> + spin_unlock_irqrestore(&cam->buf_list_lock, flags);
> +
> + return 0;
> +
> +fail_no_stream:
> + cam->stream_count--;
> + if (cam->stream_count == 0)
> + media_pipeline_stop(vdev->vdev.entity.pads);
> +fail_unlock:
> + mutex_unlock(&cam->op_lock);
> + mtk_cam_vb2_return_all_buffers(cam, VB2_BUF_STATE_QUEUED);
> +
> + return ret;
> +}
> +
> +static void mtk_cam_vb2_stop_streaming(struct vb2_queue *vq)
> +{
> + struct mtk_cam_dev *cam = vb2_get_drv_priv(vq);
> + struct mtk_cam_video_device *vdev =
> + vb2_queue_to_mtk_cam_video_device(vq);
> +
> + /* Disable CMOS and VF */
> + mtk_cam_cmos_vf_enable(cam, false, false);
> +
> + mutex_lock(&cam->op_lock);
> +
> + v4l2_subdev_disable_streams(&cam->subdev,
> + cam->subdev_pads[MTK_CAM_CIO_PAD_VIDEO].index,
> + BIT(0));
> +
> + mtk_cam_vb2_return_all_buffers(cam, VB2_BUF_STATE_ERROR);
> + cam->stream_count--;
> + if (cam->stream_count) {
> + mutex_unlock(&cam->op_lock);
> + return;
> + }
> +
> + mutex_unlock(&cam->op_lock);
> +
> + media_pipeline_stop(vdev->vdev.entity.pads);
> +}
> +
> +static const struct vb2_ops mtk_cam_vb2_ops = {
> + .queue_setup = mtk_cam_vb2_queue_setup,
> + .buf_prepare = mtk_cam_vb2_buf_prepare,
> + .buf_queue = mtk_cam_vb2_buf_queue,
> + .start_streaming = mtk_cam_vb2_start_streaming,
> + .stop_streaming = mtk_cam_vb2_stop_streaming,
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 Video IOCTLs
> + */
> +
> +static int mtk_cam_vidioc_querycap(struct file *file, void *fh,
> + struct v4l2_capability *cap)
> +{
> + struct mtk_cam_dev *cam = video_drvdata(file);
> +
> + strscpy(cap->driver, dev_driver_string(cam->dev), sizeof(cap->driver));
> + strscpy(cap->card, dev_driver_string(cam->dev), sizeof(cap->card));
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_enum_fmt(struct file *file, void *fh,
> + struct v4l2_fmtdesc *f)
> +{
> + struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
> + const struct mtk_cam_format_info *fmtinfo;
> + unsigned int i;
> +
> + /* If mbus_code is not set enumerate all supported formats. */
> + if (!f->mbus_code) {
> + if (f->index >= vdev->desc->num_fmts)
> + return -EINVAL;
> +
> + /* f->description is filled in v4l_fill_fmtdesc function */
> + f->pixelformat = vdev->desc->fmts[f->index];
> + f->flags = 0;
> +
> + return 0;
> + }
> +
> + /*
> + * Otherwise only enumerate supported pixel formats corresponding to
> + * that bus code.
> + */
> + if (f->index)
> + return -EINVAL;
> +
> + fmtinfo = mtk_cam_format_info_by_code(f->mbus_code);
> + if (!fmtinfo)
> + return -EINVAL;
> +
> + for (i = 0; i < vdev->desc->num_fmts; ++i) {
> + if (vdev->desc->fmts[i] == fmtinfo->fourcc) {
> + f->pixelformat = fmtinfo->fourcc;
> + f->flags = 0;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int mtk_cam_vidioc_g_fmt(struct file *file, void *fh,
> + struct v4l2_format *f)
> +{
> + struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
> +
> + f->fmt.pix_mp = vdev->format;
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_try_fmt(struct file *file, void *fh,
> + struct v4l2_format *f)
> +{
> + struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
> + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> + const struct mtk_cam_format_info *fmtinfo;
> +
> + /* Validate pixelformat */
> + if (!mtk_cam_dev_find_fmt(vdev->desc, pix_mp->pixelformat))
> + pix_mp->pixelformat = vdev->desc->fmts[0];
> +
> + pix_mp->width = clamp_val(pix_mp->width, IMG_MIN_WIDTH, IMG_MAX_WIDTH);
> + pix_mp->height = clamp_val(pix_mp->height, IMG_MIN_HEIGHT,
> + IMG_MAX_HEIGHT);
> +
> + pix_mp->num_planes = 1;
> +
> + fmtinfo = mtk_cam_format_info_by_fourcc(pix_mp->pixelformat);
> + calc_bpl_size_pix_mp(fmtinfo, pix_mp);
> +
> + /* Constant format fields */
> + pix_mp->colorspace = V4L2_COLORSPACE_SRGB;
> + pix_mp->field = V4L2_FIELD_NONE;
> + pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + pix_mp->quantization = V4L2_QUANTIZATION_DEFAULT;
> + pix_mp->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_s_fmt(struct file *file, void *fh,
> + struct v4l2_format *f)
> +{
> + struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
> + int ret;
> +
> + if (vb2_is_busy(vdev->vdev.queue))
> + return -EBUSY;
> +
> + ret = mtk_cam_vidioc_try_fmt(file, fh, f);
> + if (ret)
> + return ret;
> +
> + /* Configure to video device */
> + vdev->format = f->fmt.pix_mp;
> + vdev->fmtinfo =
> + mtk_cam_format_info_by_fourcc(f->fmt.pix_mp.pixelformat);
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_enum_framesizes(struct file *file, void *priv,
> + struct v4l2_frmsizeenum *sizes)
> +{
> + struct mtk_cam_video_device *vdev = file_to_mtk_cam_video_device(file);
> +
> + if (sizes->index)
> + return -EINVAL;
> +
> + if (!mtk_cam_dev_find_fmt(vdev->desc, sizes->pixel_format))
> + return -EINVAL;
> +
> + sizes->type = vdev->desc->frmsizes->type;
> + memcpy(&sizes->stepwise, &vdev->desc->frmsizes->stepwise,
> + sizeof(sizes->stepwise));
> +
> + return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops mtk_cam_v4l2_vcap_ioctl_ops = {
> + .vidioc_querycap = mtk_cam_vidioc_querycap,
> + .vidioc_enum_framesizes = mtk_cam_vidioc_enum_framesizes,
> + .vidioc_enum_fmt_vid_cap = mtk_cam_vidioc_enum_fmt,
> + .vidioc_g_fmt_vid_cap_mplane = mtk_cam_vidioc_g_fmt,
> + .vidioc_s_fmt_vid_cap_mplane = mtk_cam_vidioc_s_fmt,
> + .vidioc_try_fmt_vid_cap_mplane = mtk_cam_vidioc_try_fmt,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations mtk_cam_v4l2_fops = {
> + .unlocked_ioctl = video_ioctl2,
> + .open = v4l2_fh_open,
> + .release = vb2_fop_release,
> + .poll = vb2_fop_poll,
> + .mmap = vb2_fop_mmap,
> +#ifdef CONFIG_COMPAT
> + .compat_ioctl32 = v4l2_compat_ioctl32,
> +#endif
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Init & Cleanup
> + */
> +
> +static const u32 stream_out_fmts[] = {
> + /* The 1st entry is the default image format */
> + V4L2_PIX_FMT_SBGGR8,
> + V4L2_PIX_FMT_SGBRG8,
> + V4L2_PIX_FMT_SGRBG8,
> + V4L2_PIX_FMT_SRGGB8,
> + V4L2_PIX_FMT_UYVY,
> + V4L2_PIX_FMT_VYUY,
> + V4L2_PIX_FMT_YUYV,
> + V4L2_PIX_FMT_YVYU,
> +};
> +
> +static const struct mtk_cam_vdev_desc video_stream = {
> + .fmts = stream_out_fmts,
> + .num_fmts = ARRAY_SIZE(stream_out_fmts),
> + .frmsizes =
> + &(struct v4l2_frmsizeenum){
> + .index = 0,
> + .type = V4L2_FRMSIZE_TYPE_CONTINUOUS,
> + .stepwise = {
> + .max_width = IMG_MAX_WIDTH,
> + .min_width = IMG_MIN_WIDTH,
> + .max_height = IMG_MAX_HEIGHT,
> + .min_height = IMG_MIN_HEIGHT,
> + .step_height = 1,
> + .step_width = 1,
> + },
> + },
> +};
> +
> +int mtk_cam_video_register(struct mtk_cam_dev *cam)
> +{
> + struct device *dev = cam->dev;
> + struct mtk_cam_video_device *cam_vdev = &cam->vdev;
> + struct video_device *vdev = &cam_vdev->vdev;
> + struct vb2_queue *vbq = &cam_vdev->vbq;
> + int ret;
> +
> + vb2_dma_contig_set_max_seg_size(cam->dev, DMA_BIT_MASK(32));
> +
> + cam_vdev->desc = &video_stream;
> +
> + /* Initialize mtk_cam_video_device */
> + mtk_cam_dev_load_default_fmt(cam);
> +
> + cam_vdev->vdev_pad.flags = MEDIA_PAD_FL_SOURCE;
> +
> + /* Initialize media entities */
> + ret = media_entity_pads_init(&vdev->entity, 1, &cam_vdev->vdev_pad);
> + if (ret) {
> + dev_err(dev, "failed to initialize media pad:%d\n", ret);
> + return ret;
> + }
> + cam_vdev->vdev_pad.flags = MEDIA_PAD_FL_SINK;
> +
> + vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> + vbq->io_modes = VB2_MMAP | VB2_DMABUF;
> + vbq->dev = dev;
> + vbq->ops = &mtk_cam_vb2_ops;
> + vbq->mem_ops = &vb2_dma_contig_memops;
> + vbq->buf_struct_size = sizeof(struct mtk_cam_dev_buffer);
> + /*
> + * TODO: The hardware supports SOF interrupts, switch to a SOF
> + * timestamp source would give better accuracy, but first requires
> + * extending the V4L2 API to support it.
> + */
> + vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
> + | V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
> +
> + /* No minimum buffers limitation */
> + vbq->min_queued_buffers = 0;
> + vbq->drv_priv = cam;
> +
> + vbq->lock = &cam_vdev->vdev_lock;
> + ret = vb2_queue_init(vbq);
> + if (ret) {
> + dev_err(dev, "failed to init. vb2 queue:%d\n", ret);
> + goto fail_media_clean;
> + }
> +
> + /* Initialize vdev */
> + snprintf(vdev->name, sizeof(vdev->name), "%s video stream",
> + dev_name(dev));
> +
> + /* Set cap/type/ioctl_ops of the video device */
> + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING
> + | V4L2_CAP_IO_MC;
> + vdev->ioctl_ops = &mtk_cam_v4l2_vcap_ioctl_ops;
> + vdev->fops = &mtk_cam_v4l2_fops;
> + vdev->release = video_device_release_empty;
> + vdev->lock = &cam_vdev->vdev_lock;
> + vdev->v4l2_dev = cam->subdev.v4l2_dev;
> + vdev->queue = &cam_vdev->vbq;
> + vdev->vfl_dir = VFL_DIR_RX;
> + vdev->entity.function = MEDIA_ENT_F_IO_V4L;
> + video_set_drvdata(vdev, cam);
> +
> + /* Initialize miscellaneous variables */
> + mutex_init(&cam_vdev->vdev_lock);
> + INIT_LIST_HEAD(&cam->buf_list);
> +
> + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> + if (ret) {
> + dev_err(dev, "failed to register vde:%d\n", ret);
> + goto fail_vb2_rel;
> + }
> +
> + /* Create link between the video pad and the subdev pad. */
> + ret = media_create_pad_link(&cam->subdev.entity,
> + MTK_CAM_CIO_PAD_VIDEO,
> + &vdev->entity, 0,
> + MEDIA_LNK_FL_IMMUTABLE
> + | MEDIA_LNK_FL_ENABLED);
> + if (ret)
> + goto fail_vdev_ureg;
> +
> + return 0;
> +
> +fail_vdev_ureg:
> + video_unregister_device(vdev);
> +fail_vb2_rel:
> + mutex_destroy(&cam_vdev->vdev_lock);
> + vb2_queue_release(vbq);
> +fail_media_clean:
> + media_entity_cleanup(&vdev->entity);
> +
> + return ret;
> +}
> +
> +void mtk_cam_video_unregister(struct mtk_cam_video_device *vdev)
> +{
> + video_unregister_device(&vdev->vdev);
> + vb2_queue_release(&vdev->vbq);
> + media_entity_cleanup(&vdev->vdev.entity);
> + mutex_destroy(&vdev->vdev_lock);
> + vb2_dma_contig_clear_max_seg_size(&vdev->vdev.dev);
> +}
>
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-25 10:22 ` Laurent Pinchart
@ 2024-11-26 1:08 ` CK Hu (胡俊光)
0 siblings, 0 replies; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-26 1:08 UTC (permalink / raw)
To: laurent.pinchart@ideasonboard.com
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, mchehab@kernel.org,
conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓),
linux-arm-kernel@lists.infradead.org, Julien Stephan,
matthias.bgg@gmail.com, krzk+dt@kernel.org,
fsylvestre@baylibre.com, AngeloGioacchino Del Regno
On Mon, 2024-11-25 at 12:22 +0200, Laurent Pinchart wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> On Mon, Nov 25, 2024 at 09:56:54AM +0000, CK Hu (胡俊光) wrote:
> > On Mon, 2024-11-25 at 11:39 +0200, Laurent Pinchart wrote:
> > > On Mon, Nov 25, 2024 at 06:59:59AM +0000, CK Hu (胡俊光) wrote:
> > > > On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> > > > >
> > > > > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > >
> > > > > This driver provides a path to bypass the SoC ISP so that image data
> > > > > coming from the SENINF can go directly into memory without any image
> > > > > processing. This allows the use of an external ISP.
> > > > >
> > > > > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > > > > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > > > > [Paul Elder fix irq locking]
> > > > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > > > > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > > > > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > > > > ---
> > > >
> > > > [snip]
> > > >
> > > > > +static const struct mtk_cam_conf camsv30_conf = {
> > > > > + .tg_sen_mode = 0x00010002U, /* TIME_STP_EN = 1. DBL_DATA_BUS = 1 */
> > > > > + .module_en = 0x40000001U, /* enable double buffer and TG */
> > > > > + .imgo_con = 0x80000080U, /* DMA FIFO depth and burst */
> > > > > + .imgo_con2 = 0x00020002U, /* DMA priority */
> > > >
> > > > Now support only one SoC, so it's not necessary make these SoC variable.
> > > > They could be constant symbol now.
> > >
> > > This I would keep as a mtk_cam_conf structure instance, as I think it
> > > makes it clear what we consider to be model-specific without hindering
> > > readability. I don't have a very strong opinion though.
> >
> > If this is a configuration table, I would like it to be
> >
> > {
> > .time_stp_en = true;
> > .dbl_data_bus = 1;
> > .double_buffer_en = true;
> > .tg = 0x4;
> > ...
> > }
>
> I like that too, it's more readable than raw register values.
>
> > If next SoC has only one parameter is different, we duplicate all
> > other parameter?
>
> That's what we usually do when the amount of parameters is not too
> large. When it becomes larger, we sometimes split the configuration data
> in multiple chunks. For instance,
>
> static const char * const family_a_clks[] = {
> "core",
> "io",
> };
>
> static sont char * const family_b_clks[] = {
> "main",
> "ram",
> "bus",
> };
>
> static const foo_dev_info soc_1_info = {
> .has_time_machine = false,
> .clks = family_a_clks,
> .num_clks = ARRAY_SIZE(family_a_clks),
> };
>
> static const foo_dev_info soc_2_info = {
> .has_time_machine = false,
> .clks = family_b_clks,
> .num_clks = ARRAY_SIZE(family_b_clks),
> };
>
> static const foo_dev_info soc_3_info = {
> .has_time_machine = true,
> .clks = family_b_clks,
> .num_clks = ARRAY_SIZE(family_b_clks),
> };
>
> There's no clear rule, it's on a case-by-case basis.
OK. That's fine for me.
Regards,
CK
>
> > > > > +};
> > > > > +
>
> --
> Regards,
>
> Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
` (8 preceding siblings ...)
2024-11-25 20:33 ` Laurent Pinchart
@ 2024-11-26 2:07 ` CK Hu (胡俊光)
2024-11-26 8:43 ` Laurent Pinchart
2024-11-26 3:40 ` CK Hu (胡俊光)
10 siblings, 1 reply; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-26 2:07 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
[snip]
> +static const u32 mtk_cam_mbus_formats[] = {
> + MEDIA_BUS_FMT_SBGGR8_1X8,
> + MEDIA_BUS_FMT_SGBRG8_1X8,
> + MEDIA_BUS_FMT_SGRBG8_1X8,
> + MEDIA_BUS_FMT_SRGGB8_1X8,
> + MEDIA_BUS_FMT_SBGGR10_1X10,
> + MEDIA_BUS_FMT_SGBRG10_1X10,
> + MEDIA_BUS_FMT_SGRBG10_1X10,
> + MEDIA_BUS_FMT_SRGGB10_1X10,
> + MEDIA_BUS_FMT_SBGGR12_1X12,
> + MEDIA_BUS_FMT_SGBRG12_1X12,
> + MEDIA_BUS_FMT_SGRBG12_1X12,
> + MEDIA_BUS_FMT_SRGGB12_1X12,
> + MEDIA_BUS_FMT_UYVY8_1X16,
> + MEDIA_BUS_FMT_VYUY8_1X16,
> + MEDIA_BUS_FMT_YUYV8_1X16,
> + MEDIA_BUS_FMT_YVYU8_1X16,
> +};
> +
Format in mtk_cam_mbus_formats[] is more than mtk_cam_format_info[].
I would like these two to be consistent.
Reduce mtk_cam_mbus_formats[] or enlarge mtk_cam_format_info[].
Once these two are consistent, they could be merged into one array.
Regards,
CK
> +static const struct mtk_cam_format_info mtk_cam_format_info[] = {
> + {
> + .fourcc = V4L2_PIX_FMT_SBGGR8,
> + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .bpp = 8,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG8,
> + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .bpp = 8,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG8,
> + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .bpp = 8,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB8,
> + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .bpp = 8,
> + }, {
> + .fourcc = V4L2_PIX_FMT_YUYV,
> + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> + .bpp = 16,
> + }, {
> + .fourcc = V4L2_PIX_FMT_YVYU,
> + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> + .bpp = 16,
> + }, {
> + .fourcc = V4L2_PIX_FMT_UYVY,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .bpp = 16,
> + }, {
> + .fourcc = V4L2_PIX_FMT_VYUY,
> + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .bpp = 16,
> + },
> +};
> +
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
` (9 preceding siblings ...)
2024-11-26 2:07 ` CK Hu (胡俊光)
@ 2024-11-26 3:40 ` CK Hu (胡俊光)
10 siblings, 0 replies; 39+ messages in thread
From: CK Hu (胡俊光) @ 2024-11-26 3:40 UTC (permalink / raw)
To: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, laurent.pinchart@ideasonboard.com,
krzk+dt@kernel.org, AngeloGioacchino Del Regno
Cc: linux-kernel@vger.kernel.org, linux-mediatek@lists.infradead.org,
linux-media@vger.kernel.org, devicetree@vger.kernel.org,
paul.elder@ideasonboard.com, linux-arm-kernel@lists.infradead.org,
fsylvestre@baylibre.com, pnguyen@baylibre.com
Hi, Julien:
On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> External email : Please do not click links or open attachments until you have verified the sender or the content.
>
>
> From: Phi-bang Nguyen <pnguyen@baylibre.com>
>
> This driver provides a path to bypass the SoC ISP so that image data
> coming from the SENINF can go directly into memory without any image
> processing. This allows the use of an external ISP.
>
> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> [Paul Elder fix irq locking]
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> ---
> drivers/media/platform/mediatek/isp/Kconfig | 18 +
> drivers/media/platform/mediatek/isp/Makefile | 5 +
> drivers/media/platform/mediatek/isp/mtk_camsv.c | 275 ++++++++
It's easy for us to think the name 'mtk_camsv.c' is the entry code (probe function) of this driver.
It's not entry code and has only v4l2 related code in it.
Maybe need some suffix or merge this file with mtk_camsv_video.c.
Regards,
CK
> drivers/media/platform/mediatek/isp/mtk_camsv.h | 170 +++++
> .../media/platform/mediatek/isp/mtk_camsv30_hw.c | 539 ++++++++++++++++
> .../media/platform/mediatek/isp/mtk_camsv_video.c | 701 +++++++++++++++++++++
> 6 files changed, 1708 insertions(+)
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv
2024-11-26 2:07 ` CK Hu (胡俊光)
@ 2024-11-26 8:43 ` Laurent Pinchart
0 siblings, 0 replies; 39+ messages in thread
From: Laurent Pinchart @ 2024-11-26 8:43 UTC (permalink / raw)
To: CK Hu (胡俊光)
Cc: mchehab@kernel.org, conor+dt@kernel.org, robh@kernel.org,
Andy Hsieh (謝智皓), Julien Stephan,
matthias.bgg@gmail.com, krzk+dt@kernel.org,
AngeloGioacchino Del Regno, linux-kernel@vger.kernel.org,
linux-mediatek@lists.infradead.org, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, paul.elder@ideasonboard.com,
linux-arm-kernel@lists.infradead.org, fsylvestre@baylibre.com,
pnguyen@baylibre.com
On Tue, Nov 26, 2024 at 02:07:35AM +0000, CK Hu (胡俊光) wrote:
> On Thu, 2024-11-21 at 09:53 +0100, Julien Stephan wrote:
> >
> > From: Phi-bang Nguyen <pnguyen@baylibre.com>
> >
> > This driver provides a path to bypass the SoC ISP so that image data
> > coming from the SENINF can go directly into memory without any image
> > processing. This allows the use of an external ISP.
> >
> > Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com>
> > Signed-off-by: Florian Sylvestre <fsylvestre@baylibre.com>
> > [Paul Elder fix irq locking]
> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> > Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > Co-developed-by: Julien Stephan <jstephan@baylibre.com>
> > Signed-off-by: Julien Stephan <jstephan@baylibre.com>
> > ---
>
> [snip]
>
> > +static const u32 mtk_cam_mbus_formats[] = {
> > + MEDIA_BUS_FMT_SBGGR8_1X8,
> > + MEDIA_BUS_FMT_SGBRG8_1X8,
> > + MEDIA_BUS_FMT_SGRBG8_1X8,
> > + MEDIA_BUS_FMT_SRGGB8_1X8,
> > + MEDIA_BUS_FMT_SBGGR10_1X10,
> > + MEDIA_BUS_FMT_SGBRG10_1X10,
> > + MEDIA_BUS_FMT_SGRBG10_1X10,
> > + MEDIA_BUS_FMT_SRGGB10_1X10,
> > + MEDIA_BUS_FMT_SBGGR12_1X12,
> > + MEDIA_BUS_FMT_SGBRG12_1X12,
> > + MEDIA_BUS_FMT_SGRBG12_1X12,
> > + MEDIA_BUS_FMT_SRGGB12_1X12,
> > + MEDIA_BUS_FMT_UYVY8_1X16,
> > + MEDIA_BUS_FMT_VYUY8_1X16,
> > + MEDIA_BUS_FMT_YUYV8_1X16,
> > + MEDIA_BUS_FMT_YVYU8_1X16,
> > +};
> > +
>
> Format in mtk_cam_mbus_formats[] is more than mtk_cam_format_info[].
> I would like these two to be consistent.
> Reduce mtk_cam_mbus_formats[] or enlarge mtk_cam_format_info[].
> Once these two are consistent, they could be merged into one array.
And the array could then be extended with fields to replace the
fmt_to_sparams() function.
> > +static const struct mtk_cam_format_info mtk_cam_format_info[] = {
> > + {
> > + .fourcc = V4L2_PIX_FMT_SBGGR8,
> > + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> > + .bpp = 8,
> > + }, {
> > + .fourcc = V4L2_PIX_FMT_SGBRG8,
> > + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> > + .bpp = 8,
> > + }, {
> > + .fourcc = V4L2_PIX_FMT_SGRBG8,
> > + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> > + .bpp = 8,
> > + }, {
> > + .fourcc = V4L2_PIX_FMT_SRGGB8,
> > + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> > + .bpp = 8,
> > + }, {
> > + .fourcc = V4L2_PIX_FMT_YUYV,
> > + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> > + .bpp = 16,
> > + }, {
> > + .fourcc = V4L2_PIX_FMT_YVYU,
> > + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> > + .bpp = 16,
> > + }, {
> > + .fourcc = V4L2_PIX_FMT_UYVY,
> > + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> > + .bpp = 16,
> > + }, {
> > + .fourcc = V4L2_PIX_FMT_VYUY,
> > + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> > + .bpp = 16,
> > + },
> > +};
> > +
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v7 3/5] media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface
2024-11-25 17:33 ` Laurent Pinchart
2024-11-25 19:45 ` Laurent Pinchart
@ 2025-01-22 14:04 ` Julien Stephan
1 sibling, 0 replies; 39+ messages in thread
From: Julien Stephan @ 2025-01-22 14:04 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Andy Hsieh, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, linux-media, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, Louis Kuo, Phi-bang Nguyen,
Florian Sylvestre
Le lun. 25 nov. 2024 à 18:33, Laurent Pinchart
<laurent.pinchart@ideasonboard.com> a écrit :
[ ...]
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> > + CK_SEL_1, input->bus.clock_lane);
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> > + CK_SEL_2, 2);
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI0,
> > + PHY_SENINF_LANE_MUX_CSI0_EN, 1);
>
> In the review of v6, I wrote that you're reading and writing the same
> register 4 times to set 4 different fields. This could be replaced by a
> single register access. I seem to recall that this was needed, and that
> writing the whole register in one go didn't produce the desired
> behaviour, at least for some registers. Is that right ?
>
> It would be nice to improve this where possible, here and everywhere
> else in the driver. I won't make it a blocker, but I really dislike this
> pattern :-(
>
Hi Laurent,
I just sent a V8, which already contains a lot of changes, so I
decided to postpone this for a potential v9 to ease review of v8!
Cheers
Julien
> > + }
> > +
> > + /* CSI1 */
> > + if (priv->inputs[CSI_PORT_1].phy) {
> > + const struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_1];
> > +
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> > + DPHY_MODE, 0 /* 4D1C */);
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> > + CK_SEL_1, input->bus.clock_lane);
> > + mtk_seninf_update(priv, SENINF_TOP_PHY_SENINF_CTL_CSI1,
> > + PHY_SENINF_LANE_MUX_CSI1_EN, 1);
> > + }
> > +}
> > +
> > +static void mtk_seninf_input_setup_csi2_rx(struct mtk_seninf_input *input)
> > +{
> > + unsigned int lanes[MTK_CSI_MAX_LANES] = { };
> > + unsigned int i;
> > +
> > + /*
> > + * Configure data lane muxing. In 2D1C mode, lanes 0 to 2 correspond to
> > + * CSIx[AB]_L{0,1,2}, and in 4D1C lanes 0 to 5 correspond to
> > + * CSIxA_L{0,1,2}, CSIxB_L{0,1,2}.
> > + *
> > + * The clock lane must be skipped when calculating the index of the
> > + * physical data lane. For instance, in 4D1C mode, the sensor clock
> > + * lane is typically connected to lane 2 (CSIxA_L2), and the sensor
> > + * data lanes 0-3 to lanes 1 (CSIxA_L1), 3 (CSIxB_L0), 0 (CSIxA_L0) and
> > + * 4 (CSIxB_L1). The when skipping the clock lane, the data lane
> > + * indices become 1, 2, 0 and 3.
> > + */
> > + for (i = 0; i < input->bus.num_data_lanes; ++i) {
> > + lanes[i] = input->bus.data_lanes[i];
> > + if (lanes[i] > input->bus.clock_lane)
> > + lanes[i]--;
> > + }
> > +
> > + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> > + CSI0_BIST_LN0_MUX, lanes[0]);
> > + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> > + CSI0_BIST_LN1_MUX, lanes[1]);
> > + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> > + CSI0_BIST_LN2_MUX, lanes[2]);
> > + mtk_seninf_input_update(input, MIPI_RX_CON24_CSI0,
> > + CSI0_BIST_LN3_MUX, lanes[3]);
> > +}
> > +
> > +static s64 mtk_seninf_get_clk_divider(struct mtk_seninf *priv,
> > + int pad_num,
>
> This holds on a single line.
>
> > + u8 bpp, unsigned int num_data_lanes)
> > +{
> > + struct media_entity *entity = &priv->subdev.entity;
> > + struct media_pad *pad;
> > + struct v4l2_subdev *sd;
> > + s64 link_frequency, pixel_clock;
> > +
> > +
> > + if (!(entity->pads[pad_num].flags & MEDIA_PAD_FL_SINK))
> > + return -ENODEV;
> > +
> > + pad = media_pad_remote_pad_first(&entity->pads[pad_num]);
> > + if (!pad)
> > + return -ENOENT;
> > +
> > + if (!is_media_entity_v4l2_subdev(pad->entity))
> > + return -ENOENT;
>
> As those conditions that can happen, wouldn't pipeline validation have
> failed ? If those conditions can't happen, then
> mtk_seninf_input_setup_csi2() and mtk_seninf_start() can become void
> functions.
>
> > +
> > + sd = media_entity_to_v4l2_subdev(pad->entity);
> > + link_frequency = v4l2_get_link_freq(sd->ctrl_handler, bpp,
> > + num_data_lanes * 2);
> > + pixel_clock = div_u64(link_frequency * 2 * num_data_lanes, bpp);
> > + /*
> > + * According to datasheet: Sensor master clock = ISP_clock/(CLKCNT +1)
> > + * we also have the following constraint:
> > + * pixel_clock >= Sensor master clock
> > + */
> > + return div_u64(clk_get_rate(priv->clks[0].clk), pixel_clock) - 1;
> > +}
> > +
> > +static int mtk_seninf_input_setup_csi2(struct mtk_seninf *priv,
> > + struct mtk_seninf_input *input,
> > + struct v4l2_subdev_state *state)
> > +{
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + const struct v4l2_mbus_framefmt *format;
> > + unsigned int num_data_lanes = input->bus.num_data_lanes;
> > + unsigned int val = 0;
> > + s64 clock_count;
> > +
> > + format = v4l2_subdev_state_get_format(state, input->pad, 0);
> > + fmtinfo = mtk_seninf_format_info(format->code);
> > +
> > + /* Configure timestamp */
> > + mtk_seninf_input_write(input, SENINF_TG1_TM_STP, SENINF_TIMESTAMP_STEP);
> > +
> > + /* HQ */
> > + /*
> > + * Configure phase counter. Zero means:
> > + * - Sensor master clock: ISP_CLK
> > + * - Sensor clock polarity: Rising edge
> > + * - Sensor reset deasserted
> > + * - Sensor powered up
> > + * - Pixel clock inversion disabled
> > + * - Sensor master clock polarity disabled
> > + * - Phase counter disabled
> > + */
> > + mtk_seninf_input_write(input, SENINF_TG1_PH_CNT, 0x0);
> > +
> > + clock_count = mtk_seninf_get_clk_divider(priv, input->pad,
> > + fmtinfo->bpp,
> > + num_data_lanes);
> > + if (clock_count < 0)
> > + return clock_count;
> > +
> > + clock_count = FIELD_PREP(SENINF_TG1_SEN_CK_CLKCNT, clock_count) | 0x1;
> > + mtk_seninf_input_write(input, SENINF_TG1_SEN_CK, clock_count);
> > +
> > + /* First Enable Sensor interface and select pad (0x1a04_0200) */
> > + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CTRL, PAD2CAM_DATA_SEL,
> > + SENINF_PAD_10BIT);
> > + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_SRC_SEL, 0);
> > + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_CSI2_IP_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_NCSI2_IP_EN, 0);
> > +
> > + /* DPCM Enable */
> > + if (fmtinfo->flags & MTK_SENINF_FORMAT_DPCM)
> > + val = SENINF_CSI2_DPCM_DI_2A_DPCM_EN;
> > + else
> > + val = SENINF_CSI2_DPCM_DI_30_DPCM_EN;
> > + mtk_seninf_input_write(input, SENINF_CSI2_DPCM, val);
> > +
> > + /* Settle delay */
> > + mtk_seninf_input_update(input, SENINF_CSI2_LNRD_TIMING,
> > + DATA_SETTLE_PARAMETER, SENINF_SETTLE_DELAY);
> > +
> > + /* CSI2 control */
> > + val = mtk_seninf_inuput_read(input, SENINF_CSI2_CTL)
> > + | (FIELD_PREP(SENINF_CSI2_CTL_ED_SEL,
> > + DATA_HEADER_ORDER_DI_WCL_WCH)
> > + | SENINF_CSI2_CTL_CLOCK_LANE_EN | (BIT(num_data_lanes) - 1));
> > + mtk_seninf_input_write(input, SENINF_CSI2_CTL, val);
> > +
> > + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> > + BYPASS_LANE_RESYNC, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> > + CDPHY_SEL, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_RESYNC_MERGE_CTL,
> > + CPHY_LANE_RESYNC_CNT, 3);
> > + mtk_seninf_input_update(input, SENINF_CSI2_MODE, CSR_CSI2_MODE, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_MODE, CSR_CSI2_HEADER_LEN,
> > + 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_DPHY_SYNC, SYNC_SEQ_MASK_0,
> > + 0xff00);
> > + mtk_seninf_input_update(input, SENINF_CSI2_DPHY_SYNC, SYNC_SEQ_PAT_0,
> > + 0x001d);
> > +
> > + mtk_seninf_input_update(input, SENINF_CSI2_CTL, CLOCK_HS_OPTION, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_CTL, HSRX_DET_EN, 0);
> > + mtk_seninf_input_update(input, SENINF_CSI2_CTL, HS_TRAIL_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CSI2_HS_TRAIL, HS_TRAIL_PARAMETER,
> > + SENINF_HS_TRAIL_PARAMETER);
> > +
> > + /* Set debug port to output packet number */
> > + mtk_seninf_input_update(input, SENINF_CSI2_DGB_SEL, DEBUG_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CSI2_DGB_SEL, DEBUG_SEL, 0x1a);
> > +
> > + /* HQ */
> > + mtk_seninf_input_write(input, SENINF_CSI2_SPARE0, 0xfffffffe);
> > +
> > + /* Reset the CSI2 to commit changes */
> > + mtk_seninf_input_update(input, SENINF_CTRL, CSI2_SW_RST, 1);
> > + udelay(1);
> > + mtk_seninf_input_update(input, SENINF_CTRL, CSI2_SW_RST, 0);
> > +
> > + return 0;
> > +}
> > +
> > +static void mtk_seninf_mux_setup(struct mtk_seninf_mux *mux,
> > + struct mtk_seninf_input *input,
> > + struct v4l2_subdev_state *state)
> > +{
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + const struct v4l2_mbus_framefmt *format;
> > + unsigned int pix_sel_ext;
> > + unsigned int pix_sel;
> > + unsigned int hs_pol = 0;
> > + unsigned int vs_pol = 0;
> > + unsigned int val;
> > + u32 rst_mask;
> > +
> > + format = v4l2_subdev_state_get_format(state, input->pad, 0);
> > + fmtinfo = mtk_seninf_format_info(format->code);
> > +
> > + /* Enable mux */
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_MUX_EN, 1);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_SRC_SEL,
> > + SENINF_MIPI_SENSOR);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_SRC_SEL_EXT,
> > + SENINF_NORMAL_MODEL);
> > +
> > + pix_sel_ext = 0;
> > + pix_sel = 1;
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_PIX_SEL_EXT,
> > + pix_sel_ext);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_PIX_SEL, pix_sel);
> > +
> > + if (fmtinfo->flags & MTK_SENINF_FORMAT_JPEG) {
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 0);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN,
> > + FIFO_FLUSH_EN_JPEG_2_PIXEL_MODE);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN,
> > + FIFO_PUSH_EN_JPEG_2_PIXEL_MODE);
> > + } else {
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 2);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN,
> > + FIFO_FLUSH_EN_NORMAL_MODE);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN,
> > + FIFO_PUSH_EN_NORMAL_MODE);
> > + }
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_POL, hs_pol);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_VSYNC_POL, vs_pol);
> > +
> > + val = mtk_seninf_mux_read(mux, SENINF_MUX_CTRL);
> > + rst_mask = SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> > + SENINF_MUX_CTRL_SENINF_MUX_SW_RST;
> > +
> > + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL, val | rst_mask);
> > + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL, val & ~rst_mask);
> > +
> > + /* HQ */
> > + val = SENINF_FIFO_FULL_SEL;
> > +
> > + /* SPARE field meaning is unknown */
> > + val |= 0xc0000;
> > + mtk_seninf_mux_write(mux, SENINF_MUX_SPARE, val);
> > +}
> > +
> > +static void mtk_seninf_top_mux_setup(struct mtk_seninf *priv,
> > + enum mtk_seninf_id seninf_id,
> > + struct mtk_seninf_mux *mux)
>
> mux can be const. Please constify pointer arguments when they don't have
> to be modified (both from a language point of view, and conceptually,
> for instance priv shouldn't be const as you're writing registers in this
> function, which modifies the state of the device)..
>
> > +{
> > + unsigned int val;
> > +
> > + /*
> > + * Use the top mux (from SENINF input to MUX) to configure routing, and
> > + * hardcode a 1:1 mapping from the MUX instances to the SENINF outputs.
> > + */
> > + val = mtk_seninf_read(priv, SENINF_TOP_MUX_CTRL)
> > + & ~(0xf << (mux->mux_id * 4));
> > + val |= (seninf_id & 0xf) << (mux->mux_id * 4);
> > + mtk_seninf_write(priv, SENINF_TOP_MUX_CTRL, val);
> > +
> > + /*
> > + * We currently support only seninf version 3.0
> > + * where camsv0 and camsv1 are hardwired respectively to
> > + * SENINF_CAM2 and SENINF_CAM3 i.e :
> > + * - SENINF_TOP_CAM_MUX_CTRL[11:8] = 0
> > + * - SENINF_TOP_CAM_MUX_CTRL[15:12] = 1
> > + * so we hardcode it here
> > + */
> > + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> > + SENINF_CAM2_MUX_SRC_SEL, 0);
> > + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> > + SENINF_CAM3_MUX_SRC_SEL, 1);
> > +}
> > +
> > +static void seninf_enable_test_pattern(struct mtk_seninf *priv,
> > + struct v4l2_subdev_state *state)
> > +{
> > + struct mtk_seninf_input *input = &priv->inputs[CSI_PORT_0];
> > + struct mtk_seninf_mux *mux = &priv->muxes[0];
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + const struct v4l2_mbus_framefmt *format;
> > + unsigned int val;
> > + unsigned int pix_sel_ext;
> > + unsigned int pix_sel;
> > + unsigned int hs_pol = 0;
> > + unsigned int vs_pol = 0;
> > + unsigned int seninf = 0;
> > + unsigned int tm_size = 0;
> > + unsigned int mux_id = mux->mux_id;
> > +
> > + format = v4l2_subdev_state_get_format(state, priv->conf->nb_inputs, 0);
> > + fmtinfo = mtk_seninf_format_info(format->code);
> > +
> > + mtk_seninf_update(priv, SENINF_TOP_CTRL, MUX_LP_MODE, 0);
> > +
> > + mtk_seninf_update(priv, SENINF_TOP_CTRL, SENINF_PCLK_EN, 1);
> > + mtk_seninf_update(priv, SENINF_TOP_CTRL, SENINF2_PCLK_EN, 1);
> > +
> > + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_CTRL, SENINF_SRC_SEL, 1);
> > + mtk_seninf_input_update(input, SENINF_CTRL_EXT, SENINF_TESTMDL_IP_EN,
> > + 1);
> > +
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_EN, 1);
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_PAT, 0xc);
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_VSYNC, 4);
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_DUMMYPXL, 0x28);
> > +
> > + if (fmtinfo->flags & MTK_SENINF_FORMAT_BAYER)
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_FMT, 0x0);
> > + else
> > + mtk_seninf_input_update(input, SENINF_TG1_TM_CTL, TM_FMT, 0x1);
> > +
> > + tm_size = FIELD_PREP(SENINF_TG1_TM_SIZE_TM_LINE, format->height + 8);
> > + switch (format->code) {
> > + case MEDIA_BUS_FMT_UYVY8_1X16:
> > + case MEDIA_BUS_FMT_VYUY8_1X16:
> > + case MEDIA_BUS_FMT_YUYV8_1X16:
> > + case MEDIA_BUS_FMT_YVYU8_1X16:
> > + tm_size |= FIELD_PREP(SENINF_TG1_TM_SIZE_TM_PXL,
> > + format->width * 2);
> > + break;
> > + default:
> > + tm_size |= FIELD_PREP(SENINF_TG1_TM_SIZE_TM_PXL, format->width);
> > + break;
> > + }
> > + mtk_seninf_input_write(input, SENINF_TG1_TM_SIZE, tm_size);
> > +
> > + mtk_seninf_input_write(input, SENINF_TG1_TM_CLK,
> > + TEST_MODEL_CLK_DIVIDED_CNT);
> > + mtk_seninf_input_write(input, SENINF_TG1_TM_STP, TIME_STAMP_DIVIDER);
> > +
> > + /* Set top mux */
> > + val = (mtk_seninf_read(priv, SENINF_TOP_MUX_CTRL)
> > + & (~(0xf << (mux_id * 4)))) |
> > + ((seninf & 0xf) << (mux_id * 4));
> > + mtk_seninf_write(priv, SENINF_TOP_MUX_CTRL, val);
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_MUX_EN, 1);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT, SENINF_SRC_SEL_EXT,
> > + SENINF_TEST_MODEL);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_SRC_SEL, 1);
> > +
> > + pix_sel_ext = 0;
> > + pix_sel = 1;
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL_EXT,
> > + SENINF_PIX_SEL_EXT, pix_sel_ext);
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_PIX_SEL, pix_sel);
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_PUSH_EN, 0x1f);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FLUSH_EN, 0x1b);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, FIFO_FULL_WR_EN, 2);
> > +
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_POL, hs_pol);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_VSYNC_POL, vs_pol);
> > + mtk_seninf_mux_update(mux, SENINF_MUX_CTRL, SENINF_HSYNC_MASK, 1);
> > +
> > + mtk_seninf_mux_write(mux, SENINF_MUX_INTEN,
> > + SENINF_IRQ_CLR_SEL | SENINF_ALL_ERR_IRQ_EN);
> > +
> > + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL,
> > + mtk_seninf_mux_read(mux, SENINF_MUX_CTRL) |
> > + SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> > + SENINF_MUX_CTRL_SENINF_MUX_SW_RST);
> > + udelay(1);
> > + mtk_seninf_mux_write(mux, SENINF_MUX_CTRL,
> > + mtk_seninf_mux_read(mux, SENINF_MUX_CTRL) &
> > + ~(SENINF_MUX_CTRL_SENINF_IRQ_SW_RST |
> > + SENINF_MUX_CTRL_SENINF_MUX_SW_RST));
> > +
> > + //check this
> > + mtk_seninf_write(priv, SENINF_TOP_CAM_MUX_CTRL, 0x76540010);
> > + /*
> > + * We currently support only seninf version 3.0
> > + * where camsv0 and camsv1 are hardwired respectively to
> > + * test pattern is valid only for seninf_1 (id 0) i.e :
>
> I'm having trouble parsing this sentence.
>
> > + * - SENINF_TOP_CAM_MUX_CTRL[11:8] = 0
> > + * - SENINF_TOP_CAM_MUX_CTRL[15:12] = 0
> > + * so we hardcode it here
> > + */
> > + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> > + SENINF_CAM2_MUX_SRC_SEL, 0);
> > + mtk_seninf_update(priv, SENINF_TOP_CAM_MUX_CTRL,
> > + SENINF_CAM3_MUX_SRC_SEL, 0);
>
> Here you're reconfiguring the whole TOP_CAM_MUX. Unless I'm mistaken, if
> you want to capture from one sensor and use the TPG for the other camsv,
> the configuration performed here and in mtk_seninf_top_mux_setup() will
> clash. Isn't that a problem ?
>
> > +}
> > +
> > +static int mtk_seninf_start(struct mtk_seninf *priv,
> > + struct v4l2_subdev_state *state,
> > + struct mtk_seninf_input *input,
> > + struct mtk_seninf_mux *mux)
> > +{
> > + int ret;
> > +
> > + phy_power_on(input->phy);
> > +
> > + mtk_seninf_input_setup_csi2_rx(input);
> > + ret = mtk_seninf_input_setup_csi2(priv, input, state);
> > + if (ret)
> > + return ret;
> > +
> > + mtk_seninf_mux_setup(mux, input, state);
> > + mtk_seninf_top_mux_setup(priv, input->seninf_id, mux);
> > + return 0;
> > +}
> > +
> > +static void mtk_seninf_stop(struct mtk_seninf *priv,
> > + struct mtk_seninf_input *input)
> > +{
> > + unsigned int val;
> > +
> > + /* Disable CSI2(2.5G) first */
> > + val = mtk_seninf_inuput_read(input, SENINF_CSI2_CTL);
> > + val &= ~(SENINF_CSI2_CTL_CLOCK_LANE_EN |
> > + SENINF_CSI2_CTL_DATA_LANE3_EN |
> > + SENINF_CSI2_CTL_DATA_LANE2_EN |
> > + SENINF_CSI2_CTL_DATA_LANE1_EN |
> > + SENINF_CSI2_CTL_DATA_LANE0_EN);
> > + mtk_seninf_input_write(input, SENINF_CSI2_CTL, val);
> > +
> > + if (!priv->is_testmode)
> > + phy_power_off(input->phy);
>
> What happens if userspace alls STREAMON with the TPG disabled, then
> enables the TPG, and calls STREAMOFF ? It looks like you should disable
> changing the TPG control while streaming. You can use
> v4l2_subdev_is_streaming() to check if the subdev is streaming, but
> you'll need to hold the active state lock.
>
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * V4L2 Controls
> > + */
> > +
> > +static int seninf_set_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > + struct mtk_seninf *priv = container_of(ctrl->handler,
> > + struct mtk_seninf, ctrl_handler);
> > +
> > + switch (ctrl->id) {
> > + case V4L2_CID_TEST_PATTERN:
> > + priv->is_testmode = !!ctrl->val;
> > + break;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops seninf_ctrl_ops = {
> > + .s_ctrl = seninf_set_ctrl,
> > +};
> > +
> > +static const char *const seninf_test_pattern_menu[] = {
> > + "No test pattern",
>
> Drivers normally use "Disabled" for this.
>
> > + "Static horizontal color bars",
>
> "Static Horizontal Color Bars",
>
> Not that it would matter too much, but menu entries usually use Camel
> Case, so let's be consistent.
>
> > +};
> > +
> > +static int seninf_initialize_controls(struct mtk_seninf *priv)
> > +{
> > + struct v4l2_ctrl_handler *handler;
> > + int ret;
> > +
> > + handler = &priv->ctrl_handler;
> > + ret = v4l2_ctrl_handler_init(handler, 2);
>
> The driver creates a single control.
>
> > + if (ret)
> > + return ret;
> > +
> > + v4l2_ctrl_new_std_menu_items(handler, &seninf_ctrl_ops,
> > + V4L2_CID_TEST_PATTERN,
> > + ARRAY_SIZE(seninf_test_pattern_menu) - 1,
> > + 0, 0, seninf_test_pattern_menu);
> > +
> > + priv->is_testmode = false;
> > +
> > + if (handler->error) {
> > + ret = handler->error;
> > + dev_err(priv->dev,
> > + "Failed to init controls(%d)\n", ret);
> > + v4l2_ctrl_handler_free(handler);
> > + return ret;
> > + }
> > +
> > + priv->subdev.ctrl_handler = handler;
> > +
> > + return 0;
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * V4L2 Subdev Operations
> > + */
>
> Missing blank line.
>
> > +static int seninf_enable_streams(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state, u32 pad,
> > + u64 streams_mask)
> > +{
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > + struct mtk_seninf_input *input;
> > + struct mtk_seninf_mux *mux;
> > + struct v4l2_subdev *source;
> > + u32 sink_pad;
> > + int ret;
> > +
> > + /* Stream control can only operate on source pads. */
> > + if (pad < priv->conf->nb_inputs ||
> > + pad >= priv->conf->nb_inputs + priv->conf->nb_outputs)
> > + return -EINVAL;
> > +
> > + /*
> > + * Locate the SENINF input and MUX for the source pad.
> > + */
>
> /* Locate the SENINF input and MUX for the source pad. */
>
> Same below.
>
> > +
> > + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad,
> > + 0, &sink_pad, NULL);
> > + if (ret) {
> > + dev_dbg(priv->dev, "No sink pad routed to source pad %u\n",
> > + pad);
> > + return ret;
> > + }
> > +
> > + input = &priv->inputs[sink_pad];
> > + mux = &priv->muxes[pad - priv->conf->nb_inputs];
> > +
> > + ret = pm_runtime_get_sync(priv->dev);
>
> Use pm_runtime_resume_and_get() and drop pm_runtime_put_noidle() in the
> error path.
>
> > + if (ret < 0) {
> > + dev_err(priv->dev, "Failed to pm_runtime_get_sync: %d\n", ret);
> > + pm_runtime_put_noidle(priv->dev);
> > + return ret;
> > + }
> > +
> > + /* If test mode is enabled, just enable the test pattern generator. */
> > + if (priv->is_testmode) {
> > + seninf_enable_test_pattern(priv, state);
> > + return 0;
> > + }
> > +
> > + /* Start the SENINF first and then the source. */
> > + ret = mtk_seninf_start(priv, state, input, mux);
> > + if (ret) {
> > + dev_err(priv->dev, "failed to start seninf: %d\n", ret);
>
> Missing
>
> pm_runtime_put(priv->dev);
>
> I would move error handling to the bottom of the function, with gotos
> and error labels.
>
> > + return ret;
> > + }
> > +
> > + source = input->source_sd;
> > + ret = v4l2_subdev_call(source, video, s_stream, 1);
>
> Use v4l2_subdev_enable_streams().
>
> > + if (ret) {
> > + dev_err(priv->dev, "failed to start source %s: %d\n",
> > + source->entity.name, ret);
> > + mtk_seninf_stop(priv, input);
> > + pm_runtime_put(priv->dev);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int seninf_disable_streams(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state, u32 pad,
> > + u64 streams_mask)
> > +{
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > + struct mtk_seninf_input *input;
> > + struct mtk_seninf_mux *mux;
> > + struct v4l2_subdev *source;
> > + u32 sink_pad;
> > + int ret;
> > +
> > + /* Stream control can only operate on source pads. */
> > + if (pad < priv->conf->nb_inputs ||
> > + pad >= priv->conf->nb_inputs + priv->conf->nb_outputs)
> > + return -EINVAL;
> > +
> > + /*
> > + * Locate the SENINF input and MUX for the source pad.
> > + *
> > + */
> > +
> > + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad,
> > + 0, &sink_pad, NULL);
> > + if (ret) {
> > + dev_dbg(priv->dev, "No sink pad routed to source pad %u\n",
> > + pad);
> > + return ret;
> > + }
> > +
> > + input = &priv->inputs[sink_pad];
> > + mux = &priv->muxes[pad - priv->conf->nb_inputs];
> > +
> > + if (!priv->is_testmode) {
> > + source = input->source_sd;
> > + ret = v4l2_subdev_call(source, video, s_stream, 0);
>
> Use v4l2_subdev_disable_streams().
>
> > + if (ret)
> > + dev_err(priv->dev,
> > + "failed to stop source %s: %d\n",
> > + source->entity.name, ret);
> > + }
> > +
> > + mtk_seninf_stop(priv, input);
> > + pm_runtime_put(priv->dev);
> > + return ret;
> > +}
> > +
> > +static const struct v4l2_mbus_framefmt mtk_seninf_default_fmt = {
> > + .code = SENINF_DEFAULT_BUS_FMT,
> > + .width = SENINF_DEFAULT_WIDTH,
> > + .height = SENINF_DEFAULT_HEIGHT,
> > + .field = V4L2_FIELD_NONE,
> > + .colorspace = V4L2_COLORSPACE_SRGB,
> > + .xfer_func = V4L2_XFER_FUNC_DEFAULT,
> > + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
> > + .quantization = V4L2_QUANTIZATION_DEFAULT,
> > +};
> > +
> > +static int __seninf_set_routing(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_krouting *routing)
> > +{
> > + int ret;
> > +
> > + 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,
> > + &mtk_seninf_default_fmt);
> > +}
> > +
> > +static int seninf_init_state(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state)
> > +{
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > + struct v4l2_subdev_route routes[SENINF_MAX_NUM_OUTPUTS] = { };
> > + struct v4l2_subdev_krouting routing = {
> > + .routes = routes,
> > + .num_routes = priv->conf->nb_outputs,
> > + };
> > + unsigned int i;
> > +
> > + /*
> > + * Initialize one route for supported source pads.
> > + * It is a single route from the first sink pad to the source pad,
> > + * while on SENINF 5.0 the routing table will map sink pads to source
> > + * pads connected to CAMSV 1:1 (skipping the first two source pads
> > + * connected to the CAM instances).
> > + */
> > + for (i = 0; i < routing.num_routes; i++) {
> > + struct v4l2_subdev_route *route = &routes[i];
> > +
> > + route->sink_pad = i;
> > + route->sink_stream = 0;
> > + route->source_pad = priv->conf->nb_inputs + i;
> > + route->source_stream = 0;
> > + route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> > + }
> > +
> > + return __seninf_set_routing(sd, state, &routing);
> > +}
> > +
> > +static int seninf_enum_mbus_code(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > +
> > + if (code->index >= ARRAY_SIZE(mtk_seninf_formats))
> > + return -EINVAL;
> > +
> > + fmtinfo = &mtk_seninf_formats[code->index];
> > + if (fmtinfo->flags & MTK_SENINF_FORMAT_INPUT_ONLY &&
> > + mtk_seninf_pad_is_source(priv, code->pad))
> > + return -EINVAL;
> > +
> > + code->code = fmtinfo->code;
> > +
> > + return 0;
> > +}
> > +
> > +static int seninf_set_fmt(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_format *fmt)
> > +{
> > + struct mtk_seninf *priv = sd_to_mtk_seninf(sd);
> > + const struct mtk_seninf_format_info *fmtinfo;
> > + struct v4l2_mbus_framefmt *format;
> > +
> > + /*
> > + * TODO (?): We should disallow setting formats on the source pad
> > + * completely, as the SENINF can't perform any processing. This would
> > + * however break usage of the test pattern generator, as there would be
> > + * no way to configure formats at all when no active input is selected.
> > + */
>
> As commented in v6, I think this needs to be solved.
>
> > +
> > + /*
> > + * Default to the first format if the requested media bus code isn't
> > + * supported.
> > + */
> > + fmtinfo = mtk_seninf_format_info(fmt->format.code);
> > + if (!fmtinfo) {
> > + fmtinfo = &mtk_seninf_formats[0];
> > + fmt->format.code = fmtinfo->code;
> > + }
> > +
> > + /* Interlaced formats are not supported yet. */
> > + fmt->format.field = V4L2_FIELD_NONE;
> > +
> > + /* Store the format. */
> > + format = v4l2_subdev_state_get_format(state, fmt->pad, fmt->stream);
> > + if (!format)
> > + return -EINVAL;
> > +
> > + *format = fmt->format;
> > +
> > + if (mtk_seninf_pad_is_source(priv, fmt->pad))
> > + return 0;
> > +
> > + /* Propagate the format to the corresponding source pad. */
> > + format = v4l2_subdev_state_get_opposite_stream_format(state, fmt->pad,
> > + fmt->stream);
> > + if (!format)
> > + return -EINVAL;
> > +
> > + *format = fmt->format;
>
> Another comment from v6 that seems to have been lost:
>
> If fmtinfo is one of the INPUT_ONLY formats, the corresponding
> DPCM-uncompressed format must be set on the source pad. To facilitate
> this, you want need to add a .uncompressed field to the format info
> structure to store the corresponding uncompressed format.
>
> > +
> > + return 0;
> > +}
> > +
> > +static int seninf_set_routing(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + enum v4l2_subdev_format_whence which,
> > + struct v4l2_subdev_krouting *routing)
> > +{
> > + return __seninf_set_routing(sd, state, routing);
> > +}
> > +
> > +static const struct v4l2_subdev_core_ops seninf_subdev_core_ops = {
> > + .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> > + .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops seninf_subdev_pad_ops = {
> > + .enum_mbus_code = seninf_enum_mbus_code,
> > + .get_fmt = v4l2_subdev_get_fmt,
> > + .set_fmt = seninf_set_fmt,
> > + .link_validate = v4l2_subdev_link_validate_default,
> > + .set_routing = seninf_set_routing,
> > + .enable_streams = seninf_enable_streams,
> > + .disable_streams = seninf_disable_streams,
> > +};
> > +
> > +static const struct v4l2_subdev_ops seninf_subdev_ops = {
> > + .core = &seninf_subdev_core_ops,
> > + .pad = &seninf_subdev_pad_ops,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops seninf_subdev_internal_ops = {
> > + .init_state = seninf_init_state,
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Media Entity Operations
> > + */
> > +
> > +static const struct media_entity_operations seninf_media_ops = {
> > + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> > + .link_validate = v4l2_subdev_link_validate,
> > + .has_pad_interdep = v4l2_subdev_has_pad_interdep,
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Async Subdev Notifier
> > + */
> > +
> > +struct mtk_seninf_async_subdev {
> > + struct v4l2_async_connection asc;
> > + struct mtk_seninf_input *input;
> > + unsigned int port;
> > +};
> > +
> > +static int mtk_seninf_fwnode_parse(struct device *dev,
> > + unsigned int id)
>
> This holds on a single line.
>
> > +
>
>
> Extra blank line.
>
> I would move this function below mtk_seninf_async_ops as it's called
> directly in the probe path, before the bound and complete callbacks.
>
> > +{
> > + static const char * const phy_names[] = {
> > + "csi0", "csi1", "csi2", "csi0b" };
>
> static const char * const phy_names[] = {
> "csi0", "csi1", "csi2", "csi0b"
> };
>
> > + struct mtk_seninf *priv = dev_get_drvdata(dev);
> > + struct fwnode_handle *ep, *fwnode;
> > + struct mtk_seninf_input *input;
> > + struct mtk_seninf_async_subdev *asd;
> > + struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> > + unsigned int port;
> > + int ret;
> > +
> > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), id, 0, 0);
> > + if (!ep)
> > + return 0;
> > +
> > + fwnode = fwnode_graph_get_remote_endpoint(ep);
> > + ret = v4l2_fwnode_endpoint_parse(ep, &vep);
> > + if (ret) {
> > + dev_err(dev, "Failed to parse %p fw\n", to_of_node(fwnode));
>
> dev_err(dev, "Failed to parse %pfw\n", fwnode);
>
> > + ret = -EINVAL;
> > + goto out;
> > + }
> > +
> > + asd = v4l2_async_nf_add_fwnode(&priv->notifier, fwnode,
> > + struct mtk_seninf_async_subdev);
> > + if (IS_ERR(asd)) {
> > + ret = PTR_ERR(asd);
> > + goto out;
> > + }
> > +
> > + port = vep.base.port;
> > + asd->port = port;
> > +
> > + if (mtk_seninf_pad_is_source(priv, port)) {
> > + ret = 0;
> > + goto out;
> > + }
> > +
> > + input = &priv->inputs[port];
> > +
> > + input->pad = port;
> > + input->seninf_id = port_to_seninf_id[port];
> > + input->base = priv->base + 0x1000 * input->seninf_id;
> > + input->seninf = priv;
> > +
> > + input->bus = vep.bus.mipi_csi2;
> > +
> > + input->phy = devm_phy_get(dev, phy_names[port]);
> > + if (IS_ERR(input->phy)) {
> > + dev_err(dev, "failed to get phy: %ld\n", PTR_ERR(input->phy));
> > + ret = PTR_ERR(input->phy);
> > + goto out;
> > + }
> > + input->phy_mode = SENINF_PHY_MODE_4D1C;
> > +
> > + asd->input = input;
> > +
> > + ret = 0;
> > +out:
> > + fwnode_handle_put(ep);
> > + fwnode_handle_put(fwnode);
> > + return ret;
> > +}
> > +
> > +static int mtk_seninf_notifier_bound(struct v4l2_async_notifier *notifier,
> > + struct v4l2_subdev *sd,
> > + struct v4l2_async_connection *asc)
> > +{
> > + struct mtk_seninf *priv = container_of(notifier, struct mtk_seninf,
> > + notifier);
> > + struct mtk_seninf_async_subdev *asd =
> > + container_of(asc, struct mtk_seninf_async_subdev, asc);
> > + struct device_link *link;
> > + int ret;
> > +
> > + dev_dbg(priv->dev, "%s bound to SENINF port %u\n", sd->entity.name,
> > + asd->port);
> > +
> > + if (mtk_seninf_pad_is_sink(priv, asd->port)) {
> > + struct mtk_seninf_input *input = asd->input;
> > +
> > + input->source_sd = sd;
> > +
> > + link = device_link_add(priv->dev, sd->dev,
> > + DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
> > + if (!link) {
> > + dev_err(priv->dev,
> > + "Failed to create device link from source %s\n",
> > + sd->name);
> > + return -EINVAL;
> > + }
> > +
> > + ret = v4l2_create_fwnode_links_to_pad(sd,
> > + &priv->pads[input->pad],
> > + MEDIA_LNK_FL_IMMUTABLE |
> > + MEDIA_LNK_FL_ENABLED);
> > + } else {
> > + link = device_link_add(sd->dev, priv->dev,
> > + DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
> > + if (!link) {
> > + dev_err(priv->dev,
> > + "Failed to create device link to output %s\n",
> > + sd->name);
> > + return -EINVAL;
> > + }
> > +
> > + ret = v4l2_create_fwnode_links_to_pad(&priv->subdev,
> > + &sd->entity.pads[0],
> > + MEDIA_LNK_FL_IMMUTABLE |
> > + MEDIA_LNK_FL_ENABLED);
> > + }
>
> Add a blank line here.
>
> > + if (ret) {
> > + dev_err(priv->dev, "Failed to create links between SENINF port %u and %s (%d)\n",
> > + asd->port, sd->entity.name, ret);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int mtk_seninf_notifier_complete(struct v4l2_async_notifier *notifier)
> > +{
> > + struct mtk_seninf *priv = container_of(notifier, struct mtk_seninf,
> > + notifier);
> > + int ret;
> > +
> > + ret = v4l2_device_register_subdev_nodes(&priv->v4l2_dev);
> > + if (ret) {
> > + dev_err(priv->dev, "Failed to register subdev nodes: %d\n",
> > + ret);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const struct v4l2_async_notifier_operations mtk_seninf_async_ops = {
> > + .bound = mtk_seninf_notifier_bound,
> > + .complete = mtk_seninf_notifier_complete,
> > +};
> > +
> > +static int mtk_seninf_media_init(struct mtk_seninf *priv)
> > +{
> > + struct media_device *media_dev = &priv->media_dev;
> > + const struct mtk_seninf_conf *conf = priv->conf;
> > + unsigned int num_pads = conf->nb_outputs + conf->nb_inputs;
> > + struct media_pad *pads = priv->pads;
> > + struct device *dev = priv->dev;
> > + unsigned int i;
> > + int ret;
> > +
> > + media_dev->dev = dev;
> > + strscpy(media_dev->model, conf->model, sizeof(media_dev->model));
> > + media_dev->hw_revision = 0;
> > + media_device_init(media_dev);
> > +
> > + for (i = 0; i < conf->nb_inputs; i++)
> > + pads[i].flags = MEDIA_PAD_FL_SINK;
> > + for (i = conf->nb_inputs; i < num_pads; i++)
> > + pads[i].flags = MEDIA_PAD_FL_SOURCE;
> > +
> > + ret = media_entity_pads_init(&priv->subdev.entity, num_pads, pads);
> > + if (ret) {
> > + media_device_cleanup(media_dev);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int mtk_seninf_v4l2_async_register(struct mtk_seninf *priv)
> > +{
> > + const struct mtk_seninf_conf *conf = priv->conf;
> > + struct device *dev = priv->dev;
> > + unsigned int i;
> > + int ret;
> > +
> > + v4l2_async_nf_init(&priv->notifier, &priv->v4l2_dev);
> > +
> > + for (i = 0; i < conf->nb_inputs + conf->nb_outputs; ++i) {
> > + ret = mtk_seninf_fwnode_parse(dev, i);
> > +
> > + if (ret) {
> > + dev_err(dev,
> > + "Failed to parse endpoint at port %d: %d\n",
>
> i is unsigned, so use %u
>
> > + i, ret);
> > + goto err_clean_notififer;
> > + }
> > + }
> > +
> > + priv->notifier.ops = &mtk_seninf_async_ops;
> > + ret = v4l2_async_nf_register(&priv->notifier);
> > + if (ret) {
> > + dev_err(dev, "Failed to register async notifier: %d\n", ret);
> > + goto err_clean_notififer;
> > + }
> > + return 0;
> > +
> > +err_clean_notififer:
> > + v4l2_async_nf_cleanup(&priv->notifier);
> > +
> > + return ret;
> > +}
> > +
>
> /* -----------------------------------------------------------------------------
> * Probe & Remove
> */
>
> as we leave the "Async Subdev Notifier" section.
>
> > +static int mtk_seninf_v4l2_register(struct mtk_seninf *priv)
> > +{
> > + struct v4l2_subdev *sd = &priv->subdev;
> > + struct device *dev = priv->dev;
> > + int ret;
> > +
> > + /* Initialize media device & pads. */
> > + ret = mtk_seninf_media_init(priv);
>
> This function intializes both the media device and the entity within the
> seninf subdev, while the rest of the subdev is initialized below. I
> think that makes the code flow more difficult to understand.
>
> The following function could go above, just after seninf_media_ops:
>
> static int seninf_subdev_init(struct mtk_seninf *priv)
> {
> const unsigned int num_pads = priv->conf->nb_outputs
> + priv->conf->nb_inputs;
> struct v4l2_subdev *sd = &priv->subdev;
> struct media_pad *pads = priv->pads;
> unsigned int i;
> int ret;
>
> /* Initialize the entity. */
> for (i = 0; i < priv->conf->nb_inputs; i++)
> pads[i].flags = MEDIA_PAD_FL_SINK;
> for ( ; i < num_pads; i++)
> pads[i].flags = MEDIA_PAD_FL_SOURCE;
>
> ret = media_entity_pads_init(&priv->subdev.entity, num_pads, pads);
> if (ret)
> return ret;
>
> /* Initialize the subdev and its controls. */
> v4l2_subdev_init(sd, &seninf_subdev_ops);
> sd->internal_ops = &seninf_subdev_internal_ops;
> sd->dev = priv->dev;
> sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> sd->entity.ops = &seninf_media_ops;
> sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
> V4L2_SUBDEV_FL_STREAMS;
> strscpy(sd->name, dev_name(priv->dev), sizeof(sd->name));
> v4l2_set_subdevdata(sd, priv);
>
> ret = seninf_initialize_controls(priv);
> if (ret) {
> dev_err_probe(priv->dev, ret, "Failed to initialize controls\n");
> goto err_subdev;
> }
>
> ret = v4l2_subdev_init_finalize(sd);
> if (ret)
> goto err_free_handler;
>
> return 0;
>
> err_free_handler:
> v4l2_ctrl_handler_free(&priv->ctrl_handler);
> err_subdev:
> v4l2_subdev_cleanup(sd);
> media_entity_cleanup(&sd->entity);
> return ret;
> }
>
> I would also add
>
> static void seninf_subdev_cleanup(struct mtk_seninf *priv)
> {
> struct v4l2_subdev *sd = &priv->subdev;
>
> v4l2_ctrl_handler_free(&priv->ctrl_handler);
> v4l2_subdev_cleanup(sd);
> media_entity_cleanup(&sd->entity);
> }
>
> and use it in error paths below, as well as in .remove().
>
> Then, in this function,
>
> /* Initialize the media_device and v4l2_device. */
> media_dev->dev = dev;
> strscpy(media_dev->model, conf->model, sizeof(media_dev->model));
> media_dev->hw_revision = 0;
> media_device_init(media_dev);
>
> priv->v4l2_dev.mdev = &priv->media_dev;
>
> ret = v4l2_device_register(dev, &priv->v4l2_dev);
> if (ret) {
> dev_err_probe(dev, ret, "Failed to register V4L2 device\n");
> goto err_clean_media;
> }
>
> /* Initialize and register the SENINF subdev. */
> ret = seninf_subdev_init(priv);
> ...
>
> followed by mtk_seninf_v4l2_async_register() and
> media_device_register().
>
> You can drop mtk_seninf_media_init(). I think the result will be
> clearer.
>
> > + if (ret)
> > + return ret;
> > +
> > + /* Initialize & register v4l2 device. */
> > + priv->v4l2_dev.mdev = &priv->media_dev;
> > +
> > + ret = v4l2_device_register(dev, &priv->v4l2_dev);
> > + if (ret) {
> > + dev_err_probe(dev, ret, "Failed to register V4L2 device\n");
> > + goto err_clean_media;
> > + }
> > +
> > + /* Initialize & register subdev. */
> > + v4l2_subdev_init(sd, &seninf_subdev_ops);
> > + sd->internal_ops = &seninf_subdev_internal_ops;
> > + sd->dev = dev;
> > + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> > + sd->entity.ops = &seninf_media_ops;
> > + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
>
> If "[PATCH v2 0/2] media: i2c: Drop HAS_EVENTS and event handlers" gets
> merged first, you can drop V4L2_SUBDEV_FL_HAS_EVENTS.
>
> > + V4L2_SUBDEV_FL_STREAMS;
> > + strscpy(sd->name, dev_name(dev), sizeof(sd->name));
> > + ret = seninf_initialize_controls(priv);
> > + if (ret) {
> > + dev_err_probe(dev, ret, "Failed to initialize controls\n");
> > + goto err_unreg_v4l2;
> > + }
> > + v4l2_set_subdevdata(sd, priv);
> > +
> > + ret = v4l2_subdev_init_finalize(sd);
> > + if (ret)
> > + goto err_free_handler;
> > +
> > + ret = v4l2_device_register_subdev(&priv->v4l2_dev, sd);
> > + if (ret) {
> > + dev_err_probe(dev, ret, "Failed to register subdev\n");
> > + goto err_cleanup_subdev;
> > + }
> > +
> > + /* Set up async device */
> > + ret = mtk_seninf_v4l2_async_register(priv);
> > + if (ret) {
> > + dev_err_probe(dev, ret,
> > + "Failed to register v4l2 async notifier\n");
> > + goto err_unreg_subdev;
> > + }
> > +
> > + /* Register media device */
> > + ret = media_device_register(&priv->media_dev);
> > + if (ret) {
> > + dev_err_probe(dev, ret, "Failed to register media device\n");
> > + goto err_unreg_notifier;
> > + }
> > +
> > + return 0;
> > +
> > +err_unreg_notifier:
> > + v4l2_async_nf_unregister(&priv->notifier);
> > +err_unreg_subdev:
> > + v4l2_device_unregister_subdev(sd);
> > +err_cleanup_subdev:
> > + v4l2_subdev_cleanup(sd);
> > +err_free_handler:
> > + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> > +err_unreg_v4l2:
> > + v4l2_device_unregister(&priv->v4l2_dev);
> > +err_clean_media:
> > + media_entity_cleanup(&sd->entity);
> > + media_device_cleanup(&priv->media_dev);
> > +
> > + return ret;
> > +}
> > +
> > +static int seninf_pm_suspend(struct device *dev)
> > +{
> > + struct mtk_seninf *priv = dev_get_drvdata(dev);
> > +
> > + clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> > +
> > + return 0;
> > +}
> > +
> > +static int seninf_pm_resume(struct device *dev)
> > +{
> > + struct mtk_seninf *priv = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
> > + if (ret) {
> > + dev_err(dev, "failed to enable clock: %d\n", ret);
>
> s/clock/clocks/
>
> > + return ret;
> > + }
> > +
> > + mtk_seninf_csi2_setup_phy(priv);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dev_pm_ops runtime_pm_ops = {
> > + SET_RUNTIME_PM_OPS(seninf_pm_suspend, seninf_pm_resume, NULL)
> > + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > + pm_runtime_force_resume)
> > +};
> > +
> > +static int seninf_probe(struct platform_device *pdev)
> > +{
> > + static const char * const clk_names[] = { "camsys", "top_mux" };
> > + struct device *dev = &pdev->dev;
> > + struct mtk_seninf *priv;
> > + unsigned int i;
> > + int ret;
> > +
> > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > + if (!priv)
> > + return -ENOMEM;
> > +
> > + priv->conf = device_get_match_data(dev);
> > +
> > + dev_set_drvdata(dev, priv);
> > + priv->dev = dev;
> > +
> > + priv->base = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(priv->base))
> > + return PTR_ERR(priv->base);
> > +
> > + priv->num_clks = ARRAY_SIZE(clk_names);
> > + priv->clks = devm_kcalloc(dev, priv->num_clks,
> > + sizeof(*priv->clks), GFP_KERNEL);
> > + if (!priv->clks)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < priv->num_clks; ++i)
> > + priv->clks[i].id = clk_names[i];
> > +
> > + ret = devm_clk_bulk_get(dev, priv->num_clks, priv->clks);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to get seninf clock\n");
>
> s/clock/clocks/
>
> > +
> > + for (i = 0; i < priv->conf->nb_muxes; ++i) {
> > + struct mtk_seninf_mux *mux = &priv->muxes[i];
> > +
> > + mux->pad = priv->conf->nb_inputs + i;
> > + mux->mux_id = i;
> > + mux->base = priv->base + 0x1000 * i;
> > + mux->seninf = priv;
> > + }
> > +
> > + devm_pm_runtime_enable(dev);
> > +
> > + ret = mtk_seninf_v4l2_register(priv);
> > + return ret;
>
> return mtk_seninf_v4l2_register(priv);
>
> > +}
> > +
> > +static void seninf_remove(struct platform_device *pdev)
> > +{
> > + struct mtk_seninf *priv = dev_get_drvdata(&pdev->dev);
> > +
> > + media_device_unregister(&priv->media_dev);
> > + media_device_cleanup(&priv->media_dev);
> > + v4l2_async_nf_unregister(&priv->notifier);
> > + v4l2_async_nf_cleanup(&priv->notifier);
> > + v4l2_device_unregister_subdev(&priv->subdev);
> > + v4l2_subdev_cleanup(&priv->subdev);
> > + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> > + media_entity_cleanup(&priv->subdev.entity);
> > + v4l2_device_unregister(&priv->v4l2_dev);
> > +}
> > +
> > +static const struct mtk_seninf_conf seninf_8365_conf = {
> > + .model = "mtk-camsys-3.0",
> > + .nb_inputs = 4,
> > + .nb_muxes = 6,
> > + .nb_outputs = 4,
> > +};
> > +
> > +static const struct of_device_id mtk_seninf_of_match[] = {
> > + { .compatible = "mediatek,mt8365-seninf", .data = &seninf_8365_conf },
> > + { /* sentinel */ },
> > +};
> > +MODULE_DEVICE_TABLE(of, mtk_seninf_of_match);
> > +
> > +static struct platform_driver seninf_pdrv = {
> > + .driver = {
> > + .name = "mtk-seninf",
> > + .pm = &runtime_pm_ops,
> > + .of_match_table = mtk_seninf_of_match,
> > + },
> > + .probe = seninf_probe,
> > + .remove = seninf_remove,
> > +};
> > +
> > +module_platform_driver(seninf_pdrv);
> > +
> > +MODULE_DESCRIPTION("MTK sensor interface driver");
> > +MODULE_AUTHOR("Louis Kuo <louis.kuo@mediatek.com>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h b/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..1f13755ab2f0239b0ab7ed200523da5a7b773d1b
> > --- /dev/null
> > +++ b/drivers/media/platform/mediatek/isp/mtk_seninf_reg.h
> > @@ -0,0 +1,114 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2022 MediaTek Inc.
> > + */
> > +
> > +#ifndef __SENINF_REG_H__
> > +#define __SENINF_REG_H__
> > +
> > +#include <linux/bits.h>
> > +
> > +#define SENINF_TOP_CTRL 0x0000
> > +#define SENINF_TOP_CTRL_MUX_LP_MODE BIT(31)
> > +#define SENINF_TOP_CTRL_SENINF_PCLK_EN BIT(10)
> > +#define SENINF_TOP_CTRL_SENINF2_PCLK_EN BIT(11)
> > +#define SENINF_TOP_MUX_CTRL 0x0008
> > +#define SENINF_TOP_CAM_MUX_CTRL 0x0010
> > +#define SENINF_TOP_CAM_MUX_CTRL_SENINF_CAM2_MUX_SRC_SEL GENMASK(11, 8)
> > +#define SENINF_TOP_CAM_MUX_CTRL_SENINF_CAM3_MUX_SRC_SEL GENMASK(15, 12)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0 0x001c
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_DPHY_MODE BIT(0)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_CK_SEL_1 GENMASK(10, 8)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_CK_SEL_2 GENMASK(13, 12)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI0_PHY_SENINF_LANE_MUX_CSI0_EN BIT(31)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI1 0x0020
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_DPHY_MODE BIT(0)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_CK_SEL_1 GENMASK(10, 8)
> > +#define SENINF_TOP_PHY_SENINF_CTL_CSI1_PHY_SENINF_LANE_MUX_CSI1_EN BIT(31)
> > +#define SENINF_CTRL 0x0200
> > +#define SENINF_CTRL_SENINF_EN BIT(0)
> > +#define SENINF_CTRL_CSI2_SW_RST BIT(7)
> > +#define SENINF_CTRL_SENINF_SRC_SEL GENMASK(14, 12)
> > +#define SENINF_CTRL_PAD2CAM_DATA_SEL GENMASK(30, 28)
> > +#define SENINF_CTRL_EXT 0x0204
> > +#define SENINF_CTRL_EXT_SENINF_TESTMDL_IP_EN BIT(1)
> > +#define SENINF_CTRL_EXT_SENINF_NCSI2_IP_EN BIT(5)
> > +#define SENINF_CTRL_EXT_SENINF_CSI2_IP_EN BIT(6)
> > +#define SENINF_TG1_PH_CNT 0x0600
> > +#define SENINF_TG1_SEN_CK 0x0604
> > +#define SENINF_TG1_SEN_CK_CLKCNT GENMASK(21, 16)
> > +#define SENINF_TG1_TM_CTL 0x0608
> > +#define SENINF_TG1_TM_CTL_TM_EN BIT(0)
> > +#define SENINF_TG1_TM_CTL_TM_FMT BIT(2)
> > +#define SENINF_TG1_TM_CTL_TM_PAT GENMASK(7, 4)
> > +#define SENINF_TG1_TM_CTL_TM_VSYNC GENMASK(15, 8)
> > +#define SENINF_TG1_TM_CTL_TM_DUMMYPXL GENMASK(23, 16)
> > +#define SENINF_TG1_TM_SIZE 0x060c
> > +#define SENINF_TG1_TM_SIZE_TM_LINE GENMASK(29, 16)
> > +#define SENINF_TG1_TM_SIZE_TM_PXL GENMASK(12, 0)
> > +#define SENINF_TG1_TM_CLK 0x0610
> > +#define TEST_MODEL_CLK_DIVIDED_CNT 8
> > +#define SENINF_TG1_TM_STP 0x0614
> > +#define TIME_STAMP_DIVIDER 1
> > +#define MIPI_RX_CON24_CSI0 0x0824
> > +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN0_MUX GENMASK(25, 24)
> > +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN1_MUX GENMASK(27, 26)
> > +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN2_MUX GENMASK(29, 28)
> > +#define MIPI_RX_CON24_CSI0_CSI0_BIST_LN3_MUX GENMASK(31, 30)
> > +#define SENINF_CSI2_CTL 0x0a00
> > +#define SENINF_CSI2_CTL_DATA_LANE0_EN BIT(0)
> > +#define SENINF_CSI2_CTL_DATA_LANE1_EN BIT(1)
> > +#define SENINF_CSI2_CTL_DATA_LANE2_EN BIT(2)
> > +#define SENINF_CSI2_CTL_DATA_LANE3_EN BIT(3)
> > +#define SENINF_CSI2_CTL_CLOCK_LANE_EN BIT(4)
> > +#define SENINF_CSI2_CTL_HSRX_DET_EN BIT(7)
> > +#define SENINF_CSI2_CTL_ED_SEL BIT(16)
> > +#define DATA_HEADER_ORDER_DI_WCL_WCH 1
> > +#define SENINF_CSI2_CTL_HS_TRAIL_EN BIT(25)
> > +#define SENINF_CSI2_CTL_CLOCK_HS_OPTION BIT(27)
> > +#define SENINF_CSI2_LNRD_TIMING 0x0a08
> > +#define SENINF_CSI2_LNRD_TIMING_DATA_SETTLE_PARAMETER GENMASK(15, 8)
> > +#define SENINF_CSI2_DPCM 0x0a0c
> > +#define SENINF_CSI2_DPCM_DI_30_DPCM_EN BIT(7)
> > +#define SENINF_CSI2_DPCM_DI_2A_DPCM_EN BIT(15)
> > +#define SENINF_CSI2_DGB_SEL 0x0a18
> > +#define SENINF_CSI2_DGB_SEL_DEBUG_SEL GENMASK(7, 0)
> > +#define SENINF_CSI2_DGB_SEL_DEBUG_EN BIT(31)
> > +#define SENINF_CSI2_SPARE0 0x0a20
> > +#define SENINF_CSI2_LNRC_FSM 0x0a28
> > +#define SENINF_CSI2_HS_TRAIL 0x0a40
> > +#define SENINF_CSI2_HS_TRAIL_HS_TRAIL_PARAMETER GENMASK(7, 0)
> > +#define SENINF_CSI2_RESYNC_MERGE_CTL 0x0a74
> > +#define SENINF_CSI2_RESYNC_MERGE_CTL_CPHY_LANE_RESYNC_CNT GENMASK(2, 0)
> > +#define SENINF_CSI2_RESYNC_MERGE_CTL_BYPASS_LANE_RESYNC BIT(10)
> > +#define SENINF_CSI2_RESYNC_MERGE_CTL_CDPHY_SEL BIT(11)
> > +#define SENINF_CSI2_MODE 0x0ae8
> > +#define SENINF_CSI2_MODE_CSR_CSI2_MODE GENMASK(7, 0)
> > +#define SENINF_CSI2_MODE_CSR_CSI2_HEADER_LEN GENMASK(10, 8)
> > +#define SENINF_CSI2_DPHY_SYNC 0x0b20
> > +#define SENINF_CSI2_DPHY_SYNC_SYNC_SEQ_MASK_0 GENMASK(15, 0)
> > +#define SENINF_CSI2_DPHY_SYNC_SYNC_SEQ_PAT_0 GENMASK(31, 16)
> > +#define SENINF_MUX_CTRL 0x0d00
> > +#define SENINF_MUX_CTRL_SENINF_MUX_SW_RST BIT(0)
> > +#define SENINF_MUX_CTRL_SENINF_IRQ_SW_RST BIT(1)
> > +#define SENINF_MUX_CTRL_SENINF_HSYNC_MASK BIT(7)
> > +#define SENINF_MUX_CTRL_SENINF_PIX_SEL BIT(8)
> > +#define SENINF_MUX_CTRL_SENINF_VSYNC_POL BIT(9)
> > +#define SENINF_MUX_CTRL_SENINF_HSYNC_POL BIT(10)
> > +#define SENINF_MUX_CTRL_SENINF_SRC_SEL GENMASK(15, 12)
> > +#define SENINF_MUX_CTRL_FIFO_PUSH_EN GENMASK(21, 16)
> > +#define FIFO_PUSH_EN_NORMAL_MODE 0x1f
> > +#define FIFO_PUSH_EN_JPEG_2_PIXEL_MODE 0x1e
> > +#define SENINF_MUX_CTRL_FIFO_FLUSH_EN GENMASK(28, 22)
> > +#define FIFO_FLUSH_EN_NORMAL_MODE 0x1b
> > +#define FIFO_FLUSH_EN_JPEG_2_PIXEL_MODE 0x18
> > +#define SENINF_MUX_CTRL_FIFO_FULL_WR_EN GENMASK(29, 28)
> > +#define SENINF_MUX_CTRL_SENINF_MUX_EN BIT(31)
> > +#define SENINF_MUX_INTEN 0x0d04
> > +#define SENINF_MUX_SPARE 0x0d2c
> > +#define SENINF_FIFO_FULL_SEL BIT(13)
> > +#define SENINF_MUX_CTRL_EXT 0x0d3c
> > +#define SENINF_MUX_CTRL_EXT_SENINF_SRC_SEL_EXT GENMASK(1, 0)
> > +#define SENINF_MUX_CTRL_EXT_SENINF_PIX_SEL_EXT BIT(4)
> > +
> > +#endif /* __SENINF_REG_H__ */
> >
>
> --
> Regards,
>
> Laurent Pinchart
^ permalink raw reply [flat|nested] 39+ messages in thread
end of thread, other threads:[~2025-01-22 14:10 UTC | newest]
Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-21 8:53 [PATCH v7 0/5] Add Mediatek ISP3.0 Julien Stephan
2024-11-21 8:53 ` [PATCH v7 1/5] dt-bindings: media: add mediatek ISP3.0 sensor interface Julien Stephan
2024-11-21 8:53 ` [PATCH v7 2/5] dt-bindings: media: add mediatek ISP3.0 camsv Julien Stephan
2024-11-25 11:24 ` Laurent Pinchart
2024-11-21 8:53 ` [PATCH v7 3/5] media: platform: mediatek: isp: add mediatek ISP3.0 sensor interface Julien Stephan
2024-11-25 17:33 ` Laurent Pinchart
2024-11-25 19:45 ` Laurent Pinchart
2025-01-22 14:04 ` Julien Stephan
2024-11-21 8:53 ` [PATCH v7 4/5] media: platform: mediatek: isp: add mediatek ISP3.0 camsv Julien Stephan
2024-11-22 7:54 ` CK Hu (胡俊光)
2024-11-22 9:16 ` Julien Stephan
2024-11-22 9:28 ` CK Hu (胡俊光)
2024-11-24 6:11 ` Laurent Pinchart
2024-11-25 8:26 ` CK Hu (胡俊光)
2024-11-22 8:41 ` CK Hu (胡俊光)
2024-11-22 9:25 ` Julien Stephan
2024-11-22 9:49 ` CK Hu (胡俊光)
2024-11-22 9:50 ` Julien Stephan
2024-11-22 10:01 ` CK Hu (胡俊光)
2024-11-22 12:05 ` Julien Stephan
2024-11-22 9:18 ` CK Hu (胡俊光)
2024-11-22 9:44 ` Julien Stephan
2024-11-25 5:54 ` CK Hu (胡俊光)
2024-11-25 6:05 ` CK Hu (胡俊光)
2024-11-25 6:34 ` CK Hu (胡俊光)
2024-11-25 6:59 ` CK Hu (胡俊光)
2024-11-25 9:39 ` Laurent Pinchart
2024-11-25 9:56 ` CK Hu (胡俊光)
2024-11-25 10:22 ` Laurent Pinchart
2024-11-26 1:08 ` CK Hu (胡俊光)
2024-11-25 8:14 ` CK Hu (胡俊光)
2024-11-25 14:40 ` Julien Stephan
2024-11-25 17:36 ` Laurent Pinchart
2024-11-25 9:30 ` CK Hu (胡俊光)
2024-11-25 20:33 ` Laurent Pinchart
2024-11-26 2:07 ` CK Hu (胡俊光)
2024-11-26 8:43 ` Laurent Pinchart
2024-11-26 3:40 ` CK Hu (胡俊光)
2024-11-21 8:53 ` [PATCH v7 5/5] arm64: dts: mediatek: mt8365: Add support for camera Julien Stephan
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).