* Re: [PATCH 1/7] arm64: dts: imx8-ss-lsio: fix pwm lpcg indices
From: Fabio Estevam @ 2024-04-01 23:04 UTC (permalink / raw)
To: Frank Li
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, NXP Linux Team,
Marcel Ziswiler, Philippe Schenker, Max Krummenacher,
Alexander Stein, Joakim Zhang, devicetree, linux-arm-kernel,
linux-kernel, stable
In-Reply-To: <20240401-dts_fix-v1-1-8c51ce52d411@nxp.com>
On Mon, Apr 1, 2024 at 7:25 PM Frank Li <Frank.Li@nxp.com> wrote:
>
> lpcg's arg0 should use clock indices instead of index.
>
> pwm0_lpcg: clock-controller@5d400000 {
> ... // Col1 Col2
> clocks = <&clk IMX_SC_R_PWM_0 IMX_SC_PM_CLK_PER>, // 0 0
> <&clk IMX_SC_R_PWM_0 IMX_SC_PM_CLK_PER>, // 1 1
> <&clk IMX_SC_R_PWM_0 IMX_SC_PM_CLK_PER>, // 2 4
> <&lsio_bus_clk>, // 3 5
> <&clk IMX_SC_R_PWM_0 IMX_SC_PM_CLK_PER>; // 4 6
> clock-indices = <IMX_LPCG_CLK_0>, <IMX_LPCG_CLK_1>,
> <IMX_LPCG_CLK_4>, <IMX_LPCG_CLK_5>,
> <IMX_LPCG_CLK_6>;
> };
>
> Col1: index, which exited dts try to get.
I cannot understand this sentence, sorry.
> Col2: actual index in lpcg driver.
You should not describe DT in terms of Linux driver.
^ permalink raw reply
* Re: [PATCH] arm64: dts: rockchip: Remove unsupported node from the Pinebook Pro dts
From: Dragan Simic @ 2024-04-01 23:07 UTC (permalink / raw)
To: Heiko Stuebner
Cc: linux-rockchip, devicetree, krzysztof.kozlowski+dt, conor+dt,
linux-arm-kernel, robh+dt
In-Reply-To: <171200852040.1394042.4883299945962544319.b4-ty@sntech.de>
On 2024-04-01 23:55, Heiko Stuebner wrote:
> On Mon, 1 Apr 2024 00:20:56 +0200, Dragan Simic wrote:
>> Remove a redundant node from the Pine64 Pinebook Pro dts, which is
>> intended
>> to provide a value for the delay in PCI Express enumeration, but that
>> isn't
>> supported without additional out-of-tree kernel patches.
>>
>> There were already efforts to upstream those kernel patches, because
>> they
>> reportedly make some PCI Express cards (such as LSI SAS HBAs) usable
>> in
>> Pine64 RockPro64 (which is also based on the RK3399); otherwise,
>> those PCI
>> Express cards fail to enumerate. However, providing the required
>> background
>> and explanations proved to be a tough nut to crack, which is the
>> reason why
>> those patches remain outside of the kernel mainline for now.
>>
>> [...]
>
> Applied, thanks!
>
> [1/1] arm64: dts: rockchip: Remove unsupported node from the Pinebook
> Pro dts
> commit: 43853e843aa6c3d47ff2b0cce898318839483d05
Great, thanks!
^ permalink raw reply
* Re: Fixing the devicetree of Rock 5 Model B (and possibly others)
From: Saravana Kannan @ 2024-04-01 23:24 UTC (permalink / raw)
To: Dragan Simic
Cc: Pratham Patel, sebastian.reichel, devicetree, linux-arm-kernel,
linux-rockchip, linux-kernel, regressions, stable
In-Reply-To: <ac4246bf3786230eb9ca85b329e7d0df@manjaro.org>
On Sat, Mar 23, 2024 at 10:10 AM Dragan Simic <dsimic@manjaro.org> wrote:
>
> Hello Pratham,
>
> On 2024-03-23 18:02, Pratham Patel wrote:
> > Since the introduction of the `of: property: fw_devlink: Fix stupid
> > bug in remote-endpoint parsing` patch, an issue with the device-tree
> > of the Rock 5 Model B has been detected. All the stable kernels (6.7.y
> > and 6.8.y) work on the Orange Pi 5, which has the Rockchip RK3588S SoC
> > (same as the RK3588, but less I/O basically). So, being an owner of
> > only two SBCs which use the RK3588* SoC, it appears that the Rock 5
> > Model B's DT is incorrect.
> >
> > I looked at the patch and tried several things, neither resulted in
> > anything that would point me to the core issue. Then I tried this:
>
> Could you, please, clarify a bit what's the actual issue you're
> experiencing on your Rock 5B?
Pratham, can you reply to this please? I don't really understand what
your issue is for me to be able to help.
Also, can you give the output of <debugfs>/devices_deferred for the
good vs bad case?
Thanks,
Saravana
>
> > ```
> > $ grep -C 3 remote-endpoint
> > arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
> >
> > port {
> > es8316_p0_0: endpoint {
> > remote-endpoint = <&i2s0_8ch_p0_0>;
> > };
> > };
> > };
> > --
> > i2s0_8ch_p0_0: endpoint {
> > dai-format = "i2s";
> > mclk-fs = <256>;
> > remote-endpoint = <&es8316_p0_0>;
> > };
> > };
> > };
> > ```
> >
> > So, from a cursory look, the issue seems to be related to either the
> > DT node for the audio codec or related to the es8316's binding itself.
> > Though I doubt that the later is the issue because if that were the
> > issue, _someone_ with a Pine64 Pinebook Pro would've raised alarms. So
> > far, this seems to be related to the `rk3588-rock-5b.dts` and possibly
> > with the `rk3588s-rock-5a.dts` too.
> >
> > I would **love** to help but I'm afraid I device-trees are not
> > something that I am at-all familiar with. That said, I am open to
> > methods of debugging this issue to provide a fix myself.
> >
> > I would have replied to the patch's link but unfortunately, I haven't
> > yet setup neomutt and my email provider's web UI doesn't have a
> > [straightforward] way to reply using the 'In-Reply-To' header, hence a
> > new thread. Apologies for the inconvenience caused.
> >
> > -- Pratham Patel
> > _______________________________________________
> > Linux-rockchip mailing list
> > Linux-rockchip@lists.infradead.org
> > http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply
* Re: [PATCH v2 17/18] dt-bindings: pci: rockchip,rk3399-pcie-ep: Add ep-gpios property
From: Damien Le Moal @ 2024-04-01 23:36 UTC (permalink / raw)
To: Krzysztof Kozlowski, Manivannan Sadhasivam, Lorenzo Pieralisi,
Kishon Vijay Abraham I, Shawn Lin, Krzysztof Wilczyński,
Bjorn Helgaas, Heiko Stuebner, linux-pci, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree
Cc: linux-rockchip, linux-arm-kernel, Rick Wertenbroek,
Wilfred Mallawa, Niklas Cassel
In-Reply-To: <518f04ea-7ff6-4568-be76-60276d18b209@linaro.org>
On 4/1/24 18:57, Krzysztof Kozlowski wrote:
> On 01/04/2024 01:06, Damien Le Moal wrote:
>> On 3/30/24 18:16, Krzysztof Kozlowski wrote:
>>> On 30/03/2024 05:19, Damien Le Moal wrote:
>>>> From: Wilfred Mallawa <wilfred.mallawa@wdc.com>
>>>>
>>>> Describe the `ep-gpios` property which is used to map the PERST# input
>>>> signal for endpoint mode.
>>>>
>>>> Signed-off-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
>>>> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
>>>> ---
>>>> .../devicetree/bindings/pci/rockchip,rk3399-pcie-ep.yaml | 3 +++
>>>> 1 file changed, 3 insertions(+)
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/pci/rockchip,rk3399-pcie-ep.yaml b/Documentation/devicetree/bindings/pci/rockchip,rk3399-pcie-ep.yaml
>>>> index 6b62f6f58efe..9331d44d6963 100644
>>>> --- a/Documentation/devicetree/bindings/pci/rockchip,rk3399-pcie-ep.yaml
>>>> +++ b/Documentation/devicetree/bindings/pci/rockchip,rk3399-pcie-ep.yaml
>>>> @@ -30,6 +30,9 @@ properties:
>>>> maximum: 32
>>>> default: 32
>>>>
>>>> + ep-gpios:
>>>> + description: Input GPIO configured for the PERST# signal.
>>>
>>> Missing maxItems. But more important: why existing property perst-gpios,
>>> which you already have there in common schema, is not correct for this case?
>>
>> I am confused... Where do you find perst-gpios defined for the rk3399 ?
>> Under Documentation/devicetree/bindings/pci/, the only schema I see using
>> perst-gpios property are for the qcom (Qualcomm) controllers.
>
> You are right, it's so far only in Qualcomm.
>
>> The RC bindings for the rockchip rk3399 PCIe controller
>> (pci/rockchip,rk3399-pcie.yaml) already define the ep-gpios property. So if
>
> Any reason why this cannot be named like GPIO? Is there already a user
> of this in Linux kernel? Commit msg says nothing about this, so that's
> why I would expect name matching the signal.
The RC-mode PCIe controller node of the rk3399 DTS already defines the ep-gpios
property for RC side PERST# signal handling. So we simply reused the exact same
name to be consistent between RC and EP. I personnally have no preferences. If
there is an effort to rename such signal with some preferred pattern, I will
follow. For the EP node, there was no PERST signal handling in the driver and
no property defined for it, so any name is fine. "perst-gpios" would indeed be
a better name, but again, given that the RC controller node has ep-gpios, we
reused that. What is your recommendation here ?
>
> Best regards,
> Krzysztof
>
--
Damien Le Moal
Western Digital Research
^ permalink raw reply
* Re: [PATCH v3] dt-bindings: ata: ahci-da850: Convert to dtschema
From: Damien Le Moal @ 2024-04-01 23:40 UTC (permalink / raw)
To: Animesh Agarwal
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-ide,
devicetree, linux-kernel
In-Reply-To: <20240327064354.17384-1-animeshagarwal28@gmail.com>
On 3/27/24 15:43, Animesh Agarwal wrote:
> Convert the ahci-da850 bindings to DT schema.
>
> Signed-off-by: Animesh Agarwal <animeshagarwal28@gmail.com>
Krzysztof, Rob,
Are you OK with this patch ?
>
> ---
> Changes in v3:
> - Fixed line length issue on line 20
> - Removed unneccessary '|' character
> Changes in v2:
> - Added description for reg property items.
> ---
> .../devicetree/bindings/ata/ahci-da850.txt | 18 ---------
> .../bindings/ata/ti,da850-ahci.yaml | 39 +++++++++++++++++++
> 2 files changed, 39 insertions(+), 18 deletions(-)
> delete mode 100644 Documentation/devicetree/bindings/ata/ahci-da850.txt
> create mode 100644 Documentation/devicetree/bindings/ata/ti,da850-ahci.yaml
>
> diff --git a/Documentation/devicetree/bindings/ata/ahci-da850.txt b/Documentation/devicetree/bindings/ata/ahci-da850.txt
> deleted file mode 100644
> index 5f8193417725..000000000000
> --- a/Documentation/devicetree/bindings/ata/ahci-da850.txt
> +++ /dev/null
> @@ -1,18 +0,0 @@
> -Device tree binding for the TI DA850 AHCI SATA Controller
> ----------------------------------------------------------
> -
> -Required properties:
> - - compatible: must be "ti,da850-ahci"
> - - reg: physical base addresses and sizes of the two register regions
> - used by the controller: the register map as defined by the
> - AHCI 1.1 standard and the Power Down Control Register (PWRDN)
> - for enabling/disabling the SATA clock receiver
> - - interrupts: interrupt specifier (refer to the interrupt binding)
> -
> -Example:
> -
> - sata: sata@218000 {
> - compatible = "ti,da850-ahci";
> - reg = <0x218000 0x2000>, <0x22c018 0x4>;
> - interrupts = <67>;
> - };
> diff --git a/Documentation/devicetree/bindings/ata/ti,da850-ahci.yaml b/Documentation/devicetree/bindings/ata/ti,da850-ahci.yaml
> new file mode 100644
> index 000000000000..ce13c76bdffb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/ata/ti,da850-ahci.yaml
> @@ -0,0 +1,39 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/ata/ti,da850-ahci.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: TI DA850 AHCI SATA Controller
> +
> +maintainers:
> + - Animesh Agarwal <animeshagarwal28@gmail.com>
> +
> +properties:
> + compatible:
> + const: ti,da850-ahci
> +
> + reg:
> + items:
> + - description: Address and size of the register map as defined by the AHCI 1.1 standard.
> + - description:
> + Address and size of Power Down Control Register (PWRDN) for enabling/disabling the SATA clock
> + receiver.
> +
> + interrupts:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + sata@218000 {
> + compatible = "ti,da850-ahci";
> + reg = <0x218000 0x2000>, <0x22c018 0x4>;
> + interrupts = <67>;
> + };
--
Damien Le Moal
Western Digital Research
^ permalink raw reply
* [PATCH v3 0/4] drm/panel: add support for LG SW43408 panel
From: Dmitry Baryshkov @ 2024-04-01 23:51 UTC (permalink / raw)
To: Sumit Semwal, Caleb Connolly, Neil Armstrong, Jessica Zhang,
Sam Ravnborg, David Airlie, Daniel Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-kernel, linux-arm-msm, Vinod Koul,
Caleb Connolly
The LG SW43408 panel is used on Google Pixel3 devices. For a long time
we could not submit the driver, as the panel was not coming up from the
reset. The panel seems to be picky about some of the delays during init
and it also uses non-standard payload for MIPI_DSI_COMPRESSION_MODE.
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
Changes in v3:
- Fixed return type of MIPI DSC functions
- Replaced mipi_dsi_compression_mode_raw() with
mipi_dsi_compression_mode_ext() (Marijn)
- Link to v2: https://lore.kernel.org/r/20240330-lg-sw43408-panel-v2-0-293a58717b38@linaro.org
Changes in v2:
- Removed formatting char from schema (Krzysztof)
- Moved additionalProperties after required (Krzysztof)
- Added example to the schema (Krzysztof)
- Removed obsolete comment in the commit message (Marijn)
- Moved DSC params to the panel struct (Marijn)
- Changed dsc_en to be an array (Marijn)
- Added comment regiarding slice_width and slice_count (Marijn)
- Link to v1: https://lore.kernel.org/r/20240330-lg-sw43408-panel-v1-0-f5580fc9f2da@linaro.org
---
Dmitry Baryshkov (2):
drm/mipi-dsi: use correct return type for the DSC functions
drm/mipi-dsi: add mipi_dsi_compression_mode_ext()
Sumit Semwal (2):
dt-bindings: panel: Add LG SW43408 MIPI-DSI panel
drm: panel: Add LG sw43408 panel driver
.../bindings/display/panel/lg,sw43408.yaml | 62 ++++
MAINTAINERS | 8 +
drivers/gpu/drm/drm_mipi_dsi.c | 37 ++-
drivers/gpu/drm/panel/Kconfig | 11 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-lg-sw43408.c | 326 +++++++++++++++++++++
include/drm/drm_mipi_dsi.h | 15 +-
7 files changed, 449 insertions(+), 11 deletions(-)
---
base-commit: a6bd6c9333397f5a0e2667d4d82fef8c970108f2
change-id: 20240330-lg-sw43408-panel-b552f411c53e
Best regards,
--
Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
^ permalink raw reply
* [PATCH v3 1/4] dt-bindings: panel: Add LG SW43408 MIPI-DSI panel
From: Dmitry Baryshkov @ 2024-04-01 23:51 UTC (permalink / raw)
To: Sumit Semwal, Caleb Connolly, Neil Armstrong, Jessica Zhang,
Sam Ravnborg, David Airlie, Daniel Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-kernel, linux-arm-msm, Vinod Koul,
Caleb Connolly
In-Reply-To: <20240402-lg-sw43408-panel-v3-0-144f17a11a56@linaro.org>
From: Sumit Semwal <sumit.semwal@linaro.org>
LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel present on Google Pixel 3
phones.
Signed-off-by: Vinod Koul <vkoul@kernel.org>
Signed-off-by: Sumit Semwal <sumit.semwal@linaro.org>
[caleb: convert to yaml]
Signed-off-by: Caleb Connolly <caleb@connolly.tech>
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
.../bindings/display/panel/lg,sw43408.yaml | 62 ++++++++++++++++++++++
1 file changed, 62 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml b/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
new file mode 100644
index 000000000000..1e08648f5bc7
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/lg,sw43408.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: LG SW43408 1080x2160 DSI panel
+
+maintainers:
+ - Caleb Connolly <caleb.connolly@linaro.org>
+
+description:
+ This panel is used on the Pixel 3, it is a 60hz OLED panel which
+ required DSC (Display Stream Compression) and has rounded corners.
+
+allOf:
+ - $ref: panel-common.yaml#
+
+properties:
+ compatible:
+ items:
+ - const: lg,sw43408
+
+ reg: true
+ port: true
+ vddi-supply: true
+ vpnl-supply: true
+ reset-gpios: true
+
+required:
+ - compatible
+ - vddi-supply
+ - vpnl-supply
+ - reset-gpios
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ dsi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ panel@0 {
+ compatible = "lg,sw43408";
+ reg = <0>;
+
+ vddi-supply = <&vreg_l14a_1p88>;
+ vpnl-supply = <&vreg_l28a_3p0>;
+
+ reset-gpios = <&tlmm 6 GPIO_ACTIVE_LOW>;
+
+ port {
+ endpoint {
+ remote-endpoint = <&mdss_dsi0_out>;
+ };
+ };
+ };
+ };
+...
--
2.39.2
^ permalink raw reply related
* [PATCH v3 2/4] drm/mipi-dsi: use correct return type for the DSC functions
From: Dmitry Baryshkov @ 2024-04-01 23:51 UTC (permalink / raw)
To: Sumit Semwal, Caleb Connolly, Neil Armstrong, Jessica Zhang,
Sam Ravnborg, David Airlie, Daniel Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <20240402-lg-sw43408-panel-v3-0-144f17a11a56@linaro.org>
The functions mipi_dsi_compression_mode() and
mipi_dsi_picture_parameter_set() return 0-or-error rather than a buffer
size. Follow example of other similar MIPI DSI functions and use int
return type instead of size_t.
Fixes: f4dea1aaa9a1 ("drm/dsi: add helpers for DSI compression mode and PPS packets")
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
drivers/gpu/drm/drm_mipi_dsi.c | 6 +++---
include/drm/drm_mipi_dsi.h | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
index ef6e416522f8..9874ff6d4718 100644
--- a/drivers/gpu/drm/drm_mipi_dsi.c
+++ b/drivers/gpu/drm/drm_mipi_dsi.c
@@ -654,7 +654,7 @@ EXPORT_SYMBOL(mipi_dsi_set_maximum_return_packet_size);
*
* Return: 0 on success or a negative error code on failure.
*/
-ssize_t mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
+int mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
{
/* Note: Needs updating for non-default PPS or algorithm */
u8 tx[2] = { enable << 0, 0 };
@@ -679,8 +679,8 @@ EXPORT_SYMBOL(mipi_dsi_compression_mode);
*
* Return: 0 on success or a negative error code on failure.
*/
-ssize_t mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi,
- const struct drm_dsc_picture_parameter_set *pps)
+int mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi,
+ const struct drm_dsc_picture_parameter_set *pps)
{
struct mipi_dsi_msg msg = {
.channel = dsi->channel,
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
index c0aec0d4d664..3011d33eccbd 100644
--- a/include/drm/drm_mipi_dsi.h
+++ b/include/drm/drm_mipi_dsi.h
@@ -241,9 +241,9 @@ int mipi_dsi_shutdown_peripheral(struct mipi_dsi_device *dsi);
int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi);
int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
u16 value);
-ssize_t mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable);
-ssize_t mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi,
- const struct drm_dsc_picture_parameter_set *pps);
+int mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable);
+int mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi,
+ const struct drm_dsc_picture_parameter_set *pps);
ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
size_t size);
--
2.39.2
^ permalink raw reply related
* [PATCH v3 3/4] drm/mipi-dsi: add mipi_dsi_compression_mode_ext()
From: Dmitry Baryshkov @ 2024-04-01 23:51 UTC (permalink / raw)
To: Sumit Semwal, Caleb Connolly, Neil Armstrong, Jessica Zhang,
Sam Ravnborg, David Airlie, Daniel Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <20240402-lg-sw43408-panel-v3-0-144f17a11a56@linaro.org>
Add the extended version of mipi_dsi_compression_mode(). It provides
a way to specify the algorithm and PPS selector.
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
drivers/gpu/drm/drm_mipi_dsi.c | 33 +++++++++++++++++++++++++++------
include/drm/drm_mipi_dsi.h | 9 +++++++++
2 files changed, 36 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
index 9874ff6d4718..0ecbc811eb7a 100644
--- a/drivers/gpu/drm/drm_mipi_dsi.c
+++ b/drivers/gpu/drm/drm_mipi_dsi.c
@@ -645,19 +645,24 @@ int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
EXPORT_SYMBOL(mipi_dsi_set_maximum_return_packet_size);
/**
- * mipi_dsi_compression_mode() - enable/disable DSC on the peripheral
+ * mipi_dsi_compression_mode_ext() - enable/disable DSC on the peripheral
* @dsi: DSI peripheral device
* @enable: Whether to enable or disable the DSC
+ * @algo: Selected algorithm
+ * @pps_selector: The PPS selector
*
- * Enable or disable Display Stream Compression on the peripheral using the
- * default Picture Parameter Set and VESA DSC 1.1 algorithm.
+ * Enable or disable Display Stream Compression on the peripheral.
*
* Return: 0 on success or a negative error code on failure.
*/
-int mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
+int mipi_dsi_compression_mode_ext(struct mipi_dsi_device *dsi, bool enable,
+ enum mipi_dsi_compression_algo algo,
+ unsigned int pps_selector)
{
- /* Note: Needs updating for non-default PPS or algorithm */
- u8 tx[2] = { enable << 0, 0 };
+ u8 data = (enable << 0) |
+ (algo << 1) |
+ (pps_selector << 4);
+ u8 tx[2] = { data, 0 };
struct mipi_dsi_msg msg = {
.channel = dsi->channel,
.type = MIPI_DSI_COMPRESSION_MODE,
@@ -668,6 +673,22 @@ int mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
return (ret < 0) ? ret : 0;
}
+EXPORT_SYMBOL(mipi_dsi_compression_mode_ext);
+
+/**
+ * mipi_dsi_compression_mode() - enable/disable DSC on the peripheral
+ * @dsi: DSI peripheral device
+ * @enable: Whether to enable or disable the DSC
+ *
+ * Enable or disable Display Stream Compression on the peripheral using the
+ * default Picture Parameter Set and VESA DSC 1.1 algorithm.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable)
+{
+ return mipi_dsi_compression_mode_ext(dsi, enable, 0, MIPI_DSI_COMPRESSION_DSC);
+}
EXPORT_SYMBOL(mipi_dsi_compression_mode);
/**
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
index 3011d33eccbd..78cb7b688b1d 100644
--- a/include/drm/drm_mipi_dsi.h
+++ b/include/drm/drm_mipi_dsi.h
@@ -226,6 +226,12 @@ static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt)
return -EINVAL;
}
+enum mipi_dsi_compression_algo {
+ MIPI_DSI_COMPRESSION_DSC = 0,
+ MIPI_DSI_COMPRESSION_VENDOR = 3,
+ /* other two values are reserved, DSI 1.3 */
+};
+
struct mipi_dsi_device *
mipi_dsi_device_register_full(struct mipi_dsi_host *host,
const struct mipi_dsi_device_info *info);
@@ -242,6 +248,9 @@ int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi);
int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
u16 value);
int mipi_dsi_compression_mode(struct mipi_dsi_device *dsi, bool enable);
+int mipi_dsi_compression_mode_ext(struct mipi_dsi_device *dsi, bool enable,
+ unsigned int pps_selector,
+ enum mipi_dsi_compression_algo algo);
int mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi,
const struct drm_dsc_picture_parameter_set *pps);
--
2.39.2
^ permalink raw reply related
* [PATCH v3 4/4] drm: panel: Add LG sw43408 panel driver
From: Dmitry Baryshkov @ 2024-04-01 23:51 UTC (permalink / raw)
To: Sumit Semwal, Caleb Connolly, Neil Armstrong, Jessica Zhang,
Sam Ravnborg, David Airlie, Daniel Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-kernel, linux-arm-msm, Vinod Koul,
Caleb Connolly
In-Reply-To: <20240402-lg-sw43408-panel-v3-0-144f17a11a56@linaro.org>
From: Sumit Semwal <sumit.semwal@linaro.org>
LG SW43408 is 1080x2160, 4-lane MIPI-DSI panel, used in some Pixel3
phones.
Signed-off-by: Sumit Semwal <sumit.semwal@linaro.org>
[vinod: Add DSC support]
Signed-off-by: Vinod Koul <vkoul@kernel.org>
[caleb: cleanup and support turning off the panel]
Signed-off-by: Caleb Connolly <caleb@connolly.tech>
[DB: partially rewrote the driver and fixed DSC programming]
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
MAINTAINERS | 8 +
drivers/gpu/drm/panel/Kconfig | 11 ++
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-lg-sw43408.c | 326 +++++++++++++++++++++++++++++++
4 files changed, 346 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d36c19c1bf81..4cc43c16e07e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6789,6 +6789,14 @@ S: Maintained
F: Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml
F: drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c
+DRM DRIVER FOR LG SW43408 PANELS
+M: Sumit Semwal <sumit.semwal@linaro.org>
+M: Caleb Connolly <caleb.connolly@linaro.org>
+S: Maintained
+T: git git://anongit.freedesktop.org/drm/drm-misc
+F: Documentation/devicetree/bindings/display/panel/lg,sw43408.yaml
+F: drivers/gpu/drm/panel/panel-lg-sw43408.c
+
DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
M: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
S: Supported
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 6dc451f58a3e..a55e9437c8cf 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -335,6 +335,17 @@ config DRM_PANEL_LG_LG4573
Say Y here if you want to enable support for LG4573 RGB panel.
To compile this driver as a module, choose M here.
+config DRM_PANEL_LG_SW43408
+ tristate "LG SW43408 panel"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to enable support for LG sw43408 panel.
+ The panel has a 1080x2160 resolution and uses
+ 24 bit RGB per pixel. It provides a MIPI DSI interface to
+ the host and has a built-in LED backlight.
+
config DRM_PANEL_MAGNACHIP_D53E6EA8966
tristate "Magnachip D53E6EA8966 DSI panel"
depends on OF && SPI
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 24a02655d726..0b40b010e8e7 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o
obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o
obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
+obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
obj-$(CONFIG_DRM_PANEL_MAGNACHIP_D53E6EA8966) += panel-magnachip-d53e6ea8966.o
obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
obj-$(CONFIG_DRM_PANEL_NEWVISION_NV3051D) += panel-newvision-nv3051d.o
diff --git a/drivers/gpu/drm/panel/panel-lg-sw43408.c b/drivers/gpu/drm/panel/panel-lg-sw43408.c
new file mode 100644
index 000000000000..c7611bfa796b
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-lg-sw43408.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019-2024 Linaro Ltd
+ * Author: Sumit Semwal <sumit.semwal@linaro.org>
+ * Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+
+#define NUM_SUPPLIES 2
+
+struct sw43408_panel {
+ struct drm_panel base;
+ struct mipi_dsi_device *link;
+
+ const struct drm_display_mode *mode;
+
+ struct regulator_bulk_data supplies[NUM_SUPPLIES];
+
+ struct gpio_desc *reset_gpio;
+
+ struct drm_dsc_config dsc;
+};
+
+static inline struct sw43408_panel *to_panel_info(struct drm_panel *panel)
+{
+ return container_of(panel, struct sw43408_panel, base);
+}
+
+static int sw43408_unprepare(struct drm_panel *panel)
+{
+ struct sw43408_panel *ctx = to_panel_info(panel);
+ int ret;
+
+ ret = mipi_dsi_dcs_set_display_off(ctx->link);
+ if (ret < 0)
+ dev_err(panel->dev, "set_display_off cmd failed ret = %d\n", ret);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(ctx->link);
+ if (ret < 0)
+ dev_err(panel->dev, "enter_sleep cmd failed ret = %d\n", ret);
+
+ msleep(100);
+
+ gpiod_set_value(ctx->reset_gpio, 1);
+
+ return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+}
+
+static int sw43408_program(struct drm_panel *panel)
+{
+ struct sw43408_panel *ctx = to_panel_info(panel);
+ struct drm_dsc_picture_parameter_set pps;
+
+ mipi_dsi_dcs_write_seq(ctx->link, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
+
+ mipi_dsi_dcs_set_tear_on(ctx->link, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+
+ mipi_dsi_dcs_write_seq(ctx->link, 0x53, 0x0c, 0x30);
+ mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x00, 0x70, 0xdf, 0x00, 0x70, 0xdf);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xf7, 0x01, 0x49, 0x0c);
+
+ mipi_dsi_dcs_exit_sleep_mode(ctx->link);
+
+ msleep(135);
+
+ /* COMPRESSION_MODE moved after setting the PPS */
+
+ mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xac);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xe5,
+ 0x00, 0x3a, 0x00, 0x3a, 0x00, 0x0e, 0x10);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xb5,
+ 0x75, 0x60, 0x2d, 0x5d, 0x80, 0x00, 0x0a, 0x0b,
+ 0x00, 0x05, 0x0b, 0x00, 0x80, 0x0d, 0x0e, 0x40,
+ 0x00, 0x0c, 0x00, 0x16, 0x00, 0xb8, 0x00, 0x80,
+ 0x0d, 0x0e, 0x40, 0x00, 0x0c, 0x00, 0x16, 0x00,
+ 0xb8, 0x00, 0x81, 0x00, 0x03, 0x03, 0x03, 0x01,
+ 0x01);
+ msleep(85);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xcd,
+ 0x00, 0x00, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x16, 0x16);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xcb, 0x80, 0x5c, 0x07, 0x03, 0x28);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xc0, 0x02, 0x02, 0x0f);
+ mipi_dsi_dcs_write_seq(ctx->link, 0x55, 0x04, 0x61, 0xdb, 0x04, 0x70, 0xdb);
+ mipi_dsi_dcs_write_seq(ctx->link, 0xb0, 0xca);
+
+ mipi_dsi_dcs_set_display_on(ctx->link);
+
+ msleep(50);
+
+ ctx->link->mode_flags &= ~MIPI_DSI_MODE_LPM;
+
+ drm_dsc_pps_payload_pack(&pps, ctx->link->dsc);
+ mipi_dsi_picture_parameter_set(ctx->link, &pps);
+
+ /* This panel uses shifted PPS selectors:
+ * 1 if pps_identifier is 0
+ * 2 if pps_identifier is 1
+ */
+ mipi_dsi_compression_mode_ext(ctx->link, true,
+ MIPI_DSI_COMPRESSION_DSC, 1);
+
+ ctx->link->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ return 0;
+}
+
+static int sw43408_prepare(struct drm_panel *panel)
+{
+ struct sw43408_panel *ctx = to_panel_info(panel);
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(5000, 6000);
+
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(9000, 10000);
+ gpiod_set_value(ctx->reset_gpio, 1);
+ usleep_range(1000, 2000);
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(9000, 10000);
+
+ ret = sw43408_program(panel);
+ if (ret)
+ goto poweroff;
+
+ return 0;
+
+poweroff:
+ gpiod_set_value(ctx->reset_gpio, 1);
+ regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ return ret;
+}
+
+static int sw43408_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct sw43408_panel *ctx = to_panel_info(panel);
+
+ return drm_connector_helper_get_modes_fixed(connector, ctx->mode);
+}
+
+static int sw43408_backlight_update_status(struct backlight_device *bl)
+{
+ struct mipi_dsi_device *dsi = bl_get_data(bl);
+ uint16_t brightness = backlight_get_brightness(bl);
+
+ return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
+}
+
+const struct backlight_ops sw43408_backlight_ops = {
+ .update_status = sw43408_backlight_update_status,
+};
+
+static int sw43408_backlight_init(struct sw43408_panel *ctx)
+{
+ struct device *dev = &ctx->link->dev;
+ const struct backlight_properties props = {
+ .type = BACKLIGHT_PLATFORM,
+ .brightness = 255,
+ .max_brightness = 255,
+ };
+
+ ctx->base.backlight = devm_backlight_device_register(dev, dev_name(dev), dev,
+ ctx->link,
+ &sw43408_backlight_ops,
+ &props);
+
+ if (IS_ERR(ctx->base.backlight))
+ return dev_err_probe(dev, PTR_ERR(ctx->base.backlight),
+ "Failed to create backlight\n");
+
+ return 0;
+}
+
+static const struct drm_panel_funcs sw43408_funcs = {
+ .unprepare = sw43408_unprepare,
+ .prepare = sw43408_prepare,
+ .get_modes = sw43408_get_modes,
+};
+
+static const struct drm_display_mode sw43408_default_mode = {
+ .clock = 152340,
+
+ .hdisplay = 1080,
+ .hsync_start = 1080 + 20,
+ .hsync_end = 1080 + 20 + 32,
+ .htotal = 1080 + 20 + 32 + 20,
+
+ .vdisplay = 2160,
+ .vsync_start = 2160 + 20,
+ .vsync_end = 2160 + 20 + 4,
+ .vtotal = 2160 + 20 + 4 + 20,
+
+ .width_mm = 62,
+ .height_mm = 124,
+
+ .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+static const struct of_device_id sw43408_of_match[] = {
+ { .compatible = "lg,sw43408", .data = &sw43408_default_mode },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sw43408_of_match);
+
+static int sw43408_add(struct sw43408_panel *ctx)
+{
+ struct device *dev = &ctx->link->dev;
+ int ret;
+
+ ctx->supplies[0].supply = "vddi"; /* 1.88 V */
+ ctx->supplies[0].init_load_uA = 62000;
+ ctx->supplies[1].supply = "vpnl"; /* 3.0 V */
+ ctx->supplies[1].init_load_uA = 857000;
+
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
+ ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio)) {
+ dev_err(dev, "cannot get reset gpio %ld\n",
+ PTR_ERR(ctx->reset_gpio));
+ return PTR_ERR(ctx->reset_gpio);
+ }
+
+ ret = sw43408_backlight_init(ctx);
+ if (ret < 0)
+ return ret;
+
+ ctx->base.prepare_prev_first = true;
+
+ drm_panel_init(&ctx->base, dev, &sw43408_funcs, DRM_MODE_CONNECTOR_DSI);
+
+ drm_panel_add(&ctx->base);
+ return ret;
+}
+
+static int sw43408_probe(struct mipi_dsi_device *dsi)
+{
+ struct sw43408_panel *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->mode = of_device_get_match_data(&dsi->dev);
+ dsi->mode_flags = MIPI_DSI_MODE_LPM;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->lanes = 4;
+
+ ctx->link = dsi;
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ret = sw43408_add(ctx);
+ if (ret < 0)
+ return ret;
+
+ /* The panel is DSC panel only, set the dsc params */
+ ctx->dsc.dsc_version_major = 0x1;
+ ctx->dsc.dsc_version_minor = 0x1;
+
+ /* slice_count * slice_width == width */
+ ctx->dsc.slice_height = 16;
+ ctx->dsc.slice_width = 540;
+ ctx->dsc.slice_count = 2;
+ ctx->dsc.bits_per_component = 8;
+ ctx->dsc.bits_per_pixel = 8 << 4;
+ ctx->dsc.block_pred_enable = true;
+
+ dsi->dsc = &ctx->dsc;
+
+ return mipi_dsi_attach(dsi);
+}
+
+static void sw43408_remove(struct mipi_dsi_device *dsi)
+{
+ struct sw43408_panel *ctx = mipi_dsi_get_drvdata(dsi);
+ int ret;
+
+ ret = sw43408_unprepare(&ctx->base);
+ if (ret < 0)
+ dev_err(&dsi->dev, "failed to unprepare panel: %d\n",
+ ret);
+
+ ret = mipi_dsi_detach(dsi);
+ if (ret < 0)
+ dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", ret);
+
+ drm_panel_remove(&ctx->base);
+}
+
+static struct mipi_dsi_driver sw43408_driver = {
+ .driver = {
+ .name = "panel-lg-sw43408",
+ .of_match_table = sw43408_of_match,
+ },
+ .probe = sw43408_probe,
+ .remove = sw43408_remove,
+};
+module_mipi_dsi_driver(sw43408_driver);
+
+MODULE_AUTHOR("Sumit Semwal <sumit.semwal@linaro.org>");
+MODULE_DESCRIPTION("LG SW436408 MIPI-DSI LED panel");
+MODULE_LICENSE("GPL");
--
2.39.2
^ permalink raw reply related
* [PATCH v9 00/10] media: Add driver for the Raspberry Pi <5 CSI-2 receiver
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Dave Stevenson, David Plowman, Jean-Michel Hautbois, Hans Verkuil,
Naushir Patuck, Sakari Ailus, kernel-list, linux-rpi-kernel,
Florian Fainelli, Ray Jui, Scott Branden,
bcm-kernel-feedback-list, Conor Dooley, Krzysztof Kozlowski,
Rob Herring, devicetree
Hello everybody,
This patch series adds a new driver for the BCM2835 (and derivative)
CCP2/CSI2 camera interface named Unicam. This IP core is found in the
VC4-based Raspberry Pi, namely the Pi Zero, Pi 3 and Pi 4.
Camera support for Raspberry Pi 4 currently relies on a downstream
Unicam driver that live in the Raspberry Pi kernel tree ([1]). The
driver uses the V4L2 API, but works around the lack of features in V4L2
to properly support sensor embedded data. Since the Unicam driver
development by Raspberry Pi, some of those features have been merged in
the kernel (namely the V4L2 streams API) or are being developed (namely
generic metadata formats and subdev internal pads), with patches posted
for review on the linux-media mailing list ([2]).
This new upstream driver is based on the downstream code, extensively
reworked to use the new V4L2 APIs.
The series is based on a merge of
- v8 of the generic metadata and internal pads, rebased on v6.9-rc1 ([3])
- the downstream ISP driver ported to mainline ([4])
with a set of patches for the imx219 driver applied on top. For
convenience, it can be found in [5]. Note that the ISP driver is getting
upstreamed separately.
As in v7, I have left the imx219 patches out, as they don't need to be
bundled with the Unicam driver for review. They will be resubmitted
separately.
Compared to v8, I have addressed review comments (in patches 04/10 and
05/10) and fixed a few issues in the unicam driver. Please see
individual patches for details. A few fixes for v4l2-compliance in the
V4L2 subdev core have also been included in [3], they have been posted
to the linux-media mailing list separately.
The series starts with four patches that add the Unicam driver (04/10),
with new V4L2 pixel formats (01/10 and 02/10) and DT bindings (03/10).
The remaining patches cover DT integration (05/10 to 09/10) with a
sample DT overlay for the IMX219 camera module (10/10).
The patches have been tested on a Raspberry Pi 4 using an IMX219 camera
module (the Raspberry Pi camera v2), with libcamera. Updates are needed
to libcamera to use the new V4L2 APIs, patches have been posted to [6].
For manual testing with media-ctl, corresponding API updates to
v4l-utils have been posted to the linux-media mailing list ([7]) and are
available for convenience in a git branch at [8].
While more work is needed to be able to merge the generic metadata API
(namely implementing support for the latest API version in media-ctl and
v4l2-compliance), I'm happy with the unicam implementation, and I
believe we're really nearing completion. This series, along with the
libcamera support, help validating the new kernel APIs. We have reached
a point where we can start converting other sensor drivers from the
downstream Raspberry Pi kernel to the standard APIs for embedded data,
as well as integrating the APIs in the Raspberry Pi 5 CFE driver.
Here's the mandatory v4l2-compliance reports. It requires the v4l-utils
updates from [7]. There is a total of 2 errors and 4 warnings, all
related to the imx219 driver. They will be addressed separately, as part
of the effort to add embedded data support to the imx219 driver.
v4l2-compliance 1.27.0-5189, 64 bits, 64-bit time_t
v4l2-compliance SHA: 44bd7e27daac 2024-04-01 22:18:20
Compliance test for unicam device /dev/media0:
Media Driver Info:
Driver name : unicam
Model : unicam
Serial :
Bus info : platform:fe801000.csi
Media version : 6.9.0
Hardware revision: 0x00000000 (0)
Driver version : 6.9.0
Required ioctls:
test MEDIA_IOC_DEVICE_INFO: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/media0 open: OK
test MEDIA_IOC_DEVICE_INFO: OK
test for unlimited opens: OK
Media Controller ioctls:
test MEDIA_IOC_G_TOPOLOGY: OK
Entities: 4 Interfaces: 4 Pads: 8 Links: 7
test MEDIA_IOC_ENUM_ENTITIES/LINKS: OK
test MEDIA_IOC_SETUP_LINK: OK
Total for unicam device /dev/media0: 8, Succeeded: 8, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for unicam device /dev/video0:
Driver Info:
Driver name : unicam
Card type : unicam
Bus info : platform:fe801000.csi
Driver version : 6.9.0
Capabilities : 0xa4a00001
Video Capture
Metadata Capture
I/O MC
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x24200001
Video Capture
I/O MC
Streaming
Extended Pix Format
Media Driver Info:
Driver name : unicam
Model : unicam
Serial :
Bus info : platform:fe801000.csi
Media version : 6.9.0
Hardware revision: 0x00000000 (0)
Driver version : 6.9.0
Interface Info:
ID : 0x0300000d
Type : V4L Video
Entity Info:
ID : 0x0000000b (11)
Name : unicam-image
Function : V4L2 I/O
Flags : default
Pad 0x0100000c : 0: Sink
Link 0x0200000f: from remote pad 0x1000003 of entity 'unicam' (Video Interface Bridge): Data, Enabled, Immutable
Required ioctls:
test MC information (see 'Media Driver Info' above): OK
test VIDIOC_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/video0 open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 1 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Control ioctls (Input 0):
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
test VIDIOC_QUERYCTRL: OK (Not Supported)
test VIDIOC_G/S_CTRL: OK (Not Supported)
test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 0 Private Controls: 0
Format ioctls (Input 0):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK
Codec ioctls (Input 0):
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls (Input 0):
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK
test Requests: OK (Not Supported)
Total for unicam device /dev/video0: 48, Succeeded: 48, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for unicam device /dev/video1:
Driver Info:
Driver name : unicam
Card type : unicam
Bus info : platform:fe801000.csi
Driver version : 6.9.0
Capabilities : 0xa4a00001
Video Capture
Metadata Capture
I/O MC
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x24a00000
Metadata Capture
I/O MC
Streaming
Extended Pix Format
Media Driver Info:
Driver name : unicam
Model : unicam
Serial :
Bus info : platform:fe801000.csi
Media version : 6.9.0
Hardware revision: 0x00000000 (0)
Driver version : 6.9.0
Interface Info:
ID : 0x03000013
Type : V4L Video
Entity Info:
ID : 0x00000011 (17)
Name : unicam-embedded
Function : V4L2 I/O
Pad 0x01000012 : 0: Sink
Link 0x02000015: from remote pad 0x1000004 of entity 'unicam' (Video Interface Bridge): Data, Enabled, Immutable
Required ioctls:
test MC information (see 'Media Driver Info' above): OK
test VIDIOC_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/video1 open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 1 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Control ioctls (Input 0):
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
test VIDIOC_QUERYCTRL: OK (Not Supported)
test VIDIOC_G/S_CTRL: OK (Not Supported)
test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 0 Private Controls: 0
Format ioctls (Input 0):
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls (Input 0):
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls (Input 0):
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK
test Requests: OK (Not Supported)
Total for unicam device /dev/video1: 48, Succeeded: 48, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for unicam device /dev/v4l-subdev0:
Driver Info:
Driver version : 6.9.0
Capabilities : 0x00000002
Streams Support
Client Capabilities: 0x0000000000000003
streams interval-uses-which Media Driver Info:
Driver name : unicam
Model : unicam
Serial :
Bus info : platform:fe801000.csi
Media version : 6.9.0
Hardware revision: 0x00000000 (0)
Driver version : 6.9.0
Interface Info:
ID : 0x03000017
Type : V4L Sub-Device
Entity Info:
ID : 0x00000001 (1)
Name : unicam
Function : Video Interface Bridge
Pad 0x01000002 : 0: Sink
Link 0x02000009: from remote pad 0x1000006 of entity 'imx219 5-0010' (Camera Sensor): Data, Enabled, Immutable
Pad 0x01000003 : 1: Source
Link 0x0200000f: to remote pad 0x100000c of entity 'unicam-image' (V4L2 I/O): Data, Enabled, Immutable
Pad 0x01000004 : 2: Source
Link 0x02000015: to remote pad 0x1000012 of entity 'unicam-embedded' (V4L2 I/O): Data, Enabled, Immutable
Required ioctls:
test MC information (see 'Media Driver Info' above): OK
test VIDIOC_SUDBEV_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/v4l-subdev0 open: OK
test VIDIOC_SUBDEV_QUERYCAP: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 0 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Sub-Device routing ioctls:
test Try VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
test Active VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
Sub-Device ioctls (Sink Pad 0):
Try Stream 0
test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Try VIDIOC_SUBDEV_G/S_FMT: OK
test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
Active Stream 0
test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Active VIDIOC_SUBDEV_G/S_FMT: OK
test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
Sub-Device ioctls (Source Pad 1):
Try Stream 0
test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Try VIDIOC_SUBDEV_G/S_FMT: OK
test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
Active Stream 0
test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Active VIDIOC_SUBDEV_G/S_FMT: OK
test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
Sub-Device ioctls (Source Pad 2):
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
test VIDIOC_QUERYCTRL: OK (Not Supported)
test VIDIOC_G/S_CTRL: OK (Not Supported)
test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 0 Private Controls: 0
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK (Not Supported)
test VIDIOC_TRY_FMT: OK (Not Supported)
test VIDIOC_S_FMT: OK (Not Supported)
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK (Not Supported)
test Requests: OK (Not Supported)
Total for unicam device /dev/v4l-subdev0: 62, Succeeded: 62, Failed: 0, Warnings: 0
--------------------------------------------------------------------------------
Compliance test for unicam device /dev/v4l-subdev1:
Driver Info:
Driver version : 6.9.0
Capabilities : 0x00000002
Streams Support
Client Capabilities: 0x0000000000000003
streams interval-uses-which Media Driver Info:
Driver name : unicam
Model : unicam
Serial :
Bus info : platform:fe801000.csi
Media version : 6.9.0
Hardware revision: 0x00000000 (0)
Driver version : 6.9.0
Interface Info:
ID : 0x03000019
Type : V4L Sub-Device
Entity Info:
ID : 0x00000005 (5)
Name : imx219 5-0010
Function : Camera Sensor
Pad 0x01000006 : 0: Source
Link 0x02000009: to remote pad 0x1000002 of entity 'unicam' (Video Interface Bridge): Data, Enabled, Immutable
Pad 0x01000007 : 1: Sink, 00000008
Pad 0x01000008 : 2: Sink, 00000008
Required ioctls:
test MC information (see 'Media Driver Info' above): OK
test VIDIOC_SUDBEV_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/v4l-subdev1 open: OK
test VIDIOC_SUBDEV_QUERYCAP: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 0 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Sub-Device routing ioctls:
test Try VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
test Active VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
Sub-Device ioctls (Source Pad 0):
Try Stream 0
test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Try VIDIOC_SUBDEV_G/S_FMT: OK
warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
Try Stream 1
fail: v4l2-test-subdevs.cpp(162): doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse)
fail: v4l2-test-subdevs.cpp(270): ret && ret != ENOTTY
test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: FAIL
test Try VIDIOC_SUBDEV_G/S_FMT: OK
test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
Active Stream 0
test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Active VIDIOC_SUBDEV_G/S_FMT: OK
warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
Active Stream 1
fail: v4l2-test-subdevs.cpp(162): doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse)
fail: v4l2-test-subdevs.cpp(270): ret && ret != ENOTTY
test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: FAIL
test Active VIDIOC_SUBDEV_G/S_FMT: OK
test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
Sub-Device ioctls (Sink Pad 1):
Try Stream 0
test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Try VIDIOC_SUBDEV_G/S_FMT: OK
warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
Active Stream 0
test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Active VIDIOC_SUBDEV_G/S_FMT: OK
warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
Sub-Device ioctls (Sink Pad 2):
Try Stream 0
test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Try VIDIOC_SUBDEV_G/S_FMT: OK
test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
Active Stream 0
test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
test Active VIDIOC_SUBDEV_G/S_FMT: OK
test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 20 Private Controls: 0
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK (Not Supported)
test VIDIOC_TRY_FMT: OK (Not Supported)
test VIDIOC_S_FMT: OK (Not Supported)
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK (Not Supported)
test Requests: OK (Not Supported)
Total for unicam device /dev/v4l-subdev1: 76, Succeeded: 74, Failed: 2, Warnings: 4
Grand Total for unicam device /dev/media0: 242, Succeeded: 240, Failed: 2, Warnings: 4
[1] https://github.com/raspberrypi/linux/tree/rpi-6.1.y/drivers/media/platform/bcm2835
[2] https://lore.kernel.org/linux-media/20240313072516.241106-1-sakari.ailus@linux.intel.com/
[3] https://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git/log/?h=rpi/v6.9/metadata/v8
[4] https://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git/log/?h=rpi/v6.9/isp/v2
[5] https://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git/log/?h=rpi/v6.9/unicam/next
[6] https://lists.libcamera.org/pipermail/libcamera-devel/2024-March/040711.html
[7] https://lore.kernel.org/linux-media/20240402000033.4007-1-laurent.pinchart@ideasonboard.com/
[8] https://git.linuxtv.org/pinchartl/v4l-utils.git/log/?h=metadata
Dave Stevenson (2):
dt-bindings: media: Add bindings for bcm2835-unicam
media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface
Jean-Michel Hautbois (3):
media: v4l: Add V4L2-PIX-FMT-Y12P format
media: v4l: Add V4L2-PIX-FMT-Y14P format
ARM: dts: bcm2835: Add Unicam CSI nodes
Laurent Pinchart (3):
ARM: dts: bcm2835-rpi: Move duplicate firmware-clocks to
bcm2835-rpi.dtsi
ARM: dts: bcm2711-rpi-4-b: Add CAM1 regulator
[DNI] arm64: dts: broadcom: Add overlay for Raspberry Pi 4B IMX219
camera
Uwe Kleine-König (2):
ARM: dts: bcm2711-rpi: Add pinctrl-based multiplexing for I2C0
ARM: dts: bcm2711-rpi-cm4-io: Add RTC on I2C0
.../bindings/media/brcm,bcm2835-unicam.yaml | 127 +
.../media/v4l/pixfmt-yuv-luma.rst | 48 +
MAINTAINERS | 7 +
.../arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts | 7 +
.../boot/dts/broadcom/bcm2711-rpi-cm4-io.dts | 9 +
arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi | 34 +-
arch/arm/boot/dts/broadcom/bcm2711.dtsi | 8 +
.../boot/dts/broadcom/bcm2835-rpi-common.dtsi | 7 -
arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi | 19 +
arch/arm/boot/dts/broadcom/bcm283x.dtsi | 24 +
arch/arm64/boot/dts/broadcom/Makefile | 4 +
.../dts/broadcom/bcm2711-rpi-4-b-imx219.dtso | 65 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/broadcom/Kconfig | 23 +
drivers/media/platform/broadcom/Makefile | 3 +
.../platform/broadcom/bcm2835-unicam-regs.h | 246 ++
.../media/platform/broadcom/bcm2835-unicam.c | 2745 +++++++++++++++++
drivers/media/v4l2-core/v4l2-ioctl.c | 2 +
include/uapi/linux/videodev2.h | 2 +
20 files changed, 3370 insertions(+), 12 deletions(-)
create mode 100644 Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
create mode 100644 arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b-imx219.dtso
create mode 100644 drivers/media/platform/broadcom/Kconfig
create mode 100644 drivers/media/platform/broadcom/Makefile
create mode 100644 drivers/media/platform/broadcom/bcm2835-unicam-regs.h
create mode 100644 drivers/media/platform/broadcom/bcm2835-unicam.c
base-commit: 58abf3672a73558149fa567eafff8d5b1cc0446b
--
Regards,
Laurent Pinchart
^ permalink raw reply
* [PATCH v9 03/10] dt-bindings: media: Add bindings for bcm2835-unicam
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Dave Stevenson, David Plowman, Jean-Michel Hautbois, Hans Verkuil,
Naushir Patuck, Sakari Ailus, kernel-list, linux-rpi-kernel,
Florian Fainelli, Ray Jui, Scott Branden,
bcm-kernel-feedback-list, Conor Dooley, Krzysztof Kozlowski,
Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Introduce the dt-bindings documentation for bcm2835 CCP2/CSI2 Unicam
camera interface.
Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
Co-developed-by: Naushir Patuck <naush@raspberrypi.com>
Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
Co-developed-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>
Signed-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes since v7:
- Make bus-type mandatory
- Refuse additional properties for endpoint
Changes since v5:
- Squash MAINTAINERS changes in
Changes since v3:
- Make MAINTAINERS its own patch
- Describe the reg and clocks correctly
- Use a vendor entry for the number of data lanes
---
.../bindings/media/brcm,bcm2835-unicam.yaml | 127 ++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 133 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
diff --git a/Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml b/Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
new file mode 100644
index 000000000000..5fb5d60f069a
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
@@ -0,0 +1,127 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/brcm,bcm2835-unicam.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Broadcom BCM283x Camera Interface (Unicam)
+
+maintainers:
+ - Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+
+description: |-
+ The Unicam block on BCM283x SoCs is the receiver for either
+ CSI-2 or CCP2 data from image sensors or similar devices.
+
+ The main platform using this SoC is the Raspberry Pi family of boards. On
+ the Pi the VideoCore firmware can also control this hardware block, and
+ driving it from two different processors will cause issues. To avoid this,
+ the firmware checks the device tree configuration during boot. If it finds
+ device tree nodes whose name starts with 'csi' then it will stop the firmware
+ accessing the block, and it can then safely be used via the device tree
+ binding.
+
+properties:
+ compatible:
+ const: brcm,bcm2835-unicam
+
+ reg:
+ items:
+ - description: Unicam block.
+ - description: Clock Manager Image (CMI) block.
+
+ reg-names:
+ items:
+ - const: unicam
+ - const: cmi
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: Clock to drive the LP state machine of Unicam.
+ - description: Clock for the VPU (core clock).
+
+ clock-names:
+ items:
+ - const: lp
+ - const: vpu
+
+ power-domains:
+ items:
+ - description: Unicam power domain
+
+ brcm,num-data-lanes:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [ 2, 4 ]
+ description: |
+ Number of CSI-2 data lanes supported by this Unicam instance. The number
+ of data lanes actively used is specified with the data-lanes endpoint
+ property.
+
+ port:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ additionalProperties: false
+
+ properties:
+ bus-type:
+ enum: [ 3, 4 ]
+
+ clock-noncontinuous: true
+ data-lanes: true
+ remote-endpoint: true
+
+ required:
+ - bus-type
+ - data-lanes
+ - remote-endpoint
+
+ required:
+ - endpoint
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - clocks
+ - clock-names
+ - power-domains
+ - brcm,num-data-lanes
+ - port
+
+additionalProperties: False
+
+examples:
+ - |
+ #include <dt-bindings/clock/bcm2835.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/media/video-interfaces.h>
+ #include <dt-bindings/power/raspberrypi-power.h>
+
+ csi1: csi@7e801000 {
+ compatible = "brcm,bcm2835-unicam";
+ reg = <0x7e801000 0x800>,
+ <0x7e802004 0x4>;
+ reg-names = "unicam", "cmi";
+ interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clocks BCM2835_CLOCK_CAM1>,
+ <&firmware_clocks 4>;
+ clock-names = "lp", "vpu";
+ power-domains = <&power RPI_POWER_DOMAIN_UNICAM1>;
+ brcm,num-data-lanes = <2>;
+ port {
+ csi1_ep: endpoint {
+ remote-endpoint = <&imx219_0>;
+ bus-type = <MEDIA_BUS_TYPE_CSI2_DPHY>;
+ data-lanes = <1 2>;
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index cfa532ead715..e81ab3479702 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4034,6 +4034,12 @@ N: bcm113*
N: bcm216*
N: kona
+BROADCOM BCM2835 CAMERA DRIVERS
+M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+L: linux-media@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
+
BROADCOM BCM47XX MIPS ARCHITECTURE
M: Hauke Mehrtens <hauke@hauke-m.de>
M: Rafał Miłecki <zajec5@gmail.com>
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* [PATCH v9 04/10] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Dave Stevenson, David Plowman, Jean-Michel Hautbois, Hans Verkuil,
Naushir Patuck, Sakari Ailus, kernel-list, linux-rpi-kernel,
Florian Fainelli, Ray Jui, Scott Branden,
bcm-kernel-feedback-list, Conor Dooley, Krzysztof Kozlowski,
Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
From: Dave Stevenson <dave.stevenson@raspberrypi.com>
Add a driver for the Unicam camera receiver block on BCM283x processors.
It is represented as two video device nodes: unicam-image and
unicam-embedded which are connected to an internal subdev (named
unicam-subdev) in order to manage streams routing.
Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
Co-developed-by: Naushir Patuck <naush@raspberrypi.com>
Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
Co-developed-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>
Signed-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>
Co-developed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes since v8:
- Use MIPI_CSI2_DT_* macros
- Disable image stream on start error
- Move hardware configuration to unicam_sd_enable_streams()
- Get VC and DT from frame descriptor
- Don't cache fmtinfo in unicam_node
- Calculate line_int_freq based on subdev format
- Fix try_fmt_meta regression from v5
Changes since v7:
- Indentation, line wrap and white space fixes
- Add copyright notice for Ideas on Board
- Use unsigned values for shifts in macro
- Replace condition and assignment with max()
- Don't set the serial to an empty string manually
- Don't use loop for lane clocks setting
- Store PUM value in struct unicam_format_info
Changes since v6:
- Fix typos in comments
- Drop outdated comment
- Indentation fixes
- Turn UNICAM_SD_PAD_* into an enum
- Drop unicam_format_info.metadata_fmt
- Remove unneeded dev_dbg()
- Report meta frame sizes as V4L2_FRMSIZE_TYPE_STEPWISE
- Drop stray semicolons
- Set V4L2_FMT_FLAG_META_LINE_BASED for metadata format
- Rename error label
- Use .get_mbus_config() to get number of data lanes
- Drop minimum of 3 buffers in .queue_setup()
- Merge locks for the two video nodes
- Rework start/stop to avoid race conditions
Changes since v5:
- Move to drivers/media/platform/broadcom/
- Port to the upstream V4L2 streams API
- Rebase on latest metadata API proposal
- Add missing error message
- Drop unneeded documentation block for unicam_isr()
- Drop unneeded dev_dbg() and dev_err() messages
- Drop unneeded streams_mask and fmt checks
- Drop unused unicam_sd_pad_is_sink()
- Drop unneeded includes
- Drop v4l2_ctrl_subscribe_event() call
- Use pm_runtime_resume_and_get()
- Indentation and line wrap fixes
- Let the framework set bus_info
- Use v4l2_fwnode_endpoint_parse()
- Fix media device cleanup
- Drop lane reordering checks
- Fix subdev state locking
- Drop extra debug messages
- Move clock handling to runtime PM handlers
- Reorder functions
- Rename init functions for more clarity
- Initialize runtime PM earlier
- Clarify error messages
- Simplify subdev init with local variable
- Fix subdev cleanup
- Fix typos and indentation
- Don't initialize local variables needlessly
- Simplify num lanes check
- Fix metadata handling in subdev set_fmt
- Drop manual fallback to .s_stream()
- Pass v4l2_pix_format to unicam_calc_format_size_bpl()
- Simplify unicam_set_default_format()
- Fix default format settings
- Add busy check in unicam_s_fmt_meta()
- Add missing \n at end of format strings
- Fix metadata handling in subdev set_fmt
- Fix locking when starting streaming
- Return buffers from start streaming fails
- Fix format validation for metadata node
- Use video_device_pipeline_{start,stop}() helpers
- Simplify format enumeration
- Drop unset variable
- Update MAINTAINERS entry
- Update to the upstream v4l2_async_nf API
- Update to the latest subdev routing API
- Update to the latest subdev state API
- Move from subdev .init_cfg() to .init_state()
- Update to the latest videobuf2 API
- Fix v4l2_subdev_enable_streams() error check
- Use correct pad for the connected subdev
- Return buffers to vb2 when start streaming fails
- Improve debugging in start streaming handler
- Simplify DMA address management
- Drop comment about bcm2835-camera driver
- Clarify comments that explain min/max sizes
- Pass v4l2_pix_format to unicam_try_fmt()
- Drop unneeded local variables
- Rename image-related constants and functions
- Turn unicam_fmt.metadata_fmt into bool
- Rename unicam_fmt to unicam_format_info
- Rename unicam_format_info variables to fmtinfo
- Rename unicam_node.v_fmt to fmt
- Add metadata formats for RAW10, RAW12 and RAW14
- Make metadata formats line-based
- Validate format on metadata video device
- Add Co-devlopped-by tags
Changes since v3:
- Add the vendor prefix for DT name
- Use the reg-names in DT parsing
- Remove MAINTAINERS entry
Changes since v2:
- Change code organization
- Remove unused variables
- Correct the fmt_meta functions
- Rewrite the start/stop streaming
- You can now start the image node alone, but not the metadata one
- The buffers are allocated per-node
- only the required stream is started, if the route exists and is
enabled
- Prefix the macros with UNICAM_ to not have too generic names
- Drop colorspace support
Changes since v1:
- Replace the unicam_{info,debug,error} macros with dev_*()
---
MAINTAINERS | 1 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/broadcom/Kconfig | 23 +
drivers/media/platform/broadcom/Makefile | 3 +
.../platform/broadcom/bcm2835-unicam-regs.h | 246 ++
.../media/platform/broadcom/bcm2835-unicam.c | 2745 +++++++++++++++++
7 files changed, 3020 insertions(+)
create mode 100644 drivers/media/platform/broadcom/Kconfig
create mode 100644 drivers/media/platform/broadcom/Makefile
create mode 100644 drivers/media/platform/broadcom/bcm2835-unicam-regs.h
create mode 100644 drivers/media/platform/broadcom/bcm2835-unicam.c
diff --git a/MAINTAINERS b/MAINTAINERS
index e81ab3479702..bc39709151b9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4039,6 +4039,7 @@ M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
L: linux-media@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
+F: drivers/media/platform/bcm2835/
BROADCOM BCM47XX MIPS ARCHITECTURE
M: Hauke Mehrtens <hauke@hauke-m.de>
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 91e54215de3a..2d79bfc68c15 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -67,6 +67,7 @@ source "drivers/media/platform/amlogic/Kconfig"
source "drivers/media/platform/amphion/Kconfig"
source "drivers/media/platform/aspeed/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/broadcom/Kconfig"
source "drivers/media/platform/cadence/Kconfig"
source "drivers/media/platform/chips-media/Kconfig"
source "drivers/media/platform/intel/Kconfig"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 3296ec1ebe16..da17301f7439 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -10,6 +10,7 @@ obj-y += amlogic/
obj-y += amphion/
obj-y += aspeed/
obj-y += atmel/
+obj-y += broadcom/
obj-y += cadence/
obj-y += chips-media/
obj-y += intel/
diff --git a/drivers/media/platform/broadcom/Kconfig b/drivers/media/platform/broadcom/Kconfig
new file mode 100644
index 000000000000..cc2c9afcc948
--- /dev/null
+++ b/drivers/media/platform/broadcom/Kconfig
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config VIDEO_BCM2835_UNICAM
+ tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+ depends on PM
+ depends on VIDEO_DEV
+ select MEDIA_CONTROLLER
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
+ select VIDEOBUF2_DMA_CONTIG
+ help
+ Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
+ This is a V4L2 driver that controls the CSI-2 receiver directly,
+ independently from the VC4 firmware.
+
+ This driver is mutually exclusive with the use of bcm2835-camera. The
+ firmware will disable all access to the peripheral from within the
+ firmware if it finds a DT node using it, and bcm2835-camera will
+ therefore fail to probe.
+
+ To compile this driver as a module, choose M here. The module will be
+ called bcm2835-unicam.
diff --git a/drivers/media/platform/broadcom/Makefile b/drivers/media/platform/broadcom/Makefile
new file mode 100644
index 000000000000..03d2045aba2e
--- /dev/null
+++ b/drivers/media/platform/broadcom/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
diff --git a/drivers/media/platform/broadcom/bcm2835-unicam-regs.h b/drivers/media/platform/broadcom/bcm2835-unicam-regs.h
new file mode 100644
index 000000000000..fce6fa92707c
--- /dev/null
+++ b/drivers/media/platform/broadcom/bcm2835-unicam-regs.h
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/*
+ * Copyright (C) 2017-2020 Raspberry Pi Trading.
+ * Dave Stevenson <dave.stevenson@raspberrypi.com>
+ */
+
+#ifndef VC4_REGS_UNICAM_H
+#define VC4_REGS_UNICAM_H
+
+#include <linux/bits.h>
+
+/*
+ * The following values are taken from files found within the code drop
+ * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
+ * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
+ * They have been modified to be only the register offset.
+ */
+#define UNICAM_CTRL 0x000
+#define UNICAM_STA 0x004
+#define UNICAM_ANA 0x008
+#define UNICAM_PRI 0x00c
+#define UNICAM_CLK 0x010
+#define UNICAM_CLT 0x014
+#define UNICAM_DAT0 0x018
+#define UNICAM_DAT1 0x01c
+#define UNICAM_DAT2 0x020
+#define UNICAM_DAT3 0x024
+#define UNICAM_DLT 0x028
+#define UNICAM_CMP0 0x02c
+#define UNICAM_CMP1 0x030
+#define UNICAM_CAP0 0x034
+#define UNICAM_CAP1 0x038
+#define UNICAM_ICTL 0x100
+#define UNICAM_ISTA 0x104
+#define UNICAM_IDI0 0x108
+#define UNICAM_IPIPE 0x10c
+#define UNICAM_IBSA0 0x110
+#define UNICAM_IBEA0 0x114
+#define UNICAM_IBLS 0x118
+#define UNICAM_IBWP 0x11c
+#define UNICAM_IHWIN 0x120
+#define UNICAM_IHSTA 0x124
+#define UNICAM_IVWIN 0x128
+#define UNICAM_IVSTA 0x12c
+#define UNICAM_ICC 0x130
+#define UNICAM_ICS 0x134
+#define UNICAM_IDC 0x138
+#define UNICAM_IDPO 0x13c
+#define UNICAM_IDCA 0x140
+#define UNICAM_IDCD 0x144
+#define UNICAM_IDS 0x148
+#define UNICAM_DCS 0x200
+#define UNICAM_DBSA0 0x204
+#define UNICAM_DBEA0 0x208
+#define UNICAM_DBWP 0x20c
+#define UNICAM_DBCTL 0x300
+#define UNICAM_IBSA1 0x304
+#define UNICAM_IBEA1 0x308
+#define UNICAM_IDI1 0x30c
+#define UNICAM_DBSA1 0x310
+#define UNICAM_DBEA1 0x314
+#define UNICAM_MISC 0x400
+
+/*
+ * The following bitmasks are from the kernel released by Broadcom
+ * for Android - https://android.googlesource.com/kernel/bcm/
+ * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
+ * Unicam block as BCM2835, as defined in eg
+ * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
+ * Values reworked to use the kernel BIT and GENMASK macros.
+ *
+ * Some of the bit mnenomics have been amended to match the datasheet.
+ */
+/* UNICAM_CTRL Register */
+#define UNICAM_CPE BIT(0)
+#define UNICAM_MEM BIT(1)
+#define UNICAM_CPR BIT(2)
+#define UNICAM_CPM_MASK GENMASK(3, 3)
+#define UNICAM_CPM_CSI2 0
+#define UNICAM_CPM_CCP2 1
+#define UNICAM_SOE BIT(4)
+#define UNICAM_DCM_MASK GENMASK(5, 5)
+#define UNICAM_DCM_STROBE 0
+#define UNICAM_DCM_DATA 1
+#define UNICAM_SLS BIT(6)
+#define UNICAM_PFT_MASK GENMASK(11, 8)
+#define UNICAM_OET_MASK GENMASK(20, 12)
+
+/* UNICAM_STA Register */
+#define UNICAM_SYN BIT(0)
+#define UNICAM_CS BIT(1)
+#define UNICAM_SBE BIT(2)
+#define UNICAM_PBE BIT(3)
+#define UNICAM_HOE BIT(4)
+#define UNICAM_PLE BIT(5)
+#define UNICAM_SSC BIT(6)
+#define UNICAM_CRCE BIT(7)
+#define UNICAM_OES BIT(8)
+#define UNICAM_IFO BIT(9)
+#define UNICAM_OFO BIT(10)
+#define UNICAM_BFO BIT(11)
+#define UNICAM_DL BIT(12)
+#define UNICAM_PS BIT(13)
+#define UNICAM_IS BIT(14)
+#define UNICAM_PI0 BIT(15)
+#define UNICAM_PI1 BIT(16)
+#define UNICAM_FSI_S BIT(17)
+#define UNICAM_FEI_S BIT(18)
+#define UNICAM_LCI_S BIT(19)
+#define UNICAM_BUF0_RDY BIT(20)
+#define UNICAM_BUF0_NO BIT(21)
+#define UNICAM_BUF1_RDY BIT(22)
+#define UNICAM_BUF1_NO BIT(23)
+#define UNICAM_DI BIT(24)
+
+#define UNICAM_STA_MASK_ALL \
+ (UNICAM_SBE | UNICAM_PBE | UNICAM_HOE | UNICAM_PLE | UNICAM_SSC | \
+ UNICAM_CRCE | UNICAM_IFO | UNICAM_OFO | UNICAM_DL | UNICAM_PS | \
+ UNICAM_PI0 | UNICAM_PI1)
+
+/* UNICAM_ANA Register */
+#define UNICAM_APD BIT(0)
+#define UNICAM_BPD BIT(1)
+#define UNICAM_AR BIT(2)
+#define UNICAM_DDL BIT(3)
+#define UNICAM_CTATADJ_MASK GENMASK(7, 4)
+#define UNICAM_PTATADJ_MASK GENMASK(11, 8)
+
+/* UNICAM_PRI Register */
+#define UNICAM_PE BIT(0)
+#define UNICAM_PT_MASK GENMASK(2, 1)
+#define UNICAM_NP_MASK GENMASK(7, 4)
+#define UNICAM_PP_MASK GENMASK(11, 8)
+#define UNICAM_BS_MASK GENMASK(15, 12)
+#define UNICAM_BL_MASK GENMASK(17, 16)
+
+/* UNICAM_CLK Register */
+#define UNICAM_CLE BIT(0)
+#define UNICAM_CLPD BIT(1)
+#define UNICAM_CLLPE BIT(2)
+#define UNICAM_CLHSE BIT(3)
+#define UNICAM_CLTRE BIT(4)
+#define UNICAM_CLAC_MASK GENMASK(8, 5)
+#define UNICAM_CLSTE BIT(29)
+
+/* UNICAM_CLT Register */
+#define UNICAM_CLT1_MASK GENMASK(7, 0)
+#define UNICAM_CLT2_MASK GENMASK(15, 8)
+
+/* UNICAM_DATn Registers */
+#define UNICAM_DLE BIT(0)
+#define UNICAM_DLPD BIT(1)
+#define UNICAM_DLLPE BIT(2)
+#define UNICAM_DLHSE BIT(3)
+#define UNICAM_DLTRE BIT(4)
+#define UNICAM_DLSM BIT(5)
+#define UNICAM_DLFO BIT(28)
+#define UNICAM_DLSTE BIT(29)
+
+#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE | UNICAM_DLFO)
+
+/* UNICAM_DLT Register */
+#define UNICAM_DLT1_MASK GENMASK(7, 0)
+#define UNICAM_DLT2_MASK GENMASK(15, 8)
+#define UNICAM_DLT3_MASK GENMASK(23, 16)
+
+/* UNICAM_ICTL Register */
+#define UNICAM_FSIE BIT(0)
+#define UNICAM_FEIE BIT(1)
+#define UNICAM_IBOB BIT(2)
+#define UNICAM_FCM BIT(3)
+#define UNICAM_TFC BIT(4)
+#define UNICAM_LIP_MASK GENMASK(6, 5)
+#define UNICAM_LCIE_MASK GENMASK(28, 16)
+
+/* UNICAM_IDI0/1 Register */
+#define UNICAM_ID0_MASK GENMASK(7, 0)
+#define UNICAM_ID1_MASK GENMASK(15, 8)
+#define UNICAM_ID2_MASK GENMASK(23, 16)
+#define UNICAM_ID3_MASK GENMASK(31, 24)
+
+/* UNICAM_ISTA Register */
+#define UNICAM_FSI BIT(0)
+#define UNICAM_FEI BIT(1)
+#define UNICAM_LCI BIT(2)
+
+#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI | UNICAM_FEI | UNICAM_LCI)
+
+/* UNICAM_IPIPE Register */
+#define UNICAM_PUM_MASK GENMASK(2, 0)
+/* Unpacking modes */
+#define UNICAM_PUM_NONE 0
+#define UNICAM_PUM_UNPACK6 1
+#define UNICAM_PUM_UNPACK7 2
+#define UNICAM_PUM_UNPACK8 3
+#define UNICAM_PUM_UNPACK10 4
+#define UNICAM_PUM_UNPACK12 5
+#define UNICAM_PUM_UNPACK14 6
+#define UNICAM_PUM_UNPACK16 7
+#define UNICAM_DDM_MASK GENMASK(6, 3)
+#define UNICAM_PPM_MASK GENMASK(9, 7)
+/* Packing modes */
+#define UNICAM_PPM_NONE 0
+#define UNICAM_PPM_PACK8 1
+#define UNICAM_PPM_PACK10 2
+#define UNICAM_PPM_PACK12 3
+#define UNICAM_PPM_PACK14 4
+#define UNICAM_PPM_PACK16 5
+#define UNICAM_DEM_MASK GENMASK(11, 10)
+#define UNICAM_DEBL_MASK GENMASK(14, 12)
+#define UNICAM_ICM_MASK GENMASK(16, 15)
+#define UNICAM_IDM_MASK GENMASK(17, 17)
+
+/* UNICAM_ICC Register */
+#define UNICAM_ICFL_MASK GENMASK(4, 0)
+#define UNICAM_ICFH_MASK GENMASK(9, 5)
+#define UNICAM_ICST_MASK GENMASK(12, 10)
+#define UNICAM_ICLT_MASK GENMASK(15, 13)
+#define UNICAM_ICLL_MASK GENMASK(31, 16)
+
+/* UNICAM_DCS Register */
+#define UNICAM_DIE BIT(0)
+#define UNICAM_DIM BIT(1)
+#define UNICAM_DBOB BIT(3)
+#define UNICAM_FDE BIT(4)
+#define UNICAM_LDP BIT(5)
+#define UNICAM_EDL_MASK GENMASK(15, 8)
+
+/* UNICAM_DBCTL Register */
+#define UNICAM_DBEN BIT(0)
+#define UNICAM_BUF0_IE BIT(1)
+#define UNICAM_BUF1_IE BIT(2)
+
+/* UNICAM_CMP[0,1] register */
+#define UNICAM_PCE BIT(31)
+#define UNICAM_GI BIT(9)
+#define UNICAM_CPH BIT(8)
+#define UNICAM_PCVC_MASK GENMASK(7, 6)
+#define UNICAM_PCDT_MASK GENMASK(5, 0)
+
+/* UNICAM_MISC register */
+#define UNICAM_FL0 BIT(6)
+#define UNICAM_FL1 BIT(9)
+
+#endif
diff --git a/drivers/media/platform/broadcom/bcm2835-unicam.c b/drivers/media/platform/broadcom/bcm2835-unicam.c
new file mode 100644
index 000000000000..1418f209d6ad
--- /dev/null
+++ b/drivers/media/platform/broadcom/bcm2835-unicam.c
@@ -0,0 +1,2745 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BCM283x / BCM271x Unicam Capture Driver
+ *
+ * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
+ * Copyright (C) 2024 - Ideas on Board
+ *
+ * Dave Stevenson <dave.stevenson@raspberrypi.com>
+ *
+ * Based on TI am437x driver by
+ * Benoit Parrot <bparrot@ti.com>
+ * Lad, Prabhakar <prabhakar.csengg@gmail.com>
+ *
+ * and TI CAL camera interface driver by
+ * Benoit Parrot <bparrot@ti.com>
+ *
+ *
+ * There are two camera drivers in the kernel for BCM283x - this one and
+ * bcm2835-camera (currently in staging).
+ *
+ * This driver directly controls the Unicam peripheral - there is no
+ * involvement with the VideoCore firmware. Unicam receives CSI-2 or CCP2 data
+ * and writes it into SDRAM. The only potential processing options are to
+ * repack Bayer data into an alternate format, and applying windowing. The
+ * repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P to
+ * V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12, but
+ * not generically up to V4L2_PIX_FMT_Sxxxx16. Support for windowing may be
+ * added later.
+ *
+ * It should be possible to connect this driver to any sensor with a suitable
+ * output interface and V4L2 subdevice driver.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "bcm2835-unicam-regs.h"
+
+#define UNICAM_MODULE_NAME "unicam"
+
+/*
+ * Unicam must request a minimum of 250Mhz from the VPU clock.
+ * Otherwise the input FIFOs overrun and cause image corruption.
+ */
+#define UNICAM_MIN_VPU_CLOCK_RATE (250 * 1000 * 1000)
+
+/* Unicam has an internal DMA alignment constraint of 16 bytes for each line. */
+#define UNICAM_DMA_BPL_ALIGNMENT 16
+
+/*
+ * The image stride is stored in a 16 bit register, and needs to be aligned to
+ * the DMA constraint. As the ISP in the same SoC has a 32 bytes alignment
+ * constraint on its input, set the image stride alignment to 32 bytes here as
+ * well to avoid incompatible configurations.
+ */
+#define UNICAM_IMAGE_BPL_ALIGNMENT 32
+#define UNICAM_IMAGE_MAX_BPL ((1U << 16) - UNICAM_IMAGE_BPL_ALIGNMENT)
+
+/*
+ * Max width is therefore determined by the max stride divided by the number of
+ * bits per pixel. Take 32bpp as a worst case. No imposed limit on the height,
+ * so adopt a square image for want of anything better.
+ */
+#define UNICAM_IMAGE_MIN_WIDTH 16
+#define UNICAM_IMAGE_MIN_HEIGHT 16
+#define UNICAM_IMAGE_MAX_WIDTH (UNICAM_IMAGE_MAX_BPL / 4)
+#define UNICAM_IMAGE_MAX_HEIGHT UNICAM_IMAGE_MAX_WIDTH
+
+/*
+ * There's no intrinsic limits on the width and height for embedded data. Use
+ * the same maximum values as for the image, to avoid overflows in the image
+ * size computation.
+ */
+#define UNICAM_META_MIN_WIDTH 1
+#define UNICAM_META_MIN_HEIGHT 1
+#define UNICAM_META_MAX_WIDTH UNICAM_IMAGE_MAX_WIDTH
+#define UNICAM_META_MAX_HEIGHT UNICAM_IMAGE_MAX_HEIGHT
+
+/*
+ * Size of the dummy buffer. Can be any size really, but the DMA
+ * allocation works in units of page sizes.
+ */
+#define UNICAM_DUMMY_BUF_SIZE PAGE_SIZE
+
+enum unicam_pad {
+ UNICAM_SD_PAD_SINK,
+ UNICAM_SD_PAD_SOURCE_IMAGE,
+ UNICAM_SD_PAD_SOURCE_METADATA,
+ UNICAM_SD_NUM_PADS
+};
+
+enum unicam_node_type {
+ UNICAM_IMAGE_NODE,
+ UNICAM_METADATA_NODE,
+ UNICAM_MAX_NODES
+};
+
+/*
+ * struct unicam_format_info - Unicam media bus format information
+ * @fourcc: V4L2 pixel format FCC identifier. 0 if n/a.
+ * @unpacked_fourcc: V4L2 pixel format FCC identifier if the data is expanded
+ * out to 16bpp. 0 if n/a.
+ * @code: V4L2 media bus format code.
+ * @depth: Bits per pixel as delivered from the source.
+ * @csi_dt: CSI data type.
+ * @unpack: PUM value when unpacking to @unpacked_fourcc
+ */
+struct unicam_format_info {
+ u32 fourcc;
+ u32 unpacked_fourcc;
+ u32 code;
+ u8 depth;
+ u8 csi_dt;
+ u8 unpack;
+};
+
+struct unicam_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+ dma_addr_t dma_addr;
+ unsigned int size;
+};
+
+static inline struct unicam_buffer *to_unicam_buffer(struct vb2_buffer *vb)
+{
+ return container_of(vb, struct unicam_buffer, vb.vb2_buf);
+}
+
+struct unicam_node {
+ bool registered;
+ bool streaming;
+ unsigned int id;
+
+ /* Pointer to the current v4l2_buffer */
+ struct unicam_buffer *cur_frm;
+ /* Pointer to the next v4l2_buffer */
+ struct unicam_buffer *next_frm;
+ /* Used to store current pixel format */
+ struct v4l2_format fmt;
+ /* Buffer queue used in video-buf */
+ struct vb2_queue buffer_queue;
+ /* Queue of filled frames */
+ struct list_head dma_queue;
+ /* IRQ lock for DMA queue */
+ spinlock_t dma_queue_lock;
+ /* Identifies video device for this channel */
+ struct video_device video_dev;
+ /* Pointer to the parent handle */
+ struct unicam_device *dev;
+ struct media_pad pad;
+ /*
+ * Dummy buffer intended to be used by unicam
+ * if we have no other queued buffers to swap to.
+ */
+ struct unicam_buffer dummy_buf;
+ void *dummy_buf_cpu_addr;
+};
+
+struct unicam_device {
+ struct kref kref;
+
+ /* peripheral base address */
+ void __iomem *base;
+ /* clock gating base address */
+ void __iomem *clk_gate_base;
+ /* lp clock handle */
+ struct clk *clock;
+ /* vpu clock handle */
+ struct clk *vpu_clock;
+ /* V4l2 device */
+ struct v4l2_device v4l2_dev;
+ struct media_device mdev;
+
+ /* parent device */
+ struct device *dev;
+ /* subdevice async notifier */
+ struct v4l2_async_notifier notifier;
+ unsigned int sequence;
+
+ /* Sensor node */
+ struct {
+ struct v4l2_subdev *subdev;
+ struct media_pad *pad;
+ } sensor;
+
+ /* Internal subdev */
+ struct {
+ struct v4l2_subdev sd;
+ struct media_pad pads[UNICAM_SD_NUM_PADS];
+ unsigned int enabled_streams;
+ } subdev;
+
+ enum v4l2_mbus_type bus_type;
+ /*
+ * Stores bus.mipi_csi2.flags for CSI2 sensors, or
+ * bus.mipi_csi1.strobe for CCP2.
+ */
+ unsigned int bus_flags;
+ unsigned int max_data_lanes;
+
+ struct {
+ struct media_pipeline pipe;
+ unsigned int num_data_lanes;
+ unsigned int nodes;
+ } pipe;
+
+ /* Lock used for the video devices of both nodes */
+ struct mutex lock;
+ struct unicam_node node[UNICAM_MAX_NODES];
+};
+
+static inline struct unicam_device *
+notifier_to_unicam_device(struct v4l2_async_notifier *notifier)
+{
+ return container_of(notifier, struct unicam_device, notifier);
+}
+
+static inline struct unicam_device *
+sd_to_unicam_device(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct unicam_device, subdev.sd);
+}
+
+static void unicam_release(struct kref *kref)
+{
+ struct unicam_device *unicam =
+ container_of(kref, struct unicam_device, kref);
+
+ if (unicam->mdev.dev)
+ media_device_cleanup(&unicam->mdev);
+
+ mutex_destroy(&unicam->lock);
+ kfree(unicam);
+}
+
+static struct unicam_device *unicam_get(struct unicam_device *unicam)
+{
+ kref_get(&unicam->kref);
+
+ return unicam;
+}
+
+static void unicam_put(struct unicam_device *unicam)
+{
+ kref_put(&unicam->kref, unicam_release);
+}
+
+/* -----------------------------------------------------------------------------
+ * Misc helper functions
+ */
+
+static inline bool unicam_sd_pad_is_source(u32 pad)
+{
+ /* Camera RX has 1 sink pad, and N source pads */
+ return pad != UNICAM_SD_PAD_SINK;
+}
+
+static inline bool is_metadata_node(struct unicam_node *node)
+{
+ return node->video_dev.device_caps & V4L2_CAP_META_CAPTURE;
+}
+
+static inline bool is_image_node(struct unicam_node *node)
+{
+ return node->video_dev.device_caps & V4L2_CAP_VIDEO_CAPTURE;
+}
+
+/* -----------------------------------------------------------------------------
+ * Format data table and helper functions
+ */
+
+static const struct v4l2_mbus_framefmt unicam_default_image_format = {
+ .width = 640,
+ .height = 480,
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .ycbcr_enc = V4L2_YCBCR_ENC_601,
+ .quantization = V4L2_QUANTIZATION_LIM_RANGE,
+ .xfer_func = V4L2_XFER_FUNC_SRGB,
+ .flags = 0,
+};
+
+static const struct v4l2_mbus_framefmt unicam_default_meta_format = {
+ .width = 640,
+ .height = 2,
+ .code = MEDIA_BUS_FMT_META_8,
+ .field = V4L2_FIELD_NONE,
+};
+
+static const struct unicam_format_info unicam_image_formats[] = {
+ /* YUV Formats */
+ {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .code = MEDIA_BUS_FMT_YUYV8_1X16,
+ .depth = 16,
+ .csi_dt = MIPI_CSI2_DT_YUV422_8B,
+ }, {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .depth = 16,
+ .csi_dt = MIPI_CSI2_DT_YUV422_8B,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .code = MEDIA_BUS_FMT_YVYU8_1X16,
+ .depth = 16,
+ .csi_dt = MIPI_CSI2_DT_YUV422_8B,
+ }, {
+ .fourcc = V4L2_PIX_FMT_VYUY,
+ .code = MEDIA_BUS_FMT_VYUY8_1X16,
+ .depth = 16,
+ .csi_dt = MIPI_CSI2_DT_YUV422_8B,
+ }, {
+ /* RGB Formats */
+ .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
+ .code = MEDIA_BUS_FMT_RGB565_1X16,
+ .depth = 16,
+ .csi_dt = MIPI_CSI2_DT_RGB565,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
+ .code = MEDIA_BUS_FMT_RGB888_1X24,
+ .depth = 24,
+ .csi_dt = MIPI_CSI2_DT_RGB888,
+ }, {
+ .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
+ .code = MEDIA_BUS_FMT_BGR888_1X24,
+ .depth = 24,
+ .csi_dt = MIPI_CSI2_DT_RGB888,
+ }, {
+ /* Bayer Formats */
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .depth = 8,
+ .csi_dt = MIPI_CSI2_DT_RAW8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG8,
+ .code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .depth = 8,
+ .csi_dt = MIPI_CSI2_DT_RAW8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .depth = 8,
+ .csi_dt = MIPI_CSI2_DT_RAW8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB8,
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .depth = 8,
+ .csi_dt = MIPI_CSI2_DT_RAW8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SBGGR10,
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .depth = 10,
+ .csi_dt = MIPI_CSI2_DT_RAW10,
+ .unpack = UNICAM_PUM_UNPACK10,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGBRG10,
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .depth = 10,
+ .csi_dt = MIPI_CSI2_DT_RAW10,
+ .unpack = UNICAM_PUM_UNPACK10,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGRBG10,
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .depth = 10,
+ .csi_dt = MIPI_CSI2_DT_RAW10,
+ .unpack = UNICAM_PUM_UNPACK10,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SRGGB10,
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .depth = 10,
+ .csi_dt = MIPI_CSI2_DT_RAW10,
+ .unpack = UNICAM_PUM_UNPACK10,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SBGGR12,
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .depth = 12,
+ .csi_dt = MIPI_CSI2_DT_RAW12,
+ .unpack = UNICAM_PUM_UNPACK12,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGBRG12,
+ .code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .depth = 12,
+ .csi_dt = MIPI_CSI2_DT_RAW12,
+ .unpack = UNICAM_PUM_UNPACK12,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGRBG12,
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .depth = 12,
+ .csi_dt = MIPI_CSI2_DT_RAW12,
+ .unpack = UNICAM_PUM_UNPACK12,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SRGGB12,
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .depth = 12,
+ .csi_dt = MIPI_CSI2_DT_RAW12,
+ .unpack = UNICAM_PUM_UNPACK12,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SBGGR14,
+ .code = MEDIA_BUS_FMT_SBGGR14_1X14,
+ .depth = 14,
+ .csi_dt = MIPI_CSI2_DT_RAW14,
+ .unpack = UNICAM_PUM_UNPACK14,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGBRG14,
+ .code = MEDIA_BUS_FMT_SGBRG14_1X14,
+ .depth = 14,
+ .csi_dt = MIPI_CSI2_DT_RAW14,
+ .unpack = UNICAM_PUM_UNPACK14,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGRBG14,
+ .code = MEDIA_BUS_FMT_SGRBG14_1X14,
+ .depth = 14,
+ .csi_dt = MIPI_CSI2_DT_RAW14,
+ .unpack = UNICAM_PUM_UNPACK14,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SRGGB14,
+ .code = MEDIA_BUS_FMT_SRGGB14_1X14,
+ .depth = 14,
+ .csi_dt = MIPI_CSI2_DT_RAW14,
+ .unpack = UNICAM_PUM_UNPACK14,
+ }, {
+ /* 16 bit Bayer formats could be supported. */
+
+ /* Greyscale formats */
+ .fourcc = V4L2_PIX_FMT_GREY,
+ .code = MEDIA_BUS_FMT_Y8_1X8,
+ .depth = 8,
+ .csi_dt = MIPI_CSI2_DT_RAW8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_Y10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_Y10,
+ .code = MEDIA_BUS_FMT_Y10_1X10,
+ .depth = 10,
+ .csi_dt = MIPI_CSI2_DT_RAW10,
+ .unpack = UNICAM_PUM_UNPACK10,
+ }, {
+ .fourcc = V4L2_PIX_FMT_Y12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_Y12,
+ .code = MEDIA_BUS_FMT_Y12_1X12,
+ .depth = 12,
+ .csi_dt = MIPI_CSI2_DT_RAW12,
+ .unpack = UNICAM_PUM_UNPACK12,
+ }, {
+ .fourcc = V4L2_PIX_FMT_Y14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_Y14,
+ .code = MEDIA_BUS_FMT_Y14_1X14,
+ .depth = 14,
+ .csi_dt = MIPI_CSI2_DT_RAW14,
+ .unpack = UNICAM_PUM_UNPACK14,
+ },
+};
+
+static const struct unicam_format_info unicam_meta_formats[] = {
+ {
+ .fourcc = V4L2_META_FMT_GENERIC_8,
+ .code = MEDIA_BUS_FMT_META_8,
+ .depth = 8,
+ }, {
+ .fourcc = V4L2_META_FMT_GENERIC_CSI2_10,
+ .code = MEDIA_BUS_FMT_META_10,
+ .depth = 10,
+ }, {
+ .fourcc = V4L2_META_FMT_GENERIC_CSI2_12,
+ .code = MEDIA_BUS_FMT_META_12,
+ .depth = 12,
+ }, {
+ .fourcc = V4L2_META_FMT_GENERIC_CSI2_14,
+ .code = MEDIA_BUS_FMT_META_14,
+ .depth = 14,
+ },
+};
+
+/* Format setup functions */
+static const struct unicam_format_info *
+unicam_find_format_by_code(u32 code, u32 pad)
+{
+ const struct unicam_format_info *formats;
+ unsigned int num_formats;
+ unsigned int i;
+
+ if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ formats = unicam_image_formats;
+ num_formats = ARRAY_SIZE(unicam_image_formats);
+ } else {
+ formats = unicam_meta_formats;
+ num_formats = ARRAY_SIZE(unicam_meta_formats);
+ }
+
+ for (i = 0; i < num_formats; i++) {
+ if (formats[i].code == code)
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+static const struct unicam_format_info *
+unicam_find_format_by_fourcc(u32 fourcc, u32 pad)
+{
+ const struct unicam_format_info *formats;
+ unsigned int num_formats;
+ unsigned int i;
+
+ if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ formats = unicam_image_formats;
+ num_formats = ARRAY_SIZE(unicam_image_formats);
+ } else {
+ formats = unicam_meta_formats;
+ num_formats = ARRAY_SIZE(unicam_meta_formats);
+ }
+
+ for (i = 0; i < num_formats; ++i) {
+ if (formats[i].fourcc == fourcc)
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+static void unicam_calc_image_size_bpl(struct unicam_device *unicam,
+ const struct unicam_format_info *fmtinfo,
+ struct v4l2_pix_format *pix)
+{
+ u32 min_bpl;
+
+ v4l_bound_align_image(&pix->width, UNICAM_IMAGE_MIN_WIDTH,
+ UNICAM_IMAGE_MAX_WIDTH, 2,
+ &pix->height, UNICAM_IMAGE_MIN_HEIGHT,
+ UNICAM_IMAGE_MAX_HEIGHT, 0, 0);
+
+ /* Unpacking always goes to 16bpp */
+ if (pix->pixelformat == fmtinfo->unpacked_fourcc)
+ min_bpl = pix->width * 2;
+ else
+ min_bpl = pix->width * fmtinfo->depth / 8;
+ min_bpl = ALIGN(min_bpl, UNICAM_IMAGE_BPL_ALIGNMENT);
+
+ pix->bytesperline = ALIGN(pix->bytesperline, UNICAM_IMAGE_BPL_ALIGNMENT);
+ pix->bytesperline = clamp_t(unsigned int, pix->bytesperline, min_bpl,
+ UNICAM_IMAGE_MAX_BPL);
+
+ pix->sizeimage = pix->height * pix->bytesperline;
+}
+
+static void unicam_calc_meta_size_bpl(struct unicam_device *unicam,
+ const struct unicam_format_info *fmtinfo,
+ struct v4l2_meta_format *meta)
+{
+ v4l_bound_align_image(&meta->width, UNICAM_META_MIN_WIDTH,
+ UNICAM_META_MAX_WIDTH, 0,
+ &meta->height, UNICAM_META_MIN_HEIGHT,
+ UNICAM_META_MAX_HEIGHT, 0, 0);
+
+ meta->bytesperline = ALIGN(meta->width * fmtinfo->depth / 8,
+ UNICAM_DMA_BPL_ALIGNMENT);
+ meta->buffersize = meta->height * meta->bytesperline;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware handling
+ */
+
+static inline void unicam_clk_write(struct unicam_device *unicam, u32 val)
+{
+ /* Pass the CM_PASSWORD along with the value. */
+ writel(val | 0x5a000000, unicam->clk_gate_base);
+}
+
+static inline u32 unicam_reg_read(struct unicam_device *unicam, u32 offset)
+{
+ return readl(unicam->base + offset);
+}
+
+static inline void unicam_reg_write(struct unicam_device *unicam, u32 offset, u32 val)
+{
+ writel(val, unicam->base + offset);
+}
+
+static inline int unicam_get_field(u32 value, u32 mask)
+{
+ return (value & mask) >> __ffs(mask);
+}
+
+static inline void unicam_set_field(u32 *valp, u32 field, u32 mask)
+{
+ u32 val = *valp;
+
+ val &= ~mask;
+ val |= (field << __ffs(mask)) & mask;
+ *valp = val;
+}
+
+static inline void unicam_reg_write_field(struct unicam_device *unicam, u32 offset,
+ u32 field, u32 mask)
+{
+ u32 val = unicam_reg_read(unicam, offset);
+
+ unicam_set_field(&val, field, mask);
+ unicam_reg_write(unicam, offset, val);
+}
+
+static void unicam_wr_dma_addr(struct unicam_node *node,
+ struct unicam_buffer *buf)
+{
+ dma_addr_t endaddr = buf->dma_addr + buf->size;
+
+ if (node->id == UNICAM_IMAGE_NODE) {
+ unicam_reg_write(node->dev, UNICAM_IBSA0, buf->dma_addr);
+ unicam_reg_write(node->dev, UNICAM_IBEA0, endaddr);
+ } else {
+ unicam_reg_write(node->dev, UNICAM_DBSA0, buf->dma_addr);
+ unicam_reg_write(node->dev, UNICAM_DBEA0, endaddr);
+ }
+}
+
+static unsigned int unicam_get_lines_done(struct unicam_device *unicam)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+ unsigned int stride = node->fmt.fmt.pix.bytesperline;
+ struct unicam_buffer *frm = node->cur_frm;
+ dma_addr_t cur_addr;
+
+ if (!frm)
+ return 0;
+
+ cur_addr = unicam_reg_read(unicam, UNICAM_IBWP);
+ return (unsigned int)(cur_addr - frm->dma_addr) / stride;
+}
+
+static void unicam_schedule_next_buffer(struct unicam_node *node)
+{
+ struct unicam_buffer *buf;
+
+ buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list);
+ node->next_frm = buf;
+ list_del(&buf->list);
+
+ unicam_wr_dma_addr(node, buf);
+}
+
+static void unicam_schedule_dummy_buffer(struct unicam_node *node)
+{
+ int node_id = is_image_node(node) ? UNICAM_IMAGE_NODE : UNICAM_METADATA_NODE;
+
+ dev_dbg(node->dev->dev, "Scheduling dummy buffer for node %d\n", node_id);
+
+ unicam_wr_dma_addr(node, &node->dummy_buf);
+
+ node->next_frm = NULL;
+}
+
+static void unicam_process_buffer_complete(struct unicam_node *node,
+ unsigned int sequence)
+{
+ node->cur_frm->vb.field = node->fmt.fmt.pix.field;
+ node->cur_frm->vb.sequence = sequence;
+
+ vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+static void unicam_queue_event_sof(struct unicam_device *unicam)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+ struct v4l2_event event = {
+ .type = V4L2_EVENT_FRAME_SYNC,
+ .u.frame_sync.frame_sequence = unicam->sequence,
+ };
+
+ v4l2_event_queue(&node->video_dev, &event);
+}
+
+static irqreturn_t unicam_isr(int irq, void *dev)
+{
+ struct unicam_device *unicam = dev;
+ unsigned int lines_done = unicam_get_lines_done(dev);
+ unsigned int sequence = unicam->sequence;
+ unsigned int i;
+ u32 ista, sta;
+ bool fe;
+ u64 ts;
+
+ sta = unicam_reg_read(unicam, UNICAM_STA);
+ /* Write value back to clear the interrupts */
+ unicam_reg_write(unicam, UNICAM_STA, sta);
+
+ ista = unicam_reg_read(unicam, UNICAM_ISTA);
+ /* Write value back to clear the interrupts */
+ unicam_reg_write(unicam, UNICAM_ISTA, ista);
+
+ dev_dbg(unicam->dev, "ISR: ISTA: 0x%X, STA: 0x%X, sequence %d, lines done %d\n",
+ ista, sta, sequence, lines_done);
+
+ if (!(sta & (UNICAM_IS | UNICAM_PI0)))
+ return IRQ_HANDLED;
+
+ /*
+ * Look for either the Frame End interrupt or the Packet Capture status
+ * to signal a frame end.
+ */
+ fe = ista & UNICAM_FEI || sta & UNICAM_PI0;
+
+ /*
+ * We must run the frame end handler first. If we have a valid next_frm
+ * and we get a simultaneout FE + FS interrupt, running the FS handler
+ * first would null out the next_frm ptr and we would have lost the
+ * buffer forever.
+ */
+ if (fe) {
+ /*
+ * Ensure we have swapped buffers already as we can't
+ * stop the peripheral. If no buffer is available, use a
+ * dummy buffer to dump out frames until we get a new buffer
+ * to use.
+ */
+ for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+ if (!unicam->node[i].streaming)
+ continue;
+
+ /*
+ * If cur_frm == next_frm, it means we have not had
+ * a chance to swap buffers, likely due to having
+ * multiple interrupts occurring simultaneously (like FE
+ * + FS + LS). In this case, we cannot signal the buffer
+ * as complete, as the HW will reuse that buffer.
+ */
+ if (unicam->node[i].cur_frm &&
+ unicam->node[i].cur_frm != unicam->node[i].next_frm)
+ unicam_process_buffer_complete(&unicam->node[i],
+ sequence);
+ unicam->node[i].cur_frm = unicam->node[i].next_frm;
+ }
+ unicam->sequence++;
+ }
+
+ if (ista & UNICAM_FSI) {
+ /*
+ * Timestamp is to be when the first data byte was captured,
+ * aka frame start.
+ */
+ ts = ktime_get_ns();
+ for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+ if (!unicam->node[i].streaming)
+ continue;
+
+ if (unicam->node[i].cur_frm)
+ unicam->node[i].cur_frm->vb.vb2_buf.timestamp =
+ ts;
+ else
+ dev_dbg(unicam->v4l2_dev.dev,
+ "ISR: [%d] Dropping frame, buffer not available at FS\n",
+ i);
+ /*
+ * Set the next frame output to go to a dummy frame
+ * if we have not managed to obtain another frame
+ * from the queue.
+ */
+ unicam_schedule_dummy_buffer(&unicam->node[i]);
+ }
+
+ unicam_queue_event_sof(unicam);
+ }
+
+ /*
+ * Cannot swap buffer at frame end, there may be a race condition
+ * where the HW does not actually swap it if the new frame has
+ * already started.
+ */
+ if (ista & (UNICAM_FSI | UNICAM_LCI) && !fe) {
+ for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+ if (!unicam->node[i].streaming)
+ continue;
+
+ spin_lock(&unicam->node[i].dma_queue_lock);
+ if (!list_empty(&unicam->node[i].dma_queue) &&
+ !unicam->node[i].next_frm)
+ unicam_schedule_next_buffer(&unicam->node[i]);
+ spin_unlock(&unicam->node[i].dma_queue_lock);
+ }
+ }
+
+ if (unicam_reg_read(unicam, UNICAM_ICTL) & UNICAM_FCM) {
+ /* Switch out of trigger mode if selected */
+ unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
+ unicam_reg_write_field(unicam, UNICAM_ICTL, 0, UNICAM_FCM);
+ }
+ return IRQ_HANDLED;
+}
+
+static void unicam_set_packing_config(struct unicam_device *unicam,
+ const struct unicam_format_info *fmtinfo)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+ u32 pack, unpack;
+ u32 val;
+
+ if (node->fmt.fmt.pix.pixelformat == fmtinfo->fourcc) {
+ unpack = UNICAM_PUM_NONE;
+ pack = UNICAM_PPM_NONE;
+ } else {
+ unpack = fmtinfo->unpack;
+ /* Repacking is always to 16bpp */
+ pack = UNICAM_PPM_PACK16;
+ }
+
+ val = 0;
+ unicam_set_field(&val, unpack, UNICAM_PUM_MASK);
+ unicam_set_field(&val, pack, UNICAM_PPM_MASK);
+ unicam_reg_write(unicam, UNICAM_IPIPE, val);
+}
+
+static void unicam_cfg_image_id(struct unicam_device *unicam, u8 vc, u8 dt)
+{
+ if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
+ /* CSI2 mode */
+ unicam_reg_write(unicam, UNICAM_IDI0, (vc << 6) | dt);
+ } else {
+ /* CCP2 mode */
+ unicam_reg_write(unicam, UNICAM_IDI0, 0x80 | dt);
+ }
+}
+
+static void unicam_enable_ed(struct unicam_device *unicam)
+{
+ u32 val = unicam_reg_read(unicam, UNICAM_DCS);
+
+ unicam_set_field(&val, 2, UNICAM_EDL_MASK);
+ /* Do not wrap at the end of the embedded data buffer */
+ unicam_set_field(&val, 0, UNICAM_DBOB);
+
+ unicam_reg_write(unicam, UNICAM_DCS, val);
+}
+
+static int unicam_get_image_vc_dt(struct unicam_device *unicam,
+ struct v4l2_subdev_state *state,
+ u8 *vc, u8 *dt)
+{
+ struct v4l2_mbus_frame_desc fd;
+ u32 stream;
+ int ret;
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
+ UNICAM_SD_PAD_SOURCE_IMAGE,
+ 0, NULL, &stream);
+ if (ret)
+ return ret;
+
+ ret = v4l2_subdev_call(unicam->sensor.subdev, pad, get_frame_desc,
+ unicam->sensor.pad->index, &fd);
+ if (ret)
+ return ret;
+
+ /* Only CSI-2 supports DTs. */
+ if (fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2)
+ return -EINVAL;
+
+ for (unsigned int i = 0; i < fd.num_entries; ++i) {
+ const struct v4l2_mbus_frame_desc_entry *fde = &fd.entry[i];
+
+ if (fde->stream == stream) {
+ *vc = fde->bus.csi2.vc;
+ *dt = fde->bus.csi2.dt;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static void unicam_start_rx(struct unicam_device *unicam,
+ struct v4l2_subdev_state *state)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+ const struct unicam_format_info *fmtinfo;
+ const struct v4l2_mbus_framefmt *fmt;
+ unsigned int line_int_freq;
+ u8 vc, dt;
+ u32 val;
+ int ret;
+
+ fmt = v4l2_subdev_state_get_format(state, UNICAM_SD_PAD_SOURCE_IMAGE, 0);
+ fmtinfo = unicam_find_format_by_code(fmt->code,
+ UNICAM_SD_PAD_SOURCE_IMAGE);
+ if (WARN_ON(!fmtinfo))
+ return;
+
+ /*
+ * Enable lane clocks. The register is structured as follows:
+ *
+ * [9:8] - DAT3
+ * [7:6] - DAT2
+ * [5:4] - DAT1
+ * [3:2] - DAT0
+ * [1:0] - CLK
+ *
+ * Enabled lane must be set to b01, and disabled lanes to b00. The clock
+ * lane is always enabled.
+ */
+ val = 0x155 & GENMASK(unicam->pipe.num_data_lanes * 2 + 1, 0);
+ unicam_clk_write(unicam, val);
+
+ /* Basic init */
+ unicam_reg_write(unicam, UNICAM_CTRL, UNICAM_MEM);
+
+ /* Enable analogue control, and leave in reset. */
+ val = UNICAM_AR;
+ unicam_set_field(&val, 7, UNICAM_CTATADJ_MASK);
+ unicam_set_field(&val, 7, UNICAM_PTATADJ_MASK);
+ unicam_reg_write(unicam, UNICAM_ANA, val);
+ usleep_range(1000, 2000);
+
+ /* Come out of reset */
+ unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_AR);
+
+ /* Peripheral reset */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
+
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
+
+ /* Enable Rx control. */
+ val = unicam_reg_read(unicam, UNICAM_CTRL);
+ if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
+ unicam_set_field(&val, UNICAM_CPM_CSI2, UNICAM_CPM_MASK);
+ unicam_set_field(&val, UNICAM_DCM_STROBE, UNICAM_DCM_MASK);
+ } else {
+ unicam_set_field(&val, UNICAM_CPM_CCP2, UNICAM_CPM_MASK);
+ unicam_set_field(&val, unicam->bus_flags, UNICAM_DCM_MASK);
+ }
+ /* Packet framer timeout */
+ unicam_set_field(&val, 0xf, UNICAM_PFT_MASK);
+ unicam_set_field(&val, 128, UNICAM_OET_MASK);
+ unicam_reg_write(unicam, UNICAM_CTRL, val);
+
+ unicam_reg_write(unicam, UNICAM_IHWIN, 0);
+ unicam_reg_write(unicam, UNICAM_IVWIN, 0);
+
+ /* AXI bus access QoS setup */
+ val = unicam_reg_read(unicam, UNICAM_PRI);
+ unicam_set_field(&val, 0, UNICAM_BL_MASK);
+ unicam_set_field(&val, 0, UNICAM_BS_MASK);
+ unicam_set_field(&val, 0xe, UNICAM_PP_MASK);
+ unicam_set_field(&val, 8, UNICAM_NP_MASK);
+ unicam_set_field(&val, 2, UNICAM_PT_MASK);
+ unicam_set_field(&val, 1, UNICAM_PE);
+ unicam_reg_write(unicam, UNICAM_PRI, val);
+
+ unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_DDL);
+
+ /* Always start in trigger frame capture mode (UNICAM_FCM set) */
+ val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM | UNICAM_IBOB;
+ line_int_freq = max(fmt->height >> 2, 128);
+ unicam_set_field(&val, line_int_freq, UNICAM_LCIE_MASK);
+ unicam_reg_write(unicam, UNICAM_ICTL, val);
+ unicam_reg_write(unicam, UNICAM_STA, UNICAM_STA_MASK_ALL);
+ unicam_reg_write(unicam, UNICAM_ISTA, UNICAM_ISTA_MASK_ALL);
+
+ /* tclk_term_en */
+ unicam_reg_write_field(unicam, UNICAM_CLT, 2, UNICAM_CLT1_MASK);
+ /* tclk_settle */
+ unicam_reg_write_field(unicam, UNICAM_CLT, 6, UNICAM_CLT2_MASK);
+ /* td_term_en */
+ unicam_reg_write_field(unicam, UNICAM_DLT, 2, UNICAM_DLT1_MASK);
+ /* ths_settle */
+ unicam_reg_write_field(unicam, UNICAM_DLT, 6, UNICAM_DLT2_MASK);
+ /* trx_enable */
+ unicam_reg_write_field(unicam, UNICAM_DLT, 0, UNICAM_DLT3_MASK);
+
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_SOE);
+
+ /* Packet compare setup - required to avoid missing frame ends */
+ val = 0;
+ unicam_set_field(&val, 1, UNICAM_PCE);
+ unicam_set_field(&val, 1, UNICAM_GI);
+ unicam_set_field(&val, 1, UNICAM_CPH);
+ unicam_set_field(&val, 0, UNICAM_PCVC_MASK);
+ unicam_set_field(&val, 1, UNICAM_PCDT_MASK);
+ unicam_reg_write(unicam, UNICAM_CMP0, val);
+
+ /* Enable clock lane and set up terminations */
+ val = 0;
+ if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
+ /* CSI2 */
+ unicam_set_field(&val, 1, UNICAM_CLE);
+ unicam_set_field(&val, 1, UNICAM_CLLPE);
+ if (!(unicam->bus_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
+ unicam_set_field(&val, 1, UNICAM_CLTRE);
+ unicam_set_field(&val, 1, UNICAM_CLHSE);
+ }
+ } else {
+ /* CCP2 */
+ unicam_set_field(&val, 1, UNICAM_CLE);
+ unicam_set_field(&val, 1, UNICAM_CLHSE);
+ unicam_set_field(&val, 1, UNICAM_CLTRE);
+ }
+ unicam_reg_write(unicam, UNICAM_CLK, val);
+
+ /*
+ * Enable required data lanes with appropriate terminations.
+ * The same value needs to be written to UNICAM_DATn registers for
+ * the active lanes, and 0 for inactive ones.
+ */
+ val = 0;
+ if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
+ /* CSI2 */
+ unicam_set_field(&val, 1, UNICAM_DLE);
+ unicam_set_field(&val, 1, UNICAM_DLLPE);
+ if (!(unicam->bus_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
+ unicam_set_field(&val, 1, UNICAM_DLTRE);
+ unicam_set_field(&val, 1, UNICAM_DLHSE);
+ }
+ } else {
+ /* CCP2 */
+ unicam_set_field(&val, 1, UNICAM_DLE);
+ unicam_set_field(&val, 1, UNICAM_DLHSE);
+ unicam_set_field(&val, 1, UNICAM_DLTRE);
+ }
+ unicam_reg_write(unicam, UNICAM_DAT0, val);
+
+ if (unicam->pipe.num_data_lanes == 1)
+ val = 0;
+ unicam_reg_write(unicam, UNICAM_DAT1, val);
+
+ if (unicam->max_data_lanes > 2) {
+ /*
+ * Registers UNICAM_DAT2 and UNICAM_DAT3 only valid if the
+ * instance supports more than 2 data lanes.
+ */
+ if (unicam->pipe.num_data_lanes == 2)
+ val = 0;
+ unicam_reg_write(unicam, UNICAM_DAT2, val);
+
+ if (unicam->pipe.num_data_lanes == 3)
+ val = 0;
+ unicam_reg_write(unicam, UNICAM_DAT3, val);
+ }
+
+ unicam_reg_write(unicam, UNICAM_IBLS,
+ node->fmt.fmt.pix.bytesperline);
+ unicam_wr_dma_addr(node, node->cur_frm);
+ unicam_set_packing_config(unicam, fmtinfo);
+
+ ret = unicam_get_image_vc_dt(unicam, state, &vc, &dt);
+ if (ret) {
+ /*
+ * If the source doesn't support frame descriptors, default to
+ * VC 0 and use the DT corresponding to the format.
+ */
+ vc = 0;
+ dt = fmtinfo->csi_dt;
+ }
+
+ unicam_cfg_image_id(unicam, vc, dt);
+
+ val = unicam_reg_read(unicam, UNICAM_MISC);
+ unicam_set_field(&val, 1, UNICAM_FL0);
+ unicam_set_field(&val, 1, UNICAM_FL1);
+ unicam_reg_write(unicam, UNICAM_MISC, val);
+
+ /* Enable peripheral */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPE);
+
+ /* Load image pointers */
+ unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_LIP_MASK);
+
+ /*
+ * Enable trigger only for the first frame to
+ * sync correctly to the FS from the source.
+ */
+ unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
+}
+
+static void unicam_start_metadata(struct unicam_device *unicam)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_METADATA_NODE];
+
+ unicam_enable_ed(unicam);
+ unicam_wr_dma_addr(node, node->cur_frm);
+ unicam_reg_write_field(unicam, UNICAM_DCS, 1, UNICAM_LDP);
+}
+
+static void unicam_disable(struct unicam_device *unicam)
+{
+ /* Analogue lane control disable */
+ unicam_reg_write_field(unicam, UNICAM_ANA, 1, UNICAM_DDL);
+
+ /* Stop the output engine */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_SOE);
+
+ /* Disable the data lanes. */
+ unicam_reg_write(unicam, UNICAM_DAT0, 0);
+ unicam_reg_write(unicam, UNICAM_DAT1, 0);
+
+ if (unicam->max_data_lanes > 2) {
+ unicam_reg_write(unicam, UNICAM_DAT2, 0);
+ unicam_reg_write(unicam, UNICAM_DAT3, 0);
+ }
+
+ /* Peripheral reset */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
+ usleep_range(50, 100);
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
+
+ /* Disable peripheral */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
+
+ /* Clear ED setup */
+ unicam_reg_write(unicam, UNICAM_DCS, 0);
+
+ /* Disable all lane clocks */
+ unicam_clk_write(unicam, 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev operations
+ */
+
+static int __unicam_subdev_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_krouting *routing)
+{
+ struct v4l2_subdev_route *route;
+ int ret;
+
+ ret = v4l2_subdev_routing_validate(sd, routing,
+ V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+ if (ret)
+ return ret;
+
+ ret = v4l2_subdev_set_routing(sd, state, routing);
+ if (ret)
+ return ret;
+
+ for_each_active_route(&state->routing, route) {
+ const struct v4l2_mbus_framefmt *def_fmt;
+ struct v4l2_mbus_framefmt *fmt;
+
+ if (route->source_pad == UNICAM_SD_PAD_SOURCE_IMAGE)
+ def_fmt = &unicam_default_image_format;
+ else
+ def_fmt = &unicam_default_meta_format;
+
+ fmt = v4l2_subdev_state_get_format(state, route->sink_pad,
+ route->sink_stream);
+ *fmt = *def_fmt;
+ fmt = v4l2_subdev_state_get_format(state, route->source_pad,
+ route->source_stream);
+ *fmt = *def_fmt;
+ }
+
+ return 0;
+}
+
+static int unicam_subdev_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct v4l2_subdev_route routes[] = {
+ {
+ .sink_pad = UNICAM_SD_PAD_SINK,
+ .sink_stream = 0,
+ .source_pad = UNICAM_SD_PAD_SOURCE_IMAGE,
+ .source_stream = 0,
+ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+ },
+ };
+
+ struct v4l2_subdev_krouting routing = {
+ .len_routes = ARRAY_SIZE(routes),
+ .num_routes = ARRAY_SIZE(routes),
+ .routes = routes,
+ };
+
+ /* Initialize routing to single route to the fist source pad. */
+ return __unicam_subdev_set_routing(sd, state, &routing);
+}
+
+static int unicam_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ u32 pad, stream;
+ int ret;
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
+ code->pad, code->stream,
+ &pad, &stream);
+ if (ret)
+ return ret;
+
+ if (unicam_sd_pad_is_source(code->pad)) {
+ /* No transcoding, source and sink codes must match. */
+ const struct v4l2_mbus_framefmt *fmt;
+
+ fmt = v4l2_subdev_state_get_format(state, pad, stream);
+ if (!fmt)
+ return -EINVAL;
+
+ if (code->index > 0)
+ return -EINVAL;
+
+ code->code = fmt->code;
+ } else {
+ const struct unicam_format_info *formats;
+ unsigned int num_formats;
+
+ if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ formats = unicam_image_formats;
+ num_formats = ARRAY_SIZE(unicam_image_formats);
+ } else {
+ formats = unicam_meta_formats;
+ num_formats = ARRAY_SIZE(unicam_meta_formats);
+ }
+
+ if (code->index >= num_formats)
+ return -EINVAL;
+
+ code->code = formats[code->index].code;
+ }
+
+ return 0;
+}
+
+static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ u32 pad, stream;
+ int ret;
+
+ if (fse->index > 0)
+ return -EINVAL;
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, fse->pad,
+ fse->stream, &pad,
+ &stream);
+ if (ret)
+ return ret;
+
+ if (unicam_sd_pad_is_source(fse->pad)) {
+ /* No transcoding, source and sink formats must match. */
+ const struct v4l2_mbus_framefmt *fmt;
+
+ fmt = v4l2_subdev_state_get_format(state, pad, stream);
+ if (!fmt)
+ return -EINVAL;
+
+ if (fse->code != fmt->code)
+ return -EINVAL;
+
+ fse->min_width = fmt->width;
+ fse->max_width = fmt->width;
+ fse->min_height = fmt->height;
+ fse->max_height = fmt->height;
+ } else {
+ const struct unicam_format_info *fmtinfo;
+
+ fmtinfo = unicam_find_format_by_code(fse->code, pad);
+ if (!fmtinfo)
+ return -EINVAL;
+
+ if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ fse->min_width = UNICAM_IMAGE_MIN_WIDTH;
+ fse->max_width = UNICAM_IMAGE_MAX_WIDTH;
+ fse->min_height = UNICAM_IMAGE_MIN_HEIGHT;
+ fse->max_height = UNICAM_IMAGE_MAX_HEIGHT;
+ } else {
+ fse->min_width = UNICAM_META_MIN_WIDTH;
+ fse->max_width = UNICAM_META_MAX_WIDTH;
+ fse->min_height = UNICAM_META_MIN_HEIGHT;
+ fse->max_height = UNICAM_META_MAX_HEIGHT;
+ }
+ }
+
+ return 0;
+}
+
+static int unicam_subdev_set_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *format)
+{
+ struct unicam_device *unicam = sd_to_unicam_device(sd);
+ struct v4l2_mbus_framefmt *sink_format, *source_format;
+ const struct unicam_format_info *fmtinfo;
+ u32 source_pad, source_stream;
+ int ret;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+ unicam->subdev.enabled_streams)
+ return -EBUSY;
+
+ /* No transcoding, source and sink formats must match. */
+ if (unicam_sd_pad_is_source(format->pad))
+ return v4l2_subdev_get_fmt(sd, state, format);
+
+ /*
+ * Allowed formats for the stream on the sink pad depend on what source
+ * pad the stream is routed to. Find the corresponding source pad and
+ * use it to validate the media bus code.
+ */
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
+ format->pad, format->stream,
+ &source_pad, &source_stream);
+ if (ret)
+ return ret;
+
+ fmtinfo = unicam_find_format_by_code(format->format.code, source_pad);
+ if (!fmtinfo) {
+ fmtinfo = source_pad == UNICAM_SD_PAD_SOURCE_IMAGE
+ ? &unicam_image_formats[0] : &unicam_meta_formats[0];
+ format->format.code = fmtinfo->code;
+ }
+
+ if (source_pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ format->format.width = clamp_t(unsigned int,
+ format->format.width,
+ UNICAM_IMAGE_MIN_WIDTH,
+ UNICAM_IMAGE_MAX_WIDTH);
+ format->format.height = clamp_t(unsigned int,
+ format->format.height,
+ UNICAM_IMAGE_MIN_HEIGHT,
+ UNICAM_IMAGE_MAX_HEIGHT);
+ format->format.field = V4L2_FIELD_NONE;
+ } else {
+ format->format.width = clamp_t(unsigned int,
+ format->format.width,
+ UNICAM_META_MIN_WIDTH,
+ UNICAM_META_MAX_WIDTH);
+ format->format.height = clamp_t(unsigned int,
+ format->format.height,
+ UNICAM_META_MIN_HEIGHT,
+ UNICAM_META_MAX_HEIGHT);
+ format->format.field = V4L2_FIELD_NONE;
+
+ /* Colorspace don't apply to metadata. */
+ format->format.colorspace = 0;
+ format->format.ycbcr_enc = 0;
+ format->format.quantization = 0;
+ format->format.xfer_func = 0;
+ }
+
+ sink_format = v4l2_subdev_state_get_format(state, format->pad,
+ format->stream);
+ source_format = v4l2_subdev_state_get_format(state, source_pad,
+ source_stream);
+ *sink_format = format->format;
+ *source_format = format->format;
+
+ return 0;
+}
+
+static int unicam_subdev_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ enum v4l2_subdev_format_whence which,
+ struct v4l2_subdev_krouting *routing)
+{
+ struct unicam_device *unicam = sd_to_unicam_device(sd);
+
+ if (which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.enabled_streams)
+ return -EBUSY;
+
+ return __unicam_subdev_set_routing(sd, state, routing);
+}
+
+static int unicam_sd_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct unicam_device *unicam = sd_to_unicam_device(sd);
+ u32 other_pad, other_stream;
+ int ret;
+
+ if (!unicam->subdev.enabled_streams) {
+ /* Configure and start Unicam. */
+ unicam->sequence = 0;
+
+ if (unicam->pipe.nodes & BIT(UNICAM_METADATA_NODE))
+ unicam_start_metadata(unicam);
+
+ unicam_start_rx(unicam, state);
+ }
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
+ &other_pad, &other_stream);
+ if (ret)
+ return ret;
+
+ ret = v4l2_subdev_enable_streams(unicam->sensor.subdev,
+ unicam->sensor.pad->index,
+ BIT(other_stream));
+ if (ret) {
+ dev_err(unicam->dev, "stream on failed in subdev\n");
+ return ret;
+ }
+
+ unicam->subdev.enabled_streams |= BIT(other_stream);
+
+ return 0;
+}
+
+static int unicam_sd_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct unicam_device *unicam = sd_to_unicam_device(sd);
+ u32 other_pad, other_stream;
+ int ret;
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
+ &other_pad, &other_stream);
+ if (ret)
+ return ret;
+
+ v4l2_subdev_disable_streams(unicam->sensor.subdev,
+ unicam->sensor.pad->index,
+ BIT(other_stream));
+
+ unicam->subdev.enabled_streams &= ~BIT(other_stream);
+
+ if (!unicam->subdev.enabled_streams)
+ unicam_disable(unicam);
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops unicam_subdev_pad_ops = {
+ .enum_mbus_code = unicam_subdev_enum_mbus_code,
+ .enum_frame_size = unicam_subdev_enum_frame_size,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = unicam_subdev_set_format,
+ .set_routing = unicam_subdev_set_routing,
+ .enable_streams = unicam_sd_enable_streams,
+ .disable_streams = unicam_sd_disable_streams,
+};
+
+static const struct v4l2_subdev_ops unicam_subdev_ops = {
+ .pad = &unicam_subdev_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops unicam_subdev_internal_ops = {
+ .init_state = unicam_subdev_init_state,
+};
+
+static const struct media_entity_operations unicam_subdev_media_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+ .has_pad_interdep = v4l2_subdev_has_pad_interdep,
+};
+
+static int unicam_subdev_init(struct unicam_device *unicam)
+{
+ struct v4l2_subdev *sd = &unicam->subdev.sd;
+ int ret;
+
+ v4l2_subdev_init(sd, &unicam_subdev_ops);
+ sd->internal_ops = &unicam_subdev_internal_ops;
+ v4l2_set_subdevdata(sd, unicam);
+
+ sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+ sd->entity.ops = &unicam_subdev_media_ops;
+ sd->dev = unicam->dev;
+ sd->owner = THIS_MODULE;
+ sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
+
+ strscpy(sd->name, "unicam", sizeof(sd->name));
+
+ unicam->subdev.pads[UNICAM_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_IMAGE].flags = MEDIA_PAD_FL_SOURCE;
+ unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_METADATA].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(unicam->subdev.pads),
+ unicam->subdev.pads);
+ if (ret) {
+ dev_err(unicam->dev, "Failed to initialize media entity: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = v4l2_subdev_init_finalize(sd);
+ if (ret) {
+ dev_err(unicam->dev, "Failed to initialize subdev: %d\n", ret);
+ goto err_entity;
+ }
+
+ ret = v4l2_device_register_subdev(&unicam->v4l2_dev, sd);
+ if (ret) {
+ dev_err(unicam->dev, "Failed to register subdev: %d\n", ret);
+ goto err_subdev;
+ }
+
+ return 0;
+
+err_subdev:
+ v4l2_subdev_cleanup(sd);
+err_entity:
+ media_entity_cleanup(&sd->entity);
+ return ret;
+}
+
+static void unicam_subdev_cleanup(struct unicam_device *unicam)
+{
+ v4l2_subdev_cleanup(&unicam->subdev.sd);
+ media_entity_cleanup(&unicam->subdev.sd.entity);
+}
+
+/* -----------------------------------------------------------------------------
+ * Videobuf2 queue operations
+ */
+
+static int unicam_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+ unsigned int *nplanes, unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct unicam_node *node = vb2_get_drv_priv(vq);
+ u32 size = is_image_node(node) ? node->fmt.fmt.pix.sizeimage
+ : node->fmt.fmt.meta.buffersize;
+
+ if (*nplanes) {
+ if (sizes[0] < size) {
+ dev_dbg(node->dev->dev, "sizes[0] %i < size %u\n",
+ sizes[0], size);
+ return -EINVAL;
+ }
+ size = sizes[0];
+ }
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int unicam_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
+ struct unicam_buffer *buf = to_unicam_buffer(vb);
+ u32 size = is_image_node(node) ? node->fmt.fmt.pix.sizeimage
+ : node->fmt.fmt.meta.buffersize;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ dev_dbg(node->dev->dev,
+ "data will not fit into plane (%lu < %u)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ buf->dma_addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+ buf->size = size;
+
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
+
+ return 0;
+}
+
+static void unicam_return_buffers(struct unicam_node *node,
+ enum vb2_buffer_state state)
+{
+ struct unicam_buffer *buf, *tmp;
+
+ list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) {
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, state);
+ }
+
+ if (node->cur_frm)
+ vb2_buffer_done(&node->cur_frm->vb.vb2_buf,
+ state);
+ if (node->next_frm && node->cur_frm != node->next_frm)
+ vb2_buffer_done(&node->next_frm->vb.vb2_buf,
+ state);
+
+ node->cur_frm = NULL;
+ node->next_frm = NULL;
+}
+
+static int unicam_num_data_lanes(struct unicam_device *unicam)
+{
+ struct v4l2_mbus_config mbus_config = { 0 };
+ unsigned int num_data_lanes;
+ int ret;
+
+ if (unicam->bus_type != V4L2_MBUS_CSI2_DPHY)
+ return unicam->max_data_lanes;
+
+ ret = v4l2_subdev_call(unicam->sensor.subdev, pad, get_mbus_config,
+ unicam->sensor.pad->index, &mbus_config);
+ if (ret == -ENOIOCTLCMD)
+ return unicam->max_data_lanes;
+
+ if (ret < 0) {
+ dev_err(unicam->dev, "Failed to get mbus config: %d\n", ret);
+ return ret;
+ }
+
+ num_data_lanes = mbus_config.bus.mipi_csi2.num_data_lanes;
+
+ if (num_data_lanes != 1 && num_data_lanes != 2 && num_data_lanes != 4) {
+ dev_err(unicam->dev,
+ "Device %s has requested %u data lanes, invalid\n",
+ unicam->sensor.subdev->name, num_data_lanes);
+ return -EINVAL;
+ }
+
+ if (num_data_lanes > unicam->max_data_lanes) {
+ dev_err(unicam->dev,
+ "Device %s has requested %u data lanes, >%u configured in DT\n",
+ unicam->sensor.subdev->name, num_data_lanes,
+ unicam->max_data_lanes);
+ return -EINVAL;
+ }
+
+ return num_data_lanes;
+}
+
+static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct unicam_node *node = vb2_get_drv_priv(vq);
+ struct unicam_device *unicam = node->dev;
+ struct unicam_buffer *buf;
+ struct media_pipeline_pad_iter iter;
+ struct media_pad *pad;
+ unsigned long flags;
+ int ret;
+
+ dev_dbg(unicam->dev, "Starting stream on %s device\n",
+ is_metadata_node(node) ? "metadata" : "image");
+
+ /*
+ * Start the pipeline. This validates all links, and populates the
+ * pipeline structure.
+ */
+ ret = video_device_pipeline_start(&node->video_dev, &unicam->pipe.pipe);
+ if (ret < 0) {
+ dev_dbg(unicam->dev, "Failed to start media pipeline: %d\n", ret);
+ goto err_buffers;
+ }
+
+ /*
+ * Determine which video nodes are included in the pipeline, and get the
+ * number of data lanes.
+ */
+ if (unicam->pipe.pipe.start_count == 1) {
+ unicam->pipe.nodes = 0;
+
+ media_pipeline_for_each_pad(&unicam->pipe.pipe, &iter, pad) {
+ if (pad->entity != &unicam->subdev.sd.entity)
+ continue;
+
+ if (pad->index == UNICAM_SD_PAD_SOURCE_IMAGE)
+ unicam->pipe.nodes |= BIT(UNICAM_IMAGE_NODE);
+ else if (pad->index == UNICAM_SD_PAD_SOURCE_METADATA)
+ unicam->pipe.nodes |= BIT(UNICAM_METADATA_NODE);
+ }
+
+ if (!(unicam->pipe.nodes & BIT(UNICAM_IMAGE_NODE))) {
+ dev_dbg(unicam->dev,
+ "Pipeline does not include image node\n");
+ ret = -EPIPE;
+ goto err_pipeline;
+ }
+
+ ret = unicam_num_data_lanes(unicam);
+ if (ret < 0)
+ goto err_pipeline;
+
+ unicam->pipe.num_data_lanes = ret;
+
+ dev_dbg(unicam->dev, "Running with %u data lanes, nodes %u\n",
+ unicam->pipe.num_data_lanes, unicam->pipe.nodes);
+ }
+
+ node->streaming = true;
+
+ /* Arm the node with the first buffer from the DMA queue. */
+ spin_lock_irqsave(&node->dma_queue_lock, flags);
+ buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list);
+ node->cur_frm = buf;
+ node->next_frm = buf;
+ list_del(&buf->list);
+ spin_unlock_irqrestore(&node->dma_queue_lock, flags);
+
+ /*
+ * Wait for all the video devices in the pipeline to have been started
+ * before starting the hardware. In the general case, this would
+ * prevent capturing multiple streams independently. However, the
+ * Unicam DMA engines are not generic, they have been designed to
+ * capture image data and embedded data from the same camera sensor.
+ * Not only does the main use case not benefit from independent
+ * capture, it requires proper synchronization of the streams at start
+ * time.
+ */
+ if (unicam->pipe.pipe.start_count < hweight32(unicam->pipe.nodes))
+ return 0;
+
+ ret = pm_runtime_resume_and_get(unicam->dev);
+ if (ret < 0) {
+ dev_err(unicam->dev, "PM runtime resume failed: %d\n", ret);
+ goto err_pipeline;
+ }
+
+ /* Enable the streams on the source. */
+ ret = v4l2_subdev_enable_streams(&unicam->subdev.sd,
+ UNICAM_SD_PAD_SOURCE_IMAGE,
+ BIT(0));
+ if (ret < 0) {
+ dev_err(unicam->dev, "stream on failed in subdev\n");
+ goto err_pm_put;
+ }
+
+ if (unicam->pipe.nodes & BIT(UNICAM_METADATA_NODE)) {
+ ret = v4l2_subdev_enable_streams(&unicam->subdev.sd,
+ UNICAM_SD_PAD_SOURCE_METADATA,
+ BIT(0));
+ if (ret < 0) {
+ dev_err(unicam->dev, "stream on failed in subdev\n");
+ goto err_disable_streams;
+ }
+ }
+
+ return 0;
+
+err_disable_streams:
+ v4l2_subdev_disable_streams(&unicam->subdev.sd,
+ UNICAM_SD_PAD_SOURCE_IMAGE, BIT(0));
+err_pm_put:
+ pm_runtime_put_sync(unicam->dev);
+err_pipeline:
+ video_device_pipeline_stop(&node->video_dev);
+err_buffers:
+ unicam_return_buffers(node, VB2_BUF_STATE_QUEUED);
+ node->streaming = false;
+ return ret;
+}
+
+static void unicam_stop_streaming(struct vb2_queue *vq)
+{
+ struct unicam_node *node = vb2_get_drv_priv(vq);
+ struct unicam_device *unicam = node->dev;
+
+ /* Stop the hardware when the first video device gets stopped. */
+ if (unicam->pipe.pipe.start_count == hweight32(unicam->pipe.nodes)) {
+ if (unicam->pipe.nodes & BIT(UNICAM_METADATA_NODE))
+ v4l2_subdev_disable_streams(&unicam->subdev.sd,
+ UNICAM_SD_PAD_SOURCE_METADATA,
+ BIT(0));
+
+ v4l2_subdev_disable_streams(&unicam->subdev.sd,
+ UNICAM_SD_PAD_SOURCE_IMAGE,
+ BIT(0));
+
+ pm_runtime_put(unicam->dev);
+ }
+
+ node->streaming = false;
+
+ video_device_pipeline_stop(&node->video_dev);
+
+ /* Clear all queued buffers for the node */
+ unicam_return_buffers(node, VB2_BUF_STATE_ERROR);
+}
+
+static void unicam_buffer_queue(struct vb2_buffer *vb)
+{
+ struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
+ struct unicam_buffer *buf = to_unicam_buffer(vb);
+
+ spin_lock_irq(&node->dma_queue_lock);
+ list_add_tail(&buf->list, &node->dma_queue);
+ spin_unlock_irq(&node->dma_queue_lock);
+}
+
+static const struct vb2_ops unicam_video_qops = {
+ .queue_setup = unicam_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_prepare = unicam_buffer_prepare,
+ .start_streaming = unicam_start_streaming,
+ .stop_streaming = unicam_stop_streaming,
+ .buf_queue = unicam_buffer_queue,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 video device operations
+ */
+
+static int unicam_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, UNICAM_MODULE_NAME, sizeof(cap->driver));
+ strscpy(cap->card, UNICAM_MODULE_NAME, sizeof(cap->card));
+
+ cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE;
+
+ return 0;
+}
+
+static int unicam_enum_fmt_vid(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ unsigned int index;
+ unsigned int i;
+
+ for (i = 0, index = 0; i < ARRAY_SIZE(unicam_image_formats); i++) {
+ if (f->mbus_code && unicam_image_formats[i].code != f->mbus_code)
+ continue;
+
+ if (index == f->index) {
+ f->pixelformat = unicam_image_formats[i].fourcc;
+ return 0;
+ }
+
+ index++;
+
+ if (!unicam_image_formats[i].unpacked_fourcc)
+ continue;
+
+ if (index == f->index) {
+ f->pixelformat = unicam_image_formats[i].unpacked_fourcc;
+ return 0;
+ }
+
+ index++;
+ }
+
+ return -EINVAL;
+}
+
+static int unicam_g_fmt_vid(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ *f = node->fmt;
+
+ return 0;
+}
+
+static void __unicam_try_fmt_vid(struct unicam_node *node,
+ struct v4l2_pix_format *pix)
+{
+ const struct unicam_format_info *fmtinfo;
+
+ /*
+ * Default to the first format if the requested pixel format code isn't
+ * supported.
+ */
+ fmtinfo = unicam_find_format_by_fourcc(pix->pixelformat,
+ UNICAM_SD_PAD_SOURCE_IMAGE);
+ if (!fmtinfo) {
+ fmtinfo = &unicam_image_formats[0];
+ pix->pixelformat = fmtinfo->fourcc;
+ }
+
+ unicam_calc_image_size_bpl(node->dev, fmtinfo, pix);
+
+ if (pix->field == V4L2_FIELD_ANY)
+ pix->field = V4L2_FIELD_NONE;
+}
+
+static int unicam_try_fmt_vid(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ __unicam_try_fmt_vid(node, &f->fmt.pix);
+ return 0;
+}
+
+static int unicam_s_fmt_vid(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ if (vb2_is_busy(&node->buffer_queue))
+ return -EBUSY;
+
+ __unicam_try_fmt_vid(node, &f->fmt.pix);
+ node->fmt = *f;
+
+ return 0;
+}
+
+static int unicam_enum_fmt_meta(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ unsigned int i, index;
+
+ for (i = 0, index = 0; i < ARRAY_SIZE(unicam_meta_formats); i++) {
+ if (f->mbus_code && unicam_meta_formats[i].code != f->mbus_code)
+ continue;
+
+ if (index == f->index) {
+ f->pixelformat = unicam_meta_formats[i].fourcc;
+ f->type = V4L2_BUF_TYPE_META_CAPTURE;
+ f->flags = V4L2_FMT_FLAG_META_LINE_BASED;
+ return 0;
+ }
+
+ index++;
+ }
+
+ return -EINVAL;
+}
+
+static int unicam_g_fmt_meta(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ f->fmt.meta = node->fmt.fmt.meta;
+
+ return 0;
+}
+
+static const struct unicam_format_info *
+__unicam_try_fmt_meta(struct unicam_node *node, struct v4l2_meta_format *meta)
+{
+ const struct unicam_format_info *fmtinfo;
+
+ /*
+ * Default to the first format if the requested pixel format code isn't
+ * supported.
+ */
+ fmtinfo = unicam_find_format_by_fourcc(meta->dataformat,
+ UNICAM_SD_PAD_SOURCE_METADATA);
+ if (!fmtinfo) {
+ fmtinfo = &unicam_meta_formats[0];
+ meta->dataformat = fmtinfo->fourcc;
+ }
+
+ unicam_calc_meta_size_bpl(node->dev, fmtinfo, meta);
+
+ return fmtinfo;
+}
+
+static int unicam_try_fmt_meta(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ __unicam_try_fmt_meta(node, &f->fmt.meta);
+ return 0;
+}
+
+static int unicam_s_fmt_meta(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ if (vb2_is_busy(&node->buffer_queue))
+ return -EBUSY;
+
+ __unicam_try_fmt_meta(node, &f->fmt.meta);
+ node->fmt = *f;
+
+ return 0;
+}
+
+static int unicam_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct unicam_node *node = video_drvdata(file);
+ int ret = -EINVAL;
+
+ if (fsize->index > 0)
+ return ret;
+
+ if (is_image_node(node)) {
+ if (!unicam_find_format_by_fourcc(fsize->pixel_format,
+ UNICAM_SD_PAD_SOURCE_IMAGE))
+ return ret;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+ fsize->stepwise.min_width = UNICAM_IMAGE_MIN_WIDTH;
+ fsize->stepwise.max_width = UNICAM_IMAGE_MAX_WIDTH;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.min_height = UNICAM_IMAGE_MIN_HEIGHT;
+ fsize->stepwise.max_height = UNICAM_IMAGE_MAX_HEIGHT;
+ fsize->stepwise.step_height = 1;
+ } else {
+ if (!unicam_find_format_by_fourcc(fsize->pixel_format,
+ UNICAM_SD_PAD_SOURCE_METADATA))
+ return ret;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+ fsize->stepwise.min_width = UNICAM_META_MIN_WIDTH;
+ fsize->stepwise.max_width = UNICAM_META_MAX_WIDTH;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.min_height = UNICAM_META_MIN_HEIGHT;
+ fsize->stepwise.max_height = UNICAM_META_MAX_HEIGHT;
+ fsize->stepwise.step_height = 1;
+ }
+
+ return 0;
+}
+
+static int unicam_log_status(struct file *file, void *fh)
+{
+ struct unicam_node *node = video_drvdata(file);
+ struct unicam_device *unicam = node->dev;
+ u32 reg;
+
+ /* status for sub devices */
+ v4l2_device_call_all(&unicam->v4l2_dev, 0, core, log_status);
+
+ dev_info(unicam->dev, "-----Receiver status-----\n");
+ dev_info(unicam->dev, "V4L2 width/height: %ux%u\n",
+ node->fmt.fmt.pix.width, node->fmt.fmt.pix.height);
+ dev_info(unicam->dev, "V4L2 format: %08x\n",
+ node->fmt.fmt.pix.pixelformat);
+ reg = unicam_reg_read(unicam, UNICAM_IPIPE);
+ dev_info(unicam->dev, "Unpacking/packing: %u / %u\n",
+ unicam_get_field(reg, UNICAM_PUM_MASK),
+ unicam_get_field(reg, UNICAM_PPM_MASK));
+ dev_info(unicam->dev, "----Live data----\n");
+ dev_info(unicam->dev, "Programmed stride: %4u\n",
+ unicam_reg_read(unicam, UNICAM_IBLS));
+ dev_info(unicam->dev, "Detected resolution: %ux%u\n",
+ unicam_reg_read(unicam, UNICAM_IHSTA),
+ unicam_reg_read(unicam, UNICAM_IVSTA));
+ dev_info(unicam->dev, "Write pointer: %08x\n",
+ unicam_reg_read(unicam, UNICAM_IBWP));
+
+ return 0;
+}
+
+static int unicam_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_FRAME_SYNC:
+ return v4l2_event_subscribe(fh, sub, 2, NULL);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct v4l2_ioctl_ops unicam_ioctl_ops = {
+ .vidioc_querycap = unicam_querycap,
+
+ .vidioc_enum_fmt_vid_cap = unicam_enum_fmt_vid,
+ .vidioc_g_fmt_vid_cap = unicam_g_fmt_vid,
+ .vidioc_try_fmt_vid_cap = unicam_try_fmt_vid,
+ .vidioc_s_fmt_vid_cap = unicam_s_fmt_vid,
+
+ .vidioc_enum_fmt_meta_cap = unicam_enum_fmt_meta,
+ .vidioc_g_fmt_meta_cap = unicam_g_fmt_meta,
+ .vidioc_try_fmt_meta_cap = unicam_try_fmt_meta,
+ .vidioc_s_fmt_meta_cap = unicam_s_fmt_meta,
+
+ .vidioc_enum_framesizes = unicam_enum_framesizes,
+
+ .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_expbuf = vb2_ioctl_expbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_log_status = unicam_log_status,
+ .vidioc_subscribe_event = unicam_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/* unicam capture driver file operations */
+static const struct v4l2_file_operations unicam_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+static int unicam_video_link_validate(struct media_link *link)
+{
+ struct video_device *vdev =
+ media_entity_to_video_device(link->sink->entity);
+ struct v4l2_subdev *sd =
+ media_entity_to_v4l2_subdev(link->source->entity);
+ struct unicam_node *node = video_get_drvdata(vdev);
+ const u32 pad = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
+ : UNICAM_SD_PAD_SOURCE_METADATA;
+ const struct v4l2_mbus_framefmt *format;
+ struct v4l2_subdev_state *state;
+ int ret = 0;
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ format = v4l2_subdev_state_get_format(state, pad, 0);
+ if (!format) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (is_image_node(node)) {
+ const struct v4l2_pix_format *fmt = &node->fmt.fmt.pix;
+ const struct unicam_format_info *fmtinfo;
+
+ fmtinfo = unicam_find_format_by_fourcc(fmt->pixelformat,
+ UNICAM_SD_PAD_SOURCE_IMAGE);
+ if (WARN_ON(!fmtinfo)) {
+ ret = -EPIPE;
+ goto out;
+ }
+
+ if (fmtinfo->code != format->code ||
+ fmt->height != format->height ||
+ fmt->width != format->width ||
+ fmt->field != format->field) {
+ dev_dbg(node->dev->dev,
+ "image: (%u x %u) 0x%08x %s != (%u x %u) 0x%08x %s\n",
+ fmt->width, fmt->height, fmtinfo->code,
+ v4l2_field_names[fmt->field],
+ format->width, format->height, format->code,
+ v4l2_field_names[format->field]);
+ ret = -EPIPE;
+ }
+ } else {
+ const struct v4l2_meta_format *fmt = &node->fmt.fmt.meta;
+
+ const struct unicam_format_info *fmtinfo;
+
+ fmtinfo = unicam_find_format_by_fourcc(fmt->dataformat,
+ UNICAM_SD_PAD_SOURCE_METADATA);
+ if (WARN_ON(!fmtinfo)) {
+ ret = -EPIPE;
+ goto out;
+ }
+
+ if (fmtinfo->code != format->code ||
+ fmt->height != format->height ||
+ fmt->width != format->width) {
+ dev_dbg(node->dev->dev,
+ "meta: (%u x %u) 0x%04x != (%u x %u) 0x%04x\n",
+ fmt->width, fmt->height, fmtinfo->code,
+ format->width, format->height, format->code);
+ ret = -EPIPE;
+ }
+ }
+
+out:
+ v4l2_subdev_unlock_state(state);
+ return ret;
+}
+
+static const struct media_entity_operations unicam_video_media_ops = {
+ .link_validate = unicam_video_link_validate,
+};
+
+static void unicam_node_release(struct video_device *vdev)
+{
+ struct unicam_node *node = video_get_drvdata(vdev);
+
+ unicam_put(node->dev);
+}
+
+static void unicam_set_default_format(struct unicam_node *node)
+{
+ if (is_image_node(node)) {
+ struct v4l2_pix_format *fmt = &node->fmt.fmt.pix;
+ const struct unicam_format_info *fmtinfo =
+ &unicam_image_formats[0];
+
+ node->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ v4l2_fill_pix_format(fmt, &unicam_default_image_format);
+ fmt->pixelformat = fmtinfo->fourcc;
+ unicam_calc_image_size_bpl(node->dev, fmtinfo, fmt);
+ } else {
+ struct v4l2_meta_format *fmt = &node->fmt.fmt.meta;
+ const struct unicam_format_info *fmtinfo =
+ &unicam_meta_formats[0];
+
+ node->fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
+
+ fmt->dataformat = fmtinfo->fourcc;
+ fmt->width = unicam_default_meta_format.width;
+ fmt->height = unicam_default_meta_format.height;
+ unicam_calc_meta_size_bpl(node->dev, fmtinfo, fmt);
+ }
+}
+
+static int unicam_register_node(struct unicam_device *unicam,
+ enum unicam_node_type type)
+{
+ const u32 pad_index = type == UNICAM_IMAGE_NODE
+ ? UNICAM_SD_PAD_SOURCE_IMAGE
+ : UNICAM_SD_PAD_SOURCE_METADATA;
+ struct unicam_node *node = &unicam->node[type];
+ struct video_device *vdev = &node->video_dev;
+ struct vb2_queue *q = &node->buffer_queue;
+ int ret;
+
+ node->dev = unicam_get(unicam);
+ node->id = type;
+
+ spin_lock_init(&node->dma_queue_lock);
+
+ INIT_LIST_HEAD(&node->dma_queue);
+
+ /* Initialize the videobuf2 queue. */
+ q->type = type == UNICAM_IMAGE_NODE ? V4L2_BUF_TYPE_VIDEO_CAPTURE
+ : V4L2_BUF_TYPE_META_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->drv_priv = node;
+ q->ops = &unicam_video_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct unicam_buffer);
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &unicam->lock;
+ q->min_queued_buffers = 1;
+ q->dev = unicam->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret) {
+ dev_err(unicam->dev, "vb2_queue_init() failed\n");
+ goto err_unicam_put;
+ }
+
+ /* Initialize the video device. */
+ vdev->release = unicam_node_release;
+ vdev->fops = &unicam_fops;
+ vdev->ioctl_ops = &unicam_ioctl_ops;
+ vdev->v4l2_dev = &unicam->v4l2_dev;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->queue = q;
+ vdev->lock = &unicam->lock;
+ vdev->device_caps = type == UNICAM_IMAGE_NODE
+ ? V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_META_CAPTURE;
+ vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
+ vdev->entity.ops = &unicam_video_media_ops;
+
+ snprintf(vdev->name, sizeof(vdev->name), "%s-%s", UNICAM_MODULE_NAME,
+ type == UNICAM_IMAGE_NODE ? "image" : "embedded");
+
+ video_set_drvdata(vdev, node);
+
+ if (type == UNICAM_IMAGE_NODE)
+ vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
+
+ node->pad.flags = MEDIA_PAD_FL_SINK;
+
+ ret = media_entity_pads_init(&vdev->entity, 1, &node->pad);
+ if (ret)
+ goto err_unicam_put;
+
+ node->dummy_buf.size = UNICAM_DUMMY_BUF_SIZE;
+ node->dummy_buf_cpu_addr = dma_alloc_coherent(unicam->dev,
+ node->dummy_buf.size,
+ &node->dummy_buf.dma_addr,
+ GFP_KERNEL);
+ if (!node->dummy_buf_cpu_addr) {
+ dev_err(unicam->dev, "Unable to allocate dummy buffer.\n");
+ ret = -ENOMEM;
+ goto err_entity_cleanup;
+ }
+
+ unicam_set_default_format(node);
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ dev_err(unicam->dev, "Unable to register video device %s\n",
+ vdev->name);
+ goto err_dma_free;
+ }
+
+ node->registered = true;
+
+ ret = media_create_pad_link(&unicam->subdev.sd.entity,
+ pad_index,
+ &node->video_dev.entity,
+ 0,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+ if (ret) {
+ /*
+ * No need for cleanup, the caller will unregister the
+ * video device, which will drop the reference on the
+ * device and trigger the cleanup.
+ */
+ dev_err(unicam->dev, "Unable to create pad link for %s\n",
+ unicam->sensor.subdev->name);
+ return ret;
+ }
+
+ return 0;
+
+err_dma_free:
+ dma_free_coherent(unicam->dev, node->dummy_buf.size,
+ node->dummy_buf_cpu_addr,
+ node->dummy_buf.dma_addr);
+err_entity_cleanup:
+ media_entity_cleanup(&vdev->entity);
+err_unicam_put:
+ unicam_put(unicam);
+ return ret;
+}
+
+static void unicam_unregister_nodes(struct unicam_device *unicam)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+ struct unicam_node *node = &unicam->node[i];
+
+ if (node->dummy_buf_cpu_addr)
+ dma_free_coherent(unicam->dev, node->dummy_buf.size,
+ node->dummy_buf_cpu_addr,
+ node->dummy_buf.dma_addr);
+
+ if (node->registered) {
+ video_unregister_device(&node->video_dev);
+ node->registered = false;
+ }
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Power management
+ */
+
+static int unicam_runtime_resume(struct device *dev)
+{
+ struct unicam_device *unicam = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_set_min_rate(unicam->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
+ if (ret) {
+ dev_err(unicam->dev, "failed to set up VPU clock\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(unicam->vpu_clock);
+ if (ret) {
+ dev_err(unicam->dev, "Failed to enable VPU clock: %d\n", ret);
+ goto err_vpu_clock;
+ }
+
+ ret = clk_set_rate(unicam->clock, 100 * 1000 * 1000);
+ if (ret) {
+ dev_err(unicam->dev, "failed to set up CSI clock\n");
+ goto err_vpu_prepare;
+ }
+
+ ret = clk_prepare_enable(unicam->clock);
+ if (ret) {
+ dev_err(unicam->dev, "Failed to enable CSI clock: %d\n", ret);
+ goto err_vpu_prepare;
+ }
+
+ return 0;
+
+err_vpu_prepare:
+ clk_disable_unprepare(unicam->vpu_clock);
+err_vpu_clock:
+ if (clk_set_min_rate(unicam->vpu_clock, 0))
+ dev_err(unicam->dev, "failed to reset the VPU clock\n");
+
+ return ret;
+}
+
+static int unicam_runtime_suspend(struct device *dev)
+{
+ struct unicam_device *unicam = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(unicam->clock);
+
+ if (clk_set_min_rate(unicam->vpu_clock, 0))
+ dev_err(unicam->dev, "failed to reset the VPU clock\n");
+
+ clk_disable_unprepare(unicam->vpu_clock);
+
+ return 0;
+}
+
+static const struct dev_pm_ops unicam_pm_ops = {
+ RUNTIME_PM_OPS(unicam_runtime_suspend, unicam_runtime_resume, NULL)
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 async notifier
+ */
+
+static int unicam_async_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_connection *asc)
+{
+ struct unicam_device *unicam = notifier_to_unicam_device(notifier);
+ struct media_pad *sink = &unicam->subdev.pads[UNICAM_SD_PAD_SINK];
+ struct media_pad *source;
+ int ret;
+
+ dev_dbg(unicam->dev, "Using sensor %s for capture\n",
+ subdev->name);
+
+ ret = v4l2_create_fwnode_links_to_pad(subdev, sink, MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+ if (ret)
+ return ret;
+
+ source = media_pad_remote_pad_unique(sink);
+ if (!source) {
+ dev_err(unicam->dev, "No connected sensor pad\n");
+ return -ENOTCONN;
+ }
+
+ unicam->sensor.subdev = subdev;
+ unicam->sensor.pad = source;
+
+ return 0;
+}
+
+static int unicam_async_complete(struct v4l2_async_notifier *notifier)
+{
+ struct unicam_device *unicam = notifier_to_unicam_device(notifier);
+ int ret;
+
+ ret = unicam_register_node(unicam, UNICAM_IMAGE_NODE);
+ if (ret) {
+ dev_err(unicam->dev, "Unable to register image video device.\n");
+ goto unregister;
+ }
+
+ ret = unicam_register_node(unicam, UNICAM_METADATA_NODE);
+ if (ret) {
+ dev_err(unicam->dev, "Unable to register metadata video device.\n");
+ goto unregister;
+ }
+
+ ret = v4l2_device_register_subdev_nodes(&unicam->v4l2_dev);
+ if (ret) {
+ dev_err(unicam->dev, "Unable to register subdev nodes.\n");
+ goto unregister;
+ }
+
+ return 0;
+
+unregister:
+ unicam_unregister_nodes(unicam);
+ unicam_put(unicam);
+
+ return ret;
+}
+
+static const struct v4l2_async_notifier_operations unicam_async_ops = {
+ .bound = unicam_async_bound,
+ .complete = unicam_async_complete,
+};
+
+static int unicam_async_nf_init(struct unicam_device *unicam)
+{
+ struct v4l2_fwnode_endpoint ep = { };
+ struct fwnode_handle *ep_handle;
+ struct v4l2_async_connection *asc;
+ int ret;
+
+ ret = of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
+ &unicam->max_data_lanes);
+ if (ret < 0) {
+ dev_err(unicam->dev, "Missing %s DT property\n",
+ "brcm,num-data-lanes");
+ return -EINVAL;
+ }
+
+ /* Get and parse the local endpoint. */
+ ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev), 0, 0,
+ FWNODE_GRAPH_ENDPOINT_NEXT);
+ if (!ep_handle) {
+ dev_err(unicam->dev, "No endpoint found\n");
+ return -ENODEV;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(ep_handle, &ep);
+ if (ret) {
+ dev_err(unicam->dev, "Failed to parse endpoint: %d\n", ret);
+ goto error;
+ }
+
+ unicam->bus_type = ep.bus_type;
+
+ switch (ep.bus_type) {
+ case V4L2_MBUS_CSI2_DPHY: {
+ unsigned int num_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
+
+ if (num_data_lanes != 1 && num_data_lanes != 2 &&
+ num_data_lanes != 4) {
+ dev_err(unicam->dev, "%u data lanes not supported\n",
+ num_data_lanes);
+ goto error;
+ }
+
+ if (num_data_lanes > unicam->max_data_lanes) {
+ dev_err(unicam->dev,
+ "Endpoint uses %u data lanes when %u are supported\n",
+ num_data_lanes, unicam->max_data_lanes);
+ goto error;
+ }
+
+ unicam->max_data_lanes = num_data_lanes;
+ unicam->bus_flags = ep.bus.mipi_csi2.flags;
+ break;
+ }
+
+ case V4L2_MBUS_CCP2:
+ unicam->max_data_lanes = 1;
+ unicam->bus_flags = ep.bus.mipi_csi1.strobe;
+ break;
+
+ default:
+ /* Unsupported bus type */
+ dev_err(unicam->dev, "Unsupported bus type %u\n", ep.bus_type);
+ goto error;
+ }
+
+ /* Initialize and register the async notifier. */
+ v4l2_async_nf_init(&unicam->notifier, &unicam->v4l2_dev);
+
+ asc = v4l2_async_nf_add_fwnode_remote(&unicam->notifier, ep_handle,
+ struct v4l2_async_connection);
+ fwnode_handle_put(ep_handle);
+ ep_handle = NULL;
+
+ if (IS_ERR(asc)) {
+ ret = PTR_ERR(asc);
+ dev_err(unicam->dev, "Failed to add entry to notifier: %d\n",
+ ret);
+ goto error;
+ }
+
+ unicam->notifier.ops = &unicam_async_ops;
+
+ ret = v4l2_async_nf_register(&unicam->notifier);
+ if (ret) {
+ dev_err(unicam->dev, "Error registering device notifier: %d\n",
+ ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ fwnode_handle_put(ep_handle);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Probe & remove
+ */
+
+static int unicam_media_init(struct unicam_device *unicam)
+{
+ int ret;
+
+ unicam->mdev.dev = unicam->dev;
+ strscpy(unicam->mdev.model, UNICAM_MODULE_NAME,
+ sizeof(unicam->mdev.model));
+ unicam->mdev.hw_revision = 0;
+
+ media_device_init(&unicam->mdev);
+
+ unicam->v4l2_dev.mdev = &unicam->mdev;
+
+ ret = v4l2_device_register(unicam->dev, &unicam->v4l2_dev);
+ if (ret < 0) {
+ dev_err(unicam->dev, "Unable to register v4l2 device\n");
+ goto err_media_cleanup;
+ }
+
+ ret = media_device_register(&unicam->mdev);
+ if (ret < 0) {
+ dev_err(unicam->dev,
+ "Unable to register media-controller device\n");
+ goto err_v4l2_unregister;
+ }
+
+ return 0;
+
+err_v4l2_unregister:
+ v4l2_device_unregister(&unicam->v4l2_dev);
+err_media_cleanup:
+ media_device_cleanup(&unicam->mdev);
+ return ret;
+}
+
+static int unicam_probe(struct platform_device *pdev)
+{
+ struct unicam_device *unicam;
+ int ret;
+
+ unicam = kzalloc(sizeof(*unicam), GFP_KERNEL);
+ if (!unicam)
+ return -ENOMEM;
+
+ kref_init(&unicam->kref);
+ mutex_init(&unicam->lock);
+
+ unicam->dev = &pdev->dev;
+ platform_set_drvdata(pdev, unicam);
+
+ unicam->base = devm_platform_ioremap_resource_byname(pdev, "unicam");
+ if (IS_ERR(unicam->base)) {
+ ret = PTR_ERR(unicam->base);
+ goto err_unicam_put;
+ }
+
+ unicam->clk_gate_base = devm_platform_ioremap_resource_byname(pdev, "cmi");
+ if (IS_ERR(unicam->clk_gate_base)) {
+ ret = PTR_ERR(unicam->clk_gate_base);
+ goto err_unicam_put;
+ }
+
+ unicam->clock = devm_clk_get(&pdev->dev, "lp");
+ if (IS_ERR(unicam->clock)) {
+ dev_err(unicam->dev, "Failed to get lp clock\n");
+ ret = PTR_ERR(unicam->clock);
+ goto err_unicam_put;
+ }
+
+ unicam->vpu_clock = devm_clk_get(&pdev->dev, "vpu");
+ if (IS_ERR(unicam->vpu_clock)) {
+ dev_err(unicam->dev, "Failed to get vpu clock\n");
+ ret = PTR_ERR(unicam->vpu_clock);
+ goto err_unicam_put;
+ }
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "No IRQ resource\n");
+ ret = -EINVAL;
+ goto err_unicam_put;
+ }
+
+ ret = devm_request_irq(&pdev->dev, ret, unicam_isr, 0,
+ "unicam_capture0", unicam);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to request interrupt\n");
+ ret = -EINVAL;
+ goto err_unicam_put;
+ }
+
+ /* Enable the block power domain. */
+ pm_runtime_enable(&pdev->dev);
+
+ ret = unicam_media_init(unicam);
+ if (ret)
+ goto err_pm_runtime;
+
+ ret = unicam_subdev_init(unicam);
+ if (ret)
+ goto err_media_unregister;
+
+ ret = unicam_async_nf_init(unicam);
+ if (ret)
+ goto err_subdev_unregister;
+
+ return 0;
+
+err_subdev_unregister:
+ unicam_subdev_cleanup(unicam);
+err_media_unregister:
+ media_device_unregister(&unicam->mdev);
+err_pm_runtime:
+ pm_runtime_disable(&pdev->dev);
+err_unicam_put:
+ unicam_put(unicam);
+
+ return ret;
+}
+
+static int unicam_remove(struct platform_device *pdev)
+{
+ struct unicam_device *unicam = platform_get_drvdata(pdev);
+
+ unicam_unregister_nodes(unicam);
+ v4l2_device_unregister(&unicam->v4l2_dev);
+ media_device_unregister(&unicam->mdev);
+ v4l2_async_nf_unregister(&unicam->notifier);
+
+ unicam_subdev_cleanup(unicam);
+
+ unicam_put(unicam);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id unicam_of_match[] = {
+ { .compatible = "brcm,bcm2835-unicam", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, unicam_of_match);
+
+static struct platform_driver unicam_driver = {
+ .probe = unicam_probe,
+ .remove = unicam_remove,
+ .driver = {
+ .name = UNICAM_MODULE_NAME,
+ .pm = pm_ptr(&unicam_pm_ops),
+ .of_match_table = of_match_ptr(unicam_of_match),
+ },
+};
+
+module_platform_driver(unicam_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("BCM2835 Unicam driver");
+MODULE_LICENSE("GPL");
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* [PATCH v9 05/10] ARM: dts: bcm2835-rpi: Move duplicate firmware-clocks to bcm2835-rpi.dtsi
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Dave Stevenson, David Plowman, Jean-Michel Hautbois, Hans Verkuil,
Naushir Patuck, Sakari Ailus, kernel-list, linux-rpi-kernel,
Florian Fainelli, Ray Jui, Scott Branden,
bcm-kernel-feedback-list, Conor Dooley, Krzysztof Kozlowski,
Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
The Raspberry Pi firmware handles clocks on all BCM2835-derived SoCs. It
is currently defined identically in both bcm2711-rpi.dtsi and
bcm2835-rpi-common.dtsi. Move the definitions to the common
bcm2835-rpi.dtsi file.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
---
Changes since v8:
- Drop firmware_clocks from bcm2835-rpi-common.dtsi
---
arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi | 5 -----
arch/arm/boot/dts/broadcom/bcm2835-rpi-common.dtsi | 7 -------
arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi | 5 +++++
3 files changed, 5 insertions(+), 12 deletions(-)
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
index d233a191c139..86188eabeb24 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
@@ -20,11 +20,6 @@ aliases {
};
&firmware {
- firmware_clocks: clocks {
- compatible = "raspberrypi,firmware-clocks";
- #clock-cells = <1>;
- };
-
expgpio: gpio {
compatible = "raspberrypi,firmware-gpio";
gpio-controller;
diff --git a/arch/arm/boot/dts/broadcom/bcm2835-rpi-common.dtsi b/arch/arm/boot/dts/broadcom/bcm2835-rpi-common.dtsi
index 4e7b4a592da7..8b3c21d9f333 100644
--- a/arch/arm/boot/dts/broadcom/bcm2835-rpi-common.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm2835-rpi-common.dtsi
@@ -7,13 +7,6 @@
#include <dt-bindings/power/raspberrypi-power.h>
-&firmware {
- firmware_clocks: clocks {
- compatible = "raspberrypi,firmware-clocks";
- #clock-cells = <1>;
- };
-};
-
&hdmi {
clocks = <&firmware_clocks 9>,
<&firmware_clocks 13>;
diff --git a/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi
index 761a9da97bd0..6e6dc109f0c2 100644
--- a/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi
@@ -5,6 +5,11 @@ soc {
firmware: firmware {
compatible = "raspberrypi,bcm2835-firmware", "simple-mfd";
mboxes = <&mailbox>;
+
+ firmware_clocks: clocks {
+ compatible = "raspberrypi,firmware-clocks";
+ #clock-cells = <1>;
+ };
};
power: power {
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* [PATCH v9 06/10] ARM: dts: bcm2835: Add Unicam CSI nodes
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Jean-Michel Hautbois, Dave Stevenson, David Plowman,
Jean-Michel Hautbois, Hans Verkuil, Naushir Patuck, Sakari Ailus,
kernel-list, linux-rpi-kernel, Florian Fainelli, Ray Jui,
Scott Branden, bcm-kernel-feedback-list, Conor Dooley,
Krzysztof Kozlowski, Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
From: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>
Add both MIPI CSI-2 nodes in the bcm283x tree and take care of the
Raspberry Pi / BCM2711 specific in the related files.
Signed-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
---
arch/arm/boot/dts/broadcom/bcm2711.dtsi | 8 +++++++
arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi | 14 ++++++++++++
arch/arm/boot/dts/broadcom/bcm283x.dtsi | 24 +++++++++++++++++++++
3 files changed, 46 insertions(+)
diff --git a/arch/arm/boot/dts/broadcom/bcm2711.dtsi b/arch/arm/boot/dts/broadcom/bcm2711.dtsi
index 22c7f1561344..1d8f9f80f935 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm2711.dtsi
@@ -1114,6 +1114,14 @@ &rmem {
#address-cells = <2>;
};
+&csi0 {
+ interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&csi1 {
+ interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+};
+
&cma {
/*
* arm64 reserves the CMA by default somewhere in ZONE_DMA32,
diff --git a/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi
index 6e6dc109f0c2..e9bf41b9f5c1 100644
--- a/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi
@@ -26,6 +26,20 @@ vchiq: mailbox@7e00b840 {
};
};
+&csi0 {
+ clocks = <&clocks BCM2835_CLOCK_CAM0>,
+ <&firmware_clocks 4>;
+ clock-names = "lp", "vpu";
+ power-domains = <&power RPI_POWER_DOMAIN_UNICAM0>;
+};
+
+&csi1 {
+ clocks = <&clocks BCM2835_CLOCK_CAM1>,
+ <&firmware_clocks 4>;
+ clock-names = "lp", "vpu";
+ power-domains = <&power RPI_POWER_DOMAIN_UNICAM1>;
+};
+
&gpio {
gpioout: gpioout {
brcm,pins = <6>;
diff --git a/arch/arm/boot/dts/broadcom/bcm283x.dtsi b/arch/arm/boot/dts/broadcom/bcm283x.dtsi
index 2ca8a2505a4d..69b0919f1324 100644
--- a/arch/arm/boot/dts/broadcom/bcm283x.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm283x.dtsi
@@ -454,6 +454,30 @@ dsi1: dsi@7e700000 {
status = "disabled";
};
+ csi0: csi@7e800000 {
+ compatible = "brcm,bcm2835-unicam";
+ reg = <0x7e800000 0x800>,
+ <0x7e802000 0x4>;
+ reg-names = "unicam", "cmi";
+ interrupts = <2 6>;
+ brcm,num-data-lanes = <2>;
+ status = "disabled";
+ port {
+ };
+ };
+
+ csi1: csi@7e801000 {
+ compatible = "brcm,bcm2835-unicam";
+ reg = <0x7e801000 0x800>,
+ <0x7e802004 0x4>;
+ reg-names = "unicam", "cmi";
+ interrupts = <2 7>;
+ brcm,num-data-lanes = <4>;
+ status = "disabled";
+ port {
+ };
+ };
+
i2c1: i2c@7e804000 {
compatible = "brcm,bcm2835-i2c";
reg = <0x7e804000 0x1000>;
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* [PATCH v9 07/10] ARM: dts: bcm2711-rpi: Add pinctrl-based multiplexing for I2C0
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Uwe Kleine-König, Dave Stevenson, David Plowman,
Jean-Michel Hautbois, Hans Verkuil, Naushir Patuck, Sakari Ailus,
kernel-list, linux-rpi-kernel, Florian Fainelli, Ray Jui,
Scott Branden, bcm-kernel-feedback-list, Conor Dooley,
Krzysztof Kozlowski, Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
From: Uwe Kleine-König <uwe@kleine-koenig.org>
BCM2711-based Raspberry Pi boards (4B, CM4 and 400) multiplex the I2C0
controller over two sets of pins, GPIO0+1 and GPIO44+45. The former is
exposed on the 40-pin header, while the latter is used for the CSI and
DSI connectors.
Add a pinctrl-based I2C bus multiplexer to bcm2711-rpi.dtsi to model
this multiplexing. The two child buses are named i2c0_0 and i2c0_1.
Note that if you modified the dts before to add devices to the i2c bus
appearing on pins gpio0 + gpio1 (either directly in the dts or using an
overlay), you have to put these into the i2c0_0 node introduced here
now.
Signed-off-by: Uwe Kleine-König <uwe@kleine-koenig.org>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
---
Changes since v7:
- Rename i2c0mux to i2c-mux0
Changes since v6:
- Don't disable i2c0mux by default
Changes since v3:
- Split addition of the RTC to a separate patch
- Move the mux to bcm2711-rpi.dtsi
---
arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi | 29 +++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
index 86188eabeb24..6bf4241fe3b7 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
@@ -17,6 +17,30 @@ aliases {
pcie0 = &pcie0;
blconfig = &blconfig;
};
+
+ i2c0mux: i2c-mux0 {
+ compatible = "i2c-mux-pinctrl";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c-parent = <&i2c0>;
+
+ pinctrl-names = "i2c0", "i2c0-vc";
+ pinctrl-0 = <&i2c0_gpio0>;
+ pinctrl-1 = <&i2c0_gpio44>;
+
+ i2c0_0: i2c@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ i2c0_1: i2c@1 {
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
};
&firmware {
@@ -49,6 +73,11 @@ &hvs {
clocks = <&firmware_clocks 4>;
};
+&i2c0 {
+ /delete-property/ pinctrl-names;
+ /delete-property/ pinctrl-0;
+};
+
&rmem {
/*
* RPi4's co-processor will copy the board's bootloader configuration
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* [PATCH v9 08/10] ARM: dts: bcm2711-rpi-cm4-io: Add RTC on I2C0
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Uwe Kleine-König, Dave Stevenson, David Plowman,
Jean-Michel Hautbois, Hans Verkuil, Naushir Patuck, Sakari Ailus,
kernel-list, linux-rpi-kernel, Florian Fainelli, Ray Jui,
Scott Branden, bcm-kernel-feedback-list, Conor Dooley,
Krzysztof Kozlowski, Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
From: Uwe Kleine-König <uwe@kleine-koenig.org>
The cm4-io board comes with a PCF85063 on I2C0, connected to the GPIO44
and GPIO45 pins. Add it to the device tree.
Signed-off-by: Uwe Kleine-König <uwe@kleine-koenig.org>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
---
Changes since v6:
- Drop unneeded status = "okay"
Changes since v4:
- Use the right part number in the compatible string
- Add the quartz-load-femtofarads property
Changes since v3:
- Separate addition of the RTC to a patch of its own
---
arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4-io.dts | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4-io.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4-io.dts
index d7ba02f586d3..7c6a5bdf48aa 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4-io.dts
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4-io.dts
@@ -101,6 +101,15 @@ &genet {
status = "okay";
};
+&i2c0_1 {
+ rtc@51 {
+ /* Attention: An alarm resets the machine */
+ compatible = "nxp,pcf85063a";
+ reg = <0x51>;
+ quartz-load-femtofarads = <7000>;
+ };
+};
+
&led_act {
gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
};
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* [PATCH v9 09/10] ARM: dts: bcm2711-rpi-4-b: Add CAM1 regulator
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Dave Stevenson, David Plowman, Jean-Michel Hautbois, Hans Verkuil,
Naushir Patuck, Sakari Ailus, kernel-list, linux-rpi-kernel,
Florian Fainelli, Ray Jui, Scott Branden,
bcm-kernel-feedback-list, Conor Dooley, Krzysztof Kozlowski,
Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
Add a fixed regulator to model the power supply to the camera connector.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
---
arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
index d5f8823230db..cfc8cb5e10ba 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
@@ -15,6 +15,13 @@ chosen {
stdout-path = "serial1:115200n8";
};
+ cam1_reg: regulator-cam1 {
+ compatible = "regulator-fixed";
+ regulator-name = "cam1-reg";
+ enable-active-high;
+ gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+ };
+
sd_io_1v8_reg: regulator-sd-io-1v8 {
compatible = "regulator-gpio";
regulator-name = "vdd-sd-io";
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* [PATCH v9 10/10] [DNI] arm64: dts: broadcom: Add overlay for Raspberry Pi 4B IMX219 camera
From: Laurent Pinchart @ 2024-04-02 0:04 UTC (permalink / raw)
To: linux-media
Cc: Dave Stevenson, David Plowman, Jean-Michel Hautbois, Hans Verkuil,
Naushir Patuck, Sakari Ailus, kernel-list, linux-rpi-kernel,
Florian Fainelli, Ray Jui, Scott Branden,
bcm-kernel-feedback-list, Conor Dooley, Krzysztof Kozlowski,
Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
For testing only at this point.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes since v6:
- Add combined DTB to dtbs
---
arch/arm64/boot/dts/broadcom/Makefile | 4 ++
.../dts/broadcom/bcm2711-rpi-4-b-imx219.dtso | 65 +++++++++++++++++++
2 files changed, 69 insertions(+)
create mode 100644 arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b-imx219.dtso
diff --git a/arch/arm64/boot/dts/broadcom/Makefile b/arch/arm64/boot/dts/broadcom/Makefile
index 8b4591ddd27c..59bf2891241b 100644
--- a/arch/arm64/boot/dts/broadcom/Makefile
+++ b/arch/arm64/boot/dts/broadcom/Makefile
@@ -12,6 +12,10 @@ dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-400.dtb \
bcm2837-rpi-cm3-io3.dtb \
bcm2837-rpi-zero-2-w.dtb
+bcm2711-rpi-4-b-imx219-dtbs := bcm2711-rpi-4-b.dtb bcm2711-rpi-4-b-imx219.dtbo
+
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-4-b-imx219.dtb
+
subdir-y += bcmbca
subdir-y += northstar2
subdir-y += stingray
diff --git a/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b-imx219.dtso b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b-imx219.dtso
new file mode 100644
index 000000000000..33c3219ca4c3
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b-imx219.dtso
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX219 camera module on VC I2C bus
+
+/dts-v1/;
+/plugin/;
+
+&{/} {
+ cam1_clk: cam1_clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <24000000>;
+ };
+};
+
+&csi1 {
+ status = "okay";
+
+ port {
+ csi_ep: endpoint {
+ remote-endpoint = <&cam_endpoint>;
+ clock-lanes = <0>;
+ data-lanes = <1 2>;
+ clock-noncontinuous;
+ };
+ };
+};
+
+&i2c0 {
+ status = "okay";
+};
+
+&i2c0_1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ camera@10 {
+ compatible = "sony,imx219";
+ reg = <0x10>;
+
+ clocks = <&cam1_clk>;
+ clock-names = "xclk";
+
+ VANA-supply = <&cam1_reg>; /* 2.8v */
+ VDIG-supply = <&cam1_reg>; /* 1.8v */
+ VDDL-supply = <&cam1_reg>; /* 1.2v */
+
+ rotation = <180>;
+ orientation = <2>;
+
+ port {
+ cam_endpoint: endpoint {
+ remote-endpoint = <&csi_ep>;
+ clock-lanes = <0>;
+ data-lanes = <1 2>;
+ clock-noncontinuous;
+ link-frequencies = /bits/ 64 <456000000>;
+ };
+ };
+ };
+};
+
+&i2c0mux {
+ status = "okay";
+};
--
Regards,
Laurent Pinchart
^ permalink raw reply related
* Re: [PATCH v9 00/10] media: Add driver for the Raspberry Pi <5 CSI-2 receiver
From: Laurent Pinchart @ 2024-04-02 0:11 UTC (permalink / raw)
To: Florian Fainelli
Cc: linux-media, Dave Stevenson, David Plowman, Jean-Michel Hautbois,
Hans Verkuil, Naushir Patuck, Sakari Ailus, kernel-list,
linux-rpi-kernel, Ray Jui, Scott Branden,
bcm-kernel-feedback-list, Conor Dooley, Krzysztof Kozlowski,
Rob Herring, devicetree
In-Reply-To: <20240402000424.4650-1-laurent.pinchart@ideasonboard.com>
Hi Florian,
I think patches 05/10 ("ARM: dts: bcm2835-rpi: Move duplicate
firmware-clocks to bcm2835-rpi.dtsi"), 07/10 ("ARM: dts: bcm2711-rpi:
Add pinctrl-based multiplexing for I2C0"), 08/10 ("ARM: dts:
bcm2711-rpi-cm4-io: Add RTC on I2C0") and 09/10 ("ARM: dts:
bcm2711-rpi-4-b: Add CAM1 regulator") are ready for you to merge. This
would help reducing further iterations of this series.
On Tue, Apr 02, 2024 at 03:04:07AM +0300, Laurent Pinchart wrote:
> Hello everybody,
>
> This patch series adds a new driver for the BCM2835 (and derivative)
> CCP2/CSI2 camera interface named Unicam. This IP core is found in the
> VC4-based Raspberry Pi, namely the Pi Zero, Pi 3 and Pi 4.
>
> Camera support for Raspberry Pi 4 currently relies on a downstream
> Unicam driver that live in the Raspberry Pi kernel tree ([1]). The
> driver uses the V4L2 API, but works around the lack of features in V4L2
> to properly support sensor embedded data. Since the Unicam driver
> development by Raspberry Pi, some of those features have been merged in
> the kernel (namely the V4L2 streams API) or are being developed (namely
> generic metadata formats and subdev internal pads), with patches posted
> for review on the linux-media mailing list ([2]).
>
> This new upstream driver is based on the downstream code, extensively
> reworked to use the new V4L2 APIs.
>
> The series is based on a merge of
>
> - v8 of the generic metadata and internal pads, rebased on v6.9-rc1 ([3])
> - the downstream ISP driver ported to mainline ([4])
>
> with a set of patches for the imx219 driver applied on top. For
> convenience, it can be found in [5]. Note that the ISP driver is getting
> upstreamed separately.
>
> As in v7, I have left the imx219 patches out, as they don't need to be
> bundled with the Unicam driver for review. They will be resubmitted
> separately.
>
> Compared to v8, I have addressed review comments (in patches 04/10 and
> 05/10) and fixed a few issues in the unicam driver. Please see
> individual patches for details. A few fixes for v4l2-compliance in the
> V4L2 subdev core have also been included in [3], they have been posted
> to the linux-media mailing list separately.
>
> The series starts with four patches that add the Unicam driver (04/10),
> with new V4L2 pixel formats (01/10 and 02/10) and DT bindings (03/10).
> The remaining patches cover DT integration (05/10 to 09/10) with a
> sample DT overlay for the IMX219 camera module (10/10).
>
> The patches have been tested on a Raspberry Pi 4 using an IMX219 camera
> module (the Raspberry Pi camera v2), with libcamera. Updates are needed
> to libcamera to use the new V4L2 APIs, patches have been posted to [6].
> For manual testing with media-ctl, corresponding API updates to
> v4l-utils have been posted to the linux-media mailing list ([7]) and are
> available for convenience in a git branch at [8].
>
> While more work is needed to be able to merge the generic metadata API
> (namely implementing support for the latest API version in media-ctl and
> v4l2-compliance), I'm happy with the unicam implementation, and I
> believe we're really nearing completion. This series, along with the
> libcamera support, help validating the new kernel APIs. We have reached
> a point where we can start converting other sensor drivers from the
> downstream Raspberry Pi kernel to the standard APIs for embedded data,
> as well as integrating the APIs in the Raspberry Pi 5 CFE driver.
>
> Here's the mandatory v4l2-compliance reports. It requires the v4l-utils
> updates from [7]. There is a total of 2 errors and 4 warnings, all
> related to the imx219 driver. They will be addressed separately, as part
> of the effort to add embedded data support to the imx219 driver.
>
> v4l2-compliance 1.27.0-5189, 64 bits, 64-bit time_t
> v4l2-compliance SHA: 44bd7e27daac 2024-04-01 22:18:20
>
> Compliance test for unicam device /dev/media0:
>
> Media Driver Info:
> Driver name : unicam
> Model : unicam
> Serial :
> Bus info : platform:fe801000.csi
> Media version : 6.9.0
> Hardware revision: 0x00000000 (0)
> Driver version : 6.9.0
>
> Required ioctls:
> test MEDIA_IOC_DEVICE_INFO: OK
> test invalid ioctls: OK
>
> Allow for multiple opens:
> test second /dev/media0 open: OK
> test MEDIA_IOC_DEVICE_INFO: OK
> test for unlimited opens: OK
>
> Media Controller ioctls:
> test MEDIA_IOC_G_TOPOLOGY: OK
> Entities: 4 Interfaces: 4 Pads: 8 Links: 7
> test MEDIA_IOC_ENUM_ENTITIES/LINKS: OK
> test MEDIA_IOC_SETUP_LINK: OK
>
> Total for unicam device /dev/media0: 8, Succeeded: 8, Failed: 0, Warnings: 0
> --------------------------------------------------------------------------------
> Compliance test for unicam device /dev/video0:
>
> Driver Info:
> Driver name : unicam
> Card type : unicam
> Bus info : platform:fe801000.csi
> Driver version : 6.9.0
> Capabilities : 0xa4a00001
> Video Capture
> Metadata Capture
> I/O MC
> Streaming
> Extended Pix Format
> Device Capabilities
> Device Caps : 0x24200001
> Video Capture
> I/O MC
> Streaming
> Extended Pix Format
> Media Driver Info:
> Driver name : unicam
> Model : unicam
> Serial :
> Bus info : platform:fe801000.csi
> Media version : 6.9.0
> Hardware revision: 0x00000000 (0)
> Driver version : 6.9.0
> Interface Info:
> ID : 0x0300000d
> Type : V4L Video
> Entity Info:
> ID : 0x0000000b (11)
> Name : unicam-image
> Function : V4L2 I/O
> Flags : default
> Pad 0x0100000c : 0: Sink
> Link 0x0200000f: from remote pad 0x1000003 of entity 'unicam' (Video Interface Bridge): Data, Enabled, Immutable
>
> Required ioctls:
> test MC information (see 'Media Driver Info' above): OK
> test VIDIOC_QUERYCAP: OK
> test invalid ioctls: OK
>
> Allow for multiple opens:
> test second /dev/video0 open: OK
> test VIDIOC_QUERYCAP: OK
> test VIDIOC_G/S_PRIORITY: OK
> test for unlimited opens: OK
>
> Debug ioctls:
> test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
> test VIDIOC_LOG_STATUS: OK
>
> Input ioctls:
> test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
> test VIDIOC_ENUMAUDIO: OK (Not Supported)
> test VIDIOC_G/S/ENUMINPUT: OK
> test VIDIOC_G/S_AUDIO: OK (Not Supported)
> Inputs: 1 Audio Inputs: 0 Tuners: 0
>
> Output ioctls:
> test VIDIOC_G/S_MODULATOR: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_ENUMAUDOUT: OK (Not Supported)
> test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDOUT: OK (Not Supported)
> Outputs: 0 Audio Outputs: 0 Modulators: 0
>
> Input/Output configuration ioctls:
> test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
> test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
> test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
> test VIDIOC_G/S_EDID: OK (Not Supported)
>
> Control ioctls (Input 0):
> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
> test VIDIOC_QUERYCTRL: OK (Not Supported)
> test VIDIOC_G/S_CTRL: OK (Not Supported)
> test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> Standard Controls: 0 Private Controls: 0
>
> Format ioctls (Input 0):
> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
> test VIDIOC_G/S_PARM: OK (Not Supported)
> test VIDIOC_G_FBUF: OK (Not Supported)
> test VIDIOC_G_FMT: OK
> test VIDIOC_TRY_FMT: OK
> test VIDIOC_S_FMT: OK
> test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
> test Cropping: OK (Not Supported)
> test Composing: OK (Not Supported)
> test Scaling: OK
>
> Codec ioctls (Input 0):
> test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
> test VIDIOC_G_ENC_INDEX: OK (Not Supported)
> test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
>
> Buffer ioctls (Input 0):
> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
> test CREATE_BUFS maximum buffers: OK
> test VIDIOC_REMOVE_BUFS: OK
> test VIDIOC_EXPBUF: OK
> test Requests: OK (Not Supported)
>
> Total for unicam device /dev/video0: 48, Succeeded: 48, Failed: 0, Warnings: 0
> --------------------------------------------------------------------------------
> Compliance test for unicam device /dev/video1:
>
> Driver Info:
> Driver name : unicam
> Card type : unicam
> Bus info : platform:fe801000.csi
> Driver version : 6.9.0
> Capabilities : 0xa4a00001
> Video Capture
> Metadata Capture
> I/O MC
> Streaming
> Extended Pix Format
> Device Capabilities
> Device Caps : 0x24a00000
> Metadata Capture
> I/O MC
> Streaming
> Extended Pix Format
> Media Driver Info:
> Driver name : unicam
> Model : unicam
> Serial :
> Bus info : platform:fe801000.csi
> Media version : 6.9.0
> Hardware revision: 0x00000000 (0)
> Driver version : 6.9.0
> Interface Info:
> ID : 0x03000013
> Type : V4L Video
> Entity Info:
> ID : 0x00000011 (17)
> Name : unicam-embedded
> Function : V4L2 I/O
> Pad 0x01000012 : 0: Sink
> Link 0x02000015: from remote pad 0x1000004 of entity 'unicam' (Video Interface Bridge): Data, Enabled, Immutable
>
> Required ioctls:
> test MC information (see 'Media Driver Info' above): OK
> test VIDIOC_QUERYCAP: OK
> test invalid ioctls: OK
>
> Allow for multiple opens:
> test second /dev/video1 open: OK
> test VIDIOC_QUERYCAP: OK
> test VIDIOC_G/S_PRIORITY: OK
> test for unlimited opens: OK
>
> Debug ioctls:
> test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
> test VIDIOC_LOG_STATUS: OK
>
> Input ioctls:
> test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
> test VIDIOC_ENUMAUDIO: OK (Not Supported)
> test VIDIOC_G/S/ENUMINPUT: OK
> test VIDIOC_G/S_AUDIO: OK (Not Supported)
> Inputs: 1 Audio Inputs: 0 Tuners: 0
>
> Output ioctls:
> test VIDIOC_G/S_MODULATOR: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_ENUMAUDOUT: OK (Not Supported)
> test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDOUT: OK (Not Supported)
> Outputs: 0 Audio Outputs: 0 Modulators: 0
>
> Input/Output configuration ioctls:
> test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
> test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
> test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
> test VIDIOC_G/S_EDID: OK (Not Supported)
>
> Control ioctls (Input 0):
> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
> test VIDIOC_QUERYCTRL: OK (Not Supported)
> test VIDIOC_G/S_CTRL: OK (Not Supported)
> test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> Standard Controls: 0 Private Controls: 0
>
> Format ioctls (Input 0):
> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
> test VIDIOC_G/S_PARM: OK (Not Supported)
> test VIDIOC_G_FBUF: OK (Not Supported)
> test VIDIOC_G_FMT: OK
> test VIDIOC_TRY_FMT: OK
> test VIDIOC_S_FMT: OK
> test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
> test Cropping: OK (Not Supported)
> test Composing: OK (Not Supported)
> test Scaling: OK (Not Supported)
>
> Codec ioctls (Input 0):
> test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
> test VIDIOC_G_ENC_INDEX: OK (Not Supported)
> test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
>
> Buffer ioctls (Input 0):
> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
> test CREATE_BUFS maximum buffers: OK
> test VIDIOC_REMOVE_BUFS: OK
> test VIDIOC_EXPBUF: OK
> test Requests: OK (Not Supported)
>
> Total for unicam device /dev/video1: 48, Succeeded: 48, Failed: 0, Warnings: 0
> --------------------------------------------------------------------------------
> Compliance test for unicam device /dev/v4l-subdev0:
>
> Driver Info:
> Driver version : 6.9.0
> Capabilities : 0x00000002
> Streams Support
> Client Capabilities: 0x0000000000000003
> streams interval-uses-which Media Driver Info:
> Driver name : unicam
> Model : unicam
> Serial :
> Bus info : platform:fe801000.csi
> Media version : 6.9.0
> Hardware revision: 0x00000000 (0)
> Driver version : 6.9.0
> Interface Info:
> ID : 0x03000017
> Type : V4L Sub-Device
> Entity Info:
> ID : 0x00000001 (1)
> Name : unicam
> Function : Video Interface Bridge
> Pad 0x01000002 : 0: Sink
> Link 0x02000009: from remote pad 0x1000006 of entity 'imx219 5-0010' (Camera Sensor): Data, Enabled, Immutable
> Pad 0x01000003 : 1: Source
> Link 0x0200000f: to remote pad 0x100000c of entity 'unicam-image' (V4L2 I/O): Data, Enabled, Immutable
> Pad 0x01000004 : 2: Source
> Link 0x02000015: to remote pad 0x1000012 of entity 'unicam-embedded' (V4L2 I/O): Data, Enabled, Immutable
>
> Required ioctls:
> test MC information (see 'Media Driver Info' above): OK
> test VIDIOC_SUDBEV_QUERYCAP: OK
> test invalid ioctls: OK
>
> Allow for multiple opens:
> test second /dev/v4l-subdev0 open: OK
> test VIDIOC_SUBDEV_QUERYCAP: OK
> test for unlimited opens: OK
>
> Debug ioctls:
> test VIDIOC_LOG_STATUS: OK (Not Supported)
>
> Input ioctls:
> test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
> test VIDIOC_ENUMAUDIO: OK (Not Supported)
> test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDIO: OK (Not Supported)
> Inputs: 0 Audio Inputs: 0 Tuners: 0
>
> Output ioctls:
> test VIDIOC_G/S_MODULATOR: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_ENUMAUDOUT: OK (Not Supported)
> test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDOUT: OK (Not Supported)
> Outputs: 0 Audio Outputs: 0 Modulators: 0
>
> Input/Output configuration ioctls:
> test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
> test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
> test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
> test VIDIOC_G/S_EDID: OK (Not Supported)
>
> Sub-Device routing ioctls:
> test Try VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
> test Active VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
>
> Sub-Device ioctls (Sink Pad 0):
> Try Stream 0
> test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Try VIDIOC_SUBDEV_G/S_FMT: OK
> test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
> Active Stream 0
> test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Active VIDIOC_SUBDEV_G/S_FMT: OK
> test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
> test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
>
> Sub-Device ioctls (Source Pad 1):
> Try Stream 0
> test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Try VIDIOC_SUBDEV_G/S_FMT: OK
> test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
> Active Stream 0
> test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Active VIDIOC_SUBDEV_G/S_FMT: OK
> test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
> test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
>
> Sub-Device ioctls (Source Pad 2):
>
> Control ioctls:
> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
> test VIDIOC_QUERYCTRL: OK (Not Supported)
> test VIDIOC_G/S_CTRL: OK (Not Supported)
> test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> Standard Controls: 0 Private Controls: 0
>
> Format ioctls:
> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
> test VIDIOC_G/S_PARM: OK (Not Supported)
> test VIDIOC_G_FBUF: OK (Not Supported)
> test VIDIOC_G_FMT: OK (Not Supported)
> test VIDIOC_TRY_FMT: OK (Not Supported)
> test VIDIOC_S_FMT: OK (Not Supported)
> test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
> test Cropping: OK (Not Supported)
> test Composing: OK (Not Supported)
> test Scaling: OK (Not Supported)
>
> Codec ioctls:
> test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
> test VIDIOC_G_ENC_INDEX: OK (Not Supported)
> test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
>
> Buffer ioctls:
> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
> test CREATE_BUFS maximum buffers: OK
> test VIDIOC_REMOVE_BUFS: OK
> test VIDIOC_EXPBUF: OK (Not Supported)
> test Requests: OK (Not Supported)
>
> Total for unicam device /dev/v4l-subdev0: 62, Succeeded: 62, Failed: 0, Warnings: 0
> --------------------------------------------------------------------------------
> Compliance test for unicam device /dev/v4l-subdev1:
>
> Driver Info:
> Driver version : 6.9.0
> Capabilities : 0x00000002
> Streams Support
> Client Capabilities: 0x0000000000000003
> streams interval-uses-which Media Driver Info:
> Driver name : unicam
> Model : unicam
> Serial :
> Bus info : platform:fe801000.csi
> Media version : 6.9.0
> Hardware revision: 0x00000000 (0)
> Driver version : 6.9.0
> Interface Info:
> ID : 0x03000019
> Type : V4L Sub-Device
> Entity Info:
> ID : 0x00000005 (5)
> Name : imx219 5-0010
> Function : Camera Sensor
> Pad 0x01000006 : 0: Source
> Link 0x02000009: to remote pad 0x1000002 of entity 'unicam' (Video Interface Bridge): Data, Enabled, Immutable
> Pad 0x01000007 : 1: Sink, 00000008
> Pad 0x01000008 : 2: Sink, 00000008
>
> Required ioctls:
> test MC information (see 'Media Driver Info' above): OK
> test VIDIOC_SUDBEV_QUERYCAP: OK
> test invalid ioctls: OK
>
> Allow for multiple opens:
> test second /dev/v4l-subdev1 open: OK
> test VIDIOC_SUBDEV_QUERYCAP: OK
> test for unlimited opens: OK
>
> Debug ioctls:
> test VIDIOC_LOG_STATUS: OK (Not Supported)
>
> Input ioctls:
> test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
> test VIDIOC_ENUMAUDIO: OK (Not Supported)
> test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDIO: OK (Not Supported)
> Inputs: 0 Audio Inputs: 0 Tuners: 0
>
> Output ioctls:
> test VIDIOC_G/S_MODULATOR: OK (Not Supported)
> test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
> test VIDIOC_ENUMAUDOUT: OK (Not Supported)
> test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
> test VIDIOC_G/S_AUDOUT: OK (Not Supported)
> Outputs: 0 Audio Outputs: 0 Modulators: 0
>
> Input/Output configuration ioctls:
> test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
> test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
> test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
> test VIDIOC_G/S_EDID: OK (Not Supported)
>
> Sub-Device routing ioctls:
> test Try VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
> test Active VIDIOC_SUBDEV_G_ROUTING/VIDIOC_SUBDEV_S_ROUTING: OK
>
> Sub-Device ioctls (Source Pad 0):
> Try Stream 0
> test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Try VIDIOC_SUBDEV_G/S_FMT: OK
> warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
> test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
> Try Stream 1
> fail: v4l2-test-subdevs.cpp(162): doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse)
> fail: v4l2-test-subdevs.cpp(270): ret && ret != ENOTTY
> test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: FAIL
> test Try VIDIOC_SUBDEV_G/S_FMT: OK
> test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
> Active Stream 0
> test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Active VIDIOC_SUBDEV_G/S_FMT: OK
> warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
> test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
> test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
> Active Stream 1
> fail: v4l2-test-subdevs.cpp(162): doioctl(node, VIDIOC_SUBDEV_ENUM_FRAME_SIZE, &fse)
> fail: v4l2-test-subdevs.cpp(270): ret && ret != ENOTTY
> test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: FAIL
> test Active VIDIOC_SUBDEV_G/S_FMT: OK
> test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
> test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
>
> Sub-Device ioctls (Sink Pad 1):
> Try Stream 0
> test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Try VIDIOC_SUBDEV_G/S_FMT: OK
> warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
> test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
> Active Stream 0
> test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Active VIDIOC_SUBDEV_G/S_FMT: OK
> warn: v4l2-test-subdevs.cpp(566): VIDIOC_SUBDEV_G_SELECTION is supported for target 0 but not VIDIOC_SUBDEV_S_SELECTION
> test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK
> test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
>
> Sub-Device ioctls (Sink Pad 2):
> Try Stream 0
> test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Try VIDIOC_SUBDEV_G/S_FMT: OK
> test Try VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
> Active Stream 0
> test Active VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: OK
> test Active VIDIOC_SUBDEV_G/S_FMT: OK
> test Active VIDIOC_SUBDEV_G/S_SELECTION/CROP: OK (Not Supported)
> test Active VIDIOC_SUBDEV_G/S_FRAME_INTERVAL: OK (Not Supported)
>
> Control ioctls:
> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
> test VIDIOC_QUERYCTRL: OK
> test VIDIOC_G/S_CTRL: OK
> test VIDIOC_G/S/TRY_EXT_CTRLS: OK
> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> Standard Controls: 20 Private Controls: 0
>
> Format ioctls:
> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
> test VIDIOC_G/S_PARM: OK (Not Supported)
> test VIDIOC_G_FBUF: OK (Not Supported)
> test VIDIOC_G_FMT: OK (Not Supported)
> test VIDIOC_TRY_FMT: OK (Not Supported)
> test VIDIOC_S_FMT: OK (Not Supported)
> test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
> test Cropping: OK (Not Supported)
> test Composing: OK (Not Supported)
> test Scaling: OK (Not Supported)
>
> Codec ioctls:
> test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
> test VIDIOC_G_ENC_INDEX: OK (Not Supported)
> test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
>
> Buffer ioctls:
> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
> test CREATE_BUFS maximum buffers: OK
> test VIDIOC_REMOVE_BUFS: OK
> test VIDIOC_EXPBUF: OK (Not Supported)
> test Requests: OK (Not Supported)
>
> Total for unicam device /dev/v4l-subdev1: 76, Succeeded: 74, Failed: 2, Warnings: 4
>
> Grand Total for unicam device /dev/media0: 242, Succeeded: 240, Failed: 2, Warnings: 4
>
>
> [1] https://github.com/raspberrypi/linux/tree/rpi-6.1.y/drivers/media/platform/bcm2835
> [2] https://lore.kernel.org/linux-media/20240313072516.241106-1-sakari.ailus@linux.intel.com/
> [3] https://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git/log/?h=rpi/v6.9/metadata/v8
> [4] https://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git/log/?h=rpi/v6.9/isp/v2
> [5] https://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git/log/?h=rpi/v6.9/unicam/next
> [6] https://lists.libcamera.org/pipermail/libcamera-devel/2024-March/040711.html
> [7] https://lore.kernel.org/linux-media/20240402000033.4007-1-laurent.pinchart@ideasonboard.com/
> [8] https://git.linuxtv.org/pinchartl/v4l-utils.git/log/?h=metadata
>
> Dave Stevenson (2):
> dt-bindings: media: Add bindings for bcm2835-unicam
> media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface
>
> Jean-Michel Hautbois (3):
> media: v4l: Add V4L2-PIX-FMT-Y12P format
> media: v4l: Add V4L2-PIX-FMT-Y14P format
> ARM: dts: bcm2835: Add Unicam CSI nodes
>
> Laurent Pinchart (3):
> ARM: dts: bcm2835-rpi: Move duplicate firmware-clocks to
> bcm2835-rpi.dtsi
> ARM: dts: bcm2711-rpi-4-b: Add CAM1 regulator
> [DNI] arm64: dts: broadcom: Add overlay for Raspberry Pi 4B IMX219
> camera
>
> Uwe Kleine-König (2):
> ARM: dts: bcm2711-rpi: Add pinctrl-based multiplexing for I2C0
> ARM: dts: bcm2711-rpi-cm4-io: Add RTC on I2C0
>
> .../bindings/media/brcm,bcm2835-unicam.yaml | 127 +
> .../media/v4l/pixfmt-yuv-luma.rst | 48 +
> MAINTAINERS | 7 +
> .../arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts | 7 +
> .../boot/dts/broadcom/bcm2711-rpi-cm4-io.dts | 9 +
> arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi | 34 +-
> arch/arm/boot/dts/broadcom/bcm2711.dtsi | 8 +
> .../boot/dts/broadcom/bcm2835-rpi-common.dtsi | 7 -
> arch/arm/boot/dts/broadcom/bcm2835-rpi.dtsi | 19 +
> arch/arm/boot/dts/broadcom/bcm283x.dtsi | 24 +
> arch/arm64/boot/dts/broadcom/Makefile | 4 +
> .../dts/broadcom/bcm2711-rpi-4-b-imx219.dtso | 65 +
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 1 +
> drivers/media/platform/broadcom/Kconfig | 23 +
> drivers/media/platform/broadcom/Makefile | 3 +
> .../platform/broadcom/bcm2835-unicam-regs.h | 246 ++
> .../media/platform/broadcom/bcm2835-unicam.c | 2745 +++++++++++++++++
> drivers/media/v4l2-core/v4l2-ioctl.c | 2 +
> include/uapi/linux/videodev2.h | 2 +
> 20 files changed, 3370 insertions(+), 12 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
> create mode 100644 arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b-imx219.dtso
> create mode 100644 drivers/media/platform/broadcom/Kconfig
> create mode 100644 drivers/media/platform/broadcom/Makefile
> create mode 100644 drivers/media/platform/broadcom/bcm2835-unicam-regs.h
> create mode 100644 drivers/media/platform/broadcom/bcm2835-unicam.c
>
>
> base-commit: 58abf3672a73558149fa567eafff8d5b1cc0446b
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH v7 2/2] dmaengine: Loongson1: Add Loongson-1 APB DMA driver
From: Keguang Zhang @ 2024-04-02 1:55 UTC (permalink / raw)
To: Huacai Chen
Cc: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-mips, dmaengine, devicetree, linux-kernel
In-Reply-To: <CAAhV-H4TH+DbC2XsvysS7yH+M99qhHdpADACGdM0Q83FztSvFg@mail.gmail.com>
On Mon, Apr 1, 2024 at 9:24 PM Huacai Chen <chenhuacai@kernel.org> wrote:
>
> On Mon, Apr 1, 2024 at 7:10 PM Keguang Zhang <keguang.zhang@gmail.com> wrote:
> >
> > On Mon, Apr 1, 2024 at 5:06 PM Huacai Chen <chenhuacai@kernel.org> wrote:
> > >
> > > On Mon, Apr 1, 2024 at 10:45 AM Keguang Zhang <keguang.zhang@gmail.com> wrote:
> > > >
> > > > Hi Huacai,
> > > >
> > > > On Sat, Mar 30, 2024 at 9:59 PM Huacai Chen <chenhuacai@kernel.org> wrote:
> > > > >
> > > > > Hi, Keguang,
> > > > >
> > > > > On Fri, Mar 29, 2024 at 7:28 PM Keguang Zhang via B4 Relay
> > > > > <devnull+keguang.zhang.gmail.com@kernel.org> wrote:
> > > > > >
> > > > > > From: Keguang Zhang <keguang.zhang@gmail.com>
> > > > > >
> > > > > > This patch adds APB DMA driver for Loongson-1 SoCs.
> > > > > >
> > > > > > Signed-off-by: Keguang Zhang <keguang.zhang@gmail.com>
> > > > > > ---
> > > > > > Changes in v7:
> > > > > > - Change the comptible to 'loongson,ls1*-apbdma'
> > > > > > - Update Kconfig and Makefile accordingly
> > > > > > - Rename the file to loongson1-apb-dma.c to keep the consistency
> > > > > >
> > > > > > Changes in v6:
> > > > > > - Implement .device_prep_dma_cyclic for Loongson1 audio driver,
> > > > > > - as well as .device_pause and .device_resume.
> > > > > > - Set the limitation LS1X_DMA_MAX_DESC and put all descriptors
> > > > > > - into one page to save memory
> > > > > > - Move dma_pool_zalloc() into ls1x_dma_alloc_desc()
> > > > > > - Drop dma_slave_config structure
> > > > > > - Use .remove_new instead of .remove
> > > > > > - Use KBUILD_MODNAME for the driver name
> > > > > > - Improve the debug information
> > > > > >
> > > > > > Changes in v5:
> > > > > > - Add DT support
> > > > > > - Use DT data instead of platform data
> > > > > > - Use chan_id of struct dma_chan instead of own id
> > > > > > - Use of_dma_xlate_by_chan_id() instead of ls1x_dma_filter()
> > > > > > - Update the author information to my official name
> > > > > >
> > > > > > Changes in v4:
> > > > > > - Use dma_slave_map to find the proper channel.
> > > > > > - Explicitly call devm_request_irq() and tasklet_kill().
> > > > > > - Fix namespace issue.
> > > > > > - Some minor fixes and cleanups.
> > > > > >
> > > > > > Changes in v3:
> > > > > > - Rename ls1x_dma_filter_fn to ls1x_dma_filter.
> > > > > >
> > > > > > Changes in v2:
> > > > > > - Change the config from 'DMA_LOONGSON1' to 'LOONGSON1_DMA',
> > > > > > - and rearrange it in alphabetical order in Kconfig and Makefile.
> > > > > > - Fix comment style.
> > > > > > ---
> > > > > > drivers/dma/Kconfig | 9 +
> > > > > > drivers/dma/Makefile | 1 +
> > > > > > drivers/dma/loongson1-apb-dma.c | 665 ++++++++++++++++++++++++++++++++++++++++
> > > > > > 3 files changed, 675 insertions(+)
> > > > > >
> > > > > > diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> > > > > > index 002a5ec80620..f7b06c4cdf3f 100644
> > > > > > --- a/drivers/dma/Kconfig
> > > > > > +++ b/drivers/dma/Kconfig
> > > > > > @@ -369,6 +369,15 @@ config K3_DMA
> > > > > > Support the DMA engine for Hisilicon K3 platform
> > > > > > devices.
> > > > > >
> > > > > > +config LOONGSON1_APB_DMA
> > > > > > + tristate "Loongson1 APB DMA support"
> > > > > > + depends on MACH_LOONGSON32 || COMPILE_TEST
> > > > > > + select DMA_ENGINE
> > > > > > + select DMA_VIRTUAL_CHANNELS
> > > > > > + help
> > > > > > + This selects support for the APB DMA controller in Loongson1 SoCs,
> > > > > > + which is required by Loongson1 NAND and audio support.
> > > > > Why not rename to LS1X_APB_DMA and put it just before LS2X_APB_DMA
> > > > > (and also the driver file name)?
> > > > >
> > > > So far all Kconfig entries of Loongson-1 drivers are named with the
> > > > keyword "LOONGSON1".
> > > > The same is true for these file names.
> > > > Therefore, I need to keep the consistency.
> > > But I see LS1X_IRQ in drivers/irqchip/Kconfig
> > >
> > Indeed, that's an exception, which was submitted by Jiaxun several years ago.
> > Actually, most drivers of Loongson family use the keyword "LOONGSON"
> > for Kconfig and "loongson" for filename.
> > Thus I take this keywork as the naming convention.
> But I think keeping consistency in a same subsystem is better than
> keeping consistency in a same SoC (but cross subsystems).
>
In my opinion, "LS*X" is too short and may be confused with other SoCs.
Meanwhile, there are only four drivers that use this keyword.
config I2C_LS2X
config LS2K_RESET
config LS2X_APB_DMA
config LS1X_IRQ
Then, my suggestion is to change these "LS*X" to "LOONGSON*" to get a
clear meaning.
> Huacai
>
> >
> > > Huacai
> > >
> > > >
> > > >
> > > > > Huacai
> > > > >
> > > > > > +
> > > > > > config LPC18XX_DMAMUX
> > > > > > bool "NXP LPC18xx/43xx DMA MUX for PL080"
> > > > > > depends on ARCH_LPC18XX || COMPILE_TEST
> > > > > > diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> > > > > > index dfd40d14e408..b26f6677978a 100644
> > > > > > --- a/drivers/dma/Makefile
> > > > > > +++ b/drivers/dma/Makefile
> > > > > > @@ -47,6 +47,7 @@ obj-$(CONFIG_INTEL_IDMA64) += idma64.o
> > > > > > obj-$(CONFIG_INTEL_IOATDMA) += ioat/
> > > > > > obj-y += idxd/
> > > > > > obj-$(CONFIG_K3_DMA) += k3dma.o
> > > > > > +obj-$(CONFIG_LOONGSON1_APB_DMA) += loongson1-apb-dma.o
> > > > > > obj-$(CONFIG_LPC18XX_DMAMUX) += lpc18xx-dmamux.o
> > > > > > obj-$(CONFIG_LS2X_APB_DMA) += ls2x-apb-dma.o
> > > > > > obj-$(CONFIG_MILBEAUT_HDMAC) += milbeaut-hdmac.o
> > > > > > diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson1-apb-dma.c
> > > > > > new file mode 100644
> > > > > > index 000000000000..d474a2601e6e
> > > > > > --- /dev/null
> > > > > > +++ b/drivers/dma/loongson1-apb-dma.c
> > > > > > @@ -0,0 +1,665 @@
> > > > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > > > +/*
> > > > > > + * Driver for Loongson-1 APB DMA Controller
> > > > > > + *
> > > > > > + * Copyright (C) 2015-2024 Keguang Zhang <keguang.zhang@gmail.com>
> > > > > > + */
> > > > > > +
> > > > > > +#include <linux/dmapool.h>
> > > > > > +#include <linux/dma-mapping.h>
> > > > > > +#include <linux/init.h>
> > > > > > +#include <linux/interrupt.h>
> > > > > > +#include <linux/iopoll.h>
> > > > > > +#include <linux/module.h>
> > > > > > +#include <linux/of.h>
> > > > > > +#include <linux/of_dma.h>
> > > > > > +#include <linux/platform_device.h>
> > > > > > +#include <linux/slab.h>
> > > > > > +
> > > > > > +#include "dmaengine.h"
> > > > > > +#include "virt-dma.h"
> > > > > > +
> > > > > > +/* Loongson-1 DMA Control Register */
> > > > > > +#define DMA_CTRL 0x0
> > > > > > +
> > > > > > +/* DMA Control Register Bits */
> > > > > > +#define DMA_STOP BIT(4)
> > > > > > +#define DMA_START BIT(3)
> > > > > > +#define DMA_ASK_VALID BIT(2)
> > > > > > +
> > > > > > +#define DMA_ADDR_MASK GENMASK(31, 6)
> > > > > > +
> > > > > > +/* DMA Next Field Bits */
> > > > > > +#define DMA_NEXT_VALID BIT(0)
> > > > > > +
> > > > > > +/* DMA Command Field Bits */
> > > > > > +#define DMA_RAM2DEV BIT(12)
> > > > > > +#define DMA_INT BIT(1)
> > > > > > +#define DMA_INT_MASK BIT(0)
> > > > > > +
> > > > > > +#define LS1X_DMA_MAX_CHANNELS 3
> > > > > > +
> > > > > > +/* Size of allocations for hardware descriptors */
> > > > > > +#define LS1X_DMA_DESCS_SIZE PAGE_SIZE
> > > > > > +#define LS1X_DMA_MAX_DESC \
> > > > > > + (LS1X_DMA_DESCS_SIZE / sizeof(struct ls1x_dma_hwdesc))
> > > > > > +
> > > > > > +struct ls1x_dma_hwdesc {
> > > > > > + u32 next; /* next descriptor address */
> > > > > > + u32 saddr; /* memory DMA address */
> > > > > > + u32 daddr; /* device DMA address */
> > > > > > + u32 length;
> > > > > > + u32 stride;
> > > > > > + u32 cycles;
> > > > > > + u32 cmd;
> > > > > > + u32 stats;
> > > > > > +};
> > > > > > +
> > > > > > +struct ls1x_dma_desc {
> > > > > > + struct virt_dma_desc vdesc;
> > > > > > + enum dma_transfer_direction dir;
> > > > > > + enum dma_transaction_type type;
> > > > > > + unsigned int bus_width;
> > > > > > +
> > > > > > + unsigned int nr_descs; /* number of descriptors */
> > > > > > +
> > > > > > + struct ls1x_dma_hwdesc *hwdesc;
> > > > > > + dma_addr_t hwdesc_phys;
> > > > > > +};
> > > > > > +
> > > > > > +struct ls1x_dma_chan {
> > > > > > + struct virt_dma_chan vchan;
> > > > > > + struct dma_pool *desc_pool;
> > > > > > + phys_addr_t src_addr;
> > > > > > + phys_addr_t dst_addr;
> > > > > > + enum dma_slave_buswidth src_addr_width;
> > > > > > + enum dma_slave_buswidth dst_addr_width;
> > > > > > +
> > > > > > + void __iomem *reg_base;
> > > > > > + int irq;
> > > > > > +
> > > > > > + struct ls1x_dma_desc *desc;
> > > > > > +
> > > > > > + struct ls1x_dma_hwdesc *curr_hwdesc;
> > > > > > + dma_addr_t curr_hwdesc_phys;
> > > > > > +};
> > > > > > +
> > > > > > +struct ls1x_dma {
> > > > > > + struct dma_device ddev;
> > > > > > + void __iomem *reg_base;
> > > > > > +
> > > > > > + unsigned int nr_chans;
> > > > > > + struct ls1x_dma_chan chan[];
> > > > > > +};
> > > > > > +
> > > > > > +#define to_ls1x_dma_chan(dchan) \
> > > > > > + container_of(dchan, struct ls1x_dma_chan, vchan.chan)
> > > > > > +
> > > > > > +#define to_ls1x_dma_desc(vd) \
> > > > > > + container_of(vd, struct ls1x_dma_desc, vdesc)
> > > > > > +
> > > > > > +/* macros for registers read/write */
> > > > > > +#define chan_readl(chan, off) \
> > > > > > + readl((chan)->reg_base + (off))
> > > > > > +
> > > > > > +#define chan_writel(chan, off, val) \
> > > > > > + writel((val), (chan)->reg_base + (off))
> > > > > > +
> > > > > > +static inline struct device *chan2dev(struct dma_chan *chan)
> > > > > > +{
> > > > > > + return &chan->dev->device;
> > > > > > +}
> > > > > > +
> > > > > > +static inline int ls1x_dma_query(struct ls1x_dma_chan *chan,
> > > > > > + dma_addr_t *hwdesc_phys)
> > > > > > +{
> > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > + int val, ret;
> > > > > > +
> > > > > > + val = *hwdesc_phys & DMA_ADDR_MASK;
> > > > > > + val |= DMA_ASK_VALID;
> > > > > > + val |= dchan->chan_id;
> > > > > > + chan_writel(chan, DMA_CTRL, val);
> > > > > > + ret = readl_poll_timeout_atomic(chan->reg_base + DMA_CTRL, val,
> > > > > > + !(val & DMA_ASK_VALID), 0, 3000);
> > > > > > + if (ret)
> > > > > > + dev_err(chan2dev(dchan), "failed to query DMA\n");
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static inline int ls1x_dma_start(struct ls1x_dma_chan *chan,
> > > > > > + dma_addr_t *hwdesc_phys)
> > > > > > +{
> > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > + int val, ret;
> > > > > > +
> > > > > > + dev_dbg(chan2dev(dchan), "cookie=%d, starting hwdesc=%x\n",
> > > > > > + dchan->cookie, *hwdesc_phys);
> > > > > > +
> > > > > > + val = *hwdesc_phys & DMA_ADDR_MASK;
> > > > > > + val |= DMA_START;
> > > > > > + val |= dchan->chan_id;
> > > > > > + chan_writel(chan, DMA_CTRL, val);
> > > > > > + ret = readl_poll_timeout(chan->reg_base + DMA_CTRL, val,
> > > > > > + !(val & DMA_START), 0, 3000);
> > > > > > + if (ret)
> > > > > > + dev_err(chan2dev(dchan), "failed to start DMA\n");
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static inline void ls1x_dma_stop(struct ls1x_dma_chan *chan)
> > > > > > +{
> > > > > > + chan_writel(chan, DMA_CTRL, chan_readl(chan, DMA_CTRL) | DMA_STOP);
> > > > > > +}
> > > > > > +
> > > > > > +static void ls1x_dma_free_chan_resources(struct dma_chan *dchan)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > +
> > > > > > + dma_free_coherent(chan2dev(dchan), sizeof(struct ls1x_dma_hwdesc),
> > > > > > + chan->curr_hwdesc, chan->curr_hwdesc_phys);
> > > > > > + vchan_free_chan_resources(&chan->vchan);
> > > > > > + dma_pool_destroy(chan->desc_pool);
> > > > > > + chan->desc_pool = NULL;
> > > > > > +}
> > > > > > +
> > > > > > +static int ls1x_dma_alloc_chan_resources(struct dma_chan *dchan)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > +
> > > > > > + chan->desc_pool = dma_pool_create(dma_chan_name(dchan),
> > > > > > + chan2dev(dchan),
> > > > > > + sizeof(struct ls1x_dma_hwdesc),
> > > > > > + __alignof__(struct ls1x_dma_hwdesc),
> > > > > > + 0);
> > > > > > + if (!chan->desc_pool)
> > > > > > + return -ENOMEM;
> > > > > > +
> > > > > > + /* allocate memory for querying current HW descriptor */
> > > > > > + dma_set_coherent_mask(chan2dev(dchan), DMA_BIT_MASK(32));
> > > > > > + chan->curr_hwdesc = dma_alloc_coherent(chan2dev(dchan),
> > > > > > + sizeof(struct ls1x_dma_hwdesc),
> > > > > > + &chan->curr_hwdesc_phys,
> > > > > > + GFP_KERNEL);
> > > > > > + if (!chan->curr_hwdesc)
> > > > > > + return -ENOMEM;
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static void ls1x_dma_free_desc(struct virt_dma_desc *vdesc)
> > > > > > +{
> > > > > > + struct ls1x_dma_desc *desc = to_ls1x_dma_desc(vdesc);
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(vdesc->tx.chan);
> > > > > > +
> > > > > > + dma_pool_free(chan->desc_pool, desc->hwdesc, desc->hwdesc_phys);
> > > > > > + chan->desc = NULL;
> > > > > > + kfree(desc);
> > > > > > +}
> > > > > > +
> > > > > > +static struct ls1x_dma_desc *
> > > > > > +ls1x_dma_alloc_desc(struct dma_chan *dchan, int sg_len,
> > > > > > + enum dma_transfer_direction direction,
> > > > > > + enum dma_transaction_type type)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + struct ls1x_dma_desc *desc;
> > > > > > +
> > > > > > + if (sg_len > LS1X_DMA_MAX_DESC) {
> > > > > > + dev_err(chan2dev(dchan), "sg_len %u exceeds limit %lu",
> > > > > > + sg_len, LS1X_DMA_MAX_DESC);
> > > > > > + return NULL;
> > > > > > + }
> > > > > > +
> > > > > > + desc = kzalloc(sizeof(*desc), GFP_NOWAIT);
> > > > > > + if (!desc)
> > > > > > + return NULL;
> > > > > > +
> > > > > > + /* allocate HW descriptors */
> > > > > > + desc->hwdesc = dma_pool_zalloc(chan->desc_pool, GFP_NOWAIT,
> > > > > > + &desc->hwdesc_phys);
> > > > > > + if (!desc->hwdesc) {
> > > > > > + dev_err(chan2dev(dchan), "failed to alloc HW descriptors\n");
> > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > + return NULL;
> > > > > > + }
> > > > > > +
> > > > > > + desc->dir = direction;
> > > > > > + desc->type = type;
> > > > > > + desc->nr_descs = sg_len;
> > > > > > +
> > > > > > + return desc;
> > > > > > +}
> > > > > > +
> > > > > > +static int ls1x_dma_setup_hwdescs(struct dma_chan *dchan,
> > > > > > + struct ls1x_dma_desc *desc,
> > > > > > + struct scatterlist *sgl, unsigned int sg_len)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + dma_addr_t next_hwdesc_phys = desc->hwdesc_phys;
> > > > > > +
> > > > > > + struct scatterlist *sg;
> > > > > > + unsigned int dev_addr, cmd, i;
> > > > > > +
> > > > > > + switch (desc->dir) {
> > > > > > + case DMA_MEM_TO_DEV:
> > > > > > + dev_addr = chan->dst_addr;
> > > > > > + desc->bus_width = chan->dst_addr_width;
> > > > > > + cmd = DMA_RAM2DEV | DMA_INT;
> > > > > > + break;
> > > > > > + case DMA_DEV_TO_MEM:
> > > > > > + dev_addr = chan->src_addr;
> > > > > > + desc->bus_width = chan->src_addr_width;
> > > > > > + cmd = DMA_INT;
> > > > > > + break;
> > > > > > + default:
> > > > > > + dev_err(chan2dev(dchan), "unsupported DMA direction: %s\n",
> > > > > > + dmaengine_get_direction_text(desc->dir));
> > > > > > + return -EINVAL;
> > > > > > + }
> > > > > > +
> > > > > > + /* setup HW descriptors */
> > > > > > + for_each_sg(sgl, sg, sg_len, i) {
> > > > > > + dma_addr_t buf_addr = sg_dma_address(sg);
> > > > > > + size_t buf_len = sg_dma_len(sg);
> > > > > > + struct ls1x_dma_hwdesc *hwdesc = &desc->hwdesc[i];
> > > > > > +
> > > > > > + if (!is_dma_copy_aligned(dchan->device, buf_addr, 0, buf_len)) {
> > > > > > + dev_err(chan2dev(dchan), "buffer is not aligned!\n");
> > > > > > + return -EINVAL;
> > > > > > + }
> > > > > > +
> > > > > > + hwdesc->saddr = buf_addr;
> > > > > > + hwdesc->daddr = dev_addr;
> > > > > > + hwdesc->length = buf_len / desc->bus_width;
> > > > > > + hwdesc->stride = 0;
> > > > > > + hwdesc->cycles = 1;
> > > > > > + hwdesc->cmd = cmd;
> > > > > > +
> > > > > > + if (i) {
> > > > > > + next_hwdesc_phys += sizeof(*hwdesc);
> > > > > > + desc->hwdesc[i - 1].next = next_hwdesc_phys
> > > > > > + | DMA_NEXT_VALID;
> > > > > > + }
> > > > > > + }
> > > > > > +
> > > > > > + if (desc->type == DMA_CYCLIC)
> > > > > > + desc->hwdesc[i - 1].next = desc->hwdesc_phys | DMA_NEXT_VALID;
> > > > > > +
> > > > > > + for_each_sg(sgl, sg, sg_len, i) {
> > > > > > + struct ls1x_dma_hwdesc *hwdesc = &desc->hwdesc[i];
> > > > > > +
> > > > > > + print_hex_dump_debug("HW DESC: ", DUMP_PREFIX_OFFSET, 16, 4,
> > > > > > + hwdesc, sizeof(*hwdesc), false);
> > > > > > + }
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static struct dma_async_tx_descriptor *
> > > > > > +ls1x_dma_prep_slave_sg(struct dma_chan *dchan,
> > > > > > + struct scatterlist *sgl, unsigned int sg_len,
> > > > > > + enum dma_transfer_direction direction,
> > > > > > + unsigned long flags, void *context)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + struct ls1x_dma_desc *desc;
> > > > > > +
> > > > > > + dev_dbg(chan2dev(dchan), "sg_len=%u flags=0x%lx dir=%s\n",
> > > > > > + sg_len, flags, dmaengine_get_direction_text(direction));
> > > > > > +
> > > > > > + desc = ls1x_dma_alloc_desc(dchan, sg_len, direction, DMA_SLAVE);
> > > > > > + if (!desc)
> > > > > > + return NULL;
> > > > > > +
> > > > > > + if (ls1x_dma_setup_hwdescs(dchan, desc, sgl, sg_len)) {
> > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > + return NULL;
> > > > > > + }
> > > > > > +
> > > > > > + return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
> > > > > > +}
> > > > > > +
> > > > > > +static struct dma_async_tx_descriptor *
> > > > > > +ls1x_dma_prep_dma_cyclic(struct dma_chan *dchan,
> > > > > > + dma_addr_t buf_addr, size_t buf_len, size_t period_len,
> > > > > > + enum dma_transfer_direction direction,
> > > > > > + unsigned long flags)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + struct ls1x_dma_desc *desc;
> > > > > > + struct scatterlist *sgl;
> > > > > > + unsigned int sg_len;
> > > > > > + unsigned int i;
> > > > > > +
> > > > > > + dev_dbg(chan2dev(dchan),
> > > > > > + "buf_len=%d period_len=%zu flags=0x%lx dir=%s\n", buf_len,
> > > > > > + period_len, flags, dmaengine_get_direction_text(direction));
> > > > > > +
> > > > > > + sg_len = buf_len / period_len;
> > > > > > + desc = ls1x_dma_alloc_desc(dchan, sg_len, direction, DMA_CYCLIC);
> > > > > > + if (!desc)
> > > > > > + return NULL;
> > > > > > +
> > > > > > + /* allocate the scatterlist */
> > > > > > + sgl = kmalloc_array(sg_len, sizeof(*sgl), GFP_NOWAIT);
> > > > > > + if (!sgl)
> > > > > > + return NULL;
> > > > > > +
> > > > > > + sg_init_table(sgl, sg_len);
> > > > > > + for (i = 0; i < sg_len; ++i) {
> > > > > > + sg_set_page(&sgl[i], pfn_to_page(PFN_DOWN(buf_addr)),
> > > > > > + period_len, offset_in_page(buf_addr));
> > > > > > + sg_dma_address(&sgl[i]) = buf_addr;
> > > > > > + sg_dma_len(&sgl[i]) = period_len;
> > > > > > + buf_addr += period_len;
> > > > > > + }
> > > > > > +
> > > > > > + if (ls1x_dma_setup_hwdescs(dchan, desc, sgl, sg_len)) {
> > > > > > + ls1x_dma_free_desc(&desc->vdesc);
> > > > > > + return NULL;
> > > > > > + }
> > > > > > +
> > > > > > + kfree(sgl);
> > > > > > +
> > > > > > + return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
> > > > > > +}
> > > > > > +
> > > > > > +static int ls1x_dma_slave_config(struct dma_chan *dchan,
> > > > > > + struct dma_slave_config *config)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > +
> > > > > > + chan->src_addr = config->src_addr;
> > > > > > + chan->src_addr_width = config->src_addr_width;
> > > > > > + chan->dst_addr = config->dst_addr;
> > > > > > + chan->dst_addr_width = config->dst_addr_width;
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static int ls1x_dma_pause(struct dma_chan *dchan)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + unsigned long flags;
> > > > > > + int ret;
> > > > > > +
> > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > + ret = ls1x_dma_query(chan, &chan->curr_hwdesc_phys);
> > > > > > + if (!ret)
> > > > > > + ls1x_dma_stop(chan);
> > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static int ls1x_dma_resume(struct dma_chan *dchan)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + unsigned long flags;
> > > > > > + int ret;
> > > > > > +
> > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > + ret = ls1x_dma_start(chan, &chan->curr_hwdesc_phys);
> > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static int ls1x_dma_terminate_all(struct dma_chan *dchan)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + unsigned long flags;
> > > > > > + LIST_HEAD(head);
> > > > > > +
> > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > + ls1x_dma_stop(chan);
> > > > > > + vchan_get_all_descriptors(&chan->vchan, &head);
> > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > +
> > > > > > + vchan_dma_desc_free_list(&chan->vchan, &head);
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static enum dma_status ls1x_dma_tx_status(struct dma_chan *dchan,
> > > > > > + dma_cookie_t cookie,
> > > > > > + struct dma_tx_state *state)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + struct virt_dma_desc *vdesc;
> > > > > > + enum dma_status status;
> > > > > > + size_t bytes = 0;
> > > > > > + unsigned long flags;
> > > > > > +
> > > > > > + status = dma_cookie_status(dchan, cookie, state);
> > > > > > + if (status == DMA_COMPLETE)
> > > > > > + return status;
> > > > > > +
> > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > + vdesc = vchan_find_desc(&chan->vchan, cookie);
> > > > > > + if (chan->desc && cookie == chan->desc->vdesc.tx.cookie) {
> > > > > > + struct ls1x_dma_desc *desc = chan->desc;
> > > > > > + int i;
> > > > > > +
> > > > > > + if (ls1x_dma_query(chan, &chan->curr_hwdesc_phys))
> > > > > > + return status;
> > > > > > +
> > > > > > + /* locate the current HW descriptor */
> > > > > > + for (i = 0; i < desc->nr_descs; i++)
> > > > > > + if (desc->hwdesc[i].next == chan->curr_hwdesc->next)
> > > > > > + break;
> > > > > > +
> > > > > > + /* count the residues */
> > > > > > + for (; i < desc->nr_descs; i++)
> > > > > > + bytes += desc->hwdesc[i].length * desc->bus_width;
> > > > > > +
> > > > > > + dma_set_residue(state, bytes);
> > > > > > + }
> > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > +
> > > > > > + return status;
> > > > > > +}
> > > > > > +
> > > > > > +static void ls1x_dma_issue_pending(struct dma_chan *dchan)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = to_ls1x_dma_chan(dchan);
> > > > > > + struct virt_dma_desc *vdesc;
> > > > > > + unsigned long flags;
> > > > > > +
> > > > > > + spin_lock_irqsave(&chan->vchan.lock, flags);
> > > > > > + if (vchan_issue_pending(&chan->vchan) && !chan->desc) {
> > > > > > + vdesc = vchan_next_desc(&chan->vchan);
> > > > > > + if (!vdesc) {
> > > > > > + chan->desc = NULL;
> > > > > > + return;
> > > > > > + }
> > > > > > + chan->desc = to_ls1x_dma_desc(vdesc);
> > > > > > + ls1x_dma_start(chan, &chan->desc->hwdesc_phys);
> > > > > > + }
> > > > > > + spin_unlock_irqrestore(&chan->vchan.lock, flags);
> > > > > > +}
> > > > > > +
> > > > > > +static irqreturn_t ls1x_dma_irq_handler(int irq, void *data)
> > > > > > +{
> > > > > > + struct ls1x_dma_chan *chan = data;
> > > > > > + struct ls1x_dma_desc *desc = chan->desc;
> > > > > > + struct dma_chan *dchan = &chan->vchan.chan;
> > > > > > +
> > > > > > + if (!desc) {
> > > > > > + dev_warn(chan2dev(dchan),
> > > > > > + "IRQ %d with no active descriptor on channel %d\n",
> > > > > > + irq, dchan->chan_id);
> > > > > > + return IRQ_NONE;
> > > > > > + }
> > > > > > +
> > > > > > + dev_dbg(chan2dev(dchan), "DMA IRQ %d on channel %d\n", irq,
> > > > > > + dchan->chan_id);
> > > > > > +
> > > > > > + spin_lock(&chan->vchan.lock);
> > > > > > +
> > > > > > + if (desc->type == DMA_CYCLIC) {
> > > > > > + vchan_cyclic_callback(&desc->vdesc);
> > > > > > + } else {
> > > > > > + list_del(&desc->vdesc.node);
> > > > > > + vchan_cookie_complete(&desc->vdesc);
> > > > > > + chan->desc = NULL;
> > > > > > + }
> > > > > > +
> > > > > > + spin_unlock(&chan->vchan.lock);
> > > > > > + return IRQ_HANDLED;
> > > > > > +}
> > > > > > +
> > > > > > +static int ls1x_dma_chan_probe(struct platform_device *pdev,
> > > > > > + struct ls1x_dma *dma, int chan_id)
> > > > > > +{
> > > > > > + struct device *dev = &pdev->dev;
> > > > > > + struct ls1x_dma_chan *chan = &dma->chan[chan_id];
> > > > > > + char pdev_irqname[4];
> > > > > > + char *irqname;
> > > > > > + int ret;
> > > > > > +
> > > > > > + sprintf(pdev_irqname, "ch%u", chan_id);
> > > > > > + chan->irq = platform_get_irq_byname(pdev, pdev_irqname);
> > > > > > + if (chan->irq < 0)
> > > > > > + return -ENODEV;
> > > > > > +
> > > > > > + irqname = devm_kasprintf(dev, GFP_KERNEL, "%s:%s",
> > > > > > + dev_name(dev), pdev_irqname);
> > > > > > + if (!irqname)
> > > > > > + return -ENOMEM;
> > > > > > +
> > > > > > + ret = devm_request_irq(dev, chan->irq, ls1x_dma_irq_handler,
> > > > > > + IRQF_SHARED, irqname, chan);
> > > > > > + if (ret)
> > > > > > + return dev_err_probe(dev, ret,
> > > > > > + "failed to request IRQ %u!\n", chan->irq);
> > > > > > +
> > > > > > + chan->reg_base = dma->reg_base;
> > > > > > + chan->vchan.desc_free = ls1x_dma_free_desc;
> > > > > > + vchan_init(&chan->vchan, &dma->ddev);
> > > > > > + dev_info(dev, "%s (irq %d) initialized\n", pdev_irqname, chan->irq);
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static void ls1x_dma_chan_remove(struct ls1x_dma *dma, int chan_id)
> > > > > > +{
> > > > > > + struct device *dev = dma->ddev.dev;
> > > > > > + struct ls1x_dma_chan *chan = &dma->chan[chan_id];
> > > > > > +
> > > > > > + devm_free_irq(dev, chan->irq, chan);
> > > > > > + list_del(&chan->vchan.chan.device_node);
> > > > > > + tasklet_kill(&chan->vchan.task);
> > > > > > +}
> > > > > > +
> > > > > > +static int ls1x_dma_probe(struct platform_device *pdev)
> > > > > > +{
> > > > > > + struct device *dev = &pdev->dev;
> > > > > > + struct dma_device *ddev;
> > > > > > + struct ls1x_dma *dma;
> > > > > > + int nr_chans, ret, i;
> > > > > > +
> > > > > > + nr_chans = platform_irq_count(pdev);
> > > > > > + if (nr_chans <= 0)
> > > > > > + return nr_chans;
> > > > > > + if (nr_chans > LS1X_DMA_MAX_CHANNELS)
> > > > > > + return dev_err_probe(dev, -EINVAL,
> > > > > > + "nr_chans=%d exceeds the maximum\n",
> > > > > > + nr_chans);
> > > > > > +
> > > > > > + dma = devm_kzalloc(dev, struct_size(dma, chan, nr_chans), GFP_KERNEL);
> > > > > > + if (!dma)
> > > > > > + return -ENOMEM;
> > > > > > +
> > > > > > + /* initialize DMA device */
> > > > > > + dma->reg_base = devm_platform_ioremap_resource(pdev, 0);
> > > > > > + if (IS_ERR(dma->reg_base))
> > > > > > + return PTR_ERR(dma->reg_base);
> > > > > > +
> > > > > > + ddev = &dma->ddev;
> > > > > > + ddev->dev = dev;
> > > > > > + ddev->copy_align = DMAENGINE_ALIGN_4_BYTES;
> > > > > > + ddev->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
> > > > > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> > > > > > + ddev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
> > > > > > + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
> > > > > > + ddev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
> > > > > > + ddev->max_sg_burst = LS1X_DMA_MAX_DESC;
> > > > > > + ddev->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
> > > > > > + ddev->device_alloc_chan_resources = ls1x_dma_alloc_chan_resources;
> > > > > > + ddev->device_free_chan_resources = ls1x_dma_free_chan_resources;
> > > > > > + ddev->device_prep_slave_sg = ls1x_dma_prep_slave_sg;
> > > > > > + ddev->device_prep_dma_cyclic = ls1x_dma_prep_dma_cyclic;
> > > > > > + ddev->device_config = ls1x_dma_slave_config;
> > > > > > + ddev->device_pause = ls1x_dma_pause;
> > > > > > + ddev->device_resume = ls1x_dma_resume;
> > > > > > + ddev->device_terminate_all = ls1x_dma_terminate_all;
> > > > > > + ddev->device_tx_status = ls1x_dma_tx_status;
> > > > > > + ddev->device_issue_pending = ls1x_dma_issue_pending;
> > > > > > +
> > > > > > + dma_cap_set(DMA_SLAVE, ddev->cap_mask);
> > > > > > + INIT_LIST_HEAD(&ddev->channels);
> > > > > > +
> > > > > > + /* initialize DMA channels */
> > > > > > + for (i = 0; i < nr_chans; i++) {
> > > > > > + ret = ls1x_dma_chan_probe(pdev, dma, i);
> > > > > > + if (ret)
> > > > > > + return ret;
> > > > > > + }
> > > > > > + dma->nr_chans = nr_chans;
> > > > > > +
> > > > > > + ret = dmaenginem_async_device_register(ddev);
> > > > > > + if (ret) {
> > > > > > + dev_err(dev, "failed to register DMA device! %d\n", ret);
> > > > > > + return ret;
> > > > > > + }
> > > > > > +
> > > > > > + ret =
> > > > > > + of_dma_controller_register(dev->of_node, of_dma_xlate_by_chan_id,
> > > > > > + ddev);
> > > > > > + if (ret) {
> > > > > > + dev_err(dev, "failed to register DMA controller! %d\n", ret);
> > > > > > + return ret;
> > > > > > + }
> > > > > > +
> > > > > > + platform_set_drvdata(pdev, dma);
> > > > > > + dev_info(dev, "Loongson1 DMA driver registered\n");
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static void ls1x_dma_remove(struct platform_device *pdev)
> > > > > > +{
> > > > > > + struct ls1x_dma *dma = platform_get_drvdata(pdev);
> > > > > > + int i;
> > > > > > +
> > > > > > + of_dma_controller_free(pdev->dev.of_node);
> > > > > > +
> > > > > > + for (i = 0; i < dma->nr_chans; i++)
> > > > > > + ls1x_dma_chan_remove(dma, i);
> > > > > > +}
> > > > > > +
> > > > > > +static const struct of_device_id ls1x_dma_match[] = {
> > > > > > + { .compatible = "loongson,ls1b-apbdma" },
> > > > > > + { .compatible = "loongson,ls1c-apbdma" },
> > > > > > + { /* sentinel */ }
> > > > > > +};
> > > > > > +MODULE_DEVICE_TABLE(of, ls1x_dma_match);
> > > > > > +
> > > > > > +static struct platform_driver ls1x_dma_driver = {
> > > > > > + .probe = ls1x_dma_probe,
> > > > > > + .remove_new = ls1x_dma_remove,
> > > > > > + .driver = {
> > > > > > + .name = KBUILD_MODNAME,
> > > > > > + .of_match_table = ls1x_dma_match,
> > > > > > + },
> > > > > > +};
> > > > > > +
> > > > > > +module_platform_driver(ls1x_dma_driver);
> > > > > > +
> > > > > > +MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
> > > > > > +MODULE_DESCRIPTION("Loongson-1 APB DMA Controller driver");
> > > > > > +MODULE_LICENSE("GPL");
> > > > > >
> > > > > > --
> > > > > > 2.40.1
> > > > > >
> > > > > >
> > > >
> > > >
> > > >
> > > > --
> > > > Best regards,
> > > >
> > > > Keguang Zhang
> > > >
> >
> >
> >
> > --
> > Best regards,
> >
> > Keguang Zhang
--
Best regards,
Keguang Zhang
^ permalink raw reply
* Re: [PATCH v9 3/3] pwm: sifive: Fix the error in the idempotent test within the pwm_apply_state_debug function
From: Nylon Chen @ 2024-04-02 2:01 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: linux-riscv, devicetree, linux-kernel, linux-pwm, conor, robh+dt,
krzysztof.kozlowski+dt, palmer, paul.walmsley, aou,
thierry.reding, vincent.chen, zong.li, nylon7717
In-Reply-To: <jvwgsszvs4jtcytcphsdjulzgqfqzdp4sisu236ddwsqgmvriw@ngi4ljgh5b74>
Uwe Kleine-König <u.kleine-koenig@pengutronix.de> 於 2024年3月19日 週二 上午2:17寫道:
>
> Hello,
>
> On Thu, Feb 22, 2024 at 04:12:31PM +0800, Nylon Chen wrote:
> > Round the result to the nearest whole number. This ensures that
> > real_period is always a reasonable integer that is not lower than the
> > actual value.
> >
> > e.g.
> > $ echo 110 > /sys/devices/platform/led-controller-1/leds/d12/brightness
> > $ .apply is not idempotent (ena=1 pol=0 1739692/4032985) -> (ena=1 pol=0 1739630/4032985)
> >
> > Co-developed-by: Zong Li <zong.li@sifive.com>
> > Signed-off-by: Zong Li <zong.li@sifive.com>
> > Signed-off-by: Nylon Chen <nylon.chen@sifive.com>
> > ---
> > drivers/pwm/pwm-sifive.c | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c
> > index a586cfe4191b..bebcbebacccd 100644
> > --- a/drivers/pwm/pwm-sifive.c
> > +++ b/drivers/pwm/pwm-sifive.c
> > @@ -101,7 +101,7 @@ static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata,
> >
> > /* As scale <= 15 the shift operation cannot overflow. */
> > num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale);
> > - ddata->real_period = div64_ul(num, rate);
> > + ddata->real_period = DIV_ROUND_UP_ULL(num, rate);
> > dev_dbg(ddata->chip.dev,
> > "New real_period = %u ns\n", ddata->real_period);
> > }
Hi Uwe
>
> pwm_sifive_apply has a DIV64_U64_ROUND_CLOSEST(). I wonder if that needs
> adaption, too?!
According to my experiments, no adjustment is necessary.
>
> Best regards
> Uwe
>
> --
> Pengutronix e.K. | Uwe Kleine-König |
> Industrial Linux Solutions | https://www.pengutronix.de/ |
^ permalink raw reply
* Re: [PATCH v9 2/3] pwm: sifive: change the PWM algorithm
From: Nylon Chen @ 2024-04-02 2:08 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: linux-riscv, devicetree, linux-kernel, linux-pwm, conor, robh+dt,
krzysztof.kozlowski+dt, palmer, paul.walmsley, aou,
thierry.reding, vincent.chen, zong.li, nylon7717
In-Reply-To: <f5ukvah2ujko2iht3pd5jxq5kaukbs5z3pn5s7qwcnx4aqr3yv@mwtbwkcfa44a>
Uwe Kleine-König <u.kleine-koenig@pengutronix.de> 於 2024年3月19日 週二 上午2:16寫道:
>
> On Thu, Feb 22, 2024 at 04:12:30PM +0800, Nylon Chen wrote:
> > The `frac` variable represents the pulse inactive time, and the result
> > of this algorithm is the pulse active time.
> > Therefore, we must reverse the result.
> >
> > The reference is SiFive FU740-C000 Manual[0]
> >
> > Link: https://sifive.cdn.prismic.io/sifive/1a82e600-1f93-4f41-b2d8-86ed8b16acba_fu740-c000-manual-v1p6.pdf [0]
> >
> > Co-developed-by: Zong Li <zong.li@sifive.com>
> > Signed-off-by: Zong Li <zong.li@sifive.com>
> > Co-developed-by: Vincent Chen <vincent.chen@sifive.com>
> > Signed-off-by: Vincent Chen <vincent.chen@sifive.com>
> > Signed-off-by: Nylon Chen <nylon.chen@sifive.com>
> > ---
> > drivers/pwm/pwm-sifive.c | 10 ++++++----
> > 1 file changed, 6 insertions(+), 4 deletions(-)
> >
> > diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c
> > index eabddb7c7820..a586cfe4191b 100644
> > --- a/drivers/pwm/pwm-sifive.c
> > +++ b/drivers/pwm/pwm-sifive.c
> > @@ -110,9 +110,10 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> > struct pwm_state *state)
> > {
> > struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip);
> > - u32 duty, val;
> > + u32 duty, val, inactive;
> >
> > - duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
> > + inactive = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
> > + duty = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - inactive;
> >
> > state->enabled = duty > 0;
> >
> > @@ -123,7 +124,7 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> > state->period = ddata->real_period;
> > state->duty_cycle =
> > (u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH;
> > - state->polarity = PWM_POLARITY_INVERSED;
> > + state->polarity = PWM_POLARITY_NORMAL;
> >
> > return 0;
> > }
> > @@ -139,7 +140,7 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > int ret = 0;
> > u32 frac;
> >
> > - if (state->polarity != PWM_POLARITY_INVERSED)
> > + if (state->polarity != PWM_POLARITY_NORMAL)
> > return -EINVAL;
> >
> > cur_state = pwm->state;
> > @@ -159,6 +160,7 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> > frac = DIV64_U64_ROUND_CLOSEST(num, state->period);
> > /* The hardware cannot generate a 100% duty cycle */
>
> Is this still true now that we know that PWM_SIFIVE_PWMCMP is the
> inactive time in a period? If you fix that, the same claim in the header
> of the driver needs adaption, too.
I believe the statement is true, but I don't know which part the
driver header file refers to.
>
> > frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
> > + frac = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;
>
> I like the additional variable in pwm_sifive_get_state(). Can you please
> add one here, too?
got it
>
> > mutex_lock(&ddata->lock);
> > if (state->period != ddata->approx_period) {
>
Thank you for taking the time to help me review my implementation.
Nylon
> Best regards
> Uwe
>
> --
> Pengutronix e.K. | Uwe Kleine-König |
> Industrial Linux Solutions | https://www.pengutronix.de/ |
^ permalink raw reply
* [PATCH v7 0/4] firmware: arm_scmi: Add SCMI v3.2 pincontrol protocol basic support
From: Peng Fan (OSS) @ 2024-04-02 2:22 UTC (permalink / raw)
To: Sudeep Holla, Cristian Marussi, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Dan Carpenter
Cc: linux-arm-kernel, linux-kernel, devicetree, linux-gpio, Peng Fan,
Oleksii Moisieiev
This patchset is a rework from Oleksii's RFC v5 patchset
https://lore.kernel.org/all/cover.1698353854.git.oleksii_moisieiev@epam.com/
This patchset introduces some changes based on RFC v5:
- introduce helper get_max_msg_size
- support compatible string
- iterate the id_table
- Support multiple configs in one command
- Added i.MX support
- Patch 5 firmware: arm_scmi: Add SCMI v3.2 pincontrol protocol basic support
is almost same as RFCv5 expect multiple configs support.
- Patch 4 the dt-bindings includes compatible string to support i.MX
- Rebased on 2023-12-15 linux-next/master
If any comments from RFC v5 are missed, I am sorry in advance.
This PINCTRL Protocol is following Version 3.2 SCMI Spec Beta release.
On ARM-based systems, a separate Cortex-M based System Control Processor
(SCP) provides control on pins, as well as with power, clocks, reset
controllers. So implement the driver to support such cases.
The i.MX95 Example as below:
Configuration:
The scmi-pinctrl driver can be configured using DT bindings.
For example:
/ {
sram0: sram@445b1000 {
compatible = "mmio-sram";
reg = <0x0 0x445b1000 0x0 0x400>;
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0x0 0x445b1000 0x400>;
scmi_buf0: scmi-sram-section@0 {
compatible = "arm,scmi-shmem";
reg = <0x0 0x80>;
};
scmi_buf1: scmi-sram-section@80 {
compatible = "arm,scmi-shmem";
reg = <0x80 0x80>;
};
};
firmware {
scmi {
compatible = "arm,scmi";
mboxes = <&mu2 5 0>, <&mu2 3 0>, <&mu2 3 1>;
shmem = <&scmi_buf0>, <&scmi_buf1>;
#address-cells = <1>;
#size-cells = <0>;
scmi_iomuxc: protocol@19 {
compatible = "fsl,imx95-scmi-pinctrl";
reg = <0x19>;
};
};
};
};
&scmi_iomuxc {
pinctrl_tpm3: tpm3grp {
fsl,pins = <
IMX95_PAD_GPIO_IO12__TPM3_CH2(0x51e)
>;
};
};
This patchset has been tested on i.MX95-19x19-EVK board.
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
Changes in v7:
- Hope I not miss any comments. If any missed, please forgive. Since
i.MX95 SCMI firmware not support all the pinctrl features, I could only
do limited test.
- Version set to 0x10000
- Drop scmi_msg_func_set
- Use get_all to replace flag[0,1], not support flag 2 as of now.
- Add settings_get_one and settings_get_all ops to support get_all[false, true]
- PINCTRL_SET_PERMISSIONS is not included in this patchset
- Bail out if nr_pins is 0
- Add check nr_functions and nr_groups if they are 0.
- ext_name_flag changed to bool type
- Drop unrelated comment
- Use a central function for pin request and free
- Coding style optimization
- Use pinfunction to replace scmi_pinctrl_funcs
- For the devm_x APIs comments from Andy, I not update in the x/arm_scmi/pinctrl.c,
because it is correct usage.
- For included headers, I keep not change. I try to follow 80 max chars
for scmi driver, but with a few lines still exceed.
- Link to v6: https://lore.kernel.org/r/20240323-pinctrl-scmi-v6-0-a895243257c0@nxp.com
Changes in v6:
- Update pinctrl driver following ARM SCMI 3.2 public release
- Addressed Dan's comments, and followed Dan's suggestions, thanks.
- Dropped R-b/T-b in patch 3/4 and patch 4/4,
- Link to v5: https://lore.kernel.org/r/20240314-pinctrl-scmi-v5-0-b19576e557f2@nxp.com
Changes in v5:
- Rebased to linux-next next-20240313
- Link to v4: https://lore.kernel.org/r/20240223-pinctrl-scmi-v4-0-10eb5a379274@nxp.com
Changes in v4:
- Rebased to next-20240222
- Drop pinctrl-scmi-imx and compatible patches in V3
- Add T-b and R-b collected from v3
- Link to v3: https://lore.kernel.org/r/20240121-pinctrl-scmi-v3-0-8d94ba79dca8@nxp.com
Changes in v3:
- Add R-b for dt-binding patch
- Use 80 chars per line to align with other scmi drivers
- Add pinctrl_scmi_alloc_configs pinctrl_scmi_free_configs to replace
driver global config_value and config_type array to avoid in parrell
access issue. When num_configs is larger than 4, use alloc, else use
stack.
- Drop the separate MAITAINERS entry for firmware scmi pinctrl
- Use enum type, not u8 when referring the scmi or generic pin conf type
- Drop scmi_pinctrl_config_get_all which is not used at all for now.
- Update copyright year to 2024
- Move the enum scmi_pinctrl_conf_type above pinctrl_proto_ops for consistency
- Link to v2: https://lore.kernel.org/r/20240104-pinctrl-scmi-v2-0-a9bd86ab5a84@nxp.com
Changes in v2:
Added comments, and added R-b for Patch 1
Moved the compatile string and i.MX patch to the end, marked NOT APPLY
Patchset based on lore.kernel.org/all/20231221151129.325749-1-cristian.marussi@arm.com/
Addressed the binding doc issue, dropped i.MX content.
For the firmware pinctrl scmi driver, addressed the comments from Cristian
For the pinctrl scmi driver, addressed comments from Cristian
For the i.MX95 OEM stuff, I not have good idea, expect using compatbile
string. Maybe the firmware public an protocol attribute to indicate it is
VENDOR stuff or NXP use a new protocol id, not 0x19. But I think
current pinctrl-scmi.c not able to support OEM config, should we extend
it with some method? Anyway if patch 1-4 is good enough, they could
be picked up first.
Since I am only able to test the patch on i.MX95 which not support
geneirc pinconf, only OEM configs are tested in my side.
---
Peng Fan (4):
firmware: arm_scmi: introduce helper get_max_msg_size
dt-bindings: firmware: arm,scmi: support pinctrl protocol
firmware: arm_scmi: Add SCMI v3.2 pincontrol protocol basic support
pinctrl: Implementation of the generic scmi-pinctrl driver
.../devicetree/bindings/firmware/arm,scmi.yaml | 50 ++
MAINTAINERS | 1 +
drivers/firmware/arm_scmi/Makefile | 3 +-
drivers/firmware/arm_scmi/driver.c | 17 +
drivers/firmware/arm_scmi/pinctrl.c | 897 +++++++++++++++++++++
drivers/firmware/arm_scmi/protocols.h | 3 +
drivers/pinctrl/Kconfig | 11 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-scmi.c | 560 +++++++++++++
include/linux/scmi_protocol.h | 83 ++
10 files changed, 1625 insertions(+), 1 deletion(-)
---
base-commit: 70ca90deeca172d343bd18bf7fb2f992214c23c0
change-id: 20231215-pinctrl-scmi-4c5b0374f4c6
Best regards,
--
Peng Fan <peng.fan@nxp.com>
^ permalink raw reply
* [PATCH v7 1/4] firmware: arm_scmi: introduce helper get_max_msg_size
From: Peng Fan (OSS) @ 2024-04-02 2:22 UTC (permalink / raw)
To: Sudeep Holla, Cristian Marussi, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Dan Carpenter
Cc: linux-arm-kernel, linux-kernel, devicetree, linux-gpio, Peng Fan
In-Reply-To: <20240402-pinctrl-scmi-v7-0-3ea519d12cf7@nxp.com>
From: Peng Fan <peng.fan@nxp.com>
When Agent sending data to SCMI server, the Agent driver could check
the size to avoid protocol buffer overflow. So introduce the helper
get_max_msg_size.
Reviewed-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/firmware/arm_scmi/driver.c | 15 +++++++++++++++
drivers/firmware/arm_scmi/protocols.h | 2 ++
2 files changed, 17 insertions(+)
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 2709598f3008..415e6f510057 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -1488,6 +1488,20 @@ static int scmi_common_extended_name_get(const struct scmi_protocol_handle *ph,
return ret;
}
+/**
+ * scmi_common_get_max_msg_size - Get maximum message size
+ * @ph: A protocol handle reference.
+ *
+ * Return: Maximum message size for the current protocol.
+ */
+static int scmi_common_get_max_msg_size(const struct scmi_protocol_handle *ph)
+{
+ const struct scmi_protocol_instance *pi = ph_to_pi(ph);
+ struct scmi_info *info = handle_to_scmi_info(pi->handle);
+
+ return info->desc->max_msg_size;
+}
+
/**
* struct scmi_iterator - Iterator descriptor
* @msg: A reference to the message TX buffer; filled by @prepare_message with
@@ -1799,6 +1813,7 @@ static int scmi_protocol_msg_check(const struct scmi_protocol_handle *ph,
static const struct scmi_proto_helpers_ops helpers_ops = {
.extended_name_get = scmi_common_extended_name_get,
+ .get_max_msg_size = scmi_common_get_max_msg_size,
.iter_response_init = scmi_iterator_init,
.iter_response_run = scmi_iterator_run,
.protocol_msg_check = scmi_protocol_msg_check,
diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h
index 317d3fb32676..3e91536a77a3 100644
--- a/drivers/firmware/arm_scmi/protocols.h
+++ b/drivers/firmware/arm_scmi/protocols.h
@@ -258,6 +258,7 @@ struct scmi_fc_info {
* @fastchannel_init: A common helper used to initialize FC descriptors by
* gathering FC descriptions from the SCMI platform server.
* @fastchannel_db_ring: A common helper to ring a FC doorbell.
+ * @get_max_msg_size: A common helper to get the maximum message size.
*/
struct scmi_proto_helpers_ops {
int (*extended_name_get)(const struct scmi_protocol_handle *ph,
@@ -277,6 +278,7 @@ struct scmi_proto_helpers_ops {
struct scmi_fc_db_info **p_db,
u32 *rate_limit);
void (*fastchannel_db_ring)(struct scmi_fc_db_info *db);
+ int (*get_max_msg_size)(const struct scmi_protocol_handle *ph);
};
/**
--
2.37.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox