| /* |
| * drivers/power/smb349-charger.c |
| * |
| * Battery charger driver for smb349 from summit microelectronics |
| * |
| * Copyright (c) 2012, NVIDIA CORPORATION. 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 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. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <linux/mutex.h> |
| #include <linux/err.h> |
| #include <linux/i2c.h> |
| #include <linux/delay.h> |
| #include <linux/power_supply.h> |
| #include <linux/platform_device.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/smb349-charger.h> |
| #include <linux/slab.h> |
| #include <linux/gpio.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/usb/otg.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/uaccess.h> |
| |
| #define SMB349_CHARGE 0x00 |
| #define SMB349_CHRG_CRNTS 0x01 |
| #define SMB349_VRS_FUNC 0x02 |
| #define SMB349_FLOAT_VLTG 0x03 |
| #define SMB349_CHRG_CTRL 0x04 |
| #define SMB349_STAT_TIME_CTRL 0x05 |
| #define SMB349_PIN_CTRL 0x06 |
| #define SMB349_THERM_CTRL 0x07 |
| #define SMB349_CTRL_REG 0x09 |
| |
| #define SMB349_OTG_TLIM_REG 0x0A |
| #define SMB349_HRD_SFT_TEMP 0x0B |
| #define SMB349_FAULT_INTR 0x0C |
| #define SMB349_STS_INTR_1 0x0D |
| #define SMB349_SYSOK_USB3 0x0E |
| #define SMB349_IN_CLTG_DET 0x10 |
| #define SMB349_STS_INTR_2 0x11 |
| |
| #define SMB349_CMD_REG 0x30 |
| #define SMB349_CMD_REG_B 0x31 |
| #define SMB349_CMD_REG_c 0x33 |
| |
| #define SMB349_INTR_STS_A 0x35 |
| #define SMB349_INTR_STS_B 0x36 |
| #define SMB349_INTR_STS_C 0x37 |
| #define SMB349_INTR_STS_D 0x38 |
| #define SMB349_INTR_STS_E 0x39 |
| #define SMB349_INTR_STS_F 0x3A |
| |
| #define SMB349_STS_REG_A 0x3B |
| #define SMB349_STS_REG_B 0x3C |
| #define SMB349_STS_REG_C 0x3D |
| #define SMB349_STS_REG_D 0x3E |
| #define SMB349_STS_REG_E 0x3F |
| |
| #define SMB349_ENABLE_WRITE 1 |
| #define SMB349_DISABLE_WRITE 0 |
| #define ENABLE_WRT_ACCESS 0x80 |
| #define THERM_CTRL 0x10 |
| #define BATTERY_MISSING 0x10 |
| #define CHARGING 0x06 |
| #define DEDICATED_CHARGER 0x04 |
| #define CHRG_DOWNSTRM_PORT 0x08 |
| #define ENABLE_CHARGE 0x02 |
| |
| static struct smb349_charger *charger; |
| static int smb349_configure_charger(struct i2c_client *client, int value); |
| |
| static int smb349_read(struct i2c_client *client, int reg) |
| { |
| int ret; |
| |
| ret = i2c_smbus_read_byte_data(client, reg); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int smb349_write(struct i2c_client *client, int reg, u8 value) |
| { |
| int ret; |
| |
| ret = i2c_smbus_write_byte_data(client, reg, value); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int smb349_update_reg(struct i2c_client *client, int reg, u8 value) |
| { |
| int ret, retval; |
| |
| retval = smb349_read(client, reg); |
| if (retval < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, retval); |
| return retval; |
| } |
| |
| ret = smb349_write(client, reg, retval | value); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| int smb349_volatile_writes(struct i2c_client *client, uint8_t value) |
| { |
| int ret = 0; |
| |
| if (value == SMB349_ENABLE_WRITE) { |
| /* Enable volatile write to config registers */ |
| ret = smb349_update_reg(client, SMB349_CMD_REG, |
| ENABLE_WRT_ACCESS); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, SMB349_CMD_REG); |
| return ret; |
| } |
| } else { |
| ret = smb349_read(client, SMB349_CMD_REG); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| return ret; |
| } |
| |
| ret = smb349_write(client, SMB349_CMD_REG, ret & (~(1<<7))); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| return ret; |
| } |
| } |
| return ret; |
| } |
| |
| static int smb349_configure_otg(struct i2c_client *client, int enable) |
| { |
| int ret = 0; |
| |
| /*Enable volatile writes to registers*/ |
| ret = smb349_volatile_writes(client, SMB349_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s error in configuring otg..\n", |
| __func__); |
| goto error; |
| } |
| |
| if (enable) { |
| /* Configure PGOOD to be active low if no 5V on VBUS */ |
| ret = smb349_read(client, SMB349_STS_REG_C); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| if (!(ret & 0x01)) { |
| ret = smb349_read(client, SMB349_SYSOK_USB3); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", |
| __func__, ret); |
| goto error; |
| } |
| |
| ret = smb349_write(client, SMB349_SYSOK_USB3, |
| (ret & (~(1)))); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", |
| __func__, ret); |
| goto error; |
| } |
| } |
| |
| /* Enable OTG */ |
| ret = smb349_update_reg(client, SMB349_CMD_REG, 0x10); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: Failed in writing register" |
| "0x%02x\n", __func__, SMB349_CMD_REG); |
| goto error; |
| } |
| } else { |
| /* Disable OTG */ |
| ret = smb349_read(client, SMB349_CMD_REG); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| ret = smb349_write(client, SMB349_CMD_REG, (ret & (~(1<<4)))); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| /* Configure PGOOD to be active high */ |
| ret = smb349_update_reg(client, SMB349_SYSOK_USB3, 0x01); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| } |
| |
| /* Disable volatile writes to registers */ |
| ret = smb349_volatile_writes(client, SMB349_DISABLE_WRITE); |
| if (ret < 0) |
| dev_err(&client->dev, "%s error in configuring OTG..\n", |
| __func__); |
| error: |
| return ret; |
| } |
| |
| static int smb349_configure_charger(struct i2c_client *client, int value) |
| { |
| int ret = 0; |
| |
| /* Enable volatile writes to registers */ |
| ret = smb349_volatile_writes(client, SMB349_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| if (value) { |
| /* Enable charging */ |
| ret = smb349_update_reg(client, SMB349_CMD_REG, ENABLE_CHARGE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing register" |
| "0x%02x\n", __func__, SMB349_CMD_REG); |
| goto error; |
| } |
| |
| /* Configure THERM ctrl */ |
| ret = smb349_update_reg(client, SMB349_THERM_CTRL, THERM_CTRL); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| } else { |
| ret = smb349_read(client, SMB349_CMD_REG); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| ret = smb349_write(client, SMB349_CMD_REG, (ret & (~(1<<1)))); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| } |
| /* Disable volatile writes to registers */ |
| ret = smb349_volatile_writes(client, SMB349_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| error: |
| return ret; |
| } |
| |
| int update_charger_status(void) |
| { |
| struct i2c_client *client; |
| int val; |
| |
| if (!charger) |
| return -ENODEV; |
| else |
| client = charger->client; |
| |
| val = smb349_read(client, SMB349_STS_REG_D); |
| if (val < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading register" |
| "0x%02x\n", __func__, SMB349_STS_REG_D); |
| goto val_error; |
| } else if (val != 0) { |
| if (val & DEDICATED_CHARGER) |
| charger->chrg_type = AC; |
| else |
| charger->chrg_type = USB; |
| |
| charger->state = progress; |
| } else { |
| charger->state = stopped; |
| } |
| |
| if (charger->charger_cb) |
| charger->charger_cb(charger->state, charger->chrg_type, |
| charger->charger_cb_data); |
| return 0; |
| val_error: |
| return val; |
| } |
| EXPORT_SYMBOL_GPL(update_charger_status); |
| |
| int register_callback(charging_callback_t cb, void *args) |
| { |
| struct smb349_charger *charger_data = charger; |
| if (!charger_data) |
| return -ENODEV; |
| |
| charger_data->charger_cb = cb; |
| charger_data->charger_cb_data = args; |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(register_callback); |
| |
| int smb349_battery_online(void) |
| { |
| int val; |
| struct i2c_client *client = charger->client; |
| |
| val = smb349_read(charger->client, SMB349_INTR_STS_B); |
| if (val < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading register" |
| "0x%02x\n", __func__, SMB349_INTR_STS_B); |
| return val; |
| } |
| if (val & BATTERY_MISSING) |
| return 0; |
| else |
| return 1; |
| } |
| |
| static int smb349_enable_otg(struct regulator_dev *otg_rdev) |
| { |
| struct i2c_client *client = charger->client; |
| int ret; |
| |
| /* configure charger */ |
| ret = smb349_configure_charger(client, 0); |
| if (ret < 0) |
| goto error; |
| /* ENABLE OTG */ |
| ret = smb349_configure_otg(client, 1); |
| if (ret < 0) |
| goto error; |
| |
| charger->is_otg_enabled = 1; |
| return 0; |
| error: |
| dev_err(&client->dev, "%s() error in enabling" |
| "otg..\n", __func__); |
| return ret; |
| } |
| |
| static int smb349_disable_otg(struct regulator_dev *otg_rdev) |
| { |
| struct i2c_client *client = charger->client; |
| int ret; |
| |
| /* Disable OTG */ |
| ret = smb349_configure_otg(client, 0); |
| if (ret < 0) |
| goto error; |
| /* configure charger */ |
| ret = smb349_configure_charger(client, 1); |
| if (ret < 0) |
| goto error; |
| |
| charger->is_otg_enabled = 0; |
| return 0; |
| error: |
| dev_err(&client->dev, "%s() error in disabling" |
| "otg..\n", __func__); |
| return ret; |
| } |
| |
| static int smb349_is_otg_enabled(struct regulator_dev *otg_rdev) |
| { |
| return charger->is_otg_enabled; |
| } |
| |
| static int smb349_enable_charging(struct regulator_dev *rdev, |
| int min_uA, int max_uA) |
| { |
| struct i2c_client *client = charger->client; |
| int ret; |
| |
| if (!max_uA) { |
| /* Wait for SMB349 to debounce and get reset to POR when cable is unpluged */ |
| msleep(50); |
| |
| ret = smb349_read(client, SMB349_STS_REG_C); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading register" |
| "0x%02x\n", __func__, SMB349_STS_REG_C); |
| return ret; |
| } |
| |
| if (ret & CHARGING) |
| return 0; |
| |
| charger->state = stopped; |
| charger->chrg_type = NONE; |
| } else { |
| /* Wait for SMB349 to reload OTP setting and detect type*/ |
| msleep(500); |
| |
| ret = smb349_read(client, SMB349_STS_REG_D); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading register" |
| "0x%02x\n", __func__, SMB349_STS_REG_D); |
| return ret; |
| } else if (ret != 0) { |
| if (ret & DEDICATED_CHARGER) |
| charger->chrg_type = AC; |
| else |
| charger->chrg_type = USB; |
| |
| /* configure charger */ |
| ret = smb349_configure_charger(client, 1); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in" |
| "configuring charger..\n", __func__); |
| return ret; |
| } |
| charger->state = progress; |
| } |
| } |
| if (charger->charger_cb) |
| charger->charger_cb(charger->state, charger->chrg_type, |
| charger->charger_cb_data); |
| return 0; |
| } |
| |
| static struct regulator_ops smb349_tegra_regulator_ops = { |
| .set_current_limit = smb349_enable_charging, |
| }; |
| |
| static struct regulator_ops smb349_tegra_otg_regulator_ops = { |
| .enable = smb349_enable_otg, |
| .disable = smb349_disable_otg, |
| .is_enabled = smb349_is_otg_enabled, |
| }; |
| |
| #if defined(CONFIG_DEBUG_FS) |
| static struct dentry *smb349_dentry_regs; |
| |
| static int smb349_dump_regs(struct i2c_client *client, u8 *addrs, int num_addrs, |
| char *buf, ssize_t *len) |
| { |
| ssize_t count = *len; |
| int ret = 0; |
| int i; |
| |
| if (count >= PAGE_SIZE - 1) |
| return -ERANGE; |
| |
| for (i = 0; i < num_addrs; i++) { |
| count += sprintf(buf + count, "0x%02x: ", addrs[i]); |
| if (count >= PAGE_SIZE - 1) |
| return -ERANGE; |
| |
| ret = smb349_read(client, addrs[i]); |
| if (ret < 0) |
| count += sprintf(buf + count, "<read fail: %d\n", ret); |
| else |
| count += sprintf(buf + count, "0x%02x\n", ret); |
| |
| if (count >= PAGE_SIZE - 1) |
| return -ERANGE; |
| } |
| *len = count; |
| |
| return 0; |
| } |
| |
| static ssize_t smb349_debugfs_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| return 0; |
| } |
| |
| static u8 regs[] = { |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0A, 0x0B, |
| 0x0C, 0x0D, 0x0E, 0x10, 0x11, 0x12, 0x30, 0x31, 0x33, 0x35, 0x36, |
| 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F |
| }; |
| static ssize_t smb349_debugfs_read(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| ssize_t ret; |
| struct i2c_client *client = file->private_data; |
| char *buf; |
| size_t len = 0; |
| |
| buf = kzalloc(PAGE_SIZE, GFP_KERNEL); |
| if (!buf) |
| return -ENOMEM; |
| |
| len += sprintf(buf + len, "SMB349 Registers\n"); |
| smb349_dump_regs(client, regs, ARRAY_SIZE(regs), buf, &len); |
| |
| ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); |
| kfree(buf); |
| return ret; |
| } |
| |
| static const struct file_operations smb349_debugfs_fops = { |
| .open = smb349_debugfs_open, |
| .read = smb349_debugfs_read, |
| }; |
| |
| static void smb349_debugfs_init(struct i2c_client *client) |
| { |
| smb349_dentry_regs = debugfs_create_file(client->name, |
| 0444, 0, client, |
| &smb349_debugfs_fops); |
| } |
| |
| static void smb349_debugfs_exit(struct i2c_client *client) |
| { |
| debugfs_remove(smb349_dentry_regs); |
| } |
| #else |
| static void smb349_debugfs_init(struct i2c_client *client){} |
| static void smb349_debugfs_exit(struct i2c_client *client){} |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| static int smb349_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); |
| struct smb349_charger_platform_data *pdata; |
| struct regulator_config config = { }; |
| struct regulator_config config_otg = { }; |
| int ret; |
| |
| if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) |
| return -EIO; |
| |
| charger = kzalloc(sizeof(*charger), GFP_KERNEL); |
| if (!charger) |
| return -ENOMEM; |
| |
| charger->client = client; |
| charger->dev = &client->dev; |
| pdata = client->dev.platform_data; |
| i2c_set_clientdata(client, charger); |
| |
| /* Check battery presence */ |
| if (!smb349_battery_online()) { |
| dev_err(&client->dev, "%s() No Battery present, exiting..\n", |
| __func__); |
| ret = -ENODEV; |
| goto regulator_error; |
| } |
| |
| charger->is_otg_enabled = 0; |
| |
| charger->reg_desc.name = "vbus_charger"; |
| charger->reg_desc.ops = &smb349_tegra_regulator_ops; |
| charger->reg_desc.type = REGULATOR_CURRENT; |
| charger->reg_desc.id = pdata->regulator_id; |
| charger->reg_desc.type = REGULATOR_CURRENT; |
| charger->reg_desc.owner = THIS_MODULE; |
| |
| charger->reg_init_data.supply_regulator = NULL; |
| charger->reg_init_data.num_consumer_supplies = |
| pdata->num_consumer_supplies; |
| charger->reg_init_data.regulator_init = NULL; |
| charger->reg_init_data.consumer_supplies = |
| pdata->consumer_supplies; |
| charger->reg_init_data.driver_data = charger; |
| charger->reg_init_data.constraints.name = "vbus_charger"; |
| charger->reg_init_data.constraints.min_uA = 0; |
| charger->reg_init_data.constraints.max_uA = |
| pdata->max_charge_current_mA * 1000; |
| |
| charger->reg_init_data.constraints.valid_modes_mask = |
| REGULATOR_MODE_NORMAL | |
| REGULATOR_MODE_STANDBY; |
| |
| charger->reg_init_data.constraints.valid_ops_mask = |
| REGULATOR_CHANGE_MODE | |
| REGULATOR_CHANGE_STATUS | |
| REGULATOR_CHANGE_CURRENT; |
| |
| config.dev = charger->dev; |
| config.init_data = &charger->reg_init_data; |
| config.driver_data = charger; |
| config.of_node = NULL; |
| |
| charger->rdev = regulator_register(&charger->reg_desc, &config); |
| |
| smb349_debugfs_init(client); |
| |
| if (IS_ERR(charger->rdev)) { |
| dev_err(&client->dev, "failed to register %s\n", |
| charger->reg_desc.name); |
| ret = PTR_ERR(charger->rdev); |
| goto regulator_error; |
| } |
| |
| charger->otg_reg_desc.name = "vbus_otg"; |
| charger->otg_reg_desc.ops = &smb349_tegra_otg_regulator_ops; |
| charger->otg_reg_desc.type = REGULATOR_CURRENT; |
| charger->otg_reg_desc.id = pdata->otg_regulator_id; |
| charger->otg_reg_desc.type = REGULATOR_CURRENT; |
| charger->otg_reg_desc.owner = THIS_MODULE; |
| |
| charger->otg_reg_init_data.supply_regulator = NULL; |
| charger->otg_reg_init_data.num_consumer_supplies = |
| pdata->num_otg_consumer_supplies; |
| charger->otg_reg_init_data.regulator_init = NULL; |
| charger->otg_reg_init_data.consumer_supplies = |
| pdata->otg_consumer_supplies; |
| charger->otg_reg_init_data.driver_data = charger; |
| charger->otg_reg_init_data.constraints.name = "vbus_otg"; |
| charger->otg_reg_init_data.constraints.min_uA = 0; |
| charger->otg_reg_init_data.constraints.max_uA = 500000; |
| |
| charger->otg_reg_init_data.constraints.valid_modes_mask = |
| REGULATOR_MODE_NORMAL | |
| REGULATOR_MODE_STANDBY; |
| |
| charger->otg_reg_init_data.constraints.valid_ops_mask = |
| REGULATOR_CHANGE_MODE | |
| REGULATOR_CHANGE_STATUS | |
| REGULATOR_CHANGE_CURRENT; |
| |
| config.dev = charger->dev; |
| config.init_data = &charger->otg_reg_init_data; |
| config.driver_data = charger; |
| config.of_node = NULL; |
| |
| charger->otg_rdev = regulator_register(&charger->otg_reg_desc, &config); |
| if (IS_ERR(charger->otg_rdev)) { |
| dev_err(&client->dev, "failed to register %s\n", |
| charger->otg_reg_desc.name); |
| ret = PTR_ERR(charger->otg_rdev); |
| goto otg_regulator_error; |
| } |
| |
| /* disable OTG */ |
| ret = smb349_configure_otg(client, 0); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring" |
| "charger..\n", __func__); |
| goto error; |
| } |
| |
| ret = smb349_read(client, SMB349_STS_REG_D); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading register" |
| "0x%02x\n", __func__, SMB349_STS_REG_D); |
| goto error; |
| } else if (ret != 0) { |
| /* configure charger */ |
| ret = smb349_configure_charger(client, 1); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring" |
| "charger..\n", __func__); |
| goto error; |
| } |
| } else { |
| /* disable charger */ |
| ret = smb349_configure_charger(client, 0); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring" |
| "charger..\n", __func__); |
| goto error; |
| } |
| } |
| |
| return 0; |
| error: |
| smb349_debugfs_exit(client); |
| regulator_unregister(charger->otg_rdev); |
| otg_regulator_error: |
| regulator_unregister(charger->rdev); |
| regulator_error: |
| kfree(charger); |
| charger = NULL; |
| return ret; |
| } |
| |
| static int smb349_remove(struct i2c_client *client) |
| { |
| struct smb349_charger *charger = i2c_get_clientdata(client); |
| |
| smb349_debugfs_exit(client); |
| regulator_unregister(charger->rdev); |
| regulator_unregister(charger->otg_rdev); |
| kfree(charger); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id smb349_id[] = { |
| { "smb349", 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, smb349_id); |
| |
| static struct i2c_driver smb349_i2c_driver = { |
| .driver = { |
| .name = "smb349", |
| }, |
| .probe = smb349_probe, |
| .remove = smb349_remove, |
| .id_table = smb349_id, |
| }; |
| |
| static int __init smb349_init(void) |
| { |
| return i2c_add_driver(&smb349_i2c_driver); |
| } |
| subsys_initcall(smb349_init); |
| |
| static void __exit smb349_exit(void) |
| { |
| i2c_del_driver(&smb349_i2c_driver); |
| } |
| module_exit(smb349_exit); |
| |
| MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>"); |
| MODULE_DESCRIPTION("SMB349 Battery-Charger"); |
| MODULE_LICENSE("GPL"); |