/* * ov4682.c - ov4682 sensor driver * * Copyright (c) 2015, NVIDIA CORPORATION, All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nvc_utilities.h" #include "ov4689_tables.h" #define MAX_BUFFER_SIZE 32 /* Frame length: R0x380e~R0x380f */ #define OV4689_NUM_BYTES_FL (2) /* Coarse time: R0x3500~R0x3502 */ #define OV4689_NUM_BYTES_CT (3) /* Gain: R0x3508~R0x3509 */ #define OV4689_NUM_BYTES_GAIN (2) /* Num of regs in override list */ #define OV4689_NUM_BYTES_OVERRIDES (OV4689_NUM_BYTES_FL + \ OV4689_NUM_BYTES_CT + \ OV4689_NUM_BYTES_GAIN) #define OV4689_SUPPORT_SENSORID (1) #define OV4689_SUPPORT_EEPROM (0) #define OV4689_SUPPORT_DEBUGFS (1) struct ov4689_info { struct miscdevice miscdev_info; int mode; struct ov4689_power_rail power; struct ov4689_sensordata sensor_data; struct i2c_client *i2c_client; struct ov4689_platform_data *pdata; struct clk *mclk; struct regmap *regmap; #if OV4689_SUPPORT_DEBUGFS struct dentry *debugdir; #endif atomic_t in_use; #if OV4689_SUPPORT_EEPROM struct ov4689_eeprom_data eeprom[ov4689_EEPROM_NUM_BLOCKS]; u8 eeprom_buf[ov4689_EEPROM_SIZE]; #endif }; static const struct regmap_config sensor_regmap_config = { .reg_bits = 16, .val_bits = 8, .cache_type = REGCACHE_RBTREE, }; static inline void msleep_range(unsigned int delay_base) { usleep_range(delay_base*1000, delay_base*1000+500); } static inline void ov4689_get_frame_length_regs(struct ov4689_reg *regs, u32 frame_length) { /* 2 registers for FL, i.e., 2-byte FL */ regs->addr = 0x380e; regs->val = (frame_length >> 8) & 0xff; (regs + 1)->addr = 0x380f; (regs + 1)->val = (frame_length) & 0xff; } static inline void ov4689_get_coarse_time_regs(struct ov4689_reg *regs, u32 coarse_time) { /* 3 registers for CT, i.e., 3-byte CT */ regs->addr = 0x3500; regs->val = (coarse_time >> 12) & 0xff; (regs + 1)->addr = 0x3501; (regs + 1)->val = (coarse_time >> 4) & 0xff; (regs + 2)->addr = 0x3502; (regs + 2)->val = (coarse_time & 0xf) << 4; } static inline void ov4689_get_gain_reg(struct ov4689_reg *regs, u16 gain) { /* 2 register for gain, i.e., 2-byte gain */ regs->addr = 0x3508; regs->val = (gain >> 8) & 0xff; (regs + 1)->addr = 0x3509; (regs + 1)->val = gain & 0xff; } static inline int ov4689_read_reg(struct ov4689_info *info, u16 addr, u8 *val) { return regmap_read(info->regmap, addr, (unsigned int *) val); } static int ov4689_write_reg(struct ov4689_info *info, u16 addr, u8 val) { int err; err = regmap_write(info->regmap, addr, val); if (err) pr_err("%s:i2c write failed, %x = %x\n", __func__, addr, val); return err; } static int ov4689_write_table(struct ov4689_info *info, const struct ov4689_reg table[], const struct ov4689_reg override_list[], int num_override_regs) { int err; const struct ov4689_reg *next; int i; u16 val; for (next = table; next->addr != OV4689_TABLE_END; next++) { if (next->addr == OV4689_TABLE_WAIT_MS) { msleep_range(next->val); continue; } val = next->val; /* When an override list is passed in, replace the reg */ /* value to write if the reg is in the list */ if (override_list) { for (i = 0; i < num_override_regs; i++) { if (next->addr == override_list[i].addr) { val = override_list[i].val; break; } } } err = ov4689_write_reg(info, next->addr, val); if (err) { pr_err("%s:ov4689_write_table:%d", __func__, err); return err; } } return 0; } static int ov4689_set_mode(struct ov4689_info *info, struct ov4689_mode *mode) { int sensor_mode; int err; struct ov4689_reg reg_list[OV4689_NUM_BYTES_OVERRIDES]; if ((mode->xres == 2688 && mode->yres == 1520) || (mode->xres == 2688 && mode->yres == 1504)) { sensor_mode = OV4689_MODE_2688x1520; pr_info("ov4689_set_mode 2688x1520\n"); } else if (mode->xres == 1920 && mode->yres == 1080) { sensor_mode = OV4689_MODE_1920x1080; pr_info("ov4689_set_mode 1920x1080\n"); } else { pr_err("There is no this resolution no support %dX%d!!", mode->xres, mode->yres); return 1; } /* get a list of override regs for the asking frame length, */ /* coarse integration time, and gain. */ ov4689_get_frame_length_regs(reg_list, mode->frame_length); ov4689_get_coarse_time_regs(reg_list + OV4689_NUM_BYTES_FL, mode->coarse_time); ov4689_get_gain_reg(reg_list + OV4689_NUM_BYTES_FL + OV4689_NUM_BYTES_CT, mode->gain); err = ov4689_write_table(info, mode_table[sensor_mode], reg_list, OV4689_NUM_BYTES_OVERRIDES); info->mode = sensor_mode; pr_info("[ov4689]: stream on.\n"); return 0; } static int ov4689_get_status(struct ov4689_info *info, u8 *dev_status) { *dev_status = 0; return 0; } static int ov4689_set_frame_length(struct ov4689_info *info, u32 frame_length, bool group_hold) { struct ov4689_reg reg_list[OV4689_NUM_BYTES_FL]; int i = 0; int ret; ov4689_get_frame_length_regs(reg_list, frame_length); if (group_hold) { ret = ov4689_write_reg(info, 0x3208, 0x00); if (ret) return ret; } for (i = 0; i < OV4689_NUM_BYTES_FL; i++) { ret = ov4689_write_reg(info, reg_list[i].addr, reg_list[i].val); if (ret) return ret; } if (group_hold) { ret = ov4689_write_reg(info, 0x3208, 0x10); if (ret) return ret; ret = ov4689_write_reg(info, 0x3208, 0xA0); if (ret) return ret; } return 0; } static int ov4689_set_coarse_time(struct ov4689_info *info, u32 coarse_time, bool group_hold) { int ret; struct ov4689_reg reg_list[OV4689_NUM_BYTES_CT]; int i = 0; ov4689_get_coarse_time_regs(reg_list, coarse_time); if (group_hold) { ret = ov4689_write_reg(info, 0x3208, 0x00); if (ret) return ret; } for (i = 0; i < OV4689_NUM_BYTES_CT; i++) { ret = ov4689_write_reg(info, reg_list[i].addr, reg_list[i].val); if (ret) return ret; } if (group_hold) { ret = ov4689_write_reg(info, 0x3208, 0x10); if (ret) return ret; ret = ov4689_write_reg(info, 0x3208, 0xA0); if (ret) return ret; } return 0; } static int ov4689_set_gain(struct ov4689_info *info, u16 gain, bool group_hold) { int ret, i; struct ov4689_reg reg_list[OV4689_NUM_BYTES_GAIN]; ov4689_get_gain_reg(reg_list, gain); if (group_hold) { ret = ov4689_write_reg(info, 0x3208, 0x00); if (ret) return ret; } /* writing 1-byte gain */ for (i = 0; i < OV4689_NUM_BYTES_GAIN; i++) { ret = ov4689_write_reg(info, reg_list[i].addr, reg_list[i].val); if (ret) return ret; } if (group_hold) { ret = ov4689_write_reg(info, 0x3208, 0x10); if (ret) return ret; ret = ov4689_write_reg(info, 0x3208, 0xA0); if (ret) return ret; } return 0; } static int ov4689_set_group_hold(struct ov4689_info *info, struct ov4689_grouphold *ae) { int ret; int count = 0; bool group_hold_enabled = false; if (ae->gain_enable) count++; if (ae->coarse_time_enable) count++; if (ae->frame_length_enable) count++; if (count >= 2) group_hold_enabled = true; if (group_hold_enabled) { ret = ov4689_write_reg(info, 0x3208, 0x00); if (ret) return ret; } if (ae->gain_enable) ov4689_set_gain(info, ae->gain, false); if (ae->coarse_time_enable) ov4689_set_coarse_time(info, ae->coarse_time, false); if (ae->frame_length_enable) ov4689_set_frame_length(info, ae->frame_length, false); if (group_hold_enabled) { ret = ov4689_write_reg(info, 0x3208, 0x10); if (ret) return ret; ret = ov4689_write_reg(info, 0x3208, 0xA0); if (ret) return ret; } return 0; } #if OV4689_SUPPORT_SENSORID static int ov4689_get_sensor_id(struct ov4689_info *info) { int ret = 0; int i; u8 bak = 0; pr_info("%s\n", __func__); if (info->sensor_data.fuse_id_size) return 0; /* select bank 31 */ ov4689_write_reg(info, 0x3d84, 31); for (i = 0; i < 8; i++) { ret |= ov4689_read_reg(info, 0x300A + i, &bak); info->sensor_data.fuse_id[i] = bak; } if (!ret) info->sensor_data.fuse_id_size = 2; return ret; } #endif static void ov4689_mclk_disable(struct ov4689_info *info) { dev_dbg(&info->i2c_client->dev, "%s: disable MCLK\n", __func__); clk_disable_unprepare(info->mclk); } static int ov4689_mclk_enable(struct ov4689_info *info) { int err; unsigned long mclk_init_rate = 24000000; dev_info(&info->i2c_client->dev, "%s: enable MCLK with %lu Hz\n", __func__, mclk_init_rate); err = clk_set_rate(info->mclk, mclk_init_rate); if (!err) err = clk_prepare_enable(info->mclk); return err; } #if OV4689_SUPPORT_EEPROM static int ov4689_eeprom_device_release(struct ov4689_info *info) { int i; for (i = 0; i < ov4689_EEPROM_NUM_BLOCKS; i++) { if (info->eeprom[i].i2c_client != NULL) { i2c_unregister_device(info->eeprom[i].i2c_client); info->eeprom[i].i2c_client = NULL; } } return 0; } static int ov4689_eeprom_device_init(struct ov4689_info *info) { char *dev_name = "eeprom_ov4689"; static struct regmap_config eeprom_regmap_config = { .reg_bits = 8, .val_bits = 8, }; int i; int err; for (i = 0; i < ov4689_EEPROM_NUM_BLOCKS; i++) { info->eeprom[i].adap = i2c_get_adapter( info->i2c_client->adapter->nr); memset(&info->eeprom[i].brd, 0, sizeof(info->eeprom[i].brd)); strncpy(info->eeprom[i].brd.type, dev_name, sizeof(info->eeprom[i].brd.type)); info->eeprom[i].brd.addr = ov4689_EEPROM_ADDRESS + i; info->eeprom[i].i2c_client = i2c_new_device( info->eeprom[i].adap, &info->eeprom[i].brd); info->eeprom[i].regmap = devm_regmap_init_i2c( info->eeprom[i].i2c_client, &eeprom_regmap_config); if (IS_ERR(info->eeprom[i].regmap)) { err = PTR_ERR(info->eeprom[i].regmap); ov4689_eeprom_device_release(info); return err; } } return 0; } static int ov4689_read_eeprom(struct ov4689_info *info, u8 reg, u16 length, u8 *buf) { return regmap_raw_read(info->eeprom[0].regmap, reg, &buf[reg], length); } static int ov4689_write_eeprom(struct ov4689_info *info, u16 addr, u8 val) { return regmap_write(info->eeprom[addr >> 8].regmap, addr & 0xFF, val); } #endif static long OV4689_IOCTL(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; struct ov4689_info *info = file->private_data; switch (_IOC_NR(cmd)) { case _IOC_NR(OV4689_IOCTL_SET_POWER): if (!info->pdata) break; if (arg && info->pdata->power_on) { err = ov4689_mclk_enable(info); if (!err) err = info->pdata->power_on(&info->power); if (err < 0) ov4689_mclk_disable(info); } if (!arg && info->pdata->power_off) { info->pdata->power_off(&info->power); ov4689_mclk_disable(info); } break; case _IOC_NR(OV4689_IOCTL_SET_MODE): { struct ov4689_mode mode; if (copy_from_user(&mode, (const void __user *)arg, sizeof(struct ov4689_mode))) { pr_err("%s:Failed to get mode from user.\n", __func__); return -EFAULT; } return ov4689_set_mode(info, &mode); } case _IOC_NR(OV4689_IOCTL_SET_FRAME_LENGTH): return ov4689_set_frame_length(info, (u32)arg, true); case _IOC_NR(OV4689_IOCTL_SET_COARSE_TIME): return ov4689_set_coarse_time(info, (u32)arg, true); case _IOC_NR(OV4689_IOCTL_SET_GAIN): return ov4689_set_gain(info, (u16)arg, true); case _IOC_NR(OV4689_IOCTL_GET_STATUS): { u8 status; err = ov4689_get_status(info, &status); if (err) return err; if (copy_to_user((void __user *)arg, &status, 1)) { pr_err("%s:Failed to copy status to user\n", __func__); return -EFAULT; } return 0; } #if OV4689_SUPPORT_SENSORID case _IOC_NR(OV4689_IOCTL_GET_SENSORDATA): { err = ov4689_get_sensor_id(info); if (err) { pr_err("%s:Failed to get fuse id info.\n", __func__); return err; } if (copy_to_user((void __user *)arg, &info->sensor_data, sizeof(struct ov4689_sensordata))) { pr_info("%s:Failed to copy fuse id to user space\n", __func__); return -EFAULT; } return 0; } #endif case _IOC_NR(OV4689_IOCTL_SET_GROUP_HOLD): { struct ov4689_grouphold grouphold; if (copy_from_user(&grouphold, (const void __user *)arg, sizeof(struct ov4689_grouphold))) { pr_info("%s:fail group hold\n", __func__); return -EFAULT; } return ov4689_set_group_hold(info, &grouphold); } #if OV4689_SUPPORT_EEPROM /* there is actually one of them really in use NVC_IOCTL_GET_EEPROM_DATA or OV4689_IOCTL_GET_SENSORDATA (legacy?) */ case _IOC_NR(NVC_IOCTL_GET_EEPROM_DATA): { ov4689_read_eeprom(info, 0, ov4689_EEPROM_SIZE, info->eeprom_buf); if (copy_to_user((void __user *)arg, info->eeprom_buf, ov4689_EEPROM_SIZE)) { dev_err(&info->i2c_client->dev, "%s:Failed to copy status to user\n", __func__); return -EFAULT; } return 0; } case _IOC_NR(NVC_IOCTL_SET_EEPROM_DATA): { int i; if (copy_from_user(info->eeprom_buf, (const void __user *)arg, ov4689_EEPROM_SIZE)) { dev_err(&info->i2c_client->dev, "%s:Failed to read from user buffer\n", __func__); return -EFAULT; } for (i = 0; i < ov4689_EEPROM_SIZE; i++) { ov4689_write_eeprom(info, i, info->eeprom_buf[i]); msleep(20); } return 0; } #endif default: pr_err("%s:unknown cmd.\n", __func__); err = -EINVAL; } return err; } #if OV4689_SUPPORT_DEBUGFS static int ov4689_debugfs_show(struct seq_file *s, void *unused) { struct ov4689_info *dev = s->private; dev_dbg(&dev->i2c_client->dev, "%s: ++\n", __func__); return 0; } static ssize_t ov4689_debugfs_write(struct file *file, char const __user *buf, size_t count, loff_t *offset) { struct ov4689_info *dev = ((struct seq_file *)file->private_data)->private; struct i2c_client *i2c_client = dev->i2c_client; int ret = 0; char buffer[MAX_BUFFER_SIZE]; u32 address; u32 data; u8 readback; dev_dbg(&i2c_client->dev, "%s: ++\n", __func__); if (copy_from_user(&buffer, buf, sizeof(buffer))) goto debugfs_write_fail; if (sscanf(buf, "0x%x 0x%x", &address, &data) == 2) goto set_attr; if (sscanf(buf, "0X%x 0X%x", &address, &data) == 2) goto set_attr; if (sscanf(buf, "%d %d", &address, &data) == 2) goto set_attr; if (sscanf(buf, "0x%x 0x%x", &address, &data) == 1) goto read; if (sscanf(buf, "0X%x 0X%x", &address, &data) == 1) goto read; if (sscanf(buf, "%d %d", &address, &data) == 1) goto read; dev_err(&i2c_client->dev, "SYNTAX ERROR: %s\n", buf); return -EFAULT; set_attr: dev_info(&i2c_client->dev, "new address = %x, data = %x\n", address, data); ret |= ov4689_write_reg(dev, address, data); read: ret |= ov4689_read_reg(dev, address, &readback); dev_dbg(&i2c_client->dev, "wrote to address 0x%x with value 0x%x\n", address, readback); if (ret) goto debugfs_write_fail; return count; debugfs_write_fail: dev_err(&i2c_client->dev, "%s: test pattern write failed\n", __func__); return -EFAULT; } static int ov4689_debugfs_open(struct inode *inode, struct file *file) { struct ov4689_info *dev = inode->i_private; struct i2c_client *i2c_client = dev->i2c_client; dev_dbg(&i2c_client->dev, "%s: ++\n", __func__); return single_open(file, ov4689_debugfs_show, inode->i_private); } static const struct file_operations ov4689_debugfs_fops = { .open = ov4689_debugfs_open, .read = seq_read, .write = ov4689_debugfs_write, .llseek = seq_lseek, .release = single_release, }; static void ov4689_remove_debugfs(struct ov4689_info *dev) { struct i2c_client *i2c_client = dev->i2c_client; dev_dbg(&i2c_client->dev, "%s: ++\n", __func__); debugfs_remove_recursive(dev->debugdir); dev->debugdir = NULL; } static void ov4689_create_debugfs(struct ov4689_info *dev) { struct dentry *ret; struct i2c_client *i2c_client = dev->i2c_client; dev_dbg(&i2c_client->dev, "%s\n", __func__); dev->debugdir = debugfs_create_dir(dev->miscdev_info.this_device->kobj.name, NULL); if (!dev->debugdir) goto remove_debugfs; ret = debugfs_create_file("d", S_IWUSR | S_IRUGO, dev->debugdir, dev, &ov4689_debugfs_fops); if (!ret) goto remove_debugfs; return; remove_debugfs: dev_err(&i2c_client->dev, "couldn't create debugfs\n"); ov4689_remove_debugfs(dev); } #endif static int ov4689_power_on(struct ov4689_power_rail *pw) { struct ov4689_info *info = container_of(pw, struct ov4689_info, power); gpio_set_value(info->pdata->reset_gpio, 0); usleep_range(10, 20); gpio_set_value(info->pdata->reset_gpio, 1); usleep_range(1000, 1100); return 0; } static int ov4689_power_off(struct ov4689_power_rail *pw) { struct ov4689_info *info = container_of(pw, struct ov4689_info, power); gpio_set_value(info->pdata->reset_gpio, 0); return 0; } static int ov4689_open(struct inode *inode, struct file *file) { struct miscdevice *miscdev = file->private_data; struct ov4689_info *info; info = container_of(miscdev, struct ov4689_info, miscdev_info); /* check if the device is in use */ if (atomic_xchg(&info->in_use, 1)) { pr_info("%s:BUSY!\n", __func__); return -EBUSY; } file->private_data = info; return 0; } static int ov4689_release(struct inode *inode, struct file *file) { struct ov4689_info *info = file->private_data; file->private_data = NULL; /* warn if device is already released */ WARN_ON(!atomic_xchg(&info->in_use, 0)); return 0; } static int ov4689_power_put(struct ov4689_power_rail *pw) { if (unlikely(!pw)) return -EFAULT; if (likely(pw->avdd)) regulator_put(pw->avdd); if (likely(pw->iovdd)) regulator_put(pw->iovdd); if (likely(pw->dvdd)) regulator_put(pw->dvdd); pw->avdd = NULL; pw->iovdd = NULL; pw->dvdd = NULL; return 0; } static int ov4689_regulator_get(struct ov4689_info *info, struct regulator **vreg, char vreg_name[]) { struct regulator *reg = NULL; int err = 0; reg = regulator_get(&info->i2c_client->dev, vreg_name); if (unlikely(IS_ERR(reg))) { dev_err(&info->i2c_client->dev, "%s %s ERR: %d\n", __func__, vreg_name, (int)reg); err = PTR_ERR(reg); reg = NULL; } else dev_dbg(&info->i2c_client->dev, "%s: %s\n", __func__, vreg_name); *vreg = reg; return err; } static int ov4689_power_get(struct ov4689_info *info) { struct ov4689_power_rail *pw = &info->power; int err = 0; err |= ov4689_regulator_get(info, &pw->avdd, "vana"); /* ananlog 2.7v */ err |= ov4689_regulator_get(info, &pw->dvdd, "vdig"); /* digital 1.2v */ err |= ov4689_regulator_get(info, &pw->iovdd, "vif"); /* IO 1.8v */ return err; } static const struct file_operations ov4689_fileops = { .owner = THIS_MODULE, .open = ov4689_open, .unlocked_ioctl = OV4689_IOCTL, #ifdef CONFIG_COMPAT .compat_ioctl = OV4689_IOCTL, #endif .release = ov4689_release, }; static struct miscdevice ov4689_device = { .minor = MISC_DYNAMIC_MINOR, .name = "ov4689", .fops = &ov4689_fileops, }; static struct ov4689_platform_data *ov4689_parse_dt(struct i2c_client *client) { struct device_node *np = client->dev.of_node; struct ov4689_platform_data *board_info_pdata; board_info_pdata = devm_kzalloc(&client->dev, sizeof(*board_info_pdata), GFP_KERNEL); if (!board_info_pdata) { dev_err(&client->dev, "Failed to allocate pdata\n"); return NULL; } of_property_read_string(np, "clocks", &board_info_pdata->mclk_name); board_info_pdata->reset_gpio = of_get_named_gpio(np, "reset-gpios", 0); board_info_pdata->power_on = ov4689_power_on; board_info_pdata->power_off = ov4689_power_off; return board_info_pdata; } static int ov4689_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ov4689_info *info; int err; const char *mclk_name; pr_info("[ov4689]: probing sensor.\n"); info = devm_kzalloc(&client->dev, sizeof(struct ov4689_info), GFP_KERNEL); if (!info) { pr_err("%s:Unable to allocate memory!\n", __func__); return -ENOMEM; } info->regmap = devm_regmap_init_i2c(client, &sensor_regmap_config); if (IS_ERR(info->regmap)) { dev_err(&client->dev, "regmap init failed: %ld\n", PTR_ERR(info->regmap)); return -ENODEV; } if (client->dev.of_node) info->pdata = ov4689_parse_dt(client); else info->pdata = client->dev.platform_data; if (!info->pdata) { pr_err("[ov4689]:%s:Unable to get platform data\n", __func__); return -EFAULT; } gpio_request_one(info->pdata->reset_gpio, GPIOF_OUT_INIT_LOW, "reset_gpio"); info->i2c_client = client; atomic_set(&info->in_use, 0); info->mode = -1; mclk_name = info->pdata->mclk_name ? info->pdata->mclk_name : "default_mclk"; info->mclk = devm_clk_get(&client->dev, mclk_name); if (IS_ERR(info->mclk)) { dev_err(&client->dev, "%s: unable to get clock %s\n", __func__, mclk_name); return PTR_ERR(info->mclk); } ov4689_power_get(info); memcpy(&info->miscdev_info, &ov4689_device, sizeof(struct miscdevice)); err = misc_register(&info->miscdev_info); if (err) { pr_err("%s:Unable to register misc device!\n", __func__); goto ov4689_probe_fail; } i2c_set_clientdata(client, info); #if OV4689_SUPPORT_EEPROM /* eeprom interface */ err = ov4689_eeprom_device_init(info); if (err) { dev_err(&client->dev, "Failed to allocate eeprom register map: %d\n", err); return err; } #endif #if OV4689_SUPPORT_DEBUGFS /* create debugfs interface */ ov4689_create_debugfs(info); #endif return 0; ov4689_probe_fail: ov4689_power_put(&info->power); return err; } static int ov4689_remove(struct i2c_client *client) { struct ov4689_info *info; info = i2c_get_clientdata(client); misc_deregister(&ov4689_device); ov4689_power_put(&info->power); #if OV4689_SUPPORT_DEBUGFS ov4689_remove_debugfs(info); #endif #if OV4689_SUPPORT_EEPROM ov4689_eeprom_device_release(info); #endif return 0; } static const struct i2c_device_id ov4689_id[] = { { "ov4689", 0 }, { }, }; MODULE_DEVICE_TABLE(i2c, ov4689_id); static struct i2c_driver ov4689_i2c_driver = { .driver = { .name = "ov4689", .owner = THIS_MODULE, }, .probe = ov4689_probe, .remove = ov4689_remove, .id_table = ov4689_id, }; static int __init ov4689_init(void) { pr_info("[ov4689] sensor driver loading\n"); return i2c_add_driver(&ov4689_i2c_driver); } static void __exit ov4689_exit(void) { i2c_del_driver(&ov4689_i2c_driver); } module_init(ov4689_init); module_exit(ov4689_exit);