/* Copyright (c) 2011, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include "ov9726.h" /*============================================================= SENSOR REGISTER DEFINES ==============================================================*/ #define OV9726_Q8 0x00000100 #define OV9726_Q8Shift 8 #define OV9726_Q10 0x00000400 #define OV9726_Q10Shift 10 /* Omnivision8810 product ID register address */ #define OV9726_PIDH_REG 0x0000 #define OV9726_PIDL_REG 0x0001 /* Omnivision8810 product ID */ #define OV9726_PID 0x97 /* Omnivision8810 version */ #define OV9726_VER 0x26 /* Time in milisecs for waiting for the sensor to reset */ #define OV9726_RESET_DELAY_MSECS 66 #define OV9726_DEFAULT_CLOCK_RATE 24000000 /* Registers*/ #define OV9726_GAIN 0x3000 #define OV9726_AEC_MSB 0x3002 #define OV9726_AEC_LSB 0x3003 /* Color bar pattern selection */ #define OV9726_COLOR_BAR_PATTERN_SEL_REG 0x600 /* Color bar enabling control */ #define OV9726_COLOR_BAR_ENABLE_REG 0x601 /* Time in milisecs for waiting for the sensor to reset*/ #define OV9726_RESET_DELAY_MSECS 66 /* I2C Address of the Sensor */ /*============================================================================ DATA DECLARATIONS ============================================================================*/ #define OV9726_FULL_SIZE_DUMMY_PIXELS 0 #define OV9726_FULL_SIZE_DUMMY_LINES 0 #define OV9726_QTR_SIZE_DUMMY_PIXELS 0 #define OV9726_QTR_SIZE_DUMMY_LINES 0 #define OV9726_FULL_SIZE_WIDTH 1296 #define OV9726_FULL_SIZE_HEIGHT 808 #define OV9726_QTR_SIZE_WIDTH 1296 #define OV9726_QTR_SIZE_HEIGHT 808 #define OV9726_HRZ_FULL_BLK_PIXELS 368 #define OV9726_VER_FULL_BLK_LINES 32 #define OV9726_HRZ_QTR_BLK_PIXELS 368 #define OV9726_VER_QTR_BLK_LINES 32 #define OV9726_MSB_MASK 0xFF00 #define OV9726_LSB_MASK 0x00FF struct ov9726_work_t { struct work_struct work; }; static struct ov9726_work_t *ov9726_sensorw; static struct i2c_client *ov9726_client; struct ov9726_ctrl_t { const struct msm_camera_sensor_info *sensordata; uint32_t sensormode; uint32_t fps_divider; /* init to 1 * 0x00000400 */ uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ uint16_t fps; int16_t curr_lens_pos; uint16_t curr_step_pos; uint16_t my_reg_gain; uint32_t my_reg_line_count; uint16_t total_lines_per_frame; enum ov9726_resolution_t prev_res; enum ov9726_resolution_t pict_res; enum ov9726_resolution_t curr_res; enum ov9726_test_mode_t set_test; unsigned short imgaddr; }; static struct ov9726_ctrl_t *ov9726_ctrl; static int8_t config_not_set = 1; static DECLARE_WAIT_QUEUE_HEAD(ov9726_wait_queue); DEFINE_MUTEX(ov9726_mut); /*=============================================================*/ static int ov9726_i2c_rxdata(unsigned short saddr, unsigned char *rxdata, int length) { struct i2c_msg msgs[] = { { .addr = saddr, .flags = 0, .len = 2, .buf = rxdata, }, { .addr = saddr, .flags = I2C_M_RD, .len = length, .buf = rxdata, }, }; if (i2c_transfer(ov9726_client->adapter, msgs, 2) < 0) { CDBG("ov9726_i2c_rxdata failed!\n"); return -EIO; } return 0; } static int32_t ov9726_i2c_txdata(unsigned short saddr, unsigned char *txdata, int length) { struct i2c_msg msg[] = { { .addr = saddr , .flags = 0, .len = length, .buf = txdata, }, }; if (i2c_transfer(ov9726_client->adapter, msg, 1) < 0) { CDBG("ov9726_i2c_txdata faild 0x%x\n", ov9726_client->addr); return -EIO; } return 0; } static int32_t ov9726_i2c_read(unsigned short raddr, unsigned short *rdata, int rlen) { int32_t rc = 0; unsigned char buf[2]; if (!rdata) return -EIO; buf[0] = (raddr & OV9726_MSB_MASK) >> 8; buf[1] = (raddr & OV9726_LSB_MASK); rc = ov9726_i2c_rxdata(ov9726_client->addr, buf, rlen); if (rc < 0) { CDBG("ov9726_i2c_read 0x%x failed!\n", raddr); return rc; } *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); return rc; } static int32_t ov9726_i2c_write_b(unsigned short saddr, unsigned short waddr, uint8_t bdata) { int32_t rc = -EFAULT; unsigned char buf[3]; buf[0] = (waddr & OV9726_MSB_MASK) >> 8; buf[1] = (waddr & OV9726_LSB_MASK); buf[2] = bdata; CDBG("i2c_write_b addr = 0x%x, val = 0x%xd\n", waddr, bdata); rc = ov9726_i2c_txdata(saddr, buf, 3); if (rc < 0) { CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", waddr, bdata); } return rc; } static void ov9726_get_pict_fps(uint16_t fps, uint16_t *pfps) { uint32_t divider; /*Q10 */ uint32_t d1; uint32_t d2; uint16_t snapshot_height, preview_height, preview_width, snapshot_width; if (ov9726_ctrl->prev_res == QTR_SIZE) { preview_width = OV9726_QTR_SIZE_WIDTH + OV9726_HRZ_QTR_BLK_PIXELS ; preview_height = OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES ; } else { /* full size resolution used for preview. */ preview_width = OV9726_FULL_SIZE_WIDTH + OV9726_HRZ_FULL_BLK_PIXELS ; preview_height = OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES ; } if (ov9726_ctrl->pict_res == QTR_SIZE) { snapshot_width = OV9726_QTR_SIZE_WIDTH + OV9726_HRZ_QTR_BLK_PIXELS ; snapshot_height = OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES ; } else { snapshot_width = OV9726_FULL_SIZE_WIDTH + OV9726_HRZ_FULL_BLK_PIXELS; snapshot_height = OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES; } d1 = (uint32_t)(((uint32_t)preview_height << OV9726_Q10Shift) / snapshot_height); d2 = (uint32_t)(((uint32_t)preview_width << OV9726_Q10Shift) / snapshot_width); divider = (uint32_t) (d1 * d2) >> OV9726_Q10Shift; *pfps = (uint16_t)((uint32_t)(fps * divider) >> OV9726_Q10Shift); } static uint16_t ov9726_get_prev_lines_pf(void) { if (ov9726_ctrl->prev_res == QTR_SIZE) return OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES; else return OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES; } static uint16_t ov9726_get_prev_pixels_pl(void) { if (ov9726_ctrl->prev_res == QTR_SIZE) return OV9726_QTR_SIZE_WIDTH + OV9726_HRZ_QTR_BLK_PIXELS; else return OV9726_FULL_SIZE_WIDTH + OV9726_HRZ_FULL_BLK_PIXELS; } static uint16_t ov9726_get_pict_lines_pf(void) { if (ov9726_ctrl->pict_res == QTR_SIZE) return OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES; else return OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES; } static uint16_t ov9726_get_pict_pixels_pl(void) { if (ov9726_ctrl->pict_res == QTR_SIZE) return OV9726_QTR_SIZE_WIDTH + OV9726_HRZ_QTR_BLK_PIXELS; else return OV9726_FULL_SIZE_WIDTH + OV9726_HRZ_FULL_BLK_PIXELS; } static uint32_t ov9726_get_pict_max_exp_lc(void) { if (ov9726_ctrl->pict_res == QTR_SIZE) return (OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES)*24; else return (OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES)*24; } static int32_t ov9726_set_fps(struct fps_cfg *fps) { int32_t rc = 0; CDBG("%s: fps->fps_div = %d\n", __func__, fps->fps_div); /* TODO: Passing of fps_divider from user space has issues. */ /* ov9726_ctrl->fps_divider = fps->fps_div; */ ov9726_ctrl->fps_divider = 1 * 0x400; CDBG("%s: ov9726_ctrl->fps_divider = %d\n", __func__, ov9726_ctrl->fps_divider); ov9726_ctrl->pict_fps_divider = fps->pict_fps_div; ov9726_ctrl->fps = fps->f_mult; return rc; } static int32_t ov9726_write_exp_gain(uint16_t gain, uint32_t line) { static uint16_t max_legal_gain = 0x00FF; uint8_t gain_msb, gain_lsb; uint8_t intg_time_msb, intg_time_lsb; uint8_t ov9726_offset = 6; uint8_t line_length_pck_msb, line_length_pck_lsb; uint16_t line_length_pck, frame_length_lines; uint32_t line_length_ratio = 1 << OV9726_Q8Shift; int32_t rc = -1; CDBG("%s: gain = %d line = %d", __func__, gain, line); if (ov9726_ctrl->sensormode != SENSOR_SNAPSHOT_MODE) { if (ov9726_ctrl->curr_res == QTR_SIZE) { frame_length_lines = OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES; line_length_pck = OV9726_QTR_SIZE_WIDTH + OV9726_HRZ_QTR_BLK_PIXELS; } else { frame_length_lines = OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES; line_length_pck = OV9726_FULL_SIZE_WIDTH + OV9726_HRZ_FULL_BLK_PIXELS; } if (line > (frame_length_lines - ov9726_offset)) ov9726_ctrl->fps = (uint16_t) (((uint32_t)30 << OV9726_Q8Shift) * (frame_length_lines - ov9726_offset) / line); else ov9726_ctrl->fps = (uint16_t) ((uint32_t)30 << OV9726_Q8Shift); } else { frame_length_lines = OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES; line_length_pck = OV9726_FULL_SIZE_WIDTH + OV9726_HRZ_FULL_BLK_PIXELS; } if (ov9726_ctrl->sensormode != SENSOR_SNAPSHOT_MODE) { line = (uint32_t) (line * ov9726_ctrl->fps_divider) >> OV9726_Q10Shift; } else { line = (uint32_t) (line * ov9726_ctrl->pict_fps_divider) >> OV9726_Q10Shift; } /* calculate line_length_ratio */ if (line > (frame_length_lines - ov9726_offset)) { line_length_ratio = (line << OV9726_Q8Shift) / (frame_length_lines - ov9726_offset); line = frame_length_lines - ov9726_offset; } else line_length_ratio = (uint32_t)1 << OV9726_Q8Shift; if (gain > max_legal_gain) { /* range: 0 to 224 */ gain = max_legal_gain; } /* update gain registers */ gain_msb = (uint8_t) ((gain & 0xFF00) >> 8); gain_lsb = (uint8_t) (gain & 0x00FF); /* linear AFR horizontal stretch */ line_length_pck = (uint16_t) ((line_length_pck * line_length_ratio) >> OV9726_Q8Shift); line_length_pck_msb = (uint8_t) ((line_length_pck & 0xFF00) >> 8); line_length_pck_lsb = (uint8_t) (line_length_pck & 0x00FF); /* update line count registers */ intg_time_msb = (uint8_t) ((line & 0xFF00) >> 8); intg_time_lsb = (uint8_t) (line & 0x00FF); rc = ov9726_i2c_write_b(ov9726_client->addr, 0x104, 0x1); if (rc < 0) return rc; rc = ov9726_i2c_write_b(ov9726_client->addr, 0x204, gain_msb); if (rc < 0) return rc; rc = ov9726_i2c_write_b(ov9726_client->addr, 0x205, gain_lsb); if (rc < 0) return rc; rc = ov9726_i2c_write_b(ov9726_client->addr, 0x342, line_length_pck_msb); if (rc < 0) return rc; rc = ov9726_i2c_write_b(ov9726_client->addr, 0x343, line_length_pck_lsb); if (rc < 0) return rc; rc = ov9726_i2c_write_b(ov9726_client->addr, 0x0202, intg_time_msb); if (rc < 0) return rc; rc = ov9726_i2c_write_b(ov9726_client->addr, 0x0203, intg_time_lsb); if (rc < 0) return rc; rc = ov9726_i2c_write_b(ov9726_client->addr, 0x104, 0x0); if (rc < 0) return rc; return rc; } static int32_t ov9726_set_pict_exp_gain(uint16_t gain, uint32_t line) { int32_t rc = 0; rc = ov9726_write_exp_gain(gain, line); return rc; } static int32_t initialize_ov9726_registers(void) { int32_t i; int32_t rc = 0; ov9726_ctrl->sensormode = SENSOR_PREVIEW_MODE ; /* Configure sensor for Preview mode and Snapshot mode */ CDBG("Initialize_ov9726_registers\n"); for (i = 0; i < ov9726_array_length; i++) { rc = ov9726_i2c_write_b(ov9726_client->addr, ov9726_init_settings_array[i].reg_addr, ov9726_init_settings_array[i].reg_val); if (rc < 0) return rc; } return rc; } static int32_t ov9726_video_config(int mode) { int32_t rc = 0; ov9726_ctrl->sensormode = mode; if (config_not_set) { struct msm_camera_csi_params ov9726_csi_params; /* sensor in standby */ ov9726_i2c_write_b(ov9726_client->addr, 0x100, 0); msleep(5); /* Initialize Sensor registers */ ov9726_csi_params.data_format = CSI_10BIT; ov9726_csi_params.lane_cnt = 1; ov9726_csi_params.lane_assign = 0xe4; ov9726_csi_params.dpcm_scheme = 0; ov9726_csi_params.settle_cnt = 7; rc = msm_camio_csi_config(&ov9726_csi_params); rc = initialize_ov9726_registers(); config_not_set = 0; } return rc; } static int32_t ov9726_snapshot_config(int mode) { int32_t rc = 0; ov9726_ctrl->sensormode = mode; return rc; } static int32_t ov9726_raw_snapshot_config(int mode) { int32_t rc = 0; ov9726_ctrl->sensormode = mode; return rc; } static int32_t ov9726_set_sensor_mode(int mode, int res) { int32_t rc = 0; switch (mode) { case SENSOR_PREVIEW_MODE: rc = ov9726_video_config(mode); break; case SENSOR_SNAPSHOT_MODE: rc = ov9726_snapshot_config(mode); break; case SENSOR_RAW_SNAPSHOT_MODE: rc = ov9726_raw_snapshot_config(mode); break; default: rc = -EINVAL; break; } return rc; } static int ov9726_probe_init_sensor(const struct msm_camera_sensor_info *data) { int32_t rc = 0; uint16_t chipidl, chipidh; if (data->sensor_reset_enable) { rc = gpio_request(data->sensor_reset, "ov9726"); if (!rc) { gpio_direction_output(data->sensor_reset, 0); gpio_set_value_cansleep(data->sensor_reset, 1); msleep(20); } else goto init_probe_done; } /* 3. Read sensor Model ID: */ rc = ov9726_i2c_read(OV9726_PIDH_REG, &chipidh, 1); if (rc < 0) goto init_probe_fail; rc = ov9726_i2c_read(OV9726_PIDL_REG, &chipidl, 1); if (rc < 0) goto init_probe_fail; CDBG("kov9726 model_id = 0x%x 0x%x\n", chipidh, chipidl); /* 4. Compare sensor ID to OV9726 ID: */ if (chipidh != OV9726_PID) { rc = -ENODEV; printk(KERN_INFO "Probeinit fail\n"); goto init_probe_fail; } CDBG("chipidh == OV9726_PID\n"); msleep(OV9726_RESET_DELAY_MSECS); CDBG("after delay\n"); goto init_probe_done; init_probe_fail: if (data->sensor_reset_enable) { gpio_direction_output(data->sensor_reset, 0); gpio_free(data->sensor_reset); } init_probe_done: printk(KERN_INFO " ov9726_probe_init_sensor finishes\n"); return rc; } int ov9726_sensor_open_init(const struct msm_camera_sensor_info *data) { int32_t rc; CDBG("Calling ov9726_sensor_open_init\n"); ov9726_ctrl = kzalloc(sizeof(struct ov9726_ctrl_t), GFP_KERNEL); if (!ov9726_ctrl) { CDBG("ov9726_init failed!\n"); rc = -ENOMEM; goto init_done; } ov9726_ctrl->curr_lens_pos = -1; ov9726_ctrl->fps_divider = 1 << OV9726_Q10Shift; ov9726_ctrl->pict_fps_divider = 1 << OV9726_Q10Shift; ov9726_ctrl->set_test = TEST_OFF; ov9726_ctrl->prev_res = FULL_SIZE; ov9726_ctrl->pict_res = FULL_SIZE; ov9726_ctrl->curr_res = INVALID_SIZE; config_not_set = 1; if (data) ov9726_ctrl->sensordata = data; /* enable mclk first */ msm_camio_clk_rate_set(OV9726_DEFAULT_CLOCK_RATE); msleep(20); rc = ov9726_probe_init_sensor(data); if (rc < 0) goto init_fail; ov9726_ctrl->fps = (uint16_t)(30 << OV9726_Q8Shift); /* generate test pattern */ if (rc < 0) goto init_fail; else goto init_done; /* reset the driver state */ init_fail: CDBG(" init_fail\n"); kfree(ov9726_ctrl); init_done: CDBG("init_done\n"); return rc; } static int ov9726_init_client(struct i2c_client *client) { /* Initialize the MSM_CAMI2C Chip */ init_waitqueue_head(&ov9726_wait_queue); return 0; } static const struct i2c_device_id ov9726_i2c_id[] = { { "ov9726", 0}, { } }; static int ov9726_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { int rc = 0; CDBG("ov9726_probe called!\n"); if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { CDBG("i2c_check_functionality failed\n"); goto probe_failure; } ov9726_sensorw = kzalloc(sizeof(struct ov9726_work_t), GFP_KERNEL); if (!ov9726_sensorw) { CDBG("kzalloc failed.\n"); rc = -ENOMEM; goto probe_failure; } i2c_set_clientdata(client, ov9726_sensorw); ov9726_init_client(client); ov9726_client = client; msleep(50); CDBG("ov9726_probe successed! rc = %d\n", rc); return 0; probe_failure: CDBG("ov9726_probe failed! rc = %d\n", rc); return rc; } static int __exit ov9726_remove(struct i2c_client *client) { struct ov9726_work_t_t *sensorw = i2c_get_clientdata(client); free_irq(client->irq, sensorw); ov9726_client = NULL; kfree(sensorw); return 0; } static struct i2c_driver ov9726_i2c_driver = { .id_table = ov9726_i2c_id, .probe = ov9726_i2c_probe, .remove = __exit_p(ov9726_i2c_remove), .driver = { .name = "ov9726", }, }; int ov9726_sensor_config(void __user *argp) { struct sensor_cfg_data cdata; long rc = 0; if (copy_from_user(&cdata, (void *)argp, sizeof(struct sensor_cfg_data))) return -EFAULT; mutex_lock(&ov9726_mut); CDBG("ov9726_sensor_config: cfgtype = %d\n", cdata.cfgtype); switch (cdata.cfgtype) { case CFG_GET_PICT_FPS: ov9726_get_pict_fps(cdata.cfg.gfps.prevfps, &(cdata.cfg.gfps.pictfps)); if (copy_to_user((void *)argp, &cdata, sizeof(struct sensor_cfg_data))) rc = -EFAULT; break; case CFG_GET_PREV_L_PF: cdata.cfg.prevl_pf = ov9726_get_prev_lines_pf(); if (copy_to_user((void *)argp, &cdata, sizeof(struct sensor_cfg_data))) rc = -EFAULT; break; case CFG_GET_PREV_P_PL: cdata.cfg.prevp_pl = ov9726_get_prev_pixels_pl(); if (copy_to_user((void *)argp, &cdata, sizeof(struct sensor_cfg_data))) rc = -EFAULT; break; case CFG_GET_PICT_L_PF: cdata.cfg.pictl_pf = ov9726_get_pict_lines_pf(); if (copy_to_user((void *)argp, &cdata, sizeof(struct sensor_cfg_data))) rc = -EFAULT; break; case CFG_GET_PICT_P_PL: cdata.cfg.pictp_pl = ov9726_get_pict_pixels_pl(); if (copy_to_user((void *)argp, &cdata, sizeof(struct sensor_cfg_data))) rc = -EFAULT; break; case CFG_GET_PICT_MAX_EXP_LC: cdata.cfg.pict_max_exp_lc = ov9726_get_pict_max_exp_lc(); if (copy_to_user((void *)argp, &cdata, sizeof(struct sensor_cfg_data))) rc = -EFAULT; break; case CFG_SET_FPS: case CFG_SET_PICT_FPS: rc = ov9726_set_fps(&(cdata.cfg.fps)); break; case CFG_SET_EXP_GAIN: rc = ov9726_write_exp_gain( cdata.cfg.exp_gain.gain, cdata.cfg.exp_gain.line); break; case CFG_SET_PICT_EXP_GAIN: rc = ov9726_set_pict_exp_gain( cdata.cfg.exp_gain.gain, cdata.cfg.exp_gain.line); break; case CFG_SET_MODE: rc = ov9726_set_sensor_mode(cdata.mode, cdata.rs); break; case CFG_PWR_DOWN: case CFG_MOVE_FOCUS: case CFG_SET_DEFAULT_FOCUS: rc = 0; break; case CFG_SET_EFFECT: default: rc = -EFAULT; break; } mutex_unlock(&ov9726_mut); return rc; } static int ov9726_probe_init_done(const struct msm_camera_sensor_info *data) { if (data->sensor_reset_enable) { gpio_direction_output(data->sensor_reset, 0); gpio_free(data->sensor_reset); } return 0; } static int ov9726_sensor_release(void) { int rc = -EBADF; mutex_lock(&ov9726_mut); if (ov9726_ctrl->sensordata->sensor_reset_enable) { gpio_direction_output( ov9726_ctrl->sensordata->sensor_reset, 0); gpio_free(ov9726_ctrl->sensordata->sensor_reset); } kfree(ov9726_ctrl); ov9726_ctrl = NULL; CDBG("ov9726_release completed\n"); mutex_unlock(&ov9726_mut); return rc; } static int ov9726_sensor_probe(const struct msm_camera_sensor_info *info, struct msm_sensor_ctrl *s) { int rc = 0; rc = i2c_add_driver(&ov9726_i2c_driver); if (rc < 0 || ov9726_client == NULL) { rc = -ENOTSUPP; goto probe_fail; } msm_camio_clk_rate_set(24000000); msleep(20); rc = ov9726_probe_init_sensor(info); if (rc < 0) goto probe_fail; s->s_init = ov9726_sensor_open_init; s->s_release = ov9726_sensor_release; s->s_config = ov9726_sensor_config; s->s_camera_type = FRONT_CAMERA_2D; s->s_mount_angle = info->sensor_platform_info->mount_angle; ov9726_probe_init_done(info); return rc; probe_fail: CDBG("SENSOR PROBE FAILS!\n"); return rc; } static int __ov9726_probe(struct platform_device *pdev) { return msm_camera_drv_start(pdev, ov9726_sensor_probe); } static struct platform_driver msm_camera_driver = { .probe = __ov9726_probe, .driver = { .name = "msm_camera_ov9726", .owner = THIS_MODULE, }, }; static int __init ov9726_init(void) { return platform_driver_register(&msm_camera_driver); } module_init(ov9726_init); void ov9726_exit(void) { i2c_del_driver(&ov9726_i2c_driver); } MODULE_DESCRIPTION("OMNI VGA Bayer sensor driver"); MODULE_LICENSE("GPL v2");