// SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2019 MediaTek Inc. #include #include #include #include #include #include #include #include #include #include "kd_imgsensor_define_v4l2.h" #include "adaptor.h" #include "adaptor-hw.h" #include "adaptor-i2c.h" #include "adaptor-ctrls.h" #include "adaptor-command.h" #include "adaptor-fsync-ctrls.h" #include "adaptor-ioctl.h" #include "adaptor-trace.h" #include "imgsensor-glue/imgsensor-glue.h" #include "virt-sensor/virt-sensor-entry.h" #undef E #define E(__x__) (__x__##_entry) #define EXTERN_IMGSENSOR_SUBDRVS extern struct subdrv_entry \ IMGSENSOR_SUBDRVS #ifdef IMGSENSOR_SUBDRVS EXTERN_IMGSENSOR_SUBDRVS; #endif #undef E #define E(__x__) (&__x__##_entry) static struct subdrv_entry *imgsensor_subdrvs[] = { #ifdef IMGSENSOR_SUBDRVS IMGSENSOR_SUBDRVS #endif }; module_param(sensor_debug, uint, 0644); MODULE_PARM_DESC(sensor_debug, "imgsensor_debug"); static int get_outfmt_code(struct adaptor_ctx *ctx) { int outfmt = ctx->sensor_info.SensorOutputDataFormat; switch (outfmt) { case SENSOR_OUTPUT_FORMAT_RAW_B: return MEDIA_BUS_FMT_SBGGR10_1X10; case SENSOR_OUTPUT_FORMAT_RAW_Gb: return MEDIA_BUS_FMT_SGBRG10_1X10; case SENSOR_OUTPUT_FORMAT_RAW_Gr: return MEDIA_BUS_FMT_SGRBG10_1X10; case SENSOR_OUTPUT_FORMAT_RAW_R: return MEDIA_BUS_FMT_SRGGB10_1X10; case SENSOR_OUTPUT_FORMAT_RAW_4CELL_B: case SENSOR_OUTPUT_FORMAT_RAW_4CELL_BAYER_B: case SENSOR_OUTPUT_FORMAT_RAW_4CELL_HW_BAYER_B: pr_warn("unsupported 4cell output_format %d\n", outfmt); return MEDIA_BUS_FMT_SBGGR10_1X10; case SENSOR_OUTPUT_FORMAT_RAW_4CELL_Gb: case SENSOR_OUTPUT_FORMAT_RAW_4CELL_BAYER_Gb: case SENSOR_OUTPUT_FORMAT_RAW_4CELL_HW_BAYER_Gb: pr_warn("unsupported 4cell output_format %d\n", outfmt); return MEDIA_BUS_FMT_SGBRG10_1X10; case SENSOR_OUTPUT_FORMAT_RAW_4CELL_Gr: case SENSOR_OUTPUT_FORMAT_RAW_4CELL_BAYER_Gr: case SENSOR_OUTPUT_FORMAT_RAW_4CELL_HW_BAYER_Gr: pr_warn("unsupported 4cell output_format %d\n", outfmt); return MEDIA_BUS_FMT_SGRBG10_1X10; case SENSOR_OUTPUT_FORMAT_RAW_4CELL_R: case SENSOR_OUTPUT_FORMAT_RAW_4CELL_BAYER_R: case SENSOR_OUTPUT_FORMAT_RAW_4CELL_HW_BAYER_R: pr_warn("unsupported 4cell output_format %d\n", outfmt); return MEDIA_BUS_FMT_SRGGB10_1X10; case SENSOR_OUTPUT_FORMAT_RAW8_MONO: case SENSOR_OUTPUT_FORMAT_RAW8_B: return MEDIA_BUS_FMT_SBGGR8_1X8; case SENSOR_OUTPUT_FORMAT_RAW8_Gb: return MEDIA_BUS_FMT_SGBRG8_1X8; case SENSOR_OUTPUT_FORMAT_RAW8_Gr: return MEDIA_BUS_FMT_SGRBG8_1X8; case SENSOR_OUTPUT_FORMAT_RAW8_R: return MEDIA_BUS_FMT_SRGGB8_1X8; case SENSOR_OUTPUT_FORMAT_RAW12_B: return MEDIA_BUS_FMT_SBGGR12_1X12; case SENSOR_OUTPUT_FORMAT_RAW12_Gb: return MEDIA_BUS_FMT_SGBRG12_1X12; case SENSOR_OUTPUT_FORMAT_RAW12_Gr: return MEDIA_BUS_FMT_SGRBG12_1X12; case SENSOR_OUTPUT_FORMAT_RAW12_R: return MEDIA_BUS_FMT_SRGGB12_1X12; case SENSOR_OUTPUT_FORMAT_NV12: case SENSOR_OUTPUT_FORMAT_NV21: return MEDIA_BUS_FMT_SBGGR8_1X8; case SENSOR_OUTPUT_FORMAT_YUV_P010: case SENSOR_OUTPUT_FORMAT_YVU_P010: return MEDIA_BUS_FMT_SBGGR10_1X10; } pr_warn("unknown output format %d\n", outfmt); return MEDIA_BUS_FMT_SBGGR10_1X10; } static void add_sensor_mode(struct adaptor_ctx *ctx, int id, int width, int height) { union feature_para para; u32 idx, val, len, llp_readout; struct sensor_mode *mode; idx = ctx->mode_cnt; if (idx >= MODE_MAXCNT) { dev_warn(ctx->dev, "invalid mode idx %d\n", idx); return; } mode = &ctx->mode[idx]; mode->id = id; mode->width = width; mode->height = height; para.u64[0] = id; para.u64[1] = (u64)&val; para.u64[2] = SENSOR_GET_LINELENGTH_FOR_READOUT; subdrv_call(ctx, feature_control, SENSOR_FEATURE_GET_PERIOD_BY_SCENARIO, para.u8, &len); llp_readout = val & 0xffff; para.u64[2] = 0; val = 0; subdrv_call(ctx, feature_control, SENSOR_FEATURE_GET_PERIOD_BY_SCENARIO, para.u8, &len); mode->llp = val & 0xffff; mode->fll = val >> 16; if (!mode->llp || !mode->fll) return; val = 0; subdrv_call(ctx, feature_control, SENSOR_FEATURE_GET_MIPI_PIXEL_RATE, para.u8, &len); mode->mipi_pixel_rate = val; val = 0; subdrv_call(ctx, feature_control, SENSOR_FEATURE_GET_CUST_PIXEL_RATE, para.u8, &len); mode->cust_pixel_rate = val; val = 0; subdrv_call(ctx, feature_control, SENSOR_FEATURE_GET_DEFAULT_FRAME_RATE_BY_SCENARIO, para.u8, &len); mode->max_framerate = val; val = 0; subdrv_call(ctx, feature_control, SENSOR_FEATURE_GET_PIXEL_CLOCK_FREQ_BY_SCENARIO, para.u8, &len); mode->pclk = val; val = 0; subdrv_call(ctx, feature_control, SENSOR_FEATURE_GET_FINE_INTEG_LINE_BY_SCENARIO, para.u8, &len); mode->fine_intg_line = val; val = 0; subdrv_call(ctx, feature_control, SENSOR_FEATURE_ESD_RESET_BY_USER, para.u8, &len); mode->esd_reset_by_user = val; if (!mode->mipi_pixel_rate || !mode->max_framerate || !mode->pclk) return; subdrv_call(ctx, get_csi_param, mode->id, &mode->csi_param); /* update linetime_in_ns */ mode->linetime_in_ns = (u64)mode->llp * 1000000 + (mode->pclk / 1000 - 1); do_div(mode->linetime_in_ns, mode->pclk / 1000); mode->linetime_in_ns_readout = (u64)llp_readout * 1000000 + (mode->pclk / 1000 - 1); do_div(mode->linetime_in_ns_readout, mode->pclk / 1000); dev_dbg(ctx->dev, "%s [%d] id %d %dx%d %dx%d px %d fps %d tLine %lld|%lld fintl %u\n", __func__, idx, id, width, height, mode->llp, mode->fll, mode->mipi_pixel_rate, mode->max_framerate, mode->linetime_in_ns, mode->linetime_in_ns_readout, mode->fine_intg_line); ctx->mode_cnt++; } static int init_sensor_mode(struct adaptor_ctx *ctx) { MSDK_SENSOR_RESOLUTION_INFO_STRUCT res; unsigned int i = 0; memset(&res, 0, sizeof(res)); subdrv_call(ctx, get_resolution, &res); for (i = SENSOR_SCENARIO_ID_MIN; i < SENSOR_SCENARIO_ID_MAX; i++) { ctx->seamless_scenarios[i] = SENSOR_SCENARIO_ID_NONE; if (res.SensorWidth[i] > 0 && res.SensorHeight[i] > 0) add_sensor_mode(ctx, i, res.SensorWidth[i], res.SensorHeight[i]); } return 0; } static void control_sensor(struct adaptor_ctx *ctx) { MSDK_SENSOR_EXPOSURE_WINDOW_STRUCT image_window; MSDK_SENSOR_CONFIG_STRUCT sensor_config_data; #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]+ is_sensor_scenario_inited(%u),is_streaming(%u)\n", __func__, ctx->is_sensor_scenario_inited, ctx->is_streaming); #endif if (!ctx->is_sensor_scenario_inited && !ctx->is_streaming) { subdrv_call(ctx, control, ctx->cur_mode->id, &image_window, &sensor_config_data); ctx->is_sensor_scenario_inited = 1; } restore_ae_ctrl(ctx); #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]-\n", __func__); #endif } static int set_sensor_mode(struct adaptor_ctx *ctx, struct sensor_mode *mode, char update_ctrl_defs) { s64 min, max, def; if (ctx->cur_mode == mode) { if (update_ctrl_defs) control_sensor(ctx); return 0; } ctx->cur_mode = mode; ctx->is_sensor_scenario_inited = 0; subdrv_call(ctx, get_info, mode->id, &ctx->sensor_info, &ctx->sensor_cfg); if (update_ctrl_defs) { /* pixel rate */ min = max = def = mode->mipi_pixel_rate; __v4l2_ctrl_modify_range(ctx->pixel_rate, min, max, 1, def); /* hblank */ min = max = def = mode->llp - mode->width; __v4l2_ctrl_modify_range(ctx->hblank, min, max, 1, def); /* vblank */ min = def = get_mode_vb(ctx, mode); max = ctx->subctx.max_frame_length - mode->height; __v4l2_ctrl_modify_range(ctx->vblank, min, max, 1, def); /* max fps */ max = def = mode->max_framerate; __v4l2_ctrl_modify_range(ctx->max_fps, 1, max, 1, def); /* init sensor scenario setting */ control_sensor(ctx); } dev_dbg(ctx->dev, "select %dx%d@%d %dx%d px %d\n", mode->width, mode->height, mode->max_framerate, mode->llp, mode->fll, mode->mipi_pixel_rate); return 0; } static int init_sensor_info(struct adaptor_ctx *ctx) { init_sensor_mode(ctx); set_sensor_mode(ctx, &ctx->mode[0], 0); ctx->try_format_mode = &ctx->mode[0]; ctx->fmt_code = get_outfmt_code(ctx); return 0; } static int search_sensor(struct adaptor_ctx *ctx) { int ret, i, j, of_sensor_names_cnt, subdrvs_cnt; struct subdrv_entry **subdrvs, *subdrv; struct subdrv_entry *of_subdrvs[OF_SENSOR_NAMES_MAXCNT]; const char *of_sensor_names[OF_SENSOR_NAMES_MAXCNT]; int subdrv_name_ret; const char *of_subdrv_name; of_sensor_names_cnt = of_property_read_string_array(ctx->dev->of_node, "sensor-names", of_sensor_names, ARRAY_SIZE(of_sensor_names)); subdrv_name_ret = of_property_read_string(ctx->dev->of_node, "subdrv-name", &of_subdrv_name); dev_info(ctx->dev, "subdrv_name_ret %d\n", subdrv_name_ret); if (!subdrv_name_ret) dev_info(ctx->dev, "subdrv name %s found\n", of_subdrv_name); /* try to load custom list from DT */ if (of_sensor_names_cnt > 0) { subdrvs = of_subdrvs; subdrvs_cnt = 0; for (i = 0; i < of_sensor_names_cnt; i++) { for (j = 0; j < ARRAY_SIZE(imgsensor_subdrvs); j++) { subdrv = imgsensor_subdrvs[j]; if (!strcmp(subdrv->name, of_sensor_names[i])) { of_subdrvs[subdrvs_cnt++] = subdrv; break; } } if (j == ARRAY_SIZE(imgsensor_subdrvs)) { dev_warn(ctx->dev, "%s not found\n", of_sensor_names[i]); } } } else if (!subdrv_name_ret) { subdrvs_cnt = 0; of_subdrvs[0] = vs_query_subdrv_entry(of_subdrv_name); if (of_subdrvs[0]) { subdrvs_cnt = 1; subdrvs = of_subdrvs; dev_info(ctx->dev, "subdrv name %s found\n", of_subdrv_name); } } else { subdrvs = imgsensor_subdrvs; subdrvs_cnt = ARRAY_SIZE(imgsensor_subdrvs); } for (i = 0; i < subdrvs_cnt; i++) { u32 sensor_id; ctx->subdrv = subdrvs[i]; ctx->subctx.i2c_client = ctx->i2c_client; adaptor_hw_power_on(ctx); subdrv_call(ctx, init_ctx, ctx->i2c_client, ctx->subctx.i2c_write_id); ret = subdrv_call(ctx, get_id, &sensor_id); adaptor_hw_power_off(ctx); if (!ret) { dev_info(ctx->dev, "sensor %s found\n", ctx->subdrv->name); subdrv_call(ctx, init_ctx, ctx->i2c_client, ctx->subctx.i2c_write_id); ctx->ctx_pw_seq = kmalloc_array(ctx->subdrv->pw_seq_cnt, sizeof(struct subdrv_pw_seq_entry), GFP_KERNEL); if (ctx->ctx_pw_seq) { memcpy(ctx->ctx_pw_seq, ctx->subdrv->pw_seq, ctx->subdrv->pw_seq_cnt * sizeof(struct subdrv_pw_seq_entry)); } return 0; } } return -EIO; } static int imgsensor_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) { struct adaptor_ctx *ctx = to_ctx(sd); struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_get_try_format(sd, fh->state, 0); #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]+\n", __func__); #endif mutex_lock(&ctx->mutex); ctx->open_refcnt++; /* Initialize try_fmt */ try_fmt->width = ctx->cur_mode->width; try_fmt->height = ctx->cur_mode->height; try_fmt->code = ctx->fmt_code; try_fmt->field = V4L2_FIELD_NONE; #ifdef POWERON_ONCE_OPENED #ifdef IMGSENSOR_USE_PM_FRAMEWORK dev_info(ctx->dev, "%s use IMGSENSOR_USE_PM_FRAMEWORK\n", __func__); pm_runtime_get_sync(ctx->dev); #else dev_info(ctx->dev, "%s use self ref cnt\n", __func__); adaptor_hw_power_on(ctx); #endif adaptor_sensor_init(ctx); #endif mutex_unlock(&ctx->mutex); #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]-\n", __func__); #endif return 0; } static int imgsensor_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) { struct adaptor_ctx *ctx = to_ctx(sd); int i; #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]+\n", __func__); #endif mutex_lock(&ctx->mutex); #ifdef POWERON_ONCE_OPENED #ifdef IMGSENSOR_USE_PM_FRAMEWORK dev_info(ctx->dev, "%s use IMGSENSOR_USE_PM_FRAMEWORK\n", __func__); pm_runtime_put(ctx->dev); #else dev_info(ctx->dev, "%s use self ref cnt\n", __func__); adaptor_hw_power_off(ctx); #endif #endif ctx->open_refcnt--; if (ctx->open_refcnt <= 0) { for (i = 0; ctx->power_refcnt; i++) adaptor_hw_power_off(ctx); ctx->open_refcnt = 0; } mutex_unlock(&ctx->mutex); #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]-\n", __func__); #endif return 0; } static int imgsensor_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code) { struct adaptor_ctx *ctx = to_ctx(sd); if (code->index >= ctx->mode_cnt) return -EINVAL; code->code = to_mtk_ext_fmt_code(ctx->fmt_code, ctx->mode[code->index].id); // dev_info(ctx->dev, "enum mbus fmt code = 0x%x\n", code->code); return 0; } static int imgsensor_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_size_enum *fse) { struct adaptor_ctx *ctx = to_ctx(sd); if (fse->index >= ctx->mode_cnt) return -EINVAL; fse->min_width = ctx->mode[fse->index].width; fse->max_width = fse->min_width; fse->min_height = ctx->mode[fse->index].height; fse->max_height = fse->min_height; return 0; } static int imgsensor_enum_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_interval_enum *fie) { struct adaptor_ctx *ctx = to_ctx(sd); struct sensor_mode *mode; u32 i, index = fie->index; for (i = 0; i < ctx->mode_cnt; i++) { mode = &ctx->mode[i]; if (fie->width != mode->width || fie->height != mode->height) continue; if (index-- == 0) { fie->interval.numerator = 10; fie->interval.denominator = mode->max_framerate; return 0; } } return -EINVAL; } static int imgsensor_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { struct adaptor_ctx *ctx = to_ctx(sd); if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) return -EINVAL; switch (sel->target) { case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_CROP_DEFAULT: case V4L2_SEL_TGT_CROP: sel->r.left = 0; sel->r.top = 0; sel->r.width = ctx->cur_mode->width; sel->r.height = ctx->cur_mode->height; return 0; default: return -EINVAL; } } static void update_pad_format(struct adaptor_ctx *ctx, const struct sensor_mode *mode, struct v4l2_subdev_format *fmt) { fmt->format.width = mode->width; fmt->format.height = mode->height; fmt->format.code = to_mtk_ext_fmt_code(ctx->fmt_code, mode->id); fmt->format.field = V4L2_FIELD_NONE; } static int __imgsensor_get_pad_format(struct adaptor_ctx *ctx, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) fmt->format = *v4l2_subdev_get_try_format(&ctx->sd, state, fmt->pad); else update_pad_format(ctx, ctx->cur_mode, fmt); return 0; } static int imgsensor_get_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { struct adaptor_ctx *ctx = to_ctx(sd); int ret; mutex_lock(&ctx->mutex); ret = __imgsensor_get_pad_format(ctx, state, fmt); mutex_unlock(&ctx->mutex); return ret; } static int imgsensor_set_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { struct adaptor_ctx *ctx = to_ctx(sd); struct sensor_mode *mode; struct v4l2_mbus_framefmt *framefmt; int sensor_mode_id = 0; mutex_lock(&ctx->mutex); /* Only one raw bayer order is supported */ set_std_parts_fmt_code(fmt->format.code, ctx->fmt_code); /* Returns the best match or NULL if the Length of the array is zero */ mode = v4l2_find_nearest_size(ctx->mode, ctx->mode_cnt, width, height, fmt->format.width, fmt->format.height); /* Override mode when fmt code specified sensor mode */ if (is_mtk_ext_fmt_code(fmt->format.code)) { sensor_mode_id = get_sensor_mode_from_fmt_code(fmt->format.code); if (sensor_mode_id >= 0 && sensor_mode_id < ctx->mode_cnt) mode = &ctx->mode[sensor_mode_id]; } if (mode == NULL) { dev_info(ctx->dev, "set fmt code = 0x%x, which %d ctx->mode_cnt = %d\n", fmt->format.code, fmt->which, ctx->mode_cnt); mutex_unlock(&ctx->mutex); return -EINVAL; } update_pad_format(ctx, mode, fmt); if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { framefmt = v4l2_subdev_get_try_format(sd, state, fmt->pad); *framefmt = fmt->format; ctx->try_format_mode = mode; } else { dev_info(ctx->dev, "set fmt code = 0x%x, which %d sensor_mode_id = %u\n", fmt->format.code, fmt->which, mode->id); #ifndef POWERON_ONCE_OPENED ADAPTOR_SYSTRACE_BEGIN("imgsensor::init_sensor"); adaptor_sensor_init(ctx); ADAPTOR_SYSTRACE_END(); #endif ADAPTOR_SYSTRACE_BEGIN("imgsensor::set_mode_%u", mode->id); set_sensor_mode(ctx, mode, 1); ADAPTOR_SYSTRACE_END(); } mutex_unlock(&ctx->mutex); return 0; } #ifdef IMGSENSOR_USE_PM_FRAMEWORK static int imgsensor_runtime_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); struct adaptor_ctx *ctx = to_ctx(sd); return adaptor_hw_power_on(ctx); } static int imgsensor_runtime_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); struct adaptor_ctx *ctx = to_ctx(sd); /* clear flags once power-off */ ctx->is_sensor_inited = 0; ctx->is_sensor_scenario_inited = 0; return adaptor_hw_power_off(ctx); } #endif static int imgsensor_set_power(struct v4l2_subdev *sd, int on) { struct adaptor_ctx *ctx = to_ctx(sd); int ret; mutex_lock(&ctx->mutex); if (on) #ifdef IMGSENSOR_USE_PM_FRAMEWORK ret = pm_runtime_get_sync(ctx->dev); #else ret = adaptor_hw_power_on(ctx); #endif else #ifdef IMGSENSOR_USE_PM_FRAMEWORK ret = pm_runtime_put(ctx->dev); #else ret = adaptor_hw_power_off(ctx); #endif mutex_unlock(&ctx->mutex); return ret; } /* Start streaming */ static int imgsensor_start_streaming(struct adaptor_ctx *ctx) { // int ret; u64 data[4]; u32 len; union feature_para para; #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]+\n", __func__); #endif adaptor_sensor_init(ctx); control_sensor(ctx); #ifdef APPLY_CUSTOMIZED_VALUES_FROM_USER /* Apply customized values from user */ ret = __v4l2_ctrl_handler_setup(ctx->sd.ctrl_handler); if (ret) dev_info(ctx->dev, "failed to apply customized values\n"); #endif data[0] = 0; // shutter para.u8[0] = 0; #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s] [SENSOR_FEATURE_SET_TEST_PATTERN]+\n", __func__); #endif //Make sure close test pattern subdrv_call(ctx, feature_control, SENSOR_FEATURE_SET_TEST_PATTERN, para.u8, &len); #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s] [SENSOR_FEATURE_SET_TEST_PATTERN]-\n", __func__); #endif subdrv_call(ctx, feature_control, SENSOR_FEATURE_SET_STREAMING_RESUME, (u8 *)data, &len); #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s] [SENSOR_FEATURE_SET_STREAMING_RESUME]-\n", __func__); #endif /* update timeout value upon streaming on */ if (ctx->ae_memento.exposure.le_exposure) { ctx->shutter_for_timeout = ctx->ae_memento.exposure.le_exposure; if (ctx->cur_mode->fine_intg_line) ctx->shutter_for_timeout /= 1000; } /* notify frame-sync streaming ON */ notify_fsync_mgr_streaming(ctx, 1); #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]-\n", __func__); #endif return 0; } /* Stop streaming */ static int imgsensor_stop_streaming(struct adaptor_ctx *ctx) { u64 data[4]; u32 len; subdrv_call(ctx, feature_control, SENSOR_FEATURE_SET_STREAMING_SUSPEND, (u8 *)data, &len); /* notify frame-sync streaming OFF */ notify_fsync_mgr_streaming(ctx, 0); memset(&ctx->ae_memento, 0, sizeof(ctx->ae_memento)); return 0; } static int imgsensor_get_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi) { struct adaptor_ctx *ctx = to_ctx(sd); mutex_lock(&ctx->mutex); fi->interval.numerator = 10; if (fi->reserved[0] == V4L2_SUBDEV_FORMAT_TRY) fi->interval.denominator = ctx->try_format_mode->max_framerate; else fi->interval.denominator = ctx->cur_mode->max_framerate; mutex_unlock(&ctx->mutex); return 0; } static int imgsensor_set_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_frame_interval *fi) { struct adaptor_ctx *ctx = to_ctx(sd); dev_info(ctx->dev, "set_frame_interval = %u\n", fi->interval.denominator); return imgsensor_get_frame_interval(sd, fi); } static int imgsensor_set_stream(struct v4l2_subdev *sd, int enable) { int ret; struct adaptor_ctx *ctx = to_ctx(sd); mutex_lock(&ctx->mutex); if (ctx->is_streaming == enable) { #if IMGSENSOR_LOG_MORE dev_info(ctx->dev, "[%s]+ is_streaming(%d)\n", __func__, ctx->is_streaming); #endif mutex_unlock(&ctx->mutex); return 0; } if (enable) { #ifdef IMGSENSOR_USE_PM_FRAMEWORK ret = pm_runtime_get_sync(ctx->dev); if (ret < 0) { pm_runtime_put_noidle(ctx->dev); goto err_unlock; } #endif /* * Apply default & customized values * and then start streaming. */ ret = imgsensor_start_streaming(ctx); if (ret) goto err_rpm_put; } else { imgsensor_stop_streaming(ctx); #ifdef IMGSENSOR_USE_PM_FRAMEWORK pm_runtime_put(ctx->dev); #endif } ctx->is_streaming = enable; mutex_unlock(&ctx->mutex); adaptor_logi(ctx, "en %d\n", enable); return 0; err_rpm_put: #ifdef IMGSENSOR_USE_PM_FRAMEWORK pm_runtime_put(ctx->dev); err_unlock: #endif mutex_unlock(&ctx->mutex); return ret; } static int imgsensor_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_config *config) { struct adaptor_ctx *ctx = to_ctx(sd); config->type = ctx->sensor_info.MIPIsensorType == MIPI_CPHY ? V4L2_MBUS_CSI2_CPHY : V4L2_MBUS_CSI2_DPHY; switch (ctx->sensor_info.SensorMIPILaneNumber) { case SENSOR_MIPI_1_LANE: config->flags = V4L2_MBUS_CSI2_1_LANE; break; case SENSOR_MIPI_2_LANE: config->flags = V4L2_MBUS_CSI2_2_LANE; break; case SENSOR_MIPI_3_LANE: config->flags = V4L2_MBUS_CSI2_3_LANE; break; case SENSOR_MIPI_4_LANE: config->flags = V4L2_MBUS_CSI2_4_LANE; break; } return 0; } //def IMGSENSOR_VC_ROUTING // static int imgsensor_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, // struct mtk_mbus_frame_desc *fd) // { // int ret; // struct adaptor_ctx *ctx = to_ctx(sd); // u64 desc_visited = 0x0; // int write_to = 0, i = -1, j = 0; // while (i < SENSOR_SCENARIO_ID_MAX) { // struct mtk_mbus_frame_desc fd_tmp; // u32 scenario_id = (-1 == i) ? ctx->cur_mode->id : ctx->seamless_scenarios[i]; // if (scenario_id == SENSOR_SCENARIO_ID_NONE) // break; // ret = subdrv_call(ctx, get_frame_desc, scenario_id, &fd_tmp); // if (!ret) { // for (j = 0; // write_to < V4L2_FRAME_DESC_ENTRY_MAX && j < fd_tmp.num_entries; // ++j) { // if (desc_visited & // (0x1 << fd_tmp.entry[j].bus.csi2.user_data_desc)) // continue; // dev_info(ctx->dev, "[%s] scenario %u desc %d\n", __func__, // scenario_id, // fd_tmp.entry[j].bus.csi2.user_data_desc); // memcpy(&fd->entry[write_to++], &fd_tmp.entry[j], // sizeof(struct mtk_mbus_frame_desc_entry)); // desc_visited |= (0x1 << fd_tmp.entry[j].bus.csi2.user_data_desc); // } // } // ++i; // } // fd->num_entries = write_to; // fd->type = MTK_MBUS_FRAME_DESC_TYPE_CSI2; // return ret; // } // #define IS_3HDR(udesc) (udesc == VC_3HDR_Y \ // || udesc == VC_3HDR_AE \ // || udesc == VC_3HDR_FLICKER) // static int imgsensor_set_frame_desc(struct v4l2_subdev *sd, unsigned int pad, // struct mtk_mbus_frame_desc *fd) // { // return 0; // } #ifdef IMGSENSOR_USE_PM_FRAMEWORK static int __maybe_unused imgsensor_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); struct adaptor_ctx *ctx = to_ctx(sd); if (pm_runtime_suspended(dev)) return 0; if (ctx->is_streaming) imgsensor_stop_streaming(ctx); return imgsensor_runtime_suspend(dev); } static int __maybe_unused imgsensor_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); struct adaptor_ctx *ctx = to_ctx(sd); int ret; if (pm_runtime_suspended(dev)) return 0; ret = imgsensor_runtime_resume(dev); if (ret) return ret; if (ctx->is_streaming) { ret = imgsensor_start_streaming(ctx); if (ret) goto error; } return 0; error: imgsensor_stop_streaming(ctx); ctx->is_streaming = 0; return ret; } #endif static const struct v4l2_subdev_core_ops imgsensor_core_ops = { .s_power = imgsensor_set_power, .ioctl = adaptor_ioctl, .command = adaptor_command, }; static const struct v4l2_subdev_video_ops imgsensor_video_ops = { .g_frame_interval = imgsensor_get_frame_interval, .s_frame_interval = imgsensor_set_frame_interval, .s_stream = imgsensor_set_stream, }; static const struct v4l2_subdev_pad_ops imgsensor_pad_ops = { .enum_mbus_code = imgsensor_enum_mbus_code, .get_fmt = imgsensor_get_pad_format, .set_fmt = imgsensor_set_pad_format, .enum_frame_size = imgsensor_enum_frame_size, .enum_frame_interval = imgsensor_enum_frame_interval, .get_selection = imgsensor_get_selection, .get_mbus_config = imgsensor_g_mbus_config, #ifdef IMGSENSOR_VC_ROUTING //.get_frame_desc = imgsensor_get_frame_desc, //.set_frame_desc = imgsensor_set_frame_desc, #endif }; static const struct v4l2_subdev_ops imgsensor_subdev_ops = { .core = &imgsensor_core_ops, .video = &imgsensor_video_ops, .pad = &imgsensor_pad_ops, }; static const struct v4l2_subdev_internal_ops imgsensor_internal_ops = { .open = imgsensor_open, .close = imgsensor_close, }; static int imgsensor_get_temp(void *data, int *temperature) { struct adaptor_ctx *ctx = data; #ifdef IMGSENSOR_USE_PM_FRAMEWORK if (pm_runtime_get_if_in_use(ctx->dev) == 0) { *temperature = THERMAL_TEMP_INVALID; return 0; } #else *temperature = 0; #endif if (ctx->is_streaming) { if (ctx->aov_i2c_bus_scl_switch_en == 1 || ctx->aov_i2c_bus_sda_switch_en == 1) *temperature = THERMAL_TEMP_INVALID; else subdrv_call(ctx, get_temp, temperature); } else *temperature = THERMAL_TEMP_INVALID; #ifdef IMGSENSOR_USE_PM_FRAMEWORK pm_runtime_put(ctx->dev); #endif return 0; } static const struct thermal_zone_of_device_ops imgsensor_tz_ops = { .get_temp = imgsensor_get_temp, }; #define SHOW(buf, len, fmt, ...) { \ len += snprintf(buf + len, PAGE_SIZE - len, fmt, ##__VA_ARGS__); \ } static ssize_t debug_i2c_ops_show(struct device *dev, struct device_attribute *attr, char *buf) { int len = 0; struct adaptor_ctx *ctx = to_ctx(dev_get_drvdata(dev)); SHOW(buf, len, "%s i2c read 0x%08x = 0x%08x\n", ctx->subdrv->name, ctx->sensorReg.RegAddr, ctx->sensorReg.RegData); return len; } enum DBG_ARG_IDX { DBG_ARG_IDX_I2C_ADDR, DBG_ARG_IDX_I2C_DATA, DBG_ARG_IDX_MAX_NUM, }; static ssize_t debug_i2c_ops_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { char delim[] = " "; char *token = NULL; char *sbuf = kzalloc(sizeof(char) * (count + 1), GFP_KERNEL); char *s = sbuf; int ret; unsigned int num_para = 0; char *arg[DBG_ARG_IDX_MAX_NUM]; struct adaptor_ctx *ctx = to_ctx(dev_get_drvdata(dev)); u32 val; u32 reg; ctx->sensorReg.RegAddr = 0; ctx->sensorReg.RegData = 0; if (!sbuf) goto ERR_DEBUG_OPS_STORE; memcpy(sbuf, buf, count); token = strsep(&s, delim); while (token != NULL && num_para < DBG_ARG_IDX_MAX_NUM) { if (strlen(token)) { arg[num_para] = token; num_para++; } token = strsep(&s, delim); } if (num_para > DBG_ARG_IDX_MAX_NUM) { dev_info(dev, "Wrong command parameter number %u\n", num_para); goto ERR_DEBUG_OPS_STORE; } ret = kstrtouint(arg[DBG_ARG_IDX_I2C_ADDR], 0, ®); if (ret) goto ERR_DEBUG_OPS_STORE; ctx->sensorReg.RegAddr = reg; if (num_para == DBG_ARG_IDX_MAX_NUM) { ret = kstrtouint(arg[DBG_ARG_IDX_I2C_DATA], 0, &val); if (ret) goto ERR_DEBUG_OPS_STORE; ctx->sensorReg.RegData = val; ret = subdrv_call(ctx, feature_control, SENSOR_FEATURE_SET_REGISTER, (MUINT8 *) &ctx->sensorReg, (MUINT32 *) sizeof(MSDK_SENSOR_REG_INFO_STRUCT)); dev_info(dev, "%s i2c write 0x%08x = 0x%08x ret = %d\n", __func__, ctx->sensorReg.RegAddr, ctx->sensorReg.RegData, ret); } ret = subdrv_call(ctx, feature_control, SENSOR_FEATURE_GET_REGISTER, (MUINT8 *) &ctx->sensorReg, (MUINT32 *) sizeof(MSDK_SENSOR_REG_INFO_STRUCT)); dev_info(dev, "%s i2c read 0x%08x = 0x%08x ret = %d\n", __func__, ctx->sensorReg.RegAddr, ctx->sensorReg.RegData, ret); ERR_DEBUG_OPS_STORE: kfree(sbuf); dev_dbg(dev, "exit %s\n", __func__); return count; } static DEVICE_ATTR_RW(debug_i2c_ops); static const char * const hw_id_names[] = { HW_ID_NAMES }; static ssize_t debug_pwr_ops_show(struct device *dev, struct device_attribute *attr, char *buf) { int len = 0; struct adaptor_ctx *ctx = to_ctx(dev_get_drvdata(dev)); int i; const struct subdrv_pw_seq_entry *ent; SHOW(buf, len, "sensor%d name %s\n", ctx->idx, ctx->subdrv->name); for (i = 0; i < ctx->subdrv->pw_seq_cnt; i++) { if (ctx->ctx_pw_seq) ent = &ctx->ctx_pw_seq[i]; // use ctx pw seq else ent = &ctx->subdrv->pw_seq[i]; SHOW(buf, len, "\t%s power %d with delay %d\n", hw_id_names[(unsigned int)ent->id], ent->val, ent->delay); } return len; } enum DBG_PWR_ARG_IDX { DBG_PWR_ARG_IDX_SEQ_IDX, DBG_PWR_ARG_IDX_SEQ_VAL, DBG_PWR_ARG_IDX_SEQ_DELAY, DBG_PWR_ARG_IDX_MAX_NUM, }; static ssize_t debug_pwr_ops_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { char delim[] = " "; char *token = NULL; char *sbuf = kzalloc(sizeof(char) * (count + 1), GFP_KERNEL); char *s = sbuf; unsigned int num_para = 0; char *arg[DBG_PWR_ARG_IDX_MAX_NUM]; struct adaptor_ctx *ctx = to_ctx(dev_get_drvdata(dev)); struct subdrv_pw_seq_entry *ent; unsigned int seq_idx, seq_val, seq_delay; int ret; if (!sbuf) goto ERR_DEBUG_PWR_OPS_STORE; if (!ctx->ctx_pw_seq) { dev_info(dev, "ctx pw seq is null\n"); goto ERR_DEBUG_PWR_OPS_STORE; } memcpy(sbuf, buf, count); token = strsep(&s, delim); while (token != NULL && num_para < DBG_PWR_ARG_IDX_MAX_NUM) { if (strlen(token)) { arg[num_para] = token; num_para++; } token = strsep(&s, delim); } if (num_para > DBG_PWR_ARG_IDX_MAX_NUM) { dev_info(dev, "Wrong command parameter number %u\n", num_para); goto ERR_DEBUG_PWR_OPS_STORE; } ret = kstrtouint(arg[DBG_PWR_ARG_IDX_SEQ_IDX], 0, &seq_idx); if (ret) goto ERR_DEBUG_PWR_OPS_STORE; ret = kstrtouint(arg[DBG_PWR_ARG_IDX_SEQ_VAL], 0, &seq_val); if (ret) goto ERR_DEBUG_PWR_OPS_STORE; ret = kstrtouint(arg[DBG_PWR_ARG_IDX_SEQ_DELAY], 0, &seq_delay); if (ret) goto ERR_DEBUG_PWR_OPS_STORE; if (seq_idx < ctx->subdrv->pw_seq_cnt) { ent = &ctx->ctx_pw_seq[seq_idx]; ent->val = seq_val; ent->delay = seq_delay; } ERR_DEBUG_PWR_OPS_STORE: kfree(sbuf); return count; } static DEVICE_ATTR_RW(debug_pwr_ops); static ssize_t debug_sensor_mode_ops_show(struct device *dev, struct device_attribute *attr, char *buf) { int len = 0; struct adaptor_ctx *ctx = to_ctx(dev_get_drvdata(dev)); SHOW(buf, len, "(%s) sensor_mode_ops is (%d)\n", ctx->subdrv->name, ctx->subctx.sensor_mode_ops); return len; } static ssize_t debug_sensor_mode_ops_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { char *sbuf = kzalloc(sizeof(char) * (count + 1), GFP_KERNEL); struct adaptor_ctx *ctx = to_ctx(dev_get_drvdata(dev)); unsigned int val = 0; int ret; if (!sbuf) goto ERR_DEBUG_SENSOR_MODE_OPS_STORE; memcpy(sbuf, buf, count); ret = kstrtouint(sbuf, 0, &val); if (ret) goto ERR_DEBUG_SENSOR_MODE_OPS_STORE; switch (val) { case AOV_MODE_CTRL_OPS_SENSING_CTRL: case AOV_MODE_CTRL_OPS_MONTION_DETECTION_CTRL: ctx->subctx.sensor_mode_ops = val; break; case AOV_MODE_CTRL_OPS_SENSING_UT_ON_SCP: // true for default aov sensing mode streaming control on scp ctx->subctx.sensor_debug_sensing_ut_on_scp = true; break; case AOV_MODE_CTRL_OPS_SENSING_UT_ON_APMCU: // flase for default aov sensing mode streaming control on apmcu side ctx->subctx.sensor_debug_sensing_ut_on_scp = false; break; case AOV_MODE_CTRL_OPS_DPHY_GLOBAL_TIMING_CONTINUOUS_CLK: ctx->subctx.sensor_debug_dphy_global_timing_continuous_clk = true; break; case AOV_MODE_CTRL_OPS_DPHY_GLOBAL_TIMING_NON_CONTINUOUS_CLK: ctx->subctx.sensor_debug_dphy_global_timing_continuous_clk = false; break; default: dev_info(dev, "Wrong command parameter number (%u)\n", val); break; } ERR_DEBUG_SENSOR_MODE_OPS_STORE: kfree(sbuf); return count; } static DEVICE_ATTR_RW(debug_sensor_mode_ops); static int imgsensor_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct device_node *endpoint; struct adaptor_ctx *ctx; int i, ret; unsigned int reindex; const char *reindex_match[OF_SENSOR_NAMES_MAXCNT]; int reindex_match_cnt; ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; mutex_init(&ctx->mutex); ctx->open_refcnt = 0; ctx->power_refcnt = 0; ctx->i2c_client = client; ctx->dev = dev; ctx->sensor_debug_flag = &sensor_debug; ctx->aov_pm_ops_flag = 0; ctx->aov_mclk_ulposc_flag = 0; endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); if (!endpoint) { dev_err(dev, "endpoint node not found\n"); return -EINVAL; } ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), &ctx->ep); of_node_put(endpoint); if (ret < 0) { dev_err(dev, "parsing endpoint node failed\n"); return ret; } ret = adaptor_hw_init(ctx); if (ret) { dev_err(dev, "failed to init hw handles\n"); return ret; } ret = search_sensor(ctx); if (ret) { dev_err(dev, "no sensor found\n"); return ret; } /* read property */ of_property_read_u32(dev->of_node, "location", &ctx->location); of_property_read_u32(dev->of_node, "rotation", &ctx->rotation); /* init sensor info */ init_sensor_info(ctx); /* init subdev */ v4l2_i2c_subdev_init(&ctx->sd, client, &imgsensor_subdev_ops); ctx->sd.internal_ops = &imgsensor_internal_ops; ctx->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ctx->pad.flags = MEDIA_PAD_FL_SOURCE; ctx->sd.dev = &client->dev; ctx->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ret = sscanf(dev->of_node->name, OF_SENSOR_NAME_PREFIX"%d", &ctx->dts_idx); if (ret != 1) dev_warn(dev, "failed to parse %s\n", dev->of_node->name); ctx->idx = ctx->dts_idx; if (!of_property_read_u32(dev->of_node, "reindex-to", &reindex)) { reindex_match_cnt = of_property_read_string_array(dev->of_node, "reindex-match", reindex_match, ARRAY_SIZE(reindex_match)); for (i = 0; i < reindex_match_cnt; i++) { if (!strncmp(reindex_match[i], ctx->subdrv->name, strlen(reindex_match[i]))) { ctx->idx = reindex; dev_info(dev, "reindex to sensor%d\n", ctx->idx); break; } } } /* init subdev name */ snprintf(ctx->sd.name, V4L2_SUBDEV_NAME_SIZE, "%s%d", OF_SENSOR_NAME_PREFIX, ctx->idx); /* init controls */ ret = adaptor_init_ctrls(ctx); if (ret) { dev_err(dev, "failed to init controls\n"); return ret; } ret = media_entity_pads_init(&ctx->sd.entity, 1, &ctx->pad); if (ret < 0) { dev_err(dev, "failed to init entity pads: %d", ret); goto free_ctrl; } ret = v4l2_async_register_subdev(&ctx->sd); if (ret < 0) { dev_err(dev, "could not register v4l2 device\n"); goto free_entity; } #ifdef IMGSENSOR_USE_PM_FRAMEWORK pm_runtime_enable(dev); #else // TODO #endif /* register thermal device */ if (ctx->subdrv->ops->get_temp) { struct thermal_zone_device *tzdev; tzdev = devm_thermal_zone_of_sensor_register( dev, 0, ctx, &imgsensor_tz_ops); if (IS_ERR(tzdev)) dev_info(dev, "failed to register thermal zone\n"); } notify_fsync_mgr(ctx, 1); ret = device_create_file(dev, &dev_attr_debug_i2c_ops); if (ret) dev_info(dev, "failed to create sysfs debug_i2c_ops\n"); ret = device_create_file(dev, &dev_attr_debug_pwr_ops); if (ret) dev_info(dev, "failed to create sysfs debug_pwr_ops\n"); ret = device_create_file(dev, &dev_attr_debug_sensor_mode_ops); if (ret) dev_info(dev, "failed to create sysfs debug_sensor_mode_ops\n"); ctx->sensor_ws = wakeup_source_register(dev, ctx->sd.name); if (!ctx->sensor_ws) dev_info(dev, "failed to wakeup_source_register\n"); return 0; free_entity: media_entity_cleanup(&ctx->sd.entity); free_ctrl: v4l2_ctrl_handler_free(&ctx->ctrls); mutex_destroy(&ctx->mutex); return ret; } static int imgsensor_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); struct adaptor_ctx *ctx = to_ctx(sd); v4l2_async_unregister_subdev(sd); media_entity_cleanup(&sd->entity); v4l2_ctrl_handler_free(sd->ctrl_handler); notify_fsync_mgr(ctx, 0); if (!ctx->sensor_ws) wakeup_source_unregister(ctx->sensor_ws); ctx->sensor_ws = NULL; kfree(ctx->ctx_pw_seq); #ifdef IMGSENSOR_USE_PM_FRAMEWORK pm_runtime_disable(&client->dev); #else // TODO #endif device_remove_file(ctx->dev, &dev_attr_debug_i2c_ops); device_remove_file(ctx->dev, &dev_attr_debug_pwr_ops); device_remove_file(ctx->dev, &dev_attr_debug_sensor_mode_ops); mutex_destroy(&ctx->mutex); return 0; } #ifdef IMGSENSOR_USE_PM_FRAMEWORK static const struct dev_pm_ops imgsensor_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(imgsensor_suspend, imgsensor_resume) SET_RUNTIME_PM_OPS(imgsensor_runtime_suspend, imgsensor_runtime_resume, NULL) }; #endif static const struct i2c_device_id imgsensor_id[] = { {"imgsensor", 0}, {} }; MODULE_DEVICE_TABLE(i2c, imgsensor_id); static const struct of_device_id imgsensor_of_match[] = { {.compatible = "mediatek,imgsensor"}, {} }; MODULE_DEVICE_TABLE(of, imgsensor_of_match); static struct i2c_driver imgsensor_i2c_driver = { .driver = { .of_match_table = of_match_ptr(imgsensor_of_match), .name = "imgsensor", #ifdef IMGSENSOR_USE_PM_FRAMEWORK .pm = &imgsensor_pm_ops, #endif }, .probe_new = imgsensor_probe, .remove = imgsensor_remove, .id_table = imgsensor_id, }; static int __init adaptor_drv_init(void) { i2c_add_driver(&imgsensor_i2c_driver); return 0; } static void __exit adaptor_drv_exit(void) { i2c_del_driver(&imgsensor_i2c_driver); } late_initcall(adaptor_drv_init); module_exit(adaptor_drv_exit); MODULE_LICENSE("GPL v2");