@@ -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",
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@oss.nxp.com> --- drivers/staging/media/max96712/max96712.c | 426 +++++++++++++++++++++- 1 file changed, 418 insertions(+), 8 deletions(-)