diff options
Diffstat (limited to 'drivers/media/i2c')
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 = ®s->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, ®); + 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, ®); + 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; |