]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
media: i2c: ds90ub960: Add support for DS90UB954-Q1
authorYemike Abhilash Chandra <y-abhilashchandra@ti.com>
Tue, 24 Feb 2026 11:39:24 +0000 (17:09 +0530)
committerMauro Carvalho Chehab <mchehab+huawei@kernel.org>
Wed, 11 Mar 2026 00:05:30 +0000 (01:05 +0100)
DS90UB954-Q1 is an FPDLink-III deserializer that is mostly register
compatible with DS90UB960-Q1. The main difference is that it supports half
of the RX and TX ports, i.e. 2x FPDLink RX ports and 1x CSI TX port.

A couple of differences are between the status registers and the
strobe setting registers. Hence accommodate these differences in
the UB960 driver so that we can reuse a large part of the existing code.

Link: https://www.ti.com/lit/gpn/ds90ub954-q1
Reviewed-by: Jai Luthra <jai.luthra@ideasonboard.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Signed-off-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
drivers/media/i2c/Kconfig
drivers/media/i2c/ds90ub960.c

index 5eb1e0e0a87ab350060567f578960c0e98940d6d..fded4bf826a07622a790358c9c2011b9436ef0cc 100644 (file)
@@ -1724,8 +1724,8 @@ config VIDEO_DS90UB960
        select V4L2_FWNODE
        select VIDEO_V4L2_SUBDEV_API
        help
-         Device driver for the Texas Instruments DS90UB960
-         FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
+         Device driver for the Texas Instruments DS90UB954, DS90UB960
+         FPD-Link III Deserializers and DS90UB9702 FPD-Link IV Deserializer.
 
 config VIDEO_MAX96714
        tristate "Maxim MAX96714 GMSL2 deserializer"
index c5e00ae8ea74a9f99fd2659baf98dc650379884c..d50e977cf6ce4c2ff0e2a5ad837fce1cb28266b8 100644 (file)
 #define UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY BIT(3)
 #define UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK     GENMASK(2, 0)
 
+#define UB954_IR_RX_ANA_STROBE_SET_CLK_DATA            0x08
+#define UB954_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY  BIT(3)
+#define UB954_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY BIT(7)
+#define UB954_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK      GENMASK(2, 0)
+#define UB954_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK     GENMASK(4, 6)
+#define UB954_IR_RX_ANA_STROBE_SET_DATA_DELAY_SHIFT    4
+
 /* UB9702 Registers */
 
 #define UB9702_SR_CSI_EXCLUSIVE_FWD2           0x3c
 #define UB960_NUM_EQ_LEVELS (UB960_MAX_EQ_LEVEL - UB960_MIN_EQ_LEVEL + 1)
 
 enum chip_type {
+       UB954,
        UB960,
        UB9702,
 };
@@ -992,6 +1000,10 @@ static int ub960_txport_select(struct ub960_data *priv, u8 nport)
 
        lockdep_assert_held(&priv->reg_lock);
 
+       /* UB954 has only 1 CSI TX. Hence, no need to select */
+       if (priv->hw_data->chip_type == UB954)
+               return 0;
+
        if (priv->reg_current.txport == nport)
                return 0;
 
@@ -1416,10 +1428,11 @@ static int ub960_parse_dt_txport(struct ub960_data *priv,
        priv->tx_link_freq[0] = vep.link_frequencies[0];
        priv->tx_data_rate = priv->tx_link_freq[0] * 2;
 
-       if (priv->tx_data_rate != MHZ(1600) &&
-           priv->tx_data_rate != MHZ(1200) &&
-           priv->tx_data_rate != MHZ(800) &&
-           priv->tx_data_rate != MHZ(400)) {
+       if ((priv->tx_data_rate != MHZ(1600) &&
+            priv->tx_data_rate != MHZ(1200) &&
+            priv->tx_data_rate != MHZ(800) &&
+            priv->tx_data_rate != MHZ(400)) ||
+            (priv->hw_data->chip_type == UB954 && priv->tx_data_rate == MHZ(1200))) {
                dev_err(dev, "tx%u: invalid 'link-frequencies' value\n", nport);
                ret = -EINVAL;
                goto err_free_vep;
@@ -1543,22 +1556,35 @@ static int ub960_rxport_get_strobe_pos(struct ub960_data *priv,
        u8 clk_delay, data_delay;
        int ret;
 
-       ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
-                            UB960_IR_RX_ANA_STROBE_SET_CLK, &v, NULL);
-       if (ret)
-               return ret;
+       if (priv->hw_data->chip_type == UB954) {
+               ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+                                    UB954_IR_RX_ANA_STROBE_SET_CLK_DATA, &v, NULL);
+               if (ret)
+                       return ret;
 
-       clk_delay = (v & UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY) ?
-                           0 : UB960_MANUAL_STROBE_EXTRA_DELAY;
+               clk_delay = (v & UB954_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY) ?
+                            0 : UB960_MANUAL_STROBE_EXTRA_DELAY;
 
-       ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
-                            UB960_IR_RX_ANA_STROBE_SET_DATA, &v, NULL);
-       if (ret)
-               return ret;
+               data_delay = (v & UB954_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY) ?
+                             0 : UB960_MANUAL_STROBE_EXTRA_DELAY;
+       } else {
+               ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+                                    UB960_IR_RX_ANA_STROBE_SET_CLK, &v, NULL);
+               if (ret)
+                       return ret;
 
-       data_delay = (v & UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY) ?
+               clk_delay = (v & UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY) ?
                             0 : UB960_MANUAL_STROBE_EXTRA_DELAY;
 
+               ret = ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+                                    UB960_IR_RX_ANA_STROBE_SET_DATA, &v, NULL);
+               if (ret)
+                       return ret;
+
+               data_delay = (v & UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY) ?
+                             0 : UB960_MANUAL_STROBE_EXTRA_DELAY;
+       }
+
        ret = ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_0, &v, NULL);
        if (ret)
                return ret;
@@ -1579,26 +1605,49 @@ static int ub960_rxport_get_strobe_pos(struct ub960_data *priv,
 static int ub960_rxport_set_strobe_pos(struct ub960_data *priv,
                                       unsigned int nport, s8 strobe_pos)
 {
-       u8 clk_delay, data_delay;
        int ret = 0;
 
-       clk_delay = UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
-       data_delay = UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
-
-       if (strobe_pos < UB960_MIN_AEQ_STROBE_POS)
-               clk_delay = abs(strobe_pos) - UB960_MANUAL_STROBE_EXTRA_DELAY;
-       else if (strobe_pos > UB960_MAX_AEQ_STROBE_POS)
-               data_delay = strobe_pos - UB960_MANUAL_STROBE_EXTRA_DELAY;
-       else if (strobe_pos < 0)
-               clk_delay = abs(strobe_pos) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
-       else if (strobe_pos > 0)
-               data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
-
-       ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
-                       UB960_IR_RX_ANA_STROBE_SET_CLK, clk_delay, &ret);
-
-       ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
-                       UB960_IR_RX_ANA_STROBE_SET_DATA, data_delay, &ret);
+       if (priv->hw_data->chip_type == UB954) {
+               u8 clk_data_delay;
+
+               clk_data_delay = UB954_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY |
+                                UB954_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
+
+               if (strobe_pos < UB960_MIN_AEQ_STROBE_POS)
+                       clk_data_delay = abs(strobe_pos) - UB960_MANUAL_STROBE_EXTRA_DELAY;
+               else if (strobe_pos > UB960_MAX_AEQ_STROBE_POS)
+                       clk_data_delay = (strobe_pos - UB960_MANUAL_STROBE_EXTRA_DELAY) <<
+                                         UB954_IR_RX_ANA_STROBE_SET_DATA_DELAY_SHIFT;
+               else if (strobe_pos < 0)
+                       clk_data_delay = abs(strobe_pos) |
+                                        UB954_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
+               else if (strobe_pos > 0)
+                       clk_data_delay = (strobe_pos |
+                                         UB954_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY) <<
+                                         UB954_IR_RX_ANA_STROBE_SET_DATA_DELAY_SHIFT;
+
+               ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+                               UB954_IR_RX_ANA_STROBE_SET_CLK_DATA, clk_data_delay, &ret);
+       } else {
+               u8 clk_delay, data_delay;
+
+               clk_delay = UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
+               data_delay = UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
+
+               if (strobe_pos < UB960_MIN_AEQ_STROBE_POS)
+                       clk_delay = abs(strobe_pos) - UB960_MANUAL_STROBE_EXTRA_DELAY;
+               else if (strobe_pos > UB960_MAX_AEQ_STROBE_POS)
+                       data_delay = strobe_pos - UB960_MANUAL_STROBE_EXTRA_DELAY;
+               else if (strobe_pos < 0)
+                       clk_delay = abs(strobe_pos) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
+               else if (strobe_pos > 0)
+                       data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
+
+               ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+                               UB960_IR_RX_ANA_STROBE_SET_CLK, clk_delay, &ret);
+               ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+                               UB960_IR_RX_ANA_STROBE_SET_DATA, data_delay, &ret);
+       }
 
        return ret;
 }
@@ -3634,7 +3683,8 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
 
                case RXPORT_MODE_CSI2_SYNC:
                case RXPORT_MODE_CSI2_NONSYNC:
-                       if (priv->hw_data->chip_type == UB960) {
+                       if (priv->hw_data->chip_type == UB960 ||
+                           priv->hw_data->chip_type == UB954) {
                                /* Map all VCs from this port to the same VC */
                                ub960_rxport_write(priv, nport, UB960_RR_CSI_VC_MAP,
                                                   (vc << UB960_RR_CSI_VC_MAP_SHIFT(3)) |
@@ -4168,33 +4218,40 @@ static int ub960_log_status(struct v4l2_subdev *sd)
                dev_info(dev, "\tsync %u, pass %u\n", v & (u8)BIT(1),
                         v & (u8)BIT(0));
 
-               ret = ub960_read16(priv, UB960_SR_CSI_FRAME_COUNT_HI(nport),
-                                  &v16, NULL);
-               if (ret)
-                       return ret;
+               /*
+                * Frame counter, frame error counter, line counter and line error counter
+                * registers are marked as reserved in the UB954 datasheet. Hence restrict
+                * the following register reads only for UB960 and UB9702.
+                */
+               if (priv->hw_data->chip_type == UB960 || priv->hw_data->chip_type == UB9702) {
+                       ret = ub960_read16(priv, UB960_SR_CSI_FRAME_COUNT_HI(nport),
+                                          &v16, NULL);
+                       if (ret)
+                               return ret;
 
-               dev_info(dev, "\tframe counter %u\n", v16);
+                       dev_info(dev, "\tframe counter %u\n", v16);
 
-               ret = ub960_read16(priv, UB960_SR_CSI_FRAME_ERR_COUNT_HI(nport),
-                                  &v16, NULL);
-               if (ret)
-                       return ret;
+                       ret = ub960_read16(priv, UB960_SR_CSI_FRAME_ERR_COUNT_HI(nport),
+                                          &v16, NULL);
+                       if (ret)
+                               return ret;
 
-               dev_info(dev, "\tframe error counter %u\n", v16);
+                       dev_info(dev, "\tframe error counter %u\n", v16);
 
-               ret = ub960_read16(priv, UB960_SR_CSI_LINE_COUNT_HI(nport),
-                                  &v16, NULL);
-               if (ret)
-                       return ret;
+                       ret = ub960_read16(priv, UB960_SR_CSI_LINE_COUNT_HI(nport),
+                                          &v16, NULL);
+                       if (ret)
+                               return ret;
 
-               dev_info(dev, "\tline counter %u\n", v16);
+                       dev_info(dev, "\tline counter %u\n", v16);
 
-               ret = ub960_read16(priv, UB960_SR_CSI_LINE_ERR_COUNT_HI(nport),
-                                  &v16, NULL);
-               if (ret)
-                       return ret;
+                       ret = ub960_read16(priv, UB960_SR_CSI_LINE_ERR_COUNT_HI(nport),
+                                          &v16, NULL);
+                       if (ret)
+                               return ret;
 
-               dev_info(dev, "\tline error counter %u\n", v16);
+                       dev_info(dev, "\tline error counter %u\n", v16);
+               }
        }
 
        for_each_rxport(priv, it) {
@@ -4260,7 +4317,7 @@ static int ub960_log_status(struct v4l2_subdev *sd)
 
                dev_info(dev, "\tcsi_err_counter %u\n", v);
 
-               if (priv->hw_data->chip_type == UB960) {
+               if (priv->hw_data->chip_type == UB960 || priv->hw_data->chip_type == UB954) {
                        ret = ub960_log_status_ub960_sp_eq(priv, nport);
                        if (ret)
                                return ret;
@@ -5020,6 +5077,11 @@ static int ub960_enable_core_hw(struct ub960_data *priv)
        if (ret)
                goto err_pd_gpio;
 
+       /*
+        * UB954 REFCLK_FREQ is not synchronized, so multiple reads are recommended
+        * by the datasheet. However, a single read is practically seen to be
+        * sufficient and moreover it is only used for a debug print.
+        */
        if (priv->hw_data->chip_type == UB9702)
                ret = ub960_read(priv, UB9702_SR_REFCLK_FREQ, &refclk_freq,
                                 NULL);
@@ -5179,6 +5241,14 @@ static void ub960_remove(struct i2c_client *client)
        mutex_destroy(&priv->reg_lock);
 }
 
+static const struct ub960_hw_data ds90ub954_hw = {
+       .model = "ub954",
+       .chip_type = UB954,
+       .chip_family = FAMILY_FPD3,
+       .num_rxports = 2,
+       .num_txports = 1,
+};
+
 static const struct ub960_hw_data ds90ub960_hw = {
        .model = "ub960",
        .chip_type = UB960,
@@ -5196,6 +5266,7 @@ static const struct ub960_hw_data ds90ub9702_hw = {
 };
 
 static const struct i2c_device_id ub960_id[] = {
+       { "ds90ub954-q1", (kernel_ulong_t)&ds90ub954_hw },
        { "ds90ub960-q1", (kernel_ulong_t)&ds90ub960_hw },
        { "ds90ub9702-q1", (kernel_ulong_t)&ds90ub9702_hw },
        {}
@@ -5203,6 +5274,7 @@ static const struct i2c_device_id ub960_id[] = {
 MODULE_DEVICE_TABLE(i2c, ub960_id);
 
 static const struct of_device_id ub960_dt_ids[] = {
+       { .compatible = "ti,ds90ub954-q1", .data = &ds90ub954_hw },
        { .compatible = "ti,ds90ub960-q1", .data = &ds90ub960_hw },
        { .compatible = "ti,ds90ub9702-q1", .data = &ds90ub9702_hw },
        {}