]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
media: smiapp: Internal rename to CCS
authorSakari Ailus <sakari.ailus@linux.intel.com>
Tue, 11 Feb 2020 13:19:13 +0000 (14:19 +0100)
committerMauro Carvalho Chehab <mchehab+huawei@kernel.org>
Wed, 2 Dec 2020 14:39:44 +0000 (15:39 +0100)
Rename internal names to reflect the driver's new reference. The module
name remains the same.

Also fix trivial coding style issues on the way related to e.g. alignment
changes due to the rename.

Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
13 files changed:
drivers/media/i2c/smiapp/Makefile
drivers/media/i2c/smiapp/ccs-core.c [new file with mode: 0644]
drivers/media/i2c/smiapp/ccs-quirk.c [new file with mode: 0644]
drivers/media/i2c/smiapp/ccs-quirk.h [new file with mode: 0644]
drivers/media/i2c/smiapp/ccs-reg-access.c [new file with mode: 0644]
drivers/media/i2c/smiapp/ccs-reg-access.h [new file with mode: 0644]
drivers/media/i2c/smiapp/ccs.h [new file with mode: 0644]
drivers/media/i2c/smiapp/smiapp-core.c [deleted file]
drivers/media/i2c/smiapp/smiapp-quirk.c [deleted file]
drivers/media/i2c/smiapp/smiapp-quirk.h [deleted file]
drivers/media/i2c/smiapp/smiapp-regs.c [deleted file]
drivers/media/i2c/smiapp/smiapp-regs.h [deleted file]
drivers/media/i2c/smiapp/smiapp.h [deleted file]

index a7bf53dd4a6371fd5e388333281834073e60e40c..c9d300b5d2bc85db0b6fcbdafc84f502ac5879c1 100644 (file)
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
-smiapp-objs                    += smiapp-core.o smiapp-regs.o \
-                                  smiapp-quirk.o ccs-limits.o
+smiapp-objs                    += ccs-core.o ccs-reg-access.o \
+                                  ccs-quirk.o ccs-limits.o
 obj-$(CONFIG_VIDEO_SMIAPP)     += smiapp.o
 
 ccflags-y += -I $(srctree)/drivers/media/i2c
diff --git a/drivers/media/i2c/smiapp/ccs-core.c b/drivers/media/i2c/smiapp/ccs-core.c
new file mode 100644 (file)
index 0000000..f9dbf14
--- /dev/null
@@ -0,0 +1,3299 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/media/i2c/smiapp/ccs-core.c
+ *
+ * Generic driver for SMIA/SMIA++ compliant camera modules
+ *
+ * Copyright (C) 2010--2012 Nokia Corporation
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ *
+ * Based on smiapp driver by Vimarsh Zutshi
+ * Based on jt8ev1.c by Vimarsh Zutshi
+ * Based on smia-sensor.c by Tuukka Toivonen <tuukkat76@gmail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/smiapp.h>
+#include <linux/v4l2-mediabus.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-device.h>
+
+#include "ccs.h"
+#include "ccs-limits.h"
+
+#define CCS_ALIGN_DIM(dim, flags)      \
+       ((flags) & V4L2_SEL_FLAG_GE     \
+        ? ALIGN((dim), 2)              \
+        : (dim) & ~1)
+
+static struct ccs_limit_offset {
+       u16     lim;
+       u16     info;
+} ccs_limit_offsets[CCS_L_LAST + 1];
+
+/*
+ * ccs_module_idents - supported camera modules
+ */
+static const struct ccs_module_ident ccs_module_idents[] = {
+       CCS_IDENT_L(0x01, 0x022b, -1, "vs6555"),
+       CCS_IDENT_L(0x01, 0x022e, -1, "vw6558"),
+       CCS_IDENT_L(0x07, 0x7698, -1, "ovm7698"),
+       CCS_IDENT_L(0x0b, 0x4242, -1, "smiapp-003"),
+       CCS_IDENT_L(0x0c, 0x208a, -1, "tcm8330md"),
+       CCS_IDENT_LQ(0x0c, 0x2134, -1, "tcm8500md", &smiapp_tcm8500md_quirk),
+       CCS_IDENT_L(0x0c, 0x213e, -1, "et8en2"),
+       CCS_IDENT_L(0x0c, 0x2184, -1, "tcm8580md"),
+       CCS_IDENT_LQ(0x0c, 0x560f, -1, "jt8ew9", &smiapp_jt8ew9_quirk),
+       CCS_IDENT_LQ(0x10, 0x4141, -1, "jt8ev1", &smiapp_jt8ev1_quirk),
+       CCS_IDENT_LQ(0x10, 0x4241, -1, "imx125es", &smiapp_imx125es_quirk),
+};
+
+/*
+ *
+ * Dynamic Capability Identification
+ *
+ */
+
+static void ccs_assign_limit(void *ptr, unsigned int width, u32 val)
+{
+       switch (width) {
+       case sizeof(u8):
+               *(u8 *)ptr = val;
+               break;
+       case sizeof(u16):
+               *(u16 *)ptr = val;
+               break;
+       case sizeof(u32):
+               *(u32 *)ptr = val;
+               break;
+       }
+}
+
+static int ccs_limit_ptr(struct ccs_sensor *sensor, unsigned int limit,
+                        unsigned int offset, void **__ptr)
+{
+       const struct ccs_limit *linfo;
+
+       if (WARN_ON(limit >= CCS_L_LAST))
+               return -EINVAL;
+
+       linfo = &ccs_limits[ccs_limit_offsets[limit].info];
+
+       if (WARN_ON(!sensor->ccs_limits) ||
+           WARN_ON(offset + ccs_reg_width(linfo->reg) >
+                   ccs_limit_offsets[limit + 1].lim))
+               return -EINVAL;
+
+       *__ptr = sensor->ccs_limits + ccs_limit_offsets[limit].lim + offset;
+
+       return 0;
+}
+
+void ccs_replace_limit(struct ccs_sensor *sensor,
+                      unsigned int limit, unsigned int offset, u32 val)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       const struct ccs_limit *linfo;
+       void *ptr;
+       int ret;
+
+       ret = ccs_limit_ptr(sensor, limit, offset, &ptr);
+       if (ret)
+               return;
+
+       linfo = &ccs_limits[ccs_limit_offsets[limit].info];
+
+       dev_dbg(&client->dev, "quirk: 0x%8.8x \"%s\" %u = %d, 0x%x\n",
+               linfo->reg, linfo->name, offset, val, val);
+
+       ccs_assign_limit(ptr, ccs_reg_width(linfo->reg), val);
+}
+
+static u32 ccs_get_limit(struct ccs_sensor *sensor,
+                        unsigned int limit, unsigned int offset)
+{
+       void *ptr;
+       int ret;
+
+       ret = ccs_limit_ptr(sensor, limit, offset, &ptr);
+       if (ret)
+               return 0;
+
+       switch (ccs_reg_width(ccs_limits[ccs_limit_offsets[limit].info].reg)) {
+       case sizeof(u8):
+               return *(u8 *)ptr;
+       case sizeof(u16):
+               return *(u16 *)ptr;
+       case sizeof(u32):
+               return *(u32 *)ptr;
+       }
+
+       WARN_ON(1);
+
+       return 0;
+}
+
+#define CCS_LIM(sensor, limit) \
+       ccs_get_limit(sensor, CCS_L_##limit, 0)
+
+#define CCS_LIM_AT(sensor, limit, offset)      \
+       ccs_get_limit(sensor, CCS_L_##limit, CCS_L_##limit##_OFFSET(offset))
+
+static int ccs_read_all_limits(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       void *ptr, *alloc, *end;
+       unsigned int i, l;
+       int ret;
+
+       kfree(sensor->ccs_limits);
+       sensor->ccs_limits = NULL;
+
+       alloc = kzalloc(ccs_limit_offsets[CCS_L_LAST].lim, GFP_KERNEL);
+       if (!alloc)
+               return -ENOMEM;
+
+       end = alloc + ccs_limit_offsets[CCS_L_LAST].lim;
+
+       for (i = 0, l = 0, ptr = alloc; ccs_limits[i].size; i++) {
+               u32 reg = ccs_limits[i].reg;
+               unsigned int width = ccs_reg_width(reg);
+               unsigned int j;
+
+               if (l == CCS_L_LAST) {
+                       dev_err(&client->dev,
+                               "internal error --- end of limit array\n");
+                       ret = -EINVAL;
+                       goto out_err;
+               }
+
+               for (j = 0; j < ccs_limits[i].size / width;
+                    j++, reg += width, ptr += width) {
+                       u32 val;
+
+                       ret = ccs_read_addr(sensor, reg, &val);
+                       if (ret)
+                               goto out_err;
+
+                       if (ptr + width > end) {
+                               dev_err(&client->dev,
+                                       "internal error --- no room for regs\n");
+                               ret = -EINVAL;
+                               goto out_err;
+                       }
+
+                       ccs_assign_limit(ptr, width, val);
+
+                       dev_dbg(&client->dev, "0x%8.8x \"%s\" = %u, 0x%x\n",
+                               reg, ccs_limits[i].name, val, val);
+               }
+
+               if (ccs_limits[i].flags & CCS_L_FL_SAME_REG)
+                       continue;
+
+               l++;
+               ptr = alloc + ccs_limit_offsets[l].lim;
+       }
+
+       if (l != CCS_L_LAST) {
+               dev_err(&client->dev,
+                       "internal error --- insufficient limits\n");
+               ret = -EINVAL;
+               goto out_err;
+       }
+
+       sensor->ccs_limits = alloc;
+
+       if (CCS_LIM(sensor, SCALER_N_MIN) < 16)
+               ccs_replace_limit(sensor, CCS_L_SCALER_N_MIN, 0, 16);
+
+       return 0;
+
+out_err:
+       kfree(alloc);
+
+       return ret;
+}
+
+static int ccs_read_frame_fmt(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       u8 fmt_model_type, fmt_model_subtype, ncol_desc, nrow_desc;
+       unsigned int i;
+       int pixel_count = 0;
+       int line_count = 0;
+
+       fmt_model_type = CCS_LIM(sensor, FRAME_FORMAT_MODEL_TYPE);
+       fmt_model_subtype = CCS_LIM(sensor, FRAME_FORMAT_MODEL_SUBTYPE);
+
+       ncol_desc = (fmt_model_subtype
+                    & CCS_FRAME_FORMAT_MODEL_SUBTYPE_COLUMNS_MASK)
+               >> CCS_FRAME_FORMAT_MODEL_SUBTYPE_COLUMNS_SHIFT;
+       nrow_desc = fmt_model_subtype
+               & CCS_FRAME_FORMAT_MODEL_SUBTYPE_ROWS_MASK;
+
+       dev_dbg(&client->dev, "format_model_type %s\n",
+               fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_2_BYTE
+               ? "2 byte" :
+               fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_4_BYTE
+               ? "4 byte" : "is simply bad");
+
+       dev_dbg(&client->dev, "%u column and %u row descriptors\n",
+               ncol_desc, nrow_desc);
+
+       for (i = 0; i < ncol_desc + nrow_desc; i++) {
+               u32 desc;
+               u32 pixelcode;
+               u32 pixels;
+               char *which;
+               char *what;
+               u32 reg;
+
+               if (fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_2_BYTE) {
+                       desc = CCS_LIM_AT(sensor, FRAME_FORMAT_DESCRIPTOR, i);
+
+                       pixelcode =
+                               (desc
+                                & CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MASK)
+                               >> CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_SHIFT;
+                       pixels = desc & CCS_FRAME_FORMAT_DESCRIPTOR_PIXELS_MASK;
+               } else if (fmt_model_type
+                          == CCS_FRAME_FORMAT_MODEL_TYPE_4_BYTE) {
+                       desc = CCS_LIM_AT(sensor, FRAME_FORMAT_DESCRIPTOR_4, i);
+
+                       pixelcode =
+                               (desc
+                                & CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MASK)
+                               >> CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_SHIFT;
+                       pixels = desc &
+                               CCS_FRAME_FORMAT_DESCRIPTOR_4_PIXELS_MASK;
+               } else {
+                       dev_dbg(&client->dev,
+                               "invalid frame format model type %d\n",
+                               fmt_model_type);
+                       return -EINVAL;
+               }
+
+               if (i < ncol_desc)
+                       which = "columns";
+               else
+                       which = "rows";
+
+               switch (pixelcode) {
+               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_EMBEDDED:
+                       what = "embedded";
+                       break;
+               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_DUMMY_PIXEL:
+                       what = "dummy";
+                       break;
+               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_BLACK_PIXEL:
+                       what = "black";
+                       break;
+               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_DARK_PIXEL:
+                       what = "dark";
+                       break;
+               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL:
+                       what = "visible";
+                       break;
+               default:
+                       what = "invalid";
+                       break;
+               }
+
+               dev_dbg(&client->dev,
+                       "0x%8.8x %s pixels: %d %s (pixelcode %u)\n", reg,
+                       what, pixels, which, pixelcode);
+
+               if (i < ncol_desc) {
+                       if (pixelcode ==
+                           CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL)
+                               sensor->visible_pixel_start = pixel_count;
+                       pixel_count += pixels;
+                       continue;
+               }
+
+               /* Handle row descriptors */
+               switch (pixelcode) {
+               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_EMBEDDED:
+                       if (sensor->embedded_end)
+                               break;
+                       sensor->embedded_start = line_count;
+                       sensor->embedded_end = line_count + pixels;
+                       break;
+               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL:
+                       sensor->image_start = line_count;
+                       break;
+               }
+               line_count += pixels;
+       }
+
+       if (sensor->embedded_end > sensor->image_start) {
+               dev_dbg(&client->dev,
+                       "adjusting image start line to %u (was %u)\n",
+                       sensor->embedded_end, sensor->image_start);
+               sensor->image_start = sensor->embedded_end;
+       }
+
+       dev_dbg(&client->dev, "embedded data from lines %d to %d\n",
+               sensor->embedded_start, sensor->embedded_end);
+       dev_dbg(&client->dev, "image data starts at line %d\n",
+               sensor->image_start);
+
+       return 0;
+}
+
+static int ccs_pll_configure(struct ccs_sensor *sensor)
+{
+       struct smiapp_pll *pll = &sensor->pll;
+       int rval;
+
+       rval = ccs_write(sensor, VT_PIX_CLK_DIV, pll->vt.pix_clk_div);
+       if (rval < 0)
+               return rval;
+
+       rval = ccs_write(sensor, VT_SYS_CLK_DIV, pll->vt.sys_clk_div);
+       if (rval < 0)
+               return rval;
+
+       rval = ccs_write(sensor, PRE_PLL_CLK_DIV, pll->pre_pll_clk_div);
+       if (rval < 0)
+               return rval;
+
+       rval = ccs_write(sensor, PLL_MULTIPLIER, pll->pll_multiplier);
+       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.sys_clk_freq_hz,
+                                     1000000 / 256 / 256));
+       if (rval < 0 || sensor->minfo.smiapp_profile == SMIAPP_PROFILE_0)
+               return rval;
+
+       rval = ccs_write(sensor, OP_PIX_CLK_DIV, pll->op.pix_clk_div);
+       if (rval < 0)
+               return rval;
+
+       return ccs_write(sensor, OP_SYS_CLK_DIV, pll->op.sys_clk_div);
+}
+
+static int ccs_pll_try(struct ccs_sensor *sensor, struct smiapp_pll *pll)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       struct smiapp_pll_limits lim = {
+               .min_pre_pll_clk_div = CCS_LIM(sensor, MIN_PRE_PLL_CLK_DIV),
+               .max_pre_pll_clk_div = CCS_LIM(sensor, MAX_PRE_PLL_CLK_DIV),
+               .min_pll_ip_freq_hz = CCS_LIM(sensor, MIN_PLL_IP_CLK_FREQ_MHZ),
+               .max_pll_ip_freq_hz = CCS_LIM(sensor, MAX_PLL_IP_CLK_FREQ_MHZ),
+               .min_pll_multiplier = CCS_LIM(sensor, MIN_PLL_MULTIPLIER),
+               .max_pll_multiplier = CCS_LIM(sensor, MAX_PLL_MULTIPLIER),
+               .min_pll_op_freq_hz = CCS_LIM(sensor, MIN_PLL_OP_CLK_FREQ_MHZ),
+               .max_pll_op_freq_hz = CCS_LIM(sensor, MAX_PLL_OP_CLK_FREQ_MHZ),
+
+               .op.min_sys_clk_div = CCS_LIM(sensor, MIN_OP_SYS_CLK_DIV),
+               .op.max_sys_clk_div = CCS_LIM(sensor, MAX_OP_SYS_CLK_DIV),
+               .op.min_pix_clk_div = CCS_LIM(sensor, MIN_OP_PIX_CLK_DIV),
+               .op.max_pix_clk_div = CCS_LIM(sensor, MAX_OP_PIX_CLK_DIV),
+               .op.min_sys_clk_freq_hz = CCS_LIM(sensor, MIN_OP_SYS_CLK_FREQ_MHZ),
+               .op.max_sys_clk_freq_hz = CCS_LIM(sensor, MAX_OP_SYS_CLK_FREQ_MHZ),
+               .op.min_pix_clk_freq_hz = CCS_LIM(sensor, MIN_OP_PIX_CLK_FREQ_MHZ),
+               .op.max_pix_clk_freq_hz = CCS_LIM(sensor, MAX_OP_PIX_CLK_FREQ_MHZ),
+
+               .vt.min_sys_clk_div = CCS_LIM(sensor, MIN_VT_SYS_CLK_DIV),
+               .vt.max_sys_clk_div = CCS_LIM(sensor, MAX_VT_SYS_CLK_DIV),
+               .vt.min_pix_clk_div = CCS_LIM(sensor, MIN_VT_PIX_CLK_DIV),
+               .vt.max_pix_clk_div = CCS_LIM(sensor, MAX_VT_PIX_CLK_DIV),
+               .vt.min_sys_clk_freq_hz = CCS_LIM(sensor, MIN_VT_SYS_CLK_FREQ_MHZ),
+               .vt.max_sys_clk_freq_hz = CCS_LIM(sensor, MAX_VT_SYS_CLK_FREQ_MHZ),
+               .vt.min_pix_clk_freq_hz = CCS_LIM(sensor, MIN_VT_PIX_CLK_FREQ_MHZ),
+               .vt.max_pix_clk_freq_hz = CCS_LIM(sensor, MAX_VT_PIX_CLK_FREQ_MHZ),
+
+               .min_line_length_pck_bin = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN),
+               .min_line_length_pck = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK),
+       };
+
+       return smiapp_pll_calculate(&client->dev, &lim, pll);
+}
+
+static int ccs_pll_update(struct ccs_sensor *sensor)
+{
+       struct smiapp_pll *pll = &sensor->pll;
+       int rval;
+
+       pll->binning_horizontal = sensor->binning_horizontal;
+       pll->binning_vertical = sensor->binning_vertical;
+       pll->link_freq =
+               sensor->link_freq->qmenu_int[sensor->link_freq->val];
+       pll->scale_m = sensor->scale_m;
+       pll->bits_per_pixel = sensor->csi_format->compressed;
+
+       rval = ccs_pll_try(sensor, pll);
+       if (rval < 0)
+               return rval;
+
+       __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_parray,
+                                pll->pixel_rate_pixel_array);
+       __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_csi, pll->pixel_rate_csi);
+
+       return 0;
+}
+
+
+/*
+ *
+ * V4L2 Controls handling
+ *
+ */
+
+static void __ccs_update_exposure_limits(struct ccs_sensor *sensor)
+{
+       struct v4l2_ctrl *ctrl = sensor->exposure;
+       int max;
+
+       max = sensor->pixel_array->crop[CCS_PA_PAD_SRC].height
+               + sensor->vblank->val
+               - CCS_LIM(sensor, COARSE_INTEGRATION_TIME_MAX_MARGIN);
+
+       __v4l2_ctrl_modify_range(ctrl, ctrl->minimum, max, ctrl->step, max);
+}
+
+/*
+ * Order matters.
+ *
+ * 1. Bits-per-pixel, descending.
+ * 2. Bits-per-pixel compressed, descending.
+ * 3. Pixel order, same as in pixel_order_str. Formats for all four pixel
+ *    orders must be defined.
+ */
+static const struct ccs_csi_data_format ccs_csi_data_formats[] = {
+       { MEDIA_BUS_FMT_SGRBG16_1X16, 16, 16, CCS_PIXEL_ORDER_GRBG, },
+       { MEDIA_BUS_FMT_SRGGB16_1X16, 16, 16, CCS_PIXEL_ORDER_RGGB, },
+       { MEDIA_BUS_FMT_SBGGR16_1X16, 16, 16, CCS_PIXEL_ORDER_BGGR, },
+       { MEDIA_BUS_FMT_SGBRG16_1X16, 16, 16, CCS_PIXEL_ORDER_GBRG, },
+       { MEDIA_BUS_FMT_SGRBG14_1X14, 14, 14, CCS_PIXEL_ORDER_GRBG, },
+       { MEDIA_BUS_FMT_SRGGB14_1X14, 14, 14, CCS_PIXEL_ORDER_RGGB, },
+       { MEDIA_BUS_FMT_SBGGR14_1X14, 14, 14, CCS_PIXEL_ORDER_BGGR, },
+       { MEDIA_BUS_FMT_SGBRG14_1X14, 14, 14, CCS_PIXEL_ORDER_GBRG, },
+       { MEDIA_BUS_FMT_SGRBG12_1X12, 12, 12, CCS_PIXEL_ORDER_GRBG, },
+       { MEDIA_BUS_FMT_SRGGB12_1X12, 12, 12, CCS_PIXEL_ORDER_RGGB, },
+       { MEDIA_BUS_FMT_SBGGR12_1X12, 12, 12, CCS_PIXEL_ORDER_BGGR, },
+       { MEDIA_BUS_FMT_SGBRG12_1X12, 12, 12, CCS_PIXEL_ORDER_GBRG, },
+       { MEDIA_BUS_FMT_SGRBG10_1X10, 10, 10, CCS_PIXEL_ORDER_GRBG, },
+       { MEDIA_BUS_FMT_SRGGB10_1X10, 10, 10, CCS_PIXEL_ORDER_RGGB, },
+       { MEDIA_BUS_FMT_SBGGR10_1X10, 10, 10, CCS_PIXEL_ORDER_BGGR, },
+       { MEDIA_BUS_FMT_SGBRG10_1X10, 10, 10, CCS_PIXEL_ORDER_GBRG, },
+       { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, 10, 8, CCS_PIXEL_ORDER_GRBG, },
+       { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, 10, 8, CCS_PIXEL_ORDER_RGGB, },
+       { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, 10, 8, CCS_PIXEL_ORDER_BGGR, },
+       { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, 10, 8, CCS_PIXEL_ORDER_GBRG, },
+       { MEDIA_BUS_FMT_SGRBG8_1X8, 8, 8, CCS_PIXEL_ORDER_GRBG, },
+       { MEDIA_BUS_FMT_SRGGB8_1X8, 8, 8, CCS_PIXEL_ORDER_RGGB, },
+       { MEDIA_BUS_FMT_SBGGR8_1X8, 8, 8, CCS_PIXEL_ORDER_BGGR, },
+       { MEDIA_BUS_FMT_SGBRG8_1X8, 8, 8, CCS_PIXEL_ORDER_GBRG, },
+};
+
+static const char *pixel_order_str[] = { "GRBG", "RGGB", "BGGR", "GBRG" };
+
+#define to_csi_format_idx(fmt) (((unsigned long)(fmt)                  \
+                                - (unsigned long)ccs_csi_data_formats) \
+                               / sizeof(*ccs_csi_data_formats))
+
+static u32 ccs_pixel_order(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int flip = 0;
+
+       if (sensor->hflip) {
+               if (sensor->hflip->val)
+                       flip |= CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR;
+
+               if (sensor->vflip->val)
+                       flip |= CCS_IMAGE_ORIENTATION_VERTICAL_FLIP;
+       }
+
+       flip ^= sensor->hvflip_inv_mask;
+
+       dev_dbg(&client->dev, "flip %d\n", flip);
+       return sensor->default_pixel_order ^ flip;
+}
+
+static void ccs_update_mbus_formats(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       unsigned int csi_format_idx =
+               to_csi_format_idx(sensor->csi_format) & ~3;
+       unsigned int internal_csi_format_idx =
+               to_csi_format_idx(sensor->internal_csi_format) & ~3;
+       unsigned int pixel_order = ccs_pixel_order(sensor);
+
+       sensor->mbus_frame_fmts =
+               sensor->default_mbus_frame_fmts << pixel_order;
+       sensor->csi_format =
+               &ccs_csi_data_formats[csi_format_idx + pixel_order];
+       sensor->internal_csi_format =
+               &ccs_csi_data_formats[internal_csi_format_idx
+                                        + pixel_order];
+
+       BUG_ON(max(internal_csi_format_idx, csi_format_idx) + pixel_order
+              >= ARRAY_SIZE(ccs_csi_data_formats));
+
+       dev_dbg(&client->dev, "new pixel order %s\n",
+               pixel_order_str[pixel_order]);
+}
+
+static const char * const ccs_test_patterns[] = {
+       "Disabled",
+       "Solid Colour",
+       "Eight Vertical Colour Bars",
+       "Colour Bars With Fade to Grey",
+       "Pseudorandom Sequence (PN9)",
+};
+
+static int ccs_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct ccs_sensor *sensor =
+               container_of(ctrl->handler, struct ccs_subdev, ctrl_handler)
+                       ->sensor;
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int pm_status;
+       u32 orient = 0;
+       unsigned int i;
+       int exposure;
+       int rval;
+
+       switch (ctrl->id) {
+       case V4L2_CID_HFLIP:
+       case V4L2_CID_VFLIP:
+               if (sensor->streaming)
+                       return -EBUSY;
+
+               if (sensor->hflip->val)
+                       orient |= CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR;
+
+               if (sensor->vflip->val)
+                       orient |= CCS_IMAGE_ORIENTATION_VERTICAL_FLIP;
+
+               orient ^= sensor->hvflip_inv_mask;
+
+               ccs_update_mbus_formats(sensor);
+
+               break;
+       case V4L2_CID_VBLANK:
+               exposure = sensor->exposure->val;
+
+               __ccs_update_exposure_limits(sensor);
+
+               if (exposure > sensor->exposure->maximum) {
+                       sensor->exposure->val = sensor->exposure->maximum;
+                       rval = ccs_set_ctrl(sensor->exposure);
+                       if (rval < 0)
+                               return rval;
+               }
+
+               break;
+       case V4L2_CID_LINK_FREQ:
+               if (sensor->streaming)
+                       return -EBUSY;
+
+               rval = ccs_pll_update(sensor);
+               if (rval)
+                       return rval;
+
+               return 0;
+       case V4L2_CID_TEST_PATTERN:
+               for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++)
+                       v4l2_ctrl_activate(
+                               sensor->test_data[i],
+                               ctrl->val ==
+                               V4L2_SMIAPP_TEST_PATTERN_MODE_SOLID_COLOUR);
+
+               break;
+       }
+
+       pm_status = pm_runtime_get_if_active(&client->dev, true);
+       if (!pm_status)
+               return 0;
+
+       switch (ctrl->id) {
+       case V4L2_CID_ANALOGUE_GAIN:
+               rval = ccs_write(sensor, ANALOG_GAIN_CODE_GLOBAL, ctrl->val);
+
+               break;
+       case V4L2_CID_EXPOSURE:
+               rval = ccs_write(sensor, COARSE_INTEGRATION_TIME, ctrl->val);
+
+               break;
+       case V4L2_CID_HFLIP:
+       case V4L2_CID_VFLIP:
+               rval = ccs_write(sensor, IMAGE_ORIENTATION, orient);
+
+               break;
+       case V4L2_CID_VBLANK:
+               rval = ccs_write(sensor, FRAME_LENGTH_LINES,
+                                sensor->pixel_array->crop[
+                                        CCS_PA_PAD_SRC].height
+                                + ctrl->val);
+
+               break;
+       case V4L2_CID_HBLANK:
+               rval = ccs_write(sensor, LINE_LENGTH_PCK,
+                                sensor->pixel_array->crop[
+                                        CCS_PA_PAD_SRC].width
+                                + ctrl->val);
+
+               break;
+       case V4L2_CID_TEST_PATTERN:
+               rval = ccs_write(sensor, TEST_PATTERN_MODE, ctrl->val);
+
+               break;
+       case V4L2_CID_TEST_PATTERN_RED:
+               rval = ccs_write(sensor, TEST_DATA_RED, ctrl->val);
+
+               break;
+       case V4L2_CID_TEST_PATTERN_GREENR:
+               rval = ccs_write(sensor, TEST_DATA_GREENR, ctrl->val);
+
+               break;
+       case V4L2_CID_TEST_PATTERN_BLUE:
+               rval = ccs_write(sensor, TEST_DATA_BLUE, ctrl->val);
+
+               break;
+       case V4L2_CID_TEST_PATTERN_GREENB:
+               rval = ccs_write(sensor, TEST_DATA_GREENB, ctrl->val);
+
+               break;
+       case V4L2_CID_PIXEL_RATE:
+               /* For v4l2_ctrl_s_ctrl_int64() used internally. */
+               rval = 0;
+
+               break;
+       default:
+               rval = -EINVAL;
+       }
+
+       if (pm_status > 0) {
+               pm_runtime_mark_last_busy(&client->dev);
+               pm_runtime_put_autosuspend(&client->dev);
+       }
+
+       return rval;
+}
+
+static const struct v4l2_ctrl_ops ccs_ctrl_ops = {
+       .s_ctrl = ccs_set_ctrl,
+};
+
+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);
+       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));
+
+       /* Exposure limits will be updated soon, use just something here. */
+       sensor->exposure = v4l2_ctrl_new_std(
+               &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops,
+               V4L2_CID_EXPOSURE, 0, 0, 1, 0);
+
+       sensor->hflip = v4l2_ctrl_new_std(
+               &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops,
+               V4L2_CID_HFLIP, 0, 1, 1, 0);
+       sensor->vflip = v4l2_ctrl_new_std(
+               &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops,
+               V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+       sensor->vblank = v4l2_ctrl_new_std(
+               &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops,
+               V4L2_CID_VBLANK, 0, 1, 1, 0);
+
+       if (sensor->vblank)
+               sensor->vblank->flags |= V4L2_CTRL_FLAG_UPDATE;
+
+       sensor->hblank = v4l2_ctrl_new_std(
+               &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops,
+               V4L2_CID_HBLANK, 0, 1, 1, 0);
+
+       if (sensor->hblank)
+               sensor->hblank->flags |= V4L2_CTRL_FLAG_UPDATE;
+
+       sensor->pixel_rate_parray = v4l2_ctrl_new_std(
+               &sensor->pixel_array->ctrl_handler, &ccs_ctrl_ops,
+               V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
+
+       v4l2_ctrl_new_std_menu_items(&sensor->pixel_array->ctrl_handler,
+                                    &ccs_ctrl_ops, V4L2_CID_TEST_PATTERN,
+                                    ARRAY_SIZE(ccs_test_patterns) - 1,
+                                    0, 0, ccs_test_patterns);
+
+       if (sensor->pixel_array->ctrl_handler.error) {
+               dev_err(&client->dev,
+                       "pixel array controls initialization failed (%d)\n",
+                       sensor->pixel_array->ctrl_handler.error);
+               return sensor->pixel_array->ctrl_handler.error;
+       }
+
+       sensor->pixel_array->sd.ctrl_handler =
+               &sensor->pixel_array->ctrl_handler;
+
+       v4l2_ctrl_cluster(2, &sensor->hflip);
+
+       rval = v4l2_ctrl_handler_init(&sensor->src->ctrl_handler, 0);
+       if (rval)
+               return rval;
+
+       sensor->src->ctrl_handler.lock = &sensor->mutex;
+
+       sensor->pixel_rate_csi = v4l2_ctrl_new_std(
+               &sensor->src->ctrl_handler, &ccs_ctrl_ops,
+               V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
+
+       if (sensor->src->ctrl_handler.error) {
+               dev_err(&client->dev,
+                       "src controls initialization failed (%d)\n",
+                       sensor->src->ctrl_handler.error);
+               return sensor->src->ctrl_handler.error;
+       }
+
+       sensor->src->sd.ctrl_handler = &sensor->src->ctrl_handler;
+
+       return 0;
+}
+
+/*
+ * For controls that require information on available media bus codes
+ * and linke frequencies.
+ */
+static int ccs_init_late_controls(struct ccs_sensor *sensor)
+{
+       unsigned long *valid_link_freqs = &sensor->valid_link_freqs[
+               sensor->csi_format->compressed - sensor->compressed_min_bpp];
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++) {
+               int max_value = (1 << sensor->csi_format->width) - 1;
+
+               sensor->test_data[i] = v4l2_ctrl_new_std(
+                               &sensor->pixel_array->ctrl_handler,
+                               &ccs_ctrl_ops, V4L2_CID_TEST_PATTERN_RED + i,
+                               0, max_value, 1, max_value);
+       }
+
+       sensor->link_freq = v4l2_ctrl_new_int_menu(
+               &sensor->src->ctrl_handler, &ccs_ctrl_ops,
+               V4L2_CID_LINK_FREQ, __fls(*valid_link_freqs),
+               __ffs(*valid_link_freqs), sensor->hwcfg->op_sys_clock);
+
+       return sensor->src->ctrl_handler.error;
+}
+
+static void ccs_free_controls(struct ccs_sensor *sensor)
+{
+       unsigned int i;
+
+       for (i = 0; i < sensor->ssds_used; i++)
+               v4l2_ctrl_handler_free(&sensor->ssds[i].ctrl_handler);
+}
+
+static int ccs_get_mbus_formats(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       struct smiapp_pll *pll = &sensor->pll;
+       u8 compressed_max_bpp = 0;
+       unsigned int type, n;
+       unsigned int i, pixel_order;
+       int rval;
+
+       type = CCS_LIM(sensor, DATA_FORMAT_MODEL_TYPE);
+
+       dev_dbg(&client->dev, "data_format_model_type %d\n", type);
+
+       rval = ccs_read(sensor, PIXEL_ORDER, &pixel_order);
+       if (rval)
+               return rval;
+
+       if (pixel_order >= ARRAY_SIZE(pixel_order_str)) {
+               dev_dbg(&client->dev, "bad pixel order %d\n", pixel_order);
+               return -EINVAL;
+       }
+
+       dev_dbg(&client->dev, "pixel order %d (%s)\n", pixel_order,
+               pixel_order_str[pixel_order]);
+
+       switch (type) {
+       case CCS_DATA_FORMAT_MODEL_TYPE_NORMAL:
+               n = SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL_N;
+               break;
+       case CCS_DATA_FORMAT_MODEL_TYPE_EXTENDED:
+               n = CCS_LIM_DATA_FORMAT_DESCRIPTOR_MAX_N + 1;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       sensor->default_pixel_order = pixel_order;
+       sensor->mbus_frame_fmts = 0;
+
+       for (i = 0; i < n; i++) {
+               unsigned int fmt, j;
+
+               fmt = CCS_LIM_AT(sensor, DATA_FORMAT_DESCRIPTOR, i);
+
+               dev_dbg(&client->dev, "%u: bpp %u, compressed %u\n",
+                       i, fmt >> 8, (u8)fmt);
+
+               for (j = 0; j < ARRAY_SIZE(ccs_csi_data_formats); j++) {
+                       const struct ccs_csi_data_format *f =
+                               &ccs_csi_data_formats[j];
+
+                       if (f->pixel_order != CCS_PIXEL_ORDER_GRBG)
+                               continue;
+
+                       if (f->width != fmt >>
+                           CCS_DATA_FORMAT_DESCRIPTOR_UNCOMPRESSED_SHIFT ||
+                           f->compressed !=
+                           (fmt & CCS_DATA_FORMAT_DESCRIPTOR_COMPRESSED_MASK))
+                               continue;
+
+                       dev_dbg(&client->dev, "jolly good! %d\n", j);
+
+                       sensor->default_mbus_frame_fmts |= 1 << j;
+               }
+       }
+
+       /* Figure out which BPP values can be used with which formats. */
+       pll->binning_horizontal = 1;
+       pll->binning_vertical = 1;
+       pll->scale_m = sensor->scale_m;
+
+       for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) {
+               sensor->compressed_min_bpp =
+                       min(ccs_csi_data_formats[i].compressed,
+                           sensor->compressed_min_bpp);
+               compressed_max_bpp =
+                       max(ccs_csi_data_formats[i].compressed,
+                           compressed_max_bpp);
+       }
+
+       sensor->valid_link_freqs = devm_kcalloc(
+               &client->dev,
+               compressed_max_bpp - sensor->compressed_min_bpp + 1,
+               sizeof(*sensor->valid_link_freqs), GFP_KERNEL);
+       if (!sensor->valid_link_freqs)
+               return -ENOMEM;
+
+       for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) {
+               const struct ccs_csi_data_format *f =
+                       &ccs_csi_data_formats[i];
+               unsigned long *valid_link_freqs =
+                       &sensor->valid_link_freqs[
+                               f->compressed - sensor->compressed_min_bpp];
+               unsigned int j;
+
+               if (!(sensor->default_mbus_frame_fmts & 1 << i))
+                       continue;
+
+               pll->bits_per_pixel = f->compressed;
+
+               for (j = 0; sensor->hwcfg->op_sys_clock[j]; j++) {
+                       pll->link_freq = sensor->hwcfg->op_sys_clock[j];
+
+                       rval = ccs_pll_try(sensor, pll);
+                       dev_dbg(&client->dev, "link freq %u Hz, bpp %u %s\n",
+                               pll->link_freq, pll->bits_per_pixel,
+                               rval ? "not ok" : "ok");
+                       if (rval)
+                               continue;
+
+                       set_bit(j, valid_link_freqs);
+               }
+
+               if (!*valid_link_freqs) {
+                       dev_info(&client->dev,
+                                "no valid link frequencies for %u bpp\n",
+                                f->compressed);
+                       sensor->default_mbus_frame_fmts &= ~BIT(i);
+                       continue;
+               }
+
+               if (!sensor->csi_format
+                   || f->width > sensor->csi_format->width
+                   || (f->width == sensor->csi_format->width
+                       && f->compressed > sensor->csi_format->compressed)) {
+                       sensor->csi_format = f;
+                       sensor->internal_csi_format = f;
+               }
+       }
+
+       if (!sensor->csi_format) {
+               dev_err(&client->dev, "no supported mbus code found\n");
+               return -EINVAL;
+       }
+
+       ccs_update_mbus_formats(sensor);
+
+       return 0;
+}
+
+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;
+       int min, max;
+
+       if (sensor->binning_vertical > 1 || sensor->binning_horizontal > 1) {
+               min_fll = CCS_LIM(sensor, MIN_FRAME_LENGTH_LINES_BIN);
+               max_fll = CCS_LIM(sensor, MAX_FRAME_LENGTH_LINES_BIN);
+               min_llp = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN);
+               max_llp = CCS_LIM(sensor, MAX_LINE_LENGTH_PCK_BIN);
+               min_lbp = CCS_LIM(sensor, MIN_LINE_BLANKING_PCK_BIN);
+       } else {
+               min_fll = CCS_LIM(sensor, MIN_FRAME_LENGTH_LINES);
+               max_fll = CCS_LIM(sensor, MAX_FRAME_LENGTH_LINES);
+               min_llp = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK);
+               max_llp = CCS_LIM(sensor, MAX_LINE_LENGTH_PCK);
+               min_lbp = CCS_LIM(sensor, MIN_LINE_BLANKING_PCK);
+       }
+
+       min = max_t(int,
+                   CCS_LIM(sensor, MIN_FRAME_BLANKING_LINES),
+                   min_fll -
+                   sensor->pixel_array->crop[CCS_PA_PAD_SRC].height);
+       max = max_fll - sensor->pixel_array->crop[CCS_PA_PAD_SRC].height;
+
+       __v4l2_ctrl_modify_range(vblank, min, max, vblank->step, min);
+
+       min = max_t(int,
+                   min_llp -
+                   sensor->pixel_array->crop[CCS_PA_PAD_SRC].width,
+                   min_lbp);
+       max = max_llp - sensor->pixel_array->crop[CCS_PA_PAD_SRC].width;
+
+       __v4l2_ctrl_modify_range(hblank, min, max, hblank->step, min);
+
+       __ccs_update_exposure_limits(sensor);
+}
+
+static int ccs_pll_blanking_update(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int rval;
+
+       rval = ccs_pll_update(sensor);
+       if (rval < 0)
+               return rval;
+
+       /* Output from pixel array, including blanking */
+       ccs_update_blanking(sensor);
+
+       dev_dbg(&client->dev, "vblank\t\t%d\n", sensor->vblank->val);
+       dev_dbg(&client->dev, "hblank\t\t%d\n", sensor->hblank->val);
+
+       dev_dbg(&client->dev, "real timeperframe\t100/%d\n",
+               sensor->pll.pixel_rate_pixel_array /
+               ((sensor->pixel_array->crop[CCS_PA_PAD_SRC].width
+                 + sensor->hblank->val) *
+                (sensor->pixel_array->crop[CCS_PA_PAD_SRC].height
+                 + sensor->vblank->val) / 100));
+
+       return 0;
+}
+
+/*
+ *
+ * SMIA++ NVM handling
+ *
+ */
+
+static int ccs_read_nvm_page(struct ccs_sensor *sensor, u32 p, u8 *nvm,
+                            u8 *status)
+{
+       unsigned int i;
+       int rval;
+       u32 s;
+
+       *status = 0;
+
+       rval = ccs_write(sensor, DATA_TRANSFER_IF_1_PAGE_SELECT, p);
+       if (rval)
+               return rval;
+
+       rval = ccs_write(sensor, DATA_TRANSFER_IF_1_CTRL,
+                        CCS_DATA_TRANSFER_IF_1_CTRL_ENABLE);
+       if (rval)
+               return rval;
+
+       rval = ccs_read(sensor, DATA_TRANSFER_IF_1_STATUS, &s);
+       if (rval)
+               return rval;
+
+       if (s & CCS_DATA_TRANSFER_IF_1_STATUS_IMPROPER_IF_USAGE) {
+               *status = s;
+               return -ENODATA;
+       }
+
+       if (CCS_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) &
+           CCS_DATA_TRANSFER_IF_CAPABILITY_POLLING) {
+               for (i = 1000; i > 0; i--) {
+                       if (s & CCS_DATA_TRANSFER_IF_1_STATUS_READ_IF_READY)
+                               break;
+
+                       rval = ccs_read(sensor, DATA_TRANSFER_IF_1_STATUS, &s);
+                       if (rval)
+                               return rval;
+               }
+
+               if (!i)
+                       return -ETIMEDOUT;
+       }
+
+       for (i = 0; i <= CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P; i++) {
+               u32 v;
+
+               rval = ccs_read(sensor, DATA_TRANSFER_IF_1_DATA(i), &v);
+               if (rval)
+                       return rval;
+
+               *nvm++ = v;
+       }
+
+       return 0;
+}
+
+static int ccs_read_nvm(struct ccs_sensor *sensor, unsigned char *nvm,
+                       size_t nvm_size)
+{
+       u8 status = 0;
+       u32 p;
+       int rval = 0, rval2;
+
+       for (p = 0; p < nvm_size / (CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1)
+                    && !rval; p++) {
+               rval = ccs_read_nvm_page(sensor, p, nvm, &status);
+               nvm += CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1;
+       }
+
+       if (rval == -ENODATA &&
+           status & CCS_DATA_TRANSFER_IF_1_STATUS_IMPROPER_IF_USAGE)
+               rval = 0;
+
+       rval2 = ccs_write(sensor, DATA_TRANSFER_IF_1_CTRL, 0);
+       if (rval < 0)
+               return rval;
+       else
+               return rval2 ?: p * (CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1);
+}
+
+/*
+ *
+ * SMIA++ CCI address control
+ *
+ */
+static int ccs_change_cci_addr(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int rval;
+       u32 val;
+
+       client->addr = sensor->hwcfg->i2c_addr_dfl;
+
+       rval = ccs_write(sensor, CCI_ADDRESS_CTRL,
+                        sensor->hwcfg->i2c_addr_alt << 1);
+       if (rval)
+               return rval;
+
+       client->addr = sensor->hwcfg->i2c_addr_alt;
+
+       /* verify addr change went ok */
+       rval = ccs_read(sensor, CCI_ADDRESS_CTRL, &val);
+       if (rval)
+               return rval;
+
+       if (val != sensor->hwcfg->i2c_addr_alt << 1)
+               return -ENODEV;
+
+       return 0;
+}
+
+/*
+ *
+ * SMIA++ Mode Control
+ *
+ */
+static int ccs_setup_flash_strobe(struct ccs_sensor *sensor)
+{
+       struct ccs_flash_strobe_parms *strobe_setup;
+       unsigned int ext_freq = sensor->hwcfg->ext_clk;
+       u32 tmp;
+       u32 strobe_adjustment;
+       u32 strobe_width_high_rs;
+       int rval;
+
+       strobe_setup = sensor->hwcfg->strobe_setup;
+
+       /*
+        * How to calculate registers related to strobe length. Please
+        * do not change, or if you do at least know what you're
+        * doing. :-)
+        *
+        * Sakari Ailus <sakari.ailus@iki.fi> 2010-10-25
+        *
+        * flash_strobe_length [us] / 10^6 = (tFlash_strobe_width_ctrl
+        *      / EXTCLK freq [Hz]) * flash_strobe_adjustment
+        *
+        * tFlash_strobe_width_ctrl E N, [1 - 0xffff]
+        * flash_strobe_adjustment E N, [1 - 0xff]
+        *
+        * The formula above is written as below to keep it on one
+        * line:
+        *
+        * l / 10^6 = w / e * a
+        *
+        * Let's mark w * a by x:
+        *
+        * x = w * a
+        *
+        * Thus, we get:
+        *
+        * x = l * e / 10^6
+        *
+        * The strobe width must be at least as long as requested,
+        * thus rounding upwards is needed.
+        *
+        * x = (l * e + 10^6 - 1) / 10^6
+        * -----------------------------
+        *
+        * Maximum possible accuracy is wanted at all times. Thus keep
+        * a as small as possible.
+        *
+        * Calculate a, assuming maximum w, with rounding upwards:
+        *
+        * a = (x + (2^16 - 1) - 1) / (2^16 - 1)
+        * -------------------------------------
+        *
+        * Thus, we also get w, with that a, with rounding upwards:
+        *
+        * w = (x + a - 1) / a
+        * -------------------
+        *
+        * To get limits:
+        *
+        * x E [1, (2^16 - 1) * (2^8 - 1)]
+        *
+        * Substituting maximum x to the original formula (with rounding),
+        * the maximum l is thus
+        *
+        * (2^16 - 1) * (2^8 - 1) * 10^6 = l * e + 10^6 - 1
+        *
+        * l = (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / e
+        * --------------------------------------------------
+        *
+        * flash_strobe_length must be clamped between 1 and
+        * (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / EXTCLK freq.
+        *
+        * Then,
+        *
+        * flash_strobe_adjustment = ((flash_strobe_length *
+        *      EXTCLK freq + 10^6 - 1) / 10^6 + (2^16 - 1) - 1) / (2^16 - 1)
+        *
+        * tFlash_strobe_width_ctrl = ((flash_strobe_length *
+        *      EXTCLK freq + 10^6 - 1) / 10^6 +
+        *      flash_strobe_adjustment - 1) / flash_strobe_adjustment
+        */
+       tmp = div_u64(1000000ULL * ((1 << 16) - 1) * ((1 << 8) - 1) -
+                     1000000 + 1, ext_freq);
+       strobe_setup->strobe_width_high_us =
+               clamp_t(u32, strobe_setup->strobe_width_high_us, 1, tmp);
+
+       tmp = div_u64(((u64)strobe_setup->strobe_width_high_us * (u64)ext_freq +
+                       1000000 - 1), 1000000ULL);
+       strobe_adjustment = (tmp + (1 << 16) - 1 - 1) / ((1 << 16) - 1);
+       strobe_width_high_rs = (tmp + strobe_adjustment - 1) /
+                               strobe_adjustment;
+
+       rval = ccs_write(sensor, FLASH_MODE_RS, strobe_setup->mode);
+       if (rval < 0)
+               goto out;
+
+       rval = ccs_write(sensor, FLASH_STROBE_ADJUSTMENT, strobe_adjustment);
+       if (rval < 0)
+               goto out;
+
+       rval = ccs_write(sensor, TFLASH_STROBE_WIDTH_HIGH_RS_CTRL,
+                        strobe_width_high_rs);
+       if (rval < 0)
+               goto out;
+
+       rval = ccs_write(sensor, TFLASH_STROBE_DELAY_RS_CTRL,
+                        strobe_setup->strobe_delay);
+       if (rval < 0)
+               goto out;
+
+       rval = ccs_write(sensor, FLASH_STROBE_START_POINT,
+                        strobe_setup->stobe_start_point);
+       if (rval < 0)
+               goto out;
+
+       rval = ccs_write(sensor, FLASH_TRIGGER_RS, strobe_setup->trigger);
+
+out:
+       sensor->hwcfg->strobe_setup->trigger = 0;
+
+       return rval;
+}
+
+/* -----------------------------------------------------------------------------
+ * Power management
+ */
+
+static int ccs_power_on(struct device *dev)
+{
+       struct v4l2_subdev *subdev = dev_get_drvdata(dev);
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+       /*
+        * The sub-device related to the I2C device is always the
+        * source one, i.e. ssds[0].
+        */
+       struct ccs_sensor *sensor =
+               container_of(ssd, struct ccs_sensor, ssds[0]);
+       unsigned int sleep;
+       int rval;
+
+       rval = regulator_enable(sensor->vana);
+       if (rval) {
+               dev_err(dev, "failed to enable vana regulator\n");
+               return rval;
+       }
+       usleep_range(1000, 1000);
+
+       rval = clk_prepare_enable(sensor->ext_clk);
+       if (rval < 0) {
+               dev_dbg(dev, "failed to enable xclk\n");
+               goto out_xclk_fail;
+       }
+       usleep_range(1000, 1000);
+
+       gpiod_set_value(sensor->xshutdown, 1);
+
+       sleep = SMIAPP_RESET_DELAY(sensor->hwcfg->ext_clk);
+       usleep_range(sleep, sleep);
+
+       /*
+        * Failures to respond to the address change command have been noticed.
+        * Those failures seem to be caused by the sensor requiring a longer
+        * boot time than advertised. An additional 10ms delay seems to work
+        * around the issue, but the SMIA++ I2C write retry hack makes the delay
+        * unnecessary. The failures need to be investigated to find a proper
+        * fix, and a delay will likely need to be added here if the I2C write
+        * retry hack is reverted before the root cause of the boot time issue
+        * is found.
+        */
+
+       if (sensor->hwcfg->i2c_addr_alt) {
+               rval = ccs_change_cci_addr(sensor);
+               if (rval) {
+                       dev_err(dev, "cci address change error\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;
+       }
+
+       if (sensor->hwcfg->i2c_addr_alt) {
+               rval = ccs_change_cci_addr(sensor);
+               if (rval) {
+                       dev_err(dev, "cci address change error\n");
+                       goto out_cci_addr_fail;
+               }
+       }
+
+       rval = ccs_write(sensor, COMPRESSION_MODE,
+                        CCS_COMPRESSION_MODE_DPCM_PCM_SIMPLE);
+       if (rval) {
+               dev_err(dev, "compression mode set failed\n");
+               goto out_cci_addr_fail;
+       }
+
+       rval = ccs_write(sensor, EXTCLK_FREQUENCY_MHZ,
+                        sensor->hwcfg->ext_clk / (1000000 / (1 << 8)));
+       if (rval) {
+               dev_err(dev, "extclk frequency set failed\n");
+               goto out_cci_addr_fail;
+       }
+
+       rval = ccs_write(sensor, CSI_LANE_MODE, sensor->hwcfg->lanes - 1);
+       if (rval) {
+               dev_err(dev, "csi lane mode set failed\n");
+               goto out_cci_addr_fail;
+       }
+
+       rval = ccs_write(sensor, FAST_STANDBY_CTRL,
+                        CCS_FAST_STANDBY_CTRL_FRAME_TRUNCATION);
+       if (rval) {
+               dev_err(dev, "fast standby set failed\n");
+               goto out_cci_addr_fail;
+       }
+
+       rval = ccs_write(sensor, CSI_SIGNALING_MODE,
+                        sensor->hwcfg->csi_signalling_mode);
+       if (rval) {
+               dev_err(dev, "csi signalling mode set failed\n");
+               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);
+       if (rval < 0)
+               goto out_cci_addr_fail;
+
+       rval = ccs_call_quirk(sensor, post_poweron);
+       if (rval) {
+               dev_err(dev, "post_poweron quirks failed\n");
+               goto out_cci_addr_fail;
+       }
+
+       return 0;
+
+out_cci_addr_fail:
+       gpiod_set_value(sensor->xshutdown, 0);
+       clk_disable_unprepare(sensor->ext_clk);
+
+out_xclk_fail:
+       regulator_disable(sensor->vana);
+
+       return rval;
+}
+
+static int ccs_power_off(struct device *dev)
+{
+       struct v4l2_subdev *subdev = dev_get_drvdata(dev);
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+       struct ccs_sensor *sensor =
+               container_of(ssd, struct ccs_sensor, ssds[0]);
+
+       /*
+        * Currently power/clock to lens are enable/disabled separately
+        * but they are essentially the same signals. So if the sensor is
+        * powered off while the lens is powered on the sensor does not
+        * really see a power off and next time the cci address change
+        * will fail. So do a soft reset explicitly here.
+        */
+       if (sensor->hwcfg->i2c_addr_alt)
+               ccs_write(sensor, SOFTWARE_RESET, CCS_SOFTWARE_RESET_ON);
+
+       gpiod_set_value(sensor->xshutdown, 0);
+       clk_disable_unprepare(sensor->ext_clk);
+       usleep_range(5000, 5000);
+       regulator_disable(sensor->vana);
+       sensor->streaming = false;
+
+       return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Video stream management
+ */
+
+static int ccs_start_streaming(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       unsigned int binning_mode;
+       int rval;
+
+       mutex_lock(&sensor->mutex);
+
+       rval = ccs_write(sensor, CSI_DATA_FORMAT,
+                        (sensor->csi_format->width << 8) |
+                        sensor->csi_format->compressed);
+       if (rval)
+               goto out;
+
+       /* Binning configuration */
+       if (sensor->binning_horizontal == 1 &&
+           sensor->binning_vertical == 1) {
+               binning_mode = 0;
+       } else {
+               u8 binning_type =
+                       (sensor->binning_horizontal << 4)
+                       | sensor->binning_vertical;
+
+               rval = ccs_write(sensor, BINNING_TYPE, binning_type);
+               if (rval < 0)
+                       goto out;
+
+               binning_mode = 1;
+       }
+       rval = ccs_write(sensor, BINNING_MODE, binning_mode);
+       if (rval < 0)
+               goto out;
+
+       /* Set up PLL */
+       rval = ccs_pll_configure(sensor);
+       if (rval)
+               goto out;
+
+       /* Analog crop start coordinates */
+       rval = ccs_write(sensor, X_ADDR_START,
+                        sensor->pixel_array->crop[CCS_PA_PAD_SRC].left);
+       if (rval < 0)
+               goto out;
+
+       rval = ccs_write(sensor, Y_ADDR_START,
+                        sensor->pixel_array->crop[CCS_PA_PAD_SRC].top);
+       if (rval < 0)
+               goto out;
+
+       /* Analog crop end coordinates */
+       rval = ccs_write(
+               sensor, X_ADDR_END,
+               sensor->pixel_array->crop[CCS_PA_PAD_SRC].left
+               + sensor->pixel_array->crop[CCS_PA_PAD_SRC].width - 1);
+       if (rval < 0)
+               goto out;
+
+       rval = ccs_write(
+               sensor, Y_ADDR_END,
+               sensor->pixel_array->crop[CCS_PA_PAD_SRC].top
+               + sensor->pixel_array->crop[CCS_PA_PAD_SRC].height - 1);
+       if (rval < 0)
+               goto out;
+
+       /*
+        * Output from pixel array, including blanking, is set using
+        * controls below. No need to set here.
+        */
+
+       /* Digital crop */
+       if (CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY)
+           == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) {
+               rval = ccs_write(
+                       sensor, DIGITAL_CROP_X_OFFSET,
+                       sensor->scaler->crop[CCS_PAD_SINK].left);
+               if (rval < 0)
+                       goto out;
+
+               rval = ccs_write(
+                       sensor, DIGITAL_CROP_Y_OFFSET,
+                       sensor->scaler->crop[CCS_PAD_SINK].top);
+               if (rval < 0)
+                       goto out;
+
+               rval = ccs_write(
+                       sensor, DIGITAL_CROP_IMAGE_WIDTH,
+                       sensor->scaler->crop[CCS_PAD_SINK].width);
+               if (rval < 0)
+                       goto out;
+
+               rval = ccs_write(
+                       sensor, DIGITAL_CROP_IMAGE_HEIGHT,
+                       sensor->scaler->crop[CCS_PAD_SINK].height);
+               if (rval < 0)
+                       goto out;
+       }
+
+       /* Scaling */
+       if (CCS_LIM(sensor, SCALING_CAPABILITY)
+           != CCS_SCALING_CAPABILITY_NONE) {
+               rval = ccs_write(sensor, SCALING_MODE, sensor->scaling_mode);
+               if (rval < 0)
+                       goto out;
+
+               rval = ccs_write(sensor, SCALE_M, sensor->scale_m);
+               if (rval < 0)
+                       goto out;
+       }
+
+       /* Output size from sensor */
+       rval = ccs_write(sensor, X_OUTPUT_SIZE,
+                        sensor->src->crop[CCS_PAD_SRC].width);
+       if (rval < 0)
+               goto out;
+       rval = ccs_write(sensor, Y_OUTPUT_SIZE,
+                        sensor->src->crop[CCS_PAD_SRC].height);
+       if (rval < 0)
+               goto out;
+
+       if (CCS_LIM(sensor, FLASH_MODE_CAPABILITY) &
+           (CCS_FLASH_MODE_CAPABILITY_SINGLE_STROBE |
+            SMIAPP_FLASH_MODE_CAPABILITY_MULTIPLE_STROBE) &&
+           sensor->hwcfg->strobe_setup != NULL &&
+           sensor->hwcfg->strobe_setup->trigger != 0) {
+               rval = ccs_setup_flash_strobe(sensor);
+               if (rval)
+                       goto out;
+       }
+
+       rval = ccs_call_quirk(sensor, pre_streamon);
+       if (rval) {
+               dev_err(&client->dev, "pre_streamon quirks failed\n");
+               goto out;
+       }
+
+       rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_STREAMING);
+
+out:
+       mutex_unlock(&sensor->mutex);
+
+       return rval;
+}
+
+static int ccs_stop_streaming(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int rval;
+
+       mutex_lock(&sensor->mutex);
+       rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_SOFTWARE_STANDBY);
+       if (rval)
+               goto out;
+
+       rval = ccs_call_quirk(sensor, post_streamoff);
+       if (rval)
+               dev_err(&client->dev, "post_streamoff quirks failed\n");
+
+out:
+       mutex_unlock(&sensor->mutex);
+       return rval;
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev video operations
+ */
+
+static int ccs_pm_get_init(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int rval;
+
+       rval = pm_runtime_get_sync(&client->dev);
+       if (rval < 0) {
+               if (rval != -EBUSY && rval != -EAGAIN)
+                       pm_runtime_set_active(&client->dev);
+               pm_runtime_put_noidle(&client->dev);
+
+               return rval;
+       } else if (!rval) {
+               rval = v4l2_ctrl_handler_setup(&sensor->pixel_array->
+                                              ctrl_handler);
+               if (rval)
+                       return rval;
+
+               return v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler);
+       }
+
+       return 0;
+}
+
+static int ccs_set_stream(struct v4l2_subdev *subdev, int enable)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int rval;
+
+       if (sensor->streaming == enable)
+               return 0;
+
+       if (!enable) {
+               ccs_stop_streaming(sensor);
+               sensor->streaming = false;
+               pm_runtime_mark_last_busy(&client->dev);
+               pm_runtime_put_autosuspend(&client->dev);
+
+               return 0;
+       }
+
+       rval = ccs_pm_get_init(sensor);
+       if (rval)
+               return rval;
+
+       sensor->streaming = true;
+
+       rval = ccs_start_streaming(sensor);
+       if (rval < 0) {
+               sensor->streaming = false;
+               pm_runtime_mark_last_busy(&client->dev);
+               pm_runtime_put_autosuspend(&client->dev);
+       }
+
+       return rval;
+}
+
+static int ccs_enum_mbus_code(struct v4l2_subdev *subdev,
+                             struct v4l2_subdev_pad_config *cfg,
+                             struct v4l2_subdev_mbus_code_enum *code)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(subdev);
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       unsigned int i;
+       int idx = -1;
+       int rval = -EINVAL;
+
+       mutex_lock(&sensor->mutex);
+
+       dev_err(&client->dev, "subdev %s, pad %d, index %d\n",
+               subdev->name, code->pad, code->index);
+
+       if (subdev != &sensor->src->sd || code->pad != CCS_PAD_SRC) {
+               if (code->index)
+                       goto out;
+
+               code->code = sensor->internal_csi_format->code;
+               rval = 0;
+               goto out;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) {
+               if (sensor->mbus_frame_fmts & (1 << i))
+                       idx++;
+
+               if (idx == code->index) {
+                       code->code = ccs_csi_data_formats[i].code;
+                       dev_err(&client->dev, "found index %d, i %d, code %x\n",
+                               code->index, i, code->code);
+                       rval = 0;
+                       break;
+               }
+       }
+
+out:
+       mutex_unlock(&sensor->mutex);
+
+       return rval;
+}
+
+static u32 __ccs_get_mbus_code(struct v4l2_subdev *subdev, unsigned int pad)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+
+       if (subdev == &sensor->src->sd && pad == CCS_PAD_SRC)
+               return sensor->csi_format->code;
+       else
+               return sensor->internal_csi_format->code;
+}
+
+static int __ccs_get_format(struct v4l2_subdev *subdev,
+                           struct v4l2_subdev_pad_config *cfg,
+                           struct v4l2_subdev_format *fmt)
+{
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+
+       if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+               fmt->format = *v4l2_subdev_get_try_format(subdev, cfg,
+                                                         fmt->pad);
+       } else {
+               struct v4l2_rect *r;
+
+               if (fmt->pad == ssd->source_pad)
+                       r = &ssd->crop[ssd->source_pad];
+               else
+                       r = &ssd->sink_fmt;
+
+               fmt->format.code = __ccs_get_mbus_code(subdev, fmt->pad);
+               fmt->format.width = r->width;
+               fmt->format.height = r->height;
+               fmt->format.field = V4L2_FIELD_NONE;
+       }
+
+       return 0;
+}
+
+static int ccs_get_format(struct v4l2_subdev *subdev,
+                         struct v4l2_subdev_pad_config *cfg,
+                         struct v4l2_subdev_format *fmt)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       int rval;
+
+       mutex_lock(&sensor->mutex);
+       rval = __ccs_get_format(subdev, cfg, fmt);
+       mutex_unlock(&sensor->mutex);
+
+       return rval;
+}
+
+static void ccs_get_crop_compose(struct v4l2_subdev *subdev,
+                                struct v4l2_subdev_pad_config *cfg,
+                                struct v4l2_rect **crops,
+                                struct v4l2_rect **comps, int which)
+{
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+       unsigned int i;
+
+       if (which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+               if (crops)
+                       for (i = 0; i < subdev->entity.num_pads; i++)
+                               crops[i] = &ssd->crop[i];
+               if (comps)
+                       *comps = &ssd->compose;
+       } else {
+               if (crops) {
+                       for (i = 0; i < subdev->entity.num_pads; i++) {
+                               crops[i] = v4l2_subdev_get_try_crop(subdev, cfg, i);
+                               BUG_ON(!crops[i]);
+                       }
+               }
+               if (comps) {
+                       *comps = v4l2_subdev_get_try_compose(subdev, cfg,
+                                                            CCS_PAD_SINK);
+                       BUG_ON(!*comps);
+               }
+       }
+}
+
+/* Changes require propagation only on sink pad. */
+static void ccs_propagate(struct v4l2_subdev *subdev,
+                         struct v4l2_subdev_pad_config *cfg, int which,
+                         int target)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+       struct v4l2_rect *comp, *crops[CCS_PADS];
+
+       ccs_get_crop_compose(subdev, cfg, crops, &comp, which);
+
+       switch (target) {
+       case V4L2_SEL_TGT_CROP:
+               comp->width = crops[CCS_PAD_SINK]->width;
+               comp->height = crops[CCS_PAD_SINK]->height;
+               if (which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+                       if (ssd == sensor->scaler) {
+                               sensor->scale_m =
+                                       CCS_LIM(sensor, SCALER_N_MIN);
+                               sensor->scaling_mode =
+                                       CCS_SCALING_MODE_NO_SCALING;
+                       } else if (ssd == sensor->binner) {
+                               sensor->binning_horizontal = 1;
+                               sensor->binning_vertical = 1;
+                       }
+               }
+               fallthrough;
+       case V4L2_SEL_TGT_COMPOSE:
+               *crops[CCS_PAD_SRC] = *comp;
+               break;
+       default:
+               BUG();
+       }
+}
+
+static const struct ccs_csi_data_format
+*ccs_validate_csi_data_format(struct ccs_sensor *sensor, u32 code)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) {
+               if (sensor->mbus_frame_fmts & (1 << i) &&
+                   ccs_csi_data_formats[i].code == code)
+                       return &ccs_csi_data_formats[i];
+       }
+
+       return sensor->csi_format;
+}
+
+static int ccs_set_format_source(struct v4l2_subdev *subdev,
+                                struct v4l2_subdev_pad_config *cfg,
+                                struct v4l2_subdev_format *fmt)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       const struct ccs_csi_data_format *csi_format,
+               *old_csi_format = sensor->csi_format;
+       unsigned long *valid_link_freqs;
+       u32 code = fmt->format.code;
+       unsigned int i;
+       int rval;
+
+       rval = __ccs_get_format(subdev, cfg, fmt);
+       if (rval)
+               return rval;
+
+       /*
+        * Media bus code is changeable on src subdev's source pad. On
+        * other source pads we just get format here.
+        */
+       if (subdev != &sensor->src->sd)
+               return 0;
+
+       csi_format = ccs_validate_csi_data_format(sensor, code);
+
+       fmt->format.code = csi_format->code;
+
+       if (fmt->which != V4L2_SUBDEV_FORMAT_ACTIVE)
+               return 0;
+
+       sensor->csi_format = csi_format;
+
+       if (csi_format->width != old_csi_format->width)
+               for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++)
+                       __v4l2_ctrl_modify_range(
+                               sensor->test_data[i], 0,
+                               (1 << csi_format->width) - 1, 1, 0);
+
+       if (csi_format->compressed == old_csi_format->compressed)
+               return 0;
+
+       valid_link_freqs =
+               &sensor->valid_link_freqs[sensor->csi_format->compressed
+                                         - sensor->compressed_min_bpp];
+
+       __v4l2_ctrl_modify_range(
+               sensor->link_freq, 0,
+               __fls(*valid_link_freqs), ~*valid_link_freqs,
+               __ffs(*valid_link_freqs));
+
+       return ccs_pll_update(sensor);
+}
+
+static int ccs_set_format(struct v4l2_subdev *subdev,
+                         struct v4l2_subdev_pad_config *cfg,
+                         struct v4l2_subdev_format *fmt)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+       struct v4l2_rect *crops[CCS_PADS];
+
+       mutex_lock(&sensor->mutex);
+
+       if (fmt->pad == ssd->source_pad) {
+               int rval;
+
+               rval = ccs_set_format_source(subdev, cfg, fmt);
+
+               mutex_unlock(&sensor->mutex);
+
+               return rval;
+       }
+
+       /* Sink pad. Width and height are changeable here. */
+       fmt->format.code = __ccs_get_mbus_code(subdev, fmt->pad);
+       fmt->format.width &= ~1;
+       fmt->format.height &= ~1;
+       fmt->format.field = V4L2_FIELD_NONE;
+
+       fmt->format.width =
+               clamp(fmt->format.width,
+                     CCS_LIM(sensor, MIN_X_OUTPUT_SIZE),
+                     CCS_LIM(sensor, MAX_X_OUTPUT_SIZE));
+       fmt->format.height =
+               clamp(fmt->format.height,
+                     CCS_LIM(sensor, MIN_Y_OUTPUT_SIZE),
+                     CCS_LIM(sensor, MAX_Y_OUTPUT_SIZE));
+
+       ccs_get_crop_compose(subdev, cfg, crops, NULL, fmt->which);
+
+       crops[ssd->sink_pad]->left = 0;
+       crops[ssd->sink_pad]->top = 0;
+       crops[ssd->sink_pad]->width = fmt->format.width;
+       crops[ssd->sink_pad]->height = fmt->format.height;
+       if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+               ssd->sink_fmt = *crops[ssd->sink_pad];
+       ccs_propagate(subdev, cfg, fmt->which, V4L2_SEL_TGT_CROP);
+
+       mutex_unlock(&sensor->mutex);
+
+       return 0;
+}
+
+/*
+ * Calculate goodness of scaled image size compared to expected image
+ * size and flags provided.
+ */
+#define SCALING_GOODNESS               100000
+#define SCALING_GOODNESS_EXTREME       100000000
+static int scaling_goodness(struct v4l2_subdev *subdev, int w, int ask_w,
+                           int h, int ask_h, u32 flags)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct i2c_client *client = v4l2_get_subdevdata(subdev);
+       int val = 0;
+
+       w &= ~1;
+       ask_w &= ~1;
+       h &= ~1;
+       ask_h &= ~1;
+
+       if (flags & V4L2_SEL_FLAG_GE) {
+               if (w < ask_w)
+                       val -= SCALING_GOODNESS;
+               if (h < ask_h)
+                       val -= SCALING_GOODNESS;
+       }
+
+       if (flags & V4L2_SEL_FLAG_LE) {
+               if (w > ask_w)
+                       val -= SCALING_GOODNESS;
+               if (h > ask_h)
+                       val -= SCALING_GOODNESS;
+       }
+
+       val -= abs(w - ask_w);
+       val -= abs(h - ask_h);
+
+       if (w < CCS_LIM(sensor, MIN_X_OUTPUT_SIZE))
+               val -= SCALING_GOODNESS_EXTREME;
+
+       dev_dbg(&client->dev, "w %d ask_w %d h %d ask_h %d goodness %d\n",
+               w, ask_w, h, ask_h, val);
+
+       return val;
+}
+
+static void ccs_set_compose_binner(struct v4l2_subdev *subdev,
+                                  struct v4l2_subdev_pad_config *cfg,
+                                  struct v4l2_subdev_selection *sel,
+                                  struct v4l2_rect **crops,
+                                  struct v4l2_rect *comp)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       unsigned int i;
+       unsigned int binh = 1, binv = 1;
+       int best = scaling_goodness(
+               subdev,
+               crops[CCS_PAD_SINK]->width, sel->r.width,
+               crops[CCS_PAD_SINK]->height, sel->r.height, sel->flags);
+
+       for (i = 0; i < sensor->nbinning_subtypes; i++) {
+               int this = scaling_goodness(
+                       subdev,
+                       crops[CCS_PAD_SINK]->width
+                       / sensor->binning_subtypes[i].horizontal,
+                       sel->r.width,
+                       crops[CCS_PAD_SINK]->height
+                       / sensor->binning_subtypes[i].vertical,
+                       sel->r.height, sel->flags);
+
+               if (this > best) {
+                       binh = sensor->binning_subtypes[i].horizontal;
+                       binv = sensor->binning_subtypes[i].vertical;
+                       best = this;
+               }
+       }
+       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+               sensor->binning_vertical = binv;
+               sensor->binning_horizontal = binh;
+       }
+
+       sel->r.width = (crops[CCS_PAD_SINK]->width / binh) & ~1;
+       sel->r.height = (crops[CCS_PAD_SINK]->height / binv) & ~1;
+}
+
+/*
+ * Calculate best scaling ratio and mode for given output resolution.
+ *
+ * Try all of these: horizontal ratio, vertical ratio and smallest
+ * size possible (horizontally).
+ *
+ * Also try whether horizontal scaler or full scaler gives a better
+ * result.
+ */
+static void ccs_set_compose_scaler(struct v4l2_subdev *subdev,
+                                  struct v4l2_subdev_pad_config *cfg,
+                                  struct v4l2_subdev_selection *sel,
+                                  struct v4l2_rect **crops,
+                                  struct v4l2_rect *comp)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(subdev);
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       u32 min, max, a, b, max_m;
+       u32 scale_m = CCS_LIM(sensor, SCALER_N_MIN);
+       int mode = CCS_SCALING_MODE_HORIZONTAL;
+       u32 try[4];
+       u32 ntry = 0;
+       unsigned int i;
+       int best = INT_MIN;
+
+       sel->r.width = min_t(unsigned int, sel->r.width,
+                            crops[CCS_PAD_SINK]->width);
+       sel->r.height = min_t(unsigned int, sel->r.height,
+                             crops[CCS_PAD_SINK]->height);
+
+       a = crops[CCS_PAD_SINK]->width
+               * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.width;
+       b = crops[CCS_PAD_SINK]->height
+               * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.height;
+       max_m = crops[CCS_PAD_SINK]->width
+               * CCS_LIM(sensor, SCALER_N_MIN)
+               / CCS_LIM(sensor, MIN_X_OUTPUT_SIZE);
+
+       a = clamp(a, CCS_LIM(sensor, SCALER_M_MIN),
+                 CCS_LIM(sensor, SCALER_M_MAX));
+       b = clamp(b, CCS_LIM(sensor, SCALER_M_MIN),
+                 CCS_LIM(sensor, SCALER_M_MAX));
+       max_m = clamp(max_m, CCS_LIM(sensor, SCALER_M_MIN),
+                     CCS_LIM(sensor, SCALER_M_MAX));
+
+       dev_dbg(&client->dev, "scaling: a %d b %d max_m %d\n", a, b, max_m);
+
+       min = min(max_m, min(a, b));
+       max = min(max_m, max(a, b));
+
+       try[ntry] = min;
+       ntry++;
+       if (min != max) {
+               try[ntry] = max;
+               ntry++;
+       }
+       if (max != max_m) {
+               try[ntry] = min + 1;
+               ntry++;
+               if (min != max) {
+                       try[ntry] = max + 1;
+                       ntry++;
+               }
+       }
+
+       for (i = 0; i < ntry; i++) {
+               int this = scaling_goodness(
+                       subdev,
+                       crops[CCS_PAD_SINK]->width
+                       / try[i] * CCS_LIM(sensor, SCALER_N_MIN),
+                       sel->r.width,
+                       crops[CCS_PAD_SINK]->height,
+                       sel->r.height,
+                       sel->flags);
+
+               dev_dbg(&client->dev, "trying factor %d (%d)\n", try[i], i);
+
+               if (this > best) {
+                       scale_m = try[i];
+                       mode = CCS_SCALING_MODE_HORIZONTAL;
+                       best = this;
+               }
+
+               if (CCS_LIM(sensor, SCALING_CAPABILITY)
+                   == CCS_SCALING_CAPABILITY_HORIZONTAL)
+                       continue;
+
+               this = scaling_goodness(
+                       subdev, crops[CCS_PAD_SINK]->width
+                       / try[i]
+                       * CCS_LIM(sensor, SCALER_N_MIN),
+                       sel->r.width,
+                       crops[CCS_PAD_SINK]->height
+                       / try[i]
+                       * CCS_LIM(sensor, SCALER_N_MIN),
+                       sel->r.height,
+                       sel->flags);
+
+               if (this > best) {
+                       scale_m = try[i];
+                       mode = SMIAPP_SCALING_MODE_BOTH;
+                       best = this;
+               }
+       }
+
+       sel->r.width =
+               (crops[CCS_PAD_SINK]->width
+                / scale_m
+                * CCS_LIM(sensor, SCALER_N_MIN)) & ~1;
+       if (mode == SMIAPP_SCALING_MODE_BOTH)
+               sel->r.height =
+                       (crops[CCS_PAD_SINK]->height
+                        / scale_m
+                        * CCS_LIM(sensor, SCALER_N_MIN))
+                       & ~1;
+       else
+               sel->r.height = crops[CCS_PAD_SINK]->height;
+
+       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+               sensor->scale_m = scale_m;
+               sensor->scaling_mode = mode;
+       }
+}
+/* We're only called on source pads. This function sets scaling. */
+static int ccs_set_compose(struct v4l2_subdev *subdev,
+                          struct v4l2_subdev_pad_config *cfg,
+                          struct v4l2_subdev_selection *sel)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+       struct v4l2_rect *comp, *crops[CCS_PADS];
+
+       ccs_get_crop_compose(subdev, cfg, crops, &comp, sel->which);
+
+       sel->r.top = 0;
+       sel->r.left = 0;
+
+       if (ssd == sensor->binner)
+               ccs_set_compose_binner(subdev, cfg, sel, crops, comp);
+       else
+               ccs_set_compose_scaler(subdev, cfg, sel, crops, comp);
+
+       *comp = sel->r;
+       ccs_propagate(subdev, cfg, sel->which, V4L2_SEL_TGT_COMPOSE);
+
+       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+               return ccs_pll_blanking_update(sensor);
+
+       return 0;
+}
+
+static int __ccs_sel_supported(struct v4l2_subdev *subdev,
+                              struct v4l2_subdev_selection *sel)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+
+       /* We only implement crop in three places. */
+       switch (sel->target) {
+       case V4L2_SEL_TGT_CROP:
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+               if (ssd == sensor->pixel_array && sel->pad == CCS_PA_PAD_SRC)
+                       return 0;
+               if (ssd == sensor->src && sel->pad == CCS_PAD_SRC)
+                       return 0;
+               if (ssd == sensor->scaler && sel->pad == CCS_PAD_SINK &&
+                   CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY)
+                   == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP)
+                       return 0;
+               return -EINVAL;
+       case V4L2_SEL_TGT_NATIVE_SIZE:
+               if (ssd == sensor->pixel_array && sel->pad == CCS_PA_PAD_SRC)
+                       return 0;
+               return -EINVAL;
+       case V4L2_SEL_TGT_COMPOSE:
+       case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+               if (sel->pad == ssd->source_pad)
+                       return -EINVAL;
+               if (ssd == sensor->binner)
+                       return 0;
+               if (ssd == sensor->scaler && CCS_LIM(sensor, SCALING_CAPABILITY)
+                   != CCS_SCALING_CAPABILITY_NONE)
+                       return 0;
+               fallthrough;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ccs_set_crop(struct v4l2_subdev *subdev,
+                       struct v4l2_subdev_pad_config *cfg,
+                       struct v4l2_subdev_selection *sel)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+       struct v4l2_rect *src_size, *crops[CCS_PADS];
+       struct v4l2_rect _r;
+
+       ccs_get_crop_compose(subdev, cfg, crops, NULL, sel->which);
+
+       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+               if (sel->pad == ssd->sink_pad)
+                       src_size = &ssd->sink_fmt;
+               else
+                       src_size = &ssd->compose;
+       } else {
+               if (sel->pad == ssd->sink_pad) {
+                       _r.left = 0;
+                       _r.top = 0;
+                       _r.width = v4l2_subdev_get_try_format(subdev, cfg, sel->pad)
+                               ->width;
+                       _r.height = v4l2_subdev_get_try_format(subdev, cfg, sel->pad)
+                               ->height;
+                       src_size = &_r;
+               } else {
+                       src_size = v4l2_subdev_get_try_compose(
+                               subdev, cfg, ssd->sink_pad);
+               }
+       }
+
+       if (ssd == sensor->src && sel->pad == CCS_PAD_SRC) {
+               sel->r.left = 0;
+               sel->r.top = 0;
+       }
+
+       sel->r.width = min(sel->r.width, src_size->width);
+       sel->r.height = min(sel->r.height, src_size->height);
+
+       sel->r.left = min_t(int, sel->r.left, src_size->width - sel->r.width);
+       sel->r.top = min_t(int, sel->r.top, src_size->height - sel->r.height);
+
+       *crops[sel->pad] = sel->r;
+
+       if (ssd != sensor->pixel_array && sel->pad == CCS_PAD_SINK)
+               ccs_propagate(subdev, cfg, sel->which, V4L2_SEL_TGT_CROP);
+
+       return 0;
+}
+
+static void ccs_get_native_size(struct ccs_subdev *ssd, struct v4l2_rect *r)
+{
+       r->top = 0;
+       r->left = 0;
+       r->width = CCS_LIM(ssd->sensor, X_ADDR_MAX) + 1;
+       r->height = CCS_LIM(ssd->sensor, Y_ADDR_MAX) + 1;
+}
+
+static int __ccs_get_selection(struct v4l2_subdev *subdev,
+                              struct v4l2_subdev_pad_config *cfg,
+                              struct v4l2_subdev_selection *sel)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct ccs_subdev *ssd = to_ccs_subdev(subdev);
+       struct v4l2_rect *comp, *crops[CCS_PADS];
+       struct v4l2_rect sink_fmt;
+       int ret;
+
+       ret = __ccs_sel_supported(subdev, sel);
+       if (ret)
+               return ret;
+
+       ccs_get_crop_compose(subdev, cfg, crops, &comp, sel->which);
+
+       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+               sink_fmt = ssd->sink_fmt;
+       } else {
+               struct v4l2_mbus_framefmt *fmt =
+                       v4l2_subdev_get_try_format(subdev, cfg, ssd->sink_pad);
+
+               sink_fmt.left = 0;
+               sink_fmt.top = 0;
+               sink_fmt.width = fmt->width;
+               sink_fmt.height = fmt->height;
+       }
+
+       switch (sel->target) {
+       case V4L2_SEL_TGT_CROP_BOUNDS:
+       case V4L2_SEL_TGT_NATIVE_SIZE:
+               if (ssd == sensor->pixel_array)
+                       ccs_get_native_size(ssd, &sel->r);
+               else if (sel->pad == ssd->sink_pad)
+                       sel->r = sink_fmt;
+               else
+                       sel->r = *comp;
+               break;
+       case V4L2_SEL_TGT_CROP:
+       case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+               sel->r = *crops[sel->pad];
+               break;
+       case V4L2_SEL_TGT_COMPOSE:
+               sel->r = *comp;
+               break;
+       }
+
+       return 0;
+}
+
+static int ccs_get_selection(struct v4l2_subdev *subdev,
+                            struct v4l2_subdev_pad_config *cfg,
+                            struct v4l2_subdev_selection *sel)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       int rval;
+
+       mutex_lock(&sensor->mutex);
+       rval = __ccs_get_selection(subdev, cfg, sel);
+       mutex_unlock(&sensor->mutex);
+
+       return rval;
+}
+
+static int ccs_set_selection(struct v4l2_subdev *subdev,
+                            struct v4l2_subdev_pad_config *cfg,
+                            struct v4l2_subdev_selection *sel)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       int ret;
+
+       ret = __ccs_sel_supported(subdev, sel);
+       if (ret)
+               return ret;
+
+       mutex_lock(&sensor->mutex);
+
+       sel->r.left = max(0, sel->r.left & ~1);
+       sel->r.top = max(0, sel->r.top & ~1);
+       sel->r.width = CCS_ALIGN_DIM(sel->r.width, sel->flags);
+       sel->r.height = CCS_ALIGN_DIM(sel->r.height, sel->flags);
+
+       sel->r.width = max_t(unsigned int,
+                            CCS_LIM(sensor, MIN_X_OUTPUT_SIZE),
+                            sel->r.width);
+       sel->r.height = max_t(unsigned int,
+                             CCS_LIM(sensor, MIN_Y_OUTPUT_SIZE),
+                             sel->r.height);
+
+       switch (sel->target) {
+       case V4L2_SEL_TGT_CROP:
+               ret = ccs_set_crop(subdev, cfg, sel);
+               break;
+       case V4L2_SEL_TGT_COMPOSE:
+               ret = ccs_set_compose(subdev, cfg, sel);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       mutex_unlock(&sensor->mutex);
+       return ret;
+}
+
+static int ccs_get_skip_frames(struct v4l2_subdev *subdev, u32 *frames)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+
+       *frames = sensor->frame_skip;
+       return 0;
+}
+
+static int ccs_get_skip_top_lines(struct v4l2_subdev *subdev, u32 *lines)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+
+       *lines = sensor->image_start;
+
+       return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * sysfs attributes
+ */
+
+static ssize_t
+ccs_sysfs_nvm_read(struct device *dev, struct device_attribute *attr,
+                  char *buf)
+{
+       struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev));
+       struct i2c_client *client = v4l2_get_subdevdata(subdev);
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       int rval;
+
+       if (!sensor->dev_init_done)
+               return -EBUSY;
+
+       rval = ccs_pm_get_init(sensor);
+       if (rval < 0)
+               return -ENODEV;
+
+       rval = ccs_read_nvm(sensor, buf, PAGE_SIZE);
+       if (rval < 0) {
+               pm_runtime_put(&client->dev);
+               dev_err(&client->dev, "nvm read failed\n");
+               return -ENODEV;
+       }
+
+       pm_runtime_mark_last_busy(&client->dev);
+       pm_runtime_put_autosuspend(&client->dev);
+
+       /*
+        * NVM is still way below a PAGE_SIZE, so we can safely
+        * assume this for now.
+        */
+       return rval;
+}
+static DEVICE_ATTR(nvm, S_IRUGO, ccs_sysfs_nvm_read, NULL);
+
+static ssize_t
+ccs_sysfs_ident_read(struct device *dev, struct device_attribute *attr,
+                    char *buf)
+{
+       struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev));
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       struct ccs_module_info *minfo = &sensor->minfo;
+
+       if (minfo->mipi_manufacturer_id)
+               return snprintf(buf, PAGE_SIZE, "%4.4x%4.4x%2.2x\n",
+                               minfo->mipi_manufacturer_id, minfo->model_id,
+                               minfo->revision_number_major) + 1;
+       else
+               return snprintf(buf, PAGE_SIZE, "%2.2x%4.4x%2.2x\n",
+                               minfo->smia_manufacturer_id, minfo->model_id,
+                               minfo->revision_number_major) + 1;
+}
+
+static DEVICE_ATTR(ident, S_IRUGO, ccs_sysfs_ident_read, NULL);
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev core operations
+ */
+
+static int ccs_identify_module(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       struct ccs_module_info *minfo = &sensor->minfo;
+       unsigned int i;
+       int rval = 0;
+
+       minfo->name = SMIAPP_NAME;
+
+       /* Module info */
+       rval = ccs_read(sensor, MODULE_MANUFACTURER_ID,
+                       &minfo->mipi_manufacturer_id);
+       if (!rval && !minfo->mipi_manufacturer_id)
+               rval = ccs_read_addr_8only(sensor,
+                                          SMIAPP_REG_U8_MANUFACTURER_ID,
+                                          &minfo->smia_manufacturer_id);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor, CCS_R_MODULE_MODEL_ID,
+                                          &minfo->model_id);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor,
+                                          CCS_R_MODULE_REVISION_NUMBER_MAJOR,
+                                          &minfo->revision_number_major);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor,
+                                          CCS_R_MODULE_REVISION_NUMBER_MINOR,
+                                          &minfo->revision_number_minor);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor, CCS_R_MODULE_DATE_YEAR,
+                                          &minfo->module_year);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor, CCS_R_MODULE_DATE_MONTH,
+                                          &minfo->module_month);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor, CCS_R_MODULE_DATE_DAY,
+                                          &minfo->module_day);
+
+       /* Sensor info */
+       if (!rval)
+               rval = ccs_read(sensor, SENSOR_MANUFACTURER_ID,
+                               &minfo->sensor_mipi_manufacturer_id);
+       if (!rval && !minfo->sensor_mipi_manufacturer_id)
+               rval = ccs_read_addr_8only(sensor,
+                                          CCS_R_SENSOR_MANUFACTURER_ID,
+                                          &minfo->sensor_smia_manufacturer_id);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor,
+                                          CCS_R_SENSOR_MODEL_ID,
+                                          &minfo->sensor_model_id);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor,
+                                          CCS_R_SENSOR_REVISION_NUMBER,
+                                          &minfo->sensor_revision_number);
+       if (!rval)
+               rval = ccs_read_addr_8only(sensor,
+                                          CCS_R_SENSOR_FIRMWARE_VERSION,
+                                          &minfo->sensor_firmware_version);
+
+       /* SMIA */
+       if (!rval)
+               rval = ccs_read(sensor, MIPI_CCS_VERSION, &minfo->ccs_version);
+       if (!rval && !minfo->ccs_version)
+               rval = ccs_read_addr_8only(sensor, SMIAPP_REG_U8_SMIA_VERSION,
+                                          &minfo->smia_version);
+       if (!rval && !minfo->ccs_version)
+               rval = ccs_read_addr_8only(sensor, SMIAPP_REG_U8_SMIAPP_VERSION,
+                                          &minfo->smiapp_version);
+
+       if (rval) {
+               dev_err(&client->dev, "sensor detection failed\n");
+               return -ENODEV;
+       }
+
+       if (minfo->mipi_manufacturer_id)
+               dev_dbg(&client->dev, "MIPI CCS module 0x%4.4x-0x%4.4x\n",
+                       minfo->mipi_manufacturer_id, minfo->model_id);
+       else
+               dev_dbg(&client->dev, "SMIA module 0x%2.2x-0x%4.4x\n",
+                       minfo->smia_manufacturer_id, minfo->model_id);
+
+       dev_dbg(&client->dev,
+               "module revision 0x%2.2x-0x%2.2x date %2.2d-%2.2d-%2.2d\n",
+               minfo->revision_number_major, minfo->revision_number_minor,
+               minfo->module_year, minfo->module_month, minfo->module_day);
+
+       if (minfo->sensor_mipi_manufacturer_id)
+               dev_dbg(&client->dev, "MIPI CCS sensor 0x%4.4x-0x%4.4x\n",
+                       minfo->sensor_mipi_manufacturer_id,
+                       minfo->sensor_model_id);
+       else
+               dev_dbg(&client->dev, "SMIA sensor 0x%2.2x-0x%4.4x\n",
+                       minfo->sensor_smia_manufacturer_id,
+                       minfo->sensor_model_id);
+
+       dev_dbg(&client->dev,
+               "sensor revision 0x%2.2x firmware version 0x%2.2x\n",
+               minfo->sensor_revision_number, minfo->sensor_firmware_version);
+
+       if (minfo->ccs_version)
+               dev_dbg(&client->dev, "MIPI CCS version %u.%u",
+                       (minfo->ccs_version & CCS_MIPI_CCS_VERSION_MAJOR_MASK)
+                       >> CCS_MIPI_CCS_VERSION_MAJOR_SHIFT,
+                       (minfo->ccs_version & CCS_MIPI_CCS_VERSION_MINOR_MASK));
+       else
+               dev_dbg(&client->dev,
+                       "smia version %2.2d smiapp version %2.2d\n",
+                       minfo->smia_version, minfo->smiapp_version);
+
+       /*
+        * Some modules have bad data in the lvalues below. Hope the
+        * rvalues have better stuff. The lvalues are module
+        * parameters whereas the rvalues are sensor parameters.
+        */
+       if (minfo->sensor_smia_manufacturer_id &&
+           !minfo->smia_manufacturer_id && !minfo->model_id) {
+               minfo->smia_manufacturer_id =
+                       minfo->sensor_smia_manufacturer_id;
+               minfo->model_id = minfo->sensor_model_id;
+               minfo->revision_number_major = minfo->sensor_revision_number;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(ccs_module_idents); i++) {
+               if (ccs_module_idents[i].mipi_manufacturer_id &&
+                   ccs_module_idents[i].mipi_manufacturer_id
+                   != minfo->mipi_manufacturer_id)
+                       continue;
+               if (ccs_module_idents[i].smia_manufacturer_id &&
+                   ccs_module_idents[i].smia_manufacturer_id
+                   != minfo->smia_manufacturer_id)
+                       continue;
+               if (ccs_module_idents[i].model_id != minfo->model_id)
+                       continue;
+               if (ccs_module_idents[i].flags
+                   & CCS_MODULE_IDENT_FLAG_REV_LE) {
+                       if (ccs_module_idents[i].revision_number_major
+                           < minfo->revision_number_major)
+                               continue;
+               } else {
+                       if (ccs_module_idents[i].revision_number_major
+                           != minfo->revision_number_major)
+                               continue;
+               }
+
+               minfo->name = ccs_module_idents[i].name;
+               minfo->quirk = ccs_module_idents[i].quirk;
+               break;
+       }
+
+       if (i >= ARRAY_SIZE(ccs_module_idents))
+               dev_warn(&client->dev,
+                        "no quirks for this module; let's hope it's fully compliant\n");
+
+       dev_dbg(&client->dev, "the sensor is called %s\n",
+               minfo->name);
+
+       return 0;
+}
+
+static const struct v4l2_subdev_ops ccs_ops;
+static const struct v4l2_subdev_internal_ops ccs_internal_ops;
+static const struct media_entity_operations ccs_entity_ops;
+
+static int ccs_register_subdev(struct ccs_sensor *sensor,
+                              struct ccs_subdev *ssd,
+                              struct ccs_subdev *sink_ssd,
+                              u16 source_pad, u16 sink_pad, u32 link_flags)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int rval;
+
+       if (!sink_ssd)
+               return 0;
+
+       rval = media_entity_pads_init(&ssd->sd.entity,
+                                     ssd->npads, ssd->pads);
+       if (rval) {
+               dev_err(&client->dev,
+                       "media_entity_pads_init failed\n");
+               return rval;
+       }
+
+       rval = v4l2_device_register_subdev(sensor->src->sd.v4l2_dev,
+                                          &ssd->sd);
+       if (rval) {
+               dev_err(&client->dev,
+                       "v4l2_device_register_subdev failed\n");
+               return rval;
+       }
+
+       rval = media_create_pad_link(&ssd->sd.entity, source_pad,
+                                    &sink_ssd->sd.entity, sink_pad,
+                                    link_flags);
+       if (rval) {
+               dev_err(&client->dev,
+                       "media_create_pad_link failed\n");
+               v4l2_device_unregister_subdev(&ssd->sd);
+               return rval;
+       }
+
+       return 0;
+}
+
+static void ccs_unregistered(struct v4l2_subdev *subdev)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       unsigned int i;
+
+       for (i = 1; i < sensor->ssds_used; i++)
+               v4l2_device_unregister_subdev(&sensor->ssds[i].sd);
+}
+
+static int ccs_registered(struct v4l2_subdev *subdev)
+{
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       int rval;
+
+       if (sensor->scaler) {
+               rval = ccs_register_subdev(sensor, sensor->binner,
+                                          sensor->scaler,
+                                          CCS_PAD_SRC, CCS_PAD_SINK,
+                                          MEDIA_LNK_FL_ENABLED |
+                                          MEDIA_LNK_FL_IMMUTABLE);
+               if (rval < 0)
+                       return rval;
+       }
+
+       rval = ccs_register_subdev(sensor, sensor->pixel_array, sensor->binner,
+                                  CCS_PA_PAD_SRC, CCS_PAD_SINK,
+                                  MEDIA_LNK_FL_ENABLED |
+                                  MEDIA_LNK_FL_IMMUTABLE);
+       if (rval)
+               goto out_err;
+
+       return 0;
+
+out_err:
+       ccs_unregistered(subdev);
+
+       return rval;
+}
+
+static void ccs_cleanup(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+
+       device_remove_file(&client->dev, &dev_attr_nvm);
+       device_remove_file(&client->dev, &dev_attr_ident);
+
+       ccs_free_controls(sensor);
+}
+
+static void ccs_create_subdev(struct ccs_sensor *sensor,
+                             struct ccs_subdev *ssd, const char *name,
+                             unsigned short num_pads)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+
+       if (!ssd)
+               return;
+
+       if (ssd != sensor->src)
+               v4l2_subdev_init(&ssd->sd, &ccs_ops);
+
+       ssd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       ssd->sensor = sensor;
+
+       ssd->npads = num_pads;
+       ssd->source_pad = num_pads - 1;
+
+       v4l2_i2c_subdev_set_name(&ssd->sd, client, sensor->minfo.name, name);
+
+       ccs_get_native_size(ssd, &ssd->sink_fmt);
+
+       ssd->compose.width = ssd->sink_fmt.width;
+       ssd->compose.height = ssd->sink_fmt.height;
+       ssd->crop[ssd->source_pad] = ssd->compose;
+       ssd->pads[ssd->source_pad].flags = MEDIA_PAD_FL_SOURCE;
+       if (ssd != sensor->pixel_array) {
+               ssd->crop[ssd->sink_pad] = ssd->compose;
+               ssd->pads[ssd->sink_pad].flags = MEDIA_PAD_FL_SINK;
+       }
+
+       ssd->sd.entity.ops = &ccs_entity_ops;
+
+       if (ssd == sensor->src)
+               return;
+
+       ssd->sd.internal_ops = &ccs_internal_ops;
+       ssd->sd.owner = THIS_MODULE;
+       ssd->sd.dev = &client->dev;
+       v4l2_set_subdevdata(&ssd->sd, client);
+}
+
+static int ccs_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+       struct ccs_subdev *ssd = to_ccs_subdev(sd);
+       struct ccs_sensor *sensor = ssd->sensor;
+       unsigned int i;
+
+       mutex_lock(&sensor->mutex);
+
+       for (i = 0; i < ssd->npads; i++) {
+               struct v4l2_mbus_framefmt *try_fmt =
+                       v4l2_subdev_get_try_format(sd, fh->pad, i);
+               struct v4l2_rect *try_crop =
+                       v4l2_subdev_get_try_crop(sd, fh->pad, i);
+               struct v4l2_rect *try_comp;
+
+               ccs_get_native_size(ssd, try_crop);
+
+               try_fmt->width = try_crop->width;
+               try_fmt->height = try_crop->height;
+               try_fmt->code = sensor->internal_csi_format->code;
+               try_fmt->field = V4L2_FIELD_NONE;
+
+               if (ssd != sensor->pixel_array)
+                       continue;
+
+               try_comp = v4l2_subdev_get_try_compose(sd, fh->pad, i);
+               *try_comp = *try_crop;
+       }
+
+       mutex_unlock(&sensor->mutex);
+
+       return 0;
+}
+
+static const struct v4l2_subdev_video_ops ccs_video_ops = {
+       .s_stream = ccs_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ccs_pad_ops = {
+       .enum_mbus_code = ccs_enum_mbus_code,
+       .get_fmt = ccs_get_format,
+       .set_fmt = ccs_set_format,
+       .get_selection = ccs_get_selection,
+       .set_selection = ccs_set_selection,
+};
+
+static const struct v4l2_subdev_sensor_ops ccs_sensor_ops = {
+       .g_skip_frames = ccs_get_skip_frames,
+       .g_skip_top_lines = ccs_get_skip_top_lines,
+};
+
+static const struct v4l2_subdev_ops ccs_ops = {
+       .video = &ccs_video_ops,
+       .pad = &ccs_pad_ops,
+       .sensor = &ccs_sensor_ops,
+};
+
+static const struct media_entity_operations ccs_entity_ops = {
+       .link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct v4l2_subdev_internal_ops ccs_internal_src_ops = {
+       .registered = ccs_registered,
+       .unregistered = ccs_unregistered,
+       .open = ccs_open,
+};
+
+static const struct v4l2_subdev_internal_ops ccs_internal_ops = {
+       .open = ccs_open,
+};
+
+/* -----------------------------------------------------------------------------
+ * I2C Driver
+ */
+
+static int __maybe_unused ccs_suspend(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       bool streaming = sensor->streaming;
+       int rval;
+
+       rval = pm_runtime_get_sync(dev);
+       if (rval < 0) {
+               if (rval != -EBUSY && rval != -EAGAIN)
+                       pm_runtime_set_active(&client->dev);
+               pm_runtime_put(dev);
+               return -EAGAIN;
+       }
+
+       if (sensor->streaming)
+               ccs_stop_streaming(sensor);
+
+       /* save state for resume */
+       sensor->streaming = streaming;
+
+       return 0;
+}
+
+static int __maybe_unused ccs_resume(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       int rval = 0;
+
+       pm_runtime_put(dev);
+
+       if (sensor->streaming)
+               rval = ccs_start_streaming(sensor);
+
+       return rval;
+}
+
+static struct ccs_hwconfig *ccs_get_hwconfig(struct device *dev)
+{
+       struct ccs_hwconfig *hwcfg;
+       struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 };
+       struct fwnode_handle *ep;
+       struct fwnode_handle *fwnode = dev_fwnode(dev);
+       u32 rotation;
+       int i;
+       int rval;
+
+       if (!fwnode)
+               return dev->platform_data;
+
+       ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+       if (!ep)
+               return NULL;
+
+       bus_cfg.bus_type = V4L2_MBUS_CSI2_DPHY;
+       rval = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+       if (rval == -ENXIO) {
+               bus_cfg = (struct v4l2_fwnode_endpoint)
+                       { .bus_type = V4L2_MBUS_CCP2 };
+               rval = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+       }
+       if (rval)
+               goto out_err;
+
+       hwcfg = devm_kzalloc(dev, sizeof(*hwcfg), GFP_KERNEL);
+       if (!hwcfg)
+               goto out_err;
+
+       switch (bus_cfg.bus_type) {
+       case V4L2_MBUS_CSI2_DPHY:
+               hwcfg->csi_signalling_mode = CCS_CSI_SIGNALING_MODE_CSI_2_DPHY;
+               hwcfg->lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
+               break;
+       case V4L2_MBUS_CCP2:
+               hwcfg->csi_signalling_mode = (bus_cfg.bus.mipi_csi1.strobe) ?
+               SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE :
+               SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK;
+               hwcfg->lanes = 1;
+               break;
+       default:
+               dev_err(dev, "unsupported bus %u\n", bus_cfg.bus_type);
+               goto out_err;
+       }
+
+       dev_dbg(dev, "lanes %u\n", hwcfg->lanes);
+
+       rval = fwnode_property_read_u32(fwnode, "rotation", &rotation);
+       if (!rval) {
+               switch (rotation) {
+               case 180:
+                       hwcfg->module_board_orient =
+                               CCS_MODULE_BOARD_ORIENT_180;
+                       fallthrough;
+               case 0:
+                       break;
+               default:
+                       dev_err(dev, "invalid rotation %u\n", rotation);
+                       goto out_err;
+               }
+       }
+
+       rval = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
+                                       &hwcfg->ext_clk);
+       if (rval)
+               dev_info(dev, "can't get clock-frequency\n");
+
+       dev_dbg(dev, "clk %d, mode %d\n", hwcfg->ext_clk,
+               hwcfg->csi_signalling_mode);
+
+       if (!bus_cfg.nr_of_link_frequencies) {
+               dev_warn(dev, "no link frequencies defined\n");
+               goto out_err;
+       }
+
+       hwcfg->op_sys_clock = devm_kcalloc(
+               dev, bus_cfg.nr_of_link_frequencies + 1 /* guardian */,
+               sizeof(*hwcfg->op_sys_clock), GFP_KERNEL);
+       if (!hwcfg->op_sys_clock)
+               goto out_err;
+
+       for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) {
+               hwcfg->op_sys_clock[i] = bus_cfg.link_frequencies[i];
+               dev_dbg(dev, "freq %d: %lld\n", i, hwcfg->op_sys_clock[i]);
+       }
+
+       v4l2_fwnode_endpoint_free(&bus_cfg);
+       fwnode_handle_put(ep);
+       return hwcfg;
+
+out_err:
+       v4l2_fwnode_endpoint_free(&bus_cfg);
+       fwnode_handle_put(ep);
+       return NULL;
+}
+
+static int ccs_probe(struct i2c_client *client)
+{
+       struct ccs_sensor *sensor;
+       struct ccs_hwconfig *hwcfg = ccs_get_hwconfig(&client->dev);
+       unsigned int i;
+       int rval;
+
+       if (hwcfg == NULL)
+               return -ENODEV;
+
+       sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
+       if (sensor == NULL)
+               return -ENOMEM;
+
+       sensor->hwcfg = hwcfg;
+       sensor->src = &sensor->ssds[sensor->ssds_used];
+
+       v4l2_i2c_subdev_init(&sensor->src->sd, client, &ccs_ops);
+       sensor->src->sd.internal_ops = &ccs_internal_src_ops;
+
+       sensor->vana = devm_regulator_get(&client->dev, "vana");
+       if (IS_ERR(sensor->vana)) {
+               dev_err(&client->dev, "could not get regulator for vana\n");
+               return PTR_ERR(sensor->vana);
+       }
+
+       sensor->ext_clk = devm_clk_get(&client->dev, NULL);
+       if (PTR_ERR(sensor->ext_clk) == -ENOENT) {
+               dev_info(&client->dev, "no clock defined, continuing...\n");
+               sensor->ext_clk = NULL;
+       } else if (IS_ERR(sensor->ext_clk)) {
+               dev_err(&client->dev, "could not get clock (%ld)\n",
+                       PTR_ERR(sensor->ext_clk));
+               return -EPROBE_DEFER;
+       }
+
+       if (sensor->ext_clk) {
+               if (sensor->hwcfg->ext_clk) {
+                       unsigned long rate;
+
+                       rval = clk_set_rate(sensor->ext_clk,
+                                           sensor->hwcfg->ext_clk);
+                       if (rval < 0) {
+                               dev_err(&client->dev,
+                                       "unable to set clock freq to %u\n",
+                                       sensor->hwcfg->ext_clk);
+                               return rval;
+                       }
+
+                       rate = clk_get_rate(sensor->ext_clk);
+                       if (rate != sensor->hwcfg->ext_clk) {
+                               dev_err(&client->dev,
+                                       "can't set clock freq, asked for %u but got %lu\n",
+                                       sensor->hwcfg->ext_clk, rate);
+                               return rval;
+                       }
+               } else {
+                       sensor->hwcfg->ext_clk = clk_get_rate(sensor->ext_clk);
+                       dev_dbg(&client->dev, "obtained clock freq %u\n",
+                               sensor->hwcfg->ext_clk);
+               }
+       } else if (sensor->hwcfg->ext_clk) {
+               dev_dbg(&client->dev, "assuming clock freq %u\n",
+                       sensor->hwcfg->ext_clk);
+       } else {
+               dev_err(&client->dev, "unable to obtain clock freq\n");
+               return -EINVAL;
+       }
+
+       sensor->xshutdown = devm_gpiod_get_optional(&client->dev, "xshutdown",
+                                                   GPIOD_OUT_LOW);
+       if (IS_ERR(sensor->xshutdown))
+               return PTR_ERR(sensor->xshutdown);
+
+       rval = ccs_power_on(&client->dev);
+       if (rval < 0)
+               return rval;
+
+       mutex_init(&sensor->mutex);
+
+       rval = ccs_identify_module(sensor);
+       if (rval) {
+               rval = -ENODEV;
+               goto out_power_off;
+       }
+
+       rval = ccs_read_all_limits(sensor);
+       if (rval)
+               goto out_power_off;
+
+       rval = ccs_read_frame_fmt(sensor);
+       if (rval) {
+               rval = -ENODEV;
+               goto out_free_ccs_limits;
+       }
+
+       /*
+        * Handle Sensor Module orientation on the board.
+        *
+        * The application of H-FLIP and V-FLIP on the sensor is modified by
+        * the sensor orientation on the board.
+        *
+        * For CCS_BOARD_SENSOR_ORIENT_180 the default behaviour is to set
+        * both H-FLIP and V-FLIP for normal operation which also implies
+        * that a set/unset operation for user space HFLIP and VFLIP v4l2
+        * controls will need to be internally inverted.
+        *
+        * Rotation also changes the bayer pattern.
+        */
+       if (sensor->hwcfg->module_board_orient ==
+           CCS_MODULE_BOARD_ORIENT_180)
+               sensor->hvflip_inv_mask =
+                       CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR |
+                       CCS_IMAGE_ORIENTATION_VERTICAL_FLIP;
+
+       rval = ccs_call_quirk(sensor, limits);
+       if (rval) {
+               dev_err(&client->dev, "limits quirks failed\n");
+               goto out_free_ccs_limits;
+       }
+
+       if (CCS_LIM(sensor, BINNING_CAPABILITY)) {
+               sensor->nbinning_subtypes =
+                       min_t(u8, CCS_LIM(sensor, BINNING_SUB_TYPES),
+                             CCS_LIM_BINNING_SUB_TYPE_MAX_N);
+
+               for (i = 0; i < sensor->nbinning_subtypes; i++) {
+                       sensor->binning_subtypes[i].horizontal =
+                               CCS_LIM_AT(sensor, BINNING_SUB_TYPE, i) >>
+                               CCS_BINNING_SUB_TYPE_COLUMN_SHIFT;
+                       sensor->binning_subtypes[i].vertical =
+                               CCS_LIM_AT(sensor, BINNING_SUB_TYPE, i) &
+                               CCS_BINNING_SUB_TYPE_ROW_MASK;
+
+                       dev_dbg(&client->dev, "binning %xx%x\n",
+                               sensor->binning_subtypes[i].horizontal,
+                               sensor->binning_subtypes[i].vertical);
+               }
+       }
+       sensor->binning_horizontal = 1;
+       sensor->binning_vertical = 1;
+
+       if (device_create_file(&client->dev, &dev_attr_ident) != 0) {
+               dev_err(&client->dev, "sysfs ident entry creation failed\n");
+               rval = -ENOENT;
+               goto out_free_ccs_limits;
+       }
+
+       if (sensor->minfo.smiapp_version &&
+           CCS_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) &
+           CCS_DATA_TRANSFER_IF_CAPABILITY_SUPPORTED) {
+               if (device_create_file(&client->dev, &dev_attr_nvm) != 0) {
+                       dev_err(&client->dev, "sysfs nvm entry failed\n");
+                       rval = -EBUSY;
+                       goto out_cleanup;
+               }
+       }
+
+       /* We consider this as profile 0 sensor if any of these are zero. */
+       if (!CCS_LIM(sensor, MIN_OP_SYS_CLK_DIV) ||
+           !CCS_LIM(sensor, MAX_OP_SYS_CLK_DIV) ||
+           !CCS_LIM(sensor, MIN_OP_PIX_CLK_DIV) ||
+           !CCS_LIM(sensor, MAX_OP_PIX_CLK_DIV)) {
+               sensor->minfo.smiapp_profile = SMIAPP_PROFILE_0;
+       } else if (CCS_LIM(sensor, SCALING_CAPABILITY)
+                  != CCS_SCALING_CAPABILITY_NONE) {
+               if (CCS_LIM(sensor, SCALING_CAPABILITY)
+                   == CCS_SCALING_CAPABILITY_HORIZONTAL)
+                       sensor->minfo.smiapp_profile = SMIAPP_PROFILE_1;
+               else
+                       sensor->minfo.smiapp_profile = SMIAPP_PROFILE_2;
+               sensor->scaler = &sensor->ssds[sensor->ssds_used];
+               sensor->ssds_used++;
+       } else if (CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY)
+                  == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) {
+               sensor->scaler = &sensor->ssds[sensor->ssds_used];
+               sensor->ssds_used++;
+       }
+       sensor->binner = &sensor->ssds[sensor->ssds_used];
+       sensor->ssds_used++;
+       sensor->pixel_array = &sensor->ssds[sensor->ssds_used];
+       sensor->ssds_used++;
+
+       sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN);
+
+       /* prepare PLL configuration input values */
+       sensor->pll.bus_type = SMIAPP_PLL_BUS_TYPE_CSI2;
+       sensor->pll.csi2.lanes = sensor->hwcfg->lanes;
+       sensor->pll.ext_clk_freq_hz = sensor->hwcfg->ext_clk;
+       sensor->pll.scale_n = CCS_LIM(sensor, SCALER_N_MIN);
+       /* Profile 0 sensors have no separate OP clock branch. */
+       if (sensor->minfo.smiapp_profile == SMIAPP_PROFILE_0)
+               sensor->pll.flags |= SMIAPP_PLL_FLAG_NO_OP_CLOCKS;
+
+       ccs_create_subdev(sensor, sensor->scaler, " scaler", 2);
+       ccs_create_subdev(sensor, sensor->binner, " binner", 2);
+       ccs_create_subdev(sensor, sensor->pixel_array, " pixel_array", 1);
+
+       dev_dbg(&client->dev, "profile %d\n", sensor->minfo.smiapp_profile);
+
+       sensor->pixel_array->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+       rval = ccs_init_controls(sensor);
+       if (rval < 0)
+               goto out_cleanup;
+
+       rval = ccs_call_quirk(sensor, init);
+       if (rval)
+               goto out_cleanup;
+
+       rval = ccs_get_mbus_formats(sensor);
+       if (rval) {
+               rval = -ENODEV;
+               goto out_cleanup;
+       }
+
+       rval = ccs_init_late_controls(sensor);
+       if (rval) {
+               rval = -ENODEV;
+               goto out_cleanup;
+       }
+
+       mutex_lock(&sensor->mutex);
+       rval = ccs_pll_blanking_update(sensor);
+       mutex_unlock(&sensor->mutex);
+       if (rval) {
+               dev_err(&client->dev, "update mode failed\n");
+               goto out_cleanup;
+       }
+
+       sensor->streaming = false;
+       sensor->dev_init_done = true;
+
+       rval = media_entity_pads_init(&sensor->src->sd.entity, 2,
+                                sensor->src->pads);
+       if (rval < 0)
+               goto out_media_entity_cleanup;
+
+       pm_runtime_set_active(&client->dev);
+       pm_runtime_get_noresume(&client->dev);
+       pm_runtime_enable(&client->dev);
+
+       rval = v4l2_async_register_subdev_sensor_common(&sensor->src->sd);
+       if (rval < 0)
+               goto out_disable_runtime_pm;
+
+       pm_runtime_set_autosuspend_delay(&client->dev, 1000);
+       pm_runtime_use_autosuspend(&client->dev);
+       pm_runtime_put_autosuspend(&client->dev);
+
+       return 0;
+
+out_disable_runtime_pm:
+       pm_runtime_put_noidle(&client->dev);
+       pm_runtime_disable(&client->dev);
+
+out_media_entity_cleanup:
+       media_entity_cleanup(&sensor->src->sd.entity);
+
+out_cleanup:
+       ccs_cleanup(sensor);
+
+out_free_ccs_limits:
+       kfree(sensor->ccs_limits);
+
+out_power_off:
+       ccs_power_off(&client->dev);
+       mutex_destroy(&sensor->mutex);
+
+       return rval;
+}
+
+static int ccs_remove(struct i2c_client *client)
+{
+       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+       struct ccs_sensor *sensor = to_ccs_sensor(subdev);
+       unsigned int i;
+
+       v4l2_async_unregister_subdev(subdev);
+
+       pm_runtime_disable(&client->dev);
+       if (!pm_runtime_status_suspended(&client->dev))
+               ccs_power_off(&client->dev);
+       pm_runtime_set_suspended(&client->dev);
+
+       for (i = 0; i < sensor->ssds_used; i++) {
+               v4l2_device_unregister_subdev(&sensor->ssds[i].sd);
+               media_entity_cleanup(&sensor->ssds[i].sd.entity);
+       }
+       ccs_cleanup(sensor);
+       mutex_destroy(&sensor->mutex);
+       kfree(sensor->ccs_limits);
+
+       return 0;
+}
+
+static const struct of_device_id ccs_of_table[] = {
+       { .compatible = "nokia,smia" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, ccs_of_table);
+
+static const struct i2c_device_id ccs_id_table[] = {
+       { SMIAPP_NAME, 0 },
+       { },
+};
+MODULE_DEVICE_TABLE(i2c, ccs_id_table);
+
+static const struct dev_pm_ops ccs_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(ccs_suspend, ccs_resume)
+       SET_RUNTIME_PM_OPS(ccs_power_off, ccs_power_on, NULL)
+};
+
+static struct i2c_driver ccs_i2c_driver = {
+       .driver = {
+               .of_match_table = ccs_of_table,
+               .name = CCS_NAME,
+               .pm = &ccs_pm_ops,
+       },
+       .probe_new = ccs_probe,
+       .remove = ccs_remove,
+       .id_table = ccs_id_table,
+};
+
+static int ccs_module_init(void)
+{
+       unsigned int i, l;
+
+       for (i = 0, l = 0; ccs_limits[i].size && l < CCS_L_LAST; i++) {
+               if (!(ccs_limits[i].flags & CCS_L_FL_SAME_REG)) {
+                       ccs_limit_offsets[l + 1].lim =
+                               ALIGN(ccs_limit_offsets[l].lim +
+                                     ccs_limits[i].size,
+                                     ccs_reg_width(ccs_limits[i + 1].reg));
+                       ccs_limit_offsets[l].info = i;
+                       l++;
+               } else {
+                       ccs_limit_offsets[l].lim += ccs_limits[i].size;
+               }
+       }
+
+       if (WARN_ON(ccs_limits[i].size))
+               return -EINVAL;
+
+       if (WARN_ON(l != CCS_L_LAST))
+               return -EINVAL;
+
+       return i2c_register_driver(THIS_MODULE, &ccs_i2c_driver);
+}
+
+static void ccs_module_cleanup(void)
+{
+       i2c_del_driver(&ccs_i2c_driver);
+}
+
+module_init(ccs_module_init);
+module_exit(ccs_module_cleanup);
+
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");
+MODULE_DESCRIPTION("Generic MIPI CCS/SMIA/SMIA++ camera sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/smiapp/ccs-quirk.c b/drivers/media/i2c/smiapp/ccs-quirk.c
new file mode 100644 (file)
index 0000000..6c48d09
--- /dev/null
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/media/i2c/smiapp/ccs-quirk.c
+ *
+ * Generic driver for SMIA/SMIA++ compliant camera modules
+ *
+ * Copyright (C) 2011--2012 Nokia Corporation
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ */
+
+#include <linux/delay.h>
+
+#include "ccs.h"
+#include "ccs-limits.h"
+
+static int ccs_write_addr_8s(struct ccs_sensor *sensor,
+                            const struct ccs_reg_8 *regs, int len)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int rval;
+
+       for (; len > 0; len--, regs++) {
+               rval = ccs_write_addr(sensor, regs->reg, regs->val);
+               if (rval < 0) {
+                       dev_err(&client->dev,
+                               "error %d writing reg 0x%4.4x, val 0x%2.2x",
+                               rval, regs->reg, regs->val);
+                       return rval;
+               }
+       }
+
+       return 0;
+}
+
+static int jt8ew9_limits(struct ccs_sensor *sensor)
+{
+       if (sensor->minfo.revision_number_major < 0x03)
+               sensor->frame_skip = 1;
+
+       /* Below 24 gain doesn't have effect at all, */
+       /* but ~59 is needed for full dynamic range */
+       ccs_replace_limit(sensor, CCS_L_ANALOG_GAIN_CODE_MIN, 0, 59);
+       ccs_replace_limit(sensor, CCS_L_ANALOG_GAIN_CODE_MAX, 0, 6000);
+
+       return 0;
+}
+
+static int jt8ew9_post_poweron(struct ccs_sensor *sensor)
+{
+       static const struct ccs_reg_8 regs[] = {
+               { 0x30a3, 0xd8 }, /* Output port control : LVDS ports only */
+               { 0x30ae, 0x00 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */
+               { 0x30af, 0xd0 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */
+               { 0x322d, 0x04 }, /* Adjusting Processing Image Size to Scaler Toshiba Recommendation Setting */
+               { 0x3255, 0x0f }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */
+               { 0x3256, 0x15 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */
+               { 0x3258, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */
+               { 0x3259, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */
+               { 0x325f, 0x7c }, /* Analog Gain Control Toshiba Recommendation Setting */
+               { 0x3302, 0x06 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */
+               { 0x3304, 0x00 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */
+               { 0x3307, 0x22 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */
+               { 0x3308, 0x8d }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */
+               { 0x331e, 0x0f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
+               { 0x3320, 0x30 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
+               { 0x3321, 0x11 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
+               { 0x3322, 0x98 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
+               { 0x3323, 0x64 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
+               { 0x3325, 0x83 }, /* Read Out Timing Control Toshiba Recommendation Setting */
+               { 0x3330, 0x18 }, /* Read Out Timing Control Toshiba Recommendation Setting */
+               { 0x333c, 0x01 }, /* Read Out Timing Control Toshiba Recommendation Setting */
+               { 0x3345, 0x2f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
+               { 0x33de, 0x38 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */
+               /* Taken from v03. No idea what the rest are. */
+               { 0x32e0, 0x05 },
+               { 0x32e1, 0x05 },
+               { 0x32e2, 0x04 },
+               { 0x32e5, 0x04 },
+               { 0x32e6, 0x04 },
+
+       };
+
+       return ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs));
+}
+
+const struct ccs_quirk smiapp_jt8ew9_quirk = {
+       .limits = jt8ew9_limits,
+       .post_poweron = jt8ew9_post_poweron,
+};
+
+static int imx125es_post_poweron(struct ccs_sensor *sensor)
+{
+       /* Taken from v02. No idea what the other two are. */
+       static const struct ccs_reg_8 regs[] = {
+               /*
+                * 0x3302: clk during frame blanking:
+                * 0x00 - HS mode, 0x01 - LP11
+                */
+               { 0x3302, 0x01 },
+               { 0x302d, 0x00 },
+               { 0x3b08, 0x8c },
+       };
+
+       return ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs));
+}
+
+const struct ccs_quirk smiapp_imx125es_quirk = {
+       .post_poweron = imx125es_post_poweron,
+};
+
+static int jt8ev1_limits(struct ccs_sensor *sensor)
+{
+       ccs_replace_limit(sensor, CCS_L_X_ADDR_MAX, 0, 4271);
+       ccs_replace_limit(sensor, CCS_L_MIN_LINE_BLANKING_PCK_BIN, 0, 184);
+
+       return 0;
+}
+
+static int jt8ev1_post_poweron(struct ccs_sensor *sensor)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       int rval;
+       static const struct ccs_reg_8 regs[] = {
+               { 0x3031, 0xcd }, /* For digital binning (EQ_MONI) */
+               { 0x30a3, 0xd0 }, /* FLASH STROBE enable */
+               { 0x3237, 0x00 }, /* For control of pulse timing for ADC */
+               { 0x3238, 0x43 },
+               { 0x3301, 0x06 }, /* For analog bias for sensor */
+               { 0x3302, 0x06 },
+               { 0x3304, 0x00 },
+               { 0x3305, 0x88 },
+               { 0x332a, 0x14 },
+               { 0x332c, 0x6b },
+               { 0x3336, 0x01 },
+               { 0x333f, 0x1f },
+               { 0x3355, 0x00 },
+               { 0x3356, 0x20 },
+               { 0x33bf, 0x20 }, /* Adjust the FBC speed */
+               { 0x33c9, 0x20 },
+               { 0x33ce, 0x30 }, /* Adjust the parameter for logic function */
+               { 0x33cf, 0xec }, /* For Black sun */
+               { 0x3328, 0x80 }, /* Ugh. No idea what's this. */
+       };
+       static const struct ccs_reg_8 regs_96[] = {
+               { 0x30ae, 0x00 }, /* For control of ADC clock */
+               { 0x30af, 0xd0 },
+               { 0x30b0, 0x01 },
+       };
+
+       rval = ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs));
+       if (rval < 0)
+               return rval;
+
+       switch (sensor->hwcfg->ext_clk) {
+       case 9600000:
+               return ccs_write_addr_8s(sensor, regs_96,
+                                      ARRAY_SIZE(regs_96));
+       default:
+               dev_warn(&client->dev, "no MSRs for %d Hz ext_clk\n",
+                        sensor->hwcfg->ext_clk);
+               return 0;
+       }
+}
+
+static int jt8ev1_pre_streamon(struct ccs_sensor *sensor)
+{
+       return ccs_write_addr(sensor, 0x3328, 0x00);
+}
+
+static int jt8ev1_post_streamoff(struct ccs_sensor *sensor)
+{
+       int rval;
+
+       /* Workaround: allows fast standby to work properly */
+       rval = ccs_write_addr(sensor, 0x3205, 0x04);
+       if (rval < 0)
+               return rval;
+
+       /* Wait for 1 ms + one line => 2 ms is likely enough */
+       usleep_range(2000, 2050);
+
+       /* Restore it */
+       rval = ccs_write_addr(sensor, 0x3205, 0x00);
+       if (rval < 0)
+               return rval;
+
+       return ccs_write_addr(sensor, 0x3328, 0x80);
+}
+
+static int jt8ev1_init(struct ccs_sensor *sensor)
+{
+       sensor->pll.flags |= SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE;
+
+       return 0;
+}
+
+const struct ccs_quirk smiapp_jt8ev1_quirk = {
+       .limits = jt8ev1_limits,
+       .post_poweron = jt8ev1_post_poweron,
+       .pre_streamon = jt8ev1_pre_streamon,
+       .post_streamoff = jt8ev1_post_streamoff,
+       .init = jt8ev1_init,
+};
+
+static int tcm8500md_limits(struct ccs_sensor *sensor)
+{
+       ccs_replace_limit(sensor, CCS_L_MIN_PLL_IP_CLK_FREQ_MHZ, 0, 2700000);
+
+       return 0;
+}
+
+const struct ccs_quirk smiapp_tcm8500md_quirk = {
+       .limits = tcm8500md_limits,
+};
diff --git a/drivers/media/i2c/smiapp/ccs-quirk.h b/drivers/media/i2c/smiapp/ccs-quirk.h
new file mode 100644 (file)
index 0000000..d208379
--- /dev/null
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * drivers/media/i2c/smiapp/ccs-quirk.h
+ *
+ * Generic driver for SMIA/SMIA++ compliant camera modules
+ *
+ * Copyright (C) 2011--2012 Nokia Corporation
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ */
+
+#ifndef __CCS_QUIRK__
+#define __CCS_QUIRK__
+
+struct ccs_sensor;
+
+/**
+ * struct ccs_quirk - quirks for sensors that deviate from SMIA++ standard
+ *
+ * @limits: Replace sensor->limits with values which can't be read from
+ *         sensor registers. Called the first time the sensor is powered up.
+ * @post_poweron: Called always after the sensor has been fully powered on.
+ * @pre_streamon: Called just before streaming is enabled.
+ * @post_streamon: Called right after stopping streaming.
+ * @pll_flags: Return flags for the PLL calculator.
+ * @init: Quirk initialisation, called the last in probe(). This is
+ *       also appropriate for adding sensor specific controls, for instance.
+ * @reg_access: Register access quirk. The quirk may divert the access
+ *             to another register, or no register at all.
+ *
+ *             @write: Is this read (false) or write (true) access?
+ *             @reg: Pointer to the register to access
+ *             @value: Register value, set by the caller on write, or
+ *                     by the quirk on read
+ *
+ *             @return: 0 on success, -ENOIOCTLCMD if no register
+ *                      access may be done by the caller (default read
+ *                      value is zero), else negative error code on error
+ */
+struct ccs_quirk {
+       int (*limits)(struct ccs_sensor *sensor);
+       int (*post_poweron)(struct ccs_sensor *sensor);
+       int (*pre_streamon)(struct ccs_sensor *sensor);
+       int (*post_streamoff)(struct ccs_sensor *sensor);
+       unsigned long (*pll_flags)(struct ccs_sensor *sensor);
+       int (*init)(struct ccs_sensor *sensor);
+       int (*reg_access)(struct ccs_sensor *sensor, bool write, u32 *reg,
+                         u32 *val);
+       unsigned long flags;
+};
+
+#define CCS_QUIRK_FLAG_8BIT_READ_ONLY                  (1 << 0)
+
+struct ccs_reg_8 {
+       u16 reg;
+       u8 val;
+};
+
+#define CCS_MK_QUIRK_REG_8(_reg, _val) \
+       {                               \
+               .reg = (u16)_reg,       \
+               .val = _val,            \
+       }
+
+#define ccs_call_quirk(sensor, _quirk, ...)                            \
+       ((sensor)->minfo.quirk &&                                       \
+        (sensor)->minfo.quirk->_quirk ?                                \
+        (sensor)->minfo.quirk->_quirk(sensor, ##__VA_ARGS__) : 0)
+
+#define ccs_needs_quirk(sensor, _quirk)                \
+       ((sensor)->minfo.quirk ?                        \
+        (sensor)->minfo.quirk->flags & _quirk : 0)
+
+extern const struct ccs_quirk smiapp_jt8ev1_quirk;
+extern const struct ccs_quirk smiapp_imx125es_quirk;
+extern const struct ccs_quirk smiapp_jt8ew9_quirk;
+extern const struct ccs_quirk smiapp_tcm8500md_quirk;
+
+#endif /* __CCS_QUIRK__ */
diff --git a/drivers/media/i2c/smiapp/ccs-reg-access.c b/drivers/media/i2c/smiapp/ccs-reg-access.c
new file mode 100644 (file)
index 0000000..4e6d212
--- /dev/null
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/media/i2c/smiapp/ccs-regs.c
+ *
+ * Generic driver for SMIA/SMIA++ compliant camera modules
+ *
+ * Copyright (C) 2011--2012 Nokia Corporation
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include "ccs.h"
+
+static uint32_t float_to_u32_mul_1000000(struct i2c_client *client,
+                                        uint32_t phloat)
+{
+       int32_t exp;
+       uint64_t man;
+
+       if (phloat >= 0x80000000) {
+               dev_err(&client->dev, "this is a negative number\n");
+               return 0;
+       }
+
+       if (phloat == 0x7f800000)
+               return ~0; /* Inf. */
+
+       if ((phloat & 0x7f800000) == 0x7f800000) {
+               dev_err(&client->dev, "NaN or other special number\n");
+               return 0;
+       }
+
+       /* Valid cases begin here */
+       if (phloat == 0)
+               return 0; /* Valid zero */
+
+       if (phloat > 0x4f800000)
+               return ~0; /* larger than 4294967295 */
+
+       /*
+        * Unbias exponent (note how phloat is now guaranteed to
+        * have 0 in the high bit)
+        */
+       exp = ((int32_t)phloat >> 23) - 127;
+
+       /* Extract mantissa, add missing '1' bit and it's in MHz */
+       man = ((phloat & 0x7fffff) | 0x800000) * 1000000ULL;
+
+       if (exp < 0)
+               man >>= -exp;
+       else
+               man <<= exp;
+
+       man >>= 23; /* Remove mantissa bias */
+
+       return man & 0xffffffff;
+}
+
+
+/*
+ * Read a 8/16/32-bit i2c register.  The value is returned in 'val'.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int ____ccs_read_addr(struct ccs_sensor *sensor, u16 reg, u16 len,
+                            u32 *val)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       struct i2c_msg msg;
+       unsigned char data_buf[sizeof(u32)] = { 0 };
+       unsigned char offset_buf[sizeof(u16)];
+       int r;
+
+       if (len > sizeof(data_buf))
+               return -EINVAL;
+
+       msg.addr = client->addr;
+       msg.flags = 0;
+       msg.len = sizeof(offset_buf);
+       msg.buf = offset_buf;
+       put_unaligned_be16(reg, offset_buf);
+
+       r = i2c_transfer(client->adapter, &msg, 1);
+       if (r != 1) {
+               if (r >= 0)
+                       r = -EBUSY;
+               goto err;
+       }
+
+       msg.len = len;
+       msg.flags = I2C_M_RD;
+       msg.buf = &data_buf[sizeof(data_buf) - len];
+
+       r = i2c_transfer(client->adapter, &msg, 1);
+       if (r != 1) {
+               if (r >= 0)
+                       r = -EBUSY;
+               goto err;
+       }
+
+       *val = get_unaligned_be32(data_buf);
+
+       return 0;
+
+err:
+       dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r);
+
+       return r;
+}
+
+/* Read a register using 8-bit access only. */
+static int ____ccs_read_addr_8only(struct ccs_sensor *sensor, u16 reg,
+                                  u16 len, u32 *val)
+{
+       unsigned int i;
+       int rval;
+
+       *val = 0;
+
+       for (i = 0; i < len; i++) {
+               u32 val8;
+
+               rval = ____ccs_read_addr(sensor, reg + i, 1, &val8);
+               if (rval < 0)
+                       return rval;
+               *val |= val8 << ((len - i - 1) << 3);
+       }
+
+       return 0;
+}
+
+unsigned int ccs_reg_width(u32 reg)
+{
+       if (reg & CCS_FL_16BIT)
+               return sizeof(uint16_t);
+       if (reg & CCS_FL_32BIT)
+               return sizeof(uint32_t);
+
+       return sizeof(uint8_t);
+}
+
+/*
+ * Read a 8/16/32-bit i2c register.  The value is returned in 'val'.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int __ccs_read_addr(struct ccs_sensor *sensor, u32 reg, u32 *val,
+                          bool only8)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       unsigned int len = ccs_reg_width(reg);
+       int rval;
+
+       if (!only8)
+               rval = ____ccs_read_addr(sensor, CCS_REG_ADDR(reg), len, val);
+       else
+               rval = ____ccs_read_addr_8only(sensor, CCS_REG_ADDR(reg), len,
+                                              val);
+       if (rval < 0)
+               return rval;
+
+       if (reg & CCS_FL_FLOAT_IREAL)
+               *val = float_to_u32_mul_1000000(client, *val);
+
+       return 0;
+}
+
+int ccs_read_addr_no_quirk(struct ccs_sensor *sensor, u32 reg, u32 *val)
+{
+       return __ccs_read_addr(
+               sensor, reg, val,
+               ccs_needs_quirk(sensor, CCS_QUIRK_FLAG_8BIT_READ_ONLY));
+}
+
+static int ccs_read_addr_quirk(struct ccs_sensor *sensor, u32 reg, u32 *val,
+                              bool force8)
+{
+       int rval;
+
+       *val = 0;
+       rval = ccs_call_quirk(sensor, reg_access, false, &reg, val);
+       if (rval == -ENOIOCTLCMD)
+               return 0;
+       if (rval < 0)
+               return rval;
+
+       if (force8)
+               return __ccs_read_addr(sensor, reg, val, true);
+
+       return ccs_read_addr_no_quirk(sensor, reg, val);
+}
+
+int ccs_read_addr(struct ccs_sensor *sensor, u32 reg, u32 *val)
+{
+       return ccs_read_addr_quirk(sensor, reg, val, false);
+}
+
+int ccs_read_addr_8only(struct ccs_sensor *sensor, u32 reg, u32 *val)
+{
+       return ccs_read_addr_quirk(sensor, reg, val, true);
+}
+
+int ccs_write_addr_no_quirk(struct ccs_sensor *sensor, u32 reg, u32 val)
+{
+       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
+       struct i2c_msg msg;
+       unsigned char data[6];
+       unsigned int retries;
+       unsigned int len = ccs_reg_width(reg);
+       int r;
+
+       if (len > sizeof(data) - 2)
+               return -EINVAL;
+
+       msg.addr = client->addr;
+       msg.flags = 0; /* Write */
+       msg.len = 2 + len;
+       msg.buf = data;
+
+       put_unaligned_be16(CCS_REG_ADDR(reg), data);
+       put_unaligned_be32(val << (8 * (sizeof(val) - len)), data + 2);
+
+       for (retries = 0; retries < 5; retries++) {
+               /*
+                * Due to unknown reason sensor stops responding. This
+                * loop is a temporaty solution until the root cause
+                * is found.
+                */
+               r = i2c_transfer(client->adapter, &msg, 1);
+               if (r == 1) {
+                       if (retries)
+                               dev_err(&client->dev,
+                                       "sensor i2c stall encountered. retries: %d\n",
+                                       retries);
+                       return 0;
+               }
+
+               usleep_range(2000, 2000);
+       }
+
+       dev_err(&client->dev,
+               "wrote 0x%x to offset 0x%x error %d\n", val,
+               CCS_REG_ADDR(reg), r);
+
+       return r;
+}
+
+/*
+ * Write to a 8/16-bit register.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+int ccs_write_addr(struct ccs_sensor *sensor, u32 reg, u32 val)
+{
+       int rval;
+
+       rval = ccs_call_quirk(sensor, reg_access, true, &reg, &val);
+       if (rval == -ENOIOCTLCMD)
+               return 0;
+       if (rval < 0)
+               return rval;
+
+       return ccs_write_addr_no_quirk(sensor, reg, val);
+}
diff --git a/drivers/media/i2c/smiapp/ccs-reg-access.h b/drivers/media/i2c/smiapp/ccs-reg-access.h
new file mode 100644 (file)
index 0000000..76ac036
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * include/media/smiapp/ccs-regs.h
+ *
+ * Generic driver for SMIA/SMIA++ compliant camera modules
+ *
+ * Copyright (C) 2011--2012 Nokia Corporation
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ */
+
+#ifndef SMIAPP_REGS_H
+#define SMIAPP_REGS_H
+
+#include <linux/i2c.h>
+#include <linux/types.h>
+
+#include "ccs-regs.h"
+
+#define CCS_REG_ADDR(reg)              ((u16)reg)
+
+struct ccs_sensor;
+
+int ccs_read_addr_no_quirk(struct ccs_sensor *sensor, u32 reg, u32 *val);
+int ccs_read_addr(struct ccs_sensor *sensor, u32 reg, u32 *val);
+int ccs_read_addr_8only(struct ccs_sensor *sensor, u32 reg, u32 *val);
+int ccs_write_addr_no_quirk(struct ccs_sensor *sensor, u32 reg, u32 val);
+int ccs_write_addr(struct ccs_sensor *sensor, u32 reg, u32 val);
+
+unsigned int ccs_reg_width(u32 reg);
+
+#define ccs_read(sensor, reg_name, val) \
+       ccs_read_addr(sensor, CCS_R_##reg_name, val)
+
+#define ccs_write(sensor, reg_name, val) \
+       ccs_write_addr(sensor, CCS_R_##reg_name, val)
+
+#endif
diff --git a/drivers/media/i2c/smiapp/ccs.h b/drivers/media/i2c/smiapp/ccs.h
new file mode 100644 (file)
index 0000000..20b1125
--- /dev/null
@@ -0,0 +1,280 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * drivers/media/i2c/smiapp/ccs.h
+ *
+ * Generic driver for SMIA/SMIA++ compliant camera modules
+ *
+ * Copyright (C) 2010--2012 Nokia Corporation
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ */
+
+#ifndef __CCS_H__
+#define __CCS_H__
+
+#include <linux/mutex.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+
+#include "ccs-quirk.h"
+#include "ccs-regs.h"
+#include "ccs-reg-access.h"
+#include "../smiapp-pll.h"
+#include "smiapp-reg-defs.h"
+
+/*
+ * Standard SMIA++ constants
+ */
+#define SMIA_VERSION_1                 10
+#define SMIAPP_VERSION_0_8             8 /* Draft 0.8 */
+#define SMIAPP_VERSION_0_9             9 /* Draft 0.9 */
+#define SMIAPP_VERSION_1               10
+
+#define SMIAPP_PROFILE_0               0
+#define SMIAPP_PROFILE_1               1
+#define SMIAPP_PROFILE_2               2
+
+#define SMIAPP_NVM_PAGE_SIZE           64      /* bytes */
+
+#define SMIAPP_RESET_DELAY_CLOCKS      2400
+#define SMIAPP_RESET_DELAY(clk)                                \
+       (1000 + (SMIAPP_RESET_DELAY_CLOCKS * 1000       \
+                + (clk) / 1000 - 1) / ((clk) / 1000))
+
+#define CCS_COLOUR_COMPONENTS          4
+
+#define SMIAPP_NAME                    "smiapp"
+#define CCS_NAME                       "ccs"
+
+#define CCS_DFL_I2C_ADDR       (0x20 >> 1) /* Default I2C Address */
+#define CCS_ALT_I2C_ADDR       (0x6e >> 1) /* Alternate I2C Address */
+
+/*
+ * Sometimes due to board layout considerations the camera module can be
+ * mounted rotated. The typical rotation used is 180 degrees which can be
+ * corrected by giving a default H-FLIP and V-FLIP in the sensor readout.
+ * FIXME: rotation also changes the bayer pattern.
+ */
+enum ccs_module_board_orient {
+       CCS_MODULE_BOARD_ORIENT_0 = 0,
+       CCS_MODULE_BOARD_ORIENT_180,
+};
+
+struct ccs_flash_strobe_parms {
+       u8 mode;
+       u32 strobe_width_high_us;
+       u16 strobe_delay;
+       u16 stobe_start_point;
+       u8 trigger;
+};
+
+struct ccs_hwconfig {
+       /*
+        * Change the cci address if i2c_addr_alt is set.
+        * Both default and alternate cci addr need to be present
+        */
+       unsigned short i2c_addr_dfl;    /* Default i2c addr */
+       unsigned short i2c_addr_alt;    /* Alternate i2c addr */
+
+       uint32_t 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;
+
+       enum ccs_module_board_orient module_board_orient;
+
+       struct ccs_flash_strobe_parms *strobe_setup;
+};
+
+struct ccs_quirk;
+
+#define CCS_MODULE_IDENT_FLAG_REV_LE           (1 << 0)
+
+struct ccs_module_ident {
+       u16 mipi_manufacturer_id;
+       u16 model_id;
+       u8 smia_manufacturer_id;
+       u8 revision_number_major;
+
+       u8 flags;
+
+       char *name;
+       const struct ccs_quirk *quirk;
+};
+
+struct ccs_module_info {
+       u32 smia_manufacturer_id;
+       u32 mipi_manufacturer_id;
+       u32 model_id;
+       u32 revision_number_major;
+       u32 revision_number_minor;
+
+       u32 module_year;
+       u32 module_month;
+       u32 module_day;
+
+       u32 sensor_smia_manufacturer_id;
+       u32 sensor_mipi_manufacturer_id;
+       u32 sensor_model_id;
+       u32 sensor_revision_number;
+       u32 sensor_firmware_version;
+
+       u32 smia_version;
+       u32 smiapp_version;
+       u32 ccs_version;
+
+       u32 smiapp_profile;
+
+       char *name;
+       const struct ccs_quirk *quirk;
+};
+
+#define CCS_IDENT_FQ(manufacturer, model, rev, fl, _name, _quirk)      \
+       { .smia_manufacturer_id = manufacturer,                         \
+         .model_id = model,                                            \
+         .revision_number_major = rev,                                 \
+         .flags = fl,                                                  \
+         .name = _name,                                                \
+         .quirk = _quirk, }
+
+#define CCS_IDENT_LQ(manufacturer, model, rev, _name, _quirk)  \
+       { .smia_manufacturer_id = manufacturer,                         \
+         .model_id = model,                                            \
+         .revision_number_major = rev,                                 \
+         .flags = CCS_MODULE_IDENT_FLAG_REV_LE,                        \
+         .name = _name,                                                \
+         .quirk = _quirk, }
+
+#define CCS_IDENT_L(manufacturer, model, rev, _name)                   \
+       { .smia_manufacturer_id = manufacturer,                         \
+         .model_id = model,                                            \
+         .revision_number_major = rev,                                 \
+         .flags = CCS_MODULE_IDENT_FLAG_REV_LE,                        \
+         .name = _name, }
+
+#define CCS_IDENT_Q(manufacturer, model, rev, _name, _quirk)           \
+       { .smia_manufacturer_id = manufacturer,                         \
+         .model_id = model,                                            \
+         .revision_number_major = rev,                                 \
+         .flags = 0,                                                   \
+         .name = _name,                                                \
+         .quirk = _quirk, }
+
+#define CCS_IDENT(manufacturer, model, rev, _name)                     \
+       { .smia_manufacturer_id = manufacturer,                         \
+         .model_id = model,                                            \
+         .revision_number_major = rev,                                 \
+         .flags = 0,                                                   \
+         .name = _name, }
+
+struct ccs_csi_data_format {
+       u32 code;
+       u8 width;
+       u8 compressed;
+       u8 pixel_order;
+};
+
+#define CCS_SUBDEVS                    3
+
+#define CCS_PA_PAD_SRC                 0
+#define CCS_PAD_SINK                   0
+#define CCS_PAD_SRC                    1
+#define CCS_PADS                       2
+
+struct ccs_binning_subtype {
+       u8 horizontal:4;
+       u8 vertical:4;
+} __packed;
+
+struct ccs_subdev {
+       struct v4l2_subdev sd;
+       struct media_pad pads[CCS_PADS];
+       struct v4l2_rect sink_fmt;
+       struct v4l2_rect crop[CCS_PADS];
+       struct v4l2_rect compose; /* compose on sink */
+       unsigned short sink_pad;
+       unsigned short source_pad;
+       int npads;
+       struct ccs_sensor *sensor;
+       struct v4l2_ctrl_handler ctrl_handler;
+};
+
+/*
+ * struct ccs_sensor - Main device structure
+ */
+struct ccs_sensor {
+       /*
+        * "mutex" is used to serialise access to all fields here
+        * except v4l2_ctrls at the end of the struct. "mutex" is also
+        * used to serialise access to file handle specific
+        * information.
+        */
+       struct mutex mutex;
+       struct ccs_subdev ssds[CCS_SUBDEVS];
+       u32 ssds_used;
+       struct ccs_subdev *src;
+       struct ccs_subdev *binner;
+       struct ccs_subdev *scaler;
+       struct ccs_subdev *pixel_array;
+       struct ccs_hwconfig *hwcfg;
+       struct regulator *vana;
+       struct clk *ext_clk;
+       struct gpio_desc *xshutdown;
+       void *ccs_limits;
+       u8 nbinning_subtypes;
+       struct ccs_binning_subtype binning_subtypes[CCS_LIM_BINNING_SUB_TYPE_MAX_N + 1];
+       u32 mbus_frame_fmts;
+       const struct ccs_csi_data_format *csi_format;
+       const struct ccs_csi_data_format *internal_csi_format;
+       u32 default_mbus_frame_fmts;
+       int default_pixel_order;
+
+       u8 binning_horizontal;
+       u8 binning_vertical;
+
+       u8 scale_m;
+       u8 scaling_mode;
+
+       u8 hvflip_inv_mask; /* H/VFLIP inversion due to sensor orientation */
+       u8 frame_skip;
+       u16 embedded_start; /* embedded data start line */
+       u16 embedded_end;
+       u16 image_start; /* image data start line */
+       u16 visible_pixel_start; /* start pixel of the visible image */
+
+       bool streaming;
+       bool dev_init_done;
+       u8 compressed_min_bpp;
+
+       struct ccs_module_info minfo;
+
+       struct smiapp_pll pll;
+
+       /* Is a default format supported for a given BPP? */
+       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;
+       /* src controls */
+       struct v4l2_ctrl *link_freq;
+       struct v4l2_ctrl *pixel_rate_csi;
+       /* test pattern colour components */
+       struct v4l2_ctrl *test_data[CCS_COLOUR_COMPONENTS];
+};
+
+#define to_ccs_subdev(_sd)                             \
+       container_of(_sd, struct ccs_subdev, sd)
+
+#define to_ccs_sensor(_sd)     \
+       (to_ccs_subdev(_sd)->sensor)
+
+void ccs_replace_limit(struct ccs_sensor *sensor,
+                      unsigned int limit, unsigned int offset, u32 val);
+
+#endif /* __CCS_H__ */
diff --git a/drivers/media/i2c/smiapp/smiapp-core.c b/drivers/media/i2c/smiapp/smiapp-core.c
deleted file mode 100644 (file)
index b12e41a..0000000
+++ /dev/null
@@ -1,3305 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * drivers/media/i2c/smiapp/smiapp-core.c
- *
- * Generic driver for SMIA/SMIA++ compliant camera modules
- *
- * Copyright (C) 2010--2012 Nokia Corporation
- * Contact: Sakari Ailus <sakari.ailus@iki.fi>
- *
- * Based on smiapp driver by Vimarsh Zutshi
- * Based on jt8ev1.c by Vimarsh Zutshi
- * Based on smia-sensor.c by Tuukka Toivonen <tuukkat76@gmail.com>
- */
-
-#include <linux/clk.h>
-#include <linux/delay.h>
-#include <linux/device.h>
-#include <linux/gpio.h>
-#include <linux/gpio/consumer.h>
-#include <linux/module.h>
-#include <linux/pm_runtime.h>
-#include <linux/property.h>
-#include <linux/regulator/consumer.h>
-#include <linux/slab.h>
-#include <linux/smiapp.h>
-#include <linux/v4l2-mediabus.h>
-#include <media/v4l2-fwnode.h>
-#include <media/v4l2-device.h>
-
-#include "ccs-limits.h"
-#include "smiapp.h"
-
-#define SMIAPP_ALIGN_DIM(dim, flags)   \
-       ((flags) & V4L2_SEL_FLAG_GE     \
-        ? ALIGN((dim), 2)              \
-        : (dim) & ~1)
-
-static struct ccs_limit_offset {
-       u16     lim;
-       u16     info;
-} ccs_limit_offsets[CCS_L_LAST + 1];
-
-/*
- * smiapp_module_idents - supported camera modules
- */
-static const struct smiapp_module_ident smiapp_module_idents[] = {
-       SMIAPP_IDENT_L(0x01, 0x022b, -1, "vs6555"),
-       SMIAPP_IDENT_L(0x01, 0x022e, -1, "vw6558"),
-       SMIAPP_IDENT_L(0x07, 0x7698, -1, "ovm7698"),
-       SMIAPP_IDENT_L(0x0b, 0x4242, -1, "smiapp-003"),
-       SMIAPP_IDENT_L(0x0c, 0x208a, -1, "tcm8330md"),
-       SMIAPP_IDENT_LQ(0x0c, 0x2134, -1, "tcm8500md", &smiapp_tcm8500md_quirk),
-       SMIAPP_IDENT_L(0x0c, 0x213e, -1, "et8en2"),
-       SMIAPP_IDENT_L(0x0c, 0x2184, -1, "tcm8580md"),
-       SMIAPP_IDENT_LQ(0x0c, 0x560f, -1, "jt8ew9", &smiapp_jt8ew9_quirk),
-       SMIAPP_IDENT_LQ(0x10, 0x4141, -1, "jt8ev1", &smiapp_jt8ev1_quirk),
-       SMIAPP_IDENT_LQ(0x10, 0x4241, -1, "imx125es", &smiapp_imx125es_quirk),
-};
-
-/*
- *
- * Dynamic Capability Identification
- *
- */
-
-static void ccs_assign_limit(void *ptr, unsigned int width, u32 val)
-{
-       switch (width) {
-       case sizeof(u8):
-               *(u8 *)ptr = val;
-               break;
-       case sizeof(u16):
-               *(u16 *)ptr = val;
-               break;
-       case sizeof(u32):
-               *(u32 *)ptr = val;
-               break;
-       }
-}
-
-static int ccs_limit_ptr(struct smiapp_sensor *sensor, unsigned int limit,
-                        unsigned int offset, void **__ptr)
-{
-       const struct ccs_limit *linfo;
-
-       if (WARN_ON(limit >= CCS_L_LAST))
-               return -EINVAL;
-
-       linfo = &ccs_limits[ccs_limit_offsets[limit].info];
-
-       if (WARN_ON(!sensor->ccs_limits) ||
-           WARN_ON(offset + ccs_reg_width(linfo->reg) >
-                   ccs_limit_offsets[limit + 1].lim))
-               return -EINVAL;
-
-       *__ptr = sensor->ccs_limits + ccs_limit_offsets[limit].lim + offset;
-
-       return 0;
-}
-
-void ccs_replace_limit(struct smiapp_sensor *sensor,
-                      unsigned int limit, unsigned int offset, u32 val)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       const struct ccs_limit *linfo;
-       void *ptr;
-       int ret;
-
-       ret = ccs_limit_ptr(sensor, limit, offset, &ptr);
-       if (ret)
-               return;
-
-       linfo = &ccs_limits[ccs_limit_offsets[limit].info];
-
-       dev_dbg(&client->dev, "quirk: 0x%8.8x \"%s\" %u = %d, 0x%x\n",
-               linfo->reg, linfo->name, offset, val, val);
-
-       ccs_assign_limit(ptr, ccs_reg_width(linfo->reg), val);
-}
-
-static u32 ccs_get_limit(struct smiapp_sensor *sensor,
-                        unsigned int limit, unsigned int offset)
-{
-       void *ptr;
-       int ret;
-
-       ret = ccs_limit_ptr(sensor, limit, offset, &ptr);
-       if (ret)
-               return 0;
-
-       switch (ccs_reg_width(ccs_limits[ccs_limit_offsets[limit].info].reg)) {
-       case sizeof(u8):
-               return *(u8 *)ptr;
-       case sizeof(u16):
-               return *(u16 *)ptr;
-       case sizeof(u32):
-               return *(u32 *)ptr;
-       }
-
-       WARN_ON(1);
-
-       return 0;
-}
-
-#define CCS_LIM(sensor, limit) \
-       ccs_get_limit(sensor, CCS_L_##limit, 0)
-
-#define CCS_LIM_AT(sensor, limit, offset)      \
-       ccs_get_limit(sensor, CCS_L_##limit, CCS_L_##limit##_OFFSET(offset))
-
-static int ccs_read_all_limits(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       void *ptr, *alloc, *end;
-       unsigned int i, l;
-       int ret;
-
-       kfree(sensor->ccs_limits);
-       sensor->ccs_limits = NULL;
-
-       alloc = kzalloc(ccs_limit_offsets[CCS_L_LAST].lim, GFP_KERNEL);
-       if (!alloc)
-               return -ENOMEM;
-
-       end = alloc + ccs_limit_offsets[CCS_L_LAST].lim;
-
-       for (i = 0, l = 0, ptr = alloc; ccs_limits[i].size; i++) {
-               u32 reg = ccs_limits[i].reg;
-               unsigned int width = ccs_reg_width(reg);
-               unsigned int j;
-
-               if (l == CCS_L_LAST) {
-                       dev_err(&client->dev,
-                               "internal error --- end of limit array\n");
-                       ret = -EINVAL;
-                       goto out_err;
-               }
-
-               for (j = 0; j < ccs_limits[i].size / width;
-                    j++, reg += width, ptr += width) {
-                       u32 val;
-
-                       ret = ccs_read_addr(sensor, reg, &val);
-                       if (ret)
-                               goto out_err;
-
-                       if (ptr + width > end) {
-                               dev_err(&client->dev,
-                                       "internal error --- no room for regs\n");
-                               ret = -EINVAL;
-                               goto out_err;
-                       }
-
-                       ccs_assign_limit(ptr, width, val);
-
-                       dev_dbg(&client->dev, "0x%8.8x \"%s\" = %u, 0x%x\n",
-                               reg, ccs_limits[i].name, val, val);
-               }
-
-               if (ccs_limits[i].flags & CCS_L_FL_SAME_REG)
-                       continue;
-
-               l++;
-               ptr = alloc + ccs_limit_offsets[l].lim;
-       }
-
-       if (l != CCS_L_LAST) {
-               dev_err(&client->dev,
-                       "internal error --- insufficient limits\n");
-               ret = -EINVAL;
-               goto out_err;
-       }
-
-       sensor->ccs_limits = alloc;
-
-       if (CCS_LIM(sensor, SCALER_N_MIN) < 16)
-               ccs_replace_limit(sensor, CCS_L_SCALER_N_MIN, 0, 16);
-
-       return 0;
-
-out_err:
-       kfree(alloc);
-
-       return ret;
-}
-
-static int smiapp_read_frame_fmt(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       u8 fmt_model_type, fmt_model_subtype, ncol_desc, nrow_desc;
-       unsigned int i;
-       int pixel_count = 0;
-       int line_count = 0;
-
-       fmt_model_type = CCS_LIM(sensor, FRAME_FORMAT_MODEL_TYPE);
-       fmt_model_subtype = CCS_LIM(sensor, FRAME_FORMAT_MODEL_SUBTYPE);
-
-       ncol_desc = (fmt_model_subtype
-                    & CCS_FRAME_FORMAT_MODEL_SUBTYPE_COLUMNS_MASK)
-               >> CCS_FRAME_FORMAT_MODEL_SUBTYPE_COLUMNS_SHIFT;
-       nrow_desc = fmt_model_subtype
-               & CCS_FRAME_FORMAT_MODEL_SUBTYPE_ROWS_MASK;
-
-       dev_dbg(&client->dev, "format_model_type %s\n",
-               fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_2_BYTE
-               ? "2 byte" :
-               fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_4_BYTE
-               ? "4 byte" : "is simply bad");
-
-       dev_dbg(&client->dev, "%u column and %u row descriptors\n",
-               ncol_desc, nrow_desc);
-
-       for (i = 0; i < ncol_desc + nrow_desc; i++) {
-               u32 desc;
-               u32 pixelcode;
-               u32 pixels;
-               char *which;
-               char *what;
-               u32 reg;
-
-               if (fmt_model_type == CCS_FRAME_FORMAT_MODEL_TYPE_2_BYTE) {
-                       desc = CCS_LIM_AT(sensor, FRAME_FORMAT_DESCRIPTOR, i);
-
-                       pixelcode =
-                               (desc
-                                & CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_MASK)
-                               >> CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_SHIFT;
-                       pixels = desc & CCS_FRAME_FORMAT_DESCRIPTOR_PIXELS_MASK;
-               } else if (fmt_model_type
-                          == CCS_FRAME_FORMAT_MODEL_TYPE_4_BYTE) {
-                       desc = CCS_LIM_AT(sensor, FRAME_FORMAT_DESCRIPTOR_4, i);
-
-                       pixelcode =
-                               (desc
-                                & CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_MASK)
-                               >> CCS_FRAME_FORMAT_DESCRIPTOR_4_PCODE_SHIFT;
-                       pixels = desc &
-                               CCS_FRAME_FORMAT_DESCRIPTOR_4_PIXELS_MASK;
-               } else {
-                       dev_dbg(&client->dev,
-                               "invalid frame format model type %d\n",
-                               fmt_model_type);
-                       return -EINVAL;
-               }
-
-               if (i < ncol_desc)
-                       which = "columns";
-               else
-                       which = "rows";
-
-               switch (pixelcode) {
-               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_EMBEDDED:
-                       what = "embedded";
-                       break;
-               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_DUMMY_PIXEL:
-                       what = "dummy";
-                       break;
-               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_BLACK_PIXEL:
-                       what = "black";
-                       break;
-               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_DARK_PIXEL:
-                       what = "dark";
-                       break;
-               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL:
-                       what = "visible";
-                       break;
-               default:
-                       what = "invalid";
-                       break;
-               }
-
-               dev_dbg(&client->dev,
-                       "0x%8.8x %s pixels: %d %s (pixelcode %u)\n", reg,
-                       what, pixels, which, pixelcode);
-
-               if (i < ncol_desc) {
-                       if (pixelcode ==
-                           CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL)
-                               sensor->visible_pixel_start = pixel_count;
-                       pixel_count += pixels;
-                       continue;
-               }
-
-               /* Handle row descriptors */
-               switch (pixelcode) {
-               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_EMBEDDED:
-                       if (sensor->embedded_end)
-                               break;
-                       sensor->embedded_start = line_count;
-                       sensor->embedded_end = line_count + pixels;
-                       break;
-               case CCS_FRAME_FORMAT_DESCRIPTOR_PCODE_VISIBLE_PIXEL:
-                       sensor->image_start = line_count;
-                       break;
-               }
-               line_count += pixels;
-       }
-
-       if (sensor->embedded_end > sensor->image_start) {
-               dev_dbg(&client->dev,
-                       "adjusting image start line to %u (was %u)\n",
-                       sensor->embedded_end, sensor->image_start);
-               sensor->image_start = sensor->embedded_end;
-       }
-
-       dev_dbg(&client->dev, "embedded data from lines %d to %d\n",
-               sensor->embedded_start, sensor->embedded_end);
-       dev_dbg(&client->dev, "image data starts at line %d\n",
-               sensor->image_start);
-
-       return 0;
-}
-
-static int smiapp_pll_configure(struct smiapp_sensor *sensor)
-{
-       struct smiapp_pll *pll = &sensor->pll;
-       int rval;
-
-       rval = ccs_write(sensor, VT_PIX_CLK_DIV, pll->vt.pix_clk_div);
-       if (rval < 0)
-               return rval;
-
-       rval = ccs_write(sensor, VT_SYS_CLK_DIV, pll->vt.sys_clk_div);
-       if (rval < 0)
-               return rval;
-
-       rval = ccs_write(sensor, PRE_PLL_CLK_DIV, pll->pre_pll_clk_div);
-       if (rval < 0)
-               return rval;
-
-       rval = ccs_write(sensor, PLL_MULTIPLIER, pll->pll_multiplier);
-       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.sys_clk_freq_hz,
-                                     1000000 / 256 / 256));
-       if (rval < 0 || sensor->minfo.smiapp_profile == SMIAPP_PROFILE_0)
-               return rval;
-
-       rval = ccs_write(sensor, OP_PIX_CLK_DIV, pll->op.pix_clk_div);
-       if (rval < 0)
-               return rval;
-
-       return ccs_write(sensor, OP_SYS_CLK_DIV, pll->op.sys_clk_div);
-}
-
-static int smiapp_pll_try(struct smiapp_sensor *sensor,
-                         struct smiapp_pll *pll)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       struct smiapp_pll_limits lim = {
-               .min_pre_pll_clk_div = CCS_LIM(sensor, MIN_PRE_PLL_CLK_DIV),
-               .max_pre_pll_clk_div = CCS_LIM(sensor, MAX_PRE_PLL_CLK_DIV),
-               .min_pll_ip_freq_hz = CCS_LIM(sensor, MIN_PLL_IP_CLK_FREQ_MHZ),
-               .max_pll_ip_freq_hz = CCS_LIM(sensor, MAX_PLL_IP_CLK_FREQ_MHZ),
-               .min_pll_multiplier = CCS_LIM(sensor, MIN_PLL_MULTIPLIER),
-               .max_pll_multiplier = CCS_LIM(sensor, MAX_PLL_MULTIPLIER),
-               .min_pll_op_freq_hz = CCS_LIM(sensor, MIN_PLL_OP_CLK_FREQ_MHZ),
-               .max_pll_op_freq_hz = CCS_LIM(sensor, MAX_PLL_OP_CLK_FREQ_MHZ),
-
-               .op.min_sys_clk_div = CCS_LIM(sensor, MIN_OP_SYS_CLK_DIV),
-               .op.max_sys_clk_div = CCS_LIM(sensor, MAX_OP_SYS_CLK_DIV),
-               .op.min_pix_clk_div = CCS_LIM(sensor, MIN_OP_PIX_CLK_DIV),
-               .op.max_pix_clk_div = CCS_LIM(sensor, MAX_OP_PIX_CLK_DIV),
-               .op.min_sys_clk_freq_hz = CCS_LIM(sensor, MIN_OP_SYS_CLK_FREQ_MHZ),
-               .op.max_sys_clk_freq_hz = CCS_LIM(sensor, MAX_OP_SYS_CLK_FREQ_MHZ),
-               .op.min_pix_clk_freq_hz = CCS_LIM(sensor, MIN_OP_PIX_CLK_FREQ_MHZ),
-               .op.max_pix_clk_freq_hz = CCS_LIM(sensor, MAX_OP_PIX_CLK_FREQ_MHZ),
-
-               .vt.min_sys_clk_div = CCS_LIM(sensor, MIN_VT_SYS_CLK_DIV),
-               .vt.max_sys_clk_div = CCS_LIM(sensor, MAX_VT_SYS_CLK_DIV),
-               .vt.min_pix_clk_div = CCS_LIM(sensor, MIN_VT_PIX_CLK_DIV),
-               .vt.max_pix_clk_div = CCS_LIM(sensor, MAX_VT_PIX_CLK_DIV),
-               .vt.min_sys_clk_freq_hz = CCS_LIM(sensor, MIN_VT_SYS_CLK_FREQ_MHZ),
-               .vt.max_sys_clk_freq_hz = CCS_LIM(sensor, MAX_VT_SYS_CLK_FREQ_MHZ),
-               .vt.min_pix_clk_freq_hz = CCS_LIM(sensor, MIN_VT_PIX_CLK_FREQ_MHZ),
-               .vt.max_pix_clk_freq_hz = CCS_LIM(sensor, MAX_VT_PIX_CLK_FREQ_MHZ),
-
-               .min_line_length_pck_bin = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN),
-               .min_line_length_pck = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK),
-       };
-
-       return smiapp_pll_calculate(&client->dev, &lim, pll);
-}
-
-static int smiapp_pll_update(struct smiapp_sensor *sensor)
-{
-       struct smiapp_pll *pll = &sensor->pll;
-       int rval;
-
-       pll->binning_horizontal = sensor->binning_horizontal;
-       pll->binning_vertical = sensor->binning_vertical;
-       pll->link_freq =
-               sensor->link_freq->qmenu_int[sensor->link_freq->val];
-       pll->scale_m = sensor->scale_m;
-       pll->bits_per_pixel = sensor->csi_format->compressed;
-
-       rval = smiapp_pll_try(sensor, pll);
-       if (rval < 0)
-               return rval;
-
-       __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_parray,
-                                pll->pixel_rate_pixel_array);
-       __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_csi, pll->pixel_rate_csi);
-
-       return 0;
-}
-
-
-/*
- *
- * V4L2 Controls handling
- *
- */
-
-static void __smiapp_update_exposure_limits(struct smiapp_sensor *sensor)
-{
-       struct v4l2_ctrl *ctrl = sensor->exposure;
-       int max;
-
-       max = sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height
-               + sensor->vblank->val
-               - CCS_LIM(sensor, COARSE_INTEGRATION_TIME_MAX_MARGIN);
-
-       __v4l2_ctrl_modify_range(ctrl, ctrl->minimum, max, ctrl->step, max);
-}
-
-/*
- * Order matters.
- *
- * 1. Bits-per-pixel, descending.
- * 2. Bits-per-pixel compressed, descending.
- * 3. Pixel order, same as in pixel_order_str. Formats for all four pixel
- *    orders must be defined.
- */
-static const struct smiapp_csi_data_format smiapp_csi_data_formats[] = {
-       { MEDIA_BUS_FMT_SGRBG16_1X16, 16, 16, SMIAPP_PIXEL_ORDER_GRBG, },
-       { MEDIA_BUS_FMT_SRGGB16_1X16, 16, 16, SMIAPP_PIXEL_ORDER_RGGB, },
-       { MEDIA_BUS_FMT_SBGGR16_1X16, 16, 16, SMIAPP_PIXEL_ORDER_BGGR, },
-       { MEDIA_BUS_FMT_SGBRG16_1X16, 16, 16, SMIAPP_PIXEL_ORDER_GBRG, },
-       { MEDIA_BUS_FMT_SGRBG14_1X14, 14, 14, SMIAPP_PIXEL_ORDER_GRBG, },
-       { MEDIA_BUS_FMT_SRGGB14_1X14, 14, 14, SMIAPP_PIXEL_ORDER_RGGB, },
-       { MEDIA_BUS_FMT_SBGGR14_1X14, 14, 14, SMIAPP_PIXEL_ORDER_BGGR, },
-       { MEDIA_BUS_FMT_SGBRG14_1X14, 14, 14, SMIAPP_PIXEL_ORDER_GBRG, },
-       { MEDIA_BUS_FMT_SGRBG12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_GRBG, },
-       { MEDIA_BUS_FMT_SRGGB12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_RGGB, },
-       { MEDIA_BUS_FMT_SBGGR12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_BGGR, },
-       { MEDIA_BUS_FMT_SGBRG12_1X12, 12, 12, SMIAPP_PIXEL_ORDER_GBRG, },
-       { MEDIA_BUS_FMT_SGRBG10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_GRBG, },
-       { MEDIA_BUS_FMT_SRGGB10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_RGGB, },
-       { MEDIA_BUS_FMT_SBGGR10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_BGGR, },
-       { MEDIA_BUS_FMT_SGBRG10_1X10, 10, 10, SMIAPP_PIXEL_ORDER_GBRG, },
-       { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_GRBG, },
-       { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_RGGB, },
-       { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_BGGR, },
-       { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, 10, 8, SMIAPP_PIXEL_ORDER_GBRG, },
-       { MEDIA_BUS_FMT_SGRBG8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_GRBG, },
-       { MEDIA_BUS_FMT_SRGGB8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_RGGB, },
-       { MEDIA_BUS_FMT_SBGGR8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_BGGR, },
-       { MEDIA_BUS_FMT_SGBRG8_1X8, 8, 8, SMIAPP_PIXEL_ORDER_GBRG, },
-};
-
-static const char *pixel_order_str[] = { "GRBG", "RGGB", "BGGR", "GBRG" };
-
-#define to_csi_format_idx(fmt) (((unsigned long)(fmt)                  \
-                                - (unsigned long)smiapp_csi_data_formats) \
-                               / sizeof(*smiapp_csi_data_formats))
-
-static u32 smiapp_pixel_order(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int flip = 0;
-
-       if (sensor->hflip) {
-               if (sensor->hflip->val)
-                       flip |= CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR;
-
-               if (sensor->vflip->val)
-                       flip |= CCS_IMAGE_ORIENTATION_VERTICAL_FLIP;
-       }
-
-       flip ^= sensor->hvflip_inv_mask;
-
-       dev_dbg(&client->dev, "flip %d\n", flip);
-       return sensor->default_pixel_order ^ flip;
-}
-
-static void smiapp_update_mbus_formats(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       unsigned int csi_format_idx =
-               to_csi_format_idx(sensor->csi_format) & ~3;
-       unsigned int internal_csi_format_idx =
-               to_csi_format_idx(sensor->internal_csi_format) & ~3;
-       unsigned int pixel_order = smiapp_pixel_order(sensor);
-
-       sensor->mbus_frame_fmts =
-               sensor->default_mbus_frame_fmts << pixel_order;
-       sensor->csi_format =
-               &smiapp_csi_data_formats[csi_format_idx + pixel_order];
-       sensor->internal_csi_format =
-               &smiapp_csi_data_formats[internal_csi_format_idx
-                                        + pixel_order];
-
-       BUG_ON(max(internal_csi_format_idx, csi_format_idx) + pixel_order
-              >= ARRAY_SIZE(smiapp_csi_data_formats));
-
-       dev_dbg(&client->dev, "new pixel order %s\n",
-               pixel_order_str[pixel_order]);
-}
-
-static const char * const smiapp_test_patterns[] = {
-       "Disabled",
-       "Solid Colour",
-       "Eight Vertical Colour Bars",
-       "Colour Bars With Fade to Grey",
-       "Pseudorandom Sequence (PN9)",
-};
-
-static int smiapp_set_ctrl(struct v4l2_ctrl *ctrl)
-{
-       struct smiapp_sensor *sensor =
-               container_of(ctrl->handler, struct smiapp_subdev, ctrl_handler)
-                       ->sensor;
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int pm_status;
-       u32 orient = 0;
-       unsigned int i;
-       int exposure;
-       int rval;
-
-       switch (ctrl->id) {
-       case V4L2_CID_HFLIP:
-       case V4L2_CID_VFLIP:
-               if (sensor->streaming)
-                       return -EBUSY;
-
-               if (sensor->hflip->val)
-                       orient |= CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR;
-
-               if (sensor->vflip->val)
-                       orient |= CCS_IMAGE_ORIENTATION_VERTICAL_FLIP;
-
-               orient ^= sensor->hvflip_inv_mask;
-
-               smiapp_update_mbus_formats(sensor);
-
-               break;
-       case V4L2_CID_VBLANK:
-               exposure = sensor->exposure->val;
-
-               __smiapp_update_exposure_limits(sensor);
-
-               if (exposure > sensor->exposure->maximum) {
-                       sensor->exposure->val = sensor->exposure->maximum;
-                       rval = smiapp_set_ctrl(sensor->exposure);
-                       if (rval < 0)
-                               return rval;
-               }
-
-               break;
-       case V4L2_CID_LINK_FREQ:
-               if (sensor->streaming)
-                       return -EBUSY;
-
-               rval = smiapp_pll_update(sensor);
-               if (rval)
-                       return rval;
-
-               return 0;
-       case V4L2_CID_TEST_PATTERN:
-               for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++)
-                       v4l2_ctrl_activate(
-                               sensor->test_data[i],
-                               ctrl->val ==
-                               V4L2_SMIAPP_TEST_PATTERN_MODE_SOLID_COLOUR);
-
-               break;
-       }
-
-       pm_status = pm_runtime_get_if_active(&client->dev, true);
-       if (!pm_status)
-               return 0;
-
-       switch (ctrl->id) {
-       case V4L2_CID_ANALOGUE_GAIN:
-               rval = ccs_write(sensor, ANALOG_GAIN_CODE_GLOBAL, ctrl->val);
-
-               break;
-       case V4L2_CID_EXPOSURE:
-               rval = ccs_write(sensor, COARSE_INTEGRATION_TIME, ctrl->val);
-
-               break;
-       case V4L2_CID_HFLIP:
-       case V4L2_CID_VFLIP:
-               rval = ccs_write(sensor, IMAGE_ORIENTATION, orient);
-
-               break;
-       case V4L2_CID_VBLANK:
-               rval = ccs_write(sensor, FRAME_LENGTH_LINES,
-                                sensor->pixel_array->crop[
-                                        SMIAPP_PA_PAD_SRC].height
-                                + ctrl->val);
-
-               break;
-       case V4L2_CID_HBLANK:
-               rval = ccs_write(sensor, LINE_LENGTH_PCK,
-                                sensor->pixel_array->crop[
-                                        SMIAPP_PA_PAD_SRC].width
-                                + ctrl->val);
-
-               break;
-       case V4L2_CID_TEST_PATTERN:
-               rval = ccs_write(sensor, TEST_PATTERN_MODE, ctrl->val);
-
-               break;
-       case V4L2_CID_TEST_PATTERN_RED:
-               rval = ccs_write(sensor, TEST_DATA_RED, ctrl->val);
-
-               break;
-       case V4L2_CID_TEST_PATTERN_GREENR:
-               rval = ccs_write(sensor, TEST_DATA_GREENR, ctrl->val);
-
-               break;
-       case V4L2_CID_TEST_PATTERN_BLUE:
-               rval = ccs_write(sensor, TEST_DATA_BLUE, ctrl->val);
-
-               break;
-       case V4L2_CID_TEST_PATTERN_GREENB:
-               rval = ccs_write(sensor, TEST_DATA_GREENB, ctrl->val);
-
-               break;
-       case V4L2_CID_PIXEL_RATE:
-               /* For v4l2_ctrl_s_ctrl_int64() used internally. */
-               rval = 0;
-
-               break;
-       default:
-               rval = -EINVAL;
-       }
-
-       if (pm_status > 0) {
-               pm_runtime_mark_last_busy(&client->dev);
-               pm_runtime_put_autosuspend(&client->dev);
-       }
-
-       return rval;
-}
-
-static const struct v4l2_ctrl_ops smiapp_ctrl_ops = {
-       .s_ctrl = smiapp_set_ctrl,
-};
-
-static int smiapp_init_controls(struct smiapp_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);
-       if (rval)
-               return rval;
-
-       sensor->pixel_array->ctrl_handler.lock = &sensor->mutex;
-
-       sensor->analog_gain = v4l2_ctrl_new_std(
-               &sensor->pixel_array->ctrl_handler, &smiapp_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));
-
-       /* Exposure limits will be updated soon, use just something here. */
-       sensor->exposure = v4l2_ctrl_new_std(
-               &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops,
-               V4L2_CID_EXPOSURE, 0, 0, 1, 0);
-
-       sensor->hflip = v4l2_ctrl_new_std(
-               &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops,
-               V4L2_CID_HFLIP, 0, 1, 1, 0);
-       sensor->vflip = v4l2_ctrl_new_std(
-               &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops,
-               V4L2_CID_VFLIP, 0, 1, 1, 0);
-
-       sensor->vblank = v4l2_ctrl_new_std(
-               &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops,
-               V4L2_CID_VBLANK, 0, 1, 1, 0);
-
-       if (sensor->vblank)
-               sensor->vblank->flags |= V4L2_CTRL_FLAG_UPDATE;
-
-       sensor->hblank = v4l2_ctrl_new_std(
-               &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops,
-               V4L2_CID_HBLANK, 0, 1, 1, 0);
-
-       if (sensor->hblank)
-               sensor->hblank->flags |= V4L2_CTRL_FLAG_UPDATE;
-
-       sensor->pixel_rate_parray = v4l2_ctrl_new_std(
-               &sensor->pixel_array->ctrl_handler, &smiapp_ctrl_ops,
-               V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
-
-       v4l2_ctrl_new_std_menu_items(&sensor->pixel_array->ctrl_handler,
-                                    &smiapp_ctrl_ops, V4L2_CID_TEST_PATTERN,
-                                    ARRAY_SIZE(smiapp_test_patterns) - 1,
-                                    0, 0, smiapp_test_patterns);
-
-       if (sensor->pixel_array->ctrl_handler.error) {
-               dev_err(&client->dev,
-                       "pixel array controls initialization failed (%d)\n",
-                       sensor->pixel_array->ctrl_handler.error);
-               return sensor->pixel_array->ctrl_handler.error;
-       }
-
-       sensor->pixel_array->sd.ctrl_handler =
-               &sensor->pixel_array->ctrl_handler;
-
-       v4l2_ctrl_cluster(2, &sensor->hflip);
-
-       rval = v4l2_ctrl_handler_init(&sensor->src->ctrl_handler, 0);
-       if (rval)
-               return rval;
-
-       sensor->src->ctrl_handler.lock = &sensor->mutex;
-
-       sensor->pixel_rate_csi = v4l2_ctrl_new_std(
-               &sensor->src->ctrl_handler, &smiapp_ctrl_ops,
-               V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1);
-
-       if (sensor->src->ctrl_handler.error) {
-               dev_err(&client->dev,
-                       "src controls initialization failed (%d)\n",
-                       sensor->src->ctrl_handler.error);
-               return sensor->src->ctrl_handler.error;
-       }
-
-       sensor->src->sd.ctrl_handler = &sensor->src->ctrl_handler;
-
-       return 0;
-}
-
-/*
- * For controls that require information on available media bus codes
- * and linke frequencies.
- */
-static int smiapp_init_late_controls(struct smiapp_sensor *sensor)
-{
-       unsigned long *valid_link_freqs = &sensor->valid_link_freqs[
-               sensor->csi_format->compressed - sensor->compressed_min_bpp];
-       unsigned int i;
-
-       for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++) {
-               int max_value = (1 << sensor->csi_format->width) - 1;
-
-               sensor->test_data[i] = v4l2_ctrl_new_std(
-                               &sensor->pixel_array->ctrl_handler,
-                               &smiapp_ctrl_ops, V4L2_CID_TEST_PATTERN_RED + i,
-                               0, max_value, 1, max_value);
-       }
-
-       sensor->link_freq = v4l2_ctrl_new_int_menu(
-               &sensor->src->ctrl_handler, &smiapp_ctrl_ops,
-               V4L2_CID_LINK_FREQ, __fls(*valid_link_freqs),
-               __ffs(*valid_link_freqs), sensor->hwcfg->op_sys_clock);
-
-       return sensor->src->ctrl_handler.error;
-}
-
-static void smiapp_free_controls(struct smiapp_sensor *sensor)
-{
-       unsigned int i;
-
-       for (i = 0; i < sensor->ssds_used; i++)
-               v4l2_ctrl_handler_free(&sensor->ssds[i].ctrl_handler);
-}
-
-static int smiapp_get_mbus_formats(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       struct smiapp_pll *pll = &sensor->pll;
-       u8 compressed_max_bpp = 0;
-       unsigned int type, n;
-       unsigned int i, pixel_order;
-       int rval;
-
-       type = CCS_LIM(sensor, DATA_FORMAT_MODEL_TYPE);
-
-       dev_dbg(&client->dev, "data_format_model_type %d\n", type);
-
-       rval = ccs_read(sensor, PIXEL_ORDER, &pixel_order);
-       if (rval)
-               return rval;
-
-       if (pixel_order >= ARRAY_SIZE(pixel_order_str)) {
-               dev_dbg(&client->dev, "bad pixel order %d\n", pixel_order);
-               return -EINVAL;
-       }
-
-       dev_dbg(&client->dev, "pixel order %d (%s)\n", pixel_order,
-               pixel_order_str[pixel_order]);
-
-       switch (type) {
-       case CCS_DATA_FORMAT_MODEL_TYPE_NORMAL:
-               n = SMIAPP_DATA_FORMAT_MODEL_TYPE_NORMAL_N;
-               break;
-       case CCS_DATA_FORMAT_MODEL_TYPE_EXTENDED:
-               n = CCS_LIM_DATA_FORMAT_DESCRIPTOR_MAX_N + 1;
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       sensor->default_pixel_order = pixel_order;
-       sensor->mbus_frame_fmts = 0;
-
-       for (i = 0; i < n; i++) {
-               unsigned int fmt, j;
-
-               fmt = CCS_LIM_AT(sensor, DATA_FORMAT_DESCRIPTOR, i);
-
-               dev_dbg(&client->dev, "%u: bpp %u, compressed %u\n",
-                       i, fmt >> 8, (u8)fmt);
-
-               for (j = 0; j < ARRAY_SIZE(smiapp_csi_data_formats); j++) {
-                       const struct smiapp_csi_data_format *f =
-                               &smiapp_csi_data_formats[j];
-
-                       if (f->pixel_order != SMIAPP_PIXEL_ORDER_GRBG)
-                               continue;
-
-                       if (f->width != fmt >>
-                           CCS_DATA_FORMAT_DESCRIPTOR_UNCOMPRESSED_SHIFT ||
-                           f->compressed !=
-                           (fmt & CCS_DATA_FORMAT_DESCRIPTOR_COMPRESSED_MASK))
-                               continue;
-
-                       dev_dbg(&client->dev, "jolly good! %d\n", j);
-
-                       sensor->default_mbus_frame_fmts |= 1 << j;
-               }
-       }
-
-       /* Figure out which BPP values can be used with which formats. */
-       pll->binning_horizontal = 1;
-       pll->binning_vertical = 1;
-       pll->scale_m = sensor->scale_m;
-
-       for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) {
-               sensor->compressed_min_bpp =
-                       min(smiapp_csi_data_formats[i].compressed,
-                           sensor->compressed_min_bpp);
-               compressed_max_bpp =
-                       max(smiapp_csi_data_formats[i].compressed,
-                           compressed_max_bpp);
-       }
-
-       sensor->valid_link_freqs = devm_kcalloc(
-               &client->dev,
-               compressed_max_bpp - sensor->compressed_min_bpp + 1,
-               sizeof(*sensor->valid_link_freqs), GFP_KERNEL);
-       if (!sensor->valid_link_freqs)
-               return -ENOMEM;
-
-       for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) {
-               const struct smiapp_csi_data_format *f =
-                       &smiapp_csi_data_formats[i];
-               unsigned long *valid_link_freqs =
-                       &sensor->valid_link_freqs[
-                               f->compressed - sensor->compressed_min_bpp];
-               unsigned int j;
-
-               if (!(sensor->default_mbus_frame_fmts & 1 << i))
-                       continue;
-
-               pll->bits_per_pixel = f->compressed;
-
-               for (j = 0; sensor->hwcfg->op_sys_clock[j]; j++) {
-                       pll->link_freq = sensor->hwcfg->op_sys_clock[j];
-
-                       rval = smiapp_pll_try(sensor, pll);
-                       dev_dbg(&client->dev, "link freq %u Hz, bpp %u %s\n",
-                               pll->link_freq, pll->bits_per_pixel,
-                               rval ? "not ok" : "ok");
-                       if (rval)
-                               continue;
-
-                       set_bit(j, valid_link_freqs);
-               }
-
-               if (!*valid_link_freqs) {
-                       dev_info(&client->dev,
-                                "no valid link frequencies for %u bpp\n",
-                                f->compressed);
-                       sensor->default_mbus_frame_fmts &= ~BIT(i);
-                       continue;
-               }
-
-               if (!sensor->csi_format
-                   || f->width > sensor->csi_format->width
-                   || (f->width == sensor->csi_format->width
-                       && f->compressed > sensor->csi_format->compressed)) {
-                       sensor->csi_format = f;
-                       sensor->internal_csi_format = f;
-               }
-       }
-
-       if (!sensor->csi_format) {
-               dev_err(&client->dev, "no supported mbus code found\n");
-               return -EINVAL;
-       }
-
-       smiapp_update_mbus_formats(sensor);
-
-       return 0;
-}
-
-static void smiapp_update_blanking(struct smiapp_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;
-       int min, max;
-
-       if (sensor->binning_vertical > 1 || sensor->binning_horizontal > 1) {
-               min_fll = CCS_LIM(sensor, MIN_FRAME_LENGTH_LINES_BIN);
-               max_fll = CCS_LIM(sensor, MAX_FRAME_LENGTH_LINES_BIN);
-               min_llp = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN);
-               max_llp = CCS_LIM(sensor, MAX_LINE_LENGTH_PCK_BIN);
-               min_lbp = CCS_LIM(sensor, MIN_LINE_BLANKING_PCK_BIN);
-       } else {
-               min_fll = CCS_LIM(sensor, MIN_FRAME_LENGTH_LINES);
-               max_fll = CCS_LIM(sensor, MAX_FRAME_LENGTH_LINES);
-               min_llp = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK);
-               max_llp = CCS_LIM(sensor, MAX_LINE_LENGTH_PCK);
-               min_lbp = CCS_LIM(sensor, MIN_LINE_BLANKING_PCK);
-       }
-
-       min = max_t(int,
-                   CCS_LIM(sensor, MIN_FRAME_BLANKING_LINES),
-                   min_fll -
-                   sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height);
-       max = max_fll - sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height;
-
-       __v4l2_ctrl_modify_range(vblank, min, max, vblank->step, min);
-
-       min = max_t(int,
-                   min_llp -
-                   sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width,
-                   min_lbp);
-       max = max_llp - sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width;
-
-       __v4l2_ctrl_modify_range(hblank, min, max, hblank->step, min);
-
-       __smiapp_update_exposure_limits(sensor);
-}
-
-static int smiapp_pll_blanking_update(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int rval;
-
-       rval = smiapp_pll_update(sensor);
-       if (rval < 0)
-               return rval;
-
-       /* Output from pixel array, including blanking */
-       smiapp_update_blanking(sensor);
-
-       dev_dbg(&client->dev, "vblank\t\t%d\n", sensor->vblank->val);
-       dev_dbg(&client->dev, "hblank\t\t%d\n", sensor->hblank->val);
-
-       dev_dbg(&client->dev, "real timeperframe\t100/%d\n",
-               sensor->pll.pixel_rate_pixel_array /
-               ((sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width
-                 + sensor->hblank->val) *
-                (sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height
-                 + sensor->vblank->val) / 100));
-
-       return 0;
-}
-
-/*
- *
- * SMIA++ NVM handling
- *
- */
-
-static int smiapp_read_nvm_page(struct smiapp_sensor *sensor, u32 p, u8 *nvm,
-                               u8 *status)
-{
-       unsigned int i;
-       int rval;
-       u32 s;
-
-       *status = 0;
-
-       rval = ccs_write(sensor, DATA_TRANSFER_IF_1_PAGE_SELECT, p);
-       if (rval)
-               return rval;
-
-       rval = ccs_write(sensor, DATA_TRANSFER_IF_1_CTRL,
-                        CCS_DATA_TRANSFER_IF_1_CTRL_ENABLE);
-       if (rval)
-               return rval;
-
-       rval = ccs_read(sensor, DATA_TRANSFER_IF_1_STATUS, &s);
-       if (rval)
-               return rval;
-
-       if (s & CCS_DATA_TRANSFER_IF_1_STATUS_IMPROPER_IF_USAGE) {
-               *status = s;
-               return -ENODATA;
-       }
-
-       if (CCS_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) &
-           CCS_DATA_TRANSFER_IF_CAPABILITY_POLLING) {
-               for (i = 1000; i > 0; i--) {
-                       if (s & CCS_DATA_TRANSFER_IF_1_STATUS_READ_IF_READY)
-                               break;
-
-                       rval = ccs_read(sensor, DATA_TRANSFER_IF_1_STATUS, &s);
-                       if (rval)
-                               return rval;
-               }
-
-               if (!i)
-                       return -ETIMEDOUT;
-       }
-
-       for (i = 0; i <= CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P; i++) {
-               u32 v;
-
-               rval = ccs_read(sensor, DATA_TRANSFER_IF_1_DATA(i), &v);
-               if (rval)
-                       return rval;
-
-               *nvm++ = v;
-       }
-
-       return 0;
-}
-
-static int smiapp_read_nvm(struct smiapp_sensor *sensor, unsigned char *nvm,
-                          size_t nvm_size)
-{
-       u8 status = 0;
-       u32 p;
-       int rval = 0, rval2;
-
-       for (p = 0; p < nvm_size / (CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1)
-                    && !rval; p++) {
-               rval = smiapp_read_nvm_page(sensor, p, nvm, &status);
-               nvm += CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1;
-       }
-
-       if (rval == -ENODATA &&
-           status & CCS_DATA_TRANSFER_IF_1_STATUS_IMPROPER_IF_USAGE)
-               rval = 0;
-
-       rval2 = ccs_write(sensor, DATA_TRANSFER_IF_1_CTRL, 0);
-       if (rval < 0)
-               return rval;
-       else
-               return rval2 ?: p * (CCS_LIM_DATA_TRANSFER_IF_1_DATA_MAX_P + 1);
-}
-
-/*
- *
- * SMIA++ CCI address control
- *
- */
-static int smiapp_change_cci_addr(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int rval;
-       u32 val;
-
-       client->addr = sensor->hwcfg->i2c_addr_dfl;
-
-       rval = ccs_write(sensor, CCI_ADDRESS_CTRL,
-                        sensor->hwcfg->i2c_addr_alt << 1);
-       if (rval)
-               return rval;
-
-       client->addr = sensor->hwcfg->i2c_addr_alt;
-
-       /* verify addr change went ok */
-       rval = ccs_read(sensor, CCI_ADDRESS_CTRL, &val);
-       if (rval)
-               return rval;
-
-       if (val != sensor->hwcfg->i2c_addr_alt << 1)
-               return -ENODEV;
-
-       return 0;
-}
-
-/*
- *
- * SMIA++ Mode Control
- *
- */
-static int smiapp_setup_flash_strobe(struct smiapp_sensor *sensor)
-{
-       struct smiapp_flash_strobe_parms *strobe_setup;
-       unsigned int ext_freq = sensor->hwcfg->ext_clk;
-       u32 tmp;
-       u32 strobe_adjustment;
-       u32 strobe_width_high_rs;
-       int rval;
-
-       strobe_setup = sensor->hwcfg->strobe_setup;
-
-       /*
-        * How to calculate registers related to strobe length. Please
-        * do not change, or if you do at least know what you're
-        * doing. :-)
-        *
-        * Sakari Ailus <sakari.ailus@iki.fi> 2010-10-25
-        *
-        * flash_strobe_length [us] / 10^6 = (tFlash_strobe_width_ctrl
-        *      / EXTCLK freq [Hz]) * flash_strobe_adjustment
-        *
-        * tFlash_strobe_width_ctrl E N, [1 - 0xffff]
-        * flash_strobe_adjustment E N, [1 - 0xff]
-        *
-        * The formula above is written as below to keep it on one
-        * line:
-        *
-        * l / 10^6 = w / e * a
-        *
-        * Let's mark w * a by x:
-        *
-        * x = w * a
-        *
-        * Thus, we get:
-        *
-        * x = l * e / 10^6
-        *
-        * The strobe width must be at least as long as requested,
-        * thus rounding upwards is needed.
-        *
-        * x = (l * e + 10^6 - 1) / 10^6
-        * -----------------------------
-        *
-        * Maximum possible accuracy is wanted at all times. Thus keep
-        * a as small as possible.
-        *
-        * Calculate a, assuming maximum w, with rounding upwards:
-        *
-        * a = (x + (2^16 - 1) - 1) / (2^16 - 1)
-        * -------------------------------------
-        *
-        * Thus, we also get w, with that a, with rounding upwards:
-        *
-        * w = (x + a - 1) / a
-        * -------------------
-        *
-        * To get limits:
-        *
-        * x E [1, (2^16 - 1) * (2^8 - 1)]
-        *
-        * Substituting maximum x to the original formula (with rounding),
-        * the maximum l is thus
-        *
-        * (2^16 - 1) * (2^8 - 1) * 10^6 = l * e + 10^6 - 1
-        *
-        * l = (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / e
-        * --------------------------------------------------
-        *
-        * flash_strobe_length must be clamped between 1 and
-        * (10^6 * (2^16 - 1) * (2^8 - 1) - 10^6 + 1) / EXTCLK freq.
-        *
-        * Then,
-        *
-        * flash_strobe_adjustment = ((flash_strobe_length *
-        *      EXTCLK freq + 10^6 - 1) / 10^6 + (2^16 - 1) - 1) / (2^16 - 1)
-        *
-        * tFlash_strobe_width_ctrl = ((flash_strobe_length *
-        *      EXTCLK freq + 10^6 - 1) / 10^6 +
-        *      flash_strobe_adjustment - 1) / flash_strobe_adjustment
-        */
-       tmp = div_u64(1000000ULL * ((1 << 16) - 1) * ((1 << 8) - 1) -
-                     1000000 + 1, ext_freq);
-       strobe_setup->strobe_width_high_us =
-               clamp_t(u32, strobe_setup->strobe_width_high_us, 1, tmp);
-
-       tmp = div_u64(((u64)strobe_setup->strobe_width_high_us * (u64)ext_freq +
-                       1000000 - 1), 1000000ULL);
-       strobe_adjustment = (tmp + (1 << 16) - 1 - 1) / ((1 << 16) - 1);
-       strobe_width_high_rs = (tmp + strobe_adjustment - 1) /
-                               strobe_adjustment;
-
-       rval = ccs_write(sensor, FLASH_MODE_RS, strobe_setup->mode);
-       if (rval < 0)
-               goto out;
-
-       rval = ccs_write(sensor, FLASH_STROBE_ADJUSTMENT, strobe_adjustment);
-       if (rval < 0)
-               goto out;
-
-       rval = ccs_write(sensor, TFLASH_STROBE_WIDTH_HIGH_RS_CTRL,
-                        strobe_width_high_rs);
-       if (rval < 0)
-               goto out;
-
-       rval = ccs_write(sensor, TFLASH_STROBE_DELAY_RS_CTRL,
-                        strobe_setup->strobe_delay);
-       if (rval < 0)
-               goto out;
-
-       rval = ccs_write(sensor, FLASH_STROBE_START_POINT,
-                        strobe_setup->stobe_start_point);
-       if (rval < 0)
-               goto out;
-
-       rval = ccs_write(sensor, FLASH_TRIGGER_RS, strobe_setup->trigger);
-
-out:
-       sensor->hwcfg->strobe_setup->trigger = 0;
-
-       return rval;
-}
-
-/* -----------------------------------------------------------------------------
- * Power management
- */
-
-static int smiapp_power_on(struct device *dev)
-{
-       struct v4l2_subdev *subdev = dev_get_drvdata(dev);
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-       /*
-        * The sub-device related to the I2C device is always the
-        * source one, i.e. ssds[0].
-        */
-       struct smiapp_sensor *sensor =
-               container_of(ssd, struct smiapp_sensor, ssds[0]);
-       unsigned int sleep;
-       int rval;
-
-       rval = regulator_enable(sensor->vana);
-       if (rval) {
-               dev_err(dev, "failed to enable vana regulator\n");
-               return rval;
-       }
-       usleep_range(1000, 1000);
-
-       rval = clk_prepare_enable(sensor->ext_clk);
-       if (rval < 0) {
-               dev_dbg(dev, "failed to enable xclk\n");
-               goto out_xclk_fail;
-       }
-       usleep_range(1000, 1000);
-
-       gpiod_set_value(sensor->xshutdown, 1);
-
-       sleep = SMIAPP_RESET_DELAY(sensor->hwcfg->ext_clk);
-       usleep_range(sleep, sleep);
-
-       /*
-        * Failures to respond to the address change command have been noticed.
-        * Those failures seem to be caused by the sensor requiring a longer
-        * boot time than advertised. An additional 10ms delay seems to work
-        * around the issue, but the SMIA++ I2C write retry hack makes the delay
-        * unnecessary. The failures need to be investigated to find a proper
-        * fix, and a delay will likely need to be added here if the I2C write
-        * retry hack is reverted before the root cause of the boot time issue
-        * is found.
-        */
-
-       if (sensor->hwcfg->i2c_addr_alt) {
-               rval = smiapp_change_cci_addr(sensor);
-               if (rval) {
-                       dev_err(dev, "cci address change error\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;
-       }
-
-       if (sensor->hwcfg->i2c_addr_alt) {
-               rval = smiapp_change_cci_addr(sensor);
-               if (rval) {
-                       dev_err(dev, "cci address change error\n");
-                       goto out_cci_addr_fail;
-               }
-       }
-
-       rval = ccs_write(sensor, COMPRESSION_MODE,
-                        CCS_COMPRESSION_MODE_DPCM_PCM_SIMPLE);
-       if (rval) {
-               dev_err(dev, "compression mode set failed\n");
-               goto out_cci_addr_fail;
-       }
-
-       rval = ccs_write(sensor, EXTCLK_FREQUENCY_MHZ,
-                        sensor->hwcfg->ext_clk / (1000000 / (1 << 8)));
-       if (rval) {
-               dev_err(dev, "extclk frequency set failed\n");
-               goto out_cci_addr_fail;
-       }
-
-       rval = ccs_write(sensor, CSI_LANE_MODE, sensor->hwcfg->lanes - 1);
-       if (rval) {
-               dev_err(dev, "csi lane mode set failed\n");
-               goto out_cci_addr_fail;
-       }
-
-       rval = ccs_write(sensor, FAST_STANDBY_CTRL,
-                        CCS_FAST_STANDBY_CTRL_FRAME_TRUNCATION);
-       if (rval) {
-               dev_err(dev, "fast standby set failed\n");
-               goto out_cci_addr_fail;
-       }
-
-       rval = ccs_write(sensor, CSI_SIGNALING_MODE,
-                        sensor->hwcfg->csi_signalling_mode);
-       if (rval) {
-               dev_err(dev, "csi signalling mode set failed\n");
-               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);
-       if (rval < 0)
-               goto out_cci_addr_fail;
-
-       rval = smiapp_call_quirk(sensor, post_poweron);
-       if (rval) {
-               dev_err(dev, "post_poweron quirks failed\n");
-               goto out_cci_addr_fail;
-       }
-
-       return 0;
-
-out_cci_addr_fail:
-       gpiod_set_value(sensor->xshutdown, 0);
-       clk_disable_unprepare(sensor->ext_clk);
-
-out_xclk_fail:
-       regulator_disable(sensor->vana);
-
-       return rval;
-}
-
-static int smiapp_power_off(struct device *dev)
-{
-       struct v4l2_subdev *subdev = dev_get_drvdata(dev);
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-       struct smiapp_sensor *sensor =
-               container_of(ssd, struct smiapp_sensor, ssds[0]);
-
-       /*
-        * Currently power/clock to lens are enable/disabled separately
-        * but they are essentially the same signals. So if the sensor is
-        * powered off while the lens is powered on the sensor does not
-        * really see a power off and next time the cci address change
-        * will fail. So do a soft reset explicitly here.
-        */
-       if (sensor->hwcfg->i2c_addr_alt)
-               ccs_write(sensor, SOFTWARE_RESET, CCS_SOFTWARE_RESET_ON);
-
-       gpiod_set_value(sensor->xshutdown, 0);
-       clk_disable_unprepare(sensor->ext_clk);
-       usleep_range(5000, 5000);
-       regulator_disable(sensor->vana);
-       sensor->streaming = false;
-
-       return 0;
-}
-
-/* -----------------------------------------------------------------------------
- * Video stream management
- */
-
-static int smiapp_start_streaming(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       unsigned int binning_mode;
-       int rval;
-
-       mutex_lock(&sensor->mutex);
-
-       rval = ccs_write(sensor, CSI_DATA_FORMAT,
-                        (sensor->csi_format->width << 8) |
-                        sensor->csi_format->compressed);
-       if (rval)
-               goto out;
-
-       /* Binning configuration */
-       if (sensor->binning_horizontal == 1 &&
-           sensor->binning_vertical == 1) {
-               binning_mode = 0;
-       } else {
-               u8 binning_type =
-                       (sensor->binning_horizontal << 4)
-                       | sensor->binning_vertical;
-
-               rval = ccs_write(sensor, BINNING_TYPE, binning_type);
-               if (rval < 0)
-                       goto out;
-
-               binning_mode = 1;
-       }
-       rval = ccs_write(sensor, BINNING_MODE, binning_mode);
-       if (rval < 0)
-               goto out;
-
-       /* Set up PLL */
-       rval = smiapp_pll_configure(sensor);
-       if (rval)
-               goto out;
-
-       /* Analog crop start coordinates */
-       rval = ccs_write(sensor, X_ADDR_START,
-                        sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].left);
-       if (rval < 0)
-               goto out;
-
-       rval = ccs_write(sensor, Y_ADDR_START,
-                        sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].top);
-       if (rval < 0)
-               goto out;
-
-       /* Analog crop end coordinates */
-       rval = ccs_write(
-               sensor, X_ADDR_END,
-               sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].left
-               + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].width - 1);
-       if (rval < 0)
-               goto out;
-
-       rval = ccs_write(
-               sensor, Y_ADDR_END,
-               sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].top
-               + sensor->pixel_array->crop[SMIAPP_PA_PAD_SRC].height - 1);
-       if (rval < 0)
-               goto out;
-
-       /*
-        * Output from pixel array, including blanking, is set using
-        * controls below. No need to set here.
-        */
-
-       /* Digital crop */
-       if (CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY)
-           == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) {
-               rval = ccs_write(
-                       sensor, DIGITAL_CROP_X_OFFSET,
-                       sensor->scaler->crop[SMIAPP_PAD_SINK].left);
-               if (rval < 0)
-                       goto out;
-
-               rval = ccs_write(
-                       sensor, DIGITAL_CROP_Y_OFFSET,
-                       sensor->scaler->crop[SMIAPP_PAD_SINK].top);
-               if (rval < 0)
-                       goto out;
-
-               rval = ccs_write(
-                       sensor, DIGITAL_CROP_IMAGE_WIDTH,
-                       sensor->scaler->crop[SMIAPP_PAD_SINK].width);
-               if (rval < 0)
-                       goto out;
-
-               rval = ccs_write(
-                       sensor, DIGITAL_CROP_IMAGE_HEIGHT,
-                       sensor->scaler->crop[SMIAPP_PAD_SINK].height);
-               if (rval < 0)
-                       goto out;
-       }
-
-       /* Scaling */
-       if (CCS_LIM(sensor, SCALING_CAPABILITY)
-           != CCS_SCALING_CAPABILITY_NONE) {
-               rval = ccs_write(sensor, SCALING_MODE, sensor->scaling_mode);
-               if (rval < 0)
-                       goto out;
-
-               rval = ccs_write(sensor, SCALE_M, sensor->scale_m);
-               if (rval < 0)
-                       goto out;
-       }
-
-       /* Output size from sensor */
-       rval = ccs_write(sensor, X_OUTPUT_SIZE,
-                        sensor->src->crop[SMIAPP_PAD_SRC].width);
-       if (rval < 0)
-               goto out;
-       rval = ccs_write(sensor, Y_OUTPUT_SIZE,
-                        sensor->src->crop[SMIAPP_PAD_SRC].height);
-       if (rval < 0)
-               goto out;
-
-       if (CCS_LIM(sensor, FLASH_MODE_CAPABILITY) &
-           (CCS_FLASH_MODE_CAPABILITY_SINGLE_STROBE |
-            SMIAPP_FLASH_MODE_CAPABILITY_MULTIPLE_STROBE) &&
-           sensor->hwcfg->strobe_setup != NULL &&
-           sensor->hwcfg->strobe_setup->trigger != 0) {
-               rval = smiapp_setup_flash_strobe(sensor);
-               if (rval)
-                       goto out;
-       }
-
-       rval = smiapp_call_quirk(sensor, pre_streamon);
-       if (rval) {
-               dev_err(&client->dev, "pre_streamon quirks failed\n");
-               goto out;
-       }
-
-       rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_STREAMING);
-
-out:
-       mutex_unlock(&sensor->mutex);
-
-       return rval;
-}
-
-static int smiapp_stop_streaming(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int rval;
-
-       mutex_lock(&sensor->mutex);
-       rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_SOFTWARE_STANDBY);
-       if (rval)
-               goto out;
-
-       rval = smiapp_call_quirk(sensor, post_streamoff);
-       if (rval)
-               dev_err(&client->dev, "post_streamoff quirks failed\n");
-
-out:
-       mutex_unlock(&sensor->mutex);
-       return rval;
-}
-
-/* -----------------------------------------------------------------------------
- * V4L2 subdev video operations
- */
-
-static int smiapp_pm_get_init(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int rval;
-
-       rval = pm_runtime_get_sync(&client->dev);
-       if (rval < 0) {
-               if (rval != -EBUSY && rval != -EAGAIN)
-                       pm_runtime_set_active(&client->dev);
-               pm_runtime_put_noidle(&client->dev);
-
-               return rval;
-       } else if (!rval) {
-               rval = v4l2_ctrl_handler_setup(&sensor->pixel_array->
-                                              ctrl_handler);
-               if (rval)
-                       return rval;
-
-               return v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler);
-       }
-
-       return 0;
-}
-
-static int smiapp_set_stream(struct v4l2_subdev *subdev, int enable)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int rval;
-
-       if (sensor->streaming == enable)
-               return 0;
-
-       if (!enable) {
-               smiapp_stop_streaming(sensor);
-               sensor->streaming = false;
-               pm_runtime_mark_last_busy(&client->dev);
-               pm_runtime_put_autosuspend(&client->dev);
-
-               return 0;
-       }
-
-       rval = smiapp_pm_get_init(sensor);
-       if (rval)
-               return rval;
-
-       sensor->streaming = true;
-
-       rval = smiapp_start_streaming(sensor);
-       if (rval < 0) {
-               sensor->streaming = false;
-               pm_runtime_mark_last_busy(&client->dev);
-               pm_runtime_put_autosuspend(&client->dev);
-       }
-
-       return rval;
-}
-
-static int smiapp_enum_mbus_code(struct v4l2_subdev *subdev,
-                                struct v4l2_subdev_pad_config *cfg,
-                                struct v4l2_subdev_mbus_code_enum *code)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(subdev);
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       unsigned int i;
-       int idx = -1;
-       int rval = -EINVAL;
-
-       mutex_lock(&sensor->mutex);
-
-       dev_err(&client->dev, "subdev %s, pad %d, index %d\n",
-               subdev->name, code->pad, code->index);
-
-       if (subdev != &sensor->src->sd || code->pad != SMIAPP_PAD_SRC) {
-               if (code->index)
-                       goto out;
-
-               code->code = sensor->internal_csi_format->code;
-               rval = 0;
-               goto out;
-       }
-
-       for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) {
-               if (sensor->mbus_frame_fmts & (1 << i))
-                       idx++;
-
-               if (idx == code->index) {
-                       code->code = smiapp_csi_data_formats[i].code;
-                       dev_err(&client->dev, "found index %d, i %d, code %x\n",
-                               code->index, i, code->code);
-                       rval = 0;
-                       break;
-               }
-       }
-
-out:
-       mutex_unlock(&sensor->mutex);
-
-       return rval;
-}
-
-static u32 __smiapp_get_mbus_code(struct v4l2_subdev *subdev,
-                                 unsigned int pad)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-
-       if (subdev == &sensor->src->sd && pad == SMIAPP_PAD_SRC)
-               return sensor->csi_format->code;
-       else
-               return sensor->internal_csi_format->code;
-}
-
-static int __smiapp_get_format(struct v4l2_subdev *subdev,
-                              struct v4l2_subdev_pad_config *cfg,
-                              struct v4l2_subdev_format *fmt)
-{
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-
-       if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
-               fmt->format = *v4l2_subdev_get_try_format(subdev, cfg,
-                                                         fmt->pad);
-       } else {
-               struct v4l2_rect *r;
-
-               if (fmt->pad == ssd->source_pad)
-                       r = &ssd->crop[ssd->source_pad];
-               else
-                       r = &ssd->sink_fmt;
-
-               fmt->format.code = __smiapp_get_mbus_code(subdev, fmt->pad);
-               fmt->format.width = r->width;
-               fmt->format.height = r->height;
-               fmt->format.field = V4L2_FIELD_NONE;
-       }
-
-       return 0;
-}
-
-static int smiapp_get_format(struct v4l2_subdev *subdev,
-                            struct v4l2_subdev_pad_config *cfg,
-                            struct v4l2_subdev_format *fmt)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       int rval;
-
-       mutex_lock(&sensor->mutex);
-       rval = __smiapp_get_format(subdev, cfg, fmt);
-       mutex_unlock(&sensor->mutex);
-
-       return rval;
-}
-
-static void smiapp_get_crop_compose(struct v4l2_subdev *subdev,
-                                   struct v4l2_subdev_pad_config *cfg,
-                                   struct v4l2_rect **crops,
-                                   struct v4l2_rect **comps, int which)
-{
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-       unsigned int i;
-
-       if (which == V4L2_SUBDEV_FORMAT_ACTIVE) {
-               if (crops)
-                       for (i = 0; i < subdev->entity.num_pads; i++)
-                               crops[i] = &ssd->crop[i];
-               if (comps)
-                       *comps = &ssd->compose;
-       } else {
-               if (crops) {
-                       for (i = 0; i < subdev->entity.num_pads; i++) {
-                               crops[i] = v4l2_subdev_get_try_crop(subdev, cfg, i);
-                               BUG_ON(!crops[i]);
-                       }
-               }
-               if (comps) {
-                       *comps = v4l2_subdev_get_try_compose(subdev, cfg,
-                                                            SMIAPP_PAD_SINK);
-                       BUG_ON(!*comps);
-               }
-       }
-}
-
-/* Changes require propagation only on sink pad. */
-static void smiapp_propagate(struct v4l2_subdev *subdev,
-                            struct v4l2_subdev_pad_config *cfg, int which,
-                            int target)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-       struct v4l2_rect *comp, *crops[SMIAPP_PADS];
-
-       smiapp_get_crop_compose(subdev, cfg, crops, &comp, which);
-
-       switch (target) {
-       case V4L2_SEL_TGT_CROP:
-               comp->width = crops[SMIAPP_PAD_SINK]->width;
-               comp->height = crops[SMIAPP_PAD_SINK]->height;
-               if (which == V4L2_SUBDEV_FORMAT_ACTIVE) {
-                       if (ssd == sensor->scaler) {
-                               sensor->scale_m =
-                                       CCS_LIM(sensor, SCALER_N_MIN);
-                               sensor->scaling_mode =
-                                       CCS_SCALING_MODE_NO_SCALING;
-                       } else if (ssd == sensor->binner) {
-                               sensor->binning_horizontal = 1;
-                               sensor->binning_vertical = 1;
-                       }
-               }
-               fallthrough;
-       case V4L2_SEL_TGT_COMPOSE:
-               *crops[SMIAPP_PAD_SRC] = *comp;
-               break;
-       default:
-               BUG();
-       }
-}
-
-static const struct smiapp_csi_data_format
-*smiapp_validate_csi_data_format(struct smiapp_sensor *sensor, u32 code)
-{
-       unsigned int i;
-
-       for (i = 0; i < ARRAY_SIZE(smiapp_csi_data_formats); i++) {
-               if (sensor->mbus_frame_fmts & (1 << i)
-                   && smiapp_csi_data_formats[i].code == code)
-                       return &smiapp_csi_data_formats[i];
-       }
-
-       return sensor->csi_format;
-}
-
-static int smiapp_set_format_source(struct v4l2_subdev *subdev,
-                                   struct v4l2_subdev_pad_config *cfg,
-                                   struct v4l2_subdev_format *fmt)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       const struct smiapp_csi_data_format *csi_format,
-               *old_csi_format = sensor->csi_format;
-       unsigned long *valid_link_freqs;
-       u32 code = fmt->format.code;
-       unsigned int i;
-       int rval;
-
-       rval = __smiapp_get_format(subdev, cfg, fmt);
-       if (rval)
-               return rval;
-
-       /*
-        * Media bus code is changeable on src subdev's source pad. On
-        * other source pads we just get format here.
-        */
-       if (subdev != &sensor->src->sd)
-               return 0;
-
-       csi_format = smiapp_validate_csi_data_format(sensor, code);
-
-       fmt->format.code = csi_format->code;
-
-       if (fmt->which != V4L2_SUBDEV_FORMAT_ACTIVE)
-               return 0;
-
-       sensor->csi_format = csi_format;
-
-       if (csi_format->width != old_csi_format->width)
-               for (i = 0; i < ARRAY_SIZE(sensor->test_data); i++)
-                       __v4l2_ctrl_modify_range(
-                               sensor->test_data[i], 0,
-                               (1 << csi_format->width) - 1, 1, 0);
-
-       if (csi_format->compressed == old_csi_format->compressed)
-               return 0;
-
-       valid_link_freqs =
-               &sensor->valid_link_freqs[sensor->csi_format->compressed
-                                         - sensor->compressed_min_bpp];
-
-       __v4l2_ctrl_modify_range(
-               sensor->link_freq, 0,
-               __fls(*valid_link_freqs), ~*valid_link_freqs,
-               __ffs(*valid_link_freqs));
-
-       return smiapp_pll_update(sensor);
-}
-
-static int smiapp_set_format(struct v4l2_subdev *subdev,
-                            struct v4l2_subdev_pad_config *cfg,
-                            struct v4l2_subdev_format *fmt)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-       struct v4l2_rect *crops[SMIAPP_PADS];
-
-       mutex_lock(&sensor->mutex);
-
-       if (fmt->pad == ssd->source_pad) {
-               int rval;
-
-               rval = smiapp_set_format_source(subdev, cfg, fmt);
-
-               mutex_unlock(&sensor->mutex);
-
-               return rval;
-       }
-
-       /* Sink pad. Width and height are changeable here. */
-       fmt->format.code = __smiapp_get_mbus_code(subdev, fmt->pad);
-       fmt->format.width &= ~1;
-       fmt->format.height &= ~1;
-       fmt->format.field = V4L2_FIELD_NONE;
-
-       fmt->format.width =
-               clamp(fmt->format.width,
-                     CCS_LIM(sensor, MIN_X_OUTPUT_SIZE),
-                     CCS_LIM(sensor, MAX_X_OUTPUT_SIZE));
-       fmt->format.height =
-               clamp(fmt->format.height,
-                     CCS_LIM(sensor, MIN_Y_OUTPUT_SIZE),
-                     CCS_LIM(sensor, MAX_Y_OUTPUT_SIZE));
-
-       smiapp_get_crop_compose(subdev, cfg, crops, NULL, fmt->which);
-
-       crops[ssd->sink_pad]->left = 0;
-       crops[ssd->sink_pad]->top = 0;
-       crops[ssd->sink_pad]->width = fmt->format.width;
-       crops[ssd->sink_pad]->height = fmt->format.height;
-       if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
-               ssd->sink_fmt = *crops[ssd->sink_pad];
-       smiapp_propagate(subdev, cfg, fmt->which,
-                        V4L2_SEL_TGT_CROP);
-
-       mutex_unlock(&sensor->mutex);
-
-       return 0;
-}
-
-/*
- * Calculate goodness of scaled image size compared to expected image
- * size and flags provided.
- */
-#define SCALING_GOODNESS               100000
-#define SCALING_GOODNESS_EXTREME       100000000
-static int scaling_goodness(struct v4l2_subdev *subdev, int w, int ask_w,
-                           int h, int ask_h, u32 flags)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct i2c_client *client = v4l2_get_subdevdata(subdev);
-       int val = 0;
-
-       w &= ~1;
-       ask_w &= ~1;
-       h &= ~1;
-       ask_h &= ~1;
-
-       if (flags & V4L2_SEL_FLAG_GE) {
-               if (w < ask_w)
-                       val -= SCALING_GOODNESS;
-               if (h < ask_h)
-                       val -= SCALING_GOODNESS;
-       }
-
-       if (flags & V4L2_SEL_FLAG_LE) {
-               if (w > ask_w)
-                       val -= SCALING_GOODNESS;
-               if (h > ask_h)
-                       val -= SCALING_GOODNESS;
-       }
-
-       val -= abs(w - ask_w);
-       val -= abs(h - ask_h);
-
-       if (w < CCS_LIM(sensor, MIN_X_OUTPUT_SIZE))
-               val -= SCALING_GOODNESS_EXTREME;
-
-       dev_dbg(&client->dev, "w %d ask_w %d h %d ask_h %d goodness %d\n",
-               w, ask_w, h, ask_h, val);
-
-       return val;
-}
-
-static void smiapp_set_compose_binner(struct v4l2_subdev *subdev,
-                                     struct v4l2_subdev_pad_config *cfg,
-                                     struct v4l2_subdev_selection *sel,
-                                     struct v4l2_rect **crops,
-                                     struct v4l2_rect *comp)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       unsigned int i;
-       unsigned int binh = 1, binv = 1;
-       int best = scaling_goodness(
-               subdev,
-               crops[SMIAPP_PAD_SINK]->width, sel->r.width,
-               crops[SMIAPP_PAD_SINK]->height, sel->r.height, sel->flags);
-
-       for (i = 0; i < sensor->nbinning_subtypes; i++) {
-               int this = scaling_goodness(
-                       subdev,
-                       crops[SMIAPP_PAD_SINK]->width
-                       / sensor->binning_subtypes[i].horizontal,
-                       sel->r.width,
-                       crops[SMIAPP_PAD_SINK]->height
-                       / sensor->binning_subtypes[i].vertical,
-                       sel->r.height, sel->flags);
-
-               if (this > best) {
-                       binh = sensor->binning_subtypes[i].horizontal;
-                       binv = sensor->binning_subtypes[i].vertical;
-                       best = this;
-               }
-       }
-       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
-               sensor->binning_vertical = binv;
-               sensor->binning_horizontal = binh;
-       }
-
-       sel->r.width = (crops[SMIAPP_PAD_SINK]->width / binh) & ~1;
-       sel->r.height = (crops[SMIAPP_PAD_SINK]->height / binv) & ~1;
-}
-
-/*
- * Calculate best scaling ratio and mode for given output resolution.
- *
- * Try all of these: horizontal ratio, vertical ratio and smallest
- * size possible (horizontally).
- *
- * Also try whether horizontal scaler or full scaler gives a better
- * result.
- */
-static void smiapp_set_compose_scaler(struct v4l2_subdev *subdev,
-                                     struct v4l2_subdev_pad_config *cfg,
-                                     struct v4l2_subdev_selection *sel,
-                                     struct v4l2_rect **crops,
-                                     struct v4l2_rect *comp)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(subdev);
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       u32 min, max, a, b, max_m;
-       u32 scale_m = CCS_LIM(sensor, SCALER_N_MIN);
-       int mode = CCS_SCALING_MODE_HORIZONTAL;
-       u32 try[4];
-       u32 ntry = 0;
-       unsigned int i;
-       int best = INT_MIN;
-
-       sel->r.width = min_t(unsigned int, sel->r.width,
-                            crops[SMIAPP_PAD_SINK]->width);
-       sel->r.height = min_t(unsigned int, sel->r.height,
-                             crops[SMIAPP_PAD_SINK]->height);
-
-       a = crops[SMIAPP_PAD_SINK]->width
-               * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.width;
-       b = crops[SMIAPP_PAD_SINK]->height
-               * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.height;
-       max_m = crops[SMIAPP_PAD_SINK]->width
-               * CCS_LIM(sensor, SCALER_N_MIN)
-               / CCS_LIM(sensor, MIN_X_OUTPUT_SIZE);
-
-       a = clamp(a, CCS_LIM(sensor, SCALER_M_MIN),
-                 CCS_LIM(sensor, SCALER_M_MAX));
-       b = clamp(b, CCS_LIM(sensor, SCALER_M_MIN),
-                 CCS_LIM(sensor, SCALER_M_MAX));
-       max_m = clamp(max_m, CCS_LIM(sensor, SCALER_M_MIN),
-                     CCS_LIM(sensor, SCALER_M_MAX));
-
-       dev_dbg(&client->dev, "scaling: a %d b %d max_m %d\n", a, b, max_m);
-
-       min = min(max_m, min(a, b));
-       max = min(max_m, max(a, b));
-
-       try[ntry] = min;
-       ntry++;
-       if (min != max) {
-               try[ntry] = max;
-               ntry++;
-       }
-       if (max != max_m) {
-               try[ntry] = min + 1;
-               ntry++;
-               if (min != max) {
-                       try[ntry] = max + 1;
-                       ntry++;
-               }
-       }
-
-       for (i = 0; i < ntry; i++) {
-               int this = scaling_goodness(
-                       subdev,
-                       crops[SMIAPP_PAD_SINK]->width
-                       / try[i] * CCS_LIM(sensor, SCALER_N_MIN),
-                       sel->r.width,
-                       crops[SMIAPP_PAD_SINK]->height,
-                       sel->r.height,
-                       sel->flags);
-
-               dev_dbg(&client->dev, "trying factor %d (%d)\n", try[i], i);
-
-               if (this > best) {
-                       scale_m = try[i];
-                       mode = CCS_SCALING_MODE_HORIZONTAL;
-                       best = this;
-               }
-
-               if (CCS_LIM(sensor, SCALING_CAPABILITY)
-                   == CCS_SCALING_CAPABILITY_HORIZONTAL)
-                       continue;
-
-               this = scaling_goodness(
-                       subdev, crops[SMIAPP_PAD_SINK]->width
-                       / try[i]
-                       * CCS_LIM(sensor, SCALER_N_MIN),
-                       sel->r.width,
-                       crops[SMIAPP_PAD_SINK]->height
-                       / try[i]
-                       * CCS_LIM(sensor, SCALER_N_MIN),
-                       sel->r.height,
-                       sel->flags);
-
-               if (this > best) {
-                       scale_m = try[i];
-                       mode = SMIAPP_SCALING_MODE_BOTH;
-                       best = this;
-               }
-       }
-
-       sel->r.width =
-               (crops[SMIAPP_PAD_SINK]->width
-                / scale_m
-                * CCS_LIM(sensor, SCALER_N_MIN)) & ~1;
-       if (mode == SMIAPP_SCALING_MODE_BOTH)
-               sel->r.height =
-                       (crops[SMIAPP_PAD_SINK]->height
-                        / scale_m
-                        * CCS_LIM(sensor, SCALER_N_MIN))
-                       & ~1;
-       else
-               sel->r.height = crops[SMIAPP_PAD_SINK]->height;
-
-       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
-               sensor->scale_m = scale_m;
-               sensor->scaling_mode = mode;
-       }
-}
-/* We're only called on source pads. This function sets scaling. */
-static int smiapp_set_compose(struct v4l2_subdev *subdev,
-                             struct v4l2_subdev_pad_config *cfg,
-                             struct v4l2_subdev_selection *sel)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-       struct v4l2_rect *comp, *crops[SMIAPP_PADS];
-
-       smiapp_get_crop_compose(subdev, cfg, crops, &comp, sel->which);
-
-       sel->r.top = 0;
-       sel->r.left = 0;
-
-       if (ssd == sensor->binner)
-               smiapp_set_compose_binner(subdev, cfg, sel, crops, comp);
-       else
-               smiapp_set_compose_scaler(subdev, cfg, sel, crops, comp);
-
-       *comp = sel->r;
-       smiapp_propagate(subdev, cfg, sel->which, V4L2_SEL_TGT_COMPOSE);
-
-       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE)
-               return smiapp_pll_blanking_update(sensor);
-
-       return 0;
-}
-
-static int __smiapp_sel_supported(struct v4l2_subdev *subdev,
-                                 struct v4l2_subdev_selection *sel)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-
-       /* We only implement crop in three places. */
-       switch (sel->target) {
-       case V4L2_SEL_TGT_CROP:
-       case V4L2_SEL_TGT_CROP_BOUNDS:
-               if (ssd == sensor->pixel_array
-                   && sel->pad == SMIAPP_PA_PAD_SRC)
-                       return 0;
-               if (ssd == sensor->src
-                   && sel->pad == SMIAPP_PAD_SRC)
-                       return 0;
-               if (ssd == sensor->scaler && sel->pad == SMIAPP_PAD_SINK &&
-                   CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY)
-                   == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP)
-                       return 0;
-               return -EINVAL;
-       case V4L2_SEL_TGT_NATIVE_SIZE:
-               if (ssd == sensor->pixel_array
-                   && sel->pad == SMIAPP_PA_PAD_SRC)
-                       return 0;
-               return -EINVAL;
-       case V4L2_SEL_TGT_COMPOSE:
-       case V4L2_SEL_TGT_COMPOSE_BOUNDS:
-               if (sel->pad == ssd->source_pad)
-                       return -EINVAL;
-               if (ssd == sensor->binner)
-                       return 0;
-               if (ssd == sensor->scaler && CCS_LIM(sensor, SCALING_CAPABILITY)
-                   != CCS_SCALING_CAPABILITY_NONE)
-                       return 0;
-               fallthrough;
-       default:
-               return -EINVAL;
-       }
-}
-
-static int smiapp_set_crop(struct v4l2_subdev *subdev,
-                          struct v4l2_subdev_pad_config *cfg,
-                          struct v4l2_subdev_selection *sel)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-       struct v4l2_rect *src_size, *crops[SMIAPP_PADS];
-       struct v4l2_rect _r;
-
-       smiapp_get_crop_compose(subdev, cfg, crops, NULL, sel->which);
-
-       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
-               if (sel->pad == ssd->sink_pad)
-                       src_size = &ssd->sink_fmt;
-               else
-                       src_size = &ssd->compose;
-       } else {
-               if (sel->pad == ssd->sink_pad) {
-                       _r.left = 0;
-                       _r.top = 0;
-                       _r.width = v4l2_subdev_get_try_format(subdev, cfg, sel->pad)
-                               ->width;
-                       _r.height = v4l2_subdev_get_try_format(subdev, cfg, sel->pad)
-                               ->height;
-                       src_size = &_r;
-               } else {
-                       src_size = v4l2_subdev_get_try_compose(
-                               subdev, cfg, ssd->sink_pad);
-               }
-       }
-
-       if (ssd == sensor->src && sel->pad == SMIAPP_PAD_SRC) {
-               sel->r.left = 0;
-               sel->r.top = 0;
-       }
-
-       sel->r.width = min(sel->r.width, src_size->width);
-       sel->r.height = min(sel->r.height, src_size->height);
-
-       sel->r.left = min_t(int, sel->r.left, src_size->width - sel->r.width);
-       sel->r.top = min_t(int, sel->r.top, src_size->height - sel->r.height);
-
-       *crops[sel->pad] = sel->r;
-
-       if (ssd != sensor->pixel_array && sel->pad == SMIAPP_PAD_SINK)
-               smiapp_propagate(subdev, cfg, sel->which,
-                                V4L2_SEL_TGT_CROP);
-
-       return 0;
-}
-
-static void smiapp_get_native_size(struct smiapp_subdev *ssd,
-                                   struct v4l2_rect *r)
-{
-       r->top = 0;
-       r->left = 0;
-       r->width = CCS_LIM(ssd->sensor, X_ADDR_MAX) + 1;
-       r->height = CCS_LIM(ssd->sensor, Y_ADDR_MAX) + 1;
-}
-
-static int __smiapp_get_selection(struct v4l2_subdev *subdev,
-                                 struct v4l2_subdev_pad_config *cfg,
-                                 struct v4l2_subdev_selection *sel)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct smiapp_subdev *ssd = to_smiapp_subdev(subdev);
-       struct v4l2_rect *comp, *crops[SMIAPP_PADS];
-       struct v4l2_rect sink_fmt;
-       int ret;
-
-       ret = __smiapp_sel_supported(subdev, sel);
-       if (ret)
-               return ret;
-
-       smiapp_get_crop_compose(subdev, cfg, crops, &comp, sel->which);
-
-       if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
-               sink_fmt = ssd->sink_fmt;
-       } else {
-               struct v4l2_mbus_framefmt *fmt =
-                       v4l2_subdev_get_try_format(subdev, cfg, ssd->sink_pad);
-
-               sink_fmt.left = 0;
-               sink_fmt.top = 0;
-               sink_fmt.width = fmt->width;
-               sink_fmt.height = fmt->height;
-       }
-
-       switch (sel->target) {
-       case V4L2_SEL_TGT_CROP_BOUNDS:
-       case V4L2_SEL_TGT_NATIVE_SIZE:
-               if (ssd == sensor->pixel_array)
-                       smiapp_get_native_size(ssd, &sel->r);
-               else if (sel->pad == ssd->sink_pad)
-                       sel->r = sink_fmt;
-               else
-                       sel->r = *comp;
-               break;
-       case V4L2_SEL_TGT_CROP:
-       case V4L2_SEL_TGT_COMPOSE_BOUNDS:
-               sel->r = *crops[sel->pad];
-               break;
-       case V4L2_SEL_TGT_COMPOSE:
-               sel->r = *comp;
-               break;
-       }
-
-       return 0;
-}
-
-static int smiapp_get_selection(struct v4l2_subdev *subdev,
-                               struct v4l2_subdev_pad_config *cfg,
-                               struct v4l2_subdev_selection *sel)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       int rval;
-
-       mutex_lock(&sensor->mutex);
-       rval = __smiapp_get_selection(subdev, cfg, sel);
-       mutex_unlock(&sensor->mutex);
-
-       return rval;
-}
-static int smiapp_set_selection(struct v4l2_subdev *subdev,
-                               struct v4l2_subdev_pad_config *cfg,
-                               struct v4l2_subdev_selection *sel)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       int ret;
-
-       ret = __smiapp_sel_supported(subdev, sel);
-       if (ret)
-               return ret;
-
-       mutex_lock(&sensor->mutex);
-
-       sel->r.left = max(0, sel->r.left & ~1);
-       sel->r.top = max(0, sel->r.top & ~1);
-       sel->r.width = SMIAPP_ALIGN_DIM(sel->r.width, sel->flags);
-       sel->r.height = SMIAPP_ALIGN_DIM(sel->r.height, sel->flags);
-
-       sel->r.width = max_t(unsigned int,
-                            CCS_LIM(sensor, MIN_X_OUTPUT_SIZE),
-                            sel->r.width);
-       sel->r.height = max_t(unsigned int,
-                             CCS_LIM(sensor, MIN_Y_OUTPUT_SIZE),
-                             sel->r.height);
-
-       switch (sel->target) {
-       case V4L2_SEL_TGT_CROP:
-               ret = smiapp_set_crop(subdev, cfg, sel);
-               break;
-       case V4L2_SEL_TGT_COMPOSE:
-               ret = smiapp_set_compose(subdev, cfg, sel);
-               break;
-       default:
-               ret = -EINVAL;
-       }
-
-       mutex_unlock(&sensor->mutex);
-       return ret;
-}
-
-static int smiapp_get_skip_frames(struct v4l2_subdev *subdev, u32 *frames)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-
-       *frames = sensor->frame_skip;
-       return 0;
-}
-
-static int smiapp_get_skip_top_lines(struct v4l2_subdev *subdev, u32 *lines)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-
-       *lines = sensor->image_start;
-
-       return 0;
-}
-
-/* -----------------------------------------------------------------------------
- * sysfs attributes
- */
-
-static ssize_t
-smiapp_sysfs_nvm_read(struct device *dev, struct device_attribute *attr,
-                     char *buf)
-{
-       struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev));
-       struct i2c_client *client = v4l2_get_subdevdata(subdev);
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       int rval;
-
-       if (!sensor->dev_init_done)
-               return -EBUSY;
-
-       rval = smiapp_pm_get_init(sensor);
-       if (rval < 0)
-               return -ENODEV;
-
-       rval = smiapp_read_nvm(sensor, buf, PAGE_SIZE);
-       if (rval < 0) {
-               pm_runtime_put(&client->dev);
-               dev_err(&client->dev, "nvm read failed\n");
-               return -ENODEV;
-       }
-
-       pm_runtime_mark_last_busy(&client->dev);
-       pm_runtime_put_autosuspend(&client->dev);
-
-       /*
-        * NVM is still way below a PAGE_SIZE, so we can safely
-        * assume this for now.
-        */
-       return rval;
-}
-static DEVICE_ATTR(nvm, S_IRUGO, smiapp_sysfs_nvm_read, NULL);
-
-static ssize_t
-smiapp_sysfs_ident_read(struct device *dev, struct device_attribute *attr,
-                       char *buf)
-{
-       struct v4l2_subdev *subdev = i2c_get_clientdata(to_i2c_client(dev));
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       struct smiapp_module_info *minfo = &sensor->minfo;
-
-       if (minfo->mipi_manufacturer_id)
-               return snprintf(buf, PAGE_SIZE, "%4.4x%4.4x%2.2x\n",
-                               minfo->mipi_manufacturer_id, minfo->model_id,
-                               minfo->revision_number_major) + 1;
-       else
-               return snprintf(buf, PAGE_SIZE, "%2.2x%4.4x%2.2x\n",
-                               minfo->smia_manufacturer_id, minfo->model_id,
-                               minfo->revision_number_major) + 1;
-}
-
-static DEVICE_ATTR(ident, S_IRUGO, smiapp_sysfs_ident_read, NULL);
-
-/* -----------------------------------------------------------------------------
- * V4L2 subdev core operations
- */
-
-static int smiapp_identify_module(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       struct smiapp_module_info *minfo = &sensor->minfo;
-       unsigned int i;
-       int rval = 0;
-
-       minfo->name = SMIAPP_NAME;
-
-       /* Module info */
-       rval = ccs_read(sensor, MODULE_MANUFACTURER_ID,
-                       &minfo->mipi_manufacturer_id);
-       if (!rval && !minfo->mipi_manufacturer_id)
-               rval = ccs_read_addr_8only(sensor,
-                                          SMIAPP_REG_U8_MANUFACTURER_ID,
-                                          &minfo->smia_manufacturer_id);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor, CCS_R_MODULE_MODEL_ID,
-                                          &minfo->model_id);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor,
-                                          CCS_R_MODULE_REVISION_NUMBER_MAJOR,
-                                          &minfo->revision_number_major);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor,
-                                          CCS_R_MODULE_REVISION_NUMBER_MINOR,
-                                          &minfo->revision_number_minor);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor, CCS_R_MODULE_DATE_YEAR,
-                                          &minfo->module_year);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor, CCS_R_MODULE_DATE_MONTH,
-                                          &minfo->module_month);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor, CCS_R_MODULE_DATE_DAY,
-                                          &minfo->module_day);
-
-       /* Sensor info */
-       if (!rval)
-               rval = ccs_read(sensor, SENSOR_MANUFACTURER_ID,
-                               &minfo->sensor_mipi_manufacturer_id);
-       if (!rval && !minfo->sensor_mipi_manufacturer_id)
-               rval = ccs_read_addr_8only(sensor,
-                                          CCS_R_SENSOR_MANUFACTURER_ID,
-                                          &minfo->sensor_smia_manufacturer_id);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor,
-                                          CCS_R_SENSOR_MODEL_ID,
-                                          &minfo->sensor_model_id);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor,
-                                          CCS_R_SENSOR_REVISION_NUMBER,
-                                          &minfo->sensor_revision_number);
-       if (!rval)
-               rval = ccs_read_addr_8only(sensor,
-                                          CCS_R_SENSOR_FIRMWARE_VERSION,
-                                          &minfo->sensor_firmware_version);
-
-       /* SMIA */
-       if (!rval)
-               rval = ccs_read(sensor, MIPI_CCS_VERSION, &minfo->ccs_version);
-       if (!rval && !minfo->ccs_version)
-               rval = ccs_read_addr_8only(sensor, SMIAPP_REG_U8_SMIA_VERSION,
-                                          &minfo->smia_version);
-       if (!rval && !minfo->ccs_version)
-               rval = ccs_read_addr_8only(sensor, SMIAPP_REG_U8_SMIAPP_VERSION,
-                                          &minfo->smiapp_version);
-
-       if (rval) {
-               dev_err(&client->dev, "sensor detection failed\n");
-               return -ENODEV;
-       }
-
-       if (minfo->mipi_manufacturer_id)
-               dev_dbg(&client->dev, "MIPI CCS module 0x%4.4x-0x%4.4x\n",
-                       minfo->mipi_manufacturer_id, minfo->model_id);
-       else
-               dev_dbg(&client->dev, "SMIA module 0x%2.2x-0x%4.4x\n",
-                       minfo->smia_manufacturer_id, minfo->model_id);
-
-       dev_dbg(&client->dev,
-               "module revision 0x%2.2x-0x%2.2x date %2.2d-%2.2d-%2.2d\n",
-               minfo->revision_number_major, minfo->revision_number_minor,
-               minfo->module_year, minfo->module_month, minfo->module_day);
-
-       if (minfo->sensor_mipi_manufacturer_id)
-               dev_dbg(&client->dev, "MIPI CCS sensor 0x%4.4x-0x%4.4x\n",
-                       minfo->sensor_mipi_manufacturer_id,
-                       minfo->sensor_model_id);
-       else
-               dev_dbg(&client->dev, "SMIA sensor 0x%2.2x-0x%4.4x\n",
-                       minfo->sensor_smia_manufacturer_id,
-                       minfo->sensor_model_id);
-
-       dev_dbg(&client->dev,
-               "sensor revision 0x%2.2x firmware version 0x%2.2x\n",
-               minfo->sensor_revision_number, minfo->sensor_firmware_version);
-
-       if (minfo->ccs_version)
-               dev_dbg(&client->dev, "MIPI CCS version %u.%u",
-                       (minfo->ccs_version & CCS_MIPI_CCS_VERSION_MAJOR_MASK)
-                       >> CCS_MIPI_CCS_VERSION_MAJOR_SHIFT,
-                       (minfo->ccs_version & CCS_MIPI_CCS_VERSION_MINOR_MASK));
-       else
-               dev_dbg(&client->dev,
-                       "smia version %2.2d smiapp version %2.2d\n",
-                       minfo->smia_version, minfo->smiapp_version);
-
-       /*
-        * Some modules have bad data in the lvalues below. Hope the
-        * rvalues have better stuff. The lvalues are module
-        * parameters whereas the rvalues are sensor parameters.
-        */
-       if (minfo->sensor_smia_manufacturer_id &&
-           !minfo->smia_manufacturer_id && !minfo->model_id) {
-               minfo->smia_manufacturer_id =
-                       minfo->sensor_smia_manufacturer_id;
-               minfo->model_id = minfo->sensor_model_id;
-               minfo->revision_number_major = minfo->sensor_revision_number;
-       }
-
-       for (i = 0; i < ARRAY_SIZE(smiapp_module_idents); i++) {
-               if (smiapp_module_idents[i].mipi_manufacturer_id &&
-                   smiapp_module_idents[i].mipi_manufacturer_id
-                   != minfo->mipi_manufacturer_id)
-                       continue;
-               if (smiapp_module_idents[i].smia_manufacturer_id &&
-                   smiapp_module_idents[i].smia_manufacturer_id
-                   != minfo->smia_manufacturer_id)
-                       continue;
-               if (smiapp_module_idents[i].model_id != minfo->model_id)
-                       continue;
-               if (smiapp_module_idents[i].flags
-                   & SMIAPP_MODULE_IDENT_FLAG_REV_LE) {
-                       if (smiapp_module_idents[i].revision_number_major
-                           < minfo->revision_number_major)
-                               continue;
-               } else {
-                       if (smiapp_module_idents[i].revision_number_major
-                           != minfo->revision_number_major)
-                               continue;
-               }
-
-               minfo->name = smiapp_module_idents[i].name;
-               minfo->quirk = smiapp_module_idents[i].quirk;
-               break;
-       }
-
-       if (i >= ARRAY_SIZE(smiapp_module_idents))
-               dev_warn(&client->dev,
-                        "no quirks for this module; let's hope it's fully compliant\n");
-
-       dev_dbg(&client->dev, "the sensor is called %s\n",
-               minfo->name);
-
-       return 0;
-}
-
-static const struct v4l2_subdev_ops smiapp_ops;
-static const struct v4l2_subdev_internal_ops smiapp_internal_ops;
-static const struct media_entity_operations smiapp_entity_ops;
-
-static int smiapp_register_subdev(struct smiapp_sensor *sensor,
-                                 struct smiapp_subdev *ssd,
-                                 struct smiapp_subdev *sink_ssd,
-                                 u16 source_pad, u16 sink_pad, u32 link_flags)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int rval;
-
-       if (!sink_ssd)
-               return 0;
-
-       rval = media_entity_pads_init(&ssd->sd.entity,
-                                     ssd->npads, ssd->pads);
-       if (rval) {
-               dev_err(&client->dev,
-                       "media_entity_pads_init failed\n");
-               return rval;
-       }
-
-       rval = v4l2_device_register_subdev(sensor->src->sd.v4l2_dev,
-                                          &ssd->sd);
-       if (rval) {
-               dev_err(&client->dev,
-                       "v4l2_device_register_subdev failed\n");
-               return rval;
-       }
-
-       rval = media_create_pad_link(&ssd->sd.entity, source_pad,
-                                    &sink_ssd->sd.entity, sink_pad,
-                                    link_flags);
-       if (rval) {
-               dev_err(&client->dev,
-                       "media_create_pad_link failed\n");
-               v4l2_device_unregister_subdev(&ssd->sd);
-               return rval;
-       }
-
-       return 0;
-}
-
-static void smiapp_unregistered(struct v4l2_subdev *subdev)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       unsigned int i;
-
-       for (i = 1; i < sensor->ssds_used; i++)
-               v4l2_device_unregister_subdev(&sensor->ssds[i].sd);
-}
-
-static int smiapp_registered(struct v4l2_subdev *subdev)
-{
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       int rval;
-
-       if (sensor->scaler) {
-               rval = smiapp_register_subdev(
-                       sensor, sensor->binner, sensor->scaler,
-                       SMIAPP_PAD_SRC, SMIAPP_PAD_SINK,
-                       MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
-               if (rval < 0)
-                       return rval;
-       }
-
-       rval = smiapp_register_subdev(
-               sensor, sensor->pixel_array, sensor->binner,
-               SMIAPP_PA_PAD_SRC, SMIAPP_PAD_SINK,
-               MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
-       if (rval)
-               goto out_err;
-
-       return 0;
-
-out_err:
-       smiapp_unregistered(subdev);
-
-       return rval;
-}
-
-static void smiapp_cleanup(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-
-       device_remove_file(&client->dev, &dev_attr_nvm);
-       device_remove_file(&client->dev, &dev_attr_ident);
-
-       smiapp_free_controls(sensor);
-}
-
-static void smiapp_create_subdev(struct smiapp_sensor *sensor,
-                                struct smiapp_subdev *ssd, const char *name,
-                                unsigned short num_pads)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-
-       if (!ssd)
-               return;
-
-       if (ssd != sensor->src)
-               v4l2_subdev_init(&ssd->sd, &smiapp_ops);
-
-       ssd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
-       ssd->sensor = sensor;
-
-       ssd->npads = num_pads;
-       ssd->source_pad = num_pads - 1;
-
-       v4l2_i2c_subdev_set_name(&ssd->sd, client, sensor->minfo.name, name);
-
-       smiapp_get_native_size(ssd, &ssd->sink_fmt);
-
-       ssd->compose.width = ssd->sink_fmt.width;
-       ssd->compose.height = ssd->sink_fmt.height;
-       ssd->crop[ssd->source_pad] = ssd->compose;
-       ssd->pads[ssd->source_pad].flags = MEDIA_PAD_FL_SOURCE;
-       if (ssd != sensor->pixel_array) {
-               ssd->crop[ssd->sink_pad] = ssd->compose;
-               ssd->pads[ssd->sink_pad].flags = MEDIA_PAD_FL_SINK;
-       }
-
-       ssd->sd.entity.ops = &smiapp_entity_ops;
-
-       if (ssd == sensor->src)
-               return;
-
-       ssd->sd.internal_ops = &smiapp_internal_ops;
-       ssd->sd.owner = THIS_MODULE;
-       ssd->sd.dev = &client->dev;
-       v4l2_set_subdevdata(&ssd->sd, client);
-}
-
-static int smiapp_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
-{
-       struct smiapp_subdev *ssd = to_smiapp_subdev(sd);
-       struct smiapp_sensor *sensor = ssd->sensor;
-       unsigned int i;
-
-       mutex_lock(&sensor->mutex);
-
-       for (i = 0; i < ssd->npads; i++) {
-               struct v4l2_mbus_framefmt *try_fmt =
-                       v4l2_subdev_get_try_format(sd, fh->pad, i);
-               struct v4l2_rect *try_crop =
-                       v4l2_subdev_get_try_crop(sd, fh->pad, i);
-               struct v4l2_rect *try_comp;
-
-               smiapp_get_native_size(ssd, try_crop);
-
-               try_fmt->width = try_crop->width;
-               try_fmt->height = try_crop->height;
-               try_fmt->code = sensor->internal_csi_format->code;
-               try_fmt->field = V4L2_FIELD_NONE;
-
-               if (ssd != sensor->pixel_array)
-                       continue;
-
-               try_comp = v4l2_subdev_get_try_compose(sd, fh->pad, i);
-               *try_comp = *try_crop;
-       }
-
-       mutex_unlock(&sensor->mutex);
-
-       return 0;
-}
-
-static const struct v4l2_subdev_video_ops smiapp_video_ops = {
-       .s_stream = smiapp_set_stream,
-};
-
-static const struct v4l2_subdev_pad_ops smiapp_pad_ops = {
-       .enum_mbus_code = smiapp_enum_mbus_code,
-       .get_fmt = smiapp_get_format,
-       .set_fmt = smiapp_set_format,
-       .get_selection = smiapp_get_selection,
-       .set_selection = smiapp_set_selection,
-};
-
-static const struct v4l2_subdev_sensor_ops smiapp_sensor_ops = {
-       .g_skip_frames = smiapp_get_skip_frames,
-       .g_skip_top_lines = smiapp_get_skip_top_lines,
-};
-
-static const struct v4l2_subdev_ops smiapp_ops = {
-       .video = &smiapp_video_ops,
-       .pad = &smiapp_pad_ops,
-       .sensor = &smiapp_sensor_ops,
-};
-
-static const struct media_entity_operations smiapp_entity_ops = {
-       .link_validate = v4l2_subdev_link_validate,
-};
-
-static const struct v4l2_subdev_internal_ops smiapp_internal_src_ops = {
-       .registered = smiapp_registered,
-       .unregistered = smiapp_unregistered,
-       .open = smiapp_open,
-};
-
-static const struct v4l2_subdev_internal_ops smiapp_internal_ops = {
-       .open = smiapp_open,
-};
-
-/* -----------------------------------------------------------------------------
- * I2C Driver
- */
-
-static int __maybe_unused smiapp_suspend(struct device *dev)
-{
-       struct i2c_client *client = to_i2c_client(dev);
-       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       bool streaming = sensor->streaming;
-       int rval;
-
-       rval = pm_runtime_get_sync(dev);
-       if (rval < 0) {
-               if (rval != -EBUSY && rval != -EAGAIN)
-                       pm_runtime_set_active(&client->dev);
-               pm_runtime_put(dev);
-               return -EAGAIN;
-       }
-
-       if (sensor->streaming)
-               smiapp_stop_streaming(sensor);
-
-       /* save state for resume */
-       sensor->streaming = streaming;
-
-       return 0;
-}
-
-static int __maybe_unused smiapp_resume(struct device *dev)
-{
-       struct i2c_client *client = to_i2c_client(dev);
-       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       int rval = 0;
-
-       pm_runtime_put(dev);
-
-       if (sensor->streaming)
-               rval = smiapp_start_streaming(sensor);
-
-       return rval;
-}
-
-static struct smiapp_hwconfig *smiapp_get_hwconfig(struct device *dev)
-{
-       struct smiapp_hwconfig *hwcfg;
-       struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 };
-       struct fwnode_handle *ep;
-       struct fwnode_handle *fwnode = dev_fwnode(dev);
-       u32 rotation;
-       int i;
-       int rval;
-
-       if (!fwnode)
-               return dev->platform_data;
-
-       ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
-       if (!ep)
-               return NULL;
-
-       bus_cfg.bus_type = V4L2_MBUS_CSI2_DPHY;
-       rval = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
-       if (rval == -ENXIO) {
-               bus_cfg = (struct v4l2_fwnode_endpoint)
-                       { .bus_type = V4L2_MBUS_CCP2 };
-               rval = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
-       }
-       if (rval)
-               goto out_err;
-
-       hwcfg = devm_kzalloc(dev, sizeof(*hwcfg), GFP_KERNEL);
-       if (!hwcfg)
-               goto out_err;
-
-       switch (bus_cfg.bus_type) {
-       case V4L2_MBUS_CSI2_DPHY:
-               hwcfg->csi_signalling_mode = CCS_CSI_SIGNALING_MODE_CSI_2_DPHY;
-               hwcfg->lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
-               break;
-       case V4L2_MBUS_CCP2:
-               hwcfg->csi_signalling_mode = (bus_cfg.bus.mipi_csi1.strobe) ?
-               SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE :
-               SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK;
-               hwcfg->lanes = 1;
-               break;
-       default:
-               dev_err(dev, "unsupported bus %u\n", bus_cfg.bus_type);
-               goto out_err;
-       }
-
-       dev_dbg(dev, "lanes %u\n", hwcfg->lanes);
-
-       rval = fwnode_property_read_u32(fwnode, "rotation", &rotation);
-       if (!rval) {
-               switch (rotation) {
-               case 180:
-                       hwcfg->module_board_orient =
-                               SMIAPP_MODULE_BOARD_ORIENT_180;
-                       fallthrough;
-               case 0:
-                       break;
-               default:
-                       dev_err(dev, "invalid rotation %u\n", rotation);
-                       goto out_err;
-               }
-       }
-
-       rval = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
-                                       &hwcfg->ext_clk);
-       if (rval)
-               dev_info(dev, "can't get clock-frequency\n");
-
-       dev_dbg(dev, "clk %d, mode %d\n", hwcfg->ext_clk,
-               hwcfg->csi_signalling_mode);
-
-       if (!bus_cfg.nr_of_link_frequencies) {
-               dev_warn(dev, "no link frequencies defined\n");
-               goto out_err;
-       }
-
-       hwcfg->op_sys_clock = devm_kcalloc(
-               dev, bus_cfg.nr_of_link_frequencies + 1 /* guardian */,
-               sizeof(*hwcfg->op_sys_clock), GFP_KERNEL);
-       if (!hwcfg->op_sys_clock)
-               goto out_err;
-
-       for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) {
-               hwcfg->op_sys_clock[i] = bus_cfg.link_frequencies[i];
-               dev_dbg(dev, "freq %d: %lld\n", i, hwcfg->op_sys_clock[i]);
-       }
-
-       v4l2_fwnode_endpoint_free(&bus_cfg);
-       fwnode_handle_put(ep);
-       return hwcfg;
-
-out_err:
-       v4l2_fwnode_endpoint_free(&bus_cfg);
-       fwnode_handle_put(ep);
-       return NULL;
-}
-
-static int smiapp_probe(struct i2c_client *client)
-{
-       struct smiapp_sensor *sensor;
-       struct smiapp_hwconfig *hwcfg = smiapp_get_hwconfig(&client->dev);
-       unsigned int i;
-       int rval;
-
-       if (hwcfg == NULL)
-               return -ENODEV;
-
-       sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
-       if (sensor == NULL)
-               return -ENOMEM;
-
-       sensor->hwcfg = hwcfg;
-       sensor->src = &sensor->ssds[sensor->ssds_used];
-
-       v4l2_i2c_subdev_init(&sensor->src->sd, client, &smiapp_ops);
-       sensor->src->sd.internal_ops = &smiapp_internal_src_ops;
-
-       sensor->vana = devm_regulator_get(&client->dev, "vana");
-       if (IS_ERR(sensor->vana)) {
-               dev_err(&client->dev, "could not get regulator for vana\n");
-               return PTR_ERR(sensor->vana);
-       }
-
-       sensor->ext_clk = devm_clk_get(&client->dev, NULL);
-       if (PTR_ERR(sensor->ext_clk) == -ENOENT) {
-               dev_info(&client->dev, "no clock defined, continuing...\n");
-               sensor->ext_clk = NULL;
-       } else if (IS_ERR(sensor->ext_clk)) {
-               dev_err(&client->dev, "could not get clock (%ld)\n",
-                       PTR_ERR(sensor->ext_clk));
-               return -EPROBE_DEFER;
-       }
-
-       if (sensor->ext_clk) {
-               if (sensor->hwcfg->ext_clk) {
-                       unsigned long rate;
-
-                       rval = clk_set_rate(sensor->ext_clk,
-                                           sensor->hwcfg->ext_clk);
-                       if (rval < 0) {
-                               dev_err(&client->dev,
-                                       "unable to set clock freq to %u\n",
-                                       sensor->hwcfg->ext_clk);
-                               return rval;
-                       }
-
-                       rate = clk_get_rate(sensor->ext_clk);
-                       if (rate != sensor->hwcfg->ext_clk) {
-                               dev_err(&client->dev,
-                                       "can't set clock freq, asked for %u but got %lu\n",
-                                       sensor->hwcfg->ext_clk, rate);
-                               return rval;
-                       }
-               } else {
-                       sensor->hwcfg->ext_clk = clk_get_rate(sensor->ext_clk);
-                       dev_dbg(&client->dev, "obtained clock freq %u\n",
-                               sensor->hwcfg->ext_clk);
-               }
-       } else if (sensor->hwcfg->ext_clk) {
-               dev_dbg(&client->dev, "assuming clock freq %u\n",
-                       sensor->hwcfg->ext_clk);
-       } else {
-               dev_err(&client->dev, "unable to obtain clock freq\n");
-               return -EINVAL;
-       }
-
-       sensor->xshutdown = devm_gpiod_get_optional(&client->dev, "xshutdown",
-                                                   GPIOD_OUT_LOW);
-       if (IS_ERR(sensor->xshutdown))
-               return PTR_ERR(sensor->xshutdown);
-
-       rval = smiapp_power_on(&client->dev);
-       if (rval < 0)
-               return rval;
-
-       mutex_init(&sensor->mutex);
-
-       rval = smiapp_identify_module(sensor);
-       if (rval) {
-               rval = -ENODEV;
-               goto out_power_off;
-       }
-
-       rval = ccs_read_all_limits(sensor);
-       if (rval)
-               goto out_power_off;
-
-       rval = smiapp_read_frame_fmt(sensor);
-       if (rval) {
-               rval = -ENODEV;
-               goto out_free_ccs_limits;
-       }
-
-       /*
-        * Handle Sensor Module orientation on the board.
-        *
-        * The application of H-FLIP and V-FLIP on the sensor is modified by
-        * the sensor orientation on the board.
-        *
-        * For SMIAPP_BOARD_SENSOR_ORIENT_180 the default behaviour is to set
-        * both H-FLIP and V-FLIP for normal operation which also implies
-        * that a set/unset operation for user space HFLIP and VFLIP v4l2
-        * controls will need to be internally inverted.
-        *
-        * Rotation also changes the bayer pattern.
-        */
-       if (sensor->hwcfg->module_board_orient ==
-           SMIAPP_MODULE_BOARD_ORIENT_180)
-               sensor->hvflip_inv_mask =
-                       CCS_IMAGE_ORIENTATION_HORIZONTAL_MIRROR |
-                       CCS_IMAGE_ORIENTATION_VERTICAL_FLIP;
-
-       rval = smiapp_call_quirk(sensor, limits);
-       if (rval) {
-               dev_err(&client->dev, "limits quirks failed\n");
-               goto out_free_ccs_limits;
-       }
-
-       if (CCS_LIM(sensor, BINNING_CAPABILITY)) {
-               sensor->nbinning_subtypes =
-                       min_t(u8, CCS_LIM(sensor, BINNING_SUB_TYPES),
-                             CCS_LIM_BINNING_SUB_TYPE_MAX_N);
-
-               for (i = 0; i < sensor->nbinning_subtypes; i++) {
-                       sensor->binning_subtypes[i].horizontal =
-                               CCS_LIM_AT(sensor, BINNING_SUB_TYPE, i) >>
-                               CCS_BINNING_SUB_TYPE_COLUMN_SHIFT;
-                       sensor->binning_subtypes[i].vertical =
-                               CCS_LIM_AT(sensor, BINNING_SUB_TYPE, i) &
-                               CCS_BINNING_SUB_TYPE_ROW_MASK;
-
-                       dev_dbg(&client->dev, "binning %xx%x\n",
-                               sensor->binning_subtypes[i].horizontal,
-                               sensor->binning_subtypes[i].vertical);
-               }
-       }
-       sensor->binning_horizontal = 1;
-       sensor->binning_vertical = 1;
-
-       if (device_create_file(&client->dev, &dev_attr_ident) != 0) {
-               dev_err(&client->dev, "sysfs ident entry creation failed\n");
-               rval = -ENOENT;
-               goto out_free_ccs_limits;
-       }
-
-       if (sensor->minfo.smiapp_version &&
-           CCS_LIM(sensor, DATA_TRANSFER_IF_CAPABILITY) &
-           CCS_DATA_TRANSFER_IF_CAPABILITY_SUPPORTED) {
-               if (device_create_file(&client->dev, &dev_attr_nvm) != 0) {
-                       dev_err(&client->dev, "sysfs nvm entry failed\n");
-                       rval = -EBUSY;
-                       goto out_cleanup;
-               }
-       }
-
-       /* We consider this as profile 0 sensor if any of these are zero. */
-       if (!CCS_LIM(sensor, MIN_OP_SYS_CLK_DIV) ||
-           !CCS_LIM(sensor, MAX_OP_SYS_CLK_DIV) ||
-           !CCS_LIM(sensor, MIN_OP_PIX_CLK_DIV) ||
-           !CCS_LIM(sensor, MAX_OP_PIX_CLK_DIV)) {
-               sensor->minfo.smiapp_profile = SMIAPP_PROFILE_0;
-       } else if (CCS_LIM(sensor, SCALING_CAPABILITY)
-                  != CCS_SCALING_CAPABILITY_NONE) {
-               if (CCS_LIM(sensor, SCALING_CAPABILITY)
-                   == CCS_SCALING_CAPABILITY_HORIZONTAL)
-                       sensor->minfo.smiapp_profile = SMIAPP_PROFILE_1;
-               else
-                       sensor->minfo.smiapp_profile = SMIAPP_PROFILE_2;
-               sensor->scaler = &sensor->ssds[sensor->ssds_used];
-               sensor->ssds_used++;
-       } else if (CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY)
-                  == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) {
-               sensor->scaler = &sensor->ssds[sensor->ssds_used];
-               sensor->ssds_used++;
-       }
-       sensor->binner = &sensor->ssds[sensor->ssds_used];
-       sensor->ssds_used++;
-       sensor->pixel_array = &sensor->ssds[sensor->ssds_used];
-       sensor->ssds_used++;
-
-       sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN);
-
-       /* prepare PLL configuration input values */
-       sensor->pll.bus_type = SMIAPP_PLL_BUS_TYPE_CSI2;
-       sensor->pll.csi2.lanes = sensor->hwcfg->lanes;
-       sensor->pll.ext_clk_freq_hz = sensor->hwcfg->ext_clk;
-       sensor->pll.scale_n = CCS_LIM(sensor, SCALER_N_MIN);
-       /* Profile 0 sensors have no separate OP clock branch. */
-       if (sensor->minfo.smiapp_profile == SMIAPP_PROFILE_0)
-               sensor->pll.flags |= SMIAPP_PLL_FLAG_NO_OP_CLOCKS;
-
-       smiapp_create_subdev(sensor, sensor->scaler, " scaler", 2);
-       smiapp_create_subdev(sensor, sensor->binner, " binner", 2);
-       smiapp_create_subdev(sensor, sensor->pixel_array, " pixel_array", 1);
-
-       dev_dbg(&client->dev, "profile %d\n", sensor->minfo.smiapp_profile);
-
-       sensor->pixel_array->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
-
-       rval = smiapp_init_controls(sensor);
-       if (rval < 0)
-               goto out_cleanup;
-
-       rval = smiapp_call_quirk(sensor, init);
-       if (rval)
-               goto out_cleanup;
-
-       rval = smiapp_get_mbus_formats(sensor);
-       if (rval) {
-               rval = -ENODEV;
-               goto out_cleanup;
-       }
-
-       rval = smiapp_init_late_controls(sensor);
-       if (rval) {
-               rval = -ENODEV;
-               goto out_cleanup;
-       }
-
-       mutex_lock(&sensor->mutex);
-       rval = smiapp_pll_blanking_update(sensor);
-       mutex_unlock(&sensor->mutex);
-       if (rval) {
-               dev_err(&client->dev, "update mode failed\n");
-               goto out_cleanup;
-       }
-
-       sensor->streaming = false;
-       sensor->dev_init_done = true;
-
-       rval = media_entity_pads_init(&sensor->src->sd.entity, 2,
-                                sensor->src->pads);
-       if (rval < 0)
-               goto out_media_entity_cleanup;
-
-       pm_runtime_set_active(&client->dev);
-       pm_runtime_get_noresume(&client->dev);
-       pm_runtime_enable(&client->dev);
-
-       rval = v4l2_async_register_subdev_sensor_common(&sensor->src->sd);
-       if (rval < 0)
-               goto out_disable_runtime_pm;
-
-       pm_runtime_set_autosuspend_delay(&client->dev, 1000);
-       pm_runtime_use_autosuspend(&client->dev);
-       pm_runtime_put_autosuspend(&client->dev);
-
-       return 0;
-
-out_disable_runtime_pm:
-       pm_runtime_put_noidle(&client->dev);
-       pm_runtime_disable(&client->dev);
-
-out_media_entity_cleanup:
-       media_entity_cleanup(&sensor->src->sd.entity);
-
-out_cleanup:
-       smiapp_cleanup(sensor);
-
-out_free_ccs_limits:
-       kfree(sensor->ccs_limits);
-
-out_power_off:
-       smiapp_power_off(&client->dev);
-       mutex_destroy(&sensor->mutex);
-
-       return rval;
-}
-
-static int smiapp_remove(struct i2c_client *client)
-{
-       struct v4l2_subdev *subdev = i2c_get_clientdata(client);
-       struct smiapp_sensor *sensor = to_smiapp_sensor(subdev);
-       unsigned int i;
-
-       v4l2_async_unregister_subdev(subdev);
-
-       pm_runtime_disable(&client->dev);
-       if (!pm_runtime_status_suspended(&client->dev))
-               smiapp_power_off(&client->dev);
-       pm_runtime_set_suspended(&client->dev);
-
-       for (i = 0; i < sensor->ssds_used; i++) {
-               v4l2_device_unregister_subdev(&sensor->ssds[i].sd);
-               media_entity_cleanup(&sensor->ssds[i].sd.entity);
-       }
-       smiapp_cleanup(sensor);
-       mutex_destroy(&sensor->mutex);
-       kfree(sensor->ccs_limits);
-
-       return 0;
-}
-
-static const struct of_device_id smiapp_of_table[] = {
-       { .compatible = "nokia,smia" },
-       { },
-};
-MODULE_DEVICE_TABLE(of, smiapp_of_table);
-
-static const struct i2c_device_id smiapp_id_table[] = {
-       { SMIAPP_NAME, 0 },
-       { },
-};
-MODULE_DEVICE_TABLE(i2c, smiapp_id_table);
-
-static const struct dev_pm_ops smiapp_pm_ops = {
-       SET_SYSTEM_SLEEP_PM_OPS(smiapp_suspend, smiapp_resume)
-       SET_RUNTIME_PM_OPS(smiapp_power_off, smiapp_power_on, NULL)
-};
-
-static struct i2c_driver smiapp_i2c_driver = {
-       .driver = {
-               .of_match_table = smiapp_of_table,
-               .name = SMIAPP_NAME,
-               .pm = &smiapp_pm_ops,
-       },
-       .probe_new = smiapp_probe,
-       .remove = smiapp_remove,
-       .id_table = smiapp_id_table,
-};
-
-static int smiapp_module_init(void)
-{
-       unsigned int i, l;
-
-       for (i = 0, l = 0; ccs_limits[i].size && l < CCS_L_LAST; i++) {
-               if (!(ccs_limits[i].flags & CCS_L_FL_SAME_REG)) {
-                       ccs_limit_offsets[l + 1].lim =
-                               ALIGN(ccs_limit_offsets[l].lim +
-                                     ccs_limits[i].size,
-                                     ccs_reg_width(ccs_limits[i + 1].reg));
-                       ccs_limit_offsets[l].info = i;
-                       l++;
-               } else {
-                       ccs_limit_offsets[l].lim += ccs_limits[i].size;
-               }
-       }
-
-       if (WARN_ON(ccs_limits[i].size))
-               return -EINVAL;
-
-       if (WARN_ON(l != CCS_L_LAST))
-               return -EINVAL;
-
-       return i2c_register_driver(THIS_MODULE, &smiapp_i2c_driver);
-}
-
-static void smiapp_module_cleanup(void)
-{
-       i2c_del_driver(&smiapp_i2c_driver);
-}
-
-module_init(smiapp_module_init);
-module_exit(smiapp_module_cleanup);
-
-MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");
-MODULE_DESCRIPTION("Generic SMIA/SMIA++ camera module driver");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/smiapp/smiapp-quirk.c b/drivers/media/i2c/smiapp/smiapp-quirk.c
deleted file mode 100644 (file)
index 5db97a1..0000000
+++ /dev/null
@@ -1,215 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * drivers/media/i2c/smiapp/smiapp-quirk.c
- *
- * Generic driver for SMIA/SMIA++ compliant camera modules
- *
- * Copyright (C) 2011--2012 Nokia Corporation
- * Contact: Sakari Ailus <sakari.ailus@iki.fi>
- */
-
-#include <linux/delay.h>
-
-#include "ccs-limits.h"
-
-#include "smiapp.h"
-
-static int ccs_write_addr_8s(struct smiapp_sensor *sensor,
-                            const struct smiapp_reg_8 *regs, int len)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int rval;
-
-       for (; len > 0; len--, regs++) {
-               rval = ccs_write_addr(sensor, regs->reg, regs->val);
-               if (rval < 0) {
-                       dev_err(&client->dev,
-                               "error %d writing reg 0x%4.4x, val 0x%2.2x",
-                               rval, regs->reg, regs->val);
-                       return rval;
-               }
-       }
-
-       return 0;
-}
-
-static int jt8ew9_limits(struct smiapp_sensor *sensor)
-{
-       if (sensor->minfo.revision_number_major < 0x03)
-               sensor->frame_skip = 1;
-
-       /* Below 24 gain doesn't have effect at all, */
-       /* but ~59 is needed for full dynamic range */
-       ccs_replace_limit(sensor, CCS_L_ANALOG_GAIN_CODE_MIN, 0, 59);
-       ccs_replace_limit(sensor, CCS_L_ANALOG_GAIN_CODE_MAX, 0, 6000);
-
-       return 0;
-}
-
-static int jt8ew9_post_poweron(struct smiapp_sensor *sensor)
-{
-       static const struct smiapp_reg_8 regs[] = {
-               { 0x30a3, 0xd8 }, /* Output port control : LVDS ports only */
-               { 0x30ae, 0x00 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */
-               { 0x30af, 0xd0 }, /* 0x0307 pll_multiplier maximum value on PLL input 9.6MHz ( 19.2MHz is divided on pre_pll_div) */
-               { 0x322d, 0x04 }, /* Adjusting Processing Image Size to Scaler Toshiba Recommendation Setting */
-               { 0x3255, 0x0f }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */
-               { 0x3256, 0x15 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */
-               { 0x3258, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */
-               { 0x3259, 0x70 }, /* Analog Gain Control Toshiba Recommendation Setting */
-               { 0x325f, 0x7c }, /* Analog Gain Control Toshiba Recommendation Setting */
-               { 0x3302, 0x06 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */
-               { 0x3304, 0x00 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */
-               { 0x3307, 0x22 }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */
-               { 0x3308, 0x8d }, /* Pixel Reference Voltage Control Toshiba Recommendation Setting */
-               { 0x331e, 0x0f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
-               { 0x3320, 0x30 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
-               { 0x3321, 0x11 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
-               { 0x3322, 0x98 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
-               { 0x3323, 0x64 }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
-               { 0x3325, 0x83 }, /* Read Out Timing Control Toshiba Recommendation Setting */
-               { 0x3330, 0x18 }, /* Read Out Timing Control Toshiba Recommendation Setting */
-               { 0x333c, 0x01 }, /* Read Out Timing Control Toshiba Recommendation Setting */
-               { 0x3345, 0x2f }, /* Black Hole Sun Correction Control Toshiba Recommendation Setting */
-               { 0x33de, 0x38 }, /* Horizontal Noise Reduction Control Toshiba Recommendation Setting */
-               /* Taken from v03. No idea what the rest are. */
-               { 0x32e0, 0x05 },
-               { 0x32e1, 0x05 },
-               { 0x32e2, 0x04 },
-               { 0x32e5, 0x04 },
-               { 0x32e6, 0x04 },
-
-       };
-
-       return ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs));
-}
-
-const struct smiapp_quirk smiapp_jt8ew9_quirk = {
-       .limits = jt8ew9_limits,
-       .post_poweron = jt8ew9_post_poweron,
-};
-
-static int imx125es_post_poweron(struct smiapp_sensor *sensor)
-{
-       /* Taken from v02. No idea what the other two are. */
-       static const struct smiapp_reg_8 regs[] = {
-               /*
-                * 0x3302: clk during frame blanking:
-                * 0x00 - HS mode, 0x01 - LP11
-                */
-               { 0x3302, 0x01 },
-               { 0x302d, 0x00 },
-               { 0x3b08, 0x8c },
-       };
-
-       return ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs));
-}
-
-const struct smiapp_quirk smiapp_imx125es_quirk = {
-       .post_poweron = imx125es_post_poweron,
-};
-
-static int jt8ev1_limits(struct smiapp_sensor *sensor)
-{
-       ccs_replace_limit(sensor, CCS_L_X_ADDR_MAX, 0, 4271);
-       ccs_replace_limit(sensor, CCS_L_MIN_LINE_BLANKING_PCK_BIN, 0, 184);
-
-       return 0;
-}
-
-static int jt8ev1_post_poweron(struct smiapp_sensor *sensor)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       int rval;
-       static const struct smiapp_reg_8 regs[] = {
-               { 0x3031, 0xcd }, /* For digital binning (EQ_MONI) */
-               { 0x30a3, 0xd0 }, /* FLASH STROBE enable */
-               { 0x3237, 0x00 }, /* For control of pulse timing for ADC */
-               { 0x3238, 0x43 },
-               { 0x3301, 0x06 }, /* For analog bias for sensor */
-               { 0x3302, 0x06 },
-               { 0x3304, 0x00 },
-               { 0x3305, 0x88 },
-               { 0x332a, 0x14 },
-               { 0x332c, 0x6b },
-               { 0x3336, 0x01 },
-               { 0x333f, 0x1f },
-               { 0x3355, 0x00 },
-               { 0x3356, 0x20 },
-               { 0x33bf, 0x20 }, /* Adjust the FBC speed */
-               { 0x33c9, 0x20 },
-               { 0x33ce, 0x30 }, /* Adjust the parameter for logic function */
-               { 0x33cf, 0xec }, /* For Black sun */
-               { 0x3328, 0x80 }, /* Ugh. No idea what's this. */
-       };
-       static const struct smiapp_reg_8 regs_96[] = {
-               { 0x30ae, 0x00 }, /* For control of ADC clock */
-               { 0x30af, 0xd0 },
-               { 0x30b0, 0x01 },
-       };
-
-       rval = ccs_write_addr_8s(sensor, regs, ARRAY_SIZE(regs));
-       if (rval < 0)
-               return rval;
-
-       switch (sensor->hwcfg->ext_clk) {
-       case 9600000:
-               return ccs_write_addr_8s(sensor, regs_96,
-                                      ARRAY_SIZE(regs_96));
-       default:
-               dev_warn(&client->dev, "no MSRs for %d Hz ext_clk\n",
-                        sensor->hwcfg->ext_clk);
-               return 0;
-       }
-}
-
-static int jt8ev1_pre_streamon(struct smiapp_sensor *sensor)
-{
-       return ccs_write_addr(sensor, 0x3328, 0x00);
-}
-
-static int jt8ev1_post_streamoff(struct smiapp_sensor *sensor)
-{
-       int rval;
-
-       /* Workaround: allows fast standby to work properly */
-       rval = ccs_write_addr(sensor, 0x3205, 0x04);
-       if (rval < 0)
-               return rval;
-
-       /* Wait for 1 ms + one line => 2 ms is likely enough */
-       usleep_range(2000, 2050);
-
-       /* Restore it */
-       rval = ccs_write_addr(sensor, 0x3205, 0x00);
-       if (rval < 0)
-               return rval;
-
-       return ccs_write_addr(sensor, 0x3328, 0x80);
-}
-
-static int jt8ev1_init(struct smiapp_sensor *sensor)
-{
-       sensor->pll.flags |= SMIAPP_PLL_FLAG_OP_PIX_CLOCK_PER_LANE;
-
-       return 0;
-}
-
-const struct smiapp_quirk smiapp_jt8ev1_quirk = {
-       .limits = jt8ev1_limits,
-       .post_poweron = jt8ev1_post_poweron,
-       .pre_streamon = jt8ev1_pre_streamon,
-       .post_streamoff = jt8ev1_post_streamoff,
-       .init = jt8ev1_init,
-};
-
-static int tcm8500md_limits(struct smiapp_sensor *sensor)
-{
-       ccs_replace_limit(sensor, CCS_L_MIN_PLL_IP_CLK_FREQ_MHZ, 0, 2700000);
-
-       return 0;
-}
-
-const struct smiapp_quirk smiapp_tcm8500md_quirk = {
-       .limits = tcm8500md_limits,
-};
diff --git a/drivers/media/i2c/smiapp/smiapp-quirk.h b/drivers/media/i2c/smiapp/smiapp-quirk.h
deleted file mode 100644 (file)
index 8a479f1..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * drivers/media/i2c/smiapp/smiapp-quirk.h
- *
- * Generic driver for SMIA/SMIA++ compliant camera modules
- *
- * Copyright (C) 2011--2012 Nokia Corporation
- * Contact: Sakari Ailus <sakari.ailus@iki.fi>
- */
-
-#ifndef __SMIAPP_QUIRK__
-#define __SMIAPP_QUIRK__
-
-struct smiapp_sensor;
-
-/**
- * struct smiapp_quirk - quirks for sensors that deviate from SMIA++ standard
- *
- * @limits: Replace sensor->limits with values which can't be read from
- *         sensor registers. Called the first time the sensor is powered up.
- * @post_poweron: Called always after the sensor has been fully powered on.
- * @pre_streamon: Called just before streaming is enabled.
- * @post_streamon: Called right after stopping streaming.
- * @pll_flags: Return flags for the PLL calculator.
- * @init: Quirk initialisation, called the last in probe(). This is
- *       also appropriate for adding sensor specific controls, for instance.
- * @reg_access: Register access quirk. The quirk may divert the access
- *             to another register, or no register at all.
- *
- *             @write: Is this read (false) or write (true) access?
- *             @reg: Pointer to the register to access
- *             @value: Register value, set by the caller on write, or
- *                     by the quirk on read
- *
- *             @return: 0 on success, -ENOIOCTLCMD if no register
- *                      access may be done by the caller (default read
- *                      value is zero), else negative error code on error
- */
-struct smiapp_quirk {
-       int (*limits)(struct smiapp_sensor *sensor);
-       int (*post_poweron)(struct smiapp_sensor *sensor);
-       int (*pre_streamon)(struct smiapp_sensor *sensor);
-       int (*post_streamoff)(struct smiapp_sensor *sensor);
-       unsigned long (*pll_flags)(struct smiapp_sensor *sensor);
-       int (*init)(struct smiapp_sensor *sensor);
-       int (*reg_access)(struct smiapp_sensor *sensor, bool write, u32 *reg,
-                         u32 *val);
-       unsigned long flags;
-};
-
-#define SMIAPP_QUIRK_FLAG_8BIT_READ_ONLY                       (1 << 0)
-
-struct smiapp_reg_8 {
-       u16 reg;
-       u8 val;
-};
-
-#define SMIAPP_MK_QUIRK_REG_8(_reg, _val) \
-       {                               \
-               .reg = (u16)_reg,       \
-               .val = _val,            \
-       }
-
-#define smiapp_call_quirk(sensor, _quirk, ...)                         \
-       ((sensor)->minfo.quirk &&                                       \
-        (sensor)->minfo.quirk->_quirk ?                                \
-        (sensor)->minfo.quirk->_quirk(sensor, ##__VA_ARGS__) : 0)
-
-#define smiapp_needs_quirk(sensor, _quirk)             \
-       ((sensor)->minfo.quirk ?                        \
-        (sensor)->minfo.quirk->flags & _quirk : 0)
-
-extern const struct smiapp_quirk smiapp_jt8ev1_quirk;
-extern const struct smiapp_quirk smiapp_imx125es_quirk;
-extern const struct smiapp_quirk smiapp_jt8ew9_quirk;
-extern const struct smiapp_quirk smiapp_tcm8500md_quirk;
-
-#endif /* __SMIAPP_QUIRK__ */
diff --git a/drivers/media/i2c/smiapp/smiapp-regs.c b/drivers/media/i2c/smiapp/smiapp-regs.c
deleted file mode 100644 (file)
index 173d9f8..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * drivers/media/i2c/smiapp/smiapp-regs.c
- *
- * Generic driver for SMIA/SMIA++ compliant camera modules
- *
- * Copyright (C) 2011--2012 Nokia Corporation
- * Contact: Sakari Ailus <sakari.ailus@iki.fi>
- */
-
-#include <asm/unaligned.h>
-
-#include <linux/delay.h>
-#include <linux/i2c.h>
-
-#include "smiapp.h"
-#include "smiapp-regs.h"
-
-static uint32_t float_to_u32_mul_1000000(struct i2c_client *client,
-                                        uint32_t phloat)
-{
-       int32_t exp;
-       uint64_t man;
-
-       if (phloat >= 0x80000000) {
-               dev_err(&client->dev, "this is a negative number\n");
-               return 0;
-       }
-
-       if (phloat == 0x7f800000)
-               return ~0; /* Inf. */
-
-       if ((phloat & 0x7f800000) == 0x7f800000) {
-               dev_err(&client->dev, "NaN or other special number\n");
-               return 0;
-       }
-
-       /* Valid cases begin here */
-       if (phloat == 0)
-               return 0; /* Valid zero */
-
-       if (phloat > 0x4f800000)
-               return ~0; /* larger than 4294967295 */
-
-       /*
-        * Unbias exponent (note how phloat is now guaranteed to
-        * have 0 in the high bit)
-        */
-       exp = ((int32_t)phloat >> 23) - 127;
-
-       /* Extract mantissa, add missing '1' bit and it's in MHz */
-       man = ((phloat & 0x7fffff) | 0x800000) * 1000000ULL;
-
-       if (exp < 0)
-               man >>= -exp;
-       else
-               man <<= exp;
-
-       man >>= 23; /* Remove mantissa bias */
-
-       return man & 0xffffffff;
-}
-
-
-/*
- * Read a 8/16/32-bit i2c register.  The value is returned in 'val'.
- * Returns zero if successful, or non-zero otherwise.
- */
-static int ____ccs_read_addr(struct smiapp_sensor *sensor, u16 reg, u16 len,
-                            u32 *val)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       struct i2c_msg msg;
-       unsigned char data_buf[sizeof(u32)] = { 0 };
-       unsigned char offset_buf[sizeof(u16)];
-       int r;
-
-       if (len > sizeof(data_buf))
-               return -EINVAL;
-
-       msg.addr = client->addr;
-       msg.flags = 0;
-       msg.len = sizeof(offset_buf);
-       msg.buf = offset_buf;
-       put_unaligned_be16(reg, offset_buf);
-
-       r = i2c_transfer(client->adapter, &msg, 1);
-       if (r != 1) {
-               if (r >= 0)
-                       r = -EBUSY;
-               goto err;
-       }
-
-       msg.len = len;
-       msg.flags = I2C_M_RD;
-       msg.buf = &data_buf[sizeof(data_buf) - len];
-
-       r = i2c_transfer(client->adapter, &msg, 1);
-       if (r != 1) {
-               if (r >= 0)
-                       r = -EBUSY;
-               goto err;
-       }
-
-       *val = get_unaligned_be32(data_buf);
-
-       return 0;
-
-err:
-       dev_err(&client->dev, "read from offset 0x%x error %d\n", reg, r);
-
-       return r;
-}
-
-/* Read a register using 8-bit access only. */
-static int ____ccs_read_addr_8only(struct smiapp_sensor *sensor, u16 reg,
-                                  u16 len, u32 *val)
-{
-       unsigned int i;
-       int rval;
-
-       *val = 0;
-
-       for (i = 0; i < len; i++) {
-               u32 val8;
-
-               rval = ____ccs_read_addr(sensor, reg + i, 1, &val8);
-               if (rval < 0)
-                       return rval;
-               *val |= val8 << ((len - i - 1) << 3);
-       }
-
-       return 0;
-}
-
-unsigned int ccs_reg_width(u32 reg)
-{
-       if (reg & CCS_FL_16BIT)
-               return sizeof(uint16_t);
-       if (reg & CCS_FL_32BIT)
-               return sizeof(uint32_t);
-
-       return sizeof(uint8_t);
-}
-
-/*
- * Read a 8/16/32-bit i2c register.  The value is returned in 'val'.
- * Returns zero if successful, or non-zero otherwise.
- */
-static int __ccs_read_addr(struct smiapp_sensor *sensor, u32 reg, u32 *val,
-                          bool only8)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       unsigned int len = ccs_reg_width(reg);
-       int rval;
-
-       if (!only8)
-               rval = ____ccs_read_addr(sensor, SMIAPP_REG_ADDR(reg), len,
-                                           val);
-       else
-               rval = ____ccs_read_addr_8only(sensor, SMIAPP_REG_ADDR(reg),
-                                                 len, val);
-       if (rval < 0)
-               return rval;
-
-       if (reg & CCS_FL_FLOAT_IREAL)
-               *val = float_to_u32_mul_1000000(client, *val);
-
-       return 0;
-}
-
-int ccs_read_addr_no_quirk(struct smiapp_sensor *sensor, u32 reg, u32 *val)
-{
-       return __ccs_read_addr(
-               sensor, reg, val,
-               smiapp_needs_quirk(sensor,
-                                  SMIAPP_QUIRK_FLAG_8BIT_READ_ONLY));
-}
-
-static int ccs_read_addr_quirk(struct smiapp_sensor *sensor, u32 reg, u32 *val,
-                              bool force8)
-{
-       int rval;
-
-       *val = 0;
-       rval = smiapp_call_quirk(sensor, reg_access, false, &reg, val);
-       if (rval == -ENOIOCTLCMD)
-               return 0;
-       if (rval < 0)
-               return rval;
-
-       if (force8)
-               return __ccs_read_addr(sensor, reg, val, true);
-
-       return ccs_read_addr_no_quirk(sensor, reg, val);
-}
-
-int ccs_read_addr(struct smiapp_sensor *sensor, u32 reg, u32 *val)
-{
-       return ccs_read_addr_quirk(sensor, reg, val, false);
-}
-
-int ccs_read_addr_8only(struct smiapp_sensor *sensor, u32 reg, u32 *val)
-{
-       return ccs_read_addr_quirk(sensor, reg, val, true);
-}
-
-int ccs_write_addr_no_quirk(struct smiapp_sensor *sensor, u32 reg, u32 val)
-{
-       struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
-       struct i2c_msg msg;
-       unsigned char data[6];
-       unsigned int retries;
-       unsigned int len = ccs_reg_width(reg);
-       int r;
-
-       if (len > sizeof(data) - 2)
-               return -EINVAL;
-
-       msg.addr = client->addr;
-       msg.flags = 0; /* Write */
-       msg.len = 2 + len;
-       msg.buf = data;
-
-       put_unaligned_be16(SMIAPP_REG_ADDR(reg), data);
-       put_unaligned_be32(val << (8 * (sizeof(val) - len)), data + 2);
-
-       for (retries = 0; retries < 5; retries++) {
-               /*
-                * Due to unknown reason sensor stops responding. This
-                * loop is a temporaty solution until the root cause
-                * is found.
-                */
-               r = i2c_transfer(client->adapter, &msg, 1);
-               if (r == 1) {
-                       if (retries)
-                               dev_err(&client->dev,
-                                       "sensor i2c stall encountered. retries: %d\n",
-                                       retries);
-                       return 0;
-               }
-
-               usleep_range(2000, 2000);
-       }
-
-       dev_err(&client->dev,
-               "wrote 0x%x to offset 0x%x error %d\n", val,
-               SMIAPP_REG_ADDR(reg), r);
-
-       return r;
-}
-
-/*
- * Write to a 8/16-bit register.
- * Returns zero if successful, or non-zero otherwise.
- */
-int ccs_write_addr(struct smiapp_sensor *sensor, u32 reg, u32 val)
-{
-       int rval;
-
-       rval = smiapp_call_quirk(sensor, reg_access, true, &reg, &val);
-       if (rval == -ENOIOCTLCMD)
-               return 0;
-       if (rval < 0)
-               return rval;
-
-       return ccs_write_addr_no_quirk(sensor, reg, val);
-}
diff --git a/drivers/media/i2c/smiapp/smiapp-regs.h b/drivers/media/i2c/smiapp/smiapp-regs.h
deleted file mode 100644 (file)
index 5df794f..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * include/media/smiapp/smiapp-regs.h
- *
- * Generic driver for SMIA/SMIA++ compliant camera modules
- *
- * Copyright (C) 2011--2012 Nokia Corporation
- * Contact: Sakari Ailus <sakari.ailus@iki.fi>
- */
-
-#ifndef SMIAPP_REGS_H
-#define SMIAPP_REGS_H
-
-#include <linux/i2c.h>
-#include <linux/types.h>
-
-#include "ccs-regs.h"
-
-#define SMIAPP_REG_ADDR(reg)           ((u16)reg)
-
-struct smiapp_sensor;
-
-int ccs_read_addr_no_quirk(struct smiapp_sensor *sensor, u32 reg, u32 *val);
-int ccs_read_addr(struct smiapp_sensor *sensor, u32 reg, u32 *val);
-int ccs_read_addr_8only(struct smiapp_sensor *sensor, u32 reg, u32 *val);
-int ccs_write_addr_no_quirk(struct smiapp_sensor *sensor, u32 reg, u32 val);
-int ccs_write_addr(struct smiapp_sensor *sensor, u32 reg, u32 val);
-
-unsigned int ccs_reg_width(u32 reg);
-
-#define ccs_read(sensor, reg_name, val) \
-       ccs_read_addr(sensor, CCS_R_##reg_name, val)
-
-#define ccs_write(sensor, reg_name, val) \
-       ccs_write_addr(sensor, CCS_R_##reg_name, val)
-
-#endif
diff --git a/drivers/media/i2c/smiapp/smiapp.h b/drivers/media/i2c/smiapp/smiapp.h
deleted file mode 100644 (file)
index c6e4e05..0000000
+++ /dev/null
@@ -1,280 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * drivers/media/i2c/smiapp/smiapp.h
- *
- * Generic driver for SMIA/SMIA++ compliant camera modules
- *
- * Copyright (C) 2010--2012 Nokia Corporation
- * Contact: Sakari Ailus <sakari.ailus@iki.fi>
- */
-
-#ifndef __SMIAPP_PRIV_H_
-#define __SMIAPP_PRIV_H_
-
-#include <linux/mutex.h>
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-subdev.h>
-
-#include "ccs-regs.h"
-
-#include "smiapp-pll.h"
-#include "smiapp-reg-defs.h"
-#include "smiapp-regs.h"
-#include "smiapp-quirk.h"
-
-/*
- * Standard SMIA++ constants
- */
-#define SMIA_VERSION_1                 10
-#define SMIAPP_VERSION_0_8             8 /* Draft 0.8 */
-#define SMIAPP_VERSION_0_9             9 /* Draft 0.9 */
-#define SMIAPP_VERSION_1               10
-
-#define SMIAPP_PROFILE_0               0
-#define SMIAPP_PROFILE_1               1
-#define SMIAPP_PROFILE_2               2
-
-#define SMIAPP_NVM_PAGE_SIZE           64      /* bytes */
-
-#define SMIAPP_RESET_DELAY_CLOCKS      2400
-#define SMIAPP_RESET_DELAY(clk)                                \
-       (1000 + (SMIAPP_RESET_DELAY_CLOCKS * 1000       \
-                + (clk) / 1000 - 1) / ((clk) / 1000))
-
-#define SMIAPP_COLOUR_COMPONENTS       4
-
-#define SMIAPP_NAME            "smiapp"
-
-#define SMIAPP_DFL_I2C_ADDR    (0x20 >> 1) /* Default I2C Address */
-#define SMIAPP_ALT_I2C_ADDR    (0x6e >> 1) /* Alternate I2C Address */
-
-/*
- * Sometimes due to board layout considerations the camera module can be
- * mounted rotated. The typical rotation used is 180 degrees which can be
- * corrected by giving a default H-FLIP and V-FLIP in the sensor readout.
- * FIXME: rotation also changes the bayer pattern.
- */
-enum smiapp_module_board_orient {
-       SMIAPP_MODULE_BOARD_ORIENT_0 = 0,
-       SMIAPP_MODULE_BOARD_ORIENT_180,
-};
-
-struct smiapp_flash_strobe_parms {
-       u8 mode;
-       u32 strobe_width_high_us;
-       u16 strobe_delay;
-       u16 stobe_start_point;
-       u8 trigger;
-};
-
-struct smiapp_hwconfig {
-       /*
-        * Change the cci address if i2c_addr_alt is set.
-        * Both default and alternate cci addr need to be present
-        */
-       unsigned short i2c_addr_dfl;    /* Default i2c addr */
-       unsigned short i2c_addr_alt;    /* Alternate i2c addr */
-
-       uint32_t ext_clk;               /* sensor external clk */
-
-       unsigned int lanes;             /* Number of CSI-2 lanes */
-       uint32_t csi_signalling_mode;   /* SMIAPP_CSI_SIGNALLING_MODE_* */
-       uint64_t *op_sys_clock;
-
-       enum smiapp_module_board_orient module_board_orient;
-
-       struct smiapp_flash_strobe_parms *strobe_setup;
-};
-
-struct smiapp_quirk;
-
-#define SMIAPP_MODULE_IDENT_FLAG_REV_LE                (1 << 0)
-
-struct smiapp_module_ident {
-       u16 mipi_manufacturer_id;
-       u16 model_id;
-       u8 smia_manufacturer_id;
-       u8 revision_number_major;
-
-       u8 flags;
-
-       char *name;
-       const struct smiapp_quirk *quirk;
-};
-
-struct smiapp_module_info {
-       u32 smia_manufacturer_id;
-       u32 mipi_manufacturer_id;
-       u32 model_id;
-       u32 revision_number_major;
-       u32 revision_number_minor;
-
-       u32 module_year;
-       u32 module_month;
-       u32 module_day;
-
-       u32 sensor_smia_manufacturer_id;
-       u32 sensor_mipi_manufacturer_id;
-       u32 sensor_model_id;
-       u32 sensor_revision_number;
-       u32 sensor_firmware_version;
-
-       u32 smia_version;
-       u32 smiapp_version;
-       u32 ccs_version;
-
-       u32 smiapp_profile;
-
-       char *name;
-       const struct smiapp_quirk *quirk;
-};
-
-#define SMIAPP_IDENT_FQ(manufacturer, model, rev, fl, _name, _quirk)   \
-       { .smia_manufacturer_id = manufacturer,                         \
-         .model_id = model,                                            \
-         .revision_number_major = rev,                                 \
-         .flags = fl,                                                  \
-         .name = _name,                                                \
-         .quirk = _quirk, }
-
-#define SMIAPP_IDENT_LQ(manufacturer, model, rev, _name, _quirk)       \
-       { .smia_manufacturer_id = manufacturer,                         \
-         .model_id = model,                                            \
-         .revision_number_major = rev,                                 \
-         .flags = SMIAPP_MODULE_IDENT_FLAG_REV_LE,                     \
-         .name = _name,                                                \
-         .quirk = _quirk, }
-
-#define SMIAPP_IDENT_L(manufacturer, model, rev, _name)                        \
-       { .smia_manufacturer_id = manufacturer,                         \
-         .model_id = model,                                            \
-         .revision_number_major = rev,                                 \
-         .flags = SMIAPP_MODULE_IDENT_FLAG_REV_LE,                     \
-         .name = _name, }
-
-#define SMIAPP_IDENT_Q(manufacturer, model, rev, _name, _quirk)                \
-       { .smia_manufacturer_id = manufacturer,                         \
-         .model_id = model,                                            \
-         .revision_number_major = rev,                                 \
-         .flags = 0,                                                   \
-         .name = _name,                                                \
-         .quirk = _quirk, }
-
-#define SMIAPP_IDENT(manufacturer, model, rev, _name)                  \
-       { .smia_manufacturer_id = manufacturer,                         \
-         .model_id = model,                                            \
-         .revision_number_major = rev,                                 \
-         .flags = 0,                                                   \
-         .name = _name, }
-
-struct smiapp_csi_data_format {
-       u32 code;
-       u8 width;
-       u8 compressed;
-       u8 pixel_order;
-};
-
-#define SMIAPP_SUBDEVS                 3
-
-#define SMIAPP_PA_PAD_SRC              0
-#define SMIAPP_PAD_SINK                        0
-#define SMIAPP_PAD_SRC                 1
-#define SMIAPP_PADS                    2
-
-struct smiapp_binning_subtype {
-       u8 horizontal:4;
-       u8 vertical:4;
-} __packed;
-
-struct smiapp_subdev {
-       struct v4l2_subdev sd;
-       struct media_pad pads[SMIAPP_PADS];
-       struct v4l2_rect sink_fmt;
-       struct v4l2_rect crop[SMIAPP_PADS];
-       struct v4l2_rect compose; /* compose on sink */
-       unsigned short sink_pad;
-       unsigned short source_pad;
-       int npads;
-       struct smiapp_sensor *sensor;
-       struct v4l2_ctrl_handler ctrl_handler;
-};
-
-/*
- * struct smiapp_sensor - Main device structure
- */
-struct smiapp_sensor {
-       /*
-        * "mutex" is used to serialise access to all fields here
-        * except v4l2_ctrls at the end of the struct. "mutex" is also
-        * used to serialise access to file handle specific
-        * information.
-        */
-       struct mutex mutex;
-       struct smiapp_subdev ssds[SMIAPP_SUBDEVS];
-       u32 ssds_used;
-       struct smiapp_subdev *src;
-       struct smiapp_subdev *binner;
-       struct smiapp_subdev *scaler;
-       struct smiapp_subdev *pixel_array;
-       struct smiapp_hwconfig *hwcfg;
-       struct regulator *vana;
-       struct clk *ext_clk;
-       struct gpio_desc *xshutdown;
-       void *ccs_limits;
-       u8 nbinning_subtypes;
-       struct smiapp_binning_subtype binning_subtypes[CCS_LIM_BINNING_SUB_TYPE_MAX_N + 1];
-       u32 mbus_frame_fmts;
-       const struct smiapp_csi_data_format *csi_format;
-       const struct smiapp_csi_data_format *internal_csi_format;
-       u32 default_mbus_frame_fmts;
-       int default_pixel_order;
-
-       u8 binning_horizontal;
-       u8 binning_vertical;
-
-       u8 scale_m;
-       u8 scaling_mode;
-
-       u8 hvflip_inv_mask; /* H/VFLIP inversion due to sensor orientation */
-       u8 frame_skip;
-       u16 embedded_start; /* embedded data start line */
-       u16 embedded_end;
-       u16 image_start; /* image data start line */
-       u16 visible_pixel_start; /* start pixel of the visible image */
-
-       bool streaming;
-       bool dev_init_done;
-       u8 compressed_min_bpp;
-
-       struct smiapp_module_info minfo;
-
-       struct smiapp_pll pll;
-
-       /* Is a default format supported for a given BPP? */
-       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;
-       /* src controls */
-       struct v4l2_ctrl *link_freq;
-       struct v4l2_ctrl *pixel_rate_csi;
-       /* test pattern colour components */
-       struct v4l2_ctrl *test_data[SMIAPP_COLOUR_COMPONENTS];
-};
-
-#define to_smiapp_subdev(_sd)                          \
-       container_of(_sd, struct smiapp_subdev, sd)
-
-#define to_smiapp_sensor(_sd)  \
-       (to_smiapp_subdev(_sd)->sensor)
-
-void ccs_replace_limit(struct smiapp_sensor *sensor,
-                      unsigned int limit, unsigned int offset, u32 val);
-
-#endif /* __SMIAPP_PRIV_H_ */