From e5879baf0310fb9bfab954801a359d2f12d97941 Mon Sep 17 00:00:00 2001 From: Stefan Riedmueller Date: Mon, 26 Jul 2021 09:35:17 +0200 Subject: media: dt-bindings: mt9p031: Convert bindings to yaml Convert mt9p031 sensor bindings to yaml schema. Also update the MAINTAINERS entry. Although input-clock-frequency and pixel-clock-frequency have not been definded as endpoint propierties in the textual bindings, the sensor does parse them from the endpoint. Thus move these properties to the endpoint in the new yaml bindings. Signed-off-by: Stefan Riedmueller Reviewed-by: Rob Herring Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 5b33791bb8e9..943b7bc93c93 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12747,6 +12747,7 @@ M: Laurent Pinchart L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/aptina,mt9p031.yaml F: drivers/media/i2c/mt9p031.c F: include/media/i2c/mt9p031.h -- cgit v1.2.3 From 7ee85054682299babdb32f597b11daeec2d3e9d9 Mon Sep 17 00:00:00 2001 From: Arec Kao Date: Tue, 17 Aug 2021 06:46:07 +0200 Subject: media: Add sensor driver support for the ov13b10 camera. This driver supports following features: - phase detection auto focus (PDAF) - manual exposure and analog/digital gain control - vblank/hblank control - test pattern - image vertical flip and horizontal mirror control - 4208x3120 at 30FPS - 2080x1170 at 60FPS Signed-off-by: Arec Kao Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 7 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ov13b10.c | 1491 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1509 insertions(+) create mode 100644 drivers/media/i2c/ov13b10.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 943b7bc93c93..f3761373e7c5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13800,6 +13800,13 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov13858.c +OMNIVISION OV13B10 SENSOR DRIVER +M: Arec Kao +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: drivers/media/i2c/ov13b10.c + OMNIVISION OV2680 SENSOR DRIVER M: Rui Miguel Silva L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index c26b05ed1f72..cfbcda1f7e48 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1186,6 +1186,16 @@ config VIDEO_OV13858 This is a Video4Linux2 sensor driver for the OmniVision OV13858 camera. +config VIDEO_OV13B10 + tristate "OmniVision OV13B10 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV13B10 camera. + config VIDEO_VS6624 tristate "ST VS6624 sensor support" depends on VIDEO_V4L2 && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 83268f20aa3a..5ac8d639e5ca 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_VIDEO_OV9640) += ov9640.o obj-$(CONFIG_VIDEO_OV9650) += ov9650.o obj-$(CONFIG_VIDEO_OV9734) += ov9734.o obj-$(CONFIG_VIDEO_OV13858) += ov13858.o +obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o obj-$(CONFIG_VIDEO_MT9M032) += mt9m032.o obj-$(CONFIG_VIDEO_MT9M111) += mt9m111.o diff --git a/drivers/media/i2c/ov13b10.c b/drivers/media/i2c/ov13b10.c new file mode 100644 index 000000000000..7caeae641051 --- /dev/null +++ b/drivers/media/i2c/ov13b10.c @@ -0,0 +1,1491 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include + +#define OV13B10_REG_VALUE_08BIT 1 +#define OV13B10_REG_VALUE_16BIT 2 +#define OV13B10_REG_VALUE_24BIT 3 + +#define OV13B10_REG_MODE_SELECT 0x0100 +#define OV13B10_MODE_STANDBY 0x00 +#define OV13B10_MODE_STREAMING 0x01 + +#define OV13B10_REG_SOFTWARE_RST 0x0103 +#define OV13B10_SOFTWARE_RST 0x01 + +/* Chip ID */ +#define OV13B10_REG_CHIP_ID 0x300a +#define OV13B10_CHIP_ID 0x560d42 + +/* V_TIMING internal */ +#define OV13B10_REG_VTS 0x380e +#define OV13B10_VTS_30FPS 0x0c7c +#define OV13B10_VTS_60FPS 0x063e +#define OV13B10_VTS_MAX 0x7fff + +/* HBLANK control - read only */ +#define OV13B10_PPL_560MHZ 4704 + +/* Exposure control */ +#define OV13B10_REG_EXPOSURE 0x3500 +#define OV13B10_EXPOSURE_MIN 4 +#define OV13B10_EXPOSURE_STEP 1 +#define OV13B10_EXPOSURE_DEFAULT 0x40 + +/* Analog gain control */ +#define OV13B10_REG_ANALOG_GAIN 0x3508 +#define OV13B10_ANA_GAIN_MIN 0x80 +#define OV13B10_ANA_GAIN_MAX 0x07c0 +#define OV13B10_ANA_GAIN_STEP 1 +#define OV13B10_ANA_GAIN_DEFAULT 0x80 + +/* Digital gain control */ +#define OV13B10_REG_DGTL_GAIN_H 0x350a +#define OV13B10_REG_DGTL_GAIN_M 0x350b +#define OV13B10_REG_DGTL_GAIN_L 0x350c + +#define OV13B10_DGTL_GAIN_MIN 1024 /* Min = 1 X */ +#define OV13B10_DGTL_GAIN_MAX (4096 - 1) /* Max = 4 X */ +#define OV13B10_DGTL_GAIN_DEFAULT 2560 /* Default gain = 2.5 X */ +#define OV13B10_DGTL_GAIN_STEP 1 /* Each step = 1/1024 */ + +#define OV13B10_DGTL_GAIN_L_SHIFT 6 +#define OV13B10_DGTL_GAIN_L_MASK 0x3 +#define OV13B10_DGTL_GAIN_M_SHIFT 2 +#define OV13B10_DGTL_GAIN_M_MASK 0xff +#define OV13B10_DGTL_GAIN_H_SHIFT 10 +#define OV13B10_DGTL_GAIN_H_MASK 0x3 + +/* Test Pattern Control */ +#define OV13B10_REG_TEST_PATTERN 0x5080 +#define OV13B10_TEST_PATTERN_ENABLE BIT(7) +#define OV13B10_TEST_PATTERN_MASK 0xf3 +#define OV13B10_TEST_PATTERN_BAR_SHIFT 2 + +/* Flip Control */ +#define OV13B10_REG_FORMAT1 0x3820 +#define OV13B10_REG_FORMAT2 0x3821 + +/* Horizontal Window Offset */ +#define OV13B10_REG_H_WIN_OFFSET 0x3811 + +/* Vertical Window Offset */ +#define OV13B10_REG_V_WIN_OFFSET 0x3813 + +struct ov13b10_reg { + u16 address; + u8 val; +}; + +struct ov13b10_reg_list { + u32 num_of_regs; + const struct ov13b10_reg *regs; +}; + +/* Link frequency config */ +struct ov13b10_link_freq_config { + u32 pixels_per_line; + + /* registers for this link frequency */ + struct ov13b10_reg_list reg_list; +}; + +/* Mode : resolution and related config&values */ +struct ov13b10_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + /* V-timing */ + u32 vts_def; + u32 vts_min; + + /* Index of Link frequency config to be used */ + u32 link_freq_index; + /* Default register values */ + struct ov13b10_reg_list reg_list; +}; + +/* 4208x3120 needs 1120Mbps/lane, 4 lanes */ +static const struct ov13b10_reg mipi_data_rate_1120mbps[] = { + {0x0103, 0x01}, + {0x0303, 0x04}, + {0x0305, 0xaf}, + {0x0321, 0x00}, + {0x0323, 0x04}, + {0x0324, 0x01}, + {0x0325, 0xa4}, + {0x0326, 0x81}, + {0x0327, 0x04}, + {0x3012, 0x07}, + {0x3013, 0x32}, + {0x3107, 0x23}, + {0x3501, 0x0c}, + {0x3502, 0x10}, + {0x3504, 0x08}, + {0x3508, 0x07}, + {0x3509, 0xc0}, + {0x3600, 0x16}, + {0x3601, 0x54}, + {0x3612, 0x4e}, + {0x3620, 0x00}, + {0x3621, 0x68}, + {0x3622, 0x66}, + {0x3623, 0x03}, + {0x3662, 0x92}, + {0x3666, 0xbb}, + {0x3667, 0x44}, + {0x366e, 0xff}, + {0x366f, 0xf3}, + {0x3675, 0x44}, + {0x3676, 0x00}, + {0x367f, 0xe9}, + {0x3681, 0x32}, + {0x3682, 0x1f}, + {0x3683, 0x0b}, + {0x3684, 0x0b}, + {0x3704, 0x0f}, + {0x3706, 0x40}, + {0x3708, 0x3b}, + {0x3709, 0x72}, + {0x370b, 0xa2}, + {0x3714, 0x24}, + {0x371a, 0x3e}, + {0x3725, 0x42}, + {0x3739, 0x12}, + {0x3767, 0x00}, + {0x377a, 0x0d}, + {0x3789, 0x18}, + {0x3790, 0x40}, + {0x3791, 0xa2}, + {0x37c2, 0x04}, + {0x37c3, 0xf1}, + {0x37d9, 0x0c}, + {0x37da, 0x02}, + {0x37dc, 0x02}, + {0x37e1, 0x04}, + {0x37e2, 0x0a}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x08}, + {0x3804, 0x10}, + {0x3805, 0x8f}, + {0x3806, 0x0c}, + {0x3807, 0x47}, + {0x3808, 0x10}, + {0x3809, 0x70}, + {0x380a, 0x0c}, + {0x380b, 0x30}, + {0x380c, 0x04}, + {0x380d, 0x98}, + {0x380e, 0x0c}, + {0x380f, 0x7c}, + {0x3811, 0x0f}, + {0x3813, 0x09}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x381f, 0x08}, + {0x3820, 0x88}, + {0x3821, 0x00}, + {0x3822, 0x14}, + {0x382e, 0xe6}, + {0x3c80, 0x00}, + {0x3c87, 0x01}, + {0x3c8c, 0x19}, + {0x3c8d, 0x1c}, + {0x3ca0, 0x00}, + {0x3ca1, 0x00}, + {0x3ca2, 0x00}, + {0x3ca3, 0x00}, + {0x3ca4, 0x50}, + {0x3ca5, 0x11}, + {0x3ca6, 0x01}, + {0x3ca7, 0x00}, + {0x3ca8, 0x00}, + {0x4008, 0x02}, + {0x4009, 0x0f}, + {0x400a, 0x01}, + {0x400b, 0x19}, + {0x4011, 0x21}, + {0x4017, 0x08}, + {0x4019, 0x04}, + {0x401a, 0x58}, + {0x4032, 0x1e}, + {0x4050, 0x02}, + {0x4051, 0x09}, + {0x405e, 0x00}, + {0x4066, 0x02}, + {0x4501, 0x00}, + {0x4502, 0x10}, + {0x4505, 0x00}, + {0x4800, 0x64}, + {0x481b, 0x3e}, + {0x481f, 0x30}, + {0x4825, 0x34}, + {0x4837, 0x0e}, + {0x484b, 0x01}, + {0x4883, 0x02}, + {0x5000, 0xff}, + {0x5001, 0x0f}, + {0x5045, 0x20}, + {0x5046, 0x20}, + {0x5047, 0xa4}, + {0x5048, 0x20}, + {0x5049, 0xa4}, + {0x0100, 0x01}, +}; + +static const struct ov13b10_reg mode_4208x3120_regs[] = { + {0x0305, 0xaf}, + {0x3501, 0x0c}, + {0x3662, 0x92}, + {0x3714, 0x24}, + {0x3739, 0x12}, + {0x37c2, 0x04}, + {0x37d9, 0x0c}, + {0x37e2, 0x0a}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x08}, + {0x3804, 0x10}, + {0x3805, 0x8f}, + {0x3806, 0x0c}, + {0x3807, 0x47}, + {0x3808, 0x10}, + {0x3809, 0x70}, + {0x380a, 0x0c}, + {0x380b, 0x30}, + {0x380c, 0x04}, + {0x380d, 0x98}, + {0x380e, 0x0c}, + {0x380f, 0x7c}, + {0x3810, 0x00}, + {0x3811, 0x0f}, + {0x3812, 0x00}, + {0x3813, 0x09}, + {0x3814, 0x01}, + {0x3816, 0x01}, + {0x3820, 0x88}, + {0x3c8c, 0x19}, + {0x4008, 0x02}, + {0x4009, 0x0f}, + {0x4050, 0x02}, + {0x4051, 0x09}, + {0x4501, 0x00}, + {0x4505, 0x00}, + {0x4837, 0x0e}, + {0x5000, 0xff}, + {0x5001, 0x0f}, +}; + +static const struct ov13b10_reg mode_4160x3120_regs[] = { + {0x0305, 0xaf}, + {0x3501, 0x0c}, + {0x3662, 0x92}, + {0x3714, 0x24}, + {0x3739, 0x12}, + {0x37c2, 0x04}, + {0x37d9, 0x0c}, + {0x37e2, 0x0a}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x08}, + {0x3804, 0x10}, + {0x3805, 0x8f}, + {0x3806, 0x0c}, + {0x3807, 0x47}, + {0x3808, 0x10}, + {0x3809, 0x40}, + {0x380a, 0x0c}, + {0x380b, 0x30}, + {0x380c, 0x04}, + {0x380d, 0x98}, + {0x380e, 0x0c}, + {0x380f, 0x7c}, + {0x3810, 0x00}, + {0x3811, 0x27}, + {0x3812, 0x00}, + {0x3813, 0x09}, + {0x3814, 0x01}, + {0x3816, 0x01}, + {0x3820, 0x88}, + {0x3c8c, 0x19}, + {0x4008, 0x02}, + {0x4009, 0x0f}, + {0x4050, 0x02}, + {0x4051, 0x09}, + {0x4501, 0x00}, + {0x4505, 0x00}, + {0x4837, 0x0e}, + {0x5000, 0xff}, + {0x5001, 0x0f}, +}; + +static const struct ov13b10_reg mode_4160x2340_regs[] = { + {0x0305, 0xaf}, + {0x3501, 0x0c}, + {0x3662, 0x92}, + {0x3714, 0x24}, + {0x3739, 0x12}, + {0x37c2, 0x04}, + {0x37d9, 0x0c}, + {0x37e2, 0x0a}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x08}, + {0x3804, 0x10}, + {0x3805, 0x8f}, + {0x3806, 0x0c}, + {0x3807, 0x47}, + {0x3808, 0x10}, + {0x3809, 0x40}, + {0x380a, 0x09}, + {0x380b, 0x24}, + {0x380c, 0x04}, + {0x380d, 0x98}, + {0x380e, 0x0c}, + {0x380f, 0x7c}, + {0x3810, 0x00}, + {0x3811, 0x27}, + {0x3812, 0x01}, + {0x3813, 0x8f}, + {0x3814, 0x01}, + {0x3816, 0x01}, + {0x3820, 0x88}, + {0x3c8c, 0x19}, + {0x4008, 0x02}, + {0x4009, 0x0f}, + {0x4050, 0x02}, + {0x4051, 0x09}, + {0x4501, 0x00}, + {0x4505, 0x00}, + {0x4837, 0x0e}, + {0x5000, 0xff}, + {0x5001, 0x0f}, +}; + +static const struct ov13b10_reg mode_2104x1560_regs[] = { + {0x0305, 0xaf}, + {0x3501, 0x06}, + {0x3662, 0x88}, + {0x3714, 0x28}, + {0x3739, 0x10}, + {0x37c2, 0x14}, + {0x37d9, 0x06}, + {0x37e2, 0x0c}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x08}, + {0x3804, 0x10}, + {0x3805, 0x8f}, + {0x3806, 0x0c}, + {0x3807, 0x47}, + {0x3808, 0x08}, + {0x3809, 0x38}, + {0x380a, 0x06}, + {0x380b, 0x18}, + {0x380c, 0x04}, + {0x380d, 0x98}, + {0x380e, 0x06}, + {0x380f, 0x3e}, + {0x3810, 0x00}, + {0x3811, 0x07}, + {0x3812, 0x00}, + {0x3813, 0x05}, + {0x3814, 0x03}, + {0x3816, 0x03}, + {0x3820, 0x8b}, + {0x3c8c, 0x18}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x4050, 0x00}, + {0x4051, 0x05}, + {0x4501, 0x08}, + {0x4505, 0x00}, + {0x4837, 0x0e}, + {0x5000, 0xfd}, + {0x5001, 0x0d}, +}; + +static const struct ov13b10_reg mode_2080x1170_regs[] = { + {0x0305, 0xaf}, + {0x3501, 0x06}, + {0x3662, 0x88}, + {0x3714, 0x28}, + {0x3739, 0x10}, + {0x37c2, 0x14}, + {0x37d9, 0x06}, + {0x37e2, 0x0c}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x08}, + {0x3804, 0x10}, + {0x3805, 0x8f}, + {0x3806, 0x0c}, + {0x3807, 0x47}, + {0x3808, 0x08}, + {0x3809, 0x20}, + {0x380a, 0x04}, + {0x380b, 0x92}, + {0x380c, 0x04}, + {0x380d, 0x98}, + {0x380e, 0x06}, + {0x380f, 0x3e}, + {0x3810, 0x00}, + {0x3811, 0x13}, + {0x3812, 0x00}, + {0x3813, 0xc9}, + {0x3814, 0x03}, + {0x3816, 0x03}, + {0x3820, 0x8b}, + {0x3c8c, 0x18}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x4050, 0x00}, + {0x4051, 0x05}, + {0x4501, 0x08}, + {0x4505, 0x00}, + {0x4837, 0x0e}, + {0x5000, 0xfd}, + {0x5001, 0x0d}, +}; + +static const char * const ov13b10_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Configurations for supported link frequencies */ +#define OV13B10_LINK_FREQ_560MHZ 560000000ULL +#define OV13B10_LINK_FREQ_INDEX_0 0 + +#define OV13B10_EXT_CLK 19200000 +#define OV13B10_DATA_LANES 4 + +/* + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample + * data rate => double data rate; number of lanes => 4; bits per pixel => 10 + */ +static u64 link_freq_to_pixel_rate(u64 f) +{ + f *= 2 * OV13B10_DATA_LANES; + do_div(f, 10); + + return f; +} + +/* Menu items for LINK_FREQ V4L2 control */ +static const s64 link_freq_menu_items[] = { + OV13B10_LINK_FREQ_560MHZ +}; + +/* Link frequency configs */ +static const struct ov13b10_link_freq_config + link_freq_configs[] = { + { + .pixels_per_line = OV13B10_PPL_560MHZ, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_1120mbps), + .regs = mipi_data_rate_1120mbps, + } + } +}; + +/* Mode configs */ +static const struct ov13b10_mode supported_modes[] = { + { + .width = 4208, + .height = 3120, + .vts_def = OV13B10_VTS_30FPS, + .vts_min = OV13B10_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4208x3120_regs), + .regs = mode_4208x3120_regs, + }, + .link_freq_index = OV13B10_LINK_FREQ_INDEX_0, + }, + { + .width = 4160, + .height = 3120, + .vts_def = OV13B10_VTS_30FPS, + .vts_min = OV13B10_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4160x3120_regs), + .regs = mode_4160x3120_regs, + }, + .link_freq_index = OV13B10_LINK_FREQ_INDEX_0, + }, + { + .width = 4160, + .height = 2340, + .vts_def = OV13B10_VTS_30FPS, + .vts_min = OV13B10_VTS_30FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4160x2340_regs), + .regs = mode_4160x2340_regs, + }, + .link_freq_index = OV13B10_LINK_FREQ_INDEX_0, + }, + { + .width = 2104, + .height = 1560, + .vts_def = OV13B10_VTS_60FPS, + .vts_min = OV13B10_VTS_60FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2104x1560_regs), + .regs = mode_2104x1560_regs, + }, + .link_freq_index = OV13B10_LINK_FREQ_INDEX_0, + }, + { + .width = 2080, + .height = 1170, + .vts_def = OV13B10_VTS_60FPS, + .vts_min = OV13B10_VTS_60FPS, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2080x1170_regs), + .regs = mode_2080x1170_regs, + }, + .link_freq_index = OV13B10_LINK_FREQ_INDEX_0, + } +}; + +struct ov13b10 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov13b10_mode *cur_mode; + + /* Mutex for serialized access */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +#define to_ov13b10(_sd) container_of(_sd, struct ov13b10, sd) + +/* Read registers up to 4 at a time */ +static int ov13b10_read_reg(struct ov13b10 *ov13b, + u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13b->sd); + struct i2c_msg msgs[2]; + u8 *data_be_p; + int ret; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + + if (len > 4) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int ov13b10_write_reg(struct ov13b10 *ov13b, + u16 reg, u32 len, u32 __val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13b->sd); + int buf_i, val_i; + u8 buf[6], *val_p; + __be32 val; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val = cpu_to_be32(__val); + val_p = (u8 *)&val; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int ov13b10_write_regs(struct ov13b10 *ov13b, + const struct ov13b10_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13b->sd); + int ret; + u32 i; + + for (i = 0; i < len; i++) { + ret = ov13b10_write_reg(ov13b, regs[i].address, 1, + regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +static int ov13b10_write_reg_list(struct ov13b10 *ov13b, + const struct ov13b10_reg_list *r_list) +{ + return ov13b10_write_regs(ov13b, r_list->regs, r_list->num_of_regs); +} + +/* Open sub-device */ +static int ov13b10_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + const struct ov13b10_mode *default_mode = &supported_modes[0]; + struct ov13b10 *ov13b = to_ov13b10(sd); + struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_get_try_format(sd, + fh->state, + 0); + + mutex_lock(&ov13b->mutex); + + /* Initialize try_fmt */ + try_fmt->width = default_mode->width; + try_fmt->height = default_mode->height; + try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + /* No crop or compose */ + mutex_unlock(&ov13b->mutex); + + return 0; +} + +static int ov13b10_update_digital_gain(struct ov13b10 *ov13b, u32 d_gain) +{ + int ret; + u32 val; + + /* + * 0x350C[7:6], 0x350B[7:0], 0x350A[1:0] + */ + + val = (d_gain & OV13B10_DGTL_GAIN_L_MASK) << OV13B10_DGTL_GAIN_L_SHIFT; + ret = ov13b10_write_reg(ov13b, OV13B10_REG_DGTL_GAIN_L, + OV13B10_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = (d_gain >> OV13B10_DGTL_GAIN_M_SHIFT) & OV13B10_DGTL_GAIN_M_MASK; + ret = ov13b10_write_reg(ov13b, OV13B10_REG_DGTL_GAIN_M, + OV13B10_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = (d_gain >> OV13B10_DGTL_GAIN_H_SHIFT) & OV13B10_DGTL_GAIN_H_MASK; + ret = ov13b10_write_reg(ov13b, OV13B10_REG_DGTL_GAIN_H, + OV13B10_REG_VALUE_08BIT, val); + + return ret; +} + +static int ov13b10_enable_test_pattern(struct ov13b10 *ov13b, u32 pattern) +{ + int ret; + u32 val; + + ret = ov13b10_read_reg(ov13b, OV13B10_REG_TEST_PATTERN, + OV13B10_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + if (pattern) { + val &= OV13B10_TEST_PATTERN_MASK; + val |= ((pattern - 1) << OV13B10_TEST_PATTERN_BAR_SHIFT) | + OV13B10_TEST_PATTERN_ENABLE; + } else { + val &= ~OV13B10_TEST_PATTERN_ENABLE; + } + + return ov13b10_write_reg(ov13b, OV13B10_REG_TEST_PATTERN, + OV13B10_REG_VALUE_08BIT, val); +} + +static int ov13b10_set_ctrl_hflip(struct ov13b10 *ov13b, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov13b10_read_reg(ov13b, OV13B10_REG_FORMAT1, + OV13B10_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov13b10_write_reg(ov13b, OV13B10_REG_FORMAT1, + OV13B10_REG_VALUE_08BIT, + ctrl_val ? val & ~BIT(3) : val); + + if (ret) + return ret; + + ret = ov13b10_read_reg(ov13b, OV13B10_REG_H_WIN_OFFSET, + OV13B10_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + /* + * Applying cropping offset to reverse the change of Bayer order + * after mirroring image + */ + return ov13b10_write_reg(ov13b, OV13B10_REG_H_WIN_OFFSET, + OV13B10_REG_VALUE_08BIT, + ctrl_val ? ++val : val); +} + +static int ov13b10_set_ctrl_vflip(struct ov13b10 *ov13b, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov13b10_read_reg(ov13b, OV13B10_REG_FORMAT1, + OV13B10_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov13b10_write_reg(ov13b, OV13B10_REG_FORMAT1, + OV13B10_REG_VALUE_08BIT, + ctrl_val ? val | BIT(4) | BIT(5) : val); + + if (ret) + return ret; + + ret = ov13b10_read_reg(ov13b, OV13B10_REG_V_WIN_OFFSET, + OV13B10_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + /* + * Applying cropping offset to reverse the change of Bayer order + * after flipping image + */ + return ov13b10_write_reg(ov13b, OV13B10_REG_V_WIN_OFFSET, + OV13B10_REG_VALUE_08BIT, + ctrl_val ? --val : val); +} + +static int ov13b10_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov13b10 *ov13b = container_of(ctrl->handler, + struct ov13b10, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov13b->sd); + s64 max; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov13b->cur_mode->height + ctrl->val - 8; + __v4l2_ctrl_modify_range(ov13b->exposure, + ov13b->exposure->minimum, + max, ov13b->exposure->step, max); + break; + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + ret = 0; + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ov13b10_write_reg(ov13b, OV13B10_REG_ANALOG_GAIN, + OV13B10_REG_VALUE_16BIT, + ctrl->val << 1); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = ov13b10_update_digital_gain(ov13b, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = ov13b10_write_reg(ov13b, OV13B10_REG_EXPOSURE, + OV13B10_REG_VALUE_24BIT, + ctrl->val); + break; + case V4L2_CID_VBLANK: + ret = ov13b10_write_reg(ov13b, OV13B10_REG_VTS, + OV13B10_REG_VALUE_16BIT, + ov13b->cur_mode->height + + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov13b10_enable_test_pattern(ov13b, ctrl->val); + break; + case V4L2_CID_HFLIP: + ov13b10_set_ctrl_hflip(ov13b, ctrl->val); + break; + case V4L2_CID_VFLIP: + ov13b10_set_ctrl_vflip(ov13b, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov13b10_ctrl_ops = { + .s_ctrl = ov13b10_set_ctrl, +}; + +static int ov13b10_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order(GRBG) is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov13b10_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void ov13b10_update_pad_format(const struct ov13b10_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int ov13b10_do_get_pad_format(struct ov13b10 *ov13b, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_subdev *sd = &ov13b->sd; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + fmt->format = *framefmt; + } else { + ov13b10_update_pad_format(ov13b->cur_mode, fmt); + } + + return 0; +} + +static int ov13b10_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov13b10 *ov13b = to_ov13b10(sd); + int ret; + + mutex_lock(&ov13b->mutex); + ret = ov13b10_do_get_pad_format(ov13b, sd_state, fmt); + mutex_unlock(&ov13b->mutex); + + return ret; +} + +static int +ov13b10_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov13b10 *ov13b = to_ov13b10(sd); + const struct ov13b10_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + s64 pixel_rate; + s64 link_freq; + + mutex_lock(&ov13b->mutex); + + /* Only one raw bayer(GRBG) order is supported */ + if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10) + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + ov13b10_update_pad_format(mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + *framefmt = fmt->format; + } else { + ov13b->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov13b->link_freq, mode->link_freq_index); + link_freq = link_freq_menu_items[mode->link_freq_index]; + pixel_rate = link_freq_to_pixel_rate(link_freq); + __v4l2_ctrl_s_ctrl_int64(ov13b->pixel_rate, pixel_rate); + + /* Update limits and set FPS to default */ + vblank_def = ov13b->cur_mode->vts_def - + ov13b->cur_mode->height; + vblank_min = ov13b->cur_mode->vts_min - + ov13b->cur_mode->height; + __v4l2_ctrl_modify_range(ov13b->vblank, vblank_min, + OV13B10_VTS_MAX + - ov13b->cur_mode->height, + 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov13b->vblank, vblank_def); + h_blank = + link_freq_configs[mode->link_freq_index].pixels_per_line + - ov13b->cur_mode->width; + __v4l2_ctrl_modify_range(ov13b->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&ov13b->mutex); + + return 0; +} + +static int ov13b10_start_streaming(struct ov13b10 *ov13b) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13b->sd); + const struct ov13b10_reg_list *reg_list; + int ret, link_freq_index; + + /* Get out of from software reset */ + ret = ov13b10_write_reg(ov13b, OV13B10_REG_SOFTWARE_RST, + OV13B10_REG_VALUE_08BIT, OV13B10_SOFTWARE_RST); + if (ret) { + dev_err(&client->dev, "%s failed to set powerup registers\n", + __func__); + return ret; + } + + link_freq_index = ov13b->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = ov13b10_write_reg_list(ov13b, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &ov13b->cur_mode->reg_list; + ret = ov13b10_write_reg_list(ov13b, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(ov13b->sd.ctrl_handler); + if (ret) + return ret; + + return ov13b10_write_reg(ov13b, OV13B10_REG_MODE_SELECT, + OV13B10_REG_VALUE_08BIT, + OV13B10_MODE_STREAMING); +} + +/* Stop streaming */ +static int ov13b10_stop_streaming(struct ov13b10 *ov13b) +{ + return ov13b10_write_reg(ov13b, OV13B10_REG_MODE_SELECT, + OV13B10_REG_VALUE_08BIT, OV13B10_MODE_STANDBY); +} + +static int ov13b10_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov13b10 *ov13b = to_ov13b10(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&ov13b->mutex); + if (ov13b->streaming == enable) { + mutex_unlock(&ov13b->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto err_unlock; + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = ov13b10_start_streaming(ov13b); + if (ret) + goto err_rpm_put; + } else { + ov13b10_stop_streaming(ov13b); + pm_runtime_put(&client->dev); + } + + ov13b->streaming = enable; + mutex_unlock(&ov13b->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&ov13b->mutex); + + return ret; +} + +static int __maybe_unused ov13b10_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov13b10 *ov13b = to_ov13b10(sd); + + if (ov13b->streaming) + ov13b10_stop_streaming(ov13b); + + return 0; +} + +static int __maybe_unused ov13b10_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov13b10 *ov13b = to_ov13b10(sd); + int ret; + + if (ov13b->streaming) { + ret = ov13b10_start_streaming(ov13b); + if (ret) + goto error; + } + + return 0; + +error: + ov13b10_stop_streaming(ov13b); + ov13b->streaming = false; + return ret; +} + +/* Verify chip ID */ +static int ov13b10_identify_module(struct ov13b10 *ov13b) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13b->sd); + int ret; + u32 val; + + ret = ov13b10_read_reg(ov13b, OV13B10_REG_CHIP_ID, + OV13B10_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV13B10_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + OV13B10_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops ov13b10_video_ops = { + .s_stream = ov13b10_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov13b10_pad_ops = { + .enum_mbus_code = ov13b10_enum_mbus_code, + .get_fmt = ov13b10_get_pad_format, + .set_fmt = ov13b10_set_pad_format, + .enum_frame_size = ov13b10_enum_frame_size, +}; + +static const struct v4l2_subdev_ops ov13b10_subdev_ops = { + .video = &ov13b10_video_ops, + .pad = &ov13b10_pad_ops, +}; + +static const struct media_entity_operations ov13b10_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov13b10_internal_ops = { + .open = ov13b10_open, +}; + +/* Initialize control handlers */ +static int ov13b10_init_controls(struct ov13b10 *ov13b) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov13b->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max; + s64 vblank_def; + s64 vblank_min; + s64 hblank; + s64 pixel_rate_min; + s64 pixel_rate_max; + const struct ov13b10_mode *mode; + u32 max; + int ret; + + ctrl_hdlr = &ov13b->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + mutex_init(&ov13b->mutex); + ctrl_hdlr->lock = &ov13b->mutex; + max = ARRAY_SIZE(link_freq_menu_items) - 1; + ov13b->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &ov13b10_ctrl_ops, + V4L2_CID_LINK_FREQ, + max, + 0, + link_freq_menu_items); + if (ov13b->link_freq) + ov13b->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]); + pixel_rate_min = 0; + /* By default, PIXEL_RATE is read only */ + ov13b->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov13b10_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate_min, pixel_rate_max, + 1, pixel_rate_max); + + mode = ov13b->cur_mode; + vblank_def = mode->vts_def - mode->height; + vblank_min = mode->vts_min - mode->height; + ov13b->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov13b10_ctrl_ops, + V4L2_CID_VBLANK, + vblank_min, + OV13B10_VTS_MAX - mode->height, 1, + vblank_def); + + hblank = link_freq_configs[mode->link_freq_index].pixels_per_line - + mode->width; + ov13b->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov13b10_ctrl_ops, + V4L2_CID_HBLANK, + hblank, hblank, 1, hblank); + if (ov13b->hblank) + ov13b->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + exposure_max = mode->vts_def - 8; + ov13b->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov13b10_ctrl_ops, + V4L2_CID_EXPOSURE, + OV13B10_EXPOSURE_MIN, + exposure_max, OV13B10_EXPOSURE_STEP, + exposure_max); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov13b10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV13B10_ANA_GAIN_MIN, OV13B10_ANA_GAIN_MAX, + OV13B10_ANA_GAIN_STEP, OV13B10_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov13b10_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV13B10_DGTL_GAIN_MIN, OV13B10_DGTL_GAIN_MAX, + OV13B10_DGTL_GAIN_STEP, OV13B10_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov13b10_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov13b10_test_pattern_menu) - 1, + 0, 0, ov13b10_test_pattern_menu); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov13b10_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(ctrl_hdlr, &ov13b10_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov13b10_ctrl_ops, + &props); + if (ret) + goto error; + + ov13b->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&ov13b->mutex); + + return ret; +} + +static void ov13b10_free_controls(struct ov13b10 *ov13b) +{ + v4l2_ctrl_handler_free(ov13b->sd.ctrl_handler); + mutex_destroy(&ov13b->mutex); +} + +static int ov13b10_check_hwcfg(struct device *dev) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + unsigned int i, j; + int ret; + u32 ext_clk; + + if (!fwnode) + return -ENXIO; + + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &ext_clk); + if (ret) { + dev_err(dev, "can't get clock frequency"); + return ret; + } + + if (ext_clk != OV13B10_EXT_CLK) { + dev_err(dev, "external clock %d is not supported", + ext_clk); + return -EINVAL; + } + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV13B10_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto out_err; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto out_err; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto out_err; + } + } + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov13b10_probe(struct i2c_client *client) +{ + struct ov13b10 *ov13b; + int ret; + + /* Check HW config */ + ret = ov13b10_check_hwcfg(&client->dev); + if (ret) { + dev_err(&client->dev, "failed to check hwcfg: %d", ret); + return ret; + } + + ov13b = devm_kzalloc(&client->dev, sizeof(*ov13b), GFP_KERNEL); + if (!ov13b) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov13b->sd, client, &ov13b10_subdev_ops); + + /* Check module identity */ + ret = ov13b10_identify_module(ov13b); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + return ret; + } + + /* Set default mode to max resolution */ + ov13b->cur_mode = &supported_modes[0]; + + ret = ov13b10_init_controls(ov13b); + if (ret) + return ret; + + /* Initialize subdev */ + ov13b->sd.internal_ops = &ov13b10_internal_ops; + ov13b->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov13b->sd.entity.ops = &ov13b10_subdev_entity_ops; + ov13b->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + ov13b->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov13b->sd.entity, 1, &ov13b->pad); + if (ret) { + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&ov13b->sd); + if (ret < 0) + goto error_media_entity; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&ov13b->sd.entity); + +error_handler_free: + ov13b10_free_controls(ov13b); + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + + return ret; +} + +static int ov13b10_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov13b10 *ov13b = to_ov13b10(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + ov13b10_free_controls(ov13b); + + pm_runtime_disable(&client->dev); + + return 0; +} + +static const struct dev_pm_ops ov13b10_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov13b10_suspend, ov13b10_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov13b10_acpi_ids[] = { + {"OVTIDB10"}, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, ov13b10_acpi_ids); +#endif + +static struct i2c_driver ov13b10_i2c_driver = { + .driver = { + .name = "ov13b10", + .pm = &ov13b10_pm_ops, + .acpi_match_table = ACPI_PTR(ov13b10_acpi_ids), + }, + .probe_new = ov13b10_probe, + .remove = ov13b10_remove, +}; + +module_i2c_driver(ov13b10_i2c_driver); + +MODULE_AUTHOR("Kao, Arec "); +MODULE_DESCRIPTION("Omnivision ov13b10 sensor driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 594a2edbcce5f96a58c3e969693e5394b801d52e Mon Sep 17 00:00:00 2001 From: Cai Huoqing Date: Thu, 2 Sep 2021 13:46:41 +0200 Subject: media: MAINTAINERS: Add linux-renesas-soc mailing list to renesas JPU 'linux-renesas-soc' is the mailing list for renesas SOC driver, so add it to renesas JPU driver info. Signed-off-by: Cai Huoqing Reviewed-by: Geert Uytterhoeven Reviewed-by: Kieran Bingham Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index f3761373e7c5..d7e0febe278b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10033,6 +10033,7 @@ F: include/linux/jbd2.h JPU V4L2 MEM2MEM DRIVER FOR RENESAS M: Mikhail Ulyanov L: linux-media@vger.kernel.org +L: linux-renesas-soc@vger.kernel.org S: Maintained F: drivers/media/platform/rcar_jpu.c -- cgit v1.2.3 From 49b6f9b27ff0fcbee0c034cd56cd5e89fb7cfc31 Mon Sep 17 00:00:00 2001 From: Ezequiel Garcia Date: Thu, 2 Sep 2021 17:25:01 +0200 Subject: media: MAINTAINERS, .mailmap: Update Ezequiel Garcia's email address Update the media drivers I maintain to use my personal mail address. Signed-off-by: Ezequiel Garcia Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .mailmap | 1 + MAINTAINERS | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'MAINTAINERS') diff --git a/.mailmap b/.mailmap index 6e849110cb4e..4f6e37da6058 100644 --- a/.mailmap +++ b/.mailmap @@ -98,6 +98,7 @@ Douglas Gilbert Ed L. Cashin Erik Kaneda Evgeniy Polyakov +Ezequiel Garcia Felipe W Damasio Felix Kuhling Felix Moeller diff --git a/MAINTAINERS b/MAINTAINERS index d7e0febe278b..c4c8de673fc9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8190,7 +8190,7 @@ T: git git://linuxtv.org/anttip/media_tree.git F: drivers/media/usb/hackrf/ HANTRO VPU CODEC DRIVER -M: Ezequiel Garcia +M: Ezequiel Garcia M: Philipp Zabel L: linux-media@vger.kernel.org L: linux-rockchip@lists.infradead.org @@ -16120,7 +16120,7 @@ F: include/uapi/linux/rkisp1-config.h ROCKCHIP RASTER 2D GRAPHIC ACCELERATION UNIT DRIVER M: Jacob Chen -M: Ezequiel Garcia +M: Ezequiel Garcia L: linux-media@vger.kernel.org L: linux-rockchip@lists.infradead.org S: Maintained @@ -16128,7 +16128,7 @@ F: Documentation/devicetree/bindings/media/rockchip-rga.yaml F: drivers/media/platform/rockchip/rga/ ROCKCHIP VIDEO DECODER DRIVER -M: Ezequiel Garcia +M: Ezequiel Garcia L: linux-media@vger.kernel.org L: linux-rockchip@lists.infradead.org S: Maintained -- cgit v1.2.3 From e8c0882685f9152f0d729664a12bcbe749cb7736 Mon Sep 17 00:00:00 2001 From: Martin Kepplinger Date: Mon, 6 Sep 2021 12:28:36 +0200 Subject: media: i2c: add driver for the SK Hynix Hi-846 8M pixel camera The SK Hynix Hi-846 is a 1/4" 8M Pixel CMOS Image Sensor. It supports usual features like I2C control, CSI-2 for frame data, digital/analog gain control or test patterns. This driver supports the 640x480, 1280x720 and 1632x1224 resolution modes. It supports runtime PM in order not to draw any unnecessary power. The part is also called YACG4D0C9SHC and a datasheet can be found at https://product.skhynix.com/products/cis/cis.go The large sets of partly undocumented register values are for example found when searching for the hi846_mipi_raw_Sensor.c Android driver. Signed-off-by: Martin Kepplinger Reviewed-by: Pavel Machek Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 6 + drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/hi846.c | 2190 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2210 insertions(+) create mode 100644 drivers/media/i2c/hi846.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ee91c5472bc1..60deb26c740f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8661,6 +8661,12 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/hi556.c +HYNIX HI846 SENSOR DRIVER +M: Martin Kepplinger +L: linux-media@vger.kernel.org +S: Maintained +F: drivers/media/i2c/hi846.c + Hyper-V/Azure CORE AND DRIVERS M: "K. Y. Srinivasan" M: Haiyang Zhang diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index adb348aa8396..0e56489c97fb 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -743,6 +743,19 @@ config VIDEO_HI556 To compile this driver as a module, choose M here: the module will be called hi556. +config VIDEO_HI846 + tristate "Hynix Hi-846 sensor support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Hynix + Hi-846 camera. + + To compile this driver as a module, choose M here: the + module will be called hi846. + config VIDEO_IMX208 tristate "Sony IMX208 sensor support" depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 5ac8d639e5ca..4d4fe08d7a6a 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -118,6 +118,7 @@ obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o obj-$(CONFIG_VIDEO_HI556) += hi556.o +obj-$(CONFIG_VIDEO_HI846) += hi846.o obj-$(CONFIG_VIDEO_IMX208) += imx208.o obj-$(CONFIG_VIDEO_IMX214) += imx214.o obj-$(CONFIG_VIDEO_IMX219) += imx219.o diff --git a/drivers/media/i2c/hi846.c b/drivers/media/i2c/hi846.c new file mode 100644 index 000000000000..822ce3021fde --- /dev/null +++ b/drivers/media/i2c/hi846.c @@ -0,0 +1,2190 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021 Purism SPC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HI846_MEDIA_BUS_FORMAT MEDIA_BUS_FMT_SGBRG10_1X10 +#define HI846_RGB_DEPTH 10 + +/* Frame length lines / vertical timings */ +#define HI846_REG_FLL 0x0006 +#define HI846_FLL_MAX 0xffff + +/* Horizontal timing */ +#define HI846_REG_LLP 0x0008 +#define HI846_LINE_LENGTH 3800 + +#define HI846_REG_BINNING_MODE 0x000c + +#define HI846_REG_IMAGE_ORIENTATION 0x000e + +#define HI846_REG_UNKNOWN_0022 0x0022 + +#define HI846_REG_Y_ADDR_START_VACT_H 0x0026 +#define HI846_REG_Y_ADDR_START_VACT_L 0x0027 +#define HI846_REG_UNKNOWN_0028 0x0028 + +#define HI846_REG_Y_ADDR_END_VACT_H 0x002c +#define HI846_REG_Y_ADDR_END_VACT_L 0x002d + +#define HI846_REG_Y_ODD_INC_FOBP 0x002e +#define HI846_REG_Y_EVEN_INC_FOBP 0x002f + +#define HI846_REG_Y_ODD_INC_VACT 0x0032 +#define HI846_REG_Y_EVEN_INC_VACT 0x0033 + +#define HI846_REG_GROUPED_PARA_HOLD 0x0046 + +#define HI846_REG_TG_ENABLE 0x004c + +#define HI846_REG_UNKNOWN_005C 0x005c + +#define HI846_REG_UNKNOWN_006A 0x006a + +/* + * Long exposure time. Actually, exposure is a 20 bit value that + * includes the lower 4 bits of 0x0073 too. Only 16 bits are used + * right now + */ +#define HI846_REG_EXPOSURE 0x0074 +#define HI846_EXPOSURE_MIN 6 +#define HI846_EXPOSURE_MAX_MARGIN 2 +#define HI846_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define HI846_REG_ANALOG_GAIN 0x0077 +#define HI846_ANAL_GAIN_MIN 0 +#define HI846_ANAL_GAIN_MAX 240 +#define HI846_ANAL_GAIN_STEP 8 + +/* Digital gain controls from sensor */ +#define HI846_REG_MWB_GR_GAIN_H 0x0078 +#define HI846_REG_MWB_GR_GAIN_L 0x0079 +#define HI846_REG_MWB_GB_GAIN_H 0x007a +#define HI846_REG_MWB_GB_GAIN_L 0x007b +#define HI846_REG_MWB_R_GAIN_H 0x007c +#define HI846_REG_MWB_R_GAIN_L 0x007d +#define HI846_REG_MWB_B_GAIN_H 0x007e +#define HI846_REG_MWB_B_GAIN_L 0x007f +#define HI846_DGTL_GAIN_MIN 512 +#define HI846_DGTL_GAIN_MAX 8191 +#define HI846_DGTL_GAIN_STEP 1 +#define HI846_DGTL_GAIN_DEFAULT 512 + +#define HI846_REG_X_ADDR_START_HACT_H 0x0120 +#define HI846_REG_X_ADDR_END_HACT_H 0x0122 + +#define HI846_REG_UNKNOWN_012A 0x012a + +#define HI846_REG_UNKNOWN_0200 0x0200 + +#define HI846_REG_UNKNOWN_021C 0x021c +#define HI846_REG_UNKNOWN_021E 0x021e + +#define HI846_REG_UNKNOWN_0402 0x0402 +#define HI846_REG_UNKNOWN_0404 0x0404 +#define HI846_REG_UNKNOWN_0408 0x0408 +#define HI846_REG_UNKNOWN_0410 0x0410 +#define HI846_REG_UNKNOWN_0412 0x0412 +#define HI846_REG_UNKNOWN_0414 0x0414 + +#define HI846_REG_UNKNOWN_0418 0x0418 + +#define HI846_REG_UNKNOWN_051E 0x051e + +/* Formatter */ +#define HI846_REG_X_START_H 0x0804 +#define HI846_REG_X_START_L 0x0805 + +/* MIPI */ +#define HI846_REG_UNKNOWN_0900 0x0900 +#define HI846_REG_MIPI_TX_OP_EN 0x0901 +#define HI846_REG_MIPI_TX_OP_MODE 0x0902 +#define HI846_RAW8 BIT(5) + +#define HI846_REG_UNKNOWN_090C 0x090c +#define HI846_REG_UNKNOWN_090E 0x090e + +#define HI846_REG_UNKNOWN_0914 0x0914 +#define HI846_REG_TLPX 0x0915 +#define HI846_REG_TCLK_PREPARE 0x0916 +#define HI846_REG_TCLK_ZERO 0x0917 +#define HI846_REG_UNKNOWN_0918 0x0918 +#define HI846_REG_THS_PREPARE 0x0919 +#define HI846_REG_THS_ZERO 0x091a +#define HI846_REG_THS_TRAIL 0x091b +#define HI846_REG_TCLK_POST 0x091c +#define HI846_REG_TCLK_TRAIL_MIN 0x091d +#define HI846_REG_UNKNOWN_091E 0x091e + +#define HI846_REG_UNKNOWN_0954 0x0954 +#define HI846_REG_UNKNOWN_0956 0x0956 +#define HI846_REG_UNKNOWN_0958 0x0958 +#define HI846_REG_UNKNOWN_095A 0x095a + +/* ISP Common */ +#define HI846_REG_MODE_SELECT 0x0a00 +#define HI846_MODE_STANDBY 0x00 +#define HI846_MODE_STREAMING 0x01 +#define HI846_REG_FAST_STANDBY_MODE 0x0a02 +#define HI846_REG_ISP_EN_H 0x0a04 + +/* Test Pattern Control */ +#define HI846_REG_ISP 0x0a05 +#define HI846_REG_ISP_TPG_EN 0x01 +#define HI846_REG_TEST_PATTERN 0x020a /* 1-9 */ + +#define HI846_REG_UNKNOWN_0A0C 0x0a0c + +/* Windowing */ +#define HI846_REG_X_OUTPUT_SIZE_H 0x0a12 +#define HI846_REG_X_OUTPUT_SIZE_L 0x0a13 +#define HI846_REG_Y_OUTPUT_SIZE_H 0x0a14 +#define HI846_REG_Y_OUTPUT_SIZE_L 0x0a15 + +/* ISP Common */ +#define HI846_REG_PEDESTAL_EN 0x0a1a + +#define HI846_REG_UNKNOWN_0A1E 0x0a1e + +/* Horizontal Binning Mode */ +#define HI846_REG_HBIN_MODE 0x0a22 + +#define HI846_REG_UNKNOWN_0A24 0x0a24 +#define HI846_REG_UNKNOWN_0B02 0x0b02 +#define HI846_REG_UNKNOWN_0B10 0x0b10 +#define HI846_REG_UNKNOWN_0B12 0x0b12 +#define HI846_REG_UNKNOWN_0B14 0x0b14 + +/* BLC (Black Level Calibration) */ +#define HI846_REG_BLC_CTL0 0x0c00 + +#define HI846_REG_UNKNOWN_0C06 0x0c06 +#define HI846_REG_UNKNOWN_0C10 0x0c10 +#define HI846_REG_UNKNOWN_0C12 0x0c12 +#define HI846_REG_UNKNOWN_0C14 0x0c14 +#define HI846_REG_UNKNOWN_0C16 0x0c16 + +#define HI846_REG_UNKNOWN_0E04 0x0e04 + +#define HI846_REG_CHIP_ID_L 0x0f16 +#define HI846_REG_CHIP_ID_H 0x0f17 +#define HI846_CHIP_ID_L 0x46 +#define HI846_CHIP_ID_H 0x08 + +#define HI846_REG_UNKNOWN_0F04 0x0f04 +#define HI846_REG_UNKNOWN_0F08 0x0f08 + +/* PLL */ +#define HI846_REG_PLL_CFG_MIPI2_H 0x0f2a +#define HI846_REG_PLL_CFG_MIPI2_L 0x0f2b + +#define HI846_REG_UNKNOWN_0F30 0x0f30 +#define HI846_REG_PLL_CFG_RAMP1_H 0x0f32 +#define HI846_REG_UNKNOWN_0F36 0x0f36 +#define HI846_REG_PLL_CFG_MIPI1_H 0x0f38 + +#define HI846_REG_UNKNOWN_2008 0x2008 +#define HI846_REG_UNKNOWN_326E 0x326e + +struct hi846_reg { + u16 address; + u16 val; +}; + +struct hi846_reg_list { + u32 num_of_regs; + const struct hi846_reg *regs; +}; + +struct hi846_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timing size */ + u32 llp; + + /* Link frequency needed for this resolution */ + u8 link_freq_index; + + u16 fps; + + /* Vertical timining size */ + u16 frame_len; + + const struct hi846_reg_list reg_list_config; + const struct hi846_reg_list reg_list_2lane; + const struct hi846_reg_list reg_list_4lane; + + /* Position inside of the 3264x2448 pixel array */ + struct v4l2_rect crop; +}; + +static const struct hi846_reg hi846_init_2lane[] = { + {HI846_REG_MODE_SELECT, 0x0000}, + /* regs below are unknown */ + {0x2000, 0x100a}, + {0x2002, 0x00ff}, + {0x2004, 0x0007}, + {0x2006, 0x3fff}, + {0x2008, 0x3fff}, + {0x200a, 0xc216}, + {0x200c, 0x1292}, + {0x200e, 0xc01a}, + {0x2010, 0x403d}, + {0x2012, 0x000e}, + {0x2014, 0x403e}, + {0x2016, 0x0b80}, + {0x2018, 0x403f}, + {0x201a, 0x82ae}, + {0x201c, 0x1292}, + {0x201e, 0xc00c}, + {0x2020, 0x4130}, + {0x2022, 0x43e2}, + {0x2024, 0x0180}, + {0x2026, 0x4130}, + {0x2028, 0x7400}, + {0x202a, 0x5000}, + {0x202c, 0x0253}, + {0x202e, 0x0ad1}, + {0x2030, 0x2360}, + {0x2032, 0x0009}, + {0x2034, 0x5020}, + {0x2036, 0x000b}, + {0x2038, 0x0002}, + {0x203a, 0x0044}, + {0x203c, 0x0016}, + {0x203e, 0x1792}, + {0x2040, 0x7002}, + {0x2042, 0x154f}, + {0x2044, 0x00d5}, + {0x2046, 0x000b}, + {0x2048, 0x0019}, + {0x204a, 0x1698}, + {0x204c, 0x000e}, + {0x204e, 0x099a}, + {0x2050, 0x0058}, + {0x2052, 0x7000}, + {0x2054, 0x1799}, + {0x2056, 0x0310}, + {0x2058, 0x03c3}, + {0x205a, 0x004c}, + {0x205c, 0x064a}, + {0x205e, 0x0001}, + {0x2060, 0x0007}, + {0x2062, 0x0bc7}, + {0x2064, 0x0055}, + {0x2066, 0x7000}, + {0x2068, 0x1550}, + {0x206a, 0x158a}, + {0x206c, 0x0004}, + {0x206e, 0x1488}, + {0x2070, 0x7010}, + {0x2072, 0x1508}, + {0x2074, 0x0004}, + {0x2076, 0x0016}, + {0x2078, 0x03d5}, + {0x207a, 0x0055}, + {0x207c, 0x08ca}, + {0x207e, 0x2019}, + {0x2080, 0x0007}, + {0x2082, 0x7057}, + {0x2084, 0x0fc7}, + {0x2086, 0x5041}, + {0x2088, 0x12c8}, + {0x208a, 0x5060}, + {0x208c, 0x5080}, + {0x208e, 0x2084}, + {0x2090, 0x12c8}, + {0x2092, 0x7800}, + {0x2094, 0x0802}, + {0x2096, 0x040f}, + {0x2098, 0x1007}, + {0x209a, 0x0803}, + {0x209c, 0x080b}, + {0x209e, 0x3803}, + {0x20a0, 0x0807}, + {0x20a2, 0x0404}, + {0x20a4, 0x0400}, + {0x20a6, 0xffff}, + {0x20a8, 0xf0b2}, + {0x20aa, 0xffef}, + {0x20ac, 0x0a84}, + {0x20ae, 0x1292}, + {0x20b0, 0xc02e}, + {0x20b2, 0x4130}, + {0x23fe, 0xc056}, + {0x3232, 0xfc0c}, + {0x3236, 0xfc22}, + {0x3248, 0xfca8}, + {0x326a, 0x8302}, + {0x326c, 0x830a}, + {0x326e, 0x0000}, + {0x32ca, 0xfc28}, + {0x32cc, 0xc3bc}, + {0x32ce, 0xc34c}, + {0x32d0, 0xc35a}, + {0x32d2, 0xc368}, + {0x32d4, 0xc376}, + {0x32d6, 0xc3c2}, + {0x32d8, 0xc3e6}, + {0x32da, 0x0003}, + {0x32dc, 0x0003}, + {0x32de, 0x00c7}, + {0x32e0, 0x0031}, + {0x32e2, 0x0031}, + {0x32e4, 0x0031}, + {0x32e6, 0xfc28}, + {0x32e8, 0xc3bc}, + {0x32ea, 0xc384}, + {0x32ec, 0xc392}, + {0x32ee, 0xc3a0}, + {0x32f0, 0xc3ae}, + {0x32f2, 0xc3c4}, + {0x32f4, 0xc3e6}, + {0x32f6, 0x0003}, + {0x32f8, 0x0003}, + {0x32fa, 0x00c7}, + {0x32fc, 0x0031}, + {0x32fe, 0x0031}, + {0x3300, 0x0031}, + {0x3302, 0x82ca}, + {0x3304, 0xc164}, + {0x3306, 0x82e6}, + {0x3308, 0xc19c}, + {0x330a, 0x001f}, + {0x330c, 0x001a}, + {0x330e, 0x0034}, + {0x3310, 0x0000}, + {0x3312, 0x0000}, + {0x3314, 0xfc94}, + {0x3316, 0xc3d8}, + /* regs above are unknown */ + {HI846_REG_MODE_SELECT, 0x0000}, + {HI846_REG_UNKNOWN_0E04, 0x0012}, + {HI846_REG_Y_ODD_INC_FOBP, 0x1111}, + {HI846_REG_Y_ODD_INC_VACT, 0x1111}, + {HI846_REG_UNKNOWN_0022, 0x0008}, + {HI846_REG_Y_ADDR_START_VACT_H, 0x0040}, + {HI846_REG_UNKNOWN_0028, 0x0017}, + {HI846_REG_Y_ADDR_END_VACT_H, 0x09cf}, + {HI846_REG_UNKNOWN_005C, 0x2101}, + {HI846_REG_FLL, 0x09de}, + {HI846_REG_LLP, 0x0ed8}, + {HI846_REG_IMAGE_ORIENTATION, 0x0100}, + {HI846_REG_BINNING_MODE, 0x0022}, + {HI846_REG_HBIN_MODE, 0x0000}, + {HI846_REG_UNKNOWN_0A24, 0x0000}, + {HI846_REG_X_START_H, 0x0000}, + {HI846_REG_X_OUTPUT_SIZE_H, 0x0cc0}, + {HI846_REG_Y_OUTPUT_SIZE_H, 0x0990}, + {HI846_REG_EXPOSURE, 0x09d8}, + {HI846_REG_ANALOG_GAIN, 0x0000}, + {HI846_REG_GROUPED_PARA_HOLD, 0x0000}, + {HI846_REG_UNKNOWN_051E, 0x0000}, + {HI846_REG_UNKNOWN_0200, 0x0400}, + {HI846_REG_PEDESTAL_EN, 0x0c00}, + {HI846_REG_UNKNOWN_0A0C, 0x0010}, + {HI846_REG_UNKNOWN_0A1E, 0x0ccf}, + {HI846_REG_UNKNOWN_0402, 0x0110}, + {HI846_REG_UNKNOWN_0404, 0x00f4}, + {HI846_REG_UNKNOWN_0408, 0x0000}, + {HI846_REG_UNKNOWN_0410, 0x008d}, + {HI846_REG_UNKNOWN_0412, 0x011a}, + {HI846_REG_UNKNOWN_0414, 0x864c}, + {HI846_REG_UNKNOWN_021C, 0x0003}, + {HI846_REG_UNKNOWN_021E, 0x0235}, + {HI846_REG_BLC_CTL0, 0x9150}, + {HI846_REG_UNKNOWN_0C06, 0x0021}, + {HI846_REG_UNKNOWN_0C10, 0x0040}, + {HI846_REG_UNKNOWN_0C12, 0x0040}, + {HI846_REG_UNKNOWN_0C14, 0x0040}, + {HI846_REG_UNKNOWN_0C16, 0x0040}, + {HI846_REG_FAST_STANDBY_MODE, 0x0100}, + {HI846_REG_ISP_EN_H, 0x014a}, + {HI846_REG_UNKNOWN_0418, 0x0000}, + {HI846_REG_UNKNOWN_012A, 0x03b4}, + {HI846_REG_X_ADDR_START_HACT_H, 0x0046}, + {HI846_REG_X_ADDR_END_HACT_H, 0x0376}, + {HI846_REG_UNKNOWN_0B02, 0xe04d}, + {HI846_REG_UNKNOWN_0B10, 0x6821}, + {HI846_REG_UNKNOWN_0B12, 0x0120}, + {HI846_REG_UNKNOWN_0B14, 0x0001}, + {HI846_REG_UNKNOWN_2008, 0x38fd}, + {HI846_REG_UNKNOWN_326E, 0x0000}, + {HI846_REG_UNKNOWN_0900, 0x0320}, + {HI846_REG_MIPI_TX_OP_MODE, 0xc31a}, + {HI846_REG_UNKNOWN_0914, 0xc109}, + {HI846_REG_TCLK_PREPARE, 0x061a}, + {HI846_REG_UNKNOWN_0918, 0x0306}, + {HI846_REG_THS_ZERO, 0x0b09}, + {HI846_REG_TCLK_POST, 0x0c07}, + {HI846_REG_UNKNOWN_091E, 0x0a00}, + {HI846_REG_UNKNOWN_090C, 0x042a}, + {HI846_REG_UNKNOWN_090E, 0x006b}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca00}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_UNKNOWN_0F08, 0x2f04}, + {HI846_REG_UNKNOWN_0F30, 0x001f}, + {HI846_REG_UNKNOWN_0F36, 0x001f}, + {HI846_REG_UNKNOWN_0F04, 0x3a00}, + {HI846_REG_PLL_CFG_RAMP1_H, 0x025a}, + {HI846_REG_PLL_CFG_MIPI1_H, 0x025a}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x0024}, + {HI846_REG_UNKNOWN_006A, 0x0100}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg hi846_init_4lane[] = { + {0x2000, 0x987a}, + {0x2002, 0x00ff}, + {0x2004, 0x0047}, + {0x2006, 0x3fff}, + {0x2008, 0x3fff}, + {0x200a, 0xc216}, + {0x200c, 0x1292}, + {0x200e, 0xc01a}, + {0x2010, 0x403d}, + {0x2012, 0x000e}, + {0x2014, 0x403e}, + {0x2016, 0x0b80}, + {0x2018, 0x403f}, + {0x201a, 0x82ae}, + {0x201c, 0x1292}, + {0x201e, 0xc00c}, + {0x2020, 0x4130}, + {0x2022, 0x43e2}, + {0x2024, 0x0180}, + {0x2026, 0x4130}, + {0x2028, 0x7400}, + {0x202a, 0x5000}, + {0x202c, 0x0253}, + {0x202e, 0x0ad1}, + {0x2030, 0x2360}, + {0x2032, 0x0009}, + {0x2034, 0x5020}, + {0x2036, 0x000b}, + {0x2038, 0x0002}, + {0x203a, 0x0044}, + {0x203c, 0x0016}, + {0x203e, 0x1792}, + {0x2040, 0x7002}, + {0x2042, 0x154f}, + {0x2044, 0x00d5}, + {0x2046, 0x000b}, + {0x2048, 0x0019}, + {0x204a, 0x1698}, + {0x204c, 0x000e}, + {0x204e, 0x099a}, + {0x2050, 0x0058}, + {0x2052, 0x7000}, + {0x2054, 0x1799}, + {0x2056, 0x0310}, + {0x2058, 0x03c3}, + {0x205a, 0x004c}, + {0x205c, 0x064a}, + {0x205e, 0x0001}, + {0x2060, 0x0007}, + {0x2062, 0x0bc7}, + {0x2064, 0x0055}, + {0x2066, 0x7000}, + {0x2068, 0x1550}, + {0x206a, 0x158a}, + {0x206c, 0x0004}, + {0x206e, 0x1488}, + {0x2070, 0x7010}, + {0x2072, 0x1508}, + {0x2074, 0x0004}, + {0x2076, 0x0016}, + {0x2078, 0x03d5}, + {0x207a, 0x0055}, + {0x207c, 0x08ca}, + {0x207e, 0x2019}, + {0x2080, 0x0007}, + {0x2082, 0x7057}, + {0x2084, 0x0fc7}, + {0x2086, 0x5041}, + {0x2088, 0x12c8}, + {0x208a, 0x5060}, + {0x208c, 0x5080}, + {0x208e, 0x2084}, + {0x2090, 0x12c8}, + {0x2092, 0x7800}, + {0x2094, 0x0802}, + {0x2096, 0x040f}, + {0x2098, 0x1007}, + {0x209a, 0x0803}, + {0x209c, 0x080b}, + {0x209e, 0x3803}, + {0x20a0, 0x0807}, + {0x20a2, 0x0404}, + {0x20a4, 0x0400}, + {0x20a6, 0xffff}, + {0x20a8, 0xf0b2}, + {0x20aa, 0xffef}, + {0x20ac, 0x0a84}, + {0x20ae, 0x1292}, + {0x20b0, 0xc02e}, + {0x20b2, 0x4130}, + {0x20b4, 0xf0b2}, + {0x20b6, 0xffbf}, + {0x20b8, 0x2004}, + {0x20ba, 0x403f}, + {0x20bc, 0x00c3}, + {0x20be, 0x4fe2}, + {0x20c0, 0x8318}, + {0x20c2, 0x43cf}, + {0x20c4, 0x0000}, + {0x20c6, 0x9382}, + {0x20c8, 0xc314}, + {0x20ca, 0x2003}, + {0x20cc, 0x12b0}, + {0x20ce, 0xcab0}, + {0x20d0, 0x4130}, + {0x20d2, 0x12b0}, + {0x20d4, 0xc90a}, + {0x20d6, 0x4130}, + {0x20d8, 0x42d2}, + {0x20da, 0x8318}, + {0x20dc, 0x00c3}, + {0x20de, 0x9382}, + {0x20e0, 0xc314}, + {0x20e2, 0x2009}, + {0x20e4, 0x120b}, + {0x20e6, 0x120a}, + {0x20e8, 0x1209}, + {0x20ea, 0x1208}, + {0x20ec, 0x1207}, + {0x20ee, 0x1206}, + {0x20f0, 0x4030}, + {0x20f2, 0xc15e}, + {0x20f4, 0x4130}, + {0x20f6, 0x1292}, + {0x20f8, 0xc008}, + {0x20fa, 0x4130}, + {0x20fc, 0x42d2}, + {0x20fe, 0x82a1}, + {0x2100, 0x00c2}, + {0x2102, 0x1292}, + {0x2104, 0xc040}, + {0x2106, 0x4130}, + {0x2108, 0x1292}, + {0x210a, 0xc006}, + {0x210c, 0x42a2}, + {0x210e, 0x7324}, + {0x2110, 0x9382}, + {0x2112, 0xc314}, + {0x2114, 0x2011}, + {0x2116, 0x425f}, + {0x2118, 0x82a1}, + {0x211a, 0xf25f}, + {0x211c, 0x00c1}, + {0x211e, 0xf35f}, + {0x2120, 0x2406}, + {0x2122, 0x425f}, + {0x2124, 0x00c0}, + {0x2126, 0xf37f}, + {0x2128, 0x522f}, + {0x212a, 0x4f82}, + {0x212c, 0x7324}, + {0x212e, 0x425f}, + {0x2130, 0x82d4}, + {0x2132, 0xf35f}, + {0x2134, 0x4fc2}, + {0x2136, 0x01b3}, + {0x2138, 0x93c2}, + {0x213a, 0x829f}, + {0x213c, 0x2421}, + {0x213e, 0x403e}, + {0x2140, 0xfffe}, + {0x2142, 0x40b2}, + {0x2144, 0xec78}, + {0x2146, 0x831c}, + {0x2148, 0x40b2}, + {0x214a, 0xec78}, + {0x214c, 0x831e}, + {0x214e, 0x40b2}, + {0x2150, 0xec78}, + {0x2152, 0x8320}, + {0x2154, 0xb3d2}, + {0x2156, 0x008c}, + {0x2158, 0x2405}, + {0x215a, 0x4e0f}, + {0x215c, 0x503f}, + {0x215e, 0xffd8}, + {0x2160, 0x4f82}, + {0x2162, 0x831c}, + {0x2164, 0x90f2}, + {0x2166, 0x0003}, + {0x2168, 0x008c}, + {0x216a, 0x2401}, + {0x216c, 0x4130}, + {0x216e, 0x421f}, + {0x2170, 0x831c}, + {0x2172, 0x5e0f}, + {0x2174, 0x4f82}, + {0x2176, 0x831e}, + {0x2178, 0x5e0f}, + {0x217a, 0x4f82}, + {0x217c, 0x8320}, + {0x217e, 0x3ff6}, + {0x2180, 0x432e}, + {0x2182, 0x3fdf}, + {0x2184, 0x421f}, + {0x2186, 0x7100}, + {0x2188, 0x4f0e}, + {0x218a, 0x503e}, + {0x218c, 0xffd8}, + {0x218e, 0x4e82}, + {0x2190, 0x7a04}, + {0x2192, 0x421e}, + {0x2194, 0x831c}, + {0x2196, 0x5f0e}, + {0x2198, 0x4e82}, + {0x219a, 0x7a06}, + {0x219c, 0x0b00}, + {0x219e, 0x7304}, + {0x21a0, 0x0050}, + {0x21a2, 0x40b2}, + {0x21a4, 0xd081}, + {0x21a6, 0x0b88}, + {0x21a8, 0x421e}, + {0x21aa, 0x831e}, + {0x21ac, 0x5f0e}, + {0x21ae, 0x4e82}, + {0x21b0, 0x7a0e}, + {0x21b2, 0x521f}, + {0x21b4, 0x8320}, + {0x21b6, 0x4f82}, + {0x21b8, 0x7a10}, + {0x21ba, 0x0b00}, + {0x21bc, 0x7304}, + {0x21be, 0x007a}, + {0x21c0, 0x40b2}, + {0x21c2, 0x0081}, + {0x21c4, 0x0b88}, + {0x21c6, 0x4392}, + {0x21c8, 0x7a0a}, + {0x21ca, 0x0800}, + {0x21cc, 0x7a0c}, + {0x21ce, 0x0b00}, + {0x21d0, 0x7304}, + {0x21d2, 0x022b}, + {0x21d4, 0x40b2}, + {0x21d6, 0xd081}, + {0x21d8, 0x0b88}, + {0x21da, 0x0b00}, + {0x21dc, 0x7304}, + {0x21de, 0x0255}, + {0x21e0, 0x40b2}, + {0x21e2, 0x0081}, + {0x21e4, 0x0b88}, + {0x21e6, 0x4130}, + {0x23fe, 0xc056}, + {0x3232, 0xfc0c}, + {0x3236, 0xfc22}, + {0x3238, 0xfcfc}, + {0x323a, 0xfd84}, + {0x323c, 0xfd08}, + {0x3246, 0xfcd8}, + {0x3248, 0xfca8}, + {0x324e, 0xfcb4}, + {0x326a, 0x8302}, + {0x326c, 0x830a}, + {0x326e, 0x0000}, + {0x32ca, 0xfc28}, + {0x32cc, 0xc3bc}, + {0x32ce, 0xc34c}, + {0x32d0, 0xc35a}, + {0x32d2, 0xc368}, + {0x32d4, 0xc376}, + {0x32d6, 0xc3c2}, + {0x32d8, 0xc3e6}, + {0x32da, 0x0003}, + {0x32dc, 0x0003}, + {0x32de, 0x00c7}, + {0x32e0, 0x0031}, + {0x32e2, 0x0031}, + {0x32e4, 0x0031}, + {0x32e6, 0xfc28}, + {0x32e8, 0xc3bc}, + {0x32ea, 0xc384}, + {0x32ec, 0xc392}, + {0x32ee, 0xc3a0}, + {0x32f0, 0xc3ae}, + {0x32f2, 0xc3c4}, + {0x32f4, 0xc3e6}, + {0x32f6, 0x0003}, + {0x32f8, 0x0003}, + {0x32fa, 0x00c7}, + {0x32fc, 0x0031}, + {0x32fe, 0x0031}, + {0x3300, 0x0031}, + {0x3302, 0x82ca}, + {0x3304, 0xc164}, + {0x3306, 0x82e6}, + {0x3308, 0xc19c}, + {0x330a, 0x001f}, + {0x330c, 0x001a}, + {0x330e, 0x0034}, + {0x3310, 0x0000}, + {0x3312, 0x0000}, + {0x3314, 0xfc94}, + {0x3316, 0xc3d8}, + + {0x0a00, 0x0000}, + {0x0e04, 0x0012}, + {0x002e, 0x1111}, + {0x0032, 0x1111}, + {0x0022, 0x0008}, + {0x0026, 0x0040}, + {0x0028, 0x0017}, + {0x002c, 0x09cf}, + {0x005c, 0x2101}, + {0x0006, 0x09de}, + {0x0008, 0x0ed8}, + {0x000e, 0x0100}, + {0x000c, 0x0022}, + {0x0a22, 0x0000}, + {0x0a24, 0x0000}, + {0x0804, 0x0000}, + {0x0a12, 0x0cc0}, + {0x0a14, 0x0990}, + {0x0074, 0x09d8}, + {0x0076, 0x0000}, + {0x051e, 0x0000}, + {0x0200, 0x0400}, + {0x0a1a, 0x0c00}, + {0x0a0c, 0x0010}, + {0x0a1e, 0x0ccf}, + {0x0402, 0x0110}, + {0x0404, 0x00f4}, + {0x0408, 0x0000}, + {0x0410, 0x008d}, + {0x0412, 0x011a}, + {0x0414, 0x864c}, + /* for OTP */ + {0x021c, 0x0003}, + {0x021e, 0x0235}, + /* for OTP */ + {0x0c00, 0x9950}, + {0x0c06, 0x0021}, + {0x0c10, 0x0040}, + {0x0c12, 0x0040}, + {0x0c14, 0x0040}, + {0x0c16, 0x0040}, + {0x0a02, 0x0100}, + {0x0a04, 0x015a}, + {0x0418, 0x0000}, + {0x0128, 0x0028}, + {0x012a, 0xffff}, + {0x0120, 0x0046}, + {0x0122, 0x0376}, + {0x012c, 0x0020}, + {0x012e, 0xffff}, + {0x0124, 0x0040}, + {0x0126, 0x0378}, + {0x0746, 0x0050}, + {0x0748, 0x01d5}, + {0x074a, 0x022b}, + {0x074c, 0x03b0}, + {0x0756, 0x043f}, + {0x0758, 0x3f1d}, + {0x0b02, 0xe04d}, + {0x0b10, 0x6821}, + {0x0b12, 0x0120}, + {0x0b14, 0x0001}, + {0x2008, 0x38fd}, + {0x326e, 0x0000}, + {0x0900, 0x0300}, + {0x0902, 0xc319}, + {0x0914, 0xc109}, + {0x0916, 0x061a}, + {0x0918, 0x0407}, + {0x091a, 0x0a0b}, + {0x091c, 0x0e08}, + {0x091e, 0x0a00}, + {0x090c, 0x0427}, + {0x090e, 0x0059}, + {0x0954, 0x0089}, + {0x0956, 0x0000}, + {0x0958, 0xca80}, + {0x095a, 0x9240}, + {0x0f08, 0x2f04}, + {0x0f30, 0x001f}, + {0x0f36, 0x001f}, + {0x0f04, 0x3a00}, + {0x0f32, 0x025a}, + {0x0f38, 0x025a}, + {0x0f2a, 0x4124}, + {0x006a, 0x0100}, + {0x004c, 0x0100}, + {0x0044, 0x0001}, +}; + +static const struct hi846_reg mode_640x480_config[] = { + {HI846_REG_MODE_SELECT, 0x0000}, + {HI846_REG_Y_ODD_INC_FOBP, 0x7711}, + {HI846_REG_Y_ODD_INC_VACT, 0x7711}, + {HI846_REG_Y_ADDR_START_VACT_H, 0x0148}, + {HI846_REG_Y_ADDR_END_VACT_H, 0x08c7}, + {HI846_REG_UNKNOWN_005C, 0x4404}, + {HI846_REG_FLL, 0x0277}, + {HI846_REG_LLP, 0x0ed8}, + {HI846_REG_BINNING_MODE, 0x0322}, + {HI846_REG_HBIN_MODE, 0x0200}, + {HI846_REG_UNKNOWN_0A24, 0x0000}, + {HI846_REG_X_START_H, 0x0058}, + {HI846_REG_X_OUTPUT_SIZE_H, 0x0280}, + {HI846_REG_Y_OUTPUT_SIZE_H, 0x01e0}, + + /* For OTP */ + {HI846_REG_UNKNOWN_021C, 0x0003}, + {HI846_REG_UNKNOWN_021E, 0x0235}, + + {HI846_REG_ISP_EN_H, 0x016a}, + {HI846_REG_UNKNOWN_0418, 0x0210}, + {HI846_REG_UNKNOWN_0B02, 0xe04d}, + {HI846_REG_UNKNOWN_0B10, 0x7021}, + {HI846_REG_UNKNOWN_0B12, 0x0120}, + {HI846_REG_UNKNOWN_0B14, 0x0001}, + {HI846_REG_UNKNOWN_2008, 0x38fd}, + {HI846_REG_UNKNOWN_326E, 0x0000}, +}; + +static const struct hi846_reg mode_640x480_mipi_2lane[] = { + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0x4319}, + {HI846_REG_UNKNOWN_0914, 0xc105}, + {HI846_REG_TCLK_PREPARE, 0x030c}, + {HI846_REG_UNKNOWN_0918, 0x0304}, + {HI846_REG_THS_ZERO, 0x0708}, + {HI846_REG_TCLK_POST, 0x0b04}, + {HI846_REG_UNKNOWN_091E, 0x0500}, + {HI846_REG_UNKNOWN_090C, 0x0208}, + {HI846_REG_UNKNOWN_090E, 0x009a}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4924}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg mode_1280x720_config[] = { + {HI846_REG_MODE_SELECT, 0x0000}, + {HI846_REG_Y_ODD_INC_FOBP, 0x3311}, + {HI846_REG_Y_ODD_INC_VACT, 0x3311}, + {HI846_REG_Y_ADDR_START_VACT_H, 0x0238}, + {HI846_REG_Y_ADDR_END_VACT_H, 0x07d7}, + {HI846_REG_UNKNOWN_005C, 0x4202}, + {HI846_REG_FLL, 0x034a}, + {HI846_REG_LLP, 0x0ed8}, + {HI846_REG_BINNING_MODE, 0x0122}, + {HI846_REG_HBIN_MODE, 0x0100}, + {HI846_REG_UNKNOWN_0A24, 0x0000}, + {HI846_REG_X_START_H, 0x00b0}, + {HI846_REG_X_OUTPUT_SIZE_H, 0x0500}, + {HI846_REG_Y_OUTPUT_SIZE_H, 0x02d0}, + {HI846_REG_EXPOSURE, 0x0344}, + + /* For OTP */ + {HI846_REG_UNKNOWN_021C, 0x0003}, + {HI846_REG_UNKNOWN_021E, 0x0235}, + + {HI846_REG_ISP_EN_H, 0x016a}, + {HI846_REG_UNKNOWN_0418, 0x0410}, + {HI846_REG_UNKNOWN_0B02, 0xe04d}, + {HI846_REG_UNKNOWN_0B10, 0x6c21}, + {HI846_REG_UNKNOWN_0B12, 0x0120}, + {HI846_REG_UNKNOWN_0B14, 0x0005}, + {HI846_REG_UNKNOWN_2008, 0x38fd}, + {HI846_REG_UNKNOWN_326E, 0x0000}, +}; + +static const struct hi846_reg mode_1280x720_mipi_2lane[] = { + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0x4319}, + {HI846_REG_UNKNOWN_0914, 0xc109}, + {HI846_REG_TCLK_PREPARE, 0x061a}, + {HI846_REG_UNKNOWN_0918, 0x0407}, + {HI846_REG_THS_ZERO, 0x0a0b}, + {HI846_REG_TCLK_POST, 0x0e08}, + {HI846_REG_UNKNOWN_091E, 0x0a00}, + {HI846_REG_UNKNOWN_090C, 0x0427}, + {HI846_REG_UNKNOWN_090E, 0x0145}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4124}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg mode_1280x720_mipi_4lane[] = { + /* 360Mbps */ + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0xc319}, + {HI846_REG_UNKNOWN_0914, 0xc105}, + {HI846_REG_TCLK_PREPARE, 0x030c}, + {HI846_REG_UNKNOWN_0918, 0x0304}, + {HI846_REG_THS_ZERO, 0x0708}, + {HI846_REG_TCLK_POST, 0x0b04}, + {HI846_REG_UNKNOWN_091E, 0x0500}, + {HI846_REG_UNKNOWN_090C, 0x0208}, + {HI846_REG_UNKNOWN_090E, 0x008a}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4924}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg mode_1632x1224_config[] = { + {HI846_REG_MODE_SELECT, 0x0000}, + {HI846_REG_Y_ODD_INC_FOBP, 0x3311}, + {HI846_REG_Y_ODD_INC_VACT, 0x3311}, + {HI846_REG_Y_ADDR_START_VACT_H, 0x0040}, + {HI846_REG_Y_ADDR_END_VACT_H, 0x09cf}, + {HI846_REG_UNKNOWN_005C, 0x4202}, + {HI846_REG_FLL, 0x09de}, + {HI846_REG_LLP, 0x0ed8}, + {HI846_REG_BINNING_MODE, 0x0122}, + {HI846_REG_HBIN_MODE, 0x0100}, + {HI846_REG_UNKNOWN_0A24, 0x0000}, + {HI846_REG_X_START_H, 0x0000}, + {HI846_REG_X_OUTPUT_SIZE_H, 0x0660}, + {HI846_REG_Y_OUTPUT_SIZE_H, 0x04c8}, + {HI846_REG_EXPOSURE, 0x09d8}, + + /* For OTP */ + {HI846_REG_UNKNOWN_021C, 0x0003}, + {HI846_REG_UNKNOWN_021E, 0x0235}, + + {HI846_REG_ISP_EN_H, 0x016a}, + {HI846_REG_UNKNOWN_0418, 0x0000}, + {HI846_REG_UNKNOWN_0B02, 0xe04d}, + {HI846_REG_UNKNOWN_0B10, 0x6c21}, + {HI846_REG_UNKNOWN_0B12, 0x0120}, + {HI846_REG_UNKNOWN_0B14, 0x0005}, + {HI846_REG_UNKNOWN_2008, 0x38fd}, + {HI846_REG_UNKNOWN_326E, 0x0000}, +}; + +static const struct hi846_reg mode_1632x1224_mipi_2lane[] = { + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0x4319}, + {HI846_REG_UNKNOWN_0914, 0xc109}, + {HI846_REG_TCLK_PREPARE, 0x061a}, + {HI846_REG_UNKNOWN_0918, 0x0407}, + {HI846_REG_THS_ZERO, 0x0a0b}, + {HI846_REG_TCLK_POST, 0x0e08}, + {HI846_REG_UNKNOWN_091E, 0x0a00}, + {HI846_REG_UNKNOWN_090C, 0x0427}, + {HI846_REG_UNKNOWN_090E, 0x0069}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4124}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const struct hi846_reg mode_1632x1224_mipi_4lane[] = { + {HI846_REG_UNKNOWN_0900, 0x0300}, + {HI846_REG_MIPI_TX_OP_MODE, 0xc319}, + {HI846_REG_UNKNOWN_0914, 0xc105}, + {HI846_REG_TCLK_PREPARE, 0x030c}, + {HI846_REG_UNKNOWN_0918, 0x0304}, + {HI846_REG_THS_ZERO, 0x0708}, + {HI846_REG_TCLK_POST, 0x0b04}, + {HI846_REG_UNKNOWN_091E, 0x0500}, + {HI846_REG_UNKNOWN_090C, 0x0208}, + {HI846_REG_UNKNOWN_090E, 0x001c}, + {HI846_REG_UNKNOWN_0954, 0x0089}, + {HI846_REG_UNKNOWN_0956, 0x0000}, + {HI846_REG_UNKNOWN_0958, 0xca80}, + {HI846_REG_UNKNOWN_095A, 0x9240}, + {HI846_REG_PLL_CFG_MIPI2_H, 0x4924}, + {HI846_REG_TG_ENABLE, 0x0100}, +}; + +static const char * const hi846_test_pattern_menu[] = { + "Disabled", + "Solid Colour", + "100% Colour Bars", + "Fade To Grey Colour Bars", + "PN9", + "Gradient Horizontal", + "Gradient Vertical", + "Check Board", + "Slant Pattern", + "Resolution Pattern", +}; + +#define FREQ_INDEX_640 0 +#define FREQ_INDEX_1280 1 +static const s64 hi846_link_freqs[] = { + [FREQ_INDEX_640] = 80000000, + [FREQ_INDEX_1280] = 200000000, +}; + +static const struct hi846_reg_list hi846_init_regs_list_2lane = { + .num_of_regs = ARRAY_SIZE(hi846_init_2lane), + .regs = hi846_init_2lane, +}; + +static const struct hi846_reg_list hi846_init_regs_list_4lane = { + .num_of_regs = ARRAY_SIZE(hi846_init_4lane), + .regs = hi846_init_4lane, +}; + +static const struct hi846_mode supported_modes[] = { + { + .width = 640, + .height = 480, + .link_freq_index = FREQ_INDEX_640, + .fps = 120, + .frame_len = 631, + .llp = HI846_LINE_LENGTH, + .reg_list_config = { + .num_of_regs = ARRAY_SIZE(mode_640x480_config), + .regs = mode_640x480_config, + }, + .reg_list_2lane = { + .num_of_regs = ARRAY_SIZE(mode_640x480_mipi_2lane), + .regs = mode_640x480_mipi_2lane, + }, + .reg_list_4lane = { + .num_of_regs = 0, + }, + .crop = { + .left = 0x58, + .top = 0x148, + .width = 640 * 4, + .height = 480 * 4, + }, + }, + { + .width = 1280, + .height = 720, + .link_freq_index = FREQ_INDEX_1280, + .fps = 90, + .frame_len = 842, + .llp = HI846_LINE_LENGTH, + .reg_list_config = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_config), + .regs = mode_1280x720_config, + }, + .reg_list_2lane = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_mipi_2lane), + .regs = mode_1280x720_mipi_2lane, + }, + .reg_list_4lane = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_mipi_4lane), + .regs = mode_1280x720_mipi_4lane, + }, + .crop = { + .left = 0xb0, + .top = 0x238, + .width = 1280 * 2, + .height = 720 * 2, + }, + }, + { + .width = 1632, + .height = 1224, + .link_freq_index = FREQ_INDEX_1280, + .fps = 30, + .frame_len = 2526, + .llp = HI846_LINE_LENGTH, + .reg_list_config = { + .num_of_regs = ARRAY_SIZE(mode_1632x1224_config), + .regs = mode_1632x1224_config, + }, + .reg_list_2lane = { + .num_of_regs = ARRAY_SIZE(mode_1632x1224_mipi_2lane), + .regs = mode_1632x1224_mipi_2lane, + }, + .reg_list_4lane = { + .num_of_regs = ARRAY_SIZE(mode_1632x1224_mipi_4lane), + .regs = mode_1632x1224_mipi_4lane, + }, + .crop = { + .left = 0x0, + .top = 0x0, + .width = 1632 * 2, + .height = 1224 * 2, + }, + } +}; + +struct hi846_datafmt { + u32 code; + enum v4l2_colorspace colorspace; +}; + +static const char * const hi846_supply_names[] = { + "vddio", /* Digital I/O (1.8V or 2.8V) */ + "vdda", /* Analog (2.8V) */ + "vddd", /* Digital Core (1.2V) */ +}; + +#define HI846_NUM_SUPPLIES ARRAY_SIZE(hi846_supply_names) + +struct hi846 { + struct gpio_desc *rst_gpio; + struct gpio_desc *shutdown_gpio; + struct regulator_bulk_data supplies[HI846_NUM_SUPPLIES]; + struct clk *clock; + const struct hi846_datafmt *fmt; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + u8 nr_lanes; + + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + struct mutex mutex; /* protect cur_mode, streaming and chip access */ + const struct hi846_mode *cur_mode; + bool streaming; +}; + +static inline struct hi846 *to_hi846(struct v4l2_subdev *sd) +{ + return container_of(sd, struct hi846, sd); +} + +static const struct hi846_datafmt hi846_colour_fmts[] = { + { HI846_MEDIA_BUS_FORMAT, V4L2_COLORSPACE_RAW }, +}; + +static const struct hi846_datafmt *hi846_find_datafmt(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(hi846_colour_fmts); i++) + if (hi846_colour_fmts[i].code == code) + return &hi846_colour_fmts[i]; + + return NULL; +} + +static inline u8 hi846_get_link_freq_index(struct hi846 *hi846) +{ + return hi846->cur_mode->link_freq_index; +} + +static u64 hi846_get_link_freq(struct hi846 *hi846) +{ + u8 index = hi846_get_link_freq_index(hi846); + + return hi846_link_freqs[index]; +} + +static u64 hi846_calc_pixel_rate(struct hi846 *hi846) +{ + u64 link_freq = hi846_get_link_freq(hi846); + u64 pixel_rate = link_freq * 2 * hi846->nr_lanes; + + do_div(pixel_rate, HI846_RGB_DEPTH); + + return pixel_rate; +} + +static int hi846_read_reg(struct hi846 *hi846, u16 reg, u8 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[1] = {0}; + int ret; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 1; + msgs[1].buf = data_buf; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + dev_err(&client->dev, "i2c read error: %d\n", ret); + return -EIO; + } + + *val = data_buf[0]; + + return 0; +} + +static int hi846_write_reg(struct hi846 *hi846, u16 reg, u8 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + u8 buf[3] = { reg >> 8, reg & 0xff, val }; + struct i2c_msg msg[] = { + { .addr = client->addr, .flags = 0, + .len = ARRAY_SIZE(buf), .buf = buf }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(&client->dev, "i2c write error\n"); + return -EIO; + } + + return 0; +} + +static void hi846_write_reg_16(struct hi846 *hi846, u16 reg, u16 val, int *err) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + u8 buf[4]; + int ret; + + if (*err < 0) + return; + + put_unaligned_be16(reg, buf); + put_unaligned_be16(val, buf + 2); + ret = i2c_master_send(client, buf, sizeof(buf)); + if (ret != sizeof(buf)) { + dev_err(&client->dev, "i2c_master_send != %zu: %d\n", + sizeof(buf), ret); + *err = -EIO; + } +} + +static int hi846_write_reg_list(struct hi846 *hi846, + const struct hi846_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + unsigned int i; + int ret = 0; + + for (i = 0; i < r_list->num_of_regs; i++) { + hi846_write_reg_16(hi846, r_list->regs[i].address, + r_list->regs[i].val, &ret); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x: %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int hi846_update_digital_gain(struct hi846 *hi846, u16 d_gain) +{ + int ret = 0; + + hi846_write_reg_16(hi846, HI846_REG_MWB_GR_GAIN_H, d_gain, &ret); + hi846_write_reg_16(hi846, HI846_REG_MWB_GB_GAIN_H, d_gain, &ret); + hi846_write_reg_16(hi846, HI846_REG_MWB_R_GAIN_H, d_gain, &ret); + hi846_write_reg_16(hi846, HI846_REG_MWB_B_GAIN_H, d_gain, &ret); + + return ret; +} + +static int hi846_test_pattern(struct hi846 *hi846, u32 pattern) +{ + int ret; + u8 val; + + if (pattern) { + ret = hi846_read_reg(hi846, HI846_REG_ISP, &val); + if (ret) + return ret; + + ret = hi846_write_reg(hi846, HI846_REG_ISP, + val | HI846_REG_ISP_TPG_EN); + if (ret) + return ret; + } + + return hi846_write_reg(hi846, HI846_REG_TEST_PATTERN, pattern); +} + +static int hi846_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hi846 *hi846 = container_of(ctrl->handler, + struct hi846, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + s64 exposure_max; + int ret = 0; + u32 shutter, frame_len; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = hi846->cur_mode->height + ctrl->val - + HI846_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(hi846->exposure, + hi846->exposure->minimum, + exposure_max, hi846->exposure->step, + exposure_max); + } + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = hi846_write_reg(hi846, HI846_REG_ANALOG_GAIN, ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = hi846_update_digital_gain(hi846, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + shutter = ctrl->val; + frame_len = hi846->cur_mode->frame_len; + + if (shutter > frame_len - 6) { /* margin */ + frame_len = shutter + 6; + if (frame_len > 0xffff) { /* max frame len */ + frame_len = 0xffff; + } + } + + if (shutter < 6) + shutter = 6; + if (shutter > (0xffff - 6)) + shutter = 0xffff - 6; + + hi846_write_reg_16(hi846, HI846_REG_FLL, frame_len, &ret); + hi846_write_reg_16(hi846, HI846_REG_EXPOSURE, shutter, &ret); + break; + + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ + hi846_write_reg_16(hi846, HI846_REG_FLL, + hi846->cur_mode->height + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN: + ret = hi846_test_pattern(hi846, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops hi846_ctrl_ops = { + .s_ctrl = hi846_set_ctrl, +}; + +static int hi846_init_controls(struct hi846 *hi846) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + struct v4l2_fwnode_device_properties props; + + ctrl_hdlr = &hi846->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + ctrl_hdlr->lock = &hi846->mutex; + + hi846->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(hi846_link_freqs) - 1, + 0, hi846_link_freqs); + if (hi846->link_freq) + hi846->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hi846->pixel_rate = + v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + hi846_calc_pixel_rate(hi846), 1, + hi846_calc_pixel_rate(hi846)); + hi846->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_VBLANK, + hi846->cur_mode->frame_len - + hi846->cur_mode->height, + HI846_FLL_MAX - + hi846->cur_mode->height, 1, + hi846->cur_mode->frame_len - + hi846->cur_mode->height); + + h_blank = hi846->cur_mode->llp - hi846->cur_mode->width; + + hi846->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (hi846->hblank) + hi846->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + HI846_ANAL_GAIN_MIN, HI846_ANAL_GAIN_MAX, + HI846_ANAL_GAIN_STEP, HI846_ANAL_GAIN_MIN); + v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + HI846_DGTL_GAIN_MIN, HI846_DGTL_GAIN_MAX, + HI846_DGTL_GAIN_STEP, HI846_DGTL_GAIN_DEFAULT); + exposure_max = hi846->cur_mode->frame_len - HI846_EXPOSURE_MAX_MARGIN; + hi846->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_EXPOSURE, + HI846_EXPOSURE_MIN, exposure_max, + HI846_EXPOSURE_STEP, + exposure_max); + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &hi846_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hi846_test_pattern_menu) - 1, + 0, 0, hi846_test_pattern_menu); + if (ctrl_hdlr->error) { + dev_err(&client->dev, "v4l ctrl handler error: %d\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + return ret; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &hi846_ctrl_ops, + &props); + if (ret) + return ret; + + hi846->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static int hi846_set_video_mode(struct hi846 *hi846, int fps) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + u64 frame_length; + int ret = 0; + int dummy_lines; + u64 link_freq = hi846_get_link_freq(hi846); + + dev_dbg(&client->dev, "%s: link freq: %llu\n", __func__, + hi846_get_link_freq(hi846)); + + do_div(link_freq, fps); + frame_length = link_freq; + do_div(frame_length, HI846_LINE_LENGTH); + + dummy_lines = (frame_length > hi846->cur_mode->frame_len) ? + (frame_length - hi846->cur_mode->frame_len) : 0; + + frame_length = hi846->cur_mode->frame_len + dummy_lines; + + dev_dbg(&client->dev, "%s: frame length calculated: %llu\n", __func__, + frame_length); + + hi846_write_reg_16(hi846, HI846_REG_FLL, frame_length & 0xFFFF, &ret); + hi846_write_reg_16(hi846, HI846_REG_LLP, + HI846_LINE_LENGTH & 0xFFFF, &ret); + + return ret; +} + +static int hi846_start_streaming(struct hi846 *hi846) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + int ret = 0; + u8 val; + + if (hi846->nr_lanes == 2) + ret = hi846_write_reg_list(hi846, &hi846_init_regs_list_2lane); + else + ret = hi846_write_reg_list(hi846, &hi846_init_regs_list_4lane); + if (ret) { + dev_err(&client->dev, "failed to set plls: %d\n", ret); + return ret; + } + + ret = hi846_write_reg_list(hi846, &hi846->cur_mode->reg_list_config); + if (ret) { + dev_err(&client->dev, "failed to set mode: %d\n", ret); + return ret; + } + + if (hi846->nr_lanes == 2) + ret = hi846_write_reg_list(hi846, + &hi846->cur_mode->reg_list_2lane); + else + ret = hi846_write_reg_list(hi846, + &hi846->cur_mode->reg_list_4lane); + if (ret) { + dev_err(&client->dev, "failed to set mipi mode: %d\n", ret); + return ret; + } + + hi846_set_video_mode(hi846, hi846->cur_mode->fps); + + ret = __v4l2_ctrl_handler_setup(hi846->sd.ctrl_handler); + if (ret) + return ret; + + /* + * Reading 0x0034 is purely done for debugging reasons: It is not + * documented in the DS but only mentioned once: + * "If 0x0034[2] bit is disabled , Visible pixel width and height is 0." + * So even though that sounds like we won't see anything, we don't + * know more about this, so in that case only inform the user but do + * nothing more. + */ + ret = hi846_read_reg(hi846, 0x0034, &val); + if (ret) + return ret; + if (!(val & BIT(2))) + dev_info(&client->dev, "visible pixel width and height is 0\n"); + + ret = hi846_write_reg(hi846, HI846_REG_MODE_SELECT, + HI846_MODE_STREAMING); + if (ret) { + dev_err(&client->dev, "failed to start stream"); + return ret; + } + + hi846->streaming = 1; + + dev_dbg(&client->dev, "%s: started streaming successfully\n", __func__); + + return ret; +} + +static void hi846_stop_streaming(struct hi846 *hi846) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + + if (hi846_write_reg(hi846, HI846_REG_MODE_SELECT, HI846_MODE_STANDBY)) + dev_err(&client->dev, "failed to stop stream"); + + hi846->streaming = 0; +} + +static int hi846_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct hi846 *hi846 = to_hi846(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + if (hi846->streaming == enable) + return 0; + + mutex_lock(&hi846->mutex); + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto out; + } + + ret = hi846_start_streaming(hi846); + } + + if (!enable || ret) { + hi846_stop_streaming(hi846); + pm_runtime_put(&client->dev); + } + +out: + mutex_unlock(&hi846->mutex); + + return ret; +} + +static int hi846_power_on(struct hi846 *hi846) +{ + int ret; + + ret = regulator_bulk_enable(HI846_NUM_SUPPLIES, hi846->supplies); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(hi846->clock); + if (ret < 0) + goto err_reg; + + if (hi846->shutdown_gpio) + gpiod_set_value_cansleep(hi846->shutdown_gpio, 0); + + /* 30us = 2400 cycles at 80Mhz */ + usleep_range(30, 60); + if (hi846->rst_gpio) + gpiod_set_value_cansleep(hi846->rst_gpio, 0); + usleep_range(30, 60); + + return 0; + +err_reg: + regulator_bulk_disable(HI846_NUM_SUPPLIES, hi846->supplies); + + return ret; +} + +static void hi846_power_off(struct hi846 *hi846) +{ + if (hi846->rst_gpio) + gpiod_set_value_cansleep(hi846->rst_gpio, 1); + + if (hi846->shutdown_gpio) + gpiod_set_value_cansleep(hi846->shutdown_gpio, 1); + + clk_disable_unprepare(hi846->clock); + regulator_bulk_disable(HI846_NUM_SUPPLIES, hi846->supplies); +} + +static int __maybe_unused hi846_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi846 *hi846 = to_hi846(sd); + + if (hi846->streaming) + hi846_stop_streaming(hi846); + + hi846_power_off(hi846); + + return 0; +} + +static int __maybe_unused hi846_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi846 *hi846 = to_hi846(sd); + int ret; + + ret = hi846_power_on(hi846); + if (ret) + return ret; + + if (hi846->streaming) { + ret = hi846_start_streaming(hi846); + if (ret) { + dev_err(dev, "%s: start streaming failed: %d\n", + __func__, ret); + goto error; + } + } + + return 0; + +error: + hi846_power_off(hi846); + return ret; +} + +static int hi846_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct hi846 *hi846 = to_hi846(sd); + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + const struct hi846_datafmt *fmt = hi846_find_datafmt(mf->code); + u32 tgt_fps; + s32 vblank_def, h_blank; + + if (!fmt) { + mf->code = hi846_colour_fmts[0].code; + mf->colorspace = hi846_colour_fmts[0].colorspace; + fmt = &hi846_colour_fmts[0]; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, sd_state, format->pad) = *mf; + return 0; + } + + if (hi846->nr_lanes == 2) { + if (!hi846->cur_mode->reg_list_2lane.num_of_regs) { + dev_err(&client->dev, + "this mode is not supported for 2 lanes\n"); + return -EINVAL; + } + } else { + if (!hi846->cur_mode->reg_list_4lane.num_of_regs) { + dev_err(&client->dev, + "this mode is not supported for 4 lanes\n"); + return -EINVAL; + } + } + + mutex_lock(&hi846->mutex); + + if (hi846->streaming) { + mutex_unlock(&hi846->mutex); + return -EBUSY; + } + + hi846->fmt = fmt; + + hi846->cur_mode = + v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, mf->width, mf->height); + dev_dbg(&client->dev, "%s: found mode: %dx%d\n", __func__, + hi846->cur_mode->width, hi846->cur_mode->height); + + tgt_fps = hi846->cur_mode->fps; + dev_dbg(&client->dev, "%s: target fps: %d\n", __func__, tgt_fps); + + mf->width = hi846->cur_mode->width; + mf->height = hi846->cur_mode->height; + mf->code = HI846_MEDIA_BUS_FORMAT; + mf->field = V4L2_FIELD_NONE; + + __v4l2_ctrl_s_ctrl(hi846->link_freq, hi846_get_link_freq_index(hi846)); + __v4l2_ctrl_s_ctrl_int64(hi846->pixel_rate, + hi846_calc_pixel_rate(hi846)); + + /* Update limits and set FPS to default */ + vblank_def = hi846->cur_mode->frame_len - hi846->cur_mode->height; + __v4l2_ctrl_modify_range(hi846->vblank, + hi846->cur_mode->frame_len - + hi846->cur_mode->height, + HI846_FLL_MAX - hi846->cur_mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(hi846->vblank, vblank_def); + + h_blank = hi846->cur_mode->llp - hi846->cur_mode->width; + + __v4l2_ctrl_modify_range(hi846->hblank, h_blank, h_blank, 1, + h_blank); + + dev_dbg(&client->dev, "Set fmt w=%d h=%d code=0x%x colorspace=0x%x\n", + mf->width, mf->height, + fmt->code, fmt->colorspace); + + mutex_unlock(&hi846->mutex); + + return 0; +} + +static int hi846_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct hi846 *hi846 = to_hi846(sd); + struct v4l2_mbus_framefmt *mf = &format->format; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + format->format = *v4l2_subdev_get_try_format(&hi846->sd, + sd_state, + format->pad); + return 0; + } + + mutex_lock(&hi846->mutex); + mf->code = HI846_MEDIA_BUS_FORMAT; + mf->colorspace = V4L2_COLORSPACE_RAW; + mf->field = V4L2_FIELD_NONE; + mf->width = hi846->cur_mode->width; + mf->height = hi846->cur_mode->height; + mutex_unlock(&hi846->mutex); + dev_dbg(&client->dev, + "Get format w=%d h=%d code=0x%x colorspace=0x%x\n", + mf->width, mf->height, mf->code, mf->colorspace); + + return 0; +} + +static int hi846_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = HI846_MEDIA_BUS_FORMAT; + + return 0; +} + +static int hi846_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (fse->pad || fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != HI846_MEDIA_BUS_FORMAT) { + dev_err(&client->dev, "frame size enum not matching\n"); + return -EINVAL; + } + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = supported_modes[fse->index].height; + + dev_dbg(&client->dev, "%s: max width: %d max height: %d\n", __func__, + fse->max_width, fse->max_height); + + return 0; +} + +static int hi846_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct hi846 *hi846 = to_hi846(sd); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + mutex_lock(&hi846->mutex); + switch (sel->which) { + case V4L2_SUBDEV_FORMAT_TRY: + v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); + break; + case V4L2_SUBDEV_FORMAT_ACTIVE: + sel->r = hi846->cur_mode->crop; + break; + } + mutex_unlock(&hi846->mutex); + return 0; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = 3264; + sel->r.height = 2448; + return 0; + default: + return -EINVAL; + } +} + +static int hi846_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct hi846 *hi846 = to_hi846(sd); + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, sd_state, 0); + + mutex_lock(&hi846->mutex); + mf->code = HI846_MEDIA_BUS_FORMAT; + mf->colorspace = V4L2_COLORSPACE_RAW; + mf->field = V4L2_FIELD_NONE; + mf->width = hi846->cur_mode->width; + mf->height = hi846->cur_mode->height; + mutex_unlock(&hi846->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops hi846_video_ops = { + .s_stream = hi846_set_stream, +}; + +static const struct v4l2_subdev_pad_ops hi846_pad_ops = { + .init_cfg = hi846_init_cfg, + .enum_frame_size = hi846_enum_frame_size, + .enum_mbus_code = hi846_enum_mbus_code, + .set_fmt = hi846_set_format, + .get_fmt = hi846_get_format, + .get_selection = hi846_get_selection, +}; + +static const struct v4l2_subdev_ops hi846_subdev_ops = { + .video = &hi846_video_ops, + .pad = &hi846_pad_ops, +}; + +static const struct media_entity_operations hi846_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int hi846_identify_module(struct hi846 *hi846) +{ + struct i2c_client *client = v4l2_get_subdevdata(&hi846->sd); + int ret; + u8 hi, lo; + + ret = hi846_read_reg(hi846, HI846_REG_CHIP_ID_L, &lo); + if (ret) + return ret; + + if (lo != HI846_CHIP_ID_L) { + dev_err(&client->dev, "wrong chip id low byte: %x", lo); + return -ENXIO; + } + + ret = hi846_read_reg(hi846, HI846_REG_CHIP_ID_H, &hi); + if (ret) + return ret; + + if (hi != HI846_CHIP_ID_H) { + dev_err(&client->dev, "wrong chip id high byte: %x", hi); + return -ENXIO; + } + + dev_info(&client->dev, "chip id %02X %02X using %d mipi lanes\n", + hi, lo, hi846->nr_lanes); + + return 0; +} + +static s64 hi846_check_link_freqs(struct hi846 *hi846, + struct v4l2_fwnode_endpoint *ep) +{ + const s64 *freqs = hi846_link_freqs; + int freqs_count = ARRAY_SIZE(hi846_link_freqs); + int i, j; + + for (i = 0; i < freqs_count; i++) { + for (j = 0; j < ep->nr_of_link_frequencies; j++) + if (freqs[i] == ep->link_frequencies[j]) + break; + if (j == ep->nr_of_link_frequencies) + return freqs[i]; + } + + return 0; +} + +static int hi846_parse_dt(struct hi846 *hi846, struct device *dev) +{ + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret; + s64 fq; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) { + dev_err(dev, "unable to find endpoint node\n"); + return -ENXIO; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) { + dev_err(dev, "failed to parse endpoint node: %d\n", ret); + return ret; + } + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2 && + bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + v4l2_fwnode_endpoint_free(&bus_cfg); + return -EINVAL; + } + + hi846->nr_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + return -EINVAL; + } + + /* Check that link frequences for all the modes are in device tree */ + fq = hi846_check_link_freqs(hi846, &bus_cfg); + if (fq) { + dev_err(dev, "Link frequency of %lld is not supported\n", fq); + return -EINVAL; + } + + v4l2_fwnode_endpoint_free(&bus_cfg); + + hi846->rst_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(hi846->rst_gpio)) { + dev_err(dev, "failed to get reset gpio: %pe\n", + hi846->rst_gpio); + return PTR_ERR(hi846->rst_gpio); + } + + hi846->shutdown_gpio = devm_gpiod_get_optional(dev, "shutdown", + GPIOD_OUT_LOW); + if (IS_ERR(hi846->shutdown_gpio)) { + dev_err(dev, "failed to get shutdown gpio: %pe\n", + hi846->shutdown_gpio); + return PTR_ERR(hi846->shutdown_gpio); + } + + return 0; +} + +static int hi846_probe(struct i2c_client *client) +{ + struct hi846 *hi846; + int ret; + int i; + u32 mclk_freq; + + hi846 = devm_kzalloc(&client->dev, sizeof(*hi846), GFP_KERNEL); + if (!hi846) + return -ENOMEM; + + ret = hi846_parse_dt(hi846, &client->dev); + if (ret) { + dev_err(&client->dev, "failed to check HW configuration: %d", + ret); + return ret; + } + + hi846->clock = devm_clk_get(&client->dev, NULL); + if (IS_ERR(hi846->clock)) { + dev_err(&client->dev, "failed to get clock: %pe\n", + hi846->clock); + return PTR_ERR(hi846->clock); + } + + mclk_freq = clk_get_rate(hi846->clock); + if (mclk_freq != 25000000) + dev_warn(&client->dev, + "External clock freq should be 25000000, not %u.\n", + mclk_freq); + + for (i = 0; i < HI846_NUM_SUPPLIES; i++) + hi846->supplies[i].supply = hi846_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, HI846_NUM_SUPPLIES, + hi846->supplies); + if (ret < 0) + return ret; + + v4l2_i2c_subdev_init(&hi846->sd, client, &hi846_subdev_ops); + + mutex_init(&hi846->mutex); + + ret = hi846_power_on(hi846); + if (ret) + goto err_mutex; + + ret = hi846_identify_module(hi846); + if (ret) + goto err_power_off; + + hi846->cur_mode = &supported_modes[0]; + + ret = hi846_init_controls(hi846); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto err_power_off; + } + + hi846->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + hi846->sd.entity.ops = &hi846_subdev_entity_ops; + hi846->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + hi846->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&hi846->sd.entity, 1, &hi846->pad); + if (ret) { + dev_err(&client->dev, "failed to init entity pads: %d", ret); + goto err_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&hi846->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %d", + ret); + goto err_media_entity_cleanup; + } + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_media_entity_cleanup: + media_entity_cleanup(&hi846->sd.entity); + +err_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(hi846->sd.ctrl_handler); + +err_power_off: + hi846_power_off(hi846); + +err_mutex: + mutex_destroy(&hi846->mutex); + + return ret; +} + +static int hi846_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hi846 *hi846 = to_hi846(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + hi846_suspend(&client->dev); + pm_runtime_set_suspended(&client->dev); + + mutex_destroy(&hi846->mutex); + + return 0; +} + +static UNIVERSAL_DEV_PM_OPS(hi846_pm_ops, hi846_suspend, hi846_resume, NULL); + +static const struct of_device_id hi846_of_match[] = { + { .compatible = "hynix,hi846", }, + {}, +}; +MODULE_DEVICE_TABLE(of, hi846_of_match); + +static struct i2c_driver hi846_i2c_driver = { + .driver = { + .name = "hi846", + .pm = &hi846_pm_ops, + .of_match_table = of_match_ptr(hi846_of_match), + }, + .probe_new = hi846_probe, + .remove = hi846_remove, +}; + +module_i2c_driver(hi846_i2c_driver); + +MODULE_AUTHOR("Angus Ainslie "); +MODULE_AUTHOR("Martin Kepplinger "); +MODULE_DESCRIPTION("Hynix HI846 sensor driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From af1ffd628adfb26f7dafe8e94e0f4bad99f44c3e Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Tue, 5 Oct 2021 21:04:27 +0200 Subject: media: rcar-isp: Add Renesas R-Car Image Signal Processor driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a V4L2 driver for Renesas R-Car Image Signal Processor. The driver supports the R-Car V3U SoC where the ISP IP sits between the R-Car CSI-2 receiver and VIN and filters the CSI-2 data based on VC/DT and directs the video stream to different VIN IPs. Signed-off-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 1 + drivers/media/platform/Kconfig | 16 ++ drivers/media/platform/Makefile | 1 + drivers/media/platform/rcar-isp.c | 515 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 533 insertions(+) create mode 100644 drivers/media/platform/rcar-isp.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 60deb26c740f..cb51ebb3fb42 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11681,6 +11681,7 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/renesas,csi2.yaml F: Documentation/devicetree/bindings/media/renesas,isp.yaml F: Documentation/devicetree/bindings/media/renesas,vin.yaml +F: drivers/media/platform/rcar-isp.c F: drivers/media/platform/rcar-vin/ MEDIA DRIVERS FOR RENESAS - VSP1 diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index d9f90084c2f6..499c673fa02a 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -200,6 +200,22 @@ config VIDEO_TI_CAL_MC endif # VIDEO_TI_CAL +config VIDEO_RCAR_ISP + tristate "R-Car Image Signal Processor (ISP)" + depends on VIDEO_V4L2 && OF + depends on ARCH_RENESAS || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select RESET_CONTROLLER + select V4L2_FWNODE + help + Support for Renesas R-Car Image Signal Processor (ISP). + Enable this to support the Renesas R-Car Image Signal + Processor (ISP). + + To compile this driver as a module, choose M here: the + module will be called rcar-isp. + endif # V4L_PLATFORM_DRIVERS menuconfig V4L_MEM2MEM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 73ce083c2fc6..a148553babfc 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_VIDEO_AM437X_VPFE) += am437x/ obj-$(CONFIG_VIDEO_XILINX) += xilinx/ +obj-$(CONFIG_VIDEO_RCAR_ISP) += rcar-isp.o obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/ obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/ diff --git a/drivers/media/platform/rcar-isp.c b/drivers/media/platform/rcar-isp.c new file mode 100644 index 000000000000..2ffab30bc011 --- /dev/null +++ b/drivers/media/platform/rcar-isp.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Renesas Electronics Corp. + * + * Driver for Renesas R-Car ISP Channel Selector + * + * The ISP hardware is capable of more than just channel selection, features + * such as demosaicing, white balance control and color space conversion are + * also possible. These more advanced features are not supported by the driver + * due to lack of documentation. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define ISPINPUTSEL0_REG 0x0008 +#define ISPINPUTSEL0_SEL_CSI0 BIT(31) + +#define ISPSTART_REG 0x0014 +#define ISPSTART_START 0xffff +#define ISPSTART_STOP 0x0000 + +#define ISPPROCMODE_DT_REG(n) (0x1100 + (0x4 * (n))) +#define ISPPROCMODE_DT_PROC_MODE_VC3(pm) (((pm) & 0x3f) << 24) +#define ISPPROCMODE_DT_PROC_MODE_VC2(pm) (((pm) & 0x3f) << 16) +#define ISPPROCMODE_DT_PROC_MODE_VC1(pm) (((pm) & 0x3f) << 8) +#define ISPPROCMODE_DT_PROC_MODE_VC0(pm) ((pm) & 0x3f) + +#define ISPCS_FILTER_ID_CH_REG(n) (0x3000 + (0x0100 * (n))) + +#define ISPCS_DT_CODE03_CH_REG(n) (0x3008 + (0x100 * (n))) +#define ISPCS_DT_CODE03_EN3 BIT(31) +#define ISPCS_DT_CODE03_DT3(dt) (((dt) & 0x3f) << 24) +#define ISPCS_DT_CODE03_EN2 BIT(23) +#define ISPCS_DT_CODE03_DT2(dt) (((dt) & 0x3f) << 16) +#define ISPCS_DT_CODE03_EN1 BIT(15) +#define ISPCS_DT_CODE03_DT1(dt) (((dt) & 0x3f) << 8) +#define ISPCS_DT_CODE03_EN0 BIT(7) +#define ISPCS_DT_CODE03_DT0(dt) ((dt) & 0x3f) + +struct rcar_isp_format { + u32 code; + unsigned int datatype; + unsigned int procmode; +}; + +static const struct rcar_isp_format rcar_isp_formats[] = { + { .code = MEDIA_BUS_FMT_RGB888_1X24, .datatype = 0x24, .procmode = 0x15 }, + { .code = MEDIA_BUS_FMT_Y10_1X10, .datatype = 0x2b, .procmode = 0x10 }, + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .procmode = 0x0c }, + { .code = MEDIA_BUS_FMT_YUYV8_1X16, .datatype = 0x1e, .procmode = 0x0c }, + { .code = MEDIA_BUS_FMT_UYVY8_2X8, .datatype = 0x1e, .procmode = 0x0c }, + { .code = MEDIA_BUS_FMT_YUYV10_2X10, .datatype = 0x1e, .procmode = 0x0c }, +}; + +static const struct rcar_isp_format *risp_code_to_fmt(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcar_isp_formats); i++) { + if (rcar_isp_formats[i].code == code) + return &rcar_isp_formats[i]; + } + + return NULL; +} + +enum rcar_isp_input { + RISP_CSI_INPUT0, + RISP_CSI_INPUT1, +}; + +enum rcar_isp_pads { + RCAR_ISP_SINK, + RCAR_ISP_PORT0, + RCAR_ISP_PORT1, + RCAR_ISP_PORT2, + RCAR_ISP_PORT3, + RCAR_ISP_PORT4, + RCAR_ISP_PORT5, + RCAR_ISP_PORT6, + RCAR_ISP_PORT7, + RCAR_ISP_NUM_PADS, +}; + +struct rcar_isp { + struct device *dev; + void __iomem *base; + struct reset_control *rstc; + + enum rcar_isp_input csi_input; + + struct v4l2_subdev subdev; + struct media_pad pads[RCAR_ISP_NUM_PADS]; + + struct v4l2_async_notifier notifier; + struct v4l2_subdev *remote; + + struct mutex lock; /* Protects mf and stream_count. */ + struct v4l2_mbus_framefmt mf; + int stream_count; +}; + +static inline struct rcar_isp *sd_to_isp(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rcar_isp, subdev); +} + +static inline struct rcar_isp *notifier_to_isp(struct v4l2_async_notifier *n) +{ + return container_of(n, struct rcar_isp, notifier); +} + +static void risp_write(struct rcar_isp *isp, u32 offset, u32 value) +{ + iowrite32(value, isp->base + offset); +} + +static u32 risp_read(struct rcar_isp *isp, u32 offset) +{ + return ioread32(isp->base + offset); +} + +static int risp_power_on(struct rcar_isp *isp) +{ + int ret; + + ret = pm_runtime_resume_and_get(isp->dev); + if (ret < 0) + return ret; + + ret = reset_control_deassert(isp->rstc); + if (ret < 0) { + pm_runtime_put(isp->dev); + return ret; + } + + return 0; +} + +static void risp_power_off(struct rcar_isp *isp) +{ + reset_control_assert(isp->rstc); + pm_runtime_put(isp->dev); +} + +static int risp_start(struct rcar_isp *isp) +{ + const struct rcar_isp_format *format; + unsigned int vc; + u32 sel_csi = 0; + int ret; + + format = risp_code_to_fmt(isp->mf.code); + if (!format) { + dev_err(isp->dev, "Unsupported bus format\n"); + return -EINVAL; + } + + ret = risp_power_on(isp); + if (ret) { + dev_err(isp->dev, "Failed to power on ISP\n"); + return ret; + } + + /* Select CSI-2 input source. */ + if (isp->csi_input == RISP_CSI_INPUT1) + sel_csi = ISPINPUTSEL0_SEL_CSI0; + + risp_write(isp, ISPINPUTSEL0_REG, + risp_read(isp, ISPINPUTSEL0_REG) | sel_csi); + + /* Configure Channel Selector. */ + for (vc = 0; vc < 4; vc++) { + u8 ch = vc + 4; + u8 dt = format->datatype; + + risp_write(isp, ISPCS_FILTER_ID_CH_REG(ch), BIT(vc)); + risp_write(isp, ISPCS_DT_CODE03_CH_REG(ch), + ISPCS_DT_CODE03_EN3 | ISPCS_DT_CODE03_DT3(dt) | + ISPCS_DT_CODE03_EN2 | ISPCS_DT_CODE03_DT2(dt) | + ISPCS_DT_CODE03_EN1 | ISPCS_DT_CODE03_DT1(dt) | + ISPCS_DT_CODE03_EN0 | ISPCS_DT_CODE03_DT0(dt)); + } + + /* Setup processing method. */ + risp_write(isp, ISPPROCMODE_DT_REG(format->datatype), + ISPPROCMODE_DT_PROC_MODE_VC3(format->procmode) | + ISPPROCMODE_DT_PROC_MODE_VC2(format->procmode) | + ISPPROCMODE_DT_PROC_MODE_VC1(format->procmode) | + ISPPROCMODE_DT_PROC_MODE_VC0(format->procmode)); + + /* Start ISP. */ + risp_write(isp, ISPSTART_REG, ISPSTART_START); + + ret = v4l2_subdev_call(isp->remote, video, s_stream, 1); + if (ret) + risp_power_off(isp); + + return ret; +} + +static void risp_stop(struct rcar_isp *isp) +{ + v4l2_subdev_call(isp->remote, video, s_stream, 0); + + /* Stop ISP. */ + risp_write(isp, ISPSTART_REG, ISPSTART_STOP); + + risp_power_off(isp); +} + +static int risp_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rcar_isp *isp = sd_to_isp(sd); + int ret = 0; + + mutex_lock(&isp->lock); + + if (!isp->remote) { + ret = -ENODEV; + goto out; + } + + if (enable && isp->stream_count == 0) { + ret = risp_start(isp); + if (ret) + goto out; + } else if (!enable && isp->stream_count == 1) { + risp_stop(isp); + } + + isp->stream_count += enable ? 1 : -1; +out: + mutex_unlock(&isp->lock); + + return ret; +} + +static const struct v4l2_subdev_video_ops risp_video_ops = { + .s_stream = risp_s_stream, +}; + +static int risp_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct rcar_isp *isp = sd_to_isp(sd); + struct v4l2_mbus_framefmt *framefmt; + + mutex_lock(&isp->lock); + + if (!risp_code_to_fmt(format->format.code)) + format->format.code = rcar_isp_formats[0].code; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + isp->mf = format->format; + } else { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, 0); + *framefmt = format->format; + } + + mutex_unlock(&isp->lock); + + return 0; +} + +static int risp_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct rcar_isp *isp = sd_to_isp(sd); + + mutex_lock(&isp->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + format->format = isp->mf; + else + format->format = *v4l2_subdev_get_try_format(sd, sd_state, 0); + + mutex_unlock(&isp->lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops risp_pad_ops = { + .set_fmt = risp_set_pad_format, + .get_fmt = risp_get_pad_format, + .link_validate = v4l2_subdev_link_validate_default, +}; + +static const struct v4l2_subdev_ops rcar_isp_subdev_ops = { + .video = &risp_video_ops, + .pad = &risp_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Async handling and registration of subdevices and links + */ + +static int risp_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_isp *isp = notifier_to_isp(notifier); + int pad; + + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) { + dev_err(isp->dev, "Failed to find pad for %s\n", subdev->name); + return pad; + } + + isp->remote = subdev; + + dev_dbg(isp->dev, "Bound %s pad: %d\n", subdev->name, pad); + + return media_create_pad_link(&subdev->entity, pad, + &isp->subdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static void risp_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_isp *isp = notifier_to_isp(notifier); + + isp->remote = NULL; + + dev_dbg(isp->dev, "Unbind %s\n", subdev->name); +} + +static const struct v4l2_async_notifier_operations risp_notify_ops = { + .bound = risp_notify_bound, + .unbind = risp_notify_unbind, +}; + +static int risp_parse_dt(struct rcar_isp *isp) +{ + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode; + struct fwnode_handle *ep; + unsigned int id; + int ret; + + for (id = 0; id < 2; id++) { + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), + 0, id, 0); + if (ep) + break; + } + + if (!ep) { + dev_err(isp->dev, "Not connected to subdevice\n"); + return -EINVAL; + } + + if (id == 1) + isp->csi_input = RISP_CSI_INPUT1; + + fwnode = fwnode_graph_get_remote_endpoint(ep); + fwnode_handle_put(ep); + + dev_dbg(isp->dev, "Found '%pOF'\n", to_of_node(fwnode)); + + v4l2_async_nf_init(&isp->notifier); + isp->notifier.ops = &risp_notify_ops; + + asd = v4l2_async_nf_add_fwnode(&isp->notifier, fwnode, + struct v4l2_async_subdev); + fwnode_handle_put(fwnode); + if (IS_ERR(asd)) + return PTR_ERR(asd); + + ret = v4l2_async_subdev_nf_register(&isp->subdev, &isp->notifier); + if (ret) + v4l2_async_nf_cleanup(&isp->notifier); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver + */ + +static const struct media_entity_operations risp_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int risp_probe_resources(struct rcar_isp *isp, + struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + isp->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(isp->base)) + return PTR_ERR(isp->base); + + isp->rstc = devm_reset_control_get(&pdev->dev, NULL); + + return PTR_ERR_OR_ZERO(isp->rstc); +} + +static const struct of_device_id risp_of_id_table[] = { + { .compatible = "renesas,r8a779a0-isp" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, risp_of_id_table); + +static int risp_probe(struct platform_device *pdev) +{ + struct rcar_isp *isp; + unsigned int i; + int ret; + + isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->dev = &pdev->dev; + + mutex_init(&isp->lock); + + ret = risp_probe_resources(isp, pdev); + if (ret) { + dev_err(isp->dev, "Failed to get resources\n"); + goto error_mutex; + } + + platform_set_drvdata(pdev, isp); + + pm_runtime_enable(&pdev->dev); + + ret = risp_parse_dt(isp); + if (ret) + goto error_pm; + + isp->subdev.owner = THIS_MODULE; + isp->subdev.dev = &pdev->dev; + v4l2_subdev_init(&isp->subdev, &rcar_isp_subdev_ops); + v4l2_set_subdevdata(&isp->subdev, &pdev->dev); + snprintf(isp->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s %s", + KBUILD_MODNAME, dev_name(&pdev->dev)); + isp->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + isp->subdev.entity.function = MEDIA_ENT_F_VID_MUX; + isp->subdev.entity.ops = &risp_entity_ops; + + isp->pads[RCAR_ISP_SINK].flags = MEDIA_PAD_FL_SINK; + for (i = RCAR_ISP_PORT0; i < RCAR_ISP_NUM_PADS; i++) + isp->pads[i].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&isp->subdev.entity, RCAR_ISP_NUM_PADS, + isp->pads); + if (ret) + goto error_notifier; + + ret = v4l2_async_register_subdev(&isp->subdev); + if (ret < 0) + goto error_notifier; + + dev_info(isp->dev, "Using CSI-2 input: %u\n", isp->csi_input); + + return 0; +error_notifier: + v4l2_async_nf_unregister(&isp->notifier); + v4l2_async_nf_cleanup(&isp->notifier); +error_pm: + pm_runtime_disable(&pdev->dev); +error_mutex: + mutex_destroy(&isp->lock); + + return ret; +} + +static int risp_remove(struct platform_device *pdev) +{ + struct rcar_isp *isp = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&isp->notifier); + v4l2_async_nf_cleanup(&isp->notifier); + + v4l2_async_unregister_subdev(&isp->subdev); + + pm_runtime_disable(&pdev->dev); + + mutex_destroy(&isp->lock); + + return 0; +} + +static struct platform_driver rcar_isp_driver = { + .driver = { + .name = "rcar-isp", + .of_match_table = risp_of_id_table, + }, + .probe = risp_probe, + .remove = risp_remove, +}; + +module_platform_driver(rcar_isp_driver); + +MODULE_AUTHOR("Niklas Söderlund "); +MODULE_DESCRIPTION("Renesas R-Car ISP Channel Selector driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 305e191ccf16647eb873255d69d8882fb7d50270 Mon Sep 17 00:00:00 2001 From: Jeff Chase Date: Wed, 6 Oct 2021 19:14:57 +0100 Subject: media: MAINTAINERS: update maintainer for ch7322 driver Jeff is leaving Google so Joe will take over. Signed-off-by: Jeff Chase Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index cb51ebb3fb42..df69dfe482e7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4431,7 +4431,7 @@ N: cros_ec N: cros-ec CHRONTEL CH7322 CEC DRIVER -M: Jeff Chase +M: Joe Tessler L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git -- cgit v1.2.3