summaryrefslogtreecommitdiff
path: root/drivers/media/i2c/imx219.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/i2c/imx219.c')
-rw-r--r--drivers/media/i2c/imx219.c320
1 files changed, 207 insertions, 113 deletions
diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index 2d54cea113e1..04262bbf6306 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -70,15 +70,14 @@
#define IMX219_EXPOSURE_MAX 65535
/* V_TIMING internal */
-#define IMX219_REG_VTS CCI_REG16(0x0160)
-#define IMX219_VTS_MAX 0xffff
-
-#define IMX219_VBLANK_MIN 4
-
-/* HBLANK control - read only */
-#define IMX219_PPL_DEFAULT 3448
-
+#define IMX219_REG_FRM_LENGTH_A CCI_REG16(0x0160)
+#define IMX219_FLL_MAX 0xffff
+#define IMX219_VBLANK_MIN 32
#define IMX219_REG_LINE_LENGTH_A CCI_REG16(0x0162)
+#define IMX219_LLP_MIN 0x0d78
+#define IMX219_BINNED_LLP_MIN 0x0de8
+#define IMX219_LLP_MAX 0x7ff0
+
#define IMX219_REG_X_ADD_STA_A CCI_REG16(0x0164)
#define IMX219_REG_X_ADD_END_A CCI_REG16(0x0166)
#define IMX219_REG_Y_ADD_STA_A CCI_REG16(0x0168)
@@ -133,10 +132,11 @@
/* Pixel rate is fixed for all the modes */
#define IMX219_PIXEL_RATE 182400000
-#define IMX219_PIXEL_RATE_4LANE 280800000
+#define IMX219_PIXEL_RATE_4LANE 281600000
#define IMX219_DEFAULT_LINK_FREQ 456000000
-#define IMX219_DEFAULT_LINK_FREQ_4LANE 363000000
+#define IMX219_DEFAULT_LINK_FREQ_4LANE_UNSUPPORTED 363000000
+#define IMX219_DEFAULT_LINK_FREQ_4LANE 364000000
/* IMX219 native and active pixel array size. */
#define IMX219_NATIVE_WIDTH 3296U
@@ -154,7 +154,7 @@ struct imx219_mode {
unsigned int height;
/* V-timing */
- unsigned int vts_def;
+ unsigned int fll_def;
};
static const struct cci_reg_sequence imx219_common_regs[] = {
@@ -168,15 +168,6 @@ static const struct cci_reg_sequence imx219_common_regs[] = {
{ CCI_REG8(0x30eb), 0x05 },
{ CCI_REG8(0x30eb), 0x09 },
- /* PLL Clock Table */
- { IMX219_REG_VTPXCK_DIV, 5 },
- { IMX219_REG_VTSYCK_DIV, 1 },
- { IMX219_REG_PREPLLCK_VT_DIV, 3 }, /* 0x03 = AUTO set */
- { IMX219_REG_PREPLLCK_OP_DIV, 3 }, /* 0x03 = AUTO set */
- { IMX219_REG_PLL_VT_MPY, 57 },
- { IMX219_REG_OPSYCK_DIV, 1 },
- { IMX219_REG_PLL_OP_MPY, 114 },
-
/* Undocumented registers */
{ CCI_REG8(0x455e), 0x00 },
{ CCI_REG8(0x471e), 0x4b },
@@ -192,7 +183,6 @@ static const struct cci_reg_sequence imx219_common_regs[] = {
{ CCI_REG8(0x479b), 0x0e },
/* Frame Bank Register Group "A" */
- { IMX219_REG_LINE_LENGTH_A, 3448 },
{ IMX219_REG_X_ODD_INC_A, 1 },
{ IMX219_REG_Y_ODD_INC_A, 1 },
@@ -201,12 +191,45 @@ static const struct cci_reg_sequence imx219_common_regs[] = {
{ IMX219_REG_EXCK_FREQ, IMX219_EXCK_FREQ(IMX219_XCLK_FREQ / 1000000) },
};
+static const struct cci_reg_sequence imx219_2lane_regs[] = {
+ /* PLL Clock Table */
+ { IMX219_REG_VTPXCK_DIV, 5 },
+ { IMX219_REG_VTSYCK_DIV, 1 },
+ { IMX219_REG_PREPLLCK_VT_DIV, 3 }, /* 0x03 = AUTO set */
+ { IMX219_REG_PREPLLCK_OP_DIV, 3 }, /* 0x03 = AUTO set */
+ { IMX219_REG_PLL_VT_MPY, 57 },
+ { IMX219_REG_OPSYCK_DIV, 1 },
+ { IMX219_REG_PLL_OP_MPY, 114 },
+
+ /* 2-Lane CSI Mode */
+ { IMX219_REG_CSI_LANE_MODE, IMX219_CSI_2_LANE_MODE },
+};
+
+static const struct cci_reg_sequence imx219_4lane_regs[] = {
+ /* PLL Clock Table */
+ { IMX219_REG_VTPXCK_DIV, 5 },
+ { IMX219_REG_VTSYCK_DIV, 1 },
+ { IMX219_REG_PREPLLCK_VT_DIV, 3 }, /* 0x03 = AUTO set */
+ { IMX219_REG_PREPLLCK_OP_DIV, 3 }, /* 0x03 = AUTO set */
+ { IMX219_REG_PLL_VT_MPY, 88 },
+ { IMX219_REG_OPSYCK_DIV, 1 },
+ { IMX219_REG_PLL_OP_MPY, 91 },
+
+ /* 4-Lane CSI Mode */
+ { IMX219_REG_CSI_LANE_MODE, IMX219_CSI_4_LANE_MODE },
+};
+
static const s64 imx219_link_freq_menu[] = {
IMX219_DEFAULT_LINK_FREQ,
};
static const s64 imx219_link_freq_4lane_menu[] = {
IMX219_DEFAULT_LINK_FREQ_4LANE,
+ /*
+ * This will never be advertised to userspace, but will be used for
+ * v4l2_link_freq_to_bitmap
+ */
+ IMX219_DEFAULT_LINK_FREQ_4LANE_UNSUPPORTED,
};
static const char * const imx219_test_pattern_menu[] = {
@@ -289,25 +312,25 @@ static const struct imx219_mode supported_modes[] = {
/* 8MPix 15fps mode */
.width = 3280,
.height = 2464,
- .vts_def = 3526,
+ .fll_def = 3526,
},
{
/* 1080P 30fps cropped */
.width = 1920,
.height = 1080,
- .vts_def = 1763,
+ .fll_def = 1763,
},
{
- /* 2x2 binned 30fps mode */
+ /* 2x2 binned 60fps mode */
.width = 1640,
.height = 1232,
- .vts_def = 1763,
+ .fll_def = 1707,
},
{
- /* 640x480 30fps mode */
+ /* 640x480 60fps mode */
.width = 640,
.height = 480,
- .vts_def = 1763,
+ .fll_def = 1707,
},
};
@@ -359,6 +382,62 @@ static u32 imx219_get_format_code(struct imx219 *imx219, u32 code)
return imx219_mbus_formats[i];
}
+static u32 imx219_get_format_bpp(const struct v4l2_mbus_framefmt *format)
+{
+ switch (format->code) {
+ case MEDIA_BUS_FMT_SRGGB8_1X8:
+ case MEDIA_BUS_FMT_SGRBG8_1X8:
+ case MEDIA_BUS_FMT_SGBRG8_1X8:
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ return 8;
+
+ case MEDIA_BUS_FMT_SRGGB10_1X10:
+ case MEDIA_BUS_FMT_SGRBG10_1X10:
+ case MEDIA_BUS_FMT_SGBRG10_1X10:
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ default:
+ return 10;
+ }
+}
+
+static void imx219_get_binning(struct v4l2_subdev_state *state, u8 *bin_h,
+ u8 *bin_v)
+{
+ const struct v4l2_mbus_framefmt *format =
+ v4l2_subdev_state_get_format(state, 0);
+ const struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0);
+ u32 hbin = crop->width / format->width;
+ u32 vbin = crop->height / format->height;
+
+ *bin_h = IMX219_BINNING_NONE;
+ *bin_v = IMX219_BINNING_NONE;
+
+ /*
+ * Use analog binning only if both dimensions are binned, as it crops
+ * the other dimension.
+ */
+ if (hbin == 2 && vbin == 2) {
+ *bin_h = IMX219_BINNING_X2_ANALOG;
+ *bin_v = IMX219_BINNING_X2_ANALOG;
+
+ return;
+ }
+
+ if (hbin == 2)
+ *bin_h = IMX219_BINNING_X2;
+ if (vbin == 2)
+ *bin_v = IMX219_BINNING_X2;
+}
+
+static inline u32 imx219_get_rate_factor(struct v4l2_subdev_state *state)
+{
+ u8 bin_h, bin_v;
+
+ imx219_get_binning(state, &bin_h, &bin_v);
+
+ return (bin_h & bin_v) == IMX219_BINNING_X2_ANALOG ? 2 : 1;
+}
+
/* -----------------------------------------------------------------------------
* Controls
*/
@@ -370,10 +449,12 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd);
const struct v4l2_mbus_framefmt *format;
struct v4l2_subdev_state *state;
+ u32 rate_factor;
int ret = 0;
state = v4l2_subdev_get_locked_active_state(&imx219->sd);
format = v4l2_subdev_state_get_format(state, 0);
+ rate_factor = imx219_get_rate_factor(state);
if (ctrl->id == V4L2_CID_VBLANK) {
int exposure_max, exposure_def;
@@ -402,7 +483,7 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
break;
case V4L2_CID_EXPOSURE:
cci_write(imx219->regmap, IMX219_REG_EXPOSURE,
- ctrl->val, &ret);
+ ctrl->val / rate_factor, &ret);
break;
case V4L2_CID_DIGITAL_GAIN:
cci_write(imx219->regmap, IMX219_REG_DIGITAL_GAIN,
@@ -418,8 +499,12 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
imx219->hflip->val | imx219->vflip->val << 1, &ret);
break;
case V4L2_CID_VBLANK:
- cci_write(imx219->regmap, IMX219_REG_VTS,
- format->height + ctrl->val, &ret);
+ cci_write(imx219->regmap, IMX219_REG_FRM_LENGTH_A,
+ (format->height + ctrl->val) / rate_factor, &ret);
+ break;
+ case V4L2_CID_HBLANK:
+ cci_write(imx219->regmap, IMX219_REG_LINE_LENGTH_A,
+ format->width + ctrl->val, &ret);
break;
case V4L2_CID_TEST_PATTERN_RED:
cci_write(imx219->regmap, IMX219_REG_TESTP_RED,
@@ -466,7 +551,7 @@ static int imx219_init_controls(struct imx219 *imx219)
const struct imx219_mode *mode = &supported_modes[0];
struct v4l2_ctrl_handler *ctrl_hdlr;
struct v4l2_fwnode_device_properties props;
- int exposure_max, exposure_def, hblank;
+ int exposure_max, exposure_def;
int i, ret;
ctrl_hdlr = &imx219->ctrl_handler;
@@ -490,18 +575,17 @@ static int imx219_init_controls(struct imx219 *imx219)
if (imx219->link_freq)
imx219->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
- /* Initial vblank/hblank/exposure parameters based on current mode */
+ /* Initial blanking and exposure. Limits are updated during set_fmt */
imx219->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops,
V4L2_CID_VBLANK, IMX219_VBLANK_MIN,
- IMX219_VTS_MAX - mode->height, 1,
- mode->vts_def - mode->height);
- hblank = IMX219_PPL_DEFAULT - mode->width;
+ IMX219_FLL_MAX - mode->height, 1,
+ mode->fll_def - mode->height);
imx219->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops,
- V4L2_CID_HBLANK, hblank, hblank,
- 1, hblank);
- if (imx219->hblank)
- imx219->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
- exposure_max = mode->vts_def - 4;
+ V4L2_CID_HBLANK,
+ IMX219_LLP_MIN - mode->width,
+ IMX219_LLP_MAX - mode->width, 1,
+ IMX219_LLP_MIN - mode->width);
+ exposure_max = mode->fll_def - 4;
exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ?
exposure_max : IMX219_EXPOSURE_DEFAULT;
imx219->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops,
@@ -587,29 +671,13 @@ static int imx219_set_framefmt(struct imx219 *imx219,
{
const struct v4l2_mbus_framefmt *format;
const struct v4l2_rect *crop;
- unsigned int bpp;
- u64 bin_h, bin_v;
+ u8 bin_h, bin_v;
+ u32 bpp;
int ret = 0;
format = v4l2_subdev_state_get_format(state, 0);
crop = v4l2_subdev_state_get_crop(state, 0);
-
- switch (format->code) {
- case MEDIA_BUS_FMT_SRGGB8_1X8:
- case MEDIA_BUS_FMT_SGRBG8_1X8:
- case MEDIA_BUS_FMT_SGBRG8_1X8:
- case MEDIA_BUS_FMT_SBGGR8_1X8:
- bpp = 8;
- break;
-
- case MEDIA_BUS_FMT_SRGGB10_1X10:
- case MEDIA_BUS_FMT_SGRBG10_1X10:
- case MEDIA_BUS_FMT_SGBRG10_1X10:
- case MEDIA_BUS_FMT_SBGGR10_1X10:
- default:
- bpp = 10;
- break;
- }
+ bpp = imx219_get_format_bpp(format);
cci_write(imx219->regmap, IMX219_REG_X_ADD_STA_A,
crop->left - IMX219_PIXEL_ARRAY_LEFT, &ret);
@@ -620,26 +688,7 @@ static int imx219_set_framefmt(struct imx219 *imx219,
cci_write(imx219->regmap, IMX219_REG_Y_ADD_END_A,
crop->top - IMX219_PIXEL_ARRAY_TOP + crop->height - 1, &ret);
- switch (crop->width / format->width) {
- case 1:
- default:
- bin_h = IMX219_BINNING_NONE;
- break;
- case 2:
- bin_h = bpp == 8 ? IMX219_BINNING_X2_ANALOG : IMX219_BINNING_X2;
- break;
- }
-
- switch (crop->height / format->height) {
- case 1:
- default:
- bin_v = IMX219_BINNING_NONE;
- break;
- case 2:
- bin_v = bpp == 8 ? IMX219_BINNING_X2_ANALOG : IMX219_BINNING_X2;
- break;
- }
-
+ imx219_get_binning(state, &bin_h, &bin_v);
cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_H, bin_h, &ret);
cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_V, bin_v, &ret);
@@ -662,9 +711,11 @@ static int imx219_set_framefmt(struct imx219 *imx219,
static int imx219_configure_lanes(struct imx219 *imx219)
{
- return cci_write(imx219->regmap, IMX219_REG_CSI_LANE_MODE,
- imx219->lanes == 2 ? IMX219_CSI_2_LANE_MODE :
- IMX219_CSI_4_LANE_MODE, NULL);
+ /* Write the appropriate PLL settings for the number of MIPI lanes */
+ return cci_multi_reg_write(imx219->regmap,
+ imx219->lanes == 2 ? imx219_2lane_regs : imx219_4lane_regs,
+ imx219->lanes == 2 ? ARRAY_SIZE(imx219_2lane_regs) :
+ ARRAY_SIZE(imx219_4lane_regs), NULL);
};
static int imx219_start_streaming(struct imx219 *imx219,
@@ -815,7 +866,11 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
const struct imx219_mode *mode;
struct v4l2_mbus_framefmt *format;
struct v4l2_rect *crop;
- unsigned int bin_h, bin_v;
+ u8 bin_h, bin_v;
+ u32 prev_line_len;
+
+ format = v4l2_subdev_state_get_format(state, 0);
+ prev_line_len = format->width + imx219->hblank->val;
mode = v4l2_find_nearest_size(supported_modes,
ARRAY_SIZE(supported_modes),
@@ -823,8 +878,6 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
fmt->format.width, fmt->format.height);
imx219_update_pad_format(imx219, mode, &fmt->format, fmt->format.code);
-
- format = v4l2_subdev_state_get_format(state, 0);
*format = fmt->format;
/*
@@ -843,30 +896,51 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
int exposure_max;
int exposure_def;
- int hblank;
+ int hblank, llp_min;
+ int pixel_rate;
/* Update limits and set FPS to default */
__v4l2_ctrl_modify_range(imx219->vblank, IMX219_VBLANK_MIN,
- IMX219_VTS_MAX - mode->height, 1,
- mode->vts_def - mode->height);
+ IMX219_FLL_MAX - mode->height, 1,
+ mode->fll_def - mode->height);
__v4l2_ctrl_s_ctrl(imx219->vblank,
- mode->vts_def - mode->height);
+ mode->fll_def - mode->height);
/* Update max exposure while meeting expected vblanking */
- exposure_max = mode->vts_def - 4;
+ exposure_max = mode->fll_def - 4;
exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ?
exposure_max : IMX219_EXPOSURE_DEFAULT;
__v4l2_ctrl_modify_range(imx219->exposure,
imx219->exposure->minimum,
exposure_max, imx219->exposure->step,
exposure_def);
+
/*
- * Currently PPL is fixed to IMX219_PPL_DEFAULT, so hblank
- * depends on mode->width only, and is not changeble in any
- * way other than changing the mode.
+ * With analog binning the default minimum line length of 3448
+ * can cause artefacts with RAW10 formats, because the ADC
+ * operates on two lines together. So we switch to a higher
+ * minimum of 3560.
*/
- hblank = IMX219_PPL_DEFAULT - mode->width;
- __v4l2_ctrl_modify_range(imx219->hblank, hblank, hblank, 1,
- hblank);
+ imx219_get_binning(state, &bin_h, &bin_v);
+ llp_min = (bin_h & bin_v) == IMX219_BINNING_X2_ANALOG ?
+ IMX219_BINNED_LLP_MIN : IMX219_LLP_MIN;
+ __v4l2_ctrl_modify_range(imx219->hblank, llp_min - mode->width,
+ IMX219_LLP_MAX - mode->width, 1,
+ llp_min - mode->width);
+ /*
+ * Retain PPL setting from previous mode so that the
+ * line time does not change on a mode change.
+ * Limits have to be recomputed as the controls define
+ * the blanking only, so PPL values need to have the
+ * mode width subtracted.
+ */
+ hblank = prev_line_len - mode->width;
+ __v4l2_ctrl_s_ctrl(imx219->hblank, hblank);
+
+ /* Scale the pixel rate based on the mode specific factor */
+ pixel_rate = imx219_get_pixel_rate(imx219) *
+ imx219_get_rate_factor(state);
+ __v4l2_ctrl_modify_range(imx219->pixel_rate, pixel_rate,
+ pixel_rate, 1, pixel_rate);
}
return 0;
@@ -877,10 +951,9 @@ static int imx219_get_selection(struct v4l2_subdev *sd,
struct v4l2_subdev_selection *sel)
{
switch (sel->target) {
- case V4L2_SEL_TGT_CROP: {
+ case V4L2_SEL_TGT_CROP:
sel->r = *v4l2_subdev_state_get_crop(state, 0);
return 0;
- }
case V4L2_SEL_TGT_NATIVE_SIZE:
sel->r.top = 0;
@@ -1035,6 +1108,7 @@ static int imx219_check_hwcfg(struct device *dev, struct imx219 *imx219)
struct v4l2_fwnode_endpoint ep_cfg = {
.bus_type = V4L2_MBUS_CSI2_DPHY
};
+ unsigned long link_freq_bitmap;
int ret = -EINVAL;
endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
@@ -1056,23 +1130,40 @@ static int imx219_check_hwcfg(struct device *dev, struct imx219 *imx219)
imx219->lanes = ep_cfg.bus.mipi_csi2.num_data_lanes;
/* Check the link frequency set in device tree */
- if (!ep_cfg.nr_of_link_frequencies) {
- dev_err_probe(dev, -EINVAL,
- "link-frequency property not found in DT\n");
- goto error_out;
+ switch (imx219->lanes) {
+ case 2:
+ ret = v4l2_link_freq_to_bitmap(dev,
+ ep_cfg.link_frequencies,
+ ep_cfg.nr_of_link_frequencies,
+ imx219_link_freq_menu,
+ ARRAY_SIZE(imx219_link_freq_menu),
+ &link_freq_bitmap);
+ break;
+ case 4:
+ ret = v4l2_link_freq_to_bitmap(dev,
+ ep_cfg.link_frequencies,
+ ep_cfg.nr_of_link_frequencies,
+ imx219_link_freq_4lane_menu,
+ ARRAY_SIZE(imx219_link_freq_4lane_menu),
+ &link_freq_bitmap);
+
+ if (!ret && (link_freq_bitmap & BIT(1))) {
+ dev_warn(dev, "Link frequency of %d not supported, but has been incorrectly advertised previously\n",
+ IMX219_DEFAULT_LINK_FREQ_4LANE_UNSUPPORTED);
+ dev_warn(dev, "Using link frequency of %d\n",
+ IMX219_DEFAULT_LINK_FREQ_4LANE);
+ link_freq_bitmap |= BIT(0);
+ }
+ break;
}
- if (ep_cfg.nr_of_link_frequencies != 1 ||
- (ep_cfg.link_frequencies[0] != ((imx219->lanes == 2) ?
- IMX219_DEFAULT_LINK_FREQ : IMX219_DEFAULT_LINK_FREQ_4LANE))) {
+ if (ret || !(link_freq_bitmap & BIT(0))) {
+ ret = -EINVAL;
dev_err_probe(dev, -EINVAL,
"Link frequency not supported: %lld\n",
ep_cfg.link_frequencies[0]);
- goto error_out;
}
- ret = 0;
-
error_out:
v4l2_fwnode_endpoint_free(&ep_cfg);
fwnode_handle_put(endpoint);
@@ -1178,6 +1269,9 @@ static int imx219_probe(struct i2c_client *client)
goto error_media_entity;
}
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
ret = v4l2_async_register_subdev_sensor(&imx219->sd);
if (ret < 0) {
dev_err_probe(dev, ret,
@@ -1185,15 +1279,14 @@ static int imx219_probe(struct i2c_client *client)
goto error_subdev_cleanup;
}
- /* Enable runtime PM and turn off the device */
- pm_runtime_set_active(dev);
- pm_runtime_enable(dev);
pm_runtime_idle(dev);
return 0;
error_subdev_cleanup:
v4l2_subdev_cleanup(&imx219->sd);
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
error_media_entity:
media_entity_cleanup(&imx219->sd.entity);
@@ -1218,9 +1311,10 @@ static void imx219_remove(struct i2c_client *client)
imx219_free_controls(imx219);
pm_runtime_disable(&client->dev);
- if (!pm_runtime_status_suspended(&client->dev))
+ if (!pm_runtime_status_suspended(&client->dev)) {
imx219_power_off(&client->dev);
- pm_runtime_set_suspended(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+ }
}
static const struct of_device_id imx219_dt_ids[] = {