* Re: [PATCH 07/12] clk: qcom: gcc-mdm9607: Drop incorrect apss_tcu_clk_src
From: Stephan Gerhold @ 2026-06-10 14:09 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Georgi Djakov,
Shawn Guo, Bryan O'Donoghue, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Taniya Das, linux-arm-msm,
linux-clk, linux-kernel, devicetree
In-Reply-To: <f55526ad-ecd2-47b2-8b8f-5aec429bc1c0@oss.qualcomm.com>
On Wed, Jun 10, 2026 at 03:50:11PM +0200, Konrad Dybcio wrote:
> On 6/9/26 4:14 PM, Stephan Gerhold wrote:
> > From: Stephan Gerhold <stephan@gerhold.net>
> >
> > This clock does not seem to exist on MDM9607. Reading/writing the registers
> > always results in 0 or crashes. The math in the frequency table is also
> > broken. GPLL2 on MDM9607 runs at 480 MHz, so:
> >
> > - F(155000000, P_GPLL2, 6, 0, 0), // 480 MHz/6 = 80 MHz, not 155 MHz
> > - F(310000000, P_GPLL2, 3, 0, 0), // 480 MHz/3 = 160 MHz, not 310 MHz
> >
> > Presumably, this definition was mistakenly copied as-is from gcc-msm8916
> > (which uses 930 MHz for GPLL2). There are no branch consumers of this root
> > clock inside gcc-mdm9607 (notably, gcc_apss_tcu_clk has bimc_ddr_clk_src as
> > parent instead of this clock), so we can just drop it.
> >
> > Cc: stable@vger.kernel.org
> > Fixes: 48b7253264ea ("clk: qcom: Add MDM9607 GCC driver")
> > Signed-off-by: Stephan Gerhold <stephan@gerhold.net>
> > ---
>
> It does, CMD_RCGR @ 0x37000
>
> Interestingly, there's also an SMMU instance.. although downstream doesn't
> seem bothered with it
>
Thanks for checking, but that still doesn't give us enough to work with
it. In particular:
- What are the frequency tables? Which parents exist with which IDs?
- What is the hid_width?
- Where do we hook it up to some children?
The way it is right now it's definitely wrong. I don't think we'll need
it so removing it like this patch is probably the easiest thing to do...
Thanks,
Stephan
PS: This platform has the weird TZ-managed SMMU (like MSM8916), so there
isn't much you can do with it from Linux. Unless you are running TF-A,
then you can describe it as "arm,mmu-500" and use it for everything
pretty much exactly like you would normally expect. But you still don't
need to do anything with this clock.
^ permalink raw reply
* Re: [PATCH v3 2/8] soc: qcom: Add support for QMI TMD cooling devices
From: Daniel Lezcano @ 2026-06-10 14:15 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Gaurav Kohli, Bjorn Andersson, Mathieu Poirier, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Amit Kucheria,
Manivannan Sadhasivam, Konrad Dybcio, Kees Cook,
Gustavo A. R. Silva, cros-qcom-dts-watchers, linux-arm-msm,
linux-remoteproc, devicetree, linux-kernel, linux-pm,
linux-hardening, Manaf Meethalavalappu Pallikunhi, Casey Connolly
In-Reply-To: <awmoxzmrgqzot5yfwpoml2olntpultuguuvdfvk5mt2lxqjxqj@n3tsmix64fdb>
On 6/10/26 15:42, Dmitry Baryshkov wrote:
> On Tue, Jun 09, 2026 at 02:08:57PM +0200, Daniel Lezcano wrote:
>> On 6/9/26 13:30, Dmitry Baryshkov wrote:
>>> On Tue, Jun 09, 2026 at 03:52:57PM +0530, Gaurav Kohli wrote:
>>>> From: Casey Connolly <casey.connolly@linaro.org>
>>>>
>>>> Add a Qualcomm QMI Thermal Mitigation Device (TMD) to support thermal
>>>> cooling devices backed by remote subsystems.
>>>>
>>>> On several Qualcomm platforms, remote processors (for example modem and
>>>> CDSP) expose thermal mitigation controls through the TMD QMI service.
>>>> Client drivers need a way to discover that service, map DT thermal
>>>> mitigation endpoints to cooling devices, and forward cooling state
>>>> updates to the remote subsystem.
>>>>
>>>> Co-developed-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>>>> Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
>>>> Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
>>>> Signed-off-by: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
>>>
>>> Wrong SoB chain.
>>
>> I think Gaurav wanted to reflect the changes did a back and forth between
>> us, so I ended up in the delivery path somehow. I guess adding
>> Co-developped-by should fix the SoB but won't reflect Gaurav and Casey did
>> actually most of the work. So I'm fine if we remove my SoB to fix the chain
>
> Anyway, Gaurav's SoB should be the last one (and it should be preceeded
> by the CdB tag)
Ah yes, right :)
^ permalink raw reply
* Re: [PATCH v2 05/16] usb: hub: Associate port@ fwnode with USB port device
From: Andy Shevchenko @ 2026-06-10 14:16 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
linux-mediatek, linux-arm-kernel, linux-kernel,
Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-6-wenst@chromium.org>
On Wed, Jun 10, 2026 at 04:40:39PM +0800, Chen-Yu Tsai wrote:
> When a USB hub port is connected to a connector in a firmware node
> graph, the port itself has a node in the graph.
>
> Associate the port's firmware node with the USB port's device,
> usb_port::dev. This is used in later changes for the M.2 slot power
> sequencing provider to match against the requesting port.
Okay, would this affect ACPI-based systems? if so, how?
Can you elaborate on that, please?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v2 04/16] usb: hub: Return actual error from hub_configure() in hub_probe()
From: Andy Shevchenko @ 2026-06-10 14:20 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
linux-mediatek, linux-arm-kernel, linux-kernel,
Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-5-wenst@chromium.org>
On Wed, Jun 10, 2026 at 04:40:38PM +0800, Chen-Yu Tsai wrote:
> The addition of power sequencing descriptor handling in the USB hub code
> requires dealing with deferred probing from pwrseq_get(). The power
> sequencing provider may not yet be available when the USB hub probes.
>
> Return the actual error code from hub_configure() when it fails, so that
> the driver core can notice the deferred probe request.
Makes sense to me.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
One nit-pick, though.
...
> - if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) {
> + ret = hub_configure(hub, &desc->endpoint[0].desc);
> + if (ret >= 0) {
> onboard_dev_create_pdevs(hdev, &hub->onboard_devs);
>
> return 0;
> }
>
> hub_disconnect(intf);
> - return -ENODEV;
> + return ret;
Can we convert to regular pattern, id est checking for errors first?
ret = hub_configure(hub, &desc->endpoint[0].desc);
if (ret < 0) {
hub_disconnect(intf);
return ret;
}
onboard_dev_create_pdevs(hdev, &hub->onboard_devs);
return 0;
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v2 2/2] riscv: dts: sophgo: Add Milk-V Duo 256M board support
From: sashiko-bot @ 2026-06-10 14:20 UTC (permalink / raw)
To: Chen-Yu Yeh; +Cc: devicetree, conor+dt, robh
In-Reply-To: <20260610121026.1517621-2-chenyou910331@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] The device tree references an undefined label `&usb`, which will cause a fatal dtc compilation error.
- [Medium] The commit message claims to add support for an 'Onboard blue status LED', but the corresponding LED node is completely missing from the device tree.
- [Low] Accidental removal of the trailing newline at the end of the Makefile.
- [Low] Potentially stale or incomplete copyright attribution in the new DTS file.
--
commit 1a0c595fbd52010c818ad842d5ef53c54212434e
Author: Chen-Yu Yeh <chenyou910331@gmail.com>
riscv: dts: sophgo: Add Milk-V Duo 256M board support
The Milk-V Duo 256M is a small form factor development board based on
the Sophgo SG2002 SoC.
> - Onboard blue status LED (connected to PWR_GPIO[2] / porte 2)
>
> Tested on actual Milk-V Duo 256M hardware, verified boot to shell and
> heartbeat LED functionality.
[Severity: Medium]
Is the LED node missing from the device tree? The commit message mentions
support and verification for an onboard blue status LED, but there does
not seem to be a corresponding leds or gpio-leds node in the patch.
> diff --git a/arch/riscv/boot/dts/sophgo/Makefile b/arch/riscv/boot/dts/sophgo/Makefile
> --- a/arch/riscv/boot/dts/sophgo/Makefile
> +++ b/arch/riscv/boot/dts/sophgo/Makefile
[ ... ]
> dtb-$(CONFIG_ARCH_SOPHGO) += sg2042-evb-v1.dtb
> dtb-$(CONFIG_ARCH_SOPHGO) += sg2042-evb-v2.dtb
> -dtb-$(CONFIG_ARCH_SOPHGO) += sg2044-sophgo-srd3-10.dtb
> +dtb-$(CONFIG_ARCH_SOPHGO) += sg2044-sophgo-srd3-10.dtb
> \ No newline at end of file
[Severity: Low]
this isn't a bug, but was the trailing newline accidentally removed from
the end of the Makefile?
> diff --git a/arch/riscv/boot/dts/sophgo/sg2002-milkv-duo256m.dts b/arch/riscv/boot/dts/sophgo/sg2002-milkv-duo256m.dts
> new file mode 100644
> --- /dev/null
> +++ b/arch/riscv/boot/dts/sophgo/sg2002-milkv-duo256m.dts
> @@ -0,0 +1,108 @@
> +// SPDX-License-Identifier: (GPL-2.0 OR MIT)
> +/*
> + * Copyright (C) 2023 Jisheng Zhang <jszhang@kernel.org>
> + */
[Severity: Low]
this isn't a bug, but is this copyright attribution stale? The file appears
to use a template from 2023, and it might be missing the 2026 copyright for
the current author.
> +
> +/dts-v1/;
> +
> +#include "sg2002.dtsi"
> +#include <dt-bindings/gpio/gpio.h>
[Severity: Medium]
Is this header still required? It seems unused in the newly added device
tree file, possibly because the LED node was accidentally dropped.
[ ... ]
> +&uart0 {
> + pinctrl-0 = <&uart0_cfg>;
> + pinctrl-names = "default";
> + status = "okay";
> +};
> +
> +&usb {
> + dr_mode = "host";
> + status = "okay";
> +};
[Severity: High]
Will this compile successfully? It looks like the usb label is not defined
in the included base SoC device tree files, which could cause a dtc syntax
error during the build.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260610121026.1517621-1-chenyou910331@gmail.com?part=2
^ permalink raw reply
* [PATCH 1/3] dt-bindings: vendor-prefixes: Add youyeetoo
From: Daniele Briguglio @ 2026-06-10 13:58 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
Cc: devicetree, linux-kernel, linux-arm-kernel, linux-rockchip,
Daniele Briguglio
In-Reply-To: <20260610-yy3588-board-v1-0-4bb7176b6826@superkali.me>
Youyeetoo is the single board computer brand of Hong Kong Cybodev
Tech Limited.
Link: https://www.youyeetoo.com
Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 28784d66a..c0d05ba73 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1915,6 +1915,8 @@ patternProperties:
description: YSH & ATIL
"^yones-toptech,.*":
description: Yones Toptech Co., Ltd.
+ "^youyeetoo,.*":
+ description: Hong Kong Cybodev Tech Limited (youyeetoo)
"^ys,.*":
description: Shenzhen Yashi Changhua Intelligent Technology Co., Ltd.
"^ysoft,.*":
--
2.47.3
^ permalink raw reply related
* [PATCH 2/3] dt-bindings: arm: rockchip: Add Youyeetoo YY3588
From: Daniele Briguglio @ 2026-06-10 13:58 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
Cc: devicetree, linux-kernel, linux-arm-kernel, linux-rockchip,
Daniele Briguglio
In-Reply-To: <20260610-yy3588-board-v1-0-4bb7176b6826@superkali.me>
The YY3588 is a single board computer based on the Rockchip RK3588.
Add devicetree binding documentation for it.
Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
Documentation/devicetree/bindings/arm/rockchip.yaml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml
index 1a9dde186..e7894d2b8 100644
--- a/Documentation/devicetree/bindings/arm/rockchip.yaml
+++ b/Documentation/devicetree/bindings/arm/rockchip.yaml
@@ -1347,6 +1347,11 @@ properties:
- const: xunlong,orangepi-cm5
- const: rockchip,rk3588s
+ - description: Youyeetoo YY3588
+ items:
+ - const: youyeetoo,yy3588
+ - const: rockchip,rk3588
+
- description: Zkmagic A95X Z2
items:
- const: zkmagic,a95x-z2
--
2.47.3
^ permalink raw reply related
* [PATCH 0/3] arm64: dts: rockchip: Add Youyeetoo YY3588
From: Daniele Briguglio @ 2026-06-10 13:58 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
Cc: devicetree, linux-kernel, linux-arm-kernel, linux-rockchip,
Daniele Briguglio
This series adds support for the Youyeetoo YY3588, a single board
computer built around the Rockchip RK3588.
Both Ethernet ports, eMMC, SD card, USB, Type-C, HDMI output, WiFi on
the Mini PCIe slot, audio, the recovery key and the fan have been
tested on the board.
Board documentation: https://wiki.youyeetoo.com/YY3588
Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
Daniele Briguglio (3):
dt-bindings: vendor-prefixes: Add youyeetoo
dt-bindings: arm: rockchip: Add Youyeetoo YY3588
arm64: dts: rockchip: Add Youyeetoo YY3588
.../devicetree/bindings/arm/rockchip.yaml | 5 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
arch/arm64/boot/dts/rockchip/Makefile | 1 +
.../boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts | 1190 ++++++++++++++++++++
4 files changed, 1198 insertions(+)
---
base-commit: 8545eda00fdf3d7e17933ce0f706d005b1bad42d
change-id: 20260610-yy3588-board-ecc20882cae7
Best regards,
--
Daniele Briguglio <hello@superkali.me>
^ permalink raw reply
* [PATCH 3/3] arm64: dts: rockchip: Add Youyeetoo YY3588
From: Daniele Briguglio @ 2026-06-10 13:59 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
Cc: devicetree, linux-kernel, linux-arm-kernel, linux-rockchip,
Daniele Briguglio
In-Reply-To: <20260610-yy3588-board-v1-0-4bb7176b6826@superkali.me>
The YY3588 is a single board computer built around the Rockchip RK3588.
Specification:
- Rockchip RK3588 SoC
- 4/8/16/32 GB LPDDR4/4x
- up to 256 GB eMMC
- microSD card slot
- 1x 1000Base-T (Realtek RTL8211F) and 1x 2500Base-T (Realtek RTL8125)
- HDMI 2.1 output
- HDMI input
- 4x USB 3.0 Type-A via onboard hub, 1x USB 2.0 Type-A
- USB Type-C with USB 3.0
- M.2 M-key with PCIe 3.0 x4
- Mini PCIe slot for WiFi/BT or 4G modules
- SATA 3.0
- ES8388 audio codec with headphone jack and onboard microphone
- fan connector, RTC, recovery key
- 12 V DC input
Both Ethernet ports, eMMC, SD card, USB, Type-C, HDMI output, WiFi
on the Mini PCIe slot, audio, the recovery key and the fan have been
tested on the board.
Link: https://wiki.youyeetoo.com/YY3588
Signed-off-by: Daniele Briguglio <hello@superkali.me>
---
arch/arm64/boot/dts/rockchip/Makefile | 1 +
.../boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts | 1190 ++++++++++++++++++++
2 files changed, 1191 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile
index 761d82b4f..6cab03c9c 100644
--- a/arch/arm64/boot/dts/rockchip/Makefile
+++ b/arch/arm64/boot/dts/rockchip/Makefile
@@ -213,6 +213,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-toybrick-x0.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-turing-rk1.dtb
+dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-youyeetoo-yy3588.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-coolpi-4b.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-evb1-v10.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-gameforce-ace.dtb
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts b/arch/arm64/boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts
new file mode 100644
index 000000000..28d8790a2
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk3588-youyeetoo-yy3588.dts
@@ -0,0 +1,1190 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2024 Youyeetoo
+ * Copyright (c) 2026 Daniele Briguglio <hello@superkali.me>
+ */
+
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/leds/common.h>
+#include <dt-bindings/pinctrl/rockchip.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
+#include <dt-bindings/usb/pd.h>
+#include "rk3588.dtsi"
+
+/ {
+ model = "Youyeetoo YY3588";
+ compatible = "youyeetoo,yy3588", "rockchip,rk3588";
+
+ aliases {
+ ethernet0 = &gmac1;
+ ethernet1 = &r8125;
+ mmc0 = &sdhci;
+ mmc1 = &sdmmc;
+ };
+
+ adc-keys {
+ compatible = "adc-keys";
+ io-channels = <&saradc 1>;
+ io-channel-names = "buttons";
+ keyup-threshold-microvolt = <1800000>;
+ poll-interval = <100>;
+
+ button-recovery {
+ label = "Recovery";
+ linux,code = <KEY_VENDOR>;
+ press-threshold-microvolt = <17000>;
+ };
+ };
+
+ analog-sound {
+ compatible = "simple-audio-card";
+ pinctrl-names = "default";
+ pinctrl-0 = <&hp_det>;
+ simple-audio-card,name = "rockchip-es8388";
+ simple-audio-card,aux-devs = <&speaker_amp>;
+ simple-audio-card,format = "i2s";
+ simple-audio-card,hp-det-gpios = <&gpio1 RK_PC4 GPIO_ACTIVE_LOW>;
+ simple-audio-card,mclk-fs = <256>;
+ simple-audio-card,pin-switches = "Headphones", "Speaker";
+ simple-audio-card,routing =
+ "Headphones", "LOUT2",
+ "Headphones", "ROUT2",
+ "Speaker Amp INL", "LOUT1",
+ "Speaker Amp INR", "ROUT1",
+ "Speaker", "Speaker Amp OUTL",
+ "Speaker", "Speaker Amp OUTR",
+ "LINPUT1", "Microphone Jack",
+ "RINPUT1", "Microphone Jack",
+ "LINPUT2", "Onboard Microphone",
+ "RINPUT2", "Onboard Microphone";
+ simple-audio-card,widgets =
+ "Microphone", "Microphone Jack",
+ "Microphone", "Onboard Microphone",
+ "Headphone", "Headphones",
+ "Speaker", "Speaker";
+
+ simple-audio-card,cpu {
+ sound-dai = <&i2s0_8ch>;
+ };
+
+ simple-audio-card,codec {
+ sound-dai = <&es8388>;
+ system-clock-frequency = <12288000>;
+ };
+ };
+
+ chosen {
+ stdout-path = "serial2:1500000n8";
+ };
+
+ hdmi0-con {
+ compatible = "hdmi-connector";
+ type = "a";
+
+ port {
+ hdmi0_con_in: endpoint {
+ remote-endpoint = <&hdmi0_out_con>;
+ };
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+ pinctrl-names = "default";
+ pinctrl-0 = <&led_pins>;
+
+ led-0 {
+ color = <LED_COLOR_ID_BLUE>;
+ function = LED_FUNCTION_STATUS;
+ gpios = <&gpio1 RK_PD6 GPIO_ACTIVE_HIGH>;
+ };
+
+ led-1 {
+ function = LED_FUNCTION_HEARTBEAT;
+ gpios = <&gpio1 RK_PD7 GPIO_ACTIVE_HIGH>;
+ linux,default-trigger = "heartbeat";
+ };
+ };
+
+ /* PI6C557-05BLE PCIe 3.0 reference clock generator */
+ pcie30_port0_refclk: pcie-oscillator {
+ compatible = "gated-fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ clock-output-names = "pcie30_refclk";
+ vdd-supply = <&vcc3v3_pi6c_05>;
+ };
+
+ fan: pwm-fan {
+ compatible = "pwm-fan";
+ #cooling-cells = <2>;
+ cooling-levels = <0 50 100 150 200 255>;
+ fan-supply = <&vcc12v_dcin>;
+ pwms = <&pwm2 0 50000 0>;
+ };
+
+ pcie20_avdd0v85: regulator-pcie20-avdd0v85 {
+ compatible = "regulator-fixed";
+ regulator-name = "pcie20_avdd0v85";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ vin-supply = <&vdd_0v85_s0>;
+ };
+
+ pcie20_avdd1v8: regulator-pcie20-avdd1v8 {
+ compatible = "regulator-fixed";
+ regulator-name = "pcie20_avdd1v8";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ vin-supply = <&avcc_1v8_s0>;
+ };
+
+ pcie30_avdd0v75: regulator-pcie30-avdd0v75 {
+ compatible = "regulator-fixed";
+ regulator-name = "pcie30_avdd0v75";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ vin-supply = <&avdd_0v75_s0>;
+ };
+
+ pcie30_avdd1v8: regulator-pcie30-avdd1v8 {
+ compatible = "regulator-fixed";
+ regulator-name = "pcie30_avdd1v8";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ vin-supply = <&avcc_1v8_s0>;
+ };
+
+ vbus5v0_typec: regulator-vbus5v0-typec {
+ compatible = "regulator-fixed";
+ enable-active-high;
+ gpio = <&gpio4 RK_PB0 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&typec5v_pwren>;
+ regulator-name = "vbus5v0_typec";
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+ vin-supply = <&vcc5v0_sys>;
+ };
+
+ vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc_1v1_nldo_s3";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1100000>;
+ regulator-max-microvolt = <1100000>;
+ vin-supply = <&vcc5v0_sys>;
+ };
+
+ vcc12v_dcin: regulator-vcc12v-dcin {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc12v_dcin";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <12000000>;
+ regulator-max-microvolt = <12000000>;
+ };
+
+ vcc3v3_pi6c_05: regulator-vcc3v3-pi6c-05 {
+ compatible = "regulator-fixed";
+ enable-active-high;
+ gpios = <&gpio2 RK_PC5 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pi6c_05_pwren>;
+ regulator-name = "vcc3v3_pi6c_05";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ startup-delay-us = <5000>;
+ vin-supply = <&vcc5v0_sys>;
+ };
+
+ vcc3v3_sd_s0: regulator-vcc3v3-sd-s0 {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc3v3_sd_s0";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ vin-supply = <&vcc3v3_sys>;
+ };
+
+ vcc3v3_sys: regulator-vcc3v3-sys {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc3v3_sys";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ vin-supply = <&vcc12v_dcin>;
+ };
+
+ vcc5v0_sys: regulator-vcc5v0-sys {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc5v0_sys";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+ vin-supply = <&vcc12v_dcin>;
+ };
+
+ speaker_amp: speaker-amplifier {
+ compatible = "simple-audio-amplifier";
+ enable-gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&spk_en>;
+ sound-name-prefix = "Speaker Amp";
+ VCC-supply = <&vcc5v0_sys>;
+ };
+};
+
+&combphy0_ps {
+ status = "okay";
+};
+
+&combphy1_ps {
+ status = "okay";
+};
+
+&combphy2_psu {
+ status = "okay";
+};
+
+&cpu_b0 {
+ cpu-supply = <&vdd_cpu_big0_s0>;
+};
+
+&cpu_b1 {
+ cpu-supply = <&vdd_cpu_big0_s0>;
+};
+
+&cpu_b2 {
+ cpu-supply = <&vdd_cpu_big1_s0>;
+};
+
+&cpu_b3 {
+ cpu-supply = <&vdd_cpu_big1_s0>;
+};
+
+&cpu_l0 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l1 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l2 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l3 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&gmac1 {
+ clock_in_out = "input";
+ phy-handle = <&rgmii_phy>;
+ /* RX delay is added by the PHY, TX delay by the GMAC */
+ phy-mode = "rgmii-rxid";
+ pinctrl-names = "default";
+ pinctrl-0 = <&gmac1_miim
+ &gmac1_tx_bus2
+ &gmac1_rx_bus2
+ &gmac1_rgmii_clk
+ &gmac1_rgmii_bus
+ &gmac1_clkinout>;
+ tx_delay = <0x43>;
+ status = "okay";
+};
+
+&gpu {
+ mali-supply = <&vdd_gpu_s0>;
+ status = "okay";
+};
+
+&hdmi0 {
+ frl-enable-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_LOW>;
+ status = "okay";
+};
+
+&hdmi0_in {
+ hdmi0_in_vp0: endpoint {
+ remote-endpoint = <&vp0_out_hdmi0>;
+ };
+};
+
+&hdmi0_out {
+ hdmi0_out_con: endpoint {
+ remote-endpoint = <&hdmi0_con_in>;
+ };
+};
+
+&hdmi0_sound {
+ status = "okay";
+};
+
+&hdmi_receiver_cma {
+ status = "okay";
+};
+
+&hdmi_receiver {
+ hpd-gpios = <&gpio1 RK_PC6 GPIO_ACTIVE_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&hdmim1_rx_cec &hdmim1_rx_hpdin &hdmim1_rx_scl &hdmim1_rx_sda &hdmirx_5v_det>;
+ status = "okay";
+};
+
+&hdptxphy0 {
+ status = "okay";
+};
+
+&i2c0 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c0m2_xfer>;
+ status = "okay";
+
+ vdd_cpu_big0_s0: regulator@42 {
+ compatible = "rockchip,rk8602";
+ reg = <0x42>;
+ fcs,suspend-voltage-selector = <1>;
+ regulator-name = "vdd_cpu_big0_s0";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <1050000>;
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_cpu_big1_s0: regulator@43 {
+ compatible = "rockchip,rk8603", "rockchip,rk8602";
+ reg = <0x43>;
+ fcs,suspend-voltage-selector = <1>;
+ regulator-name = "vdd_cpu_big1_s0";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <1050000>;
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+};
+
+&i2c1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c1m2_xfer>;
+ status = "okay";
+
+ vdd_npu_s0: regulator@42 {
+ compatible = "rockchip,rk8602";
+ reg = <0x42>;
+ fcs,suspend-voltage-selector = <1>;
+ regulator-name = "vdd_npu_s0";
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+};
+
+&i2c6 {
+ clock-frequency = <400000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c6m0_xfer>;
+ status = "okay";
+
+ usbc0: usb-typec@22 {
+ compatible = "fcs,fusb302";
+ reg = <0x22>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <RK_PD3 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&usbc0_int>;
+ vbus-supply = <&vbus5v0_typec>;
+
+ usb_con: connector {
+ compatible = "usb-c-connector";
+ data-role = "dual";
+ label = "USB-C";
+ power-role = "source";
+ source-pdos = <PDO_FIXED(5000, 1500, PDO_FIXED_USB_COMM)>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ usbc0_hs: endpoint {
+ remote-endpoint = <&usb_host0_xhci_drd_sw>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ usbc0_ss: endpoint {
+ remote-endpoint = <&usbdp_phy0_typec_ss>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+
+ usbc0_sbu: endpoint {
+ remote-endpoint = <&usbdp_phy0_typec_sbu>;
+ };
+ };
+ };
+ };
+ };
+
+ hym8563: rtc@51 {
+ compatible = "haoyu,hym8563";
+ reg = <0x51>;
+ #clock-cells = <0>;
+ clock-output-names = "hym8563";
+ };
+};
+
+&i2c7 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c7m0_xfer>;
+ status = "okay";
+
+ es8388: audio-codec@11 {
+ compatible = "everest,es8388", "everest,es8328";
+ reg = <0x11>;
+ assigned-clocks = <&cru I2S0_8CH_MCLKOUT>;
+ assigned-clock-rates = <12288000>;
+ clocks = <&cru I2S0_8CH_MCLKOUT>;
+ AVDD-supply = <&vcc3v3_sys>;
+ DVDD-supply = <&vcc_1v8_s3>;
+ HPVDD-supply = <&vcc3v3_sys>;
+ PVDD-supply = <&vcc_1v8_s3>;
+ #sound-dai-cells = <0>;
+ };
+};
+
+&i2s0_8ch {
+ pinctrl-0 = <&i2s0_lrck
+ &i2s0_mclk
+ &i2s0_sclk
+ &i2s0_sdi0
+ &i2s0_sdo0>;
+ status = "okay";
+};
+
+&i2s5_8ch {
+ status = "okay";
+};
+
+&i2s7_8ch {
+ status = "okay";
+};
+
+&mdio1 {
+ rgmii_phy: ethernet-phy@1 {
+ /* RTL8211F */
+ compatible = "ethernet-phy-id001c.c916";
+ reg = <0x1>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&rtl8211f_rst>;
+ reset-assert-us = <20000>;
+ reset-deassert-us = <100000>;
+ reset-gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_LOW>;
+ };
+};
+
+&package_thermal {
+ polling-delay = <1000>;
+
+ trips {
+ package_fan0: package-fan0 {
+ temperature = <55000>;
+ hysteresis = <2000>;
+ type = "active";
+ };
+
+ package_fan1: package-fan1 {
+ temperature = <65000>;
+ hysteresis = <2000>;
+ type = "active";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&package_fan0>;
+ cooling-device = <&fan THERMAL_NO_LIMIT 1>;
+ };
+
+ map1 {
+ trip = <&package_fan1>;
+ cooling-device = <&fan 2 THERMAL_NO_LIMIT>;
+ };
+ };
+};
+
+&pcie2x1l0 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pcie2_0_rst>;
+ reset-gpios = <&gpio4 RK_PA5 GPIO_ACTIVE_HIGH>;
+ vpcie3v3-supply = <&vcc3v3_sys>;
+ status = "okay";
+};
+
+&pcie2x1l1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pcie2_1_rst>;
+ reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>;
+ vpcie3v3-supply = <&vcc3v3_sys>;
+ status = "okay";
+
+ pcie@0,0 {
+ reg = <0x300000 0 0 0 0>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+ device_type = "pci";
+ bus-range = <0x30 0x3f>;
+
+ r8125: ethernet@0,0 {
+ reg = <0x310000 0 0 0 0>;
+ };
+ };
+};
+
+&pcie30phy {
+ status = "okay";
+};
+
+&pcie3x4 {
+ clocks = <&cru ACLK_PCIE_4L_MSTR>, <&cru ACLK_PCIE_4L_SLV>,
+ <&cru ACLK_PCIE_4L_DBI>, <&cru PCLK_PCIE_4L>,
+ <&cru CLK_PCIE_AUX0>, <&cru CLK_PCIE4L_PIPE>,
+ <&pcie30_port0_refclk>;
+ clock-names = "aclk_mst", "aclk_slv",
+ "aclk_dbi", "pclk",
+ "aux", "pipe",
+ "ref";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pcie3x4_perstn>;
+ reset-gpios = <&gpio1 RK_PB4 GPIO_ACTIVE_HIGH>;
+ vpcie3v3-supply = <&vcc3v3_sys>;
+ status = "okay";
+};
+
+&pinctrl {
+ hdmirx {
+ hdmirx_5v_det: hdmirx-5v-det {
+ rockchip,pins = <1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+
+ leds {
+ led_pins: led-pins {
+ rockchip,pins = <1 RK_PD6 RK_FUNC_GPIO &pcfg_pull_none>,
+ <1 RK_PD7 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+
+ pcie {
+ pcie2_0_rst: pcie2-0-rst {
+ rockchip,pins = <4 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ pcie2_1_rst: pcie2-1-rst {
+ rockchip,pins = <4 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ pi6c_05_pwren: pi6c-05-pwren {
+ rockchip,pins = <2 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ pcie3x4_perstn: pcie3x4-perstn {
+ rockchip,pins = <1 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+
+ rtl8211f {
+ rtl8211f_rst: rtl8211f-rst {
+ rockchip,pins = <3 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+
+ sound {
+ hp_det: hp-det {
+ rockchip,pins = <1 RK_PC4 RK_FUNC_GPIO &pcfg_pull_down>;
+ };
+
+ spk_en: spk-en {
+ rockchip,pins = <4 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+
+ usb {
+ typec5v_pwren: typec5v-pwren {
+ rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ usbc0_int: usbc0-int {
+ rockchip,pins = <0 RK_PD3 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+};
+
+&pwm2 {
+ pinctrl-0 = <&pwm2m2_pins>;
+ status = "okay";
+};
+
+&rknn_core_0 {
+ npu-supply = <&vdd_npu_s0>;
+ sram-supply = <&vdd_npu_s0>;
+ status = "okay";
+};
+
+&rknn_core_1 {
+ npu-supply = <&vdd_npu_s0>;
+ sram-supply = <&vdd_npu_s0>;
+ status = "okay";
+};
+
+&rknn_core_2 {
+ npu-supply = <&vdd_npu_s0>;
+ sram-supply = <&vdd_npu_s0>;
+ status = "okay";
+};
+
+&rknn_mmu_0 {
+ status = "okay";
+};
+
+&rknn_mmu_1 {
+ status = "okay";
+};
+
+&rknn_mmu_2 {
+ status = "okay";
+};
+
+&saradc {
+ vref-supply = <&avcc_1v8_s0>;
+ status = "okay";
+};
+
+&sata0 {
+ status = "okay";
+};
+
+&sdhci {
+ bus-width = <8>;
+ mmc-hs400-1_8v;
+ mmc-hs400-enhanced-strobe;
+ no-sd;
+ no-sdio;
+ non-removable;
+ status = "okay";
+};
+
+&sdmmc {
+ bus-width = <4>;
+ cap-mmc-highspeed;
+ cap-sd-highspeed;
+ disable-wp;
+ max-frequency = <150000000>;
+ no-mmc;
+ no-sdio;
+ sd-uhs-sdr104;
+ vmmc-supply = <&vcc3v3_sd_s0>;
+ vqmmc-supply = <&vccio_sd_s0>;
+ status = "okay";
+};
+
+&spi2 {
+ assigned-clocks = <&cru CLK_SPI2>;
+ assigned-clock-rates = <200000000>;
+ num-cs = <1>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&spi2m2_cs0 &spi2m2_pins>;
+ status = "okay";
+
+ pmic@0 {
+ compatible = "rockchip,rk806";
+ reg = <0x0>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>,
+ <&rk806_dvs2_null>, <&rk806_dvs3_null>;
+ spi-max-frequency = <1000000>;
+ system-power-controller;
+
+ vcc1-supply = <&vcc5v0_sys>;
+ vcc2-supply = <&vcc5v0_sys>;
+ vcc3-supply = <&vcc5v0_sys>;
+ vcc4-supply = <&vcc5v0_sys>;
+ vcc5-supply = <&vcc5v0_sys>;
+ vcc6-supply = <&vcc5v0_sys>;
+ vcc7-supply = <&vcc5v0_sys>;
+ vcc8-supply = <&vcc5v0_sys>;
+ vcc9-supply = <&vcc5v0_sys>;
+ vcc10-supply = <&vcc5v0_sys>;
+ vcc11-supply = <&vcc_2v0_pldo_s3>;
+ vcc12-supply = <&vcc5v0_sys>;
+ vcc13-supply = <&vcc_1v1_nldo_s3>;
+ vcc14-supply = <&vcc_1v1_nldo_s3>;
+ vcca-supply = <&vcc5v0_sys>;
+
+ gpio-controller;
+ #gpio-cells = <2>;
+
+ rk806_dvs1_null: dvs1-null-pins {
+ pins = "gpio_pwrctrl1";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs2_null: dvs2-null-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs3_null: dvs3-null-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun0";
+ };
+
+ regulators {
+ vdd_gpu_s0: vdd_gpu_mem_s0: dcdc-reg1 {
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_gpu_s0";
+ regulator-enable-ramp-delay = <400>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_cpu_lit_s0: vdd_cpu_lit_mem_s0: dcdc-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_cpu_lit_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_log_s0: dcdc-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <675000>;
+ regulator-max-microvolt = <750000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_log_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <750000>;
+ };
+ };
+
+ vdd_vdenc_s0: vdd_vdenc_mem_s0: dcdc-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_vdenc_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_ddr_s0: dcdc-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <675000>;
+ regulator-max-microvolt = <900000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_ddr_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <850000>;
+ };
+ };
+
+ vdd2_ddr_s3: dcdc-reg6 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-name = "vdd2_ddr_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ };
+ };
+
+ vcc_2v0_pldo_s3: dcdc-reg7 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <2000000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_2v0_pldo_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <2000000>;
+ };
+ };
+
+ vcc_3v3_s3: dcdc-reg8 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vcc_3v3_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <3300000>;
+ };
+ };
+
+ vddq_ddr_s0: dcdc-reg9 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-name = "vddq_ddr_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_1v8_s3: dcdc-reg10 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcc_1v8_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ avcc_1v8_s0: pldo-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "avcc_1v8_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ vcc_1v8_s0: pldo-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcc_1v8_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ avdd_1v2_s0: pldo-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-name = "avdd_1v2_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_3v3_s0: pldo-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vcc_3v3_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vccio_sd_s0: pldo-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vccio_sd_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ pldo6_s3: pldo-reg6 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "pldo6_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ vdd_0v75_s3: nldo-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdd_0v75_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <750000>;
+ };
+ };
+
+ vdd_ddr_pll_s0: nldo-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ regulator-name = "vdd_ddr_pll_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <850000>;
+ };
+ };
+
+ avdd_0v75_s0: nldo-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "avdd_0v75_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_0v85_s0: nldo-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ regulator-name = "vdd_0v85_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_0v75_s0: nldo-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdd_0v75_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+ };
+ };
+};
+
+&tsadc {
+ status = "okay";
+};
+
+&u2phy0 {
+ status = "okay";
+};
+
+&u2phy0_otg {
+ status = "okay";
+};
+
+&u2phy1 {
+ status = "okay";
+};
+
+&u2phy1_otg {
+ status = "okay";
+};
+
+&u2phy2 {
+ status = "okay";
+};
+
+&u2phy2_host {
+ phy-supply = <&vcc5v0_sys>;
+ status = "okay";
+};
+
+&u2phy3 {
+ status = "okay";
+};
+
+&u2phy3_host {
+ phy-supply = <&vcc5v0_sys>;
+ status = "okay";
+};
+
+&uart1 {
+ pinctrl-0 = <&uart1m0_xfer>;
+ status = "okay";
+};
+
+&uart2 {
+ pinctrl-0 = <&uart2m0_xfer>;
+ status = "okay";
+};
+
+&uart6 {
+ pinctrl-0 = <&uart6m0_xfer>;
+ status = "okay";
+};
+
+&uart7 {
+ pinctrl-0 = <&uart7m0_xfer>;
+ status = "okay";
+};
+
+&uart9 {
+ pinctrl-0 = <&uart9m0_xfer>;
+ status = "okay";
+};
+
+&usb_host0_ehci {
+ status = "okay";
+};
+
+&usb_host0_ohci {
+ status = "okay";
+};
+
+&usb_host0_xhci {
+ usb-role-switch;
+ status = "okay";
+
+ port {
+ usb_host0_xhci_drd_sw: endpoint {
+ remote-endpoint = <&usbc0_hs>;
+ };
+ };
+};
+
+&usb_host1_ehci {
+ status = "okay";
+};
+
+&usb_host1_ohci {
+ status = "okay";
+};
+
+&usb_host1_xhci {
+ dr_mode = "host";
+ status = "okay";
+};
+
+&usbdp_phy0 {
+ mode-switch;
+ orientation-switch;
+ sbu1-dc-gpios = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>;
+ sbu2-dc-gpios = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>;
+ status = "okay";
+
+ port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ usbdp_phy0_typec_ss: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&usbc0_ss>;
+ };
+
+ usbdp_phy0_typec_sbu: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usbc0_sbu>;
+ };
+ };
+};
+
+&usbdp_phy1 {
+ status = "okay";
+};
+
+&vop {
+ status = "okay";
+};
+
+&vop_mmu {
+ status = "okay";
+};
+
+&vp0 {
+ vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 {
+ reg = <ROCKCHIP_VOP2_EP_HDMI0>;
+ remote-endpoint = <&hdmi0_in_vp0>;
+ };
+};
--
2.47.3
^ permalink raw reply related
* Re: [RFC PATCH v3 0/9] accel: rocket: Add RK3568 NPU support
From: Diederik de Haas @ 2026-06-10 14:28 UTC (permalink / raw)
To: Midgy Balon, Diederik de Haas
Cc: Chaoyi Chen, tomeu, ogabbay, heiko, robh, krzk+dt, conor+dt, joro,
will, robin.murphy, dri-devel, linux-rockchip, devicetree,
linux-arm-kernel, iommu, linux-kernel, Simon Xue, Finley Xiao,
Jonas Karlman
In-Reply-To: <CA+GS1Y1xAq-9eMyMmoVE6NG9KLG7XRxgPoSr5RkW=6fT5D820g@mail.gmail.com>
On Wed Jun 10, 2026 at 3:36 PM CEST, Midgy Balon wrote:
> Hello Chaoyi & Diederik,
>
> I compared the RK3568 and RK3588 NPU power-domain + DTS as you
> suggested, and it lines up
> exactly with what you described.
>
> The difference is the `need_regulator` capability. RK3588's NPU domain is
> `DOMAIN_RK3588("npu", …, false, true)` — the trailing `true` is
> `regulator`/`need_regulator`.
> The mainline RK3568 macro `DOMAIN_RK3568(name, pwr, req, wakeup)` has
> no regulator parameter at
> all, so `RK3568_PD_NPU` can't be marked need_regulator. My v4 adds
> that: a regulator-capable
> RK3568 NPU domain (need_regulator = true) plus `domain-supply =
> <&vdd_npu>` on the NPU node —
> i.e. the same shape as RK3588.
>
> And the fix you referenced (Frank Zhang's "pmdomain: rockchip: Fix init genpd as
> GENPD_STATE_ON before regulator ready", plus "quiet regulator error on
> -EPROBE_DEFER") is
> already in my base (v7.1-rc6), so the `if (need_regulator)
> rockchip_pd_power(pd, false)`
> default-off path is in effect. That's what resolves the actual problem
> for me: with rocket
> built as a module (the normal config), need_regulator on the NPU
> domain, and those pmdomain
> patches in place, the board boots cleanly and NPU jobs run with no RCU
> stall / no deadlock. My
> earlier hang was an artifact of a self-contained rocket=y image
> probing in the initcalls before
> the I2C regulator core was up — as a module it loads ~6.8 s in, well
> after, so it's gone.
>
> I also went back and checked the `fw_devlink=permissive` question
> myself — and good news, it
> turns out it is NOT needed. I rebooted the exact same kernel with
> permissive removed from the
> cmdline (strict fw_devlink, the default), and the board boots cleanly,
> the NPU probes
> (`rocket fde40000.npu: Rockchip NPU core 0 version: 0`), and NPU jobs
> submit and run five times
> in a row with no deadlock and no RCU stall. So strict fw_devlink
> resolves the NPU/PMIC ordering
> fine via deferred probe.
>
> The one remaining thing is cosmetic: at power-domain-controller probe
> (~2.94 s) I still get,
> in BOTH modes (with or without permissive):
>
> rockchip-pm-domain …: Failed to create device link (0x180) with
> supplier 0-0020 …power-domain@6
>
> i.e. genpd can't form the link to the rk809 (the I2C PMIC supplying
> vdd_npu) because the PMIC
> isn't registered yet at that point. It's non-fatal — the domain
> defaults off (Frank's patch),
> the rail comes up via the regulator core, the NPU probes a few seconds
> later, and all jobs run.
>
> One question: on RK3588 with need_regulator, do you also see that
> "Failed to create device
> link … supplier <pmic>" line at pmdomain probe, or does it order
> cleanly? If RK3588 is clean,
> is there a DTS detail (e.g. the regulator's bus/probe order) I should
> mirror on RK3568 to make
> the link form in time — or is this line just expected/harmless and
> best left as-is?
[ 2.110935] rockchip-pm-domain fd8d8000.power-management:power-controller: Failed to create device link (0x180) with supplier 2-0042 for /power-management@fd8d8000/power-controller/power-domain@8
[ 2.557459] sdhci-dwcmshc fe2e0000.mmc: Can't reduce the clock below 52MHz in HS200/HS400 mode
[ 2.647174] rockchip-pm-domain fd8d8000.power-management:power-controller: Failed to create device link (0x180) with supplier 2-0042 for /power-management@fd8d8000/power-controller/power-domain@8
[ 2.945089] rockchip-pm-domain fd8d8000.power-management:power-controller: Failed to create device link (0x180) with supplier spi2.0 for /power-management@fd8d8000/power-controller/power-domain@12
8 = NPU; 12 = GPU
on both nanopc-t6-lts and nanopc-t6-plus (both RK3588).
And on a 6.18 dmesg output I have for Rock 5B, I see the ~ same, but then
it's 1-0042 instead of 2-0042.
I don't know if it's bad or harmless, but it is consistent.
HTH,
Diederik
> @Diederik — thanks; the DCDC_REG2 change and Jonas's USB-suspend
> series look like generally
> useful RK356x robustness fixes, though for this specific NPU
> device-link the need_regulator +
> Frank's pmdomain patches seem to be the relevant piece. I'll keep them
> in mind for suspend.
>
> The convolution-output / compute-completion issue is still separate
> and open (@Finley — that's
> the PVTPLL/NoC one); the power-domain side is in good shape for v4.
>
> Thanks y'all for your help :)
>
> Kind regards,
> Midgy
>
> Le mer. 10 juin 2026 à 12:05, Diederik de Haas
> <diederik@cknow-tech.com> a écrit :
>>
>> Hi,
>>
>> On Wed Jun 10, 2026 at 3:14 AM CEST, Chaoyi Chen wrote:
>> > Hi Midgy,
>> >
>> > On 6/9/2026 7:11 PM, Midgy Balon wrote:
>> >> Hello Chaoyi,
>> >>
>> >> You were right - building rocket as a module fixes it. Thanks for the pointer.
>> >>
>> >> I rebuilt with CONFIG_DRM_ACCEL_ROCKET=m (everything else the same:
>> >> need_regulator on
>> >> the RK3568 NPU power domain via a DOMAIN_M_R variant, domain-supply =
>> >> <&vdd_npu>, and the
>> >> regulator-always-on workaround dropped). The board now boots cleanly
>> >> and, more importantly,
>> >> an NPU job submit no longer hangs: I ran the test workload five times
>> >> with no RCU stall and
>> >> no freeze.
>> >>
>> >> So with rocket=m the need_regulator approach works on RK3568, and I'll
>> >> keep it for v4
>> >> (domain-supply + need_regulator, instead of marking vdd_npu
>> >> always-on). rocket=m is the
>> >> normal configuration anyway; my earlier hang came from building it =y
>> >> in a self-contained
>> >> image, so it probed in the initcalls (around 2 s) and the genpd ->
>> >> I2C-PMIC regulator
>> >> transition ran before the system was ready. As a module it loads from
>> >> udev much later
>> >> (~6.8 s here), after the I2C controller and regulator core are fully up.
>> >>
>> >> On your question of when the device-link error is printed - it is at
>> >> power-domain
>> >> controller probe, not at the rocket probe:
>> >>
>> >> [ 2.700618] vdd_npu: Bringing 500000uV into 825000-825000uV
>> >> [ 2.749637] rockchip-pm-domain fdd90000.power-management:power-controller:
>> >> Failed to create device link (0x180) with supplier 0-0020 for
>> >> /power-management@fdd90000/power-controller/power-domain@6
>> >> [ 2.945955] platform fde40000.npu: Adding to iommu group 3
>> >> ...
>> >> [ 6.840374] rocket: loading out-of-tree module taints kernel.
>> >> [ 6.877647] [drm] Initialized rocket 0.0.0 for rknn on minor 0
>> >> [ 6.879950] rocket fde40000.npu: Rockchip NPU core 0 version: 0
>> >>
>> >> So the device-link to the rk809 PMIC (0-0020) fails to form at ~2.75
>> >> s, well before rocket
>> >> loads at ~6.8 s. It is non-fatal here - the vdd_npu rail is brought up
>> >> by the regulator core
>> >> and all jobs run - and there is no "failed to get ack on domain npu"
>> >> NoC warning this boot
>> >> (the always-on kernel had one). The complete boot log is attached.
>> >>
>> >> Two notes / one question:
>> >> - This boot used fw_devlink=permissive on the command line. Is the
>> >> "Failed to create device
>> >> link ... supplier 0-0020" at pmdomain probe expected/benign, or is
>> >> there a clean way to make
>> >> it order correctly (so it also works without permissive, and a =y
>> >> build wouldn't deadlock in
>> >> the initcalls)?
>> >
>> > We encountered the same issue on the RK3588 NPU before. And it was
>> > resolved with the following patch at that time.
>> >
>> > https://lore.kernel.org/all/20251216055247.13150-1-rmxpzlb@gmail.com/
>> >
>> > Please compare the differences in NPU pmdomain and DTS configuration
>> > between the RK3568 and RK3588.
>>
>> About a month ago on #linux-rockchip we were discussing PM 'stuff':
>> https://libera.catirclogs.org/linux-rockchip/2026-05-15#39939137;
>> which references this paste
>> https://paste.sr.ht/~diederik/89d9f84e22474e837b55286d213b67f03859ce2e
>> I've since removed the DCDC_REG2 for PineTab2 and the 'fix' should likely
>> be extended to cover all RK3566/RK3568 devices though.
>>
>> It's what I made at the time hoping to fix a suspend/resume issue when
>> trying upstream TF-A. It didn't fix the issue at the time, but may still
>> be useful/needed and I think it's what Chaoyi hinted at.
>>
>> Just yesterday, Jonas posted this patch which may be useful/needed too:
>> https://lore.kernel.org/linux-rockchip/20260609154124.445182-1-jonas@kwiboo.se/
>>
>> HTH,
>> Diederik
>>
>> >> - (The convolution output is still uniform zero-point / the job times
>> >> out - that is the
>> >> separate NPU compute-completion issue, unrelated to the power-domain
>> >> work. Finley, that is
>> >> the one I flagged earlier re PVTPLL/NoC.)
>> >>
>> >> Kind regards,
>> >> Midgy
>> >>
>>
>
> _______________________________________________
> Linux-rockchip mailing list
> Linux-rockchip@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-rockchip
^ permalink raw reply
* Re: [PATCH v2 1/4] dt-bindings: remoteproc: imx_rproc: document optional "memory-region-names"
From: Frank Li @ 2026-06-10 14:29 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Mathieu Poirier, Laurentiu Mihalcea, Bjorn Andersson, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sascha Hauer, Peng Fan,
Fabio Estevam, Daniel Baluta, Francesco Dolcini, linux-remoteproc,
devicetree, imx, linux-arm-kernel, linux-kernel
In-Reply-To: <20260610-accomplished-antique-mink-cf0ead@quoll>
On Wed, Jun 10, 2026 at 09:39:25AM +0200, Krzysztof Kozlowski wrote:
> On Tue, Jun 09, 2026 at 11:33:03AM -0600, Mathieu Poirier wrote:
> > On Tue, 9 Jun 2026 at 11:06, Frank Li <Frank.li@oss.nxp.com> wrote:
> > >
> > > On Tue, Jun 09, 2026 at 10:40:06AM -0600, Mathieu Poirier wrote:
> > > > [You don't often get email from mathieu.poirier@linaro.org. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> > > >
> > > > On Fri, Jun 05, 2026 at 04:36:18AM -0700, Laurentiu Mihalcea wrote:
> > > > > From: Laurentiu Mihalcea <laurentiu.mihalcea@nxp.com>
> > > > >
> > > > > The names of the carveout regions are derived using the names of the
> > > > > reserved memory devicetree nodes, which are referenced using the
> > > > > "memory-region" property. This adds a restriction on the names of said
> > > > > devicetree nodes, often bearing specific names such as: "vdevbuffer",
> > > > > "vdev0vring0", "rsc-table", etc... This goes against the devicetree
> > > > > specification's recommendation, which states that the devicetree node
> > > > > names should be generic.
> > > >
> > > > I don't see what is so restrictive in using the node name of the reserved-memory
> > > > regions. Function of_reserved_mem_region_to_resource() is already doing all the
> > > > parsing, packaging everything in a neat and easy to use "struct resource". What
> > > > will you gain with this new "memory-region-names" that can't be done with the
> > > > current solution?
> > >
> > > DT Binding check can't find such wrong if node name is not what expected.
> > > Binding can't restrict memory's node name because there ware not specific
> > > compatible string for it.
> > >
> >
> > But what "wrong" could that be, and what kind of restriction are you
> > hoping to enforce? What specific problem are you hoping to solve?
> >
> > I'll wait to see what the DT people think about this - I personally
> > don't see the value in it.
>
> I see no point in this commit, but maybe because the commit msg is just
> misleading. It mixes node names with names for phandles which are two
> separate things.
For example:
rsc_table: rsc-table@90000000
{ ret = <0x90000000>;
no-map;
}
m4 {
...
memory-region = <&rsc_table>;
}
If you change node name "rsc-table" to "memory", driver will failure
because it parse node name "rsc-table", which phandle point to. but no
binding to restrict node name to "rsc-table". So rsc-table became hidden
ABI.
if use memory-region-names, we can restrict memory-region-name to
"rsc-table" earsily.
Frank
>
> Plus this change actually makes nothing - no names are restricted to any
> meaningful values!
>
> Best regards,
> Krzysztof
>
^ permalink raw reply
* Re: [PATCH v2 07/16] usb: hub: Power on connected M.2 E-key connectors
From: Andy Shevchenko @ 2026-06-10 14:31 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
linux-mediatek, linux-arm-kernel, linux-kernel,
Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-8-wenst@chromium.org>
On Wed, Jun 10, 2026 at 04:40:41PM +0800, Chen-Yu Tsai wrote:
> The new M.2 E-key connector can have a USB connection. For the USB device
> on this connector to work, its power must be enabled and the W_DISABLE2#
> signal deasserted. The connector driver handles this and provides a
> toggle over the power sequencing API.
>
> This feature currently only supports a directly connected (no mux in
> between) M.2 E-key connector. Existing USB connector types are not
> covered. The USB A connector was recently added to the onboard devices
> driver. USB B connectors have historically been managed by the USB
> gadget or dual-role device controller drivers. USB C connectors are
> handled by TCPM drivers.
>
> The power sequencing API does not know whether a power sequence provider
> is not needed or not available yet, so we only request it for connectors
> that we know need it, which at this time is just the E-key connector.
>
> On the USB side, the port firmware node (if present) is tied to the
> usb_port device. This device is used to acquire the power sequencing
> descriptor. This allows the provider to tell the different ports on one
> hub apart.
>
> This feature is not implemented in the onboard USB devices driver. The
> power sequencing API expects the consumer device to make the request,
> but there is no device node to instantiate a platform device to tie
> the driver to. The connector is not a child node of the USB host or
> hub, and the graph connection is from a USB port to the connector.
> And the connector itself already has a driver.
>
> Power sequencing is not directly enabled in the connector driver as
> that would completely decouple the timing of it from the USB subsystem.
> It would not be possible for the USB subsystem to toggle the power
> for a power cycle or to disable the port.
>
> This change depends on another change to make the power sequencing
> framework bool instead of tristate. The USB core and hub driver are
> bool, so if the power sequencing framework is built as a module, the
> kernel will fail to link.
> int usb_hub_set_port_power(struct usb_device *hdev, struct usb_hub *hub,
> int port1, bool set)
> {
> - int ret;
> + struct usb_port *pwrseq_port = hub->ports[port1 - 1];
> + int ret = 0;
Don't touch ret here. It's easier to maintain when assignment is closer to it's
first user (because it's getting validated there).
> + /* non-SuperSpeed USB port holds pwrseq descriptor reference. */
> + if (hub->ports[port1 - 1]->is_superspeed && hub->ports[port1 - 1]->peer)
> + pwrseq_port = hub->ports[port1 - 1]->peer;
ret = 0;
> + if (set && !pwrseq_port->pwrseq_on)
> + ret = pwrseq_power_on(pwrseq_port->pwrseq);
> + else if (!set && pwrseq_port->pwrseq_on)
> + ret = pwrseq_power_off(pwrseq_port->pwrseq);
> + if (ret)
> + return ret;
>
> if (set)
> ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
> else
> ret = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
>
> - if (ret)
> + if (ret) {
> + if (set && !pwrseq_port->pwrseq_on)
> + pwrseq_power_off(pwrseq_port->pwrseq);
> + else if (!set && pwrseq_port->pwrseq_on)
> + pwrseq_power_on(pwrseq_port->pwrseq);
> return ret;
Can we rather have a couple of helpers? It might be hard to follow all this.
In such a case you won't even need the ret assignment here.
> + }
>
> - if (set)
> + if (set) {
> set_bit(port1, hub->power_bits);
> - else
> + pwrseq_port->pwrseq_on = 1;
> + } else {
> clear_bit(port1, hub->power_bits);
> + pwrseq_port->pwrseq_on = 0;
> + }
Just
pwrseq_port->pwrseq_on = set; // or explicit comparison
assign_bit(port1, hub->power_bits, pwrseq_port->pwrseq_on);
> return 0;
> }
...
> +static bool port_pwrseq_is_supported(struct usb_port *port_dev)
> +{
> + struct device *dev = &port_dev->dev;
> + struct fwnode_handle *port = dev->fwnode;
+ blank line here, because for RAII we assume the C99 definitions inside
the code, so one can insert the code in between. Doing it before ep validation
may lead to interesting errors in the future.
> + struct fwnode_handle *ep __free(fwnode_handle) =
> + fwnode_graph_get_next_port_endpoint(port, NULL);
> + if (!ep)
> + return false;
> +
> + struct fwnode_handle *remote __free(fwnode_handle) =
> + fwnode_graph_get_remote_port_parent(ep);
> + if (!remote)
> + return false;
> +
> + if (!fwnode_device_is_compatible(remote, "pcie-m2-e-connector")) {
> + dev_dbg(dev, "remote endpoint %pfw is not a supported connector", remote);
> + return false;
> + }
> +
> + return true;
> +}
...
> + if (IS_ERR(port_dev->pwrseq)) {
> + retval = PTR_ERR(port_dev->pwrseq);
> + dev_err_probe(&port_dev->dev, retval,
> + "failed to get power sequencing descriptor\n");
retval = dev_err_probe(PTR_ERR(...));
> + goto err_put_kn;
> + }
...
> retval = component_add(&port_dev->dev, &connector_ops);
> if (retval) {
> dev_warn(&port_dev->dev, "failed to add component\n");
dev_warn_probe() // however it's not in your patch and was before...
> - goto err_put_kn;
> + goto err_pwrseq_off;
> }
...
> +err_pwrseq_off:
> + if (port_dev->pwrseq_on)
> + pwrseq_power_off(port_dev->pwrseq);
Hmm... I would rather see pwrseq framework to provide something like
_is_powered_on().
if (pwrseq_is_powered_on())
_power_off();
...
> + if (port_dev->pwrseq_on)
> + pwrseq_power_off(port_dev->pwrseq);
Ditto.
And perhaps even _power_off_if_on() that combines the check and the call.
However it seems that is reference counted and this _power_off() calls won't
guarantee actual power off.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v13 14/22] media: i2c: add Maxim GMSL2/3 serializer framework
From: Niklas Söderlund @ 2026-06-10 14:32 UTC (permalink / raw)
To: dumitru.ceclan
Cc: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring, Greg Kroah-Hartman,
mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Martin Hecht, Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-14-9d8a4919983b@analog.com>
Hello,
Small nit which I'm not sure is correct "fixing", but running
checkpatch,
total: 0 errors, 187 warnings, 0 checks, 2335 lines checked
A quick look at the warnings suggest all are of this type,
WARNING: 'ser' may be misspelled - perhaps 'set'?
#72: FILE: drivers/media/i2c/maxim-serdes/max_ser.c:25:
+ struct max_ser *ser;
^^^
So no need to change anything, but maybe worth doing to make life easier
for the future? I'm OK with it just thought I mention it.
On 2026-06-04 17:14:01 +0300, Dumitru Ceclan via B4 Relay wrote:
> From: Cosmin Tanislav <demonsingur@gmail.com>
>
> These drivers are meant to be used as a common framework for Maxim
> GMSL2/3 serializers.
>
> This framework enables support for the following new features across
> all the chips:
> * Full Streams API support
> * .get_frame_desc()
> * I2C ATR
> * automatic GMSL link version negotiation
> * automatic stream id selection
> * automatic VC remapping
> * automatic pixel mode / tunnel mode selection
> * automatic double mode selection / data padding
> * logging of internal state and chip status registers via .log_status()
> * PHY modes
> * serializer pinctrl
> * TPG
>
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> ---
> drivers/media/i2c/maxim-serdes/Makefile | 2 +-
> drivers/media/i2c/maxim-serdes/max_ser.c | 2184 ++++++++++++++++++++++++++++++
> drivers/media/i2c/maxim-serdes/max_ser.h | 147 ++
> 3 files changed, 2332 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> index 630fbb486bab..17511cb03369 100644
> --- a/drivers/media/i2c/maxim-serdes/Makefile
> +++ b/drivers/media/i2c/maxim-serdes/Makefile
> @@ -1,3 +1,3 @@
> # SPDX-License-Identifier: GPL-2.0
> -max-serdes-objs := max_serdes.o
> +max-serdes-objs := max_serdes.o max_ser.o
> obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
> diff --git a/drivers/media/i2c/maxim-serdes/max_ser.c b/drivers/media/i2c/maxim-serdes/max_ser.c
> new file mode 100644
> index 000000000000..a193381435e6
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_ser.c
> @@ -0,0 +1,2184 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim GMSL2 Serializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c-atr.h>
> +#include <linux/i2c-mux.h>
> +#include <linux/module.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "max_ser.h"
> +#include "max_serdes.h"
> +
> +#define MAX_SER_NUM_LINKS 1
> +#define MAX_SER_NUM_PHYS 1
> +
> +struct max_ser_priv {
> + struct max_ser *ser;
> + struct device *dev;
> + struct i2c_client *client;
> +
> + struct i2c_atr *atr;
> + struct i2c_mux_core *mux;
> +
> + struct media_pad *pads;
> + struct max_serdes_source *sources;
> + u64 *streams_masks;
> + u32 double_bpps;
> +
> + struct v4l2_subdev sd;
> + struct v4l2_async_notifier nf;
> + struct v4l2_ctrl_handler ctrl_handler;
> +};
> +
> +struct max_ser_route_hw {
> + struct max_serdes_source *source;
> + struct max_ser_pipe *pipe;
> + struct v4l2_mbus_frame_desc_entry entry;
> + bool is_tpg;
> +};
> +
> +static inline struct max_ser_priv *sd_to_priv(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct max_ser_priv, sd);
> +}
> +
> +static inline struct max_ser_priv *nf_to_priv(struct v4l2_async_notifier *nf)
> +{
> + return container_of(nf, struct max_ser_priv, nf);
> +}
> +
> +static inline struct max_ser_priv *ctrl_to_priv(struct v4l2_ctrl_handler *handler)
> +{
> + return container_of(handler, struct max_ser_priv, ctrl_handler);
> +}
> +
> +static inline bool max_ser_pad_is_sink(struct max_ser *ser, u32 pad)
> +{
> + return pad < ser->ops->num_phys;
> +}
> +
> +static inline bool max_ser_pad_is_source(struct max_ser *ser, u32 pad)
> +{
> + return pad >= ser->ops->num_phys &&
> + pad < ser->ops->num_phys + MAX_SER_NUM_LINKS;
> +}
> +
> +static inline u32 max_ser_source_pad(struct max_ser *ser)
> +{
> + return ser->ops->num_phys;
> +}
> +
> +static inline bool max_ser_pad_is_tpg(struct max_ser *ser, u32 pad)
> +{
> + return pad >= ser->ops->num_phys + MAX_SER_NUM_LINKS;
> +}
> +
> +static inline unsigned int max_ser_phy_to_pad(struct max_ser *ser,
> + struct max_ser_phy *phy)
> +{
> + return phy->index;
> +}
> +
> +static inline unsigned int max_ser_num_pads(struct max_ser *ser)
> +{
> + return ser->ops->num_phys + MAX_SER_NUM_LINKS +
> + (ser->ops->set_tpg ? 1 : 0);
> +}
> +
> +static struct max_ser_phy *max_ser_pad_to_phy(struct max_ser *ser, u32 pad)
> +{
> + if (!max_ser_pad_is_sink(ser, pad))
> + return NULL;
> +
> + return &ser->phys[pad];
> +}
> +
> +static struct max_ser_pipe *
> +max_ser_find_phy_pipe(struct max_ser *ser, struct max_ser_phy *phy)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ser->ops->num_pipes; i++) {
> + struct max_ser_pipe *pipe = &ser->pipes[i];
> +
> + if (pipe->phy_id == phy->index)
> + return pipe;
> + }
> +
> + return NULL;
> +}
> +
> +static struct max_serdes_source *
> +max_ser_get_phy_source(struct max_ser_priv *priv, struct max_ser_phy *phy)
> +{
> + return &priv->sources[phy->index];
> +}
> +
> +static const struct max_serdes_tpg_entry *
> +max_ser_find_tpg_entry(struct max_ser *ser, u32 target_index,
> + u32 width, u32 height, u32 code,
> + u32 numerator, u32 denominator)
> +{
> + const struct max_serdes_tpg_entry *entry;
> + unsigned int index = 0;
> + unsigned int i;
> +
> + for (i = 0; i < ser->ops->tpg_entries.num_entries; i++) {
> + entry = &ser->ops->tpg_entries.entries[i];
> +
> + if ((width != 0 && width != entry->width) ||
> + (height != 0 && height != entry->height) ||
> + (code != 0 && code != entry->code) ||
> + (numerator != 0 && numerator != entry->interval.numerator) ||
> + (denominator != 0 && denominator != entry->interval.denominator))
> + continue;
> +
> + if (index == target_index)
> + break;
> +
> + index++;
> + }
> +
> + if (i == ser->ops->tpg_entries.num_entries)
> + return NULL;
> +
> + return &ser->ops->tpg_entries.entries[i];
> +}
> +
> +static const struct max_serdes_tpg_entry *
> +max_ser_find_state_tpg_entry(struct max_ser *ser, struct v4l2_subdev_state *state,
> + unsigned int pad)
> +{
> + struct v4l2_mbus_framefmt *fmt;
> + struct v4l2_fract *in;
> +
> + fmt = v4l2_subdev_state_get_format(state, pad, MAX_SERDES_TPG_STREAM);
> + if (!fmt)
> + return NULL;
> +
> + in = v4l2_subdev_state_get_interval(state, pad, MAX_SERDES_TPG_STREAM);
> + if (!in)
> + return NULL;
> +
> + return max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height, fmt->code,
> + in->numerator, in->denominator);
> +}
> +
> +static int max_ser_get_tpg_fd_entry_state(struct max_ser *ser,
> + struct v4l2_subdev_state *state,
> + struct v4l2_mbus_frame_desc_entry *fd_entry,
> + unsigned int pad)
> +{
> + const struct max_serdes_tpg_entry *entry;
> +
> + entry = max_ser_find_state_tpg_entry(ser, state, pad);
> + if (!entry)
> + return -EINVAL;
> +
> + fd_entry->stream = MAX_SERDES_TPG_STREAM;
> + fd_entry->flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
> + fd_entry->length = entry->width * entry->height * entry->bpp / 8;
> + fd_entry->pixelcode = entry->code;
> + fd_entry->bus.csi2.vc = 0;
> + fd_entry->bus.csi2.dt = entry->dt;
> +
> + return 0;
> +}
> +
> +static int max_ser_tpg_route_to_hw(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_route *route,
> + struct max_ser_route_hw *hw)
> +{
> + struct max_ser *ser = priv->ser;
> +
> + hw->pipe = &ser->pipes[0];
> +
> + return max_ser_get_tpg_fd_entry_state(ser, state, &hw->entry,
> + route->sink_pad);
> +}
> +
> +static int max_ser_route_to_hw(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_route *route,
> + struct max_ser_route_hw *hw)
> +{
> + struct max_ser *ser = priv->ser;
> + struct v4l2_mbus_frame_desc fd = {};
> + struct max_ser_phy *phy;
> + unsigned int i;
> + int ret;
> +
> + memset(hw, 0, sizeof(*hw));
> +
> + hw->is_tpg = max_ser_pad_is_tpg(ser, route->sink_pad);
> + if (hw->is_tpg)
> + return max_ser_tpg_route_to_hw(priv, state, route, hw);
> +
> + phy = max_ser_pad_to_phy(ser, route->sink_pad);
> + if (!phy)
> + return -ENOENT;
> +
> + hw->pipe = max_ser_find_phy_pipe(ser, phy);
> + if (!hw->pipe)
> + return -ENOENT;
> +
> + hw->source = max_ser_get_phy_source(priv, phy);
> + if (!hw->source->sd)
> + return 0;
> +
> + ret = v4l2_subdev_call(hw->source->sd, pad, get_frame_desc,
> + hw->source->pad, &fd);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < fd.num_entries; i++)
> + if (fd.entry[i].stream == route->sink_stream)
> + break;
> +
> + if (i == fd.num_entries)
> + return -ENOENT;
> +
> + hw->entry = fd.entry[i];
> +
> + return 0;
> +}
> +
> +static int max_ser_phy_set_active(struct max_ser *ser, struct max_ser_phy *phy,
> + bool active)
> +{
> + int ret;
> +
> + if (ser->ops->set_phy_active) {
> + ret = ser->ops->set_phy_active(ser, phy, active);
> + if (ret)
> + return ret;
> + }
> +
> + phy->active = active;
> +
> + return 0;
> +}
> +
> +static int max_ser_set_pipe_dts(struct max_ser_priv *priv, struct max_ser_pipe *pipe,
> + unsigned int *dts, unsigned int num_dts)
> +{
> + struct max_ser *ser = priv->ser;
> + unsigned int i;
> + int ret;
> +
> + if (!ser->ops->set_pipe_dt || !ser->ops->set_pipe_dt_en)
> + return 0;
> +
> + for (i = 0; i < num_dts; i++) {
> + ret = ser->ops->set_pipe_dt(ser, pipe, i, dts[i]);
> + if (ret)
> + return ret;
> +
> + ret = ser->ops->set_pipe_dt_en(ser, pipe, i, true);
> + if (ret)
> + return ret;
> + }
> +
> + if (num_dts == pipe->num_dts)
> + return 0;
> +
> + for (i = num_dts; i < ser->ops->num_dts_per_pipe; i++) {
> + ret = ser->ops->set_pipe_dt_en(ser, pipe, i, false);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int max_ser_set_pipe_mode(struct max_ser_priv *priv, struct max_ser_pipe *pipe,
> + struct max_ser_pipe_mode *mode)
> +{
> + struct max_ser *ser = priv->ser;
> +
> + if (mode->bpp == pipe->mode.bpp &&
> + mode->soft_bpp == pipe->mode.soft_bpp &&
> + mode->dbl8 == pipe->mode.dbl8 &&
> + mode->dbl10 == pipe->mode.dbl10 &&
> + mode->dbl12 == pipe->mode.dbl12)
> + return 0;
> +
> + if (!ser->ops->set_pipe_mode)
> + return 0;
> +
> + return ser->ops->set_pipe_mode(ser, pipe, mode);
> +}
> +
> +static int max_ser_i2c_atr_attach_addr(struct i2c_atr *atr, u32 chan_id,
> + u16 addr, u16 alias)
> +{
> + struct max_serdes_i2c_xlate xlate = {
> + .src = alias,
> + .dst = addr,
> + .en = true,
> + };
> + struct max_ser_priv *priv = i2c_atr_get_driver_data(atr);
> + struct max_ser *ser = priv->ser;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < ser->ops->num_i2c_xlates; i++)
> + if (!ser->i2c_xlates[i].en)
> + break;
> +
> + if (i == ser->ops->num_i2c_xlates) {
> + dev_err(priv->dev,
> + "Reached maximum number of I2C translations\n");
> + return -EINVAL;
> + }
> +
> + ret = ser->ops->set_i2c_xlate(ser, i, &xlate);
> + if (ret)
> + return ret;
> +
> + ser->i2c_xlates[i] = xlate;
> +
> + return 0;
> +}
> +
> +static void max_ser_i2c_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, u16 addr)
> +{
> + struct max_ser_priv *priv = i2c_atr_get_driver_data(atr);
> + struct max_ser *ser = priv->ser;
> + struct max_serdes_i2c_xlate xlate = { 0 };
> + unsigned int i;
> +
> + /* Find index of matching I2C translation. */
> + for (i = 0; i < ser->ops->num_i2c_xlates; i++)
> + if (ser->i2c_xlates[i].dst == addr)
> + break;
> +
> + if (WARN_ON(i == ser->ops->num_i2c_xlates))
> + return;
> +
> + ser->ops->set_i2c_xlate(ser, i, &xlate);
> + ser->i2c_xlates[i] = xlate;
> +}
> +
> +static const struct i2c_atr_ops max_ser_i2c_atr_ops = {
> + .attach_addr = max_ser_i2c_atr_attach_addr,
> + .detach_addr = max_ser_i2c_atr_detach_addr,
> +};
> +
> +static void max_ser_i2c_atr_deinit(struct max_ser_priv *priv)
> +{
> + /* Deleting adapters that haven't been added does no harm. */
> + i2c_atr_del_adapter(priv->atr, 0);
> +
> + i2c_atr_delete(priv->atr);
> +}
> +
> +static int max_ser_i2c_atr_init(struct max_ser_priv *priv)
> +{
> + struct i2c_atr_adap_desc desc = {
> + .chan_id = 0,
> + };
> + int ret;
> +
> + if (!i2c_check_functionality(priv->client->adapter,
> + I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
> + return -ENODEV;
> +
> + priv->atr = i2c_atr_new(priv->client->adapter, priv->dev,
> + &max_ser_i2c_atr_ops, 1, 0);
> + if (IS_ERR(priv->atr))
> + return PTR_ERR(priv->atr);
> +
> + i2c_atr_set_driver_data(priv->atr, priv);
> +
> + ret = i2c_atr_add_adapter(priv->atr, &desc);
> + if (ret) {
> + i2c_atr_delete(priv->atr);
> + priv->atr = NULL;
> + }
> +
> + return ret;
> +}
> +
> +static int max_ser_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
> +{
> + return 0;
> +}
> +
> +static void max_ser_i2c_mux_deinit(struct max_ser_priv *priv)
> +{
> + i2c_mux_del_adapters(priv->mux);
> +}
> +
> +static int max_ser_i2c_mux_init(struct max_ser_priv *priv)
> +{
> + priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
> + 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
> + max_ser_i2c_mux_select, NULL);
> + if (!priv->mux)
> + return -ENOMEM;
> +
> + return i2c_mux_add_adapter(priv->mux, 0, 0);
> +}
> +
> +static int max_ser_i2c_adapter_init(struct max_ser_priv *priv)
> +{
> + struct fwnode_handle *fwnode;
> +
> + fwnode = device_get_named_child_node(priv->dev, "i2c-gate");
> + if (fwnode) {
> + fwnode_handle_put(fwnode);
> + return max_ser_i2c_mux_init(priv);
> + }
> +
> + return max_ser_i2c_atr_init(priv);
> +}
> +
> +static void max_ser_i2c_adapter_deinit(struct max_ser_priv *priv)
> +{
> + if (device_get_named_child_node(priv->dev, "i2c-gate"))
> + max_ser_i2c_mux_deinit(priv);
> + else
> + max_ser_i2c_atr_deinit(priv);
> +}
> +
> +static int max_ser_set_tpg_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct v4l2_mbus_framefmt *fmt = &format->format;
> + struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_ser *ser = priv->ser;
> + const struct max_serdes_tpg_entry *entry;
> + struct v4l2_fract *in;
> +
> + if (format->stream != MAX_SERDES_TPG_STREAM)
> + return -EINVAL;
> +
> + entry = max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height,
> + fmt->code, 0, 0);
> + if (!entry)
> + return -EINVAL;
> +
> + in = v4l2_subdev_state_get_interval(state, format->pad, format->stream);
> + if (!in)
> + return -EINVAL;
> +
> + in->numerator = entry->interval.numerator;
> + in->denominator = entry->interval.denominator;
> +
> + return 0;
> +}
> +
> +static int max_ser_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_ser *ser = priv->ser;
> + struct v4l2_mbus_framefmt *fmt;
> + int ret;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
> + return -EBUSY;
> +
> + /* No transcoding, source and sink formats must match. */
> + if (max_ser_pad_is_source(ser, format->pad))
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + if (max_ser_pad_is_tpg(ser, format->pad)) {
> + ret = max_ser_set_tpg_fmt(sd, state, format);
> + if (ret)
> + return ret;
> + }
> +
> + if (format->format.code == ~0U)
> + format->format.code = MEDIA_BUS_FMT_UYVY8_1X16;
> +
> + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = format->format;
> +
> + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> + format->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = format->format;
> +
> + return 0;
> +}
> +
> +static int max_ser_log_status(struct v4l2_subdev *sd)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + struct v4l2_subdev_state *state;
> + unsigned int i, j;
> + int ret;
> +
> + state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
> +
> + v4l2_info(sd, "mode: %s\n", max_serdes_gmsl_mode_str(ser->mode));
> + if (ser->ops->set_tpg) {
> + const struct max_serdes_tpg_entry *entry = ser->tpg_entry;
> +
> + if (entry) {
> + v4l2_info(sd, "tpg: %ux%u@%u/%u, code: %u, dt: %u, bpp: %u\n",
> + entry->width, entry->height,
> + entry->interval.numerator,
> + entry->interval.denominator,
> + entry->code, entry->dt, entry->bpp);
> + } else {
> + v4l2_info(sd, "tpg: disabled\n");
> + }
> + }
> + if (ser->ops->log_status) {
> + ret = ser->ops->log_status(ser);
> + if (ret)
> + return ret;
> + }
> + v4l2_info(sd, "i2c_xlates:\n");
> + for (i = 0; i < ser->ops->num_i2c_xlates; i++) {
> + v4l2_info(sd, "\ten: %u, src: 0x%02x dst: 0x%02x\n",
> + ser->i2c_xlates[i].en, ser->i2c_xlates[i].src,
> + ser->i2c_xlates[i].dst);
> + if (!ser->i2c_xlates[i].en)
> + break;
> + }
> + v4l2_info(sd, "\n");
> + if (ser->ops->set_vc_remap) {
> + v4l2_info(sd, "vc_remaps: %u\n", ser->num_vc_remaps);
> + for (j = 0; j < ser->num_vc_remaps; j++) {
> + v4l2_info(sd, "\tvc_remap: src: %u, dst: %u\n",
> + ser->vc_remaps[j].src, ser->vc_remaps[j].dst);
> + }
> + }
> + v4l2_info(sd, "\n");
> +
> + for (i = 0; i < ser->ops->num_pipes; i++) {
> + struct max_ser_pipe *pipe = &ser->pipes[i];
> +
> + v4l2_info(sd, "pipe: %u\n", pipe->index);
> + v4l2_info(sd, "\tenabled: %u\n", pipe->enabled);
> +
> + if (!pipe->enabled) {
> + v4l2_info(sd, "\n");
> + continue;
> + }
> +
> + v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
> + v4l2_info(sd, "\tstream_id: %u\n", pipe->stream_id);
> + if (ser->ops->set_pipe_phy)
> + v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
> + if (ser->ops->set_pipe_dt) {
> + v4l2_info(sd, "\tdts: %u\n", pipe->num_dts);
> + for (j = 0; j < pipe->num_dts; j++)
> + v4l2_info(sd, "\t\tdt: 0x%02x\n", pipe->dts[j]);
> + }
> + if (ser->ops->set_pipe_vcs)
> + v4l2_info(sd, "\tvcs: 0x%08x\n", pipe->vcs);
> + if (ser->ops->set_pipe_mode) {
> + v4l2_info(sd, "\tdbl8: %u\n", pipe->mode.dbl8);
> + v4l2_info(sd, "\tdbl10: %u\n", pipe->mode.dbl10);
> + v4l2_info(sd, "\tdbl12: %u\n", pipe->mode.dbl12);
> + v4l2_info(sd, "\tsoft_bpp: %u\n", pipe->mode.soft_bpp);
> + v4l2_info(sd, "\tbpp: %u\n", pipe->mode.bpp);
> + }
> + if (ser->ops->log_pipe_status) {
> + ret = ser->ops->log_pipe_status(ser, pipe);
> + if (ret)
> + goto out_unlock;
> + }
> + v4l2_info(sd, "\n");
> + }
> +
> + for (i = 0; i < ser->ops->num_phys; i++) {
> + struct max_ser_phy *phy = &ser->phys[i];
> +
> + v4l2_info(sd, "phy: %u\n", phy->index);
> + v4l2_info(sd, "\tenabled: %u\n", phy->enabled);
> +
> + if (!phy->enabled) {
> + v4l2_info(sd, "\n");
> + continue;
> + }
> +
> + v4l2_info(sd, "\tactive: %u\n", phy->active);
> + v4l2_info(sd, "\tnum_data_lanes: %u\n", phy->mipi.num_data_lanes);
> + v4l2_info(sd, "\tclock_lane: %u\n", phy->mipi.clock_lane);
> + v4l2_info(sd, "\tnoncontinuous_clock: %u\n",
> + !!(phy->mipi.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK));
> + if (ser->ops->log_phy_status) {
> + ret = ser->ops->log_phy_status(ser, phy);
> + if (ret)
> + goto out_unlock;
> + }
> + v4l2_info(sd, "\n");
> + }
> +
> + ret = 0;
> +
> +out_unlock:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int max_ser_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct max_ser_priv *priv = ctrl_to_priv(ctrl->handler);
> + struct max_ser *ser = priv->ser;
> +
> + switch (ctrl->id) {
> + case V4L2_CID_TEST_PATTERN:
> + ser->tpg_pattern = ctrl->val;
> + return 0;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int max_ser_enum_frame_interval(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_interval_enum *fie)
> +{
> + struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_ser *ser = priv->ser;
> + const struct max_serdes_tpg_entry *entry;
> +
> + if (!max_ser_pad_is_tpg(ser, fie->pad) ||
> + fie->stream != MAX_SERDES_TPG_STREAM)
> + return -ENOTTY;
> +
> + entry = max_ser_find_tpg_entry(ser, fie->index, fie->width, fie->height,
> + fie->code, fie->interval.denominator,
> + fie->interval.numerator);
> + if (!entry)
> + return -EINVAL;
> +
> + fie->interval.numerator = entry->interval.numerator;
> + fie->interval.denominator = entry->interval.denominator;
> +
> + return 0;
> +}
> +
> +static int max_ser_set_frame_interval(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_interval *fi)
> +{
> + struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_ser *ser = priv->ser;
> + const struct max_serdes_tpg_entry *entry;
> + struct v4l2_mbus_framefmt *fmt;
> + struct v4l2_fract *in;
> +
> + if (!max_ser_pad_is_tpg(ser, fi->pad) ||
> + fi->stream != MAX_SERDES_TPG_STREAM)
> + return -ENOTTY;
> +
> + if (fi->which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
> + return -EBUSY;
> +
> + fmt = v4l2_subdev_state_get_format(state, fi->pad, fi->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + entry = max_ser_find_tpg_entry(ser, 0, fmt->width, fmt->height,
> + fmt->code, fi->interval.denominator,
> + fi->interval.numerator);
> + if (!entry)
> + return -EINVAL;
> +
> + in = v4l2_subdev_state_get_interval(state, fi->pad, fi->stream);
> + if (!in)
> + return -EINVAL;
> +
> + in->numerator = fi->interval.numerator;
> + in->denominator = fi->interval.denominator;
> +
> + return 0;
> +}
> +
> +static int max_ser_get_frame_interval(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_interval *fi)
> +{
> + struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_ser *ser = priv->ser;
> +
> + if (!max_ser_pad_is_tpg(ser, fi->pad) ||
> + fi->stream != MAX_SERDES_TPG_STREAM)
> + return -ENOTTY;
> +
> + return v4l2_subdev_get_frame_interval(sd, state, fi);
> +}
> +
> +static int max_ser_get_frame_desc_state(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_mbus_frame_desc *fd,
> + unsigned int pad)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + struct v4l2_subdev_route *route;
> + int ret;
> +
> + if (!max_ser_pad_is_source(ser, pad))
> + return -ENOENT;
> +
> + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_ser_route_hw hw;
> +
> + if (pad != route->source_pad)
> + continue;
> +
> + ret = max_ser_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + hw.entry.stream = route->source_stream;
> +
> + fd->entry[fd->num_entries++] = hw.entry;
> + }
> +
> + return 0;
> +}
> +
> +static int max_ser_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
> + struct v4l2_mbus_frame_desc *fd)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct v4l2_subdev_state *state;
> + int ret;
> +
> + state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
> +
> + ret = max_ser_get_frame_desc_state(sd, state, fd, pad);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int max_ser_set_tpg_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + const struct max_serdes_tpg_entry *entry;
> + struct v4l2_mbus_framefmt fmt = { 0 };
> + int ret;
> +
> + ret = max_serdes_validate_tpg_routing(routing);
> + if (ret)
> + return ret;
> +
> + entry = &ser->ops->tpg_entries.entries[0];
> +
> + fmt.width = entry->width;
> + fmt.height = entry->height;
> + fmt.code = entry->code;
> +
> + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &fmt);
> +}
> +
> +static int __max_ser_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + struct v4l2_subdev_route *route;
> + bool is_tpg = false;
> + int ret;
> +
> + ret = v4l2_subdev_routing_validate(sd, routing,
> + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
> + V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX);
> + if (ret)
> + return ret;
> +
> + for_each_active_route(routing, route) {
> + if (max_ser_pad_is_tpg(ser, route->sink_pad)) {
> + is_tpg = true;
> + break;
> + }
> + }
> +
> + if (is_tpg)
> + return max_ser_set_tpg_routing(sd, state, routing);
> +
> + static const struct v4l2_mbus_framefmt format = {
> + .code = MEDIA_BUS_FMT_Y8_1X8,
> + .field = V4L2_FIELD_NONE,
> + .width = 640,
> + .height = 480,
> + };
> +
> + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +}
> +
> +static int max_ser_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> +
> + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && ser->active)
> + return -EBUSY;
> +
> + return __max_ser_set_routing(sd, state, routing);
> +}
> +
> +static int max_ser_get_pipe_vcs_dts(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state,
> + struct max_ser_pipe *pipe,
> + unsigned int *vcs,
> + unsigned int *dts, unsigned int *num_dts,
> + u64 *streams_masks)
> +{
> + struct v4l2_subdev_route *route;
> + struct max_ser *ser = priv->ser;
> + unsigned int i;
> + int ret;
> +
> + *vcs = 0;
> + *num_dts = 0;
> +
> + if (ser->mode != MAX_SERDES_GMSL_PIXEL_MODE)
> + return 0;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_ser_route_hw hw;
> + unsigned int vc, dt;
> +
> + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> + continue;
> +
> + ret = max_ser_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (hw.pipe != pipe)
> + continue;
> +
> + vc = hw.entry.bus.csi2.vc;
> + dt = hw.entry.bus.csi2.dt;
> +
> + if (vc >= MAX_SERDES_VC_ID_NUM)
> + return -E2BIG;
> +
> + *vcs |= BIT(vc);
> +
> + /* Skip already added DT. */
> + for (i = 0; i < *num_dts; i++)
> + if (dts[i] == dt)
> + break;
> +
> + if (i < *num_dts)
> + continue;
> +
> + if (*num_dts >= ser->ops->num_dts_per_pipe)
> + return -EINVAL;
> +
> + dts[*num_dts] = dt;
> + (*num_dts)++;
> + }
> +
> + /*
> + * Hardware cannot distinguish between different pairs of VC and DT,
> + * issue a warning.
> + */
> + for_each_active_route(&state->routing, route) {
> + struct max_ser_route_hw hw;
> + unsigned int vc, dt;
> +
> + /*
> + * Skip enabled streams, we only want to check for leaks
> + * among the disabled streams.
> + */
> + if ((BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> + continue;
> +
> + ret = max_ser_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (hw.pipe != pipe)
> + continue;
> +
> + vc = hw.entry.bus.csi2.vc;
> + dt = hw.entry.bus.csi2.dt;
> +
> + if (vc >= MAX_SERDES_VC_ID_NUM)
> + return -E2BIG;
> +
> + if (!(*vcs & BIT(vc)))
> + continue;
> +
> + for (i = 0; i < *num_dts; i++)
> + if (dts[i] == dt)
> + break;
> +
> + if (i == *num_dts)
> + continue;
> +
> + dev_warn(priv->dev, "Leaked disabled stream %u:%u with VC: %u, DT: %u",
> + route->source_pad, route->source_stream, vc, dt);
> + }
> +
> + return 0;
> +}
> +
> +static int max_ser_get_pipe_mode(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state,
> + struct max_ser_pipe *pipe,
> + struct max_ser_pipe_mode *mode)
> +{
> + struct v4l2_subdev_route *route;
> + struct max_ser *ser = priv->ser;
> + bool force_set_bpp = false;
> + unsigned int doubled_bpp = 0;
> + unsigned int min_bpp;
> + unsigned int max_bpp;
> + u32 bpps = 0;
> + int ret;
> +
> + if (ser->mode != MAX_SERDES_GMSL_PIXEL_MODE)
> + return 0;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_ser_route_hw hw;
> + unsigned int bpp;
> +
> + ret = max_ser_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (hw.pipe != pipe)
> + continue;
> +
> + if (hw.is_tpg)
> + force_set_bpp = true;
> +
> + ret = max_serdes_get_fd_bpp(&hw.entry, &bpp);
> + if (ret)
> + return ret;
> +
> + bpps |= BIT(bpp);
> + }
> +
> + ret = max_serdes_process_bpps(priv->dev, bpps, priv->double_bpps, &doubled_bpp);
> + if (ret)
> + return ret;
> +
> + if (doubled_bpp == 8)
> + mode->dbl8 = true;
> + else if (doubled_bpp == 10)
> + mode->dbl10 = true;
> + else if (doubled_bpp == 12)
> + mode->dbl12 = true;
> +
> + if (doubled_bpp) {
> + bpps &= ~BIT(doubled_bpp);
> + bpps |= BIT(doubled_bpp * 2);
> + }
> +
> + if (!bpps)
> + return 0;
> +
> + min_bpp = __ffs(bpps);
> + max_bpp = __fls(bpps);
> +
> + if (doubled_bpp)
> + mode->soft_bpp = min_bpp;
> +
> + if (min_bpp != max_bpp || force_set_bpp)
> + mode->bpp = max_bpp;
> +
> + return 0;
> +}
> +
> +static int max_ser_update_pipe_enable(struct max_ser_priv *priv,
> + struct max_ser_pipe *pipe,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_ser *ser = priv->ser;
> + struct v4l2_subdev_route *route;
> + bool enable = false;
> + int ret;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_ser_route_hw hw;
> +
> + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> + continue;
> +
> + ret = max_ser_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (hw.pipe != pipe)
> + continue;
> +
> + enable = true;
> + break;
> + }
> +
> + if (enable == pipe->enabled)
> + return 0;
> +
> + ret = ser->ops->set_pipe_enable(ser, pipe, enable);
> + if (ret)
> + return ret;
> +
> + pipe->enabled = enable;
> +
> + return 0;
> +}
> +
> +static int max_ser_update_pipe(struct max_ser_priv *priv,
> + struct max_ser_pipe *pipe,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_ser *ser = priv->ser;
> + struct max_ser_pipe_mode mode = { 0 };
> + unsigned int num_dts;
> + unsigned int *dts;
> + unsigned int vcs;
> + int ret;
> +
> + if (!ser->ops->num_dts_per_pipe)
> + return 0;
> +
> + dts = devm_kcalloc(priv->dev, ser->ops->num_dts_per_pipe, sizeof(*dts),
> + GFP_KERNEL);
> + if (!dts)
> + return -ENOMEM;
> +
> + ret = max_ser_get_pipe_vcs_dts(priv, state, pipe, &vcs, dts, &num_dts,
> + streams_masks);
> + if (ret)
> + goto err_free_dts;
> +
> + ret = max_ser_get_pipe_mode(priv, state, pipe, &mode);
> + if (ret)
> + goto err_free_dts;
> +
> + if (ser->ops->set_pipe_vcs) {
> + ret = ser->ops->set_pipe_vcs(ser, pipe, vcs);
> + if (ret)
> + goto err_free_dts;
> + }
> +
> + ret = max_ser_set_pipe_mode(priv, pipe, &mode);
> + if (ret)
> + goto err_revert_vcs;
> +
> + ret = max_ser_set_pipe_dts(priv, pipe, dts, num_dts);
> + if (ret)
> + goto err_revert_mode;
> +
> + pipe->vcs = vcs;
> + pipe->mode = mode;
> +
> + if (pipe->dts)
> + devm_kfree(priv->dev, pipe->dts);
> +
> + pipe->dts = dts;
> + pipe->num_dts = num_dts;
> +
> + return 0;
> +
> +err_revert_mode:
> + max_ser_set_pipe_mode(priv, pipe, &pipe->mode);
> +
> +err_revert_vcs:
> + if (ser->ops->set_pipe_vcs)
> + ser->ops->set_pipe_vcs(ser, pipe, pipe->vcs);
> +
> +err_free_dts:
> + devm_kfree(priv->dev, dts);
> +
> + return ret;
> +}
> +
> +static int max_ser_update_phy(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state,
> + struct max_ser_phy *phy, u64 *streams_masks)
> +{
> + struct max_ser *ser = priv->ser;
> + u32 pad = max_ser_phy_to_pad(ser, phy);
> + bool enable_changed = !streams_masks[pad] != !priv->streams_masks[pad];
> + bool enable = !!streams_masks[pad];
> + struct max_ser_pipe *pipe;
> + int ret;
> +
> + pipe = max_ser_find_phy_pipe(ser, phy);
> + if (!pipe)
> + return -ENOENT;
> +
> + if (!enable && enable_changed) {
> + ret = max_ser_phy_set_active(ser, phy, enable);
> + if (ret)
> + return ret;
> + }
> +
> + ret = max_ser_update_pipe(priv, pipe, state, streams_masks);
> + if (ret)
> + goto err_revert_phy_disable;
> +
> + ret = max_ser_update_pipe_enable(priv, pipe, state, streams_masks);
> + if (ret)
> + goto err_revert_pipe_update;
> +
> + if (enable && enable_changed) {
> + ret = max_ser_phy_set_active(ser, phy, enable);
> + if (ret)
> + goto err_revert_update_pipe_enable;
> + }
> +
> + return 0;
> +
> +err_revert_update_pipe_enable:
> + max_ser_update_pipe_enable(priv, pipe, state, priv->streams_masks);
> +
> +err_revert_pipe_update:
> + max_ser_update_pipe(priv, pipe, state, priv->streams_masks);
> +
> +err_revert_phy_disable:
> + if (!enable && enable_changed)
> + max_ser_phy_set_active(ser, phy, !enable);
> +
> + return ret;
> +}
> +
> +static int max_ser_update_phys(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_ser *ser = priv->ser;
> + unsigned int failed_update_phy_id = ser->ops->num_phys;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < ser->ops->num_phys; i++) {
> + struct max_ser_phy *phy = &ser->phys[i];
> +
> + ret = max_ser_update_phy(priv, state, phy, streams_masks);
> + if (ret) {
> + failed_update_phy_id = i;
> + goto err;
> + }
> + }
> +
> + return 0;
> +
> +err:
> + for (i = 0; i < failed_update_phy_id; i++) {
> + struct max_ser_phy *phy = &ser->phys[i];
> +
> + max_ser_update_phy(priv, state, phy, priv->streams_masks);
> + }
> +
> + return ret;
> +}
> +
> +static int max_ser_enable_disable_streams(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 updated_streams_mask,
> + bool enable)
> +{
> + struct max_ser *ser = priv->ser;
> +
> + return max_serdes_xlate_enable_disable_streams(priv->sources, 0, state,
> + pad, updated_streams_mask, 0,
> + ser->ops->num_phys, enable);
> +}
> +
> +static bool max_ser_is_tpg_routed(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route *route;
> + int ret;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_ser_route_hw hw;
> +
> + ret = max_ser_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return false;
> +
> + if (hw.is_tpg)
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static int max_ser_update_tpg(struct max_ser_priv *priv,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + const struct max_serdes_tpg_entry *entry = NULL;
> + struct max_ser *ser = priv->ser;
> + struct v4l2_subdev_route *route;
> + int ret;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_ser_route_hw hw;
> +
> + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> + continue;
> +
> + ret = max_ser_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (!hw.is_tpg)
> + continue;
> +
> + entry = max_ser_find_state_tpg_entry(ser, state, route->sink_pad);
> + break;
> + }
> +
> + if (entry == ser->tpg_entry)
> + return 0;
> +
> + ret = ser->ops->set_tpg(ser, entry);
> + if (ret)
> + return ret;
> +
> + ser->tpg_entry = entry;
> +
> + return 0;
> +}
> +
> +static int max_ser_update_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 updated_streams_mask, bool enable)
> +{
> + struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_ser *ser = priv->ser;
> + unsigned int num_pads = max_ser_num_pads(ser);
> + u64 *streams_masks;
> + int ret;
> +
> + ret = max_serdes_get_streams_masks(priv->dev, state, pad, updated_streams_mask,
> + num_pads, priv->streams_masks, &streams_masks,
> + enable);
> + if (ret)
> + return ret;
> +
> + if (!enable) {
> + ret = max_ser_enable_disable_streams(priv, state, pad,
> + updated_streams_mask, enable);
> + if (ret)
> + goto err_free_streams_masks;
> + }
> +
> + ret = max_ser_update_tpg(priv, state, streams_masks);
> + if (ret)
> + goto err_revert_streams_disable;
> +
> + ret = max_ser_update_phys(priv, state, streams_masks);
> + if (ret)
> + goto err_revert_update_tpg;
> +
> + if (enable) {
> + ret = max_ser_enable_disable_streams(priv, state, pad,
> + updated_streams_mask, enable);
> + if (ret)
> + goto err_revert_phys_update;
> + }
> +
> + devm_kfree(priv->dev, priv->streams_masks);
> + priv->streams_masks = streams_masks;
> + ser->active = !!streams_masks[pad];
> +
> + return 0;
> +
> +err_revert_phys_update:
> + max_ser_update_phys(priv, state, priv->streams_masks);
> +
> +err_revert_update_tpg:
> + max_ser_update_tpg(priv, state, priv->streams_masks);
> +
> +err_revert_streams_disable:
> + if (!enable)
> + max_ser_enable_disable_streams(priv, state, pad,
> + updated_streams_mask, !enable);
> +
> +err_free_streams_masks:
> + devm_kfree(priv->dev, streams_masks);
> +
> + return ret;
> +}
> +
> +static int max_ser_enable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 streams_mask)
> +{
> + return max_ser_update_streams(sd, state, pad, streams_mask, true);
> +}
> +
> +static int max_ser_disable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 streams_mask)
> +{
> + return max_ser_update_streams(sd, state, pad, streams_mask, false);
> +}
> +
> +static int max_ser_init_state(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route routes[MAX_SER_NUM_PHYS] = { 0 };
> + struct v4l2_subdev_krouting routing = {
> + .routes = routes,
> + };
> + struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_ser *ser = priv->ser;
> + unsigned int stream = 0;
> + unsigned int i;
> +
> + for (i = 0; i < ser->ops->num_phys; i++) {
> + struct max_ser_phy *phy = &ser->phys[i];
> +
> + if (!phy->enabled)
> + continue;
> +
> + routing.routes[routing.num_routes++] = (struct v4l2_subdev_route) {
> + .sink_pad = max_ser_phy_to_pad(ser, phy),
> + .sink_stream = 0,
> + .source_pad = max_ser_source_pad(ser),
> + .source_stream = stream,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + };
> + stream++;
> +
> + /*
> + * The Streams API is an experimental feature.
> + * If multiple routes are provided here, userspace will not be
> + * able to configure them unless the Streams API is enabled.
> + * Provide a single route until it is enabled.
> + */
> + break;
> + }
> +
> + return __max_ser_set_routing(sd, state, &routing);
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int max_ser_g_register(struct v4l2_subdev *sd,
> + struct v4l2_dbg_register *reg)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + unsigned int val;
> + int ret;
> +
> + ret = ser->ops->reg_read(ser, reg->reg, &val);
> + if (ret)
> + return ret;
> +
> + reg->val = val;
> + reg->size = 1;
> +
> + return 0;
> +}
> +
> +static int max_ser_s_register(struct v4l2_subdev *sd,
> + const struct v4l2_dbg_register *reg)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> +
> + return ser->ops->reg_write(ser, reg->reg, reg->val);
> +}
> +#endif
> +
> +static const struct v4l2_subdev_core_ops max_ser_core_ops = {
> + .log_status = max_ser_log_status,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + .g_register = max_ser_g_register,
> + .s_register = max_ser_s_register,
> +#endif
> +};
> +
> +static const struct v4l2_ctrl_ops max_ser_ctrl_ops = {
> + .s_ctrl = max_ser_s_ctrl,
> +};
> +
> +static const struct v4l2_subdev_pad_ops max_ser_pad_ops = {
> + .enable_streams = max_ser_enable_streams,
> + .disable_streams = max_ser_disable_streams,
> +
> + .set_routing = max_ser_set_routing,
> + .get_frame_desc = max_ser_get_frame_desc,
> +
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = max_ser_set_fmt,
> +
> + .enum_frame_interval = max_ser_enum_frame_interval,
> + .get_frame_interval = max_ser_get_frame_interval,
> + .set_frame_interval = max_ser_set_frame_interval,
> +};
> +
> +static const struct v4l2_subdev_ops max_ser_subdev_ops = {
> + .core = &max_ser_core_ops,
> + .pad = &max_ser_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops max_ser_internal_ops = {
> + .init_state = &max_ser_init_state,
> +};
> +
> +static const struct media_entity_operations max_ser_media_ops = {
> + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> + .has_pad_interdep = v4l2_subdev_has_pad_interdep,
> + .link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int max_ser_init(struct max_ser_priv *priv)
> +{
> + struct max_ser *ser = priv->ser;
> + unsigned int i;
> + int ret;
> +
> + if (ser->ops->init) {
> + ret = ser->ops->init(ser);
> + if (ret)
> + return ret;
> + }
> +
> + if (ser->ops->set_tunnel_enable) {
> + ret = ser->ops->set_tunnel_enable(ser, false);
> + if (ret)
> + return ret;
> + }
> +
> + for (i = 0; i < ser->ops->num_phys; i++) {
> + struct max_ser_phy *phy = &ser->phys[i];
> +
> + if (phy->enabled) {
> + ret = ser->ops->init_phy(ser, phy);
> + if (ret)
> + return ret;
> + }
> +
> + if (ser->ops->set_phy_active) {
> + ret = ser->ops->set_phy_active(ser, phy, false);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + for (i = 0; i < ser->ops->num_pipes; i++) {
> + struct max_ser_pipe *pipe = &ser->pipes[i];
> + struct max_ser_phy *phy = &ser->phys[pipe->phy_id];
> +
> + ret = ser->ops->set_pipe_enable(ser, pipe, false);
> + if (ret)
> + return ret;
> +
> + if (ser->ops->set_pipe_stream_id) {
> + ret = ser->ops->set_pipe_stream_id(ser, pipe, pipe->stream_id);
> + if (ret)
> + return ret;
> + }
> +
> + if (ser->ops->set_pipe_phy) {
> + ret = ser->ops->set_pipe_phy(ser, pipe, phy);
> + if (ret)
> + return ret;
> + }
> +
> + if (ser->ops->set_pipe_vcs) {
> + ret = ser->ops->set_pipe_vcs(ser, pipe, 0);
> + if (ret)
> + return ret;
> + }
> +
> + if (ser->ops->set_pipe_mode) {
> + ret = ser->ops->set_pipe_mode(ser, pipe, &pipe->mode);
> + if (ret)
> + return ret;
> + }
> +
> + ret = max_ser_set_pipe_dts(priv, pipe, NULL, 0);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int max_ser_notify_bound(struct v4l2_async_notifier *nf,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_connection *base_asc)
> +{
> + struct max_ser_priv *priv = nf_to_priv(nf);
> + struct max_serdes_asc *asc = asc_to_max(base_asc);
> + struct max_serdes_source *source = asc->source;
> + u32 pad = source->index;
> + int ret;
> +
> + ret = media_entity_get_fwnode_pad(&subdev->entity,
> + source->ep_fwnode,
> + MEDIA_PAD_FL_SOURCE);
> + if (ret < 0) {
> + dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name);
> + return ret;
> + }
> +
> + source->sd = subdev;
> + source->pad = ret;
> +
> + ret = media_create_pad_link(&source->sd->entity, source->pad,
> + &priv->sd.entity, pad,
> + MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);
> + if (ret) {
> + dev_err(priv->dev, "Unable to link %s:%u -> %s:%u\n",
> + source->sd->name, source->pad, priv->sd.name, pad);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void max_ser_notify_unbind(struct v4l2_async_notifier *nf,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_connection *base_asc)
> +{
> + struct max_serdes_asc *asc = asc_to_max(base_asc);
> + struct max_serdes_source *source = asc->source;
> +
> + source->sd = NULL;
> +}
> +
> +static const struct v4l2_async_notifier_operations max_ser_notify_ops = {
> + .bound = max_ser_notify_bound,
> + .unbind = max_ser_notify_unbind,
> +};
> +
> +static int max_ser_v4l2_notifier_register(struct max_ser_priv *priv)
> +{
> + struct max_ser *ser = priv->ser;
> + unsigned int i;
> + int ret;
> +
> + v4l2_async_subdev_nf_init(&priv->nf, &priv->sd);
> +
> + for (i = 0; i < ser->ops->num_phys; i++) {
> + struct max_ser_phy *phy = &ser->phys[i];
> + struct max_serdes_source *source;
> + struct max_serdes_asc *asc;
> +
> + source = max_ser_get_phy_source(priv, phy);
> + if (!source->ep_fwnode)
> + continue;
> +
> + asc = v4l2_async_nf_add_fwnode(&priv->nf, source->ep_fwnode,
> + struct max_serdes_asc);
> + if (IS_ERR(asc)) {
> + dev_err(priv->dev,
> + "Failed to add subdev for source %u: %pe", i,
> + asc);
> +
> + v4l2_async_nf_cleanup(&priv->nf);
> +
> + return PTR_ERR(asc);
> + }
> +
> + asc->source = source;
> + }
> +
> + priv->nf.ops = &max_ser_notify_ops;
> +
> + ret = v4l2_async_nf_register(&priv->nf);
> + if (ret) {
> + dev_err(priv->dev, "Failed to register subdev notifier");
> + v4l2_async_nf_cleanup(&priv->nf);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void max_ser_v4l2_notifier_unregister(struct max_ser_priv *priv)
> +{
> + v4l2_async_nf_unregister(&priv->nf);
> + v4l2_async_nf_cleanup(&priv->nf);
> +}
> +
> +static int max_ser_v4l2_register(struct max_ser_priv *priv)
> +{
> + struct v4l2_subdev *sd = &priv->sd;
> + struct max_ser *ser = priv->ser;
> + void *data = i2c_get_clientdata(priv->client);
> + unsigned int num_pads = max_ser_num_pads(ser);
> + unsigned int i;
> + int ret;
> +
> + v4l2_i2c_subdev_init(sd, priv->client, &max_ser_subdev_ops);
> + i2c_set_clientdata(priv->client, data);
> + sd->internal_ops = &max_ser_internal_ops;
> + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + sd->entity.ops = &max_ser_media_ops;
> + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +
> + priv->pads = devm_kcalloc(priv->dev, num_pads, sizeof(*priv->pads), GFP_KERNEL);
> + if (!priv->pads)
> + return -ENOMEM;
> +
> + for (i = 0; i < num_pads; i++) {
> + if (max_ser_pad_is_sink(ser, i))
> + priv->pads[i].flags = MEDIA_PAD_FL_SINK;
> + else if (max_ser_pad_is_source(ser, i))
> + priv->pads[i].flags = MEDIA_PAD_FL_SOURCE;
> + else if (max_ser_pad_is_tpg(ser, i))
> + priv->pads[i].flags = MEDIA_PAD_FL_SINK |
> + MEDIA_PAD_FL_INTERNAL;
> + else
> + return -EINVAL;
> + }
> +
> + v4l2_set_subdevdata(sd, priv);
> +
> + if (ser->ops->tpg_patterns) {
> + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
> + priv->sd.ctrl_handler = &priv->ctrl_handler;
> +
> + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
> + &max_ser_ctrl_ops,
> + V4L2_CID_TEST_PATTERN,
> + MAX_SERDES_TPG_PATTERN_MAX,
> + ~ser->ops->tpg_patterns,
> + __ffs(ser->ops->tpg_patterns),
> + max_serdes_tpg_patterns);
> + if (priv->ctrl_handler.error) {
> + ret = priv->ctrl_handler.error;
> + goto err_free_ctrl;
> + }
> + }
> +
> + ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads);
> + if (ret)
> + goto err_free_ctrl;
> +
> + ret = max_ser_v4l2_notifier_register(priv);
> + if (ret)
> + goto err_media_entity_cleanup;
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret)
> + goto err_nf_cleanup;
> +
> + ret = v4l2_async_register_subdev(sd);
> + if (ret)
> + goto err_sd_cleanup;
> +
> + return 0;
> +
> +err_sd_cleanup:
> + v4l2_subdev_cleanup(sd);
> +err_nf_cleanup:
> + max_ser_v4l2_notifier_unregister(priv);
> +err_media_entity_cleanup:
> + media_entity_cleanup(&sd->entity);
> +err_free_ctrl:
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +
> + return ret;
> +}
> +
> +static void max_ser_v4l2_unregister(struct max_ser_priv *priv)
> +{
> + struct v4l2_subdev *sd = &priv->sd;
> +
> + max_ser_v4l2_notifier_unregister(priv);
> + v4l2_async_unregister_subdev(sd);
> + v4l2_subdev_cleanup(sd);
> + media_entity_cleanup(&sd->entity);
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +}
> +
> +static int max_ser_parse_sink_dt_endpoint(struct max_ser_priv *priv,
> + struct max_ser_phy *phy,
> + struct max_serdes_source *source,
> + struct fwnode_handle *fwnode)
> +{
> + struct max_ser *ser = priv->ser;
> + u32 pad = max_ser_phy_to_pad(ser, phy);
> + struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> + struct fwnode_handle *ep;
> + int ret;
> +
> + ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0);
> + if (!ep)
> + return 0;
> +
> + source->ep_fwnode = fwnode_graph_get_remote_endpoint(ep);
> + if (!source->ep_fwnode) {
> + fwnode_handle_put(ep);
> + dev_err(priv->dev,
> + "Failed to get remote endpoint on port %u\n", pad);
> + return -EINVAL;
> + }
> +
> + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep);
> + fwnode_handle_put(ep);
> + if (ret) {
> + dev_err(priv->dev, "Could not parse endpoint on port %u\n", pad);
> + return ret;
> + }
> +
> + phy->mipi = v4l2_ep.bus.mipi_csi2;
> + phy->enabled = true;
> +
> + return 0;
> +}
> +
> +static int max_ser_find_phys_config(struct max_ser_priv *priv)
> +{
> + struct max_ser *ser = priv->ser;
> + const struct max_serdes_phys_configs *configs = &ser->ops->phys_configs;
> + struct max_ser_phy *phy;
> + unsigned int i, j;
> +
> + if (!configs->num_configs)
> + return 0;
> +
> + for (i = 0; i < configs->num_configs; i++) {
> + const struct max_serdes_phys_config *config = &configs->configs[i];
> + bool matching = true;
> +
> + for (j = 0; j < ser->ops->num_phys; j++) {
> + phy = &ser->phys[j];
> +
> + if (!phy->enabled)
> + continue;
> +
> + if (phy->mipi.num_data_lanes <= config->lanes[j])
> + continue;
> +
> + matching = false;
> +
> + break;
> + }
> +
> + if (matching)
> + break;
> + }
> +
> + if (i == configs->num_configs) {
> + dev_err(priv->dev, "Invalid lane configuration\n");
> + return -EINVAL;
> + }
> +
> + ser->phys_config = i;
> +
> + return 0;
> +}
> +
> +static int max_ser_parse_dt(struct max_ser_priv *priv)
> +{
> + struct fwnode_handle *fwnode = dev_fwnode(priv->dev);
> + struct max_ser *ser = priv->ser;
> + struct max_ser_pipe *pipe;
> + struct max_ser_phy *phy;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < ser->ops->num_phys; i++) {
> + phy = &ser->phys[i];
> + phy->index = i;
> + }
> +
> + for (i = 0; i < ser->ops->num_pipes; i++) {
> + pipe = &ser->pipes[i];
> + pipe->index = i;
> + pipe->phy_id = i % ser->ops->num_phys;
> + pipe->stream_id = i % MAX_SERDES_STREAMS_NUM;
> + }
> +
> + for (i = 0; i < ser->ops->num_phys; i++) {
> + struct max_ser_phy *phy = &ser->phys[i];
> + struct max_serdes_source *source;
> +
> + source = max_ser_get_phy_source(priv, phy);
> + source->index = i;
> +
> + ret = max_ser_parse_sink_dt_endpoint(priv, phy, source, fwnode);
> + if (ret)
> + return ret;
> + }
> +
> + return max_ser_find_phys_config(priv);
> +}
> +
> +static int max_ser_allocate(struct max_ser_priv *priv)
> +{
> + struct max_ser *ser = priv->ser;
> + unsigned int num_pads = max_ser_num_pads(ser);
> +
> + ser->phys = devm_kcalloc(priv->dev, ser->ops->num_phys,
> + sizeof(*ser->phys), GFP_KERNEL);
> + if (!ser->phys)
> + return -ENOMEM;
> +
> + ser->pipes = devm_kcalloc(priv->dev, ser->ops->num_pipes,
> + sizeof(*ser->pipes), GFP_KERNEL);
> + if (!ser->pipes)
> + return -ENOMEM;
> +
> + ser->vc_remaps = devm_kcalloc(priv->dev, ser->ops->num_vc_remaps,
> + sizeof(*ser->vc_remaps), GFP_KERNEL);
> + if (!ser->vc_remaps)
> + return -ENOMEM;
> +
> + ser->i2c_xlates = devm_kcalloc(priv->dev, ser->ops->num_i2c_xlates,
> + sizeof(*ser->i2c_xlates), GFP_KERNEL);
> + if (!ser->i2c_xlates)
> + return -ENOMEM;
> +
> + priv->sources = devm_kcalloc(priv->dev, ser->ops->num_phys,
> + sizeof(*priv->sources), GFP_KERNEL);
> + if (!priv->sources)
> + return -ENOMEM;
> +
> + priv->streams_masks = devm_kcalloc(priv->dev, num_pads,
> + sizeof(*priv->streams_masks),
> + GFP_KERNEL);
> + if (!priv->streams_masks)
> + return -ENOMEM;
> +
> + return 0;
> +}
> +
> +int max_ser_probe(struct i2c_client *client, struct max_ser *ser)
> +{
> + struct device *dev = &client->dev;
> + struct max_ser_priv *priv;
> + int ret;
> +
> + if (ser->ops->num_phys > MAX_SER_NUM_PHYS)
> + return -E2BIG;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->client = client;
> + priv->dev = dev;
> + priv->ser = ser;
> + ser->priv = priv;
> + ser->mode = __ffs(ser->ops->modes);
> +
> + ret = max_ser_allocate(priv);
> + if (ret)
> + return ret;
> +
> + ret = max_ser_parse_dt(priv);
> + if (ret)
> + return ret;
> +
> + ret = max_ser_init(priv);
> + if (ret)
> + return ret;
> +
> + ret = max_ser_i2c_adapter_init(priv);
> + if (ret)
> + return ret;
> +
> + ret = max_ser_v4l2_register(priv);
> + if (ret)
> + goto err_i2c_adapter_deinit;
> +
> + return 0;
> +
> +err_i2c_adapter_deinit:
> + max_ser_i2c_adapter_deinit(priv);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_NS_GPL(max_ser_probe, "MAX_SERDES");
> +
> +int max_ser_remove(struct max_ser *ser)
> +{
> + struct max_ser_priv *priv = ser->priv;
> +
> + max_ser_v4l2_unregister(priv);
> +
> + max_ser_i2c_adapter_deinit(priv);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(max_ser_remove, "MAX_SERDES");
> +
> +int max_ser_set_double_bpps(struct v4l2_subdev *sd, u32 double_bpps)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> +
> + priv->double_bpps = double_bpps;
> +
> + return 0;
> +}
> +
> +int max_ser_set_stream_id(struct v4l2_subdev *sd, unsigned int stream_id)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + struct max_ser_pipe *pipe = &ser->pipes[0];
> +
> + if (!ser->ops->set_pipe_stream_id)
> + return -EOPNOTSUPP;
> +
> + return ser->ops->set_pipe_stream_id(ser, pipe, stream_id);
> +}
> +
> +int max_ser_get_stream_id(struct v4l2_subdev *sd, unsigned int *stream_id)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + struct max_ser_pipe *pipe = &ser->pipes[0];
> +
> + if (!ser->ops->get_pipe_stream_id)
> + return -EOPNOTSUPP;
> +
> + *stream_id = ser->ops->get_pipe_stream_id(ser, pipe);
> +
> + return 0;
> +}
> +
> +unsigned int max_ser_get_supported_modes(struct v4l2_subdev *sd)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + struct v4l2_subdev_state *state;
> + unsigned int modes = ser->ops->modes;
> +
> + state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
> +
> + if (max_ser_is_tpg_routed(priv, state))
> + modes = BIT(ser->ops->tpg_mode);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return modes;
> +}
> +
> +bool max_ser_supports_vc_remap(struct v4l2_subdev *sd)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> +
> + return !!ser->ops->set_vc_remap;
> +}
> +
> +int max_ser_set_mode(struct v4l2_subdev *sd, enum max_serdes_gmsl_mode mode)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + int ret;
> +
> + if (!(ser->ops->modes & BIT(mode)))
> + return -EINVAL;
> +
> + if (ser->mode == mode)
> + return 0;
> +
> + if (ser->ops->set_tunnel_enable) {
> + bool tunnel_enable = mode == MAX_SERDES_GMSL_TUNNEL_MODE;
> +
> + ret = ser->ops->set_tunnel_enable(ser, tunnel_enable);
> + if (ret)
> + return ret;
> + }
> +
> + ser->mode = mode;
> +
> + return 0;
> +}
> +
> +int max_ser_set_vc_remaps(struct v4l2_subdev *sd,
> + struct max_serdes_vc_remap *vc_remaps,
> + int num_vc_remaps)
> +{
> + struct max_ser_priv *priv = sd_to_priv(sd);
> + struct max_ser *ser = priv->ser;
> + unsigned int mask = 0;
> + unsigned int i;
> + int ret;
> +
> + if (!ser->ops->set_vc_remap)
> + return -EOPNOTSUPP;
> +
> + if (num_vc_remaps > ser->ops->num_vc_remaps)
> + return -E2BIG;
> +
> + for (i = 0; i < num_vc_remaps; i++) {
> + ret = ser->ops->set_vc_remap(ser, i, &vc_remaps[i]);
> + if (ret)
> + return ret;
> +
> + mask |= BIT(i);
> + }
> +
> + ret = ser->ops->set_vc_remaps_enable(ser, mask);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < num_vc_remaps; i++)
> + ser->vc_remaps[i] = vc_remaps[i];
> +
> + ser->num_vc_remaps = num_vc_remaps;
> +
> + return 0;
> +}
> +
> +static int max_ser_read_reg(struct i2c_adapter *adapter, u8 addr,
> + u16 reg, u8 *val)
> +{
> + u8 buf[2] = { reg >> 8, reg & 0xff };
> + struct i2c_msg msg[2] = {
> + {
> + .addr = addr,
> + .flags = 0,
> + .buf = buf,
> + .len = sizeof(buf),
> + },
> + {
> + .addr = addr,
> + .flags = I2C_M_RD,
> + .buf = buf,
> + .len = 1,
> + },
> + };
> + int ret;
> +
> + ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg));
> + if (ret < 0)
> + return ret;
> +
> + *val = buf[0];
> +
> + return 0;
> +}
> +
> +static int max_ser_write_reg(struct i2c_adapter *adapter, u8 addr,
> + u16 reg, u8 val)
> +{
> + u8 buf[3] = { reg >> 8, reg & 0xff, val };
> + struct i2c_msg msg[1] = {
> + {
> + .addr = addr,
> + .flags = 0,
> + .buf = buf,
> + .len = sizeof(buf),
> + },
> + };
> + int ret;
> +
> + ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg));
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +int max_ser_reset(struct i2c_adapter *adapter, u8 addr)
> +{
> + int ret;
> + u8 val;
> +
> + ret = max_ser_read_reg(adapter, addr, MAX_SER_CTRL0, &val);
> + if (ret)
> + return ret;
> +
> + val |= MAX_SER_CTRL0_RESET_ALL;
> +
> + return max_ser_write_reg(adapter, addr, MAX_SER_CTRL0, val);
> +}
> +
> +int max_ser_wait_for_multiple(struct i2c_adapter *adapter, u8 *addrs,
> + unsigned int num_addrs, u8 *current_addr)
> +{
> + unsigned int i, j;
> + int ret = 0;
> + u8 val;
> +
> + for (i = 0; i < 10; i++) {
> + for (j = 0; j < num_addrs; j++) {
> + ret = max_ser_read_reg(adapter, addrs[j], MAX_SER_REG0, &val);
> + if (!ret && val) {
> + *current_addr = addrs[j];
> + return 0;
> + }
> +
> + msleep(100);
> + }
> + }
> +
> + return ret;
> +}
> +
> +int max_ser_wait(struct i2c_adapter *adapter, u8 addr)
> +{
> + u8 current_addr;
> +
> + return max_ser_wait_for_multiple(adapter, &addr, 1, ¤t_addr);
> +}
> +
> +int max_ser_fix_tx_ids(struct i2c_adapter *adapter, u8 addr)
> +{
> + unsigned int addr_regs[] = {
> + MAX_SER_CFGI_INFOFR_TR3,
> + MAX_SER_CFGL_SPI_TR3,
> + MAX_SER_CFGC_CC_TR3,
> + MAX_SER_CFGC_GPIO_TR3,
> + MAX_SER_CFGL_IIC_X_TR3,
> + MAX_SER_CFGL_IIC_Y_TR3,
> + };
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < ARRAY_SIZE(addr_regs); i++) {
> + ret = max_ser_write_reg(adapter, addr, addr_regs[i], addr);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +int max_ser_change_address(struct i2c_adapter *adapter, u8 addr, u8 new_addr)
> +{
> + u8 val = FIELD_PREP(MAX_SER_REG0_DEV_ADDR, new_addr);
> +
> + return max_ser_write_reg(adapter, addr, MAX_SER_REG0, val);
> +}
> +
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("I2C_ATR");
> diff --git a/drivers/media/i2c/maxim-serdes/max_ser.h b/drivers/media/i2c/maxim-serdes/max_ser.h
> new file mode 100644
> index 000000000000..a9365be5e62d
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_ser.h
> @@ -0,0 +1,147 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#ifndef MAX_SER_H
> +#define MAX_SER_H
> +
> +#include <linux/i2c.h>
> +
> +#include <media/v4l2-mediabus.h>
> +
> +#include "max_serdes.h"
> +
> +#define MAX_SER_REG0 0x0
> +#define MAX_SER_REG0_DEV_ADDR GENMASK(7, 1)
> +
> +#define MAX_SER_CTRL0 0x10
> +#define MAX_SER_CTRL0_RESET_ALL BIT(7)
> +
> +#define MAX_SER_CFGI_INFOFR_TR3 0x7b
> +#define MAX_SER_CFGL_SPI_TR3 0x83
> +#define MAX_SER_CFGC_CC_TR3 0x8b
> +#define MAX_SER_CFGC_GPIO_TR3 0x93
> +#define MAX_SER_CFGL_IIC_X_TR3 0xa3
> +#define MAX_SER_CFGL_IIC_Y_TR3 0xab
> +
> +struct max_ser_phy {
> + unsigned int index;
> + struct v4l2_mbus_config_mipi_csi2 mipi;
> + bool enabled;
> + bool active;
> +};
> +
> +struct max_ser_pipe_mode {
> + unsigned int soft_bpp;
> + unsigned int bpp;
> + bool dbl8;
> + bool dbl10;
> + bool dbl12;
> +};
> +
> +struct max_ser_pipe {
> + unsigned int index;
> + unsigned int phy_id;
> + unsigned int stream_id;
> + unsigned int *dts;
> + unsigned int num_dts;
> + unsigned int vcs;
> + struct max_ser_pipe_mode mode;
> + bool enabled;
> +};
> +
> +struct max_ser;
> +
> +struct max_ser_ops {
> + unsigned int modes;
> + unsigned int num_pipes;
> + unsigned int num_dts_per_pipe;
> + unsigned int num_phys;
> + unsigned int num_i2c_xlates;
> + unsigned int num_vc_remaps;
> +
> + struct max_serdes_phys_configs phys_configs;
> + struct max_serdes_tpg_entries tpg_entries;
> + enum max_serdes_gmsl_mode tpg_mode;
> + unsigned int tpg_patterns;
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + int (*reg_read)(struct max_ser *ser, unsigned int reg, unsigned int *val);
> + int (*reg_write)(struct max_ser *ser, unsigned int reg, unsigned int val);
> +#endif
> + int (*log_status)(struct max_ser *ser);
> + int (*log_pipe_status)(struct max_ser *ser, struct max_ser_pipe *pipe);
> + int (*log_phy_status)(struct max_ser *ser, struct max_ser_phy *phy);
> + int (*init)(struct max_ser *ser);
> + int (*set_i2c_xlate)(struct max_ser *ser, unsigned int i,
> + struct max_serdes_i2c_xlate *i2c_xlate);
> + int (*set_tunnel_enable)(struct max_ser *ser, bool enable);
> + int (*set_tpg)(struct max_ser *ser, const struct max_serdes_tpg_entry *entry);
> + int (*init_phy)(struct max_ser *ser, struct max_ser_phy *phy);
> + int (*set_phy_active)(struct max_ser *ser, struct max_ser_phy *phy,
> + bool enable);
> + int (*set_pipe_enable)(struct max_ser *ser, struct max_ser_pipe *pipe,
> + bool enable);
> + int (*set_pipe_dt)(struct max_ser *ser, struct max_ser_pipe *pipe,
> + unsigned int i, unsigned int dt);
> + int (*set_pipe_dt_en)(struct max_ser *ser, struct max_ser_pipe *pipe,
> + unsigned int i, bool enable);
> + int (*set_pipe_vcs)(struct max_ser *ser, struct max_ser_pipe *pipe,
> + unsigned int vcs);
> + int (*set_pipe_mode)(struct max_ser *ser, struct max_ser_pipe *pipe,
> + struct max_ser_pipe_mode *mode);
> + int (*set_vc_remap)(struct max_ser *ser, unsigned int i,
> + struct max_serdes_vc_remap *vc_remap);
> + int (*set_vc_remaps_enable)(struct max_ser *ser, unsigned int mask);
> + int (*set_pipe_stream_id)(struct max_ser *ser, struct max_ser_pipe *pipe,
> + unsigned int stream_id);
> + unsigned int (*get_pipe_stream_id)(struct max_ser *ser, struct max_ser_pipe *pipe);
> + int (*set_pipe_phy)(struct max_ser *ser, struct max_ser_pipe *pipe,
> + struct max_ser_phy *phy);
> +};
> +
> +struct max_ser_priv;
> +
> +struct max_ser {
> + struct max_ser_priv *priv;
> +
> + const struct max_ser_ops *ops;
> +
> + struct max_serdes_i2c_xlate *i2c_xlates;
> +
> + struct max_ser_phy *phys;
> + struct max_ser_pipe *pipes;
> + const struct max_serdes_tpg_entry *tpg_entry;
> + enum max_serdes_tpg_pattern tpg_pattern;
> +
> + struct max_serdes_vc_remap *vc_remaps;
> + unsigned int num_vc_remaps;
> +
> + unsigned int phys_config;
> + unsigned int active;
> + enum max_serdes_gmsl_mode mode;
> +};
> +
> +int max_ser_probe(struct i2c_client *client, struct max_ser *ser);
> +
> +int max_ser_remove(struct max_ser *ser);
> +
> +int max_ser_set_double_bpps(struct v4l2_subdev *sd, u32 double_bpps);
> +unsigned int max_ser_get_supported_modes(struct v4l2_subdev *sd);
> +int max_ser_set_mode(struct v4l2_subdev *sd, enum max_serdes_gmsl_mode mode);
> +bool max_ser_supports_vc_remap(struct v4l2_subdev *sd);
> +int max_ser_set_stream_id(struct v4l2_subdev *sd, unsigned int stream_id);
> +int max_ser_get_stream_id(struct v4l2_subdev *sd, unsigned int *stream_id);
> +int max_ser_set_vc_remaps(struct v4l2_subdev *sd, struct max_serdes_vc_remap *vc_remaps,
> + int num_vc_remaps);
> +
> +int max_ser_reset(struct i2c_adapter *adapter, u8 addr);
> +int max_ser_wait(struct i2c_adapter *adapter, u8 addr);
> +int max_ser_wait_for_multiple(struct i2c_adapter *adapter, u8 *addrs,
> + unsigned int num_addrs, u8 *current_addr);
> +
> +int max_ser_change_address(struct i2c_adapter *adapter, u8 addr, u8 new_addr);
> +int max_ser_fix_tx_ids(struct i2c_adapter *adapter, u8 addr);
> +
> +#endif // MAX_SER_H
>
> --
> 2.53.0
>
>
--
Kind Regards,
Niklas Söderlund
^ permalink raw reply
* Re: [PATCH v4 02/14] mfd: lm3533: Remove driver specific regmap wrappers
From: Svyatoslav Ryhel @ 2026-06-10 14:32 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
Johan Hovold, dri-devel, linux-leds, devicetree, linux-kernel,
linux-iio, linux-fbdev
In-Reply-To: <aihjVZ9xvM2BRFu4@ashevche-desk.local>
вт, 9 черв. 2026 р. о 22:02 Andy Shevchenko <andriy.shevchenko@intel.com> пише:
>
> On Sat, Jun 06, 2026 at 10:22:43AM +0300, Svyatoslav Ryhel wrote:
> > сб, 6 черв. 2026 р. о 09:53 Andy Shevchenko <andriy.shevchenko@intel.com> пише:
> > > On Sat, Jun 06, 2026 at 07:57:26AM +0300, Svyatoslav Ryhel wrote:
>
> ...
>
> > > > + ret = regmap_assign_bits(als->lm3533->regmap, LM3533_REG_ALS_ZONE_INFO,
> > > > + LM3533_ALS_INT_ENABLE_MASK, enable);
> > >
> > > In cases like this perhaps leaving mask would be fine and together with
> >
> > I prefer to remove intermediate variables it the helper allows to
> > directly pass needed value.
> >
> > > struct regmap *map = als->lm3533->regmap;
> >
> > next patch drops lm3533 so there will be als->regmap which IMHO is
> > more logical instead of passing entire lm3533 to child devices.
>
> Still it's longer than map. A local variable may help with making lines
> shorter.
>
Introducing and assigning a local variable for a single use regmap
operation seems excessive especially since next patch removes operator
chaining leaving only als->regmap.
> > > this be nice one-liner:
> > >
> > > ret = regmap_assign_bits(map, LM3533_REG_ALS_ZONE_INFO, mask, enable);
> > >
> > > > if (ret) {
> > > > dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> > > > enable);
> > >
> > > In many cases it won't increase LoC count.
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
^ permalink raw reply
* Re: [PATCH v2 10/16] power: sequencing: pcie-m2: support matching on remote "port" node
From: Andy Shevchenko @ 2026-06-10 14:33 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
linux-mediatek, linux-arm-kernel, linux-kernel,
Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-11-wenst@chromium.org>
On Wed, Jun 10, 2026 at 04:40:44PM +0800, Chen-Yu Tsai wrote:
> A USB hub can have multiple ports, and this driver needs to
> differentiate which port is being matched to. The USB hub driver now
> associates the "port" node with the usb_port device, so here we can
> use the remote "port" node to check for a match. Then fall back to
> the remote device node for the other connection types.
...
> + if (remote_port && remote_port == dev_of_node(dev))
> + return PWRSEQ_MATCH_OK;
> if (remote && (remote == dev_of_node(dev)))
> return PWRSEQ_MATCH_OK;
We have device_match_of_node() IIRC the name of that API.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 1/3] media: dt-bindings: media: renesas,fcp: Document RZ/T2H and RZ/N2H SoCs
From: Laurent Pinchart @ 2026-06-10 14:33 UTC (permalink / raw)
To: Prabhakar
Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Kieran Bingham, Philipp Zabel, Geert Uytterhoeven,
Magnus Damm, linux-media, linux-renesas-soc, devicetree,
linux-kernel, Biju Das, Fabrizio Castro, Lad Prabhakar
In-Reply-To: <20260430100929.1088281-2-prabhakar.mahadev-lad.rj@bp.renesas.com>
Hi Prabhakar,
Thank you for the patch.
On Thu, Apr 30, 2026 at 11:09:27AM +0100, Prabhakar wrote:
> From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
>
> Document the FCPVD blocks present on the RZ/T2H and RZ/N2H SoCs.
>
> The FCPVD implementation on these SoCs is identical to that found on the
> RZ/G2L family.
>
> Update the schema to disallow the "resets" property for these SoCs,
> reflecting the absence of a reset control for the FCPVD instance.
>
> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
> ---
> .../devicetree/bindings/media/renesas,fcp.yaml | 18 +++++++++++++++++-
> 1 file changed, 17 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/devicetree/bindings/media/renesas,fcp.yaml b/Documentation/devicetree/bindings/media/renesas,fcp.yaml
> index 5e11ae0ee456..cbb16a7a5481 100644
> --- a/Documentation/devicetree/bindings/media/renesas,fcp.yaml
> +++ b/Documentation/devicetree/bindings/media/renesas,fcp.yaml
> @@ -34,6 +34,8 @@ properties:
> - renesas,r9a09g047-fcpvd # RZ/G3E
> - renesas,r9a09g056-fcpvd # RZ/V2N
> - renesas,r9a09g057-fcpvd # RZ/V2H(P)
> + - renesas,r9a09g077-fcpvd # RZ/T2H
> + - renesas,r9a09g087-fcpvd # RZ/N2H
> - const: renesas,fcpv # Generic FCP for VSP fallback
>
> reg:
> @@ -66,7 +68,6 @@ required:
> - reg
> - clocks
> - power-domains
> - - resets
>
> additionalProperties: false
>
> @@ -83,6 +84,8 @@ allOf:
> - renesas,r9a09g047-fcpvd
> - renesas,r9a09g056-fcpvd
> - renesas,r9a09g057-fcpvd
> + - renesas,r9a09g077-fcpvd
> + - renesas,r9a09g087-fcpvd
> then:
> properties:
> clocks:
> @@ -94,6 +97,19 @@ allOf:
> clocks:
> maxItems: 1
> clock-names: false
> + - if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - renesas,r9a09g077-fcpvd
> + - renesas,r9a09g087-fcpvd
> + then:
> + properties:
> + resets: false
> + else:
> + required:
> + - resets
>
> examples:
> # R8A7795 (R-Car H3) FCP for VSP-D1
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH v4 04/14] mfd: lm3533: Pass only regmap and light sensor presence to child devices
From: Svyatoslav Ryhel @ 2026-06-10 14:34 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
Johan Hovold, dri-devel, linux-leds, devicetree, linux-kernel,
linux-iio, linux-fbdev
In-Reply-To: <aihkNekrgfu6-6Q_@ashevche-desk.local>
вт, 9 черв. 2026 р. о 22:06 Andy Shevchenko <andriy.shevchenko@intel.com> пише:
>
> On Sat, Jun 06, 2026 at 07:57:28AM +0300, Svyatoslav Ryhel wrote:
> > Instead of passing the entire lm3533 core data structure, only pass the
> > regmap and the light sensor presence flag to child devices.
>
> ...
>
> > struct lm3533_als {
> > - struct lm3533 *lm3533;
> > + struct regmap *regmap;
> > struct platform_device *pdev;
>
> And this pdev is probably not needed. But I haven't checked the whole lot of
> the patches yet.
>
It is needed since it holds childs pdev (dev would be better, but not
in this patchset), you cannot get childs dev from regmap since regmap
holds cores dev.
> > unsigned long flags;
>
> ...
>
> > struct lm3533_ctrlbank {
> > - struct lm3533 *lm3533;
> > + struct regmap *regmap;
> > struct device *dev;
>
> Ditto.
>
Same here, dev holds either LEDs dev or backlight dev.
> > int id;
> > };
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
^ permalink raw reply
* Re: [PATCH v13 15/22] media: i2c: add Maxim GMSL2/3 deserializer framework
From: Niklas Söderlund @ 2026-06-10 14:35 UTC (permalink / raw)
To: dumitru.ceclan
Cc: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring, Greg Kroah-Hartman,
mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Martin Hecht, Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-15-9d8a4919983b@analog.com>
Hello,
Thanks for your work.
On 2026-06-04 17:14:02 +0300, Dumitru Ceclan via B4 Relay wrote:
> From: Cosmin Tanislav <demonsingur@gmail.com>
>
> These drivers are meant to be used as a common framework for Maxim
> GMSL2/3 deserializer.
>
> This framework enables support for the following new features across
> all the chips:
> * Full Streams API support
> * .get_frame_desc()
> * .get_mbus_config()
> * I2C ATR
> * automatic GMSL link version negotiation
> * automatic stream id selection
> * automatic VC remapping
> * automatic pixel mode / tunnel mode selection
> * automatic double mode selection / data padding
> * logging of internal state and chip status registers via .log_status()
> * PHY modes
> * serializer pinctrl
> * TPG
>
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> ---
> drivers/media/i2c/maxim-serdes/Makefile | 2 +-
> drivers/media/i2c/maxim-serdes/max_des.c | 3243 ++++++++++++++++++++++++++++++
> drivers/media/i2c/maxim-serdes/max_des.h | 157 ++
> 3 files changed, 3401 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> index 17511cb03369..b54326a5c81b 100644
> --- a/drivers/media/i2c/maxim-serdes/Makefile
> +++ b/drivers/media/i2c/maxim-serdes/Makefile
> @@ -1,3 +1,3 @@
> # SPDX-License-Identifier: GPL-2.0
> -max-serdes-objs := max_serdes.o max_ser.o
> +max-serdes-objs := max_serdes.o max_ser.o max_des.o
> obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
> diff --git a/drivers/media/i2c/maxim-serdes/max_des.c b/drivers/media/i2c/maxim-serdes/max_des.c
> new file mode 100644
> index 000000000000..732e3ab83664
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_des.c
> @@ -0,0 +1,3243 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim GMSL2 Deserializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c-atr.h>
> +#include <linux/i2c-mux.h>
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/units.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "max_des.h"
> +#include "max_ser.h"
> +#include "max_serdes.h"
> +
> +#define MAX_DES_LINK_FREQUENCY_MIN (100 * HZ_PER_MHZ)
> +#define MAX_DES_LINK_FREQUENCY_DEFAULT (750 * HZ_PER_MHZ)
> +#define MAX_DES_LINK_FREQUENCY_MAX (1250 * HZ_PER_MHZ)
> +
> +#define MAX_DES_NUM_PHYS 4
> +#define MAX_DES_NUM_LINKS 4
> +#define MAX_DES_NUM_PIPES 8
> +
> +struct max_des_priv {
> + struct max_des *des;
> +
> + struct device *dev;
> + struct i2c_client *client;
> + struct i2c_atr *atr;
> + struct i2c_mux_core *mux;
> +
> + struct media_pad *pads;
> + struct regulator **pocs;
> + struct max_serdes_source *sources;
> + u64 *streams_masks;
> +
> + struct notifier_block i2c_nb;
> + struct v4l2_subdev sd;
> + struct v4l2_async_notifier nf;
> + struct v4l2_ctrl_handler ctrl_handler;
> +
> + struct max_des_phy *unused_phy;
> +};
> +
> +struct max_des_remap_context {
> + enum max_serdes_gmsl_mode mode;
> + /* Mark whether TPG is enabled */
> + bool tpg;
> + /* Mark the PHYs to which each pipe is mapped. */
> + unsigned long pipe_phy_masks[MAX_DES_NUM_PIPES];
> + /* Mark the pipes in use. */
> + bool pipe_in_use[MAX_DES_NUM_PIPES];
> + /* Mark whether pipe has remapped VC ids. */
> + bool vc_ids_remapped[MAX_DES_NUM_PIPES];
> + /* Map between pipe VC ids and PHY VC ids. */
> + unsigned int vc_ids_map[MAX_DES_NUM_PIPES][MAX_DES_NUM_PHYS][MAX_SERDES_VC_ID_NUM];
> + /* Mark whether a pipe VC id has been mapped to a PHY VC id. */
> + unsigned long vc_ids_masks[MAX_DES_NUM_PIPES][MAX_DES_NUM_PHYS];
> + /* Mark whether a PHY VC id has been mapped. */
> + unsigned long dst_vc_ids_masks[MAX_DES_NUM_PHYS];
> +};
> +
> +struct max_des_mode_context {
> + bool phys_bpp8_shared_with_16[MAX_DES_NUM_PHYS];
> + bool pipes_bpp8_shared_with_16[MAX_DES_NUM_PIPES];
> + u32 phys_double_bpps[MAX_DES_NUM_PHYS];
> + u32 pipes_double_bpps[MAX_DES_NUM_PIPES];
> +};
> +
> +struct max_des_route_hw {
> + struct max_serdes_source *source;
> + struct max_des_pipe *pipe;
> + struct max_des_phy *phy;
> + struct v4l2_mbus_frame_desc_entry entry;
> + bool is_tpg;
> +};
> +
> +struct max_des_link_hw {
> + struct max_serdes_source *source;
> + struct max_des_link *link;
> + struct max_des_pipe *pipe;
> +};
> +
> +static inline struct max_des_priv *sd_to_priv(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct max_des_priv, sd);
> +}
> +
> +static inline struct max_des_priv *nf_to_priv(struct v4l2_async_notifier *nf)
> +{
> + return container_of(nf, struct max_des_priv, nf);
> +}
> +
> +static inline struct max_des_priv *ctrl_to_priv(struct v4l2_ctrl_handler *handler)
> +{
> + return container_of(handler, struct max_des_priv, ctrl_handler);
> +}
> +
> +static inline bool max_des_pad_is_sink(struct max_des *des, u32 pad)
> +{
> + return pad < des->info->num_links;
> +}
> +
> +static inline bool max_des_pad_is_source(struct max_des *des, u32 pad)
> +{
> + return pad >= des->info->num_links &&
> + pad < des->info->num_links + des->info->num_phys;
> +}
> +
> +static inline bool max_des_pad_is_tpg(struct max_des *des, u32 pad)
> +{
> + return pad == des->info->num_links + des->info->num_phys;
> +}
> +
> +static inline unsigned int max_des_link_to_pad(struct max_des *des,
> + struct max_des_link *link)
> +{
> + return link->index;
> +}
> +
> +static inline unsigned int max_des_phy_to_pad(struct max_des *des,
> + struct max_des_phy *phy)
> +{
> + return phy->index + des->info->num_links;
> +}
> +
> +static inline unsigned int max_des_num_pads(struct max_des *des)
> +{
> + return des->info->num_links + des->info->num_phys +
> + (des->ops->set_tpg ? 1 : 0);
> +}
> +
> +static struct max_des_phy *max_des_pad_to_phy(struct max_des *des, u32 pad)
> +{
> + if (!max_des_pad_is_source(des, pad))
> + return NULL;
> +
> + return &des->phys[pad - des->info->num_links];
> +}
> +
> +static struct max_des_link *max_des_pad_to_link(struct max_des *des, u32 pad)
> +{
> + if (!max_des_pad_is_sink(des, pad))
> + return NULL;
> +
> + return &des->links[pad];
> +}
> +
> +static struct max_des_pipe *
> +max_des_find_link_pipe(struct max_des *des, struct max_des_link *link)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < des->info->num_pipes; i++) {
> + struct max_des_pipe *pipe = &des->pipes[i];
> +
> + if (pipe->link_id == link->index)
> + return pipe;
> + }
> +
> + return NULL;
> +}
> +
> +static struct max_serdes_source *
> +max_des_get_link_source(struct max_des_priv *priv, struct max_des_link *link)
> +{
> + return &priv->sources[link->index];
> +}
> +
> +static const struct max_serdes_tpg_entry *
> +max_des_find_tpg_entry(struct max_des *des, u32 target_index,
> + u32 width, u32 height, u32 code,
> + u32 numerator, u32 denominator)
> +{
> + const struct max_serdes_tpg_entry *entry;
> + unsigned int index = 0;
> + unsigned int i;
> +
> + for (i = 0; i < des->info->tpg_entries.num_entries; i++) {
> + entry = &des->info->tpg_entries.entries[i];
> +
> + if ((width != 0 && width != entry->width) ||
> + (height != 0 && height != entry->height) ||
> + (code != 0 && code != entry->code) ||
> + (numerator != 0 && numerator != entry->interval.numerator) ||
> + (denominator != 0 && denominator != entry->interval.denominator))
> + continue;
> +
> + if (index == target_index)
> + break;
> +
> + index++;
> + }
> +
> + if (i == des->info->tpg_entries.num_entries)
> + return NULL;
> +
> + return &des->info->tpg_entries.entries[i];
> +}
> +
> +static const struct max_serdes_tpg_entry *
> +max_des_find_state_tpg_entry(struct max_des *des, struct v4l2_subdev_state *state,
> + unsigned int pad)
> +{
> + struct v4l2_mbus_framefmt *fmt;
> + struct v4l2_fract *in;
> +
> + fmt = v4l2_subdev_state_get_format(state, pad, MAX_SERDES_TPG_STREAM);
> + if (!fmt)
> + return NULL;
> +
> + in = v4l2_subdev_state_get_interval(state, pad, MAX_SERDES_TPG_STREAM);
> + if (!in)
> + return NULL;
> +
> + return max_des_find_tpg_entry(des, 0, fmt->width, fmt->height, fmt->code,
> + in->numerator, in->denominator);
> +}
> +
> +static int max_des_get_tpg_fd_entry_state(struct max_des *des,
> + struct v4l2_subdev_state *state,
> + struct v4l2_mbus_frame_desc_entry *fd_entry,
> + unsigned int pad)
> +{
> + const struct max_serdes_tpg_entry *entry;
> +
> + entry = max_des_find_state_tpg_entry(des, state, pad);
> + if (!entry)
> + return -EINVAL;
> +
> + fd_entry->stream = MAX_SERDES_TPG_STREAM;
> + fd_entry->flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
> + fd_entry->length = entry->width * entry->height * entry->bpp / 8;
> + fd_entry->pixelcode = entry->code;
> + fd_entry->bus.csi2.vc = 0;
> + fd_entry->bus.csi2.dt = entry->dt;
> +
> + return 0;
> +}
> +
> +static int max_des_tpg_route_to_hw(struct max_des_priv *priv,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_route *route,
> + struct max_des_route_hw *hw)
> +{
> + struct max_des *des = priv->des;
> +
> + /* TPG injects its data into all pipes, but use pipe 0 for simplicity. */
> + hw->pipe = &des->pipes[0];
> +
> + hw->phy = max_des_pad_to_phy(des, route->source_pad);
> + if (!hw->phy)
> + return -ENOENT;
> +
> + return max_des_get_tpg_fd_entry_state(des, state, &hw->entry,
> + route->sink_pad);
> +}
> +
> +static int max_des_route_to_hw(struct max_des_priv *priv,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_route *route,
> + struct max_des_route_hw *hw)
> +{
> + struct max_des *des = priv->des;
> + struct v4l2_mbus_frame_desc fd = {};
> + struct max_des_link *link;
> + unsigned int i;
> + int ret;
> +
> + memset(hw, 0, sizeof(*hw));
> +
> + hw->is_tpg = max_des_pad_is_tpg(des, route->sink_pad);
> + if (hw->is_tpg)
> + return max_des_tpg_route_to_hw(priv, state, route, hw);
> +
> + link = max_des_pad_to_link(des, route->sink_pad);
> + if (!link)
> + return -ENOENT;
> +
> + hw->phy = max_des_pad_to_phy(des, route->source_pad);
> + if (!hw->phy)
> + return -ENOENT;
> +
> + hw->pipe = max_des_find_link_pipe(des, link);
> + if (!hw->pipe)
> + return -ENOENT;
> +
> + hw->source = max_des_get_link_source(priv, link);
> + if (!hw->source->sd)
> + return 0;
> +
> + ret = v4l2_subdev_call(hw->source->sd, pad, get_frame_desc,
> + hw->source->pad, &fd);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < fd.num_entries; i++)
> + if (fd.entry[i].stream == route->sink_stream)
> + break;
> +
> + if (i == fd.num_entries)
> + return -ENOENT;
> +
> + hw->entry = fd.entry[i];
> +
> + return 0;
> +}
> +
> +static int max_des_link_to_hw(struct max_des_priv *priv,
> + struct max_des_link *link,
> + struct max_des_link_hw *hw)
> +{
> + struct max_des *des = priv->des;
> +
> + memset(hw, 0, sizeof(*hw));
> +
> + hw->link = link;
> +
> + hw->pipe = max_des_find_link_pipe(des, hw->link);
> + if (!hw->pipe)
> + return -ENOENT;
> +
> + hw->source = max_des_get_link_source(priv, hw->link);
> +
> + return 0;
> +}
> +
> +static int max_des_link_index_to_hw(struct max_des_priv *priv, unsigned int i,
> + struct max_des_link_hw *hw)
> +{
> + return max_des_link_to_hw(priv, &priv->des->links[i], hw);
> +}
> +
> +static int max_des_set_pipe_remaps(struct max_des_priv *priv,
> + struct max_des_pipe *pipe,
> + struct max_des_remap *remaps,
> + unsigned int num_remaps)
> +{
> + struct max_des *des = priv->des;
> + unsigned int mask = 0;
> + unsigned int i;
> + int ret;
> +
> + if (!des->ops->set_pipe_remap)
> + return 0;
> +
> + for (i = 0; i < num_remaps; i++) {
> + ret = des->ops->set_pipe_remap(des, pipe, i, &remaps[i]);
> + if (ret)
> + return ret;
> +
> + mask |= BIT(i);
> + }
> +
> + return des->ops->set_pipe_remaps_enable(des, pipe, mask);
> +}
> +
> +static int max_des_set_pipe_vc_remaps(struct max_des_priv *priv,
> + struct max_des_pipe *pipe,
> + struct max_serdes_vc_remap *vc_remaps,
> + unsigned int num_vc_remaps)
> +{
> + struct max_des *des = priv->des;
> + unsigned int mask = 0;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < num_vc_remaps; i++) {
> + ret = des->ops->set_pipe_vc_remap(des, pipe, i, &vc_remaps[i]);
> + if (ret)
> + return ret;
> +
> + mask |= BIT(i);
> + }
> +
> + return des->ops->set_pipe_vc_remaps_enable(des, pipe, mask);
> +}
> +
> +static int max_des_map_src_dst_vc_id(struct max_des_remap_context *context,
> + unsigned int pipe_id, unsigned int phy_id,
> + unsigned int src_vc_id, bool keep_vc)
> +{
> + unsigned int vc_id;
> +
> + if (src_vc_id >= MAX_SERDES_VC_ID_NUM)
> + return -E2BIG;
> +
> + if (context->vc_ids_masks[pipe_id][phy_id] & BIT(src_vc_id))
> + return 0;
> +
> + if (keep_vc && !(context->dst_vc_ids_masks[phy_id] & BIT(src_vc_id)))
> + vc_id = src_vc_id;
> + else
> + vc_id = ffz(context->dst_vc_ids_masks[phy_id]);
> +
> + if (vc_id != src_vc_id)
> + context->vc_ids_remapped[pipe_id] = true;
> +
> + if (vc_id >= MAX_SERDES_VC_ID_NUM)
> + return -E2BIG;
> +
> + context->pipe_phy_masks[pipe_id] |= BIT(phy_id);
> + context->dst_vc_ids_masks[phy_id] |= BIT(vc_id);
> +
> + context->vc_ids_map[pipe_id][phy_id][src_vc_id] = vc_id;
> + context->vc_ids_masks[pipe_id][phy_id] |= BIT(src_vc_id);
> +
> + return 0;
> +}
> +
> +static int max_des_get_src_dst_vc_id(struct max_des_remap_context *context,
> + unsigned int pipe_id, unsigned int phy_id,
> + unsigned int src_vc_id, unsigned int *dst_vc_id)
> +{
> + if (!(context->vc_ids_masks[pipe_id][phy_id] & BIT(src_vc_id)))
> + return -ENOENT;
> +
> + *dst_vc_id = context->vc_ids_map[pipe_id][phy_id][src_vc_id];
> +
> + return 0;
> +}
> +
> +static int max_des_populate_remap_usage(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route *route;
> + int ret;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_des_route_hw hw;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (hw.is_tpg)
> + context->tpg = true;
> +
> + context->pipe_in_use[hw.pipe->index] = true;
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_get_supported_modes(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + unsigned int *modes)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + *modes = des->info->modes;
> +
> + if (context->tpg)
> + *modes = BIT(des->info->tpg_mode);
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link_hw hw;
> +
> + ret = max_des_link_index_to_hw(priv, i, &hw);
> + if (ret)
> + return ret;
> +
> + if (!hw.link->enabled)
> + continue;
> +
> + if (!hw.source->sd)
> + continue;
> +
> + if (!context->pipe_in_use[hw.pipe->index])
> + continue;
> +
> + *modes &= max_ser_get_supported_modes(hw.source->sd);
> + }
> +
> + /*
> + * Serializers need to all be in the same mode because of hardware
> + * issues when running them in mixed modes.
> + */
> + if (!*modes)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int max_des_populate_remap_context_mode(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + unsigned int modes)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + context->mode = MAX_SERDES_GMSL_PIXEL_MODE;
> +
> + /*
> + * If pixel mode is the only supported mode, do not try to see if
> + * tunnel mode can be used.
> + */
> + if (modes == BIT(MAX_SERDES_GMSL_PIXEL_MODE))
> + return 0;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link_hw hw;
> +
> + ret = max_des_link_index_to_hw(priv, i, &hw);
> + if (ret)
> + return ret;
> +
> + if (!hw.link->enabled)
> + continue;
> +
> + if (!hw.source->sd)
> + continue;
> +
> + if (!context->pipe_in_use[hw.pipe->index])
> + continue;
> +
> + if (hweight_long(context->pipe_phy_masks[hw.pipe->index]) == 1 &&
> + (!context->vc_ids_remapped[hw.pipe->index] ||
> + max_ser_supports_vc_remap(hw.source->sd) ||
> + des->ops->set_pipe_vc_remap))
> + continue;
> +
> + return 0;
> + }
> +
> + context->mode = MAX_SERDES_GMSL_TUNNEL_MODE;
> +
> + return 0;
> +}
> +
> +static int max_des_should_keep_vc(struct max_des_priv *priv,
> + struct max_des_route_hw *hw,
> + unsigned int modes)
> +{
> + struct max_des *des = priv->des;
> +
> + /* Pixel mode deserializers always have the ability to remap VCs. */
> + if (modes == BIT(MAX_SERDES_GMSL_PIXEL_MODE))
> + return false;
> +
> + if (des->ops->set_pipe_vc_remap)
> + return false;
> +
> + if (!hw->is_tpg && hw->source && hw->source->sd &&
> + max_ser_supports_vc_remap(hw->source->sd))
> + return false;
> +
> + return true;
> +}
> +
> +static int max_des_populate_remap_context(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route *route;
> + unsigned int modes;
> + int ret;
> +
> + ret = max_des_populate_remap_usage(priv, context, state);
> + if (ret)
> + return ret;
> +
> + ret = max_des_get_supported_modes(priv, context, &modes);
> + if (ret)
> + return ret;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_des_route_hw hw;
> + bool keep_vc;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + keep_vc = max_des_should_keep_vc(priv, &hw, modes);
> +
> + ret = max_des_map_src_dst_vc_id(context, hw.pipe->index, hw.phy->index,
> + hw.entry.bus.csi2.vc, keep_vc);
> + if (ret)
> + return ret;
> + }
> +
> + return max_des_populate_remap_context_mode(priv, context, modes);
> +}
> +
> +static int max_des_populate_mode_context(struct max_des_priv *priv,
> + struct max_des_mode_context *context,
> + struct v4l2_subdev_state *state,
> + enum max_serdes_gmsl_mode mode)
> +{
> + bool bpp8_not_shared_with_16_phys[MAX_DES_NUM_PHYS] = { 0 };
> + u32 undoubled_bpps_phys[MAX_DES_NUM_PHYS] = { 0 };
> + u32 bpps_pipes[MAX_DES_NUM_PIPES] = { 0 };
> + struct max_des *des = priv->des;
> + struct v4l2_subdev_route *route;
> + unsigned int i;
> + int ret;
> +
> + if (mode != MAX_SERDES_GMSL_PIXEL_MODE)
> + return 0;
> +
> + /*
> + * Go over all streams and gather the bpps for all pipes.
> + *
> + * Then, go over all the streams again and check if the
> + * current stream is doubled.
> + *
> + * If the current stream is doubled, add it to a doubled mask for both
> + * the pipe and the PHY.
> + *
> + * If the current stream is not doubled, add it to a local undoubled
> + * mask for the PHY.
> + *
> + * Also, track whether an 8bpp stream is shared with any bpp > 8 on both
> + * the PHYs and the pipes, since that needs to be special cased.
> + *
> + * After going over all the streams, remove the undoubled streams from
> + * the doubled ones. Doubled and undoubled streams cannot be streamed
> + * over the same PHY.
> + *
> + * Then, do a second pass to remove the undoubled streams from the pipes.
> + *
> + * This operation cannot be done in a single pass because any pipe might
> + * generate an undoubled stream for a specific bpp, causing already
> + * processed pipes to need to have their doubled bpps updated.
> + */
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_des_route_hw hw;
> + unsigned int bpp;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + ret = max_serdes_get_fd_bpp(&hw.entry, &bpp);
> + if (ret)
> + return ret;
> +
> + bpps_pipes[hw.pipe->index] |= BIT(bpp);
> + }
> +
> + for_each_active_route(&state->routing, route) {
> + unsigned int bpp, min_bpp, max_bpp, doubled_bpp;
> + unsigned int pipe_id, phy_id;
> + struct max_des_route_hw hw;
> + u32 sink_bpps;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + ret = max_serdes_get_fd_bpp(&hw.entry, &bpp);
> + if (ret)
> + return ret;
> +
> + sink_bpps = bpps_pipes[hw.pipe->index];
> +
> + ret = max_serdes_process_bpps(priv->dev, sink_bpps, ~0U, &doubled_bpp);
> + if (ret)
> + return ret;
> +
> + min_bpp = __ffs(sink_bpps);
> + max_bpp = __fls(sink_bpps);
> + pipe_id = hw.pipe->index;
> + phy_id = hw.phy->index;
> +
> + if (bpp == doubled_bpp) {
> + context->phys_double_bpps[phy_id] |= BIT(bpp);
> + context->pipes_double_bpps[pipe_id] |= BIT(bpp);
> + } else {
> + undoubled_bpps_phys[phy_id] |= BIT(bpp);
> + }
> +
> + if (min_bpp == 8 && max_bpp > 8) {
> + context->phys_bpp8_shared_with_16[phy_id] = true;
> + context->pipes_bpp8_shared_with_16[pipe_id] = true;
> + } else if (min_bpp == 8 && max_bpp == 8) {
> + bpp8_not_shared_with_16_phys[phy_id] = true;
> + }
> + }
> +
> + for (i = 0; i < des->info->num_phys; i++) {
> + if (context->phys_bpp8_shared_with_16[i] && bpp8_not_shared_with_16_phys[i]) {
> + dev_err(priv->dev,
> + "Cannot stream 8bpp coming from pipes padded to 16bpp "
> + "and pipes not padded to 16bpp on the same PHY\n");
WARNING: quoted string split across lines
#747: FILE: drivers/media/i2c/maxim-serdes/max_des.c:699:
+ "Cannot stream 8bpp coming from pipes padded to 16bpp "
+ "and pipes not padded to 16bpp on the
same PHY\n");
> + return -EINVAL;
> + }
> + }
> +
> + for (i = 0; i < des->info->num_phys; i++)
> + context->phys_double_bpps[i] &= ~undoubled_bpps_phys[i];
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_des_route_hw hw;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + context->pipes_double_bpps[hw.pipe->index] &=
> + context->phys_double_bpps[hw.phy->index];
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_add_vc_remap(struct max_des *des, struct max_serdes_vc_remap *vc_remaps,
> + unsigned int *num_vc_remaps, unsigned int src_vc_id,
> + unsigned int dst_vc_id)
> +{
> + struct max_serdes_vc_remap *vc_remap;
> + unsigned int i;
> +
> + for (i = 0; i < *num_vc_remaps; i++) {
> + vc_remap = &vc_remaps[i];
> +
> + if (vc_remap->src == src_vc_id && vc_remap->dst == dst_vc_id)
> + return 0;
> + }
> +
> + if (*num_vc_remaps == MAX_SERDES_VC_ID_NUM)
> + return -E2BIG;
> +
> + vc_remaps[*num_vc_remaps].src = src_vc_id;
> + vc_remaps[*num_vc_remaps].dst = dst_vc_id;
> +
> + (*num_vc_remaps)++;
> +
> + return 0;
> +}
> +
> +static int max_des_get_pipe_vc_remaps(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct max_des_pipe *pipe,
> + struct max_serdes_vc_remap *vc_remaps,
> + unsigned int *num_vc_remaps,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks, bool with_tpg)
> +{
> + struct max_des *des = priv->des;
> + struct v4l2_subdev_route *route;
> + int ret;
> +
> + *num_vc_remaps = 0;
> +
> + if (context->mode != MAX_SERDES_GMSL_TUNNEL_MODE)
> + return 0;
> +
> + for_each_active_route(&state->routing, route) {
> + unsigned int src_vc_id, dst_vc_id;
> + struct max_des_route_hw hw;
> +
> + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> + continue;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (!with_tpg && hw.is_tpg)
> + continue;
> +
> + if (hw.pipe != pipe)
> + continue;
> +
> + src_vc_id = hw.entry.bus.csi2.vc;
> +
> + ret = max_des_get_src_dst_vc_id(context, pipe->index, hw.phy->index,
> + src_vc_id, &dst_vc_id);
> + if (ret)
> + return ret;
> +
> + ret = max_des_add_vc_remap(des, vc_remaps, num_vc_remaps,
> + src_vc_id, dst_vc_id);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void max_des_get_pipe_mode(struct max_des_mode_context *context,
> + struct max_des_pipe *pipe,
> + struct max_des_pipe_mode *mode)
> +{
> + u32 double_bpps = context->pipes_double_bpps[pipe->index];
> +
> + if ((double_bpps & BIT(8)) &&
> + !context->pipes_bpp8_shared_with_16[pipe->index]) {
> + mode->dbl8 = true;
> + mode->dbl8mode = true;
> + }
> +}
> +
> +static void max_des_get_phy_mode(struct max_des_mode_context *context,
> + struct max_des_phy *phy,
> + struct max_des_phy_mode *mode)
> +{
> + bool bpp8_pipe_shared_with_16 = context->phys_bpp8_shared_with_16[phy->index];
> + u32 double_bpps = context->phys_double_bpps[phy->index];
> +
> + if (BIT(8) & double_bpps) {
> + if (bpp8_pipe_shared_with_16)
> + mode->alt2_mem_map8 = true;
> + else
> + mode->alt_mem_map8 = true;
> + }
> +
> + if (BIT(10) & double_bpps)
> + mode->alt_mem_map10 = true;
> +
> + if (BIT(12) & double_bpps)
> + mode->alt_mem_map12 = true;
> +}
> +
> +static int max_des_set_modes(struct max_des_priv *priv,
> + struct max_des_mode_context *context)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < des->info->num_phys; i++) {
> + struct max_des_phy *phy = &des->phys[i];
> + struct max_des_phy_mode mode = { 0 };
> +
> + max_des_get_phy_mode(context, phy, &mode);
> +
> + if (phy->mode.alt_mem_map8 == mode.alt_mem_map8 &&
> + phy->mode.alt_mem_map10 == mode.alt_mem_map10 &&
> + phy->mode.alt_mem_map12 == mode.alt_mem_map12 &&
> + phy->mode.alt2_mem_map8 == mode.alt2_mem_map8)
> + continue;
> +
> + if (des->ops->set_phy_mode) {
> + ret = des->ops->set_phy_mode(des, phy, &mode);
> + if (ret)
> + return ret;
> + }
> +
> + phy->mode = mode;
> + }
> +
> + for (i = 0; i < des->info->num_pipes; i++) {
> + struct max_des_pipe *pipe = &des->pipes[i];
> + struct max_des_pipe_mode mode = { 0 };
> +
> + max_des_get_pipe_mode(context, pipe, &mode);
> +
> + if (pipe->mode.dbl8 == mode.dbl8 &&
> + pipe->mode.dbl10 == mode.dbl10 &&
> + pipe->mode.dbl12 == mode.dbl12 &&
> + pipe->mode.dbl8mode == mode.dbl8mode &&
> + pipe->mode.dbl10mode == mode.dbl10mode)
> + continue;
> +
> + if (des->ops->set_pipe_mode) {
> + ret = des->ops->set_pipe_mode(des, pipe, &mode);
> + if (ret)
> + return ret;
> + }
> +
> + pipe->mode = mode;
> + }
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link_hw hw;
> + u32 pipe_double_bpps = 0;
> +
> + ret = max_des_link_index_to_hw(priv, i, &hw);
> + if (ret)
> + return ret;
> +
> + if (!hw.link->enabled)
> + continue;
> +
> + if (!hw.source->sd)
> + continue;
> +
> + pipe_double_bpps = context->pipes_double_bpps[hw.pipe->index];
> +
> + ret = max_ser_set_double_bpps(hw.source->sd, pipe_double_bpps);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_set_tunnel(struct max_des_priv *priv,
> + struct max_des_remap_context *context)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + if (des->ops->set_pipe_tunnel_enable) {
> + for (i = 0; i < des->info->num_pipes; i++) {
> + struct max_des_pipe *pipe = &des->pipes[i];
> + bool tunnel_mode = context->mode == MAX_SERDES_GMSL_TUNNEL_MODE;
> +
> + ret = des->ops->set_pipe_tunnel_enable(des, pipe, tunnel_mode);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link_hw hw;
> +
> + ret = max_des_link_index_to_hw(priv, i, &hw);
> + if (ret)
> + return ret;
> +
> + if (!hw.link->enabled)
> + continue;
> +
> + if (!hw.source->sd)
> + continue;
> +
> + if (!context->pipe_in_use[hw.pipe->index])
> + continue;
> +
> + ret = max_ser_set_mode(hw.source->sd, context->mode);
> + if (ret)
> + return ret;
> + }
> +
> + des->mode = context->mode;
> +
> + return 0;
> +}
> +
> +static int max_des_set_vc_remaps(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + if (des->ops->set_pipe_vc_remap)
> + return 0;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_serdes_vc_remap vc_remaps[MAX_SERDES_VC_ID_NUM];
> + struct max_des_link_hw hw;
> + unsigned int num_vc_remaps;
> +
> + ret = max_des_link_index_to_hw(priv, i, &hw);
> + if (ret)
> + return ret;
> +
> + if (!hw.link->enabled)
> + continue;
> +
> + if (!hw.source->sd)
> + continue;
> +
> + if (!max_ser_supports_vc_remap(hw.source->sd))
> + continue;
> +
> + ret = max_des_get_pipe_vc_remaps(priv, context, hw.pipe,
> + vc_remaps, &num_vc_remaps,
> + state, streams_masks, false);
> + if (ret)
> + return ret;
> +
> + ret = max_ser_set_vc_remaps(hw.source->sd, vc_remaps, num_vc_remaps);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_set_pipes_stream_id(struct max_des_priv *priv)
> +{
> + bool stream_id_usage[MAX_SERDES_STREAMS_NUM] = { 0 };
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link_hw hw;
> + unsigned int stream_id;
> +
> + ret = max_des_link_index_to_hw(priv, i, &hw);
> + if (ret)
> + return ret;
> +
> + if (!hw.link->enabled)
> + continue;
> +
> + if (!hw.source->sd)
> + continue;
> +
> + stream_id = hw.pipe->stream_id;
> +
> + ret = max_ser_set_stream_id(hw.source->sd, stream_id);
> + if (ret == -EOPNOTSUPP) {
> + /*
> + * Serializer does not support setting the stream id,
> + * retrieve its hardcoded stream id.
> + */
> + ret = max_ser_get_stream_id(hw.source->sd, &stream_id);
> + }
> +
> + if (ret)
> + return ret;
> +
> + if (stream_id >= MAX_SERDES_STREAMS_NUM) {
> + dev_err(priv->dev, "Invalid stream id %u\n", stream_id);
> + return -EINVAL;
> + }
> +
> + if (stream_id_usage[stream_id] && des->info->needs_unique_stream_id) {
> + dev_err(priv->dev, "Duplicate stream id %u\n", stream_id);
> + return -EINVAL;
> + }
> +
> + ret = des->ops->set_pipe_stream_id(des, hw.pipe, stream_id);
> + if (ret)
> + return ret;
> +
> + stream_id_usage[stream_id] = true;
> + hw.pipe->stream_id = stream_id;
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_set_pipes_phy(struct max_des_priv *priv,
> + struct max_des_remap_context *context)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + if (!des->ops->set_pipe_phy && !des->ops->set_pipe_tunnel_phy)
> + return 0;
> +
> + for (i = 0; i < des->info->num_pipes; i++) {
> + struct max_des_pipe *pipe = &des->pipes[i];
> + struct max_des_phy *phy;
> + unsigned int phy_id;
> +
> + phy_id = find_first_bit(&context->pipe_phy_masks[pipe->index],
> + des->info->num_phys);
> +
> + if (priv->unused_phy &&
> + (context->mode != MAX_SERDES_GMSL_TUNNEL_MODE ||
> + phy_id == des->info->num_phys))
> + phy_id = priv->unused_phy->index;
> +
> + if (phy_id != des->info->num_phys) {
> + phy = &des->phys[phy_id];
> +
> + if (context->mode == MAX_SERDES_GMSL_PIXEL_MODE &&
> + des->ops->set_pipe_phy)
> + ret = des->ops->set_pipe_phy(des, pipe, phy);
> + else if (context->mode == MAX_SERDES_GMSL_TUNNEL_MODE &&
> + des->ops->set_pipe_tunnel_phy)
> + ret = des->ops->set_pipe_tunnel_phy(des, pipe, phy);
> + else
> + ret = 0;
> +
> + if (ret)
> + return ret;
> + }
> +
> + pipe->phy_id = phy_id;
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_add_remap(struct max_des *des, struct max_des_remap *remaps,
> + unsigned int *num_remaps, unsigned int phy_id,
> + unsigned int src_vc_id, unsigned int dst_vc_id,
> + unsigned int dt)
> +{
> + struct max_des_remap *remap;
> + unsigned int i;
> +
> + for (i = 0; i < *num_remaps; i++) {
> + remap = &remaps[i];
> +
> + if (remap->from_dt == dt && remap->to_dt == dt &&
> + remap->from_vc == src_vc_id && remap->to_vc == dst_vc_id &&
> + remap->phy == phy_id)
> + return 0;
> + }
> +
> + if (*num_remaps == des->info->num_remaps_per_pipe)
> + return -E2BIG;
> +
> + remap = &remaps[*num_remaps];
> + remap->from_dt = dt;
> + remap->from_vc = src_vc_id;
> + remap->to_dt = dt;
> + remap->to_vc = dst_vc_id;
> + remap->phy = phy_id;
> +
> + (*num_remaps)++;
> +
> + return 0;
> +}
> +
> +static int max_des_add_remaps(struct max_des *des, struct max_des_remap *remaps,
> + unsigned int *num_remaps, unsigned int phy_id,
> + unsigned int src_vc_id, unsigned int dst_vc_id,
> + unsigned int dt)
> +{
> + int ret;
> +
> + ret = max_des_add_remap(des, remaps, num_remaps, phy_id,
> + src_vc_id, dst_vc_id, dt);
> + if (ret)
> + return ret;
> +
> + ret = max_des_add_remap(des, remaps, num_remaps, phy_id,
> + src_vc_id, dst_vc_id, MIPI_CSI2_DT_FS);
> + if (ret)
> + return ret;
> +
> + ret = max_des_add_remap(des, remaps, num_remaps, phy_id,
> + src_vc_id, dst_vc_id, MIPI_CSI2_DT_FE);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int max_des_get_pipe_remaps(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct max_des_pipe *pipe,
> + struct max_des_remap *remaps,
> + unsigned int *num_remaps,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct v4l2_mbus_frame_desc_entry tpg_entry = { 0 };
> + struct max_des *des = priv->des;
> + struct v4l2_subdev_route *route;
> + bool is_tpg_pipe = true;
> + int ret;
> +
> + *num_remaps = 0;
> +
> + if (context->mode != MAX_SERDES_GMSL_PIXEL_MODE)
> + return 0;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_des_route_hw hw;
> + unsigned int src_vc_id, dst_vc_id;
> +
> + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> + continue;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (hw.is_tpg && hw.pipe != pipe) {
> + is_tpg_pipe = false;
> + tpg_entry = hw.entry;
> + }
> +
> + if (hw.pipe != pipe)
> + continue;
> +
> + src_vc_id = hw.entry.bus.csi2.vc;
> +
> + ret = max_des_get_src_dst_vc_id(context, pipe->index, hw.phy->index,
> + src_vc_id, &dst_vc_id);
> + if (ret)
> + return ret;
> +
> + ret = max_des_add_remaps(des, remaps, num_remaps, hw.phy->index,
> + src_vc_id, dst_vc_id,
> + hw.entry.bus.csi2.dt);
> + if (ret)
> + return ret;
> + }
> +
> + /*
> + * TPG mode is only handled on pipe 0, but the TPG pollutes other pipes
> + * with the same data.
> + * For devices that do not support setting the default PHY of a pipe,
> + * we want to filter out this data so it does not end up on the wrong
> + * PHY.
> + * Devices that support setting the default PHY of a pipe already use it
> + * to route unused pipes to an unused PHY.
> + */
> + if (context->tpg && !is_tpg_pipe && !des->ops->set_pipe_phy &&
> + priv->unused_phy) {
> + ret = max_des_add_remaps(des, remaps, num_remaps,
> + priv->unused_phy->index,
> + tpg_entry.bus.csi2.vc,
> + tpg_entry.bus.csi2.vc,
> + tpg_entry.bus.csi2.dt);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_update_pipe_vc_remaps(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct max_des_pipe *pipe,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_des *des = priv->des;
> + struct max_serdes_vc_remap *vc_remaps;
> + unsigned int num_vc_remaps;
> + int ret;
> +
> + if (!des->ops->set_pipe_vc_remap)
> + return 0;
> +
> + vc_remaps = devm_kcalloc(priv->dev, MAX_SERDES_VC_ID_NUM,
> + sizeof(*vc_remaps), GFP_KERNEL);
> + if (!vc_remaps)
> + return -ENOMEM;
> +
> + ret = max_des_get_pipe_vc_remaps(priv, context, pipe, vc_remaps, &num_vc_remaps,
> + state, streams_masks, true);
> + if (ret)
> + goto err_free_new_vc_remaps;
> +
> + ret = max_des_set_pipe_vc_remaps(priv, pipe, vc_remaps, num_vc_remaps);
> + if (ret)
> + goto err_free_new_vc_remaps;
> +
> + if (pipe->vc_remaps)
> + devm_kfree(priv->dev, pipe->vc_remaps);
> +
> + pipe->vc_remaps = vc_remaps;
> + pipe->num_vc_remaps = num_vc_remaps;
> +
> + return 0;
> +
> +err_free_new_vc_remaps:
> + devm_kfree(priv->dev, vc_remaps);
> +
> + return ret;
> +}
> +
> +static int max_des_update_pipe_remaps(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct max_des_pipe *pipe,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_des *des = priv->des;
> + struct max_des_remap *remaps;
> + unsigned int num_remaps;
> + int ret;
> +
> + if (!des->ops->set_pipe_remap)
> + return 0;
> +
> + remaps = devm_kcalloc(priv->dev, des->info->num_remaps_per_pipe,
> + sizeof(*remaps), GFP_KERNEL);
> + if (!remaps)
> + return -ENOMEM;
> +
> + ret = max_des_get_pipe_remaps(priv, context, pipe, remaps, &num_remaps,
> + state, streams_masks);
> + if (ret)
> + goto err_free_new_remaps;
> +
> + ret = max_des_set_pipe_remaps(priv, pipe, remaps, num_remaps);
> + if (ret)
> + goto err_free_new_remaps;
> +
> + if (pipe->remaps)
> + devm_kfree(priv->dev, pipe->remaps);
> +
> + pipe->remaps = remaps;
> + pipe->num_remaps = num_remaps;
> +
> + return 0;
> +
> +err_free_new_remaps:
> + devm_kfree(priv->dev, remaps);
> +
> + return ret;
> +}
> +
> +static int max_des_update_pipe_enable(struct max_des_priv *priv,
> + struct max_des_pipe *pipe,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_des *des = priv->des;
> + struct v4l2_subdev_route *route;
> + bool enable = false;
> + int ret;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_des_route_hw hw;
> +
> + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> + continue;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (hw.pipe != pipe)
> + continue;
> +
> + enable = true;
> + break;
> + }
> +
> + if (enable == pipe->enabled)
> + return 0;
> +
> + ret = des->ops->set_pipe_enable(des, pipe, enable);
> + if (ret)
> + return ret;
> +
> + pipe->enabled = enable;
> +
> + return 0;
> +}
> +
> +static int max_des_update_pipe(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct max_des_pipe *pipe,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + int ret;
> +
> + ret = max_des_update_pipe_remaps(priv, context, pipe,
> + state, streams_masks);
> + if (ret)
> + return ret;
> +
> + ret = max_des_update_pipe_vc_remaps(priv, context, pipe, state,
> + streams_masks);
> + if (ret)
> + goto err_revert_update_pipe_remaps;
> +
> + ret = max_des_update_pipe_enable(priv, pipe, state, streams_masks);
> + if (ret)
> + goto err_revert_update_pipe_vc_remaps;
> +
> + return 0;
> +
> +err_revert_update_pipe_vc_remaps:
> + max_des_update_pipe_vc_remaps(priv, context, pipe, state,
> + priv->streams_masks);
> +
> +err_revert_update_pipe_remaps:
> + max_des_update_pipe_remaps(priv, context, pipe, state,
> + priv->streams_masks);
> +
> + return ret;
> +}
> +
> +static int max_des_init_link_ser_xlate(struct max_des_priv *priv,
> + struct max_des_link *link,
> + struct i2c_adapter *adapter,
> + u8 power_up_addr, u8 new_addr)
> +{
> + struct max_des *des = priv->des;
> + u8 addrs[] = { power_up_addr, new_addr };
> + u8 current_addr;
> + int ret;
> +
> + ret = des->ops->select_links(des, BIT(link->index));
> + if (ret)
> + return ret;
> +
> + ret = max_ser_wait_for_multiple(adapter, addrs, ARRAY_SIZE(addrs),
> + ¤t_addr);
> + if (ret) {
> + dev_err(priv->dev,
> + "Failed to wait for serializer at 0x%02x or 0x%02x: %d\n",
> + power_up_addr, new_addr, ret);
> + return ret;
> + }
> +
> + ret = max_ser_reset(adapter, current_addr);
> + if (ret) {
> + dev_err(priv->dev, "Failed to reset serializer: %d\n", ret);
> + return ret;
> + }
> +
> + ret = max_ser_wait(adapter, power_up_addr);
> + if (ret) {
> + dev_err(priv->dev,
> + "Failed to wait for serializer at 0x%02x: %d\n",
> + power_up_addr, ret);
> + return ret;
> + }
> +
> + ret = max_ser_change_address(adapter, power_up_addr, new_addr);
> + if (ret) {
> + dev_err(priv->dev,
> + "Failed to change serializer from 0x%02x to 0x%02x: %d\n",
> + power_up_addr, new_addr, ret);
> + return ret;
> + }
> +
> + ret = max_ser_wait(adapter, new_addr);
> + if (ret) {
> + dev_err(priv->dev,
> + "Failed to wait for serializer at 0x%02x: %d\n",
> + new_addr, ret);
> + return ret;
> + }
> +
> + if (des->info->fix_tx_ids) {
> + ret = max_ser_fix_tx_ids(adapter, new_addr);
> + if (ret)
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static int max_des_init(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + if (des->ops->init) {
> + ret = des->ops->init(des);
> + if (ret)
> + return ret;
> + }
> +
> + if (des->ops->set_enable) {
> + ret = des->ops->set_enable(des, false);
> + if (ret)
> + return ret;
> + }
> +
> + for (i = 0; i < des->info->num_phys; i++) {
> + struct max_des_phy *phy = &des->phys[i];
> +
> + if (phy->enabled) {
> + ret = des->ops->init_phy(des, phy);
> + if (ret)
> + return ret;
> + }
> +
> + ret = des->ops->set_phy_enable(des, phy, phy->enabled);
> + if (ret)
> + return ret;
> + }
> +
> + for (i = 0; i < des->info->num_pipes; i++) {
> + struct max_des_pipe *pipe = &des->pipes[i];
> + struct max_des_link *link = &des->links[pipe->link_id];
> +
> + ret = des->ops->set_pipe_enable(des, pipe, false);
> + if (ret)
> + return ret;
> +
> + if (des->ops->set_pipe_tunnel_enable) {
> + ret = des->ops->set_pipe_tunnel_enable(des, pipe, false);
> + if (ret)
> + return ret;
> + }
> +
> + if (des->ops->set_pipe_stream_id) {
> + ret = des->ops->set_pipe_stream_id(des, pipe, pipe->stream_id);
> + if (ret)
> + return ret;
> + }
> +
> + if (des->ops->set_pipe_link) {
> + ret = des->ops->set_pipe_link(des, pipe, link);
> + if (ret)
> + return ret;
> + }
> +
> + ret = max_des_set_pipe_remaps(priv, pipe, pipe->remaps,
> + pipe->num_remaps);
> + if (ret)
> + return ret;
> + }
> +
> + if (!des->ops->init_link)
> + return 0;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + if (!link->enabled)
> + continue;
> +
> + ret = des->ops->init_link(des, link);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void max_des_ser_find_version_range(struct max_des *des, int *min, int *max)
> +{
> + unsigned int i;
> +
> + *min = MAX_SERDES_GMSL_MIN;
> + *max = MAX_SERDES_GMSL_MAX;
> +
> + if (!des->info->needs_single_link_version)
> + return;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + if (!link->enabled)
> + continue;
> +
> + if (!link->ser_xlate.en)
> + continue;
> +
> + *min = *max = link->version;
> +
> + return;
> + }
> +}
> +
> +static int max_des_ser_attach_addr(struct max_des_priv *priv, u32 chan_id,
> + u16 addr, u16 alias)
> +{
> + struct max_des *des = priv->des;
> + struct max_des_link *link = &des->links[chan_id];
> + int i, min, max;
> + int ret = 0;
> +
> + max_des_ser_find_version_range(des, &min, &max);
> +
> + if (link->ser_xlate.en) {
> + dev_err(priv->dev, "Serializer for link %u already bound\n",
> + link->index);
> + return -EINVAL;
> + }
> +
> + for (i = max; i >= min; i--) {
> + if (!(des->info->versions & BIT(i)))
> + continue;
> +
> + if (des->ops->set_link_version) {
> + ret = des->ops->set_link_version(des, link, i);
> + if (ret)
> + return ret;
> + }
> +
> + ret = max_des_init_link_ser_xlate(priv, link, priv->client->adapter,
> + addr, alias);
> + if (!ret)
> + break;
> + }
> +
> + if (ret) {
> + dev_err(priv->dev, "Cannot find serializer for link %u\n",
> + link->index);
> + return -ENOENT;
> + }
> +
> + link->version = i;
> + link->ser_xlate.src = alias;
> + link->ser_xlate.dst = addr;
> + link->ser_xlate.en = true;
> +
> + return 0;
> +}
> +
> +static int max_des_ser_atr_attach_addr(struct i2c_atr *atr, u32 chan_id,
> + u16 addr, u16 alias)
> +{
> + struct max_des_priv *priv = i2c_atr_get_driver_data(atr);
> +
> + return max_des_ser_attach_addr(priv, chan_id, addr, alias);
> +}
> +
> +static void max_des_ser_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, u16 addr)
> +{
> + /* Don't do anything. */
> +}
> +
> +static const struct i2c_atr_ops max_des_i2c_atr_ops = {
> + .attach_addr = max_des_ser_atr_attach_addr,
> + .detach_addr = max_des_ser_atr_detach_addr,
> +};
> +
> +static void max_des_i2c_atr_deinit(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + /* Deleting adapters that haven't been added does no harm. */
> + i2c_atr_del_adapter(priv->atr, link->index);
> + }
> +
> + i2c_atr_delete(priv->atr);
> +}
> +
> +static int max_des_i2c_atr_init(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> + unsigned int mask = 0;
> + unsigned int i;
> + int ret;
> +
> + if (!i2c_check_functionality(priv->client->adapter,
> + I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
> + return -ENODEV;
> +
> + priv->atr = i2c_atr_new(priv->client->adapter, priv->dev,
> + &max_des_i2c_atr_ops, des->info->num_links,
> + I2C_ATR_F_STATIC | I2C_ATR_F_PASSTHROUGH);
> + if (IS_ERR(priv->atr))
> + return PTR_ERR(priv->atr);
> +
> + i2c_atr_set_driver_data(priv->atr, priv);
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> + struct i2c_atr_adap_desc desc = {
> + .chan_id = i,
> + };
> +
> + if (!link->enabled)
> + continue;
> +
> + ret = i2c_atr_add_adapter(priv->atr, &desc);
> + if (ret)
> + goto err_add_adapters;
> + }
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + if (!link->enabled)
> + continue;
> +
> + mask |= BIT(link->index);
> + }
> +
> + ret = des->ops->select_links(des, mask);
> + if (ret)
> + goto err_add_adapters;
> +
> + return 0;
> +
> +err_add_adapters:
> + max_des_i2c_atr_deinit(priv);
> +
> + return ret;
> +}
> +
> +static void max_des_i2c_mux_deinit(struct max_des_priv *priv)
> +{
> + i2c_mux_del_adapters(priv->mux);
> + bus_unregister_notifier(&i2c_bus_type, &priv->i2c_nb);
> +}
> +
> +static int max_des_i2c_mux_bus_notifier_call(struct notifier_block *nb,
> + unsigned long event, void *device)
> +{
> + struct max_des_priv *priv = container_of(nb, struct max_des_priv, i2c_nb);
> + struct max_des *des = priv->des;
> + struct device *dev = device;
> + struct i2c_client *client;
> + unsigned int i;
> +
> + /*
> + * Ideally, we would want to negotiate the GMSL version on
> + * BUS_NOTIFY_ADD_DEVICE, but the adapters list is only populated with
> + * the new adapter after BUS_NOTIFY_ADD_DEVICE is issued.
> + */
> + if (event != BUS_NOTIFY_BIND_DRIVER)
> + return NOTIFY_DONE;
> +
> + client = i2c_verify_client(dev);
> + if (!client)
> + return NOTIFY_DONE;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + if (des->links[i].enabled &&
> + client->adapter == des->links[i].adapter)
> + break;
> + }
> +
> + if (i == des->info->num_links)
> + return NOTIFY_DONE;
> +
> + max_des_ser_attach_addr(priv, i, client->addr, client->addr);
> +
> + return NOTIFY_DONE;
> +}
> +
> +static int max_des_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
> +{
> + struct max_des_priv *priv = i2c_mux_priv(muxc);
> + struct max_des *des = priv->des;
> +
> + if (!des->ops->select_links)
> + return 0;
> +
> + return des->ops->select_links(des, BIT(chan));
> +}
> +
> +static int max_des_i2c_mux_init(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> + u32 flags = I2C_MUX_LOCKED;
> + unsigned int i;
> + int ret;
> +
> + if (des->info->num_links == 1)
> + flags |= I2C_MUX_GATE;
> +
> + priv->mux = i2c_mux_alloc(priv->client->adapter, priv->dev,
> + des->info->num_links, 0, flags,
> + max_des_i2c_mux_select, NULL);
> + if (!priv->mux)
> + return -ENOMEM;
> +
> + priv->mux->priv = priv;
> +
> + priv->i2c_nb.notifier_call = max_des_i2c_mux_bus_notifier_call;
> + ret = bus_register_notifier(&i2c_bus_type, &priv->i2c_nb);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + if (!link->enabled)
> + continue;
> +
> + ret = i2c_mux_add_adapter(priv->mux, 0, i);
> + if (ret)
> + goto err_add_adapters;
> +
> + link->adapter = priv->mux->adapter[priv->mux->num_adapters - 1];
> + }
> +
> + return 0;
> +
> +err_add_adapters:
> + max_des_i2c_mux_deinit(priv);
> +
> + return ret;
> +}
> +
> +static void max_des_i2c_adapter_deinit(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> +
> + if (des->info->use_atr)
> + return max_des_i2c_atr_deinit(priv);
> + else
> + return max_des_i2c_mux_deinit(priv);
> +}
> +
> +static int max_des_i2c_adapter_init(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> +
> + if (des->info->use_atr)
> + return max_des_i2c_atr_init(priv);
> + else
> + return max_des_i2c_mux_init(priv);
> +
> + return 0;
> +}
> +
> +static int max_des_set_tpg_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct v4l2_mbus_framefmt *fmt = &format->format;
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> + const struct max_serdes_tpg_entry *entry;
> + struct v4l2_fract *in;
> +
> + if (format->stream != MAX_SERDES_TPG_STREAM)
> + return -EINVAL;
> +
> + entry = max_des_find_tpg_entry(des, 0, fmt->width, fmt->height,
> + fmt->code, 0, 0);
> + if (!entry)
> + return -EINVAL;
> +
> + in = v4l2_subdev_state_get_interval(state, format->pad, format->stream);
> + if (!in)
> + return -EINVAL;
> +
> + in->numerator = entry->interval.numerator;
> + in->denominator = entry->interval.denominator;
> +
> + return 0;
> +}
> +
> +static int max_des_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> + struct v4l2_mbus_framefmt *fmt;
> + int ret;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active)
> + return -EBUSY;
> +
> + /* No transcoding, source and sink formats must match. */
> + if (max_des_pad_is_source(des, format->pad))
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + if (max_des_pad_is_tpg(des, format->pad)) {
> + ret = max_des_set_tpg_fmt(sd, state, format);
> + if (ret)
> + return ret;
> + }
> +
> + if (format->format.code == ~0U)
> + format->format.code = MEDIA_BUS_FMT_UYVY8_1X16;
> +
> + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = format->format;
> +
> + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> + format->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = format->format;
> +
> + return 0;
> +}
> +
> +static int max_des_enum_frame_interval(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_interval_enum *fie)
> +{
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> + const struct max_serdes_tpg_entry *entry;
> +
> + if (!max_des_pad_is_tpg(des, fie->pad) ||
> + fie->stream != MAX_SERDES_TPG_STREAM)
> + return -ENOTTY;
> +
> + entry = max_des_find_tpg_entry(des, fie->index, fie->width, fie->height,
> + fie->code, fie->interval.denominator,
> + fie->interval.numerator);
> + if (!entry)
> + return -EINVAL;
> +
> + fie->interval.numerator = entry->interval.numerator;
> + fie->interval.denominator = entry->interval.denominator;
> +
> + return 0;
> +}
> +
> +static int max_des_get_frame_interval(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_interval *fi)
> +{
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> +
> + if (!max_des_pad_is_tpg(des, fi->pad) ||
> + fi->stream != MAX_SERDES_TPG_STREAM)
> + return -ENOTTY;
> +
> + return v4l2_subdev_get_frame_interval(sd, state, fi);
> +}
> +
> +static int max_des_set_frame_interval(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_interval *fi)
> +{
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> + const struct max_serdes_tpg_entry *entry;
> + struct v4l2_mbus_framefmt *fmt;
> + struct v4l2_fract *in;
> +
> + if (!max_des_pad_is_tpg(des, fi->pad) ||
> + fi->stream != MAX_SERDES_TPG_STREAM)
> + return -ENOTTY;
> +
> + if (fi->which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active)
> + return -EBUSY;
> +
> + fmt = v4l2_subdev_state_get_format(state, fi->pad, fi->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + entry = max_des_find_tpg_entry(des, 0, fmt->width, fmt->height,
> + fmt->code, fi->interval.denominator,
> + fi->interval.numerator);
> + if (!entry)
> + return -EINVAL;
> +
> + in = v4l2_subdev_state_get_interval(state, fi->pad, fi->stream);
> + if (!in)
> + return -EINVAL;
> +
> + in->numerator = fi->interval.numerator;
> + in->denominator = fi->interval.denominator;
> +
> + return 0;
> +}
> +
> +static int max_des_log_status(struct v4l2_subdev *sd)
> +{
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> + unsigned int i, j;
> + int ret;
> +
> + v4l2_info(sd, "active: %u\n", des->active);
> + v4l2_info(sd, "mode: %s", max_serdes_gmsl_mode_str(des->mode));
> + if (des->ops->set_tpg) {
> + const struct max_serdes_tpg_entry *entry = des->tpg_entry;
> +
> + if (entry) {
> + v4l2_info(sd, "tpg: %ux%u@%u/%u, code: %u, dt: %u, bpp: %u\n",
> + entry->width, entry->height,
> + entry->interval.numerator,
> + entry->interval.denominator,
> + entry->code, entry->dt, entry->bpp);
> + } else {
> + v4l2_info(sd, "tpg: disabled\n");
> + }
> + }
> + if (des->ops->log_status) {
> + ret = des->ops->log_status(des);
> + if (ret)
> + return ret;
> + }
> + v4l2_info(sd, "\n");
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + v4l2_info(sd, "link: %u\n", link->index);
> + v4l2_info(sd, "\tenabled: %u\n", link->enabled);
> +
> + if (!link->enabled) {
> + v4l2_info(sd, "\n");
> + continue;
> + }
> +
> + v4l2_info(sd, "\tversion: %s\n", max_serdes_gmsl_version_str(link->version));
> + v4l2_info(sd, "\tser_xlate: en: %u, src: 0x%02x dst: 0x%02x\n",
> + link->ser_xlate.en, link->ser_xlate.src,
> + link->ser_xlate.dst);
> + v4l2_info(sd, "\n");
> + }
> +
> + for (i = 0; i < des->info->num_pipes; i++) {
> + struct max_des_pipe *pipe = &des->pipes[i];
> +
> + v4l2_info(sd, "pipe: %u\n", pipe->index);
> + v4l2_info(sd, "\tenabled: %u\n", pipe->enabled);
> + if (pipe->phy_id == des->info->num_phys ||
> + (priv->unused_phy && pipe->phy_id == priv->unused_phy->index))
> + v4l2_info(sd, "\tphy_id: invalid\n");
> + else
> + v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
> + v4l2_info(sd, "\tlink_id: %u\n", pipe->link_id);
> + if (des->ops->set_pipe_stream_id)
> + v4l2_info(sd, "\tstream_id: %u\n", pipe->stream_id);
> + if (des->ops->set_pipe_mode) {
> + v4l2_info(sd, "\tdbl8: %u\n", pipe->mode.dbl8);
> + v4l2_info(sd, "\tdbl8mode: %u\n", pipe->mode.dbl8mode);
> + v4l2_info(sd, "\tdbl10: %u\n", pipe->mode.dbl10);
> + v4l2_info(sd, "\tdbl10mode: %u\n", pipe->mode.dbl10mode);
> + v4l2_info(sd, "\tdbl12: %u\n", pipe->mode.dbl12);
> + }
> + if (des->ops->set_pipe_remap) {
> + v4l2_info(sd, "\tremaps: %u\n", pipe->num_remaps);
> + for (j = 0; j < pipe->num_remaps; j++) {
> + struct max_des_remap *remap = &pipe->remaps[j];
> +
> + v4l2_info(sd, "\t\tremap: from: vc: %u, dt: 0x%02x\n",
> + remap->from_vc, remap->from_dt);
> + v4l2_info(sd, "\t\t to: vc: %u, dt: 0x%02x, phy: %u\n",
> + remap->to_vc, remap->to_dt, remap->phy);
> + }
> + }
> + if (des->ops->set_pipe_vc_remap) {
> + v4l2_info(sd, "\tvc_remaps: %u\n", pipe->num_vc_remaps);
> + for (j = 0; j < pipe->num_vc_remaps; j++) {
> + v4l2_info(sd, "\t\tvc_remap: src: %u, dst: %u\n",
> + pipe->vc_remaps[j].src, pipe->vc_remaps[j].dst);
> + }
> + }
> + if (des->ops->log_pipe_status) {
> + ret = des->ops->log_pipe_status(des, pipe);
> + if (ret)
> + return ret;
> + }
> + v4l2_info(sd, "\n");
> + }
> +
> + for (i = 0; i < des->info->num_phys; i++) {
> + struct max_des_phy *phy = &des->phys[i];
> +
> + v4l2_info(sd, "phy: %u\n", phy->index);
> + v4l2_info(sd, "\tenabled: %u\n", phy->enabled);
> +
> + if (!phy->enabled) {
> + v4l2_info(sd, "\n");
> + continue;
> + }
> +
> + v4l2_info(sd, "\tlink_frequency: %llu\n", phy->link_frequency);
> + v4l2_info(sd, "\tnum_data_lanes: %u\n", phy->mipi.num_data_lanes);
> + v4l2_info(sd, "\tclock_lane: %u\n", phy->mipi.clock_lane);
> + if (des->ops->set_phy_mode) {
> + v4l2_info(sd, "\talt_mem_map8: %u\n", phy->mode.alt_mem_map8);
> + v4l2_info(sd, "\talt2_mem_map8: %u\n", phy->mode.alt2_mem_map8);
> + v4l2_info(sd, "\talt_mem_map10: %u\n", phy->mode.alt_mem_map10);
> + v4l2_info(sd, "\talt_mem_map12: %u\n", phy->mode.alt_mem_map12);
> + }
> + if (des->ops->log_phy_status) {
> + ret = des->ops->log_phy_status(des, phy);
> + if (ret)
> + return ret;
> + }
> + v4l2_info(sd, "\n");
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct max_des_priv *priv = ctrl_to_priv(ctrl->handler);
> + struct max_des *des = priv->des;
> +
> + switch (ctrl->id) {
> + case V4L2_CID_TEST_PATTERN:
> + des->tpg_pattern = ctrl->val;
> + return 0;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int max_des_get_frame_desc_state(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_mbus_frame_desc *fd,
> + unsigned int pad)
> +{
> + struct max_des_remap_context context = { 0 };
> + struct max_des_priv *priv = sd_to_priv(sd);
> + struct v4l2_subdev_route *route;
> + int ret;
> +
> + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> + fd->num_entries = 0;
> +
> + ret = max_des_populate_remap_context(priv, &context, state);
> + if (ret)
> + return ret;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_des_route_hw hw;
> + unsigned int dst_vc_id;
> +
> + if (pad != route->source_pad)
> + continue;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + ret = max_des_get_src_dst_vc_id(&context, hw.pipe->index, hw.phy->index,
> + hw.entry.bus.csi2.vc, &dst_vc_id);
> + if (ret)
> + return ret;
> +
> + hw.entry.bus.csi2.vc = dst_vc_id;
> + hw.entry.stream = route->source_stream;
> +
> + fd->entry[fd->num_entries++] = hw.entry;
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
> + struct v4l2_mbus_frame_desc *fd)
> +{
> + struct max_des_priv *priv = sd_to_priv(sd);
> + struct v4l2_subdev_state *state;
> + int ret;
> +
> + state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
> +
> + ret = max_des_get_frame_desc_state(sd, state, fd, pad);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int max_des_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
> + struct v4l2_mbus_config *cfg)
> +{
> + struct max_des_priv *priv = sd_to_priv(sd);
> + struct max_des *des = priv->des;
> + struct max_des_phy *phy;
> +
> + phy = max_des_pad_to_phy(des, pad);
> + if (!phy)
> + return -EINVAL;
> +
> + cfg->type = phy->bus_type;
> + cfg->bus.mipi_csi2 = phy->mipi;
> + cfg->link_freq = phy->link_frequency;
> +
> + return 0;
> +}
> +
> +static int max_des_set_tpg_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct max_des_priv *priv = sd_to_priv(sd);
> + struct max_des *des = priv->des;
> + const struct max_serdes_tpg_entry *entry;
> + struct v4l2_mbus_framefmt fmt = { 0 };
> + int ret;
> +
> + ret = max_serdes_validate_tpg_routing(routing);
> + if (ret)
> + return ret;
> +
> + entry = &des->info->tpg_entries.entries[0];
> +
> + fmt.width = entry->width;
> + fmt.height = entry->height;
> + fmt.code = entry->code;
> +
> + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &fmt);
> +}
> +
> +static int __max_des_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct max_des_priv *priv = sd_to_priv(sd);
> + struct max_des *des = priv->des;
> + struct v4l2_subdev_route *route;
> + bool is_tpg = false;
> + int ret;
> +
> + ret = v4l2_subdev_routing_validate(sd, routing,
> + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
> + V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX);
> + if (ret)
> + return ret;
> +
> + for_each_active_route(routing, route) {
> + if (max_des_pad_is_tpg(des, route->sink_pad)) {
> + is_tpg = true;
> + break;
> + }
> + }
> +
> + if (is_tpg)
> + return max_des_set_tpg_routing(sd, state, routing);
> +
> + static const struct v4l2_mbus_framefmt format = {
> + .code = MEDIA_BUS_FMT_Y8_1X8,
> + .field = V4L2_FIELD_NONE,
> + .width = 640,
> + .height = 480,
> + };
> +
> + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +}
> +
> +static int max_des_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct max_des_priv *priv = sd_to_priv(sd);
> + struct max_des *des = priv->des;
> +
> + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && des->active)
> + return -EBUSY;
> +
> + return __max_des_set_routing(sd, state, routing);
> +}
> +
> +static int max_des_update_link(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct max_des_link *link,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_des *des = priv->des;
> + struct max_des_pipe *pipe;
> + int ret;
> +
> + pipe = max_des_find_link_pipe(des, link);
> + if (!pipe)
> + return -ENOENT;
> +
> + ret = max_des_update_pipe(priv, context, pipe, state, streams_masks);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int max_des_update_tpg(struct max_des_priv *priv,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + const struct max_serdes_tpg_entry *entry = NULL;
> + struct max_des *des = priv->des;
> + struct v4l2_subdev_route *route;
> + int ret;
> +
> + for_each_active_route(&state->routing, route) {
> + struct max_des_route_hw hw;
> +
> + if (!(BIT_ULL(route->sink_stream) & streams_masks[route->sink_pad]))
> + continue;
> +
> + ret = max_des_route_to_hw(priv, state, route, &hw);
> + if (ret)
> + return ret;
> +
> + if (!hw.is_tpg)
> + continue;
> +
> + entry = max_des_find_state_tpg_entry(des, state, route->sink_pad);
> + break;
> + }
> +
> + if (entry == des->tpg_entry)
> + return 0;
> +
> + ret = des->ops->set_tpg(des, entry);
> + if (ret)
> + return ret;
> +
> + des->tpg_entry = entry;
> +
> + return 0;
> +}
> +
> +static int max_des_update_active(struct max_des_priv *priv, u64 *streams_masks,
> + bool expected_active)
> +{
> + struct max_des *des = priv->des;
> + bool active = false;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < des->info->num_phys; i++) {
> + struct max_des_phy *phy = &des->phys[i];
> + u32 pad = max_des_phy_to_pad(des, phy);
> +
> + if (streams_masks[pad]) {
> + active = true;
> + break;
> + }
> + }
> +
> + if (active != expected_active || des->active == active)
> + return 0;
> +
> + if (des->ops->set_enable) {
> + ret = des->ops->set_enable(des, active);
> + if (ret)
> + return ret;
> + }
> +
> + des->active = active;
> +
> + return 0;
> +}
> +
> +static int max_des_update_links(struct max_des_priv *priv,
> + struct max_des_remap_context *context,
> + struct v4l2_subdev_state *state,
> + u64 *streams_masks)
> +{
> + struct max_des *des = priv->des;
> + unsigned int failed_update_link_id = des->info->num_links;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + ret = max_des_update_link(priv, context, link, state,
> + streams_masks);
> + if (ret) {
> + failed_update_link_id = i;
> + goto err;
> + }
> + }
> +
> + return 0;
> +
> +err:
> + for (i = 0; i < failed_update_link_id; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + max_des_update_link(priv, context, link, state,
> + priv->streams_masks);
> + }
> +
> + return ret;
> +}
> +
> +static int max_des_enable_disable_streams(struct max_des_priv *priv,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 updated_streams_mask,
> + bool enable)
> +{
> + struct max_des *des = priv->des;
> +
> + return max_serdes_xlate_enable_disable_streams(priv->sources, 0, state,
> + pad, updated_streams_mask, 0,
> + des->info->num_links, enable);
> +}
> +
> +static int max_des_update_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 updated_streams_mask, bool enable)
> +{
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des_remap_context context = { 0 };
> + struct max_des_mode_context mode_context = { 0 };
> + struct max_des *des = priv->des;
> + unsigned int num_pads = max_des_num_pads(des);
> + u64 *streams_masks;
> + int ret;
> +
> + ret = max_des_populate_remap_context(priv, &context, state);
> + if (ret)
> + return ret;
> +
> + ret = max_des_populate_mode_context(priv, &mode_context, state, context.mode);
> + if (ret)
> + return ret;
> +
> + ret = max_serdes_get_streams_masks(priv->dev, state, pad, updated_streams_mask,
> + num_pads, priv->streams_masks, &streams_masks,
> + enable);
> + if (ret)
> + return ret;
> +
> + ret = max_des_set_pipes_phy(priv, &context);
> + if (ret)
> + goto err_free_streams_masks;
> +
> + ret = max_des_set_tunnel(priv, &context);
> + if (ret)
> + goto err_free_streams_masks;
> +
> + ret = max_des_set_modes(priv, &mode_context);
> + if (ret)
> + goto err_free_streams_masks;
> +
> + ret = max_des_set_vc_remaps(priv, &context, state, streams_masks);
> + if (ret)
> + goto err_free_streams_masks;
> +
> + ret = max_des_set_pipes_stream_id(priv);
> + if (ret)
> + goto err_free_streams_masks;
> +
> + if (!enable) {
> + ret = max_des_enable_disable_streams(priv, state, pad,
> + updated_streams_mask, enable);
> + if (ret)
> + goto err_free_streams_masks;
> + }
> +
> + ret = max_des_update_active(priv, streams_masks, false);
> + if (ret)
> + goto err_revert_streams_disable;
> +
> + ret = max_des_update_links(priv, &context, state, streams_masks);
> + if (ret)
> + goto err_revert_active_disable;
> +
> + ret = max_des_update_tpg(priv, state, streams_masks);
> + if (ret)
> + goto err_revert_links_update;
> +
> + ret = max_des_update_active(priv, streams_masks, true);
> + if (ret)
> + goto err_revert_tpg_update;
> +
> + if (enable) {
> + ret = max_des_enable_disable_streams(priv, state, pad,
> + updated_streams_mask, enable);
> + if (ret)
> + goto err_revert_active_enable;
> + }
> +
> + devm_kfree(priv->dev, priv->streams_masks);
> + priv->streams_masks = streams_masks;
> +
> + return 0;
> +
> +err_revert_active_enable:
> + max_des_update_active(priv, priv->streams_masks, false);
> +
> +err_revert_tpg_update:
> + max_des_update_tpg(priv, state, priv->streams_masks);
> +
> +err_revert_links_update:
> + max_des_update_links(priv, &context, state, priv->streams_masks);
> +
> +err_revert_active_disable:
> + max_des_update_active(priv, priv->streams_masks, true);
> +
> +err_revert_streams_disable:
> + if (!enable)
> + max_des_enable_disable_streams(priv, state, pad,
> + updated_streams_mask, !enable);
> +
> +err_free_streams_masks:
> + devm_kfree(priv->dev, streams_masks);
> +
> + return ret;
> +}
> +
> +static int max_des_enable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 streams_mask)
> +{
> + return max_des_update_streams(sd, state, pad, streams_mask, true);
> +}
> +
> +static int max_des_disable_streams(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + u32 pad, u64 streams_mask)
> +{
> + return max_des_update_streams(sd, state, pad, streams_mask, false);
> +}
> +
> +static int max_des_init_state(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route routes[MAX_DES_NUM_LINKS] = { 0 };
> + struct v4l2_subdev_krouting routing = {
> + .routes = routes,
> + };
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> + struct max_des_phy *phy = NULL;
> + unsigned int stream = 0;
> + unsigned int i;
> +
> + for (i = 0; i < des->info->num_phys; i++) {
> + if (des->phys[i].enabled) {
> + phy = &des->phys[i];
> + break;
> + }
> + }
> +
> + if (!phy)
> + return 0;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + if (!link->enabled)
> + continue;
> +
> + routing.routes[routing.num_routes++] = (struct v4l2_subdev_route) {
> + .sink_pad = max_des_link_to_pad(des, link),
> + .sink_stream = 0,
> + .source_pad = max_des_phy_to_pad(des, phy),
> + .source_stream = stream,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + };
> + stream++;
> +
> + /*
> + * The Streams API is an experimental feature.
> + * If multiple routes are provided here, userspace will not be
> + * able to configure them unless the Streams API is enabled.
> + * Provide a single route until it is enabled.
> + */
> + break;
> + }
> +
> + return __max_des_set_routing(sd, state, &routing);
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int max_des_g_register(struct v4l2_subdev *sd,
> + struct v4l2_dbg_register *reg)
> +{
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> + unsigned int val;
> + int ret;
> +
> + ret = des->ops->reg_read(des, reg->reg, &val);
> + if (ret)
> + return ret;
> +
> + reg->val = val;
> + reg->size = 1;
> +
> + return 0;
> +}
> +
> +static int max_des_s_register(struct v4l2_subdev *sd,
> + const struct v4l2_dbg_register *reg)
> +{
> + struct max_des_priv *priv = v4l2_get_subdevdata(sd);
> + struct max_des *des = priv->des;
> +
> + return des->ops->reg_write(des, reg->reg, reg->val);
> +}
> +#endif
> +
> +static const struct v4l2_subdev_core_ops max_des_core_ops = {
> + .log_status = max_des_log_status,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + .g_register = max_des_g_register,
> + .s_register = max_des_s_register,
> +#endif
> +};
> +
> +static const struct v4l2_ctrl_ops max_des_ctrl_ops = {
> + .s_ctrl = max_des_s_ctrl,
> +};
> +
> +static const struct v4l2_subdev_pad_ops max_des_pad_ops = {
> + .enable_streams = max_des_enable_streams,
> + .disable_streams = max_des_disable_streams,
> +
> + .set_routing = max_des_set_routing,
> + .get_frame_desc = max_des_get_frame_desc,
> +
> + .get_mbus_config = max_des_get_mbus_config,
> +
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = max_des_set_fmt,
> +
> + .enum_frame_interval = max_des_enum_frame_interval,
> + .get_frame_interval = max_des_get_frame_interval,
> + .set_frame_interval = max_des_set_frame_interval,
> +};
> +
> +static const struct v4l2_subdev_ops max_des_subdev_ops = {
> + .core = &max_des_core_ops,
> + .pad = &max_des_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops max_des_internal_ops = {
> + .init_state = &max_des_init_state,
> +};
> +
> +static const struct media_entity_operations max_des_media_ops = {
> + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> + .has_pad_interdep = v4l2_subdev_has_pad_interdep,
> + .link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int max_des_notify_bound(struct v4l2_async_notifier *nf,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_connection *base_asc)
> +{
> + struct max_des_priv *priv = nf_to_priv(nf);
> + struct max_serdes_asc *asc = asc_to_max(base_asc);
> + struct max_serdes_source *source = asc->source;
> + struct max_des *des = priv->des;
> + struct max_des_link *link = &des->links[source->index];
> + u32 pad = max_des_link_to_pad(des, link);
> + int ret;
> +
> + ret = media_entity_get_fwnode_pad(&subdev->entity,
> + source->ep_fwnode,
> + MEDIA_PAD_FL_SOURCE);
> + if (ret < 0) {
> + dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name);
> + return ret;
> + }
> +
> + source->sd = subdev;
> + source->pad = ret;
> +
> + ret = media_create_pad_link(&source->sd->entity, source->pad,
> + &priv->sd.entity, pad,
> + MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);
> + if (ret) {
> + dev_err(priv->dev, "Unable to link %s:%u -> %s:%u\n",
> + source->sd->name, source->pad, priv->sd.name, pad);
> + source->sd = NULL;
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void max_des_notify_unbind(struct v4l2_async_notifier *nf,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_connection *base_asc)
> +{
> + struct max_serdes_asc *asc = asc_to_max(base_asc);
> + struct max_serdes_source *source = asc->source;
> +
> + source->sd = NULL;
> +}
> +
> +static const struct v4l2_async_notifier_operations max_des_notify_ops = {
> + .bound = max_des_notify_bound,
> + .unbind = max_des_notify_unbind,
> +};
> +
> +static int max_des_v4l2_notifier_register(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + v4l2_async_subdev_nf_init(&priv->nf, &priv->sd);
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> + struct max_serdes_source *source;
> + struct max_serdes_asc *asc;
> +
> + if (!link->enabled)
> + continue;
> +
> + source = max_des_get_link_source(priv, link);
> + if (!source->ep_fwnode)
> + continue;
> +
> + asc = v4l2_async_nf_add_fwnode(&priv->nf, source->ep_fwnode,
> + struct max_serdes_asc);
> + if (IS_ERR(asc)) {
> + dev_err(priv->dev,
> + "Failed to add subdev for source %u: %pe", i,
> + asc);
> +
> + v4l2_async_nf_cleanup(&priv->nf);
> +
> + return PTR_ERR(asc);
> + }
> +
> + asc->source = source;
> + }
> +
> + priv->nf.ops = &max_des_notify_ops;
> +
> + ret = v4l2_async_nf_register(&priv->nf);
> + if (ret) {
> + dev_err(priv->dev, "Failed to register subdev notifier");
> + v4l2_async_nf_cleanup(&priv->nf);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void max_des_v4l2_notifier_unregister(struct max_des_priv *priv)
> +{
> + v4l2_async_nf_unregister(&priv->nf);
> + v4l2_async_nf_cleanup(&priv->nf);
> +}
> +
> +static int max_des_v4l2_register(struct max_des_priv *priv)
> +{
> + struct v4l2_subdev *sd = &priv->sd;
> + struct max_des *des = priv->des;
> + void *data = i2c_get_clientdata(priv->client);
> + unsigned int num_pads = max_des_num_pads(des);
> + unsigned int i;
> + int ret;
> +
> + v4l2_i2c_subdev_init(sd, priv->client, &max_des_subdev_ops);
> + i2c_set_clientdata(priv->client, data);
> + sd->internal_ops = &max_des_internal_ops;
> + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + sd->entity.ops = &max_des_media_ops;
> + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +
> + for (i = 0; i < num_pads; i++) {
> + if (max_des_pad_is_sink(des, i))
> + priv->pads[i].flags = MEDIA_PAD_FL_SINK;
> + else if (max_des_pad_is_source(des, i))
> + priv->pads[i].flags = MEDIA_PAD_FL_SOURCE;
> + else if (max_des_pad_is_tpg(des, i))
> + priv->pads[i].flags = MEDIA_PAD_FL_SINK |
> + MEDIA_PAD_FL_INTERNAL;
> + else
> + return -EINVAL;
> + }
> +
> + v4l2_set_subdevdata(sd, priv);
> +
> + if (des->info->tpg_patterns) {
> + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
> + priv->sd.ctrl_handler = &priv->ctrl_handler;
> +
> + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
> + &max_des_ctrl_ops,
> + V4L2_CID_TEST_PATTERN,
> + MAX_SERDES_TPG_PATTERN_MAX,
> + ~des->info->tpg_patterns,
> + __ffs(des->info->tpg_patterns),
> + max_serdes_tpg_patterns);
> + if (priv->ctrl_handler.error) {
> + ret = priv->ctrl_handler.error;
> + goto err_free_ctrl;
> + }
> + }
> +
> + ret = media_entity_pads_init(&sd->entity, num_pads, priv->pads);
> + if (ret)
> + goto err_free_ctrl;
> +
> + ret = max_des_v4l2_notifier_register(priv);
> + if (ret)
> + goto err_media_entity_cleanup;
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret)
> + goto err_nf_cleanup;
> +
> + ret = v4l2_async_register_subdev(sd);
> + if (ret)
> + goto err_sd_cleanup;
> +
> + return 0;
> +
> +err_sd_cleanup:
> + v4l2_subdev_cleanup(sd);
> +err_nf_cleanup:
> + max_des_v4l2_notifier_unregister(priv);
> +err_media_entity_cleanup:
> + media_entity_cleanup(&sd->entity);
> +err_free_ctrl:
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +
> + return ret;
> +}
> +
> +static void max_des_v4l2_unregister(struct max_des_priv *priv)
> +{
> + struct v4l2_subdev *sd = &priv->sd;
> + struct max_des *des = priv->des;
> + unsigned int i;
> +
> + v4l2_async_unregister_subdev(sd);
> + v4l2_subdev_cleanup(sd);
> + max_des_v4l2_notifier_unregister(priv);
> + media_entity_cleanup(&sd->entity);
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +
> + for (i = 0; i < des->info->num_links; i++)
> + fwnode_handle_put(priv->sources[i].ep_fwnode);
> +}
> +
> +static int max_des_update_pocs(struct max_des_priv *priv, bool enable)
> +{
> + struct max_des *des = priv->des;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> +
> + if (!link->enabled)
> + continue;
> +
> + if (!priv->pocs[i])
> + continue;
> +
> + if (enable)
> + ret = regulator_enable(priv->pocs[i]);
> + else
> + ret = regulator_disable(priv->pocs[i]);
> +
> + if (ret) {
> + dev_err(priv->dev,
> + "Failed to set POC supply to %u: %u\n",
> + enable, ret);
> + if (!enable)
> + return ret;
> + goto err_rollback;
> + }
> + }
> +
> + return 0;
> +
> +err_rollback:
> + while (i--) {
> + struct max_des_link *link = &des->links[i];
> +
> + if (!link->enabled)
> + continue;
> +
> + if (!priv->pocs[i])
> + continue;
> +
> + regulator_disable(priv->pocs[i]);
> + }
> +
> + return ret;
> +}
> +
> +static int max_des_parse_sink_dt_endpoint(struct max_des_priv *priv,
> + struct max_des_link *link,
> + struct max_serdes_source *source,
> + struct fwnode_handle *fwnode)
> +{
> + struct max_des *des = priv->des;
> + u32 pad = max_des_link_to_pad(des, link);
> + unsigned int index = link->index;
> + struct fwnode_handle *ep;
> + char poc_name[10];
> + int ret;
> +
> + ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0);
> + if (!ep)
> + return 0;
> +
> + source->ep_fwnode = fwnode_graph_get_remote_endpoint(ep);
> + fwnode_handle_put(ep);
> + if (!source->ep_fwnode) {
> + dev_err(priv->dev,
> + "Failed to get remote endpoint on port %u\n", pad);
> + return -ENODEV;
> + }
> +
> + snprintf(poc_name, sizeof(poc_name), "port%u-poc", index);
> + priv->pocs[index] = devm_regulator_get_optional(priv->dev, poc_name);
> + if (IS_ERR(priv->pocs[index])) {
> + ret = PTR_ERR(priv->pocs[index]);
> + if (ret != -ENODEV) {
> + dev_err(priv->dev,
> + "Failed to get POC supply on port %u: %d\n",
> + index, ret);
> + goto err_put_source_ep_fwnode;
> + }
> +
> + priv->pocs[index] = NULL;
> + }
> +
> + link->enabled = true;
> +
> + return 0;
> +
> +err_put_source_ep_fwnode:
> + fwnode_handle_put(source->ep_fwnode);
> +
> + return ret;
> +}
> +
> +static int max_des_parse_src_dt_endpoint(struct max_des_priv *priv,
> + struct max_des_phy *phy,
> + struct fwnode_handle *fwnode)
> +{
> + struct max_des *des = priv->des;
> + u32 pad = max_des_phy_to_pad(des, phy);
> + struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_UNKNOWN };
> + struct v4l2_mbus_config_mipi_csi2 *mipi = &v4l2_ep.bus.mipi_csi2;
> + enum v4l2_mbus_type bus_type;
> + struct fwnode_handle *ep;
> + u64 link_frequency;
> + unsigned int i;
> + int ret;
> +
> + ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0);
> + if (!ep)
> + return 0;
> +
> + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &v4l2_ep);
> + fwnode_handle_put(ep);
> + if (ret) {
> + dev_err(priv->dev, "Could not parse endpoint on port %u\n", pad);
> + return ret;
> + }
> +
> + bus_type = v4l2_ep.bus_type;
> + if (bus_type != V4L2_MBUS_CSI2_DPHY &&
> + bus_type != V4L2_MBUS_CSI2_CPHY) {
> + v4l2_fwnode_endpoint_free(&v4l2_ep);
> + dev_err(priv->dev, "Unsupported bus-type %u on port %u\n",
> + pad, bus_type);
> + return -EINVAL;
> + }
> +
> + if (v4l2_ep.nr_of_link_frequencies == 0)
> + link_frequency = MAX_DES_LINK_FREQUENCY_DEFAULT;
> + else if (v4l2_ep.nr_of_link_frequencies == 1)
> + link_frequency = v4l2_ep.link_frequencies[0];
> + else
> + ret = -EINVAL;
> +
> + v4l2_fwnode_endpoint_free(&v4l2_ep);
> +
> + if (ret) {
> + dev_err(priv->dev, "Invalid link frequencies %u on port %u\n",
> + v4l2_ep.nr_of_link_frequencies, pad);
> + return -EINVAL;
> + }
> +
> + if (link_frequency < MAX_DES_LINK_FREQUENCY_MIN ||
> + link_frequency > MAX_DES_LINK_FREQUENCY_MAX) {
> + dev_err(priv->dev, "Invalid link frequency %llu on port %u\n",
> + link_frequency, pad);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < mipi->num_data_lanes; i++) {
> + if (mipi->data_lanes[i] > mipi->num_data_lanes) {
> + dev_err(priv->dev, "Invalid data lane %u on port %u\n",
> + mipi->data_lanes[i], pad);
> + return -EINVAL;
> + }
> + }
> +
> + phy->bus_type = bus_type;
> + phy->mipi = *mipi;
> + phy->link_frequency = link_frequency;
> + phy->enabled = true;
> +
> + return 0;
> +}
> +
> +int max_des_phy_hw_data_lanes(struct max_des *des, struct max_des_phy *phy)
> +{
> + const struct max_serdes_phys_configs *configs = &des->info->phys_configs;
> + const struct max_serdes_phys_config *config =
> + &configs->configs[des->phys_config];
> +
> + return config->lanes[phy->index];
> +}
> +EXPORT_SYMBOL_NS_GPL(max_des_phy_hw_data_lanes, "MAX_SERDES");
> +
> +static int max_des_find_phys_config(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> + const struct max_serdes_phys_configs *configs = &des->info->phys_configs;
> + struct max_des_phy *phy;
> + unsigned int i, j;
> +
> + if (!configs->num_configs)
> + return 0;
> +
> + for (i = 0; i < configs->num_configs; i++) {
> + const struct max_serdes_phys_config *config = &configs->configs[i];
> + bool matching = true;
> +
> + for (j = 0; j < des->info->num_phys; j++) {
> + phy = &des->phys[j];
> +
> + if (!phy->enabled)
> + continue;
> +
> + if (phy->mipi.num_data_lanes <= config->lanes[j] &&
> + phy->mipi.clock_lane == config->clock_lane[j])
> + continue;
> +
> + matching = false;
> +
> + break;
> + }
> +
> + if (matching)
> + break;
> + }
> +
> + if (i == configs->num_configs) {
> + dev_err(priv->dev, "Invalid lane configuration\n");
> + return -EINVAL;
> + }
> +
> + des->phys_config = i;
> +
> + return 0;
> +}
> +
> +static int max_des_parse_dt(struct max_des_priv *priv)
> +{
> + struct fwnode_handle *fwnode = dev_fwnode(priv->dev);
> + struct max_des *des = priv->des;
> + struct max_des_link *link;
> + struct max_des_pipe *pipe;
> + struct max_des_phy *phy;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < des->info->num_phys; i++) {
> + phy = &des->phys[i];
> + phy->index = i;
> +
> + ret = max_des_parse_src_dt_endpoint(priv, phy, fwnode);
> + if (ret)
> + return ret;
> + }
> +
> + ret = max_des_find_phys_config(priv);
> + if (ret)
> + return ret;
> +
> + /* Find an unsed PHY to send unampped data to. */
WARNING: 'unsed' may be misspelled - perhaps 'unused'?
#3101: FILE: drivers/media/i2c/maxim-serdes/max_des.c:3053:
+ /* Find an unsed PHY to send unampped data to. */
^^^^^
> + for (i = 0; i < des->info->num_phys; i++) {
> + phy = &des->phys[i];
> +
> + if (!phy->enabled) {
> + priv->unused_phy = phy;
> + break;
> + }
> + }
> +
> + for (i = 0; i < des->info->num_pipes; i++) {
> + pipe = &des->pipes[i];
> + pipe->index = i;
> +
> + /*
> + * Serializers can send data on different stream ids over the
> + * same link, and some deserializers support stream id autoselect
> + * allowing them to receive data from all stream ids.
> + * Deserializers that support that feature should enable it.
> + * Deserializers that support per-link stream ids do not need
> + * to assign unique stream ids to each serializer.
> + */
> + if (des->info->needs_unique_stream_id)
> + pipe->stream_id = i;
> + else
> + pipe->stream_id = 0;
> +
> + /*
> + * We already checked that num_pipes >= num_links.
> + * Set up pipe to receive data from the link with the same index.
> + * This is already the default for most chips, and some of them
> + * don't even support receiving pipe data from a different link.
> + */
> + pipe->link_id = i % des->info->num_links;
> + }
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + link = &des->links[i];
> + link->index = i;
> + }
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + struct max_des_link *link = &des->links[i];
> + struct max_serdes_source *source;
> +
> + source = max_des_get_link_source(priv, link);
> + source->index = i;
> +
> + ret = max_des_parse_sink_dt_endpoint(priv, link, source, fwnode);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int max_des_allocate(struct max_des_priv *priv)
> +{
> + struct max_des *des = priv->des;
> + unsigned int num_pads = max_des_num_pads(des);
> +
> + des->phys = devm_kcalloc(priv->dev, des->info->num_phys,
> + sizeof(*des->phys), GFP_KERNEL);
> + if (!des->phys)
> + return -ENOMEM;
> +
> + des->pipes = devm_kcalloc(priv->dev, des->info->num_pipes,
> + sizeof(*des->pipes), GFP_KERNEL);
> + if (!des->pipes)
> + return -ENOMEM;
> +
> + des->links = devm_kcalloc(priv->dev, des->info->num_links,
> + sizeof(*des->links), GFP_KERNEL);
> + if (!des->links)
> + return -ENOMEM;
> +
> + priv->sources = devm_kcalloc(priv->dev, des->info->num_links,
> + sizeof(*priv->sources), GFP_KERNEL);
> + if (!priv->sources)
> + return -ENOMEM;
> +
> + priv->pocs = devm_kcalloc(priv->dev, des->info->num_links,
> + sizeof(*priv->pocs), GFP_KERNEL);
> + if (!priv->pocs)
> + return -ENOMEM;
> +
> + priv->pads = devm_kcalloc(priv->dev, num_pads,
> + sizeof(*priv->pads), GFP_KERNEL);
> + if (!priv->pads)
> + return -ENOMEM;
> +
> + priv->streams_masks = devm_kcalloc(priv->dev, num_pads,
> + sizeof(*priv->streams_masks),
> + GFP_KERNEL);
> + if (!priv->streams_masks)
> + return -ENOMEM;
> +
> + return 0;
> +}
> +
> +int max_des_probe(struct i2c_client *client, struct max_des *des)
> +{
> + struct device *dev = &client->dev;
> + struct max_des_priv *priv;
> + int ret;
> +
> + if (des->info->num_phys > MAX_DES_NUM_PHYS)
> + return -E2BIG;
> +
> + if (des->info->num_pipes > MAX_DES_NUM_PIPES)
> + return -E2BIG;
> +
> + if (des->info->num_links > MAX_DES_NUM_LINKS)
> + return -E2BIG;
> +
> + if (des->info->num_links > des->info->num_pipes)
> + return -E2BIG;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + if (des->ops->set_link_version && !des->ops->select_links) {
> + dev_err(dev,
> + "Cannot implement .select_link_version() without .select_links()\n");
> + return -EINVAL;
> + }
> +
> + if (hweight_long(des->info->versions) >= 1 &&
> + !des->ops->set_link_version) {
> + dev_err(dev, "Multiple version without .select_link_version()\n");
> + return -EINVAL;
> + }
> +
> + priv->client = client;
> + priv->dev = dev;
> + priv->des = des;
> + des->priv = priv;
> +
> + ret = max_des_allocate(priv);
> + if (ret)
> + return ret;
> +
> + ret = max_des_parse_dt(priv);
> + if (ret)
> + return ret;
> +
> + ret = max_des_init(priv);
> + if (ret)
> + return ret;
> +
> + ret = max_des_update_pocs(priv, true);
> + if (ret)
> + return ret;
> +
> + ret = max_des_i2c_adapter_init(priv);
> + if (ret)
> + goto err_disable_pocs;
> +
> + ret = max_des_v4l2_register(priv);
> + if (ret)
> + goto err_i2c_adapter_deinit;
> +
> + return 0;
> +
> +err_i2c_adapter_deinit:
> + max_des_i2c_adapter_deinit(priv);
> +
> +err_disable_pocs:
> + max_des_update_pocs(priv, false);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_NS_GPL(max_des_probe, "MAX_SERDES");
> +
> +int max_des_remove(struct max_des *des)
> +{
> + struct max_des_priv *priv = des->priv;
> +
> + max_des_v4l2_unregister(priv);
> +
> + max_des_i2c_adapter_deinit(priv);
> +
> + max_des_update_pocs(priv, false);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(max_des_remove, "MAX_SERDES");
> +
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("I2C_ATR");
> diff --git a/drivers/media/i2c/maxim-serdes/max_des.h b/drivers/media/i2c/maxim-serdes/max_des.h
> new file mode 100644
> index 000000000000..3ad8246b1981
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max_des.h
> @@ -0,0 +1,157 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#ifndef MAX_DES_H
> +#define MAX_DES_H
> +
> +#include <media/v4l2-mediabus.h>
> +
> +#include "max_serdes.h"
> +
> +#define MAX_DES_DT_VC(dt, vc) (((vc) & 0x3) << 6 | ((dt) & 0x3f))
> +
> +struct max_des_remap {
> + u8 from_dt;
> + u8 from_vc;
> + u8 to_dt;
> + u8 to_vc;
> + u8 phy;
> +};
> +
> +struct max_des_link {
> + unsigned int index;
> + bool enabled;
> + enum max_serdes_gmsl_version version;
> + struct max_serdes_i2c_xlate ser_xlate;
> + struct i2c_adapter *adapter;
> +};
> +
> +struct max_des_pipe_mode {
> + bool dbl8;
> + bool dbl10;
> + bool dbl12;
> + bool dbl8mode;
> + bool dbl10mode;
> +};
> +
> +struct max_des_pipe {
> + unsigned int index;
> + unsigned int stream_id;
> + unsigned int link_id;
> + unsigned int phy_id;
> + struct max_des_remap *remaps;
> + unsigned int num_remaps;
> + struct max_serdes_vc_remap *vc_remaps;
> + unsigned int num_vc_remaps;
> + struct max_des_pipe_mode mode;
> + bool enabled;
> +};
> +
> +struct max_des_phy_mode {
> + bool alt_mem_map8;
> + bool alt2_mem_map8;
> + bool alt_mem_map10;
> + bool alt_mem_map12;
> +};
> +
> +struct max_des_phy {
> + unsigned int index;
> + u64 link_frequency;
> + struct v4l2_mbus_config_mipi_csi2 mipi;
> + enum v4l2_mbus_type bus_type;
> + struct max_des_phy_mode mode;
> + bool enabled;
> +};
> +
> +struct max_des;
> +
> +struct max_des_info {
> + unsigned int num_phys;
> + unsigned int num_pipes;
> + unsigned int num_links;
> + unsigned int num_remaps_per_pipe;
> + unsigned int versions;
> + unsigned int modes;
> + bool fix_tx_ids;
> + bool use_atr;
> + bool needs_single_link_version;
> + bool needs_unique_stream_id;
> +
> + struct max_serdes_phys_configs phys_configs;
> + struct max_serdes_tpg_entries tpg_entries;
> + enum max_serdes_gmsl_mode tpg_mode;
> + unsigned int tpg_patterns;
> +};
> +
> +struct max_des_ops {
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + int (*reg_read)(struct max_des *des, unsigned int reg, unsigned int *val);
> + int (*reg_write)(struct max_des *des, unsigned int reg, unsigned int val);
> +#endif
> + int (*log_status)(struct max_des *des);
> + int (*log_pipe_status)(struct max_des *des, struct max_des_pipe *pipe);
> + int (*log_phy_status)(struct max_des *des, struct max_des_phy *phy);
> + int (*set_enable)(struct max_des *des, bool enable);
> + int (*set_tpg)(struct max_des *des, const struct max_serdes_tpg_entry *entry);
> + int (*init)(struct max_des *des);
> + int (*init_phy)(struct max_des *des, struct max_des_phy *phy);
> + int (*set_phy_mode)(struct max_des *des, struct max_des_phy *phy,
> + struct max_des_phy_mode *mode);
> + int (*set_phy_enable)(struct max_des *des, struct max_des_phy *phy,
> + bool active);
> + int (*set_pipe_stream_id)(struct max_des *des, struct max_des_pipe *pipe,
> + unsigned int stream_id);
> + int (*set_pipe_link)(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_link *link);
> + int (*set_pipe_phy)(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_phy *phy);
> + int (*set_pipe_tunnel_phy)(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_phy *phy);
> + int (*set_pipe_enable)(struct max_des *des, struct max_des_pipe *pipe,
> + bool enable);
> + int (*set_pipe_remap)(struct max_des *des, struct max_des_pipe *pipe,
> + unsigned int i, struct max_des_remap *remap);
> + int (*set_pipe_remaps_enable)(struct max_des *des, struct max_des_pipe *pipe,
> + unsigned int mask);
> + int (*set_pipe_vc_remap)(struct max_des *des, struct max_des_pipe *pipe,
> + unsigned int i, struct max_serdes_vc_remap *vc_remap);
> + int (*set_pipe_vc_remaps_enable)(struct max_des *des, struct max_des_pipe *pipe,
> + unsigned int mask);
> + int (*set_pipe_mode)(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_pipe_mode *mode);
> + int (*set_pipe_tunnel_enable)(struct max_des *des, struct max_des_pipe *pipe,
> + bool enable);
> + int (*init_link)(struct max_des *des, struct max_des_link *link);
> + int (*select_links)(struct max_des *des, unsigned int mask);
> + int (*set_link_version)(struct max_des *des, struct max_des_link *link,
> + enum max_serdes_gmsl_version version);
> +};
> +
> +struct max_des_priv;
> +
> +struct max_des {
> + struct max_des_priv *priv;
> +
> + const struct max_des_info *info;
> + const struct max_des_ops *ops;
> +
> + struct max_des_phy *phys;
> + struct max_des_pipe *pipes;
> + struct max_des_link *links;
> + const struct max_serdes_tpg_entry *tpg_entry;
> + enum max_serdes_tpg_pattern tpg_pattern;
> +
> + unsigned int phys_config;
> + enum max_serdes_gmsl_mode mode;
> + bool active;
> +};
> +
> +int max_des_probe(struct i2c_client *client, struct max_des *des);
> +
> +int max_des_remove(struct max_des *des);
> +
> +int max_des_phy_hw_data_lanes(struct max_des *des, struct max_des_phy *phy);
> +
> +#endif // MAX_DES_H
>
> --
> 2.53.0
>
>
--
Kind Regards,
Niklas Söderlund
^ permalink raw reply
* Re: [PATCH v4 05/14] iio: light: lm3533-als: Remove redundant pdata helpers
From: Svyatoslav Ryhel @ 2026-06-10 14:35 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
Johan Hovold, dri-devel, linux-leds, devicetree, linux-kernel,
linux-iio, linux-fbdev
In-Reply-To: <aihlDGNZRuHI-vMR@ashevche-desk.local>
вт, 9 черв. 2026 р. о 22:10 Andy Shevchenko <andriy.shevchenko@intel.com> пише:
>
> On Sat, Jun 06, 2026 at 07:57:29AM +0300, Svyatoslav Ryhel wrote:
> > The lm3533_als_set_input_mode and lm3533_als_set_resistor functions are
> > used only in lm3533_als_setup. Incorporate their code into
> > lm3533_als_setup directly to simplify driver readability.
>
> Use func() when referring to a function in the commit message.
>
I must have missed, thanks.
> ...
>
> > static int lm3533_als_setup(struct lm3533_als *als,
> > const struct lm3533_als_platform_data *pdata)
> > {
> > + struct device *dev = &als->pdev->dev;
> > int ret;
> >
> > - ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
> > + ret = regmap_assign_bits(als->regmap, LM3533_REG_ALS_CONF,
> > + LM3533_ALS_INPUT_MODE_MASK, pdata->pwm_mode);
> > if (ret)
> > - return ret;
> > + return dev_err_probe(dev, ret, "failed to set input mode %d\n",
> > + pdata->pwm_mode);
> >
> > /* ALS input is always high impedance in PWM-mode. */
> > if (!pdata->pwm_mode) {
> > - ret = lm3533_als_set_resistor(als, pdata->r_select);
> > + if (pdata->r_select < LM3533_ALS_RESISTOR_MIN ||
> > + pdata->r_select > LM3533_ALS_RESISTOR_MAX)
> > + return dev_err_probe(dev, -EINVAL,
> > + "invalid resistor value\n");
> > +
> > + ret = regmap_write(als->regmap, LM3533_REG_ALS_RESISTOR_SELECT,
> > + pdata->r_select);
> > if (ret)
> > - return ret;
> > + return dev_err_probe(dev, ret, "failed to set resistor\n");
> > }
> >
> > return 0;
>
> Wondering if it would be better to
>
> /* Bail out when in PWM-mode */
> if (pdata->pwm_mode)
> return 0;
>
> /* ALS input is always high impedance in PWM-mode. */
> ...
>
> as the above changes almost every line in that conditional.
>
This is a decent idea, thank you!
> --
> With Best Regards,
> Andy Shevchenko
>
>
^ permalink raw reply
* Re: [PATCH v2 11/16] power: sequencing: pcie-m2: Add usb and sdio targets for E-key connector
From: Andy Shevchenko @ 2026-06-10 14:36 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Bartosz Golaszewski, Greg Kroah-Hartman, Daniel Scally,
Heikki Krogerus, Sakari Ailus, Rafael J. Wysocki,
Danilo Krummrich, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Alan Stern,
linux-acpi, driver-core, linux-pm, linux-usb, devicetree,
linux-mediatek, linux-arm-kernel, linux-kernel,
Manivannan Sadhasivam
In-Reply-To: <20260610084053.2059858-12-wenst@chromium.org>
On Wed, Jun 10, 2026 at 04:40:45PM +0800, Chen-Yu Tsai wrote:
> The M.2 E-key connector allows either PCIe or SDIO for WiFi and USB or
> UART for BT. Currently the driver only supports PCIe and UART.
>
> Add power sequencing targets for SDIO and USB. To avoid adding a
> complicated dependency tree, rename the existing power sequencing units
> "pcie" and "uart" to "wifi" and "bt". The existing target names are left
> untouched. The new "sdio" and "usb" targets just point to the renamed
> "wifi" and "bt" units.
Why can we do that? No breakage? Only internal names? No ABI affected?
Please, clarify all this in the commit message.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v4 07/14] mfd: lm3533: Switch sysfs_create_group() to device_add_group()
From: Svyatoslav Ryhel @ 2026-06-10 14:38 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
Johan Hovold, dri-devel, linux-leds, devicetree, linux-kernel,
linux-iio, linux-fbdev
In-Reply-To: <aihl9yIqN3adKWLr@ashevche-desk.local>
вт, 9 черв. 2026 р. о 22:14 Andy Shevchenko <andriy.shevchenko@intel.com> пише:
>
> On Sat, Jun 06, 2026 at 07:57:31AM +0300, Svyatoslav Ryhel wrote:
> > Switch from sysfs_create_group() to device_add_group() including device
> > managed where appropriate.
>
> This should use .dev_groups member of struct device_driver.
>
Specify pls, device_add_group literally uses dev_groups, I don't
understand what is wrong.
> ...
>
> > + ret = devm_device_add_group(&bd->dev, &lm3533_bl_attribute_group);
>
> This will make Greg KH very grumpy. (For the record, original code as well
> but it already is in upstream. So, thanks for trying to address this, just
> needs a bit more of work.)
>
In the prev iteration YOU asked to me to adjust this. I have adjusted
and now you say that this is not appropriate. I will just drop this
commit altogether.
> > + if (ret < 0)
> > + return dev_err_probe(&pdev->dev, ret,
> > + "failed to create sysfs attributes\n");
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
^ permalink raw reply
* Re: [PATCH v13 16/22] media: i2c: maxim-serdes: add MAX96717 driver
From: Niklas Söderlund @ 2026-06-10 14:39 UTC (permalink / raw)
To: dumitru.ceclan
Cc: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring, Greg Kroah-Hartman,
mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Martin Hecht, Cosmin Tanislav
In-Reply-To: <20260604-gmsl2-3_serdes-v13-16-9d8a4919983b@analog.com>
On 2026-06-04 17:14:03 +0300, Dumitru Ceclan via B4 Relay wrote:
> From: Cosmin Tanislav <demonsingur@gmail.com>
>
> Add a new MAX96717 driver that also supports MAX9295A, MAX96717F and
> MAX96793.
>
> Integrate it with the common serializer framework, while keeping
> compatibility with existing usecases, avoiding code duplication, and
> also enabling more features across all chips.
>
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> ---
> drivers/media/i2c/maxim-serdes/Kconfig | 19 +
> drivers/media/i2c/maxim-serdes/Makefile | 1 +
> drivers/media/i2c/maxim-serdes/max96717.c | 1688 +++++++++++++++++++++++++++++
> 3 files changed, 1708 insertions(+)
>
> diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
> index f5a4ca80a263..c811790c09b9 100644
> --- a/drivers/media/i2c/maxim-serdes/Kconfig
> +++ b/drivers/media/i2c/maxim-serdes/Kconfig
> @@ -15,3 +15,22 @@ config VIDEO_MAXIM_SERDES
>
> To compile this driver as a module, choose M here: the module
> will be called max_serdes.
> +
> +config VIDEO_MAX96717
> + tristate "Maxim MAX96717 Serializer support"
> + depends on COMMON_CLK
> + depends on I2C
> + depends on PINCTRL
> + depends on VIDEO_DEV
> + select VIDEO_MAXIM_SERDES
> + select GENERIC_PINCONF
> + select GENERIC_PINCTRL_GROUPS
> + select GENERIC_PINMUX_FUNCTIONS
> + select GPIOLIB
> + help
> + This driver supports the Maxim MAX9295A, MAX96717, MAX96717F,
> + MAX96793 Serializers, which receive video on a MIPI CSI-2
> + interface and output it on a GMSL2/3 link.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called max96717.
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> index b54326a5c81b..04abda6a5437 100644
> --- a/drivers/media/i2c/maxim-serdes/Makefile
> +++ b/drivers/media/i2c/maxim-serdes/Makefile
> @@ -1,3 +1,4 @@
> # SPDX-License-Identifier: GPL-2.0
> max-serdes-objs := max_serdes.o max_ser.o max_des.o
> obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
> +obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
> diff --git a/drivers/media/i2c/maxim-serdes/max96717.c b/drivers/media/i2c/maxim-serdes/max96717.c
> new file mode 100644
> index 000000000000..6cc4060e10f3
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max96717.c
> @@ -0,0 +1,1688 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim MAX96717 GMSL2 Serializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/iopoll.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +#include <linux/pinctrl/pinconf.h>
> +#include <linux/pinctrl/pinconf-generic.h>
> +#include <linux/regmap.h>
> +
> +#include "max_ser.h"
> +
> +#define MAX96717_REG0 0x0
> +
> +#define MAX96717_REG2 0x2
> +#define MAX96717_REG2_VID_TX_EN_P(p) BIT(4 + (p))
> +
> +#define MAX96717_REG3 0x3
> +#define MAX96717_REG3_RCLKSEL GENMASK(1, 0)
> +#define MAX96717_REG3_RCLK_ALT BIT(2)
> +
> +#define MAX96717_REG6 0x6
> +#define MAX96717_REG6_RCLKEN BIT(5)
> +
> +#define MAX96717_I2C_2(x) (0x42 + (x) * 0x2)
> +#define MAX96717_I2C_2_SRC GENMASK(7, 1)
> +
> +#define MAX96717_I2C_3(x) (0x43 + (x) * 0x2)
> +#define MAX96717_I2C_3_DST GENMASK(7, 1)
> +
> +#define MAX96717_TX3(p) (0x53 + (p) * 0x4)
> +#define MAX96717_TX3_TX_STR_SEL GENMASK(1, 0)
> +
> +#define MAX96717_VIDEO_TX0(p) (0x100 + (p) * 0x8)
> +#define MAX96717_VIDEO_TX0_AUTO_BPP BIT(3)
> +
> +#define MAX96717_VIDEO_TX1(p) (0x101 + (p) * 0x8)
> +#define MAX96717_VIDEO_TX1_BPP GENMASK(5, 0)
> +
> +#define MAX96717_VIDEO_TX2(p) (0x102 + (p) * 0x8)
> +#define MAX96717_VIDEO_TX2_PCLKDET BIT(7)
> +#define MAX96717_VIDEO_TX2_DRIFT_DET_EN BIT(1)
> +
> +#define MAX96717_VTX0(p) (0x1c8 + (p) * 0x43)
> +#define MAX96717_VTX0_VTG_MODE GENMASK(1, 0)
> +#define MAX96717_VTX0_VTG_MODE_FREE_RUNNING 0b11
> +#define MAX96717_VTX0_DE_INV BIT(2)
> +#define MAX96717_VTX0_HS_INV BIT(3)
> +#define MAX96717_VTX0_VS_INV BIT(4)
> +#define MAX96717_VTX0_GEN_DE BIT(5)
> +#define MAX96717_VTX0_GEN_HS BIT(6)
> +#define MAX96717_VTX0_GEN_VS BIT(7)
> +
> +#define MAX96717_VTX1(p) (0x1c9 + (p) * 0x43)
> +#define MAX96717_VTX1_PATGEN_CLK_SRC GENMASK(3, 1)
> +#define MAX96717_VTX1_PATGEN_CLK_SRC_25MHZ 0b100
> +#define MAX96717_VTX1_PATGEN_CLK_SRC_75MHZ 0b101
> +#define MAX96717_VTX1_PATGEN_CLK_SRC_150MHZ 0b110
> +#define MAX96717_VTX1_PATGEN_CLK_SRC_375MHZ 0b111
> +
> +#define MAX96717_VTX2_VS_DLY_2(p) (0x1ca + (p) * 0x43)
> +#define MAX96717_VTX5_VS_HIGH_2(p) (0x1cd + (p) * 0x43)
> +#define MAX96717_VTX8_VS_LOW_2(p) (0x1d0 + (p) * 0x43)
> +#define MAX96717_VTX11_V2H_2(p) (0x1d3 + (p) * 0x43)
> +#define MAX96717_VTX14_HS_HIGH_1(p) (0x1d6 + (p) * 0x43)
> +#define MAX96717_VTX16_HS_LOW_1(p) (0x1d8 + (p) * 0x43)
> +#define MAX96717_VTX18_HS_CNT_1(p) (0x1da + (p) * 0x43)
> +#define MAX96717_VTX20_V2D_2(p) (0x1dc + (p) * 0x43)
> +#define MAX96717_VTX23_DE_HIGH_1(p) (0x1df + (p) * 0x43)
> +#define MAX96717_VTX25_DE_LOW_1(p) (0x1e1 + (p) * 0x43)
> +#define MAX96717_VTX27_DE_CNT_1(p) (0x1e3 + (p) * 0x43)
> +#define MAX96717_VTX29(p) (0x1e5 + (p) * 0x43)
> +
> +#define MAX96717_VTX29_PATGEN_MODE GENMASK(1, 0)
> +#define MAX96717_VTX29_PATGEN_MODE_DISABLED 0b00
> +#define MAX96717_VTX29_PATGEN_MODE_CHECKER 0b01
> +#define MAX96717_VTX29_PATGEN_MODE_GRADIENT 0b10
> +
> +#define MAX96717_VTX30_GRAD_INCR(p) (0x1e6 + (p) * 0x43)
> +#define MAX96717_VTX31_CHKR_A_L(p) (0x1e7 + (p) * 0x43)
> +#define MAX96717_VTX34_CHKR_B_L(p) (0x1ea + (p) * 0x43)
> +#define MAX96717_VTX37_CHKR_RPT_A(p) (0x1ed + (p) * 0x43)
> +#define MAX96717_VTX38_CHKR_RPT_B(p) (0x1ee + (p) * 0x43)
> +#define MAX96717_VTX39_CHKR_ALT(p) (0x1ef + (p) * 0x43)
> +
> +#define MAX96717_GPIO_A(x) (0x2be + (x) * 0x3)
> +#define MAX96717_GPIO_A_GPIO_OUT_DIS BIT(0)
> +#define MAX96717_GPIO_A_GPIO_TX_EN BIT(1)
> +#define MAX96717_GPIO_A_GPIO_RX_EN BIT(2)
> +#define MAX96717_GPIO_A_GPIO_IN BIT(3)
> +#define MAX96717_GPIO_A_GPIO_OUT BIT(4)
> +#define MAX96717_GPIO_A_TX_COMP_EN BIT(5)
> +#define MAX96717_GPIO_A_RES_CFG BIT(7)
> +
> +#define MAX96717_GPIO_B(x) (0x2bf + (x) * 0x3)
> +#define MAX96717_GPIO_B_GPIO_TX_ID GENMASK(4, 0)
> +#define MAX96717_GPIO_B_OUT_TYPE BIT(5)
> +#define MAX96717_GPIO_B_PULL_UPDN_SEL GENMASK(7, 6)
> +#define MAX96717_GPIO_B_PULL_UPDN_SEL_NONE 0b00
> +#define MAX96717_GPIO_B_PULL_UPDN_SEL_PU 0b01
> +#define MAX96717_GPIO_B_PULL_UPDN_SEL_PD 0b10
> +
> +#define MAX96717_GPIO_C(x) (0x2c0 + (x) * 0x3)
> +#define MAX96717_GPIO_C_GPIO_RX_ID GENMASK(4, 0)
> +
> +#define MAX96717_CMU2 0x302
> +#define MAX96717_CMU2_PFDDIV_RSHORT GENMASK(6, 4)
> +#define MAX96717_CMU2_PFDDIV_RSHORT_1_1V 0b001
> +
> +#define MAX96717_FRONTTOP_0 0x308
> +#define MAX96717_FRONTTOP_0_CLK_SEL_P(x) BIT(x)
> +#define MAX96717_FRONTTOP_0_START_PORT(x) BIT((x) + 4)
> +
> +#define MAX96717_FRONTTOP_1(p) (0x309 + (p) * 0x2)
> +#define MAX96717_FRONTTOP_2(p) (0x30a + (p) * 0x2)
> +
> +#define MAX96717_FRONTTOP_9 0x311
> +#define MAX96717_FRONTTOP_9_START_PORT(p, x) BIT((p) + (x) * 4)
> +
> +#define MAX96717_FRONTTOP_10 0x312
> +#define MAX96717_FRONTTOP_10_BPP8DBL(p) BIT(p)
> +
> +#define MAX96717_FRONTTOP_11 0x313
> +#define MAX96717_FRONTTOP_11_BPP10DBL(p) BIT(p)
> +#define MAX96717_FRONTTOP_11_BPP12DBL(p) BIT((p) + 4)
> +
> +#define MAX96717_FRONTTOP_12(p, x) (0x314 + (p) * 0x2 + (x))
> +#define MAX96717_MEM_DT_SEL GENMASK(5, 0)
> +#define MAX96717_MEM_DT_EN BIT(6)
> +
> +#define MAX96717_FRONTTOP_20(p) (0x31c + (p) * 0x1)
> +#define MAX96717_FRONTTOP_20_SOFT_BPP_EN BIT(5)
> +#define MAX96717_FRONTTOP_20_SOFT_BPP GENMASK(4, 0)
> +
> +#define MAX96717_MIPI_RX0 0x330
> +#define MAX96717_MIPI_RX0_NONCONTCLK_EN BIT(6)
> +
> +#define MAX96717_MIPI_RX1 0x331
> +#define MAX96717_MIPI_RX1_CTRL_NUM_LANES GENMASK(5, 4)
> +
> +#define MAX96717_MIPI_RX2 0x332
> +#define MAX96717_MIPI_RX2_PHY1_LANE_MAP GENMASK(7, 4)
> +
> +#define MAX96717_MIPI_RX3 0x333
> +#define MAX96717_MIPI_RX3_PHY2_LANE_MAP GENMASK(3, 0)
> +
> +#define MAX96717_MIPI_RX4 0x334
> +#define MAX96717_MIPI_RX4_PHY1_POL_MAP GENMASK(5, 4)
> +
> +#define MAX96717_MIPI_RX5 0x335
> +#define MAX96717_MIPI_RX5_PHY2_POL_MAP GENMASK(1, 0)
> +#define MAX96717_MIPI_RX5_PHY2_POL_MAP_CLK BIT(2)
> +
> +#define MAX96717_EXTA(x) (0x3dc + (x))
> +
> +#define MAX96717_EXT11 0x383
> +#define MAX96717_EXT11_TUN_MODE BIT(7)
> +
> +#define MAX96717_EXT21 0x38d
> +#define MAX96717_EXT22 0x38e
> +#define MAX96717_EXT23 0x38f
> +#define MAX96717_EXT24 0x390
> +
> +#define MAX96717_REF_VTG0 0x3f0
> +#define MAX96717_REF_VTG0_REFGEN_EN BIT(0)
> +#define MAX96717_REF_VTG0_REFGEN_RST BIT(1)
> +#define MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ_ALT\
> + BIT(3)
> +#define MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ GENMASK(5, 4)
> +
> +#define MAX96717_PIO_SLEW_0 0x56f
> +#define MAX96717_PIO_SLEW_0_PIO00_SLEW GENMASK(1, 0)
> +#define MAX96717_PIO_SLEW_0_PIO01_SLEW GENMASK(3, 2)
> +#define MAX96717_PIO_SLEW_0_PIO02_SLEW GENMASK(5, 4)
> +
> +#define MAX96717_PIO_SLEW_1 0x570
> +#define MAX96717_PIO_SLEW_1_PIO05_SLEW GENMASK(3, 2)
> +#define MAX96717_PIO_SLEW_1_PIO06_SLEW GENMASK(5, 4)
> +
> +#define MAX96717_PIO_SLEW_2 0x571
> +#define MAX96717_PIO_SLEW_2_PIO010_SLEW GENMASK(5, 4)
> +#define MAX96717_PIO_SLEW_2_PIO011_SLEW GENMASK(7, 6)
> +
> +#define MAX96717_PIO_SLEW_FASTEST 0b00
> +
> +#define MAX96717_BIAS_PULL_STRENGTH_1000000_OHM 1000000U
> +#define MAX96717_BIAS_PULL_STRENGTH_40000_OHM 40000U
> +
> +#define MAX96717_DEFAULT_CLKOUT_RATE 24000000UL
> +
> +#define MAX96717_NAME "max96717"
> +#define MAX96717_PINCTRL_NAME MAX96717_NAME "-pinctrl"
> +#define MAX96717_GPIOCHIP_NAME MAX96717_NAME "-gpiochip"
> +#define MAX96717_GPIO_NUM 11
> +#define MAX96717_RCLK_ALT_MFP 2
> +#define MAX96717_RCLK_MFP 4
> +#define MAX96717_PIPES_NUM 4
> +#define MAX96717_PHYS_NUM 2
> +
> +struct max96717_priv {
> + struct max_ser ser;
> + struct pinctrl_desc pctldesc;
> + struct gpio_chip gc;
> + const struct max96717_chip_info *info;
> +
> + struct device *dev;
> + struct i2c_client *client;
> + struct regmap *regmap;
> + struct pinctrl_dev *pctldev;
> +
> + struct clk_hw clk_hw;
> + u8 pll_predef_index;
> +};
> +
> +struct max96717_chip_info {
> + bool supports_3_data_lanes;
> + bool supports_noncontinuous_clock;
> + bool supports_pkt_cnt;
> + unsigned int modes;
> + unsigned int num_pipes;
> + unsigned int num_dts_per_pipe;
> + unsigned int pipe_hw_ids[MAX96717_PIPES_NUM];
> + unsigned int num_phys;
> + unsigned int phy_hw_ids[MAX96717_PHYS_NUM];
> +};
> +
> +#define ser_to_priv(_ser) \
> + container_of(_ser, struct max96717_priv, ser)
> +
> +static inline struct max96717_priv *clk_hw_to_priv(struct clk_hw *hw)
> +{
> + return container_of(hw, struct max96717_priv, clk_hw);
> +}
> +
> +static const struct regmap_config max96717_i2c_regmap = {
> + .reg_bits = 16,
> + .val_bits = 8,
> + .max_register = 0x1f00,
> +};
> +
> +static int max96717_wait_for_device(struct max96717_priv *priv)
> +{
> + unsigned int val;
> + int ret, err;
> +
> + err = read_poll_timeout(regmap_read, ret,
> + !ret && val,
> + 100 * USEC_PER_MSEC,
> + 1 * USEC_PER_SEC, false,
> + priv->regmap, MAX96717_REG0, &val);
> + if (err)
> + dev_err(priv->dev, "Timeout waiting for serializer: %d\n", ret);
> +
> + return err;
> +}
> +
> +#define MAX96717_PIN(n) \
> + PINCTRL_PIN(n, "mfp" __stringify(n))
> +
> +static const struct pinctrl_pin_desc max96717_pins[] = {
> + MAX96717_PIN(0),
> + MAX96717_PIN(1),
> + MAX96717_PIN(2),
> + MAX96717_PIN(3),
> + MAX96717_PIN(4),
> + MAX96717_PIN(5),
> + MAX96717_PIN(6),
> + MAX96717_PIN(7),
> + MAX96717_PIN(8),
> + MAX96717_PIN(9),
> + MAX96717_PIN(10),
> +};
> +
> +#define MAX96717_GROUP_PINS(name, ...) \
> + static const unsigned int name ## _pins[] = { __VA_ARGS__ }
> +
> +MAX96717_GROUP_PINS(mfp0, 0);
> +MAX96717_GROUP_PINS(mfp1, 1);
> +MAX96717_GROUP_PINS(mfp2, 2);
> +MAX96717_GROUP_PINS(mfp3, 3);
> +MAX96717_GROUP_PINS(mfp4, 4);
> +MAX96717_GROUP_PINS(mfp5, 5);
> +MAX96717_GROUP_PINS(mfp6, 6);
> +MAX96717_GROUP_PINS(mfp7, 7);
> +MAX96717_GROUP_PINS(mfp8, 8);
> +MAX96717_GROUP_PINS(mfp9, 9);
> +MAX96717_GROUP_PINS(mfp10, 10);
> +
> +#define MAX96717_GROUP(name) \
> + PINCTRL_PINGROUP(__stringify(name), name ## _pins, ARRAY_SIZE(name ## _pins))
> +
> +static const struct pingroup max96717_ctrl_groups[] = {
> + MAX96717_GROUP(mfp0),
> + MAX96717_GROUP(mfp1),
> + MAX96717_GROUP(mfp2),
> + MAX96717_GROUP(mfp3),
> + MAX96717_GROUP(mfp4),
> + MAX96717_GROUP(mfp5),
> + MAX96717_GROUP(mfp6),
> + MAX96717_GROUP(mfp7),
> + MAX96717_GROUP(mfp8),
> + MAX96717_GROUP(mfp9),
> + MAX96717_GROUP(mfp10),
> +};
> +
> +#define MAX96717_FUNC_GROUPS(name, ...) \
> + static const char * const name ## _groups[] = { __VA_ARGS__ }
> +
> +MAX96717_FUNC_GROUPS(gpio, "mfp0", "mfp1", "mfp2", "mfp3", "mfp4", "mfp5",
> + "mfp6", "mfp7", "mfp8", "mfp9", "mfp10");
> +MAX96717_FUNC_GROUPS(rclkout, "mfp2", "mfp4");
> +
> +enum max96717_func {
> + max96717_func_gpio,
> + max96717_func_rclkout,
> +};
> +
> +#define MAX96717_FUNC(name) \
> + [max96717_func_ ## name] = \
> + PINCTRL_PINFUNCTION(__stringify(name), name ## _groups, \
> + ARRAY_SIZE(name ## _groups))
> +
> +static const struct pinfunction max96717_functions[] = {
> + MAX96717_FUNC(gpio),
> + MAX96717_FUNC(rclkout),
> +};
> +
> +#define MAX96717_PINCTRL_X(x) (PIN_CONFIG_END + (x))
> +#define MAX96717_PINCTRL_JITTER_COMPENSATION_EN MAX96717_PINCTRL_X(1)
> +#define MAX96717_PINCTRL_TX_ID MAX96717_PINCTRL_X(2)
> +#define MAX96717_PINCTRL_RX_ID MAX96717_PINCTRL_X(3)
> +#define MAX96717_PINCTRL_PULL_STRENGTH_HIGH MAX96717_PINCTRL_X(4)
> +#define MAX96717_PINCTRL_INPUT_VALUE MAX96717_PINCTRL_X(5)
> +#define MAX96717_PINCTRL_TX_EN MAX96717_PINCTRL_X(6)
> +#define MAX96717_PINCTRL_RX_EN MAX96717_PINCTRL_X(7)
> +
> +static const struct pinconf_generic_params max96717_cfg_params[] = {
> + { "maxim,jitter-compensation", MAX96717_PINCTRL_JITTER_COMPENSATION_EN, 0 },
> + { "maxim,tx-id", MAX96717_PINCTRL_TX_ID, 0 },
> + { "maxim,rx-id", MAX96717_PINCTRL_RX_ID, 0 },
> +};
> +
> +static int max96717_ctrl_get_groups_count(struct pinctrl_dev *pctldev)
> +{
> + return ARRAY_SIZE(max96717_ctrl_groups);
> +}
> +
> +static const char *max96717_ctrl_get_group_name(struct pinctrl_dev *pctldev,
> + unsigned int selector)
> +{
> + return max96717_ctrl_groups[selector].name;
> +}
> +
> +static int max96717_ctrl_get_group_pins(struct pinctrl_dev *pctldev,
> + unsigned int selector,
> + const unsigned int **pins,
> + unsigned int *num_pins)
> +{
> + *pins = (unsigned int *)max96717_ctrl_groups[selector].pins;
> + *num_pins = max96717_ctrl_groups[selector].npins;
> +
> + return 0;
> +}
> +
> +static int max96717_get_pin_config_reg(unsigned int offset, u32 param,
> + unsigned int *reg, unsigned int *mask,
> + unsigned int *val)
> +{
> + *reg = MAX96717_GPIO_A(offset);
> +
> + switch (param) {
> + case PIN_CONFIG_OUTPUT_ENABLE:
> + *mask = MAX96717_GPIO_A_GPIO_OUT_DIS;
> + *val = 0b0;
> + return 0;
> + case PIN_CONFIG_INPUT_ENABLE:
> + *mask = MAX96717_GPIO_A_GPIO_OUT_DIS;
> + *val = 0b1;
> + return 0;
> + case MAX96717_PINCTRL_TX_EN:
> + *mask = MAX96717_GPIO_A_GPIO_TX_EN;
> + *val = 0b1;
> + return 0;
> + case MAX96717_PINCTRL_RX_EN:
> + *mask = MAX96717_GPIO_A_GPIO_RX_EN;
> + *val = 0b1;
> + return 0;
> + case MAX96717_PINCTRL_INPUT_VALUE:
> + *mask = MAX96717_GPIO_A_GPIO_IN;
> + *val = 0b1;
> + return 0;
> + case PIN_CONFIG_LEVEL:
> + *mask = MAX96717_GPIO_A_GPIO_OUT;
> + *val = 0b1;
> + return 0;
> + case MAX96717_PINCTRL_JITTER_COMPENSATION_EN:
> + *mask = MAX96717_GPIO_A_TX_COMP_EN;
> + *val = 0b1;
> + return 0;
> + case MAX96717_PINCTRL_PULL_STRENGTH_HIGH:
> + *mask = MAX96717_GPIO_A_RES_CFG;
> + *val = 0b1;
> + return 0;
> + }
> +
> + *reg = MAX96717_GPIO_B(offset);
> +
> + switch (param) {
> + case MAX96717_PINCTRL_TX_ID:
> + *mask = MAX96717_GPIO_B_GPIO_TX_ID;
> + return 0;
> + case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> + *mask = MAX96717_GPIO_B_OUT_TYPE;
> + *val = 0b0;
> + return 0;
> + case PIN_CONFIG_DRIVE_PUSH_PULL:
> + *mask = MAX96717_GPIO_B_OUT_TYPE;
> + *val = 0b1;
> + return 0;
> + case PIN_CONFIG_BIAS_DISABLE:
> + *mask = MAX96717_GPIO_B_PULL_UPDN_SEL;
> + *val = MAX96717_GPIO_B_PULL_UPDN_SEL_NONE;
> + return 0;
> + case PIN_CONFIG_BIAS_PULL_DOWN:
> + *mask = MAX96717_GPIO_B_PULL_UPDN_SEL;
> + *val = MAX96717_GPIO_B_PULL_UPDN_SEL_PD;
> + return 0;
> + case PIN_CONFIG_BIAS_PULL_UP:
> + *mask = MAX96717_GPIO_B_PULL_UPDN_SEL;
> + *val = MAX96717_GPIO_B_PULL_UPDN_SEL_PU;
> + return 0;
> + }
> +
> + switch (param) {
> + case PIN_CONFIG_SLEW_RATE:
> + if (offset < 3) {
> + *reg = MAX96717_PIO_SLEW_0;
> + if (offset == 0)
> + *mask = MAX96717_PIO_SLEW_0_PIO00_SLEW;
> + else if (offset == 1)
> + *mask = MAX96717_PIO_SLEW_0_PIO01_SLEW;
> + else
> + *mask = MAX96717_PIO_SLEW_0_PIO02_SLEW;
> + } else if (offset < 5) {
> + *reg = MAX96717_PIO_SLEW_1;
> + if (offset == 3)
> + *mask = MAX96717_PIO_SLEW_1_PIO05_SLEW;
> + else
> + *mask = MAX96717_PIO_SLEW_1_PIO06_SLEW;
> + } else if (offset < 7) {
> + return -EINVAL;
> + } else if (offset < 9) {
> + *reg = MAX96717_PIO_SLEW_2;
> + if (offset == 7)
> + *mask = MAX96717_PIO_SLEW_2_PIO010_SLEW;
> + else
> + *mask = MAX96717_PIO_SLEW_2_PIO011_SLEW;
> + } else {
> + return -EINVAL;
> + }
> + return 0;
> + case MAX96717_PINCTRL_RX_ID:
> + *reg = MAX96717_GPIO_C(offset);
> + *mask = MAX96717_GPIO_C_GPIO_RX_ID;
> + return 0;
> + default:
> + return -ENOTSUPP;
Is it possible to use EOPNOTSUPP? Same below.
> + }
> +}
> +
> +static int max96717_conf_pin_config_get(struct pinctrl_dev *pctldev,
> + unsigned int offset,
> + unsigned long *config)
> +{
> + struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev);
> + u32 param = pinconf_to_config_param(*config);
> + unsigned int reg, mask, val, en_val;
> + int ret;
> +
> + ret = max96717_get_pin_config_reg(offset, param, ®, &mask, &en_val);
> + if (ret)
> + return ret;
> +
> + switch (param) {
> + case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> + case PIN_CONFIG_DRIVE_PUSH_PULL:
> + case PIN_CONFIG_BIAS_DISABLE:
> + case PIN_CONFIG_BIAS_PULL_DOWN:
> + case PIN_CONFIG_BIAS_PULL_UP:
> + case MAX96717_PINCTRL_JITTER_COMPENSATION_EN:
> + case MAX96717_PINCTRL_TX_EN:
> + case MAX96717_PINCTRL_RX_EN:
> + ret = regmap_read(priv->regmap, reg, &val);
> + if (ret)
> + return ret;
> +
> + val = field_get(mask, val) == en_val;
> + if (!val)
> + return -EINVAL;
> +
> + break;
> + case PIN_CONFIG_OUTPUT_ENABLE:
> + case PIN_CONFIG_INPUT_ENABLE:
> + case MAX96717_PINCTRL_PULL_STRENGTH_HIGH:
> + case MAX96717_PINCTRL_INPUT_VALUE:
> + case PIN_CONFIG_LEVEL:
> + ret = regmap_read(priv->regmap, reg, &val);
> + if (ret)
> + return ret;
> +
> + val = field_get(mask, val) == en_val;
> + break;
> + case MAX96717_PINCTRL_TX_ID:
> + case MAX96717_PINCTRL_RX_ID:
> + case PIN_CONFIG_SLEW_RATE:
> + ret = regmap_read(priv->regmap, reg, &val);
> + if (ret)
> + return ret;
> +
> + val = field_get(mask, val);
> + break;
> + default:
> + return -ENOTSUPP;
> + }
> +
> + switch (param) {
> + case PIN_CONFIG_BIAS_PULL_DOWN:
> + case PIN_CONFIG_BIAS_PULL_UP:
> + *config = pinconf_to_config_packed(MAX96717_PINCTRL_PULL_STRENGTH_HIGH, 0);
> +
> + ret = max96717_conf_pin_config_get(pctldev, offset, config);
> + if (ret)
> + return ret;
> +
> + val = pinconf_to_config_argument(*config);
> + if (val)
> + val = MAX96717_BIAS_PULL_STRENGTH_1000000_OHM;
> + else
> + val = MAX96717_BIAS_PULL_STRENGTH_40000_OHM;
> +
> + break;
> + case MAX96717_PINCTRL_TX_ID:
> + *config = pinconf_to_config_packed(MAX96717_PINCTRL_TX_EN, 0);
> +
> + ret = max96717_conf_pin_config_get(pctldev, offset, config);
> + if (ret)
> + return ret;
> +
> + break;
> + case MAX96717_PINCTRL_RX_ID:
> + *config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 0);
> +
> + ret = max96717_conf_pin_config_get(pctldev, offset, config);
> + if (ret)
> + return ret;
> +
> + break;
> + default:
> + break;
> + }
> +
> + *config = pinconf_to_config_packed(param, val);
> +
> + return 0;
> +}
> +
> +static int max96717_conf_pin_config_set_one(struct max96717_priv *priv,
> + unsigned int offset,
> + unsigned long config)
> +{
> + u32 param = pinconf_to_config_param(config);
> + u32 arg = pinconf_to_config_argument(config);
> + unsigned int reg, mask, val, en_val;
> + int ret;
> +
> + ret = max96717_get_pin_config_reg(offset, param, ®, &mask, &en_val);
> + if (ret)
> + return ret;
> +
> + switch (param) {
> + case PIN_CONFIG_DRIVE_OPEN_DRAIN:
> + case PIN_CONFIG_DRIVE_PUSH_PULL:
> + case PIN_CONFIG_BIAS_DISABLE:
> + case PIN_CONFIG_BIAS_PULL_DOWN:
> + case PIN_CONFIG_BIAS_PULL_UP:
> + val = field_prep(mask, en_val);
> +
> + ret = regmap_update_bits(priv->regmap, reg, mask, val);
> + break;
> + case MAX96717_PINCTRL_JITTER_COMPENSATION_EN:
> + case MAX96717_PINCTRL_PULL_STRENGTH_HIGH:
> + case MAX96717_PINCTRL_TX_EN:
> + case MAX96717_PINCTRL_RX_EN:
> + case PIN_CONFIG_OUTPUT_ENABLE:
> + case PIN_CONFIG_INPUT_ENABLE:
> + case PIN_CONFIG_LEVEL:
> + val = field_prep(mask, arg ? en_val : ~en_val);
> +
> + ret = regmap_update_bits(priv->regmap, reg, mask, val);
> + break;
> + case MAX96717_PINCTRL_TX_ID:
> + case MAX96717_PINCTRL_RX_ID:
> + case PIN_CONFIG_SLEW_RATE:
> + val = field_prep(mask, arg);
> +
> + ret = regmap_update_bits(priv->regmap, reg, mask, val);
> + break;
> + default:
> + return -ENOTSUPP;
> + }
> +
> + if (ret)
> + return ret;
> +
> + switch (param) {
> + case PIN_CONFIG_BIAS_PULL_DOWN:
> + case PIN_CONFIG_BIAS_PULL_UP:
> + arg = arg >= MAX96717_BIAS_PULL_STRENGTH_1000000_OHM;
> + config = pinconf_to_config_packed(MAX96717_PINCTRL_PULL_STRENGTH_HIGH, arg);
> + return max96717_conf_pin_config_set_one(priv, offset, config);
> + case PIN_CONFIG_LEVEL:
> + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT_ENABLE, 1);
> + return max96717_conf_pin_config_set_one(priv, offset, config);
> + case PIN_CONFIG_OUTPUT_ENABLE:
> + config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 0);
> + return max96717_conf_pin_config_set_one(priv, offset, config);
> + case MAX96717_PINCTRL_TX_ID:
> + config = pinconf_to_config_packed(MAX96717_PINCTRL_TX_EN, 1);
> + return max96717_conf_pin_config_set_one(priv, offset, config);
> + case MAX96717_PINCTRL_RX_ID:
> + config = pinconf_to_config_packed(MAX96717_PINCTRL_RX_EN, 1);
> + return max96717_conf_pin_config_set_one(priv, offset, config);
> + default:
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static int max96717_conf_pin_config_set(struct pinctrl_dev *pctldev,
> + unsigned int offset,
> + unsigned long *configs,
> + unsigned int num_configs)
> +{
> + struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev);
> + int ret;
> +
> + while (num_configs--) {
> + unsigned long config = *configs;
> +
> + ret = max96717_conf_pin_config_set_one(priv, offset, config);
> + if (ret)
> + return ret;
> +
> + configs++;
> + }
> +
> + return 0;
> +}
> +
> +static int max96717_mux_get_functions_count(struct pinctrl_dev *pctldev)
> +{
> + return ARRAY_SIZE(max96717_functions);
> +}
> +
> +static const char *max96717_mux_get_function_name(struct pinctrl_dev *pctldev,
> + unsigned int selector)
> +{
> + return max96717_functions[selector].name;
> +}
> +
> +static int max96717_mux_get_groups(struct pinctrl_dev *pctldev,
> + unsigned int selector,
> + const char * const **groups,
> + unsigned int * const num_groups)
> +{
> + *groups = max96717_functions[selector].groups;
> + *num_groups = max96717_functions[selector].ngroups;
> +
> + return 0;
> +}
> +
> +static int max96717_mux_set_rclkout(struct max96717_priv *priv, unsigned int group)
> +{
> + unsigned long config;
> + int ret;
> +
> + config = pinconf_to_config_packed(PIN_CONFIG_SLEW_RATE,
> + MAX96717_PIO_SLEW_FASTEST);
> + ret = max96717_conf_pin_config_set_one(priv, group, config);
> + if (ret)
> + return ret;
> +
> + return regmap_assign_bits(priv->regmap, MAX96717_REG3,
> + MAX96717_REG3_RCLK_ALT,
> + group == MAX96717_RCLK_ALT_MFP);
> +}
> +
> +static int max96717_mux_set(struct pinctrl_dev *pctldev, unsigned int selector,
> + unsigned int group)
> +{
> + struct max96717_priv *priv = pinctrl_dev_get_drvdata(pctldev);
> +
> + switch (selector) {
> + case max96717_func_rclkout:
> + return max96717_mux_set_rclkout(priv, group);
> + }
> +
> + return 0;
> +}
> +
> +static int max96717_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
> +{
> + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT_ENABLE, 0);
> + struct max96717_priv *priv = gpiochip_get_data(gc);
> + int ret;
> +
> + ret = max96717_conf_pin_config_get(priv->pctldev, offset, &config);
> + if (ret)
> + return ret;
> +
> + return pinconf_to_config_argument(config) ? GPIO_LINE_DIRECTION_OUT
> + : GPIO_LINE_DIRECTION_IN;
> +}
> +
> +static int max96717_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
> +{
> + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1);
> + struct max96717_priv *priv = gpiochip_get_data(gc);
> +
> + return max96717_conf_pin_config_set_one(priv, offset, config);
> +}
> +
> +static int max96717_gpio_direction_output(struct gpio_chip *gc, unsigned int offset,
> + int value)
> +{
> + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_LEVEL, value);
> + struct max96717_priv *priv = gpiochip_get_data(gc);
> +
> + return max96717_conf_pin_config_set_one(priv, offset, config);
> +}
> +
> +static int max96717_gpio_get(struct gpio_chip *gc, unsigned int offset)
> +{
> + unsigned long config = pinconf_to_config_packed(MAX96717_PINCTRL_INPUT_VALUE, 0);
> + struct max96717_priv *priv = gpiochip_get_data(gc);
> + int ret;
> +
> + ret = max96717_conf_pin_config_get(priv->pctldev, offset, &config);
> + if (ret)
> + return ret;
> +
> + return pinconf_to_config_argument(config);
> +}
> +
> +static int max96717_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
> +{
> + unsigned long config = pinconf_to_config_packed(PIN_CONFIG_LEVEL, value);
> + struct max96717_priv *priv = gpiochip_get_data(gc);
> +
> + return max96717_conf_pin_config_set_one(priv, offset, config);
> +}
> +
> +static unsigned int max96717_pipe_id(struct max96717_priv *priv,
> + struct max_ser_pipe *pipe)
> +{
> + return priv->info->pipe_hw_ids[pipe->index];
> +}
> +
> +static unsigned int max96717_phy_id(struct max96717_priv *priv,
> + struct max_ser_phy *phy)
> +{
> + return priv->info->phy_hw_ids[phy->index];
> +}
> +
> +static int max96717_set_pipe_enable(struct max_ser *ser,
> + struct max_ser_pipe *pipe, bool enable)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_pipe_id(priv, pipe);
> + unsigned int mask = MAX96717_REG2_VID_TX_EN_P(index);
> +
> + return regmap_assign_bits(priv->regmap, MAX96717_REG2, mask, enable);
> +}
> +
> +static int __maybe_unused max96717_reg_read(struct max_ser *ser, unsigned int reg,
> + unsigned int *val)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> +
> + return regmap_read(priv->regmap, reg, val);
> +}
> +
> +static int __maybe_unused max96717_reg_write(struct max_ser *ser, unsigned int reg,
> + unsigned int val)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> +
> + return regmap_write(priv->regmap, reg, val);
> +}
> +
> +static int max96717_set_pipe_dt_en(struct max_ser *ser, struct max_ser_pipe *pipe,
> + unsigned int i, bool enable)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_pipe_id(priv, pipe);
> + unsigned int reg;
> +
> + if (i < 2)
> + reg = MAX96717_FRONTTOP_12(index, i);
> + else
> + /*
> + * DT 7 and 8 are only supported on MAX96717, no need for pipe
> + * index to be taken into account.
> + */
> + reg = MAX96717_EXTA(i - 2);
> +
> + return regmap_assign_bits(priv->regmap, reg, MAX96717_MEM_DT_EN, enable);
> +}
> +
> +static int max96717_set_pipe_dt(struct max_ser *ser, struct max_ser_pipe *pipe,
> + unsigned int i, unsigned int dt)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_pipe_id(priv, pipe);
> + unsigned int reg;
> +
> + if (i < 2)
> + reg = MAX96717_FRONTTOP_12(index, i);
> + else
> + reg = MAX96717_EXTA(i - 2);
> +
> + return regmap_update_bits(priv->regmap, reg, MAX96717_MEM_DT_SEL,
> + FIELD_PREP(MAX96717_MEM_DT_SEL, dt));
> +}
> +
> +static int max96717_set_pipe_vcs(struct max_ser *ser,
> + struct max_ser_pipe *pipe,
> + unsigned int vcs)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_pipe_id(priv, pipe);
> + int ret;
> +
> + ret = regmap_write(priv->regmap, MAX96717_FRONTTOP_1(index),
> + (vcs >> 0) & 0xff);
> + if (ret)
> + return ret;
> +
> + return regmap_write(priv->regmap, MAX96717_FRONTTOP_2(index),
> + (vcs >> 8) & 0xff);
> +}
> +
> +static int max96717_log_status(struct max_ser *ser)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int val;
> + int ret;
> +
> + if (!(priv->info->modes & BIT(MAX_SERDES_GMSL_TUNNEL_MODE)))
> + return 0;
> +
> + ret = regmap_read(priv->regmap, MAX96717_EXT23, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "tun_pkt_cnt: %u\n", val);
> +
> + return 0;
> +}
> +
> +static int max96717_log_pipe_status(struct max_ser *ser,
> + struct max_ser_pipe *pipe)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_pipe_id(priv, pipe);
> + unsigned int val;
> + int ret;
> +
> + ret = regmap_read(priv->regmap, MAX96717_VIDEO_TX2(index), &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tpclkdet: %u\n",
> + !!(val & MAX96717_VIDEO_TX2_PCLKDET));
> +
> + return 0;
> +}
> +
> +static int max96717_log_phy_status(struct max_ser *ser,
> + struct max_ser_phy *phy)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int val;
> + int ret;
> +
> + if (!priv->info->supports_pkt_cnt)
> + return 0;
> +
> + ret = regmap_read(priv->regmap, MAX96717_EXT21, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tphy_pkt_cnt: %u\n", val);
> +
> + ret = regmap_read(priv->regmap, MAX96717_EXT22, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tcsi_pkt_cnt: %u\n", val);
> +
> + ret = regmap_read(priv->regmap, MAX96717_EXT24, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tphy_clk_cnt: %u\n", val);
> +
> + return 0;
> +}
> +
> +static int max96717_init_phy(struct max_ser *ser,
> + struct max_ser_phy *phy)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int num_data_lanes = phy->mipi.num_data_lanes;
> + unsigned int used_data_lanes = 0;
> + unsigned int val;
> + unsigned int i;
> + int ret;
> +
> + if (num_data_lanes == 3 && !priv->info->supports_3_data_lanes) {
> + dev_err(priv->dev, "Unsupported 3 data lane mode\n");
> + return -EINVAL;
> + }
> +
> + if (phy->mipi.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK &&
> + !priv->info->supports_noncontinuous_clock) {
> + dev_err(priv->dev, "Unsupported non-continuous mode\n");
> + return -EINVAL;
> + }
> +
> + /* Configure a lane count. */
> + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX1,
> + MAX96717_MIPI_RX1_CTRL_NUM_LANES,
> + FIELD_PREP(MAX96717_MIPI_RX1_CTRL_NUM_LANES,
> + num_data_lanes - 1));
> + if (ret)
> + return ret;
> +
> + /* Configure lane mapping. */
> + val = 0;
> + for (i = 0; i < 4; i++) {
> + unsigned int map;
> +
> + if (i < num_data_lanes)
> + map = phy->mipi.data_lanes[i] - 1;
> + else
> + map = ffz(used_data_lanes);
> +
> + val |= map << (i * 2);
> + used_data_lanes |= BIT(map);
> + }
> +
> + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX3,
> + MAX96717_MIPI_RX3_PHY2_LANE_MAP,
> + FIELD_PREP(MAX96717_MIPI_RX3_PHY2_LANE_MAP, val));
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX2,
> + MAX96717_MIPI_RX2_PHY1_LANE_MAP,
> + FIELD_PREP(MAX96717_MIPI_RX2_PHY1_LANE_MAP, val >> 4));
> + if (ret)
> + return ret;
> +
> + /* Configure lane polarity. */
> + for (i = 0, val = 0; i < num_data_lanes; i++)
> + if (phy->mipi.lane_polarities[i + 1])
> + val |= BIT(i);
> +
> + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX5,
> + MAX96717_MIPI_RX5_PHY2_POL_MAP,
> + FIELD_PREP(MAX96717_MIPI_RX5_PHY2_POL_MAP, val));
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96717_MIPI_RX4,
> + MAX96717_MIPI_RX4_PHY1_POL_MAP,
> + FIELD_PREP(MAX96717_MIPI_RX4_PHY1_POL_MAP, val >> 2));
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96717_MIPI_RX5,
> + MAX96717_MIPI_RX5_PHY2_POL_MAP_CLK,
> + phy->mipi.lane_polarities[0]);
> + if (ret)
> + return ret;
> +
> + if (priv->info->supports_noncontinuous_clock) {
> + ret = regmap_assign_bits(priv->regmap, MAX96717_MIPI_RX0,
> + MAX96717_MIPI_RX0_NONCONTCLK_EN,
> + phy->mipi.flags &
> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int max96717_set_phy_active(struct max_ser *ser, struct max_ser_phy *phy,
> + bool enable)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_phy_id(priv, phy);
> +
> + return regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_0,
> + MAX96717_FRONTTOP_0_START_PORT(index), enable);
> +}
> +
> +static int max96717_set_pipe_stream_id(struct max_ser *ser,
> + struct max_ser_pipe *pipe,
> + unsigned int stream_id)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_pipe_id(priv, pipe);
> +
> + return regmap_update_bits(priv->regmap, MAX96717_TX3(index),
> + MAX96717_TX3_TX_STR_SEL,
> + FIELD_PREP(MAX96717_TX3_TX_STR_SEL, stream_id));
> +}
> +
> +static int max96717_set_pipe_phy(struct max_ser *ser, struct max_ser_pipe *pipe,
> + struct max_ser_phy *phy)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_pipe_id(priv, pipe);
> + unsigned int phy_id = max96717_phy_id(priv, phy);
> + int ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_0,
> + MAX96717_FRONTTOP_0_CLK_SEL_P(index),
> + phy_id == 1);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_9,
> + MAX96717_FRONTTOP_9_START_PORT(index, 0),
> + phy_id == 0);
> + if (ret)
> + return ret;
> +
> + return regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_9,
> + MAX96717_FRONTTOP_9_START_PORT(index, 1),
> + phy_id == 1);
> +}
> +
> +static int max96717_set_pipe_mode(struct max_ser *ser,
> + struct max_ser_pipe *pipe,
> + struct max_ser_pipe_mode *mode)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + unsigned int index = max96717_pipe_id(priv, pipe);
> + int ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96717_VIDEO_TX0(index),
> + MAX96717_VIDEO_TX0_AUTO_BPP, !mode->bpp);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96717_VIDEO_TX1(index),
> + MAX96717_VIDEO_TX1_BPP,
> + FIELD_PREP(MAX96717_VIDEO_TX1_BPP, mode->bpp));
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96717_VIDEO_TX2(index),
> + MAX96717_VIDEO_TX2_DRIFT_DET_EN, !mode->bpp);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_10,
> + MAX96717_FRONTTOP_10_BPP8DBL(index),
> + mode->dbl8);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_11,
> + MAX96717_FRONTTOP_11_BPP10DBL(index),
> + mode->dbl10);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96717_FRONTTOP_11,
> + MAX96717_FRONTTOP_11_BPP12DBL(index),
> + mode->dbl12);
> + if (ret)
> + return ret;
> +
> + return regmap_update_bits(priv->regmap, MAX96717_FRONTTOP_20(index),
> + MAX96717_FRONTTOP_20_SOFT_BPP |
> + MAX96717_FRONTTOP_20_SOFT_BPP_EN,
> + FIELD_PREP(MAX96717_FRONTTOP_20_SOFT_BPP,
> + mode->soft_bpp) |
> + FIELD_PREP(MAX96717_FRONTTOP_20_SOFT_BPP_EN,
> + !!mode->soft_bpp));
> +}
> +
> +static int max96717_set_i2c_xlate(struct max_ser *ser, unsigned int i,
> + struct max_serdes_i2c_xlate *xlate)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + int ret;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96717_I2C_2(i),
> + MAX96717_I2C_2_SRC,
> + FIELD_PREP(MAX96717_I2C_2_SRC, xlate->src));
> + if (ret)
> + return ret;
> +
> + return regmap_update_bits(priv->regmap, MAX96717_I2C_3(i),
> + MAX96717_I2C_3_DST,
> + FIELD_PREP(MAX96717_I2C_3_DST, xlate->dst));
> +}
> +
> +static int max96717_set_tunnel_enable(struct max_ser *ser, bool enable)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> +
> + return regmap_assign_bits(priv->regmap, MAX96717_EXT11,
> + MAX96717_EXT11_TUN_MODE, enable);
> +}
> +
> +static int max96717_set_tpg_timings(struct max96717_priv *priv,
> + const struct max_serdes_tpg_timings *tm,
> + unsigned int index)
> +{
> + const struct reg_sequence regs[] = {
> + REG_SEQUENCE_3(MAX96717_VTX2_VS_DLY_2(index), tm->vs_dly),
> + REG_SEQUENCE_3(MAX96717_VTX5_VS_HIGH_2(index), tm->vs_high),
> + REG_SEQUENCE_3(MAX96717_VTX8_VS_LOW_2(index), tm->vs_low),
> + REG_SEQUENCE_3(MAX96717_VTX11_V2H_2(index), tm->v2h),
> + REG_SEQUENCE_2(MAX96717_VTX14_HS_HIGH_1(index), tm->hs_high),
> + REG_SEQUENCE_2(MAX96717_VTX16_HS_LOW_1(index), tm->hs_low),
> + REG_SEQUENCE_2(MAX96717_VTX18_HS_CNT_1(index), tm->hs_cnt),
> + REG_SEQUENCE_3(MAX96717_VTX20_V2D_2(index), tm->v2d),
> + REG_SEQUENCE_2(MAX96717_VTX23_DE_HIGH_1(index), tm->de_high),
> + REG_SEQUENCE_2(MAX96717_VTX25_DE_LOW_1(index), tm->de_low),
> + REG_SEQUENCE_2(MAX96717_VTX27_DE_CNT_1(index), tm->de_cnt),
> + };
> + int ret;
> +
> + ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> + if (ret)
> + return ret;
> +
> + return regmap_write(priv->regmap, MAX96717_VTX0(index),
> + FIELD_PREP(MAX96717_VTX0_VTG_MODE,
> + MAX96717_VTX0_VTG_MODE_FREE_RUNNING) |
> + FIELD_PREP(MAX96717_VTX0_DE_INV, tm->de_inv) |
> + FIELD_PREP(MAX96717_VTX0_HS_INV, tm->hs_inv) |
> + FIELD_PREP(MAX96717_VTX0_VS_INV, tm->vs_inv) |
> + FIELD_PREP(MAX96717_VTX0_GEN_DE, tm->gen_de) |
> + FIELD_PREP(MAX96717_VTX0_GEN_HS, tm->gen_hs) |
> + FIELD_PREP(MAX96717_VTX0_GEN_VS, tm->gen_vs));
> +}
> +
> +static int max96717_set_tpg_clk(struct max96717_priv *priv, u32 clock,
> + unsigned int index)
> +{
> + u8 pclk_src;
> +
> + switch (clock) {
> + case 25000000:
> + pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_25MHZ;
> + break;
> + case 75000000:
> + pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_75MHZ;
> + break;
> + case 150000000:
> + pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_150MHZ;
> + break;
> + case 375000000:
> + pclk_src = MAX96717_VTX1_PATGEN_CLK_SRC_375MHZ;
> + break;
> + case 0:
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +
> + return regmap_update_bits(priv->regmap, MAX96717_VTX1(index),
> + MAX96717_VTX1_PATGEN_CLK_SRC,
> + FIELD_PREP(MAX96717_VTX1_PATGEN_CLK_SRC,
> + pclk_src));
> +}
> +
> +static int max96717_set_tpg_mode(struct max96717_priv *priv, bool enable,
> + unsigned int index)
> +{
> + unsigned int patgen_mode;
> +
> + switch (priv->ser.tpg_pattern) {
> + case MAX_SERDES_TPG_PATTERN_GRADIENT:
> + patgen_mode = MAX96717_VTX29_PATGEN_MODE_GRADIENT;
> + break;
> + case MAX_SERDES_TPG_PATTERN_CHECKERBOARD:
> + patgen_mode = MAX96717_VTX29_PATGEN_MODE_CHECKER;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return regmap_update_bits(priv->regmap, MAX96717_VTX29(index),
> + MAX96717_VTX29_PATGEN_MODE,
> + FIELD_PREP(MAX96717_VTX29_PATGEN_MODE,
> + enable ? patgen_mode
> + : MAX96717_VTX29_PATGEN_MODE_DISABLED));
> +}
> +
> +static int max96717_set_tpg(struct max_ser *ser,
> + const struct max_serdes_tpg_entry *entry)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + /*
> + * MAX9295A supports multiple pipes, each with a pattern generator,
> + * use only the first pipe for simplicity.
> + */
> + unsigned int index = max96717_pipe_id(priv, &ser->pipes[0]);
> + struct max_serdes_tpg_timings timings = { 0 };
> + int ret;
> +
> + ret = max_serdes_get_tpg_timings(entry, &timings);
> + if (ret)
> + return ret;
> +
> + ret = max96717_set_tpg_timings(priv, &timings, index);
> + if (ret)
> + return ret;
> +
> + ret = max96717_set_tpg_clk(priv, timings.clock, index);
> + if (ret)
> + return ret;
> +
> + return max96717_set_tpg_mode(priv, entry, index);
> +}
> +
> +static const struct max_serdes_phys_config max96717_phys_configs[] = {
> + { { 4 } },
> +};
> +
> +static int max96717_init_tpg(struct max_ser *ser)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + /*
> + * MAX9295A supports multiple pipes, each with a pattern generator,
> + * use only the first pipe for simplicity.
> + */
> + unsigned int index = max96717_pipe_id(priv, &ser->pipes[0]);
> +
> + const struct reg_sequence regs[] = {
> + { MAX96717_VTX30_GRAD_INCR(index), MAX_SERDES_GRAD_INCR },
> + REG_SEQUENCE_3_LE(MAX96717_VTX31_CHKR_A_L(index),
> + MAX_SERDES_CHECKER_COLOR_A),
> + REG_SEQUENCE_3_LE(MAX96717_VTX34_CHKR_B_L(index),
> + MAX_SERDES_CHECKER_COLOR_B),
> + { MAX96717_VTX37_CHKR_RPT_A(index), MAX_SERDES_CHECKER_SIZE },
> + { MAX96717_VTX38_CHKR_RPT_B(index), MAX_SERDES_CHECKER_SIZE },
> + { MAX96717_VTX39_CHKR_ALT(index), MAX_SERDES_CHECKER_SIZE },
> + };
> +
> + return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> +}
> +
> +static int max96717_init(struct max_ser *ser)
> +{
> + struct max96717_priv *priv = ser_to_priv(ser);
> + int ret;
> +
> + /*
> + * Set CMU2 PFDDIV to 1.1V for correct functionality of the device,
> + * as mentioned in the datasheet, under section MANDATORY REGISTER PROGRAMMING.
> + */
> + ret = regmap_update_bits(priv->regmap, MAX96717_CMU2,
> + MAX96717_CMU2_PFDDIV_RSHORT,
> + FIELD_PREP(MAX96717_CMU2_PFDDIV_RSHORT,
> + MAX96717_CMU2_PFDDIV_RSHORT_1_1V));
> + if (ret)
> + return ret;
> +
> + if (ser->ops->set_tunnel_enable) {
> + ret = ser->ops->set_tunnel_enable(ser, false);
> + if (ret)
> + return ret;
> + }
> +
> + return max96717_init_tpg(ser);
> +}
> +
> +static const struct pinctrl_ops max96717_ctrl_ops = {
> + .get_groups_count = max96717_ctrl_get_groups_count,
> + .get_group_name = max96717_ctrl_get_group_name,
> + .get_group_pins = max96717_ctrl_get_group_pins,
> + .dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
> + .dt_free_map = pinconf_generic_dt_free_map,
> +};
> +
> +static const struct pinconf_ops max96717_conf_ops = {
> + .pin_config_get = max96717_conf_pin_config_get,
> + .pin_config_set = max96717_conf_pin_config_set,
> + .is_generic = true,
> +};
> +
> +static const struct pinmux_ops max96717_mux_ops = {
> + .get_functions_count = max96717_mux_get_functions_count,
> + .get_function_name = max96717_mux_get_function_name,
> + .get_function_groups = max96717_mux_get_groups,
> + .set_mux = max96717_mux_set,
> +};
> +
> +static const struct max_serdes_tpg_entry max96717_tpg_entries[] = {
> + MAX_TPG_ENTRY_640X480P60_RGB888,
> + MAX_TPG_ENTRY_1920X1080P30_RGB888,
> + MAX_TPG_ENTRY_1920X1080P60_RGB888,
> +};
> +
> +static const struct max_ser_ops max96717_ops = {
> + .num_i2c_xlates = 2,
> + .phys_configs = {
> + .num_configs = ARRAY_SIZE(max96717_phys_configs),
> + .configs = max96717_phys_configs,
> + },
> + .tpg_entries = {
> + .num_entries = ARRAY_SIZE(max96717_tpg_entries),
> + .entries = max96717_tpg_entries,
> + },
> + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE,
> + .tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) |
> + BIT(MAX_SERDES_TPG_PATTERN_GRADIENT),
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + .reg_read = max96717_reg_read,
> + .reg_write = max96717_reg_write,
> +#endif
> + .log_status = max96717_log_status,
> + .log_pipe_status = max96717_log_pipe_status,
> + .log_phy_status = max96717_log_phy_status,
> + .init = max96717_init,
> + .set_i2c_xlate = max96717_set_i2c_xlate,
> + .set_tpg = max96717_set_tpg,
> + .init_phy = max96717_init_phy,
> + .set_phy_active = max96717_set_phy_active,
> + .set_pipe_enable = max96717_set_pipe_enable,
> + .set_pipe_dt = max96717_set_pipe_dt,
> + .set_pipe_dt_en = max96717_set_pipe_dt_en,
> + .set_pipe_vcs = max96717_set_pipe_vcs,
> + .set_pipe_mode = max96717_set_pipe_mode,
> + .set_pipe_stream_id = max96717_set_pipe_stream_id,
> + .set_pipe_phy = max96717_set_pipe_phy,
> +};
> +
> +struct max96717_pll_predef_freq {
> + unsigned long freq;
> + bool is_rclk;
> + bool is_alt;
> + u8 val;
> + u8 rclksel;
> +};
> +
> +static const struct max96717_pll_predef_freq max96717_predef_freqs[] = {
> + { 6250000, true, false, 0, 2 },
> + { 12500000, true, false, 0, 1 },
> + { 13500000, false, true, 0, 3 },
> + { 19200000, false, false, 0, 3 },
> + { 24000000, false, true, 1, 3 },
> + { 25000000, true, false, 0, 0 },
> + { 27000000, false, false, 1, 3 },
> + { 37125000, false, false, 2, 3 },
> + { 74250000, false, false, 3, 3 },
> +};
> +
> +static unsigned long
> +max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
> +{
> + struct max96717_priv *priv = clk_hw_to_priv(hw);
> +
> + return max96717_predef_freqs[priv->pll_predef_index].freq;
> +}
> +
> +static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv,
> + unsigned long rate)
> +{
> + unsigned int i, idx = 0;
> + unsigned long diff_new, diff_old = U32_MAX;
> +
> + for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) {
> + diff_new = abs(rate - max96717_predef_freqs[i].freq);
> + if (diff_new < diff_old) {
> + diff_old = diff_new;
> + idx = i;
> + }
> + }
> +
> + return idx;
> +}
> +
> +static int max96717_clk_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct max96717_priv *priv = clk_hw_to_priv(hw);
> + struct device *dev = &priv->client->dev;
> + unsigned int idx;
> +
> + idx = max96717_clk_find_best_index(priv, req->rate);
> +
> + if (req->rate != max96717_predef_freqs[idx].freq) {
> + dev_dbg(dev, "Request CLK freq:%lu, found CLK freq:%lu\n",
> + req->rate, max96717_predef_freqs[idx].freq);
> + }
> +
> + req->rate = max96717_predef_freqs[idx].freq;
> +
> + return 0;
> +}
> +
> +static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + const struct max96717_pll_predef_freq *predef_freq;
> + struct max96717_priv *priv = clk_hw_to_priv(hw);
> + unsigned int val, idx;
> + int ret = 0;
> +
> + idx = max96717_clk_find_best_index(priv, rate);
> + predef_freq = &max96717_predef_freqs[idx];
> +
> + ret = regmap_update_bits(priv->regmap, MAX96717_REG3,
> + MAX96717_REG3_RCLKSEL,
> + FIELD_PREP(MAX96717_REG3_RCLKSEL,
> + predef_freq->rclksel));
> + if (ret)
> + return ret;
> +
> + val = FIELD_PREP(MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ,
> + predef_freq->val);
> +
> + if (predef_freq->is_alt)
> + val |= MAX96717_REF_VTG0_REFGEN_PREDEF_FREQ_ALT;
> + if (!predef_freq->is_rclk)
> + val |= MAX96717_REF_VTG0_REFGEN_EN;
> +
> + val |= MAX96717_REF_VTG0_REFGEN_RST;
> +
> + ret = regmap_write(priv->regmap, MAX96717_REF_VTG0, val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_clear_bits(priv->regmap, MAX96717_REF_VTG0,
> + MAX96717_REF_VTG0_REFGEN_RST);
> + if (ret)
> + return ret;
> +
> + priv->pll_predef_index = idx;
> +
> + return 0;
> +}
> +
> +static int max96717_clk_prepare(struct clk_hw *hw)
> +{
> + struct max96717_priv *priv = clk_hw_to_priv(hw);
> +
> + return regmap_set_bits(priv->regmap, MAX96717_REG6, MAX96717_REG6_RCLKEN);
> +}
> +
> +static void max96717_clk_unprepare(struct clk_hw *hw)
> +{
> + struct max96717_priv *priv = clk_hw_to_priv(hw);
> +
> + regmap_clear_bits(priv->regmap, MAX96717_REG6, MAX96717_REG6_RCLKEN);
> +}
> +
> +static const struct clk_ops max96717_clk_ops = {
> + .prepare = max96717_clk_prepare,
> + .unprepare = max96717_clk_unprepare,
> + .set_rate = max96717_clk_set_rate,
> + .recalc_rate = max96717_clk_recalc_rate,
> + .determine_rate = max96717_clk_determine_rate,
> +};
> +
> +static int max96717_register_clkout(struct max96717_priv *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + struct clk_init_data init = { .ops = &max96717_clk_ops };
> + int ret;
> +
> + ret = max96717_mux_set_rclkout(priv, MAX96717_RCLK_MFP);
> + if (ret)
> + return ret;
> +
> + init.name = kasprintf(GFP_KERNEL, "max96717.%s.clk_out", dev_name(dev));
> + if (!init.name)
> + return -ENOMEM;
> +
> + priv->clk_hw.init = &init;
> +
> + ret = max96717_clk_set_rate(&priv->clk_hw,
> + MAX96717_DEFAULT_CLKOUT_RATE, 0);
> + if (ret)
> + goto free_init_name;
> +
> + ret = devm_clk_hw_register(dev, &priv->clk_hw);
> + kfree(init.name);
> + if (ret)
> + return dev_err_probe(dev, ret, "Cannot register clock HW\n");
> +
> + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
> + &priv->clk_hw);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "Cannot add OF clock provider\n");
> +
> + return 0;
> +
> +free_init_name:
> + kfree(init.name);
> + return ret;
> +}
> +
> +static int max96717_gpiochip_probe(struct max96717_priv *priv)
> +{
> + struct device *dev = priv->dev;
> + int ret;
> +
> + priv->pctldesc = (struct pinctrl_desc) {
> + .owner = THIS_MODULE,
> + .name = MAX96717_PINCTRL_NAME,
> + .pins = max96717_pins,
> + .npins = ARRAY_SIZE(max96717_pins),
> + .pctlops = &max96717_ctrl_ops,
> + .confops = &max96717_conf_ops,
> + .pmxops = &max96717_mux_ops,
> + .custom_params = max96717_cfg_params,
> + .num_custom_params = ARRAY_SIZE(max96717_cfg_params),
> + };
> +
> + ret = devm_pinctrl_register_and_init(dev, &priv->pctldesc, priv, &priv->pctldev);
> + if (ret)
> + return ret;
> +
> + ret = pinctrl_enable(priv->pctldev);
> + if (ret)
> + return ret;
> +
> + priv->gc = (struct gpio_chip) {
> + .owner = THIS_MODULE,
> + .label = MAX96717_GPIOCHIP_NAME,
> + .base = -1,
> + .ngpio = MAX96717_GPIO_NUM,
> + .parent = dev,
> + .can_sleep = true,
> + .request = gpiochip_generic_request,
> + .free = gpiochip_generic_free,
> + .set_config = gpiochip_generic_config,
> + .get_direction = max96717_gpio_get_direction,
> + .direction_input = max96717_gpio_direction_input,
> + .direction_output = max96717_gpio_direction_output,
> + .get = max96717_gpio_get,
> + .set = max96717_gpio_set,
> + };
> +
> + return devm_gpiochip_add_data(dev, &priv->gc, priv);
> +}
> +
> +static int max96717_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct max96717_priv *priv;
> + struct max_ser_ops *ops;
> + int ret;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL);
> + if (!ops)
> + return -ENOMEM;
> +
> + priv->info = device_get_match_data(dev);
> + if (!priv->info) {
> + dev_err(dev, "Failed to get match data\n");
> + return -ENODEV;
> + }
> +
> + priv->dev = dev;
> + priv->client = client;
> + i2c_set_clientdata(client, priv);
> +
> + priv->regmap = devm_regmap_init_i2c(client, &max96717_i2c_regmap);
> + if (IS_ERR(priv->regmap))
> + return PTR_ERR(priv->regmap);
> +
> + *ops = max96717_ops;
> +
> + if (priv->info->modes & BIT(MAX_SERDES_GMSL_TUNNEL_MODE))
> + ops->set_tunnel_enable = max96717_set_tunnel_enable;
> +
> + ops->modes = priv->info->modes;
> + ops->num_pipes = priv->info->num_pipes;
> + ops->num_dts_per_pipe = priv->info->num_dts_per_pipe;
> + ops->num_phys = priv->info->num_phys;
> + priv->ser.ops = ops;
> +
> + ret = max96717_wait_for_device(priv);
> + if (ret)
> + return ret;
> +
> + ret = max96717_gpiochip_probe(priv);
> + if (ret)
> + return ret;
> +
> + ret = max96717_register_clkout(priv);
> + if (ret)
> + return ret;
> +
> + return max_ser_probe(client, &priv->ser);
> +}
> +
> +static void max96717_remove(struct i2c_client *client)
> +{
> + struct max96717_priv *priv = i2c_get_clientdata(client);
> +
> + max_ser_remove(&priv->ser);
> +}
> +
> +static const struct max96717_chip_info max9295a_info = {
> + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE),
> + .num_pipes = 4,
> + .num_dts_per_pipe = 2,
> + .pipe_hw_ids = { 0, 1, 2, 3 },
> + .num_phys = 1,
> + .phy_hw_ids = { 1 },
> +};
> +
> +static const struct max96717_chip_info max96717_info = {
> + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
> + BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
> + .supports_3_data_lanes = true,
> + .supports_pkt_cnt = true,
> + .supports_noncontinuous_clock = true,
> + .num_pipes = 1,
> + .num_dts_per_pipe = 4,
> + .pipe_hw_ids = { 2 },
> + .num_phys = 1,
> + .phy_hw_ids = { 1 },
> +};
> +
> +static const struct of_device_id max96717_of_ids[] = {
> + { .compatible = "maxim,max9295a", .data = &max9295a_info },
> + { .compatible = "maxim,max96717", .data = &max96717_info },
> + { .compatible = "maxim,max96717f", .data = &max96717_info },
> + { .compatible = "maxim,max96793", .data = &max96717_info },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, max96717_of_ids);
> +
> +static struct i2c_driver max96717_i2c_driver = {
> + .driver = {
> + .name = MAX96717_NAME,
> + .of_match_table = max96717_of_ids,
> + },
> + .probe = max96717_probe,
> + .remove = max96717_remove,
> +};
> +
> +module_i2c_driver(max96717_i2c_driver);
> +
> +MODULE_IMPORT_NS("MAX_SERDES");
> +MODULE_DESCRIPTION("MAX96717 GMSL2 Serializer Driver");
> +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
> +MODULE_LICENSE("GPL");
>
> --
> 2.53.0
>
>
--
Kind Regards,
Niklas Söderlund
^ permalink raw reply
* Re: [PATCH 2/3] media: dt-bindings: media: renesas,vsp1: Document RZ/T2H and RZ/N2H SoCs
From: Laurent Pinchart @ 2026-06-10 14:41 UTC (permalink / raw)
To: Prabhakar
Cc: Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Kieran Bingham, Philipp Zabel, Geert Uytterhoeven,
Magnus Damm, linux-media, linux-renesas-soc, devicetree,
linux-kernel, Biju Das, Fabrizio Castro, Lad Prabhakar
In-Reply-To: <20260430100929.1088281-3-prabhakar.mahadev-lad.rj@bp.renesas.com>
Hi Prabhakar,
Thank you for the patch.
On Thu, Apr 30, 2026 at 11:09:28AM +0100, Prabhakar wrote:
> From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
>
> Document the VSP2 blocks present on the RZ/T2H and RZ/N2H SoCs.
>
> The VSP2 implementation on these SoCs is identical to that on the
> RZ/G2L SoC.
>
> Update the schema to disallow the "resets" property for these SoCs to
> reflect the hardware integration.
>
> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
> ---
> .../devicetree/bindings/media/renesas,vsp1.yaml | 17 ++++++++++++++++-
> 1 file changed, 16 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/devicetree/bindings/media/renesas,vsp1.yaml b/Documentation/devicetree/bindings/media/renesas,vsp1.yaml
> index 803358780f01..a28632165804 100644
> --- a/Documentation/devicetree/bindings/media/renesas,vsp1.yaml
> +++ b/Documentation/devicetree/bindings/media/renesas,vsp1.yaml
> @@ -29,6 +29,8 @@ properties:
> - renesas,r9a09g047-vsp2 # RZ/G3E
> - renesas,r9a09g056-vsp2 # RZ/V2N
> - renesas,r9a09g057-vsp2 # RZ/V2H(P)
> + - renesas,r9a09g077-vsp2 # RZ/T2H
> + - renesas,r9a09g087-vsp2 # RZ/N2H
> - const: renesas,r9a07g044-vsp2 # RZ/G2L fallback
>
> reg:
> @@ -67,7 +69,6 @@ required:
> - interrupts
> - clocks
> - power-domains
> - - resets
>
> additionalProperties: false
>
> @@ -101,6 +102,20 @@ allOf:
> maxItems: 1
> clock-names: false
>
> + - if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - renesas,r9a09g077-vsp2
> + - renesas,r9a09g087-vsp2
> + then:
> + properties:
> + resets: false
> + else:
> + required:
> + - resets
> +
> examples:
> # R8A7790 (R-Car H2) VSP1-S
> - |
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH v4 10/14] mfd: lm3533: Set DMA mask
From: Svyatoslav Ryhel @ 2026-06-10 14:40 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
Johan Hovold, dri-devel, linux-leds, devicetree, linux-kernel,
linux-iio, linux-fbdev
In-Reply-To: <aihm315UtdqJclhh@ashevche-desk.local>
вт, 9 черв. 2026 р. о 22:17 Andy Shevchenko <andriy.shevchenko@intel.com> пише:
>
> On Sat, Jun 06, 2026 at 07:57:34AM +0300, Svyatoslav Ryhel wrote:
> > Missing coherent_dma_mask assigning triggers the following warning in
> > dmesg:
> >
> > [ 3.287872] platform lm3533-backlight.0: DMA mask not set
> >
> > Since this warning might be elevated to an error in the future, set
> > coherent_dma_mask to zero because both the core and cells do not utilize
> > DMA.
>
> Hmm... I am not sure about this. The entire kernel has only two drivers that
> do that, and thanks to their commit messages one of them pointed out to the
> commit from 2018. So, if no other devices suffer from this, I think it has to
> be a better way of achieving the same.
>
If mfd framework warns that DMA mask is not set then this must be
addressed. Why then there is such warning at the first place if mask
can be just skipped. Then warning would be just a debug message. What
is warning today can become error tomorrow.
> --
> With Best Regards,
> Andy Shevchenko
>
>
^ permalink raw reply
* Re: [PATCH v13 17/22] media: i2c: maxim-serdes: add MAX96724 driver
From: Niklas Söderlund @ 2026-06-10 14:42 UTC (permalink / raw)
To: dumitru.ceclan
Cc: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring, Greg Kroah-Hartman,
mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Martin Hecht, Cosmin Tanislav,
Cory Keitz
In-Reply-To: <20260604-gmsl2-3_serdes-v13-17-9d8a4919983b@analog.com>
Hi,
Thanks for your work.
This patch gives me new compiler warnings, can they be avoided?
.../max96724.c:402 max96724_log_phy_status() warn: subtract is higher precedence than shift
.../max96724.c:409 max96724_log_phy_status() warn: subtract is higher precedence than shift
.../max96724.c:588 max96724_init_phy() warn: subtract is higher precedence than shift
.../max96724.c:756 max96724_set_pipe_remap() warn: subtract is higher precedence than shift
.../max96724.c:796 max96724_set_pipe_phy() warn: subtract is higher precedence than shift
.../max96724.c:818 max96724_set_pipe_stream_id() warn: subtract is higher precedence than shift
.../max96724.c:830 max96724_set_pipe_link() warn: subtract is higher precedence than shift
.../max96724.c:942 max96724_set_link_version() warn: subtract is higher precedence than shift
On 2026-06-04 17:14:04 +0300, Dumitru Ceclan via B4 Relay wrote:
> From: Cosmin Tanislav <demonsingur@gmail.com>
>
> Add a new MAX96724 driver that also supports MAX96712, MAX96724F
> and MAX96724R.
>
> Integrate it with the common deserializer framework, while keeping
> compatibility with existing usecases, avoiding code duplication, and
> also enabling more features across all chips.
>
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Tested-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> Tested-by: Cory Keitz <ckeitz@amazon.com>
> ---
> drivers/media/i2c/maxim-serdes/Kconfig | 13 +
> drivers/media/i2c/maxim-serdes/Makefile | 1 +
> drivers/media/i2c/maxim-serdes/max96724.c | 1279 +++++++++++++++++++++++++++++
> 3 files changed, 1293 insertions(+)
>
> diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
> index c811790c09b9..9d3621ae8d90 100644
> --- a/drivers/media/i2c/maxim-serdes/Kconfig
> +++ b/drivers/media/i2c/maxim-serdes/Kconfig
> @@ -34,3 +34,16 @@ config VIDEO_MAX96717
>
> To compile this driver as a module, choose M here: the module
> will be called max96717.
> +
> +config VIDEO_MAX96724
> + tristate "Maxim MAX96724 Quad Deserializer support"
> + depends on I2C
> + depends on VIDEO_DEV
> + select VIDEO_MAXIM_SERDES
> + help
> + This driver supports the Maxim MAX96712, MAX96724, MAX96724F,
> + MAX96724R Quad Deserializers, which convert from four GMSL2
> + links to up to four MIPI D-PHY or C-PHY outputs.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called max96724.
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> index 04abda6a5437..b6d5aebfaee1 100644
> --- a/drivers/media/i2c/maxim-serdes/Makefile
> +++ b/drivers/media/i2c/maxim-serdes/Makefile
> @@ -2,3 +2,4 @@
> max-serdes-objs := max_serdes.o max_ser.o max_des.o
> obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
> obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
> +obj-$(CONFIG_VIDEO_MAX96724) += max96724.o
> diff --git a/drivers/media/i2c/maxim-serdes/max96724.c b/drivers/media/i2c/maxim-serdes/max96724.c
> new file mode 100644
> index 000000000000..7fc51254e1ef
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max96724.c
> @@ -0,0 +1,1279 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim MAX96724 Quad GMSL2 Deserializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of_graph.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +
> +#include "max_des.h"
> +
> +#define MAX96724_REG0 0x0
> +
> +#define MAX96724_REG3 0x3
> +#define MAX96724_REG3_CC_PORT_SEL(n) GENMASK((n) * 2 + 1, (n) * 2)
> +#define MAX96724_REG3_CC_PORT_SEL_MASK (MAX96724_REG3_CC_PORT_SEL(0) | \
> + MAX96724_REG3_CC_PORT_SEL(1) | \
> + MAX96724_REG3_CC_PORT_SEL(2) | \
> + MAX96724_REG3_CC_PORT_SEL(3))
> +#define MAX96724_REG3_CC_PORT_SEL_PORT0 0x2
> +#define MAX96724_REG3_CC_PORT_SEL_PORT1 0x1
> +#define MAX96724_REG3_CC_PORT_CFG(sel) \
> + (FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(0), (sel)) | \
> + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(1), (sel)) | \
> + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(2), (sel)) | \
> + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(3), (sel)))
> +#define MAX96724_REG3_CC_PORT_CFG_PORT0 \
> + MAX96724_REG3_CC_PORT_CFG(MAX96724_REG3_CC_PORT_SEL_PORT0)
> +#define MAX96724_REG3_CC_PORT_CFG_PORT1 \
> + MAX96724_REG3_CC_PORT_CFG(MAX96724_REG3_CC_PORT_SEL_PORT1)
> +
> +#define MAX96724_REG6 0x6
> +#define MAX96724_REG6_LINK_EN GENMASK(3, 0)
> +
> +#define MAX96724_REG7 0x7
> +#define MAX96724_REG7_CC_CROSSOVER_SEL GENMASK(7, 4)
> +
> +#define MAX96724_DEBUG_EXTRA 0x9
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC GENMASK(1, 0)
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ 0b00
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_75MHZ 0b01
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE 0b10
> +
> +#define MAX96724_REG26(x) (0x10 + (x) / 2)
> +#define MAX96724_REG26_RX_RATE_PHY(x) (GENMASK(1, 0) << (4 * ((x) % 2)))
> +#define MAX96724_REG26_RX_RATE_3GBPS 0b01
> +#define MAX96724_REG26_RX_RATE_6GBPS 0b10
> +
> +#define MAX96724_PWR1 0x13
> +#define MAX96724_PWR1_RESET_ALL BIT(6)
> +
> +#define MAX96724_CTRL1 0x18
> +#define MAX96724_CTRL1_RESET_ONESHOT GENMASK(3, 0)
> +
> +#define MAX96724_VIDEO_PIPE_SEL(p) (0xf0 + (p) / 2)
> +#define MAX96724_VIDEO_PIPE_SEL_STREAM(p) (GENMASK(1, 0) << (4 * ((p) % 2)))
> +#define MAX96724_VIDEO_PIPE_SEL_LINK(p) (GENMASK(3, 2) << (4 * ((p) % 2)))
> +
> +#define MAX96724_VIDEO_PIPE_EN 0xf4
> +#define MAX96724_VIDEO_PIPE_EN_MASK(p) BIT(p)
> +#define MAX96724_VIDEO_PIPE_EN_STREAM_SEL_ALL BIT(4)
> +
> +#define MAX96724_VPRBS(p) (0x1dc + (p) * 0x20)
> +#define MAX96724_VPRBS_VIDEO_LOCK BIT(0)
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC BIT(7)
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC_150MHZ 0b0
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC_375MHZ 0b1
> +
> +#define MAX96724_BACKTOP12 0x40b
> +#define MAX96724_BACKTOP12_CSI_OUT_EN BIT(1)
> +
> +#define MAX96724_BACKTOP21(p) (0x414 + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP21_BPP8DBL(p) BIT(4 + (p) % 4)
> +
> +#define MAX96724_BACKTOP22(x) (0x415 + (x) * 0x3)
> +#define MAX96724_BACKTOP22_PHY_CSI_TX_DPLL GENMASK(4, 0)
> +#define MAX96724_BACKTOP22_PHY_CSI_TX_DPLL_EN BIT(5)
> +
> +#define MAX96724_BACKTOP24(p) (0x417 + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP24_BPP8DBL_MODE(p) BIT(4 + (p) % 4)
> +
> +#define MAX96724_BACKTOP30(p) (0x41d + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP30_BPP10DBL3 BIT(4)
> +#define MAX96724_BACKTOP30_BPP10DBL3_MODE BIT(5)
> +
> +#define MAX96724_BACKTOP31(p) (0x41e + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP31_BPP10DBL2 BIT(6)
> +#define MAX96724_BACKTOP31_BPP10DBL2_MODE BIT(7)
> +
> +#define MAX96724_BACKTOP32(p) (0x41f + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP32_BPP12(p) BIT((p) % 4)
> +#define MAX96724_BACKTOP32_BPP10DBL0 BIT(4)
> +#define MAX96724_BACKTOP32_BPP10DBL0_MODE BIT(5)
> +#define MAX96724_BACKTOP32_BPP10DBL1 BIT(6)
> +#define MAX96724_BACKTOP32_BPP10DBL1_MODE BIT(7)
> +
> +#define MAX96724_MIPI_PHY0 0x8a0
> +#define MAX96724_MIPI_PHY0_PHY_CONFIG GENMASK(4, 0)
> +#define MAX96724_MIPI_PHY0_PHY_4X2 BIT(0)
> +#define MAX96724_MIPI_PHY0_PHY_2X4 BIT(2)
> +#define MAX96724_MIPI_PHY0_PHY_1X4A_2X2 BIT(3)
> +#define MAX96724_MIPI_PHY0_PHY_1X4B_2X2 BIT(4)
> +#define MAX96724_MIPI_PHY0_FORCE_CSI_OUT_EN BIT(7)
> +
> +#define MAX96724_MIPI_PHY2 0x8a2
> +#define MAX96724_MIPI_PHY2_PHY_STDB_N_4(x) (GENMASK(5, 4) << ((x) / 2 * 2))
> +#define MAX96724_MIPI_PHY2_PHY_STDB_N_2(x) (BIT(4 + (x)))
> +
> +#define MAX96724_MIPI_PHY3(x) (0x8a3 + (x) / 2)
> +#define MAX96724_MIPI_PHY3_PHY_LANE_MAP_4 GENMASK(7, 0)
> +#define MAX96724_MIPI_PHY3_PHY_LANE_MAP_2(x) (GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_PHY5(x) (0x8a5 + (x) / 2)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1 GENMASK(1, 0)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3 GENMASK(4, 3)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_CLK BIT(5)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_2(x) (GENMASK(1, 0) << (3 * ((x) % 2)))
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_2_CLK(x) BIT(2 + 3 * ((x) % 2))
> +
> +#define MAX96724_MIPI_PHY13 0x8ad
> +#define MAX96724_MIPI_PHY13_T_T3_PREBEGIN GENMASK(5, 0)
> +#define MAX96724_MIPI_PHY13_T_T3_PREBEGIN_64X7 FIELD_PREP(MAX96724_MIPI_PHY13_T_T3_PREBEGIN, 63)
> +
> +#define MAX96724_MIPI_PHY14 0x8ae
> +#define MAX96724_MIPI_PHY14_T_T3_PREP GENMASK(1, 0)
> +#define MAX96724_MIPI_PHY14_T_T3_PREP_55NS FIELD_PREP(MAX96724_MIPI_PHY14_T_T3_PREP, 0b01)
> +#define MAX96724_MIPI_PHY14_T_T3_POST GENMASK(6, 2)
> +#define MAX96724_MIPI_PHY14_T_T3_POST_32X7 FIELD_PREP(MAX96724_MIPI_PHY14_T_T3_POST, 31)
> +
> +#define MAX96724_MIPI_CTRL_SEL 0x8ca
> +#define MAX96724_MIPI_CTRL_SEL_MASK(p) (GENMASK(1, 0) << ((p) * 2))
> +
> +#define MAX96724_MIPI_PHY25(x) (0x8d0 + (x) / 2)
> +#define MAX96724_MIPI_PHY25_CSI2_TX_PKT_CNT(x) (GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_PHY27(x) (0x8d2 + (x) / 2)
> +#define MAX96724_MIPI_PHY27_PHY_PKT_CNT(x) (GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_TX3(x) (0x903 + (x) * 0x40)
> +#define MAX96724_MIPI_TX3_DESKEW_INIT_8X32K FIELD_PREP(GENMASK(2, 0), 0b001)
> +#define MAX96724_MIPI_TX3_DESKEW_INIT_AUTO BIT(7)
> +
> +#define MAX96724_MIPI_TX4(x) (0x904 + (x) * 0x40)
> +#define MAX96724_MIPI_TX4_DESKEW_PER_2K FIELD_PREP(GENMASK(2, 0), 0b001)
> +#define MAX96724_MIPI_TX4_DESKEW_PER_AUTO BIT(7)
> +
> +#define MAX96724_MIPI_TX10(x) (0x90a + (x) * 0x40)
> +#define MAX96724_MIPI_TX10_CSI2_CPHY_EN BIT(5)
> +#define MAX96724_MIPI_TX10_CSI2_LANE_CNT GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX11(p) (0x90b + (p) * 0x40)
> +#define MAX96724_MIPI_TX12(p) (0x90c + (p) * 0x40)
> +
> +#define MAX96724_MIPI_TX13(p, x) (0x90d + (p) * 0x40 + (x) * 0x2)
> +#define MAX96724_MIPI_TX13_MAP_SRC_DT GENMASK(5, 0)
> +#define MAX96724_MIPI_TX13_MAP_SRC_VC GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX14(p, x) (0x90e + (p) * 0x40 + (x) * 0x2)
> +#define MAX96724_MIPI_TX14_MAP_DST_DT GENMASK(5, 0)
> +#define MAX96724_MIPI_TX14_MAP_DST_VC GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX45(p, x) (0x92d + (p) * 0x40 + (x) / 4)
> +#define MAX96724_MIPI_TX45_MAP_DPHY_DEST(x) (GENMASK(1, 0) << (2 * ((x) % 4)))
> +
> +#define MAX96724_MIPI_TX51(x) (0x933 + (x) * 0x40)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_12 BIT(0)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_8 BIT(1)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_10 BIT(2)
> +#define MAX96724_MIPI_TX51_ALT2_MEM_MAP_8 BIT(4)
> +
> +#define MAX96724_MIPI_TX54(x) (0x936 + (x) * 0x40)
> +#define MAX96724_MIPI_TX54_TUN_EN BIT(0)
> +
> +#define MAX96724_MIPI_TX57(x) (0x939 + (x) * 0x40)
> +#define MAX96724_MIPI_TX57_TUN_DEST GENMASK(5, 4)
> +#define MAX96724_MIPI_TX57_DIS_AUTO_TUN_DET BIT(6)
> +#define MAX96724_DET(p) BIT(p)
> +
> +#define MAX96724_PATGEN_0 0x1050
> +#define MAX96724_PATGEN_0_VTG_MODE GENMASK(1, 0)
> +#define MAX96724_PATGEN_0_VTG_MODE_FREE_RUNNING 0b11
> +#define MAX96724_PATGEN_0_DE_INV BIT(2)
> +#define MAX96724_PATGEN_0_HS_INV BIT(3)
> +#define MAX96724_PATGEN_0_VS_INV BIT(4)
> +#define MAX96724_PATGEN_0_GEN_DE BIT(5)
> +#define MAX96724_PATGEN_0_GEN_HS BIT(6)
> +#define MAX96724_PATGEN_0_GEN_VS BIT(7)
> +
> +#define MAX96724_PATGEN_1 0x1051
> +#define MAX96724_PATGEN_1_PATGEN_MODE GENMASK(5, 4)
> +#define MAX96724_PATGEN_1_PATGEN_MODE_DISABLED 0b00
> +#define MAX96724_PATGEN_1_PATGEN_MODE_CHECKER 0b01
> +#define MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT 0b10
> +
> +#define MAX96724_VS_DLY_2 0x1052
> +#define MAX96724_VS_HIGH_2 0x1055
> +#define MAX96724_VS_LOW_2 0x1058
> +#define MAX96724_V2H_2 0x105b
> +#define MAX96724_HS_HIGH_1 0x105e
> +#define MAX96724_HS_LOW_1 0x1060
> +#define MAX96724_HS_CNT_1 0x1062
> +#define MAX96724_V2D_2 0x1064
> +#define MAX96724_DE_HIGH_1 0x1067
> +#define MAX96724_DE_LOW_1 0x1069
> +#define MAX96724_DE_CNT_1 0x106b
> +#define MAX96724_GRAD_INCR 0x106d
> +#define MAX96724_CHKR_COLOR_A_L 0x106e
> +#define MAX96724_CHKR_COLOR_B_L 0x1071
> +#define MAX96724_CHKR_RPT_A 0x1074
> +#define MAX96724_CHKR_RPT_B 0x1075
> +#define MAX96724_CHKR_ALT 0x1076
> +
> +#define MAX96724_DE_DET 0x11f0
> +#define MAX96724_HS_DET 0x11f1
> +#define MAX96724_VS_DET 0x11f2
> +#define MAX96724_HS_POL 0x11f3
> +#define MAX96724_VS_POL 0x11f4
> +#define MAX96724_DET(p) BIT(p)
> +
> +#define MAX96724_DPLL_0(x) (0x1c00 + (x) * 0x100)
> +#define MAX96724_DPLL_0_CONFIG_SOFT_RST_N BIT(0)
> +
> +#define MAX96724_PHY1_ALT_CLOCK 5
> +
> +static const struct regmap_config max96724_i2c_regmap = {
> + .reg_bits = 16,
> + .val_bits = 8,
> + .max_register = 0x1f00,
> +};
> +
> +struct max96724_priv {
> + struct max_des des;
> + const struct max96724_chip_info *info;
> +
> + struct device *dev;
> + struct i2c_client *client;
> + struct regmap *regmap;
> +
> + struct gpio_desc *gpiod_enable;
> + unsigned int cc_port_cfg;
> +};
> +
> +struct max96724_chip_info {
> + unsigned int versions;
> + unsigned int modes;
> + bool supports_pipe_stream_autoselect;
> + unsigned int num_pipes;
> +
> + int (*set_pipe_phy)(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_phy *phy);
> + int (*set_pipe_tunnel_phy)(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_phy *phy);
> + int (*set_pipe_tunnel_enable)(struct max_des *des, struct max_des_pipe *pipe,
> + bool enable);
> +};
> +
> +#define des_to_priv(_des) \
> + container_of(_des, struct max96724_priv, des)
> +
> +static int max96724_wait_for_device(struct max96724_priv *priv)
> +{
> + unsigned int val;
> + int ret, err;
> +
> + err = read_poll_timeout(regmap_read, ret,
> + !ret && val,
> + 100 * USEC_PER_MSEC,
> + 1 * USEC_PER_SEC, false,
> + priv->regmap, MAX96724_REG0, &val);
> + if (err)
> + dev_err(priv->dev, "Timeout waiting for deserializer: %d\n", ret);
> +
> + return err;
> +}
> +
> +static int max96724_reset(struct max96724_priv *priv)
> +{
> + int ret;
> +
> + ret = max96724_wait_for_device(priv);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96724_PWR1,
> + MAX96724_PWR1_RESET_ALL,
> + FIELD_PREP(MAX96724_PWR1_RESET_ALL, 1));
> + if (ret)
> + return ret;
> +
> + fsleep(10000);
> +
> + ret = max96724_wait_for_device(priv);
> + if (ret)
> + return ret;
> +
> + /* Restore I2C control-channel access after a reset. */
> + return regmap_update_bits(priv->regmap, MAX96724_REG3,
> + MAX96724_REG3_CC_PORT_SEL_MASK,
> + priv->cc_port_cfg);
> +}
> +
> +static int __maybe_unused max96724_reg_read(struct max_des *des, unsigned int reg,
> + unsigned int *val)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_read(priv->regmap, reg, val);
> +}
> +
> +static int __maybe_unused max96724_reg_write(struct max_des *des, unsigned int reg,
> + unsigned int val)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_write(priv->regmap, reg, val);
> +}
> +
> +static unsigned int max96724_phy_id(struct max_des *des, struct max_des_phy *phy)
> +{
> + unsigned int num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> +
> + /* PHY 1 is the master PHY when combining PHY 0 and PHY 1. */
> + if (phy->index == 0 && num_hw_data_lanes == 4)
> + return 1;
> +
> + if (phy->index == 1 && !des->phys[1].enabled)
> + return 0;
> +
> + return phy->index;
> +}
> +
> +static int max96724_log_pipe_status(struct max_des *des,
> + struct max_des_pipe *pipe)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> + unsigned int val, mask;
> + int ret;
> +
> + ret = regmap_read(priv->regmap, MAX96724_VPRBS(index), &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tvideo_lock: %u\n",
> + !!(val & MAX96724_VPRBS_VIDEO_LOCK));
> +
> + mask = MAX96724_DET(index);
> +
> + ret = regmap_read(priv->regmap, MAX96724_DE_DET, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tde_det: %u\n", !!(val & mask));
> +
> + ret = regmap_read(priv->regmap, MAX96724_HS_DET, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\ths_det: %u\n", !!(val & mask));
> +
> + ret = regmap_read(priv->regmap, MAX96724_VS_DET, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tvs_det: %u\n", !!(val & mask));
> +
> + ret = regmap_read(priv->regmap, MAX96724_HS_POL, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\ths_pol: %u\n", !!(val & mask));
> +
> + ret = regmap_read(priv->regmap, MAX96724_VS_POL, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tvs_pol: %u\n", !!(val & mask));
> +
> + return 0;
> +}
> +
> +static int max96724_log_phy_status(struct max_des *des,
> + struct max_des_phy *phy)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = max96724_phy_id(des, phy);
> + unsigned int val;
> + int ret;
> +
> + ret = regmap_read(priv->regmap, MAX96724_MIPI_PHY25(index), &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tcsi2_pkt_cnt: %lu\n",
> + field_get(MAX96724_MIPI_PHY25_CSI2_TX_PKT_CNT(index), val));
> +
> + ret = regmap_read(priv->regmap, MAX96724_MIPI_PHY27(index), &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tphy_pkt_cnt: %lu\n",
> + field_get(MAX96724_MIPI_PHY27_PHY_PKT_CNT(index), val));
> +
> + return 0;
> +}
> +
> +static int max96724_set_enable(struct max_des *des, bool enable)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_BACKTOP12,
> + MAX96724_BACKTOP12_CSI_OUT_EN, enable);
> +}
> +
> +static const unsigned int max96724_phys_configs_reg_val[] = {
> + MAX96724_MIPI_PHY0_PHY_1X4A_2X2,
> + MAX96724_MIPI_PHY0_PHY_2X4,
> +
> + MAX96724_MIPI_PHY0_PHY_4X2,
> + MAX96724_MIPI_PHY0_PHY_1X4A_2X2,
> + MAX96724_MIPI_PHY0_PHY_1X4B_2X2,
> + MAX96724_MIPI_PHY0_PHY_2X4,
> +};
> +
> +static const struct max_serdes_phys_config max96724_phys_configs[] = {
> + /*
> + * PHY 1 can be in 4-lane mode (combining lanes of PHY 0 and PHY 1)
> + * but only use the data lanes of PHY0, while continuing to use the
> + * clock lane of PHY 1.
> + * Specifying clock-lanes as 5 turns on alternate clocking mode.
> + */
> + { { 2, 0, 2, 2 }, { MAX96724_PHY1_ALT_CLOCK, 0, 0, 0 } },
> + { { 2, 0, 4, 0 }, { MAX96724_PHY1_ALT_CLOCK, 0, 0, 0 } },
> +
> + /*
> + * When combining PHY 0 and PHY 1 to make them function in 4-lane mode,
> + * PHY 1 is the master PHY, but we use PHY 0 here to maintain
> + * compatibility.
> + */
> + { { 2, 2, 2, 2 } },
> + { { 4, 0, 2, 2 } },
> + { { 2, 2, 4, 0 } },
> + { { 4, 0, 4, 0 } },
> +};
> +
> +static int max96724_init_tpg(struct max_des *des)
> +{
> + const struct reg_sequence regs[] = {
> + { MAX96724_GRAD_INCR, MAX_SERDES_GRAD_INCR },
> + REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_A_L,
> + MAX_SERDES_CHECKER_COLOR_A),
> + REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_B_L,
> + MAX_SERDES_CHECKER_COLOR_B),
> + { MAX96724_CHKR_RPT_A, MAX_SERDES_CHECKER_SIZE },
> + { MAX96724_CHKR_RPT_B, MAX_SERDES_CHECKER_SIZE },
> + { MAX96724_CHKR_ALT, MAX_SERDES_CHECKER_SIZE },
> + };
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> +}
> +
> +static int max96724_init(struct max_des *des)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int i;
> + int ret;
> +
> + if (priv->info->set_pipe_tunnel_enable) {
> + for (i = 0; i < des->info->num_pipes; i++) {
> + ret = regmap_set_bits(priv->regmap, MAX96724_MIPI_TX57(i),
> + MAX96724_MIPI_TX57_DIS_AUTO_TUN_DET);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + if (priv->info->supports_pipe_stream_autoselect) {
> + /* Enable stream autoselect. */
> + ret = regmap_set_bits(priv->regmap, MAX96724_VIDEO_PIPE_EN,
> + MAX96724_VIDEO_PIPE_EN_STREAM_SEL_ALL);
> + if (ret)
> + return ret;
> + }
> +
> + /* Enable I2C control ports crossover. */
> + ret = regmap_set_bits(priv->regmap, MAX96724_REG7,
> + MAX96724_REG7_CC_CROSSOVER_SEL);
> + if (ret)
> + return ret;
> +
> + /* Set PHY mode. */
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY0,
> + MAX96724_MIPI_PHY0_PHY_CONFIG,
> + max96724_phys_configs_reg_val[des->phys_config]);
> + if (ret)
> + return ret;
> +
> + return max96724_init_tpg(des);
> +}
> +
> +static int max96724_init_phy(struct max_des *des, struct max_des_phy *phy)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + bool is_cphy = phy->bus_type == V4L2_MBUS_CSI2_CPHY;
> + unsigned int num_data_lanes = phy->mipi.num_data_lanes;
> + unsigned int num_hw_data_lanes;
> + unsigned int index;
> + unsigned int used_data_lanes = 0;
> + unsigned int val, mask;
> + unsigned int i;
> + u64 dpll_freq;
> + int ret;
> +
> + index = max96724_phy_id(des, phy);
> + num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> + dpll_freq = is_cphy ? phy->link_frequency
> + : phy->link_frequency * 2;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_TX10(index),
> + MAX96724_MIPI_TX10_CSI2_LANE_CNT,
> + FIELD_PREP(MAX96724_MIPI_TX10_CSI2_LANE_CNT,
> + num_data_lanes - 1));
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX10(index),
> + MAX96724_MIPI_TX10_CSI2_CPHY_EN, is_cphy);
> + if (ret)
> + return ret;
> +
> + /* Configure lane mapping. */
> + val = 0;
> + for (i = 0; i < num_hw_data_lanes ; i++) {
> + unsigned int map;
> +
> + if (i < num_data_lanes)
> + map = phy->mipi.data_lanes[i] - 1;
> + else
> + map = ffz(used_data_lanes);
> +
> + val |= map << (i * 2);
> + used_data_lanes |= BIT(map);
> + }
> +
> + if (num_hw_data_lanes == 4)
> + mask = MAX96724_MIPI_PHY3_PHY_LANE_MAP_4;
> + else
> + mask = MAX96724_MIPI_PHY3_PHY_LANE_MAP_2(index);
> +
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY3(index),
> + mask, field_prep(mask, val));
> + if (ret)
> + return ret;
> +
> + /* Configure lane polarity. */
> + for (i = 0, val = 0; i < num_data_lanes; i++)
> + if (phy->mipi.lane_polarities[i + 1])
> + val |= BIT(i);
> +
> + if (num_hw_data_lanes == 4) {
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1 |
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3,
> + FIELD_PREP(MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1,
> + val) |
> + FIELD_PREP(MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3,
> + val >> 2));
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_CLK,
> + phy->mipi.lane_polarities[0]);
> + if (ret)
> + return ret;
> + } else {
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_2(index),
> + field_prep(MAX96724_MIPI_PHY5_PHY_POL_MAP_2(index), val));
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_2_CLK(index),
> + phy->mipi.lane_polarities[0]);
> + if (ret)
> + return ret;
> + }
> +
> + if (!is_cphy && dpll_freq > 1500000000ull) {
> + /* Enable initial deskew with 2 x 32k UI. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX3(index),
> + MAX96724_MIPI_TX3_DESKEW_INIT_AUTO |
> + MAX96724_MIPI_TX3_DESKEW_INIT_8X32K);
> + if (ret)
> + return ret;
> +
> + /* Enable periodic deskew with 2 x 1k UI.. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX4(index),
> + MAX96724_MIPI_TX4_DESKEW_PER_AUTO |
> + MAX96724_MIPI_TX4_DESKEW_PER_2K);
> + if (ret)
> + return ret;
> + } else {
> + /* Disable initial deskew. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX3(index), 0x0);
> + if (ret)
> + return ret;
> +
> + /* Disable periodic deskew. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX4(index), 0x0);
> + if (ret)
> + return ret;
> + }
> +
> + if (is_cphy) {
> + /* Configure C-PHY timings. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_PHY13,
> + MAX96724_MIPI_PHY13_T_T3_PREBEGIN_64X7);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_PHY14,
> + MAX96724_MIPI_PHY14_T_T3_PREP_55NS |
> + MAX96724_MIPI_PHY14_T_T3_POST_32X7);
> + if (ret)
> + return ret;
> + }
> +
> + /* Put DPLL block into reset. */
> + ret = regmap_clear_bits(priv->regmap, MAX96724_DPLL_0(index),
> + MAX96724_DPLL_0_CONFIG_SOFT_RST_N);
> + if (ret)
> + return ret;
> +
> + /* Set DPLL frequency. */
> + ret = regmap_update_bits(priv->regmap, MAX96724_BACKTOP22(index),
> + MAX96724_BACKTOP22_PHY_CSI_TX_DPLL,
> + FIELD_PREP(MAX96724_BACKTOP22_PHY_CSI_TX_DPLL,
> + div_u64(dpll_freq, 100000000)));
> + if (ret)
> + return ret;
> +
> + /* Enable DPLL frequency. */
> + ret = regmap_set_bits(priv->regmap, MAX96724_BACKTOP22(index),
> + MAX96724_BACKTOP22_PHY_CSI_TX_DPLL_EN);
> + if (ret)
> + return ret;
> +
> + /* Pull DPLL block out of reset. */
> + return regmap_set_bits(priv->regmap, MAX96724_DPLL_0(index),
> + MAX96724_DPLL_0_CONFIG_SOFT_RST_N);
> +}
> +
> +static int max96724_set_phy_mode(struct max_des *des, struct max_des_phy *phy,
> + struct max_des_phy_mode *mode)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = max96724_phy_id(des, phy);
> + int ret;
> +
> + /* Set alternate memory map modes. */
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> + MAX96724_MIPI_TX51_ALT_MEM_MAP_12,
> + mode->alt_mem_map12);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> + MAX96724_MIPI_TX51_ALT_MEM_MAP_8,
> + mode->alt_mem_map8);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> + MAX96724_MIPI_TX51_ALT_MEM_MAP_10,
> + mode->alt_mem_map10);
> + if (ret)
> + return ret;
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> + MAX96724_MIPI_TX51_ALT2_MEM_MAP_8,
> + mode->alt2_mem_map8);
> +}
> +
> +static int max96724_set_phy_enable(struct max_des *des, struct max_des_phy *phy,
> + bool enable)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = max96724_phy_id(des, phy);
> + unsigned int num_hw_data_lanes;
> + unsigned int mask;
> +
> + num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> +
> + /*
> + * Some configurations merge two logical PHYs into one hardware PHY.
> + * Skip writes for absorbed PHYs to avoid clobbering the master's bits.
> + */
> + if (!num_hw_data_lanes)
> + return 0;
> +
> + if (num_hw_data_lanes == 4)
> + /* PHY 1 -> bits [5:4] */
> + /* PHY 2 -> bits [7:6] */
> + mask = MAX96724_MIPI_PHY2_PHY_STDB_N_4(index);
> + else
> + mask = MAX96724_MIPI_PHY2_PHY_STDB_N_2(index);
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY2, mask, enable);
> +}
> +
> +static int max96724_set_pipe_remap(struct max_des *des,
> + struct max_des_pipe *pipe,
> + unsigned int i,
> + struct max_des_remap *remap)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + struct max_des_phy *phy = &des->phys[remap->phy];
> + unsigned int phy_id = max96724_phy_id(des, phy);
> + unsigned int index = pipe->index;
> + int ret;
> +
> + /* Set source Data Type and Virtual Channel. */
> + /* TODO: implement extended Virtual Channel. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX13(index, i),
> + FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_DT,
> + remap->from_dt) |
> + FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_VC,
> + remap->from_vc));
> + if (ret)
> + return ret;
> +
> + /* Set destination Data Type and Virtual Channel. */
> + /* TODO: implement extended Virtual Channel. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX14(index, i),
> + FIELD_PREP(MAX96724_MIPI_TX14_MAP_DST_DT,
> + remap->to_dt) |
> + FIELD_PREP(MAX96724_MIPI_TX14_MAP_DST_VC,
> + remap->to_vc));
> + if (ret)
> + return ret;
> +
> + /* Set destination PHY. */
> + return regmap_update_bits(priv->regmap, MAX96724_MIPI_TX45(index, i),
> + MAX96724_MIPI_TX45_MAP_DPHY_DEST(i),
> + field_prep(MAX96724_MIPI_TX45_MAP_DPHY_DEST(i),
> + phy_id));
> +}
> +
> +static int max96724_set_pipe_remaps_enable(struct max_des *des,
> + struct max_des_pipe *pipe,
> + unsigned int mask)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> + int ret;
> +
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX11(index), mask);
> + if (ret)
> + return ret;
> +
> + return regmap_write(priv->regmap, MAX96724_MIPI_TX12(index), mask >> 8);
> +}
> +
> +static int max96724_set_pipe_tunnel_phy(struct max_des *des,
> + struct max_des_pipe *pipe,
> + struct max_des_phy *phy)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int phy_index = max96724_phy_id(des, phy);
> +
> + return regmap_update_bits(priv->regmap, MAX96724_MIPI_TX57(pipe->index),
> + MAX96724_MIPI_TX57_TUN_DEST,
> + FIELD_PREP(MAX96724_MIPI_TX57_TUN_DEST,
> + phy_index));
> +}
> +
> +static int max96724_set_pipe_phy(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_phy *phy)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int phy_index = max96724_phy_id(des, phy);
> +
> + return regmap_update_bits(priv->regmap, MAX96724_MIPI_CTRL_SEL,
> + MAX96724_MIPI_CTRL_SEL_MASK(pipe->index),
> + field_prep(MAX96724_MIPI_CTRL_SEL_MASK(pipe->index),
> + phy_index));
> +}
> +
> +static int max96724_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe,
> + bool enable)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_VIDEO_PIPE_EN,
> + MAX96724_VIDEO_PIPE_EN_MASK(index), enable);
> +}
> +
> +static int max96724_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe,
> + unsigned int stream_id)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> +
> + return regmap_update_bits(priv->regmap, MAX96724_VIDEO_PIPE_SEL(index),
> + MAX96724_VIDEO_PIPE_SEL_STREAM(index),
> + field_prep(MAX96724_VIDEO_PIPE_SEL_STREAM(index),
> + stream_id));
> +}
> +
> +static int max96724_set_pipe_link(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_link *link)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> +
> + return regmap_update_bits(priv->regmap, MAX96724_VIDEO_PIPE_SEL(index),
> + MAX96724_VIDEO_PIPE_SEL_LINK(index),
> + field_prep(MAX96724_VIDEO_PIPE_SEL_LINK(index),
> + link->index));
> +}
> +
> +static int max96724_set_pipe_mode(struct max_des *des,
> + struct max_des_pipe *pipe,
> + struct max_des_pipe_mode *mode)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> + unsigned int reg, mask, mode_mask;
> + int ret;
> +
> + /* Set 8bit double mode. */
> + ret = regmap_assign_bits(priv->regmap, MAX96724_BACKTOP21(index),
> + MAX96724_BACKTOP21_BPP8DBL(index), mode->dbl8);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_BACKTOP24(index),
> + MAX96724_BACKTOP24_BPP8DBL_MODE(index),
> + mode->dbl8mode);
> + if (ret)
> + return ret;
> +
> + /* Set 10bit double mode. */
> + if (index % 4 == 3) {
> + reg = MAX96724_BACKTOP30(index);
> + mask = MAX96724_BACKTOP30_BPP10DBL3;
> + mode_mask = MAX96724_BACKTOP30_BPP10DBL3_MODE;
> + } else if (index % 4 == 2) {
> + reg = MAX96724_BACKTOP31(index);
> + mask = MAX96724_BACKTOP31_BPP10DBL2;
> + mode_mask = MAX96724_BACKTOP31_BPP10DBL2_MODE;
> + } else if (index % 4 == 1) {
> + reg = MAX96724_BACKTOP32(index);
> + mask = MAX96724_BACKTOP32_BPP10DBL1;
> + mode_mask = MAX96724_BACKTOP32_BPP10DBL1_MODE;
> + } else {
> + reg = MAX96724_BACKTOP32(index);
> + mask = MAX96724_BACKTOP32_BPP10DBL0;
> + mode_mask = MAX96724_BACKTOP32_BPP10DBL0_MODE;
> + }
> +
> + ret = regmap_assign_bits(priv->regmap, reg, mask, mode->dbl10);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, reg, mode_mask, mode->dbl10mode);
> + if (ret)
> + return ret;
> +
> + /* Set 12bit double mode. */
> + return regmap_assign_bits(priv->regmap, MAX96724_BACKTOP32(index),
> + MAX96724_BACKTOP32_BPP12(index), mode->dbl12);
> +}
> +
> +static int max96724_set_pipe_tunnel_enable(struct max_des *des,
> + struct max_des_pipe *pipe, bool enable)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX54(pipe->index),
> + MAX96724_MIPI_TX54_TUN_EN, enable);
> +}
> +
> +static int max96724_select_links(struct max_des *des, unsigned int mask)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int val = priv->cc_port_cfg;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + if (!(mask & BIT(i)))
> + val |= MAX96724_REG3_CC_PORT_SEL(i);
> + }
> +
> + ret = regmap_write(priv->regmap, MAX96724_REG3, val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96724_REG6, MAX96724_REG6_LINK_EN,
> + field_prep(MAX96724_REG6_LINK_EN, mask));
> + if (ret)
> + return ret;
> +
> + ret = regmap_set_bits(priv->regmap, MAX96724_CTRL1,
> + MAX96724_CTRL1_RESET_ONESHOT);
> + if (ret)
> + return ret;
> +
> + msleep(60);
> +
> + return 0;
> +}
> +
> +static int max96724_set_link_version(struct max_des *des,
> + struct max_des_link *link,
> + enum max_serdes_gmsl_version version)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = link->index;
> + unsigned int val;
> +
> + if (version == MAX_SERDES_GMSL_2_6GBPS)
> + val = MAX96724_REG26_RX_RATE_6GBPS;
> + else
> + val = MAX96724_REG26_RX_RATE_3GBPS;
> +
> + return regmap_update_bits(priv->regmap, MAX96724_REG26(index),
> + MAX96724_REG26_RX_RATE_PHY(index),
> + field_prep(MAX96724_REG26_RX_RATE_PHY(index), val));
> +}
> +
> +static int max96724_set_tpg_timings(struct max96724_priv *priv,
> + const struct max_serdes_tpg_timings *tm)
> +{
> + const struct reg_sequence regs[] = {
> + REG_SEQUENCE_3(MAX96724_VS_DLY_2, tm->vs_dly),
> + REG_SEQUENCE_3(MAX96724_VS_HIGH_2, tm->vs_high),
> + REG_SEQUENCE_3(MAX96724_VS_LOW_2, tm->vs_low),
> + REG_SEQUENCE_3(MAX96724_V2H_2, tm->v2h),
> + REG_SEQUENCE_2(MAX96724_HS_HIGH_1, tm->hs_high),
> + REG_SEQUENCE_2(MAX96724_HS_LOW_1, tm->hs_low),
> + REG_SEQUENCE_2(MAX96724_HS_CNT_1, tm->hs_cnt),
> + REG_SEQUENCE_3(MAX96724_V2D_2, tm->v2d),
> + REG_SEQUENCE_2(MAX96724_DE_HIGH_1, tm->de_high),
> + REG_SEQUENCE_2(MAX96724_DE_LOW_1, tm->de_low),
> + REG_SEQUENCE_2(MAX96724_DE_CNT_1, tm->de_cnt),
> + };
> + int ret;
> +
> + ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> + if (ret)
> + return ret;
> +
> + return regmap_write(priv->regmap, MAX96724_PATGEN_0,
> + FIELD_PREP(MAX96724_PATGEN_0_VTG_MODE,
> + MAX96724_PATGEN_0_VTG_MODE_FREE_RUNNING) |
> + FIELD_PREP(MAX96724_PATGEN_0_DE_INV, tm->de_inv) |
> + FIELD_PREP(MAX96724_PATGEN_0_HS_INV, tm->hs_inv) |
> + FIELD_PREP(MAX96724_PATGEN_0_VS_INV, tm->vs_inv) |
> + FIELD_PREP(MAX96724_PATGEN_0_GEN_DE, tm->gen_de) |
> + FIELD_PREP(MAX96724_PATGEN_0_GEN_HS, tm->gen_hs) |
> + FIELD_PREP(MAX96724_PATGEN_0_GEN_VS, tm->gen_vs));
> +}
> +
> +static int max96724_set_tpg_clk(struct max96724_priv *priv, u32 clock)
> +{
> + bool patgen_clk_src = 0;
> + u8 pclk_src;
> + int ret;
> +
> + switch (clock) {
> + case 25000000:
> + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ;
> + break;
> + case 75000000:
> + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_75MHZ;
> + break;
> + case 150000000:
> + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE;
> + patgen_clk_src = MAX96724_VPRBS_PATGEN_CLK_SRC_150MHZ;
> + break;
> + case 375000000:
> + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE;
> + patgen_clk_src = MAX96724_VPRBS_PATGEN_CLK_SRC_375MHZ;
> + break;
> + case 0:
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +
> + /*
> + * TPG data is always injected on link 0, which is always routed to
> + * pipe 0.
> + */
> + ret = regmap_update_bits(priv->regmap, MAX96724_VPRBS(0),
> + MAX96724_VPRBS_PATGEN_CLK_SRC,
> + FIELD_PREP(MAX96724_VPRBS_PATGEN_CLK_SRC,
> + patgen_clk_src));
> + if (ret)
> + return ret;
> +
> + return regmap_update_bits(priv->regmap, MAX96724_DEBUG_EXTRA,
> + MAX96724_DEBUG_EXTRA_PCLK_SRC,
> + FIELD_PREP(MAX96724_DEBUG_EXTRA_PCLK_SRC,
> + pclk_src));
> +}
> +
> +static int max96724_set_tpg_mode(struct max96724_priv *priv, bool enable)
> +{
> + unsigned int patgen_mode;
> +
> + switch (priv->des.tpg_pattern) {
> + case MAX_SERDES_TPG_PATTERN_GRADIENT:
> + patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT;
> + break;
> + case MAX_SERDES_TPG_PATTERN_CHECKERBOARD:
> + patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_CHECKER;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return regmap_update_bits(priv->regmap, MAX96724_PATGEN_1,
> + MAX96724_PATGEN_1_PATGEN_MODE,
> + FIELD_PREP(MAX96724_PATGEN_1_PATGEN_MODE,
> + enable ? patgen_mode
> + : MAX96724_PATGEN_1_PATGEN_MODE_DISABLED));
> +}
> +
> +static int max96724_set_tpg(struct max_des *des,
> + const struct max_serdes_tpg_entry *entry)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + struct max_serdes_tpg_timings timings = { 0 };
> + int ret;
> +
> + ret = max_serdes_get_tpg_timings(entry, &timings);
> + if (ret)
> + return ret;
> +
> + ret = max96724_set_tpg_timings(priv, &timings);
> + if (ret)
> + return ret;
> +
> + ret = max96724_set_tpg_clk(priv, timings.clock);
> + if (ret)
> + return ret;
> +
> + ret = max96724_set_tpg_mode(priv, entry);
> + if (ret)
> + return ret;
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY0,
> + MAX96724_MIPI_PHY0_FORCE_CSI_OUT_EN, !!entry);
> +}
> +
> +static const struct max_serdes_tpg_entry max96724_tpg_entries[] = {
> + MAX_TPG_ENTRY_640X480P60_RGB888,
> + MAX_TPG_ENTRY_1920X1080P30_RGB888,
> + MAX_TPG_ENTRY_1920X1080P60_RGB888,
> +};
> +
> +static const struct max_des_info max96724_des_info = {
> + .num_phys = 4,
> + .num_links = 4,
> + .num_remaps_per_pipe = 16,
> + .phys_configs = {
> + .num_configs = ARRAY_SIZE(max96724_phys_configs),
> + .configs = max96724_phys_configs,
> + },
> + .tpg_entries = {
> + .num_entries = ARRAY_SIZE(max96724_tpg_entries),
> + .entries = max96724_tpg_entries,
> + },
> + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE,
> + .tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) |
> + BIT(MAX_SERDES_TPG_PATTERN_GRADIENT),
> + .use_atr = true,
> +};
> +
> +static const struct max_des_ops max96724_des_ops = {
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + .reg_read = max96724_reg_read,
> + .reg_write = max96724_reg_write,
> +#endif
> + .log_pipe_status = max96724_log_pipe_status,
> + .log_phy_status = max96724_log_phy_status,
> + .set_enable = max96724_set_enable,
> + .init = max96724_init,
> + .init_phy = max96724_init_phy,
> + .set_phy_mode = max96724_set_phy_mode,
> + .set_phy_enable = max96724_set_phy_enable,
> + .set_pipe_stream_id = max96724_set_pipe_stream_id,
> + .set_pipe_link = max96724_set_pipe_link,
> + .set_pipe_enable = max96724_set_pipe_enable,
> + .set_pipe_remap = max96724_set_pipe_remap,
> + .set_pipe_remaps_enable = max96724_set_pipe_remaps_enable,
> + .set_pipe_mode = max96724_set_pipe_mode,
> + .set_tpg = max96724_set_tpg,
> + .select_links = max96724_select_links,
> + .set_link_version = max96724_set_link_version,
> +};
> +
> +static const struct max96724_chip_info max96724_info = {
> + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
> + BIT(MAX_SERDES_GMSL_2_6GBPS),
> + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
> + BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
> + .set_pipe_tunnel_enable = max96724_set_pipe_tunnel_enable,
> + .set_pipe_phy = max96724_set_pipe_phy,
> + .set_pipe_tunnel_phy = max96724_set_pipe_tunnel_phy,
> + .supports_pipe_stream_autoselect = true,
> + .num_pipes = 4,
> +};
> +
> +static const struct max96724_chip_info max96724f_info = {
> + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS),
> + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
> + BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
> + .set_pipe_tunnel_enable = max96724_set_pipe_tunnel_enable,
> + .set_pipe_phy = max96724_set_pipe_phy,
> + .set_pipe_tunnel_phy = max96724_set_pipe_tunnel_phy,
> + .supports_pipe_stream_autoselect = true,
> + .num_pipes = 4,
> +};
> +
> +static const struct max96724_chip_info max96712_info = {
> + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
> + BIT(MAX_SERDES_GMSL_2_6GBPS),
> + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE),
> + .num_pipes = 8,
> +};
> +
> +static void max96724_power_off(void *data)
> +{
> + struct max96724_priv *priv = data;
> +
> + gpiod_set_value_cansleep(priv->gpiod_enable, 0);
> +}
> +
> +static int max96724_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct max96724_priv *priv;
> + struct max_des_info *info;
> + struct max_des_ops *ops;
> + u32 cc_port;
> + int ret;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL);
> + if (!ops)
> + return -ENOMEM;
> +
> + priv->info = device_get_match_data(dev);
> + if (!priv->info) {
> + dev_err(dev, "Failed to get match data\n");
> + return -ENODEV;
> + }
> +
> + priv->dev = dev;
> + priv->client = client;
> + i2c_set_clientdata(client, priv);
> +
> + priv->regmap = devm_regmap_init_i2c(client, &max96724_i2c_regmap);
> + if (IS_ERR(priv->regmap))
> + return PTR_ERR(priv->regmap);
> +
> + priv->gpiod_enable = devm_gpiod_get_optional(&client->dev, "enable",
> + GPIOD_OUT_LOW);
> + if (IS_ERR(priv->gpiod_enable))
> + return PTR_ERR(priv->gpiod_enable);
> +
> + if (priv->gpiod_enable) {
> + /* PWDN must be held for 1us for reset */
> + udelay(1);
> +
> + gpiod_set_value_cansleep(priv->gpiod_enable, 1);
> +
> + /* Maximum power-up time (tLOCK) 4ms */
> + usleep_range(4000, 5000);
> +
> + ret = devm_add_action_or_reset(dev, max96724_power_off,
> + priv);
> + if (ret)
> + return ret;
> + }
> +
> + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT0;
> +
> + ret = device_property_read_u32(dev, "maxim,control-channel-port",
> + &cc_port);
> + if (!ret) {
> + switch (cc_port) {
> + case 0:
> + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT0;
> + break;
> + case 1:
> + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT1;
> + break;
> + default:
> + dev_err(dev, "Invalid control-channel port %u\n", cc_port);
> + return -EINVAL;
> + }
> + } else if (ret != -ENODATA && ret != -ENOENT && ret != -EINVAL) {
> + return ret;
> + }
> +
> + *info = max96724_des_info;
> + info->versions = priv->info->versions;
> + info->modes = priv->info->modes;
> + info->num_pipes = priv->info->num_pipes;
> + priv->des.info = info;
> +
> + *ops = max96724_des_ops;
> + ops->set_pipe_tunnel_enable = priv->info->set_pipe_tunnel_enable;
> + ops->set_pipe_phy = priv->info->set_pipe_phy;
> + ops->set_pipe_tunnel_phy = priv->info->set_pipe_tunnel_phy;
> + priv->des.ops = ops;
> +
> + ret = max96724_reset(priv);
> + if (ret)
> + return ret;
> +
> + return max_des_probe(client, &priv->des);
> +}
> +
> +static void max96724_remove(struct i2c_client *client)
> +{
> + struct max96724_priv *priv = i2c_get_clientdata(client);
> +
> + max_des_remove(&priv->des);
> +}
> +
> +static const struct of_device_id max96724_of_table[] = {
> + { .compatible = "maxim,max96712", .data = &max96712_info },
> + { .compatible = "maxim,max96724", .data = &max96724_info },
> + { .compatible = "maxim,max96724f", .data = &max96724f_info },
> + { .compatible = "maxim,max96724r", .data = &max96724f_info },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, max96724_of_table);
> +
> +static struct i2c_driver max96724_i2c_driver = {
> + .driver = {
> + .name = "max96724",
> + .of_match_table = max96724_of_table,
> + },
> + .probe = max96724_probe,
> + .remove = max96724_remove,
> +};
> +
> +module_i2c_driver(max96724_i2c_driver);
> +
> +MODULE_IMPORT_NS("MAX_SERDES");
> +MODULE_DESCRIPTION("Maxim MAX96724 Quad GMSL2 Deserializer Driver");
> +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
> +MODULE_LICENSE("GPL");
>
> --
> 2.53.0
>
>
--
Kind Regards,
Niklas Söderlund
^ permalink raw reply
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