summaryrefslogtreecommitdiff
path: root/drivers/media/i2c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/i2c')
-rw-r--r--drivers/media/i2c/Kconfig59
-rw-r--r--drivers/media/i2c/Makefile8
-rw-r--r--drivers/media/i2c/ccs-pll.c124
-rw-r--r--drivers/media/i2c/ccs-pll.h86
-rw-r--r--drivers/media/i2c/ccs/ccs-core.c318
-rw-r--r--drivers/media/i2c/ccs/ccs-data.c27
-rw-r--r--drivers/media/i2c/ccs/ccs-data.h2
-rw-r--r--drivers/media/i2c/ccs/ccs-reg-access.c29
-rw-r--r--drivers/media/i2c/ccs/ccs.h8
-rw-r--r--drivers/media/i2c/ccs/smiapp-reg-defs.h2
-rw-r--r--drivers/media/i2c/imx219.c23
-rw-r--r--drivers/media/i2c/imx258.c82
-rw-r--r--drivers/media/i2c/imx334.c1132
-rw-r--r--drivers/media/i2c/max9271.c5
-rw-r--r--drivers/media/i2c/max9286.c74
-rw-r--r--drivers/media/i2c/mt9m111.c17
-rw-r--r--drivers/media/i2c/mt9v111.c6
-rw-r--r--drivers/media/i2c/ov02a10.c2
-rw-r--r--drivers/media/i2c/ov5647.c1259
-rw-r--r--drivers/media/i2c/ov5648.c2624
-rw-r--r--drivers/media/i2c/ov5670.c3
-rw-r--r--drivers/media/i2c/ov5675.c6
-rw-r--r--drivers/media/i2c/ov6650.c28
-rw-r--r--drivers/media/i2c/ov8856.c4
-rw-r--r--drivers/media/i2c/ov8865.c2972
-rw-r--r--drivers/media/i2c/ov9640.c15
-rw-r--r--drivers/media/i2c/ov9640.h2
-rw-r--r--drivers/media/i2c/rdacm20.c4
-rw-r--r--drivers/media/i2c/rdacm21.c623
-rw-r--r--drivers/media/i2c/st-mipid02.c21
30 files changed, 9085 insertions, 480 deletions
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 2b9d81e4794a..462c0e059754 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -813,6 +813,20 @@ config VIDEO_IMX319
To compile this driver as a module, choose M here: the
module will be called imx319.
+config VIDEO_IMX334
+ tristate "Sony IMX334 sensor support"
+ depends on OF_GPIO
+ depends on I2C && VIDEO_V4L2
+ select VIDEO_V4L2_SUBDEV_API
+ select MEDIA_CONTROLLER
+ select V4L2_FWNODE
+ help
+ This is a Video4Linux2 sensor driver for the Sony
+ IMX334 camera.
+
+ To compile this driver as a module, choose M here: the
+ module will be called imx334.
+
config VIDEO_IMX355
tristate "Sony IMX355 sensor support"
depends on I2C && VIDEO_V4L2
@@ -936,6 +950,19 @@ config VIDEO_OV5647
To compile this driver as a module, choose M here: the
module will be called ov5647.
+config VIDEO_OV5648
+ tristate "OmniVision OV5648 sensor support"
+ depends on I2C && PM && VIDEO_V4L2
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ help
+ This is a Video4Linux2 sensor driver for the OmniVision
+ OV5648 camera.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ov5648.
+
config VIDEO_OV6650
tristate "OmniVision OV6650 sensor support"
depends on I2C && VIDEO_V4L2
@@ -1000,6 +1027,7 @@ config VIDEO_OV772X
tristate "OmniVision OV772x sensor support"
depends on I2C && VIDEO_V4L2
select REGMAP_SCCB
+ select V4L2_FWNODE
help
This is a Video4Linux2 sensor driver for the OmniVision
OV772x camera.
@@ -1047,6 +1075,19 @@ config VIDEO_OV8856
To compile this driver as a module, choose M here: the
module will be called ov8856.
+config VIDEO_OV8865
+ tristate "OmniVision OV8865 sensor support"
+ depends on I2C && PM && VIDEO_V4L2
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ help
+ This is a Video4Linux2 sensor driver for OmniVision
+ OV8865 camera sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ov8865.
+
config VIDEO_OV9640
tristate "OmniVision OV9640 sensor support"
depends on I2C && VIDEO_V4L2
@@ -1199,12 +1240,16 @@ config VIDEO_NOON010PC30
source "drivers/media/i2c/m5mols/Kconfig"
+config VIDEO_MAX9271_LIB
+ tristate
+
config VIDEO_RDACM20
tristate "IMI RDACM20 camera support"
depends on I2C
select V4L2_FWNODE
select VIDEO_V4L2_SUBDEV_API
select MEDIA_CONTROLLER
+ select VIDEO_MAX9271_LIB
help
This driver supports the IMI RDACM20 GMSL camera, used in
ADAS systems.
@@ -1212,6 +1257,20 @@ config VIDEO_RDACM20
This camera should be used in conjunction with a GMSL
deserialiser such as the MAX9286.
+config VIDEO_RDACM21
+ tristate "IMI RDACM21 camera support"
+ depends on I2C
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
+ select MEDIA_CONTROLLER
+ select VIDEO_MAX9271_LIB
+ help
+ This driver supports the IMI RDACM21 GMSL camera, used in
+ ADAS systems.
+
+ This camera should be used in conjunction with a GMSL
+ deserialiser such as the MAX9286.
+
config VIDEO_RJ54N1
tristate "Sharp RJ54N1CB0C sensor support"
depends on I2C && VIDEO_V4L2
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index a3149dce21bb..0c067beca066 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_VIDEO_OV2740) += ov2740.o
obj-$(CONFIG_VIDEO_OV5640) += ov5640.o
obj-$(CONFIG_VIDEO_OV5645) += ov5645.o
obj-$(CONFIG_VIDEO_OV5647) += ov5647.o
+obj-$(CONFIG_VIDEO_OV5648) += ov5648.o
obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
obj-$(CONFIG_VIDEO_OV5675) += ov5675.o
obj-$(CONFIG_VIDEO_OV5695) += ov5695.o
@@ -82,6 +83,7 @@ obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
obj-$(CONFIG_VIDEO_OV7740) += ov7740.o
obj-$(CONFIG_VIDEO_OV8856) += ov8856.o
+obj-$(CONFIG_VIDEO_OV8865) += ov8865.o
obj-$(CONFIG_VIDEO_OV9640) += ov9640.o
obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
obj-$(CONFIG_VIDEO_OV9734) += ov9734.o
@@ -120,10 +122,12 @@ obj-$(CONFIG_VIDEO_IMX258) += imx258.o
obj-$(CONFIG_VIDEO_IMX274) += imx274.o
obj-$(CONFIG_VIDEO_IMX290) += imx290.o
obj-$(CONFIG_VIDEO_IMX319) += imx319.o
+obj-$(CONFIG_VIDEO_IMX334) += imx334.o
obj-$(CONFIG_VIDEO_IMX355) += imx355.o
obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
-rdacm20-camera_module-objs := rdacm20.o max9271.o
-obj-$(CONFIG_VIDEO_RDACM20) += rdacm20-camera_module.o
+obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
+obj-$(CONFIG_VIDEO_RDACM20) += rdacm20.o
+obj-$(CONFIG_VIDEO_RDACM21) += rdacm21.o
obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
obj-$(CONFIG_SDR_MAX2175) += max2175.o
diff --git a/drivers/media/i2c/ccs-pll.c b/drivers/media/i2c/ccs-pll.c
index eb7b6f01f623..fcc39360cc50 100644
--- a/drivers/media/i2c/ccs-pll.c
+++ b/drivers/media/i2c/ccs-pll.c
@@ -17,20 +17,20 @@
#include "ccs-pll.h"
/* Return an even number or one. */
-static inline uint32_t clk_div_even(uint32_t a)
+static inline u32 clk_div_even(u32 a)
{
- return max_t(uint32_t, 1, a & ~1);
+ return max_t(u32, 1, a & ~1);
}
/* Return an even number or one. */
-static inline uint32_t clk_div_even_up(uint32_t a)
+static inline u32 clk_div_even_up(u32 a)
{
if (a == 1)
return 1;
return (a + 1) & ~1;
}
-static inline uint32_t is_one_or_even(uint32_t a)
+static inline u32 is_one_or_even(u32 a)
{
if (a == 1)
return 1;
@@ -40,13 +40,13 @@ static inline uint32_t is_one_or_even(uint32_t a)
return 1;
}
-static inline uint32_t one_or_more(uint32_t a)
+static inline u32 one_or_more(u32 a)
{
return a ?: 1;
}
-static int bounds_check(struct device *dev, uint32_t val,
- uint32_t min, uint32_t max, const char *prefix,
+static int bounds_check(struct device *dev, u32 val,
+ u32 min, u32 max, const char *prefix,
char *str)
{
if (val >= min && val <= max)
@@ -138,12 +138,12 @@ static void print_pll(struct device *dev, struct ccs_pll *pll)
pll->flags & PLL_FL(OP_PIX_DDR) ? " op-pix-ddr" : "");
}
-static uint32_t op_sys_ddr(uint32_t flags)
+static u32 op_sys_ddr(u32 flags)
{
return flags & CCS_PLL_FLAG_OP_SYS_DDR ? 1 : 0;
}
-static uint32_t op_pix_ddr(uint32_t flags)
+static u32 op_pix_ddr(u32 flags)
{
return flags & CCS_PLL_FLAG_OP_PIX_DDR ? 1 : 0;
}
@@ -250,8 +250,8 @@ static int check_ext_bounds(struct device *dev, struct ccs_pll *pll)
static void
ccs_pll_find_vt_sys_div(struct device *dev, const struct ccs_pll_limits *lim,
struct ccs_pll *pll, struct ccs_pll_branch_fr *pll_fr,
- uint16_t min_vt_div, uint16_t max_vt_div,
- uint16_t *min_sys_div, uint16_t *max_sys_div)
+ u16 min_vt_div, u16 max_vt_div,
+ u16 *min_sys_div, u16 *max_sys_div)
{
/*
* Find limits for sys_clk_div. Not all values are possible with all
@@ -259,11 +259,11 @@ ccs_pll_find_vt_sys_div(struct device *dev, const struct ccs_pll_limits *lim,
*/
*min_sys_div = lim->vt_bk.min_sys_clk_div;
dev_dbg(dev, "min_sys_div: %u\n", *min_sys_div);
- *min_sys_div = max_t(uint16_t, *min_sys_div,
+ *min_sys_div = max_t(u16, *min_sys_div,
DIV_ROUND_UP(min_vt_div,
lim->vt_bk.max_pix_clk_div));
dev_dbg(dev, "min_sys_div: max_vt_pix_clk_div: %u\n", *min_sys_div);
- *min_sys_div = max_t(uint16_t, *min_sys_div,
+ *min_sys_div = max_t(u16, *min_sys_div,
pll_fr->pll_op_clk_freq_hz
/ lim->vt_bk.max_sys_clk_freq_hz);
dev_dbg(dev, "min_sys_div: max_pll_op_clk_freq_hz: %u\n", *min_sys_div);
@@ -272,11 +272,11 @@ ccs_pll_find_vt_sys_div(struct device *dev, const struct ccs_pll_limits *lim,
*max_sys_div = lim->vt_bk.max_sys_clk_div;
dev_dbg(dev, "max_sys_div: %u\n", *max_sys_div);
- *max_sys_div = min_t(uint16_t, *max_sys_div,
+ *max_sys_div = min_t(u16, *max_sys_div,
DIV_ROUND_UP(max_vt_div,
lim->vt_bk.min_pix_clk_div));
dev_dbg(dev, "max_sys_div: min_vt_pix_clk_div: %u\n", *max_sys_div);
- *max_sys_div = min_t(uint16_t, *max_sys_div,
+ *max_sys_div = min_t(u16, *max_sys_div,
DIV_ROUND_UP(pll_fr->pll_op_clk_freq_hz,
lim->vt_bk.min_pix_clk_freq_hz));
dev_dbg(dev, "max_sys_div: min_vt_pix_clk_freq_hz: %u\n", *max_sys_div);
@@ -289,15 +289,15 @@ ccs_pll_find_vt_sys_div(struct device *dev, const struct ccs_pll_limits *lim,
static inline int
__ccs_pll_calculate_vt_tree(struct device *dev,
const struct ccs_pll_limits *lim,
- struct ccs_pll *pll, uint32_t mul, uint32_t div)
+ struct ccs_pll *pll, u32 mul, u32 div)
{
const struct ccs_pll_branch_limits_fr *lim_fr = &lim->vt_fr;
const struct ccs_pll_branch_limits_bk *lim_bk = &lim->vt_bk;
struct ccs_pll_branch_fr *pll_fr = &pll->vt_fr;
struct ccs_pll_branch_bk *pll_bk = &pll->vt_bk;
- uint32_t more_mul;
- uint16_t best_pix_div = SHRT_MAX >> 1, best_div;
- uint16_t vt_div, min_sys_div, max_sys_div, sys_div;
+ u32 more_mul;
+ u16 best_pix_div = SHRT_MAX >> 1, best_div;
+ u16 vt_div, min_sys_div, max_sys_div, sys_div;
pll_fr->pll_ip_clk_freq_hz =
pll->ext_clk_freq_hz / pll_fr->pre_pll_clk_div;
@@ -331,7 +331,7 @@ __ccs_pll_calculate_vt_tree(struct device *dev,
for (sys_div = min_sys_div; sys_div <= max_sys_div;
sys_div += 2 - (sys_div & 1)) {
- uint16_t pix_div;
+ u16 pix_div;
if (vt_div % sys_div)
continue;
@@ -379,9 +379,9 @@ static int ccs_pll_calculate_vt_tree(struct device *dev,
{
const struct ccs_pll_branch_limits_fr *lim_fr = &lim->vt_fr;
struct ccs_pll_branch_fr *pll_fr = &pll->vt_fr;
- uint16_t min_pre_pll_clk_div = lim_fr->min_pre_pll_clk_div;
- uint16_t max_pre_pll_clk_div = lim_fr->max_pre_pll_clk_div;
- uint32_t pre_mul, pre_div;
+ u16 min_pre_pll_clk_div = lim_fr->min_pre_pll_clk_div;
+ u16 max_pre_pll_clk_div = lim_fr->max_pre_pll_clk_div;
+ u32 pre_mul, pre_div;
pre_div = gcd(pll->pixel_rate_csi,
pll->ext_clk_freq_hz * pll->vt_lanes);
@@ -390,11 +390,11 @@ static int ccs_pll_calculate_vt_tree(struct device *dev,
/* Make sure PLL input frequency is within limits */
max_pre_pll_clk_div =
- min_t(uint16_t, max_pre_pll_clk_div,
+ min_t(u16, max_pre_pll_clk_div,
DIV_ROUND_UP(pll->ext_clk_freq_hz,
lim_fr->min_pll_ip_clk_freq_hz));
- min_pre_pll_clk_div = max_t(uint16_t, min_pre_pll_clk_div,
+ min_pre_pll_clk_div = max_t(u16, min_pre_pll_clk_div,
pll->ext_clk_freq_hz /
lim_fr->max_pll_ip_clk_freq_hz);
@@ -406,7 +406,7 @@ static int ccs_pll_calculate_vt_tree(struct device *dev,
pll_fr->pre_pll_clk_div +=
(pll->flags & CCS_PLL_FLAG_EXT_IP_PLL_DIVIDER) ? 1 :
2 - (pll_fr->pre_pll_clk_div & 1)) {
- uint32_t mul, div;
+ u32 mul, div;
int rval;
div = gcd(pre_mul * pll_fr->pre_pll_clk_div, pre_div);
@@ -440,13 +440,13 @@ ccs_pll_calculate_vt(struct device *dev, const struct ccs_pll_limits *lim,
const struct ccs_pll_branch_limits_bk *op_lim_bk,
struct ccs_pll *pll, struct ccs_pll_branch_fr *pll_fr,
struct ccs_pll_branch_bk *op_pll_bk, bool cphy,
- uint32_t phy_const)
+ u32 phy_const)
{
- uint16_t sys_div;
- uint16_t best_pix_div = SHRT_MAX >> 1;
- uint16_t vt_op_binning_div;
- uint16_t min_vt_div, max_vt_div, vt_div;
- uint16_t min_sys_div, max_sys_div;
+ u16 sys_div;
+ u16 best_pix_div = SHRT_MAX >> 1;
+ u16 vt_op_binning_div;
+ u16 min_vt_div, max_vt_div, vt_div;
+ u16 min_sys_div, max_sys_div;
if (pll->flags & CCS_PLL_FLAG_NO_OP_CLOCKS)
goto out_calc_pixel_rate;
@@ -500,18 +500,18 @@ ccs_pll_calculate_vt(struct device *dev, const struct ccs_pll_limits *lim,
/* Find smallest and biggest allowed vt divisor. */
dev_dbg(dev, "min_vt_div: %u\n", min_vt_div);
- min_vt_div = max_t(uint16_t, min_vt_div,
+ min_vt_div = max_t(u16, min_vt_div,
DIV_ROUND_UP(pll_fr->pll_op_clk_freq_hz,
lim->vt_bk.max_pix_clk_freq_hz));
dev_dbg(dev, "min_vt_div: max_vt_pix_clk_freq_hz: %u\n",
min_vt_div);
- min_vt_div = max_t(uint16_t, min_vt_div, lim->vt_bk.min_pix_clk_div
- * lim->vt_bk.min_sys_clk_div);
+ min_vt_div = max_t(u16, min_vt_div, lim->vt_bk.min_pix_clk_div
+ * lim->vt_bk.min_sys_clk_div);
dev_dbg(dev, "min_vt_div: min_vt_clk_div: %u\n", min_vt_div);
max_vt_div = lim->vt_bk.max_sys_clk_div * lim->vt_bk.max_pix_clk_div;
dev_dbg(dev, "max_vt_div: %u\n", max_vt_div);
- max_vt_div = min_t(uint16_t, max_vt_div,
+ max_vt_div = min_t(u16, max_vt_div,
DIV_ROUND_UP(pll_fr->pll_op_clk_freq_hz,
lim->vt_bk.min_pix_clk_freq_hz));
dev_dbg(dev, "max_vt_div: min_vt_pix_clk_freq_hz: %u\n",
@@ -526,12 +526,12 @@ ccs_pll_calculate_vt(struct device *dev, const struct ccs_pll_limits *lim,
* divisor.
*/
for (vt_div = min_vt_div; vt_div <= max_vt_div; vt_div++) {
- uint16_t __max_sys_div = vt_div & 1 ? 1 : max_sys_div;
+ u16 __max_sys_div = vt_div & 1 ? 1 : max_sys_div;
for (sys_div = min_sys_div; sys_div <= __max_sys_div;
sys_div += 2 - (sys_div & 1)) {
- uint16_t pix_div;
- uint16_t rounded_div;
+ u16 pix_div;
+ u16 rounded_div;
pix_div = DIV_ROUND_UP(vt_div, sys_div);
@@ -588,9 +588,9 @@ ccs_pll_calculate_op(struct device *dev, const struct ccs_pll_limits *lim,
const struct ccs_pll_branch_limits_fr *op_lim_fr,
const struct ccs_pll_branch_limits_bk *op_lim_bk,
struct ccs_pll *pll, struct ccs_pll_branch_fr *op_pll_fr,
- struct ccs_pll_branch_bk *op_pll_bk, uint32_t mul,
- uint32_t div, uint32_t op_sys_clk_freq_hz_sdr, uint32_t l,
- bool cphy, uint32_t phy_const)
+ struct ccs_pll_branch_bk *op_pll_bk, u32 mul,
+ u32 div, u32 op_sys_clk_freq_hz_sdr, u32 l,
+ bool cphy, u32 phy_const)
{
/*
* Higher multipliers (and divisors) are often required than
@@ -598,9 +598,9 @@ ccs_pll_calculate_op(struct device *dev, const struct ccs_pll_limits *lim,
* There are limits for all values in the clock tree. These
* are the minimum and maximum multiplier for mul.
*/
- uint32_t more_mul_min, more_mul_max;
- uint32_t more_mul_factor;
- uint32_t i;
+ u32 more_mul_min, more_mul_max;
+ u32 more_mul_factor;
+ u32 i;
/*
* Get pre_pll_clk_div so that our pll_op_clk_freq_hz won't be
@@ -614,7 +614,7 @@ ccs_pll_calculate_op(struct device *dev, const struct ccs_pll_limits *lim,
more_mul_max);
/* Don't go above max pll op frequency. */
more_mul_max =
- min_t(uint32_t,
+ min_t(u32,
more_mul_max,
op_lim_fr->max_pll_op_clk_freq_hz
/ (pll->ext_clk_freq_hz /
@@ -706,14 +706,14 @@ int ccs_pll_calculate(struct device *dev, const struct ccs_pll_limits *lim,
struct ccs_pll_branch_fr *op_pll_fr;
struct ccs_pll_branch_bk *op_pll_bk;
bool cphy = pll->bus_type == CCS_PLL_BUS_TYPE_CSI2_CPHY;
- uint32_t phy_const = cphy ? CPHY_CONST : DPHY_CONST;
- uint32_t op_sys_clk_freq_hz_sdr;
- uint16_t min_op_pre_pll_clk_div;
- uint16_t max_op_pre_pll_clk_div;
- uint32_t mul, div;
- uint32_t l = (!pll->op_bits_per_lane ||
- pll->op_bits_per_lane >= pll->bits_per_pixel) ? 1 : 2;
- uint32_t i;
+ u32 phy_const = cphy ? CPHY_CONST : DPHY_CONST;
+ u32 op_sys_clk_freq_hz_sdr;
+ u16 min_op_pre_pll_clk_div;
+ u16 max_op_pre_pll_clk_div;
+ u32 mul, div;
+ u32 l = (!pll->op_bits_per_lane ||
+ pll->op_bits_per_lane >= pll->bits_per_pixel) ? 1 : 2;
+ u32 i;
int rval = -EINVAL;
if (!(pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL)) {
@@ -772,14 +772,8 @@ int ccs_pll_calculate(struct device *dev, const struct ccs_pll_limits *lim,
switch (pll->bus_type) {
case CCS_PLL_BUS_TYPE_CSI2_DPHY:
- /* CSI transfers 2 bits per clock per lane; thus times 2 */
- op_sys_clk_freq_hz_sdr = pll->link_freq * 2
- * (pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL ?
- 1 : pll->csi2.lanes);
- break;
case CCS_PLL_BUS_TYPE_CSI2_CPHY:
- op_sys_clk_freq_hz_sdr =
- pll->link_freq
+ op_sys_clk_freq_hz_sdr = pll->link_freq * 2
* (pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL ?
1 : pll->csi2.lanes);
break;
@@ -797,11 +791,11 @@ int ccs_pll_calculate(struct device *dev, const struct ccs_pll_limits *lim,
dev_dbg(dev, "min / max op_pre_pll_clk_div: %u / %u\n",
op_lim_fr->min_pre_pll_clk_div, op_lim_fr->max_pre_pll_clk_div);
max_op_pre_pll_clk_div =
- min_t(uint16_t, op_lim_fr->max_pre_pll_clk_div,
+ min_t(u16, op_lim_fr->max_pre_pll_clk_div,
clk_div_even(pll->ext_clk_freq_hz /
op_lim_fr->min_pll_ip_clk_freq_hz));
min_op_pre_pll_clk_div =
- max_t(uint16_t, op_lim_fr->min_pre_pll_clk_div,
+ max_t(u16, op_lim_fr->min_pre_pll_clk_div,
clk_div_even_up(
DIV_ROUND_UP(pll->ext_clk_freq_hz,
op_lim_fr->max_pll_ip_clk_freq_hz)));
@@ -815,7 +809,7 @@ int ccs_pll_calculate(struct device *dev, const struct ccs_pll_limits *lim,
dev_dbg(dev, "mul %u / div %u\n", mul, div);
min_op_pre_pll_clk_div =
- max_t(uint16_t, min_op_pre_pll_clk_div,
+ max_t(u16, min_op_pre_pll_clk_div,
clk_div_even_up(
mul /
one_or_more(
@@ -883,4 +877,4 @@ EXPORT_SYMBOL_GPL(ccs_pll_calculate);
MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>");
MODULE_DESCRIPTION("Generic MIPI CCS/SMIA/SMIA++ PLL calculator");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/i2c/ccs-pll.h b/drivers/media/i2c/ccs-pll.h
index b97d7ff50ea5..6eb1b1c68e1e 100644
--- a/drivers/media/i2c/ccs-pll.h
+++ b/drivers/media/i2c/ccs-pll.h
@@ -44,10 +44,10 @@
* @pll_op_clk_freq_hz: PLL output clock frequency
*/
struct ccs_pll_branch_fr {
- uint16_t pre_pll_clk_div;
- uint16_t pll_multiplier;
- uint32_t pll_ip_clk_freq_hz;
- uint32_t pll_op_clk_freq_hz;
+ u16 pre_pll_clk_div;
+ u16 pll_multiplier;
+ u32 pll_ip_clk_freq_hz;
+ u32 pll_op_clk_freq_hz;
};
/**
@@ -61,10 +61,10 @@ struct ccs_pll_branch_fr {
* @pix_clk_freq_hz: Pixel clock frequency
*/
struct ccs_pll_branch_bk {
- uint16_t sys_clk_div;
- uint16_t pix_clk_div;
- uint32_t sys_clk_freq_hz;
- uint32_t pix_clk_freq_hz;
+ u16 sys_clk_div;
+ u16 pix_clk_div;
+ u32 sys_clk_freq_hz;
+ u32 pix_clk_freq_hz;
};
/**
@@ -97,21 +97,21 @@ struct ccs_pll_branch_bk {
*/
struct ccs_pll {
/* input values */
- uint8_t bus_type;
- uint8_t op_lanes;
- uint8_t vt_lanes;
+ u8 bus_type;
+ u8 op_lanes;
+ u8 vt_lanes;
struct {
- uint8_t lanes;
+ u8 lanes;
} csi2;
- uint8_t binning_horizontal;
- uint8_t binning_vertical;
- uint8_t scale_m;
- uint8_t scale_n;
- uint8_t bits_per_pixel;
- uint8_t op_bits_per_lane;
- uint16_t flags;
- uint32_t link_freq;
- uint32_t ext_clk_freq_hz;
+ u8 binning_horizontal;
+ u8 binning_vertical;
+ u8 scale_m;
+ u8 scale_n;
+ u8 bits_per_pixel;
+ u8 op_bits_per_lane;
+ u16 flags;
+ u32 link_freq;
+ u32 ext_clk_freq_hz;
/* output values */
struct ccs_pll_branch_fr vt_fr;
@@ -119,8 +119,8 @@ struct ccs_pll {
struct ccs_pll_branch_fr op_fr;
struct ccs_pll_branch_bk op_bk;
- uint32_t pixel_rate_csi;
- uint32_t pixel_rate_pixel_array;
+ u32 pixel_rate_csi;
+ u32 pixel_rate_pixel_array;
};
/**
@@ -136,14 +136,14 @@ struct ccs_pll {
* @max_pll_op_clk_freq_hz: Maximum PLL output clock frequency
*/
struct ccs_pll_branch_limits_fr {
- uint16_t min_pre_pll_clk_div;
- uint16_t max_pre_pll_clk_div;
- uint32_t min_pll_ip_clk_freq_hz;
- uint32_t max_pll_ip_clk_freq_hz;
- uint16_t min_pll_multiplier;
- uint16_t max_pll_multiplier;
- uint32_t min_pll_op_clk_freq_hz;
- uint32_t max_pll_op_clk_freq_hz;
+ u16 min_pre_pll_clk_div;
+ u16 max_pre_pll_clk_div;
+ u32 min_pll_ip_clk_freq_hz;
+ u32 max_pll_ip_clk_freq_hz;
+ u16 min_pll_multiplier;
+ u16 max_pll_multiplier;
+ u32 min_pll_op_clk_freq_hz;
+ u32 max_pll_op_clk_freq_hz;
};
/**
@@ -159,14 +159,14 @@ struct ccs_pll_branch_limits_fr {
* @max_pix_clk_freq_hz: Maximum pixel clock frequency
*/
struct ccs_pll_branch_limits_bk {
- uint16_t min_sys_clk_div;
- uint16_t max_sys_clk_div;
- uint32_t min_sys_clk_freq_hz;
- uint32_t max_sys_clk_freq_hz;
- uint16_t min_pix_clk_div;
- uint16_t max_pix_clk_div;
- uint32_t min_pix_clk_freq_hz;
- uint32_t max_pix_clk_freq_hz;
+ u16 min_sys_clk_div;
+ u16 max_sys_clk_div;
+ u32 min_sys_clk_freq_hz;
+ u32 max_sys_clk_freq_hz;
+ u16 min_pix_clk_div;
+ u16 max_pix_clk_div;
+ u32 min_pix_clk_freq_hz;
+ u32 max_pix_clk_freq_hz;
};
/**
@@ -183,8 +183,8 @@ struct ccs_pll_branch_limits_bk {
*/
struct ccs_pll_limits {
/* Strict PLL limits */
- uint32_t min_ext_clk_freq_hz;
- uint32_t max_ext_clk_freq_hz;
+ u32 min_ext_clk_freq_hz;
+ u32 max_ext_clk_freq_hz;
struct ccs_pll_branch_limits_fr vt_fr;
struct ccs_pll_branch_limits_bk vt_bk;
@@ -192,8 +192,8 @@ struct ccs_pll_limits {
struct ccs_pll_branch_limits_bk op_bk;
/* Other relevant limits */
- uint32_t min_line_length_pck_bin;
- uint32_t min_line_length_pck;
+ u32 min_line_length_pck_bin;
+ u32 min_line_length_pck;
};
struct device;
diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c
index b39ae5f8446b..15afbb4f5b31 100644
--- a/drivers/media/i2c/ccs/ccs-core.c
+++ b/drivers/media/i2c/ccs/ccs-core.c
@@ -28,6 +28,7 @@
#include <linux/v4l2-mediabus.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-device.h>
+#include <uapi/linux/ccs.h>
#include "ccs.h"
@@ -382,15 +383,22 @@ static int ccs_pll_configure(struct ccs_sensor *sensor)
if (rval < 0)
return rval;
- /* Lane op clock ratio does not apply here. */
- rval = ccs_write(sensor, REQUESTED_LINK_RATE,
- DIV_ROUND_UP(pll->op_bk.sys_clk_freq_hz,
- 1000000 / 256 / 256) *
- (pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL ?
- sensor->pll.csi2.lanes : 1) <<
- (pll->flags & CCS_PLL_FLAG_OP_SYS_DDR ? 1 : 0));
- if (rval < 0 || sensor->pll.flags & CCS_PLL_FLAG_NO_OP_CLOCKS)
- return rval;
+ if (!(CCS_LIM(sensor, PHY_CTRL_CAPABILITY) &
+ CCS_PHY_CTRL_CAPABILITY_AUTO_PHY_CTL)) {
+ /* Lane op clock ratio does not apply here. */
+ rval = ccs_write(sensor, REQUESTED_LINK_RATE,
+ DIV_ROUND_UP(pll->op_bk.sys_clk_freq_hz,
+ 1000000 / 256 / 256) *
+ (pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL ?
+ sensor->pll.csi2.lanes : 1) <<
+ (pll->flags & CCS_PLL_FLAG_OP_SYS_DDR ?
+ 1 : 0));
+ if (rval < 0)
+ return rval;
+ }
+
+ if (sensor->pll.flags & CCS_PLL_FLAG_NO_OP_CLOCKS)
+ return 0;
rval = ccs_write(sensor, OP_PIX_CLK_DIV, pll->op_bk.pix_clk_div);
if (rval < 0)
@@ -671,6 +679,49 @@ static int ccs_set_ctrl(struct v4l2_ctrl *ctrl)
rval = ccs_write(sensor, ANALOG_GAIN_CODE_GLOBAL, ctrl->val);
break;
+
+ case V4L2_CID_CCS_ANALOGUE_LINEAR_GAIN:
+ rval = ccs_write(sensor, ANALOG_LINEAR_GAIN_GLOBAL, ctrl->val);
+
+ break;
+
+ case V4L2_CID_CCS_ANALOGUE_EXPONENTIAL_GAIN:
+ rval = ccs_write(sensor, ANALOG_EXPONENTIAL_GAIN_GLOBAL,
+ ctrl->val);
+
+ break;
+
+ case V4L2_CID_DIGITAL_GAIN:
+ if (CCS_LIM(sensor, DIGITAL_GAIN_CAPABILITY) ==
+ CCS_DIGITAL_GAIN_CAPABILITY_GLOBAL) {
+ rval = ccs_write(sensor, DIGITAL_GAIN_GLOBAL,
+ ctrl->val);
+ break;
+ }
+
+ rval = ccs_write_addr(sensor,
+ SMIAPP_REG_U16_DIGITAL_GAIN_GREENR,
+ ctrl->val);
+ if (rval)
+ break;
+
+ rval = ccs_write_addr(sensor,
+ SMIAPP_REG_U16_DIGITAL_GAIN_RED,
+ ctrl->val);
+ if (rval)
+ break;
+
+ rval = ccs_write_addr(sensor,
+ SMIAPP_REG_U16_DIGITAL_GAIN_BLUE,
+ ctrl->val);
+ if (rval)
+ break;
+
+ rval = ccs_write_addr(sensor,
+ SMIAPP_REG_U16_DIGITAL_GAIN_GREENB,
+ ctrl->val);
+
+ break;
case V4L2_CID_EXPOSURE:
rval = ccs_write(sensor, COARSE_INTEGRATION_TIME, ctrl->val);
@@ -713,6 +764,19 @@ static int ccs_set_ctrl(struct v4l2_ctrl *ctrl)
rval = ccs_write(sensor, TEST_DATA_GREENB, ctrl->val);
break;
+ case V4L2_CID_CCS_SHADING_CORRECTION:
+ rval = ccs_write(sensor, SHADING_CORRECTION_EN,
+ ctrl->val ? CCS_SHADING_CORRECTION_EN_ENABLE :
+ 0);
+
+ if (!rval && sensor->luminance_level)
+ v4l2_ctrl_activate(sensor->luminance_level, ctrl->val);
+
+ break;
+ case V4L2_CID_CCS_LUMINANCE_CORRECTION_LEVEL:
+ rval = ccs_write(sensor, LUMINANCE_CORRECTION_LEVEL, ctrl->val);
+
+ break;
case V4L2_CID_PIXEL_RATE:
/* For v4l2_ctrl_s_ctrl_int64() used internally. */
rval = 0;
@@ -739,19 +803,144 @@ static int ccs_init_controls(struct ccs_sensor *sensor)
struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
int rval;
- rval = v4l2_ctrl_handler_init(&sensor->pixel_array->ctrl_handler, 12);
+ rval = v4l2_ctrl_handler_init(&sensor->pixel_array->ctrl_handler, 17);
if (rval)
return rval;
sensor->pixel_array->ctrl_handler.lock = &sensor->mutex;
- sensor->analog_gain = v4l2_ctrl_new_std(
- &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops,
- V4L2_CID_ANALOGUE_GAIN,
- CCS_LIM(sensor, ANALOG_GAIN_CODE_MIN),
- CCS_LIM(sensor, ANALOG_GAIN_CODE_MAX),
- max(CCS_LIM(sensor, ANALOG_GAIN_CODE_STEP), 1U),
- CCS_LIM(sensor, ANALOG_GAIN_CODE_MIN));
+ switch (CCS_LIM(sensor, ANALOG_GAIN_CAPABILITY)) {
+ case CCS_ANALOG_GAIN_CAPABILITY_GLOBAL: {
+ struct {
+ const char *name;
+ u32 id;
+ s32 value;
+ } const gain_ctrls[] = {
+ { "Analogue Gain m0", V4L2_CID_CCS_ANALOGUE_GAIN_M0,
+ CCS_LIM(sensor, ANALOG_GAIN_M0), },
+ { "Analogue Gain c0", V4L2_CID_CCS_ANALOGUE_GAIN_C0,
+ CCS_LIM(sensor, ANALOG_GAIN_C0), },
+ { "Analogue Gain m1", V4L2_CID_CCS_ANALOGUE_GAIN_M1,
+ CCS_LIM(sensor, ANALOG_GAIN_M1), },
+ { "Analogue Gain c1", V4L2_CID_CCS_ANALOGUE_GAIN_C1,
+ CCS_LIM(sensor, ANALOG_GAIN_C1), },
+ };
+ struct v4l2_ctrl_config ctrl_cfg = {
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .ops = &ccs_ctrl_ops,
+ .flags = V4L2_CTRL_FLAG_READ_ONLY,
+ .step = 1,
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(gain_ctrls); i++) {
+ ctrl_cfg.name = gain_ctrls[i].name;
+ ctrl_cfg.id = gain_ctrls[i].id;
+ ctrl_cfg.min = ctrl_cfg.max = ctrl_cfg.def =
+ gain_ctrls[i].value;
+
+ v4l2_ctrl_new_custom(&sensor->pixel_array->ctrl_handler,
+ &ctrl_cfg, NULL);
+ }
+
+ v4l2_ctrl_new_std(&sensor->pixel_array->ctrl_handler,
+ &ccs_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+ CCS_LIM(sensor, ANALOG_GAIN_CODE_MIN),
+ CCS_LIM(sensor, ANALOG_GAIN_CODE_MAX),
+ max(CCS_LIM(sensor, ANALOG_GAIN_CODE_STEP),
+ 1U),
+ CCS_LIM(sensor, ANALOG_GAIN_CODE_MIN));
+ }
+ break;
+
+ case CCS_ANALOG_GAIN_CAPABILITY_ALTERNATE_GLOBAL: {
+ struct {
+ const char *name;
+ u32 id;
+ u16 min, max, step;
+ } const gain_ctrls[] = {
+ {
+ "Analogue Linear Gain",
+ V4L2_CID_CCS_ANALOGUE_LINEAR_GAIN,
+ CCS_LIM(sensor, ANALOG_LINEAR_GAIN_MIN),
+ CCS_LIM(sensor, ANALOG_LINEAR_GAIN_MAX),
+ max(CCS_LIM(sensor,
+ ANALOG_LINEAR_GAIN_STEP_SIZE),
+ 1U),
+ },
+ {
+ "Analogue Exponential Gain",
+ V4L2_CID_CCS_ANALOGUE_EXPONENTIAL_GAIN,
+ CCS_LIM(sensor, ANALOG_EXPONENTIAL_GAIN_MIN),
+ CCS_LIM(sensor, ANALOG_EXPONENTIAL_GAIN_MAX),
+ max(CCS_LIM(sensor,
+ ANALOG_EXPONENTIAL_GAIN_STEP_SIZE),
+ 1U),
+ },
+ };
+ struct v4l2_ctrl_config ctrl_cfg = {
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .ops = &ccs_ctrl_ops,
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(gain_ctrls); i++) {
+ ctrl_cfg.name = gain_ctrls[i].name;
+ ctrl_cfg.min = ctrl_cfg.def = gain_ctrls[i].min;
+ ctrl_cfg.max = gain_ctrls[i].max;
+ ctrl_cfg.step = gain_ctrls[i].step;
+ ctrl_cfg.id = gain_ctrls[i].id;
+
+ v4l2_ctrl_new_custom(&sensor->pixel_array->ctrl_handler,
+ &ctrl_cfg, NULL);
+ }
+ }
+ }
+
+ if (CCS_LIM(sensor, SHADING_CORRECTION_CAPABILITY) &
+ (CCS_SHADING_CORRECTION_CAPABILITY_COLOR_SHADING |
+ CCS_SHADING_CORRECTION_CAPABILITY_LUMINANCE_CORRECTION)) {
+ const struct v4l2_ctrl_config ctrl_cfg = {
+ .name = "Shading Correction",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .id = V4L2_CID_CCS_SHADING_CORRECTION,
+ .ops = &ccs_ctrl_ops,
+ .max = 1,
+ .step = 1,
+ };
+
+ v4l2_ctrl_new_custom(&sensor->pixel_array->ctrl_handler,
+ &ctrl_cfg, NULL);
+ }
+
+ if (CCS_LIM(sensor, SHADING_CORRECTION_CAPABILITY) &
+ CCS_SHADING_CORRECTION_CAPABILITY_LUMINANCE_CORRECTION) {
+ const struct v4l2_ctrl_config ctrl_cfg = {
+ .name = "Luminance Correction Level",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .id = V4L2_CID_CCS_LUMINANCE_CORRECTION_LEVEL,
+ .ops = &ccs_ctrl_ops,
+ .max = 255,
+ .step = 1,
+ .def = 128,
+ };
+
+ sensor->luminance_level =
+ v4l2_ctrl_new_custom(&sensor->pixel_array->ctrl_handler,
+ &ctrl_cfg, NULL);
+ }
+
+ if (CCS_LIM(sensor, DIGITAL_GAIN_CAPABILITY) ==
+ CCS_DIGITAL_GAIN_CAPABILITY_GLOBAL ||
+ CCS_LIM(sensor, DIGITAL_GAIN_CAPABILITY) ==
+ SMIAPP_DIGITAL_GAIN_CAPABILITY_PER_CHANNEL)
+ v4l2_ctrl_new_std(&sensor->pixel_array->ctrl_handler,
+ &ccs_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+ CCS_LIM(sensor, DIGITAL_GAIN_MIN),
+ CCS_LIM(sensor, DIGITAL_GAIN_MAX),
+ max(CCS_LIM(sensor, DIGITAL_GAIN_STEP_SIZE),
+ 1U),
+ 0x100);
/* Exposure limits will be updated soon, use just something here. */
sensor->exposure = v4l2_ctrl_new_std(
@@ -1001,7 +1190,7 @@ static void ccs_update_blanking(struct ccs_sensor *sensor)
{
struct v4l2_ctrl *vblank = sensor->vblank;
struct v4l2_ctrl *hblank = sensor->hblank;
- uint16_t min_fll, max_fll, min_llp, max_llp, min_lbp;
+ u16 min_fll, max_fll, min_llp, max_llp, min_lbp;
int min, max;
if (sensor->binning_vertical > 1 || sensor->binning_horizontal > 1) {
@@ -1322,6 +1511,28 @@ static int ccs_write_msr_regs(struct ccs_sensor *sensor)
sensor->mdata.num_module_manufacturer_regs);
}
+static int ccs_update_phy_ctrl(struct ccs_sensor *sensor)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+ u8 val;
+
+ if (!sensor->ccs_limits)
+ return 0;
+
+ if (CCS_LIM(sensor, PHY_CTRL_CAPABILITY) &
+ CCS_PHY_CTRL_CAPABILITY_AUTO_PHY_CTL) {
+ val = CCS_PHY_CTRL_AUTO;
+ } else if (CCS_LIM(sensor, PHY_CTRL_CAPABILITY) &
+ CCS_PHY_CTRL_CAPABILITY_UI_PHY_CTL) {
+ val = CCS_PHY_CTRL_UI;
+ } else {
+ dev_err(&client->dev, "manual PHY control not supported\n");
+ return -EINVAL;
+ }
+
+ return ccs_write(sensor, PHY_CTRL, val);
+}
+
static int ccs_power_on(struct device *dev)
{
struct v4l2_subdev *subdev = dev_get_drvdata(dev);
@@ -1333,7 +1544,6 @@ static int ccs_power_on(struct device *dev)
struct ccs_sensor *sensor =
container_of(ssd, struct ccs_sensor, ssds[0]);
const struct ccs_device *ccsdev = device_get_match_data(dev);
- unsigned int sleep;
int rval;
rval = regulator_bulk_enable(ARRAY_SIZE(ccs_regulators),
@@ -1343,21 +1553,25 @@ static int ccs_power_on(struct device *dev)
return rval;
}
- rval = clk_prepare_enable(sensor->ext_clk);
- if (rval < 0) {
- dev_dbg(dev, "failed to enable xclk\n");
- goto out_xclk_fail;
- }
+ if (sensor->reset || sensor->xshutdown || sensor->ext_clk) {
+ unsigned int sleep;
+
+ rval = clk_prepare_enable(sensor->ext_clk);
+ if (rval < 0) {
+ dev_dbg(dev, "failed to enable xclk\n");
+ goto out_xclk_fail;
+ }
- gpiod_set_value(sensor->reset, 0);
- gpiod_set_value(sensor->xshutdown, 1);
+ gpiod_set_value(sensor->reset, 0);
+ gpiod_set_value(sensor->xshutdown, 1);
- if (ccsdev->flags & CCS_DEVICE_FLAG_IS_SMIA)
- sleep = SMIAPP_RESET_DELAY(sensor->hwcfg.ext_clk);
- else
- sleep = 5000;
+ if (ccsdev->flags & CCS_DEVICE_FLAG_IS_SMIA)
+ sleep = SMIAPP_RESET_DELAY(sensor->hwcfg.ext_clk);
+ else
+ sleep = 5000;
- usleep_range(sleep, sleep);
+ usleep_range(sleep, sleep);
+ }
/*
* Failures to respond to the address change command have been noticed.
@@ -1370,18 +1584,27 @@ static int ccs_power_on(struct device *dev)
* is found.
*/
- if (sensor->hwcfg.i2c_addr_alt) {
- rval = ccs_change_cci_addr(sensor);
- if (rval) {
- dev_err(dev, "cci address change error\n");
+ if (!sensor->reset && !sensor->xshutdown) {
+ u8 retry = 100;
+ u32 reset;
+
+ rval = ccs_write(sensor, SOFTWARE_RESET, CCS_SOFTWARE_RESET_ON);
+ if (rval < 0) {
+ dev_err(dev, "software reset failed\n");
goto out_cci_addr_fail;
}
- }
- rval = ccs_write(sensor, SOFTWARE_RESET, CCS_SOFTWARE_RESET_ON);
- if (rval < 0) {
- dev_err(dev, "software reset failed\n");
- goto out_cci_addr_fail;
+ do {
+ rval = ccs_read(sensor, SOFTWARE_RESET, &reset);
+ reset = !rval && reset == CCS_SOFTWARE_RESET_OFF;
+ if (reset)
+ break;
+
+ usleep_range(1000, 2000);
+ } while (--retry);
+
+ if (!reset)
+ return -EIO;
}
if (sensor->hwcfg.i2c_addr_alt) {
@@ -1426,8 +1649,7 @@ static int ccs_power_on(struct device *dev)
goto out_cci_addr_fail;
}
- /* DPHY control done by sensor based on requested link rate */
- rval = ccs_write(sensor, PHY_CTRL, CCS_PHY_CTRL_UI);
+ rval = ccs_update_phy_ctrl(sensor);
if (rval < 0)
goto out_cci_addr_fail;
@@ -2908,7 +3130,8 @@ static int ccs_get_hwconfig(struct ccs_sensor *sensor, struct device *dev)
int i;
int rval;
- ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+ ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0,
+ FWNODE_GRAPH_ENDPOINT_NEXT);
if (!ep)
return -ENODEV;
@@ -3080,6 +3303,11 @@ static int ccs_probe(struct i2c_client *client)
return -EINVAL;
}
+ if (!sensor->hwcfg.ext_clk) {
+ dev_err(&client->dev, "cannot work with xclk frequency 0\n");
+ return -EINVAL;
+ }
+
sensor->reset = devm_gpiod_get_optional(&client->dev, "reset",
GPIOD_OUT_HIGH);
if (IS_ERR(sensor->reset))
@@ -3148,6 +3376,10 @@ static int ccs_probe(struct i2c_client *client)
goto out_free_ccs_limits;
}
+ rval = ccs_update_phy_ctrl(sensor);
+ if (rval < 0)
+ goto out_free_ccs_limits;
+
/*
* Handle Sensor Module orientation on the board.
*
diff --git a/drivers/media/i2c/ccs/ccs-data.c b/drivers/media/i2c/ccs/ccs-data.c
index 9a6097b088bd..45f2b2f55ec5 100644
--- a/drivers/media/i2c/ccs/ccs-data.c
+++ b/drivers/media/i2c/ccs/ccs-data.c
@@ -10,7 +10,6 @@
#include <linux/limits.h>
#include <linux/mm.h>
#include <linux/slab.h>
-#include <linux/types.h>
#include "ccs-data-defs.h"
@@ -152,7 +151,7 @@ static int ccs_data_parse_version(struct bin_container *bin,
vv->version_major = ((u16)v->static_data_version_major[0] << 8) +
v->static_data_version_major[1];
vv->version_minor = ((u16)v->static_data_version_minor[0] << 8) +
- v->static_data_version_major[1];
+ v->static_data_version_minor[1];
vv->date_year = ((u16)v->year[0] << 8) + v->year[1];
vv->date_month = v->month;
vv->date_day = v->day;
@@ -215,7 +214,7 @@ static int ccs_data_parse_regs(struct bin_container *bin,
size_t *__num_regs, const void *payload,
const void *endp, struct device *dev)
{
- struct ccs_reg *regs_base, *regs;
+ struct ccs_reg *regs_base = NULL, *regs = NULL;
size_t num_regs = 0;
u16 addr = 0;
@@ -286,6 +285,9 @@ static int ccs_data_parse_regs(struct bin_container *bin,
if (!bin->base) {
bin_reserve(bin, len);
} else if (__regs) {
+ if (!regs)
+ return -EIO;
+
regs->addr = addr;
regs->len = len;
regs->value = bin_alloc(bin, len);
@@ -306,8 +308,12 @@ static int ccs_data_parse_regs(struct bin_container *bin,
if (__num_regs)
*__num_regs = num_regs;
- if (bin->base && __regs)
+ if (bin->base && __regs) {
+ if (!regs_base)
+ return -EIO;
+
*__regs = regs_base;
+ }
return 0;
}
@@ -426,7 +432,7 @@ static int ccs_data_parse_rules(struct bin_container *bin,
size_t *__num_rules, const void *payload,
const void *endp, struct device *dev)
{
- struct ccs_rule *rules_base, *rules = NULL, *next_rule;
+ struct ccs_rule *rules_base = NULL, *rules = NULL, *next_rule = NULL;
size_t num_rules = 0;
const void *__next_rule = payload;
int rval;
@@ -484,6 +490,9 @@ static int ccs_data_parse_rules(struct bin_container *bin,
} else {
unsigned int i;
+ if (!next_rule)
+ return -EIO;
+
rules = next_rule;
next_rule++;
@@ -556,6 +565,9 @@ static int ccs_data_parse_rules(struct bin_container *bin,
bin_reserve(bin, sizeof(*rules) * num_rules);
*__num_rules = num_rules;
} else {
+ if (!rules_base)
+ return -EIO;
+
*__rules = rules_base;
}
@@ -691,7 +703,7 @@ static int ccs_data_parse_pdaf(struct bin_container *bin, struct ccs_pdaf_pix_lo
}
for (i = 0; i < max_block_type_id; i++) {
- struct ccs_pdaf_pix_loc_pixel_desc_group *pdgroup;
+ struct ccs_pdaf_pix_loc_pixel_desc_group *pdgroup = NULL;
unsigned int j;
if (!is_contained(__num_pixel_descs, endp))
@@ -722,6 +734,9 @@ static int ccs_data_parse_pdaf(struct bin_container *bin, struct ccs_pdaf_pix_lo
if (!bin->base)
continue;
+ if (!pdgroup)
+ return -EIO;
+
pdesc = &pdgroup->descs[j];
pdesc->pixel_type = __pixel_desc->pixel_type;
pdesc->small_offset_x = __pixel_desc->small_offset_x;
diff --git a/drivers/media/i2c/ccs/ccs-data.h b/drivers/media/i2c/ccs/ccs-data.h
index 50d6508b24f3..c75d480c8792 100644
--- a/drivers/media/i2c/ccs/ccs-data.h
+++ b/drivers/media/i2c/ccs/ccs-data.h
@@ -10,6 +10,8 @@
#include <linux/types.h>
+struct device;
+
/**
* struct ccs_data_block_version - CCS static data version
* @version_major: Major version number
diff --git a/drivers/media/i2c/ccs/ccs-reg-access.c b/drivers/media/i2c/ccs/ccs-reg-access.c
index b776af2a3c33..25993445f4fe 100644
--- a/drivers/media/i2c/ccs/ccs-reg-access.c
+++ b/drivers/media/i2c/ccs/ccs-reg-access.c
@@ -17,11 +17,10 @@
#include "ccs.h"
#include "ccs-limits.h"
-static uint32_t float_to_u32_mul_1000000(struct i2c_client *client,
- uint32_t phloat)
+static u32 float_to_u32_mul_1000000(struct i2c_client *client, u32 phloat)
{
- int32_t exp;
- uint64_t man;
+ s32 exp;
+ u64 man;
if (phloat >= 0x80000000) {
dev_err(&client->dev, "this is a negative number\n");
@@ -137,11 +136,11 @@ static int ____ccs_read_addr_8only(struct ccs_sensor *sensor, u16 reg,
unsigned int ccs_reg_width(u32 reg)
{
if (reg & CCS_FL_16BIT)
- return sizeof(uint16_t);
+ return sizeof(u16);
if (reg & CCS_FL_32BIT)
- return sizeof(uint32_t);
+ return sizeof(u32);
- return sizeof(uint8_t);
+ return sizeof(u8);
}
static u32 ireal32_to_u32_mul_1000000(struct i2c_client *client, u32 val)
@@ -205,7 +204,7 @@ static int __ccs_read_data(struct ccs_reg *regs, size_t num_regs,
size_t i;
for (i = 0; i < num_regs; i++, regs++) {
- uint8_t *data;
+ u8 *data;
if (regs->addr + regs->len < CCS_REG_ADDR(reg) + width)
continue;
@@ -216,13 +215,13 @@ static int __ccs_read_data(struct ccs_reg *regs, size_t num_regs,
data = &regs->value[CCS_REG_ADDR(reg) - regs->addr];
switch (width) {
- case sizeof(uint8_t):
+ case sizeof(u8):
*val = *data;
break;
- case sizeof(uint16_t):
+ case sizeof(u16):
*val = get_unaligned_be16(data);
break;
- case sizeof(uint32_t):
+ case sizeof(u32):
*val = get_unaligned_be32(data);
break;
default:
@@ -387,12 +386,20 @@ int ccs_write_data_regs(struct ccs_sensor *sensor, struct ccs_reg *regs,
for (j = 0; j < regs->len;
j += msg.len - 2, regdata += msg.len - 2) {
+ char printbuf[(MAX_WRITE_LEN << 1) +
+ 1 /* \0 */] = { 0 };
int rval;
msg.len = min(regs->len - j, MAX_WRITE_LEN);
+ bin2hex(printbuf, regdata, msg.len);
+ dev_dbg(&client->dev,
+ "writing msr reg 0x%4.4x value 0x%s\n",
+ regs->addr + j, printbuf);
+
put_unaligned_be16(regs->addr + j, buf);
memcpy(buf + 2, regdata, msg.len);
+
msg.len += 2;
rval = ccs_write_retry(client, &msg);
diff --git a/drivers/media/i2c/ccs/ccs.h b/drivers/media/i2c/ccs/ccs.h
index 356b87c33405..6beac375cc48 100644
--- a/drivers/media/i2c/ccs/ccs.h
+++ b/drivers/media/i2c/ccs/ccs.h
@@ -84,11 +84,11 @@ struct ccs_hwconfig {
unsigned short i2c_addr_dfl; /* Default i2c addr */
unsigned short i2c_addr_alt; /* Alternate i2c addr */
- uint32_t ext_clk; /* sensor external clk */
+ u32 ext_clk; /* sensor external clk */
unsigned int lanes; /* Number of CSI-2 lanes */
- uint32_t csi_signalling_mode; /* CCS_CSI_SIGNALLING_MODE_* */
- uint64_t *op_sys_clock;
+ u32 csi_signalling_mode; /* CCS_CSI_SIGNALLING_MODE_* */
+ u64 *op_sys_clock;
enum ccs_module_board_orient module_board_orient;
@@ -262,13 +262,13 @@ struct ccs_sensor {
unsigned long *valid_link_freqs;
/* Pixel array controls */
- struct v4l2_ctrl *analog_gain;
struct v4l2_ctrl *exposure;
struct v4l2_ctrl *hflip;
struct v4l2_ctrl *vflip;
struct v4l2_ctrl *vblank;
struct v4l2_ctrl *hblank;
struct v4l2_ctrl *pixel_rate_parray;
+ struct v4l2_ctrl *luminance_level;
/* src controls */
struct v4l2_ctrl *link_freq;
struct v4l2_ctrl *pixel_rate_csi;
diff --git a/drivers/media/i2c/ccs/smiapp-reg-defs.h b/drivers/media/i2c/ccs/smiapp-reg-defs.h
index e80c110ebf3a..177e3e51207a 100644
--- a/drivers/media/i2c/ccs/smiapp-reg-defs.h
+++ b/drivers/media/i2c/ccs/smiapp-reg-defs.h
@@ -535,6 +535,8 @@
#define SMIAPP_DIGITAL_CROP_CAPABILITY_NONE 0
#define SMIAPP_DIGITAL_CROP_CAPABILITY_INPUT_CROP 1
+#define SMIAPP_DIGITAL_GAIN_CAPABILITY_PER_CHANNEL 1
+
#define SMIAPP_BINNING_CAPABILITY_NO 0
#define SMIAPP_BINNING_CAPABILITY_YES 1
diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index e7791a0848b3..6e3382b85a90 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -390,6 +390,10 @@ static const struct imx219_reg raw10_framefmt_regs[] = {
{0x0309, 0x0a},
};
+static const s64 imx219_link_freq_menu[] = {
+ IMX219_DEFAULT_LINK_FREQ,
+};
+
static const char * const imx219_test_pattern_menu[] = {
"Disabled",
"Color Bars",
@@ -547,6 +551,7 @@ struct imx219 {
struct v4l2_ctrl_handler ctrl_handler;
/* V4L2 Controls */
struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *link_freq;
struct v4l2_ctrl *exposure;
struct v4l2_ctrl *vflip;
struct v4l2_ctrl *hflip;
@@ -806,7 +811,9 @@ static int imx219_enum_mbus_code(struct v4l2_subdev *sd,
if (code->index >= (ARRAY_SIZE(codes) / 4))
return -EINVAL;
+ mutex_lock(&imx219->mutex);
code->code = imx219_get_format_code(imx219, codes[code->index * 4]);
+ mutex_unlock(&imx219->mutex);
return 0;
}
@@ -816,11 +823,15 @@ static int imx219_enum_frame_size(struct v4l2_subdev *sd,
struct v4l2_subdev_frame_size_enum *fse)
{
struct imx219 *imx219 = to_imx219(sd);
+ u32 code;
if (fse->index >= ARRAY_SIZE(supported_modes))
return -EINVAL;
- if (fse->code != imx219_get_format_code(imx219, fse->code))
+ mutex_lock(&imx219->mutex);
+ code = imx219_get_format_code(imx219, fse->code);
+ mutex_unlock(&imx219->mutex);
+ if (fse->code != code)
return -EINVAL;
fse->min_width = supported_modes[fse->index].width;
@@ -1263,7 +1274,7 @@ static int imx219_init_controls(struct imx219 *imx219)
int i, ret;
ctrl_hdlr = &imx219->ctrl_handler;
- ret = v4l2_ctrl_handler_init(ctrl_hdlr, 11);
+ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 12);
if (ret)
return ret;
@@ -1277,6 +1288,14 @@ static int imx219_init_controls(struct imx219 *imx219)
IMX219_PIXEL_RATE, 1,
IMX219_PIXEL_RATE);
+ imx219->link_freq =
+ v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx219_ctrl_ops,
+ V4L2_CID_LINK_FREQ,
+ ARRAY_SIZE(imx219_link_freq_menu) - 1, 0,
+ imx219_link_freq_menu);
+ if (imx219->link_freq)
+ imx219->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
/* Initial vblank/hblank/exposure parameters based on current mode */
imx219->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops,
V4L2_CID_VBLANK, IMX219_VBLANK_MIN,
diff --git a/drivers/media/i2c/imx258.c b/drivers/media/i2c/imx258.c
index df62c69a48c0..61d74b794582 100644
--- a/drivers/media/i2c/imx258.c
+++ b/drivers/media/i2c/imx258.c
@@ -2,6 +2,7 @@
// Copyright (C) 2018 Intel Corporation
#include <linux/acpi.h>
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
@@ -68,6 +69,9 @@
#define REG_CONFIG_MIRROR_FLIP 0x03
#define REG_CONFIG_FLIP_TEST_PATTERN 0x02
+/* Input clock frequency in Hz */
+#define IMX258_INPUT_CLOCK_FREQ 19200000
+
struct imx258_reg {
u16 address;
u8 val;
@@ -610,6 +614,8 @@ struct imx258 {
/* Streaming on/off */
bool streaming;
+
+ struct clk *clk;
};
static inline struct imx258 *to_imx258(struct v4l2_subdev *_sd)
@@ -972,6 +978,29 @@ static int imx258_stop_streaming(struct imx258 *imx258)
return 0;
}
+static int imx258_power_on(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct imx258 *imx258 = to_imx258(sd);
+ int ret;
+
+ ret = clk_prepare_enable(imx258->clk);
+ if (ret)
+ dev_err(dev, "failed to enable clock\n");
+
+ return ret;
+}
+
+static int imx258_power_off(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct imx258 *imx258 = to_imx258(sd);
+
+ clk_disable_unprepare(imx258->clk);
+
+ return 0;
+}
+
static int imx258_set_stream(struct v4l2_subdev *sd, int enable)
{
struct imx258 *imx258 = to_imx258(sd);
@@ -1018,8 +1047,7 @@ err_unlock:
static int __maybe_unused imx258_suspend(struct device *dev)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
struct imx258 *imx258 = to_imx258(sd);
if (imx258->streaming)
@@ -1030,8 +1058,7 @@ static int __maybe_unused imx258_suspend(struct device *dev)
static int __maybe_unused imx258_resume(struct device *dev)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
struct imx258 *imx258 = to_imx258(sd);
int ret;
@@ -1201,9 +1228,26 @@ static int imx258_probe(struct i2c_client *client)
int ret;
u32 val = 0;
- device_property_read_u32(&client->dev, "clock-frequency", &val);
- if (val != 19200000)
+ imx258 = devm_kzalloc(&client->dev, sizeof(*imx258), GFP_KERNEL);
+ if (!imx258)
+ return -ENOMEM;
+
+ imx258->clk = devm_clk_get_optional(&client->dev, NULL);
+ if (!imx258->clk) {
+ dev_dbg(&client->dev,
+ "no clock provided, using clock-frequency property\n");
+
+ device_property_read_u32(&client->dev, "clock-frequency", &val);
+ if (val != IMX258_INPUT_CLOCK_FREQ)
+ return -EINVAL;
+ } else if (IS_ERR(imx258->clk)) {
+ return dev_err_probe(&client->dev, PTR_ERR(imx258->clk),
+ "error getting clock\n");
+ }
+ if (clk_get_rate(imx258->clk) != IMX258_INPUT_CLOCK_FREQ) {
+ dev_err(&client->dev, "input clock frequency not supported\n");
return -EINVAL;
+ }
/*
* Check that the device is mounted upside down. The driver only
@@ -1213,24 +1257,25 @@ static int imx258_probe(struct i2c_client *client)
if (ret || val != 180)
return -EINVAL;
- imx258 = devm_kzalloc(&client->dev, sizeof(*imx258), GFP_KERNEL);
- if (!imx258)
- return -ENOMEM;
-
/* Initialize subdev */
v4l2_i2c_subdev_init(&imx258->sd, client, &imx258_subdev_ops);
+ /* Will be powered off via pm_runtime_idle */
+ ret = imx258_power_on(&client->dev);
+ if (ret)
+ return ret;
+
/* Check module identity */
ret = imx258_identify_module(imx258);
if (ret)
- return ret;
+ goto error_identify;
/* Set default mode to max resolution */
imx258->cur_mode = &supported_modes[0];
ret = imx258_init_controls(imx258);
if (ret)
- return ret;
+ goto error_identify;
/* Initialize subdev */
imx258->sd.internal_ops = &imx258_internal_ops;
@@ -1260,6 +1305,9 @@ error_media_entity:
error_handler_free:
imx258_free_controls(imx258);
+error_identify:
+ imx258_power_off(&client->dev);
+
return ret;
}
@@ -1273,6 +1321,8 @@ static int imx258_remove(struct i2c_client *client)
imx258_free_controls(imx258);
pm_runtime_disable(&client->dev);
+ if (!pm_runtime_status_suspended(&client->dev))
+ imx258_power_off(&client->dev);
pm_runtime_set_suspended(&client->dev);
return 0;
@@ -1280,6 +1330,7 @@ static int imx258_remove(struct i2c_client *client)
static const struct dev_pm_ops imx258_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(imx258_suspend, imx258_resume)
+ SET_RUNTIME_PM_OPS(imx258_power_off, imx258_power_on, NULL)
};
#ifdef CONFIG_ACPI
@@ -1291,11 +1342,18 @@ static const struct acpi_device_id imx258_acpi_ids[] = {
MODULE_DEVICE_TABLE(acpi, imx258_acpi_ids);
#endif
+static const struct of_device_id imx258_dt_ids[] = {
+ { .compatible = "sony,imx258" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx258_dt_ids);
+
static struct i2c_driver imx258_i2c_driver = {
.driver = {
.name = "imx258",
.pm = &imx258_pm_ops,
.acpi_match_table = ACPI_PTR(imx258_acpi_ids),
+ .of_match_table = imx258_dt_ids,
},
.probe_new = imx258_probe,
.remove = imx258_remove,
diff --git a/drivers/media/i2c/imx334.c b/drivers/media/i2c/imx334.c
new file mode 100644
index 000000000000..ad530f0d338a
--- /dev/null
+++ b/drivers/media/i2c/imx334.c
@@ -0,0 +1,1132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sony imx334 sensor driver
+ *
+ * Copyright (C) 2021 Intel Corporation
+ */
+#include <asm/unaligned.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+/* Streaming Mode */
+#define IMX334_REG_MODE_SELECT 0x3000
+#define IMX334_MODE_STANDBY 0x01
+#define IMX334_MODE_STREAMING 0x00
+
+/* Lines per frame */
+#define IMX334_REG_LPFR 0x3030
+
+/* Chip ID */
+#define IMX334_REG_ID 0x3044
+#define IMX334_ID 0x1e
+
+/* Exposure control */
+#define IMX334_REG_SHUTTER 0x3058
+#define IMX334_EXPOSURE_MIN 1
+#define IMX334_EXPOSURE_OFFSET 5
+#define IMX334_EXPOSURE_STEP 1
+#define IMX334_EXPOSURE_DEFAULT 0x0648
+
+/* Analog gain control */
+#define IMX334_REG_AGAIN 0x30e8
+#define IMX334_AGAIN_MIN 0
+#define IMX334_AGAIN_MAX 240
+#define IMX334_AGAIN_STEP 1
+#define IMX334_AGAIN_DEFAULT 0
+
+/* Group hold register */
+#define IMX334_REG_HOLD 0x3001
+
+/* Input clock rate */
+#define IMX334_INCLK_RATE 24000000
+
+/* CSI2 HW configuration */
+#define IMX334_LINK_FREQ 891000000
+#define IMX334_NUM_DATA_LANES 4
+
+#define IMX334_REG_MIN 0x00
+#define IMX334_REG_MAX 0xfffff
+
+/**
+ * struct imx334_reg - imx334 sensor register
+ * @address: Register address
+ * @val: Register value
+ */
+struct imx334_reg {
+ u16 address;
+ u8 val;
+};
+
+/**
+ * struct imx334_reg_list - imx334 sensor register list
+ * @num_of_regs: Number of registers in the list
+ * @regs: Pointer to register list
+ */
+struct imx334_reg_list {
+ u32 num_of_regs;
+ const struct imx334_reg *regs;
+};
+
+/**
+ * struct imx334_mode - imx334 sensor mode structure
+ * @width: Frame width
+ * @height: Frame height
+ * @code: Format code
+ * @hblank: Horizontal blanking in lines
+ * @vblank: Vertical blanking in lines
+ * @vblank_min: Minimal vertical blanking in lines
+ * @vblank_max: Maximum vertical blanking in lines
+ * @pclk: Sensor pixel clock
+ * @link_freq_idx: Link frequency index
+ * @reg_list: Register list for sensor mode
+ */
+struct imx334_mode {
+ u32 width;
+ u32 height;
+ u32 code;
+ u32 hblank;
+ u32 vblank;
+ u32 vblank_min;
+ u32 vblank_max;
+ u64 pclk;
+ u32 link_freq_idx;
+ struct imx334_reg_list reg_list;
+};
+
+/**
+ * struct imx334 - imx334 sensor device structure
+ * @dev: Pointer to generic device
+ * @client: Pointer to i2c client
+ * @sd: V4L2 sub-device
+ * @pad: Media pad. Only one pad supported
+ * @reset_gpio: Sensor reset gpio
+ * @inclk: Sensor input clock
+ * @ctrl_handler: V4L2 control handler
+ * @link_freq_ctrl: Pointer to link frequency control
+ * @pclk_ctrl: Pointer to pixel clock control
+ * @hblank_ctrl: Pointer to horizontal blanking control
+ * @vblank_ctrl: Pointer to vertical blanking control
+ * @exp_ctrl: Pointer to exposure control
+ * @again_ctrl: Pointer to analog gain control
+ * @vblank: Vertical blanking in lines
+ * @cur_mode: Pointer to current selected sensor mode
+ * @mutex: Mutex for serializing sensor controls
+ * @streaming: Flag indicating streaming state
+ */
+struct imx334 {
+ struct device *dev;
+ struct i2c_client *client;
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+ struct gpio_desc *reset_gpio;
+ struct clk *inclk;
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct v4l2_ctrl *link_freq_ctrl;
+ struct v4l2_ctrl *pclk_ctrl;
+ struct v4l2_ctrl *hblank_ctrl;
+ struct v4l2_ctrl *vblank_ctrl;
+ struct {
+ struct v4l2_ctrl *exp_ctrl;
+ struct v4l2_ctrl *again_ctrl;
+ };
+ u32 vblank;
+ const struct imx334_mode *cur_mode;
+ struct mutex mutex;
+ bool streaming;
+};
+
+static const s64 link_freq[] = {
+ IMX334_LINK_FREQ,
+};
+
+/* Sensor mode registers */
+static const struct imx334_reg mode_3840x2160_regs[] = {
+ {0x3000, 0x01},
+ {0x3002, 0x00},
+ {0x3018, 0x04},
+ {0x37b0, 0x36},
+ {0x304c, 0x00},
+ {0x300c, 0x3b},
+ {0x300d, 0x2a},
+ {0x3034, 0x26},
+ {0x3035, 0x02},
+ {0x314c, 0x29},
+ {0x314d, 0x01},
+ {0x315a, 0x02},
+ {0x3168, 0xa0},
+ {0x316a, 0x7e},
+ {0x3288, 0x21},
+ {0x328a, 0x02},
+ {0x302c, 0x3c},
+ {0x302e, 0x00},
+ {0x302f, 0x0f},
+ {0x3076, 0x70},
+ {0x3077, 0x08},
+ {0x3090, 0x70},
+ {0x3091, 0x08},
+ {0x30d8, 0x20},
+ {0x30d9, 0x12},
+ {0x3308, 0x70},
+ {0x3309, 0x08},
+ {0x3414, 0x05},
+ {0x3416, 0x18},
+ {0x35ac, 0x0e},
+ {0x3648, 0x01},
+ {0x364a, 0x04},
+ {0x364c, 0x04},
+ {0x3678, 0x01},
+ {0x367c, 0x31},
+ {0x367e, 0x31},
+ {0x3708, 0x02},
+ {0x3714, 0x01},
+ {0x3715, 0x02},
+ {0x3716, 0x02},
+ {0x3717, 0x02},
+ {0x371c, 0x3d},
+ {0x371d, 0x3f},
+ {0x372c, 0x00},
+ {0x372d, 0x00},
+ {0x372e, 0x46},
+ {0x372f, 0x00},
+ {0x3730, 0x89},
+ {0x3731, 0x00},
+ {0x3732, 0x08},
+ {0x3733, 0x01},
+ {0x3734, 0xfe},
+ {0x3735, 0x05},
+ {0x375d, 0x00},
+ {0x375e, 0x00},
+ {0x375f, 0x61},
+ {0x3760, 0x06},
+ {0x3768, 0x1b},
+ {0x3769, 0x1b},
+ {0x376a, 0x1a},
+ {0x376b, 0x19},
+ {0x376c, 0x18},
+ {0x376d, 0x14},
+ {0x376e, 0x0f},
+ {0x3776, 0x00},
+ {0x3777, 0x00},
+ {0x3778, 0x46},
+ {0x3779, 0x00},
+ {0x377a, 0x08},
+ {0x377b, 0x01},
+ {0x377c, 0x45},
+ {0x377d, 0x01},
+ {0x377e, 0x23},
+ {0x377f, 0x02},
+ {0x3780, 0xd9},
+ {0x3781, 0x03},
+ {0x3782, 0xf5},
+ {0x3783, 0x06},
+ {0x3784, 0xa5},
+ {0x3788, 0x0f},
+ {0x378a, 0xd9},
+ {0x378b, 0x03},
+ {0x378c, 0xeb},
+ {0x378d, 0x05},
+ {0x378e, 0x87},
+ {0x378f, 0x06},
+ {0x3790, 0xf5},
+ {0x3792, 0x43},
+ {0x3794, 0x7a},
+ {0x3796, 0xa1},
+ {0x3e04, 0x0e},
+ {0x3a00, 0x01},
+};
+
+/* Supported sensor mode configurations */
+static const struct imx334_mode supported_mode = {
+ .width = 3840,
+ .height = 2160,
+ .hblank = 560,
+ .vblank = 2340,
+ .vblank_min = 90,
+ .vblank_max = 132840,
+ .pclk = 594000000,
+ .link_freq_idx = 0,
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .reg_list = {
+ .num_of_regs = ARRAY_SIZE(mode_3840x2160_regs),
+ .regs = mode_3840x2160_regs,
+ },
+};
+
+/**
+ * to_imx334() - imv334 V4L2 sub-device to imx334 device.
+ * @subdev: pointer to imx334 V4L2 sub-device
+ *
+ * Return: pointer to imx334 device
+ */
+static inline struct imx334 *to_imx334(struct v4l2_subdev *subdev)
+{
+ return container_of(subdev, struct imx334, sd);
+}
+
+/**
+ * imx334_read_reg() - Read registers.
+ * @imx334: pointer to imx334 device
+ * @reg: register address
+ * @len: length of bytes to read. Max supported bytes is 4
+ * @val: pointer to register value to be filled.
+ *
+ * Big endian register addresses with little endian values.
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_read_reg(struct imx334 *imx334, u16 reg, u32 len, u32 *val)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&imx334->sd);
+ struct i2c_msg msgs[2] = {0};
+ u8 addr_buf[2] = {0};
+ u8 data_buf[4] = {0};
+ int ret;
+
+ if (WARN_ON(len > 4))
+ return -EINVAL;
+
+ put_unaligned_be16(reg, addr_buf);
+
+ /* Write register address */
+ msgs[0].addr = client->addr;
+ msgs[0].flags = 0;
+ msgs[0].len = ARRAY_SIZE(addr_buf);
+ msgs[0].buf = addr_buf;
+
+ /* Read data from register */
+ msgs[1].addr = client->addr;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].len = len;
+ msgs[1].buf = data_buf;
+
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret != ARRAY_SIZE(msgs))
+ return -EIO;
+
+ *val = get_unaligned_le32(data_buf);
+
+ return 0;
+}
+
+/**
+ * imx334_write_reg() - Write register
+ * @imx334: pointer to imx334 device
+ * @reg: register address
+ * @len: length of bytes. Max supported bytes is 4
+ * @val: register value
+ *
+ * Big endian register addresses with little endian values.
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_write_reg(struct imx334 *imx334, u16 reg, u32 len, u32 val)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&imx334->sd);
+ u8 buf[6] = {0};
+
+ if (WARN_ON(len > 4))
+ return -EINVAL;
+
+ put_unaligned_be16(reg, buf);
+ put_unaligned_le32(val, buf + 2);
+ if (i2c_master_send(client, buf, len + 2) != len + 2)
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * imx334_write_regs() - Write a list of registers
+ * @imx334: pointer to imx334 device
+ * @regs: list of registers to be written
+ * @len: length of registers array
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_write_regs(struct imx334 *imx334,
+ const struct imx334_reg *regs, u32 len)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < len; i++) {
+ ret = imx334_write_reg(imx334, regs[i].address, 1, regs[i].val);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * imx334_update_controls() - Update control ranges based on streaming mode
+ * @imx334: pointer to imx334 device
+ * @mode: pointer to imx334_mode sensor mode
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_update_controls(struct imx334 *imx334,
+ const struct imx334_mode *mode)
+{
+ int ret;
+
+ ret = __v4l2_ctrl_s_ctrl(imx334->link_freq_ctrl, mode->link_freq_idx);
+ if (ret)
+ return ret;
+
+ ret = __v4l2_ctrl_s_ctrl(imx334->hblank_ctrl, mode->hblank);
+ if (ret)
+ return ret;
+
+ return __v4l2_ctrl_modify_range(imx334->vblank_ctrl, mode->vblank_min,
+ mode->vblank_max, 1, mode->vblank);
+}
+
+/**
+ * imx334_update_exp_gain() - Set updated exposure and gain
+ * @imx334: pointer to imx334 device
+ * @exposure: updated exposure value
+ * @gain: updated analog gain value
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_update_exp_gain(struct imx334 *imx334, u32 exposure, u32 gain)
+{
+ u32 lpfr, shutter;
+ int ret;
+
+ lpfr = imx334->vblank + imx334->cur_mode->height;
+ shutter = lpfr - exposure;
+
+ dev_dbg(imx334->dev, "Set long exp %u analog gain %u sh0 %u lpfr %u",
+ exposure, gain, shutter, lpfr);
+
+ ret = imx334_write_reg(imx334, IMX334_REG_HOLD, 1, 1);
+ if (ret)
+ return ret;
+
+ ret = imx334_write_reg(imx334, IMX334_REG_LPFR, 3, lpfr);
+ if (ret)
+ goto error_release_group_hold;
+
+ ret = imx334_write_reg(imx334, IMX334_REG_SHUTTER, 3, shutter);
+ if (ret)
+ goto error_release_group_hold;
+
+ ret = imx334_write_reg(imx334, IMX334_REG_AGAIN, 1, gain);
+
+error_release_group_hold:
+ imx334_write_reg(imx334, IMX334_REG_HOLD, 1, 0);
+
+ return ret;
+}
+
+/**
+ * imx334_set_ctrl() - Set subdevice control
+ * @ctrl: pointer to v4l2_ctrl structure
+ *
+ * Supported controls:
+ * - V4L2_CID_VBLANK
+ * - cluster controls:
+ * - V4L2_CID_ANALOGUE_GAIN
+ * - V4L2_CID_EXPOSURE
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct imx334 *imx334 =
+ container_of(ctrl->handler, struct imx334, ctrl_handler);
+ u32 analog_gain;
+ u32 exposure;
+ int ret;
+
+ switch (ctrl->id) {
+ case V4L2_CID_VBLANK:
+ imx334->vblank = imx334->vblank_ctrl->val;
+
+ dev_dbg(imx334->dev, "Received vblank %u, new lpfr %u",
+ imx334->vblank,
+ imx334->vblank + imx334->cur_mode->height);
+
+ ret = __v4l2_ctrl_modify_range(imx334->exp_ctrl,
+ IMX334_EXPOSURE_MIN,
+ imx334->vblank +
+ imx334->cur_mode->height -
+ IMX334_EXPOSURE_OFFSET,
+ 1, IMX334_EXPOSURE_DEFAULT);
+ break;
+ case V4L2_CID_EXPOSURE:
+
+ /* Set controls only if sensor is in power on state */
+ if (!pm_runtime_get_if_in_use(imx334->dev))
+ return 0;
+
+ exposure = ctrl->val;
+ analog_gain = imx334->again_ctrl->val;
+
+ dev_dbg(imx334->dev, "Received exp %u analog gain %u",
+ exposure, analog_gain);
+
+ ret = imx334_update_exp_gain(imx334, exposure, analog_gain);
+
+ pm_runtime_put(imx334->dev);
+
+ break;
+ default:
+ dev_err(imx334->dev, "Invalid control %d", ctrl->id);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/* V4l2 subdevice control ops*/
+static const struct v4l2_ctrl_ops imx334_ctrl_ops = {
+ .s_ctrl = imx334_set_ctrl,
+};
+
+/**
+ * imx334_enum_mbus_code() - Enumerate V4L2 sub-device mbus codes
+ * @sd: pointer to imx334 V4L2 sub-device structure
+ * @cfg: V4L2 sub-device pad configuration
+ * @code: V4L2 sub-device code enumeration need to be filled
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index > 0)
+ return -EINVAL;
+
+ code->code = supported_mode.code;
+
+ return 0;
+}
+
+/**
+ * imx334_enum_frame_size() - Enumerate V4L2 sub-device frame sizes
+ * @sd: pointer to imx334 V4L2 sub-device structure
+ * @cfg: V4L2 sub-device pad configuration
+ * @fsize: V4L2 sub-device size enumeration need to be filled
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fsize)
+{
+ if (fsize->index > 0)
+ return -EINVAL;
+
+ if (fsize->code != supported_mode.code)
+ return -EINVAL;
+
+ fsize->min_width = supported_mode.width;
+ fsize->max_width = fsize->min_width;
+ fsize->min_height = supported_mode.height;
+ fsize->max_height = fsize->min_height;
+
+ return 0;
+}
+
+/**
+ * imx334_fill_pad_format() - Fill subdevice pad format
+ * from selected sensor mode
+ * @imx334: pointer to imx334 device
+ * @mode: pointer to imx334_mode sensor mode
+ * @fmt: V4L2 sub-device format need to be filled
+ */
+static void imx334_fill_pad_format(struct imx334 *imx334,
+ const struct imx334_mode *mode,
+ struct v4l2_subdev_format *fmt)
+{
+ fmt->format.width = mode->width;
+ fmt->format.height = mode->height;
+ fmt->format.code = mode->code;
+ fmt->format.field = V4L2_FIELD_NONE;
+ fmt->format.colorspace = V4L2_COLORSPACE_RAW;
+ fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT;
+ fmt->format.xfer_func = V4L2_XFER_FUNC_NONE;
+}
+
+/**
+ * imx334_get_pad_format() - Get subdevice pad format
+ * @sd: pointer to imx334 V4L2 sub-device structure
+ * @cfg: V4L2 sub-device pad configuration
+ * @fmt: V4L2 sub-device format need to be set
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_get_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct imx334 *imx334 = to_imx334(sd);
+
+ mutex_lock(&imx334->mutex);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ struct v4l2_mbus_framefmt *framefmt;
+
+ framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
+ fmt->format = *framefmt;
+ } else {
+ imx334_fill_pad_format(imx334, imx334->cur_mode, fmt);
+ }
+
+ mutex_unlock(&imx334->mutex);
+
+ return 0;
+}
+
+/**
+ * imx334_set_pad_format() - Set subdevice pad format
+ * @sd: pointer to imx334 V4L2 sub-device structure
+ * @cfg: V4L2 sub-device pad configuration
+ * @fmt: V4L2 sub-device format need to be set
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct imx334 *imx334 = to_imx334(sd);
+ const struct imx334_mode *mode;
+ int ret = 0;
+
+ mutex_lock(&imx334->mutex);
+
+ mode = &supported_mode;
+ imx334_fill_pad_format(imx334, mode, fmt);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ struct v4l2_mbus_framefmt *framefmt;
+
+ framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
+ *framefmt = fmt->format;
+ } else {
+ ret = imx334_update_controls(imx334, mode);
+ if (!ret)
+ imx334->cur_mode = mode;
+ }
+
+ mutex_unlock(&imx334->mutex);
+
+ return ret;
+}
+
+/**
+ * imx334_init_pad_cfg() - Initialize sub-device pad configuration
+ * @sd: pointer to imx334 V4L2 sub-device structure
+ * @cfg: V4L2 sub-device pad configuration
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_init_pad_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg)
+{
+ struct imx334 *imx334 = to_imx334(sd);
+ struct v4l2_subdev_format fmt = { 0 };
+
+ fmt.which = cfg ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ imx334_fill_pad_format(imx334, &supported_mode, &fmt);
+
+ return imx334_set_pad_format(sd, cfg, &fmt);
+}
+
+/**
+ * imx334_start_streaming() - Start sensor stream
+ * @imx334: pointer to imx334 device
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_start_streaming(struct imx334 *imx334)
+{
+ const struct imx334_reg_list *reg_list;
+ int ret;
+
+ /* Write sensor mode registers */
+ reg_list = &imx334->cur_mode->reg_list;
+ ret = imx334_write_regs(imx334, reg_list->regs,
+ reg_list->num_of_regs);
+ if (ret) {
+ dev_err(imx334->dev, "fail to write initial registers");
+ return ret;
+ }
+
+ /* Setup handler will write actual exposure and gain */
+ ret = __v4l2_ctrl_handler_setup(imx334->sd.ctrl_handler);
+ if (ret) {
+ dev_err(imx334->dev, "fail to setup handler");
+ return ret;
+ }
+
+ /* Start streaming */
+ ret = imx334_write_reg(imx334, IMX334_REG_MODE_SELECT,
+ 1, IMX334_MODE_STREAMING);
+ if (ret) {
+ dev_err(imx334->dev, "fail to start streaming");
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * imx334_stop_streaming() - Stop sensor stream
+ * @imx334: pointer to imx334 device
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_stop_streaming(struct imx334 *imx334)
+{
+ return imx334_write_reg(imx334, IMX334_REG_MODE_SELECT,
+ 1, IMX334_MODE_STANDBY);
+}
+
+/**
+ * imx334_set_stream() - Enable sensor streaming
+ * @sd: pointer to imx334 subdevice
+ * @enable: set to enable sensor streaming
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct imx334 *imx334 = to_imx334(sd);
+ int ret;
+
+ mutex_lock(&imx334->mutex);
+
+ if (imx334->streaming == enable) {
+ mutex_unlock(&imx334->mutex);
+ return 0;
+ }
+
+ if (enable) {
+ ret = pm_runtime_get_sync(imx334->dev);
+ if (ret)
+ goto error_power_off;
+
+ ret = imx334_start_streaming(imx334);
+ if (ret)
+ goto error_power_off;
+ } else {
+ imx334_stop_streaming(imx334);
+ pm_runtime_put(imx334->dev);
+ }
+
+ imx334->streaming = enable;
+
+ mutex_unlock(&imx334->mutex);
+
+ return 0;
+
+error_power_off:
+ pm_runtime_put(imx334->dev);
+ mutex_unlock(&imx334->mutex);
+
+ return ret;
+}
+
+/**
+ * imx334_detect() - Detect imx334 sensor
+ * @imx334: pointer to imx334 device
+ *
+ * Return: 0 if successful, -EIO if sensor id does not match
+ */
+static int imx334_detect(struct imx334 *imx334)
+{
+ int ret;
+ u32 val;
+
+ ret = imx334_read_reg(imx334, IMX334_REG_ID, 2, &val);
+ if (ret)
+ return ret;
+
+ if (val != IMX334_ID) {
+ dev_err(imx334->dev, "chip id mismatch: %x!=%x",
+ IMX334_ID, val);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+/**
+ * imx334_parse_hw_config() - Parse HW configuration and check if supported
+ * @imx334: pointer to imx334 device
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_parse_hw_config(struct imx334 *imx334)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(imx334->dev);
+ struct v4l2_fwnode_endpoint bus_cfg = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY
+ };
+ struct fwnode_handle *ep;
+ unsigned long rate;
+ int ret;
+ int i;
+
+ if (!fwnode)
+ return -ENXIO;
+
+ /* Request optional reset pin */
+ imx334->reset_gpio = devm_gpiod_get_optional(imx334->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(imx334->reset_gpio)) {
+ dev_err(imx334->dev, "failed to get reset gpio %ld",
+ PTR_ERR(imx334->reset_gpio));
+ return PTR_ERR(imx334->reset_gpio);
+ }
+
+ /* Get sensor input clock */
+ imx334->inclk = devm_clk_get(imx334->dev, NULL);
+ if (IS_ERR(imx334->inclk)) {
+ dev_err(imx334->dev, "could not get inclk");
+ return PTR_ERR(imx334->inclk);
+ }
+
+ rate = clk_get_rate(imx334->inclk);
+ if (rate != IMX334_INCLK_RATE) {
+ dev_err(imx334->dev, "inclk frequency mismatch");
+ return -EINVAL;
+ }
+
+ ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+ if (!ep)
+ return -ENXIO;
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+ fwnode_handle_put(ep);
+ if (ret)
+ return ret;
+
+ if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX334_NUM_DATA_LANES) {
+ dev_err(imx334->dev,
+ "number of CSI2 data lanes %d is not supported",
+ bus_cfg.bus.mipi_csi2.num_data_lanes);
+ ret = -EINVAL;
+ goto done_endpoint_free;
+ }
+
+ if (!bus_cfg.nr_of_link_frequencies) {
+ dev_err(imx334->dev, "no link frequencies defined");
+ ret = -EINVAL;
+ goto done_endpoint_free;
+ }
+
+ for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++)
+ if (bus_cfg.link_frequencies[i] == IMX334_LINK_FREQ)
+ goto done_endpoint_free;
+
+ ret = -EINVAL;
+
+done_endpoint_free:
+ v4l2_fwnode_endpoint_free(&bus_cfg);
+
+ return ret;
+}
+
+/* V4l2 subdevice ops */
+static const struct v4l2_subdev_video_ops imx334_video_ops = {
+ .s_stream = imx334_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops imx334_pad_ops = {
+ .init_cfg = imx334_init_pad_cfg,
+ .enum_mbus_code = imx334_enum_mbus_code,
+ .enum_frame_size = imx334_enum_frame_size,
+ .get_fmt = imx334_get_pad_format,
+ .set_fmt = imx334_set_pad_format,
+};
+
+static const struct v4l2_subdev_ops imx334_subdev_ops = {
+ .video = &imx334_video_ops,
+ .pad = &imx334_pad_ops,
+};
+
+/**
+ * imx334_power_on() - Sensor power on sequence
+ * @dev: pointer to i2c device
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_power_on(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct imx334 *imx334 = to_imx334(sd);
+ int ret;
+
+ gpiod_set_value_cansleep(imx334->reset_gpio, 1);
+
+ ret = clk_prepare_enable(imx334->inclk);
+ if (ret) {
+ dev_err(imx334->dev, "fail to enable inclk");
+ goto error_reset;
+ }
+
+ usleep_range(18000, 20000);
+
+ return 0;
+
+error_reset:
+ gpiod_set_value_cansleep(imx334->reset_gpio, 0);
+
+ return ret;
+}
+
+/**
+ * imx334_power_off() - Sensor power off sequence
+ * @dev: pointer to i2c device
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_power_off(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct imx334 *imx334 = to_imx334(sd);
+
+ gpiod_set_value_cansleep(imx334->reset_gpio, 0);
+
+ clk_disable_unprepare(imx334->inclk);
+
+ return 0;
+}
+
+/**
+ * imx334_init_controls() - Initialize sensor subdevice controls
+ * @imx334: pointer to imx334 device
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_init_controls(struct imx334 *imx334)
+{
+ struct v4l2_ctrl_handler *ctrl_hdlr = &imx334->ctrl_handler;
+ const struct imx334_mode *mode = imx334->cur_mode;
+ u32 lpfr;
+ int ret;
+
+ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 6);
+ if (ret)
+ return ret;
+
+ /* Serialize controls with sensor device */
+ ctrl_hdlr->lock = &imx334->mutex;
+
+ /* Initialize exposure and gain */
+ lpfr = mode->vblank + mode->height;
+ imx334->exp_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
+ &imx334_ctrl_ops,
+ V4L2_CID_EXPOSURE,
+ IMX334_EXPOSURE_MIN,
+ lpfr - IMX334_EXPOSURE_OFFSET,
+ IMX334_EXPOSURE_STEP,
+ IMX334_EXPOSURE_DEFAULT);
+
+ imx334->again_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
+ &imx334_ctrl_ops,
+ V4L2_CID_ANALOGUE_GAIN,
+ IMX334_AGAIN_MIN,
+ IMX334_AGAIN_MAX,
+ IMX334_AGAIN_STEP,
+ IMX334_AGAIN_DEFAULT);
+
+ v4l2_ctrl_cluster(2, &imx334->exp_ctrl);
+
+ imx334->vblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
+ &imx334_ctrl_ops,
+ V4L2_CID_VBLANK,
+ mode->vblank_min,
+ mode->vblank_max,
+ 1, mode->vblank);
+
+ /* Read only controls */
+ imx334->pclk_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
+ &imx334_ctrl_ops,
+ V4L2_CID_PIXEL_RATE,
+ mode->pclk, mode->pclk,
+ 1, mode->pclk);
+
+ imx334->link_freq_ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr,
+ &imx334_ctrl_ops,
+ V4L2_CID_LINK_FREQ,
+ ARRAY_SIZE(link_freq) -
+ 1,
+ mode->link_freq_idx,
+ link_freq);
+ if (imx334->link_freq_ctrl)
+ imx334->link_freq_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ imx334->hblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
+ &imx334_ctrl_ops,
+ V4L2_CID_HBLANK,
+ IMX334_REG_MIN,
+ IMX334_REG_MAX,
+ 1, mode->hblank);
+ if (imx334->hblank_ctrl)
+ imx334->hblank_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ if (ctrl_hdlr->error) {
+ dev_err(imx334->dev, "control init failed: %d",
+ ctrl_hdlr->error);
+ v4l2_ctrl_handler_free(ctrl_hdlr);
+ return ctrl_hdlr->error;
+ }
+
+ imx334->sd.ctrl_handler = ctrl_hdlr;
+
+ return 0;
+}
+
+/**
+ * imx334_probe() - I2C client device binding
+ * @client: pointer to i2c client device
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_probe(struct i2c_client *client)
+{
+ struct imx334 *imx334;
+ int ret;
+
+ imx334 = devm_kzalloc(&client->dev, sizeof(*imx334), GFP_KERNEL);
+ if (!imx334)
+ return -ENOMEM;
+
+ imx334->dev = &client->dev;
+
+ /* Initialize subdev */
+ v4l2_i2c_subdev_init(&imx334->sd, client, &imx334_subdev_ops);
+
+ ret = imx334_parse_hw_config(imx334);
+ if (ret) {
+ dev_err(imx334->dev, "HW configuration is not supported");
+ return ret;
+ }
+
+ mutex_init(&imx334->mutex);
+
+ ret = imx334_power_on(imx334->dev);
+ if (ret) {
+ dev_err(imx334->dev, "failed to power-on the sensor");
+ goto error_mutex_destroy;
+ }
+
+ /* Check module identity */
+ ret = imx334_detect(imx334);
+ if (ret) {
+ dev_err(imx334->dev, "failed to find sensor: %d", ret);
+ goto error_power_off;
+ }
+
+ /* Set default mode to max resolution */
+ imx334->cur_mode = &supported_mode;
+ imx334->vblank = imx334->cur_mode->vblank;
+
+ ret = imx334_init_controls(imx334);
+ if (ret) {
+ dev_err(imx334->dev, "failed to init controls: %d", ret);
+ goto error_power_off;
+ }
+
+ /* Initialize subdev */
+ imx334->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ imx334->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+ /* Initialize source pad */
+ imx334->pad.flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&imx334->sd.entity, 1, &imx334->pad);
+ if (ret) {
+ dev_err(imx334->dev, "failed to init entity pads: %d", ret);
+ goto error_handler_free;
+ }
+
+ ret = v4l2_async_register_subdev_sensor_common(&imx334->sd);
+ if (ret < 0) {
+ dev_err(imx334->dev,
+ "failed to register async subdev: %d", ret);
+ goto error_media_entity;
+ }
+
+ pm_runtime_set_active(imx334->dev);
+ pm_runtime_enable(imx334->dev);
+ pm_runtime_idle(imx334->dev);
+
+ return 0;
+
+error_media_entity:
+ media_entity_cleanup(&imx334->sd.entity);
+error_handler_free:
+ v4l2_ctrl_handler_free(imx334->sd.ctrl_handler);
+error_power_off:
+ imx334_power_off(imx334->dev);
+error_mutex_destroy:
+ mutex_destroy(&imx334->mutex);
+
+ return ret;
+}
+
+/**
+ * imx334_remove() - I2C client device unbinding
+ * @client: pointer to I2C client device
+ *
+ * Return: 0 if successful, error code otherwise.
+ */
+static int imx334_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct imx334 *imx334 = to_imx334(sd);
+
+ v4l2_async_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+
+ pm_runtime_disable(&client->dev);
+ pm_runtime_suspended(&client->dev);
+
+ mutex_destroy(&imx334->mutex);
+
+ return 0;
+}
+
+static const struct dev_pm_ops imx334_pm_ops = {
+ SET_RUNTIME_PM_OPS(imx334_power_off, imx334_power_on, NULL)
+};
+
+static const struct of_device_id imx334_of_match[] = {
+ { .compatible = "sony,imx334" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, imx334_of_match);
+
+static struct i2c_driver imx334_driver = {
+ .probe_new = imx334_probe,
+ .remove = imx334_remove,
+ .driver = {
+ .name = "imx334",
+ .pm = &imx334_pm_ops,
+ .of_match_table = imx334_of_match,
+ },
+};
+
+module_i2c_driver(imx334_driver);
+
+MODULE_DESCRIPTION("Sony imx334 sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/i2c/max9271.c b/drivers/media/i2c/max9271.c
index c247db569bab..c495582dcff6 100644
--- a/drivers/media/i2c/max9271.c
+++ b/drivers/media/i2c/max9271.c
@@ -18,6 +18,7 @@
#include <linux/delay.h>
#include <linux/i2c.h>
+#include <linux/module.h>
#include "max9271.h"
@@ -339,3 +340,7 @@ int max9271_set_translation(struct max9271_device *dev, u8 source, u8 dest)
return 0;
}
EXPORT_SYMBOL_GPL(max9271_set_translation);
+
+MODULE_DESCRIPTION("Maxim MAX9271 GMSL Serializer");
+MODULE_AUTHOR("Jacopo Mondi");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c
index c82c1493e099..6fd4d59fcc72 100644
--- a/drivers/media/i2c/max9286.c
+++ b/drivers/media/i2c/max9286.c
@@ -163,6 +163,8 @@ struct max9286_priv {
unsigned int mux_channel;
bool mux_open;
+ u32 reverse_channel_mv;
+
struct v4l2_ctrl_handler ctrls;
struct v4l2_ctrl *pixelrate;
@@ -336,6 +338,31 @@ static void max9286_configure_i2c(struct max9286_priv *priv, bool localack)
usleep_range(3000, 5000);
}
+static void max9286_reverse_channel_setup(struct max9286_priv *priv,
+ unsigned int chan_amplitude)
+{
+ /* Reverse channel transmission time: default to 1. */
+ u8 chan_config = MAX9286_REV_TRF(1);
+
+ /*
+ * 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
*
@@ -531,10 +558,14 @@ static int max9286_notify_bound(struct v4l2_async_notifier *notifier,
* All enabled sources have probed and enabled their reverse control
* channels:
*
+ * - Increase the reverse channel amplitude to compensate for the
+ * remote ends high threshold, if not done already
* - Verify all configuration links are properly detected
* - Disable auto-ack as communication on the control channel are now
* stable.
*/
+ if (priv->reverse_channel_mv < 170)
+ max9286_reverse_channel_setup(priv, 170);
max9286_check_config_link(priv, priv->source_mask);
/*
@@ -576,19 +607,19 @@ static int max9286_v4l2_notifier_register(struct max9286_priv *priv)
for_each_source(priv, source) {
unsigned int i = to_index(priv, source);
- struct v4l2_async_subdev *asd;
+ struct max9286_asd *mas;
- asd = v4l2_async_notifier_add_fwnode_subdev(&priv->notifier,
+ mas = v4l2_async_notifier_add_fwnode_subdev(&priv->notifier,
source->fwnode,
- sizeof(*asd));
- if (IS_ERR(asd)) {
+ struct max9286_asd);
+ if (IS_ERR(mas)) {
dev_err(dev, "Failed to add subdev for source %u: %ld",
- i, PTR_ERR(asd));
+ i, PTR_ERR(mas));
v4l2_async_notifier_cleanup(&priv->notifier);
- return PTR_ERR(asd);
+ return PTR_ERR(mas);
}
- to_max9286_asd(asd)->source = source;
+ mas->source = source;
}
priv->notifier.ops = &max9286_notify_ops;
@@ -941,19 +972,7 @@ static int max9286_setup(struct max9286_priv *priv)
* only. This should be disabled after the mux is initialised.
*/
max9286_configure_i2c(priv, true);
-
- /*
- * Reverse channel setup.
- *
- * - Enable custom reverse channel configuration (through register 0x3f)
- * and set the first pulse length to 35 clock cycles.
- * - Increase the reverse channel amplitude to 170mV to accommodate the
- * high threshold enabled by the serializer driver.
- */
- max9286_write(priv, 0x3f, MAX9286_EN_REV_CFG | MAX9286_REV_FLEN(35));
- max9286_write(priv, 0x3b, MAX9286_REV_TRF(1) | MAX9286_REV_AMP(70) |
- MAX9286_REV_AMP_X);
- usleep_range(2000, 2500);
+ max9286_reverse_channel_setup(priv, priv->reverse_channel_mv);
/*
* Enable GMSL links, mask unused ones and autodetect link
@@ -1117,6 +1136,7 @@ static int max9286_parse_dt(struct max9286_priv *priv)
struct device_node *i2c_mux;
struct device_node *node = NULL;
unsigned int i2c_mux_mask = 0;
+ u32 reverse_channel_microvolt;
/* Balance the of_node_put() performed by of_find_node_by_name(). */
of_node_get(dev->of_node);
@@ -1207,6 +1227,20 @@ static int max9286_parse_dt(struct max9286_priv *priv)
}
of_node_put(node);
+ /*
+ * 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->reverse_channel_mv = 170;
+ else
+ priv->reverse_channel_mv = reverse_channel_microvolt / 1000U;
+
priv->route_mask = priv->source_mask;
return 0;
diff --git a/drivers/media/i2c/mt9m111.c b/drivers/media/i2c/mt9m111.c
index 69697386ffcd..0e11734f75aa 100644
--- a/drivers/media/i2c/mt9m111.c
+++ b/drivers/media/i2c/mt9m111.c
@@ -4,6 +4,7 @@
*
* Copyright (C) 2008, Robert Jarzmik <robert.jarzmik@free.fr>
*/
+#include <linux/clk.h>
#include <linux/videodev2.h>
#include <linux/slab.h>
#include <linux/i2c.h>
@@ -16,7 +17,6 @@
#include <linux/property.h>
#include <media/v4l2-async.h>
-#include <media/v4l2-clk.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
@@ -232,7 +232,7 @@ struct mt9m111 {
struct v4l2_ctrl *gain;
struct mt9m111_context *ctx;
struct v4l2_rect rect; /* cropping rectangle */
- struct v4l2_clk *clk;
+ struct clk *clk;
unsigned int width; /* output */
unsigned int height; /* sizes */
struct v4l2_fract frame_interval;
@@ -977,7 +977,7 @@ static int mt9m111_power_on(struct mt9m111 *mt9m111)
struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev);
int ret;
- ret = v4l2_clk_enable(mt9m111->clk);
+ ret = clk_prepare_enable(mt9m111->clk);
if (ret < 0)
return ret;
@@ -995,7 +995,7 @@ out_regulator_disable:
regulator_disable(mt9m111->regulator);
out_clk_disable:
- v4l2_clk_disable(mt9m111->clk);
+ clk_disable_unprepare(mt9m111->clk);
dev_err(&client->dev, "Failed to resume the sensor: %d\n", ret);
@@ -1006,7 +1006,7 @@ static void mt9m111_power_off(struct mt9m111 *mt9m111)
{
mt9m111_suspend(mt9m111);
regulator_disable(mt9m111->regulator);
- v4l2_clk_disable(mt9m111->clk);
+ clk_disable_unprepare(mt9m111->clk);
}
static int mt9m111_s_power(struct v4l2_subdev *sd, int on)
@@ -1266,7 +1266,7 @@ static int mt9m111_probe(struct i2c_client *client)
return ret;
}
- mt9m111->clk = v4l2_clk_get(&client->dev, "mclk");
+ mt9m111->clk = devm_clk_get(&client->dev, "mclk");
if (IS_ERR(mt9m111->clk))
return PTR_ERR(mt9m111->clk);
@@ -1311,7 +1311,7 @@ static int mt9m111_probe(struct i2c_client *client)
mt9m111->subdev.ctrl_handler = &mt9m111->hdl;
if (mt9m111->hdl.error) {
ret = mt9m111->hdl.error;
- goto out_clkput;
+ return ret;
}
#ifdef CONFIG_MEDIA_CONTROLLER
@@ -1354,8 +1354,6 @@ out_entityclean:
out_hdlfree:
#endif
v4l2_ctrl_handler_free(&mt9m111->hdl);
-out_clkput:
- v4l2_clk_put(mt9m111->clk);
return ret;
}
@@ -1366,7 +1364,6 @@ static int mt9m111_remove(struct i2c_client *client)
v4l2_async_unregister_subdev(&mt9m111->subdev);
media_entity_cleanup(&mt9m111->subdev.entity);
- v4l2_clk_put(mt9m111->clk);
v4l2_ctrl_handler_free(&mt9m111->hdl);
return 0;
diff --git a/drivers/media/i2c/mt9v111.c b/drivers/media/i2c/mt9v111.c
index 61ae6a0d5679..97c7527b74ed 100644
--- a/drivers/media/i2c/mt9v111.c
+++ b/drivers/media/i2c/mt9v111.c
@@ -1253,12 +1253,6 @@ static int mt9v111_remove(struct i2c_client *client)
mutex_destroy(&mt9v111->pwr_mutex);
mutex_destroy(&mt9v111->stream_mutex);
- devm_gpiod_put(mt9v111->dev, mt9v111->oe);
- devm_gpiod_put(mt9v111->dev, mt9v111->standby);
- devm_gpiod_put(mt9v111->dev, mt9v111->reset);
-
- devm_clk_put(mt9v111->dev, mt9v111->clk);
-
return 0;
}
diff --git a/drivers/media/i2c/ov02a10.c b/drivers/media/i2c/ov02a10.c
index 8683ffd3287a..60b4bc645334 100644
--- a/drivers/media/i2c/ov02a10.c
+++ b/drivers/media/i2c/ov02a10.c
@@ -388,7 +388,7 @@ static int ov02a10_check_sensor_id(struct ov02a10 *ov02a10)
if (ret < 0)
return ret;
- chip_id = le16_to_cpu(ret);
+ chip_id = le16_to_cpu((__force __le16)ret);
if ((chip_id & OV02A10_ID_MASK) != OV02A10_ID) {
dev_err(&client->dev, "unexpected sensor id(0x%04x)\n", chip_id);
diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c
index e7d2e5b4ad4b..1cefa15729ce 100644
--- a/drivers/media/i2c/ov5647.c
+++ b/drivers/media/i2c/ov5647.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* A V4L2 driver for OmniVision OV5647 cameras.
*
@@ -8,119 +9,316 @@
* Copyright (C) 2006-7 Jonathan Corbet <corbet@lwn.net>
*
* Copyright (C) 2016, Synopsys, Inc.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed .as is. WITHOUT ANY WARRANTY of any
- * kind, whether express or implied; without even the implied warranty
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-image-sizes.h>
#include <media/v4l2-mediabus.h>
-#define SENSOR_NAME "ov5647"
+/*
+ * From the datasheet, "20ms after PWDN goes low or 20ms after RESETB goes
+ * high if reset is inserted after PWDN goes high, host can access sensor's
+ * SCCB to initialize sensor."
+ */
+#define PWDN_ACTIVE_DELAY_MS 20
#define MIPI_CTRL00_CLOCK_LANE_GATE BIT(5)
+#define MIPI_CTRL00_LINE_SYNC_ENABLE BIT(4)
#define MIPI_CTRL00_BUS_IDLE BIT(2)
#define MIPI_CTRL00_CLOCK_LANE_DISABLE BIT(0)
#define OV5647_SW_STANDBY 0x0100
#define OV5647_SW_RESET 0x0103
-#define OV5647_REG_CHIPID_H 0x300A
-#define OV5647_REG_CHIPID_L 0x300B
-#define OV5640_REG_PAD_OUT 0x300D
+#define OV5647_REG_CHIPID_H 0x300a
+#define OV5647_REG_CHIPID_L 0x300b
+#define OV5640_REG_PAD_OUT 0x300d
+#define OV5647_REG_EXP_HI 0x3500
+#define OV5647_REG_EXP_MID 0x3501
+#define OV5647_REG_EXP_LO 0x3502
+#define OV5647_REG_AEC_AGC 0x3503
+#define OV5647_REG_GAIN_HI 0x350a
+#define OV5647_REG_GAIN_LO 0x350b
+#define OV5647_REG_VTS_HI 0x380e
+#define OV5647_REG_VTS_LO 0x380f
#define OV5647_REG_FRAME_OFF_NUMBER 0x4202
#define OV5647_REG_MIPI_CTRL00 0x4800
#define OV5647_REG_MIPI_CTRL14 0x4814
+#define OV5647_REG_AWB 0x5001
#define REG_TERM 0xfffe
#define VAL_TERM 0xfe
#define REG_DLY 0xffff
-#define OV5647_ROW_START 0x01
-#define OV5647_ROW_START_MIN 0
-#define OV5647_ROW_START_MAX 2004
-#define OV5647_ROW_START_DEF 54
+/* OV5647 native and active pixel array size */
+#define OV5647_NATIVE_WIDTH 2624U
+#define OV5647_NATIVE_HEIGHT 1956U
-#define OV5647_COLUMN_START 0x02
-#define OV5647_COLUMN_START_MIN 0
-#define OV5647_COLUMN_START_MAX 2750
-#define OV5647_COLUMN_START_DEF 16
+#define OV5647_PIXEL_ARRAY_LEFT 16U
+#define OV5647_PIXEL_ARRAY_TOP 16U
+#define OV5647_PIXEL_ARRAY_WIDTH 2592U
+#define OV5647_PIXEL_ARRAY_HEIGHT 1944U
-#define OV5647_WINDOW_HEIGHT 0x03
-#define OV5647_WINDOW_HEIGHT_MIN 2
-#define OV5647_WINDOW_HEIGHT_MAX 2006
-#define OV5647_WINDOW_HEIGHT_DEF 1944
+#define OV5647_VBLANK_MIN 4
+#define OV5647_VTS_MAX 32767
-#define OV5647_WINDOW_WIDTH 0x04
-#define OV5647_WINDOW_WIDTH_MIN 2
-#define OV5647_WINDOW_WIDTH_MAX 2752
-#define OV5647_WINDOW_WIDTH_DEF 2592
+#define OV5647_EXPOSURE_MIN 4
+#define OV5647_EXPOSURE_STEP 1
+#define OV5647_EXPOSURE_DEFAULT 1000
+#define OV5647_EXPOSURE_MAX 65535
struct regval_list {
u16 addr;
u8 data;
};
+struct ov5647_mode {
+ struct v4l2_mbus_framefmt format;
+ struct v4l2_rect crop;
+ u64 pixel_rate;
+ int hts;
+ int vts;
+ const struct regval_list *reg_list;
+ unsigned int num_regs;
+};
+
struct ov5647 {
struct v4l2_subdev sd;
struct media_pad pad;
struct mutex lock;
- struct v4l2_mbus_framefmt format;
- unsigned int width;
- unsigned int height;
- int power_count;
struct clk *xclk;
+ struct gpio_desc *pwdn;
+ bool clock_ncont;
+ struct v4l2_ctrl_handler ctrls;
+ const struct ov5647_mode *mode;
+ struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *hblank;
+ struct v4l2_ctrl *vblank;
+ struct v4l2_ctrl *exposure;
+ bool streaming;
};
-static inline struct ov5647 *to_state(struct v4l2_subdev *sd)
+static inline struct ov5647 *to_sensor(struct v4l2_subdev *sd)
{
return container_of(sd, struct ov5647, sd);
}
-static struct regval_list sensor_oe_disable_regs[] = {
+static const struct regval_list sensor_oe_disable_regs[] = {
{0x3000, 0x00},
{0x3001, 0x00},
{0x3002, 0x00},
};
-static struct regval_list sensor_oe_enable_regs[] = {
+static const struct regval_list sensor_oe_enable_regs[] = {
{0x3000, 0x0f},
{0x3001, 0xff},
{0x3002, 0xe4},
};
-static struct regval_list ov5647_640x480[] = {
+static struct regval_list ov5647_2592x1944_10bpp[] = {
{0x0100, 0x00},
{0x0103, 0x01},
- {0x3034, 0x08},
+ {0x3034, 0x1a},
{0x3035, 0x21},
- {0x3036, 0x46},
+ {0x3036, 0x69},
+ {0x303c, 0x11},
+ {0x3106, 0xf5},
+ {0x3821, 0x06},
+ {0x3820, 0x00},
+ {0x3827, 0xec},
+ {0x370c, 0x03},
+ {0x3612, 0x5b},
+ {0x3618, 0x04},
+ {0x5000, 0x06},
+ {0x5002, 0x41},
+ {0x5003, 0x08},
+ {0x5a00, 0x08},
+ {0x3000, 0x00},
+ {0x3001, 0x00},
+ {0x3002, 0x00},
+ {0x3016, 0x08},
+ {0x3017, 0xe0},
+ {0x3018, 0x44},
+ {0x301c, 0xf8},
+ {0x301d, 0xf0},
+ {0x3a18, 0x00},
+ {0x3a19, 0xf8},
+ {0x3c01, 0x80},
+ {0x3b07, 0x0c},
+ {0x380c, 0x0b},
+ {0x380d, 0x1c},
+ {0x3814, 0x11},
+ {0x3815, 0x11},
+ {0x3708, 0x64},
+ {0x3709, 0x12},
+ {0x3808, 0x0a},
+ {0x3809, 0x20},
+ {0x380a, 0x07},
+ {0x380b, 0x98},
+ {0x3800, 0x00},
+ {0x3801, 0x00},
+ {0x3802, 0x00},
+ {0x3803, 0x00},
+ {0x3804, 0x0a},
+ {0x3805, 0x3f},
+ {0x3806, 0x07},
+ {0x3807, 0xa3},
+ {0x3811, 0x10},
+ {0x3813, 0x06},
+ {0x3630, 0x2e},
+ {0x3632, 0xe2},
+ {0x3633, 0x23},
+ {0x3634, 0x44},
+ {0x3636, 0x06},
+ {0x3620, 0x64},
+ {0x3621, 0xe0},
+ {0x3600, 0x37},
+ {0x3704, 0xa0},
+ {0x3703, 0x5a},
+ {0x3715, 0x78},
+ {0x3717, 0x01},
+ {0x3731, 0x02},
+ {0x370b, 0x60},
+ {0x3705, 0x1a},
+ {0x3f05, 0x02},
+ {0x3f06, 0x10},
+ {0x3f01, 0x0a},
+ {0x3a08, 0x01},
+ {0x3a09, 0x28},
+ {0x3a0a, 0x00},
+ {0x3a0b, 0xf6},
+ {0x3a0d, 0x08},
+ {0x3a0e, 0x06},
+ {0x3a0f, 0x58},
+ {0x3a10, 0x50},
+ {0x3a1b, 0x58},
+ {0x3a1e, 0x50},
+ {0x3a11, 0x60},
+ {0x3a1f, 0x28},
+ {0x4001, 0x02},
+ {0x4004, 0x04},
+ {0x4000, 0x09},
+ {0x4837, 0x19},
+ {0x4800, 0x24},
+ {0x3503, 0x03},
+ {0x0100, 0x01},
+};
+
+static struct regval_list ov5647_1080p30_10bpp[] = {
+ {0x0100, 0x00},
+ {0x0103, 0x01},
+ {0x3034, 0x1a},
+ {0x3035, 0x21},
+ {0x3036, 0x62},
+ {0x303c, 0x11},
+ {0x3106, 0xf5},
+ {0x3821, 0x06},
+ {0x3820, 0x00},
+ {0x3827, 0xec},
+ {0x370c, 0x03},
+ {0x3612, 0x5b},
+ {0x3618, 0x04},
+ {0x5000, 0x06},
+ {0x5002, 0x41},
+ {0x5003, 0x08},
+ {0x5a00, 0x08},
+ {0x3000, 0x00},
+ {0x3001, 0x00},
+ {0x3002, 0x00},
+ {0x3016, 0x08},
+ {0x3017, 0xe0},
+ {0x3018, 0x44},
+ {0x301c, 0xf8},
+ {0x301d, 0xf0},
+ {0x3a18, 0x00},
+ {0x3a19, 0xf8},
+ {0x3c01, 0x80},
+ {0x3b07, 0x0c},
+ {0x380c, 0x09},
+ {0x380d, 0x70},
+ {0x3814, 0x11},
+ {0x3815, 0x11},
+ {0x3708, 0x64},
+ {0x3709, 0x12},
+ {0x3808, 0x07},
+ {0x3809, 0x80},
+ {0x380a, 0x04},
+ {0x380b, 0x38},
+ {0x3800, 0x01},
+ {0x3801, 0x5c},
+ {0x3802, 0x01},
+ {0x3803, 0xb2},
+ {0x3804, 0x08},
+ {0x3805, 0xe3},
+ {0x3806, 0x05},
+ {0x3807, 0xf1},
+ {0x3811, 0x04},
+ {0x3813, 0x02},
+ {0x3630, 0x2e},
+ {0x3632, 0xe2},
+ {0x3633, 0x23},
+ {0x3634, 0x44},
+ {0x3636, 0x06},
+ {0x3620, 0x64},
+ {0x3621, 0xe0},
+ {0x3600, 0x37},
+ {0x3704, 0xa0},
+ {0x3703, 0x5a},
+ {0x3715, 0x78},
+ {0x3717, 0x01},
+ {0x3731, 0x02},
+ {0x370b, 0x60},
+ {0x3705, 0x1a},
+ {0x3f05, 0x02},
+ {0x3f06, 0x10},
+ {0x3f01, 0x0a},
+ {0x3a08, 0x01},
+ {0x3a09, 0x4b},
+ {0x3a0a, 0x01},
+ {0x3a0b, 0x13},
+ {0x3a0d, 0x04},
+ {0x3a0e, 0x03},
+ {0x3a0f, 0x58},
+ {0x3a10, 0x50},
+ {0x3a1b, 0x58},
+ {0x3a1e, 0x50},
+ {0x3a11, 0x60},
+ {0x3a1f, 0x28},
+ {0x4001, 0x02},
+ {0x4004, 0x04},
+ {0x4000, 0x09},
+ {0x4837, 0x19},
+ {0x4800, 0x34},
+ {0x3503, 0x03},
+ {0x0100, 0x01},
+};
+
+static struct regval_list ov5647_2x2binned_10bpp[] = {
+ {0x0100, 0x00},
+ {0x0103, 0x01},
+ {0x3034, 0x1a},
+ {0x3035, 0x21},
+ {0x3036, 0x62},
{0x303c, 0x11},
{0x3106, 0xf5},
- {0x3821, 0x07},
- {0x3820, 0x41},
{0x3827, 0xec},
- {0x370c, 0x0f},
+ {0x370c, 0x03},
{0x3612, 0x59},
{0x3618, 0x00},
{0x5000, 0x06},
- {0x5001, 0x01},
{0x5002, 0x41},
{0x5003, 0x08},
{0x5a00, 0x08},
@@ -136,32 +334,115 @@ static struct regval_list ov5647_640x480[] = {
{0x3a19, 0xf8},
{0x3c01, 0x80},
{0x3b07, 0x0c},
+ {0x3800, 0x00},
+ {0x3801, 0x00},
+ {0x3802, 0x00},
+ {0x3803, 0x00},
+ {0x3804, 0x0a},
+ {0x3805, 0x3f},
+ {0x3806, 0x07},
+ {0x3807, 0xa3},
+ {0x3808, 0x05},
+ {0x3809, 0x10},
+ {0x380a, 0x03},
+ {0x380b, 0xcc},
{0x380c, 0x07},
{0x380d, 0x68},
- {0x380e, 0x03},
- {0x380f, 0xd8},
+ {0x3811, 0x0c},
+ {0x3813, 0x06},
{0x3814, 0x31},
{0x3815, 0x31},
+ {0x3630, 0x2e},
+ {0x3632, 0xe2},
+ {0x3633, 0x23},
+ {0x3634, 0x44},
+ {0x3636, 0x06},
+ {0x3620, 0x64},
+ {0x3621, 0xe0},
+ {0x3600, 0x37},
+ {0x3704, 0xa0},
+ {0x3703, 0x5a},
+ {0x3715, 0x78},
+ {0x3717, 0x01},
+ {0x3731, 0x02},
+ {0x370b, 0x60},
+ {0x3705, 0x1a},
+ {0x3f05, 0x02},
+ {0x3f06, 0x10},
+ {0x3f01, 0x0a},
+ {0x3a08, 0x01},
+ {0x3a09, 0x28},
+ {0x3a0a, 0x00},
+ {0x3a0b, 0xf6},
+ {0x3a0d, 0x08},
+ {0x3a0e, 0x06},
+ {0x3a0f, 0x58},
+ {0x3a10, 0x50},
+ {0x3a1b, 0x58},
+ {0x3a1e, 0x50},
+ {0x3a11, 0x60},
+ {0x3a1f, 0x28},
+ {0x4001, 0x02},
+ {0x4004, 0x04},
+ {0x4000, 0x09},
+ {0x4837, 0x16},
+ {0x4800, 0x24},
+ {0x3503, 0x03},
+ {0x3820, 0x41},
+ {0x3821, 0x07},
+ {0x350a, 0x00},
+ {0x350b, 0x10},
+ {0x3500, 0x00},
+ {0x3501, 0x1a},
+ {0x3502, 0xf0},
+ {0x3212, 0xa0},
+ {0x0100, 0x01},
+};
+
+static struct regval_list ov5647_640x480_10bpp[] = {
+ {0x0100, 0x00},
+ {0x0103, 0x01},
+ {0x3035, 0x11},
+ {0x3036, 0x46},
+ {0x303c, 0x11},
+ {0x3821, 0x07},
+ {0x3820, 0x41},
+ {0x370c, 0x03},
+ {0x3612, 0x59},
+ {0x3618, 0x00},
+ {0x5000, 0x06},
+ {0x5003, 0x08},
+ {0x5a00, 0x08},
+ {0x3000, 0xff},
+ {0x3001, 0xff},
+ {0x3002, 0xff},
+ {0x301d, 0xf0},
+ {0x3a18, 0x00},
+ {0x3a19, 0xf8},
+ {0x3c01, 0x80},
+ {0x3b07, 0x0c},
+ {0x380c, 0x07},
+ {0x380d, 0x3c},
+ {0x3814, 0x35},
+ {0x3815, 0x35},
{0x3708, 0x64},
{0x3709, 0x52},
{0x3808, 0x02},
{0x3809, 0x80},
{0x380a, 0x01},
- {0x380b, 0xE0},
- {0x3801, 0x00},
+ {0x380b, 0xe0},
+ {0x3800, 0x00},
+ {0x3801, 0x10},
{0x3802, 0x00},
{0x3803, 0x00},
{0x3804, 0x0a},
- {0x3805, 0x3f},
+ {0x3805, 0x2f},
{0x3806, 0x07},
- {0x3807, 0xa1},
- {0x3811, 0x08},
- {0x3813, 0x02},
+ {0x3807, 0x9f},
{0x3630, 0x2e},
{0x3632, 0xe2},
{0x3633, 0x23},
{0x3634, 0x44},
- {0x3636, 0x06},
{0x3620, 0x64},
{0x3621, 0xe0},
{0x3600, 0x37},
@@ -176,11 +457,11 @@ static struct regval_list ov5647_640x480[] = {
{0x3f06, 0x10},
{0x3f01, 0x0a},
{0x3a08, 0x01},
- {0x3a09, 0x27},
+ {0x3a09, 0x2e},
{0x3a0a, 0x00},
- {0x3a0b, 0xf6},
- {0x3a0d, 0x04},
- {0x3a0e, 0x03},
+ {0x3a0b, 0xfb},
+ {0x3a0d, 0x02},
+ {0x3a0e, 0x01},
{0x3a0f, 0x58},
{0x3a10, 0x50},
{0x3a1b, 0x58},
@@ -190,31 +471,152 @@ static struct regval_list ov5647_640x480[] = {
{0x4001, 0x02},
{0x4004, 0x02},
{0x4000, 0x09},
- {0x4837, 0x24},
- {0x4050, 0x6e},
- {0x4051, 0x8f},
+ {0x3000, 0x00},
+ {0x3001, 0x00},
+ {0x3002, 0x00},
+ {0x3017, 0xe0},
+ {0x301c, 0xfc},
+ {0x3636, 0x06},
+ {0x3016, 0x08},
+ {0x3827, 0xec},
+ {0x3018, 0x44},
+ {0x3035, 0x21},
+ {0x3106, 0xf5},
+ {0x3034, 0x1a},
+ {0x301c, 0xf8},
+ {0x4800, 0x34},
+ {0x3503, 0x03},
{0x0100, 0x01},
};
-static int ov5647_write(struct v4l2_subdev *sd, u16 reg, u8 val)
+static const struct ov5647_mode ov5647_modes[] = {
+ /* 2592x1944 full resolution full FOV 10-bit mode. */
+ {
+ .format = {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .field = V4L2_FIELD_NONE,
+ .width = 2592,
+ .height = 1944
+ },
+ .crop = {
+ .left = OV5647_PIXEL_ARRAY_LEFT,
+ .top = OV5647_PIXEL_ARRAY_TOP,
+ .width = 2592,
+ .height = 1944
+ },
+ .pixel_rate = 87500000,
+ .hts = 2844,
+ .vts = 0x7b0,
+ .reg_list = ov5647_2592x1944_10bpp,
+ .num_regs = ARRAY_SIZE(ov5647_2592x1944_10bpp)
+ },
+ /* 1080p30 10-bit mode. Full resolution centre-cropped down to 1080p. */
+ {
+ .format = {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .field = V4L2_FIELD_NONE,
+ .width = 1920,
+ .height = 1080
+ },
+ .crop = {
+ .left = 348 + OV5647_PIXEL_ARRAY_LEFT,
+ .top = 434 + OV5647_PIXEL_ARRAY_TOP,
+ .width = 1928,
+ .height = 1080,
+ },
+ .pixel_rate = 81666700,
+ .hts = 2416,
+ .vts = 0x450,
+ .reg_list = ov5647_1080p30_10bpp,
+ .num_regs = ARRAY_SIZE(ov5647_1080p30_10bpp)
+ },
+ /* 2x2 binned full FOV 10-bit mode. */
+ {
+ .format = {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .field = V4L2_FIELD_NONE,
+ .width = 1296,
+ .height = 972
+ },
+ .crop = {
+ .left = OV5647_PIXEL_ARRAY_LEFT,
+ .top = OV5647_PIXEL_ARRAY_TOP,
+ .width = 2592,
+ .height = 1944,
+ },
+ .pixel_rate = 81666700,
+ .hts = 1896,
+ .vts = 0x59b,
+ .reg_list = ov5647_2x2binned_10bpp,
+ .num_regs = ARRAY_SIZE(ov5647_2x2binned_10bpp)
+ },
+ /* 10-bit VGA full FOV 60fps. 2x2 binned and subsampled down to VGA. */
+ {
+ .format = {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .field = V4L2_FIELD_NONE,
+ .width = 640,
+ .height = 480
+ },
+ .crop = {
+ .left = 16 + OV5647_PIXEL_ARRAY_LEFT,
+ .top = OV5647_PIXEL_ARRAY_TOP,
+ .width = 2560,
+ .height = 1920,
+ },
+ .pixel_rate = 55000000,
+ .hts = 1852,
+ .vts = 0x1f8,
+ .reg_list = ov5647_640x480_10bpp,
+ .num_regs = ARRAY_SIZE(ov5647_640x480_10bpp)
+ },
+};
+
+/* Default sensor mode is 2x2 binned 640x480 SBGGR10_1X10. */
+#define OV5647_DEFAULT_MODE (&ov5647_modes[3])
+#define OV5647_DEFAULT_FORMAT (ov5647_modes[3].format)
+
+static int ov5647_write16(struct v4l2_subdev *sd, u16 reg, u16 val)
{
+ unsigned char data[4] = { reg >> 8, reg & 0xff, val >> 8, val & 0xff};
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
int ret;
+
+ ret = i2c_master_send(client, data, 4);
+ if (ret < 0) {
+ dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n",
+ __func__, reg);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov5647_write(struct v4l2_subdev *sd, u16 reg, u8 val)
+{
unsigned char data[3] = { reg >> 8, reg & 0xff, val};
struct i2c_client *client = v4l2_get_subdevdata(sd);
+ int ret;
ret = i2c_master_send(client, data, 3);
- if (ret < 0)
+ if (ret < 0) {
dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n",
__func__, reg);
+ return ret;
+ }
- return ret;
+ return 0;
}
static int ov5647_read(struct v4l2_subdev *sd, u16 reg, u8 *val)
{
- int ret;
unsigned char data_w[2] = { reg >> 8, reg & 0xff };
struct i2c_client *client = v4l2_get_subdevdata(sd);
+ int ret;
ret = i2c_master_send(client, data_w, 2);
if (ret < 0) {
@@ -224,15 +626,17 @@ static int ov5647_read(struct v4l2_subdev *sd, u16 reg, u8 *val)
}
ret = i2c_master_recv(client, val, 1);
- if (ret < 0)
+ if (ret < 0) {
dev_dbg(&client->dev, "%s: i2c read error, reg: %x\n",
__func__, reg);
+ return ret;
+ }
- return ret;
+ return 0;
}
static int ov5647_write_array(struct v4l2_subdev *sd,
- struct regval_list *regs, int array_size)
+ const struct regval_list *regs, int array_size)
{
int i, ret;
@@ -255,161 +659,174 @@ static int ov5647_set_virtual_channel(struct v4l2_subdev *sd, int channel)
return ret;
channel_id &= ~(3 << 6);
- return ov5647_write(sd, OV5647_REG_MIPI_CTRL14, channel_id | (channel << 6));
+
+ return ov5647_write(sd, OV5647_REG_MIPI_CTRL14,
+ channel_id | (channel << 6));
}
-static int ov5647_stream_on(struct v4l2_subdev *sd)
+static int ov5647_set_mode(struct v4l2_subdev *sd)
{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5647 *sensor = to_sensor(sd);
+ u8 resetval, rdval;
int ret;
- ret = ov5647_write(sd, OV5647_REG_MIPI_CTRL00, MIPI_CTRL00_BUS_IDLE);
+ ret = ov5647_read(sd, OV5647_SW_STANDBY, &rdval);
+ if (ret < 0)
+ return ret;
+
+ ret = ov5647_write_array(sd, sensor->mode->reg_list,
+ sensor->mode->num_regs);
+ if (ret < 0) {
+ dev_err(&client->dev, "write sensor default regs error\n");
+ return ret;
+ }
+
+ ret = ov5647_set_virtual_channel(sd, 0);
if (ret < 0)
return ret;
- ret = ov5647_write(sd, OV5647_REG_FRAME_OFF_NUMBER, 0x00);
+ ret = ov5647_read(sd, OV5647_SW_STANDBY, &resetval);
if (ret < 0)
return ret;
- return ov5647_write(sd, OV5640_REG_PAD_OUT, 0x00);
+ if (!(resetval & 0x01)) {
+ dev_err(&client->dev, "Device was in SW standby");
+ ret = ov5647_write(sd, OV5647_SW_STANDBY, 0x01);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
}
-static int ov5647_stream_off(struct v4l2_subdev *sd)
+static int ov5647_stream_on(struct v4l2_subdev *sd)
{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5647 *sensor = to_sensor(sd);
+ u8 val = MIPI_CTRL00_BUS_IDLE;
int ret;
- ret = ov5647_write(sd, OV5647_REG_MIPI_CTRL00, MIPI_CTRL00_CLOCK_LANE_GATE
- | MIPI_CTRL00_BUS_IDLE | MIPI_CTRL00_CLOCK_LANE_DISABLE);
- if (ret < 0)
+ ret = ov5647_set_mode(sd);
+ if (ret) {
+ dev_err(&client->dev, "Failed to program sensor mode: %d\n", ret);
return ret;
+ }
- ret = ov5647_write(sd, OV5647_REG_FRAME_OFF_NUMBER, 0x0f);
- if (ret < 0)
+ /* Apply customized values from user when stream starts. */
+ ret = __v4l2_ctrl_handler_setup(sd->ctrl_handler);
+ if (ret)
return ret;
- return ov5647_write(sd, OV5640_REG_PAD_OUT, 0x01);
-}
-
-static int set_sw_standby(struct v4l2_subdev *sd, bool standby)
-{
- int ret;
- u8 rdval;
+ if (sensor->clock_ncont)
+ val |= MIPI_CTRL00_CLOCK_LANE_GATE |
+ MIPI_CTRL00_LINE_SYNC_ENABLE;
- ret = ov5647_read(sd, OV5647_SW_STANDBY, &rdval);
+ ret = ov5647_write(sd, OV5647_REG_MIPI_CTRL00, val);
if (ret < 0)
return ret;
- if (standby)
- rdval &= ~0x01;
- else
- rdval |= 0x01;
+ ret = ov5647_write(sd, OV5647_REG_FRAME_OFF_NUMBER, 0x00);
+ if (ret < 0)
+ return ret;
- return ov5647_write(sd, OV5647_SW_STANDBY, rdval);
+ return ov5647_write(sd, OV5640_REG_PAD_OUT, 0x00);
}
-static int __sensor_init(struct v4l2_subdev *sd)
+static int ov5647_stream_off(struct v4l2_subdev *sd)
{
int ret;
- u8 resetval, rdval;
- struct i2c_client *client = v4l2_get_subdevdata(sd);
- ret = ov5647_read(sd, OV5647_SW_STANDBY, &rdval);
+ ret = ov5647_write(sd, OV5647_REG_MIPI_CTRL00,
+ MIPI_CTRL00_CLOCK_LANE_GATE | MIPI_CTRL00_BUS_IDLE |
+ MIPI_CTRL00_CLOCK_LANE_DISABLE);
if (ret < 0)
return ret;
- ret = ov5647_write_array(sd, ov5647_640x480,
- ARRAY_SIZE(ov5647_640x480));
- if (ret < 0) {
- dev_err(&client->dev, "write sensor default regs error\n");
- return ret;
- }
-
- ret = ov5647_set_virtual_channel(sd, 0);
- if (ret < 0)
- return ret;
-
- ret = ov5647_read(sd, OV5647_SW_STANDBY, &resetval);
+ ret = ov5647_write(sd, OV5647_REG_FRAME_OFF_NUMBER, 0x0f);
if (ret < 0)
return ret;
- if (!(resetval & 0x01)) {
- dev_err(&client->dev, "Device was in SW standby");
- ret = ov5647_write(sd, OV5647_SW_STANDBY, 0x01);
- if (ret < 0)
- return ret;
- }
-
- /*
- * stream off to make the clock lane into LP-11 state.
- */
- return ov5647_stream_off(sd);
+ return ov5647_write(sd, OV5640_REG_PAD_OUT, 0x01);
}
-static int ov5647_sensor_power(struct v4l2_subdev *sd, int on)
+static int ov5647_power_on(struct device *dev)
{
- int ret = 0;
- struct ov5647 *ov5647 = to_state(sd);
- struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5647 *sensor = dev_get_drvdata(dev);
+ int ret;
- mutex_lock(&ov5647->lock);
+ dev_dbg(dev, "OV5647 power on\n");
- if (on && !ov5647->power_count) {
- dev_dbg(&client->dev, "OV5647 power on\n");
+ if (sensor->pwdn) {
+ gpiod_set_value_cansleep(sensor->pwdn, 0);
+ msleep(PWDN_ACTIVE_DELAY_MS);
+ }
- ret = clk_prepare_enable(ov5647->xclk);
- if (ret < 0) {
- dev_err(&client->dev, "clk prepare enable failed\n");
- goto out;
- }
+ ret = clk_prepare_enable(sensor->xclk);
+ if (ret < 0) {
+ dev_err(dev, "clk prepare enable failed\n");
+ goto error_pwdn;
+ }
- ret = ov5647_write_array(sd, sensor_oe_enable_regs,
- ARRAY_SIZE(sensor_oe_enable_regs));
- if (ret < 0) {
- clk_disable_unprepare(ov5647->xclk);
- dev_err(&client->dev,
- "write sensor_oe_enable_regs error\n");
- goto out;
- }
+ ret = ov5647_write_array(&sensor->sd, sensor_oe_enable_regs,
+ ARRAY_SIZE(sensor_oe_enable_regs));
+ if (ret < 0) {
+ dev_err(dev, "write sensor_oe_enable_regs error\n");
+ goto error_clk_disable;
+ }
- ret = __sensor_init(sd);
- if (ret < 0) {
- clk_disable_unprepare(ov5647->xclk);
- dev_err(&client->dev,
- "Camera not available, check Power\n");
- goto out;
- }
- } else if (!on && ov5647->power_count == 1) {
- dev_dbg(&client->dev, "OV5647 power off\n");
+ /* Stream off to coax lanes into LP-11 state. */
+ ret = ov5647_stream_off(&sensor->sd);
+ if (ret < 0) {
+ dev_err(dev, "camera not available, check power\n");
+ goto error_clk_disable;
+ }
- ret = ov5647_write_array(sd, sensor_oe_disable_regs,
- ARRAY_SIZE(sensor_oe_disable_regs));
+ return 0;
- if (ret < 0)
- dev_dbg(&client->dev, "disable oe failed\n");
+error_clk_disable:
+ clk_disable_unprepare(sensor->xclk);
+error_pwdn:
+ gpiod_set_value_cansleep(sensor->pwdn, 1);
- ret = set_sw_standby(sd, true);
+ return ret;
+}
- if (ret < 0)
- dev_dbg(&client->dev, "soft stby failed\n");
+static int ov5647_power_off(struct device *dev)
+{
+ struct ov5647 *sensor = dev_get_drvdata(dev);
+ u8 rdval;
+ int ret;
- clk_disable_unprepare(ov5647->xclk);
- }
+ dev_dbg(dev, "OV5647 power off\n");
- /* Update the power count. */
- ov5647->power_count += on ? 1 : -1;
- WARN_ON(ov5647->power_count < 0);
+ ret = ov5647_write_array(&sensor->sd, sensor_oe_disable_regs,
+ ARRAY_SIZE(sensor_oe_disable_regs));
+ if (ret < 0)
+ dev_dbg(dev, "disable oe failed\n");
-out:
- mutex_unlock(&ov5647->lock);
+ /* Enter software standby */
+ ret = ov5647_read(&sensor->sd, OV5647_SW_STANDBY, &rdval);
+ if (ret < 0)
+ dev_dbg(dev, "software standby failed\n");
- return ret;
+ rdval &= ~0x01;
+ ret = ov5647_write(&sensor->sd, OV5647_SW_STANDBY, rdval);
+ if (ret < 0)
+ dev_dbg(dev, "software standby failed\n");
+
+ clk_disable_unprepare(sensor->xclk);
+ gpiod_set_value_cansleep(sensor->pwdn, 1);
+
+ return 0;
}
#ifdef CONFIG_VIDEO_ADV_DEBUG
static int ov5647_sensor_get_register(struct v4l2_subdev *sd,
- struct v4l2_dbg_register *reg)
+ struct v4l2_dbg_register *reg)
{
- u8 val;
int ret;
+ u8 val;
ret = ov5647_read(sd, reg->reg & 0xff, &val);
if (ret < 0)
@@ -422,29 +839,77 @@ static int ov5647_sensor_get_register(struct v4l2_subdev *sd,
}
static int ov5647_sensor_set_register(struct v4l2_subdev *sd,
- const struct v4l2_dbg_register *reg)
+ const struct v4l2_dbg_register *reg)
{
return ov5647_write(sd, reg->reg & 0xff, reg->val & 0xff);
}
#endif
-/*
- * Subdev core operations registration
- */
+/* Subdev core operations registration */
static const struct v4l2_subdev_core_ops ov5647_subdev_core_ops = {
- .s_power = ov5647_sensor_power,
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
#ifdef CONFIG_VIDEO_ADV_DEBUG
.g_register = ov5647_sensor_get_register,
.s_register = ov5647_sensor_set_register,
#endif
};
+static const struct v4l2_rect *
+__ov5647_get_pad_crop(struct ov5647 *ov5647, struct v4l2_subdev_pad_config *cfg,
+ unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_crop(&ov5647->sd, cfg, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &ov5647->mode->crop;
+ }
+
+ return NULL;
+}
+
static int ov5647_s_stream(struct v4l2_subdev *sd, int enable)
{
- if (enable)
- return ov5647_stream_on(sd);
- else
- return ov5647_stream_off(sd);
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5647 *sensor = to_sensor(sd);
+ int ret;
+
+ mutex_lock(&sensor->lock);
+ if (sensor->streaming == enable) {
+ mutex_unlock(&sensor->lock);
+ return 0;
+ }
+
+ if (enable) {
+ ret = pm_runtime_get_sync(&client->dev);
+ if (ret < 0)
+ goto error_unlock;
+
+ ret = ov5647_stream_on(sd);
+ if (ret < 0) {
+ dev_err(&client->dev, "stream start failed: %d\n", ret);
+ goto error_unlock;
+ }
+ } else {
+ ret = ov5647_stream_off(sd);
+ if (ret < 0) {
+ dev_err(&client->dev, "stream stop failed: %d\n", ret);
+ goto error_unlock;
+ }
+ pm_runtime_put(&client->dev);
+ }
+
+ sensor->streaming = enable;
+ mutex_unlock(&sensor->lock);
+
+ return 0;
+
+error_unlock:
+ pm_runtime_put(&client->dev);
+ mutex_unlock(&sensor->lock);
+
+ return ret;
}
static const struct v4l2_subdev_video_ops ov5647_subdev_video_ops = {
@@ -452,19 +917,150 @@ static const struct v4l2_subdev_video_ops ov5647_subdev_video_ops = {
};
static int ov5647_enum_mbus_code(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_mbus_code_enum *code)
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
{
if (code->index > 0)
return -EINVAL;
- code->code = MEDIA_BUS_FMT_SBGGR8_1X8;
+ code->code = MEDIA_BUS_FMT_SBGGR10_1X10;
+
+ return 0;
+}
+
+static int ov5647_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ const struct v4l2_mbus_framefmt *fmt;
+
+ if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10 ||
+ fse->index >= ARRAY_SIZE(ov5647_modes))
+ return -EINVAL;
+
+ fmt = &ov5647_modes[fse->index].format;
+ fse->min_width = fmt->width;
+ fse->max_width = fmt->width;
+ fse->min_height = fmt->height;
+ fse->max_height = fmt->height;
+
+ return 0;
+}
+
+static int ov5647_get_pad_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct v4l2_mbus_framefmt *fmt = &format->format;
+ const struct v4l2_mbus_framefmt *sensor_format;
+ struct ov5647 *sensor = to_sensor(sd);
+
+ mutex_lock(&sensor->lock);
+ switch (format->which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ sensor_format = v4l2_subdev_get_try_format(sd, cfg, format->pad);
+ break;
+ default:
+ sensor_format = &sensor->mode->format;
+ break;
+ }
+
+ *fmt = *sensor_format;
+ mutex_unlock(&sensor->lock);
return 0;
}
+static int ov5647_set_pad_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct v4l2_mbus_framefmt *fmt = &format->format;
+ struct ov5647 *sensor = to_sensor(sd);
+ const struct ov5647_mode *mode;
+
+ mode = v4l2_find_nearest_size(ov5647_modes, ARRAY_SIZE(ov5647_modes),
+ format.width, format.height,
+ fmt->width, fmt->height);
+
+ /* Update the sensor mode and apply at it at streamon time. */
+ mutex_lock(&sensor->lock);
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *v4l2_subdev_get_try_format(sd, cfg, format->pad) = mode->format;
+ } else {
+ int exposure_max, exposure_def;
+ int hblank, vblank;
+
+ sensor->mode = mode;
+ __v4l2_ctrl_modify_range(sensor->pixel_rate, mode->pixel_rate,
+ mode->pixel_rate, 1, mode->pixel_rate);
+
+ hblank = mode->hts - mode->format.width;
+ __v4l2_ctrl_modify_range(sensor->hblank, hblank, hblank, 1,
+ hblank);
+
+ vblank = mode->vts - mode->format.height;
+ __v4l2_ctrl_modify_range(sensor->vblank, OV5647_VBLANK_MIN,
+ OV5647_VTS_MAX - mode->format.height,
+ 1, vblank);
+ __v4l2_ctrl_s_ctrl(sensor->vblank, vblank);
+
+ exposure_max = mode->vts - 4;
+ exposure_def = min(exposure_max, OV5647_EXPOSURE_DEFAULT);
+ __v4l2_ctrl_modify_range(sensor->exposure,
+ sensor->exposure->minimum,
+ exposure_max, sensor->exposure->step,
+ exposure_def);
+ }
+ *fmt = mode->format;
+ mutex_unlock(&sensor->lock);
+
+ return 0;
+}
+
+static int ov5647_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_selection *sel)
+{
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP: {
+ struct ov5647 *sensor = to_sensor(sd);
+
+ mutex_lock(&sensor->lock);
+ sel->r = *__ov5647_get_pad_crop(sensor, cfg, sel->pad,
+ sel->which);
+ mutex_unlock(&sensor->lock);
+
+ return 0;
+ }
+
+ case V4L2_SEL_TGT_NATIVE_SIZE:
+ sel->r.top = 0;
+ sel->r.left = 0;
+ sel->r.width = OV5647_NATIVE_WIDTH;
+ sel->r.height = OV5647_NATIVE_HEIGHT;
+
+ return 0;
+
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ sel->r.top = OV5647_PIXEL_ARRAY_TOP;
+ sel->r.left = OV5647_PIXEL_ARRAY_LEFT;
+ sel->r.width = OV5647_PIXEL_ARRAY_WIDTH;
+ sel->r.height = OV5647_PIXEL_ARRAY_HEIGHT;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
static const struct v4l2_subdev_pad_ops ov5647_subdev_pad_ops = {
- .enum_mbus_code = ov5647_enum_mbus_code,
+ .enum_mbus_code = ov5647_enum_mbus_code,
+ .enum_frame_size = ov5647_enum_frame_size,
+ .set_fmt = ov5647_set_pad_fmt,
+ .get_fmt = ov5647_get_pad_fmt,
+ .get_selection = ov5647_get_selection,
};
static const struct v4l2_subdev_ops ov5647_subdev_ops = {
@@ -475,9 +1071,9 @@ static const struct v4l2_subdev_ops ov5647_subdev_ops = {
static int ov5647_detect(struct v4l2_subdev *sd)
{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
u8 read;
int ret;
- struct i2c_client *client = v4l2_get_subdevdata(sd);
ret = ov5647_write(sd, OV5647_SW_RESET, 0x01);
if (ret < 0)
@@ -508,20 +1104,14 @@ static int ov5647_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
struct v4l2_mbus_framefmt *format =
v4l2_subdev_get_try_format(sd, fh->pad, 0);
- struct v4l2_rect *crop =
- v4l2_subdev_get_try_crop(sd, fh->pad, 0);
+ struct v4l2_rect *crop = v4l2_subdev_get_try_crop(sd, fh->pad, 0);
- crop->left = OV5647_COLUMN_START_DEF;
- crop->top = OV5647_ROW_START_DEF;
- crop->width = OV5647_WINDOW_WIDTH_DEF;
- crop->height = OV5647_WINDOW_HEIGHT_DEF;
+ crop->left = OV5647_PIXEL_ARRAY_LEFT;
+ crop->top = OV5647_PIXEL_ARRAY_TOP;
+ crop->width = OV5647_PIXEL_ARRAY_WIDTH;
+ crop->height = OV5647_PIXEL_ARRAY_HEIGHT;
- format->code = MEDIA_BUS_FMT_SBGGR8_1X8;
-
- format->width = OV5647_WINDOW_WIDTH_DEF;
- format->height = OV5647_WINDOW_HEIGHT_DEF;
- format->field = V4L2_FIELD_NONE;
- format->colorspace = V4L2_COLORSPACE_SRGB;
+ *format = OV5647_DEFAULT_FORMAT;
return 0;
}
@@ -530,11 +1120,220 @@ static const struct v4l2_subdev_internal_ops ov5647_subdev_internal_ops = {
.open = ov5647_open,
};
-static int ov5647_parse_dt(struct device_node *np)
+static int ov5647_s_auto_white_balance(struct v4l2_subdev *sd, u32 val)
{
- struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 };
- struct device_node *ep;
+ return ov5647_write(sd, OV5647_REG_AWB, val ? 1 : 0);
+}
+static int ov5647_s_autogain(struct v4l2_subdev *sd, u32 val)
+{
+ int ret;
+ u8 reg;
+
+ /* Non-zero turns on AGC by clearing bit 1.*/
+ ret = ov5647_read(sd, OV5647_REG_AEC_AGC, &reg);
+ if (ret)
+ return ret;
+
+ return ov5647_write(sd, OV5647_REG_AEC_AGC, val ? reg & ~BIT(1)
+ : reg | BIT(1));
+}
+
+static int ov5647_s_exposure_auto(struct v4l2_subdev *sd, u32 val)
+{
+ int ret;
+ u8 reg;
+
+ /*
+ * Everything except V4L2_EXPOSURE_MANUAL turns on AEC by
+ * clearing bit 0.
+ */
+ ret = ov5647_read(sd, OV5647_REG_AEC_AGC, &reg);
+ if (ret)
+ return ret;
+
+ return ov5647_write(sd, OV5647_REG_AEC_AGC,
+ val == V4L2_EXPOSURE_MANUAL ? reg | BIT(0)
+ : reg & ~BIT(0));
+}
+
+static int ov5647_s_analogue_gain(struct v4l2_subdev *sd, u32 val)
+{
+ int ret;
+
+ /* 10 bits of gain, 2 in the high register. */
+ ret = ov5647_write(sd, OV5647_REG_GAIN_HI, (val >> 8) & 3);
+ if (ret)
+ return ret;
+
+ return ov5647_write(sd, OV5647_REG_GAIN_LO, val & 0xff);
+}
+
+static int ov5647_s_exposure(struct v4l2_subdev *sd, u32 val)
+{
+ int ret;
+
+ /*
+ * Sensor has 20 bits, but the bottom 4 bits are fractions of a line
+ * which we leave as zero (and don't receive in "val").
+ */
+ ret = ov5647_write(sd, OV5647_REG_EXP_HI, (val >> 12) & 0xf);
+ if (ret)
+ return ret;
+
+ ret = ov5647_write(sd, OV5647_REG_EXP_MID, (val >> 4) & 0xff);
+ if (ret)
+ return ret;
+
+ return ov5647_write(sd, OV5647_REG_EXP_LO, (val & 0xf) << 4);
+}
+
+static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct ov5647 *sensor = container_of(ctrl->handler,
+ struct ov5647, ctrls);
+ struct v4l2_subdev *sd = &sensor->sd;
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ int ret = 0;
+
+
+ /* v4l2_ctrl_lock() locks our own mutex */
+
+ if (ctrl->id == V4L2_CID_VBLANK) {
+ int exposure_max, exposure_def;
+
+ /* Update max exposure while meeting expected vblanking */
+ exposure_max = sensor->mode->format.height + ctrl->val - 4;
+ exposure_def = min(exposure_max, OV5647_EXPOSURE_DEFAULT);
+ __v4l2_ctrl_modify_range(sensor->exposure,
+ sensor->exposure->minimum,
+ exposure_max, sensor->exposure->step,
+ exposure_def);
+ }
+
+ /*
+ * If the device is not powered up do not apply any controls
+ * to H/W at this time. Instead the controls will be restored
+ * at s_stream(1) time.
+ */
+ if (pm_runtime_get_if_in_use(&client->dev) == 0)
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUTO_WHITE_BALANCE:
+ ret = ov5647_s_auto_white_balance(sd, ctrl->val);
+ break;
+ case V4L2_CID_AUTOGAIN:
+ ret = ov5647_s_autogain(sd, ctrl->val);
+ break;
+ case V4L2_CID_EXPOSURE_AUTO:
+ ret = ov5647_s_exposure_auto(sd, ctrl->val);
+ break;
+ case V4L2_CID_ANALOGUE_GAIN:
+ ret = ov5647_s_analogue_gain(sd, ctrl->val);
+ break;
+ case V4L2_CID_EXPOSURE:
+ ret = ov5647_s_exposure(sd, ctrl->val);
+ break;
+ case V4L2_CID_VBLANK:
+ ret = ov5647_write16(sd, OV5647_REG_VTS_HI,
+ sensor->mode->format.height + ctrl->val);
+ break;
+
+ /* Read-only, but we adjust it based on mode. */
+ case V4L2_CID_PIXEL_RATE:
+ case V4L2_CID_HBLANK:
+ /* Read-only, but we adjust it based on mode. */
+ break;
+
+ default:
+ dev_info(&client->dev,
+ "Control (id:0x%x, val:0x%x) not supported\n",
+ ctrl->id, ctrl->val);
+ return -EINVAL;
+ }
+
+ pm_runtime_put(&client->dev);
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops ov5647_ctrl_ops = {
+ .s_ctrl = ov5647_s_ctrl,
+};
+
+static int ov5647_init_controls(struct ov5647 *sensor)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
+ int hblank, exposure_max, exposure_def;
+
+ v4l2_ctrl_handler_init(&sensor->ctrls, 8);
+
+ v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+ V4L2_CID_AUTOGAIN, 0, 1, 1, 0);
+
+ v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+ V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 0);
+
+ v4l2_ctrl_new_std_menu(&sensor->ctrls, &ov5647_ctrl_ops,
+ V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL,
+ 0, V4L2_EXPOSURE_MANUAL);
+
+ exposure_max = sensor->mode->vts - 4;
+ exposure_def = min(exposure_max, OV5647_EXPOSURE_DEFAULT);
+ sensor->exposure = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+ V4L2_CID_EXPOSURE,
+ OV5647_EXPOSURE_MIN,
+ exposure_max, OV5647_EXPOSURE_STEP,
+ exposure_def);
+
+ /* min: 16 = 1.0x; max (10 bits); default: 32 = 2.0x. */
+ v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 32);
+
+ /* By default, PIXEL_RATE is read only, but it does change per mode */
+ sensor->pixel_rate = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+ V4L2_CID_PIXEL_RATE,
+ sensor->mode->pixel_rate,
+ sensor->mode->pixel_rate, 1,
+ sensor->mode->pixel_rate);
+
+ /* By default, HBLANK is read only, but it does change per mode. */
+ hblank = sensor->mode->hts - sensor->mode->format.width;
+ sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+ V4L2_CID_HBLANK, hblank, hblank, 1,
+ hblank);
+
+ sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+ V4L2_CID_VBLANK, OV5647_VBLANK_MIN,
+ OV5647_VTS_MAX -
+ sensor->mode->format.height, 1,
+ sensor->mode->vts -
+ sensor->mode->format.height);
+
+ if (sensor->ctrls.error)
+ goto handler_free;
+
+ sensor->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+ sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+ sensor->sd.ctrl_handler = &sensor->ctrls;
+
+ return 0;
+
+handler_free:
+ dev_err(&client->dev, "%s Controls initialization failed (%d)\n",
+ __func__, sensor->ctrls.error);
+ v4l2_ctrl_handler_free(&sensor->ctrls);
+
+ return sensor->ctrls.error;
+}
+
+static int ov5647_parse_dt(struct ov5647 *sensor, struct device_node *np)
+{
+ struct v4l2_fwnode_endpoint bus_cfg = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY,
+ };
+ struct device_node *ep;
int ret;
ep = of_graph_get_next_endpoint(np, NULL);
@@ -542,33 +1341,39 @@ static int ov5647_parse_dt(struct device_node *np)
return -EINVAL;
ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &bus_cfg);
+ if (ret)
+ goto out;
+ sensor->clock_ncont = bus_cfg.bus.mipi_csi2.flags &
+ V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+
+out:
of_node_put(ep);
+
return ret;
}
static int ov5647_probe(struct i2c_client *client)
{
+ struct device_node *np = client->dev.of_node;
struct device *dev = &client->dev;
struct ov5647 *sensor;
- int ret;
struct v4l2_subdev *sd;
- struct device_node *np = client->dev.of_node;
u32 xclk_freq;
+ int ret;
sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
if (IS_ENABLED(CONFIG_OF) && np) {
- ret = ov5647_parse_dt(np);
+ ret = ov5647_parse_dt(sensor, np);
if (ret) {
dev_err(dev, "DT parsing error: %d\n", ret);
return ret;
}
}
- /* get system clock (xclk) */
sensor->xclk = devm_clk_get(dev, NULL);
if (IS_ERR(sensor->xclk)) {
dev_err(dev, "could not get xclk");
@@ -581,52 +1386,87 @@ static int ov5647_probe(struct i2c_client *client)
return -EINVAL;
}
+ /* Request the power down GPIO asserted. */
+ sensor->pwdn = devm_gpiod_get_optional(dev, "pwdn", GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->pwdn)) {
+ dev_err(dev, "Failed to get 'pwdn' gpio\n");
+ return -EINVAL;
+ }
+
mutex_init(&sensor->lock);
+ sensor->mode = OV5647_DEFAULT_MODE;
+
+ ret = ov5647_init_controls(sensor);
+ if (ret)
+ goto mutex_destroy;
+
sd = &sensor->sd;
v4l2_i2c_subdev_init(sd, client, &ov5647_subdev_ops);
- sensor->sd.internal_ops = &ov5647_subdev_internal_ops;
- sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ sd->internal_ops = &ov5647_subdev_internal_ops;
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad);
if (ret < 0)
- goto mutex_remove;
+ goto ctrl_handler_free;
+
+ ret = ov5647_power_on(dev);
+ if (ret)
+ goto entity_cleanup;
ret = ov5647_detect(sd);
if (ret < 0)
- goto error;
+ goto power_off;
ret = v4l2_async_register_subdev(sd);
if (ret < 0)
- goto error;
+ goto power_off;
+
+ /* Enable runtime PM and turn off the device */
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_idle(dev);
dev_dbg(dev, "OmniVision OV5647 camera driver probed\n");
+
return 0;
-error:
+
+power_off:
+ ov5647_power_off(dev);
+entity_cleanup:
media_entity_cleanup(&sd->entity);
-mutex_remove:
+ctrl_handler_free:
+ v4l2_ctrl_handler_free(&sensor->ctrls);
+mutex_destroy:
mutex_destroy(&sensor->lock);
+
return ret;
}
static int ov5647_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
- struct ov5647 *ov5647 = to_state(sd);
+ struct ov5647 *sensor = to_sensor(sd);
- v4l2_async_unregister_subdev(&ov5647->sd);
- media_entity_cleanup(&ov5647->sd.entity);
+ v4l2_async_unregister_subdev(&sensor->sd);
+ media_entity_cleanup(&sensor->sd.entity);
+ v4l2_ctrl_handler_free(&sensor->ctrls);
v4l2_device_unregister_subdev(sd);
- mutex_destroy(&ov5647->lock);
+ pm_runtime_disable(&client->dev);
+ mutex_destroy(&sensor->lock);
return 0;
}
+static const struct dev_pm_ops ov5647_pm_ops = {
+ SET_RUNTIME_PM_OPS(ov5647_power_off, ov5647_power_on, NULL)
+};
+
static const struct i2c_device_id ov5647_id[] = {
{ "ov5647", 0 },
- { }
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, ov5647_id);
@@ -641,7 +1481,8 @@ MODULE_DEVICE_TABLE(of, ov5647_of_match);
static struct i2c_driver ov5647_driver = {
.driver = {
.of_match_table = of_match_ptr(ov5647_of_match),
- .name = SENSOR_NAME,
+ .name = "ov5647",
+ .pm = &ov5647_pm_ops,
},
.probe_new = ov5647_probe,
.remove = ov5647_remove,
diff --git a/drivers/media/i2c/ov5648.c b/drivers/media/i2c/ov5648.c
new file mode 100644
index 000000000000..dfe38ab8224d
--- /dev/null
+++ b/drivers/media/i2c/ov5648.c
@@ -0,0 +1,2624 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-image-sizes.h>
+#include <media/v4l2-mediabus.h>
+
+/* Clock rate */
+
+#define OV5648_XVCLK_RATE 24000000
+
+/* Register definitions */
+
+/* System */
+
+#define OV5648_SW_STANDBY_REG 0x100
+#define OV5648_SW_STANDBY_STREAM_ON BIT(0)
+
+#define OV5648_SW_RESET_REG 0x103
+#define OV5648_SW_RESET_RESET BIT(0)
+
+#define OV5648_PAD_OEN0_REG 0x3000
+#define OV5648_PAD_OEN1_REG 0x3001
+#define OV5648_PAD_OEN2_REG 0x3002
+#define OV5648_PAD_OUT0_REG 0x3008
+#define OV5648_PAD_OUT1_REG 0x3009
+
+#define OV5648_CHIP_ID_H_REG 0x300a
+#define OV5648_CHIP_ID_H_VALUE 0x56
+#define OV5648_CHIP_ID_L_REG 0x300b
+#define OV5648_CHIP_ID_L_VALUE 0x48
+
+#define OV5648_PAD_OUT2_REG 0x300d
+#define OV5648_PAD_SEL0_REG 0x300e
+#define OV5648_PAD_SEL1_REG 0x300f
+#define OV5648_PAD_SEL2_REG 0x3010
+#define OV5648_PAD_PK_REG 0x3011
+#define OV5648_PAD_PK_PD_DATO_EN BIT(7)
+#define OV5648_PAD_PK_DRIVE_STRENGTH_1X (0 << 5)
+#define OV5648_PAD_PK_DRIVE_STRENGTH_2X (2 << 5)
+#define OV5648_PAD_PK_FREX_N BIT(1)
+
+#define OV5648_A_PWC_PK_O0_REG 0x3013
+#define OV5648_A_PWC_PK_O0_BP_REGULATOR_N BIT(3)
+#define OV5648_A_PWC_PK_O1_REG 0x3014
+
+#define OV5648_MIPI_PHY0_REG 0x3016
+#define OV5648_MIPI_PHY1_REG 0x3017
+#define OV5648_MIPI_SC_CTRL0_REG 0x3018
+#define OV5648_MIPI_SC_CTRL0_MIPI_LANES(v) (((v) << 5) & GENMASK(7, 5))
+#define OV5648_MIPI_SC_CTRL0_PHY_HS_TX_PD BIT(4)
+#define OV5648_MIPI_SC_CTRL0_PHY_LP_RX_PD BIT(3)
+#define OV5648_MIPI_SC_CTRL0_MIPI_EN BIT(2)
+#define OV5648_MIPI_SC_CTRL0_MIPI_SUSP BIT(1)
+#define OV5648_MIPI_SC_CTRL0_LANE_DIS_OP BIT(0)
+#define OV5648_MIPI_SC_CTRL1_REG 0x3019
+#define OV5648_MISC_CTRL0_REG 0x3021
+#define OV5648_MIPI_SC_CTRL2_REG 0x3022
+#define OV5648_SUB_ID_REG 0x302a
+
+#define OV5648_PLL_CTRL0_REG 0x3034
+#define OV5648_PLL_CTRL0_PLL_CHARGE_PUMP(v) (((v) << 4) & GENMASK(6, 4))
+#define OV5648_PLL_CTRL0_BITS(v) ((v) & GENMASK(3, 0))
+#define OV5648_PLL_CTRL1_REG 0x3035
+#define OV5648_PLL_CTRL1_SYS_DIV(v) (((v) << 4) & GENMASK(7, 4))
+#define OV5648_PLL_CTRL1_MIPI_DIV(v) ((v) & GENMASK(3, 0))
+#define OV5648_PLL_MUL_REG 0x3036
+#define OV5648_PLL_MUL(v) ((v) & GENMASK(7, 0))
+#define OV5648_PLL_DIV_REG 0x3037
+#define OV5648_PLL_DIV_ROOT_DIV(v) ((((v) - 1) << 4) & BIT(4))
+#define OV5648_PLL_DIV_PLL_PRE_DIV(v) ((v) & GENMASK(3, 0))
+#define OV5648_PLL_DEBUG_REG 0x3038
+#define OV5648_PLL_BYPASS_REG 0x3039
+
+#define OV5648_PLLS_BYPASS_REG 0x303a
+#define OV5648_PLLS_MUL_REG 0x303b
+#define OV5648_PLLS_MUL(v) ((v) & GENMASK(4, 0))
+#define OV5648_PLLS_CTRL_REG 0x303c
+#define OV5648_PLLS_CTRL_PLL_CHARGE_PUMP(v) (((v) << 4) & GENMASK(6, 4))
+#define OV5648_PLLS_CTRL_SYS_DIV(v) ((v) & GENMASK(3, 0))
+#define OV5648_PLLS_DIV_REG 0x303d
+#define OV5648_PLLS_DIV_PLLS_PRE_DIV(v) (((v) << 4) & GENMASK(5, 4))
+#define OV5648_PLLS_DIV_PLLS_DIV_R(v) ((((v) - 1) << 2) & BIT(2))
+#define OV5648_PLLS_DIV_PLLS_SEL_DIV(v) ((v) & GENMASK(1, 0))
+
+#define OV5648_SRB_CTRL_REG 0x3106
+#define OV5648_SRB_CTRL_SCLK_DIV(v) (((v) << 2) & GENMASK(3, 2))
+#define OV5648_SRB_CTRL_RESET_ARBITER_EN BIT(1)
+#define OV5648_SRB_CTRL_SCLK_ARBITER_EN BIT(0)
+
+/* Group Hold */
+
+#define OV5648_GROUP_ADR0_REG 0x3200
+#define OV5648_GROUP_ADR1_REG 0x3201
+#define OV5648_GROUP_ADR2_REG 0x3202
+#define OV5648_GROUP_ADR3_REG 0x3203
+#define OV5648_GROUP_LEN0_REG 0x3204
+#define OV5648_GROUP_LEN1_REG 0x3205
+#define OV5648_GROUP_LEN2_REG 0x3206
+#define OV5648_GROUP_LEN3_REG 0x3207
+#define OV5648_GROUP_ACCESS_REG 0x3208
+
+/* Exposure/gain/banding */
+
+#define OV5648_EXPOSURE_CTRL_HH_REG 0x3500
+#define OV5648_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(19, 16)) >> 16)
+#define OV5648_EXPOSURE_CTRL_HH_VALUE(v) (((v) << 16) & GENMASK(19, 16))
+#define OV5648_EXPOSURE_CTRL_H_REG 0x3501
+#define OV5648_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV5648_EXPOSURE_CTRL_H_VALUE(v) (((v) << 8) & GENMASK(15, 8))
+#define OV5648_EXPOSURE_CTRL_L_REG 0x3502
+#define OV5648_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_EXPOSURE_CTRL_L_VALUE(v) ((v) & GENMASK(7, 0))
+#define OV5648_MANUAL_CTRL_REG 0x3503
+#define OV5648_MANUAL_CTRL_FRAME_DELAY(v) (((v) << 4) & GENMASK(5, 4))
+#define OV5648_MANUAL_CTRL_AGC_MANUAL_EN BIT(1)
+#define OV5648_MANUAL_CTRL_AEC_MANUAL_EN BIT(0)
+#define OV5648_GAIN_CTRL_H_REG 0x350a
+#define OV5648_GAIN_CTRL_H(v) (((v) & GENMASK(9, 8)) >> 8)
+#define OV5648_GAIN_CTRL_H_VALUE(v) (((v) << 8) & GENMASK(9, 8))
+#define OV5648_GAIN_CTRL_L_REG 0x350b
+#define OV5648_GAIN_CTRL_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_GAIN_CTRL_L_VALUE(v) ((v) & GENMASK(7, 0))
+
+#define OV5648_ANALOG_CTRL0_REG_BASE 0x3600
+#define OV5648_ANALOG_CTRL1_REG_BASE 0x3700
+
+#define OV5648_AEC_CTRL0_REG 0x3a00
+#define OV5648_AEC_CTRL0_DEBUG BIT(6)
+#define OV5648_AEC_CTRL0_DEBAND_EN BIT(5)
+#define OV5648_AEC_CTRL0_DEBAND_LOW_LIMIT_EN BIT(4)
+#define OV5648_AEC_CTRL0_START_SEL_EN BIT(3)
+#define OV5648_AEC_CTRL0_NIGHT_MODE_EN BIT(2)
+#define OV5648_AEC_CTRL0_FREEZE_EN BIT(0)
+#define OV5648_EXPOSURE_MIN_REG 0x3a01
+#define OV5648_EXPOSURE_MAX_60_H_REG 0x3a02
+#define OV5648_EXPOSURE_MAX_60_L_REG 0x3a03
+#define OV5648_AEC_CTRL5_REG 0x3a05
+#define OV5648_AEC_CTRL6_REG 0x3a06
+#define OV5648_AEC_CTRL7_REG 0x3a07
+#define OV5648_BANDING_STEP_50_H_REG 0x3a08
+#define OV5648_BANDING_STEP_50_L_REG 0x3a09
+#define OV5648_BANDING_STEP_60_H_REG 0x3a0a
+#define OV5648_BANDING_STEP_60_L_REG 0x3a0b
+#define OV5648_AEC_CTRLC_REG 0x3a0c
+#define OV5648_BANDING_MAX_60_REG 0x3a0d
+#define OV5648_BANDING_MAX_50_REG 0x3a0e
+#define OV5648_WPT_REG 0x3a0f
+#define OV5648_BPT_REG 0x3a10
+#define OV5648_VPT_HIGH_REG 0x3a11
+#define OV5648_AVG_MANUAL_REG 0x3a12
+#define OV5648_PRE_GAIN_REG 0x3a13
+#define OV5648_EXPOSURE_MAX_50_H_REG 0x3a14
+#define OV5648_EXPOSURE_MAX_50_L_REG 0x3a15
+#define OV5648_GAIN_BASE_NIGHT_REG 0x3a17
+#define OV5648_AEC_GAIN_CEILING_H_REG 0x3a18
+#define OV5648_AEC_GAIN_CEILING_L_REG 0x3a19
+#define OV5648_DIFF_MAX_REG 0x3a1a
+#define OV5648_WPT2_REG 0x3a1b
+#define OV5648_LED_ADD_ROW_H_REG 0x3a1c
+#define OV5648_LED_ADD_ROW_L_REG 0x3a1d
+#define OV5648_BPT2_REG 0x3a1e
+#define OV5648_VPT_LOW_REG 0x3a1f
+#define OV5648_AEC_CTRL20_REG 0x3a20
+#define OV5648_AEC_CTRL21_REG 0x3a21
+
+#define OV5648_AVG_START_X_H_REG 0x5680
+#define OV5648_AVG_START_X_L_REG 0x5681
+#define OV5648_AVG_START_Y_H_REG 0x5682
+#define OV5648_AVG_START_Y_L_REG 0x5683
+#define OV5648_AVG_WINDOW_X_H_REG 0x5684
+#define OV5648_AVG_WINDOW_X_L_REG 0x5685
+#define OV5648_AVG_WINDOW_Y_H_REG 0x5686
+#define OV5648_AVG_WINDOW_Y_L_REG 0x5687
+#define OV5648_AVG_WEIGHT00_REG 0x5688
+#define OV5648_AVG_WEIGHT01_REG 0x5689
+#define OV5648_AVG_WEIGHT02_REG 0x568a
+#define OV5648_AVG_WEIGHT03_REG 0x568b
+#define OV5648_AVG_WEIGHT04_REG 0x568c
+#define OV5648_AVG_WEIGHT05_REG 0x568d
+#define OV5648_AVG_WEIGHT06_REG 0x568e
+#define OV5648_AVG_WEIGHT07_REG 0x568f
+#define OV5648_AVG_CTRL10_REG 0x5690
+#define OV5648_AVG_WEIGHT_SUM_REG 0x5691
+#define OV5648_AVG_READOUT_REG 0x5693
+
+#define OV5648_DIG_CTRL0_REG 0x5a00
+#define OV5648_DIG_COMP_MAN_H_REG 0x5a02
+#define OV5648_DIG_COMP_MAN_L_REG 0x5a03
+
+#define OV5648_GAINC_MAN_H_REG 0x5a20
+#define OV5648_GAINC_MAN_L_REG 0x5a21
+#define OV5648_GAINC_DGC_MAN_H_REG 0x5a22
+#define OV5648_GAINC_DGC_MAN_L_REG 0x5a23
+#define OV5648_GAINC_CTRL0_REG 0x5a24
+
+#define OV5648_GAINF_ANA_NUM_REG 0x5a40
+#define OV5648_GAINF_DIG_GAIN_REG 0x5a41
+
+/* Timing */
+
+#define OV5648_CROP_START_X_H_REG 0x3800
+#define OV5648_CROP_START_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_CROP_START_X_L_REG 0x3801
+#define OV5648_CROP_START_X_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_CROP_START_Y_H_REG 0x3802
+#define OV5648_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_CROP_START_Y_L_REG 0x3803
+#define OV5648_CROP_START_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_CROP_END_X_H_REG 0x3804
+#define OV5648_CROP_END_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_CROP_END_X_L_REG 0x3805
+#define OV5648_CROP_END_X_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_CROP_END_Y_H_REG 0x3806
+#define OV5648_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_CROP_END_Y_L_REG 0x3807
+#define OV5648_CROP_END_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_OUTPUT_SIZE_X_H_REG 0x3808
+#define OV5648_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_OUTPUT_SIZE_X_L_REG 0x3809
+#define OV5648_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_OUTPUT_SIZE_Y_H_REG 0x380a
+#define OV5648_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_OUTPUT_SIZE_Y_L_REG 0x380b
+#define OV5648_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_HTS_H_REG 0x380c
+#define OV5648_HTS_H(v) (((v) & GENMASK(12, 8)) >> 8)
+#define OV5648_HTS_L_REG 0x380d
+#define OV5648_HTS_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_VTS_H_REG 0x380e
+#define OV5648_VTS_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV5648_VTS_L_REG 0x380f
+#define OV5648_VTS_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_OFFSET_X_H_REG 0x3810
+#define OV5648_OFFSET_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_OFFSET_X_L_REG 0x3811
+#define OV5648_OFFSET_X_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_OFFSET_Y_H_REG 0x3812
+#define OV5648_OFFSET_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_OFFSET_Y_L_REG 0x3813
+#define OV5648_OFFSET_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_SUB_INC_X_REG 0x3814
+#define OV5648_SUB_INC_X_ODD(v) (((v) << 4) & GENMASK(7, 4))
+#define OV5648_SUB_INC_X_EVEN(v) ((v) & GENMASK(3, 0))
+#define OV5648_SUB_INC_Y_REG 0x3815
+#define OV5648_SUB_INC_Y_ODD(v) (((v) << 4) & GENMASK(7, 4))
+#define OV5648_SUB_INC_Y_EVEN(v) ((v) & GENMASK(3, 0))
+#define OV5648_HSYNCST_H_REG 0x3816
+#define OV5648_HSYNCST_H(v) (((v) >> 8) & 0xf)
+#define OV5648_HSYNCST_L_REG 0x3817
+#define OV5648_HSYNCST_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_HSYNCW_H_REG 0x3818
+#define OV5648_HSYNCW_H(v) (((v) >> 8) & 0xf)
+#define OV5648_HSYNCW_L_REG 0x3819
+#define OV5648_HSYNCW_L(v) ((v) & GENMASK(7, 0))
+
+#define OV5648_TC20_REG 0x3820
+#define OV5648_TC20_DEBUG BIT(6)
+#define OV5648_TC20_FLIP_VERT_ISP_EN BIT(2)
+#define OV5648_TC20_FLIP_VERT_SENSOR_EN BIT(1)
+#define OV5648_TC20_BINNING_VERT_EN BIT(0)
+#define OV5648_TC21_REG 0x3821
+#define OV5648_TC21_FLIP_HORZ_ISP_EN BIT(2)
+#define OV5648_TC21_FLIP_HORZ_SENSOR_EN BIT(1)
+#define OV5648_TC21_BINNING_HORZ_EN BIT(0)
+
+/* Strobe/exposure */
+
+#define OV5648_STROBE_REG 0x3b00
+#define OV5648_FREX_EXP_HH_REG 0x3b01
+#define OV5648_SHUTTER_DLY_H_REG 0x3b02
+#define OV5648_SHUTTER_DLY_L_REG 0x3b03
+#define OV5648_FREX_EXP_H_REG 0x3b04
+#define OV5648_FREX_EXP_L_REG 0x3b05
+#define OV5648_FREX_CTRL_REG 0x3b06
+#define OV5648_FREX_MODE_SEL_REG 0x3b07
+#define OV5648_FREX_MODE_SEL_FREX_SA1 BIT(4)
+#define OV5648_FREX_MODE_SEL_FX1_FM_EN BIT(3)
+#define OV5648_FREX_MODE_SEL_FREX_INV BIT(2)
+#define OV5648_FREX_MODE_SEL_MODE1 0x0
+#define OV5648_FREX_MODE_SEL_MODE2 0x1
+#define OV5648_FREX_MODE_SEL_ROLLING 0x2
+#define OV5648_FREX_EXP_REQ_REG 0x3b08
+#define OV5648_FREX_SHUTTER_DLY_REG 0x3b09
+#define OV5648_FREX_RST_LEN_REG 0x3b0a
+#define OV5648_STROBE_WIDTH_HH_REG 0x3b0b
+#define OV5648_STROBE_WIDTH_H_REG 0x3b0c
+
+/* OTP */
+
+#define OV5648_OTP_DATA_REG_BASE 0x3d00
+#define OV5648_OTP_PROGRAM_CTRL_REG 0x3d80
+#define OV5648_OTP_LOAD_CTRL_REG 0x3d81
+
+/* PSRAM */
+
+#define OV5648_PSRAM_CTRL1_REG 0x3f01
+#define OV5648_PSRAM_CTRLF_REG 0x3f0f
+
+/* Black Level */
+
+#define OV5648_BLC_CTRL0_REG 0x4000
+#define OV5648_BLC_CTRL1_REG 0x4001
+#define OV5648_BLC_CTRL1_START_LINE(v) ((v) & GENMASK(5, 0))
+#define OV5648_BLC_CTRL2_REG 0x4002
+#define OV5648_BLC_CTRL2_AUTO_EN BIT(6)
+#define OV5648_BLC_CTRL2_RESET_FRAME_NUM(v) ((v) & GENMASK(5, 0))
+#define OV5648_BLC_CTRL3_REG 0x4003
+#define OV5648_BLC_LINE_NUM_REG 0x4004
+#define OV5648_BLC_LINE_NUM(v) ((v) & GENMASK(7, 0))
+#define OV5648_BLC_CTRL5_REG 0x4005
+#define OV5648_BLC_CTRL5_UPDATE_EN BIT(1)
+#define OV5648_BLC_LEVEL_REG 0x4009
+
+/* Frame */
+
+#define OV5648_FRAME_CTRL_REG 0x4200
+#define OV5648_FRAME_ON_NUM_REG 0x4201
+#define OV5648_FRAME_OFF_NUM_REG 0x4202
+
+/* MIPI CSI-2 */
+
+#define OV5648_MIPI_CTRL0_REG 0x4800
+#define OV5648_MIPI_CTRL0_CLK_LANE_AUTOGATE BIT(5)
+#define OV5648_MIPI_CTRL0_LANE_SYNC_EN BIT(4)
+#define OV5648_MIPI_CTRL0_LANE_SELECT_LANE1 0
+#define OV5648_MIPI_CTRL0_LANE_SELECT_LANE2 BIT(3)
+#define OV5648_MIPI_CTRL0_IDLE_LP00 0
+#define OV5648_MIPI_CTRL0_IDLE_LP11 BIT(2)
+
+#define OV5648_MIPI_CTRL1_REG 0x4801
+#define OV5648_MIPI_CTRL2_REG 0x4802
+#define OV5648_MIPI_CTRL3_REG 0x4803
+#define OV5648_MIPI_CTRL4_REG 0x4804
+#define OV5648_MIPI_CTRL5_REG 0x4805
+#define OV5648_MIPI_MAX_FRAME_COUNT_H_REG 0x4810
+#define OV5648_MIPI_MAX_FRAME_COUNT_L_REG 0x4811
+#define OV5648_MIPI_CTRL14_REG 0x4814
+#define OV5648_MIPI_DT_SPKT_REG 0x4815
+#define OV5648_MIPI_HS_ZERO_MIN_H_REG 0x4818
+#define OV5648_MIPI_HS_ZERO_MIN_L_REG 0x4819
+#define OV5648_MIPI_HS_TRAIN_MIN_H_REG 0x481a
+#define OV5648_MIPI_HS_TRAIN_MIN_L_REG 0x481b
+#define OV5648_MIPI_CLK_ZERO_MIN_H_REG 0x481c
+#define OV5648_MIPI_CLK_ZERO_MIN_L_REG 0x481d
+#define OV5648_MIPI_CLK_PREPARE_MIN_H_REG 0x481e
+#define OV5648_MIPI_CLK_PREPARE_MIN_L_REG 0x481f
+#define OV5648_MIPI_CLK_POST_MIN_H_REG 0x4820
+#define OV5648_MIPI_CLK_POST_MIN_L_REG 0x4821
+#define OV5648_MIPI_CLK_TRAIL_MIN_H_REG 0x4822
+#define OV5648_MIPI_CLK_TRAIL_MIN_L_REG 0x4823
+#define OV5648_MIPI_LPX_P_MIN_H_REG 0x4824
+#define OV5648_MIPI_LPX_P_MIN_L_REG 0x4825
+#define OV5648_MIPI_HS_PREPARE_MIN_H_REG 0x4826
+#define OV5648_MIPI_HS_PREPARE_MIN_L_REG 0x4827
+#define OV5648_MIPI_HS_EXIT_MIN_H_REG 0x4828
+#define OV5648_MIPI_HS_EXIT_MIN_L_REG 0x4829
+#define OV5648_MIPI_HS_ZERO_MIN_UI_REG 0x482a
+#define OV5648_MIPI_HS_TRAIL_MIN_UI_REG 0x482b
+#define OV5648_MIPI_CLK_ZERO_MIN_UI_REG 0x482c
+#define OV5648_MIPI_CLK_PREPARE_MIN_UI_REG 0x482d
+#define OV5648_MIPI_CLK_POST_MIN_UI_REG 0x482e
+#define OV5648_MIPI_CLK_TRAIL_MIN_UI_REG 0x482f
+#define OV5648_MIPI_LPX_P_MIN_UI_REG 0x4830
+#define OV5648_MIPI_HS_PREPARE_MIN_UI_REG 0x4831
+#define OV5648_MIPI_HS_EXIT_MIN_UI_REG 0x4832
+#define OV5648_MIPI_REG_MIN_H_REG 0x4833
+#define OV5648_MIPI_REG_MIN_L_REG 0x4834
+#define OV5648_MIPI_REG_MAX_H_REG 0x4835
+#define OV5648_MIPI_REG_MAX_L_REG 0x4836
+#define OV5648_MIPI_PCLK_PERIOD_REG 0x4837
+#define OV5648_MIPI_WKUP_DLY_REG 0x4838
+#define OV5648_MIPI_LP_GPIO_REG 0x483b
+#define OV5648_MIPI_SNR_PCLK_DIV_REG 0x4843
+
+/* ISP */
+
+#define OV5648_ISP_CTRL0_REG 0x5000
+#define OV5648_ISP_CTRL0_BLACK_CORRECT_EN BIT(2)
+#define OV5648_ISP_CTRL0_WHITE_CORRECT_EN BIT(1)
+#define OV5648_ISP_CTRL1_REG 0x5001
+#define OV5648_ISP_CTRL1_AWB_EN BIT(0)
+#define OV5648_ISP_CTRL2_REG 0x5002
+#define OV5648_ISP_CTRL2_WIN_EN BIT(6)
+#define OV5648_ISP_CTRL2_OTP_EN BIT(1)
+#define OV5648_ISP_CTRL2_AWB_GAIN_EN BIT(0)
+#define OV5648_ISP_CTRL3_REG 0x5003
+#define OV5648_ISP_CTRL3_BUF_EN BIT(3)
+#define OV5648_ISP_CTRL3_BIN_MAN_SET BIT(2)
+#define OV5648_ISP_CTRL3_BIN_AUTO_EN BIT(1)
+#define OV5648_ISP_CTRL4_REG 0x5004
+#define OV5648_ISP_CTRL5_REG 0x5005
+#define OV5648_ISP_CTRL6_REG 0x5006
+#define OV5648_ISP_CTRL7_REG 0x5007
+#define OV5648_ISP_MAN_OFFSET_X_H_REG 0x5008
+#define OV5648_ISP_MAN_OFFSET_X_L_REG 0x5009
+#define OV5648_ISP_MAN_OFFSET_Y_H_REG 0x500a
+#define OV5648_ISP_MAN_OFFSET_Y_L_REG 0x500b
+#define OV5648_ISP_MAN_WIN_OFFSET_X_H_REG 0x500c
+#define OV5648_ISP_MAN_WIN_OFFSET_X_L_REG 0x500d
+#define OV5648_ISP_MAN_WIN_OFFSET_Y_H_REG 0x500e
+#define OV5648_ISP_MAN_WIN_OFFSET_Y_L_REG 0x500f
+#define OV5648_ISP_MAN_WIN_OUTPUT_X_H_REG 0x5010
+#define OV5648_ISP_MAN_WIN_OUTPUT_X_L_REG 0x5011
+#define OV5648_ISP_MAN_WIN_OUTPUT_Y_H_REG 0x5012
+#define OV5648_ISP_MAN_WIN_OUTPUT_Y_L_REG 0x5013
+#define OV5648_ISP_MAN_INPUT_X_H_REG 0x5014
+#define OV5648_ISP_MAN_INPUT_X_L_REG 0x5015
+#define OV5648_ISP_MAN_INPUT_Y_H_REG 0x5016
+#define OV5648_ISP_MAN_INPUT_Y_L_REG 0x5017
+#define OV5648_ISP_CTRL18_REG 0x5018
+#define OV5648_ISP_CTRL19_REG 0x5019
+#define OV5648_ISP_CTRL1A_REG 0x501a
+#define OV5648_ISP_CTRL1D_REG 0x501d
+#define OV5648_ISP_CTRL1F_REG 0x501f
+#define OV5648_ISP_CTRL1F_OUTPUT_EN 3
+#define OV5648_ISP_CTRL25_REG 0x5025
+
+#define OV5648_ISP_CTRL3D_REG 0x503d
+#define OV5648_ISP_CTRL3D_PATTERN_EN BIT(7)
+#define OV5648_ISP_CTRL3D_ROLLING_BAR_EN BIT(6)
+#define OV5648_ISP_CTRL3D_TRANSPARENT_MODE BIT(5)
+#define OV5648_ISP_CTRL3D_SQUARES_BW_MODE BIT(4)
+#define OV5648_ISP_CTRL3D_PATTERN_COLOR_BARS 0
+#define OV5648_ISP_CTRL3D_PATTERN_RANDOM_DATA 1
+#define OV5648_ISP_CTRL3D_PATTERN_COLOR_SQUARES 2
+#define OV5648_ISP_CTRL3D_PATTERN_INPUT 3
+
+#define OV5648_ISP_CTRL3E_REG 0x503e
+#define OV5648_ISP_CTRL4B_REG 0x504b
+#define OV5648_ISP_CTRL4B_POST_BIN_H_EN BIT(5)
+#define OV5648_ISP_CTRL4B_POST_BIN_V_EN BIT(4)
+#define OV5648_ISP_CTRL4C_REG 0x504c
+#define OV5648_ISP_CTRL57_REG 0x5057
+#define OV5648_ISP_CTRL58_REG 0x5058
+#define OV5648_ISP_CTRL59_REG 0x5059
+
+#define OV5648_ISP_WINDOW_START_X_H_REG 0x5980
+#define OV5648_ISP_WINDOW_START_X_L_REG 0x5981
+#define OV5648_ISP_WINDOW_START_Y_H_REG 0x5982
+#define OV5648_ISP_WINDOW_START_Y_L_REG 0x5983
+#define OV5648_ISP_WINDOW_WIN_X_H_REG 0x5984
+#define OV5648_ISP_WINDOW_WIN_X_L_REG 0x5985
+#define OV5648_ISP_WINDOW_WIN_Y_H_REG 0x5986
+#define OV5648_ISP_WINDOW_WIN_Y_L_REG 0x5987
+#define OV5648_ISP_WINDOW_MAN_REG 0x5988
+
+/* White Balance */
+
+#define OV5648_AWB_CTRL_REG 0x5180
+#define OV5648_AWB_CTRL_FAST_AWB BIT(6)
+#define OV5648_AWB_CTRL_GAIN_FREEZE_EN BIT(5)
+#define OV5648_AWB_CTRL_SUM_FREEZE_EN BIT(4)
+#define OV5648_AWB_CTRL_GAIN_MANUAL_EN BIT(3)
+
+#define OV5648_AWB_DELTA_REG 0x5181
+#define OV5648_AWB_STABLE_RANGE_REG 0x5182
+#define OV5648_AWB_STABLE_RANGE_WIDE_REG 0x5183
+#define OV5648_HSIZE_MAN_REG 0x5185
+
+#define OV5648_GAIN_RED_MAN_H_REG 0x5186
+#define OV5648_GAIN_RED_MAN_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_GAIN_RED_MAN_L_REG 0x5187
+#define OV5648_GAIN_RED_MAN_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_GAIN_GREEN_MAN_H_REG 0x5188
+#define OV5648_GAIN_GREEN_MAN_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_GAIN_GREEN_MAN_L_REG 0x5189
+#define OV5648_GAIN_GREEN_MAN_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_GAIN_BLUE_MAN_H_REG 0x518a
+#define OV5648_GAIN_BLUE_MAN_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV5648_GAIN_BLUE_MAN_L_REG 0x518b
+#define OV5648_GAIN_BLUE_MAN_L(v) ((v) & GENMASK(7, 0))
+#define OV5648_GAIN_RED_LIMIT_REG 0x518c
+#define OV5648_GAIN_GREEN_LIMIT_REG 0x518d
+#define OV5648_GAIN_BLUE_LIMIT_REG 0x518e
+#define OV5648_AWB_FRAME_COUNT_REG 0x518f
+#define OV5648_AWB_BASE_MAN_REG 0x51df
+
+/* Macros */
+
+#define ov5648_subdev_sensor(s) \
+ container_of(s, struct ov5648_sensor, subdev)
+
+#define ov5648_ctrl_subdev(c) \
+ (&container_of((c)->handler, struct ov5648_sensor, \
+ ctrls.handler)->subdev)
+
+/* Data structures */
+
+struct ov5648_register_value {
+ u16 address;
+ u8 value;
+ unsigned int delay_ms;
+};
+
+/*
+ * PLL1 Clock Tree:
+ *
+ * +-< XVCLK
+ * |
+ * +-+ pll_pre_div (0x3037 [3:0], special values: 5: 1.5, 7: 2.5)
+ * |
+ * +-+ pll_mul (0x3036 [7:0])
+ * |
+ * +-+ sys_div (0x3035 [7:4])
+ * |
+ * +-+ mipi_div (0x3035 [3:0])
+ * | |
+ * | +-> MIPI_SCLK
+ * | |
+ * | +-+ mipi_phy_div (2)
+ * | |
+ * | +-> MIPI_CLK
+ * |
+ * +-+ root_div (0x3037 [4])
+ * |
+ * +-+ bit_div (0x3034 [3:0], 8 bits: 2, 10 bits: 2.5, other: 1)
+ * |
+ * +-+ sclk_div (0x3106 [3:2])
+ * |
+ * +-> SCLK
+ * |
+ * +-+ mipi_div (0x3035, 1: PCLK = SCLK)
+ * |
+ * +-> PCLK
+ */
+
+struct ov5648_pll1_config {
+ unsigned int pll_pre_div;
+ unsigned int pll_mul;
+ unsigned int sys_div;
+ unsigned int root_div;
+ unsigned int sclk_div;
+ unsigned int mipi_div;
+};
+
+/*
+ * PLL2 Clock Tree:
+ *
+ * +-< XVCLK
+ * |
+ * +-+ plls_pre_div (0x303d [5:4], special values: 0: 1, 1: 1.5)
+ * |
+ * +-+ plls_div_r (0x303d [2])
+ * |
+ * +-+ plls_mul (0x303b [4:0])
+ * |
+ * +-+ sys_div (0x303c [3:0])
+ * |
+ * +-+ sel_div (0x303d [1:0], special values: 0: 1, 3: 2.5)
+ * |
+ * +-> ADCLK
+ */
+
+struct ov5648_pll2_config {
+ unsigned int plls_pre_div;
+ unsigned int plls_div_r;
+ unsigned int plls_mul;
+ unsigned int sys_div;
+ unsigned int sel_div;
+};
+
+/*
+ * General formulas for (array-centered) mode calculation:
+ * - photo_array_width = 2624
+ * - crop_start_x = (photo_array_width - output_size_x) / 2
+ * - crop_end_x = crop_start_x + offset_x + output_size_x - 1
+ *
+ * - photo_array_height = 1956
+ * - crop_start_y = (photo_array_height - output_size_y) / 2
+ * - crop_end_y = crop_start_y + offset_y + output_size_y - 1
+ */
+
+struct ov5648_mode {
+ unsigned int crop_start_x;
+ unsigned int offset_x;
+ unsigned int output_size_x;
+ unsigned int crop_end_x;
+ unsigned int hts;
+
+ unsigned int crop_start_y;
+ unsigned int offset_y;
+ unsigned int output_size_y;
+ unsigned int crop_end_y;
+ unsigned int vts;
+
+ bool binning_x;
+ bool binning_y;
+
+ unsigned int inc_x_odd;
+ unsigned int inc_x_even;
+ unsigned int inc_y_odd;
+ unsigned int inc_y_even;
+
+ /* 8-bit frame interval followed by 10-bit frame interval. */
+ struct v4l2_fract frame_interval[2];
+
+ /* 8-bit config followed by 10-bit config. */
+ const struct ov5648_pll1_config *pll1_config[2];
+ const struct ov5648_pll2_config *pll2_config;
+
+ const struct ov5648_register_value *register_values;
+ unsigned int register_values_count;
+};
+
+struct ov5648_state {
+ const struct ov5648_mode *mode;
+ u32 mbus_code;
+
+ bool streaming;
+};
+
+struct ov5648_ctrls {
+ struct v4l2_ctrl *exposure_auto;
+ struct v4l2_ctrl *exposure;
+
+ struct v4l2_ctrl *gain_auto;
+ struct v4l2_ctrl *gain;
+
+ struct v4l2_ctrl *white_balance_auto;
+ struct v4l2_ctrl *red_balance;
+ struct v4l2_ctrl *blue_balance;
+
+ struct v4l2_ctrl *link_freq;
+ struct v4l2_ctrl *pixel_rate;
+
+ struct v4l2_ctrl_handler handler;
+} __packed;
+
+struct ov5648_sensor {
+ struct device *dev;
+ struct i2c_client *i2c_client;
+ struct gpio_desc *reset;
+ struct gpio_desc *powerdown;
+ struct regulator *avdd;
+ struct regulator *dvdd;
+ struct regulator *dovdd;
+ struct clk *xvclk;
+
+ struct v4l2_fwnode_endpoint endpoint;
+ struct v4l2_subdev subdev;
+ struct media_pad pad;
+
+ struct mutex mutex;
+
+ struct ov5648_state state;
+ struct ov5648_ctrls ctrls;
+};
+
+/* Static definitions */
+
+/*
+ * XVCLK = 24 MHz
+ * SCLK = 84 MHz
+ * PCLK = 84 MHz
+ */
+static const struct ov5648_pll1_config ov5648_pll1_config_native_8_bits = {
+ .pll_pre_div = 3,
+ .pll_mul = 84,
+ .sys_div = 2,
+ .root_div = 1,
+ .sclk_div = 1,
+ .mipi_div = 1,
+};
+
+/*
+ * XVCLK = 24 MHz
+ * SCLK = 84 MHz
+ * PCLK = 84 MHz
+ */
+static const struct ov5648_pll1_config ov5648_pll1_config_native_10_bits = {
+ .pll_pre_div = 3,
+ .pll_mul = 105,
+ .sys_div = 2,
+ .root_div = 1,
+ .sclk_div = 1,
+ .mipi_div = 1,
+};
+
+/*
+ * XVCLK = 24 MHz
+ * ADCLK = 200 MHz
+ */
+static const struct ov5648_pll2_config ov5648_pll2_config_native = {
+ .plls_pre_div = 3,
+ .plls_div_r = 1,
+ .plls_mul = 25,
+ .sys_div = 1,
+ .sel_div = 1,
+};
+
+static const struct ov5648_mode ov5648_modes[] = {
+ /* 2592x1944 */
+ {
+ /* Horizontal */
+ .crop_start_x = 16,
+ .offset_x = 0,
+ .output_size_x = 2592,
+ .crop_end_x = 2607,
+ .hts = 2816,
+
+ /* Vertical */
+ .crop_start_y = 6,
+ .offset_y = 0,
+ .output_size_y = 1944,
+ .crop_end_y = 1949,
+ .vts = 1984,
+
+ /* Subsample increase */
+ .inc_x_odd = 1,
+ .inc_x_even = 1,
+ .inc_y_odd = 1,
+ .inc_y_even = 1,
+
+ /* Frame Interval */
+ .frame_interval = {
+ { 1, 15 },
+ { 1, 15 },
+ },
+
+ /* PLL */
+ .pll1_config = {
+ &ov5648_pll1_config_native_8_bits,
+ &ov5648_pll1_config_native_10_bits,
+ },
+ .pll2_config = &ov5648_pll2_config_native,
+ },
+ /* 1600x1200 (UXGA) */
+ {
+ /* Horizontal */
+ .crop_start_x = 512,
+ .offset_x = 0,
+ .output_size_x = 1600,
+ .crop_end_x = 2111,
+ .hts = 2816,
+
+ /* Vertical */
+ .crop_start_y = 378,
+ .offset_y = 0,
+ .output_size_y = 1200,
+ .crop_end_y = 1577,
+ .vts = 1984,
+
+ /* Subsample increase */
+ .inc_x_odd = 1,
+ .inc_x_even = 1,
+ .inc_y_odd = 1,
+ .inc_y_even = 1,
+
+ /* Frame Interval */
+ .frame_interval = {
+ { 1, 15 },
+ { 1, 15 },
+ },
+
+ /* PLL */
+ .pll1_config = {
+ &ov5648_pll1_config_native_8_bits,
+ &ov5648_pll1_config_native_10_bits,
+ },
+ .pll2_config = &ov5648_pll2_config_native,
+ },
+ /* 1920x1080 (Full HD) */
+ {
+ /* Horizontal */
+ .crop_start_x = 352,
+ .offset_x = 0,
+ .output_size_x = 1920,
+ .crop_end_x = 2271,
+ .hts = 2816,
+
+ /* Vertical */
+ .crop_start_y = 438,
+ .offset_y = 0,
+ .output_size_y = 1080,
+ .crop_end_y = 1517,
+ .vts = 1984,
+
+ /* Subsample increase */
+ .inc_x_odd = 1,
+ .inc_x_even = 1,
+ .inc_y_odd = 1,
+ .inc_y_even = 1,
+
+ /* Frame Interval */
+ .frame_interval = {
+ { 1, 15 },
+ { 1, 15 },
+ },
+
+ /* PLL */
+ .pll1_config = {
+ &ov5648_pll1_config_native_8_bits,
+ &ov5648_pll1_config_native_10_bits,
+ },
+ .pll2_config = &ov5648_pll2_config_native,
+ },
+ /* 1280x960 */
+ {
+ /* Horizontal */
+ .crop_start_x = 16,
+ .offset_x = 8,
+ .output_size_x = 1280,
+ .crop_end_x = 2607,
+ .hts = 1912,
+
+ /* Vertical */
+ .crop_start_y = 6,
+ .offset_y = 6,
+ .output_size_y = 960,
+ .crop_end_y = 1949,
+ .vts = 1496,
+
+ /* Binning */
+ .binning_x = true,
+
+ /* Subsample increase */
+ .inc_x_odd = 3,
+ .inc_x_even = 1,
+ .inc_y_odd = 3,
+ .inc_y_even = 1,
+
+ /* Frame Interval */
+ .frame_interval = {
+ { 1, 30 },
+ { 1, 30 },
+ },
+
+ /* PLL */
+ .pll1_config = {
+ &ov5648_pll1_config_native_8_bits,
+ &ov5648_pll1_config_native_10_bits,
+ },
+ .pll2_config = &ov5648_pll2_config_native,
+ },
+ /* 1280x720 (HD) */
+ {
+ /* Horizontal */
+ .crop_start_x = 16,
+ .offset_x = 8,
+ .output_size_x = 1280,
+ .crop_end_x = 2607,
+ .hts = 1912,
+
+ /* Vertical */
+ .crop_start_y = 254,
+ .offset_y = 2,
+ .output_size_y = 720,
+ .crop_end_y = 1701,
+ .vts = 1496,
+
+ /* Binning */
+ .binning_x = true,
+
+ /* Subsample increase */
+ .inc_x_odd = 3,
+ .inc_x_even = 1,
+ .inc_y_odd = 3,
+ .inc_y_even = 1,
+
+ /* Frame Interval */
+ .frame_interval = {
+ { 1, 30 },
+ { 1, 30 },
+ },
+
+ /* PLL */
+ .pll1_config = {
+ &ov5648_pll1_config_native_8_bits,
+ &ov5648_pll1_config_native_10_bits,
+ },
+ .pll2_config = &ov5648_pll2_config_native,
+ },
+ /* 640x480 (VGA) */
+ {
+ /* Horizontal */
+ .crop_start_x = 0,
+ .offset_x = 8,
+ .output_size_x = 640,
+ .crop_end_x = 2623,
+ .hts = 1896,
+
+ /* Vertical */
+ .crop_start_y = 0,
+ .offset_y = 2,
+ .output_size_y = 480,
+ .crop_end_y = 1953,
+ .vts = 984,
+
+ /* Binning */
+ .binning_x = true,
+
+ /* Subsample increase */
+ .inc_x_odd = 7,
+ .inc_x_even = 1,
+ .inc_y_odd = 7,
+ .inc_y_even = 1,
+
+ /* Frame Interval */
+ .frame_interval = {
+ { 1, 30 },
+ { 1, 30 },
+ },
+
+ /* PLL */
+ .pll1_config = {
+ &ov5648_pll1_config_native_8_bits,
+ &ov5648_pll1_config_native_10_bits,
+ },
+ .pll2_config = &ov5648_pll2_config_native,
+ },
+};
+
+static const u32 ov5648_mbus_codes[] = {
+ MEDIA_BUS_FMT_SBGGR8_1X8,
+ MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+static const struct ov5648_register_value ov5648_init_sequence[] = {
+ /* PSRAM */
+ { OV5648_PSRAM_CTRL1_REG, 0x0d },
+ { OV5648_PSRAM_CTRLF_REG, 0xf5 },
+};
+
+static const s64 ov5648_link_freq_menu[] = {
+ 210000000,
+ 168000000,
+};
+
+static const char *const ov5648_test_pattern_menu[] = {
+ "Disabled",
+ "Random data",
+ "Color bars",
+ "Color bars with rolling bar",
+ "Color squares",
+ "Color squares with rolling bar"
+};
+
+static const u8 ov5648_test_pattern_bits[] = {
+ 0,
+ OV5648_ISP_CTRL3D_PATTERN_EN | OV5648_ISP_CTRL3D_PATTERN_RANDOM_DATA,
+ OV5648_ISP_CTRL3D_PATTERN_EN | OV5648_ISP_CTRL3D_PATTERN_COLOR_BARS,
+ OV5648_ISP_CTRL3D_PATTERN_EN | OV5648_ISP_CTRL3D_ROLLING_BAR_EN |
+ OV5648_ISP_CTRL3D_PATTERN_COLOR_BARS,
+ OV5648_ISP_CTRL3D_PATTERN_EN | OV5648_ISP_CTRL3D_PATTERN_COLOR_SQUARES,
+ OV5648_ISP_CTRL3D_PATTERN_EN | OV5648_ISP_CTRL3D_ROLLING_BAR_EN |
+ OV5648_ISP_CTRL3D_PATTERN_COLOR_SQUARES,
+};
+
+/* Input/Output */
+
+static int ov5648_read(struct ov5648_sensor *sensor, u16 address, u8 *value)
+{
+ unsigned char data[2] = { address >> 8, address & 0xff };
+ struct i2c_client *client = sensor->i2c_client;
+ int ret;
+
+ ret = i2c_master_send(client, data, sizeof(data));
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c send error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ ret = i2c_master_recv(client, value, 1);
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c recv error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov5648_write(struct ov5648_sensor *sensor, u16 address, u8 value)
+{
+ unsigned char data[3] = { address >> 8, address & 0xff, value };
+ struct i2c_client *client = sensor->i2c_client;
+ int ret;
+
+ ret = i2c_master_send(client, data, sizeof(data));
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c send error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov5648_write_sequence(struct ov5648_sensor *sensor,
+ const struct ov5648_register_value *sequence,
+ unsigned int sequence_count)
+{
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < sequence_count; i++) {
+ ret = ov5648_write(sensor, sequence[i].address,
+ sequence[i].value);
+ if (ret)
+ break;
+
+ if (sequence[i].delay_ms)
+ msleep(sequence[i].delay_ms);
+ }
+
+ return ret;
+}
+
+static int ov5648_update_bits(struct ov5648_sensor *sensor, u16 address,
+ u8 mask, u8 bits)
+{
+ u8 value = 0;
+ int ret;
+
+ ret = ov5648_read(sensor, address, &value);
+ if (ret)
+ return ret;
+
+ value &= ~mask;
+ value |= bits;
+
+ ret = ov5648_write(sensor, address, value);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/* Sensor */
+
+static int ov5648_sw_reset(struct ov5648_sensor *sensor)
+{
+ return ov5648_write(sensor, OV5648_SW_RESET_REG, OV5648_SW_RESET_RESET);
+}
+
+static int ov5648_sw_standby(struct ov5648_sensor *sensor, int standby)
+{
+ u8 value = 0;
+
+ if (!standby)
+ value = OV5648_SW_STANDBY_STREAM_ON;
+
+ return ov5648_write(sensor, OV5648_SW_STANDBY_REG, value);
+}
+
+static int ov5648_chip_id_check(struct ov5648_sensor *sensor)
+{
+ u16 regs[] = { OV5648_CHIP_ID_H_REG, OV5648_CHIP_ID_L_REG };
+ u8 values[] = { OV5648_CHIP_ID_H_VALUE, OV5648_CHIP_ID_L_VALUE };
+ unsigned int i;
+ u8 value;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ ret = ov5648_read(sensor, regs[i], &value);
+ if (ret < 0)
+ return ret;
+
+ if (value != values[i]) {
+ dev_err(sensor->dev,
+ "chip id value mismatch: %#x instead of %#x\n",
+ value, values[i]);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int ov5648_avdd_internal_power(struct ov5648_sensor *sensor, int on)
+{
+ return ov5648_write(sensor, OV5648_A_PWC_PK_O0_REG,
+ on ? 0 : OV5648_A_PWC_PK_O0_BP_REGULATOR_N);
+}
+
+static int ov5648_pad_configure(struct ov5648_sensor *sensor)
+{
+ int ret;
+
+ /* Configure pads as input. */
+
+ ret = ov5648_write(sensor, OV5648_PAD_OEN1_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_PAD_OEN2_REG, 0);
+ if (ret)
+ return ret;
+
+ /* Disable FREX pin. */
+
+ return ov5648_write(sensor, OV5648_PAD_PK_REG,
+ OV5648_PAD_PK_DRIVE_STRENGTH_1X |
+ OV5648_PAD_PK_FREX_N);
+}
+
+static int ov5648_mipi_configure(struct ov5648_sensor *sensor)
+{
+ struct v4l2_fwnode_bus_mipi_csi2 *bus_mipi_csi2 =
+ &sensor->endpoint.bus.mipi_csi2;
+ unsigned int lanes_count = bus_mipi_csi2->num_data_lanes;
+ int ret;
+
+ ret = ov5648_write(sensor, OV5648_MIPI_CTRL0_REG,
+ OV5648_MIPI_CTRL0_CLK_LANE_AUTOGATE |
+ OV5648_MIPI_CTRL0_LANE_SELECT_LANE1 |
+ OV5648_MIPI_CTRL0_IDLE_LP11);
+ if (ret)
+ return ret;
+
+ return ov5648_write(sensor, OV5648_MIPI_SC_CTRL0_REG,
+ OV5648_MIPI_SC_CTRL0_MIPI_LANES(lanes_count) |
+ OV5648_MIPI_SC_CTRL0_PHY_LP_RX_PD |
+ OV5648_MIPI_SC_CTRL0_MIPI_EN);
+}
+
+static int ov5648_black_level_configure(struct ov5648_sensor *sensor)
+{
+ int ret;
+
+ /* Up to 6 lines are available for black level calibration. */
+
+ ret = ov5648_write(sensor, OV5648_BLC_CTRL1_REG,
+ OV5648_BLC_CTRL1_START_LINE(2));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_BLC_CTRL2_REG,
+ OV5648_BLC_CTRL2_AUTO_EN |
+ OV5648_BLC_CTRL2_RESET_FRAME_NUM(5));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_BLC_LINE_NUM_REG,
+ OV5648_BLC_LINE_NUM(4));
+ if (ret)
+ return ret;
+
+ return ov5648_update_bits(sensor, OV5648_BLC_CTRL5_REG,
+ OV5648_BLC_CTRL5_UPDATE_EN,
+ OV5648_BLC_CTRL5_UPDATE_EN);
+}
+
+static int ov5648_isp_configure(struct ov5648_sensor *sensor)
+{
+ u8 bits;
+ int ret;
+
+ /* Enable black and white level correction. */
+ bits = OV5648_ISP_CTRL0_BLACK_CORRECT_EN |
+ OV5648_ISP_CTRL0_WHITE_CORRECT_EN;
+
+ ret = ov5648_update_bits(sensor, OV5648_ISP_CTRL0_REG, bits, bits);
+ if (ret)
+ return ret;
+
+ /* Enable AWB. */
+ ret = ov5648_write(sensor, OV5648_ISP_CTRL1_REG,
+ OV5648_ISP_CTRL1_AWB_EN);
+ if (ret)
+ return ret;
+
+ /* Enable AWB gain and windowing. */
+ ret = ov5648_write(sensor, OV5648_ISP_CTRL2_REG,
+ OV5648_ISP_CTRL2_WIN_EN |
+ OV5648_ISP_CTRL2_AWB_GAIN_EN);
+ if (ret)
+ return ret;
+
+ /* Enable buffering and auto-binning. */
+ ret = ov5648_write(sensor, OV5648_ISP_CTRL3_REG,
+ OV5648_ISP_CTRL3_BUF_EN |
+ OV5648_ISP_CTRL3_BIN_AUTO_EN);
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_ISP_CTRL4_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_ISP_CTRL1F_REG,
+ OV5648_ISP_CTRL1F_OUTPUT_EN);
+ if (ret)
+ return ret;
+
+ /* Enable post-binning filters. */
+ ret = ov5648_write(sensor, OV5648_ISP_CTRL4B_REG,
+ OV5648_ISP_CTRL4B_POST_BIN_H_EN |
+ OV5648_ISP_CTRL4B_POST_BIN_V_EN);
+ if (ret)
+ return ret;
+
+ /* Disable debanding and night mode. Debug bit seems necessary. */
+ ret = ov5648_write(sensor, OV5648_AEC_CTRL0_REG,
+ OV5648_AEC_CTRL0_DEBUG |
+ OV5648_AEC_CTRL0_START_SEL_EN);
+ if (ret)
+ return ret;
+
+ return ov5648_write(sensor, OV5648_MANUAL_CTRL_REG,
+ OV5648_MANUAL_CTRL_FRAME_DELAY(1));
+}
+
+static unsigned long ov5648_mode_pll1_rate(struct ov5648_sensor *sensor,
+ const struct ov5648_pll1_config *config)
+{
+ unsigned long xvclk_rate;
+ unsigned long pll1_rate;
+
+ xvclk_rate = clk_get_rate(sensor->xvclk);
+ pll1_rate = xvclk_rate * config->pll_mul;
+
+ switch (config->pll_pre_div) {
+ case 5:
+ pll1_rate *= 3;
+ pll1_rate /= 2;
+ break;
+ case 7:
+ pll1_rate *= 5;
+ pll1_rate /= 2;
+ break;
+ default:
+ pll1_rate /= config->pll_pre_div;
+ break;
+ }
+
+ return pll1_rate;
+}
+
+static int ov5648_mode_pll1_configure(struct ov5648_sensor *sensor,
+ const struct ov5648_mode *mode,
+ u32 mbus_code)
+{
+ const struct ov5648_pll1_config *config;
+ u8 value;
+ int ret;
+
+ value = OV5648_PLL_CTRL0_PLL_CHARGE_PUMP(1);
+
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ config = mode->pll1_config[0];
+ value |= OV5648_PLL_CTRL0_BITS(8);
+ break;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ config = mode->pll1_config[1];
+ value |= OV5648_PLL_CTRL0_BITS(10);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = ov5648_write(sensor, OV5648_PLL_CTRL0_REG, value);
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_PLL_DIV_REG,
+ OV5648_PLL_DIV_ROOT_DIV(config->root_div) |
+ OV5648_PLL_DIV_PLL_PRE_DIV(config->pll_pre_div));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_PLL_MUL_REG,
+ OV5648_PLL_MUL(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_PLL_CTRL1_REG,
+ OV5648_PLL_CTRL1_SYS_DIV(config->sys_div) |
+ OV5648_PLL_CTRL1_MIPI_DIV(config->mipi_div));
+ if (ret)
+ return ret;
+
+ return ov5648_write(sensor, OV5648_SRB_CTRL_REG,
+ OV5648_SRB_CTRL_SCLK_DIV(config->sclk_div) |
+ OV5648_SRB_CTRL_SCLK_ARBITER_EN);
+}
+
+static int ov5648_mode_pll2_configure(struct ov5648_sensor *sensor,
+ const struct ov5648_mode *mode)
+{
+ const struct ov5648_pll2_config *config = mode->pll2_config;
+ int ret;
+
+ ret = ov5648_write(sensor, OV5648_PLLS_DIV_REG,
+ OV5648_PLLS_DIV_PLLS_PRE_DIV(config->plls_pre_div) |
+ OV5648_PLLS_DIV_PLLS_DIV_R(config->plls_div_r) |
+ OV5648_PLLS_DIV_PLLS_SEL_DIV(config->sel_div));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_PLLS_MUL_REG,
+ OV5648_PLLS_MUL(config->plls_mul));
+ if (ret)
+ return ret;
+
+ return ov5648_write(sensor, OV5648_PLLS_CTRL_REG,
+ OV5648_PLLS_CTRL_PLL_CHARGE_PUMP(1) |
+ OV5648_PLLS_CTRL_SYS_DIV(config->sys_div));
+}
+
+static int ov5648_mode_configure(struct ov5648_sensor *sensor,
+ const struct ov5648_mode *mode, u32 mbus_code)
+{
+ int ret;
+
+ /* Crop Start X */
+
+ ret = ov5648_write(sensor, OV5648_CROP_START_X_H_REG,
+ OV5648_CROP_START_X_H(mode->crop_start_x));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_CROP_START_X_L_REG,
+ OV5648_CROP_START_X_L(mode->crop_start_x));
+ if (ret)
+ return ret;
+
+ /* Offset X */
+
+ ret = ov5648_write(sensor, OV5648_OFFSET_X_H_REG,
+ OV5648_OFFSET_X_H(mode->offset_x));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_OFFSET_X_L_REG,
+ OV5648_OFFSET_X_L(mode->offset_x));
+ if (ret)
+ return ret;
+
+ /* Output Size X */
+
+ ret = ov5648_write(sensor, OV5648_OUTPUT_SIZE_X_H_REG,
+ OV5648_OUTPUT_SIZE_X_H(mode->output_size_x));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_OUTPUT_SIZE_X_L_REG,
+ OV5648_OUTPUT_SIZE_X_L(mode->output_size_x));
+ if (ret)
+ return ret;
+
+ /* Crop End X */
+
+ ret = ov5648_write(sensor, OV5648_CROP_END_X_H_REG,
+ OV5648_CROP_END_X_H(mode->crop_end_x));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_CROP_END_X_L_REG,
+ OV5648_CROP_END_X_L(mode->crop_end_x));
+ if (ret)
+ return ret;
+
+ /* Horizontal Total Size */
+
+ ret = ov5648_write(sensor, OV5648_HTS_H_REG, OV5648_HTS_H(mode->hts));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_HTS_L_REG, OV5648_HTS_L(mode->hts));
+ if (ret)
+ return ret;
+
+ /* Crop Start Y */
+
+ ret = ov5648_write(sensor, OV5648_CROP_START_Y_H_REG,
+ OV5648_CROP_START_Y_H(mode->crop_start_y));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_CROP_START_Y_L_REG,
+ OV5648_CROP_START_Y_L(mode->crop_start_y));
+ if (ret)
+ return ret;
+
+ /* Offset Y */
+
+ ret = ov5648_write(sensor, OV5648_OFFSET_Y_H_REG,
+ OV5648_OFFSET_Y_H(mode->offset_y));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_OFFSET_Y_L_REG,
+ OV5648_OFFSET_Y_L(mode->offset_y));
+ if (ret)
+ return ret;
+
+ /* Output Size Y */
+
+ ret = ov5648_write(sensor, OV5648_OUTPUT_SIZE_Y_H_REG,
+ OV5648_OUTPUT_SIZE_Y_H(mode->output_size_y));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_OUTPUT_SIZE_Y_L_REG,
+ OV5648_OUTPUT_SIZE_Y_L(mode->output_size_y));
+ if (ret)
+ return ret;
+
+ /* Crop End Y */
+
+ ret = ov5648_write(sensor, OV5648_CROP_END_Y_H_REG,
+ OV5648_CROP_END_Y_H(mode->crop_end_y));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_CROP_END_Y_L_REG,
+ OV5648_CROP_END_Y_L(mode->crop_end_y));
+ if (ret)
+ return ret;
+
+ /* Vertical Total Size */
+
+ ret = ov5648_write(sensor, OV5648_VTS_H_REG, OV5648_VTS_H(mode->vts));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_VTS_L_REG, OV5648_VTS_L(mode->vts));
+ if (ret)
+ return ret;
+
+ /* Flip/Mirror/Binning */
+
+ /*
+ * A debug bit is enabled by default and needs to be cleared for
+ * subsampling to work.
+ */
+ ret = ov5648_update_bits(sensor, OV5648_TC20_REG,
+ OV5648_TC20_DEBUG |
+ OV5648_TC20_BINNING_VERT_EN,
+ mode->binning_y ? OV5648_TC20_BINNING_VERT_EN :
+ 0);
+ if (ret)
+ return ret;
+
+ ret = ov5648_update_bits(sensor, OV5648_TC21_REG,
+ OV5648_TC21_BINNING_HORZ_EN,
+ mode->binning_x ? OV5648_TC21_BINNING_HORZ_EN :
+ 0);
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_SUB_INC_X_REG,
+ OV5648_SUB_INC_X_ODD(mode->inc_x_odd) |
+ OV5648_SUB_INC_X_EVEN(mode->inc_x_even));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_SUB_INC_Y_REG,
+ OV5648_SUB_INC_Y_ODD(mode->inc_y_odd) |
+ OV5648_SUB_INC_Y_EVEN(mode->inc_y_even));
+ if (ret)
+ return ret;
+
+ /* PLLs */
+
+ ret = ov5648_mode_pll1_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+
+ ret = ov5648_mode_pll2_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ /* Extra registers */
+
+ if (mode->register_values) {
+ ret = ov5648_write_sequence(sensor, mode->register_values,
+ mode->register_values_count);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static unsigned long ov5648_mode_mipi_clk_rate(struct ov5648_sensor *sensor,
+ const struct ov5648_mode *mode,
+ u32 mbus_code)
+{
+ const struct ov5648_pll1_config *config;
+ unsigned long pll1_rate;
+
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ config = mode->pll1_config[0];
+ break;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ config = mode->pll1_config[1];
+ break;
+ default:
+ return 0;
+ }
+
+ pll1_rate = ov5648_mode_pll1_rate(sensor, config);
+
+ return pll1_rate / config->sys_div / config->mipi_div / 2;
+}
+
+/* Exposure */
+
+static int ov5648_exposure_auto_configure(struct ov5648_sensor *sensor,
+ bool enable)
+{
+ return ov5648_update_bits(sensor, OV5648_MANUAL_CTRL_REG,
+ OV5648_MANUAL_CTRL_AEC_MANUAL_EN,
+ enable ? 0 : OV5648_MANUAL_CTRL_AEC_MANUAL_EN);
+}
+
+static int ov5648_exposure_configure(struct ov5648_sensor *sensor, u32 exposure)
+{
+ struct ov5648_ctrls *ctrls = &sensor->ctrls;
+ int ret;
+
+ if (ctrls->exposure_auto->val != V4L2_EXPOSURE_MANUAL)
+ return -EINVAL;
+
+ ret = ov5648_write(sensor, OV5648_EXPOSURE_CTRL_HH_REG,
+ OV5648_EXPOSURE_CTRL_HH(exposure));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_EXPOSURE_CTRL_H_REG,
+ OV5648_EXPOSURE_CTRL_H(exposure));
+ if (ret)
+ return ret;
+
+ return ov5648_write(sensor, OV5648_EXPOSURE_CTRL_L_REG,
+ OV5648_EXPOSURE_CTRL_L(exposure));
+}
+
+static int ov5648_exposure_value(struct ov5648_sensor *sensor,
+ u32 *exposure)
+{
+ u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0;
+ int ret;
+
+ ret = ov5648_read(sensor, OV5648_EXPOSURE_CTRL_HH_REG, &exposure_hh);
+ if (ret)
+ return ret;
+
+ ret = ov5648_read(sensor, OV5648_EXPOSURE_CTRL_H_REG, &exposure_h);
+ if (ret)
+ return ret;
+
+ ret = ov5648_read(sensor, OV5648_EXPOSURE_CTRL_L_REG, &exposure_l);
+ if (ret)
+ return ret;
+
+ *exposure = OV5648_EXPOSURE_CTRL_HH_VALUE((u32)exposure_hh) |
+ OV5648_EXPOSURE_CTRL_H_VALUE((u32)exposure_h) |
+ OV5648_EXPOSURE_CTRL_L_VALUE((u32)exposure_l);
+
+ return 0;
+}
+
+/* Gain */
+
+static int ov5648_gain_auto_configure(struct ov5648_sensor *sensor, bool enable)
+{
+ return ov5648_update_bits(sensor, OV5648_MANUAL_CTRL_REG,
+ OV5648_MANUAL_CTRL_AGC_MANUAL_EN,
+ enable ? 0 : OV5648_MANUAL_CTRL_AGC_MANUAL_EN);
+}
+
+static int ov5648_gain_configure(struct ov5648_sensor *sensor, u32 gain)
+{
+ struct ov5648_ctrls *ctrls = &sensor->ctrls;
+ int ret;
+
+ if (ctrls->gain_auto->val)
+ return -EINVAL;
+
+ ret = ov5648_write(sensor, OV5648_GAIN_CTRL_H_REG,
+ OV5648_GAIN_CTRL_H(gain));
+ if (ret)
+ return ret;
+
+ return ov5648_write(sensor, OV5648_GAIN_CTRL_L_REG,
+ OV5648_GAIN_CTRL_L(gain));
+}
+
+static int ov5648_gain_value(struct ov5648_sensor *sensor, u32 *gain)
+{
+ u8 gain_h = 0, gain_l = 0;
+ int ret;
+
+ ret = ov5648_read(sensor, OV5648_GAIN_CTRL_H_REG, &gain_h);
+ if (ret)
+ return ret;
+
+ ret = ov5648_read(sensor, OV5648_GAIN_CTRL_L_REG, &gain_l);
+ if (ret)
+ return ret;
+
+ *gain = OV5648_GAIN_CTRL_H_VALUE((u32)gain_h) |
+ OV5648_GAIN_CTRL_L_VALUE((u32)gain_l);
+
+ return 0;
+}
+
+/* White Balance */
+
+static int ov5648_white_balance_auto_configure(struct ov5648_sensor *sensor,
+ bool enable)
+{
+ return ov5648_write(sensor, OV5648_AWB_CTRL_REG,
+ enable ? 0 : OV5648_AWB_CTRL_GAIN_MANUAL_EN);
+}
+
+static int ov5648_white_balance_configure(struct ov5648_sensor *sensor,
+ u32 red_balance, u32 blue_balance)
+{
+ struct ov5648_ctrls *ctrls = &sensor->ctrls;
+ int ret;
+
+ if (ctrls->white_balance_auto->val)
+ return -EINVAL;
+
+ ret = ov5648_write(sensor, OV5648_GAIN_RED_MAN_H_REG,
+ OV5648_GAIN_RED_MAN_H(red_balance));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_GAIN_RED_MAN_L_REG,
+ OV5648_GAIN_RED_MAN_L(red_balance));
+ if (ret)
+ return ret;
+
+ ret = ov5648_write(sensor, OV5648_GAIN_BLUE_MAN_H_REG,
+ OV5648_GAIN_BLUE_MAN_H(blue_balance));
+ if (ret)
+ return ret;
+
+ return ov5648_write(sensor, OV5648_GAIN_BLUE_MAN_L_REG,
+ OV5648_GAIN_BLUE_MAN_L(blue_balance));
+}
+
+/* Flip */
+
+static int ov5648_flip_vert_configure(struct ov5648_sensor *sensor, bool enable)
+{
+ u8 bits = OV5648_TC20_FLIP_VERT_ISP_EN |
+ OV5648_TC20_FLIP_VERT_SENSOR_EN;
+
+ return ov5648_update_bits(sensor, OV5648_TC20_REG, bits,
+ enable ? bits : 0);
+}
+
+static int ov5648_flip_horz_configure(struct ov5648_sensor *sensor, bool enable)
+{
+ u8 bits = OV5648_TC21_FLIP_HORZ_ISP_EN |
+ OV5648_TC21_FLIP_HORZ_SENSOR_EN;
+
+ return ov5648_update_bits(sensor, OV5648_TC21_REG, bits,
+ enable ? bits : 0);
+}
+
+/* Test Pattern */
+
+static int ov5648_test_pattern_configure(struct ov5648_sensor *sensor,
+ unsigned int index)
+{
+ if (index >= ARRAY_SIZE(ov5648_test_pattern_bits))
+ return -EINVAL;
+
+ return ov5648_write(sensor, OV5648_ISP_CTRL3D_REG,
+ ov5648_test_pattern_bits[index]);
+}
+
+/* State */
+
+static int ov5648_state_mipi_configure(struct ov5648_sensor *sensor,
+ const struct ov5648_mode *mode,
+ u32 mbus_code)
+{
+ struct ov5648_ctrls *ctrls = &sensor->ctrls;
+ struct v4l2_fwnode_bus_mipi_csi2 *bus_mipi_csi2 =
+ &sensor->endpoint.bus.mipi_csi2;
+ unsigned long mipi_clk_rate;
+ unsigned int bits_per_sample;
+ unsigned int lanes_count;
+ unsigned int i, j;
+ s64 mipi_pixel_rate;
+
+ mipi_clk_rate = ov5648_mode_mipi_clk_rate(sensor, mode, mbus_code);
+ if (!mipi_clk_rate)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(ov5648_link_freq_menu); i++) {
+ s64 freq = ov5648_link_freq_menu[i];
+
+ if (freq == mipi_clk_rate)
+ break;
+ }
+
+ for (j = 0; j < sensor->endpoint.nr_of_link_frequencies; j++) {
+ u64 freq = sensor->endpoint.link_frequencies[j];
+
+ if (freq == mipi_clk_rate)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(ov5648_link_freq_menu)) {
+ dev_err(sensor->dev,
+ "failed to find %lu clk rate in link freq\n",
+ mipi_clk_rate);
+ } else if (j == sensor->endpoint.nr_of_link_frequencies) {
+ dev_err(sensor->dev,
+ "failed to find %lu clk rate in endpoint link-frequencies\n",
+ mipi_clk_rate);
+ } else {
+ __v4l2_ctrl_s_ctrl(ctrls->link_freq, i);
+ }
+
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ bits_per_sample = 8;
+ break;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ bits_per_sample = 10;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ lanes_count = bus_mipi_csi2->num_data_lanes;
+ mipi_pixel_rate = mipi_clk_rate * 2 * lanes_count / bits_per_sample;
+
+ __v4l2_ctrl_s_ctrl_int64(ctrls->pixel_rate, mipi_pixel_rate);
+
+ return 0;
+}
+
+static int ov5648_state_configure(struct ov5648_sensor *sensor,
+ const struct ov5648_mode *mode,
+ u32 mbus_code)
+{
+ int ret;
+
+ if (sensor->state.streaming)
+ return -EBUSY;
+
+ /* State will be configured at first power on otherwise. */
+ if (pm_runtime_enabled(sensor->dev) &&
+ !pm_runtime_suspended(sensor->dev)) {
+ ret = ov5648_mode_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+ }
+
+ ret = ov5648_state_mipi_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+
+ sensor->state.mode = mode;
+ sensor->state.mbus_code = mbus_code;
+
+ return 0;
+}
+
+static int ov5648_state_init(struct ov5648_sensor *sensor)
+{
+ return ov5648_state_configure(sensor, &ov5648_modes[0],
+ ov5648_mbus_codes[0]);
+}
+
+/* Sensor Base */
+
+static int ov5648_sensor_init(struct ov5648_sensor *sensor)
+{
+ int ret;
+
+ ret = ov5648_sw_reset(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to perform sw reset\n");
+ return ret;
+ }
+
+ ret = ov5648_sw_standby(sensor, 1);
+ if (ret) {
+ dev_err(sensor->dev, "failed to set sensor standby\n");
+ return ret;
+ }
+
+ ret = ov5648_chip_id_check(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to check sensor chip id\n");
+ return ret;
+ }
+
+ ret = ov5648_avdd_internal_power(sensor, !sensor->avdd);
+ if (ret) {
+ dev_err(sensor->dev, "failed to set internal avdd power\n");
+ return ret;
+ }
+
+ ret = ov5648_write_sequence(sensor, ov5648_init_sequence,
+ ARRAY_SIZE(ov5648_init_sequence));
+ if (ret) {
+ dev_err(sensor->dev, "failed to write init sequence\n");
+ return ret;
+ }
+
+ ret = ov5648_pad_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure pad\n");
+ return ret;
+ }
+
+ ret = ov5648_mipi_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure MIPI\n");
+ return ret;
+ }
+
+ ret = ov5648_isp_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure ISP\n");
+ return ret;
+ }
+
+ ret = ov5648_black_level_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure black level\n");
+ return ret;
+ }
+
+ /* Configure current mode. */
+ ret = ov5648_state_configure(sensor, sensor->state.mode,
+ sensor->state.mbus_code);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure state\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov5648_sensor_power(struct ov5648_sensor *sensor, bool on)
+{
+ /* Keep initialized to zero for disable label. */
+ int ret = 0;
+
+ /*
+ * General notes about the power sequence:
+ * - power-down GPIO must be active (low) during power-on;
+ * - reset GPIO state does not matter during power-on;
+ * - XVCLK must be provided 1 ms before register access;
+ * - 10 ms are needed between power-down deassert and register access.
+ */
+
+ /* Note that regulator-and-GPIO-based power is untested. */
+ if (on) {
+ gpiod_set_value_cansleep(sensor->reset, 1);
+ gpiod_set_value_cansleep(sensor->powerdown, 1);
+
+ ret = regulator_enable(sensor->dovdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable DOVDD regulator\n");
+ goto disable;
+ }
+
+ if (sensor->avdd) {
+ ret = regulator_enable(sensor->avdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable AVDD regulator\n");
+ goto disable;
+ }
+ }
+
+ ret = regulator_enable(sensor->dvdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable DVDD regulator\n");
+ goto disable;
+ }
+
+ /* According to OV5648 power up diagram. */
+ usleep_range(5000, 10000);
+
+ ret = clk_prepare_enable(sensor->xvclk);
+ if (ret) {
+ dev_err(sensor->dev, "failed to enable XVCLK clock\n");
+ goto disable;
+ }
+
+ gpiod_set_value_cansleep(sensor->reset, 0);
+ gpiod_set_value_cansleep(sensor->powerdown, 0);
+
+ usleep_range(20000, 25000);
+ } else {
+disable:
+ gpiod_set_value_cansleep(sensor->powerdown, 1);
+ gpiod_set_value_cansleep(sensor->reset, 1);
+
+ clk_disable_unprepare(sensor->xvclk);
+
+ regulator_disable(sensor->dvdd);
+
+ if (sensor->avdd)
+ regulator_disable(sensor->avdd);
+
+ regulator_disable(sensor->dovdd);
+ }
+
+ return ret;
+}
+
+/* Controls */
+
+static int ov5648_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *subdev = ov5648_ctrl_subdev(ctrl);
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+ struct ov5648_ctrls *ctrls = &sensor->ctrls;
+ int ret;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE_AUTO:
+ ret = ov5648_exposure_value(sensor, &ctrls->exposure->val);
+ if (ret)
+ return ret;
+ break;
+ case V4L2_CID_AUTOGAIN:
+ ret = ov5648_gain_value(sensor, &ctrls->gain->val);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ov5648_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *subdev = ov5648_ctrl_subdev(ctrl);
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+ struct ov5648_ctrls *ctrls = &sensor->ctrls;
+ unsigned int index;
+ bool enable;
+ int ret;
+
+ /* Wait for the sensor to be on before setting controls. */
+ if (pm_runtime_suspended(sensor->dev))
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE_AUTO:
+ enable = ctrl->val == V4L2_EXPOSURE_AUTO;
+
+ ret = ov5648_exposure_auto_configure(sensor, enable);
+ if (ret)
+ return ret;
+
+ if (!enable && ctrls->exposure->is_new) {
+ ret = ov5648_exposure_configure(sensor,
+ ctrls->exposure->val);
+ if (ret)
+ return ret;
+ }
+ break;
+ case V4L2_CID_AUTOGAIN:
+ enable = !!ctrl->val;
+
+ ret = ov5648_gain_auto_configure(sensor, enable);
+ if (ret)
+ return ret;
+
+ if (!enable) {
+ ret = ov5648_gain_configure(sensor, ctrls->gain->val);
+ if (ret)
+ return ret;
+ }
+ break;
+ case V4L2_CID_AUTO_WHITE_BALANCE:
+ enable = !!ctrl->val;
+
+ ret = ov5648_white_balance_auto_configure(sensor, enable);
+ if (ret)
+ return ret;
+
+ if (!enable) {
+ ret = ov5648_white_balance_configure(sensor,
+ ctrls->red_balance->val,
+ ctrls->blue_balance->val);
+ if (ret)
+ return ret;
+ }
+ break;
+ case V4L2_CID_HFLIP:
+ enable = !!ctrl->val;
+ return ov5648_flip_horz_configure(sensor, enable);
+ case V4L2_CID_VFLIP:
+ enable = !!ctrl->val;
+ return ov5648_flip_vert_configure(sensor, enable);
+ case V4L2_CID_TEST_PATTERN:
+ index = (unsigned int)ctrl->val;
+ return ov5648_test_pattern_configure(sensor, index);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops ov5648_ctrl_ops = {
+ .g_volatile_ctrl = ov5648_g_volatile_ctrl,
+ .s_ctrl = ov5648_s_ctrl,
+};
+
+static int ov5648_ctrls_init(struct ov5648_sensor *sensor)
+{
+ struct ov5648_ctrls *ctrls = &sensor->ctrls;
+ struct v4l2_ctrl_handler *handler = &ctrls->handler;
+ const struct v4l2_ctrl_ops *ops = &ov5648_ctrl_ops;
+ int ret;
+
+ v4l2_ctrl_handler_init(handler, 32);
+
+ /* Use our mutex for ctrl locking. */
+ handler->lock = &sensor->mutex;
+
+ /* Exposure */
+
+ ctrls->exposure_auto = v4l2_ctrl_new_std_menu(handler, ops,
+ V4L2_CID_EXPOSURE_AUTO,
+ V4L2_EXPOSURE_MANUAL, 0,
+ V4L2_EXPOSURE_AUTO);
+
+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE,
+ 16, 1048575, 16, 512);
+
+ v4l2_ctrl_auto_cluster(2, &ctrls->exposure_auto, 1, true);
+
+ /* Gain */
+
+ ctrls->gain_auto =
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+
+ ctrls->gain = v4l2_ctrl_new_std(handler, ops, V4L2_CID_GAIN, 16, 1023,
+ 16, 16);
+
+ v4l2_ctrl_auto_cluster(2, &ctrls->gain_auto, 0, true);
+
+ /* White Balance */
+
+ ctrls->white_balance_auto =
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_AUTO_WHITE_BALANCE, 0,
+ 1, 1, 1);
+
+ ctrls->red_balance = v4l2_ctrl_new_std(handler, ops,
+ V4L2_CID_RED_BALANCE, 0, 4095,
+ 1, 1024);
+
+ ctrls->blue_balance = v4l2_ctrl_new_std(handler, ops,
+ V4L2_CID_BLUE_BALANCE, 0, 4095,
+ 1, 1024);
+
+ v4l2_ctrl_auto_cluster(3, &ctrls->white_balance_auto, 0, false);
+
+ /* Flip */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+ /* Test Pattern */
+
+ v4l2_ctrl_new_std_menu_items(handler, ops, V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(ov5648_test_pattern_menu) - 1,
+ 0, 0, ov5648_test_pattern_menu);
+
+ /* MIPI CSI-2 */
+
+ ctrls->link_freq =
+ v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
+ ARRAY_SIZE(ov5648_link_freq_menu) - 1,
+ 0, ov5648_link_freq_menu);
+
+ ctrls->pixel_rate =
+ v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1,
+ INT_MAX, 1, 1);
+
+ if (handler->error) {
+ ret = handler->error;
+ goto error_ctrls;
+ }
+
+ ctrls->exposure->flags |= V4L2_CTRL_FLAG_VOLATILE;
+ ctrls->gain->flags |= V4L2_CTRL_FLAG_VOLATILE;
+
+ ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ sensor->subdev.ctrl_handler = handler;
+
+ return 0;
+
+error_ctrls:
+ v4l2_ctrl_handler_free(handler);
+
+ return ret;
+}
+
+/* Subdev Video Operations */
+
+static int ov5648_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+ struct ov5648_state *state = &sensor->state;
+ int ret;
+
+ if (enable) {
+ ret = pm_runtime_get_sync(sensor->dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(sensor->dev);
+ return ret;
+ }
+ }
+
+ mutex_lock(&sensor->mutex);
+ ret = ov5648_sw_standby(sensor, !enable);
+ mutex_unlock(&sensor->mutex);
+
+ if (ret)
+ return ret;
+
+ state->streaming = !!enable;
+
+ if (!enable)
+ pm_runtime_put(sensor->dev);
+
+ return 0;
+}
+
+static int ov5648_g_frame_interval(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+ const struct ov5648_mode *mode;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ mode = sensor->state.mode;
+
+ switch (sensor->state.mbus_code) {
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ interval->interval = mode->frame_interval[0];
+ break;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ interval->interval = mode->frame_interval[1];
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static const struct v4l2_subdev_video_ops ov5648_subdev_video_ops = {
+ .s_stream = ov5648_s_stream,
+ .g_frame_interval = ov5648_g_frame_interval,
+ .s_frame_interval = ov5648_g_frame_interval,
+};
+
+/* Subdev Pad Operations */
+
+static int ov5648_enum_mbus_code(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_mbus_code_enum *code_enum)
+{
+ if (code_enum->index >= ARRAY_SIZE(ov5648_mbus_codes))
+ return -EINVAL;
+
+ code_enum->code = ov5648_mbus_codes[code_enum->index];
+
+ return 0;
+}
+
+static void ov5648_mbus_format_fill(struct v4l2_mbus_framefmt *mbus_format,
+ u32 mbus_code,
+ const struct ov5648_mode *mode)
+{
+ mbus_format->width = mode->output_size_x;
+ mbus_format->height = mode->output_size_y;
+ mbus_format->code = mbus_code;
+
+ mbus_format->field = V4L2_FIELD_NONE;
+ mbus_format->colorspace = V4L2_COLORSPACE_RAW;
+ mbus_format->ycbcr_enc =
+ V4L2_MAP_YCBCR_ENC_DEFAULT(mbus_format->colorspace);
+ mbus_format->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ mbus_format->xfer_func =
+ V4L2_MAP_XFER_FUNC_DEFAULT(mbus_format->colorspace);
+}
+
+static int ov5648_get_fmt(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_format *format)
+{
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+ struct v4l2_mbus_framefmt *mbus_format = &format->format;
+
+ mutex_lock(&sensor->mutex);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ *mbus_format = *v4l2_subdev_get_try_format(subdev, config,
+ format->pad);
+ else
+ ov5648_mbus_format_fill(mbus_format, sensor->state.mbus_code,
+ sensor->state.mode);
+
+ mutex_unlock(&sensor->mutex);
+
+ return 0;
+}
+
+static int ov5648_set_fmt(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_format *format)
+{
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+ struct v4l2_mbus_framefmt *mbus_format = &format->format;
+ const struct ov5648_mode *mode;
+ u32 mbus_code = 0;
+ unsigned int index;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ if (sensor->state.streaming) {
+ ret = -EBUSY;
+ goto complete;
+ }
+
+ /* Try to find requested mbus code. */
+ for (index = 0; index < ARRAY_SIZE(ov5648_mbus_codes); index++) {
+ if (ov5648_mbus_codes[index] == mbus_format->code) {
+ mbus_code = mbus_format->code;
+ break;
+ }
+ }
+
+ /* Fallback to default. */
+ if (!mbus_code)
+ mbus_code = ov5648_mbus_codes[0];
+
+ /* Find the mode with nearest dimensions. */
+ mode = v4l2_find_nearest_size(ov5648_modes, ARRAY_SIZE(ov5648_modes),
+ output_size_x, output_size_y,
+ mbus_format->width, mbus_format->height);
+ if (!mode) {
+ ret = -EINVAL;
+ goto complete;
+ }
+
+ ov5648_mbus_format_fill(mbus_format, mbus_code, mode);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ *v4l2_subdev_get_try_format(subdev, config, format->pad) =
+ *mbus_format;
+ else if (sensor->state.mode != mode ||
+ sensor->state.mbus_code != mbus_code)
+ ret = ov5648_state_configure(sensor, mode, mbus_code);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov5648_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_frame_size_enum *size_enum)
+{
+ const struct ov5648_mode *mode;
+
+ if (size_enum->index >= ARRAY_SIZE(ov5648_modes))
+ return -EINVAL;
+
+ mode = &ov5648_modes[size_enum->index];
+
+ size_enum->min_width = size_enum->max_width = mode->output_size_x;
+ size_enum->min_height = size_enum->max_height = mode->output_size_y;
+
+ return 0;
+}
+
+static int ov5648_enum_frame_interval(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_frame_interval_enum *interval_enum)
+{
+ const struct ov5648_mode *mode = NULL;
+ unsigned int mode_index;
+ unsigned int interval_index;
+
+ if (interval_enum->index > 0)
+ return -EINVAL;
+
+ /*
+ * Multiple modes with the same dimensions may have different frame
+ * intervals, so look up each relevant mode.
+ */
+ for (mode_index = 0, interval_index = 0;
+ mode_index < ARRAY_SIZE(ov5648_modes); mode_index++) {
+ mode = &ov5648_modes[mode_index];
+
+ if (mode->output_size_x == interval_enum->width &&
+ mode->output_size_y == interval_enum->height) {
+ if (interval_index == interval_enum->index)
+ break;
+
+ interval_index++;
+ }
+ }
+
+ if (mode_index == ARRAY_SIZE(ov5648_modes))
+ return -EINVAL;
+
+ switch (interval_enum->code) {
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ interval_enum->interval = mode->frame_interval[0];
+ break;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ interval_enum->interval = mode->frame_interval[1];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops ov5648_subdev_pad_ops = {
+ .enum_mbus_code = ov5648_enum_mbus_code,
+ .get_fmt = ov5648_get_fmt,
+ .set_fmt = ov5648_set_fmt,
+ .enum_frame_size = ov5648_enum_frame_size,
+ .enum_frame_interval = ov5648_enum_frame_interval,
+};
+
+static const struct v4l2_subdev_ops ov5648_subdev_ops = {
+ .video = &ov5648_subdev_video_ops,
+ .pad = &ov5648_subdev_pad_ops,
+};
+
+static int ov5648_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+ struct ov5648_state *state = &sensor->state;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ if (state->streaming) {
+ ret = ov5648_sw_standby(sensor, true);
+ if (ret)
+ goto complete;
+ }
+
+ ret = ov5648_sensor_power(sensor, false);
+ if (ret)
+ ov5648_sw_standby(sensor, false);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov5648_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+ struct ov5648_state *state = &sensor->state;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ ret = ov5648_sensor_power(sensor, true);
+ if (ret)
+ goto complete;
+
+ ret = ov5648_sensor_init(sensor);
+ if (ret)
+ goto error_power;
+
+ ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler);
+ if (ret)
+ goto error_power;
+
+ if (state->streaming) {
+ ret = ov5648_sw_standby(sensor, false);
+ if (ret)
+ goto error_power;
+ }
+
+ goto complete;
+
+error_power:
+ ov5648_sensor_power(sensor, false);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov5648_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fwnode_handle *handle;
+ struct ov5648_sensor *sensor;
+ struct v4l2_subdev *subdev;
+ struct media_pad *pad;
+ unsigned long rate;
+ int ret;
+
+ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ sensor->dev = dev;
+ sensor->i2c_client = client;
+
+ /* Graph Endpoint */
+
+ handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+ if (!handle) {
+ dev_err(dev, "unable to find endpoint node\n");
+ return -EINVAL;
+ }
+
+ sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY;
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(handle, &sensor->endpoint);
+ fwnode_handle_put(handle);
+ if (ret) {
+ dev_err(dev, "failed to parse endpoint node\n");
+ return ret;
+ }
+
+ /* GPIOs */
+
+ sensor->powerdown = devm_gpiod_get_optional(dev, "powerdown",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->powerdown)) {
+ ret = PTR_ERR(sensor->powerdown);
+ goto error_endpoint;
+ }
+
+ sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->reset)) {
+ ret = PTR_ERR(sensor->reset);
+ goto error_endpoint;
+ }
+
+ /* Regulators */
+
+ /* DVDD: digital core */
+ sensor->dvdd = devm_regulator_get(dev, "dvdd");
+ if (IS_ERR(sensor->dvdd)) {
+ dev_err(dev, "cannot get DVDD (digital core) regulator\n");
+ ret = PTR_ERR(sensor->dvdd);
+ goto error_endpoint;
+ }
+
+ /* DOVDD: digital I/O */
+ sensor->dovdd = devm_regulator_get(dev, "dovdd");
+ if (IS_ERR(sensor->dvdd)) {
+ dev_err(dev, "cannot get DOVDD (digital I/O) regulator\n");
+ ret = PTR_ERR(sensor->dvdd);
+ goto error_endpoint;
+ }
+
+ /* AVDD: analog */
+ sensor->avdd = devm_regulator_get_optional(dev, "avdd");
+ if (IS_ERR(sensor->avdd)) {
+ dev_info(dev, "no AVDD regulator provided, using internal\n");
+ sensor->avdd = NULL;
+ }
+
+ /* External Clock */
+
+ sensor->xvclk = devm_clk_get(dev, NULL);
+ if (IS_ERR(sensor->xvclk)) {
+ dev_err(dev, "failed to get external clock\n");
+ ret = PTR_ERR(sensor->xvclk);
+ goto error_endpoint;
+ }
+
+ rate = clk_get_rate(sensor->xvclk);
+ if (rate != OV5648_XVCLK_RATE) {
+ dev_err(dev, "clock rate %lu Hz is unsupported\n", rate);
+ ret = -EINVAL;
+ goto error_endpoint;
+ }
+
+ /* Subdev, entity and pad */
+
+ subdev = &sensor->subdev;
+ v4l2_i2c_subdev_init(subdev, client, &ov5648_subdev_ops);
+
+ subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ subdev->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+ pad = &sensor->pad;
+ pad->flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&subdev->entity, 1, pad);
+ if (ret)
+ goto error_entity;
+
+ /* Mutex */
+
+ mutex_init(&sensor->mutex);
+
+ /* Sensor */
+
+ ret = ov5648_ctrls_init(sensor);
+ if (ret)
+ goto error_mutex;
+
+ ret = ov5648_state_init(sensor);
+ if (ret)
+ goto error_ctrls;
+
+ /* Runtime PM */
+
+ pm_runtime_enable(sensor->dev);
+ pm_runtime_set_suspended(sensor->dev);
+
+ /* V4L2 subdev register */
+
+ ret = v4l2_async_register_subdev_sensor_common(subdev);
+ if (ret)
+ goto error_pm;
+
+ return 0;
+
+error_pm:
+ pm_runtime_disable(sensor->dev);
+
+error_ctrls:
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+
+error_mutex:
+ mutex_destroy(&sensor->mutex);
+
+error_entity:
+ media_entity_cleanup(&sensor->subdev.entity);
+
+error_endpoint:
+ v4l2_fwnode_endpoint_free(&sensor->endpoint);
+
+ return ret;
+}
+
+static int ov5648_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov5648_sensor *sensor = ov5648_subdev_sensor(subdev);
+
+ v4l2_async_unregister_subdev(subdev);
+ pm_runtime_disable(sensor->dev);
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+ mutex_destroy(&sensor->mutex);
+ media_entity_cleanup(&subdev->entity);
+
+ return 0;
+}
+
+static const struct dev_pm_ops ov5648_pm_ops = {
+ SET_RUNTIME_PM_OPS(ov5648_suspend, ov5648_resume, NULL)
+};
+
+static const struct of_device_id ov5648_of_match[] = {
+ { .compatible = "ovti,ov5648" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ov5648_of_match);
+
+static struct i2c_driver ov5648_driver = {
+ .driver = {
+ .name = "ov5648",
+ .of_match_table = ov5648_of_match,
+ .pm = &ov5648_pm_ops,
+ },
+ .probe_new = ov5648_probe,
+ .remove = ov5648_remove,
+};
+
+module_i2c_driver(ov5648_driver);
+
+MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
+MODULE_DESCRIPTION("V4L2 driver for the OmniVision OV5648 image sensor");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/ov5670.c b/drivers/media/i2c/ov5670.c
index 148fd4e05029..866c8c2e8f59 100644
--- a/drivers/media/i2c/ov5670.c
+++ b/drivers/media/i2c/ov5670.c
@@ -2084,7 +2084,8 @@ static int ov5670_init_controls(struct ov5670 *ov5670)
/* By default, V4L2_CID_PIXEL_RATE is read only */
ov5670->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov5670_ctrl_ops,
- V4L2_CID_PIXEL_RATE, 0,
+ V4L2_CID_PIXEL_RATE,
+ link_freq_configs[0].pixel_rate,
link_freq_configs[0].pixel_rate,
1,
link_freq_configs[0].pixel_rate);
diff --git a/drivers/media/i2c/ov5675.c b/drivers/media/i2c/ov5675.c
index 5e35808037ad..ae00d717e599 100644
--- a/drivers/media/i2c/ov5675.c
+++ b/drivers/media/i2c/ov5675.c
@@ -624,7 +624,7 @@ static int ov5675_set_ctrl_hflip(struct ov5675 *ov5675, u32 ctrl_val)
return ov5675_write_reg(ov5675, OV5675_REG_FORMAT1,
OV5675_REG_VALUE_08BIT,
- ctrl_val ? val & ~BIT(3) : val);
+ ctrl_val ? val & ~BIT(3) : val | BIT(3));
}
static int ov5675_set_ctrl_vflip(struct ov5675 *ov5675, u8 ctrl_val)
@@ -639,7 +639,7 @@ static int ov5675_set_ctrl_vflip(struct ov5675 *ov5675, u8 ctrl_val)
ret = ov5675_write_reg(ov5675, OV5675_REG_FORMAT1,
OV5675_REG_VALUE_08BIT,
- ctrl_val ? val | BIT(4) | BIT(5) : val);
+ ctrl_val ? val | BIT(4) | BIT(5) : val & ~BIT(4) & ~BIT(5));
if (ret)
return ret;
@@ -652,7 +652,7 @@ static int ov5675_set_ctrl_vflip(struct ov5675 *ov5675, u8 ctrl_val)
return ov5675_write_reg(ov5675, OV5675_REG_FORMAT2,
OV5675_REG_VALUE_08BIT,
- ctrl_val ? val | BIT(1) : val);
+ ctrl_val ? val | BIT(1) : val & ~BIT(1));
}
static int ov5675_set_ctrl(struct v4l2_ctrl *ctrl)
diff --git a/drivers/media/i2c/ov6650.c b/drivers/media/i2c/ov6650.c
index d73f9f540932..85dd13694bd2 100644
--- a/drivers/media/i2c/ov6650.c
+++ b/drivers/media/i2c/ov6650.c
@@ -22,13 +22,13 @@
*/
#include <linux/bitops.h>
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/v4l2-mediabus.h>
#include <linux/module.h>
-#include <media/v4l2-clk.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
@@ -194,7 +194,7 @@ struct ov6650 {
struct v4l2_ctrl *blue;
struct v4l2_ctrl *red;
};
- struct v4l2_clk *clk;
+ struct clk *clk;
bool half_scale; /* scale down output by 2 */
struct v4l2_rect rect; /* sensor cropping window */
struct v4l2_fract tpf; /* as requested with s_frame_interval */
@@ -459,9 +459,9 @@ static int ov6650_s_power(struct v4l2_subdev *sd, int on)
int ret = 0;
if (on)
- ret = v4l2_clk_enable(priv->clk);
+ ret = clk_prepare_enable(priv->clk);
else
- v4l2_clk_disable(priv->clk);
+ clk_disable_unprepare(priv->clk);
return ret;
}
@@ -821,14 +821,14 @@ static int ov6650_video_probe(struct v4l2_subdev *sd)
u8 pidh, pidl, midh, midl;
int i, ret = 0;
- priv->clk = v4l2_clk_get(&client->dev, NULL);
+ priv->clk = devm_clk_get(&client->dev, NULL);
if (IS_ERR(priv->clk)) {
ret = PTR_ERR(priv->clk);
- dev_err(&client->dev, "v4l2_clk request err: %d\n", ret);
+ dev_err(&client->dev, "clk request err: %d\n", ret);
return ret;
}
- rate = v4l2_clk_get_rate(priv->clk);
+ rate = clk_get_rate(priv->clk);
for (i = 0; rate && i < ARRAY_SIZE(ov6650_xclk); i++) {
if (rate != ov6650_xclk[i].rate)
continue;
@@ -839,8 +839,8 @@ static int ov6650_video_probe(struct v4l2_subdev *sd)
break;
}
for (i = 0; !xclk && i < ARRAY_SIZE(ov6650_xclk); i++) {
- ret = v4l2_clk_set_rate(priv->clk, ov6650_xclk[i].rate);
- if (ret || v4l2_clk_get_rate(priv->clk) != ov6650_xclk[i].rate)
+ ret = clk_set_rate(priv->clk, ov6650_xclk[i].rate);
+ if (ret || clk_get_rate(priv->clk) != ov6650_xclk[i].rate)
continue;
xclk = &ov6650_xclk[i];
@@ -852,12 +852,12 @@ static int ov6650_video_probe(struct v4l2_subdev *sd)
dev_err(&client->dev, "unable to get supported clock rate\n");
if (!ret)
ret = -EINVAL;
- goto eclkput;
+ return ret;
}
ret = ov6650_s_power(sd, 1);
if (ret < 0)
- goto eclkput;
+ return ret;
msleep(20);
@@ -899,11 +899,6 @@ static int ov6650_video_probe(struct v4l2_subdev *sd)
done:
ov6650_s_power(sd, 0);
- if (!ret)
- return 0;
-eclkput:
- v4l2_clk_put(priv->clk);
-
return ret;
}
@@ -1089,7 +1084,6 @@ static int ov6650_remove(struct i2c_client *client)
{
struct ov6650 *priv = to_ov6650(client);
- v4l2_clk_put(priv->clk);
v4l2_async_unregister_subdev(&priv->subdev);
v4l2_ctrl_handler_free(&priv->hdl);
return 0;
diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c
index d8cefd3d4045..b337f729d5e3 100644
--- a/drivers/media/i2c/ov8856.c
+++ b/drivers/media/i2c/ov8856.c
@@ -428,7 +428,7 @@ static const struct ov8856_reg mode_3264x2448_regs[] = {
{0x3810, 0x00},
{0x3811, 0x04},
{0x3812, 0x00},
- {0x3813, 0x02},
+ {0x3813, 0x01},
{0x3814, 0x01},
{0x3815, 0x01},
{0x3816, 0x00},
@@ -821,7 +821,7 @@ static const struct ov8856_reg mode_1632x1224_regs[] = {
{0x3810, 0x00},
{0x3811, 0x02},
{0x3812, 0x00},
- {0x3813, 0x02},
+ {0x3813, 0x01},
{0x3814, 0x03},
{0x3815, 0x01},
{0x3816, 0x00},
diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c
new file mode 100644
index 000000000000..36a60fbc211d
--- /dev/null
+++ b/drivers/media/i2c/ov8865.c
@@ -0,0 +1,2972 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Kévin L'hôpital <kevin.lhopital@bootlin.com>
+ * Copyright 2020 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-image-sizes.h>
+#include <media/v4l2-mediabus.h>
+
+/* Clock rate */
+
+#define OV8865_EXTCLK_RATE 24000000
+
+/* Register definitions */
+
+/* System */
+
+#define OV8865_SW_STANDBY_REG 0x100
+#define OV8865_SW_STANDBY_STREAM_ON BIT(0)
+
+#define OV8865_SW_RESET_REG 0x103
+#define OV8865_SW_RESET_RESET BIT(0)
+
+#define OV8865_PLL_CTRL0_REG 0x300
+#define OV8865_PLL_CTRL0_PRE_DIV(v) ((v) & GENMASK(2, 0))
+#define OV8865_PLL_CTRL1_REG 0x301
+#define OV8865_PLL_CTRL1_MUL_H(v) (((v) & GENMASK(9, 8)) >> 8)
+#define OV8865_PLL_CTRL2_REG 0x302
+#define OV8865_PLL_CTRL2_MUL_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_PLL_CTRL3_REG 0x303
+#define OV8865_PLL_CTRL3_M_DIV(v) (((v) - 1) & GENMASK(3, 0))
+#define OV8865_PLL_CTRL4_REG 0x304
+#define OV8865_PLL_CTRL4_MIPI_DIV(v) ((v) & GENMASK(1, 0))
+#define OV8865_PLL_CTRL5_REG 0x305
+#define OV8865_PLL_CTRL5_SYS_PRE_DIV(v) ((v) & GENMASK(1, 0))
+#define OV8865_PLL_CTRL6_REG 0x306
+#define OV8865_PLL_CTRL6_SYS_DIV(v) (((v) - 1) & BIT(0))
+
+#define OV8865_PLL_CTRL8_REG 0x308
+#define OV8865_PLL_CTRL9_REG 0x309
+#define OV8865_PLL_CTRLA_REG 0x30a
+#define OV8865_PLL_CTRLA_PRE_DIV_HALF(v) (((v) - 1) & BIT(0))
+#define OV8865_PLL_CTRLB_REG 0x30b
+#define OV8865_PLL_CTRLB_PRE_DIV(v) ((v) & GENMASK(2, 0))
+#define OV8865_PLL_CTRLC_REG 0x30c
+#define OV8865_PLL_CTRLC_MUL_H(v) (((v) & GENMASK(9, 8)) >> 8)
+#define OV8865_PLL_CTRLD_REG 0x30d
+#define OV8865_PLL_CTRLD_MUL_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_PLL_CTRLE_REG 0x30e
+#define OV8865_PLL_CTRLE_SYS_DIV(v) ((v) & GENMASK(2, 0))
+#define OV8865_PLL_CTRLF_REG 0x30f
+#define OV8865_PLL_CTRLF_SYS_PRE_DIV(v) (((v) - 1) & GENMASK(3, 0))
+#define OV8865_PLL_CTRL10_REG 0x310
+#define OV8865_PLL_CTRL11_REG 0x311
+#define OV8865_PLL_CTRL12_REG 0x312
+#define OV8865_PLL_CTRL12_PRE_DIV_HALF(v) ((((v) - 1) << 4) & BIT(4))
+#define OV8865_PLL_CTRL12_DAC_DIV(v) (((v) - 1) & GENMASK(3, 0))
+
+#define OV8865_PLL_CTRL1B_REG 0x31b
+#define OV8865_PLL_CTRL1C_REG 0x31c
+
+#define OV8865_PLL_CTRL1E_REG 0x31e
+#define OV8865_PLL_CTRL1E_PLL1_NO_LAT BIT(3)
+
+#define OV8865_PAD_OEN0_REG 0x3000
+
+#define OV8865_PAD_OEN2_REG 0x3002
+
+#define OV8865_CLK_RST5_REG 0x3005
+
+#define OV8865_CHIP_ID_HH_REG 0x300a
+#define OV8865_CHIP_ID_HH_VALUE 0x00
+#define OV8865_CHIP_ID_H_REG 0x300b
+#define OV8865_CHIP_ID_H_VALUE 0x88
+#define OV8865_CHIP_ID_L_REG 0x300c
+#define OV8865_CHIP_ID_L_VALUE 0x65
+#define OV8865_PAD_OUT2_REG 0x300d
+
+#define OV8865_PAD_SEL2_REG 0x3010
+#define OV8865_PAD_PK_REG 0x3011
+#define OV8865_PAD_PK_DRIVE_STRENGTH_1X (0 << 5)
+#define OV8865_PAD_PK_DRIVE_STRENGTH_2X (1 << 5)
+#define OV8865_PAD_PK_DRIVE_STRENGTH_3X (2 << 5)
+#define OV8865_PAD_PK_DRIVE_STRENGTH_4X (3 << 5)
+
+#define OV8865_PUMP_CLK_DIV_REG 0x3015
+#define OV8865_PUMP_CLK_DIV_PUMP_N(v) (((v) << 4) & GENMASK(6, 4))
+#define OV8865_PUMP_CLK_DIV_PUMP_P(v) ((v) & GENMASK(2, 0))
+
+#define OV8865_MIPI_SC_CTRL0_REG 0x3018
+#define OV8865_MIPI_SC_CTRL0_LANES(v) ((((v) - 1) << 5) & \
+ GENMASK(7, 5))
+#define OV8865_MIPI_SC_CTRL0_MIPI_EN BIT(4)
+#define OV8865_MIPI_SC_CTRL0_UNKNOWN BIT(1)
+#define OV8865_MIPI_SC_CTRL0_LANES_PD_MIPI BIT(0)
+#define OV8865_MIPI_SC_CTRL1_REG 0x3019
+#define OV8865_CLK_RST0_REG 0x301a
+#define OV8865_CLK_RST1_REG 0x301b
+#define OV8865_CLK_RST2_REG 0x301c
+#define OV8865_CLK_RST3_REG 0x301d
+#define OV8865_CLK_RST4_REG 0x301e
+
+#define OV8865_PCLK_SEL_REG 0x3020
+#define OV8865_PCLK_SEL_PCLK_DIV_MASK BIT(3)
+#define OV8865_PCLK_SEL_PCLK_DIV(v) ((((v) - 1) << 3) & BIT(3))
+
+#define OV8865_MISC_CTRL_REG 0x3021
+#define OV8865_MIPI_SC_CTRL2_REG 0x3022
+#define OV8865_MIPI_SC_CTRL2_CLK_LANES_PD_MIPI BIT(1)
+#define OV8865_MIPI_SC_CTRL2_PD_MIPI_RST_SYNC BIT(0)
+
+#define OV8865_MIPI_BIT_SEL_REG 0x3031
+#define OV8865_MIPI_BIT_SEL(v) (((v) << 0) & GENMASK(4, 0))
+#define OV8865_CLK_SEL0_REG 0x3032
+#define OV8865_CLK_SEL0_PLL1_SYS_SEL(v) (((v) << 7) & BIT(7))
+#define OV8865_CLK_SEL1_REG 0x3033
+#define OV8865_CLK_SEL1_MIPI_EOF BIT(5)
+#define OV8865_CLK_SEL1_UNKNOWN BIT(2)
+#define OV8865_CLK_SEL1_PLL_SCLK_SEL_MASK BIT(1)
+#define OV8865_CLK_SEL1_PLL_SCLK_SEL(v) (((v) << 1) & BIT(1))
+
+#define OV8865_SCLK_CTRL_REG 0x3106
+#define OV8865_SCLK_CTRL_SCLK_DIV(v) (((v) << 4) & GENMASK(7, 4))
+#define OV8865_SCLK_CTRL_SCLK_PRE_DIV(v) (((v) << 2) & GENMASK(3, 2))
+#define OV8865_SCLK_CTRL_UNKNOWN BIT(0)
+
+/* Exposure/gain */
+
+#define OV8865_EXPOSURE_CTRL_HH_REG 0x3500
+#define OV8865_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(19, 16)) >> 16)
+#define OV8865_EXPOSURE_CTRL_H_REG 0x3501
+#define OV8865_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_EXPOSURE_CTRL_L_REG 0x3502
+#define OV8865_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_EXPOSURE_GAIN_MANUAL_REG 0x3503
+
+#define OV8865_GAIN_CTRL_H_REG 0x3508
+#define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8)
+#define OV8865_GAIN_CTRL_L_REG 0x3509
+#define OV8865_GAIN_CTRL_L(v) ((v) & GENMASK(7, 0))
+
+/* Timing */
+
+#define OV8865_CROP_START_X_H_REG 0x3800
+#define OV8865_CROP_START_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_CROP_START_X_L_REG 0x3801
+#define OV8865_CROP_START_X_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_CROP_START_Y_H_REG 0x3802
+#define OV8865_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_CROP_START_Y_L_REG 0x3803
+#define OV8865_CROP_START_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_CROP_END_X_H_REG 0x3804
+#define OV8865_CROP_END_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_CROP_END_X_L_REG 0x3805
+#define OV8865_CROP_END_X_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_CROP_END_Y_H_REG 0x3806
+#define OV8865_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_CROP_END_Y_L_REG 0x3807
+#define OV8865_CROP_END_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_OUTPUT_SIZE_X_H_REG 0x3808
+#define OV8865_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_OUTPUT_SIZE_X_L_REG 0x3809
+#define OV8865_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_OUTPUT_SIZE_Y_H_REG 0x380a
+#define OV8865_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_OUTPUT_SIZE_Y_L_REG 0x380b
+#define OV8865_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_HTS_H_REG 0x380c
+#define OV8865_HTS_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_HTS_L_REG 0x380d
+#define OV8865_HTS_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_VTS_H_REG 0x380e
+#define OV8865_VTS_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_VTS_L_REG 0x380f
+#define OV8865_VTS_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_OFFSET_X_H_REG 0x3810
+#define OV8865_OFFSET_X_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_OFFSET_X_L_REG 0x3811
+#define OV8865_OFFSET_X_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_OFFSET_Y_H_REG 0x3812
+#define OV8865_OFFSET_Y_H(v) (((v) & GENMASK(14, 8)) >> 8)
+#define OV8865_OFFSET_Y_L_REG 0x3813
+#define OV8865_OFFSET_Y_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_INC_X_ODD_REG 0x3814
+#define OV8865_INC_X_ODD(v) ((v) & GENMASK(4, 0))
+#define OV8865_INC_X_EVEN_REG 0x3815
+#define OV8865_INC_X_EVEN(v) ((v) & GENMASK(4, 0))
+#define OV8865_VSYNC_START_H_REG 0x3816
+#define OV8865_VSYNC_START_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_VSYNC_START_L_REG 0x3817
+#define OV8865_VSYNC_START_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_VSYNC_END_H_REG 0x3818
+#define OV8865_VSYNC_END_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_VSYNC_END_L_REG 0x3819
+#define OV8865_VSYNC_END_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_HSYNC_FIRST_H_REG 0x381a
+#define OV8865_HSYNC_FIRST_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_HSYNC_FIRST_L_REG 0x381b
+#define OV8865_HSYNC_FIRST_L(v) ((v) & GENMASK(7, 0))
+
+#define OV8865_FORMAT1_REG 0x3820
+#define OV8865_FORMAT1_FLIP_VERT_ISP_EN BIT(2)
+#define OV8865_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1)
+#define OV8865_FORMAT2_REG 0x3821
+#define OV8865_FORMAT2_HSYNC_EN BIT(6)
+#define OV8865_FORMAT2_FST_VBIN_EN BIT(5)
+#define OV8865_FORMAT2_FST_HBIN_EN BIT(4)
+#define OV8865_FORMAT2_ISP_HORZ_VAR2_EN BIT(3)
+#define OV8865_FORMAT2_FLIP_HORZ_ISP_EN BIT(2)
+#define OV8865_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1)
+#define OV8865_FORMAT2_SYNC_HBIN_EN BIT(0)
+
+#define OV8865_INC_Y_ODD_REG 0x382a
+#define OV8865_INC_Y_ODD(v) ((v) & GENMASK(4, 0))
+#define OV8865_INC_Y_EVEN_REG 0x382b
+#define OV8865_INC_Y_EVEN(v) ((v) & GENMASK(4, 0))
+
+#define OV8865_ABLC_NUM_REG 0x3830
+#define OV8865_ABLC_NUM(v) ((v) & GENMASK(4, 0))
+
+#define OV8865_ZLINE_NUM_REG 0x3836
+#define OV8865_ZLINE_NUM(v) ((v) & GENMASK(4, 0))
+
+#define OV8865_AUTO_SIZE_CTRL_REG 0x3841
+#define OV8865_AUTO_SIZE_CTRL_OFFSET_Y_REG BIT(5)
+#define OV8865_AUTO_SIZE_CTRL_OFFSET_X_REG BIT(4)
+#define OV8865_AUTO_SIZE_CTRL_CROP_END_Y_REG BIT(3)
+#define OV8865_AUTO_SIZE_CTRL_CROP_END_X_REG BIT(2)
+#define OV8865_AUTO_SIZE_CTRL_CROP_START_Y_REG BIT(1)
+#define OV8865_AUTO_SIZE_CTRL_CROP_START_X_REG BIT(0)
+#define OV8865_AUTO_SIZE_X_OFFSET_H_REG 0x3842
+#define OV8865_AUTO_SIZE_X_OFFSET_L_REG 0x3843
+#define OV8865_AUTO_SIZE_Y_OFFSET_H_REG 0x3844
+#define OV8865_AUTO_SIZE_Y_OFFSET_L_REG 0x3845
+#define OV8865_AUTO_SIZE_BOUNDARIES_REG 0x3846
+#define OV8865_AUTO_SIZE_BOUNDARIES_Y(v) (((v) << 4) & GENMASK(7, 4))
+#define OV8865_AUTO_SIZE_BOUNDARIES_X(v) ((v) & GENMASK(3, 0))
+
+/* PSRAM */
+
+#define OV8865_PSRAM_CTRL8_REG 0x3f08
+
+/* Black Level */
+
+#define OV8865_BLC_CTRL0_REG 0x4000
+#define OV8865_BLC_CTRL0_TRIG_RANGE_EN BIT(7)
+#define OV8865_BLC_CTRL0_TRIG_FORMAT_EN BIT(6)
+#define OV8865_BLC_CTRL0_TRIG_GAIN_EN BIT(5)
+#define OV8865_BLC_CTRL0_TRIG_EXPOSURE_EN BIT(4)
+#define OV8865_BLC_CTRL0_TRIG_MANUAL_EN BIT(3)
+#define OV8865_BLC_CTRL0_FREEZE_EN BIT(2)
+#define OV8865_BLC_CTRL0_ALWAYS_EN BIT(1)
+#define OV8865_BLC_CTRL0_FILTER_EN BIT(0)
+#define OV8865_BLC_CTRL1_REG 0x4001
+#define OV8865_BLC_CTRL1_DITHER_EN BIT(7)
+#define OV8865_BLC_CTRL1_ZERO_LINE_DIFF_EN BIT(6)
+#define OV8865_BLC_CTRL1_COL_SHIFT_256 (0 << 4)
+#define OV8865_BLC_CTRL1_COL_SHIFT_128 (1 << 4)
+#define OV8865_BLC_CTRL1_COL_SHIFT_64 (2 << 4)
+#define OV8865_BLC_CTRL1_COL_SHIFT_32 (3 << 4)
+#define OV8865_BLC_CTRL1_OFFSET_LIMIT_EN BIT(2)
+#define OV8865_BLC_CTRL1_COLUMN_CANCEL_EN BIT(1)
+#define OV8865_BLC_CTRL2_REG 0x4002
+#define OV8865_BLC_CTRL3_REG 0x4003
+#define OV8865_BLC_CTRL4_REG 0x4004
+#define OV8865_BLC_CTRL5_REG 0x4005
+#define OV8865_BLC_CTRL6_REG 0x4006
+#define OV8865_BLC_CTRL7_REG 0x4007
+#define OV8865_BLC_CTRL8_REG 0x4008
+#define OV8865_BLC_CTRL9_REG 0x4009
+#define OV8865_BLC_CTRLA_REG 0x400a
+#define OV8865_BLC_CTRLB_REG 0x400b
+#define OV8865_BLC_CTRLC_REG 0x400c
+#define OV8865_BLC_CTRLD_REG 0x400d
+#define OV8865_BLC_CTRLD_OFFSET_TRIGGER(v) ((v) & GENMASK(7, 0))
+
+#define OV8865_BLC_CTRL1F_REG 0x401f
+#define OV8865_BLC_CTRL1F_RB_REVERSE BIT(3)
+#define OV8865_BLC_CTRL1F_INTERPOL_X_EN BIT(2)
+#define OV8865_BLC_CTRL1F_INTERPOL_Y_EN BIT(1)
+
+#define OV8865_BLC_ANCHOR_LEFT_START_H_REG 0x4020
+#define OV8865_BLC_ANCHOR_LEFT_START_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_BLC_ANCHOR_LEFT_START_L_REG 0x4021
+#define OV8865_BLC_ANCHOR_LEFT_START_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_BLC_ANCHOR_LEFT_END_H_REG 0x4022
+#define OV8865_BLC_ANCHOR_LEFT_END_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_BLC_ANCHOR_LEFT_END_L_REG 0x4023
+#define OV8865_BLC_ANCHOR_LEFT_END_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_BLC_ANCHOR_RIGHT_START_H_REG 0x4024
+#define OV8865_BLC_ANCHOR_RIGHT_START_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_BLC_ANCHOR_RIGHT_START_L_REG 0x4025
+#define OV8865_BLC_ANCHOR_RIGHT_START_L(v) ((v) & GENMASK(7, 0))
+#define OV8865_BLC_ANCHOR_RIGHT_END_H_REG 0x4026
+#define OV8865_BLC_ANCHOR_RIGHT_END_H(v) (((v) & GENMASK(11, 8)) >> 8)
+#define OV8865_BLC_ANCHOR_RIGHT_END_L_REG 0x4027
+#define OV8865_BLC_ANCHOR_RIGHT_END_L(v) ((v) & GENMASK(7, 0))
+
+#define OV8865_BLC_TOP_ZLINE_START_REG 0x4028
+#define OV8865_BLC_TOP_ZLINE_START(v) ((v) & GENMASK(5, 0))
+#define OV8865_BLC_TOP_ZLINE_NUM_REG 0x4029
+#define OV8865_BLC_TOP_ZLINE_NUM(v) ((v) & GENMASK(4, 0))
+#define OV8865_BLC_TOP_BLKLINE_START_REG 0x402a
+#define OV8865_BLC_TOP_BLKLINE_START(v) ((v) & GENMASK(5, 0))
+#define OV8865_BLC_TOP_BLKLINE_NUM_REG 0x402b
+#define OV8865_BLC_TOP_BLKLINE_NUM(v) ((v) & GENMASK(4, 0))
+#define OV8865_BLC_BOT_ZLINE_START_REG 0x402c
+#define OV8865_BLC_BOT_ZLINE_START(v) ((v) & GENMASK(5, 0))
+#define OV8865_BLC_BOT_ZLINE_NUM_REG 0x402d
+#define OV8865_BLC_BOT_ZLINE_NUM(v) ((v) & GENMASK(4, 0))
+#define OV8865_BLC_BOT_BLKLINE_START_REG 0x402e
+#define OV8865_BLC_BOT_BLKLINE_START(v) ((v) & GENMASK(5, 0))
+#define OV8865_BLC_BOT_BLKLINE_NUM_REG 0x402f
+#define OV8865_BLC_BOT_BLKLINE_NUM(v) ((v) & GENMASK(4, 0))
+
+#define OV8865_BLC_OFFSET_LIMIT_REG 0x4034
+#define OV8865_BLC_OFFSET_LIMIT(v) ((v) & GENMASK(7, 0))
+
+/* VFIFO */
+
+#define OV8865_VFIFO_READ_START_H_REG 0x4600
+#define OV8865_VFIFO_READ_START_H(v) (((v) & GENMASK(15, 8)) >> 8)
+#define OV8865_VFIFO_READ_START_L_REG 0x4601
+#define OV8865_VFIFO_READ_START_L(v) ((v) & GENMASK(7, 0))
+
+/* MIPI */
+
+#define OV8865_MIPI_CTRL0_REG 0x4800
+#define OV8865_MIPI_CTRL1_REG 0x4801
+#define OV8865_MIPI_CTRL2_REG 0x4802
+#define OV8865_MIPI_CTRL3_REG 0x4803
+#define OV8865_MIPI_CTRL4_REG 0x4804
+#define OV8865_MIPI_CTRL5_REG 0x4805
+#define OV8865_MIPI_CTRL6_REG 0x4806
+#define OV8865_MIPI_CTRL7_REG 0x4807
+#define OV8865_MIPI_CTRL8_REG 0x4808
+
+#define OV8865_MIPI_FCNT_MAX_H_REG 0x4810
+#define OV8865_MIPI_FCNT_MAX_L_REG 0x4811
+
+#define OV8865_MIPI_CTRL13_REG 0x4813
+#define OV8865_MIPI_CTRL14_REG 0x4814
+#define OV8865_MIPI_CTRL15_REG 0x4815
+#define OV8865_MIPI_EMBEDDED_DT_REG 0x4816
+
+#define OV8865_MIPI_HS_ZERO_MIN_H_REG 0x4818
+#define OV8865_MIPI_HS_ZERO_MIN_L_REG 0x4819
+#define OV8865_MIPI_HS_TRAIL_MIN_H_REG 0x481a
+#define OV8865_MIPI_HS_TRAIL_MIN_L_REG 0x481b
+#define OV8865_MIPI_CLK_ZERO_MIN_H_REG 0x481c
+#define OV8865_MIPI_CLK_ZERO_MIN_L_REG 0x481d
+#define OV8865_MIPI_CLK_PREPARE_MAX_REG 0x481e
+#define OV8865_MIPI_CLK_PREPARE_MIN_REG 0x481f
+#define OV8865_MIPI_CLK_POST_MIN_H_REG 0x4820
+#define OV8865_MIPI_CLK_POST_MIN_L_REG 0x4821
+#define OV8865_MIPI_CLK_TRAIL_MIN_H_REG 0x4822
+#define OV8865_MIPI_CLK_TRAIL_MIN_L_REG 0x4823
+#define OV8865_MIPI_LPX_P_MIN_H_REG 0x4824
+#define OV8865_MIPI_LPX_P_MIN_L_REG 0x4825
+#define OV8865_MIPI_HS_PREPARE_MIN_REG 0x4826
+#define OV8865_MIPI_HS_PREPARE_MAX_REG 0x4827
+#define OV8865_MIPI_HS_EXIT_MIN_H_REG 0x4828
+#define OV8865_MIPI_HS_EXIT_MIN_L_REG 0x4829
+#define OV8865_MIPI_UI_HS_ZERO_MIN_REG 0x482a
+#define OV8865_MIPI_UI_HS_TRAIL_MIN_REG 0x482b
+#define OV8865_MIPI_UI_CLK_ZERO_MIN_REG 0x482c
+#define OV8865_MIPI_UI_CLK_PREPARE_REG 0x482d
+#define OV8865_MIPI_UI_CLK_POST_MIN_REG 0x482e
+#define OV8865_MIPI_UI_CLK_TRAIL_MIN_REG 0x482f
+#define OV8865_MIPI_UI_LPX_P_MIN_REG 0x4830
+#define OV8865_MIPI_UI_HS_PREPARE_REG 0x4831
+#define OV8865_MIPI_UI_HS_EXIT_MIN_REG 0x4832
+#define OV8865_MIPI_PKT_START_SIZE_REG 0x4833
+
+#define OV8865_MIPI_PCLK_PERIOD_REG 0x4837
+#define OV8865_MIPI_LP_GPIO0_REG 0x4838
+#define OV8865_MIPI_LP_GPIO1_REG 0x4839
+
+#define OV8865_MIPI_CTRL3C_REG 0x483c
+#define OV8865_MIPI_LP_GPIO4_REG 0x483d
+
+#define OV8865_MIPI_CTRL4A_REG 0x484a
+#define OV8865_MIPI_CTRL4B_REG 0x484b
+#define OV8865_MIPI_CTRL4C_REG 0x484c
+#define OV8865_MIPI_LANE_TEST_PATTERN_REG 0x484d
+#define OV8865_MIPI_FRAME_END_DELAY_REG 0x484e
+#define OV8865_MIPI_CLOCK_TEST_PATTERN_REG 0x484f
+#define OV8865_MIPI_LANE_SEL01_REG 0x4850
+#define OV8865_MIPI_LANE_SEL01_LANE0(v) (((v) << 0) & GENMASK(2, 0))
+#define OV8865_MIPI_LANE_SEL01_LANE1(v) (((v) << 4) & GENMASK(6, 4))
+#define OV8865_MIPI_LANE_SEL23_REG 0x4851
+#define OV8865_MIPI_LANE_SEL23_LANE2(v) (((v) << 0) & GENMASK(2, 0))
+#define OV8865_MIPI_LANE_SEL23_LANE3(v) (((v) << 4) & GENMASK(6, 4))
+
+/* ISP */
+
+#define OV8865_ISP_CTRL0_REG 0x5000
+#define OV8865_ISP_CTRL0_LENC_EN BIT(7)
+#define OV8865_ISP_CTRL0_WHITE_BALANCE_EN BIT(4)
+#define OV8865_ISP_CTRL0_DPC_BLACK_EN BIT(2)
+#define OV8865_ISP_CTRL0_DPC_WHITE_EN BIT(1)
+#define OV8865_ISP_CTRL1_REG 0x5001
+#define OV8865_ISP_CTRL1_BLC_EN BIT(0)
+#define OV8865_ISP_CTRL2_REG 0x5002
+#define OV8865_ISP_CTRL2_DEBUG BIT(3)
+#define OV8865_ISP_CTRL2_VARIOPIXEL_EN BIT(2)
+#define OV8865_ISP_CTRL2_VSYNC_LATCH_EN BIT(0)
+#define OV8865_ISP_CTRL3_REG 0x5003
+
+#define OV8865_ISP_GAIN_RED_H_REG 0x5018
+#define OV8865_ISP_GAIN_RED_H(v) (((v) & GENMASK(13, 6)) >> 6)
+#define OV8865_ISP_GAIN_RED_L_REG 0x5019
+#define OV8865_ISP_GAIN_RED_L(v) ((v) & GENMASK(5, 0))
+#define OV8865_ISP_GAIN_GREEN_H_REG 0x501a
+#define OV8865_ISP_GAIN_GREEN_H(v) (((v) & GENMASK(13, 6)) >> 6)
+#define OV8865_ISP_GAIN_GREEN_L_REG 0x501b
+#define OV8865_ISP_GAIN_GREEN_L(v) ((v) & GENMASK(5, 0))
+#define OV8865_ISP_GAIN_BLUE_H_REG 0x501c
+#define OV8865_ISP_GAIN_BLUE_H(v) (((v) & GENMASK(13, 6)) >> 6)
+#define OV8865_ISP_GAIN_BLUE_L_REG 0x501d
+#define OV8865_ISP_GAIN_BLUE_L(v) ((v) & GENMASK(5, 0))
+
+/* VarioPixel */
+
+#define OV8865_VAP_CTRL0_REG 0x5900
+#define OV8865_VAP_CTRL1_REG 0x5901
+#define OV8865_VAP_CTRL1_HSUB_COEF(v) ((((v) - 1) << 2) & \
+ GENMASK(3, 2))
+#define OV8865_VAP_CTRL1_VSUB_COEF(v) (((v) - 1) & GENMASK(1, 0))
+
+/* Pre-DSP */
+
+#define OV8865_PRE_CTRL0_REG 0x5e00
+#define OV8865_PRE_CTRL0_PATTERN_EN BIT(7)
+#define OV8865_PRE_CTRL0_ROLLING_BAR_EN BIT(6)
+#define OV8865_PRE_CTRL0_TRANSPARENT_MODE BIT(5)
+#define OV8865_PRE_CTRL0_SQUARES_BW_MODE BIT(4)
+#define OV8865_PRE_CTRL0_PATTERN_COLOR_BARS 0
+#define OV8865_PRE_CTRL0_PATTERN_RANDOM_DATA 1
+#define OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES 2
+#define OV8865_PRE_CTRL0_PATTERN_BLACK 3
+
+/* Macros */
+
+#define ov8865_subdev_sensor(s) \
+ container_of(s, struct ov8865_sensor, subdev)
+
+#define ov8865_ctrl_subdev(c) \
+ (&container_of((c)->handler, struct ov8865_sensor, \
+ ctrls.handler)->subdev)
+
+/* Data structures */
+
+struct ov8865_register_value {
+ u16 address;
+ u8 value;
+ unsigned int delay_ms;
+};
+
+/*
+ * PLL1 Clock Tree:
+ *
+ * +-< EXTCLK
+ * |
+ * +-+ pll_pre_div_half (0x30a [0])
+ * |
+ * +-+ pll_pre_div (0x300 [2:0], special values:
+ * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 4, 7: 8)
+ * +-+ pll_mul (0x301 [1:0], 0x302 [7:0])
+ * |
+ * +-+ m_div (0x303 [3:0])
+ * | |
+ * | +-> PHY_SCLK
+ * | |
+ * | +-+ mipi_div (0x304 [1:0], special values: 0: 4, 1: 5, 2: 6, 3: 8)
+ * | |
+ * | +-+ pclk_div (0x3020 [3])
+ * | |
+ * | +-> PCLK
+ * |
+ * +-+ sys_pre_div (0x305 [1:0], special values: 0: 3, 1: 4, 2: 5, 3: 6)
+ * |
+ * +-+ sys_div (0x306 [0])
+ * |
+ * +-+ sys_sel (0x3032 [7], 0: PLL1, 1: PLL2)
+ * |
+ * +-+ sclk_sel (0x3033 [1], 0: sys_sel, 1: PLL2 DAC_CLK)
+ * |
+ * +-+ sclk_pre_div (0x3106 [3:2], special values:
+ * | 0: 1, 1: 2, 2: 4, 3: 1)
+ * |
+ * +-+ sclk_div (0x3106 [7:4], special values: 0: 1)
+ * |
+ * +-> SCLK
+ */
+
+struct ov8865_pll1_config {
+ unsigned int pll_pre_div_half;
+ unsigned int pll_pre_div;
+ unsigned int pll_mul;
+ unsigned int m_div;
+ unsigned int mipi_div;
+ unsigned int pclk_div;
+ unsigned int sys_pre_div;
+ unsigned int sys_div;
+};
+
+/*
+ * PLL2 Clock Tree:
+ *
+ * +-< EXTCLK
+ * |
+ * +-+ pll_pre_div_half (0x312 [4])
+ * |
+ * +-+ pll_pre_div (0x30b [2:0], special values:
+ * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 4, 7: 8)
+ * +-+ pll_mul (0x30c [1:0], 0x30d [7:0])
+ * |
+ * +-+ dac_div (0x312 [3:0])
+ * | |
+ * | +-> DAC_CLK
+ * |
+ * +-+ sys_pre_div (0x30f [3:0])
+ * |
+ * +-+ sys_div (0x30e [2:0], special values:
+ * | 0: 1, 1: 1.5, 3: 2.5, 4: 3, 5: 3.5, 6: 4, 7:5)
+ * |
+ * +-+ sys_sel (0x3032 [7], 0: PLL1, 1: PLL2)
+ * |
+ * +-+ sclk_sel (0x3033 [1], 0: sys_sel, 1: PLL2 DAC_CLK)
+ * |
+ * +-+ sclk_pre_div (0x3106 [3:2], special values:
+ * | 0: 1, 1: 2, 2: 4, 3: 1)
+ * |
+ * +-+ sclk_div (0x3106 [7:4], special values: 0: 1)
+ * |
+ * +-> SCLK
+ */
+
+struct ov8865_pll2_config {
+ unsigned int pll_pre_div_half;
+ unsigned int pll_pre_div;
+ unsigned int pll_mul;
+ unsigned int dac_div;
+ unsigned int sys_pre_div;
+ unsigned int sys_div;
+};
+
+struct ov8865_sclk_config {
+ unsigned int sys_sel;
+ unsigned int sclk_sel;
+ unsigned int sclk_pre_div;
+ unsigned int sclk_div;
+};
+
+/*
+ * General formulas for (array-centered) mode calculation:
+ * - photo_array_width = 3296
+ * - crop_start_x = (photo_array_width - output_size_x) / 2
+ * - crop_end_x = crop_start_x + offset_x + output_size_x - 1
+ *
+ * - photo_array_height = 2480
+ * - crop_start_y = (photo_array_height - output_size_y) / 2
+ * - crop_end_y = crop_start_y + offset_y + output_size_y - 1
+ */
+
+struct ov8865_mode {
+ unsigned int crop_start_x;
+ unsigned int offset_x;
+ unsigned int output_size_x;
+ unsigned int crop_end_x;
+ unsigned int hts;
+
+ unsigned int crop_start_y;
+ unsigned int offset_y;
+ unsigned int output_size_y;
+ unsigned int crop_end_y;
+ unsigned int vts;
+
+ /* With auto size, only output and total sizes need to be set. */
+ bool size_auto;
+ unsigned int size_auto_boundary_x;
+ unsigned int size_auto_boundary_y;
+
+ bool binning_x;
+ bool binning_y;
+ bool variopixel;
+ unsigned int variopixel_hsub_coef;
+ unsigned int variopixel_vsub_coef;
+
+ /* Bits for the format register, used for binning. */
+ bool sync_hbin;
+ bool horz_var2;
+
+ unsigned int inc_x_odd;
+ unsigned int inc_x_even;
+ unsigned int inc_y_odd;
+ unsigned int inc_y_even;
+
+ unsigned int vfifo_read_start;
+
+ unsigned int ablc_num;
+ unsigned int zline_num;
+
+ unsigned int blc_top_zero_line_start;
+ unsigned int blc_top_zero_line_num;
+ unsigned int blc_top_black_line_start;
+ unsigned int blc_top_black_line_num;
+
+ unsigned int blc_bottom_zero_line_start;
+ unsigned int blc_bottom_zero_line_num;
+ unsigned int blc_bottom_black_line_start;
+ unsigned int blc_bottom_black_line_num;
+
+ u8 blc_col_shift_mask;
+
+ unsigned int blc_anchor_left_start;
+ unsigned int blc_anchor_left_end;
+ unsigned int blc_anchor_right_start;
+ unsigned int blc_anchor_right_end;
+
+ struct v4l2_fract frame_interval;
+
+ const struct ov8865_pll1_config *pll1_config;
+ const struct ov8865_pll2_config *pll2_config;
+ const struct ov8865_sclk_config *sclk_config;
+
+ const struct ov8865_register_value *register_values;
+ unsigned int register_values_count;
+};
+
+struct ov8865_state {
+ const struct ov8865_mode *mode;
+ u32 mbus_code;
+
+ bool streaming;
+};
+
+struct ov8865_ctrls {
+ struct v4l2_ctrl *link_freq;
+ struct v4l2_ctrl *pixel_rate;
+
+ struct v4l2_ctrl_handler handler;
+};
+
+struct ov8865_sensor {
+ struct device *dev;
+ struct i2c_client *i2c_client;
+ struct gpio_desc *reset;
+ struct gpio_desc *powerdown;
+ struct regulator *avdd;
+ struct regulator *dvdd;
+ struct regulator *dovdd;
+ struct clk *extclk;
+
+ struct v4l2_fwnode_endpoint endpoint;
+ struct v4l2_subdev subdev;
+ struct media_pad pad;
+
+ struct mutex mutex;
+
+ struct ov8865_state state;
+ struct ov8865_ctrls ctrls;
+};
+
+/* Static definitions */
+
+/*
+ * EXTCLK = 24 MHz
+ * PHY_SCLK = 720 MHz
+ * MIPI_PCLK = 90 MHz
+ */
+static const struct ov8865_pll1_config ov8865_pll1_config_native = {
+ .pll_pre_div_half = 1,
+ .pll_pre_div = 0,
+ .pll_mul = 30,
+ .m_div = 1,
+ .mipi_div = 3,
+ .pclk_div = 1,
+ .sys_pre_div = 1,
+ .sys_div = 2,
+};
+
+/*
+ * EXTCLK = 24 MHz
+ * DAC_CLK = 360 MHz
+ * SCLK = 144 MHz
+ */
+
+static const struct ov8865_pll2_config ov8865_pll2_config_native = {
+ .pll_pre_div_half = 1,
+ .pll_pre_div = 0,
+ .pll_mul = 30,
+ .dac_div = 2,
+ .sys_pre_div = 5,
+ .sys_div = 0,
+};
+
+/*
+ * EXTCLK = 24 MHz
+ * DAC_CLK = 360 MHz
+ * SCLK = 80 MHz
+ */
+
+static const struct ov8865_pll2_config ov8865_pll2_config_binning = {
+ .pll_pre_div_half = 1,
+ .pll_pre_div = 0,
+ .pll_mul = 30,
+ .dac_div = 2,
+ .sys_pre_div = 10,
+ .sys_div = 0,
+};
+
+static const struct ov8865_sclk_config ov8865_sclk_config_native = {
+ .sys_sel = 1,
+ .sclk_sel = 0,
+ .sclk_pre_div = 0,
+ .sclk_div = 0,
+};
+
+static const struct ov8865_register_value ov8865_register_values_native[] = {
+ /* Sensor */
+
+ { 0x3700, 0x48 },
+ { 0x3701, 0x18 },
+ { 0x3702, 0x50 },
+ { 0x3703, 0x32 },
+ { 0x3704, 0x28 },
+ { 0x3706, 0x70 },
+ { 0x3707, 0x08 },
+ { 0x3708, 0x48 },
+ { 0x3709, 0x80 },
+ { 0x370a, 0x01 },
+ { 0x370b, 0x70 },
+ { 0x370c, 0x07 },
+ { 0x3718, 0x14 },
+ { 0x3712, 0x44 },
+ { 0x371e, 0x31 },
+ { 0x371f, 0x7f },
+ { 0x3720, 0x0a },
+ { 0x3721, 0x0a },
+ { 0x3724, 0x04 },
+ { 0x3725, 0x04 },
+ { 0x3726, 0x0c },
+ { 0x3728, 0x0a },
+ { 0x3729, 0x03 },
+ { 0x372a, 0x06 },
+ { 0x372b, 0xa6 },
+ { 0x372c, 0xa6 },
+ { 0x372d, 0xa6 },
+ { 0x372e, 0x0c },
+ { 0x372f, 0x20 },
+ { 0x3730, 0x02 },
+ { 0x3731, 0x0c },
+ { 0x3732, 0x28 },
+ { 0x3736, 0x30 },
+ { 0x373a, 0x04 },
+ { 0x373b, 0x18 },
+ { 0x373c, 0x14 },
+ { 0x373e, 0x06 },
+ { 0x375a, 0x0c },
+ { 0x375b, 0x26 },
+ { 0x375d, 0x04 },
+ { 0x375f, 0x28 },
+ { 0x3767, 0x1e },
+ { 0x3772, 0x46 },
+ { 0x3773, 0x04 },
+ { 0x3774, 0x2c },
+ { 0x3775, 0x13 },
+ { 0x3776, 0x10 },
+ { 0x37a0, 0x88 },
+ { 0x37a1, 0x7a },
+ { 0x37a2, 0x7a },
+ { 0x37a3, 0x02 },
+ { 0x37a5, 0x09 },
+ { 0x37a7, 0x88 },
+ { 0x37a8, 0xb0 },
+ { 0x37a9, 0xb0 },
+ { 0x37aa, 0x88 },
+ { 0x37ab, 0x5c },
+ { 0x37ac, 0x5c },
+ { 0x37ad, 0x55 },
+ { 0x37ae, 0x19 },
+ { 0x37af, 0x19 },
+ { 0x37b3, 0x84 },
+ { 0x37b4, 0x84 },
+ { 0x37b5, 0x66 },
+
+ /* PSRAM */
+
+ { OV8865_PSRAM_CTRL8_REG, 0x16 },
+
+ /* ADC Sync */
+
+ { 0x4500, 0x68 },
+};
+
+static const struct ov8865_register_value ov8865_register_values_binning[] = {
+ /* Sensor */
+
+ { 0x3700, 0x24 },
+ { 0x3701, 0x0c },
+ { 0x3702, 0x28 },
+ { 0x3703, 0x19 },
+ { 0x3704, 0x14 },
+ { 0x3706, 0x38 },
+ { 0x3707, 0x04 },
+ { 0x3708, 0x24 },
+ { 0x3709, 0x40 },
+ { 0x370a, 0x00 },
+ { 0x370b, 0xb8 },
+ { 0x370c, 0x04 },
+ { 0x3718, 0x12 },
+ { 0x3712, 0x42 },
+ { 0x371e, 0x19 },
+ { 0x371f, 0x40 },
+ { 0x3720, 0x05 },
+ { 0x3721, 0x05 },
+ { 0x3724, 0x02 },
+ { 0x3725, 0x02 },
+ { 0x3726, 0x06 },
+ { 0x3728, 0x05 },
+ { 0x3729, 0x02 },
+ { 0x372a, 0x03 },
+ { 0x372b, 0x53 },
+ { 0x372c, 0xa3 },
+ { 0x372d, 0x53 },
+ { 0x372e, 0x06 },
+ { 0x372f, 0x10 },
+ { 0x3730, 0x01 },
+ { 0x3731, 0x06 },
+ { 0x3732, 0x14 },
+ { 0x3736, 0x20 },
+ { 0x373a, 0x02 },
+ { 0x373b, 0x0c },
+ { 0x373c, 0x0a },
+ { 0x373e, 0x03 },
+ { 0x375a, 0x06 },
+ { 0x375b, 0x13 },
+ { 0x375d, 0x02 },
+ { 0x375f, 0x14 },
+ { 0x3767, 0x1c },
+ { 0x3772, 0x23 },
+ { 0x3773, 0x02 },
+ { 0x3774, 0x16 },
+ { 0x3775, 0x12 },
+ { 0x3776, 0x08 },
+ { 0x37a0, 0x44 },
+ { 0x37a1, 0x3d },
+ { 0x37a2, 0x3d },
+ { 0x37a3, 0x01 },
+ { 0x37a5, 0x08 },
+ { 0x37a7, 0x44 },
+ { 0x37a8, 0x58 },
+ { 0x37a9, 0x58 },
+ { 0x37aa, 0x44 },
+ { 0x37ab, 0x2e },
+ { 0x37ac, 0x2e },
+ { 0x37ad, 0x33 },
+ { 0x37ae, 0x0d },
+ { 0x37af, 0x0d },
+ { 0x37b3, 0x42 },
+ { 0x37b4, 0x42 },
+ { 0x37b5, 0x33 },
+
+ /* PSRAM */
+
+ { OV8865_PSRAM_CTRL8_REG, 0x0b },
+
+ /* ADC Sync */
+
+ { 0x4500, 0x40 },
+};
+
+static const struct ov8865_mode ov8865_modes[] = {
+ /* 3264x2448 */
+ {
+ /* Horizontal */
+ .output_size_x = 3264,
+ .hts = 1944,
+
+ /* Vertical */
+ .output_size_y = 2448,
+ .vts = 2470,
+
+ .size_auto = true,
+ .size_auto_boundary_x = 8,
+ .size_auto_boundary_y = 4,
+
+ /* Subsample increase */
+ .inc_x_odd = 1,
+ .inc_x_even = 1,
+ .inc_y_odd = 1,
+ .inc_y_even = 1,
+
+ /* VFIFO */
+ .vfifo_read_start = 16,
+
+ .ablc_num = 4,
+ .zline_num = 1,
+
+ /* Black Level */
+
+ .blc_top_zero_line_start = 0,
+ .blc_top_zero_line_num = 2,
+ .blc_top_black_line_start = 4,
+ .blc_top_black_line_num = 4,
+
+ .blc_bottom_zero_line_start = 2,
+ .blc_bottom_zero_line_num = 2,
+ .blc_bottom_black_line_start = 8,
+ .blc_bottom_black_line_num = 2,
+
+ .blc_anchor_left_start = 576,
+ .blc_anchor_left_end = 831,
+ .blc_anchor_right_start = 1984,
+ .blc_anchor_right_end = 2239,
+
+ /* Frame Interval */
+ .frame_interval = { 1, 30 },
+
+ /* PLL */
+ .pll1_config = &ov8865_pll1_config_native,
+ .pll2_config = &ov8865_pll2_config_native,
+ .sclk_config = &ov8865_sclk_config_native,
+
+ /* Registers */
+ .register_values = ov8865_register_values_native,
+ .register_values_count =
+ ARRAY_SIZE(ov8865_register_values_native),
+ },
+ /* 3264x1836 */
+ {
+ /* Horizontal */
+ .output_size_x = 3264,
+ .hts = 2582,
+
+ /* Vertical */
+ .output_size_y = 1836,
+ .vts = 2002,
+
+ .size_auto = true,
+ .size_auto_boundary_x = 8,
+ .size_auto_boundary_y = 4,
+
+ /* Subsample increase */
+ .inc_x_odd = 1,
+ .inc_x_even = 1,
+ .inc_y_odd = 1,
+ .inc_y_even = 1,
+
+ /* VFIFO */
+ .vfifo_read_start = 16,
+
+ .ablc_num = 4,
+ .zline_num = 1,
+
+ /* Black Level */
+
+ .blc_top_zero_line_start = 0,
+ .blc_top_zero_line_num = 2,
+ .blc_top_black_line_start = 4,
+ .blc_top_black_line_num = 4,
+
+ .blc_bottom_zero_line_start = 2,
+ .blc_bottom_zero_line_num = 2,
+ .blc_bottom_black_line_start = 8,
+ .blc_bottom_black_line_num = 2,
+
+ .blc_anchor_left_start = 576,
+ .blc_anchor_left_end = 831,
+ .blc_anchor_right_start = 1984,
+ .blc_anchor_right_end = 2239,
+
+ /* Frame Interval */
+ .frame_interval = { 1, 30 },
+
+ /* PLL */
+ .pll1_config = &ov8865_pll1_config_native,
+ .pll2_config = &ov8865_pll2_config_native,
+ .sclk_config = &ov8865_sclk_config_native,
+
+ /* Registers */
+ .register_values = ov8865_register_values_native,
+ .register_values_count =
+ ARRAY_SIZE(ov8865_register_values_native),
+ },
+ /* 1632x1224 */
+ {
+ /* Horizontal */
+ .output_size_x = 1632,
+ .hts = 1923,
+
+ /* Vertical */
+ .output_size_y = 1224,
+ .vts = 1248,
+
+ .size_auto = true,
+ .size_auto_boundary_x = 8,
+ .size_auto_boundary_y = 8,
+
+ /* Subsample increase */
+ .inc_x_odd = 3,
+ .inc_x_even = 1,
+ .inc_y_odd = 3,
+ .inc_y_even = 1,
+
+ /* Binning */
+ .binning_y = true,
+ .sync_hbin = true,
+
+ /* VFIFO */
+ .vfifo_read_start = 116,
+
+ .ablc_num = 8,
+ .zline_num = 2,
+
+ /* Black Level */
+
+ .blc_top_zero_line_start = 0,
+ .blc_top_zero_line_num = 2,
+ .blc_top_black_line_start = 4,
+ .blc_top_black_line_num = 4,
+
+ .blc_bottom_zero_line_start = 2,
+ .blc_bottom_zero_line_num = 2,
+ .blc_bottom_black_line_start = 8,
+ .blc_bottom_black_line_num = 2,
+
+ .blc_anchor_left_start = 288,
+ .blc_anchor_left_end = 415,
+ .blc_anchor_right_start = 992,
+ .blc_anchor_right_end = 1119,
+
+ /* Frame Interval */
+ .frame_interval = { 1, 30 },
+
+ /* PLL */
+ .pll1_config = &ov8865_pll1_config_native,
+ .pll2_config = &ov8865_pll2_config_binning,
+ .sclk_config = &ov8865_sclk_config_native,
+
+ /* Registers */
+ .register_values = ov8865_register_values_binning,
+ .register_values_count =
+ ARRAY_SIZE(ov8865_register_values_binning),
+ },
+ /* 800x600 (SVGA) */
+ {
+ /* Horizontal */
+ .output_size_x = 800,
+ .hts = 1250,
+
+ /* Vertical */
+ .output_size_y = 600,
+ .vts = 640,
+
+ .size_auto = true,
+ .size_auto_boundary_x = 8,
+ .size_auto_boundary_y = 8,
+
+ /* Subsample increase */
+ .inc_x_odd = 3,
+ .inc_x_even = 1,
+ .inc_y_odd = 5,
+ .inc_y_even = 3,
+
+ /* Binning */
+ .binning_y = true,
+ .variopixel = true,
+ .variopixel_hsub_coef = 2,
+ .variopixel_vsub_coef = 1,
+ .sync_hbin = true,
+ .horz_var2 = true,
+
+ /* VFIFO */
+ .vfifo_read_start = 80,
+
+ .ablc_num = 8,
+ .zline_num = 2,
+
+ /* Black Level */
+
+ .blc_top_zero_line_start = 0,
+ .blc_top_zero_line_num = 2,
+ .blc_top_black_line_start = 2,
+ .blc_top_black_line_num = 2,
+
+ .blc_bottom_zero_line_start = 0,
+ .blc_bottom_zero_line_num = 0,
+ .blc_bottom_black_line_start = 4,
+ .blc_bottom_black_line_num = 2,
+
+ .blc_col_shift_mask = OV8865_BLC_CTRL1_COL_SHIFT_128,
+
+ .blc_anchor_left_start = 288,
+ .blc_anchor_left_end = 415,
+ .blc_anchor_right_start = 992,
+ .blc_anchor_right_end = 1119,
+
+ /* Frame Interval */
+ .frame_interval = { 1, 90 },
+
+ /* PLL */
+ .pll1_config = &ov8865_pll1_config_native,
+ .pll2_config = &ov8865_pll2_config_binning,
+ .sclk_config = &ov8865_sclk_config_native,
+
+ /* Registers */
+ .register_values = ov8865_register_values_binning,
+ .register_values_count =
+ ARRAY_SIZE(ov8865_register_values_binning),
+ },
+};
+
+static const u32 ov8865_mbus_codes[] = {
+ MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+static const struct ov8865_register_value ov8865_init_sequence[] = {
+ /* Analog */
+
+ { 0x3604, 0x04 },
+ { 0x3602, 0x30 },
+ { 0x3605, 0x00 },
+ { 0x3607, 0x20 },
+ { 0x3608, 0x11 },
+ { 0x3609, 0x68 },
+ { 0x360a, 0x40 },
+ { 0x360c, 0xdd },
+ { 0x360e, 0x0c },
+ { 0x3610, 0x07 },
+ { 0x3612, 0x86 },
+ { 0x3613, 0x58 },
+ { 0x3614, 0x28 },
+ { 0x3617, 0x40 },
+ { 0x3618, 0x5a },
+ { 0x3619, 0x9b },
+ { 0x361c, 0x00 },
+ { 0x361d, 0x60 },
+ { 0x3631, 0x60 },
+ { 0x3633, 0x10 },
+ { 0x3634, 0x10 },
+ { 0x3635, 0x10 },
+ { 0x3636, 0x10 },
+ { 0x3638, 0xff },
+ { 0x3641, 0x55 },
+ { 0x3646, 0x86 },
+ { 0x3647, 0x27 },
+ { 0x364a, 0x1b },
+
+ /* Sensor */
+
+ { 0x3700, 0x24 },
+ { 0x3701, 0x0c },
+ { 0x3702, 0x28 },
+ { 0x3703, 0x19 },
+ { 0x3704, 0x14 },
+ { 0x3705, 0x00 },
+ { 0x3706, 0x38 },
+ { 0x3707, 0x04 },
+ { 0x3708, 0x24 },
+ { 0x3709, 0x40 },
+ { 0x370a, 0x00 },
+ { 0x370b, 0xb8 },
+ { 0x370c, 0x04 },
+ { 0x3718, 0x12 },
+ { 0x3719, 0x31 },
+ { 0x3712, 0x42 },
+ { 0x3714, 0x12 },
+ { 0x371e, 0x19 },
+ { 0x371f, 0x40 },
+ { 0x3720, 0x05 },
+ { 0x3721, 0x05 },
+ { 0x3724, 0x02 },
+ { 0x3725, 0x02 },
+ { 0x3726, 0x06 },
+ { 0x3728, 0x05 },
+ { 0x3729, 0x02 },
+ { 0x372a, 0x03 },
+ { 0x372b, 0x53 },
+ { 0x372c, 0xa3 },
+ { 0x372d, 0x53 },
+ { 0x372e, 0x06 },
+ { 0x372f, 0x10 },
+ { 0x3730, 0x01 },
+ { 0x3731, 0x06 },
+ { 0x3732, 0x14 },
+ { 0x3733, 0x10 },
+ { 0x3734, 0x40 },
+ { 0x3736, 0x20 },
+ { 0x373a, 0x02 },
+ { 0x373b, 0x0c },
+ { 0x373c, 0x0a },
+ { 0x373e, 0x03 },
+ { 0x3755, 0x40 },
+ { 0x3758, 0x00 },
+ { 0x3759, 0x4c },
+ { 0x375a, 0x06 },
+ { 0x375b, 0x13 },
+ { 0x375c, 0x40 },
+ { 0x375d, 0x02 },
+ { 0x375e, 0x00 },
+ { 0x375f, 0x14 },
+ { 0x3767, 0x1c },
+ { 0x3768, 0x04 },
+ { 0x3769, 0x20 },
+ { 0x376c, 0xc0 },
+ { 0x376d, 0xc0 },
+ { 0x376a, 0x08 },
+ { 0x3761, 0x00 },
+ { 0x3762, 0x00 },
+ { 0x3763, 0x00 },
+ { 0x3766, 0xff },
+ { 0x376b, 0x42 },
+ { 0x3772, 0x23 },
+ { 0x3773, 0x02 },
+ { 0x3774, 0x16 },
+ { 0x3775, 0x12 },
+ { 0x3776, 0x08 },
+ { 0x37a0, 0x44 },
+ { 0x37a1, 0x3d },
+ { 0x37a2, 0x3d },
+ { 0x37a3, 0x01 },
+ { 0x37a4, 0x00 },
+ { 0x37a5, 0x08 },
+ { 0x37a6, 0x00 },
+ { 0x37a7, 0x44 },
+ { 0x37a8, 0x58 },
+ { 0x37a9, 0x58 },
+ { 0x3760, 0x00 },
+ { 0x376f, 0x01 },
+ { 0x37aa, 0x44 },
+ { 0x37ab, 0x2e },
+ { 0x37ac, 0x2e },
+ { 0x37ad, 0x33 },
+ { 0x37ae, 0x0d },
+ { 0x37af, 0x0d },
+ { 0x37b0, 0x00 },
+ { 0x37b1, 0x00 },
+ { 0x37b2, 0x00 },
+ { 0x37b3, 0x42 },
+ { 0x37b4, 0x42 },
+ { 0x37b5, 0x33 },
+ { 0x37b6, 0x00 },
+ { 0x37b7, 0x00 },
+ { 0x37b8, 0x00 },
+ { 0x37b9, 0xff },
+
+ /* ADC Sync */
+
+ { 0x4503, 0x10 },
+};
+
+static const s64 ov8865_link_freq_menu[] = {
+ 360000000,
+};
+
+static const char *const ov8865_test_pattern_menu[] = {
+ "Disabled",
+ "Random data",
+ "Color bars",
+ "Color bars with rolling bar",
+ "Color squares",
+ "Color squares with rolling bar"
+};
+
+static const u8 ov8865_test_pattern_bits[] = {
+ 0,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_RANDOM_DATA,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_COLOR_BARS,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_ROLLING_BAR_EN |
+ OV8865_PRE_CTRL0_PATTERN_COLOR_BARS,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES,
+ OV8865_PRE_CTRL0_PATTERN_EN | OV8865_PRE_CTRL0_ROLLING_BAR_EN |
+ OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES,
+};
+
+/* Input/Output */
+
+static int ov8865_read(struct ov8865_sensor *sensor, u16 address, u8 *value)
+{
+ unsigned char data[2] = { address >> 8, address & 0xff };
+ struct i2c_client *client = sensor->i2c_client;
+ int ret;
+
+ ret = i2c_master_send(client, data, sizeof(data));
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c send error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ ret = i2c_master_recv(client, value, 1);
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c recv error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov8865_write(struct ov8865_sensor *sensor, u16 address, u8 value)
+{
+ unsigned char data[3] = { address >> 8, address & 0xff, value };
+ struct i2c_client *client = sensor->i2c_client;
+ int ret;
+
+ ret = i2c_master_send(client, data, sizeof(data));
+ if (ret < 0) {
+ dev_dbg(&client->dev, "i2c send error at address %#04x\n",
+ address);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov8865_write_sequence(struct ov8865_sensor *sensor,
+ const struct ov8865_register_value *sequence,
+ unsigned int sequence_count)
+{
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < sequence_count; i++) {
+ ret = ov8865_write(sensor, sequence[i].address,
+ sequence[i].value);
+ if (ret)
+ break;
+
+ if (sequence[i].delay_ms)
+ msleep(sequence[i].delay_ms);
+ }
+
+ return ret;
+}
+
+static int ov8865_update_bits(struct ov8865_sensor *sensor, u16 address,
+ u8 mask, u8 bits)
+{
+ u8 value = 0;
+ int ret;
+
+ ret = ov8865_read(sensor, address, &value);
+ if (ret)
+ return ret;
+
+ value &= ~mask;
+ value |= bits;
+
+ return ov8865_write(sensor, address, value);
+}
+
+/* Sensor */
+
+static int ov8865_sw_reset(struct ov8865_sensor *sensor)
+{
+ return ov8865_write(sensor, OV8865_SW_RESET_REG, OV8865_SW_RESET_RESET);
+}
+
+static int ov8865_sw_standby(struct ov8865_sensor *sensor, int standby)
+{
+ u8 value = 0;
+
+ if (!standby)
+ value = OV8865_SW_STANDBY_STREAM_ON;
+
+ return ov8865_write(sensor, OV8865_SW_STANDBY_REG, value);
+}
+
+static int ov8865_chip_id_check(struct ov8865_sensor *sensor)
+{
+ u16 regs[] = { OV8865_CHIP_ID_HH_REG, OV8865_CHIP_ID_H_REG,
+ OV8865_CHIP_ID_L_REG };
+ u8 values[] = { OV8865_CHIP_ID_HH_VALUE, OV8865_CHIP_ID_H_VALUE,
+ OV8865_CHIP_ID_L_VALUE };
+ unsigned int i;
+ u8 value;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ ret = ov8865_read(sensor, regs[i], &value);
+ if (ret < 0)
+ return ret;
+
+ if (value != values[i]) {
+ dev_err(sensor->dev,
+ "chip id value mismatch: %#x instead of %#x\n",
+ value, values[i]);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int ov8865_charge_pump_configure(struct ov8865_sensor *sensor)
+{
+ return ov8865_write(sensor, OV8865_PUMP_CLK_DIV_REG,
+ OV8865_PUMP_CLK_DIV_PUMP_P(1));
+}
+
+static int ov8865_mipi_configure(struct ov8865_sensor *sensor)
+{
+ struct v4l2_fwnode_bus_mipi_csi2 *bus_mipi_csi2 =
+ &sensor->endpoint.bus.mipi_csi2;
+ unsigned int lanes_count = bus_mipi_csi2->num_data_lanes;
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_MIPI_SC_CTRL0_REG,
+ OV8865_MIPI_SC_CTRL0_LANES(lanes_count) |
+ OV8865_MIPI_SC_CTRL0_MIPI_EN |
+ OV8865_MIPI_SC_CTRL0_UNKNOWN);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_MIPI_SC_CTRL2_REG,
+ OV8865_MIPI_SC_CTRL2_PD_MIPI_RST_SYNC);
+ if (ret)
+ return ret;
+
+ if (lanes_count >= 2) {
+ ret = ov8865_write(sensor, OV8865_MIPI_LANE_SEL01_REG,
+ OV8865_MIPI_LANE_SEL01_LANE0(0) |
+ OV8865_MIPI_LANE_SEL01_LANE1(1));
+ if (ret)
+ return ret;
+ }
+
+ if (lanes_count >= 4) {
+ ret = ov8865_write(sensor, OV8865_MIPI_LANE_SEL23_REG,
+ OV8865_MIPI_LANE_SEL23_LANE2(2) |
+ OV8865_MIPI_LANE_SEL23_LANE3(3));
+ if (ret)
+ return ret;
+ }
+
+ ret = ov8865_update_bits(sensor, OV8865_CLK_SEL1_REG,
+ OV8865_CLK_SEL1_MIPI_EOF,
+ OV8865_CLK_SEL1_MIPI_EOF);
+ if (ret)
+ return ret;
+
+ /*
+ * This value might need to change depending on PCLK rate,
+ * but it's unclear how. This value seems to generally work
+ * while the default value was found to cause transmission errors.
+ */
+ return ov8865_write(sensor, OV8865_MIPI_PCLK_PERIOD_REG, 0x16);
+}
+
+static int ov8865_black_level_configure(struct ov8865_sensor *sensor)
+{
+ int ret;
+
+ /* Trigger BLC on relevant events and enable filter. */
+ ret = ov8865_write(sensor, OV8865_BLC_CTRL0_REG,
+ OV8865_BLC_CTRL0_TRIG_RANGE_EN |
+ OV8865_BLC_CTRL0_TRIG_FORMAT_EN |
+ OV8865_BLC_CTRL0_TRIG_GAIN_EN |
+ OV8865_BLC_CTRL0_TRIG_EXPOSURE_EN |
+ OV8865_BLC_CTRL0_FILTER_EN);
+ if (ret)
+ return ret;
+
+ /* Lower BLC offset trigger threshold. */
+ ret = ov8865_write(sensor, OV8865_BLC_CTRLD_REG,
+ OV8865_BLC_CTRLD_OFFSET_TRIGGER(16));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_CTRL1F_REG, 0);
+ if (ret)
+ return ret;
+
+ /* Increase BLC offset maximum limit. */
+ return ov8865_write(sensor, OV8865_BLC_OFFSET_LIMIT_REG,
+ OV8865_BLC_OFFSET_LIMIT(63));
+}
+
+static int ov8865_isp_configure(struct ov8865_sensor *sensor)
+{
+ int ret;
+
+ /* Disable lens correction. */
+ ret = ov8865_write(sensor, OV8865_ISP_CTRL0_REG,
+ OV8865_ISP_CTRL0_WHITE_BALANCE_EN |
+ OV8865_ISP_CTRL0_DPC_BLACK_EN |
+ OV8865_ISP_CTRL0_DPC_WHITE_EN);
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_ISP_CTRL1_REG,
+ OV8865_ISP_CTRL1_BLC_EN);
+}
+
+static unsigned long ov8865_mode_pll1_rate(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ const struct ov8865_pll1_config *config = mode->pll1_config;
+ unsigned long extclk_rate;
+ unsigned long pll1_rate;
+
+ extclk_rate = clk_get_rate(sensor->extclk);
+ pll1_rate = extclk_rate * config->pll_mul / config->pll_pre_div_half;
+
+ switch (config->pll_pre_div) {
+ case 0:
+ break;
+ case 1:
+ pll1_rate *= 3;
+ pll1_rate /= 2;
+ break;
+ case 3:
+ pll1_rate *= 5;
+ pll1_rate /= 2;
+ break;
+ case 4:
+ pll1_rate /= 3;
+ break;
+ case 5:
+ pll1_rate /= 4;
+ break;
+ case 7:
+ pll1_rate /= 8;
+ break;
+ default:
+ pll1_rate /= config->pll_pre_div;
+ break;
+ }
+
+ return pll1_rate;
+}
+
+static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode,
+ u32 mbus_code)
+{
+ const struct ov8865_pll1_config *config = mode->pll1_config;
+ u8 value;
+ int ret;
+
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ value = OV8865_MIPI_BIT_SEL(10);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = ov8865_write(sensor, OV8865_MIPI_BIT_SEL_REG, value);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLA_REG,
+ OV8865_PLL_CTRLA_PRE_DIV_HALF(config->pll_pre_div_half));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL0_REG,
+ OV8865_PLL_CTRL0_PRE_DIV(config->pll_pre_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL1_REG,
+ OV8865_PLL_CTRL1_MUL_H(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL2_REG,
+ OV8865_PLL_CTRL2_MUL_L(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL3_REG,
+ OV8865_PLL_CTRL3_M_DIV(config->m_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL4_REG,
+ OV8865_PLL_CTRL4_MIPI_DIV(config->mipi_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_update_bits(sensor, OV8865_PCLK_SEL_REG,
+ OV8865_PCLK_SEL_PCLK_DIV_MASK,
+ OV8865_PCLK_SEL_PCLK_DIV(config->pclk_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL5_REG,
+ OV8865_PLL_CTRL5_SYS_PRE_DIV(config->sys_pre_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL6_REG,
+ OV8865_PLL_CTRL6_SYS_DIV(config->sys_div));
+ if (ret)
+ return ret;
+
+ return ov8865_update_bits(sensor, OV8865_PLL_CTRL1E_REG,
+ OV8865_PLL_CTRL1E_PLL1_NO_LAT,
+ OV8865_PLL_CTRL1E_PLL1_NO_LAT);
+}
+
+static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ const struct ov8865_pll2_config *config = mode->pll2_config;
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRL12_REG,
+ OV8865_PLL_CTRL12_PRE_DIV_HALF(config->pll_pre_div_half) |
+ OV8865_PLL_CTRL12_DAC_DIV(config->dac_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLB_REG,
+ OV8865_PLL_CTRLB_PRE_DIV(config->pll_pre_div));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLC_REG,
+ OV8865_PLL_CTRLC_MUL_H(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLD_REG,
+ OV8865_PLL_CTRLD_MUL_L(config->pll_mul));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_PLL_CTRLF_REG,
+ OV8865_PLL_CTRLF_SYS_PRE_DIV(config->sys_pre_div));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_PLL_CTRLE_REG,
+ OV8865_PLL_CTRLE_SYS_DIV(config->sys_div));
+}
+
+static int ov8865_mode_sclk_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ const struct ov8865_sclk_config *config = mode->sclk_config;
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_CLK_SEL0_REG,
+ OV8865_CLK_SEL0_PLL1_SYS_SEL(config->sys_sel));
+ if (ret)
+ return ret;
+
+ ret = ov8865_update_bits(sensor, OV8865_CLK_SEL1_REG,
+ OV8865_CLK_SEL1_PLL_SCLK_SEL_MASK,
+ OV8865_CLK_SEL1_PLL_SCLK_SEL(config->sclk_sel));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_SCLK_CTRL_REG,
+ OV8865_SCLK_CTRL_UNKNOWN |
+ OV8865_SCLK_CTRL_SCLK_DIV(config->sclk_div) |
+ OV8865_SCLK_CTRL_SCLK_PRE_DIV(config->sclk_pre_div));
+}
+
+static int ov8865_mode_binning_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ unsigned int variopixel_hsub_coef, variopixel_vsub_coef;
+ u8 value;
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_FORMAT1_REG, 0);
+ if (ret)
+ return ret;
+
+ value = OV8865_FORMAT2_HSYNC_EN;
+
+ if (mode->binning_x)
+ value |= OV8865_FORMAT2_FST_HBIN_EN;
+
+ if (mode->binning_y)
+ value |= OV8865_FORMAT2_FST_VBIN_EN;
+
+ if (mode->sync_hbin)
+ value |= OV8865_FORMAT2_SYNC_HBIN_EN;
+
+ if (mode->horz_var2)
+ value |= OV8865_FORMAT2_ISP_HORZ_VAR2_EN;
+
+ ret = ov8865_write(sensor, OV8865_FORMAT2_REG, value);
+ if (ret)
+ return ret;
+
+ ret = ov8865_update_bits(sensor, OV8865_ISP_CTRL2_REG,
+ OV8865_ISP_CTRL2_VARIOPIXEL_EN,
+ mode->variopixel ?
+ OV8865_ISP_CTRL2_VARIOPIXEL_EN : 0);
+ if (ret)
+ return ret;
+
+ if (mode->variopixel) {
+ /* VarioPixel coefs needs to be > 1. */
+ variopixel_hsub_coef = mode->variopixel_hsub_coef;
+ variopixel_vsub_coef = mode->variopixel_vsub_coef;
+ } else {
+ variopixel_hsub_coef = 1;
+ variopixel_vsub_coef = 1;
+ }
+
+ ret = ov8865_write(sensor, OV8865_VAP_CTRL1_REG,
+ OV8865_VAP_CTRL1_HSUB_COEF(variopixel_hsub_coef) |
+ OV8865_VAP_CTRL1_VSUB_COEF(variopixel_vsub_coef));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_INC_X_ODD_REG,
+ OV8865_INC_X_ODD(mode->inc_x_odd));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_INC_X_EVEN_REG,
+ OV8865_INC_X_EVEN(mode->inc_x_even));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_INC_Y_ODD_REG,
+ OV8865_INC_Y_ODD(mode->inc_y_odd));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_INC_Y_EVEN_REG,
+ OV8865_INC_Y_EVEN(mode->inc_y_even));
+}
+
+static int ov8865_mode_black_level_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ int ret;
+
+ /* Note that a zero value for blc_col_shift_mask is the default 256. */
+ ret = ov8865_write(sensor, OV8865_BLC_CTRL1_REG,
+ mode->blc_col_shift_mask |
+ OV8865_BLC_CTRL1_OFFSET_LIMIT_EN);
+ if (ret)
+ return ret;
+
+ /* BLC top zero line */
+
+ ret = ov8865_write(sensor, OV8865_BLC_TOP_ZLINE_START_REG,
+ OV8865_BLC_TOP_ZLINE_START(mode->blc_top_zero_line_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_TOP_ZLINE_NUM_REG,
+ OV8865_BLC_TOP_ZLINE_NUM(mode->blc_top_zero_line_num));
+ if (ret)
+ return ret;
+
+ /* BLC top black line */
+
+ ret = ov8865_write(sensor, OV8865_BLC_TOP_BLKLINE_START_REG,
+ OV8865_BLC_TOP_BLKLINE_START(mode->blc_top_black_line_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_TOP_BLKLINE_NUM_REG,
+ OV8865_BLC_TOP_BLKLINE_NUM(mode->blc_top_black_line_num));
+ if (ret)
+ return ret;
+
+ /* BLC bottom zero line */
+
+ ret = ov8865_write(sensor, OV8865_BLC_BOT_ZLINE_START_REG,
+ OV8865_BLC_BOT_ZLINE_START(mode->blc_bottom_zero_line_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_BOT_ZLINE_NUM_REG,
+ OV8865_BLC_BOT_ZLINE_NUM(mode->blc_bottom_zero_line_num));
+ if (ret)
+ return ret;
+
+ /* BLC bottom black line */
+
+ ret = ov8865_write(sensor, OV8865_BLC_BOT_BLKLINE_START_REG,
+ OV8865_BLC_BOT_BLKLINE_START(mode->blc_bottom_black_line_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_BOT_BLKLINE_NUM_REG,
+ OV8865_BLC_BOT_BLKLINE_NUM(mode->blc_bottom_black_line_num));
+ if (ret)
+ return ret;
+
+ /* BLC anchor */
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_START_H_REG,
+ OV8865_BLC_ANCHOR_LEFT_START_H(mode->blc_anchor_left_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_START_L_REG,
+ OV8865_BLC_ANCHOR_LEFT_START_L(mode->blc_anchor_left_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_END_H_REG,
+ OV8865_BLC_ANCHOR_LEFT_END_H(mode->blc_anchor_left_end));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_LEFT_END_L_REG,
+ OV8865_BLC_ANCHOR_LEFT_END_L(mode->blc_anchor_left_end));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_START_H_REG,
+ OV8865_BLC_ANCHOR_RIGHT_START_H(mode->blc_anchor_right_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_START_L_REG,
+ OV8865_BLC_ANCHOR_RIGHT_START_L(mode->blc_anchor_right_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_END_H_REG,
+ OV8865_BLC_ANCHOR_RIGHT_END_H(mode->blc_anchor_right_end));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_BLC_ANCHOR_RIGHT_END_L_REG,
+ OV8865_BLC_ANCHOR_RIGHT_END_L(mode->blc_anchor_right_end));
+}
+
+static int ov8865_mode_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode, u32 mbus_code)
+{
+ int ret;
+
+ /* Output Size X */
+
+ ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_X_H_REG,
+ OV8865_OUTPUT_SIZE_X_H(mode->output_size_x));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_X_L_REG,
+ OV8865_OUTPUT_SIZE_X_L(mode->output_size_x));
+ if (ret)
+ return ret;
+
+ /* Horizontal Total Size */
+
+ ret = ov8865_write(sensor, OV8865_HTS_H_REG, OV8865_HTS_H(mode->hts));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_HTS_L_REG, OV8865_HTS_L(mode->hts));
+ if (ret)
+ return ret;
+
+ /* Output Size Y */
+
+ ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_Y_H_REG,
+ OV8865_OUTPUT_SIZE_Y_H(mode->output_size_y));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_OUTPUT_SIZE_Y_L_REG,
+ OV8865_OUTPUT_SIZE_Y_L(mode->output_size_y));
+ if (ret)
+ return ret;
+
+ /* Vertical Total Size */
+
+ ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(mode->vts));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(mode->vts));
+ if (ret)
+ return ret;
+
+ if (mode->size_auto) {
+ /* Auto Size */
+
+ ret = ov8865_write(sensor, OV8865_AUTO_SIZE_CTRL_REG,
+ OV8865_AUTO_SIZE_CTRL_OFFSET_Y_REG |
+ OV8865_AUTO_SIZE_CTRL_OFFSET_X_REG |
+ OV8865_AUTO_SIZE_CTRL_CROP_END_Y_REG |
+ OV8865_AUTO_SIZE_CTRL_CROP_END_X_REG |
+ OV8865_AUTO_SIZE_CTRL_CROP_START_Y_REG |
+ OV8865_AUTO_SIZE_CTRL_CROP_START_X_REG);
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_AUTO_SIZE_BOUNDARIES_REG,
+ OV8865_AUTO_SIZE_BOUNDARIES_Y(mode->size_auto_boundary_y) |
+ OV8865_AUTO_SIZE_BOUNDARIES_X(mode->size_auto_boundary_x));
+ if (ret)
+ return ret;
+ } else {
+ /* Crop Start X */
+
+ ret = ov8865_write(sensor, OV8865_CROP_START_X_H_REG,
+ OV8865_CROP_START_X_H(mode->crop_start_x));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_CROP_START_X_L_REG,
+ OV8865_CROP_START_X_L(mode->crop_start_x));
+ if (ret)
+ return ret;
+
+ /* Offset X */
+
+ ret = ov8865_write(sensor, OV8865_OFFSET_X_H_REG,
+ OV8865_OFFSET_X_H(mode->offset_x));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_OFFSET_X_L_REG,
+ OV8865_OFFSET_X_L(mode->offset_x));
+ if (ret)
+ return ret;
+
+ /* Crop End X */
+
+ ret = ov8865_write(sensor, OV8865_CROP_END_X_H_REG,
+ OV8865_CROP_END_X_H(mode->crop_end_x));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_CROP_END_X_L_REG,
+ OV8865_CROP_END_X_L(mode->crop_end_x));
+ if (ret)
+ return ret;
+
+ /* Crop Start Y */
+
+ ret = ov8865_write(sensor, OV8865_CROP_START_Y_H_REG,
+ OV8865_CROP_START_Y_H(mode->crop_start_y));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_CROP_START_Y_L_REG,
+ OV8865_CROP_START_Y_L(mode->crop_start_y));
+ if (ret)
+ return ret;
+
+ /* Offset Y */
+
+ ret = ov8865_write(sensor, OV8865_OFFSET_Y_H_REG,
+ OV8865_OFFSET_Y_H(mode->offset_y));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_OFFSET_Y_L_REG,
+ OV8865_OFFSET_Y_L(mode->offset_y));
+ if (ret)
+ return ret;
+
+ /* Crop End Y */
+
+ ret = ov8865_write(sensor, OV8865_CROP_END_Y_H_REG,
+ OV8865_CROP_END_Y_H(mode->crop_end_y));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_CROP_END_Y_L_REG,
+ OV8865_CROP_END_Y_L(mode->crop_end_y));
+ if (ret)
+ return ret;
+ }
+
+ /* VFIFO */
+
+ ret = ov8865_write(sensor, OV8865_VFIFO_READ_START_H_REG,
+ OV8865_VFIFO_READ_START_H(mode->vfifo_read_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_VFIFO_READ_START_L_REG,
+ OV8865_VFIFO_READ_START_L(mode->vfifo_read_start));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_ABLC_NUM_REG,
+ OV8865_ABLC_NUM(mode->ablc_num));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_ZLINE_NUM_REG,
+ OV8865_ZLINE_NUM(mode->zline_num));
+ if (ret)
+ return ret;
+
+ /* Binning */
+
+ ret = ov8865_mode_binning_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ /* Black Level */
+
+ ret = ov8865_mode_black_level_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ /* PLLs */
+
+ ret = ov8865_mode_pll1_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+
+ ret = ov8865_mode_pll2_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ ret = ov8865_mode_sclk_configure(sensor, mode);
+ if (ret)
+ return ret;
+
+ /* Extra registers */
+
+ if (mode->register_values) {
+ ret = ov8865_write_sequence(sensor, mode->register_values,
+ mode->register_values_count);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static unsigned long ov8865_mode_mipi_clk_rate(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode)
+{
+ const struct ov8865_pll1_config *config = mode->pll1_config;
+ unsigned long pll1_rate;
+
+ pll1_rate = ov8865_mode_pll1_rate(sensor, mode);
+
+ return pll1_rate / config->m_div / 2;
+}
+
+/* Exposure */
+
+static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure)
+{
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_HH_REG,
+ OV8865_EXPOSURE_CTRL_HH(exposure));
+ if (ret)
+ return ret;
+
+ ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_H_REG,
+ OV8865_EXPOSURE_CTRL_H(exposure));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_EXPOSURE_CTRL_L_REG,
+ OV8865_EXPOSURE_CTRL_L(exposure));
+}
+
+/* Gain */
+
+static int ov8865_gain_configure(struct ov8865_sensor *sensor, u32 gain)
+{
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_GAIN_CTRL_H_REG,
+ OV8865_GAIN_CTRL_H(gain));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_GAIN_CTRL_L_REG,
+ OV8865_GAIN_CTRL_L(gain));
+}
+
+/* White Balance */
+
+static int ov8865_red_balance_configure(struct ov8865_sensor *sensor,
+ u32 red_balance)
+{
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_ISP_GAIN_RED_H_REG,
+ OV8865_ISP_GAIN_RED_H(red_balance));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_ISP_GAIN_RED_L_REG,
+ OV8865_ISP_GAIN_RED_L(red_balance));
+}
+
+static int ov8865_blue_balance_configure(struct ov8865_sensor *sensor,
+ u32 blue_balance)
+{
+ int ret;
+
+ ret = ov8865_write(sensor, OV8865_ISP_GAIN_BLUE_H_REG,
+ OV8865_ISP_GAIN_BLUE_H(blue_balance));
+ if (ret)
+ return ret;
+
+ return ov8865_write(sensor, OV8865_ISP_GAIN_BLUE_L_REG,
+ OV8865_ISP_GAIN_BLUE_L(blue_balance));
+}
+
+/* Flip */
+
+static int ov8865_flip_vert_configure(struct ov8865_sensor *sensor, bool enable)
+{
+ u8 bits = OV8865_FORMAT1_FLIP_VERT_ISP_EN |
+ OV8865_FORMAT1_FLIP_VERT_SENSOR_EN;
+
+ return ov8865_update_bits(sensor, OV8865_FORMAT1_REG, bits,
+ enable ? bits : 0);
+}
+
+static int ov8865_flip_horz_configure(struct ov8865_sensor *sensor, bool enable)
+{
+ u8 bits = OV8865_FORMAT2_FLIP_HORZ_ISP_EN |
+ OV8865_FORMAT2_FLIP_HORZ_SENSOR_EN;
+
+ return ov8865_update_bits(sensor, OV8865_FORMAT2_REG, bits,
+ enable ? bits : 0);
+}
+
+/* Test Pattern */
+
+static int ov8865_test_pattern_configure(struct ov8865_sensor *sensor,
+ unsigned int index)
+{
+ if (index >= ARRAY_SIZE(ov8865_test_pattern_bits))
+ return -EINVAL;
+
+ return ov8865_write(sensor, OV8865_PRE_CTRL0_REG,
+ ov8865_test_pattern_bits[index]);
+}
+
+/* State */
+
+static int ov8865_state_mipi_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode,
+ u32 mbus_code)
+{
+ struct ov8865_ctrls *ctrls = &sensor->ctrls;
+ struct v4l2_fwnode_bus_mipi_csi2 *bus_mipi_csi2 =
+ &sensor->endpoint.bus.mipi_csi2;
+ unsigned long mipi_clk_rate;
+ unsigned int bits_per_sample;
+ unsigned int lanes_count;
+ unsigned int i, j;
+ s64 mipi_pixel_rate;
+
+ mipi_clk_rate = ov8865_mode_mipi_clk_rate(sensor, mode);
+ if (!mipi_clk_rate)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(ov8865_link_freq_menu); i++) {
+ s64 freq = ov8865_link_freq_menu[i];
+
+ if (freq == mipi_clk_rate)
+ break;
+ }
+
+ for (j = 0; j < sensor->endpoint.nr_of_link_frequencies; j++) {
+ u64 freq = sensor->endpoint.link_frequencies[j];
+
+ if (freq == mipi_clk_rate)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(ov8865_link_freq_menu)) {
+ dev_err(sensor->dev,
+ "failed to find %lu clk rate in link freq\n",
+ mipi_clk_rate);
+ } else if (j == sensor->endpoint.nr_of_link_frequencies) {
+ dev_err(sensor->dev,
+ "failed to find %lu clk rate in endpoint link-frequencies\n",
+ mipi_clk_rate);
+ } else {
+ __v4l2_ctrl_s_ctrl(ctrls->link_freq, i);
+ }
+
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ bits_per_sample = 10;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ lanes_count = bus_mipi_csi2->num_data_lanes;
+ mipi_pixel_rate = mipi_clk_rate * 2 * lanes_count / bits_per_sample;
+
+ __v4l2_ctrl_s_ctrl_int64(ctrls->pixel_rate, mipi_pixel_rate);
+
+ return 0;
+}
+
+static int ov8865_state_configure(struct ov8865_sensor *sensor,
+ const struct ov8865_mode *mode,
+ u32 mbus_code)
+{
+ int ret;
+
+ if (sensor->state.streaming)
+ return -EBUSY;
+
+ /* State will be configured at first power on otherwise. */
+ if (pm_runtime_enabled(sensor->dev) &&
+ !pm_runtime_suspended(sensor->dev)) {
+ ret = ov8865_mode_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+ }
+
+ ret = ov8865_state_mipi_configure(sensor, mode, mbus_code);
+ if (ret)
+ return ret;
+
+ sensor->state.mode = mode;
+ sensor->state.mbus_code = mbus_code;
+
+ return 0;
+}
+
+static int ov8865_state_init(struct ov8865_sensor *sensor)
+{
+ return ov8865_state_configure(sensor, &ov8865_modes[0],
+ ov8865_mbus_codes[0]);
+}
+
+/* Sensor Base */
+
+static int ov8865_sensor_init(struct ov8865_sensor *sensor)
+{
+ int ret;
+
+ ret = ov8865_sw_reset(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to perform sw reset\n");
+ return ret;
+ }
+
+ ret = ov8865_sw_standby(sensor, 1);
+ if (ret) {
+ dev_err(sensor->dev, "failed to set sensor standby\n");
+ return ret;
+ }
+
+ ret = ov8865_chip_id_check(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to check sensor chip id\n");
+ return ret;
+ }
+
+ ret = ov8865_write_sequence(sensor, ov8865_init_sequence,
+ ARRAY_SIZE(ov8865_init_sequence));
+ if (ret) {
+ dev_err(sensor->dev, "failed to write init sequence\n");
+ return ret;
+ }
+
+ ret = ov8865_charge_pump_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure pad\n");
+ return ret;
+ }
+
+ ret = ov8865_mipi_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure MIPI\n");
+ return ret;
+ }
+
+ ret = ov8865_isp_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure ISP\n");
+ return ret;
+ }
+
+ ret = ov8865_black_level_configure(sensor);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure black level\n");
+ return ret;
+ }
+
+ /* Configure current mode. */
+ ret = ov8865_state_configure(sensor, sensor->state.mode,
+ sensor->state.mbus_code);
+ if (ret) {
+ dev_err(sensor->dev, "failed to configure state\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov8865_sensor_power(struct ov8865_sensor *sensor, bool on)
+{
+ /* Keep initialized to zero for disable label. */
+ int ret = 0;
+
+ if (on) {
+ gpiod_set_value_cansleep(sensor->reset, 1);
+ gpiod_set_value_cansleep(sensor->powerdown, 1);
+
+ ret = regulator_enable(sensor->dovdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable DOVDD regulator\n");
+ goto disable;
+ }
+
+ ret = regulator_enable(sensor->avdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable AVDD regulator\n");
+ goto disable;
+ }
+
+ ret = regulator_enable(sensor->dvdd);
+ if (ret) {
+ dev_err(sensor->dev,
+ "failed to enable DVDD regulator\n");
+ goto disable;
+ }
+
+ ret = clk_prepare_enable(sensor->extclk);
+ if (ret) {
+ dev_err(sensor->dev, "failed to enable EXTCLK clock\n");
+ goto disable;
+ }
+
+ gpiod_set_value_cansleep(sensor->reset, 0);
+ gpiod_set_value_cansleep(sensor->powerdown, 0);
+
+ /* Time to enter streaming mode according to power timings. */
+ usleep_range(10000, 12000);
+ } else {
+disable:
+ gpiod_set_value_cansleep(sensor->powerdown, 1);
+ gpiod_set_value_cansleep(sensor->reset, 1);
+
+ clk_disable_unprepare(sensor->extclk);
+
+ regulator_disable(sensor->dvdd);
+ regulator_disable(sensor->avdd);
+ regulator_disable(sensor->dovdd);
+ }
+
+ return ret;
+}
+
+/* Controls */
+
+static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct v4l2_subdev *subdev = ov8865_ctrl_subdev(ctrl);
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ unsigned int index;
+ int ret;
+
+ /* Wait for the sensor to be on before setting controls. */
+ if (pm_runtime_suspended(sensor->dev))
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ ret = ov8865_exposure_configure(sensor, ctrl->val);
+ if (ret)
+ return ret;
+ break;
+ case V4L2_CID_GAIN:
+ ret = ov8865_gain_configure(sensor, ctrl->val);
+ if (ret)
+ return ret;
+ break;
+ case V4L2_CID_RED_BALANCE:
+ return ov8865_red_balance_configure(sensor, ctrl->val);
+ case V4L2_CID_BLUE_BALANCE:
+ return ov8865_blue_balance_configure(sensor, ctrl->val);
+ case V4L2_CID_HFLIP:
+ return ov8865_flip_horz_configure(sensor, !!ctrl->val);
+ case V4L2_CID_VFLIP:
+ return ov8865_flip_vert_configure(sensor, !!ctrl->val);
+ case V4L2_CID_TEST_PATTERN:
+ index = (unsigned int)ctrl->val;
+ return ov8865_test_pattern_configure(sensor, index);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops ov8865_ctrl_ops = {
+ .s_ctrl = ov8865_s_ctrl,
+};
+
+static int ov8865_ctrls_init(struct ov8865_sensor *sensor)
+{
+ struct ov8865_ctrls *ctrls = &sensor->ctrls;
+ struct v4l2_ctrl_handler *handler = &ctrls->handler;
+ const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops;
+ int ret;
+
+ v4l2_ctrl_handler_init(handler, 32);
+
+ /* Use our mutex for ctrl locking. */
+ handler->lock = &sensor->mutex;
+
+ /* Exposure */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16,
+ 512);
+
+ /* Gain */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_GAIN, 128, 8191, 128, 128);
+
+ /* White Balance */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_RED_BALANCE, 1, 32767, 1,
+ 1024);
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_BLUE_BALANCE, 1, 32767, 1,
+ 1024);
+
+ /* Flip */
+
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+ /* Test Pattern */
+
+ v4l2_ctrl_new_std_menu_items(handler, ops, V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(ov8865_test_pattern_menu) - 1,
+ 0, 0, ov8865_test_pattern_menu);
+
+ /* MIPI CSI-2 */
+
+ ctrls->link_freq =
+ v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
+ ARRAY_SIZE(ov8865_link_freq_menu) - 1,
+ 0, ov8865_link_freq_menu);
+
+ ctrls->pixel_rate =
+ v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1,
+ INT_MAX, 1, 1);
+
+ if (handler->error) {
+ ret = handler->error;
+ goto error_ctrls;
+ }
+
+ ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+ ctrls->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ sensor->subdev.ctrl_handler = handler;
+
+ return 0;
+
+error_ctrls:
+ v4l2_ctrl_handler_free(handler);
+
+ return ret;
+}
+
+/* Subdev Video Operations */
+
+static int ov8865_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct ov8865_state *state = &sensor->state;
+ int ret;
+
+ if (enable) {
+ ret = pm_runtime_get_sync(sensor->dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(sensor->dev);
+ return ret;
+ }
+ }
+
+ mutex_lock(&sensor->mutex);
+ ret = ov8865_sw_standby(sensor, !enable);
+ mutex_unlock(&sensor->mutex);
+
+ if (ret)
+ return ret;
+
+ state->streaming = !!enable;
+
+ if (!enable)
+ pm_runtime_put(sensor->dev);
+
+ return 0;
+}
+
+static int ov8865_g_frame_interval(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ const struct ov8865_mode *mode;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ mode = sensor->state.mode;
+ interval->interval = mode->frame_interval;
+
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static const struct v4l2_subdev_video_ops ov8865_subdev_video_ops = {
+ .s_stream = ov8865_s_stream,
+ .g_frame_interval = ov8865_g_frame_interval,
+ .s_frame_interval = ov8865_g_frame_interval,
+};
+
+/* Subdev Pad Operations */
+
+static int ov8865_enum_mbus_code(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_mbus_code_enum *code_enum)
+{
+ if (code_enum->index >= ARRAY_SIZE(ov8865_mbus_codes))
+ return -EINVAL;
+
+ code_enum->code = ov8865_mbus_codes[code_enum->index];
+
+ return 0;
+}
+
+static void ov8865_mbus_format_fill(struct v4l2_mbus_framefmt *mbus_format,
+ u32 mbus_code,
+ const struct ov8865_mode *mode)
+{
+ mbus_format->width = mode->output_size_x;
+ mbus_format->height = mode->output_size_y;
+ mbus_format->code = mbus_code;
+
+ mbus_format->field = V4L2_FIELD_NONE;
+ mbus_format->colorspace = V4L2_COLORSPACE_RAW;
+ mbus_format->ycbcr_enc =
+ V4L2_MAP_YCBCR_ENC_DEFAULT(mbus_format->colorspace);
+ mbus_format->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ mbus_format->xfer_func =
+ V4L2_MAP_XFER_FUNC_DEFAULT(mbus_format->colorspace);
+}
+
+static int ov8865_get_fmt(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_format *format)
+{
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct v4l2_mbus_framefmt *mbus_format = &format->format;
+
+ mutex_lock(&sensor->mutex);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ *mbus_format = *v4l2_subdev_get_try_format(subdev, config,
+ format->pad);
+ else
+ ov8865_mbus_format_fill(mbus_format, sensor->state.mbus_code,
+ sensor->state.mode);
+
+ mutex_unlock(&sensor->mutex);
+
+ return 0;
+}
+
+static int ov8865_set_fmt(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_format *format)
+{
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct v4l2_mbus_framefmt *mbus_format = &format->format;
+ const struct ov8865_mode *mode;
+ u32 mbus_code = 0;
+ unsigned int index;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ if (sensor->state.streaming) {
+ ret = -EBUSY;
+ goto complete;
+ }
+
+ /* Try to find requested mbus code. */
+ for (index = 0; index < ARRAY_SIZE(ov8865_mbus_codes); index++) {
+ if (ov8865_mbus_codes[index] == mbus_format->code) {
+ mbus_code = mbus_format->code;
+ break;
+ }
+ }
+
+ /* Fallback to default. */
+ if (!mbus_code)
+ mbus_code = ov8865_mbus_codes[0];
+
+ /* Find the mode with nearest dimensions. */
+ mode = v4l2_find_nearest_size(ov8865_modes, ARRAY_SIZE(ov8865_modes),
+ output_size_x, output_size_y,
+ mbus_format->width, mbus_format->height);
+ if (!mode) {
+ ret = -EINVAL;
+ goto complete;
+ }
+
+ ov8865_mbus_format_fill(mbus_format, mbus_code, mode);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ *v4l2_subdev_get_try_format(subdev, config, format->pad) =
+ *mbus_format;
+ else if (sensor->state.mode != mode ||
+ sensor->state.mbus_code != mbus_code)
+ ret = ov8865_state_configure(sensor, mode, mbus_code);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov8865_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_frame_size_enum *size_enum)
+{
+ const struct ov8865_mode *mode;
+
+ if (size_enum->index >= ARRAY_SIZE(ov8865_modes))
+ return -EINVAL;
+
+ mode = &ov8865_modes[size_enum->index];
+
+ size_enum->min_width = size_enum->max_width = mode->output_size_x;
+ size_enum->min_height = size_enum->max_height = mode->output_size_y;
+
+ return 0;
+}
+
+static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *config,
+ struct v4l2_subdev_frame_interval_enum *interval_enum)
+{
+ const struct ov8865_mode *mode = NULL;
+ unsigned int mode_index;
+ unsigned int interval_index;
+
+ if (interval_enum->index > 0)
+ return -EINVAL;
+ /*
+ * Multiple modes with the same dimensions may have different frame
+ * intervals, so look up each relevant mode.
+ */
+ for (mode_index = 0, interval_index = 0;
+ mode_index < ARRAY_SIZE(ov8865_modes); mode_index++) {
+ mode = &ov8865_modes[mode_index];
+
+ if (mode->output_size_x == interval_enum->width &&
+ mode->output_size_y == interval_enum->height) {
+ if (interval_index == interval_enum->index)
+ break;
+
+ interval_index++;
+ }
+ }
+
+ if (mode_index == ARRAY_SIZE(ov8865_modes) || !mode)
+ return -EINVAL;
+
+ interval_enum->interval = mode->frame_interval;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = {
+ .enum_mbus_code = ov8865_enum_mbus_code,
+ .get_fmt = ov8865_get_fmt,
+ .set_fmt = ov8865_set_fmt,
+ .enum_frame_size = ov8865_enum_frame_size,
+ .enum_frame_interval = ov8865_enum_frame_interval,
+};
+
+static const struct v4l2_subdev_ops ov8865_subdev_ops = {
+ .video = &ov8865_subdev_video_ops,
+ .pad = &ov8865_subdev_pad_ops,
+};
+
+static int ov8865_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct ov8865_state *state = &sensor->state;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ if (state->streaming) {
+ ret = ov8865_sw_standby(sensor, true);
+ if (ret)
+ goto complete;
+ }
+
+ ret = ov8865_sensor_power(sensor, false);
+ if (ret)
+ ov8865_sw_standby(sensor, false);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov8865_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+ struct ov8865_state *state = &sensor->state;
+ int ret = 0;
+
+ mutex_lock(&sensor->mutex);
+
+ ret = ov8865_sensor_power(sensor, true);
+ if (ret)
+ goto complete;
+
+ ret = ov8865_sensor_init(sensor);
+ if (ret)
+ goto error_power;
+
+ ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler);
+ if (ret)
+ goto error_power;
+
+ if (state->streaming) {
+ ret = ov8865_sw_standby(sensor, false);
+ if (ret)
+ goto error_power;
+ }
+
+ goto complete;
+
+error_power:
+ ov8865_sensor_power(sensor, false);
+
+complete:
+ mutex_unlock(&sensor->mutex);
+
+ return ret;
+}
+
+static int ov8865_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fwnode_handle *handle;
+ struct ov8865_sensor *sensor;
+ struct v4l2_subdev *subdev;
+ struct media_pad *pad;
+ unsigned long rate;
+ int ret;
+
+ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ sensor->dev = dev;
+ sensor->i2c_client = client;
+
+ /* Graph Endpoint */
+
+ handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+ if (!handle) {
+ dev_err(dev, "unable to find endpoint node\n");
+ return -EINVAL;
+ }
+
+ sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY;
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(handle, &sensor->endpoint);
+ fwnode_handle_put(handle);
+ if (ret) {
+ dev_err(dev, "failed to parse endpoint node\n");
+ return ret;
+ }
+
+ /* GPIOs */
+
+ sensor->powerdown = devm_gpiod_get_optional(dev, "powerdown",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->powerdown)) {
+ ret = PTR_ERR(sensor->powerdown);
+ goto error_endpoint;
+ }
+
+ sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(sensor->reset)) {
+ ret = PTR_ERR(sensor->reset);
+ goto error_endpoint;
+ }
+
+ /* Regulators */
+
+ /* DVDD: digital core */
+ sensor->dvdd = devm_regulator_get(dev, "dvdd");
+ if (IS_ERR(sensor->dvdd)) {
+ dev_err(dev, "cannot get DVDD (digital core) regulator\n");
+ ret = PTR_ERR(sensor->dvdd);
+ goto error_endpoint;
+ }
+
+ /* DOVDD: digital I/O */
+ sensor->dovdd = devm_regulator_get(dev, "dovdd");
+ if (IS_ERR(sensor->dovdd)) {
+ dev_err(dev, "cannot get DOVDD (digital I/O) regulator\n");
+ ret = PTR_ERR(sensor->dovdd);
+ goto error_endpoint;
+ }
+
+ /* AVDD: analog */
+ sensor->avdd = devm_regulator_get(dev, "avdd");
+ if (IS_ERR(sensor->avdd)) {
+ dev_err(dev, "cannot get AVDD (analog) regulator\n");
+ ret = PTR_ERR(sensor->avdd);
+ goto error_endpoint;
+ }
+
+ /* External Clock */
+
+ sensor->extclk = devm_clk_get(dev, NULL);
+ if (IS_ERR(sensor->extclk)) {
+ dev_err(dev, "failed to get external clock\n");
+ ret = PTR_ERR(sensor->extclk);
+ goto error_endpoint;
+ }
+
+ rate = clk_get_rate(sensor->extclk);
+ if (rate != OV8865_EXTCLK_RATE) {
+ dev_err(dev, "clock rate %lu Hz is unsupported\n", rate);
+ ret = -EINVAL;
+ goto error_endpoint;
+ }
+
+ /* Subdev, entity and pad */
+
+ subdev = &sensor->subdev;
+ v4l2_i2c_subdev_init(subdev, client, &ov8865_subdev_ops);
+
+ subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ subdev->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+ pad = &sensor->pad;
+ pad->flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&subdev->entity, 1, pad);
+ if (ret)
+ goto error_entity;
+
+ /* Mutex */
+
+ mutex_init(&sensor->mutex);
+
+ /* Sensor */
+
+ ret = ov8865_ctrls_init(sensor);
+ if (ret)
+ goto error_mutex;
+
+ ret = ov8865_state_init(sensor);
+ if (ret)
+ goto error_ctrls;
+
+ /* Runtime PM */
+
+ pm_runtime_enable(sensor->dev);
+ pm_runtime_set_suspended(sensor->dev);
+
+ /* V4L2 subdev register */
+
+ ret = v4l2_async_register_subdev_sensor_common(subdev);
+ if (ret)
+ goto error_pm;
+
+ return 0;
+
+error_pm:
+ pm_runtime_disable(sensor->dev);
+
+error_ctrls:
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+
+error_mutex:
+ mutex_destroy(&sensor->mutex);
+
+error_entity:
+ media_entity_cleanup(&sensor->subdev.entity);
+
+error_endpoint:
+ v4l2_fwnode_endpoint_free(&sensor->endpoint);
+
+ return ret;
+}
+
+static int ov8865_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev);
+
+ v4l2_async_unregister_subdev(subdev);
+ pm_runtime_disable(sensor->dev);
+ v4l2_ctrl_handler_free(&sensor->ctrls.handler);
+ mutex_destroy(&sensor->mutex);
+ media_entity_cleanup(&subdev->entity);
+
+ v4l2_fwnode_endpoint_free(&sensor->endpoint);
+
+ return 0;
+}
+
+static const struct dev_pm_ops ov8865_pm_ops = {
+ SET_RUNTIME_PM_OPS(ov8865_suspend, ov8865_resume, NULL)
+};
+
+static const struct of_device_id ov8865_of_match[] = {
+ { .compatible = "ovti,ov8865" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ov8865_of_match);
+
+static struct i2c_driver ov8865_driver = {
+ .driver = {
+ .name = "ov8865",
+ .of_match_table = ov8865_of_match,
+ .pm = &ov8865_pm_ops,
+ },
+ .probe_new = ov8865_probe,
+ .remove = ov8865_remove,
+};
+
+module_i2c_driver(ov8865_driver);
+
+MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
+MODULE_DESCRIPTION("V4L2 driver for the OmniVision OV8865 image sensor");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/ov9640.c b/drivers/media/i2c/ov9640.c
index e2a25240fc85..d36b04c49628 100644
--- a/drivers/media/i2c/ov9640.c
+++ b/drivers/media/i2c/ov9640.c
@@ -17,6 +17,7 @@
* Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de>
*/
+#include <linux/clk.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
@@ -26,7 +27,6 @@
#include <linux/videodev2.h>
#include <media/v4l2-async.h>
-#include <media/v4l2-clk.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
@@ -333,13 +333,13 @@ static int ov9640_s_power(struct v4l2_subdev *sd, int on)
if (on) {
gpiod_set_value(priv->gpio_power, 1);
usleep_range(1000, 2000);
- ret = v4l2_clk_enable(priv->clk);
+ ret = clk_prepare_enable(priv->clk);
usleep_range(1000, 2000);
gpiod_set_value(priv->gpio_reset, 0);
} else {
gpiod_set_value(priv->gpio_reset, 1);
usleep_range(1000, 2000);
- v4l2_clk_disable(priv->clk);
+ clk_disable_unprepare(priv->clk);
usleep_range(1000, 2000);
gpiod_set_value(priv->gpio_power, 0);
}
@@ -719,7 +719,7 @@ static int ov9640_probe(struct i2c_client *client,
priv->subdev.ctrl_handler = &priv->hdl;
- priv->clk = v4l2_clk_get(&client->dev, "mclk");
+ priv->clk = devm_clk_get(&client->dev, "mclk");
if (IS_ERR(priv->clk)) {
ret = PTR_ERR(priv->clk);
goto ectrlinit;
@@ -727,17 +727,15 @@ static int ov9640_probe(struct i2c_client *client,
ret = ov9640_video_probe(client);
if (ret)
- goto eprobe;
+ goto ectrlinit;
priv->subdev.dev = &client->dev;
ret = v4l2_async_register_subdev(&priv->subdev);
if (ret)
- goto eprobe;
+ goto ectrlinit;
return 0;
-eprobe:
- v4l2_clk_put(priv->clk);
ectrlinit:
v4l2_ctrl_handler_free(&priv->hdl);
@@ -749,7 +747,6 @@ static int ov9640_remove(struct i2c_client *client)
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ov9640_priv *priv = to_ov9640_sensor(sd);
- v4l2_clk_put(priv->clk);
v4l2_async_unregister_subdev(&priv->subdev);
v4l2_ctrl_handler_free(&priv->hdl);
diff --git a/drivers/media/i2c/ov9640.h b/drivers/media/i2c/ov9640.h
index a8ed6992c1a8..c105594b2472 100644
--- a/drivers/media/i2c/ov9640.h
+++ b/drivers/media/i2c/ov9640.h
@@ -196,7 +196,7 @@ struct ov9640_reg {
struct ov9640_priv {
struct v4l2_subdev subdev;
struct v4l2_ctrl_handler hdl;
- struct v4l2_clk *clk;
+ struct clk *clk;
struct gpio_desc *gpio_power;
struct gpio_desc *gpio_reset;
diff --git a/drivers/media/i2c/rdacm20.c b/drivers/media/i2c/rdacm20.c
index 16bcb764b0e0..90eb73f0e6e9 100644
--- a/drivers/media/i2c/rdacm20.c
+++ b/drivers/media/i2c/rdacm20.c
@@ -435,7 +435,7 @@ static int rdacm20_get_fmt(struct v4l2_subdev *sd,
return 0;
}
-static struct v4l2_subdev_video_ops rdacm20_video_ops = {
+static const struct v4l2_subdev_video_ops rdacm20_video_ops = {
.s_stream = rdacm20_s_stream,
};
@@ -445,7 +445,7 @@ static const struct v4l2_subdev_pad_ops rdacm20_subdev_pad_ops = {
.set_fmt = rdacm20_get_fmt,
};
-static struct v4l2_subdev_ops rdacm20_subdev_ops = {
+static const struct v4l2_subdev_ops rdacm20_subdev_ops = {
.video = &rdacm20_video_ops,
.pad = &rdacm20_subdev_pad_ops,
};
diff --git a/drivers/media/i2c/rdacm21.c b/drivers/media/i2c/rdacm21.c
new file mode 100644
index 000000000000..dcc21515e5a4
--- /dev/null
+++ b/drivers/media/i2c/rdacm21.c
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IMI RDACM21 GMSL Camera Driver
+ *
+ * Copyright (C) 2017-2020 Jacopo Mondi
+ * Copyright (C) 2017-2019 Kieran Bingham
+ * Copyright (C) 2017-2019 Laurent Pinchart
+ * Copyright (C) 2017-2019 Niklas Söderlund
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ * Copyright (C) 2015 Cogent Embedded, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/fwnode.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+#include "max9271.h"
+
+#define MAX9271_RESET_CYCLES 10
+
+#define OV490_I2C_ADDRESS 0x24
+
+#define OV490_PAGE_HIGH_REG 0xfffd
+#define OV490_PAGE_LOW_REG 0xfffe
+
+/*
+ * The SCCB slave handling is undocumented; the registers naming scheme is
+ * totally arbitrary.
+ */
+#define OV490_SCCB_SLAVE_WRITE 0x00
+#define OV490_SCCB_SLAVE_READ 0x01
+#define OV490_SCCB_SLAVE0_DIR 0x80195000
+#define OV490_SCCB_SLAVE0_ADDR_HIGH 0x80195001
+#define OV490_SCCB_SLAVE0_ADDR_LOW 0x80195002
+
+#define OV490_DVP_CTRL3 0x80286009
+
+#define OV490_ODS_CTRL_FRAME_OUTPUT_EN 0x0c
+#define OV490_ODS_CTRL 0x8029d000
+
+#define OV490_HOST_CMD 0x808000c0
+#define OV490_HOST_CMD_TRIGGER 0xc1
+
+#define OV490_ID_VAL 0x0490
+#define OV490_ID(_p, _v) ((((_p) & 0xff) << 8) | ((_v) & 0xff))
+#define OV490_PID 0x8080300a
+#define OV490_VER 0x8080300b
+#define OV490_PID_TIMEOUT 20
+#define OV490_OUTPUT_EN_TIMEOUT 300
+
+#define OV490_GPIO0 BIT(0)
+#define OV490_SPWDN0 BIT(0)
+#define OV490_GPIO_SEL0 0x80800050
+#define OV490_GPIO_SEL1 0x80800051
+#define OV490_GPIO_DIRECTION0 0x80800054
+#define OV490_GPIO_DIRECTION1 0x80800055
+#define OV490_GPIO_OUTPUT_VALUE0 0x80800058
+#define OV490_GPIO_OUTPUT_VALUE1 0x80800059
+
+#define OV490_ISP_HSIZE_LOW 0x80820060
+#define OV490_ISP_HSIZE_HIGH 0x80820061
+#define OV490_ISP_VSIZE_LOW 0x80820062
+#define OV490_ISP_VSIZE_HIGH 0x80820063
+
+#define OV10640_ID_HIGH 0xa6
+#define OV10640_CHIP_ID 0x300a
+#define OV10640_PIXEL_RATE 55000000
+
+struct rdacm21_device {
+ struct device *dev;
+ struct max9271_device serializer;
+ struct i2c_client *isp;
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+ struct v4l2_mbus_framefmt fmt;
+ struct v4l2_ctrl_handler ctrls;
+ u32 addrs[2];
+ u16 last_page;
+};
+
+static inline struct rdacm21_device *sd_to_rdacm21(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct rdacm21_device, sd);
+}
+
+static const struct ov490_reg {
+ u16 reg;
+ u8 val;
+} ov490_regs_wizard[] = {
+ {0xfffd, 0x80},
+ {0xfffe, 0x82},
+ {0x0071, 0x11},
+ {0x0075, 0x11},
+ {0xfffe, 0x29},
+ {0x6010, 0x01},
+ /*
+ * OV490 EMB line disable in YUV and RAW data,
+ * NOTE: EMB line is still used in ISP and sensor
+ */
+ {0xe000, 0x14},
+ {0xfffe, 0x28},
+ {0x6000, 0x04},
+ {0x6004, 0x00},
+ /*
+ * PCLK polarity - useless due to silicon bug.
+ * Use 0x808000bb register instead.
+ */
+ {0x6008, 0x00},
+ {0xfffe, 0x80},
+ {0x0091, 0x00},
+ /* bit[3]=0 - PCLK polarity workaround. */
+ {0x00bb, 0x1d},
+ /* Ov490 FSIN: app_fsin_from_fsync */
+ {0xfffe, 0x85},
+ {0x0008, 0x00},
+ {0x0009, 0x01},
+ /* FSIN0 source. */
+ {0x000A, 0x05},
+ {0x000B, 0x00},
+ /* FSIN0 delay. */
+ {0x0030, 0x02},
+ {0x0031, 0x00},
+ {0x0032, 0x00},
+ {0x0033, 0x00},
+ /* FSIN1 delay. */
+ {0x0038, 0x02},
+ {0x0039, 0x00},
+ {0x003A, 0x00},
+ {0x003B, 0x00},
+ /* FSIN0 length. */
+ {0x0070, 0x2C},
+ {0x0071, 0x01},
+ {0x0072, 0x00},
+ {0x0073, 0x00},
+ /* FSIN1 length. */
+ {0x0074, 0x64},
+ {0x0075, 0x00},
+ {0x0076, 0x00},
+ {0x0077, 0x00},
+ {0x0000, 0x14},
+ {0x0001, 0x00},
+ {0x0002, 0x00},
+ {0x0003, 0x00},
+ /*
+ * Load fsin0,load fsin1,load other,
+ * It will be cleared automatically.
+ */
+ {0x0004, 0x32},
+ {0x0005, 0x00},
+ {0x0006, 0x00},
+ {0x0007, 0x00},
+ {0xfffe, 0x80},
+ /* Sensor FSIN. */
+ {0x0081, 0x00},
+ /* ov10640 FSIN enable */
+ {0xfffe, 0x19},
+ {0x5000, 0x00},
+ {0x5001, 0x30},
+ {0x5002, 0x8c},
+ {0x5003, 0xb2},
+ {0xfffe, 0x80},
+ {0x00c0, 0xc1},
+ /* ov10640 HFLIP=1 by default */
+ {0xfffe, 0x19},
+ {0x5000, 0x01},
+ {0x5001, 0x00},
+ {0xfffe, 0x80},
+ {0x00c0, 0xdc},
+};
+
+static int ov490_read(struct rdacm21_device *dev, u16 reg, u8 *val)
+{
+ u8 buf[2] = { reg >> 8, reg };
+ int ret;
+
+ ret = i2c_master_send(dev->isp, buf, 2);
+ if (ret == 2)
+ ret = i2c_master_recv(dev->isp, val, 1);
+
+ if (ret < 0) {
+ dev_dbg(dev->dev, "%s: register 0x%04x read failed (%d)\n",
+ __func__, reg, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov490_write(struct rdacm21_device *dev, u16 reg, u8 val)
+{
+ u8 buf[3] = { reg >> 8, reg, val };
+ int ret;
+
+ ret = i2c_master_send(dev->isp, buf, 3);
+ if (ret < 0) {
+ dev_err(dev->dev, "%s: register 0x%04x write failed (%d)\n",
+ __func__, reg, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ov490_set_page(struct rdacm21_device *dev, u16 page)
+{
+ u8 page_high = page >> 8;
+ u8 page_low = page;
+ int ret;
+
+ if (page == dev->last_page)
+ return 0;
+
+ if (page_high != (dev->last_page >> 8)) {
+ ret = ov490_write(dev, OV490_PAGE_HIGH_REG, page_high);
+ if (ret)
+ return ret;
+ }
+
+ if (page_low != (u8)dev->last_page) {
+ ret = ov490_write(dev, OV490_PAGE_LOW_REG, page_low);
+ if (ret)
+ return ret;
+ }
+
+ dev->last_page = page;
+ usleep_range(100, 150);
+
+ return 0;
+}
+
+static int ov490_read_reg(struct rdacm21_device *dev, u32 reg, u8 *val)
+{
+ int ret;
+
+ ret = ov490_set_page(dev, reg >> 16);
+ if (ret)
+ return ret;
+
+ ret = ov490_read(dev, (u16)reg, val);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev->dev, "%s: 0x%08x = 0x%02x\n", __func__, reg, *val);
+
+ return 0;
+}
+
+static int ov490_write_reg(struct rdacm21_device *dev, u32 reg, u8 val)
+{
+ int ret;
+
+ ret = ov490_set_page(dev, reg >> 16);
+ if (ret)
+ return ret;
+
+ ret = ov490_write(dev, (u16)reg, val);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev->dev, "%s: 0x%08x = 0x%02x\n", __func__, reg, val);
+
+ return 0;
+}
+
+static int rdacm21_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct rdacm21_device *dev = sd_to_rdacm21(sd);
+
+ /*
+ * Enable serial link now that the ISP provides a valid pixel clock
+ * to start serializing video data on the GMSL link.
+ */
+ return max9271_set_serial_link(&dev->serializer, enable);
+}
+
+static int rdacm21_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->pad || code->index > 0)
+ return -EINVAL;
+
+ code->code = MEDIA_BUS_FMT_YUYV8_1X16;
+
+ return 0;
+}
+
+static int rdacm21_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct v4l2_mbus_framefmt *mf = &format->format;
+ struct rdacm21_device *dev = sd_to_rdacm21(sd);
+
+ if (format->pad)
+ return -EINVAL;
+
+ mf->width = dev->fmt.width;
+ mf->height = dev->fmt.height;
+ mf->code = MEDIA_BUS_FMT_YUYV8_1X16;
+ mf->colorspace = V4L2_COLORSPACE_SRGB;
+ mf->field = V4L2_FIELD_NONE;
+ mf->ycbcr_enc = V4L2_YCBCR_ENC_601;
+ mf->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ mf->xfer_func = V4L2_XFER_FUNC_NONE;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops rdacm21_video_ops = {
+ .s_stream = rdacm21_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops rdacm21_subdev_pad_ops = {
+ .enum_mbus_code = rdacm21_enum_mbus_code,
+ .get_fmt = rdacm21_get_fmt,
+ .set_fmt = rdacm21_get_fmt,
+};
+
+static const struct v4l2_subdev_ops rdacm21_subdev_ops = {
+ .video = &rdacm21_video_ops,
+ .pad = &rdacm21_subdev_pad_ops,
+};
+
+static int ov10640_initialize(struct rdacm21_device *dev)
+{
+ u8 val;
+
+ /* Power-up OV10640 by setting RESETB and PWDNB pins high. */
+ ov490_write_reg(dev, OV490_GPIO_SEL0, OV490_GPIO0);
+ ov490_write_reg(dev, OV490_GPIO_SEL1, OV490_SPWDN0);
+ ov490_write_reg(dev, OV490_GPIO_DIRECTION0, OV490_GPIO0);
+ ov490_write_reg(dev, OV490_GPIO_DIRECTION1, OV490_SPWDN0);
+ ov490_write_reg(dev, OV490_GPIO_OUTPUT_VALUE0, OV490_GPIO0);
+ ov490_write_reg(dev, OV490_GPIO_OUTPUT_VALUE0, OV490_SPWDN0);
+ usleep_range(3000, 5000);
+
+ /* Read OV10640 ID to test communications. */
+ ov490_write_reg(dev, OV490_SCCB_SLAVE0_DIR, OV490_SCCB_SLAVE_READ);
+ ov490_write_reg(dev, OV490_SCCB_SLAVE0_ADDR_HIGH, OV10640_CHIP_ID >> 8);
+ ov490_write_reg(dev, OV490_SCCB_SLAVE0_ADDR_LOW, (u8)OV10640_CHIP_ID);
+
+ /* Trigger SCCB slave transaction and give it some time to complete. */
+ ov490_write_reg(dev, OV490_HOST_CMD, OV490_HOST_CMD_TRIGGER);
+ usleep_range(1000, 1500);
+
+ ov490_read_reg(dev, OV490_SCCB_SLAVE0_DIR, &val);
+ if (val != OV10640_ID_HIGH) {
+ dev_err(dev->dev, "OV10640 ID mismatch: (0x%02x)\n", val);
+ return -ENODEV;
+ }
+
+ dev_dbg(dev->dev, "OV10640 ID = 0x%2x\n", val);
+
+ return 0;
+}
+
+static int ov490_initialize(struct rdacm21_device *dev)
+{
+ u8 pid, ver, val;
+ unsigned int i;
+ int ret;
+
+ /*
+ * Read OV490 Id to test communications. Give it up to 40msec to
+ * exit from reset.
+ */
+ for (i = 0; i < OV490_PID_TIMEOUT; ++i) {
+ ret = ov490_read_reg(dev, OV490_PID, &pid);
+ if (ret == 0)
+ break;
+ usleep_range(1000, 2000);
+ }
+ if (i == OV490_PID_TIMEOUT) {
+ dev_err(dev->dev, "OV490 PID read failed (%d)\n", ret);
+ return ret;
+ }
+
+ ret = ov490_read_reg(dev, OV490_VER, &ver);
+ if (ret < 0)
+ return ret;
+
+ if (OV490_ID(pid, ver) != OV490_ID_VAL) {
+ dev_err(dev->dev, "OV490 ID mismatch (0x%04x)\n",
+ OV490_ID(pid, ver));
+ return -ENODEV;
+ }
+
+ /* Wait for firmware boot by reading streamon status. */
+ for (i = 0; i < OV490_OUTPUT_EN_TIMEOUT; ++i) {
+ ov490_read_reg(dev, OV490_ODS_CTRL, &val);
+ if (val == OV490_ODS_CTRL_FRAME_OUTPUT_EN)
+ break;
+ usleep_range(1000, 2000);
+ }
+ if (i == OV490_OUTPUT_EN_TIMEOUT) {
+ dev_err(dev->dev, "Timeout waiting for firmware boot\n");
+ return -ENODEV;
+ }
+
+ ret = ov10640_initialize(dev);
+ if (ret)
+ return ret;
+
+ /* Program OV490 with register-value table. */
+ for (i = 0; i < ARRAY_SIZE(ov490_regs_wizard); ++i) {
+ ret = ov490_write(dev, ov490_regs_wizard[i].reg,
+ ov490_regs_wizard[i].val);
+ if (ret < 0) {
+ dev_err(dev->dev,
+ "%s: register %u (0x%04x) write failed (%d)\n",
+ __func__, i, ov490_regs_wizard[i].reg, ret);
+
+ return -EIO;
+ }
+
+ usleep_range(100, 150);
+ }
+
+ /*
+ * The ISP is programmed with the content of a serial flash memory.
+ * Read the firmware configuration to reflect it through the V4L2 APIs.
+ */
+ ov490_read_reg(dev, OV490_ISP_HSIZE_HIGH, &val);
+ dev->fmt.width = (val & 0xf) << 8;
+ ov490_read_reg(dev, OV490_ISP_HSIZE_LOW, &val);
+ dev->fmt.width |= (val & 0xff);
+
+ ov490_read_reg(dev, OV490_ISP_VSIZE_HIGH, &val);
+ dev->fmt.height = (val & 0xf) << 8;
+ ov490_read_reg(dev, OV490_ISP_VSIZE_LOW, &val);
+ dev->fmt.height |= val & 0xff;
+
+ /* Set bus width to 12 bits with [0:11] ordering. */
+ ov490_write_reg(dev, OV490_DVP_CTRL3, 0x10);
+
+ dev_info(dev->dev, "Identified RDACM21 camera module\n");
+
+ return 0;
+}
+
+static int rdacm21_initialize(struct rdacm21_device *dev)
+{
+ int ret;
+
+ /* Verify communication with the MAX9271: ping to wakeup. */
+ dev->serializer.client->addr = MAX9271_DEFAULT_ADDR;
+ i2c_smbus_read_byte(dev->serializer.client);
+ usleep_range(3000, 5000);
+
+ /* Enable reverse channel and disable the serial link. */
+ ret = max9271_set_serial_link(&dev->serializer, false);
+ if (ret)
+ return ret;
+
+ /* Configure I2C bus at 105Kbps speed and configure GMSL. */
+ ret = max9271_configure_i2c(&dev->serializer,
+ MAX9271_I2CSLVSH_469NS_234NS |
+ MAX9271_I2CSLVTO_1024US |
+ MAX9271_I2CMSTBT_105KBPS);
+ if (ret)
+ return ret;
+
+ ret = max9271_verify_id(&dev->serializer);
+ if (ret)
+ return ret;
+
+ /* Enable GPIO1 and hold OV490 in reset during max9271 configuration. */
+ ret = max9271_enable_gpios(&dev->serializer, MAX9271_GPIO1OUT);
+ if (ret)
+ return ret;
+
+ ret = max9271_clear_gpios(&dev->serializer, MAX9271_GPIO1OUT);
+ if (ret)
+ return ret;
+
+ ret = max9271_configure_gmsl_link(&dev->serializer);
+ if (ret)
+ return ret;
+
+ ret = max9271_set_address(&dev->serializer, dev->addrs[0]);
+ if (ret)
+ return ret;
+ dev->serializer.client->addr = dev->addrs[0];
+
+ ret = max9271_set_translation(&dev->serializer, dev->addrs[1],
+ OV490_I2C_ADDRESS);
+ if (ret)
+ return ret;
+ dev->isp->addr = dev->addrs[1];
+
+ /* Release OV490 from reset and initialize it. */
+ ret = max9271_set_gpios(&dev->serializer, MAX9271_GPIO1OUT);
+ if (ret)
+ return ret;
+ usleep_range(3000, 5000);
+
+ ret = ov490_initialize(dev);
+ if (ret)
+ return ret;
+
+ /*
+ * Set reverse channel high threshold to increase noise immunity.
+ *
+ * This should be compensated by increasing the reverse channel
+ * amplitude on the remote deserializer side.
+ */
+ return max9271_set_high_threshold(&dev->serializer, true);
+}
+
+static int rdacm21_probe(struct i2c_client *client)
+{
+ struct rdacm21_device *dev;
+ struct fwnode_handle *ep;
+ int ret;
+
+ dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+ dev->dev = &client->dev;
+ dev->serializer.client = client;
+
+ ret = of_property_read_u32_array(client->dev.of_node, "reg",
+ dev->addrs, 2);
+ if (ret < 0) {
+ dev_err(dev->dev, "Invalid DT reg property: %d\n", ret);
+ return -EINVAL;
+ }
+
+ /* Create the dummy I2C client for the sensor. */
+ dev->isp = i2c_new_dummy_device(client->adapter, OV490_I2C_ADDRESS);
+ if (IS_ERR(dev->isp))
+ return PTR_ERR(dev->isp);
+
+ ret = rdacm21_initialize(dev);
+ if (ret < 0)
+ goto error;
+
+ /* Initialize and register the subdevice. */
+ v4l2_i2c_subdev_init(&dev->sd, client, &rdacm21_subdev_ops);
+ dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ v4l2_ctrl_handler_init(&dev->ctrls, 1);
+ v4l2_ctrl_new_std(&dev->ctrls, NULL, V4L2_CID_PIXEL_RATE,
+ OV10640_PIXEL_RATE, OV10640_PIXEL_RATE, 1,
+ OV10640_PIXEL_RATE);
+ dev->sd.ctrl_handler = &dev->ctrls;
+
+ ret = dev->ctrls.error;
+ if (ret)
+ goto error_free_ctrls;
+
+ dev->pad.flags = MEDIA_PAD_FL_SOURCE;
+ dev->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR;
+ ret = media_entity_pads_init(&dev->sd.entity, 1, &dev->pad);
+ if (ret < 0)
+ goto error_free_ctrls;
+
+ ep = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL);
+ if (!ep) {
+ dev_err(&client->dev,
+ "Unable to get endpoint in node %pOF\n",
+ client->dev.of_node);
+ ret = -ENOENT;
+ goto error_free_ctrls;
+ }
+ dev->sd.fwnode = ep;
+
+ ret = v4l2_async_register_subdev(&dev->sd);
+ if (ret)
+ goto error_put_node;
+
+ return 0;
+
+error_put_node:
+ fwnode_handle_put(dev->sd.fwnode);
+error_free_ctrls:
+ v4l2_ctrl_handler_free(&dev->ctrls);
+error:
+ i2c_unregister_device(dev->isp);
+
+ return ret;
+}
+
+static int rdacm21_remove(struct i2c_client *client)
+{
+ struct rdacm21_device *dev = sd_to_rdacm21(i2c_get_clientdata(client));
+
+ v4l2_async_unregister_subdev(&dev->sd);
+ v4l2_ctrl_handler_free(&dev->ctrls);
+ i2c_unregister_device(dev->isp);
+ fwnode_handle_put(dev->sd.fwnode);
+
+ return 0;
+}
+
+static const struct of_device_id rdacm21_of_ids[] = {
+ { .compatible = "imi,rdacm21" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rdacm21_of_ids);
+
+static struct i2c_driver rdacm21_i2c_driver = {
+ .driver = {
+ .name = "rdacm21",
+ .of_match_table = rdacm21_of_ids,
+ },
+ .probe_new = rdacm21_probe,
+ .remove = rdacm21_remove,
+};
+
+module_i2c_driver(rdacm21_i2c_driver);
+
+MODULE_DESCRIPTION("GMSL Camera driver for RDACM21");
+MODULE_AUTHOR("Jacopo Mondi");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/st-mipid02.c b/drivers/media/i2c/st-mipid02.c
index 003ba22334cd..7f07ef56fbbd 100644
--- a/drivers/media/i2c/st-mipid02.c
+++ b/drivers/media/i2c/st-mipid02.c
@@ -92,7 +92,6 @@ struct mipid02_dev {
u64 link_frequency;
struct v4l2_fwnode_endpoint tx;
/* remote source */
- struct v4l2_async_subdev asd;
struct v4l2_async_notifier notifier;
struct v4l2_subdev *s_subdev;
/* registers */
@@ -844,6 +843,7 @@ static int mipid02_parse_rx_ep(struct mipid02_dev *bridge)
{
struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
struct i2c_client *client = bridge->i2c_client;
+ struct v4l2_async_subdev *asd;
struct device_node *ep_node;
int ret;
@@ -875,18 +875,17 @@ static int mipid02_parse_rx_ep(struct mipid02_dev *bridge)
bridge->rx = ep;
/* register async notifier so we get noticed when sensor is connected */
- bridge->asd.match.fwnode =
- fwnode_graph_get_remote_port_parent(of_fwnode_handle(ep_node));
- bridge->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+ v4l2_async_notifier_init(&bridge->notifier);
+ asd = v4l2_async_notifier_add_fwnode_remote_subdev(
+ &bridge->notifier,
+ of_fwnode_handle(ep_node),
+ struct v4l2_async_subdev);
of_node_put(ep_node);
- v4l2_async_notifier_init(&bridge->notifier);
- ret = v4l2_async_notifier_add_subdev(&bridge->notifier, &bridge->asd);
- if (ret) {
- dev_err(&client->dev, "fail to register asd to notifier %d",
- ret);
- fwnode_handle_put(bridge->asd.match.fwnode);
- return ret;
+ if (IS_ERR(asd)) {
+ dev_err(&client->dev, "fail to register asd to notifier %ld",
+ PTR_ERR(asd));
+ return PTR_ERR(asd);
}
bridge->notifier.ops = &mipid02_notifier_ops;