* [PATCH v3 0/5] phy: rockchip-samsung-dcphy: add the MIPI D-PHY receiver
@ 2026-08-10 12:10 Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 1/5] dt-bindings: phy: Add PHY_TYPE_DSI and PHY_TYPE_CSI definitions Jason Yang via B4 Relay
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Jason Yang via B4 Relay @ 2026-08-10 12:10 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Guochun Huang, Philipp Zabel
Cc: Michael Riesch, Bryan O'Donoghue, linux-phy, devicetree,
linux-arm-kernel, linux-rockchip, linux-kernel, Jason Yang
The Samsung MIPI D-/C-PHY block on RK3588 drives a MIPI DSI transmitter
and, on the same PHY, can receive from a MIPI CSI camera. Mainline
currently models the block as a single struct phy, so the two cannot be
brought up independently: the PHY core reference-counts power_on() per
struct phy, and the devicetree has no way to say which of the two a
consumer is attaching to.
This series registers one struct phy each for the transmitter and the
receiver, adds generic PHY_TYPE_DSI/PHY_TYPE_CSI constants so the
devicetree can name them, and implements the receiver side.
1/5 adds PHY_TYPE_DSI and PHY_TYPE_CSI to the generic header
2/5 lets this binding's existing single cell take them
3/5 factors the transmitter power on/off into helpers (no functional
change; the object code is unchanged)
4/5 registers the two PHYs and use-counts the shared common block
5/5 implements the receiver bring-up
Note on the constant values: 1/5 takes 14 and 15, and 14 is also claimed
by "dt-bindings: phy: Add PHY_TYPE_EUSB2 definition" [1], which is still
under discussion. Whichever series lands first, I am happy to respin on
whatever values you prefer - please just say which.
[1] https://lore.kernel.org/all/20260723-eusb2-int-handling-v5-v5-1-f32ea6c1a318@oss.qualcomm.com/
The in-tree devicetrees and the rockchip,rk3588-mipi-dsi2.yaml example
keep using PHY_TYPE_DPHY on purpose: for the transmitter the two
spellings are equivalent, and converting them would make new DTBs
require a new kernel for no functional gain.
D-PHY versus C-PHY selection is unchanged by this series: the
PHY_TYPE_CPHY cell still selects a C-PHY transmitter, and the driver
still returns -EOPNOTSUPP for it. The receiver support added here is
D-PHY only.
Testing
=======
Tested on an RK3588 board: DCPHY0 drives a DSI panel from its
transmitter while an IMX219 camera feeds its receiver, using the new
PHY_TYPE_DSI and PHY_TYPE_CSI cells; DCPHY1 drives a second DSI panel
through an unchanged PHY_TYPE_DPHY cell.
- transmitter-first: panels light from boot, the camera joins and
streams (30-frame byte-exact captures)
- receiver-first: with the DCPHY0 transmitter disabled, the camera
brings the block up as the first user through the use-counted
reset path; the transmitter then joins mid-stream and its panel
lights without disturbing the capture
- transmitter leaving mid-stream: 150 consecutive frames captured
byte-exact across a panel disable
- repeated camera start/stop and panel enable/disable cycles on
both PHYs, with no PHY warnings or use-count underflows logged
- the D-PHY settle registers, including the SETTLE_CLK_SEL bit the
TRM marks read-only, read back as programmed while streaming
- receiver-first bring-up and the mid-stream transmitter join were
also verified with an OV5640
The 3- and 4-lane receiver paths share the same table-driven
programming but are untested here.
As in v2, driving the DSI panels needs a local dw-mipi-dsi2 timing
workaround. That has since been root-caused: the bootloader can leave
the SPLL at the 351 MHz bootrom default while the devicetree describes
it as a 702 MHz fixed clock, halving the rate of both DSI hosts. A fix
is already on the U-Boot list [3]. None of this affects the receiver
path, which the camera captures exercise independently.
[3] https://lore.kernel.org/u-boot/20260804002643.2308515-3-jonas@kwiboo.se/
Changes since v2 [2]
====================
- Add generic PHY_TYPE_DSI/PHY_TYPE_CSI and select the PHY by the
protocol the consumer speaks (the constants were suggested by Michael
Riesch, and Vinod Koul agreed they are reasonable). With the protocol
named in the first cell there is nothing left for a second cell to
carry, so the Rockchip-specific header and the enum: [1, 2] are both
gone.
- Because 1/4 of v2 was rewritten around the new constants, Bryan
O'Donoghue's Reviewed-by on it has not been carried over.
- Turn the type switch into an early guard and drop the unreachable
trailing return (Bryan O'Donoghue; the object code is unchanged either
way).
- Rework the shared-block handling as Michael Riesch suggested: one
struct samsung_mipi_phy per PHY, a use count in the parent taken and
dropped inside the power helpers, lockdep_assert_held() in the shared
helper rather than taking the mutex there, and no "first" flags.
- The APB reset is now issued on the same use-counted path for both
PHYs, so a receiver-first power-on gets it too.
- On the open question from the v2 thread: the BIAS registers have no
enable bit - they hold only static analog settings, per TRM section
22.4.3 - so the release side only drops the use count. Michael has
since confirmed the block is shared per the TRM and had no objection
to renaming; get()/put() is used rather than a one-shot
configure()/bool because the APB reset folded into the same path
must be allowed to run again once every user is gone, which a flag
that is never cleared cannot express.
- In the receiver patch itself: reject a zero lane count at power-on,
correct the clock-lane comment (the register spacing is regular; the
field layout is what differs), document the vendor settle-table
quirks (the 1490 Mbps boundary and the read-only-marked SETTLE_CLK_SEL
bit) and the per-lane tuning provenance, annotate the teardown delay,
and use unsigned loop counters throughout.
- The driver still does not touch the GRF M/S_CPHY_MODE selects that
Michael pointed at: both PHYs here run D-PHY, which is the GRF
reset default. Explicit mode programming belongs to C-PHY support,
pending the Rockchip clarification he is chasing. The first-user
BIAS programming also stays correct once C-PHY lands: the only
mode-dependent BIAS field is BIAS_CON4.I_MUX_SEL, and TRM section
22.1 states that "The TX and RX only support operate in the same
PHY mode", so the two PHYs cannot disagree about it. With the
driver D-PHY only for now, that combination cannot be exercised
yet.
[2] https://lore.kernel.org/all/20260726-dcphy-rx-v1-v2-0-cf9cc34a412a@gmail.com/
---
Jason Yang (5):
dt-bindings: phy: Add PHY_TYPE_DSI and PHY_TYPE_CSI definitions
dt-bindings: phy: rockchip,rk3588-mipi-dcphy: allow DSI and CSI consumers
phy: rockchip-samsung-dcphy: factor MIPI D-PHY power on/off into helpers
phy: rockchip-samsung-dcphy: model TX and RX as separate PHYs
phy: rockchip-samsung-dcphy: add MIPI D-PHY receiver support
.../bindings/phy/rockchip,rk3588-mipi-dcphy.yaml | 11 +-
drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c | 555 +++++++++++++++++++--
include/dt-bindings/phy/phy.h | 2 +
3 files changed, 517 insertions(+), 51 deletions(-)
---
base-commit: 1590cf0329716306e948a8fc29f1d3ee87d3989f
change-id: 20260721-dcphy-rx-v1-9af57f3edfd9
Best regards,
--
Jason Yang <jason98166@gmail.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 1/5] dt-bindings: phy: Add PHY_TYPE_DSI and PHY_TYPE_CSI definitions
2026-08-10 12:10 [PATCH v3 0/5] phy: rockchip-samsung-dcphy: add the MIPI D-PHY receiver Jason Yang via B4 Relay
@ 2026-08-10 12:10 ` Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 2/5] dt-bindings: phy: rockchip,rk3588-mipi-dcphy: allow DSI and CSI consumers Jason Yang via B4 Relay
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Jason Yang via B4 Relay @ 2026-08-10 12:10 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Guochun Huang, Philipp Zabel
Cc: Michael Riesch, Bryan O'Donoghue, linux-phy, devicetree,
linux-arm-kernel, linux-rockchip, linux-kernel, Jason Yang
From: Jason Yang <jason98166@gmail.com>
MIPI D-PHY and C-PHY blocks are increasingly direction-agnostic: the
same PHY IP can drive a MIPI DSI display or receive from a MIPI CSI-2
camera, and combo blocks like the Samsung IP on RK3588 expose both
directions to independent consumers at the same time. A binding that
needs to tell the two consumers apart has nothing generic to reach
for: most constants in this header name a protocol (PHY_TYPE_USB3,
PHY_TYPE_DP, ...), while the MIPI entries name only the electrical
layer.
Add PHY_TYPE_DSI and PHY_TYPE_CSI to select a PHY by the MIPI
protocol it speaks, which also implies the direction. They do not
replace PHY_TYPE_DPHY/PHY_TYPE_CPHY, which remain the right choice
where the cell selects the electrical layer. First user is the
Rockchip RK3588 MIPI DC-PHY binding.
Suggested-by: Michael Riesch <michael.riesch@collabora.com>
Signed-off-by: Jason Yang <jason98166@gmail.com>
Assisted-by: Claude:claude-fable-5
---
include/dt-bindings/phy/phy.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/include/dt-bindings/phy/phy.h b/include/dt-bindings/phy/phy.h
index 979b5dfd8353..8ee3f88e6d80 100644
--- a/include/dt-bindings/phy/phy.h
+++ b/include/dt-bindings/phy/phy.h
@@ -24,6 +24,8 @@
#define PHY_TYPE_CPHY 11
#define PHY_TYPE_USXGMII 12
#define PHY_TYPE_XAUI 13
+#define PHY_TYPE_DSI 14
+#define PHY_TYPE_CSI 15
#define PHY_POL_NORMAL 0
#define PHY_POL_INVERT 1
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 2/5] dt-bindings: phy: rockchip,rk3588-mipi-dcphy: allow DSI and CSI consumers
2026-08-10 12:10 [PATCH v3 0/5] phy: rockchip-samsung-dcphy: add the MIPI D-PHY receiver Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 1/5] dt-bindings: phy: Add PHY_TYPE_DSI and PHY_TYPE_CSI definitions Jason Yang via B4 Relay
@ 2026-08-10 12:10 ` Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 3/5] phy: rockchip-samsung-dcphy: factor MIPI D-PHY power on/off into helpers Jason Yang via B4 Relay
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Jason Yang via B4 Relay @ 2026-08-10 12:10 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Guochun Huang, Philipp Zabel
Cc: Michael Riesch, Bryan O'Donoghue, linux-phy, devicetree,
linux-arm-kernel, linux-rockchip, linux-kernel, Jason Yang
From: Jason Yang <jason98166@gmail.com>
The Samsung MIPI D-/C-PHY block on RK3588 drives a MIPI DSI
transmitter and, on the same PHY, can receive from a MIPI CSI camera.
Describing both as independent consumers of one PHY requires the
devicetree to tell them apart.
Let the existing single cell also take PHY_TYPE_DSI or PHY_TYPE_CSI,
selecting the PHY by the protocol the consumer speaks. PHY_TYPE_DPHY
stays accepted and equivalent to PHY_TYPE_DSI so shipped device trees
remain valid, and PHY_TYPE_CPHY keeps selecting a C-PHY transmitter.
The receiver support added by this series is D-PHY only.
Signed-off-by: Jason Yang <jason98166@gmail.com>
Assisted-by: Claude:claude-fable-5
---
.../devicetree/bindings/phy/rockchip,rk3588-mipi-dcphy.yaml | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/phy/rockchip,rk3588-mipi-dcphy.yaml b/Documentation/devicetree/bindings/phy/rockchip,rk3588-mipi-dcphy.yaml
index c8ff5ba22a86..1f7c2e2ddfc1 100644
--- a/Documentation/devicetree/bindings/phy/rockchip,rk3588-mipi-dcphy.yaml
+++ b/Documentation/devicetree/bindings/phy/rockchip,rk3588-mipi-dcphy.yaml
@@ -22,9 +22,14 @@ properties:
"#phy-cells":
const: 1
description: |
- Argument is mode to operate in. Supported modes are:
- - PHY_TYPE_DPHY
- - PHY_TYPE_CPHY
+ The block contains a MIPI transmitter and a MIPI receiver, which
+ can be referenced independently. The argument selects one of
+ them:
+ - PHY_TYPE_DSI: the transmitter, for a MIPI DSI host
+ - PHY_TYPE_CSI: the receiver, for a MIPI CSI-2 host
+ - PHY_TYPE_DPHY: the transmitter in D-PHY mode; equivalent to
+ PHY_TYPE_DSI and kept for existing device trees
+ - PHY_TYPE_CPHY: the transmitter in C-PHY mode
See include/dt-bindings/phy/phy.h for constants.
clocks:
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 3/5] phy: rockchip-samsung-dcphy: factor MIPI D-PHY power on/off into helpers
2026-08-10 12:10 [PATCH v3 0/5] phy: rockchip-samsung-dcphy: add the MIPI D-PHY receiver Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 1/5] dt-bindings: phy: Add PHY_TYPE_DSI and PHY_TYPE_CSI definitions Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 2/5] dt-bindings: phy: rockchip,rk3588-mipi-dcphy: allow DSI and CSI consumers Jason Yang via B4 Relay
@ 2026-08-10 12:10 ` Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 4/5] phy: rockchip-samsung-dcphy: model TX and RX as separate PHYs Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 5/5] phy: rockchip-samsung-dcphy: add MIPI D-PHY receiver support Jason Yang via B4 Relay
4 siblings, 0 replies; 6+ messages in thread
From: Jason Yang via B4 Relay @ 2026-08-10 12:10 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Guochun Huang, Philipp Zabel
Cc: Michael Riesch, Bryan O'Donoghue, linux-phy, devicetree,
linux-arm-kernel, linux-rockchip, linux-kernel, Jason Yang
From: Jason Yang <jason98166@gmail.com>
To prepare for adding a receiver (CSI) PHY next to the existing
transmitter (DSI) one, rename the transmitter power-on helper to
samsung_mipi_dphy_tx_power_on() and gather its teardown into a new
samsung_mipi_dphy_tx_power_off(), so transmitter and receiver can
later be powered on and off through symmetric helpers.
With both arms of the type switch reduced to plain returns, turn it
into an early guard for the not yet supported C-PHY and drop the
unreachable trailing return.
No functional change intended.
Signed-off-by: Jason Yang <jason98166@gmail.com>
Assisted-by: Claude:claude-fable-5
---
drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c | 33 +++++++++++------------
1 file changed, 15 insertions(+), 18 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
index cbd780556da8..99ca0e1cc574 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
@@ -1332,7 +1332,7 @@ samsung_mipi_dphy_data_lane_timing_init(struct samsung_mipi_dcphy *samsung)
regmap_write(samsung->regmap, DPHY_MD3_TIME_CON4, 0x1f4);
}
-static int samsung_mipi_dphy_power_on(struct samsung_mipi_dcphy *samsung)
+static int samsung_mipi_dphy_tx_power_on(struct samsung_mipi_dcphy *samsung)
{
int ret;
@@ -1358,6 +1358,14 @@ static int samsung_mipi_dphy_power_on(struct samsung_mipi_dcphy *samsung)
return 0;
}
+static int samsung_mipi_dphy_tx_power_off(struct samsung_mipi_dcphy *samsung)
+{
+ samsung_mipi_dphy_lane_disable(samsung);
+ samsung_mipi_dcphy_pll_disable(samsung);
+
+ return 0;
+}
+
static int samsung_mipi_dcphy_power_on(struct phy *phy)
{
struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
@@ -1366,33 +1374,22 @@ static int samsung_mipi_dcphy_power_on(struct phy *phy)
udelay(1);
reset_control_deassert(samsung->apb_rst);
- switch (samsung->type) {
- case PHY_TYPE_DPHY:
- return samsung_mipi_dphy_power_on(samsung);
- default:
- /* CPHY part to be implemented later */
+ /* CPHY part to be implemented later */
+ if (samsung->type != PHY_TYPE_DPHY)
return -EOPNOTSUPP;
- }
- return 0;
+ return samsung_mipi_dphy_tx_power_on(samsung);
}
static int samsung_mipi_dcphy_power_off(struct phy *phy)
{
struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
- switch (samsung->type) {
- case PHY_TYPE_DPHY:
- samsung_mipi_dphy_lane_disable(samsung);
- break;
- default:
- /* CPHY part to be implemented later */
+ /* CPHY part to be implemented later */
+ if (samsung->type != PHY_TYPE_DPHY)
return -EOPNOTSUPP;
- }
- samsung_mipi_dcphy_pll_disable(samsung);
-
- return 0;
+ return samsung_mipi_dphy_tx_power_off(samsung);
}
static int
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 4/5] phy: rockchip-samsung-dcphy: model TX and RX as separate PHYs
2026-08-10 12:10 [PATCH v3 0/5] phy: rockchip-samsung-dcphy: add the MIPI D-PHY receiver Jason Yang via B4 Relay
` (2 preceding siblings ...)
2026-08-10 12:10 ` [PATCH v3 3/5] phy: rockchip-samsung-dcphy: factor MIPI D-PHY power on/off into helpers Jason Yang via B4 Relay
@ 2026-08-10 12:10 ` Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 5/5] phy: rockchip-samsung-dcphy: add MIPI D-PHY receiver support Jason Yang via B4 Relay
4 siblings, 0 replies; 6+ messages in thread
From: Jason Yang via B4 Relay @ 2026-08-10 12:10 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Guochun Huang, Philipp Zabel
Cc: Michael Riesch, Bryan O'Donoghue, linux-phy, devicetree,
linux-arm-kernel, linux-rockchip, linux-kernel, Jason Yang
From: Jason Yang <jason98166@gmail.com>
The DC-PHY drives a MIPI DSI transmitter and a MIPI CSI receiver, and
on RK3588 both can be wired up at the same time. The PHY core
reference-counts power_on() per struct phy, so a single struct phy
cannot bring the two up independently.
Register one struct phy for each and move the per-PHY state (PHY type
and lane count) into a new struct samsung_mipi_phy. of_xlate() maps
the single cell onto the two PHYs: PHY_TYPE_DSI selects the
transmitter and PHY_TYPE_CSI the receiver, while PHY_TYPE_DPHY and
PHY_TYPE_CPHY keep selecting the transmitter so existing device trees
keep working. Values outside those four are now rejected there instead
of failing later in power_on(); no in-tree devicetree uses any other
value.
The two PHYs share the common block (BIAS, and the PLL that only the
transmitter drives - RK3588 TRM section 22.2) and the block-level APB
reset. That reset has to be assumed to reset a running peer's PLL,
timing and lane configuration as well, so it must not be pulsed while
the peer is up. Guard it with a use count taken and dropped inside the
power helpers, and program the shared BIAS references on the same
transition away from zero: a count rather than a one-shot flag, so
that once every user is gone the next bring-up gets the reset and the
BIAS programming again. There is nothing to write on release itself -
the BIAS registers hold only static analog settings, with no enable
bit (section 22.4.3). A PHY that powers on while its peer is already
up therefore no longer gets the block-level reset; its bring-up
sequence programs the configuration it uses.
Folding the BIAS programming into that helper also moves it ahead of
the per-PHY reset assert, where the TRM's worked example puts it after
(section 22.6.4.1). That is safe: neither M_RESETN nor S_RESETN covers
the BIAS registers (section 22.6.1.2), and the values written are
those registers' reset defaults (section 22.4.2).
The receiver stays rejected in configure(), power_on() and power_off()
until the receiver bring-up is added in the next change.
Signed-off-by: Jason Yang <jason98166@gmail.com>
Assisted-by: Claude:claude-fable-5
---
drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c | 188 ++++++++++++++++++----
1 file changed, 155 insertions(+), 33 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
index 99ca0e1cc574..09dbcf438f99 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
@@ -11,8 +11,10 @@
#include <linux/hw_bitfield.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/lockdep.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
@@ -280,6 +282,24 @@ struct samsung_mipi_dcphy_plat_data {
u32 dphy_tx_max_lane_kbps;
};
+struct samsung_mipi_dcphy;
+
+/* Index of the two PHYs the block exposes. */
+enum {
+ SAMSUNG_MIPI_TX,
+ SAMSUNG_MIPI_RX,
+ SAMSUNG_MIPI_PHY_MAX,
+};
+
+struct samsung_mipi_phy {
+ struct phy *phy;
+ struct samsung_mipi_dcphy *parent;
+ u8 id;
+ /* Electrical layer (PHY_TYPE_DPHY/CPHY), not the DT cell value. */
+ u8 type;
+ unsigned int lanes;
+};
+
struct samsung_mipi_dcphy {
struct device *dev;
struct clk *ref_clk;
@@ -290,9 +310,14 @@ struct samsung_mipi_dcphy {
struct reset_control *s_phy_rst;
struct reset_control *apb_rst;
struct reset_control *grf_apb_rst;
- unsigned int lanes;
- struct phy *phy;
- u8 type;
+ struct samsung_mipi_phy phys[SAMSUNG_MIPI_PHY_MAX];
+ /* Serialises the two PHYs' access to the shared common block. */
+ struct mutex lock;
+ /*
+ * Number of powered-on PHYs using the common block (APB reset
+ * and BIAS references). Written under the lock above.
+ */
+ unsigned int common_users;
const struct samsung_mipi_dcphy_plat_data *pdata;
struct {
@@ -973,8 +998,25 @@ struct samsung_mipi_dphy_timing samsung_mipi_dphy_timing_table[] = {
{ 80, 2, 0, 0, 28, 5, 0, 22, 2, 0, 5},
};
-static void samsung_mipi_dcphy_bias_block_enable(struct samsung_mipi_dcphy *samsung)
+/*
+ * The APB reset is block-level and has to be assumed to return the whole
+ * register file to the defaults of TRM section 22.4.2, which would leave
+ * a running peer PHY unconfigured - so it may only run while neither PHY
+ * is powered. The pairing of get and put relies on the phy core calling
+ * power_on/power_off only on the 0<->1 transitions of each phy's own
+ * power_count.
+ */
+static void samsung_mipi_dcphy_common_get(struct samsung_mipi_dcphy *samsung)
{
+ lockdep_assert_held(&samsung->lock);
+
+ if (samsung->common_users++)
+ return;
+
+ reset_control_assert(samsung->apb_rst);
+ udelay(1);
+ reset_control_deassert(samsung->apb_rst);
+
regmap_write(samsung->regmap, BIAS_CON0, I_DEV_DIV_6 | I_RES_100_2UA);
regmap_write(samsung->regmap, BIAS_CON1, I_VBG_SEL_820MV | I_BGR_VREF_820MV |
I_LADDER_1_00V);
@@ -984,18 +1026,34 @@ static void samsung_mipi_dcphy_bias_block_enable(struct samsung_mipi_dcphy *sams
/* default output voltage select:
* dphy: 400mv
* cphy: 530mv
+ * C-PHY is not supported yet, so the D-PHY value serves both PHYs.
*/
regmap_update_bits(samsung->regmap, BIAS_CON4,
I_MUX_SEL_MASK, I_MUX_400MV);
}
+/*
+ * Nothing to undo on the way down: the BIAS registers hold only static
+ * analog settings - current and voltage references, the bandgap chopper
+ * divider and the D-PHY/C-PHY level select - with no enable bit.
+ */
+static void samsung_mipi_dcphy_common_put(struct samsung_mipi_dcphy *samsung)
+{
+ lockdep_assert_held(&samsung->lock);
+
+ if (WARN_ON(!samsung->common_users))
+ return;
+
+ samsung->common_users--;
+}
+
static void samsung_mipi_dphy_lane_enable(struct samsung_mipi_dcphy *samsung)
{
regmap_write(samsung->regmap, DPHY_MC_GNR_CON1, T_PHY_READY(0x2000));
regmap_update_bits(samsung->regmap, DPHY_MC_GNR_CON0,
PHY_ENABLE, PHY_ENABLE);
- switch (samsung->lanes) {
+ switch (samsung->phys[SAMSUNG_MIPI_TX].lanes) {
case 4:
regmap_write(samsung->regmap, DPHY_MD3_GNR_CON1,
T_PHY_READY(0x2000));
@@ -1026,7 +1084,7 @@ static void samsung_mipi_dphy_lane_enable(struct samsung_mipi_dcphy *samsung)
static void samsung_mipi_dphy_lane_disable(struct samsung_mipi_dcphy *samsung)
{
- switch (samsung->lanes) {
+ switch (samsung->phys[SAMSUNG_MIPI_TX].lanes) {
case 4:
regmap_update_bits(samsung->regmap, DPHY_MD3_GNR_CON0,
PHY_ENABLE, 0);
@@ -1336,15 +1394,16 @@ static int samsung_mipi_dphy_tx_power_on(struct samsung_mipi_dcphy *samsung)
{
int ret;
+ samsung_mipi_dcphy_common_get(samsung);
+
reset_control_assert(samsung->m_phy_rst);
- samsung_mipi_dcphy_bias_block_enable(samsung);
samsung_mipi_dcphy_pll_configure(samsung);
samsung_mipi_dphy_clk_lane_timing_init(samsung);
samsung_mipi_dphy_data_lane_timing_init(samsung);
ret = samsung_mipi_dcphy_pll_enable(samsung);
if (ret < 0)
- return ret;
+ goto err_put;
samsung_mipi_dphy_lane_enable(samsung);
@@ -1356,6 +1415,11 @@ static int samsung_mipi_dphy_tx_power_on(struct samsung_mipi_dcphy *samsung)
usleep_range(100, 110);
return 0;
+
+err_put:
+ samsung_mipi_dcphy_common_put(samsung);
+
+ return ret;
}
static int samsung_mipi_dphy_tx_power_off(struct samsung_mipi_dcphy *samsung)
@@ -1363,33 +1427,49 @@ static int samsung_mipi_dphy_tx_power_off(struct samsung_mipi_dcphy *samsung)
samsung_mipi_dphy_lane_disable(samsung);
samsung_mipi_dcphy_pll_disable(samsung);
+ samsung_mipi_dcphy_common_put(samsung);
+
return 0;
}
static int samsung_mipi_dcphy_power_on(struct phy *phy)
{
- struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
-
- reset_control_assert(samsung->apb_rst);
- udelay(1);
- reset_control_deassert(samsung->apb_rst);
+ struct samsung_mipi_phy *samsung_phy = phy_get_drvdata(phy);
+ struct samsung_mipi_dcphy *samsung = samsung_phy->parent;
+ int ret;
/* CPHY part to be implemented later */
- if (samsung->type != PHY_TYPE_DPHY)
+ if (samsung_phy->type != PHY_TYPE_DPHY)
return -EOPNOTSUPP;
- return samsung_mipi_dphy_tx_power_on(samsung);
+ mutex_lock(&samsung->lock);
+ if (samsung_phy->id == SAMSUNG_MIPI_RX)
+ ret = -EOPNOTSUPP;
+ else
+ ret = samsung_mipi_dphy_tx_power_on(samsung);
+ mutex_unlock(&samsung->lock);
+
+ return ret;
}
static int samsung_mipi_dcphy_power_off(struct phy *phy)
{
- struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+ struct samsung_mipi_phy *samsung_phy = phy_get_drvdata(phy);
+ struct samsung_mipi_dcphy *samsung = samsung_phy->parent;
+ int ret;
/* CPHY part to be implemented later */
- if (samsung->type != PHY_TYPE_DPHY)
+ if (samsung_phy->type != PHY_TYPE_DPHY)
return -EOPNOTSUPP;
- return samsung_mipi_dphy_tx_power_off(samsung);
+ mutex_lock(&samsung->lock);
+ if (samsung_phy->id == SAMSUNG_MIPI_RX)
+ ret = -EOPNOTSUPP;
+ else
+ ret = samsung_mipi_dphy_tx_power_off(samsung);
+ mutex_unlock(&samsung->lock);
+
+ return ret;
}
static int
@@ -1482,10 +1562,15 @@ samsung_mipi_dcphy_pll_calc_rate(struct samsung_mipi_dcphy *samsung,
static int samsung_mipi_dcphy_configure(struct phy *phy,
union phy_configure_opts *opts)
{
- struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+ struct samsung_mipi_phy *samsung_phy = phy_get_drvdata(phy);
+ struct samsung_mipi_dcphy *samsung = samsung_phy->parent;
unsigned long long target_rate = opts->mipi_dphy.hs_clk_rate;
- samsung->lanes = opts->mipi_dphy.lanes > 4 ? 4 : opts->mipi_dphy.lanes;
+ /* The receiver is brought up in a later change. */
+ if (samsung_phy->id == SAMSUNG_MIPI_RX)
+ return -EOPNOTSUPP;
+
+ samsung_phy->lanes = opts->mipi_dphy.lanes > 4 ? 4 : opts->mipi_dphy.lanes;
samsung_mipi_dcphy_pll_calc_rate(samsung, target_rate);
opts->mipi_dphy.hs_clk_rate = samsung->pll.rate;
@@ -1495,16 +1580,16 @@ static int samsung_mipi_dcphy_configure(struct phy *phy,
static int samsung_mipi_dcphy_init(struct phy *phy)
{
- struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+ struct samsung_mipi_phy *samsung_phy = phy_get_drvdata(phy);
- return pm_runtime_resume_and_get(samsung->dev);
+ return pm_runtime_resume_and_get(samsung_phy->parent->dev);
}
static int samsung_mipi_dcphy_exit(struct phy *phy)
{
- struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+ struct samsung_mipi_phy *samsung_phy = phy_get_drvdata(phy);
- pm_runtime_put(samsung->dev);
+ pm_runtime_put(samsung_phy->parent->dev);
return 0;
}
@@ -1530,19 +1615,43 @@ static struct phy *samsung_mipi_dcphy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct samsung_mipi_dcphy *samsung = dev_get_drvdata(dev);
+ struct samsung_mipi_phy *samsung_phy;
+ u8 id = SAMSUNG_MIPI_TX;
+ u8 type;
if (args->args_count != 1) {
dev_err(dev, "invalid number of arguments\n");
return ERR_PTR(-EINVAL);
}
- if (samsung->type != PHY_NONE && samsung->type != args->args[0])
- dev_warn(dev, "phy type select %d overwriting type %d\n",
- args->args[0], samsung->type);
+ switch (args->args[0]) {
+ case PHY_TYPE_CSI:
+ id = SAMSUNG_MIPI_RX;
+ fallthrough;
+ case PHY_TYPE_DSI:
+ /*
+ * Both protocols run over D-PHY here; C-PHY is selected
+ * with PHY_TYPE_CPHY and is not supported yet.
+ */
+ type = PHY_TYPE_DPHY;
+ break;
+ case PHY_TYPE_DPHY:
+ case PHY_TYPE_CPHY:
+ /* Electrical-layer selectors for the transmitter. */
+ type = args->args[0];
+ break;
+ default:
+ dev_err(dev, "invalid phy type %u\n", args->args[0]);
+ return ERR_PTR(-EINVAL);
+ }
- samsung->type = args->args[0];
+ samsung_phy = &samsung->phys[id];
+ if (samsung_phy->type != PHY_NONE && samsung_phy->type != type)
+ dev_warn(dev, "phy type select %u overwriting type %u\n",
+ type, samsung_phy->type);
+ samsung_phy->type = type;
- return samsung->phy;
+ return samsung_phy->phy;
}
static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
@@ -1553,6 +1662,7 @@ static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
struct phy_provider *phy_provider;
struct resource *res;
void __iomem *regs;
+ unsigned int i;
int ret;
samsung = devm_kzalloc(dev, sizeof(*samsung), GFP_KERNEL);
@@ -1563,6 +1673,10 @@ static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
samsung->pdata = device_get_match_data(dev);
platform_set_drvdata(pdev, samsung);
+ ret = devm_mutex_init(dev, &samsung->lock);
+ if (ret)
+ return ret;
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
@@ -1607,11 +1721,19 @@ static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
return dev_err_probe(dev, PTR_ERR(samsung->grf_apb_rst),
"Failed to get system grf_apb_rst control\n");
- samsung->phy = devm_phy_create(dev, NULL, &samsung_mipi_dcphy_ops);
- if (IS_ERR(samsung->phy))
- return dev_err_probe(dev, PTR_ERR(samsung->phy), "Failed to create MIPI DC-PHY\n");
+ for (i = 0; i < ARRAY_SIZE(samsung->phys); i++) {
+ struct phy *phy = devm_phy_create(dev, NULL,
+ &samsung_mipi_dcphy_ops);
+
+ if (IS_ERR(phy))
+ return dev_err_probe(dev, PTR_ERR(phy),
+ "Failed to create MIPI DC-PHY\n");
- phy_set_drvdata(samsung->phy, samsung);
+ samsung->phys[i].phy = phy;
+ samsung->phys[i].parent = samsung;
+ samsung->phys[i].id = i;
+ phy_set_drvdata(phy, &samsung->phys[i]);
+ }
ret = devm_pm_runtime_enable(dev);
if (ret)
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v3 5/5] phy: rockchip-samsung-dcphy: add MIPI D-PHY receiver support
2026-08-10 12:10 [PATCH v3 0/5] phy: rockchip-samsung-dcphy: add the MIPI D-PHY receiver Jason Yang via B4 Relay
` (3 preceding siblings ...)
2026-08-10 12:10 ` [PATCH v3 4/5] phy: rockchip-samsung-dcphy: model TX and RX as separate PHYs Jason Yang via B4 Relay
@ 2026-08-10 12:10 ` Jason Yang via B4 Relay
4 siblings, 0 replies; 6+ messages in thread
From: Jason Yang via B4 Relay @ 2026-08-10 12:10 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Guochun Huang, Philipp Zabel
Cc: Michael Riesch, Bryan O'Donoghue, linux-phy, devicetree,
linux-arm-kernel, linux-rockchip, linux-kernel, Jason Yang
From: Jason Yang <jason98166@gmail.com>
Implement the receiver (CSI) PHY: add the RX register layout (clock
lane and four data lanes), the HS-frequency settle table, and the
receiver bring-up and teardown, wired into the per-PHY power paths.
The PLL stays off in the receiver - RK3588 TRM section 22.6.3 step (3)
notes that "If Slave Lanes are only used, then PLL sequence can be
skipped", the sensor supplying the link clock instead - so configure()
only records the requested rate for the settle lookup.
The bring-up follows the TRM receiver start-up sequence (section 22.6.3
and the worked receiver example in section 22.6.4.3): assert S_RESETN,
program the settle configuration, enable the lanes, wait for PHY_READY,
and only then release S_RESETN. Step (8) of section 22.6.3 is explicit
that S_RESETN is released only "after all of PHY_READY of each Lane",
so PHY_READY is polled while the lane reset is still asserted.
The receiver reaches the shared common block through the same
use-counted helper as the transmitter, so a receiver-first power-on
also gets the APB reset and the BIAS programming.
The HS-RX settle values come from the rk3588 vendor kernel; the table
in the driver records why they are not derived from the TRM.
Tested on an RK3588 board with an IMX219 camera on DCPHY0's
receiver and a DSI panel on the same PHY's transmitter. Both power-on
orders work: transmitter first with the camera joining, and receiver
first with the transmitter joining mid-stream without disturbing the
capture (150 consecutive frames, byte-exact). Repeated camera
start/stop and panel enable/disable cycles complete without PHY
errors, the settle registers read back as programmed while streaming,
and the receiver-first bring-up was also verified with an OV5640.
Signed-off-by: Jason Yang <jason98166@gmail.com>
Assisted-by: Claude:claude-fable-5
---
drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c | 356 +++++++++++++++++++++-
1 file changed, 348 insertions(+), 8 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
index 09dbcf438f99..4f945b6dcc96 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
@@ -246,6 +246,63 @@
#define T_TA_GET(x) FIELD_PREP(GENMASK(7, 4), x)
#define T_TA_GO(x) FIELD_PREP(GENMASK(3, 0), x)
+/* D-PHY receiver registers (clock lane + four data lanes) */
+#define DPHY_SC_GNR_CON0 0x0b00
+#define DPHY_SC_GNR_CON1 0x0b04
+#define DPHY_SC_ANA_CON1 0x0b0c
+#define HS_RX_BIAS_CON(x) FIELD_PREP(GENMASK(15, 11), x)
+#define DPHY_SC_ANA_CON2 0x0b10
+#define HS_TERM_SW(x) FIELD_PREP(GENMASK(2, 0), x)
+#define DPHY_SC_ANA_CON3 0x0b14
+#define ULPS_HYS_SW_DPHY(x) FIELD_PREP(GENMASK(10, 8), x)
+#define DPHY_SC_TIME_CON0 0x0b30
+#define T_CLK_SETTLE(x) FIELD_PREP(GENMASK(7, 0), x)
+#define T_CLK_MISS(x) FIELD_PREP(GENMASK(11, 8), x)
+#define COMBO_SD0_GNR_CON0 0x0c00
+#define COMBO_SD0_GNR_CON1 0x0c04
+#define COMBO_SD0_ANA_CON1 0x0c0c
+#define COMBO_SD0_ANA_CON2 0x0c10
+#define SKEW_DLYSEL(x) FIELD_PREP(GENMASK(9, 8), x)
+#define RX_TERM_SW(x) FIELD_PREP(GENMASK(2, 0), x)
+#define COMBO_SD0_ANA_CON3 0x0c14
+#define SEL_ESCPOL BIT(11)
+#define LP_HYS_SW(x) FIELD_PREP(GENMASK(5, 4), x)
+#define COMBO_SD0_ANA_CON7 0x0c24
+#define CLK_DBL_CTRL(x) FIELD_PREP(GENMASK(7, 6), x)
+#define COMBO_SD0_TIME_CON0 0x0c30
+#define T_HS_SETTLE(x) FIELD_PREP(GENMASK(7, 0), x)
+#define SETTLE_CLK_SEL BIT(8)
+#define COMBO_SD0_TIME_CON1 0x0c34
+#define T_ERR_SOT_SYNC(x) FIELD_PREP(GENMASK(7, 0), x)
+#define COMBO_SD0_DESKEW_CON2 0x0c48
+#define SKEW_CAL_CLK_COARSE_SET(x) FIELD_PREP(GENMASK(4, 0), x)
+#define COMBO_SD1_GNR_CON0 0x0d00
+#define COMBO_SD1_GNR_CON1 0x0d04
+#define COMBO_SD1_ANA_CON1 0x0d0c
+#define COMBO_SD1_ANA_CON2 0x0d10
+#define COMBO_SD1_ANA_CON3 0x0d14
+#define COMBO_SD1_ANA_CON7 0x0d24
+#define COMBO_SD1_TIME_CON0 0x0d30
+#define COMBO_SD1_TIME_CON1 0x0d34
+#define COMBO_SD1_DESKEW_CON2 0x0d48
+#define COMBO_SD2_GNR_CON0 0x0e00
+#define COMBO_SD2_GNR_CON1 0x0e04
+#define COMBO_SD2_ANA_CON1 0x0e0c
+#define COMBO_SD2_ANA_CON2 0x0e10
+#define COMBO_SD2_ANA_CON3 0x0e14
+#define COMBO_SD2_ANA_CON7 0x0e24
+#define COMBO_SD2_TIME_CON0 0x0e30
+#define COMBO_SD2_TIME_CON1 0x0e34
+#define COMBO_SD2_DESKEW_CON2 0x0e48
+#define DPHY_SD3_GNR_CON0 0x0f00
+#define DPHY_SD3_GNR_CON1 0x0f04
+#define DPHY_SD3_ANA_CON1 0x0f0c
+#define DPHY_SD3_ANA_CON2 0x0f10
+#define DPHY_SD3_ANA_CON3 0x0f14
+#define DPHY_SD3_TIME_CON0 0x0f30
+#define DPHY_SD3_TIME_CON1 0x0f34
+#define DPHY_SD3_DESKEW_CON2 0x0f48
+
/* MIPI_CDPHY_GRF registers */
#define MIPI_DCPHY_GRF_CON0 0x0000
#define S_CPHY_MODE FIELD_PREP_WM16(BIT(3), 1)
@@ -298,6 +355,7 @@ struct samsung_mipi_phy {
/* Electrical layer (PHY_TYPE_DPHY/CPHY), not the DT cell value. */
u8 type;
unsigned int lanes;
+ unsigned long long hs_clk_rate;
};
struct samsung_mipi_dcphy {
@@ -998,6 +1056,238 @@ struct samsung_mipi_dphy_timing samsung_mipi_dphy_timing_table[] = {
{ 80, 2, 0, 0, 28, 5, 0, 22, 2, 0, 5},
};
+/* D-PHY receiver HS-RX configuration lookup */
+struct samsung_mipi_dphy_rx_hsfreq_range {
+ u32 range_h_mbps;
+ u16 cfg_bit;
+};
+
+/*
+ * HS RX settle values taken verbatim from the rk3588 vendor kernel.
+ * The TRM defines these fields but defers the per-data-rate table to a
+ * timing supplement. Each cfg_bit is a pre-combined SETTLE_CLK_SEL |
+ * T_HS_SETTLE value for the data-lane TIME_CON0: bit 8 selects the
+ * divide-by-2 settle clock below 1500 Mbps per the TRM (the vendor
+ * table switches one bucket early, at 1490 Mbps, and is kept
+ * unchanged). The TRM marks bit 8 read-only, but the vendor kernel has
+ * always programmed it. Sorted by .range_h_mbps ascending.
+ */
+static const struct samsung_mipi_dphy_rx_hsfreq_range samsung_mipi_dphy_rx_hsfreq_ranges[] = {
+ { 80, 0x105 }, { 100, 0x106 }, { 120, 0x107 }, { 140, 0x108 },
+ { 160, 0x109 }, { 180, 0x10a }, { 200, 0x10b }, { 220, 0x10c },
+ { 240, 0x10d }, { 270, 0x10e }, { 290, 0x10f }, { 310, 0x110 },
+ { 330, 0x111 }, { 350, 0x112 }, { 370, 0x113 }, { 390, 0x114 },
+ { 410, 0x115 }, { 430, 0x116 }, { 450, 0x117 }, { 470, 0x118 },
+ { 490, 0x119 }, { 510, 0x11a }, { 540, 0x11b }, { 560, 0x11c },
+ { 580, 0x11d }, { 600, 0x11e }, { 620, 0x11f }, { 640, 0x120 },
+ { 660, 0x121 }, { 680, 0x122 }, { 700, 0x123 }, { 720, 0x124 },
+ { 740, 0x125 }, { 760, 0x126 }, { 790, 0x127 }, { 810, 0x128 },
+ { 830, 0x129 }, { 850, 0x12a }, { 870, 0x12b }, { 890, 0x12c },
+ { 910, 0x12d }, { 930, 0x12e }, { 950, 0x12f }, { 970, 0x130 },
+ { 990, 0x131 }, { 1010, 0x132 }, { 1030, 0x133 }, { 1060, 0x134 },
+ { 1080, 0x135 }, { 1100, 0x136 }, { 1120, 0x137 }, { 1140, 0x138 },
+ { 1160, 0x139 }, { 1180, 0x13a }, { 1200, 0x13b }, { 1220, 0x13c },
+ { 1240, 0x13d }, { 1260, 0x13e }, { 1280, 0x13f }, { 1310, 0x140 },
+ { 1330, 0x141 }, { 1350, 0x142 }, { 1370, 0x143 }, { 1390, 0x144 },
+ { 1410, 0x145 }, { 1430, 0x146 }, { 1450, 0x147 }, { 1470, 0x148 },
+ { 1490, 0x149 }, { 1580, 0x007 }, { 1740, 0x008 }, { 1910, 0x009 },
+ { 2070, 0x00a }, { 2240, 0x00b }, { 2410, 0x00c }, { 2570, 0x00d },
+ { 2740, 0x00e }, { 2910, 0x00f }, { 3070, 0x010 }, { 3240, 0x011 },
+ { 3410, 0x012 }, { 3570, 0x013 }, { 3740, 0x014 }, { 3890, 0x015 },
+ { 4070, 0x016 }, { 4240, 0x017 }, { 4400, 0x018 }, { 4500, 0x019 },
+};
+
+static int samsung_mipi_dphy_rx_lookup_hsfreq(u32 lane_mbps, u16 *cfg_bit)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(samsung_mipi_dphy_rx_hsfreq_ranges); i++) {
+ if (lane_mbps <= samsung_mipi_dphy_rx_hsfreq_ranges[i].range_h_mbps) {
+ *cfg_bit = samsung_mipi_dphy_rx_hsfreq_ranges[i].cfg_bit;
+ return 0;
+ }
+ }
+
+ return -ERANGE;
+}
+
+/* SKEW_DLYSEL per data rate, from the RK3588 TRM ANA_CON2 description */
+static u32 samsung_mipi_dphy_rx_data_lane_dlysel(u32 lane_mbps)
+{
+ if (lane_mbps < 1500)
+ return 0;
+ if (lane_mbps < 2000)
+ return SKEW_DLYSEL(3);
+ if (lane_mbps < 3000)
+ return SKEW_DLYSEL(2);
+ if (lane_mbps < 4000)
+ return SKEW_DLYSEL(1);
+ return 0;
+}
+
+/*
+ * Per-data-lane register bases, indexed by lane number. The clock lane
+ * shares the same block spacing but is programmed separately: its
+ * TIME_CON0 holds T_CLK_SETTLE/T_CLK_MISS rather than the per-rate
+ * T_HS_SETTLE/SETTLE_CLK_SEL, and it has no deskew configuration.
+ */
+static const u32 rx_data_lane_gnr_con0[] = {
+ COMBO_SD0_GNR_CON0, COMBO_SD1_GNR_CON0,
+ COMBO_SD2_GNR_CON0, DPHY_SD3_GNR_CON0,
+};
+
+static const u32 rx_lane_time_con0[] = {
+ COMBO_SD0_TIME_CON0, COMBO_SD1_TIME_CON0,
+ COMBO_SD2_TIME_CON0, DPHY_SD3_TIME_CON0,
+};
+
+static const u32 rx_lane_time_con1[] = {
+ COMBO_SD0_TIME_CON1, COMBO_SD1_TIME_CON1,
+ COMBO_SD2_TIME_CON1, DPHY_SD3_TIME_CON1,
+};
+
+/*
+ * These RX analog tuning values come from the vendor BSP. The
+ * termination (RX_TERM_SW/HS_TERM_SW = 2) is the 96.6 ohm setting the
+ * TRM ANA_CON2 value list annotates as the default, although the
+ * register itself resets to 102 ohm. Data lane 0 differs on purpose:
+ * the vendor's default receive profile applies the LP hysteresis and
+ * the inverted escape clock polarity to lane 0 only, and a coarse
+ * skew-calibration clock of 0 there against 3 on the other lanes.
+ * Data lane 3 is a plain D-PHY lane with no ANA_CON7, so it gets no
+ * CLK_DBL_CTRL write.
+ */
+static void samsung_mipi_dphy_rx_config_common(struct samsung_mipi_dcphy *samsung)
+{
+ struct samsung_mipi_phy *rx = &samsung->phys[SAMSUNG_MIPI_RX];
+ u32 dlysel = samsung_mipi_dphy_rx_data_lane_dlysel(div_u64(rx->hs_clk_rate,
+ 1000000));
+ u32 ana_con2_common = dlysel | RX_TERM_SW(2);
+
+ /* Clock lane */
+ regmap_write(samsung->regmap, DPHY_SC_GNR_CON1, T_PHY_READY(0x1450));
+ regmap_write(samsung->regmap, DPHY_SC_ANA_CON1, HS_RX_BIAS_CON(0x10));
+ regmap_write(samsung->regmap, DPHY_SC_ANA_CON2, HS_TERM_SW(2));
+ regmap_write(samsung->regmap, DPHY_SC_ANA_CON3, ULPS_HYS_SW_DPHY(6));
+
+ /* Data lane 0; a zero lane count is rejected at power-on */
+ regmap_write(samsung->regmap, COMBO_SD0_GNR_CON1, T_PHY_READY(0x1450));
+ regmap_write(samsung->regmap, COMBO_SD0_ANA_CON1, HS_RX_BIAS_CON(0x10));
+ regmap_write(samsung->regmap, COMBO_SD0_ANA_CON2, ana_con2_common);
+ regmap_write(samsung->regmap, COMBO_SD0_ANA_CON3,
+ ULPS_HYS_SW_DPHY(6) | LP_HYS_SW(3) | SEL_ESCPOL);
+ regmap_write(samsung->regmap, COMBO_SD0_ANA_CON7, CLK_DBL_CTRL(1));
+ regmap_write(samsung->regmap, COMBO_SD0_DESKEW_CON2, SKEW_CAL_CLK_COARSE_SET(0));
+
+ /* Data lane 1 */
+ if (rx->lanes > 1) {
+ regmap_write(samsung->regmap, COMBO_SD1_GNR_CON1, T_PHY_READY(0x1450));
+ regmap_write(samsung->regmap, COMBO_SD1_ANA_CON1, HS_RX_BIAS_CON(0x10));
+ regmap_write(samsung->regmap, COMBO_SD1_ANA_CON2, ana_con2_common);
+ regmap_write(samsung->regmap, COMBO_SD1_ANA_CON3, ULPS_HYS_SW_DPHY(6));
+ regmap_write(samsung->regmap, COMBO_SD1_ANA_CON7, CLK_DBL_CTRL(1));
+ regmap_write(samsung->regmap, COMBO_SD1_DESKEW_CON2, SKEW_CAL_CLK_COARSE_SET(3));
+ }
+
+ /* Data lane 2 */
+ if (rx->lanes > 2) {
+ regmap_write(samsung->regmap, COMBO_SD2_GNR_CON1, T_PHY_READY(0x1450));
+ regmap_write(samsung->regmap, COMBO_SD2_ANA_CON1, HS_RX_BIAS_CON(0x10));
+ regmap_write(samsung->regmap, COMBO_SD2_ANA_CON2, ana_con2_common);
+ regmap_write(samsung->regmap, COMBO_SD2_ANA_CON3, ULPS_HYS_SW_DPHY(6));
+ regmap_write(samsung->regmap, COMBO_SD2_ANA_CON7, CLK_DBL_CTRL(1));
+ regmap_write(samsung->regmap, COMBO_SD2_DESKEW_CON2, SKEW_CAL_CLK_COARSE_SET(3));
+ }
+
+ /* Data lane 3 */
+ if (rx->lanes > 3) {
+ regmap_write(samsung->regmap, DPHY_SD3_GNR_CON1, T_PHY_READY(0x1450));
+ regmap_write(samsung->regmap, DPHY_SD3_ANA_CON1, HS_RX_BIAS_CON(0x10));
+ regmap_write(samsung->regmap, DPHY_SD3_ANA_CON2, ana_con2_common);
+ regmap_write(samsung->regmap, DPHY_SD3_ANA_CON3, ULPS_HYS_SW_DPHY(6));
+ regmap_write(samsung->regmap, DPHY_SD3_DESKEW_CON2, SKEW_CAL_CLK_COARSE_SET(3));
+ }
+}
+
+static int samsung_mipi_dphy_rx_config_settle(struct samsung_mipi_dcphy *samsung)
+{
+ struct samsung_mipi_phy *rx = &samsung->phys[SAMSUNG_MIPI_RX];
+ u32 lane_mbps = div_u64(rx->hs_clk_rate, 1000000);
+ unsigned int i;
+ u16 cfg_bit;
+ int ret;
+
+ ret = samsung_mipi_dphy_rx_lookup_hsfreq(lane_mbps, &cfg_bit);
+ if (ret) {
+ dev_err(samsung->dev,
+ "no RX hsfreq cfg for %u Mbps (limit ~4500 Mbps)\n",
+ lane_mbps);
+ return ret;
+ }
+
+ /*
+ * Clock-lane settle uses the fixed value from the TRM RX bring-up
+ * example, unlike the per-rate data-lane settle below.
+ */
+ regmap_write(samsung->regmap, DPHY_SC_TIME_CON0,
+ T_CLK_SETTLE(0x01) | T_CLK_MISS(0x03));
+
+ for (i = 0; i < rx->lanes; i++) {
+ regmap_update_bits(samsung->regmap, rx_lane_time_con0[i],
+ T_HS_SETTLE(0xff) | SETTLE_CLK_SEL, cfg_bit);
+ regmap_update_bits(samsung->regmap, rx_lane_time_con1[i],
+ T_ERR_SOT_SYNC(0xff), T_ERR_SOT_SYNC(0x03));
+ }
+
+ return 0;
+}
+
+static void samsung_mipi_dphy_rx_lane_disable(struct samsung_mipi_dcphy *samsung)
+{
+ struct samsung_mipi_phy *rx = &samsung->phys[SAMSUNG_MIPI_RX];
+ unsigned int i;
+
+ regmap_update_bits(samsung->regmap, DPHY_SC_GNR_CON0, PHY_ENABLE, 0);
+ for (i = 0; i < rx->lanes; i++)
+ regmap_update_bits(samsung->regmap, rx_data_lane_gnr_con0[i],
+ PHY_ENABLE, 0);
+}
+
+static int samsung_mipi_dphy_rx_lane_enable(struct samsung_mipi_dcphy *samsung)
+{
+ struct samsung_mipi_phy *rx = &samsung->phys[SAMSUNG_MIPI_RX];
+ unsigned int i;
+ u32 sts;
+ int ret;
+
+ regmap_update_bits(samsung->regmap, DPHY_SC_GNR_CON0, PHY_ENABLE, PHY_ENABLE);
+ for (i = 0; i < rx->lanes; i++)
+ regmap_update_bits(samsung->regmap, rx_data_lane_gnr_con0[i],
+ PHY_ENABLE, PHY_ENABLE);
+
+ ret = regmap_read_poll_timeout(samsung->regmap, DPHY_SC_GNR_CON0,
+ sts, sts & PHY_READY, 200, 4000);
+ if (ret) {
+ dev_err(samsung->dev, "RX clock lane not ready\n");
+ goto err_lane_disable;
+ }
+
+ for (i = 0; i < rx->lanes; i++) {
+ ret = regmap_read_poll_timeout(samsung->regmap, rx_data_lane_gnr_con0[i],
+ sts, sts & PHY_READY, 200, 2000);
+ if (ret) {
+ dev_err(samsung->dev, "RX data lane %u not ready\n", i);
+ goto err_lane_disable;
+ }
+ }
+
+ return 0;
+
+err_lane_disable:
+ samsung_mipi_dphy_rx_lane_disable(samsung);
+ return ret;
+}
+
/*
* The APB reset is block-level and has to be assumed to return the whole
* register file to the defaults of TRM section 22.4.2, which would leave
@@ -1047,6 +1337,55 @@ static void samsung_mipi_dcphy_common_put(struct samsung_mipi_dcphy *samsung)
samsung->common_users--;
}
+static int samsung_mipi_dphy_rx_power_on(struct samsung_mipi_dcphy *samsung)
+{
+ struct samsung_mipi_phy *rx = &samsung->phys[SAMSUNG_MIPI_RX];
+ int ret;
+
+ if (!rx->hs_clk_rate || !rx->lanes)
+ return -EINVAL;
+
+ samsung_mipi_dcphy_common_get(samsung);
+
+ reset_control_assert(samsung->s_phy_rst);
+
+ samsung_mipi_dphy_rx_config_common(samsung);
+
+ ret = samsung_mipi_dphy_rx_config_settle(samsung);
+ if (ret)
+ goto out_deassert;
+
+ ret = samsung_mipi_dphy_rx_lane_enable(samsung);
+
+out_deassert:
+ reset_control_deassert(samsung->s_phy_rst);
+
+ if (ret)
+ samsung_mipi_dcphy_common_put(samsung);
+
+ return ret;
+}
+
+static int samsung_mipi_dphy_rx_power_off(struct samsung_mipi_dcphy *samsung)
+{
+ reset_control_assert(samsung->s_phy_rst);
+
+ samsung_mipi_dphy_rx_lane_disable(samsung);
+
+ reset_control_deassert(samsung->s_phy_rst);
+
+ /*
+ * Let the lanes settle out of reset before the block may be reset
+ * again by the next bring-up. The delay follows the vendor driver;
+ * the TRM does not document a teardown sequence.
+ */
+ usleep_range(500, 1000);
+
+ samsung_mipi_dcphy_common_put(samsung);
+
+ return 0;
+}
+
static void samsung_mipi_dphy_lane_enable(struct samsung_mipi_dcphy *samsung)
{
regmap_write(samsung->regmap, DPHY_MC_GNR_CON1, T_PHY_READY(0x2000));
@@ -1444,7 +1783,7 @@ static int samsung_mipi_dcphy_power_on(struct phy *phy)
mutex_lock(&samsung->lock);
if (samsung_phy->id == SAMSUNG_MIPI_RX)
- ret = -EOPNOTSUPP;
+ ret = samsung_mipi_dphy_rx_power_on(samsung);
else
ret = samsung_mipi_dphy_tx_power_on(samsung);
mutex_unlock(&samsung->lock);
@@ -1464,7 +1803,7 @@ static int samsung_mipi_dcphy_power_off(struct phy *phy)
mutex_lock(&samsung->lock);
if (samsung_phy->id == SAMSUNG_MIPI_RX)
- ret = -EOPNOTSUPP;
+ ret = samsung_mipi_dphy_rx_power_off(samsung);
else
ret = samsung_mipi_dphy_tx_power_off(samsung);
mutex_unlock(&samsung->lock);
@@ -1566,14 +1905,15 @@ static int samsung_mipi_dcphy_configure(struct phy *phy,
struct samsung_mipi_dcphy *samsung = samsung_phy->parent;
unsigned long long target_rate = opts->mipi_dphy.hs_clk_rate;
- /* The receiver is brought up in a later change. */
- if (samsung_phy->id == SAMSUNG_MIPI_RX)
- return -EOPNOTSUPP;
-
samsung_phy->lanes = opts->mipi_dphy.lanes > 4 ? 4 : opts->mipi_dphy.lanes;
- samsung_mipi_dcphy_pll_calc_rate(samsung, target_rate);
- opts->mipi_dphy.hs_clk_rate = samsung->pll.rate;
+ if (samsung_phy->id == SAMSUNG_MIPI_RX) {
+ /* The sensor supplies the link clock; the PLL stays off. */
+ samsung_phy->hs_clk_rate = target_rate;
+ } else {
+ samsung_mipi_dcphy_pll_calc_rate(samsung, target_rate);
+ opts->mipi_dphy.hs_clk_rate = samsung->pll.rate;
+ }
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-10 12:10 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 12:10 [PATCH v3 0/5] phy: rockchip-samsung-dcphy: add the MIPI D-PHY receiver Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 1/5] dt-bindings: phy: Add PHY_TYPE_DSI and PHY_TYPE_CSI definitions Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 2/5] dt-bindings: phy: rockchip,rk3588-mipi-dcphy: allow DSI and CSI consumers Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 3/5] phy: rockchip-samsung-dcphy: factor MIPI D-PHY power on/off into helpers Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 4/5] phy: rockchip-samsung-dcphy: model TX and RX as separate PHYs Jason Yang via B4 Relay
2026-08-10 12:10 ` [PATCH v3 5/5] phy: rockchip-samsung-dcphy: add MIPI D-PHY receiver support Jason Yang via B4 Relay
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox