* [PATCH v6 0/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver
@ 2026-05-21 12:20 Bryan O'Donoghue
2026-05-21 12:20 ` [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema Bryan O'Donoghue
2026-05-21 12:20 ` [PATCH v6 2/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Bryan O'Donoghue
0 siblings, 2 replies; 8+ messages in thread
From: Bryan O'Donoghue @ 2026-05-21 12:20 UTC (permalink / raw)
To: Vinod Koul, Kishon Vijay Abraham I, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong
Cc: Bryan O'Donoghue, Vladimir Zapolskiy, linux-arm-msm,
linux-phy, linux-media, devicetree, linux-kernel,
Bryan O'Donoghue
Changes in v6:
- Taking feedback from lively debate added ports and
endpoints to the PHY - Neil, Vlad
- Detection of split mode by way of which ports are declared.
port@0 is always a sensor input.
port@1 is optional and if present implies split-mode
port@2 is always the output. - Dmitry, Neil, Vlad.
- Split mode is left as -ENOTSUPP unless/until someone with the appropriate
hardware can take on responsibility to drive to completion.
- Extending phy_config_opts dropped.
I think this is a worthwhile extension but this series no longer depends
on it so dropped. - Bryan
- MX/MXC.
Two OPP tables one for CSIPHY0/1/2 scaling MXC one for CSIPHY4 keeping
MXA at LOWSVS_D1 - to be implemented in DT not here. Taniya, Konrad, Bryan
- Changed MAINTAINERS from Supported to Maintained.
Hobby time for me right now. - Bryan
- Link to v5: https://lore.kernel.org/r/20260326-x1e-csi2-phy-v5-0-0c0fc7f5c01b@linaro.org
v5:
- Adds support to apply passed parameters for clock/data position/polarity - Neil
- Drops GEN1/GEN2 differentiation this can be reconstituted if GEN1 ever
gets supported in this driver - Dmitry
- Drops camnoc_axi, cpas_ahb - Konrad
- Renames csiphy->core csiphy_timer->timer - Konrad
- Renames rail from 0p8 to 0p9 schematics say VDD_A_CSI_n_0P9 - Konrad
- TITAN_TOP_GDSC dropped - Konrad
- Passes PHY_QCOM_CSI2_MODE_{DPHY|CPHY|SPLIT_DPHY} with the controller
selecting the mode. Only DPHY mode is supported but the method to pass
CPHY or split-mode DPHY configuration is there.
Since split-mode is a Qualcomm specific mode the PHY modes are defined in
our binding instead of adding a new type to include/linux/phy/phy.h - bod
- Depends-on: https://lore.kernel.org/r/20260325-dphy-params-extension-v1-0-c6df5599284a@linaro.org
- Link to v4: https://lore.kernel.org/r/20260315-x1e-csi2-phy-v4-0-90c09203888d@linaro.org
v4:
- MMCX, MCX and MX/MXA power-domains added - Dmitry, Vijay, Konrad
- power-domain-names added as required - bod
- opp-tables amended to capture RPMHPD deps - Dmitry, Vijay
- Switched to dev_pm_opp_set_rate, dev_pm_domain_attach_by_name etc
dropped inherited CAMSS code - Dmitry
- Amended parameters structure to specify power-domain name list - bod
- Removed dead defines - Dmitry
- Noted in CSIPHY commit log intention to rework patterns of
PHY lane configs into loops/defines/bit-fields later - Dmitry, bod
- Lowercase hex throughout - Dmitry
- The yaml and code in this driver doesn't care if the node is a
sibling or a sub-node of CAMSS confirmed to work both ways - Dmitry, bod
- Link to v3: https://lore.kernel.org/r/20260226-x1e-csi2-phy-v3-0-11e608759410@linaro.org
v3:
- Resending this to make clear this submission is additive to x1e/Hamoa
The existing bindings and code will continue to work
Bindings are added only, nothing is subtracted from existing ABI.
- Link to v2: https://lore.kernel.org/r/20260225-x1e-csi2-phy-v2-0-7756edb67ea9@linaro.org
v2:
In this updated version
- Added operating-point support
The csiphy clock sets the OPP prior to setting the rate
for csiphy and csiphy_timer - Konrad
- Combo mode
Combo mode in CAMSS yaml has been added. Right now
no code has been changed in the PHY driver to support it as
I don't have hardware to test. In principle though it can
be supported. - Vladimir
- CSIPHY init sequences
I left these as their "magic number formats". With my diminished
status as a non-qcom VPN person - I can no longer see what the bits
map to. Moreover this is the situation any non-VPN community member
will be in when submitting CSIPHY sequences derived from downstream.
I think it is perfectly reasonable to take public CSIPHY init sequences
as magic numbers. If someone with bit-level access wants to enumerate
the bits that's fine but, it shouldn't gate in the interim. - Konrad/bod
- Sensor endpoints
I've stuck to the format used by every other CSIPHY in upstream.
Sensor endpoints hit the CAMSS/CSID endpoint not a endpoint in the PHY.
Given the proposed changes to CAMSS though to support "combo mode" I
think this should achieve the same outcome - multiple sensors on the one
PHY without introducing endpoints into the PHY that no other CSIPHY in
upstream currently has.
- Bitmask of enabled lanes
Work needs to be done in the v4l2 layer to really support this.
I propose making a separate series dedicated to non-linear bit
interpretation after merging this so as to contain the scope of the
series to something more bite (byte haha) sized. - Konrad/bod
- Link to v1: https://lore.kernel.org/r/20250710-x1e-csi2-phy-v1-0-74acbb5b162b@linaro.org
v1:
This short series adds a CSI2 MIPI PHY driver, initially supporting D-PHY
mode. The core logic and init sequences come directly from CAMSS and are
working on at least five separate x1e devices.
The rationale to instantiate CSI2 PHYs as standalone devices instead of as
sub-nodes of CAMSS is as follows.
1. Precedence
CAMSS has a dedicated I2C bus called CCI Camera Control Interface.
We model this controller as its own separate device in devicetree.
This makes sense and CCI/I2C is a well defined bus type already modelled
in Linux.
MIPI CSI2 PHY devices similarly fit into a well defined separate
bus/device structure.
Contrast to another CAMSS component such as VFE, CSID or TPG these
components only interact with other CAMSS inputs/outputs unlike CSIPHY
which interacts with non-SoC components.
2. Hardware pinouts and rails
The CSI2 PHY has its own data/clock lanes out from the SoC and indeed
has its own incoming power-rails.
3. Other devicetree schemas
There are several examples throughout the kernel of CSI PHYs modeled as
standalone devices which one assumes follows the same reasoning as given
above.
I've been working on this on-and-off since the end of April:
Link: https://lore.kernel.org/linux-media/c5cf0155-f839-4db9-b865-d39b56bb1e0a@linaro.org
There is another proposal to have the PHYs be subdevices of CAMSS but, I
believe we should go with a "full fat" PHY to match best practices in
drivers/phy/qualcomm/*.
Using the standard PHY API and the parameter passing that goes with it
allows us to move away from custom interfaces in CAMSS and to conform more
clearly to established PHY paradigms such as the QMP combo PHY.
Looking at existing compat strings I settled on
"qcom,x1e80100-mipi-csi2-combo-phy" deliberately omitting reference to the
fact the PHY is built on a four nano-meter process node, which seems to
match recent submissions to QMP PHY.
My first pass at this driver included support for the old two phase
devices:
Link: https://git.codelinaro.org/bryan.odonoghue/kernel/-/commit/a504c28d109296c93470340cfe7281231f573bcb#b6e59ed7db94c9da22e492bb03fcda6a4300983c
I realised that the device tree schema changes required to support a
comprehensive conversion of all CAMSS to this driver would be an
almost certainly be unacceptable ABI break or at the very least an enormous
amount of work and verification so I instead aimed to support just one new
SoC in the submission.
I've retained the callback indirections give us scope to add in another type of
future PHY including potentially adding in the 2PH later on.
This driver is tested and working on x1e/Hamoa and has been tested as not
breaking sc8280xp/Makena and sm8250/Kona.
Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
---
Bryan O'Donoghue (2):
dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema
phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver
.../bindings/phy/qcom,x1e80100-csi2-phy.yaml | 205 ++++++++++++
MAINTAINERS | 10 +
drivers/phy/qualcomm/Kconfig | 13 +
drivers/phy/qualcomm/Makefile | 5 +
drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c | 361 ++++++++++++++++++++
drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c | 368 +++++++++++++++++++++
drivers/phy/qualcomm/phy-qcom-mipi-csi2.h | 94 ++++++
7 files changed, 1056 insertions(+)
---
base-commit: a1db83cc6f7e88a166c77d9060507ec01d617784
change-id: 20250710-x1e-csi2-phy-f6434b651d3a
Best regards,
--
Bryan O'Donoghue <bryan.odonoghue@linaro.org>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema 2026-05-21 12:20 [PATCH v6 0/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Bryan O'Donoghue @ 2026-05-21 12:20 ` Bryan O'Donoghue 2026-05-21 12:26 ` sashiko-bot 2026-05-21 15:15 ` Rob Herring (Arm) 2026-05-21 12:20 ` [PATCH v6 2/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Bryan O'Donoghue 1 sibling, 2 replies; 8+ messages in thread From: Bryan O'Donoghue @ 2026-05-21 12:20 UTC (permalink / raw) To: Vinod Koul, Kishon Vijay Abraham I, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong Cc: Bryan O'Donoghue, Vladimir Zapolskiy, linux-arm-msm, linux-phy, linux-media, devicetree, linux-kernel, Bryan O'Donoghue Add a base schema initially compatible with x1e80100 to describe MIPI CSI2 PHY devices. The hardware can support both CPHY, DPHY and a special split-mode DPHY. The schema here defines three ports: port@0: The first input port where a sensor is always required. port@1: A second optional input port which if present implies DPHY split-mode. port@2: A third always required output port which connects to the controller. The CSIPHY devices have their own pinouts on the SoC as well as their own individual voltage rails. The need to model voltage rails on a per-PHY basis leads us to define CSIPHY devices as individual nodes. Two nice outcomes in terms of schema and DT arise from this change. 1. The ability to define on a per-PHY basis voltage rails. 2. The ability to require those voltage. We have had a complete bodge upstream for this where a single set of voltage rail for all CSIPHYs has been buried inside of CAMSS. Much like the I2C bus which is dedicated to Camera sensors - the CCI bus in CAMSS parlance, the CSIPHY devices should be individually modelled. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> --- .../bindings/phy/qcom,x1e80100-csi2-phy.yaml | 205 +++++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml new file mode 100644 index 0000000000000..c9116246c1e9e --- /dev/null +++ b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml @@ -0,0 +1,205 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/qcom,x1e80100-csi2-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm CSI2 PHY + +maintainers: + - Bryan O'Donoghue <bod@kernel.org> + +description: + Qualcomm MIPI CSI2 C-PHY/D-PHY combination PHY. Connects MIPI CSI2 sensors + to Qualcomm's Camera CSI Decoder. The PHY supports both C-PHY and D-PHY + modes. + +properties: + compatible: + const: qcom,x1e80100-csi2-phy + + reg: + maxItems: 1 + + "#phy-cells": + const: 1 + description: + The single cell specifies the PHY operating mode. + + clocks: + maxItems: 2 + + clock-names: + items: + - const: core + - const: timer + + interrupts: + maxItems: 1 + + operating-points-v2: + maxItems: 1 + + power-domains: + items: + - description: MMCX voltage rail + - description: MXC or MXA voltage rail + + power-domain-names: + items: + - const: mmcx + - const: mx + + vdda-0p9-supply: + description: Phandle to a 0.9V regulator supply to a PHY. + + vdda-1p2-supply: + description: Phandle to 1.2V regulator supply to a PHY. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + description: Sensor input. Always present. + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + properties: + data-lanes: + minItems: 1 + maxItems: 4 + clock-lanes: + maxItems: 1 + remote-endpoint: true + required: + - data-lanes + - clock-lanes + - remote-endpoint + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + description: + Second sensor input. When present, indicates DPHY split mode. + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + properties: + data-lanes: + maxItems: 1 + clock-lanes: + maxItems: 1 + remote-endpoint: true + required: + - data-lanes + - clock-lanes + - remote-endpoint + + port@2: + $ref: /schemas/graph.yaml#/$defs/port-base + description: Output to CAMSS controller. + + properties: + endpoint: + $ref: /schemas/graph.yaml#/$defs/endpoint-base + unevaluatedProperties: false + properties: + remote-endpoint: true + required: + - remote-endpoint + + required: + - port@0 + - port@2 + +required: + - compatible + - reg + - "#phy-cells" + - clocks + - clock-names + - interrupts + - operating-points-v2 + - power-domains + - power-domain-names + - vdda-0p9-supply + - vdda-1p2-supply + - ports + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/clock/qcom,x1e80100-camcc.h> + #include <dt-bindings/clock/qcom,x1e80100-gcc.h> + #include <dt-bindings/power/qcom,rpmhpd.h> + + csiphy4: csiphy@ace4000 { + compatible = "qcom,x1e80100-csi2-phy"; + reg = <0x0ace4000 0x2000>; + #phy-cells = <1>; + + clocks = <&camcc CAM_CC_CSIPHY0_CLK>, + <&camcc CAM_CC_CSI0PHYTIMER_CLK>; + clock-names = "core", + "timer"; + + operating-points-v2 = <&csiphy_opp_table>; + + interrupts = <GIC_SPI 477 IRQ_TYPE_EDGE_RISING>; + + power-domains = <&rpmhpd RPMHPD_MMCX>, + <&rpmhpd RPMHPD_MX>; + power-domain-names = "mmcx", + "mx"; + + vdda-0p9-supply = <&vreg_l2c_0p8>; + vdda-1p2-supply = <&vreg_l1c_1p2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + csiphy0_in_ep: endpoint { + remote-endpoint = <&sensor_out>; + }; + }; + + port@2 { + reg = <2>; + csiphy0_out_ep: endpoint { + remote-endpoint = <&controller_in>; + }; + }; + }; + }; + + csiphy_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&rpmhpd_opp_low_svs_d1>, + <&rpmhpd_opp_low_svs_d1>; + }; + + opp-400000000 { + opp-hz = /bits/ 64 <400000000>; + required-opps = <&rpmhpd_opp_low_svs>, + <&rpmhpd_opp_low_svs_d1>; + }; + + opp-480000000 { + opp-hz = /bits/ 64 <480000000>; + required-opps = <&rpmhpd_opp_low_svs>, + <&rpmhpd_opp_low_svs_d1>; + }; + }; -- 2.54.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema 2026-05-21 12:20 ` [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema Bryan O'Donoghue @ 2026-05-21 12:26 ` sashiko-bot 2026-05-21 15:36 ` Bryan O'Donoghue 2026-05-21 15:15 ` Rob Herring (Arm) 1 sibling, 1 reply; 8+ messages in thread From: sashiko-bot @ 2026-05-21 12:26 UTC (permalink / raw) To: Bryan O'Donoghue Cc: vkoul, neil.armstrong, devicetree, conor+dt, olteanv, linux-phy, robh Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] The schema unconditionally requires `clock-lanes` for `port@0`, which incorrectly breaks C-PHY configurations. - [Low] The example device tree node is missing required `data-lanes` and `clock-lanes` properties in the `port@0` endpoint. -- commit bec75335fd5214a065f090cefeb28f9c895cfd3c Author: Bryan O'Donoghue <bryan.odonoghue@linaro.org> dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema This commit adds a new base devicetree schema for Qualcomm MIPI CSI2 PHY devices, initially for the x1e80100. It supports CPHY, DPHY, and DPHY split modes across three ports, allowing per-PHY voltage rails to be individually defined and required. > diff --git a/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml > new file mode 100644 > index 0000000000000..c9116246c1e9e > --- /dev/null > +++ b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml [ ... ] > + properties: > + port@0: > + $ref: /schemas/graph.yaml#/$defs/port-base > + description: Sensor input. Always present. > + > + properties: > + endpoint: > + $ref: /schemas/media/video-interfaces.yaml# > + unevaluatedProperties: false > + properties: > + data-lanes: > + minItems: 1 > + maxItems: 4 > + clock-lanes: > + maxItems: 1 > + remote-endpoint: true > + required: > + - data-lanes > + - clock-lanes [Severity: Medium] The commit description mentions the PHY supports both C-PHY and D-PHY modes. Since C-PHY embeds the clock signal within the 3-phase data lanes, there isn't a separate physical clock lane. Does requiring clock-lanes unconditionally here break configurations that need to use C-PHY mode? > + - remote-endpoint [ ... ] > +examples: > + - | [ ... ] > + ports { > + #address-cells = <1>; > + #size-cells = <0>; > + > + port@0 { > + reg = <0>; > + csiphy0_in_ep: endpoint { > + remote-endpoint = <&sensor_out>; [Severity: Low] The schema definition above explicitly requires data-lanes and clock-lanes to be present in the port@0 endpoint. Will omitting them from this example cause a validation failure when running dt_binding_check? > + }; > + }; > + > + port@2 { > + reg = <2>; > + csiphy0_out_ep: endpoint { > + remote-endpoint = <&controller_in>; > + }; > + }; > + }; > + }; [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260521-x1e-csi2-phy-v6-0-9d73d9bd7d20@linaro.org?part=1 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema 2026-05-21 12:26 ` sashiko-bot @ 2026-05-21 15:36 ` Bryan O'Donoghue 0 siblings, 0 replies; 8+ messages in thread From: Bryan O'Donoghue @ 2026-05-21 15:36 UTC (permalink / raw) To: sashiko-reviews Cc: vkoul, neil.armstrong, devicetree, conor+dt, olteanv, linux-phy, robh On 21/05/2026 13:26, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: > - [Medium] The schema unconditionally requires `clock-lanes` for `port@0`, which incorrectly breaks C-PHY configurations. > - [Low] The example device tree node is missing required `data-lanes` and `clock-lanes` properties in the `port@0` endpoint. > -- > > commit bec75335fd5214a065f090cefeb28f9c895cfd3c > Author: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > > dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema > > This commit adds a new base devicetree schema for Qualcomm MIPI CSI2 PHY > devices, initially for the x1e80100. It supports CPHY, DPHY, and DPHY split > modes across three ports, allowing per-PHY voltage rails to be individually > defined and required. > >> diff --git a/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml >> new file mode 100644 >> index 0000000000000..c9116246c1e9e >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml > [ ... ] >> + properties: >> + port@0: >> + $ref: /schemas/graph.yaml#/$defs/port-base >> + description: Sensor input. Always present. >> + >> + properties: >> + endpoint: >> + $ref: /schemas/media/video-interfaces.yaml# >> + unevaluatedProperties: false >> + properties: >> + data-lanes: >> + minItems: 1 >> + maxItems: 4 >> + clock-lanes: >> + maxItems: 1 >> + remote-endpoint: true >> + required: >> + - data-lanes >> + - clock-lanes > > [Severity: Medium] > The commit description mentions the PHY supports both C-PHY and D-PHY modes. > Since C-PHY embeds the clock signal within the 3-phase data lanes, there isn't > a separate physical clock lane. Does requiring clock-lanes unconditionally > here break configurations that need to use C-PHY mode? Fair hadn't thought of that. > >> + - remote-endpoint > [ ... ] >> +examples: >> + - | > [ ... ] >> + ports { >> + #address-cells = <1>; >> + #size-cells = <0>; >> + >> + port@0 { >> + reg = <0>; >> + csiphy0_in_ep: endpoint { >> + remote-endpoint = <&sensor_out>; > > [Severity: Low] > The schema definition above explicitly requires data-lanes and clock-lanes to > be present in the port@0 endpoint. Will omitting them from this example cause > a validation failure when running dt_binding_check? Yes this is what happens when you ask the question "should I run the check one more time" and say no. >> + }; >> + }; >> + >> + port@2 { >> + reg = <2>; >> + csiphy0_out_ep: endpoint { >> + remote-endpoint = <&controller_in>; >> + }; >> + }; >> + }; >> + }; > [ ... ] > -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema 2026-05-21 12:20 ` [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema Bryan O'Donoghue 2026-05-21 12:26 ` sashiko-bot @ 2026-05-21 15:15 ` Rob Herring (Arm) 1 sibling, 0 replies; 8+ messages in thread From: Rob Herring (Arm) @ 2026-05-21 15:15 UTC (permalink / raw) To: Bryan O'Donoghue Cc: linux-arm-msm, linux-media, linux-phy, linux-kernel, Kishon Vijay Abraham I, Bryan O'Donoghue, devicetree, Neil Armstrong, Vinod Koul, Krzysztof Kozlowski, Conor Dooley, Vladimir Zapolskiy On Thu, 21 May 2026 13:20:08 +0100, Bryan O'Donoghue wrote: > Add a base schema initially compatible with x1e80100 to describe MIPI CSI2 > PHY devices. > > The hardware can support both CPHY, DPHY and a special split-mode DPHY. > > The schema here defines three ports: > > port@0: > The first input port where a sensor is always required. > > port@1: > A second optional input port which if present implies DPHY split-mode. > > port@2: > A third always required output port which connects to the controller. > > The CSIPHY devices have their own pinouts on the SoC as well as their own > individual voltage rails. > > The need to model voltage rails on a per-PHY basis leads us to define > CSIPHY devices as individual nodes. > > Two nice outcomes in terms of schema and DT arise from this change. > > 1. The ability to define on a per-PHY basis voltage rails. > 2. The ability to require those voltage. > > We have had a complete bodge upstream for this where a single set of > voltage rail for all CSIPHYs has been buried inside of CAMSS. > > Much like the I2C bus which is dedicated to Camera sensors - the CCI bus in > CAMSS parlance, the CSIPHY devices should be individually modelled. > > Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > --- > .../bindings/phy/qcom,x1e80100-csi2-phy.yaml | 205 +++++++++++++++++++++ > 1 file changed, 205 insertions(+) > My bot found errors running 'make dt_binding_check' on your patch: yamllint warnings/errors: dtschema/dtc warnings/errors: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml: port@0: Missing additionalProperties/unevaluatedProperties constraint /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml: port@1: Missing additionalProperties/unevaluatedProperties constraint /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.yaml: port@2: Missing additionalProperties/unevaluatedProperties constraint /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.example.dtb: csiphy@ace4000 (qcom,x1e80100-csi2-phy): ports:port@0:endpoint: 'data-lanes' is a required property from schema $id: http://devicetree.org/schemas/phy/qcom,x1e80100-csi2-phy.yaml /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/phy/qcom,x1e80100-csi2-phy.example.dtb: csiphy@ace4000 (qcom,x1e80100-csi2-phy): ports:port@0:endpoint: 'clock-lanes' is a required property from schema $id: http://devicetree.org/schemas/phy/qcom,x1e80100-csi2-phy.yaml doc reference errors (make refcheckdocs): See https://patchwork.kernel.org/project/devicetree/patch/20260521-x1e-csi2-phy-v6-1-9d73d9bd7d20@linaro.org The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch. If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date: pip3 install dtschema --upgrade Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema. -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v6 2/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver 2026-05-21 12:20 [PATCH v6 0/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Bryan O'Donoghue 2026-05-21 12:20 ` [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema Bryan O'Donoghue @ 2026-05-21 12:20 ` Bryan O'Donoghue 2026-05-21 12:49 ` sashiko-bot 1 sibling, 1 reply; 8+ messages in thread From: Bryan O'Donoghue @ 2026-05-21 12:20 UTC (permalink / raw) To: Vinod Koul, Kishon Vijay Abraham I, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong Cc: Bryan O'Donoghue, Vladimir Zapolskiy, linux-arm-msm, linux-phy, linux-media, devicetree, linux-kernel, Bryan O'Donoghue Add a new MIPI CSI2 driver in DPHY mode initially. The entire set of existing CAMSS CSI PHY init sequences are imported in order to save time and effort in later patches. The following devices are supported in this drop: "qcom,x1e80100-csi2-phy" In-line with other PHY drivers the process node is included in the name. Data-lane and clock lane positioning and polarity selection via newly amended struct phy_configure_opts_mipi_dphy{} is supported. The Qualcomm 3PH class of PHYs can do both DPHY and CPHY mode. For now only DPHY is supported. In porting some of the logic over from camss-csiphy*.c to here its also possible to rationalise some of the code. In particular use of regulator_bulk and clk_bulk as well as dropping the seemingly useless and unused interrupt handler. The PHY sequences and a lot of the logic that goes with them are well proven in CAMSS and mature so the main thing to watch out for here is how to get the right sequencing of regulators, clocks and register-writes. The register init sequence table is imported verbatim from the existing CAMSS csiphy driver. A follow-up series will rework the table to extract the repetitive per-lane pattern into a loop. Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> --- MAINTAINERS | 10 + drivers/phy/qualcomm/Kconfig | 13 + drivers/phy/qualcomm/Makefile | 5 + drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c | 361 ++++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c | 368 +++++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-mipi-csi2.h | 94 ++++++ 6 files changed, 851 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 63389fea5d150..3b5da8a40383f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22018,6 +22018,16 @@ S: Maintained F: Documentation/devicetree/bindings/media/qcom,*-iris.yaml F: drivers/media/platform/qcom/iris/ +QUALCOMM MIPI CSI2 PHY DRIVER +M: Bryan O'Donoghue <bod@kernel.org> +L: linux-phy@lists.infradead.org +L: linux-media@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/phy/qcom,*-csi2-phy.yaml +F: drivers/phy/qualcomm/phy-qcom-mipi-csi2*.c +F: drivers/phy/qualcomm/phy-qcom-mipi-csi2*.h + QUALCOMM NAND CONTROLLER DRIVER M: Manivannan Sadhasivam <mani@kernel.org> L: linux-mtd@lists.infradead.org diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index 60a0ead127fa9..ea33025a40fd0 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -28,6 +28,19 @@ config PHY_QCOM_EDP Enable this driver to support the Qualcomm eDP PHY found in various Qualcomm chipsets. +config PHY_QCOM_MIPI_CSI2 + tristate "Qualcomm MIPI CSI2 PHY driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on OF + depends on COMMON_CLK + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + help + Enable this to support the MIPI CSI2 PHY driver found in various + Qualcomm chipsets. This PHY is used to connect MIPI CSI2 + camera sensors to the CSI Decoder in the Qualcomm Camera Subsystem + CAMSS. + config PHY_QCOM_IPQ4019_USB tristate "Qualcomm IPQ4019 USB PHY driver" depends on OF && (ARCH_QCOM || COMPILE_TEST) diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile index b71a6a0bed3f1..382cb594b06b6 100644 --- a/drivers/phy/qualcomm/Makefile +++ b/drivers/phy/qualcomm/Makefile @@ -6,6 +6,11 @@ obj-$(CONFIG_PHY_QCOM_IPQ4019_USB) += phy-qcom-ipq4019-usb.o obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o obj-$(CONFIG_PHY_QCOM_M31_USB) += phy-qcom-m31.o obj-$(CONFIG_PHY_QCOM_M31_EUSB) += phy-qcom-m31-eusb2.o + +phy-qcom-mipi-csi2-objs += phy-qcom-mipi-csi2-core.o \ + phy-qcom-mipi-csi2-3ph-dphy.o +obj-$(CONFIG_PHY_QCOM_MIPI_CSI2) += phy-qcom-mipi-csi2.o + obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o obj-$(CONFIG_PHY_QCOM_QMP_COMBO) += phy-qcom-qmp-combo.o phy-qcom-qmp-usbc.o diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c new file mode 100644 index 0000000000000..8cdff35be2da9 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm MSM Camera Subsystem - CSIPHY Module 3phase v1.0 + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2016-2025 Linaro Ltd. + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/time64.h> + +#include "phy-qcom-mipi-csi2.h" + +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(offset, n) ((offset) + 0x4 * (n)) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL0_PHY_SW_RESET BIT(0) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE BIT(7) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B BIT(0) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID BIT(1) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL10_IRQ_CLEAR_CMD BIT(0) +#define CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(offset, n) ((offset) + 0xb0 + 0x4 * (n)) + +/* + * 3 phase CSI has 19 common status regs with only 0-10 being used + * and 11-18 being reserved. + */ +#define CSI_COMMON_STATUS_NUM 11 +/* + * There are a number of common control registers + * The offset to clear the CSIPHY IRQ status starts @ 22 + * So to clear CSI_COMMON_STATUS0 this is CSI_COMMON_CONTROL22, STATUS1 is + * CONTROL23 and so on + */ +#define CSI_CTRL_STATUS_INDEX 22 + +/* + * There are 43 COMMON_CTRL registers with regs after # 33 being reserved + */ +#define CSI_CTRL_MAX 33 + +#define CSIPHY_DEFAULT_PARAMS 0 +#define CSIPHY_SETTLE_CNT_LOWER_BYTE 2 +#define CSIPHY_SKEW_CAL 7 + +/* 4nm 2PH v 2.1.2 2p5Gbps 4 lane DPHY mode */ +static const struct +mipi_csi2phy_lane_regs lane_regs_x1e80100[] = { + /* Power up lanes 2ph mode */ + {.reg_addr = 0x1014, .reg_data = 0xd5, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x101c, .reg_data = 0x7a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x1018, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + + {.reg_addr = 0x0094, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x00a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0090, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0098, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0094, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0030, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0000, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0038, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x002c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0034, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x001c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0014, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x003c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0004, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0020, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0008, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0010, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0094, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x005c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0060, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0064, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL}, + + {.reg_addr = 0x0e94, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0ea0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e90, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e98, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e94, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e30, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e28, .reg_data = 0x04, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e00, .reg_data = 0x80, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e0c, .reg_data = 0xff, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e38, .reg_data = 0x1f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e2c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e34, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e1c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e14, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e3c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e04, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e20, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0e08, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0e10, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + + {.reg_addr = 0x0494, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x04a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0490, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0498, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0494, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0430, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0400, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0438, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x042c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0434, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x041c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0414, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x043c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0404, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0420, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0408, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0410, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0494, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x045c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0460, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0464, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL}, + + {.reg_addr = 0x0894, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x08a0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0890, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0898, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0894, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0830, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0800, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0838, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x082c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0834, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x081c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0814, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x083c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0804, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0820, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0808, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0810, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0894, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x085c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0860, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0864, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL}, + + {.reg_addr = 0x0c94, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0ca0, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c90, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c98, .reg_data = 0x08, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c94, .reg_data = 0x07, .delay_us = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c30, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c00, .reg_data = 0x8e, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c38, .reg_data = 0xfe, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c2c, .reg_data = 0x01, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c34, .reg_data = 0x0f, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c1c, .reg_data = 0x0a, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c14, .reg_data = 0x60, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c3c, .reg_data = 0xb8, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c04, .reg_data = 0x0c, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c20, .reg_data = 0x00, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c08, .reg_data = 0x10, .param_type = CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {.reg_addr = 0x0c10, .reg_data = 0x52, .param_type = CSIPHY_DEFAULT_PARAMS}, + {.reg_addr = 0x0c94, .reg_data = 0xd7, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0c5c, .reg_data = 0x00, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0c60, .reg_data = 0xbd, .param_type = CSIPHY_SKEW_CAL}, + {.reg_addr = 0x0c64, .reg_data = 0x7f, .param_type = CSIPHY_SKEW_CAL}, +}; + +static inline const struct mipi_csi2phy_device_regs * +csi2phy_dev_to_regs(struct mipi_csi2phy_device *csi2phy) +{ + return &csi2phy->soc_cfg->reg_info; +} + +static void phy_qcom_mipi_csi2_hw_version_read(struct mipi_csi2phy_device *csi2phy) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + u32 tmp; + + writel(CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6)); + + tmp = readl_relaxed(csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 12)); + csi2phy->hw_version = tmp; + + tmp = readl_relaxed(csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 13)); + csi2phy->hw_version |= (tmp << 8) & 0xFF00; + + tmp = readl_relaxed(csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 14)); + csi2phy->hw_version |= (tmp << 16) & 0xFF0000; + + tmp = readl_relaxed(csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->common_regs_offset, 15)); + csi2phy->hw_version |= (tmp << 24) & 0xFF000000; + + dev_dbg_once(csi2phy->dev, "CSIPHY 3PH HW Version = 0x%08x\n", csi2phy->hw_version); +} + +/* + * phy_qcom_mipi_csi2_reset - Perform software reset on CSIPHY module + * @phy_qcom_mipi_csi2: CSIPHY device + */ +static void phy_qcom_mipi_csi2_reset(struct mipi_csi2phy_device *csi2phy) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + + writel(CSIPHY_3PH_CMN_CSI_COMMON_CTRL0_PHY_SW_RESET, + csi2phy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0)); + usleep_range(5000, 8000); + writel(0x0, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0)); +} + +/* + * phy_qcom_mipi_csi2_settle_cnt_calc - Calculate settle count value + * + * Helper function to calculate settle count value. This is + * based on the CSI2 T_hs_settle parameter which in turn + * is calculated based on the CSI2 transmitter link frequency. + * + * Return settle count value or 0 if the CSI2 link frequency + * is not available + */ +static u8 phy_qcom_mipi_csi2_settle_cnt_calc(s64 link_freq, u32 timer_clk_rate) +{ + u32 t_hs_prepare_max_ps; + u32 timer_period_ps; + u32 t_hs_settle_ps; + u8 settle_cnt; + u32 ui_ps; + + if (link_freq <= 0) + return 0; + + ui_ps = div_u64(PSEC_PER_SEC, link_freq); + ui_ps /= 2; + t_hs_prepare_max_ps = 85000 + 6 * ui_ps; + t_hs_settle_ps = t_hs_prepare_max_ps; + + timer_period_ps = div_u64(PSEC_PER_SEC, timer_clk_rate); + settle_cnt = t_hs_settle_ps / timer_period_ps - 6; + + return settle_cnt; +} + +static void +phy_qcom_mipi_csi2_gen2_config_lanes(struct mipi_csi2phy_device *csi2phy, + u8 settle_cnt) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + const struct mipi_csi2phy_lane_regs *r = regs->init_seq; + int i, array_size = regs->lane_array_size; + u32 val; + + for (i = 0; i < array_size; i++, r++) { + switch (r->param_type) { + case CSIPHY_SETTLE_CNT_LOWER_BYTE: + val = settle_cnt & 0xff; + break; + case CSIPHY_SKEW_CAL: + /* TODO: support application of skew from dt flag */ + continue; + default: + val = r->reg_data; + break; + } + writel(val, csi2phy->base + r->reg_addr); + if (r->delay_us) + udelay(r->delay_us); + } +} + +static int phy_qcom_mipi_csi2_lanes_enable(struct mipi_csi2phy_device *csi2phy, + struct mipi_csi2phy_stream_cfg *cfg) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + struct mipi_csi2phy_lanes_cfg *lane_cfg = &cfg->lane_cfg; + u8 settle_cnt; + u8 val; + int i; + + settle_cnt = phy_qcom_mipi_csi2_settle_cnt_calc(cfg->link_freq, csi2phy->timer_clk_rate); + + val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; + for (i = 0; i < cfg->num_data_lanes; i++) + val |= BIT(lane_cfg->data[i].pos * 2); + + writel(val, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 5)); + + val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B; + writel(val, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6)); + + val = 0x02; + writel(val, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 7)); + + val = 0x00; + writel(val, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 0)); + + phy_qcom_mipi_csi2_gen2_config_lanes(csi2phy, settle_cnt); + + /* IRQ_MASK registers - disable all interrupts */ + for (i = CSI_COMMON_STATUS_NUM; i < CSI_CTRL_STATUS_INDEX; i++) { + writel(0, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, i)); + } + + return 0; +} + +static void +phy_qcom_mipi_csi2_lanes_disable(struct mipi_csi2phy_device *csi2phy, + struct mipi_csi2phy_stream_cfg *cfg) +{ + const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + + writel(0, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 5)); + + writel(0, csi2phy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 6)); +} + +static const struct mipi_csi2phy_hw_ops phy_qcom_mipi_csi2_ops_3ph_1_0 = { + .hw_version_read = phy_qcom_mipi_csi2_hw_version_read, + .reset = phy_qcom_mipi_csi2_reset, + .lanes_enable = phy_qcom_mipi_csi2_lanes_enable, + .lanes_disable = phy_qcom_mipi_csi2_lanes_disable, +}; + +static const char * const x1e_clks[] = { + "core", + "timer" +}; + +static const char * const x1e_supplies[] = { + "vdda-0p9", + "vdda-1p2" +}; + +static const char * const x1e_genpd_names[] = { + "mmcx", + "mx", +}; + +const struct mipi_csi2phy_soc_cfg mipi_csi2_dphy_4nm_x1e = { + .ops = &phy_qcom_mipi_csi2_ops_3ph_1_0, + .reg_info = { + .init_seq = lane_regs_x1e80100, + .lane_array_size = ARRAY_SIZE(lane_regs_x1e80100), + .common_regs_offset = 0x1000, + }, + .supply_names = (const char **)x1e_supplies, + .num_supplies = ARRAY_SIZE(x1e_supplies), + .clk_names = (const char **)x1e_clks, + .num_clk = ARRAY_SIZE(x1e_clks), + .opp_clk = x1e_clks[0], + .timer_clk = x1e_clks[1], + .genpd_names = (const char **)x1e_genpd_names, + .num_genpd_names = ARRAY_SIZE(x1e_genpd_names), +}; diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c new file mode 100644 index 0000000000000..d8bdb2dd3c2ac --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025, Linaro Ltd. + */ +#include <dt-bindings/phy/phy.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm_opp.h> +#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h> +#include <linux/platform_device.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include "phy-qcom-mipi-csi2.h" + +static int +phy_qcom_mipi_csi2_set_clock_rates(struct mipi_csi2phy_device *csi2phy, + s64 link_freq) +{ + struct device *dev = csi2phy->dev; + unsigned long opp_rate = link_freq / 4; + struct dev_pm_opp *opp; + long timer_rate; + int ret; + + opp = dev_pm_opp_find_freq_ceil(dev, &opp_rate); + if (IS_ERR(opp)) { + dev_err(csi2phy->dev, "Couldn't find ceiling for %lld Hz\n", + link_freq); + return PTR_ERR(opp); + } + + for (int i = 0; i < csi2phy->pd_list->num_pds; i++) { + unsigned int perf = dev_pm_opp_get_required_pstate(opp, i); + + ret = dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], perf); + if (ret) { + dev_err(csi2phy->dev, "Couldn't set perf state %u\n", + perf); + dev_pm_opp_put(opp); + return ret; + } + } + dev_pm_opp_put(opp); + + ret = dev_pm_opp_set_rate(dev, opp_rate); + if (ret) { + dev_err(csi2phy->dev, "dev_pm_opp_set_rate() fail\n"); + return ret; + } + + timer_rate = clk_round_rate(csi2phy->timer_clk, link_freq / 4); + if (timer_rate < 0) + return timer_rate; + + ret = clk_set_rate(csi2phy->timer_clk, timer_rate); + if (ret) + return ret; + + csi2phy->timer_clk_rate = timer_rate; + + return 0; +} + +static int phy_qcom_mipi_csi2_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + struct phy_configure_opts_mipi_dphy *dphy_cfg = &opts->mipi_dphy; + struct mipi_csi2phy_stream_cfg *stream_cfg = &csi2phy->stream_cfg; + int ret; + + ret = phy_mipi_dphy_config_validate(dphy_cfg); + if (ret) + return ret; + + if (dphy_cfg->lanes < 1 || dphy_cfg->lanes > CSI2_MAX_DATA_LANES) + return -EINVAL; + + stream_cfg->link_freq = dphy_cfg->hs_clk_rate; + stream_cfg->num_data_lanes = dphy_cfg->lanes; + + return 0; +} + +static int phy_qcom_mipi_csi2_power_on(struct phy *phy) +{ + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + const struct mipi_csi2phy_hw_ops *ops = csi2phy->soc_cfg->ops; + struct device *dev = &phy->dev; + int ret; + + ret = regulator_bulk_enable(csi2phy->soc_cfg->num_supplies, + csi2phy->supplies); + if (ret) + return ret; + + ret = phy_qcom_mipi_csi2_set_clock_rates(csi2phy, csi2phy->stream_cfg.link_freq); + if (ret) + goto poweroff_phy; + + ret = clk_bulk_prepare_enable(csi2phy->soc_cfg->num_clk, + csi2phy->clks); + if (ret) { + dev_err(dev, "failed to enable clocks, %d\n", ret); + goto poweroff_phy; + } + + ops->reset(csi2phy); + + ops->hw_version_read(csi2phy); + + return ops->lanes_enable(csi2phy, &csi2phy->stream_cfg); + +poweroff_phy: + regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, + csi2phy->supplies); + + return ret; +} + +static int phy_qcom_mipi_csi2_power_off(struct phy *phy) +{ + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + int i; + + for (i = 0; i < csi2phy->pd_list->num_pds; i++) + dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0); + + clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk, + csi2phy->clks); + regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, + csi2phy->supplies); + + return 0; +} + +static const struct phy_ops phy_qcom_mipi_csi2_ops = { + .configure = phy_qcom_mipi_csi2_configure, + .power_on = phy_qcom_mipi_csi2_power_on, + .power_off = phy_qcom_mipi_csi2_power_off, + .owner = THIS_MODULE, +}; + +static struct phy *qcom_csi2_phy_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct mipi_csi2phy_device *csi2phy = dev_get_drvdata(dev); + + if (args->args[0] != PHY_TYPE_DPHY) { + dev_err(csi2phy->dev, "mode %d -EOPNOTSUPP\n", args->args[0]); + return ERR_PTR(-EOPNOTSUPP); + } + + csi2phy->phy_mode = args->args[0]; + + return csi2phy->phy; +} + +static int phy_qcom_mipi_csi2_attach_pm_domains(struct mipi_csi2phy_device *csi2phy) +{ + const struct dev_pm_domain_attach_data pd_data = { + .pd_names = csi2phy->soc_cfg->genpd_names, + .num_pd_names = csi2phy->soc_cfg->num_genpd_names, + }; + + return devm_pm_domain_attach_list(csi2phy->dev, &pd_data, &csi2phy->pd_list); +} + +static int phy_qcom_mipi_csi2_parse_routing(struct mipi_csi2phy_device *csi2phy) +{ + struct mipi_csi2phy_stream_cfg *stream_cfg = &csi2phy->stream_cfg; + u32 lane_polarities[CSI2_MAX_DATA_LANES + 1]; + u32 data_lanes[CSI2_MAX_DATA_LANES]; + struct device *dev = csi2phy->dev; + struct fwnode_handle *ep; + int num_polarities; + int num_data_lanes; + u32 clock_lane; + int i, ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 1, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (ep) { + fwnode_handle_put(ep); + dev_err(dev, "DPHY split mode is not supported\n"); + return -EOPNOTSUPP; + } + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!ep) { + dev_err(dev, "Missing port@0\n"); + return -ENODEV; + } + + num_data_lanes = fwnode_property_count_u32(ep, "data-lanes"); + if (num_data_lanes < 1 || num_data_lanes > CSI2_MAX_DATA_LANES) { + ret = -EINVAL; + dev_err(dev, "Invalid data-lanes count: %d\n", num_data_lanes); + goto out_put; + } + stream_cfg->num_data_lanes = num_data_lanes; + + ret = fwnode_property_read_u32_array(ep, "data-lanes", data_lanes, + stream_cfg->num_data_lanes); + if (ret) { + dev_err(dev, "Failed to read data-lanes: %d\n", ret); + goto out_put; + } + + ret = fwnode_property_read_u32(ep, "clock-lanes", &clock_lane); + if (ret) { + dev_err(dev, "Failed to read clock-lanes: %d\n", ret); + goto out_put; + } + + /* lane-polarities: optional, up to num_data_lanes + 1 entries */ + memset(lane_polarities, 0x00, sizeof(lane_polarities)); + num_polarities = fwnode_property_count_u32(ep, "lane-polarities"); + if (num_polarities > 0) { + if (num_polarities != stream_cfg->num_data_lanes + 1) { + ret = -EINVAL; + dev_err(dev, "clock+data-lane %d/polarities %d mismatch\n", + stream_cfg->num_data_lanes + 1, num_polarities); + goto out_put; + } + + ret = fwnode_property_read_u32_array(ep, "lane-polarities", lane_polarities, + num_polarities); + if (ret) { + dev_err(dev, "Failed to read lane-polarities: %d\n", ret); + goto out_put; + } + } + + for (i = 0; i < csi2phy->stream_cfg.num_data_lanes; i++) { + csi2phy->stream_cfg.lane_cfg.data[i].pos = data_lanes[i]; + csi2phy->stream_cfg.lane_cfg.data[i].pol = lane_polarities[i + 1]; + } + csi2phy->stream_cfg.lane_cfg.clk.pos = clock_lane; + csi2phy->stream_cfg.lane_cfg.clk.pol = lane_polarities[0]; + + ret = 0; + +out_put: + fwnode_handle_put(ep); + + return ret; +} + +static int phy_qcom_mipi_csi2_probe(struct platform_device *pdev) +{ + unsigned int i, num_clk, num_supplies; + struct mipi_csi2phy_device *csi2phy; + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *generic_phy; + int ret; + + csi2phy = devm_kzalloc(dev, sizeof(*csi2phy), GFP_KERNEL); + if (!csi2phy) + return -ENOMEM; + + csi2phy->dev = dev; + dev_set_drvdata(dev, csi2phy); + + csi2phy->soc_cfg = device_get_match_data(&pdev->dev); + + if (!csi2phy->soc_cfg) + return -EINVAL; + + num_clk = csi2phy->soc_cfg->num_clk; + csi2phy->clks = devm_kzalloc(dev, sizeof(*csi2phy->clks) * num_clk, GFP_KERNEL); + if (!csi2phy->clks) + return -ENOMEM; + + ret = phy_qcom_mipi_csi2_parse_routing(csi2phy); + if (ret) + return ret; + + ret = phy_qcom_mipi_csi2_attach_pm_domains(csi2phy); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to attach power-domain list\n"); + + for (i = 0; i < num_clk; i++) + csi2phy->clks[i].id = csi2phy->soc_cfg->clk_names[i]; + + ret = devm_clk_bulk_get(dev, num_clk, csi2phy->clks); + if (ret) + return dev_err_probe(dev, ret, "Failed to get clocks\n"); + + csi2phy->timer_clk = devm_clk_get(dev, csi2phy->soc_cfg->timer_clk); + if (IS_ERR(csi2phy->timer_clk)) { + return dev_err_probe(dev, PTR_ERR(csi2phy->timer_clk), + "Failed to get timer clock\n"); + } + + ret = devm_pm_opp_set_clkname(dev, csi2phy->soc_cfg->opp_clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to set opp clkname\n"); + + ret = devm_pm_opp_of_add_table(dev); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "invalid OPP table in device tree\n"); + + num_supplies = csi2phy->soc_cfg->num_supplies; + csi2phy->supplies = devm_kzalloc(dev, sizeof(*csi2phy->supplies) * num_supplies, + GFP_KERNEL); + if (!csi2phy->supplies) + return -ENOMEM; + + for (i = 0; i < num_supplies; i++) + csi2phy->supplies[i].supply = csi2phy->soc_cfg->supply_names[i]; + + ret = devm_regulator_bulk_get(dev, num_supplies, csi2phy->supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get regulator supplies\n"); + + csi2phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2phy->base)) + return PTR_ERR(csi2phy->base); + + generic_phy = devm_phy_create(dev, NULL, &phy_qcom_mipi_csi2_ops); + if (IS_ERR(generic_phy)) { + ret = PTR_ERR(generic_phy); + return dev_err_probe(dev, ret, "failed to create phy\n"); + } + csi2phy->phy = generic_phy; + + phy_set_drvdata(generic_phy, csi2phy); + + phy_provider = devm_of_phy_provider_register(dev, qcom_csi2_phy_xlate); + if (!IS_ERR(phy_provider)) + dev_dbg(dev, "Registered MIPI CSI2 PHY device\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id phy_qcom_mipi_csi2_of_match_table[] = { + { .compatible = "qcom,x1e80100-csi2-phy", .data = &mipi_csi2_dphy_4nm_x1e }, + { } +}; +MODULE_DEVICE_TABLE(of, phy_qcom_mipi_csi2_of_match_table); + +static struct platform_driver phy_qcom_mipi_csi2_driver = { + .probe = phy_qcom_mipi_csi2_probe, + .driver = { + .name = "qcom-mipi-csi2-phy", + .of_match_table = phy_qcom_mipi_csi2_of_match_table, + }, +}; + +module_platform_driver(phy_qcom_mipi_csi2_driver); + +MODULE_DESCRIPTION("Qualcomm MIPI CSI2 PHY driver"); +MODULE_AUTHOR("Bryan O'Donoghue <bryan.odonoghue@linaro.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h b/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h new file mode 100644 index 0000000000000..52e7fbecf79db --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * Qualcomm MIPI CSI2 CPHY/DPHY driver + * + * Copyright (C) 2025 Linaro Ltd. + */ +#ifndef __PHY_QCOM_MIPI_CSI2_H__ +#define __PHY_QCOM_MIPI_CSI2_H__ + +#include <linux/phy/phy.h> + +#define CSI2_MAX_DATA_LANES 4 + +struct mipi_csi2phy_lane { + u8 pos; + u8 pol; +}; + +struct mipi_csi2phy_lanes_cfg { + struct mipi_csi2phy_lane data[CSI2_MAX_DATA_LANES]; + struct mipi_csi2phy_lane clk; +}; + +struct mipi_csi2phy_stream_cfg { + s64 link_freq; + u8 num_data_lanes; + struct mipi_csi2phy_lanes_cfg lane_cfg; +}; + +struct mipi_csi2phy_device; + +struct mipi_csi2phy_hw_ops { + void (*hw_version_read)(struct mipi_csi2phy_device *csi2phy_dev); + void (*reset)(struct mipi_csi2phy_device *csi2phy_dev); + int (*lanes_enable)(struct mipi_csi2phy_device *csi2phy_dev, + struct mipi_csi2phy_stream_cfg *cfg); + void (*lanes_disable)(struct mipi_csi2phy_device *csi2phy_dev, + struct mipi_csi2phy_stream_cfg *cfg); +}; + +struct mipi_csi2phy_lane_regs { + const s32 reg_addr; + const s32 reg_data; + const u32 delay_us; + const u32 param_type; +}; + +struct mipi_csi2phy_device_regs { + const struct mipi_csi2phy_lane_regs *init_seq; + const int lane_array_size; + const u32 common_regs_offset; +}; + +struct mipi_csi2phy_soc_cfg { + const struct mipi_csi2phy_hw_ops *ops; + const struct mipi_csi2phy_device_regs reg_info; + + const char ** const supply_names; + const unsigned int num_supplies; + + const char ** const clk_names; + const unsigned int num_clk; + + const char * const opp_clk; + const char * const timer_clk; + + const char ** const genpd_names; + const unsigned int num_genpd_names; +}; + +struct mipi_csi2phy_device { + struct device *dev; + u8 phy_mode; + + struct phy *phy; + void __iomem *base; + + struct clk_bulk_data *clks; + struct clk *timer_clk; + u32 timer_clk_rate; + + struct regulator_bulk_data *supplies; + struct dev_pm_domain_list *pd_list; + + const struct mipi_csi2phy_soc_cfg *soc_cfg; + struct mipi_csi2phy_stream_cfg stream_cfg; + + u32 hw_version; +}; + +extern const struct mipi_csi2phy_soc_cfg mipi_csi2_dphy_4nm_x1e; + +#endif /* __PHY_QCOM_MIPI_CSI2_H__ */ -- 2.54.0 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v6 2/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver 2026-05-21 12:20 ` [PATCH v6 2/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Bryan O'Donoghue @ 2026-05-21 12:49 ` sashiko-bot 2026-05-21 15:58 ` Bryan O'Donoghue 0 siblings, 1 reply; 8+ messages in thread From: sashiko-bot @ 2026-05-21 12:49 UTC (permalink / raw) To: Bryan O'Donoghue Cc: devicetree, vkoul, robh, olteanv, linux-phy, neil.armstrong, conor+dt Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: - [High] Extensive resource leaks on error paths in power_on and set_clock_rates, and missing OPP rate reset in power_off. - [High] phy_qcom_mipi_csi2_configure overrides the DT-parsed lane count without bounds checking against the physical layout. - [High] Unconditional dereference of pd_list leads to a NULL pointer dereference panic. - [High] Division by zero occurs if the timer clock rate resolves to 0. - [High] Settle count calculation can underflow to a very large positive value. - [Medium] The ops->lanes_disable callback is omitted in phy_qcom_mipi_csi2_power_off. - [Medium] Clock lane positioning and lane polarities are parsed but entirely ignored. - [Medium] The probe function allows the absence of an OPP table, but power_on strictly requires it. - [Medium] qcom_csi2_phy_xlate accesses PHY arguments without checking args_count. - [Medium] Unvalidated data-lanes integer from DT can cause an out-of-bounds bitwise shift. -- commit 3c373b3ab7783a677ec4c98f344d8b1c57e2e110 Author: Bryan O'Donoghue <bryan.odonoghue@linaro.org> phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Add a new MIPI CSI2 driver in DPHY mode initially. The entire set of existing CAMSS CSI PHY init sequences are imported in order to save time and effort in later patches. > diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c > index 0000000000000..8cdff35be2da9 > --- /dev/null > +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c [ ... ] > +static u8 phy_qcom_mipi_csi2_settle_cnt_calc(s64 link_freq, u32 timer_clk_rate) > +{ [ ... ] > + timer_period_ps = div_u64(PSEC_PER_SEC, timer_clk_rate); > + settle_cnt = t_hs_settle_ps / timer_period_ps - 6; [Severity: High] Can this subtraction underflow to a very large positive value? If the system has a low timer clock rate resulting in a high timer_period_ps, the division could yield a value less than 6. Since settle_cnt is an unsigned 8-bit integer, wrapping around to a large value could severely misconfigure the PHY hardware timings. > + > + return settle_cnt; > +} [ ... ] > +static int phy_qcom_mipi_csi2_lanes_enable(struct mipi_csi2phy_device *csi2phy, > + struct mipi_csi2phy_stream_cfg *cfg) > +{ [ ... ] > + settle_cnt = phy_qcom_mipi_csi2_settle_cnt_calc(cfg->link_freq, csi2phy->timer_clk_rate); > + > + val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; [Severity: Medium] The commit message states that data-lane and clock lane positioning and polarity selection are supported. Are the polarity fields ignored here? It appears the clock lane enable bit is hardcoded and the polarity fields parsed from the DT are not programmed into the PHY registers. > + for (i = 0; i < cfg->num_data_lanes; i++) > + val |= BIT(lane_cfg->data[i].pos * 2); [Severity: Medium] Can this result in an out-of-bounds shift? The pos value is read from the device tree as an unvalidated 32-bit integer. A malformed DT with large lane positions (e.g. > 15) could result in a shift value >= 32, which is undefined behavior and might truncate the hardware configuration mask. > + > + writel(val, csi2phy->base + > + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 5)); [ ... ] > diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c > index 0000000000000..d8bdb2dd3c2ac > --- /dev/null > +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c [ ... ] > +static int > +phy_qcom_mipi_csi2_set_clock_rates(struct mipi_csi2phy_device *csi2phy, > + s64 link_freq) > +{ [ ... ] > + opp = dev_pm_opp_find_freq_ceil(dev, &opp_rate); > + if (IS_ERR(opp)) { [Severity: Medium] Since the probe function tolerates -ENODEV for the OPP table, won't power_on unconditionally fail here if the table is actually missing? > + dev_err(csi2phy->dev, "Couldn't find ceiling for %lld Hz\n", > + link_freq); > + return PTR_ERR(opp); > + } > + > + for (int i = 0; i < csi2phy->pd_list->num_pds; i++) { [Severity: High] What happens to pd_list if CONFIG_PM is disabled? devm_pm_domain_attach_list() returns 0 when CONFIG_PM is disabled, but pd_list will remain NULL. This would cause a NULL pointer dereference here and in phy_qcom_mipi_csi2_power_off(). > + unsigned int perf = dev_pm_opp_get_required_pstate(opp, i); > + > + ret = dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], perf); > + if (ret) { > + dev_err(csi2phy->dev, "Couldn't set perf state %u\n", > + perf); > + dev_pm_opp_put(opp); > + return ret; [Severity: High] Does this leak the performance state for previously set domains? If the loop fails on a later iteration, it appears to return without resetting the domains that were already successfully configured. > + } > + } > + dev_pm_opp_put(opp); > + > + ret = dev_pm_opp_set_rate(dev, opp_rate); > + if (ret) { > + dev_err(csi2phy->dev, "dev_pm_opp_set_rate() fail\n"); > + return ret; > + } > + > + timer_rate = clk_round_rate(csi2phy->timer_clk, link_freq / 4); > + if (timer_rate < 0) > + return timer_rate; > + > + ret = clk_set_rate(csi2phy->timer_clk, timer_rate); > + if (ret) > + return ret; > + > + csi2phy->timer_clk_rate = timer_rate; [Severity: High] What if clk_round_rate() returns 0? If the clock is disabled or misconfigured, timer_rate could be 0. Later, phy_qcom_mipi_csi2_settle_cnt_calc() calls div_u64(PSEC_PER_SEC, timer_clk_rate), which would result in a division by zero and a kernel panic. > + > + return 0; > +} > + > +static int phy_qcom_mipi_csi2_configure(struct phy *phy, > + union phy_configure_opts *opts) > +{ [ ... ] > + stream_cfg->link_freq = dphy_cfg->hs_clk_rate; > + stream_cfg->num_data_lanes = dphy_cfg->lanes; [Severity: High] Does this blindly overwrite the DT-parsed lane count without bounds checking against the physical layout? If a consumer requests more lanes than are physically mapped in the device tree, the hardware configuration could iterate out-of-bounds of the initialized array elements, repeatedly applying bitmasks for physical position 0. > + > + return 0; > +} > + > +static int phy_qcom_mipi_csi2_power_on(struct phy *phy) > +{ [ ... ] > + ret = clk_bulk_prepare_enable(csi2phy->soc_cfg->num_clk, > + csi2phy->clks); > + if (ret) { > + dev_err(dev, "failed to enable clocks, %d\n", ret); > + goto poweroff_phy; > + } > + [ ... ] > +poweroff_phy: > + regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, > + csi2phy->supplies); > + > + return ret; [Severity: High] Does this error path leak the performance state and OPP rate? It seems to only disable regulators, completely missing the cleanup for dev_pm_genpd_set_performance_state() and dev_pm_opp_set_rate(). > +} > + > +static int phy_qcom_mipi_csi2_power_off(struct phy *phy) > +{ > + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); > + int i; > + > + for (i = 0; i < csi2phy->pd_list->num_pds; i++) > + dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0); [Severity: High] Does this leave the OPP frequency vote elevated? It doesn't look like dev_pm_opp_set_rate(dev, 0) is called to release the OPP frequency vote. [Severity: Medium] Is the ops->lanes_disable() callback intentionally omitted here? Disabling the clocks and regulators without invoking the callback bypasses the hardware-specific teardown sequence (like clearing CLK_ENABLE and COMMON_PWRDN_B). Could this leave the PHY in an invalid logical state for the next power-on cycle? > + > + clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk, > + csi2phy->clks); > + regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, > + csi2phy->supplies); > + > + return 0; > +} [ ... ] > +static struct phy *qcom_csi2_phy_xlate(struct device *dev, > + const struct of_phandle_args *args) > +{ > + struct mipi_csi2phy_device *csi2phy = dev_get_drvdata(dev); > + > + if (args->args[0] != PHY_TYPE_DPHY) { [Severity: Medium] Can this access uninitialized stack memory? If the device tree node has #phy-cells = <0>, args_count will be 0, and reading args->args[0] might yield uninitialized memory, leading to unpredictable behavior or the incorrect rejection of valid PHY bindings. Should args->args_count be checked first? > + dev_err(csi2phy->dev, "mode %d -EOPNOTSUPP\n", args->args[0]); > + return ERR_PTR(-EOPNOTSUPP); > + } -- Sashiko AI review · https://sashiko.dev/#/patchset/20260521-x1e-csi2-phy-v6-0-9d73d9bd7d20@linaro.org?part=2 -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v6 2/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver 2026-05-21 12:49 ` sashiko-bot @ 2026-05-21 15:58 ` Bryan O'Donoghue 0 siblings, 0 replies; 8+ messages in thread From: Bryan O'Donoghue @ 2026-05-21 15:58 UTC (permalink / raw) To: sashiko-reviews Cc: devicetree, vkoul, robh, olteanv, linux-phy, neil.armstrong, conor+dt On 21/05/2026 13:49, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: > - [High] Extensive resource leaks on error paths in power_on and set_clock_rates, and missing OPP rate reset in power_off. > - [High] phy_qcom_mipi_csi2_configure overrides the DT-parsed lane count without bounds checking against the physical layout. > - [High] Unconditional dereference of pd_list leads to a NULL pointer dereference panic. > - [High] Division by zero occurs if the timer clock rate resolves to 0. > - [High] Settle count calculation can underflow to a very large positive value. > - [Medium] The ops->lanes_disable callback is omitted in phy_qcom_mipi_csi2_power_off. > - [Medium] Clock lane positioning and lane polarities are parsed but entirely ignored. > - [Medium] The probe function allows the absence of an OPP table, but power_on strictly requires it. > - [Medium] qcom_csi2_phy_xlate accesses PHY arguments without checking args_count. > - [Medium] Unvalidated data-lanes integer from DT can cause an out-of-bounds bitwise shift. > -- > > commit 3c373b3ab7783a677ec4c98f344d8b1c57e2e110 > Author: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > > phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver > > Add a new MIPI CSI2 driver in DPHY mode initially. The entire set of > existing CAMSS CSI PHY init sequences are imported in order to save time > and effort in later patches. > >> diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c >> index 0000000000000..8cdff35be2da9 >> --- /dev/null >> +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c > [ ... ] >> +static u8 phy_qcom_mipi_csi2_settle_cnt_calc(s64 link_freq, u32 timer_clk_rate) >> +{ > [ ... ] >> + timer_period_ps = div_u64(PSEC_PER_SEC, timer_clk_rate); >> + settle_cnt = t_hs_settle_ps / timer_period_ps - 6; > > [Severity: High] > Can this subtraction underflow to a very large positive value? > > If the system has a low timer clock rate resulting in a high > timer_period_ps, the division could yield a value less than 6. Since > settle_cnt is an unsigned 8-bit integer, wrapping around to a large > value could severely misconfigure the PHY hardware timings. I'm not sure it is really worthwhile writing defensive code for this case. We audit every DT binding for clock frequency sanity so you have to input junk into a device tree and upstream it and we aren't generally in the habit of verifying those inputs. > >> + >> + return settle_cnt; >> +} > [ ... ] >> +static int phy_qcom_mipi_csi2_lanes_enable(struct mipi_csi2phy_device *csi2phy, >> + struct mipi_csi2phy_stream_cfg *cfg) >> +{ > [ ... ] >> + settle_cnt = phy_qcom_mipi_csi2_settle_cnt_calc(cfg->link_freq, csi2phy->timer_clk_rate); >> + >> + val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; > > [Severity: Medium] > The commit message states that data-lane and clock lane positioning and > polarity selection are supported. Are the polarity fields ignored here? Exactly the same situation as we already have for this PHY in CAMSS since forever. We parse but don't program the various fields. > It appears the clock lane enable bit is hardcoded and the polarity fields > parsed from the DT are not programmed into the PHY registers. >> + for (i = 0; i < cfg->num_data_lanes; i++) >> + val |= BIT(lane_cfg->data[i].pos * 2); Yeah this is longstanding behaviour but, its probably worthwhile to update it since we consume the data from DT anyway. > [Severity: Medium] > Can this result in an out-of-bounds shift? > > The pos value is read from the device tree as an unvalidated 32-bit > integer. A malformed DT with large lane positions (e.g. > 15) could > result in a shift value >= 32, which is undefined behavior and might > truncate the hardware configuration mask. Still no - not going to spend time picking through what goes into DT in the driver. For example we don't sanity check register values or IRQ vector numbers. > >> + >> + writel(val, csi2phy->base + >> + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->common_regs_offset, 5)); > [ ... ] >> diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c >> index 0000000000000..d8bdb2dd3c2ac >> --- /dev/null >> +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c > [ ... ] >> +static int >> +phy_qcom_mipi_csi2_set_clock_rates(struct mipi_csi2phy_device *csi2phy, >> + s64 link_freq) >> +{ > [ ... ] >> + opp = dev_pm_opp_find_freq_ceil(dev, &opp_rate); >> + if (IS_ERR(opp)) { > > [Severity: Medium] > Since the probe function tolerates -ENODEV for the OPP table, won't > power_on unconditionally fail here if the table is actually missing? > >> + dev_err(csi2phy->dev, "Couldn't find ceiling for %lld Hz\n", >> + link_freq); >> + return PTR_ERR(opp); >> + } >> + >> + for (int i = 0; i < csi2phy->pd_list->num_pds; i++) { > > [Severity: High] > What happens to pd_list if CONFIG_PM is disabled? > > devm_pm_domain_attach_list() returns 0 when CONFIG_PM is disabled, but > pd_list will remain NULL. This would cause a NULL pointer dereference > here and in phy_qcom_mipi_csi2_power_off(). No idea, lets find out. >> + unsigned int perf = dev_pm_opp_get_required_pstate(opp, i); >> + >> + ret = dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], perf); >> + if (ret) { >> + dev_err(csi2phy->dev, "Couldn't set perf state %u\n", >> + perf); >> + dev_pm_opp_put(opp); >> + return ret; > > [Severity: High] > Does this leak the performance state for previously set domains? > > If the loop fails on a later iteration, it appears to return without resetting > the domains that were already successfully configured. True we need to unwind. >> + } >> + } >> + dev_pm_opp_put(opp); >> + >> + ret = dev_pm_opp_set_rate(dev, opp_rate); >> + if (ret) { >> + dev_err(csi2phy->dev, "dev_pm_opp_set_rate() fail\n"); >> + return ret; >> + } >> + >> + timer_rate = clk_round_rate(csi2phy->timer_clk, link_freq / 4); >> + if (timer_rate < 0) >> + return timer_rate; >> + >> + ret = clk_set_rate(csi2phy->timer_clk, timer_rate); >> + if (ret) >> + return ret; >> + >> + csi2phy->timer_clk_rate = timer_rate; > > [Severity: High] > What if clk_round_rate() returns 0? > > If the clock is disabled or misconfigured, timer_rate could be 0. Later, > phy_qcom_mipi_csi2_settle_cnt_calc() calls > div_u64(PSEC_PER_SEC, timer_clk_rate), which would result in a division > by zero and a kernel panic. An esoteric ask since you again have to have bugs in your DT but, for the sake of not being spammed by a static analysis tool, sure lets check for zero. >> + >> + return 0; >> +} >> + >> +static int phy_qcom_mipi_csi2_configure(struct phy *phy, >> + union phy_configure_opts *opts) >> +{ > [ ... ] >> + stream_cfg->link_freq = dphy_cfg->hs_clk_rate; >> + stream_cfg->num_data_lanes = dphy_cfg->lanes; > > [Severity: High] > Does this blindly overwrite the DT-parsed lane count without bounds > checking against the physical layout? It takes the lanes_count from the controller DT field. > If a consumer requests more lanes than are physically mapped in the > device tree, the hardware configuration could iterate out-of-bounds of > the initialized array elements, repeatedly applying bitmasks for physical > position 0. No the value comes from the controller DT data-lanes field but since we are consuming data-lanes from the PHY its actually a worthwhile test to validate what the controller says to what we have an -EINVAL if they don't agree. > >> + >> + return 0; >> +} >> + >> +static int phy_qcom_mipi_csi2_power_on(struct phy *phy) >> +{ > [ ... ] >> + ret = clk_bulk_prepare_enable(csi2phy->soc_cfg->num_clk, >> + csi2phy->clks); >> + if (ret) { >> + dev_err(dev, "failed to enable clocks, %d\n", ret); >> + goto poweroff_phy; >> + } >> + > [ ... ] >> +poweroff_phy: >> + regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, >> + csi2phy->supplies); >> + >> + return ret; > > [Severity: High] > Does this error path leak the performance state and OPP rate? > > It seems to only disable regulators, completely missing the cleanup for > dev_pm_genpd_set_performance_state() and dev_pm_opp_set_rate(). > When the clocks disable the perf state _should_ drop back but, I will check. >> +} >> + >> +static int phy_qcom_mipi_csi2_power_off(struct phy *phy) >> +{ >> + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); >> + int i; >> + >> + for (i = 0; i < csi2phy->pd_list->num_pds; i++) >> + dev_pm_genpd_set_performance_state(csi2phy->pd_list->pd_devs[i], 0); > > [Severity: High] > Does this leave the OPP frequency vote elevated? > > It doesn't look like dev_pm_opp_set_rate(dev, 0) is called to release > the OPP frequency vote. > > [Severity: Medium] > Is the ops->lanes_disable() callback intentionally omitted here? > > Disabling the clocks and regulators without invoking the callback > bypasses the hardware-specific teardown sequence (like clearing > CLK_ENABLE and COMMON_PWRDN_B). Could this leave the PHY in an invalid > logical state for the next power-on cycle? I mean, when power goes off you loose the state in the regs ... but for the sake of completeness sure, I will add this. >> + >> + clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk, >> + csi2phy->clks); >> + regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, >> + csi2phy->supplies); >> + >> + return 0; >> +} > [ ... ] >> +static struct phy *qcom_csi2_phy_xlate(struct device *dev, >> + const struct of_phandle_args *args) >> +{ >> + struct mipi_csi2phy_device *csi2phy = dev_get_drvdata(dev); >> + >> + if (args->args[0] != PHY_TYPE_DPHY) { > > [Severity: Medium] > Can this access uninitialized stack memory? > > If the device tree node has #phy-cells = <0>, args_count will be 0, and > reading args->args[0] might yield uninitialized memory, leading to > unpredictable behavior or the incorrect rejection of valid PHY bindings. > Should args->args_count be checked first? I feel the answer is "no" but I'm not averse to checking. > >> + dev_err(csi2phy->dev, "mode %d -EOPNOTSUPP\n", args->args[0]); >> + return ERR_PTR(-EOPNOTSUPP); >> + } > -- linux-phy mailing list linux-phy@lists.infradead.org https://lists.infradead.org/mailman/listinfo/linux-phy ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-05-21 15:58 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-21 12:20 [PATCH v6 0/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Bryan O'Donoghue 2026-05-21 12:20 ` [PATCH v6 1/2] dt-bindings: phy: qcom: Add CSI2 C-PHY/DPHY schema Bryan O'Donoghue 2026-05-21 12:26 ` sashiko-bot 2026-05-21 15:36 ` Bryan O'Donoghue 2026-05-21 15:15 ` Rob Herring (Arm) 2026-05-21 12:20 ` [PATCH v6 2/2] phy: qcom-mipi-csi2: Add a CSI2 MIPI DPHY driver Bryan O'Donoghue 2026-05-21 12:49 ` sashiko-bot 2026-05-21 15:58 ` Bryan O'Donoghue
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox