/* * The sink and source pads are created to match the OF graph port numbers so * that their indexes can be used interchangeably.
*/ #define MAX9286_NUM_GMSL 4 #define MAX9286_N_SINKS 4 #define MAX9286_N_PADS 5 #define MAX9286_SRC_PAD 4
/* * We must sleep after any change to the forward or reverse channel * configuration.
*/
usleep_range(3000, 5000);
}
staticvoid max9286_i2c_mux_open(struct max9286_priv *priv)
{ /* Open all channels on the MAX9286 */
max9286_i2c_mux_configure(priv, 0xff);
priv->mux_open = true;
}
staticvoid max9286_i2c_mux_close(struct max9286_priv *priv)
{ /* * Ensure that both the forward and reverse channel are disabled on the * mux, and that the channel ID is invalidated to ensure we reconfigure * on the next max9286_i2c_mux_select() call.
*/
max9286_i2c_mux_configure(priv, 0x00);
/* * Reverse channel setup. * * - Enable custom reverse channel configuration (through register 0x3f) * and set the first pulse length to 35 clock cycles. * - Adjust reverse channel amplitude: values > 130 are programmed * using the additional +100mV REV_AMP_X boost flag
*/
max9286_write(priv, 0x3f, MAX9286_EN_REV_CFG | MAX9286_REV_FLEN(35));
if (chan_amplitude > 100) { /* It is not possible to express values (100 < x < 130) */
chan_amplitude = max(30U, chan_amplitude - 100);
chan_config |= MAX9286_REV_AMP_X;
}
max9286_write(priv, 0x3b, chan_config | MAX9286_REV_AMP(chan_amplitude));
usleep_range(2000, 2500);
}
/* * max9286_check_video_links() - Make sure video links are detected and locked * * Performs safety checks on video link status. Make sure they are detected * and all enabled links are locked. * * Returns 0 for success, -EIO for errors.
*/ staticint max9286_check_video_links(struct max9286_priv *priv)
{ unsignedint i; int ret;
/* * Make sure valid video links are detected. * The delay is not characterized in de-serializer manual, wait up * to 5 ms.
*/ for (i = 0; i < 10; i++) {
ret = max9286_read(priv, 0x49); if (ret < 0) return -EIO;
if ((ret & MAX9286_VIDEO_DETECT_MASK) == priv->source_mask) break;
usleep_range(350, 500);
}
if (i == 10) {
dev_err(&priv->client->dev, "Unable to detect video links: 0x%02x\n", ret); return -EIO;
}
/* Make sure all enabled links are locked (4ms max). */ for (i = 0; i < 10; i++) {
ret = max9286_read(priv, 0x27); if (ret < 0) return -EIO;
if (ret & MAX9286_LOCKED) break;
usleep_range(350, 450);
}
if (i == 10) {
dev_err(&priv->client->dev, "Not all enabled links locked\n"); return -EIO;
}
return 0;
}
/* * max9286_check_config_link() - Detect and wait for configuration links * * Determine if the configuration channel is up and settled for a link. * * Returns 0 for success, -EIO for errors.
*/ staticint max9286_check_config_link(struct max9286_priv *priv, unsignedint source_mask)
{ unsignedint conflink_mask = (source_mask & 0x0f) << 4; unsignedint i; int ret;
/* * Make sure requested configuration links are detected. * The delay is not characterized in the chip manual: wait up * to 5 milliseconds.
*/ for (i = 0; i < 10; i++) {
ret = max9286_read(priv, 0x49); if (ret < 0) return -EIO;
ret &= 0xf0; if (ret == conflink_mask) break;
usleep_range(350, 500);
}
if (ret != conflink_mask) {
dev_err(&priv->client->dev, "Unable to detect configuration links: 0x%02x expected 0x%02x\n",
ret, conflink_mask); return -EIO;
}
dev_info(&priv->client->dev, "Successfully detected configuration links after %u loops: 0x%02x\n",
i, conflink_mask);
for (i = 0; i < ARRAY_SIZE(max9286_formats); ++i) { if (max9286_formats[i].code == format->code) {
info = &max9286_formats[i]; break;
}
}
if (WARN_ON(!info)) return;
/* * Video format setup: disable CSI output, set VC according to Link * number, enable I2C clock stretching when CCBSY is low, enable CCBSY * in external GPI-to-GPO mode.
*/
max9286_write(priv, 0x15, MAX9286_VCTYPE | MAX9286_EN_CCBSYB_CLK_STR |
MAX9286_EN_GPI_CCBSYB);
/* * Enable HS/VS encoding, use HS as line valid source, use D14/15 for * HS/VS, invert VS.
*/
max9286_write(priv, 0x0c, MAX9286_HVEN | MAX9286_DESEL |
MAX9286_INVVS | MAX9286_HVSRC_D14);
}
interval = v4l2_subdev_state_get_interval(state, MAX9286_SRC_PAD); if (!interval->numerator || !interval->denominator) { /* * Special case, a null interval enables automatic FRAMESYNC * mode. FRAMESYNC is taken from the slowest link.
*/
max9286_write(priv, 0x01, MAX9286_FSYNCMODE_INT_HIZ |
MAX9286_FSYNCMETH_AUTO); return;
}
/* * Manual FRAMESYNC * * The FRAMESYNC generator is configured with a period expressed as a * number of PCLK periods.
*/
fsync = div_u64((u64)priv->pixelrate * interval->numerator,
interval->denominator);
dev_dbg(&priv->client->dev, "fsync period %u (pclk %u)\n", fsync,
priv->pixelrate);
/* Pixel rate is mandatory to be reported by sources. */
ctrl = v4l2_ctrl_find(source->sd->ctrl_handler,
V4L2_CID_PIXEL_RATE); if (!ctrl) {
pixelrate = 0; break;
}
/* All source must report the same pixel rate. */
source_rate = v4l2_ctrl_g_ctrl_int64(ctrl); if (!pixelrate) {
pixelrate = source_rate;
} elseif (pixelrate != source_rate) {
dev_err(&priv->client->dev, "Unable to calculate pixel rate\n"); return -EINVAL;
}
}
if (!pixelrate) {
dev_err(&priv->client->dev, "No pixel rate control available in sources\n"); return -EINVAL;
}
priv->pixelrate = pixelrate;
/* * The CSI-2 transmitter pixel rate is the single source rate multiplied * by the number of available sources.
*/ return v4l2_ctrl_s_ctrl_int64(priv->pixelrate_ctrl,
pixelrate * priv->nsources);
}
ret = media_entity_get_fwnode_pad(&subdev->entity,
source->fwnode,
MEDIA_PAD_FL_SOURCE); if (ret < 0) {
dev_err(&priv->client->dev, "Failed to find pad for %s\n", subdev->name); return ret;
}
ret = media_create_pad_link(&source->sd->entity, src_pad,
&priv->sd.entity, index,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE); if (ret) {
dev_err(&priv->client->dev, "Unable to link %s:%u -> %s:%u\n",
source->sd->name, src_pad, priv->sd.name, index); return ret;
}
dev_dbg(&priv->client->dev, "Bound %s pad: %u on index %u\n",
subdev->name, src_pad, index);
/* * As we register a subdev notifiers we won't get a .complete() callback * here, so we have to use bound_sources to identify when all remote * serializers have probed.
*/ if (priv->bound_sources != priv->source_mask) return 0;
/* * All enabled sources have probed and enabled their reverse control * channels: * * - Increase the reverse channel amplitude to compensate for the * remote ends high threshold * - Verify all configuration links are properly detected * - Disable auto-ack as communication on the control channel are now * stable.
*/
max9286_reverse_channel_setup(priv, MAX9286_REV_AMP_HIGH);
max9286_check_config_link(priv, priv->source_mask);
max9286_configure_i2c(priv, false);
mas = v4l2_async_nf_add_fwnode(&priv->notifier, source->fwnode, struct max9286_asd); if (IS_ERR(mas)) {
dev_err(dev, "Failed to add subdev for source %u: %ld",
i, PTR_ERR(mas));
v4l2_async_nf_cleanup(&priv->notifier); return PTR_ERR(mas);
}
mas->source = source;
}
priv->notifier.ops = &max9286_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;
}
staticvoid max9286_v4l2_notifier_unregister(struct max9286_priv *priv)
{ if (!priv->nsources) return;
/* * The frame sync between cameras is transmitted across the * reverse channel as GPIO. We must open all channels while * streaming to allow this synchronisation signal to be shared.
*/
max9286_i2c_mux_open(priv);
/* Start all cameras. */
for_each_source(priv, source) {
ret = v4l2_subdev_call(source->sd, video, s_stream, 1); if (ret) goto unlock;
}
ret = max9286_check_video_links(priv); if (ret) goto unlock;
/* * Wait until frame synchronization is locked. * * Manual says frame sync locking should take ~6 VTS. * From practical experience at least 8 are required. Give * 12 complete frames time (~400ms at 30 fps) to achieve frame * locking before returning error.
*/ for (i = 0; i < 40; i++) { if (max9286_read(priv, 0x31) & MAX9286_FSYNC_LOCKED) {
sync = true; break;
}
usleep_range(9000, 11000);
}
if (!sync) {
dev_err(&priv->client->dev, "Failed to get frame synchronization\n");
ret = -EXDEV; /* Invalid cross-device link */ goto unlock;
}
/* * Configure the CSI-2 output to line interleaved mode (W x (N * x H), as opposed to the (N x W) x H mode that outputs the * images stitched side-by-side) and enable it.
*/
max9286_write(priv, 0x15, MAX9286_CSI_IMAGE_TYP | MAX9286_VCTYPE |
MAX9286_CSIOUTEN | MAX9286_EN_CCBSYB_CLK_STR |
MAX9286_EN_GPI_CCBSYB);
} else {
max9286_write(priv, 0x15, MAX9286_VCTYPE |
MAX9286_EN_CCBSYB_CLK_STR |
MAX9286_EN_GPI_CCBSYB);
/* * Disable setting format on the source pad: format is propagated * from the sinks.
*/ if (format->pad == MAX9286_SRC_PAD) return v4l2_subdev_get_fmt(sd, state, format);
/* Validate the format. */ for (i = 0; i < ARRAY_SIZE(max9286_formats); ++i) { if (max9286_formats[i].code == format->format.code) break;
}
if (i == ARRAY_SIZE(max9286_formats))
format->format.code = max9286_formats[0].code;
/* * Apply the same format on all the other pad as all links must have the * same format.
*/
for_each_source(priv, source) { unsignedint index = to_index(priv, source);
priv->pads[MAX9286_SRC_PAD].flags = MEDIA_PAD_FL_SOURCE; for (i = 0; i < MAX9286_SRC_PAD; i++)
priv->pads[i].flags = MEDIA_PAD_FL_SINK;
ret = media_entity_pads_init(&priv->sd.entity, MAX9286_N_PADS,
priv->pads); if (ret) goto err_async;
priv->sd.state_lock = priv->ctrls.lock;
ret = v4l2_subdev_init_finalize(&priv->sd); if (ret) goto err_async;
ret = v4l2_async_register_subdev(&priv->sd); if (ret < 0) {
dev_err(dev, "Unable to register subdevice\n"); goto err_subdev;
}
/* * Set the I2C bus speed. * * Enable I2C Local Acknowledge during the probe sequences of the camera * only. This should be disabled after the mux is initialised.
*/
max9286_configure_i2c(priv, true);
max9286_reverse_channel_setup(priv, priv->init_rev_chan_mv);
/* * Enable GMSL links, mask unused ones and autodetect link * used as CSI clock source.
*/
max9286_write(priv, 0x00, MAX9286_MSTLINKSEL_AUTO | priv->route_mask);
max9286_write(priv, 0x0b, link_order[priv->route_mask]);
max9286_write(priv, 0x69, (0xf & ~priv->route_mask));
/* * The overlap window seems to provide additional validation by tracking * the delay between vsync and frame sync, generating an error if the * delay is bigger than the programmed window, though it's not yet clear * what value should be set. * * As it's an optional value and can be disabled, we do so by setting * a 0 overlap value.
*/
max9286_write(priv, 0x63, 0);
max9286_write(priv, 0x64, 0);
/* * Wait for 2ms to allow the link to resynchronize after the * configuration change.
*/
usleep_range(2000, 5000);
return 0;
}
staticint max9286_gpio_set(struct max9286_priv *priv, unsignedint offset, int value)
{ if (value)
priv->gpio_state |= BIT(offset); else
priv->gpio_state &= ~BIT(offset);
/* * Parse the "gpio-poc" vendor property. If the property is not * specified the camera power is controlled by a regulator.
*/
ret = of_property_read_u32_array(dev->of_node, "maxim,gpio-poc",
priv->gpio_poc, 2); if (ret == -EINVAL) { /* * If gpio lines are not used for the camera power, register * a gpio controller for consumers.
*/ return max9286_register_gpio(priv);
}
/* If the property is specified make sure it is well formed. */ if (ret || priv->gpio_poc[0] > 1 ||
(priv->gpio_poc[1] != GPIO_ACTIVE_HIGH &&
priv->gpio_poc[1] != GPIO_ACTIVE_LOW)) {
dev_err(dev, "Invalid 'gpio-poc' property\n"); return -EINVAL;
}
ret = max9286_poc_enable(priv, true); if (ret) return ret;
ret = max9286_setup(priv); if (ret) {
dev_err(&client->dev, "Unable to setup max9286\n"); goto err_poc_disable;
}
/* * Register all V4L2 interactions for the MAX9286 and notifiers for * any subdevices connected.
*/
ret = max9286_v4l2_register(priv); if (ret) {
dev_err(&client->dev, "Failed to register with V4L2\n"); goto err_poc_disable;
}
ret = max9286_i2c_mux_init(priv); if (ret) {
dev_err(&client->dev, "Unable to initialize I2C multiplexer\n"); goto err_v4l2_register;
}
/* Leave the mux channels disabled until they are selected. */
max9286_i2c_mux_close(priv);
/* Balance the of_node_put() performed by of_find_node_by_name(). */
of_node_get(dev->of_node);
i2c_mux = of_find_node_by_name(dev->of_node, "i2c-mux"); if (!i2c_mux) {
dev_err(dev, "Failed to find i2c-mux node\n"); return -EINVAL;
}
/* Identify which i2c-mux channels are enabled */
for_each_child_of_node(i2c_mux, node) {
u32 id = 0;
of_property_read_u32(node, "reg", &id); if (id >= MAX9286_NUM_GMSL) continue;
if (!of_device_is_available(node)) {
dev_dbg(dev, "Skipping disabled I2C bus port %u\n", id); continue;
}
of_graph_parse_endpoint(node, &ep);
dev_dbg(dev, "Endpoint %pOF on port %d",
ep.local_node, ep.port);
if (ep.port > MAX9286_NUM_GMSL) {
dev_err(dev, "Invalid endpoint %s on port %d",
of_node_full_name(ep.local_node), ep.port); continue;
}
/* For the source endpoint just parse the bus configuration. */ if (ep.port == MAX9286_SRC_PAD) { struct v4l2_fwnode_endpoint vep = {
.bus_type = V4L2_MBUS_CSI2_DPHY
}; int ret;
ret = v4l2_fwnode_endpoint_parse(
of_fwnode_handle(node), &vep); if (ret) {
of_node_put(node); return ret;
}
of_property_read_u32(dev->of_node, "maxim,bus-width", &priv->bus_width); switch (priv->bus_width) { case 0: /* * The property isn't specified in the device tree, the driver * will keep the default value selected by the BWS pin.
*/ case 24: case 27: case 32: break; default:
dev_err(dev, "Invalid %s value %u\n", "maxim,bus-width",
priv->bus_width); return -EINVAL;
}
of_property_read_u32(dev->of_node, "maxim,i2c-remote-bus-hz",
&i2c_clk_freq); for (i = 0; i < ARRAY_SIZE(max9286_i2c_speeds); ++i) { conststruct max9286_i2c_speed *speed = &max9286_i2c_speeds[i];
if (i == ARRAY_SIZE(max9286_i2c_speeds)) {
dev_err(dev, "Invalid %s value %u\n", "maxim,i2c-remote-bus-hz",
i2c_clk_freq); return -EINVAL;
}
/* * Parse the initial value of the reverse channel amplitude from * the firmware interface and convert it to millivolts. * * Default it to 170mV for backward compatibility with DTBs that do not * provide the property.
*/ if (of_property_read_u32(dev->of_node, "maxim,reverse-channel-microvolt",
&reverse_channel_microvolt))
priv->init_rev_chan_mv = 170; else
priv->init_rev_chan_mv = reverse_channel_microvolt / 1000U;
/* Start by getting the global regulator. */
priv->regulator = devm_regulator_get_optional(dev, "poc"); if (!IS_ERR(priv->regulator)) return 0;
if (PTR_ERR(priv->regulator) != -ENODEV) return dev_err_probe(dev, PTR_ERR(priv->regulator), "Unable to get PoC regulator\n");
/* If there's no global regulator, get per-port regulators. */
dev_dbg(dev, "No global PoC regulator, looking for per-port regulators\n");
priv->regulator = NULL;
for_each_source(priv, source) { unsignedint index = to_index(priv, source); char name[10];
snprintf(name, sizeof(name), "port%u-poc", index);
source->regulator = devm_regulator_get(dev, name); if (IS_ERR(source->regulator)) {
ret = PTR_ERR(source->regulator);
dev_err_probe(dev, ret, "Unable to get port %u PoC regulator\n",
index); return ret;
}
}
return 0;
}
staticint max9286_probe(struct i2c_client *client)
{ struct max9286_priv *priv; int ret;
priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
priv->client = client;
/* GPIO values default to high */
priv->gpio_state = BIT(0) | BIT(1);
ret = max9286_parse_dt(priv); if (ret) goto err_cleanup_dt;
priv->gpiod_pwdn = devm_gpiod_get_optional(&client->dev, "enable",
GPIOD_OUT_HIGH); if (IS_ERR(priv->gpiod_pwdn)) {
ret = PTR_ERR(priv->gpiod_pwdn); goto err_cleanup_dt;
}
/* Wait at least 4ms before the I2C lines latch to the address */ if (priv->gpiod_pwdn)
usleep_range(4000, 5000);
/* * The MAX9286 starts by default with all ports enabled, we disable all * ports early to ensure that all channels are disabled if we error out * and keep the bus consistent.
*/
max9286_i2c_mux_close(priv);
/* * The MAX9286 initialises with auto-acknowledge enabled by default. * This can be invasive to other transactions on the same bus, so * disable it early. It will be enabled only as and when needed.
*/
max9286_configure_i2c(priv, false);
ret = max9286_parse_gpios(priv); if (ret) goto err_powerdown;
if (!priv->use_gpio_poc) {
ret = max9286_get_poc_supplies(priv); if (ret) goto err_cleanup_dt;
}
ret = max9286_init(priv); if (ret < 0) goto err_cleanup_dt;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.