2023-10-24 01:04:52

by Sebastian Reichel

[permalink] [raw]
Subject: [PATCH v2 0/4] GC0308 Camera Sensor

Hi,

I did the following tests done by me on an i.MX6ULL based system [0]:

* v4l2-compliance -u /dev/v4l-subdev1
- v4l2-compliance 1.24.1, 32 bits, 32-bit time_t
(from Debian testing)
- Total for device /dev/v4l-subdev1: 44, Succeeded: 44
* Using gstreamer + v4l2-ctl
- Tried 640x480, 320x240, 160x120 YUYV8_2X8 formats
- Tested effect of all exposed user controls
* checkpatch does not report any driver issues
* dt_binding_check does not report anything

[0] https://embedded-recipes.org/2023/schedule/running-foss-thermal-camera/

Changes since PATCHv1:
* https://lore.kernel.org/all/[email protected]/
* Update binding: i2c0 -> i2c
* Update binding: make GC0309 use GC0308 as fallback compatible
* Add regulator handling in power_on/power_off
* Fix alignment of regmap_multi_reg_write arguments
* Fix useless extra return
* Do not check for pad in gc0308_enum_frame_size()
* Drop get_mbus_config implementation
* Use V4L2_CID_AUTO_EXPOSURE_BIAS instead of V4L2_CID_EXPOSURE
* Drop gc0308_get_format in favour of v4l2_subdev_get_fmt
* Replace open-coded v4l2_find_nearest_size() logic
* check clock rate instead of setting it
* use fwnode_graph_get_endpoint_by_id()
* power off device when probe errors out after power on
* replace mutex with sub-device state
* add Galaxycore to generic camera sensors section in MAINTAINERS
* add GC0308 entry in MAINTAINERS

Greetings,

-- Sebastian

Sebastian Reichel (4):
dt-bindings: vendor-prefixes: add GalaxyCore
media: dt-bindings: gc0308: add binding
media: MAINTAINERS: Add GalaxyCore in camera sensor section
media: i2c: gc0308: new driver

.../bindings/media/i2c/galaxycore,gc0308.yaml | 108 ++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 8 +
drivers/media/i2c/Kconfig | 14 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/gc0308.c | 1418 +++++++++++++++++
6 files changed, 1551 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/i2c/galaxycore,gc0308.yaml
create mode 100644 drivers/media/i2c/gc0308.c

--
2.42.0


2023-10-24 01:05:07

by Sebastian Reichel

[permalink] [raw]
Subject: [PATCH v2 4/4] media: i2c: gc0308: new driver

Introduce new driver for GalaxyCore GC0308, which is a cheap
640x480 with an on-chip ISP sensor sold since 2010. Data is
provided via parallel bus.

Signed-off-by: Sebastian Reichel <[email protected]>
---
MAINTAINERS | 7 +
drivers/media/i2c/Kconfig | 14 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/gc0308.c | 1418 ++++++++++++++++++++++++++++++++++++
4 files changed, 1440 insertions(+)
create mode 100644 drivers/media/i2c/gc0308.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 5f64cd42ff37..f03fd34067d0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8627,6 +8627,13 @@ F: drivers/mfd/gateworks-gsc.c
F: include/linux/mfd/gsc.h
F: include/linux/platform_data/gsc_hwmon.h

+GC0308 GALAXYCORE CAMERA SENSOR DRIVER
+M: Sebastian Reichel <[email protected]>
+L: [email protected]
+S: Maintained
+F: Documentation/devicetree/bindings/media/i2c/galaxycore,gc0308.yaml
+F: drivers/media/i2c/gc0308.c
+
GCC PLUGINS
M: Kees Cook <[email protected]>
L: [email protected]
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 74ff833ff48c..7db4abc0ef88 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -50,6 +50,20 @@ config VIDEO_AR0521
To compile this driver as a module, choose M here: the
module will be called ar0521.

+config VIDEO_GC0308
+ tristate "GalaxyCore GC0308 sensor support"
+ depends on I2C && VIDEO_DEV
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ select REGMAP_I2C
+ help
+ This is a Video4Linux2 sensor driver for the GalaxyCore
+ GC0308 camera.
+
+ To compile this driver as a module, choose M here: the
+ module will be called gc0308.
+
config VIDEO_HI556
tristate "Hynix Hi-556 sensor support"
help
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 80b00d39b48f..f03a21f2962a 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_GC0308) += gc0308.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/gc0308.c b/drivers/media/i2c/gc0308.c
new file mode 100644
index 000000000000..f6e02284b661
--- /dev/null
+++ b/drivers/media/i2c/gc0308.c
@@ -0,0 +1,1418 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the GalaxyCore GC0308 camera sensor.
+ *
+ * Copyright (c) 2023 Sebastian Reichel <[email protected]>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+/* Analog & CISCTL*/
+#define GC0308_CHIP_ID 0x000
+#define GC0308_HBLANK 0x001
+#define GC0308_VBLANK 0x002
+#define GC0308_EXPH 0x003
+#define GC0308_EXPL 0x004
+#define GC0308_ROW_STARTH 0x005
+#define GC0308_ROW_STARTL 0x006
+#define GC0308_COL_STARTH 0x007
+#define GC0308_COL_STARTL 0x008
+#define GC0308_WIN_HEIGHTH 0x009
+#define GC0308_WIN_HEIGHTL 0x00a
+#define GC0308_WIN_WIDTHH 0x00b
+#define GC0308_WIN_WIDTHL 0x00c
+#define GC0308_VS_ST 0x00d
+#define GC0308_VS_ET 0x00e
+#define GC0308_VB_HB 0x00f
+#define GC0308_RSH_WIDTH 0x010
+#define GC0308_TSP_WIDTH 0x011
+#define GC0308_SH_DELAY 0x012
+#define GC0308_ROW_TAIL_WIDTH 0x013
+#define GC0308_CISCTL_MODE1 0x014
+#define GC0308_CISCTL_MODE2 0x015
+#define GC0308_CISCTL_MODE3 0x016
+#define GC0308_CISCTL_MODE4 0x017
+#define GC0308_ANALOG_MODE1 0x01a
+#define GC0308_ANALOG_MODE2 0x01b
+#define GC0308_HRST_RSG_V18 0x01c
+#define GC0308_VREF_V25 0x01d
+#define GC0308_ADC_R 0x01e
+#define GC0308_PAD_DRV 0x01f
+#define GC0308_SOFT_RESET 0x0fe
+
+/* ISP */
+#define GC0308_BLOCK_EN1 0x020
+#define GC0308_BLOCK_EN2 0x021
+#define GC0308_AAAA_EN 0x022
+#define GC0308_SPECIAL_EFFECT 0x023
+#define GC0308_OUT_FORMAT 0x024
+#define GC0308_OUT_EN 0x025
+#define GC0308_SYNC_MODE 0x026
+#define GC0308_CLK_DIV_MODE 0x028
+#define GC0308_BYPASS_MODE 0x029
+#define GC0308_CLK_GATING 0x02a
+#define GC0308_DITHER_MODE 0x02b
+#define GC0308_DITHER_BIT 0x02c
+#define GC0308_DEBUG_MODE1 0x02d
+#define GC0308_DEBUG_MODE2 0x02e
+#define GC0308_DEBUG_MODE3 0x02f
+#define GC0308_CROP_WIN_MODE 0x046
+#define GC0308_CROP_WIN_Y1 0x047
+#define GC0308_CROP_WIN_X1 0x048
+#define GC0308_CROP_WIN_HEIGHTH 0x049
+#define GC0308_CROP_WIN_HEIGHTL 0x04a
+#define GC0308_CROP_WIN_WIDTHH 0x04b
+#define GC0308_CROP_WIN_WIDTHL 0x04c
+
+/* BLK */
+#define GC0308_BLK_MODE 0x030
+#define GC0308_BLK_LIMIT_VAL 0x031
+#define GC0308_GLOBAL_OFF 0x032
+#define GC0308_CURRENT_R_OFF 0x033
+#define GC0308_CURRENT_G_OFF 0x034
+#define GC0308_CURRENT_B_OFF 0x035
+#define GC0308_CURRENT_R_DARK_CURRENT 0x036
+#define GC0308_CURRENT_G_DARK_CURRENT 0x037
+#define GC0308_CURRENT_B_DARK_CURRENT 0x038
+#define GC0308_EXP_RATE_DARKC 0x039
+#define GC0308_OFF_SUBMODE 0x03a
+#define GC0308_DARKC_SUBMODE 0x03b
+#define GC0308_MANUAL_G1_OFF 0x03c
+#define GC0308_MANUAL_R1_OFF 0x03d
+#define GC0308_MANUAL_B2_OFF 0x03e
+#define GC0308_MANUAL_G2_OFF 0x03f
+
+/* PREGAIN */
+#define GC0308_GLOBAL_GAIN 0x050
+#define GC0308_AUTO_PREGAIN 0x051
+#define GC0308_AUTO_POSTGAIN 0x052
+#define GC0308_CHANNEL_GAIN_G1 0x053
+#define GC0308_CHANNEL_GAIN_R 0x054
+#define GC0308_CHANNEL_GAIN_B 0x055
+#define GC0308_CHANNEL_GAIN_G2 0x056
+#define GC0308_R_RATIO 0x057
+#define GC0308_G_RATIO 0x058
+#define GC0308_B_RATIO 0x059
+#define GC0308_AWB_R_GAIN 0x05a
+#define GC0308_AWB_G_GAIN 0x05b
+#define GC0308_AWB_B_GAIN 0x05c
+#define GC0308_LSC_DEC_LVL1 0x05d
+#define GC0308_LSC_DEC_LVL2 0x05e
+#define GC0308_LSC_DEC_LVL3 0x05f
+
+/* DNDD */
+#define GC0308_DN_MODE_EN 0x060
+#define GC0308_DN_MODE_RATIO 0x061
+#define GC0308_DN_BILAT_B_BASE 0x062
+#define GC0308_DN_B_INCR 0x063
+#define GC0308_DN_BILAT_N_BASE 0x064
+#define GC0308_DN_N_INCR 0x065
+#define GC0308_DD_DARK_BRIGHT_TH 0x066
+#define GC0308_DD_FLAT_TH 0x067
+#define GC0308_DD_LIMIT 0x068
+
+/* ASDE - Auto Saturation De-noise and Edge-Enhancement */
+#define GC0308_ASDE_GAIN_TRESH 0x069
+#define GC0308_ASDE_GAIN_MODE 0x06a
+#define GC0308_ASDE_DN_SLOPE 0x06b
+#define GC0308_ASDE_DD_BRIGHT 0x06c
+#define GC0308_ASDE_DD_LIMIT 0x06d
+#define GC0308_ASDE_AUTO_EE1 0x06e
+#define GC0308_ASDE_AUTO_EE2 0x06f
+#define GC0308_ASDE_AUTO_SAT_DEC_SLOPE 0x070
+#define GC0308_ASDE_AUTO_SAT_LOW_LIMIT 0x071
+
+/* INTPEE - Interpolation and Edge-Enhancement */
+#define GC0308_EEINTP_MODE_1 0x072
+#define GC0308_EEINTP_MODE_2 0x073
+#define GC0308_DIRECTION_TH1 0x074
+#define GC0308_DIRECTION_TH2 0x075
+#define GC0308_DIFF_HV_TI_TH 0x076
+#define GC0308_EDGE12_EFFECT 0x077
+#define GC0308_EDGE_POS_RATIO 0x078
+#define GC0308_EDGE1_MINMAX 0x079
+#define GC0308_EDGE2_MINMAX 0x07a
+#define GC0308_EDGE12_TH 0x07b
+#define GC0308_EDGE_MAX 0x07c
+
+/* ABB - Auto Black Balance */
+#define GC0308_ABB_MODE 0x080
+#define GC0308_ABB_TARGET_AVGH 0x081
+#define GC0308_ABB_TARGET_AVGL 0x082
+#define GC0308_ABB_LIMIT_VAL 0x083
+#define GC0308_ABB_SPEED 0x084
+#define GC0308_CURR_R_BLACK_LVL 0x085
+#define GC0308_CURR_G_BLACK_LVL 0x086
+#define GC0308_CURR_B_BLACK_LVL 0x087
+#define GC0308_CURR_R_BLACK_FACTOR 0x088
+#define GC0308_CURR_G_BLACK_FACTOR 0x089
+#define GC0308_CURR_B_BLACK_FACTOR 0x08a
+
+/* LSC - Lens Shading Correction */
+#define GC0308_LSC_RED_B2 0x08b
+#define GC0308_LSC_GREEN_B2 0x08c
+#define GC0308_LSC_BLUE_B2 0x08d
+#define GC0308_LSC_RED_B4 0x08e
+#define GC0308_LSC_GREEN_B4 0x08f
+#define GC0308_LSC_BLUE_B4 0x090
+#define GC0308_LSC_ROW_CENTER 0x091
+#define GC0308_LSC_COL_CENTER 0x092
+
+/* CC - Channel Coefficient */
+#define GC0308_CC_MATRIX_C11 0x093
+#define GC0308_CC_MATRIX_C12 0x094
+#define GC0308_CC_MATRIX_C13 0x095
+#define GC0308_CC_MATRIX_C21 0x096
+#define GC0308_CC_MATRIX_C22 0x097
+#define GC0308_CC_MATRIX_C23 0x098
+#define GC0308_CC_MATRIX_C41 0x09c
+#define GC0308_CC_MATRIX_C42 0x09d
+#define GC0308_CC_MATRIX_C43 0x09e
+
+/* GAMMA */
+#define GC0308_GAMMA_OUT0 0x09f
+#define GC0308_GAMMA_OUT1 0x0a0
+#define GC0308_GAMMA_OUT2 0x0a1
+#define GC0308_GAMMA_OUT3 0x0a2
+#define GC0308_GAMMA_OUT4 0x0a3
+#define GC0308_GAMMA_OUT5 0x0a4
+#define GC0308_GAMMA_OUT6 0x0a5
+#define GC0308_GAMMA_OUT7 0x0a6
+#define GC0308_GAMMA_OUT8 0x0a7
+#define GC0308_GAMMA_OUT9 0x0a8
+#define GC0308_GAMMA_OUT10 0x0a9
+#define GC0308_GAMMA_OUT11 0x0aa
+#define GC0308_GAMMA_OUT12 0x0ab
+#define GC0308_GAMMA_OUT13 0x0ac
+#define GC0308_GAMMA_OUT14 0x0ad
+#define GC0308_GAMMA_OUT15 0x0ae
+#define GC0308_GAMMA_OUT16 0x0af
+
+/* YCP */
+#define GC0308_GLOBAL_SATURATION 0x0b0
+#define GC0308_SATURATION_CB 0x0b1
+#define GC0308_SATURATION_CR 0x0b2
+#define GC0308_LUMA_CONTRAST 0x0b3
+#define GC0308_CONTRAST_CENTER 0x0b4
+#define GC0308_LUMA_OFFSET 0x0b5
+#define GC0308_SKIN_CB_CENTER 0x0b6
+#define GC0308_SKIN_CR_CENTER 0x0b7
+#define GC0308_SKIN_RADIUS_SQUARE 0x0b8
+#define GC0308_SKIN_BRIGHTNESS 0x0b9
+#define GC0308_FIXED_CB 0x0ba
+#define GC0308_FIXED_CR 0x0bb
+#define GC0308_EDGE_DEC_SA 0x0bd
+#define GC0308_AUTO_GRAY_MODE 0x0be
+#define GC0308_SATURATION_SUB_STRENGTH 0x0bf
+#define GC0308_Y_GAMMA_OUT0 0x0c0
+#define GC0308_Y_GAMMA_OUT1 0x0c1
+#define GC0308_Y_GAMMA_OUT2 0x0c2
+#define GC0308_Y_GAMMA_OUT3 0x0c3
+#define GC0308_Y_GAMMA_OUT4 0x0c4
+#define GC0308_Y_GAMMA_OUT5 0x0c5
+#define GC0308_Y_GAMMA_OUT6 0x0c6
+#define GC0308_Y_GAMMA_OUT7 0x0c7
+#define GC0308_Y_GAMMA_OUT8 0x0c8
+#define GC0308_Y_GAMMA_OUT9 0x0c9
+#define GC0308_Y_GAMMA_OUT10 0x0ca
+#define GC0308_Y_GAMMA_OUT11 0x0cb
+#define GC0308_Y_GAMMA_OUT12 0x0cc
+
+/* AEC - Automatic Exposure Control */
+#define GC0308_AEC_MODE1 0x0d0
+#define GC0308_AEC_MODE2 0x0d1
+#define GC0308_AEC_MODE3 0x0d2
+#define GC0308_AEC_TARGET_Y 0x0d3
+#define GC0308_Y_AVG 0x0d4
+#define GC0308_AEC_HIGH_LOW_RANGE 0x0d5
+#define GC0308_AEC_IGNORE 0x0d6
+#define GC0308_AEC_LIMIT_HIGH_RANGE 0x0d7
+#define GC0308_AEC_R_OFFSET 0x0d9
+#define GC0308_AEC_GB_OFFSET 0x0da
+#define GC0308_AEC_SLOW_MARGIN 0x0db
+#define GC0308_AEC_FAST_MARGIN 0x0dc
+#define GC0308_AEC_EXP_CHANGE_GAIN 0x0dd
+#define GC0308_AEC_STEP2_SUNLIGHT 0x0de
+#define GC0308_AEC_I_FRAMES 0x0df
+#define GC0308_AEC_I_STOP_L_MARGIN 0x0e0
+#define GC0308_AEC_I_STOP_MARGIN 0x0e1
+#define GC0308_ANTI_FLICKER_STEP_H 0x0e2
+#define GC0308_ANTI_FLICKER_STEP_L 0x0e3
+#define GC0308_EXP_LVL_1H 0x0e4
+#define GC0308_EXP_LVL_1L 0x0e5
+#define GC0308_EXP_LVL_2H 0x0e6
+#define GC0308_EXP_LVL_2L 0x0e7
+#define GC0308_EXP_LVL_3H 0x0e8
+#define GC0308_EXP_LVL_3L 0x0e9
+#define GC0308_EXP_LVL_4H 0x0ea
+#define GC0308_EXP_LVL_4L 0x0eb
+#define GC0308_MAX_EXP_LVL 0x0ec
+#define GC0308_EXP_MIN_L 0x0ed
+#define GC0308_MAX_POST_DF_GAIN 0x0ee
+#define GC0308_MAX_PRE_DG_GAIN 0x0ef
+
+/* ABS */
+#define GC0308_ABS_RANGE_COMP 0x0f0
+#define GC0308_ABS_STOP_MARGIN 0x0f1
+#define GC0308_Y_S_COMP 0x0f2
+#define GC0308_Y_STRETCH_LIMIT 0x0f3
+#define GC0308_Y_TILT 0x0f4
+#define GC0308_Y_STRETCH 0x0f5
+
+/* Measure Window */
+#define GC0308_BIG_WIN_X0 0x0f7
+#define GC0308_BIG_WIN_Y0 0x0f8
+#define GC0308_BIG_WIN_X1 0x0f9
+#define GC0308_BIG_WIN_Y1 0x0fa
+#define GC0308_DIFF_Y_BIG_THD 0x0fb
+
+/* OUT Module (P1) */
+#define GC0308_CLOSE_FRAME_EN 0x150
+#define GC0308_CLOSE_FRAME_NUM1 0x151
+#define GC0308_CLOSE_FRAME_NUM2 0x152
+#define GC0308_BAYER_MODE 0x153
+#define GC0308_SUBSAMPLE 0x154
+#define GC0308_SUBMODE 0x155
+#define GC0308_SUB_ROW_N1 0x156
+#define GC0308_SUB_ROW_N2 0x157
+#define GC0308_SUB_COL_N1 0x158
+#define GC0308_SUB_COL_N2 0x159
+
+/* AWB (P1) - Auto White Balance */
+#define GC0308_AWB_RGB_HIGH_LOW 0x100
+#define GC0308_AWB_Y_TO_C_DIFF2 0x102
+#define GC0308_AWB_C_MAX 0x104
+#define GC0308_AWB_C_INTER 0x105
+#define GC0308_AWB_C_INTER2 0x106
+#define GC0308_AWB_C_MAX_BIG 0x108
+#define GC0308_AWB_Y_HIGH 0x109
+#define GC0308_AWB_NUMBER_LIMIT 0x10a
+#define GC0308_KWIN_RATIO 0x10b
+#define GC0308_KWIN_THD 0x10c
+#define GC0308_LIGHT_GAIN_RANGE 0x10d
+#define GC0308_SMALL_WIN_WIDTH_STEP 0x10e
+#define GC0308_SMALL_WIN_HEIGHT_STEP 0x10f
+#define GC0308_AWB_YELLOW_TH 0x110
+#define GC0308_AWB_MODE 0x111
+#define GC0308_AWB_ADJUST_SPEED 0x112
+#define GC0308_AWB_EVERY_N 0x113
+#define GC0308_R_AVG_USE 0x1d0
+#define GC0308_G_AVG_USE 0x1d1
+#define GC0308_B_AVG_USE 0x1d2
+
+#define GC0308_FRAME_RATE 29
+
+enum gc0308_exp_val {
+ GC0308_EXP_M4 = 0,
+ GC0308_EXP_M3,
+ GC0308_EXP_M2,
+ GC0308_EXP_M1,
+ GC0308_EXP_0,
+ GC0308_EXP_P1,
+ GC0308_EXP_P2,
+ GC0308_EXP_P3,
+ GC0308_EXP_P4,
+};
+
+static const s64 gc0308_exposure_menu[] = {
+ -4, -3, -2, -1, 0, 1, 2, 3, 4
+};
+
+struct gc0308_exposure {
+ u8 luma_offset;
+ u8 aec_target_y;
+};
+
+#define GC0308_EXPOSURE(luma_offset_reg, aec_target_y_reg) \
+ { .luma_offset = luma_offset_reg, .aec_target_y = aec_target_y_reg }
+
+static const struct gc0308_exposure gc0308_exposure_values[] = {
+ [GC0308_EXP_M4] = GC0308_EXPOSURE(0xc0, 0x30),
+ [GC0308_EXP_M3] = GC0308_EXPOSURE(0xd0, 0x38),
+ [GC0308_EXP_M2] = GC0308_EXPOSURE(0xe0, 0x40),
+ [GC0308_EXP_M1] = GC0308_EXPOSURE(0xf0, 0x48),
+ [GC0308_EXP_0] = GC0308_EXPOSURE(0x08, 0x50),
+ [GC0308_EXP_P1] = GC0308_EXPOSURE(0x10, 0x5c),
+ [GC0308_EXP_P2] = GC0308_EXPOSURE(0x20, 0x60),
+ [GC0308_EXP_P3] = GC0308_EXPOSURE(0x30, 0x68),
+ [GC0308_EXP_P4] = GC0308_EXPOSURE(0x40, 0x70),
+};
+
+struct gc0308_awb_gains {
+ u8 r;
+ u8 g;
+ u8 b;
+};
+
+#define GC0308_AWB_GAINS(red, green, blue) \
+ { .r = red, .g = green, .b = blue }
+
+static const struct gc0308_awb_gains gc0308_awb_gains[] = {
+ [V4L2_WHITE_BALANCE_AUTO] = GC0308_AWB_GAINS(0x56, 0x40, 0x4a),
+ [V4L2_WHITE_BALANCE_CLOUDY] = GC0308_AWB_GAINS(0x8c, 0x50, 0x40),
+ [V4L2_WHITE_BALANCE_DAYLIGHT] = GC0308_AWB_GAINS(0x74, 0x52, 0x40),
+ [V4L2_WHITE_BALANCE_INCANDESCENT] = GC0308_AWB_GAINS(0x48, 0x40, 0x5c),
+ [V4L2_WHITE_BALANCE_FLUORESCENT] = GC0308_AWB_GAINS(0x40, 0x42, 0x50),
+};
+
+struct gc0308_format {
+ u32 code;
+ u8 regval;
+};
+
+#define GC0308_FORMAT(v4l2_code, gc0308_regval) \
+ { .code = v4l2_code, .regval = gc0308_regval }
+
+static const struct gc0308_format gc0308_formats[] = {
+ GC0308_FORMAT(MEDIA_BUS_FMT_UYVY8_2X8, 0x00),
+ GC0308_FORMAT(MEDIA_BUS_FMT_VYUY8_2X8, 0x01),
+ GC0308_FORMAT(MEDIA_BUS_FMT_YUYV8_2X8, 0x02),
+ GC0308_FORMAT(MEDIA_BUS_FMT_YVYU8_2X8, 0x03),
+ GC0308_FORMAT(MEDIA_BUS_FMT_RGB565_2X8_BE, 0x06),
+ GC0308_FORMAT(MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, 0x07),
+ GC0308_FORMAT(MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, 0x09),
+};
+
+struct gc0308_frame_size {
+ u8 subsample;
+ u32 width;
+ u32 height;
+};
+
+#define GC0308_FRAME_SIZE(s, w, h) \
+ { .subsample = s, .width = w, .height = h }
+
+static const struct gc0308_frame_size gc0308_frame_sizes[] = {
+ GC0308_FRAME_SIZE(0x11, 640, 480),
+ GC0308_FRAME_SIZE(0x22, 320, 240),
+ GC0308_FRAME_SIZE(0x44, 160, 120),
+};
+
+struct gc0308 {
+ struct v4l2_subdev sd;
+ struct v4l2_ctrl_handler hdl;
+ struct media_pad pad;
+ struct v4l2_mbus_framefmt format;
+ struct device *dev;
+ struct clk *clk;
+ struct regmap *regmap;
+ struct regulator *vdd;
+ struct gpio_desc *pwdn_gpio;
+ struct gpio_desc *reset_gpio;
+ unsigned int mbus_config;
+ struct v4l2_fract frame_interval;
+};
+
+static inline struct gc0308 *to_gc0308(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct gc0308, sd);
+}
+
+static const struct regmap_range_cfg gc0308_ranges[] = {
+ {
+ .range_min = 0x0000,
+ .range_max = 0x01ff,
+ .selector_reg = 0xfe,
+ .selector_mask = 0x01,
+ .selector_shift = 0x00,
+ .window_start = 0x00,
+ .window_len = 0x100,
+ },
+};
+
+static const struct regmap_config gc0308_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x1ff,
+ .ranges = gc0308_ranges,
+ .num_ranges = ARRAY_SIZE(gc0308_ranges),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct reg_sequence sensor_default_regs[] = {
+ REG_SEQ0(GC0308_VB_HB, 0x00),
+ REG_SEQ0(GC0308_HBLANK, 0x40),
+ REG_SEQ0(GC0308_VBLANK, 0x20),
+ REG_SEQ0(GC0308_EXPH, 0x02),
+ REG_SEQ0(GC0308_EXPL, 0x58),
+ REG_SEQ0(GC0308_AWB_R_GAIN, 0x56),
+ REG_SEQ0(GC0308_AWB_G_GAIN, 0x40),
+ REG_SEQ0(GC0308_AWB_B_GAIN, 0x4a),
+ REG_SEQ0(GC0308_ANTI_FLICKER_STEP_H, 0x00),
+ REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x78),
+ REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
+ REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
+ REG_SEQ0(GC0308_EXP_LVL_2H, 0x02),
+ REG_SEQ0(GC0308_EXP_LVL_2L, 0x58),
+ REG_SEQ0(GC0308_EXP_LVL_3H, 0x02),
+ REG_SEQ0(GC0308_EXP_LVL_3L, 0x58),
+ REG_SEQ0(GC0308_EXP_LVL_4H, 0x0e),
+ REG_SEQ0(GC0308_EXP_LVL_4L, 0xa6),
+ REG_SEQ0(GC0308_MAX_EXP_LVL, 0x20),
+ REG_SEQ0(GC0308_ROW_STARTH, 0x00),
+ REG_SEQ0(GC0308_ROW_STARTL, 0x00),
+ REG_SEQ0(GC0308_COL_STARTH, 0x00),
+ REG_SEQ0(GC0308_COL_STARTL, 0x00),
+ REG_SEQ0(GC0308_WIN_HEIGHTH, 0x01), /* 0x1e8 = 488 => VT=480 */
+ REG_SEQ0(GC0308_WIN_HEIGHTL, 0xe8),
+ REG_SEQ0(GC0308_WIN_WIDTHH, 0x02), /* 0x288 = 648 => VH=640 */
+ REG_SEQ0(GC0308_WIN_WIDTHL, 0x88),
+ REG_SEQ0(GC0308_VS_ST, 0x02), /* Start Time (in rows) */
+ REG_SEQ0(GC0308_VS_ET, 0x02), /* End Time (in rows) */
+ REG_SEQ0(GC0308_RSH_WIDTH, 0x22),
+ REG_SEQ0(GC0308_TSP_WIDTH, 0x0d),
+ REG_SEQ0(GC0308_SH_DELAY, 0x50), /* sample hold delay post row finish */
+ REG_SEQ0(GC0308_ROW_TAIL_WIDTH, 0x0f),
+ REG_SEQ0(GC0308_CISCTL_MODE1, 0x10),
+ REG_SEQ0(GC0308_CISCTL_MODE2, 0x0a),
+ REG_SEQ0(GC0308_CISCTL_MODE3, 0x05),
+ REG_SEQ0(GC0308_CISCTL_MODE4, 0x01),
+ REG_SEQ0(0x018, 0x44), /* undocumented */
+ REG_SEQ0(0x019, 0x44), /* undocumented */
+ REG_SEQ0(GC0308_ANALOG_MODE1, 0x2a),
+ REG_SEQ0(GC0308_ANALOG_MODE2, 0x00),
+ REG_SEQ0(GC0308_HRST_RSG_V18, 0x49),
+ REG_SEQ0(GC0308_VREF_V25, 0x9a),
+ REG_SEQ0(GC0308_ADC_R, 0x61),
+ REG_SEQ0(GC0308_PAD_DRV, 0x01), /* drv strength: pclk=4mA */
+ REG_SEQ0(GC0308_BLOCK_EN1, 0x7f),
+ REG_SEQ0(GC0308_BLOCK_EN2, 0xfa),
+ REG_SEQ0(GC0308_AAAA_EN, 0x57),
+ REG_SEQ0(GC0308_OUT_FORMAT, 0xa2), /* YCbYCr */
+ REG_SEQ0(GC0308_OUT_EN, 0x0f),
+ REG_SEQ0(GC0308_SYNC_MODE, 0x03),
+ REG_SEQ0(GC0308_CLK_DIV_MODE, 0x00),
+ REG_SEQ0(GC0308_DEBUG_MODE1, 0x0a),
+ REG_SEQ0(GC0308_DEBUG_MODE2, 0x00),
+ REG_SEQ0(GC0308_DEBUG_MODE3, 0x01),
+ REG_SEQ0(GC0308_BLK_MODE, 0xf7),
+ REG_SEQ0(GC0308_BLK_LIMIT_VAL, 0x50),
+ REG_SEQ0(GC0308_GLOBAL_OFF, 0x00),
+ REG_SEQ0(GC0308_CURRENT_R_OFF, 0x28),
+ REG_SEQ0(GC0308_CURRENT_G_OFF, 0x2a),
+ REG_SEQ0(GC0308_CURRENT_B_OFF, 0x28),
+ REG_SEQ0(GC0308_EXP_RATE_DARKC, 0x04),
+ REG_SEQ0(GC0308_OFF_SUBMODE, 0x20),
+ REG_SEQ0(GC0308_DARKC_SUBMODE, 0x20),
+ REG_SEQ0(GC0308_MANUAL_G1_OFF, 0x00),
+ REG_SEQ0(GC0308_MANUAL_R1_OFF, 0x00),
+ REG_SEQ0(GC0308_MANUAL_B2_OFF, 0x00),
+ REG_SEQ0(GC0308_MANUAL_G2_OFF, 0x00),
+ REG_SEQ0(GC0308_GLOBAL_GAIN, 0x14),
+ REG_SEQ0(GC0308_AUTO_POSTGAIN, 0x41),
+ REG_SEQ0(GC0308_CHANNEL_GAIN_G1, 0x80),
+ REG_SEQ0(GC0308_CHANNEL_GAIN_R, 0x80),
+ REG_SEQ0(GC0308_CHANNEL_GAIN_B, 0x80),
+ REG_SEQ0(GC0308_CHANNEL_GAIN_G2, 0x80),
+ REG_SEQ0(GC0308_LSC_RED_B2, 0x20),
+ REG_SEQ0(GC0308_LSC_GREEN_B2, 0x20),
+ REG_SEQ0(GC0308_LSC_BLUE_B2, 0x20),
+ REG_SEQ0(GC0308_LSC_RED_B4, 0x14),
+ REG_SEQ0(GC0308_LSC_GREEN_B4, 0x10),
+ REG_SEQ0(GC0308_LSC_BLUE_B4, 0x14),
+ REG_SEQ0(GC0308_LSC_ROW_CENTER, 0x3c),
+ REG_SEQ0(GC0308_LSC_COL_CENTER, 0x50),
+ REG_SEQ0(GC0308_LSC_DEC_LVL1, 0x12),
+ REG_SEQ0(GC0308_LSC_DEC_LVL2, 0x1a),
+ REG_SEQ0(GC0308_LSC_DEC_LVL3, 0x24),
+ REG_SEQ0(GC0308_DN_MODE_EN, 0x07),
+ REG_SEQ0(GC0308_DN_MODE_RATIO, 0x15),
+ REG_SEQ0(GC0308_DN_BILAT_B_BASE, 0x08),
+ REG_SEQ0(GC0308_DN_BILAT_N_BASE, 0x03),
+ REG_SEQ0(GC0308_DD_DARK_BRIGHT_TH, 0xe8),
+ REG_SEQ0(GC0308_DD_FLAT_TH, 0x86),
+ REG_SEQ0(GC0308_DD_LIMIT, 0x82),
+ REG_SEQ0(GC0308_ASDE_GAIN_TRESH, 0x18),
+ REG_SEQ0(GC0308_ASDE_GAIN_MODE, 0x0f),
+ REG_SEQ0(GC0308_ASDE_DN_SLOPE, 0x00),
+ REG_SEQ0(GC0308_ASDE_DD_BRIGHT, 0x5f),
+ REG_SEQ0(GC0308_ASDE_DD_LIMIT, 0x8f),
+ REG_SEQ0(GC0308_ASDE_AUTO_EE1, 0x55),
+ REG_SEQ0(GC0308_ASDE_AUTO_EE2, 0x38),
+ REG_SEQ0(GC0308_ASDE_AUTO_SAT_DEC_SLOPE, 0x15),
+ REG_SEQ0(GC0308_ASDE_AUTO_SAT_LOW_LIMIT, 0x33),
+ REG_SEQ0(GC0308_EEINTP_MODE_1, 0xdc),
+ REG_SEQ0(GC0308_EEINTP_MODE_2, 0x00),
+ REG_SEQ0(GC0308_DIRECTION_TH1, 0x02),
+ REG_SEQ0(GC0308_DIRECTION_TH2, 0x3f),
+ REG_SEQ0(GC0308_DIFF_HV_TI_TH, 0x02),
+ REG_SEQ0(GC0308_EDGE12_EFFECT, 0x38),
+ REG_SEQ0(GC0308_EDGE_POS_RATIO, 0x88),
+ REG_SEQ0(GC0308_EDGE1_MINMAX, 0x81),
+ REG_SEQ0(GC0308_EDGE2_MINMAX, 0x81),
+ REG_SEQ0(GC0308_EDGE12_TH, 0x22),
+ REG_SEQ0(GC0308_EDGE_MAX, 0xff),
+ REG_SEQ0(GC0308_CC_MATRIX_C11, 0x48),
+ REG_SEQ0(GC0308_CC_MATRIX_C12, 0x02),
+ REG_SEQ0(GC0308_CC_MATRIX_C13, 0x07),
+ REG_SEQ0(GC0308_CC_MATRIX_C21, 0xe0),
+ REG_SEQ0(GC0308_CC_MATRIX_C22, 0x40),
+ REG_SEQ0(GC0308_CC_MATRIX_C23, 0xf0),
+ REG_SEQ0(GC0308_SATURATION_CB, 0x40),
+ REG_SEQ0(GC0308_SATURATION_CR, 0x40),
+ REG_SEQ0(GC0308_LUMA_CONTRAST, 0x40),
+ REG_SEQ0(GC0308_SKIN_CB_CENTER, 0xe0),
+ REG_SEQ0(GC0308_EDGE_DEC_SA, 0x38),
+ REG_SEQ0(GC0308_AUTO_GRAY_MODE, 0x36),
+ REG_SEQ0(GC0308_AEC_MODE1, 0xcb),
+ REG_SEQ0(GC0308_AEC_MODE2, 0x10),
+ REG_SEQ0(GC0308_AEC_MODE3, 0x90),
+ REG_SEQ0(GC0308_AEC_TARGET_Y, 0x48),
+ REG_SEQ0(GC0308_AEC_HIGH_LOW_RANGE, 0xf2),
+ REG_SEQ0(GC0308_AEC_IGNORE, 0x16),
+ REG_SEQ0(GC0308_AEC_SLOW_MARGIN, 0x92),
+ REG_SEQ0(GC0308_AEC_FAST_MARGIN, 0xa5),
+ REG_SEQ0(GC0308_AEC_I_FRAMES, 0x23),
+ REG_SEQ0(GC0308_AEC_R_OFFSET, 0x00),
+ REG_SEQ0(GC0308_AEC_GB_OFFSET, 0x00),
+ REG_SEQ0(GC0308_AEC_I_STOP_L_MARGIN, 0x09),
+ REG_SEQ0(GC0308_EXP_MIN_L, 0x04),
+ REG_SEQ0(GC0308_MAX_POST_DF_GAIN, 0xa0),
+ REG_SEQ0(GC0308_MAX_PRE_DG_GAIN, 0x40),
+ REG_SEQ0(GC0308_ABB_MODE, 0x03),
+ REG_SEQ0(GC0308_GAMMA_OUT0, 0x10),
+ REG_SEQ0(GC0308_GAMMA_OUT1, 0x20),
+ REG_SEQ0(GC0308_GAMMA_OUT2, 0x38),
+ REG_SEQ0(GC0308_GAMMA_OUT3, 0x4e),
+ REG_SEQ0(GC0308_GAMMA_OUT4, 0x63),
+ REG_SEQ0(GC0308_GAMMA_OUT5, 0x76),
+ REG_SEQ0(GC0308_GAMMA_OUT6, 0x87),
+ REG_SEQ0(GC0308_GAMMA_OUT7, 0xa2),
+ REG_SEQ0(GC0308_GAMMA_OUT8, 0xb8),
+ REG_SEQ0(GC0308_GAMMA_OUT9, 0xca),
+ REG_SEQ0(GC0308_GAMMA_OUT10, 0xd8),
+ REG_SEQ0(GC0308_GAMMA_OUT11, 0xe3),
+ REG_SEQ0(GC0308_GAMMA_OUT12, 0xeb),
+ REG_SEQ0(GC0308_GAMMA_OUT13, 0xf0),
+ REG_SEQ0(GC0308_GAMMA_OUT14, 0xf8),
+ REG_SEQ0(GC0308_GAMMA_OUT15, 0xfd),
+ REG_SEQ0(GC0308_GAMMA_OUT16, 0xff),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT0, 0x00),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT1, 0x10),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT2, 0x1c),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT3, 0x30),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT4, 0x43),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT5, 0x54),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT6, 0x65),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT7, 0x75),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT8, 0x93),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT9, 0xb0),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT10, 0xcb),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT11, 0xe6),
+ REG_SEQ0(GC0308_Y_GAMMA_OUT12, 0xff),
+ REG_SEQ0(GC0308_ABS_RANGE_COMP, 0x02),
+ REG_SEQ0(GC0308_ABS_STOP_MARGIN, 0x01),
+ REG_SEQ0(GC0308_Y_S_COMP, 0x02),
+ REG_SEQ0(GC0308_Y_STRETCH_LIMIT, 0x30),
+ REG_SEQ0(GC0308_BIG_WIN_X0, 0x12),
+ REG_SEQ0(GC0308_BIG_WIN_Y0, 0x0a),
+ REG_SEQ0(GC0308_BIG_WIN_X1, 0x9f),
+ REG_SEQ0(GC0308_BIG_WIN_Y1, 0x78),
+ REG_SEQ0(GC0308_AWB_RGB_HIGH_LOW, 0xf5),
+ REG_SEQ0(GC0308_AWB_Y_TO_C_DIFF2, 0x20),
+ REG_SEQ0(GC0308_AWB_C_MAX, 0x10),
+ REG_SEQ0(GC0308_AWB_C_INTER, 0x08),
+ REG_SEQ0(GC0308_AWB_C_INTER2, 0x20),
+ REG_SEQ0(GC0308_AWB_C_MAX_BIG, 0x0a),
+ REG_SEQ0(GC0308_AWB_NUMBER_LIMIT, 0xa0),
+ REG_SEQ0(GC0308_KWIN_RATIO, 0x60),
+ REG_SEQ0(GC0308_KWIN_THD, 0x08),
+ REG_SEQ0(GC0308_SMALL_WIN_WIDTH_STEP, 0x44),
+ REG_SEQ0(GC0308_SMALL_WIN_HEIGHT_STEP, 0x32),
+ REG_SEQ0(GC0308_AWB_YELLOW_TH, 0x41),
+ REG_SEQ0(GC0308_AWB_MODE, 0x37),
+ REG_SEQ0(GC0308_AWB_ADJUST_SPEED, 0x22),
+ REG_SEQ0(GC0308_AWB_EVERY_N, 0x19),
+ REG_SEQ0(0x114, 0x44), /* AWB set1 */
+ REG_SEQ0(0x115, 0x44), /* AWB set1 */
+ REG_SEQ0(0x116, 0xc2), /* AWB set1 */
+ REG_SEQ0(0x117, 0xa8), /* AWB set1 */
+ REG_SEQ0(0x118, 0x18), /* AWB set1 */
+ REG_SEQ0(0x119, 0x50), /* AWB set1 */
+ REG_SEQ0(0x11a, 0xd8), /* AWB set1 */
+ REG_SEQ0(0x11b, 0xf5), /* AWB set1 */
+ REG_SEQ0(0x170, 0x40), /* AWB set2 */
+ REG_SEQ0(0x171, 0x58), /* AWB set2 */
+ REG_SEQ0(0x172, 0x30), /* AWB set2 */
+ REG_SEQ0(0x173, 0x48), /* AWB set2 */
+ REG_SEQ0(0x174, 0x20), /* AWB set2 */
+ REG_SEQ0(0x175, 0x60), /* AWB set2 */
+ REG_SEQ0(0x177, 0x20), /* AWB set2 */
+ REG_SEQ0(0x178, 0x32), /* AWB set2 */
+ REG_SEQ0(0x130, 0x03), /* undocumented */
+ REG_SEQ0(0x131, 0x40), /* undocumented */
+ REG_SEQ0(0x132, 0x10), /* undocumented */
+ REG_SEQ0(0x133, 0xe0), /* undocumented */
+ REG_SEQ0(0x134, 0xe0), /* undocumented */
+ REG_SEQ0(0x135, 0x00), /* undocumented */
+ REG_SEQ0(0x136, 0x80), /* undocumented */
+ REG_SEQ0(0x137, 0x00), /* undocumented */
+ REG_SEQ0(0x138, 0x04), /* undocumented */
+ REG_SEQ0(0x139, 0x09), /* undocumented */
+ REG_SEQ0(0x13a, 0x12), /* undocumented */
+ REG_SEQ0(0x13b, 0x1c), /* undocumented */
+ REG_SEQ0(0x13c, 0x28), /* undocumented */
+ REG_SEQ0(0x13d, 0x31), /* undocumented */
+ REG_SEQ0(0x13e, 0x44), /* undocumented */
+ REG_SEQ0(0x13f, 0x57), /* undocumented */
+ REG_SEQ0(0x140, 0x6c), /* undocumented */
+ REG_SEQ0(0x141, 0x81), /* undocumented */
+ REG_SEQ0(0x142, 0x94), /* undocumented */
+ REG_SEQ0(0x143, 0xa7), /* undocumented */
+ REG_SEQ0(0x144, 0xb8), /* undocumented */
+ REG_SEQ0(0x145, 0xd6), /* undocumented */
+ REG_SEQ0(0x146, 0xee), /* undocumented */
+ REG_SEQ0(0x147, 0x0d), /* undocumented */
+ REG_SEQ0(0x162, 0xf7), /* undocumented */
+ REG_SEQ0(0x163, 0x68), /* undocumented */
+ REG_SEQ0(0x164, 0xd3), /* undocumented */
+ REG_SEQ0(0x165, 0xd3), /* undocumented */
+ REG_SEQ0(0x166, 0x60), /* undocumented */
+};
+
+struct gc0308_colormode {
+ u8 special_effect;
+ u8 dbg_mode1;
+ u8 block_en1;
+ u8 aec_mode3;
+ u8 eeintp_mode_2;
+ u8 edge12_effect;
+ u8 luma_contrast;
+ u8 contrast_center;
+ u8 fixed_cb;
+ u8 fixed_cr;
+};
+
+#define GC0308_COLOR_FX(reg_special_effect, reg_dbg_mode1, reg_block_en1, \
+ reg_aec_mode3, reg_eeintp_mode_2, reg_edge12_effect, \
+ reg_luma_contrast, reg_contrast_center, \
+ reg_fixed_cb, reg_fixed_cr) \
+ { \
+ .special_effect = reg_special_effect, \
+ .dbg_mode1 = reg_dbg_mode1, \
+ .block_en1 = reg_block_en1, \
+ .aec_mode3 = reg_aec_mode3, \
+ .eeintp_mode_2 = reg_eeintp_mode_2, \
+ .edge12_effect = reg_edge12_effect, \
+ .luma_contrast = reg_luma_contrast, \
+ .contrast_center = reg_contrast_center, \
+ .fixed_cb = reg_fixed_cb, \
+ .fixed_cr = reg_fixed_cr, \
+ }
+
+static const struct gc0308_colormode gc0308_colormodes[] = {
+ [V4L2_COLORFX_NONE] =
+ GC0308_COLOR_FX(0x00, 0x0a, 0xff, 0x90, 0x00,
+ 0x54, 0x3c, 0x80, 0x00, 0x00),
+ [V4L2_COLORFX_BW] =
+ GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
+ 0x54, 0x40, 0x80, 0x00, 0x00),
+ [V4L2_COLORFX_SEPIA] =
+ GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
+ 0x38, 0x40, 0x80, 0xd0, 0x28),
+ [V4L2_COLORFX_NEGATIVE] =
+ GC0308_COLOR_FX(0x01, 0x0a, 0xff, 0x90, 0x00,
+ 0x38, 0x40, 0x80, 0x00, 0x00),
+ [V4L2_COLORFX_EMBOSS] =
+ GC0308_COLOR_FX(0x02, 0x0a, 0xbf, 0x10, 0x01,
+ 0x38, 0x40, 0x80, 0x00, 0x00),
+ [V4L2_COLORFX_SKETCH] =
+ GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x10, 0x80,
+ 0x38, 0x80, 0x90, 0x00, 0x00),
+ [V4L2_COLORFX_SKY_BLUE] =
+ GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
+ 0x38, 0x40, 0x80, 0x50, 0xe0),
+ [V4L2_COLORFX_GRASS_GREEN] =
+ GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x01,
+ 0x38, 0x40, 0x80, 0xc0, 0xc0),
+ [V4L2_COLORFX_SKIN_WHITEN] =
+ GC0308_COLOR_FX(0x02, 0x0a, 0xbf, 0x10, 0x01,
+ 0x38, 0x60, 0x40, 0x00, 0x00),
+};
+
+static const struct reg_sequence sensor_banding_50hz[] = {
+ REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x78),
+ REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
+ REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
+ REG_SEQ0(GC0308_EXP_LVL_2H, 0x03),
+ REG_SEQ0(GC0308_EXP_LVL_2L, 0x48),
+ REG_SEQ0(GC0308_EXP_LVL_3H, 0x04),
+ REG_SEQ0(GC0308_EXP_LVL_3L, 0xb0),
+ REG_SEQ0(GC0308_EXP_LVL_4H, 0x05),
+ REG_SEQ0(GC0308_EXP_LVL_4L, 0xa0),
+};
+
+static const struct reg_sequence sensor_banding_60hz[] = {
+ REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x64),
+ REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
+ REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
+ REG_SEQ0(GC0308_EXP_LVL_2H, 0x03),
+ REG_SEQ0(GC0308_EXP_LVL_2L, 0x84),
+ REG_SEQ0(GC0308_EXP_LVL_3H, 0x04),
+ REG_SEQ0(GC0308_EXP_LVL_3L, 0xb0),
+ REG_SEQ0(GC0308_EXP_LVL_4H, 0x05),
+ REG_SEQ0(GC0308_EXP_LVL_4L, 0xdc),
+};
+
+static int gc0308_power_on(struct device *dev)
+{
+ struct gc0308 *gc0308 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_enable(gc0308->vdd);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(gc0308->clk);
+ if (ret)
+ goto clk_fail;
+
+ gpiod_set_value_cansleep(gc0308->pwdn_gpio, 0);
+ usleep_range(10000, 20000);
+
+ gpiod_set_value_cansleep(gc0308->reset_gpio, 1);
+ msleep(100);
+ gpiod_set_value_cansleep(gc0308->reset_gpio, 0);
+ msleep(100);
+
+ return 0;
+
+clk_fail:
+ regulator_disable(gc0308->vdd);
+ return ret;
+}
+
+static int gc0308_power_off(struct device *dev)
+{
+ struct gc0308 *gc0308 = dev_get_drvdata(dev);
+
+ gpiod_set_value_cansleep(gc0308->pwdn_gpio, 1);
+ clk_disable_unprepare(gc0308->clk);
+ regulator_disable(gc0308->vdd);
+
+ return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int gc0308_g_register(struct v4l2_subdev *sd,
+ struct v4l2_dbg_register *reg)
+{
+ struct gc0308 *gc0308 = to_gc0308(sd);
+ int ret, val;
+
+ ret = regmap_read(gc0308->regmap, reg->reg, &val);
+ if (ret)
+ return ret;
+
+ reg->val = val;
+ return 0;
+}
+
+static int gc0308_s_register(struct v4l2_subdev *sd,
+ const struct v4l2_dbg_register *reg)
+{
+ struct gc0308 *gc0308 = to_gc0308(sd);
+
+ return regmap_write(gc0308->regmap, reg->reg, reg->val);
+}
+#endif
+
+static int gc0308_s_format(struct gc0308 *gc0308)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(gc0308_formats); i++) {
+ if (gc0308->format.code != gc0308_formats[i].code)
+ continue;
+ return regmap_update_bits(gc0308->regmap,
+ GC0308_OUT_FORMAT, GENMASK(4, 0),
+ gc0308_formats[i].regval);
+ }
+
+ return -EINVAL;
+}
+
+static int gc0308_set_exposure(struct gc0308 *gc0308, enum gc0308_exp_val exp)
+{
+ const struct gc0308_exposure *regs = &gc0308_exposure_values[exp];
+ struct reg_sequence exposure_reg_seq[] = {
+ REG_SEQ0(GC0308_LUMA_OFFSET, regs->luma_offset),
+ REG_SEQ0(GC0308_AEC_TARGET_Y, regs->aec_target_y),
+ };
+
+ dev_err(gc0308->dev, "exposure: %i\n", exp);
+
+ return regmap_multi_reg_write(gc0308->regmap,
+ exposure_reg_seq,
+ ARRAY_SIZE(exposure_reg_seq));
+}
+
+static int gc0308_set_awb_mode(struct gc0308 *gc0308,
+ enum v4l2_auto_n_preset_white_balance val)
+{
+ const struct gc0308_awb_gains *regs = &gc0308_awb_gains[val];
+ struct reg_sequence awb_reg_seq[] = {
+ REG_SEQ0(GC0308_AWB_R_GAIN, regs->r),
+ REG_SEQ0(GC0308_AWB_G_GAIN, regs->g),
+ REG_SEQ0(GC0308_AWB_B_GAIN, regs->b),
+ };
+ int ret;
+
+ ret = regmap_update_bits(gc0308->regmap, GC0308_AAAA_EN,
+ BIT(1), val == V4L2_WHITE_BALANCE_AUTO);
+ if (ret)
+ return ret;
+
+ return regmap_multi_reg_write(gc0308->regmap,
+ awb_reg_seq,
+ ARRAY_SIZE(awb_reg_seq));
+}
+
+static int gc0308_set_colormode(struct gc0308 *gc0308, enum v4l2_colorfx mode)
+{
+ const struct gc0308_colormode *regs = &gc0308_colormodes[mode];
+ struct reg_sequence colormode_reg_seq[] = {
+ REG_SEQ0(GC0308_SPECIAL_EFFECT, regs->special_effect),
+ REG_SEQ0(GC0308_DEBUG_MODE1, regs->dbg_mode1),
+ REG_SEQ0(GC0308_BLOCK_EN1, regs->block_en1),
+ REG_SEQ0(GC0308_AEC_MODE3, regs->aec_mode3),
+ REG_SEQ0(GC0308_EEINTP_MODE_2, regs->eeintp_mode_2),
+ REG_SEQ0(GC0308_EDGE12_EFFECT, regs->edge12_effect),
+ REG_SEQ0(GC0308_LUMA_CONTRAST, regs->luma_contrast),
+ REG_SEQ0(GC0308_CONTRAST_CENTER, regs->contrast_center),
+ REG_SEQ0(GC0308_FIXED_CB, regs->fixed_cb),
+ REG_SEQ0(GC0308_FIXED_CR, regs->fixed_cr),
+ };
+
+ return regmap_multi_reg_write(gc0308->regmap,
+ colormode_reg_seq,
+ ARRAY_SIZE(colormode_reg_seq));
+}
+
+static int gc0308_set_power_line_freq(struct gc0308 *gc0308, int frequency)
+{
+ const struct reg_sequence *regs;
+ int num_regs;
+
+ switch (frequency) {
+ case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
+ regs = sensor_banding_60hz;
+ num_regs = ARRAY_SIZE(sensor_banding_60hz);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
+ default:
+ regs = sensor_banding_50hz;
+ num_regs = ARRAY_SIZE(sensor_banding_50hz);
+ break;
+ }
+
+ return regmap_multi_reg_write(gc0308->regmap, regs, num_regs);
+}
+
+static int _gc0308_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl);
+ uint8_t flipval = ctrl->val ? 0xff : 0x00;
+
+ switch (ctrl->id) {
+ case V4L2_CID_VFLIP:
+ return regmap_update_bits(gc0308->regmap, GC0308_CISCTL_MODE1,
+ BIT(1), flipval);
+ case V4L2_CID_HFLIP:
+ return regmap_update_bits(gc0308->regmap, GC0308_CISCTL_MODE1,
+ BIT(0), flipval);
+ case V4L2_CID_AUTO_WHITE_BALANCE:
+ return regmap_update_bits(gc0308->regmap, GC0308_AAAA_EN,
+ BIT(1), flipval);
+ case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE:
+ return gc0308_set_awb_mode(gc0308, ctrl->val);
+ case V4L2_CID_POWER_LINE_FREQUENCY:
+ return gc0308_set_power_line_freq(gc0308, ctrl->val);
+ case V4L2_CID_COLORFX:
+ return gc0308_set_colormode(gc0308, ctrl->val);
+ case V4L2_CID_TEST_PATTERN:
+ return regmap_update_bits(gc0308->regmap, GC0308_DEBUG_MODE2,
+ GENMASK(1, 0), ctrl->val);
+ case V4L2_CID_AUTO_EXPOSURE_BIAS:
+ return gc0308_set_exposure(gc0308, ctrl->val);
+ }
+
+ return -EINVAL;
+}
+
+static int gc0308_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(gc0308->dev);
+ if (ret < 0)
+ return ret;
+
+ ret = _gc0308_s_ctrl(ctrl);
+
+ pm_runtime_put(gc0308->dev);
+
+ if (ret)
+ dev_err(gc0308->dev, "s_ctrl failed: %d\n", ret);
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops gc0308_ctrl_ops = {
+ .s_ctrl = gc0308_s_ctrl,
+};
+
+static const struct v4l2_subdev_core_ops gc0308_core_ops = {
+ .log_status = v4l2_ctrl_subdev_log_status,
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .g_register = gc0308_g_register,
+ .s_register = gc0308_s_register,
+#endif
+};
+
+static int gc0308_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index >= ARRAY_SIZE(gc0308_formats))
+ return -EINVAL;
+
+ code->code = gc0308_formats[code->index].code;
+
+ return 0;
+}
+
+static int gc0308_enum_frame_size(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ if (fse->index >= ARRAY_SIZE(gc0308_frame_sizes))
+ return -EINVAL;
+
+ fse->min_width = gc0308_frame_sizes[fse->index].width;
+ fse->max_width = gc0308_frame_sizes[fse->index].width;
+ fse->min_height = gc0308_frame_sizes[fse->index].height;
+ fse->max_height = gc0308_frame_sizes[fse->index].height;
+
+ return 0;
+}
+
+static void gc0308_update_pad_format(const struct gc0308_frame_size *mode,
+ struct v4l2_mbus_framefmt *fmt, u32 code)
+{
+ fmt->width = mode->width;
+ fmt->height = mode->height;
+ fmt->code = code;
+ fmt->field = V4L2_FIELD_NONE;
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
+}
+
+static int gc0308_set_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct gc0308 *gc0308 = to_gc0308(sd);
+ const struct gc0308_frame_size *mode;
+ unsigned int i;
+ u32 code = 0;
+
+ for (i = 0; i < ARRAY_SIZE(gc0308_formats); i++) {
+ if (fmt->format.code == gc0308_formats[i].code) {
+ code = gc0308_formats[i].code;
+ break;
+ }
+ }
+
+ if (!code) {
+ dev_warn(gc0308->dev, "unsupported format code: %08x\n",
+ fmt->format.code);
+ code = gc0308_formats[2].code;
+ }
+
+ mode = v4l2_find_nearest_size(gc0308_frame_sizes,
+ ARRAY_SIZE(gc0308_frame_sizes), width,
+ height, fmt->format.width,
+ fmt->format.height);
+
+ gc0308_update_pad_format(mode, &fmt->format, code);
+ *v4l2_subdev_get_pad_format(sd, sd_state, fmt->pad) = fmt->format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
+ return 0;
+
+ gc0308->format.width = fmt->format.width;
+ gc0308->format.height = fmt->format.height;
+ gc0308->format.code = fmt->format.code;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops gc0308_pad_ops = {
+ .enum_mbus_code = gc0308_enum_mbus_code,
+ .enum_frame_size = gc0308_enum_frame_size,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = gc0308_set_format,
+};
+
+static int gc0308_set_resolution(struct gc0308 *gc0308,
+ u8 subsample, u16 w, u16 h)
+{
+ struct reg_sequence resolution_regs[] = {
+ REG_SEQ0(GC0308_SUBSAMPLE, subsample),
+ REG_SEQ0(GC0308_SUBMODE, 0x03),
+ REG_SEQ0(GC0308_SUB_ROW_N1, 0x00),
+ REG_SEQ0(GC0308_SUB_ROW_N2, 0x00),
+ REG_SEQ0(GC0308_SUB_COL_N1, 0x00),
+ REG_SEQ0(GC0308_SUB_COL_N2, 0x00),
+ REG_SEQ0(GC0308_CROP_WIN_MODE, 0x80),
+ REG_SEQ0(GC0308_CROP_WIN_Y1, 0x00),
+ REG_SEQ0(GC0308_CROP_WIN_X1, 0x00),
+ REG_SEQ0(GC0308_CROP_WIN_HEIGHTH, (h >> 8) & 0xFF),
+ REG_SEQ0(GC0308_CROP_WIN_HEIGHTL, (h >> 0) & 0xFF),
+ REG_SEQ0(GC0308_CROP_WIN_WIDTHH, (w >> 8) & 0xFF),
+ REG_SEQ0(GC0308_CROP_WIN_WIDTHL, (w >> 0) & 0xFF),
+ };
+
+ return regmap_multi_reg_write(gc0308->regmap, resolution_regs,
+ ARRAY_SIZE(resolution_regs));
+}
+
+static int gc0308_start_stream(struct gc0308 *gc0308)
+{
+ int ret, sync_mode;
+ u16 width = 0, height = 0;
+ u8 subsample = 0;
+ unsigned int i;
+
+ ret = pm_runtime_resume_and_get(gc0308->dev);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_multi_reg_write(gc0308->regmap, sensor_default_regs,
+ ARRAY_SIZE(sensor_default_regs));
+ if (ret) {
+ dev_err(gc0308->dev, "failed to write default regs\n");
+ goto disable_pm;
+ }
+
+ ret = gc0308_s_format(gc0308);
+ if (ret) {
+ dev_err(gc0308->dev, "failed to set format\n");
+ goto disable_pm;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(gc0308_frame_sizes); i++) {
+ if (gc0308_frame_sizes[i].width == gc0308->format.width &&
+ gc0308_frame_sizes[i].height == gc0308->format.height) {
+ width = gc0308_frame_sizes[i].width;
+ height = gc0308_frame_sizes[i].height;
+ subsample = gc0308_frame_sizes[i].subsample;
+ }
+ }
+
+ if (!width || !height) {
+ dev_err(gc0308->dev, "invalid resolution\n");
+ goto disable_pm;
+ }
+
+ ret = gc0308_set_resolution(gc0308, subsample, width, height);
+ if (ret) {
+ dev_err(gc0308->dev, "failed to write resolution regs\n");
+ goto disable_pm;
+ }
+
+ ret = __v4l2_ctrl_handler_setup(&gc0308->hdl);
+ if (ret) {
+ dev_err(gc0308->dev, "failed to setup controls\n");
+ goto disable_pm;
+ }
+
+ /* HSYNC/VSYNC polarity */
+ sync_mode = 0x3;
+ if (gc0308->mbus_config & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+ sync_mode &= ~BIT(0);
+ if (gc0308->mbus_config & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+ sync_mode &= ~BIT(1);
+ ret = regmap_write(gc0308->regmap, GC0308_SYNC_MODE, sync_mode);
+ if (ret)
+ goto disable_pm;
+
+ return 0;
+
+disable_pm:
+ pm_runtime_put(gc0308->dev);
+ return ret;
+}
+
+static int gc0308_stop_stream(struct gc0308 *gc0308)
+{
+ pm_runtime_put(gc0308->dev);
+ return 0;
+}
+
+static int gc0308_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct gc0308 *gc0308 = to_gc0308(sd);
+ struct v4l2_subdev_state *sd_state;
+ int ret;
+
+ sd_state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ if (enable)
+ ret = gc0308_start_stream(gc0308);
+ else
+ ret = gc0308_stop_stream(gc0308);
+
+ v4l2_subdev_unlock_state(sd_state);
+ return ret;
+}
+
+static int gc0308_g_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *fi)
+{
+ struct gc0308 *gc0308 = to_gc0308(sd);
+ struct v4l2_subdev_state *sd_state;
+
+ sd_state = v4l2_subdev_lock_and_get_active_state(sd);
+ fi->interval = gc0308->frame_interval;
+ v4l2_subdev_unlock_state(sd_state);
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops gc0308_video_ops = {
+ .g_frame_interval = gc0308_g_frame_interval,
+ .s_stream = gc0308_s_stream,
+};
+
+static const struct v4l2_subdev_ops gc0308_subdev_ops = {
+ .core = &gc0308_core_ops,
+ .pad = &gc0308_pad_ops,
+ .video = &gc0308_video_ops,
+};
+
+static int gc0308_bus_config(struct gc0308 *gc0308)
+{
+ struct device *dev = gc0308->dev;
+ struct v4l2_fwnode_endpoint bus_cfg = {
+ .bus_type = V4L2_MBUS_PARALLEL
+ };
+ struct fwnode_handle *ep;
+ int ret;
+
+ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
+ if (!ep)
+ return -EINVAL;
+
+ ret = v4l2_fwnode_endpoint_parse(ep, &bus_cfg);
+ fwnode_handle_put(ep);
+ if (ret)
+ return ret;
+
+ gc0308->mbus_config = bus_cfg.bus.parallel.flags;
+
+ return 0;
+}
+
+static const char * const gc0308_test_pattern_menu[] = {
+ "Disabled",
+ "Test Image 1",
+ "Test Image 2",
+};
+
+static int gc0308_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct gc0308 *gc0308;
+ unsigned long clkrate;
+ int ret, regval;
+
+ gc0308 = devm_kzalloc(dev, sizeof(*gc0308), GFP_KERNEL);
+ if (!gc0308)
+ return -ENOMEM;
+
+ gc0308->dev = dev;
+ dev_set_drvdata(dev, gc0308);
+
+ ret = gc0308_bus_config(gc0308);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get bus config\n");
+
+ gc0308->format.width = gc0308_frame_sizes[0].width;
+ gc0308->format.height = gc0308_frame_sizes[0].height;
+ gc0308->format.code = gc0308_formats[2].code;
+ gc0308->frame_interval.numerator = 1;
+ gc0308->frame_interval.denominator = GC0308_FRAME_RATE;
+
+ gc0308->clk = devm_clk_get_optional(dev, NULL);
+ if (IS_ERR(gc0308->clk))
+ return dev_err_probe(dev, PTR_ERR(gc0308->clk),
+ "could not get clk\n");
+
+ gc0308->vdd = devm_regulator_get(dev, "vdd28");
+ if (IS_ERR(gc0308->vdd))
+ return dev_err_probe(dev, PTR_ERR(gc0308->vdd),
+ "failed to get vdd28 regulator\n");
+
+ gc0308->pwdn_gpio = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_LOW);
+ if (IS_ERR(gc0308->pwdn_gpio))
+ return dev_err_probe(dev, PTR_ERR(gc0308->pwdn_gpio),
+ "failed to get powerdown gpio\n");
+
+ gc0308->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(gc0308->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(gc0308->reset_gpio),
+ "failed to get reset gpio\n");
+
+ gc0308->regmap = devm_regmap_init_i2c(client, &gc0308_regmap_config);
+ if (IS_ERR(gc0308->regmap))
+ return dev_err_probe(dev, PTR_ERR(gc0308->regmap),
+ "failed to init regmap\n");
+
+ v4l2_i2c_subdev_init(&gc0308->sd, client, &gc0308_subdev_ops);
+ gc0308->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ gc0308->sd.flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
+
+ v4l2_ctrl_handler_init(&gc0308->hdl, 8);
+ v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
+ V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1);
+ v4l2_ctrl_new_std_menu_items(&gc0308->hdl, &gc0308_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(gc0308_test_pattern_menu) - 1,
+ 0, 0, gc0308_test_pattern_menu);
+ v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
+ V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
+ 8, ~0x14e, V4L2_WHITE_BALANCE_AUTO);
+ v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
+ V4L2_CID_COLORFX, 8, 0, V4L2_COLORFX_NONE);
+ v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
+ V4L2_CID_POWER_LINE_FREQUENCY,
+ V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+ ~0x6, V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
+ v4l2_ctrl_new_int_menu(&gc0308->hdl, &gc0308_ctrl_ops,
+ V4L2_CID_AUTO_EXPOSURE_BIAS,
+ ARRAY_SIZE(gc0308_exposure_menu) - 1,
+ ARRAY_SIZE(gc0308_exposure_menu)/2,
+ gc0308_exposure_menu);
+
+ gc0308->sd.ctrl_handler = &gc0308->hdl;
+ if (gc0308->hdl.error) {
+ ret = gc0308->hdl.error;
+ return ret;
+ }
+
+ gc0308->sd.state_lock = gc0308->hdl.lock;
+ gc0308->pad.flags = MEDIA_PAD_FL_SOURCE;
+ gc0308->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+ ret = media_entity_pads_init(&gc0308->sd.entity, 1, &gc0308->pad);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_subdev_init_finalize(&gc0308->sd);
+ if (ret)
+ goto fail_media_entity_cleanup;
+
+ ret = gc0308_power_on(dev);
+ if (ret)
+ goto fail_subdev_cleanup;
+
+ if (gc0308->clk) {
+ clkrate = clk_get_rate(gc0308->clk);
+ if (clkrate != 24000000)
+ dev_warn(dev, "unexpected clock rate: %lu\n", clkrate);
+ }
+
+ ret = regmap_read(gc0308->regmap, GC0308_CHIP_ID, &regval);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "failed to read chip ID\n");
+ goto fail_power_off;
+ }
+
+ if (regval != 0x9b) {
+ ret = -EINVAL;
+ dev_err_probe(dev, ret, "invalid chip ID (%02x)\n", regval);
+ goto fail_power_off;
+ }
+
+ ret = v4l2_async_register_subdev(&gc0308->sd);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to register v4l subdev\n");
+ goto fail_power_off;
+ }
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_idle(dev);
+
+ return 0;
+
+fail_power_off:
+ gc0308_power_off(dev);
+fail_subdev_cleanup:
+ v4l2_subdev_cleanup(&gc0308->sd);
+fail_media_entity_cleanup:
+ media_entity_cleanup(&gc0308->sd.entity);
+ return ret;
+}
+
+static void gc0308_remove(struct i2c_client *client)
+{
+ struct gc0308 *gc0308 = i2c_get_clientdata(client);
+ struct device *dev = &client->dev;
+
+ pm_runtime_get_sync(dev);
+
+ v4l2_async_unregister_subdev(&gc0308->sd);
+ v4l2_ctrl_handler_free(&gc0308->hdl);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+ gc0308_power_off(dev);
+}
+
+static const struct dev_pm_ops gc0308_pm_ops = {
+ SET_RUNTIME_PM_OPS(gc0308_power_off, gc0308_power_on, NULL)
+};
+
+static const struct of_device_id gc0308_of_match[] = {
+ { .compatible = "galaxycore,gc0308" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, gc0308_of_match);
+
+static struct i2c_driver gc0308_i2c_driver = {
+ .driver = {
+ .name = "gc0308",
+ .pm = &gc0308_pm_ops,
+ .of_match_table = gc0308_of_match,
+ },
+ .probe = gc0308_probe,
+ .remove = gc0308_remove,
+};
+module_i2c_driver(gc0308_i2c_driver);
+
+MODULE_DESCRIPTION("GalaxyCore GC0308 Camera Driver");
+MODULE_AUTHOR("Sebastian Reichel <[email protected]>");
+MODULE_LICENSE("GPL");
--
2.42.0

2023-10-24 01:05:09

by Sebastian Reichel

[permalink] [raw]
Subject: [PATCH v2 3/4] media: MAINTAINERS: Add GalaxyCore in camera sensor section

"gc" prefixed i2c media drivers are most likely GalaxyCore camera
sensor drivers, so add it to the list.

Signed-off-by: Sebastian Reichel <[email protected]>
---
MAINTAINERS | 1 +
1 file changed, 1 insertion(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 2894f0777537..5f64cd42ff37 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22553,6 +22553,7 @@ S: Maintained
F: Documentation/driver-api/media/camera-sensor.rst
F: Documentation/driver-api/media/tx-rx.rst
F: drivers/media/i2c/ar*
+F: drivers/media/i2c/gc*
F: drivers/media/i2c/hi*
F: drivers/media/i2c/imx*
F: drivers/media/i2c/mt*
--
2.42.0

2023-10-24 08:40:26

by Jacopo Mondi

[permalink] [raw]
Subject: Re: [PATCH v2 4/4] media: i2c: gc0308: new driver

Hi Sebastian

On Tue, Oct 24, 2023 at 02:59:53AM +0200, Sebastian Reichel wrote:
> Introduce new driver for GalaxyCore GC0308, which is a cheap
> 640x480 with an on-chip ISP sensor sold since 2010. Data is
> provided via parallel bus.
>
> Signed-off-by: Sebastian Reichel <[email protected]>
> ---
> MAINTAINERS | 7 +
> drivers/media/i2c/Kconfig | 14 +
> drivers/media/i2c/Makefile | 1 +
> drivers/media/i2c/gc0308.c | 1418 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 1440 insertions(+)
> create mode 100644 drivers/media/i2c/gc0308.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5f64cd42ff37..f03fd34067d0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8627,6 +8627,13 @@ F: drivers/mfd/gateworks-gsc.c
> F: include/linux/mfd/gsc.h
> F: include/linux/platform_data/gsc_hwmon.h
>
> +GC0308 GALAXYCORE CAMERA SENSOR DRIVER
> +M: Sebastian Reichel <[email protected]>
> +L: [email protected]
> +S: Maintained
> +F: Documentation/devicetree/bindings/media/i2c/galaxycore,gc0308.yaml
> +F: drivers/media/i2c/gc0308.c
> +
> GCC PLUGINS
> M: Kees Cook <[email protected]>
> L: [email protected]
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 74ff833ff48c..7db4abc0ef88 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -50,6 +50,20 @@ config VIDEO_AR0521
> To compile this driver as a module, choose M here: the
> module will be called ar0521.
>
> +config VIDEO_GC0308
> + tristate "GalaxyCore GC0308 sensor support"
> + depends on I2C && VIDEO_DEV
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE

The top-level VIDEO_CAMERA_SENSOR menu already

depends on MEDIA_CAMERA_SUPPORT && I2C
select MEDIA_CONTROLLER
select V4L2_FWNODE
select VIDEO_V4L2_SUBDEV_API

So you can omit them

> + select REGMAP_I2C

Annoying questions, is this driver compatible with the newly
introduced CCI helpers ? Not that pressing as you're on regmap so you
haven't your own read/write routines.

> + help
> + This is a Video4Linux2 sensor driver for the GalaxyCore
> + GC0308 camera.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called gc0308.
> +
> config VIDEO_HI556
> tristate "Hynix Hi-556 sensor support"
> help
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 80b00d39b48f..f03a21f2962a 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_GC0308) += gc0308.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/gc0308.c b/drivers/media/i2c/gc0308.c
> new file mode 100644
> index 000000000000..f6e02284b661
> --- /dev/null
> +++ b/drivers/media/i2c/gc0308.c
> @@ -0,0 +1,1418 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for the GalaxyCore GC0308 camera sensor.
> + *
> + * Copyright (c) 2023 Sebastian Reichel <[email protected]>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* Analog & CISCTL*/
> +#define GC0308_CHIP_ID 0x000
> +#define GC0308_HBLANK 0x001
> +#define GC0308_VBLANK 0x002
> +#define GC0308_EXPH 0x003
> +#define GC0308_EXPL 0x004
> +#define GC0308_ROW_STARTH 0x005
> +#define GC0308_ROW_STARTL 0x006
> +#define GC0308_COL_STARTH 0x007
> +#define GC0308_COL_STARTL 0x008
> +#define GC0308_WIN_HEIGHTH 0x009
> +#define GC0308_WIN_HEIGHTL 0x00a
> +#define GC0308_WIN_WIDTHH 0x00b
> +#define GC0308_WIN_WIDTHL 0x00c
> +#define GC0308_VS_ST 0x00d
> +#define GC0308_VS_ET 0x00e
> +#define GC0308_VB_HB 0x00f
> +#define GC0308_RSH_WIDTH 0x010
> +#define GC0308_TSP_WIDTH 0x011
> +#define GC0308_SH_DELAY 0x012
> +#define GC0308_ROW_TAIL_WIDTH 0x013
> +#define GC0308_CISCTL_MODE1 0x014
> +#define GC0308_CISCTL_MODE2 0x015
> +#define GC0308_CISCTL_MODE3 0x016
> +#define GC0308_CISCTL_MODE4 0x017
> +#define GC0308_ANALOG_MODE1 0x01a
> +#define GC0308_ANALOG_MODE2 0x01b
> +#define GC0308_HRST_RSG_V18 0x01c
> +#define GC0308_VREF_V25 0x01d
> +#define GC0308_ADC_R 0x01e
> +#define GC0308_PAD_DRV 0x01f
> +#define GC0308_SOFT_RESET 0x0fe
> +
> +/* ISP */
> +#define GC0308_BLOCK_EN1 0x020
> +#define GC0308_BLOCK_EN2 0x021
> +#define GC0308_AAAA_EN 0x022
> +#define GC0308_SPECIAL_EFFECT 0x023
> +#define GC0308_OUT_FORMAT 0x024
> +#define GC0308_OUT_EN 0x025
> +#define GC0308_SYNC_MODE 0x026
> +#define GC0308_CLK_DIV_MODE 0x028
> +#define GC0308_BYPASS_MODE 0x029
> +#define GC0308_CLK_GATING 0x02a
> +#define GC0308_DITHER_MODE 0x02b
> +#define GC0308_DITHER_BIT 0x02c
> +#define GC0308_DEBUG_MODE1 0x02d
> +#define GC0308_DEBUG_MODE2 0x02e
> +#define GC0308_DEBUG_MODE3 0x02f
> +#define GC0308_CROP_WIN_MODE 0x046
> +#define GC0308_CROP_WIN_Y1 0x047
> +#define GC0308_CROP_WIN_X1 0x048
> +#define GC0308_CROP_WIN_HEIGHTH 0x049
> +#define GC0308_CROP_WIN_HEIGHTL 0x04a
> +#define GC0308_CROP_WIN_WIDTHH 0x04b
> +#define GC0308_CROP_WIN_WIDTHL 0x04c
> +
> +/* BLK */
> +#define GC0308_BLK_MODE 0x030
> +#define GC0308_BLK_LIMIT_VAL 0x031
> +#define GC0308_GLOBAL_OFF 0x032
> +#define GC0308_CURRENT_R_OFF 0x033
> +#define GC0308_CURRENT_G_OFF 0x034
> +#define GC0308_CURRENT_B_OFF 0x035
> +#define GC0308_CURRENT_R_DARK_CURRENT 0x036
> +#define GC0308_CURRENT_G_DARK_CURRENT 0x037
> +#define GC0308_CURRENT_B_DARK_CURRENT 0x038
> +#define GC0308_EXP_RATE_DARKC 0x039
> +#define GC0308_OFF_SUBMODE 0x03a
> +#define GC0308_DARKC_SUBMODE 0x03b
> +#define GC0308_MANUAL_G1_OFF 0x03c
> +#define GC0308_MANUAL_R1_OFF 0x03d
> +#define GC0308_MANUAL_B2_OFF 0x03e
> +#define GC0308_MANUAL_G2_OFF 0x03f
> +
> +/* PREGAIN */
> +#define GC0308_GLOBAL_GAIN 0x050
> +#define GC0308_AUTO_PREGAIN 0x051
> +#define GC0308_AUTO_POSTGAIN 0x052
> +#define GC0308_CHANNEL_GAIN_G1 0x053
> +#define GC0308_CHANNEL_GAIN_R 0x054
> +#define GC0308_CHANNEL_GAIN_B 0x055
> +#define GC0308_CHANNEL_GAIN_G2 0x056
> +#define GC0308_R_RATIO 0x057
> +#define GC0308_G_RATIO 0x058
> +#define GC0308_B_RATIO 0x059
> +#define GC0308_AWB_R_GAIN 0x05a
> +#define GC0308_AWB_G_GAIN 0x05b
> +#define GC0308_AWB_B_GAIN 0x05c
> +#define GC0308_LSC_DEC_LVL1 0x05d
> +#define GC0308_LSC_DEC_LVL2 0x05e
> +#define GC0308_LSC_DEC_LVL3 0x05f
> +
> +/* DNDD */
> +#define GC0308_DN_MODE_EN 0x060
> +#define GC0308_DN_MODE_RATIO 0x061
> +#define GC0308_DN_BILAT_B_BASE 0x062
> +#define GC0308_DN_B_INCR 0x063
> +#define GC0308_DN_BILAT_N_BASE 0x064
> +#define GC0308_DN_N_INCR 0x065
> +#define GC0308_DD_DARK_BRIGHT_TH 0x066
> +#define GC0308_DD_FLAT_TH 0x067
> +#define GC0308_DD_LIMIT 0x068
> +
> +/* ASDE - Auto Saturation De-noise and Edge-Enhancement */
> +#define GC0308_ASDE_GAIN_TRESH 0x069
> +#define GC0308_ASDE_GAIN_MODE 0x06a
> +#define GC0308_ASDE_DN_SLOPE 0x06b
> +#define GC0308_ASDE_DD_BRIGHT 0x06c
> +#define GC0308_ASDE_DD_LIMIT 0x06d
> +#define GC0308_ASDE_AUTO_EE1 0x06e
> +#define GC0308_ASDE_AUTO_EE2 0x06f
> +#define GC0308_ASDE_AUTO_SAT_DEC_SLOPE 0x070
> +#define GC0308_ASDE_AUTO_SAT_LOW_LIMIT 0x071
> +
> +/* INTPEE - Interpolation and Edge-Enhancement */
> +#define GC0308_EEINTP_MODE_1 0x072
> +#define GC0308_EEINTP_MODE_2 0x073
> +#define GC0308_DIRECTION_TH1 0x074
> +#define GC0308_DIRECTION_TH2 0x075
> +#define GC0308_DIFF_HV_TI_TH 0x076
> +#define GC0308_EDGE12_EFFECT 0x077
> +#define GC0308_EDGE_POS_RATIO 0x078
> +#define GC0308_EDGE1_MINMAX 0x079
> +#define GC0308_EDGE2_MINMAX 0x07a
> +#define GC0308_EDGE12_TH 0x07b
> +#define GC0308_EDGE_MAX 0x07c
> +
> +/* ABB - Auto Black Balance */
> +#define GC0308_ABB_MODE 0x080
> +#define GC0308_ABB_TARGET_AVGH 0x081
> +#define GC0308_ABB_TARGET_AVGL 0x082
> +#define GC0308_ABB_LIMIT_VAL 0x083
> +#define GC0308_ABB_SPEED 0x084
> +#define GC0308_CURR_R_BLACK_LVL 0x085
> +#define GC0308_CURR_G_BLACK_LVL 0x086
> +#define GC0308_CURR_B_BLACK_LVL 0x087
> +#define GC0308_CURR_R_BLACK_FACTOR 0x088
> +#define GC0308_CURR_G_BLACK_FACTOR 0x089
> +#define GC0308_CURR_B_BLACK_FACTOR 0x08a
> +
> +/* LSC - Lens Shading Correction */
> +#define GC0308_LSC_RED_B2 0x08b
> +#define GC0308_LSC_GREEN_B2 0x08c
> +#define GC0308_LSC_BLUE_B2 0x08d
> +#define GC0308_LSC_RED_B4 0x08e
> +#define GC0308_LSC_GREEN_B4 0x08f
> +#define GC0308_LSC_BLUE_B4 0x090
> +#define GC0308_LSC_ROW_CENTER 0x091
> +#define GC0308_LSC_COL_CENTER 0x092
> +
> +/* CC - Channel Coefficient */
> +#define GC0308_CC_MATRIX_C11 0x093
> +#define GC0308_CC_MATRIX_C12 0x094
> +#define GC0308_CC_MATRIX_C13 0x095
> +#define GC0308_CC_MATRIX_C21 0x096
> +#define GC0308_CC_MATRIX_C22 0x097
> +#define GC0308_CC_MATRIX_C23 0x098
> +#define GC0308_CC_MATRIX_C41 0x09c
> +#define GC0308_CC_MATRIX_C42 0x09d
> +#define GC0308_CC_MATRIX_C43 0x09e
> +
> +/* GAMMA */
> +#define GC0308_GAMMA_OUT0 0x09f
> +#define GC0308_GAMMA_OUT1 0x0a0
> +#define GC0308_GAMMA_OUT2 0x0a1
> +#define GC0308_GAMMA_OUT3 0x0a2
> +#define GC0308_GAMMA_OUT4 0x0a3
> +#define GC0308_GAMMA_OUT5 0x0a4
> +#define GC0308_GAMMA_OUT6 0x0a5
> +#define GC0308_GAMMA_OUT7 0x0a6
> +#define GC0308_GAMMA_OUT8 0x0a7
> +#define GC0308_GAMMA_OUT9 0x0a8
> +#define GC0308_GAMMA_OUT10 0x0a9
> +#define GC0308_GAMMA_OUT11 0x0aa
> +#define GC0308_GAMMA_OUT12 0x0ab
> +#define GC0308_GAMMA_OUT13 0x0ac
> +#define GC0308_GAMMA_OUT14 0x0ad
> +#define GC0308_GAMMA_OUT15 0x0ae
> +#define GC0308_GAMMA_OUT16 0x0af
> +
> +/* YCP */
> +#define GC0308_GLOBAL_SATURATION 0x0b0
> +#define GC0308_SATURATION_CB 0x0b1
> +#define GC0308_SATURATION_CR 0x0b2
> +#define GC0308_LUMA_CONTRAST 0x0b3
> +#define GC0308_CONTRAST_CENTER 0x0b4
> +#define GC0308_LUMA_OFFSET 0x0b5
> +#define GC0308_SKIN_CB_CENTER 0x0b6
> +#define GC0308_SKIN_CR_CENTER 0x0b7
> +#define GC0308_SKIN_RADIUS_SQUARE 0x0b8
> +#define GC0308_SKIN_BRIGHTNESS 0x0b9
> +#define GC0308_FIXED_CB 0x0ba
> +#define GC0308_FIXED_CR 0x0bb
> +#define GC0308_EDGE_DEC_SA 0x0bd
> +#define GC0308_AUTO_GRAY_MODE 0x0be
> +#define GC0308_SATURATION_SUB_STRENGTH 0x0bf
> +#define GC0308_Y_GAMMA_OUT0 0x0c0
> +#define GC0308_Y_GAMMA_OUT1 0x0c1
> +#define GC0308_Y_GAMMA_OUT2 0x0c2
> +#define GC0308_Y_GAMMA_OUT3 0x0c3
> +#define GC0308_Y_GAMMA_OUT4 0x0c4
> +#define GC0308_Y_GAMMA_OUT5 0x0c5
> +#define GC0308_Y_GAMMA_OUT6 0x0c6
> +#define GC0308_Y_GAMMA_OUT7 0x0c7
> +#define GC0308_Y_GAMMA_OUT8 0x0c8
> +#define GC0308_Y_GAMMA_OUT9 0x0c9
> +#define GC0308_Y_GAMMA_OUT10 0x0ca
> +#define GC0308_Y_GAMMA_OUT11 0x0cb
> +#define GC0308_Y_GAMMA_OUT12 0x0cc
> +
> +/* AEC - Automatic Exposure Control */
> +#define GC0308_AEC_MODE1 0x0d0
> +#define GC0308_AEC_MODE2 0x0d1
> +#define GC0308_AEC_MODE3 0x0d2
> +#define GC0308_AEC_TARGET_Y 0x0d3
> +#define GC0308_Y_AVG 0x0d4
> +#define GC0308_AEC_HIGH_LOW_RANGE 0x0d5
> +#define GC0308_AEC_IGNORE 0x0d6
> +#define GC0308_AEC_LIMIT_HIGH_RANGE 0x0d7
> +#define GC0308_AEC_R_OFFSET 0x0d9
> +#define GC0308_AEC_GB_OFFSET 0x0da
> +#define GC0308_AEC_SLOW_MARGIN 0x0db
> +#define GC0308_AEC_FAST_MARGIN 0x0dc
> +#define GC0308_AEC_EXP_CHANGE_GAIN 0x0dd
> +#define GC0308_AEC_STEP2_SUNLIGHT 0x0de
> +#define GC0308_AEC_I_FRAMES 0x0df
> +#define GC0308_AEC_I_STOP_L_MARGIN 0x0e0
> +#define GC0308_AEC_I_STOP_MARGIN 0x0e1
> +#define GC0308_ANTI_FLICKER_STEP_H 0x0e2
> +#define GC0308_ANTI_FLICKER_STEP_L 0x0e3
> +#define GC0308_EXP_LVL_1H 0x0e4
> +#define GC0308_EXP_LVL_1L 0x0e5
> +#define GC0308_EXP_LVL_2H 0x0e6
> +#define GC0308_EXP_LVL_2L 0x0e7
> +#define GC0308_EXP_LVL_3H 0x0e8
> +#define GC0308_EXP_LVL_3L 0x0e9
> +#define GC0308_EXP_LVL_4H 0x0ea
> +#define GC0308_EXP_LVL_4L 0x0eb
> +#define GC0308_MAX_EXP_LVL 0x0ec
> +#define GC0308_EXP_MIN_L 0x0ed
> +#define GC0308_MAX_POST_DF_GAIN 0x0ee
> +#define GC0308_MAX_PRE_DG_GAIN 0x0ef
> +
> +/* ABS */
> +#define GC0308_ABS_RANGE_COMP 0x0f0
> +#define GC0308_ABS_STOP_MARGIN 0x0f1
> +#define GC0308_Y_S_COMP 0x0f2
> +#define GC0308_Y_STRETCH_LIMIT 0x0f3
> +#define GC0308_Y_TILT 0x0f4
> +#define GC0308_Y_STRETCH 0x0f5
> +
> +/* Measure Window */
> +#define GC0308_BIG_WIN_X0 0x0f7
> +#define GC0308_BIG_WIN_Y0 0x0f8
> +#define GC0308_BIG_WIN_X1 0x0f9
> +#define GC0308_BIG_WIN_Y1 0x0fa
> +#define GC0308_DIFF_Y_BIG_THD 0x0fb
> +
> +/* OUT Module (P1) */
> +#define GC0308_CLOSE_FRAME_EN 0x150
> +#define GC0308_CLOSE_FRAME_NUM1 0x151
> +#define GC0308_CLOSE_FRAME_NUM2 0x152
> +#define GC0308_BAYER_MODE 0x153
> +#define GC0308_SUBSAMPLE 0x154
> +#define GC0308_SUBMODE 0x155
> +#define GC0308_SUB_ROW_N1 0x156
> +#define GC0308_SUB_ROW_N2 0x157
> +#define GC0308_SUB_COL_N1 0x158
> +#define GC0308_SUB_COL_N2 0x159
> +
> +/* AWB (P1) - Auto White Balance */
> +#define GC0308_AWB_RGB_HIGH_LOW 0x100
> +#define GC0308_AWB_Y_TO_C_DIFF2 0x102
> +#define GC0308_AWB_C_MAX 0x104
> +#define GC0308_AWB_C_INTER 0x105
> +#define GC0308_AWB_C_INTER2 0x106
> +#define GC0308_AWB_C_MAX_BIG 0x108
> +#define GC0308_AWB_Y_HIGH 0x109
> +#define GC0308_AWB_NUMBER_LIMIT 0x10a
> +#define GC0308_KWIN_RATIO 0x10b
> +#define GC0308_KWIN_THD 0x10c
> +#define GC0308_LIGHT_GAIN_RANGE 0x10d
> +#define GC0308_SMALL_WIN_WIDTH_STEP 0x10e
> +#define GC0308_SMALL_WIN_HEIGHT_STEP 0x10f
> +#define GC0308_AWB_YELLOW_TH 0x110
> +#define GC0308_AWB_MODE 0x111
> +#define GC0308_AWB_ADJUST_SPEED 0x112
> +#define GC0308_AWB_EVERY_N 0x113
> +#define GC0308_R_AVG_USE 0x1d0
> +#define GC0308_G_AVG_USE 0x1d1
> +#define GC0308_B_AVG_USE 0x1d2
> +
> +#define GC0308_FRAME_RATE 29
> +
> +enum gc0308_exp_val {
> + GC0308_EXP_M4 = 0,
> + GC0308_EXP_M3,
> + GC0308_EXP_M2,
> + GC0308_EXP_M1,
> + GC0308_EXP_0,
> + GC0308_EXP_P1,
> + GC0308_EXP_P2,
> + GC0308_EXP_P3,
> + GC0308_EXP_P4,
> +};
> +
> +static const s64 gc0308_exposure_menu[] = {
> + -4, -3, -2, -1, 0, 1, 2, 3, 4
> +};
> +
> +struct gc0308_exposure {
> + u8 luma_offset;
> + u8 aec_target_y;
> +};
> +
> +#define GC0308_EXPOSURE(luma_offset_reg, aec_target_y_reg) \
> + { .luma_offset = luma_offset_reg, .aec_target_y = aec_target_y_reg }
> +
> +static const struct gc0308_exposure gc0308_exposure_values[] = {
> + [GC0308_EXP_M4] = GC0308_EXPOSURE(0xc0, 0x30),
> + [GC0308_EXP_M3] = GC0308_EXPOSURE(0xd0, 0x38),
> + [GC0308_EXP_M2] = GC0308_EXPOSURE(0xe0, 0x40),
> + [GC0308_EXP_M1] = GC0308_EXPOSURE(0xf0, 0x48),
> + [GC0308_EXP_0] = GC0308_EXPOSURE(0x08, 0x50),
> + [GC0308_EXP_P1] = GC0308_EXPOSURE(0x10, 0x5c),
> + [GC0308_EXP_P2] = GC0308_EXPOSURE(0x20, 0x60),
> + [GC0308_EXP_P3] = GC0308_EXPOSURE(0x30, 0x68),
> + [GC0308_EXP_P4] = GC0308_EXPOSURE(0x40, 0x70),
> +};
> +
> +struct gc0308_awb_gains {
> + u8 r;
> + u8 g;
> + u8 b;
> +};
> +
> +#define GC0308_AWB_GAINS(red, green, blue) \
> + { .r = red, .g = green, .b = blue }
> +
> +static const struct gc0308_awb_gains gc0308_awb_gains[] = {
> + [V4L2_WHITE_BALANCE_AUTO] = GC0308_AWB_GAINS(0x56, 0x40, 0x4a),
> + [V4L2_WHITE_BALANCE_CLOUDY] = GC0308_AWB_GAINS(0x8c, 0x50, 0x40),
> + [V4L2_WHITE_BALANCE_DAYLIGHT] = GC0308_AWB_GAINS(0x74, 0x52, 0x40),
> + [V4L2_WHITE_BALANCE_INCANDESCENT] = GC0308_AWB_GAINS(0x48, 0x40, 0x5c),
> + [V4L2_WHITE_BALANCE_FLUORESCENT] = GC0308_AWB_GAINS(0x40, 0x42, 0x50),
> +};
> +
> +struct gc0308_format {
> + u32 code;
> + u8 regval;
> +};
> +
> +#define GC0308_FORMAT(v4l2_code, gc0308_regval) \
> + { .code = v4l2_code, .regval = gc0308_regval }
> +
> +static const struct gc0308_format gc0308_formats[] = {
> + GC0308_FORMAT(MEDIA_BUS_FMT_UYVY8_2X8, 0x00),
> + GC0308_FORMAT(MEDIA_BUS_FMT_VYUY8_2X8, 0x01),
> + GC0308_FORMAT(MEDIA_BUS_FMT_YUYV8_2X8, 0x02),
> + GC0308_FORMAT(MEDIA_BUS_FMT_YVYU8_2X8, 0x03),
> + GC0308_FORMAT(MEDIA_BUS_FMT_RGB565_2X8_BE, 0x06),
> + GC0308_FORMAT(MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, 0x07),
> + GC0308_FORMAT(MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, 0x09),
> +};
> +
> +struct gc0308_frame_size {
> + u8 subsample;
> + u32 width;
> + u32 height;
> +};
> +
> +#define GC0308_FRAME_SIZE(s, w, h) \
> + { .subsample = s, .width = w, .height = h }
> +
> +static const struct gc0308_frame_size gc0308_frame_sizes[] = {
> + GC0308_FRAME_SIZE(0x11, 640, 480),
> + GC0308_FRAME_SIZE(0x22, 320, 240),
> + GC0308_FRAME_SIZE(0x44, 160, 120),
> +};
> +
> +struct gc0308 {
> + struct v4l2_subdev sd;
> + struct v4l2_ctrl_handler hdl;
> + struct media_pad pad;
> + struct v4l2_mbus_framefmt format;

You're using subdev active state in this driver (\o/), so you don't need to
store this information in the driver structure. You always get access
to a state, either as a pad operation argument or by getting and
locking it directly at s_stream time. You can access the format from
the state at any time with v4l2_subdev_get_pad_format().

Also, you need the init_cfg operation to be implemented if you're
using the subdev state.

> + struct device *dev;
> + struct clk *clk;
> + struct regmap *regmap;
> + struct regulator *vdd;
> + struct gpio_desc *pwdn_gpio;
> + struct gpio_desc *reset_gpio;
> + unsigned int mbus_config;
> + struct v4l2_fract frame_interval;
> +};
> +
> +static inline struct gc0308 *to_gc0308(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct gc0308, sd);
> +}
> +
> +static const struct regmap_range_cfg gc0308_ranges[] = {
> + {
> + .range_min = 0x0000,
> + .range_max = 0x01ff,
> + .selector_reg = 0xfe,
> + .selector_mask = 0x01,
> + .selector_shift = 0x00,
> + .window_start = 0x00,
> + .window_len = 0x100,
> + },
> +};
> +
> +static const struct regmap_config gc0308_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = 0x1ff,
> + .ranges = gc0308_ranges,
> + .num_ranges = ARRAY_SIZE(gc0308_ranges),
> + .cache_type = REGCACHE_RBTREE,
> +};
> +
> +static const struct reg_sequence sensor_default_regs[] = {
> + REG_SEQ0(GC0308_VB_HB, 0x00),
> + REG_SEQ0(GC0308_HBLANK, 0x40),
> + REG_SEQ0(GC0308_VBLANK, 0x20),
> + REG_SEQ0(GC0308_EXPH, 0x02),
> + REG_SEQ0(GC0308_EXPL, 0x58),
> + REG_SEQ0(GC0308_AWB_R_GAIN, 0x56),
> + REG_SEQ0(GC0308_AWB_G_GAIN, 0x40),
> + REG_SEQ0(GC0308_AWB_B_GAIN, 0x4a),
> + REG_SEQ0(GC0308_ANTI_FLICKER_STEP_H, 0x00),
> + REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x78),
> + REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_2H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_2L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_3H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_3L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_4H, 0x0e),
> + REG_SEQ0(GC0308_EXP_LVL_4L, 0xa6),
> + REG_SEQ0(GC0308_MAX_EXP_LVL, 0x20),
> + REG_SEQ0(GC0308_ROW_STARTH, 0x00),
> + REG_SEQ0(GC0308_ROW_STARTL, 0x00),
> + REG_SEQ0(GC0308_COL_STARTH, 0x00),
> + REG_SEQ0(GC0308_COL_STARTL, 0x00),
> + REG_SEQ0(GC0308_WIN_HEIGHTH, 0x01), /* 0x1e8 = 488 => VT=480 */
> + REG_SEQ0(GC0308_WIN_HEIGHTL, 0xe8),
> + REG_SEQ0(GC0308_WIN_WIDTHH, 0x02), /* 0x288 = 648 => VH=640 */
> + REG_SEQ0(GC0308_WIN_WIDTHL, 0x88),
> + REG_SEQ0(GC0308_VS_ST, 0x02), /* Start Time (in rows) */
> + REG_SEQ0(GC0308_VS_ET, 0x02), /* End Time (in rows) */
> + REG_SEQ0(GC0308_RSH_WIDTH, 0x22),
> + REG_SEQ0(GC0308_TSP_WIDTH, 0x0d),
> + REG_SEQ0(GC0308_SH_DELAY, 0x50), /* sample hold delay post row finish */
> + REG_SEQ0(GC0308_ROW_TAIL_WIDTH, 0x0f),
> + REG_SEQ0(GC0308_CISCTL_MODE1, 0x10),
> + REG_SEQ0(GC0308_CISCTL_MODE2, 0x0a),
> + REG_SEQ0(GC0308_CISCTL_MODE3, 0x05),
> + REG_SEQ0(GC0308_CISCTL_MODE4, 0x01),
> + REG_SEQ0(0x018, 0x44), /* undocumented */
> + REG_SEQ0(0x019, 0x44), /* undocumented */
> + REG_SEQ0(GC0308_ANALOG_MODE1, 0x2a),
> + REG_SEQ0(GC0308_ANALOG_MODE2, 0x00),
> + REG_SEQ0(GC0308_HRST_RSG_V18, 0x49),
> + REG_SEQ0(GC0308_VREF_V25, 0x9a),
> + REG_SEQ0(GC0308_ADC_R, 0x61),
> + REG_SEQ0(GC0308_PAD_DRV, 0x01), /* drv strength: pclk=4mA */
> + REG_SEQ0(GC0308_BLOCK_EN1, 0x7f),
> + REG_SEQ0(GC0308_BLOCK_EN2, 0xfa),
> + REG_SEQ0(GC0308_AAAA_EN, 0x57),
> + REG_SEQ0(GC0308_OUT_FORMAT, 0xa2), /* YCbYCr */
> + REG_SEQ0(GC0308_OUT_EN, 0x0f),
> + REG_SEQ0(GC0308_SYNC_MODE, 0x03),
> + REG_SEQ0(GC0308_CLK_DIV_MODE, 0x00),
> + REG_SEQ0(GC0308_DEBUG_MODE1, 0x0a),
> + REG_SEQ0(GC0308_DEBUG_MODE2, 0x00),
> + REG_SEQ0(GC0308_DEBUG_MODE3, 0x01),
> + REG_SEQ0(GC0308_BLK_MODE, 0xf7),
> + REG_SEQ0(GC0308_BLK_LIMIT_VAL, 0x50),
> + REG_SEQ0(GC0308_GLOBAL_OFF, 0x00),
> + REG_SEQ0(GC0308_CURRENT_R_OFF, 0x28),
> + REG_SEQ0(GC0308_CURRENT_G_OFF, 0x2a),
> + REG_SEQ0(GC0308_CURRENT_B_OFF, 0x28),
> + REG_SEQ0(GC0308_EXP_RATE_DARKC, 0x04),
> + REG_SEQ0(GC0308_OFF_SUBMODE, 0x20),
> + REG_SEQ0(GC0308_DARKC_SUBMODE, 0x20),
> + REG_SEQ0(GC0308_MANUAL_G1_OFF, 0x00),
> + REG_SEQ0(GC0308_MANUAL_R1_OFF, 0x00),
> + REG_SEQ0(GC0308_MANUAL_B2_OFF, 0x00),
> + REG_SEQ0(GC0308_MANUAL_G2_OFF, 0x00),
> + REG_SEQ0(GC0308_GLOBAL_GAIN, 0x14),
> + REG_SEQ0(GC0308_AUTO_POSTGAIN, 0x41),
> + REG_SEQ0(GC0308_CHANNEL_GAIN_G1, 0x80),
> + REG_SEQ0(GC0308_CHANNEL_GAIN_R, 0x80),
> + REG_SEQ0(GC0308_CHANNEL_GAIN_B, 0x80),
> + REG_SEQ0(GC0308_CHANNEL_GAIN_G2, 0x80),
> + REG_SEQ0(GC0308_LSC_RED_B2, 0x20),
> + REG_SEQ0(GC0308_LSC_GREEN_B2, 0x20),
> + REG_SEQ0(GC0308_LSC_BLUE_B2, 0x20),
> + REG_SEQ0(GC0308_LSC_RED_B4, 0x14),
> + REG_SEQ0(GC0308_LSC_GREEN_B4, 0x10),
> + REG_SEQ0(GC0308_LSC_BLUE_B4, 0x14),
> + REG_SEQ0(GC0308_LSC_ROW_CENTER, 0x3c),
> + REG_SEQ0(GC0308_LSC_COL_CENTER, 0x50),
> + REG_SEQ0(GC0308_LSC_DEC_LVL1, 0x12),
> + REG_SEQ0(GC0308_LSC_DEC_LVL2, 0x1a),
> + REG_SEQ0(GC0308_LSC_DEC_LVL3, 0x24),
> + REG_SEQ0(GC0308_DN_MODE_EN, 0x07),
> + REG_SEQ0(GC0308_DN_MODE_RATIO, 0x15),
> + REG_SEQ0(GC0308_DN_BILAT_B_BASE, 0x08),
> + REG_SEQ0(GC0308_DN_BILAT_N_BASE, 0x03),
> + REG_SEQ0(GC0308_DD_DARK_BRIGHT_TH, 0xe8),
> + REG_SEQ0(GC0308_DD_FLAT_TH, 0x86),
> + REG_SEQ0(GC0308_DD_LIMIT, 0x82),
> + REG_SEQ0(GC0308_ASDE_GAIN_TRESH, 0x18),
> + REG_SEQ0(GC0308_ASDE_GAIN_MODE, 0x0f),
> + REG_SEQ0(GC0308_ASDE_DN_SLOPE, 0x00),
> + REG_SEQ0(GC0308_ASDE_DD_BRIGHT, 0x5f),
> + REG_SEQ0(GC0308_ASDE_DD_LIMIT, 0x8f),
> + REG_SEQ0(GC0308_ASDE_AUTO_EE1, 0x55),
> + REG_SEQ0(GC0308_ASDE_AUTO_EE2, 0x38),
> + REG_SEQ0(GC0308_ASDE_AUTO_SAT_DEC_SLOPE, 0x15),
> + REG_SEQ0(GC0308_ASDE_AUTO_SAT_LOW_LIMIT, 0x33),
> + REG_SEQ0(GC0308_EEINTP_MODE_1, 0xdc),
> + REG_SEQ0(GC0308_EEINTP_MODE_2, 0x00),
> + REG_SEQ0(GC0308_DIRECTION_TH1, 0x02),
> + REG_SEQ0(GC0308_DIRECTION_TH2, 0x3f),
> + REG_SEQ0(GC0308_DIFF_HV_TI_TH, 0x02),
> + REG_SEQ0(GC0308_EDGE12_EFFECT, 0x38),
> + REG_SEQ0(GC0308_EDGE_POS_RATIO, 0x88),
> + REG_SEQ0(GC0308_EDGE1_MINMAX, 0x81),
> + REG_SEQ0(GC0308_EDGE2_MINMAX, 0x81),
> + REG_SEQ0(GC0308_EDGE12_TH, 0x22),
> + REG_SEQ0(GC0308_EDGE_MAX, 0xff),
> + REG_SEQ0(GC0308_CC_MATRIX_C11, 0x48),
> + REG_SEQ0(GC0308_CC_MATRIX_C12, 0x02),
> + REG_SEQ0(GC0308_CC_MATRIX_C13, 0x07),
> + REG_SEQ0(GC0308_CC_MATRIX_C21, 0xe0),
> + REG_SEQ0(GC0308_CC_MATRIX_C22, 0x40),
> + REG_SEQ0(GC0308_CC_MATRIX_C23, 0xf0),
> + REG_SEQ0(GC0308_SATURATION_CB, 0x40),
> + REG_SEQ0(GC0308_SATURATION_CR, 0x40),
> + REG_SEQ0(GC0308_LUMA_CONTRAST, 0x40),
> + REG_SEQ0(GC0308_SKIN_CB_CENTER, 0xe0),
> + REG_SEQ0(GC0308_EDGE_DEC_SA, 0x38),
> + REG_SEQ0(GC0308_AUTO_GRAY_MODE, 0x36),
> + REG_SEQ0(GC0308_AEC_MODE1, 0xcb),
> + REG_SEQ0(GC0308_AEC_MODE2, 0x10),
> + REG_SEQ0(GC0308_AEC_MODE3, 0x90),
> + REG_SEQ0(GC0308_AEC_TARGET_Y, 0x48),
> + REG_SEQ0(GC0308_AEC_HIGH_LOW_RANGE, 0xf2),
> + REG_SEQ0(GC0308_AEC_IGNORE, 0x16),
> + REG_SEQ0(GC0308_AEC_SLOW_MARGIN, 0x92),
> + REG_SEQ0(GC0308_AEC_FAST_MARGIN, 0xa5),
> + REG_SEQ0(GC0308_AEC_I_FRAMES, 0x23),
> + REG_SEQ0(GC0308_AEC_R_OFFSET, 0x00),
> + REG_SEQ0(GC0308_AEC_GB_OFFSET, 0x00),
> + REG_SEQ0(GC0308_AEC_I_STOP_L_MARGIN, 0x09),
> + REG_SEQ0(GC0308_EXP_MIN_L, 0x04),
> + REG_SEQ0(GC0308_MAX_POST_DF_GAIN, 0xa0),
> + REG_SEQ0(GC0308_MAX_PRE_DG_GAIN, 0x40),
> + REG_SEQ0(GC0308_ABB_MODE, 0x03),
> + REG_SEQ0(GC0308_GAMMA_OUT0, 0x10),
> + REG_SEQ0(GC0308_GAMMA_OUT1, 0x20),
> + REG_SEQ0(GC0308_GAMMA_OUT2, 0x38),
> + REG_SEQ0(GC0308_GAMMA_OUT3, 0x4e),
> + REG_SEQ0(GC0308_GAMMA_OUT4, 0x63),
> + REG_SEQ0(GC0308_GAMMA_OUT5, 0x76),
> + REG_SEQ0(GC0308_GAMMA_OUT6, 0x87),
> + REG_SEQ0(GC0308_GAMMA_OUT7, 0xa2),
> + REG_SEQ0(GC0308_GAMMA_OUT8, 0xb8),
> + REG_SEQ0(GC0308_GAMMA_OUT9, 0xca),
> + REG_SEQ0(GC0308_GAMMA_OUT10, 0xd8),
> + REG_SEQ0(GC0308_GAMMA_OUT11, 0xe3),
> + REG_SEQ0(GC0308_GAMMA_OUT12, 0xeb),
> + REG_SEQ0(GC0308_GAMMA_OUT13, 0xf0),
> + REG_SEQ0(GC0308_GAMMA_OUT14, 0xf8),
> + REG_SEQ0(GC0308_GAMMA_OUT15, 0xfd),
> + REG_SEQ0(GC0308_GAMMA_OUT16, 0xff),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT0, 0x00),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT1, 0x10),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT2, 0x1c),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT3, 0x30),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT4, 0x43),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT5, 0x54),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT6, 0x65),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT7, 0x75),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT8, 0x93),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT9, 0xb0),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT10, 0xcb),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT11, 0xe6),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT12, 0xff),
> + REG_SEQ0(GC0308_ABS_RANGE_COMP, 0x02),
> + REG_SEQ0(GC0308_ABS_STOP_MARGIN, 0x01),
> + REG_SEQ0(GC0308_Y_S_COMP, 0x02),
> + REG_SEQ0(GC0308_Y_STRETCH_LIMIT, 0x30),
> + REG_SEQ0(GC0308_BIG_WIN_X0, 0x12),
> + REG_SEQ0(GC0308_BIG_WIN_Y0, 0x0a),
> + REG_SEQ0(GC0308_BIG_WIN_X1, 0x9f),
> + REG_SEQ0(GC0308_BIG_WIN_Y1, 0x78),
> + REG_SEQ0(GC0308_AWB_RGB_HIGH_LOW, 0xf5),
> + REG_SEQ0(GC0308_AWB_Y_TO_C_DIFF2, 0x20),
> + REG_SEQ0(GC0308_AWB_C_MAX, 0x10),
> + REG_SEQ0(GC0308_AWB_C_INTER, 0x08),
> + REG_SEQ0(GC0308_AWB_C_INTER2, 0x20),
> + REG_SEQ0(GC0308_AWB_C_MAX_BIG, 0x0a),
> + REG_SEQ0(GC0308_AWB_NUMBER_LIMIT, 0xa0),
> + REG_SEQ0(GC0308_KWIN_RATIO, 0x60),
> + REG_SEQ0(GC0308_KWIN_THD, 0x08),
> + REG_SEQ0(GC0308_SMALL_WIN_WIDTH_STEP, 0x44),
> + REG_SEQ0(GC0308_SMALL_WIN_HEIGHT_STEP, 0x32),
> + REG_SEQ0(GC0308_AWB_YELLOW_TH, 0x41),
> + REG_SEQ0(GC0308_AWB_MODE, 0x37),
> + REG_SEQ0(GC0308_AWB_ADJUST_SPEED, 0x22),
> + REG_SEQ0(GC0308_AWB_EVERY_N, 0x19),
> + REG_SEQ0(0x114, 0x44), /* AWB set1 */
> + REG_SEQ0(0x115, 0x44), /* AWB set1 */
> + REG_SEQ0(0x116, 0xc2), /* AWB set1 */
> + REG_SEQ0(0x117, 0xa8), /* AWB set1 */
> + REG_SEQ0(0x118, 0x18), /* AWB set1 */
> + REG_SEQ0(0x119, 0x50), /* AWB set1 */
> + REG_SEQ0(0x11a, 0xd8), /* AWB set1 */
> + REG_SEQ0(0x11b, 0xf5), /* AWB set1 */
> + REG_SEQ0(0x170, 0x40), /* AWB set2 */
> + REG_SEQ0(0x171, 0x58), /* AWB set2 */
> + REG_SEQ0(0x172, 0x30), /* AWB set2 */
> + REG_SEQ0(0x173, 0x48), /* AWB set2 */
> + REG_SEQ0(0x174, 0x20), /* AWB set2 */
> + REG_SEQ0(0x175, 0x60), /* AWB set2 */
> + REG_SEQ0(0x177, 0x20), /* AWB set2 */
> + REG_SEQ0(0x178, 0x32), /* AWB set2 */
> + REG_SEQ0(0x130, 0x03), /* undocumented */
> + REG_SEQ0(0x131, 0x40), /* undocumented */
> + REG_SEQ0(0x132, 0x10), /* undocumented */
> + REG_SEQ0(0x133, 0xe0), /* undocumented */
> + REG_SEQ0(0x134, 0xe0), /* undocumented */
> + REG_SEQ0(0x135, 0x00), /* undocumented */
> + REG_SEQ0(0x136, 0x80), /* undocumented */
> + REG_SEQ0(0x137, 0x00), /* undocumented */
> + REG_SEQ0(0x138, 0x04), /* undocumented */
> + REG_SEQ0(0x139, 0x09), /* undocumented */
> + REG_SEQ0(0x13a, 0x12), /* undocumented */
> + REG_SEQ0(0x13b, 0x1c), /* undocumented */
> + REG_SEQ0(0x13c, 0x28), /* undocumented */
> + REG_SEQ0(0x13d, 0x31), /* undocumented */
> + REG_SEQ0(0x13e, 0x44), /* undocumented */
> + REG_SEQ0(0x13f, 0x57), /* undocumented */
> + REG_SEQ0(0x140, 0x6c), /* undocumented */
> + REG_SEQ0(0x141, 0x81), /* undocumented */
> + REG_SEQ0(0x142, 0x94), /* undocumented */
> + REG_SEQ0(0x143, 0xa7), /* undocumented */
> + REG_SEQ0(0x144, 0xb8), /* undocumented */
> + REG_SEQ0(0x145, 0xd6), /* undocumented */
> + REG_SEQ0(0x146, 0xee), /* undocumented */
> + REG_SEQ0(0x147, 0x0d), /* undocumented */
> + REG_SEQ0(0x162, 0xf7), /* undocumented */
> + REG_SEQ0(0x163, 0x68), /* undocumented */
> + REG_SEQ0(0x164, 0xd3), /* undocumented */
> + REG_SEQ0(0x165, 0xd3), /* undocumented */
> + REG_SEQ0(0x166, 0x60), /* undocumented */
> +};
> +
> +struct gc0308_colormode {
> + u8 special_effect;
> + u8 dbg_mode1;
> + u8 block_en1;
> + u8 aec_mode3;
> + u8 eeintp_mode_2;
> + u8 edge12_effect;
> + u8 luma_contrast;
> + u8 contrast_center;
> + u8 fixed_cb;
> + u8 fixed_cr;
> +};
> +
> +#define GC0308_COLOR_FX(reg_special_effect, reg_dbg_mode1, reg_block_en1, \
> + reg_aec_mode3, reg_eeintp_mode_2, reg_edge12_effect, \
> + reg_luma_contrast, reg_contrast_center, \
> + reg_fixed_cb, reg_fixed_cr) \
> + { \
> + .special_effect = reg_special_effect, \
> + .dbg_mode1 = reg_dbg_mode1, \
> + .block_en1 = reg_block_en1, \
> + .aec_mode3 = reg_aec_mode3, \
> + .eeintp_mode_2 = reg_eeintp_mode_2, \
> + .edge12_effect = reg_edge12_effect, \
> + .luma_contrast = reg_luma_contrast, \
> + .contrast_center = reg_contrast_center, \
> + .fixed_cb = reg_fixed_cb, \
> + .fixed_cr = reg_fixed_cr, \
> + }
> +
> +static const struct gc0308_colormode gc0308_colormodes[] = {
> + [V4L2_COLORFX_NONE] =
> + GC0308_COLOR_FX(0x00, 0x0a, 0xff, 0x90, 0x00,
> + 0x54, 0x3c, 0x80, 0x00, 0x00),
> + [V4L2_COLORFX_BW] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
> + 0x54, 0x40, 0x80, 0x00, 0x00),
> + [V4L2_COLORFX_SEPIA] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
> + 0x38, 0x40, 0x80, 0xd0, 0x28),
> + [V4L2_COLORFX_NEGATIVE] =
> + GC0308_COLOR_FX(0x01, 0x0a, 0xff, 0x90, 0x00,
> + 0x38, 0x40, 0x80, 0x00, 0x00),
> + [V4L2_COLORFX_EMBOSS] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xbf, 0x10, 0x01,
> + 0x38, 0x40, 0x80, 0x00, 0x00),
> + [V4L2_COLORFX_SKETCH] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x10, 0x80,
> + 0x38, 0x80, 0x90, 0x00, 0x00),
> + [V4L2_COLORFX_SKY_BLUE] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
> + 0x38, 0x40, 0x80, 0x50, 0xe0),
> + [V4L2_COLORFX_GRASS_GREEN] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x01,
> + 0x38, 0x40, 0x80, 0xc0, 0xc0),
> + [V4L2_COLORFX_SKIN_WHITEN] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xbf, 0x10, 0x01,
> + 0x38, 0x60, 0x40, 0x00, 0x00),
> +};
> +
> +static const struct reg_sequence sensor_banding_50hz[] = {
> + REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x78),
> + REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_2H, 0x03),
> + REG_SEQ0(GC0308_EXP_LVL_2L, 0x48),
> + REG_SEQ0(GC0308_EXP_LVL_3H, 0x04),
> + REG_SEQ0(GC0308_EXP_LVL_3L, 0xb0),
> + REG_SEQ0(GC0308_EXP_LVL_4H, 0x05),
> + REG_SEQ0(GC0308_EXP_LVL_4L, 0xa0),
> +};
> +
> +static const struct reg_sequence sensor_banding_60hz[] = {
> + REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x64),
> + REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_2H, 0x03),
> + REG_SEQ0(GC0308_EXP_LVL_2L, 0x84),
> + REG_SEQ0(GC0308_EXP_LVL_3H, 0x04),
> + REG_SEQ0(GC0308_EXP_LVL_3L, 0xb0),
> + REG_SEQ0(GC0308_EXP_LVL_4H, 0x05),
> + REG_SEQ0(GC0308_EXP_LVL_4L, 0xdc),
> +};
> +
> +static int gc0308_power_on(struct device *dev)
> +{
> + struct gc0308 *gc0308 = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = regulator_enable(gc0308->vdd);
> + if (ret)
> + return ret;
> +
> + ret = clk_prepare_enable(gc0308->clk);
> + if (ret)
> + goto clk_fail;
> +
> + gpiod_set_value_cansleep(gc0308->pwdn_gpio, 0);
> + usleep_range(10000, 20000);
> +
> + gpiod_set_value_cansleep(gc0308->reset_gpio, 1);
> + msleep(100);
> + gpiod_set_value_cansleep(gc0308->reset_gpio, 0);
> + msleep(100);

Wow! that's long :)

> +
> + return 0;
> +
> +clk_fail:
> + regulator_disable(gc0308->vdd);
> + return ret;
> +}
> +
> +static int gc0308_power_off(struct device *dev)
> +{
> + struct gc0308 *gc0308 = dev_get_drvdata(dev);
> +
> + gpiod_set_value_cansleep(gc0308->pwdn_gpio, 1);
> + clk_disable_unprepare(gc0308->clk);
> + regulator_disable(gc0308->vdd);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int gc0308_g_register(struct v4l2_subdev *sd,
> + struct v4l2_dbg_register *reg)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> + int ret, val;
> +
> + ret = regmap_read(gc0308->regmap, reg->reg, &val);
> + if (ret)
> + return ret;
> +
> + reg->val = val;
> + return 0;
> +}
> +
> +static int gc0308_s_register(struct v4l2_subdev *sd,
> + const struct v4l2_dbg_register *reg)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> +
> + return regmap_write(gc0308->regmap, reg->reg, reg->val);
> +}
> +#endif
> +
> +static int gc0308_s_format(struct gc0308 *gc0308)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(gc0308_formats); i++) {
> + if (gc0308->format.code != gc0308_formats[i].code)
> + continue;
> + return regmap_update_bits(gc0308->regmap,
> + GC0308_OUT_FORMAT, GENMASK(4, 0),
> + gc0308_formats[i].regval);
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int gc0308_set_exposure(struct gc0308 *gc0308, enum gc0308_exp_val exp)
> +{
> + const struct gc0308_exposure *regs = &gc0308_exposure_values[exp];
> + struct reg_sequence exposure_reg_seq[] = {
> + REG_SEQ0(GC0308_LUMA_OFFSET, regs->luma_offset),
> + REG_SEQ0(GC0308_AEC_TARGET_Y, regs->aec_target_y),
> + };
> +
> + dev_err(gc0308->dev, "exposure: %i\n", exp);

maybe dev_dbg ?

> +
> + return regmap_multi_reg_write(gc0308->regmap,
> + exposure_reg_seq,
> + ARRAY_SIZE(exposure_reg_seq));
> +}
> +
> +static int gc0308_set_awb_mode(struct gc0308 *gc0308,
> + enum v4l2_auto_n_preset_white_balance val)
> +{
> + const struct gc0308_awb_gains *regs = &gc0308_awb_gains[val];
> + struct reg_sequence awb_reg_seq[] = {
> + REG_SEQ0(GC0308_AWB_R_GAIN, regs->r),
> + REG_SEQ0(GC0308_AWB_G_GAIN, regs->g),
> + REG_SEQ0(GC0308_AWB_B_GAIN, regs->b),
> + };
> + int ret;
> +
> + ret = regmap_update_bits(gc0308->regmap, GC0308_AAAA_EN,
> + BIT(1), val == V4L2_WHITE_BALANCE_AUTO);
> + if (ret)
> + return ret;
> +
> + return regmap_multi_reg_write(gc0308->regmap,
> + awb_reg_seq,
> + ARRAY_SIZE(awb_reg_seq));

Can you save one line here ?

> +}
> +
> +static int gc0308_set_colormode(struct gc0308 *gc0308, enum v4l2_colorfx mode)
> +{
> + const struct gc0308_colormode *regs = &gc0308_colormodes[mode];
> + struct reg_sequence colormode_reg_seq[] = {
> + REG_SEQ0(GC0308_SPECIAL_EFFECT, regs->special_effect),
> + REG_SEQ0(GC0308_DEBUG_MODE1, regs->dbg_mode1),
> + REG_SEQ0(GC0308_BLOCK_EN1, regs->block_en1),
> + REG_SEQ0(GC0308_AEC_MODE3, regs->aec_mode3),
> + REG_SEQ0(GC0308_EEINTP_MODE_2, regs->eeintp_mode_2),
> + REG_SEQ0(GC0308_EDGE12_EFFECT, regs->edge12_effect),
> + REG_SEQ0(GC0308_LUMA_CONTRAST, regs->luma_contrast),
> + REG_SEQ0(GC0308_CONTRAST_CENTER, regs->contrast_center),
> + REG_SEQ0(GC0308_FIXED_CB, regs->fixed_cb),
> + REG_SEQ0(GC0308_FIXED_CR, regs->fixed_cr),
> + };
> +
> + return regmap_multi_reg_write(gc0308->regmap,
> + colormode_reg_seq,
> + ARRAY_SIZE(colormode_reg_seq));
> +}
> +
> +static int gc0308_set_power_line_freq(struct gc0308 *gc0308, int frequency)
> +{
> + const struct reg_sequence *regs;
> + int num_regs;
> +
> + switch (frequency) {
> + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
> + regs = sensor_banding_60hz;
> + num_regs = ARRAY_SIZE(sensor_banding_60hz);
> + break;
> + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
> + default:
> + regs = sensor_banding_50hz;
> + num_regs = ARRAY_SIZE(sensor_banding_50hz);
> + break;
> + }
> +
> + return regmap_multi_reg_write(gc0308->regmap, regs, num_regs);
> +}
> +
> +static int _gc0308_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl);
> + uint8_t flipval = ctrl->val ? 0xff : 0x00;
> +
> + switch (ctrl->id) {
> + case V4L2_CID_VFLIP:
> + return regmap_update_bits(gc0308->regmap, GC0308_CISCTL_MODE1,
> + BIT(1), flipval);
> + case V4L2_CID_HFLIP:
> + return regmap_update_bits(gc0308->regmap, GC0308_CISCTL_MODE1,
> + BIT(0), flipval);
> + case V4L2_CID_AUTO_WHITE_BALANCE:
> + return regmap_update_bits(gc0308->regmap, GC0308_AAAA_EN,
> + BIT(1), flipval);
> + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE:
> + return gc0308_set_awb_mode(gc0308, ctrl->val);
> + case V4L2_CID_POWER_LINE_FREQUENCY:
> + return gc0308_set_power_line_freq(gc0308, ctrl->val);
> + case V4L2_CID_COLORFX:
> + return gc0308_set_colormode(gc0308, ctrl->val);
> + case V4L2_CID_TEST_PATTERN:
> + return regmap_update_bits(gc0308->regmap, GC0308_DEBUG_MODE2,
> + GENMASK(1, 0), ctrl->val);
> + case V4L2_CID_AUTO_EXPOSURE_BIAS:
> + return gc0308_set_exposure(gc0308, ctrl->val);
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int gc0308_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl);
> + int ret;
> +
> + ret = pm_runtime_resume_and_get(gc0308->dev);

Sensor drivers are usually not resumed in the s_ctrl call path, but
usually the current power state is checked and the function returns
early if the device is not powered

if (!pm_runtime_get_if_in_use(&client->dev))
return 0;

Then in the the s_stream() call path, after the sensor has been
resumed, the controls are applied by calling

__v4l2_ctrl_handler_setup()

as you aready do!


> + if (ret < 0)
> + return ret;
> +
> + ret = _gc0308_s_ctrl(ctrl);
> +
> + pm_runtime_put(gc0308->dev);
> +
> + if (ret)
> + dev_err(gc0308->dev, "s_ctrl failed: %d\n", ret);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops gc0308_ctrl_ops = {
> + .s_ctrl = gc0308_s_ctrl,
> +};
> +
> +static const struct v4l2_subdev_core_ops gc0308_core_ops = {
> + .log_status = v4l2_ctrl_subdev_log_status,
> + .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> + .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + .g_register = gc0308_g_register,
> + .s_register = gc0308_s_register,
> +#endif
> +};
> +
> +static int gc0308_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index >= ARRAY_SIZE(gc0308_formats))
> + return -EINVAL;
> +
> + code->code = gc0308_formats[code->index].code;
> +
> + return 0;
> +}
> +
> +static int gc0308_enum_frame_size(struct v4l2_subdev *subdev,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_frame_size_enum *fse)
> +{
> + if (fse->index >= ARRAY_SIZE(gc0308_frame_sizes))
> + return -EINVAL;
> +

You should check fse->code is supported, assuming all sizes are
supported for all formats

> + fse->min_width = gc0308_frame_sizes[fse->index].width;
> + fse->max_width = gc0308_frame_sizes[fse->index].width;
> + fse->min_height = gc0308_frame_sizes[fse->index].height;
> + fse->max_height = gc0308_frame_sizes[fse->index].height;
> +
> + return 0;
> +}
> +
> +static void gc0308_update_pad_format(const struct gc0308_frame_size *mode,
> + struct v4l2_mbus_framefmt *fmt, u32 code)
> +{
> + fmt->width = mode->width;
> + fmt->height = mode->height;
> + fmt->code = code;
> + fmt->field = V4L2_FIELD_NONE;
> + fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +}
> +
> +static int gc0308_set_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> + const struct gc0308_frame_size *mode;
> + unsigned int i;
> + u32 code = 0;
> +
> + for (i = 0; i < ARRAY_SIZE(gc0308_formats); i++) {
> + if (fmt->format.code == gc0308_formats[i].code) {
> + code = gc0308_formats[i].code;
> + break;
> + }
> + }
> +
> + if (!code) {
> + dev_warn(gc0308->dev, "unsupported format code: %08x\n",
> + fmt->format.code);
> + code = gc0308_formats[2].code;
> + }
> +
> + mode = v4l2_find_nearest_size(gc0308_frame_sizes,
> + ARRAY_SIZE(gc0308_frame_sizes), width,
> + height, fmt->format.width,
> + fmt->format.height);
> +
> + gc0308_update_pad_format(mode, &fmt->format, code);
> + *v4l2_subdev_get_pad_format(sd, sd_state, fmt->pad) = fmt->format;
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
> + return 0;
> +
> + gc0308->format.width = fmt->format.width;
> + gc0308->format.height = fmt->format.height;
> + gc0308->format.code = fmt->format.code;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops gc0308_pad_ops = {
> + .enum_mbus_code = gc0308_enum_mbus_code,
> + .enum_frame_size = gc0308_enum_frame_size,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = gc0308_set_format,
> +};
> +
> +static int gc0308_set_resolution(struct gc0308 *gc0308,
> + u8 subsample, u16 w, u16 h)
> +{
> + struct reg_sequence resolution_regs[] = {
> + REG_SEQ0(GC0308_SUBSAMPLE, subsample),
> + REG_SEQ0(GC0308_SUBMODE, 0x03),
> + REG_SEQ0(GC0308_SUB_ROW_N1, 0x00),
> + REG_SEQ0(GC0308_SUB_ROW_N2, 0x00),
> + REG_SEQ0(GC0308_SUB_COL_N1, 0x00),
> + REG_SEQ0(GC0308_SUB_COL_N2, 0x00),
> + REG_SEQ0(GC0308_CROP_WIN_MODE, 0x80),
> + REG_SEQ0(GC0308_CROP_WIN_Y1, 0x00),
> + REG_SEQ0(GC0308_CROP_WIN_X1, 0x00),
> + REG_SEQ0(GC0308_CROP_WIN_HEIGHTH, (h >> 8) & 0xFF),
> + REG_SEQ0(GC0308_CROP_WIN_HEIGHTL, (h >> 0) & 0xFF),
> + REG_SEQ0(GC0308_CROP_WIN_WIDTHH, (w >> 8) & 0xFF),
> + REG_SEQ0(GC0308_CROP_WIN_WIDTHL, (w >> 0) & 0xFF),
> + };
> +
> + return regmap_multi_reg_write(gc0308->regmap, resolution_regs,
> + ARRAY_SIZE(resolution_regs));
> +}
> +
> +static int gc0308_start_stream(struct gc0308 *gc0308)
> +{
> + int ret, sync_mode;
> + u16 width = 0, height = 0;
> + u8 subsample = 0;
> + unsigned int i;
> +
> + ret = pm_runtime_resume_and_get(gc0308->dev);
> + if (ret < 0)
> + return ret;
> +
> + ret = regmap_multi_reg_write(gc0308->regmap, sensor_default_regs,
> + ARRAY_SIZE(sensor_default_regs));
> + if (ret) {
> + dev_err(gc0308->dev, "failed to write default regs\n");
> + goto disable_pm;
> + }
> +
> + ret = gc0308_s_format(gc0308);
> + if (ret) {
> + dev_err(gc0308->dev, "failed to set format\n");
> + goto disable_pm;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(gc0308_frame_sizes); i++) {
> + if (gc0308_frame_sizes[i].width == gc0308->format.width &&
> + gc0308_frame_sizes[i].height == gc0308->format.height) {
> + width = gc0308_frame_sizes[i].width;
> + height = gc0308_frame_sizes[i].height;
> + subsample = gc0308_frame_sizes[i].subsample;
> + }
> + }
> +
> + if (!width || !height) {
> + dev_err(gc0308->dev, "invalid resolution\n");
> + goto disable_pm;
> + }

Can this happen? Since sizes are setup with s_fmt they should be
correct.

> +
> + ret = gc0308_set_resolution(gc0308, subsample, width, height);
> + if (ret) {
> + dev_err(gc0308->dev, "failed to write resolution regs\n");
> + goto disable_pm;
> + }
> +
> + ret = __v4l2_ctrl_handler_setup(&gc0308->hdl);
> + if (ret) {
> + dev_err(gc0308->dev, "failed to setup controls\n");
> + goto disable_pm;
> + }
> +
> + /* HSYNC/VSYNC polarity */
> + sync_mode = 0x3;
> + if (gc0308->mbus_config & V4L2_MBUS_VSYNC_ACTIVE_LOW)
> + sync_mode &= ~BIT(0);
> + if (gc0308->mbus_config & V4L2_MBUS_HSYNC_ACTIVE_LOW)
> + sync_mode &= ~BIT(1);
> + ret = regmap_write(gc0308->regmap, GC0308_SYNC_MODE, sync_mode);
> + if (ret)
> + goto disable_pm;
> +
> + return 0;
> +
> +disable_pm:
> + pm_runtime_put(gc0308->dev);
> + return ret;
> +}
> +
> +static int gc0308_stop_stream(struct gc0308 *gc0308)
> +{
> + pm_runtime_put(gc0308->dev);
> + return 0;
> +}
> +
> +static int gc0308_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> + struct v4l2_subdev_state *sd_state;
> + int ret;
> +
> + sd_state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> + if (enable)
> + ret = gc0308_start_stream(gc0308);
> + else
> + ret = gc0308_stop_stream(gc0308);
> +
> + v4l2_subdev_unlock_state(sd_state);
> + return ret;
> +}
> +
> +static int gc0308_g_frame_interval(struct v4l2_subdev *sd,
> + struct v4l2_subdev_frame_interval *fi)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> + struct v4l2_subdev_state *sd_state;
> +
> + sd_state = v4l2_subdev_lock_and_get_active_state(sd);
> + fi->interval = gc0308->frame_interval;
> + v4l2_subdev_unlock_state(sd_state);
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_video_ops gc0308_video_ops = {
> + .g_frame_interval = gc0308_g_frame_interval,
> + .s_stream = gc0308_s_stream,
> +};
> +
> +static const struct v4l2_subdev_ops gc0308_subdev_ops = {
> + .core = &gc0308_core_ops,
> + .pad = &gc0308_pad_ops,
> + .video = &gc0308_video_ops,
> +};
> +
> +static int gc0308_bus_config(struct gc0308 *gc0308)
> +{
> + struct device *dev = gc0308->dev;
> + struct v4l2_fwnode_endpoint bus_cfg = {
> + .bus_type = V4L2_MBUS_PARALLEL
> + };
> + struct fwnode_handle *ep;
> + int ret;
> +
> + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> + if (!ep)
> + return -EINVAL;
> +
> + ret = v4l2_fwnode_endpoint_parse(ep, &bus_cfg);
> + fwnode_handle_put(ep);
> + if (ret)
> + return ret;
> +
> + gc0308->mbus_config = bus_cfg.bus.parallel.flags;
> +
> + return 0;
> +}
> +
> +static const char * const gc0308_test_pattern_menu[] = {
> + "Disabled",
> + "Test Image 1",
> + "Test Image 2",
> +};
> +
> +static int gc0308_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct gc0308 *gc0308;
> + unsigned long clkrate;
> + int ret, regval;
> +
> + gc0308 = devm_kzalloc(dev, sizeof(*gc0308), GFP_KERNEL);
> + if (!gc0308)
> + return -ENOMEM;
> +
> + gc0308->dev = dev;
> + dev_set_drvdata(dev, gc0308);
> +
> + ret = gc0308_bus_config(gc0308);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to get bus config\n");
> +
> + gc0308->format.width = gc0308_frame_sizes[0].width;
> + gc0308->format.height = gc0308_frame_sizes[0].height;
> + gc0308->format.code = gc0308_formats[2].code;
> + gc0308->frame_interval.numerator = 1;
> + gc0308->frame_interval.denominator = GC0308_FRAME_RATE;
> +
> + gc0308->clk = devm_clk_get_optional(dev, NULL);
> + if (IS_ERR(gc0308->clk))
> + return dev_err_probe(dev, PTR_ERR(gc0308->clk),
> + "could not get clk\n");
> +
> + gc0308->vdd = devm_regulator_get(dev, "vdd28");
> + if (IS_ERR(gc0308->vdd))
> + return dev_err_probe(dev, PTR_ERR(gc0308->vdd),
> + "failed to get vdd28 regulator\n");
> +
> + gc0308->pwdn_gpio = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_LOW);
> + if (IS_ERR(gc0308->pwdn_gpio))
> + return dev_err_probe(dev, PTR_ERR(gc0308->pwdn_gpio),
> + "failed to get powerdown gpio\n");
> +
> + gc0308->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> + if (IS_ERR(gc0308->reset_gpio))
> + return dev_err_probe(dev, PTR_ERR(gc0308->reset_gpio),
> + "failed to get reset gpio\n");
> +
> + gc0308->regmap = devm_regmap_init_i2c(client, &gc0308_regmap_config);
> + if (IS_ERR(gc0308->regmap))
> + return dev_err_probe(dev, PTR_ERR(gc0308->regmap),
> + "failed to init regmap\n");
> +
> + v4l2_i2c_subdev_init(&gc0308->sd, client, &gc0308_subdev_ops);
> + gc0308->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + gc0308->sd.flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
> +
> + v4l2_ctrl_handler_init(&gc0308->hdl, 8);
> + v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_VFLIP, 0, 1, 1, 0);
> + v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_HFLIP, 0, 1, 1, 0);
> + v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1);
> + v4l2_ctrl_new_std_menu_items(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_TEST_PATTERN,
> + ARRAY_SIZE(gc0308_test_pattern_menu) - 1,
> + 0, 0, gc0308_test_pattern_menu);
> + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
> + 8, ~0x14e, V4L2_WHITE_BALANCE_AUTO);
> + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_COLORFX, 8, 0, V4L2_COLORFX_NONE);
> + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_POWER_LINE_FREQUENCY,
> + V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
> + ~0x6, V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
> + v4l2_ctrl_new_int_menu(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_AUTO_EXPOSURE_BIAS,
> + ARRAY_SIZE(gc0308_exposure_menu) - 1,
> + ARRAY_SIZE(gc0308_exposure_menu)/2,
> + gc0308_exposure_menu);
> +
> + gc0308->sd.ctrl_handler = &gc0308->hdl;
> + if (gc0308->hdl.error) {
> + ret = gc0308->hdl.error;
> + return ret;
> + }
> +
> + gc0308->sd.state_lock = gc0308->hdl.lock;
> + gc0308->pad.flags = MEDIA_PAD_FL_SOURCE;
> + gc0308->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> + ret = media_entity_pads_init(&gc0308->sd.entity, 1, &gc0308->pad);
> + if (ret < 0)
> + return ret;
> +
> + ret = v4l2_subdev_init_finalize(&gc0308->sd);
> + if (ret)
> + goto fail_media_entity_cleanup;
> +
> + ret = gc0308_power_on(dev);
> + if (ret)
> + goto fail_subdev_cleanup;
> +
> + if (gc0308->clk) {
> + clkrate = clk_get_rate(gc0308->clk);
> + if (clkrate != 24000000)
> + dev_warn(dev, "unexpected clock rate: %lu\n", clkrate);

Should the driver continue to operate if the frequency is not
supported ?

> + }
> +
> + ret = regmap_read(gc0308->regmap, GC0308_CHIP_ID, &regval);
> + if (ret < 0) {
> + dev_err_probe(dev, ret, "failed to read chip ID\n");
> + goto fail_power_off;
> + }
> +
> + if (regval != 0x9b) {
> + ret = -EINVAL;
> + dev_err_probe(dev, ret, "invalid chip ID (%02x)\n", regval);
> + goto fail_power_off;
> + }
> +
> + ret = v4l2_async_register_subdev(&gc0308->sd);
> + if (ret) {
> + dev_err_probe(dev, ret, "failed to register v4l subdev\n");
> + goto fail_power_off;
> + }
> +
> + pm_runtime_set_active(dev);
> + pm_runtime_enable(dev);
> + pm_runtime_idle(dev);

using autosuspend might be nice. Have a look at the CCS driver as an
example

pm_runtime_set_active(&client->dev);
pm_runtime_get_noresume(&client->dev);
pm_runtime_enable(&client->dev);

rval = v4l2_async_register_subdev_sensor(&sensor->src->sd);
if (rval < 0)
goto out_disable_runtime_pm;

pm_runtime_set_autosuspend_delay(&client->dev, 1000);
pm_runtime_use_autosuspend(&client->dev);
pm_runtime_put_autosuspend(&client->dev);

> +
> + return 0;
> +
> +fail_power_off:
> + gc0308_power_off(dev);
> +fail_subdev_cleanup:
> + v4l2_subdev_cleanup(&gc0308->sd);
> +fail_media_entity_cleanup:
> + media_entity_cleanup(&gc0308->sd.entity);
> + return ret;
> +}
> +
> +static void gc0308_remove(struct i2c_client *client)
> +{
> + struct gc0308 *gc0308 = i2c_get_clientdata(client);
> + struct device *dev = &client->dev;
> +
> + pm_runtime_get_sync(dev);
> +
> + v4l2_async_unregister_subdev(&gc0308->sd);
> + v4l2_ctrl_handler_free(&gc0308->hdl);
> +
> + pm_runtime_disable(dev);
> + pm_runtime_set_suspended(dev);
> + pm_runtime_put_noidle(dev);
> + gc0308_power_off(dev);
> +}
> +
> +static const struct dev_pm_ops gc0308_pm_ops = {
> + SET_RUNTIME_PM_OPS(gc0308_power_off, gc0308_power_on, NULL)
> +};
> +
> +static const struct of_device_id gc0308_of_match[] = {
> + { .compatible = "galaxycore,gc0308" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, gc0308_of_match);
> +
> +static struct i2c_driver gc0308_i2c_driver = {
> + .driver = {
> + .name = "gc0308",
> + .pm = &gc0308_pm_ops,
> + .of_match_table = gc0308_of_match,
> + },
> + .probe = gc0308_probe,
> + .remove = gc0308_remove,
> +};
> +module_i2c_driver(gc0308_i2c_driver);
> +
> +MODULE_DESCRIPTION("GalaxyCore GC0308 Camera Driver");
> +MODULE_AUTHOR("Sebastian Reichel <[email protected]>");
> +MODULE_LICENSE("GPL");
> --
> 2.42.0
>

2023-10-26 02:15:27

by Sebastian Reichel

[permalink] [raw]
Subject: Re: [PATCH v2 4/4] media: i2c: gc0308: new driver

Hi Jacopo,

Thanks for the quick review, much appreciated!

On Tue, Oct 24, 2023 at 10:38:45AM +0200, Jacopo Mondi wrote:
> On Tue, Oct 24, 2023 at 02:59:53AM +0200, Sebastian Reichel wrote:
> > Introduce new driver for GalaxyCore GC0308, which is a cheap
> > 640x480 with an on-chip ISP sensor sold since 2010. Data is
> > provided via parallel bus.
> >
> > Signed-off-by: Sebastian Reichel <[email protected]>
> > ---

...

> Annoying questions, is this driver compatible with the newly
> introduced CCI helpers? Not that pressing as you're on regmap
> so you haven't your own read/write routines.

I cannot use devm_cci_regmap_init_i2c(), because I use regmap's
pagination feature. So instead of doing something like this:

{REG_PAGE_SELECT, 0x00},
{CCI_REG8(0x00), 0x23},
{CCI_REG8(0x01), 0x42},
{REG_PAGE_SELECT, 0x01},
{CCI_REG8(0x00), 0x13},
{CCI_REG8(0x01), 0x37},

I can do

{CCI_REG8(0x000), 0x23},
{CCI_REG8(0x001), 0x42},
{CCI_REG8(0x100), 0x13},
{CCI_REG8(0x101), 0x37},

That said, I updated the driver to use the CCI helpers instead of
directly using regmap with the exception of the initialization.

[...]

> > + gpiod_set_value_cansleep(gc0308->reset_gpio, 1);
> > + msleep(100);
> > + gpiod_set_value_cansleep(gc0308->reset_gpio, 0);
> > + msleep(100);
>
> Wow! that's long :)

I shortened them a bit. These were just the initial values I put
there. Unfortunately the timings are not described in the datasheet.
I now use values I found in some downstream drivers (10-20ms and
30ms).

[...]

> > +static int gc0308_set_exposure(struct gc0308 *gc0308, enum gc0308_exp_val exp)
> > +{
> > + const struct gc0308_exposure *regs = &gc0308_exposure_values[exp];
> > + struct reg_sequence exposure_reg_seq[] = {
> > + REG_SEQ0(GC0308_LUMA_OFFSET, regs->luma_offset),
> > + REG_SEQ0(GC0308_AEC_TARGET_Y, regs->aec_target_y),
> > + };
> > +
> > + dev_err(gc0308->dev, "exposure: %i\n", exp);
>
> maybe dev_dbg ?

oops. That should just go away. I had some issues when switching
from V4L2_CID_EXPOSURE to V4L2_CID_AUTO_EXPOSURE_BIAS after the
feedback from Sakari and forgot to remove this "debug" print.

[...]

> > +static int gc0308_s_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > + struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl);
> > + int ret;
> > +
> > + ret = pm_runtime_resume_and_get(gc0308->dev);
>
> Sensor drivers are usually not resumed in the s_ctrl call path, but
> usually the current power state is checked and the function returns
> early if the device is not powered
>
> if (!pm_runtime_get_if_in_use(&client->dev))
> return 0;
>
> Then in the the s_stream() call path, after the sensor has been
> resumed, the controls are applied by calling
>
> __v4l2_ctrl_handler_setup()
>
> as you aready do!

Right and the value is trashed by the following poweroff anyways.

[...]

> > + if (gc0308->clk) {
> > + clkrate = clk_get_rate(gc0308->clk);
> > + if (clkrate != 24000000)
> > + dev_warn(dev, "unexpected clock rate: %lu\n", clkrate);
>
> Should the driver continue to operate if the frequency is not
> supported ?

That's what Sakari suggested. I guess a clock rate of 23.9 MHz
would be fine, but different from 24MHz. Unfortunately the
datasheet does not describe the allowed clock rates. It just says,
that the max framerate is 30FPS at 24MHz. So I think a non-fatal
warning is the right thing to do.

[...]

I fixed the other things and plan to send a new series soon. Just
need to do some more testing.

Greetings,

-- Sebastian


Attachments:
(No filename) (3.56 kB)
signature.asc (849.00 B)
Download all attachments

2023-10-26 11:58:52

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH v2 4/4] media: i2c: gc0308: new driver

Hi Sebastian,

On Tue, Oct 24, 2023 at 02:59:53AM +0200, Sebastian Reichel wrote:
> Introduce new driver for GalaxyCore GC0308, which is a cheap
> 640x480 with an on-chip ISP sensor sold since 2010. Data is
> provided via parallel bus.

I missed to note the last time the driver doesn't care about link
frequencies, but I'm not sure it matters that much for a such device (very
old and uses parallel interface). It could be added later if there's
hardware that depends on it though.

On CCI --- it seems there are jus a few 16-bit registers and the driver
accesses only a couple of them so I think plain regmap is about as good.
The CCI helper is the most useful when you have a lot of registers the size
of which is not obvious. Just my thoughts...

A few minor comments below.

>
> Signed-off-by: Sebastian Reichel <[email protected]>
> ---
> MAINTAINERS | 7 +
> drivers/media/i2c/Kconfig | 14 +
> drivers/media/i2c/Makefile | 1 +
> drivers/media/i2c/gc0308.c | 1418 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 1440 insertions(+)
> create mode 100644 drivers/media/i2c/gc0308.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5f64cd42ff37..f03fd34067d0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8627,6 +8627,13 @@ F: drivers/mfd/gateworks-gsc.c
> F: include/linux/mfd/gsc.h
> F: include/linux/platform_data/gsc_hwmon.h
>
> +GC0308 GALAXYCORE CAMERA SENSOR DRIVER
> +M: Sebastian Reichel <[email protected]>
> +L: [email protected]
> +S: Maintained
> +F: Documentation/devicetree/bindings/media/i2c/galaxycore,gc0308.yaml
> +F: drivers/media/i2c/gc0308.c
> +
> GCC PLUGINS
> M: Kees Cook <[email protected]>
> L: [email protected]
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 74ff833ff48c..7db4abc0ef88 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -50,6 +50,20 @@ config VIDEO_AR0521
> To compile this driver as a module, choose M here: the
> module will be called ar0521.
>
> +config VIDEO_GC0308
> + tristate "GalaxyCore GC0308 sensor support"
> + depends on I2C && VIDEO_DEV
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + select REGMAP_I2C
> + help
> + This is a Video4Linux2 sensor driver for the GalaxyCore
> + GC0308 camera.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called gc0308.
> +
> config VIDEO_HI556
> tristate "Hynix Hi-556 sensor support"
> help
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 80b00d39b48f..f03a21f2962a 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_GC0308) += gc0308.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/gc0308.c b/drivers/media/i2c/gc0308.c
> new file mode 100644
> index 000000000000..f6e02284b661
> --- /dev/null
> +++ b/drivers/media/i2c/gc0308.c
> @@ -0,0 +1,1418 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for the GalaxyCore GC0308 camera sensor.
> + *
> + * Copyright (c) 2023 Sebastian Reichel <[email protected]>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/pm_runtime.h>

#include <linux/property.h> for fwnode API.

> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* Analog & CISCTL*/
> +#define GC0308_CHIP_ID 0x000
> +#define GC0308_HBLANK 0x001
> +#define GC0308_VBLANK 0x002
> +#define GC0308_EXPH 0x003
> +#define GC0308_EXPL 0x004
> +#define GC0308_ROW_STARTH 0x005
> +#define GC0308_ROW_STARTL 0x006
> +#define GC0308_COL_STARTH 0x007
> +#define GC0308_COL_STARTL 0x008
> +#define GC0308_WIN_HEIGHTH 0x009
> +#define GC0308_WIN_HEIGHTL 0x00a
> +#define GC0308_WIN_WIDTHH 0x00b
> +#define GC0308_WIN_WIDTHL 0x00c
> +#define GC0308_VS_ST 0x00d
> +#define GC0308_VS_ET 0x00e
> +#define GC0308_VB_HB 0x00f
> +#define GC0308_RSH_WIDTH 0x010
> +#define GC0308_TSP_WIDTH 0x011
> +#define GC0308_SH_DELAY 0x012
> +#define GC0308_ROW_TAIL_WIDTH 0x013
> +#define GC0308_CISCTL_MODE1 0x014
> +#define GC0308_CISCTL_MODE2 0x015
> +#define GC0308_CISCTL_MODE3 0x016
> +#define GC0308_CISCTL_MODE4 0x017
> +#define GC0308_ANALOG_MODE1 0x01a
> +#define GC0308_ANALOG_MODE2 0x01b
> +#define GC0308_HRST_RSG_V18 0x01c
> +#define GC0308_VREF_V25 0x01d
> +#define GC0308_ADC_R 0x01e
> +#define GC0308_PAD_DRV 0x01f
> +#define GC0308_SOFT_RESET 0x0fe
> +
> +/* ISP */
> +#define GC0308_BLOCK_EN1 0x020
> +#define GC0308_BLOCK_EN2 0x021
> +#define GC0308_AAAA_EN 0x022
> +#define GC0308_SPECIAL_EFFECT 0x023
> +#define GC0308_OUT_FORMAT 0x024
> +#define GC0308_OUT_EN 0x025
> +#define GC0308_SYNC_MODE 0x026
> +#define GC0308_CLK_DIV_MODE 0x028
> +#define GC0308_BYPASS_MODE 0x029
> +#define GC0308_CLK_GATING 0x02a
> +#define GC0308_DITHER_MODE 0x02b
> +#define GC0308_DITHER_BIT 0x02c
> +#define GC0308_DEBUG_MODE1 0x02d
> +#define GC0308_DEBUG_MODE2 0x02e
> +#define GC0308_DEBUG_MODE3 0x02f
> +#define GC0308_CROP_WIN_MODE 0x046
> +#define GC0308_CROP_WIN_Y1 0x047
> +#define GC0308_CROP_WIN_X1 0x048
> +#define GC0308_CROP_WIN_HEIGHTH 0x049
> +#define GC0308_CROP_WIN_HEIGHTL 0x04a
> +#define GC0308_CROP_WIN_WIDTHH 0x04b
> +#define GC0308_CROP_WIN_WIDTHL 0x04c
> +
> +/* BLK */
> +#define GC0308_BLK_MODE 0x030
> +#define GC0308_BLK_LIMIT_VAL 0x031
> +#define GC0308_GLOBAL_OFF 0x032
> +#define GC0308_CURRENT_R_OFF 0x033
> +#define GC0308_CURRENT_G_OFF 0x034
> +#define GC0308_CURRENT_B_OFF 0x035
> +#define GC0308_CURRENT_R_DARK_CURRENT 0x036
> +#define GC0308_CURRENT_G_DARK_CURRENT 0x037
> +#define GC0308_CURRENT_B_DARK_CURRENT 0x038
> +#define GC0308_EXP_RATE_DARKC 0x039
> +#define GC0308_OFF_SUBMODE 0x03a
> +#define GC0308_DARKC_SUBMODE 0x03b
> +#define GC0308_MANUAL_G1_OFF 0x03c
> +#define GC0308_MANUAL_R1_OFF 0x03d
> +#define GC0308_MANUAL_B2_OFF 0x03e
> +#define GC0308_MANUAL_G2_OFF 0x03f
> +
> +/* PREGAIN */
> +#define GC0308_GLOBAL_GAIN 0x050
> +#define GC0308_AUTO_PREGAIN 0x051
> +#define GC0308_AUTO_POSTGAIN 0x052
> +#define GC0308_CHANNEL_GAIN_G1 0x053
> +#define GC0308_CHANNEL_GAIN_R 0x054
> +#define GC0308_CHANNEL_GAIN_B 0x055
> +#define GC0308_CHANNEL_GAIN_G2 0x056
> +#define GC0308_R_RATIO 0x057
> +#define GC0308_G_RATIO 0x058
> +#define GC0308_B_RATIO 0x059
> +#define GC0308_AWB_R_GAIN 0x05a
> +#define GC0308_AWB_G_GAIN 0x05b
> +#define GC0308_AWB_B_GAIN 0x05c
> +#define GC0308_LSC_DEC_LVL1 0x05d
> +#define GC0308_LSC_DEC_LVL2 0x05e
> +#define GC0308_LSC_DEC_LVL3 0x05f
> +
> +/* DNDD */
> +#define GC0308_DN_MODE_EN 0x060
> +#define GC0308_DN_MODE_RATIO 0x061
> +#define GC0308_DN_BILAT_B_BASE 0x062
> +#define GC0308_DN_B_INCR 0x063
> +#define GC0308_DN_BILAT_N_BASE 0x064
> +#define GC0308_DN_N_INCR 0x065
> +#define GC0308_DD_DARK_BRIGHT_TH 0x066
> +#define GC0308_DD_FLAT_TH 0x067
> +#define GC0308_DD_LIMIT 0x068
> +
> +/* ASDE - Auto Saturation De-noise and Edge-Enhancement */
> +#define GC0308_ASDE_GAIN_TRESH 0x069
> +#define GC0308_ASDE_GAIN_MODE 0x06a
> +#define GC0308_ASDE_DN_SLOPE 0x06b
> +#define GC0308_ASDE_DD_BRIGHT 0x06c
> +#define GC0308_ASDE_DD_LIMIT 0x06d
> +#define GC0308_ASDE_AUTO_EE1 0x06e
> +#define GC0308_ASDE_AUTO_EE2 0x06f
> +#define GC0308_ASDE_AUTO_SAT_DEC_SLOPE 0x070
> +#define GC0308_ASDE_AUTO_SAT_LOW_LIMIT 0x071
> +
> +/* INTPEE - Interpolation and Edge-Enhancement */
> +#define GC0308_EEINTP_MODE_1 0x072
> +#define GC0308_EEINTP_MODE_2 0x073
> +#define GC0308_DIRECTION_TH1 0x074
> +#define GC0308_DIRECTION_TH2 0x075
> +#define GC0308_DIFF_HV_TI_TH 0x076
> +#define GC0308_EDGE12_EFFECT 0x077
> +#define GC0308_EDGE_POS_RATIO 0x078
> +#define GC0308_EDGE1_MINMAX 0x079
> +#define GC0308_EDGE2_MINMAX 0x07a
> +#define GC0308_EDGE12_TH 0x07b
> +#define GC0308_EDGE_MAX 0x07c
> +
> +/* ABB - Auto Black Balance */
> +#define GC0308_ABB_MODE 0x080
> +#define GC0308_ABB_TARGET_AVGH 0x081
> +#define GC0308_ABB_TARGET_AVGL 0x082
> +#define GC0308_ABB_LIMIT_VAL 0x083
> +#define GC0308_ABB_SPEED 0x084
> +#define GC0308_CURR_R_BLACK_LVL 0x085
> +#define GC0308_CURR_G_BLACK_LVL 0x086
> +#define GC0308_CURR_B_BLACK_LVL 0x087
> +#define GC0308_CURR_R_BLACK_FACTOR 0x088
> +#define GC0308_CURR_G_BLACK_FACTOR 0x089
> +#define GC0308_CURR_B_BLACK_FACTOR 0x08a
> +
> +/* LSC - Lens Shading Correction */
> +#define GC0308_LSC_RED_B2 0x08b
> +#define GC0308_LSC_GREEN_B2 0x08c
> +#define GC0308_LSC_BLUE_B2 0x08d
> +#define GC0308_LSC_RED_B4 0x08e
> +#define GC0308_LSC_GREEN_B4 0x08f
> +#define GC0308_LSC_BLUE_B4 0x090
> +#define GC0308_LSC_ROW_CENTER 0x091
> +#define GC0308_LSC_COL_CENTER 0x092
> +
> +/* CC - Channel Coefficient */
> +#define GC0308_CC_MATRIX_C11 0x093
> +#define GC0308_CC_MATRIX_C12 0x094
> +#define GC0308_CC_MATRIX_C13 0x095
> +#define GC0308_CC_MATRIX_C21 0x096
> +#define GC0308_CC_MATRIX_C22 0x097
> +#define GC0308_CC_MATRIX_C23 0x098
> +#define GC0308_CC_MATRIX_C41 0x09c
> +#define GC0308_CC_MATRIX_C42 0x09d
> +#define GC0308_CC_MATRIX_C43 0x09e
> +
> +/* GAMMA */
> +#define GC0308_GAMMA_OUT0 0x09f
> +#define GC0308_GAMMA_OUT1 0x0a0
> +#define GC0308_GAMMA_OUT2 0x0a1
> +#define GC0308_GAMMA_OUT3 0x0a2
> +#define GC0308_GAMMA_OUT4 0x0a3
> +#define GC0308_GAMMA_OUT5 0x0a4
> +#define GC0308_GAMMA_OUT6 0x0a5
> +#define GC0308_GAMMA_OUT7 0x0a6
> +#define GC0308_GAMMA_OUT8 0x0a7
> +#define GC0308_GAMMA_OUT9 0x0a8
> +#define GC0308_GAMMA_OUT10 0x0a9
> +#define GC0308_GAMMA_OUT11 0x0aa
> +#define GC0308_GAMMA_OUT12 0x0ab
> +#define GC0308_GAMMA_OUT13 0x0ac
> +#define GC0308_GAMMA_OUT14 0x0ad
> +#define GC0308_GAMMA_OUT15 0x0ae
> +#define GC0308_GAMMA_OUT16 0x0af
> +
> +/* YCP */
> +#define GC0308_GLOBAL_SATURATION 0x0b0
> +#define GC0308_SATURATION_CB 0x0b1
> +#define GC0308_SATURATION_CR 0x0b2
> +#define GC0308_LUMA_CONTRAST 0x0b3
> +#define GC0308_CONTRAST_CENTER 0x0b4
> +#define GC0308_LUMA_OFFSET 0x0b5
> +#define GC0308_SKIN_CB_CENTER 0x0b6
> +#define GC0308_SKIN_CR_CENTER 0x0b7
> +#define GC0308_SKIN_RADIUS_SQUARE 0x0b8
> +#define GC0308_SKIN_BRIGHTNESS 0x0b9
> +#define GC0308_FIXED_CB 0x0ba
> +#define GC0308_FIXED_CR 0x0bb
> +#define GC0308_EDGE_DEC_SA 0x0bd
> +#define GC0308_AUTO_GRAY_MODE 0x0be
> +#define GC0308_SATURATION_SUB_STRENGTH 0x0bf
> +#define GC0308_Y_GAMMA_OUT0 0x0c0
> +#define GC0308_Y_GAMMA_OUT1 0x0c1
> +#define GC0308_Y_GAMMA_OUT2 0x0c2
> +#define GC0308_Y_GAMMA_OUT3 0x0c3
> +#define GC0308_Y_GAMMA_OUT4 0x0c4
> +#define GC0308_Y_GAMMA_OUT5 0x0c5
> +#define GC0308_Y_GAMMA_OUT6 0x0c6
> +#define GC0308_Y_GAMMA_OUT7 0x0c7
> +#define GC0308_Y_GAMMA_OUT8 0x0c8
> +#define GC0308_Y_GAMMA_OUT9 0x0c9
> +#define GC0308_Y_GAMMA_OUT10 0x0ca
> +#define GC0308_Y_GAMMA_OUT11 0x0cb
> +#define GC0308_Y_GAMMA_OUT12 0x0cc
> +
> +/* AEC - Automatic Exposure Control */
> +#define GC0308_AEC_MODE1 0x0d0
> +#define GC0308_AEC_MODE2 0x0d1
> +#define GC0308_AEC_MODE3 0x0d2
> +#define GC0308_AEC_TARGET_Y 0x0d3
> +#define GC0308_Y_AVG 0x0d4
> +#define GC0308_AEC_HIGH_LOW_RANGE 0x0d5
> +#define GC0308_AEC_IGNORE 0x0d6
> +#define GC0308_AEC_LIMIT_HIGH_RANGE 0x0d7
> +#define GC0308_AEC_R_OFFSET 0x0d9
> +#define GC0308_AEC_GB_OFFSET 0x0da
> +#define GC0308_AEC_SLOW_MARGIN 0x0db
> +#define GC0308_AEC_FAST_MARGIN 0x0dc
> +#define GC0308_AEC_EXP_CHANGE_GAIN 0x0dd
> +#define GC0308_AEC_STEP2_SUNLIGHT 0x0de
> +#define GC0308_AEC_I_FRAMES 0x0df
> +#define GC0308_AEC_I_STOP_L_MARGIN 0x0e0
> +#define GC0308_AEC_I_STOP_MARGIN 0x0e1
> +#define GC0308_ANTI_FLICKER_STEP_H 0x0e2
> +#define GC0308_ANTI_FLICKER_STEP_L 0x0e3
> +#define GC0308_EXP_LVL_1H 0x0e4
> +#define GC0308_EXP_LVL_1L 0x0e5
> +#define GC0308_EXP_LVL_2H 0x0e6
> +#define GC0308_EXP_LVL_2L 0x0e7
> +#define GC0308_EXP_LVL_3H 0x0e8
> +#define GC0308_EXP_LVL_3L 0x0e9
> +#define GC0308_EXP_LVL_4H 0x0ea
> +#define GC0308_EXP_LVL_4L 0x0eb
> +#define GC0308_MAX_EXP_LVL 0x0ec
> +#define GC0308_EXP_MIN_L 0x0ed
> +#define GC0308_MAX_POST_DF_GAIN 0x0ee
> +#define GC0308_MAX_PRE_DG_GAIN 0x0ef
> +
> +/* ABS */
> +#define GC0308_ABS_RANGE_COMP 0x0f0
> +#define GC0308_ABS_STOP_MARGIN 0x0f1
> +#define GC0308_Y_S_COMP 0x0f2
> +#define GC0308_Y_STRETCH_LIMIT 0x0f3
> +#define GC0308_Y_TILT 0x0f4
> +#define GC0308_Y_STRETCH 0x0f5
> +
> +/* Measure Window */
> +#define GC0308_BIG_WIN_X0 0x0f7
> +#define GC0308_BIG_WIN_Y0 0x0f8
> +#define GC0308_BIG_WIN_X1 0x0f9
> +#define GC0308_BIG_WIN_Y1 0x0fa
> +#define GC0308_DIFF_Y_BIG_THD 0x0fb
> +
> +/* OUT Module (P1) */
> +#define GC0308_CLOSE_FRAME_EN 0x150
> +#define GC0308_CLOSE_FRAME_NUM1 0x151
> +#define GC0308_CLOSE_FRAME_NUM2 0x152
> +#define GC0308_BAYER_MODE 0x153
> +#define GC0308_SUBSAMPLE 0x154
> +#define GC0308_SUBMODE 0x155
> +#define GC0308_SUB_ROW_N1 0x156
> +#define GC0308_SUB_ROW_N2 0x157
> +#define GC0308_SUB_COL_N1 0x158
> +#define GC0308_SUB_COL_N2 0x159
> +
> +/* AWB (P1) - Auto White Balance */
> +#define GC0308_AWB_RGB_HIGH_LOW 0x100
> +#define GC0308_AWB_Y_TO_C_DIFF2 0x102
> +#define GC0308_AWB_C_MAX 0x104
> +#define GC0308_AWB_C_INTER 0x105
> +#define GC0308_AWB_C_INTER2 0x106
> +#define GC0308_AWB_C_MAX_BIG 0x108
> +#define GC0308_AWB_Y_HIGH 0x109
> +#define GC0308_AWB_NUMBER_LIMIT 0x10a
> +#define GC0308_KWIN_RATIO 0x10b
> +#define GC0308_KWIN_THD 0x10c
> +#define GC0308_LIGHT_GAIN_RANGE 0x10d
> +#define GC0308_SMALL_WIN_WIDTH_STEP 0x10e
> +#define GC0308_SMALL_WIN_HEIGHT_STEP 0x10f
> +#define GC0308_AWB_YELLOW_TH 0x110
> +#define GC0308_AWB_MODE 0x111
> +#define GC0308_AWB_ADJUST_SPEED 0x112
> +#define GC0308_AWB_EVERY_N 0x113
> +#define GC0308_R_AVG_USE 0x1d0
> +#define GC0308_G_AVG_USE 0x1d1
> +#define GC0308_B_AVG_USE 0x1d2
> +
> +#define GC0308_FRAME_RATE 29
> +
> +enum gc0308_exp_val {
> + GC0308_EXP_M4 = 0,
> + GC0308_EXP_M3,
> + GC0308_EXP_M2,
> + GC0308_EXP_M1,
> + GC0308_EXP_0,
> + GC0308_EXP_P1,
> + GC0308_EXP_P2,
> + GC0308_EXP_P3,
> + GC0308_EXP_P4,
> +};
> +
> +static const s64 gc0308_exposure_menu[] = {
> + -4, -3, -2, -1, 0, 1, 2, 3, 4
> +};
> +
> +struct gc0308_exposure {
> + u8 luma_offset;
> + u8 aec_target_y;
> +};
> +
> +#define GC0308_EXPOSURE(luma_offset_reg, aec_target_y_reg) \
> + { .luma_offset = luma_offset_reg, .aec_target_y = aec_target_y_reg }
> +
> +static const struct gc0308_exposure gc0308_exposure_values[] = {
> + [GC0308_EXP_M4] = GC0308_EXPOSURE(0xc0, 0x30),
> + [GC0308_EXP_M3] = GC0308_EXPOSURE(0xd0, 0x38),
> + [GC0308_EXP_M2] = GC0308_EXPOSURE(0xe0, 0x40),
> + [GC0308_EXP_M1] = GC0308_EXPOSURE(0xf0, 0x48),
> + [GC0308_EXP_0] = GC0308_EXPOSURE(0x08, 0x50),
> + [GC0308_EXP_P1] = GC0308_EXPOSURE(0x10, 0x5c),
> + [GC0308_EXP_P2] = GC0308_EXPOSURE(0x20, 0x60),
> + [GC0308_EXP_P3] = GC0308_EXPOSURE(0x30, 0x68),
> + [GC0308_EXP_P4] = GC0308_EXPOSURE(0x40, 0x70),
> +};
> +
> +struct gc0308_awb_gains {
> + u8 r;
> + u8 g;
> + u8 b;
> +};
> +
> +#define GC0308_AWB_GAINS(red, green, blue) \
> + { .r = red, .g = green, .b = blue }
> +
> +static const struct gc0308_awb_gains gc0308_awb_gains[] = {
> + [V4L2_WHITE_BALANCE_AUTO] = GC0308_AWB_GAINS(0x56, 0x40, 0x4a),
> + [V4L2_WHITE_BALANCE_CLOUDY] = GC0308_AWB_GAINS(0x8c, 0x50, 0x40),
> + [V4L2_WHITE_BALANCE_DAYLIGHT] = GC0308_AWB_GAINS(0x74, 0x52, 0x40),
> + [V4L2_WHITE_BALANCE_INCANDESCENT] = GC0308_AWB_GAINS(0x48, 0x40, 0x5c),
> + [V4L2_WHITE_BALANCE_FLUORESCENT] = GC0308_AWB_GAINS(0x40, 0x42, 0x50),
> +};
> +
> +struct gc0308_format {
> + u32 code;
> + u8 regval;
> +};
> +
> +#define GC0308_FORMAT(v4l2_code, gc0308_regval) \
> + { .code = v4l2_code, .regval = gc0308_regval }
> +
> +static const struct gc0308_format gc0308_formats[] = {
> + GC0308_FORMAT(MEDIA_BUS_FMT_UYVY8_2X8, 0x00),
> + GC0308_FORMAT(MEDIA_BUS_FMT_VYUY8_2X8, 0x01),
> + GC0308_FORMAT(MEDIA_BUS_FMT_YUYV8_2X8, 0x02),
> + GC0308_FORMAT(MEDIA_BUS_FMT_YVYU8_2X8, 0x03),
> + GC0308_FORMAT(MEDIA_BUS_FMT_RGB565_2X8_BE, 0x06),
> + GC0308_FORMAT(MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, 0x07),
> + GC0308_FORMAT(MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, 0x09),
> +};
> +
> +struct gc0308_frame_size {
> + u8 subsample;
> + u32 width;
> + u32 height;
> +};
> +
> +#define GC0308_FRAME_SIZE(s, w, h) \
> + { .subsample = s, .width = w, .height = h }
> +
> +static const struct gc0308_frame_size gc0308_frame_sizes[] = {
> + GC0308_FRAME_SIZE(0x11, 640, 480),
> + GC0308_FRAME_SIZE(0x22, 320, 240),
> + GC0308_FRAME_SIZE(0x44, 160, 120),
> +};
> +
> +struct gc0308 {
> + struct v4l2_subdev sd;
> + struct v4l2_ctrl_handler hdl;
> + struct media_pad pad;
> + struct v4l2_mbus_framefmt format;
> + struct device *dev;
> + struct clk *clk;
> + struct regmap *regmap;
> + struct regulator *vdd;
> + struct gpio_desc *pwdn_gpio;
> + struct gpio_desc *reset_gpio;
> + unsigned int mbus_config;
> + struct v4l2_fract frame_interval;
> +};
> +
> +static inline struct gc0308 *to_gc0308(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct gc0308, sd);
> +}
> +
> +static const struct regmap_range_cfg gc0308_ranges[] = {
> + {
> + .range_min = 0x0000,
> + .range_max = 0x01ff,
> + .selector_reg = 0xfe,
> + .selector_mask = 0x01,
> + .selector_shift = 0x00,
> + .window_start = 0x00,
> + .window_len = 0x100,
> + },
> +};
> +
> +static const struct regmap_config gc0308_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = 0x1ff,
> + .ranges = gc0308_ranges,
> + .num_ranges = ARRAY_SIZE(gc0308_ranges),
> + .cache_type = REGCACHE_RBTREE,
> +};
> +
> +static const struct reg_sequence sensor_default_regs[] = {
> + REG_SEQ0(GC0308_VB_HB, 0x00),
> + REG_SEQ0(GC0308_HBLANK, 0x40),
> + REG_SEQ0(GC0308_VBLANK, 0x20),
> + REG_SEQ0(GC0308_EXPH, 0x02),
> + REG_SEQ0(GC0308_EXPL, 0x58),
> + REG_SEQ0(GC0308_AWB_R_GAIN, 0x56),
> + REG_SEQ0(GC0308_AWB_G_GAIN, 0x40),
> + REG_SEQ0(GC0308_AWB_B_GAIN, 0x4a),
> + REG_SEQ0(GC0308_ANTI_FLICKER_STEP_H, 0x00),
> + REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x78),
> + REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_2H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_2L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_3H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_3L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_4H, 0x0e),
> + REG_SEQ0(GC0308_EXP_LVL_4L, 0xa6),
> + REG_SEQ0(GC0308_MAX_EXP_LVL, 0x20),
> + REG_SEQ0(GC0308_ROW_STARTH, 0x00),
> + REG_SEQ0(GC0308_ROW_STARTL, 0x00),
> + REG_SEQ0(GC0308_COL_STARTH, 0x00),
> + REG_SEQ0(GC0308_COL_STARTL, 0x00),
> + REG_SEQ0(GC0308_WIN_HEIGHTH, 0x01), /* 0x1e8 = 488 => VT=480 */
> + REG_SEQ0(GC0308_WIN_HEIGHTL, 0xe8),
> + REG_SEQ0(GC0308_WIN_WIDTHH, 0x02), /* 0x288 = 648 => VH=640 */
> + REG_SEQ0(GC0308_WIN_WIDTHL, 0x88),
> + REG_SEQ0(GC0308_VS_ST, 0x02), /* Start Time (in rows) */
> + REG_SEQ0(GC0308_VS_ET, 0x02), /* End Time (in rows) */
> + REG_SEQ0(GC0308_RSH_WIDTH, 0x22),
> + REG_SEQ0(GC0308_TSP_WIDTH, 0x0d),
> + REG_SEQ0(GC0308_SH_DELAY, 0x50), /* sample hold delay post row finish */
> + REG_SEQ0(GC0308_ROW_TAIL_WIDTH, 0x0f),
> + REG_SEQ0(GC0308_CISCTL_MODE1, 0x10),
> + REG_SEQ0(GC0308_CISCTL_MODE2, 0x0a),
> + REG_SEQ0(GC0308_CISCTL_MODE3, 0x05),
> + REG_SEQ0(GC0308_CISCTL_MODE4, 0x01),
> + REG_SEQ0(0x018, 0x44), /* undocumented */
> + REG_SEQ0(0x019, 0x44), /* undocumented */
> + REG_SEQ0(GC0308_ANALOG_MODE1, 0x2a),
> + REG_SEQ0(GC0308_ANALOG_MODE2, 0x00),
> + REG_SEQ0(GC0308_HRST_RSG_V18, 0x49),
> + REG_SEQ0(GC0308_VREF_V25, 0x9a),
> + REG_SEQ0(GC0308_ADC_R, 0x61),
> + REG_SEQ0(GC0308_PAD_DRV, 0x01), /* drv strength: pclk=4mA */
> + REG_SEQ0(GC0308_BLOCK_EN1, 0x7f),
> + REG_SEQ0(GC0308_BLOCK_EN2, 0xfa),
> + REG_SEQ0(GC0308_AAAA_EN, 0x57),
> + REG_SEQ0(GC0308_OUT_FORMAT, 0xa2), /* YCbYCr */
> + REG_SEQ0(GC0308_OUT_EN, 0x0f),
> + REG_SEQ0(GC0308_SYNC_MODE, 0x03),
> + REG_SEQ0(GC0308_CLK_DIV_MODE, 0x00),
> + REG_SEQ0(GC0308_DEBUG_MODE1, 0x0a),
> + REG_SEQ0(GC0308_DEBUG_MODE2, 0x00),
> + REG_SEQ0(GC0308_DEBUG_MODE3, 0x01),
> + REG_SEQ0(GC0308_BLK_MODE, 0xf7),
> + REG_SEQ0(GC0308_BLK_LIMIT_VAL, 0x50),
> + REG_SEQ0(GC0308_GLOBAL_OFF, 0x00),
> + REG_SEQ0(GC0308_CURRENT_R_OFF, 0x28),
> + REG_SEQ0(GC0308_CURRENT_G_OFF, 0x2a),
> + REG_SEQ0(GC0308_CURRENT_B_OFF, 0x28),
> + REG_SEQ0(GC0308_EXP_RATE_DARKC, 0x04),
> + REG_SEQ0(GC0308_OFF_SUBMODE, 0x20),
> + REG_SEQ0(GC0308_DARKC_SUBMODE, 0x20),
> + REG_SEQ0(GC0308_MANUAL_G1_OFF, 0x00),
> + REG_SEQ0(GC0308_MANUAL_R1_OFF, 0x00),
> + REG_SEQ0(GC0308_MANUAL_B2_OFF, 0x00),
> + REG_SEQ0(GC0308_MANUAL_G2_OFF, 0x00),
> + REG_SEQ0(GC0308_GLOBAL_GAIN, 0x14),
> + REG_SEQ0(GC0308_AUTO_POSTGAIN, 0x41),
> + REG_SEQ0(GC0308_CHANNEL_GAIN_G1, 0x80),
> + REG_SEQ0(GC0308_CHANNEL_GAIN_R, 0x80),
> + REG_SEQ0(GC0308_CHANNEL_GAIN_B, 0x80),
> + REG_SEQ0(GC0308_CHANNEL_GAIN_G2, 0x80),
> + REG_SEQ0(GC0308_LSC_RED_B2, 0x20),
> + REG_SEQ0(GC0308_LSC_GREEN_B2, 0x20),
> + REG_SEQ0(GC0308_LSC_BLUE_B2, 0x20),
> + REG_SEQ0(GC0308_LSC_RED_B4, 0x14),
> + REG_SEQ0(GC0308_LSC_GREEN_B4, 0x10),
> + REG_SEQ0(GC0308_LSC_BLUE_B4, 0x14),
> + REG_SEQ0(GC0308_LSC_ROW_CENTER, 0x3c),
> + REG_SEQ0(GC0308_LSC_COL_CENTER, 0x50),
> + REG_SEQ0(GC0308_LSC_DEC_LVL1, 0x12),
> + REG_SEQ0(GC0308_LSC_DEC_LVL2, 0x1a),
> + REG_SEQ0(GC0308_LSC_DEC_LVL3, 0x24),
> + REG_SEQ0(GC0308_DN_MODE_EN, 0x07),
> + REG_SEQ0(GC0308_DN_MODE_RATIO, 0x15),
> + REG_SEQ0(GC0308_DN_BILAT_B_BASE, 0x08),
> + REG_SEQ0(GC0308_DN_BILAT_N_BASE, 0x03),
> + REG_SEQ0(GC0308_DD_DARK_BRIGHT_TH, 0xe8),
> + REG_SEQ0(GC0308_DD_FLAT_TH, 0x86),
> + REG_SEQ0(GC0308_DD_LIMIT, 0x82),
> + REG_SEQ0(GC0308_ASDE_GAIN_TRESH, 0x18),
> + REG_SEQ0(GC0308_ASDE_GAIN_MODE, 0x0f),
> + REG_SEQ0(GC0308_ASDE_DN_SLOPE, 0x00),
> + REG_SEQ0(GC0308_ASDE_DD_BRIGHT, 0x5f),
> + REG_SEQ0(GC0308_ASDE_DD_LIMIT, 0x8f),
> + REG_SEQ0(GC0308_ASDE_AUTO_EE1, 0x55),
> + REG_SEQ0(GC0308_ASDE_AUTO_EE2, 0x38),
> + REG_SEQ0(GC0308_ASDE_AUTO_SAT_DEC_SLOPE, 0x15),
> + REG_SEQ0(GC0308_ASDE_AUTO_SAT_LOW_LIMIT, 0x33),
> + REG_SEQ0(GC0308_EEINTP_MODE_1, 0xdc),
> + REG_SEQ0(GC0308_EEINTP_MODE_2, 0x00),
> + REG_SEQ0(GC0308_DIRECTION_TH1, 0x02),
> + REG_SEQ0(GC0308_DIRECTION_TH2, 0x3f),
> + REG_SEQ0(GC0308_DIFF_HV_TI_TH, 0x02),
> + REG_SEQ0(GC0308_EDGE12_EFFECT, 0x38),
> + REG_SEQ0(GC0308_EDGE_POS_RATIO, 0x88),
> + REG_SEQ0(GC0308_EDGE1_MINMAX, 0x81),
> + REG_SEQ0(GC0308_EDGE2_MINMAX, 0x81),
> + REG_SEQ0(GC0308_EDGE12_TH, 0x22),
> + REG_SEQ0(GC0308_EDGE_MAX, 0xff),
> + REG_SEQ0(GC0308_CC_MATRIX_C11, 0x48),
> + REG_SEQ0(GC0308_CC_MATRIX_C12, 0x02),
> + REG_SEQ0(GC0308_CC_MATRIX_C13, 0x07),
> + REG_SEQ0(GC0308_CC_MATRIX_C21, 0xe0),
> + REG_SEQ0(GC0308_CC_MATRIX_C22, 0x40),
> + REG_SEQ0(GC0308_CC_MATRIX_C23, 0xf0),
> + REG_SEQ0(GC0308_SATURATION_CB, 0x40),
> + REG_SEQ0(GC0308_SATURATION_CR, 0x40),
> + REG_SEQ0(GC0308_LUMA_CONTRAST, 0x40),
> + REG_SEQ0(GC0308_SKIN_CB_CENTER, 0xe0),
> + REG_SEQ0(GC0308_EDGE_DEC_SA, 0x38),
> + REG_SEQ0(GC0308_AUTO_GRAY_MODE, 0x36),
> + REG_SEQ0(GC0308_AEC_MODE1, 0xcb),
> + REG_SEQ0(GC0308_AEC_MODE2, 0x10),
> + REG_SEQ0(GC0308_AEC_MODE3, 0x90),
> + REG_SEQ0(GC0308_AEC_TARGET_Y, 0x48),
> + REG_SEQ0(GC0308_AEC_HIGH_LOW_RANGE, 0xf2),
> + REG_SEQ0(GC0308_AEC_IGNORE, 0x16),
> + REG_SEQ0(GC0308_AEC_SLOW_MARGIN, 0x92),
> + REG_SEQ0(GC0308_AEC_FAST_MARGIN, 0xa5),
> + REG_SEQ0(GC0308_AEC_I_FRAMES, 0x23),
> + REG_SEQ0(GC0308_AEC_R_OFFSET, 0x00),
> + REG_SEQ0(GC0308_AEC_GB_OFFSET, 0x00),
> + REG_SEQ0(GC0308_AEC_I_STOP_L_MARGIN, 0x09),
> + REG_SEQ0(GC0308_EXP_MIN_L, 0x04),
> + REG_SEQ0(GC0308_MAX_POST_DF_GAIN, 0xa0),
> + REG_SEQ0(GC0308_MAX_PRE_DG_GAIN, 0x40),
> + REG_SEQ0(GC0308_ABB_MODE, 0x03),
> + REG_SEQ0(GC0308_GAMMA_OUT0, 0x10),
> + REG_SEQ0(GC0308_GAMMA_OUT1, 0x20),
> + REG_SEQ0(GC0308_GAMMA_OUT2, 0x38),
> + REG_SEQ0(GC0308_GAMMA_OUT3, 0x4e),
> + REG_SEQ0(GC0308_GAMMA_OUT4, 0x63),
> + REG_SEQ0(GC0308_GAMMA_OUT5, 0x76),
> + REG_SEQ0(GC0308_GAMMA_OUT6, 0x87),
> + REG_SEQ0(GC0308_GAMMA_OUT7, 0xa2),
> + REG_SEQ0(GC0308_GAMMA_OUT8, 0xb8),
> + REG_SEQ0(GC0308_GAMMA_OUT9, 0xca),
> + REG_SEQ0(GC0308_GAMMA_OUT10, 0xd8),
> + REG_SEQ0(GC0308_GAMMA_OUT11, 0xe3),
> + REG_SEQ0(GC0308_GAMMA_OUT12, 0xeb),
> + REG_SEQ0(GC0308_GAMMA_OUT13, 0xf0),
> + REG_SEQ0(GC0308_GAMMA_OUT14, 0xf8),
> + REG_SEQ0(GC0308_GAMMA_OUT15, 0xfd),
> + REG_SEQ0(GC0308_GAMMA_OUT16, 0xff),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT0, 0x00),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT1, 0x10),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT2, 0x1c),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT3, 0x30),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT4, 0x43),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT5, 0x54),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT6, 0x65),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT7, 0x75),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT8, 0x93),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT9, 0xb0),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT10, 0xcb),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT11, 0xe6),
> + REG_SEQ0(GC0308_Y_GAMMA_OUT12, 0xff),
> + REG_SEQ0(GC0308_ABS_RANGE_COMP, 0x02),
> + REG_SEQ0(GC0308_ABS_STOP_MARGIN, 0x01),
> + REG_SEQ0(GC0308_Y_S_COMP, 0x02),
> + REG_SEQ0(GC0308_Y_STRETCH_LIMIT, 0x30),
> + REG_SEQ0(GC0308_BIG_WIN_X0, 0x12),
> + REG_SEQ0(GC0308_BIG_WIN_Y0, 0x0a),
> + REG_SEQ0(GC0308_BIG_WIN_X1, 0x9f),
> + REG_SEQ0(GC0308_BIG_WIN_Y1, 0x78),
> + REG_SEQ0(GC0308_AWB_RGB_HIGH_LOW, 0xf5),
> + REG_SEQ0(GC0308_AWB_Y_TO_C_DIFF2, 0x20),
> + REG_SEQ0(GC0308_AWB_C_MAX, 0x10),
> + REG_SEQ0(GC0308_AWB_C_INTER, 0x08),
> + REG_SEQ0(GC0308_AWB_C_INTER2, 0x20),
> + REG_SEQ0(GC0308_AWB_C_MAX_BIG, 0x0a),
> + REG_SEQ0(GC0308_AWB_NUMBER_LIMIT, 0xa0),
> + REG_SEQ0(GC0308_KWIN_RATIO, 0x60),
> + REG_SEQ0(GC0308_KWIN_THD, 0x08),
> + REG_SEQ0(GC0308_SMALL_WIN_WIDTH_STEP, 0x44),
> + REG_SEQ0(GC0308_SMALL_WIN_HEIGHT_STEP, 0x32),
> + REG_SEQ0(GC0308_AWB_YELLOW_TH, 0x41),
> + REG_SEQ0(GC0308_AWB_MODE, 0x37),
> + REG_SEQ0(GC0308_AWB_ADJUST_SPEED, 0x22),
> + REG_SEQ0(GC0308_AWB_EVERY_N, 0x19),
> + REG_SEQ0(0x114, 0x44), /* AWB set1 */
> + REG_SEQ0(0x115, 0x44), /* AWB set1 */
> + REG_SEQ0(0x116, 0xc2), /* AWB set1 */
> + REG_SEQ0(0x117, 0xa8), /* AWB set1 */
> + REG_SEQ0(0x118, 0x18), /* AWB set1 */
> + REG_SEQ0(0x119, 0x50), /* AWB set1 */
> + REG_SEQ0(0x11a, 0xd8), /* AWB set1 */
> + REG_SEQ0(0x11b, 0xf5), /* AWB set1 */
> + REG_SEQ0(0x170, 0x40), /* AWB set2 */
> + REG_SEQ0(0x171, 0x58), /* AWB set2 */
> + REG_SEQ0(0x172, 0x30), /* AWB set2 */
> + REG_SEQ0(0x173, 0x48), /* AWB set2 */
> + REG_SEQ0(0x174, 0x20), /* AWB set2 */
> + REG_SEQ0(0x175, 0x60), /* AWB set2 */
> + REG_SEQ0(0x177, 0x20), /* AWB set2 */
> + REG_SEQ0(0x178, 0x32), /* AWB set2 */
> + REG_SEQ0(0x130, 0x03), /* undocumented */
> + REG_SEQ0(0x131, 0x40), /* undocumented */
> + REG_SEQ0(0x132, 0x10), /* undocumented */
> + REG_SEQ0(0x133, 0xe0), /* undocumented */
> + REG_SEQ0(0x134, 0xe0), /* undocumented */
> + REG_SEQ0(0x135, 0x00), /* undocumented */
> + REG_SEQ0(0x136, 0x80), /* undocumented */
> + REG_SEQ0(0x137, 0x00), /* undocumented */
> + REG_SEQ0(0x138, 0x04), /* undocumented */
> + REG_SEQ0(0x139, 0x09), /* undocumented */
> + REG_SEQ0(0x13a, 0x12), /* undocumented */
> + REG_SEQ0(0x13b, 0x1c), /* undocumented */
> + REG_SEQ0(0x13c, 0x28), /* undocumented */
> + REG_SEQ0(0x13d, 0x31), /* undocumented */
> + REG_SEQ0(0x13e, 0x44), /* undocumented */
> + REG_SEQ0(0x13f, 0x57), /* undocumented */
> + REG_SEQ0(0x140, 0x6c), /* undocumented */
> + REG_SEQ0(0x141, 0x81), /* undocumented */
> + REG_SEQ0(0x142, 0x94), /* undocumented */
> + REG_SEQ0(0x143, 0xa7), /* undocumented */
> + REG_SEQ0(0x144, 0xb8), /* undocumented */
> + REG_SEQ0(0x145, 0xd6), /* undocumented */
> + REG_SEQ0(0x146, 0xee), /* undocumented */
> + REG_SEQ0(0x147, 0x0d), /* undocumented */
> + REG_SEQ0(0x162, 0xf7), /* undocumented */
> + REG_SEQ0(0x163, 0x68), /* undocumented */
> + REG_SEQ0(0x164, 0xd3), /* undocumented */
> + REG_SEQ0(0x165, 0xd3), /* undocumented */
> + REG_SEQ0(0x166, 0x60), /* undocumented */
> +};
> +
> +struct gc0308_colormode {
> + u8 special_effect;
> + u8 dbg_mode1;
> + u8 block_en1;
> + u8 aec_mode3;
> + u8 eeintp_mode_2;
> + u8 edge12_effect;
> + u8 luma_contrast;
> + u8 contrast_center;
> + u8 fixed_cb;
> + u8 fixed_cr;
> +};
> +
> +#define GC0308_COLOR_FX(reg_special_effect, reg_dbg_mode1, reg_block_en1, \
> + reg_aec_mode3, reg_eeintp_mode_2, reg_edge12_effect, \
> + reg_luma_contrast, reg_contrast_center, \
> + reg_fixed_cb, reg_fixed_cr) \
> + { \
> + .special_effect = reg_special_effect, \
> + .dbg_mode1 = reg_dbg_mode1, \
> + .block_en1 = reg_block_en1, \
> + .aec_mode3 = reg_aec_mode3, \
> + .eeintp_mode_2 = reg_eeintp_mode_2, \
> + .edge12_effect = reg_edge12_effect, \
> + .luma_contrast = reg_luma_contrast, \
> + .contrast_center = reg_contrast_center, \
> + .fixed_cb = reg_fixed_cb, \
> + .fixed_cr = reg_fixed_cr, \
> + }
> +
> +static const struct gc0308_colormode gc0308_colormodes[] = {
> + [V4L2_COLORFX_NONE] =
> + GC0308_COLOR_FX(0x00, 0x0a, 0xff, 0x90, 0x00,
> + 0x54, 0x3c, 0x80, 0x00, 0x00),
> + [V4L2_COLORFX_BW] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
> + 0x54, 0x40, 0x80, 0x00, 0x00),
> + [V4L2_COLORFX_SEPIA] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
> + 0x38, 0x40, 0x80, 0xd0, 0x28),
> + [V4L2_COLORFX_NEGATIVE] =
> + GC0308_COLOR_FX(0x01, 0x0a, 0xff, 0x90, 0x00,
> + 0x38, 0x40, 0x80, 0x00, 0x00),
> + [V4L2_COLORFX_EMBOSS] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xbf, 0x10, 0x01,
> + 0x38, 0x40, 0x80, 0x00, 0x00),
> + [V4L2_COLORFX_SKETCH] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x10, 0x80,
> + 0x38, 0x80, 0x90, 0x00, 0x00),
> + [V4L2_COLORFX_SKY_BLUE] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x00,
> + 0x38, 0x40, 0x80, 0x50, 0xe0),
> + [V4L2_COLORFX_GRASS_GREEN] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xff, 0x90, 0x01,
> + 0x38, 0x40, 0x80, 0xc0, 0xc0),
> + [V4L2_COLORFX_SKIN_WHITEN] =
> + GC0308_COLOR_FX(0x02, 0x0a, 0xbf, 0x10, 0x01,
> + 0x38, 0x60, 0x40, 0x00, 0x00),
> +};
> +
> +static const struct reg_sequence sensor_banding_50hz[] = {
> + REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x78),
> + REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_2H, 0x03),
> + REG_SEQ0(GC0308_EXP_LVL_2L, 0x48),
> + REG_SEQ0(GC0308_EXP_LVL_3H, 0x04),
> + REG_SEQ0(GC0308_EXP_LVL_3L, 0xb0),
> + REG_SEQ0(GC0308_EXP_LVL_4H, 0x05),
> + REG_SEQ0(GC0308_EXP_LVL_4L, 0xa0),
> +};
> +
> +static const struct reg_sequence sensor_banding_60hz[] = {
> + REG_SEQ0(GC0308_ANTI_FLICKER_STEP_L, 0x64),
> + REG_SEQ0(GC0308_EXP_LVL_1H, 0x02),
> + REG_SEQ0(GC0308_EXP_LVL_1L, 0x58),
> + REG_SEQ0(GC0308_EXP_LVL_2H, 0x03),
> + REG_SEQ0(GC0308_EXP_LVL_2L, 0x84),
> + REG_SEQ0(GC0308_EXP_LVL_3H, 0x04),
> + REG_SEQ0(GC0308_EXP_LVL_3L, 0xb0),
> + REG_SEQ0(GC0308_EXP_LVL_4H, 0x05),
> + REG_SEQ0(GC0308_EXP_LVL_4L, 0xdc),
> +};
> +
> +static int gc0308_power_on(struct device *dev)
> +{
> + struct gc0308 *gc0308 = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = regulator_enable(gc0308->vdd);
> + if (ret)
> + return ret;
> +
> + ret = clk_prepare_enable(gc0308->clk);
> + if (ret)
> + goto clk_fail;
> +
> + gpiod_set_value_cansleep(gc0308->pwdn_gpio, 0);
> + usleep_range(10000, 20000);
> +
> + gpiod_set_value_cansleep(gc0308->reset_gpio, 1);
> + msleep(100);
> + gpiod_set_value_cansleep(gc0308->reset_gpio, 0);
> + msleep(100);
> +
> + return 0;
> +
> +clk_fail:
> + regulator_disable(gc0308->vdd);
> + return ret;
> +}
> +
> +static int gc0308_power_off(struct device *dev)
> +{
> + struct gc0308 *gc0308 = dev_get_drvdata(dev);
> +
> + gpiod_set_value_cansleep(gc0308->pwdn_gpio, 1);
> + clk_disable_unprepare(gc0308->clk);
> + regulator_disable(gc0308->vdd);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int gc0308_g_register(struct v4l2_subdev *sd,
> + struct v4l2_dbg_register *reg)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> + int ret, val;
> +
> + ret = regmap_read(gc0308->regmap, reg->reg, &val);
> + if (ret)
> + return ret;
> +
> + reg->val = val;
> + return 0;
> +}
> +
> +static int gc0308_s_register(struct v4l2_subdev *sd,
> + const struct v4l2_dbg_register *reg)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> +
> + return regmap_write(gc0308->regmap, reg->reg, reg->val);
> +}
> +#endif
> +
> +static int gc0308_s_format(struct gc0308 *gc0308)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(gc0308_formats); i++) {
> + if (gc0308->format.code != gc0308_formats[i].code)
> + continue;
> + return regmap_update_bits(gc0308->regmap,
> + GC0308_OUT_FORMAT, GENMASK(4, 0),
> + gc0308_formats[i].regval);
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int gc0308_set_exposure(struct gc0308 *gc0308, enum gc0308_exp_val exp)
> +{
> + const struct gc0308_exposure *regs = &gc0308_exposure_values[exp];
> + struct reg_sequence exposure_reg_seq[] = {
> + REG_SEQ0(GC0308_LUMA_OFFSET, regs->luma_offset),
> + REG_SEQ0(GC0308_AEC_TARGET_Y, regs->aec_target_y),
> + };
> +
> + dev_err(gc0308->dev, "exposure: %i\n", exp);
> +
> + return regmap_multi_reg_write(gc0308->regmap,
> + exposure_reg_seq,
> + ARRAY_SIZE(exposure_reg_seq));
> +}
> +
> +static int gc0308_set_awb_mode(struct gc0308 *gc0308,
> + enum v4l2_auto_n_preset_white_balance val)
> +{
> + const struct gc0308_awb_gains *regs = &gc0308_awb_gains[val];
> + struct reg_sequence awb_reg_seq[] = {
> + REG_SEQ0(GC0308_AWB_R_GAIN, regs->r),
> + REG_SEQ0(GC0308_AWB_G_GAIN, regs->g),
> + REG_SEQ0(GC0308_AWB_B_GAIN, regs->b),
> + };
> + int ret;
> +
> + ret = regmap_update_bits(gc0308->regmap, GC0308_AAAA_EN,
> + BIT(1), val == V4L2_WHITE_BALANCE_AUTO);
> + if (ret)
> + return ret;
> +
> + return regmap_multi_reg_write(gc0308->regmap,
> + awb_reg_seq,

Fits on the same line.

> + ARRAY_SIZE(awb_reg_seq));
> +}
> +
> +static int gc0308_set_colormode(struct gc0308 *gc0308, enum v4l2_colorfx mode)
> +{
> + const struct gc0308_colormode *regs = &gc0308_colormodes[mode];
> + struct reg_sequence colormode_reg_seq[] = {
> + REG_SEQ0(GC0308_SPECIAL_EFFECT, regs->special_effect),
> + REG_SEQ0(GC0308_DEBUG_MODE1, regs->dbg_mode1),
> + REG_SEQ0(GC0308_BLOCK_EN1, regs->block_en1),
> + REG_SEQ0(GC0308_AEC_MODE3, regs->aec_mode3),
> + REG_SEQ0(GC0308_EEINTP_MODE_2, regs->eeintp_mode_2),
> + REG_SEQ0(GC0308_EDGE12_EFFECT, regs->edge12_effect),
> + REG_SEQ0(GC0308_LUMA_CONTRAST, regs->luma_contrast),
> + REG_SEQ0(GC0308_CONTRAST_CENTER, regs->contrast_center),
> + REG_SEQ0(GC0308_FIXED_CB, regs->fixed_cb),
> + REG_SEQ0(GC0308_FIXED_CR, regs->fixed_cr),
> + };
> +
> + return regmap_multi_reg_write(gc0308->regmap,
> + colormode_reg_seq,
> + ARRAY_SIZE(colormode_reg_seq));
> +}
> +
> +static int gc0308_set_power_line_freq(struct gc0308 *gc0308, int frequency)
> +{
> + const struct reg_sequence *regs;
> + int num_regs;
> +
> + switch (frequency) {
> + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
> + regs = sensor_banding_60hz;
> + num_regs = ARRAY_SIZE(sensor_banding_60hz);
> + break;
> + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
> + default:
> + regs = sensor_banding_50hz;
> + num_regs = ARRAY_SIZE(sensor_banding_50hz);
> + break;
> + }
> +
> + return regmap_multi_reg_write(gc0308->regmap, regs, num_regs);

I'd do this without local variables: you can just pass the values as
arguments.

> +}
> +
> +static int _gc0308_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl);
> + uint8_t flipval = ctrl->val ? 0xff : 0x00;
> +
> + switch (ctrl->id) {
> + case V4L2_CID_VFLIP:
> + return regmap_update_bits(gc0308->regmap, GC0308_CISCTL_MODE1,
> + BIT(1), flipval);
> + case V4L2_CID_HFLIP:
> + return regmap_update_bits(gc0308->regmap, GC0308_CISCTL_MODE1,
> + BIT(0), flipval);
> + case V4L2_CID_AUTO_WHITE_BALANCE:
> + return regmap_update_bits(gc0308->regmap, GC0308_AAAA_EN,
> + BIT(1), flipval);
> + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE:
> + return gc0308_set_awb_mode(gc0308, ctrl->val);
> + case V4L2_CID_POWER_LINE_FREQUENCY:
> + return gc0308_set_power_line_freq(gc0308, ctrl->val);
> + case V4L2_CID_COLORFX:
> + return gc0308_set_colormode(gc0308, ctrl->val);
> + case V4L2_CID_TEST_PATTERN:
> + return regmap_update_bits(gc0308->regmap, GC0308_DEBUG_MODE2,
> + GENMASK(1, 0), ctrl->val);
> + case V4L2_CID_AUTO_EXPOSURE_BIAS:
> + return gc0308_set_exposure(gc0308, ctrl->val);
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int gc0308_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> + struct gc0308 *gc0308 = container_of(ctrl->handler, struct gc0308, hdl);
> + int ret;
> +
> + ret = pm_runtime_resume_and_get(gc0308->dev);
> + if (ret < 0)
> + return ret;
> +
> + ret = _gc0308_s_ctrl(ctrl);
> +
> + pm_runtime_put(gc0308->dev);
> +
> + if (ret)
> + dev_err(gc0308->dev, "s_ctrl failed: %d\n", ret);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops gc0308_ctrl_ops = {
> + .s_ctrl = gc0308_s_ctrl,
> +};
> +
> +static const struct v4l2_subdev_core_ops gc0308_core_ops = {
> + .log_status = v4l2_ctrl_subdev_log_status,
> + .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> + .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + .g_register = gc0308_g_register,
> + .s_register = gc0308_s_register,
> +#endif
> +};
> +
> +static int gc0308_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index >= ARRAY_SIZE(gc0308_formats))
> + return -EINVAL;
> +
> + code->code = gc0308_formats[code->index].code;
> +
> + return 0;
> +}
> +
> +static int gc0308_enum_frame_size(struct v4l2_subdev *subdev,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_frame_size_enum *fse)
> +{
> + if (fse->index >= ARRAY_SIZE(gc0308_frame_sizes))
> + return -EINVAL;
> +
> + fse->min_width = gc0308_frame_sizes[fse->index].width;
> + fse->max_width = gc0308_frame_sizes[fse->index].width;
> + fse->min_height = gc0308_frame_sizes[fse->index].height;
> + fse->max_height = gc0308_frame_sizes[fse->index].height;
> +
> + return 0;
> +}
> +
> +static void gc0308_update_pad_format(const struct gc0308_frame_size *mode,
> + struct v4l2_mbus_framefmt *fmt, u32 code)
> +{
> + fmt->width = mode->width;
> + fmt->height = mode->height;
> + fmt->code = code;
> + fmt->field = V4L2_FIELD_NONE;
> + fmt->colorspace = V4L2_COLORSPACE_SRGB;
> +}
> +
> +static int gc0308_set_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> + const struct gc0308_frame_size *mode;
> + unsigned int i;
> + u32 code = 0;
> +
> + for (i = 0; i < ARRAY_SIZE(gc0308_formats); i++) {
> + if (fmt->format.code == gc0308_formats[i].code) {
> + code = gc0308_formats[i].code;
> + break;
> + }
> + }
> +
> + if (!code) {
> + dev_warn(gc0308->dev, "unsupported format code: %08x\n",
> + fmt->format.code);
> + code = gc0308_formats[2].code;

Why 2 instaed of e.g. 0?

> + }
> +
> + mode = v4l2_find_nearest_size(gc0308_frame_sizes,
> + ARRAY_SIZE(gc0308_frame_sizes), width,
> + height, fmt->format.width,
> + fmt->format.height);

Could you store the mode in struct gc0308 so you wouldn't need to obtain it
again in s_stream()?

> +
> + gc0308_update_pad_format(mode, &fmt->format, code);
> + *v4l2_subdev_get_pad_format(sd, sd_state, fmt->pad) = fmt->format;
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
> + return 0;
> +
> + gc0308->format.width = fmt->format.width;
> + gc0308->format.height = fmt->format.height;
> + gc0308->format.code = fmt->format.code;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops gc0308_pad_ops = {
> + .enum_mbus_code = gc0308_enum_mbus_code,
> + .enum_frame_size = gc0308_enum_frame_size,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = gc0308_set_format,
> +};
> +
> +static int gc0308_set_resolution(struct gc0308 *gc0308,
> + u8 subsample, u16 w, u16 h)
> +{
> + struct reg_sequence resolution_regs[] = {
> + REG_SEQ0(GC0308_SUBSAMPLE, subsample),
> + REG_SEQ0(GC0308_SUBMODE, 0x03),
> + REG_SEQ0(GC0308_SUB_ROW_N1, 0x00),
> + REG_SEQ0(GC0308_SUB_ROW_N2, 0x00),
> + REG_SEQ0(GC0308_SUB_COL_N1, 0x00),
> + REG_SEQ0(GC0308_SUB_COL_N2, 0x00),
> + REG_SEQ0(GC0308_CROP_WIN_MODE, 0x80),
> + REG_SEQ0(GC0308_CROP_WIN_Y1, 0x00),
> + REG_SEQ0(GC0308_CROP_WIN_X1, 0x00),
> + REG_SEQ0(GC0308_CROP_WIN_HEIGHTH, (h >> 8) & 0xFF),
> + REG_SEQ0(GC0308_CROP_WIN_HEIGHTL, (h >> 0) & 0xFF),
> + REG_SEQ0(GC0308_CROP_WIN_WIDTHH, (w >> 8) & 0xFF),
> + REG_SEQ0(GC0308_CROP_WIN_WIDTHL, (w >> 0) & 0xFF),
> + };
> +
> + return regmap_multi_reg_write(gc0308->regmap, resolution_regs,
> + ARRAY_SIZE(resolution_regs));
> +}
> +
> +static int gc0308_start_stream(struct gc0308 *gc0308)
> +{
> + int ret, sync_mode;
> + u16 width = 0, height = 0;
> + u8 subsample = 0;
> + unsigned int i;
> +
> + ret = pm_runtime_resume_and_get(gc0308->dev);
> + if (ret < 0)
> + return ret;
> +
> + ret = regmap_multi_reg_write(gc0308->regmap, sensor_default_regs,
> + ARRAY_SIZE(sensor_default_regs));
> + if (ret) {
> + dev_err(gc0308->dev, "failed to write default regs\n");
> + goto disable_pm;
> + }
> +
> + ret = gc0308_s_format(gc0308);
> + if (ret) {
> + dev_err(gc0308->dev, "failed to set format\n");
> + goto disable_pm;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(gc0308_frame_sizes); i++) {
> + if (gc0308_frame_sizes[i].width == gc0308->format.width &&
> + gc0308_frame_sizes[i].height == gc0308->format.height) {
> + width = gc0308_frame_sizes[i].width;
> + height = gc0308_frame_sizes[i].height;
> + subsample = gc0308_frame_sizes[i].subsample;
> + }
> + }
> +
> + if (!width || !height) {
> + dev_err(gc0308->dev, "invalid resolution\n");
> + goto disable_pm;
> + }
> +
> + ret = gc0308_set_resolution(gc0308, subsample, width, height);
> + if (ret) {
> + dev_err(gc0308->dev, "failed to write resolution regs\n");
> + goto disable_pm;
> + }
> +
> + ret = __v4l2_ctrl_handler_setup(&gc0308->hdl);
> + if (ret) {
> + dev_err(gc0308->dev, "failed to setup controls\n");
> + goto disable_pm;
> + }
> +
> + /* HSYNC/VSYNC polarity */
> + sync_mode = 0x3;
> + if (gc0308->mbus_config & V4L2_MBUS_VSYNC_ACTIVE_LOW)
> + sync_mode &= ~BIT(0);
> + if (gc0308->mbus_config & V4L2_MBUS_HSYNC_ACTIVE_LOW)
> + sync_mode &= ~BIT(1);
> + ret = regmap_write(gc0308->regmap, GC0308_SYNC_MODE, sync_mode);
> + if (ret)
> + goto disable_pm;
> +
> + return 0;
> +
> +disable_pm:
> + pm_runtime_put(gc0308->dev);
> + return ret;
> +}
> +
> +static int gc0308_stop_stream(struct gc0308 *gc0308)
> +{
> + pm_runtime_put(gc0308->dev);
> + return 0;
> +}
> +
> +static int gc0308_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> + struct v4l2_subdev_state *sd_state;
> + int ret;
> +
> + sd_state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> + if (enable)
> + ret = gc0308_start_stream(gc0308);
> + else
> + ret = gc0308_stop_stream(gc0308);
> +
> + v4l2_subdev_unlock_state(sd_state);
> + return ret;
> +}
> +
> +static int gc0308_g_frame_interval(struct v4l2_subdev *sd,
> + struct v4l2_subdev_frame_interval *fi)
> +{
> + struct gc0308 *gc0308 = to_gc0308(sd);
> + struct v4l2_subdev_state *sd_state;
> +
> + sd_state = v4l2_subdev_lock_and_get_active_state(sd);
> + fi->interval = gc0308->frame_interval;
> + v4l2_subdev_unlock_state(sd_state);
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_video_ops gc0308_video_ops = {
> + .g_frame_interval = gc0308_g_frame_interval,
> + .s_stream = gc0308_s_stream,
> +};
> +
> +static const struct v4l2_subdev_ops gc0308_subdev_ops = {
> + .core = &gc0308_core_ops,
> + .pad = &gc0308_pad_ops,
> + .video = &gc0308_video_ops,
> +};
> +
> +static int gc0308_bus_config(struct gc0308 *gc0308)
> +{
> + struct device *dev = gc0308->dev;
> + struct v4l2_fwnode_endpoint bus_cfg = {
> + .bus_type = V4L2_MBUS_PARALLEL
> + };
> + struct fwnode_handle *ep;
> + int ret;
> +
> + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> + if (!ep)
> + return -EINVAL;
> +
> + ret = v4l2_fwnode_endpoint_parse(ep, &bus_cfg);
> + fwnode_handle_put(ep);
> + if (ret)
> + return ret;
> +
> + gc0308->mbus_config = bus_cfg.bus.parallel.flags;
> +
> + return 0;
> +}
> +
> +static const char * const gc0308_test_pattern_menu[] = {
> + "Disabled",
> + "Test Image 1",
> + "Test Image 2",
> +};
> +
> +static int gc0308_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct gc0308 *gc0308;
> + unsigned long clkrate;
> + int ret, regval;
> +
> + gc0308 = devm_kzalloc(dev, sizeof(*gc0308), GFP_KERNEL);
> + if (!gc0308)
> + return -ENOMEM;
> +
> + gc0308->dev = dev;
> + dev_set_drvdata(dev, gc0308);
> +
> + ret = gc0308_bus_config(gc0308);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to get bus config\n");
> +
> + gc0308->format.width = gc0308_frame_sizes[0].width;
> + gc0308->format.height = gc0308_frame_sizes[0].height;
> + gc0308->format.code = gc0308_formats[2].code;
> + gc0308->frame_interval.numerator = 1;
> + gc0308->frame_interval.denominator = GC0308_FRAME_RATE;
> +
> + gc0308->clk = devm_clk_get_optional(dev, NULL);
> + if (IS_ERR(gc0308->clk))
> + return dev_err_probe(dev, PTR_ERR(gc0308->clk),
> + "could not get clk\n");
> +
> + gc0308->vdd = devm_regulator_get(dev, "vdd28");
> + if (IS_ERR(gc0308->vdd))
> + return dev_err_probe(dev, PTR_ERR(gc0308->vdd),
> + "failed to get vdd28 regulator\n");
> +
> + gc0308->pwdn_gpio = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_LOW);
> + if (IS_ERR(gc0308->pwdn_gpio))
> + return dev_err_probe(dev, PTR_ERR(gc0308->pwdn_gpio),
> + "failed to get powerdown gpio\n");
> +
> + gc0308->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> + if (IS_ERR(gc0308->reset_gpio))
> + return dev_err_probe(dev, PTR_ERR(gc0308->reset_gpio),
> + "failed to get reset gpio\n");
> +
> + gc0308->regmap = devm_regmap_init_i2c(client, &gc0308_regmap_config);
> + if (IS_ERR(gc0308->regmap))
> + return dev_err_probe(dev, PTR_ERR(gc0308->regmap),
> + "failed to init regmap\n");
> +
> + v4l2_i2c_subdev_init(&gc0308->sd, client, &gc0308_subdev_ops);
> + gc0308->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + gc0308->sd.flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
> +
> + v4l2_ctrl_handler_init(&gc0308->hdl, 8);
> + v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_VFLIP, 0, 1, 1, 0);
> + v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_HFLIP, 0, 1, 1, 0);
> + v4l2_ctrl_new_std(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1);
> + v4l2_ctrl_new_std_menu_items(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_TEST_PATTERN,
> + ARRAY_SIZE(gc0308_test_pattern_menu) - 1,
> + 0, 0, gc0308_test_pattern_menu);
> + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
> + 8, ~0x14e, V4L2_WHITE_BALANCE_AUTO);
> + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_COLORFX, 8, 0, V4L2_COLORFX_NONE);
> + v4l2_ctrl_new_std_menu(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_POWER_LINE_FREQUENCY,
> + V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
> + ~0x6, V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
> + v4l2_ctrl_new_int_menu(&gc0308->hdl, &gc0308_ctrl_ops,
> + V4L2_CID_AUTO_EXPOSURE_BIAS,
> + ARRAY_SIZE(gc0308_exposure_menu) - 1,
> + ARRAY_SIZE(gc0308_exposure_menu)/2,
> + gc0308_exposure_menu);
> +
> + gc0308->sd.ctrl_handler = &gc0308->hdl;
> + if (gc0308->hdl.error) {
> + ret = gc0308->hdl.error;

You'll need to free the control handler here. Same below in
media_entity_pads_init() error handling.

> + return ret;
> + }
> +
> + gc0308->sd.state_lock = gc0308->hdl.lock;
> + gc0308->pad.flags = MEDIA_PAD_FL_SOURCE;
> + gc0308->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
> + ret = media_entity_pads_init(&gc0308->sd.entity, 1, &gc0308->pad);
> + if (ret < 0)
> + return ret;
> +
> + ret = v4l2_subdev_init_finalize(&gc0308->sd);
> + if (ret)
> + goto fail_media_entity_cleanup;
> +
> + ret = gc0308_power_on(dev);
> + if (ret)
> + goto fail_subdev_cleanup;
> +
> + if (gc0308->clk) {
> + clkrate = clk_get_rate(gc0308->clk);
> + if (clkrate != 24000000)
> + dev_warn(dev, "unexpected clock rate: %lu\n", clkrate);
> + }
> +
> + ret = regmap_read(gc0308->regmap, GC0308_CHIP_ID, &regval);
> + if (ret < 0) {
> + dev_err_probe(dev, ret, "failed to read chip ID\n");
> + goto fail_power_off;
> + }
> +
> + if (regval != 0x9b) {
> + ret = -EINVAL;
> + dev_err_probe(dev, ret, "invalid chip ID (%02x)\n", regval);
> + goto fail_power_off;
> + }
> +
> + ret = v4l2_async_register_subdev(&gc0308->sd);
> + if (ret) {
> + dev_err_probe(dev, ret, "failed to register v4l subdev\n");
> + goto fail_power_off;
> + }
> +
> + pm_runtime_set_active(dev);
> + pm_runtime_enable(dev);
> + pm_runtime_idle(dev);
> +
> + return 0;
> +
> +fail_power_off:
> + gc0308_power_off(dev);
> +fail_subdev_cleanup:
> + v4l2_subdev_cleanup(&gc0308->sd);
> +fail_media_entity_cleanup:
> + media_entity_cleanup(&gc0308->sd.entity);
> + return ret;
> +}
> +
> +static void gc0308_remove(struct i2c_client *client)
> +{
> + struct gc0308 *gc0308 = i2c_get_clientdata(client);
> + struct device *dev = &client->dev;
> +
> + pm_runtime_get_sync(dev);
> +
> + v4l2_async_unregister_subdev(&gc0308->sd);
> + v4l2_ctrl_handler_free(&gc0308->hdl);
> +
> + pm_runtime_disable(dev);
> + pm_runtime_set_suspended(dev);
> + pm_runtime_put_noidle(dev);
> + gc0308_power_off(dev);
> +}
> +
> +static const struct dev_pm_ops gc0308_pm_ops = {
> + SET_RUNTIME_PM_OPS(gc0308_power_off, gc0308_power_on, NULL)
> +};
> +
> +static const struct of_device_id gc0308_of_match[] = {
> + { .compatible = "galaxycore,gc0308" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, gc0308_of_match);
> +
> +static struct i2c_driver gc0308_i2c_driver = {
> + .driver = {
> + .name = "gc0308",
> + .pm = &gc0308_pm_ops,
> + .of_match_table = gc0308_of_match,
> + },
> + .probe = gc0308_probe,
> + .remove = gc0308_remove,
> +};
> +module_i2c_driver(gc0308_i2c_driver);
> +
> +MODULE_DESCRIPTION("GalaxyCore GC0308 Camera Driver");
> +MODULE_AUTHOR("Sebastian Reichel <[email protected]>");
> +MODULE_LICENSE("GPL");

--
Regards,

Sakari Ailus