Re: [PATCH/RFC v1 2/9] media: i2c: Add a driver for the onsemi AR0144 camera sensor

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Dave,

On Mon, Jul 01, 2024 at 05:49:20PM +0100, Dave Stevenson wrote:
> Hi Laurent
> 
> I have a couple of these modules, so I'll try to give this driver a
> test in the next few days.

Thank you. I'll cross my fingers :-)

> A couple of observations as I was reading through.
> 
> On Sun, 30 Jun 2024 at 15:18, Laurent Pinchart wrote:
> >
> > The AR0144 is a 1/4" 1MP CMOS camera sensor from onsemi. The driver
> > supports both the monochrome and color versions, and both the parallel
> > and MIPI CSI-2 interfaces. Due to limitations of the test platform, only
> > the CSI-2 output has been tested.
> >
> > The driver supports 8-, 10- and 12-bit output formats, analog crop and
> > binning/skipping. It exposes controls that cover the usual use cases for
> > camera sensors.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> > ---
> >  MAINTAINERS                |    1 +
> >  drivers/media/i2c/Kconfig  |   11 +
> >  drivers/media/i2c/Makefile |    1 +
> >  drivers/media/i2c/ar0144.c | 1826 ++++++++++++++++++++++++++++++++++++
> >  4 files changed, 1839 insertions(+)
> >  create mode 100644 drivers/media/i2c/ar0144.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 8e591445bec4..fff7554805dd 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -1642,6 +1642,7 @@ M:        Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> >  L:     linux-media@xxxxxxxxxxxxxxx
> >  S:     Maintained
> >  F:     Documentation/devicetree/bindings/media/i2c/onnn,ar0144.yaml
> > +F:     drivers/media/i2c/ar0144.c
> >
> >  AR0521 ON SEMICONDUCTOR CAMERA SENSOR DRIVER
> >  M:     Krzysztof Hałasa <khalasa@xxxxxxx>
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index d37944e667f3..122cfea07853 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -51,6 +51,17 @@ config VIDEO_ALVIUM_CSI2
> >           To compile this driver as a module, choose M here: the
> >           module will be called alvium-csi2.
> >
> > +config VIDEO_AR0144
> > +       tristate "onsemi AR0144 sensor support"
> > +       select V4L2_CCI_I2C
> > +       select VIDEO_CCS_PLL
> > +       help
> > +         This is a Video4Linux2 sensor driver for the onsemi AR0144 camera
> > +         sensor.
> > +
> > +         To compile this driver as a module, choose M here: the
> > +         module will be called ar0144.
> > +
> >  config VIDEO_AR0521
> >         tristate "ON Semiconductor AR0521 sensor support"
> >         help
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index 3ca63cec3441..c21b8df0d50c 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -19,6 +19,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
> >  obj-$(CONFIG_VIDEO_AK881X) += ak881x.o
> >  obj-$(CONFIG_VIDEO_ALVIUM_CSI2) += alvium-csi2.o
> >  obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o
> > +obj-$(CONFIG_VIDEO_AR0144) += ar0144.o
> >  obj-$(CONFIG_VIDEO_AR0521) += ar0521.o
> >  obj-$(CONFIG_VIDEO_BT819) += bt819.o
> >  obj-$(CONFIG_VIDEO_BT856) += bt856.o
> > diff --git a/drivers/media/i2c/ar0144.c b/drivers/media/i2c/ar0144.c
> > new file mode 100644
> > index 000000000000..b0e738a28de6
> > --- /dev/null
> > +++ b/drivers/media/i2c/ar0144.c
> > @@ -0,0 +1,1826 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2024 Ideas on Board Oy
> > + * Author: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
> > + */
> > +
> > +/*
> > + * Possible improvements:
> > + *
> > + * - Use grouped parameter hold to update controls atomically
> > + * - Expose debug information through debugfs (FRAME_COUNT, FRAME_STATUS, ...)
> > + * - Make MIPI D-PHY timings configurable
> > + * - Support the parallel interface
> > + * - Expose additional controls (in particular the temperature sensor and the
> > + *   on-chip black level correction)
> > + */
> > +
> > +#include <linux/bitops.h>
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/errno.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/ktime.h>
> > +#include <linux/log2.h>
> > +#include <linux/module.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/property.h>
> > +#include <linux/regmap.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/types.h>
> > +#include <linux/v4l2-mediabus.h>
> > +
> > +#include <media/mipi-csi2.h>
> > +#include <media/v4l2-async.h>
> > +#include <media/v4l2-cci.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +#include "ccs-pll.h"
> > +
> > +#define AR0144_CHIP_VERSION_REG                                        CCI_REG16(0x3000)
> > +#define                AR0144_CHIP_VERSION                                     0x0356
> > +#define AR0144_Y_ADDR_START                                    CCI_REG16(0x3002)
> > +#define AR0144_X_ADDR_START                                    CCI_REG16(0x3004)
> > +#define AR0144_Y_ADDR_END                                      CCI_REG16(0x3006)
> > +#define AR0144_X_ADDR_END                                      CCI_REG16(0x3008)
> > +#define AR0144_FRAME_LENGTH_LINES                              CCI_REG16(0x300a)
> > +#define                AR0144_MIN_FRAME_LENGTH_LINES                           29
> > +#define                AR0144_MAX_FRAME_LENGTH_LINES                           65535
> > +#define AR0144_LINE_LENGTH_PCK                                 CCI_REG16(0x300c)
> > +#define                AR0144_MIN_LINE_LENGTH_PCK                              1488
> > +#define                AR0144_MAX_LINE_LENGTH_PCK                              65534
> > +#define AR0144_REVISION_NUMBER                                 CCI_REG16(0x300e)
> > +#define                AR0144_REVISION_NUMBER_CREV(n)                          (((n) >> 12) & 0xf)
> > +#define                AR0144_REVISION_NUMBER_SILICON(n)                       (((n) >> 4) & 0xf)
> > +#define                AR0144_REVISION_NUMBER_OTPM(n)                          (((n) >> 0) & 0xf)
> > +#define AR0144_LOCK_CONTROL                                    CCI_REG16(0x3010)
> > +#define                AR0144_LOCK_CONTROL_VALUE                               0xbeef
> > +#define AR0144_COARSE_INTEGRATION_TIME                         CCI_REG16(0x3012)
> > +#define AR0144_FINE_INTEGRATION_TIME                           CCI_REG16(0x3014)
> > +#define AR0144_COARSE_INTEGRATION_TIME_CB                      CCI_REG16(0x3016)
> > +#define AR0144_FINE_INTEGRATION_TIME_CB                                CCI_REG16(0x3018)
> > +#define AR0144_RESET_REGISTER                                  CCI_REG16(0x301a)
> > +#define                AR0144_GROUPED_PARAMETER_HOLD                           BIT(15)
> > +#define                AR0144_SMIA_SERIALIZER_DIS                              BIT(12)
> > +#define                AR0144_FORCED_PLL_ON                                    BIT(11)
> > +#define                AR0144_RESTART_BAD                                      BIT(10)
> > +#define                AR0144_MASK_BAD                                         BIT(9)
> > +#define                AR0144_GPI_EN                                           BIT(8)
> > +#define                AR0144_PARALLEL_EN                                      BIT(7)
> > +#define                AR0144_DRIVE_PINS                                       BIT(6)
> > +#define                AR0144_LOCK_REG                                         BIT(3)
> > +#define                AR0144_STREAM                                           BIT(2)
> > +#define                AR0144_RESTART                                          BIT(1)
> > +#define                AR0144_RESET                                            BIT(0)
> > +#define AR0144_MODE_SELECT                                     CCI_REG8(0x301c)
> > +#define                AR0144_MODE_STREAM                                      BIT(0)
> > +#define AR0144_IMAGE_ORIENTATION                               CCI_REG8(0x301d)
> > +#define                AR0144_ORIENTATION_VERT_FLIP                            BIT(1)
> > +#define                AR0144_ORIENTATION_HORIZ_MIRROR                         BIT(0)
> > +#define AR0144_DATA_PEDESTAL                                   CCI_REG16(0x301e)
> > +#define AR0144_SOFTWARE_RESET                                  CCI_REG8(0x3021)
> > +#define AR0144_GROUPED_PARAMETER_HOLD_                         CCI_REG8(0x3022)
> > +#define AR0144_MASK_CORRUPTED_FRAMES                           CCI_REG8(0x3023)
> > +#define AR0144_PIXEL_ORDER                                     CCI_REG8(0x3024)
> > +#define AR0144_GPI_STATUS                                      CCI_REG16(0x3026)
> > +#define                AR0144_TRIGGER                                          BIT(2)
> > +#define                AR0144_OUTPUT_ENABLE_N                                  BIT(1)
> > +#define                AR0144_SADDR                                            BIT(0)
> > +#define AR0144_ROW_SPEED                                       CCI_REG16(0x3028)
> > +#define                AR0144_ROW_SPEED_HALF_CLK                               BIT(4)
> > +#define AR0144_VT_PIX_CLK_DIV                                  CCI_REG16(0x302a)
> > +#define AR0144_VT_SYS_CLK_DIV                                  CCI_REG16(0x302c)
> > +#define AR0144_PRE_PLL_CLK_DIV                                 CCI_REG16(0x302e)
> > +#define AR0144_PLL_MULTIPLIER                                  CCI_REG16(0x3030)
> > +#define AR0144_CTX_CONTROL_REG                                 CCI_REG16(0x3034)
> > +#define AR0144_OP_PIX_CLK_DIV                                  CCI_REG16(0x3036)
> > +#define AR0144_OP_SYS_CLK_DIV                                  CCI_REG16(0x3038)
> > +#define AR0144_FRAME_COUNT                                     CCI_REG16(0x303a)
> > +#define AR0144_FRAME_STATUS                                    CCI_REG16(0x303c)
> > +#define                AR0144_PLL_LOCKED                                       BIT(3)
> > +#define                AR0144_FRAME_START_DURING_GPH                           BIT(2)
> > +#define                AR0144_STANDBY_STATUS                                   BIT(1)
> > +#define                AR0144_FRAMESYNC                                        BIT(0)
> > +#define AR0144_LINE_LENGTH_PCK_CB                              CCI_REG16(0x303e)
> > +#define AR0144_READ_MODE                                       CCI_REG16(0x3040)
> > +#define                AR0144_VERT_FLIP                                        BIT(15)
> > +#define                AR0144_HORIZ_MIRROR                                     BIT(14)
> > +#define                AR0144_READ_MODE_COL_BIN                                BIT(13)
> > +#define                AR0144_READ_MODE_ROW_BIN                                BIT(12)
> > +#define                AR0144_READ_MODE_COL_BIN_CB                             BIT(11)
> > +#define                AR0144_READ_MODE_ROW_BIN_CB                             BIT(10)
> > +#define                AR0144_READ_MODE_COL_SF_BIN_EN                          BIT(9)
> > +#define                AR0144_READ_MODE_COL_SF_BIN_EN_CB                       BIT(8)
> > +#define                AR0144_READ_MODE_COL_SF_BIN_MONO_EN                     BIT(7)
> > +#define                AR0144_READ_MODE_COL_SF_BIN_MONO_EN_CB                  BIT(6)
> > +#define                AR0144_READ_MODE_COL_SUM                                BIT(5)
> > +#define AR0144_EXTRA_DELAY                                     CCI_REG16(0x3042)
> > +#define AR0144_GREEN1_GAIN                                     CCI_REG16(0x3056)
> > +#define AR0144_BLUE_GAIN                                       CCI_REG16(0x3058)
> > +#define AR0144_RED_GAIN                                                CCI_REG16(0x305a)
> > +#define AR0144_GREEN2_GAIN                                     CCI_REG16(0x305c)
> > +#define AR0144_GLOBAL_GAIN                                     CCI_REG16(0x305e)
> > +#define AR0144_ANALOG_GAIN                                     CCI_REG16(0x3060)
> > +#define                AR0144_COARSE_GAIN_CB(n)                                ((n) << 12)
> > +#define                AR0144_FINE_GAIN_CB(n)                                  ((n) << 8)
> > +#define                AR0144_COARSE_GAIN(n)                                   ((n) << 4)
> > +#define                AR0144_FINE_GAIN(n)                                     ((n) << 0)
> > +#define AR0144_SMIA_TEST                                       CCI_REG16(0x3064)
> > +#define                AR0144_EMBEDDED_DATA                                    BIT(8)
> > +#define                AR0144_STATS_EN                                         BIT(7)
> > +#define AR0144_CTX_WR_DATA_REG                                 CCI_REG16(0x3066)
> > +#define AR0144_CTX_RD_DATA_REG                                 CCI_REG16(0x3068)
> > +#define AR0144_DATAPATH_SELECT                                 CCI_REG16(0x306e)
> > +#define                AR0144_SLEW_RATE_CTRL_PARALLEL(n)                       (((n) & 7) << 13)
> > +#define                AR0144_SLEW_RATE_CTRL_PARALLEL_MASK                     GENMASK(15, 13)
> > +#define                AR0144_SLEW_RATE_CTRL_PIXCLK(n)                         (((n) & 7) << 10)
> > +#define                AR0144_SLOW_RATE_CTRL_PIXCLK_MASK                       GENMASK(12, 10)
> > +#define                AR0144_SPECIAL_LINE_VALID_NORMAL                        (0U << 0)
> > +#define                AR0144_SPECIAL_LINE_VALID_CONTINUOUS                    (1U << 0)
> > +#define                AR0144_SPECIAL_LINE_VALID_CONTINUOUS_XOR                (2U << 0)
> > +#define AR0144_TEST_PATTERN_MODE                               CCI_REG16(0x3070)
> > +#define                AR0144_TEST_PATTERN_NONE                                0
> > +#define                AR0144_TEST_PATTERN_SOLID                               1
> > +#define                AR0144_TEST_PATTERN_BARS                                2
> > +#define                AR0144_TEST_PATTERN_BARS_FADE                           3
> > +#define                AR0144_TEST_PATTERN_WALKING_1S                          256
> > +#define AR0144_TEST_DATA_RED                                   CCI_REG16(0x3072)
> > +#define AR0144_TEST_DATA_GREENR                                        CCI_REG16(0x3074)
> > +#define AR0144_TEST_DATA_BLUE                                  CCI_REG16(0x3076)
> > +#define AR0144_TEST_DATA_GREENB                                        CCI_REG16(0x3078)
> > +#define AR0144_OPERATION_MODE_CTRL                             CCI_REG16(0x3082)
> > +#define AR0144_OPERATION_MODE_CTRL_CB                          CCI_REG16(0x3084)
> > +#define AR0144_SEQ_DATA_PORT                                   CCI_REG16(0x3086)
> > +#define AR0144_SEQ_CTRL_PORT                                   CCI_REG16(0x3088)
> > +#define                AR0144_SEQUENCER_STOPPED                                BIT(15)
> > +#define                AR0144_AUTO_INC_ON_READ                                 BIT(14)
> > +#define                AR0144_ACCESS_ADDRESS(n)                                ((n) & 0x3ff)
> > +#define AR0144_X_ADDR_START_CB                                 CCI_REG16(0x308a)
> > +#define AR0144_Y_ADDR_START_CB                                 CCI_REG16(0x308c)
> > +#define AR0144_X_ADDR_END_CB                                   CCI_REG16(0x308e)
> > +#define AR0144_Y_ADDR_END_CB                                   CCI_REG16(0x3090)
> > +#define AR0144_X_EVEN_INC                                      CCI_REG16(0x30a0)
> > +#define AR0144_X_ODD_INC                                       CCI_REG16(0x30a2)
> > +#define                AR0144_X_ODD_INC_SKIP(n)                                (((n) << 1) - 1)
> > +#define AR0144_Y_EVEN_INC                                      CCI_REG16(0x30a4)
> > +#define AR0144_Y_ODD_INC                                       CCI_REG16(0x30a6)
> > +#define                AR0144_Y_ODD_INC_SKIP(n)                                (((n) << 1) - 1)
> > +#define AR0144_Y_ODD_INC_CB                                    CCI_REG16(0x30a8)
> > +#define AR0144_FRAME_LINE_LENGTH_CB                            CCI_REG16(0x30aa)
> > +#define AR0144_X_ODD_INC_CB                                    CCI_REG16(0x30ae)
> > +#define AR0144_DIGITAL_TEST                                    CCI_REG16(0x30b0)
> > +#define                AR0144_PLL_COMPLETE_BYPASS                              BIT(14)
> > +#define                AR0144_PIXCLK_ON                                        BIT(8)
> > +#define                AR0144_MONO_CHROME_OPERATION                            BIT(7)
> > +#define AR0144_TEMPSENS_DATA_REG                               CCI_REG16(0x30b2)
> > +#define AR0144_TEMPSENS_CTRL_REG                               CCI_REG16(0x30b4)
> > +#define                AR0144_RETRIGGER_THRESHOLD(n)                           ((n) << 6)
> > +#define                AR0144_RETRIGGER_THRESHOLD_MASK                         GENMASK(15, 6)
> > +#define                AR0144_TEMP_CLEAR_VALUE                                 BIT(5)
> > +#define                AR0144_TEMP_START_CONVERSION                            BIT(4)
> > +#define                AR0144_TEMPSENS_POWER_ON                                BIT(0)
> > +#define AR0144_GREEN1_GAIN_CB                                  CCI_REG16(0x30bc)
> > +#define AR0144_BLUE_GAIN_CB                                    CCI_REG16(0x30be)
> > +#define AR0144_RED_GAIN_CB                                     CCI_REG16(0x30c0)
> > +#define AR0144_GREEN2_GAIN_CB                                  CCI_REG16(0x30c2)
> > +#define AR0144_GLOBAL_GAIN_CB                                  CCI_REG16(0x30c4)
> > +#define AR0144_TEMPSENS_CALIB1                                 CCI_REG16(0x30c6)
> > +#define AR0144_TEMPSENS_CALIB2                                 CCI_REG16(0x30c8)
> > +#define AR0144_GRR_CONTROL1                                    CCI_REG16(0x30ce)
> > +#define AR0144_NOISE_PEDESTAL                                  CCI_REG16(0x30fe)
> > +#define AR0144_AECTRLREG                                       CCI_REG16(0x3100)
> > +#define                AR0144_MIN_ANA_GAIN(n)                                  ((n) << 5)
> > +#define                AR0144_MIN_ANA_GAIN_MASK                                GENMASK(6, 5)
> > +#define                AR0144_AUTO_DG_EN                                       BIT(4)
> > +#define                AR0144_AUTO_AG_EN                                       BIT(1)
> > +#define                AR0144_AE_ENABLE                                        BIT(0)
> > +#define AR0144_AE_LUMA_TARGET_REG                              CCI_REG16(0x3102)
> > +#define AR0144_AE_MIN_EV_STEP_REG                              CCI_REG16(0x3108)
> > +#define AR0144_AE_MAX_EV_STEP_REG                              CCI_REG16(0x310a)
> > +#define AR0144_AE_DAMP_OFFSET_REG                              CCI_REG16(0x310c)
> > +#define AR0144_AE_DAMP_GAIN_REG                                        CCI_REG16(0x310e)
> > +#define AR0144_AE_DAMP_MAX_REG                                 CCI_REG16(0x3110)
> > +#define AR0144_AE_MAX_EXPOSURE_REG                             CCI_REG16(0x311c)
> > +#define AR0144_AE_MIN_EXPOSURE_REG                             CCI_REG16(0x311e)
> > +#define AR0144_AE_COARSE_INTEGRATION_TIME                      CCI_REG16(0x3164)
> > +#define AR0144_DELTA_DK_CONTROL                                        CCI_REG16(0x3180)
> > +#define                AR0144_DELTA_DK_SUB_EN                                  BIT(15)
> > +#define                AR0144_DELTA_DK_EVERY_FRAME                             BIT(14)
> > +#define                AR0144_DELTA_DK_RECALC                                  BIT(13)
> > +#define                AR0144_DELTA_DK_GRADIENT_REMOVAL                        BIT(10)
> > +#define AR0144_DATA_FORMAT_BITS                                        CCI_REG16(0x31ac)
> > +#define                AR0144_DATA_FORMAT_IN(n)                                ((n) << 8)
> > +#define                AR0144_DATA_FORMAT_OUT(n)                               ((n) << 0)
> > +#define AR0144_SERIAL_FORMAT                                   CCI_REG16(0x31ae)
> > +#define                AR0144_NUM_LANES(n)                                     (n)
> > +#define                AR0144_NUM_LANES_MASK                                   GENMASK(1, 0)
> > +#define AR0144_FRAME_PREAMBLE                                  CCI_REG16(0x31b0)
> > +#define AR0144_LINE_PREAMBLE                                   CCI_REG16(0x31b2)
> > +#define AR0144_MIPI_TIMING_0                                   CCI_REG16(0x31b4)
> > +#define                AR0144_T_HS_PREPARE(n)                                  ((n) << 12)
> > +#define                AR0144_T_HS_ZERO(n)                                     ((n) << 8)
> > +#define                AR0144_T_HS_TRAIL(n)                                    ((n) << 4)
> > +#define                AR0144_T_CLK_TRAIL(n)                                   ((n) << 0)
> > +#define AR0144_MIPI_TIMING_1                                   CCI_REG16(0x31b6)
> > +#define                AR0144_T_CLK_PREPARE(n)                                 ((n) << 12)
> > +#define                AR0144_T_HS_EXIT(n)                                     ((n) << 6)
> > +#define                AR0144_T_CLK_ZERO(n)                                    ((n) << 0)
> > +#define AR0144_MIPI_TIMING_2                                   CCI_REG16(0x31b8)
> > +#define                AR0144_T_BGAP(n)                                        ((n) << 12)
> > +#define                AR0144_T_CLK_PRE(n)                                     ((n) << 6)
> > +#define                AR0144_T_CLK_POST(n)                                    ((n) << 0)
> > +#define AR0144_MIPI_TIMING_3                                   CCI_REG16(0x31ba)
> > +#define                AR0144_T_LPX(n)                                         ((n) << 7)
> > +#define                AR0144_T_WAKE_UP(n)                                     ((n) << 0)
> > +#define AR0144_MIPI_TIMING_4                                   CCI_REG16(0x31bc)
> > +#define                AR0144_CONT_TX_CLK                                      BIT(15)
> > +#define                AR0144_HEAVY_LP_LOAD                                    BIT(14)
> > +#define                AR0144_T_INIT(n)                                        ((n) << 0)
> > +#define AR0144_SERIAL_CONFIG_STATUS                            CCI_REG16(0x31be)
> > +#define AR0144_SERIAL_CONTROL_STATUS                           CCI_REG16(0x31c6)
> > +#define                AR0144_FRAMER_TEST_MODE                                 BIT(7)
> > +#define AR0144_SERIAL_CRC_0                                    CCI_REG16(0x31c8)
> > +#define AR0144_COMPANDING                                      CCI_REG16(0x31d0)
> > +#define                AR0144_COMPAND_EN                                       BIT(0)
> > +#define AR0144_STAT_FRAME_ID                                   CCI_REG16(0x31d2)
> > +#define AR0144_I2C_WRT_CHEKCSUM                                        CCI_REG16(0x31d6)
> > +#define AR0144_SERIAL_TEST                                     CCI_REG16(0x31d8)
> > +#define                AR0144_TEST_LANE_EN(n)                                  ((n) << 8)
> > +#define                AR0144_TEST_LANE_EN_MASK                                GENMASK(11, 8)
> > +#define                AR0144_TEST_MODE_LP00                                   (0 << 4)
> > +#define                AR0144_TEST_MODE_LP11                                   (1 << 4)
> > +#define                AR0144_TEST_MODE_HS0                                    (2 << 4)
> > +#define                AR0144_TEST_MODE_HS1                                    (3 << 4)
> > +#define                AR0144_TEST_MODE_SQUARE_HALF                            (4 << 4)
> > +#define                AR0144_TEST_MODE_SQUARE_FULL                            (5 << 4)
> > +#define                AR0144_TEST_MODE_SQUARE_LP                              (6 << 4)
> > +#define                AR0144_TEST_MODE_PRBS31                                 (7 << 4)
> > +#define                AR0144_TEST_MODE_PRBS9                                  (8 << 4)
> > +#define                AR0144_TEST_MODE_MASK                                   GENMASK(7, 0)
> > +#define AR0144_PIX_DEF_1D                                      CCI_REG16(0x31e0)
> > +#define                AR0144_PIX_DEF_1D_DDC_EN                                BIT(3)
> > +#define                AR0144_PIX_DEF_CORRECTION_MODE                          BIT(1)
> > +#define                AR0144_PIX_DEF_ENABLE                                   BIT(0)
> > +#define AR0144_HORIZONTAL_CURSOR_POSITION                      CCI_REG16(0x31e8)
> > +#define AR0144_VERTICAL_CURSOR_POSITION                                CCI_REG16(0x31ea)
> > +#define AR0144_HORIZONTAL_CURSOR_WIDTH                         CCI_REG16(0x31ec)
> > +#define AR0144_VERTICAL_CURSOR_WIDTH                           CCI_REG16(0x31ee)
> > +#define AR0144_CCI_IDS                                         CCI_REG16(0x31fc)
> > +#define AR0144_CUSTOMER_REV                                    CCI_REG16(0x31fe)
> > +#define                AR0144_CUSTOMER_REV_CRA(n)                              (((n) & GENMASK(9, 7)) >> 7)
> > +#define                AR0144_CUSTOMER_REV_CFA(n)                              (((n) & GENMASK(6, 4)) >> 4)
> > +#define                AR0144_CUSTOMER_REV_CFA_COLOR                           1
> > +#define                AR0144_CUSTOMER_REV_CFA_MONO                            2
> > +#define AR0144_LED_FLASH_CONTROL                               CCI_REG16(0x3270)
> > +#define                AR0144_LED_FLASH_EN                                     BIT(8)
> > +#define                AR0144_LED_DELAY(n)                                     ((n) << 0)
> > +#define                AR0144_LED_DELAY_MASK                                   GENMASK(7, 0)
> > +#define AR0144_MIPI_TEST_CNTRL                                 CCI_REG16(0x3338)
> > +#define AR0144_MIPI_COMPRESS_8_DATA_TYPE                       CCI_REG16(0x333a)
> > +#define AR0144_MIPI_COMPRESS_7_DATA_TYPE                       CCI_REG16(0x333c)
> > +#define AR0144_MIPI_COMPRESS_6_DATA_TYPE                       CCI_REG16(0x333e)
> > +#define AR0144_MIPI_JPEG_PN9_DATA_TYPE                         CCI_REG16(0x3340)
> > +#define AR0144_MIPI_CNTRL                                      CCI_REG16(0x3354)
> > +#define                AR0144_CHAN_NUM(n)                                      ((n) << 6)
> > +#define                AR0144_DATA_TYPE_XMIT(n)                                ((n) << 0)
> > +#define AR0144_MIPI_TEST_PATTERN_CNTRL                         CCI_REG16(0x3356)
> > +#define AR0144_MIPI_TEST_PATTERN_STATUS                                CCI_REG16(0x3358)
> > +#define AR0144_DIGITAL_CTRL_1                                  CCI_REG16(0x3786)
> > +#define                AR0144_LSB_ALIGN_PARA_OUT                               BIT(5)
> > +#define                AR0144_USE_1FRAME_SYNCED                                BIT(4)
> > +#define                AR0144_PLL_TEST_MODE                                    BIT(3)
> > +#define                AR0144_DRIVE_PIX_CLK                                    BIT(0)
> > +
> > +#define AR0144_MIN_CSI2_LINK_FREQ              150000000U
> > +#define AR0144_MAX_CSI2_LINK_FREQ              384000000U
> > +
> > +/*
> > + * The pixel array contains 1300x820 optically transparent pixels, with 6 dummy
> > + * pixels on each side that can't be read out. The active size is 1288x808.
> > + */
> > +#define AR0144_PIXEL_ARRAY_WIDTH               1300U
> > +#define AR0144_PIXEL_ARRAY_HEIGHT              820U
> > +#define AR0144_PIXEL_ARRAY_ACTIVE_LEFT         6U
> > +#define AR0144_PIXEL_ARRAY_ACTIVE_TOP          6U
> > +#define AR0144_PIXEL_ARRAY_ACTIVE_WIDTH                1288U
> > +#define AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT       808U
> > +
> > +/*
> > + * Documentation indicates minimum horizontal and vertical blanking of 208
> > + * pixels and 27 lines respectively, which matches the default values for the
> > + * LINE_LENGTH_PCK and FRAME_LENGTH_LINES registers. Out-of-tree drivers are
> > + * known to successfully use 22 or 25 lines of vertical blanking. This is
> > + * likely related to the fact that the total number of rows per frame is equal
> > + * to FRAME_LENGTH_LINES plus a 5 lines overhead, as indicated in at least one
> > + * place in the documentation.
> > + */
> > +#define AR0144_MIN_HBLANK                      208U
> > +#define AR0144_MIN_VBLANK                      27U
> > +
> > +#define AR0144_FRAME_LENGTH_OVERHEAD           5U
> > +
> > +/* The minimum values are not documented, pick reasonable minimums. */
> > +#define AR0144_MIN_WIDTH                       32U
> > +#define AR0144_MIN_HEIGHT                      32U
> > +#define AR0144_DEF_WIDTH                       1280U
> > +#define AR0144_DEF_HEIGHT                      800U
> > +
> > +#define AR0144_NUM_SUPPLIES                    3
> > +
> > +struct ar0144_model {
> > +       bool mono;
> > +};
> > +
> > +struct ar0144_format_info {
> > +       u32 colour;
> > +       u32 mono;
> > +       u16 bpp;
> > +       u16 dt;
> > +};
> > +
> > +static const struct ar0144_model ar0144_model_color = {
> > +       .mono = false,
> > +};
> > +
> > +static const struct ar0144_model ar0144_model_mono = {
> > +       .mono = true,
> > +};
> > +
> > +static const struct ar0144_format_info ar0144_formats[] = {
> > +       {
> > +               .colour = MEDIA_BUS_FMT_SGRBG12_1X12,
> > +               .mono = MEDIA_BUS_FMT_Y12_1X12,
> > +               .bpp = 12,
> > +               .dt = MIPI_CSI2_DT_RAW12,
> > +       }, {
> > +               .colour = MEDIA_BUS_FMT_SGRBG10_1X10,
> > +               .mono = MEDIA_BUS_FMT_Y10_1X10,
> > +               .bpp = 10,
> > +               .dt = MIPI_CSI2_DT_RAW10,
> > +       }, {
> > +               .colour = MEDIA_BUS_FMT_SGRBG8_1X8,
> > +               .mono = MEDIA_BUS_FMT_Y8_1X8,
> > +               .bpp = 8,
> > +               .dt = MIPI_CSI2_DT_RAW8,
> > +       },
> > +};
> > +
> > +struct ar0144 {
> > +       struct device *dev;
> > +
> > +       struct regmap *regmap;
> > +       struct clk *clk;
> > +       struct gpio_desc *reset;
> > +       struct regulator_bulk_data supplies[AR0144_NUM_SUPPLIES];
> > +
> > +       ktime_t off_time;
> > +
> > +       struct ccs_pll pll;
> > +
> > +       struct v4l2_fwnode_endpoint bus_cfg;
> > +       u64 valid_link_freqs[ARRAY_SIZE(ar0144_formats)];
> > +       u32 valid_formats;
> > +
> > +       struct v4l2_subdev sd;
> > +       struct media_pad pad;
> > +
> > +       const struct ar0144_model *model;
> > +
> > +       struct v4l2_ctrl_handler ctrls;
> > +       struct v4l2_ctrl *pixel_rate;
> > +       struct v4l2_ctrl *link_freq;
> > +       struct v4l2_ctrl *hblank;
> > +       struct v4l2_ctrl *vblank;
> > +       struct v4l2_ctrl *exposure;
> > +       struct v4l2_ctrl *test_data[4];
> > +       struct {
> > +               struct v4l2_ctrl *hflip;
> > +               struct v4l2_ctrl *vflip;
> > +       };
> > +};
> > +
> > +static inline struct ar0144 *to_ar0144(struct v4l2_subdev *sd)
> > +{
> > +       return container_of(sd, struct ar0144, sd);
> > +}
> > +
> > +static u32 ar0144_format_code(struct ar0144 *sensor,
> > +                             const struct ar0144_format_info *info)
> > +{
> > +       return sensor->model->mono ? info->mono : info->colour;
> > +}
> > +
> > +static const struct ar0144_format_info *
> > +ar0144_format_info(struct ar0144 *sensor, u32 code, bool use_def)
> > +{
> > +       const struct ar0144_format_info *def = NULL;
> > +       unsigned int i;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(ar0144_formats); ++i) {
> > +               const struct ar0144_format_info *info = &ar0144_formats[i];
> > +               u32 info_code = ar0144_format_code(sensor, info);
> > +
> > +               if (!(sensor->valid_formats & BIT(i)))
> > +                       continue;
> > +
> > +               if (info_code == code)
> > +                       return info;
> > +
> > +               if (!def)
> > +                       def = info;
> > +       }
> > +
> > +       return use_def ? def : NULL;
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Hardware configuration
> > + */
> > +
> > +static int ar0144_configure_pll(struct ar0144 *sensor)
> > +{
> > +       int ret = 0;
> > +
> > +       cci_write(sensor->regmap, AR0144_PRE_PLL_CLK_DIV,
> > +                 sensor->pll.vt_fr.pre_pll_clk_div, &ret);
> > +       cci_write(sensor->regmap, AR0144_PLL_MULTIPLIER,
> > +                 sensor->pll.vt_fr.pll_multiplier, &ret);
> > +       cci_write(sensor->regmap, AR0144_VT_PIX_CLK_DIV,
> > +                 sensor->pll.vt_bk.pix_clk_div, &ret);
> > +       cci_write(sensor->regmap, AR0144_VT_SYS_CLK_DIV,
> > +                 sensor->pll.vt_bk.sys_clk_div, &ret);
> > +       cci_write(sensor->regmap, AR0144_OP_PIX_CLK_DIV,
> > +                 sensor->pll.op_bk.pix_clk_div, &ret);
> > +       cci_write(sensor->regmap, AR0144_OP_SYS_CLK_DIV,
> > +                 sensor->pll.op_bk.sys_clk_div, &ret);
> > +
> > +       /* Wait 1ms for the PLL to lock. */
> > +       fsleep(1000);
> > +
> > +       return ret;
> > +}
> > +
> > +static int ar0144_configure_mipi(struct ar0144 *sensor,
> > +                                const struct ar0144_format_info *info)
> > +{
> > +       unsigned int num_lanes = sensor->bus_cfg.bus.mipi_csi2.num_data_lanes;
> > +       bool cont_clk = !(sensor->bus_cfg.bus.mipi_csi2.flags &
> > +                         V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK);
> > +       int ret = 0;
> > +
> > +       cci_write(sensor->regmap, AR0144_SERIAL_FORMAT, num_lanes | 0x0200,
> > +                 &ret);
> > +
> > +       cci_write(sensor->regmap, AR0144_MIPI_CNTRL,
> > +                 AR0144_CHAN_NUM(0) | AR0144_DATA_TYPE_XMIT(info->dt), &ret);
> > +
> > +       cci_write(sensor->regmap, AR0144_MIPI_TIMING_0,
> > +                 AR0144_T_HS_PREPARE(2) | AR0144_T_HS_ZERO(6) |
> > +                 AR0144_T_HS_TRAIL(6) | AR0144_T_CLK_TRAIL(5), &ret);
> > +       cci_write(sensor->regmap, AR0144_MIPI_TIMING_1,
> > +                 AR0144_T_CLK_PREPARE(1) | AR0144_T_HS_EXIT(4) |
> > +                 AR0144_T_CLK_ZERO(14), &ret);
> > +       cci_write(sensor->regmap, AR0144_MIPI_TIMING_2,
> > +                 AR0144_T_BGAP(2) | AR0144_T_CLK_PRE(1) | AR0144_T_CLK_POST(7),
> > +                 &ret);
> > +       cci_write(sensor->regmap, AR0144_MIPI_TIMING_3,
> > +                 AR0144_T_LPX(2) | AR0144_T_WAKE_UP(5), &ret);
> > +       cci_write(sensor->regmap, AR0144_MIPI_TIMING_4,
> > +                 AR0144_T_INIT(4) | cont_clk ? AR0144_CONT_TX_CLK : 0,
> 
> Does changing to non-continuous clock require HBLANK to be increased
> to provide enough time for the transitions? I hit that on OV9281/9282.

I'm using non-continuous clocking, and the default hblank, which is also
the minimum, seems to work fine.

> > +                 &ret);
> > +
> > +       return ret;
> > +}
> > +
> > +static int ar0144_start_streaming(struct ar0144 *sensor,
> > +                                 const struct v4l2_subdev_state *state)
> > +{
> > +       const struct v4l2_mbus_framefmt *format;
> > +       const struct ar0144_format_info *info;
> > +       const struct v4l2_rect *crop;
> > +       unsigned int bin_x, bin_y;
> > +       int ret = 0;
> > +       u16 val;
> > +
> > +       format = v4l2_subdev_state_get_format(state, 0);
> > +       crop = v4l2_subdev_state_get_crop(state, 0);
> > +       info = ar0144_format_info(sensor, format->code, true);
> > +
> > +       ret = ar0144_configure_pll(sensor);
> > +       if (ret)
> > +               return ret;
> > +
> > +       cci_write(sensor->regmap, AR0144_DIGITAL_TEST,
> > +                 (sensor->model->mono ? AR0144_MONO_CHROME_OPERATION : 0) |
> > +                 0x0038, &ret);
> > +
> > +       cci_write(sensor->regmap, AR0144_DATA_FORMAT_BITS,
> > +                 AR0144_DATA_FORMAT_IN(info->bpp) |
> > +                 AR0144_DATA_FORMAT_OUT(info->bpp), &ret);
> > +
> > +       /* Analog crop rectangle, binning/skipping. */
> > +       cci_write(sensor->regmap, AR0144_X_ADDR_START, crop->left, &ret);
> > +       cci_write(sensor->regmap, AR0144_Y_ADDR_START, crop->top, &ret);
> > +       cci_write(sensor->regmap, AR0144_X_ADDR_END,
> > +                 crop->left + crop->width - 1, &ret);
> > +       cci_write(sensor->regmap, AR0144_Y_ADDR_END,
> > +                 crop->top + crop->height - 1, &ret);
> > +
> > +       bin_x = crop->width / format->width;
> > +       bin_y = crop->height / format->height;
> > +
> > +       cci_write(sensor->regmap, AR0144_X_ODD_INC, (bin_x << 1) - 1, &ret);
> > +       cci_write(sensor->regmap, AR0144_Y_ODD_INC, (bin_y << 1) - 1, &ret);
> 
> Your commit text claims support for binning and skipping. From my
> reading of the datasheet, this is enabling skipping only. Binning
> needs to be enabled through 0x3040 (which also sets H/V flips).
> (The registers for a 640x400 mode we were given by OnSemi was
> certainly setting 0x3040 to 0x0c00)

True. I'll update the commit message. We need to extend the userspace
API to give userspace more precise configuration over binning and
skipping. This is something Sakari and I have previously discussed and I
expect we'll send a proposal at some point.

> > +
> > +       /*
> > +        * Enable generation of embedded statistics, required for the on-chip
> > +        * auto-exposure. There is no downside in enabling it unconditionally.
> > +        */
> > +       cci_write(sensor->regmap, AR0144_SMIA_TEST, AR0144_STATS_EN | 0x1802,
> > +                 &ret);
> > +
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = __v4l2_ctrl_handler_setup(&sensor->ctrls);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = ar0144_configure_mipi(sensor, info);
> > +
> > +       /*
> > +        * We're all set, start streaming. Mask bad frames and keep read-only
> > +        * registers locked.
> > +        */
> > +       val = AR0144_MASK_BAD | AR0144_LOCK_REG | AR0144_STREAM | 0x2010;
> > +       cci_write(sensor->regmap, AR0144_RESET_REGISTER, val, &ret);
> > +
> > +       return ret;
> > +}
> > +
> > +#define ar0144_read_poll_timeout(sensor, addr, val, cond, sleep_us, timeout_us) \
> > +({ \
> > +       int __ret, __err; \
> > +       __ret = read_poll_timeout(cci_read, __err, __err || (cond), sleep_us, \
> > +                                 timeout_us, false, (sensor)->regmap, addr, \
> > +                                 &(val), NULL); \
> > +       __ret ? : __err; \
> > +})
> > +
> > +static int ar0144_stop_streaming(struct ar0144 *sensor)
> > +{
> > +       u16 reset_bits = AR0144_MASK_BAD | AR0144_LOCK_REG | 0x2010;
> > +       int ret;
> > +       u64 val;
> > +
> > +       /*
> > +        * Initiate the transition to standby by clearing the STREAM bit. Don't
> > +        * clear the bits that affect the output interface yet.
> > +        */
> > +       ret = cci_write(sensor->regmap, AR0144_RESET_REGISTER, reset_bits, NULL);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = ar0144_read_poll_timeout(sensor, AR0144_FRAME_STATUS, val,
> > +                                      val & AR0144_STANDBY_STATUS, 2000,
> > +                                      2000000);
> > +       if (ret)
> > +               dev_warn(sensor->dev, "%s while trying to enter standby (%d)\n",
> > +                        ret == -ETIMEDOUT ? "timeout" : "error", ret);
> > +
> > +       /* Standby state reached, disable the output interface. */
> > +       val |= AR0144_SMIA_SERIALIZER_DIS;
> > +       val &= ~(AR0144_PARALLEL_EN | AR0144_DRIVE_PINS);
> > +
> > +       return cci_write(sensor->regmap, AR0144_RESET_REGISTER, reset_bits, NULL);
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * PLL
> > + */
> > +
> > +static int ar0144_pll_calculate(struct ar0144 *sensor, struct ccs_pll *pll,
> > +                               unsigned int link_freq, unsigned int bpp)
> > +{
> > +       struct ccs_pll_limits limits = {
> > +               .min_ext_clk_freq_hz = 6000000,
> > +               .max_ext_clk_freq_hz = 48000000,
> > +
> > +               .vt_fr = {
> > +                       .min_pre_pll_clk_div = 1,
> > +                       .max_pre_pll_clk_div = 63,
> > +                       .min_pll_ip_clk_freq_hz = 1000000,      /* min_pll_op_clk_freq_hz / max_pll_multiplier */
> > +                       .max_pll_ip_clk_freq_hz = 24000000,     /* max_pll_op_clk_freq_hz / min_pll_multiplier */
> > +                       .min_pll_multiplier = 32,
> > +                       .max_pll_multiplier = 384,
> > +                       .min_pll_op_clk_freq_hz = 384000000,
> > +                       .max_pll_op_clk_freq_hz = 768000000,
> > +               },
> > +               .vt_bk = {
> > +                       .min_sys_clk_div = 1,
> > +                       .max_sys_clk_div = 16,
> > +                       .min_pix_clk_div = 4,
> > +                       .max_pix_clk_div = 16,
> > +                       .min_pix_clk_freq_hz = 6000000,         /* No documented limit */
> > +                       .max_pix_clk_freq_hz = 74250000,
> > +               },
> > +               .op_bk = {
> > +                       .min_sys_clk_div = 1,
> > +                       .max_sys_clk_div = 16,
> > +                       .min_pix_clk_div = 8,
> > +                       .max_pix_clk_div = 12,
> > +                       .min_pix_clk_freq_hz = 6000000,         /* No documented limit */
> > +                       .max_pix_clk_freq_hz = 74250000,
> > +               },
> > +
> > +               .min_line_length_pck_bin = 1200 + AR0144_MIN_HBLANK,    /* To be checked */
> > +               .min_line_length_pck = 1200 + AR0144_MIN_HBLANK,
> > +       };
> > +       unsigned int num_lanes = sensor->bus_cfg.bus.mipi_csi2.num_data_lanes;
> > +       int ret;
> > +
> > +       /*
> > +        * The OP pixel clock limits depends on the number of lanes, which the
> > +        * PLL calculator doesn't take into account despite specifying the
> > +        * CCS_PLL_FLAG_LANE_SPEED_MODEL flag. Adjust them manually.
> > +        */
> > +       limits.op_bk.min_pix_clk_freq_hz /= num_lanes;
> > +       limits.op_bk.max_pix_clk_freq_hz /= num_lanes;
> > +
> > +       /*
> > +        * There are no documented constraints on the sys clock frequency, for
> > +        * either branch. Recover them based on the PLL output clock frequency
> > +        * and sys_clk_div limits on one hand, and the pix clock frequency and
> > +        * the pix_clk_div limits on the other hand.
> > +        */
> > +       limits.vt_bk.min_sys_clk_freq_hz =
> > +               max(limits.vt_fr.min_pll_op_clk_freq_hz / limits.vt_bk.max_sys_clk_div,
> > +                   limits.vt_bk.min_pix_clk_freq_hz * limits.vt_bk.min_pix_clk_div);
> > +       limits.vt_bk.max_sys_clk_freq_hz =
> > +               min(limits.vt_fr.max_pll_op_clk_freq_hz / limits.vt_bk.min_sys_clk_div,
> > +                   limits.vt_bk.max_pix_clk_freq_hz * limits.vt_bk.max_pix_clk_div);
> > +
> > +       limits.op_bk.min_sys_clk_freq_hz =
> > +               max(limits.vt_fr.min_pll_op_clk_freq_hz / limits.op_bk.max_sys_clk_div,
> > +                   limits.op_bk.min_pix_clk_freq_hz * limits.op_bk.min_pix_clk_div);
> > +       limits.op_bk.max_sys_clk_freq_hz =
> > +               min(limits.vt_fr.max_pll_op_clk_freq_hz / limits.op_bk.min_sys_clk_div,
> > +                   limits.op_bk.max_pix_clk_freq_hz * limits.op_bk.max_pix_clk_div);
> > +
> > +       memset(pll, 0, sizeof(*pll));
> > +
> > +       pll->bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY;
> > +       pll->op_lanes = num_lanes;
> > +       pll->vt_lanes = 1;
> > +       pll->csi2.lanes = num_lanes;
> > +       /*
> > +        * As the sensor doesn't support FIFO derating, binning doesn't
> > +        * influence the PLL configuration. Hardcode the binning factors.
> > +        */
> > +       pll->binning_horizontal = 1;
> > +       pll->binning_vertical = 1;
> > +       pll->scale_m = 1;
> > +       pll->scale_n = 1;
> > +       pll->bits_per_pixel = bpp;
> > +       pll->flags = CCS_PLL_FLAG_LANE_SPEED_MODEL;
> > +       pll->link_freq = link_freq;
> > +       pll->ext_clk_freq_hz = clk_get_rate(sensor->clk);
> > +
> > +       ret = ccs_pll_calculate(sensor->dev, &limits, pll);
> > +       if (ret)
> > +               return ret;
> > +
> > +       /*
> > +        * The sensor ignores the LSB of the PLL multiplier. If the multiplier
> > +        * is an odd value, as a workaround to avoid precision loss, multiply
> > +        * both the pre-divider and the multiplier by 2 if this doesn't bring
> > +        * them beyond their maximum values. This doesn't necessarily guarantee
> > +        * optimum PLL parameters. Ideally the PLL calculator should handle
> > +        * this constraint.
> > +        */
> > +       if ((pll->vt_fr.pll_multiplier & 1) &&
> > +           pll->vt_fr.pll_multiplier * 2 <= limits.vt_fr.max_pll_multiplier &&
> > +           pll->vt_fr.pre_pll_clk_div * 2 <= limits.vt_fr.max_pre_pll_clk_div) {
> > +               pll->vt_fr.pll_multiplier *= 2;
> > +               pll->vt_fr.pre_pll_clk_div *= 2;
> > +       }
> > +
> > +       if (pll->vt_fr.pll_multiplier & 1)
> > +               dev_warn(sensor->dev,
> > +                        "Odd PLL multiplier, link frequency %u will not be exact\n",
> > +                        pll->link_freq);
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_pll_update(struct ar0144 *sensor,
> > +                            const struct ar0144_format_info *info)
> > +{
> > +       u64 link_freq;
> > +       int ret;
> > +
> > +       link_freq = sensor->bus_cfg.link_frequencies[sensor->link_freq->val];
> > +       ret = ar0144_pll_calculate(sensor, &sensor->pll, link_freq, info->bpp);
> > +       if (ret) {
> > +               dev_err(sensor->dev, "PLL calculations failed: %d\n", ret);
> > +               return ret;
> > +       }
> > +
> > +       __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate,
> > +                                sensor->pll.pixel_rate_pixel_array);
> > +
> > +       return 0;
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * V4L2 controls
> > + */
> > +
> > +static const char * const ar0144_test_pattern_menu[] = {
> > +       "Disabled",
> > +       "Solid color",
> > +       "Full Color Bars",
> > +       "Fade to Gray Color Bars",
> > +       "Walking 1",
> > +};
> > +
> > +static const u32 ar0144_test_pattern_values[] = {
> > +       AR0144_TEST_PATTERN_NONE,
> > +       AR0144_TEST_PATTERN_SOLID,
> > +       AR0144_TEST_PATTERN_BARS,
> > +       AR0144_TEST_PATTERN_BARS_FADE,
> > +       AR0144_TEST_PATTERN_WALKING_1S,
> > +};
> > +
> > +static void ar0144_update_link_freqs(struct ar0144 *sensor,
> > +                                    const struct ar0144_format_info *info)
> > +{
> > +       u64 valid_link_freqs;
> > +       unsigned int index;
> > +       unsigned int min;
> > +       unsigned int max;
> > +
> > +       index = info - ar0144_formats;
> > +       valid_link_freqs = sensor->valid_link_freqs[index];
> > +
> > +       min = __ffs(valid_link_freqs);
> > +       max = __fls(valid_link_freqs);
> > +
> > +       __v4l2_ctrl_modify_range(sensor->link_freq, min, max, ~valid_link_freqs,
> > +                                min);
> > +}
> > +
> > +static int ar0144_update_exposure(struct ar0144 *sensor,
> > +                                 const struct v4l2_rect *crop)
> > +{
> > +       unsigned int max;
> > +
> > +       /* The coarse integration time is limited to FRAME_LENGTH_LINES - 1. */
> > +       max = crop->height + sensor->vblank->val - 1
> > +           - AR0144_FRAME_LENGTH_OVERHEAD;
> > +       return __v4l2_ctrl_modify_range(sensor->exposure, 1, max, 1, max);
> > +}
> > +
> > +static void ar0144_update_blanking(struct ar0144 *sensor,
> > +                                  const struct v4l2_subdev_state *state)
> > +{
> > +       const struct v4l2_rect *crop;
> > +       unsigned int min;
> > +       unsigned int max;
> > +
> > +       crop = v4l2_subdev_state_get_crop(state, 0);
> > +
> > +       /*
> > +        * Horizontally, the line length (in pixel clocks), programmed in the
> > +        * LINE_LENGTH_PCK register, is equal to the sum of the crop width and
> > +        * the horizontal blanking. The sensor has lower and upper bounds for
> > +        * the LINE_LENGTH_PCK value, as well as a lower bound for the
> > +        * horizontal blanking.
> > +        */
> > +       min = max_t(int, AR0144_MIN_LINE_LENGTH_PCK - crop->width,
> > +                   AR0144_MIN_HBLANK);
> > +       max = AR0144_MAX_LINE_LENGTH_PCK - crop->width;
> > +       __v4l2_ctrl_modify_range(sensor->hblank, min, max, 2, min);
> > +
> > +       /*
> > +        * Vertically, the situation is more complicated. The frame length (in
> > +        * lines) is equal to the FRAME_LENGTH_LINES register value plus 5
> > +        * lines of overhead. This needs to be taken into account for the
> > +        * VBLANK calculation.
> > +        */
> > +       min = max_t(int, AR0144_MIN_FRAME_LENGTH_LINES - crop->height +
> > +                   AR0144_FRAME_LENGTH_OVERHEAD, AR0144_MIN_VBLANK);
> > +       max = AR0144_MAX_FRAME_LENGTH_LINES - crop->height
> > +           + AR0144_FRAME_LENGTH_OVERHEAD;
> > +       __v4l2_ctrl_modify_range(sensor->vblank, min, max, 1, min);
> > +
> > +       ar0144_update_exposure(sensor, crop);
> > +}
> > +
> > +static int ar0144_s_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +       struct ar0144 *sensor = container_of(ctrl->handler, struct ar0144, ctrls);
> > +       const struct v4l2_subdev_state *state;
> > +       const struct v4l2_mbus_framefmt *format;
> > +       const struct ar0144_format_info *info;
> > +       const struct v4l2_rect *crop;
> > +       int ret = 0;
> > +
> > +       /*
> > +        * Return immediately for controls that don't need to be applied to the
> > +        * device.
> > +        */
> > +       if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)
> > +               return 0;
> > +
> > +       /*
> > +        * First process controls that update other parts of the device
> > +        * configuration.
> > +        */
> > +       state = v4l2_subdev_get_locked_active_state(&sensor->sd);
> > +       format = v4l2_subdev_state_get_format(state, 0);
> > +       info = ar0144_format_info(sensor, format->code, true);
> > +       crop = v4l2_subdev_state_get_crop(state, 0);
> > +
> > +       switch (ctrl->id) {
> > +       case V4L2_CID_VBLANK:
> > +               ret = ar0144_update_exposure(sensor, crop);
> > +               break;
> > +
> > +       case V4L2_CID_LINK_FREQ:
> > +               if (v4l2_subdev_is_streaming(&sensor->sd))
> > +                       return -EBUSY;
> 
> Is this potentially better achieved by grabbing the control ta start_streaming?

I'll give it a try.

> > +
> > +               ret = ar0144_pll_update(sensor, info);
> > +               break;
> > +
> > +       default:
> > +               break;
> > +       }
> > +
> > +       if (ret)
> > +               return ret;
> > +
> > +       /*
> > +        * Apply controls to the hardware. If power is down, they controls will
> > +        * be applied when starting streaming.
> > +        */
> > +       if (!pm_runtime_get_if_in_use(sensor->dev))
> > +               return 0;
> > +
> > +       switch (ctrl->id) {
> > +       case V4L2_CID_ANALOGUE_GAIN:
> > +               cci_write(sensor->regmap, AR0144_ANALOG_GAIN, ctrl->val, &ret);
> > +               break;
> > +
> > +       case V4L2_CID_DIGITAL_GAIN:
> > +               cci_write(sensor->regmap, AR0144_GLOBAL_GAIN, ctrl->val, &ret);
> > +               break;
> > +
> > +       case V4L2_CID_EXPOSURE:
> > +               cci_write(sensor->regmap, AR0144_COARSE_INTEGRATION_TIME,
> > +                         ctrl->val, &ret);
> > +               break;
> > +
> > +       case V4L2_CID_HBLANK:
> > +               cci_write(sensor->regmap, AR0144_LINE_LENGTH_PCK,
> > +                         crop->width + ctrl->val, &ret);
> > +               break;
> > +
> > +       case V4L2_CID_VBLANK:
> > +               cci_write(sensor->regmap, AR0144_FRAME_LENGTH_LINES,
> > +                         crop->height + ctrl->val - AR0144_FRAME_LENGTH_OVERHEAD,
> > +                         &ret);
> > +               break;
> > +
> > +       case V4L2_CID_TEST_PATTERN:
> > +               cci_write(sensor->regmap, AR0144_TEST_PATTERN_MODE,
> > +                         ar0144_test_pattern_values[ctrl->val], &ret);
> > +
> > +               /*
> > +                * Register 0x3044 is not documented, but mentioned in the test
> > +                * pattern configuration. Bits [5:4] should be set to 0 to
> > +                * avoid clipping pixel values to 0xf70.
> > +                */
> > +               cci_write(sensor->regmap, CCI_REG16(0x3044),
> > +                         ctrl->val ? 0x0400 : 0x0410, &ret);
> > +               break;
> > +
> > +       case V4L2_CID_TEST_PATTERN_RED:
> > +       case V4L2_CID_TEST_PATTERN_GREENR:
> > +       case V4L2_CID_TEST_PATTERN_BLUE:
> > +       case V4L2_CID_TEST_PATTERN_GREENB:
> > +               cci_write(sensor->regmap, AR0144_GROUPED_PARAMETER_HOLD_,
> > +                         1, &ret);
> > +
> > +               cci_write(sensor->regmap, AR0144_TEST_DATA_RED,
> > +                         sensor->test_data[0]->val, &ret);
> > +               cci_write(sensor->regmap, AR0144_TEST_DATA_GREENR,
> > +                         sensor->test_data[1]->val, &ret);
> > +               cci_write(sensor->regmap, AR0144_TEST_DATA_BLUE,
> > +                         sensor->test_data[2]->val, &ret);
> > +               cci_write(sensor->regmap, AR0144_TEST_DATA_GREENB,
> > +                         sensor->test_data[3]->val, &ret);
> > +
> > +               cci_write(sensor->regmap, AR0144_GROUPED_PARAMETER_HOLD_,
> > +                         0, &ret);
> > +               break;
> > +
> > +       case V4L2_CID_HFLIP:
> > +       case V4L2_CID_VFLIP:
> > +       {
> > +               u16 reg = 0;
> > +
> > +               if (sensor->hflip->val)
> > +                       reg |= AR0144_ORIENTATION_HORIZ_MIRROR;
> > +               if (sensor->vflip->val)
> > +                       reg |= AR0144_ORIENTATION_VERT_FLIP;
> > +
> > +               cci_write(sensor->regmap, AR0144_IMAGE_ORIENTATION, reg, &ret);
> > +               break;
> > +       }
> > +
> > +       case V4L2_CID_EXPOSURE_AUTO:
> > +               cci_write(sensor->regmap, AR0144_AECTRLREG,
> > +                         ctrl->val == V4L2_EXPOSURE_AUTO ?
> > +                         AR0144_AUTO_AG_EN | AR0144_AE_ENABLE : 0, &ret);
> > +               break;
> > +
> > +       case V4L2_CID_LINK_FREQ:
> > +               break;
> > +
> > +       default:
> > +               ret = -EINVAL;
> > +               break;
> > +       }
> > +
> > +       pm_runtime_mark_last_busy(sensor->dev);
> > +       pm_runtime_put_autosuspend(sensor->dev);
> > +
> > +       return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops ar0144_ctrl_ops = {
> > +       .s_ctrl = ar0144_s_ctrl,
> > +};
> > +
> > +static int ar0144_init_ctrls(struct ar0144 *sensor)
> > +{
> > +       static const u32 test_pattern_ctrls[] = {
> > +               V4L2_CID_TEST_PATTERN_RED,
> > +               V4L2_CID_TEST_PATTERN_GREENR,
> > +               V4L2_CID_TEST_PATTERN_BLUE,
> > +               V4L2_CID_TEST_PATTERN_GREENB,
> > +       };
> > +       struct v4l2_fwnode_device_properties props;
> > +       unsigned int i;
> > +       int ret;
> > +
> > +       ret = v4l2_fwnode_device_parse(sensor->dev, &props);
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       v4l2_ctrl_handler_init(&sensor->ctrls, 17);
> > +
> > +       v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                       &props);
> > +
> > +       /*
> > +        * Set the link frequency, pixel rate, horizontal blanking and vertical
> > +        * blanking and exposure to hardcoded values. They will be updated by
> > +        * ar0144_update_link_freqs(), ar0144_pll_update() and
> > +        * ar0144_update_blanking().
> > +        */
> > +       sensor->pixel_rate =
> > +               v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                 V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
> > +       sensor->link_freq =
> > +               v4l2_ctrl_new_int_menu(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                      V4L2_CID_LINK_FREQ,
> > +                                      sensor->bus_cfg.nr_of_link_frequencies - 1, 0,
> > +                                      sensor->bus_cfg.link_frequencies);
> > +
> > +       sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                          V4L2_CID_HBLANK, AR0144_MIN_HBLANK,
> > +                                          AR0144_MIN_HBLANK, 2, AR0144_MIN_HBLANK);
> > +       sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                          V4L2_CID_VBLANK, AR0144_MIN_VBLANK,
> > +                                          AR0144_MIN_VBLANK, 1, AR0144_MIN_VBLANK);
> > +
> > +       sensor->exposure = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                            V4L2_CID_EXPOSURE, 1,
> > +                                            AR0144_MAX_FRAME_LENGTH_LINES - 1,
> > +                                            1, 16);
> > +
> > +       /*
> > +        * The sensor analogue gain is split in an exponential coarse gain and
> > +        * a fine gain. The minimum recommended gain is 1.6842, which maps to a
> > +        * gain code of 13. Set the minimum to 0 to expose the whole range of
> > +        * possible values, and the default to the recommended minimum.
> > +        */
> > +       v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                         V4L2_CID_ANALOGUE_GAIN, 0, 79, 1, 13);
> > +
> > +       v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                         V4L2_CID_DIGITAL_GAIN, 0, 0x7ff, 1, 0x080);
> > +
> > +       v4l2_ctrl_new_std_menu(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                              V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL,
> > +                              ~(BIT(V4L2_EXPOSURE_AUTO) | BIT(V4L2_EXPOSURE_MANUAL)),
> > +                              V4L2_EXPOSURE_MANUAL);
> > +
> > +       v4l2_ctrl_new_std_menu_items(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                    V4L2_CID_TEST_PATTERN,
> > +                                    ARRAY_SIZE(ar0144_test_pattern_menu) - 1,
> > +                                    0, 0, ar0144_test_pattern_menu);
> > +
> > +       for (i = 0; i < ARRAY_SIZE(test_pattern_ctrls); ++i)
> > +               sensor->test_data[i] =
> > +                       v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                         test_pattern_ctrls[i], 0, 4095, 1, 0);
> > +
> > +       sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                         V4L2_CID_HFLIP, 0, 1, 1, 0);
> > +       sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &ar0144_ctrl_ops,
> > +                                         V4L2_CID_VFLIP, 0, 1, 1, 0);
> > +
> > +       if (sensor->ctrls.error) {
> > +               ret = sensor->ctrls.error;
> > +               v4l2_ctrl_handler_free(&sensor->ctrls);
> > +               return ret;
> > +       }
> > +
> > +       v4l2_ctrl_cluster(4, sensor->test_data);
> > +       v4l2_ctrl_cluster(2, &sensor->hflip);
> > +
> > +       sensor->sd.ctrl_handler = &sensor->ctrls;
> > +
> > +       return 0;
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * V4L2 subdev operations
> > + */
> > +
> > +static int ar0144_enum_mbus_code(struct v4l2_subdev *sd,
> > +                                struct v4l2_subdev_state *state,
> > +                                struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +       unsigned int index = 0;
> > +       unsigned int i;
> > +
> > +       for (i = 0; i < ARRAY_SIZE(ar0144_formats); ++i) {
> > +               const struct ar0144_format_info *info = &ar0144_formats[i];
> > +
> > +               if (!(sensor->valid_formats & BIT(i)))
> > +                       continue;
> > +
> > +               if (code->index == index) {
> > +                       code->code = ar0144_format_code(sensor, info);
> > +                       return 0;
> > +               }
> > +
> > +               index++;
> > +       }
> > +
> > +       return -EINVAL;
> > +}
> > +
> > +static int ar0144_enum_frame_size(struct v4l2_subdev *sd,
> > +                                 struct v4l2_subdev_state *state,
> > +                                 struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +       const struct ar0144_format_info *info;
> > +       const struct v4l2_rect *crop;
> > +
> > +       info = ar0144_format_info(sensor, fse->code, false);
> > +       if (!info)
> > +               return -EINVAL;
> > +
> > +       /*
> > +        * Enumerate binning/skipping. Supported factors are powers of two from
> > +        * /1 to /16.
> > +        */
> > +
> > +       if (fse->index >= 5)
> > +               return -EINVAL;
> > +
> > +       crop = v4l2_subdev_state_get_crop(state, fse->pad);
> > +
> > +       fse->min_width = crop->width / (1 << fse->index);
> > +       fse->max_width = fse->min_width;
> > +       fse->min_height = crop->height / (1 << fse->index);
> > +       fse->max_height = fse->min_height;
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_set_fmt(struct v4l2_subdev *sd,
> > +                         struct v4l2_subdev_state *state,
> > +                         struct v4l2_subdev_format *format)
> > +{
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +       const struct ar0144_format_info *info;
> > +       struct v4l2_mbus_framefmt *fmt;
> > +       const struct v4l2_rect *crop;
> > +       unsigned int bin_x, bin_y;
> > +
> > +       if (v4l2_subdev_is_streaming(sd) &&
> > +           format->which == V4L2_SUBDEV_FORMAT_ACTIVE)
> > +               return -EBUSY;
> > +
> > +       fmt = v4l2_subdev_state_get_format(state, format->pad);
> > +       crop = v4l2_subdev_state_get_crop(state, format->pad);
> > +
> > +       info = ar0144_format_info(sensor, format->format.code, true);
> > +       fmt->code = ar0144_format_code(sensor, info);
> > +
> > +       /*
> > +        * The output size results from the binning/skipping applied to the
> > +        * crop rectangle. The x/y increments must be powers of 2. Clamp the
> > +        * width/height first, to avoid both divisions by 0 and the undefined
> > +        * behaviour of roundup_pow_of_two(0).
> > +        */
> > +       fmt->width = clamp(format->format.width, 1U, crop->width);
> > +       fmt->height = clamp(format->format.height, 1U, crop->height);
> > +       bin_x = clamp(roundup_pow_of_two(crop->width / fmt->width), 1, 16);
> > +       bin_y = clamp(roundup_pow_of_two(crop->height / fmt->height), 1, 16);
> > +       fmt->width = crop->width / bin_x;
> > +       fmt->height = crop->height / bin_y;
> > +
> > +       format->format = *fmt;
> > +
> > +       if (format->which != V4L2_SUBDEV_FORMAT_ACTIVE)
> > +               return 0;
> > +
> > +       ar0144_update_link_freqs(sensor, info);
> > +       ar0144_pll_update(sensor, info);
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_get_selection(struct v4l2_subdev *sd,
> > +                               struct v4l2_subdev_state *state,
> > +                               struct v4l2_subdev_selection *sel)
> > +{
> > +       switch (sel->target) {
> > +       case V4L2_SEL_TGT_CROP:
> > +               sel->r = *v4l2_subdev_state_get_crop(state, sel->pad);
> > +               break;
> > +
> > +       case V4L2_SEL_TGT_CROP_DEFAULT:
> > +               sel->r.left = (AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - AR0144_DEF_WIDTH) / 2;
> > +               sel->r.top = (AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - AR0144_DEF_HEIGHT) / 2;
> > +               sel->r.width = AR0144_DEF_WIDTH;
> > +               sel->r.height = AR0144_DEF_HEIGHT;
> > +               break;
> > +
> > +       case V4L2_SEL_TGT_CROP_BOUNDS:
> > +               sel->r.left = 0;
> > +               sel->r.top = 0;
> > +               sel->r.width = AR0144_PIXEL_ARRAY_ACTIVE_WIDTH;
> > +               sel->r.height = AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT;
> > +               break;
> > +
> > +       default:
> > +               return -EINVAL;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_set_selection(struct v4l2_subdev *sd,
> > +                               struct v4l2_subdev_state *state,
> > +                               struct v4l2_subdev_selection *sel)
> > +{
> > +       struct v4l2_mbus_framefmt *fmt;
> > +       struct v4l2_rect *crop;
> > +
> > +       if (v4l2_subdev_is_streaming(sd) &&
> > +           sel->which == V4L2_SUBDEV_FORMAT_ACTIVE)
> > +               return -EBUSY;
> > +
> > +       if (sel->target != V4L2_SEL_TGT_CROP)
> > +               return -EINVAL;
> > +
> > +       crop = v4l2_subdev_state_get_crop(state, 0);
> > +       fmt = v4l2_subdev_state_get_format(state, 0);
> > +
> > +       crop->left = min_t(unsigned int, ALIGN(sel->r.left, 2),
> > +                          AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - AR0144_MIN_WIDTH);
> > +       crop->top = min_t(unsigned int, ALIGN(sel->r.top, 2),
> > +                       AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - AR0144_MIN_HEIGHT);
> > +       crop->width = clamp(sel->r.width, AR0144_MIN_WIDTH,
> > +                           AR0144_PIXEL_ARRAY_ACTIVE_WIDTH - crop->left);
> > +       crop->height = clamp(sel->r.height, AR0144_MIN_HEIGHT,
> > +                            AR0144_PIXEL_ARRAY_ACTIVE_HEIGHT - crop->top);
> > +
> > +       sel->r = *crop;
> > +
> > +       fmt->width = crop->width;
> > +       fmt->height = crop->height;
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
> > +                                struct v4l2_mbus_frame_desc *fd)
> > +{
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +       const struct ar0144_format_info *info;
> > +       const struct v4l2_mbus_framefmt *fmt;
> > +       struct v4l2_subdev_state *state;
> > +       u32 code;
> > +
> > +       state = v4l2_subdev_lock_and_get_active_state(sd);
> > +       fmt = v4l2_subdev_state_get_format(state, 0);
> > +       code = fmt->code;
> > +       v4l2_subdev_unlock_state(state);
> > +
> > +       info = ar0144_format_info(sensor, code, true);
> > +
> > +       fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> > +       fd->num_entries = 1;
> > +
> > +       fd->entry[0].pixelcode = code;
> > +       fd->entry[0].stream = 0;
> > +       fd->entry[0].bus.csi2.vc = 0;
> > +       fd->entry[0].bus.csi2.dt = info->dt;
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
> > +                                 struct v4l2_mbus_config *cfg)
> > +{
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +
> > +       cfg->type = sensor->bus_cfg.bus_type;
> > +
> > +       switch (sensor->bus_cfg.bus_type) {
> > +       case V4L2_MBUS_CSI2_DPHY:
> > +       default:
> > +               cfg->bus.mipi_csi2 = sensor->bus_cfg.bus.mipi_csi2;
> > +               break;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_enable_streams(struct v4l2_subdev *sd,
> > +                                struct v4l2_subdev_state *state, u32 pad,
> > +                                u64 streams_mask)
> > +{
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +       int ret;
> > +
> > +       ret = pm_runtime_resume_and_get(sensor->dev);
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       ret = ar0144_start_streaming(sensor, state);
> > +       if (ret) {
> > +               dev_err(sensor->dev, "Failed to start streaming: %d\n",
> > +                       ret);
> > +               pm_runtime_put_sync(sensor->dev);
> > +               return ret;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_disable_streams(struct v4l2_subdev *sd,
> > +                                 struct v4l2_subdev_state *state, u32 pad,
> > +                                 u64 streams_mask)
> > +{
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +
> > +       ar0144_stop_streaming(sensor);
> > +       pm_runtime_mark_last_busy(sensor->dev);
> > +       pm_runtime_put_autosuspend(sensor->dev);
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_entity_init_state(struct v4l2_subdev *sd,
> > +               struct v4l2_subdev_state *state)
> > +{
> > +       const struct ar0144_format_info *info;
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +       struct v4l2_mbus_framefmt *fmt;
> > +       struct v4l2_rect *crop;
> > +
> > +       info = ar0144_format_info(sensor, 0, true);
> > +
> > +       fmt = v4l2_subdev_state_get_format(state, 0);
> > +       fmt->width = AR0144_DEF_WIDTH;
> > +       fmt->height = AR0144_DEF_HEIGHT;
> > +       fmt->code = ar0144_format_code(sensor, info);
> > +       fmt->field = V4L2_FIELD_NONE;
> > +       fmt->colorspace = V4L2_COLORSPACE_RAW;
> > +       fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
> > +       fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> > +       fmt->xfer_func = V4L2_XFER_FUNC_NONE;
> > +
> > +       crop = v4l2_subdev_state_get_crop(state, 0);
> > +       crop->left = 4;
> > +       crop->top = 4;
> > +       crop->width = AR0144_DEF_WIDTH;
> > +       crop->height = AR0144_DEF_HEIGHT;
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct v4l2_subdev_pad_ops ar0144_subdev_pad_ops = {
> > +       .enum_mbus_code = ar0144_enum_mbus_code,
> > +       .enum_frame_size = ar0144_enum_frame_size,
> > +       .get_fmt = v4l2_subdev_get_fmt,
> > +       .set_fmt = ar0144_set_fmt,
> > +       .get_selection = ar0144_get_selection,
> > +       .set_selection = ar0144_set_selection,
> > +       .get_frame_desc = ar0144_get_frame_desc,
> > +       .get_mbus_config = ar0144_get_mbus_config,
> > +       .enable_streams = ar0144_enable_streams,
> > +       .disable_streams = ar0144_disable_streams,
> > +};
> > +
> > +static const struct v4l2_subdev_ops ar0144_subdev_ops = {
> > +       .pad = &ar0144_subdev_pad_ops,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops ar0144_subdev_internal_ops = {
> > +       .init_state = ar0144_entity_init_state,
> > +};
> > +
> > +static const struct media_entity_operations ar0144_entity_ops = {
> > +       .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
> > +};
> > +
> > +static int ar0144_init_subdev(struct ar0144 *sensor)
> > +{
> > +       struct i2c_client *client = to_i2c_client(sensor->dev);
> > +       struct v4l2_subdev *sd = &sensor->sd;
> > +       const struct v4l2_mbus_framefmt *format;
> > +       const struct ar0144_format_info *info;
> > +       struct v4l2_subdev_state *state;
> > +       int ret;
> > +
> > +       v4l2_i2c_subdev_init(sd, client, &ar0144_subdev_ops);
> > +
> > +       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > +       sd->internal_ops = &ar0144_subdev_internal_ops;
> > +       sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > +       sd->entity.ops = &ar0144_entity_ops;
> > +
> > +       sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
> > +
> > +       ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad);
> > +       if (ret)
> > +               return ret;
> > +
> > +       ret = ar0144_init_ctrls(sensor);
> > +       if (ret)
> > +               goto err_entity;
> > +
> > +       sensor->sd.state_lock = sensor->ctrls.lock;
> > +       ret = v4l2_subdev_init_finalize(&sensor->sd);
> > +       if (ret)
> > +               goto err_ctrls;
> > +
> > +       /*
> > +        * Update the link frequency, PLL configuration (including the pixel
> > +        * rate) and blanking controls.
> > +        */
> > +       state = v4l2_subdev_lock_and_get_active_state(sd);
> > +       format = v4l2_subdev_state_get_format(state, 0);
> > +       info = ar0144_format_info(sensor, format->code, true);
> > +
> > +       ar0144_update_link_freqs(sensor, info);
> > +       ar0144_pll_update(sensor, info);
> > +       ar0144_update_blanking(sensor, state);
> > +
> > +       v4l2_subdev_unlock_state(state);
> > +
> > +       return 0;
> > +
> > +err_ctrls:
> > +       v4l2_ctrl_handler_free(&sensor->ctrls);
> > +err_entity:
> > +       media_entity_cleanup(&sd->entity);
> > +       return ret;
> > +}
> > +
> > +static void ar0144_cleanup_subdev(struct ar0144 *sensor)
> > +{
> > +       v4l2_subdev_cleanup(&sensor->sd);
> > +       v4l2_ctrl_handler_free(&sensor->ctrls);
> > +       media_entity_cleanup(&sensor->sd.entity);
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Power management
> > + */
> > +
> > +static int ar0144_power_on(struct ar0144 *sensor)
> > +{
> > +       u64 reset_delay;
> > +       long rate;
> > +       int ret;
> > +
> > +       /*
> > +        * The sensor must be powered off for at least 100ms before being
> > +        * powered on again.
> > +        */
> > +       if (sensor->off_time) {
> > +               u64 off_duration;
> > +
> > +               off_duration = ktime_us_delta(ktime_get_boottime(),
> > +                                             sensor->off_time);
> > +               if (off_duration < 100000)
> > +                       fsleep(100000 - off_duration);
> > +       }
> > +
> > +       ret = regulator_bulk_enable(ARRAY_SIZE(sensor->supplies),
> > +                                   sensor->supplies);
> > +       if (ret) {
> > +               dev_err(sensor->dev, "Failed to enable regulators\n");
> > +               return ret;
> > +       }
> > +
> > +       ret = clk_prepare_enable(sensor->clk);
> > +       if (ret) {
> > +               regulator_bulk_disable(ARRAY_SIZE(sensor->supplies),
> > +                                      sensor->supplies);
> > +               dev_err(sensor->dev, "Failed to enable clock\n");
> > +               return ret;
> > +       }
> > +
> > +       /*
> > +        * The internal initialization time after hard reset is 160000 EXTCLK
> > +        * cycles.
> > +        */
> > +       rate = clk_get_rate(sensor->clk);
> > +       reset_delay = DIV_ROUND_UP_ULL(160000ULL * USEC_PER_SEC, rate);
> > +
> > +       if (sensor->reset) {
> > +               gpiod_set_value_cansleep(sensor->reset, 1);
> > +               fsleep(1000);
> > +               gpiod_set_value_cansleep(sensor->reset, 0);
> > +       } else {
> > +               cci_write(sensor->regmap, AR0144_RESET_REGISTER, AR0144_RESET,
> > +                         NULL);
> > +       }
> > +
> > +       fsleep(reset_delay);
> > +
> > +       return 0;
> > +}
> > +
> > +static void ar0144_power_off(struct ar0144 *sensor)
> > +{
> > +       regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies);
> > +       sensor->off_time = ktime_get_boottime();
> > +
> > +       clk_disable_unprepare(sensor->clk);
> > +       gpiod_set_value_cansleep(sensor->reset, 1);
> > +}
> > +
> > +static int ar0144_runtime_resume(struct device *dev)
> > +{
> > +       struct v4l2_subdev *sd = dev_get_drvdata(dev);
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +
> > +       return ar0144_power_on(sensor);
> > +}
> > +
> > +static int ar0144_runtime_suspend(struct device *dev)
> > +{
> > +       struct v4l2_subdev *sd = dev_get_drvdata(dev);
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +
> > +       ar0144_power_off(sensor);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct dev_pm_ops ar0144_pm_ops = {
> > +       RUNTIME_PM_OPS(ar0144_runtime_suspend, ar0144_runtime_resume, NULL)
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Probe & remove
> > + */
> > +
> > +static int ar0144_identify_device(struct ar0144 *sensor)
> > +{
> > +       const struct ar0144_model *model;
> > +       u64 customer_rev;
> > +       u64 chip_id;
> > +       int ret = 0;
> > +
> > +       cci_read(sensor->regmap, AR0144_CHIP_VERSION_REG, &chip_id, &ret);
> > +       cci_read(sensor->regmap, AR0144_CUSTOMER_REV, &customer_rev, &ret);
> > +
> > +       if (ret) {
> > +               dev_err(sensor->dev, "Failed to read chip ID: %d\n", ret);
> > +               return ret;
> > +       }
> > +
> > +       if (chip_id != AR0144_CHIP_VERSION) {
> 
> The mono modules we received from OnSemi had register 0x3000 set to
> 0x1356, with the colour ones being 0x0356. It was a few years back,
> but I'm fairly certain they were production modules rather than
> engineering samples.

Ah, that's a good point. I don't have a mono module, so I couldn't test
this. I'll update the driver accordingly. I wonder if that's applicable
to all mono modules though, could it be that the AR0144CS reports 0x0356
while the AR0144AT reports 0x1356 ? Are your mono modules CS or AT parts
?

> The register reference also says it can be made read/write (why?!), so
> can we guarantee this value?

If the sensor is power cycled I think we can rely on the value. If power
is always on and the boot loader decides to reconfigure the chip ID...
well, I'd say someone really deserves a probe failure in that case :-)

> > +               dev_err(sensor->dev,
> > +                       "Wrong chip version 0x%04x (expected 0x%04x)\n",
> > +                       (u32)chip_id, AR0144_CHIP_VERSION);
> > +               return -ENODEV;
> > +       }
> > +
> > +       switch (AR0144_CUSTOMER_REV_CFA(customer_rev)) {
> 
> I can't find AR0144_CUSTOMER_REV (0x31fe) in the register reference.
> A register with "CUSTOMER" in the name implies that it is a customer
> specific register, so can we rely on it 100% that all AR0144 modules
> will contain the CFA in the manner you've implemented?

There's more information in the developer guide.

> Those were the main comments. I may come back with more once I've tried it out.
> 
> > +       default:
> > +               dev_warn(sensor->dev,
> > +                        "Unknown CFA %u, defaulting to color\n",
> > +                        (u32)AR0144_CUSTOMER_REV_CFA(customer_rev));
> > +               fallthrough;
> > +       case AR0144_CUSTOMER_REV_CFA_COLOR:
> > +               model = &ar0144_model_color;
> > +               break;
> > +       case AR0144_CUSTOMER_REV_CFA_MONO:
> > +               model = &ar0144_model_mono;
> > +               break;
> > +       }
> > +
> > +       if (sensor->model != model)
> > +               dev_warn(sensor->dev,
> > +                        "Customer rev CFA doesn't match compatible string\n");
> 
> Seeing as the CFA can be determined via registers, do we need to be
> able to set it via the compatible string?

On some platforms, you may want to avoid powering up the sensor at probe
time. This is more common for consumer camera sensors, when powering up
the camera will turn the privacy LED on for instance. A flashing privacy
LED at boot time scares users. I'm not sure if that will ever be an
issue with this sensor (and I haven't implemented support for this in
the driver), but I think being prepared for this situation by specifying
the CFA in the compatible string is safer.

> > +
> > +       sensor->model = model;
> 
> You've now totally ignored the value requested via the compatible
> string, which does make it redundant. An invalid CFA in registers has
> fallen through and been set to colour, so there's no way to force
> mono.
> I'm happy to have a way of forcing the CFA, but this doesn't achieve it.

As explained above, the goal wasn't to force the CFA through the
compatible string. I'd be fine doing so though.

> > +
> > +       if (IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || IS_ENABLED(DEBUG)) {
> > +               cci_read(sensor->regmap, AR0144_REVISION_NUMBER, &chip_id, NULL);
> > +               dev_dbg(sensor->dev,
> > +                       "Sensor detected, CFA %s, CRA %u, OTPM r%u, silicon r%u, CREV r%u\n",
> > +                       sensor->model->mono ? "mono" : "color",
> > +                       (u32)AR0144_CUSTOMER_REV_CRA(customer_rev),
> > +                       (u32)AR0144_REVISION_NUMBER_OTPM(chip_id),
> > +                       (u32)AR0144_REVISION_NUMBER_SILICON(chip_id),
> > +                       (u32)AR0144_REVISION_NUMBER_CREV(chip_id));
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int ar0144_parse_dt(struct ar0144 *sensor)
> > +{
> > +       struct v4l2_fwnode_endpoint *ep = &sensor->bus_cfg;
> > +       struct fwnode_handle *endpoint;
> > +       u64 valid_link_freqs = 0;
> > +       unsigned int nlanes;
> > +       unsigned int i, j;
> > +       int ret;
> > +
> > +       endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(sensor->dev), NULL);
> > +       if (!endpoint) {
> > +               dev_err(sensor->dev, "Endpoint node not found\n");
> > +               return -EINVAL;
> > +       }
> > +
> > +       ep->bus_type = V4L2_MBUS_UNKNOWN;
> > +       ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, ep);
> > +       fwnode_handle_put(endpoint);
> > +       if (ret) {
> > +               dev_err(sensor->dev, "Parsing endpoint node failed\n");
> > +               goto error;
> > +       }
> > +
> > +       switch (ep->bus_type) {
> > +       case V4L2_MBUS_CSI2_DPHY:
> > +               /* Validate the number of data lanes. */
> > +               nlanes = ep->bus.mipi_csi2.num_data_lanes;
> > +               if (nlanes != 1 && nlanes != 2) {
> > +                       dev_err(sensor->dev, "Invalid data lanes: %d\n", nlanes);
> > +                       ret = -EINVAL;
> > +                       goto error;
> > +               }
> > +               break;
> > +
> > +       default:
> > +               dev_err(sensor->dev, "Unsupported bus type %u\n", ep->bus_type);
> > +               goto error;
> > +       }
> > +
> > +       /* Get and validate the link frequencies. */
> > +       if (!ep->nr_of_link_frequencies) {
> > +               dev_err(sensor->dev, "link-frequency property not found in DT\n");
> > +               ret = -EINVAL;
> > +               goto error;
> > +       }
> > +
> > +       if (ep->nr_of_link_frequencies > 64) {
> > +               dev_err(sensor->dev, "Too many link-frequency entries\n");
> > +               ret = -EINVAL;
> > +               goto error;
> > +       }
> > +
> > +       for (i = 0; i < ARRAY_SIZE(ar0144_formats); ++i) {
> > +               const struct ar0144_format_info *info = &ar0144_formats[i];
> > +
> > +               for (j = 0; j < ep->nr_of_link_frequencies; ++j) {
> > +                       u64 freq = ep->link_frequencies[j];
> > +                       struct ccs_pll pll;
> > +
> > +                       ret = ar0144_pll_calculate(sensor, &pll, freq,
> > +                                                  info->bpp);
> > +                       if (ret)
> > +                               continue;
> > +
> > +                       sensor->valid_link_freqs[i] |= BIT(j);
> > +                       valid_link_freqs |= BIT(j);
> > +               }
> > +
> > +               if (!sensor->valid_link_freqs[i]) {
> > +                       dev_warn(sensor->dev,
> > +                                "No valid link frequency for %u bpp\n",
> > +                                info->bpp);
> > +                       continue;
> > +               }
> > +
> > +               sensor->valid_formats |= BIT(i);
> > +       }
> > +
> > +       if (!sensor->valid_formats) {
> > +               dev_err(sensor->dev,
> > +                       "No valid link frequency found for any format\n");
> > +               ret = -EINVAL;
> > +               goto error;
> > +       }
> > +
> > +       for (i = 0; i < ep->nr_of_link_frequencies; ++i) {
> > +               if (!(valid_link_freqs & BIT(i)))
> > +                       dev_warn(sensor->dev,
> > +                                "Link frequency %llu not valid for any format\n",
> > +                                ep->link_frequencies[i]);
> > +       }
> > +
> > +       dev_dbg(sensor->dev, "Using %u data lanes and %u link frequencies\n",
> > +               nlanes, ep->nr_of_link_frequencies);
> > +
> > +       return 0;
> > +
> > +error:
> > +       v4l2_fwnode_endpoint_free(&sensor->bus_cfg);
> > +       return ret;
> > +}
> > +
> > +static const char * const ar0144_supply_name[AR0144_NUM_SUPPLIES] = {
> > +       "vaa",
> > +       "vdd_io",
> > +       "vdd",
> > +};
> > +
> > +static int ar0144_probe(struct i2c_client *client)
> > +{
> > +       struct device *dev = &client->dev;
> > +       struct ar0144 *sensor;
> > +       unsigned int i;
> > +       int ret;
> > +
> > +       sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
> > +       if (!sensor)
> > +               return -ENOMEM;
> > +
> > +       sensor->dev = dev;
> > +       sensor->model = device_get_match_data(sensor->dev);
> > +
> > +       /* Acquire resources. */
> > +       sensor->regmap = devm_cci_regmap_init_i2c(client, 16);
> > +       if (IS_ERR(sensor->regmap))
> > +               return dev_err_probe(dev, PTR_ERR(sensor->regmap),
> > +                                    "Unable to initialize I2C\n");
> > +
> > +       sensor->clk = devm_clk_get(dev, NULL);
> > +       if (IS_ERR(sensor->clk))
> > +               return dev_err_probe(dev, PTR_ERR(sensor->clk),
> > +                                    "Cannot get clock\n");
> > +
> > +       sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> > +       if (IS_ERR(sensor->reset))
> > +               return dev_err_probe(dev, PTR_ERR(sensor->reset),
> > +                                    "Cannot get reset gpio\n");
> > +
> > +       for (i = 0; i < ARRAY_SIZE(sensor->supplies); i++)
> > +               sensor->supplies[i].supply = ar0144_supply_name[i];
> > +
> > +       ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(sensor->supplies),
> > +                                     sensor->supplies);
> > +       if (ret)
> > +               return dev_err_probe(dev, ret, "Cannot get supplies\n");
> > +
> > +       /* Parse the DT properties. */
> > +       ret = ar0144_parse_dt(sensor);
> > +       if (ret)
> > +               return ret;
> > +
> > +       /*
> > +        * Enable power management. The driver supports runtime PM, but needs to
> > +        * work when runtime PM is disabled in the kernel. To that end, power
> > +        * the sensor on manually here, identify it, and fully initialize it.
> > +        */
> > +       ret = ar0144_power_on(sensor);
> > +       if (ret < 0) {
> > +               dev_err_probe(dev, ret, "Could not power on the device\n");
> > +               goto err_dt;
> > +       }
> > +
> > +       ret = ar0144_identify_device(sensor);
> > +       if (ret)
> > +               goto err_power;
> > +
> > +       /*
> > +        * Enable runtime PM with autosuspend. As the device has been powered
> > +        * manually, mark it as active, and increase the usage count without
> > +        * resuming the device.
> > +        */
> > +       pm_runtime_set_active(dev);
> > +       pm_runtime_get_noresume(dev);
> > +       pm_runtime_enable(dev);
> > +       pm_runtime_set_autosuspend_delay(dev, 1000);
> > +       pm_runtime_use_autosuspend(dev);
> > +
> > +       /* Initialize and register the subdev. */
> > +       ret = ar0144_init_subdev(sensor);
> > +       if (ret) {
> > +               dev_err(dev, "Subdev initialization error %d\n", ret);
> > +               goto err_pm;
> > +       }
> > +
> > +       ret = v4l2_async_register_subdev_sensor(&sensor->sd);
> > +       if (ret) {
> > +               dev_err(dev, "Could not register V4L2 subdevice\n");
> > +               goto err_subdev;
> > +       }
> > +
> > +       /*
> > +        * Decrease the PM usage count. The device will get suspended after the
> > +        * autosuspend delay, turning the power off.
> > +        */
> > +       pm_runtime_mark_last_busy(dev);
> > +       pm_runtime_put_autosuspend(dev);
> > +
> > +       return 0;
> > +
> > +err_subdev:
> > +       ar0144_cleanup_subdev(sensor);
> > +err_pm:
> > +       pm_runtime_disable(dev);
> > +       pm_runtime_put_noidle(dev);
> > +err_power:
> > +       ar0144_power_off(sensor);
> > +err_dt:
> > +       v4l2_fwnode_endpoint_free(&sensor->bus_cfg);
> > +       return ret;
> > +}
> > +
> > +static void ar0144_remove(struct i2c_client *client)
> > +{
> > +       struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > +       struct ar0144 *sensor = to_ar0144(sd);
> > +
> > +       v4l2_async_unregister_subdev(&sensor->sd);
> > +       ar0144_cleanup_subdev(sensor);
> > +       v4l2_fwnode_endpoint_free(&sensor->bus_cfg);
> > +
> > +       /*
> > +        * Disable runtime PM. In case runtime PM is disabled in the kernel,
> > +        * make sure to turn power off manually.
> > +        */
> > +       pm_runtime_disable(sensor->dev);
> > +       if (!pm_runtime_status_suspended(sensor->dev))
> > +               ar0144_power_off(sensor);
> > +       pm_runtime_set_suspended(sensor->dev);
> > +}
> > +
> > +static const struct of_device_id ar0144_of_match[] = {
> > +       { .compatible = "onnn,ar0144c", &ar0144_model_color },
> > +       { .compatible = "onnn,ar0144m", &ar0144_model_mono },
> > +       { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, ar0144_of_match);
> > +
> > +static struct i2c_driver ar0144_i2c_driver = {
> > +       .driver = {
> > +               .name = "ar0144",
> > +               .of_match_table = ar0144_of_match,
> > +               .pm = pm_ptr(&ar0144_pm_ops),
> > +       },
> > +       .probe = ar0144_probe,
> > +       .remove = ar0144_remove,
> > +};
> > +
> > +module_i2c_driver(ar0144_i2c_driver);
> > +
> > +MODULE_DESCRIPTION("onsemi AR0144 Camera Sensor Driver");
> > +MODULE_LICENSE("GPL");

-- 
Regards,

Laurent Pinchart




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux