From 7a6b0c38e5e1502f309b89b16fad8c70645e1221 Mon Sep 17 00:00:00 2001 From: Vladimir Barinov Date: Sun, 14 May 2017 15:20:01 +0300 Subject: [PATCH] Gen3: LVDS cameras This add Gen3 LVDS cameras support: - deserializers: MAX9286, TI964, TI954, TI960 - cameras: ov10635, ov490+ov10640, ov495+OV2775, ar0132 Signed-off-by: Vladimir Barinov --- drivers/media/i2c/soc_camera/Kconfig | 47 + drivers/media/i2c/soc_camera/Makefile | 7 + drivers/media/i2c/soc_camera/ar0132.c | 548 +++++++++++ drivers/media/i2c/soc_camera/ar0132.h | 213 ++++ drivers/media/i2c/soc_camera/max9286_max9271.c | 567 +++++++++++ drivers/media/i2c/soc_camera/max9286_max9271.h | 243 +++++ drivers/media/i2c/soc_camera/ov10635.c | 759 ++++++++++++++ drivers/media/i2c/soc_camera/ov10635.h | 1139 ++++++++++++++++++++++ drivers/media/i2c/soc_camera/ov10635_debug.h | 54 + drivers/media/i2c/soc_camera/ov106xx.c | 106 ++ drivers/media/i2c/soc_camera/ov490_ov10640.c | 1046 ++++++++++++++++++++ drivers/media/i2c/soc_camera/ov490_ov10640.h | 88 ++ drivers/media/i2c/soc_camera/ov495_ov2775.c | 658 +++++++++++++ drivers/media/i2c/soc_camera/ov495_ov2775.h | 23 + drivers/media/i2c/soc_camera/ti954_ti9x3.c | 417 ++++++++ drivers/media/i2c/soc_camera/ti964_ti9x3.c | 385 ++++++++ drivers/media/i2c/soc_camera/ti9x4_ti9x3.h | 153 +++ drivers/media/platform/soc_camera/rcar_csi2.c | 297 ++++-- drivers/media/platform/soc_camera/rcar_vin.c | 174 +++- drivers/media/platform/soc_camera/soc_camera.c | 17 +- drivers/media/platform/soc_camera/soc_mediabus.c | 16 + include/media/drv-intf/soc_mediabus.h | 3 + include/media/soc_camera.h | 1 + 23 files changed, 6852 insertions(+), 109 deletions(-) create mode 100644 drivers/media/i2c/soc_camera/ar0132.c create mode 100644 drivers/media/i2c/soc_camera/ar0132.h create mode 100644 drivers/media/i2c/soc_camera/max9286_max9271.c create mode 100644 drivers/media/i2c/soc_camera/max9286_max9271.h create mode 100644 drivers/media/i2c/soc_camera/ov10635.c create mode 100644 drivers/media/i2c/soc_camera/ov10635.h create mode 100644 drivers/media/i2c/soc_camera/ov10635_debug.h create mode 100644 drivers/media/i2c/soc_camera/ov106xx.c create mode 100644 drivers/media/i2c/soc_camera/ov490_ov10640.c create mode 100644 drivers/media/i2c/soc_camera/ov490_ov10640.h create mode 100644 drivers/media/i2c/soc_camera/ov495_ov2775.c create mode 100644 drivers/media/i2c/soc_camera/ov495_ov2775.h create mode 100644 drivers/media/i2c/soc_camera/ti954_ti9x3.c create mode 100644 drivers/media/i2c/soc_camera/ti964_ti9x3.c create mode 100644 drivers/media/i2c/soc_camera/ti9x4_ti9x3.h diff --git a/drivers/media/i2c/soc_camera/Kconfig b/drivers/media/i2c/soc_camera/Kconfig index 7704bcf..82da59f 100644 --- a/drivers/media/i2c/soc_camera/Kconfig +++ b/drivers/media/i2c/soc_camera/Kconfig @@ -6,6 +6,53 @@ config SOC_CAMERA_IMX074 help This driver supports IMX074 cameras from Sony +config SOC_CAMERA_MAX9286_MAX9271 + tristate "max9286-max9271 GMSL support" + depends on SOC_CAMERA && I2C + help + This is a MAXIM max9286-max9271 GMSL driver + +config SOC_CAMERA_OV106XX + tristate "ov106xx camera support" + depends on SOC_CAMERA && SOC_CAMERA_MAX9286_MAX9271 && I2C + help + This is a runtime detected OmniVision ov10635 or ov490-ov10640 + or ov495-ov2775 sensors camera driver + +if !SOC_CAMERA_OV106XX + +config SOC_CAMERA_OV10635 + tristate "ov10635 camera support" + depends on SOC_CAMERA && SOC_CAMERA_MAX9286_MAX9271 && I2C + help + This is an OmniVision ov10635 sensor camera driver + +config SOC_CAMERA_OV490_OV10640 + tristate "ov490-ov10640 camera support" + depends on SOC_CAMERA && SOC_CAMERA_MAX9286_MAX9271 && I2C + help + This is an OmniVision ov490-ov10640 sensor camera driver + +config SOC_CAMERA_OV495_OV2775 + tristate "ov495-ov2775 camera support" + depends on SOC_CAMERA && I2C + help + This is an OmniVision ov495-ov2775 sensor camera driver + +endif + +config SOC_CAMERA_TI964_TI9X3 + tristate "ti964-ti9x3 FPDLinkIII support" + depends on SOC_CAMERA && I2C + help + This is an Texas Instruments ti964-ti9X3 FPDLinkIII driver + +config SOC_CAMERA_TI954_TI9X3 + tristate "ti954-ti9X3 FPDLinkIII support" + depends on SOC_CAMERA && I2C + help + This is an Texas Instruments ti954-ti9X3 FPDLinkIII driver + config SOC_CAMERA_MT9M001 tristate "mt9m001 support" depends on SOC_CAMERA && I2C diff --git a/drivers/media/i2c/soc_camera/Makefile b/drivers/media/i2c/soc_camera/Makefile index 6f994f9..7d4c1ab 100644 --- a/drivers/media/i2c/soc_camera/Makefile +++ b/drivers/media/i2c/soc_camera/Makefile @@ -1,8 +1,15 @@ obj-$(CONFIG_SOC_CAMERA_IMX074) += imx074.o +obj-$(CONFIG_SOC_CAMERA_MAX9286_MAX9271) += max9286_max9271.o +obj-$(CONFIG_SOC_CAMERA_TI964_TI9X3) += ti964_ti9x3.o +obj-$(CONFIG_SOC_CAMERA_TI954_TI9X3) += ti954_ti9x3.o obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o obj-$(CONFIG_SOC_CAMERA_MT9T112) += mt9t112.o obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o +obj-$(CONFIG_SOC_CAMERA_OV10635) += ov10635.o +obj-$(CONFIG_SOC_CAMERA_OV490_OV10640) += ov490_ov10640.o +obj-$(CONFIG_SOC_CAMERA_OV495_OV2775) += ov495_ov2775.o +obj-$(CONFIG_SOC_CAMERA_OV106XX) += ov106xx.o obj-$(CONFIG_SOC_CAMERA_OV2640) += ov2640.o obj-$(CONFIG_SOC_CAMERA_OV5642) += ov5642.o obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o diff --git a/drivers/media/i2c/soc_camera/ar0132.c b/drivers/media/i2c/soc_camera/ar0132.c new file mode 100644 index 0000000..284c522 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ar0132.c @@ -0,0 +1,548 @@ +/* + * Aptina AR0132 sensor camera driver + * + * Copyright (C) 2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ar0132.h" + +#define AR0132_I2C_ADDR 0x18 +//#define AR0132_I2C_ADDR 0x50 // eeprom + +#define AR0132_PID 0x3000 +#define AR0132_VERSION_REG 0x2400 + +#define AR0132_MEDIA_BUS_FMT MEDIA_BUS_FMT_SBGGR12_1X12 + +struct ar0132_priv { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct media_pad pad; + struct v4l2_rect rect; + int init_complete; + u8 id[6]; + int exposure; + int gain; + int autogain; + int dvp_order; + /* serializers */ + int ti964_addr; + int ti954_addr; + int ti9x3_addr; + int port; + int gpio_resetb; + int gpio_fsin; + +}; + +static inline struct ar0132_priv *to_ar0132(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ar0132_priv, sd); +} + +static int ar0132_set_regs(struct i2c_client *client, + const struct ar0132_reg *regs, int nr_regs) +{ + int i; + + for (i = 0; i < nr_regs; i++) { + if (regs[i].reg == AR0132_DELAY) { + mdelay(regs[i].val); + continue; + } + + reg16_write16(client, regs[i].reg, regs[i].val); + } + + return 0; +} + +static int ar0132_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +static int ar0132_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ar0132_priv *priv = to_ar0132(client); + + if (format->pad) + return -EINVAL; + + mf->width = priv->rect.width; + mf->height = priv->rect.height; + mf->code = AR0132_MEDIA_BUS_FMT; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ar0132_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + + mf->code = AR0132_MEDIA_BUS_FMT; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_NONE; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + cfg->try_fmt = *mf; + + return 0; +} + +static int ar0132_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = AR0132_MEDIA_BUS_FMT; + + return 0; +} + +static int ar0132_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ar0132_priv *priv = to_ar0132(client); + + memcpy(edid->edid, priv->id, 6); + + edid->edid[6] = 0xff; + edid->edid[7] = client->addr; + edid->edid[8] = AR0132_VERSION_REG >> 8; + edid->edid[9] = AR0132_VERSION_REG & 0xff; + + return 0; +} + +static int ar0132_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_rect *rect = &sel->r; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ar0132_priv *priv = to_ar0132(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + rect->left = ALIGN(rect->left, 2); + rect->top = ALIGN(rect->top, 2); + rect->width = ALIGN(rect->width, 2); + rect->height = ALIGN(rect->height, 2); + + if ((rect->left + rect->width > AR0132_MAX_WIDTH) || + (rect->top + rect->height > AR0132_MAX_HEIGHT)) + *rect = priv->rect; + + priv->rect.left = rect->left; + priv->rect.top = rect->top; + priv->rect.width = rect->width; + priv->rect.height = rect->height; + + return 0; +} + +static int ar0132_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ar0132_priv *priv = to_ar0132(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = AR0132_MAX_WIDTH; + sel->r.height = AR0132_MAX_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = AR0132_MAX_WIDTH; + sel->r.height = AR0132_MAX_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = priv->rect; + return 0; + default: + return -EINVAL; + } +} + +static int ar0132_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + cfg->flags = V4L2_MBUS_CSI2_1_LANE | V4L2_MBUS_CSI2_CHANNEL_0 | + V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + cfg->type = V4L2_MBUS_CSI2; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ar0132_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u16 val = 0; + + ret = reg16_read16(client, (u16)reg->reg, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = sizeof(u16); + + return 0; +} + +static int ar0132_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return reg16_write16(client, (u16)reg->reg, (u16)reg->val); +} +#endif + +static struct v4l2_subdev_core_ops ar0132_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ar0132_g_register, + .s_register = ar0132_s_register, +#endif +}; + +static int ar0132_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ar0132_priv *priv = to_ar0132(client); + int ret = -EINVAL; + + if (!priv->init_complete) + return 0; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + case V4L2_CID_GAMMA: + case V4L2_CID_SHARPNESS: + case V4L2_CID_AUTOGAIN: + case V4L2_CID_GAIN: + case V4L2_CID_EXPOSURE: + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ar0132_ctrl_ops = { + .s_ctrl = ar0132_s_ctrl, +}; + +static struct v4l2_subdev_video_ops ar0132_video_ops = { + .s_stream = ar0132_s_stream, + .g_mbus_config = ar0132_g_mbus_config, +}; + +static const struct v4l2_subdev_pad_ops ar0132_subdev_pad_ops = { + .get_edid = ar0132_get_edid, + .enum_mbus_code = ar0132_enum_mbus_code, + .get_selection = ar0132_get_selection, + .set_selection = ar0132_set_selection, + .get_fmt = ar0132_get_fmt, + .set_fmt = ar0132_set_fmt, +}; + +static struct v4l2_subdev_ops ar0132_subdev_ops = { + .core = &ar0132_core_ops, + .video = &ar0132_video_ops, + .pad = &ar0132_subdev_pad_ops, +}; + +static void ar0132_otp_id_read(struct i2c_client *client) +{ +} + +static ssize_t ar0132_otp_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(to_i2c_client(dev)); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ar0132_priv *priv = to_ar0132(client); + + return snprintf(buf, 32, "%02x:%02x:%02x:%02x:%02x:%02x\n", + priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]); +} + +static DEVICE_ATTR(otp_id_ar0132, S_IRUGO, ar0132_otp_id_show, NULL); + +static int ar0132_initialize(struct i2c_client *client) +{ + struct ar0132_priv *priv = to_ar0132(client); + u16 val = 0; + u16 pid = 0; + int ret = 0; + + /* check and show model ID */ + reg16_read16(client, AR0132_PID, &pid); + + if (pid != AR0132_VERSION_REG) { + dev_dbg(&client->dev, "Product ID error %x\n", pid); + ret = -ENODEV; + goto err; + } + + /* Program wizard registers */ + ar0132_set_regs(client, ar0132_regs_wizard, ARRAY_SIZE(ar0132_regs_wizard)); + + /* Enable stream */ + reg16_read16(client, 0x301a, &val); // read inital reset_register value + val |= (1 << 2); // Set streamOn bit + reg16_write16(client, 0x301a, val); // Start Streaming + + /* Read OTP IDs */ + ar0132_otp_id_read(client); + + dev_info(&client->dev, "ar0132 PID %x, res %dx%d, OTP_ID %02x:%02x:%02x:%02x:%02x:%02x\n", + pid, AR0132_MAX_WIDTH, AR0132_MAX_HEIGHT, priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]); +err: + return ret; +} + +static int ar0132_parse_dt(struct device_node *np, struct ar0132_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int i; + struct device_node *endpoint = NULL, *rendpoint = NULL; + int tmp_addr = 0; + + for (i = 0; ; i++) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + if (!endpoint) + break; + + of_node_put(endpoint); + + of_property_read_u32(endpoint, "dvp-order", &priv->dvp_order); + + rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0); + if (!rendpoint) + continue; + + if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) && + !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti964-ti9x3") && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti964_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + + if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) && + !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti954-ti9x3") && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti954_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + } + + if (!priv->ti964_addr && !priv->ti954_addr) { + dev_err(&client->dev, "deserializer does not present\n"); + return -EINVAL; + } + + /* setup I2C translator address */ + tmp_addr = client->addr; + if (priv->ti964_addr) { + client->addr = priv->ti964_addr; /* Deserializer I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x65, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x5d, AR0132_I2C_ADDR << 1); /* Sensor native I2C address */ + + reg8_write(client, 0x6e, 0xa9); /* GPIO0 - reset, GPIO1 - fsin */ + } + if (priv->ti954_addr) { + client->addr = priv->ti954_addr; /* Deserializer I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x65, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x5d, AR0132_I2C_ADDR << 1); /* Sensor native I2C address */ + + reg8_write(client, 0x6e, 0xa9); /* GPIO0 - reset, GPIO1 - fsin */ + } + client->addr = tmp_addr; + + mdelay(10); + + return 0; +} + +static int ar0132_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ar0132_priv *priv; + int ret; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + v4l2_i2c_subdev_init(&priv->sd, client, &ar0132_subdev_ops); + priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + priv->exposure = 0x100; + priv->gain = 0x100; + priv->autogain = 1; + v4l2_ctrl_handler_init(&priv->hdl, 4); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 16, 1, 7); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_CONTRAST, 0, 16, 1, 7); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_SATURATION, 0, 7, 1, 2); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_HUE, 0, 23, 1, 12); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_GAMMA, -128, 128, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_SHARPNESS, 0, 10, 1, 3); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, priv->autogain); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_GAIN, 0, 0xffff, 1, priv->gain); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0xffff, 1, priv->exposure); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 1); + v4l2_ctrl_new_std(&priv->hdl, &ar0132_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + priv->sd.ctrl_handler = &priv->hdl; + + ret = priv->hdl.error; + if (ret) + goto cleanup; + + v4l2_ctrl_handler_setup(&priv->hdl); + + priv->pad.flags = MEDIA_PAD_FL_SOURCE; + priv->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&priv->sd.entity, 1, &priv->pad); + if (ret < 0) + goto cleanup; + + ret = ar0132_parse_dt(client->dev.of_node, priv); + if (ret) + goto cleanup; + + ret = ar0132_initialize(client); + if (ret < 0) + goto cleanup; + + priv->rect.left = 0; + priv->rect.top = 0; + priv->rect.width = AR0132_MAX_WIDTH; + priv->rect.height = AR0132_MAX_HEIGHT; + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) + goto cleanup; + + if (device_create_file(&client->dev, &dev_attr_otp_id_ar0132) != 0) { + dev_err(&client->dev, "sysfs otp_id entry creation failed\n"); + goto cleanup; + } + + priv->init_complete = 1; + + return 0; + +cleanup: + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); +#ifdef CONFIG_SOC_CAMERA_AR0132 + v4l_err(client, "failed to probe @ 0x%02x (%s)\n", + client->addr, client->adapter->name); +#endif + return ret; +} + +static int ar0132_remove(struct i2c_client *client) +{ + struct ar0132_priv *priv = i2c_get_clientdata(client); + + device_remove_file(&client->dev, &dev_attr_otp_id_ar0132); + v4l2_async_unregister_subdev(&priv->sd); + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); + + return 0; +} + +#ifdef CONFIG_SOC_CAMERA_AR0132 +static const struct i2c_device_id ar0132_id[] = { + { "ar0132", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ar0132_id); + +static const struct of_device_id ar0132_of_ids[] = { + { .compatible = "aptina,ar0132", }, + { } +}; +MODULE_DEVICE_TABLE(of, ar0132_of_ids); + +static struct i2c_driver ar0132_i2c_driver = { + .driver = { + .name = "ar0132", + .of_match_table = ar0132_of_ids, + }, + .probe = ar0132_probe, + .remove = ar0132_remove, + .id_table = ar0132_id, +}; + +module_i2c_driver(ar0132_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for AR0132"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); +#endif diff --git a/drivers/media/i2c/soc_camera/ar0132.h b/drivers/media/i2c/soc_camera/ar0132.h new file mode 100644 index 0000000..055841d --- /dev/null +++ b/drivers/media/i2c/soc_camera/ar0132.h @@ -0,0 +1,213 @@ +/* + * OmniVision ar0132 sensor camera wizard 1110x620@30/BGGR/BT601/12bit + * + * Copyright (C) 2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +//#define AR0132_DISPLAY_PATTERN_FIXED +//#define AR0132_DISPLAY_PATTERN_COLOR_BAR + +#define AR0132_EMBEDDED_LINE + +#define AR0132_MAX_WIDTH 1665 // (1110*3/2) +#define AR0132_MAX_HEIGHT 624 + +#define AR0132_DELAY 0xffff + +#define AR0132_MAX_ROI_DIM_X 1288 +#define AR0132_MAX_ROI_DIM_Y 968 +#define AR0132_InfoLines 4 + +#define AR0132_ROI_DIM_X 1110 // 1104 +#define AR0132_ROI_DIM_Y 620 // AR0132_MAX_HEIGHT + +#define AR0132_ROI_Y_START 0x00AE +#define AR0132_ROI_X_START 0x005C +#define AR0132_ROI_Y_END AR0132_ROI_Y_START+AR0132_ROI_DIM_Y-1 +#define AR0132_ROI_X_END AR0132_ROI_X_START+AR0132_ROI_DIM_X-1 + +#define AR0132_FrameLength_Lines 0x029E +#define AR0132_LineLength_Ticks 0x06B6 + +#define AR0132_PLL_VT_Pix_Clk_Div 0x0008 +#define AR0132_PLL_VT_Sys_Clk_Div 0x0001 +#define AR0132_PLL_Pre_Clk_Div 0x0004 +#define AR0132_PLL_Multiplier 0x003C + +#define AR0132_DigitalTest 0x2002 + +struct ar0132_reg { + u16 reg; + u16 val; +}; + +static const struct ar0132_reg ar0132_regs_wizard[] = { +{0x301A, 0x0001}, // reset +{AR0132_DELAY, 100}, +{0x301A, 0x10D8}, // Stream off and setup parallel +{0x3070, 0x0001}, +{0x3070, 0x0000}, // 1: Solid color test pattern, + // 2: Full color bar test pattern, + // 3: Fade to grey color bar test pattern, + //256: Walking 1 test pattern (12 bit) +#ifdef AR0132_DISPLAY_PATTERN_FIXED +{0x3070, 0x0001}, +{0x3072, 0x0123}, // R +{0x3074, 0x0456}, // G(GR row) +{0x3076, 0x0abc}, // B +{0x3078, 0x0def}, // G(GB row) +#endif +#ifdef AR0132_DISPLAY_PATTERN_COLOR_BAR +{0x3070, 0x0002}, +#endif +{AR0132_DELAY, 250}, +// patch begin +{0x3088, 0x8000}, +{0x3086, 0x0025}, {0x3086, 0x5050}, {0x3086, 0x2D26}, {0x3086, 0x0828}, {0x3086, 0x0D17}, {0x3086, 0x0926}, {0x3086, 0x0028}, {0x3086, 0x0526}, +{0x3086, 0xA728}, {0x3086, 0x0725}, {0x3086, 0x8080}, {0x3086, 0x2925}, {0x3086, 0x0040}, {0x3086, 0x2702}, {0x3086, 0x1616}, {0x3086, 0x2706}, +{0x3086, 0x1736}, {0x3086, 0x26A6}, {0x3086, 0x1703}, {0x3086, 0x26A4}, {0x3086, 0x171F}, {0x3086, 0x2805}, {0x3086, 0x2620}, {0x3086, 0x2804}, +{0x3086, 0x2520}, {0x3086, 0x2027}, {0x3086, 0x0017}, {0x3086, 0x1D25}, {0x3086, 0x0020}, {0x3086, 0x1F17}, {0x3086, 0x1028}, {0x3086, 0x0519}, +{0x3086, 0x1703}, {0x3086, 0x2706}, {0x3086, 0x1703}, {0x3086, 0x1741}, {0x3086, 0x2660}, {0x3086, 0x17AE}, {0x3086, 0x2500}, {0x3086, 0x9027}, +{0x3086, 0x0026}, {0x3086, 0x1828}, {0x3086, 0x002E}, {0x3086, 0x2A28}, {0x3086, 0x081C}, {0x3086, 0x1470}, {0x3086, 0x7003}, {0x3086, 0x1470}, +{0x3086, 0x7004}, {0x3086, 0x1470}, {0x3086, 0x7005}, {0x3086, 0x1470}, {0x3086, 0x7009}, {0x3086, 0x170C}, {0x3086, 0x0014}, {0x3086, 0x0020}, +{0x3086, 0x2300}, {0x3086, 0x1400}, {0x3086, 0x5003}, {0x3086, 0x1400}, {0x3086, 0x2003}, {0x3086, 0x1400}, {0x3086, 0x5022}, {0x3086, 0x0414}, +{0x3086, 0x0020}, {0x3086, 0x0414}, {0x3086, 0x0050}, {0x3086, 0x0514}, {0x3086, 0x0020}, {0x3086, 0x2405}, {0x3086, 0x1400}, {0x3086, 0x5001}, +{0x3086, 0x2550}, {0x3086, 0x502D}, {0x3086, 0x2608}, {0x3086, 0x280D}, {0x3086, 0x1709}, {0x3086, 0x2600}, {0x3086, 0x2805}, {0x3086, 0x26A7}, +{0x3086, 0x2807}, {0x3086, 0x2580}, {0x3086, 0x8029}, {0x3086, 0x2500}, {0x3086, 0x4027}, {0x3086, 0x0216}, {0x3086, 0x1627}, {0x3086, 0x0617}, +{0x3086, 0x3626}, {0x3086, 0xA617}, {0x3086, 0x0326}, {0x3086, 0xA417}, {0x3086, 0x1F28}, {0x3086, 0x0526}, {0x3086, 0x2028}, {0x3086, 0x0425}, +{0x3086, 0x2020}, {0x3086, 0x2700}, {0x3086, 0x171D}, {0x3086, 0x2500}, {0x3086, 0x2020}, {0x3086, 0x1710}, {0x3086, 0x2805}, {0x3086, 0x1A17}, +{0x3086, 0x0327}, {0x3086, 0x0617}, {0x3086, 0x0317}, {0x3086, 0x4126}, {0x3086, 0x6017}, {0x3086, 0xAE25}, {0x3086, 0x0090}, {0x3086, 0x2700}, +{0x3086, 0x2618}, {0x3086, 0x2800}, {0x3086, 0x2E2A}, {0x3086, 0x2808}, {0x3086, 0x1D05}, {0x3086, 0x1470}, {0x3086, 0x7009}, {0x3086, 0x1720}, +{0x3086, 0x1400}, {0x3086, 0x2024}, {0x3086, 0x1400}, {0x3086, 0x5002}, {0x3086, 0x2550}, {0x3086, 0x502D}, {0x3086, 0x2608}, {0x3086, 0x280D}, +{0x3086, 0x1709}, {0x3086, 0x2600}, {0x3086, 0x2805}, {0x3086, 0x26A7}, {0x3086, 0x2807}, {0x3086, 0x2580}, {0x3086, 0x8029}, {0x3086, 0x2500}, +{0x3086, 0x4027}, {0x3086, 0x0216}, {0x3086, 0x1627}, {0x3086, 0x0617}, {0x3086, 0x3626}, {0x3086, 0xA617}, {0x3086, 0x0326}, {0x3086, 0xA417}, +{0x3086, 0x1F28}, {0x3086, 0x0526}, {0x3086, 0x2028}, {0x3086, 0x0425}, {0x3086, 0x2020}, {0x3086, 0x2700}, {0x3086, 0x171D}, {0x3086, 0x2500}, +{0x3086, 0x2021}, {0x3086, 0x1710}, {0x3086, 0x2805}, {0x3086, 0x1B17}, {0x3086, 0x0327}, {0x3086, 0x0617}, {0x3086, 0x0317}, {0x3086, 0x4126}, +{0x3086, 0x6017}, {0x3086, 0xAE25}, {0x3086, 0x0090}, {0x3086, 0x2700}, {0x3086, 0x2618}, {0x3086, 0x2800}, {0x3086, 0x2E2A}, {0x3086, 0x2808}, +{0x3086, 0x1E17}, {0x3086, 0x0A05}, {0x3086, 0x1470}, {0x3086, 0x7009}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, +{0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, +{0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1616}, {0x3086, 0x1400}, {0x3086, 0x2024}, {0x3086, 0x1400}, {0x3086, 0x502B}, +{0x3086, 0x302C}, {0x3086, 0x2C2C}, {0x3086, 0x2C00}, {0x3086, 0x0225}, {0x3086, 0x5050}, {0x3086, 0x2D26}, {0x3086, 0x0828}, {0x3086, 0x0D17}, +{0x3086, 0x0926}, {0x3086, 0x0028}, {0x3086, 0x0526}, {0x3086, 0xA728}, {0x3086, 0x0725}, {0x3086, 0x8080}, {0x3086, 0x2917}, {0x3086, 0x0525}, +{0x3086, 0x0040}, {0x3086, 0x2702}, {0x3086, 0x1616}, {0x3086, 0x2706}, {0x3086, 0x1736}, {0x3086, 0x26A6}, {0x3086, 0x1703}, {0x3086, 0x26A4}, +{0x3086, 0x171F}, {0x3086, 0x2805}, {0x3086, 0x2620}, {0x3086, 0x2804}, {0x3086, 0x2520}, {0x3086, 0x2027}, {0x3086, 0x0017}, {0x3086, 0x1E25}, +{0x3086, 0x0020}, {0x3086, 0x2117}, {0x3086, 0x1028}, {0x3086, 0x051B}, {0x3086, 0x1703}, {0x3086, 0x2706}, {0x3086, 0x1703}, {0x3086, 0x1747}, +{0x3086, 0x2660}, {0x3086, 0x17AE}, {0x3086, 0x2500}, {0x3086, 0x9027}, {0x3086, 0x0026}, {0x3086, 0x1828}, {0x3086, 0x002E}, {0x3086, 0x2A28}, +{0x3086, 0x081E}, {0x3086, 0x0831}, {0x3086, 0x1440}, {0x3086, 0x4014}, {0x3086, 0x2020}, {0x3086, 0x1410}, {0x3086, 0x1034}, {0x3086, 0x1400}, +{0x3086, 0x1014}, {0x3086, 0x0020}, {0x3086, 0x1400}, {0x3086, 0x4013}, {0x3086, 0x1802}, {0x3086, 0x1470}, {0x3086, 0x7004}, {0x3086, 0x1470}, +{0x3086, 0x7003}, {0x3086, 0x1470}, {0x3086, 0x7017}, {0x3086, 0x2002}, {0x3086, 0x1400}, {0x3086, 0x2002}, {0x3086, 0x1400}, {0x3086, 0x5004}, +{0x3086, 0x1400}, {0x3086, 0x2004}, {0x3086, 0x1400}, {0x3086, 0x5022}, {0x3086, 0x0314}, {0x3086, 0x0020}, {0x3086, 0x0314}, {0x3086, 0x0050}, +{0x3086, 0x2C2C}, {0x3086, 0x2C2C}, +{0x309E, 0x0186}, +{0x309E, 0x0186}, +// patch end +{AR0132_DELAY, 250}, +{0x301A, 0x10D8}, // WR= RESET_REGISTER, 0x10D8 - stop streaming +{0x3082, 0x0028}, // Set HiDy OPERATION_MODE_CTRL(A) Requested integration time ratio (T2 to T3): 8 & (T1 t0 T2): 16 +{0x3084, 0x0028}, // Set HiDy OPERATION_MODE_CTRL(B) Requested integration time ratio (T2 to T3): 16 & (T1 t0 T2): 16 +{0x301E, 0x00C8}, // set datapedestal to 200 to avoid clipping near saturation +{0x3EDA, 0x0F03}, // Set vln_dac to 0x3 as recommended by Sergey +{0x3EDE, 0xC007}, +{0x3ED8, 0x01EF}, // Vrst_low = +1 +{0x3EE2, 0xA46B}, +{0x3EE0, 0x067D}, // enable anti eclipse and adjust setting for high conversion gain +{0x3EDC, 0x0070}, // adjust anti eclipse setting for low conversion gain +{0x3044, 0x0404}, // disable digital row noise correction and cancels TX during column correction +{0x3EE6, 0x4303}, // Helps with column noise at low light +{0x3EE4, 0xD208}, // enable analog row noise correction +{0x3ED6, 0x00BD}, +{0x3EE6, 0x8303}, // improves low light FPN +{0x30E4, 0x6372}, // ADC settings to improve noise performance +{0x30E2, 0x7253}, +{0x30E0, 0x5470}, +{0x30E6, 0xC4CC}, +{0x30E8, 0x8050}, +{AR0132_DELAY, 250}, +{0x3058, 0x003F}, // WR= BLUE_GAIN, 0x003F +{0x3014, 0}, // Fine_IT_Time(A) +{0x3002, AR0132_ROI_Y_START}, // WR= Y_ADDR_START_(A) +{0x3004, AR0132_ROI_X_START}, // WR= X_ADDR_START_(A) +{0x3006, AR0132_ROI_Y_END}, // WR= Y_ADDR_END_(A) +{0x3008, AR0132_ROI_X_END}, // WR= X_ADDR_END_(A) +{0x300A, AR0132_FrameLength_Lines}, // WR= FRAME_LENGTH_LINES_(A) +{0x3018, 0}, // Fine_IT_Time(B) +{0x308C, AR0132_ROI_Y_START}, // Y_ADDR_START_(B) +{0x308A, AR0132_ROI_X_START}, // X_ADDR_START_(B) +{0x3090, AR0132_ROI_Y_END}, // Y_ADDR_END_(B) +{0x308E, AR0132_ROI_X_END}, // X_ADDR_END_(B) +{0x30AA, AR0132_FrameLength_Lines}, // FRAME_LENGTH_LINES_(B) +{0x300C, AR0132_LineLength_Ticks}, // Line Length +{0x301A, 0x10D8}, // Disable Streaming and setup parallel +{0x31D0, 0x0001}, // Set to 12 bits +{0x3028, 0x0010}, // ROW_SPEED = 16 +{0x302A, AR0132_PLL_VT_Pix_Clk_Div}, +{0x302C, AR0132_PLL_VT_Sys_Clk_Div}, +{0x302E, AR0132_PLL_Pre_Clk_Div}, +{0x3030, AR0132_PLL_Multiplier}, +{0x3032, 0x0000}, // SCALING_MODE = 0 +{0x3040, 0xC000}, // READ_MODE = read_mode_vert_flip | read_mode_horiz_mirror +{0x3044, 0x0404}, // Dark Control = 1028 +{0x30A6, 0x0001}, // Y Odd Inc. (A) = 1 +{0x30A8, 0x0001}, // Y Odd Inc. (B) = 1 +{0x30B0, AR0132_DigitalTest}, +{AR0132_DELAY, 100}, +#ifdef AR0132_EMBEDDED_LINE +{0x3064, 0x1982}, // Embedded Data on +#else +{0x3064, 0x1802}, // Embedded Data off +#endif +{0x3100, 0x0084}, // WR= AECTRLREG, +{0x3190, 0x6BA0}, +{0x3194, 0x0E74}, +{0x3196, 0x0ED8}, +{0x3198, 0x0FA0}, +{0x319E, 0x5040}, // resetvalue +{0x31A2, 0x0FA0}, +//FrontCamera Specific Section +//Common +#ifdef AR0132_EMBEDDED_LINE +{0x3064, 0x1982}, +#else +{0x3064, 0x1802}, +#endif +{0x30B4, 0x0011}, +{0x30ba, 0x0008}, +{0x3180, 0xE000}, +{0x3182, 0x012C}, +{0x3190, 0x6BA0}, +{0x3194, 0x0E74}, +{0x3196, 0x0ED8}, +{0x3198, 0x0FA0}, +{0x319E, 0x5040}, +{0x31A2, 0x0FA0}, +//Context A:0 +{0x3012, 0x0021}, +{0x3014, 0x0000}, +{0x30A6, 0x0001}, +{0x3056, 0x0008}, +{0x3058, 0x0008}, +{0x305A, 0x0008}, +{0x305C, 0x0008}, +{0x305E, 0x0008}, +{0x3082, 0x0014}, +//Context B:0 +{0x3016, 0x007F}, +{0x3018, 0x0000}, +{0x30A8, 0x0001}, +{0x30BC, 0x0020}, +{0x30BE, 0x0020}, +{0x30C0, 0x0020}, +{0x30C2, 0x0020}, +{0x30C4, 0x0020}, +{0x3084, 0x0028}, +//not covered +{0x301E, 0x00C8}, +{0x3044, 0x0404}, +{0x31D0, 0x0001}, +{0x30B0, 0x2002}, +}; diff --git a/drivers/media/i2c/soc_camera/max9286_max9271.c b/drivers/media/i2c/soc_camera/max9286_max9271.c new file mode 100644 index 0000000..9797d24 --- /dev/null +++ b/drivers/media/i2c/soc_camera/max9286_max9271.c @@ -0,0 +1,567 @@ +/* + * MAXIM max9286-max9271 GMSL driver + * + * Copyright (C) 2015-2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "max9286_max9271.h" + +#define MAXIM_I2C_I2C_SPEED_837KHZ (0x7 << 2) /* 837kbps */ +#define MAXIM_I2C_I2C_SPEED_533KHZ (0x6 << 2) /* 533kbps */ +#define MAXIM_I2C_I2C_SPEED_339KHZ (0x5 << 2) /* 339 kbps */ +#define MAXIM_I2C_I2C_SPEED_173KHZ (0x4 << 2) /* 174kbps */ +#define MAXIM_I2C_I2C_SPEED_105KHZ (0x3 << 2) /* 105 kbps */ +#define MAXIM_I2C_I2C_SPEED_085KHZ (0x2 << 2) /* 84.7 kbps */ +#define MAXIM_I2C_I2C_SPEED_028KHZ (0x1 << 2) /* 28.3 kbps */ +#define MAXIM_I2C_I2C_SPEED MAXIM_I2C_I2C_SPEED_339KHZ + +struct max9286_max9271_priv { + struct v4l2_subdev sd[4]; + struct device_node *sd_of_node[4]; + int des_addr; + int des_quirk_addr; /* second MAX9286 on the same I2C bus */ + int links; + int links_mask; + int lanes; + int csi_rate; + const char *fsync_mode; + int fsync_period; + char pclk_rising_edge; + int gpio_resetb; + int active_low_resetb; + int timeout; + atomic_t use_count; + u32 csi2_outord; + struct i2c_client *client; + int max9271_addr_map[4]; +}; + +static int force_conf_link; + +static __init int max9286_max9271_force_conf_link(char *str) +{ + /* force configuration link */ + /* used only if robust firmware flashing required (f.e. recovery) */ + force_conf_link = 1; + return 0; +} +early_param("force_conf_link", max9286_max9271_force_conf_link); + +static void max9286_max9271_preinit(struct i2c_client *client, int addr) +{ + client->addr = addr; /* MAX9286-CAMx I2C */ + reg8_write(client, 0x0a, 0x00); /* disable reverse control for all cams */ + reg8_write(client, 0x00, 0x00); /* disable all GMSL links [0:3] */ + usleep_range(2000, 2500); /* wait 2ms after any change of reverse channel settings */ +} + +static void max9286_max9271_sensor_reset(struct i2c_client *client, int addr) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + + if (priv->gpio_resetb < 1 || priv->gpio_resetb > 5) + return; + + /* get out from sensor reset */ + client->addr = addr; /* MAX9271-CAMx I2C */ + reg8_write(client, 0x0f, (0xfe & ~BIT(priv->gpio_resetb)) | + (priv->active_low_resetb ? 0 : BIT(priv->gpio_resetb))); /* set GPIOn value to reset */ + reg8_write(client, 0x0e, 0x42 | BIT(priv->gpio_resetb)); /* set GPIOn direction output */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x0f, (0xfe & ~BIT(priv->gpio_resetb)) | + (priv->active_low_resetb ? BIT(priv->gpio_resetb) : 0)); /* set GPIOn value to un-reset */ + usleep_range(2000, 2500); /* wait 2ms */ +} + +static void max9286_max9271_postinit(struct i2c_client *client, int addr) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + + client->addr = addr; /* MAX9286 I2C */ + reg8_write(client, 0x0a, 0x00); /* disable reverse control for all cams */ + reg8_write(client, 0x00, 0xe0 | priv->links_mask); /* enable GMSL link for CAMs */ + reg8_write(client, 0x0b, priv->csi2_outord); /* CSI2 output order */ + reg8_write(client, 0x15, 0x9b); /* enable CSI output, VC is set accordingly to Link number, BIT7 magic must be set */ + reg8_write(client, 0x1b, priv->links_mask); /* enable equalizer for CAMs */ + usleep_range(5000, 5500); /* wait 2ms after any change of reverse channel settings */ +} + +static int max9286_max9271_reverse_channel_setup(struct i2c_client *client, int idx) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + u8 val = 0; + int timeout = priv->timeout; + int ret = 0; + + /* Reverse channel enable */ + client->addr = priv->des_addr; /* MAX9286-CAMx I2C */ + reg8_write(client, 0x3f, 0x4f); /* enable custom reverse channel & first pulse length */ + reg8_write(client, 0x34, 0xa2 | MAXIM_I2C_I2C_SPEED); /* enable artificial ACKs, I2C speed set */ + usleep_range(2000, 2500); /* wait 2ms after any change of reverse channel settings */ + reg8_write(client, 0x00, 0xe0 | BIT(idx)); /* enable GMSL link for CAMx */ + reg8_write(client, 0x0a, 0x11 << idx); /* enable reverse control for CAMx */ + usleep_range(2000, 2500); /* wait 2ms after any change of reverse channel settings */ + + for (;;) { + client->addr = priv->des_addr; /* MAX9286-CAMx I2C */ + reg8_write(client, 0x3b, 0x1e); /* first pulse length rise time changed from 300ns to 200ns, amplitude 100mV */ + usleep_range(2000, 2500); /* wait 2ms after any change of reverse channel settings */ + + client->addr = 0x40; /* MAX9271-CAMx I2C */ + reg8_write(client, 0x08, 0x1); /* reverse channel receiver high threshold enable */ + usleep_range(2000, 2500); /* wait 2ms after any change of reverse channel settings */ + + client->addr = priv->des_addr; /* MAX9286-CAMx I2C */ + reg8_write(client, 0x3b, 0x19); /* reverse channel increase amplitude 170mV to compensate high threshold enabled */ + usleep_range(2000, 2500); /* wait 2ms after any change of reverse channel settings */ + + client->addr = 0x40; /* MAX9271-CAMx I2C */ + reg8_write(client, 0x04, 0x43); /* wake-up, enable reverse_control/conf_link */ + usleep_range(2000, 2500); /* wait 2ms after any change of reverse channel settings */ + + client->addr = 0x40; /* MAX9271-CAMx I2C */ + reg8_read(client, 0x1e, &val); /* read max9271 ID */ + if (val == MAX9271_ID || --timeout == 0) + break; + + /* Check if already initialized (after reboot/reset ?) */ + client->addr = priv->max9271_addr_map[idx]; /* MAX9271-CAMx I2C */ + reg8_read(client, 0x1e, &val); /* read max9271 ID */ + if (val == MAX9271_ID) { + reg8_write(client, 0x04, 0x43); /* enable reverse_control/conf_link */ + usleep_range(2000, 2500); /* wait 2ms after any change of reverse channel settings */ + ret = -EADDRINUSE; + break; + } + } + + max9286_max9271_sensor_reset(client, client->addr); /* sensor reset */ + + if (!timeout) { + ret = -ETIMEDOUT; + goto out; + } + + priv->links_mask |= BIT(idx); + priv->csi2_outord &= ~(0x3 << (idx * 2)); + priv->csi2_outord |= ((hweight8(priv->links_mask) - 1) << (idx * 2)); + +out: + dev_info(&client->dev, "link%d MAX9271 %sat 0x%x %s\n", idx, + ret == -EADDRINUSE ? "already " : "", priv->max9271_addr_map[idx], + ret == -ETIMEDOUT ? "not found: timeout GMSL link establish" : ""); + + return ret; +} + +static void max9286_max9271_initial_setup(struct i2c_client *client) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + + /* Initial setup */ + client->addr = priv->des_addr; /* MAX9286-CAMx I2C */ + reg8_write(client, 0x15, 0x13); /* disable CSI output, VC is set accordingly to Link number */ + reg8_write(client, 0x69, 0x0f); /* mask CSI forwarding from all links */ + switch (priv->lanes) { + case 1: + reg8_write(client, 0x12, 0x33); /* enable CSI-2 Lane D0, DBL mode, YUV422 8-bit*/ + break; + case 2: + reg8_write(client, 0x12, 0x73); /* enable CSI-2 Lanes D0,D1, DBL mode, YUV422 8-bit*/ + break; + case 3: + reg8_write(client, 0x12, 0xd3); /* enable CSI-2 Lanes D0-D2, DBL mode, YUV422 8-bit*/ + break; + case 4: + reg8_write(client, 0x12, 0xf3); /* enable CSI-2 Lanes D0-D3, DBL mode, YUV422 8-bit*/ + break; + default: + dev_err(&client->dev, "CSI2 lanes number is invalid (%d)\n", priv->lanes); + } + + if (strcmp(priv->fsync_mode, "manual") == 0) { + reg8_write(client, 0x06, priv->fsync_period & 0xff); + reg8_write(client, 0x07, (priv->fsync_period >> 8) & 0xff); + reg8_write(client, 0x08, priv->fsync_period >> 16); + reg8_write(client, 0x01, 0x00); /* manual: FRAMESYNC set manually via [0x06:0x08] regs */ + } else if (strcmp(priv->fsync_mode, "automatic") == 0) { + reg8_write(client, 0x01, 0x02); /* automatic: FRAMESYNC taken from the slowest Link */ + } else if (strcmp(priv->fsync_mode, "semi-automatic") == 0) { + reg8_write(client, 0x01, 0x01); /* semi-automatic: FRAMESYNC taken from the slowest Link */ + } else if (strcmp(priv->fsync_mode, "external") == 0) { + reg8_write(client, 0x01, 0xc0); /* ECU (aka MCU) based FrameSync using GPI-to-GPO */ + } + + reg8_write(client, 0x63, 0); /* disable overlap window */ + reg8_write(client, 0x64, 0); + reg8_write(client, 0x0c, 0x89); /* enable HS/VS encoding, use D14/15 for HS/VS, invert VS */ +} + +static void max9286_max9271_gmsl_link_setup(struct i2c_client *client, int idx) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + + /* GMSL setup */ + client->addr = 0x40; /* MAX9271-CAMx I2C */ + reg8_write(client, 0x0d, 0x22 | MAXIM_I2C_I2C_SPEED); /* disable artificial ACK, I2C speed set */ + reg8_write(client, 0x07, 0x84 | (priv->pclk_rising_edge ? 0 : 0x10)); /* RAW/YUV, PCLK edge, HS/VS encoding enabled */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x02, 0xff); /* spread spectrum +-4%, pclk range automatic, Gbps automatic */ + usleep_range(2000, 2500); /* wait 2ms */ + + client->addr = priv->des_addr; /* MAX9286-CAMx I2C */ + reg8_write(client, 0x34, 0x22 | MAXIM_I2C_I2C_SPEED); /* disable artificial ACK, I2C speed set */ + usleep_range(2000, 2500); /* wait 2ms */ + + /* I2C translator setup */ + client->addr = 0x40; /* MAX9271-CAMx I2C */ +// reg8_write(client, 0x09, maxim_map[2][idx] << 1); /* SENSOR I2C translated - must be set by sensor driver */ +// reg8_write(client, 0x0A, 0x30 << 1); /* SENSOR I2C native - must be set by sensor driver */ + reg8_write(client, 0x0B, BROADCAST << 1); /* broadcast I2C */ + reg8_write(client, 0x0C, priv->max9271_addr_map[idx] << 1); /* MAX9271-CAMx I2C new */ + /* I2C addresse change */ + reg8_write(client, 0x01, priv->des_addr << 1); /* MAX9286 I2C */ + reg8_write(client, 0x00, priv->max9271_addr_map[idx] << 1); /* MAX9271-CAM0 I2C new */ + usleep_range(2000, 2500); /* wait 2ms */ + /* put MAX9271 in configuration link state */ + client->addr = priv->max9271_addr_map[idx]; /* MAX9271-CAMx I2C new */ + reg8_write(client, 0x04, 0x43); /* enable reverse_control/conf_link */ + usleep_range(2000, 2500); /* wait 2ms */ +#ifdef MAXIM_DUMP + client->addr = priv->des_addr; /* MAX9286-CAMx I2C */ + maxim_max927x_dump_regs(client); + client->addr = priv->max9271_addr_map[idx]; /* MAX9271-CAMx I2C new */ + maxim_max927x_dump_regs(client); +#endif +} + +static int max9286_max9271_initialize(struct i2c_client *client) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + int idx, ret; + + dev_info(&client->dev, "LINKs=%d, LANES=%d, FSYNC mode=%s, FSYNC period=%d, PCLK edge=%s\n", + priv->links, priv->lanes, priv->fsync_mode, priv->fsync_period, + priv->pclk_rising_edge ? "rising" : "falling"); + + if (priv->des_quirk_addr) + max9286_max9271_preinit(client, priv->des_quirk_addr); + + max9286_max9271_preinit(client, priv->des_addr); + max9286_max9271_initial_setup(client); + + for (idx = 0; idx < priv->links; idx++) { + ret = max9286_max9271_reverse_channel_setup(client, idx); + if (ret) + continue; + max9286_max9271_gmsl_link_setup(client, idx); + } + + max9286_max9271_postinit(client, priv->des_addr); + + client->addr = priv->des_addr; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int max9286_max9271_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct max9286_max9271_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + int ret; + u8 val = 0; + + ret = reg8_read(client, (u8)reg->reg, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = sizeof(u8); + + return 0; +} + +static int max9286_max9271_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct max9286_max9271_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + + return reg8_write(client, (u8)reg->reg, (u8)reg->val); +} +#endif + +static int max9286_max9271_s_power(struct v4l2_subdev *sd, int on) +{ + struct max9286_max9271_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + + if (on) { + if (atomic_inc_return(&priv->use_count) == 1) + reg8_write(client, 0x69, priv->links_mask ^ 0x0f); /* unmask CSI forwarding from detected links */ + } else { + if (atomic_dec_return(&priv->use_count) == 0) + reg8_write(client, 0x69, 0x0f); /* mask CSI forwarding from all links */ + } + + return 0; +} + +static int max9286_max9271_registered_async(struct v4l2_subdev *sd) +{ + struct max9286_max9271_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + int idx, tmp_addr; + + /* switch to GMSL serial_link for streaming video */ + tmp_addr = client->addr; + idx = sd->grp_id; + + client->addr = priv->des_addr; /* MAX9286 I2C */ + reg8_write(client, 0x0a, 0x11 << idx); /* enable reverse/forward control for CAMx */ + + client->addr = priv->max9271_addr_map[idx]; /* MAX9271-CAMx */ + reg8_write(client, 0x04, force_conf_link ? 0x43 : 0x83); /* enable reverse_control/serial_link */ + usleep_range(2000, 2500); /* wait 2ms after changing reverse_control */ + + client->addr = priv->des_addr; /* MAX9286 I2C */ + reg8_write(client, 0x0a, (priv->links_mask << 4) | priv->links_mask); /* enable reverse/forward control for all CAMs */ + + client->addr = tmp_addr; + + return 0; +} + +static struct v4l2_subdev_core_ops max9286_max9271_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = max9286_max9271_g_register, + .s_register = max9286_max9271_s_register, +#endif + .s_power = max9286_max9271_s_power, + .registered_async = max9286_max9271_registered_async, +}; + +static struct v4l2_subdev_ops max9286_max9271_subdev_ops = { + .core = &max9286_max9271_subdev_core_ops, +}; + +static int max9286_max9271_parse_dt(struct i2c_client *client) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + struct device_node *np = client->dev.of_node; + struct device_node *endpoint = NULL; + struct property *prop; + int err, pwen, i; + int sensor_delay, gpio0 = 1, gpio1 = 1; + char fsync_mode_default[20] = "manual"; /* manual, automatic, semi-automatic, external */ + u8 val = 0; + + if (of_property_read_u32(np, "maxim,links", &priv->links)) + priv->links = 4; + + if (of_property_read_u32(np, "maxim,lanes", &priv->lanes)) + priv->lanes = 4; + + pwen = of_get_gpio(np, 0); + if (pwen > 0) { + err = gpio_request_one(pwen, GPIOF_OUT_INIT_HIGH, dev_name(&client->dev)); + if (err) + dev_err(&client->dev, "cannot request PWEN gpio %d: %d\n", pwen, err); + } + + mdelay(250); + + reg8_read(client, 0x1e, &val); /* read max9286 ID */ + if (val != MAX9286_ID) { + prop = of_find_property(np, "reg", NULL); + if (prop) + of_remove_property(np, prop); + return -ENODEV; + } + + if (!of_property_read_u32(np, "maxim,gpio0", &gpio0) || + !of_property_read_u32(np, "maxim,gpio1", &gpio1)) + reg8_write(client, 0x0f, 0x08 | (gpio1 << 1) | gpio0); + + if (of_property_read_u32(np, "maxim,resetb-gpio", &priv->gpio_resetb)) { + priv->gpio_resetb = -1; + } else { + if (of_property_read_bool(np, "maxim,resetb-active-high")) + priv->active_low_resetb = false; + else + priv->active_low_resetb = true; + } + + if (!of_property_read_u32(np, "maxim,sensor_delay", &sensor_delay)) + mdelay(sensor_delay); + + if (of_property_read_string(np, "maxim,fsync-mode", &priv->fsync_mode)) + priv->fsync_mode = fsync_mode_default; + + if (of_property_read_u32(np, "maxim,fsync-period", &priv->fsync_period)) + priv->fsync_period = 3200000; /* 96MHz/30fps */ + priv->pclk_rising_edge = true; + if (of_property_read_bool(np, "maxim,pclk-falling-edge")) + priv->pclk_rising_edge = false; + if (of_property_read_u32(np, "maxim,timeout", &priv->timeout)) + priv->timeout = 100; + if (of_property_read_u32(np, "maxim,i2c-quirk", &priv->des_quirk_addr)) + priv->des_quirk_addr = 0; + + for (i = 0; i < priv->links; i++) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + if (!endpoint) + break; + + of_node_put(endpoint); + + if (of_property_read_u32(endpoint, "max9271-addr", &priv->max9271_addr_map[i])) { + dev_err(&client->dev, "max9271-addr not set\n"); + return -EINVAL; + } + + priv->sd_of_node[i] = endpoint; + } + + return 0; +} + +static void max9286_max9271_setup_remote_endpoint(struct i2c_client *client) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + struct device_node *np = client->dev.of_node; + struct device_node *endpoint = NULL, *rendpoint = NULL; + int i; + struct property *csi_rate_prop, *dvp_order_prop; + + for (i = 0; ; i++) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + if (!endpoint) + break; + + of_node_put(endpoint); + + rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0); + if (!rendpoint) + continue; + + csi_rate_prop = of_find_property(endpoint, "csi-rate", NULL); + if (csi_rate_prop) { + /* CSI2_RATE = PCLK*sizeof(YUV8)*links/lanes */ + priv->csi_rate = cpu_to_be32(100 * 8 * hweight8(priv->links_mask) / priv->lanes); + csi_rate_prop->value = &priv->csi_rate; + of_update_property(rendpoint, csi_rate_prop); + } + + dvp_order_prop = of_find_property(endpoint, "dvp-order", NULL); + if (dvp_order_prop) + of_update_property(rendpoint, dvp_order_prop); + } +} + +static int max9286_max9271_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct max9286_max9271_priv *priv; + int err, i; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(client, priv); + priv->des_addr = client->addr; + priv->client = client; + atomic_set(&priv->use_count, 0); + priv->csi2_outord = 0xff; + + err = max9286_max9271_parse_dt(client); + if (err) + goto out; + + err = max9286_max9271_initialize(client); + if (err < 0) + goto out; + + max9286_max9271_setup_remote_endpoint(client); + + for (i = 0; i < 4; i++) { + v4l2_subdev_init(&priv->sd[i], &max9286_max9271_subdev_ops); + priv->sd[i].owner = client->dev.driver->owner; + priv->sd[i].dev = &client->dev; + priv->sd[i].grp_id = i; + v4l2_set_subdevdata(&priv->sd[i], priv); + priv->sd[i].of_node = priv->sd_of_node[i]; + + snprintf(priv->sd[i].name, V4L2_SUBDEV_NAME_SIZE, "%s.%d %d-%04x", + client->dev.driver->name, i, i2c_adapter_id(client->adapter), + client->addr); + + err = v4l2_async_register_subdev(&priv->sd[i]); + if (err < 0) + goto out; + } + +out: + return err; +} + +static int max9286_max9271_remove(struct i2c_client *client) +{ + struct max9286_max9271_priv *priv = i2c_get_clientdata(client); + int i; + + for (i = 0; i < 4; i++) { + v4l2_async_unregister_subdev(&priv->sd[i]); + v4l2_device_unregister_subdev(&priv->sd[i]); + } + + return 0; +} + +static const struct of_device_id max9286_max9271_dt_ids[] = { + { .compatible = "maxim,max9286-max9271" }, + {}, +}; +MODULE_DEVICE_TABLE(of, max9286_max9271_dt_ids); + +static const struct i2c_device_id max9286_max9271_id[] = { + { "max9286_max9271", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9286_max9271_id); + +static struct i2c_driver max9286_max9271_i2c_driver = { + .driver = { + .name = "max9286_max9271", + .of_match_table = of_match_ptr(max9286_max9271_dt_ids), + }, + .probe = max9286_max9271_probe, + .remove = max9286_max9271_remove, + .id_table = max9286_max9271_id, +}; + +module_i2c_driver(max9286_max9271_i2c_driver); + +MODULE_DESCRIPTION("GMSL driver for MAX9286-MAX9271"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/soc_camera/max9286_max9271.h b/drivers/media/i2c/soc_camera/max9286_max9271.h new file mode 100644 index 0000000..0016f28a --- /dev/null +++ b/drivers/media/i2c/soc_camera/max9286_max9271.h @@ -0,0 +1,243 @@ +/* + * MAXIM max9286-max9271 GMSL driver include file + * + * Copyright (C) 2015-2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _MAX9286_MAX9271_H +#define _MAX9286_MAX9271_H + +//#define DEBUG +#ifdef DEBUG +//#define WRITE_VERIFY +#define MAXIM_DUMP +#undef dev_dbg +#define dev_dbg dev_info +#endif + +#define REG8_NUM_RETRIES 1 /* number of read/write retries */ +#define REG16_NUM_RETRIES 10 /* number of read/write retries */ +#define MAX9271_ID 0x9 +#define MAX9286_ID 0x40 +#define BROADCAST 0x6f + +static inline int reg8_read(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret, retries; + + for (retries = REG8_NUM_RETRIES; retries; retries--) { + ret = i2c_smbus_read_byte_data(client, reg); + if (!(ret < 0)) + break; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "read fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } else { + *val = ret; + } + + return ret < 0 ? ret : 0; +} + +static inline int reg8_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret, retries; + + for (retries = REG8_NUM_RETRIES; retries; retries--) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (!(ret < 0)) + break; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "write fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } else { +#ifdef WRITE_VERIFY + u8 val2; + reg8_read(client, reg, &val2); + if (val != val2) + dev_err(&client->dev, + "write verify mismatch: chip 0x%x reg=0x%x " + "0x%x->0x%x\n", client->addr, reg, val, val2); +#endif + } + + return ret < 0 ? ret : 0; +} + +static inline int reg16_read(struct i2c_client *client, u16 reg, u8 *val) +{ + int ret, retries; + u8 buf[2] = {reg >> 8, reg & 0xff}; + + for (retries = REG16_NUM_RETRIES; retries; retries--) { + ret = i2c_master_send(client, buf, 2); + if (ret == 2) { + ret = i2c_master_recv(client, buf, 1); + if (ret == 1) + break; + } + } + + if (ret < 0) { + dev_dbg(&client->dev, + "read fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } else { + *val = buf[0]; + } + + return ret < 0 ? ret : 0; +} + +static inline int reg16_write(struct i2c_client *client, u16 reg, u8 val) +{ + int ret, retries; + u8 buf[3] = {reg >> 8, reg & 0xff, val}; + + for (retries = REG16_NUM_RETRIES; retries; retries--) { + ret = i2c_master_send(client, buf, 3); + if (ret == 3) + break; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "write fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } else { +#ifdef WRITE_VERIFY + u8 val2; + reg16_read(client, reg, &val2); + if (val != val2) + dev_err(&client->dev, + "write verify mismatch: chip 0x%x reg=0x%x " + "0x%x->0x%x\n", client->addr, reg, val, val2); +#endif + } + + return ret < 0 ? ret : 0; +} + + +static inline int reg16_read16(struct i2c_client *client, u16 reg, u16 *val) +{ + int ret, retries; + u8 buf[2] = {reg >> 8, reg & 0xff}; + + for (retries = REG8_NUM_RETRIES; retries; retries--) { + ret = i2c_master_send(client, buf, 2); + if (ret == 2) { + ret = i2c_master_recv(client, buf, 2); + if (ret == 2) + break; + } + } + + if (ret < 0) { + dev_err(&client->dev, + "read fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } else { + *val = ((u16)buf[0] << 8) | buf[1]; + } + + return ret < 0 ? ret : 0; +} + +static inline int reg16_write16(struct i2c_client *client, u16 reg, u16 val) +{ + int ret, retries; + u8 buf[4] = {reg >> 8, reg & 0xff, val >> 8, val & 0xff}; + + for (retries = REG8_NUM_RETRIES; retries; retries--) { + ret = i2c_master_send(client, buf, 4); + if (ret == 4) + break; + } + + if (ret < 0) { + dev_err(&client->dev, + "write fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } + + return ret < 0 ? ret : 0; +} + + +#ifdef MAXIM_DUMP +static void maxim_ovsensor_dump_regs(struct i2c_client *client) +{ + int ret, i; + u8 val = 0; + u16 regs[] = {0x300a, 0x300b, 0x300c}; + + dev_dbg(&client->dev, "dump regs 0x%x\n", client->addr); + + for (i = 0; i < sizeof(regs) / 2; i++) { + ret = reg16_read(client, regs[i], &val); + if (ret < 0) + dev_err(&client->dev, + "read fail: chip 0x%x register 0x%02x: %d\n", + client->addr, regs[i], ret); + printk("0x%02x -> 0x%x\n", regs[i], val); + } +} + +static void maxim_ov10635_dump_format_regs(struct i2c_client *client) +{ + int ret, i; + u8 val; + u16 regs[] = {0x3003, 0x3004, 0x4300, + 0x4605, 0x3621, 0x3702, 0x3703, 0x3704, + 0x3802, 0x3803, 0x3806, 0x3807, 0x3808, 0x3809, 0x380a, + 0x380b, 0x380c, 0x380d, 0x380e, 0x380f, + 0x4606, 0x4607, 0x460a, 0x460b, + 0xc488, 0xc489, 0xc48a, 0xc48b, + 0xc4cc, 0xc4cd, 0xc4ce, 0xc4cf, 0xc512, 0xc513, + 0xc518, 0xc519, 0xc51a, 0xc51b, + }; + + dev_dbg(&client->dev, "dump regs 0x%x\n", client->addr); + + for (i = 0; i < sizeof(regs) / 2; i++) { + ret = reg16_read(client, regs[i], &val); + if (ret < 0) + dev_err(&client->dev, + "read fail: chip 0x%x register 0x%02x: %d\n", + client->addr, regs[i], ret); + printk("0x%02x -> 0x%x\n", regs[i], val); + } +} + +static void maxim_max927x_dump_regs(struct i2c_client *client) +{ + int ret; + u8 reg; + + dev_dbg(&client->dev, "dump regs 0x%x\n", client->addr); + + for (reg = 0; reg < 0x20; reg++) { + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + dev_err(&client->dev, + "read fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + printk("0x%02x ", ret); + if (((reg + 1) % 0x10) == 0) + printk("\n"); + } +} +#endif /* MAXIM_DUMP */ +#endif /* _MAX9286_MAX9271_H */ diff --git a/drivers/media/i2c/soc_camera/ov10635.c b/drivers/media/i2c/soc_camera/ov10635.c new file mode 100644 index 0000000..45169de --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov10635.c @@ -0,0 +1,759 @@ +/* + * OmniVision ov10635 sensor camera driver + * + * Copyright (C) 2015-2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "max9286_max9271.h" +#include "ov10635.h" + +#define OV10635_I2C_ADDR 0x30 + +#define OV10635_PID 0x300a +#define OV10635_VER 0x300b +#define OV10635_VERSION_REG 0xa635 +#define OV10635_VERSION(pid, ver) (((pid) << 8) | ((ver) & 0xff)) + +struct ov10635_priv { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct media_pad pad; + struct v4l2_rect rect; + int subsampling; + int fps_denominator; + int init_complete; + u8 id[6]; + int dvp_order; + /* serializers */ + int max9286_addr; + int max9271_addr; + int ti964_addr; + int ti954_addr; + int ti9x3_addr; + int port; + int gpio_resetb; + int gpio_fsin; +}; + +static inline struct ov10635_priv *to_ov10635(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov10635_priv, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct ov10635_priv, hdl)->sd; +} + +static void ov10635_s_port(struct i2c_client *client, int fwd_en) +{ + struct ov10635_priv *priv = to_ov10635(client); + int tmp_addr; + + if (priv->max9286_addr) { + tmp_addr = client->addr; + client->addr = priv->max9286_addr; /* Deserializer I2C address */ + reg8_write(client, 0x0a, fwd_en ? 0x11 << priv->port : 0); /* Enable/disable reverse/forward control for this port */ + client->addr = tmp_addr; + }; +} + +static int ov10635_set_regs(struct i2c_client *client, + const struct ov10635_reg *regs, int nr_regs) +{ + int i; + + for (i = 0; i < nr_regs; i++) { + if (reg16_write(client, regs[i].reg, regs[i].val)) { + usleep_range(100, 150); /* wait 100ns */ + reg16_write(client, regs[i].reg, regs[i].val); + } + } + + return 0; +} + +static int ov10635_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +static int ov10635_set_window(struct v4l2_subdev *sd, int subsampling) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + + /* disable clocks */ + reg16_write(client, 0x302e, 0x00); + reg16_write(client, 0x301b, 0xff); + reg16_write(client, 0x301c, 0xff); + reg16_write(client, 0x301a, 0xff); + + /* setup resolution */ + reg16_write(client, 0x3808, priv->rect.width >> 8); + reg16_write(client, 0x3809, priv->rect.width & 0xff); + reg16_write(client, 0x380a, priv->rect.height >> 8); + reg16_write(client, 0x380b, priv->rect.height & 0xff); + + /* enable/disable subsampling */ + reg16_write(client, 0x5005, subsampling ? 0x89 : 0x08); + reg16_write(client, 0x3007, subsampling ? 0x02 : 0x01); + reg16_write(client, 0x4004, subsampling ? 0x02 : 0x04); + +#if 0 /* This is implemented in VIN via SOC_CAMERA layer, hence skip */ + /* horiz crop start */ + reg16_write(client, 0x3800, priv->rect.left >> 8); + reg16_write(client, 0x3801, priv->rect.left & 0xff); + /* horiz crop end */ + reg16_write(client, 0x3804, (priv->rect.left + priv->rect.width + 1) >> 8); + reg16_write(client, 0x3805, (priv->rect.left + priv->rect.width + 1) & 0xff); + /* vert crop start */ + reg16_write(client, 0x3802, priv->rect.top >> 8); + reg16_write(client, 0x3803, priv->rect.top & 0xff); + /* vert crop end */ + reg16_write(client, 0x3806, (priv->rect.top + priv->rect.height + 1) >> 8); + reg16_write(client, 0x3807, (priv->rect.top + priv->rect.height + 1) & 0xff); +#endif + /* enable clocks */ + reg16_write(client, 0x301b, 0xf0); + reg16_write(client, 0x301c, 0xf0); + reg16_write(client, 0x301a, 0xf0); + reg16_write(client, 0x302e, 0x01); + + return 0; +}; + +static int ov10635_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + + if (format->pad) + return -EINVAL; + + mf->width = priv->rect.width; + mf->height = priv->rect.height; + mf->code = MEDIA_BUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov10635_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + + mf->code = MEDIA_BUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_NONE; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + cfg->try_fmt = *mf; + + return 0; +} + +static int ov10635_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUYV8_2X8; + + return 0; +} + +static int ov10635_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + + memcpy(edid->edid, priv->id, 6); + + edid->edid[6] = 0xff; + edid->edid[7] = client->addr; + edid->edid[8] = OV10635_VERSION_REG >> 8; + edid->edid[9] = OV10635_VERSION_REG & 0xff; + + return 0; +} + +static int ov10635_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_rect *rect = &sel->r; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + int subsampling = 0; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + rect->left = ALIGN(rect->left, 2); + rect->top = ALIGN(rect->top, 2); + rect->width = ALIGN(rect->width, 2); + rect->height = ALIGN(rect->height, 2); + + if ((rect->left + rect->width > OV10635_MAX_WIDTH) || + (rect->top + rect->height > OV10635_MAX_HEIGHT)) + *rect = priv->rect; + + if (rect->width == OV10635_MAX_WIDTH / 2 && + rect->height == OV10635_MAX_HEIGHT / 2) + subsampling = 1; + + priv->rect.left = rect->left; + priv->rect.top = rect->top; + priv->rect.width = rect->width; + priv->rect.height = rect->height; + + /* change window only for subsampling, crop is done by VIN */ + if (subsampling != priv->subsampling) { + ov10635_set_window(sd, subsampling); + priv->subsampling = subsampling; + } + + return 0; +} + +static int ov10635_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = OV10635_MAX_WIDTH; + sel->r.height = OV10635_MAX_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = OV10635_MAX_WIDTH; + sel->r.height = OV10635_MAX_HEIGHT; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = priv->rect; + return 0; + default: + return -EINVAL; + } +} + +static int ov10635_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + cfg->flags = V4L2_MBUS_CSI2_1_LANE | V4L2_MBUS_CSI2_CHANNEL_0 | + V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + cfg->type = V4L2_MBUS_CSI2; + + return 0; +} + +static int ov10635_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + struct v4l2_captureparm *cp = &parms->parm.capture; + + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + memset(cp, 0, sizeof(struct v4l2_captureparm)); + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = 1; + cp->timeperframe.denominator = priv->fps_denominator; + + return 0; +} + +static int ov10635_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + struct v4l2_captureparm *cp = &parms->parm.capture; + int ret = 0; + + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (cp->extendedmode != 0) + return -EINVAL; + + if (priv->fps_denominator != cp->timeperframe.denominator) { + switch (cp->timeperframe.denominator) { + case 5: + ret = ov10635_set_regs(client, ov10635_regs_5fps, + ARRAY_SIZE(ov10635_regs_5fps)); + break; + case 10: + ret = ov10635_set_regs(client, ov10635_regs_10fps, + ARRAY_SIZE(ov10635_regs_10fps)); + break; + case 15: + ret = ov10635_set_regs(client, ov10635_regs_15fps, + ARRAY_SIZE(ov10635_regs_15fps)); + break; + case 30: + ret = ov10635_set_regs(client, ov10635_regs_30fps, + ARRAY_SIZE(ov10635_regs_30fps)); + break; + default: + ret = -EINVAL; + goto out; + } + + priv->fps_denominator = cp->timeperframe.denominator; + } + +out: + return ret; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov10635_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val = 0; + + ret = reg16_read(client, (u16)reg->reg, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = sizeof(u16); + + return 0; +} + +static int ov10635_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return reg16_write(client, (u16)reg->reg, (u8)reg->val); +} +#endif + +static struct v4l2_subdev_core_ops ov10635_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov10635_g_register, + .s_register = ov10635_s_register, +#endif +}; + +static int ov10635_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + int ret = -EINVAL; + u8 val = 0; + + if (!priv->init_complete) + return 0; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + /* AEC/AGC target */ + ret = reg16_write(client, 0xc46a, ctrl->val); + break; + case V4L2_CID_CONTRAST: + udelay(100); + ret = ov10635_set_regs(client, &ov10635_regs_contrast[ctrl->val][0], 18); + break; + case V4L2_CID_SATURATION: + ret = reg16_write(client, 0xc316, ctrl->val); + break; + case V4L2_CID_HUE: + /* CMX ? */ + ret = 0; + break; + case V4L2_CID_GAMMA: + ret = reg16_write(client, 0xc4be, ctrl->val >> 8); + ret |= reg16_write(client, 0xc4bf, ctrl->val & 0xff); + break; + case V4L2_CID_AUTOGAIN: + /* automatic gain/exposure */ + ret = reg16_write(client, 0x56d0, !ctrl->val); + break; + case V4L2_CID_GAIN: + /* manual gain */ + ret = reg16_write(client, 0x3504, 0); + ret |= reg16_write(client, 0x56d1, ctrl->val >> 8); + ret |= reg16_write(client, 0x56d2, ctrl->val & 0xff); + ret |= reg16_write(client, 0x3504, 1); /* validate gain */ + break; + case V4L2_CID_EXPOSURE: + /* manual exposure */ + ret = reg16_write(client, 0x3504, 0); + ret |= reg16_write(client, 0x56d5, ctrl->val >> 8); + ret |= reg16_write(client, 0x56d6, ctrl->val & 0xff); + ret |= reg16_write(client, 0x3504, 1); /* validate exposure */ + break; + case V4L2_CID_HFLIP: + ret = reg16_read(client, 0x381d, &val); + if (ret < 0) + goto out; + if (ctrl->val) + val |= 0x3; + else + val &= ~0x3; + ret = reg16_write(client, 0x381d, val); + break; + case V4L2_CID_VFLIP: + ret = reg16_read(client, 0x381c, &val); + if (ctrl->val) + val |= 0xc0; + else + val &= ~0xc0; + ret = reg16_write(client, 0x381c, val); + break; + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + ret = 0; + break; + } + +out: + return ret; +} + +static const struct v4l2_ctrl_ops ov10635_ctrl_ops = { + .s_ctrl = ov10635_s_ctrl, +}; + +static struct v4l2_subdev_video_ops ov10635_video_ops = { + .s_stream = ov10635_s_stream, + .g_mbus_config = ov10635_g_mbus_config, + .g_parm = ov10635_g_parm, + .s_parm = ov10635_s_parm, +}; + +static const struct v4l2_subdev_pad_ops ov10635_subdev_pad_ops = { + .get_edid = ov10635_get_edid, + .enum_mbus_code = ov10635_enum_mbus_code, + .get_selection = ov10635_get_selection, + .set_selection = ov10635_set_selection, + .get_fmt = ov10635_get_fmt, + .set_fmt = ov10635_set_fmt, +}; + +static struct v4l2_subdev_ops ov10635_subdev_ops = { + .core = &ov10635_core_ops, + .video = &ov10635_video_ops, + .pad = &ov10635_subdev_pad_ops, +}; + +static void ov10635_otp_id_read(struct i2c_client *client) +{ + struct ov10635_priv *priv = to_ov10635(client); + int i; + + /* read camera id from OTP memory */ + reg16_write(client, 0x3d10, 1); + + usleep_range(15000, 16000); /* wait 15ms */ + + for (i = 0; i < 6; i++) + reg16_read(client, 0x3d00 + i, &priv->id[i]); +} + +static ssize_t ov10635_otp_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(to_i2c_client(dev)); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov10635_priv *priv = to_ov10635(client); + + return snprintf(buf, 32, "%02x:%02x:%02x:%02x:%02x:%02x\n", + priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]); +} + +static DEVICE_ATTR(otp_id_ov10635, S_IRUGO, ov10635_otp_id_show, NULL); + +static int ov10635_initialize(struct i2c_client *client) +{ + struct ov10635_priv *priv = to_ov10635(client); + u8 pid = 0, ver = 0; + int ret = 0; + + ov10635_s_port(client, 1); + + /* check and show product ID and manufacturer ID */ + reg16_read(client, OV10635_PID, &pid); + reg16_read(client, OV10635_VER, &ver); + + if (OV10635_VERSION(pid, ver) != OV10635_VERSION_REG) { + dev_dbg(&client->dev, "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto out; + } + + /* s/w reset sensor */ + reg16_write(client, 0x103, 0x1); + udelay(100); + /* Program wizard registers */ + ov10635_set_regs(client, ov10635_regs_wizard, ARRAY_SIZE(ov10635_regs_wizard)); + /* Set DVP bit swap */ + reg16_write(client, 0x4709, priv->dvp_order << 4); + /* Read OTP IDs */ + ov10635_otp_id_read(client); + + dev_info(&client->dev, "ov10635 Product ID %x Manufacturer ID %x OTP_ID %02x:%02x:%02x:%02x:%02x:%02x\n", + pid, ver, priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]); + +out: + ov10635_s_port(client, 0); + + return ret; +} + +static int ov10635_parse_dt(struct device_node *np, struct ov10635_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int i; + struct device_node *endpoint = NULL, *rendpoint = NULL; + int tmp_addr = 0; + + for (i = 0; ; i++) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + if (!endpoint) + break; + + of_node_put(endpoint); + + of_property_read_u32(endpoint, "dvp-order", &priv->dvp_order); + + rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0); + if (!rendpoint) + continue; + + if (!of_property_read_u32(rendpoint, "max9271-addr", &priv->max9271_addr) && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->max9286_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + + if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) && + !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti964-ti9x3") && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti964_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + + if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) && + !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti954-ti9x3") && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti954_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + } + + if (!priv->max9286_addr && !priv->ti964_addr && !priv->ti954_addr) { + dev_err(&client->dev, "deserializer does not present\n"); + return -EINVAL; + } + + ov10635_s_port(client, 1); + + /* setup I2C translator address */ + tmp_addr = client->addr; + if (priv->max9286_addr) { + client->addr = priv->max9271_addr; /* Serializer I2C address */ + + reg8_write(client, 0x09, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x0A, OV10635_I2C_ADDR << 1); /* Sensor native I2C address */ + usleep_range(2000, 2500); /* wait 2ms */ + }; + + if (priv->ti964_addr) { + client->addr = priv->ti964_addr; /* Deserializer I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x65, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x5d, OV10635_I2C_ADDR << 1); /* Sensor native I2C address */ + + reg8_write(client, 0x6e, 0xa9); /* GPIO0 - resetb, GPIO1 - fsin */ + } + + if (priv->ti954_addr) { + client->addr = priv->ti954_addr; /* Deserializer I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x65, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x5d, OV10635_I2C_ADDR << 1); /* Sensor native I2C address */ + + reg8_write(client, 0x6e, 0xa9); /* GPIO0 - resetb, GPIO1 - fsin */ + } + client->addr = tmp_addr; + + udelay(100); + + return 0; +} + +static int ov10635_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov10635_priv *priv; + struct v4l2_ctrl *ctrl; + int ret; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + v4l2_i2c_subdev_init(&priv->sd, client, &ov10635_subdev_ops); + priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + priv->rect.left = 0; + priv->rect.top = 0; + priv->rect.width = OV10635_MAX_WIDTH; + priv->rect.height = OV10635_MAX_HEIGHT; + priv->fps_denominator = 30; + + v4l2_ctrl_handler_init(&priv->hdl, 4); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 0xff, 1, 0x30); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_CONTRAST, 0, 4, 1, 2); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_SATURATION, 0, 0xff, 1, 0xff); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_HUE, 0, 255, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_GAMMA, 0, 0xffff, 1, 0x233); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_GAIN, 0, 0x3ff, 1, 0x10); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0xffff, 1, 0x80); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + ctrl = v4l2_ctrl_new_std(&priv->hdl, &ov10635_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 9); + if (ctrl) + ctrl->flags &= ~V4L2_CTRL_FLAG_READ_ONLY; + priv->sd.ctrl_handler = &priv->hdl; + + ret = priv->hdl.error; + if (ret) + goto cleanup; + + v4l2_ctrl_handler_setup(&priv->hdl); + + priv->pad.flags = MEDIA_PAD_FL_SOURCE; + priv->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&priv->sd.entity, 1, &priv->pad); + if (ret < 0) + goto cleanup; + + ret = ov10635_parse_dt(client->dev.of_node, priv); + if (ret) + goto cleanup; + + ret = ov10635_initialize(client); + if (ret < 0) + goto cleanup; + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) + goto cleanup; + + if (device_create_file(&client->dev, &dev_attr_otp_id_ov10635) != 0) { + dev_err(&client->dev, "sysfs otp_id entry creation failed\n"); + goto cleanup; + } + + priv->init_complete = 1; + + return 0; + +cleanup: + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); +#ifdef CONFIG_SOC_CAMERA_OV10635 + v4l_err(client, "failed to probe @ 0x%02x (%s)\n", + client->addr, client->adapter->name); +#endif + return ret; +} + +static int ov10635_remove(struct i2c_client *client) +{ + struct ov10635_priv *priv = i2c_get_clientdata(client); + + device_remove_file(&client->dev, &dev_attr_otp_id_ov10635); + v4l2_async_unregister_subdev(&priv->sd); + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); + + return 0; +} + +#ifdef CONFIG_SOC_CAMERA_OV10635 +static const struct i2c_device_id ov10635_id[] = { + { "ov10635", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov10635_id); + +static const struct of_device_id ov10635_of_ids[] = { + { .compatible = "ovti,ov10635", }, + { } +}; +MODULE_DEVICE_TABLE(of, ov10635_of_ids); + +static struct i2c_driver ov10635_i2c_driver = { + .driver = { + .name = "ov10635", + .of_match_table = ov10635_of_ids, + }, + .probe = ov10635_probe, + .remove = ov10635_remove, + .id_table = ov10635_id, +}; + +module_i2c_driver(ov10635_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for OV10635"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); +#endif diff --git a/drivers/media/i2c/soc_camera/ov10635.h b/drivers/media/i2c/soc_camera/ov10635.h new file mode 100644 index 0000000..66cc490 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov10635.h @@ -0,0 +1,1139 @@ +/* + * OmniVision ov10635 sensor camera wizard 1280x800@30/UYVY/BT601/8bit + * + * Copyright (C) 2015-2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +//#define OV10635_DISPLAY_PATTERN + +#define OV10635_SENSOR_WIDTH 1312 +#define OV10635_SENSOR_HEIGHT 814 + +#define OV10635_MAX_WIDTH 1280 +#define OV10635_MAX_HEIGHT 800 + +//#define OV10635_PCLK_96MHZ +#define OV10635_PCLK_88MHZ + +#if defined(OV10635_PCLK_96MHZ) +/* VTS=PCLK/FPS/HTS/2 (=96MHz/30/1600/2) */ + #define OV10635_HTS 1600 + #define OV10635_VTS 1000 /* fps=30 */ +#elif defined(OV10635_PCLK_88MHZ) +/* VTS=PCLK/FPS/HTS/2 (=88MHz/1572/30/2) */ + #define OV10635_HTS 1572 + #define OV10635_VTS 933 /* fps=29.9998 */ +#else + #error PCLK not defined +#endif + +struct ov10635_reg { + u16 reg; + u8 val; +}; + +static const struct ov10635_reg ov10635_regs_wizard[] = { +//{0x0103, 0x01}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x300C, 0x61}, +{0x301B, 0xFF}, +{0x301C, 0xFF}, +{0x301A, 0xFF}, +{0x3011, 0x42}, +{0x6900, 0x0C}, +{0x6901, 0x19}, +{0x3503, 0x10}, +{0x3025, 0x03}, +#if defined(OV10635_PCLK_96MHZ) +{0x3003, 0x20}, +{0x3004, 0x21}, +#elif defined(OV10635_PCLK_88MHZ) +{0x3003, 0x16}, +{0x3004, 0x30}, +#endif +{0x3005, 0x40}, +{0x3006, 0x91}, +{0x3600, 0x74}, +{0x3601, 0x2B}, +{0x3612, 0x00}, +{0x3611, 0x67}, +{0x3633, 0xCA}, +{0x3602, 0xAF}, +{0x3603, 0x04}, +{0x3630, 0x28}, +{0x3631, 0x16}, +{0x3714, 0x10}, +{0x371D, 0x01}, +{0x4300, 0x3A}, +{0x3007, 0x01}, +{0x3024, 0x03}, +{0x3020, 0x0A}, +{0x3702, 0x0D}, +{0x3703, 0x20}, +{0x3704, 0x15}, +{0x3709, 0xA8}, +{0x370C, 0xC7}, +{0x370D, 0x80}, +{0x3712, 0x00}, +{0x3713, 0x20}, +{0x3715, 0x04}, +{0x381D, 0x40}, +{0x381C, 0x00}, +{0x3822, 0x50}, +{0x3824, 0x10}, +{0x3815, 0x8C}, +{0x3804, 0x05}, +{0x3805, 0x1F}, +{0x3800, 0x00}, +{0x3801, 0x00}, +{0x3806, 0x03}, +{0x3807, 0x28}, +{0x3802, 0x00}, +{0x3803, 0x07}, +{0x3808, 0x05}, +{0x3809, 0x00}, +{0x380A, 0x03}, +{0x380B, 0x20}, +{0x380C, OV10635_HTS >> 8}, +{0x380D, OV10635_HTS & 0xff}, +{0x380E, OV10635_VTS >> 8}, +{0x380F, OV10635_VTS & 0xff}, +{0x3813, 0x02}, +{0x3811, 0x08}, +{0x381F, 0x0C}, +{0x3819, 0x04}, +{0x3804, 0x01}, +{0x3805, 0x00}, +{0x3828, 0x03}, +{0x3829, 0x10}, +{0x382A, 0x10}, +{0x3621, 0x63}, +{0x5005, 0x08}, +{0x56D5, 0x00}, +{0x56D6, 0x80}, +{0x56D7, 0x00}, +{0x56D8, 0x00}, +{0x56D9, 0x00}, +{0x56DA, 0x80}, +{0x56DB, 0x00}, +{0x56DC, 0x00}, +{0x56E8, 0x00}, +{0x56E9, 0x7F}, +{0x56EA, 0x00}, +{0x56EB, 0x7F}, +{0x5100, 0x00}, +{0x5101, 0x80}, +{0x5102, 0x00}, +{0x5103, 0x80}, +{0x5104, 0x00}, +{0x5105, 0x80}, +{0x5106, 0x00}, +{0x5107, 0x80}, +{0x5108, 0x00}, +{0x5109, 0x00}, +{0x510A, 0x00}, +{0x510B, 0x00}, +{0x510C, 0x00}, +{0x510D, 0x00}, +{0x510E, 0x00}, +{0x510F, 0x00}, +{0x5110, 0x00}, +{0x5111, 0x80}, +{0x5112, 0x00}, +{0x5113, 0x80}, +{0x5114, 0x00}, +{0x5115, 0x80}, +{0x5116, 0x00}, +{0x5117, 0x80}, +{0x5118, 0x00}, +{0x5119, 0x00}, +{0x511A, 0x00}, +{0x511B, 0x00}, +{0x511C, 0x00}, +{0x511D, 0x00}, +{0x511E, 0x00}, +{0x511F, 0x00}, +{0x56D0, 0x00}, +{0x5006, 0x04}, +{0x5608, 0x05}, +{0x52D7, 0x06}, +{0x528D, 0x08}, +{0x5293, 0x12}, +{0x52D3, 0x12}, +{0x5288, 0x06}, +{0x5289, 0x20}, +{0x52C8, 0x06}, +{0x52C9, 0x20}, +{0x52CD, 0x04}, +{0x5381, 0x00}, +{0x5382, 0xFF}, +{0x5589, 0x76}, +{0x558A, 0x47}, +{0x558B, 0xEF}, +{0x558C, 0xC9}, +{0x558D, 0x49}, +{0x558E, 0x30}, +{0x558F, 0x67}, +{0x5590, 0x3F}, +{0x5591, 0xF0}, +{0x5592, 0x10}, +{0x55A2, 0x6D}, +{0x55A3, 0x55}, +{0x55A4, 0xC3}, +{0x55A5, 0xB5}, +{0x55A6, 0x43}, +{0x55A7, 0x38}, +{0x55A8, 0x5F}, +{0x55A9, 0x4B}, +{0x55AA, 0xF0}, +{0x55AB, 0x10}, +{0x5581, 0x52}, +{0x5300, 0x01}, +{0x5301, 0x00}, +{0x5302, 0x00}, +{0x5303, 0x0E}, +{0x5304, 0x00}, +{0x5305, 0x0E}, +{0x5306, 0x00}, +{0x5307, 0x36}, +{0x5308, 0x00}, +{0x5309, 0xD9}, +{0x530A, 0x00}, +{0x530B, 0x0F}, +{0x530C, 0x00}, +{0x530D, 0x2C}, +{0x530E, 0x00}, +{0x530F, 0x59}, +{0x5310, 0x00}, +{0x5311, 0x7B}, +{0x5312, 0x00}, +{0x5313, 0x22}, +{0x5314, 0x00}, +{0x5315, 0xD5}, +{0x5316, 0x00}, +{0x5317, 0x13}, +{0x5318, 0x00}, +{0x5319, 0x18}, +{0x531A, 0x00}, +{0x531B, 0x26}, +{0x531C, 0x00}, +{0x531D, 0xDC}, +{0x531E, 0x00}, +{0x531F, 0x02}, +{0x5320, 0x00}, +{0x5321, 0x24}, +{0x5322, 0x00}, +{0x5323, 0x56}, +{0x5324, 0x00}, +{0x5325, 0x85}, +{0x5326, 0x00}, +{0x5327, 0x20}, +{0x5609, 0x01}, +{0x560A, 0x40}, +{0x560B, 0x01}, +{0x560C, 0x40}, +{0x560D, 0x00}, +{0x560E, 0xFA}, +{0x560F, 0x00}, +{0x5610, 0xFA}, +{0x5611, 0x02}, +{0x5612, 0x80}, +{0x5613, 0x02}, +{0x5614, 0x80}, +{0x5615, 0x01}, +{0x5616, 0x2C}, +{0x5617, 0x01}, +{0x5618, 0x2C}, +{0x563B, 0x01}, +{0x563C, 0x01}, +{0x563D, 0x01}, +{0x563E, 0x01}, +{0x563F, 0x03}, +{0x5640, 0x03}, +{0x5641, 0x03}, +{0x5642, 0x05}, +{0x5643, 0x09}, +{0x5644, 0x05}, +{0x5645, 0x05}, +{0x5646, 0x05}, +{0x5647, 0x05}, +{0x5651, 0x00}, +{0x5652, 0x80}, +{0x521A, 0x01}, +{0x521B, 0x03}, +{0x521C, 0x06}, +{0x521D, 0x0A}, +{0x521E, 0x0E}, +{0x521F, 0x12}, +{0x5220, 0x16}, +{0x5223, 0x02}, +{0x5225, 0x04}, +{0x5227, 0x08}, +{0x5229, 0x0C}, +{0x522B, 0x12}, +{0x522D, 0x18}, +{0x522F, 0x1E}, +{0x5241, 0x04}, +{0x5242, 0x01}, +{0x5243, 0x03}, +{0x5244, 0x06}, +{0x5245, 0x0A}, +{0x5246, 0x0E}, +{0x5247, 0x12}, +{0x5248, 0x16}, +{0x524A, 0x03}, +{0x524C, 0x04}, +{0x524E, 0x08}, +{0x5250, 0x0C}, +{0x5252, 0x12}, +{0x5254, 0x18}, +{0x5256, 0x1E}, +{0x4606, (2*OV10635_HTS) >> 8}, /* fifo_line_length = 2*hts */ +{0x4607, (2*OV10635_HTS) & 0xff}, +{0x460a, (2*(OV10635_HTS-OV10635_MAX_WIDTH)) >> 8}, /* fifo_hsync_start = 2*(hts - xres) */ +{0x460b, (2*(OV10635_HTS-OV10635_MAX_WIDTH)) & 0xff }, +{0x460C, 0x00}, +{0x4620, 0x0E}, +#if 0 +{0x4700, 0x02}, // BT656: mode is acceptable but artefact lines on left/bottom due to BT656 SAV/EAV are parsed as image data +#else +{0x4700, 0x04}, // BT601: 0x08 is also accaptable as HS/VS mode +#endif +{0x4701, 0x00}, +{0x4702, 0x01}, +{0x4004, 0x04}, +{0x4005, 0x18}, +{0x4001, 0x06}, +{0x4050, 0x22}, +{0x4051, 0x24}, +{0x4052, 0x02}, +{0x4057, 0x9C}, +{0x405A, 0x00}, +{0x4202, 0x02}, +{0x3023, 0x10}, +{0x0100, 0x01}, +{0x0100, 0x01}, +{0x6F10, 0x07}, +{0x6F11, 0x82}, +{0x6F12, 0x04}, +{0x6F13, 0x00}, +{0xD000, 0x19}, +{0xD001, 0xA0}, +{0xD002, 0x00}, +{0xD003, 0x01}, +{0xD004, 0xA9}, +{0xD005, 0xAD}, +{0xD006, 0x10}, +{0xD007, 0x40}, +{0xD008, 0x44}, +{0xD009, 0x00}, +{0xD00A, 0x68}, +{0xD00B, 0x00}, +{0xD00C, 0x15}, +{0xD00D, 0x00}, +{0xD00E, 0x00}, +{0xD00F, 0x00}, +{0xD040, 0x9C}, +{0xD041, 0x21}, +{0xD042, 0xFF}, +{0xD043, 0xF8}, +{0xD044, 0xD4}, +{0xD045, 0x01}, +{0xD046, 0x48}, +{0xD047, 0x00}, +{0xD048, 0xD4}, +{0xD049, 0x01}, +{0xD04A, 0x50}, +{0xD04B, 0x04}, +{0xD04C, 0x18}, +{0xD04D, 0x60}, +{0xD04E, 0x00}, +{0xD04F, 0x01}, +{0xD050, 0xA8}, +{0xD051, 0x63}, +{0xD052, 0x02}, +{0xD053, 0xA4}, +{0xD054, 0x85}, +{0xD055, 0x43}, +{0xD056, 0x00}, +{0xD057, 0x00}, +{0xD058, 0x18}, +{0xD059, 0x60}, +{0xD05A, 0x00}, +{0xD05B, 0x01}, +{0xD05C, 0xA8}, +{0xD05D, 0x63}, +{0xD05E, 0x03}, +{0xD05F, 0xF0}, +{0xD060, 0x98}, +{0xD061, 0xA3}, +{0xD062, 0x00}, +{0xD063, 0x00}, +{0xD064, 0x8C}, +{0xD065, 0x6A}, +{0xD066, 0x00}, +{0xD067, 0x6E}, +{0xD068, 0xE5}, +{0xD069, 0x85}, +{0xD06A, 0x18}, +{0xD06B, 0x00}, +{0xD06C, 0x10}, +{0xD06D, 0x00}, +{0xD06E, 0x00}, +{0xD06F, 0x10}, +{0xD070, 0x9C}, +{0xD071, 0x80}, +{0xD072, 0x00}, +{0xD073, 0x03}, +{0xD074, 0x18}, +{0xD075, 0x60}, +{0xD076, 0x00}, +{0xD077, 0x01}, +{0xD078, 0xA8}, +{0xD079, 0x63}, +{0xD07A, 0x07}, +{0xD07B, 0x80}, +{0xD07C, 0x07}, +{0xD07D, 0xFF}, +{0xD07E, 0xF9}, +{0xD07F, 0x03}, +{0xD080, 0x8C}, +{0xD081, 0x63}, +{0xD082, 0x00}, +{0xD083, 0x00}, +{0xD084, 0xA5}, +{0xD085, 0x6B}, +{0xD086, 0x00}, +{0xD087, 0xFF}, +{0xD088, 0x18}, +{0xD089, 0x80}, +{0xD08A, 0x00}, +{0xD08B, 0x01}, +{0xD08C, 0xA8}, +{0xD08D, 0x84}, +{0xD08E, 0x01}, +{0xD08F, 0x04}, +{0xD090, 0xE1}, +{0xD091, 0x6B}, +{0xD092, 0x58}, +{0xD093, 0x00}, +{0xD094, 0x94}, +{0xD095, 0x6A}, +{0xD096, 0x00}, +{0xD097, 0x70}, +{0xD098, 0xE1}, +{0xD099, 0x6B}, +{0xD09A, 0x20}, +{0xD09B, 0x00}, +{0xD09C, 0x95}, +{0xD09D, 0x6B}, +{0xD09E, 0x00}, +{0xD09F, 0x00}, +{0xD0A0, 0xE4}, +{0xD0A1, 0x8B}, +{0xD0A2, 0x18}, +{0xD0A3, 0x00}, +{0xD0A4, 0x0C}, +{0xD0A5, 0x00}, +{0xD0A6, 0x00}, +{0xD0A7, 0x23}, +{0xD0A8, 0x15}, +{0xD0A9, 0x00}, +{0xD0AA, 0x00}, +{0xD0AB, 0x00}, +{0xD0AC, 0x18}, +{0xD0AD, 0x60}, +{0xD0AE, 0x80}, +{0xD0AF, 0x06}, +{0xD0B0, 0xA8}, +{0xD0B1, 0x83}, +{0xD0B2, 0x40}, +{0xD0B3, 0x08}, +{0xD0B4, 0xA8}, +{0xD0B5, 0xE3}, +{0xD0B6, 0x38}, +{0xD0B7, 0x2A}, +{0xD0B8, 0xA8}, +{0xD0B9, 0xC3}, +{0xD0BA, 0x40}, +{0xD0BB, 0x09}, +{0xD0BC, 0xA8}, +{0xD0BD, 0xA3}, +{0xD0BE, 0x38}, +{0xD0BF, 0x29}, +{0xD0C0, 0x8C}, +{0xD0C1, 0x65}, +{0xD0C2, 0x00}, +{0xD0C3, 0x00}, +{0xD0C4, 0xD8}, +{0xD0C5, 0x04}, +{0xD0C6, 0x18}, +{0xD0C7, 0x00}, +{0xD0C8, 0x8C}, +{0xD0C9, 0x67}, +{0xD0CA, 0x00}, +{0xD0CB, 0x00}, +{0xD0CC, 0xD8}, +{0xD0CD, 0x06}, +{0xD0CE, 0x18}, +{0xD0CF, 0x00}, +{0xD0D0, 0x18}, +{0xD0D1, 0x60}, +{0xD0D2, 0x80}, +{0xD0D3, 0x06}, +{0xD0D4, 0xA8}, +{0xD0D5, 0xE3}, +{0xD0D6, 0x67}, +{0xD0D7, 0x02}, +{0xD0D8, 0xA9}, +{0xD0D9, 0x03}, +{0xD0DA, 0x67}, +{0xD0DB, 0x03}, +{0xD0DC, 0xA8}, +{0xD0DD, 0xC3}, +{0xD0DE, 0x3D}, +{0xD0DF, 0x05}, +{0xD0E0, 0x8C}, +{0xD0E1, 0x66}, +{0xD0E2, 0x00}, +{0xD0E3, 0x00}, +{0xD0E4, 0xB8}, +{0xD0E5, 0x63}, +{0xD0E6, 0x00}, +{0xD0E7, 0x18}, +{0xD0E8, 0xB8}, +{0xD0E9, 0x63}, +{0xD0EA, 0x00}, +{0xD0EB, 0x98}, +{0xD0EC, 0xBC}, +{0xD0ED, 0x03}, +{0xD0EE, 0x00}, +{0xD0EF, 0x00}, +{0xD0F0, 0x10}, +{0xD0F1, 0x00}, +{0xD0F2, 0x00}, +{0xD0F3, 0x16}, +{0xD0F4, 0xB8}, +{0xD0F5, 0x83}, +{0xD0F6, 0x00}, +{0xD0F7, 0x19}, +{0xD0F8, 0x8C}, +{0xD0F9, 0x67}, +{0xD0FA, 0x00}, +{0xD0FB, 0x00}, +{0xD0FC, 0xB8}, +{0xD0FD, 0xA4}, +{0xD0FE, 0x00}, +{0xD0FF, 0x98}, +{0xD100, 0xB8}, +{0xD101, 0x83}, +{0xD102, 0x00}, +{0xD103, 0x08}, +{0xD104, 0x8C}, +{0xD105, 0x68}, +{0xD106, 0x00}, +{0xD107, 0x00}, +{0xD108, 0xE0}, +{0xD109, 0x63}, +{0xD10A, 0x20}, +{0xD10B, 0x04}, +{0xD10C, 0xE0}, +{0xD10D, 0x65}, +{0xD10E, 0x18}, +{0xD10F, 0x00}, +{0xD110, 0xA4}, +{0xD111, 0x83}, +{0xD112, 0xFF}, +{0xD113, 0xFF}, +{0xD114, 0xB8}, +{0xD115, 0x64}, +{0xD116, 0x00}, +{0xD117, 0x48}, +{0xD118, 0xD8}, +{0xD119, 0x07}, +{0xD11A, 0x18}, +{0xD11B, 0x00}, +{0xD11C, 0xD8}, +{0xD11D, 0x08}, +{0xD11E, 0x20}, +{0xD11F, 0x00}, +{0xD120, 0x9C}, +{0xD121, 0x60}, +{0xD122, 0x00}, +{0xD123, 0x00}, +{0xD124, 0xD8}, +{0xD125, 0x06}, +{0xD126, 0x18}, +{0xD127, 0x00}, +{0xD128, 0x00}, +{0xD129, 0x00}, +{0xD12A, 0x00}, +{0xD12B, 0x08}, +{0xD12C, 0x15}, +{0xD12D, 0x00}, +{0xD12E, 0x00}, +{0xD12F, 0x00}, +{0xD130, 0x8C}, +{0xD131, 0x6A}, +{0xD132, 0x00}, +{0xD133, 0x76}, +{0xD134, 0xBC}, +{0xD135, 0x23}, +{0xD136, 0x00}, +{0xD137, 0x00}, +{0xD138, 0x13}, +{0xD139, 0xFF}, +{0xD13A, 0xFF}, +{0xD13B, 0xE6}, +{0xD13C, 0x18}, +{0xD13D, 0x60}, +{0xD13E, 0x80}, +{0xD13F, 0x06}, +{0xD140, 0x03}, +{0xD141, 0xFF}, +{0xD142, 0xFF}, +{0xD143, 0xDD}, +{0xD144, 0xA8}, +{0xD145, 0x83}, +{0xD146, 0x40}, +{0xD147, 0x08}, +{0xD148, 0x85}, +{0xD149, 0x21}, +{0xD14A, 0x00}, +{0xD14B, 0x00}, +{0xD14C, 0x85}, +{0xD14D, 0x41}, +{0xD14E, 0x00}, +{0xD14F, 0x04}, +{0xD150, 0x44}, +{0xD151, 0x00}, +{0xD152, 0x48}, +{0xD153, 0x00}, +{0xD154, 0x9C}, +{0xD155, 0x21}, +{0xD156, 0x00}, +{0xD157, 0x08}, +{0x6F0E, 0x03}, +{0x6F0F, 0x00}, +{0x460E, 0x08}, +{0x460F, 0x01}, +{0x4610, 0x00}, +{0x4611, 0x01}, +{0x4612, 0x00}, +{0x4613, 0x01}, +{0x4605, 0x08}, // 8bit +//{0x4709, 0x10}, // swap data bits order [9:0] -> [0:9] +{0x4608, 0x00}, +{0x4609, 0x08}, +{0x6804, 0x00}, +{0x6805, 0x06}, +{0x6806, 0x00}, +{0x5120, 0x00}, +{0x3510, 0x00}, +{0x3504, 0x00}, +{0x6800, 0x00}, +{0x6F0D, 0x01}, +{0x4708, 0x01}, // PCLK rising edge +{0x5000, 0xFF}, +{0x5001, 0xBF}, +{0x5002, 0x7E}, +#ifdef OV10635_DISPLAY_PATTERN +{0x503d, 0x80}, +#else +{0x503D, 0x00}, +#endif +{0xC450, 0x01}, /* AA mode */ +{0xC452, 0x04}, +{0xC453, 0x00}, +{0xC454, 0x00}, +{0xC455, 0x01}, +{0xC456, 0x01}, +{0xC457, 0x00}, +{0xC458, 0x00}, +{0xC459, 0x00}, +{0xC45B, 0x00}, +{0xC45C, 0x01}, +{0xC45D, 0x00}, +{0xC45E, 0x00}, +{0xC45F, 0x00}, +{0xC460, 0x00}, +{0xC461, 0x01}, +{0xC462, 0x01}, +{0xC464, 0x03}, +{0xC465, 0x00}, +{0xC466, 0x8A}, +{0xC467, 0x00}, +{0xC468, 0x86}, +{0xC469, 0x00}, +{0xC46A, 0x30}, +{0xC46B, 0x50}, +{0xC46C, 0x30}, +{0xC46D, 0x28}, +{0xC46E, 0x60}, +{0xC46F, 0x40}, +{0xC47C, 0x01}, +{0xC47D, 0x38}, +{0xC47E, 0x00}, +{0xC47F, 0x00}, +{0xC480, 0x00}, +{0xC481, 0xFF}, +{0xC482, 0x00}, +{0xC483, 0x40}, +{0xC484, 0x00}, +{0xC485, 0x18}, +{0xC486, 0x00}, +{0xC487, 0x18}, +{0xC488, (OV10635_VTS-8)*16 >> 8}, +{0xC489, (OV10635_VTS-8)*16 & 0xff}, +{0xC48A, (OV10635_VTS-8)*16 >> 8}, +{0xC48B, (OV10635_VTS-8)*16 & 0xff}, +{0xC48C, 0x00}, +{0xC48D, 0x04}, +{0xC48E, 0x00}, +{0xC48F, 0x04}, +{0xC490, 0x03}, +{0xC492, 0x20}, +{0xC493, 0x08}, +{0xC498, 0x02}, +{0xC499, 0x00}, +{0xC49A, 0x02}, +{0xC49B, 0x00}, +{0xC49C, 0x02}, +{0xC49D, 0x00}, +{0xC49E, 0x02}, +{0xC49F, 0x60}, +{0xC4A0, 0x03}, +{0xC4A1, 0x00}, +{0xC4A2, 0x04}, +{0xC4A3, 0x00}, +{0xC4A4, 0x00}, +{0xC4A5, 0x10}, +{0xC4A6, 0x00}, +{0xC4A7, 0x40}, +{0xC4A8, 0x00}, +{0xC4A9, 0x80}, +{0xC4AA, 0x0D}, +{0xC4AB, 0x00}, +{0xC4AC, 0x0F}, +{0xC4AD, 0xC0}, +{0xC4B4, 0x01}, +{0xC4B5, 0x01}, +{0xC4B6, 0x00}, +{0xC4B7, 0x01}, +{0xC4B8, 0x00}, +{0xC4B9, 0x01}, +{0xC4BA, 0x01}, +{0xC4BB, 0x00}, +{0xC4BC, 0x01}, +{0xC4BD, 0x60}, +{0xC4BE, 0x02}, +{0xC4BF, 0x33}, +{0xC4C8, 0x03}, +{0xC4C9, 0xD0}, +{0xC4CA, 0x0E}, +{0xC4CB, 0x00}, +{0xC4CC, 0x0E}, +{0xC4CD, 0x51}, +{0xC4CE, 0x0E}, +{0xC4CF, 0x51}, +{0xC4D0, 0x04}, +{0xC4D1, 0x80}, +{0xC4E0, 0x04}, +{0xC4E1, 0x02}, +{0xC4E2, 0x01}, +{0xC4E4, 0x10}, +{0xC4E5, 0x20}, +{0xC4E6, 0x30}, +{0xC4E7, 0x40}, +{0xC4E8, 0x50}, +{0xC4E9, 0x60}, +{0xC4EA, 0x70}, +{0xC4EB, 0x80}, +{0xC4EC, 0x90}, +{0xC4ED, 0xA0}, +{0xC4EE, 0xB0}, +{0xC4EF, 0xC0}, +{0xC4F0, 0xD0}, +{0xC4F1, 0xE0}, +{0xC4F2, 0xF0}, +{0xC4F3, 0x80}, +{0xC4F4, 0x00}, +{0xC4F5, 0x20}, +{0xC4F6, 0x02}, +{0xC4F7, 0x00}, +{0xC4F8, 0x00}, +{0xC4F9, 0x00}, +{0xC4FA, 0x00}, +{0xC4FB, 0x01}, +{0xC4FC, 0x01}, +{0xC4FD, 0x00}, +{0xC4FE, 0x04}, +{0xC4FF, 0x02}, +{0xC500, 0x48}, +{0xC501, 0x74}, +{0xC502, 0x58}, +{0xC503, 0x80}, +{0xC504, 0x05}, +{0xC505, 0x80}, +{0xC506, 0x03}, +{0xC507, 0x80}, +{0xC508, 0x01}, +{0xC509, 0xC0}, +{0xC50A, 0x01}, +{0xC50B, 0xA0}, +{0xC50C, 0x01}, +{0xC50D, 0x2C}, +{0xC50E, 0x01}, +{0xC50F, 0x0A}, +{0xC510, 0x00}, +{0xC511, 0x00}, +{0xC512, 0xE5}, +{0xC513, 0x14}, +{0xC514, 0x04}, +{0xC515, 0x00}, +{0xC518, OV10635_VTS >> 8}, +{0xC519, OV10635_VTS & 0xff}, +{0xC51A, OV10635_HTS >> 8}, +{0xC51B, OV10635_HTS & 0xff}, +{0xC2E0, 0x00}, +{0xC2E1, 0x51}, +{0xC2E2, 0x00}, +{0xC2E3, 0xD6}, +{0xC2E4, 0x01}, +{0xC2E5, 0x5E}, +{0xC2E9, 0x01}, +{0xC2EA, 0x7A}, +{0xC2EB, 0x90}, +{0xC2ED, 0x00}, +{0xC2EE, 0x7A}, +{0xC2EF, 0x64}, +{0xC308, 0x00}, +{0xC309, 0x00}, +{0xC30A, 0x00}, +{0xC30C, 0x00}, +{0xC30D, 0x01}, +{0xC30E, 0x00}, +{0xC30F, 0x00}, +{0xC310, 0x01}, +{0xC311, 0x60}, +{0xC312, 0xFF}, +{0xC313, 0x08}, +{0xC314, 0x01}, +{0xC315, 0x00}, /* min saturation gain */ +{0xC316, 0xFF}, /* max saturation gain */ +{0xC317, 0x0B}, +{0xC318, 0x00}, +{0xC319, 0x0C}, +{0xC31A, 0x00}, +{0xC31B, 0xE0}, +{0xC31C, 0x00}, +{0xC31D, 0x14}, +{0xC31E, 0x00}, +{0xC31F, 0xC5}, +{0xC320, 0xFF}, +{0xC321, 0x4B}, +{0xC322, 0xFF}, +{0xC323, 0xF0}, +{0xC324, 0xFF}, +{0xC325, 0xE8}, +{0xC326, 0x00}, +{0xC327, 0x46}, +{0xC328, 0xFF}, +{0xC329, 0xD2}, +{0xC32A, 0xFF}, +{0xC32B, 0xE4}, +{0xC32C, 0xFF}, +{0xC32D, 0xBB}, +{0xC32E, 0x00}, +{0xC32F, 0x61}, +{0xC330, 0xFF}, +{0xC331, 0xF9}, +{0xC332, 0x00}, +{0xC333, 0xD9}, +{0xC334, 0x00}, +{0xC335, 0x2E}, +{0xC336, 0x00}, +{0xC337, 0xB1}, +{0xC338, 0xFF}, +{0xC339, 0x64}, +{0xC33A, 0xFF}, +{0xC33B, 0xEB}, +{0xC33C, 0xFF}, +{0xC33D, 0xE8}, +{0xC33E, 0x00}, +{0xC33F, 0x48}, +{0xC340, 0xFF}, +{0xC341, 0xD0}, +{0xC342, 0xFF}, +{0xC343, 0xED}, +{0xC344, 0xFF}, +{0xC345, 0xAD}, +{0xC346, 0x00}, +{0xC347, 0x66}, +{0xC348, 0x01}, +{0xC349, 0x00}, +{0x6700, 0x04}, +{0x6701, 0x7B}, +{0x6702, 0xFD}, +{0x6703, 0xF9}, +{0x6704, 0x3D}, +{0x6705, 0x71}, +{0x6706, 0x78}, +{0x6708, 0x05}, +{0x6F06, 0x6F}, +{0x6F07, 0x00}, +{0x6F0A, 0x6F}, +{0x6F0B, 0x00}, +{0x6F00, 0x03}, +{0xC34C, 0x01}, +{0xC34D, 0x00}, +{0xC34E, 0x46}, +{0xC34F, 0x55}, +{0xC350, 0x00}, +{0xC351, 0x40}, +{0xC352, 0x00}, +{0xC353, 0xFF}, +{0xC354, 0x04}, +{0xC355, 0x08}, +{0xC356, 0x01}, +{0xC357, 0xEF}, +{0xC358, 0x30}, +{0xC359, 0x01}, +{0xC35A, 0x64}, +{0xC35B, 0x46}, +{0xC35C, 0x00}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0x3042, 0xF0}, +{0xC261, 0x01}, +{0x301B, 0xF0}, +{0x301C, 0xF0}, +{0x301A, 0xF0}, +{0x6F00, 0xC3}, +{0xC46A, 0x30}, +{0xC46D, 0x20}, +{0xC464, 0x84}, +{0xC465, 0x00}, +{0x6F00, 0x03}, +{0x6F00, 0x43}, +{0x381C, 0x00}, +{0x381D, 0x40}, +{0xC454, 0x01}, +{0x6F00, 0xC3}, +{0xC454, 0x00}, +{0xC4B1, 0x02}, +{0xC4B2, 0x01}, +{0xC4B3, 0x03}, +{0x6F00, 0x03}, +{0x6F00, 0x43}, +/* enable FSIN (FRAMESYNC input) functionality */ +{0x3832, (0x0d+2*0x20+0x15+38) >> 8}, +{0x3833, (0x0d+2*0x20+0x15+38) & 0xff}, +{0x3834, OV10635_VTS >> 8}, +{0x3835, OV10635_VTS & 0xff}, +{0x302E, 0x01}, +}; + +static const struct ov10635_reg ov10635_regs_30fps[] = { +/* disable clocks */ +{0x301b, 0xff}, +{0x301c, 0xff}, +{0x301a, 0xff}, +/* clk = 24Mhz/2*32/2(1+1)=96Mhz, 30fps */ +{0x3003, 0x20}, +{0x3004, 0x21}, +/* enable clocks */ +{0x301b, 0xf0}, +{0x301c, 0xf0}, +{0x301a, 0xf0}, +}; + +static const struct ov10635_reg ov10635_regs_15fps[] = { +/* disable clocks */ +{0x301b, 0xff}, +{0x301c, 0xff}, +{0x301a, 0xff}, +/* clk = 24Mhz/2*32/2(1+3)=48Mhz, 15fps */ +{0x3003, 0x20}, +{0x3004, 0x23}, +/* enable clocks */ +{0x301b, 0xf0}, +{0x301c, 0xf0}, +{0x301a, 0xf0}, +}; + +static const struct ov10635_reg ov10635_regs_10fps[] = { +/* disable clocks */ +{0x301b, 0xff}, +{0x301c, 0xff}, +{0x301a, 0xff}, +/* clk = 24Mhz/2*32/2(1+5)=32Mhz, 10fps */ +{0x3003, 0x20}, +{0x3004, 0x25}, +/* enable clocks */ +{0x301b, 0xf0}, +{0x301c, 0xf0}, +{0x301a, 0xf0}, +}; + +static const struct ov10635_reg ov10635_regs_5fps[] = { +/* disable clocks */ +{0x301b, 0xff}, +{0x301c, 0xff}, +{0x301a, 0xff}, +/* clk = 24Mhz/4*32/2(1+5)=96Mhz, 5fps */ +{0x3003, 0x20}, +{0x3004, 0x45}, +/* enable clocks */ +{0x301b, 0xf0}, +{0x301c, 0xf0}, +{0x301a, 0xf0}, +}; + +static const struct ov10635_reg ov10635_regs_contrast[5][18] = { +{ + {0x6f00, 0xc3}, + {0xc4e4, 0x20}, + {0xc4e5, 0x40}, + {0xc4e6, 0x60}, + {0xc4e7, 0x80}, + {0xc4e8, 0xa0}, + {0xc4e9, 0xb4}, + {0xc4ea, 0xc0}, + {0xc4eb, 0xcb}, + {0xc4ec, 0xd5}, + {0xc4ed, 0xde}, + {0xc4ee, 0xe6}, + {0xc4ef, 0xed}, + {0xc4f0, 0xf3}, + {0xc4f1, 0xf8}, + {0xc4f2, 0xfc}, + {0x6f00, 0x03}, + {0x6f00, 0x43}, +}, { + {0x6f00, 0xc3}, + {0xc4e4, 0x18}, + {0xc4e5, 0x30}, + {0xc4e6, 0x48}, + {0xc4e7, 0x60}, + {0xc4e8, 0x78}, + {0xc4e9, 0x90}, + {0xc4ea, 0xa4}, + {0xc4eb, 0xb4}, + {0xc4ec, 0xc2}, + {0xc4ed, 0xcf}, + {0xc4ee, 0xdb}, + {0xc4ef, 0xe5}, + {0xc4f0, 0xee}, + {0xc4f1, 0xf6}, + {0xc4f2, 0xfc}, + {0x6f00, 0x03}, + {0x6f00, 0x43}, +}, { + {0x6f00, 0xc3}, + {0xc4e4, 0x10}, + {0xc4e5, 0x20}, + {0xc4e6, 0x30}, + {0xc4e7, 0x40}, + {0xc4e8, 0x50}, + {0xc4e9, 0x60}, + {0xc4ea, 0x70}, + {0xc4eb, 0x80}, + {0xc4ec, 0x90}, + {0xc4ed, 0xa0}, + {0xc4ee, 0xb0}, + {0xc4ef, 0xc0}, + {0xc4f0, 0xd0}, + {0xc4f1, 0xe0}, + {0xc4f2, 0xf0}, + {0x6f00, 0x03}, + {0x6f00, 0x43}, +}, { + {0x6f00, 0xc3}, + {0xc4e4, 0x0c}, + {0xc4e5, 0x18}, + {0xc4e6, 0x24}, + {0xc4e7, 0x30}, + {0xc4e8, 0x3c}, + {0xc4e9, 0x48}, + {0xc4ea, 0x54}, + {0xc4eb, 0x62}, + {0xc4ec, 0x72}, + {0xc4ed, 0x84}, + {0xc4ee, 0x94}, + {0xc4ef, 0xa6}, + {0xc4f0, 0xb9}, + {0xc4f1, 0xcd}, + {0xc4f2, 0xe2}, + {0x6f00, 0x03}, + {0x6f00, 0x43}, +}, { + {0x6f00, 0xc3}, + {0xc4e4, 0x06}, + {0xc4e5, 0x0d}, + {0xc4e6, 0x15}, + {0xc4e7, 0x1e}, + {0xc4e8, 0x28}, + {0xc4e9, 0x32}, + {0xc4ea, 0x3c}, + {0xc4eb, 0x48}, + {0xc4ec, 0x56}, + {0xc4ed, 0x66}, + {0xc4ee, 0x78}, + {0xc4ef, 0x8c}, + {0xc4f0, 0xa2}, + {0xc4f1, 0xba}, + {0xc4f2, 0xd4}, + {0x6f00, 0x03}, + {0x6f00, 0x43}, +} +}; diff --git a/drivers/media/i2c/soc_camera/ov10635_debug.h b/drivers/media/i2c/soc_camera/ov10635_debug.h new file mode 100644 index 0000000..4c3515a --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov10635_debug.h @@ -0,0 +1,54 @@ + +#if 0 +{0x4700, 0x02}, // BT656 +{0x381d, 0x40}, // mirror off +{0x381c, 0x00}, // flip off +{0x4300, 0x3a}, // YUV: UYVY +{0x4708, 0x00}, // PCLK rising edge + +// clk = 24Mhz/3*22/2= 88Mhz +{0x3003, 0x16}, +{0x3004, 0x30}, +#endif + +#define WIDTH 1280 +#define HEIGHT 720 + +// DVP frame size +{0x3808, WIDTH >> 8}, +{0x3809, WIDTH & 0xff}, +{0x380a, HEIGHT >> 8}, +{0x380b, HEIGHT & 0xff}, + +{0x3802, ((814 - HEIGHT)/2) >> 8}, // vert crop start +{0x3803, ((814 - HEIGHT)/2) & 0xff}, +{0x3806, ((814 - HEIGHT)/2 + HEIGHT + 1) >> 8}, // vert crop end +{0x3807, ((814 - HEIGHT)/2 + HEIGHT + 1) & 0xff}, + +#if 0 +#define HTS 0x6f6 // got from above table 1782 +#define VTS (0x2ec+80) // got from above table 748 + 80 + +{0x380c, HTS >> 8}, // hts +{0x380d, HTS & 0xff}, +{0x380e, VTS >> 8}, // vts +{0x380f, VTS & 0xff}, + +// fifo +{0x4606, (2*HTS) >> 8}, // fifo_line_length = 2*hts +{0x4607, (2*HTS) & 0xff}, +{0x460a, (2*(HTS-1280)) >> 8}, // fifo_hsync_start = 2*(hts - xres) +{0x460b, (2*(HTS-1280)) & 0xff }, + +// exposure +{0xC488, (VTS-8)*16 >> 8}, +{0xC489, (VTS-8)*16 & 0xff}, +{0xC48A, (VTS-8)*16 >> 8}, +{0xC48B, (VTS-8)*16 & 0xff}, + +// vts/hts +{0xC518, VTS >> 8}, +{0xC519, VTS & 0xff}, +{0xC51A, HTS >> 8}, +{0xC51B, HTS & 0xff}, +#endif diff --git a/drivers/media/i2c/soc_camera/ov106xx.c b/drivers/media/i2c/soc_camera/ov106xx.c new file mode 100644 index 0000000..f2bb706 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov106xx.c @@ -0,0 +1,106 @@ +/* + * OmniVision ov10635/ov490-ov10640/ov495-ov2775 sensor camera driver + * + * Copyright (C) 2016-2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include "ov10635.c" +#include "ov490_ov10640.c" +#include "ov495_ov2775.c" +#include "ar0132.c" + +static enum { + ID_OV10635, + ID_OV490_OV10640, + ID_OV495_OV2775, + ID_AR0132, +} chip_id; + +static int ov106xx_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + int ret; + chip_id = -EINVAL; + + ret = ov10635_probe(client, did); + if (!ret) { + chip_id = ID_OV10635; + goto out; + } + + ret = ov490_probe(client, did); + if (!ret) { + chip_id = ID_OV490_OV10640; + goto out; + } + + ret = ov495_probe(client, did); + if (!ret) { + chip_id = ID_OV495_OV2775; + goto out; + } + + ret = ar0132_probe(client, did); + if (!ret) { + chip_id = ID_AR0132; + goto out; + } + + v4l_err(client, "failed to probe @ 0x%02x (%s)\n", + client->addr, client->adapter->name); +out: + return ret; +} + +static int ov106xx_remove(struct i2c_client *client) +{ + switch (chip_id) { + case ID_OV10635: + ov10635_remove(client); + break; + case ID_OV490_OV10640: + ov490_remove(client); + break; + case ID_OV495_OV2775: + ov495_remove(client); + break; + case ID_AR0132: + ar0132_remove(client); + break; + }; + + return 0; +} + +static const struct i2c_device_id ov106xx_id[] = { + { "ov106xx", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov106xx_id); + +static const struct of_device_id ov106xx_of_ids[] = { + { .compatible = "ovti,ov106xx", }, + { } +}; +MODULE_DEVICE_TABLE(of, ov106xx_of_ids); + +static struct i2c_driver ov106xx_i2c_driver = { + .driver = { + .name = "ov106xx", + .of_match_table = ov106xx_of_ids, + }, + .probe = ov106xx_probe, + .remove = ov106xx_remove, + .id_table = ov106xx_id, +}; + +module_i2c_driver(ov106xx_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for OV10635 or OV490/OV10640 or OV495/OV2775 or AR0132"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/soc_camera/ov490_ov10640.c b/drivers/media/i2c/soc_camera/ov490_ov10640.c new file mode 100644 index 0000000..15acc51 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov490_ov10640.c @@ -0,0 +1,1046 @@ +/* + * OmniVision ov490-ov10640 sensor camera driver + * + * Copyright (C) 2016-2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "max9286_max9271.h" +#include "ov490_ov10640.h" + +#define OV490_I2C_ADDR 0x24 + +#define OV490_PID 0x300a +#define OV490_VER 0x300b +#define OV490_VERSION_REG 0x0490 +#define OV490_VERSION(pid, ver) (((pid) << 8) | ((ver) & 0xff)) + +#define OV490_ISP_HSIZE_LOW 0x60 +#define OV490_ISP_HSIZE_HIGH 0x61 +#define OV490_ISP_VSIZE_LOW 0x62 +#define OV490_ISP_VSIZE_HIGH 0x63 + +struct ov490_priv { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct media_pad pad; + struct v4l2_rect rect; + int max_width; + int max_height; + char is_fixed_sensor; + int init_complete; + u8 id[6]; + int exposure; + int gain; + int autogain; + int dvp_order; + /* serializers */ + int max9286_addr; + int max9271_addr; + int ti964_addr; + int ti954_addr; + int ti9x3_addr; + int port; + int gpio_resetb; + int active_low_resetb; + int gpio_fsin; +}; + +static int force_conf_link; + +static __init int ov490_force_conf_link(char *str) +{ + /* force configuration link */ + /* used only if robust firmware flashing required (f.e. recovery) */ + force_conf_link = 1; + return 0; +} +early_param("force_conf_link", ov490_force_conf_link); + +static inline struct ov490_priv *to_ov490(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov490_priv, sd); +} + +static void ov490_s_port(struct i2c_client *client, int fwd_en) +{ + struct ov490_priv *priv = to_ov490(client); + int tmp_addr; + + if (priv->max9286_addr) { + tmp_addr = client->addr; + client->addr = priv->max9286_addr; /* Deserializer I2C address */ + reg8_write(client, 0x0a, fwd_en ? 0x11 << priv->port : 0); /* Enable/disable reverse/forward control for this port */ + usleep_range(2000, 2500); /* wait 2ms */ + client->addr = tmp_addr; + }; +} + +static void ov490_reset(struct i2c_client *client) +{ + struct ov490_priv *priv = to_ov490(client); + int tmp_addr; + + if (priv->max9286_addr) { + if (priv->gpio_resetb < 1 || priv->gpio_resetb > 5) + return; + + tmp_addr = client->addr; + /* get out from sensor reset */ + client->addr = priv->max9271_addr; /* MAX9271 I2C address */ + reg8_write(client, 0x0f, (0xfe & ~BIT(priv->gpio_resetb)) | + (priv->active_low_resetb ? 0 : BIT(priv->gpio_resetb))); /* set GPIOn value to reset */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x0f, (0xfe & ~BIT(priv->gpio_resetb)) | + (priv->active_low_resetb ? BIT(priv->gpio_resetb) : 0)); /* set GPIOn value to un-reset */ + usleep_range(2000, 2500); /* wait 2ms */ + client->addr = tmp_addr; + } + + if (priv->ti964_addr) { + client->addr = priv->ti964_addr; /* TI964 I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x6e, 0x8a); /* set GPIO1 value to reset */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x6e, 0x9a); /* set GPIO1 value to un-reset */ + } + + if (priv->ti954_addr) { + client->addr = priv->ti954_addr; /* TI964 I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x6e, 0x8a); /* set GPIO1 value to reset */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x6e, 0x9a); /* set GPIO1 value to un-reset */ + } +} + +static int ov490_set_regs(struct i2c_client *client, + const struct ov490_reg *regs, int nr_regs) +{ + int i; + + for (i = 0; i < nr_regs; i++) { + if (reg16_write(client, regs[i].reg, regs[i].val)) { + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, regs[i].reg, regs[i].val); + } + } + + return 0; +} + +static int ov490_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +static int ov490_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov490_priv *priv = to_ov490(client); + + if (format->pad) + return -EINVAL; + + mf->width = priv->rect.width; + mf->height = priv->rect.height; + mf->code = MEDIA_BUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov490_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + + mf->code = MEDIA_BUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_NONE; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + cfg->try_fmt = *mf; + + return 0; +} + +static int ov490_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUYV8_2X8; + + return 0; +} + +static int ov490_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov490_priv *priv = to_ov490(client); + + memcpy(edid->edid, priv->id, 6); + + edid->edid[6] = 0xff; + edid->edid[7] = client->addr; + edid->edid[8] = OV490_VERSION_REG >> 8; + edid->edid[9] = OV490_VERSION_REG & 0xff; + + return 0; +} + +static int ov490_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_rect *rect = &sel->r; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov490_priv *priv = to_ov490(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + rect->left = ALIGN(rect->left, 2); + rect->top = ALIGN(rect->top, 2); + rect->width = ALIGN(rect->width, 2); + rect->height = ALIGN(rect->height, 2); + + if ((rect->left + rect->width > priv->max_width) || + (rect->top + rect->height > priv->max_height)) + *rect = priv->rect; + + priv->rect.left = rect->left; + priv->rect.top = rect->top; + priv->rect.width = rect->width; + priv->rect.height = rect->height; + + return 0; +} + +static int ov490_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov490_priv *priv = to_ov490(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = priv->max_width; + sel->r.height = priv->max_height; + return 0; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = priv->max_width; + sel->r.height = priv->max_height; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = priv->rect; + return 0; + default: + return -EINVAL; + } +} + +static int ov490_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + cfg->flags = V4L2_MBUS_CSI2_1_LANE | V4L2_MBUS_CSI2_CHANNEL_0 | + V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + cfg->type = V4L2_MBUS_CSI2; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov490_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val = 0; + + ret = reg16_read(client, (u16)reg->reg, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = sizeof(u16); + + return 0; +} + +static int ov490_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + ret = reg16_write(client, (u16)reg->reg, (u8)reg->val); + if ((u8)reg->reg == 0xFFFD) + usleep_range(100, 150); /* wait 100 us */ + if ((u8)reg->reg == 0xFFFE) + usleep_range(100, 150); /* wait 100 us */ + return ret; +} +#endif + +static struct v4l2_subdev_core_ops ov490_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov490_g_register, + .s_register = ov490_s_register, +#endif +}; + +static int ov490_s_gamma(int a, int ref) +{ + if ((a + ref) > 0xff) + return 0xff; + + if ((a + ref) < 0) + return 0; + + return a + ref; +} + +static int ov490_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov490_priv *priv = to_ov490(client); + int ret = -EINVAL; + + if (!priv->init_complete) + return 0; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + /* SDE (rough) brightness */ + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x00); + ret |= reg16_write(client, 0x5001, ctrl->val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xf1); + break; + case V4L2_CID_CONTRAST: + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, ctrl->val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xfd); + break; + case V4L2_CID_SATURATION: + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, ctrl->val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xf3); + break; + case V4L2_CID_HUE: + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, ctrl->val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xf5); + break; + case V4L2_CID_GAMMA: + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, ov490_s_gamma(ctrl->val, 0x12)); + ret |= reg16_write(client, 0x5001, ov490_s_gamma(ctrl->val, 0x20)); + ret |= reg16_write(client, 0x5002, ov490_s_gamma(ctrl->val, 0x3b)); + ret |= reg16_write(client, 0x5003, ov490_s_gamma(ctrl->val, 0x5d)); + ret |= reg16_write(client, 0x5004, ov490_s_gamma(ctrl->val, 0x6a)); + ret |= reg16_write(client, 0x5005, ov490_s_gamma(ctrl->val, 0x76)); + ret |= reg16_write(client, 0x5006, ov490_s_gamma(ctrl->val, 0x81)); + ret |= reg16_write(client, 0x5007, ov490_s_gamma(ctrl->val, 0x8b)); + ret |= reg16_write(client, 0x5008, ov490_s_gamma(ctrl->val, 0x96)); + ret |= reg16_write(client, 0x5009, ov490_s_gamma(ctrl->val, 0x9e)); + ret |= reg16_write(client, 0x500a, ov490_s_gamma(ctrl->val, 0xae)); + ret |= reg16_write(client, 0x500b, ov490_s_gamma(ctrl->val, 0xbc)); + ret |= reg16_write(client, 0x500c, ov490_s_gamma(ctrl->val, 0xcf)); + ret |= reg16_write(client, 0x500d, ov490_s_gamma(ctrl->val, 0xde)); + ret |= reg16_write(client, 0x500e, ov490_s_gamma(ctrl->val, 0xec)); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xf9); + break; + case V4L2_CID_SHARPNESS: + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, ctrl->val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xfb); + break; + case V4L2_CID_AUTOGAIN: + case V4L2_CID_GAIN: + case V4L2_CID_EXPOSURE: + if (ctrl->id == V4L2_CID_AUTOGAIN) + priv->autogain = ctrl->val; + if (ctrl->id == V4L2_CID_GAIN) + priv->gain = ctrl->val; + if (ctrl->id == V4L2_CID_EXPOSURE) + priv->exposure = ctrl->val; + + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, !priv->autogain); + ret |= reg16_write(client, 0x5001, priv->exposure >> 8); + ret |= reg16_write(client, 0x5002, priv->exposure & 0xff); + ret |= reg16_write(client, 0x5003, priv->exposure >> 8); + ret |= reg16_write(client, 0x5004, priv->exposure & 0xff); + ret |= reg16_write(client, 0x5005, priv->exposure >> 8); + ret |= reg16_write(client, 0x5006, priv->exposure & 0xff); + ret |= reg16_write(client, 0x5007, priv->gain >> 8); + ret |= reg16_write(client, 0x5008, priv->gain & 0xff); + ret |= reg16_write(client, 0x5009, priv->gain >> 8); + ret |= reg16_write(client, 0x500a, priv->gain & 0xff); + ret |= reg16_write(client, 0x500b, priv->gain >> 8); + ret |= reg16_write(client, 0x500c, priv->gain & 0xff); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xea); + break; + case V4L2_CID_HFLIP: +#if 1 + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, ctrl->val); + ret |= reg16_write(client, 0x5001, 0x00); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xdc); +#else + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x01); // read 0x3128 + ret |= reg16_write(client, 0x5001, 0x31); + ret |= reg16_write(client, 0x5002, 0x28); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_read(client, 0x5000, &val); + val &= ~(0x1 << 0); + val |= (ctrl->val << 0); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x00); // write 0x3128 + ret |= reg16_write(client, 0x5001, 0x31); + ret |= reg16_write(client, 0x5002, 0x28); + ret |= reg16_write(client, 0x5003, val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x01); // read 0x3291 + ret |= reg16_write(client, 0x5001, 0x32); + ret |= reg16_write(client, 0x5002, 0x91); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_read(client, 0x5000, &val); + val &= ~(0x1 << 1); + val |= (ctrl->val << 1); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x00); // write 0x3291 + ret |= reg16_write(client, 0x5001, 0x32); + ret |= reg16_write(client, 0x5002, 0x91); + ret |= reg16_write(client, 0x5003, val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x01); // read 0x3090 + ret |= reg16_write(client, 0x5001, 0x30); + ret |= reg16_write(client, 0x5002, 0x90); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_read(client, 0x5000, &val); + val &= ~(0x1 << 2); + val |= (ctrl->val << 2); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x00); // write 0x3090 + ret |= reg16_write(client, 0x5001, 0x30); + ret |= reg16_write(client, 0x5002, 0x90); + ret |= reg16_write(client, 0x5003, val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); +#endif + break; + case V4L2_CID_VFLIP: +#if 1 + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, ctrl->val); + ret |= reg16_write(client, 0x5001, 0x01); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xdc); +#else + ret = reg16_write(client, 0xFFFD, 0x80); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x01); // read 0x3128 + ret |= reg16_write(client, 0x5001, 0x31); + ret |= reg16_write(client, 0x5002, 0x28); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_read(client, 0x5000, &val); + val &= ~(0x1 << 1); + val |= (ctrl->val << 1); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x00); // write 0x3128 + ret |= reg16_write(client, 0x5001, 0x31); + ret |= reg16_write(client, 0x5002, 0x28); + ret |= reg16_write(client, 0x5003, val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x01); // read 0x3291 + ret |= reg16_write(client, 0x5001, 0x32); + ret |= reg16_write(client, 0x5002, 0x91); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_read(client, 0x5000, &val); + val &= ~(0x1 << 2); + val |= (ctrl->val << 2); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x00); // write 0x3291 + ret |= reg16_write(client, 0x5001, 0x32); + ret |= reg16_write(client, 0x5002, 0x91); + ret |= reg16_write(client, 0x5003, val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x01); // read 0x3090 + ret |= reg16_write(client, 0x5001, 0x30); + ret |= reg16_write(client, 0x5002, 0x90); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_read(client, 0x5000, &val); + val &= ~(0x1 << 3); + val |= (ctrl->val << 3); + ret |= reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x5000, 0x00); // write 0x3090 + ret |= reg16_write(client, 0x5001, 0x30); + ret |= reg16_write(client, 0x5002, 0x90); + ret |= reg16_write(client, 0x5003, val); + ret |= reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x00C0, 0xc1); +#endif + break; + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + ret = 0; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ov490_ctrl_ops = { + .s_ctrl = ov490_s_ctrl, +}; + +static struct v4l2_subdev_video_ops ov490_video_ops = { + .s_stream = ov490_s_stream, + .g_mbus_config = ov490_g_mbus_config, +}; + +static const struct v4l2_subdev_pad_ops ov490_subdev_pad_ops = { + .get_edid = ov490_get_edid, + .enum_mbus_code = ov490_enum_mbus_code, + .get_selection = ov490_get_selection, + .set_selection = ov490_set_selection, + .get_fmt = ov490_get_fmt, + .set_fmt = ov490_set_fmt, +}; + +static struct v4l2_subdev_ops ov490_subdev_ops = { + .core = &ov490_core_ops, + .video = &ov490_video_ops, + .pad = &ov490_subdev_pad_ops, +}; + +static void ov490_otp_id_read(struct i2c_client *client) +{ + struct ov490_priv *priv = to_ov490(client); + int i; + +#if 0 + /* read camera id from ov490 OTP memory */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x28); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0xE084, 0x40); /* manual mode, bank#0 */ + reg16_write(client, 0xE081, 1); /* start OTP read */ + + usleep_range(25000, 26000); /* wait 25 ms */ + + for (i = 0; i < 6; i++) + reg16_read(client, 0xe000 + i + 4, &priv->id[i]); +#else + /* read camera id from ov10640 OTP memory */ + reg16_write(client, 0xFFFD, 0x80); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0x5000, 0x00); /* write 0x349C -> 1 */ + reg16_write(client, 0x5001, 0x34); + reg16_write(client, 0x5002, 0x9C); + reg16_write(client, 0x5003, 1); + reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0x00C0, 0xc1); + + usleep_range(25000, 25500); /* wait 25 ms */ + + for (i = 0; i < 6; i++) { + reg16_write(client, 0xFFFE, 0x19); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0x5000, 0x01); /* read (0x349E + i) */ + reg16_write(client, 0x5001, 0x34); + reg16_write(client, 0x5002, 0x9e + i + 6); /* first 6 bytes are equal on all ov10640 */ + reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0x00C0, 0xc1); + reg16_write(client, 0xFFFE, 0x19); + usleep_range(1000, 1500); /* wait 1 ms */ + reg16_read(client, 0x5000, &priv->id[i]); + } +#endif +} + +static ssize_t ov490_otp_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(to_i2c_client(dev)); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov490_priv *priv = to_ov490(client); + + return snprintf(buf, 32, "%02x:%02x:%02x:%02x:%02x:%02x\n", + priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]); +} + +static DEVICE_ATTR(otp_id_ov490, S_IRUGO, ov490_otp_id_show, NULL); + +static int ov490_initialize(struct i2c_client *client) +{ + struct ov490_priv *priv = to_ov490(client); + u8 val = 0; + u8 pid = 0, ver = 0; + int ret = 0, timeout, retry_timeout = 3; + + if (priv->is_fixed_sensor) { + dev_info(&client->dev, "ov490/ov10640 fixed-sensor res %dx%d\n", priv->max_width, priv->max_height); + return 0; + } + + ov490_s_port(client, 1); + + /* check and show product ID and manufacturer ID */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + reg16_read(client, OV490_PID, &pid); + reg16_read(client, OV490_VER, &ver); + + if (OV490_VERSION(pid, ver) != OV490_VERSION_REG) { + dev_dbg(&client->dev, "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto err; + } + + if (unlikely(force_conf_link)) + goto out; + +again: + /* Check if firmware booted by reading stream-on status */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x29); + usleep_range(100, 150); /* wait 100 us */ + timeout = 300; + for (;;) { + reg16_read(client, 0xd000, &val); + if (val == 0x0c || --timeout == 0) + break; + mdelay(1); + } + + if (!timeout) { + dev_err(&client->dev, "Timeout firmware boot wait, retrying\n"); + /* reset OV10640 using RESETB pin controlled by OV490 GPIO0 */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0x0050, 0x01); + reg16_write(client, 0x0054, 0x01); + reg16_write(client, 0x0058, 0x00); + mdelay(10); + reg16_write(client, 0x0058, 0x01); + /* reset OV490 using RESETB pin controlled by serializer */ + ov490_reset(client); + if (retry_timeout--) + goto again; + } + + /* read resolution used by current firmware */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x82); + usleep_range(100, 150); /* wait 100 us */ + reg16_read(client, OV490_ISP_HSIZE_HIGH, &val); + priv->max_width = val; + reg16_read(client, OV490_ISP_HSIZE_LOW, &val); + priv->max_width = (priv->max_width << 8) | val; + reg16_read(client, OV490_ISP_VSIZE_HIGH, &val); + priv->max_height = val; + reg16_read(client, OV490_ISP_VSIZE_LOW, &val); + priv->max_height = (priv->max_height << 8) | val; + /* Program wizard registers */ + ov490_set_regs(client, ov490_regs_wizard, ARRAY_SIZE(ov490_regs_wizard)); + /* Set DVP bit swap */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x28); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0x6009, priv->dvp_order << 4); + /* Read OTP IDs */ + ov490_otp_id_read(client); + +out: + dev_info(&client->dev, "ov490/ov10640 PID %x%x, res %dx%d, OTP_ID %02x:%02x:%02x:%02x:%02x:%02x\n", + pid, ver, priv->max_width, priv->max_height, priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]); +err: + ov490_s_port(client, 0); + + return ret; +} + +static int ov490_parse_dt(struct device_node *np, struct ov490_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int err, i; + const char *fixed_sensor; + struct device_node *endpoint = NULL, *rendpoint = NULL; + int tmp_addr = 0; + + for (i = 0; ; i++) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + if (!endpoint) + break; + + of_node_put(endpoint); + + of_property_read_u32(endpoint, "dvp-order", &priv->dvp_order); + + rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0); + if (!rendpoint) + continue; + + if (!of_property_read_u32(rendpoint, "max9271-addr", &priv->max9271_addr) && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->max9286_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) { + if (of_property_read_u32(rendpoint->parent->parent, "maxim,resetb-gpio", &priv->gpio_resetb)) { + priv->gpio_resetb = -1; + } else { + if (of_property_read_bool(rendpoint->parent->parent, "maxim,resetb-active-high")) + priv->active_low_resetb = false; + else + priv->active_low_resetb = true; + } + break; + } + + if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) && + !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti964-ti9x3") && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti964_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + + if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) && + !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti954-ti9x3") && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti954_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + } + + if (!priv->max9286_addr && !priv->ti964_addr && !priv->ti954_addr) { + dev_err(&client->dev, "deserializer does not present\n"); + return -EINVAL; + } + + ov490_s_port(client, 1); + + /* setup I2C translator address */ + tmp_addr = client->addr; + if (priv->max9286_addr) { + client->addr = priv->max9271_addr; /* Serializer I2C address */ + + reg8_write(client, 0x09, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x0A, OV490_I2C_ADDR << 1); /* Sensor native I2C address */ + usleep_range(2000, 2500); /* wait 2ms */ + }; + if (priv->ti964_addr) { + client->addr = priv->ti964_addr; /* Deserializer I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x65, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x5d, OV490_I2C_ADDR << 1); /* Sensor native I2C address */ + + reg8_write(client, 0x6e, 0x9a); /* GPIO0 - fsin, GPIO1 - resetb */ + } + if (priv->ti954_addr) { + client->addr = priv->ti954_addr; /* Deserializer I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x65, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x5d, OV490_I2C_ADDR << 1); /* Sensor native I2C address */ + + reg8_write(client, 0x6e, 0x9a); /* GPIO0 - fsin, GPIO1 - resetb */ + } + client->addr = tmp_addr; + + err = of_property_read_string(np, "maxim,fixed-sensor", &fixed_sensor); + if (err) + return 0; + + if (strcmp(fixed_sensor, "ov490") == 0) { + err = of_property_read_u32(np, "maxim,width", &priv->max_width); + if (err) { + dev_err(&client->dev, "maxim,width must be set for fixed-sensor\n"); + goto out; + } + + err = of_property_read_u32(np, "maxim,height", &priv->max_height); + if (err) { + dev_err(&client->dev, "maxim,height must be set for fixed-sensor\n"); + goto out; + } + + priv->is_fixed_sensor = true; + } + +out: + return err; +} + +static int ov490_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov490_priv *priv; + struct v4l2_ctrl *ctrl; + int ret; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + v4l2_i2c_subdev_init(&priv->sd, client, &ov490_subdev_ops); + priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + priv->exposure = 0x100; + priv->gain = 0x100; + priv->autogain = 1; + v4l2_ctrl_handler_init(&priv->hdl, 4); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 16, 1, 7); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_CONTRAST, 0, 16, 1, 7); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_SATURATION, 0, 7, 1, 2); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_HUE, 0, 23, 1, 12); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_GAMMA, -128, 128, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_SHARPNESS, 0, 10, 1, 3); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, priv->autogain); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_GAIN, 0, 0xffff, 1, priv->gain); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0xffff, 1, priv->exposure); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 1); + v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + ctrl = v4l2_ctrl_new_std(&priv->hdl, &ov490_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 9); + if (ctrl) + ctrl->flags &= ~V4L2_CTRL_FLAG_READ_ONLY; + priv->sd.ctrl_handler = &priv->hdl; + + ret = priv->hdl.error; + if (ret) + goto cleanup; + + v4l2_ctrl_handler_setup(&priv->hdl); + + priv->pad.flags = MEDIA_PAD_FL_SOURCE; + priv->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&priv->sd.entity, 1, &priv->pad); + if (ret < 0) + goto cleanup; + + ret = ov490_parse_dt(client->dev.of_node, priv); + if (ret) + goto cleanup; + + ret = ov490_initialize(client); + if (ret < 0) + goto cleanup; + + priv->rect.left = 0; + priv->rect.top = 0; + priv->rect.width = priv->max_width; + priv->rect.height = priv->max_height; + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) + goto cleanup; + + if (device_create_file(&client->dev, &dev_attr_otp_id_ov490) != 0) { + dev_err(&client->dev, "sysfs otp_id entry creation failed\n"); + goto cleanup; + } + + priv->init_complete = 1; + + return 0; + +cleanup: + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); +#ifdef CONFIG_SOC_CAMERA_OV490_OV10640 + v4l_err(client, "failed to probe @ 0x%02x (%s)\n", + client->addr, client->adapter->name); +#endif + return ret; +} + +static int ov490_remove(struct i2c_client *client) +{ + struct ov490_priv *priv = i2c_get_clientdata(client); + + device_remove_file(&client->dev, &dev_attr_otp_id_ov490); + v4l2_async_unregister_subdev(&priv->sd); + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); + + return 0; +} + +#ifdef CONFIG_SOC_CAMERA_OV490_OV10640 +static const struct i2c_device_id ov490_id[] = { + { "ov490-ov10640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov490_id); + +static const struct of_device_id ov490_of_ids[] = { + { .compatible = "ovti,ov490-ov10640", }, + { } +}; +MODULE_DEVICE_TABLE(of, ov490_of_ids); + +static struct i2c_driver ov490_i2c_driver = { + .driver = { + .name = "ov490-ov10640", + .of_match_table = ov490_of_ids, + }, + .probe = ov490_probe, + .remove = ov490_remove, + .id_table = ov490_id, +}; + +module_i2c_driver(ov490_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for OV490-OV10640"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); +#endif diff --git a/drivers/media/i2c/soc_camera/ov490_ov10640.h b/drivers/media/i2c/soc_camera/ov490_ov10640.h new file mode 100644 index 0000000..d3290c7 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov490_ov10640.h @@ -0,0 +1,88 @@ +/* + * OmniVision ov490-ov10640 sensor camera wizard 1280x1080@30/UYVY/BT601/8bit + * + * Copyright (C) 2016-2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +struct ov490_reg { + u16 reg; + u8 val; +}; + +static const struct ov490_reg ov490_regs_wizard[] = { +/* The following registers should match firmware */ +{0xfffd, 0x80}, +{0xfffe, 0x82}, +{0x0071, 0x11}, +{0x0075, 0x11}, +{0xfffe, 0x29}, +{0x6010, 0x01}, +/* ov490 EMB line disable in YUV and RAW data, NOTE: EMB line is still used in ISP and sensor */ +{0xe000, 0x14}, +#if 0 /* do not disable EMB line in ISP! */ +{0x4017, 0x00}, +#endif +{0xfffe, 0x28}, +{0x6000, 0x04}, +{0x6004, 0x00}, +{0x6008, 0x00}, // PCLK polarity - useless due to silicon bug -> use 0x808000bb register +{0xfffe, 0x80}, +{0x0091, 0x00}, +{0x00bb, 0x1d}, // bit[3]=0 - PCLK polarity workaround +/* ov10640 EMB line disable */ +#if 0 /* do not disable EMB line in sensor! */ +{0xfffe, 0x19}, +{0x5000, 0x00}, +{0x5001, 0x30}, +{0x5002, 0x91}, +{0x5003, 0x08}, +{0xfffe, 0x80}, +{0x00c0, 0xc1}, +#endif +/* Ov490 FSIN: app_fsin_from_fsync */ +{0xfffe, 0x85}, +{0x0008, 0x00}, +{0x0009, 0x01}, +{0x000A, 0x05}, // fsin0 src +{0x000B, 0x00}, +{0x0030, 0x02}, // fsin0_delay +{0x0031, 0x00}, +{0x0032, 0x00}, +{0x0033, 0x00}, +{0x0038, 0x02}, // fsin1_delay +{0x0039, 0x00}, +{0x003A, 0x00}, +{0x003B, 0x00}, +{0x0070, 0x2C}, // fsin0_length +{0x0071, 0x01}, +{0x0072, 0x00}, +{0x0073, 0x00}, +{0x0074, 0x64}, // fsin1_length +{0x0075, 0x00}, +{0x0076, 0x00}, +{0x0077, 0x00}, +{0x0000, 0x14}, +{0x0001, 0x00}, +{0x0002, 0x00}, +{0x0003, 0x00}, +{0x0004, 0x32}, // load fsin0,load fsin1,load other, it will be cleared automatically. +{0x0005, 0x00}, +{0x0006, 0x00}, +{0x0007, 0x00}, +{0xfffe, 0x80}, +{0x0081, 0x00}, // 03;SENSOR FSIN +/* ov10640 FSIN */ +{0xfffd, 0x80}, +{0xfffe, 0x19}, +{0x5000, 0x00}, +{0x5001, 0x30}, +{0x5002, 0x8c}, +{0x5003, 0xb2}, +{0xfffe, 0x80}, +{0x00c0, 0xc1}, +}; diff --git a/drivers/media/i2c/soc_camera/ov495_ov2775.c b/drivers/media/i2c/soc_camera/ov495_ov2775.c new file mode 100644 index 0000000..881615e --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov495_ov2775.c @@ -0,0 +1,658 @@ +/* + * OmniVision ov495-ov2775 sensor camera driver + * + * Copyright (C) 2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ov495_ov2775.h" + +#define OV495_I2C_ADDR 0x24 + +#define OV495_PID 0x300a +#define OV495_VER 0x300b +#define OV495_VERSION_REG 0x0495 +#define OV495_VERSION(pid, ver) (((pid) << 8) | ((ver) & 0xff)) + +#define OV495_ISP_HSIZE_LOW 0x60 +#define OV495_ISP_HSIZE_HIGH 0x61 +#define OV495_ISP_VSIZE_LOW 0x62 +#define OV495_ISP_VSIZE_HIGH 0x63 + +struct ov495_priv { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct media_pad pad; + struct v4l2_rect rect; + int max_width; + int max_height; + int init_complete; + u8 id[6]; + int exposure; + int gain; + int autogain; + /* serializers */ + int max9286_addr; + int max9271_addr; + int ti960_addr; + int ti954_addr; + int ti9x3_addr; + int port; + int gpio_resetb; + int gpio_fsin; + +}; + +static int force_conf_link; + +static __init int ov495_force_conf_link(char *str) +{ + /* force configuration link */ + /* used only if robust firmware flashing required (f.e. recovery) */ + force_conf_link = 1; + return 0; +} +early_param("force_conf_link", ov495_force_conf_link); + +static inline struct ov495_priv *to_ov495(const struct i2c_client *client) +{ + return container_of(i2c_get_clientdata(client), struct ov495_priv, sd); +} + +static int ov495_set_regs(struct i2c_client *client, + const struct ov495_reg *regs, int nr_regs) +{ + int i; + + for (i = 0; i < nr_regs; i++) { + if (reg16_write(client, regs[i].reg, regs[i].val)) { + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, regs[i].reg, regs[i].val); + } + } + + return 0; +} + +static int ov495_s_stream(struct v4l2_subdev *sd, int enable) +{ + return 0; +} + +static int ov495_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov495_priv *priv = to_ov495(client); + + if (format->pad) + return -EINVAL; + + mf->width = priv->rect.width; + mf->height = priv->rect.height; + mf->code = MEDIA_BUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov495_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + + mf->code = MEDIA_BUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SMPTE170M; + mf->field = V4L2_FIELD_NONE; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + cfg->try_fmt = *mf; + + return 0; +} + +static int ov495_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUYV8_2X8; + + return 0; +} + +static int ov495_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov495_priv *priv = to_ov495(client); + + memcpy(edid->edid, priv->id, 6); + + edid->edid[6] = 0xff; + edid->edid[7] = client->addr; + edid->edid[8] = OV495_VERSION_REG >> 8; + edid->edid[9] = OV495_VERSION_REG & 0xff; + + return 0; +} + +static int ov495_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_rect *rect = &sel->r; + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov495_priv *priv = to_ov495(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + rect->left = ALIGN(rect->left, 2); + rect->top = ALIGN(rect->top, 2); + rect->width = ALIGN(rect->width, 2); + rect->height = ALIGN(rect->height, 2); + + if ((rect->left + rect->width > priv->max_width) || + (rect->top + rect->height > priv->max_height)) + *rect = priv->rect; + + priv->rect.left = rect->left; + priv->rect.top = rect->top; + priv->rect.width = rect->width; + priv->rect.height = rect->height; + + return 0; +} + +static int ov495_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov495_priv *priv = to_ov495(client); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = priv->max_width; + sel->r.height = priv->max_height; + return 0; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = priv->max_width; + sel->r.height = priv->max_height; + return 0; + case V4L2_SEL_TGT_CROP: + sel->r = priv->rect; + return 0; + default: + return -EINVAL; + } +} + +static int ov495_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + cfg->flags = V4L2_MBUS_CSI2_1_LANE | V4L2_MBUS_CSI2_CHANNEL_0 | + V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; + cfg->type = V4L2_MBUS_CSI2; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov495_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val = 0; + + ret = reg16_read(client, (u16)reg->reg, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = sizeof(u16); + + return 0; +} + +static int ov495_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + ret = reg16_write(client, (u16)reg->reg, (u8)reg->val); + if ((u8)reg->reg == 0xFFFD) + usleep_range(100, 150); /* wait 100 us */ + if ((u8)reg->reg == 0xFFFE) + usleep_range(100, 150); /* wait 100 us */ + return ret; +} +#endif + +static struct v4l2_subdev_core_ops ov495_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov495_g_register, + .s_register = ov495_s_register, +#endif +}; + +static int ov495_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov495_priv *priv = to_ov495(client); + int ret = -EINVAL; + + if (!priv->init_complete) + return 0; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + break; + case V4L2_CID_CONTRAST: + break; + case V4L2_CID_SATURATION: + break; + case V4L2_CID_HUE: + break; + case V4L2_CID_GAMMA: + break; + case V4L2_CID_SHARPNESS: + break; + case V4L2_CID_AUTOGAIN: + case V4L2_CID_GAIN: + case V4L2_CID_EXPOSURE: + break; + case V4L2_CID_HFLIP: + ret = reg16_write(client, 0x3516, 0x00); + ret |= reg16_write(client, 0x0ffc, 0x00); + ret |= reg16_write(client, 0x0500, ctrl->val); + ret |= reg16_write(client, 0x0501, 0x00); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x30C0, 0xdc); + ret |= reg16_write(client, 0x3516, 0x01); + break; + case V4L2_CID_VFLIP: + ret = reg16_write(client, 0x3516, 0x00); + ret |= reg16_write(client, 0x0ffc, 0x00); + ret |= reg16_write(client, 0x0500, ctrl->val); + ret |= reg16_write(client, 0x0501, 0x01); + usleep_range(100, 150); /* wait 100 us */ + ret |= reg16_write(client, 0x30C0, 0xdc); + ret |= reg16_write(client, 0x3516, 0x01); + break; + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + ret = 0; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ov495_ctrl_ops = { + .s_ctrl = ov495_s_ctrl, +}; + +static struct v4l2_subdev_video_ops ov495_video_ops = { + .s_stream = ov495_s_stream, + .g_mbus_config = ov495_g_mbus_config, +}; + +static const struct v4l2_subdev_pad_ops ov495_subdev_pad_ops = { + .get_edid = ov495_get_edid, + .enum_mbus_code = ov495_enum_mbus_code, + .get_selection = ov495_get_selection, + .set_selection = ov495_set_selection, + .get_fmt = ov495_get_fmt, + .set_fmt = ov495_set_fmt, +}; + +static struct v4l2_subdev_ops ov495_subdev_ops = { + .core = &ov495_core_ops, + .video = &ov495_video_ops, + .pad = &ov495_subdev_pad_ops, +}; + +static void ov495_otp_id_read(struct i2c_client *client) +{ + struct ov495_priv *priv = to_ov495(client); + int i; + +#if 0 + /* read camera id from ov495 OTP memory */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x20); + usleep_range(100, 150); /* wait 100 us */ + reg16_write(client, 0x7384, 0x40); /* manual mode, bank#0 */ + reg16_write(client, 0x7381, 1); /* start OTP read */ + + usleep_range(25000, 26000); /* wait 25 ms */ + + for (i = 0; i < 6; i++) + reg16_read(client, 0x7300 + i + 4, &priv->id[i]); +#else + /* read camera id from ov2775 OTP memory */ + reg16_write(client, 0x3516, 0x00); /* unlock write */ + reg16_write(client, 0x0FFC, 0); + reg16_write(client, 0x0500, 0x00); /* write 0x34a1 -> 1 */ + reg16_write(client, 0x0501, 0x34); + reg16_write(client, 0x0502, 0xa1); + reg16_write(client, 0x0503, 1); + reg16_write(client, 0x30C0, 0xc1); + + usleep_range(25000, 25500); /* wait 25 ms */ + + for (i = 0; i < 6; i++) { + reg16_write(client, 0x3516, 0x00); /* unlock write */ + reg16_write(client, 0x0500, 0x01); /* read (0x7a00 + i) */ + reg16_write(client, 0x0501, 0x7a); + reg16_write(client, 0x0502, 0x00 + i + (i < 3 ? 11 : 3)); /* take bytes 11,12,13,6,7,8 */ + reg16_write(client, 0x30C0, 0xc1); + usleep_range(1000, 1500); /* wait 1 ms */ + reg16_read(client, 0x0500, &priv->id[i]); + } +#endif +} + +static ssize_t ov495_otp_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(to_i2c_client(dev)); + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov495_priv *priv = to_ov495(client); + + return snprintf(buf, 32, "%02x:%02x:%02x:%02x:%02x:%02x\n", + priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]); +} + +static DEVICE_ATTR(otp_id_ov495, S_IRUGO, ov495_otp_id_show, NULL); + +static int ov495_initialize(struct i2c_client *client) +{ + struct ov495_priv *priv = to_ov495(client); + u8 pid = 0, ver = 0; + int ret = 0; + + /* check and show product ID and manufacturer ID */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x80); + usleep_range(100, 150); /* wait 100 us */ + reg16_read(client, OV495_PID, &pid); + reg16_read(client, OV495_VER, &ver); + + if (OV495_VERSION(pid, ver) != OV495_VERSION_REG) { + dev_err(&client->dev, "Product ID error %x:%x\n", pid, ver); + ret = -ENODEV; + goto err; + } + + if (unlikely(force_conf_link)) + goto out; + +#if 0 + /* read resolution used by current firmware */ + reg16_write(client, 0xFFFD, 0x80); + reg16_write(client, 0xFFFE, 0x82); + usleep_range(100, 150); /* wait 100 us */ + reg16_read(client, OV495_ISP_HSIZE_HIGH, &val); + priv->max_width = val; + reg16_read(client, OV495_ISP_HSIZE_LOW, &val); + priv->max_width = (priv->max_width << 8) | val; + reg16_read(client, OV495_ISP_VSIZE_HIGH, &val); + priv->max_height = val; + reg16_read(client, OV495_ISP_VSIZE_LOW, &val); + priv->max_height = (priv->max_height << 8) | val; +#else + priv->max_width = 1920; + priv->max_height = 1080; +#endif + + /* set virtual channel */ + ov495_regs_wizard[3].val = 0x1e | (priv->port << 6); + /* Program wizard registers */ + ov495_set_regs(client, ov495_regs_wizard, ARRAY_SIZE(ov495_regs_wizard)); + /* Read OTP IDs */ + ov495_otp_id_read(client); + +out: + dev_info(&client->dev, "ov495/ov2775 PID %x%x, res %dx%d, OTP_ID %02x:%02x:%02x:%02x:%02x:%02x\n", + pid, ver, priv->max_width, priv->max_height, priv->id[0], priv->id[1], priv->id[2], priv->id[3], priv->id[4], priv->id[5]); +err: + return ret; +} + +static int ov495_parse_dt(struct device_node *np, struct ov495_priv *priv) +{ + struct i2c_client *client = v4l2_get_subdevdata(&priv->sd); + int i; + struct device_node *endpoint = NULL, *rendpoint = NULL; + int tmp_addr = 0; + + for (i = 0; ; i++) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + if (!endpoint) + break; + + of_node_put(endpoint); + + rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0); + if (!rendpoint) + continue; + + if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) && + !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti964-ti9x3") && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti960_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + + if (!of_property_read_u32(rendpoint, "ti9x3-addr", &priv->ti9x3_addr) && + !of_property_match_string(rendpoint->parent->parent, "compatible", "ti,ti954-ti9x3") && + !of_property_read_u32(rendpoint->parent->parent, "reg", &priv->ti954_addr) && + !kstrtouint(strrchr(rendpoint->full_name, '@') + 1, 0, &priv->port)) + break; + } + + if (!priv->ti960_addr && !priv->ti954_addr) { + dev_err(&client->dev, "deserializer does not present\n"); + return -EINVAL; + } + + /* setup I2C translator address */ + tmp_addr = client->addr; + if (priv->ti960_addr) { + client->addr = priv->ti960_addr; /* Deserializer I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x65, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x5d, OV495_I2C_ADDR << 1); /* Sensor native I2C address */ + + reg8_write(client, 0x6e, 0x9a); /* GPIO0 - fsin, GPIO1 - resetb */ + /* TODO: why too long? move logic to workqueue? */ + mdelay(350); /* time needed to boot all sensor IPs */ + } + if (priv->ti954_addr) { + client->addr = priv->ti954_addr; /* Deserializer I2C address */ + + reg8_write(client, 0x4c, (priv->port << 4) | (1 << priv->port)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x65, tmp_addr << 1); /* Sensor translated I2C address */ + reg8_write(client, 0x5d, OV495_I2C_ADDR << 1); /* Sensor native I2C address */ + + reg8_write(client, 0x6e, 0x9a); /* GPIO0 - fsin, GPIO1 - resetb */ + /* TODO: why too long? move logic to workqueue? */ + mdelay(350); /* time needed to boot all sensor IPs */ + } + client->addr = tmp_addr; + + return 0; +} + +static int ov495_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov495_priv *priv; + struct v4l2_ctrl *ctrl; + int ret; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + v4l2_i2c_subdev_init(&priv->sd, client, &ov495_subdev_ops); + priv->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + priv->exposure = 0x100; + priv->gain = 0x100; + priv->autogain = 1; + v4l2_ctrl_handler_init(&priv->hdl, 4); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 16, 1, 7); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_CONTRAST, 0, 16, 1, 7); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_SATURATION, 0, 7, 1, 2); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_HUE, 0, 23, 1, 12); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_GAMMA, -128, 128, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_SHARPNESS, 0, 10, 1, 3); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, priv->autogain); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_GAIN, 0, 0xffff, 1, priv->gain); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 0xffff, 1, priv->exposure); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + ctrl = v4l2_ctrl_new_std(&priv->hdl, &ov495_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 9); + if (ctrl) + ctrl->flags &= ~V4L2_CTRL_FLAG_READ_ONLY; + priv->sd.ctrl_handler = &priv->hdl; + + ret = priv->hdl.error; + if (ret) + goto cleanup; + + v4l2_ctrl_handler_setup(&priv->hdl); + + priv->pad.flags = MEDIA_PAD_FL_SOURCE; + priv->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&priv->sd.entity, 1, &priv->pad); + if (ret < 0) + goto cleanup; + + ret = ov495_parse_dt(client->dev.of_node, priv); + if (ret) + goto cleanup; + + ret = ov495_initialize(client); + if (ret < 0) + goto cleanup; + + priv->rect.left = 0; + priv->rect.top = 0; + priv->rect.width = priv->max_width; + priv->rect.height = priv->max_height; + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) + goto cleanup; + + if (device_create_file(&client->dev, &dev_attr_otp_id_ov495) != 0) { + dev_err(&client->dev, "sysfs otp_id entry creation failed\n"); + goto cleanup; + } + + priv->init_complete = 1; + + return 0; + +cleanup: + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); +#ifdef CONFIG_SOC_CAMERA_OV495_OV2775 + v4l_err(client, "failed to probe @ 0x%02x (%s)\n", + client->addr, client->adapter->name); +#endif + return ret; +} + +static int ov495_remove(struct i2c_client *client) +{ + struct ov495_priv *priv = i2c_get_clientdata(client); + + device_remove_file(&client->dev, &dev_attr_otp_id_ov495); + v4l2_async_unregister_subdev(&priv->sd); + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->hdl); + v4l2_device_unregister_subdev(&priv->sd); + + return 0; +} + +#ifdef CONFIG_SOC_CAMERA_OV495_OV2775 +static const struct i2c_device_id ov495_id[] = { + { "ov495-ov2775", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov495_id); + +static const struct of_device_id ov495_of_ids[] = { + { .compatible = "ovti,ov495-ov2775", }, + { } +}; +MODULE_DEVICE_TABLE(of, ov495_of_ids); + +static struct i2c_driver ov495_i2c_driver = { + .driver = { + .name = "ov495-ov2775", + .of_match_table = ov495_of_ids, + }, + .probe = ov495_probe, + .remove = ov495_remove, + .id_table = ov495_id, +}; + +module_i2c_driver(ov495_i2c_driver); + +MODULE_DESCRIPTION("SoC Camera driver for OV495-OV2775"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); +#endif diff --git a/drivers/media/i2c/soc_camera/ov495_ov2775.h b/drivers/media/i2c/soc_camera/ov495_ov2775.h new file mode 100644 index 0000000..3f53689 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ov495_ov2775.h @@ -0,0 +1,23 @@ +/* + * OmniVision ov495-ov2775 sensor camera wizard 1280x1080@30/UYVY/BT601/8bit + * + * Copyright (C) 2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +struct ov495_reg { + u16 reg; + u8 val; +}; + +static struct ov495_reg ov495_regs_wizard[] = { +{0x3516, 0x00}, /* unlock write */ +{0xFFFD, 0x80}, +{0xFFFE, 0x20}, +{0x8017, 0x1e | (0 << 6)}, +{0x7c10, 0x01}, /* UYVY */ +}; diff --git a/drivers/media/i2c/soc_camera/ti954_ti9x3.c b/drivers/media/i2c/soc_camera/ti954_ti9x3.c new file mode 100644 index 0000000..fc7ccda --- /dev/null +++ b/drivers/media/i2c/soc_camera/ti954_ti9x3.c @@ -0,0 +1,417 @@ +/* + * TI ti954-(ti913/ti953) FPDLinkIII driver + * + * Copyright (C) 2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ti9x4_ti9x3.h" + +struct ti954_ti9x3_priv { + struct v4l2_subdev sd[4]; + struct device_node *sd_of_node[4]; + int des_addr; + int links; + int lanes; + int csi_rate; + const char *forwarding_mode; + const char *cable_mode; + atomic_t use_count; + struct i2c_client *client; + int ti9x3_addr_map[4]; + char chip_id[6]; + int xtal_gpio; +}; + +static int indirect_write(struct i2c_client *client, unsigned int page, u8 reg, u8 val) +{ + if (page > 7) + return -EINVAL; + + reg8_write(client, 0xb0, page << 2); + reg8_write(client, 0xb1, reg); + reg8_write(client, 0xb2, val); + + return 0; +} + +#if 0 +static int indirect_read(struct i2c_client *client, unsigned int page, u8 reg, u8 *val) +{ + if (page > 7) + return -EINVAL; + + reg8_write(client, 0xb0, page << 2); + reg8_write(client, 0xb1, reg); + reg8_read(client, 0xb2, val); + + return 0; +} +#endif + +static void ti954_ti9x3_read_chipid(struct i2c_client *client) +{ + struct ti954_ti9x3_priv *priv = i2c_get_clientdata(client); + + /* Chip ID */ + reg8_read(client, 0xf1, &priv->chip_id[0]); + reg8_read(client, 0xf2, &priv->chip_id[1]); + reg8_read(client, 0xf3, &priv->chip_id[2]); + reg8_read(client, 0xf4, &priv->chip_id[3]); + reg8_read(client, 0xf5, &priv->chip_id[4]); + priv->chip_id[5] = '\0'; +} + +static void ti954_ti9x3_initial_setup(struct i2c_client *client) +{ + struct ti954_ti9x3_priv *priv = i2c_get_clientdata(client); + + /* Initial setup */ + client->addr = priv->des_addr; /* TI954 I2C */ + reg8_write(client, 0x08, 0x1c); /* I2C glitch filter depth */ + reg8_write(client, 0x0a, 0x79); /* I2C high pulse width */ + reg8_write(client, 0x0b, 0x79); /* I2C low pulse width */ + reg8_write(client, 0x0d, 0xb9); /* VDDIO 3.3V */ + switch (priv->csi_rate) { + case 1600: /* REFCLK = 25MHZ */ + case 1450: /* REFCLK = 22.5MHZ */ + reg8_write(client, 0x1f, 0x00); /* CSI rate 1.5/1.6Gbps */ + break; + case 800: /* REFCLK = 25MHZ */ + reg8_write(client, 0x1f, 0x02); /* CSI rate 800Mbps */ + break; + case 400: /* REFCLK = 25MHZ */ + reg8_write(client, 0x1f, 0x03); /* CSI rate 400Mbps */ + break; + default: + dev_err(&client->dev, "unsupported CSI rate %d\n", priv->csi_rate); + } + + if (strcmp(priv->forwarding_mode, "round-robin") == 0) { + reg8_write(client, 0x21, 0x01); /* Round Robin forwarding enable */ + } else if (strcmp(priv->forwarding_mode, "synchronized") == 0) { + reg8_write(client, 0x21, 0x44); /* Basic Syncronized forwarding enable (FrameSync must be enabled!!) */ + } + + reg8_write(client, 0x32, 0x01); /* Select TX (CSI) port 0 */ + reg8_write(client, 0x33, ((priv->lanes - 1) ^ 0x3) << 4); /* disable CSI output, set CSI lane count, non-continuous CSI mode */ + reg8_write(client, 0x20, 0xf0); /* disable port forwarding */ +#if 0 + /* FrameSync setup for REFCLK=25MHz, FPS=30: period_counts=1/2/FPS*25MHz =1/2/30*25Mhz =416666 -> FS_TIME=416666 */ + /* FrameSync setup for REFCLK=22.5MHz, FPS=30: period_counts=1/2/FPS*22.5Mhz=1/2/30*22.5Mhz=375000 -> FS_TIME=375000 */ +// #define FS_TIME (priv->csi_rate == 1450 ? 376000 : 417666) + #define FS_TIME (priv->csi_rate == 1450 ? 385000 : 428000) // FPS=29.2 (new vendor's firmware AWB restriction?) + reg8_write(client, 0x1a, FS_TIME >> 16); /* FrameSync time 24bit */ + reg8_write(client, 0x1b, (FS_TIME >> 8) & 0xff); + reg8_write(client, 0x1c, FS_TIME & 0xff); + reg8_write(client, 0x18, 0x43); /* Enable FrameSync, 50/50 mode, Frame clock from 25MHz */ +#else + /* FrameSync setup for REFCLK=25MHz, FPS=30: period_counts=1/FPS/12mks=1/30/12e-6=2777 -> HI=2, LO=2775 */ + /* FrameSync setup for REFCLK=22.5MHz, FPS=30: period_counts=1/FPS/13.333mks=1/30/13.333e-6=2500 -> HI=2, LO=2498 */ + #define FS_TIME (priv->csi_rate == 1450 ? (2498+15) : (2775+15)) + reg8_write(client, 0x19, 2 >> 8); /* FrameSync high time MSB */ + reg8_write(client, 0x1a, 2 & 0xff); /* FrameSync high time LSB */ + reg8_write(client, 0x1b, FS_TIME >> 8); /* FrameSync low time MSB */ + reg8_write(client, 0x1c, FS_TIME & 0xff); /* FrameSync low time LSB */ + reg8_write(client, 0x18, 0x01); /* Enable FrameSync, HI/LO mode, Frame clock from port0 */ +#endif +} + +//#define SENSOR_ID 0x30 // ov10635 +//#define SENSOR_ID 0x24 // ov490 + +static void ti954_ti9x3_fpdlink3_setup(struct i2c_client *client, int idx) +{ + struct ti954_ti9x3_priv *priv = i2c_get_clientdata(client); + + /* FPDLinkIII setup */ + client->addr = priv->des_addr; /* TI954 I2C */ + reg8_write(client, 0x4c, (idx << 4) | (1 << idx)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x58, 0x58); /* Back channel: pass-through/backchannel/CRC enable, Freq=2.5Mbps */ + reg8_write(client, 0x5c, priv->ti9x3_addr_map[idx] << 1); /* TI9X3 I2C addr */ +// reg8_write(client, 0x5d, SENSOR_ID << 1); /* SENSOR I2C native - must be set by sensor driver */ +// reg8_write(client, 0x65, (0x60 + idx) << 1); /* SENSOR I2C translated - must be set by sensor driver */ + if (strcmp(priv->cable_mode, "coax") == 0) { + reg8_write(client, 0x6d, 0x7f); /* Coax, RAW10 */ + } else if (strcmp(priv->cable_mode, "stp") == 0) { + reg8_write(client, 0x6d, 0x78); /* STP, CSI */ + } + reg8_write(client, 0x70, (idx << 6) | 0x1e); /* CSI data type: yuv422 8-bit, assign VC */ + reg8_write(client, 0x7c, 0x81); /* BIT(7) - magic to Use RAW10 as 8-bit mode */ + reg8_write(client, 0x6e, 0x88); /* Sensor reset: backchannel GPIO0/GPIO1 set low */ +} + +static int ti954_ti9x3_initialize(struct i2c_client *client) +{ + struct ti954_ti9x3_priv *priv = i2c_get_clientdata(client); + int idx; + + dev_info(&client->dev, "LINKs=%d, LANES=%d, FORWARDING=%s, CABLE=%s, ID=%s\n", + priv->links, priv->lanes, priv->forwarding_mode, priv->cable_mode, priv->chip_id); + + ti954_ti9x3_initial_setup(client); + + for (idx = 0; idx < priv->links; idx++) + ti954_ti9x3_fpdlink3_setup(client, idx); + + client->addr = priv->des_addr; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ti954_ti9x3_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct ti954_ti9x3_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + int ret; + u8 val = 0; + + ret = reg8_read(client, (u8)reg->reg, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = sizeof(u8); + + return 0; +} + +static int ti954_ti9x3_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct ti954_ti9x3_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + + return reg8_write(client, (u8)reg->reg, (u8)reg->val); +} +#endif + +static int ti954_ti9x3_s_power(struct v4l2_subdev *sd, int on) +{ + struct ti954_ti9x3_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + + if (on) { + if (atomic_inc_return(&priv->use_count) == 1) + reg8_write(client, 0x20, 0x00); /* enable port forwarding to CSI */ + } else { + if (atomic_dec_return(&priv->use_count) == 0) + reg8_write(client, 0x20, 0xf0); /* disable port forwarding to CSI */ + } + + return 0; +} + +static int ti954_ti9x3_registered_async(struct v4l2_subdev *sd) +{ + struct ti954_ti9x3_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + + reg8_write(client, 0x33, ((priv->lanes - 1) ^ 0x3) << 4 | 0x1); /* enable CSI output, set CSI lane count, non-continuous CSI mode */ + + return 0; +} + +static struct v4l2_subdev_core_ops ti954_ti9x3_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ti954_ti9x3_g_register, + .s_register = ti954_ti9x3_s_register, +#endif + .s_power = ti954_ti9x3_s_power, + .registered_async = ti954_ti9x3_registered_async, +}; + +static struct v4l2_subdev_ops ti954_ti9x3_subdev_ops = { + .core = &ti954_ti9x3_subdev_core_ops, +}; + +static int ti954_ti9x3_parse_dt(struct i2c_client *client) +{ + struct ti954_ti9x3_priv *priv = i2c_get_clientdata(client); + struct device_node *np = client->dev.of_node; + struct device_node *endpoint = NULL, *rendpoint = NULL; + struct property *prop; + int err, i; + int sensor_delay; + char forwarding_mode_default[20] = "round-robin"; /* round-robin, synchronized */ + char cable_mode_default[5] = "coax"; /* coax, stp */ + struct property *csi_rate_prop, *dvp_order_prop; + u8 val = 0; + + if (of_property_read_u32(np, "ti,links", &priv->links)) + priv->links = 2; + + if (of_property_read_u32(np, "ti,lanes", &priv->lanes)) + priv->lanes = 4; + + priv->xtal_gpio = of_get_gpio(np, 0); + if (priv->xtal_gpio > 0) { + err = devm_gpio_request_one(&client->dev, priv->xtal_gpio, GPIOF_OUT_INIT_LOW, dev_name(&client->dev)); + if (err) + dev_err(&client->dev, "cannot request XTAL gpio %d: %d\n", priv->xtal_gpio, err); + else + mdelay(250); + } + + reg8_read(client, 0x00, &val); /* read TI954 I2C address */ + if (val != (priv->des_addr << 1)) { + prop = of_find_property(np, "reg", NULL); + if (prop) + of_remove_property(np, prop); + return -ENODEV; + } + + ti954_ti9x3_read_chipid(client); + + indirect_write(client, 7, 0x15, 0x30); + gpio_set_value(priv->xtal_gpio, 1); + usleep_range(5000, 5500); /* wait 5ms */ + indirect_write(client, 7, 0x15, 0); + + if (!of_property_read_u32(np, "ti,sensor_delay", &sensor_delay)) + mdelay(sensor_delay); + + err = of_property_read_string(np, "ti,forwarding-mode", &priv->forwarding_mode); + if (err) + priv->forwarding_mode = forwarding_mode_default; + + err = of_property_read_string(np, "ti,cable-mode", &priv->cable_mode); + if (err) + priv->cable_mode = cable_mode_default; + + for (i = 0; ; i++) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + if (!endpoint) + break; + + of_node_put(endpoint); + + if (i < priv->links) { + if (of_property_read_u32(endpoint, "ti9x3-addr", &priv->ti9x3_addr_map[i])) { + dev_err(&client->dev, "ti9x3-addr not set\n"); + return -EINVAL; + } + priv->sd_of_node[i] = endpoint; + } + + rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0); + if (!rendpoint) + continue; + + csi_rate_prop = of_find_property(endpoint, "csi-rate", NULL); + if (csi_rate_prop) { + of_property_read_u32(endpoint, "csi-rate", &priv->csi_rate); + of_update_property(rendpoint, csi_rate_prop); + } + + dvp_order_prop = of_find_property(endpoint, "dvp-order", NULL); + if (dvp_order_prop) + of_update_property(rendpoint, dvp_order_prop); + } + + return 0; +} + +static int ti954_ti9x3_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ti954_ti9x3_priv *priv; + int err, i; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(client, priv); + priv->des_addr = client->addr; + priv->client = client; + atomic_set(&priv->use_count, 0); + + err = ti954_ti9x3_parse_dt(client); + if (err) + goto out; + + err = ti954_ti9x3_initialize(client); + if (err < 0) + goto out; + + for (i = 0; i < priv->links; i++) { + v4l2_subdev_init(&priv->sd[i], &ti954_ti9x3_subdev_ops); + priv->sd[i].owner = client->dev.driver->owner; + priv->sd[i].dev = &client->dev; + priv->sd[i].grp_id = i; + v4l2_set_subdevdata(&priv->sd[i], priv); + priv->sd[i].of_node = priv->sd_of_node[i]; + + snprintf(priv->sd[i].name, V4L2_SUBDEV_NAME_SIZE, "%s %d-%04x", + client->dev.driver->name, i2c_adapter_id(client->adapter), + client->addr); + + err = v4l2_async_register_subdev(&priv->sd[i]); + if (err < 0) + goto out; + } + +out: + return err; +} + +static int ti954_ti9x3_remove(struct i2c_client *client) +{ + struct ti954_ti9x3_priv *priv = i2c_get_clientdata(client); + int i; + + for (i = 0; i < priv->links; i++) { + v4l2_async_unregister_subdev(&priv->sd[i]); + v4l2_device_unregister_subdev(&priv->sd[i]); + } + + return 0; +} + +static const struct of_device_id ti954_ti9x3_dt_ids[] = { + { .compatible = "ti,ti954-ti9x3" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ti954_ti9x3_dt_ids); + +static const struct i2c_device_id ti954_ti9x3_id[] = { + { "ti954_ti9x3", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ti954_ti9x3_id); + +static struct i2c_driver ti954_ti9x3_i2c_driver = { + .driver = { + .name = "ti954_ti9x3", + .of_match_table = of_match_ptr(ti954_ti9x3_dt_ids), + }, + .probe = ti954_ti9x3_probe, + .remove = ti954_ti9x3_remove, + .id_table = ti954_ti9x3_id, +}; + +module_i2c_driver(ti954_ti9x3_i2c_driver); + +MODULE_DESCRIPTION("FPDLinkIII driver for TI954-TI9X3"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/soc_camera/ti964_ti9x3.c b/drivers/media/i2c/soc_camera/ti964_ti9x3.c new file mode 100644 index 0000000..8dd0f99 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ti964_ti9x3.c @@ -0,0 +1,385 @@ +/* + * TI (ti964/ti960)-(ti913/ti953) FPDLinkIII driver + * + * Copyright (C) 2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ti9x4_ti9x3.h" + +struct ti964_ti9x3_priv { + struct v4l2_subdev sd[4]; + struct device_node *sd_of_node[4]; + int des_addr; + int links; + int lanes; + int csi_rate; + const char *forwarding_mode; + const char *cable_mode; + atomic_t use_count; + struct i2c_client *client; + int ti9x3_addr_map[4]; + char chip_id[6]; +}; + +static void ti964_ti9x3_read_chipid(struct i2c_client *client) +{ + struct ti964_ti9x3_priv *priv = i2c_get_clientdata(client); + + /* Chip ID */ + reg8_read(client, 0xf1, &priv->chip_id[0]); + reg8_read(client, 0xf2, &priv->chip_id[1]); + reg8_read(client, 0xf3, &priv->chip_id[2]); + reg8_read(client, 0xf4, &priv->chip_id[3]); + reg8_read(client, 0xf5, &priv->chip_id[4]); + priv->chip_id[5] = '\0'; +} + +static void ti964_ti9x3_initial_setup(struct i2c_client *client) +{ + struct ti964_ti9x3_priv *priv = i2c_get_clientdata(client); + + /* Initial setup */ + client->addr = priv->des_addr; /* TI964 I2C */ + reg8_write(client, 0x08, 0x1c); /* I2C glitch filter depth */ + reg8_write(client, 0x0a, 0x79); /* I2C high pulse width */ + reg8_write(client, 0x0b, 0x79); /* I2C low pulse width */ + reg8_write(client, 0x0d, 0xb9); /* VDDIO 3.3V */ + switch (priv->csi_rate) { + case 1600: /* REFCLK = 25MHZ */ + case 1450: /* REFCLK = 22.5MHZ */ + reg8_write(client, 0x1f, 0x00); /* CSI rate 1.5/1.6Gbps */ + break; + case 800: /* REFCLK = 25MHZ */ + reg8_write(client, 0x1f, 0x02); /* CSI rate 800Mbps */ + break; + case 400: /* REFCLK = 25MHZ */ + reg8_write(client, 0x1f, 0x03); /* CSI rate 400Mbps */ + break; + default: + dev_err(&client->dev, "unsupported CSI rate %d\n", priv->csi_rate); + } + + if (strcmp(priv->forwarding_mode, "round-robin") == 0) { + reg8_write(client, 0x21, 0x01); /* Round Robin forwarding enable */ + } else if (strcmp(priv->forwarding_mode, "synchronized") == 0) { + reg8_write(client, 0x21, 0x44); /* Basic Syncronized forwarding enable (FrameSync must be enabled!!) */ + } + + reg8_write(client, 0x32, 0x01); /* Select TX (CSI) port 0 */ + reg8_write(client, 0x33, ((priv->lanes - 1) ^ 0x3) << 4); /* disable CSI output, set CSI lane count, non-continuous CSI mode */ + reg8_write(client, 0x20, 0xf0); /* disable port forwarding */ +#if 0 + /* FrameSync setup for REFCLK=25MHz, FPS=30: period_counts=1/2/FPS*25MHz =1/2/30*25Mhz =416666 -> FS_TIME=416666 */ + /* FrameSync setup for REFCLK=22.5MHz, FPS=30: period_counts=1/2/FPS*22.5Mhz=1/2/30*22.5Mhz=375000 -> FS_TIME=375000 */ +// #define FS_TIME (priv->csi_rate == 1450 ? 376000 : 417666) + #define FS_TIME (priv->csi_rate == 1450 ? 385000 : 428000) // FPS=29.2 (new vendor's firmware AWB restriction?) + reg8_write(client, 0x1a, FS_TIME >> 16); /* FrameSync time 24bit */ + reg8_write(client, 0x1b, (FS_TIME >> 8) & 0xff); + reg8_write(client, 0x1c, FS_TIME & 0xff); + reg8_write(client, 0x18, 0x43); /* Enable FrameSync, 50/50 mode, Frame clock from 25MHz */ +#else + /* FrameSync setup for REFCLK=25MHz, FPS=30: period_counts=1/FPS/12mks=1/30/12e-6=2777 -> HI=2, LO=2775 */ + /* FrameSync setup for REFCLK=22.5MHz, FPS=30: period_counts=1/FPS/13.333mks=1/30/13.333e-6=2500 -> HI=2, LO=2498 */ + #define FS_TIME (priv->csi_rate == 1450 ? (2498+15) : (2775+15)) + reg8_write(client, 0x19, 2 >> 8); /* FrameSync high time MSB */ + reg8_write(client, 0x1a, 2 & 0xff); /* FrameSync high time LSB */ + reg8_write(client, 0x1b, FS_TIME >> 8); /* FrameSync low time MSB */ + reg8_write(client, 0x1c, FS_TIME & 0xff); /* FrameSync low time LSB */ + reg8_write(client, 0x18, 0x01); /* Enable FrameSync, HI/LO mode, Frame clock from port0 */ +#endif +} + +//#define SENSOR_ID 0x30 // ov10635 +//#define SENSOR_ID 0x24 // ov490 + +static void ti964_ti9x3_fpdlink3_setup(struct i2c_client *client, int idx) +{ + struct ti964_ti9x3_priv *priv = i2c_get_clientdata(client); + + /* FPDLinkIII setup */ + client->addr = priv->des_addr; /* TI964 I2C */ + reg8_write(client, 0x4c, (idx << 4) | (1 << idx)); /* Select RX port number */ + usleep_range(2000, 2500); /* wait 2ms */ + reg8_write(client, 0x58, 0x58); /* Back channel: pass-through/backchannel/CRC enable, Freq=2.5Mbps */ + reg8_write(client, 0x5c, priv->ti9x3_addr_map[idx] << 1); /* TI9X3 I2C addr */ +// reg8_write(client, 0x5d, SENSOR_ID << 1); /* SENSOR I2C native - must be set by sensor driver */ +// reg8_write(client, 0x65, (0x60 + idx) << 1); /* SENSOR I2C translated - must be set by sensor driver */ + if (strcmp(priv->cable_mode, "coax") == 0) { + reg8_write(client, 0x6d, 0x7f); /* Coax, RAW10 */ + } else if (strcmp(priv->cable_mode, "stp") == 0) { + reg8_write(client, 0x6d, 0x78); /* STP, CSI */ + } + reg8_write(client, 0x70, (idx << 6) | 0x1e); /* CSI data type: yuv422 8-bit, assign VC */ + reg8_write(client, 0x7c, 0x81); /* BIT(7) - magic to Use RAW10 as 8-bit mode */ + reg8_write(client, 0x6e, 0x88); /* Sensor reset: backchannel GPIO0/GPIO1 set low */ +} + +static int ti964_ti9x3_initialize(struct i2c_client *client) +{ + struct ti964_ti9x3_priv *priv = i2c_get_clientdata(client); + int idx; + + dev_info(&client->dev, "LINKs=%d, LANES=%d, FORWARDING=%s, CABLE=%s, ID=%s\n", + priv->links, priv->lanes, priv->forwarding_mode, priv->cable_mode, priv->chip_id); + + ti964_ti9x3_initial_setup(client); + + for (idx = 0; idx < priv->links; idx++) + ti964_ti9x3_fpdlink3_setup(client, idx); + + client->addr = priv->des_addr; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ti964_ti9x3_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct ti964_ti9x3_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + int ret; + u8 val = 0; + + ret = reg8_read(client, (u8)reg->reg, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = sizeof(u8); + + return 0; +} + +static int ti964_ti9x3_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct ti964_ti9x3_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + + return reg8_write(client, (u8)reg->reg, (u8)reg->val); +} +#endif + +static int ti964_ti9x3_s_power(struct v4l2_subdev *sd, int on) +{ + struct ti964_ti9x3_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + + if (on) { + if (atomic_inc_return(&priv->use_count) == 1) + reg8_write(client, 0x20, 0x00); /* enable port forwarding to CSI */ + } else { + if (atomic_dec_return(&priv->use_count) == 0) + reg8_write(client, 0x20, 0xf0); /* disable port forwarding to CSI */ + } + + return 0; +} + +static int ti964_ti9x3_registered_async(struct v4l2_subdev *sd) +{ + struct ti964_ti9x3_priv *priv = v4l2_get_subdevdata(sd); + struct i2c_client *client = priv->client; + + reg8_write(client, 0x33, ((priv->lanes - 1) ^ 0x3) << 4 | 0x1); /* enable CSI output, set CSI lane count, non-continuous CSI mode */ + + return 0; +} + +static struct v4l2_subdev_core_ops ti964_ti9x3_subdev_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ti964_ti9x3_g_register, + .s_register = ti964_ti9x3_s_register, +#endif + .s_power = ti964_ti9x3_s_power, + .registered_async = ti964_ti9x3_registered_async, +}; + +static struct v4l2_subdev_ops ti964_ti9x3_subdev_ops = { + .core = &ti964_ti9x3_subdev_core_ops, +}; + +static int ti964_ti9x3_parse_dt(struct i2c_client *client) +{ + struct ti964_ti9x3_priv *priv = i2c_get_clientdata(client); + struct device_node *np = client->dev.of_node; + struct device_node *endpoint = NULL, *rendpoint = NULL; + struct property *prop; + int err, pwen, i; + int sensor_delay; + char forwarding_mode_default[20] = "round-robin"; /* round-robin, synchronized */ + char cable_mode_default[5] = "coax"; /* coax, stp */ + struct property *csi_rate_prop, *dvp_order_prop; + u8 val = 0; + + if (of_property_read_u32(np, "ti,links", &priv->links)) + priv->links = 4; + + if (of_property_read_u32(np, "ti,lanes", &priv->lanes)) + priv->lanes = 4; + + pwen = of_get_gpio(np, 0); + if (pwen > 0) { + err = devm_gpio_request_one(&client->dev, pwen, GPIOF_OUT_INIT_HIGH, dev_name(&client->dev)); + if (err) + dev_err(&client->dev, "cannot request PWEN gpio %d: %d\n", pwen, err); + else + mdelay(250); + } + + reg8_read(client, 0x00, &val); /* read TI964 I2C address */ + if (val != (priv->des_addr << 1)) { + prop = of_find_property(np, "reg", NULL); + if (prop) + of_remove_property(np, prop); + return -ENODEV; + } + + ti964_ti9x3_read_chipid(client); + + if (!of_property_read_u32(np, "ti,sensor_delay", &sensor_delay)) + mdelay(sensor_delay); + + err = of_property_read_string(np, "ti,forwarding-mode", &priv->forwarding_mode); + if (err) + priv->forwarding_mode = forwarding_mode_default; + + err = of_property_read_string(np, "ti,cable-mode", &priv->cable_mode); + if (err) + priv->cable_mode = cable_mode_default; + + for (i = 0; ; i++) { + endpoint = of_graph_get_next_endpoint(np, endpoint); + if (!endpoint) + break; + + of_node_put(endpoint); + + if (i < priv->links) { + if (of_property_read_u32(endpoint, "ti9x3-addr", &priv->ti9x3_addr_map[i])) { + dev_err(&client->dev, "ti9x3-addr not set\n"); + return -EINVAL; + } + priv->sd_of_node[i] = endpoint; + } + + rendpoint = of_parse_phandle(endpoint, "remote-endpoint", 0); + if (!rendpoint) + continue; + + csi_rate_prop = of_find_property(endpoint, "csi-rate", NULL); + if (csi_rate_prop) { + of_property_read_u32(endpoint, "csi-rate", &priv->csi_rate); + of_update_property(rendpoint, csi_rate_prop); + } + + dvp_order_prop = of_find_property(endpoint, "dvp-order", NULL); + if (dvp_order_prop) + of_update_property(rendpoint, dvp_order_prop); + } + + return 0; +} + +static int ti964_ti9x3_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ti964_ti9x3_priv *priv; + int err, i; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(client, priv); + priv->des_addr = client->addr; + priv->client = client; + atomic_set(&priv->use_count, 0); + + err = ti964_ti9x3_parse_dt(client); + if (err) + goto out; + + err = ti964_ti9x3_initialize(client); + if (err < 0) + goto out; + + for (i = 0; i < priv->links; i++) { + v4l2_subdev_init(&priv->sd[i], &ti964_ti9x3_subdev_ops); + priv->sd[i].owner = client->dev.driver->owner; + priv->sd[i].dev = &client->dev; + priv->sd[i].grp_id = i; + v4l2_set_subdevdata(&priv->sd[i], priv); + priv->sd[i].of_node = priv->sd_of_node[i]; + + snprintf(priv->sd[i].name, V4L2_SUBDEV_NAME_SIZE, "%s %d-%04x", + client->dev.driver->name, i2c_adapter_id(client->adapter), + client->addr); + + err = v4l2_async_register_subdev(&priv->sd[i]); + if (err < 0) + goto out; + } + +out: + return err; +} + +static int ti964_ti9x3_remove(struct i2c_client *client) +{ + struct ti964_ti9x3_priv *priv = i2c_get_clientdata(client); + int i; + + for (i = 0; i < priv->links; i++) { + v4l2_async_unregister_subdev(&priv->sd[i]); + v4l2_device_unregister_subdev(&priv->sd[i]); + } + + return 0; +} + +static const struct of_device_id ti964_ti9x3_dt_ids[] = { + { .compatible = "ti,ti964-ti9x3" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ti964_ti9x3_dt_ids); + +static const struct i2c_device_id ti964_ti9x3_id[] = { + { "ti964_ti9x3", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ti964_ti9x3_id); + +static struct i2c_driver ti964_ti9x3_i2c_driver = { + .driver = { + .name = "ti964_ti9x3", + .of_match_table = of_match_ptr(ti964_ti9x3_dt_ids), + }, + .probe = ti964_ti9x3_probe, + .remove = ti964_ti9x3_remove, + .id_table = ti964_ti9x3_id, +}; + +module_i2c_driver(ti964_ti9x3_i2c_driver); + +MODULE_DESCRIPTION("FPDLinkIII driver for TI964-TI9X3"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/soc_camera/ti9x4_ti9x3.h b/drivers/media/i2c/soc_camera/ti9x4_ti9x3.h new file mode 100644 index 0000000..69d3728 --- /dev/null +++ b/drivers/media/i2c/soc_camera/ti9x4_ti9x3.h @@ -0,0 +1,153 @@ +/* + * TI FPDLinkIII driver include file + * + * Copyright (C) 2017 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _TI9X4_H +#define _TI9X4_H + +//#define DEBUG +#ifdef DEBUG +#undef dev_dbg +#define dev_dbg dev_info +#endif + +#define MAXIM_NUM_RETRIES 1 /* number of read/write retries */ +#define BROADCAST 0x6f + +static inline int reg8_read(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret, retries; + + for (retries = MAXIM_NUM_RETRIES; retries; retries--) { + ret = i2c_smbus_read_byte_data(client, reg); + if (!(ret < 0)) + break; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "read fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } else { + *val = ret; + } + + return ret < 0 ? ret : 0; +} + +static inline int reg8_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret, retries; + + for (retries = MAXIM_NUM_RETRIES; retries; retries--) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (!(ret < 0)) + break; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "write fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } + + return ret < 0 ? ret : 0; +} + +static inline int reg16_read(struct i2c_client *client, u16 reg, u8 *val) +{ + int ret, retries; + u8 buf[2] = {reg >> 8, reg & 0xff}; + + for (retries = MAXIM_NUM_RETRIES; retries; retries--) { + ret = i2c_master_send(client, buf, 2); + if (ret == 2) { + ret = i2c_master_recv(client, buf, 1); + if (ret == 1) + break; + } + } + + if (ret < 0) { + dev_dbg(&client->dev, + "read fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } else { + *val = buf[0]; + } + + return ret < 0 ? ret : 0; +} + +static inline int reg16_write(struct i2c_client *client, u16 reg, u8 val) +{ + int ret, retries; + u8 buf[3] = {reg >> 8, reg & 0xff, val}; + + for (retries = MAXIM_NUM_RETRIES; retries; retries--) { + ret = i2c_master_send(client, buf, 3); + if (ret == 3) + break; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "write fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } + + return ret < 0 ? ret : 0; +} + +static inline int reg16_read16(struct i2c_client *client, u16 reg, u16 *val) +{ + int ret, retries; + u8 buf[2] = {reg >> 8, reg & 0xff}; + + for (retries = MAXIM_NUM_RETRIES; retries; retries--) { + ret = i2c_master_send(client, buf, 2); + if (ret == 2) { + ret = i2c_master_recv(client, buf, 2); + if (ret == 2) + break; + } + } + + if (ret < 0) { + dev_err(&client->dev, + "read fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } else { + *val = ((u16)buf[0] << 8) | buf[1]; + } + + return ret < 0 ? ret : 0; +} + +static inline int reg16_write16(struct i2c_client *client, u16 reg, u16 val) +{ + int ret, retries; + u8 buf[4] = {reg >> 8, reg & 0xff, val >> 8, val & 0xff}; + + for (retries = MAXIM_NUM_RETRIES; retries; retries--) { + ret = i2c_master_send(client, buf, 4); + if (ret == 4) + break; + } + + if (ret < 0) { + dev_err(&client->dev, + "write fail: chip 0x%x register 0x%x: %d\n", + client->addr, reg, ret); + } + + return ret < 0 ? ret : 0; +} +#endif /* _TI9X4_H */ diff --git a/drivers/media/platform/soc_camera/rcar_csi2.c b/drivers/media/platform/soc_camera/rcar_csi2.c index 4d95da6..2ef27e8 100644 --- a/drivers/media/platform/soc_camera/rcar_csi2.c +++ b/drivers/media/platform/soc_camera/rcar_csi2.c @@ -37,8 +37,9 @@ #include +//#define RCAR_CSI2_DUMP + #define DRV_NAME "rcar_csi2" -#define CONNECT_SLAVE_NAME "adv7482" #define VC_MAX_CHANNEL 4 #define RCAR_CSI2_TREF 0x00 @@ -63,6 +64,7 @@ #define RCAR_CSI2_LINKCNT 0x48 #define RCAR_CSI2_LSWAP 0x4C +#define RCAR_CSI2_PHTW 0x50 #define RCAR_CSI2_PHTC 0x58 #define RCAR_CSI2_PHYPLL 0x68 @@ -70,6 +72,10 @@ #define RCAR_CSI2_PHCLM 0x78 #define RCAR_CSI2_PHDLM 0x7C +#define RCAR_CSI2_CSI0CLKFCPR 0x254 /* CSI0CLK Frequency Configuration Preset */ +/* CSI0CLK frequency configuration bit */ +#define CSI0CLKFREQRANGE(n) ((n & 0x3f) << 16) + #define RCAR_CSI2_PHYCNT_SHUTDOWNZ (1 << 17) #define RCAR_CSI2_PHYCNT_RSTZ (1 << 16) #define RCAR_CSI2_PHYCNT_ENABLECLK (1 << 4) @@ -106,6 +112,9 @@ #define RCAR_CSI2_LSWAP_L0SEL_PLANE2 (2 << 0) #define RCAR_CSI2_LSWAP_L0SEL_PLANE3 (3 << 0) +#define RCAR_CSI2_PHTW_DWEN (1 << 24) +#define RCAR_CSI2_PHTW_CWEN (1 << 8) + #define RCAR_CSI2_PHTC_TESTCLR (1 << 0) /* interrupt status registers */ @@ -159,6 +168,11 @@ { } }; +static const struct soc_device_attribute r8a7795[] = { + { .soc_id = "r8a7795", .revision = "ES2.0" }, + { } +}; + enum chip_id { RCAR_GEN3, RCAR_GEN2, @@ -179,6 +193,7 @@ struct rcar_csi2_link_config { unsigned char lanes; unsigned long vcdt; unsigned long vcdt2; + unsigned int csi_rate; }; #define INIT_RCAR_CSI2_LINK_CONFIG(m) \ @@ -192,8 +207,7 @@ struct rcar_csi_irq_counter_log { }; struct rcar_csi2 { - struct v4l2_subdev subdev; - struct v4l2_mbus_framefmt *mf; + struct v4l2_subdev subdev[4]; unsigned int irq; unsigned long mipi_flags; void __iomem *base; @@ -205,7 +219,9 @@ struct rcar_csi2 { unsigned int field; unsigned int code; unsigned int lanes; + unsigned int csi_rate; spinlock_t lock; + atomic_t use_count; }; #define RCAR_CSI_80MBPS 0 @@ -251,6 +267,89 @@ struct rcar_csi2 { #define RCAR_CSI_1400MBPS 40 #define RCAR_CSI_1450MBPS 41 #define RCAR_CSI_1500MBPS 42 +#define RCAR_CSI_NUMRATES 43 + +#define RCAR_CSI2_PHxM0(i) (0xf0 + i * 0x08) +#define RCAR_CSI2_PHxM1(i) (0xf4 + i * 0x08) +#define RCAR_CSI2_PHRM(i) (0x110 + i * 0x04) +#define RCAR_CSI2_PHCM(i) (0x120 + i * 0x04) +#define RCAR_CSI2_SERCCNT 0x140 +#define RCAR_CSI2_SSERCCNT 0x144 +#define RCAR_CSI2_ECCCM 0x148 +#define RCAR_CSI2_ECECM 0x14c +#define RCAR_CSI2_CRCECM 0x150 +#define RCAR_CSI2_LCNT(i) (0x160 + i * 0x04) +#define RCAR_CSI2_LCNTM(i) (0x168 + i * 0x04) +#define RCAR_CSI2_FCNTM 0x170 +#define RCAR_CSI2_FCNTM2 0x174 +#define RCAR_CSI2_VINSM(i) (0x190 + i * 0x04) +#define RCAR_CSI2_PHM(i) (0x1C0 + i * 0x04) + +#define RCAR_CSI2_INTSTATE_ALL 0x3FFFFCDD + +#ifdef RCAR_CSI2_DUMP +static void rcar_sci2_debug_show(struct rcar_csi2 *priv) +{ + int i; + u32 reg0, reg1; + + printk("Debug registers:\n"); + printk("FCNTM : 0x%08x\n", ioread32(priv->base + RCAR_CSI2_FCNTM)); + printk("FCNTM2: 0x%08x\n", ioread32(priv->base + RCAR_CSI2_FCNTM2)); + + for (i = 0; i < 4; i++) { + reg0 = ioread32(priv->base + RCAR_CSI2_PHxM0(i)); + reg1 = ioread32(priv->base + RCAR_CSI2_PHxM1(i)); + + printk("Packet header %d: dt: 0x%02x, vc: %d, wc: %d, cnt: %d\n", + i, + reg0 & 0x3F, (reg0 >> 6) & 0x03, (reg0 >> 8) & 0xffff, + reg1 & 0xffff); + } + for (i = 0; i < 3; i++) { + reg0 = ioread32(priv->base + RCAR_CSI2_PHRM(i)); + + printk("Packet header R %d dt: 0x%02x, vc: %d, wc: %d, ecc: 0x%02x\n", + i, + reg0 & 0x3F, (reg0 >> 6) & 0x03, (reg0 >> 8) & 0xffff, + (reg0 >> 24) & 0xff); + } + for (i = 0; i < 2; i++) { + reg0 = ioread32(priv->base + RCAR_CSI2_PHCM(i)); + + printk("Packet header C %d: dt: 0x%02x, vc: %d, wc: %d, cal_parity: 0x%02x\n", + i, + reg0 & 0x3F, (reg0 >> 6) & 0x03, (reg0 >> 8) & 0xffff, + (reg0 >> 24) & 0xff); + } + for (i = 0; i < 8; i++) { + reg0 = ioread32(priv->base + RCAR_CSI2_PHM(i)); + + printk("Packet header Monitor %d: dt: 0x%02x, vc: %d, wc: %d, ecc: 0x%02x\n", + i + 1, + reg0 & 0x3F, (reg0 >> 6) & 0x03, (reg0 >> 8) & 0xffff, + (reg0 >> 24) & 0xff); + } + for (i = 0; i < 3; i++) + printk("VINSM%d: 0x%08x\n", i, ioread32(priv->base + RCAR_CSI2_VINSM(i))); + printk("SERCCNT: %d\n", + ioread32(priv->base + RCAR_CSI2_SERCCNT)); + printk("SSERCCNT: %d\n", + ioread32(priv->base + RCAR_CSI2_SSERCCNT)); + printk("ECCCM: %d\n", + ioread32(priv->base + RCAR_CSI2_ECCCM)); + printk("ECECM: %d\n", + ioread32(priv->base + RCAR_CSI2_ECECM)); + printk("CRCECM: %d\n", + ioread32(priv->base + RCAR_CSI2_CRCECM)); + for (i = 0; i < 2; i++) + printk("LCNT%d: 0x%08x\n", i, ioread32(priv->base + RCAR_CSI2_LCNT(i))); + for (i = 0; i < 2; i++) + printk("LCNTM%d: 0x%08x\n", i, ioread32(priv->base + RCAR_CSI2_LCNTM(i))); +} +#else +#define rcar_sci2_debug_show(args) +#endif /* RCAR_CSI2_DUMP */ static int rcar_csi2_set_phy_freq(struct rcar_csi2 *priv) { @@ -265,7 +364,7 @@ static int rcar_csi2_set_phy_freq(struct rcar_csi2 *priv) 0x16, 0x36, 0x56, 0x76, 0x18, /* 1150M, 1200M, 1250M, 1300M, 1350M */ 0x38, 0x58, 0x78 /* 1400M, 1450M, 1500M */ }; - const uint32_t const hs_freq_range[43] = { + const uint32_t const hs_freq_range_m3[43] = { 0x00, 0x10, 0x20, 0x30, 0x01, /* 0-4 */ 0x11, 0x21, 0x31, 0x02, 0x12, /* 5-9 */ 0x22, 0x32, 0x03, 0x13, 0x23, /* 10-14 */ @@ -276,47 +375,33 @@ static int rcar_csi2_set_phy_freq(struct rcar_csi2 *priv) 0x0B, 0x1B, 0x2B, 0x3B, 0x0C, /* 35-39 */ 0x1C, 0x2C, 0x3C /* 40-42 */ }; + const uint32_t const hs_freq_range_h3[43] = { + 0x00, 0x10, 0x20, 0x30, 0x01, /* 0-4 */ + 0x11, 0x21, 0x31, 0x02, 0x12, /* 5-9 */ + 0x22, 0x32, 0x03, 0x13, 0x23, /* 10-14 */ + 0x33, 0x04, 0x14, 0x25, 0x35, /* 15-19 */ + 0x05, 0x26, 0x36, 0x37, 0x07, /* 20-24 */ + 0x18, 0x28, 0x39, 0x09, 0x19, /* 25-29 */ + 0x29, 0x3A, 0x0A, 0x1A, 0x2A, /* 30-34 */ + 0x3B, 0x0B, 0x1B, 0x2B, 0x3C, /* 35-39 */ + 0x0C, 0x1C, 0x2C /* 40-42 */ + }; + const uint32_t const csi2_rate_range[43] = { + 80, 90, 100, 110, 120, /* 0-4 */ + 130, 140, 150, 160, 170, /* 5-9 */ + 180, 190, 205, 220, 235, /* 10-14 */ + 250, 275, 300, 325, 350, /* 15-19 */ + 400, 450, 500, 550, 600, /* 20-24 */ + 650, 700, 750, 800, 850, /* 25-29 */ + 900, 950, 1000, 1050, 1100, /* 30-34 */ + 1150, 1200, 1250, 1300, 1350, /* 35-39 */ + 1400, 1450, 1500 /* 40-42 */ + }; uint32_t bps_per_lane = RCAR_CSI_190MBPS; - dev_dbg(&priv->pdev->dev, "Input size (%dx%d%c)\n", - priv->mf->width, priv->mf->height, - (priv->mf->field == V4L2_FIELD_NONE) ? 'p' : 'i'); - - switch (priv->lanes) { - case 1: - bps_per_lane = RCAR_CSI_400MBPS; - break; - case 4: - if (priv->mf->field == V4L2_FIELD_NONE) { - if ((priv->mf->width == 1920) && - (priv->mf->height == 1080)) - bps_per_lane = RCAR_CSI_900MBPS; - else if ((priv->mf->width == 1280) && - (priv->mf->height == 720)) - bps_per_lane = RCAR_CSI_450MBPS; - else if ((priv->mf->width == 720) && - (priv->mf->height == 480)) - bps_per_lane = RCAR_CSI_190MBPS; - else if ((priv->mf->width == 720) && - (priv->mf->height == 576)) - bps_per_lane = RCAR_CSI_190MBPS; - else if ((priv->mf->width == 640) && - (priv->mf->height == 480)) - bps_per_lane = RCAR_CSI_100MBPS; - else - goto error; - } else { - if ((priv->mf->width == 1920) && - (priv->mf->height == 1080)) - bps_per_lane = RCAR_CSI_450MBPS; - else - goto error; - } - break; - default: - dev_err(&priv->pdev->dev, "ERROR: lanes is invalid (%d)\n", - priv->lanes); - return -EINVAL; + for (bps_per_lane = 0; bps_per_lane < RCAR_CSI_NUMRATES; bps_per_lane++) { + if (priv->csi_rate <= csi2_rate_range[bps_per_lane]) + break; } dev_dbg(&priv->pdev->dev, "bps_per_lane (%d)\n", bps_per_lane); @@ -325,16 +410,14 @@ static int rcar_csi2_set_phy_freq(struct rcar_csi2 *priv) iowrite32((hs_freq_range_v3m[bps_per_lane] << 16) | RCAR_CSI2_PHTW_DWEN | RCAR_CSI2_PHTW_CWEN | 0x44, priv->base + RCAR_CSI2_PHTW); + else if (soc_device_match(r8a7795)) + iowrite32(hs_freq_range_h3[bps_per_lane] << 16, + priv->base + RCAR_CSI2_PHYPLL); else - iowrite32(hs_freq_range[bps_per_lane] << 16, + /* h3 ws1.x is similar to m3 */ + iowrite32(hs_freq_range_m3[bps_per_lane] << 16, priv->base + RCAR_CSI2_PHYPLL); return 0; - -error: - dev_err(&priv->pdev->dev, "Not support resolution (%dx%d%c)\n", - priv->mf->width, priv->mf->height, - (priv->mf->field == V4L2_FIELD_NONE) ? 'p' : 'i'); - return -EINVAL; } static irqreturn_t rcar_csi2_irq(int irq, void *data) @@ -392,6 +475,16 @@ static int rcar_csi2_hwinit(struct rcar_csi2 *priv) iowrite32(0x0001000f, priv->base + RCAR_CSI2_FLD); tmp |= 0x1; break; + case 2: + /* First field number setting */ + iowrite32(0x0001000f, priv->base + RCAR_CSI2_FLD); + tmp |= 0x3; + break; + case 3: + /* First field number setting */ + iowrite32(0x0001000f, priv->base + RCAR_CSI2_FLD); + tmp |= 0x7; + break; case 4: /* First field number setting */ iowrite32(0x0002000f, priv->base + RCAR_CSI2_FLD); @@ -404,11 +497,27 @@ static int rcar_csi2_hwinit(struct rcar_csi2 *priv) return -EINVAL; } + if (soc_device_match(r8a7795)) { + /* Set PHY Test Interface Write Register in R-Car H3(ES2.0) */ + iowrite32(0x01cc01e2, priv->base + RCAR_CSI2_PHTW); + iowrite32(0x010101e3, priv->base + RCAR_CSI2_PHTW); + iowrite32(0x010101e4, priv->base + RCAR_CSI2_PHTW); + iowrite32(0x01100104, priv->base + RCAR_CSI2_PHTW); + iowrite32(0x01030100, priv->base + RCAR_CSI2_PHTW); + iowrite32(0x01800107, priv->base + RCAR_CSI2_PHTW); + } + /* set PHY frequency */ ret = rcar_csi2_set_phy_freq(priv); if (ret < 0) return ret; + /* Set CSI0CLK Frequency Configuration Preset Register + * in R-Car H3(ES2.0) + */ + if (soc_device_match(r8a7795)) + iowrite32(CSI0CLKFREQRANGE(32), priv->base + RCAR_CSI2_CSI0CLKFCPR); + /* Enable lanes */ iowrite32(tmp, priv->base + RCAR_CSI2_PHYCNT); @@ -469,32 +578,22 @@ static int rcar_csi2_hwinit(struct rcar_csi2 *priv) static int rcar_csi2_s_power(struct v4l2_subdev *sd, int on) { - struct rcar_csi2 *priv = container_of(sd, struct rcar_csi2, subdev); - struct v4l2_subdev *tmp_sd; - struct v4l2_subdev_format fmt = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - struct v4l2_mbus_framefmt *mf = &fmt.format; + struct rcar_csi2 *priv = v4l2_get_subdevdata(sd); int ret = 0; if (on) { - v4l2_device_for_each_subdev(tmp_sd, sd->v4l2_dev) { - if (strncmp(tmp_sd->name, CONNECT_SLAVE_NAME, - sizeof(CONNECT_SLAVE_NAME) - 1) == 0) { - v4l2_subdev_call(tmp_sd, pad, get_fmt, - NULL, &fmt); - if (ret < 0) - return ret; - } + if (atomic_inc_return(&priv->use_count) == 1) { + pm_runtime_get_sync(&priv->pdev->dev); + ret = rcar_csi2_hwinit(priv); + if (ret < 0) + return ret; } - priv->mf = mf; - pm_runtime_get_sync(&priv->pdev->dev); - ret = rcar_csi2_hwinit(priv); - if (ret < 0) - return ret; } else { - rcar_csi2_hwdeinit(priv); - pm_runtime_put_sync(&priv->pdev->dev); + if (atomic_dec_return(&priv->use_count) == 0) { + rcar_sci2_debug_show(priv); + rcar_csi2_hwdeinit(priv); + pm_runtime_put_sync(&priv->pdev->dev); + } } return ret; @@ -543,18 +642,19 @@ static int rcar_csi2_parse_dt(struct device_node *np, return -EINVAL; v4l2_of_parse_endpoint(endpoint, &bus_cfg); + ret = of_property_read_u32(endpoint, "csi-rate", &config->csi_rate); + if (ret < 0) { + printk(KERN_ERR "csi-rate not set\n"); + return ret; + } of_node_put(endpoint); config->lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; - ret = of_property_read_string(np, "adi,input-interface", &str); - if (ret < 0) - return ret; - vc_np = of_get_child_by_name(np, "virtual,channel"); - config->vcdt = 0; - config->vcdt2 = 0; + config->vcdt = 0x81008000; + config->vcdt2 = 0x83008200; for (i = 0; i < VC_MAX_CHANNEL; i++) { sprintf(csi_name, "csi2_vc%d", i); @@ -573,6 +673,8 @@ static int rcar_csi2_parse_dt(struct device_node *np, config->vcdt |= (0x24 << (i * 16)); else if (!strcmp(str, "ycbcr422")) config->vcdt |= (0x1e << (i * 16)); + else if (!strcmp(str, "raw8")) + config->vcdt |= (0x2a << (i * 16)); else config->vcdt |= 0; @@ -587,6 +689,8 @@ static int rcar_csi2_parse_dt(struct device_node *np, config->vcdt2 |= (0x24 << (j * 16)); else if (!strcmp(str, "ycbcr422")) config->vcdt2 |= (0x1e << (j * 16)); + else if (!strcmp(str, "raw8")) + config->vcdt2 |= (0x2a << (j * 16)); else config->vcdt2 |= 0; @@ -608,6 +712,7 @@ static int rcar_csi2_probe(struct platform_device *pdev) /* Platform data specify the PHY, lanes, ECC, CRC */ struct rcar_csi2_pdata *pdata; struct rcar_csi2_link_config link_config; + int i; dev_dbg(&pdev->dev, "CSI2 probed.\n"); @@ -618,12 +723,7 @@ static int rcar_csi2_probe(struct platform_device *pdev) if (ret) return ret; - if (link_config.lanes == 4) - dev_info(&pdev->dev, - "Detected rgb888 in rcar_csi2_parse_dt\n"); - else - dev_info(&pdev->dev, - "Detected YCbCr422 in rcar_csi2_parse_dt\n"); + dev_info(&pdev->dev, "Data lanes %d, link freq %d\n", link_config.lanes, link_config.csi_rate); } else { pdata = pdev->dev.platform_data; if (!pdata) @@ -655,23 +755,27 @@ static int rcar_csi2_probe(struct platform_device *pdev) return ret; priv->pdev = pdev; - priv->subdev.owner = THIS_MODULE; - priv->subdev.dev = &pdev->dev; priv->lanes = link_config.lanes; priv->vcdt = link_config.vcdt; priv->vcdt2 = link_config.vcdt2; + priv->csi_rate = link_config.csi_rate; + atomic_set(&priv->use_count, 0); - platform_set_drvdata(pdev, &priv->subdev); + platform_set_drvdata(pdev, priv); - v4l2_subdev_init(&priv->subdev, &rcar_csi2_subdev_ops); - v4l2_set_subdevdata(&priv->subdev, &pdev->dev); + for (i= 0; i < 4; i++) { + priv->subdev[i].owner = THIS_MODULE; + priv->subdev[i].dev = &pdev->dev; + v4l2_subdev_init(&priv->subdev[i], &rcar_csi2_subdev_ops); + v4l2_set_subdevdata(&priv->subdev[i], priv); - snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "rcar_csi2.%s", - dev_name(&pdev->dev)); + snprintf(priv->subdev[i].name, V4L2_SUBDEV_NAME_SIZE, "rcar_csi2.%s", + dev_name(&pdev->dev)); - ret = v4l2_async_register_subdev(&priv->subdev); - if (ret < 0) - return ret; + ret = v4l2_async_register_subdev(&priv->subdev[i]); + if (ret < 0) + return ret; + } spin_lock_init(&priv->lock); @@ -684,10 +788,11 @@ static int rcar_csi2_probe(struct platform_device *pdev) static int rcar_csi2_remove(struct platform_device *pdev) { - struct v4l2_subdev *subdev = platform_get_drvdata(pdev); - struct rcar_csi2 *priv = container_of(subdev, struct rcar_csi2, subdev); + struct rcar_csi2 *priv = platform_get_drvdata(pdev); + int i; - v4l2_async_unregister_subdev(&priv->subdev); + for (i= 0; i < 4; i++) + v4l2_async_unregister_subdev(&priv->subdev[i]); pm_runtime_disable(&pdev->dev); return 0; diff --git a/drivers/media/platform/soc_camera/rcar_vin.c b/drivers/media/platform/soc_camera/rcar_vin.c index 74fb005..496a8bd 100644 --- a/drivers/media/platform/soc_camera/rcar_vin.c +++ b/drivers/media/platform/soc_camera/rcar_vin.c @@ -106,6 +106,7 @@ #define VNMC_INF_YUV8_BT601 (1 << 16) #define VNMC_INF_YUV10_BT656 (2 << 16) #define VNMC_INF_YUV10_BT601 (3 << 16) +#define VNMC_INF_RAW8 (4 << 16) #define VNMC_INF_YUV16 (5 << 16) #define VNMC_INF_RGB888 (6 << 16) #define VNMC_INF_MASK (7 << 16) @@ -138,6 +139,7 @@ #define VNINTS_FOS (1 << 0) /* Video n Data Mode Register bits */ +#define VNDMR_YMODE_Y8 (1 << 12) #define VNDMR_EXRGB (1 << 8) #define VNDMR_BPSM (1 << 4) #define VNDMR_DTMD_YCSEP (1 << 1) @@ -408,6 +410,7 @@ enum csi2_fmt { RCAR_CSI_FMT_NONE = -1, RCAR_CSI_RGB888, RCAR_CSI_YCBCR422, + RCAR_CSI_RAW8, }; struct vin_coeff { @@ -773,10 +776,13 @@ struct rcar_vin_priv { enum csi2_fmt csi_fmt; enum virtual_ch vc; bool csi_sync; + bool deser_sync; struct rcar_vin_async_client *async_client; /* Asynchronous CSI2 linking */ struct v4l2_subdev *csi2_sd; + /* Asynchronous Deserializer linking */ + struct v4l2_subdev *deser_sd; /* Synchronous probing compatibility */ struct platform_device *csi2_pdev; @@ -989,6 +995,10 @@ static int rcar_vin_setup(struct rcar_vin_priv *priv) VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601; input_is_yuv = true; break; + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SBGGR12_1X12: + vnmc |= VNMC_INF_RAW8 | VNMC_BPS; + break; default: break; } @@ -1021,6 +1031,10 @@ static int rcar_vin_setup(struct rcar_vin_priv *priv) dmr = 0; output_is_yuv = true; break; + case V4L2_PIX_FMT_GREY: + dmr = VNDMR_DTMD_YCSEP | VNDMR_YMODE_Y8; + output_is_yuv = true; + break; case V4L2_PIX_FMT_ARGB555: dmr = VNDMR_DTMD_ARGB; break; @@ -1043,6 +1057,10 @@ static int rcar_vin_setup(struct rcar_vin_priv *priv) dmr = VNDMR_EXRGB | VNDMR_DTMD_ARGB; break; + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SBGGR12: + dmr = 0; + break; default: goto e_format; } @@ -1061,7 +1079,9 @@ static int rcar_vin_setup(struct rcar_vin_priv *priv) else vnmc |= VNMC_DPINE; - if ((icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_NV12) + if ((icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_NV12) && + (icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_SBGGR8) && + (icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_SBGGR12) && is_scaling(cam)) vnmc |= VNMC_SCLE; } @@ -1211,6 +1231,10 @@ static void rcar_vin_videobuf_queue(struct vb2_buffer *vb) */ static void rcar_vin_wait_stop_streaming(struct rcar_vin_priv *priv) { + /* update the status if hardware is not stopped */ + if (ioread32(priv->base + VNMS_REG) & VNMS_CA) + priv->state = RUNNING; + while (priv->state != STOPPED) { /* issue stop if running */ if (priv->state == RUNNING) @@ -1361,6 +1385,31 @@ static struct v4l2_subdev *find_csi2(struct rcar_vin_priv *pcdev) return NULL; } +static struct v4l2_subdev *find_deser(struct rcar_vin_priv *pcdev) +{ + struct v4l2_subdev *sd; + char name[] = "max9286_max9271"; + char name2[] = "ti964_ti9x3"; + char name3[] = "ti954_ti9x3"; + + v4l2_device_for_each_subdev(sd, &pcdev->ici.v4l2_dev) { + if (!strncmp(name, sd->name, sizeof(name) - 1)) { + pcdev->deser_sd = sd; + return sd; + } + if (!strncmp(name2, sd->name, sizeof(name2) - 1)) { + pcdev->deser_sd = sd; + return sd; + } + if (!strncmp(name3, sd->name, sizeof(name3) - 1)) { + pcdev->deser_sd = sd; + return sd; + } + } + + return NULL; +} + static int rcar_vin_add_device(struct soc_camera_device *icd) { struct soc_camera_host *ici = to_soc_camera_host(icd->parent); @@ -1375,7 +1424,8 @@ static int rcar_vin_add_device(struct soc_camera_device *icd) if (priv->chip == RCAR_H3 || priv->chip == RCAR_M3 || priv->chip == RCAR_V3M) { struct v4l2_subdev *csi2_sd = find_csi2(priv); - int ret; + struct v4l2_subdev *deser_sd = find_deser(priv); + int ret = 0; if (csi2_sd) { csi2_sd->grp_id = soc_camera_grp_id(icd); @@ -1390,6 +1440,18 @@ static int rcar_vin_add_device(struct soc_camera_device *icd) if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) return ret; } + if (deser_sd) { + v4l2_set_subdev_hostdata(deser_sd, icd); + + ret = v4l2_subdev_call(deser_sd, core, s_power, 1); + priv->deser_sync = true; + + if (ret < 0 && ret != -EINVAL) + priv->deser_sync = false; + + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + } /* * -ENODEV is special: * either csi2_sd == NULL or the CSI-2 driver @@ -1417,6 +1479,7 @@ static void rcar_vin_remove_device(struct soc_camera_device *icd) struct rcar_vin_priv *priv = ici->priv; struct vb2_v4l2_buffer *vbuf; struct v4l2_subdev *csi2_sd = find_csi2(priv); + struct v4l2_subdev *deser_sd = find_deser(priv); int i; /* disable capture, disable interrupts */ @@ -1443,6 +1506,8 @@ static void rcar_vin_remove_device(struct soc_camera_device *icd) if ((csi2_sd) && (priv->csi_sync)) v4l2_subdev_call(csi2_sd, core, s_power, 0); + if ((deser_sd) && (priv->deser_sync)) + v4l2_subdev_call(deser_sd, core, s_power, 0); dev_dbg(icd->parent, "R-Car VIN driver detached from camera %d\n", icd->devnum); @@ -1621,13 +1686,19 @@ static int rcar_vin_set_rect(struct soc_camera_device *icd) if (priv->chip == RCAR_H3 || priv->chip == RCAR_M3 || priv->chip == RCAR_V3M) { - if ((icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_NV12) + if ((icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_NV12) && + (icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_SBGGR8) && + (icd->current_fmt->host_fmt->fourcc != V4L2_PIX_FMT_SBGGR12) && is_scaling(cam)) { ret = rcar_vin_uds_set(priv, cam); if (ret < 0) return ret; } - if (is_scaling(cam) || + if ((icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_SBGGR8) || + (icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_SBGGR12)) + iowrite32(ALIGN(cam->out_width / 2, 0x10), + priv->base + VNIS_REG); + else if (is_scaling(cam) || (icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_NV16) || (icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_NV12)) iowrite32(ALIGN(cam->out_width, 0x20), @@ -1868,6 +1939,14 @@ static bool rcar_vin_packing_supported(const struct soc_mbus_pixelfmt *fmt) .layout = SOC_MBUS_LAYOUT_PACKED, }, { + .fourcc = V4L2_PIX_FMT_GREY, + .name = "GREY8", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, + { .fourcc = V4L2_PIX_FMT_RGB565, .name = "RGB565", .bits_per_sample = 16, @@ -1899,6 +1978,22 @@ static bool rcar_vin_packing_supported(const struct soc_mbus_pixelfmt *fmt) .order = SOC_MBUS_ORDER_LE, .layout = SOC_MBUS_LAYOUT_PACKED, }, + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .name = "Bayer 8 BGGR", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .name = "Bayer 12 BGGR", + .bits_per_sample = 8, + .packing = SOC_MBUS_PACKING_NONE, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, }; static int rcar_vin_get_formats(struct soc_camera_device *icd, unsigned int idx, @@ -2012,6 +2107,8 @@ static int rcar_vin_get_formats(struct soc_camera_device *icd, unsigned int idx, case MEDIA_BUS_FMT_YUYV8_2X8: case MEDIA_BUS_FMT_YUYV10_2X10: case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SBGGR12_1X12: if (cam->extra_fmt) break; @@ -2218,12 +2315,15 @@ static int rcar_vin_set_fmt(struct soc_camera_device *icd, case V4L2_PIX_FMT_ABGR32: case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_GREY: case V4L2_PIX_FMT_RGB565: case V4L2_PIX_FMT_ARGB555: case V4L2_PIX_FMT_NV16: can_scale = true; break; case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SBGGR12: default: can_scale = false; break; @@ -2316,7 +2416,8 @@ static int rcar_vin_try_fmt(struct soc_camera_device *icd, /* odd number clipping by pixel post clip processing, */ /* it is outputted to a memory per even pixels. */ if ((pixfmt == V4L2_PIX_FMT_NV16) || (pixfmt == V4L2_PIX_FMT_NV12) || - (pixfmt == V4L2_PIX_FMT_YUYV) || (pixfmt == V4L2_PIX_FMT_UYVY)) + (pixfmt == V4L2_PIX_FMT_YUYV) || (pixfmt == V4L2_PIX_FMT_UYVY) || + (pixfmt == V4L2_PIX_FMT_GREY)) v4l_bound_align_image(&pix->width, 5, priv->max_width, 1, &pix->height, 2, priv->max_height, 0, 0); else @@ -2486,6 +2587,19 @@ static int rcar_vin_cropcap(struct soc_camera_device *icd, } #endif +static int rcar_vin_get_edid(struct soc_camera_device *icd, + struct v4l2_edid *edid) +{ + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + int ret; + + ret = v4l2_subdev_call(sd, pad, get_edid, edid); + if (ret < 0) + return ret; + + return 0; +} + static struct soc_camera_host_ops rcar_vin_host_ops = { .owner = THIS_MODULE, .add = rcar_vin_add_device, @@ -2504,6 +2618,7 @@ static int rcar_vin_cropcap(struct soc_camera_device *icd, .get_selection = rcar_vin_get_selection, .cropcap = rcar_vin_cropcap, #endif + .get_edid = rcar_vin_get_edid, }; #ifdef CONFIG_OF @@ -2524,7 +2639,7 @@ static int rcar_vin_cropcap(struct soc_camera_device *icd, MODULE_DEVICE_TABLE(of, rcar_vin_of_table); #endif -#define MAP_MAX_NUM 32 +#define MAP_MAX_NUM 128 static DECLARE_BITMAP(device_map, MAP_MAX_NUM); static DEFINE_MUTEX(list_lock); @@ -2714,7 +2829,11 @@ static int rcar_vin_probe(struct platform_device *pdev) const char *str; unsigned int i; struct device_node *epn = NULL, *ren = NULL; + struct device_node *csi2_ren = NULL, *max9286_ren = NULL, *ti964_ren = NULL, *ti954_ren = NULL; bool csi_use = false; + bool max9286_use = false; + bool ti964_use = false; + bool ti954_use = false; match = of_match_device(of_match_ptr(rcar_vin_of_table), &pdev->dev); @@ -2741,13 +2860,27 @@ static int rcar_vin_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "node name:%s\n", of_node_full_name(ren->parent)); - if (strcmp(ren->parent->name, "csi2") == 0) + if (strcmp(ren->parent->name, "csi2") == 0) { + csi2_ren = ren; csi_use = true; + } - of_node_put(ren); + if (strcmp(ren->parent->name, "max9286-max9271") == 0) { + max9286_ren = of_parse_phandle(epn, "remote-endpoint", 0); + max9286_use = true; + } - if (i) - break; + if (strcmp(ren->parent->name, "ti964-ti9x3") == 0) { + ti964_ren = of_parse_phandle(epn, "remote-endpoint", 0); + ti964_use = true; + } + + if (strcmp(ren->parent->name, "ti954-ti9x3") == 0) { + ti954_ren = of_parse_phandle(epn, "remote-endpoint", 0); + ti954_use = true; + } + + of_node_put(ren); } ret = v4l2_of_parse_endpoint(np, &ep); @@ -2799,6 +2932,7 @@ static int rcar_vin_probe(struct platform_device *pdev) priv->ici.drv_name = dev_name(&pdev->dev); priv->ici.ops = &rcar_vin_host_ops; priv->csi_sync = false; + priv->deser_sync = false; priv->pdata_flags = pdata_flags; if (!match) { @@ -2983,7 +3117,25 @@ static int rcar_vin_probe(struct platform_device *pdev) goto cleanup; if (csi_use) { - ret = rcar_vin_soc_of_bind(priv, &priv->ici, epn, ren->parent); + ret = rcar_vin_soc_of_bind(priv, &priv->ici, epn, csi2_ren->parent); + if (ret) + goto cleanup; + } + + if (max9286_use) { + ret = rcar_vin_soc_of_bind(priv, &priv->ici, epn, max9286_ren); + if (ret) + goto cleanup; + } + + if (ti964_use) { + ret = rcar_vin_soc_of_bind(priv, &priv->ici, epn, ti964_ren); + if (ret) + goto cleanup; + } + + if (ti954_use) { + ret = rcar_vin_soc_of_bind(priv, &priv->ici, epn, ti954_ren); if (ret) goto cleanup; } diff --git a/drivers/media/platform/soc_camera/soc_camera.c b/drivers/media/platform/soc_camera/soc_camera.c index edd1c1d..54f4c9d 100644 --- a/drivers/media/platform/soc_camera/soc_camera.c +++ b/drivers/media/platform/soc_camera/soc_camera.c @@ -49,7 +49,7 @@ (icd)->vb_vidq.streaming : \ vb2_is_streaming(&(icd)->vb2_vidq)) -#define MAP_MAX_NUM 32 +#define MAP_MAX_NUM 128 static DECLARE_BITMAP(device_map, MAP_MAX_NUM); static LIST_HEAD(hosts); static LIST_HEAD(devices); @@ -1106,6 +1106,18 @@ static int soc_camera_s_parm(struct file *file, void *fh, return -ENOIOCTLCMD; } +static int soc_camera_g_edid(struct file *file, void *fh, + struct v4l2_edid *edid) +{ + struct soc_camera_device *icd = file->private_data; + struct soc_camera_host *ici = to_soc_camera_host(icd->parent); + + if (ici->ops->get_edid) + return ici->ops->get_edid(icd, edid); + + return -ENOIOCTLCMD; +} + static int soc_camera_probe(struct soc_camera_host *ici, struct soc_camera_device *icd); @@ -1664,7 +1676,7 @@ static void scan_of_host(struct soc_camera_host *ici) of_node_put(ren); if (i) { - dev_err(dev, "multiple subdevices aren't supported yet!\n"); + dev_dbg(dev, "multiple subdevices aren't supported yet!\n"); break; } } @@ -2077,6 +2089,7 @@ static int soc_camera_device_register(struct soc_camera_device *icd) .vidioc_s_selection = soc_camera_s_selection, .vidioc_g_parm = soc_camera_g_parm, .vidioc_s_parm = soc_camera_s_parm, + .vidioc_g_edid = soc_camera_g_edid, }; static int video_dev_create(struct soc_camera_device *icd) diff --git a/drivers/media/platform/soc_camera/soc_mediabus.c b/drivers/media/platform/soc_camera/soc_mediabus.c index e3e665e..84754a4 100644 --- a/drivers/media/platform/soc_camera/soc_mediabus.c +++ b/drivers/media/platform/soc_camera/soc_mediabus.c @@ -57,6 +57,16 @@ .layout = SOC_MBUS_LAYOUT_PACKED, }, }, { + .code = MEDIA_BUS_FMT_YUYV10_2X10, + .fmt = { + .fourcc = V4L2_PIX_FMT_YUYV, + .name = "YUYV", + .bits_per_sample = 10, + .packing = SOC_MBUS_PACKING_2X10_PADHI, + .order = SOC_MBUS_ORDER_LE, + .layout = SOC_MBUS_LAYOUT_PACKED, + }, +}, { .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, .fmt = { .fourcc = V4L2_PIX_FMT_RGB555, @@ -403,6 +413,10 @@ int soc_mbus_samples_per_pixel(const struct soc_mbus_pixelfmt *mf, *numerator = 2; *denominator = 1; return 0; + case SOC_MBUS_PACKING_2X10_PADHI: + *numerator = 3; + *denominator = 1; + return 0; case SOC_MBUS_PACKING_1_5X8: *numerator = 3; *denominator = 2; @@ -428,6 +442,8 @@ s32 soc_mbus_bytes_per_line(u32 width, const struct soc_mbus_pixelfmt *mf) case SOC_MBUS_PACKING_2X8_PADLO: case SOC_MBUS_PACKING_EXTEND16: return width * 2; + case SOC_MBUS_PACKING_2X10_PADHI: + return width * 3; case SOC_MBUS_PACKING_1_5X8: return width * 3 / 2; case SOC_MBUS_PACKING_VARIABLE: diff --git a/include/media/drv-intf/soc_mediabus.h b/include/media/drv-intf/soc_mediabus.h index 2ff7737..e5f3f53 100644 --- a/include/media/drv-intf/soc_mediabus.h +++ b/include/media/drv-intf/soc_mediabus.h @@ -21,6 +21,8 @@ * @SOC_MBUS_PACKING_2X8_PADHI: 16 bits transferred in 2 8-bit samples, in the * possibly incomplete byte high bits are padding * @SOC_MBUS_PACKING_2X8_PADLO: as above, but low bits are padding + * @SOC_MBUS_PACKING_2X10_PADHI:20 bits transferred in 2 10-bit samples. The + * high bits are padding * @SOC_MBUS_PACKING_EXTEND16: sample width (e.g., 10 bits) has to be extended * to 16 bits * @SOC_MBUS_PACKING_VARIABLE: compressed formats with variable packing @@ -33,6 +35,7 @@ enum soc_mbus_packing { SOC_MBUS_PACKING_NONE, SOC_MBUS_PACKING_2X8_PADHI, SOC_MBUS_PACKING_2X8_PADLO, + SOC_MBUS_PACKING_2X10_PADHI, SOC_MBUS_PACKING_EXTEND16, SOC_MBUS_PACKING_VARIABLE, SOC_MBUS_PACKING_1_5X8, diff --git a/include/media/soc_camera.h b/include/media/soc_camera.h index 1a15c3e..dad1ed8 100644 --- a/include/media/soc_camera.h +++ b/include/media/soc_camera.h @@ -125,6 +125,7 @@ struct soc_camera_host_ops { int (*set_parm)(struct soc_camera_device *, struct v4l2_streamparm *); int (*enum_framesizes)(struct soc_camera_device *, struct v4l2_frmsizeenum *); unsigned int (*poll)(struct file *, poll_table *); + int (*get_edid)(struct soc_camera_device *, struct v4l2_edid *); }; #define SOCAM_SENSOR_INVERT_PCLK (1 << 0) -- 1.9.1