From 94203b7e5bbb814c1d18fc5d95fea18b66041c03 Mon Sep 17 00:00:00 2001 From: Lee Jackson Date: Thu, 14 Apr 2022 17:31:01 +0800 Subject: [PATCH] media: i2c: Add driver of Arducam Pivariety series camera Add a driver for the Arducam Pivariety series CSI2 camera sensor. Signed-off-by: Lee Jackson --- drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/arducam-pivariety.c | 1469 +++++++++++++++++++++++++ drivers/media/i2c/arducam-pivariety.h | 107 ++ 4 files changed, 1588 insertions(+) create mode 100644 drivers/media/i2c/arducam-pivariety.c create mode 100644 drivers/media/i2c/arducam-pivariety.h --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -730,6 +730,17 @@ config VIDEO_APTINA_PLL config VIDEO_CCS_PLL tristate +config VIDEO_ARDUCAM_PIVARIETY + tristate "Arducam Pivariety sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + help + This is a Video4Linux2 sensor driver for the Arducam + Pivariety camera series. + + To compile this driver as a module, choose M here: the + module will be called arducam-pivariety. + config VIDEO_HI556 tristate "Hynix Hi-556 sensor support" depends on I2C && VIDEO_V4L2 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ obj-$(CONFIG_VIDEO_CX25840) += cx25840/ obj-$(CONFIG_VIDEO_M5MOLS) += m5mols/ +obj-$(CONFIG_VIDEO_ARDUCAM_PIVARIETY) += arducam-pivariety.o obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o --- /dev/null +++ b/drivers/media/i2c/arducam-pivariety.c @@ -0,0 +1,1469 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Arducam Pivariety Cameras + * Copyright (C) 2022 Arducam Technology co., Ltd. + * + * Based on Sony IMX219 camera driver + * Copyright (C) 2019, Raspberry Pi (Trading) Ltd + * + * I2C read and write method is taken from the OV9281 driver + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "arducam-pivariety.h" + +static int debug; +module_param(debug, int, 0644); + +/* regulator supplies */ +static const char * const pivariety_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA", /* Analog (2.8V) supply */ + "VDIG", /* Digital Core (1.8V) supply */ + "VDDL", /* IF (1.2V) supply */ +}; + +/* The supported raw formats. */ +static const u32 codes[] = { + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_Y8_1X8, + + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_Y10_1X10, + + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_Y12_1X12, +}; + +#define ARDUCAM_NUM_SUPPLIES ARRAY_SIZE(pivariety_supply_name) + +#define ARDUCAM_XCLR_MIN_DELAY_US 10000 +#define ARDUCAM_XCLR_DELAY_RANGE_US 1000 + +#define MAX_CTRLS 32 + +struct pivariety { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_fwnode_bus_mipi_csi2 bus; + struct clk *xclk; + u32 xclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARDUCAM_NUM_SUPPLIES]; + + struct arducam_format *supported_formats; + int num_supported_formats; + int current_format_idx; + int current_resolution_idx; + int lanes; + int bayer_order_volatile; + bool wait_until_free; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *ctrls[MAX_CTRLS]; + /* V4L2 Controls */ + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + struct v4l2_rect crop; + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +static inline struct pivariety *to_pivariety(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct pivariety, sd); +} + +/* Write registers up to 4 at a time */ +static int pivariety_write_reg(struct i2c_client *client, u16 reg, u32 val) +{ + unsigned int len = sizeof(u32); + u32 buf_i, val_i = 0; + u8 buf[6]; + u8 *val_p; + __be32 val_be; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Read registers up to 4 at a time */ +static int pivariety_read_reg(struct i2c_client *client, u16 reg, u32 *val) +{ + struct i2c_msg msgs[2]; + unsigned int len = sizeof(u32); + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = data_be_p; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +static int +pivariety_read(struct pivariety *pivariety, u16 addr, u32 *value) +{ + struct v4l2_subdev *sd = &pivariety->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret, count = 0; + + while (count++ < I2C_READ_RETRY_COUNT) { + ret = pivariety_read_reg(client, addr, value); + if (!ret) { + v4l2_dbg(2, debug, sd, "%s: 0x%02x 0x%04x\n", + __func__, addr, *value); + return ret; + } + } + + v4l2_err(sd, "%s: Reading register 0x%02x failed\n", + __func__, addr); + + return ret; +} + +static int pivariety_write(struct pivariety *pivariety, u16 addr, u32 value) +{ + struct v4l2_subdev *sd = &pivariety->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret, count = 0; + + while (count++ < I2C_WRITE_RETRY_COUNT) { + ret = pivariety_write_reg(client, addr, value); + if (!ret) + return ret; + } + + v4l2_err(sd, "%s: Write 0x%04x to register 0x%02x failed\n", + __func__, value, addr); + + return ret; +} + +static int wait_for_free(struct pivariety *pivariety, int interval) +{ + u32 value; + u32 count = 0; + + while (count++ < (1000 / interval)) { + int ret = pivariety_read(pivariety, SYSTEM_IDLE_REG, &value); + + if (!ret && !value) + break; + msleep(interval); + } + + v4l2_dbg(2, debug, &pivariety->sd, "%s: End wait, Count: %d.\n", + __func__, count); + + return 0; +} + +static int is_raw(int pixformat) +{ + return pixformat >= 0x28 && pixformat <= 0x2D; +} + +static u32 bayer_to_mbus_code(int data_type, int bayer_order) +{ + const u32 depth8[] = { + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_Y8_1X8, + }; + + const u32 depth10[] = { + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_Y10_1X10, + }; + + const u32 depth12[] = { + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_Y12_1X12, + }; + + if (bayer_order < 0 || bayer_order > 4) + return 0; + + switch (data_type) { + case IMAGE_DT_RAW8: + return depth8[bayer_order]; + case IMAGE_DT_RAW10: + return depth10[bayer_order]; + case IMAGE_DT_RAW12: + return depth12[bayer_order]; + } + + return 0; +} + +static u32 yuv422_to_mbus_code(int data_type, int order) +{ + const u32 depth8[] = { + MEDIA_BUS_FMT_YUYV8_1X16, + MEDIA_BUS_FMT_YVYU8_1X16, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_VYUY8_1X16, + }; + + const u32 depth10[] = { + MEDIA_BUS_FMT_YUYV10_1X20, + MEDIA_BUS_FMT_YVYU10_1X20, + MEDIA_BUS_FMT_UYVY10_1X20, + MEDIA_BUS_FMT_VYUY10_1X20, + }; + + if (order < 0 || order > 3) + return 0; + + switch (data_type) { + case IMAGE_DT_YUV422_8: + return depth8[order]; + case IMAGE_DT_YUV422_10: + return depth10[order]; + } + + return 0; +} + +static u32 data_type_to_mbus_code(int data_type, int bayer_order) +{ + if (is_raw(data_type)) + return bayer_to_mbus_code(data_type, bayer_order); + + switch (data_type) { + case IMAGE_DT_YUV422_8: + case IMAGE_DT_YUV422_10: + return yuv422_to_mbus_code(data_type, bayer_order); + case IMAGE_DT_RGB565: + return MEDIA_BUS_FMT_RGB565_2X8_LE; + case IMAGE_DT_RGB888: + return MEDIA_BUS_FMT_RGB888_1X24; + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 pivariety_get_format_code(struct pivariety *pivariety, + struct arducam_format *format) +{ + unsigned int order, origin_order; + + lockdep_assert_held(&pivariety->mutex); + + /* + * Only the bayer format needs to transform the format. + */ + if (!is_raw(format->data_type) || + !pivariety->bayer_order_volatile || + format->bayer_order == BAYER_ORDER_GRAY) + return data_type_to_mbus_code(format->data_type, + format->bayer_order); + + order = format->bayer_order; + + origin_order = order; + + order = (pivariety->hflip && pivariety->hflip->val ? order ^ 1 : order); + order = (pivariety->vflip && pivariety->vflip->val ? order ^ 2 : order); + + v4l2_dbg(1, debug, &pivariety->sd, "%s: before: %d, after: %d.\n", + __func__, origin_order, order); + + return data_type_to_mbus_code(format->data_type, order); +} + +/* Power/clock management functions */ +static int pivariety_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + int ret; + + ret = regulator_bulk_enable(ARDUCAM_NUM_SUPPLIES, + pivariety->supplies); + if (ret) { + dev_err(dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(pivariety->xclk); + if (ret) { + dev_err(dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(pivariety->reset_gpio, 1); + usleep_range(ARDUCAM_XCLR_MIN_DELAY_US, + ARDUCAM_XCLR_MIN_DELAY_US + ARDUCAM_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(ARDUCAM_NUM_SUPPLIES, pivariety->supplies); + + return ret; +} + +static int pivariety_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + + gpiod_set_value_cansleep(pivariety->reset_gpio, 0); + regulator_bulk_disable(ARDUCAM_NUM_SUPPLIES, pivariety->supplies); + clk_disable_unprepare(pivariety->xclk); + + return 0; +} + +static int pivariety_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct pivariety *pivariety = to_pivariety(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->state, 0); + struct arducam_format *def_fmt = &pivariety->supported_formats[0]; + + /* Initialize try_fmt */ + try_fmt->width = def_fmt->resolution_set->width; + try_fmt->height = def_fmt->resolution_set->height; + try_fmt->code = def_fmt->mbus_code; + try_fmt->field = V4L2_FIELD_NONE; + + return 0; +} + +static int pivariety_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret, i; + struct pivariety *pivariety = + container_of(ctrl->handler, struct pivariety, + ctrl_handler); + struct arducam_format *supported_fmts = pivariety->supported_formats; + int num_supported_formats = pivariety->num_supported_formats; + + v4l2_dbg(3, debug, &pivariety->sd, "%s: cid = (0x%X), value = (%d).\n", + __func__, ctrl->id, ctrl->val); + + ret = pivariety_write(pivariety, CTRL_ID_REG, ctrl->id); + ret += pivariety_write(pivariety, CTRL_VALUE_REG, ctrl->val); + if (ret < 0) + return -EINVAL; + + /* When flip is set, modify all bayer formats */ + if (ctrl->id == V4L2_CID_VFLIP || ctrl->id == V4L2_CID_HFLIP) { + for (i = 0; i < num_supported_formats; i++) { + supported_fmts[i].mbus_code = + pivariety_get_format_code(pivariety, + &supported_fmts[i]); + } + } + + /* + * When starting streaming, controls are set in batches, + * and the short interval will cause some controls to be unsuccessfully + * set. + */ + if (pivariety->wait_until_free) + wait_for_free(pivariety, 1); + else + usleep_range(200, 210); + + return 0; +} + +static const struct v4l2_ctrl_ops pivariety_ctrl_ops = { + .s_ctrl = pivariety_s_ctrl, +}; + +static int pivariety_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct pivariety *pivariety = to_pivariety(sd); + struct arducam_format *supported_formats = pivariety->supported_formats; + int num_supported_formats = pivariety->num_supported_formats; + + v4l2_dbg(1, debug, sd, "%s: index = (%d)\n", __func__, code->index); + + if (code->index >= num_supported_formats) + return -EINVAL; + + code->code = supported_formats[code->index].mbus_code; + + return 0; +} + +static int pivariety_enum_framesizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + int i; + struct pivariety *pivariety = to_pivariety(sd); + struct arducam_format *supported_formats = pivariety->supported_formats; + int num_supported_formats = pivariety->num_supported_formats; + struct arducam_format *format; + struct arducam_resolution *resolution; + + v4l2_dbg(1, debug, sd, "%s: code = (0x%X), index = (%d)\n", + __func__, fse->code, fse->index); + + for (i = 0; i < num_supported_formats; i++) { + format = &supported_formats[i]; + if (fse->code == format->mbus_code) { + if (fse->index >= format->num_resolution_set) + return -EINVAL; + + resolution = &format->resolution_set[fse->index]; + fse->min_width = resolution->width; + fse->max_width = resolution->width; + fse->min_height = resolution->height; + fse->max_height = resolution->height; + + return 0; + } + } + + return -EINVAL; +} + +static int pivariety_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct pivariety *pivariety = to_pivariety(sd); + struct arducam_format *current_format; + struct v4l2_mbus_framefmt *fmt = &format->format; + int cur_res_idx; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&pivariety->mutex); + + current_format = + &pivariety->supported_formats[pivariety->current_format_idx]; + cur_res_idx = pivariety->current_resolution_idx; + format->format.width = + current_format->resolution_set[cur_res_idx].width; + format->format.height = + current_format->resolution_set[cur_res_idx].height; + format->format.code = current_format->mbus_code; + format->format.field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + + v4l2_dbg(1, debug, sd, "%s: width: (%d) height: (%d) code: (0x%X)\n", + __func__, format->format.width, format->format.height, + format->format.code); + + mutex_unlock(&pivariety->mutex); + return 0; +} + +static int pivariety_get_fmt_idx_by_code(struct pivariety *pivariety, + u32 mbus_code) +{ + int i; + u32 data_type; + struct arducam_format *formats = pivariety->supported_formats; + + for (i = 0; i < pivariety->num_supported_formats; i++) { + if (formats[i].mbus_code == mbus_code) + return i; + } + + /* + * If the specified format is not found in the list of supported + * formats, try to find a format of the same data type. + */ + for (i = 0; i < ARRAY_SIZE(codes); i++) + if (codes[i] == mbus_code) + break; + + if (i >= ARRAY_SIZE(codes)) + return -EINVAL; + + data_type = i / 5 + IMAGE_DT_RAW8; + + for (i = 0; i < pivariety->num_supported_formats; i++) { + if (formats[i].data_type == data_type) + return i; + } + + return -EINVAL; +} + +static struct v4l2_ctrl *get_control(struct pivariety *pivariety, + u32 id) +{ + int index = 0; + + while (index < MAX_CTRLS && pivariety->ctrls[index]) { + if (pivariety->ctrls[index]->id == id) + return pivariety->ctrls[index]; + index++; + } + + return NULL; +} + +static int update_control(struct pivariety *pivariety, u32 id) +{ + struct v4l2_subdev *sd = &pivariety->sd; + struct v4l2_ctrl *ctrl; + u32 min, max, step, def, id2; + int ret = 0; + + pivariety_write(pivariety, CTRL_ID_REG, id); + pivariety_read(pivariety, CTRL_ID_REG, &id2); + + v4l2_dbg(1, debug, sd, "%s: Write ID: 0x%08X Read ID: 0x%08X\n", + __func__, id, id2); + + pivariety_write(pivariety, CTRL_VALUE_REG, 0); + wait_for_free(pivariety, 1); + + ret += pivariety_read(pivariety, CTRL_MAX_REG, &max); + ret += pivariety_read(pivariety, CTRL_MIN_REG, &min); + ret += pivariety_read(pivariety, CTRL_DEF_REG, &def); + ret += pivariety_read(pivariety, CTRL_STEP_REG, &step); + + if (ret < 0) + goto err; + + if (id == NO_DATA_AVAILABLE || max == NO_DATA_AVAILABLE || + min == NO_DATA_AVAILABLE || def == NO_DATA_AVAILABLE || + step == NO_DATA_AVAILABLE) + goto err; + + v4l2_dbg(1, debug, sd, "%s: min: %d, max: %d, step: %d, def: %d\n", + __func__, min, max, step, def); + + ctrl = get_control(pivariety, id); + return __v4l2_ctrl_modify_range(ctrl, min, max, step, def); + +err: + return -EINVAL; +} + +static int update_controls(struct pivariety *pivariety) +{ + int ret = 0; + int index = 0; + + wait_for_free(pivariety, 5); + + while (index < MAX_CTRLS && pivariety->ctrls[index]) { + ret += update_control(pivariety, pivariety->ctrls[index]->id); + index++; + } + + return ret; +} + +static int pivariety_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + int i, j; + struct pivariety *pivariety = to_pivariety(sd); + struct arducam_format *supported_formats = pivariety->supported_formats; + + if (format->pad != 0) + return -EINVAL; + + mutex_lock(&pivariety->mutex); + + format->format.colorspace = V4L2_COLORSPACE_RAW; + format->format.field = V4L2_FIELD_NONE; + + v4l2_dbg(1, debug, sd, "%s: code: 0x%X, width: %d, height: %d\n", + __func__, format->format.code, format->format.width, + format->format.height); + + i = pivariety_get_fmt_idx_by_code(pivariety, format->format.code); + if (i < 0) + i = 0; + + format->format.code = supported_formats[i].mbus_code; + + for (j = 0; j < supported_formats[i].num_resolution_set; j++) { + if (supported_formats[i].resolution_set[j].width == + format->format.width && + supported_formats[i].resolution_set[j].height == + format->format.height) { + v4l2_dbg(1, debug, sd, + "%s: format match.\n", __func__); + v4l2_dbg(1, debug, sd, + "%s: set format to device: %d %d.\n", + __func__, supported_formats[i].index, j); + + pivariety_write(pivariety, PIXFORMAT_INDEX_REG, + supported_formats[i].index); + pivariety_write(pivariety, RESOLUTION_INDEX_REG, j); + + pivariety->current_format_idx = i; + pivariety->current_resolution_idx = j; + + update_controls(pivariety); + + goto unlock; + } + } + + format->format.width = supported_formats[i].resolution_set[0].width; + format->format.height = supported_formats[i].resolution_set[0].height; + + pivariety_write(pivariety, PIXFORMAT_INDEX_REG, + supported_formats[i].index); + pivariety_write(pivariety, RESOLUTION_INDEX_REG, 0); + + pivariety->current_format_idx = i; + pivariety->current_resolution_idx = 0; + update_controls(pivariety); + +unlock: + + mutex_unlock(&pivariety->mutex); + + return 0; +} + +/* Start streaming */ +static int pivariety_start_streaming(struct pivariety *pivariety) +{ + int ret; + + /* set stream on register */ + ret = pivariety_write(pivariety, MODE_SELECT_REG, + ARDUCAM_MODE_STREAMING); + + if (ret) + return ret; + + wait_for_free(pivariety, 2); + + /* + * When starting streaming, controls are set in batches, + * and the short interval will cause some controls to be unsuccessfully + * set. + */ + pivariety->wait_until_free = true; + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(pivariety->sd.ctrl_handler); + + pivariety->wait_until_free = false; + if (ret) + return ret; + + wait_for_free(pivariety, 2); + + return ret; +} + +static int pivariety_read_sel(struct pivariety *pivariety, + struct v4l2_rect *rect) +{ + int ret = 0; + + ret += pivariety_read(pivariety, IPC_SEL_TOP_REG, &rect->top); + ret += pivariety_read(pivariety, IPC_SEL_LEFT_REG, &rect->left); + ret += pivariety_read(pivariety, IPC_SEL_WIDTH_REG, &rect->width); + ret += pivariety_read(pivariety, IPC_SEL_HEIGHT_REG, &rect->height); + + if (ret || rect->top == NO_DATA_AVAILABLE || + rect->left == NO_DATA_AVAILABLE || + rect->width == NO_DATA_AVAILABLE || + rect->height == NO_DATA_AVAILABLE) { + v4l2_err(&pivariety->sd, "%s: Failed to read selection.\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_rect * +__pivariety_get_pad_crop(struct pivariety *pivariety, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + int ret; + + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&pivariety->sd, sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + ret = pivariety_read_sel(pivariety, &pivariety->crop); + if (ret) + return NULL; + return &pivariety->crop; + } + + return NULL; +} + +static int pivariety_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + int ret = 0; + struct v4l2_rect rect; + struct pivariety *pivariety = to_pivariety(sd); + + ret = pivariety_write(pivariety, IPC_SEL_TARGET_REG, sel->target); + if (ret) { + v4l2_err(sd, "%s: Write register 0x%02x failed\n", + __func__, IPC_SEL_TARGET_REG); + return -EINVAL; + } + + wait_for_free(pivariety, 2); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + mutex_lock(&pivariety->mutex); + sel->r = *__pivariety_get_pad_crop(pivariety, sd_state, + sel->pad, + sel->which); + mutex_unlock(&pivariety->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + ret = pivariety_read_sel(pivariety, &rect); + if (ret) + return -EINVAL; + + sel->r = rect; + return 0; + } + + return -EINVAL; +} + +/* Stop streaming */ +static int pivariety_stop_streaming(struct pivariety *pivariety) +{ + int ret; + + /* set stream off register */ + ret = pivariety_write(pivariety, MODE_SELECT_REG, ARDUCAM_MODE_STANDBY); + if (ret) + v4l2_err(&pivariety->sd, "%s failed to set stream\n", __func__); + + /* + * Return success even if it was an error, as there is nothing the + * caller can do about it. + */ + return 0; +} + +static int pivariety_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct pivariety *pivariety = to_pivariety(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&pivariety->mutex); + if (pivariety->streaming == enable) { + mutex_unlock(&pivariety->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = pivariety_start_streaming(pivariety); + if (ret) + goto err_rpm_put; + } else { + pivariety_stop_streaming(pivariety); + pm_runtime_put(&client->dev); + } + + pivariety->streaming = enable; + + /* + * vflip and hflip cannot change during streaming + * Pivariety may not implement flip control. + */ + if (pivariety->vflip) + __v4l2_ctrl_grab(pivariety->vflip, enable); + + if (pivariety->hflip) + __v4l2_ctrl_grab(pivariety->hflip, enable); + + mutex_unlock(&pivariety->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&pivariety->mutex); + + return ret; +} + +static int __maybe_unused pivariety_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + + if (pivariety->streaming) + pivariety_stop_streaming(pivariety); + + return 0; +} + +static int __maybe_unused pivariety_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + int ret; + + if (pivariety->streaming) { + ret = pivariety_start_streaming(pivariety); + if (ret) + goto error; + } + + return 0; + +error: + pivariety_stop_streaming(pivariety); + pivariety->streaming = 0; + return ret; +} + +static int pivariety_get_regulators(struct pivariety *pivariety) +{ + struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd); + int i; + + for (i = 0; i < ARDUCAM_NUM_SUPPLIES; i++) + pivariety->supplies[i].supply = pivariety_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + ARDUCAM_NUM_SUPPLIES, + pivariety->supplies); +} + +static int pivariety_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct pivariety *pivariety = to_pivariety(sd); + const u32 mask = V4L2_MBUS_CSI2_LANE_MASK; + + if (pivariety->lanes > pivariety->bus.num_data_lanes) + return -EINVAL; + + cfg->type = V4L2_MBUS_CSI2_DPHY; + cfg->flags = (pivariety->lanes << __ffs(mask)) & mask; + + return 0; +} + +static const struct v4l2_subdev_core_ops pivariety_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops pivariety_video_ops = { + .s_stream = pivariety_set_stream, +}; + +static const struct v4l2_subdev_pad_ops pivariety_pad_ops = { + .enum_mbus_code = pivariety_enum_mbus_code, + .get_fmt = pivariety_get_fmt, + .set_fmt = pivariety_set_fmt, + .enum_frame_size = pivariety_enum_framesizes, + .get_selection = pivariety_get_selection, + .get_mbus_config = pivariety_get_mbus_config, +}; + +static const struct v4l2_subdev_ops pivariety_subdev_ops = { + .core = &pivariety_core_ops, + .video = &pivariety_video_ops, + .pad = &pivariety_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops pivariety_internal_ops = { + .open = pivariety_open, +}; + +static void pivariety_free_controls(struct pivariety *pivariety) +{ + v4l2_ctrl_handler_free(pivariety->sd.ctrl_handler); + mutex_destroy(&pivariety->mutex); +} + +static int pivariety_get_length_of_set(struct pivariety *pivariety, + u16 idx_reg, u16 val_reg) +{ + int ret; + int index = 0; + u32 val; + + while (1) { + ret = pivariety_write(pivariety, idx_reg, index); + ret += pivariety_read(pivariety, val_reg, &val); + + if (ret < 0) + return -1; + + if (val == NO_DATA_AVAILABLE) + break; + index++; + } + pivariety_write(pivariety, idx_reg, 0); + return index; +} + +static int pivariety_enum_resolution(struct pivariety *pivariety, + struct arducam_format *format) +{ + struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd); + int index = 0; + u32 width, height; + int num_resolution = 0; + int ret; + + num_resolution = pivariety_get_length_of_set(pivariety, + RESOLUTION_INDEX_REG, + FORMAT_WIDTH_REG); + if (num_resolution < 0) + goto err; + + format->resolution_set = devm_kzalloc(&client->dev, + sizeof(*format->resolution_set) * + num_resolution, + GFP_KERNEL); + while (1) { + ret = pivariety_write(pivariety, RESOLUTION_INDEX_REG, index); + ret += pivariety_read(pivariety, FORMAT_WIDTH_REG, &width); + ret += pivariety_read(pivariety, FORMAT_HEIGHT_REG, &height); + + if (ret < 0) + goto err; + + if (width == NO_DATA_AVAILABLE || height == NO_DATA_AVAILABLE) + break; + + format->resolution_set[index].width = width; + format->resolution_set[index].height = height; + + index++; + } + + format->num_resolution_set = index; + pivariety_write(pivariety, RESOLUTION_INDEX_REG, 0); + return 0; +err: + return -ENODEV; +} + +static int pivariety_enum_pixformat(struct pivariety *pivariety) +{ + int ret = 0; + u32 mbus_code = 0; + int pixfmt_type; + int bayer_order; + int bayer_order_not_volatile; + int lanes; + int index = 0; + int num_pixformat = 0; + struct arducam_format *arducam_fmt; + struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd); + + num_pixformat = pivariety_get_length_of_set(pivariety, + PIXFORMAT_INDEX_REG, + PIXFORMAT_TYPE_REG); + + if (num_pixformat < 0) + goto err; + + ret = pivariety_read(pivariety, FLIPS_DONT_CHANGE_ORDER_REG, + &bayer_order_not_volatile); + if (bayer_order_not_volatile == NO_DATA_AVAILABLE) + pivariety->bayer_order_volatile = 1; + else + pivariety->bayer_order_volatile = !bayer_order_not_volatile; + + if (ret < 0) + goto err; + + pivariety->supported_formats = + devm_kzalloc(&client->dev, + sizeof(*pivariety->supported_formats) * + num_pixformat, + GFP_KERNEL); + + while (1) { + ret = pivariety_write(pivariety, PIXFORMAT_INDEX_REG, index); + ret += pivariety_read(pivariety, PIXFORMAT_TYPE_REG, + &pixfmt_type); + + if (pixfmt_type == NO_DATA_AVAILABLE) + break; + + ret += pivariety_read(pivariety, MIPI_LANES_REG, &lanes); + if (lanes == NO_DATA_AVAILABLE) + break; + + ret += pivariety_read(pivariety, PIXFORMAT_ORDER_REG, + &bayer_order); + if (ret < 0) + goto err; + + mbus_code = data_type_to_mbus_code(pixfmt_type, bayer_order); + arducam_fmt = &pivariety->supported_formats[index]; + arducam_fmt->index = index; + arducam_fmt->mbus_code = mbus_code; + arducam_fmt->bayer_order = bayer_order; + arducam_fmt->data_type = pixfmt_type; + if (pivariety_enum_resolution(pivariety, arducam_fmt)) + goto err; + + index++; + } + + pivariety_write(pivariety, PIXFORMAT_INDEX_REG, 0); + pivariety->num_supported_formats = index; + pivariety->current_format_idx = 0; + pivariety->current_resolution_idx = 0; + pivariety->lanes = lanes; + + return 0; + +err: + return -ENODEV; +} + +static const char *pivariety_ctrl_get_name(u32 id) +{ + switch (id) { + case V4L2_CID_ARDUCAM_EXT_TRI: + return "trigger_mode"; + case V4L2_CID_ARDUCAM_IRCUT: + return "ircut"; + default: + return NULL; + } +} + +enum v4l2_ctrl_type pivariety_get_v4l2_ctrl_type(u32 id) +{ + switch (id) { + case V4L2_CID_ARDUCAM_EXT_TRI: + return V4L2_CTRL_TYPE_BOOLEAN; + case V4L2_CID_ARDUCAM_IRCUT: + return V4L2_CTRL_TYPE_BOOLEAN; + default: + return V4L2_CTRL_TYPE_INTEGER; + } +} + +static struct v4l2_ctrl *v4l2_ctrl_new_arducam(struct v4l2_ctrl_handler *hdl, + const struct v4l2_ctrl_ops *ops, + u32 id, s64 min, s64 max, + u64 step, s64 def) +{ + struct v4l2_ctrl_config ctrl_cfg = { + .ops = ops, + .id = id, + .name = NULL, + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = 0, + .min = min, + .max = max, + .def = def, + .step = step, + }; + + ctrl_cfg.name = pivariety_ctrl_get_name(id); + ctrl_cfg.type = pivariety_get_v4l2_ctrl_type(id); + + return v4l2_ctrl_new_custom(hdl, &ctrl_cfg, NULL); +} + +static int pivariety_enum_controls(struct pivariety *pivariety) +{ + struct v4l2_subdev *sd = &pivariety->sd; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct v4l2_ctrl_handler *ctrl_hdlr = &pivariety->ctrl_handler; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl **ctrl = pivariety->ctrls; + int ret, index, num_ctrls; + u32 id, min, max, def, step; + + num_ctrls = pivariety_get_length_of_set(pivariety, CTRL_INDEX_REG, + CTRL_ID_REG); + if (num_ctrls < 0) + goto err; + + v4l2_dbg(1, debug, sd, "%s: num_ctrls = %d\n", + __func__, num_ctrls); + + ret = v4l2_ctrl_handler_init(ctrl_hdlr, num_ctrls); + if (ret) + return ret; + + index = 0; + while (1) { + ret = pivariety_write(pivariety, CTRL_INDEX_REG, index); + pivariety_write(pivariety, CTRL_VALUE_REG, 0); + wait_for_free(pivariety, 1); + + ret += pivariety_read(pivariety, CTRL_ID_REG, &id); + ret += pivariety_read(pivariety, CTRL_MAX_REG, &max); + ret += pivariety_read(pivariety, CTRL_MIN_REG, &min); + ret += pivariety_read(pivariety, CTRL_DEF_REG, &def); + ret += pivariety_read(pivariety, CTRL_STEP_REG, &step); + if (ret < 0) + goto err; + + if (id == NO_DATA_AVAILABLE || max == NO_DATA_AVAILABLE || + min == NO_DATA_AVAILABLE || def == NO_DATA_AVAILABLE || + step == NO_DATA_AVAILABLE) + break; + + v4l2_dbg(1, debug, sd, + "%s: index = %d, id = 0x%x, max = %d, min = %d, def = %d, step = %d\n", + __func__, index, id, max, min, def, step); + + if (v4l2_ctrl_get_name(id)) { + *ctrl = v4l2_ctrl_new_std(ctrl_hdlr, + &pivariety_ctrl_ops, + id, min, + max, step, + def); + v4l2_dbg(1, debug, sd, "%s: ctrl: 0x%p\n", + __func__, *ctrl); + } else if (pivariety_ctrl_get_name(id)) { + *ctrl = v4l2_ctrl_new_arducam(ctrl_hdlr, + &pivariety_ctrl_ops, + id, min, max, step, def); + + v4l2_dbg(1, debug, sd, + "%s: new custom ctrl, ctrl: 0x%p.\n", + __func__, *ctrl); + } else { + index++; + continue; + } + + if (!*ctrl) + goto err; + + switch (id) { + case V4L2_CID_HFLIP: + pivariety->hflip = *ctrl; + if (pivariety->bayer_order_volatile) + pivariety->hflip->flags |= + V4L2_CTRL_FLAG_MODIFY_LAYOUT; + break; + + case V4L2_CID_VFLIP: + pivariety->vflip = *ctrl; + if (pivariety->bayer_order_volatile) + pivariety->vflip->flags |= + V4L2_CTRL_FLAG_MODIFY_LAYOUT; + break; + + case V4L2_CID_HBLANK: + (*ctrl)->flags |= V4L2_CTRL_FLAG_READ_ONLY; + break; + } + + ctrl++; + index++; + } + + pivariety_write(pivariety, CTRL_INDEX_REG, 0); + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto err; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, + &pivariety_ctrl_ops, + &props); + if (ret) + goto err; + + pivariety->sd.ctrl_handler = ctrl_hdlr; + v4l2_ctrl_handler_setup(ctrl_hdlr); + return 0; +err: + return -ENODEV; +} + +static int pivariety_parse_dt(struct pivariety *pivariety, struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + /* Get CSI2 bus config */ + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + pivariety->bus = ep_cfg.bus.mipi_csi2; + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int pivariety_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct pivariety *pivariety; + u32 device_id, firmware_version; + int ret; + + pivariety = devm_kzalloc(&client->dev, sizeof(*pivariety), GFP_KERNEL); + if (!pivariety) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&pivariety->sd, client, + &pivariety_subdev_ops); + + if (pivariety_parse_dt(pivariety, dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + pivariety->xclk = devm_clk_get(dev, "xclk"); + if (IS_ERR(pivariety->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(pivariety->xclk); + } + + pivariety->xclk_freq = clk_get_rate(pivariety->xclk); + if (pivariety->xclk_freq != 24000000) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + pivariety->xclk_freq); + return -EINVAL; + } + + ret = pivariety_get_regulators(pivariety); + if (ret) + return ret; + + /* Request optional enable pin */ + pivariety->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + ret = pivariety_power_on(dev); + if (ret) + return ret; + + ret = pivariety_read(pivariety, DEVICE_ID_REG, &device_id); + if (ret || device_id != DEVICE_ID) { + dev_err(dev, "probe failed\n"); + ret = -ENODEV; + goto error_power_off; + } + + ret = pivariety_read(pivariety, DEVICE_VERSION_REG, &firmware_version); + if (ret) + dev_err(dev, "read firmware version failed\n"); + + dev_info(dev, "firmware version: 0x%04X\n", firmware_version); + + if (pivariety_enum_pixformat(pivariety)) { + dev_err(dev, "enum pixformat failed.\n"); + ret = -ENODEV; + goto error_power_off; + } + + if (pivariety_enum_controls(pivariety)) { + dev_err(dev, "enum controls failed.\n"); + ret = -ENODEV; + goto error_power_off; + } + + /* Initialize subdev */ + pivariety->sd.internal_ops = &pivariety_internal_ops; + pivariety->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + pivariety->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + /* Initialize source pad */ + pivariety->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&pivariety->sd.entity, 1, &pivariety->pad); + if (ret) + goto error_handler_free; + + ret = v4l2_async_register_subdev_sensor(&pivariety->sd); + if (ret < 0) + goto error_media_entity; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&pivariety->sd.entity); + +error_handler_free: + pivariety_free_controls(pivariety); + +error_power_off: + pivariety_power_off(dev); + + return ret; +} + +static int pivariety_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct pivariety *pivariety = to_pivariety(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + pivariety_free_controls(pivariety); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static const struct dev_pm_ops pivariety_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pivariety_suspend, pivariety_resume) + SET_RUNTIME_PM_OPS(pivariety_power_off, pivariety_power_on, NULL) +}; + +static const struct of_device_id arducam_pivariety_dt_ids[] = { + { .compatible = "arducam,arducam-pivariety" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, arducam_pivariety_dt_ids); + +static struct i2c_driver arducam_pivariety_i2c_driver = { + .driver = { + .name = "arducam-pivariety", + .of_match_table = arducam_pivariety_dt_ids, + .pm = &pivariety_pm_ops, + }, + .probe = pivariety_probe, + .remove = pivariety_remove, +}; + +module_i2c_driver(arducam_pivariety_i2c_driver); + +MODULE_AUTHOR("Lee Jackson "); +MODULE_DESCRIPTION("Arducam Pivariety v4l2 driver"); +MODULE_LICENSE("GPL v2"); --- /dev/null +++ b/drivers/media/i2c/arducam-pivariety.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ARDUCAM_PIVARIETY_H_ +#define _ARDUCAM_PIVARIETY_H_ + +#define DEVICE_REG_BASE 0x0100 +#define PIXFORMAT_REG_BASE 0x0200 +#define FORMAT_REG_BASE 0x0300 +#define CTRL_REG_BASE 0x0400 +#define IPC_REG_BASE 0x0600 + +#define ARDUCAM_MODE_STANDBY 0x00 +#define ARDUCAM_MODE_STREAMING 0x01 + +#define MODE_SELECT_REG (DEVICE_REG_BASE | 0x0000) +#define DEVICE_VERSION_REG (DEVICE_REG_BASE | 0x0001) +#define SENSOR_ID_REG (DEVICE_REG_BASE | 0x0002) +#define DEVICE_ID_REG (DEVICE_REG_BASE | 0x0003) +#define SYSTEM_IDLE_REG (DEVICE_REG_BASE | 0x0007) + +#define PIXFORMAT_INDEX_REG (PIXFORMAT_REG_BASE | 0x0000) +#define PIXFORMAT_TYPE_REG (PIXFORMAT_REG_BASE | 0x0001) +#define PIXFORMAT_ORDER_REG (PIXFORMAT_REG_BASE | 0x0002) +#define MIPI_LANES_REG (PIXFORMAT_REG_BASE | 0x0003) +#define FLIPS_DONT_CHANGE_ORDER_REG (PIXFORMAT_REG_BASE | 0x0004) + +#define RESOLUTION_INDEX_REG (FORMAT_REG_BASE | 0x0000) +#define FORMAT_WIDTH_REG (FORMAT_REG_BASE | 0x0001) +#define FORMAT_HEIGHT_REG (FORMAT_REG_BASE | 0x0002) + +#define CTRL_INDEX_REG (CTRL_REG_BASE | 0x0000) +#define CTRL_ID_REG (CTRL_REG_BASE | 0x0001) +#define CTRL_MIN_REG (CTRL_REG_BASE | 0x0002) +#define CTRL_MAX_REG (CTRL_REG_BASE | 0x0003) +#define CTRL_STEP_REG (CTRL_REG_BASE | 0x0004) +#define CTRL_DEF_REG (CTRL_REG_BASE | 0x0005) +#define CTRL_VALUE_REG (CTRL_REG_BASE | 0x0006) + +#define IPC_SEL_TARGET_REG (IPC_REG_BASE | 0x0000) +#define IPC_SEL_TOP_REG (IPC_REG_BASE | 0x0001) +#define IPC_SEL_LEFT_REG (IPC_REG_BASE | 0x0002) +#define IPC_SEL_WIDTH_REG (IPC_REG_BASE | 0x0003) +#define IPC_SEL_HEIGHT_REG (IPC_REG_BASE | 0x0004) +#define IPC_DELAY_REG (IPC_REG_BASE | 0x0005) + +#define NO_DATA_AVAILABLE 0xFFFFFFFE + +#define DEVICE_ID 0x0030 + +#define I2C_READ_RETRY_COUNT 3 +#define I2C_WRITE_RETRY_COUNT 2 + +#define V4L2_CID_ARDUCAM_BASE (V4L2_CID_USER_BASE + 0x1000) +#define V4L2_CID_ARDUCAM_EXT_TRI (V4L2_CID_ARDUCAM_BASE + 1) +#define V4L2_CID_ARDUCAM_IRCUT (V4L2_CID_ARDUCAM_BASE + 8) + +enum image_dt { + IMAGE_DT_YUV420_8 = 0x18, + IMAGE_DT_YUV420_10, + + IMAGE_DT_YUV420CSPS_8 = 0x1C, + IMAGE_DT_YUV420CSPS_10, + IMAGE_DT_YUV422_8, + IMAGE_DT_YUV422_10, + IMAGE_DT_RGB444, + IMAGE_DT_RGB555, + IMAGE_DT_RGB565, + IMAGE_DT_RGB666, + IMAGE_DT_RGB888, + + IMAGE_DT_RAW6 = 0x28, + IMAGE_DT_RAW7, + IMAGE_DT_RAW8, + IMAGE_DT_RAW10, + IMAGE_DT_RAW12, + IMAGE_DT_RAW14, +}; + +enum bayer_order { + BAYER_ORDER_BGGR = 0, + BAYER_ORDER_GBRG = 1, + BAYER_ORDER_GRBG = 2, + BAYER_ORDER_RGGB = 3, + BAYER_ORDER_GRAY = 4, +}; + +enum yuv_order { + YUV_ORDER_YUYV = 0, + YUV_ORDER_YVYU = 1, + YUV_ORDER_UYVY = 2, + YUV_ORDER_VYUY = 3, +}; + +struct arducam_resolution { + u32 width; + u32 height; +}; + +struct arducam_format { + u32 index; + u32 mbus_code; + u32 bayer_order; + u32 data_type; + u32 num_resolution_set; + struct arducam_resolution *resolution_set; +}; + +#endif