media: i2c: ds90ub960: Configure serializer using back-channel

For DS90UB9702-Q1, it is recommended to configure some serializer
settings over the back-channel before the forward-channel is active.

This can only be done if the serializer's I2C address on the FPD-Link
bus is populated in the device tree node.

Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
This commit is contained in:
Jai Luthra 2025-03-03 21:32:21 +05:30 committed by Hans Verkuil
parent b8e2193b1e
commit e2a3b695bc
2 changed files with 126 additions and 4 deletions

View File

@ -92,4 +92,8 @@
#define UB953_IND_PGEN_VFP 0x0f
#define UB953_IND_PGEN_COLOR(n) (0x10 + (n)) /* n <= 15 */
/* UB971 Registers */
#define UB971_ENH_BC_CHK 0x4b
#endif /* __MEDIA_I2C_DS90UB953_H__ */

View File

@ -52,6 +52,8 @@
#include <media/v4l2-fwnode.h>
#include <media/v4l2-subdev.h>
#include "ds90ub953.h"
#define MHZ(v) ((u32)((v) * HZ_PER_MHZ))
/*
@ -244,13 +246,16 @@
#define UB960_RR_BCC_CONFIG 0x58
#define UB960_RR_BCC_CONFIG_BC_ALWAYS_ON BIT(4)
#define UB960_RR_BCC_CONFIG_AUTO_ACK_ALL BIT(5)
#define UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH BIT(6)
#define UB960_RR_BCC_CONFIG_BC_FREQ_SEL_MASK GENMASK(2, 0)
#define UB960_RR_DATAPATH_CTL1 0x59
#define UB960_RR_DATAPATH_CTL2 0x5a
#define UB960_RR_SER_ID 0x5b
#define UB960_RR_SER_ID_FREEZE_DEVICE_ID BIT(0)
#define UB960_RR_SER_ALIAS_ID 0x5c
#define UB960_RR_SER_ALIAS_ID_AUTO_ACK BIT(0)
/* For these two register sets: n < UB960_MAX_PORT_ALIASES */
#define UB960_RR_SLAVE_ID(n) (0x5d + (n))
@ -486,7 +491,9 @@ struct ub960_rxport {
struct fwnode_handle *fwnode;
struct i2c_client *client;
unsigned short alias; /* I2C alias (lower 7 bits) */
short addr; /* Local I2C address (lower 7 bits) */
struct ds90ub9xx_platform_data pdata;
struct regmap *regmap;
} ser;
enum ub960_rxport_mode rx_mode;
@ -1984,6 +1991,78 @@ static unsigned long ub960_calc_bc_clk_rate_ub9702(struct ub960_data *priv,
}
}
static int ub960_rxport_serializer_write(struct ub960_rxport *rxport, u8 reg,
u8 val, int *err)
{
struct ub960_data *priv = rxport->priv;
struct device *dev = &priv->client->dev;
union i2c_smbus_data data;
int ret;
if (err && *err)
return *err;
data.byte = val;
ret = i2c_smbus_xfer(priv->client->adapter, rxport->ser.alias, 0,
I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, &data);
if (ret)
dev_err(dev,
"rx%u: cannot write serializer register 0x%02x (%d)!\n",
rxport->nport, reg, ret);
if (ret && err)
*err = ret;
return ret;
}
static int ub960_rxport_bc_ser_config(struct ub960_rxport *rxport)
{
struct ub960_data *priv = rxport->priv;
struct device *dev = &priv->client->dev;
u8 nport = rxport->nport;
int ret;
/* Skip port if serializer's address is not known */
if (rxport->ser.addr < 0) {
dev_dbg(dev,
"rx%u: serializer address missing, skip configuration\n",
nport);
return 0;
}
/*
* Note: the code here probably only works for CSI-2 serializers in
* sync mode. To support other serializers the BC related configuration
* should be done before calling this function.
*/
/* Enable I2C passthrough and auto-ack on BC */
ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG,
UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH |
UB960_RR_BCC_CONFIG_AUTO_ACK_ALL,
UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH |
UB960_RR_BCC_CONFIG_AUTO_ACK_ALL,
&ret);
if (ret)
return ret;
/* Disable BC alternate mode auto detect */
ub960_rxport_serializer_write(rxport, UB971_ENH_BC_CHK, 0x02, &ret);
/* Decrease link detect timer */
ub960_rxport_serializer_write(rxport, UB953_REG_BC_CTRL, 0x06, &ret);
/* Disable I2C passthrough and auto-ack on BC */
ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG,
UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH |
UB960_RR_BCC_CONFIG_AUTO_ACK_ALL,
0x0, &ret);
return ret;
}
static int ub960_rxport_add_serializer(struct ub960_data *priv, u8 nport)
{
struct ub960_rxport *rxport = priv->rxports[nport];
@ -2860,6 +2939,36 @@ static int ub960_init_rx_ports_ub9702(struct ub960_data *priv)
if (ret)
return ret;
for_each_active_rxport(priv, it) {
if (it.rxport->ser.addr >= 0) {
/*
* Set serializer's I2C address if set in the dts file,
* and freeze it to prevent updates from the FC.
*/
ub960_rxport_write(priv, it.nport, UB960_RR_SER_ID,
it.rxport->ser.addr << 1 |
UB960_RR_SER_ID_FREEZE_DEVICE_ID,
&ret);
}
/* Set serializer I2C alias with auto-ack */
ub960_rxport_write(priv, it.nport, UB960_RR_SER_ALIAS_ID,
it.rxport->ser.alias << 1 |
UB960_RR_SER_ALIAS_ID_AUTO_ACK, &ret);
if (ret)
return ret;
}
for_each_active_rxport(priv, it) {
if (fwnode_device_is_compatible(it.rxport->ser.fwnode,
"ti,ds90ub971-q1")) {
ret = ub960_rxport_bc_ser_config(it.rxport);
if (ret)
return ret;
}
}
for_each_active_rxport_fpd4(priv, it) {
/* Hold state machine in reset */
ub960_rxport_write(priv, it.nport, UB9702_RR_RX_SM_SEL_2, 0x10,
@ -2988,16 +3097,17 @@ static int ub960_init_rx_ports_ub9702(struct ub960_data *priv)
ub960_rxport_write(priv, it.nport, UB960_RR_PORT_ICR_LO, 0x7f,
&ret);
/* Clear serializer I2C alias auto-ack */
ub960_rxport_update_bits(priv, it.nport, UB960_RR_SER_ALIAS_ID,
UB960_RR_SER_ALIAS_ID_AUTO_ACK, 0,
&ret);
/* Enable I2C_PASS_THROUGH */
ub960_rxport_update_bits(priv, it.nport, UB960_RR_BCC_CONFIG,
UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH,
UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH,
&ret);
/* Enable I2C communication to the serializer via the alias */
ub960_rxport_write(priv, it.nport, UB960_RR_SER_ALIAS_ID,
it.rxport->ser.alias << 1, &ret);
if (ret)
return ret;
}
@ -4156,6 +4266,7 @@ ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
s32 strobe_pos;
u32 eq_level;
u32 ser_i2c_alias;
u32 ser_i2c_addr;
int ret;
cdr_mode = RXPORT_CDR_FPD3;
@ -4267,6 +4378,13 @@ ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
return -EINVAL;
}
ret = fwnode_property_read_u32(rxport->ser.fwnode, "reg",
&ser_i2c_addr);
if (ret)
rxport->ser.addr = -EINVAL;
else
rxport->ser.addr = ser_i2c_addr;
return 0;
}