From: Yemike Abhilash Chandra Date: Tue, 24 Feb 2026 11:39:24 +0000 (+0530) Subject: media: i2c: ds90ub960: Add support for DS90UB954-Q1 X-Git-Tag: v7.1-rc1~169^2~238 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0707307f039813eed83e9e8d6cd70c02a6e86158;p=thirdparty%2Fkernel%2Flinux.git media: i2c: ds90ub960: Add support for DS90UB954-Q1 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 Reviewed-by: Tomi Valkeinen Signed-off-by: Yemike Abhilash Chandra Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 5eb1e0e0a87ab..fded4bf826a07 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -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" diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c index c5e00ae8ea74a..d50e977cf6ce4 100644 --- a/drivers/media/i2c/ds90ub960.c +++ b/drivers/media/i2c/ds90ub960.c @@ -396,6 +396,13 @@ #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 @@ -455,6 +462,7 @@ #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 }, {}