mirror of
https://github.com/edk2-porting/linux-next.git
synced 2025-01-06 20:53:54 +08:00
ae502e08f4
Add support for decoupled OP domain clock calculation. This means that the number of VT and OP domain clocks are no longer dependent on the number of CSI-2 lanes in the lane speed mode. The support also replaces the existing quirk flag to calculate OP domain clocks per lane. Also support decoupled OP domain calculation in the CCS driver. Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
3413 lines
88 KiB
C
3413 lines
88 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* drivers/media/i2c/ccs/ccs-core.c
|
|
*
|
|
* Generic driver for MIPI CCS/SMIA/SMIA++ compliant camera sensors
|
|
*
|
|
* Copyright (C) 2020 Intel Corporation
|
|
* Copyright (C) 2010--2012 Nokia Corporation
|
|
* Contact: Sakari Ailus <sakari.ailus@linux.intel.com>
|
|
*
|
|
* 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/firmware.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),
|
|
};
|
|
|
|
#define CCS_DEVICE_FLAG_IS_SMIA BIT(0)
|
|
|
|
struct ccs_device {
|
|
unsigned char flags;
|
|
};
|
|
|
|
static const char * const ccs_regulators[] = { "vcore", "vio", "vana" };
|
|
|
|
/*
|
|
*
|
|
* 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);
|
|
}
|
|
|
|
u32 ccs_get_limit(struct ccs_sensor *sensor, unsigned int limit,
|
|
unsigned int offset)
|
|
{
|
|
void *ptr;
|
|
u32 val;
|
|
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):
|
|
val = *(u8 *)ptr;
|
|
break;
|
|
case sizeof(u16):
|
|
val = *(u16 *)ptr;
|
|
break;
|
|
case sizeof(u32):
|
|
val = *(u32 *)ptr;
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
return 0;
|
|
}
|
|
|
|
return ccs_reg_conv(sensor, ccs_limits[limit].reg, val);
|
|
}
|
|
|
|
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_noconv(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;
|
|
}
|
|
|
|
if (!val && j)
|
|
break;
|
|
|
|
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;
|
|
|
|
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,
|
|
"%s pixels: %d %s (pixelcode %u)\n",
|
|
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 ccs_pll *pll = &sensor->pll;
|
|
int rval;
|
|
|
|
rval = ccs_write(sensor, VT_PIX_CLK_DIV, pll->vt_bk.pix_clk_div);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
rval = ccs_write(sensor, VT_SYS_CLK_DIV, pll->vt_bk.sys_clk_div);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
rval = ccs_write(sensor, PRE_PLL_CLK_DIV, pll->vt_fr.pre_pll_clk_div);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
rval = ccs_write(sensor, PLL_MULTIPLIER, pll->vt_fr.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_bk.sys_clk_freq_hz,
|
|
1000000 / 256 / 256) *
|
|
(pll->flags & CCS_PLL_FLAG_LANE_SPEED_MODEL ?
|
|
sensor->pll.csi2.lanes : 1));
|
|
if (rval < 0 || sensor->pll.flags & CCS_PLL_FLAG_NO_OP_CLOCKS)
|
|
return rval;
|
|
|
|
rval = ccs_write(sensor, OP_PIX_CLK_DIV, pll->op_bk.pix_clk_div);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
return ccs_write(sensor, OP_SYS_CLK_DIV, pll->op_bk.sys_clk_div);
|
|
}
|
|
|
|
static int ccs_pll_try(struct ccs_sensor *sensor, struct ccs_pll *pll)
|
|
{
|
|
struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd);
|
|
struct ccs_pll_limits lim = {
|
|
.vt_fr = {
|
|
.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_clk_freq_hz = CCS_LIM(sensor, MIN_PLL_IP_CLK_FREQ_MHZ),
|
|
.max_pll_ip_clk_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_clk_freq_hz = CCS_LIM(sensor, MIN_PLL_OP_CLK_FREQ_MHZ),
|
|
.max_pll_op_clk_freq_hz = CCS_LIM(sensor, MAX_PLL_OP_CLK_FREQ_MHZ),
|
|
},
|
|
.op_bk = {
|
|
.min_sys_clk_div = CCS_LIM(sensor, MIN_OP_SYS_CLK_DIV),
|
|
.max_sys_clk_div = CCS_LIM(sensor, MAX_OP_SYS_CLK_DIV),
|
|
.min_pix_clk_div = CCS_LIM(sensor, MIN_OP_PIX_CLK_DIV),
|
|
.max_pix_clk_div = CCS_LIM(sensor, MAX_OP_PIX_CLK_DIV),
|
|
.min_sys_clk_freq_hz = CCS_LIM(sensor, MIN_OP_SYS_CLK_FREQ_MHZ),
|
|
.max_sys_clk_freq_hz = CCS_LIM(sensor, MAX_OP_SYS_CLK_FREQ_MHZ),
|
|
.min_pix_clk_freq_hz = CCS_LIM(sensor, MIN_OP_PIX_CLK_FREQ_MHZ),
|
|
.max_pix_clk_freq_hz = CCS_LIM(sensor, MAX_OP_PIX_CLK_FREQ_MHZ),
|
|
},
|
|
.vt_bk = {
|
|
.min_sys_clk_div = CCS_LIM(sensor, MIN_VT_SYS_CLK_DIV),
|
|
.max_sys_clk_div = CCS_LIM(sensor, MAX_VT_SYS_CLK_DIV),
|
|
.min_pix_clk_div = CCS_LIM(sensor, MIN_VT_PIX_CLK_DIV),
|
|
.max_pix_clk_div = CCS_LIM(sensor, MAX_VT_PIX_CLK_DIV),
|
|
.min_sys_clk_freq_hz = CCS_LIM(sensor, MIN_VT_SYS_CLK_FREQ_MHZ),
|
|
.max_sys_clk_freq_hz = CCS_LIM(sensor, MAX_VT_SYS_CLK_FREQ_MHZ),
|
|
.min_pix_clk_freq_hz = CCS_LIM(sensor, MIN_VT_PIX_CLK_FREQ_MHZ),
|
|
.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 ccs_pll_calculate(&client->dev, &lim, pll);
|
|
}
|
|
|
|
static int ccs_pll_update(struct ccs_sensor *sensor)
|
|
{
|
|
struct ccs_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);
|
|
|
|
if (WARN_ON_ONCE(max(internal_csi_format_idx, csi_format_idx) +
|
|
pixel_order >= ARRAY_SIZE(ccs_csi_data_formats)))
|
|
return;
|
|
|
|
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];
|
|
|
|
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 ccs_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@linux.intel.com> 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_write_msr_regs(struct ccs_sensor *sensor)
|
|
{
|
|
int rval;
|
|
|
|
rval = ccs_write_data_regs(sensor,
|
|
sensor->sdata.sensor_manufacturer_regs,
|
|
sensor->sdata.num_sensor_manufacturer_regs);
|
|
if (rval)
|
|
return rval;
|
|
|
|
return ccs_write_data_regs(sensor,
|
|
sensor->mdata.module_manufacturer_regs,
|
|
sensor->mdata.num_module_manufacturer_regs);
|
|
}
|
|
|
|
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]);
|
|
const struct ccs_device *ccsdev = device_get_match_data(dev);
|
|
unsigned int sleep;
|
|
int rval;
|
|
|
|
rval = regulator_bulk_enable(ARRAY_SIZE(ccs_regulators),
|
|
sensor->regulators);
|
|
if (rval) {
|
|
dev_err(dev, "failed to enable vana regulator\n");
|
|
return rval;
|
|
}
|
|
|
|
rval = clk_prepare_enable(sensor->ext_clk);
|
|
if (rval < 0) {
|
|
dev_dbg(dev, "failed to enable xclk\n");
|
|
goto out_xclk_fail;
|
|
}
|
|
|
|
gpiod_set_value(sensor->reset, 0);
|
|
gpiod_set_value(sensor->xshutdown, 1);
|
|
|
|
if (ccsdev->flags & CCS_DEVICE_FLAG_IS_SMIA)
|
|
sleep = SMIAPP_RESET_DELAY(sensor->hwcfg.ext_clk);
|
|
else
|
|
sleep = 5000;
|
|
|
|
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_write_msr_regs(sensor);
|
|
if (rval)
|
|
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->reset, 1);
|
|
gpiod_set_value(sensor->xshutdown, 0);
|
|
clk_disable_unprepare(sensor->ext_clk);
|
|
|
|
out_xclk_fail:
|
|
regulator_bulk_disable(ARRAY_SIZE(ccs_regulators),
|
|
sensor->regulators);
|
|
|
|
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->reset, 1);
|
|
gpiod_set_value(sensor->xshutdown, 0);
|
|
clk_disable_unprepare(sensor->ext_clk);
|
|
usleep_range(5000, 5000);
|
|
regulator_bulk_disable(ARRAY_SIZE(ccs_regulators),
|
|
sensor->regulators);
|
|
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) {
|
|
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);
|
|
}
|
|
if (comps)
|
|
*comps = v4l2_subdev_get_try_compose(subdev, cfg,
|
|
CCS_PAD_SINK);
|
|
}
|
|
}
|
|
|
|
/* 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:
|
|
WARN_ON_ONCE(1);
|
|
}
|
|
}
|
|
|
|
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) + 1;
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "%2.2x%4.4x%2.2x\n",
|
|
minfo->smia_manufacturer_id, minfo->model_id,
|
|
minfo->revision_number) + 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;
|
|
u32 rev;
|
|
int rval = 0;
|
|
|
|
/* 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,
|
|
&rev);
|
|
if (!rval) {
|
|
rval = ccs_read_addr_8only(sensor,
|
|
CCS_R_MODULE_REVISION_NUMBER_MINOR,
|
|
&minfo->revision_number);
|
|
minfo->revision_number |= rev << 8;
|
|
}
|
|
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%4.4x date %2.2d-%2.2d-%2.2d\n",
|
|
minfo->revision_number, 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));
|
|
minfo->name = CCS_NAME;
|
|
} else {
|
|
dev_dbg(&client->dev,
|
|
"smia version %2.2d smiapp version %2.2d\n",
|
|
minfo->smia_version, minfo->smiapp_version);
|
|
minfo->name = SMIAPP_NAME;
|
|
}
|
|
|
|
/*
|
|
* 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 = 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 >> 8))
|
|
continue;
|
|
} else {
|
|
if (ccs_module_idents[i].revision_number_major
|
|
!= (minfo->revision_number >> 8))
|
|
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, u32 function)
|
|
{
|
|
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->sd.entity.function = function;
|
|
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) {
|
|
pm_runtime_put_noidle(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 int ccs_get_hwconfig(struct ccs_sensor *sensor, struct device *dev)
|
|
{
|
|
struct ccs_hwconfig *hwcfg = &sensor->hwcfg;
|
|
struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = V4L2_MBUS_UNKNOWN };
|
|
struct fwnode_handle *ep;
|
|
struct fwnode_handle *fwnode = dev_fwnode(dev);
|
|
u32 rotation;
|
|
int i;
|
|
int rval;
|
|
|
|
ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
|
|
if (!ep)
|
|
return -ENODEV;
|
|
|
|
/*
|
|
* Note that we do need to rely on detecting the bus type between CSI-2
|
|
* D-PHY and CCP2 as the old bindings did not require it.
|
|
*/
|
|
rval = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
|
|
if (rval)
|
|
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_CSI1:
|
|
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);
|
|
rval = -EINVAL;
|
|
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);
|
|
rval = -EINVAL;
|
|
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");
|
|
rval = -EINVAL;
|
|
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) {
|
|
rval = -ENOMEM;
|
|
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 0;
|
|
|
|
out_err:
|
|
v4l2_fwnode_endpoint_free(&bus_cfg);
|
|
fwnode_handle_put(ep);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static int ccs_probe(struct i2c_client *client)
|
|
{
|
|
struct ccs_sensor *sensor;
|
|
const struct firmware *fw;
|
|
char filename[40];
|
|
unsigned int i;
|
|
int rval;
|
|
|
|
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
|
|
if (sensor == NULL)
|
|
return -ENOMEM;
|
|
|
|
rval = ccs_get_hwconfig(sensor, &client->dev);
|
|
if (rval)
|
|
return rval;
|
|
|
|
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->regulators = devm_kcalloc(&client->dev,
|
|
ARRAY_SIZE(ccs_regulators),
|
|
sizeof(*sensor->regulators),
|
|
GFP_KERNEL);
|
|
if (!sensor->regulators)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ccs_regulators); i++)
|
|
sensor->regulators[i].supply = ccs_regulators[i];
|
|
|
|
rval = devm_regulator_bulk_get(&client->dev, ARRAY_SIZE(ccs_regulators),
|
|
sensor->regulators);
|
|
if (rval) {
|
|
dev_err(&client->dev, "could not get regulators\n");
|
|
return rval;
|
|
}
|
|
|
|
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 -EINVAL;
|
|
}
|
|
} 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->reset = devm_gpiod_get_optional(&client->dev, "reset",
|
|
GPIOD_OUT_HIGH);
|
|
if (IS_ERR(sensor->reset))
|
|
return PTR_ERR(sensor->reset);
|
|
/* Support old users that may have used "xshutdown" property. */
|
|
if (!sensor->reset)
|
|
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 = snprintf(filename, sizeof(filename),
|
|
"ccs/ccs-sensor-%4.4x-%4.4x-%4.4x.fw",
|
|
sensor->minfo.sensor_mipi_manufacturer_id,
|
|
sensor->minfo.sensor_model_id,
|
|
sensor->minfo.sensor_revision_number);
|
|
if (rval >= sizeof(filename)) {
|
|
rval = -ENOMEM;
|
|
goto out_power_off;
|
|
}
|
|
|
|
rval = request_firmware(&fw, filename, &client->dev);
|
|
if (!rval) {
|
|
ccs_data_parse(&sensor->sdata, fw->data, fw->size, &client->dev,
|
|
true);
|
|
release_firmware(fw);
|
|
}
|
|
|
|
rval = snprintf(filename, sizeof(filename),
|
|
"ccs/ccs-module-%4.4x-%4.4x-%4.4x.fw",
|
|
sensor->minfo.mipi_manufacturer_id,
|
|
sensor->minfo.model_id,
|
|
sensor->minfo.revision_number);
|
|
if (rval >= sizeof(filename)) {
|
|
rval = -ENOMEM;
|
|
goto out_release_sdata;
|
|
}
|
|
|
|
rval = request_firmware(&fw, filename, &client->dev);
|
|
if (!rval) {
|
|
ccs_data_parse(&sensor->mdata, fw->data, fw->size, &client->dev,
|
|
true);
|
|
release_firmware(fw);
|
|
}
|
|
|
|
rval = ccs_read_all_limits(sensor);
|
|
if (rval)
|
|
goto out_release_mdata;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
/* No OP clock branch */
|
|
sensor->pll.flags |= CCS_PLL_FLAG_NO_OP_CLOCKS;
|
|
} else if (CCS_LIM(sensor, SCALING_CAPABILITY)
|
|
!= CCS_SCALING_CAPABILITY_NONE ||
|
|
CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY)
|
|
== CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) {
|
|
/* We have a scaler or digital 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 = CCS_PLL_BUS_TYPE_CSI2_DPHY;
|
|
sensor->pll.csi2.lanes = sensor->hwcfg.lanes;
|
|
if (CCS_LIM(sensor, CLOCK_CALCULATION) &
|
|
CCS_CLOCK_CALCULATION_LANE_SPEED) {
|
|
sensor->pll.flags |= CCS_PLL_FLAG_LANE_SPEED_MODEL;
|
|
if (CCS_LIM(sensor, CLOCK_CALCULATION) &
|
|
CCS_CLOCK_CALCULATION_LINK_DECOUPLED) {
|
|
sensor->pll.vt_lanes =
|
|
CCS_LIM(sensor, NUM_OF_VT_LANES) + 1;
|
|
sensor->pll.op_lanes =
|
|
CCS_LIM(sensor, NUM_OF_OP_LANES) + 1;
|
|
sensor->pll.flags |= CCS_PLL_FLAG_LINK_DECOUPLED;
|
|
} else {
|
|
sensor->pll.vt_lanes = sensor->pll.csi2.lanes;
|
|
sensor->pll.op_lanes = sensor->pll.csi2.lanes;
|
|
}
|
|
}
|
|
sensor->pll.ext_clk_freq_hz = sensor->hwcfg.ext_clk;
|
|
sensor->pll.scale_n = CCS_LIM(sensor, SCALER_N_MIN);
|
|
|
|
ccs_create_subdev(sensor, sensor->scaler, " scaler", 2,
|
|
MEDIA_ENT_F_CAM_SENSOR);
|
|
ccs_create_subdev(sensor, sensor->binner, " binner", 2,
|
|
MEDIA_ENT_F_PROC_VIDEO_SCALER);
|
|
ccs_create_subdev(sensor, sensor->pixel_array, " pixel_array", 1,
|
|
MEDIA_ENT_F_PROC_VIDEO_SCALER);
|
|
|
|
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;
|
|
|
|
rval = ccs_write_msr_regs(sensor);
|
|
if (rval)
|
|
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_release_mdata:
|
|
kvfree(sensor->mdata.backing);
|
|
|
|
out_release_sdata:
|
|
kvfree(sensor->sdata.backing);
|
|
|
|
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);
|
|
kvfree(sensor->sdata.backing);
|
|
kvfree(sensor->mdata.backing);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct ccs_device smia_device = {
|
|
.flags = CCS_DEVICE_FLAG_IS_SMIA,
|
|
};
|
|
|
|
static const struct ccs_device ccs_device = {};
|
|
|
|
static const struct acpi_device_id ccs_acpi_table[] = {
|
|
{ .id = "MIPI0200", .driver_data = (unsigned long)&ccs_device },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, ccs_acpi_table);
|
|
|
|
static const struct of_device_id ccs_of_table[] = {
|
|
{ .compatible = "mipi-ccs-1.1", .data = &ccs_device },
|
|
{ .compatible = "mipi-ccs-1.0", .data = &ccs_device },
|
|
{ .compatible = "mipi-ccs", .data = &ccs_device },
|
|
{ .compatible = "nokia,smia", .data = &smia_device },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ccs_of_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 = {
|
|
.acpi_match_table = ccs_acpi_table,
|
|
.of_match_table = ccs_of_table,
|
|
.name = CCS_NAME,
|
|
.pm = &ccs_pm_ops,
|
|
},
|
|
.probe_new = ccs_probe,
|
|
.remove = ccs_remove,
|
|
};
|
|
|
|
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@linux.intel.com>");
|
|
MODULE_DESCRIPTION("Generic MIPI CCS/SMIA/SMIA++ camera sensor driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("smiapp");
|