2020-09-10 13:31:10

by Togorean, Bogdan

[permalink] [raw]
Subject: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

The ADDI9036 is a complete, 45 MHz, front-end solution for charge
coupled device (CCD) time of flight (TOF) imaging applications.

It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.

The programming of calibration and firmware is performed by userspace
SDK through CID_USER class IOCTLs.

Signed-off-by: Bogdan Togorean <[email protected]>
---
MAINTAINERS | 10 +
drivers/media/i2c/Kconfig | 14 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/addi9036.c | 673 +++++++++++++++++++++++++++++
include/uapi/linux/v4l2-controls.h | 6 +
5 files changed, 704 insertions(+)
create mode 100644 drivers/media/i2c/addi9036.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b5cfab015bd6..fb03ad9ca920 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -477,6 +477,16 @@ W: http://wiki.analog.com/AD7879
W: http://ez.analog.com/community/linux-device-drivers
F: drivers/input/touchscreen/ad7879.c

+ADDI9036 TOF FRONTEND DRIVER
+M: Bogdan Togorean <[email protected]>
+L: [email protected]
+S: Supported
+W: https://www.analog.com/en/products/addi9036.html
+W: http://ez.analog.com/community/linux-device-drivers
+T: git git://linuxtv.org/media_tree.git
+F: Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
+F: drivers/media/i2c/addi9036.c
+
ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR)
M: Jiri Kosina <[email protected]>
S: Maintained
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index c7ba76fee599..087dd307505d 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -725,6 +725,20 @@ config VIDEO_APTINA_PLL
config VIDEO_SMIAPP_PLL
tristate

+config VIDEO_ADDI9036
+ tristate "Analog Devices ADDI9036 ToF front-end support"
+ depends on I2C && VIDEO_V4L2
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ select REGMAP_I2C
+ help
+ This is a Video4Linux2 driver for Analog Devices ADDI9036
+ Time of Flight front-end.
+
+ To compile this driver as a module, choose M here: the
+ module will be called addi9036.
+
config VIDEO_HI556
tristate "Hynix Hi-556 sensor support"
depends on I2C && VIDEO_V4L2
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index f0a77473979d..631a7c7612ca 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
obj-$(CONFIG_VIDEO_DW9714) += dw9714.o
obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
+obj-$(CONFIG_VIDEO_ADDI9036) += addi9036.o
obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o
obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
diff --git a/drivers/media/i2c/addi9036.c b/drivers/media/i2c/addi9036.c
new file mode 100644
index 000000000000..76c3444058dc
--- /dev/null
+++ b/drivers/media/i2c/addi9036.c
@@ -0,0 +1,673 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Analog Devices ADDI9036 ToF front-end.
+ *
+ * Copyright (C) 2019-2020 Analog Devices, All Rights Reserved.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/version.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+struct addi9036_mode_info {
+ u32 width;
+ u32 height;
+ u32 pixel_rate;
+ u32 link_freq_idx;
+};
+
+struct addi9036 {
+ struct regmap *regmap;
+ struct device *dev;
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+ struct v4l2_fwnode_endpoint ep;
+ struct v4l2_mbus_framefmt fmt;
+ struct v4l2_rect crop;
+
+ const struct addi9036_mode_info *current_mode;
+
+ struct v4l2_ctrl_handler ctrls;
+ struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *link_freq;
+
+ /* lock to protect power state */
+ struct mutex power_lock;
+ int power_count;
+
+ struct gpio_desc *rst_gpio;
+
+ /* controls */
+ struct v4l2_ctrl *set_chip_config;
+ struct v4l2_ctrl *reg_read;
+};
+
+static inline struct addi9036 *to_addi9036(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct addi9036, sd);
+}
+
+#define V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY (V4L2_CID_USER_ADDI9036_BASE + 0)
+#define V4L2_CID_AD_DEV_REG_READ_QUERY (V4L2_CID_USER_ADDI9036_BASE + 1)
+
+static const struct reg_sequence addi9036_powerup_setting[] = {
+ { 0xc4c0, 0x001c },
+ { 0xc4c3, 0x001c },
+ { 0xc4d7, 0x0000 },
+ { 0xc4d5, 0x0002 },
+ { 0xc4da, 0x0001 },
+ { 0xc4f0, 0x0000 },
+ { 0xc427, 0x0003 },
+ { 0xc427, 0x0001 },
+ { 0xc427, 0x0000 },
+ { 0xc426, 0x0030 },
+ { 0xc426, 0x0010 },
+ { 0xc426, 0x0000 },
+ { 0xc423, 0x0080 },
+ { 0xc431, 0x0080 },
+ { 0x4001, 0x0007 },
+ { 0x7c22, 0x0004 }
+};
+
+static const struct reg_sequence addi9036_powerdown_setting[] = {
+ { 0xc022, 0x0001 },
+ { 0x4001, 0x0006 },
+ { 0x7c22, 0x0004 },
+ { 0xc431, 0x0082 },
+ { 0xc423, 0x0000 },
+ { 0xc426, 0x0020 },
+ { 0xc427, 0x0002 },
+ { 0xc4c0, 0x003c },
+ { 0xc4c3, 0x003c },
+ { 0xc4d5, 0x0003 },
+ { 0xc4da, 0x0000 },
+ { 0xc4d7, 0x0001 },
+ { 0xc4f0, 0x0001 }
+};
+
+static const s64 link_freq_tbl[] = {
+ 110529000,
+ 221184000,
+};
+
+/* Elements of the structure must be ordered ascending by width & height */
+static const struct addi9036_mode_info addi9036_mode_info_data[] = {
+ {
+ .width = 640,
+ .height = 480,
+ .pixel_rate = 36864000,
+ .link_freq_idx = 0 /* an index in link_freq_tbl[] */
+ },
+ {
+ .width = 640,
+ .height = 960,
+ .pixel_rate = 73728000,
+ .link_freq_idx = 1 /* an index in link_freq_tbl[] */
+ },
+};
+
+static bool addi9306_readable_register(struct device *dev, unsigned int reg)
+{
+ if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
+ ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
+ ((reg >= 0xC000) && (reg <= 0xC200)) ||
+ ((reg >= 0xC300) && (reg <= 0xC6BF)))
+ return true;
+ else
+ return false;
+}
+
+static bool addi9306_writeable_register(struct device *dev, unsigned int reg)
+{
+ if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
+ ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
+ ((reg >= 0xC000) && (reg <= 0xC113)) ||
+ ((reg >= 0xC300) && (reg <= 0xC7FF)))
+ return true;
+ else
+ return false;
+}
+
+static const struct regmap_config addi9036_i2c_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 16,
+
+ .max_register = 0xC7FF,
+ .writeable_reg = addi9306_writeable_register,
+ .readable_reg = addi9306_readable_register,
+ .cache_type = REGCACHE_NONE,
+};
+
+static int addi9036_set_power_on(struct addi9036 *addi9036)
+{
+ int ret;
+
+ if (addi9036->rst_gpio)
+ gpiod_set_value_cansleep(addi9036->rst_gpio, 0);
+
+ ret = regmap_register_patch(addi9036->regmap, addi9036_powerup_setting,
+ ARRAY_SIZE(addi9036_powerup_setting));
+ if (ret)
+ dev_err(addi9036->dev, "Could not set power up registers\n");
+
+ return ret;
+}
+
+static int addi9036_set_power_off(struct addi9036 *addi9036)
+{
+ int ret;
+
+ ret = regmap_register_patch(addi9036->regmap,
+ addi9036_powerdown_setting,
+ ARRAY_SIZE(addi9036_powerdown_setting));
+ if (ret)
+ dev_err(addi9036->dev, "could not set power down registers\n");
+
+ if (addi9036->rst_gpio)
+ gpiod_set_value_cansleep(addi9036->rst_gpio, 1);
+
+ return ret;
+}
+
+static int addi9036_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct addi9036 *addi9036 = to_addi9036(sd);
+ int ret = 0;
+
+ mutex_lock(&addi9036->power_lock);
+
+ dev_dbg(addi9036->dev, "s_power: %d\n", on);
+
+ /* If the power count is modified from 0 to != 0 or from != 0 to 0,
+ * update the power state.
+ */
+ if (addi9036->power_count == !on) {
+ if (on)
+ ret = addi9036_set_power_on(addi9036);
+ else
+ ret = addi9036_set_power_off(addi9036);
+ }
+
+ if (!ret) {
+ /* Update the power count. */
+ addi9036->power_count += on ? 1 : -1;
+ WARN(addi9036->power_count < 0, "Unbalanced power count\n");
+ WARN(addi9036->power_count > 1, "Duplicated s_power call\n");
+ }
+
+ mutex_unlock(&addi9036->power_lock);
+
+ return ret;
+}
+
+static int addi9036_set_chip_config(struct addi9036 *addi9036,
+ struct v4l2_ctrl *ctrl)
+{
+ int index;
+ unsigned short *reg, *val;
+
+ reg = (unsigned short *)(ctrl->p_new.p_u16);
+ val = (unsigned short *)(ctrl->p_new.p_u16 + 1);
+
+ for (index = 0; index < ctrl->elems; index += 2) {
+ regmap_write(addi9036->regmap, *reg, *val);
+ reg += 2;
+ val += 2;
+ }
+
+ return 0;
+}
+
+static int addi9036_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct addi9036 *addi9036 = container_of(ctrl->handler,
+ struct addi9036, ctrls);
+ int ret = 0;
+ unsigned int val;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY:
+ ret = addi9036_set_chip_config(addi9036, ctrl);
+ break;
+ case V4L2_CID_AD_DEV_REG_READ_QUERY:
+ ret = regmap_read(addi9036->regmap, *(u16 *)(ctrl->p_new.p_u16),
+ &val);
+ if (ret)
+ dev_warn(addi9036->dev,
+ "could not read from register\n");
+ else
+ *(u16 *)(ctrl->p_new.p_u16) = val;
+ break;
+ case V4L2_CID_PIXEL_RATE:
+ case V4L2_CID_LINK_FREQ:
+ break;
+ default:
+ dev_err(addi9036->dev, "%s > Unhandled: %x param=%x\n",
+ __func__, ctrl->id, ctrl->val);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops addi9036_ctrl_ops = {
+ .s_ctrl = addi9036_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config addi9036_ctrl_chip_config = {
+ .ops = &addi9036_ctrl_ops,
+ .id = V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY,
+ .name = "chip_config",
+ .type = V4L2_CTRL_TYPE_U16,
+ .def = 0xFF,
+ .min = 0x00,
+ .max = 0xFFFF,
+ .step = 1,
+ .dims = { 2048 },
+ .elem_size = 2
+};
+
+static const struct v4l2_ctrl_config addi9036_ctrl_reg_read = {
+ .ops = &addi9036_ctrl_ops,
+ .id = V4L2_CID_AD_DEV_REG_READ_QUERY,
+ .name = "reg_read",
+ .type = V4L2_CTRL_TYPE_U16,
+ .def = 0,
+ .min = 0x00,
+ .max = 0xFFFF,
+ .step = 1
+};
+
+static int addi9036_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index > 0)
+ return -EINVAL;
+
+ code->code = MEDIA_BUS_FMT_SBGGR12_1X12;
+
+ return 0;
+}
+
+static int addi9036_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12)
+ return -EINVAL;
+
+ if (fse->index >= ARRAY_SIZE(addi9036_mode_info_data))
+ return -EINVAL;
+
+ fse->min_width = addi9036_mode_info_data[fse->index].width;
+ fse->max_width = addi9036_mode_info_data[fse->index].width;
+ fse->min_height = addi9036_mode_info_data[fse->index].height;
+ fse->max_height = addi9036_mode_info_data[fse->index].height;
+
+ return 0;
+}
+
+static struct v4l2_mbus_framefmt *
+addi9036_get_pad_format(struct addi9036 *addi9036,
+ struct v4l2_subdev_pad_config *cfg,
+ unsigned int pad,
+ enum v4l2_subdev_format_whence which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_format(&addi9036->sd, cfg, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &addi9036->fmt;
+ default:
+ return NULL;
+ }
+}
+
+static int addi9036_get_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct addi9036 *addi9036 = to_addi9036(sd);
+ struct v4l2_mbus_framefmt *pad_format;
+
+ pad_format = addi9036_get_pad_format(addi9036, cfg, format->pad,
+ format->which);
+
+ if (!pad_format)
+ return -EINVAL;
+
+ format->format = *pad_format;
+
+ return 0;
+}
+
+static struct v4l2_rect *
+addi9036_get_pad_crop(struct addi9036 *addi9036,
+ struct v4l2_subdev_pad_config *cfg,
+ unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+ switch (which) {
+ case V4L2_SUBDEV_FORMAT_TRY:
+ return v4l2_subdev_get_try_crop(&addi9036->sd, cfg, pad);
+ case V4L2_SUBDEV_FORMAT_ACTIVE:
+ return &addi9036->crop;
+ default:
+ return NULL;
+ }
+}
+
+static const struct addi9036_mode_info *
+addi9036_find_nearest_mode(unsigned int width, unsigned int height)
+{
+ unsigned int i;
+
+ for (i = ARRAY_SIZE(addi9036_mode_info_data) - 1; i > 0; i--) {
+ if (addi9036_mode_info_data[i].width <= width &&
+ addi9036_mode_info_data[i].height <= height)
+ break;
+ }
+
+ return &addi9036_mode_info_data[i];
+}
+
+static int addi9036_set_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct addi9036 *addi9036 = to_addi9036(sd);
+ struct v4l2_mbus_framefmt *framefmt;
+ struct v4l2_rect *crop;
+ const struct addi9036_mode_info *new_mode;
+ int ret;
+
+ dev_dbg(addi9036->dev, "set_fmt: %x %dx%d\n",
+ format->format.code, format->format.width,
+ format->format.height);
+
+ crop = addi9036_get_pad_crop(addi9036, cfg, format->pad,
+ format->which);
+
+ if (!crop)
+ return -EINVAL;
+
+ new_mode = addi9036_find_nearest_mode(format->format.width,
+ format->format.height);
+ crop->width = new_mode->width;
+ crop->height = new_mode->height;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+ ret = v4l2_ctrl_s_ctrl_int64(addi9036->pixel_rate,
+ new_mode->pixel_rate);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_ctrl_s_ctrl(addi9036->link_freq,
+ new_mode->link_freq_idx);
+ if (ret < 0)
+ return ret;
+
+ addi9036->current_mode = new_mode;
+ }
+
+ framefmt = addi9036_get_pad_format(addi9036, cfg, format->pad,
+ format->which);
+
+ if (!framefmt)
+ return -EINVAL;
+
+ framefmt->width = crop->width;
+ framefmt->height = crop->height;
+ framefmt->code = MEDIA_BUS_FMT_SBGGR12_1X12;
+ framefmt->field = V4L2_FIELD_NONE;
+ framefmt->colorspace = V4L2_COLORSPACE_SRGB;
+
+ format->format = *framefmt;
+
+ return 0;
+}
+
+static int addi9036_entity_init_cfg(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_pad_config *cfg)
+{
+ struct v4l2_subdev_format fmt = { 0 };
+
+ if (cfg)
+ fmt.which = V4L2_SUBDEV_FORMAT_TRY;
+ else
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+
+ fmt.format.width = 640;
+ fmt.format.height = 960;
+
+ addi9036_set_format(subdev, cfg, &fmt);
+
+ return 0;
+}
+
+static int addi9036_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_selection *sel)
+{
+ struct addi9036 *addi9036 = to_addi9036(sd);
+
+ if (sel->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+
+ sel->r = *addi9036_get_pad_crop(addi9036, cfg, sel->pad, sel->which);
+
+ return 0;
+}
+
+static int addi9036_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+ struct addi9036 *addi9036 = to_addi9036(subdev);
+
+ dev_dbg(addi9036->dev, "stream %d\n", enable);
+
+ return 0;
+}
+
+static const struct v4l2_subdev_core_ops addi9036_core_ops = {
+ .s_power = addi9036_s_power,
+};
+
+static const struct v4l2_subdev_video_ops addi9036_video_ops = {
+ .s_stream = addi9036_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops addi9036_subdev_pad_ops = {
+ .init_cfg = addi9036_entity_init_cfg,
+ .enum_mbus_code = addi9036_enum_mbus_code,
+ .enum_frame_size = addi9036_enum_frame_size,
+ .get_fmt = addi9036_get_format,
+ .set_fmt = addi9036_set_format,
+ .get_selection = addi9036_get_selection,
+};
+
+static const struct v4l2_subdev_ops addi9036_subdev_ops = {
+ .core = &addi9036_core_ops,
+ .video = &addi9036_video_ops,
+ .pad = &addi9036_subdev_pad_ops,
+};
+
+static int addi9036_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fwnode_handle *endpoint;
+ struct addi9036 *addi9036;
+ int ret;
+
+ dev_dbg(dev, "%s: i2c addr = 0x%x\n", __func__, client->addr);
+
+ addi9036 = devm_kzalloc(dev, sizeof(struct addi9036), GFP_KERNEL);
+ if (!addi9036)
+ return -ENOMEM;
+
+ addi9036->dev = dev;
+
+ addi9036->regmap = devm_regmap_init_i2c(client,
+ &addi9036_i2c_regmap_config);
+ if (IS_ERR(addi9036->regmap)) {
+ dev_err(dev, "Error initializing i2c regmap\n");
+ return PTR_ERR(addi9036->regmap);
+ }
+
+ mutex_init(&addi9036->power_lock);
+
+ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+ if (!endpoint) {
+ dev_err(dev, "endpoint node not found\n");
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(endpoint, &addi9036->ep);
+ if (ret < 0) {
+ dev_err(dev, "parsing endpoint node failed\n");
+ return ret;
+ }
+ fwnode_handle_put(endpoint);
+
+ if (addi9036->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
+ dev_err(dev, "invalid bus type, must be MIPI CSI2\n");
+ return -EINVAL;
+ }
+
+ addi9036->rst_gpio = gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(addi9036->rst_gpio)) {
+ dev_info(dev, "Unable to get \"reset\" gpio\n");
+ return PTR_ERR(addi9036->rst_gpio);
+ }
+
+ v4l2_ctrl_handler_init(&addi9036->ctrls, 4);
+
+ addi9036->pixel_rate = v4l2_ctrl_new_std(&addi9036->ctrls,
+ &addi9036_ctrl_ops,
+ V4L2_CID_PIXEL_RATE,
+ 1, INT_MAX, 1, 1);
+ addi9036->link_freq = v4l2_ctrl_new_int_menu(&addi9036->ctrls,
+ &addi9036_ctrl_ops,
+ V4L2_CID_LINK_FREQ,
+ ARRAY_SIZE(
+ link_freq_tbl) - 1,
+ 0, link_freq_tbl);
+ if (addi9036->link_freq)
+ addi9036->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ addi9036->set_chip_config = v4l2_ctrl_new_custom(&addi9036->ctrls,
+ &addi9036_ctrl_chip_config,
+ NULL);
+
+ addi9036->reg_read = v4l2_ctrl_new_custom(&addi9036->ctrls,
+ &addi9036_ctrl_reg_read,
+ NULL);
+
+ addi9036->sd.ctrl_handler = &addi9036->ctrls;
+
+ if (addi9036->ctrls.error) {
+ dev_err(dev, "%s: control initialization error %d\n",
+ __func__, addi9036->ctrls.error);
+ ret = addi9036->ctrls.error;
+ goto free_ctrl;
+ }
+
+ v4l2_i2c_subdev_init(&addi9036->sd, client, &addi9036_subdev_ops);
+ addi9036->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ addi9036->pad.flags = MEDIA_PAD_FL_SOURCE;
+ addi9036->sd.dev = &client->dev;
+ addi9036->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+ ret = media_entity_pads_init(&addi9036->sd.entity, 1, &addi9036->pad);
+
+ if (ret < 0) {
+ dev_err(dev, "could not register media entity\n");
+ goto free_ctrl;
+ }
+
+ ret = addi9036_s_power(&addi9036->sd, true);
+ if (ret < 0) {
+ dev_err(dev, "could not power up addi9036\n");
+ goto free_entity;
+ }
+
+ dev_info(dev, "addi9036 detected at address 0x%02x\n", client->addr);
+
+ ret = addi9036_s_power(&addi9036->sd, false);
+ if (ret < 0) {
+ dev_err(dev, "could not power down addi9036\n");
+ goto free_entity;
+ }
+
+ addi9036_entity_init_cfg(&addi9036->sd, NULL);
+
+ ret = v4l2_async_register_subdev(&addi9036->sd);
+ if (ret < 0)
+ dev_err(dev, "could not register v4l2 device\n");
+
+ return 0;
+
+free_entity:
+ media_entity_cleanup(&addi9036->sd.entity);
+free_ctrl:
+ v4l2_ctrl_handler_free(&addi9036->ctrls);
+ mutex_destroy(&addi9036->power_lock);
+
+ return ret;
+}
+
+static int addi9036_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct addi9036 *addi9036 = to_addi9036(sd);
+
+ v4l2_async_unregister_subdev(&addi9036->sd);
+ media_entity_cleanup(&addi9036->sd.entity);
+ if (addi9036->rst_gpio)
+ gpiod_put(addi9036->rst_gpio);
+ v4l2_ctrl_handler_free(&addi9036->ctrls);
+ mutex_destroy(&addi9036->power_lock);
+
+ return 0;
+}
+
+static const struct i2c_device_id addi9036_id[] = {
+ { "addi9036", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, addi9036_id);
+
+static const struct of_device_id addi9036_of_match[] = {
+ { .compatible = "adi,addi9036" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, addi9036_of_match);
+
+static struct i2c_driver addi9036_i2c_driver = {
+ .driver = {
+ .of_match_table = addi9036_of_match,
+ .name = "addi9036",
+ },
+ .probe_new = addi9036_probe,
+ .remove = addi9036_remove,
+ .id_table = addi9036_id,
+};
+
+module_i2c_driver(addi9036_i2c_driver);
+
+MODULE_DESCRIPTION("Analog Devices ADDI9036 Camera Driver");
+MODULE_AUTHOR("Bogdan Togorean");
+MODULE_LICENSE("GPL v2");
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 62271418c1be..f88b56479bc1 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -198,6 +198,12 @@ enum v4l2_colorfx {
*/
#define V4L2_CID_USER_ATMEL_ISC_BASE (V4L2_CID_USER_BASE + 0x10c0)

+/*
+ * The base for the addi9036 driver controls.
+ * We reserve 16 controls for this driver.
+ */
+#define V4L2_CID_USER_ADDI9036_BASE (V4L2_CID_USER_BASE + 0x10e0)
+
/* MPEG-class control IDs */
/* The MPEG controls are applicable to all codec controls
* and the 'MPEG' part of the define is historical */
--
2.28.0


2020-09-10 13:45:29

by Togorean, Bogdan

[permalink] [raw]
Subject: [PATCH 2/2] media: dt-bindings: media: i2c: Add bindings for ADDI9036

Add YAML device tree bindings for Analog Devices Inc. ADDI9036 CCD TOF
front-end.

Signed-off-by: Bogdan Togorean <[email protected]>
---
.../bindings/media/i2c/adi,addi9036.yaml | 72 +++++++++++++++++++
1 file changed, 72 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml b/Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
new file mode 100644
index 000000000000..8af7f397d4e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/adi,addi9036.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADDI9036 VGA CCD Time of Flight Sensor
+
+maintainers:
+ - Bogdan Togorean <[email protected]>
+
+description: |-
+ The ADDI9036 is a complete, 45 MHz, front-end solution for charge coupled
+ device (CCD) time of flight (TOF) imaging applications. It is programmable
+ through I2C interface. Image data is sent through MIPI CSI-2 2 lanes and
+ can output two RAW12 packed data streams. One is IR and the other is Depth.
+ Each data stream is on a separate or same MIPI Virtual Channel, depending
+ on configuration and each have 640x480 resolution.
+
+properties:
+ compatible:
+ const: adi,addi9036
+
+ reset-gpios:
+ description: |-
+ Reference to the GPIO connected to the RST/SYNC pin, if any.
+ Must be released (set high) after all supplies are applied.
+
+ # See ../video-interfaces.txt for more details
+ port:
+ type: object
+ properties:
+ endpoint:
+ type: object
+ properties:
+ data-lanes:
+ description: |-
+ The sensor supports two-lane operation.
+ For two-lane operation the property must be set to <1 2>.
+ items:
+ - const: 1
+ - const: 2
+
+required:
+ - compatible
+ - reg
+ - port
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ addi9036: addi9036_tof@64 {
+ compatible = "adi,addi9036";
+ reg = <0x64>;
+
+ reset-gpios = <&gpio 41 1>;
+
+ port {
+ addi9036_ep: endpoint {
+ remote-endpoint = <&csi1_ep>;
+ data-lanes = <1 2>;
+ };
+ };
+ };
+ };
+
+...
--
2.28.0

2020-09-10 22:03:18

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

Hi Bogdan,

Thank you for the patch.

On Thu, Sep 10, 2020 at 07:24:06PM +0300, Bogdan Togorean wrote:
> The ADDI9036 is a complete, 45 MHz, front-end solution for charge
> coupled device (CCD) time of flight (TOF) imaging applications.
>
> It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.
>
> The programming of calibration and firmware is performed by userspace
> SDK through CID_USER class IOCTLs.

I'm afraid that's not acceptable. The firmware should be loaded using
the Linux kernel firmware API. As for calibration, is it static data ?
How is it generated ?

> Signed-off-by: Bogdan Togorean <[email protected]>
> ---
> MAINTAINERS | 10 +
> drivers/media/i2c/Kconfig | 14 +
> drivers/media/i2c/Makefile | 1 +
> drivers/media/i2c/addi9036.c | 673 +++++++++++++++++++++++++++++
> include/uapi/linux/v4l2-controls.h | 6 +
> 5 files changed, 704 insertions(+)
> create mode 100644 drivers/media/i2c/addi9036.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b5cfab015bd6..fb03ad9ca920 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -477,6 +477,16 @@ W: http://wiki.analog.com/AD7879
> W: http://ez.analog.com/community/linux-device-drivers
> F: drivers/input/touchscreen/ad7879.c
>
> +ADDI9036 TOF FRONTEND DRIVER
> +M: Bogdan Togorean <[email protected]>
> +L: [email protected]
> +S: Supported
> +W: https://www.analog.com/en/products/addi9036.html
> +W: http://ez.analog.com/community/linux-device-drivers
> +T: git git://linuxtv.org/media_tree.git
> +F: Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
> +F: drivers/media/i2c/addi9036.c
> +
> ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR)
> M: Jiri Kosina <[email protected]>
> S: Maintained
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index c7ba76fee599..087dd307505d 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -725,6 +725,20 @@ config VIDEO_APTINA_PLL
> config VIDEO_SMIAPP_PLL
> tristate
>
> +config VIDEO_ADDI9036
> + tristate "Analog Devices ADDI9036 ToF front-end support"
> + depends on I2C && VIDEO_V4L2
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + select REGMAP_I2C
> + help
> + This is a Video4Linux2 driver for Analog Devices ADDI9036
> + Time of Flight front-end.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called addi9036.
> +
> config VIDEO_HI556
> tristate "Hynix Hi-556 sensor support"
> depends on I2C && VIDEO_V4L2
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index f0a77473979d..631a7c7612ca 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
> obj-$(CONFIG_VIDEO_DW9714) += dw9714.o
> obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
> obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
> +obj-$(CONFIG_VIDEO_ADDI9036) += addi9036.o
> obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
> obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o
> obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
> diff --git a/drivers/media/i2c/addi9036.c b/drivers/media/i2c/addi9036.c
> new file mode 100644
> index 000000000000..76c3444058dc
> --- /dev/null
> +++ b/drivers/media/i2c/addi9036.c
> @@ -0,0 +1,673 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for the Analog Devices ADDI9036 ToF front-end.
> + *
> + * Copyright (C) 2019-2020 Analog Devices, All Rights Reserved.
> + *
> + */
> +
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +#include <linux/version.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +struct addi9036_mode_info {
> + u32 width;
> + u32 height;
> + u32 pixel_rate;
> + u32 link_freq_idx;
> +};
> +
> +struct addi9036 {
> + struct regmap *regmap;
> + struct device *dev;
> + struct v4l2_subdev sd;
> + struct media_pad pad;
> + struct v4l2_fwnode_endpoint ep;
> + struct v4l2_mbus_framefmt fmt;
> + struct v4l2_rect crop;
> +
> + const struct addi9036_mode_info *current_mode;
> +
> + struct v4l2_ctrl_handler ctrls;
> + struct v4l2_ctrl *pixel_rate;
> + struct v4l2_ctrl *link_freq;
> +
> + /* lock to protect power state */
> + struct mutex power_lock;
> + int power_count;
> +
> + struct gpio_desc *rst_gpio;
> +
> + /* controls */
> + struct v4l2_ctrl *set_chip_config;
> + struct v4l2_ctrl *reg_read;
> +};
> +
> +static inline struct addi9036 *to_addi9036(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct addi9036, sd);
> +}
> +
> +#define V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY (V4L2_CID_USER_ADDI9036_BASE + 0)
> +#define V4L2_CID_AD_DEV_REG_READ_QUERY (V4L2_CID_USER_ADDI9036_BASE + 1)
> +
> +static const struct reg_sequence addi9036_powerup_setting[] = {
> + { 0xc4c0, 0x001c },
> + { 0xc4c3, 0x001c },
> + { 0xc4d7, 0x0000 },
> + { 0xc4d5, 0x0002 },
> + { 0xc4da, 0x0001 },
> + { 0xc4f0, 0x0000 },
> + { 0xc427, 0x0003 },
> + { 0xc427, 0x0001 },
> + { 0xc427, 0x0000 },
> + { 0xc426, 0x0030 },
> + { 0xc426, 0x0010 },
> + { 0xc426, 0x0000 },
> + { 0xc423, 0x0080 },
> + { 0xc431, 0x0080 },
> + { 0x4001, 0x0007 },
> + { 0x7c22, 0x0004 }
> +};
> +
> +static const struct reg_sequence addi9036_powerdown_setting[] = {
> + { 0xc022, 0x0001 },
> + { 0x4001, 0x0006 },
> + { 0x7c22, 0x0004 },
> + { 0xc431, 0x0082 },
> + { 0xc423, 0x0000 },
> + { 0xc426, 0x0020 },
> + { 0xc427, 0x0002 },
> + { 0xc4c0, 0x003c },
> + { 0xc4c3, 0x003c },
> + { 0xc4d5, 0x0003 },
> + { 0xc4da, 0x0000 },
> + { 0xc4d7, 0x0001 },
> + { 0xc4f0, 0x0001 }
> +};
> +
> +static const s64 link_freq_tbl[] = {
> + 110529000,
> + 221184000,
> +};
> +
> +/* Elements of the structure must be ordered ascending by width & height */
> +static const struct addi9036_mode_info addi9036_mode_info_data[] = {
> + {
> + .width = 640,
> + .height = 480,
> + .pixel_rate = 36864000,
> + .link_freq_idx = 0 /* an index in link_freq_tbl[] */
> + },
> + {
> + .width = 640,
> + .height = 960,
> + .pixel_rate = 73728000,
> + .link_freq_idx = 1 /* an index in link_freq_tbl[] */
> + },
> +};
> +
> +static bool addi9306_readable_register(struct device *dev, unsigned int reg)
> +{
> + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> + ((reg >= 0xC000) && (reg <= 0xC200)) ||
> + ((reg >= 0xC300) && (reg <= 0xC6BF)))
> + return true;
> + else
> + return false;
> +}
> +
> +static bool addi9306_writeable_register(struct device *dev, unsigned int reg)
> +{
> + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> + ((reg >= 0xC000) && (reg <= 0xC113)) ||
> + ((reg >= 0xC300) && (reg <= 0xC7FF)))
> + return true;
> + else
> + return false;
> +}
> +
> +static const struct regmap_config addi9036_i2c_regmap_config = {
> + .reg_bits = 16,
> + .val_bits = 16,
> +
> + .max_register = 0xC7FF,
> + .writeable_reg = addi9306_writeable_register,
> + .readable_reg = addi9306_readable_register,
> + .cache_type = REGCACHE_NONE,
> +};
> +
> +static int addi9036_set_power_on(struct addi9036 *addi9036)
> +{
> + int ret;
> +
> + if (addi9036->rst_gpio)
> + gpiod_set_value_cansleep(addi9036->rst_gpio, 0);
> +
> + ret = regmap_register_patch(addi9036->regmap, addi9036_powerup_setting,
> + ARRAY_SIZE(addi9036_powerup_setting));
> + if (ret)
> + dev_err(addi9036->dev, "Could not set power up registers\n");
> +
> + return ret;
> +}
> +
> +static int addi9036_set_power_off(struct addi9036 *addi9036)
> +{
> + int ret;
> +
> + ret = regmap_register_patch(addi9036->regmap,
> + addi9036_powerdown_setting,
> + ARRAY_SIZE(addi9036_powerdown_setting));
> + if (ret)
> + dev_err(addi9036->dev, "could not set power down registers\n");
> +
> + if (addi9036->rst_gpio)
> + gpiod_set_value_cansleep(addi9036->rst_gpio, 1);
> +
> + return ret;
> +}
> +
> +static int addi9036_s_power(struct v4l2_subdev *sd, int on)
> +{
> + struct addi9036 *addi9036 = to_addi9036(sd);
> + int ret = 0;
> +
> + mutex_lock(&addi9036->power_lock);
> +
> + dev_dbg(addi9036->dev, "s_power: %d\n", on);
> +
> + /* If the power count is modified from 0 to != 0 or from != 0 to 0,
> + * update the power state.
> + */
> + if (addi9036->power_count == !on) {
> + if (on)
> + ret = addi9036_set_power_on(addi9036);
> + else
> + ret = addi9036_set_power_off(addi9036);
> + }
> +
> + if (!ret) {
> + /* Update the power count. */
> + addi9036->power_count += on ? 1 : -1;
> + WARN(addi9036->power_count < 0, "Unbalanced power count\n");
> + WARN(addi9036->power_count > 1, "Duplicated s_power call\n");
> + }
> +
> + mutex_unlock(&addi9036->power_lock);
> +
> + return ret;
> +}
> +
> +static int addi9036_set_chip_config(struct addi9036 *addi9036,
> + struct v4l2_ctrl *ctrl)
> +{
> + int index;
> + unsigned short *reg, *val;
> +
> + reg = (unsigned short *)(ctrl->p_new.p_u16);
> + val = (unsigned short *)(ctrl->p_new.p_u16 + 1);
> +
> + for (index = 0; index < ctrl->elems; index += 2) {
> + regmap_write(addi9036->regmap, *reg, *val);
> + reg += 2;
> + val += 2;
> + }
> +
> + return 0;
> +}
> +
> +static int addi9036_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct addi9036 *addi9036 = container_of(ctrl->handler,
> + struct addi9036, ctrls);
> + int ret = 0;
> + unsigned int val;
> +
> + switch (ctrl->id) {
> + case V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY:
> + ret = addi9036_set_chip_config(addi9036, ctrl);
> + break;
> + case V4L2_CID_AD_DEV_REG_READ_QUERY:
> + ret = regmap_read(addi9036->regmap, *(u16 *)(ctrl->p_new.p_u16),
> + &val);
> + if (ret)
> + dev_warn(addi9036->dev,
> + "could not read from register\n");
> + else
> + *(u16 *)(ctrl->p_new.p_u16) = val;
> + break;
> + case V4L2_CID_PIXEL_RATE:
> + case V4L2_CID_LINK_FREQ:
> + break;
> + default:
> + dev_err(addi9036->dev, "%s > Unhandled: %x param=%x\n",
> + __func__, ctrl->id, ctrl->val);
> + ret = -EINVAL;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops addi9036_ctrl_ops = {
> + .s_ctrl = addi9036_s_ctrl,
> +};
> +
> +static const struct v4l2_ctrl_config addi9036_ctrl_chip_config = {
> + .ops = &addi9036_ctrl_ops,
> + .id = V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY,
> + .name = "chip_config",
> + .type = V4L2_CTRL_TYPE_U16,
> + .def = 0xFF,
> + .min = 0x00,
> + .max = 0xFFFF,
> + .step = 1,
> + .dims = { 2048 },
> + .elem_size = 2
> +};
> +
> +static const struct v4l2_ctrl_config addi9036_ctrl_reg_read = {
> + .ops = &addi9036_ctrl_ops,
> + .id = V4L2_CID_AD_DEV_REG_READ_QUERY,
> + .name = "reg_read",
> + .type = V4L2_CTRL_TYPE_U16,
> + .def = 0,
> + .min = 0x00,
> + .max = 0xFFFF,
> + .step = 1
> +};
> +
> +static int addi9036_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index > 0)
> + return -EINVAL;
> +
> + code->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> +
> + return 0;
> +}
> +
> +static int addi9036_enum_frame_size(struct v4l2_subdev *subdev,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_frame_size_enum *fse)
> +{
> + if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12)
> + return -EINVAL;
> +
> + if (fse->index >= ARRAY_SIZE(addi9036_mode_info_data))
> + return -EINVAL;
> +
> + fse->min_width = addi9036_mode_info_data[fse->index].width;
> + fse->max_width = addi9036_mode_info_data[fse->index].width;
> + fse->min_height = addi9036_mode_info_data[fse->index].height;
> + fse->max_height = addi9036_mode_info_data[fse->index].height;
> +
> + return 0;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +addi9036_get_pad_format(struct addi9036 *addi9036,
> + struct v4l2_subdev_pad_config *cfg,
> + unsigned int pad,
> + enum v4l2_subdev_format_whence which)
> +{
> + switch (which) {
> + case V4L2_SUBDEV_FORMAT_TRY:
> + return v4l2_subdev_get_try_format(&addi9036->sd, cfg, pad);
> + case V4L2_SUBDEV_FORMAT_ACTIVE:
> + return &addi9036->fmt;
> + default:
> + return NULL;
> + }
> +}
> +
> +static int addi9036_get_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_format *format)
> +{
> + struct addi9036 *addi9036 = to_addi9036(sd);
> + struct v4l2_mbus_framefmt *pad_format;
> +
> + pad_format = addi9036_get_pad_format(addi9036, cfg, format->pad,
> + format->which);
> +
> + if (!pad_format)
> + return -EINVAL;
> +
> + format->format = *pad_format;
> +
> + return 0;
> +}
> +
> +static struct v4l2_rect *
> +addi9036_get_pad_crop(struct addi9036 *addi9036,
> + struct v4l2_subdev_pad_config *cfg,
> + unsigned int pad, enum v4l2_subdev_format_whence which)
> +{
> + switch (which) {
> + case V4L2_SUBDEV_FORMAT_TRY:
> + return v4l2_subdev_get_try_crop(&addi9036->sd, cfg, pad);
> + case V4L2_SUBDEV_FORMAT_ACTIVE:
> + return &addi9036->crop;
> + default:
> + return NULL;
> + }
> +}
> +
> +static const struct addi9036_mode_info *
> +addi9036_find_nearest_mode(unsigned int width, unsigned int height)
> +{
> + unsigned int i;
> +
> + for (i = ARRAY_SIZE(addi9036_mode_info_data) - 1; i > 0; i--) {
> + if (addi9036_mode_info_data[i].width <= width &&
> + addi9036_mode_info_data[i].height <= height)
> + break;
> + }
> +
> + return &addi9036_mode_info_data[i];
> +}
> +
> +static int addi9036_set_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_format *format)
> +{
> + struct addi9036 *addi9036 = to_addi9036(sd);
> + struct v4l2_mbus_framefmt *framefmt;
> + struct v4l2_rect *crop;
> + const struct addi9036_mode_info *new_mode;
> + int ret;
> +
> + dev_dbg(addi9036->dev, "set_fmt: %x %dx%d\n",
> + format->format.code, format->format.width,
> + format->format.height);
> +
> + crop = addi9036_get_pad_crop(addi9036, cfg, format->pad,
> + format->which);
> +
> + if (!crop)
> + return -EINVAL;
> +
> + new_mode = addi9036_find_nearest_mode(format->format.width,
> + format->format.height);
> + crop->width = new_mode->width;
> + crop->height = new_mode->height;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> + ret = v4l2_ctrl_s_ctrl_int64(addi9036->pixel_rate,
> + new_mode->pixel_rate);
> + if (ret < 0)
> + return ret;
> +
> + ret = v4l2_ctrl_s_ctrl(addi9036->link_freq,
> + new_mode->link_freq_idx);
> + if (ret < 0)
> + return ret;
> +
> + addi9036->current_mode = new_mode;
> + }
> +
> + framefmt = addi9036_get_pad_format(addi9036, cfg, format->pad,
> + format->which);
> +
> + if (!framefmt)
> + return -EINVAL;
> +
> + framefmt->width = crop->width;
> + framefmt->height = crop->height;
> + framefmt->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> + framefmt->field = V4L2_FIELD_NONE;
> + framefmt->colorspace = V4L2_COLORSPACE_SRGB;
> +
> + format->format = *framefmt;
> +
> + return 0;
> +}
> +
> +static int addi9036_entity_init_cfg(struct v4l2_subdev *subdev,
> + struct v4l2_subdev_pad_config *cfg)
> +{
> + struct v4l2_subdev_format fmt = { 0 };
> +
> + if (cfg)
> + fmt.which = V4L2_SUBDEV_FORMAT_TRY;
> + else
> + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> +
> + fmt.format.width = 640;
> + fmt.format.height = 960;
> +
> + addi9036_set_format(subdev, cfg, &fmt);
> +
> + return 0;
> +}
> +
> +static int addi9036_get_selection(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_selection *sel)
> +{
> + struct addi9036 *addi9036 = to_addi9036(sd);
> +
> + if (sel->target != V4L2_SEL_TGT_CROP)
> + return -EINVAL;
> +
> + sel->r = *addi9036_get_pad_crop(addi9036, cfg, sel->pad, sel->which);
> +
> + return 0;
> +}
> +
> +static int addi9036_s_stream(struct v4l2_subdev *subdev, int enable)
> +{
> + struct addi9036 *addi9036 = to_addi9036(subdev);
> +
> + dev_dbg(addi9036->dev, "stream %d\n", enable);
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_core_ops addi9036_core_ops = {
> + .s_power = addi9036_s_power,
> +};
> +
> +static const struct v4l2_subdev_video_ops addi9036_video_ops = {
> + .s_stream = addi9036_s_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops addi9036_subdev_pad_ops = {
> + .init_cfg = addi9036_entity_init_cfg,
> + .enum_mbus_code = addi9036_enum_mbus_code,
> + .enum_frame_size = addi9036_enum_frame_size,
> + .get_fmt = addi9036_get_format,
> + .set_fmt = addi9036_set_format,
> + .get_selection = addi9036_get_selection,
> +};
> +
> +static const struct v4l2_subdev_ops addi9036_subdev_ops = {
> + .core = &addi9036_core_ops,
> + .video = &addi9036_video_ops,
> + .pad = &addi9036_subdev_pad_ops,
> +};
> +
> +static int addi9036_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct fwnode_handle *endpoint;
> + struct addi9036 *addi9036;
> + int ret;
> +
> + dev_dbg(dev, "%s: i2c addr = 0x%x\n", __func__, client->addr);
> +
> + addi9036 = devm_kzalloc(dev, sizeof(struct addi9036), GFP_KERNEL);
> + if (!addi9036)
> + return -ENOMEM;
> +
> + addi9036->dev = dev;
> +
> + addi9036->regmap = devm_regmap_init_i2c(client,
> + &addi9036_i2c_regmap_config);
> + if (IS_ERR(addi9036->regmap)) {
> + dev_err(dev, "Error initializing i2c regmap\n");
> + return PTR_ERR(addi9036->regmap);
> + }
> +
> + mutex_init(&addi9036->power_lock);
> +
> + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
> + if (!endpoint) {
> + dev_err(dev, "endpoint node not found\n");
> + return -EINVAL;
> + }
> +
> + ret = v4l2_fwnode_endpoint_parse(endpoint, &addi9036->ep);
> + if (ret < 0) {
> + dev_err(dev, "parsing endpoint node failed\n");
> + return ret;
> + }
> + fwnode_handle_put(endpoint);
> +
> + if (addi9036->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
> + dev_err(dev, "invalid bus type, must be MIPI CSI2\n");
> + return -EINVAL;
> + }
> +
> + addi9036->rst_gpio = gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(addi9036->rst_gpio)) {
> + dev_info(dev, "Unable to get \"reset\" gpio\n");
> + return PTR_ERR(addi9036->rst_gpio);
> + }
> +
> + v4l2_ctrl_handler_init(&addi9036->ctrls, 4);
> +
> + addi9036->pixel_rate = v4l2_ctrl_new_std(&addi9036->ctrls,
> + &addi9036_ctrl_ops,
> + V4L2_CID_PIXEL_RATE,
> + 1, INT_MAX, 1, 1);
> + addi9036->link_freq = v4l2_ctrl_new_int_menu(&addi9036->ctrls,
> + &addi9036_ctrl_ops,
> + V4L2_CID_LINK_FREQ,
> + ARRAY_SIZE(
> + link_freq_tbl) - 1,
> + 0, link_freq_tbl);
> + if (addi9036->link_freq)
> + addi9036->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> +
> + addi9036->set_chip_config = v4l2_ctrl_new_custom(&addi9036->ctrls,
> + &addi9036_ctrl_chip_config,
> + NULL);
> +
> + addi9036->reg_read = v4l2_ctrl_new_custom(&addi9036->ctrls,
> + &addi9036_ctrl_reg_read,
> + NULL);
> +
> + addi9036->sd.ctrl_handler = &addi9036->ctrls;
> +
> + if (addi9036->ctrls.error) {
> + dev_err(dev, "%s: control initialization error %d\n",
> + __func__, addi9036->ctrls.error);
> + ret = addi9036->ctrls.error;
> + goto free_ctrl;
> + }
> +
> + v4l2_i2c_subdev_init(&addi9036->sd, client, &addi9036_subdev_ops);
> + addi9036->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + addi9036->pad.flags = MEDIA_PAD_FL_SOURCE;
> + addi9036->sd.dev = &client->dev;
> + addi9036->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> +
> + ret = media_entity_pads_init(&addi9036->sd.entity, 1, &addi9036->pad);
> +
> + if (ret < 0) {
> + dev_err(dev, "could not register media entity\n");
> + goto free_ctrl;
> + }
> +
> + ret = addi9036_s_power(&addi9036->sd, true);
> + if (ret < 0) {
> + dev_err(dev, "could not power up addi9036\n");
> + goto free_entity;
> + }
> +
> + dev_info(dev, "addi9036 detected at address 0x%02x\n", client->addr);
> +
> + ret = addi9036_s_power(&addi9036->sd, false);
> + if (ret < 0) {
> + dev_err(dev, "could not power down addi9036\n");
> + goto free_entity;
> + }
> +
> + addi9036_entity_init_cfg(&addi9036->sd, NULL);
> +
> + ret = v4l2_async_register_subdev(&addi9036->sd);
> + if (ret < 0)
> + dev_err(dev, "could not register v4l2 device\n");
> +
> + return 0;
> +
> +free_entity:
> + media_entity_cleanup(&addi9036->sd.entity);
> +free_ctrl:
> + v4l2_ctrl_handler_free(&addi9036->ctrls);
> + mutex_destroy(&addi9036->power_lock);
> +
> + return ret;
> +}
> +
> +static int addi9036_remove(struct i2c_client *client)
> +{
> + struct v4l2_subdev *sd = i2c_get_clientdata(client);
> + struct addi9036 *addi9036 = to_addi9036(sd);
> +
> + v4l2_async_unregister_subdev(&addi9036->sd);
> + media_entity_cleanup(&addi9036->sd.entity);
> + if (addi9036->rst_gpio)
> + gpiod_put(addi9036->rst_gpio);
> + v4l2_ctrl_handler_free(&addi9036->ctrls);
> + mutex_destroy(&addi9036->power_lock);
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id addi9036_id[] = {
> + { "addi9036", 0 },
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, addi9036_id);
> +
> +static const struct of_device_id addi9036_of_match[] = {
> + { .compatible = "adi,addi9036" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, addi9036_of_match);
> +
> +static struct i2c_driver addi9036_i2c_driver = {
> + .driver = {
> + .of_match_table = addi9036_of_match,
> + .name = "addi9036",
> + },
> + .probe_new = addi9036_probe,
> + .remove = addi9036_remove,
> + .id_table = addi9036_id,
> +};
> +
> +module_i2c_driver(addi9036_i2c_driver);
> +
> +MODULE_DESCRIPTION("Analog Devices ADDI9036 Camera Driver");
> +MODULE_AUTHOR("Bogdan Togorean");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
> index 62271418c1be..f88b56479bc1 100644
> --- a/include/uapi/linux/v4l2-controls.h
> +++ b/include/uapi/linux/v4l2-controls.h
> @@ -198,6 +198,12 @@ enum v4l2_colorfx {
> */
> #define V4L2_CID_USER_ATMEL_ISC_BASE (V4L2_CID_USER_BASE + 0x10c0)
>
> +/*
> + * The base for the addi9036 driver controls.
> + * We reserve 16 controls for this driver.
> + */
> +#define V4L2_CID_USER_ADDI9036_BASE (V4L2_CID_USER_BASE + 0x10e0)
> +
> /* MPEG-class control IDs */
> /* The MPEG controls are applicable to all codec controls
> * and the 'MPEG' part of the define is historical */
> --
> 2.28.0
>

--
Regards,

Laurent Pinchart

2020-09-11 15:36:03

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

Hi Bogdan,

On Fri, Sep 11, 2020 at 01:32:39PM +0000, Togorean, Bogdan wrote:
> Hi Laurent,
>
> > Hi Bogdan,
> >
> > Thank you for the patch.
>
> Thank you for review!
>
> >
> > On Thu, Sep 10, 2020 at 07:24:06PM +0300, Bogdan Togorean wrote:
> > > The ADDI9036 is a complete, 45 MHz, front-end solution for charge
> > > coupled device (CCD) time of flight (TOF) imaging applications.
> > >
> > > It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.
> > >
> > > The programming of calibration and firmware is performed by userspace
> > > SDK through CID_USER class IOCTLs.
> >
> > I'm afraid that's not acceptable. The firmware should be loaded using
> > the Linux kernel firmware API. As for calibration, is it static data ?
> > How is it generated ?
>
> Calibration data is static data, generated at production and stored on and EEPROM
> memory on camera board. The camera board consists mainly of ADDI9036 processor
> + CCD image sensor + EEPROM.
>
> The firmware is a set of (address, data) tuples that are written via I2C.
> We want this driver to be a generic one and support all implementations
> of ADDI9036 ToF processor. Now the problem is that some implementations
> store firmware + calibration on external EEPROM and some store just calibration
> data on EEPROM and firmware is loaded from a file stored in SDK. With the help
> of the 2 custom controls we move the camera model detection, firmware
> and calibration fetching from driver to user space SDK.

Can you figure out by reading EEPROM which one of these is the case?
Alternatively you could add a custom DT property IMO.

The calibration data could be loaded using request_firmware(), too. I'd use
a specific directory for just this purpose.

>
> Also an important point is that the ToF processor has 3 operating modes
> (near, medium and far). At each mode change a new set of firmware +
> calibration data is loaded into ADDI9036 chip. If we move the firmware loading
> in driver we would still need a custom enumeration control to select the working mode.

I don't see there being an issue having such a control.

--
Kind regards,

Sakari Ailus

2020-09-11 16:14:24

by Togorean, Bogdan

[permalink] [raw]
Subject: RE: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

Hi Laurent,

> Hi Bogdan,
>
> Thank you for the patch.

Thank you for review!

>
> On Thu, Sep 10, 2020 at 07:24:06PM +0300, Bogdan Togorean wrote:
> > The ADDI9036 is a complete, 45 MHz, front-end solution for charge
> > coupled device (CCD) time of flight (TOF) imaging applications.
> >
> > It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.
> >
> > The programming of calibration and firmware is performed by userspace
> > SDK through CID_USER class IOCTLs.
>
> I'm afraid that's not acceptable. The firmware should be loaded using
> the Linux kernel firmware API. As for calibration, is it static data ?
> How is it generated ?

Calibration data is static data, generated at production and stored on and EEPROM
memory on camera board. The camera board consists mainly of ADDI9036 processor
+ CCD image sensor + EEPROM.

The firmware is a set of (address, data) tuples that are written via I2C.
We want this driver to be a generic one and support all implementations
of ADDI9036 ToF processor. Now the problem is that some implementations
store firmware + calibration on external EEPROM and some store just calibration
data on EEPROM and firmware is loaded from a file stored in SDK. With the help
of the 2 custom controls we move the camera model detection, firmware
and calibration fetching from driver to user space SDK.

Also an important point is that the ToF processor has 3 operating modes
(near, medium and far). At each mode change a new set of firmware +
calibration data is loaded into ADDI9036 chip. If we move the firmware loading
in driver we would still need a custom enumeration control to select the working mode.

>
> > Signed-off-by: Bogdan Togorean <[email protected]>
> > ---
> > MAINTAINERS | 10 +
> > drivers/media/i2c/Kconfig | 14 +
> > drivers/media/i2c/Makefile | 1 +
> > drivers/media/i2c/addi9036.c | 673 +++++++++++++++++++++++++++++
> > include/uapi/linux/v4l2-controls.h | 6 +
> > 5 files changed, 704 insertions(+)
> > create mode 100644 drivers/media/i2c/addi9036.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index b5cfab015bd6..fb03ad9ca920 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -477,6 +477,16 @@ W: http://wiki.analog.com/AD7879
> > W: http://ez.analog.com/community/linux-device-drivers
> > F: drivers/input/touchscreen/ad7879.c
> >
> > +ADDI9036 TOF FRONTEND DRIVER
> > +M: Bogdan Togorean <[email protected]>
> > +L: [email protected]
> > +S: Supported
> > +W: https://www.analog.com/en/products/addi9036.html
> > +W: http://ez.analog.com/community/linux-device-drivers
> > +T: git git://linuxtv.org/media_tree.git
> > +F: Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
> > +F: drivers/media/i2c/addi9036.c
> > +
> > ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR)
> > M: Jiri Kosina <[email protected]>
> > S: Maintained
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index c7ba76fee599..087dd307505d 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -725,6 +725,20 @@ config VIDEO_APTINA_PLL
> > config VIDEO_SMIAPP_PLL
> > tristate
> >
> > +config VIDEO_ADDI9036
> > + tristate "Analog Devices ADDI9036 ToF front-end support"
> > + depends on I2C && VIDEO_V4L2
> > + select MEDIA_CONTROLLER
> > + select VIDEO_V4L2_SUBDEV_API
> > + select V4L2_FWNODE
> > + select REGMAP_I2C
> > + help
> > + This is a Video4Linux2 driver for Analog Devices ADDI9036
> > + Time of Flight front-end.
> > +
> > + To compile this driver as a module, choose M here: the
> > + module will be called addi9036.
> > +
> > config VIDEO_HI556
> > tristate "Hynix Hi-556 sensor support"
> > depends on I2C && VIDEO_V4L2
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index f0a77473979d..631a7c7612ca 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
> > obj-$(CONFIG_VIDEO_DW9714) += dw9714.o
> > obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
> > obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
> > +obj-$(CONFIG_VIDEO_ADDI9036) += addi9036.o
> > obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
> > obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o
> > obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
> > diff --git a/drivers/media/i2c/addi9036.c b/drivers/media/i2c/addi9036.c
> > new file mode 100644
> > index 000000000000..76c3444058dc
> > --- /dev/null
> > +++ b/drivers/media/i2c/addi9036.c
> > @@ -0,0 +1,673 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Driver for the Analog Devices ADDI9036 ToF front-end.
> > + *
> > + * Copyright (C) 2019-2020 Analog Devices, All Rights Reserved.
> > + *
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/init.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +#include <linux/types.h>
> > +#include <linux/version.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +struct addi9036_mode_info {
> > + u32 width;
> > + u32 height;
> > + u32 pixel_rate;
> > + u32 link_freq_idx;
> > +};
> > +
> > +struct addi9036 {
> > + struct regmap *regmap;
> > + struct device *dev;
> > + struct v4l2_subdev sd;
> > + struct media_pad pad;
> > + struct v4l2_fwnode_endpoint ep;
> > + struct v4l2_mbus_framefmt fmt;
> > + struct v4l2_rect crop;
> > +
> > + const struct addi9036_mode_info *current_mode;
> > +
> > + struct v4l2_ctrl_handler ctrls;
> > + struct v4l2_ctrl *pixel_rate;
> > + struct v4l2_ctrl *link_freq;
> > +
> > + /* lock to protect power state */
> > + struct mutex power_lock;
> > + int power_count;
> > +
> > + struct gpio_desc *rst_gpio;
> > +
> > + /* controls */
> > + struct v4l2_ctrl *set_chip_config;
> > + struct v4l2_ctrl *reg_read;
> > +};
> > +
> > +static inline struct addi9036 *to_addi9036(struct v4l2_subdev *sd)
> > +{
> > + return container_of(sd, struct addi9036, sd);
> > +}
> > +
> > +#define V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY
> (V4L2_CID_USER_ADDI9036_BASE + 0)
> > +#define V4L2_CID_AD_DEV_REG_READ_QUERY
> (V4L2_CID_USER_ADDI9036_BASE + 1)
> > +
> > +static const struct reg_sequence addi9036_powerup_setting[] = {
> > + { 0xc4c0, 0x001c },
> > + { 0xc4c3, 0x001c },
> > + { 0xc4d7, 0x0000 },
> > + { 0xc4d5, 0x0002 },
> > + { 0xc4da, 0x0001 },
> > + { 0xc4f0, 0x0000 },
> > + { 0xc427, 0x0003 },
> > + { 0xc427, 0x0001 },
> > + { 0xc427, 0x0000 },
> > + { 0xc426, 0x0030 },
> > + { 0xc426, 0x0010 },
> > + { 0xc426, 0x0000 },
> > + { 0xc423, 0x0080 },
> > + { 0xc431, 0x0080 },
> > + { 0x4001, 0x0007 },
> > + { 0x7c22, 0x0004 }
> > +};
> > +
> > +static const struct reg_sequence addi9036_powerdown_setting[] = {
> > + { 0xc022, 0x0001 },
> > + { 0x4001, 0x0006 },
> > + { 0x7c22, 0x0004 },
> > + { 0xc431, 0x0082 },
> > + { 0xc423, 0x0000 },
> > + { 0xc426, 0x0020 },
> > + { 0xc427, 0x0002 },
> > + { 0xc4c0, 0x003c },
> > + { 0xc4c3, 0x003c },
> > + { 0xc4d5, 0x0003 },
> > + { 0xc4da, 0x0000 },
> > + { 0xc4d7, 0x0001 },
> > + { 0xc4f0, 0x0001 }
> > +};
> > +
> > +static const s64 link_freq_tbl[] = {
> > + 110529000,
> > + 221184000,
> > +};
> > +
> > +/* Elements of the structure must be ordered ascending by width & height */
> > +static const struct addi9036_mode_info addi9036_mode_info_data[] = {
> > + {
> > + .width = 640,
> > + .height = 480,
> > + .pixel_rate = 36864000,
> > + .link_freq_idx = 0 /* an index in link_freq_tbl[] */
> > + },
> > + {
> > + .width = 640,
> > + .height = 960,
> > + .pixel_rate = 73728000,
> > + .link_freq_idx = 1 /* an index in link_freq_tbl[] */
> > + },
> > +};
> > +
> > +static bool addi9306_readable_register(struct device *dev, unsigned int reg)
> > +{
> > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > + ((reg >= 0xC000) && (reg <= 0xC200)) ||
> > + ((reg >= 0xC300) && (reg <= 0xC6BF)))
> > + return true;
> > + else
> > + return false;
> > +}
> > +
> > +static bool addi9306_writeable_register(struct device *dev, unsigned int reg)
> > +{
> > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > + ((reg >= 0xC000) && (reg <= 0xC113)) ||
> > + ((reg >= 0xC300) && (reg <= 0xC7FF)))
> > + return true;
> > + else
> > + return false;
> > +}
> > +
> > +static const struct regmap_config addi9036_i2c_regmap_config = {
> > + .reg_bits = 16,
> > + .val_bits = 16,
> > +
> > + .max_register = 0xC7FF,
> > + .writeable_reg = addi9306_writeable_register,
> > + .readable_reg = addi9306_readable_register,
> > + .cache_type = REGCACHE_NONE,
> > +};
> > +
> > +static int addi9036_set_power_on(struct addi9036 *addi9036)
> > +{
> > + int ret;
> > +
> > + if (addi9036->rst_gpio)
> > + gpiod_set_value_cansleep(addi9036->rst_gpio, 0);
> > +
> > + ret = regmap_register_patch(addi9036->regmap,
> addi9036_powerup_setting,
> > + ARRAY_SIZE(addi9036_powerup_setting));
> > + if (ret)
> > + dev_err(addi9036->dev, "Could not set power up registers\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int addi9036_set_power_off(struct addi9036 *addi9036)
> > +{
> > + int ret;
> > +
> > + ret = regmap_register_patch(addi9036->regmap,
> > + addi9036_powerdown_setting,
> > + ARRAY_SIZE(addi9036_powerdown_setting));
> > + if (ret)
> > + dev_err(addi9036->dev, "could not set power down
> registers\n");
> > +
> > + if (addi9036->rst_gpio)
> > + gpiod_set_value_cansleep(addi9036->rst_gpio, 1);
> > +
> > + return ret;
> > +}
> > +
> > +static int addi9036_s_power(struct v4l2_subdev *sd, int on)
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > + int ret = 0;
> > +
> > + mutex_lock(&addi9036->power_lock);
> > +
> > + dev_dbg(addi9036->dev, "s_power: %d\n", on);
> > +
> > + /* If the power count is modified from 0 to != 0 or from != 0 to 0,
> > + * update the power state.
> > + */
> > + if (addi9036->power_count == !on) {
> > + if (on)
> > + ret = addi9036_set_power_on(addi9036);
> > + else
> > + ret = addi9036_set_power_off(addi9036);
> > + }
> > +
> > + if (!ret) {
> > + /* Update the power count. */
> > + addi9036->power_count += on ? 1 : -1;
> > + WARN(addi9036->power_count < 0, "Unbalanced power
> count\n");
> > + WARN(addi9036->power_count > 1, "Duplicated s_power
> call\n");
> > + }
> > +
> > + mutex_unlock(&addi9036->power_lock);
> > +
> > + return ret;
> > +}
> > +
> > +static int addi9036_set_chip_config(struct addi9036 *addi9036,
> > + struct v4l2_ctrl *ctrl)
> > +{
> > + int index;
> > + unsigned short *reg, *val;
> > +
> > + reg = (unsigned short *)(ctrl->p_new.p_u16);
> > + val = (unsigned short *)(ctrl->p_new.p_u16 + 1);
> > +
> > + for (index = 0; index < ctrl->elems; index += 2) {
> > + regmap_write(addi9036->regmap, *reg, *val);
> > + reg += 2;
> > + val += 2;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_s_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > + struct addi9036 *addi9036 = container_of(ctrl->handler,
> > + struct addi9036, ctrls);
> > + int ret = 0;
> > + unsigned int val;
> > +
> > + switch (ctrl->id) {
> > + case V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY:
> > + ret = addi9036_set_chip_config(addi9036, ctrl);
> > + break;
> > + case V4L2_CID_AD_DEV_REG_READ_QUERY:
> > + ret = regmap_read(addi9036->regmap, *(u16 *)(ctrl-
> >p_new.p_u16),
> > + &val);
> > + if (ret)
> > + dev_warn(addi9036->dev,
> > + "could not read from register\n");
> > + else
> > + *(u16 *)(ctrl->p_new.p_u16) = val;
> > + break;
> > + case V4L2_CID_PIXEL_RATE:
> > + case V4L2_CID_LINK_FREQ:
> > + break;
> > + default:
> > + dev_err(addi9036->dev, "%s > Unhandled: %x param=%x\n",
> > + __func__, ctrl->id, ctrl->val);
> > + ret = -EINVAL;
> > + break;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops addi9036_ctrl_ops = {
> > + .s_ctrl = addi9036_s_ctrl,
> > +};
> > +
> > +static const struct v4l2_ctrl_config addi9036_ctrl_chip_config = {
> > + .ops = &addi9036_ctrl_ops,
> > + .id = V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY,
> > + .name = "chip_config",
> > + .type = V4L2_CTRL_TYPE_U16,
> > + .def = 0xFF,
> > + .min = 0x00,
> > + .max = 0xFFFF,
> > + .step = 1,
> > + .dims = { 2048 },
> > + .elem_size = 2
> > +};
> > +
> > +static const struct v4l2_ctrl_config addi9036_ctrl_reg_read = {
> > + .ops = &addi9036_ctrl_ops,
> > + .id = V4L2_CID_AD_DEV_REG_READ_QUERY,
> > + .name = "reg_read",
> > + .type = V4L2_CTRL_TYPE_U16,
> > + .def = 0,
> > + .min = 0x00,
> > + .max = 0xFFFF,
> > + .step = 1
> > +};
> > +
> > +static int addi9036_enum_mbus_code(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > + if (code->index > 0)
> > + return -EINVAL;
> > +
> > + code->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_enum_frame_size(struct v4l2_subdev *subdev,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > + if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12)
> > + return -EINVAL;
> > +
> > + if (fse->index >= ARRAY_SIZE(addi9036_mode_info_data))
> > + return -EINVAL;
> > +
> > + fse->min_width = addi9036_mode_info_data[fse->index].width;
> > + fse->max_width = addi9036_mode_info_data[fse->index].width;
> > + fse->min_height = addi9036_mode_info_data[fse->index].height;
> > + fse->max_height = addi9036_mode_info_data[fse->index].height;
> > +
> > + return 0;
> > +}
> > +
> > +static struct v4l2_mbus_framefmt *
> > +addi9036_get_pad_format(struct addi9036 *addi9036,
> > + struct v4l2_subdev_pad_config *cfg,
> > + unsigned int pad,
> > + enum v4l2_subdev_format_whence which)
> > +{
> > + switch (which) {
> > + case V4L2_SUBDEV_FORMAT_TRY:
> > + return v4l2_subdev_get_try_format(&addi9036->sd, cfg, pad);
> > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > + return &addi9036->fmt;
> > + default:
> > + return NULL;
> > + }
> > +}
> > +
> > +static int addi9036_get_format(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_format *format)
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > + struct v4l2_mbus_framefmt *pad_format;
> > +
> > + pad_format = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > + format->which);
> > +
> > + if (!pad_format)
> > + return -EINVAL;
> > +
> > + format->format = *pad_format;
> > +
> > + return 0;
> > +}
> > +
> > +static struct v4l2_rect *
> > +addi9036_get_pad_crop(struct addi9036 *addi9036,
> > + struct v4l2_subdev_pad_config *cfg,
> > + unsigned int pad, enum v4l2_subdev_format_whence which)
> > +{
> > + switch (which) {
> > + case V4L2_SUBDEV_FORMAT_TRY:
> > + return v4l2_subdev_get_try_crop(&addi9036->sd, cfg, pad);
> > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > + return &addi9036->crop;
> > + default:
> > + return NULL;
> > + }
> > +}
> > +
> > +static const struct addi9036_mode_info *
> > +addi9036_find_nearest_mode(unsigned int width, unsigned int height)
> > +{
> > + unsigned int i;
> > +
> > + for (i = ARRAY_SIZE(addi9036_mode_info_data) - 1; i > 0; i--) {
> > + if (addi9036_mode_info_data[i].width <= width &&
> > + addi9036_mode_info_data[i].height <= height)
> > + break;
> > + }
> > +
> > + return &addi9036_mode_info_data[i];
> > +}
> > +
> > +static int addi9036_set_format(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_format *format)
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > + struct v4l2_mbus_framefmt *framefmt;
> > + struct v4l2_rect *crop;
> > + const struct addi9036_mode_info *new_mode;
> > + int ret;
> > +
> > + dev_dbg(addi9036->dev, "set_fmt: %x %dx%d\n",
> > + format->format.code, format->format.width,
> > + format->format.height);
> > +
> > + crop = addi9036_get_pad_crop(addi9036, cfg, format->pad,
> > + format->which);
> > +
> > + if (!crop)
> > + return -EINVAL;
> > +
> > + new_mode = addi9036_find_nearest_mode(format->format.width,
> > + format->format.height);
> > + crop->width = new_mode->width;
> > + crop->height = new_mode->height;
> > +
> > + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > + ret = v4l2_ctrl_s_ctrl_int64(addi9036->pixel_rate,
> > + new_mode->pixel_rate);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = v4l2_ctrl_s_ctrl(addi9036->link_freq,
> > + new_mode->link_freq_idx);
> > + if (ret < 0)
> > + return ret;
> > +
> > + addi9036->current_mode = new_mode;
> > + }
> > +
> > + framefmt = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > + format->which);
> > +
> > + if (!framefmt)
> > + return -EINVAL;
> > +
> > + framefmt->width = crop->width;
> > + framefmt->height = crop->height;
> > + framefmt->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > + framefmt->field = V4L2_FIELD_NONE;
> > + framefmt->colorspace = V4L2_COLORSPACE_SRGB;
> > +
> > + format->format = *framefmt;
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_entity_init_cfg(struct v4l2_subdev *subdev,
> > + struct v4l2_subdev_pad_config *cfg)
> > +{
> > + struct v4l2_subdev_format fmt = { 0 };
> > +
> > + if (cfg)
> > + fmt.which = V4L2_SUBDEV_FORMAT_TRY;
> > + else
> > + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > +
> > + fmt.format.width = 640;
> > + fmt.format.height = 960;
> > +
> > + addi9036_set_format(subdev, cfg, &fmt);
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_get_selection(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_selection *sel)
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > +
> > + if (sel->target != V4L2_SEL_TGT_CROP)
> > + return -EINVAL;
> > +
> > + sel->r = *addi9036_get_pad_crop(addi9036, cfg, sel->pad, sel->which);
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_s_stream(struct v4l2_subdev *subdev, int enable)
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(subdev);
> > +
> > + dev_dbg(addi9036->dev, "stream %d\n", enable);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct v4l2_subdev_core_ops addi9036_core_ops = {
> > + .s_power = addi9036_s_power,
> > +};
> > +
> > +static const struct v4l2_subdev_video_ops addi9036_video_ops = {
> > + .s_stream = addi9036_s_stream,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops addi9036_subdev_pad_ops = {
> > + .init_cfg = addi9036_entity_init_cfg,
> > + .enum_mbus_code = addi9036_enum_mbus_code,
> > + .enum_frame_size = addi9036_enum_frame_size,
> > + .get_fmt = addi9036_get_format,
> > + .set_fmt = addi9036_set_format,
> > + .get_selection = addi9036_get_selection,
> > +};
> > +
> > +static const struct v4l2_subdev_ops addi9036_subdev_ops = {
> > + .core = &addi9036_core_ops,
> > + .video = &addi9036_video_ops,
> > + .pad = &addi9036_subdev_pad_ops,
> > +};
> > +
> > +static int addi9036_probe(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev;
> > + struct fwnode_handle *endpoint;
> > + struct addi9036 *addi9036;
> > + int ret;
> > +
> > + dev_dbg(dev, "%s: i2c addr = 0x%x\n", __func__, client->addr);
> > +
> > + addi9036 = devm_kzalloc(dev, sizeof(struct addi9036), GFP_KERNEL);
> > + if (!addi9036)
> > + return -ENOMEM;
> > +
> > + addi9036->dev = dev;
> > +
> > + addi9036->regmap = devm_regmap_init_i2c(client,
> > + &addi9036_i2c_regmap_config);
> > + if (IS_ERR(addi9036->regmap)) {
> > + dev_err(dev, "Error initializing i2c regmap\n");
> > + return PTR_ERR(addi9036->regmap);
> > + }
> > +
> > + mutex_init(&addi9036->power_lock);
> > +
> > + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev),
> NULL);
> > + if (!endpoint) {
> > + dev_err(dev, "endpoint node not found\n");
> > + return -EINVAL;
> > + }
> > +
> > + ret = v4l2_fwnode_endpoint_parse(endpoint, &addi9036->ep);
> > + if (ret < 0) {
> > + dev_err(dev, "parsing endpoint node failed\n");
> > + return ret;
> > + }
> > + fwnode_handle_put(endpoint);
> > +
> > + if (addi9036->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
> > + dev_err(dev, "invalid bus type, must be MIPI CSI2\n");
> > + return -EINVAL;
> > + }
> > +
> > + addi9036->rst_gpio = gpiod_get_optional(dev, "reset",
> GPIOD_OUT_HIGH);
> > + if (IS_ERR(addi9036->rst_gpio)) {
> > + dev_info(dev, "Unable to get \"reset\" gpio\n");
> > + return PTR_ERR(addi9036->rst_gpio);
> > + }
> > +
> > + v4l2_ctrl_handler_init(&addi9036->ctrls, 4);
> > +
> > + addi9036->pixel_rate = v4l2_ctrl_new_std(&addi9036->ctrls,
> > + &addi9036_ctrl_ops,
> > + V4L2_CID_PIXEL_RATE,
> > + 1, INT_MAX, 1, 1);
> > + addi9036->link_freq = v4l2_ctrl_new_int_menu(&addi9036->ctrls,
> > + &addi9036_ctrl_ops,
> > + V4L2_CID_LINK_FREQ,
> > + ARRAY_SIZE(
> > + link_freq_tbl) - 1,
> > + 0, link_freq_tbl);
> > + if (addi9036->link_freq)
> > + addi9036->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > + addi9036->set_chip_config = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > + &addi9036_ctrl_chip_config,
> > + NULL);
> > +
> > + addi9036->reg_read = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > + &addi9036_ctrl_reg_read,
> > + NULL);
> > +
> > + addi9036->sd.ctrl_handler = &addi9036->ctrls;
> > +
> > + if (addi9036->ctrls.error) {
> > + dev_err(dev, "%s: control initialization error %d\n",
> > + __func__, addi9036->ctrls.error);
> > + ret = addi9036->ctrls.error;
> > + goto free_ctrl;
> > + }
> > +
> > + v4l2_i2c_subdev_init(&addi9036->sd, client, &addi9036_subdev_ops);
> > + addi9036->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > + addi9036->pad.flags = MEDIA_PAD_FL_SOURCE;
> > + addi9036->sd.dev = &client->dev;
> > + addi9036->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > +
> > + ret = media_entity_pads_init(&addi9036->sd.entity, 1, &addi9036->pad);
> > +
> > + if (ret < 0) {
> > + dev_err(dev, "could not register media entity\n");
> > + goto free_ctrl;
> > + }
> > +
> > + ret = addi9036_s_power(&addi9036->sd, true);
> > + if (ret < 0) {
> > + dev_err(dev, "could not power up addi9036\n");
> > + goto free_entity;
> > + }
> > +
> > + dev_info(dev, "addi9036 detected at address 0x%02x\n", client->addr);
> > +
> > + ret = addi9036_s_power(&addi9036->sd, false);
> > + if (ret < 0) {
> > + dev_err(dev, "could not power down addi9036\n");
> > + goto free_entity;
> > + }
> > +
> > + addi9036_entity_init_cfg(&addi9036->sd, NULL);
> > +
> > + ret = v4l2_async_register_subdev(&addi9036->sd);
> > + if (ret < 0)
> > + dev_err(dev, "could not register v4l2 device\n");
> > +
> > + return 0;
> > +
> > +free_entity:
> > + media_entity_cleanup(&addi9036->sd.entity);
> > +free_ctrl:
> > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > + mutex_destroy(&addi9036->power_lock);
> > +
> > + return ret;
> > +}
> > +
> > +static int addi9036_remove(struct i2c_client *client)
> > +{
> > + struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > +
> > + v4l2_async_unregister_subdev(&addi9036->sd);
> > + media_entity_cleanup(&addi9036->sd.entity);
> > + if (addi9036->rst_gpio)
> > + gpiod_put(addi9036->rst_gpio);
> > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > + mutex_destroy(&addi9036->power_lock);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct i2c_device_id addi9036_id[] = {
> > + { "addi9036", 0 },
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(i2c, addi9036_id);
> > +
> > +static const struct of_device_id addi9036_of_match[] = {
> > + { .compatible = "adi,addi9036" },
> > + { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, addi9036_of_match);
> > +
> > +static struct i2c_driver addi9036_i2c_driver = {
> > + .driver = {
> > + .of_match_table = addi9036_of_match,
> > + .name = "addi9036",
> > + },
> > + .probe_new = addi9036_probe,
> > + .remove = addi9036_remove,
> > + .id_table = addi9036_id,
> > +};
> > +
> > +module_i2c_driver(addi9036_i2c_driver);
> > +
> > +MODULE_DESCRIPTION("Analog Devices ADDI9036 Camera Driver");
> > +MODULE_AUTHOR("Bogdan Togorean");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-
> controls.h
> > index 62271418c1be..f88b56479bc1 100644
> > --- a/include/uapi/linux/v4l2-controls.h
> > +++ b/include/uapi/linux/v4l2-controls.h
> > @@ -198,6 +198,12 @@ enum v4l2_colorfx {
> > */
> > #define V4L2_CID_USER_ATMEL_ISC_BASE (V4L2_CID_USER_BASE +
> 0x10c0)
> >
> > +/*
> > + * The base for the addi9036 driver controls.
> > + * We reserve 16 controls for this driver.
> > + */
> > +#define V4L2_CID_USER_ADDI9036_BASE (V4L2_CID_USER_BASE +
> 0x10e0)
> > +
> > /* MPEG-class control IDs */
> > /* The MPEG controls are applicable to all codec controls
> > * and the 'MPEG' part of the define is historical */
> > --
> > 2.28.0
> >
>
> --
> Regards,
>
> Laurent Pinchart
Regards,

Bogdan

2020-09-11 22:55:28

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 2/2] media: dt-bindings: media: i2c: Add bindings for ADDI9036

On Thu, 10 Sep 2020 19:24:07 +0300, Bogdan Togorean wrote:
> Add YAML device tree bindings for Analog Devices Inc. ADDI9036 CCD TOF
> front-end.
>
> Signed-off-by: Bogdan Togorean <[email protected]>
> ---
> .../bindings/media/i2c/adi,addi9036.yaml | 72 +++++++++++++++++++
> 1 file changed, 72 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
>


My bot found errors running 'make dt_binding_check' on your patch:

/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/media/i2c/adi,addi9036.example.dt.yaml: addi9036_tof@64: 'reg' does not match any of the regexes: 'pinctrl-[0-9]+'
From schema: /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml


See https://patchwork.ozlabs.org/patch/1361583

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure dt-schema is up to date:

pip3 install git+https://github.com/devicetree-org/dt-schema.git@master --upgrade

Please check and re-submit.

2020-09-15 21:31:01

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

Hi Bogdan,

On Fri, Sep 11, 2020 at 01:32:39PM +0000, Togorean, Bogdan wrote:
> > On Thu, Sep 10, 2020 at 07:24:06PM +0300, Bogdan Togorean wrote:
> > > The ADDI9036 is a complete, 45 MHz, front-end solution for charge
> > > coupled device (CCD) time of flight (TOF) imaging applications.
> > >
> > > It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.
> > >
> > > The programming of calibration and firmware is performed by userspace
> > > SDK through CID_USER class IOCTLs.
> >
> > I'm afraid that's not acceptable. The firmware should be loaded using
> > the Linux kernel firmware API. As for calibration, is it static data ?
> > How is it generated ?
>
> Calibration data is static data, generated at production and stored on and EEPROM
> memory on camera board. The camera board consists mainly of ADDI9036 processor
> + CCD image sensor + EEPROM.
>
> The firmware is a set of (address, data) tuples that are written via I2C.

Is it a firmware in the sense of software running on the ADDI9036, or is
it a device configuration to be loaded in registers ?

> We want this driver to be a generic one and support all implementations
> of ADDI9036 ToF processor. Now the problem is that some implementations
> store firmware + calibration on external EEPROM and some store just calibration
> data on EEPROM and firmware is loaded from a file stored in SDK. With the help
> of the 2 custom controls we move the camera model detection, firmware
> and calibration fetching from driver to user space SDK.
>
> Also an important point is that the ToF processor has 3 operating modes
> (near, medium and far). At each mode change a new set of firmware +
> calibration data is loaded into ADDI9036 chip. If we move the firmware loading
> in driver we would still need a custom enumeration control to select the working mode.
>
> > > Signed-off-by: Bogdan Togorean <[email protected]>
> > > ---
> > > MAINTAINERS | 10 +
> > > drivers/media/i2c/Kconfig | 14 +
> > > drivers/media/i2c/Makefile | 1 +
> > > drivers/media/i2c/addi9036.c | 673 +++++++++++++++++++++++++++++
> > > include/uapi/linux/v4l2-controls.h | 6 +
> > > 5 files changed, 704 insertions(+)
> > > create mode 100644 drivers/media/i2c/addi9036.c
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index b5cfab015bd6..fb03ad9ca920 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -477,6 +477,16 @@ W: http://wiki.analog.com/AD7879
> > > W: http://ez.analog.com/community/linux-device-drivers
> > > F: drivers/input/touchscreen/ad7879.c
> > >
> > > +ADDI9036 TOF FRONTEND DRIVER
> > > +M: Bogdan Togorean <[email protected]>
> > > +L: [email protected]
> > > +S: Supported
> > > +W: https://www.analog.com/en/products/addi9036.html
> > > +W: http://ez.analog.com/community/linux-device-drivers
> > > +T: git git://linuxtv.org/media_tree.git
> > > +F: Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
> > > +F: drivers/media/i2c/addi9036.c
> > > +
> > > ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR)
> > > M: Jiri Kosina <[email protected]>
> > > S: Maintained
> > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > > index c7ba76fee599..087dd307505d 100644
> > > --- a/drivers/media/i2c/Kconfig
> > > +++ b/drivers/media/i2c/Kconfig
> > > @@ -725,6 +725,20 @@ config VIDEO_APTINA_PLL
> > > config VIDEO_SMIAPP_PLL
> > > tristate
> > >
> > > +config VIDEO_ADDI9036
> > > + tristate "Analog Devices ADDI9036 ToF front-end support"
> > > + depends on I2C && VIDEO_V4L2
> > > + select MEDIA_CONTROLLER
> > > + select VIDEO_V4L2_SUBDEV_API
> > > + select V4L2_FWNODE
> > > + select REGMAP_I2C
> > > + help
> > > + This is a Video4Linux2 driver for Analog Devices ADDI9036
> > > + Time of Flight front-end.
> > > +
> > > + To compile this driver as a module, choose M here: the
> > > + module will be called addi9036.
> > > +
> > > config VIDEO_HI556
> > > tristate "Hynix Hi-556 sensor support"
> > > depends on I2C && VIDEO_V4L2
> > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > > index f0a77473979d..631a7c7612ca 100644
> > > --- a/drivers/media/i2c/Makefile
> > > +++ b/drivers/media/i2c/Makefile
> > > @@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
> > > obj-$(CONFIG_VIDEO_DW9714) += dw9714.o
> > > obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
> > > obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
> > > +obj-$(CONFIG_VIDEO_ADDI9036) += addi9036.o
> > > obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
> > > obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o
> > > obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
> > > diff --git a/drivers/media/i2c/addi9036.c b/drivers/media/i2c/addi9036.c
> > > new file mode 100644
> > > index 000000000000..76c3444058dc
> > > --- /dev/null
> > > +++ b/drivers/media/i2c/addi9036.c
> > > @@ -0,0 +1,673 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Driver for the Analog Devices ADDI9036 ToF front-end.
> > > + *
> > > + * Copyright (C) 2019-2020 Analog Devices, All Rights Reserved.
> > > + *
> > > + */
> > > +
> > > +#include <linux/device.h>
> > > +#include <linux/gpio/consumer.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/init.h>
> > > +#include <linux/module.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/of.h>
> > > +#include <linux/of_graph.h>
> > > +#include <linux/regmap.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/types.h>
> > > +#include <linux/version.h>
> > > +#include <media/v4l2-ctrls.h>
> > > +#include <media/v4l2-fwnode.h>
> > > +#include <media/v4l2-subdev.h>
> > > +
> > > +struct addi9036_mode_info {
> > > + u32 width;
> > > + u32 height;
> > > + u32 pixel_rate;
> > > + u32 link_freq_idx;
> > > +};
> > > +
> > > +struct addi9036 {
> > > + struct regmap *regmap;
> > > + struct device *dev;
> > > + struct v4l2_subdev sd;
> > > + struct media_pad pad;
> > > + struct v4l2_fwnode_endpoint ep;
> > > + struct v4l2_mbus_framefmt fmt;
> > > + struct v4l2_rect crop;
> > > +
> > > + const struct addi9036_mode_info *current_mode;
> > > +
> > > + struct v4l2_ctrl_handler ctrls;
> > > + struct v4l2_ctrl *pixel_rate;
> > > + struct v4l2_ctrl *link_freq;
> > > +
> > > + /* lock to protect power state */
> > > + struct mutex power_lock;
> > > + int power_count;
> > > +
> > > + struct gpio_desc *rst_gpio;
> > > +
> > > + /* controls */
> > > + struct v4l2_ctrl *set_chip_config;
> > > + struct v4l2_ctrl *reg_read;
> > > +};
> > > +
> > > +static inline struct addi9036 *to_addi9036(struct v4l2_subdev *sd)
> > > +{
> > > + return container_of(sd, struct addi9036, sd);
> > > +}
> > > +
> > > +#define V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY (V4L2_CID_USER_ADDI9036_BASE + 0)
> > > +#define V4L2_CID_AD_DEV_REG_READ_QUERY (V4L2_CID_USER_ADDI9036_BASE + 1)
> > > +
> > > +static const struct reg_sequence addi9036_powerup_setting[] = {
> > > + { 0xc4c0, 0x001c },
> > > + { 0xc4c3, 0x001c },
> > > + { 0xc4d7, 0x0000 },
> > > + { 0xc4d5, 0x0002 },
> > > + { 0xc4da, 0x0001 },
> > > + { 0xc4f0, 0x0000 },
> > > + { 0xc427, 0x0003 },
> > > + { 0xc427, 0x0001 },
> > > + { 0xc427, 0x0000 },
> > > + { 0xc426, 0x0030 },
> > > + { 0xc426, 0x0010 },
> > > + { 0xc426, 0x0000 },
> > > + { 0xc423, 0x0080 },
> > > + { 0xc431, 0x0080 },
> > > + { 0x4001, 0x0007 },
> > > + { 0x7c22, 0x0004 }
> > > +};
> > > +
> > > +static const struct reg_sequence addi9036_powerdown_setting[] = {
> > > + { 0xc022, 0x0001 },
> > > + { 0x4001, 0x0006 },
> > > + { 0x7c22, 0x0004 },
> > > + { 0xc431, 0x0082 },
> > > + { 0xc423, 0x0000 },
> > > + { 0xc426, 0x0020 },
> > > + { 0xc427, 0x0002 },
> > > + { 0xc4c0, 0x003c },
> > > + { 0xc4c3, 0x003c },
> > > + { 0xc4d5, 0x0003 },
> > > + { 0xc4da, 0x0000 },
> > > + { 0xc4d7, 0x0001 },
> > > + { 0xc4f0, 0x0001 }
> > > +};
> > > +
> > > +static const s64 link_freq_tbl[] = {
> > > + 110529000,
> > > + 221184000,
> > > +};
> > > +
> > > +/* Elements of the structure must be ordered ascending by width & height */
> > > +static const struct addi9036_mode_info addi9036_mode_info_data[] = {
> > > + {
> > > + .width = 640,
> > > + .height = 480,
> > > + .pixel_rate = 36864000,
> > > + .link_freq_idx = 0 /* an index in link_freq_tbl[] */
> > > + },
> > > + {
> > > + .width = 640,
> > > + .height = 960,
> > > + .pixel_rate = 73728000,
> > > + .link_freq_idx = 1 /* an index in link_freq_tbl[] */
> > > + },
> > > +};
> > > +
> > > +static bool addi9306_readable_register(struct device *dev, unsigned int reg)
> > > +{
> > > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > > + ((reg >= 0xC000) && (reg <= 0xC200)) ||
> > > + ((reg >= 0xC300) && (reg <= 0xC6BF)))
> > > + return true;
> > > + else
> > > + return false;
> > > +}
> > > +
> > > +static bool addi9306_writeable_register(struct device *dev, unsigned int reg)
> > > +{
> > > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > > + ((reg >= 0xC000) && (reg <= 0xC113)) ||
> > > + ((reg >= 0xC300) && (reg <= 0xC7FF)))
> > > + return true;
> > > + else
> > > + return false;
> > > +}
> > > +
> > > +static const struct regmap_config addi9036_i2c_regmap_config = {
> > > + .reg_bits = 16,
> > > + .val_bits = 16,
> > > +
> > > + .max_register = 0xC7FF,
> > > + .writeable_reg = addi9306_writeable_register,
> > > + .readable_reg = addi9306_readable_register,
> > > + .cache_type = REGCACHE_NONE,
> > > +};
> > > +
> > > +static int addi9036_set_power_on(struct addi9036 *addi9036)
> > > +{
> > > + int ret;
> > > +
> > > + if (addi9036->rst_gpio)
> > > + gpiod_set_value_cansleep(addi9036->rst_gpio, 0);
> > > +
> > > + ret = regmap_register_patch(addi9036->regmap, addi9036_powerup_setting,
> > > + ARRAY_SIZE(addi9036_powerup_setting));
> > > + if (ret)
> > > + dev_err(addi9036->dev, "Could not set power up registers\n");
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int addi9036_set_power_off(struct addi9036 *addi9036)
> > > +{
> > > + int ret;
> > > +
> > > + ret = regmap_register_patch(addi9036->regmap,
> > > + addi9036_powerdown_setting,
> > > + ARRAY_SIZE(addi9036_powerdown_setting));
> > > + if (ret)
> > > + dev_err(addi9036->dev, "could not set power down registers\n");
> > > +
> > > + if (addi9036->rst_gpio)
> > > + gpiod_set_value_cansleep(addi9036->rst_gpio, 1);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int addi9036_s_power(struct v4l2_subdev *sd, int on)
> > > +{
> > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > + int ret = 0;
> > > +
> > > + mutex_lock(&addi9036->power_lock);
> > > +
> > > + dev_dbg(addi9036->dev, "s_power: %d\n", on);
> > > +
> > > + /* If the power count is modified from 0 to != 0 or from != 0 to 0,
> > > + * update the power state.
> > > + */
> > > + if (addi9036->power_count == !on) {
> > > + if (on)
> > > + ret = addi9036_set_power_on(addi9036);
> > > + else
> > > + ret = addi9036_set_power_off(addi9036);
> > > + }
> > > +
> > > + if (!ret) {
> > > + /* Update the power count. */
> > > + addi9036->power_count += on ? 1 : -1;
> > > + WARN(addi9036->power_count < 0, "Unbalanced power count\n");
> > > + WARN(addi9036->power_count > 1, "Duplicated s_power call\n");
> > > + }
> > > +
> > > + mutex_unlock(&addi9036->power_lock);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int addi9036_set_chip_config(struct addi9036 *addi9036,
> > > + struct v4l2_ctrl *ctrl)
> > > +{
> > > + int index;
> > > + unsigned short *reg, *val;
> > > +
> > > + reg = (unsigned short *)(ctrl->p_new.p_u16);
> > > + val = (unsigned short *)(ctrl->p_new.p_u16 + 1);
> > > +
> > > + for (index = 0; index < ctrl->elems; index += 2) {
> > > + regmap_write(addi9036->regmap, *reg, *val);
> > > + reg += 2;
> > > + val += 2;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int addi9036_s_ctrl(struct v4l2_ctrl *ctrl)
> > > +{
> > > + struct addi9036 *addi9036 = container_of(ctrl->handler,
> > > + struct addi9036, ctrls);
> > > + int ret = 0;
> > > + unsigned int val;
> > > +
> > > + switch (ctrl->id) {
> > > + case V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY:
> > > + ret = addi9036_set_chip_config(addi9036, ctrl);
> > > + break;
> > > + case V4L2_CID_AD_DEV_REG_READ_QUERY:
> > > + ret = regmap_read(addi9036->regmap, *(u16 *)(ctrl-
> > >p_new.p_u16),
> > > + &val);
> > > + if (ret)
> > > + dev_warn(addi9036->dev,
> > > + "could not read from register\n");
> > > + else
> > > + *(u16 *)(ctrl->p_new.p_u16) = val;
> > > + break;
> > > + case V4L2_CID_PIXEL_RATE:
> > > + case V4L2_CID_LINK_FREQ:
> > > + break;
> > > + default:
> > > + dev_err(addi9036->dev, "%s > Unhandled: %x param=%x\n",
> > > + __func__, ctrl->id, ctrl->val);
> > > + ret = -EINVAL;
> > > + break;
> > > + }
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static const struct v4l2_ctrl_ops addi9036_ctrl_ops = {
> > > + .s_ctrl = addi9036_s_ctrl,
> > > +};
> > > +
> > > +static const struct v4l2_ctrl_config addi9036_ctrl_chip_config = {
> > > + .ops = &addi9036_ctrl_ops,
> > > + .id = V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY,
> > > + .name = "chip_config",
> > > + .type = V4L2_CTRL_TYPE_U16,
> > > + .def = 0xFF,
> > > + .min = 0x00,
> > > + .max = 0xFFFF,
> > > + .step = 1,
> > > + .dims = { 2048 },
> > > + .elem_size = 2
> > > +};
> > > +
> > > +static const struct v4l2_ctrl_config addi9036_ctrl_reg_read = {
> > > + .ops = &addi9036_ctrl_ops,
> > > + .id = V4L2_CID_AD_DEV_REG_READ_QUERY,
> > > + .name = "reg_read",
> > > + .type = V4L2_CTRL_TYPE_U16,
> > > + .def = 0,
> > > + .min = 0x00,
> > > + .max = 0xFFFF,
> > > + .step = 1
> > > +};
> > > +
> > > +static int addi9036_enum_mbus_code(struct v4l2_subdev *sd,
> > > + struct v4l2_subdev_pad_config *cfg,
> > > + struct v4l2_subdev_mbus_code_enum *code)
> > > +{
> > > + if (code->index > 0)
> > > + return -EINVAL;
> > > +
> > > + code->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int addi9036_enum_frame_size(struct v4l2_subdev *subdev,
> > > + struct v4l2_subdev_pad_config *cfg,
> > > + struct v4l2_subdev_frame_size_enum *fse)
> > > +{
> > > + if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12)
> > > + return -EINVAL;
> > > +
> > > + if (fse->index >= ARRAY_SIZE(addi9036_mode_info_data))
> > > + return -EINVAL;
> > > +
> > > + fse->min_width = addi9036_mode_info_data[fse->index].width;
> > > + fse->max_width = addi9036_mode_info_data[fse->index].width;
> > > + fse->min_height = addi9036_mode_info_data[fse->index].height;
> > > + fse->max_height = addi9036_mode_info_data[fse->index].height;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static struct v4l2_mbus_framefmt *
> > > +addi9036_get_pad_format(struct addi9036 *addi9036,
> > > + struct v4l2_subdev_pad_config *cfg,
> > > + unsigned int pad,
> > > + enum v4l2_subdev_format_whence which)
> > > +{
> > > + switch (which) {
> > > + case V4L2_SUBDEV_FORMAT_TRY:
> > > + return v4l2_subdev_get_try_format(&addi9036->sd, cfg, pad);
> > > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > > + return &addi9036->fmt;
> > > + default:
> > > + return NULL;
> > > + }
> > > +}
> > > +
> > > +static int addi9036_get_format(struct v4l2_subdev *sd,
> > > + struct v4l2_subdev_pad_config *cfg,
> > > + struct v4l2_subdev_format *format)
> > > +{
> > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > + struct v4l2_mbus_framefmt *pad_format;
> > > +
> > > + pad_format = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > > + format->which);
> > > +
> > > + if (!pad_format)
> > > + return -EINVAL;
> > > +
> > > + format->format = *pad_format;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static struct v4l2_rect *
> > > +addi9036_get_pad_crop(struct addi9036 *addi9036,
> > > + struct v4l2_subdev_pad_config *cfg,
> > > + unsigned int pad, enum v4l2_subdev_format_whence which)
> > > +{
> > > + switch (which) {
> > > + case V4L2_SUBDEV_FORMAT_TRY:
> > > + return v4l2_subdev_get_try_crop(&addi9036->sd, cfg, pad);
> > > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > > + return &addi9036->crop;
> > > + default:
> > > + return NULL;
> > > + }
> > > +}
> > > +
> > > +static const struct addi9036_mode_info *
> > > +addi9036_find_nearest_mode(unsigned int width, unsigned int height)
> > > +{
> > > + unsigned int i;
> > > +
> > > + for (i = ARRAY_SIZE(addi9036_mode_info_data) - 1; i > 0; i--) {
> > > + if (addi9036_mode_info_data[i].width <= width &&
> > > + addi9036_mode_info_data[i].height <= height)
> > > + break;
> > > + }
> > > +
> > > + return &addi9036_mode_info_data[i];
> > > +}
> > > +
> > > +static int addi9036_set_format(struct v4l2_subdev *sd,
> > > + struct v4l2_subdev_pad_config *cfg,
> > > + struct v4l2_subdev_format *format)
> > > +{
> > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > + struct v4l2_mbus_framefmt *framefmt;
> > > + struct v4l2_rect *crop;
> > > + const struct addi9036_mode_info *new_mode;
> > > + int ret;
> > > +
> > > + dev_dbg(addi9036->dev, "set_fmt: %x %dx%d\n",
> > > + format->format.code, format->format.width,
> > > + format->format.height);
> > > +
> > > + crop = addi9036_get_pad_crop(addi9036, cfg, format->pad,
> > > + format->which);
> > > +
> > > + if (!crop)
> > > + return -EINVAL;
> > > +
> > > + new_mode = addi9036_find_nearest_mode(format->format.width,
> > > + format->format.height);
> > > + crop->width = new_mode->width;
> > > + crop->height = new_mode->height;
> > > +
> > > + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > > + ret = v4l2_ctrl_s_ctrl_int64(addi9036->pixel_rate,
> > > + new_mode->pixel_rate);
> > > + if (ret < 0)
> > > + return ret;
> > > +
> > > + ret = v4l2_ctrl_s_ctrl(addi9036->link_freq,
> > > + new_mode->link_freq_idx);
> > > + if (ret < 0)
> > > + return ret;
> > > +
> > > + addi9036->current_mode = new_mode;
> > > + }
> > > +
> > > + framefmt = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > > + format->which);
> > > +
> > > + if (!framefmt)
> > > + return -EINVAL;
> > > +
> > > + framefmt->width = crop->width;
> > > + framefmt->height = crop->height;
> > > + framefmt->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > > + framefmt->field = V4L2_FIELD_NONE;
> > > + framefmt->colorspace = V4L2_COLORSPACE_SRGB;
> > > +
> > > + format->format = *framefmt;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int addi9036_entity_init_cfg(struct v4l2_subdev *subdev,
> > > + struct v4l2_subdev_pad_config *cfg)
> > > +{
> > > + struct v4l2_subdev_format fmt = { 0 };
> > > +
> > > + if (cfg)
> > > + fmt.which = V4L2_SUBDEV_FORMAT_TRY;
> > > + else
> > > + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > > +
> > > + fmt.format.width = 640;
> > > + fmt.format.height = 960;
> > > +
> > > + addi9036_set_format(subdev, cfg, &fmt);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int addi9036_get_selection(struct v4l2_subdev *sd,
> > > + struct v4l2_subdev_pad_config *cfg,
> > > + struct v4l2_subdev_selection *sel)
> > > +{
> > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > +
> > > + if (sel->target != V4L2_SEL_TGT_CROP)
> > > + return -EINVAL;
> > > +
> > > + sel->r = *addi9036_get_pad_crop(addi9036, cfg, sel->pad, sel->which);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int addi9036_s_stream(struct v4l2_subdev *subdev, int enable)
> > > +{
> > > + struct addi9036 *addi9036 = to_addi9036(subdev);
> > > +
> > > + dev_dbg(addi9036->dev, "stream %d\n", enable);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct v4l2_subdev_core_ops addi9036_core_ops = {
> > > + .s_power = addi9036_s_power,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_video_ops addi9036_video_ops = {
> > > + .s_stream = addi9036_s_stream,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_pad_ops addi9036_subdev_pad_ops = {
> > > + .init_cfg = addi9036_entity_init_cfg,
> > > + .enum_mbus_code = addi9036_enum_mbus_code,
> > > + .enum_frame_size = addi9036_enum_frame_size,
> > > + .get_fmt = addi9036_get_format,
> > > + .set_fmt = addi9036_set_format,
> > > + .get_selection = addi9036_get_selection,
> > > +};
> > > +
> > > +static const struct v4l2_subdev_ops addi9036_subdev_ops = {
> > > + .core = &addi9036_core_ops,
> > > + .video = &addi9036_video_ops,
> > > + .pad = &addi9036_subdev_pad_ops,
> > > +};
> > > +
> > > +static int addi9036_probe(struct i2c_client *client)
> > > +{
> > > + struct device *dev = &client->dev;
> > > + struct fwnode_handle *endpoint;
> > > + struct addi9036 *addi9036;
> > > + int ret;
> > > +
> > > + dev_dbg(dev, "%s: i2c addr = 0x%x\n", __func__, client->addr);
> > > +
> > > + addi9036 = devm_kzalloc(dev, sizeof(struct addi9036), GFP_KERNEL);
> > > + if (!addi9036)
> > > + return -ENOMEM;
> > > +
> > > + addi9036->dev = dev;
> > > +
> > > + addi9036->regmap = devm_regmap_init_i2c(client,
> > > + &addi9036_i2c_regmap_config);
> > > + if (IS_ERR(addi9036->regmap)) {
> > > + dev_err(dev, "Error initializing i2c regmap\n");
> > > + return PTR_ERR(addi9036->regmap);
> > > + }
> > > +
> > > + mutex_init(&addi9036->power_lock);
> > > +
> > > + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
> > > + if (!endpoint) {
> > > + dev_err(dev, "endpoint node not found\n");
> > > + return -EINVAL;
> > > + }
> > > +
> > > + ret = v4l2_fwnode_endpoint_parse(endpoint, &addi9036->ep);
> > > + if (ret < 0) {
> > > + dev_err(dev, "parsing endpoint node failed\n");
> > > + return ret;
> > > + }
> > > + fwnode_handle_put(endpoint);
> > > +
> > > + if (addi9036->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
> > > + dev_err(dev, "invalid bus type, must be MIPI CSI2\n");
> > > + return -EINVAL;
> > > + }
> > > +
> > > + addi9036->rst_gpio = gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> > > + if (IS_ERR(addi9036->rst_gpio)) {
> > > + dev_info(dev, "Unable to get \"reset\" gpio\n");
> > > + return PTR_ERR(addi9036->rst_gpio);
> > > + }
> > > +
> > > + v4l2_ctrl_handler_init(&addi9036->ctrls, 4);
> > > +
> > > + addi9036->pixel_rate = v4l2_ctrl_new_std(&addi9036->ctrls,
> > > + &addi9036_ctrl_ops,
> > > + V4L2_CID_PIXEL_RATE,
> > > + 1, INT_MAX, 1, 1);
> > > + addi9036->link_freq = v4l2_ctrl_new_int_menu(&addi9036->ctrls,
> > > + &addi9036_ctrl_ops,
> > > + V4L2_CID_LINK_FREQ,
> > > + ARRAY_SIZE(
> > > + link_freq_tbl) - 1,
> > > + 0, link_freq_tbl);
> > > + if (addi9036->link_freq)
> > > + addi9036->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > +
> > > + addi9036->set_chip_config = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > > + &addi9036_ctrl_chip_config,
> > > + NULL);
> > > +
> > > + addi9036->reg_read = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > > + &addi9036_ctrl_reg_read,
> > > + NULL);
> > > +
> > > + addi9036->sd.ctrl_handler = &addi9036->ctrls;
> > > +
> > > + if (addi9036->ctrls.error) {
> > > + dev_err(dev, "%s: control initialization error %d\n",
> > > + __func__, addi9036->ctrls.error);
> > > + ret = addi9036->ctrls.error;
> > > + goto free_ctrl;
> > > + }
> > > +
> > > + v4l2_i2c_subdev_init(&addi9036->sd, client, &addi9036_subdev_ops);
> > > + addi9036->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > + addi9036->pad.flags = MEDIA_PAD_FL_SOURCE;
> > > + addi9036->sd.dev = &client->dev;
> > > + addi9036->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > > +
> > > + ret = media_entity_pads_init(&addi9036->sd.entity, 1, &addi9036->pad);
> > > +
> > > + if (ret < 0) {
> > > + dev_err(dev, "could not register media entity\n");
> > > + goto free_ctrl;
> > > + }
> > > +
> > > + ret = addi9036_s_power(&addi9036->sd, true);
> > > + if (ret < 0) {
> > > + dev_err(dev, "could not power up addi9036\n");
> > > + goto free_entity;
> > > + }
> > > +
> > > + dev_info(dev, "addi9036 detected at address 0x%02x\n", client->addr);
> > > +
> > > + ret = addi9036_s_power(&addi9036->sd, false);
> > > + if (ret < 0) {
> > > + dev_err(dev, "could not power down addi9036\n");
> > > + goto free_entity;
> > > + }
> > > +
> > > + addi9036_entity_init_cfg(&addi9036->sd, NULL);
> > > +
> > > + ret = v4l2_async_register_subdev(&addi9036->sd);
> > > + if (ret < 0)
> > > + dev_err(dev, "could not register v4l2 device\n");
> > > +
> > > + return 0;
> > > +
> > > +free_entity:
> > > + media_entity_cleanup(&addi9036->sd.entity);
> > > +free_ctrl:
> > > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > > + mutex_destroy(&addi9036->power_lock);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int addi9036_remove(struct i2c_client *client)
> > > +{
> > > + struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > +
> > > + v4l2_async_unregister_subdev(&addi9036->sd);
> > > + media_entity_cleanup(&addi9036->sd.entity);
> > > + if (addi9036->rst_gpio)
> > > + gpiod_put(addi9036->rst_gpio);
> > > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > > + mutex_destroy(&addi9036->power_lock);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct i2c_device_id addi9036_id[] = {
> > > + { "addi9036", 0 },
> > > + {}
> > > +};
> > > +MODULE_DEVICE_TABLE(i2c, addi9036_id);
> > > +
> > > +static const struct of_device_id addi9036_of_match[] = {
> > > + { .compatible = "adi,addi9036" },
> > > + { /* sentinel */ }
> > > +};
> > > +MODULE_DEVICE_TABLE(of, addi9036_of_match);
> > > +
> > > +static struct i2c_driver addi9036_i2c_driver = {
> > > + .driver = {
> > > + .of_match_table = addi9036_of_match,
> > > + .name = "addi9036",
> > > + },
> > > + .probe_new = addi9036_probe,
> > > + .remove = addi9036_remove,
> > > + .id_table = addi9036_id,
> > > +};
> > > +
> > > +module_i2c_driver(addi9036_i2c_driver);
> > > +
> > > +MODULE_DESCRIPTION("Analog Devices ADDI9036 Camera Driver");
> > > +MODULE_AUTHOR("Bogdan Togorean");
> > > +MODULE_LICENSE("GPL v2");
> > > diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2- controls.h
> > > index 62271418c1be..f88b56479bc1 100644
> > > --- a/include/uapi/linux/v4l2-controls.h
> > > +++ b/include/uapi/linux/v4l2-controls.h
> > > @@ -198,6 +198,12 @@ enum v4l2_colorfx {
> > > */
> > > #define V4L2_CID_USER_ATMEL_ISC_BASE (V4L2_CID_USER_BASE + 0x10c0)
> > >
> > > +/*
> > > + * The base for the addi9036 driver controls.
> > > + * We reserve 16 controls for this driver.
> > > + */
> > > +#define V4L2_CID_USER_ADDI9036_BASE (V4L2_CID_USER_BASE + 0x10e0)
> > > +
> > > /* MPEG-class control IDs */
> > > /* The MPEG controls are applicable to all codec controls
> > > * and the 'MPEG' part of the define is historical */

--
Regards,

Laurent Pinchart

2020-09-17 08:23:58

by Togorean, Bogdan

[permalink] [raw]
Subject: RE: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

Hi Laurent and Sakari,
Thank you for your review,
> Hi Bogdan,
>
> On Fri, Sep 11, 2020 at 01:32:39PM +0000, Togorean, Bogdan wrote:
> > > On Thu, Sep 10, 2020 at 07:24:06PM +0300, Bogdan Togorean wrote:
> > > > The ADDI9036 is a complete, 45 MHz, front-end solution for charge
> > > > coupled device (CCD) time of flight (TOF) imaging applications.
> > > >
> > > > It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.
> > > >
> > > > The programming of calibration and firmware is performed by userspace
> > > > SDK through CID_USER class IOCTLs.
> > >
> > > I'm afraid that's not acceptable. The firmware should be loaded using
> > > the Linux kernel firmware API. As for calibration, is it static data ?
> > > How is it generated ?
> >
> > Calibration data is static data, generated at production and stored on and
> EEPROM
> > memory on camera board. The camera board consists mainly of ADDI9036
> processor
> > + CCD image sensor + EEPROM.
> >
> > The firmware is a set of (address, data) tuples that are written via I2C.
>
> Is it a firmware in the sense of software running on the ADDI9036, or is
> it a device configuration to be loaded in registers ?
>
Actually what we call "firmware" is a combination of both. The binary blob that gets written
contains ToF processor instructions that end in a RAM memory internal to ADDI9036 and
also register configurations.

I am working on a V2 that uses firmware API and NVMEM subsystem for EEPROM stored data.
> > We want this driver to be a generic one and support all implementations
> > of ADDI9036 ToF processor. Now the problem is that some implementations
> > store firmware + calibration on external EEPROM and some store just
> calibration
> > data on EEPROM and firmware is loaded from a file stored in SDK. With the help
> > of the 2 custom controls we move the camera model detection, firmware
> > and calibration fetching from driver to user space SDK.
> >
> > Also an important point is that the ToF processor has 3 operating modes
> > (near, medium and far). At each mode change a new set of firmware +
> > calibration data is loaded into ADDI9036 chip. If we move the firmware loading
> > in driver we would still need a custom enumeration control to select the
> working mode.
> >
> > > > Signed-off-by: Bogdan Togorean <[email protected]>
> > > > ---
> > > > MAINTAINERS | 10 +
> > > > drivers/media/i2c/Kconfig | 14 +
> > > > drivers/media/i2c/Makefile | 1 +
> > > > drivers/media/i2c/addi9036.c | 673
> +++++++++++++++++++++++++++++
> > > > include/uapi/linux/v4l2-controls.h | 6 +
> > > > 5 files changed, 704 insertions(+)
> > > > create mode 100644 drivers/media/i2c/addi9036.c
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index b5cfab015bd6..fb03ad9ca920 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -477,6 +477,16 @@ W: http://wiki.analog.com/AD7879
> > > > W: http://ez.analog.com/community/linux-device-drivers
> > > > F: drivers/input/touchscreen/ad7879.c
> > > >
> > > > +ADDI9036 TOF FRONTEND DRIVER
> > > > +M: Bogdan Togorean <[email protected]>
> > > > +L: [email protected]
> > > > +S: Supported
> > > > +W: https://www.analog.com/en/products/addi9036.html
> > > > +W: http://ez.analog.com/community/linux-device-drivers
> > > > +T: git git://linuxtv.org/media_tree.git
> > > > +F:
> Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
> > > > +F: drivers/media/i2c/addi9036.c
> > > > +
> > > > ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR)
> > > > M: Jiri Kosina <[email protected]>
> > > > S: Maintained
> > > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > > > index c7ba76fee599..087dd307505d 100644
> > > > --- a/drivers/media/i2c/Kconfig
> > > > +++ b/drivers/media/i2c/Kconfig
> > > > @@ -725,6 +725,20 @@ config VIDEO_APTINA_PLL
> > > > config VIDEO_SMIAPP_PLL
> > > > tristate
> > > >
> > > > +config VIDEO_ADDI9036
> > > > + tristate "Analog Devices ADDI9036 ToF front-end support"
> > > > + depends on I2C && VIDEO_V4L2
> > > > + select MEDIA_CONTROLLER
> > > > + select VIDEO_V4L2_SUBDEV_API
> > > > + select V4L2_FWNODE
> > > > + select REGMAP_I2C
> > > > + help
> > > > + This is a Video4Linux2 driver for Analog Devices ADDI9036
> > > > + Time of Flight front-end.
> > > > +
> > > > + To compile this driver as a module, choose M here: the
> > > > + module will be called addi9036.
> > > > +
> > > > config VIDEO_HI556
> > > > tristate "Hynix Hi-556 sensor support"
> > > > depends on I2C && VIDEO_V4L2
> > > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > > > index f0a77473979d..631a7c7612ca 100644
> > > > --- a/drivers/media/i2c/Makefile
> > > > +++ b/drivers/media/i2c/Makefile
> > > > @@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
> > > > obj-$(CONFIG_VIDEO_DW9714) += dw9714.o
> > > > obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
> > > > obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
> > > > +obj-$(CONFIG_VIDEO_ADDI9036) += addi9036.o
> > > > obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
> > > > obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o
> > > > obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
> > > > diff --git a/drivers/media/i2c/addi9036.c b/drivers/media/i2c/addi9036.c
> > > > new file mode 100644
> > > > index 000000000000..76c3444058dc
> > > > --- /dev/null
> > > > +++ b/drivers/media/i2c/addi9036.c
> > > > @@ -0,0 +1,673 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> > > > + * Driver for the Analog Devices ADDI9036 ToF front-end.
> > > > + *
> > > > + * Copyright (C) 2019-2020 Analog Devices, All Rights Reserved.
> > > > + *
> > > > + */
> > > > +
> > > > +#include <linux/device.h>
> > > > +#include <linux/gpio/consumer.h>
> > > > +#include <linux/i2c.h>
> > > > +#include <linux/init.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/mutex.h>
> > > > +#include <linux/of.h>
> > > > +#include <linux/of_graph.h>
> > > > +#include <linux/regmap.h>
> > > > +#include <linux/slab.h>
> > > > +#include <linux/types.h>
> > > > +#include <linux/version.h>
> > > > +#include <media/v4l2-ctrls.h>
> > > > +#include <media/v4l2-fwnode.h>
> > > > +#include <media/v4l2-subdev.h>
> > > > +
> > > > +struct addi9036_mode_info {
> > > > + u32 width;
> > > > + u32 height;
> > > > + u32 pixel_rate;
> > > > + u32 link_freq_idx;
> > > > +};
> > > > +
> > > > +struct addi9036 {
> > > > + struct regmap *regmap;
> > > > + struct device *dev;
> > > > + struct v4l2_subdev sd;
> > > > + struct media_pad pad;
> > > > + struct v4l2_fwnode_endpoint ep;
> > > > + struct v4l2_mbus_framefmt fmt;
> > > > + struct v4l2_rect crop;
> > > > +
> > > > + const struct addi9036_mode_info *current_mode;
> > > > +
> > > > + struct v4l2_ctrl_handler ctrls;
> > > > + struct v4l2_ctrl *pixel_rate;
> > > > + struct v4l2_ctrl *link_freq;
> > > > +
> > > > + /* lock to protect power state */
> > > > + struct mutex power_lock;
> > > > + int power_count;
> > > > +
> > > > + struct gpio_desc *rst_gpio;
> > > > +
> > > > + /* controls */
> > > > + struct v4l2_ctrl *set_chip_config;
> > > > + struct v4l2_ctrl *reg_read;
> > > > +};
> > > > +
> > > > +static inline struct addi9036 *to_addi9036(struct v4l2_subdev *sd)
> > > > +{
> > > > + return container_of(sd, struct addi9036, sd);
> > > > +}
> > > > +
> > > > +#define V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY
> (V4L2_CID_USER_ADDI9036_BASE + 0)
> > > > +#define V4L2_CID_AD_DEV_REG_READ_QUERY
> (V4L2_CID_USER_ADDI9036_BASE + 1)
> > > > +
> > > > +static const struct reg_sequence addi9036_powerup_setting[] = {
> > > > + { 0xc4c0, 0x001c },
> > > > + { 0xc4c3, 0x001c },
> > > > + { 0xc4d7, 0x0000 },
> > > > + { 0xc4d5, 0x0002 },
> > > > + { 0xc4da, 0x0001 },
> > > > + { 0xc4f0, 0x0000 },
> > > > + { 0xc427, 0x0003 },
> > > > + { 0xc427, 0x0001 },
> > > > + { 0xc427, 0x0000 },
> > > > + { 0xc426, 0x0030 },
> > > > + { 0xc426, 0x0010 },
> > > > + { 0xc426, 0x0000 },
> > > > + { 0xc423, 0x0080 },
> > > > + { 0xc431, 0x0080 },
> > > > + { 0x4001, 0x0007 },
> > > > + { 0x7c22, 0x0004 }
> > > > +};
> > > > +
> > > > +static const struct reg_sequence addi9036_powerdown_setting[] = {
> > > > + { 0xc022, 0x0001 },
> > > > + { 0x4001, 0x0006 },
> > > > + { 0x7c22, 0x0004 },
> > > > + { 0xc431, 0x0082 },
> > > > + { 0xc423, 0x0000 },
> > > > + { 0xc426, 0x0020 },
> > > > + { 0xc427, 0x0002 },
> > > > + { 0xc4c0, 0x003c },
> > > > + { 0xc4c3, 0x003c },
> > > > + { 0xc4d5, 0x0003 },
> > > > + { 0xc4da, 0x0000 },
> > > > + { 0xc4d7, 0x0001 },
> > > > + { 0xc4f0, 0x0001 }
> > > > +};
> > > > +
> > > > +static const s64 link_freq_tbl[] = {
> > > > + 110529000,
> > > > + 221184000,
> > > > +};
> > > > +
> > > > +/* Elements of the structure must be ordered ascending by width & height
> */
> > > > +static const struct addi9036_mode_info addi9036_mode_info_data[] = {
> > > > + {
> > > > + .width = 640,
> > > > + .height = 480,
> > > > + .pixel_rate = 36864000,
> > > > + .link_freq_idx = 0 /* an index in link_freq_tbl[] */
> > > > + },
> > > > + {
> > > > + .width = 640,
> > > > + .height = 960,
> > > > + .pixel_rate = 73728000,
> > > > + .link_freq_idx = 1 /* an index in link_freq_tbl[] */
> > > > + },
> > > > +};
> > > > +
> > > > +static bool addi9306_readable_register(struct device *dev, unsigned int
> reg)
> > > > +{
> > > > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > > > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > > > + ((reg >= 0xC000) && (reg <= 0xC200)) ||
> > > > + ((reg >= 0xC300) && (reg <= 0xC6BF)))
> > > > + return true;
> > > > + else
> > > > + return false;
> > > > +}
> > > > +
> > > > +static bool addi9306_writeable_register(struct device *dev, unsigned int
> reg)
> > > > +{
> > > > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > > > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > > > + ((reg >= 0xC000) && (reg <= 0xC113)) ||
> > > > + ((reg >= 0xC300) && (reg <= 0xC7FF)))
> > > > + return true;
> > > > + else
> > > > + return false;
> > > > +}
> > > > +
> > > > +static const struct regmap_config addi9036_i2c_regmap_config = {
> > > > + .reg_bits = 16,
> > > > + .val_bits = 16,
> > > > +
> > > > + .max_register = 0xC7FF,
> > > > + .writeable_reg = addi9306_writeable_register,
> > > > + .readable_reg = addi9306_readable_register,
> > > > + .cache_type = REGCACHE_NONE,
> > > > +};
> > > > +
> > > > +static int addi9036_set_power_on(struct addi9036 *addi9036)
> > > > +{
> > > > + int ret;
> > > > +
> > > > + if (addi9036->rst_gpio)
> > > > + gpiod_set_value_cansleep(addi9036->rst_gpio, 0);
> > > > +
> > > > + ret = regmap_register_patch(addi9036->regmap,
> addi9036_powerup_setting,
> > > > + ARRAY_SIZE(addi9036_powerup_setting));
> > > > + if (ret)
> > > > + dev_err(addi9036->dev, "Could not set power up registers\n");
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int addi9036_set_power_off(struct addi9036 *addi9036)
> > > > +{
> > > > + int ret;
> > > > +
> > > > + ret = regmap_register_patch(addi9036->regmap,
> > > > + addi9036_powerdown_setting,
> > > > + ARRAY_SIZE(addi9036_powerdown_setting));
> > > > + if (ret)
> > > > + dev_err(addi9036->dev, "could not set power down
> registers\n");
> > > > +
> > > > + if (addi9036->rst_gpio)
> > > > + gpiod_set_value_cansleep(addi9036->rst_gpio, 1);
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int addi9036_s_power(struct v4l2_subdev *sd, int on)
> > > > +{
> > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > + int ret = 0;
> > > > +
> > > > + mutex_lock(&addi9036->power_lock);
> > > > +
> > > > + dev_dbg(addi9036->dev, "s_power: %d\n", on);
> > > > +
> > > > + /* If the power count is modified from 0 to != 0 or from != 0 to 0,
> > > > + * update the power state.
> > > > + */
> > > > + if (addi9036->power_count == !on) {
> > > > + if (on)
> > > > + ret = addi9036_set_power_on(addi9036);
> > > > + else
> > > > + ret = addi9036_set_power_off(addi9036);
> > > > + }
> > > > +
> > > > + if (!ret) {
> > > > + /* Update the power count. */
> > > > + addi9036->power_count += on ? 1 : -1;
> > > > + WARN(addi9036->power_count < 0, "Unbalanced power
> count\n");
> > > > + WARN(addi9036->power_count > 1, "Duplicated s_power
> call\n");
> > > > + }
> > > > +
> > > > + mutex_unlock(&addi9036->power_lock);
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int addi9036_set_chip_config(struct addi9036 *addi9036,
> > > > + struct v4l2_ctrl *ctrl)
> > > > +{
> > > > + int index;
> > > > + unsigned short *reg, *val;
> > > > +
> > > > + reg = (unsigned short *)(ctrl->p_new.p_u16);
> > > > + val = (unsigned short *)(ctrl->p_new.p_u16 + 1);
> > > > +
> > > > + for (index = 0; index < ctrl->elems; index += 2) {
> > > > + regmap_write(addi9036->regmap, *reg, *val);
> > > > + reg += 2;
> > > > + val += 2;
> > > > + }
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int addi9036_s_ctrl(struct v4l2_ctrl *ctrl)
> > > > +{
> > > > + struct addi9036 *addi9036 = container_of(ctrl->handler,
> > > > + struct addi9036, ctrls);
> > > > + int ret = 0;
> > > > + unsigned int val;
> > > > +
> > > > + switch (ctrl->id) {
> > > > + case V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY:
> > > > + ret = addi9036_set_chip_config(addi9036, ctrl);
> > > > + break;
> > > > + case V4L2_CID_AD_DEV_REG_READ_QUERY:
> > > > + ret = regmap_read(addi9036->regmap, *(u16 *)(ctrl-
> > > >p_new.p_u16),
> > > > + &val);
> > > > + if (ret)
> > > > + dev_warn(addi9036->dev,
> > > > + "could not read from register\n");
> > > > + else
> > > > + *(u16 *)(ctrl->p_new.p_u16) = val;
> > > > + break;
> > > > + case V4L2_CID_PIXEL_RATE:
> > > > + case V4L2_CID_LINK_FREQ:
> > > > + break;
> > > > + default:
> > > > + dev_err(addi9036->dev, "%s > Unhandled: %x param=%x\n",
> > > > + __func__, ctrl->id, ctrl->val);
> > > > + ret = -EINVAL;
> > > > + break;
> > > > + }
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static const struct v4l2_ctrl_ops addi9036_ctrl_ops = {
> > > > + .s_ctrl = addi9036_s_ctrl,
> > > > +};
> > > > +
> > > > +static const struct v4l2_ctrl_config addi9036_ctrl_chip_config = {
> > > > + .ops = &addi9036_ctrl_ops,
> > > > + .id = V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY,
> > > > + .name = "chip_config",
> > > > + .type = V4L2_CTRL_TYPE_U16,
> > > > + .def = 0xFF,
> > > > + .min = 0x00,
> > > > + .max = 0xFFFF,
> > > > + .step = 1,
> > > > + .dims = { 2048 },
> > > > + .elem_size = 2
> > > > +};
> > > > +
> > > > +static const struct v4l2_ctrl_config addi9036_ctrl_reg_read = {
> > > > + .ops = &addi9036_ctrl_ops,
> > > > + .id = V4L2_CID_AD_DEV_REG_READ_QUERY,
> > > > + .name = "reg_read",
> > > > + .type = V4L2_CTRL_TYPE_U16,
> > > > + .def = 0,
> > > > + .min = 0x00,
> > > > + .max = 0xFFFF,
> > > > + .step = 1
> > > > +};
> > > > +
> > > > +static int addi9036_enum_mbus_code(struct v4l2_subdev *sd,
> > > > + struct v4l2_subdev_pad_config *cfg,
> > > > + struct v4l2_subdev_mbus_code_enum *code)
> > > > +{
> > > > + if (code->index > 0)
> > > > + return -EINVAL;
> > > > +
> > > > + code->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int addi9036_enum_frame_size(struct v4l2_subdev *subdev,
> > > > + struct v4l2_subdev_pad_config *cfg,
> > > > + struct v4l2_subdev_frame_size_enum *fse)
> > > > +{
> > > > + if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12)
> > > > + return -EINVAL;
> > > > +
> > > > + if (fse->index >= ARRAY_SIZE(addi9036_mode_info_data))
> > > > + return -EINVAL;
> > > > +
> > > > + fse->min_width = addi9036_mode_info_data[fse->index].width;
> > > > + fse->max_width = addi9036_mode_info_data[fse->index].width;
> > > > + fse->min_height = addi9036_mode_info_data[fse->index].height;
> > > > + fse->max_height = addi9036_mode_info_data[fse->index].height;
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static struct v4l2_mbus_framefmt *
> > > > +addi9036_get_pad_format(struct addi9036 *addi9036,
> > > > + struct v4l2_subdev_pad_config *cfg,
> > > > + unsigned int pad,
> > > > + enum v4l2_subdev_format_whence which)
> > > > +{
> > > > + switch (which) {
> > > > + case V4L2_SUBDEV_FORMAT_TRY:
> > > > + return v4l2_subdev_get_try_format(&addi9036->sd, cfg, pad);
> > > > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > > > + return &addi9036->fmt;
> > > > + default:
> > > > + return NULL;
> > > > + }
> > > > +}
> > > > +
> > > > +static int addi9036_get_format(struct v4l2_subdev *sd,
> > > > + struct v4l2_subdev_pad_config *cfg,
> > > > + struct v4l2_subdev_format *format)
> > > > +{
> > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > + struct v4l2_mbus_framefmt *pad_format;
> > > > +
> > > > + pad_format = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > > > + format->which);
> > > > +
> > > > + if (!pad_format)
> > > > + return -EINVAL;
> > > > +
> > > > + format->format = *pad_format;
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static struct v4l2_rect *
> > > > +addi9036_get_pad_crop(struct addi9036 *addi9036,
> > > > + struct v4l2_subdev_pad_config *cfg,
> > > > + unsigned int pad, enum v4l2_subdev_format_whence which)
> > > > +{
> > > > + switch (which) {
> > > > + case V4L2_SUBDEV_FORMAT_TRY:
> > > > + return v4l2_subdev_get_try_crop(&addi9036->sd, cfg, pad);
> > > > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > > > + return &addi9036->crop;
> > > > + default:
> > > > + return NULL;
> > > > + }
> > > > +}
> > > > +
> > > > +static const struct addi9036_mode_info *
> > > > +addi9036_find_nearest_mode(unsigned int width, unsigned int height)
> > > > +{
> > > > + unsigned int i;
> > > > +
> > > > + for (i = ARRAY_SIZE(addi9036_mode_info_data) - 1; i > 0; i--) {
> > > > + if (addi9036_mode_info_data[i].width <= width &&
> > > > + addi9036_mode_info_data[i].height <= height)
> > > > + break;
> > > > + }
> > > > +
> > > > + return &addi9036_mode_info_data[i];
> > > > +}
> > > > +
> > > > +static int addi9036_set_format(struct v4l2_subdev *sd,
> > > > + struct v4l2_subdev_pad_config *cfg,
> > > > + struct v4l2_subdev_format *format)
> > > > +{
> > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > + struct v4l2_mbus_framefmt *framefmt;
> > > > + struct v4l2_rect *crop;
> > > > + const struct addi9036_mode_info *new_mode;
> > > > + int ret;
> > > > +
> > > > + dev_dbg(addi9036->dev, "set_fmt: %x %dx%d\n",
> > > > + format->format.code, format->format.width,
> > > > + format->format.height);
> > > > +
> > > > + crop = addi9036_get_pad_crop(addi9036, cfg, format->pad,
> > > > + format->which);
> > > > +
> > > > + if (!crop)
> > > > + return -EINVAL;
> > > > +
> > > > + new_mode = addi9036_find_nearest_mode(format->format.width,
> > > > + format->format.height);
> > > > + crop->width = new_mode->width;
> > > > + crop->height = new_mode->height;
> > > > +
> > > > + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > > > + ret = v4l2_ctrl_s_ctrl_int64(addi9036->pixel_rate,
> > > > + new_mode->pixel_rate);
> > > > + if (ret < 0)
> > > > + return ret;
> > > > +
> > > > + ret = v4l2_ctrl_s_ctrl(addi9036->link_freq,
> > > > + new_mode->link_freq_idx);
> > > > + if (ret < 0)
> > > > + return ret;
> > > > +
> > > > + addi9036->current_mode = new_mode;
> > > > + }
> > > > +
> > > > + framefmt = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > > > + format->which);
> > > > +
> > > > + if (!framefmt)
> > > > + return -EINVAL;
> > > > +
> > > > + framefmt->width = crop->width;
> > > > + framefmt->height = crop->height;
> > > > + framefmt->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > > > + framefmt->field = V4L2_FIELD_NONE;
> > > > + framefmt->colorspace = V4L2_COLORSPACE_SRGB;
> > > > +
> > > > + format->format = *framefmt;
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int addi9036_entity_init_cfg(struct v4l2_subdev *subdev,
> > > > + struct v4l2_subdev_pad_config *cfg)
> > > > +{
> > > > + struct v4l2_subdev_format fmt = { 0 };
> > > > +
> > > > + if (cfg)
> > > > + fmt.which = V4L2_SUBDEV_FORMAT_TRY;
> > > > + else
> > > > + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > > > +
> > > > + fmt.format.width = 640;
> > > > + fmt.format.height = 960;
> > > > +
> > > > + addi9036_set_format(subdev, cfg, &fmt);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int addi9036_get_selection(struct v4l2_subdev *sd,
> > > > + struct v4l2_subdev_pad_config *cfg,
> > > > + struct v4l2_subdev_selection *sel)
> > > > +{
> > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > +
> > > > + if (sel->target != V4L2_SEL_TGT_CROP)
> > > > + return -EINVAL;
> > > > +
> > > > + sel->r = *addi9036_get_pad_crop(addi9036, cfg, sel->pad, sel->which);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int addi9036_s_stream(struct v4l2_subdev *subdev, int enable)
> > > > +{
> > > > + struct addi9036 *addi9036 = to_addi9036(subdev);
> > > > +
> > > > + dev_dbg(addi9036->dev, "stream %d\n", enable);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static const struct v4l2_subdev_core_ops addi9036_core_ops = {
> > > > + .s_power = addi9036_s_power,
> > > > +};
> > > > +
> > > > +static const struct v4l2_subdev_video_ops addi9036_video_ops = {
> > > > + .s_stream = addi9036_s_stream,
> > > > +};
> > > > +
> > > > +static const struct v4l2_subdev_pad_ops addi9036_subdev_pad_ops = {
> > > > + .init_cfg = addi9036_entity_init_cfg,
> > > > + .enum_mbus_code = addi9036_enum_mbus_code,
> > > > + .enum_frame_size = addi9036_enum_frame_size,
> > > > + .get_fmt = addi9036_get_format,
> > > > + .set_fmt = addi9036_set_format,
> > > > + .get_selection = addi9036_get_selection,
> > > > +};
> > > > +
> > > > +static const struct v4l2_subdev_ops addi9036_subdev_ops = {
> > > > + .core = &addi9036_core_ops,
> > > > + .video = &addi9036_video_ops,
> > > > + .pad = &addi9036_subdev_pad_ops,
> > > > +};
> > > > +
> > > > +static int addi9036_probe(struct i2c_client *client)
> > > > +{
> > > > + struct device *dev = &client->dev;
> > > > + struct fwnode_handle *endpoint;
> > > > + struct addi9036 *addi9036;
> > > > + int ret;
> > > > +
> > > > + dev_dbg(dev, "%s: i2c addr = 0x%x\n", __func__, client->addr);
> > > > +
> > > > + addi9036 = devm_kzalloc(dev, sizeof(struct addi9036), GFP_KERNEL);
> > > > + if (!addi9036)
> > > > + return -ENOMEM;
> > > > +
> > > > + addi9036->dev = dev;
> > > > +
> > > > + addi9036->regmap = devm_regmap_init_i2c(client,
> > > > + &addi9036_i2c_regmap_config);
> > > > + if (IS_ERR(addi9036->regmap)) {
> > > > + dev_err(dev, "Error initializing i2c regmap\n");
> > > > + return PTR_ERR(addi9036->regmap);
> > > > + }
> > > > +
> > > > + mutex_init(&addi9036->power_lock);
> > > > +
> > > > + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev),
> NULL);
> > > > + if (!endpoint) {
> > > > + dev_err(dev, "endpoint node not found\n");
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + ret = v4l2_fwnode_endpoint_parse(endpoint, &addi9036->ep);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "parsing endpoint node failed\n");
> > > > + return ret;
> > > > + }
> > > > + fwnode_handle_put(endpoint);
> > > > +
> > > > + if (addi9036->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
> > > > + dev_err(dev, "invalid bus type, must be MIPI CSI2\n");
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + addi9036->rst_gpio = gpiod_get_optional(dev, "reset",
> GPIOD_OUT_HIGH);
> > > > + if (IS_ERR(addi9036->rst_gpio)) {
> > > > + dev_info(dev, "Unable to get \"reset\" gpio\n");
> > > > + return PTR_ERR(addi9036->rst_gpio);
> > > > + }
> > > > +
> > > > + v4l2_ctrl_handler_init(&addi9036->ctrls, 4);
> > > > +
> > > > + addi9036->pixel_rate = v4l2_ctrl_new_std(&addi9036->ctrls,
> > > > + &addi9036_ctrl_ops,
> > > > + V4L2_CID_PIXEL_RATE,
> > > > + 1, INT_MAX, 1, 1);
> > > > + addi9036->link_freq = v4l2_ctrl_new_int_menu(&addi9036->ctrls,
> > > > + &addi9036_ctrl_ops,
> > > > + V4L2_CID_LINK_FREQ,
> > > > + ARRAY_SIZE(
> > > > + link_freq_tbl) - 1,
> > > > + 0, link_freq_tbl);
> > > > + if (addi9036->link_freq)
> > > > + addi9036->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > > +
> > > > + addi9036->set_chip_config = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > > > + &addi9036_ctrl_chip_config,
> > > > + NULL);
> > > > +
> > > > + addi9036->reg_read = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > > > + &addi9036_ctrl_reg_read,
> > > > + NULL);
> > > > +
> > > > + addi9036->sd.ctrl_handler = &addi9036->ctrls;
> > > > +
> > > > + if (addi9036->ctrls.error) {
> > > > + dev_err(dev, "%s: control initialization error %d\n",
> > > > + __func__, addi9036->ctrls.error);
> > > > + ret = addi9036->ctrls.error;
> > > > + goto free_ctrl;
> > > > + }
> > > > +
> > > > + v4l2_i2c_subdev_init(&addi9036->sd, client, &addi9036_subdev_ops);
> > > > + addi9036->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > > + addi9036->pad.flags = MEDIA_PAD_FL_SOURCE;
> > > > + addi9036->sd.dev = &client->dev;
> > > > + addi9036->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > > > +
> > > > + ret = media_entity_pads_init(&addi9036->sd.entity, 1, &addi9036->pad);
> > > > +
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "could not register media entity\n");
> > > > + goto free_ctrl;
> > > > + }
> > > > +
> > > > + ret = addi9036_s_power(&addi9036->sd, true);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "could not power up addi9036\n");
> > > > + goto free_entity;
> > > > + }
> > > > +
> > > > + dev_info(dev, "addi9036 detected at address 0x%02x\n", client->addr);
> > > > +
> > > > + ret = addi9036_s_power(&addi9036->sd, false);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "could not power down addi9036\n");
> > > > + goto free_entity;
> > > > + }
> > > > +
> > > > + addi9036_entity_init_cfg(&addi9036->sd, NULL);
> > > > +
> > > > + ret = v4l2_async_register_subdev(&addi9036->sd);
> > > > + if (ret < 0)
> > > > + dev_err(dev, "could not register v4l2 device\n");
> > > > +
> > > > + return 0;
> > > > +
> > > > +free_entity:
> > > > + media_entity_cleanup(&addi9036->sd.entity);
> > > > +free_ctrl:
> > > > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > > > + mutex_destroy(&addi9036->power_lock);
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int addi9036_remove(struct i2c_client *client)
> > > > +{
> > > > + struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > +
> > > > + v4l2_async_unregister_subdev(&addi9036->sd);
> > > > + media_entity_cleanup(&addi9036->sd.entity);
> > > > + if (addi9036->rst_gpio)
> > > > + gpiod_put(addi9036->rst_gpio);
> > > > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > > > + mutex_destroy(&addi9036->power_lock);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static const struct i2c_device_id addi9036_id[] = {
> > > > + { "addi9036", 0 },
> > > > + {}
> > > > +};
> > > > +MODULE_DEVICE_TABLE(i2c, addi9036_id);
> > > > +
> > > > +static const struct of_device_id addi9036_of_match[] = {
> > > > + { .compatible = "adi,addi9036" },
> > > > + { /* sentinel */ }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(of, addi9036_of_match);
> > > > +
> > > > +static struct i2c_driver addi9036_i2c_driver = {
> > > > + .driver = {
> > > > + .of_match_table = addi9036_of_match,
> > > > + .name = "addi9036",
> > > > + },
> > > > + .probe_new = addi9036_probe,
> > > > + .remove = addi9036_remove,
> > > > + .id_table = addi9036_id,
> > > > +};
> > > > +
> > > > +module_i2c_driver(addi9036_i2c_driver);
> > > > +
> > > > +MODULE_DESCRIPTION("Analog Devices ADDI9036 Camera Driver");
> > > > +MODULE_AUTHOR("Bogdan Togorean");
> > > > +MODULE_LICENSE("GPL v2");
> > > > diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-
> controls.h
> > > > index 62271418c1be..f88b56479bc1 100644
> > > > --- a/include/uapi/linux/v4l2-controls.h
> > > > +++ b/include/uapi/linux/v4l2-controls.h
> > > > @@ -198,6 +198,12 @@ enum v4l2_colorfx {
> > > > */
> > > > #define V4L2_CID_USER_ATMEL_ISC_BASE
> (V4L2_CID_USER_BASE + 0x10c0)
> > > >
> > > > +/*
> > > > + * The base for the addi9036 driver controls.
> > > > + * We reserve 16 controls for this driver.
> > > > + */
> > > > +#define V4L2_CID_USER_ADDI9036_BASE
> (V4L2_CID_USER_BASE + 0x10e0)
> > > > +
> > > > /* MPEG-class control IDs */
> > > > /* The MPEG controls are applicable to all codec controls
> > > > * and the 'MPEG' part of the define is historical */
>
> --
> Regards,
>
> Laurent Pinchart


Attachments:
winmail.dat (24.48 kB)

2020-09-17 09:48:37

by Togorean, Bogdan

[permalink] [raw]
Subject: RE: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

Thank you Bingbu for review,
I'll implement your remarks in V2 that I'm working on now

> Bogdan, thanks for your patch.
>
> On 9/11/20 12:24 AM, Bogdan Togorean wrote:
> > The ADDI9036 is a complete, 45 MHz, front-end solution for charge
> > coupled device (CCD) time of flight (TOF) imaging applications.
> >
> > It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.
> >
> > The programming of calibration and firmware is performed by userspace
> > SDK through CID_USER class IOCTLs.
> >
> > Signed-off-by: Bogdan Togorean <[email protected]>
> > ---
> > MAINTAINERS | 10 +
> > drivers/media/i2c/Kconfig | 14 +
> > drivers/media/i2c/Makefile | 1 +
> > drivers/media/i2c/addi9036.c | 673 +++++++++++++++++++++++++++++
> > include/uapi/linux/v4l2-controls.h | 6 +
> > 5 files changed, 704 insertions(+)
> > create mode 100644 drivers/media/i2c/addi9036.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index b5cfab015bd6..fb03ad9ca920 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -477,6 +477,16 @@ W: http://wiki.analog.com/AD7879
> > W: http://ez.analog.com/community/linux-device-drivers
> > F: drivers/input/touchscreen/ad7879.c
> >
> > +ADDI9036 TOF FRONTEND DRIVER
> > +M: Bogdan Togorean <[email protected]>
> > +L: [email protected]
> > +S: Supported
> > +W: https://www.analog.com/en/products/addi9036.html
> > +W: http://ez.analog.com/community/linux-device-drivers
> > +T: git git://linuxtv.org/media_tree.git
> > +F: Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
> > +F: drivers/media/i2c/addi9036.c
> > +
> > ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR)
> > M: Jiri Kosina <[email protected]>
> > S: Maintained
> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > index c7ba76fee599..087dd307505d 100644
> > --- a/drivers/media/i2c/Kconfig
> > +++ b/drivers/media/i2c/Kconfig
> > @@ -725,6 +725,20 @@ config VIDEO_APTINA_PLL
> > config VIDEO_SMIAPP_PLL
> > tristate
> >
> > +config VIDEO_ADDI9036
> > + tristate "Analog Devices ADDI9036 ToF front-end support"
> > + depends on I2C && VIDEO_V4L2
> > + select MEDIA_CONTROLLER
> > + select VIDEO_V4L2_SUBDEV_API
> > + select V4L2_FWNODE
> > + select REGMAP_I2C
> > + help
> > + This is a Video4Linux2 driver for Analog Devices ADDI9036
> > + Time of Flight front-end.
> > +
> > + To compile this driver as a module, choose M here: the
> > + module will be called addi9036.
> > +
> > config VIDEO_HI556
> > tristate "Hynix Hi-556 sensor support"
> > depends on I2C && VIDEO_V4L2
> > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > index f0a77473979d..631a7c7612ca 100644
> > --- a/drivers/media/i2c/Makefile
> > +++ b/drivers/media/i2c/Makefile
> > @@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
> > obj-$(CONFIG_VIDEO_DW9714) += dw9714.o
> > obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
> > obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
> > +obj-$(CONFIG_VIDEO_ADDI9036) += addi9036.o
> > obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
> > obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o
> > obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
> > diff --git a/drivers/media/i2c/addi9036.c b/drivers/media/i2c/addi9036.c
> > new file mode 100644
> > index 000000000000..76c3444058dc
> > --- /dev/null
> > +++ b/drivers/media/i2c/addi9036.c
> > @@ -0,0 +1,673 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Driver for the Analog Devices ADDI9036 ToF front-end.
> > + *
> > + * Copyright (C) 2019-2020 Analog Devices, All Rights Reserved.
> > + *
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/init.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +#include <linux/types.h>
> > +#include <linux/version.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +struct addi9036_mode_info {
> > + u32 width;
> > + u32 height;
> > + u32 pixel_rate;
> > + u32 link_freq_idx;
> > +};
> > +
> > +struct addi9036 {
> > + struct regmap *regmap;
> > + struct device *dev;
> > + struct v4l2_subdev sd;
> > + struct media_pad pad;
> > + struct v4l2_fwnode_endpoint ep;
> > + struct v4l2_mbus_framefmt fmt;
> > + struct v4l2_rect crop;
> > +
> > + const struct addi9036_mode_info *current_mode;
> > +
> > + struct v4l2_ctrl_handler ctrls;
> > + struct v4l2_ctrl *pixel_rate;
> > + struct v4l2_ctrl *link_freq;
> > +
> > + /* lock to protect power state */
> > + struct mutex power_lock;
> > + int power_count;
> > +
> > + struct gpio_desc *rst_gpio;
> > +
> > + /* controls */
> > + struct v4l2_ctrl *set_chip_config;
> > + struct v4l2_ctrl *reg_read;
> > +};
> > +
> > +static inline struct addi9036 *to_addi9036(struct v4l2_subdev *sd)
> > +{
> > + return container_of(sd, struct addi9036, sd);
> > +}
> > +
> > +#define V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY
> (V4L2_CID_USER_ADDI9036_BASE + 0)
> > +#define V4L2_CID_AD_DEV_REG_READ_QUERY
> (V4L2_CID_USER_ADDI9036_BASE + 1)
> > +
> > +static const struct reg_sequence addi9036_powerup_setting[] = {
> > + { 0xc4c0, 0x001c },
> > + { 0xc4c3, 0x001c },
> > + { 0xc4d7, 0x0000 },
> > + { 0xc4d5, 0x0002 },
> > + { 0xc4da, 0x0001 },
> > + { 0xc4f0, 0x0000 },
> > + { 0xc427, 0x0003 },
> > + { 0xc427, 0x0001 },
> > + { 0xc427, 0x0000 },
> > + { 0xc426, 0x0030 },
> > + { 0xc426, 0x0010 },
> > + { 0xc426, 0x0000 },
> > + { 0xc423, 0x0080 },
> > + { 0xc431, 0x0080 },
> > + { 0x4001, 0x0007 },
> > + { 0x7c22, 0x0004 }
> > +};
> > +
> > +static const struct reg_sequence addi9036_powerdown_setting[] = {
> > + { 0xc022, 0x0001 },
> > + { 0x4001, 0x0006 },
> > + { 0x7c22, 0x0004 },
> > + { 0xc431, 0x0082 },
> > + { 0xc423, 0x0000 },
> > + { 0xc426, 0x0020 },
> > + { 0xc427, 0x0002 },
> > + { 0xc4c0, 0x003c },
> > + { 0xc4c3, 0x003c },
> > + { 0xc4d5, 0x0003 },
> > + { 0xc4da, 0x0000 },
> > + { 0xc4d7, 0x0001 },
> > + { 0xc4f0, 0x0001 }
> > +};
> > +
> > +static const s64 link_freq_tbl[] = {
> > + 110529000,
> > + 221184000,
> > +};
> Menu makes more sense than table, IMO.
> > +
> > +/* Elements of the structure must be ordered ascending by width & height */
> > +static const struct addi9036_mode_info addi9036_mode_info_data[] = {
> > + {
> > + .width = 640,
> > + .height = 480,
> > + .pixel_rate = 36864000,
> > + .link_freq_idx = 0 /* an index in link_freq_tbl[] */
> > + },
> > + {
> > + .width = 640,
> > + .height = 960,
> > + .pixel_rate = 73728000,
> > + .link_freq_idx = 1 /* an index in link_freq_tbl[] */
> > + },
> > +};
> > +
> > +static bool addi9306_readable_register(struct device *dev, unsigned int reg)
> > +{
> > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > + ((reg >= 0xC000) && (reg <= 0xC200)) ||
> > + ((reg >= 0xC300) && (reg <= 0xC6BF)))
> > + return true;
> > + else
> > + return false;
> > +}
> > +
> > +static bool addi9306_writeable_register(struct device *dev, unsigned int reg)
> > +{
> > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > + ((reg >= 0xC000) && (reg <= 0xC113)) ||
> > + ((reg >= 0xC300) && (reg <= 0xC7FF)))
> > + return true;
> > + else
> > + return false;
> > +}
> Unnecessary parentheses around 'xxx >= 0xxxxx'. So how about
> (...&&...||..) ? true : false, and the first 'dev' parameter is unused and could
> be removed.
>
> > +
> > +static const struct regmap_config addi9036_i2c_regmap_config = {
> > + .reg_bits = 16,
> > + .val_bits = 16,
> > +
> unnecessary blank line here?
>
> > + .max_register = 0xC7FF,
> lower case.
> > + .writeable_reg = addi9306_writeable_register,
> > + .readable_reg = addi9306_readable_register,
> > + .cache_type = REGCACHE_NONE,
> > +};
> > +
> > +static int addi9036_set_power_on(struct addi9036 *addi9036)
> > +{
> > + int ret;
> > +
> > + if (addi9036->rst_gpio)
> > + gpiod_set_value_cansleep(addi9036->rst_gpio, 0);
> > +
> > + ret = regmap_register_patch(addi9036->regmap,
> addi9036_powerup_setting,
> > + ARRAY_SIZE(addi9036_powerup_setting));
> > + if (ret)
> > + dev_err(addi9036->dev, "Could not set power up registers\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int addi9036_set_power_off(struct addi9036 *addi9036)
> > +{
> > + int ret;
> > +
> > + ret = regmap_register_patch(addi9036->regmap,
> > + addi9036_powerdown_setting,
> > + ARRAY_SIZE(addi9036_powerdown_setting));
> > + if (ret)
> > + dev_err(addi9036->dev, "could not set power down
> registers\n");
> > +
> > + if (addi9036->rst_gpio)
> > + gpiod_set_value_cansleep(addi9036->rst_gpio, 1);
> > +
> > + return ret;
> > +}
>
> Could we merge these 2 wrapper into one? it could reduce some code
> duplication
> and looks succinct.
> > +
> > +static int addi9036_s_power(struct v4l2_subdev *sd, int on)
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > + int ret = 0;
> > +
> > + mutex_lock(&addi9036->power_lock);
> > +
> > + dev_dbg(addi9036->dev, "s_power: %d\n", on);
>
> This line can be out of protection.
> > +
> > + /* If the power count is modified from 0 to != 0 or from != 0 to 0,
> > + * update the power state.
> > + */
> > + if (addi9036->power_count == !on) {
> > + if (on)
> > + ret = addi9036_set_power_on(addi9036);
> > + else
> > + ret = addi9036_set_power_off(addi9036);
> If you use single one power function, one line here is enough.
> > + }
> > +
> > + if (!ret) {
> > + /* Update the power count. */
> > + addi9036->power_count += on ? 1 : -1;
> > + WARN(addi9036->power_count < 0, "Unbalanced power
> count\n");
> > + WARN(addi9036->power_count > 1, "Duplicated s_power
> call\n");
> > + }
> > +
> > + mutex_unlock(&addi9036->power_lock);
> > +
> > + return ret;
> > +}
> > +
> > +static int addi9036_set_chip_config(struct addi9036 *addi9036,
> > + struct v4l2_ctrl *ctrl)
> > +{
> > + int index;
> > + unsigned short *reg, *val;
> > +
> > + reg = (unsigned short *)(ctrl->p_new.p_u16);
> > + val = (unsigned short *)(ctrl->p_new.p_u16 + 1);
> > +
> > + for (index = 0; index < ctrl->elems; index += 2) {
> > + regmap_write(addi9036->regmap, *reg, *val);
> > + reg += 2;
> > + val += 2;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_s_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > + struct addi9036 *addi9036 = container_of(ctrl->handler,
> > + struct addi9036, ctrls);
> > + int ret = 0;
> > + unsigned int val;
> > +
> > + switch (ctrl->id) {
> > + case V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY:
> > + ret = addi9036_set_chip_config(addi9036, ctrl);
> > + break;
> > + case V4L2_CID_AD_DEV_REG_READ_QUERY:
> > + ret = regmap_read(addi9036->regmap, *(u16 *)(ctrl-
> >p_new.p_u16),
> > + &val);
> > + if (ret)
> > + dev_warn(addi9036->dev,
> > + "could not read from register\n");
> > + else
> > + *(u16 *)(ctrl->p_new.p_u16) = val;
> > + break;
> > + case V4L2_CID_PIXEL_RATE:
> > + case V4L2_CID_LINK_FREQ:
> > + break;
> > + default:
> > + dev_err(addi9036->dev, "%s > Unhandled: %x param=%x\n",
> > + __func__, ctrl->id, ctrl->val);
> > + ret = -EINVAL;
> > + break;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static const struct v4l2_ctrl_ops addi9036_ctrl_ops = {
> > + .s_ctrl = addi9036_s_ctrl,
> > +};
> > +
> > +static const struct v4l2_ctrl_config addi9036_ctrl_chip_config = {
> > + .ops = &addi9036_ctrl_ops,
> > + .id = V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY,
> > + .name = "chip_config",
> > + .type = V4L2_CTRL_TYPE_U16,
> > + .def = 0xFF,
> > + .min = 0x00,
> > + .max = 0xFFFF,
> > + .step = 1,
> > + .dims = { 2048 },
> > + .elem_size = 2
> > +};
> > +
> > +static const struct v4l2_ctrl_config addi9036_ctrl_reg_read = {
> > + .ops = &addi9036_ctrl_ops,
> > + .id = V4L2_CID_AD_DEV_REG_READ_QUERY,
> > + .name = "reg_read",
> > + .type = V4L2_CTRL_TYPE_U16,
> > + .def = 0,
> > + .min = 0x00,
> > + .max = 0xFFFF,
> > + .step = 1
> > +};
> > +
> > +static int addi9036_enum_mbus_code(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > + if (code->index > 0)
> > + return -EINVAL;
> > +
> > + code->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_enum_frame_size(struct v4l2_subdev *subdev,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > + if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12)
> > + return -EINVAL;
> > +
> > + if (fse->index >= ARRAY_SIZE(addi9036_mode_info_data))
> > + return -EINVAL;
> > +
> > + fse->min_width = addi9036_mode_info_data[fse->index].width;
> > + fse->max_width = addi9036_mode_info_data[fse->index].width;
> > + fse->min_height = addi9036_mode_info_data[fse->index].height;
> > + fse->max_height = addi9036_mode_info_data[fse->index].height;
> > +
> > + return 0;
> > +}
> > +
> > +static struct v4l2_mbus_framefmt *
> > +addi9036_get_pad_format(struct addi9036 *addi9036,
> > + struct v4l2_subdev_pad_config *cfg,
> > + unsigned int pad,
> > + enum v4l2_subdev_format_whence which)
> > +{
> > + switch (which) {
> > + case V4L2_SUBDEV_FORMAT_TRY:
> > + return v4l2_subdev_get_try_format(&addi9036->sd, cfg, pad);
> > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > + return &addi9036->fmt;
> > + default:
> > + return NULL;
> > + }
> > +}
> > +
> > +static int addi9036_get_format(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_format *format)
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > + struct v4l2_mbus_framefmt *pad_format;
> > +
> > + pad_format = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > + format->which);
> > +
> > + if (!pad_format)
> > + return -EINVAL;
> > +
> > + format->format = *pad_format;
> > +
> > + return 0;
> > +}
> > +
> > +static struct v4l2_rect *
> > +addi9036_get_pad_crop(struct addi9036 *addi9036,
> > + struct v4l2_subdev_pad_config *cfg,
> > + unsigned int pad, enum v4l2_subdev_format_whence which)
> > +{
> > + switch (which) {
> > + case V4L2_SUBDEV_FORMAT_TRY:
> > + return v4l2_subdev_get_try_crop(&addi9036->sd, cfg, pad);
> > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > + return &addi9036->crop;
> > + default:
> > + return NULL;
> > + }
> > +}
> > +
> > +static const struct addi9036_mode_info *
> > +addi9036_find_nearest_mode(unsigned int width, unsigned int height)
> > +{
> > + unsigned int i;
> > +
> > + for (i = ARRAY_SIZE(addi9036_mode_info_data) - 1; i > 0; i--) {
> > + if (addi9036_mode_info_data[i].width <= width &&
> > + addi9036_mode_info_data[i].height <= height)
> > + break;
> > + }
> > +
> > + return &addi9036_mode_info_data[i];
> > +}
> Could v4l2_find_nearest_size() cover this?
>
> > +
> > +static int addi9036_set_format(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_format *format)
> open parenthesis alignment
>
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > + struct v4l2_mbus_framefmt *framefmt;
> > + struct v4l2_rect *crop;
> > + const struct addi9036_mode_info *new_mode;
> > + int ret;
> > +
> > + dev_dbg(addi9036->dev, "set_fmt: %x %dx%d\n",
> > + format->format.code, format->format.width,
> > + format->format.height);
> > +
> > + crop = addi9036_get_pad_crop(addi9036, cfg, format->pad,
> > + format->which);
> > +
> > + if (!crop)
> > + return -EINVAL;
> > +
> > + new_mode = addi9036_find_nearest_mode(format->format.width,
> > + format->format.height);
> > + crop->width = new_mode->width;
> > + crop->height = new_mode->height;
> > +
> > + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > + ret = v4l2_ctrl_s_ctrl_int64(addi9036->pixel_rate,
> > + new_mode->pixel_rate);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = v4l2_ctrl_s_ctrl(addi9036->link_freq,
> > + new_mode->link_freq_idx);
> > + if (ret < 0)
> > + return ret;
> > +
> > + addi9036->current_mode = new_mode;
> > + }
> > +
> > + framefmt = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > + format->which);
> > +
> > + if (!framefmt)
> > + return -EINVAL;
> > +
> > + framefmt->width = crop->width;
> > + framefmt->height = crop->height;
> > + framefmt->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > + framefmt->field = V4L2_FIELD_NONE;
> > + framefmt->colorspace = V4L2_COLORSPACE_SRGB;
> > +
> > + format->format = *framefmt;
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_entity_init_cfg(struct v4l2_subdev *subdev,
> > + struct v4l2_subdev_pad_config *cfg)
> > +{
> > + struct v4l2_subdev_format fmt = { 0 };
> > +
> > + if (cfg)
> > + fmt.which = V4L2_SUBDEV_FORMAT_TRY;
> > + else
> > + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > +
> > + fmt.format.width = 640;
> > + fmt.format.height = 960;
>
> Get from mode by proper index?
> > +
> > + addi9036_set_format(subdev, cfg, &fmt);
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_get_selection(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_pad_config *cfg,
> > + struct v4l2_subdev_selection *sel)
> open parenthesis alignment
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > +
> > + if (sel->target != V4L2_SEL_TGT_CROP)
> > + return -EINVAL;
> > +
> > + sel->r = *addi9036_get_pad_crop(addi9036, cfg, sel->pad, sel->which);
> > +
> > + return 0;
> > +}
> > +
> > +static int addi9036_s_stream(struct v4l2_subdev *subdev, int enable)
> > +{
> > + struct addi9036 *addi9036 = to_addi9036(subdev);
> > +
> > + dev_dbg(addi9036->dev, "stream %d\n", enable);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct v4l2_subdev_core_ops addi9036_core_ops = {
> > + .s_power = addi9036_s_power,
> > +};
> > +
> > +static const struct v4l2_subdev_video_ops addi9036_video_ops = {
> > + .s_stream = addi9036_s_stream,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops addi9036_subdev_pad_ops = {
> > + .init_cfg = addi9036_entity_init_cfg,
> > + .enum_mbus_code = addi9036_enum_mbus_code,
> > + .enum_frame_size = addi9036_enum_frame_size,
> > + .get_fmt = addi9036_get_format,
> > + .set_fmt = addi9036_set_format,
> > + .get_selection = addi9036_get_selection,
> > +};
> > +
> > +static const struct v4l2_subdev_ops addi9036_subdev_ops = {
> > + .core = &addi9036_core_ops,
> > + .video = &addi9036_video_ops,
> > + .pad = &addi9036_subdev_pad_ops,
> > +};
> > +
> > +static int addi9036_probe(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev;
> > + struct fwnode_handle *endpoint;
> > + struct addi9036 *addi9036;
> > + int ret;
> > +
> > + dev_dbg(dev, "%s: i2c addr = 0x%x\n", __func__, client->addr);
> > +
> > + addi9036 = devm_kzalloc(dev, sizeof(struct addi9036), GFP_KERNEL);
> > + if (!addi9036)
> > + return -ENOMEM;
> > +
> > + addi9036->dev = dev;
> > +
> > + addi9036->regmap = devm_regmap_init_i2c(client,
> > + &addi9036_i2c_regmap_config);
> > + if (IS_ERR(addi9036->regmap)) {
> > + dev_err(dev, "Error initializing i2c regmap\n");
> > + return PTR_ERR(addi9036->regmap);
> > + }
> > +
> > + mutex_init(&addi9036->power_lock);
> > +
> > + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev),
> NULL);
> > + if (!endpoint) {
> > + dev_err(dev, "endpoint node not found\n");
> > + return -EINVAL;
> > + }
> > +
> > + ret = v4l2_fwnode_endpoint_parse(endpoint, &addi9036->ep);
> > + if (ret < 0) {
> > + dev_err(dev, "parsing endpoint node failed\n");
> > + return ret;
> > + }
> > + fwnode_handle_put(endpoint);
> > +
> > + if (addi9036->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
> > + dev_err(dev, "invalid bus type, must be MIPI CSI2\n");
> > + return -EINVAL;
> > + }
> > +
> > + addi9036->rst_gpio = gpiod_get_optional(dev, "reset",
> GPIOD_OUT_HIGH);
> > + if (IS_ERR(addi9036->rst_gpio)) {
> > + dev_info(dev, "Unable to get \"reset\" gpio\n");
> > + return PTR_ERR(addi9036->rst_gpio);
> > + }
> > +
> > + v4l2_ctrl_handler_init(&addi9036->ctrls, 4);
> > +
> > + addi9036->pixel_rate = v4l2_ctrl_new_std(&addi9036->ctrls,
> > + &addi9036_ctrl_ops,
> > + V4L2_CID_PIXEL_RATE,
> > + 1, INT_MAX, 1, 1);
> open parenthesis alignment
>
> > + addi9036->link_freq = v4l2_ctrl_new_int_menu(&addi9036->ctrls,
> > + &addi9036_ctrl_ops,
> > + V4L2_CID_LINK_FREQ,
> > + ARRAY_SIZE(
> > + link_freq_tbl) - 1,
> > + 0, link_freq_tbl);
> > + if (addi9036->link_freq)
> > + addi9036->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > + addi9036->set_chip_config = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > + &addi9036_ctrl_chip_config,
> > + NULL);
> ditto, alignment.
> > +
> > + addi9036->reg_read = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > + &addi9036_ctrl_reg_read,
> > + NULL);
> These two ctrls are used for debug?
>
> > +
> > + addi9036->sd.ctrl_handler = &addi9036->ctrls;
> > +
> > + if (addi9036->ctrls.error) {
> > + dev_err(dev, "%s: control initialization error %d\n",
> > + __func__, addi9036->ctrls.error);
> > + ret = addi9036->ctrls.error;
> > + goto free_ctrl;
> > + }
> > +
> > + v4l2_i2c_subdev_init(&addi9036->sd, client, &addi9036_subdev_ops);
> > + addi9036->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > + addi9036->pad.flags = MEDIA_PAD_FL_SOURCE;
> > + addi9036->sd.dev = &client->dev;
> > + addi9036->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > +
> > + ret = media_entity_pads_init(&addi9036->sd.entity, 1, &addi9036->pad);
> > +
> > + if (ret < 0) {
> > + dev_err(dev, "could not register media entity\n");
> > + goto free_ctrl;
> > + }
> > +
> > + ret = addi9036_s_power(&addi9036->sd, true);
> > + if (ret < 0) {
> > + dev_err(dev, "could not power up addi9036\n");
> > + goto free_entity;
> > + }
> > +
> > + dev_info(dev, "addi9036 detected at address 0x%02x\n", client->addr);
> dev_dbg(...), and why here?
> > +
> > + ret = addi9036_s_power(&addi9036->sd, false);
> > + if (ret < 0) {
> > + dev_err(dev, "could not power down addi9036\n");
> > + goto free_entity;
> > + }
> Any chance to use pm runtime?
>
> > +
> > + addi9036_entity_init_cfg(&addi9036->sd, NULL);
> > +
> > + ret = v4l2_async_register_subdev(&addi9036->sd);
> > + if (ret < 0)
> > + dev_err(dev, "could not register v4l2 device\n");
> > +
> > + return 0;
> > +
> > +free_entity:
> > + media_entity_cleanup(&addi9036->sd.entity);
> > +free_ctrl:
> > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > + mutex_destroy(&addi9036->power_lock);
> > +
> > + return ret;
> > +}
> > +
> > +static int addi9036_remove(struct i2c_client *client)
> > +{
> > + struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > + struct addi9036 *addi9036 = to_addi9036(sd);
> > +
> > + v4l2_async_unregister_subdev(&addi9036->sd);
> > + media_entity_cleanup(&addi9036->sd.entity);
> > + if (addi9036->rst_gpio)
> > + gpiod_put(addi9036->rst_gpio);
> > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > + mutex_destroy(&addi9036->power_lock);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct i2c_device_id addi9036_id[] = {
> > + { "addi9036", 0 },
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(i2c, addi9036_id);
> > +
> > +static const struct of_device_id addi9036_of_match[] = {
> > + { .compatible = "adi,addi9036" },
> > + { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, addi9036_of_match);
> > +
> > +static struct i2c_driver addi9036_i2c_driver = {
> > + .driver = {
> > + .of_match_table = addi9036_of_match,
> > + .name = "addi9036",
> > + },
> > + .probe_new = addi9036_probe,
> > + .remove = addi9036_remove,
> > + .id_table = addi9036_id,
> > +};
> > +
> > +module_i2c_driver(addi9036_i2c_driver);
> > +
> > +MODULE_DESCRIPTION("Analog Devices ADDI9036 Camera Driver");
> > +MODULE_AUTHOR("Bogdan Togorean");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-
> controls.h
> > index 62271418c1be..f88b56479bc1 100644
> > --- a/include/uapi/linux/v4l2-controls.h
> > +++ b/include/uapi/linux/v4l2-controls.h
> > @@ -198,6 +198,12 @@ enum v4l2_colorfx {
> > */
> > #define V4L2_CID_USER_ATMEL_ISC_BASE (V4L2_CID_USER_BASE +
> 0x10c0)
> >
> > +/*
> > + * The base for the addi9036 driver controls.
> > + * We reserve 16 controls for this driver.
> > + */
> > +#define V4L2_CID_USER_ADDI9036_BASE (V4L2_CID_USER_BASE +
> 0x10e0)
> > +
> > /* MPEG-class control IDs */
> > /* The MPEG controls are applicable to all codec controls
> > * and the 'MPEG' part of the define is historical */
> >
>
> --
> Best regards,
> Bingbu Cao

Best regards,
Bogdan


Attachments:
winmail.dat (23.51 kB)

2020-09-17 12:19:53

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

Hi Bogdan,

On Thu, Sep 17, 2020 at 07:06:10AM +0000, Togorean, Bogdan wrote:
> Hi Laurent and Sakari,
> Thank you for your review,
> > Hi Bogdan,
> >
> > On Fri, Sep 11, 2020 at 01:32:39PM +0000, Togorean, Bogdan wrote:
> > > > On Thu, Sep 10, 2020 at 07:24:06PM +0300, Bogdan Togorean wrote:
> > > > > The ADDI9036 is a complete, 45 MHz, front-end solution for charge
> > > > > coupled device (CCD) time of flight (TOF) imaging applications.
> > > > >
> > > > > It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.
> > > > >
> > > > > The programming of calibration and firmware is performed by userspace
> > > > > SDK through CID_USER class IOCTLs.
> > > >
> > > > I'm afraid that's not acceptable. The firmware should be loaded using
> > > > the Linux kernel firmware API. As for calibration, is it static data ?
> > > > How is it generated ?
> > >
> > > Calibration data is static data, generated at production and stored on and EEPROM
> > > memory on camera board. The camera board consists mainly of ADDI9036 processor
> > > + CCD image sensor + EEPROM.
> > >
> > > The firmware is a set of (address, data) tuples that are written via I2C.
> >
> > Is it a firmware in the sense of software running on the ADDI9036, or is
> > it a device configuration to be loaded in registers ?
>
> Actually what we call "firmware" is a combination of both. The binary blob that gets written
> contains ToF processor instructions that end in a RAM memory internal to ADDI9036 and
> also register configurations.

This makes it effectively impossible to use the device unless you have a
closed-source userspace application to configure it, isn't it ?

Firmware that runs on a device can be shipped as a binary blob, but
configuration of the device must not require closed-source software. To
be upstreamed, a driver needs to be usable either on its own (with its
userspace APIs document and usable by all applications), or in
combination with an open-source userspace stack.

> I am working on a V2 that uses firmware API and NVMEM subsystem for EEPROM stored data.
>
> > > We want this driver to be a generic one and support all implementations
> > > of ADDI9036 ToF processor. Now the problem is that some implementations
> > > store firmware + calibration on external EEPROM and some store just calibration
> > > data on EEPROM and firmware is loaded from a file stored in SDK. With the help
> > > of the 2 custom controls we move the camera model detection, firmware
> > > and calibration fetching from driver to user space SDK.
> > >
> > > Also an important point is that the ToF processor has 3 operating modes
> > > (near, medium and far). At each mode change a new set of firmware +
> > > calibration data is loaded into ADDI9036 chip. If we move the firmware loading
> > > in driver we would still need a custom enumeration control to select the working mode.
> > >
> > > > > Signed-off-by: Bogdan Togorean <[email protected]>
> > > > > ---
> > > > > MAINTAINERS | 10 +
> > > > > drivers/media/i2c/Kconfig | 14 +
> > > > > drivers/media/i2c/Makefile | 1 +
> > > > > drivers/media/i2c/addi9036.c | 673 +++++++++++++++++++++++++++++
> > > > > include/uapi/linux/v4l2-controls.h | 6 +
> > > > > 5 files changed, 704 insertions(+)
> > > > > create mode 100644 drivers/media/i2c/addi9036.c
> > > > >
> > > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > > index b5cfab015bd6..fb03ad9ca920 100644
> > > > > --- a/MAINTAINERS
> > > > > +++ b/MAINTAINERS
> > > > > @@ -477,6 +477,16 @@ W: http://wiki.analog.com/AD7879
> > > > > W: http://ez.analog.com/community/linux-device-drivers
> > > > > F: drivers/input/touchscreen/ad7879.c
> > > > >
> > > > > +ADDI9036 TOF FRONTEND DRIVER
> > > > > +M: Bogdan Togorean <[email protected]>
> > > > > +L: [email protected]
> > > > > +S: Supported
> > > > > +W: https://www.analog.com/en/products/addi9036.html
> > > > > +W: http://ez.analog.com/community/linux-device-drivers
> > > > > +T: git git://linuxtv.org/media_tree.git
> > > > > +F: Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
> > > > > +F: drivers/media/i2c/addi9036.c
> > > > > +
> > > > > ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR)
> > > > > M: Jiri Kosina <[email protected]>
> > > > > S: Maintained
> > > > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > > > > index c7ba76fee599..087dd307505d 100644
> > > > > --- a/drivers/media/i2c/Kconfig
> > > > > +++ b/drivers/media/i2c/Kconfig
> > > > > @@ -725,6 +725,20 @@ config VIDEO_APTINA_PLL
> > > > > config VIDEO_SMIAPP_PLL
> > > > > tristate
> > > > >
> > > > > +config VIDEO_ADDI9036
> > > > > + tristate "Analog Devices ADDI9036 ToF front-end support"
> > > > > + depends on I2C && VIDEO_V4L2
> > > > > + select MEDIA_CONTROLLER
> > > > > + select VIDEO_V4L2_SUBDEV_API
> > > > > + select V4L2_FWNODE
> > > > > + select REGMAP_I2C
> > > > > + help
> > > > > + This is a Video4Linux2 driver for Analog Devices ADDI9036
> > > > > + Time of Flight front-end.
> > > > > +
> > > > > + To compile this driver as a module, choose M here: the
> > > > > + module will be called addi9036.
> > > > > +
> > > > > config VIDEO_HI556
> > > > > tristate "Hynix Hi-556 sensor support"
> > > > > depends on I2C && VIDEO_V4L2
> > > > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > > > > index f0a77473979d..631a7c7612ca 100644
> > > > > --- a/drivers/media/i2c/Makefile
> > > > > +++ b/drivers/media/i2c/Makefile
> > > > > @@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
> > > > > obj-$(CONFIG_VIDEO_DW9714) += dw9714.o
> > > > > obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
> > > > > obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
> > > > > +obj-$(CONFIG_VIDEO_ADDI9036) += addi9036.o
> > > > > obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
> > > > > obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o
> > > > > obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
> > > > > diff --git a/drivers/media/i2c/addi9036.c b/drivers/media/i2c/addi9036.c
> > > > > new file mode 100644
> > > > > index 000000000000..76c3444058dc
> > > > > --- /dev/null
> > > > > +++ b/drivers/media/i2c/addi9036.c
> > > > > @@ -0,0 +1,673 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0
> > > > > +/*
> > > > > + * Driver for the Analog Devices ADDI9036 ToF front-end.
> > > > > + *
> > > > > + * Copyright (C) 2019-2020 Analog Devices, All Rights Reserved.
> > > > > + *
> > > > > + */
> > > > > +
> > > > > +#include <linux/device.h>
> > > > > +#include <linux/gpio/consumer.h>
> > > > > +#include <linux/i2c.h>
> > > > > +#include <linux/init.h>
> > > > > +#include <linux/module.h>
> > > > > +#include <linux/mutex.h>
> > > > > +#include <linux/of.h>
> > > > > +#include <linux/of_graph.h>
> > > > > +#include <linux/regmap.h>
> > > > > +#include <linux/slab.h>
> > > > > +#include <linux/types.h>
> > > > > +#include <linux/version.h>
> > > > > +#include <media/v4l2-ctrls.h>
> > > > > +#include <media/v4l2-fwnode.h>
> > > > > +#include <media/v4l2-subdev.h>
> > > > > +
> > > > > +struct addi9036_mode_info {
> > > > > + u32 width;
> > > > > + u32 height;
> > > > > + u32 pixel_rate;
> > > > > + u32 link_freq_idx;
> > > > > +};
> > > > > +
> > > > > +struct addi9036 {
> > > > > + struct regmap *regmap;
> > > > > + struct device *dev;
> > > > > + struct v4l2_subdev sd;
> > > > > + struct media_pad pad;
> > > > > + struct v4l2_fwnode_endpoint ep;
> > > > > + struct v4l2_mbus_framefmt fmt;
> > > > > + struct v4l2_rect crop;
> > > > > +
> > > > > + const struct addi9036_mode_info *current_mode;
> > > > > +
> > > > > + struct v4l2_ctrl_handler ctrls;
> > > > > + struct v4l2_ctrl *pixel_rate;
> > > > > + struct v4l2_ctrl *link_freq;
> > > > > +
> > > > > + /* lock to protect power state */
> > > > > + struct mutex power_lock;
> > > > > + int power_count;
> > > > > +
> > > > > + struct gpio_desc *rst_gpio;
> > > > > +
> > > > > + /* controls */
> > > > > + struct v4l2_ctrl *set_chip_config;
> > > > > + struct v4l2_ctrl *reg_read;
> > > > > +};
> > > > > +
> > > > > +static inline struct addi9036 *to_addi9036(struct v4l2_subdev *sd)
> > > > > +{
> > > > > + return container_of(sd, struct addi9036, sd);
> > > > > +}
> > > > > +
> > > > > +#define V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY (V4L2_CID_USER_ADDI9036_BASE + 0)
> > > > > +#define V4L2_CID_AD_DEV_REG_READ_QUERY (V4L2_CID_USER_ADDI9036_BASE + 1)
> > > > > +
> > > > > +static const struct reg_sequence addi9036_powerup_setting[] = {
> > > > > + { 0xc4c0, 0x001c },
> > > > > + { 0xc4c3, 0x001c },
> > > > > + { 0xc4d7, 0x0000 },
> > > > > + { 0xc4d5, 0x0002 },
> > > > > + { 0xc4da, 0x0001 },
> > > > > + { 0xc4f0, 0x0000 },
> > > > > + { 0xc427, 0x0003 },
> > > > > + { 0xc427, 0x0001 },
> > > > > + { 0xc427, 0x0000 },
> > > > > + { 0xc426, 0x0030 },
> > > > > + { 0xc426, 0x0010 },
> > > > > + { 0xc426, 0x0000 },
> > > > > + { 0xc423, 0x0080 },
> > > > > + { 0xc431, 0x0080 },
> > > > > + { 0x4001, 0x0007 },
> > > > > + { 0x7c22, 0x0004 }
> > > > > +};
> > > > > +
> > > > > +static const struct reg_sequence addi9036_powerdown_setting[] = {
> > > > > + { 0xc022, 0x0001 },
> > > > > + { 0x4001, 0x0006 },
> > > > > + { 0x7c22, 0x0004 },
> > > > > + { 0xc431, 0x0082 },
> > > > > + { 0xc423, 0x0000 },
> > > > > + { 0xc426, 0x0020 },
> > > > > + { 0xc427, 0x0002 },
> > > > > + { 0xc4c0, 0x003c },
> > > > > + { 0xc4c3, 0x003c },
> > > > > + { 0xc4d5, 0x0003 },
> > > > > + { 0xc4da, 0x0000 },
> > > > > + { 0xc4d7, 0x0001 },
> > > > > + { 0xc4f0, 0x0001 }
> > > > > +};
> > > > > +
> > > > > +static const s64 link_freq_tbl[] = {
> > > > > + 110529000,
> > > > > + 221184000,
> > > > > +};
> > > > > +
> > > > > +/* Elements of the structure must be ordered ascending by width & height */
> > > > > +static const struct addi9036_mode_info addi9036_mode_info_data[] = {
> > > > > + {
> > > > > + .width = 640,
> > > > > + .height = 480,
> > > > > + .pixel_rate = 36864000,
> > > > > + .link_freq_idx = 0 /* an index in link_freq_tbl[] */
> > > > > + },
> > > > > + {
> > > > > + .width = 640,
> > > > > + .height = 960,
> > > > > + .pixel_rate = 73728000,
> > > > > + .link_freq_idx = 1 /* an index in link_freq_tbl[] */
> > > > > + },
> > > > > +};
> > > > > +
> > > > > +static bool addi9306_readable_register(struct device *dev, unsigned int reg)
> > > > > +{
> > > > > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > > > > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > > > > + ((reg >= 0xC000) && (reg <= 0xC200)) ||
> > > > > + ((reg >= 0xC300) && (reg <= 0xC6BF)))
> > > > > + return true;
> > > > > + else
> > > > > + return false;
> > > > > +}
> > > > > +
> > > > > +static bool addi9306_writeable_register(struct device *dev, unsigned int reg)
> > > > > +{
> > > > > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > > > > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > > > > + ((reg >= 0xC000) && (reg <= 0xC113)) ||
> > > > > + ((reg >= 0xC300) && (reg <= 0xC7FF)))
> > > > > + return true;
> > > > > + else
> > > > > + return false;
> > > > > +}
> > > > > +
> > > > > +static const struct regmap_config addi9036_i2c_regmap_config = {
> > > > > + .reg_bits = 16,
> > > > > + .val_bits = 16,
> > > > > +
> > > > > + .max_register = 0xC7FF,
> > > > > + .writeable_reg = addi9306_writeable_register,
> > > > > + .readable_reg = addi9306_readable_register,
> > > > > + .cache_type = REGCACHE_NONE,
> > > > > +};
> > > > > +
> > > > > +static int addi9036_set_power_on(struct addi9036 *addi9036)
> > > > > +{
> > > > > + int ret;
> > > > > +
> > > > > + if (addi9036->rst_gpio)
> > > > > + gpiod_set_value_cansleep(addi9036->rst_gpio, 0);
> > > > > +
> > > > > + ret = regmap_register_patch(addi9036->regmap, addi9036_powerup_setting,
> > > > > + ARRAY_SIZE(addi9036_powerup_setting));
> > > > > + if (ret)
> > > > > + dev_err(addi9036->dev, "Could not set power up registers\n");
> > > > > +
> > > > > + return ret;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_set_power_off(struct addi9036 *addi9036)
> > > > > +{
> > > > > + int ret;
> > > > > +
> > > > > + ret = regmap_register_patch(addi9036->regmap,
> > > > > + addi9036_powerdown_setting,
> > > > > + ARRAY_SIZE(addi9036_powerdown_setting));
> > > > > + if (ret)
> > > > > + dev_err(addi9036->dev, "could not set power down registers\n");
> > > > > +
> > > > > + if (addi9036->rst_gpio)
> > > > > + gpiod_set_value_cansleep(addi9036->rst_gpio, 1);
> > > > > +
> > > > > + return ret;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_s_power(struct v4l2_subdev *sd, int on)
> > > > > +{
> > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > + int ret = 0;
> > > > > +
> > > > > + mutex_lock(&addi9036->power_lock);
> > > > > +
> > > > > + dev_dbg(addi9036->dev, "s_power: %d\n", on);
> > > > > +
> > > > > + /* If the power count is modified from 0 to != 0 or from != 0 to 0,
> > > > > + * update the power state.
> > > > > + */
> > > > > + if (addi9036->power_count == !on) {
> > > > > + if (on)
> > > > > + ret = addi9036_set_power_on(addi9036);
> > > > > + else
> > > > > + ret = addi9036_set_power_off(addi9036);
> > > > > + }
> > > > > +
> > > > > + if (!ret) {
> > > > > + /* Update the power count. */
> > > > > + addi9036->power_count += on ? 1 : -1;
> > > > > + WARN(addi9036->power_count < 0, "Unbalanced power count\n");
> > > > > + WARN(addi9036->power_count > 1, "Duplicated s_power call\n");
> > > > > + }
> > > > > +
> > > > > + mutex_unlock(&addi9036->power_lock);
> > > > > +
> > > > > + return ret;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_set_chip_config(struct addi9036 *addi9036,
> > > > > + struct v4l2_ctrl *ctrl)
> > > > > +{
> > > > > + int index;
> > > > > + unsigned short *reg, *val;
> > > > > +
> > > > > + reg = (unsigned short *)(ctrl->p_new.p_u16);
> > > > > + val = (unsigned short *)(ctrl->p_new.p_u16 + 1);
> > > > > +
> > > > > + for (index = 0; index < ctrl->elems; index += 2) {
> > > > > + regmap_write(addi9036->regmap, *reg, *val);
> > > > > + reg += 2;
> > > > > + val += 2;
> > > > > + }
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_s_ctrl(struct v4l2_ctrl *ctrl)
> > > > > +{
> > > > > + struct addi9036 *addi9036 = container_of(ctrl->handler,
> > > > > + struct addi9036, ctrls);
> > > > > + int ret = 0;
> > > > > + unsigned int val;
> > > > > +
> > > > > + switch (ctrl->id) {
> > > > > + case V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY:
> > > > > + ret = addi9036_set_chip_config(addi9036, ctrl);
> > > > > + break;
> > > > > + case V4L2_CID_AD_DEV_REG_READ_QUERY:
> > > > > + ret = regmap_read(addi9036->regmap, *(u16 *)(ctrl-
> > > > >p_new.p_u16),
> > > > > + &val);
> > > > > + if (ret)
> > > > > + dev_warn(addi9036->dev,
> > > > > + "could not read from register\n");
> > > > > + else
> > > > > + *(u16 *)(ctrl->p_new.p_u16) = val;
> > > > > + break;
> > > > > + case V4L2_CID_PIXEL_RATE:
> > > > > + case V4L2_CID_LINK_FREQ:
> > > > > + break;
> > > > > + default:
> > > > > + dev_err(addi9036->dev, "%s > Unhandled: %x param=%x\n",
> > > > > + __func__, ctrl->id, ctrl->val);
> > > > > + ret = -EINVAL;
> > > > > + break;
> > > > > + }
> > > > > +
> > > > > + return ret;
> > > > > +}
> > > > > +
> > > > > +static const struct v4l2_ctrl_ops addi9036_ctrl_ops = {
> > > > > + .s_ctrl = addi9036_s_ctrl,
> > > > > +};
> > > > > +
> > > > > +static const struct v4l2_ctrl_config addi9036_ctrl_chip_config = {
> > > > > + .ops = &addi9036_ctrl_ops,
> > > > > + .id = V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY,
> > > > > + .name = "chip_config",
> > > > > + .type = V4L2_CTRL_TYPE_U16,
> > > > > + .def = 0xFF,
> > > > > + .min = 0x00,
> > > > > + .max = 0xFFFF,
> > > > > + .step = 1,
> > > > > + .dims = { 2048 },
> > > > > + .elem_size = 2
> > > > > +};
> > > > > +
> > > > > +static const struct v4l2_ctrl_config addi9036_ctrl_reg_read = {
> > > > > + .ops = &addi9036_ctrl_ops,
> > > > > + .id = V4L2_CID_AD_DEV_REG_READ_QUERY,
> > > > > + .name = "reg_read",
> > > > > + .type = V4L2_CTRL_TYPE_U16,
> > > > > + .def = 0,
> > > > > + .min = 0x00,
> > > > > + .max = 0xFFFF,
> > > > > + .step = 1
> > > > > +};
> > > > > +
> > > > > +static int addi9036_enum_mbus_code(struct v4l2_subdev *sd,
> > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > + struct v4l2_subdev_mbus_code_enum *code)
> > > > > +{
> > > > > + if (code->index > 0)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + code->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_enum_frame_size(struct v4l2_subdev *subdev,
> > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > + struct v4l2_subdev_frame_size_enum *fse)
> > > > > +{
> > > > > + if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + if (fse->index >= ARRAY_SIZE(addi9036_mode_info_data))
> > > > > + return -EINVAL;
> > > > > +
> > > > > + fse->min_width = addi9036_mode_info_data[fse->index].width;
> > > > > + fse->max_width = addi9036_mode_info_data[fse->index].width;
> > > > > + fse->min_height = addi9036_mode_info_data[fse->index].height;
> > > > > + fse->max_height = addi9036_mode_info_data[fse->index].height;
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static struct v4l2_mbus_framefmt *
> > > > > +addi9036_get_pad_format(struct addi9036 *addi9036,
> > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > + unsigned int pad,
> > > > > + enum v4l2_subdev_format_whence which)
> > > > > +{
> > > > > + switch (which) {
> > > > > + case V4L2_SUBDEV_FORMAT_TRY:
> > > > > + return v4l2_subdev_get_try_format(&addi9036->sd, cfg, pad);
> > > > > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > > > > + return &addi9036->fmt;
> > > > > + default:
> > > > > + return NULL;
> > > > > + }
> > > > > +}
> > > > > +
> > > > > +static int addi9036_get_format(struct v4l2_subdev *sd,
> > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > + struct v4l2_subdev_format *format)
> > > > > +{
> > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > + struct v4l2_mbus_framefmt *pad_format;
> > > > > +
> > > > > + pad_format = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > > > > + format->which);
> > > > > +
> > > > > + if (!pad_format)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + format->format = *pad_format;
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static struct v4l2_rect *
> > > > > +addi9036_get_pad_crop(struct addi9036 *addi9036,
> > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > + unsigned int pad, enum v4l2_subdev_format_whence which)
> > > > > +{
> > > > > + switch (which) {
> > > > > + case V4L2_SUBDEV_FORMAT_TRY:
> > > > > + return v4l2_subdev_get_try_crop(&addi9036->sd, cfg, pad);
> > > > > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > > > > + return &addi9036->crop;
> > > > > + default:
> > > > > + return NULL;
> > > > > + }
> > > > > +}
> > > > > +
> > > > > +static const struct addi9036_mode_info *
> > > > > +addi9036_find_nearest_mode(unsigned int width, unsigned int height)
> > > > > +{
> > > > > + unsigned int i;
> > > > > +
> > > > > + for (i = ARRAY_SIZE(addi9036_mode_info_data) - 1; i > 0; i--) {
> > > > > + if (addi9036_mode_info_data[i].width <= width &&
> > > > > + addi9036_mode_info_data[i].height <= height)
> > > > > + break;
> > > > > + }
> > > > > +
> > > > > + return &addi9036_mode_info_data[i];
> > > > > +}
> > > > > +
> > > > > +static int addi9036_set_format(struct v4l2_subdev *sd,
> > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > + struct v4l2_subdev_format *format)
> > > > > +{
> > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > + struct v4l2_mbus_framefmt *framefmt;
> > > > > + struct v4l2_rect *crop;
> > > > > + const struct addi9036_mode_info *new_mode;
> > > > > + int ret;
> > > > > +
> > > > > + dev_dbg(addi9036->dev, "set_fmt: %x %dx%d\n",
> > > > > + format->format.code, format->format.width,
> > > > > + format->format.height);
> > > > > +
> > > > > + crop = addi9036_get_pad_crop(addi9036, cfg, format->pad,
> > > > > + format->which);
> > > > > +
> > > > > + if (!crop)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + new_mode = addi9036_find_nearest_mode(format->format.width,
> > > > > + format->format.height);
> > > > > + crop->width = new_mode->width;
> > > > > + crop->height = new_mode->height;
> > > > > +
> > > > > + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > > > > + ret = v4l2_ctrl_s_ctrl_int64(addi9036->pixel_rate,
> > > > > + new_mode->pixel_rate);
> > > > > + if (ret < 0)
> > > > > + return ret;
> > > > > +
> > > > > + ret = v4l2_ctrl_s_ctrl(addi9036->link_freq,
> > > > > + new_mode->link_freq_idx);
> > > > > + if (ret < 0)
> > > > > + return ret;
> > > > > +
> > > > > + addi9036->current_mode = new_mode;
> > > > > + }
> > > > > +
> > > > > + framefmt = addi9036_get_pad_format(addi9036, cfg, format->pad,
> > > > > + format->which);
> > > > > +
> > > > > + if (!framefmt)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + framefmt->width = crop->width;
> > > > > + framefmt->height = crop->height;
> > > > > + framefmt->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > > > > + framefmt->field = V4L2_FIELD_NONE;
> > > > > + framefmt->colorspace = V4L2_COLORSPACE_SRGB;
> > > > > +
> > > > > + format->format = *framefmt;
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_entity_init_cfg(struct v4l2_subdev *subdev,
> > > > > + struct v4l2_subdev_pad_config *cfg)
> > > > > +{
> > > > > + struct v4l2_subdev_format fmt = { 0 };
> > > > > +
> > > > > + if (cfg)
> > > > > + fmt.which = V4L2_SUBDEV_FORMAT_TRY;
> > > > > + else
> > > > > + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > > > > +
> > > > > + fmt.format.width = 640;
> > > > > + fmt.format.height = 960;
> > > > > +
> > > > > + addi9036_set_format(subdev, cfg, &fmt);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_get_selection(struct v4l2_subdev *sd,
> > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > + struct v4l2_subdev_selection *sel)
> > > > > +{
> > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > +
> > > > > + if (sel->target != V4L2_SEL_TGT_CROP)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + sel->r = *addi9036_get_pad_crop(addi9036, cfg, sel->pad, sel->which);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_s_stream(struct v4l2_subdev *subdev, int enable)
> > > > > +{
> > > > > + struct addi9036 *addi9036 = to_addi9036(subdev);
> > > > > +
> > > > > + dev_dbg(addi9036->dev, "stream %d\n", enable);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static const struct v4l2_subdev_core_ops addi9036_core_ops = {
> > > > > + .s_power = addi9036_s_power,
> > > > > +};
> > > > > +
> > > > > +static const struct v4l2_subdev_video_ops addi9036_video_ops = {
> > > > > + .s_stream = addi9036_s_stream,
> > > > > +};
> > > > > +
> > > > > +static const struct v4l2_subdev_pad_ops addi9036_subdev_pad_ops = {
> > > > > + .init_cfg = addi9036_entity_init_cfg,
> > > > > + .enum_mbus_code = addi9036_enum_mbus_code,
> > > > > + .enum_frame_size = addi9036_enum_frame_size,
> > > > > + .get_fmt = addi9036_get_format,
> > > > > + .set_fmt = addi9036_set_format,
> > > > > + .get_selection = addi9036_get_selection,
> > > > > +};
> > > > > +
> > > > > +static const struct v4l2_subdev_ops addi9036_subdev_ops = {
> > > > > + .core = &addi9036_core_ops,
> > > > > + .video = &addi9036_video_ops,
> > > > > + .pad = &addi9036_subdev_pad_ops,
> > > > > +};
> > > > > +
> > > > > +static int addi9036_probe(struct i2c_client *client)
> > > > > +{
> > > > > + struct device *dev = &client->dev;
> > > > > + struct fwnode_handle *endpoint;
> > > > > + struct addi9036 *addi9036;
> > > > > + int ret;
> > > > > +
> > > > > + dev_dbg(dev, "%s: i2c addr = 0x%x\n", __func__, client->addr);
> > > > > +
> > > > > + addi9036 = devm_kzalloc(dev, sizeof(struct addi9036), GFP_KERNEL);
> > > > > + if (!addi9036)
> > > > > + return -ENOMEM;
> > > > > +
> > > > > + addi9036->dev = dev;
> > > > > +
> > > > > + addi9036->regmap = devm_regmap_init_i2c(client,
> > > > > + &addi9036_i2c_regmap_config);
> > > > > + if (IS_ERR(addi9036->regmap)) {
> > > > > + dev_err(dev, "Error initializing i2c regmap\n");
> > > > > + return PTR_ERR(addi9036->regmap);
> > > > > + }
> > > > > +
> > > > > + mutex_init(&addi9036->power_lock);
> > > > > +
> > > > > + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
> > > > > + if (!endpoint) {
> > > > > + dev_err(dev, "endpoint node not found\n");
> > > > > + return -EINVAL;
> > > > > + }
> > > > > +
> > > > > + ret = v4l2_fwnode_endpoint_parse(endpoint, &addi9036->ep);
> > > > > + if (ret < 0) {
> > > > > + dev_err(dev, "parsing endpoint node failed\n");
> > > > > + return ret;
> > > > > + }
> > > > > + fwnode_handle_put(endpoint);
> > > > > +
> > > > > + if (addi9036->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
> > > > > + dev_err(dev, "invalid bus type, must be MIPI CSI2\n");
> > > > > + return -EINVAL;
> > > > > + }
> > > > > +
> > > > > + addi9036->rst_gpio = gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> > > > > + if (IS_ERR(addi9036->rst_gpio)) {
> > > > > + dev_info(dev, "Unable to get \"reset\" gpio\n");
> > > > > + return PTR_ERR(addi9036->rst_gpio);
> > > > > + }
> > > > > +
> > > > > + v4l2_ctrl_handler_init(&addi9036->ctrls, 4);
> > > > > +
> > > > > + addi9036->pixel_rate = v4l2_ctrl_new_std(&addi9036->ctrls,
> > > > > + &addi9036_ctrl_ops,
> > > > > + V4L2_CID_PIXEL_RATE,
> > > > > + 1, INT_MAX, 1, 1);
> > > > > + addi9036->link_freq = v4l2_ctrl_new_int_menu(&addi9036->ctrls,
> > > > > + &addi9036_ctrl_ops,
> > > > > + V4L2_CID_LINK_FREQ,
> > > > > + ARRAY_SIZE(
> > > > > + link_freq_tbl) - 1,
> > > > > + 0, link_freq_tbl);
> > > > > + if (addi9036->link_freq)
> > > > > + addi9036->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > > > +
> > > > > + addi9036->set_chip_config = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > > > > + &addi9036_ctrl_chip_config,
> > > > > + NULL);
> > > > > +
> > > > > + addi9036->reg_read = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > > > > + &addi9036_ctrl_reg_read,
> > > > > + NULL);
> > > > > +
> > > > > + addi9036->sd.ctrl_handler = &addi9036->ctrls;
> > > > > +
> > > > > + if (addi9036->ctrls.error) {
> > > > > + dev_err(dev, "%s: control initialization error %d\n",
> > > > > + __func__, addi9036->ctrls.error);
> > > > > + ret = addi9036->ctrls.error;
> > > > > + goto free_ctrl;
> > > > > + }
> > > > > +
> > > > > + v4l2_i2c_subdev_init(&addi9036->sd, client, &addi9036_subdev_ops);
> > > > > + addi9036->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > > > + addi9036->pad.flags = MEDIA_PAD_FL_SOURCE;
> > > > > + addi9036->sd.dev = &client->dev;
> > > > > + addi9036->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > > > > +
> > > > > + ret = media_entity_pads_init(&addi9036->sd.entity, 1, &addi9036->pad);
> > > > > +
> > > > > + if (ret < 0) {
> > > > > + dev_err(dev, "could not register media entity\n");
> > > > > + goto free_ctrl;
> > > > > + }
> > > > > +
> > > > > + ret = addi9036_s_power(&addi9036->sd, true);
> > > > > + if (ret < 0) {
> > > > > + dev_err(dev, "could not power up addi9036\n");
> > > > > + goto free_entity;
> > > > > + }
> > > > > +
> > > > > + dev_info(dev, "addi9036 detected at address 0x%02x\n", client->addr);
> > > > > +
> > > > > + ret = addi9036_s_power(&addi9036->sd, false);
> > > > > + if (ret < 0) {
> > > > > + dev_err(dev, "could not power down addi9036\n");
> > > > > + goto free_entity;
> > > > > + }
> > > > > +
> > > > > + addi9036_entity_init_cfg(&addi9036->sd, NULL);
> > > > > +
> > > > > + ret = v4l2_async_register_subdev(&addi9036->sd);
> > > > > + if (ret < 0)
> > > > > + dev_err(dev, "could not register v4l2 device\n");
> > > > > +
> > > > > + return 0;
> > > > > +
> > > > > +free_entity:
> > > > > + media_entity_cleanup(&addi9036->sd.entity);
> > > > > +free_ctrl:
> > > > > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > > > > + mutex_destroy(&addi9036->power_lock);
> > > > > +
> > > > > + return ret;
> > > > > +}
> > > > > +
> > > > > +static int addi9036_remove(struct i2c_client *client)
> > > > > +{
> > > > > + struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > +
> > > > > + v4l2_async_unregister_subdev(&addi9036->sd);
> > > > > + media_entity_cleanup(&addi9036->sd.entity);
> > > > > + if (addi9036->rst_gpio)
> > > > > + gpiod_put(addi9036->rst_gpio);
> > > > > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > > > > + mutex_destroy(&addi9036->power_lock);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static const struct i2c_device_id addi9036_id[] = {
> > > > > + { "addi9036", 0 },
> > > > > + {}
> > > > > +};
> > > > > +MODULE_DEVICE_TABLE(i2c, addi9036_id);
> > > > > +
> > > > > +static const struct of_device_id addi9036_of_match[] = {
> > > > > + { .compatible = "adi,addi9036" },
> > > > > + { /* sentinel */ }
> > > > > +};
> > > > > +MODULE_DEVICE_TABLE(of, addi9036_of_match);
> > > > > +
> > > > > +static struct i2c_driver addi9036_i2c_driver = {
> > > > > + .driver = {
> > > > > + .of_match_table = addi9036_of_match,
> > > > > + .name = "addi9036",
> > > > > + },
> > > > > + .probe_new = addi9036_probe,
> > > > > + .remove = addi9036_remove,
> > > > > + .id_table = addi9036_id,
> > > > > +};
> > > > > +
> > > > > +module_i2c_driver(addi9036_i2c_driver);
> > > > > +
> > > > > +MODULE_DESCRIPTION("Analog Devices ADDI9036 Camera Driver");
> > > > > +MODULE_AUTHOR("Bogdan Togorean");
> > > > > +MODULE_LICENSE("GPL v2");
> > > > > diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2- controls.h
> > > > > index 62271418c1be..f88b56479bc1 100644
> > > > > --- a/include/uapi/linux/v4l2-controls.h
> > > > > +++ b/include/uapi/linux/v4l2-controls.h
> > > > > @@ -198,6 +198,12 @@ enum v4l2_colorfx {
> > > > > */
> > > > > #define V4L2_CID_USER_ATMEL_ISC_BASE (V4L2_CID_USER_BASE + 0x10c0)
> > > > >
> > > > > +/*
> > > > > + * The base for the addi9036 driver controls.
> > > > > + * We reserve 16 controls for this driver.
> > > > > + */
> > > > > +#define V4L2_CID_USER_ADDI9036_BASE (V4L2_CID_USER_BASE + 0x10e0)
> > > > > +
> > > > > /* MPEG-class control IDs */
> > > > > /* The MPEG controls are applicable to all codec controls
> > > > > * and the 'MPEG' part of the define is historical */

--
Regards,

Laurent Pinchart

2020-09-18 07:17:21

by Togorean, Bogdan

[permalink] [raw]
Subject: RE: [PATCH 1/2] media: i2c: Add driver for the Analog Devices ADDI9036 ToF front-end

Hi Laurent,
>
> Hi Bogdan,
>
> On Thu, Sep 17, 2020 at 07:06:10AM +0000, Togorean, Bogdan wrote:
> > Hi Laurent and Sakari,
> > Thank you for your review,
> > > Hi Bogdan,
> > >
> > > On Fri, Sep 11, 2020 at 01:32:39PM +0000, Togorean, Bogdan wrote:
> > > > > On Thu, Sep 10, 2020 at 07:24:06PM +0300, Bogdan Togorean wrote:
> > > > > > The ADDI9036 is a complete, 45 MHz, front-end solution for charge
> > > > > > coupled device (CCD) time of flight (TOF) imaging applications.
> > > > > >
> > > > > > It has 2-lane MIPI CSI-2 RAW12 data output and i2c control interface.
> > > > > >
> > > > > > The programming of calibration and firmware is performed by userspace
> > > > > > SDK through CID_USER class IOCTLs.
> > > > >
> > > > > I'm afraid that's not acceptable. The firmware should be loaded using
> > > > > the Linux kernel firmware API. As for calibration, is it static data ?
> > > > > How is it generated ?
> > > >
> > > > Calibration data is static data, generated at production and stored on and
> EEPROM
> > > > memory on camera board. The camera board consists mainly of ADDI9036
> processor
> > > > + CCD image sensor + EEPROM.
> > > >
> > > > The firmware is a set of (address, data) tuples that are written via I2C.
> > >
> > > Is it a firmware in the sense of software running on the ADDI9036, or is
> > > it a device configuration to be loaded in registers ?
> >
> > Actually what we call "firmware" is a combination of both. The binary blob that
> gets written
> > contains ToF processor instructions that end in a RAM memory internal to
> ADDI9036 and
> > also register configurations.
>
> This makes it effectively impossible to use the device unless you have a
> closed-source userspace application to configure it, isn't it ?
>
> Firmware that runs on a device can be shipped as a binary blob, but
> configuration of the device must not require closed-source software. To
> be upstreamed, a driver needs to be usable either on its own (with its
> userspace APIs document and usable by all applications), or in
> combination with an open-source userspace stack.
>
The open source userspace SDK provided by Analog Devices will still be required
because it computes corrections and apply them to every frame based on
calibration data read from EEPROM.

> > I am working on a V2 that uses firmware API and NVMEM subsystem for
> EEPROM stored data.
> >
> > > > We want this driver to be a generic one and support all implementations
> > > > of ADDI9036 ToF processor. Now the problem is that some implementations
> > > > store firmware + calibration on external EEPROM and some store just
> calibration
> > > > data on EEPROM and firmware is loaded from a file stored in SDK. With the
> help
> > > > of the 2 custom controls we move the camera model detection, firmware
> > > > and calibration fetching from driver to user space SDK.
> > > >
> > > > Also an important point is that the ToF processor has 3 operating modes
> > > > (near, medium and far). At each mode change a new set of firmware +
> > > > calibration data is loaded into ADDI9036 chip. If we move the firmware
> loading
> > > > in driver we would still need a custom enumeration control to select the
> working mode.
> > > >
> > > > > > Signed-off-by: Bogdan Togorean <[email protected]>
> > > > > > ---
> > > > > > MAINTAINERS | 10 +
> > > > > > drivers/media/i2c/Kconfig | 14 +
> > > > > > drivers/media/i2c/Makefile | 1 +
> > > > > > drivers/media/i2c/addi9036.c | 673
> +++++++++++++++++++++++++++++
> > > > > > include/uapi/linux/v4l2-controls.h | 6 +
> > > > > > 5 files changed, 704 insertions(+)
> > > > > > create mode 100644 drivers/media/i2c/addi9036.c
> > > > > >
> > > > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > > > index b5cfab015bd6..fb03ad9ca920 100644
> > > > > > --- a/MAINTAINERS
> > > > > > +++ b/MAINTAINERS
> > > > > > @@ -477,6 +477,16 @@ W: http://wiki.analog.com/AD7879
> > > > > > W: http://ez.analog.com/community/linux-device-drivers
> > > > > > F: drivers/input/touchscreen/ad7879.c
> > > > > >
> > > > > > +ADDI9036 TOF FRONTEND DRIVER
> > > > > > +M: Bogdan Togorean <[email protected]>
> > > > > > +L: [email protected]
> > > > > > +S: Supported
> > > > > > +W: https://www.analog.com/en/products/addi9036.html
> > > > > > +W: http://ez.analog.com/community/linux-device-drivers
> > > > > > +T: git git://linuxtv.org/media_tree.git
> > > > > > +F:
> Documentation/devicetree/bindings/media/i2c/adi,addi9036.yaml
> > > > > > +F: drivers/media/i2c/addi9036.c
> > > > > > +
> > > > > > ADDRESS SPACE LAYOUT RANDOMIZATION (ASLR)
> > > > > > M: Jiri Kosina <[email protected]>
> > > > > > S: Maintained
> > > > > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> > > > > > index c7ba76fee599..087dd307505d 100644
> > > > > > --- a/drivers/media/i2c/Kconfig
> > > > > > +++ b/drivers/media/i2c/Kconfig
> > > > > > @@ -725,6 +725,20 @@ config VIDEO_APTINA_PLL
> > > > > > config VIDEO_SMIAPP_PLL
> > > > > > tristate
> > > > > >
> > > > > > +config VIDEO_ADDI9036
> > > > > > + tristate "Analog Devices ADDI9036 ToF front-end support"
> > > > > > + depends on I2C && VIDEO_V4L2
> > > > > > + select MEDIA_CONTROLLER
> > > > > > + select VIDEO_V4L2_SUBDEV_API
> > > > > > + select V4L2_FWNODE
> > > > > > + select REGMAP_I2C
> > > > > > + help
> > > > > > + This is a Video4Linux2 driver for Analog Devices ADDI9036
> > > > > > + Time of Flight front-end.
> > > > > > +
> > > > > > + To compile this driver as a module, choose M here: the
> > > > > > + module will be called addi9036.
> > > > > > +
> > > > > > config VIDEO_HI556
> > > > > > tristate "Hynix Hi-556 sensor support"
> > > > > > depends on I2C && VIDEO_V4L2
> > > > > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> > > > > > index f0a77473979d..631a7c7612ca 100644
> > > > > > --- a/drivers/media/i2c/Makefile
> > > > > > +++ b/drivers/media/i2c/Makefile
> > > > > > @@ -26,6 +26,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o
> > > > > > obj-$(CONFIG_VIDEO_DW9714) += dw9714.o
> > > > > > obj-$(CONFIG_VIDEO_DW9768) += dw9768.o
> > > > > > obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o
> > > > > > +obj-$(CONFIG_VIDEO_ADDI9036) += addi9036.o
> > > > > > obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
> > > > > > obj-$(CONFIG_VIDEO_ADV7175) += adv7175.o
> > > > > > obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
> > > > > > diff --git a/drivers/media/i2c/addi9036.c b/drivers/media/i2c/addi9036.c
> > > > > > new file mode 100644
> > > > > > index 000000000000..76c3444058dc
> > > > > > --- /dev/null
> > > > > > +++ b/drivers/media/i2c/addi9036.c
> > > > > > @@ -0,0 +1,673 @@
> > > > > > +// SPDX-License-Identifier: GPL-2.0
> > > > > > +/*
> > > > > > + * Driver for the Analog Devices ADDI9036 ToF front-end.
> > > > > > + *
> > > > > > + * Copyright (C) 2019-2020 Analog Devices, All Rights Reserved.
> > > > > > + *
> > > > > > + */
> > > > > > +
> > > > > > +#include <linux/device.h>
> > > > > > +#include <linux/gpio/consumer.h>
> > > > > > +#include <linux/i2c.h>
> > > > > > +#include <linux/init.h>
> > > > > > +#include <linux/module.h>
> > > > > > +#include <linux/mutex.h>
> > > > > > +#include <linux/of.h>
> > > > > > +#include <linux/of_graph.h>
> > > > > > +#include <linux/regmap.h>
> > > > > > +#include <linux/slab.h>
> > > > > > +#include <linux/types.h>
> > > > > > +#include <linux/version.h>
> > > > > > +#include <media/v4l2-ctrls.h>
> > > > > > +#include <media/v4l2-fwnode.h>
> > > > > > +#include <media/v4l2-subdev.h>
> > > > > > +
> > > > > > +struct addi9036_mode_info {
> > > > > > + u32 width;
> > > > > > + u32 height;
> > > > > > + u32 pixel_rate;
> > > > > > + u32 link_freq_idx;
> > > > > > +};
> > > > > > +
> > > > > > +struct addi9036 {
> > > > > > + struct regmap *regmap;
> > > > > > + struct device *dev;
> > > > > > + struct v4l2_subdev sd;
> > > > > > + struct media_pad pad;
> > > > > > + struct v4l2_fwnode_endpoint ep;
> > > > > > + struct v4l2_mbus_framefmt fmt;
> > > > > > + struct v4l2_rect crop;
> > > > > > +
> > > > > > + const struct addi9036_mode_info *current_mode;
> > > > > > +
> > > > > > + struct v4l2_ctrl_handler ctrls;
> > > > > > + struct v4l2_ctrl *pixel_rate;
> > > > > > + struct v4l2_ctrl *link_freq;
> > > > > > +
> > > > > > + /* lock to protect power state */
> > > > > > + struct mutex power_lock;
> > > > > > + int power_count;
> > > > > > +
> > > > > > + struct gpio_desc *rst_gpio;
> > > > > > +
> > > > > > + /* controls */
> > > > > > + struct v4l2_ctrl *set_chip_config;
> > > > > > + struct v4l2_ctrl *reg_read;
> > > > > > +};
> > > > > > +
> > > > > > +static inline struct addi9036 *to_addi9036(struct v4l2_subdev *sd)
> > > > > > +{
> > > > > > + return container_of(sd, struct addi9036, sd);
> > > > > > +}
> > > > > > +
> > > > > > +#define V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY
> (V4L2_CID_USER_ADDI9036_BASE + 0)
> > > > > > +#define V4L2_CID_AD_DEV_REG_READ_QUERY
> (V4L2_CID_USER_ADDI9036_BASE + 1)
> > > > > > +
> > > > > > +static const struct reg_sequence addi9036_powerup_setting[] = {
> > > > > > + { 0xc4c0, 0x001c },
> > > > > > + { 0xc4c3, 0x001c },
> > > > > > + { 0xc4d7, 0x0000 },
> > > > > > + { 0xc4d5, 0x0002 },
> > > > > > + { 0xc4da, 0x0001 },
> > > > > > + { 0xc4f0, 0x0000 },
> > > > > > + { 0xc427, 0x0003 },
> > > > > > + { 0xc427, 0x0001 },
> > > > > > + { 0xc427, 0x0000 },
> > > > > > + { 0xc426, 0x0030 },
> > > > > > + { 0xc426, 0x0010 },
> > > > > > + { 0xc426, 0x0000 },
> > > > > > + { 0xc423, 0x0080 },
> > > > > > + { 0xc431, 0x0080 },
> > > > > > + { 0x4001, 0x0007 },
> > > > > > + { 0x7c22, 0x0004 }
> > > > > > +};
> > > > > > +
> > > > > > +static const struct reg_sequence addi9036_powerdown_setting[] = {
> > > > > > + { 0xc022, 0x0001 },
> > > > > > + { 0x4001, 0x0006 },
> > > > > > + { 0x7c22, 0x0004 },
> > > > > > + { 0xc431, 0x0082 },
> > > > > > + { 0xc423, 0x0000 },
> > > > > > + { 0xc426, 0x0020 },
> > > > > > + { 0xc427, 0x0002 },
> > > > > > + { 0xc4c0, 0x003c },
> > > > > > + { 0xc4c3, 0x003c },
> > > > > > + { 0xc4d5, 0x0003 },
> > > > > > + { 0xc4da, 0x0000 },
> > > > > > + { 0xc4d7, 0x0001 },
> > > > > > + { 0xc4f0, 0x0001 }
> > > > > > +};
> > > > > > +
> > > > > > +static const s64 link_freq_tbl[] = {
> > > > > > + 110529000,
> > > > > > + 221184000,
> > > > > > +};
> > > > > > +
> > > > > > +/* Elements of the structure must be ordered ascending by width &
> height */
> > > > > > +static const struct addi9036_mode_info addi9036_mode_info_data[] =
> {
> > > > > > + {
> > > > > > + .width = 640,
> > > > > > + .height = 480,
> > > > > > + .pixel_rate = 36864000,
> > > > > > + .link_freq_idx = 0 /* an index in link_freq_tbl[] */
> > > > > > + },
> > > > > > + {
> > > > > > + .width = 640,
> > > > > > + .height = 960,
> > > > > > + .pixel_rate = 73728000,
> > > > > > + .link_freq_idx = 1 /* an index in link_freq_tbl[] */
> > > > > > + },
> > > > > > +};
> > > > > > +
> > > > > > +static bool addi9306_readable_register(struct device *dev, unsigned
> int reg)
> > > > > > +{
> > > > > > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > > > > > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > > > > > + ((reg >= 0xC000) && (reg <= 0xC200)) ||
> > > > > > + ((reg >= 0xC300) && (reg <= 0xC6BF)))
> > > > > > + return true;
> > > > > > + else
> > > > > > + return false;
> > > > > > +}
> > > > > > +
> > > > > > +static bool addi9306_writeable_register(struct device *dev, unsigned
> int reg)
> > > > > > +{
> > > > > > + if (((reg >= 0x4000) && (reg <= 0x6FFF)) ||
> > > > > > + ((reg >= 0x7C00) && (reg <= 0x7FFF)) ||
> > > > > > + ((reg >= 0xC000) && (reg <= 0xC113)) ||
> > > > > > + ((reg >= 0xC300) && (reg <= 0xC7FF)))
> > > > > > + return true;
> > > > > > + else
> > > > > > + return false;
> > > > > > +}
> > > > > > +
> > > > > > +static const struct regmap_config addi9036_i2c_regmap_config = {
> > > > > > + .reg_bits = 16,
> > > > > > + .val_bits = 16,
> > > > > > +
> > > > > > + .max_register = 0xC7FF,
> > > > > > + .writeable_reg = addi9306_writeable_register,
> > > > > > + .readable_reg = addi9306_readable_register,
> > > > > > + .cache_type = REGCACHE_NONE,
> > > > > > +};
> > > > > > +
> > > > > > +static int addi9036_set_power_on(struct addi9036 *addi9036)
> > > > > > +{
> > > > > > + int ret;
> > > > > > +
> > > > > > + if (addi9036->rst_gpio)
> > > > > > + gpiod_set_value_cansleep(addi9036->rst_gpio, 0);
> > > > > > +
> > > > > > + ret = regmap_register_patch(addi9036->regmap,
> addi9036_powerup_setting,
> > > > > > +
> ARRAY_SIZE(addi9036_powerup_setting));
> > > > > > + if (ret)
> > > > > > + dev_err(addi9036->dev, "Could not set power up
> registers\n");
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_set_power_off(struct addi9036 *addi9036)
> > > > > > +{
> > > > > > + int ret;
> > > > > > +
> > > > > > + ret = regmap_register_patch(addi9036->regmap,
> > > > > > + addi9036_powerdown_setting,
> > > > > > +
> ARRAY_SIZE(addi9036_powerdown_setting));
> > > > > > + if (ret)
> > > > > > + dev_err(addi9036->dev, "could not set power down
> registers\n");
> > > > > > +
> > > > > > + if (addi9036->rst_gpio)
> > > > > > + gpiod_set_value_cansleep(addi9036->rst_gpio, 1);
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_s_power(struct v4l2_subdev *sd, int on)
> > > > > > +{
> > > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > > + int ret = 0;
> > > > > > +
> > > > > > + mutex_lock(&addi9036->power_lock);
> > > > > > +
> > > > > > + dev_dbg(addi9036->dev, "s_power: %d\n", on);
> > > > > > +
> > > > > > + /* If the power count is modified from 0 to != 0 or from != 0 to 0,
> > > > > > + * update the power state.
> > > > > > + */
> > > > > > + if (addi9036->power_count == !on) {
> > > > > > + if (on)
> > > > > > + ret = addi9036_set_power_on(addi9036);
> > > > > > + else
> > > > > > + ret = addi9036_set_power_off(addi9036);
> > > > > > + }
> > > > > > +
> > > > > > + if (!ret) {
> > > > > > + /* Update the power count. */
> > > > > > + addi9036->power_count += on ? 1 : -1;
> > > > > > + WARN(addi9036->power_count < 0, "Unbalanced power
> count\n");
> > > > > > + WARN(addi9036->power_count > 1, "Duplicated s_power
> call\n");
> > > > > > + }
> > > > > > +
> > > > > > + mutex_unlock(&addi9036->power_lock);
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_set_chip_config(struct addi9036 *addi9036,
> > > > > > + struct v4l2_ctrl *ctrl)
> > > > > > +{
> > > > > > + int index;
> > > > > > + unsigned short *reg, *val;
> > > > > > +
> > > > > > + reg = (unsigned short *)(ctrl->p_new.p_u16);
> > > > > > + val = (unsigned short *)(ctrl->p_new.p_u16 + 1);
> > > > > > +
> > > > > > + for (index = 0; index < ctrl->elems; index += 2) {
> > > > > > + regmap_write(addi9036->regmap, *reg, *val);
> > > > > > + reg += 2;
> > > > > > + val += 2;
> > > > > > + }
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_s_ctrl(struct v4l2_ctrl *ctrl)
> > > > > > +{
> > > > > > + struct addi9036 *addi9036 = container_of(ctrl->handler,
> > > > > > + struct addi9036, ctrls);
> > > > > > + int ret = 0;
> > > > > > + unsigned int val;
> > > > > > +
> > > > > > + switch (ctrl->id) {
> > > > > > + case V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY:
> > > > > > + ret = addi9036_set_chip_config(addi9036, ctrl);
> > > > > > + break;
> > > > > > + case V4L2_CID_AD_DEV_REG_READ_QUERY:
> > > > > > + ret = regmap_read(addi9036->regmap, *(u16 *)(ctrl-
> > > > > >p_new.p_u16),
> > > > > > + &val);
> > > > > > + if (ret)
> > > > > > + dev_warn(addi9036->dev,
> > > > > > + "could not read from register\n");
> > > > > > + else
> > > > > > + *(u16 *)(ctrl->p_new.p_u16) = val;
> > > > > > + break;
> > > > > > + case V4L2_CID_PIXEL_RATE:
> > > > > > + case V4L2_CID_LINK_FREQ:
> > > > > > + break;
> > > > > > + default:
> > > > > > + dev_err(addi9036->dev, "%s > Unhandled: %x
> param=%x\n",
> > > > > > + __func__, ctrl->id, ctrl->val);
> > > > > > + ret = -EINVAL;
> > > > > > + break;
> > > > > > + }
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static const struct v4l2_ctrl_ops addi9036_ctrl_ops = {
> > > > > > + .s_ctrl = addi9036_s_ctrl,
> > > > > > +};
> > > > > > +
> > > > > > +static const struct v4l2_ctrl_config addi9036_ctrl_chip_config = {
> > > > > > + .ops = &addi9036_ctrl_ops,
> > > > > > + .id =
> V4L2_CID_AD_DEV_SET_CHIP_CONFIG_QUERY,
> > > > > > + .name = "chip_config",
> > > > > > + .type = V4L2_CTRL_TYPE_U16,
> > > > > > + .def = 0xFF,
> > > > > > + .min = 0x00,
> > > > > > + .max = 0xFFFF,
> > > > > > + .step = 1,
> > > > > > + .dims = { 2048 },
> > > > > > + .elem_size = 2
> > > > > > +};
> > > > > > +
> > > > > > +static const struct v4l2_ctrl_config addi9036_ctrl_reg_read = {
> > > > > > + .ops = &addi9036_ctrl_ops,
> > > > > > + .id = V4L2_CID_AD_DEV_REG_READ_QUERY,
> > > > > > + .name = "reg_read",
> > > > > > + .type = V4L2_CTRL_TYPE_U16,
> > > > > > + .def = 0,
> > > > > > + .min = 0x00,
> > > > > > + .max = 0xFFFF,
> > > > > > + .step = 1
> > > > > > +};
> > > > > > +
> > > > > > +static int addi9036_enum_mbus_code(struct v4l2_subdev *sd,
> > > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > > + struct v4l2_subdev_mbus_code_enum
> *code)
> > > > > > +{
> > > > > > + if (code->index > 0)
> > > > > > + return -EINVAL;
> > > > > > +
> > > > > > + code->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_enum_frame_size(struct v4l2_subdev *subdev,
> > > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > > + struct v4l2_subdev_frame_size_enum
> *fse)
> > > > > > +{
> > > > > > + if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12)
> > > > > > + return -EINVAL;
> > > > > > +
> > > > > > + if (fse->index >= ARRAY_SIZE(addi9036_mode_info_data))
> > > > > > + return -EINVAL;
> > > > > > +
> > > > > > + fse->min_width = addi9036_mode_info_data[fse->index].width;
> > > > > > + fse->max_width = addi9036_mode_info_data[fse->index].width;
> > > > > > + fse->min_height = addi9036_mode_info_data[fse-
> >index].height;
> > > > > > + fse->max_height = addi9036_mode_info_data[fse-
> >index].height;
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static struct v4l2_mbus_framefmt *
> > > > > > +addi9036_get_pad_format(struct addi9036 *addi9036,
> > > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > > + unsigned int pad,
> > > > > > + enum v4l2_subdev_format_whence which)
> > > > > > +{
> > > > > > + switch (which) {
> > > > > > + case V4L2_SUBDEV_FORMAT_TRY:
> > > > > > + return v4l2_subdev_get_try_format(&addi9036->sd, cfg,
> pad);
> > > > > > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > > > > > + return &addi9036->fmt;
> > > > > > + default:
> > > > > > + return NULL;
> > > > > > + }
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_get_format(struct v4l2_subdev *sd,
> > > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > > + struct v4l2_subdev_format *format)
> > > > > > +{
> > > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > > + struct v4l2_mbus_framefmt *pad_format;
> > > > > > +
> > > > > > + pad_format = addi9036_get_pad_format(addi9036, cfg, format-
> >pad,
> > > > > > + format->which);
> > > > > > +
> > > > > > + if (!pad_format)
> > > > > > + return -EINVAL;
> > > > > > +
> > > > > > + format->format = *pad_format;
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static struct v4l2_rect *
> > > > > > +addi9036_get_pad_crop(struct addi9036 *addi9036,
> > > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > > + unsigned int pad, enum v4l2_subdev_format_whence
> which)
> > > > > > +{
> > > > > > + switch (which) {
> > > > > > + case V4L2_SUBDEV_FORMAT_TRY:
> > > > > > + return v4l2_subdev_get_try_crop(&addi9036->sd, cfg,
> pad);
> > > > > > + case V4L2_SUBDEV_FORMAT_ACTIVE:
> > > > > > + return &addi9036->crop;
> > > > > > + default:
> > > > > > + return NULL;
> > > > > > + }
> > > > > > +}
> > > > > > +
> > > > > > +static const struct addi9036_mode_info *
> > > > > > +addi9036_find_nearest_mode(unsigned int width, unsigned int height)
> > > > > > +{
> > > > > > + unsigned int i;
> > > > > > +
> > > > > > + for (i = ARRAY_SIZE(addi9036_mode_info_data) - 1; i > 0; i--) {
> > > > > > + if (addi9036_mode_info_data[i].width <= width &&
> > > > > > + addi9036_mode_info_data[i].height <= height)
> > > > > > + break;
> > > > > > + }
> > > > > > +
> > > > > > + return &addi9036_mode_info_data[i];
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_set_format(struct v4l2_subdev *sd,
> > > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > > + struct v4l2_subdev_format *format)
> > > > > > +{
> > > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > > + struct v4l2_mbus_framefmt *framefmt;
> > > > > > + struct v4l2_rect *crop;
> > > > > > + const struct addi9036_mode_info *new_mode;
> > > > > > + int ret;
> > > > > > +
> > > > > > + dev_dbg(addi9036->dev, "set_fmt: %x %dx%d\n",
> > > > > > + format->format.code, format->format.width,
> > > > > > + format->format.height);
> > > > > > +
> > > > > > + crop = addi9036_get_pad_crop(addi9036, cfg, format->pad,
> > > > > > + format->which);
> > > > > > +
> > > > > > + if (!crop)
> > > > > > + return -EINVAL;
> > > > > > +
> > > > > > + new_mode = addi9036_find_nearest_mode(format-
> >format.width,
> > > > > > + format->format.height);
> > > > > > + crop->width = new_mode->width;
> > > > > > + crop->height = new_mode->height;
> > > > > > +
> > > > > > + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > > > > > + ret = v4l2_ctrl_s_ctrl_int64(addi9036->pixel_rate,
> > > > > > + new_mode->pixel_rate);
> > > > > > + if (ret < 0)
> > > > > > + return ret;
> > > > > > +
> > > > > > + ret = v4l2_ctrl_s_ctrl(addi9036->link_freq,
> > > > > > + new_mode->link_freq_idx);
> > > > > > + if (ret < 0)
> > > > > > + return ret;
> > > > > > +
> > > > > > + addi9036->current_mode = new_mode;
> > > > > > + }
> > > > > > +
> > > > > > + framefmt = addi9036_get_pad_format(addi9036, cfg, format-
> >pad,
> > > > > > + format->which);
> > > > > > +
> > > > > > + if (!framefmt)
> > > > > > + return -EINVAL;
> > > > > > +
> > > > > > + framefmt->width = crop->width;
> > > > > > + framefmt->height = crop->height;
> > > > > > + framefmt->code = MEDIA_BUS_FMT_SBGGR12_1X12;
> > > > > > + framefmt->field = V4L2_FIELD_NONE;
> > > > > > + framefmt->colorspace = V4L2_COLORSPACE_SRGB;
> > > > > > +
> > > > > > + format->format = *framefmt;
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_entity_init_cfg(struct v4l2_subdev *subdev,
> > > > > > + struct v4l2_subdev_pad_config *cfg)
> > > > > > +{
> > > > > > + struct v4l2_subdev_format fmt = { 0 };
> > > > > > +
> > > > > > + if (cfg)
> > > > > > + fmt.which = V4L2_SUBDEV_FORMAT_TRY;
> > > > > > + else
> > > > > > + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> > > > > > +
> > > > > > + fmt.format.width = 640;
> > > > > > + fmt.format.height = 960;
> > > > > > +
> > > > > > + addi9036_set_format(subdev, cfg, &fmt);
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_get_selection(struct v4l2_subdev *sd,
> > > > > > + struct v4l2_subdev_pad_config *cfg,
> > > > > > + struct v4l2_subdev_selection *sel)
> > > > > > +{
> > > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > > +
> > > > > > + if (sel->target != V4L2_SEL_TGT_CROP)
> > > > > > + return -EINVAL;
> > > > > > +
> > > > > > + sel->r = *addi9036_get_pad_crop(addi9036, cfg, sel->pad, sel-
> >which);
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_s_stream(struct v4l2_subdev *subdev, int enable)
> > > > > > +{
> > > > > > + struct addi9036 *addi9036 = to_addi9036(subdev);
> > > > > > +
> > > > > > + dev_dbg(addi9036->dev, "stream %d\n", enable);
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static const struct v4l2_subdev_core_ops addi9036_core_ops = {
> > > > > > + .s_power = addi9036_s_power,
> > > > > > +};
> > > > > > +
> > > > > > +static const struct v4l2_subdev_video_ops addi9036_video_ops = {
> > > > > > + .s_stream = addi9036_s_stream,
> > > > > > +};
> > > > > > +
> > > > > > +static const struct v4l2_subdev_pad_ops addi9036_subdev_pad_ops =
> {
> > > > > > + .init_cfg = addi9036_entity_init_cfg,
> > > > > > + .enum_mbus_code = addi9036_enum_mbus_code,
> > > > > > + .enum_frame_size = addi9036_enum_frame_size,
> > > > > > + .get_fmt = addi9036_get_format,
> > > > > > + .set_fmt = addi9036_set_format,
> > > > > > + .get_selection = addi9036_get_selection,
> > > > > > +};
> > > > > > +
> > > > > > +static const struct v4l2_subdev_ops addi9036_subdev_ops = {
> > > > > > + .core = &addi9036_core_ops,
> > > > > > + .video = &addi9036_video_ops,
> > > > > > + .pad = &addi9036_subdev_pad_ops,
> > > > > > +};
> > > > > > +
> > > > > > +static int addi9036_probe(struct i2c_client *client)
> > > > > > +{
> > > > > > + struct device *dev = &client->dev;
> > > > > > + struct fwnode_handle *endpoint;
> > > > > > + struct addi9036 *addi9036;
> > > > > > + int ret;
> > > > > > +
> > > > > > + dev_dbg(dev, "%s: i2c addr = 0x%x\n", __func__, client->addr);
> > > > > > +
> > > > > > + addi9036 = devm_kzalloc(dev, sizeof(struct addi9036),
> GFP_KERNEL);
> > > > > > + if (!addi9036)
> > > > > > + return -ENOMEM;
> > > > > > +
> > > > > > + addi9036->dev = dev;
> > > > > > +
> > > > > > + addi9036->regmap = devm_regmap_init_i2c(client,
> > > > > > +
> &addi9036_i2c_regmap_config);
> > > > > > + if (IS_ERR(addi9036->regmap)) {
> > > > > > + dev_err(dev, "Error initializing i2c regmap\n");
> > > > > > + return PTR_ERR(addi9036->regmap);
> > > > > > + }
> > > > > > +
> > > > > > + mutex_init(&addi9036->power_lock);
> > > > > > +
> > > > > > + endpoint =
> fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
> > > > > > + if (!endpoint) {
> > > > > > + dev_err(dev, "endpoint node not found\n");
> > > > > > + return -EINVAL;
> > > > > > + }
> > > > > > +
> > > > > > + ret = v4l2_fwnode_endpoint_parse(endpoint, &addi9036->ep);
> > > > > > + if (ret < 0) {
> > > > > > + dev_err(dev, "parsing endpoint node failed\n");
> > > > > > + return ret;
> > > > > > + }
> > > > > > + fwnode_handle_put(endpoint);
> > > > > > +
> > > > > > + if (addi9036->ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
> > > > > > + dev_err(dev, "invalid bus type, must be MIPI CSI2\n");
> > > > > > + return -EINVAL;
> > > > > > + }
> > > > > > +
> > > > > > + addi9036->rst_gpio = gpiod_get_optional(dev, "reset",
> GPIOD_OUT_HIGH);
> > > > > > + if (IS_ERR(addi9036->rst_gpio)) {
> > > > > > + dev_info(dev, "Unable to get \"reset\" gpio\n");
> > > > > > + return PTR_ERR(addi9036->rst_gpio);
> > > > > > + }
> > > > > > +
> > > > > > + v4l2_ctrl_handler_init(&addi9036->ctrls, 4);
> > > > > > +
> > > > > > + addi9036->pixel_rate = v4l2_ctrl_new_std(&addi9036->ctrls,
> > > > > > + &addi9036_ctrl_ops,
> > > > > > + V4L2_CID_PIXEL_RATE,
> > > > > > + 1, INT_MAX, 1, 1);
> > > > > > + addi9036->link_freq = v4l2_ctrl_new_int_menu(&addi9036-
> >ctrls,
> > > > > > + &addi9036_ctrl_ops,
> > > > > > + V4L2_CID_LINK_FREQ,
> > > > > > + ARRAY_SIZE(
> > > > > > + link_freq_tbl) -
> 1,
> > > > > > + 0, link_freq_tbl);
> > > > > > + if (addi9036->link_freq)
> > > > > > + addi9036->link_freq->flags |=
> V4L2_CTRL_FLAG_READ_ONLY;
> > > > > > +
> > > > > > + addi9036->set_chip_config = v4l2_ctrl_new_custom(&addi9036-
> >ctrls,
> > > > > > +
> &addi9036_ctrl_chip_config,
> > > > > > + NULL);
> > > > > > +
> > > > > > + addi9036->reg_read = v4l2_ctrl_new_custom(&addi9036->ctrls,
> > > > > > +
> &addi9036_ctrl_reg_read,
> > > > > > + NULL);
> > > > > > +
> > > > > > + addi9036->sd.ctrl_handler = &addi9036->ctrls;
> > > > > > +
> > > > > > + if (addi9036->ctrls.error) {
> > > > > > + dev_err(dev, "%s: control initialization error %d\n",
> > > > > > + __func__, addi9036->ctrls.error);
> > > > > > + ret = addi9036->ctrls.error;
> > > > > > + goto free_ctrl;
> > > > > > + }
> > > > > > +
> > > > > > + v4l2_i2c_subdev_init(&addi9036->sd, client,
> &addi9036_subdev_ops);
> > > > > > + addi9036->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > > > > > + addi9036->pad.flags = MEDIA_PAD_FL_SOURCE;
> > > > > > + addi9036->sd.dev = &client->dev;
> > > > > > + addi9036->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> > > > > > +
> > > > > > + ret = media_entity_pads_init(&addi9036->sd.entity, 1,
> &addi9036->pad);
> > > > > > +
> > > > > > + if (ret < 0) {
> > > > > > + dev_err(dev, "could not register media entity\n");
> > > > > > + goto free_ctrl;
> > > > > > + }
> > > > > > +
> > > > > > + ret = addi9036_s_power(&addi9036->sd, true);
> > > > > > + if (ret < 0) {
> > > > > > + dev_err(dev, "could not power up addi9036\n");
> > > > > > + goto free_entity;
> > > > > > + }
> > > > > > +
> > > > > > + dev_info(dev, "addi9036 detected at address 0x%02x\n", client-
> >addr);
> > > > > > +
> > > > > > + ret = addi9036_s_power(&addi9036->sd, false);
> > > > > > + if (ret < 0) {
> > > > > > + dev_err(dev, "could not power down addi9036\n");
> > > > > > + goto free_entity;
> > > > > > + }
> > > > > > +
> > > > > > + addi9036_entity_init_cfg(&addi9036->sd, NULL);
> > > > > > +
> > > > > > + ret = v4l2_async_register_subdev(&addi9036->sd);
> > > > > > + if (ret < 0)
> > > > > > + dev_err(dev, "could not register v4l2 device\n");
> > > > > > +
> > > > > > + return 0;
> > > > > > +
> > > > > > +free_entity:
> > > > > > + media_entity_cleanup(&addi9036->sd.entity);
> > > > > > +free_ctrl:
> > > > > > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > > > > > + mutex_destroy(&addi9036->power_lock);
> > > > > > +
> > > > > > + return ret;
> > > > > > +}
> > > > > > +
> > > > > > +static int addi9036_remove(struct i2c_client *client)
> > > > > > +{
> > > > > > + struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > > > > + struct addi9036 *addi9036 = to_addi9036(sd);
> > > > > > +
> > > > > > + v4l2_async_unregister_subdev(&addi9036->sd);
> > > > > > + media_entity_cleanup(&addi9036->sd.entity);
> > > > > > + if (addi9036->rst_gpio)
> > > > > > + gpiod_put(addi9036->rst_gpio);
> > > > > > + v4l2_ctrl_handler_free(&addi9036->ctrls);
> > > > > > + mutex_destroy(&addi9036->power_lock);
> > > > > > +
> > > > > > + return 0;
> > > > > > +}
> > > > > > +
> > > > > > +static const struct i2c_device_id addi9036_id[] = {
> > > > > > + { "addi9036", 0 },
> > > > > > + {}
> > > > > > +};
> > > > > > +MODULE_DEVICE_TABLE(i2c, addi9036_id);
> > > > > > +
> > > > > > +static const struct of_device_id addi9036_of_match[] = {
> > > > > > + { .compatible = "adi,addi9036" },
> > > > > > + { /* sentinel */ }
> > > > > > +};
> > > > > > +MODULE_DEVICE_TABLE(of, addi9036_of_match);
> > > > > > +
> > > > > > +static struct i2c_driver addi9036_i2c_driver = {
> > > > > > + .driver = {
> > > > > > + .of_match_table = addi9036_of_match,
> > > > > > + .name = "addi9036",
> > > > > > + },
> > > > > > + .probe_new = addi9036_probe,
> > > > > > + .remove = addi9036_remove,
> > > > > > + .id_table = addi9036_id,
> > > > > > +};
> > > > > > +
> > > > > > +module_i2c_driver(addi9036_i2c_driver);
> > > > > > +
> > > > > > +MODULE_DESCRIPTION("Analog Devices ADDI9036 Camera Driver");
> > > > > > +MODULE_AUTHOR("Bogdan Togorean");
> > > > > > +MODULE_LICENSE("GPL v2");
> > > > > > diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-
> controls.h
> > > > > > index 62271418c1be..f88b56479bc1 100644
> > > > > > --- a/include/uapi/linux/v4l2-controls.h
> > > > > > +++ b/include/uapi/linux/v4l2-controls.h
> > > > > > @@ -198,6 +198,12 @@ enum v4l2_colorfx {
> > > > > > */
> > > > > > #define V4L2_CID_USER_ATMEL_ISC_BASE (V4L2_CID_USER_BASE +
> 0x10c0)
> > > > > >
> > > > > > +/*
> > > > > > + * The base for the addi9036 driver controls.
> > > > > > + * We reserve 16 controls for this driver.
> > > > > > + */
> > > > > > +#define V4L2_CID_USER_ADDI9036_BASE (V4L2_CID_USER_BASE +
> 0x10e0)
> > > > > > +
> > > > > > /* MPEG-class control IDs */
> > > > > > /* The MPEG controls are applicable to all codec controls
> > > > > > * and the 'MPEG' part of the define is historical */
>
> --
> Regards,
>
> Laurent Pinchart