From ff01ad1318d5e4dcac89901ea99f762b591f2cd6 Mon Sep 17 00:00:00 2001 From: Ankur Pawar Date: Fri, 24 Feb 2023 05:47:55 +0000 Subject: [PATCH] media: add imx219 sensor driver Add imx219 camera sensor driver code, mode tables and makefile changes. Bug 3583587 Change-Id: Ic7693de3cbdeeaecd7d9118a1e7819eaab5fdb3a Signed-off-by: Ankur Pawar Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2862964 Tested-by: mobile promotions Reviewed-by: mobile promotions --- drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx219_mode_tbls.h | 366 ++++++++++++ drivers/media/i2c/nv_imx219.c | 795 +++++++++++++++++++++++++++ include/media/imx219.h | 29 + include/media/nvc.h | 5 +- include/uapi/media/imx219.h | 59 ++ 6 files changed, 1252 insertions(+), 3 deletions(-) create mode 100644 drivers/media/i2c/imx219_mode_tbls.h create mode 100644 drivers/media/i2c/nv_imx219.c create mode 100644 include/media/imx219.h create mode 100644 include/uapi/media/imx219.h diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 8b98fac9..bb5cd123 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -10,6 +10,7 @@ ifeq ($(findstring ack_src,$(NV_BUILD_KERNEL_OPTIONS)),) obj-m += ar1335_common.o obj-m += lt6911uxc.o obj-m += nv_imx185.o +obj-m += nv_imx219.o obj-m += nv_imx274.o obj-m += nv_imx318.o obj-m += nv_imx390.o diff --git a/drivers/media/i2c/imx219_mode_tbls.h b/drivers/media/i2c/imx219_mode_tbls.h new file mode 100644 index 00000000..d85017bd --- /dev/null +++ b/drivers/media/i2c/imx219_mode_tbls.h @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * imx219_tables.h - sensor mode tables for imx219 HDR sensor. + * + * Copyright (c) 2015-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + */ + +#ifndef __IMX219_I2C_TABLES__ +#define __IMX219_I2C_TABLES__ + +#define IMX219_TABLE_WAIT_MS 0 +#define IMX219_TABLE_END 1 + +#define imx219_reg struct reg_8 + +static imx219_reg imx219_start_stream[] = { + {0x0100, 0x01}, + {IMX219_TABLE_WAIT_MS, 3}, + {IMX219_TABLE_END, 0x00} +}; + +static imx219_reg imx219_stop_stream[] = { + {0x0100, 0x00}, + {IMX219_TABLE_END, 0x00} +}; + +static imx219_reg imx219_mode_common[] = { + /* software reset */ + {0x0103, 0x01}, + {IMX219_TABLE_WAIT_MS, 10}, + /* sensor config */ + {0x0114, 0x01}, /* D-Phy, 2-lane */ + {0x0128, 0x00}, + {0x012A, 0x18}, /* 24 MHz INCK */ + {0x012B, 0x00}, + /* access code - vendor addr. ranges */ + {0x30EB, 0x05}, + {0x30EB, 0x0C}, + {0x300A, 0xFF}, + {0x300B, 0xFF}, + {0x30EB, 0x05}, + {0x30EB, 0x09}, + /* cis tuning */ + {0x455E, 0x00}, + {0x471E, 0x4B}, + {0x4767, 0x0F}, + {0x4750, 0x14}, + {0x4540, 0x00}, + {0x47B4, 0x14}, + {0x4713, 0x30}, + {0x478B, 0x10}, + {0x478F, 0x10}, + {0x4793, 0x10}, + {0x4797, 0x0E}, + {0x479B, 0x0E}, + {IMX219_TABLE_END, 0x00} +}; + +static imx219_reg imx219_mode_3280x2464_21fps[] = { + /* capture settings */ + {0x0157, 0x00}, /* ANALOG_GAIN_GLOBAL[7:0] */ + {0x015A, 0x09}, /* COARSE_INTEG_TIME[15:8] */ + {0x015B, 0xbd}, /* COARSE_INTEG_TIME[7:0] */ + /* format settings */ + {0x0160, 0x09}, /* FRM_LENGTH[15:8] */ + {0x0161, 0xC1}, /* FRM_LENGTH[7:0] */ + {0x0162, 0x0D}, /* LINE_LENGTH[15:8] */ + {0x0163, 0x78}, /* LINE_LENGTH[7:0] */ + {0x0164, 0x00}, + {0x0165, 0x00}, + {0x0166, 0x0C}, + {0x0167, 0xCF}, + {0x0168, 0x00}, + {0x0169, 0x00}, + {0x016A, 0x09}, + {0x016B, 0x9F}, + {0x016C, 0x0C}, + {0x016D, 0xD0}, + {0x016E, 0x09}, + {0x016F, 0xA0}, + {0x0170, 0x01}, + {0x0171, 0x01}, + {0x0174, 0x00}, + {0x0175, 0x00}, + {0x018C, 0x0A}, + {0x018D, 0x0A}, + /* clock dividers */ + {0x0301, 0x05}, + {0x0303, 0x01}, + {0x0304, 0x03}, + {0x0305, 0x03}, + {0x0306, 0x00}, + {0x0307, 0x39}, + {0x0309, 0x0A}, + {0x030B, 0x01}, + {0x030C, 0x00}, + {0x030D, 0x72}, + {IMX219_TABLE_END, 0x00} +}; + +static imx219_reg imx219_mode_3280x1848_28fps[] = { + /* capture settings */ + {0x0157, 0x00}, /* ANALOG_GAIN_GLOBAL[7:0] */ + {0x015A, 0x07}, /* COARSE_INTEG_TIME[15:8] */ + {0x015B, 0x55}, /* COARSE_INTEG_TIME[7:0] */ + /* format settings */ + {0x0160, 0x07}, /* FRM_LENGTH[15:8] */ + {0x0161, 0x59}, /* FRM_LENGTH[7:0] */ + {0x0162, 0x0D}, /* LINE_LENGTH[15:8] */ + {0x0163, 0x78}, /* LINE_LENGTH[7:0] */ + {0x0164, 0x00}, + {0x0165, 0x00}, + {0x0166, 0x0C}, + {0x0167, 0xCF}, + {0x0168, 0x01}, + {0x0169, 0x34}, + {0x016A, 0x08}, + {0x016B, 0x6B}, + {0x016C, 0x0C}, + {0x016D, 0xD0}, + {0x016E, 0x07}, + {0x016F, 0x38}, + {0x0170, 0x01}, + {0x0171, 0x01}, + {0x0174, 0x00}, + {0x0175, 0x00}, + {0x018C, 0x0A}, + {0x018D, 0x0A}, + /* clocks dividers */ + {0x0301, 0x05}, + {0x0303, 0x01}, + {0x0304, 0x03}, + {0x0305, 0x03}, + {0x0306, 0x00}, + {0x0307, 0x39}, + {0x0309, 0x0A}, + {0x030B, 0x01}, + {0x030C, 0x00}, + {0x030D, 0x72}, + {IMX219_TABLE_END, 0x00} +}; + +static imx219_reg imx219_mode_1920x1080_30fps[] = { + /* capture settings */ + {0x0157, 0x00}, /* ANALOG_GAIN_GLOBAL[7:0] */ + {0x015A, 0x06}, /* COARSE_INTEG_TIME[15:8] */ + {0x015B, 0xde}, /* COARSE_INTEG_TIME[7:0] */ + /* format settings */ + {0x0160, 0x06}, /* FRM_LENGTH[15:8] */ + {0x0161, 0xE2}, /* FRM_LENGTH[7:0] */ + {0x0162, 0x0D}, /* LINE_LENGTH[15:8] */ + {0x0163, 0x78}, /* LINE_LENGTH[7:0] */ + {0x0164, 0x02}, + {0x0165, 0xA8}, + {0x0166, 0x0A}, + {0x0167, 0x27}, + {0x0168, 0x02}, + {0x0169, 0xB4}, + {0x016A, 0x06}, + {0x016B, 0xEB}, + {0x016C, 0x07}, + {0x016D, 0x80}, + {0x016E, 0x04}, + {0x016F, 0x38}, + {0x0170, 0x01}, + {0x0171, 0x01}, + {0x0174, 0x00}, + {0x0175, 0x00}, + {0x018C, 0x0A}, + {0x018D, 0x0A}, + /* clocks dividers */ + {0x0301, 0x05}, + {0x0303, 0x01}, + {0x0304, 0x03}, + {0x0305, 0x03}, + {0x0306, 0x00}, + {0x0307, 0x39}, + {0x0309, 0x0A}, + {0x030B, 0x01}, + {0x030C, 0x00}, + {0x030D, 0x72}, + {IMX219_TABLE_END, 0x00} +}; + +static imx219_reg imx219_mode_1640x1232_30fps[] = { + /* capture settings */ + {0x0157, 0x00}, /* ANALOG_GAIN_GLOBAL[7:0] */ + {0x015A, 0x06}, /* COARSE_INTEG_TIME[15:8] */ + {0x015B, 0xA8}, /* COARSE_INTEG_TIME[7:0] */ + /* format settings */ + {0x0160, 0x06}, /* FRM_LENGTH[15:8] */ + {0x0161, 0xE2}, /* FRM_LENGTH[7:0] */ + {0x0162, 0x0D}, /* LINE_LENGTH[15:8] */ + {0x0163, 0x78}, /* LINE_LENGTH[7:0] */ + {0x0164, 0x00}, + {0x0165, 0x00}, + {0x0166, 0x0C}, + {0x0167, 0xCF}, + {0x0168, 0x00}, + {0x0169, 0x00}, + {0x016A, 0x09}, + {0x016B, 0x9F}, + {0x016C, 0x06}, + {0x016D, 0x68}, + {0x016E, 0x04}, + {0x016F, 0xD0}, + {0x0170, 0x01}, + {0x0171, 0x01}, + {0x0174, 0x01}, + {0x0175, 0x01}, + {0x018C, 0x0A}, + {0x018D, 0x0A}, + /* clocks dividers */ + {0x0301, 0x05}, + {0x0303, 0x01}, + {0x0304, 0x03}, + {0x0305, 0x03}, + {0x0306, 0x00}, + {0x0307, 0x39}, + {0x0309, 0x0A}, + {0x030B, 0x01}, + {0x030C, 0x00}, + {0x030D, 0x72}, + {IMX219_TABLE_END, 0x00} +}; + +static imx219_reg imx219_mode_1280x720_60fps[] = { + /* capture settings */ + {0x0157, 0x00}, /* ANALOG_GAIN_GLOBAL[7:0] */ + {0x015A, 0x03}, /* COARSE_INTEG_TIME[15:8] */ + {0x015B, 0x6c}, /* COARSE_INTEG_TIME[7:0] */ + /* format settings */ + {0x0160, 0x03}, /* FRM_LENGTH[15:8] */ + {0x0161, 0x70}, /* FRM_LENGTH[7:0] */ + {0x0162, 0x0D}, /* LINE_LENGTH[15:8] */ + {0x0163, 0x78}, /* LINE_LENGTH[7:0] */ + {0x0164, 0x01}, + {0x0165, 0x68}, + {0x0166, 0x0B}, + {0x0167, 0x67}, + {0x0168, 0x02}, + {0x0169, 0x00}, + {0x016A, 0x07}, + {0x016B, 0x9F}, + {0x016C, 0x05}, + {0x016D, 0x00}, + {0x016E, 0x02}, + {0x016F, 0xD0}, + {0x0170, 0x01}, + {0x0171, 0x01}, + {0x0174, 0x01}, + {0x0175, 0x01}, + {0x018C, 0x0A}, + {0x018D, 0x0A}, + /* clocks dividers */ + {0x0301, 0x05}, + {0x0303, 0x01}, + {0x0304, 0x03}, + {0x0305, 0x03}, + {0x0306, 0x00}, + {0x0307, 0x39}, + {0x0309, 0x0A}, + {0x030B, 0x01}, + {0x030C, 0x00}, + {0x030D, 0x72}, + {IMX219_TABLE_END, 0x00} +}; + +/* +static imx219_reg imx219_mode_1280x720_120fps[] = { + // capture settings + {0x0157, 0x00}, // ANALOG_GAIN_GLOBAL[7:0] + {0x015A, 0x01}, // COARSE_INTEG_TIME[15:8] + {0x015B, 0x85}, // COARSE_INTEG_TIME[7:0] + // format settings + {0x0160, 0x01}, // FRM_LENGTH[15:8] + {0x0161, 0x89}, // FRM_LENGTH[7:0] + {0x0162, 0x0D}, // LINE_LENGTH[15:8] + {0x0163, 0xE8}, // LINE_LENGTH[7:0] + {0x0164, 0x01}, + {0x0165, 0x68}, + {0x0166, 0x0B}, + {0x0167, 0x67}, + {0x0168, 0x02}, + {0x0169, 0x00}, + {0x016A, 0x07}, + {0x016B, 0x9F}, + {0x016C, 0x05}, + {0x016D, 0x00}, + {0x016E, 0x02}, + {0x016F, 0xD0}, + {0x0170, 0x01}, + {0x0171, 0x01}, + {0x0174, 0x03}, + {0x0175, 0x03}, + {0x018C, 0x0A}, + {0x018D, 0x0A}, + // clocks dividers + {0x0301, 0x05}, + {0x0303, 0x01}, + {0x0304, 0x03}, + {0x0305, 0x03}, + {0x0306, 0x00}, + {0x0307, 0x35}, + {0x0309, 0x0A}, + {0x030B, 0x01}, + {0x030C, 0x00}, + {0x030D, 0x66}, + {IMX219_TABLE_END, 0x00} +}; +*/ +enum { + IMX219_MODE_3280x2464_21FPS, + IMX219_MODE_3280x1848_28FPS, + IMX219_MODE_1920x1080_30FPS, + IMX219_MODE_1640x1232_30FPS, + IMX219_MODE_1280x720_60FPS, + + IMX219_MODE_COMMON, + IMX219_START_STREAM, + IMX219_STOP_STREAM, +}; + +static imx219_reg *mode_table[] = { + [IMX219_MODE_3280x2464_21FPS] = imx219_mode_3280x2464_21fps, + [IMX219_MODE_3280x1848_28FPS] = imx219_mode_3280x1848_28fps, + [IMX219_MODE_1920x1080_30FPS] = imx219_mode_1920x1080_30fps, + [IMX219_MODE_1640x1232_30FPS] = imx219_mode_1640x1232_30fps, + [IMX219_MODE_1280x720_60FPS] = imx219_mode_1280x720_60fps, + + [IMX219_MODE_COMMON] = imx219_mode_common, + [IMX219_START_STREAM] = imx219_start_stream, + [IMX219_STOP_STREAM] = imx219_stop_stream, +}; + +static const int imx219_21fps[] = { + 21, +}; + +static const int imx219_28fps[] = { + 28, +}; + +static const int imx219_30fps[] = { + 30, +}; + +static const int imx219_60fps[] = { + 60, +}; + +/* + * WARNING: frmfmt ordering need to match mode definition in + * device tree! + */ +static const struct camera_common_frmfmt imx219_frmfmt[] = { + {{3280, 2464}, imx219_21fps, 1, 0, IMX219_MODE_3280x2464_21FPS}, + /* Add modes with no device tree support after below */ + {{3280, 1848}, imx219_28fps, 1, 0, IMX219_MODE_3280x1848_28FPS}, + {{1920, 1080}, imx219_30fps, 1, 0, IMX219_MODE_1920x1080_30FPS}, + {{1640, 1232}, imx219_30fps, 1, 0, IMX219_MODE_1640x1232_30FPS}, + {{1280, 720}, imx219_60fps, 1, 0, IMX219_MODE_1280x720_60FPS}, +}; + +#endif /* __IMX219_I2C_TABLES__ */ diff --git a/drivers/media/i2c/nv_imx219.c b/drivers/media/i2c/nv_imx219.c new file mode 100644 index 00000000..533c6866 --- /dev/null +++ b/drivers/media/i2c/nv_imx219.c @@ -0,0 +1,795 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * nv_imx219.c - imx219 sensor driver + * + * Copyright (c) 2015-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../platform/tegra/camera/camera_gpio.h" +#include "imx219_mode_tbls.h" + +/* imx219 - sensor parameter limits */ +#define IMX219_GAIN_SHIFT 8 +#define IMX219_MIN_GAIN 0x0000 +#define IMX219_MAX_GAIN 0x00e8 +#define IMX219_MIN_FRAME_LENGTH 0x0100 +#define IMX219_MAX_FRAME_LENGTH 0xffff +#define IMX219_MIN_COARSE_EXPOSURE 0x0001 +#define IMX219_MAX_COARSE_DIFF 0x0004 + +/* imx219 sensor register address */ +#define IMX219_MODEL_ID_ADDR_MSB 0x0000 +#define IMX219_MODEL_ID_ADDR_LSB 0x0001 +#define IMX219_GAIN_ADDR 0x0157 +#define IMX219_FRAME_LENGTH_ADDR_MSB 0x0160 +#define IMX219_FRAME_LENGTH_ADDR_LSB 0x0161 +#define IMX219_COARSE_INTEG_TIME_ADDR_MSB 0x015a +#define IMX219_COARSE_INTEG_TIME_ADDR_LSB 0x015b +#define IMX219_FINE_INTEG_TIME_ADDR_MSB 0x0388 +#define IMX219_FINE_INTEG_TIME_ADDR_LSB 0x0389 + +static const struct of_device_id imx219_of_match[] = { + { .compatible = "sony,imx219", }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx219_of_match); + +static const u32 ctrl_cid_list[] = { + TEGRA_CAMERA_CID_GAIN, + TEGRA_CAMERA_CID_EXPOSURE, + TEGRA_CAMERA_CID_FRAME_RATE, + TEGRA_CAMERA_CID_SENSOR_MODE_ID, +}; + +struct imx219 { + struct i2c_client *i2c_client; + struct v4l2_subdev *subdev; + u16 fine_integ_time; + u32 frame_length; + struct camera_common_data *s_data; + struct tegracam_device *tc_dev; +}; + +static const struct regmap_config sensor_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) + .use_single_rw = true, +#else + .use_single_read = true, + .use_single_write = true, +#endif +}; + +static inline void imx219_get_frame_length_regs(imx219_reg *regs, + u32 frame_length) +{ + regs->addr = IMX219_FRAME_LENGTH_ADDR_MSB; + regs->val = (frame_length >> 8) & 0xff; + (regs + 1)->addr = IMX219_FRAME_LENGTH_ADDR_LSB; + (regs + 1)->val = (frame_length) & 0xff; +} + +static inline void imx219_get_coarse_integ_time_regs(imx219_reg *regs, + u32 coarse_time) +{ + regs->addr = IMX219_COARSE_INTEG_TIME_ADDR_MSB; + regs->val = (coarse_time >> 8) & 0xff; + (regs + 1)->addr = IMX219_COARSE_INTEG_TIME_ADDR_LSB; + (regs + 1)->val = (coarse_time) & 0xff; +} + +static inline void imx219_get_gain_reg(imx219_reg *reg, u8 gain) +{ + reg->addr = IMX219_GAIN_ADDR; + reg->val = gain & 0xff; +} + +static inline int imx219_read_reg(struct camera_common_data *s_data, + u16 addr, u8 *val) +{ + int err = 0; + u32 reg_val = 0; + + err = regmap_read(s_data->regmap, addr, ®_val); + *val = reg_val & 0xff; + + return err; +} + +static inline int imx219_write_reg(struct camera_common_data *s_data, + u16 addr, u8 val) +{ + int err = 0; + + err = regmap_write(s_data->regmap, addr, val); + if (err) + dev_err(s_data->dev, "%s: i2c write failed, 0x%x = %x", + __func__, addr, val); + + return err; +} + +static int imx219_write_table(struct imx219 *priv, const imx219_reg table[]) +{ + return regmap_util_write_table_8(priv->s_data->regmap, table, NULL, 0, + IMX219_TABLE_WAIT_MS, IMX219_TABLE_END); +} + +static int imx219_set_group_hold(struct tegracam_device *tc_dev, bool val) +{ + /* imx219 does not support group hold */ + return 0; +} + +static int imx219_get_fine_integ_time(struct imx219 *priv, u16 *fine_time) +{ + struct camera_common_data *s_data = priv->s_data; + int err = 0; + u8 reg_val[2]; + + err = imx219_read_reg(s_data, IMX219_FINE_INTEG_TIME_ADDR_MSB, + ®_val[0]); + if (err) + goto done; + + err = imx219_read_reg(s_data, IMX219_FINE_INTEG_TIME_ADDR_LSB, + ®_val[1]); + if (err) + goto done; + + *fine_time = (reg_val[0] << 8) | reg_val[1]; + +done: + return err; +} + +static int imx219_set_gain(struct tegracam_device *tc_dev, s64 val) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct device *dev = s_data->dev; + const struct sensor_mode_properties *mode = + &s_data->sensor_props.sensor_modes[s_data->mode_prop_idx]; + + int err = 0; + imx219_reg gain_reg; + s16 gain; + + if (val < mode->control_properties.min_gain_val) + val = mode->control_properties.min_gain_val; + else if (val > mode->control_properties.max_gain_val) + val = mode->control_properties.max_gain_val; + + if (val == 0) + return -EINVAL; + /* translate value (from normalized analog gain) */ + gain = (s16)((256 * mode->control_properties.gain_factor) / val); + gain = 256 - gain; + + if (gain < IMX219_MIN_GAIN) + gain = IMX219_MAX_GAIN; + else if (gain > IMX219_MAX_GAIN) + gain = IMX219_MAX_GAIN; + + dev_dbg(dev, "%s: val: %lld (/%d) [times], gain: %u\n", + __func__, val, mode->control_properties.gain_factor, gain); + + imx219_get_gain_reg(&gain_reg, (u8)gain); + err = imx219_write_reg(s_data, gain_reg.addr, gain_reg.val); + if (err) + dev_dbg(dev, "%s: gain control error\n", __func__); + + return 0; +} + +static int imx219_set_frame_rate(struct tegracam_device *tc_dev, s64 val) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct imx219 *priv = (struct imx219 *)tc_dev->priv; + struct device *dev = tc_dev->dev; + const struct sensor_mode_properties *mode = + &s_data->sensor_props.sensor_modes[s_data->mode_prop_idx]; + + int err = 0; + imx219_reg fl_regs[2]; + u32 frame_length; + int i; + + if (val == 0 || mode->image_properties.line_length == 0) + return -EINVAL; + frame_length = (u32)(mode->signal_properties.pixel_clock.val * + (u64)mode->control_properties.framerate_factor / + mode->image_properties.line_length / val); + + if (frame_length < IMX219_MIN_FRAME_LENGTH) + frame_length = IMX219_MIN_FRAME_LENGTH; + else if (frame_length > IMX219_MAX_FRAME_LENGTH) + frame_length = IMX219_MAX_FRAME_LENGTH; + + dev_dbg(dev, + "%s: val: %llde-6 [fps], frame_length: %u [lines]\n", + __func__, val, frame_length); + + imx219_get_frame_length_regs(fl_regs, frame_length); + for (i = 0; i < 2; i++) { + err = imx219_write_reg(s_data, fl_regs[i].addr, fl_regs[i].val); + if (err) { + dev_dbg(dev, + "%s: frame_length control error\n", __func__); + return err; + } + } + + priv->frame_length = frame_length; + + return 0; +} + +static int imx219_set_exposure(struct tegracam_device *tc_dev, s64 val) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct imx219 *priv = (struct imx219 *)tc_dev->priv; + struct device *dev = tc_dev->dev; + const struct sensor_mode_properties *mode = + &s_data->sensor_props.sensor_modes[s_data->mode_prop_idx]; + + int err = 0; + imx219_reg ct_regs[2]; + const s32 max_coarse_time = priv->frame_length - IMX219_MAX_COARSE_DIFF; + s32 fine_integ_time_factor; + u32 coarse_time; + int i; + + if (mode->signal_properties.pixel_clock.val == 0 || + mode->control_properties.exposure_factor == 0 || + mode->image_properties.line_length) + return -EINVAL; + + + fine_integ_time_factor = priv->fine_integ_time * + mode->control_properties.exposure_factor / + mode->signal_properties.pixel_clock.val; + + coarse_time = (val - fine_integ_time_factor) + * mode->signal_properties.pixel_clock.val + / mode->control_properties.exposure_factor + / mode->image_properties.line_length; + + if (coarse_time < IMX219_MIN_COARSE_EXPOSURE) + coarse_time = IMX219_MIN_COARSE_EXPOSURE; + else if (coarse_time > max_coarse_time) { + coarse_time = max_coarse_time; + dev_dbg(dev, + "%s: exposure limited by frame_length: %d [lines]\n", + __func__, max_coarse_time); + } + + dev_dbg(dev, "%s: val: %lld [us], coarse_time: %d [lines]\n", + __func__, val, coarse_time); + + imx219_get_coarse_integ_time_regs(ct_regs, coarse_time); + + for (i = 0; i < 2; i++) { + err = imx219_write_reg(s_data, ct_regs[i].addr, ct_regs[i].val); + if (err) { + dev_dbg(dev, + "%s: coarse_time control error\n", __func__); + return err; + } + } + + return 0; +} + +static struct tegracam_ctrl_ops imx219_ctrl_ops = { + .numctrls = ARRAY_SIZE(ctrl_cid_list), + .ctrl_cid_list = ctrl_cid_list, + .set_gain = imx219_set_gain, + .set_exposure = imx219_set_exposure, + .set_frame_rate = imx219_set_frame_rate, + .set_group_hold = imx219_set_group_hold, +}; + +static int imx219_power_on(struct camera_common_data *s_data) +{ + int err = 0; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + + dev_dbg(dev, "%s: power on\n", __func__); + if (pdata && pdata->power_on) { + err = pdata->power_on(pw); + if (err) + dev_err(dev, "%s failed.\n", __func__); + else + pw->state = SWITCH_ON; + return err; + } + + if (unlikely(!(pw->avdd || pw->iovdd || pw->dvdd))) + goto skip_power_seqn; + + if (pw->reset_gpio) { + if (gpio_cansleep(pw->reset_gpio)) + gpio_set_value_cansleep(pw->reset_gpio, 0); + else + gpio_set_value(pw->reset_gpio, 0); + } + + usleep_range(10, 20); + + if (pw->avdd) { + err = regulator_enable(pw->avdd); + if (err) + goto imx219_avdd_fail; + } + + if (pw->iovdd) { + err = regulator_enable(pw->iovdd); + if (err) + goto imx219_iovdd_fail; + } + + if (pw->dvdd) { + err = regulator_enable(pw->dvdd); + if (err) + goto imx219_dvdd_fail; + } + + usleep_range(10, 20); + +skip_power_seqn: + if (pw->reset_gpio) { + if (gpio_cansleep(pw->reset_gpio)) + gpio_set_value_cansleep(pw->reset_gpio, 1); + else + gpio_set_value(pw->reset_gpio, 1); + } + + /* Need to wait for t4 + t5 + t9 time as per the data sheet */ + /* t4 - 200us, t5 - 6ms, t9 - 1.2ms */ + /* also add 2ms to workaround the i2c timeout issue on 2nd camera */ + usleep_range(10000, 10100); + + pw->state = SWITCH_ON; + + return 0; + +imx219_dvdd_fail: + regulator_disable(pw->iovdd); + +imx219_iovdd_fail: + regulator_disable(pw->avdd); + +imx219_avdd_fail: + dev_err(dev, "%s failed.\n", __func__); + + return -ENODEV; +} + +static int imx219_power_off(struct camera_common_data *s_data) +{ + int err = 0; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + + dev_dbg(dev, "%s: power off\n", __func__); + + if (pdata && pdata->power_off) { + err = pdata->power_off(pw); + if (err) { + dev_err(dev, "%s failed.\n", __func__); + return err; + } + } else { + if (pw->reset_gpio) { + if (gpio_cansleep(pw->reset_gpio)) + gpio_set_value_cansleep(pw->reset_gpio, 0); + else + gpio_set_value(pw->reset_gpio, 0); + } + + usleep_range(10, 20); + + if (pw->dvdd) + regulator_disable(pw->dvdd); + if (pw->iovdd) + regulator_disable(pw->iovdd); + if (pw->avdd) + regulator_disable(pw->avdd); + } + + usleep_range(5000, 5000); + pw->state = SWITCH_OFF; + + return 0; +} + +static int imx219_power_put(struct tegracam_device *tc_dev) +{ + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + + if (unlikely(!pw)) + return -EFAULT; + + if (likely(pw->dvdd)) + devm_regulator_put(pw->dvdd); + + if (likely(pw->avdd)) + devm_regulator_put(pw->avdd); + + if (likely(pw->iovdd)) + devm_regulator_put(pw->iovdd); + + pw->dvdd = NULL; + pw->avdd = NULL; + pw->iovdd = NULL; + + if (likely(pw->reset_gpio)) + gpio_free(pw->reset_gpio); + + return 0; +} + +static int imx219_power_get(struct tegracam_device *tc_dev) +{ + struct device *dev = tc_dev->dev; + struct camera_common_data *s_data = tc_dev->s_data; + struct camera_common_power_rail *pw = s_data->power; + struct camera_common_pdata *pdata = s_data->pdata; + struct clk *parent; + int err = 0; + + if (!pdata) { + dev_err(dev, "pdata missing\n"); + return -EFAULT; + } + + /* Sensor MCLK (aka. INCK) */ + if (pdata->mclk_name) { + pw->mclk = devm_clk_get(dev, pdata->mclk_name); + if (IS_ERR(pw->mclk)) { + dev_err(dev, "unable to get clock %s\n", + pdata->mclk_name); + return PTR_ERR(pw->mclk); + } + + if (pdata->parentclk_name) { + parent = devm_clk_get(dev, pdata->parentclk_name); + if (IS_ERR(parent)) { + dev_err(dev, "unable to get parent clock %s", + pdata->parentclk_name); + } else + clk_set_parent(pw->mclk, parent); + } + } + + /* analog 2.8v */ + if (pdata->regulators.avdd) + err |= camera_common_regulator_get(dev, + &pw->avdd, pdata->regulators.avdd); + /* IO 1.8v */ + if (pdata->regulators.iovdd) + err |= camera_common_regulator_get(dev, + &pw->iovdd, pdata->regulators.iovdd); + /* dig 1.2v */ + if (pdata->regulators.dvdd) + err |= camera_common_regulator_get(dev, + &pw->dvdd, pdata->regulators.dvdd); + if (err) { + dev_err(dev, "%s: unable to get regulator(s)\n", __func__); + goto done; + } + + /* Reset or ENABLE GPIO */ + pw->reset_gpio = pdata->reset_gpio; + err = gpio_request(pw->reset_gpio, "cam_reset_gpio"); + if (err < 0) { + dev_err(dev, "%s: unable to request reset_gpio (%d)\n", + __func__, err); + goto done; + } + +done: + pw->state = SWITCH_OFF; + + return err; +} + +static struct camera_common_pdata *imx219_parse_dt( + struct tegracam_device *tc_dev) +{ + struct device *dev = tc_dev->dev; + struct device_node *np = dev->of_node; + struct camera_common_pdata *board_priv_pdata; + const struct of_device_id *match; + struct camera_common_pdata *ret = NULL; + int err = 0; + int gpio; + + if (!np) + return NULL; + + match = of_match_device(imx219_of_match, dev); + if (!match) { + dev_err(dev, "Failed to find matching dt id\n"); + return NULL; + } + + board_priv_pdata = devm_kzalloc(dev, + sizeof(*board_priv_pdata), GFP_KERNEL); + if (!board_priv_pdata) + return NULL; + + gpio = of_get_named_gpio(np, "reset-gpios", 0); + if (gpio < 0) { + if (gpio == -EPROBE_DEFER) + ret = ERR_PTR(-EPROBE_DEFER); + dev_err(dev, "reset-gpios not found\n"); + goto error; + } + board_priv_pdata->reset_gpio = (unsigned int)gpio; + + err = of_property_read_string(np, "mclk", &board_priv_pdata->mclk_name); + if (err) + dev_dbg(dev, + "mclk name not present, assume sensor driven externally\n"); + + err = of_property_read_string(np, "avdd-reg", + &board_priv_pdata->regulators.avdd); + err |= of_property_read_string(np, "iovdd-reg", + &board_priv_pdata->regulators.iovdd); + err |= of_property_read_string(np, "dvdd-reg", + &board_priv_pdata->regulators.dvdd); + if (err) + dev_dbg(dev, + "avdd, iovdd and/or dvdd reglrs. not present, assume sensor powered independently\n"); + + board_priv_pdata->has_eeprom = + of_property_read_bool(np, "has-eeprom"); + + return board_priv_pdata; + +error: + devm_kfree(dev, board_priv_pdata); + + return ret; +} + +static int imx219_set_mode(struct tegracam_device *tc_dev) +{ + struct imx219 *priv = (struct imx219 *)tegracam_get_privdata(tc_dev); + struct camera_common_data *s_data = tc_dev->s_data; + + int err = 0; + + err = imx219_write_table(priv, mode_table[IMX219_MODE_COMMON]); + if (err) + return err; + + if (s_data->mode < 0) + return -EINVAL; + err = imx219_write_table(priv, mode_table[s_data->mode]); + if (err) + return err; + + return 0; +} + +static int imx219_start_streaming(struct tegracam_device *tc_dev) +{ + struct imx219 *priv = (struct imx219 *)tegracam_get_privdata(tc_dev); + + return imx219_write_table(priv, mode_table[IMX219_START_STREAM]); +} + +static int imx219_stop_streaming(struct tegracam_device *tc_dev) +{ + struct imx219 *priv = (struct imx219 *)tegracam_get_privdata(tc_dev); + + return imx219_write_table(priv, mode_table[IMX219_STOP_STREAM]); +} + +static struct camera_common_sensor_ops imx219_common_ops = { + .numfrmfmts = ARRAY_SIZE(imx219_frmfmt), + .frmfmt_table = imx219_frmfmt, + .power_on = imx219_power_on, + .power_off = imx219_power_off, + .write_reg = imx219_write_reg, + .read_reg = imx219_read_reg, + .parse_dt = imx219_parse_dt, + .power_get = imx219_power_get, + .power_put = imx219_power_put, + .set_mode = imx219_set_mode, + .start_streaming = imx219_start_streaming, + .stop_streaming = imx219_stop_streaming, +}; + +static int imx219_board_setup(struct imx219 *priv) +{ + struct camera_common_data *s_data = priv->s_data; + struct camera_common_pdata *pdata = s_data->pdata; + struct device *dev = s_data->dev; + u8 reg_val[2]; + int err = 0; + + if (pdata->mclk_name) { + err = camera_common_mclk_enable(s_data); + if (err) { + dev_err(dev, "error turning on mclk (%d)\n", err); + goto done; + } + } + + err = imx219_power_on(s_data); + if (err) { + dev_err(dev, "error during power on sensor (%d)\n", err); + goto err_power_on; + } + + /* Probe sensor model id registers */ + err = imx219_read_reg(s_data, IMX219_MODEL_ID_ADDR_MSB, ®_val[0]); + if (err) { + dev_err(dev, "%s: error during i2c read probe (%d)\n", + __func__, err); + goto err_reg_probe; + } + err = imx219_read_reg(s_data, IMX219_MODEL_ID_ADDR_LSB, ®_val[1]); + if (err) { + dev_err(dev, "%s: error during i2c read probe (%d)\n", + __func__, err); + goto err_reg_probe; + } + if (!((reg_val[0] == 0x02) && reg_val[1] == 0x19)) + dev_err(dev, "%s: invalid sensor model id: %x%x\n", + __func__, reg_val[0], reg_val[1]); + + /* Sensor fine integration time */ + err = imx219_get_fine_integ_time(priv, &priv->fine_integ_time); + if (err) + dev_err(dev, "%s: error querying sensor fine integ. time\n", + __func__); + +err_reg_probe: + imx219_power_off(s_data); + +err_power_on: + if (pdata->mclk_name) + camera_common_mclk_disable(s_data); + +done: + return err; +} + +static int imx219_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + dev_dbg(&client->dev, "%s:\n", __func__); + + return 0; +} + +static const struct v4l2_subdev_internal_ops imx219_subdev_internal_ops = { + .open = imx219_open, +}; + +#if KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE +static int imx219_probe(struct i2c_client *client) +#else +static int imx219_probe(struct i2c_client *client, + const struct i2c_device_id *id) +#endif +{ + struct device *dev = &client->dev; + struct tegracam_device *tc_dev; + struct imx219 *priv; + int err; + + dev_dbg(dev, "probing v4l2 sensor at addr 0x%0x\n", client->addr); + + if (!IS_ENABLED(CONFIG_OF) || !client->dev.of_node) + return -EINVAL; + + priv = devm_kzalloc(dev, + sizeof(struct imx219), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + tc_dev = devm_kzalloc(dev, + sizeof(struct tegracam_device), GFP_KERNEL); + if (!tc_dev) + return -ENOMEM; + + priv->i2c_client = tc_dev->client = client; + tc_dev->dev = dev; + strncpy(tc_dev->name, "imx219", sizeof(tc_dev->name)); + tc_dev->dev_regmap_config = &sensor_regmap_config; + tc_dev->sensor_ops = &imx219_common_ops; + tc_dev->v4l2sd_internal_ops = &imx219_subdev_internal_ops; + tc_dev->tcctrl_ops = &imx219_ctrl_ops; + + err = tegracam_device_register(tc_dev); + if (err) { + dev_err(dev, "tegra camera driver registration failed\n"); + return err; + } + priv->tc_dev = tc_dev; + priv->s_data = tc_dev->s_data; + priv->subdev = &tc_dev->s_data->subdev; + tegracam_set_privdata(tc_dev, (void *)priv); + + err = imx219_board_setup(priv); + if (err) { + tegracam_device_unregister(tc_dev); + dev_err(dev, "board setup failed\n"); + return err; + } + + err = tegracam_v4l2subdev_register(tc_dev, true); + if (err) { + dev_err(dev, "tegra camera subdev registration failed\n"); + return err; + } + + dev_dbg(dev, "detected imx219 sensor\n"); + + return 0; +} + +#if KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE +static void imx219_remove(struct i2c_client *client) +#else +static int imx219_remove(struct i2c_client *client) +#endif +{ + struct camera_common_data *s_data = to_camera_common_data(&client->dev); + struct imx219 *priv = (struct imx219 *)s_data->priv; + + tegracam_v4l2subdev_unregister(priv->tc_dev); + tegracam_device_unregister(priv->tc_dev); + +#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE + return 0; +#endif +} + +static const struct i2c_device_id imx219_id[] = { + { "imx219", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, imx219_id); + +static struct i2c_driver imx219_i2c_driver = { + .driver = { + .name = "imx219", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(imx219_of_match), + }, + .probe = imx219_probe, + .remove = imx219_remove, + .id_table = imx219_id, +}; +module_i2c_driver(imx219_i2c_driver); + +MODULE_DESCRIPTION("Media Controller driver for Sony IMX219"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/include/media/imx219.h b/include/media/imx219.h new file mode 100644 index 00000000..24bb6471 --- /dev/null +++ b/include/media/imx219.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + */ + +#ifndef __IMX219_H__ +#define __IMX219_H__ + +#include + +#define IMX219_FUSE_ID_SIZE 6 +#define IMX219_FUSE_ID_STR_SIZE (IMX219_FUSE_ID_SIZE * 2) + +struct imx219_power_rail { + struct regulator *dvdd; + struct regulator *avdd; + struct regulator *iovdd; + struct regulator *vdd_af; +}; + +struct imx219_platform_data { + struct imx219_flash_control flash_cap; + const char *mclk_name; /* NULL for default default_mclk */ + int (*power_on)(struct imx219_power_rail *pw); + int (*power_off)(struct imx219_power_rail *pw); +}; + +#endif /* __IMX219_H__ */ diff --git a/include/media/nvc.h b/include/media/nvc.h index d9bac299..62747a97 100644 --- a/include/media/nvc.h +++ b/include/media/nvc.h @@ -1,7 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2012-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. - * + * Copyright (c) 2012-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #ifndef __NVC_H__ diff --git a/include/uapi/media/imx219.h b/include/uapi/media/imx219.h new file mode 100644 index 00000000..af053670 --- /dev/null +++ b/include/uapi/media/imx219.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * include/uapi/linux/imx219.h + */ + +#ifndef __UAPI_IMX219_H__ +#define __UAPI_IMX219_H__ + +#include +#include + +#define IMX219_IOCTL_SET_MODE _IOW('o', 1, struct imx219_mode) +#define IMX219_IOCTL_GET_STATUS _IOR('o', 2, __u8) +#define IMX219_IOCTL_SET_FRAME_LENGTH _IOW('o', 3, __u32) +#define IMX219_IOCTL_SET_COARSE_TIME _IOW('o', 4, __u32) +#define IMX219_IOCTL_SET_GAIN _IOW('o', 5, struct imx219_gain) +#define IMX219_IOCTL_GET_FUSEID _IOR('o', 6, struct nvc_fuseid) +#define IMX219_IOCTL_SET_GROUP_HOLD _IOW('o', 7, struct imx219_ae) +#define IMX219_IOCTL_GET_AFDAT _IOR('o', 8, __u32) +#define IMX219_IOCTL_SET_POWER _IOW('o', 20, __u32) +#define IMX219_IOCTL_GET_FLASH_CAP _IOR('o', 30, __u32) +#define IMX219_IOCTL_SET_FLASH_MODE _IOW('o', 31, \ + struct imx219_flash_control) + +/* TODO: revisit these values for IMX219 */ +#define IMX219_FRAME_LENGTH_ADDR_MSB 0x0160 +#define IMX219_FRAME_LENGTH_ADDR_LSB 0x0161 +#define IMX219_COARSE_TIME_ADDR_MSB 0x015a +#define IMX219_COARSE_TIME_ADDR_LSB 0x015b +#define IMX219_GAIN_ADDR 0x0157 + +struct imx219_flash_control { + __u8 enable; + __u8 edge_trig_en; + __u8 start_edge; + __u8 repeat; + __u16 delay_frm; +}; + +struct imx219_mode { + int xres; + int yres; + __u32 frame_length; + __u32 coarse_time; + __u32 gain; +}; + +struct imx219_ae { + __u32 frame_length; + __u8 frame_length_enable; + __u32 coarse_time; + __u8 coarse_time_enable; + __u32 gain; + __u8 gain_enable; +}; + +#endif /* __UAPI_IMX219_H__ */