Message ID | 20231123115104.32094-3-zhi.mao@mediatek.com |
---|---|
State | New |
Headers | show |
Series | media: i2c: Add support for GC08A3 sensor | expand |
Hi Zhi, kernel test robot noticed the following build warnings: [auto build test WARNING on robh/for-next] [also build test WARNING on linuxtv-media-stage/master sailus-media-tree/streams linus/master v6.7-rc2 next-20231124] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Zhi-Mao/media-dt-bindings-media-i2c-Document-GC08A3-bindings/20231123-203838 base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next patch link: https://lore.kernel.org/r/20231123115104.32094-3-zhi.mao%40mediatek.com patch subject: [PATCH 2/2] media: i2c: Add GC08A3 image sensor driver config: parisc-allmodconfig (https://download.01.org/0day-ci/archive/20231124/202311241238.fnVeS1ty-lkp@intel.com/config) compiler: hppa-linux-gcc (GCC) 13.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231124/202311241238.fnVeS1ty-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202311241238.fnVeS1ty-lkp@intel.com/ All warnings (new ones prefixed by >>): drivers/media/i2c/gc08a3.c: In function 'gc08a3_probe': drivers/media/i2c/gc08a3.c:1965:13: error: 'V4L2_SUBDEV_NAME_SIZE' undeclared (first use in this function); did you mean 'V4L2_SUBDEV_FL_IS_I2C'? 1965 | if (V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2 < | ^~~~~~~~~~~~~~~~~~~~~ | V4L2_SUBDEV_FL_IS_I2C drivers/media/i2c/gc08a3.c:1965:13: note: each undeclared identifier is reported only once for each function it appears in drivers/media/i2c/gc08a3.c: At top level: drivers/media/i2c/gc08a3.c:2038:10: error: 'struct i2c_driver' has no member named 'probe_new' 2038 | .probe_new = gc08a3_probe, | ^~~~~~~~~ drivers/media/i2c/gc08a3.c:2038:23: error: initialization of 'const struct i2c_device_id *' from incompatible pointer type 'int (*)(struct i2c_client *)' [-Werror=incompatible-pointer-types] 2038 | .probe_new = gc08a3_probe, | ^~~~~~~~~~~~ drivers/media/i2c/gc08a3.c:2038:23: note: (near initialization for 'gc08a3_i2c_driver.id_table') drivers/media/i2c/gc08a3.c: In function 'gc08a3_probe': >> drivers/media/i2c/gc08a3.c:1971:9: warning: 'strncat' specified bound 1 equals source length [-Wstringop-overflow=] 1971 | strncat(gc08a3->sd.name, " ", 1); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cc1: some warnings being treated as errors vim +/strncat +1971 drivers/media/i2c/gc08a3.c 1889 1890 static int gc08a3_probe(struct i2c_client *client) 1891 { 1892 struct device *dev = &client->dev; 1893 struct gc08a3 *gc08a3; 1894 int ret; 1895 1896 dev_info(dev, "--- %s +", __func__); 1897 1898 ret = gc08a3_parse_fwnode(dev); 1899 if (ret) 1900 return ret; 1901 1902 gc08a3 = devm_kzalloc(dev, sizeof(*gc08a3), GFP_KERNEL); 1903 if (!gc08a3) 1904 return -ENOMEM; 1905 1906 gc08a3->dev = dev; 1907 1908 gc08a3->xclk = devm_clk_get(dev, NULL); 1909 if (IS_ERR(gc08a3->xclk)) { 1910 dev_err(dev, "could not get xclk\n"); 1911 return PTR_ERR(gc08a3->xclk); 1912 } 1913 1914 ret = clk_set_rate(gc08a3->xclk, GC08A3_DEFAULT_CLK_FREQ); 1915 if (ret) { 1916 dev_err(dev, "could not set xclk frequency\n"); 1917 return ret; 1918 } 1919 1920 ret = gc08a3_get_regulators(dev, gc08a3); 1921 if (ret < 0) { 1922 dev_err(dev, "cannot get regulators\n"); 1923 return ret; 1924 } 1925 1926 gc08a3->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); 1927 if (IS_ERR(gc08a3->enable_gpio)) { 1928 dev_err(dev, "cannot get enable gpio\n"); 1929 return PTR_ERR(gc08a3->enable_gpio); 1930 } 1931 1932 gc08a3->regmap = devm_regmap_init_i2c(client, &sensor_regmap_config); 1933 if (IS_ERR(gc08a3->regmap)) { 1934 dev_err(dev, "regmap init failed\n"); 1935 return PTR_ERR(gc08a3->regmap); 1936 } 1937 1938 v4l2_i2c_subdev_init(&gc08a3->sd, client, &gc08a3_subdev_ops); 1939 1940 gc08a3_power_on(gc08a3->dev); 1941 1942 ret = gc08a3_identify_module(gc08a3); 1943 if (ret) { 1944 dev_err(&client->dev, "failed to find sensor: %d\n", ret); 1945 gc08a3_power_off(gc08a3->dev); 1946 return ret; 1947 } 1948 1949 mutex_init(&gc08a3->mutex); 1950 gc08a3->cur_mode = &gc08a3_modes[0]; 1951 1952 ret = gc08a3_init_controls(gc08a3); 1953 if (ret) { 1954 dev_err(&client->dev, "failed to init controls: %d", ret); 1955 goto free_ctrl; 1956 } 1957 1958 gc08a3->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; 1959 gc08a3->pad.flags = MEDIA_PAD_FL_SOURCE; 1960 gc08a3->sd.dev = &client->dev; 1961 gc08a3->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; 1962 1963 dev_dbg(&client->dev, "gc08a3->sd.name: %s, dev->of_node->name: %s\n", 1964 gc08a3->sd.name, dev->of_node->name); 1965 if (V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2 < 1966 strlen(dev->of_node->name)) { 1967 dev_err(&client->dev, 1968 "the string length of (sd.name + of_node->name) is too long.\n"); 1969 return -EINVAL; 1970 } > 1971 strncat(gc08a3->sd.name, " ", 1); 1972 strncat(gc08a3->sd.name, dev->of_node->name, 1973 V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2); 1974 dev_dbg(&client->dev, "after: gc08a3->sd.name: %s\n", gc08a3->sd.name); 1975 1976 ret = media_entity_pads_init(&gc08a3->sd.entity, 1, &gc08a3->pad); 1977 if (ret < 0) { 1978 dev_err(dev, "could not register media entity\n"); 1979 goto free_ctrl; 1980 } 1981 1982 ret = v4l2_async_register_subdev_sensor(&gc08a3->sd); 1983 if (ret < 0) { 1984 dev_err(dev, "could not register v4l2 device\n"); 1985 goto free_entity; 1986 } 1987 1988 pm_runtime_set_active(gc08a3->dev); 1989 pm_runtime_enable(gc08a3->dev); 1990 pm_runtime_idle(gc08a3->dev); 1991 1992 dev_info(dev, "--- %s -", __func__); 1993 1994 return 0; 1995 1996 free_entity: 1997 media_entity_cleanup(&gc08a3->sd.entity); 1998 free_ctrl: 1999 mutex_destroy(&gc08a3->mutex); 2000 v4l2_ctrl_handler_free(&gc08a3->ctrls); 2001 pm_runtime_disable(gc08a3->dev); 2002 2003 return ret; 2004 } 2005
Hi Zhi Mao, Thank you for the patch. This is a partial review, as lots of things will change already for v2. I'll review the next version in more details. First of all, please rebase the driver on top of the master branch of git://linuxtv.org/media_stage.git. Quite a few in-kernel APIs have changed. On Thu, Nov 23, 2023 at 07:51:04PM +0800, Zhi Mao wrote: > Add a V4L2 sub-device driver for Galaxycore GC08A3 image sensor. > > Signed-off-by: Zhi Mao <zhi.mao@mediatek.com> > --- > drivers/media/i2c/Kconfig | 14 + > drivers/media/i2c/Makefile | 1 + > drivers/media/i2c/gc08a3.c | 2046 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 2061 insertions(+) > create mode 100644 drivers/media/i2c/gc08a3.c > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > index 59ee0ca2c978..2e37be537690 100644 > --- a/drivers/media/i2c/Kconfig > +++ b/drivers/media/i2c/Kconfig > @@ -1451,6 +1451,20 @@ config VIDEO_THS7303 > To compile this driver as a module, choose M here: the > module will be called ths7303. > > +config VIDEO_GC08A3 Alphabetical order please. > + tristate "GalaxyCore gc08a3 sensor support" > + depends on GPIOLIB && I2C && VIDEO_DEV > + select V4L2_FWNODE > + select MEDIA_CONTROLLER > + select VIDEO_V4L2_SUBDEV_API You can drop all of the above except depends on GPIOLIB The dependencies on I2C and VIDEO_DEV and the selection of V4L2_FWNODE, MEDIA_CONTROLLER and VIDEIO_V4L2_SUBDEV_API are now handled automatically for all drivers in the VIDEO_CAMERA_SENSOR menu. > + select REGMAP_I2C > + help > + This is a Video4Linux2 sensor driver for the GalaxyCore gc08a3 > + camera. > + > + To compile this driver as a module, choose M here: the > + module will be called gc08a3. > + > endmenu > > # > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile > index f5010f80a21f..ec40dbd75e7a 100644 > --- a/drivers/media/i2c/Makefile > +++ b/drivers/media/i2c/Makefile > @@ -36,6 +36,7 @@ obj-$(CONFIG_VIDEO_DW9719) += dw9719.o > obj-$(CONFIG_VIDEO_DW9768) += dw9768.o > obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o > obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ > +obj-$(CONFIG_VIDEO_GC08A3) += gc08a3.o > obj-$(CONFIG_VIDEO_HI556) += hi556.o > obj-$(CONFIG_VIDEO_HI846) += hi846.o > obj-$(CONFIG_VIDEO_HI847) += hi847.o > diff --git a/drivers/media/i2c/gc08a3.c b/drivers/media/i2c/gc08a3.c > new file mode 100644 > index 000000000000..d3cf62b65c2e > --- /dev/null > +++ b/drivers/media/i2c/gc08a3.c > @@ -0,0 +1,2046 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * gc08a3.c - gc08a3 sensor driver > + * > + * Copyright 2023 Mediatek > + * > + * Zhi Mao <zhi.mao@mediatek.com> > + */ > + > +#include <asm/unaligned.h> > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/gpio/consumer.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/pm_runtime.h> > +#include <linux/regmap.h> > +#include <linux/regulator/consumer.h> > +#include <media/media-entity.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-subdev.h> > + > +//========================================= V4L2 uses C-style comments. > +#define GC08A3_REG_VALUE_08BIT 1 > +#define GC08A3_REG_VALUE_16BIT 2 > +#define GC08A3_REG_VALUE_24BIT 3 Please use the v4l2-cci helpers for register access. > + > +#define GC08A3_REG_CHIP_ID 0x03f0 > +#define GC08A3_CHIP_ID 0x08a3 > + > +#define GC08A3_NATIVE_WIDTH 3264 > +#define GC08A3_NATIVE_HEIGHT 2448 > + > +#define GC08A3_REG_TEST_PATTERN_EN 0x008c > +#define GC08A3_REG_TEST_PATTERN_IDX 0x008d > +#define GC08A3_TEST_PATTERN_EN 0x01 > + > +#define GC08A3_STRAEMING_REG 0x0100 > + > +#define GC08A3_SCLK 280000000LL > + > +#define GC08A3_DEFAULT_CLK_FREQ 24000000 > +#define GC08A3_FPS 30 > +#define GC08A3_MBUS_CODE MEDIA_BUS_FMT_SRGGB10_1X10 > +#define GC08A3_DATA_LANES 4 > + > +//for 1920*1080 > +#define GC08A3_LINK_FREQ_207MHZ 207000000ULL > +//for 3264*2448 > +#define GC08A3_LINK_FREQ_336MHZ 336000000ULL Macros don't help readibility, simply use the numerical values in the link_freq_menu_items array below. > + > +#define GC08A3_RGB_DEPTH 10 > + > +//frame length > +#define GC08A3_FL_REG 0x0340 > +#define GC08A3_VTS_30FPS 2548 > +#define GC08A3_VTS_30FPS_MIN 2548 > +#define GC08A3_VTS_60FPS 1276 > +#define GC08A3_VTS_60FPS_MIN 1276 > +#define GC08A3_VTS_MAX 0xfff0 > +#define GC08A3_FL_MARGIN 48 > + > +// line length > +#define GC08A3_LL_REG 0x0342 > +#define GC08A3_HTS_30FPS 3640 > +#define GC08A3_HTS_60FPS 3640 > + > +#define GC08A3_EXP_REG 0x0202 > +#define GC08A3_EXP_MARGIN 16 > +#define GC08A3_EXP_MIN 4 > +#define GC08A3_EXP_STEP 1 > + > +#define GC08A3_FLIP_REG 0x0101 > +#define GC08A3_FLIP_H_MASK 0x1 > +#define GC08A3_FLIP_V_MASK 0x2 > + > +#define GC08A3_AGAIN_REG 0x0204 > +#define GC08A3_AGAIN_MIN 1024 > +#define GC08A3_AGAIN_MAX (1024 * 16) > +#define GC08A3_AGAIN_STEP 1 > + > +#define GC08A3_DGAIN_REG 0x020e > +#define GC08A3_DGAIN_MIN 1024 > +#define GC08A3_DGAIN_MAX 1024 > +#define GC08A3_DGAIN_STEP 1 > +//============================================================ > + > +//============================================================ > +static const char *const gc08a3_test_pattern_menu[] = { > + "No Pattern", "Solid Black", "Solid White", "Solid Red", > + "Solid Green", "Solid Blue", "Solid Yellow", "Colour Bar", > +}; > + > +enum { > + GC08A3_LINK_FREQ_336MHZ_CFG, > + GC08A3_LINK_FREQ_207MHZ_CFG, > +}; > + > +static const s64 link_freq_menu_items[] = { > + GC08A3_LINK_FREQ_336MHZ, > + GC08A3_LINK_FREQ_207MHZ, > +}; > + > +static const char *const gc08a3_supply_name[] = { > + "avdd", > + "dvdd", > + "dovdd", > +}; > + > +#define GC08A3_NUM_SUPPLIES ARRAY_SIZE(gc08a3_supply_name) > + > +struct gc08a3 { > + struct device *dev; > + struct v4l2_subdev sd; > + struct media_pad pad; > + struct v4l2_mbus_framefmt fmt; Please use the subdev active state to store the active format. See commit e8a5b1df000e ("media: i2c: imx219: Use subdev active state") as an example of how to do so. There's also documentation in Documentation/driver-api/media/v4l2-subdev.rst (please read the most up-to-date version in the master branch of the media_stage tree). > + struct i2c_client *client; > + > + struct v4l2_rect crop; This should be stored in the subdev active state too. > + > + struct clk *xclk; > + struct regulator_bulk_data supplies[GC08A3_NUM_SUPPLIES]; > + struct gpio_desc *enable_gpio; > + > + struct regmap *regmap; > + > + struct v4l2_ctrl_handler ctrls; > + struct v4l2_ctrl *pixel_rate; > + struct v4l2_ctrl *link_freq; > + struct v4l2_ctrl *exposure; > + struct v4l2_ctrl *vblank; > + struct v4l2_ctrl *hblank; > + > + /* > + * Serialize control access, get/set format, get selection > + * and start streaming. > + */ > + struct mutex mutex; The mutex won't be needed anymore once you use the subdev active state. > + > + bool streaming; > + > + /* Current mode */ > + const struct gc08a3_mode *cur_mode; > +}; > + > +struct reg_8 { > + u16 address; > + u8 val; > +}; > + > +struct gc08a3_reg_list { > + u32 num_of_regs; > + const struct reg_8 *regs; > +}; > + > +struct gc08a3_link_freq_config { > + const struct gc08a3_reg_list reg_list; > +}; > + > +enum { > + GC08A3_TABLE_WAIT_MS = 0, > + GC08A3_TABLE_END, > +}; > + > +/*From gc08a3_mode_tbls.h*/ > +static const struct reg_8 mode_3264x2448[] = { > + /*system*/ > + { 0x031c, 0x60 }, > + { 0x0337, 0x04 }, > + { 0x0335, 0x51 }, > + { 0x0336, 0x70 }, > + { 0x0383, 0xbb }, > + { 0x031a, 0x00 }, > + { 0x0321, 0x10 }, > + { 0x0327, 0x03 }, > + { 0x0325, 0x40 }, > + { 0x0326, 0x23 }, > + { 0x0314, 0x11 }, > + { 0x0315, 0xd6 }, > + { 0x0316, 0x01 }, > + { 0x0334, 0x40 }, > + { 0x0324, 0x42 }, > + { 0x031c, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x0344, 0x00 }, > + { 0x0345, 0x06 }, > + { 0x0346, 0x00 }, > + { 0x0347, 0x04 }, > + { 0x0348, 0x0c }, > + { 0x0349, 0xd0 }, //3280 > + { 0x034a, 0x09 }, > + { 0x034b, 0x9c }, //2460 > + { 0x0202, 0x09 }, > + { 0x0203, 0x04 }, //Exp > + { 0x0340, 0x09 }, > + { 0x0341, 0xf4 }, //FL > + { 0x0342, 0x07 }, > + { 0x0343, 0x1c }, //LineLength > + > + { 0x0226, 0x00 }, //min vb[15:8] > + { 0x0227, 0x28 }, //min vb[7:0] > + { 0x0e38, 0x49 }, > + { 0x0210, 0x13 }, > + { 0x0218, 0x00 }, > + { 0x0241, 0x88 }, > + { 0x0392, 0x60 }, > + > + /*ISP*/ > + { 0x031c, 0x80 }, > + { 0x03fe, 0x10 }, //CISCTL_rst > + { 0x03fe, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x031c, 0x80 }, > + { 0x03fe, 0x10 }, > + { 0x03fe, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x00a2, 0x00 }, > + { 0x00a3, 0x00 }, > + { 0x00ab, 0x00 }, > + { 0x00ac, 0x00 }, > + { 0x05a0, 0x82 }, > + { 0x05ac, 0x00 }, > + { 0x05ad, 0x01 }, > + { 0x05ae, 0x00 }, > + { 0x0800, 0x0a }, > + { 0x0801, 0x14 }, > + { 0x0802, 0x28 }, > + { 0x0803, 0x34 }, > + { 0x0804, 0x0e }, > + { 0x0805, 0x33 }, > + { 0x0806, 0x03 }, > + { 0x0807, 0x8a }, > + { 0x0808, 0x50 }, > + { 0x0809, 0x00 }, > + { 0x080a, 0x34 }, > + { 0x080b, 0x03 }, > + { 0x080c, 0x26 }, > + { 0x080d, 0x03 }, > + { 0x080e, 0x18 }, > + { 0x080f, 0x03 }, > + { 0x0810, 0x10 }, > + { 0x0811, 0x03 }, > + { 0x0812, 0x00 }, > + { 0x0813, 0x00 }, > + { 0x0814, 0x01 }, > + { 0x0815, 0x00 }, > + { 0x0816, 0x01 }, > + { 0x0817, 0x00 }, > + { 0x0818, 0x00 }, > + { 0x0819, 0x0a }, > + { 0x081a, 0x01 }, > + { 0x081b, 0x6c }, > + { 0x081c, 0x00 }, > + { 0x081d, 0x0b }, > + { 0x081e, 0x02 }, > + { 0x081f, 0x00 }, > + { 0x0820, 0x00 }, > + { 0x0821, 0x0c }, > + { 0x0822, 0x02 }, > + { 0x0823, 0xd9 }, > + { 0x0824, 0x00 }, > + { 0x0825, 0x0d }, > + { 0x0826, 0x03 }, > + { 0x0827, 0xf0 }, > + { 0x0828, 0x00 }, > + { 0x0829, 0x0e }, > + { 0x082a, 0x05 }, > + { 0x082b, 0x94 }, > + { 0x082c, 0x09 }, > + { 0x082d, 0x6e }, > + { 0x082e, 0x07 }, > + { 0x082f, 0xe6 }, > + { 0x0830, 0x10 }, > + { 0x0831, 0x0e }, > + { 0x0832, 0x0b }, > + { 0x0833, 0x2c }, > + { 0x0834, 0x14 }, > + { 0x0835, 0xae }, > + { 0x0836, 0x0f }, > + { 0x0837, 0xc4 }, > + { 0x0838, 0x18 }, > + { 0x0839, 0x0e }, > + { 0x05ac, 0x01 }, > + { 0x059a, 0x00 }, > + { 0x059b, 0x00 }, > + { 0x059c, 0x01 }, > + { 0x0598, 0x00 }, > + { 0x0597, 0x14 }, > + { 0x05ab, 0x09 }, > + { 0x05a4, 0x02 }, > + { 0x05a3, 0x05 }, > + { 0x05a0, 0xc2 }, > + { 0x0207, 0xc4 }, > + > + /*GAIN*/ > + { 0x0204, 0x04 }, > + { 0x0205, 0x00 }, > + { 0x0050, 0x5c }, > + { 0x0051, 0x44 }, > + > + /*out window*/ > + { 0x009a, 0x66 }, > + { 0x0351, 0x00 }, > + { 0x0352, 0x06 }, //out_win_y1 > + { 0x0353, 0x00 }, > + { 0x0354, 0x08 }, //out_win_x1 > + { 0x034c, 0x0c }, > + { 0x034d, 0xc0 }, //3264 > + { 0x034e, 0x09 }, > + { 0x034f, 0x90 }, //2448 > + > + /*MIPI*/ > + { 0x0114, 0x03 }, //0:1lane 1:2lane 3:4lane > + { 0x0180, 0x65 }, //[3:0]dphy_mipi_diff > + { 0x0181, 0xf0 }, > + { 0x0185, 0x01 }, > + { 0x0115, 0x30 }, > + { 0x011b, 0x12 }, > + { 0x011c, 0x12 }, > + { 0x0121, 0x06 }, //T_LPX > + { 0x0122, 0x06 }, //T_CLK_HS_PREPARE > + { 0x0123, 0x15 }, //T_CLK_zero > + { 0x0124, 0x01 }, //T_CLK_PRE > + { 0x0125, 0x0b }, //T_CLK_POST > + { 0x0126, 0x08 }, //T_CLK_TRAIL > + { 0x0129, 0x06 }, //T_HS_PREPARE > + { 0x012a, 0x08 }, //T_HS_Zero > + { 0x012b, 0x08 }, //T_HS_TRAIL > + > + { 0x0a73, 0x60 }, > + { 0x0a70, 0x11 }, > + { 0x0313, 0x80 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0a70, 0x00 }, > + { 0x00a4, 0x80 }, > + { 0x0316, 0x01 }, > + { 0x0a67, 0x00 }, > + { 0x0084, 0x10 }, > + { 0x0102, 0x09 }, > + > + { GC08A3_TABLE_END, 0x00 } > +}; > + > +static const struct reg_8 mode_1920x1080[] = { > + /*system*/ > + { 0x031c, 0x60 }, > + { 0x0337, 0x04 }, > + { 0x0335, 0x51 }, > + { 0x0336, 0x45 }, > + { 0x0383, 0x8b }, > + { 0x031a, 0x00 }, > + { 0x0321, 0x10 }, > + { 0x0327, 0x03 }, > + { 0x0325, 0x40 }, > + { 0x0326, 0x23 }, > + { 0x0314, 0x11 }, > + { 0x0315, 0xd6 }, > + { 0x0316, 0x01 }, > + { 0x0334, 0x40 }, > + { 0x0324, 0x42 }, > + { 0x031c, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x0344, 0x02 }, > + { 0x0345, 0xa6 }, > + { 0x0346, 0x02 }, > + { 0x0347, 0xb0 }, > + { 0x0348, 0x07 }, > + { 0x0349, 0x90 }, //1936 > + { 0x034a, 0x04 }, > + { 0x034b, 0x44 }, //1092 > + { 0x0202, 0x03 }, > + { 0x0203, 0x00 }, //Exp > + { 0x0340, 0x04 }, > + { 0x0341, 0xfc }, //FL > + { 0x0342, 0x07 }, > + { 0x0343, 0x1c }, //LineLength > + > + { 0x0226, 0x00 }, //min vb[15:8] > + { 0x0227, 0x88 }, //min vb[7:0] > + { 0x0e38, 0x49 }, > + { 0x0210, 0x13 }, > + { 0x0218, 0x00 }, > + { 0x0241, 0x88 }, > + { 0x0392, 0x60 }, > + > + /*ISP*/ > + { 0x031c, 0x80 }, > + { 0x03fe, 0x10 }, //CISCTL_rst > + { 0x03fe, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x031c, 0x80 }, > + { 0x03fe, 0x10 }, > + { 0x03fe, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x00a2, 0xac }, > + { 0x00a3, 0x02 }, > + { 0x00ab, 0xa0 }, > + { 0x00ac, 0x02 }, > + { 0x05a0, 0x82 }, > + { 0x05ac, 0x00 }, > + { 0x05ad, 0x01 }, > + { 0x05ae, 0x00 }, > + { 0x0800, 0x0a }, > + { 0x0801, 0x14 }, > + { 0x0802, 0x28 }, > + { 0x0803, 0x34 }, > + { 0x0804, 0x0e }, > + { 0x0805, 0x33 }, > + { 0x0806, 0x03 }, > + { 0x0807, 0x8a }, > + { 0x0808, 0x50 }, > + { 0x0809, 0x00 }, > + { 0x080a, 0x34 }, > + { 0x080b, 0x03 }, > + { 0x080c, 0x26 }, > + { 0x080d, 0x03 }, > + { 0x080e, 0x18 }, > + { 0x080f, 0x03 }, > + { 0x0810, 0x10 }, > + { 0x0811, 0x03 }, > + { 0x0812, 0x00 }, > + { 0x0813, 0x00 }, > + { 0x0814, 0x01 }, > + { 0x0815, 0x00 }, > + { 0x0816, 0x01 }, > + { 0x0817, 0x00 }, > + { 0x0818, 0x00 }, > + { 0x0819, 0x0a }, > + { 0x081a, 0x01 }, > + { 0x081b, 0x6c }, > + { 0x081c, 0x00 }, > + { 0x081d, 0x0b }, > + { 0x081e, 0x02 }, > + { 0x081f, 0x00 }, > + { 0x0820, 0x00 }, > + { 0x0821, 0x0c }, > + { 0x0822, 0x02 }, > + { 0x0823, 0xd9 }, > + { 0x0824, 0x00 }, > + { 0x0825, 0x0d }, > + { 0x0826, 0x03 }, > + { 0x0827, 0xf0 }, > + { 0x0828, 0x00 }, > + { 0x0829, 0x0e }, > + { 0x082a, 0x05 }, > + { 0x082b, 0x94 }, > + { 0x082c, 0x09 }, > + { 0x082d, 0x6e }, > + { 0x082e, 0x07 }, > + { 0x082f, 0xe6 }, > + { 0x0830, 0x10 }, > + { 0x0831, 0x0e }, > + { 0x0832, 0x0b }, > + { 0x0833, 0x2c }, > + { 0x0834, 0x14 }, > + { 0x0835, 0xae }, > + { 0x0836, 0x0f }, > + { 0x0837, 0xc4 }, > + { 0x0838, 0x18 }, > + { 0x0839, 0x0e }, > + { 0x05ac, 0x01 }, > + { 0x059a, 0x00 }, > + { 0x059b, 0x00 }, > + { 0x059c, 0x01 }, > + { 0x0598, 0x00 }, > + { 0x0597, 0x14 }, > + { 0x05ab, 0x09 }, > + { 0x05a4, 0x02 }, > + { 0x05a3, 0x05 }, > + { 0x05a0, 0xc2 }, > + { 0x0207, 0xc4 }, > + > + /*GAIN*/ > + { 0x0204, 0x04 }, > + { 0x0205, 0x00 }, > + { 0x0050, 0x38 }, > + { 0x0051, 0x20 }, > + > + /*out window*/ > + { 0x009a, 0x66 }, > + { 0x0351, 0x00 }, > + { 0x0352, 0x06 }, //out_win_y1 > + { 0x0353, 0x00 }, > + { 0x0354, 0x08 }, //out_win_x1 > + { 0x034c, 0x07 }, > + { 0x034d, 0x80 }, //1920 > + { 0x034e, 0x04 }, > + { 0x034f, 0x38 }, //1080 > + > + /*MIPI*/ > + { 0x0114, 0x03 }, //0:1lane 1:2lane 3:4lane > + { 0x0180, 0x65 }, //[3:0]dphy_mipi_diff > + { 0x0181, 0xf0 }, > + { 0x0185, 0x01 }, > + { 0x0115, 0x30 }, > + { 0x011b, 0x12 }, > + { 0x011c, 0x12 }, > + { 0x0121, 0x02 }, //T_LPX > + { 0x0122, 0x03 }, //T_CLK_HS_PREPARE > + { 0x0123, 0x0c }, //T_CLK_zero > + { 0x0124, 0x00 }, //T_CLK_PRE > + { 0x0125, 0x09 }, //T_CLK_POST > + { 0x0126, 0x06 }, //T_CLK_TRAIL > + { 0x0129, 0x04 }, //T_HS_PREPARE > + { 0x012a, 0x03 }, //T_HS_Zero > + { 0x012b, 0x06 }, //T_HS_TRAIL > + > + { 0x0a73, 0x60 }, > + { 0x0a70, 0x11 }, > + { 0x0313, 0x80 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0aff, 0x00 }, > + { 0x0a70, 0x00 }, > + { 0x00a4, 0x80 }, > + { 0x0316, 0x01 }, > + { 0x0a67, 0x00 }, > + { 0x0084, 0x10 }, > + { 0x0102, 0x09 }, > + > + { GC08A3_TABLE_END, 0x00 } > +}; > + > +static const struct reg_8 mode_table_common[] = { > + { GC08A3_STRAEMING_REG, 0x00 }, > + /*system*/ > + { 0x031c, 0x60 }, > + { 0x0337, 0x04 }, > + { 0x0335, 0x51 }, > + { 0x0336, 0x70 }, > + { 0x0383, 0xbb }, > + { 0x031a, 0x00 }, > + { 0x0321, 0x10 }, > + { 0x0327, 0x03 }, > + { 0x0325, 0x40 }, > + { 0x0326, 0x23 }, > + { 0x0314, 0x11 }, > + { 0x0315, 0xd6 }, > + { 0x0316, 0x01 }, > + { 0x0334, 0x40 }, > + { 0x0324, 0x42 }, > + { 0x031c, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x039a, 0x13 }, > + { 0x0084, 0x30 }, > + { 0x02b3, 0x08 }, > + { 0x0057, 0x0c }, > + { 0x05c3, 0x50 }, > + { 0x0311, 0x90 }, > + { 0x05a0, 0x02 }, > + { 0x0074, 0x0a }, > + { 0x0059, 0x11 }, > + { 0x0070, 0x05 }, > + { 0x0101, 0x00 }, //[1]updown [0]mirror > + > + /*analog*/ > + { 0x0344, 0x00 }, > + { 0x0345, 0x06 }, > + { 0x0346, 0x00 }, > + { 0x0347, 0x04 }, > + { 0x0348, 0x0c }, > + { 0x0349, 0xd0 }, //3280 > + { 0x034a, 0x09 }, > + { 0x034b, 0x9c }, //2460 > + { 0x0202, 0x09 }, > + { 0x0203, 0x04 }, //Exp > + > + { 0x0219, 0x05 }, //[4]FL_depend_exp > + { 0x0226, 0x00 }, //min vb[15:8] > + { 0x0227, 0x28 }, //min vb[7:0] > + { 0x0e0a, 0x00 }, > + { 0x0e0b, 0x00 }, > + { 0x0e24, 0x04 }, > + { 0x0e25, 0x04 }, > + { 0x0e26, 0x00 }, > + { 0x0e27, 0x10 }, > + { 0x0e01, 0x74 }, > + { 0x0e03, 0x47 }, > + { 0x0e04, 0x33 }, > + { 0x0e05, 0x44 }, > + { 0x0e06, 0x44 }, > + { 0x0e0c, 0x1e }, > + { 0x0e17, 0x3a }, > + { 0x0e18, 0x3c }, > + { 0x0e19, 0x40 }, > + { 0x0e1a, 0x42 }, > + { 0x0e28, 0x21 }, > + { 0x0e2b, 0x68 }, > + { 0x0e2c, 0x0d }, > + { 0x0e2d, 0x08 }, > + { 0x0e34, 0xf4 }, > + { 0x0e35, 0x44 }, > + { 0x0e36, 0x07 }, > + { 0x0e38, 0x49 }, > + { 0x0210, 0x13 }, > + { 0x0218, 0x00 }, > + { 0x0241, 0x88 }, > + { 0x0e32, 0x00 }, > + { 0x0e33, 0x18 }, > + { 0x0e42, 0x03 }, > + { 0x0e43, 0x80 }, > + { 0x0e44, 0x04 }, > + { 0x0e45, 0x00 }, > + { 0x0e4f, 0x04 }, > + { 0x057a, 0x20 }, > + { 0x0381, 0x7c }, > + { 0x0382, 0x9b }, > + { 0x0384, 0xfb }, > + { 0x0389, 0x38 }, > + { 0x038a, 0x03 }, > + { 0x0390, 0x6a }, > + { 0x0391, 0x0b }, > + { 0x0392, 0x60 }, > + { 0x0393, 0xc1 }, > + { 0x0396, 0xff }, > + { 0x0398, 0x62 }, > + > + /*cisctl reset*/ > + { 0x031c, 0x80 }, > + { 0x03fe, 0x10 }, //CISCTL_rst > + { 0x03fe, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x03fe, 0x00 }, > + { 0x031c, 0x80 }, > + { 0x03fe, 0x10 }, //CISCTL_rst > + { 0x03fe, 0x00 }, > + { 0x031c, 0x9f }, > + { 0x0360, 0x01 }, > + { 0x0360, 0x00 }, > + { 0x0316, 0x09 }, > + { 0x0a67, 0x80 }, > + { 0x0313, 0x00 }, > + { 0x0a53, 0x0e }, > + { 0x0a65, 0x17 }, > + { 0x0a68, 0xa1 }, > + { 0x0a58, 0x00 }, > + { 0x0ace, 0x0c }, > + { 0x00a4, 0x00 }, > + { 0x00a5, 0x01 }, > + { 0x00a7, 0x09 }, > + { 0x00a8, 0x9c }, > + { 0x00a9, 0x0c }, > + { 0x00aa, 0xd0 }, > + { 0x0a8a, 0x00 }, > + { 0x0a8b, 0xe0 }, > + { 0x0a8c, 0x13 }, > + { 0x0a8d, 0xe8 }, > + { 0x0a90, 0x0a }, > + { 0x0a91, 0x10 }, > + { 0x0a92, 0xf8 }, > + { 0x0a71, 0xf2 }, > + { 0x0a72, 0x12 }, > + { 0x0a73, 0x64 }, > + { 0x0a75, 0x41 }, > + { 0x0a70, 0x07 }, > + { 0x0313, 0x80 }, > + > + /*ISP*/ > + { 0x00a0, 0x01 }, > + { 0x0080, 0xd2 }, > + { 0x0081, 0x3f }, > + { 0x0087, 0x51 }, > + { 0x0089, 0x03 }, > + { 0x009b, 0x40 }, > + { 0x05a0, 0x82 }, > + { 0x05ac, 0x00 }, > + { 0x05ad, 0x01 }, > + { 0x05ae, 0x00 }, > + { 0x0800, 0x0a }, > + { 0x0801, 0x14 }, > + { 0x0802, 0x28 }, > + { 0x0803, 0x34 }, > + { 0x0804, 0x0e }, > + { 0x0805, 0x33 }, > + { 0x0806, 0x03 }, > + { 0x0807, 0x8a }, > + { 0x0808, 0x50 }, > + { 0x0809, 0x00 }, > + { 0x080a, 0x34 }, > + { 0x080b, 0x03 }, > + { 0x080c, 0x26 }, > + { 0x080d, 0x03 }, > + { 0x080e, 0x18 }, > + { 0x080f, 0x03 }, > + { 0x0810, 0x10 }, > + { 0x0811, 0x03 }, > + { 0x0812, 0x00 }, > + { 0x0813, 0x00 }, > + { 0x0814, 0x01 }, > + { 0x0815, 0x00 }, > + { 0x0816, 0x01 }, > + { 0x0817, 0x00 }, > + { 0x0818, 0x00 }, > + { 0x0819, 0x0a }, > + { 0x081a, 0x01 }, > + { 0x081b, 0x6c }, > + { 0x081c, 0x00 }, > + { 0x081d, 0x0b }, > + { 0x081e, 0x02 }, > + { 0x081f, 0x00 }, > + { 0x0820, 0x00 }, > + { 0x0821, 0x0c }, > + { 0x0822, 0x02 }, > + { 0x0823, 0xd9 }, > + { 0x0824, 0x00 }, > + { 0x0825, 0x0d }, > + { 0x0826, 0x03 }, > + { 0x0827, 0xf0 }, > + { 0x0828, 0x00 }, > + { 0x0829, 0x0e }, > + { 0x082a, 0x05 }, > + { 0x082b, 0x94 }, > + { 0x082c, 0x09 }, > + { 0x082d, 0x6e }, > + { 0x082e, 0x07 }, > + { 0x082f, 0xe6 }, > + { 0x0830, 0x10 }, > + { 0x0831, 0x0e }, > + { 0x0832, 0x0b }, > + { 0x0833, 0x2c }, > + { 0x0834, 0x14 }, > + { 0x0835, 0xae }, > + { 0x0836, 0x0f }, > + { 0x0837, 0xc4 }, > + { 0x0838, 0x18 }, > + { 0x0839, 0x0e }, > + { 0x05ac, 0x01 }, > + { 0x059a, 0x00 }, > + { 0x059b, 0x00 }, > + { 0x059c, 0x01 }, > + { 0x0598, 0x00 }, > + { 0x0597, 0x14 }, > + { 0x05ab, 0x09 }, > + { 0x05a4, 0x02 }, > + { 0x05a3, 0x05 }, > + { 0x05a0, 0xc2 }, > + { 0x0207, 0xc4 }, > + > + /*GAIN*/ > + { 0x0208, 0x01 }, > + { 0x0209, 0x72 }, > + { 0x0204, 0x04 }, > + { 0x0205, 0x00 }, > + > + { 0x0040, 0x22 }, > + { 0x0041, 0x20 }, > + { 0x0043, 0x10 }, > + { 0x0044, 0x00 }, > + { 0x0046, 0x08 }, > + { 0x0047, 0xf0 }, > + { 0x0048, 0x0f }, > + { 0x004b, 0x0f }, > + { 0x004c, 0x00 }, > + { 0x0050, 0x5c }, > + { 0x0051, 0x44 }, > + { 0x005b, 0x03 }, > + { 0x00c0, 0x00 }, > + { 0x00c1, 0x80 }, > + { 0x00c2, 0x31 }, > + { 0x00c3, 0x00 }, > + { 0x0460, 0x04 }, > + { 0x0462, 0x08 }, > + { 0x0464, 0x0e }, > + { 0x0466, 0x0a }, > + { 0x0468, 0x12 }, > + { 0x046a, 0x12 }, > + { 0x046c, 0x10 }, > + { 0x046e, 0x0c }, > + { 0x0461, 0x03 }, > + { 0x0463, 0x03 }, > + { 0x0465, 0x03 }, > + { 0x0467, 0x03 }, > + { 0x0469, 0x04 }, > + { 0x046b, 0x04 }, > + { 0x046d, 0x04 }, > + { 0x046f, 0x04 }, > + { 0x0470, 0x04 }, > + { 0x0472, 0x10 }, > + { 0x0474, 0x26 }, > + { 0x0476, 0x38 }, > + { 0x0478, 0x20 }, > + { 0x047a, 0x30 }, > + { 0x047c, 0x38 }, > + { 0x047e, 0x60 }, > + { 0x0471, 0x05 }, > + { 0x0473, 0x05 }, > + { 0x0475, 0x05 }, > + { 0x0477, 0x05 }, > + { 0x0479, 0x04 }, > + { 0x047b, 0x04 }, > + { 0x047d, 0x04 }, > + { 0x047f, 0x04 }, > + > + { GC08A3_TABLE_END, 0x00 } > +}; > + > +static const struct gc08a3_link_freq_config link_freq_configs[] = { > + [GC08A3_LINK_FREQ_336MHZ_CFG] = { > + .reg_list = { > + .num_of_regs = ARRAY_SIZE(mode_table_common), > + .regs = mode_table_common, > + } > + }, > + [GC08A3_LINK_FREQ_207MHZ_CFG] = { > + .reg_list = { > + .num_of_regs = ARRAY_SIZE(mode_table_common), > + .regs = mode_table_common, > + } > + }, > + Extra blank line. > +}; > + > +enum { > + GC08A3_PREV, > + GC08A3_HS, > +}; > + > +/* > + * Declare modes in order, from biggest > + * to smallest height. > + */ > +static const struct gc08a3_mode { > + u8 mode_id; > + u32 width; > + u32 height; > + const struct gc08a3_reg_list reg_list; > + > + u32 hts; /* Horizontal timining size */ > + u32 vts_def; /* Default vertical timining size */ > + u32 vts_min; /* Min vertical timining size */ > + u32 link_freq_index; /* Link frequency needed for this resolution */ > + u32 max_framerate; > + Extra blank line. > +} gc08a3_modes[] = { > + { > + .mode_id = GC08A3_PREV, > + .width = 3264, > + .height = 2448, > + .reg_list = { > + .num_of_regs = ARRAY_SIZE(mode_3264x2448), > + .regs = mode_3264x2448, > + }, > + .link_freq_index = GC08A3_LINK_FREQ_336MHZ_CFG, > + > + .hts = GC08A3_HTS_30FPS, > + .vts_def = GC08A3_VTS_30FPS, > + .vts_min = GC08A3_VTS_30FPS_MIN, > + .max_framerate = 300, > + }, > + { > + .mode_id = GC08A3_HS, > + .width = 1920, > + .height = 1080, > + .reg_list = { > + .num_of_regs = ARRAY_SIZE(mode_1920x1080), > + .regs = mode_1920x1080, > + }, > + .link_freq_index = GC08A3_LINK_FREQ_207MHZ_CFG, > + > + .hts = GC08A3_HTS_60FPS, > + .vts_def = GC08A3_VTS_60FPS, > + .vts_min = GC08A3_VTS_60FPS_MIN, > + .max_framerate = 600, > + }, > +}; > + > +static u64 to_pixel_rate(u32 f_index) > +{ > + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * GC08A3_DATA_LANES; > + > + do_div(pixel_rate, GC08A3_RGB_DEPTH); > + > + return pixel_rate; > +} > + > +static u64 __maybe_unused to_pixels_per_line(u32 hts, u32 f_index) > +{ > + u64 ppl = hts * to_pixel_rate(f_index); > + > + do_div(ppl, GC08A3_SCLK); > + > + return ppl; > +} > + > +static int gc08a3_read_reg(struct gc08a3 *gc08a3, u16 reg, u16 len, u32 *val) > +{ > + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > + struct i2c_msg msgs[2]; > + u8 addr_buf[2]; > + u8 data_buf[4] = { 0 }; > + int ret; > + > + if (len > 4) > + return -EINVAL; > + > + 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 = len; > + msgs[1].buf = &data_buf[4 - len]; > + > + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); > + if (ret != ARRAY_SIZE(msgs)) > + return -EIO; > + > + *val = get_unaligned_be32(data_buf); > + > + return 0; > +} > + > +static int gc08a3_write_reg(struct gc08a3 *gc08a3, u16 reg, u16 len, u32 val) > +{ > + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > + u8 buf[6]; > + > + if (len > 4) > + return -EINVAL; > + > + put_unaligned_be16(reg, buf); > + put_unaligned_be32(val << 8 * (4 - len), buf + 2); > + if (i2c_master_send(client, buf, len + 2) != len + 2) > + return -EIO; > + > + return 0; > +} > + > +static int gc08a3_write_reg_list(struct gc08a3 *gc08a3, > + const struct gc08a3_reg_list *r_list) > +{ > + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > + unsigned int i; > + int ret; > + > + for (i = 0; i < r_list->num_of_regs; i++) { > + if (r_list->regs[i].address == GC08A3_TABLE_WAIT_MS) { > + usleep_range(r_list->regs[i].val * 1000, > + r_list->regs[i].val * 1000 + 500); > + continue; > + } > + > + if (r_list->regs[i].address == GC08A3_TABLE_END) > + break; > + > + ret = gc08a3_write_reg(gc08a3, r_list->regs[i].address, > + GC08A3_REG_VALUE_08BIT, > + r_list->regs[i].val); > + if (ret) { > + dev_err_ratelimited(&client->dev, > + "failed to write reg 0x%4.4x. error = %d", > + r_list->regs[i].address, ret); > + return ret; > + } > + } > + > + return 0; > +} > + > +static int gc08a3_identify_module(struct gc08a3 *gc08a3) > +{ > + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > + u32 val = 0; > + > + gc08a3_read_reg(gc08a3, GC08A3_REG_CHIP_ID, GC08A3_REG_VALUE_16BIT, > + &val); > + > + if (val != GC08A3_CHIP_ID) { > + dev_err(&client->dev, "chip id mismatch: 0x%x!=0x%x", > + GC08A3_CHIP_ID, val); > + return -ENXIO; > + } > + > + dev_info(&client->dev, "sensor_id: 0x%04x\n", val); > + return 0; > +} > + > +static inline struct gc08a3 *to_gc08a3(struct v4l2_subdev *sd) > +{ > + return container_of(sd, struct gc08a3, sd); > +} > + > +static int __maybe_unused gc08a3_power_on(struct device *dev) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > + struct gc08a3 *gc08a3 = to_gc08a3(sd); > + int ret; > + > + dev_info(dev, "--- %s +", __func__); Drop this. Tracing is done with ftrace. Same comment below where applicable. > + > + gpiod_set_value_cansleep(gc08a3->enable_gpio, 0); > + usleep_range(2000, 3000); > + > + ret = regulator_bulk_enable(GC08A3_NUM_SUPPLIES, gc08a3->supplies); > + if (ret < 0) { > + dev_err(gc08a3->dev, "failed to enable regulators: %d\n", ret); > + return ret; > + } > + > + ret = clk_prepare_enable(gc08a3->xclk); > + if (ret < 0) { > + regulator_bulk_disable(GC08A3_NUM_SUPPLIES, gc08a3->supplies); > + dev_err(gc08a3->dev, "clk prepare enable failed\n"); > + return ret; > + } > + > + usleep_range(2000, 3000); > + > + gpiod_set_value_cansleep(gc08a3->enable_gpio, 1); > + usleep_range(12000, 15000); > + > + dev_info(dev, "--- %s -", __func__); > + > + return 0; > +} > + > +static int __maybe_unused gc08a3_power_off(struct device *dev) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > + struct gc08a3 *gc08a3 = to_gc08a3(sd); > + > + dev_info(dev, "--- %s +", __func__); > + > + gpiod_set_value_cansleep(gc08a3->enable_gpio, 0); > + > + clk_disable_unprepare(gc08a3->xclk); > + > + regulator_bulk_disable(GC08A3_NUM_SUPPLIES, gc08a3->supplies); > + usleep_range(10, 20); > + > + dev_info(dev, "--- %s -", __func__); > + > + return 0; > +} > + > +static int gc08a3_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + if (code->index > 0) > + return -EINVAL; > + > + code->code = GC08A3_MBUS_CODE; > + > + return 0; > +} > + > +static int gc08a3_enum_frame_size(struct v4l2_subdev *subdev, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_frame_size_enum *fse) > +{ > + if (fse->code != GC08A3_MBUS_CODE) > + return -EINVAL; > + > + if (fse->index >= ARRAY_SIZE(gc08a3_modes)) > + return -EINVAL; > + > + fse->min_width = gc08a3_modes[fse->index].width; > + fse->max_width = gc08a3_modes[fse->index].width; > + fse->min_height = gc08a3_modes[fse->index].height; > + fse->max_height = gc08a3_modes[fse->index].height; > + > + return 0; > +} > + > +#ifdef CONFIG_VIDEO_ADV_DEBUG > +static int gc08a3_g_register(struct v4l2_subdev *subdev, > + struct v4l2_dbg_register *reg) > +{ > + int ret; > + u32 val; > + struct gc08a3 *gc08a3 = container_of(subdev, struct gc08a3, sd); > + > + ret = gc08a3_read_reg(gc08a3, reg->reg, GC08A3_REG_VALUE_08BIT, &val); > + if (ret < 0) > + return ret; > + > + reg->val = val; > + reg->size = 1; > + > + return 0; > +} > + > +static int gc08a3_s_register(struct v4l2_subdev *subdev, > + const struct v4l2_dbg_register *reg) > +{ > + struct gc08a3 *gc08a3 = container_of(subdev, struct gc08a3, sd); > + > + return gc08a3_write_reg(gc08a3, reg->reg, GC08A3_REG_VALUE_08BIT, > + reg->val & 0xff); > +} > + > +#endif > + > +static const struct v4l2_subdev_core_ops gc08a3_core_ops = { > +#ifdef CONFIG_VIDEO_ADV_DEBUG > + .g_register = gc08a3_g_register, > + .s_register = gc08a3_s_register, > +#endif Drop these too. If you need to access registers directly from userspace for development purpose, you can do so through i2cdev. > +}; > + > +static struct v4l2_mbus_framefmt * > +__gc08a3_get_pad_format(struct gc08a3 *gc08a3, > + struct v4l2_subdev_state *sd_state, unsigned int pad, > + enum v4l2_subdev_format_whence which) > +{ > + switch (which) { > + case V4L2_SUBDEV_FORMAT_TRY: > + return v4l2_subdev_get_try_format(&gc08a3->sd, sd_state, pad); > + case V4L2_SUBDEV_FORMAT_ACTIVE: > + return &gc08a3->fmt; > + default: > + return NULL; > + } > +} > + > +static int gc08a3_get_format(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_format *format) > +{ > + struct gc08a3 *gc08a3 = to_gc08a3(sd); > + > + mutex_lock(&gc08a3->mutex); > + format->format = *__gc08a3_get_pad_format(gc08a3, sd_state, format->pad, > + format->which); > + mutex_unlock(&gc08a3->mutex); > + > + return 0; > +} > + > +static struct v4l2_rect * > +__gc08a3_get_pad_crop(struct gc08a3 *gc08a3, struct v4l2_subdev_state *sd_state, > + unsigned int pad, enum v4l2_subdev_format_whence which) > +{ > + switch (which) { > + case V4L2_SUBDEV_FORMAT_TRY: > + return v4l2_subdev_get_try_crop(&gc08a3->sd, sd_state, pad); > + case V4L2_SUBDEV_FORMAT_ACTIVE: > + return &gc08a3->crop; > + default: > + return NULL; > + } > +} > + > +static int gc08a3_update_cur_mode_controls(struct gc08a3 *gc08a3) > +{ > + s64 exposure_max, h_blank; > + int ret = 0; > + > + ret = __v4l2_ctrl_modify_range(gc08a3->vblank, > + gc08a3->cur_mode->vts_min - gc08a3->cur_mode->height, > + GC08A3_VTS_MAX - gc08a3->cur_mode->height, 1, > + gc08a3->cur_mode->vts_def - gc08a3->cur_mode->height); > + if (ret) > + dev_err(gc08a3->dev, "VB ctrl range update failed\n"); > + > + h_blank = gc08a3->cur_mode->hts - gc08a3->cur_mode->width; > + ret = __v4l2_ctrl_modify_range(gc08a3->hblank, h_blank, h_blank, 1, > + h_blank); > + if (ret) > + dev_err(gc08a3->dev, "HB ctrl range update failed\n"); > + > + exposure_max = gc08a3->cur_mode->vts_def - GC08A3_EXP_MARGIN; > + ret = __v4l2_ctrl_modify_range(gc08a3->exposure, GC08A3_EXP_MIN, > + exposure_max, GC08A3_EXP_STEP, > + exposure_max); > + if (ret) > + dev_err(gc08a3->dev, "exposure ctrl range update failed\n"); > + > + return ret; > +} > + > +static int gc08a3_set_format(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_format *format) > +{ > + struct gc08a3 *gc08a3 = to_gc08a3(sd); > + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > + struct device *dev = &client->dev; > + struct v4l2_mbus_framefmt *__format; > + struct v4l2_rect *__crop; > + const struct gc08a3_mode *mode; > + > + mutex_lock(&gc08a3->mutex); > + > + dev_dbg(dev, "---- %s, format(W x H:%d x %d) +", __func__, > + format->format.width, format->format.height); > + > + __crop = __gc08a3_get_pad_crop(gc08a3, sd_state, format->pad, > + format->which); > + > + mode = v4l2_find_nearest_size(gc08a3_modes, ARRAY_SIZE(gc08a3_modes), > + width, height, format->format.width, > + format->format.height); > + > + dev_dbg(dev, "----nearest mode(W x H:%d x %d)", mode->width, > + mode->height); > + > + __crop->width = mode->width; > + __crop->height = mode->height; > + > + __format = __gc08a3_get_pad_format(gc08a3, sd_state, format->pad, > + format->which); > + __format->width = __crop->width; > + __format->height = __crop->height; > + __format->code = GC08A3_MBUS_CODE; > + __format->field = V4L2_FIELD_NONE; > + __format->colorspace = V4L2_COLORSPACE_SRGB; > + __format->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(__format->colorspace); > + __format->quantization = > + V4L2_MAP_QUANTIZATION_DEFAULT(true, > + __format->colorspace, > + __format->ycbcr_enc); > + __format->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(__format->colorspace); > + > + format->format = *__format; > + > + gc08a3->cur_mode = mode; > + gc08a3_update_cur_mode_controls(gc08a3); > + > + mutex_unlock(&gc08a3->mutex); > + > + return 0; > +} > + > +static int gc08a3_get_selection(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_selection *sel) > +{ > + struct gc08a3 *gc08a3 = to_gc08a3(sd); > + > + switch (sel->target) { > + case V4L2_SEL_TGT_CROP: > + mutex_lock(&gc08a3->mutex); > + sel->r = *__gc08a3_get_pad_crop(gc08a3, sd_state, sel->pad, sel->which); > + mutex_unlock(&gc08a3->mutex); > + break; > + case V4L2_SEL_TGT_CROP_BOUNDS: > + sel->r.top = 0; > + sel->r.left = 0; > + sel->r.width = GC08A3_NATIVE_WIDTH; > + sel->r.height = GC08A3_NATIVE_HEIGHT; > + break; > + case V4L2_SEL_TGT_CROP_DEFAULT: > + if (gc08a3->cur_mode->width == GC08A3_NATIVE_WIDTH) { > + sel->r.top = 0; > + sel->r.left = 0; > + sel->r.width = GC08A3_NATIVE_WIDTH; > + sel->r.height = GC08A3_NATIVE_HEIGHT; > + } else { > + sel->r.top = 0; > + sel->r.left = 0; > + sel->r.width = 1920; > + sel->r.height = 1080; > + } > + break; > + default: > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int gc08a3_entity_init_cfg(struct v4l2_subdev *subdev, > + struct v4l2_subdev_state *sd_state) > +{ > + struct v4l2_subdev_format fmt = {}; > + > + fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : > + V4L2_SUBDEV_FORMAT_ACTIVE; > + fmt.format.width = gc08a3_modes[0].width; > + fmt.format.height = gc08a3_modes[0].height; > + > + gc08a3_set_format(subdev, sd_state, &fmt); > + > + return 0; > +} > + > +static int gc08a3_set_ctrl_hflip(struct gc08a3 *gc08a3, u32 ctrl_val) > +{ > + int ret; > + u32 val; > + > + ret = gc08a3_read_reg(gc08a3, GC08A3_FLIP_REG, GC08A3_REG_VALUE_08BIT, > + &val); > + if (ret) { > + dev_err(gc08a3->dev, "read hflip register failed: %d\n", ret); > + return ret; > + } > + > + val = (ctrl_val) ? (val | GC08A3_FLIP_H_MASK) : > + (val & ~GC08A3_FLIP_H_MASK); > + ret = gc08a3_write_reg(gc08a3, GC08A3_FLIP_REG, GC08A3_REG_VALUE_08BIT, > + val); > + if (ret < 0) > + dev_err(gc08a3->dev, "Error %d\n", ret); > + > + return ret; > +} > + > +static int gc08a3_set_ctrl_vflip(struct gc08a3 *gc08a3, u32 ctrl_val) > +{ > + int ret; > + u32 val; > + > + ret = gc08a3_read_reg(gc08a3, GC08A3_FLIP_REG, GC08A3_REG_VALUE_08BIT, > + &val); > + if (ret) { > + dev_err(gc08a3->dev, "read vflip register failed: %d\n", ret); > + return ret; > + } > + > + val = (ctrl_val) ? (val | GC08A3_FLIP_V_MASK) : > + (val & ~GC08A3_FLIP_V_MASK); > + ret = gc08a3_write_reg(gc08a3, GC08A3_FLIP_REG, GC08A3_REG_VALUE_08BIT, > + val); > + if (ret < 0) > + dev_err(gc08a3->dev, "Error %d\n", ret); > + > + return ret; > +} > + > +static int gc08a3_test_pattern(struct gc08a3 *gc08a3, u32 pattern_menu) > +{ > + int ret = 0; > + u32 pattern = 0; > + > + if (pattern_menu) { > + // write bit16:0x0110 -> color bar > + switch (pattern_menu) { > + case 1: > + pattern = 0x00; > + break; > + case 2: > + case 3: > + case 4: > + case 5: > + case 6: > + pattern = pattern_menu + 2; > + break; > + case 7: > + pattern = 0x10; > + break; > + > + default: > + dev_dbg(gc08a3->dev, "invalid pattern menu!\n"); > + break; > + } > + > + ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_EN, > + GC08A3_REG_VALUE_08BIT, > + GC08A3_TEST_PATTERN_EN); > + ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_IDX, > + GC08A3_REG_VALUE_08BIT, pattern); > + > + } else { > + ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_EN, > + GC08A3_REG_VALUE_08BIT, 0x00); > + } > + > + return ret; > +} > + > +static int gc08a3_set_ctrl(struct v4l2_ctrl *ctrl) > +{ > + struct gc08a3 *gc08a3 = > + container_of(ctrl->handler, struct gc08a3, ctrls); > + int ret = 0; > + s64 exposure_max; > + > + if (ctrl->id == V4L2_CID_VBLANK) { > + /* Update max exposure while meeting expected vblanking */ > + exposure_max = gc08a3->cur_mode->height + ctrl->val - > + GC08A3_EXP_MARGIN; > + __v4l2_ctrl_modify_range(gc08a3->exposure, > + gc08a3->exposure->minimum, > + exposure_max, gc08a3->exposure->step, > + exposure_max); > + } > + > + /* > + * Applying V4L2 control value only happens > + * when power is up for streaming > + */ > + if (!pm_runtime_get_if_in_use(gc08a3->dev)) > + return 0; > + > + switch (ctrl->id) { > + case V4L2_CID_EXPOSURE: > + ret = gc08a3_write_reg(gc08a3, GC08A3_EXP_REG, > + GC08A3_REG_VALUE_16BIT, ctrl->val); > + break; > + > + case V4L2_CID_ANALOGUE_GAIN: > + ret = gc08a3_write_reg(gc08a3, GC08A3_AGAIN_REG, > + GC08A3_REG_VALUE_16BIT, ctrl->val); > + break; > + > + case V4L2_CID_VBLANK: > + dev_info(gc08a3->dev, "V4L2_CID_VBLANK:height:%d, V_BLNK:%d\n", > + gc08a3->cur_mode->height, ctrl->val); I would drop this too, or at the very least convert it to dev_dbg(). There should be no message but debug messages printed by the driver to the kernel log during normal operation. > + ret = gc08a3_write_reg(gc08a3, GC08A3_FL_REG, > + GC08A3_REG_VALUE_16BIT, > + gc08a3->cur_mode->height + ctrl->val); > + break; > + > + case V4L2_CID_HFLIP: > + gc08a3_set_ctrl_hflip(gc08a3, ctrl->val); > + break; > + > + case V4L2_CID_VFLIP: > + gc08a3_set_ctrl_vflip(gc08a3, ctrl->val); > + break; > + > + case V4L2_CID_TEST_PATTERN: > + ret = gc08a3_test_pattern(gc08a3, ctrl->val); > + break; > + > + default: > + break; > + } > + > + pm_runtime_put(gc08a3->dev); > + > + return ret; > +} > + > +static int get_volatile_ext_ctrl(struct v4l2_ctrl *ctrl) > +{ > + int ret = 0; > + struct gc08a3 *gc08a3 = > + container_of(ctrl->handler, struct gc08a3, ctrls); > + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > + > + dev_dbg(&client->dev, "---- %s, CMD:0x%x\n", __func__, ctrl->id); > + switch (ctrl->id) { > + default: > + dev_info(&client->dev, "[gc08a3] %s, un-support CMD: 0x%x\n", > + __func__, ctrl->id); > + break; > + } > + > + return ret; > +} > + > +static int gc08a3_g_volatile_ctrl(struct v4l2_ctrl *ctrl) > +{ > + int ret = 0; > + > + switch (ctrl->id) { > + default: > + ret = get_volatile_ext_ctrl(ctrl); > + break; > + } > + > + return ret; > +} > + > +static int try_ext_ctrl(struct v4l2_ctrl *ctrl) > +{ > + int ret = 0; > + struct gc08a3 *gc08a3 = > + container_of(ctrl->handler, struct gc08a3, ctrls); > + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > + > + switch (ctrl->id) { > + default: > + dev_dbg(&client->dev, > + "[gc08a3] un-handle CMD: 0x%x (%s : %d)\n", ctrl->id, > + __func__, __LINE__); > + break; > + } > + > + return ret; > +} > + > +static int gc08a3_try_ctrl(struct v4l2_ctrl *ctrl) > +{ > + int ret = 0; > + > + switch (ctrl->id) { > + case V4L2_CID_EXPOSURE: > + case V4L2_CID_ANALOGUE_GAIN: > + case V4L2_CID_VBLANK: > + case V4L2_CID_HFLIP: > + case V4L2_CID_VFLIP: > + case V4L2_CID_TEST_PATTERN: > + return 0; > + > + default: > + ret = try_ext_ctrl(ctrl); > + break; > + } > + > + return ret; > +} > + > +static const struct v4l2_ctrl_ops gc08a3_ctrl_ops = { > + .g_volatile_ctrl = gc08a3_g_volatile_ctrl, > + .try_ctrl = gc08a3_try_ctrl, Drop the .g_volatile_ctrl() and .try_ctrl() implementations, they don't do anything, and the operations are optional. > + .s_ctrl = gc08a3_set_ctrl, > +}; > + > +static int gc08a3_start_streaming(struct gc08a3 *gc08a3) > +{ > + const struct gc08a3_mode *mode; > + const struct gc08a3_reg_list *reg_list; > + int link_freq_index; > + int ret; > + > + dev_info(gc08a3->dev, "%s ++\n", __func__); > + > + mutex_lock(&gc08a3->mutex); > + > + link_freq_index = gc08a3->cur_mode->link_freq_index; > + dev_info(gc08a3->dev, "----link_freq_index = %d ", link_freq_index); > + > + reg_list = &link_freq_configs[link_freq_index].reg_list; > + ret = gc08a3_write_reg_list(gc08a3, reg_list); > + if (ret) { > + dev_err(gc08a3->dev, "could not sent common table %d\n", ret); > + goto error; > + } > + > + mode = gc08a3->cur_mode; > + dev_info(gc08a3->dev, "----write regtbl: mode(id:%d, WxH:%dx%d)", > + mode->mode_id, mode->width, mode->height); > + reg_list = &mode->reg_list; > + > + ret = gc08a3_write_reg_list(gc08a3, reg_list); > + if (ret < 0) { > + dev_err(gc08a3->dev, "could not sent mode table %d\n", ret); > + goto error; > + } > + ret = __v4l2_ctrl_handler_setup(&gc08a3->ctrls); > + if (ret < 0) { > + dev_err(gc08a3->dev, "could not sync v4l2 controls\n"); > + goto error; > + } > + > + ret = gc08a3_write_reg(gc08a3, GC08A3_STRAEMING_REG, > + GC08A3_REG_VALUE_08BIT, 1); > + if (ret < 0) { > + dev_err(gc08a3->dev, "write STRAEMING_REG failed: %d\n", ret); > + goto error; > + } > + > + mutex_unlock(&gc08a3->mutex); > + > + dev_info(gc08a3->dev, "%s --\n", __func__); > + > + return 0; > + > +error: > + mutex_unlock(&gc08a3->mutex); > + return ret; > +} > + > +static int gc08a3_stop_streaming(struct gc08a3 *gc08a3) > +{ > + int ret; > + > + dev_info(gc08a3->dev, "%s ++\n", __func__); > + > + ret = gc08a3_write_reg(gc08a3, GC08A3_STRAEMING_REG, > + GC08A3_REG_VALUE_08BIT, 0); > + if (ret < 0) > + dev_err(gc08a3->dev, "could not sent stop streaming %d\n", ret); > + > + dev_info(gc08a3->dev, "%s --\n", __func__); > + > + return ret; > +} > + > +static int gc08a3_s_stream(struct v4l2_subdev *subdev, int enable) > +{ > + struct gc08a3 *gc08a3 = to_gc08a3(subdev); > + int ret; > + > + if (gc08a3->streaming == enable) > + return 0; > + > + if (enable) { > + ret = pm_runtime_resume_and_get(gc08a3->dev); > + if (ret < 0) > + return ret; > + > + ret = gc08a3_start_streaming(gc08a3); > + if (ret < 0) > + goto err_rpm_put; > + } else { > + ret = gc08a3_stop_streaming(gc08a3); > + if (ret < 0) > + goto err_rpm_put; > + pm_runtime_put(gc08a3->dev); > + } > + > + gc08a3->streaming = enable; > + return 0; > + > +err_rpm_put: > + pm_runtime_put(gc08a3->dev); > + return ret; > +} > + > +static int gc08a3_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad, > + struct v4l2_mbus_config *config) > +{ > + config->type = V4L2_MBUS_CSI2_DPHY; > + config->bus.mipi_csi2.num_data_lanes = 4; > + config->bus.mipi_csi2.flags = 0; > + return 0; > +} > + > +static int gc08a3_g_frame_interval(struct v4l2_subdev *subdev, > + struct v4l2_subdev_frame_interval *fival) > +{ > + struct gc08a3 *gc08a3 = to_gc08a3(subdev); > + > + fival->interval.numerator = 1; > + fival->interval.denominator = gc08a3->cur_mode->max_framerate / 10; > + > + return 0; > +} > + > +static int > +gc08a3_enum_frame_interval(struct v4l2_subdev *subdev, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_frame_interval_enum *fie) > +{ > + const struct gc08a3_mode *mode; > + > + if (fie->index != 0) > + return -EINVAL; > + > + mode = v4l2_find_nearest_size(gc08a3_modes, ARRAY_SIZE(gc08a3_modes), > + width, height, fie->width, fie->height); > + > + fie->code = GC08A3_MBUS_CODE; > + fie->width = mode->width; > + fie->height = mode->height; > + fie->interval.numerator = 1; > + fie->interval.denominator = mode->max_framerate / 10; > + > + return 0; > +} > + > +static const struct v4l2_subdev_video_ops gc08a3_video_ops = { > + .s_stream = gc08a3_s_stream, > + .g_frame_interval = gc08a3_g_frame_interval, > + .s_frame_interval = gc08a3_g_frame_interval, > +}; > + > +static const struct v4l2_subdev_pad_ops gc08a3_subdev_pad_ops = { > + .enum_mbus_code = gc08a3_enum_mbus_code, > + .enum_frame_size = gc08a3_enum_frame_size, > + .enum_frame_interval = gc08a3_enum_frame_interval, > + .get_fmt = gc08a3_get_format, > + .set_fmt = gc08a3_set_format, > + .get_selection = gc08a3_get_selection, > + .init_cfg = gc08a3_entity_init_cfg, > + .get_mbus_config = gc08a3_g_mbus_config, > +}; > + > +static const struct v4l2_subdev_ops gc08a3_subdev_ops = { > + .core = &gc08a3_core_ops, > + .video = &gc08a3_video_ops, > + .pad = &gc08a3_subdev_pad_ops, > +}; > + > +static const struct regmap_config sensor_regmap_config = { > + .reg_bits = 16, > + .val_bits = 8, > + .cache_type = REGCACHE_RBTREE, > +}; > + > +static int gc08a3_get_regulators(struct device *dev, struct gc08a3 *gc08a3) > +{ > + unsigned int i; > + > + for (i = 0; i < GC08A3_NUM_SUPPLIES; i++) > + gc08a3->supplies[i].supply = gc08a3_supply_name[i]; > + > + return devm_regulator_bulk_get(dev, GC08A3_NUM_SUPPLIES, > + gc08a3->supplies); > +} > + > +static int gc08a3_parse_fwnode(struct device *dev) > +{ > + struct fwnode_handle *endpoint; > + struct v4l2_fwnode_endpoint bus_cfg = { > + .bus_type = V4L2_MBUS_CSI2_DPHY, > + }; > + unsigned int i, j; > + int ret; > + > + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); > + if (!endpoint) { > + dev_err(dev, "endpoint node not found\n"); > + return -EINVAL; > + } > + > + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); > + if (ret) { > + dev_err(dev, "parsing endpoint node failed\n"); > + goto done; > + } > + > + if (!bus_cfg.nr_of_link_frequencies) { > + dev_err(dev, "no link frequencies defined"); > + ret = -EINVAL; > + goto done; > + } > + > + 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, please check DT", > + link_freq_menu_items[i]); > + ret = -EINVAL; > + goto done; > + } > + } > + > +done: > + v4l2_fwnode_endpoint_free(&bus_cfg); > + fwnode_handle_put(endpoint); > + return ret; > +} > + > +static int __maybe_unused gc08a3_suspend(struct device *dev) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > + struct gc08a3 *gc08a3 = to_gc08a3(sd); > + > + if (gc08a3->streaming) > + gc08a3_stop_streaming(gc08a3); > + > + return 0; > +} > + > +static int __maybe_unused gc08a3_resume(struct device *dev) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > + struct gc08a3 *gc08a3 = to_gc08a3(sd); > + int ret; > + > + if (gc08a3->streaming) { > + ret = gc08a3_start_streaming(gc08a3); > + if (ret) > + goto error; > + } > + > + return 0; > + > +error: > + gc08a3_stop_streaming(gc08a3); > + gc08a3->streaming = 0; > + return ret; > +} No need for system suspend/resume operations. Please read Documentation/driver-api/media/camera-sensor.rst in the master branch of git://linuxtv.org/media_stage.git for an explanation. > + > +static int gc08a3_init_controls(struct gc08a3 *gc08a3) > +{ > + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > + const struct v4l2_ctrl_ops *ops = &gc08a3_ctrl_ops; > + struct v4l2_fwnode_device_properties props; > + struct v4l2_ctrl_handler *ctrl_hdlr; > + s64 exposure_max, h_blank; > + int ret; > + > + ctrl_hdlr = &gc08a3->ctrls; > + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); > + if (ret) > + return ret; > + > + ctrl_hdlr->lock = &gc08a3->mutex; > + > + gc08a3->link_freq = > + v4l2_ctrl_new_int_menu(ctrl_hdlr, > + &gc08a3_ctrl_ops, > + V4L2_CID_LINK_FREQ, > + ARRAY_SIZE(link_freq_menu_items) - 1, > + 0, link_freq_menu_items); > + if (gc08a3->link_freq) > + gc08a3->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; > + > + gc08a3->pixel_rate = > + v4l2_ctrl_new_std(ctrl_hdlr, > + &gc08a3_ctrl_ops, > + V4L2_CID_PIXEL_RATE, 0, > + to_pixel_rate(GC08A3_LINK_FREQ_336MHZ_CFG), 1, > + to_pixel_rate(GC08A3_LINK_FREQ_336MHZ_CFG)); > + > + gc08a3->vblank = > + v4l2_ctrl_new_std(ctrl_hdlr, > + &gc08a3_ctrl_ops, V4L2_CID_VBLANK, > + gc08a3->cur_mode->vts_min - gc08a3->cur_mode->height, > + GC08A3_VTS_MAX - gc08a3->cur_mode->height, 1, > + gc08a3->cur_mode->vts_def - gc08a3->cur_mode->height); > + > + h_blank = gc08a3->cur_mode->hts - gc08a3->cur_mode->width; > + gc08a3->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, > + V4L2_CID_HBLANK, h_blank, h_blank, 1, > + h_blank); > + if (gc08a3->hblank) > + gc08a3->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; > + > + v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, > + V4L2_CID_ANALOGUE_GAIN, GC08A3_AGAIN_MIN, > + GC08A3_AGAIN_MAX, GC08A3_AGAIN_STEP, > + GC08A3_AGAIN_MIN); > + > + exposure_max = gc08a3->cur_mode->vts_def - GC08A3_EXP_MARGIN; > + gc08a3->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, > + V4L2_CID_EXPOSURE, GC08A3_EXP_MIN, > + exposure_max, GC08A3_EXP_STEP, > + exposure_max); > + > + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &gc08a3_ctrl_ops, > + V4L2_CID_TEST_PATTERN, > + ARRAY_SIZE(gc08a3_test_pattern_menu) - 1, > + 0, 0, gc08a3_test_pattern_menu); > + > + v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, V4L2_CID_HFLIP, 0, > + 1, 1, 0); > + > + v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, V4L2_CID_VFLIP, 0, > + 1, 1, 0); > + > + /* register properties to fwnode (e.g. rotation, orientation) */ > + ret = v4l2_fwnode_device_parse(&client->dev, &props); > + if (ret) > + goto error_ctrls; > + > + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, ops, &props); > + if (ret) > + goto error_ctrls; > + > + if (ctrl_hdlr->error) { > + ret = ctrl_hdlr->error; > + goto error_ctrls; > + } > + > + gc08a3->sd.ctrl_handler = ctrl_hdlr; > + > + return 0; > + > +error_ctrls: > + v4l2_ctrl_handler_free(ctrl_hdlr); > + > + return ret; > +} > + > +static int gc08a3_probe(struct i2c_client *client) > +{ > + struct device *dev = &client->dev; > + struct gc08a3 *gc08a3; > + int ret; > + > + dev_info(dev, "--- %s +", __func__); > + > + ret = gc08a3_parse_fwnode(dev); > + if (ret) > + return ret; > + > + gc08a3 = devm_kzalloc(dev, sizeof(*gc08a3), GFP_KERNEL); > + if (!gc08a3) > + return -ENOMEM; > + > + gc08a3->dev = dev; > + > + gc08a3->xclk = devm_clk_get(dev, NULL); > + if (IS_ERR(gc08a3->xclk)) { > + dev_err(dev, "could not get xclk\n"); > + return PTR_ERR(gc08a3->xclk); > + } > + > + ret = clk_set_rate(gc08a3->xclk, GC08A3_DEFAULT_CLK_FREQ); > + if (ret) { > + dev_err(dev, "could not set xclk frequency\n"); > + return ret; > + } > + > + ret = gc08a3_get_regulators(dev, gc08a3); > + if (ret < 0) { > + dev_err(dev, "cannot get regulators\n"); > + return ret; > + } > + > + gc08a3->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); > + if (IS_ERR(gc08a3->enable_gpio)) { > + dev_err(dev, "cannot get enable gpio\n"); > + return PTR_ERR(gc08a3->enable_gpio); > + } > + > + gc08a3->regmap = devm_regmap_init_i2c(client, &sensor_regmap_config); > + if (IS_ERR(gc08a3->regmap)) { > + dev_err(dev, "regmap init failed\n"); > + return PTR_ERR(gc08a3->regmap); > + } > + > + v4l2_i2c_subdev_init(&gc08a3->sd, client, &gc08a3_subdev_ops); > + > + gc08a3_power_on(gc08a3->dev); > + > + ret = gc08a3_identify_module(gc08a3); > + if (ret) { > + dev_err(&client->dev, "failed to find sensor: %d\n", ret); > + gc08a3_power_off(gc08a3->dev); > + return ret; > + } > + > + mutex_init(&gc08a3->mutex); > + gc08a3->cur_mode = &gc08a3_modes[0]; > + > + ret = gc08a3_init_controls(gc08a3); > + if (ret) { > + dev_err(&client->dev, "failed to init controls: %d", ret); > + goto free_ctrl; > + } > + > + gc08a3->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > + gc08a3->pad.flags = MEDIA_PAD_FL_SOURCE; > + gc08a3->sd.dev = &client->dev; > + gc08a3->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; > + > + dev_dbg(&client->dev, "gc08a3->sd.name: %s, dev->of_node->name: %s\n", > + gc08a3->sd.name, dev->of_node->name); > + if (V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2 < > + strlen(dev->of_node->name)) { > + dev_err(&client->dev, > + "the string length of (sd.name + of_node->name) is too long.\n"); > + return -EINVAL; > + } > + strncat(gc08a3->sd.name, " ", 1); > + strncat(gc08a3->sd.name, dev->of_node->name, > + V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2); > + dev_dbg(&client->dev, "after: gc08a3->sd.name: %s\n", gc08a3->sd.name); > + > + ret = media_entity_pads_init(&gc08a3->sd.entity, 1, &gc08a3->pad); > + if (ret < 0) { > + dev_err(dev, "could not register media entity\n"); > + goto free_ctrl; > + } > + > + ret = v4l2_async_register_subdev_sensor(&gc08a3->sd); > + if (ret < 0) { > + dev_err(dev, "could not register v4l2 device\n"); > + goto free_entity; > + } > + > + pm_runtime_set_active(gc08a3->dev); > + pm_runtime_enable(gc08a3->dev); > + pm_runtime_idle(gc08a3->dev); Please see the imx219 driver for an example of how to correctly implement runtime PM support. Please also use PM runtime autosuspend. > + > + dev_info(dev, "--- %s -", __func__); > + > + return 0; > + > +free_entity: > + media_entity_cleanup(&gc08a3->sd.entity); > +free_ctrl: > + mutex_destroy(&gc08a3->mutex); > + v4l2_ctrl_handler_free(&gc08a3->ctrls); > + pm_runtime_disable(gc08a3->dev); > + > + return ret; > +} > + > +static void gc08a3_remove(struct i2c_client *client) > +{ > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > + struct gc08a3 *gc08a3 = to_gc08a3(sd); > + > + v4l2_async_unregister_subdev(&gc08a3->sd); > + media_entity_cleanup(&gc08a3->sd.entity); > + v4l2_ctrl_handler_free(&gc08a3->ctrls); > + > + pm_runtime_disable(&client->dev); > + pm_runtime_set_suspended(&client->dev); > + > + mutex_destroy(&gc08a3->mutex); > +} > + > +static const struct of_device_id gc08a3_of_match[] = { > + { .compatible = "GalaxyCore,gc08a3" }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, gc08a3_of_match); > + > +static const struct dev_pm_ops gc08a3_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(gc08a3_suspend, gc08a3_resume) > + SET_RUNTIME_PM_OPS(gc08a3_power_off, gc08a3_power_on, NULL) > +}; > + > +static struct i2c_driver gc08a3_i2c_driver = { > + .driver = { > + .of_match_table = gc08a3_of_match, > + .pm = &gc08a3_pm_ops, > + .name = "gc08a3", > + }, > + .probe_new = gc08a3_probe, > + .remove = gc08a3_remove, > +}; > + > +module_i2c_driver(gc08a3_i2c_driver); > + > +MODULE_DESCRIPTION("GalaxyCore gc08a3 Camera driver"); > +MODULE_AUTHOR("Zhi Mao <zhi.mao@mediatek.com>"); > +MODULE_LICENSE("GPL");
On Thu, 2023-12-07 at 13:06 +0200, Laurent Pinchart wrote: > > External email : Please do not click links or open attachments until > you have verified the sender or the content. > Hi Zhi Mao, > > Thank you for the patch. > > This is a partial review, as lots of things will change already for > v2. > I'll review the next version in more details. > > First of all, please rebase the driver on top of the master branch of > git://linuxtv.org/media_stage.git. Quite a few in-kernel APIs have > changed. > > On Thu, Nov 23, 2023 at 07:51:04PM +0800, Zhi Mao wrote: > > Add a V4L2 sub-device driver for Galaxycore GC08A3 image sensor. > > > > Signed-off-by: Zhi Mao <zhi.mao@mediatek.com> > > --- > > drivers/media/i2c/Kconfig | 14 + > > drivers/media/i2c/Makefile | 1 + > > drivers/media/i2c/gc08a3.c | 2046 > ++++++++++++++++++++++++++++++++++++ > > 3 files changed, 2061 insertions(+) > > create mode 100644 drivers/media/i2c/gc08a3.c > > > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > > index 59ee0ca2c978..2e37be537690 100644 > > --- a/drivers/media/i2c/Kconfig > > +++ b/drivers/media/i2c/Kconfig > > @@ -1451,6 +1451,20 @@ config VIDEO_THS7303 > > To compile this driver as a module, choose M here: the > > module will be called ths7303. > > > > +config VIDEO_GC08A3 > > Alphabetical order please. [mtk]: fixed in patch:v3 > > > +tristate "GalaxyCore gc08a3 sensor support" > > +depends on GPIOLIB && I2C && VIDEO_DEV > > +select V4L2_FWNODE > > +select MEDIA_CONTROLLER > > +select VIDEO_V4L2_SUBDEV_API > > You can drop all of the above except > > depends on GPIOLIB > > The dependencies on I2C and VIDEO_DEV and the selection of > V4L2_FWNODE, > MEDIA_CONTROLLER and VIDEIO_V4L2_SUBDEV_API are now handled > automatically for all drivers in the VIDEO_CAMERA_SENSOR menu. [mtk]: fixed in patch:v3 > > > +select REGMAP_I2C > > +help > > + This is a Video4Linux2 sensor driver for the GalaxyCore gc08a3 > > + camera. > > + > > + To compile this driver as a module, choose M here: the > > + module will be called gc08a3. > > + > > endmenu > > > > # > > diff --git a/drivers/media/i2c/Makefile > b/drivers/media/i2c/Makefile > > index f5010f80a21f..ec40dbd75e7a 100644 > > --- a/drivers/media/i2c/Makefile > > +++ b/drivers/media/i2c/Makefile > > @@ -36,6 +36,7 @@ obj-$(CONFIG_VIDEO_DW9719) += dw9719.o > > obj-$(CONFIG_VIDEO_DW9768) += dw9768.o > > obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o > > obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ > > +obj-$(CONFIG_VIDEO_GC08A3) += gc08a3.o > > obj-$(CONFIG_VIDEO_HI556) += hi556.o > > obj-$(CONFIG_VIDEO_HI846) += hi846.o > > obj-$(CONFIG_VIDEO_HI847) += hi847.o > > diff --git a/drivers/media/i2c/gc08a3.c > b/drivers/media/i2c/gc08a3.c > > new file mode 100644 > > index 000000000000..d3cf62b65c2e > > --- /dev/null > > +++ b/drivers/media/i2c/gc08a3.c > > @@ -0,0 +1,2046 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * gc08a3.c - gc08a3 sensor driver > > + * > > + * Copyright 2023 Mediatek > > + * > > + * Zhi Mao <zhi.mao@mediatek.com> > > + */ > > + > > +#include <asm/unaligned.h> > > +#include <linux/clk.h> > > +#include <linux/delay.h> > > +#include <linux/gpio/consumer.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/pm_runtime.h> > > +#include <linux/regmap.h> > > +#include <linux/regulator/consumer.h> > > +#include <media/media-entity.h> > > +#include <media/v4l2-ctrls.h> > > +#include <media/v4l2-fwnode.h> > > +#include <media/v4l2-subdev.h> > > + > > +//========================================= > > V4L2 uses C-style comments. [mtk]: fixed in patch:v3 > > > +#define GC08A3_REG_VALUE_08BIT 1 > > +#define GC08A3_REG_VALUE_16BIT 2 > > +#define GC08A3_REG_VALUE_24BIT 3 > > Please use the v4l2-cci helpers for register access. [mtk]: fixed in patch:v3 > > > + > > +#define GC08A3_REG_CHIP_ID 0x03f0 > > +#define GC08A3_CHIP_ID 0x08a3 > > + > > +#define GC08A3_NATIVE_WIDTH3264 > > +#define GC08A3_NATIVE_HEIGHT2448 > > + > > +#define GC08A3_REG_TEST_PATTERN_EN 0x008c > > +#define GC08A3_REG_TEST_PATTERN_IDX 0x008d > > +#define GC08A3_TEST_PATTERN_EN 0x01 > > + > > +#define GC08A3_STRAEMING_REG 0x0100 > > + > > +#define GC08A3_SCLK 280000000LL > > + > > +#define GC08A3_DEFAULT_CLK_FREQ 24000000 > > +#define GC08A3_FPS 30 > > +#define GC08A3_MBUS_CODE MEDIA_BUS_FMT_SRGGB10_1X10 > > +#define GC08A3_DATA_LANES 4 > > + > > +//for 1920*1080 > > +#define GC08A3_LINK_FREQ_207MHZ 207000000ULL > > +//for 3264*2448 > > +#define GC08A3_LINK_FREQ_336MHZ 336000000ULL > > Macros don't help readibility, simply use the numerical values in the > link_freq_menu_items array below. [mtk]: Using magic number may not improve code readability, and I find some other sensor driver also use macros in link_freq_menu_items. Can we keep this useage in gc08a driver? > > > + > > +#define GC08A3_RGB_DEPTH 10 > > + > > +//frame length > > +#define GC08A3_FL_REG 0x0340 > > +#define GC08A3_VTS_30FPS 2548 > > +#define GC08A3_VTS_30FPS_MIN 2548 > > +#define GC08A3_VTS_60FPS 1276 > > +#define GC08A3_VTS_60FPS_MIN 1276 > > +#define GC08A3_VTS_MAX 0xfff0 > > +#define GC08A3_FL_MARGIN 48 > > + > > +// line length > > +#define GC08A3_LL_REG 0x0342 > > +#define GC08A3_HTS_30FPS 3640 > > +#define GC08A3_HTS_60FPS 3640 > > + > > +#define GC08A3_EXP_REG 0x0202 > > +#define GC08A3_EXP_MARGIN 16 > > +#define GC08A3_EXP_MIN 4 > > +#define GC08A3_EXP_STEP 1 > > + > > +#define GC08A3_FLIP_REG 0x0101 > > +#define GC08A3_FLIP_H_MASK 0x1 > > +#define GC08A3_FLIP_V_MASK 0x2 > > + > > +#define GC08A3_AGAIN_REG 0x0204 > > +#define GC08A3_AGAIN_MIN 1024 > > +#define GC08A3_AGAIN_MAX (1024 * 16) > > +#define GC08A3_AGAIN_STEP 1 > > + > > +#define GC08A3_DGAIN_REG 0x020e > > +#define GC08A3_DGAIN_MIN 1024 > > +#define GC08A3_DGAIN_MAX 1024 > > +#define GC08A3_DGAIN_STEP 1 > > +//============================================================ > > + > > +//============================================================ > > +static const char *const gc08a3_test_pattern_menu[] = { > > +"No Pattern", "Solid Black", "Solid White", "Solid Red", > > +"Solid Green", "Solid Blue", "Solid Yellow", "Colour Bar", > > +}; > > + > > +enum { > > +GC08A3_LINK_FREQ_336MHZ_CFG, > > +GC08A3_LINK_FREQ_207MHZ_CFG, > > +}; > > + > > +static const s64 link_freq_menu_items[] = { > > +GC08A3_LINK_FREQ_336MHZ, > > +GC08A3_LINK_FREQ_207MHZ, > > +}; > > + > > +static const char *const gc08a3_supply_name[] = { > > +"avdd", > > +"dvdd", > > +"dovdd", > > +}; > > + > > +#define GC08A3_NUM_SUPPLIES ARRAY_SIZE(gc08a3_supply_name) > > + > > +struct gc08a3 { > > +struct device *dev; > > +struct v4l2_subdev sd; > > +struct media_pad pad; > > +struct v4l2_mbus_framefmt fmt; > > Please use the subdev active state to store the active format. See > commit e8a5b1df000e ("media: i2c: imx219: Use subdev active state") > as > an example of how to do so. There's also documentation in > Documentation/driver-api/media/v4l2-subdev.rst (please read the most > up-to-date version in the master branch of the media_stage tree). > [mtk]: fixed in patch:v3 > > +struct i2c_client *client; > > + > > +struct v4l2_rect crop; > > This should be stored in the subdev active state too. > [mtk]: fixed in patch:v3 > > + > > +struct clk *xclk; > > +struct regulator_bulk_data supplies[GC08A3_NUM_SUPPLIES]; > > +struct gpio_desc *enable_gpio; > > + > > +struct regmap *regmap; > > + > > +struct v4l2_ctrl_handler ctrls; > > +struct v4l2_ctrl *pixel_rate; > > +struct v4l2_ctrl *link_freq; > > +struct v4l2_ctrl *exposure; > > +struct v4l2_ctrl *vblank; > > +struct v4l2_ctrl *hblank; > > + > > +/* > > + * Serialize control access, get/set format, get selection > > + * and start streaming. > > + */ > > +struct mutex mutex; > > The mutex won't be needed anymore once you use the subdev active > state. > [mtk]: fixed in patch:v3 > > + > > +bool streaming; > > + > > +/* Current mode */ > > +const struct gc08a3_mode *cur_mode; > > +}; > > + > > +struct reg_8 { > > +u16 address; > > +u8 val; > > +}; > > + > > +struct gc08a3_reg_list { > > +u32 num_of_regs; > > +const struct reg_8 *regs; > > +}; > > + > > +struct gc08a3_link_freq_config { > > +const struct gc08a3_reg_list reg_list; > > +}; > > + > > +enum { > > +GC08A3_TABLE_WAIT_MS = 0, > > +GC08A3_TABLE_END, > > +}; > > + > > +/*From gc08a3_mode_tbls.h*/ > > +static const struct reg_8 mode_3264x2448[] = { > > +/*system*/ > > +{ 0x031c, 0x60 }, > > +{ 0x0337, 0x04 }, > > +{ 0x0335, 0x51 }, > > +{ 0x0336, 0x70 }, > > +{ 0x0383, 0xbb }, > > +{ 0x031a, 0x00 }, > > +{ 0x0321, 0x10 }, > > +{ 0x0327, 0x03 }, > > +{ 0x0325, 0x40 }, > > +{ 0x0326, 0x23 }, > > +{ 0x0314, 0x11 }, > > +{ 0x0315, 0xd6 }, > > +{ 0x0316, 0x01 }, > > +{ 0x0334, 0x40 }, > > +{ 0x0324, 0x42 }, > > +{ 0x031c, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x0344, 0x00 }, > > +{ 0x0345, 0x06 }, > > +{ 0x0346, 0x00 }, > > +{ 0x0347, 0x04 }, > > +{ 0x0348, 0x0c }, > > +{ 0x0349, 0xd0 }, //3280 > > +{ 0x034a, 0x09 }, > > +{ 0x034b, 0x9c }, //2460 > > +{ 0x0202, 0x09 }, > > +{ 0x0203, 0x04 }, //Exp > > +{ 0x0340, 0x09 }, > > +{ 0x0341, 0xf4 }, //FL > > +{ 0x0342, 0x07 }, > > +{ 0x0343, 0x1c }, //LineLength > > + > > +{ 0x0226, 0x00 }, //min vb[15:8] > > +{ 0x0227, 0x28 }, //min vb[7:0] > > +{ 0x0e38, 0x49 }, > > +{ 0x0210, 0x13 }, > > +{ 0x0218, 0x00 }, > > +{ 0x0241, 0x88 }, > > +{ 0x0392, 0x60 }, > > + > > +/*ISP*/ > > +{ 0x031c, 0x80 }, > > +{ 0x03fe, 0x10 }, //CISCTL_rst > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x80 }, > > +{ 0x03fe, 0x10 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x00a2, 0x00 }, > > +{ 0x00a3, 0x00 }, > > +{ 0x00ab, 0x00 }, > > +{ 0x00ac, 0x00 }, > > +{ 0x05a0, 0x82 }, > > +{ 0x05ac, 0x00 }, > > +{ 0x05ad, 0x01 }, > > +{ 0x05ae, 0x00 }, > > +{ 0x0800, 0x0a }, > > +{ 0x0801, 0x14 }, > > +{ 0x0802, 0x28 }, > > +{ 0x0803, 0x34 }, > > +{ 0x0804, 0x0e }, > > +{ 0x0805, 0x33 }, > > +{ 0x0806, 0x03 }, > > +{ 0x0807, 0x8a }, > > +{ 0x0808, 0x50 }, > > +{ 0x0809, 0x00 }, > > +{ 0x080a, 0x34 }, > > +{ 0x080b, 0x03 }, > > +{ 0x080c, 0x26 }, > > +{ 0x080d, 0x03 }, > > +{ 0x080e, 0x18 }, > > +{ 0x080f, 0x03 }, > > +{ 0x0810, 0x10 }, > > +{ 0x0811, 0x03 }, > > +{ 0x0812, 0x00 }, > > +{ 0x0813, 0x00 }, > > +{ 0x0814, 0x01 }, > > +{ 0x0815, 0x00 }, > > +{ 0x0816, 0x01 }, > > +{ 0x0817, 0x00 }, > > +{ 0x0818, 0x00 }, > > +{ 0x0819, 0x0a }, > > +{ 0x081a, 0x01 }, > > +{ 0x081b, 0x6c }, > > +{ 0x081c, 0x00 }, > > +{ 0x081d, 0x0b }, > > +{ 0x081e, 0x02 }, > > +{ 0x081f, 0x00 }, > > +{ 0x0820, 0x00 }, > > +{ 0x0821, 0x0c }, > > +{ 0x0822, 0x02 }, > > +{ 0x0823, 0xd9 }, > > +{ 0x0824, 0x00 }, > > +{ 0x0825, 0x0d }, > > +{ 0x0826, 0x03 }, > > +{ 0x0827, 0xf0 }, > > +{ 0x0828, 0x00 }, > > +{ 0x0829, 0x0e }, > > +{ 0x082a, 0x05 }, > > +{ 0x082b, 0x94 }, > > +{ 0x082c, 0x09 }, > > +{ 0x082d, 0x6e }, > > +{ 0x082e, 0x07 }, > > +{ 0x082f, 0xe6 }, > > +{ 0x0830, 0x10 }, > > +{ 0x0831, 0x0e }, > > +{ 0x0832, 0x0b }, > > +{ 0x0833, 0x2c }, > > +{ 0x0834, 0x14 }, > > +{ 0x0835, 0xae }, > > +{ 0x0836, 0x0f }, > > +{ 0x0837, 0xc4 }, > > +{ 0x0838, 0x18 }, > > +{ 0x0839, 0x0e }, > > +{ 0x05ac, 0x01 }, > > +{ 0x059a, 0x00 }, > > +{ 0x059b, 0x00 }, > > +{ 0x059c, 0x01 }, > > +{ 0x0598, 0x00 }, > > +{ 0x0597, 0x14 }, > > +{ 0x05ab, 0x09 }, > > +{ 0x05a4, 0x02 }, > > +{ 0x05a3, 0x05 }, > > +{ 0x05a0, 0xc2 }, > > +{ 0x0207, 0xc4 }, > > + > > +/*GAIN*/ > > +{ 0x0204, 0x04 }, > > +{ 0x0205, 0x00 }, > > +{ 0x0050, 0x5c }, > > +{ 0x0051, 0x44 }, > > + > > +/*out window*/ > > +{ 0x009a, 0x66 }, > > +{ 0x0351, 0x00 }, > > +{ 0x0352, 0x06 }, //out_win_y1 > > +{ 0x0353, 0x00 }, > > +{ 0x0354, 0x08 }, //out_win_x1 > > +{ 0x034c, 0x0c }, > > +{ 0x034d, 0xc0 }, //3264 > > +{ 0x034e, 0x09 }, > > +{ 0x034f, 0x90 }, //2448 > > + > > +/*MIPI*/ > > +{ 0x0114, 0x03 }, //0:1lane 1:2lane 3:4lane > > +{ 0x0180, 0x65 }, //[3:0]dphy_mipi_diff > > +{ 0x0181, 0xf0 }, > > +{ 0x0185, 0x01 }, > > +{ 0x0115, 0x30 }, > > +{ 0x011b, 0x12 }, > > +{ 0x011c, 0x12 }, > > +{ 0x0121, 0x06 }, //T_LPX > > +{ 0x0122, 0x06 }, //T_CLK_HS_PREPARE > > +{ 0x0123, 0x15 }, //T_CLK_zero > > +{ 0x0124, 0x01 }, //T_CLK_PRE > > +{ 0x0125, 0x0b }, //T_CLK_POST > > +{ 0x0126, 0x08 }, //T_CLK_TRAIL > > +{ 0x0129, 0x06 }, //T_HS_PREPARE > > +{ 0x012a, 0x08 }, //T_HS_Zero > > +{ 0x012b, 0x08 }, //T_HS_TRAIL > > + > > +{ 0x0a73, 0x60 }, > > +{ 0x0a70, 0x11 }, > > +{ 0x0313, 0x80 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0a70, 0x00 }, > > +{ 0x00a4, 0x80 }, > > +{ 0x0316, 0x01 }, > > +{ 0x0a67, 0x00 }, > > +{ 0x0084, 0x10 }, > > +{ 0x0102, 0x09 }, > > + > > +{ GC08A3_TABLE_END, 0x00 } > > +}; > > + > > +static const struct reg_8 mode_1920x1080[] = { > > +/*system*/ > > +{ 0x031c, 0x60 }, > > +{ 0x0337, 0x04 }, > > +{ 0x0335, 0x51 }, > > +{ 0x0336, 0x45 }, > > +{ 0x0383, 0x8b }, > > +{ 0x031a, 0x00 }, > > +{ 0x0321, 0x10 }, > > +{ 0x0327, 0x03 }, > > +{ 0x0325, 0x40 }, > > +{ 0x0326, 0x23 }, > > +{ 0x0314, 0x11 }, > > +{ 0x0315, 0xd6 }, > > +{ 0x0316, 0x01 }, > > +{ 0x0334, 0x40 }, > > +{ 0x0324, 0x42 }, > > +{ 0x031c, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x0344, 0x02 }, > > +{ 0x0345, 0xa6 }, > > +{ 0x0346, 0x02 }, > > +{ 0x0347, 0xb0 }, > > +{ 0x0348, 0x07 }, > > +{ 0x0349, 0x90 }, //1936 > > +{ 0x034a, 0x04 }, > > +{ 0x034b, 0x44 }, //1092 > > +{ 0x0202, 0x03 }, > > +{ 0x0203, 0x00 }, //Exp > > +{ 0x0340, 0x04 }, > > +{ 0x0341, 0xfc }, //FL > > +{ 0x0342, 0x07 }, > > +{ 0x0343, 0x1c }, //LineLength > > + > > +{ 0x0226, 0x00 }, //min vb[15:8] > > +{ 0x0227, 0x88 }, //min vb[7:0] > > +{ 0x0e38, 0x49 }, > > +{ 0x0210, 0x13 }, > > +{ 0x0218, 0x00 }, > > +{ 0x0241, 0x88 }, > > +{ 0x0392, 0x60 }, > > + > > +/*ISP*/ > > +{ 0x031c, 0x80 }, > > +{ 0x03fe, 0x10 }, //CISCTL_rst > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x80 }, > > +{ 0x03fe, 0x10 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x00a2, 0xac }, > > +{ 0x00a3, 0x02 }, > > +{ 0x00ab, 0xa0 }, > > +{ 0x00ac, 0x02 }, > > +{ 0x05a0, 0x82 }, > > +{ 0x05ac, 0x00 }, > > +{ 0x05ad, 0x01 }, > > +{ 0x05ae, 0x00 }, > > +{ 0x0800, 0x0a }, > > +{ 0x0801, 0x14 }, > > +{ 0x0802, 0x28 }, > > +{ 0x0803, 0x34 }, > > +{ 0x0804, 0x0e }, > > +{ 0x0805, 0x33 }, > > +{ 0x0806, 0x03 }, > > +{ 0x0807, 0x8a }, > > +{ 0x0808, 0x50 }, > > +{ 0x0809, 0x00 }, > > +{ 0x080a, 0x34 }, > > +{ 0x080b, 0x03 }, > > +{ 0x080c, 0x26 }, > > +{ 0x080d, 0x03 }, > > +{ 0x080e, 0x18 }, > > +{ 0x080f, 0x03 }, > > +{ 0x0810, 0x10 }, > > +{ 0x0811, 0x03 }, > > +{ 0x0812, 0x00 }, > > +{ 0x0813, 0x00 }, > > +{ 0x0814, 0x01 }, > > +{ 0x0815, 0x00 }, > > +{ 0x0816, 0x01 }, > > +{ 0x0817, 0x00 }, > > +{ 0x0818, 0x00 }, > > +{ 0x0819, 0x0a }, > > +{ 0x081a, 0x01 }, > > +{ 0x081b, 0x6c }, > > +{ 0x081c, 0x00 }, > > +{ 0x081d, 0x0b }, > > +{ 0x081e, 0x02 }, > > +{ 0x081f, 0x00 }, > > +{ 0x0820, 0x00 }, > > +{ 0x0821, 0x0c }, > > +{ 0x0822, 0x02 }, > > +{ 0x0823, 0xd9 }, > > +{ 0x0824, 0x00 }, > > +{ 0x0825, 0x0d }, > > +{ 0x0826, 0x03 }, > > +{ 0x0827, 0xf0 }, > > +{ 0x0828, 0x00 }, > > +{ 0x0829, 0x0e }, > > +{ 0x082a, 0x05 }, > > +{ 0x082b, 0x94 }, > > +{ 0x082c, 0x09 }, > > +{ 0x082d, 0x6e }, > > +{ 0x082e, 0x07 }, > > +{ 0x082f, 0xe6 }, > > +{ 0x0830, 0x10 }, > > +{ 0x0831, 0x0e }, > > +{ 0x0832, 0x0b }, > > +{ 0x0833, 0x2c }, > > +{ 0x0834, 0x14 }, > > +{ 0x0835, 0xae }, > > +{ 0x0836, 0x0f }, > > +{ 0x0837, 0xc4 }, > > +{ 0x0838, 0x18 }, > > +{ 0x0839, 0x0e }, > > +{ 0x05ac, 0x01 }, > > +{ 0x059a, 0x00 }, > > +{ 0x059b, 0x00 }, > > +{ 0x059c, 0x01 }, > > +{ 0x0598, 0x00 }, > > +{ 0x0597, 0x14 }, > > +{ 0x05ab, 0x09 }, > > +{ 0x05a4, 0x02 }, > > +{ 0x05a3, 0x05 }, > > +{ 0x05a0, 0xc2 }, > > +{ 0x0207, 0xc4 }, > > + > > +/*GAIN*/ > > +{ 0x0204, 0x04 }, > > +{ 0x0205, 0x00 }, > > +{ 0x0050, 0x38 }, > > +{ 0x0051, 0x20 }, > > + > > +/*out window*/ > > +{ 0x009a, 0x66 }, > > +{ 0x0351, 0x00 }, > > +{ 0x0352, 0x06 }, //out_win_y1 > > +{ 0x0353, 0x00 }, > > +{ 0x0354, 0x08 }, //out_win_x1 > > +{ 0x034c, 0x07 }, > > +{ 0x034d, 0x80 }, //1920 > > +{ 0x034e, 0x04 }, > > +{ 0x034f, 0x38 }, //1080 > > + > > +/*MIPI*/ > > +{ 0x0114, 0x03 }, //0:1lane 1:2lane 3:4lane > > +{ 0x0180, 0x65 }, //[3:0]dphy_mipi_diff > > +{ 0x0181, 0xf0 }, > > +{ 0x0185, 0x01 }, > > +{ 0x0115, 0x30 }, > > +{ 0x011b, 0x12 }, > > +{ 0x011c, 0x12 }, > > +{ 0x0121, 0x02 }, //T_LPX > > +{ 0x0122, 0x03 }, //T_CLK_HS_PREPARE > > +{ 0x0123, 0x0c }, //T_CLK_zero > > +{ 0x0124, 0x00 }, //T_CLK_PRE > > +{ 0x0125, 0x09 }, //T_CLK_POST > > +{ 0x0126, 0x06 }, //T_CLK_TRAIL > > +{ 0x0129, 0x04 }, //T_HS_PREPARE > > +{ 0x012a, 0x03 }, //T_HS_Zero > > +{ 0x012b, 0x06 }, //T_HS_TRAIL > > + > > +{ 0x0a73, 0x60 }, > > +{ 0x0a70, 0x11 }, > > +{ 0x0313, 0x80 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0aff, 0x00 }, > > +{ 0x0a70, 0x00 }, > > +{ 0x00a4, 0x80 }, > > +{ 0x0316, 0x01 }, > > +{ 0x0a67, 0x00 }, > > +{ 0x0084, 0x10 }, > > +{ 0x0102, 0x09 }, > > + > > +{ GC08A3_TABLE_END, 0x00 } > > +}; > > + > > +static const struct reg_8 mode_table_common[] = { > > +{ GC08A3_STRAEMING_REG, 0x00 }, > > +/*system*/ > > +{ 0x031c, 0x60 }, > > +{ 0x0337, 0x04 }, > > +{ 0x0335, 0x51 }, > > +{ 0x0336, 0x70 }, > > +{ 0x0383, 0xbb }, > > +{ 0x031a, 0x00 }, > > +{ 0x0321, 0x10 }, > > +{ 0x0327, 0x03 }, > > +{ 0x0325, 0x40 }, > > +{ 0x0326, 0x23 }, > > +{ 0x0314, 0x11 }, > > +{ 0x0315, 0xd6 }, > > +{ 0x0316, 0x01 }, > > +{ 0x0334, 0x40 }, > > +{ 0x0324, 0x42 }, > > +{ 0x031c, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x039a, 0x13 }, > > +{ 0x0084, 0x30 }, > > +{ 0x02b3, 0x08 }, > > +{ 0x0057, 0x0c }, > > +{ 0x05c3, 0x50 }, > > +{ 0x0311, 0x90 }, > > +{ 0x05a0, 0x02 }, > > +{ 0x0074, 0x0a }, > > +{ 0x0059, 0x11 }, > > +{ 0x0070, 0x05 }, > > +{ 0x0101, 0x00 }, //[1]updown [0]mirror > > + > > +/*analog*/ > > +{ 0x0344, 0x00 }, > > +{ 0x0345, 0x06 }, > > +{ 0x0346, 0x00 }, > > +{ 0x0347, 0x04 }, > > +{ 0x0348, 0x0c }, > > +{ 0x0349, 0xd0 }, //3280 > > +{ 0x034a, 0x09 }, > > +{ 0x034b, 0x9c }, //2460 > > +{ 0x0202, 0x09 }, > > +{ 0x0203, 0x04 }, //Exp > > + > > +{ 0x0219, 0x05 }, //[4]FL_depend_exp > > +{ 0x0226, 0x00 }, //min vb[15:8] > > +{ 0x0227, 0x28 }, //min vb[7:0] > > +{ 0x0e0a, 0x00 }, > > +{ 0x0e0b, 0x00 }, > > +{ 0x0e24, 0x04 }, > > +{ 0x0e25, 0x04 }, > > +{ 0x0e26, 0x00 }, > > +{ 0x0e27, 0x10 }, > > +{ 0x0e01, 0x74 }, > > +{ 0x0e03, 0x47 }, > > +{ 0x0e04, 0x33 }, > > +{ 0x0e05, 0x44 }, > > +{ 0x0e06, 0x44 }, > > +{ 0x0e0c, 0x1e }, > > +{ 0x0e17, 0x3a }, > > +{ 0x0e18, 0x3c }, > > +{ 0x0e19, 0x40 }, > > +{ 0x0e1a, 0x42 }, > > +{ 0x0e28, 0x21 }, > > +{ 0x0e2b, 0x68 }, > > +{ 0x0e2c, 0x0d }, > > +{ 0x0e2d, 0x08 }, > > +{ 0x0e34, 0xf4 }, > > +{ 0x0e35, 0x44 }, > > +{ 0x0e36, 0x07 }, > > +{ 0x0e38, 0x49 }, > > +{ 0x0210, 0x13 }, > > +{ 0x0218, 0x00 }, > > +{ 0x0241, 0x88 }, > > +{ 0x0e32, 0x00 }, > > +{ 0x0e33, 0x18 }, > > +{ 0x0e42, 0x03 }, > > +{ 0x0e43, 0x80 }, > > +{ 0x0e44, 0x04 }, > > +{ 0x0e45, 0x00 }, > > +{ 0x0e4f, 0x04 }, > > +{ 0x057a, 0x20 }, > > +{ 0x0381, 0x7c }, > > +{ 0x0382, 0x9b }, > > +{ 0x0384, 0xfb }, > > +{ 0x0389, 0x38 }, > > +{ 0x038a, 0x03 }, > > +{ 0x0390, 0x6a }, > > +{ 0x0391, 0x0b }, > > +{ 0x0392, 0x60 }, > > +{ 0x0393, 0xc1 }, > > +{ 0x0396, 0xff }, > > +{ 0x0398, 0x62 }, > > + > > +/*cisctl reset*/ > > +{ 0x031c, 0x80 }, > > +{ 0x03fe, 0x10 }, //CISCTL_rst > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x80 }, > > +{ 0x03fe, 0x10 }, //CISCTL_rst > > +{ 0x03fe, 0x00 }, > > +{ 0x031c, 0x9f }, > > +{ 0x0360, 0x01 }, > > +{ 0x0360, 0x00 }, > > +{ 0x0316, 0x09 }, > > +{ 0x0a67, 0x80 }, > > +{ 0x0313, 0x00 }, > > +{ 0x0a53, 0x0e }, > > +{ 0x0a65, 0x17 }, > > +{ 0x0a68, 0xa1 }, > > +{ 0x0a58, 0x00 }, > > +{ 0x0ace, 0x0c }, > > +{ 0x00a4, 0x00 }, > > +{ 0x00a5, 0x01 }, > > +{ 0x00a7, 0x09 }, > > +{ 0x00a8, 0x9c }, > > +{ 0x00a9, 0x0c }, > > +{ 0x00aa, 0xd0 }, > > +{ 0x0a8a, 0x00 }, > > +{ 0x0a8b, 0xe0 }, > > +{ 0x0a8c, 0x13 }, > > +{ 0x0a8d, 0xe8 }, > > +{ 0x0a90, 0x0a }, > > +{ 0x0a91, 0x10 }, > > +{ 0x0a92, 0xf8 }, > > +{ 0x0a71, 0xf2 }, > > +{ 0x0a72, 0x12 }, > > +{ 0x0a73, 0x64 }, > > +{ 0x0a75, 0x41 }, > > +{ 0x0a70, 0x07 }, > > +{ 0x0313, 0x80 }, > > + > > +/*ISP*/ > > +{ 0x00a0, 0x01 }, > > +{ 0x0080, 0xd2 }, > > +{ 0x0081, 0x3f }, > > +{ 0x0087, 0x51 }, > > +{ 0x0089, 0x03 }, > > +{ 0x009b, 0x40 }, > > +{ 0x05a0, 0x82 }, > > +{ 0x05ac, 0x00 }, > > +{ 0x05ad, 0x01 }, > > +{ 0x05ae, 0x00 }, > > +{ 0x0800, 0x0a }, > > +{ 0x0801, 0x14 }, > > +{ 0x0802, 0x28 }, > > +{ 0x0803, 0x34 }, > > +{ 0x0804, 0x0e }, > > +{ 0x0805, 0x33 }, > > +{ 0x0806, 0x03 }, > > +{ 0x0807, 0x8a }, > > +{ 0x0808, 0x50 }, > > +{ 0x0809, 0x00 }, > > +{ 0x080a, 0x34 }, > > +{ 0x080b, 0x03 }, > > +{ 0x080c, 0x26 }, > > +{ 0x080d, 0x03 }, > > +{ 0x080e, 0x18 }, > > +{ 0x080f, 0x03 }, > > +{ 0x0810, 0x10 }, > > +{ 0x0811, 0x03 }, > > +{ 0x0812, 0x00 }, > > +{ 0x0813, 0x00 }, > > +{ 0x0814, 0x01 }, > > +{ 0x0815, 0x00 }, > > +{ 0x0816, 0x01 }, > > +{ 0x0817, 0x00 }, > > +{ 0x0818, 0x00 }, > > +{ 0x0819, 0x0a }, > > +{ 0x081a, 0x01 }, > > +{ 0x081b, 0x6c }, > > +{ 0x081c, 0x00 }, > > +{ 0x081d, 0x0b }, > > +{ 0x081e, 0x02 }, > > +{ 0x081f, 0x00 }, > > +{ 0x0820, 0x00 }, > > +{ 0x0821, 0x0c }, > > +{ 0x0822, 0x02 }, > > +{ 0x0823, 0xd9 }, > > +{ 0x0824, 0x00 }, > > +{ 0x0825, 0x0d }, > > +{ 0x0826, 0x03 }, > > +{ 0x0827, 0xf0 }, > > +{ 0x0828, 0x00 }, > > +{ 0x0829, 0x0e }, > > +{ 0x082a, 0x05 }, > > +{ 0x082b, 0x94 }, > > +{ 0x082c, 0x09 }, > > +{ 0x082d, 0x6e }, > > +{ 0x082e, 0x07 }, > > +{ 0x082f, 0xe6 }, > > +{ 0x0830, 0x10 }, > > +{ 0x0831, 0x0e }, > > +{ 0x0832, 0x0b }, > > +{ 0x0833, 0x2c }, > > +{ 0x0834, 0x14 }, > > +{ 0x0835, 0xae }, > > +{ 0x0836, 0x0f }, > > +{ 0x0837, 0xc4 }, > > +{ 0x0838, 0x18 }, > > +{ 0x0839, 0x0e }, > > +{ 0x05ac, 0x01 }, > > +{ 0x059a, 0x00 }, > > +{ 0x059b, 0x00 }, > > +{ 0x059c, 0x01 }, > > +{ 0x0598, 0x00 }, > > +{ 0x0597, 0x14 }, > > +{ 0x05ab, 0x09 }, > > +{ 0x05a4, 0x02 }, > > +{ 0x05a3, 0x05 }, > > +{ 0x05a0, 0xc2 }, > > +{ 0x0207, 0xc4 }, > > + > > +/*GAIN*/ > > +{ 0x0208, 0x01 }, > > +{ 0x0209, 0x72 }, > > +{ 0x0204, 0x04 }, > > +{ 0x0205, 0x00 }, > > + > > +{ 0x0040, 0x22 }, > > +{ 0x0041, 0x20 }, > > +{ 0x0043, 0x10 }, > > +{ 0x0044, 0x00 }, > > +{ 0x0046, 0x08 }, > > +{ 0x0047, 0xf0 }, > > +{ 0x0048, 0x0f }, > > +{ 0x004b, 0x0f }, > > +{ 0x004c, 0x00 }, > > +{ 0x0050, 0x5c }, > > +{ 0x0051, 0x44 }, > > +{ 0x005b, 0x03 }, > > +{ 0x00c0, 0x00 }, > > +{ 0x00c1, 0x80 }, > > +{ 0x00c2, 0x31 }, > > +{ 0x00c3, 0x00 }, > > +{ 0x0460, 0x04 }, > > +{ 0x0462, 0x08 }, > > +{ 0x0464, 0x0e }, > > +{ 0x0466, 0x0a }, > > +{ 0x0468, 0x12 }, > > +{ 0x046a, 0x12 }, > > +{ 0x046c, 0x10 }, > > +{ 0x046e, 0x0c }, > > +{ 0x0461, 0x03 }, > > +{ 0x0463, 0x03 }, > > +{ 0x0465, 0x03 }, > > +{ 0x0467, 0x03 }, > > +{ 0x0469, 0x04 }, > > +{ 0x046b, 0x04 }, > > +{ 0x046d, 0x04 }, > > +{ 0x046f, 0x04 }, > > +{ 0x0470, 0x04 }, > > +{ 0x0472, 0x10 }, > > +{ 0x0474, 0x26 }, > > +{ 0x0476, 0x38 }, > > +{ 0x0478, 0x20 }, > > +{ 0x047a, 0x30 }, > > +{ 0x047c, 0x38 }, > > +{ 0x047e, 0x60 }, > > +{ 0x0471, 0x05 }, > > +{ 0x0473, 0x05 }, > > +{ 0x0475, 0x05 }, > > +{ 0x0477, 0x05 }, > > +{ 0x0479, 0x04 }, > > +{ 0x047b, 0x04 }, > > +{ 0x047d, 0x04 }, > > +{ 0x047f, 0x04 }, > > + > > +{ GC08A3_TABLE_END, 0x00 } > > +}; > > + > > +static const struct gc08a3_link_freq_config link_freq_configs[] = > { > > +[GC08A3_LINK_FREQ_336MHZ_CFG] = { > > +.reg_list = { > > +.num_of_regs = ARRAY_SIZE(mode_table_common), > > +.regs = mode_table_common, > > +} > > +}, > > +[GC08A3_LINK_FREQ_207MHZ_CFG] = { > > +.reg_list = { > > +.num_of_regs = ARRAY_SIZE(mode_table_common), > > +.regs = mode_table_common, > > +} > > +}, > > + > > Extra blank line. > [mtk]: fixed in patch:v3 > > +}; > > + > > +enum { > > +GC08A3_PREV, > > +GC08A3_HS, > > +}; > > + > > +/* > > + * Declare modes in order, from biggest > > + * to smallest height. > > + */ > > +static const struct gc08a3_mode { > > +u8 mode_id; > > +u32 width; > > +u32 height; > > +const struct gc08a3_reg_list reg_list; > > + > > +u32 hts; /* Horizontal timining size */ > > +u32 vts_def; /* Default vertical timining size */ > > +u32 vts_min; /* Min vertical timining size */ > > +u32 link_freq_index; /* Link frequency needed for this resolution > */ > > +u32 max_framerate; > > + > > Extra blank line. > [mtk]: fixed in patch:v3 > > +} gc08a3_modes[] = { > > +{ > > +.mode_id = GC08A3_PREV, > > +.width = 3264, > > +.height = 2448, > > +.reg_list = { > > +.num_of_regs = ARRAY_SIZE(mode_3264x2448), > > +.regs = mode_3264x2448, > > +}, > > +.link_freq_index = GC08A3_LINK_FREQ_336MHZ_CFG, > > + > > +.hts = GC08A3_HTS_30FPS, > > +.vts_def = GC08A3_VTS_30FPS, > > +.vts_min = GC08A3_VTS_30FPS_MIN, > > +.max_framerate = 300, > > +}, > > +{ > > +.mode_id = GC08A3_HS, > > +.width = 1920, > > +.height = 1080, > > +.reg_list = { > > +.num_of_regs = ARRAY_SIZE(mode_1920x1080), > > +.regs = mode_1920x1080, > > +}, > > +.link_freq_index = GC08A3_LINK_FREQ_207MHZ_CFG, > > + > > +.hts = GC08A3_HTS_60FPS, > > +.vts_def = GC08A3_VTS_60FPS, > > +.vts_min = GC08A3_VTS_60FPS_MIN, > > +.max_framerate = 600, > > +}, > > +}; > > + > > +static u64 to_pixel_rate(u32 f_index) > > +{ > > +u64 pixel_rate = link_freq_menu_items[f_index] * 2 * > GC08A3_DATA_LANES; > > + > > +do_div(pixel_rate, GC08A3_RGB_DEPTH); > > + > > +return pixel_rate; > > +} > > + > > +static u64 __maybe_unused to_pixels_per_line(u32 hts, u32 f_index) > > +{ > > +u64 ppl = hts * to_pixel_rate(f_index); > > + > > +do_div(ppl, GC08A3_SCLK); > > + > > +return ppl; > > +} > > + > > +static int gc08a3_read_reg(struct gc08a3 *gc08a3, u16 reg, u16 > len, u32 *val) > > +{ > > +struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > > +struct i2c_msg msgs[2]; > > +u8 addr_buf[2]; > > +u8 data_buf[4] = { 0 }; > > +int ret; > > + > > +if (len > 4) > > +return -EINVAL; > > + > > +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 = len; > > +msgs[1].buf = &data_buf[4 - len]; > > + > > +ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); > > +if (ret != ARRAY_SIZE(msgs)) > > +return -EIO; > > + > > +*val = get_unaligned_be32(data_buf); > > + > > +return 0; > > +} > > + > > +static int gc08a3_write_reg(struct gc08a3 *gc08a3, u16 reg, u16 > len, u32 val) > > +{ > > +struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > > +u8 buf[6]; > > + > > +if (len > 4) > > +return -EINVAL; > > + > > +put_unaligned_be16(reg, buf); > > +put_unaligned_be32(val << 8 * (4 - len), buf + 2); > > +if (i2c_master_send(client, buf, len + 2) != len + 2) > > +return -EIO; > > + > > +return 0; > > +} > > + > > +static int gc08a3_write_reg_list(struct gc08a3 *gc08a3, > > + const struct gc08a3_reg_list *r_list) > > +{ > > +struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > > +unsigned int i; > > +int ret; > > + > > +for (i = 0; i < r_list->num_of_regs; i++) { > > +if (r_list->regs[i].address == GC08A3_TABLE_WAIT_MS) { > > +usleep_range(r_list->regs[i].val * 1000, > > + r_list->regs[i].val * 1000 + 500); > > +continue; > > +} > > + > > +if (r_list->regs[i].address == GC08A3_TABLE_END) > > +break; > > + > > +ret = gc08a3_write_reg(gc08a3, r_list->regs[i].address, > > + GC08A3_REG_VALUE_08BIT, > > + r_list->regs[i].val); > > +if (ret) { > > +dev_err_ratelimited(&client->dev, > > + "failed to write reg 0x%4.4x. error = %d", > > + r_list->regs[i].address, ret); > > +return ret; > > +} > > +} > > + > > +return 0; > > +} > > + > > +static int gc08a3_identify_module(struct gc08a3 *gc08a3) > > +{ > > +struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > > +u32 val = 0; > > + > > +gc08a3_read_reg(gc08a3, GC08A3_REG_CHIP_ID, > GC08A3_REG_VALUE_16BIT, > > +&val); > > + > > +if (val != GC08A3_CHIP_ID) { > > +dev_err(&client->dev, "chip id mismatch: 0x%x!=0x%x", > > +GC08A3_CHIP_ID, val); > > +return -ENXIO; > > +} > > + > > +dev_info(&client->dev, "sensor_id: 0x%04x\n", val); > > +return 0; > > +} > > + > > +static inline struct gc08a3 *to_gc08a3(struct v4l2_subdev *sd) > > +{ > > +return container_of(sd, struct gc08a3, sd); > > +} > > + > > +static int __maybe_unused gc08a3_power_on(struct device *dev) > > +{ > > +struct i2c_client *client = to_i2c_client(dev); > > +struct v4l2_subdev *sd = i2c_get_clientdata(client); > > +struct gc08a3 *gc08a3 = to_gc08a3(sd); > > +int ret; > > + > > +dev_info(dev, "--- %s +", __func__); > > Drop this. Tracing is done with ftrace. Same comment below where > applicable. > [mtk]: fixed in patch:v3 > > + > > +gpiod_set_value_cansleep(gc08a3->enable_gpio, 0); > > +usleep_range(2000, 3000); > > + > > +ret = regulator_bulk_enable(GC08A3_NUM_SUPPLIES, gc08a3- > >supplies); > > +if (ret < 0) { > > +dev_err(gc08a3->dev, "failed to enable regulators: %d\n", ret); > > +return ret; > > +} > > + > > +ret = clk_prepare_enable(gc08a3->xclk); > > +if (ret < 0) { > > +regulator_bulk_disable(GC08A3_NUM_SUPPLIES, gc08a3->supplies); > > +dev_err(gc08a3->dev, "clk prepare enable failed\n"); > > +return ret; > > +} > > + > > +usleep_range(2000, 3000); > > + > > +gpiod_set_value_cansleep(gc08a3->enable_gpio, 1); > > +usleep_range(12000, 15000); > > + > > +dev_info(dev, "--- %s -", __func__); > > + > > +return 0; > > +} > > + > > +static int __maybe_unused gc08a3_power_off(struct device *dev) > > +{ > > +struct i2c_client *client = to_i2c_client(dev); > > +struct v4l2_subdev *sd = i2c_get_clientdata(client); > > +struct gc08a3 *gc08a3 = to_gc08a3(sd); > > + > > +dev_info(dev, "--- %s +", __func__); > > + > > +gpiod_set_value_cansleep(gc08a3->enable_gpio, 0); > > + > > +clk_disable_unprepare(gc08a3->xclk); > > + > > +regulator_bulk_disable(GC08A3_NUM_SUPPLIES, gc08a3->supplies); > > +usleep_range(10, 20); > > + > > +dev_info(dev, "--- %s -", __func__); > > + > > +return 0; > > +} > > + > > +static int gc08a3_enum_mbus_code(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_mbus_code_enum *code) > > +{ > > +if (code->index > 0) > > +return -EINVAL; > > + > > +code->code = GC08A3_MBUS_CODE; > > + > > +return 0; > > +} > > + > > +static int gc08a3_enum_frame_size(struct v4l2_subdev *subdev, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_frame_size_enum *fse) > > +{ > > +if (fse->code != GC08A3_MBUS_CODE) > > +return -EINVAL; > > + > > +if (fse->index >= ARRAY_SIZE(gc08a3_modes)) > > +return -EINVAL; > > + > > +fse->min_width = gc08a3_modes[fse->index].width; > > +fse->max_width = gc08a3_modes[fse->index].width; > > +fse->min_height = gc08a3_modes[fse->index].height; > > +fse->max_height = gc08a3_modes[fse->index].height; > > + > > +return 0; > > +} > > + > > +#ifdef CONFIG_VIDEO_ADV_DEBUG > > +static int gc08a3_g_register(struct v4l2_subdev *subdev, > > + struct v4l2_dbg_register *reg) > > +{ > > +int ret; > > +u32 val; > > +struct gc08a3 *gc08a3 = container_of(subdev, struct gc08a3, sd); > > + > > +ret = gc08a3_read_reg(gc08a3, reg->reg, GC08A3_REG_VALUE_08BIT, > &val); > > +if (ret < 0) > > +return ret; > > + > > +reg->val = val; > > +reg->size = 1; > > + > > +return 0; > > +} > > + > > +static int gc08a3_s_register(struct v4l2_subdev *subdev, > > + const struct v4l2_dbg_register *reg) > > +{ > > +struct gc08a3 *gc08a3 = container_of(subdev, struct gc08a3, sd); > > + > > +return gc08a3_write_reg(gc08a3, reg->reg, GC08A3_REG_VALUE_08BIT, > > +reg->val & 0xff); > > +} > > + > > +#endif > > + > > +static const struct v4l2_subdev_core_ops gc08a3_core_ops = { > > +#ifdef CONFIG_VIDEO_ADV_DEBUG > > +.g_register = gc08a3_g_register, > > +.s_register = gc08a3_s_register, > > +#endif > > Drop these too. If you need to access registers directly from > userspace > for development purpose, you can do so through i2cdev. > [mtk]: fixed in patch:v3 > > +}; > > + > > +static struct v4l2_mbus_framefmt * > > +__gc08a3_get_pad_format(struct gc08a3 *gc08a3, > > +struct v4l2_subdev_state *sd_state, unsigned int pad, > > +enum v4l2_subdev_format_whence which) > > +{ > > +switch (which) { > > +case V4L2_SUBDEV_FORMAT_TRY: > > +return v4l2_subdev_get_try_format(&gc08a3->sd, sd_state, pad); > > +case V4L2_SUBDEV_FORMAT_ACTIVE: > > +return &gc08a3->fmt; > > +default: > > +return NULL; > > +} > > +} > > + > > +static int gc08a3_get_format(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_format *format) > > +{ > > +struct gc08a3 *gc08a3 = to_gc08a3(sd); > > + > > +mutex_lock(&gc08a3->mutex); > > +format->format = *__gc08a3_get_pad_format(gc08a3, sd_state, > format->pad, > > + format->which); > > +mutex_unlock(&gc08a3->mutex); > > + > > +return 0; > > +} > > + > > +static struct v4l2_rect * > > +__gc08a3_get_pad_crop(struct gc08a3 *gc08a3, struct > v4l2_subdev_state *sd_state, > > + unsigned int pad, enum v4l2_subdev_format_whence which) > > +{ > > +switch (which) { > > +case V4L2_SUBDEV_FORMAT_TRY: > > +return v4l2_subdev_get_try_crop(&gc08a3->sd, sd_state, pad); > > +case V4L2_SUBDEV_FORMAT_ACTIVE: > > +return &gc08a3->crop; > > +default: > > +return NULL; > > +} > > +} > > + > > +static int gc08a3_update_cur_mode_controls(struct gc08a3 *gc08a3) > > +{ > > +s64 exposure_max, h_blank; > > +int ret = 0; > > + > > +ret = __v4l2_ctrl_modify_range(gc08a3->vblank, > > + gc08a3->cur_mode->vts_min - gc08a3->cur_mode->height, > > + GC08A3_VTS_MAX - gc08a3->cur_mode->height, 1, > > + gc08a3->cur_mode->vts_def - gc08a3->cur_mode->height); > > +if (ret) > > +dev_err(gc08a3->dev, "VB ctrl range update failed\n"); > > + > > +h_blank = gc08a3->cur_mode->hts - gc08a3->cur_mode->width; > > +ret = __v4l2_ctrl_modify_range(gc08a3->hblank, h_blank, h_blank, > 1, > > + h_blank); > > +if (ret) > > +dev_err(gc08a3->dev, "HB ctrl range update failed\n"); > > + > > +exposure_max = gc08a3->cur_mode->vts_def - GC08A3_EXP_MARGIN; > > +ret = __v4l2_ctrl_modify_range(gc08a3->exposure, GC08A3_EXP_MIN, > > + exposure_max, GC08A3_EXP_STEP, > > + exposure_max); > > +if (ret) > > +dev_err(gc08a3->dev, "exposure ctrl range update failed\n"); > > + > > +return ret; > > +} > > + > > +static int gc08a3_set_format(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_format *format) > > +{ > > +struct gc08a3 *gc08a3 = to_gc08a3(sd); > > +struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > > +struct device *dev = &client->dev; > > +struct v4l2_mbus_framefmt *__format; > > +struct v4l2_rect *__crop; > > +const struct gc08a3_mode *mode; > > + > > +mutex_lock(&gc08a3->mutex); > > + > > +dev_dbg(dev, "---- %s, format(W x H:%d x %d) +", __func__, > > +format->format.width, format->format.height); > > + > > +__crop = __gc08a3_get_pad_crop(gc08a3, sd_state, format->pad, > > + format->which); > > + > > +mode = v4l2_find_nearest_size(gc08a3_modes, > ARRAY_SIZE(gc08a3_modes), > > + width, height, format->format.width, > > + format->format.height); > > + > > +dev_dbg(dev, "----nearest mode(W x H:%d x %d)", mode->width, > > +mode->height); > > + > > +__crop->width = mode->width; > > +__crop->height = mode->height; > > + > > +__format = __gc08a3_get_pad_format(gc08a3, sd_state, format->pad, > > + format->which); > > +__format->width = __crop->width; > > +__format->height = __crop->height; > > +__format->code = GC08A3_MBUS_CODE; > > +__format->field = V4L2_FIELD_NONE; > > +__format->colorspace = V4L2_COLORSPACE_SRGB; > > +__format->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(__format- > >colorspace); > > +__format->quantization = > > +V4L2_MAP_QUANTIZATION_DEFAULT(true, > > + __format->colorspace, > > + __format->ycbcr_enc); > > +__format->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(__format- > >colorspace); > > + > > +format->format = *__format; > > + > > +gc08a3->cur_mode = mode; > > +gc08a3_update_cur_mode_controls(gc08a3); > > + > > +mutex_unlock(&gc08a3->mutex); > > + > > +return 0; > > +} > > + > > +static int gc08a3_get_selection(struct v4l2_subdev *sd, > > +struct v4l2_subdev_state *sd_state, > > +struct v4l2_subdev_selection *sel) > > +{ > > +struct gc08a3 *gc08a3 = to_gc08a3(sd); > > + > > +switch (sel->target) { > > +case V4L2_SEL_TGT_CROP: > > +mutex_lock(&gc08a3->mutex); > > +sel->r = *__gc08a3_get_pad_crop(gc08a3, sd_state, sel->pad, sel- > >which); > > +mutex_unlock(&gc08a3->mutex); > > +break; > > +case V4L2_SEL_TGT_CROP_BOUNDS: > > +sel->r.top = 0; > > +sel->r.left = 0; > > +sel->r.width = GC08A3_NATIVE_WIDTH; > > +sel->r.height = GC08A3_NATIVE_HEIGHT; > > +break; > > +case V4L2_SEL_TGT_CROP_DEFAULT: > > +if (gc08a3->cur_mode->width == GC08A3_NATIVE_WIDTH) { > > +sel->r.top = 0; > > +sel->r.left = 0; > > +sel->r.width = GC08A3_NATIVE_WIDTH; > > +sel->r.height = GC08A3_NATIVE_HEIGHT; > > +} else { > > +sel->r.top = 0; > > +sel->r.left = 0; > > +sel->r.width = 1920; > > +sel->r.height = 1080; > > +} > > +break; > > +default: > > +return -EINVAL; > > +} > > + > > +return 0; > > +} > > + > > +static int gc08a3_entity_init_cfg(struct v4l2_subdev *subdev, > > + struct v4l2_subdev_state *sd_state) > > +{ > > +struct v4l2_subdev_format fmt = {}; > > + > > +fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : > > + V4L2_SUBDEV_FORMAT_ACTIVE; > > +fmt.format.width = gc08a3_modes[0].width; > > +fmt.format.height = gc08a3_modes[0].height; > > + > > +gc08a3_set_format(subdev, sd_state, &fmt); > > + > > +return 0; > > +} > > + > > +static int gc08a3_set_ctrl_hflip(struct gc08a3 *gc08a3, u32 > ctrl_val) > > +{ > > +int ret; > > +u32 val; > > + > > +ret = gc08a3_read_reg(gc08a3, GC08A3_FLIP_REG, > GC08A3_REG_VALUE_08BIT, > > + &val); > > +if (ret) { > > +dev_err(gc08a3->dev, "read hflip register failed: %d\n", ret); > > +return ret; > > +} > > + > > +val = (ctrl_val) ? (val | GC08A3_FLIP_H_MASK) : > > + (val & ~GC08A3_FLIP_H_MASK); > > +ret = gc08a3_write_reg(gc08a3, GC08A3_FLIP_REG, > GC08A3_REG_VALUE_08BIT, > > + val); > > +if (ret < 0) > > +dev_err(gc08a3->dev, "Error %d\n", ret); > > + > > +return ret; > > +} > > + > > +static int gc08a3_set_ctrl_vflip(struct gc08a3 *gc08a3, u32 > ctrl_val) > > +{ > > +int ret; > > +u32 val; > > + > > +ret = gc08a3_read_reg(gc08a3, GC08A3_FLIP_REG, > GC08A3_REG_VALUE_08BIT, > > + &val); > > +if (ret) { > > +dev_err(gc08a3->dev, "read vflip register failed: %d\n", ret); > > +return ret; > > +} > > + > > +val = (ctrl_val) ? (val | GC08A3_FLIP_V_MASK) : > > + (val & ~GC08A3_FLIP_V_MASK); > > +ret = gc08a3_write_reg(gc08a3, GC08A3_FLIP_REG, > GC08A3_REG_VALUE_08BIT, > > + val); > > +if (ret < 0) > > +dev_err(gc08a3->dev, "Error %d\n", ret); > > + > > +return ret; > > +} > > + > > +static int gc08a3_test_pattern(struct gc08a3 *gc08a3, u32 > pattern_menu) > > +{ > > +int ret = 0; > > +u32 pattern = 0; > > + > > +if (pattern_menu) { > > +// write bit16:0x0110 -> color bar > > +switch (pattern_menu) { > > +case 1: > > +pattern = 0x00; > > +break; > > +case 2: > > +case 3: > > +case 4: > > +case 5: > > +case 6: > > +pattern = pattern_menu + 2; > > +break; > > +case 7: > > +pattern = 0x10; > > +break; > > + > > +default: > > +dev_dbg(gc08a3->dev, "invalid pattern menu!\n"); > > +break; > > +} > > + > > +ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_EN, > > + GC08A3_REG_VALUE_08BIT, > > + GC08A3_TEST_PATTERN_EN); > > +ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_IDX, > > + GC08A3_REG_VALUE_08BIT, pattern); > > + > > +} else { > > +ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_EN, > > + GC08A3_REG_VALUE_08BIT, 0x00); > > +} > > + > > +return ret; > > +} > > + > > +static int gc08a3_set_ctrl(struct v4l2_ctrl *ctrl) > > +{ > > +struct gc08a3 *gc08a3 = > > +container_of(ctrl->handler, struct gc08a3, ctrls); > > +int ret = 0; > > +s64 exposure_max; > > + > > +if (ctrl->id == V4L2_CID_VBLANK) { > > +/* Update max exposure while meeting expected vblanking */ > > +exposure_max = gc08a3->cur_mode->height + ctrl->val - > > + GC08A3_EXP_MARGIN; > > +__v4l2_ctrl_modify_range(gc08a3->exposure, > > + gc08a3->exposure->minimum, > > + exposure_max, gc08a3->exposure->step, > > + exposure_max); > > +} > > + > > +/* > > + * Applying V4L2 control value only happens > > + * when power is up for streaming > > + */ > > +if (!pm_runtime_get_if_in_use(gc08a3->dev)) > > +return 0; > > + > > +switch (ctrl->id) { > > +case V4L2_CID_EXPOSURE: > > +ret = gc08a3_write_reg(gc08a3, GC08A3_EXP_REG, > > + GC08A3_REG_VALUE_16BIT, ctrl->val); > > +break; > > + > > +case V4L2_CID_ANALOGUE_GAIN: > > +ret = gc08a3_write_reg(gc08a3, GC08A3_AGAIN_REG, > > + GC08A3_REG_VALUE_16BIT, ctrl->val); > > +break; > > + > > +case V4L2_CID_VBLANK: > > +dev_info(gc08a3->dev, "V4L2_CID_VBLANK:height:%d, V_BLNK:%d\n", > > + gc08a3->cur_mode->height, ctrl->val); > > I would drop this too, or at the very least convert it to dev_dbg(). > There should be no message but debug messages printed by the driver > to > the kernel log during normal operation. > [mtk]: fixed in patch:v3 > > +ret = gc08a3_write_reg(gc08a3, GC08A3_FL_REG, > > + GC08A3_REG_VALUE_16BIT, > > + gc08a3->cur_mode->height + ctrl->val); > > +break; > > + > > +case V4L2_CID_HFLIP: > > +gc08a3_set_ctrl_hflip(gc08a3, ctrl->val); > > +break; > > + > > +case V4L2_CID_VFLIP: > > +gc08a3_set_ctrl_vflip(gc08a3, ctrl->val); > > +break; > > + > > +case V4L2_CID_TEST_PATTERN: > > +ret = gc08a3_test_pattern(gc08a3, ctrl->val); > > +break; > > + > > +default: > > +break; > > +} > > + > > +pm_runtime_put(gc08a3->dev); > > + > > +return ret; > > +} > > + > > +static int get_volatile_ext_ctrl(struct v4l2_ctrl *ctrl) > > +{ > > +int ret = 0; > > +struct gc08a3 *gc08a3 = > > +container_of(ctrl->handler, struct gc08a3, ctrls); > > +struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > > + > > +dev_dbg(&client->dev, "---- %s, CMD:0x%x\n", __func__, ctrl->id); > > +switch (ctrl->id) { > > +default: > > +dev_info(&client->dev, "[gc08a3] %s, un-support CMD: 0x%x\n", > > + __func__, ctrl->id); > > +break; > > +} > > + > > +return ret; > > +} > > + > > +static int gc08a3_g_volatile_ctrl(struct v4l2_ctrl *ctrl) > > +{ > > +int ret = 0; > > + > > +switch (ctrl->id) { > > +default: > > +ret = get_volatile_ext_ctrl(ctrl); > > +break; > > +} > > + > > +return ret; > > +} > > + > > +static int try_ext_ctrl(struct v4l2_ctrl *ctrl) > > +{ > > +int ret = 0; > > +struct gc08a3 *gc08a3 = > > +container_of(ctrl->handler, struct gc08a3, ctrls); > > +struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > > + > > +switch (ctrl->id) { > > +default: > > +dev_dbg(&client->dev, > > +"[gc08a3] un-handle CMD: 0x%x (%s : %d)\n", ctrl->id, > > +__func__, __LINE__); > > +break; > > +} > > + > > +return ret; > > +} > > + > > +static int gc08a3_try_ctrl(struct v4l2_ctrl *ctrl) > > +{ > > +int ret = 0; > > + > > +switch (ctrl->id) { > > +case V4L2_CID_EXPOSURE: > > +case V4L2_CID_ANALOGUE_GAIN: > > +case V4L2_CID_VBLANK: > > +case V4L2_CID_HFLIP: > > +case V4L2_CID_VFLIP: > > +case V4L2_CID_TEST_PATTERN: > > +return 0; > > + > > +default: > > +ret = try_ext_ctrl(ctrl); > > +break; > > +} > > + > > +return ret; > > +} > > + > > +static const struct v4l2_ctrl_ops gc08a3_ctrl_ops = { > > +.g_volatile_ctrl = gc08a3_g_volatile_ctrl, > > +.try_ctrl = gc08a3_try_ctrl, > > Drop the .g_volatile_ctrl() and .try_ctrl() implementations, they > don't > do anything, and the operations are optional. > [mtk]: fixed in patch:v3 > > +.s_ctrl = gc08a3_set_ctrl, > > +}; > > + > > +static int gc08a3_start_streaming(struct gc08a3 *gc08a3) > > +{ > > +const struct gc08a3_mode *mode; > > +const struct gc08a3_reg_list *reg_list; > > +int link_freq_index; > > +int ret; > > + > > +dev_info(gc08a3->dev, "%s ++\n", __func__); > > + > > +mutex_lock(&gc08a3->mutex); > > + > > +link_freq_index = gc08a3->cur_mode->link_freq_index; > > +dev_info(gc08a3->dev, "----link_freq_index = %d ", > link_freq_index); > > + > > +reg_list = &link_freq_configs[link_freq_index].reg_list; > > +ret = gc08a3_write_reg_list(gc08a3, reg_list); > > +if (ret) { > > +dev_err(gc08a3->dev, "could not sent common table %d\n", ret); > > +goto error; > > +} > > + > > +mode = gc08a3->cur_mode; > > +dev_info(gc08a3->dev, "----write regtbl: mode(id:%d, WxH:%dx%d)", > > + mode->mode_id, mode->width, mode->height); > > +reg_list = &mode->reg_list; > > + > > +ret = gc08a3_write_reg_list(gc08a3, reg_list); > > +if (ret < 0) { > > +dev_err(gc08a3->dev, "could not sent mode table %d\n", ret); > > +goto error; > > +} > > +ret = __v4l2_ctrl_handler_setup(&gc08a3->ctrls); > > +if (ret < 0) { > > +dev_err(gc08a3->dev, "could not sync v4l2 controls\n"); > > +goto error; > > +} > > + > > +ret = gc08a3_write_reg(gc08a3, GC08A3_STRAEMING_REG, > > + GC08A3_REG_VALUE_08BIT, 1); > > +if (ret < 0) { > > +dev_err(gc08a3->dev, "write STRAEMING_REG failed: %d\n", ret); > > +goto error; > > +} > > + > > +mutex_unlock(&gc08a3->mutex); > > + > > +dev_info(gc08a3->dev, "%s --\n", __func__); > > + > > +return 0; > > + > > +error: > > +mutex_unlock(&gc08a3->mutex); > > +return ret; > > +} > > + > > +static int gc08a3_stop_streaming(struct gc08a3 *gc08a3) > > +{ > > +int ret; > > + > > +dev_info(gc08a3->dev, "%s ++\n", __func__); > > + > > +ret = gc08a3_write_reg(gc08a3, GC08A3_STRAEMING_REG, > > + GC08A3_REG_VALUE_08BIT, 0); > > +if (ret < 0) > > +dev_err(gc08a3->dev, "could not sent stop streaming %d\n", ret); > > + > > +dev_info(gc08a3->dev, "%s --\n", __func__); > > + > > +return ret; > > +} > > + > > +static int gc08a3_s_stream(struct v4l2_subdev *subdev, int enable) > > +{ > > +struct gc08a3 *gc08a3 = to_gc08a3(subdev); > > +int ret; > > + > > +if (gc08a3->streaming == enable) > > +return 0; > > + > > +if (enable) { > > +ret = pm_runtime_resume_and_get(gc08a3->dev); > > +if (ret < 0) > > +return ret; > > + > > +ret = gc08a3_start_streaming(gc08a3); > > +if (ret < 0) > > +goto err_rpm_put; > > +} else { > > +ret = gc08a3_stop_streaming(gc08a3); > > +if (ret < 0) > > +goto err_rpm_put; > > +pm_runtime_put(gc08a3->dev); > > +} > > + > > +gc08a3->streaming = enable; > > +return 0; > > + > > +err_rpm_put: > > +pm_runtime_put(gc08a3->dev); > > +return ret; > > +} > > + > > +static int gc08a3_g_mbus_config(struct v4l2_subdev *sd, unsigned > int pad, > > +struct v4l2_mbus_config *config) > > +{ > > +config->type = V4L2_MBUS_CSI2_DPHY; > > +config->bus.mipi_csi2.num_data_lanes = 4; > > +config->bus.mipi_csi2.flags = 0; > > +return 0; > > +} > > + > > +static int gc08a3_g_frame_interval(struct v4l2_subdev *subdev, > > + struct v4l2_subdev_frame_interval *fival) > > +{ > > +struct gc08a3 *gc08a3 = to_gc08a3(subdev); > > + > > +fival->interval.numerator = 1; > > +fival->interval.denominator = gc08a3->cur_mode->max_framerate / > 10; > > + > > +return 0; > > +} > > + > > +static int > > +gc08a3_enum_frame_interval(struct v4l2_subdev *subdev, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_frame_interval_enum *fie) > > +{ > > +const struct gc08a3_mode *mode; > > + > > +if (fie->index != 0) > > +return -EINVAL; > > + > > +mode = v4l2_find_nearest_size(gc08a3_modes, > ARRAY_SIZE(gc08a3_modes), > > + width, height, fie->width, fie->height); > > + > > +fie->code = GC08A3_MBUS_CODE; > > +fie->width = mode->width; > > +fie->height = mode->height; > > +fie->interval.numerator = 1; > > +fie->interval.denominator = mode->max_framerate / 10; > > + > > +return 0; > > +} > > + > > +static const struct v4l2_subdev_video_ops gc08a3_video_ops = { > > +.s_stream = gc08a3_s_stream, > > +.g_frame_interval = gc08a3_g_frame_interval, > > +.s_frame_interval = gc08a3_g_frame_interval, > > +}; > > + > > +static const struct v4l2_subdev_pad_ops gc08a3_subdev_pad_ops = { > > +.enum_mbus_code = gc08a3_enum_mbus_code, > > +.enum_frame_size = gc08a3_enum_frame_size, > > +.enum_frame_interval = gc08a3_enum_frame_interval, > > +.get_fmt = gc08a3_get_format, > > +.set_fmt = gc08a3_set_format, > > +.get_selection = gc08a3_get_selection, > > +.init_cfg = gc08a3_entity_init_cfg, > > +.get_mbus_config = gc08a3_g_mbus_config, > > +}; > > + > > +static const struct v4l2_subdev_ops gc08a3_subdev_ops = { > > +.core = &gc08a3_core_ops, > > +.video = &gc08a3_video_ops, > > +.pad = &gc08a3_subdev_pad_ops, > > +}; > > + > > +static const struct regmap_config sensor_regmap_config = { > > +.reg_bits = 16, > > +.val_bits = 8, > > +.cache_type = REGCACHE_RBTREE, > > +}; > > + > > +static int gc08a3_get_regulators(struct device *dev, struct gc08a3 > *gc08a3) > > +{ > > +unsigned int i; > > + > > +for (i = 0; i < GC08A3_NUM_SUPPLIES; i++) > > +gc08a3->supplies[i].supply = gc08a3_supply_name[i]; > > + > > +return devm_regulator_bulk_get(dev, GC08A3_NUM_SUPPLIES, > > + gc08a3->supplies); > > +} > > + > > +static int gc08a3_parse_fwnode(struct device *dev) > > +{ > > +struct fwnode_handle *endpoint; > > +struct v4l2_fwnode_endpoint bus_cfg = { > > +.bus_type = V4L2_MBUS_CSI2_DPHY, > > +}; > > +unsigned int i, j; > > +int ret; > > + > > +endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); > > +if (!endpoint) { > > +dev_err(dev, "endpoint node not found\n"); > > +return -EINVAL; > > +} > > + > > +ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); > > +if (ret) { > > +dev_err(dev, "parsing endpoint node failed\n"); > > +goto done; > > +} > > + > > +if (!bus_cfg.nr_of_link_frequencies) { > > +dev_err(dev, "no link frequencies defined"); > > +ret = -EINVAL; > > +goto done; > > +} > > + > > +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, please check DT", > > +link_freq_menu_items[i]); > > +ret = -EINVAL; > > +goto done; > > +} > > +} > > + > > +done: > > +v4l2_fwnode_endpoint_free(&bus_cfg); > > +fwnode_handle_put(endpoint); > > +return ret; > > +} > > + > > +static int __maybe_unused gc08a3_suspend(struct device *dev) > > +{ > > +struct i2c_client *client = to_i2c_client(dev); > > +struct v4l2_subdev *sd = i2c_get_clientdata(client); > > +struct gc08a3 *gc08a3 = to_gc08a3(sd); > > + > > +if (gc08a3->streaming) > > +gc08a3_stop_streaming(gc08a3); > > + > > +return 0; > > +} > > + > > +static int __maybe_unused gc08a3_resume(struct device *dev) > > +{ > > +struct i2c_client *client = to_i2c_client(dev); > > +struct v4l2_subdev *sd = i2c_get_clientdata(client); > > +struct gc08a3 *gc08a3 = to_gc08a3(sd); > > +int ret; > > + > > +if (gc08a3->streaming) { > > +ret = gc08a3_start_streaming(gc08a3); > > +if (ret) > > +goto error; > > +} > > + > > +return 0; > > + > > +error: > > +gc08a3_stop_streaming(gc08a3); > > +gc08a3->streaming = 0; > > +return ret; > > +} > > No need for system suspend/resume operations. Please read > Documentation/driver-api/media/camera-sensor.rst in the master branch > of > git://linuxtv.org/media_stage.git for an explanation. > [mtk]: fixed in patch:v3 > > + > > +static int gc08a3_init_controls(struct gc08a3 *gc08a3) > > +{ > > +struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); > > +const struct v4l2_ctrl_ops *ops = &gc08a3_ctrl_ops; > > +struct v4l2_fwnode_device_properties props; > > +struct v4l2_ctrl_handler *ctrl_hdlr; > > +s64 exposure_max, h_blank; > > +int ret; > > + > > +ctrl_hdlr = &gc08a3->ctrls; > > +ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); > > +if (ret) > > +return ret; > > + > > +ctrl_hdlr->lock = &gc08a3->mutex; > > + > > +gc08a3->link_freq = > > +v4l2_ctrl_new_int_menu(ctrl_hdlr, > > + &gc08a3_ctrl_ops, > > + V4L2_CID_LINK_FREQ, > > + ARRAY_SIZE(link_freq_menu_items) - 1, > > + 0, link_freq_menu_items); > > +if (gc08a3->link_freq) > > +gc08a3->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > + > > +gc08a3->pixel_rate = > > +v4l2_ctrl_new_std(ctrl_hdlr, > > + &gc08a3_ctrl_ops, > > + V4L2_CID_PIXEL_RATE, 0, > > + to_pixel_rate(GC08A3_LINK_FREQ_336MHZ_CFG), 1, > > + to_pixel_rate(GC08A3_LINK_FREQ_336MHZ_CFG)); > > + > > +gc08a3->vblank = > > +v4l2_ctrl_new_std(ctrl_hdlr, > > + &gc08a3_ctrl_ops, V4L2_CID_VBLANK, > > + gc08a3->cur_mode->vts_min - gc08a3->cur_mode->height, > > + GC08A3_VTS_MAX - gc08a3->cur_mode->height, 1, > > + gc08a3->cur_mode->vts_def - gc08a3->cur_mode->height); > > + > > +h_blank = gc08a3->cur_mode->hts - gc08a3->cur_mode->width; > > +gc08a3->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, > > + V4L2_CID_HBLANK, h_blank, h_blank, 1, > > + h_blank); > > +if (gc08a3->hblank) > > +gc08a3->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > + > > +v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, > > + V4L2_CID_ANALOGUE_GAIN, GC08A3_AGAIN_MIN, > > + GC08A3_AGAIN_MAX, GC08A3_AGAIN_STEP, > > + GC08A3_AGAIN_MIN); > > + > > +exposure_max = gc08a3->cur_mode->vts_def - GC08A3_EXP_MARGIN; > > +gc08a3->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, > > + V4L2_CID_EXPOSURE, GC08A3_EXP_MIN, > > + exposure_max, GC08A3_EXP_STEP, > > + exposure_max); > > + > > +v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &gc08a3_ctrl_ops, > > + V4L2_CID_TEST_PATTERN, > > + ARRAY_SIZE(gc08a3_test_pattern_menu) - 1, > > + 0, 0, gc08a3_test_pattern_menu); > > + > > +v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, V4L2_CID_HFLIP, 0, > > + 1, 1, 0); > > + > > +v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, V4L2_CID_VFLIP, 0, > > + 1, 1, 0); > > + > > +/* register properties to fwnode (e.g. rotation, orientation) */ > > +ret = v4l2_fwnode_device_parse(&client->dev, &props); > > +if (ret) > > +goto error_ctrls; > > + > > +ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, ops, &props); > > +if (ret) > > +goto error_ctrls; > > + > > +if (ctrl_hdlr->error) { > > +ret = ctrl_hdlr->error; > > +goto error_ctrls; > > +} > > + > > +gc08a3->sd.ctrl_handler = ctrl_hdlr; > > + > > +return 0; > > + > > +error_ctrls: > > +v4l2_ctrl_handler_free(ctrl_hdlr); > > + > > +return ret; > > +} > > + > > +static int gc08a3_probe(struct i2c_client *client) > > +{ > > +struct device *dev = &client->dev; > > +struct gc08a3 *gc08a3; > > +int ret; > > + > > +dev_info(dev, "--- %s +", __func__); > > + > > +ret = gc08a3_parse_fwnode(dev); > > +if (ret) > > +return ret; > > + > > +gc08a3 = devm_kzalloc(dev, sizeof(*gc08a3), GFP_KERNEL); > > +if (!gc08a3) > > +return -ENOMEM; > > + > > +gc08a3->dev = dev; > > + > > +gc08a3->xclk = devm_clk_get(dev, NULL); > > +if (IS_ERR(gc08a3->xclk)) { > > +dev_err(dev, "could not get xclk\n"); > > +return PTR_ERR(gc08a3->xclk); > > +} > > + > > +ret = clk_set_rate(gc08a3->xclk, GC08A3_DEFAULT_CLK_FREQ); > > +if (ret) { > > +dev_err(dev, "could not set xclk frequency\n"); > > +return ret; > > +} > > + > > +ret = gc08a3_get_regulators(dev, gc08a3); > > +if (ret < 0) { > > +dev_err(dev, "cannot get regulators\n"); > > +return ret; > > +} > > + > > +gc08a3->enable_gpio = devm_gpiod_get(dev, "enable", > GPIOD_OUT_LOW); > > +if (IS_ERR(gc08a3->enable_gpio)) { > > +dev_err(dev, "cannot get enable gpio\n"); > > +return PTR_ERR(gc08a3->enable_gpio); > > +} > > + > > +gc08a3->regmap = devm_regmap_init_i2c(client, > &sensor_regmap_config); > > +if (IS_ERR(gc08a3->regmap)) { > > +dev_err(dev, "regmap init failed\n"); > > +return PTR_ERR(gc08a3->regmap); > > +} > > + > > +v4l2_i2c_subdev_init(&gc08a3->sd, client, &gc08a3_subdev_ops); > > + > > +gc08a3_power_on(gc08a3->dev); > > + > > +ret = gc08a3_identify_module(gc08a3); > > +if (ret) { > > +dev_err(&client->dev, "failed to find sensor: %d\n", ret); > > +gc08a3_power_off(gc08a3->dev); > > +return ret; > > +} > > + > > +mutex_init(&gc08a3->mutex); > > +gc08a3->cur_mode = &gc08a3_modes[0]; > > + > > +ret = gc08a3_init_controls(gc08a3); > > +if (ret) { > > +dev_err(&client->dev, "failed to init controls: %d", ret); > > +goto free_ctrl; > > +} > > + > > +gc08a3->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > > +gc08a3->pad.flags = MEDIA_PAD_FL_SOURCE; > > +gc08a3->sd.dev = &client->dev; > > +gc08a3->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; > > + > > +dev_dbg(&client->dev, "gc08a3->sd.name: %s, dev->of_node->name: > %s\n", > > +gc08a3->sd.name, dev->of_node->name); > > +if (V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2 < > > + strlen(dev->of_node->name)) { > > +dev_err(&client->dev, > > +"the string length of (sd.name + of_node->name) is too long.\n"); > > +return -EINVAL; > > +} > > +strncat(gc08a3->sd.name, " ", 1); > > +strncat(gc08a3->sd.name, dev->of_node->name, > > +V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2); > > +dev_dbg(&client->dev, "after: gc08a3->sd.name: %s\n", gc08a3- > >sd.name); > > + > > +ret = media_entity_pads_init(&gc08a3->sd.entity, 1, &gc08a3->pad); > > +if (ret < 0) { > > +dev_err(dev, "could not register media entity\n"); > > +goto free_ctrl; > > +} > > + > > +ret = v4l2_async_register_subdev_sensor(&gc08a3->sd); > > +if (ret < 0) { > > +dev_err(dev, "could not register v4l2 device\n"); > > +goto free_entity; > > +} > > + > > +pm_runtime_set_active(gc08a3->dev); > > +pm_runtime_enable(gc08a3->dev); > > +pm_runtime_idle(gc08a3->dev); > > Please see the imx219 driver for an example of how to correctly > implement runtime PM support. Please also use PM runtime autosuspend. > [mtk]: fixed in patch:v3 > > + > > +dev_info(dev, "--- %s -", __func__); > > + > > +return 0; > > + > > +free_entity: > > +media_entity_cleanup(&gc08a3->sd.entity); > > +free_ctrl: > > +mutex_destroy(&gc08a3->mutex); > > +v4l2_ctrl_handler_free(&gc08a3->ctrls); > > +pm_runtime_disable(gc08a3->dev); > > + > > +return ret; > > +} > > + > > +static void gc08a3_remove(struct i2c_client *client) > > +{ > > +struct v4l2_subdev *sd = i2c_get_clientdata(client); > > +struct gc08a3 *gc08a3 = to_gc08a3(sd); > > + > > +v4l2_async_unregister_subdev(&gc08a3->sd); > > +media_entity_cleanup(&gc08a3->sd.entity); > > +v4l2_ctrl_handler_free(&gc08a3->ctrls); > > + > > +pm_runtime_disable(&client->dev); > > +pm_runtime_set_suspended(&client->dev); > > + > > +mutex_destroy(&gc08a3->mutex); > > +} > > + > > +static const struct of_device_id gc08a3_of_match[] = { > > +{ .compatible = "GalaxyCore,gc08a3" }, > > +{} > > +}; > > +MODULE_DEVICE_TABLE(of, gc08a3_of_match); > > + > > +static const struct dev_pm_ops gc08a3_pm_ops = { > > +SET_SYSTEM_SLEEP_PM_OPS(gc08a3_suspend, gc08a3_resume) > > +SET_RUNTIME_PM_OPS(gc08a3_power_off, gc08a3_power_on, NULL) > > +}; > > + > > +static struct i2c_driver gc08a3_i2c_driver = { > > +.driver = { > > +.of_match_table = gc08a3_of_match, > > +.pm = &gc08a3_pm_ops, > > +.name = "gc08a3", > > +}, > > +.probe_new = gc08a3_probe, > > +.remove = gc08a3_remove, > > +}; > > + > > +module_i2c_driver(gc08a3_i2c_driver); > > + > > +MODULE_DESCRIPTION("GalaxyCore gc08a3 Camera driver"); > > +MODULE_AUTHOR("Zhi Mao <zhi.mao@mediatek.com>"); > > +MODULE_LICENSE("GPL"); > > -- > Regards, > > Laurent Pinchart
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 59ee0ca2c978..2e37be537690 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1451,6 +1451,20 @@ config VIDEO_THS7303 To compile this driver as a module, choose M here: the module will be called ths7303. +config VIDEO_GC08A3 + tristate "GalaxyCore gc08a3 sensor support" + depends on GPIOLIB && I2C && VIDEO_DEV + select V4L2_FWNODE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP_I2C + help + This is a Video4Linux2 sensor driver for the GalaxyCore gc08a3 + camera. + + To compile this driver as a module, choose M here: the + module will be called gc08a3. + endmenu # diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index f5010f80a21f..ec40dbd75e7a 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_VIDEO_DW9719) += dw9719.o obj-$(CONFIG_VIDEO_DW9768) += dw9768.o obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ +obj-$(CONFIG_VIDEO_GC08A3) += gc08a3.o obj-$(CONFIG_VIDEO_HI556) += hi556.o obj-$(CONFIG_VIDEO_HI846) += hi846.o obj-$(CONFIG_VIDEO_HI847) += hi847.o diff --git a/drivers/media/i2c/gc08a3.c b/drivers/media/i2c/gc08a3.c new file mode 100644 index 000000000000..d3cf62b65c2e --- /dev/null +++ b/drivers/media/i2c/gc08a3.c @@ -0,0 +1,2046 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * gc08a3.c - gc08a3 sensor driver + * + * Copyright 2023 Mediatek + * + * Zhi Mao <zhi.mao@mediatek.com> + */ + +#include <asm/unaligned.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +//========================================= +#define GC08A3_REG_VALUE_08BIT 1 +#define GC08A3_REG_VALUE_16BIT 2 +#define GC08A3_REG_VALUE_24BIT 3 + +#define GC08A3_REG_CHIP_ID 0x03f0 +#define GC08A3_CHIP_ID 0x08a3 + +#define GC08A3_NATIVE_WIDTH 3264 +#define GC08A3_NATIVE_HEIGHT 2448 + +#define GC08A3_REG_TEST_PATTERN_EN 0x008c +#define GC08A3_REG_TEST_PATTERN_IDX 0x008d +#define GC08A3_TEST_PATTERN_EN 0x01 + +#define GC08A3_STRAEMING_REG 0x0100 + +#define GC08A3_SCLK 280000000LL + +#define GC08A3_DEFAULT_CLK_FREQ 24000000 +#define GC08A3_FPS 30 +#define GC08A3_MBUS_CODE MEDIA_BUS_FMT_SRGGB10_1X10 +#define GC08A3_DATA_LANES 4 + +//for 1920*1080 +#define GC08A3_LINK_FREQ_207MHZ 207000000ULL +//for 3264*2448 +#define GC08A3_LINK_FREQ_336MHZ 336000000ULL + +#define GC08A3_RGB_DEPTH 10 + +//frame length +#define GC08A3_FL_REG 0x0340 +#define GC08A3_VTS_30FPS 2548 +#define GC08A3_VTS_30FPS_MIN 2548 +#define GC08A3_VTS_60FPS 1276 +#define GC08A3_VTS_60FPS_MIN 1276 +#define GC08A3_VTS_MAX 0xfff0 +#define GC08A3_FL_MARGIN 48 + +// line length +#define GC08A3_LL_REG 0x0342 +#define GC08A3_HTS_30FPS 3640 +#define GC08A3_HTS_60FPS 3640 + +#define GC08A3_EXP_REG 0x0202 +#define GC08A3_EXP_MARGIN 16 +#define GC08A3_EXP_MIN 4 +#define GC08A3_EXP_STEP 1 + +#define GC08A3_FLIP_REG 0x0101 +#define GC08A3_FLIP_H_MASK 0x1 +#define GC08A3_FLIP_V_MASK 0x2 + +#define GC08A3_AGAIN_REG 0x0204 +#define GC08A3_AGAIN_MIN 1024 +#define GC08A3_AGAIN_MAX (1024 * 16) +#define GC08A3_AGAIN_STEP 1 + +#define GC08A3_DGAIN_REG 0x020e +#define GC08A3_DGAIN_MIN 1024 +#define GC08A3_DGAIN_MAX 1024 +#define GC08A3_DGAIN_STEP 1 +//============================================================ + +//============================================================ +static const char *const gc08a3_test_pattern_menu[] = { + "No Pattern", "Solid Black", "Solid White", "Solid Red", + "Solid Green", "Solid Blue", "Solid Yellow", "Colour Bar", +}; + +enum { + GC08A3_LINK_FREQ_336MHZ_CFG, + GC08A3_LINK_FREQ_207MHZ_CFG, +}; + +static const s64 link_freq_menu_items[] = { + GC08A3_LINK_FREQ_336MHZ, + GC08A3_LINK_FREQ_207MHZ, +}; + +static const char *const gc08a3_supply_name[] = { + "avdd", + "dvdd", + "dovdd", +}; + +#define GC08A3_NUM_SUPPLIES ARRAY_SIZE(gc08a3_supply_name) + +struct gc08a3 { + struct device *dev; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt fmt; + struct i2c_client *client; + + struct v4l2_rect crop; + + struct clk *xclk; + struct regulator_bulk_data supplies[GC08A3_NUM_SUPPLIES]; + struct gpio_desc *enable_gpio; + + struct regmap *regmap; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + + /* + * Serialize control access, get/set format, get selection + * and start streaming. + */ + struct mutex mutex; + + bool streaming; + + /* Current mode */ + const struct gc08a3_mode *cur_mode; +}; + +struct reg_8 { + u16 address; + u8 val; +}; + +struct gc08a3_reg_list { + u32 num_of_regs; + const struct reg_8 *regs; +}; + +struct gc08a3_link_freq_config { + const struct gc08a3_reg_list reg_list; +}; + +enum { + GC08A3_TABLE_WAIT_MS = 0, + GC08A3_TABLE_END, +}; + +/*From gc08a3_mode_tbls.h*/ +static const struct reg_8 mode_3264x2448[] = { + /*system*/ + { 0x031c, 0x60 }, + { 0x0337, 0x04 }, + { 0x0335, 0x51 }, + { 0x0336, 0x70 }, + { 0x0383, 0xbb }, + { 0x031a, 0x00 }, + { 0x0321, 0x10 }, + { 0x0327, 0x03 }, + { 0x0325, 0x40 }, + { 0x0326, 0x23 }, + { 0x0314, 0x11 }, + { 0x0315, 0xd6 }, + { 0x0316, 0x01 }, + { 0x0334, 0x40 }, + { 0x0324, 0x42 }, + { 0x031c, 0x00 }, + { 0x031c, 0x9f }, + { 0x0344, 0x00 }, + { 0x0345, 0x06 }, + { 0x0346, 0x00 }, + { 0x0347, 0x04 }, + { 0x0348, 0x0c }, + { 0x0349, 0xd0 }, //3280 + { 0x034a, 0x09 }, + { 0x034b, 0x9c }, //2460 + { 0x0202, 0x09 }, + { 0x0203, 0x04 }, //Exp + { 0x0340, 0x09 }, + { 0x0341, 0xf4 }, //FL + { 0x0342, 0x07 }, + { 0x0343, 0x1c }, //LineLength + + { 0x0226, 0x00 }, //min vb[15:8] + { 0x0227, 0x28 }, //min vb[7:0] + { 0x0e38, 0x49 }, + { 0x0210, 0x13 }, + { 0x0218, 0x00 }, + { 0x0241, 0x88 }, + { 0x0392, 0x60 }, + + /*ISP*/ + { 0x031c, 0x80 }, + { 0x03fe, 0x10 }, //CISCTL_rst + { 0x03fe, 0x00 }, + { 0x031c, 0x9f }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x031c, 0x80 }, + { 0x03fe, 0x10 }, + { 0x03fe, 0x00 }, + { 0x031c, 0x9f }, + { 0x00a2, 0x00 }, + { 0x00a3, 0x00 }, + { 0x00ab, 0x00 }, + { 0x00ac, 0x00 }, + { 0x05a0, 0x82 }, + { 0x05ac, 0x00 }, + { 0x05ad, 0x01 }, + { 0x05ae, 0x00 }, + { 0x0800, 0x0a }, + { 0x0801, 0x14 }, + { 0x0802, 0x28 }, + { 0x0803, 0x34 }, + { 0x0804, 0x0e }, + { 0x0805, 0x33 }, + { 0x0806, 0x03 }, + { 0x0807, 0x8a }, + { 0x0808, 0x50 }, + { 0x0809, 0x00 }, + { 0x080a, 0x34 }, + { 0x080b, 0x03 }, + { 0x080c, 0x26 }, + { 0x080d, 0x03 }, + { 0x080e, 0x18 }, + { 0x080f, 0x03 }, + { 0x0810, 0x10 }, + { 0x0811, 0x03 }, + { 0x0812, 0x00 }, + { 0x0813, 0x00 }, + { 0x0814, 0x01 }, + { 0x0815, 0x00 }, + { 0x0816, 0x01 }, + { 0x0817, 0x00 }, + { 0x0818, 0x00 }, + { 0x0819, 0x0a }, + { 0x081a, 0x01 }, + { 0x081b, 0x6c }, + { 0x081c, 0x00 }, + { 0x081d, 0x0b }, + { 0x081e, 0x02 }, + { 0x081f, 0x00 }, + { 0x0820, 0x00 }, + { 0x0821, 0x0c }, + { 0x0822, 0x02 }, + { 0x0823, 0xd9 }, + { 0x0824, 0x00 }, + { 0x0825, 0x0d }, + { 0x0826, 0x03 }, + { 0x0827, 0xf0 }, + { 0x0828, 0x00 }, + { 0x0829, 0x0e }, + { 0x082a, 0x05 }, + { 0x082b, 0x94 }, + { 0x082c, 0x09 }, + { 0x082d, 0x6e }, + { 0x082e, 0x07 }, + { 0x082f, 0xe6 }, + { 0x0830, 0x10 }, + { 0x0831, 0x0e }, + { 0x0832, 0x0b }, + { 0x0833, 0x2c }, + { 0x0834, 0x14 }, + { 0x0835, 0xae }, + { 0x0836, 0x0f }, + { 0x0837, 0xc4 }, + { 0x0838, 0x18 }, + { 0x0839, 0x0e }, + { 0x05ac, 0x01 }, + { 0x059a, 0x00 }, + { 0x059b, 0x00 }, + { 0x059c, 0x01 }, + { 0x0598, 0x00 }, + { 0x0597, 0x14 }, + { 0x05ab, 0x09 }, + { 0x05a4, 0x02 }, + { 0x05a3, 0x05 }, + { 0x05a0, 0xc2 }, + { 0x0207, 0xc4 }, + + /*GAIN*/ + { 0x0204, 0x04 }, + { 0x0205, 0x00 }, + { 0x0050, 0x5c }, + { 0x0051, 0x44 }, + + /*out window*/ + { 0x009a, 0x66 }, + { 0x0351, 0x00 }, + { 0x0352, 0x06 }, //out_win_y1 + { 0x0353, 0x00 }, + { 0x0354, 0x08 }, //out_win_x1 + { 0x034c, 0x0c }, + { 0x034d, 0xc0 }, //3264 + { 0x034e, 0x09 }, + { 0x034f, 0x90 }, //2448 + + /*MIPI*/ + { 0x0114, 0x03 }, //0:1lane 1:2lane 3:4lane + { 0x0180, 0x65 }, //[3:0]dphy_mipi_diff + { 0x0181, 0xf0 }, + { 0x0185, 0x01 }, + { 0x0115, 0x30 }, + { 0x011b, 0x12 }, + { 0x011c, 0x12 }, + { 0x0121, 0x06 }, //T_LPX + { 0x0122, 0x06 }, //T_CLK_HS_PREPARE + { 0x0123, 0x15 }, //T_CLK_zero + { 0x0124, 0x01 }, //T_CLK_PRE + { 0x0125, 0x0b }, //T_CLK_POST + { 0x0126, 0x08 }, //T_CLK_TRAIL + { 0x0129, 0x06 }, //T_HS_PREPARE + { 0x012a, 0x08 }, //T_HS_Zero + { 0x012b, 0x08 }, //T_HS_TRAIL + + { 0x0a73, 0x60 }, + { 0x0a70, 0x11 }, + { 0x0313, 0x80 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0a70, 0x00 }, + { 0x00a4, 0x80 }, + { 0x0316, 0x01 }, + { 0x0a67, 0x00 }, + { 0x0084, 0x10 }, + { 0x0102, 0x09 }, + + { GC08A3_TABLE_END, 0x00 } +}; + +static const struct reg_8 mode_1920x1080[] = { + /*system*/ + { 0x031c, 0x60 }, + { 0x0337, 0x04 }, + { 0x0335, 0x51 }, + { 0x0336, 0x45 }, + { 0x0383, 0x8b }, + { 0x031a, 0x00 }, + { 0x0321, 0x10 }, + { 0x0327, 0x03 }, + { 0x0325, 0x40 }, + { 0x0326, 0x23 }, + { 0x0314, 0x11 }, + { 0x0315, 0xd6 }, + { 0x0316, 0x01 }, + { 0x0334, 0x40 }, + { 0x0324, 0x42 }, + { 0x031c, 0x00 }, + { 0x031c, 0x9f }, + { 0x0344, 0x02 }, + { 0x0345, 0xa6 }, + { 0x0346, 0x02 }, + { 0x0347, 0xb0 }, + { 0x0348, 0x07 }, + { 0x0349, 0x90 }, //1936 + { 0x034a, 0x04 }, + { 0x034b, 0x44 }, //1092 + { 0x0202, 0x03 }, + { 0x0203, 0x00 }, //Exp + { 0x0340, 0x04 }, + { 0x0341, 0xfc }, //FL + { 0x0342, 0x07 }, + { 0x0343, 0x1c }, //LineLength + + { 0x0226, 0x00 }, //min vb[15:8] + { 0x0227, 0x88 }, //min vb[7:0] + { 0x0e38, 0x49 }, + { 0x0210, 0x13 }, + { 0x0218, 0x00 }, + { 0x0241, 0x88 }, + { 0x0392, 0x60 }, + + /*ISP*/ + { 0x031c, 0x80 }, + { 0x03fe, 0x10 }, //CISCTL_rst + { 0x03fe, 0x00 }, + { 0x031c, 0x9f }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x031c, 0x80 }, + { 0x03fe, 0x10 }, + { 0x03fe, 0x00 }, + { 0x031c, 0x9f }, + { 0x00a2, 0xac }, + { 0x00a3, 0x02 }, + { 0x00ab, 0xa0 }, + { 0x00ac, 0x02 }, + { 0x05a0, 0x82 }, + { 0x05ac, 0x00 }, + { 0x05ad, 0x01 }, + { 0x05ae, 0x00 }, + { 0x0800, 0x0a }, + { 0x0801, 0x14 }, + { 0x0802, 0x28 }, + { 0x0803, 0x34 }, + { 0x0804, 0x0e }, + { 0x0805, 0x33 }, + { 0x0806, 0x03 }, + { 0x0807, 0x8a }, + { 0x0808, 0x50 }, + { 0x0809, 0x00 }, + { 0x080a, 0x34 }, + { 0x080b, 0x03 }, + { 0x080c, 0x26 }, + { 0x080d, 0x03 }, + { 0x080e, 0x18 }, + { 0x080f, 0x03 }, + { 0x0810, 0x10 }, + { 0x0811, 0x03 }, + { 0x0812, 0x00 }, + { 0x0813, 0x00 }, + { 0x0814, 0x01 }, + { 0x0815, 0x00 }, + { 0x0816, 0x01 }, + { 0x0817, 0x00 }, + { 0x0818, 0x00 }, + { 0x0819, 0x0a }, + { 0x081a, 0x01 }, + { 0x081b, 0x6c }, + { 0x081c, 0x00 }, + { 0x081d, 0x0b }, + { 0x081e, 0x02 }, + { 0x081f, 0x00 }, + { 0x0820, 0x00 }, + { 0x0821, 0x0c }, + { 0x0822, 0x02 }, + { 0x0823, 0xd9 }, + { 0x0824, 0x00 }, + { 0x0825, 0x0d }, + { 0x0826, 0x03 }, + { 0x0827, 0xf0 }, + { 0x0828, 0x00 }, + { 0x0829, 0x0e }, + { 0x082a, 0x05 }, + { 0x082b, 0x94 }, + { 0x082c, 0x09 }, + { 0x082d, 0x6e }, + { 0x082e, 0x07 }, + { 0x082f, 0xe6 }, + { 0x0830, 0x10 }, + { 0x0831, 0x0e }, + { 0x0832, 0x0b }, + { 0x0833, 0x2c }, + { 0x0834, 0x14 }, + { 0x0835, 0xae }, + { 0x0836, 0x0f }, + { 0x0837, 0xc4 }, + { 0x0838, 0x18 }, + { 0x0839, 0x0e }, + { 0x05ac, 0x01 }, + { 0x059a, 0x00 }, + { 0x059b, 0x00 }, + { 0x059c, 0x01 }, + { 0x0598, 0x00 }, + { 0x0597, 0x14 }, + { 0x05ab, 0x09 }, + { 0x05a4, 0x02 }, + { 0x05a3, 0x05 }, + { 0x05a0, 0xc2 }, + { 0x0207, 0xc4 }, + + /*GAIN*/ + { 0x0204, 0x04 }, + { 0x0205, 0x00 }, + { 0x0050, 0x38 }, + { 0x0051, 0x20 }, + + /*out window*/ + { 0x009a, 0x66 }, + { 0x0351, 0x00 }, + { 0x0352, 0x06 }, //out_win_y1 + { 0x0353, 0x00 }, + { 0x0354, 0x08 }, //out_win_x1 + { 0x034c, 0x07 }, + { 0x034d, 0x80 }, //1920 + { 0x034e, 0x04 }, + { 0x034f, 0x38 }, //1080 + + /*MIPI*/ + { 0x0114, 0x03 }, //0:1lane 1:2lane 3:4lane + { 0x0180, 0x65 }, //[3:0]dphy_mipi_diff + { 0x0181, 0xf0 }, + { 0x0185, 0x01 }, + { 0x0115, 0x30 }, + { 0x011b, 0x12 }, + { 0x011c, 0x12 }, + { 0x0121, 0x02 }, //T_LPX + { 0x0122, 0x03 }, //T_CLK_HS_PREPARE + { 0x0123, 0x0c }, //T_CLK_zero + { 0x0124, 0x00 }, //T_CLK_PRE + { 0x0125, 0x09 }, //T_CLK_POST + { 0x0126, 0x06 }, //T_CLK_TRAIL + { 0x0129, 0x04 }, //T_HS_PREPARE + { 0x012a, 0x03 }, //T_HS_Zero + { 0x012b, 0x06 }, //T_HS_TRAIL + + { 0x0a73, 0x60 }, + { 0x0a70, 0x11 }, + { 0x0313, 0x80 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0aff, 0x00 }, + { 0x0a70, 0x00 }, + { 0x00a4, 0x80 }, + { 0x0316, 0x01 }, + { 0x0a67, 0x00 }, + { 0x0084, 0x10 }, + { 0x0102, 0x09 }, + + { GC08A3_TABLE_END, 0x00 } +}; + +static const struct reg_8 mode_table_common[] = { + { GC08A3_STRAEMING_REG, 0x00 }, + /*system*/ + { 0x031c, 0x60 }, + { 0x0337, 0x04 }, + { 0x0335, 0x51 }, + { 0x0336, 0x70 }, + { 0x0383, 0xbb }, + { 0x031a, 0x00 }, + { 0x0321, 0x10 }, + { 0x0327, 0x03 }, + { 0x0325, 0x40 }, + { 0x0326, 0x23 }, + { 0x0314, 0x11 }, + { 0x0315, 0xd6 }, + { 0x0316, 0x01 }, + { 0x0334, 0x40 }, + { 0x0324, 0x42 }, + { 0x031c, 0x00 }, + { 0x031c, 0x9f }, + { 0x039a, 0x13 }, + { 0x0084, 0x30 }, + { 0x02b3, 0x08 }, + { 0x0057, 0x0c }, + { 0x05c3, 0x50 }, + { 0x0311, 0x90 }, + { 0x05a0, 0x02 }, + { 0x0074, 0x0a }, + { 0x0059, 0x11 }, + { 0x0070, 0x05 }, + { 0x0101, 0x00 }, //[1]updown [0]mirror + + /*analog*/ + { 0x0344, 0x00 }, + { 0x0345, 0x06 }, + { 0x0346, 0x00 }, + { 0x0347, 0x04 }, + { 0x0348, 0x0c }, + { 0x0349, 0xd0 }, //3280 + { 0x034a, 0x09 }, + { 0x034b, 0x9c }, //2460 + { 0x0202, 0x09 }, + { 0x0203, 0x04 }, //Exp + + { 0x0219, 0x05 }, //[4]FL_depend_exp + { 0x0226, 0x00 }, //min vb[15:8] + { 0x0227, 0x28 }, //min vb[7:0] + { 0x0e0a, 0x00 }, + { 0x0e0b, 0x00 }, + { 0x0e24, 0x04 }, + { 0x0e25, 0x04 }, + { 0x0e26, 0x00 }, + { 0x0e27, 0x10 }, + { 0x0e01, 0x74 }, + { 0x0e03, 0x47 }, + { 0x0e04, 0x33 }, + { 0x0e05, 0x44 }, + { 0x0e06, 0x44 }, + { 0x0e0c, 0x1e }, + { 0x0e17, 0x3a }, + { 0x0e18, 0x3c }, + { 0x0e19, 0x40 }, + { 0x0e1a, 0x42 }, + { 0x0e28, 0x21 }, + { 0x0e2b, 0x68 }, + { 0x0e2c, 0x0d }, + { 0x0e2d, 0x08 }, + { 0x0e34, 0xf4 }, + { 0x0e35, 0x44 }, + { 0x0e36, 0x07 }, + { 0x0e38, 0x49 }, + { 0x0210, 0x13 }, + { 0x0218, 0x00 }, + { 0x0241, 0x88 }, + { 0x0e32, 0x00 }, + { 0x0e33, 0x18 }, + { 0x0e42, 0x03 }, + { 0x0e43, 0x80 }, + { 0x0e44, 0x04 }, + { 0x0e45, 0x00 }, + { 0x0e4f, 0x04 }, + { 0x057a, 0x20 }, + { 0x0381, 0x7c }, + { 0x0382, 0x9b }, + { 0x0384, 0xfb }, + { 0x0389, 0x38 }, + { 0x038a, 0x03 }, + { 0x0390, 0x6a }, + { 0x0391, 0x0b }, + { 0x0392, 0x60 }, + { 0x0393, 0xc1 }, + { 0x0396, 0xff }, + { 0x0398, 0x62 }, + + /*cisctl reset*/ + { 0x031c, 0x80 }, + { 0x03fe, 0x10 }, //CISCTL_rst + { 0x03fe, 0x00 }, + { 0x031c, 0x9f }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x03fe, 0x00 }, + { 0x031c, 0x80 }, + { 0x03fe, 0x10 }, //CISCTL_rst + { 0x03fe, 0x00 }, + { 0x031c, 0x9f }, + { 0x0360, 0x01 }, + { 0x0360, 0x00 }, + { 0x0316, 0x09 }, + { 0x0a67, 0x80 }, + { 0x0313, 0x00 }, + { 0x0a53, 0x0e }, + { 0x0a65, 0x17 }, + { 0x0a68, 0xa1 }, + { 0x0a58, 0x00 }, + { 0x0ace, 0x0c }, + { 0x00a4, 0x00 }, + { 0x00a5, 0x01 }, + { 0x00a7, 0x09 }, + { 0x00a8, 0x9c }, + { 0x00a9, 0x0c }, + { 0x00aa, 0xd0 }, + { 0x0a8a, 0x00 }, + { 0x0a8b, 0xe0 }, + { 0x0a8c, 0x13 }, + { 0x0a8d, 0xe8 }, + { 0x0a90, 0x0a }, + { 0x0a91, 0x10 }, + { 0x0a92, 0xf8 }, + { 0x0a71, 0xf2 }, + { 0x0a72, 0x12 }, + { 0x0a73, 0x64 }, + { 0x0a75, 0x41 }, + { 0x0a70, 0x07 }, + { 0x0313, 0x80 }, + + /*ISP*/ + { 0x00a0, 0x01 }, + { 0x0080, 0xd2 }, + { 0x0081, 0x3f }, + { 0x0087, 0x51 }, + { 0x0089, 0x03 }, + { 0x009b, 0x40 }, + { 0x05a0, 0x82 }, + { 0x05ac, 0x00 }, + { 0x05ad, 0x01 }, + { 0x05ae, 0x00 }, + { 0x0800, 0x0a }, + { 0x0801, 0x14 }, + { 0x0802, 0x28 }, + { 0x0803, 0x34 }, + { 0x0804, 0x0e }, + { 0x0805, 0x33 }, + { 0x0806, 0x03 }, + { 0x0807, 0x8a }, + { 0x0808, 0x50 }, + { 0x0809, 0x00 }, + { 0x080a, 0x34 }, + { 0x080b, 0x03 }, + { 0x080c, 0x26 }, + { 0x080d, 0x03 }, + { 0x080e, 0x18 }, + { 0x080f, 0x03 }, + { 0x0810, 0x10 }, + { 0x0811, 0x03 }, + { 0x0812, 0x00 }, + { 0x0813, 0x00 }, + { 0x0814, 0x01 }, + { 0x0815, 0x00 }, + { 0x0816, 0x01 }, + { 0x0817, 0x00 }, + { 0x0818, 0x00 }, + { 0x0819, 0x0a }, + { 0x081a, 0x01 }, + { 0x081b, 0x6c }, + { 0x081c, 0x00 }, + { 0x081d, 0x0b }, + { 0x081e, 0x02 }, + { 0x081f, 0x00 }, + { 0x0820, 0x00 }, + { 0x0821, 0x0c }, + { 0x0822, 0x02 }, + { 0x0823, 0xd9 }, + { 0x0824, 0x00 }, + { 0x0825, 0x0d }, + { 0x0826, 0x03 }, + { 0x0827, 0xf0 }, + { 0x0828, 0x00 }, + { 0x0829, 0x0e }, + { 0x082a, 0x05 }, + { 0x082b, 0x94 }, + { 0x082c, 0x09 }, + { 0x082d, 0x6e }, + { 0x082e, 0x07 }, + { 0x082f, 0xe6 }, + { 0x0830, 0x10 }, + { 0x0831, 0x0e }, + { 0x0832, 0x0b }, + { 0x0833, 0x2c }, + { 0x0834, 0x14 }, + { 0x0835, 0xae }, + { 0x0836, 0x0f }, + { 0x0837, 0xc4 }, + { 0x0838, 0x18 }, + { 0x0839, 0x0e }, + { 0x05ac, 0x01 }, + { 0x059a, 0x00 }, + { 0x059b, 0x00 }, + { 0x059c, 0x01 }, + { 0x0598, 0x00 }, + { 0x0597, 0x14 }, + { 0x05ab, 0x09 }, + { 0x05a4, 0x02 }, + { 0x05a3, 0x05 }, + { 0x05a0, 0xc2 }, + { 0x0207, 0xc4 }, + + /*GAIN*/ + { 0x0208, 0x01 }, + { 0x0209, 0x72 }, + { 0x0204, 0x04 }, + { 0x0205, 0x00 }, + + { 0x0040, 0x22 }, + { 0x0041, 0x20 }, + { 0x0043, 0x10 }, + { 0x0044, 0x00 }, + { 0x0046, 0x08 }, + { 0x0047, 0xf0 }, + { 0x0048, 0x0f }, + { 0x004b, 0x0f }, + { 0x004c, 0x00 }, + { 0x0050, 0x5c }, + { 0x0051, 0x44 }, + { 0x005b, 0x03 }, + { 0x00c0, 0x00 }, + { 0x00c1, 0x80 }, + { 0x00c2, 0x31 }, + { 0x00c3, 0x00 }, + { 0x0460, 0x04 }, + { 0x0462, 0x08 }, + { 0x0464, 0x0e }, + { 0x0466, 0x0a }, + { 0x0468, 0x12 }, + { 0x046a, 0x12 }, + { 0x046c, 0x10 }, + { 0x046e, 0x0c }, + { 0x0461, 0x03 }, + { 0x0463, 0x03 }, + { 0x0465, 0x03 }, + { 0x0467, 0x03 }, + { 0x0469, 0x04 }, + { 0x046b, 0x04 }, + { 0x046d, 0x04 }, + { 0x046f, 0x04 }, + { 0x0470, 0x04 }, + { 0x0472, 0x10 }, + { 0x0474, 0x26 }, + { 0x0476, 0x38 }, + { 0x0478, 0x20 }, + { 0x047a, 0x30 }, + { 0x047c, 0x38 }, + { 0x047e, 0x60 }, + { 0x0471, 0x05 }, + { 0x0473, 0x05 }, + { 0x0475, 0x05 }, + { 0x0477, 0x05 }, + { 0x0479, 0x04 }, + { 0x047b, 0x04 }, + { 0x047d, 0x04 }, + { 0x047f, 0x04 }, + + { GC08A3_TABLE_END, 0x00 } +}; + +static const struct gc08a3_link_freq_config link_freq_configs[] = { + [GC08A3_LINK_FREQ_336MHZ_CFG] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_table_common), + .regs = mode_table_common, + } + }, + [GC08A3_LINK_FREQ_207MHZ_CFG] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_table_common), + .regs = mode_table_common, + } + }, + +}; + +enum { + GC08A3_PREV, + GC08A3_HS, +}; + +/* + * Declare modes in order, from biggest + * to smallest height. + */ +static const struct gc08a3_mode { + u8 mode_id; + u32 width; + u32 height; + const struct gc08a3_reg_list reg_list; + + u32 hts; /* Horizontal timining size */ + u32 vts_def; /* Default vertical timining size */ + u32 vts_min; /* Min vertical timining size */ + u32 link_freq_index; /* Link frequency needed for this resolution */ + u32 max_framerate; + +} gc08a3_modes[] = { + { + .mode_id = GC08A3_PREV, + .width = 3264, + .height = 2448, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448), + .regs = mode_3264x2448, + }, + .link_freq_index = GC08A3_LINK_FREQ_336MHZ_CFG, + + .hts = GC08A3_HTS_30FPS, + .vts_def = GC08A3_VTS_30FPS, + .vts_min = GC08A3_VTS_30FPS_MIN, + .max_framerate = 300, + }, + { + .mode_id = GC08A3_HS, + .width = 1920, + .height = 1080, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080), + .regs = mode_1920x1080, + }, + .link_freq_index = GC08A3_LINK_FREQ_207MHZ_CFG, + + .hts = GC08A3_HTS_60FPS, + .vts_def = GC08A3_VTS_60FPS, + .vts_min = GC08A3_VTS_60FPS_MIN, + .max_framerate = 600, + }, +}; + +static u64 to_pixel_rate(u32 f_index) +{ + u64 pixel_rate = link_freq_menu_items[f_index] * 2 * GC08A3_DATA_LANES; + + do_div(pixel_rate, GC08A3_RGB_DEPTH); + + return pixel_rate; +} + +static u64 __maybe_unused to_pixels_per_line(u32 hts, u32 f_index) +{ + u64 ppl = hts * to_pixel_rate(f_index); + + do_div(ppl, GC08A3_SCLK); + + return ppl; +} + +static int gc08a3_read_reg(struct gc08a3 *gc08a3, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = { 0 }; + int ret; + + if (len > 4) + return -EINVAL; + + 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 = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int gc08a3_write_reg(struct gc08a3 *gc08a3, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int gc08a3_write_reg_list(struct gc08a3 *gc08a3, + const struct gc08a3_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + if (r_list->regs[i].address == GC08A3_TABLE_WAIT_MS) { + usleep_range(r_list->regs[i].val * 1000, + r_list->regs[i].val * 1000 + 500); + continue; + } + + if (r_list->regs[i].address == GC08A3_TABLE_END) + break; + + ret = gc08a3_write_reg(gc08a3, r_list->regs[i].address, + GC08A3_REG_VALUE_08BIT, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x. error = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int gc08a3_identify_module(struct gc08a3 *gc08a3) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + u32 val = 0; + + gc08a3_read_reg(gc08a3, GC08A3_REG_CHIP_ID, GC08A3_REG_VALUE_16BIT, + &val); + + if (val != GC08A3_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: 0x%x!=0x%x", + GC08A3_CHIP_ID, val); + return -ENXIO; + } + + dev_info(&client->dev, "sensor_id: 0x%04x\n", val); + return 0; +} + +static inline struct gc08a3 *to_gc08a3(struct v4l2_subdev *sd) +{ + return container_of(sd, struct gc08a3, sd); +} + +static int __maybe_unused gc08a3_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc08a3 *gc08a3 = to_gc08a3(sd); + int ret; + + dev_info(dev, "--- %s +", __func__); + + gpiod_set_value_cansleep(gc08a3->enable_gpio, 0); + usleep_range(2000, 3000); + + ret = regulator_bulk_enable(GC08A3_NUM_SUPPLIES, gc08a3->supplies); + if (ret < 0) { + dev_err(gc08a3->dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(gc08a3->xclk); + if (ret < 0) { + regulator_bulk_disable(GC08A3_NUM_SUPPLIES, gc08a3->supplies); + dev_err(gc08a3->dev, "clk prepare enable failed\n"); + return ret; + } + + usleep_range(2000, 3000); + + gpiod_set_value_cansleep(gc08a3->enable_gpio, 1); + usleep_range(12000, 15000); + + dev_info(dev, "--- %s -", __func__); + + return 0; +} + +static int __maybe_unused gc08a3_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc08a3 *gc08a3 = to_gc08a3(sd); + + dev_info(dev, "--- %s +", __func__); + + gpiod_set_value_cansleep(gc08a3->enable_gpio, 0); + + clk_disable_unprepare(gc08a3->xclk); + + regulator_bulk_disable(GC08A3_NUM_SUPPLIES, gc08a3->supplies); + usleep_range(10, 20); + + dev_info(dev, "--- %s -", __func__); + + return 0; +} + +static int gc08a3_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = GC08A3_MBUS_CODE; + + return 0; +} + +static int gc08a3_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->code != GC08A3_MBUS_CODE) + return -EINVAL; + + if (fse->index >= ARRAY_SIZE(gc08a3_modes)) + return -EINVAL; + + fse->min_width = gc08a3_modes[fse->index].width; + fse->max_width = gc08a3_modes[fse->index].width; + fse->min_height = gc08a3_modes[fse->index].height; + fse->max_height = gc08a3_modes[fse->index].height; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int gc08a3_g_register(struct v4l2_subdev *subdev, + struct v4l2_dbg_register *reg) +{ + int ret; + u32 val; + struct gc08a3 *gc08a3 = container_of(subdev, struct gc08a3, sd); + + ret = gc08a3_read_reg(gc08a3, reg->reg, GC08A3_REG_VALUE_08BIT, &val); + if (ret < 0) + return ret; + + reg->val = val; + reg->size = 1; + + return 0; +} + +static int gc08a3_s_register(struct v4l2_subdev *subdev, + const struct v4l2_dbg_register *reg) +{ + struct gc08a3 *gc08a3 = container_of(subdev, struct gc08a3, sd); + + return gc08a3_write_reg(gc08a3, reg->reg, GC08A3_REG_VALUE_08BIT, + reg->val & 0xff); +} + +#endif + +static const struct v4l2_subdev_core_ops gc08a3_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = gc08a3_g_register, + .s_register = gc08a3_s_register, +#endif +}; + +static struct v4l2_mbus_framefmt * +__gc08a3_get_pad_format(struct gc08a3 *gc08a3, + struct v4l2_subdev_state *sd_state, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&gc08a3->sd, sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &gc08a3->fmt; + default: + return NULL; + } +} + +static int gc08a3_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct gc08a3 *gc08a3 = to_gc08a3(sd); + + mutex_lock(&gc08a3->mutex); + format->format = *__gc08a3_get_pad_format(gc08a3, sd_state, format->pad, + format->which); + mutex_unlock(&gc08a3->mutex); + + return 0; +} + +static struct v4l2_rect * +__gc08a3_get_pad_crop(struct gc08a3 *gc08a3, struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&gc08a3->sd, sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &gc08a3->crop; + default: + return NULL; + } +} + +static int gc08a3_update_cur_mode_controls(struct gc08a3 *gc08a3) +{ + s64 exposure_max, h_blank; + int ret = 0; + + ret = __v4l2_ctrl_modify_range(gc08a3->vblank, + gc08a3->cur_mode->vts_min - gc08a3->cur_mode->height, + GC08A3_VTS_MAX - gc08a3->cur_mode->height, 1, + gc08a3->cur_mode->vts_def - gc08a3->cur_mode->height); + if (ret) + dev_err(gc08a3->dev, "VB ctrl range update failed\n"); + + h_blank = gc08a3->cur_mode->hts - gc08a3->cur_mode->width; + ret = __v4l2_ctrl_modify_range(gc08a3->hblank, h_blank, h_blank, 1, + h_blank); + if (ret) + dev_err(gc08a3->dev, "HB ctrl range update failed\n"); + + exposure_max = gc08a3->cur_mode->vts_def - GC08A3_EXP_MARGIN; + ret = __v4l2_ctrl_modify_range(gc08a3->exposure, GC08A3_EXP_MIN, + exposure_max, GC08A3_EXP_STEP, + exposure_max); + if (ret) + dev_err(gc08a3->dev, "exposure ctrl range update failed\n"); + + return ret; +} + +static int gc08a3_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct gc08a3 *gc08a3 = to_gc08a3(sd); + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + struct device *dev = &client->dev; + struct v4l2_mbus_framefmt *__format; + struct v4l2_rect *__crop; + const struct gc08a3_mode *mode; + + mutex_lock(&gc08a3->mutex); + + dev_dbg(dev, "---- %s, format(W x H:%d x %d) +", __func__, + format->format.width, format->format.height); + + __crop = __gc08a3_get_pad_crop(gc08a3, sd_state, format->pad, + format->which); + + mode = v4l2_find_nearest_size(gc08a3_modes, ARRAY_SIZE(gc08a3_modes), + width, height, format->format.width, + format->format.height); + + dev_dbg(dev, "----nearest mode(W x H:%d x %d)", mode->width, + mode->height); + + __crop->width = mode->width; + __crop->height = mode->height; + + __format = __gc08a3_get_pad_format(gc08a3, sd_state, format->pad, + format->which); + __format->width = __crop->width; + __format->height = __crop->height; + __format->code = GC08A3_MBUS_CODE; + __format->field = V4L2_FIELD_NONE; + __format->colorspace = V4L2_COLORSPACE_SRGB; + __format->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(__format->colorspace); + __format->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(true, + __format->colorspace, + __format->ycbcr_enc); + __format->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(__format->colorspace); + + format->format = *__format; + + gc08a3->cur_mode = mode; + gc08a3_update_cur_mode_controls(gc08a3); + + mutex_unlock(&gc08a3->mutex); + + return 0; +} + +static int gc08a3_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct gc08a3 *gc08a3 = to_gc08a3(sd); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + mutex_lock(&gc08a3->mutex); + sel->r = *__gc08a3_get_pad_crop(gc08a3, sd_state, sel->pad, sel->which); + mutex_unlock(&gc08a3->mutex); + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = GC08A3_NATIVE_WIDTH; + sel->r.height = GC08A3_NATIVE_HEIGHT; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + if (gc08a3->cur_mode->width == GC08A3_NATIVE_WIDTH) { + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = GC08A3_NATIVE_WIDTH; + sel->r.height = GC08A3_NATIVE_HEIGHT; + } else { + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = 1920; + sel->r.height = 1080; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int gc08a3_entity_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_format fmt = {}; + + fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.format.width = gc08a3_modes[0].width; + fmt.format.height = gc08a3_modes[0].height; + + gc08a3_set_format(subdev, sd_state, &fmt); + + return 0; +} + +static int gc08a3_set_ctrl_hflip(struct gc08a3 *gc08a3, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = gc08a3_read_reg(gc08a3, GC08A3_FLIP_REG, GC08A3_REG_VALUE_08BIT, + &val); + if (ret) { + dev_err(gc08a3->dev, "read hflip register failed: %d\n", ret); + return ret; + } + + val = (ctrl_val) ? (val | GC08A3_FLIP_H_MASK) : + (val & ~GC08A3_FLIP_H_MASK); + ret = gc08a3_write_reg(gc08a3, GC08A3_FLIP_REG, GC08A3_REG_VALUE_08BIT, + val); + if (ret < 0) + dev_err(gc08a3->dev, "Error %d\n", ret); + + return ret; +} + +static int gc08a3_set_ctrl_vflip(struct gc08a3 *gc08a3, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = gc08a3_read_reg(gc08a3, GC08A3_FLIP_REG, GC08A3_REG_VALUE_08BIT, + &val); + if (ret) { + dev_err(gc08a3->dev, "read vflip register failed: %d\n", ret); + return ret; + } + + val = (ctrl_val) ? (val | GC08A3_FLIP_V_MASK) : + (val & ~GC08A3_FLIP_V_MASK); + ret = gc08a3_write_reg(gc08a3, GC08A3_FLIP_REG, GC08A3_REG_VALUE_08BIT, + val); + if (ret < 0) + dev_err(gc08a3->dev, "Error %d\n", ret); + + return ret; +} + +static int gc08a3_test_pattern(struct gc08a3 *gc08a3, u32 pattern_menu) +{ + int ret = 0; + u32 pattern = 0; + + if (pattern_menu) { + // write bit16:0x0110 -> color bar + switch (pattern_menu) { + case 1: + pattern = 0x00; + break; + case 2: + case 3: + case 4: + case 5: + case 6: + pattern = pattern_menu + 2; + break; + case 7: + pattern = 0x10; + break; + + default: + dev_dbg(gc08a3->dev, "invalid pattern menu!\n"); + break; + } + + ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_EN, + GC08A3_REG_VALUE_08BIT, + GC08A3_TEST_PATTERN_EN); + ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_IDX, + GC08A3_REG_VALUE_08BIT, pattern); + + } else { + ret = gc08a3_write_reg(gc08a3, GC08A3_REG_TEST_PATTERN_EN, + GC08A3_REG_VALUE_08BIT, 0x00); + } + + return ret; +} + +static int gc08a3_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gc08a3 *gc08a3 = + container_of(ctrl->handler, struct gc08a3, ctrls); + int ret = 0; + s64 exposure_max; + + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = gc08a3->cur_mode->height + ctrl->val - + GC08A3_EXP_MARGIN; + __v4l2_ctrl_modify_range(gc08a3->exposure, + gc08a3->exposure->minimum, + exposure_max, gc08a3->exposure->step, + exposure_max); + } + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (!pm_runtime_get_if_in_use(gc08a3->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = gc08a3_write_reg(gc08a3, GC08A3_EXP_REG, + GC08A3_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_ANALOGUE_GAIN: + ret = gc08a3_write_reg(gc08a3, GC08A3_AGAIN_REG, + GC08A3_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_VBLANK: + dev_info(gc08a3->dev, "V4L2_CID_VBLANK:height:%d, V_BLNK:%d\n", + gc08a3->cur_mode->height, ctrl->val); + ret = gc08a3_write_reg(gc08a3, GC08A3_FL_REG, + GC08A3_REG_VALUE_16BIT, + gc08a3->cur_mode->height + ctrl->val); + break; + + case V4L2_CID_HFLIP: + gc08a3_set_ctrl_hflip(gc08a3, ctrl->val); + break; + + case V4L2_CID_VFLIP: + gc08a3_set_ctrl_vflip(gc08a3, ctrl->val); + break; + + case V4L2_CID_TEST_PATTERN: + ret = gc08a3_test_pattern(gc08a3, ctrl->val); + break; + + default: + break; + } + + pm_runtime_put(gc08a3->dev); + + return ret; +} + +static int get_volatile_ext_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + struct gc08a3 *gc08a3 = + container_of(ctrl->handler, struct gc08a3, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + + dev_dbg(&client->dev, "---- %s, CMD:0x%x\n", __func__, ctrl->id); + switch (ctrl->id) { + default: + dev_info(&client->dev, "[gc08a3] %s, un-support CMD: 0x%x\n", + __func__, ctrl->id); + break; + } + + return ret; +} + +static int gc08a3_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + + switch (ctrl->id) { + default: + ret = get_volatile_ext_ctrl(ctrl); + break; + } + + return ret; +} + +static int try_ext_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + struct gc08a3 *gc08a3 = + container_of(ctrl->handler, struct gc08a3, ctrls); + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + + switch (ctrl->id) { + default: + dev_dbg(&client->dev, + "[gc08a3] un-handle CMD: 0x%x (%s : %d)\n", ctrl->id, + __func__, __LINE__); + break; + } + + return ret; +} + +static int gc08a3_try_ctrl(struct v4l2_ctrl *ctrl) +{ + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + case V4L2_CID_ANALOGUE_GAIN: + case V4L2_CID_VBLANK: + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + case V4L2_CID_TEST_PATTERN: + return 0; + + default: + ret = try_ext_ctrl(ctrl); + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops gc08a3_ctrl_ops = { + .g_volatile_ctrl = gc08a3_g_volatile_ctrl, + .try_ctrl = gc08a3_try_ctrl, + .s_ctrl = gc08a3_set_ctrl, +}; + +static int gc08a3_start_streaming(struct gc08a3 *gc08a3) +{ + const struct gc08a3_mode *mode; + const struct gc08a3_reg_list *reg_list; + int link_freq_index; + int ret; + + dev_info(gc08a3->dev, "%s ++\n", __func__); + + mutex_lock(&gc08a3->mutex); + + link_freq_index = gc08a3->cur_mode->link_freq_index; + dev_info(gc08a3->dev, "----link_freq_index = %d ", link_freq_index); + + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = gc08a3_write_reg_list(gc08a3, reg_list); + if (ret) { + dev_err(gc08a3->dev, "could not sent common table %d\n", ret); + goto error; + } + + mode = gc08a3->cur_mode; + dev_info(gc08a3->dev, "----write regtbl: mode(id:%d, WxH:%dx%d)", + mode->mode_id, mode->width, mode->height); + reg_list = &mode->reg_list; + + ret = gc08a3_write_reg_list(gc08a3, reg_list); + if (ret < 0) { + dev_err(gc08a3->dev, "could not sent mode table %d\n", ret); + goto error; + } + ret = __v4l2_ctrl_handler_setup(&gc08a3->ctrls); + if (ret < 0) { + dev_err(gc08a3->dev, "could not sync v4l2 controls\n"); + goto error; + } + + ret = gc08a3_write_reg(gc08a3, GC08A3_STRAEMING_REG, + GC08A3_REG_VALUE_08BIT, 1); + if (ret < 0) { + dev_err(gc08a3->dev, "write STRAEMING_REG failed: %d\n", ret); + goto error; + } + + mutex_unlock(&gc08a3->mutex); + + dev_info(gc08a3->dev, "%s --\n", __func__); + + return 0; + +error: + mutex_unlock(&gc08a3->mutex); + return ret; +} + +static int gc08a3_stop_streaming(struct gc08a3 *gc08a3) +{ + int ret; + + dev_info(gc08a3->dev, "%s ++\n", __func__); + + ret = gc08a3_write_reg(gc08a3, GC08A3_STRAEMING_REG, + GC08A3_REG_VALUE_08BIT, 0); + if (ret < 0) + dev_err(gc08a3->dev, "could not sent stop streaming %d\n", ret); + + dev_info(gc08a3->dev, "%s --\n", __func__); + + return ret; +} + +static int gc08a3_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct gc08a3 *gc08a3 = to_gc08a3(subdev); + int ret; + + if (gc08a3->streaming == enable) + return 0; + + if (enable) { + ret = pm_runtime_resume_and_get(gc08a3->dev); + if (ret < 0) + return ret; + + ret = gc08a3_start_streaming(gc08a3); + if (ret < 0) + goto err_rpm_put; + } else { + ret = gc08a3_stop_streaming(gc08a3); + if (ret < 0) + goto err_rpm_put; + pm_runtime_put(gc08a3->dev); + } + + gc08a3->streaming = enable; + return 0; + +err_rpm_put: + pm_runtime_put(gc08a3->dev); + return ret; +} + +static int gc08a3_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *config) +{ + config->type = V4L2_MBUS_CSI2_DPHY; + config->bus.mipi_csi2.num_data_lanes = 4; + config->bus.mipi_csi2.flags = 0; + return 0; +} + +static int gc08a3_g_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_frame_interval *fival) +{ + struct gc08a3 *gc08a3 = to_gc08a3(subdev); + + fival->interval.numerator = 1; + fival->interval.denominator = gc08a3->cur_mode->max_framerate / 10; + + return 0; +} + +static int +gc08a3_enum_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + const struct gc08a3_mode *mode; + + if (fie->index != 0) + return -EINVAL; + + mode = v4l2_find_nearest_size(gc08a3_modes, ARRAY_SIZE(gc08a3_modes), + width, height, fie->width, fie->height); + + fie->code = GC08A3_MBUS_CODE; + fie->width = mode->width; + fie->height = mode->height; + fie->interval.numerator = 1; + fie->interval.denominator = mode->max_framerate / 10; + + return 0; +} + +static const struct v4l2_subdev_video_ops gc08a3_video_ops = { + .s_stream = gc08a3_s_stream, + .g_frame_interval = gc08a3_g_frame_interval, + .s_frame_interval = gc08a3_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops gc08a3_subdev_pad_ops = { + .enum_mbus_code = gc08a3_enum_mbus_code, + .enum_frame_size = gc08a3_enum_frame_size, + .enum_frame_interval = gc08a3_enum_frame_interval, + .get_fmt = gc08a3_get_format, + .set_fmt = gc08a3_set_format, + .get_selection = gc08a3_get_selection, + .init_cfg = gc08a3_entity_init_cfg, + .get_mbus_config = gc08a3_g_mbus_config, +}; + +static const struct v4l2_subdev_ops gc08a3_subdev_ops = { + .core = &gc08a3_core_ops, + .video = &gc08a3_video_ops, + .pad = &gc08a3_subdev_pad_ops, +}; + +static const struct regmap_config sensor_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +static int gc08a3_get_regulators(struct device *dev, struct gc08a3 *gc08a3) +{ + unsigned int i; + + for (i = 0; i < GC08A3_NUM_SUPPLIES; i++) + gc08a3->supplies[i].supply = gc08a3_supply_name[i]; + + return devm_regulator_bulk_get(dev, GC08A3_NUM_SUPPLIES, + gc08a3->supplies); +} + +static int gc08a3_parse_fwnode(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + unsigned int i, j; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + if (ret) { + dev_err(dev, "parsing endpoint node failed\n"); + goto done; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto done; + } + + 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, please check DT", + link_freq_menu_items[i]); + ret = -EINVAL; + goto done; + } + } + +done: + v4l2_fwnode_endpoint_free(&bus_cfg); + fwnode_handle_put(endpoint); + return ret; +} + +static int __maybe_unused gc08a3_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc08a3 *gc08a3 = to_gc08a3(sd); + + if (gc08a3->streaming) + gc08a3_stop_streaming(gc08a3); + + return 0; +} + +static int __maybe_unused gc08a3_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc08a3 *gc08a3 = to_gc08a3(sd); + int ret; + + if (gc08a3->streaming) { + ret = gc08a3_start_streaming(gc08a3); + if (ret) + goto error; + } + + return 0; + +error: + gc08a3_stop_streaming(gc08a3); + gc08a3->streaming = 0; + return ret; +} + +static int gc08a3_init_controls(struct gc08a3 *gc08a3) +{ + struct i2c_client *client = v4l2_get_subdevdata(&gc08a3->sd); + const struct v4l2_ctrl_ops *ops = &gc08a3_ctrl_ops; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max, h_blank; + int ret; + + ctrl_hdlr = &gc08a3->ctrls; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + ctrl_hdlr->lock = &gc08a3->mutex; + + gc08a3->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, + &gc08a3_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (gc08a3->link_freq) + gc08a3->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + gc08a3->pixel_rate = + v4l2_ctrl_new_std(ctrl_hdlr, + &gc08a3_ctrl_ops, + V4L2_CID_PIXEL_RATE, 0, + to_pixel_rate(GC08A3_LINK_FREQ_336MHZ_CFG), 1, + to_pixel_rate(GC08A3_LINK_FREQ_336MHZ_CFG)); + + gc08a3->vblank = + v4l2_ctrl_new_std(ctrl_hdlr, + &gc08a3_ctrl_ops, V4L2_CID_VBLANK, + gc08a3->cur_mode->vts_min - gc08a3->cur_mode->height, + GC08A3_VTS_MAX - gc08a3->cur_mode->height, 1, + gc08a3->cur_mode->vts_def - gc08a3->cur_mode->height); + + h_blank = gc08a3->cur_mode->hts - gc08a3->cur_mode->width; + gc08a3->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_HBLANK, h_blank, h_blank, 1, + h_blank); + if (gc08a3->hblank) + gc08a3->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, GC08A3_AGAIN_MIN, + GC08A3_AGAIN_MAX, GC08A3_AGAIN_STEP, + GC08A3_AGAIN_MIN); + + exposure_max = gc08a3->cur_mode->vts_def - GC08A3_EXP_MARGIN; + gc08a3->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_EXPOSURE, GC08A3_EXP_MIN, + exposure_max, GC08A3_EXP_STEP, + exposure_max); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &gc08a3_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(gc08a3_test_pattern_menu) - 1, + 0, 0, gc08a3_test_pattern_menu); + + v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, V4L2_CID_HFLIP, 0, + 1, 1, 0); + + v4l2_ctrl_new_std(ctrl_hdlr, &gc08a3_ctrl_ops, V4L2_CID_VFLIP, 0, + 1, 1, 0); + + /* register properties to fwnode (e.g. rotation, orientation) */ + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error_ctrls; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, ops, &props); + if (ret) + goto error_ctrls; + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + goto error_ctrls; + } + + gc08a3->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error_ctrls: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int gc08a3_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct gc08a3 *gc08a3; + int ret; + + dev_info(dev, "--- %s +", __func__); + + ret = gc08a3_parse_fwnode(dev); + if (ret) + return ret; + + gc08a3 = devm_kzalloc(dev, sizeof(*gc08a3), GFP_KERNEL); + if (!gc08a3) + return -ENOMEM; + + gc08a3->dev = dev; + + gc08a3->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(gc08a3->xclk)) { + dev_err(dev, "could not get xclk\n"); + return PTR_ERR(gc08a3->xclk); + } + + ret = clk_set_rate(gc08a3->xclk, GC08A3_DEFAULT_CLK_FREQ); + if (ret) { + dev_err(dev, "could not set xclk frequency\n"); + return ret; + } + + ret = gc08a3_get_regulators(dev, gc08a3); + if (ret < 0) { + dev_err(dev, "cannot get regulators\n"); + return ret; + } + + gc08a3->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(gc08a3->enable_gpio)) { + dev_err(dev, "cannot get enable gpio\n"); + return PTR_ERR(gc08a3->enable_gpio); + } + + gc08a3->regmap = devm_regmap_init_i2c(client, &sensor_regmap_config); + if (IS_ERR(gc08a3->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(gc08a3->regmap); + } + + v4l2_i2c_subdev_init(&gc08a3->sd, client, &gc08a3_subdev_ops); + + gc08a3_power_on(gc08a3->dev); + + ret = gc08a3_identify_module(gc08a3); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + gc08a3_power_off(gc08a3->dev); + return ret; + } + + mutex_init(&gc08a3->mutex); + gc08a3->cur_mode = &gc08a3_modes[0]; + + ret = gc08a3_init_controls(gc08a3); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto free_ctrl; + } + + gc08a3->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + gc08a3->pad.flags = MEDIA_PAD_FL_SOURCE; + gc08a3->sd.dev = &client->dev; + gc08a3->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + dev_dbg(&client->dev, "gc08a3->sd.name: %s, dev->of_node->name: %s\n", + gc08a3->sd.name, dev->of_node->name); + if (V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2 < + strlen(dev->of_node->name)) { + dev_err(&client->dev, + "the string length of (sd.name + of_node->name) is too long.\n"); + return -EINVAL; + } + strncat(gc08a3->sd.name, " ", 1); + strncat(gc08a3->sd.name, dev->of_node->name, + V4L2_SUBDEV_NAME_SIZE - strlen(gc08a3->sd.name) - 2); + dev_dbg(&client->dev, "after: gc08a3->sd.name: %s\n", gc08a3->sd.name); + + ret = media_entity_pads_init(&gc08a3->sd.entity, 1, &gc08a3->pad); + if (ret < 0) { + dev_err(dev, "could not register media entity\n"); + goto free_ctrl; + } + + ret = v4l2_async_register_subdev_sensor(&gc08a3->sd); + if (ret < 0) { + dev_err(dev, "could not register v4l2 device\n"); + goto free_entity; + } + + pm_runtime_set_active(gc08a3->dev); + pm_runtime_enable(gc08a3->dev); + pm_runtime_idle(gc08a3->dev); + + dev_info(dev, "--- %s -", __func__); + + return 0; + +free_entity: + media_entity_cleanup(&gc08a3->sd.entity); +free_ctrl: + mutex_destroy(&gc08a3->mutex); + v4l2_ctrl_handler_free(&gc08a3->ctrls); + pm_runtime_disable(gc08a3->dev); + + return ret; +} + +static void gc08a3_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc08a3 *gc08a3 = to_gc08a3(sd); + + v4l2_async_unregister_subdev(&gc08a3->sd); + media_entity_cleanup(&gc08a3->sd.entity); + v4l2_ctrl_handler_free(&gc08a3->ctrls); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + mutex_destroy(&gc08a3->mutex); +} + +static const struct of_device_id gc08a3_of_match[] = { + { .compatible = "GalaxyCore,gc08a3" }, + {} +}; +MODULE_DEVICE_TABLE(of, gc08a3_of_match); + +static const struct dev_pm_ops gc08a3_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(gc08a3_suspend, gc08a3_resume) + SET_RUNTIME_PM_OPS(gc08a3_power_off, gc08a3_power_on, NULL) +}; + +static struct i2c_driver gc08a3_i2c_driver = { + .driver = { + .of_match_table = gc08a3_of_match, + .pm = &gc08a3_pm_ops, + .name = "gc08a3", + }, + .probe_new = gc08a3_probe, + .remove = gc08a3_remove, +}; + +module_i2c_driver(gc08a3_i2c_driver); + +MODULE_DESCRIPTION("GalaxyCore gc08a3 Camera driver"); +MODULE_AUTHOR("Zhi Mao <zhi.mao@mediatek.com>"); +MODULE_LICENSE("GPL");
Add a V4L2 sub-device driver for Galaxycore GC08A3 image sensor. Signed-off-by: Zhi Mao <zhi.mao@mediatek.com> --- drivers/media/i2c/Kconfig | 14 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/gc08a3.c | 2046 ++++++++++++++++++++++++++++++++++++ 3 files changed, 2061 insertions(+) create mode 100644 drivers/media/i2c/gc08a3.c