This adds support for starting/stopping streaming from connected sensors as well. The user can also switch over to testing the test pattern by configuring the routes accordingly. Use the 'maxim,operation-mode' DT setting to allow the user to select which operation mode the deserializer should run in, though only tunneling mode is supported currently. Signed-off-by: Laurentiu Palcu <laurentiu.palcu@xxxxxxxxxxx> --- drivers/staging/media/max96712/max96712.c | 426 +++++++++++++++++++++- 1 file changed, 418 insertions(+), 8 deletions(-) diff --git a/drivers/staging/media/max96712/max96712.c b/drivers/staging/media/max96712/max96712.c index 0112052171b06..b4c3d1d3c9539 100644 --- a/drivers/staging/media/max96712/max96712.c +++ b/drivers/staging/media/max96712/max96712.c @@ -4,6 +4,7 @@ * * Copyright (C) 2021 Renesas Electronics Corporation * Copyright (C) 2021 Niklas Söderlund + * Copyright 2025 NXP */ #include <linux/delay.h> @@ -30,6 +31,12 @@ #define DIS_REM_CC_C_SHIFT 4 #define DIS_REM_CC_D_MASK GENMASK(7, 6) #define DIS_REM_CC_D_SHIFT 6 +#define MAX96712_DEV_CTRL12 CCI_REG8(0x000a) +#define LOCKED_B BIT(3) +#define MAX96712_DEV_CTRL13 CCI_REG8(0x000b) +#define LOCKED_C BIT(3) +#define MAX96712_DEV_CTRL14 CCI_REG8(0x000c) +#define LOCKED_D BIT(3) /* TOP_CTRL */ #define MAX96712_DEBUG_EXTRA_REG CCI_REG8(0x0009) @@ -37,6 +44,11 @@ #define DEBUG_EXTRA_PCLK_75MHZ 0x01 #define MAX96724_TOP_CTRL_PWR1 CCI_REG8(0x0013) #define RESET_ALL BIT(6) +#define MAX96712_TOP_CTRL_CTRL3 CCI_REG8(0x001a) +#define LOCK_PIN BIT(0) +#define CMU_LOCKED BIT(1) +#define ERROR BIT(2) +#define LOCKED_A BIT(3) /* BACKTOP0 */ #define MAX96712_BACKTOP0_12 CCI_REG8(0x040b) @@ -110,6 +122,15 @@ #define T_T3_PREP_86_7NS 3 #define T_T3_POST_MASK GENMASK(6, 2) #define T_T3_POST_SHIFT 2 +#define MAX96712_MIPI_PHY_MIPI_CTRL_SEL CCI_REG8(0x08ca) +#define MIPI_CTRL_SEL_0_MASK GENMASK(1, 0) +#define MIPI_CTRL_SEL_0_SHIFT 0 +#define MIPI_CTRL_SEL_1_MASK GENMASK(3, 2) +#define MIPI_CTRL_SEL_1_SHIFT 2 +#define MIPI_CTRL_SEL_2_MASK GENMASK(5, 4) +#define MIPI_CTRL_SEL_2_SHIFT 4 +#define MIPI_CTRL_SEL_3_MASK GENMASK(7, 6) +#define MIPI_CTRL_SEL_3_SHIFT 6 /* MIPI_TX: 0 <= phy < 4 */ #define MAX96712_MIPI_TX_DESKEW_INIT(phy) CCI_REG8(0x0903 + (phy) * 0x40) @@ -123,6 +144,22 @@ #define CSI2_CPHY_EN BIT(5) #define CSI2_LANE_CNT_MASK GENMASK(7, 6) #define CSI2_LANE_CNT_SHIFT 6 +#define MAX96712_MIPI_TX_54(phy) CCI_REG8(0x0936 + (phy) * 0x40) +#define TUN_EN BIT(0) +#define DESKEW_TUN_SRC_MASK GENMASK(2, 1) +#define DESKEW_TUN_SRC_SHIFT 1 +#define TUN_SER_LANE_NUM_MASK GENMASK(4, 3) +#define TUN_SER_LANE_NUM_SHIFT 3 +#define DESKEW_TUN_MASK GENMASK(6, 5) +#define DESKEW_TUN_SHIFT 5 +#define TUN_NO_CORR BIT(7) +#define MAX96712_MIPI_TX_57(phy) CCI_REG8(0x0939 + (phy) * 0x40) +#define TUN_DPHY_TO_CPHY_CONV_OVRD BIT(1) +#define TUN_DPHY_TO_CPHY_CONV BIT(2) +#define TUN_DEST_MASK GENMASK(5, 4) +#define TUN_DEST_SHIFT 4 +#define DIS_AUTO_TUN_DET BIT(6) +#define DIS_AUTO_SER_LANE_DET BIT(7) /* GPIO_A: 0 <= gpio < 11 */ #define MAX96712_GPIO_A_A(gpio) CCI_REG8(0x0300 + (gpio) * 0x03) @@ -241,6 +278,16 @@ struct max96712_rx_port { struct fwnode_handle *fwnode; }; +struct max96712_asc { + struct v4l2_async_connection base; + struct max96712_rx_port *rx_port; +}; + +enum max96712_operation_mode { + MAX96712_TUNNEL_MODE, + MAX96712_PIXEL_MODE, +}; + struct max96712_priv { struct i2c_client *client; struct regmap *regmap; @@ -253,6 +300,8 @@ struct max96712_priv { const struct max96712_info *info; + enum max96712_operation_mode operation_mode; + bool cphy; struct v4l2_mbus_config_mipi_csi2 mipi; s64 link_freq; @@ -260,12 +309,15 @@ struct max96712_priv { struct v4l2_subdev sd; struct v4l2_ctrl_handler ctrl_handler; struct media_pad pads[MAX96712_MAX_PORTS]; + struct v4l2_async_notifier notifier; + u32 enabled_streams; struct max96712_rx_port rx_ports[MAX96712_MAX_RX_PORTS]; unsigned int rx_port_mask; unsigned int n_rx_ports; enum max96712_pattern pattern; + bool vpg_started; struct max96712_fsync fsync; struct v4l2_fract interval; @@ -382,6 +434,17 @@ static inline bool max96712_pad_is_source(u32 pad) return pad >= MAX96712_FIRST_SOURCE_PAD && pad < MAX96712_VPG_PAD; } +static int max96712_read(struct max96712_priv *priv, unsigned int reg, u64 *val) +{ + int ret; + + ret = cci_read(priv->regmap, reg, val, NULL); + if (ret) + dev_err(&priv->client->dev, "read 0x%04x failed\n", reg); + + return ret; +} + static int max96712_write(struct max96712_priv *priv, unsigned int reg, u64 val) { int ret; @@ -422,6 +485,14 @@ static void max96712_mipi_enable(struct max96712_priv *priv, bool enable) } } +static void max96712_tunneling_enable(struct max96712_priv *priv, bool enable) +{ + int i; + + for (i = 0; i < 4; i++) + max96712_update_bits(priv, MAX96712_MIPI_TX_54(i), TUN_EN, enable ? TUN_EN : 0); +} + static void max96712_mipi_configure(struct max96712_priv *priv) { unsigned int i; @@ -485,14 +556,26 @@ static void max96712_mipi_configure(struct max96712_priv *priv) PERIODIC_DESKEW_CALIBRATION_EN, auto_deskew_calib_en); } + if (priv->operation_mode == MAX96712_TUNNEL_MODE) { + int i; + /* + * Disable tunnel auto-detection for all phys, will enable tunnelling + * explicitly when needed. + */ + for (i = 0; i < 4; i++) + max96712_update_bits(priv, MAX96712_MIPI_TX_57(i), + DIS_AUTO_TUN_DET, DIS_AUTO_TUN_DET); + } + /* Enable PHY0 and PHY1 */ max96712_update_bits(priv, MAX96712_MIPI_PHY_2, PHY_STDBY_N_MASK, PHY0_EN | PHY1_EN); } -static void max96712_pattern_enable(struct max96712_priv *priv, struct v4l2_subdev_state *state, - bool enable) +static int max96712_pattern_enable(struct max96712_priv *priv, struct v4l2_subdev_state *state, + bool enable) { struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(state, MAX96712_VPG_PAD); + struct device *dev = &priv->client->dev; const u32 h_active = fmt->width; const u32 h_fp = 88; @@ -506,9 +589,16 @@ static void max96712_pattern_enable(struct max96712_priv *priv, struct v4l2_subd const u32 v_bp = 36; const u32 v_tot = v_active + v_fp + v_sw + v_bp; + priv->vpg_started = enable; + if (!enable) { max96712_write(priv, MAX96712_VRX_PATGEN_1, 0x00); - return; + return 0; + } + + if (priv->enabled_streams) { + dev_err(dev, "Cannot enable VPG when other streams are enabled.\n"); + return -EINVAL; } max96712_write(priv, MAX96712_DEBUG_EXTRA_REG, DEBUG_EXTRA_PCLK_75MHZ); @@ -553,14 +643,44 @@ static void max96712_pattern_enable(struct max96712_priv *priv, struct v4l2_subd /* Generate gradient pattern. */ max96712_write(priv, MAX96712_VRX_PATGEN_1, PATGEN_MODE_GRADIENT); } + + return 0; +} + +static u8 max96712_get_link_status(struct max96712_priv *priv) +{ + u32 link_lock_addr[4] = { + MAX96712_TOP_CTRL_CTRL3, + MAX96712_DEV_CTRL12, + MAX96712_DEV_CTRL13, + MAX96712_DEV_CTRL14 + }; + int nport; + u8 link_status_mask = 0; + + for (nport = 0; nport < MAX96712_MAX_RX_PORTS; nport++) { + u64 reg_val = 0; + + max96712_read(priv, link_lock_addr[nport], ®_val); + + link_status_mask |= reg_val & BIT(3) ? (1 << nport) : 0; + } + + return link_status_mask; } -static int __maybe_unused max96712_fsync_set(struct max96712_priv *priv) +static int max96712_fsync_enable(struct max96712_priv *priv, bool enable) { u32 fsync; int ret; u8 mode_map[4] = {3, 0, 1, 2}; + if (!enable) { + max96712_update_bits(priv, MAX96712_FSYNC_0, + FSYNC_MODE_MASK, 0x3 << FSYNC_MODE_SHIFT); + return 0; + } + if (priv->fsync.mode == MAX96712_FSYNC_OFF) return 0; @@ -739,14 +859,117 @@ static int max96712_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, return ret; } +static struct v4l2_subdev *max96712_xlate_streams(struct max96712_priv *priv, + struct v4l2_subdev_state *state, u32 src_pad, + u64 src_streams, u32 sink_pad, u64 *sink_streams, + u32 *remote_pad) +{ + struct device *dev = &priv->client->dev; + u64 streams; + struct v4l2_subdev *remote_sd; + struct media_pad *pad; + + streams = v4l2_subdev_state_xlate_streams(state, src_pad, sink_pad, &src_streams); + if (!streams) + dev_dbg(dev, "no streams found on sink pad\n"); + + pad = media_pad_remote_pad_first(&priv->pads[sink_pad]); + if (!pad) { + dev_dbg(dev, "no remote pad found for sink pad\n"); + return ERR_PTR(-EPIPE); + } + + remote_sd = media_entity_to_v4l2_subdev(pad->entity); + if (!remote_sd) { + dev_dbg(dev, "no entity connected to CSI2 input\n"); + return ERR_PTR(-EPIPE); + } + + *sink_streams = streams; + *remote_pad = pad->index; + + return remote_sd; +} + +static int max96712_enable_remote_stream(struct max96712_priv *priv, + struct v4l2_subdev_state *state, + u32 source_pad, u32 stream, u32 sink_pad, + bool enable) +{ + struct device *dev = &priv->client->dev; + struct v4l2_subdev *remote_sd; + u64 sink_streams = 0; + u32 remote_pad = 0; + int ret = 0; + + if (enable && priv->vpg_started) { + dev_err(dev, "Cannot enable remote streams while VPG is enabled.\n"); + return -EINVAL; + } + + remote_sd = max96712_xlate_streams(priv, state, source_pad, BIT(stream), sink_pad, + &sink_streams, &remote_pad); + if (IS_ERR(remote_sd)) + return PTR_ERR(remote_sd); + + ret = enable ? v4l2_subdev_enable_streams(remote_sd, remote_pad, 0x1) : + v4l2_subdev_disable_streams(remote_sd, remote_pad, 0x1); + + if (ret) + dev_err(&priv->client->dev, "failed to %s streams 0x%llx on '%s':%u: %d\n", + enable ? "enable" : "disable", + sink_streams, remote_sd->name, remote_pad, ret); + + return ret; +} + static int max96712_enable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, u32 source_pad, u64 streams_mask) { struct max96712_priv *priv = v4l2_get_subdevdata(sd); + u64 sources_mask = streams_mask; + u32 sink_pad, sink_stream; + int ret = 0; + + if (!priv->enabled_streams) + max96712_mipi_enable(priv, true); + + while (true) { + int pos = ffs(sources_mask) - 1; - max96712_pattern_enable(priv, state, true); - max96712_mipi_enable(priv, true); + if (pos == -1) + break; + + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, + source_pad, pos, + &sink_pad, &sink_stream); + if (ret) + return ret; + + if (sink_pad == MAX96712_VPG_PAD && sink_stream == 0) { + /* need to have tunneling disabled for VPG to work */ + max96712_tunneling_enable(priv, false); + + ret = max96712_pattern_enable(priv, state, true); + } else { + if (!priv->enabled_streams) { + max96712_fsync_enable(priv, true); + if (priv->operation_mode == MAX96712_TUNNEL_MODE) + max96712_tunneling_enable(priv, true); + } + + ret = max96712_enable_remote_stream(priv, state, source_pad, pos, + sink_pad, true); + } + + if (ret) + return ret; + + sources_mask &= ~BIT(pos); + } + + priv->enabled_streams |= streams_mask; return 0; } @@ -756,9 +979,42 @@ static int max96712_disable_streams(struct v4l2_subdev *sd, u32 source_pad, u64 streams_mask) { struct max96712_priv *priv = v4l2_get_subdevdata(sd); + u64 sources_mask = streams_mask; + u32 sink_pad, sink_stream; + int ret = 0; - max96712_mipi_enable(priv, false); - max96712_pattern_enable(priv, state, false); + while (true) { + int pos = ffs(sources_mask) - 1; + + if (pos == -1) + break; + + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, + source_pad, pos, + &sink_pad, &sink_stream); + if (ret) + return ret; + + max96712_update_bits(priv, MAX96712_MIPI_PHY_0, FORCE_CSI_OUT_EN, 0x00); + + if (sink_pad == MAX96712_VPG_PAD && sink_stream == 0) + ret = max96712_pattern_enable(priv, state, false); + else + ret = max96712_enable_remote_stream(priv, state, source_pad, pos, + sink_pad, false); + + if (ret) + return ret; + + sources_mask &= ~BIT(pos); + } + + priv->enabled_streams &= ~streams_mask; + + if (!priv->enabled_streams) { + max96712_fsync_enable(priv, false); + max96712_mipi_enable(priv, false); + } return 0; } @@ -842,6 +1098,104 @@ static int max96712_set_fmt(struct v4l2_subdev *sd, return 0; } +#define to_index(priv, rx_port) ((rx_port) - &(priv)->rx_ports[0]) + +static int max96712_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asc) +{ + struct max96712_priv *priv = container_of(notifier->sd, struct max96712_priv, sd); + struct max96712_asc *async_conn = container_of(asc, struct max96712_asc, base); + struct max96712_rx_port *rx_port = async_conn->rx_port; + unsigned int index = to_index(priv, rx_port); + struct device *dev = &priv->client->dev; + unsigned int src_pad; + int ret; + + ret = media_entity_get_fwnode_pad(&subdev->entity, rx_port->fwnode, MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", subdev->name); + return ret; + } + + rx_port->sd = subdev; + src_pad = ret; + + ret = media_create_pad_link(&rx_port->sd->entity, src_pad, &priv->sd.entity, index, 0); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:%u\n", + rx_port->sd->name, src_pad, priv->sd.name, index); + return ret; + } + + dev_dbg(dev, "Bound %s pad: %u on index %u\n", subdev->name, src_pad, index); + + return 0; +} + +static void max96712_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asc) +{ + struct max96712_asc *async_conn = container_of(asc, struct max96712_asc, base); + struct max96712_rx_port *rx_port = async_conn->rx_port; + + rx_port->sd = NULL; +} + +static const struct v4l2_async_notifier_operations max96724_notify_ops = { + .bound = max96712_notify_bound, + .unbind = max96712_notify_unbind, +}; + +static int max96712_v4l2_notifier_register(struct max96712_priv *priv) +{ + int i, ret; + struct device *dev = &priv->client->dev; + struct max96712_rx_port *rx_port = NULL; + u32 rx_port_mask = priv->rx_port_mask; + + if (!priv->n_rx_ports) + return 0; + + v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd); + + while (true) { + int pos = ffs(rx_port_mask) - 1; + struct max96712_asc *asc; + + if (pos == -1) + break; + + rx_port = &priv->rx_ports[pos]; + rx_port_mask &= ~BIT(pos); + + if (!rx_port->fwnode) + continue; + + asc = v4l2_async_nf_add_fwnode(&priv->notifier, rx_port->fwnode, + struct max96712_asc); + if (IS_ERR(asc)) { + dev_err(dev, "Failed to add subdev for source %u: %ld", i, PTR_ERR(asc)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asc); + } + + asc->rx_port = rx_port; + } + + priv->notifier.ops = &max96724_notify_ops; + + ret = v4l2_async_nf_register(&priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + static int max96712_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { @@ -862,6 +1216,37 @@ static int max96712_init_state(struct v4l2_subdev *sd, return _max96712_set_routing(sd, state, &routing); } +static int max96712_log_status(struct v4l2_subdev *sd) +{ + struct max96712_priv *priv = container_of(sd, struct max96712_priv, sd); + struct device *dev = &priv->client->dev; + u8 gmsl_link_status_mask; + char hdr[64]; + int nport; + + gmsl_link_status_mask = max96712_get_link_status(priv); + + dev_info(dev, "Deserializer status:\n"); + + dev_info(dev, "RX ports:\n"); + + for (nport = 0; nport < MAX96712_MAX_RX_PORTS; nport++) { + struct max96712_rx_port *rx_port = &priv->rx_ports[nport]; + + sprintf(hdr, "\t* RX %d:", nport); + + if (!rx_port->fwnode) { + dev_info(dev, "%s Not Configured\n", hdr); + continue; + } + + dev_info(dev, "%s Link %s\n", hdr, + gmsl_link_status_mask & BIT(nport) ? "locked" : "not locked"); + } + + return 0; +} + static const struct v4l2_subdev_internal_ops max96712_internal_ops = { .init_state = max96712_init_state, }; @@ -878,8 +1263,13 @@ static const struct v4l2_subdev_pad_ops max96712_pad_ops = { .set_frame_interval = max96712_set_frame_interval, }; +static const struct v4l2_subdev_core_ops max96712_subdev_core_ops = { + .log_status = max96712_log_status, +}; + static const struct v4l2_subdev_ops max96712_subdev_ops = { .video = &max96712_video_ops, + .core = &max96712_subdev_core_ops, .pad = &max96712_pad_ops, }; @@ -907,6 +1297,10 @@ static const struct v4l2_ctrl_ops max96712_ctrl_ops = { .s_ctrl = max96712_s_ctrl, }; +static const struct media_entity_operations max96712_v4l2_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + static int max96712_v4l2_register(struct max96712_priv *priv) { struct v4l2_ctrl *link_freq_ctrl; @@ -917,6 +1311,7 @@ static int max96712_v4l2_register(struct max96712_priv *priv) v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96712_subdev_ops); priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &max96712_v4l2_media_ops; v4l2_ctrl_handler_init(&priv->ctrl_handler, 2); @@ -953,6 +1348,12 @@ static int max96712_v4l2_register(struct max96712_priv *priv) if (ret) goto error; + ret = max96712_v4l2_notifier_register(priv); + if (ret) { + dev_err(&priv->client->dev, "Unable to register v4l2 async notifiers\n"); + goto error; + } + ret = v4l2_async_register_subdev(&priv->sd); if (ret < 0) { dev_err(&priv->client->dev, "Unable to register subdevice\n"); @@ -960,6 +1361,7 @@ static int max96712_v4l2_register(struct max96712_priv *priv) } return 0; + error: v4l2_ctrl_handler_free(&priv->ctrl_handler); @@ -1223,6 +1625,14 @@ static int max96712_parse_dt(struct max96712_priv *priv) int ret = 0, count; u32 dt_val[3]; + if (!fwnode_property_read_u32(dev_fwnode(dev), "maxim,operation-mode", &dt_val[0])) + priv->operation_mode = dt_val[0]; + + if (priv->operation_mode != MAX96712_TUNNEL_MODE) { + dev_err(dev, "Unsupported mode, only tunneling mode is supported currently.\n"); + return -EINVAL; + } + count = fwnode_property_count_u32(dev_fwnode(dev), "maxim,fsync-config"); if (count > 0) { ret = fwnode_property_read_u32_array(dev_fwnode(dev), "maxim,fsync-config", -- 2.44.1