From c5e0b1b35a8cca17e49ccdb2fbf864ee1a6ec3ab Mon Sep 17 00:00:00 2001 From: Cai YiWei Date: Mon, 16 Jul 2018 19:49:50 +0800 Subject: [PATCH] media: i2c: add gc2155 driver Change-Id: I8c7ab7abf9ca2b3d33b3bdae3593f727d61955dc Signed-off-by: Cai YiWei --- drivers/media/i2c/Kconfig | 9 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/gc2155.c | 1379 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1389 insertions(+) create mode 100644 drivers/media/i2c/gc2155.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 06f0ce851fa1..08d350bc7398 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -713,6 +713,15 @@ config VIDEO_S5C73M3 This is a V4L2 sensor-level driver for Samsung S5C73M3 8 Mpixel camera. +config VIDEO_GC2155 + tristate "GalaxyCore GC2155 sensor support" + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API + help + Support for the GalaxyCore GC2155 sensor. + + To compile this driver as a module, choose M here: the + module will be called gc2155. + comment "Flash devices" config VIDEO_ADP1653 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index f871b6b42aef..2fba2f69de33 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -87,3 +87,4 @@ obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_TC35874X) += tc35874x.o obj-$(CONFIG_VIDEO_OV5647) += ov5647.o obj-$(CONFIG_VIDEO_IMX219) += imx219.o +obj-$(CONFIG_VIDEO_GC2155) += gc2155.o diff --git a/drivers/media/i2c/gc2155.c b/drivers/media/i2c/gc2155.c new file mode 100644 index 000000000000..927c9462ac90 --- /dev/null +++ b/drivers/media/i2c/gc2155.c @@ -0,0 +1,1379 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * gc2155 driver + * + * Copyright (C) 2018 Fuzhou Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_CHIP_ID_H 0xf0 +#define REG_CHIP_ID_L 0xf1 +#define CHIP_ID_H 0x21 +#define CHIP_ID_L 0x55 + +#define REG_NULL 0xFF + +#define GC2155_XVCLK_FREQ 24000000 + +static const char * const gc2155_supply_names[] = { + "avdd", + "dovdd", + "dvdd", +}; + +#define GC2155_NUM_SUPPLIES ARRAY_SIZE(gc2155_supply_names) + +struct regval { + u8 addr; + u8 val; +}; + +struct gc2155_mode { + u32 width; + u32 height; + const struct regval *reg_list; +}; + +struct gc2155 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[GC2155_NUM_SUPPLIES]; + + bool streaming; + struct mutex mutex; /* lock to serialize v4l2 callback */ + struct v4l2_subdev subdev; + struct media_pad pad; + + const struct gc2155_mode *cur_mode; +}; + +#define to_gc2155(sd) container_of(sd, struct gc2155, subdev) + +static struct regval gc2155_global_regs[] = { + {0xfe, 0xf0}, + {0xfe, 0xf0}, + {0xfe, 0xf0}, + {0xfc, 0x06}, + {0xf6, 0x00}, + {0xf7, 0x1d}, + {0xf8, 0x84}, + {0xfa, 0x00}, + {0xf9, 0xfe}, + {0xf2, 0x00}, + /* ISP reg */ + {0xfe, 0x00}, + {0x03, 0x04}, + {0x04, 0xe2}, + {0x09, 0x00}, + {0x0a, 0x00}, + {0x0b, 0x00}, + {0x0c, 0x00}, + {0x0d, 0x04}, + {0x0e, 0xc0}, + {0x0f, 0x06}, + {0x10, 0x50}, + {0x12, 0x2e}, + {0x17, 0x14}, // mirror + {0x18, 0x02}, + {0x19, 0x0e}, + {0x1a, 0x01}, + {0x1b, 0x4b}, + {0x1c, 0x07}, + {0x1d, 0x10}, + {0x1e, 0x98}, + {0x1f, 0x78}, + {0x20, 0x05}, + {0x21, 0x40}, + {0x22, 0xf0}, + {0x24, 0x16}, + {0x25, 0x01}, + {0x26, 0x10}, + {0x2d, 0x40}, + {0x30, 0x01}, + {0x31, 0x90}, + {0x33, 0x04}, + {0x34, 0x01}, + /* ISP reg */ + {0xfe, 0x00}, + {0x80, 0xff}, + {0x81, 0x2c}, + {0x82, 0xfa}, + {0x83, 0x00}, + {0x84, 0x00}, //yuv 01 + {0x85, 0x08}, + {0x86, 0x02}, + {0x89, 0x03}, + {0x8a, 0x00}, + {0x8b, 0x00}, + {0xb0, 0x55}, + {0xc3, 0x11}, //00 + {0xc4, 0x20}, + {0xc5, 0x30}, + {0xc6, 0x38}, + {0xc7, 0x40}, + {0xec, 0x02}, + {0xed, 0x04}, + {0xee, 0x60}, + {0xef, 0x90}, + {0xb6, 0x01}, + {0x90, 0x01}, + {0x91, 0x00}, + {0x92, 0x00}, + {0x93, 0x00}, + {0x94, 0x00}, + {0x95, 0x04}, + {0x96, 0xb0}, + {0x97, 0x06}, + {0x98, 0x40}, + /* BLK */ + {0xfe, 0x00}, + {0x18, 0x02}, + {0x40, 0x42}, + {0x41, 0x00}, + {0x43, 0x5b}, //0x54 + {0x5e, 0x00}, + {0x5f, 0x00}, + {0x60, 0x00}, + {0x61, 0x00}, + {0x62, 0x00}, + {0x63, 0x00}, + {0x64, 0x00}, + {0x65, 0x00}, + {0x66, 0x20}, + {0x67, 0x20}, + {0x68, 0x20}, + {0x69, 0x20}, + {0x6a, 0x08}, + {0x6b, 0x08}, + {0x6c, 0x08}, + {0x6d, 0x08}, + {0x6e, 0x08}, + {0x6f, 0x08}, + {0x70, 0x08}, + {0x71, 0x08}, + {0x72, 0xf0}, + {0x7e, 0x3c}, + {0x7f, 0x00}, + {0xfe, 0x00}, + /* AEC */ + {0xfe, 0x01}, + {0x01, 0x08}, + {0x02, 0xc0}, + {0x03, 0x04}, + {0x04, 0x90}, + {0x05, 0x30}, + {0x06, 0x98}, + {0x07, 0x28}, + {0x08, 0x6c}, + {0x09, 0x00}, + {0x0a, 0xc2}, + {0x0b, 0x11}, + {0x0c, 0x10}, + {0x13, 0x2d}, + {0x17, 0x00}, + {0x1c, 0x11}, + {0x1e, 0x61}, + {0x1f, 0x30}, + {0x20, 0x40}, + {0x22, 0x80}, + {0x23, 0x20}, + + {0x12, 0x35}, + {0x15, 0x50}, + {0x10, 0x31}, + {0x3e, 0x28}, + {0x3f, 0xe0}, + {0x40, 0xe0}, + {0x41, 0x08}, + + {0xfe, 0x02}, + {0x0f, 0x05}, + /* INTPEE */ + {0xfe, 0x02}, + {0x90, 0x6c}, + {0x91, 0x03}, + {0x92, 0xc4}, + {0x97, 0x64}, + {0x98, 0x88}, + {0x9d, 0x08}, + {0xa2, 0x11}, + {0xfe, 0x00}, + /* DNDD */ + {0xfe, 0x02}, + {0x80, 0xc1}, + {0x81, 0x08}, + {0x82, 0x05}, + {0x83, 0x04}, + {0x84, 0x0a}, + {0x86, 0x80}, + {0x87, 0x30}, + {0x88, 0x15}, + {0x89, 0x80}, + {0x8a, 0x60}, + {0x8b, 0x30}, + /* ASDE */ + {0xfe, 0x01}, + {0x21, 0x14}, + {0xfe, 0x02}, + {0x3c, 0x06}, + {0x3d, 0x40}, + {0x48, 0x30}, + {0x49, 0x06}, + {0x4b, 0x08}, + {0x4c, 0x20}, + {0xa3, 0x50}, + {0xa4, 0x30}, + {0xa5, 0x40}, + {0xa6, 0x80}, + {0xab, 0x40}, + {0xae, 0x0c}, + {0xb3, 0x42}, + {0xb4, 0x24}, + {0xb6, 0x50}, + {0xb7, 0x01}, + {0xb9, 0x28}, + {0xfe, 0x00}, + /* gamma1 */ + {0xfe, 0x02}, + {0x10, 0x0d}, + {0x11, 0x12}, + {0x12, 0x17}, + {0x13, 0x1c}, + {0x14, 0x27}, + {0x15, 0x34}, + {0x16, 0x44}, + {0x17, 0x55}, + {0x18, 0x6e}, + {0x19, 0x81}, + {0x1a, 0x91}, + {0x1b, 0x9c}, + {0x1c, 0xaa}, + {0x1d, 0xbb}, + {0x1e, 0xca}, + {0x1f, 0xd5}, + {0x20, 0xe0}, + {0x21, 0xe7}, + {0x22, 0xed}, + {0x23, 0xf6}, + {0x24, 0xfb}, + {0x25, 0xff}, + /* gamma2 */ + {0xfe, 0x02}, + {0x26, 0x0d}, + {0x27, 0x12}, + {0x28, 0x17}, + {0x29, 0x1c}, + {0x2a, 0x27}, + {0x2b, 0x34}, + {0x2c, 0x44}, + {0x2d, 0x55}, + {0x2e, 0x6e}, + {0x2f, 0x81}, + {0x30, 0x91}, + {0x31, 0x9c}, + {0x32, 0xaa}, + {0x33, 0xbb}, + {0x34, 0xca}, + {0x35, 0xd5}, + {0x36, 0xe0}, + {0x37, 0xe7}, + {0x38, 0xed}, + {0x39, 0xf6}, + {0x3a, 0xfb}, + {0x3b, 0xff}, + /* YCP */ + {0xfe, 0x02}, + {0xd1, 0x28}, + {0xd2, 0x28}, + {0xdd, 0x14}, + {0xde, 0x88}, + {0xed, 0x80}, + /* LSC */ + {0xfe, 0x01}, + {0xc2, 0x1f}, + {0xc3, 0x13}, + {0xc4, 0x0e}, + {0xc8, 0x16}, + {0xc9, 0x0f}, + {0xca, 0x0c}, + {0xbc, 0x52}, + {0xbd, 0x2c}, + {0xbe, 0x27}, + {0xb6, 0x47}, + {0xb7, 0x32}, + {0xb8, 0x30}, + {0xc5, 0x00}, + {0xc6, 0x00}, + {0xc7, 0x00}, + {0xcb, 0x00}, + {0xcc, 0x00}, + {0xcd, 0x00}, + {0xbf, 0x0e}, + {0xc0, 0x00}, + {0xc1, 0x00}, + {0xb9, 0x08}, + {0xba, 0x00}, + {0xbb, 0x00}, + {0xaa, 0x0a}, + {0xab, 0x0c}, + {0xac, 0x0d}, + {0xad, 0x02}, + {0xae, 0x06}, + {0xaf, 0x05}, + {0xb0, 0x00}, + {0xb1, 0x05}, + {0xb2, 0x02}, + {0xb3, 0x04}, + {0xb4, 0x04}, + {0xb5, 0x05}, + {0xd0, 0x00}, + {0xd1, 0x00}, + {0xd2, 0x00}, + {0xd6, 0x02}, + {0xd7, 0x00}, + {0xd8, 0x00}, + {0xd9, 0x00}, + {0xda, 0x00}, + {0xdb, 0x00}, + {0xd3, 0x00}, + {0xd4, 0x00}, + {0xd5, 0x00}, + {0xa4, 0x04}, + {0xa5, 0x00}, + {0xa6, 0x77}, + {0xa7, 0x77}, + {0xa8, 0x77}, + {0xa9, 0x77}, + {0xa1, 0x80}, + {0xa2, 0x80}, + + {0xfe, 0x01}, + {0xdc, 0x35}, + {0xdd, 0x28}, + {0xdf, 0x0d}, + {0xe0, 0x70}, + {0xe1, 0x78}, + {0xe2, 0x70}, + {0xe3, 0x78}, + {0xe6, 0x90}, + {0xe7, 0x70}, + {0xe8, 0x90}, + {0xe9, 0x70}, + {0xfe, 0x00}, + /* AWB */ + {0xfe, 0x01}, + {0x4f, 0x00}, + {0x4f, 0x00}, + {0x4b, 0x01}, + {0x4f, 0x00}, + {0x4c, 0x01}, + {0x4d, 0x71}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x91}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x50}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x70}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x90}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xb0}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xd0}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x4f}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x6f}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x8f}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xaf}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xcf}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x6e}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8e}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xae}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xce}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x4d}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x6d}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8d}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xad}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xcd}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x4c}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x6c}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8c}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xac}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xcc}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xec}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x4b}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x6b}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8b}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xab}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8a}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xaa}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xca}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xa9}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xc9}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xcb}, + {0x4e, 0x05}, + {0x4c, 0x01}, + {0x4d, 0xeb}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x0b}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x2b}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x4b}, + {0x4e, 0x05}, + {0x4c, 0x01}, + {0x4d, 0xea}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x0a}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x2a}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x6a}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x29}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x49}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x69}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x89}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0xa9}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0xc9}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x48}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x68}, + {0x4e, 0x06}, + {0x4c, 0x03}, + {0x4d, 0x09}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xa8}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xc8}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xe8}, + {0x4e, 0x07}, + {0x4c, 0x03}, + {0x4d, 0x08}, + {0x4e, 0x07}, + {0x4c, 0x03}, + {0x4d, 0x28}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0x87}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xa7}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xc7}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xe7}, + {0x4e, 0x07}, + {0x4c, 0x03}, + {0x4d, 0x07}, + {0x4e, 0x07}, + {0x4f, 0x01}, + {0xfe, 0x01}, + + {0x50, 0x80}, + {0x51, 0xa8}, + {0x52, 0x57}, + {0x53, 0x38}, + {0x54, 0xc7}, + {0x56, 0x0e}, + {0x58, 0x08}, + {0x5b, 0x00}, + {0x5c, 0x74}, + {0x5d, 0x8b}, + {0x61, 0xd3}, + {0x62, 0x90}, + {0x63, 0xaa}, + {0x65, 0x04}, + {0x67, 0xb2}, + {0x68, 0xac}, + {0x69, 0x00}, + {0x6a, 0xb2}, + {0x6b, 0xac}, + {0x6c, 0xdc}, + {0x6d, 0xb0}, + {0x6e, 0x30}, + {0x6f, 0x40}, + {0x70, 0x05}, + {0x71, 0x80}, + {0x72, 0x80}, + {0x73, 0x30}, + {0x74, 0x01}, + {0x75, 0x01}, + {0x7f, 0x08}, + {0x76, 0x70}, + {0x77, 0x48}, + {0x78, 0xa0}, + {0xfe, 0x00}, + /* CC */ + {0xfe, 0x02}, + {0xc0, 0x01}, + {0xc1, 0x4a}, + {0xc2, 0xf3}, + {0xc3, 0xfc}, + {0xc4, 0xe4}, + {0xc5, 0x48}, + {0xc6, 0xec}, + {0xc7, 0x45}, + {0xc8, 0xf8}, + {0xc9, 0x02}, + {0xca, 0xfe}, + {0xcb, 0x42}, + {0xcc, 0x00}, + {0xcd, 0x45}, + {0xce, 0xf0}, + {0xcf, 0x00}, + {0xe3, 0xf0}, + {0xe4, 0x45}, + {0xe5, 0xe8}, + /* ABS */ + {0xfe, 0x01}, + {0x9f, 0x42}, + {0xfe, 0x00}, + /* frame rate 50Hz */ +#if 1 + {0xfe, 0x00}, + {0x05, 0x01}, + {0x06, 0x56}, + {0x07, 0x00}, + {0x08, 0x32}, + {0xfe, 0x01}, + {0x25, 0x00}, + {0x26, 0xfa}, + {0x27, 0x04}, + {0x28, 0xe2}, //20fps + {0x29, 0x06}, + {0x2a, 0xd6}, //16fps + {0x2b, 0x07}, + {0x2c, 0xd0}, //12fps + {0x2d, 0x0b}, + {0x2e, 0xb8}, //8fps + {0xfe, 0x00}, +#else + /* frame rate 50Hz */ + {0xfe, 0x00}, + {0x05, 0x02}, + {0x06, 0x2d}, + {0x07, 0x00}, + {0x08, 0xa0}, + {0xfe, 0x01}, + {0x25, 0x00}, + {0x26, 0xd4}, + {0x27, 0x04}, + {0x28, 0xf8}, + {0x29, 0x08}, + {0x2a, 0x48}, + {0x2b, 0x0a}, + {0x2c, 0xc4}, + {0x2d, 0x0f}, + {0x2e, 0xbc}, + {0xfe, 0x00}, +#endif + /* SVGA */ + {0xfe, 0x00}, + {0xfa, 0x00}, + {0xfd, 0x01}, + /* crop window */ + {0xfe, 0x00}, + {0x90, 0x01}, + {0x91, 0x00}, + {0x92, 0x00}, + {0x93, 0x00}, + {0x94, 0x00}, + {0x95, 0x02}, + {0x96, 0x58}, + {0x97, 0x03}, + {0x98, 0x20}, + {0x99, 0x11}, + {0x9a, 0x06}, + /* AWB */ + {0xfe, 0x00}, + {0xec, 0x01}, + {0xed, 0x02}, + {0xee, 0x30}, + {0xef, 0x48}, + {0xfe, 0x01}, + {0x74, 0x00}, + /* AEC */ + {0xfe, 0x01}, + {0x01, 0x04}, + {0x02, 0x60}, + {0x03, 0x02}, + {0x04, 0x48}, + {0x05, 0x18}, + {0x06, 0x4c}, + {0x07, 0x14}, + {0x08, 0x36}, + {0x0a, 0xc0}, + {0x21, 0x14}, + {0xfe, 0x00}, + /* gamma */ + {0xfe, 0x00}, + {0xc3, 0x11}, + {0xc4, 0x20}, + {0xc5, 0x30}, + {0xfe, 0x00}, + /* OUTPUT */ + {0xfe, 0x00}, + {0xf2, 0x0f}, + {REG_NULL, 0x0}, +}; + +static struct regval gc2155_800x600_15fps[] = { + {0xfe, 0x00}, + {0xb6, 0x01}, + {0xfa, 0x00}, + {0xfd, 0x01}, + /* window setting */ + {0x09, 0x00}, + {0x0a, 0x00}, + {0x0b, 0x00}, + {0x0c, 0x00}, + {0x0d, 0x04}, + {0x0e, 0xc0}, + {0x0f, 0x06}, + {0x10, 0x50}, + /* crop window */ + {0xfe, 0x00}, + {0x90, 0x01}, + {0x91, 0x00}, + {0x92, 0x00}, + {0x93, 0x00}, + {0x94, 0x00}, + {0x95, 0x02}, + {0x96, 0x58}, + {0x97, 0x03}, + {0x98, 0x20}, + {0x99, 0x11}, + {0x9a, 0x06}, + {0x9b, 0x00}, + {0x9c, 0x00}, + {0x9d, 0x00}, + {0x9e, 0x00}, + {0x9f, 0x00}, + {0xa0, 0x00}, + {0xa1, 0x00}, + {0xa2, 0x00}, + /* AWB */ + {0xfe, 0x00}, + {0xec, 0x01}, + {0xed, 0x02}, + {0xee, 0x30}, + {0xef, 0x48}, + {0xfe, 0x01}, + {0x74, 0x00}, + /* AEC */ + {0xfe, 0x01}, + {0x01, 0x04}, + {0x02, 0x60}, + {0x03, 0x02}, + {0x04, 0x48}, + {0x05, 0x18}, + {0x06, 0x4c}, + {0x07, 0x14}, + {0x08, 0x36}, + {0x0a, 0xc0}, + {0x21, 0x14}, + {0xfe, 0x00}, + /* gamma */ + {0xfe, 0x00}, + {0xc3, 0x11}, + {0xc4, 0x20}, + {0xc5, 0x30}, + {0xfe, 0x00}, + + /* frame rate 50Hz */ +#if 1 + {0xfe, 0x00}, + {0x05, 0x01}, + {0x06, 0x56}, + {0x07, 0x00}, + {0x08, 0x32}, + {0xfe, 0x01}, + {0x25, 0x00}, + {0x26, 0xfa}, + {0x27, 0x04}, + {0x28, 0xe2}, //20fps + {0x29, 0x06}, + {0x2a, 0xd6}, //16fps + {0x2b, 0x07}, + {0x2c, 0xd0}, //12fps + {0x2d, 0x0b}, + {0x2e, 0xb8}, //8fps + {0xfe, 0x00}, +#else + /* frame rate 50Hz */ + {0xfe, 0x00}, + {0x05, 0x02}, + {0x06, 0x2d}, + {0x07, 0x00}, + {0x08, 0xa0}, + {0xfe, 0x01}, + {0x25, 0x00}, + {0x26, 0xd4}, + {0x27, 0x04}, + {0x28, 0xf8}, + {0x29, 0x08}, + {0x2a, 0x48}, + {0x2b, 0x0a}, + {0x2c, 0xc4}, + {0x2d, 0x0f}, + {0x2e, 0xbc}, + {0xfe, 0x00}, +#endif + {REG_NULL, 0x0}, +}; + +static struct regval gc2155_1600x1200_7fps[] = { + {0xfe, 0x00}, + {0xb6, 0x00}, + {0xfa, 0x11}, + {0xfd, 0x00}, + /* crop window */ + {0xfe, 0x00}, + {0x90, 0x01}, + {0x91, 0x00}, + {0x92, 0x00}, + {0x93, 0x00}, + {0x94, 0x00}, + {0x95, 0x04}, + {0x96, 0xb0}, + {0x97, 0x06}, + {0x98, 0x40}, + {0x99, 0x11}, + {0x9a, 0x06}, + + {0x9b, 0x00}, + {0x9c, 0x00}, + {0x9d, 0x00}, + {0x9e, 0x00}, + {0x9f, 0x00}, + {0xa0, 0x00}, + {0xa1, 0x00}, + {0xa2, 0x00}, + /* AWB */ + {0xfe, 0x00}, + {0xec, 0x02}, + {0xed, 0x04}, + {0xee, 0x60}, + {0xef, 0x90}, + {0xfe, 0x01}, + {0x74, 0x01}, + /* AEC */ + {0xfe, 0x01}, + {0x01, 0x08}, + {0x02, 0xc0}, + {0x03, 0x04}, + {0x04, 0x90}, + {0x05, 0x30}, + {0x06, 0x98}, + {0x07, 0x28}, + {0x08, 0x6c}, + {0x0a, 0xc2}, + {0x21, 0x15}, //if 0xfa=11,then 0x21=15;else if 0xfa=00,then 0x21=14 + {0xfe, 0x00}, + /* gamma */ + {0xfe, 0x00}, + {0xc3, 0x00}, //if shutter/2 when capture,then exp_gamma_th/2 + {0xc4, 0x90}, + {0xc5, 0x98}, + {0xfe, 0x00}, + {REG_NULL, 0x0}, +}; + +static const struct gc2155_mode supported_modes[] = { + { + .width = 800, + .height = 600, + .reg_list = gc2155_800x600_15fps, + }, + { + .width = 1600, + .height = 1200, + .reg_list = gc2155_1600x1200_7fps, + }, +}; + +static int gc2155_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, val); + + if (ret < 0) + dev_err(&client->dev, "write reg error: %d\n", ret); + + return ret; +} + +static int gc2155_write_array(struct i2c_client *client, + const struct regval *regs) +{ + int i, ret = 0; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = gc2155_write_reg(client, regs[i].addr, regs[i].val); + + return ret; +} + +static inline u8 gc2155_read_reg(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int gc2155_get_reso_dist(const struct gc2155_mode *mode, + struct v4l2_mbus_framefmt *framefmt) +{ + return abs(mode->width - framefmt->width) + + abs(mode->height - framefmt->height); +} + +static const struct gc2155_mode * +gc2155_find_best_fit(struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt = &fmt->format; + int dist; + int cur_best_fit = 0; + int cur_best_fit_dist = -1; + int i; + + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) { + dist = gc2155_get_reso_dist(&supported_modes[i], framefmt); + if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) { + cur_best_fit_dist = dist; + cur_best_fit = i; + } + } + + return &supported_modes[cur_best_fit]; +} + +static int gc2155_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct gc2155 *gc2155 = to_gc2155(sd); + const struct gc2155_mode *mode; + + mutex_lock(&gc2155->mutex); + + mode = gc2155_find_best_fit(fmt); + fmt->format.code = MEDIA_BUS_FMT_UYVY8_2X8; + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_JPEG; + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; +#else + mutex_unlock(&gc2155->mutex); + return -ENOTTY; +#endif + } else { + gc2155->cur_mode = mode; + } + + mutex_unlock(&gc2155->mutex); + + return 0; +} + +static int gc2155_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct gc2155 *gc2155 = to_gc2155(sd); + const struct gc2155_mode *mode = gc2155->cur_mode; + + mutex_lock(&gc2155->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad); +#else + mutex_unlock(&gc2155->mutex); + return -ENOTTY; +#endif + } else { + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_UYVY8_2X8; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_JPEG; + } + mutex_unlock(&gc2155->mutex); + + return 0; +} + +static int gc2155_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_UYVY8_2X8; + + return 0; +} + +static int gc2155_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + u32 index = fse->index; + + if (index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + fse->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fse->min_width = supported_modes[index].width; + fse->max_width = supported_modes[index].width; + fse->max_height = supported_modes[index].height; + fse->min_height = supported_modes[index].height; + + return 0; +} + +static int __gc2155_power_on(struct gc2155 *gc2155) +{ + int ret; + struct device *dev = &gc2155->client->dev; + + if (!IS_ERR(gc2155->reset_gpio)) + gpiod_set_value_cansleep(gc2155->reset_gpio, 0); + + ret = regulator_bulk_enable(GC2155_NUM_SUPPLIES, gc2155->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + return ret; + } + + if (!IS_ERR(gc2155->xvclk)) { + ret = clk_prepare_enable(gc2155->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + } + + if (!IS_ERR(gc2155->pwdn_gpio)) + gpiod_set_value_cansleep(gc2155->pwdn_gpio, 0); + + if (!IS_ERR(gc2155->reset_gpio)) + gpiod_set_value_cansleep(gc2155->reset_gpio, 1); + + return 0; +} + +static void __gc2155_power_off(struct gc2155 *gc2155) +{ + if (!IS_ERR(gc2155->reset_gpio)) + gpiod_set_value_cansleep(gc2155->reset_gpio, 0); + if (!IS_ERR(gc2155->pwdn_gpio)) + gpiod_set_value_cansleep(gc2155->pwdn_gpio, 1); + + if (!IS_ERR(gc2155->xvclk)) + clk_disable_unprepare(gc2155->xvclk); + + regulator_bulk_disable(GC2155_NUM_SUPPLIES, gc2155->supplies); +} + +static int gc2155_s_stream(struct v4l2_subdev *sd, int on) +{ + struct gc2155 *gc2155 = to_gc2155(sd); + struct i2c_client *client = gc2155->client; + int ret = 0; + + mutex_lock(&gc2155->mutex); + + on = !!on; + if (on == gc2155->streaming) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_get_sync(&gc2155->client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + + ret = gc2155_write_array(gc2155->client, gc2155_global_regs); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + + ret = gc2155_write_array(gc2155->client, + gc2155->cur_mode->reg_list); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + + } else { + pm_runtime_put(&client->dev); + } + + gc2155->streaming = on; + +unlock_and_return: + mutex_unlock(&gc2155->mutex); + + return ret; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int gc2155_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct gc2155 *gc2155 = to_gc2155(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->pad, 0); + const struct gc2155_mode *def_mode = &supported_modes[0]; + + mutex_lock(&gc2155->mutex); + + try_fmt->width = def_mode->width; + try_fmt->height = def_mode->height; + try_fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + try_fmt->field = V4L2_FIELD_NONE; + try_fmt->colorspace = V4L2_COLORSPACE_JPEG; + + mutex_unlock(&gc2155->mutex); + + return 0; +} +#endif + +static int gc2155_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc2155 *gc2155 = to_gc2155(sd); + + return __gc2155_power_on(gc2155); +} + +static int gc2155_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc2155 *gc2155 = to_gc2155(sd); + + __gc2155_power_off(gc2155); + + return 0; +} + +static const struct dev_pm_ops gc2155_pm_ops = { + SET_RUNTIME_PM_OPS(gc2155_runtime_suspend, + gc2155_runtime_resume, NULL) +}; + +static const struct v4l2_subdev_video_ops gc2155_video_ops = { + .s_stream = gc2155_s_stream, +}; + +static const struct v4l2_subdev_pad_ops gc2155_pad_ops = { + .enum_mbus_code = gc2155_enum_mbus_code, + .enum_frame_size = gc2155_enum_frame_sizes, + .get_fmt = gc2155_get_fmt, + .set_fmt = gc2155_set_fmt, +}; + +static const struct v4l2_subdev_ops gc2155_subdev_ops = { + .video = &gc2155_video_ops, + .pad = &gc2155_pad_ops, +}; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_internal_ops gc2155_internal_ops = { + .open = gc2155_open, +}; +#endif + +static int gc2155_check_sensor_id(struct gc2155 *gc2155, + struct i2c_client *client) +{ + struct device *dev = &gc2155->client->dev; + u8 id_h, id_l; + + id_h = gc2155_read_reg(client, REG_CHIP_ID_H); + id_l = gc2155_read_reg(client, REG_CHIP_ID_L); + if (id_h != CHIP_ID_H && id_l != CHIP_ID_L) { + dev_err(dev, "Wrong camera sensor id(0x%02x%02x)\n", + id_h, id_l); + return -EINVAL; + } + + dev_info(dev, "Detected GC2155 (0x%02x%02x) sensor\n", + CHIP_ID_H, CHIP_ID_L); + + return 0; +} + +static int gc2155_configure_regulators(struct gc2155 *gc2155) +{ + u32 i; + + for (i = 0; i < GC2155_NUM_SUPPLIES; i++) + gc2155->supplies[i].supply = gc2155_supply_names[i]; + + return devm_regulator_bulk_get(&gc2155->client->dev, + GC2155_NUM_SUPPLIES, + gc2155->supplies); +} + +static int gc2155_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct gc2155 *gc2155; + int ret; + + gc2155 = devm_kzalloc(dev, sizeof(*gc2155), GFP_KERNEL); + if (!gc2155) + return -ENOMEM; + + gc2155->client = client; + gc2155->cur_mode = &supported_modes[0]; + + gc2155->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(gc2155->xvclk)) { + dev_err(dev, "Failed to get xvclk\n"); + return -EINVAL; + } + ret = clk_set_rate(gc2155->xvclk, GC2155_XVCLK_FREQ); + if (ret < 0) { + dev_err(dev, "Failed to set xvclk rate (24MHz)\n"); + return ret; + } + if (clk_get_rate(gc2155->xvclk) != GC2155_XVCLK_FREQ) + dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n"); + + gc2155->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gc2155->reset_gpio)) + dev_warn(dev, "Failed to get reset-gpios\n"); + + gc2155->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW); + if (IS_ERR(gc2155->pwdn_gpio)) + dev_warn(dev, "Failed to get gc2155-gpios\n"); + + ret = gc2155_configure_regulators(gc2155); + if (ret) { + dev_warn(dev, "Failed to get power regulators\n"); + return ret; + } + + mutex_init(&gc2155->mutex); + v4l2_i2c_subdev_init(&gc2155->subdev, client, &gc2155_subdev_ops); + + ret = __gc2155_power_on(gc2155); + if (ret) + goto err_destroy_mutex; + + ret = gc2155_check_sensor_id(gc2155, client); + if (ret) + goto err_power_off; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + gc2155->subdev.internal_ops = &gc2155_internal_ops; + gc2155->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; +#endif +#if defined(CONFIG_MEDIA_CONTROLLER) + gc2155->pad.flags = MEDIA_PAD_FL_SOURCE; + gc2155->subdev.entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; + ret = media_entity_init(&gc2155->subdev.entity, 1, &gc2155->pad, 0); + if (ret < 0) + goto err_power_off; +#endif + + ret = v4l2_async_register_subdev(&gc2155->subdev); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&gc2155->subdev.entity); +#endif +err_power_off: + __gc2155_power_off(gc2155); +err_destroy_mutex: + mutex_destroy(&gc2155->mutex); + + return ret; +} + +static int gc2155_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct gc2155 *gc2155 = to_gc2155(sd); + + v4l2_async_unregister_subdev(sd); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif + mutex_destroy(&gc2155->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + __gc2155_power_off(gc2155); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id gc2155_of_match[] = { + { .compatible = "gc,gc2155" }, + {}, +}; +MODULE_DEVICE_TABLE(of, gc2155_of_match); +#endif + +static const struct i2c_device_id gc2155_match_id[] = { + {"gc2155", 0}, + {}, +}; + +static struct i2c_driver gc2155_i2c_driver = { + .driver = { + .name = "gc2155", + .pm = &gc2155_pm_ops, + .of_match_table = of_match_ptr(gc2155_of_match), + }, + .probe = gc2155_probe, + .remove = gc2155_remove, + .id_table = gc2155_match_id, +}; + +static int __init sensor_mod_init(void) +{ + return i2c_add_driver(&gc2155_i2c_driver); +} + +static void __exit sensor_mod_exit(void) +{ + i2c_del_driver(&gc2155_i2c_driver); +} + +device_initcall_sync(sensor_mod_init); +module_exit(sensor_mod_exit); + +MODULE_DESCRIPTION("GalaxyCore gc2155 sensor driver"); +MODULE_LICENSE("GPL v2");