diff options
Diffstat (limited to 'drivers/battery/smb328_charger.c')
| -rwxr-xr-x | drivers/battery/smb328_charger.c | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/drivers/battery/smb328_charger.c b/drivers/battery/smb328_charger.c new file mode 100755 index 00000000..23cf5e08 --- /dev/null +++ b/drivers/battery/smb328_charger.c @@ -0,0 +1,808 @@ +/* + * smb328_charger.c + * Samsung SMB328 Charger Driver + * + * Copyright (C) 2013 Samsung Electronics + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/battery/sec_charger.h> +#include <linux/battery/charger/smb328_charger.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/interrupt.h> + +#include <linux/gpio-pxa.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_irq.h> + +#if defined(SIOP_CHARGING_CURRENT_LIMIT_FEATURE) +#define SIOP_CHARGING_LIMIT_CURRENT 800 +static bool is_siop_limited; +#endif + +static enum power_supply_property sec_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, +}; + +static int smb328_i2c_write(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_write_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + pr_err("%s: Error(%d) : 0x%x\n", __func__, ret, reg); + + return ret; +} + +static int smb328_i2c_read(struct i2c_client *client, + int reg, u8 *buf) +{ + int ret; + ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); + if (ret < 0) + pr_err("%s: Error(%d) : 0x%x\n", __func__, ret, reg); + + return ret; +} + +static void smb328_i2c_write_array(struct i2c_client *client, + u8 *buf, int size) +{ + int i; + for (i = 0; i < size; i += 3) + smb328_i2c_write(client, (u8) (*(buf + i)), (buf + i) + 1); +} + +static void smb328_set_command(struct i2c_client *client, + int reg, int datum) +{ + int val; + u8 data = 0; + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) { + pr_debug("%s : reg(0x%02x): 0x%02x", __func__, reg, data); + if (data != datum) { + data = datum; + if (smb328_i2c_write(client, reg, &data) < 0) + pr_err("%s : error!\n", __func__); + val = smb328_i2c_read(client, reg, &data); + if (val >= 0) + pr_debug(" => 0x%02x\n", data); + } + } +} + +static void smb328_test_read(struct i2c_client *client) +{ + u8 data = 0; + u32 addr = 0; + for (addr = 0; addr < 0x0c; addr++) { + smb328_i2c_read(client, addr, &data); + pr_info("smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } + for (addr = 0x31; addr < 0x3D; addr++) { + smb328_i2c_read(client, addr, &data); + pr_info("smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } +} + +static void smb328_read_regs(struct i2c_client *client, char *str) +{ + u8 data = 0; + u32 addr = 0; + + for (addr = 0x0; addr < 0x0A; addr++) { + smb328_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } + + /* "#" considered as new line in application */ + sprintf(str+strlen(str), "#"); + + for (addr = 0x31; addr < 0x39; addr++) { + smb328_i2c_read(client, addr, &data); + sprintf(str+strlen(str), "0x%x, ", data); + } +} + +static int smb328_get_charging_status(struct i2c_client *client) +{ + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 stat_c = 0; + + smb328_i2c_read(client, SMB328_BATTERY_CHARGING_STATUS_C, &stat_c); + pr_debug("%s : Charging status C(0x%02x)\n", __func__, stat_c); + + /* At least one charge cycle terminated, + * Charge current < Termination Current + */ + if (stat_c & 0xc0) { + /* top-off by full charging */ + status = POWER_SUPPLY_STATUS_FULL; + return status; + } + + /* Is enabled ? */ + if (stat_c & 0x01) { + /* check for 0x30 : 'safety timer' (0b01 or 0b10) or + * 'waiting to begin charging' (0b11) + * check for 0x06 : no charging (0b00) + */ + /* not charging */ + if ((stat_c & 0x30) || !(stat_c & 0x06)) + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } else { + status = POWER_SUPPLY_STATUS_DISCHARGING; + } + + return (int)status; +} + +static int smb328_get_battery_present(struct i2c_client *client) +{ + u8 reg_data, irq_data; + + smb328_i2c_read(client, SMB328_BATTERY_CHARGING_STATUS_B, ®_data); + smb328_i2c_read(client, SMB328_INTERRUPT_STATUS_C, &irq_data); + + reg_data = ((reg_data & 0x01) && (irq_data & 0x10)); + + return !reg_data; +} + +static void smb328_set_writable(struct i2c_client *client, int writable) +{ + u8 reg_data; + + smb328_i2c_read(client, SMB328_COMMAND, ®_data); + + if (writable) + reg_data |= CMD_A_ALLOW_WRITE; + else + reg_data &= ~CMD_A_ALLOW_WRITE; + + smb328_i2c_write(client, SMB328_COMMAND, ®_data); +} + +static u8 smb328_set_charge_enable(struct i2c_client *client, int enable) +{ + u8 chg_en; + u8 reg_data; + + reg_data = 0x00; + + smb328_i2c_write(client, SMB328_FUNCTION_CONTROL_C, ®_data); + + smb328_i2c_read(client, SMB328_COMMAND, &chg_en); + if (!enable) + chg_en |= CMD_CHARGE_EN; + else + chg_en &= ~CMD_CHARGE_EN; + + smb328_i2c_write(client, SMB328_COMMAND, &chg_en); + + return chg_en; +} + +static u8 smb328_set_float_voltage(struct i2c_client *client, int float_voltage) +{ + u8 reg_data, float_data; + + float_voltage = 4350; + + if (float_voltage < 3460) + float_data = 0; + else if (float_voltage <= 4340) + float_data = (float_voltage - 3500) / 20 + 2; + else if ((float_voltage == 4350) || (float_voltage == 4360)) + float_data = 0x2D; /* (4340 -3500)/20 + 1 */ + else + float_data = 0x3F; + + smb328_i2c_read(client, SMB328_FLOAT_VOLTAGE, ®_data); + reg_data &= ~CFG_FLOAT_VOLTAGE_MASK; + reg_data |= float_data << CFG_FLOAT_VOLTAGE_SHIFT; + + smb328_i2c_write(client, SMB328_FLOAT_VOLTAGE, ®_data); + + return reg_data; +} + +static u8 smb328_set_input_current_limit(struct i2c_client *client, + int input_current) +{ + u8 curr_data, reg_data; + + curr_data = input_current < 500 ? 0x0 : + input_current > 1200 ? 0x7 : + (input_current - 500) / 100; + + smb328_i2c_read(client, SMB328_INPUT_CURRENTLIMIT, ®_data); + reg_data &= ~CFG_INPUT_CURRENT_MASK; + reg_data |= curr_data << CFG_INPUT_CURRENT_SHIFT; + + smb328_i2c_write(client, SMB328_INPUT_CURRENTLIMIT, ®_data); + + pr_info("%s: set current limit : 0x%x\n", __func__, reg_data); + + return reg_data; +} + +static u8 smb328_set_termination_current_limit(struct i2c_client *client, + int termination_current) +{ + u8 reg_data, term_data; + + term_data = termination_current < 25 ? 0x0 : + termination_current > 200 ? 0x7 : + (termination_current - 25) / 25; + + /* Charge completion termination current */ + smb328_i2c_read(client, SMB328_CHARGE_CURRENT, ®_data); + reg_data &= ~CFG_TERMINATION_CURRENT_MASK; + reg_data |= term_data << CFG_TERMINATION_CURRENT_SHIFT; + + smb328_i2c_write(client, SMB328_CHARGE_CURRENT, ®_data); + + /* set STAT assertion termination current */ + smb328_i2c_read(client, SMB328_VARIOUS_FUNCTIONS, ®_data); + reg_data &= ~CFG_STAT_ASSETION_TERM_MASK; + reg_data |= term_data << CFG_STAT_ASSETION_TERM_SHIFT; + + smb328_i2c_write(client, SMB328_VARIOUS_FUNCTIONS, ®_data); + + return reg_data; +} + +static u8 smb328_set_fast_charging_current(struct i2c_client *client, + int fast_charging_current) +{ + u8 reg_data, chg_data; + + chg_data = fast_charging_current < 500 ? 0x0 : + fast_charging_current > 1200 ? 0x7 : + (fast_charging_current - 500) / 100; + + smb328_i2c_read(client, SMB328_CHARGE_CURRENT, ®_data); + reg_data &= ~CFG_CHARGE_CURRENT_MASK; + reg_data |= chg_data << CFG_CHARGE_CURRENT_SHIFT; + + smb328_i2c_write(client, SMB328_CHARGE_CURRENT, ®_data); + + pr_info("%s: Charge Current : 0x%x\n", __func__, reg_data); + + return reg_data; +} + +static void smb328_charger_function_control(struct sec_charger_info *charger) +{ + u8 reg_data, charge_mode; + + smb328_set_writable(charger->client, 1); + reg_data = 0x6E; + smb328_i2c_write(charger->client, SMB328_FUNCTION_CONTROL_B, ®_data); + + smb328_i2c_read(charger->client, + SMB328_INTERRUPT_SIGNAL_SELECTION, ®_data); + reg_data |= 0x1; + smb328_i2c_write(charger->client, + SMB328_INTERRUPT_SIGNAL_SELECTION, ®_data); + + if (charger->cable_type == POWER_SUPPLY_TYPE_BATTERY) { + /* turn off charger */ + smb328_set_charge_enable(charger->client, 0); + } else { + pr_info("%s: Input : %d, Charge : %d\n", __func__, + charger->pdata->charging_current[charger->cable_type].input_current_limit, + charger->pdata->charging_current[charger->cable_type].fast_charging_current); + /* AICL enable */ + smb328_i2c_read(charger->client, + SMB328_INPUT_CURRENTLIMIT, ®_data); + reg_data &= ~CFG_AICL_ENABLE; + reg_data &= ~CFG_AICL_VOLTAGE; /* AICL enable voltage 4.25V */ + smb328_i2c_write(charger->client, + SMB328_INPUT_CURRENTLIMIT, ®_data); + + /* Function control A */ + reg_data = 0xDA; + smb328_i2c_write(charger->client, + SMB328_FUNCTION_CONTROL_A, ®_data); + + /* 4.2V float voltage */ + smb328_set_float_voltage(charger->client, + charger->pdata->chg_float_voltage); + + /* Set termination current */ + smb328_set_termination_current_limit(charger->client, + charger->pdata->charging_current[charger-> + cable_type].full_check_current_1st); + + smb328_set_input_current_limit(charger->client, + charger->pdata->charging_current + [charger->cable_type].input_current_limit); + + smb328_set_fast_charging_current(charger->client, + charger->pdata->charging_current + [charger->cable_type].fast_charging_current); + + /* SET USB5/1, AC/USB Mode */ + charge_mode = (charger->cable_type == POWER_SUPPLY_TYPE_MAINS) || + (charger->cable_type == POWER_SUPPLY_TYPE_UARTOFF) ? + 0x3 : 0x2; + smb328_i2c_read(charger->client, SMB328_COMMAND, ®_data); + reg_data &= ~0x0C; + reg_data |= charge_mode << 2; + smb328_i2c_write(charger->client, SMB328_COMMAND, ®_data); + + smb328_set_charge_enable(charger->client, 1); + } + smb328_test_read(charger->client); + smb328_set_writable(charger->client, 0); +} + +static int smb328_get_charging_health(struct i2c_client *client) +{ + struct sec_charger_info *charger = i2c_get_clientdata(client); + int health = POWER_SUPPLY_HEALTH_GOOD; + u8 status_reg, data; + + smb328_i2c_read(client, SMB328_INTERRUPT_STATUS_C, &status_reg); + pr_info("%s : Interrupt status C(0x%02x)\n", __func__, status_reg); + + /* Is enabled ? */ + if (status_reg & 0x2) { + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (status_reg & 0x4) { + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (status_reg & 0x80) { /* watchdog enable workaround */ + /* clear IRQ */ + data = 0xFF; + smb328_i2c_write(client, 0x30, &data); + /* reset function control */ + smb328_charger_function_control(charger); + } + + return (int)health; +} + +static int smb328_get_charge_now(struct i2c_client *client) +{ + u8 chg_now, data, addr; + + smb328_i2c_read(client, SMB328_BATTERY_CHARGING_STATUS_A, &chg_now); + + for (addr = 0; addr < 0x0c; addr++) { + smb328_i2c_read(client, addr, &data); + pr_debug("smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } + for (addr = 0x31; addr <= 0x3D; addr++) { + smb328_i2c_read(client, addr, &data); + pr_debug("smb328 addr : 0x%02x data : 0x%02x\n", addr, data); + } + + /* Clear IRQ */ + data = 0xFF; + smb328_i2c_write(client, 0x30, &data); + + return (chg_now & 0x2); +} + +static void smb328_charger_otg_control(struct sec_charger_info *charger, + int enable) +{ + u8 reg_data; + + smb328_i2c_read(charger->client, SMB328_OTG_POWER_AND_LDO, ®_data); + + if (!enable) + reg_data |= CFG_OTG_ENABLE; + else + reg_data &= ~CFG_OTG_ENABLE; + + smb328_set_writable(charger->client, 1); + smb328_i2c_write(charger->client, SMB328_OTG_POWER_AND_LDO, ®_data); + smb328_set_writable(charger->client, 0); +} + +static void smb328_irq_enable(struct i2c_client *client) +{ + u8 data; + + smb328_set_writable(client, 1); + data = 0x6D; + smb328_i2c_write(client, 0x04, &data); + + smb328_i2c_read(client, + SMB328_INTERRUPT_SIGNAL_SELECTION, &data); + data |= 0x1; + smb328_i2c_write(client, + SMB328_INTERRUPT_SIGNAL_SELECTION, &data); + + smb328_set_writable(client, 0); +} + +static void smb328_irq_disable(struct i2c_client *client) +{ + u8 data; + + smb328_set_writable(client, 1); + data = 0x6E; + smb328_i2c_write(client, 0x04, &data); + + smb328_i2c_read(client, + SMB328_INTERRUPT_SIGNAL_SELECTION, &data); + data &= 0xfe; + smb328_i2c_write(client, + SMB328_INTERRUPT_SIGNAL_SELECTION, &data); + + smb328_set_writable(client, 0); +} + +static int smb328_debugfs_show(struct seq_file *s, void *data) +{ + struct sec_charger_info *charger = s->private; + u8 reg; + u8 reg_data; + + seq_printf(s, "SMB CHARGER IC :\n"); + seq_printf(s, "==================\n"); + for (reg = 0x00; reg <= 0x0A; reg++) { + smb328_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + for (reg = 0x30; reg <= 0x39; reg++) { + smb328_i2c_read(charger->client, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + seq_printf(s, "\n"); + return 0; +} + +static int smb328_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, smb328_debugfs_show, inode->i_private); +} + +static const struct file_operations smb328_debugfs_fops = { + .open = smb328_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static bool smb328_chg_init(struct sec_charger_info *charger) +{ + smb328_irq_disable(charger->client); + + (void) debugfs_create_file("smb328-regs", + S_IRUGO, NULL, (void *)charger, &smb328_debugfs_fops); + + return true; +} + +static int sec_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + u8 data; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb328_get_charging_status(charger->client); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!charger->is_charging) + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb328_get_charging_health(charger->client); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = smb328_get_battery_present(charger->client); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + smb328_irq_disable(charger->client); + if (charger->charging_current) { + smb328_i2c_read(charger->client, SMB328_CHARGE_CURRENT, &data); + switch (data >> 5) { + case 0: + val->intval = 450; + break; + case 1: + val->intval = 600; + break; + case 2: + val->intval = 700; + break; + case 3: + val->intval = 800; + break; + case 4: + val->intval = 900; + break; + case 5: + val->intval = 1000; + break; + case 6: + val->intval = 1100; + break; + case 7: + val->intval = 1200; + break; + } + } else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = smb328_get_charge_now(charger->client); + break; +#if defined(CONFIG_FUELGAUGE_88PM822) + case POWER_SUPPLY_PROP_POWER_STATUS: + val->intval = smb328_get_power_status(charger); + break; +#endif + default: + return false; + } + return true; +} + +static int sec_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_charger_info *charger = + container_of(psy, struct sec_charger_info, psy_chg); + int cable_type, current_now; + + switch (psp) { + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + smb328_set_writable(charger->client, 1); + current_now = + charger->charging_current * val->intval / 100; + smb328_set_fast_charging_current(charger->client, current_now); + smb328_set_writable(charger->client, 0); + break; + /* val->intval : type */ + case POWER_SUPPLY_PROP_ONLINE: + if (charger->charging_current < 0) + smb328_charger_otg_control(charger, 1); + else if (charger->charging_current > 0) { + smb328_charger_function_control(charger); + smb328_charger_otg_control(charger, 1); + } else { + smb328_charger_function_control(charger); + smb328_charger_otg_control(charger, 0); + } + break; + default: + return false; + } + return true; +} + +#ifdef CONFIG_OF +static int smb328_charger_parse_dt(struct device *dev, + sec_battery_platform_data_t *pdata) +{ + struct device_node *np = dev->of_node; + int ret = 0; + int i, len; + const u32 *p; + + if (np == NULL) { + pr_err("%s np NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "chg_float_voltage", + &pdata->chg_float_voltage); + } + + np = of_find_node_by_name(NULL, "sec-battery"); + if (!np) { + pr_err("%s np NULL\n", __func__); + } else { + p = of_get_property(np, "battery,input_current_limit", &len); + if (!p) + return 1; + + len = len / sizeof(u32); + + pdata->charging_current = kzalloc(sizeof(sec_charging_current_t) * len, + GFP_KERNEL); + + for(i = 0; i < len; i++) { + ret = of_property_read_u32_index(np, + "battery,input_current_limit", i, + &pdata->charging_current[i].input_current_limit); + ret = of_property_read_u32_index(np, + "battery,fast_charging_current", i, + &pdata->charging_current[i].fast_charging_current); + ret = of_property_read_u32_index(np, + "battery,full_check_current_1st", i, + &pdata->charging_current[i].full_check_current_1st); + ret = of_property_read_u32_index(np, + "battery,full_check_current_2nd", i, + &pdata->charging_current[i].full_check_current_2nd); + } + } + + return ret; +} + +static struct of_device_id sec_charger_dt_ids[] = { + { .compatible = "QC,smb328", }, + {} +}; +MODULE_DEVICE_TABLE(of, sec_charger_dt_ids); + +#else +static int max77843_charger_parse_dt(struct max77843_charger_data *charger) +{ + return -ENOSYS; +} + +#define rt5033_charger_match_table NULL +#endif + + +static int smb328_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + sec_battery_platform_data_t *pdata; + struct sec_charger_info *charger; + int ret = 0; + + dev_info(&client->dev, + "%s: SEC Charger Driver Loading\n", __func__); + + if (IS_ENABLED(CONFIG_OF)) { + if (!pdata) + pdata = kzalloc(sizeof(sec_battery_platform_data_t), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = smb328_charger_parse_dt(&client->dev, pdata); + if (ret) + return ret; + } else { + pdata = client->dev.platform_data; + } + + ret = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK); + if (ret < 0) + return -EIO; + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger->client = client; + charger->pdata = pdata; + + i2c_set_clientdata(client, charger); + + charger->psy_chg.name = "sec-charger"; + charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; + charger->psy_chg.get_property = sec_chg_get_property; + charger->psy_chg.set_property = sec_chg_set_property; + charger->psy_chg.properties = sec_charger_props; + charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props); + + if (charger->pdata->chg_gpio_init) { + if (!charger->pdata->chg_gpio_init()) { + dev_err(&client->dev, + "%s: Failed to Initialize GPIO\n", __func__); + goto err_free; + } + } + + pr_info("%s: Clinet Address : 0x%x, Add : 0x%x\n", __func__, charger->client->addr, &charger->client->addr); + if (!smb328_chg_init(charger)) { + dev_err(&client->dev, + "%s: Failed to Initialize Charger\n", __func__); + goto err_free; + } + + ret = power_supply_register(&client->dev, &charger->psy_chg); + if (ret) { + dev_err(&client->dev, + "%s: Failed to Register psy_chg\n", __func__); + goto err_free; + } + + dev_dbg(&client->dev, + "%s: SEC Charger Driver Loaded\n", __func__); + return 0; + +err_supply_unreg: + power_supply_unregister(&charger->psy_chg); +err_free: + kfree(charger); + kfree(pdata); + + return ret; +} + +static int smb328_charger_remove( + struct i2c_client *client) +{ + return 0; +} + +static int smb328_charger_suspend(struct i2c_client *client, + pm_message_t state) +{ + return 0; +} + +static int smb328_charger_resume(struct i2c_client *client) +{ + return 0; +} + +static void smb328_charger_shutdown(struct i2c_client *client) +{ +} + +static const struct i2c_device_id sec_charger_id[] = { + {"smb328-charger", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, sec_charger_id); + +static struct i2c_driver sec_charger_driver = { + .driver = { + .name = "smb328-charger", + .of_match_table = sec_charger_dt_ids, + }, + .probe = smb328_charger_probe, + .remove = smb328_charger_remove, + .suspend = smb328_charger_suspend, + .resume = smb328_charger_resume, + .shutdown = smb328_charger_shutdown, + .id_table = sec_charger_id, +}; + +static int __init smb328_charger_init(void) +{ + return i2c_add_driver(&sec_charger_driver); +} + +static void __exit smb328_charger_exit(void) +{ + i2c_del_driver(&sec_charger_driver); +} + +module_init(smb328_charger_init); +module_exit(smb328_charger_exit); + +MODULE_DESCRIPTION("SMB328 Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); |
