diff --git a/Documentation/devicetree/bindings/media/i2c/techpoint.txt b/Documentation/devicetree/bindings/media/i2c/techpoint.txt new file mode 100644 index 000000000000..05f63ae3576b --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/techpoint.txt @@ -0,0 +1,75 @@ +* techpoint,tpxxxx Decoder + +Required Properties: +- compatible: should be "techpoint,tpxxxx" +- clocks: reference to the 27M xvclk input clock. +- clock-names: should be "xvclk". +- dovdd-supply: Digital I/O voltage supply, 1.8 volts +- avdd-supply: Analog voltage supply, 3.3 volts +- dvdd-supply: Digital core voltage supply, 1.2 volts +- reset-gpios: Low active reset gpio +- pwdn-gpios: High active power-down gpio + +Attention: +techpoint device address (7bit) +techpoint + evb reuse the power bus + +Example: +&i2c1 { + status = "okay"; + + // BT1120 + tp9930: tp9930@45 { + compatible = "techpoint,tp9930"; + reg = <0x45>; + clocks = <&cru CLK_MIPICSI_OUT>; + clock-names = "xvclk"; + avdd-supply = <&vcc_avdd>; + dovdd-supply = <&vcc_dovdd>; + dvdd-supply = <&vcc_dvdd>; + power-domains = <&power RV1126_PD_VI>; + pinctrl-names = "rockchip,camera_default"; + pinctrl-0 = <&mipicsi_clk0 &cifm0_bt1120_ctl>; + rockchip,grf = <&grf>; + reset-gpios = <&gpio3 RK_PC4 GPIO_ACTIVE_LOW>; + power-gpios = <&gpio2 RK_PA2 GPIO_ACTIVE_HIGH>; + rockchip,camera-module-index = <0>; + rockchip,camera-module-facing = "back"; + rockchip,camera-module-name = "default"; + rockchip,camera-module-lens-name = "default"; + port { + cam_para_out1: endpoint { + remote-endpoint = <&cif_para_in>; + bus-width = <16>; + pclk-sample = <1>; + }; + }; + }; + + // MIPI + tp2855: tp2855@44 { + compatible = "techpoint,tp2855"; + reg = <0x44>; + clocks = <&cru CLK_MIPICSI_OUT>; + clock-names = "xvclk"; + avdd-supply = <&vcc_avdd>; + dovdd-supply = <&vcc_dovdd>; + dvdd-supply = <&vcc_dvdd>; + power-domains = <&power RV1126_PD_VI>; + pinctrl-names = "rockchip,camera_default"; + pinctrl-0 = <&mipicsi_clk0>; + reset-gpios = <&gpio3 RK_PC4 GPIO_ACTIVE_LOW>; + power-gpios = <&gpio2 RK_PA2 GPIO_ACTIVE_HIGH>; + rockchip,camera-module-index = <1>; + rockchip,camera-module-facing = "front"; + rockchip,camera-module-name = "tp2855"; + rockchip,camera-module-lens-name = "tp2855"; + + port { + ucam_out0: endpoint { + remote-endpoint = <&mipi_in_ucam0>; + data-lanes = <1 2 3 4>; + }; + }; + }; +}; diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index db4716cc2178..52dafe941b14 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -438,16 +438,16 @@ config VIDEO_RK628_CSI To compile this driver as a module, choose M here: the module will be called rk628-csi. -config VIDEO_TP2855 - tristate "TechPoint TP2855 sensor support" +config VIDEO_TECHPOINT + tristate "TechPoint decoder" depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on MEDIA_CAMERA_SUPPORT help - This is a Video4Linux2 sensor driver for the TechPoint - TP2855 camera. + Support for the TechPoint Multichannel digital decode to + MIPI CSI-2 bridge. To compile this driver as a module, choose M here: the - module will be called tp2855. + module will be called TechPoint. config VIDEO_TVP514X tristate "Texas Instruments TVP514x video decoder" diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 6a9307226171..2d4946c3eb97 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -50,7 +50,7 @@ obj-$(CONFIG_VIDEO_BT866) += bt866.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o obj-$(CONFIG_VIDEO_THS7303) += ths7303.o obj-$(CONFIG_VIDEO_THS8200) += ths8200.o -obj-$(CONFIG_VIDEO_TP2855) += tp2855.o +obj-$(CONFIG_VIDEO_TECHPOINT) += techpoint/ obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o obj-$(CONFIG_VIDEO_TVP514X) += tvp514x.o obj-$(CONFIG_VIDEO_TVP7002) += tvp7002.o diff --git a/drivers/media/i2c/techpoint/Makefile b/drivers/media/i2c/techpoint/Makefile new file mode 100644 index 000000000000..9749e0688d2b --- /dev/null +++ b/drivers/media/i2c/techpoint/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +techpoint-objs += techpoint_v4l2.o techpoint_dev.o \ + techpoint_tp9930.o techpoint_tp2855.o +obj-$(CONFIG_VIDEO_TECHPOINT) += techpoint.o diff --git a/drivers/media/i2c/techpoint/techpoint_common.h b/drivers/media/i2c/techpoint/techpoint_common.h new file mode 100644 index 000000000000..f6bbc9a5fe0f --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_common.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip CIF Driver + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + */ + +#ifndef _TECHPOINT_COMMON_H +#define _TECHPOINT_COMMON_H + +// #define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x0) + +#define TECHPOINT_CAMERA_XVCLK_FREQ "xvclk_freq" +#define TECHPOINT_CHANNEL_NUMS "channel_nums" + +#define TECHPOINT_TEST_PATTERN 0 +#define DEF_1080P 1 +#define TECHPOINT_SHARING_POWER 1 + +#define INPUT_STATUS_REG 0x01 +#define PAGE_REG 0x40 +#define CHIP_ID_H_REG 0xFE +#define CHIP_ID_L_REG 0xFF + +#define INPUT_STATUS_MASK 0x80 + +enum techpoint_chips { + CHIP_UNKNOWN = 0, + CHIP_TP2855, + CHIP_TP2815, + CHIP_TP9930, +}; + +enum techpoint_input_type { + TECHPOINT_MIPI = 0, + TECHPOINT_DVP_BT1120, + TECHPOINT_DVP_BT565, +}; + +enum techpoint_support_reso { + TECHPOINT_S_RESO_720P_25 = 0, + TECHPOINT_S_RESO_1080P_25, + TECHPOINT_S_RESO_720P_30, + TECHPOINT_S_RESO_1080P_30, + TECHPOINT_S_RESO_NUMS, +}; + +enum techpoint_max_pad { + PAD0, + PAD1, + PAD2, + PAD3, + PAD_MAX, +}; + +struct techpoint_video_modes { + u32 bus_fmt; + u32 width; + u32 height; + struct v4l2_fract max_fps; + u32 bpp; + u32 lane; + s64 link_freq_value; + u32 vc[PAD_MAX]; + const struct regval *common_reg_list; + int common_reg_size; + enum techpoint_support_reso channel_reso[PAD_MAX]; +}; + +struct techpoint { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *power_gpio; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_sleep; + struct pinctrl_state *pins_default; + struct media_pad pad[PAD_MAX]; + struct v4l2_subdev subdev; + struct v4l2_ctrl *pixel_rate_ctrl; + struct v4l2_ctrl *link_freq_ctrl; + struct v4l2_ctrl_handler ctrl_handler; + struct mutex mutex; + struct regulator_bulk_data *supplies; + u32 xvclk_freq_value; + u32 channel_nums; + + enum techpoint_chips chip_id; + struct techpoint_video_modes *video_modes; + const struct techpoint_video_modes *cur_video_mode; + u32 video_modes_num; + enum techpoint_input_type input_type; + + u32 module_index; + const char *module_facing; + const char *module_name; + const char *len_name; + bool power_on; + bool streaming; + bool do_reset; + + u8 detect_status[PAD_MAX]; + struct task_struct *detect_thread; +}; + +#endif // _TECHPOINT_COMMON_H diff --git a/drivers/media/i2c/techpoint/techpoint_dev.c b/drivers/media/i2c/techpoint/techpoint_dev.c new file mode 100644 index 000000000000..2353247cec77 --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_dev.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * techpoint dev driver + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + * + * V0.0X01.0X00 first version. + */ + +#include "techpoint_dev.h" +#include "techpoint_tp9930.h" +#include "techpoint_tp2855.h" +#include "techpoint_tp2815.h" + +static struct semaphore reg_sem; + +int techpoint_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + struct i2c_msg msg; + u8 buf[2]; + int ret; + + buf[0] = reg & 0xFF; + buf[1] = val; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = sizeof(buf); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret >= 0) { + usleep_range(300, 400); + return 0; + } + + dev_err(&client->dev, + "techpoint write reg(0x%x val:0x%x) failed !\n", reg, val); + + return ret; +} + +int techpoint_write_array(struct i2c_client *client, + const struct regval *regs, int size) +{ + int i, ret = 0; + + i = 0; + + while (i < size) { + ret = techpoint_write_reg(client, regs[i].addr, regs[i].val); + if (ret) { + dev_err(&client->dev, "%s failed !\n", __func__); + break; + } + i++; + } + + return ret; +} + +int techpoint_read_reg(struct i2c_client *client, u8 reg, u8 *val) +{ + struct i2c_msg msg[2]; + u8 buf[1]; + int ret; + + buf[0] = reg & 0xFF; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = buf; + msg[1].len = 1; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret >= 0) { + *val = buf[0]; + return 0; + } + + dev_err(&client->dev, "techpoint read reg(0x%x) failed !\n", reg); + + return ret; +} + +static int check_chip_id(struct techpoint *techpoint) +{ + struct i2c_client *client = techpoint->client; + struct device *dev = &client->dev; + unsigned char chip_id_h = 0xFF, chip_id_l = 0xFF; + + techpoint_read_reg(client, CHIP_ID_H_REG, &chip_id_h); + techpoint_read_reg(client, CHIP_ID_L_REG, &chip_id_l); + dev_err(dev, "chip_id_h:0x%2x chip_id_l:0x%2x\n", chip_id_h, chip_id_l); + if (chip_id_h == TP9930_CHIP_ID_H_VALUE && + chip_id_l == TP9930_CHIP_ID_L_VALUE) { + dev_info(&client->dev, + "techpoint check chip id CHIP_TP9930 !\n"); + techpoint->chip_id = CHIP_TP9930; + techpoint->input_type = TECHPOINT_DVP_BT1120; + return 0; + } else if (chip_id_h == TP2855_CHIP_ID_H_VALUE && + chip_id_l == TP2855_CHIP_ID_L_VALUE) { + dev_info(&client->dev, + "techpoint check chip id CHIP_TP2855 !\n"); + techpoint->chip_id = CHIP_TP2855; + techpoint->input_type = TECHPOINT_MIPI; + return 0; + } else if (chip_id_h == TP2815_CHIP_ID_H_VALUE && + chip_id_l == TP2815_CHIP_ID_L_VALUE) { + dev_info(&client->dev, + "techpoint check chip id CHIP_TP2815 !\n"); + techpoint->chip_id = CHIP_TP2855; + techpoint->input_type = TECHPOINT_MIPI; + return 0; + } else { + dev_info(&client->dev, "techpoint check chip id failed !\n"); + } + + return -1; +} + +int techpoint_initialize_devices(struct techpoint *techpoint) +{ + if (check_chip_id(techpoint)) + return -1; + + if (techpoint->chip_id == CHIP_TP9930) { + tp9930_initialize(techpoint); + } else if (techpoint->chip_id == CHIP_TP2855) { + tp2855_initialize(techpoint); + } + + sema_init(®_sem, 1); + + return 0; +} + +static int detect_thread_function(void *data) +{ + struct techpoint *techpoint = (struct techpoint *)data; + struct i2c_client *client = techpoint->client; + u8 detect_status = 0, i; + int need_reset_wait = -1; + + if (techpoint->power_on) { + down(®_sem); + if (techpoint->chip_id == CHIP_TP9930) { + tp9930_get_all_input_status(client, + techpoint->detect_status); + for (i = 0; i < PAD_MAX; i++) + tp9930_set_decoder_mode(client, i, + techpoint->detect_status[i]); + } else if (techpoint->chip_id == CHIP_TP2855) + tp2855_get_all_input_status(client, + techpoint->detect_status); + up(®_sem); + techpoint->do_reset = 0; + } + + while (!kthread_should_stop()) { + down(®_sem); + if (techpoint->power_on) { + for (i = 0; i < PAD_MAX; i++) { + if (techpoint->chip_id == CHIP_TP9930) + detect_status = + tp9930_get_channel_input_status + (client, i); + else if (techpoint->chip_id == CHIP_TP2855) + detect_status = + tp2855_get_channel_input_status + (client, i); + + if (techpoint->detect_status[i] != + detect_status) { + if (!detect_status) + dev_err(&client->dev, + "detect channel %d video plug out\n", + i); + else + dev_err(&client->dev, + "detect channel %d video plug in\n", + i); + + if (techpoint->chip_id == CHIP_TP9930) + tp9930_set_decoder_mode(client, + i, + detect_status); + + techpoint->detect_status[i] = + detect_status; + need_reset_wait = 5; + } + } + if (need_reset_wait > 0) { + need_reset_wait--; + } else if (need_reset_wait == 0) { + need_reset_wait = -1; + techpoint->do_reset = 1; + dev_err(&client->dev, + "trigger reset time up\n"); + } + } + up(®_sem); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(200)); + } + return 0; +} + +static int __maybe_unused detect_thread_start(struct techpoint *techpoint) +{ + int ret = 0; + struct i2c_client *client = techpoint->client; + + techpoint->detect_thread = kthread_create(detect_thread_function, + techpoint, + "techpoint_kthread"); + if (IS_ERR(techpoint->detect_thread)) { + dev_err(&client->dev, + "kthread_create techpoint_kthread failed\n"); + ret = PTR_ERR(techpoint->detect_thread); + techpoint->detect_thread = NULL; + return ret; + } + wake_up_process(techpoint->detect_thread); + return ret; +} + +static int __maybe_unused detect_thread_stop(struct techpoint *techpoint) +{ + if (techpoint->detect_thread) + kthread_stop(techpoint->detect_thread); + techpoint->detect_thread = NULL; + return 0; +} + +static __maybe_unused int auto_detect_channel_fmt(struct techpoint *techpoint) +{ + int ch = 0; + enum techpoint_support_reso reso = 0xff; + struct i2c_client *client = techpoint->client; + + down(®_sem); + + for (ch = 0; ch < PAD_MAX; ch++) { + if (techpoint->chip_id == CHIP_TP9930) { + reso = tp9930_get_channel_reso(client, ch); + tp9930_set_channel_reso(client, ch, reso); + } else if (techpoint->chip_id == CHIP_TP2855) { + reso = tp2855_get_channel_reso(client, ch); + tp2855_set_channel_reso(client, ch, reso); + } + } + + up(®_sem); + + return 0; +} + +void __techpoint_get_vc_fmt_inf(struct techpoint *techpoint, + struct rkmodule_vc_fmt_info *inf) +{ + int ch = 0; + int val = 0; + enum techpoint_support_reso reso = 0xff; + struct i2c_client *client = techpoint->client; + + down(®_sem); + + for (ch = 0; ch < PAD_MAX; ch++) { + if (techpoint->chip_id == CHIP_TP9930) { + reso = tp9930_get_channel_reso(client, ch); + } else if (techpoint->chip_id == CHIP_TP2855) { + reso = tp2855_get_channel_reso(client, ch); + } + val = reso; + switch (val) { + case TECHPOINT_S_RESO_1080P_30: + inf->width[ch] = 1920; + inf->height[ch] = 1080; + inf->fps[ch] = 30; + break; + case TECHPOINT_S_RESO_1080P_25: + inf->width[ch] = 1920; + inf->height[ch] = 1080; + inf->fps[ch] = 25; + break; + case TECHPOINT_S_RESO_720P_30: + inf->width[ch] = 1280; + inf->height[ch] = 720; + inf->fps[ch] = 30; + break; + case TECHPOINT_S_RESO_720P_25: + inf->width[ch] = 1280; + inf->height[ch] = 720; + inf->fps[ch] = 25; + break; + default: +#if DEF_1080P + inf->width[ch] = 1920; + inf->height[ch] = 1080; + inf->fps[ch] = 25; +#else + inf->width[ch] = 1280; + inf->height[ch] = 720; + inf->fps[ch] = 25; +#endif + break; + } + } + + up(®_sem); +} + +void techpoint_get_vc_fmt_inf(struct techpoint *techpoint, + struct rkmodule_vc_fmt_info *inf) +{ + down(®_sem); + + if (techpoint->chip_id == CHIP_TP9930) { + tp9930_pll_reset(techpoint->client); + } + + techpoint_write_array(techpoint->client, + techpoint->cur_video_mode->common_reg_list, + techpoint->cur_video_mode->common_reg_size); + + if (techpoint->chip_id == CHIP_TP9930) { + tp9930_do_reset_pll(techpoint->client); + } + + up(®_sem); + + __techpoint_get_vc_fmt_inf(techpoint, inf); +} + +void techpoint_get_vc_hotplug_inf(struct techpoint *techpoint, + struct rkmodule_vc_hotplug_info *inf) +{ + int ch = 0; + int detect_status = 0; + struct i2c_client *client = techpoint->client; + + memset(inf, 0, sizeof(*inf)); + + down(®_sem); + + for (ch = 0; ch < 4; ch++) { + if (techpoint->chip_id == CHIP_TP9930) + detect_status = + tp9930_get_channel_input_status(client, ch); + else if (techpoint->chip_id == CHIP_TP2855) + detect_status = + tp2855_get_channel_input_status(client, ch); + + inf->detect_status |= detect_status << ch; + } + + up(®_sem); +} + +int techpoint_start_video_stream(struct techpoint *techpoint) +{ + int ret = 0; + struct i2c_client *client = techpoint->client; + + down(®_sem); + if (techpoint->chip_id == CHIP_TP9930) { + tp9930_pll_reset(techpoint->client); + } + up(®_sem); + + ret = techpoint_write_array(techpoint->client, + techpoint->cur_video_mode->common_reg_list, + techpoint->cur_video_mode->common_reg_size); + if (ret) { + dev_err(&client->dev, + "techpoint_start_video_stream common_reg_list failed"); + return ret; + } + + down(®_sem); + if (techpoint->chip_id == CHIP_TP9930) { + tp9930_do_reset_pll(techpoint->client); + } + up(®_sem); + + usleep_range(500 * 1000, 1000 * 1000); + auto_detect_channel_fmt(techpoint); + + detect_thread_start(techpoint); + + return 0; +} + +int techpoint_stop_video_stream(struct techpoint *techpoint) +{ + detect_thread_stop(techpoint); + + if (techpoint->chip_id == CHIP_TP9930) { + + } else if (techpoint->chip_id == CHIP_TP2855) { + + } + return 0; +} diff --git a/drivers/media/i2c/techpoint/techpoint_dev.h b/drivers/media/i2c/techpoint/techpoint_dev.h new file mode 100644 index 000000000000..fa9aada6d9dd --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_dev.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip CIF Driver + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + */ + +#ifndef _TECHPOINT_DEV_H +#define _TECHPOINT_DEV_H + +#include "techpoint_common.h" + +struct regval { + u8 addr; + u8 val; +}; + +int techpoint_write_reg(struct i2c_client *client, u8 reg, u8 val); +int techpoint_read_reg(struct i2c_client *client, u8 reg, u8 *val); +int techpoint_write_array(struct i2c_client *client, + const struct regval *regs, int size); + +void __techpoint_get_vc_fmt_inf(struct techpoint *techpoint, + struct rkmodule_vc_fmt_info *inf); +void techpoint_get_vc_fmt_inf(struct techpoint *techpoint, + struct rkmodule_vc_fmt_info *inf); +void techpoint_get_vc_hotplug_inf(struct techpoint *techpoint, + struct rkmodule_vc_hotplug_info *inf); + +int techpoint_initialize_devices(struct techpoint *techpoint); +int techpoint_start_video_stream(struct techpoint *techpoint); +int techpoint_stop_video_stream(struct techpoint *techpoint); + +#endif // _TECHPOINT_DEV_H diff --git a/drivers/media/i2c/techpoint/techpoint_tp2815.h b/drivers/media/i2c/techpoint/techpoint_tp2815.h new file mode 100644 index 000000000000..b5b6ea74aa77 --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_tp2815.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * techpoint tp9930 regs + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + */ + +#ifndef _TECHPOINT_TP2815_H +#define _TECHPOINT_TP2815_H + +#define TP2815_CHIP_ID_H_REG 0xFE +#define TP2815_CHIP_ID_H_VALUE 0x28 +#define TP2815_CHIP_ID_L_REG 0xFF +#define TP2815_CHIP_ID_L_VALUE 0x15 + +#endif // _TECHPOINT_TP9930_H diff --git a/drivers/media/i2c/techpoint/techpoint_tp2855.c b/drivers/media/i2c/techpoint/techpoint_tp2855.c new file mode 100644 index 000000000000..5723bd0ec658 --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_tp2855.c @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * techpoint techpoint lib + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + */ + +#include "techpoint_tp2855.h" +#include "techpoint_dev.h" + +static __maybe_unused const struct regval common_setting_297M_720p_regs[] = { + { 0x40, 0x08 }, + { 0x01, 0xf0 }, + { 0x02, 0x01 }, + { 0x08, 0x0f }, + { 0x20, 0x44 }, + { 0x34, 0xe4 }, + { 0x14, 0x44 }, + { 0x15, 0x0d }, + { 0x25, 0x04 }, + { 0x26, 0x03 }, + { 0x27, 0x09 }, + { 0x29, 0x02 }, + { 0x33, 0x07 }, + { 0x33, 0x00 }, + { 0x14, 0xc4 }, + { 0x14, 0x44 }, + // {0x23, 0x02}, //vi test ok + // {0x23, 0x00}, +}; + +static __maybe_unused const struct regval common_setting_594M_1080p_regs[] = { + { 0x40, 0x08 }, + { 0x01, 0xf0 }, + { 0x02, 0x01 }, + { 0x08, 0x0f }, + { 0x20, 0x44 }, + { 0x34, 0xe4 }, + { 0x15, 0x0C }, + { 0x25, 0x08 }, + { 0x26, 0x06 }, + { 0x27, 0x11 }, + { 0x29, 0x0a }, + { 0x33, 0x07 }, + { 0x33, 0x00 }, + { 0x14, 0x33 }, + { 0x14, 0xb3 }, + { 0x14, 0x33 }, + // {0x23, 0x02}, //vi test ok + // {0x23, 0x00}, +}; + +static struct techpoint_video_modes supported_modes[] = { + { + .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080, + .max_fps = { + .numerator = 10000, + .denominator = 250000, + }, + .link_freq_value = TP2855_LINK_FREQ_594M, + .common_reg_list = common_setting_594M_1080p_regs, + .common_reg_size = ARRAY_SIZE(common_setting_594M_1080p_regs), + .bpp = 8, + .lane = 4, + .vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_0, + .vc[PAD1] = V4L2_MBUS_CSI2_CHANNEL_1, + .vc[PAD2] = V4L2_MBUS_CSI2_CHANNEL_2, + .vc[PAD3] = V4L2_MBUS_CSI2_CHANNEL_3, + }, + { + .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1280, + .height = 720, + .max_fps = { + .numerator = 10000, + .denominator = 250000, + }, + .link_freq_value = TP2855_LINK_FREQ_297M, + .common_reg_list = common_setting_297M_720p_regs, + .common_reg_size = ARRAY_SIZE(common_setting_297M_720p_regs), + .bpp = 8, + .lane = 4, + .vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_0, + .vc[PAD1] = V4L2_MBUS_CSI2_CHANNEL_1, + .vc[PAD2] = V4L2_MBUS_CSI2_CHANNEL_2, + .vc[PAD3] = V4L2_MBUS_CSI2_CHANNEL_3, + }, +}; + +int tp2855_initialize(struct techpoint *techpoint) +{ + int array_size = 0; + struct i2c_client *client = techpoint->client; + struct device *dev = &client->dev; + + techpoint->video_modes_num = ARRAY_SIZE(supported_modes); + array_size = + sizeof(struct techpoint_video_modes) * techpoint->video_modes_num; + techpoint->video_modes = devm_kzalloc(dev, array_size, GFP_KERNEL); + memcpy(techpoint->video_modes, supported_modes, array_size); + + techpoint->cur_video_mode = &techpoint->video_modes[0]; + + return 0; +} + +int tp2855_get_channel_input_status(struct i2c_client *client, u8 ch) +{ + u8 val = 0; + + techpoint_write_reg(client, PAGE_REG, ch); + techpoint_read_reg(client, INPUT_STATUS_REG, &val); + dev_dbg(&client->dev, "input_status ch %d : %x\n", ch, val); + + return (val & INPUT_STATUS_MASK) ? 0 : 1; +} + +int tp2855_get_all_input_status(struct i2c_client *client, u8 *detect_status) +{ + u8 val = 0, i; + + for (i = 0; i < PAD_MAX; i++) { + techpoint_write_reg(client, PAGE_REG, i); + techpoint_read_reg(client, INPUT_STATUS_REG, &val); + detect_status[i] = tp2855_get_channel_input_status(client, i); + } + + return 0; +} + +int tp2855_set_channel_reso(struct i2c_client *client, int ch, + enum techpoint_support_reso reso) +{ + int val = reso; + u8 tmp; + const unsigned char SYS_MODE[5] = { 0x01, 0x02, 0x04, 0x08, 0x0f }; + + techpoint_write_reg(client, 0x40, ch); + + switch (val) { + case TECHPOINT_S_RESO_1080P_30: + dev_err(&client->dev, "set channel %d 1080P_30, TBD\n", ch); + techpoint_read_reg(client, 0xf5, &tmp); + tmp &= ~SYS_MODE[ch]; + techpoint_write_reg(client, 0xf5, tmp); + techpoint_write_reg(client, 0x02, 0x40); + techpoint_write_reg(client, 0x07, 0xc0); + techpoint_write_reg(client, 0x0b, 0xc0); + techpoint_write_reg(client, 0x0c, 0x03); + techpoint_write_reg(client, 0x0d, 0x50); + techpoint_write_reg(client, 0x15, 0x03); + techpoint_write_reg(client, 0x16, 0xd2); + techpoint_write_reg(client, 0x17, 0x80); + techpoint_write_reg(client, 0x18, 0x29); + techpoint_write_reg(client, 0x19, 0x38); + techpoint_write_reg(client, 0x1a, 0x47); + techpoint_write_reg(client, 0x1c, 0x08); + techpoint_write_reg(client, 0x1d, 0x98); + techpoint_write_reg(client, 0x20, 0x30); + techpoint_write_reg(client, 0x21, 0x84); + techpoint_write_reg(client, 0x22, 0x36); + techpoint_write_reg(client, 0x23, 0x3c); + techpoint_write_reg(client, 0x2b, 0x60); + techpoint_write_reg(client, 0x2c, 0x0a); + techpoint_write_reg(client, 0x2d, 0x30); + techpoint_write_reg(client, 0x2e, 0x70); + techpoint_write_reg(client, 0x30, 0x48); + techpoint_write_reg(client, 0x31, 0xbb); + techpoint_write_reg(client, 0x32, 0x2e); + techpoint_write_reg(client, 0x33, 0x90); + techpoint_write_reg(client, 0x35, 0x05); + techpoint_write_reg(client, 0x38, 0x00); + techpoint_write_reg(client, 0x39, 0x1C); + //def ahd config + techpoint_write_reg(client, 0x02, 0x44); + techpoint_write_reg(client, 0x0d, 0x72); + techpoint_write_reg(client, 0x15, 0x01); + techpoint_write_reg(client, 0x16, 0xf0); + techpoint_write_reg(client, 0x20, 0x38); + techpoint_write_reg(client, 0x21, 0x46); + techpoint_write_reg(client, 0x25, 0xfe); + techpoint_write_reg(client, 0x26, 0x0d); + techpoint_write_reg(client, 0x2c, 0x3a); + techpoint_write_reg(client, 0x2d, 0x54); + techpoint_write_reg(client, 0x2e, 0x40); + techpoint_write_reg(client, 0x30, 0xa5); + techpoint_write_reg(client, 0x31, 0x95); + techpoint_write_reg(client, 0x32, 0xe0); + techpoint_write_reg(client, 0x33, 0x60); + break; + case TECHPOINT_S_RESO_1080P_25: + dev_err(&client->dev, "set channel %d 1080P_25\n", ch); + techpoint_read_reg(client, 0xf5, &tmp); + tmp &= ~SYS_MODE[ch]; + techpoint_write_reg(client, 0xf5, tmp); + techpoint_write_reg(client, 0x02, 0x40); + techpoint_write_reg(client, 0x07, 0xc0); + techpoint_write_reg(client, 0x0b, 0xc0); + techpoint_write_reg(client, 0x0c, 0x03); + techpoint_write_reg(client, 0x0d, 0x50); + techpoint_write_reg(client, 0x15, 0x03); + techpoint_write_reg(client, 0x16, 0xd2); + techpoint_write_reg(client, 0x17, 0x80); + techpoint_write_reg(client, 0x18, 0x29); + techpoint_write_reg(client, 0x19, 0x38); + techpoint_write_reg(client, 0x1a, 0x47); + techpoint_write_reg(client, 0x1c, 0x0a); + techpoint_write_reg(client, 0x1d, 0x50); + techpoint_write_reg(client, 0x20, 0x30); + techpoint_write_reg(client, 0x21, 0x84); + techpoint_write_reg(client, 0x22, 0x36); + techpoint_write_reg(client, 0x23, 0x3c); + techpoint_write_reg(client, 0x2b, 0x60); + techpoint_write_reg(client, 0x2c, 0x0a); + techpoint_write_reg(client, 0x2d, 0x30); + techpoint_write_reg(client, 0x2e, 0x70); + techpoint_write_reg(client, 0x30, 0x48); + techpoint_write_reg(client, 0x31, 0xbb); + techpoint_write_reg(client, 0x32, 0x2e); + techpoint_write_reg(client, 0x33, 0x90); + techpoint_write_reg(client, 0x35, 0x05); + techpoint_write_reg(client, 0x38, 0x00); + techpoint_write_reg(client, 0x39, 0x1C); + //def ahd config + techpoint_write_reg(client, 0x02, 0x44); + techpoint_write_reg(client, 0x0d, 0x73); + techpoint_write_reg(client, 0x15, 0x01); + techpoint_write_reg(client, 0x16, 0xf0); + techpoint_write_reg(client, 0x20, 0x3c); + techpoint_write_reg(client, 0x21, 0x46); + techpoint_write_reg(client, 0x25, 0xfe); + techpoint_write_reg(client, 0x26, 0x0d); + techpoint_write_reg(client, 0x2c, 0x3a); + techpoint_write_reg(client, 0x2d, 0x54); + techpoint_write_reg(client, 0x2e, 0x40); + techpoint_write_reg(client, 0x30, 0xa5); + techpoint_write_reg(client, 0x31, 0x86); + techpoint_write_reg(client, 0x32, 0xfb); + techpoint_write_reg(client, 0x33, 0x60); + break; + case TECHPOINT_S_RESO_720P_30: + dev_err(&client->dev, "set channel %d 720P_30\n", ch); + techpoint_read_reg(client, 0xf5, &tmp); + tmp |= SYS_MODE[ch]; + techpoint_write_reg(client, 0xf5, tmp); + techpoint_write_reg(client, 0x02, 0x42); + techpoint_write_reg(client, 0x07, 0xc0); + techpoint_write_reg(client, 0x0b, 0xc0); + techpoint_write_reg(client, 0x0c, 0x13); + techpoint_write_reg(client, 0x0d, 0x50); + techpoint_write_reg(client, 0x15, 0x13); + techpoint_write_reg(client, 0x16, 0x15); + techpoint_write_reg(client, 0x17, 0x00); + techpoint_write_reg(client, 0x18, 0x19); + techpoint_write_reg(client, 0x19, 0xd0); + techpoint_write_reg(client, 0x1a, 0x25); + techpoint_write_reg(client, 0x1c, 0x06); + techpoint_write_reg(client, 0x1d, 0x72); + techpoint_write_reg(client, 0x20, 0x30); + techpoint_write_reg(client, 0x21, 0x84); + techpoint_write_reg(client, 0x22, 0x36); + techpoint_write_reg(client, 0x23, 0x3c); + techpoint_write_reg(client, 0x2b, 0x60); + techpoint_write_reg(client, 0x2c, 0x0a); + techpoint_write_reg(client, 0x2d, 0x30); + techpoint_write_reg(client, 0x2e, 0x70); + techpoint_write_reg(client, 0x30, 0x48); + techpoint_write_reg(client, 0x31, 0xbb); + techpoint_write_reg(client, 0x32, 0x2e); + techpoint_write_reg(client, 0x33, 0x90); + techpoint_write_reg(client, 0x35, 0x25); + techpoint_write_reg(client, 0x38, 0x00); + techpoint_write_reg(client, 0x39, 0x18); + //def ahd config + techpoint_write_reg(client, 0x02, 0x46); + techpoint_write_reg(client, 0x0d, 0x70); + techpoint_write_reg(client, 0x20, 0x40); + techpoint_write_reg(client, 0x21, 0x46); + techpoint_write_reg(client, 0x25, 0xfe); + techpoint_write_reg(client, 0x26, 0x01); + techpoint_write_reg(client, 0x2c, 0x3a); + techpoint_write_reg(client, 0x2d, 0x5a); + techpoint_write_reg(client, 0x2e, 0x40); + techpoint_write_reg(client, 0x30, 0x9d); + techpoint_write_reg(client, 0x31, 0xca); + techpoint_write_reg(client, 0x32, 0x01); + techpoint_write_reg(client, 0x33, 0xd0); + break; + case TECHPOINT_S_RESO_720P_25: + dev_err(&client->dev, "set channel %d 720P_25\n", ch); + techpoint_read_reg(client, 0xf5, &tmp); + tmp |= SYS_MODE[ch]; + techpoint_write_reg(client, 0xf5, tmp); + techpoint_write_reg(client, 0x02, 0x42); + techpoint_write_reg(client, 0x07, 0xc0); + techpoint_write_reg(client, 0x0b, 0xc0); + techpoint_write_reg(client, 0x0c, 0x13); + techpoint_write_reg(client, 0x0d, 0x50); + techpoint_write_reg(client, 0x15, 0x13); + techpoint_write_reg(client, 0x16, 0x15); + techpoint_write_reg(client, 0x17, 0x00); + techpoint_write_reg(client, 0x18, 0x19); + techpoint_write_reg(client, 0x19, 0xd0); + techpoint_write_reg(client, 0x1a, 0x25); + techpoint_write_reg(client, 0x1c, 0x07); + techpoint_write_reg(client, 0x1d, 0xbc); + techpoint_write_reg(client, 0x20, 0x30); + techpoint_write_reg(client, 0x21, 0x84); + techpoint_write_reg(client, 0x22, 0x36); + techpoint_write_reg(client, 0x23, 0x3c); + techpoint_write_reg(client, 0x2b, 0x60); + techpoint_write_reg(client, 0x2c, 0x0a); + techpoint_write_reg(client, 0x2d, 0x30); + techpoint_write_reg(client, 0x2e, 0x70); + techpoint_write_reg(client, 0x30, 0x48); + techpoint_write_reg(client, 0x31, 0xbb); + techpoint_write_reg(client, 0x32, 0x2e); + techpoint_write_reg(client, 0x33, 0x90); + techpoint_write_reg(client, 0x35, 0x25); + techpoint_write_reg(client, 0x38, 0x00); + techpoint_write_reg(client, 0x39, 0x18); + //def ahd config + techpoint_write_reg(client, 0x02, 0x46); + techpoint_write_reg(client, 0x0d, 0x71); + techpoint_write_reg(client, 0x20, 0x40); + techpoint_write_reg(client, 0x21, 0x46); + techpoint_write_reg(client, 0x25, 0xfe); + techpoint_write_reg(client, 0x26, 0x01); + techpoint_write_reg(client, 0x2c, 0x3a); + techpoint_write_reg(client, 0x2d, 0x5a); + techpoint_write_reg(client, 0x2e, 0x40); + techpoint_write_reg(client, 0x30, 0x9e); + techpoint_write_reg(client, 0x31, 0x20); + techpoint_write_reg(client, 0x32, 0x10); + techpoint_write_reg(client, 0x33, 0x90); + break; + default: + dev_err(&client->dev, + "set channel %d UNSUPPORT, default 1080P_25\n", ch); + techpoint_read_reg(client, 0xf5, &tmp); + tmp &= ~SYS_MODE[ch]; + techpoint_write_reg(client, 0xf5, tmp); + techpoint_write_reg(client, 0x02, 0x40); + techpoint_write_reg(client, 0x07, 0xc0); + techpoint_write_reg(client, 0x0b, 0xc0); + techpoint_write_reg(client, 0x0c, 0x03); + techpoint_write_reg(client, 0x0d, 0x50); + techpoint_write_reg(client, 0x15, 0x03); + techpoint_write_reg(client, 0x16, 0xd2); + techpoint_write_reg(client, 0x17, 0x80); + techpoint_write_reg(client, 0x18, 0x29); + techpoint_write_reg(client, 0x19, 0x38); + techpoint_write_reg(client, 0x1a, 0x47); + techpoint_write_reg(client, 0x1c, 0x0a); + techpoint_write_reg(client, 0x1d, 0x50); + techpoint_write_reg(client, 0x20, 0x30); + techpoint_write_reg(client, 0x21, 0x84); + techpoint_write_reg(client, 0x22, 0x36); + techpoint_write_reg(client, 0x23, 0x3c); + techpoint_write_reg(client, 0x2b, 0x60); + techpoint_write_reg(client, 0x2c, 0x0a); + techpoint_write_reg(client, 0x2d, 0x30); + techpoint_write_reg(client, 0x2e, 0x70); + techpoint_write_reg(client, 0x30, 0x48); + techpoint_write_reg(client, 0x31, 0xbb); + techpoint_write_reg(client, 0x32, 0x2e); + techpoint_write_reg(client, 0x33, 0x90); + techpoint_write_reg(client, 0x35, 0x05); + techpoint_write_reg(client, 0x38, 0x00); + techpoint_write_reg(client, 0x39, 0x1C); + //def ahd config + techpoint_write_reg(client, 0x02, 0x44); + techpoint_write_reg(client, 0x0d, 0x73); + techpoint_write_reg(client, 0x15, 0x01); + techpoint_write_reg(client, 0x16, 0xf0); + techpoint_write_reg(client, 0x20, 0x3c); + techpoint_write_reg(client, 0x21, 0x46); + techpoint_write_reg(client, 0x25, 0xfe); + techpoint_write_reg(client, 0x26, 0x0d); + techpoint_write_reg(client, 0x2c, 0x3a); + techpoint_write_reg(client, 0x2d, 0x54); + techpoint_write_reg(client, 0x2e, 0x40); + techpoint_write_reg(client, 0x30, 0xa5); + techpoint_write_reg(client, 0x31, 0x86); + techpoint_write_reg(client, 0x32, 0xfb); + techpoint_write_reg(client, 0x33, 0x60); + break; + } + +#if TECHPOINT_TEST_PATTERN + techpoint_write_reg(client, 0x2a, 0x3c); +#endif + + return 0; +} + +int tp2855_get_channel_reso(struct i2c_client *client, int ch) +{ + u8 detect_fmt = 0xff; + u8 reso = 0xff; + + techpoint_write_reg(client, 0x40, ch); + techpoint_read_reg(client, 0x03, &detect_fmt); + reso = detect_fmt & 0x7; + + switch (reso) { + case TP2855_CVSTD_1080P_30: + dev_err(&client->dev, "detect channel %d 1080P_30\n", ch); + return TECHPOINT_S_RESO_1080P_30; + break; + case TP2855_CVSTD_1080P_25: + dev_err(&client->dev, "detect channel %d 1080P_25\n", ch); + return TECHPOINT_S_RESO_1080P_25; + break; + case TP2855_CVSTD_720P_30: + dev_err(&client->dev, "detect channel %d 720P_30\n", ch); + return TECHPOINT_S_RESO_720P_30; + break; + case TP2855_CVSTD_720P_25: + dev_err(&client->dev, "detect channel %d 720P_25\n", ch); + return TECHPOINT_S_RESO_720P_25; + break; + default: + dev_err(&client->dev, + "detect channel %d UNSUPPORT, default 1080P_25\n", ch); + return TECHPOINT_S_RESO_1080P_25; + break; + } + + return reso; +} diff --git a/drivers/media/i2c/techpoint/techpoint_tp2855.h b/drivers/media/i2c/techpoint/techpoint_tp2855.h new file mode 100644 index 000000000000..38963665efab --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_tp2855.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * techpoint tp2855 regs + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + */ + +#ifndef _TECHPOINT_TP2855_H +#define _TECHPOINT_TP2855_H + +#include "techpoint_common.h" + +#define TP2855_CHIP_ID_H_REG 0xFE +#define TP2855_CHIP_ID_H_VALUE 0x28 +#define TP2855_CHIP_ID_L_REG 0xFF +#define TP2855_CHIP_ID_L_VALUE 0x55 + +#define TP2855_LINK_FREQ_297M (297000000UL >> 1) +#define TP2855_LINK_FREQ_594M (594000000UL >> 1) +#define TP2855_LANES 4 +#define TP2855_BITS_PER_SAMPLE 8 + +enum tp2855_support_reso { + TP2855_CVSTD_720P_60 = 0, + TP2855_CVSTD_720P_50, + TP2855_CVSTD_1080P_30, + TP2855_CVSTD_1080P_25, + TP2855_CVSTD_720P_30, + TP2855_CVSTD_720P_25, + TP2855_CVSTD_SD, + TP2855_CVSTD_OTHER, +}; + +int tp2855_initialize(struct techpoint *techpoint); +int tp2855_get_channel_input_status(struct i2c_client *client, u8 ch); +int tp2855_get_all_input_status(struct i2c_client *client, u8 *detect_status); +int tp2855_set_channel_reso(struct i2c_client *client, int ch, + enum techpoint_support_reso reso); +int tp2855_get_channel_reso(struct i2c_client *client, int ch); + +#endif // _TECHPOINT_TP2855_H diff --git a/drivers/media/i2c/techpoint/techpoint_tp9930.c b/drivers/media/i2c/techpoint/techpoint_tp9930.c new file mode 100644 index 000000000000..f2338c994af7 --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_tp9930.c @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * techpoint techpoint lib + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + */ + +#include "techpoint_tp9930.h" +#include "techpoint_dev.h" + +static __maybe_unused const struct regval common_setting_148M_1080p_25fps_regs[] = { + { 0x40, 0x04 }, + { 0x02, 0x44 }, + { 0x05, 0x00 }, + { 0x06, 0x32 }, + { 0x07, 0x80 }, + { 0x08, 0x00 }, + { 0x09, 0x24 }, + { 0x0a, 0x48 }, + { 0x0b, 0xc0 }, + { 0x0c, 0x03 }, + { 0x0d, 0x73 }, + { 0x10, 0x00 }, + { 0x11, 0x40 }, + { 0x12, 0x40 }, + { 0x13, 0x00 }, + { 0x14, 0x00 }, + { 0x15, 0x01 }, + { 0x16, 0xf0 }, + { 0x17, 0x80 }, + { 0x18, 0x29 }, + { 0x19, 0x38 }, + { 0x1a, 0x47 }, + { 0x1c, 0x0a }, + { 0x1d, 0x50 }, + { 0x20, 0x3c }, + { 0x21, 0x46 }, + { 0x22, 0x36 }, + { 0x23, 0x3c }, + { 0x24, 0x04 }, + { 0x25, 0xfe }, + { 0x26, 0x0d }, + { 0x27, 0x2d }, + { 0x28, 0x00 }, + { 0x29, 0x48 }, +#if TECHPOINT_TEST_PATTERN + { 0x2a, 0x3c }, +#else + { 0x2a, 0x30 }, +#endif + { 0x2b, 0x60 }, + { 0x2c, 0x3a }, + { 0x2d, 0x54 }, + { 0x2e, 0x40 }, + { 0x30, 0xa5 }, + { 0x31, 0x86 }, + { 0x32, 0xfb }, + { 0x33, 0x60 }, + { 0x35, 0x05 }, + { 0x36, 0xca }, + { 0x38, 0x00 }, + { 0x39, 0x1c }, + { 0x3a, 0x32 }, + { 0x3b, 0x26 }, + { 0x40, 0x00 }, + { 0x34, 0x10 }, + { 0x40, 0x01 }, + { 0x34, 0x11 }, + { 0x40, 0x02 }, + { 0x34, 0x12 }, + { 0x40, 0x03 }, + { 0x34, 0x13 }, + { 0x4f, 0x03 }, + { 0x50, 0xb2 }, + { 0x52, 0xf6 }, + { 0xf1, 0x04 }, + { 0xf2, 0x77 }, + { 0xf3, 0x77 }, + { 0xf5, 0xf0 }, + { 0xf6, 0x10 }, + { 0xf8, 0x54 }, + { 0xfa, 0x88 }, + { 0xfb, 0x88 }, + { 0x4d, 0x07 }, + { 0x4e, 0x05 }, +}; + +static __maybe_unused const struct regval common_setting_148M_1080p_30fps_regs[] = { + { 0x40, 0x04 }, + { 0x02, 0x44 }, + { 0x07, 0x80 }, + { 0x0b, 0xc0 }, + { 0x0c, 0x03 }, + { 0x0d, 0x72 }, + { 0x10, 0x00 }, + { 0x11, 0x40 }, + { 0x12, 0x40 }, + { 0x13, 0x00 }, + { 0x14, 0x00 }, + { 0x15, 0x01 }, + { 0x16, 0xf0 }, + { 0x17, 0x80 }, + { 0x18, 0x29 }, + { 0x19, 0x38 }, + { 0x1a, 0x47 }, + { 0x1c, 0x08 }, + { 0x1d, 0x98 }, + { 0x20, 0x38 }, + { 0x21, 0x46 }, + { 0x22, 0x36 }, + { 0x23, 0x3c }, + { 0x24, 0x04 }, + { 0x25, 0xfe }, + { 0x26, 0x0d }, + { 0x27, 0x2d }, + { 0x28, 0x00 }, + { 0x29, 0x48 }, +#if TECHPOINT_TEST_PATTERN + { 0x2a, 0x3c }, +#else + { 0x2a, 0x30 }, +#endif + { 0x2b, 0x60 }, + { 0x2c, 0x3a }, + { 0x2d, 0x54 }, + { 0x2e, 0x40 }, + { 0x30, 0xa5 }, + { 0x31, 0x95 }, + { 0x32, 0xe0 }, + { 0x33, 0x60 }, + { 0x35, 0x45 }, + { 0x36, 0xca }, + { 0x38, 0x00 }, + { 0x39, 0x1c }, + { 0x3a, 0x32 }, + { 0x3b, 0x26 }, + { 0x40, 0x00 }, + { 0x34, 0x10 }, + { 0x40, 0x01 }, + { 0x34, 0x11 }, + { 0x40, 0x02 }, + { 0x34, 0x12 }, + { 0x40, 0x03 }, + { 0x34, 0x13 }, + { 0x4f, 0x03 }, + { 0x50, 0xA3 }, + { 0x52, 0xE7 }, + { 0xf1, 0x04 }, + { 0xf2, 0x77 }, + { 0xf3, 0x77 }, + { 0xf4, 0x00 }, + { 0xf5, 0xf0 }, + { 0xf6, 0x10 }, + { 0xf8, 0x54 }, + { 0xfa, 0x99 }, + { 0xfb, 0x99 }, + { 0x4d, 0x07 }, + { 0x4e, 0x05 }, +}; + +static __maybe_unused const struct regval common_setting_148M_720p_25fps_regs[] = { + { 0x40, 0x04 }, + { 0x02, 0x4e }, + { 0x05, 0x00 }, + { 0x06, 0x32 }, + { 0x07, 0xc0 }, + { 0x08, 0x00 }, + { 0x09, 0x24 }, + { 0x0a, 0x48 }, + { 0x0b, 0xc0 }, + { 0x0c, 0x13 }, + { 0x0d, 0x71 }, + { 0x0e, 0x00 }, + { 0x0f, 0x00 }, + { 0x10, 0x00 }, + { 0x11, 0x40 }, + { 0x12, 0x40 }, + { 0x13, 0x00 }, + { 0x14, 0x00 }, + { 0x15, 0x13 }, + { 0x16, 0x16 }, + { 0x17, 0x00 }, + { 0x18, 0x19 }, + { 0x19, 0xd0 }, + { 0x1a, 0x25 }, + { 0x1b, 0x00 }, + { 0x1c, 0x07 }, + { 0x1d, 0xbc }, + { 0x1e, 0x60 }, + { 0x1f, 0x06 }, + { 0x20, 0x40 }, + { 0x21, 0x46 }, + { 0x22, 0x36 }, + { 0x23, 0x3c }, + { 0x24, 0x04 }, + { 0x25, 0xfe }, + { 0x26, 0x01 }, + { 0x27, 0x2d }, + { 0x28, 0x00 }, + { 0x29, 0x48 }, +#if TECHPOINT_TEST_PATTERN + { 0x2a, 0x3c }, +#else + { 0x2a, 0x30 }, +#endif + { 0x2b, 0x60 }, + { 0x2c, 0x3a }, + { 0x2d, 0x5a }, + { 0x2e, 0x40 }, + { 0x2f, 0x06 }, + { 0x30, 0x9e }, + { 0x31, 0x20 }, + { 0x32, 0x01 }, + { 0x33, 0x90 }, + { 0x35, 0x25 }, + { 0x36, 0xca }, + { 0x37, 0x00 }, + { 0x38, 0x00 }, + { 0x39, 0x18 }, + { 0x3a, 0x32 }, + { 0x3b, 0x26 }, + { 0x3c, 0x00 }, + { 0x3d, 0x60 }, + { 0x3e, 0x00 }, + { 0x3f, 0x00 }, + + { 0x40, 0x00 }, + { 0x34, 0x10 }, + { 0x40, 0x01 }, + { 0x34, 0x11 }, + { 0x40, 0x02 }, + { 0x34, 0x12 }, + { 0x40, 0x03 }, + { 0x34, 0x13 }, + + { 0x4f, 0x03 }, + { 0x50, 0xA3 }, + { 0x52, 0xE7 }, + { 0xf1, 0x04 }, + { 0xf2, 0x77 }, + { 0xf3, 0x77 }, + { 0xf4, 0x00 }, + { 0xf5, 0xff }, + { 0xf6, 0x10 }, + { 0xf8, 0x54 }, + { 0xfa, 0x99 }, + { 0xfb, 0x99 }, + + { 0x4d, 0x07 }, + { 0x4e, 0x05 }, +}; + +static __maybe_unused const struct regval common_setting_148M_720p_30fps_regs[] = { + { 0x40, 0x04 }, + { 0x02, 0x4e }, + { 0x05, 0x00 }, + { 0x06, 0x32 }, + { 0x07, 0xc0 }, + { 0x08, 0x00 }, + { 0x09, 0x24 }, + { 0x0a, 0x48 }, + { 0x0b, 0xc0 }, + { 0x0c, 0x13 }, + { 0x0d, 0x70 }, + { 0x0e, 0x00 }, + { 0x0f, 0x00 }, + { 0x10, 0x00 }, + { 0x11, 0x40 }, + { 0x12, 0x40 }, + { 0x13, 0x00 }, + { 0x14, 0x00 }, + { 0x15, 0x13 }, + { 0x16, 0x16 }, + { 0x17, 0x00 }, + { 0x18, 0x19 }, + { 0x19, 0xd0 }, + { 0x1a, 0x25 }, + { 0x1b, 0x00 }, + { 0x1c, 0x06 }, + { 0x1d, 0x72 }, + { 0x1e, 0x60 }, + { 0x1f, 0x06 }, + { 0x20, 0x40 }, + { 0x21, 0x46 }, + { 0x22, 0x36 }, + { 0x23, 0x3c }, + { 0x24, 0x04 }, + { 0x25, 0xfe }, + { 0x26, 0x01 }, + { 0x27, 0x2d }, + { 0x28, 0x00 }, + { 0x29, 0x48 }, +#if TECHPOINT_TEST_PATTERN + { 0x2a, 0x3c }, +#else + { 0x2a, 0x30 }, +#endif + { 0x2b, 0x60 }, + { 0x2c, 0x3a }, + { 0x2d, 0x5a }, + { 0x2e, 0x40 }, + { 0x2f, 0x06 }, + { 0x30, 0x9d }, + { 0x31, 0xca }, + { 0x32, 0x01 }, + { 0x33, 0xd0 }, + { 0x35, 0x25 }, + { 0x36, 0xca }, + { 0x37, 0x00 }, + { 0x38, 0x00 }, + { 0x39, 0x18 }, + { 0x3a, 0x32 }, + { 0x3b, 0x26 }, + { 0x3c, 0x00 }, + { 0x3d, 0x60 }, + { 0x3e, 0x00 }, + { 0x3f, 0x00 }, + + { 0x40, 0x00 }, + { 0x34, 0x10 }, + { 0x40, 0x01 }, + { 0x34, 0x11 }, + { 0x40, 0x02 }, + { 0x34, 0x12 }, + { 0x40, 0x03 }, + { 0x34, 0x13 }, + + { 0x4f, 0x03 }, + { 0x50, 0xA3 }, + { 0x52, 0xE7 }, + { 0xf1, 0x04 }, + { 0xf2, 0x77 }, + { 0xf3, 0x77 }, + { 0xf4, 0x00 }, + { 0xf5, 0xff }, + { 0xf6, 0x10 }, + { 0xf8, 0x54 }, + { 0xfa, 0x99 }, + { 0xfb, 0x99 }, + + { 0x4d, 0x07 }, + { 0x4e, 0x05 }, +}; + +static struct techpoint_video_modes supported_modes[] = { +#if DEF_1080P + { + .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080, + .max_fps = { + .numerator = 10000, + .denominator = 250000, + }, + .link_freq_value = TP9930_LINK_FREQ_297M, + .common_reg_list = common_setting_148M_1080p_25fps_regs, + .common_reg_size = ARRAY_SIZE(common_setting_148M_1080p_25fps_regs), + }, + { + .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080, + .max_fps = { + .numerator = 10000, + .denominator = 300000, + }, + .link_freq_value = TP9930_LINK_FREQ_297M, + .common_reg_list = common_setting_148M_1080p_30fps_regs, + .common_reg_size = ARRAY_SIZE(common_setting_148M_1080p_30fps_regs), + }, +#endif + { + .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1280, + .height = 720, + .max_fps = { + .numerator = 10000, + .denominator = 250000, + }, + .link_freq_value = TP9930_LINK_FREQ_148M5, + .common_reg_list = common_setting_148M_720p_25fps_regs, + .common_reg_size = ARRAY_SIZE(common_setting_148M_720p_25fps_regs), + }, + { + .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1280, + .height = 720, + .max_fps = { + .numerator = 10000, + .denominator = 300000, + }, + .link_freq_value = TP9930_LINK_FREQ_148M5, + .common_reg_list = common_setting_148M_720p_30fps_regs, + .common_reg_size = ARRAY_SIZE(common_setting_148M_720p_30fps_regs), + } +}; + +int tp9930_initialize(struct techpoint *techpoint) +{ + int array_size = 0; + struct i2c_client *client = techpoint->client; + struct device *dev = &client->dev; + + techpoint->video_modes_num = ARRAY_SIZE(supported_modes); + array_size = + sizeof(struct techpoint_video_modes) * techpoint->video_modes_num; + techpoint->video_modes = devm_kzalloc(dev, array_size, GFP_KERNEL); + memcpy(techpoint->video_modes, supported_modes, array_size); + + techpoint->cur_video_mode = &techpoint->video_modes[0]; + + return 0; +} + +int tp9930_do_reset_pll(struct i2c_client *client) +{ + u8 val_0x44 = 0, val_0x43 = 0, val_0xf4 = 0; + + techpoint_read_reg(client, 0x43, &val_0x43); + techpoint_write_reg(client, 0x43, val_0x43 | 0x40); + techpoint_read_reg(client, 0x44, &val_0x44); + techpoint_write_reg(client, 0x44, val_0x44 | 0x40); + + techpoint_read_reg(client, 0xf4, &val_0xf4); + techpoint_write_reg(client, 0xf4, val_0xf4 | 0x80); + msleep(10); + + techpoint_write_reg(client, 0x43, val_0x43); + techpoint_write_reg(client, 0x44, val_0x44); + + return 0; +} + +int tp9930_pll_reset(struct i2c_client *client) +{ + techpoint_write_reg(client, 0x40, 0x00); + // output disable + techpoint_write_reg(client, 0x4d, 0x00); + techpoint_write_reg(client, 0x4e, 0x00); + // PLL reset + tp9930_do_reset_pll(client); + + techpoint_write_reg(client, 0x40, 0x04); + techpoint_write_reg(client, 0x3b, 0x20); + techpoint_write_reg(client, 0x3d, 0xe0); + techpoint_write_reg(client, 0x3d, 0x60); + techpoint_write_reg(client, 0x3b, 0x25); + techpoint_write_reg(client, 0x40, 0x40); + techpoint_write_reg(client, 0x7a, 0x20); + techpoint_write_reg(client, 0x3c, 0x20); + techpoint_write_reg(client, 0x3c, 0x00); + techpoint_write_reg(client, 0x7a, 0x25); + techpoint_write_reg(client, 0x40, 0x00); + +#if DEF_1080P +#if 1 // 25FPS + techpoint_write_reg(client, 0x44, 0x07); + techpoint_write_reg(client, 0x43, 0x17); + techpoint_write_reg(client, 0x45, 0x09); + techpoint_write_reg(client, 0xf4, 0xa0); +#else + techpoint_write_reg(client, 0x44, 0x17); + techpoint_write_reg(client, 0x43, 0x12); + techpoint_write_reg(client, 0x45, 0x09); +#endif +#else + techpoint_write_reg(client, 0x44, 0x17); + techpoint_write_reg(client, 0x43, 0x17); + techpoint_write_reg(client, 0x45, 0x09); +#endif + + return 0; +} + +int tp9930_set_decoder_mode(struct i2c_client *client, int ch, int status) +{ + u8 val = 0; + + techpoint_write_reg(client, PAGE_REG, ch); + techpoint_read_reg(client, 0x26, &val); + if (status) + val |= 0x1; + else + val &= ~0x1; + techpoint_write_reg(client, 0x26, val); + + return 0; +} + +int tp9930_get_channel_input_status(struct i2c_client *client, u8 ch) +{ + u8 val = 0; + + techpoint_write_reg(client, PAGE_REG, ch); + techpoint_read_reg(client, INPUT_STATUS_REG, &val); + dev_dbg(&client->dev, "input_status ch %d : %x\n", ch, val); + +#if 0 // inaccuracy + return (val & INPUT_STATUS_MASK) ? 0 : 1; +#else + return (val == INPUT_STATUS_MATCH) ? 1 : 0; +#endif +} + +int tp9930_get_all_input_status(struct i2c_client *client, u8 *detect_status) +{ + u8 val = 0, i; + + for (i = 0; i < PAD_MAX; i++) { + techpoint_write_reg(client, PAGE_REG, i); + techpoint_read_reg(client, INPUT_STATUS_REG, &val); + detect_status[i] = tp9930_get_channel_input_status(client, i); + } + + return 0; +} + +int tp9930_set_channel_reso(struct i2c_client *client, int ch, + enum techpoint_support_reso reso) +{ + int val = reso; + + switch (val) { + case TECHPOINT_S_RESO_1080P_30: + dev_err(&client->dev, "set channel %d 1080P_30, TBD", ch); + break; + case TECHPOINT_S_RESO_1080P_25: + dev_err(&client->dev, "set channel %d 1080P_25, TBD", ch); + break; + case TECHPOINT_S_RESO_720P_30: + dev_err(&client->dev, "set channel %d 720P_30, TBD", ch); + break; + case TECHPOINT_S_RESO_720P_25: + dev_err(&client->dev, "set channel %d 720P_25, TBD", ch); + break; + default: +#if DEF_1080P + dev_err(&client->dev, + "set channel %d UNSUPPORT, default 1080P_25, TBD", ch); +#else + dev_err(&client->dev, + "set channel %d UNSUPPORT, default 720P_25, TBD", ch); +#endif + break; + } + + return 0; +} + +int tp9930_get_channel_reso(struct i2c_client *client, int ch) +{ + u8 detect_fmt = 0xff; + u8 reso = 0xff; + + techpoint_write_reg(client, 0x40, ch); + techpoint_read_reg(client, 0x03, &detect_fmt); + reso = detect_fmt & 0x7; + + switch (reso) { + case TP9930_CVSTD_1080P_30: + dev_err(&client->dev, "detect channel %d 1080P_30", ch); + return TECHPOINT_S_RESO_1080P_30; + break; + case TP9930_CVSTD_1080P_25: + dev_err(&client->dev, "detect channel %d 1080P_25", ch); + return TECHPOINT_S_RESO_1080P_25; + break; + case TP9930_CVSTD_720P_30: + dev_err(&client->dev, "detect channel %d 720P_30", ch); + return TECHPOINT_S_RESO_720P_30; + break; + case TP9930_CVSTD_720P_25: + dev_err(&client->dev, "detect channel %d 720P_25", ch); + return TECHPOINT_S_RESO_720P_25; + break; + case TP9930_CVSTD_720P_60: + case TP9930_CVSTD_720P_50: + default: +#if DEF_1080P + dev_err(&client->dev, + "detect channel %d UNSUPPORT, default 1080P_25", ch); + return TECHPOINT_S_RESO_1080P_25; +#else + dev_err(&client->dev, + "detect channel %d UNSUPPORT, default 720P_25", ch); + return TECHPOINT_S_RESO_720P_25; +#endif + break; + } + + return reso; +} diff --git a/drivers/media/i2c/techpoint/techpoint_tp9930.h b/drivers/media/i2c/techpoint/techpoint_tp9930.h new file mode 100644 index 000000000000..a6a09dfc61ec --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_tp9930.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * techpoint tp9930 regs + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + */ + +#ifndef _TECHPOINT_TP9930_H +#define _TECHPOINT_TP9930_H + +#include "techpoint_common.h" + +#define TP9930_CHIP_ID_H_REG 0xFE +#define TP9930_CHIP_ID_H_VALUE 0x28 +#define TP9930_CHIP_ID_L_REG 0xFF +#define TP9930_CHIP_ID_L_VALUE 0x32 + +#define TP9930_LINK_FREQ_74M25 (74250000) +#define TP9930_LINK_FREQ_148M5 (148500000) +#define TP9930_LINK_FREQ_297M (297000000) + +#define INPUT_STATUS_MATCH 0x7a + +enum tp9930_support_reso { + TP9930_CVSTD_720P_60 = 0, + TP9930_CVSTD_720P_50, + TP9930_CVSTD_1080P_30, + TP9930_CVSTD_1080P_25, + TP9930_CVSTD_720P_30, + TP9930_CVSTD_720P_25, + TP9930_CVSTD_SD, + TP9930_CVSTD_OTHER, +}; + +int tp9930_initialize(struct techpoint *techpoint); +int tp9930_do_reset_pll(struct i2c_client *client); +int tp9930_pll_reset(struct i2c_client *client); + +int tp9930_set_decoder_mode(struct i2c_client *client, int ch, int status); +int tp9930_set_channel_reso(struct i2c_client *client, int ch, + enum techpoint_support_reso reso); +int tp9930_get_channel_reso(struct i2c_client *client, int ch); +int tp9930_get_channel_input_status(struct i2c_client *client, u8 index); +int tp9930_get_all_input_status(struct i2c_client *client, u8 *detect_status); + +#endif // _TECHPOINT_TP9930_H diff --git a/drivers/media/i2c/techpoint/techpoint_v4l2.c b/drivers/media/i2c/techpoint/techpoint_v4l2.c new file mode 100644 index 000000000000..1a97b2777f58 --- /dev/null +++ b/drivers/media/i2c/techpoint/techpoint_v4l2.c @@ -0,0 +1,943 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * techpoint v4l2 driver + * + * Copyright (C) 2021 Rockchip Electronics Co., Ltd. + * + */ + +#include "techpoint_dev.h" + +#define TECHPOINT_NAME "techpoint" + +#define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default" +#define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep" + +static const char *const techpoint_supply_names[] = { + "dovdd", /* Digital I/O power */ + "avdd", /* Analog power */ + "dvdd", /* Digital power */ +}; + +#define TECHPOINT_NUM_SUPPLIES ARRAY_SIZE(techpoint_supply_names) + +#define to_techpoint(sd) container_of(sd, struct techpoint, subdev) + +static int techpoint_get_regulators(struct techpoint *techpoint) +{ + unsigned int i; + struct i2c_client *client = techpoint->client; + struct device *dev = &techpoint->client->dev; + + if (!techpoint->supplies) + techpoint->supplies = devm_kzalloc(dev, + sizeof(struct + regulator_bulk_data) * + TECHPOINT_NUM_SUPPLIES, + GFP_KERNEL); + + for (i = 0; i < TECHPOINT_NUM_SUPPLIES; i++) + techpoint->supplies[i].supply = techpoint_supply_names[i]; + return devm_regulator_bulk_get(&client->dev, + TECHPOINT_NUM_SUPPLIES, + techpoint->supplies); +} + +static int techpoint_analyze_dts(struct techpoint *techpoint) +{ + int ret; + struct i2c_client *client = techpoint->client; + struct device *dev = &client->dev; + struct device_node *node = dev->of_node; + + ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX, + &techpoint->module_index); + ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING, + &techpoint->module_facing); + ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME, + &techpoint->module_name); + ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME, + &techpoint->len_name); + if (ret) { + dev_err(dev, "could not get %s!\n", RKMODULE_CAMERA_LENS_NAME); + return -EINVAL; + } + + ret = of_property_read_u32(node, TECHPOINT_CAMERA_XVCLK_FREQ, + &techpoint->xvclk_freq_value); + if (ret) + techpoint->xvclk_freq_value = 27000000; + + ret = of_property_read_u32(node, TECHPOINT_CHANNEL_NUMS, + &techpoint->channel_nums); + if (ret) + techpoint->channel_nums = 4; + + techpoint->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(techpoint->xvclk)) { + dev_err(dev, "Failed to get xvclk\n"); + return -EINVAL; + } + + techpoint->power_gpio = devm_gpiod_get(dev, "power", GPIOD_OUT_HIGH); + if (IS_ERR(techpoint->power_gpio)) + dev_warn(dev, "Failed to get power-gpios\n"); + else + gpiod_set_value_cansleep(techpoint->power_gpio, 1); + + techpoint_get_regulators(techpoint); + ret = + regulator_bulk_enable(TECHPOINT_NUM_SUPPLIES, techpoint->supplies); + if (ret < 0) + dev_err(dev, "Failed to enable regulators\n"); + + techpoint->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(techpoint->reset_gpio)) + dev_warn(dev, "Failed to get reset-gpios\n"); + else + gpiod_set_value_cansleep(techpoint->reset_gpio, 0); + + techpoint->pinctrl = devm_pinctrl_get(dev); + if (!IS_ERR(techpoint->pinctrl)) { + techpoint->pins_default = + pinctrl_lookup_state(techpoint->pinctrl, + OF_CAMERA_PINCTRL_STATE_DEFAULT); + if (IS_ERR(techpoint->pins_default)) + dev_info(dev, "could not get default pinstate\n"); + + techpoint->pins_sleep = + pinctrl_lookup_state(techpoint->pinctrl, + OF_CAMERA_PINCTRL_STATE_SLEEP); + if (IS_ERR(techpoint->pins_sleep)) + dev_info(dev, "could not get sleep pinstate\n"); + } else { + dev_info(dev, "no pinctrl\n"); + } + + return 0; +} + +static int techpoint_initialize_controls(struct techpoint *techpoint) +{ + int ret; + u64 pixel_rate; + struct v4l2_ctrl_handler *handler; + const struct techpoint_video_modes *mode; + struct device *dev = &techpoint->client->dev; + + handler = &techpoint->ctrl_handler; + mode = techpoint->cur_video_mode; + + if (techpoint->input_type == TECHPOINT_DVP_BT1120) { + ret = v4l2_ctrl_handler_init(handler, 1); + if (ret) + return ret; + handler->lock = &techpoint->mutex; + pixel_rate = mode->link_freq_value; + techpoint->pixel_rate_ctrl = v4l2_ctrl_new_std(handler, NULL, + V4L2_CID_PIXEL_RATE, + 0, pixel_rate, 1, + pixel_rate); + dev_dbg(dev, "initialize pixel_rate %lld\n", pixel_rate); + } else if (techpoint->input_type == TECHPOINT_MIPI) { + ret = v4l2_ctrl_handler_init(handler, 2); + if (ret) + return ret; + handler->lock = &techpoint->mutex; + techpoint->link_freq_ctrl = + v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, 0, + 0, &mode->link_freq_value); + __v4l2_ctrl_s_ctrl(techpoint->link_freq_ctrl, 0); + dev_dbg(dev, "initialize link_freq %lld\n", + mode->link_freq_value); + + pixel_rate = + (u32) mode->link_freq_value / mode->bpp * 2 * mode->lane; + techpoint->pixel_rate_ctrl = + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 0, + pixel_rate, 1, pixel_rate); + dev_dbg(dev, "initialize pixel_rate %lld\n", pixel_rate); + } + + if (handler->error) { + ret = handler->error; + dev_err(dev, "Failed to init controls(%d)\n", ret); + goto err_free_handler; + } + + techpoint->subdev.ctrl_handler = handler; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int __techpoint_power_on(struct techpoint *techpoint) +{ + int ret; + struct device *dev = &techpoint->client->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (!IS_ERR_OR_NULL(techpoint->pins_default)) { + ret = pinctrl_select_state(techpoint->pinctrl, + techpoint->pins_default); + if (ret < 0) + dev_err(dev, "could not set pins. ret=%d\n", ret); + } + + if (!IS_ERR(techpoint->power_gpio)) { + gpiod_set_value_cansleep(techpoint->power_gpio, 1); + usleep_range(25 * 1000, 30 * 1000); + } + + usleep_range(1500, 2000); + + if (!IS_ERR(techpoint->xvclk)) { + ret = + clk_set_rate(techpoint->xvclk, techpoint->xvclk_freq_value); + if (ret < 0) + dev_warn(dev, "Failed to set xvclk rate\n"); + if (clk_get_rate(techpoint->xvclk) != + techpoint->xvclk_freq_value) + dev_warn(dev, "xvclk mismatched\n"); + ret = clk_prepare_enable(techpoint->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + goto err_clk; + } + } + + if (!IS_ERR(techpoint->reset_gpio)) { + gpiod_set_value_cansleep(techpoint->reset_gpio, 0); + usleep_range(10 * 1000, 20 * 1000); + gpiod_set_value_cansleep(techpoint->reset_gpio, 1); + usleep_range(10 * 1000, 20 * 1000); + gpiod_set_value_cansleep(techpoint->reset_gpio, 0); + } + + usleep_range(10 * 1000, 20 * 1000); + + return 0; + +err_clk: + if (!IS_ERR(techpoint->reset_gpio)) + gpiod_set_value_cansleep(techpoint->reset_gpio, 0); + + if (!IS_ERR_OR_NULL(techpoint->pins_sleep)) + pinctrl_select_state(techpoint->pinctrl, techpoint->pins_sleep); + + if (!IS_ERR(techpoint->power_gpio)) + gpiod_set_value_cansleep(techpoint->power_gpio, 0); + + return ret; +} + +static void __techpoint_power_off(struct techpoint *techpoint) +{ + int ret; + struct device *dev = &techpoint->client->dev; + +#if TECHPOINT_SHARING_POWER + return; +#endif + + dev_dbg(dev, "%s\n", __func__); + + if (!IS_ERR(techpoint->reset_gpio)) + gpiod_set_value_cansleep(techpoint->reset_gpio, 1); + + if (IS_ERR(techpoint->xvclk)) + clk_disable_unprepare(techpoint->xvclk); + + if (!IS_ERR_OR_NULL(techpoint->pins_sleep)) { + ret = pinctrl_select_state(techpoint->pinctrl, + techpoint->pins_sleep); + if (ret < 0) + dev_dbg(dev, "could not set pins\n"); + } + + if (!IS_ERR(techpoint->power_gpio)) + gpiod_set_value_cansleep(techpoint->power_gpio, 0); +} + +static int techpoint_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct techpoint *techpoint = to_techpoint(sd); + + return __techpoint_power_on(techpoint); +} + +static int techpoint_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct techpoint *techpoint = to_techpoint(sd); + + __techpoint_power_off(techpoint); + + return 0; +} + +static int techpoint_power(struct v4l2_subdev *sd, int on) +{ + struct techpoint *techpoint = to_techpoint(sd); + struct i2c_client *client = techpoint->client; + int ret = 0; + + mutex_lock(&techpoint->mutex); + + /* If the power state is not modified - no work to do. */ + if (techpoint->power_on == !!on) + goto exit; + + dev_dbg(&client->dev, "%s: on %d\n", __func__, on); + + if (on) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto exit; + } + techpoint->power_on = true; + } else { + pm_runtime_put(&client->dev); + techpoint->power_on = false; + } + +exit: + mutex_unlock(&techpoint->mutex); + + return ret; +} + +static int techpoint_get_reso_dist(const struct techpoint_video_modes *mode, + struct v4l2_mbus_framefmt *framefmt) +{ + return abs(mode->width - framefmt->width) + + abs(mode->height - framefmt->height); +} + +static const struct techpoint_video_modes * +techpoint_find_best_fit(struct techpoint *techpoint, + 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; + unsigned int i; + + for (i = 0; i < techpoint->video_modes_num; i++) { + dist = + techpoint_get_reso_dist(&techpoint->video_modes[i], + framefmt); + if ((cur_best_fit_dist == -1 || dist <= cur_best_fit_dist) && + techpoint->video_modes[i].bus_fmt == framefmt->code) { + cur_best_fit_dist = dist; + cur_best_fit = i; + } + } + + return &techpoint->video_modes[cur_best_fit]; +} + +static int techpoint_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct techpoint *techpoint = to_techpoint(sd); + const struct techpoint_video_modes *mode; + + mutex_lock(&techpoint->mutex); + + mode = techpoint_find_best_fit(techpoint, fmt); + techpoint->cur_video_mode = mode; + fmt->format.code = mode->bus_fmt; + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_SRGB; + + 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(&techpoint->mutex); + return -ENOTTY; +#endif + } else { + if (techpoint->streaming) { + mutex_unlock(&techpoint->mutex); + return -EBUSY; + } + } + + mutex_unlock(&techpoint->mutex); + return 0; +} + +static int techpoint_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct techpoint *techpoint = to_techpoint(sd); + struct i2c_client *client = techpoint->client; + const struct techpoint_video_modes *mode = techpoint->cur_video_mode; + + mutex_lock(&techpoint->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(&techpoint->mutex); + return -ENOTTY; +#endif + } else { + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = mode->bus_fmt; + fmt->format.field = V4L2_FIELD_NONE; + if (fmt->pad < PAD_MAX && fmt->pad >= PAD0) + fmt->reserved[0] = mode->vc[fmt->pad]; + else + fmt->reserved[0] = mode->vc[PAD0]; + } + mutex_unlock(&techpoint->mutex); + + dev_dbg(&client->dev, "%s: %x %dx%d\n", + __func__, fmt->format.code, + fmt->format.width, fmt->format.height); + + return 0; +} + +static int techpoint_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct techpoint *techpoint = to_techpoint(sd); + + if (code->index != 0) + return -EINVAL; + code->code = techpoint->cur_video_mode->bus_fmt; + + return 0; +} + +static int techpoint_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct techpoint *techpoint = to_techpoint(sd); + struct i2c_client *client = techpoint->client; + + dev_dbg(&client->dev, "%s:\n", __func__); + + if (fse->index >= techpoint->video_modes_num) + return -EINVAL; + + if (fse->code != techpoint->video_modes[fse->index].bus_fmt) + return -EINVAL; + + fse->min_width = techpoint->video_modes[fse->index].width; + fse->max_width = techpoint->video_modes[fse->index].width; + fse->max_height = techpoint->video_modes[fse->index].height; + fse->min_height = techpoint->video_modes[fse->index].height; + + return 0; +} + +static int techpoint_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct techpoint *techpoint = to_techpoint(sd); + + mutex_lock(&techpoint->mutex); + fi->interval = techpoint->cur_video_mode->max_fps; + mutex_unlock(&techpoint->mutex); + + return 0; +} + +static int techpoint_g_mbus_config(struct v4l2_subdev *sd, + struct v4l2_mbus_config *cfg) +{ + struct techpoint *techpoint = to_techpoint(sd); + + if (techpoint->input_type == TECHPOINT_DVP_BT1120) { + cfg->type = V4L2_MBUS_BT656; + cfg->flags = RKMODULE_CAMERA_BT656_CHANNELS | + V4L2_MBUS_PCLK_SAMPLE_RISING | + V4L2_MBUS_PCLK_SAMPLE_FALLING; + } else if (techpoint->input_type == TECHPOINT_MIPI) { + cfg->type = V4L2_MBUS_CSI2; + cfg->flags = V4L2_MBUS_CSI2_4_LANE | V4L2_MBUS_CSI2_CHANNELS; + } + + return 0; +} + +static int techpoint_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) +{ + struct techpoint *techpoint = to_techpoint(sd); + + if (techpoint->input_type == TECHPOINT_DVP_BT1120) + *std = V4L2_STD_ATSC; + + return 0; +} + +static __maybe_unused void techpoint_get_module_inf(struct techpoint *techpoint, + struct rkmodule_inf *inf) +{ + memset(inf, 0, sizeof(*inf)); + strlcpy(inf->base.sensor, TECHPOINT_NAME, sizeof(inf->base.sensor)); + strlcpy(inf->base.module, techpoint->module_name, + sizeof(inf->base.module)); + strlcpy(inf->base.lens, techpoint->len_name, sizeof(inf->base.lens)); +} + +static __maybe_unused void +techpoint_get_bt656_module_inf(struct techpoint *techpoint, + struct rkmodule_bt656_mbus_info *inf) +{ + memset(inf, 0, sizeof(*inf)); + if (techpoint->input_type == TECHPOINT_DVP_BT1120) { + inf->id_en_bits = RKMODULE_CAMERA_BT656_ID_EN_BITS_2; + inf->flags = RKMODULE_CAMERA_BT656_PARSE_ID_LSB | + RKMODULE_CAMERA_BT656_CHANNELS; + } +} + +static void techpoint_get_vicap_rst_inf(struct techpoint *techpoint, + struct rkmodule_vicap_reset_info *rst_info) +{ + rst_info->is_reset = techpoint->do_reset; + rst_info->src = RKCIF_RESET_SRC_ERR_HOTPLUG; +} + +static void techpoint_set_vicap_rst_inf(struct techpoint *techpoint, + struct rkmodule_vicap_reset_info *rst_info) +{ + techpoint->do_reset = rst_info->is_reset; +} + +static long techpoint_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct techpoint *techpoint = to_techpoint(sd); + long ret = 0; + + switch (cmd) { + case RKMODULE_GET_MODULE_INFO: + techpoint_get_module_inf(techpoint, (struct rkmodule_inf *)arg); + break; + case RKMODULE_GET_BT656_MBUS_INFO: + techpoint_get_bt656_module_inf(techpoint, + (struct rkmodule_bt656_mbus_info + *)arg); + break; + case RKMODULE_GET_VC_FMT_INFO: + if (!techpoint->streaming) + techpoint_get_vc_fmt_inf(techpoint, + (struct rkmodule_vc_fmt_info *) + arg); + else + __techpoint_get_vc_fmt_inf(techpoint, + (struct rkmodule_vc_fmt_info + *)arg); + break; + case RKMODULE_GET_VC_HOTPLUG_INFO: + techpoint_get_vc_hotplug_inf(techpoint, + (struct rkmodule_vc_hotplug_info *) + arg); + break; + case RKMODULE_GET_START_STREAM_SEQ: + *(int *)arg = RKMODULE_START_STREAM_FRONT; + break; + case RKMODULE_GET_VICAP_RST_INFO: + techpoint_get_vicap_rst_inf(techpoint, + (struct rkmodule_vicap_reset_info *) + arg); + break; + case RKMODULE_SET_VICAP_RST_INFO: + techpoint_set_vicap_rst_inf(techpoint, + (struct rkmodule_vicap_reset_info *) + arg); + break; + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long techpoint_compat_ioctl32(struct v4l2_subdev *sd, + unsigned int cmd, unsigned long arg) +{ + void __user *up = compat_ptr(arg); + struct rkmodule_inf *inf; + struct rkmodule_bt656_mbus_info *bt565_inf; + struct rkmodule_awb_cfg *cfg; + struct rkmodule_vc_fmt_info *vc_fmt_inf; + struct rkmodule_vc_hotplug_info *vc_hp_inf; + struct rkmodule_vicap_reset_info *vicap_rst_inf; + int *stream_seq; + long ret = 0; + + switch (cmd) { + case RKMODULE_GET_MODULE_INFO: + inf = kzalloc(sizeof(*inf), GFP_KERNEL); + if (!inf) { + ret = -ENOMEM; + return ret; + } + + ret = techpoint_ioctl(sd, cmd, inf); + if (!ret) + ret = copy_to_user(up, inf, sizeof(*inf)); + kfree(inf); + break; + case RKMODULE_AWB_CFG: + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) { + ret = -ENOMEM; + return ret; + } + + ret = copy_from_user(cfg, up, sizeof(*cfg)); + if (!ret) + ret = techpoint_ioctl(sd, cmd, cfg); + kfree(cfg); + break; + case RKMODULE_GET_VC_FMT_INFO: + vc_fmt_inf = kzalloc(sizeof(*vc_fmt_inf), GFP_KERNEL); + if (!vc_fmt_inf) { + ret = -ENOMEM; + return ret; + } + + ret = techpoint_ioctl(sd, cmd, vc_fmt_inf); + if (!ret) + ret = copy_to_user(up, vc_fmt_inf, sizeof(*vc_fmt_inf)); + kfree(vc_fmt_inf); + break; + case RKMODULE_GET_VC_HOTPLUG_INFO: + vc_hp_inf = kzalloc(sizeof(*vc_hp_inf), GFP_KERNEL); + if (!vc_hp_inf) { + ret = -ENOMEM; + return ret; + } + + ret = techpoint_ioctl(sd, cmd, vc_hp_inf); + if (!ret) + ret = copy_to_user(up, vc_hp_inf, sizeof(*vc_hp_inf)); + kfree(vc_hp_inf); + break; + case RKMODULE_GET_BT656_MBUS_INFO: + bt565_inf = kzalloc(sizeof(*bt565_inf), GFP_KERNEL); + if (!bt565_inf) { + ret = -ENOMEM; + return ret; + } + + ret = techpoint_ioctl(sd, cmd, bt565_inf); + if (!ret) + ret = copy_to_user(up, bt565_inf, sizeof(*bt565_inf)); + kfree(bt565_inf); + break; + case RKMODULE_GET_VICAP_RST_INFO: + vicap_rst_inf = kzalloc(sizeof(*vicap_rst_inf), GFP_KERNEL); + if (!vicap_rst_inf) { + ret = -ENOMEM; + return ret; + } + + ret = techpoint_ioctl(sd, cmd, vicap_rst_inf); + if (!ret) + ret = + copy_to_user(up, vicap_rst_inf, + sizeof(*vicap_rst_inf)); + kfree(vicap_rst_inf); + break; + case RKMODULE_SET_VICAP_RST_INFO: + vicap_rst_inf = kzalloc(sizeof(*vicap_rst_inf), GFP_KERNEL); + if (!vicap_rst_inf) { + ret = -ENOMEM; + return ret; + } + + ret = copy_from_user(vicap_rst_inf, up, sizeof(*vicap_rst_inf)); + if (!ret) + ret = techpoint_ioctl(sd, cmd, vicap_rst_inf); + kfree(vicap_rst_inf); + break; + case RKMODULE_GET_START_STREAM_SEQ: + stream_seq = kzalloc(sizeof(*stream_seq), GFP_KERNEL); + if (!stream_seq) { + ret = -ENOMEM; + return ret; + } + + ret = techpoint_ioctl(sd, cmd, stream_seq); + if (!ret) + ret = copy_to_user(up, stream_seq, sizeof(*stream_seq)); + kfree(stream_seq); + break; + default: + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} +#endif + +static __maybe_unused int __techpoint_start_stream(struct techpoint *techpoint) +{ + techpoint_start_video_stream(techpoint); + return 0; +} + +static __maybe_unused int __techpoint_stop_stream(struct techpoint *techpoint) +{ + techpoint_stop_video_stream(techpoint); + return 0; +} + +static int techpoint_stream(struct v4l2_subdev *sd, int on) +{ + struct techpoint *techpoint = to_techpoint(sd); + struct i2c_client *client = techpoint->client; + + dev_dbg(&client->dev, "s_stream: %d. %dx%d\n", on, + techpoint->cur_video_mode->width, + techpoint->cur_video_mode->height); + + mutex_lock(&techpoint->mutex); + on = !!on; + if (techpoint->streaming == on) + goto unlock; + + if (on) + __techpoint_start_stream(techpoint); + else + __techpoint_stop_stream(techpoint); + + techpoint->streaming = on; + +unlock: + mutex_unlock(&techpoint->mutex); + return 0; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int techpoint_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return 0; +} +#endif + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_internal_ops techpoint_internal_ops = { + .open = techpoint_open, +}; +#endif + +static const struct v4l2_subdev_video_ops techpoint_video_ops = { + .s_stream = techpoint_stream, + .g_mbus_config = techpoint_g_mbus_config, + .g_frame_interval = techpoint_g_frame_interval, + .querystd = techpoint_querystd, +}; + +static const struct v4l2_subdev_pad_ops techpoint_subdev_pad_ops = { + .enum_mbus_code = techpoint_enum_mbus_code, + .enum_frame_size = techpoint_enum_frame_sizes, + .get_fmt = techpoint_get_fmt, + .set_fmt = techpoint_set_fmt, +}; + +static const struct v4l2_subdev_core_ops techpoint_core_ops = { + .s_power = techpoint_power, + .ioctl = techpoint_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = techpoint_compat_ioctl32, +#endif +}; + +static const struct v4l2_subdev_ops techpoint_subdev_ops = { + .core = &techpoint_core_ops, + .video = &techpoint_video_ops, + .pad = &techpoint_subdev_pad_ops, +}; + +static const struct dev_pm_ops techpoint_pm_ops = { + SET_RUNTIME_PM_OPS(techpoint_runtime_suspend, + techpoint_runtime_resume, NULL) +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id techpoint_of_match[] = { + { .compatible = "techpoint,tp2855" }, + { .compatible = "techpoint,tp2815" }, + { .compatible = "techpoint,tp9930" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, techpoint_of_match); +#endif + +static const struct i2c_device_id techpoint_match_id[] = { + { "techpoint", 0 }, + { }, +}; + +static int techpoint_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct techpoint *techpoint; + struct v4l2_subdev *sd; + __maybe_unused char facing[2]; + int ret, index; + + dev_info(dev, "driver version: %02x.%02x.%02x", + DRIVER_VERSION >> 16, + (DRIVER_VERSION & 0xff00) >> 8, DRIVER_VERSION & 0x00ff); + + techpoint = devm_kzalloc(dev, sizeof(*techpoint), GFP_KERNEL); + if (!techpoint) + return -ENOMEM; + + techpoint->client = client; + techpoint->supplies = NULL; + + mutex_init(&techpoint->mutex); + + sd = &techpoint->subdev; + v4l2_i2c_subdev_init(sd, client, &techpoint_subdev_ops); + + techpoint_analyze_dts(techpoint); + + ret = __techpoint_power_on(techpoint); + if (ret) { + dev_err(dev, "Failed to power on techpoint\n"); + goto err_destroy_mutex; + } + + ret = techpoint_initialize_devices(techpoint); + if (ret) { + dev_err(dev, "Failed to initialize techpoint device\n"); + goto err_power_off; + } + + ret = techpoint_initialize_controls(techpoint); + if (ret) { + dev_err(dev, "Failed to initialize controls techpoint\n"); + goto err_free_handler; + } +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + sd->internal_ops = &techpoint_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; +#endif + +#if defined(CONFIG_MEDIA_CONTROLLER) + for (index = 0; index < PAD_MAX; index++) + techpoint->pad[index].flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, PAD_MAX, techpoint->pad); + if (ret < 0) + goto err_power_off; +#endif + + memset(facing, 0, sizeof(facing)); + if (strcmp(techpoint->module_facing, "back") == 0) + facing[0] = 'b'; + else + facing[0] = 'f'; + + snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", + techpoint->module_index, facing, + TECHPOINT_NAME, dev_name(sd->dev)); + + ret = v4l2_async_register_subdev_sensor_common(sd); + 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(&sd->entity); +#endif +err_free_handler: + v4l2_ctrl_handler_free(&techpoint->ctrl_handler); +err_power_off: + __techpoint_power_off(techpoint); +err_destroy_mutex: + mutex_destroy(&techpoint->mutex); + + return ret; +} + +static int techpoint_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct techpoint *techpoint = to_techpoint(sd); + + v4l2_async_unregister_subdev(sd); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif + v4l2_ctrl_handler_free(&techpoint->ctrl_handler); + mutex_destroy(&techpoint->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + __techpoint_power_off(techpoint); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static struct i2c_driver techpoint_i2c_driver = { + .driver = { + .name = TECHPOINT_NAME, + .pm = &techpoint_pm_ops, + .of_match_table = of_match_ptr(techpoint_of_match), + }, + .probe = &techpoint_probe, + .remove = &techpoint_remove, + .id_table = techpoint_match_id, +}; + +static int __init sensor_mod_init(void) +{ + return i2c_add_driver(&techpoint_i2c_driver); +} + +static void __exit sensor_mod_exit(void) +{ + i2c_del_driver(&techpoint_i2c_driver); +} + +device_initcall_sync(sensor_mod_init); +module_exit(sensor_mod_exit); + +MODULE_AUTHOR("Vicent Chi "); +MODULE_DESCRIPTION("Techpoint decoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/tp2855.c b/drivers/media/i2c/tp2855.c deleted file mode 100644 index 1efca73a5235..000000000000 --- a/drivers/media/i2c/tp2855.c +++ /dev/null @@ -1,1125 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * tp2855 driver - * - * Copyright (C) 2020 Rockchip Electronics Co., Ltd. - * - * V0.0X01.0X00 first version. - */ - -#define DEBUG -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x0) -#define TP2855_TEST_PATTERN 0 -#define TP2855_XVCLK_FREQ 27000000 -#define TP2855_LINK_FREQ_297M (297000000UL >> 1) -#define TP2855_LINK_FREQ_594M (594000000UL >> 1) -#define TP2855_LANES 4 -#define TP2855_BITS_PER_SAMPLE 8 -#define TP2855_NAME "tp2855" -#define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default" -#define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep" - -enum tp2855_max_pad { - PAD0, - PAD1, - PAD2, - PAD3, - PAD_MAX, -}; - -enum{ - CH_1=0, - CH_2=1, - CH_3=2, - CH_4=3, - CH_ALL=4, - MIPI_PAGE=8, -}; - -enum{ - STD_TVI, //TVI - STD_HDA, //AHD -}; - -struct regval { - u8 addr; - u8 val; -}; - -struct tp2855_mode { - u32 bus_fmt; - u32 width; - u32 height; - struct v4l2_fract max_fps; - u32 mipi_freq_idx; - u32 bpp; - const struct regval *global_reg_list; - const struct regval *reg_list; - u32 hdr_mode; - u32 vc[PAD_MAX]; - u32 channel_reso[PAD_MAX]; -}; - -struct tp2855 { - struct i2c_client *client; - struct clk *xvclk; - struct gpio_desc *reset_gpio; - struct gpio_desc *power_gpio; - - struct pinctrl *pinctrl; - struct pinctrl_state *pins_default; - struct pinctrl_state *pins_sleep; - - struct v4l2_subdev subdev; - struct media_pad pad; - struct v4l2_ctrl_handler ctrl_handler; - struct v4l2_ctrl *pixel_rate; - struct v4l2_ctrl *link_freq; - struct mutex mutex; - bool power_on; - struct tp2855_mode *cur_mode; - - u32 module_index; - u32 cfg_num; - const char *module_facing; - const char *module_name; - const char *len_name; - bool lost_video_status; - struct task_struct *detect_thread; - - int streaming; -}; - - -#define to_tp2855(sd) container_of(sd, struct tp2855, subdev) - -static __maybe_unused const struct regval common_setting_297M_720p_25fps_regs[] = { - {0x40, 0x04}, - {0xf5, 0x00}, - {0x02, 0x42}, - {0x07, 0xc0}, - {0x0b, 0xc0}, - {0x0c, 0x13}, - {0x0d, 0x50}, - {0x15, 0x13}, - {0x16, 0x15}, - {0x17, 0x00}, - {0x18, 0x19}, - {0x19, 0xd0}, - {0x1a, 0x25}, - {0x1c, 0x07}, - {0x1d, 0xbc}, - {0x20, 0x30}, - {0x21, 0x84}, - {0x22, 0x36}, - {0x23, 0x3c}, -#if TP2855_TEST_PATTERN - {0x2a, 0x3c}, //vi test -#endif - {0x2b, 0x60}, - {0x2c, 0x0a}, - {0x2d, 0x30}, - {0x2e, 0x70}, - {0x30, 0x48}, - {0x31, 0xbb}, - {0x32, 0x2e}, - {0x33, 0x90}, - {0x35, 0x25}, - {0x38, 0x00}, - {0x39, 0x18}, - - {0x40, 0x08}, - {0x01, 0xf0}, - {0x02, 0x01}, - {0x08, 0x0f}, - {0x20, 0x44}, - {0x34, 0xe4}, - {0x14, 0x44}, - {0x15, 0x0d}, - {0x25, 0x04}, - {0x26, 0x03}, - {0x27, 0x09}, - {0x29, 0x02}, - {0x33, 0x07}, - {0x33, 0x00}, - {0x14, 0xc4}, - {0x14, 0x44}, - - // {0x23, 0x02}, //vi test ok - // {0x23, 0x00}, -}; - -static __maybe_unused const struct regval common_setting_594M_1080p_25fps_regs[] = { - {0x40, 0x04}, - {0xf5, 0x00}, - {0x02, 0x40}, - {0x07, 0xc0}, - {0x0b, 0xc0}, - {0x0c, 0x03}, - {0x0d, 0x50}, - {0x15, 0x03}, - {0x16, 0xd2}, - {0x17, 0x80}, - {0x18, 0x29}, - {0x19, 0x38}, - {0x1a, 0x47}, - {0x1c, 0x0a}, - {0x1d, 0x50}, - {0x20, 0x30}, - {0x21, 0x84}, - {0x22, 0x36}, - {0x23, 0x3c}, -#if TP2855_TEST_PATTERN - {0x2a, 0x3c}, //vi test -#endif - {0x2b, 0x60}, - {0x2c, 0x0a}, - {0x2d, 0x30}, - {0x2e, 0x70}, - {0x30, 0x48}, - {0x31, 0xbb}, - {0x32, 0x2e}, - {0x33, 0x90}, - {0x35, 0x05}, - {0x38, 0x00}, - {0x39, 0x1C}, - - {0x40, 0x08}, - {0x01, 0xf0}, - {0x02, 0x01}, - {0x08, 0x0f}, - {0x20, 0x44}, - {0x34, 0xe4}, - {0x15, 0x0C}, - {0x25, 0x08}, - {0x26, 0x06}, - {0x27, 0x11}, - {0x29, 0x0a}, - {0x33, 0x07}, - {0x33, 0x00}, - {0x14, 0x33}, - {0x14, 0xb3}, - {0x14, 0x33}, - // {0x23, 0x02}, //vi test ok - // {0x23, 0x00}, -}; - -static struct tp2855_mode supported_modes[] = { - { - .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, - .width = 1920, - .height = 1080, - .max_fps = { - .numerator = 10000, - .denominator = 250000, - }, - .global_reg_list = common_setting_594M_1080p_25fps_regs, - .mipi_freq_idx = 0, - .bpp = 8, - .vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_0, - .vc[PAD1] = V4L2_MBUS_CSI2_CHANNEL_1, - .vc[PAD2] = V4L2_MBUS_CSI2_CHANNEL_2, - .vc[PAD3] = V4L2_MBUS_CSI2_CHANNEL_3, - }, - { - .bus_fmt = MEDIA_BUS_FMT_UYVY8_2X8, - .width = 1280, - .height = 720, - .max_fps = { - .numerator = 10000, - .denominator = 250000, - }, - .global_reg_list = common_setting_297M_720p_25fps_regs, - .mipi_freq_idx = 1, - .bpp = 8, - .vc[PAD0] = V4L2_MBUS_CSI2_CHANNEL_0, - .vc[PAD1] = V4L2_MBUS_CSI2_CHANNEL_1, - .vc[PAD2] = V4L2_MBUS_CSI2_CHANNEL_2, - .vc[PAD3] = V4L2_MBUS_CSI2_CHANNEL_3, - } -}; - -static const s64 link_freq_items[] = { - TP2855_LINK_FREQ_594M, - TP2855_LINK_FREQ_297M, -}; - -/* sensor register write */ -static int tp2855_write_reg(struct i2c_client *client, u8 reg, u8 val) -{ - struct i2c_msg msg; - u8 buf[2]; - int ret; - - buf[0] = reg & 0xFF; - buf[1] = val; - - msg.addr = client->addr; - msg.flags = client->flags; - msg.buf = buf; - msg.len = sizeof(buf); - - ret = i2c_transfer(client->adapter, &msg, 1); - if (ret >= 0) { - usleep_range(300, 400); - return 0; - } - - dev_err(&client->dev, - "tp2855 write reg(0x%x val:0x%x) failed !\n", reg, val); - - return ret; -} - -static int tp2855_write_array(struct i2c_client *client, - const struct regval *regs, int size) -{ - int i, ret = 0; - - i = 0; - while (i < size) { - ret = tp2855_write_reg(client, regs[i].addr, regs[i].val); - if (ret) { - dev_err(&client->dev, "%s failed !\n", __func__); - break; - } - i++; - } - - return ret; -} - -/* sensor register read */ -static int tp2855_read_reg(struct i2c_client *client, u8 reg, u8 *val) -{ - struct i2c_msg msg[2]; - u8 buf[1]; - int ret; - - buf[0] = reg & 0xFF; - - msg[0].addr = client->addr; - msg[0].flags = client->flags; - msg[0].buf = buf; - msg[0].len = sizeof(buf); - - msg[1].addr = client->addr; - msg[1].flags = client->flags | I2C_M_RD; - msg[1].buf = buf; - msg[1].len = 1; - - ret = i2c_transfer(client->adapter, msg, 2); - if (ret >= 0) { - *val = buf[0]; - return 0; - } - - dev_err(&client->dev, "tp2855 read reg(0x%x) failed !\n", reg); - - return ret; -} - -static int tp2855_get_reso_dist(const struct tp2855_mode *mode, - struct v4l2_mbus_framefmt *framefmt) -{ - return abs(mode->width - framefmt->width) + - abs(mode->height - framefmt->height); -} - -static const struct tp2855_mode * -tp2855_find_best_fit(struct tp2855 *tp2855, - 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; - unsigned int i; - - for (i = 0; i < tp2855->cfg_num; i++) { - dist = tp2855_get_reso_dist(&supported_modes[i], framefmt); - if ((cur_best_fit_dist == -1 || dist <= cur_best_fit_dist) && - supported_modes[i].bus_fmt == framefmt->code) { - cur_best_fit_dist = dist; - cur_best_fit = i; - } - } - - return &supported_modes[cur_best_fit]; -} - -static int tp2855_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct tp2855 *tp2855 = to_tp2855(sd); - const struct tp2855_mode *mode; - u64 pixel_rate; - - mutex_lock(&tp2855->mutex); - - mode = tp2855_find_best_fit(tp2855, fmt); - fmt->format.code = mode->bus_fmt; - fmt->format.width = mode->width; - fmt->format.height = mode->height; - fmt->format.field = V4L2_FIELD_NONE; - fmt->format.colorspace = V4L2_COLORSPACE_SRGB; - - 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(&tp2855->mutex); - return -ENOTTY; -#endif - } else { - __v4l2_ctrl_s_ctrl(tp2855->link_freq, mode->mipi_freq_idx); - pixel_rate = (u32)link_freq_items[mode->mipi_freq_idx] / mode->bpp * 2 * TP2855_LANES; - __v4l2_ctrl_s_ctrl_int64(tp2855->pixel_rate, pixel_rate); - dev_dbg(&tp2855->client->dev, "mipi_freq_idx %d\n", mode->mipi_freq_idx); - dev_dbg(&tp2855->client->dev, "pixel_rate %lld\n", pixel_rate); - } - - mutex_unlock(&tp2855->mutex); - return 0; -} - -static int tp2855_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - struct tp2855 *tp2855 = to_tp2855(sd); - struct i2c_client *client = tp2855->client; - const struct tp2855_mode *mode = tp2855->cur_mode; - - mutex_lock(&tp2855->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(&tp2855->mutex); - return -ENOTTY; -#endif - } else { - fmt->format.width = mode->width; - fmt->format.height = mode->height; - fmt->format.code = mode->bus_fmt; - fmt->format.field = V4L2_FIELD_NONE; - if (fmt->pad < PAD_MAX && fmt->pad >= PAD0) - fmt->reserved[0] = mode->vc[fmt->pad]; - else - fmt->reserved[0] = mode->vc[PAD0]; - } - mutex_unlock(&tp2855->mutex); - - dev_dbg(&client->dev, "%s: %x %dx%d\n", - __func__, fmt->format.code, - fmt->format.width, fmt->format.height); - - return 0; -} - - -static int tp2855_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_mbus_code_enum *code) -{ - struct tp2855 *tp2855 = to_tp2855(sd); - - if (code->index != 0) - return -EINVAL; - code->code = tp2855->cur_mode->bus_fmt; - - return 0; -} - -static int tp2855_enum_frame_sizes(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_frame_size_enum *fse) -{ - struct tp2855 *tp2855 = to_tp2855(sd); - struct i2c_client *client = tp2855->client; - - dev_dbg(&client->dev, "%s:\n", __func__); - - if (fse->index >= tp2855->cfg_num) - return -EINVAL; - - if (fse->code != supported_modes[fse->index].bus_fmt) - return -EINVAL; - - fse->min_width = supported_modes[fse->index].width; - fse->max_width = supported_modes[fse->index].width; - fse->max_height = supported_modes[fse->index].height; - fse->min_height = supported_modes[fse->index].height; - return 0; -} - -static int tp2855_g_mbus_config(struct v4l2_subdev *sd, - struct v4l2_mbus_config *cfg) -{ - cfg->type = V4L2_MBUS_CSI2; - cfg->flags = V4L2_MBUS_CSI2_4_LANE | - V4L2_MBUS_CSI2_CHANNELS; - - return 0; -} - -static void tp2855_get_module_inf(struct tp2855 *tp2855, - struct rkmodule_inf *inf) -{ - memset(inf, 0, sizeof(*inf)); - strlcpy(inf->base.sensor, TP2855_NAME, sizeof(inf->base.sensor)); - strlcpy(inf->base.module, tp2855->module_name, - sizeof(inf->base.module)); - strlcpy(inf->base.lens, tp2855->len_name, sizeof(inf->base.lens)); -} - -static long tp2855_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) -{ - struct tp2855 *tp2855 = to_tp2855(sd); - long ret = 0; - - switch (cmd) { - case RKMODULE_GET_MODULE_INFO: - tp2855_get_module_inf(tp2855, (struct rkmodule_inf *)arg); - break; - default: - ret = -ENOTTY; - break; - } - - return ret; -} - -#ifdef CONFIG_COMPAT -static long tp2855_compat_ioctl32(struct v4l2_subdev *sd, - unsigned int cmd, unsigned long arg) -{ - void __user *up = compat_ptr(arg); - struct rkmodule_inf *inf; - struct rkmodule_awb_cfg *cfg; - long ret = 0; - - switch (cmd) { - case RKMODULE_GET_MODULE_INFO: - inf = kzalloc(sizeof(*inf), GFP_KERNEL); - if (!inf) { - ret = -ENOMEM; - return ret; - } - - ret = tp2855_ioctl(sd, cmd, inf); - if (!ret) - ret = copy_to_user(up, inf, sizeof(*inf)); - kfree(inf); - break; - case RKMODULE_AWB_CFG: - cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); - if (!cfg) { - ret = -ENOMEM; - return ret; - } - - ret = copy_from_user(cfg, up, sizeof(*cfg)); - if (!ret) - ret = tp2855_ioctl(sd, cmd, cfg); - kfree(cfg); - break; - default: - ret = -ENOIOCTLCMD; - break; - } - - return ret; -} -#endif - -static int detect_thread_function(void *data) -{ - struct tp2855 *tp2855 = (struct tp2855 *) data; - struct i2c_client *client = tp2855->client; - u8 detect_status = 0, reg26_val = 0; - bool lost_video = false; - tp2855->lost_video_status = true; - while (!kthread_should_stop()) { - if (tp2855->power_on) { - tp2855_write_reg(client, 0x40, 0x04); - tp2855_read_reg(client, 0x01, &detect_status); - tp2855_read_reg(client, 0x26, ®26_val); - lost_video = (detect_status & 0x80) ? true : false; - if (tp2855->lost_video_status != lost_video) { - if (lost_video) { - tp2855_write_reg(client, 0x26, (reg26_val & 0xfe)); - } else { - tp2855_write_reg(client, 0x26, (reg26_val | 0x01)); - } - tp2855->lost_video_status = lost_video; - tp2855_read_reg(client, 0x26, ®26_val); - dev_err(&client->dev, "tp2855 detect video lost status %d reg26_val %x\n", - lost_video, reg26_val); - } - } - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(msecs_to_jiffies(200)); - } - return 0; -} - -static int __maybe_unused detect_thread_start(struct tp2855 *tp2855) { - int ret = 0; - struct i2c_client *client = tp2855->client; - tp2855->detect_thread = kthread_create(detect_thread_function, - tp2855, "tp2855_kthread"); - if (IS_ERR(tp2855->detect_thread)) { - dev_err(&client->dev, "kthread_create tp2855_kthread failed\n"); - ret = PTR_ERR(tp2855->detect_thread); - tp2855->detect_thread = NULL; - return ret; - } - wake_up_process(tp2855->detect_thread); - return ret; -} - -static int __maybe_unused detect_thread_stop(struct tp2855 *tp2855) { - if (tp2855->detect_thread) - kthread_stop(tp2855->detect_thread); - tp2855->detect_thread = NULL; - return 0; -} - -static int __tp2855_start_stream(struct tp2855 *tp2855) -{ - int ret; - int array_size = 0; - struct i2c_client *client = tp2855->client; - - if (tp2855->cur_mode->global_reg_list == common_setting_594M_1080p_25fps_regs) { - array_size = ARRAY_SIZE(common_setting_594M_1080p_25fps_regs); - } else if (tp2855->cur_mode->global_reg_list == common_setting_297M_720p_25fps_regs) { - array_size = ARRAY_SIZE(common_setting_297M_720p_25fps_regs); - } else { - return -1; - } - - ret = tp2855_write_array(tp2855->client, - tp2855->cur_mode->global_reg_list, array_size); - if (ret) { - dev_err(&client->dev, "__tp2855_start_stream global_reg_list faild"); - return ret; - } - - detect_thread_start(tp2855); - - return 0; -} - -static int __tp2855_stop_stream(struct tp2855 *tp2855) -{ - struct i2c_client *client = tp2855->client; - - tp2855_write_reg(client, 0x40, 0x08); - tp2855_write_reg(client, 0x23, 0x02); - detect_thread_stop(tp2855); - - return 0; -} - -static int tp2855_stream(struct v4l2_subdev *sd, int on) -{ - struct tp2855 *tp2855 = to_tp2855(sd); - struct i2c_client *client = tp2855->client; - - dev_dbg(&client->dev, "s_stream: %d. %dx%d\n", on, - tp2855->cur_mode->width, - tp2855->cur_mode->height); - - mutex_lock(&tp2855->mutex); - on = !!on; - if (tp2855->streaming == on) - goto unlock; - - if (on) { - __tp2855_start_stream(tp2855); - } else { - __tp2855_stop_stream(tp2855); - } - - tp2855->streaming = on; - -unlock: - mutex_unlock(&tp2855->mutex); - - return 0; -} - -static int tp2855_power(struct v4l2_subdev *sd, int on) -{ - struct tp2855 *tp2855 = to_tp2855(sd); - struct i2c_client *client = tp2855->client; - int ret = 0; - - mutex_lock(&tp2855->mutex); - - /* If the power state is not modified - no work to do. */ - if (tp2855->power_on == !!on) - goto exit; - - dev_dbg(&client->dev, "%s: on %d\n", __func__, on); - - if (on) { - ret = pm_runtime_get_sync(&client->dev); - if (ret < 0) { - pm_runtime_put_noidle(&client->dev); - goto exit; - } - tp2855->power_on = true; - } else { - pm_runtime_put(&client->dev); - tp2855->power_on = false; - } - -exit: - mutex_unlock(&tp2855->mutex); - - return ret; -} - -static int __tp2855_power_on(struct tp2855 *tp2855) -{ - int ret; - struct device *dev = &tp2855->client->dev; - - dev_dbg(dev, "%s\n", __func__); - - if (!IS_ERR_OR_NULL(tp2855->pins_default)) { - ret = pinctrl_select_state(tp2855->pinctrl, - tp2855->pins_default); - if (ret < 0) - dev_err(dev, "could not set pins. ret=%d\n", ret); - } - - if (!IS_ERR(tp2855->power_gpio)) { - gpiod_set_value_cansleep(tp2855->power_gpio, 1); - usleep_range(25*1000, 30*1000); - } - - usleep_range(1500, 2000); - - ret = clk_set_rate(tp2855->xvclk, TP2855_XVCLK_FREQ); - if (ret < 0) - dev_warn(dev, "Failed to set xvclk rate\n"); - if (clk_get_rate(tp2855->xvclk) != TP2855_XVCLK_FREQ) - dev_warn(dev, "xvclk mismatched\n"); - ret = clk_prepare_enable(tp2855->xvclk); - if (ret < 0) { - dev_err(dev, "Failed to enable xvclk\n"); - goto err_clk; - } - - if (!IS_ERR(tp2855->reset_gpio)) { - gpiod_set_value_cansleep(tp2855->reset_gpio, 1); - usleep_range(10*1000, 20*1000); - gpiod_set_value_cansleep(tp2855->reset_gpio, 0); - usleep_range(10*1000, 20*1000); - } - - usleep_range(10*1000, 20*1000); - - return 0; - -err_clk: - if (!IS_ERR(tp2855->reset_gpio)) - gpiod_set_value_cansleep(tp2855->reset_gpio, 0); - - if (!IS_ERR_OR_NULL(tp2855->pins_sleep)) - pinctrl_select_state(tp2855->pinctrl, tp2855->pins_sleep); - - return ret; -} - -static void __tp2855_power_off(struct tp2855 *tp2855) -{ - int ret; - struct device *dev = &tp2855->client->dev; - - dev_dbg(dev, "%s\n", __func__); - - if (!IS_ERR(tp2855->reset_gpio)) - gpiod_set_value_cansleep(tp2855->reset_gpio, 0); - clk_disable_unprepare(tp2855->xvclk); - - if (!IS_ERR_OR_NULL(tp2855->pins_sleep)) { - ret = pinctrl_select_state(tp2855->pinctrl, - tp2855->pins_sleep); - if (ret < 0) - dev_dbg(dev, "could not set pins\n"); - } - - if (!IS_ERR(tp2855->power_gpio)) - gpiod_set_value_cansleep(tp2855->power_gpio, 0); -} - -static int tp2855_initialize_controls(struct tp2855 *tp2855) -{ - const struct tp2855_mode *mode; - struct v4l2_ctrl_handler *handler; - u64 pixel_rate; - int ret; - - handler = &tp2855->ctrl_handler; - mode = tp2855->cur_mode; - ret = v4l2_ctrl_handler_init(handler, 2); - if (ret) - return ret; - handler->lock = &tp2855->mutex; - - tp2855->link_freq = v4l2_ctrl_new_int_menu(handler, NULL, - V4L2_CID_LINK_FREQ, - ARRAY_SIZE(link_freq_items) - 1, 0, - link_freq_items); - __v4l2_ctrl_s_ctrl(tp2855->link_freq, mode->mipi_freq_idx); - - /* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */ - pixel_rate = (u32)link_freq_items[mode->mipi_freq_idx] / mode->bpp * 2 * TP2855_LANES; - tp2855->pixel_rate = v4l2_ctrl_new_std(handler, NULL, - V4L2_CID_PIXEL_RATE, 0, pixel_rate, - 1, pixel_rate); - - - if (handler->error) { - ret = handler->error; - dev_err(&tp2855->client->dev, - "Failed to init controls(%d)\n", ret); - goto err_free_handler; - } - - tp2855->subdev.ctrl_handler = handler; - - return 0; - -err_free_handler: - v4l2_ctrl_handler_free(handler); - - return ret; -} - -static int tp2855_runtime_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct tp2855 *tp2855 = to_tp2855(sd); - - return __tp2855_power_on(tp2855); -} - -static int tp2855_runtime_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct tp2855 *tp2855 = to_tp2855(sd); - - __tp2855_power_off(tp2855); - - return 0; -} - -#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API -static int tp2855_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct tp2855 *tp2855 = to_tp2855(sd); - struct v4l2_mbus_framefmt *try_fmt = - v4l2_subdev_get_try_format(sd, fh->pad, 0); - const struct tp2855_mode *def_mode = &supported_modes[0]; - - dev_dbg(&tp2855->client->dev, "%s\n", __func__); - - mutex_lock(&tp2855->mutex); - /* Initialize try_fmt */ - try_fmt->width = def_mode->width; - try_fmt->height = def_mode->height; - try_fmt->code = def_mode->bus_fmt; - try_fmt->field = V4L2_FIELD_NONE; - - mutex_unlock(&tp2855->mutex); - /* No crop or compose */ - - return 0; -} -#endif - -#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API -static const struct v4l2_subdev_internal_ops tp2855_internal_ops = { - .open = tp2855_open, -}; -#endif - -static const struct v4l2_subdev_video_ops tp2855_video_ops = { - .s_stream = tp2855_stream, - .g_mbus_config = tp2855_g_mbus_config, -}; - -static const struct v4l2_subdev_pad_ops tp2855_subdev_pad_ops = { - .enum_mbus_code = tp2855_enum_mbus_code, - .enum_frame_size = tp2855_enum_frame_sizes, - .get_fmt = tp2855_get_fmt, - .set_fmt = tp2855_set_fmt, -}; - -static const struct v4l2_subdev_core_ops tp2855_core_ops = { - .s_power = tp2855_power, - .ioctl = tp2855_ioctl, -#ifdef CONFIG_COMPAT - .compat_ioctl32 = tp2855_compat_ioctl32, -#endif -}; - -static const struct v4l2_subdev_ops tp2855_subdev_ops = { - .core = &tp2855_core_ops, - .video = &tp2855_video_ops, - .pad = &tp2855_subdev_pad_ops, -}; - -static int check_chip_id(struct i2c_client *client){ - struct device *dev = &client->dev; - unsigned char chip_id = 0xFF; - tp2855_read_reg(client, 0x34, &chip_id); - dev_err(dev, "chip_id : 0x%2x\n", chip_id); - if (chip_id != 0x0) { - return -1; - } - return 0; -} - -static int tp2855_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct device *dev = &client->dev; - struct device_node *node = dev->of_node; - struct tp2855 *tp2855; - struct v4l2_subdev *sd; - __maybe_unused char facing[2]; - int ret; - - dev_info(dev, "driver version: %02x.%02x.%02x", - DRIVER_VERSION >> 16, - (DRIVER_VERSION & 0xff00) >> 8, - DRIVER_VERSION & 0x00ff); - - tp2855 = devm_kzalloc(dev, sizeof(*tp2855), GFP_KERNEL); - if (!tp2855) - return -ENOMEM; - - ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX, - &tp2855->module_index); - ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING, - &tp2855->module_facing); - ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME, - &tp2855->module_name); - ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME, - &tp2855->len_name); - if (ret) { - dev_err(dev, "could not get %s!\n", RKMODULE_CAMERA_LENS_NAME); - return -EINVAL; - } - - tp2855->client = client; - tp2855->cur_mode = &supported_modes[0]; - - tp2855->xvclk = devm_clk_get(dev, "xvclk"); - if (IS_ERR(tp2855->xvclk)) { - dev_err(dev, "Failed to get xvclk\n"); - return -EINVAL; - } - - tp2855->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(tp2855->reset_gpio)) - dev_warn(dev, "Failed to get reset-gpios\n"); - - tp2855->power_gpio = devm_gpiod_get(dev, "power", GPIOD_OUT_LOW); - if (IS_ERR(tp2855->power_gpio)) - dev_warn(dev, "Failed to get power-gpios\n"); - - tp2855->pinctrl = devm_pinctrl_get(dev); - if (!IS_ERR(tp2855->pinctrl)) { - tp2855->pins_default = - pinctrl_lookup_state(tp2855->pinctrl, - OF_CAMERA_PINCTRL_STATE_DEFAULT); - if (IS_ERR(tp2855->pins_default)) - dev_info(dev, "could not get default pinstate\n"); - - tp2855->pins_sleep = - pinctrl_lookup_state(tp2855->pinctrl, - OF_CAMERA_PINCTRL_STATE_SLEEP); - if (IS_ERR(tp2855->pins_sleep)) - dev_info(dev, "could not get sleep pinstate\n"); - } else { - dev_info(dev, "no pinctrl\n"); - } - - mutex_init(&tp2855->mutex); - - sd = &tp2855->subdev; - v4l2_i2c_subdev_init(sd, client, &tp2855_subdev_ops); - ret = tp2855_initialize_controls(tp2855); - if (ret) { - dev_err(dev, "Failed to initialize controls tp2855\n"); - goto err_destroy_mutex; - } - - ret = __tp2855_power_on(tp2855); - if (ret) { - dev_err(dev, "Failed to power on tp2855\n"); - goto err_free_handler; - } - - ret = check_chip_id(client); - if (ret) { - dev_err(dev, "Failed to check senosr id\n"); - goto err_free_handler; - } - -#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API - sd->internal_ops = &tp2855_internal_ops; - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -#endif - -#if defined(CONFIG_MEDIA_CONTROLLER) - tp2855->pad.flags = MEDIA_PAD_FL_SOURCE; - sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; - ret = media_entity_pads_init(&sd->entity, 1, &tp2855->pad); - if (ret < 0) - goto err_power_off; -#endif - - memset(facing, 0, sizeof(facing)); - if (strcmp(tp2855->module_facing, "back") == 0) - facing[0] = 'b'; - else - facing[0] = 'f'; - - snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", - tp2855->module_index, facing, - TP2855_NAME, dev_name(sd->dev)); - - ret = v4l2_async_register_subdev_sensor_common(sd); - 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(&sd->entity); -#endif -err_power_off: - __tp2855_power_off(tp2855); -err_free_handler: - v4l2_ctrl_handler_free(&tp2855->ctrl_handler); -err_destroy_mutex: - mutex_destroy(&tp2855->mutex); - - return ret; -} - -static int tp2855_remove(struct i2c_client *client) -{ - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct tp2855 *tp2855 = to_tp2855(sd); - - v4l2_async_unregister_subdev(sd); -#if defined(CONFIG_MEDIA_CONTROLLER) - media_entity_cleanup(&sd->entity); -#endif - v4l2_ctrl_handler_free(&tp2855->ctrl_handler); - mutex_destroy(&tp2855->mutex); - - pm_runtime_disable(&client->dev); - if (!pm_runtime_status_suspended(&client->dev)) - __tp2855_power_off(tp2855); - pm_runtime_set_suspended(&client->dev); - - return 0; -} - -static const struct dev_pm_ops tp2855_pm_ops = { - SET_RUNTIME_PM_OPS(tp2855_runtime_suspend, - tp2855_runtime_resume, NULL) -}; - -#if IS_ENABLED(CONFIG_OF) -static const struct of_device_id tp2855_of_match[] = { - { .compatible = "tp2855" }, - {}, -}; -MODULE_DEVICE_TABLE(of, tp2855_of_match); -#endif - -static const struct i2c_device_id tp2855_match_id[] = { - { "tp2855", 0 }, - { }, -}; - -static struct i2c_driver tp2855_i2c_driver = { - .driver = { - .name = TP2855_NAME, - .pm = &tp2855_pm_ops, - .of_match_table = of_match_ptr(tp2855_of_match), - }, - .probe = &tp2855_probe, - .remove = &tp2855_remove, - .id_table = tp2855_match_id, -}; - -static int __init sensor_mod_init(void) -{ - return i2c_add_driver(&tp2855_i2c_driver); -} - -static void __exit sensor_mod_exit(void) -{ - i2c_del_driver(&tp2855_i2c_driver); -} - -device_initcall_sync(sensor_mod_init); -module_exit(sensor_mod_exit); - -MODULE_AUTHOR("Vicent Chi "); -MODULE_DESCRIPTION("tp2855 sensor driver"); -MODULE_LICENSE("GPL v2"); \ No newline at end of file