summaryrefslogtreecommitdiff
path: root/drivers/media/i2c/ov7670.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/i2c/ov7670.c')
-rw-r--r--drivers/media/i2c/ov7670.c665
1 files changed, 473 insertions, 192 deletions
diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c
index 7270c68ed18a..0cb96b6c9990 100644
--- a/drivers/media/i2c/ov7670.c
+++ b/drivers/media/i2c/ov7670.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* A V4L2 driver for OmniVision OV7670 cameras.
*
@@ -6,21 +7,20 @@
* McClelland's ovcamchip code.
*
* Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
- *
- * This file may be distributed under the terms of the GNU General
- * Public License, version 2.
*/
#include <linux/clk.h>
#include <linux/init.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
-#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-image-sizes.h>
#include <media/i2c/ov7670.h>
@@ -158,10 +158,15 @@ MODULE_PARM_DESC(debug, "Debug level (0-1)");
#define REG_GFIX 0x69 /* Fix gain control */
#define REG_DBLV 0x6b /* PLL control an debugging */
-#define DBLV_BYPASS 0x00 /* Bypass PLL */
-#define DBLV_X4 0x01 /* clock x4 */
-#define DBLV_X6 0x10 /* clock x6 */
-#define DBLV_X8 0x11 /* clock x8 */
+#define DBLV_BYPASS 0x0a /* Bypass PLL */
+#define DBLV_X4 0x4a /* clock x4 */
+#define DBLV_X6 0x8a /* clock x6 */
+#define DBLV_X8 0xca /* clock x8 */
+
+#define REG_SCALING_XSC 0x70 /* Test pattern and horizontal scale factor */
+#define TEST_PATTTERN_0 0x80
+#define REG_SCALING_YSC 0x71 /* Test pattern and vertical scale factor */
+#define TEST_PATTTERN_1 0x80
#define REG_REG76 0x76 /* OV's name */
#define R76_BLKPCOR 0x80 /* Black pixel correction enable */
@@ -182,11 +187,6 @@ MODULE_PARM_DESC(debug, "Debug level (0-1)");
#define REG_HAECC7 0xaa /* Hist AEC/AGC control 7 */
#define REG_BD60MAX 0xab /* 60hz banding step limit */
-enum ov7670_model {
- MODEL_OV7670 = 0,
- MODEL_OV7675,
-};
-
struct ov7670_win_size {
int width;
int height;
@@ -213,6 +213,7 @@ struct ov7670_devtype {
struct ov7670_format_struct; /* coming later */
struct ov7670_info {
struct v4l2_subdev sd;
+ struct media_pad pad;
struct v4l2_ctrl_handler hdl;
struct {
/* gain cluster */
@@ -229,10 +230,14 @@ struct ov7670_info {
struct v4l2_ctrl *saturation;
struct v4l2_ctrl *hue;
};
+ struct v4l2_mbus_framefmt format;
struct ov7670_format_struct *fmt; /* Current format */
+ struct ov7670_win_size *wsize;
struct clk *clk;
+ int on;
struct gpio_desc *resetb_gpio;
struct gpio_desc *pwdn_gpio;
+ unsigned int mbus_config; /* Media bus configuration flags */
int min_width; /* Filter out smaller sizes */
int min_height; /* Filter out smaller sizes */
int clock_speed; /* External clock speed (MHz) */
@@ -288,7 +293,8 @@ static struct regval_list ov7670_default_regs[] = {
{ REG_COM3, 0 }, { REG_COM14, 0 },
/* Mystery scaling numbers */
- { 0x70, 0x3a }, { 0x71, 0x35 },
+ { REG_SCALING_XSC, 0x3a },
+ { REG_SCALING_YSC, 0x35 },
{ 0x72, 0x11 }, { 0x73, 0xf0 },
{ 0xa2, 0x02 }, { REG_COM10, 0x0 },
@@ -402,12 +408,12 @@ static struct regval_list ov7670_fmt_yuv422[] = {
{ REG_COM1, 0 }, /* CCIR601 */
{ REG_COM15, COM15_R00FF },
{ REG_COM9, 0x48 }, /* 32x gain ceiling; 0x8 is reserved bit */
- { 0x4f, 0x80 }, /* "matrix coefficient 1" */
- { 0x50, 0x80 }, /* "matrix coefficient 2" */
+ { 0x4f, 0x80 }, /* "matrix coefficient 1" */
+ { 0x50, 0x80 }, /* "matrix coefficient 2" */
{ 0x51, 0 }, /* vb */
- { 0x52, 0x22 }, /* "matrix coefficient 4" */
- { 0x53, 0x5e }, /* "matrix coefficient 5" */
- { 0x54, 0x80 }, /* "matrix coefficient 6" */
+ { 0x52, 0x22 }, /* "matrix coefficient 4" */
+ { 0x53, 0x5e }, /* "matrix coefficient 5" */
+ { 0x54, 0x80 }, /* "matrix coefficient 6" */
{ REG_COM13, COM13_GAMMA|COM13_UVSAT },
{ 0xff, 0xff },
};
@@ -417,13 +423,13 @@ static struct regval_list ov7670_fmt_rgb565[] = {
{ REG_RGB444, 0 }, /* No RGB444 please */
{ REG_COM1, 0x0 }, /* CCIR601 */
{ REG_COM15, COM15_RGB565 },
- { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */
- { 0x4f, 0xb3 }, /* "matrix coefficient 1" */
- { 0x50, 0xb3 }, /* "matrix coefficient 2" */
+ { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */
+ { 0x4f, 0xb3 }, /* "matrix coefficient 1" */
+ { 0x50, 0xb3 }, /* "matrix coefficient 2" */
{ 0x51, 0 }, /* vb */
- { 0x52, 0x3d }, /* "matrix coefficient 4" */
- { 0x53, 0xa7 }, /* "matrix coefficient 5" */
- { 0x54, 0xe4 }, /* "matrix coefficient 6" */
+ { 0x52, 0x3d }, /* "matrix coefficient 4" */
+ { 0x53, 0xa7 }, /* "matrix coefficient 5" */
+ { 0x54, 0xe4 }, /* "matrix coefficient 6" */
{ REG_COM13, COM13_GAMMA|COM13_UVSAT },
{ 0xff, 0xff },
};
@@ -433,13 +439,13 @@ static struct regval_list ov7670_fmt_rgb444[] = {
{ REG_RGB444, R444_ENABLE }, /* Enable xxxxrrrr ggggbbbb */
{ REG_COM1, 0x0 }, /* CCIR601 */
{ REG_COM15, COM15_R01FE|COM15_RGB565 }, /* Data range needed? */
- { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */
- { 0x4f, 0xb3 }, /* "matrix coefficient 1" */
- { 0x50, 0xb3 }, /* "matrix coefficient 2" */
+ { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */
+ { 0x4f, 0xb3 }, /* "matrix coefficient 1" */
+ { 0x50, 0xb3 }, /* "matrix coefficient 2" */
{ 0x51, 0 }, /* vb */
- { 0x52, 0x3d }, /* "matrix coefficient 4" */
- { 0x53, 0xa7 }, /* "matrix coefficient 5" */
- { 0x54, 0xe4 }, /* "matrix coefficient 6" */
+ { 0x52, 0x3d }, /* "matrix coefficient 4" */
+ { 0x53, 0xa7 }, /* "matrix coefficient 5" */
+ { 0x54, 0xe4 }, /* "matrix coefficient 6" */
{ REG_COM13, COM13_GAMMA|COM13_UVSAT|0x2 }, /* Magic rsvd bit */
{ 0xff, 0xff },
};
@@ -548,6 +554,7 @@ static int ov7670_read(struct v4l2_subdev *sd, unsigned char reg,
unsigned char *value)
{
struct ov7670_info *info = to_state(sd);
+
if (info->use_smbus)
return ov7670_read_smbus(sd, reg, value);
else
@@ -558,12 +565,26 @@ static int ov7670_write(struct v4l2_subdev *sd, unsigned char reg,
unsigned char value)
{
struct ov7670_info *info = to_state(sd);
+
if (info->use_smbus)
return ov7670_write_smbus(sd, reg, value);
else
return ov7670_write_i2c(sd, reg, value);
}
+static int ov7670_update_bits(struct v4l2_subdev *sd, unsigned char reg,
+ unsigned char mask, unsigned char value)
+{
+ unsigned char orig;
+ int ret;
+
+ ret = ov7670_read(sd, reg, &orig);
+ if (ret)
+ return ret;
+
+ return ov7670_write(sd, reg, (orig & ~mask) | (value & mask));
+}
+
/*
* Write a list of register settings; ff/ff stops the process.
*/
@@ -571,6 +592,7 @@ static int ov7670_write_array(struct v4l2_subdev *sd, struct regval_list *vals)
{
while (vals->reg_num != 0xff || vals->value != 0xff) {
int ret = ov7670_write(sd, vals->reg_num, vals->value);
+
if (ret < 0)
return ret;
vals++;
@@ -644,7 +666,7 @@ static struct ov7670_format_struct {
{
.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
.colorspace = V4L2_COLORSPACE_SRGB,
- .regs = ov7670_fmt_yuv422,
+ .regs = ov7670_fmt_yuv422,
.cmatrix = { 128, -128, 0, -34, -94, 128 },
},
{
@@ -662,7 +684,7 @@ static struct ov7670_format_struct {
{
.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
.colorspace = V4L2_COLORSPACE_SRGB,
- .regs = ov7670_fmt_raw,
+ .regs = ov7670_fmt_raw,
.cmatrix = { 0, 0, 0, 0, 0, 0 },
},
};
@@ -784,13 +806,25 @@ static void ov7675_get_framerate(struct v4l2_subdev *sd,
(4 * clkrc);
}
+static int ov7675_apply_framerate(struct v4l2_subdev *sd)
+{
+ struct ov7670_info *info = to_state(sd);
+ int ret;
+
+ ret = ov7670_write(sd, REG_CLKRC, info->clkrc);
+ if (ret < 0)
+ return ret;
+
+ return ov7670_write(sd, REG_DBLV,
+ info->pll_bypass ? DBLV_BYPASS : DBLV_X4);
+}
+
static int ov7675_set_framerate(struct v4l2_subdev *sd,
struct v4l2_fract *tpf)
{
struct ov7670_info *info = to_state(sd);
u32 clkrc;
int pll_factor;
- int ret;
/*
* The formula is fps = 5/4*pixclk for YUV/RGB and
@@ -799,19 +833,10 @@ static int ov7675_set_framerate(struct v4l2_subdev *sd,
* pixclk = clock_speed / (clkrc + 1) * PLLfactor
*
*/
- if (info->pll_bypass) {
- pll_factor = 1;
- ret = ov7670_write(sd, REG_DBLV, DBLV_BYPASS);
- } else {
- pll_factor = PLL_FACTOR;
- ret = ov7670_write(sd, REG_DBLV, DBLV_X4);
- }
- if (ret < 0)
- return ret;
-
if (tpf->numerator == 0 || tpf->denominator == 0) {
clkrc = 0;
} else {
+ pll_factor = info->pll_bypass ? 1 : PLL_FACTOR;
clkrc = (5 * pll_factor * info->clock_speed * tpf->numerator) /
(4 * tpf->denominator);
if (info->fmt->mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8)
@@ -833,11 +858,15 @@ static int ov7675_set_framerate(struct v4l2_subdev *sd,
/* Recalculate frame rate */
ov7675_get_framerate(sd, tpf);
- ret = ov7670_write(sd, REG_CLKRC, info->clkrc);
- if (ret < 0)
- return ret;
+ /*
+ * If the device is not powered up by the host driver do
+ * not apply any changes to H/W at this time. Instead
+ * the framerate will be restored right after power-up.
+ */
+ if (info->on)
+ return ov7675_apply_framerate(sd);
- return ov7670_write(sd, REG_DBLV, DBLV_X4);
+ return 0;
}
static void ov7670_get_framerate_legacy(struct v4l2_subdev *sd,
@@ -868,7 +897,16 @@ static int ov7670_set_framerate_legacy(struct v4l2_subdev *sd,
info->clkrc = (info->clkrc & 0x80) | div;
tpf->numerator = 1;
tpf->denominator = info->clock_speed / div;
- return ov7670_write(sd, REG_CLKRC, info->clkrc);
+
+ /*
+ * If the device is not powered up by the host driver do
+ * not apply any changes to H/W at this time. Instead
+ * the framerate will be restored right after power-up.
+ */
+ if (info->on)
+ return ov7670_write(sd, REG_CLKRC, info->clkrc);
+
+ return 0;
}
/*
@@ -879,32 +917,43 @@ static int ov7670_set_hw(struct v4l2_subdev *sd, int hstart, int hstop,
{
int ret;
unsigned char v;
-/*
- * Horizontal: 11 bits, top 8 live in hstart and hstop. Bottom 3 of
- * hstart are in href[2:0], bottom 3 of hstop in href[5:3]. There is
- * a mystery "edge offset" value in the top two bits of href.
- */
- ret = ov7670_write(sd, REG_HSTART, (hstart >> 3) & 0xff);
- ret += ov7670_write(sd, REG_HSTOP, (hstop >> 3) & 0xff);
- ret += ov7670_read(sd, REG_HREF, &v);
+ /*
+ * Horizontal: 11 bits, top 8 live in hstart and hstop. Bottom 3 of
+ * hstart are in href[2:0], bottom 3 of hstop in href[5:3]. There is
+ * a mystery "edge offset" value in the top two bits of href.
+ */
+ ret = ov7670_write(sd, REG_HSTART, (hstart >> 3) & 0xff);
+ if (ret)
+ return ret;
+ ret = ov7670_write(sd, REG_HSTOP, (hstop >> 3) & 0xff);
+ if (ret)
+ return ret;
+ ret = ov7670_read(sd, REG_HREF, &v);
+ if (ret)
+ return ret;
v = (v & 0xc0) | ((hstop & 0x7) << 3) | (hstart & 0x7);
msleep(10);
- ret += ov7670_write(sd, REG_HREF, v);
-/*
- * Vertical: similar arrangement, but only 10 bits.
- */
- ret += ov7670_write(sd, REG_VSTART, (vstart >> 2) & 0xff);
- ret += ov7670_write(sd, REG_VSTOP, (vstop >> 2) & 0xff);
- ret += ov7670_read(sd, REG_VREF, &v);
+ ret = ov7670_write(sd, REG_HREF, v);
+ if (ret)
+ return ret;
+ /* Vertical: similar arrangement, but only 10 bits. */
+ ret = ov7670_write(sd, REG_VSTART, (vstart >> 2) & 0xff);
+ if (ret)
+ return ret;
+ ret = ov7670_write(sd, REG_VSTOP, (vstop >> 2) & 0xff);
+ if (ret)
+ return ret;
+ ret = ov7670_read(sd, REG_VREF, &v);
+ if (ret)
+ return ret;
v = (v & 0xf0) | ((vstop & 0x3) << 2) | (vstart & 0x3);
msleep(10);
- ret += ov7670_write(sd, REG_VREF, v);
- return ret;
+ return ov7670_write(sd, REG_VREF, v);
}
static int ov7670_enum_mbus_code(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
if (code->pad || code->index >= N_OV7670_FMTS)
@@ -972,56 +1021,61 @@ static int ov7670_try_fmt_internal(struct v4l2_subdev *sd,
fmt->width = wsize->width;
fmt->height = wsize->height;
fmt->colorspace = ov7670_formats[index].colorspace;
+
+ info->format = *fmt;
+
return 0;
}
-/*
- * Set a format.
- */
-static int ov7670_set_fmt(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *format)
+static int ov7670_apply_fmt(struct v4l2_subdev *sd)
{
- struct ov7670_format_struct *ovfmt;
- struct ov7670_win_size *wsize;
struct ov7670_info *info = to_state(sd);
- unsigned char com7;
+ struct ov7670_win_size *wsize = info->wsize;
+ unsigned char com7, com10 = 0;
int ret;
- if (format->pad)
- return -EINVAL;
-
- if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
- ret = ov7670_try_fmt_internal(sd, &format->format, NULL, NULL);
- if (ret)
- return ret;
- cfg->try_fmt = format->format;
- return 0;
- }
-
- ret = ov7670_try_fmt_internal(sd, &format->format, &ovfmt, &wsize);
-
- if (ret)
- return ret;
/*
* COM7 is a pain in the ass, it doesn't like to be read then
* quickly written afterward. But we have everything we need
* to set it absolutely here, as long as the format-specific
* register sets list it first.
*/
- com7 = ovfmt->regs[0].value;
+ com7 = info->fmt->regs[0].value;
com7 |= wsize->com7_bit;
- ov7670_write(sd, REG_COM7, com7);
+ ret = ov7670_write(sd, REG_COM7, com7);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure the media bus through COM10 register
+ */
+ if (info->mbus_config & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+ com10 |= COM10_VS_NEG;
+ if (info->mbus_config & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+ com10 |= COM10_HREF_REV;
+ if (info->pclk_hb_disable)
+ com10 |= COM10_PCLK_HB;
+ ret = ov7670_write(sd, REG_COM10, com10);
+ if (ret)
+ return ret;
+
/*
* Now write the rest of the array. Also store start/stops
*/
- ov7670_write_array(sd, ovfmt->regs + 1);
- ov7670_set_hw(sd, wsize->hstart, wsize->hstop, wsize->vstart,
- wsize->vstop);
- ret = 0;
- if (wsize->regs)
+ ret = ov7670_write_array(sd, info->fmt->regs + 1);
+ if (ret)
+ return ret;
+
+ ret = ov7670_set_hw(sd, wsize->hstart, wsize->hstop, wsize->vstart,
+ wsize->vstop);
+ if (ret)
+ return ret;
+
+ if (wsize->regs) {
ret = ov7670_write_array(sd, wsize->regs);
- info->fmt = ovfmt;
+ if (ret)
+ return ret;
+ }
/*
* If we're running RGB565, we must rewrite clkrc after setting
@@ -1033,8 +1087,66 @@ static int ov7670_set_fmt(struct v4l2_subdev *sd,
* to write it unconditionally, and that will make the frame
* rate persistent too.
*/
- if (ret == 0)
- ret = ov7670_write(sd, REG_CLKRC, info->clkrc);
+ ret = ov7670_write(sd, REG_CLKRC, info->clkrc);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Set a format.
+ */
+static int ov7670_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct ov7670_info *info = to_state(sd);
+ struct v4l2_mbus_framefmt *mbus_fmt;
+ int ret;
+
+ if (format->pad)
+ return -EINVAL;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ ret = ov7670_try_fmt_internal(sd, &format->format, NULL, NULL);
+ if (ret)
+ return ret;
+ mbus_fmt = v4l2_subdev_state_get_format(sd_state, format->pad);
+ *mbus_fmt = format->format;
+ return 0;
+ }
+
+ ret = ov7670_try_fmt_internal(sd, &format->format, &info->fmt, &info->wsize);
+ if (ret)
+ return ret;
+
+ /*
+ * If the device is not powered up by the host driver do
+ * not apply any changes to H/W at this time. Instead
+ * the frame format will be restored right after power-up.
+ */
+ if (info->on)
+ return ov7670_apply_fmt(sd);
+
+ return 0;
+}
+
+static int ov7670_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct ov7670_info *info = to_state(sd);
+ struct v4l2_mbus_framefmt *mbus_fmt;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ mbus_fmt = v4l2_subdev_state_get_format(sd_state, 0);
+ format->format = *mbus_fmt;
+ return 0;
+ } else {
+ format->format = info->format;
+ }
+
return 0;
}
@@ -1042,30 +1154,38 @@ static int ov7670_set_fmt(struct v4l2_subdev *sd,
* Implement G/S_PARM. There is a "high quality" mode we could try
* to do someday; for now, we just do the frame rate tweak.
*/
-static int ov7670_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms)
+static int ov7670_get_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_interval *ival)
{
- struct v4l2_captureparm *cp = &parms->parm.capture;
struct ov7670_info *info = to_state(sd);
- if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ /*
+ * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2
+ * subdev active state API.
+ */
+ if (ival->which != V4L2_SUBDEV_FORMAT_ACTIVE)
return -EINVAL;
- cp->capability = V4L2_CAP_TIMEPERFRAME;
- info->devtype->get_framerate(sd, &cp->timeperframe);
+ info->devtype->get_framerate(sd, &ival->interval);
return 0;
}
-static int ov7670_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms)
+static int ov7670_set_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_interval *ival)
{
- struct v4l2_captureparm *cp = &parms->parm.capture;
- struct v4l2_fract *tpf = &cp->timeperframe;
+ struct v4l2_fract *tpf = &ival->interval;
struct ov7670_info *info = to_state(sd);
- if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ /*
+ * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2
+ * subdev active state API.
+ */
+ if (ival->which != V4L2_SUBDEV_FORMAT_ACTIVE)
return -EINVAL;
- cp->capability = V4L2_CAP_TIMEPERFRAME;
return info->devtype->set_framerate(sd, tpf);
}
@@ -1079,7 +1199,7 @@ static int ov7670_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms)
static int ov7670_frame_rates[] = { 30, 15, 10, 5, 1 };
static int ov7670_enum_frame_interval(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_interval_enum *fie)
{
struct ov7670_info *info = to_state(sd);
@@ -1118,7 +1238,7 @@ static int ov7670_enum_frame_interval(struct v4l2_subdev *sd,
* Frame size enumeration
*/
static int ov7670_enum_frame_size(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct ov7670_info *info = to_state(sd);
@@ -1136,6 +1256,7 @@ static int ov7670_enum_frame_size(struct v4l2_subdev *sd,
*/
for (i = 0; i < n_win_sizes; i++) {
struct ov7670_win_size *win = &info->devtype->win_sizes[i];
+
if (info->min_width && win->width < info->min_width)
continue;
if (info->min_height && win->height < info->min_height)
@@ -1176,17 +1297,17 @@ static int ov7670_store_cmatrix(struct v4l2_subdev *sd,
raw = 0xff;
else
raw = (-1 * matrix[i]) & 0xff;
- }
- else {
+ } else {
if (matrix[i] > 255)
raw = 0xff;
else
raw = matrix[i] & 0xff;
}
- ret += ov7670_write(sd, REG_CMATRIX_BASE + i, raw);
+ ret = ov7670_write(sd, REG_CMATRIX_BASE + i, raw);
+ if (ret)
+ return ret;
}
- ret += ov7670_write(sd, REG_CMATRIX_SIGN, signbits);
- return ret;
+ return ov7670_write(sd, REG_CMATRIX_SIGN, signbits);
}
@@ -1272,11 +1393,9 @@ static int ov7670_s_sat_hue(struct v4l2_subdev *sd, int sat, int hue)
{
struct ov7670_info *info = to_state(sd);
int matrix[CMATRIX_LEN];
- int ret;
ov7670_calc_cmatrix(info, matrix, sat, hue);
- ret = ov7670_store_cmatrix(sd, matrix);
- return ret;
+ return ov7670_store_cmatrix(sd, matrix);
}
@@ -1294,14 +1413,12 @@ static unsigned char ov7670_abs_to_sm(unsigned char v)
static int ov7670_s_brightness(struct v4l2_subdev *sd, int value)
{
unsigned char com8 = 0, v;
- int ret;
ov7670_read(sd, REG_COM8, &com8);
com8 &= ~COM8_AEC;
ov7670_write(sd, REG_COM8, com8);
v = ov7670_abs_to_sm(value);
- ret = ov7670_write(sd, REG_BRIGHT, v);
- return ret;
+ return ov7670_write(sd, REG_BRIGHT, v);
}
static int ov7670_s_contrast(struct v4l2_subdev *sd, int value)
@@ -1315,13 +1432,14 @@ static int ov7670_s_hflip(struct v4l2_subdev *sd, int value)
int ret;
ret = ov7670_read(sd, REG_MVFP, &v);
+ if (ret)
+ return ret;
if (value)
v |= MVFP_MIRROR;
else
v &= ~MVFP_MIRROR;
msleep(10); /* FIXME */
- ret += ov7670_write(sd, REG_MVFP, v);
- return ret;
+ return ov7670_write(sd, REG_MVFP, v);
}
static int ov7670_s_vflip(struct v4l2_subdev *sd, int value)
@@ -1330,13 +1448,14 @@ static int ov7670_s_vflip(struct v4l2_subdev *sd, int value)
int ret;
ret = ov7670_read(sd, REG_MVFP, &v);
+ if (ret)
+ return ret;
if (value)
v |= MVFP_FLIP;
else
v &= ~MVFP_FLIP;
msleep(10); /* FIXME */
- ret += ov7670_write(sd, REG_MVFP, v);
- return ret;
+ return ov7670_write(sd, REG_MVFP, v);
}
/*
@@ -1351,8 +1470,10 @@ static int ov7670_g_gain(struct v4l2_subdev *sd, __s32 *value)
unsigned char gain;
ret = ov7670_read(sd, REG_GAIN, &gain);
+ if (ret)
+ return ret;
*value = gain;
- return ret;
+ return 0;
}
static int ov7670_s_gain(struct v4l2_subdev *sd, int value)
@@ -1361,12 +1482,13 @@ static int ov7670_s_gain(struct v4l2_subdev *sd, int value)
unsigned char com8;
ret = ov7670_write(sd, REG_GAIN, value & 0xff);
+ if (ret)
+ return ret;
/* Have to turn off AGC as well */
- if (ret == 0) {
- ret = ov7670_read(sd, REG_COM8, &com8);
- ret = ov7670_write(sd, REG_COM8, com8 & ~COM8_AGC);
- }
- return ret;
+ ret = ov7670_read(sd, REG_COM8, &com8);
+ if (ret)
+ return ret;
+ return ov7670_write(sd, REG_COM8, com8 & ~COM8_AGC);
}
/*
@@ -1431,6 +1553,25 @@ static int ov7670_s_autoexp(struct v4l2_subdev *sd,
return ret;
}
+static const char * const ov7670_test_pattern_menu[] = {
+ "No test output",
+ "Shifting \"1\"",
+ "8-bar color bar",
+ "Fade to gray color bar",
+};
+
+static int ov7670_s_test_pattern(struct v4l2_subdev *sd, int value)
+{
+ int ret;
+
+ ret = ov7670_update_bits(sd, REG_SCALING_XSC, TEST_PATTTERN_0,
+ value & BIT(0) ? TEST_PATTTERN_0 : 0);
+ if (ret)
+ return ret;
+
+ return ov7670_update_bits(sd, REG_SCALING_YSC, TEST_PATTTERN_1,
+ value & BIT(1) ? TEST_PATTTERN_1 : 0);
+}
static int ov7670_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
{
@@ -1477,6 +1618,8 @@ static int ov7670_s_ctrl(struct v4l2_ctrl *ctrl)
return ov7670_s_exp(sd, info->exposure->val);
}
return ov7670_s_autoexp(sd, ctrl->val);
+ case V4L2_CID_TEST_PATTERN:
+ return ov7670_s_test_pattern(sd, ctrl->val);
}
return -EINVAL;
}
@@ -1505,52 +1648,121 @@ static int ov7670_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_regis
}
#endif
+static void ov7670_power_on(struct v4l2_subdev *sd)
+{
+ struct ov7670_info *info = to_state(sd);
+
+ if (info->on)
+ return;
+
+ clk_prepare_enable(info->clk);
+
+ if (info->pwdn_gpio)
+ gpiod_set_value(info->pwdn_gpio, 0);
+ if (info->resetb_gpio) {
+ gpiod_set_value(info->resetb_gpio, 1);
+ usleep_range(500, 1000);
+ gpiod_set_value(info->resetb_gpio, 0);
+ }
+ if (info->pwdn_gpio || info->resetb_gpio || info->clk)
+ usleep_range(3000, 5000);
+
+ info->on = true;
+}
+
+static void ov7670_power_off(struct v4l2_subdev *sd)
+{
+ struct ov7670_info *info = to_state(sd);
+
+ if (!info->on)
+ return;
+
+ clk_disable_unprepare(info->clk);
+
+ if (info->pwdn_gpio)
+ gpiod_set_value(info->pwdn_gpio, 1);
+
+ info->on = false;
+}
+
+static int ov7670_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct ov7670_info *info = to_state(sd);
+
+ if (info->on == on)
+ return 0;
+
+ if (on) {
+ ov7670_power_on(sd);
+ ov7670_init(sd, 0);
+ ov7670_apply_fmt(sd);
+ ov7675_apply_framerate(sd);
+ v4l2_ctrl_handler_setup(&info->hdl);
+ } else {
+ ov7670_power_off(sd);
+ }
+
+ return 0;
+}
+
+static void ov7670_get_default_format(struct v4l2_subdev *sd,
+ struct v4l2_mbus_framefmt *format)
+{
+ struct ov7670_info *info = to_state(sd);
+
+ format->width = info->devtype->win_sizes[0].width;
+ format->height = info->devtype->win_sizes[0].height;
+ format->colorspace = info->fmt->colorspace;
+ format->code = info->fmt->mbus_code;
+ format->field = V4L2_FIELD_NONE;
+}
+
+static int ov7670_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_mbus_framefmt *format =
+ v4l2_subdev_state_get_format(fh->state, 0);
+
+ ov7670_get_default_format(sd, format);
+
+ return 0;
+}
+
/* ----------------------------------------------------------------------- */
static const struct v4l2_subdev_core_ops ov7670_core_ops = {
.reset = ov7670_reset,
.init = ov7670_init,
+ .s_power = ov7670_s_power,
+ .log_status = v4l2_ctrl_subdev_log_status,
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
#ifdef CONFIG_VIDEO_ADV_DEBUG
.g_register = ov7670_g_register,
.s_register = ov7670_s_register,
#endif
};
-static const struct v4l2_subdev_video_ops ov7670_video_ops = {
- .s_parm = ov7670_s_parm,
- .g_parm = ov7670_g_parm,
-};
-
static const struct v4l2_subdev_pad_ops ov7670_pad_ops = {
.enum_frame_interval = ov7670_enum_frame_interval,
.enum_frame_size = ov7670_enum_frame_size,
.enum_mbus_code = ov7670_enum_mbus_code,
+ .get_fmt = ov7670_get_fmt,
.set_fmt = ov7670_set_fmt,
+ .get_frame_interval = ov7670_get_frame_interval,
+ .set_frame_interval = ov7670_set_frame_interval,
};
static const struct v4l2_subdev_ops ov7670_ops = {
.core = &ov7670_core_ops,
- .video = &ov7670_video_ops,
.pad = &ov7670_pad_ops,
};
-/* ----------------------------------------------------------------------- */
-
-static const struct ov7670_devtype ov7670_devdata[] = {
- [MODEL_OV7670] = {
- .win_sizes = ov7670_win_sizes,
- .n_win_sizes = ARRAY_SIZE(ov7670_win_sizes),
- .set_framerate = ov7670_set_framerate_legacy,
- .get_framerate = ov7670_get_framerate_legacy,
- },
- [MODEL_OV7675] = {
- .win_sizes = ov7675_win_sizes,
- .n_win_sizes = ARRAY_SIZE(ov7675_win_sizes),
- .set_framerate = ov7675_set_framerate,
- .get_framerate = ov7675_get_framerate,
- },
+static const struct v4l2_subdev_internal_ops ov7670_subdev_internal_ops = {
+ .open = ov7670_open,
};
+/* ----------------------------------------------------------------------- */
+
static int ov7670_init_gpio(struct i2c_client *client, struct ov7670_info *info)
{
info->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown",
@@ -1572,8 +1784,44 @@ static int ov7670_init_gpio(struct i2c_client *client, struct ov7670_info *info)
return 0;
}
-static int ov7670_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+/*
+ * ov7670_parse_dt() - Parse device tree to collect mbus configuration
+ * properties
+ */
+static int ov7670_parse_dt(struct device *dev,
+ struct ov7670_info *info)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 };
+ struct fwnode_handle *ep;
+ int ret;
+
+ if (!fwnode)
+ return -EINVAL;
+
+ info->pclk_hb_disable = false;
+ if (fwnode_property_present(fwnode, "ov7670,pclk-hb-disable"))
+ info->pclk_hb_disable = true;
+
+ ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+ if (!ep)
+ return -EINVAL;
+
+ ret = v4l2_fwnode_endpoint_parse(ep, &bus_cfg);
+ fwnode_handle_put(ep);
+ if (ret)
+ return ret;
+
+ if (bus_cfg.bus_type != V4L2_MBUS_PARALLEL) {
+ dev_err(dev, "Unsupported media bus type\n");
+ return -EINVAL;
+ }
+ info->mbus_config = bus_cfg.bus.parallel.flags;
+
+ return 0;
+}
+
+static int ov7670_probe(struct i2c_client *client)
{
struct v4l2_fract tpf;
struct v4l2_subdev *sd;
@@ -1586,8 +1834,17 @@ static int ov7670_probe(struct i2c_client *client,
sd = &info->sd;
v4l2_i2c_subdev_init(sd, client, &ov7670_ops);
+ sd->internal_ops = &ov7670_subdev_internal_ops;
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
+
info->clock_speed = 30; /* default: a guess */
- if (client->dev.platform_data) {
+
+ if (dev_fwnode(&client->dev)) {
+ ret = ov7670_parse_dt(&client->dev, info);
+ if (ret)
+ return ret;
+
+ } else if (client->dev.platform_data) {
struct ov7670_config *config = client->dev.platform_data;
/*
@@ -1601,30 +1858,29 @@ static int ov7670_probe(struct i2c_client *client,
if (config->clock_speed)
info->clock_speed = config->clock_speed;
- /*
- * It should be allowed for ov7670 too when it is migrated to
- * the new frame rate formula.
- */
- if (config->pll_bypass && id->driver_data != MODEL_OV7670)
+ if (config->pll_bypass)
info->pll_bypass = true;
if (config->pclk_hb_disable)
info->pclk_hb_disable = true;
}
- info->clk = devm_clk_get(&client->dev, "xclk");
+ info->clk = devm_clk_get_optional(&client->dev, "xclk");
if (IS_ERR(info->clk))
- return -EPROBE_DEFER;
- clk_prepare_enable(info->clk);
+ return PTR_ERR(info->clk);
ret = ov7670_init_gpio(client, info);
if (ret)
- goto clk_disable;
+ return ret;
+
+ ov7670_power_on(sd);
- info->clock_speed = clk_get_rate(info->clk) / 1000000;
- if (info->clock_speed < 10 || info->clock_speed > 48) {
- ret = -EINVAL;
- goto clk_disable;
+ if (info->clk) {
+ info->clock_speed = clk_get_rate(info->clk) / 1000000;
+ if (info->clock_speed < 10 || info->clock_speed > 48) {
+ ret = -EINVAL;
+ goto power_off;
+ }
}
/* Make sure it's an ov7670 */
@@ -1633,13 +1889,17 @@ static int ov7670_probe(struct i2c_client *client,
v4l_dbg(1, debug, client,
"chip found @ 0x%x (%s) is not an ov7670 chip.\n",
client->addr << 1, client->adapter->name);
- goto clk_disable;
+ goto power_off;
}
v4l_info(client, "chip found @ 0x%02x (%s)\n",
client->addr << 1, client->adapter->name);
- info->devtype = &ov7670_devdata[id->driver_data];
+ info->devtype = i2c_get_match_data(client);
info->fmt = &ov7670_formats[0];
+ info->wsize = &info->devtype->win_sizes[0];
+
+ ov7670_get_default_format(sd, &info->format);
+
info->clkrc = 0;
/* Set default frame rate to 30 fps */
@@ -1647,9 +1907,6 @@ static int ov7670_probe(struct i2c_client *client,
tpf.denominator = 30;
info->devtype->set_framerate(sd, &tpf);
- if (info->pclk_hb_disable)
- ov7670_write(sd, REG_COM10, COM10_PCLK_HB);
-
v4l2_ctrl_handler_init(&info->hdl, 10);
v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
@@ -1672,6 +1929,10 @@ static int ov7670_probe(struct i2c_client *client,
info->auto_exposure = v4l2_ctrl_new_std_menu(&info->hdl, &ov7670_ctrl_ops,
V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 0,
V4L2_EXPOSURE_AUTO);
+ v4l2_ctrl_new_std_menu_items(&info->hdl, &ov7670_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(ov7670_test_pattern_menu) - 1, 0, 0,
+ ov7670_test_pattern_menu);
sd->ctrl_handler = &info->hdl;
if (info->hdl.error) {
ret = info->hdl.error;
@@ -1686,52 +1947,72 @@ static int ov7670_probe(struct i2c_client *client,
v4l2_ctrl_auto_cluster(2, &info->auto_exposure,
V4L2_EXPOSURE_MANUAL, false);
v4l2_ctrl_cluster(2, &info->saturation);
+
+ info->pad.flags = MEDIA_PAD_FL_SOURCE;
+ info->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+ ret = media_entity_pads_init(&info->sd.entity, 1, &info->pad);
+ if (ret < 0)
+ goto hdl_free;
+
v4l2_ctrl_handler_setup(&info->hdl);
ret = v4l2_async_register_subdev(&info->sd);
if (ret < 0)
- goto hdl_free;
+ goto entity_cleanup;
+ ov7670_power_off(sd);
return 0;
+entity_cleanup:
+ media_entity_cleanup(&info->sd.entity);
hdl_free:
v4l2_ctrl_handler_free(&info->hdl);
-clk_disable:
- clk_disable_unprepare(info->clk);
+power_off:
+ ov7670_power_off(sd);
return ret;
}
-
-static int ov7670_remove(struct i2c_client *client)
+static void ov7670_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ov7670_info *info = to_state(sd);
- v4l2_device_unregister_subdev(sd);
+ v4l2_async_unregister_subdev(sd);
v4l2_ctrl_handler_free(&info->hdl);
- clk_disable_unprepare(info->clk);
- return 0;
+ media_entity_cleanup(&info->sd.entity);
}
+static const struct ov7670_devtype ov7670_devdata = {
+ .win_sizes = ov7670_win_sizes,
+ .n_win_sizes = ARRAY_SIZE(ov7670_win_sizes),
+ .set_framerate = ov7670_set_framerate_legacy,
+ .get_framerate = ov7670_get_framerate_legacy,
+};
+
+static const struct ov7670_devtype ov7675_devdata = {
+ .win_sizes = ov7675_win_sizes,
+ .n_win_sizes = ARRAY_SIZE(ov7675_win_sizes),
+ .set_framerate = ov7675_set_framerate,
+ .get_framerate = ov7675_get_framerate,
+};
+
static const struct i2c_device_id ov7670_id[] = {
- { "ov7670", MODEL_OV7670 },
- { "ov7675", MODEL_OV7675 },
- { }
+ { "ov7670", (kernel_ulong_t)&ov7670_devdata },
+ { "ov7675", (kernel_ulong_t)&ov7675_devdata },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, ov7670_id);
-#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id ov7670_of_match[] = {
- { .compatible = "ovti,ov7670", },
- { /* sentinel */ },
+ { .compatible = "ovti,ov7670", &ov7670_devdata },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ov7670_of_match);
-#endif
static struct i2c_driver ov7670_driver = {
.driver = {
.name = "ov7670",
- .of_match_table = of_match_ptr(ov7670_of_match),
+ .of_match_table = ov7670_of_match,
},
.probe = ov7670_probe,
.remove = ov7670_remove,