/* Number of lines by which exposure must be less than VMAX */ #define IMX290_EXPOSURE_OFFSET 2
#define IMX290_PIXEL_RATE 148500000
/* * The IMX290 pixel array is organized as follows: * * +------------------------------------+ * | Optical Black | } Vertical effective optical black (10) * +---+------------------------------------+---+ * | | | | } Effective top margin (8) * | | +----------------------------+ | | \ * | | | | | | | * | | | | | | | * | | | | | | | * | | | Recording Pixel Area | | | | Recommended height (1080) * | | | | | | | * | | | | | | | * | | | | | | | * | | +----------------------------+ | | / * | | | | } Effective bottom margin (9) * +---+------------------------------------+---+ * <-> <-> <--------------------------> <-> <-> * \---- Ignored right margin (4) * \-------- Effective right margin (9) * \------------------------- Recommended width (1920) * \----------------------------------------- Effective left margin (8) * \--------------------------------------------- Ignored left margin (4) * * The optical black lines are output over CSI-2 with a separate data type. * * The pixel array is meant to have 1920x1080 usable pixels after image * processing in an ISP. It has 8 (9) extra active pixels usable for color * processing in the ISP on the top and left (bottom and right) sides of the * image. In addition, 4 additional pixels are present on the left and right * sides of the image, documented as "ignored area". * * As far as is understood, all pixels of the pixel array (ignored area, color * processing margins and recording area) can be output by the sensor.
*/
/* * In this function and in the similar ones below We rely on imx290_probe() * to ensure that nlanes is either 2 or 4.
*/ staticinlineconst s64 *imx290_link_freqs_ptr(conststruct imx290 *imx290)
{ if (imx290->nlanes == 2) return imx290_link_freq_2lanes; else return imx290_link_freq_4lanes;
}
ret = imx290_set_register_array(imx290, info->regs, info->num_regs); if (ret < 0) {
dev_err(imx290->dev, "Could not set format registers\n"); return ret;
}
return imx290_set_black_level(imx290, format,
IMX290_BLACK_LEVEL_DEFAULT, &ret);
}
/* * Return immediately for controls that don't need to be applied to the * device.
*/ if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY) return 0;
if (ctrl->id == V4L2_CID_VBLANK) { /* Changing vblank changes the allowed range for exposure. */
imx290_exposure_update(imx290, imx290->current_mode);
}
/* V4L2 controls values will be applied only when power is already up */ if (!pm_runtime_get_if_in_use(imx290->dev)) return 0;
state = v4l2_subdev_get_locked_active_state(&imx290->sd);
format = v4l2_subdev_state_get_format(state, 0);
switch (ctrl->id) { case V4L2_CID_ANALOGUE_GAIN:
ret = cci_write(imx290->regmap, IMX290_GAIN, ctrl->val, NULL); break;
case V4L2_CID_VBLANK:
ret = cci_write(imx290->regmap, IMX290_VMAX,
ctrl->val + imx290->current_mode->height, NULL); /* * Due to the way that exposure is programmed in this sensor in * relation to VMAX, we have to reprogramme it whenever VMAX is * changed. * Update ctrl so that the V4L2_CID_EXPOSURE case can refer to * it.
*/
ctrl = imx290->exposure;
fallthrough; case V4L2_CID_EXPOSURE:
vmax = imx290->vblank->val + imx290->current_mode->height;
ret = cci_write(imx290->regmap, IMX290_SHS1,
vmax - ctrl->val - 1, NULL); break;
case V4L2_CID_TEST_PATTERN: if (ctrl->val) {
imx290_set_black_level(imx290, format, 0, &ret);
usleep_range(10000, 11000);
cci_write(imx290->regmap, IMX290_PGCTRL,
(u8)(IMX290_PGCTRL_REGEN |
IMX290_PGCTRL_THRU |
IMX290_PGCTRL_MODE(ctrl->val)), &ret);
} else {
cci_write(imx290->regmap, IMX290_PGCTRL, 0x00, &ret);
usleep_range(10000, 11000);
imx290_set_black_level(imx290, format,
IMX290_BLACK_LEVEL_DEFAULT, &ret);
} break;
case V4L2_CID_HBLANK:
ret = cci_write(imx290->regmap, IMX290_HMAX,
ctrl->val + imx290->current_mode->width, NULL); break;
case V4L2_CID_HFLIP: case V4L2_CID_VFLIP:
{
u32 reg;
reg = imx290->current_mode->ctrl_07; if (imx290->hflip->val)
reg |= IMX290_HREVERSE; if (imx290->vflip->val)
reg |= IMX290_VREVERSE;
ret = cci_write(imx290->regmap, IMX290_CTRL_07, reg, NULL); break;
}
staticint imx290_ctrl_init(struct imx290 *imx290)
{ struct v4l2_fwnode_device_properties props; int ret;
ret = v4l2_fwnode_device_parse(imx290->dev, &props); if (ret < 0) return ret;
v4l2_ctrl_handler_init(&imx290->ctrls, 11);
/* * The sensor has an analog gain and a digital gain, both controlled * through a single gain value, expressed in 0.3dB increments. Values * from 0.0dB (0) to 30.0dB (100) apply analog gain only, higher values * up to 72.0dB (240) add further digital gain. Limit the range to * analog gain only, support for digital gain can be added separately * if needed.
*/
v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
V4L2_CID_ANALOGUE_GAIN, 0,
imx290->model->max_analog_gain, 1, 0);
/* * Correct range will be determined through imx290_ctrl_update setting * V4L2_CID_VBLANK.
*/
imx290->exposure = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
V4L2_CID_EXPOSURE, 1, 65535, 1,
65535);
/* * Set the link frequency, pixel rate, horizontal blanking and vertical * blanking to hardcoded values, they will be updated by * imx290_ctrl_update().
*/
imx290->link_freq =
v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops,
V4L2_CID_LINK_FREQ,
imx290_link_freqs_num(imx290) - 1, 0,
imx290_link_freqs_ptr(imx290)); if (imx290->link_freq)
imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
/* * Actual range will be set from imx290_ctrl_update later in the probe.
*/
imx290->hblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
V4L2_CID_HBLANK, 1, 1, 1, 1);
/* Set init register settings */
ret = imx290_set_register_array(imx290, imx290_global_init_settings,
ARRAY_SIZE(imx290_global_init_settings)); if (ret < 0) {
dev_err(imx290->dev, "Could not set init registers\n"); return ret;
}
/* Set mdel specific init register settings */
ret = imx290_set_register_array(imx290, imx290->model->init_regs,
imx290->model->init_regs_num); if (ret < 0) {
dev_err(imx290->dev, "Could not set model specific init registers\n"); return ret;
}
/* Set clock parameters based on mode and xclk */
ret = imx290_set_clock(imx290); if (ret < 0) {
dev_err(imx290->dev, "Could not set clocks - %d\n", ret); return ret;
}
/* Set data lane count */
ret = imx290_set_data_lanes(imx290); if (ret < 0) {
dev_err(imx290->dev, "Could not set data lanes - %d\n", ret); return ret;
}
ret = imx290_set_csi_config(imx290); if (ret < 0) {
dev_err(imx290->dev, "Could not set csi cfg - %d\n", ret); return ret;
}
/* Apply the register values related to current frame format */
format = v4l2_subdev_state_get_format(state, 0);
ret = imx290_setup_format(imx290, format); if (ret < 0) {
dev_err(imx290->dev, "Could not set frame format - %d\n", ret); return ret;
}
/* Apply default values of current mode */
ret = imx290_set_register_array(imx290, imx290->current_mode->data,
imx290->current_mode->data_size); if (ret < 0) {
dev_err(imx290->dev, "Could not set current mode - %d\n", ret); return ret;
}
/* Apply customized values from user */
ret = __v4l2_ctrl_handler_setup(imx290->sd.ctrl_handler); if (ret) {
dev_err(imx290->dev, "Could not sync v4l2 controls - %d\n", ret); return ret;
}
/* * vflip and hflip should not be changed during streaming as the sensor * will produce an invalid frame.
*/
__v4l2_ctrl_grab(imx290->vflip, enable);
__v4l2_ctrl_grab(imx290->hflip, enable);
/* * After linking the subdev with the imx290 instance, we are allowed to * use the pm_runtime functions. Decrease the PM usage count. The device * will get suspended after the autosuspend delay, turning the power * off. However, the communication happening in imx290_ctrl_update() * will already be prevented even before the delay.
*/
v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops);
pm_runtime_put_autosuspend(imx290->dev);
staticint imx290_init_clk(struct imx290 *imx290)
{
u32 xclk_freq; int ret;
ret = device_property_read_u32(imx290->dev, "clock-frequency",
&xclk_freq); if (ret) {
dev_err(imx290->dev, "Could not get xclk frequency\n"); return ret;
}
/* external clock must be 37.125 MHz or 74.25MHz */ switch (xclk_freq) { case 37125000:
imx290->xclk_idx = IMX290_CLK_37_125; break; case 74250000:
imx290->xclk_idx = IMX290_CLK_74_25; break; default:
dev_err(imx290->dev, "External clock frequency %u is not supported\n",
xclk_freq); return -EINVAL;
}
ret = clk_set_rate(imx290->xclk, xclk_freq); if (ret) {
dev_err(imx290->dev, "Could not set xclk frequency\n"); return ret;
}
return 0;
}
/* * Returns 0 if all link frequencies used by the driver for the given number * of MIPI data lanes are mentioned in the device tree, or the value of the * first missing frequency otherwise.
*/ static s64 imx290_check_link_freqs(conststruct imx290 *imx290, conststruct v4l2_fwnode_endpoint *ep)
{ int i, j; const s64 *freqs = imx290_link_freqs_ptr(imx290); int freqs_count = imx290_link_freqs_num(imx290);
for (i = 0; i < freqs_count; i++) { for (j = 0; j < ep->nr_of_link_frequencies; j++) if (freqs[i] == ep->link_frequencies[j]) break; if (j == ep->nr_of_link_frequencies) return freqs[i];
} return 0;
}
endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(imx290->dev), NULL); if (!endpoint) {
dev_err(imx290->dev, "Endpoint node not found\n"); return -EINVAL;
}
ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
fwnode_handle_put(endpoint); if (ret == -ENXIO) {
dev_err(imx290->dev, "Unsupported bus type, should be CSI2\n"); goto done;
} elseif (ret) {
dev_err(imx290->dev, "Parsing endpoint node failed\n"); goto done;
}
/* Get number of data lanes */
imx290->nlanes = ep.bus.mipi_csi2.num_data_lanes; if (imx290->nlanes != 2 && imx290->nlanes != 4) {
dev_err(imx290->dev, "Invalid data lanes: %d\n", imx290->nlanes);
ret = -EINVAL; goto done;
}
dev_dbg(imx290->dev, "Using %u data lanes\n", imx290->nlanes);
if (!ep.nr_of_link_frequencies) {
dev_err(imx290->dev, "link-frequency property not found in DT\n");
ret = -EINVAL; goto done;
}
/* Check that link frequences for all the modes are in device tree */
fq = imx290_check_link_freqs(imx290, &ep); if (fq) {
dev_err(imx290->dev, "Link frequency of %lld is not supported\n",
fq);
ret = -EINVAL; goto done;
}
imx290 = devm_kzalloc(dev, sizeof(*imx290), GFP_KERNEL); if (!imx290) return -ENOMEM;
imx290->dev = dev;
imx290->regmap = devm_cci_regmap_init_i2c(client, 16); if (IS_ERR(imx290->regmap)) {
dev_err(dev, "Unable to initialize I2C\n"); return -ENODEV;
}
ret = imx290_parse_dt(imx290); if (ret) return ret;
/* Acquire resources. */
imx290->xclk = devm_clk_get(dev, "xclk"); if (IS_ERR(imx290->xclk)) return dev_err_probe(dev, PTR_ERR(imx290->xclk), "Could not get xclk\n");
ret = imx290_get_regulators(dev, imx290); if (ret < 0) return dev_err_probe(dev, ret, "Cannot get regulators\n");
imx290->rst_gpio = devm_gpiod_get_optional(dev, "reset",
GPIOD_OUT_HIGH); if (IS_ERR(imx290->rst_gpio)) return dev_err_probe(dev, PTR_ERR(imx290->rst_gpio), "Cannot get reset gpio\n");
/* Initialize external clock frequency. */
ret = imx290_init_clk(imx290); if (ret) return ret;
/* * Enable power management. The driver supports runtime PM, but needs to * work when runtime PM is disabled in the kernel. To that end, power * the sensor on manually here.
*/
ret = imx290_power_on(imx290); if (ret < 0) {
dev_err(dev, "Could not power on the device\n"); return ret;
}
/* * Enable runtime PM with autosuspend. As the device has been powered * manually, mark it as active, and increase the usage count without * resuming the device.
*/
pm_runtime_set_active(dev);
pm_runtime_get_noresume(dev);
pm_runtime_enable(dev);
pm_runtime_set_autosuspend_delay(dev, 1000);
pm_runtime_use_autosuspend(dev);
/* * Make sure the sensor is available, in STANDBY and not streaming * before the V4L2 subdev is initialized.
*/
ret = imx290_stop_streaming(imx290); if (ret) {
ret = dev_err_probe(dev, ret, "Could not initialize device\n"); goto err_pm;
}
/* Initialize the V4L2 subdev. */
ret = imx290_subdev_init(imx290); if (ret) goto err_pm;
/* * Finally, register the V4L2 subdev. This must be done after * initializing everything as the subdev can be used immediately after * being registered.
*/
ret = v4l2_async_register_subdev(&imx290->sd); if (ret < 0) {
dev_err(dev, "Could not register v4l2 device\n"); goto err_subdev;
}
/* * Disable runtime PM. In case runtime PM is disabled in the kernel, * make sure to turn power off manually.
*/
pm_runtime_disable(imx290->dev); if (!pm_runtime_status_suspended(imx290->dev))
imx290_power_off(imx290);
pm_runtime_set_suspended(imx290->dev);
}
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.