@@ -988,6 +1046,228 @@ 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 from the rk3588 vendor kernel. The TRM
+ * defines these fields but does not publish the per-data-rate table
+ * (it defers to a separate timing-table supplement). Each cfg_bit is a
+ * pre-combined SETTLE_CLK_SEL | T_HS_SETTLE value written to the
+ * data-lane TIME_CON0. 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)
+{
+ 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 is
+ * programmed separately; its enable/settle registers do not follow this
+ * regular spacing.
+ */
+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, 96.6 ohm) additionally
+ * matches the TRM ANA_CON2 default.
+ */
+static void samsung_mipi_dphy_rx_config_common(struct samsung_mipi_dcphy *samsung)
+{
+ struct samsung_mipi_dcphy_dir *pd = &samsung->phys[RK_DCPHY_DIR_RX];
+ u32 dlysel = samsung_mipi_dphy_rx_data_lane_dlysel(div_u64(pd->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 */
+ if (pd->lanes > 0) {
+ 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 (pd->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 (pd->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 (pd->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_dcphy_dir *pd = &samsung->phys[RK_DCPHY_DIR_RX];
+ u32 lane_mbps = div_u64(pd->hs_clk_rate, 1000000);
+ u16 cfg_bit;
+ int ret, i;
+
+ 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 < pd->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_dcphy_dir *pd = &samsung->phys[RK_DCPHY_DIR_RX];
+ int i;
+
+ regmap_update_bits(samsung->regmap, DPHY_SC_GNR_CON0, PHY_ENABLE, 0);
+ for (i = 0; i < pd->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_dcphy_dir *pd = &samsung->phys[RK_DCPHY_DIR_RX];
+ u32 sts;
+ int ret, i;
+
+ regmap_update_bits(samsung->regmap, DPHY_SC_GNR_CON0, PHY_ENABLE, PHY_ENABLE);
+ for (i = 0; i < pd->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 < pd->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 %d not ready\n", i);
+ goto err_lane_disable;
+ }
+ }
+
+ return 0;
+
+err_lane_disable:
+ samsung_mipi_dphy_rx_lane_disable(samsung);
+ return ret;
+}
+
static void samsung_mipi_dcphy_bias_block_enable(struct samsung_mipi_dcphy *samsung)
{
regmap_write(samsung->regmap, BIAS_CON0, I_DEV_DIV_6 | I_RES_100_2UA);