// SPDX-License-Identifier: GPL-2.0 /* * vehicle sensor adv7181 * * Copyright (C) 2022 Rockchip Electronics Co.Ltd * Authors: * Zhiqin Wei * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vehicle_cfg.h" #include "vehicle_main.h" #include "vehicle_ad.h" #include "vehicle_ad_7181.h" enum { FORCE_PAL_WIDTH = 720, FORCE_PAL_HEIGHT = 576, FORCE_NTSC_WIDTH = 720, FORCE_NTSC_HEIGHT = 480, FORCE_CIF_OUTPUT_FORMAT = CIF_OUTPUT_FORMAT_420, }; static struct vehicle_ad_dev *ad7181_g_addev; static v4l2_std_id std_old = V4L2_STD_NTSC; #define SENSOR_REGISTER_LEN 1 /* sensor register address bytes*/ #define SENSOR_VALUE_LEN 1 /* sensor register value bytes*/ struct rk_sensor_reg { unsigned int reg; unsigned int val; }; #define ADV7181_STATUS1_REG 0x10 #define ADV7181_STATUS1_IN_LOCK 0x01 #define ADV7181_STATUS1_AUTOD_MASK 0x70 #define ADV7181_STATUS1_AUTOD_NTSM_M_J 0x00 #define ADV7181_STATUS1_AUTOD_NTSC_4_43 0x10 #define ADV7181_STATUS1_AUTOD_PAL_M 0x20 #define ADV7181_STATUS1_AUTOD_PAL_60 0x30 #define ADV7181_STATUS1_AUTOD_PAL_B_G 0x40 #define ADV7181_STATUS1_AUTOD_SECAM 0x50 #define ADV7181_STATUS1_AUTOD_PAL_COMB 0x60 #define ADV7181_STATUS1_AUTOD_SECAM_525 0x70 #define ADV7181_INPUT_CONTROL 0x00 #define ADV7181_INPUT_DEFAULT 0x00 #define ADV7181_INPUT_CVBS_AIN2 0x00 #define ADV7181_INPUT_CVBS_AIN3 0x01 #define ADV7181_INPUT_CVBS_AIN5 0x02 #define ADV7181_INPUT_CVBS_AIN6 0x03 #define ADV7181_INPUT_CVBS_AIN8 0x04 #define ADV7181_INPUT_CVBS_AIN10 0x05 #define ADV7181_INPUT_CVBS_AIN1 0x0B #define ADV7181_INPUT_CVBS_AIN4 0x0D #define ADV7181_INPUT_CVBS_AIN7 0x0F #define ADV7181_INPUT_YPRPB_AIN6_8_10 0x00 #define SEQCMD_END 0xFF000000 #define SensorEnd {SEQCMD_END, 0x00} #define SENSOR_DG VEHICLE_DG /* Preview resolution setting*/ static struct rk_sensor_reg sensor_preview_data[] = { /* autodetect cvbs in ntsc/pal/secam 8-bit 422 encode */ {0x00, 0x0B}, /*cvbs in AIN1*/ {0x04, 0x77}, {0x17, 0x41}, {0x1D, 0x47}, {0x31, 0x02}, {0x3A, 0x17}, {0x3B, 0x81}, {0x3D, 0xA2}, {0x3E, 0x6A}, {0x3F, 0xA0}, {0x86, 0x0B}, {0xF3, 0x01}, {0xF9, 0x03}, {0x0E, 0x80}, {0x52, 0x46}, {0x54, 0x80}, {0x7F, 0xFF}, {0x81, 0x30}, {0x90, 0xC9}, {0x91, 0x40}, {0x92, 0x3C}, {0x93, 0xCA}, {0x94, 0xD5}, {0xB1, 0xFF}, {0xB6, 0x08}, {0xC0, 0x9A}, {0xCF, 0x50}, {0xD0, 0x4E}, {0xD1, 0xB9}, {0xD6, 0xDD}, {0xD7, 0xE2}, {0xE5, 0x51}, {0xF6, 0x3B}, {0x0E, 0x00}, {0x03, 0x4C}, //stream off {0xDF, 0X46}, {0xC9, 0x04}, {0xC5, 0x81}, {0xC4, 0x34}, {0xBf, 0x02}, {0xB5, 0x83}, {0xB6, 0x00}, {0xaf, 0x03}, {0xae, 0x00}, {0xac, 0x00}, {0xAB, 0x00}, {0xa1, 0xFF}, {0xA2, 0x00}, {0xA3, 0x00}, {0xA4, 0x00}, {0xa5, 0x01}, {0xA6, 0x00}, {0xA6, 0x00}, {0xA7, 0x00}, {0xA8, 0x00}, {0xa0, 0x03}, {0x98, 0X00}, {0x97, 0X00}, {0X90, 0X00}, {0X85, 0X02}, {0x7B, 0x1E}, {0x74, 0x04}, {0x75, 0x01}, {0x76, 0x00}, {0x6B, 0xC0}, {0x67, 0x03}, {0x3C, 0x58}, {0x30, 0x4C}, {0x2E, 0X9F}, {0x12, 0XC0}, {0x10, 0X0D}, {0x05, 0X00}, {0x06, 0X02}, {0x60, 0x01}, SensorEnd }; static struct rk_sensor_reg sensor_preview_data_yprpb_p[] = { {0x05, 0x01}, {0x06, 0x06}, {0xc3, 0x56}, {0xc4, 0xb4}, {0x1d, 0x47}, {0x3a, 0x11}, {0x3b, 0x81}, {0x3c, 0x3b}, {0x6b, 0x83}, {0xc9, 0x00}, {0x73, 0x10}, {0x74, 0xa3}, {0x75, 0xe8}, {0x76, 0xfa}, {0x7b, 0x1c}, {0x85, 0x19}, {0x86, 0x0b}, {0xbf, 0x06}, {0xc0, 0x40}, {0xc1, 0xf0}, {0xc2, 0x80}, {0xc5, 0x01}, {0xc9, 0x08}, {0x0e, 0x80}, {0x52, 0x46}, {0x54, 0x80}, {0x57, 0x01}, {0xf6, 0x3b}, {0x0e, 0x00}, {0x67, 0x2f}, {0x03, 0x4C}, //disable out put SensorEnd }; static v4l2_std_id adv7181_std_to_v4l2(u8 status1) { /* in case V4L2_IN_ST_NO_SIGNAL */ if (!(status1 & ADV7181_STATUS1_IN_LOCK)) return V4L2_STD_UNKNOWN; switch (status1 & ADV7181_STATUS1_AUTOD_MASK) { case ADV7181_STATUS1_AUTOD_PAL_M: case ADV7181_STATUS1_AUTOD_NTSM_M_J: return V4L2_STD_NTSC; case ADV7181_STATUS1_AUTOD_NTSC_4_43: return V4L2_STD_NTSC_443; case ADV7181_STATUS1_AUTOD_PAL_60: return V4L2_STD_PAL_60; case ADV7181_STATUS1_AUTOD_PAL_B_G: return V4L2_STD_PAL; case ADV7181_STATUS1_AUTOD_SECAM: return V4L2_STD_SECAM; case ADV7181_STATUS1_AUTOD_PAL_COMB: return V4L2_STD_PAL_Nc | V4L2_STD_PAL_N; case ADV7181_STATUS1_AUTOD_SECAM_525: return V4L2_STD_SECAM; default: return V4L2_STD_UNKNOWN; } } static u32 adv7181_status_to_v4l2(u8 status1) { if (!(status1 & ADV7181_STATUS1_IN_LOCK)) return V4L2_IN_ST_NO_SIGNAL; return 0; } static int adv7181_vehicle_status(struct vehicle_ad_dev *ad, u32 *status, v4l2_std_id *std) { unsigned char status1 = 0; status1 = vehicle_generic_sensor_read(ad, ADV7181_STATUS1_REG); if (status1) return status1; if (status) *status = adv7181_status_to_v4l2(status1); if (std) *std = adv7181_std_to_v4l2(status1); return 0; } static void adv7181_reinit_parameter(struct vehicle_ad_dev *ad, v4l2_std_id std) { int i; if (ad7181_g_addev->ad_chl == 0) { ad->cfg.width = 1024; ad->cfg.height = 500; ad->cfg.start_x = 56; ad->cfg.start_y = 0; ad->cfg.input_format = CIF_INPUT_FORMAT_YUV; ad->cfg.output_format = FORCE_CIF_OUTPUT_FORMAT; ad->cfg.field_order = 0; ad->cfg.yuv_order = 1; ad->cfg.href = 0; ad->cfg.vsync = 0; ad->cfg.frame_rate = 60; ad->cfg.type = V4L2_MBUS_PARALLEL; ad->cfg.mbus_flags = V4L2_MBUS_HSYNC_ACTIVE_LOW | V4L2_MBUS_VSYNC_ACTIVE_LOW | V4L2_MBUS_PCLK_SAMPLE_RISING; } else if (std == V4L2_STD_PAL) { ad->cfg.width = FORCE_PAL_WIDTH; ad->cfg.height = FORCE_PAL_HEIGHT; ad->cfg.start_x = 0; ad->cfg.start_y = 0; ad->cfg.input_format = CIF_INPUT_FORMAT_PAL; ad->cfg.output_format = FORCE_CIF_OUTPUT_FORMAT; ad->cfg.field_order = 0; ad->cfg.yuv_order = 0; ad->cfg.href = 0; ad->cfg.vsync = 0; ad->cfg.frame_rate = 25; ad->cfg.type = V4L2_MBUS_PARALLEL; ad->cfg.mbus_flags = V4L2_MBUS_HSYNC_ACTIVE_LOW | V4L2_MBUS_VSYNC_ACTIVE_LOW | V4L2_MBUS_PCLK_SAMPLE_RISING; } else { ad->cfg.width = FORCE_NTSC_WIDTH; ad->cfg.height = FORCE_NTSC_HEIGHT; ad->cfg.start_x = 0; ad->cfg.start_y = 0; ad->cfg.input_format = CIF_INPUT_FORMAT_NTSC; ad->cfg.output_format = FORCE_CIF_OUTPUT_FORMAT; ad->cfg.field_order = 0; ad->cfg.yuv_order = 2; ad->cfg.href = 0; ad->cfg.vsync = 0; ad->cfg.frame_rate = 30; ad->cfg.type = V4L2_MBUS_PARALLEL; ad->cfg.mbus_flags = V4L2_MBUS_HSYNC_ACTIVE_LOW | V4L2_MBUS_VSYNC_ACTIVE_LOW | V4L2_MBUS_PCLK_SAMPLE_RISING; } /* fix crop info from dts config */ for (i = 0; i < 4; i++) { if ((ad->defrects[i].width == ad->cfg.width) && (ad->defrects[i].height == ad->cfg.height)) { ad->cfg.start_x = ad->defrects[i].crop_x; ad->cfg.start_y = ad->defrects[i].crop_y; ad->cfg.width = ad->defrects[i].crop_width; ad->cfg.height = ad->defrects[i].crop_height; } } SENSOR_DG("size %dx%d, crop(%d,%d)\n", ad->cfg.width, ad->cfg.height, ad->cfg.start_x, ad->cfg.start_y); } static void adv7181_reg_init(struct vehicle_ad_dev *ad, unsigned char cvstd) { struct rk_sensor_reg *sensor; int i = 0; unsigned char val[2]; switch (ad->ad_chl) { case 0: ad->ad_chl = ADV7181_INPUT_CVBS_AIN1; break; case 1: ad->ad_chl = ADV7181_INPUT_CVBS_AIN6; break; case 2: ad->ad_chl = ADV7181_INPUT_CVBS_AIN8; break; case 3: ad->ad_chl = ADV7181_INPUT_CVBS_AIN10; break; case 4: ad->ad_chl = ADV7181_INPUT_YPRPB_AIN6_8_10; break; default: ad->ad_chl = ADV7181_INPUT_CVBS_AIN1; } val[0] = ad->ad_chl; vehicle_generic_sensor_write(ad, ADV7181_INPUT_CONTROL, val); if (ad->ad_chl == ADV7181_INPUT_YPRPB_AIN6_8_10) { SENSOR_DG("%s %d set sensor_preview_data_yprpb_p/p", __func__, __LINE__); sensor = sensor_preview_data_yprpb_p; } else { SENSOR_DG("%s %d set n/p", __func__, __LINE__); sensor = sensor_preview_data; } while ((sensor[i].reg != SEQCMD_END) && (sensor[i].reg != 0xFC000000)) { if (sensor[i].reg == ADV7181_INPUT_CONTROL) { SENSOR_DG("%s %d lkg test ad channel = %d\n", __func__, __LINE__, ad->ad_chl); } else { val[0] = sensor[i].val; vehicle_generic_sensor_write(ad, sensor[i].reg, val); } i++; } val[0] = ad->ad_chl; vehicle_generic_sensor_write(ad, ADV7181_INPUT_CONTROL, val); } int adv7181_ad_get_cfg(struct vehicle_cfg **cfg) { u32 status; if (!ad7181_g_addev) return -1; adv7181_vehicle_status(ad7181_g_addev, &status, NULL); ad7181_g_addev->cfg.ad_ready = true; *cfg = &ad7181_g_addev->cfg; return 0; } void adv7181_ad_check_cif_error(struct vehicle_ad_dev *ad, int last_line) { SENSOR_DG("%s, last_line %d\n", __func__, last_line); if (last_line < 1) return; ad->cif_error_last_line = last_line; if (std_old == V4L2_STD_PAL) { if (last_line == FORCE_NTSC_HEIGHT) { if (ad->state_check_work.state_check_wq) queue_delayed_work( ad->state_check_work.state_check_wq, &ad->state_check_work.work, msecs_to_jiffies(0)); } } else if (std_old == V4L2_STD_NTSC) { if (last_line == FORCE_PAL_HEIGHT) { if (ad->state_check_work.state_check_wq) queue_delayed_work( ad->state_check_work.state_check_wq, &ad->state_check_work.work, msecs_to_jiffies(0)); } } } int adv7181_check_id(struct vehicle_ad_dev *ad) { int ret = 0; int val; val = vehicle_generic_sensor_read(ad, 0x11); SENSOR_DG("%s vehicle read 0x11 --> 0x%02x\n", ad->ad_name, val); if (val != 0x20) { SENSOR_DG("%s vehicle wrong camera ID, expected 0x20, detected 0x%02x\n", ad->ad_name, val); ret = -EINVAL; } return ret; } static int adv7181_check_std(struct vehicle_ad_dev *ad, v4l2_std_id *std) { u32 status = 0; adv7181_vehicle_status(ad, &status, std); if (status != 0) { /* No signal */ mdelay(30); adv7181_vehicle_status(ad, &status, std); SENSOR_DG("status 0x%x\n", status); } return 0; } void adv7181_channel_set(struct vehicle_ad_dev *ad, int channel) { static int channel_change = 11; v4l2_std_id std = 0; ad->ad_chl = channel; adv7181_reg_init(ad, std); adv7181_check_std(ad, &std); adv7181_reinit_parameter(ad, std); if (channel_change != ad->ad_chl) { SENSOR_DG("%s %d channel changed now channel = %d old_channel = %d\n", __func__, __LINE__, ad->ad_chl, channel); channel_change = ad->ad_chl; vehicle_ad_stat_change_notify(); } } int adv7181_stream(struct vehicle_ad_dev *ad, int value) { char val; if (value) val = 0x0c; //on else val = 0x4c; SENSOR_DG("stream write 0x%x to reg 0x03\n", val); vehicle_generic_sensor_write(ad, 0x03, &val); if (value) val = 0x47; //on else val = 0x87; SENSOR_DG("stream write 0x%x to reg 0x01d\n", val); vehicle_generic_sensor_write(ad, 0x1d, &val); return 0; } static void power_on(struct vehicle_ad_dev *ad) { /* gpio_direction_output(ad->power, ad->pwr_active); */ if (gpio_is_valid(ad->powerdown)) { gpio_request(ad->powerdown, "ad_powerdown"); gpio_direction_output(ad->powerdown, !ad->pwdn_active); /* gpio_set_value(ad->powerdown, !ad->pwdn_active); */ } if (gpio_is_valid(ad->power)) { gpio_request(ad->power, "ad_power"); gpio_direction_output(ad->power, ad->pwr_active); /* gpio_set_value(ad->power, ad->pwr_active); */ } if (gpio_is_valid(ad->reset)) { gpio_request(ad->reset, "ad_reset"); gpio_direction_output(ad->reset, 0); usleep_range(10000, 12000); gpio_set_value(ad->reset, 1); usleep_range(10000, 12000); } } static void power_off(struct vehicle_ad_dev *ad) { if (gpio_is_valid(ad->powerdown)) gpio_free(ad->powerdown); if (gpio_is_valid(ad->power)) gpio_free(ad->power); if (gpio_is_valid(ad->reset)) gpio_free(ad->reset); } static void adv7181_check_state_work(struct work_struct *work) { struct vehicle_ad_dev *ad; v4l2_std_id std; ad = ad7181_g_addev; if (ad->cif_error_last_line > 0) ad->cif_error_last_line = 0; adv7181_check_std(ad, &std); SENSOR_DG("%s:new std(%llx), std_old(%llx)\n", __func__, std, std_old); if (std != std_old) { std_old = std; adv7181_reinit_parameter(ad, std); SENSOR_DG("%s:ad signal change notify\n", __func__); vehicle_ad_stat_change_notify(); } queue_delayed_work(ad->state_check_work.state_check_wq, &ad->state_check_work.work, msecs_to_jiffies(3000)); } int adv7181_ad_deinit(void) { struct vehicle_ad_dev *ad; ad = ad7181_g_addev; if (!ad) return -ENODEV; if (ad->state_check_work.state_check_wq) { cancel_delayed_work_sync(&ad->state_check_work.work); flush_delayed_work(&ad->state_check_work.work); flush_workqueue(ad->state_check_work.state_check_wq); destroy_workqueue(ad->state_check_work.state_check_wq); } if (ad->irq) free_irq(ad->irq, ad); power_off(ad); return 0; } int adv7181_ad_init(struct vehicle_ad_dev *ad) { v4l2_std_id std = V4L2_STD_NTSC; if (!ad) return -1; ad7181_g_addev = ad; /* 1. i2c init */ while (ad->adapter == NULL) { ad->adapter = i2c_get_adapter(ad->i2c_chl); usleep_range(10000, 12000); } if (!i2c_check_functionality(ad->adapter, I2C_FUNC_I2C)) return -EIO; /* 2. ad power on sequence */ power_on(ad); /* fix mode */ adv7181_check_std(ad, &std); std_old = std; SENSOR_DG("std: %s\n", (std == V4L2_STD_NTSC) ? "ntsc" : "pal"); SENSOR_DG("std_old: %s\n", (std_old == V4L2_STD_NTSC) ? "ntsc" : "pal"); /* 3 .init default format params */ adv7181_reg_init(ad, std); adv7181_reinit_parameter(ad, std); vehicle_ad_stat_change_notify(); /* 5. create workqueue to detect signal change */ INIT_DELAYED_WORK(&ad->state_check_work.work, adv7181_check_state_work); ad->state_check_work.state_check_wq = create_singlethread_workqueue("vehicle-ad-adv7181"); queue_delayed_work(ad->state_check_work.state_check_wq, &ad->state_check_work.work, msecs_to_jiffies(100)); return 0; }