| /* |
| * drivers/power/smb347-charger.c |
| * |
| * Battery charger driver for smb347 from summit microelectronics |
| * |
| * Copyright (c) 2012, NVIDIA Corporation. |
| * |
| * 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/smb347-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/workqueue.h> |
| #include <asm/uaccess.h> |
| #include <mach/board-grouper-misc.h> |
| #include "../../arch/arm/mach-tegra/gpio-names.h" |
| |
| #define smb347_CHARGE 0x00 |
| #define smb347_CHRG_CRNTS 0x01 |
| #define smb347_VRS_FUNC 0x02 |
| #define smb347_FLOAT_VLTG 0x03 |
| #define smb347_CHRG_CTRL 0x04 |
| #define smb347_STAT_TIME_CTRL 0x05 |
| #define smb347_PIN_CTRL 0x06 |
| #define smb347_THERM_CTRL 0x07 |
| #define smb347_SYSOK_USB3 0x08 |
| #define smb347_CTRL_REG 0x09 |
| |
| #define smb347_OTG_TLIM_REG 0x0A |
| #define smb347_HRD_SFT_TEMP 0x0B |
| #define smb347_FAULT_INTR 0x0C |
| #define smb347_STS_INTR_1 0x0D |
| #define smb347_I2C_ADDR 0x0E |
| #define smb347_IN_CLTG_DET 0x10 |
| #define smb347_STS_INTR_2 0x11 |
| |
| /* Command registers */ |
| #define smb347_CMD_REG 0x30 |
| #define smb347_CMD_REG_B 0x31 |
| #define smb347_CMD_REG_c 0x33 |
| |
| /* Interrupt Status registers */ |
| #define smb347_INTR_STS_A 0x35 |
| #define smb347_INTR_STS_B 0x36 |
| #define smb347_INTR_STS_C 0x37 |
| #define smb347_INTR_STS_D 0x38 |
| #define smb347_INTR_STS_E 0x39 |
| #define smb347_INTR_STS_F 0x3A |
| |
| /* Status registers */ |
| #define smb347_STS_REG_A 0x3B |
| #define smb347_STS_REG_B 0x3C |
| #define smb347_STS_REG_C 0x3D |
| #define smb347_STS_REG_D 0x3E |
| #define smb347_STS_REG_E 0x3F |
| |
| #define smb347_ENABLE_WRITE 1 |
| #define smb347_DISABLE_WRITE 0 |
| #define ENABLE_WRT_ACCESS 0x80 |
| #define ENABLE_APSD 0x04 |
| #define HC_MODE 0x01 |
| #define USB_5_9_CUR 0x02 |
| #define PIN_CTRL 0x10 |
| #define PIN_ACT_LOW 0x20 |
| #define THERM_CTRL 0x10 |
| #define BATTERY_MISSING 0x10 |
| #define CHARGING 0x06 |
| #define DEDICATED_CHARGER 0x02 |
| #define CHRG_DOWNSTRM_PORT 0x04 |
| #define ENABLE_CHARGE 0x02 |
| #define ENABLE_CHARGER 1 |
| #define DISABLE_CHARGER 0 |
| #define USBIN 0x80 |
| #define APSD_OK 0x08 |
| #define APSD_RESULT 0x07 |
| #define APSD_CDP 0x01 |
| #define APSD_DCP 0x02 |
| #define APSD_OTHER 0x03 |
| #define APSD_SDP 0x04 |
| #define USB_30 0x20 |
| #define DCIN_OV_UV_STS 0x50 |
| #define DELAY_FOR_CURR_LIMIT_RECONF (60) |
| #define ADAPTER_PROTECT_DELAY (4*HZ) |
| #define GPIO_AC_OK TEGRA_GPIO_PV1 |
| #define ENABLE_PIN_CTRL_MASK 0x60 |
| #define BAT_Hot_Limit 45 |
| |
| /* Functions declaration */ |
| static int smb347_configure_charger(struct i2c_client *client, int value); |
| static int smb347_configure_interrupts(struct i2c_client *client); |
| extern int battery_callback(unsigned usb_cable_state); |
| /* Enable or disable the callback for the battery driver. */ |
| #define TOUCH_CALLBACK_ENABLED 1 |
| #ifdef TOUCH_CALLBACK_ENABLED |
| extern void touch_callback(unsigned cable_status); |
| #endif |
| |
| static ssize_t smb347_reg_show(struct device *dev, struct device_attribute *attr, char *buf); |
| |
| /* Global variables */ |
| static struct smb347_charger *charger; |
| static struct workqueue_struct *smb347_wq; |
| struct wake_lock charger_wakelock; |
| static unsigned int project_id; |
| static unsigned int pcba_ver; |
| static int gpio_dock_in = 0; |
| static int charge_en_flag = 1; |
| static unsigned usb_det_cable_type = non_cable; |
| |
| /* Sysfs interface */ |
| static DEVICE_ATTR(reg_status, S_IWUSR | S_IRUGO, smb347_reg_show, NULL); |
| |
| static struct attribute *smb347_attributes[] = { |
| &dev_attr_reg_status.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group smb347_group = { |
| .attrs = smb347_attributes, |
| }; |
| |
| static int smb347_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 smb347_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 smb347_update_reg(struct i2c_client *client, int reg, u8 value) |
| { |
| int ret, retval; |
| |
| retval = smb347_read(client, reg); |
| if (retval < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, retval); |
| return retval; |
| } |
| |
| ret = smb347_write(client, reg, retval | value); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int smb347_clear_reg(struct i2c_client *client, int reg, u8 value) |
| { |
| int ret, retval; |
| |
| retval = smb347_read(client, reg); |
| if (retval < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, retval); |
| return retval; |
| } |
| |
| ret = smb347_write(client, reg, retval & (~value)); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| int smb347_volatile_writes(struct i2c_client *client, uint8_t value) |
| { |
| int ret = 0; |
| |
| if (value == smb347_ENABLE_WRITE) { |
| /* Enable volatile write to config registers */ |
| ret = smb347_update_reg(client, smb347_CMD_REG, |
| ENABLE_WRT_ACCESS); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_CMD_REG); |
| return ret; |
| } |
| } else { |
| ret = smb347_read(client, smb347_CMD_REG); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| return ret; |
| } |
| |
| ret = smb347_write(client, smb347_CMD_REG, ret & (~(1<<7))); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| return ret; |
| } |
| } |
| return ret; |
| } |
| |
| static void smb347_clear_interrupts(struct i2c_client *client) |
| { |
| uint8_t val, buf[6]; |
| |
| val = i2c_smbus_read_i2c_block_data(client, smb347_INTR_STS_A, 6, buf); |
| if (val < 0) |
| dev_err(&client->dev, "%s(): Failed in clearing interrupts\n", |
| __func__); |
| } |
| |
| static int smb347_configure_otg(struct i2c_client *client, int enable) |
| { |
| int ret = 0; |
| |
| /*Enable volatile writes to registers*/ |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s error in configuring otg..\n", |
| __func__); |
| goto error; |
| } |
| |
| if (enable) { |
| |
| /* Configure INOK to be active high */ |
| ret = smb347_update_reg(client, smb347_SYSOK_USB3, 0x01); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| /* Change "OTG output current limit" to 250mA */ |
| ret = smb347_read(client, smb347_OTG_TLIM_REG); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| ret = smb347_write(client, smb347_OTG_TLIM_REG, (ret & (~(1<<3)))); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| /* Enable OTG */ |
| ret = smb347_update_reg(client, smb347_CMD_REG, 0x10); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: Failed in writing register" |
| "0x%02x\n", __func__, smb347_CMD_REG); |
| goto error; |
| } |
| |
| /* Change "OTG output current limit" from 250mA to 750mA */ |
| ret = smb347_update_reg(client, smb347_OTG_TLIM_REG, 0x08); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: Failed in writing register" |
| "0x%02x\n", __func__, smb347_OTG_TLIM_REG); |
| goto error; |
| } |
| |
| } else { |
| /* Disable OTG */ |
| ret = smb347_read(client, smb347_CMD_REG); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| ret = smb347_write(client, smb347_CMD_REG, (ret & (~(1<<4)))); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| /* Configure INOK to be active low */ |
| ret = smb347_read(client, smb347_SYSOK_USB3); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| ret = smb347_write(client, smb347_SYSOK_USB3, (ret & (~(1)))); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| } |
| |
| /* Disable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s error in configuring OTG..\n", |
| __func__); |
| goto error; |
| } |
| error: |
| return ret; |
| } |
| |
| static int smb347_configure_charger(struct i2c_client *client, int value) |
| { |
| int ret = 0; |
| |
| /* Enable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| if (value) { |
| /* Enable charging */ |
| ret = smb347_update_reg(client, smb347_CMD_REG, ENABLE_CHARGE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing register" |
| "0x%02x\n", __func__, smb347_CMD_REG); |
| goto error; |
| } |
| |
| /* Configure THERM ctrl */ |
| /* |
| ret = smb347_update_reg(client, smb347_THERM_CTRL, THERM_CTRL); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| */ |
| } else { |
| /* Disable charging */ |
| ret = smb347_read(client, smb347_CMD_REG); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| ret = smb347_write(client, smb347_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 = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| error: |
| return ret; |
| } |
| |
| static int smb347_pin_control(bool state) |
| { |
| struct i2c_client *client = charger->client; |
| u8 ret = 0; |
| |
| mutex_lock(&charger->pinctrl_lock); |
| |
| if (state) { |
| /*Pin Controls -active low */ |
| ret = smb347_update_reg(client, smb347_PIN_CTRL, PIN_ACT_LOW); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed to" |
| "enable charger\n", __func__); |
| } |
| } else { |
| /*Pin Controls -active high */ |
| ret = smb347_clear_reg(client, smb347_PIN_CTRL, PIN_ACT_LOW); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed to" |
| "disable charger\n", __func__); |
| } |
| } |
| |
| mutex_unlock(&charger->pinctrl_lock); |
| return ret; |
| } |
| |
| int smb347_charger_enable(bool state) |
| { |
| struct i2c_client *client = charger->client; |
| u8 ret = 0; |
| |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| charge_en_flag = state; |
| smb347_pin_control(state); |
| |
| ret = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| error: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(smb347_charger_enable); |
| |
| static int |
| smb347_set_InputCurrentlimit(struct i2c_client *client, u32 current_limit) |
| { |
| int ret = 0, retval; |
| u8 setting = 0; |
| |
| if (charger->curr_limit == current_limit) |
| return ret; |
| |
| wake_lock(&charger_wakelock); |
| /* Enable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| /* disable charger */ |
| if (charge_en_flag) |
| smb347_pin_control(0); |
| |
| /* AICL disable */ |
| retval = smb347_read(client, smb347_VRS_FUNC); |
| if (retval < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading 0x%02x", |
| __func__, smb347_VRS_FUNC); |
| goto error; |
| } |
| |
| setting = retval & (~(BIT(4))); |
| printk(KERN_INFO "[charger] Disable AICL, retval=%x setting=%x\n", |
| retval, setting); |
| ret = smb347_write(client, smb347_VRS_FUNC, setting); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register" |
| "0x%02x\n", __func__, setting, smb347_VRS_FUNC); |
| goto error; |
| } |
| |
| /* set input current limit */ |
| retval = smb347_read(client, smb347_CHRG_CRNTS); |
| if (retval < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading 0x%02x", |
| __func__, smb347_CHRG_CRNTS); |
| goto error; |
| } |
| setting = retval & 0xF0; |
| if (current_limit > 900) |
| setting |= 0x06; |
| else |
| setting |= 0x03; |
| |
| printk(KERN_INFO "[charger] set cahrger limmit, limit=%u retval =%x setting=%x\n", |
| current_limit, retval, setting); |
| |
| ret = smb347_write(client, smb347_CHRG_CRNTS, setting); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register" |
| "0x%02x\n", __func__, setting, smb347_CHRG_CRNTS); |
| goto error; |
| } |
| |
| if (current_limit > 900) { |
| charger->time_of_1800mA_limit = jiffies; |
| charger->curr_limit = 1800; |
| } else{ |
| charger->time_of_1800mA_limit = 0; |
| charger->curr_limit = 900; |
| } |
| |
| /* AICL enable */ |
| retval = smb347_read(client, smb347_VRS_FUNC); |
| if (retval < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading 0x%02x", |
| __func__, smb347_VRS_FUNC); |
| goto error; |
| } |
| |
| setting = retval | BIT(4); |
| printk(KERN_INFO "[charger] re-enable AICL, setting=%x\n", setting); |
| msleep(20); |
| ret = smb347_write(client, smb347_VRS_FUNC, setting); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register" |
| "0x%02x\n", __func__, setting, smb347_VRS_FUNC); |
| goto error; |
| } |
| |
| /* enable charger */ |
| if (charge_en_flag) |
| smb347_pin_control(1); |
| |
| /* Disable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| error: |
| wake_unlock(&charger_wakelock); |
| return ret; |
| } |
| |
| static void smb347_set_curr_limit_work_func(struct work_struct *work) |
| { |
| smb347_set_InputCurrentlimit(charger->client, 1800); |
| } |
| |
| static void smb347_test_fail_clear_work_function(struct work_struct *work) |
| { |
| charger->test_1800mA_fail = 0; |
| } |
| |
| static irqreturn_t smb347_inok_isr(int irq, void *dev_id) |
| { |
| struct smb347_charger *smb = dev_id; |
| |
| queue_delayed_work(smb347_wq, &smb->inok_isr_work, 0.6*HZ); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t smb347_dockin_isr(int irq, void *dev_id) |
| { |
| struct smb347_charger *smb = dev_id; |
| |
| queue_delayed_work(smb347_wq, &smb->dockin_isr_work, 0*HZ); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int smb347_inok_irq(struct smb347_charger *smb) |
| { |
| int err = 0 ; |
| unsigned gpio = GPIO_AC_OK; |
| unsigned irq_num = gpio_to_irq(gpio); |
| |
| err = gpio_request(gpio, "smb347_inok"); |
| if (err) { |
| printk("gpio %d request failed \n", gpio); |
| goto err1; |
| } |
| |
| tegra_gpio_enable(gpio); |
| |
| err = gpio_direction_input(gpio); |
| if (err) { |
| printk("gpio %d unavaliable for input \n", gpio); |
| goto err2; |
| } |
| |
| err = request_irq(irq_num, smb347_inok_isr, IRQF_TRIGGER_FALLING |IRQF_TRIGGER_RISING, |
| "smb347_inok", smb); |
| if (err < 0) { |
| printk("%s irq %d request failed \n","smb347_inok", irq_num); |
| goto err2 ; |
| } |
| printk("GPIO pin irq %d requested ok, smb347_INOK = %s\n", irq_num, gpio_get_value(gpio)? "H":"L"); |
| |
| return 0 ; |
| |
| err2: |
| gpio_free(gpio); |
| err1: |
| return err; |
| } |
| |
| static int smb347_dockin_irq(struct smb347_charger *smb) |
| { |
| int err = 0 ; |
| unsigned gpio = gpio_dock_in; |
| unsigned irq_num = gpio_to_irq(gpio); |
| |
| err = gpio_request(gpio, "smb347_dockin"); |
| if (err) { |
| printk("gpio %d request failed \n", gpio); |
| goto err1; |
| } |
| |
| tegra_gpio_enable(gpio); |
| |
| err = gpio_direction_input(gpio); |
| if (err) { |
| printk("gpio %d unavaliable for input \n", gpio); |
| goto err2; |
| } |
| |
| err = request_irq(irq_num, smb347_dockin_isr, IRQF_SHARED|IRQF_TRIGGER_FALLING |IRQF_TRIGGER_RISING, |
| "smb347_dockin", smb); |
| if (err < 0) { |
| printk("%s irq %d request failed \n","smb347_dockin", irq_num); |
| goto err2 ; |
| } |
| |
| enable_irq_wake(irq_num); |
| printk("GPIO pin irq %d requested ok, smb347_DOCK_IN# = %s\n", irq_num, gpio_get_value(gpio)? "H":"L"); |
| |
| return 0; |
| |
| err2: |
| gpio_free(gpio); |
| err1: |
| return err; |
| } |
| |
| |
| int register_callback(charging_callback_t cb, void *args) |
| { |
| struct smb347_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 smb347_hc_mode_callback(bool enable, int cur) |
| { |
| struct i2c_client *client = charger->client; |
| u8 ret = 0; |
| |
| if((pcba_ver > GROUPER_PCBA_ER2) && (project_id == GROUPER_PROJECT_NAKASI)) |
| return 0; |
| |
| if (charger->suspend_ongoing) |
| return 0; |
| |
| printk("smb347_hc_mode_callback+\n"); |
| |
| /* Enable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| if(enable) { |
| /* Force switch to HC mode */ |
| ret = smb347_update_reg(client, smb347_CMD_REG_B, |
| HC_MODE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_CMD_REG_B); |
| return ret; |
| } |
| |
| /* Change to i2c register control */ |
| ret = smb347_clear_reg(client, smb347_PIN_CTRL, PIN_CTRL); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_PIN_CTRL); |
| return ret; |
| } |
| } |
| else |
| { |
| /* USB 2.0 input current limit (ICL) */ |
| ret = smb347_clear_reg(client, smb347_SYSOK_USB3, USB_30); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_SYSOK_USB3); |
| return ret; |
| } |
| |
| /* Switch back to USB mode */ |
| ret = smb347_clear_reg(client, smb347_CMD_REG_B, HC_MODE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_CMD_REG_B); |
| return ret; |
| } |
| |
| if(cur) { |
| /* USB 500mA */ |
| ret = smb347_update_reg(client, smb347_CMD_REG_B, USB_5_9_CUR); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_CMD_REG_B); |
| return ret; |
| } |
| } else { |
| /* USB 100mA */ |
| ret = smb347_clear_reg(client, smb347_CMD_REG_B, USB_5_9_CUR); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_CMD_REG_B); |
| return ret; |
| } |
| } |
| |
| /* Disable auto power source detection (APSD) */ |
| ret = smb347_clear_reg(client, smb347_CHRG_CTRL, ENABLE_APSD); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_CHRG_CTRL); |
| return ret; |
| } |
| |
| /* Change to i2c register control */ |
| ret = smb347_clear_reg(client, smb347_PIN_CTRL, PIN_CTRL); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing" |
| "register 0x%02x\n", __func__, smb347_PIN_CTRL); |
| return ret; |
| } |
| } |
| |
| /* Disable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| printk("smb347_hc_mode_callback-\n"); |
| return ret; |
| |
| error: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(smb347_hc_mode_callback); |
| |
| int smb347_battery_online(void) |
| { |
| int val, ret; |
| struct i2c_client *client = charger->client; |
| |
| /* Enable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| val = smb347_read(client, smb347_INTR_STS_B); |
| if (val < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading register" |
| "0x%02x\n", __func__, smb347_INTR_STS_B); |
| return val; |
| } |
| if (val & BATTERY_MISSING) |
| return 0; |
| else |
| return 1; |
| |
| /* Disable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| error: |
| return ret; |
| } |
| |
| static int smb347_configure_interrupts(struct i2c_client *client) |
| { |
| int ret = 0; |
| |
| /* Enable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| /* Setting: Fault assert STAT IRQ */ |
| ret = smb347_update_reg(client, smb347_FAULT_INTR, 0x00); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing register" |
| "0x%02x\n", __func__, smb347_CMD_REG); |
| goto error; |
| } |
| /* Setting: Status assert STAT IRQ */ |
| ret = smb347_update_reg(client, smb347_STS_INTR_1, 0x14); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| goto error; |
| } |
| |
| /* Disable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| |
| error: |
| return ret; |
| } |
| |
| static void smb347_otg_status(enum usb_otg_state to, enum usb_otg_state from, void *data) |
| { |
| struct i2c_client *client = charger->client; |
| int ret; |
| |
| if ((from == OTG_STATE_A_SUSPEND) && (to == OTG_STATE_A_HOST)) { |
| |
| /* configure charger */ |
| ret = smb347_configure_charger(client, 0); |
| if (ret < 0) |
| dev_err(&client->dev, "%s() error in configuring" |
| "otg..\n", __func__); |
| |
| /* ENABLE OTG */ |
| ret = smb347_configure_otg(client, 1); |
| if (ret < 0) |
| dev_err(&client->dev, "%s() error in configuring" |
| "otg..\n", __func__); |
| |
| } else if ((from == OTG_STATE_A_HOST) && (to == OTG_STATE_A_SUSPEND)) { |
| |
| /* Disable OTG */ |
| ret = smb347_configure_otg(client, 0); |
| if (ret < 0) |
| dev_err(&client->dev, "%s() error in configuring" |
| "otg..\n", __func__); |
| |
| /* configure charger */ |
| ret = smb347_configure_charger(client, 1); |
| if (ret < 0) |
| dev_err(&client->dev, "%s() error in configuring" |
| "otg..\n", __func__); |
| |
| /* |
| ret = smb347_configure_interrupts(client); |
| if (ret < 0) |
| dev_err(&client->dev, "%s() error in configuring" |
| "otg..\n", __func__); |
| */ |
| } |
| } |
| |
| /* workqueue function */ |
| static int cable_type_detect(void) |
| { |
| struct i2c_client *client = charger->client; |
| u8 retval; |
| int success = 0; |
| int ac_ok = GPIO_AC_OK; |
| int dock_in = gpio_dock_in; |
| |
| /* |
| printk("cable_type_detect %d %lu %d %x jiffies=%lu %lu+\n", |
| charger->old_cable_type, |
| charger->time_of_1800mA_limit, |
| gpio_get_value(gpio), |
| time_after(charger->time_of_1800mA_limit+(4*HZ), jiffies ), |
| jiffies, |
| charger->time_of_1800mA_limit+(ADAPTER_PROTECT_DELAY*HZ)); |
| */ |
| |
| if((pcba_ver <= GROUPER_PCBA_ER2) && (project_id == GROUPER_PROJECT_NAKASI)) |
| return 0; |
| |
| mutex_lock(&charger->cable_lock); |
| |
| if ((charger->old_cable_type == ac_cable) && |
| charger->time_of_1800mA_limit && gpio_get_value(ac_ok) && |
| time_after(charger->time_of_1800mA_limit+ |
| ADAPTER_PROTECT_DELAY, jiffies)) { |
| smb347_set_InputCurrentlimit(client, 900); |
| charger->test_1800mA_fail = 1; |
| queue_delayed_work(smb347_wq, |
| &charger->test_fail_clear_work, 1*HZ); |
| } |
| |
| if (gpio_get_value(ac_ok)) { |
| printk(KERN_INFO "INOK=H\n"); |
| charger->cur_cable_type = non_cable; |
| smb347_set_InputCurrentlimit(client, 900); |
| success = battery_callback(non_cable); |
| #ifdef TOUCH_CALLBACK_ENABLED |
| touch_callback(non_cable); |
| #endif |
| wake_unlock(&charger_wakelock); |
| } else { |
| printk(KERN_INFO "INOK=L\n"); |
| retval = smb347_read(client, smb347_INTR_STS_E); |
| SMB_NOTICE("Reg39 : 0x%02x\n", retval); |
| if (!(retval & DCIN_OV_UV_STS) && !gpio_get_value(dock_in)) { |
| SMB_NOTICE("DC_IN\n"); |
| success = battery_callback(ac_cable); |
| } else { |
| |
| /* cable type dection */ |
| retval = smb347_read(client, smb347_STS_REG_E); |
| SMB_NOTICE("Reg3F : 0x%02x\n", retval); |
| if (retval & USBIN) { |
| SMB_NOTICE("USB_IN\n"); |
| retval = smb347_read(client, smb347_STS_REG_D); |
| SMB_NOTICE("Reg3E : 0x%02x\n", retval); |
| if (retval & APSD_OK) { |
| retval &= APSD_RESULT; |
| if (retval == APSD_CDP) { |
| printk(KERN_INFO "Cable: CDP\n"); |
| charger->cur_cable_type = ac_cable; |
| success = battery_callback(ac_cable); |
| #ifdef TOUCH_CALLBACK_ENABLED |
| touch_callback(ac_cable); |
| #endif |
| } else if (retval == APSD_DCP) { |
| printk(KERN_INFO "Cable: DCP\n"); |
| charger->cur_cable_type = ac_cable; |
| success = battery_callback(ac_cable); |
| #ifdef TOUCH_CALLBACK_ENABLED |
| touch_callback(ac_cable); |
| #endif |
| } else if (retval == APSD_OTHER) { |
| charger->cur_cable_type = ac_cable; |
| success = battery_callback(ac_cable); |
| #ifdef TOUCH_CALLBACK_ENABLED |
| touch_callback(ac_cable); |
| #endif |
| printk(KERN_INFO "Cable: OTHER\n"); |
| } else if (retval == APSD_SDP) { |
| printk(KERN_INFO "Cable: SDP\n"); |
| charger->cur_cable_type = usb_cable; |
| success = battery_callback(usb_cable); |
| #ifdef TOUCH_CALLBACK_ENABLED |
| touch_callback(usb_cable); |
| #endif |
| } else { |
| charger->cur_cable_type = unknow_cable; |
| printk(KERN_INFO "Unkown Plug In Cable type !\n"); |
| |
| if(usb_det_cable_type) { |
| printk(KERN_INFO "Use usb det %s cable to report\n", |
| (usb_det_cable_type == ac_cable) ? "ac" : "usb"); |
| charger->cur_cable_type = usb_det_cable_type; |
| success = battery_callback(usb_det_cable_type); |
| } |
| } |
| } else { |
| charger->cur_cable_type = unknow_cable; |
| printk(KERN_INFO "APSD not completed\n"); |
| } |
| } else { |
| charger->cur_cable_type = unknow_cable; |
| printk(KERN_INFO "USBIN=0\n"); |
| } |
| } |
| } |
| |
| if (charger->cur_cable_type == ac_cable && |
| charger->old_cable_type != ac_cable && |
| charger->test_1800mA_fail == 0) { |
| wake_lock(&charger_wakelock); |
| queue_delayed_work(smb347_wq, &charger->curr_limit_work, |
| DELAY_FOR_CURR_LIMIT_RECONF*HZ); |
| } |
| charger->old_cable_type = charger->cur_cable_type; |
| |
| mutex_unlock(&charger->cable_lock); |
| return success; |
| } |
| |
| void usb_det_cable_callback(unsigned cable_type) |
| { |
| usb_det_cable_type = cable_type; |
| SMB_NOTICE("usb_det_cable_type=%d\n", usb_det_cable_type); |
| |
| if(unknow_cable == charger->cur_cable_type) { |
| cable_type_detect(); |
| } |
| } |
| |
| static void inok_isr_work_function(struct work_struct *dat) |
| { |
| struct i2c_client *client = charger->client; |
| |
| cancel_delayed_work(&charger->curr_limit_work); |
| cancel_delayed_work(&charger->inok_isr_work); |
| |
| cable_type_detect(); |
| |
| smb347_clear_interrupts(client); |
| } |
| |
| static void dockin_isr_work_function(struct work_struct *dat) |
| { |
| struct i2c_client *client = charger->client; |
| |
| int dock_in = gpio_dock_in; |
| int ac_ok = GPIO_AC_OK; |
| |
| wake_lock(&charger->wake_lock_dockin); |
| mutex_lock(&charger->dockin_lock); |
| |
| if (gpio_get_value(dock_in)) { |
| if (!gpio_get_value(ac_ok)) { |
| SMB_NOTICE("dc_in=H & ac_ok=L\n"); |
| cable_type_detect(); |
| } |
| } else { |
| if (!gpio_get_value(ac_ok)) { |
| SMB_NOTICE("dc_in=L & ac_ok=L\n"); |
| msleep(40); |
| cable_type_detect(); |
| } |
| } |
| |
| mutex_unlock(&charger->dockin_lock); |
| wake_unlock(&charger->wake_lock_dockin); |
| } |
| |
| /* Sysfs function */ |
| static ssize_t smb347_reg_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct i2c_client *client = charger->client; |
| uint8_t config_reg[15], cmd_reg[1], status_reg[10]; |
| char tmp_buf[64]; |
| int i, cfg_ret, cmd_ret, sts_ret = 0; |
| |
| cfg_ret = i2c_smbus_read_i2c_block_data(client, smb347_CHARGE, 15, config_reg); |
| cmd_ret = i2c_smbus_read_i2c_block_data(client, smb347_CMD_REG, 2, cmd_reg); |
| sts_ret = i2c_smbus_read_i2c_block_data(client, smb347_INTR_STS_A, 11, status_reg); |
| |
| sprintf(tmp_buf, "SMB34x Configuration Registers Detail\n" |
| "==================\n"); |
| strcpy(buf, tmp_buf); |
| |
| if (cfg_ret > 0) { |
| for(i=0;i<=14;i++) { |
| sprintf(tmp_buf, "Reg%02xh:\t0x%02x\n", i, config_reg[i]); |
| strcat(buf, tmp_buf); |
| } |
| } |
| if (cmd_ret > 0) { |
| for(i=0;i<=1;i++) { |
| sprintf(tmp_buf, "Reg%02xh:\t0x%02x\n", 48+i, cmd_reg[i]); |
| strcat(buf, tmp_buf); |
| } |
| } |
| if (sts_ret > 0) { |
| for(i=0;i<=10;i++) { |
| sprintf(tmp_buf, "Reg%02xh:\t0x%02x\n", 53+i, status_reg[i]); |
| strcat(buf, tmp_buf); |
| } |
| } |
| return strlen(buf); |
| } |
| |
| static void smb347_default_setback(void) |
| { |
| struct i2c_client *client = charger->client; |
| int err; |
| |
| /* Enable volatile writes to registers */ |
| err = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (err < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", __func__); |
| } |
| err = smb347_update_reg(client, smb347_PIN_CTRL, PIN_CTRL); |
| if (err < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, err); |
| } |
| err = smb347_update_reg(client, smb347_CHRG_CTRL, ENABLE_APSD); |
| if (err < 0) { |
| dev_err(&client->dev, "%s: err %d\n", __func__, err); |
| } |
| /* Disable volatile writes to registers */ |
| err = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (err < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", __func__); |
| } |
| } |
| |
| static int smb347_temp_limit_setting(void) |
| { |
| struct i2c_client *client = charger->client; |
| int ret = 0, retval, val; |
| |
| /* Enable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| val = smb347_read(client, smb347_HRD_SFT_TEMP); |
| if (val < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading 0x%02x", |
| __func__, smb347_HRD_SFT_TEMP); |
| goto error; |
| } |
| val &= 0xcf; |
| /* Set Hard Limit Hot Temperature 59 Degree */ |
| ret = smb347_write(client, smb347_HRD_SFT_TEMP, val | 0x20); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register" |
| "0x%02x\n", __func__, val, smb347_HRD_SFT_TEMP); |
| goto error; |
| } |
| /* Disable volatile writes to registers */ |
| ret = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() error in configuring charger..\n", |
| __func__); |
| goto error; |
| } |
| return 0; |
| error: |
| return -1; |
| } |
| |
| int smb347_config_thermal_charging(int temp) |
| { |
| struct i2c_client *client = charger->client; |
| int ret = 0, retval, setting = 0; |
| |
| mdelay(150); |
| SMB_NOTICE("temp=%d\n", temp); |
| |
| ret = smb347_volatile_writes(client, smb347_ENABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() charger enable write error..\n", __func__); |
| goto error; |
| } |
| |
| /*charger enable/disable*/ |
| retval = smb347_read(client, smb347_PIN_CTRL); |
| if (retval < 0) { |
| dev_err(&client->dev, "%s(): Failed in reading 0x%02x", |
| __func__, smb347_PIN_CTRL); |
| goto error; |
| } |
| |
| setting = retval & ENABLE_PIN_CTRL_MASK; |
| if (temp > BAT_Hot_Limit) { |
| if (setting != 0x40) { |
| SMB_NOTICE("Charger disable\n"); |
| smb347_charger_enable(false); |
| } else |
| SMB_NOTICE("Bypass charger disable\n"); |
| } else { |
| if (setting != 0x60) { |
| SMB_NOTICE("Charger enable\n"); |
| smb347_charger_enable(true); |
| } else |
| SMB_NOTICE("Bypass charger enable\n"); |
| } |
| |
| ret = smb347_volatile_writes(client, smb347_DISABLE_WRITE); |
| if (ret < 0) { |
| dev_err(&client->dev, "%s() charger enable write error..\n", __func__); |
| goto error; |
| } |
| error: |
| return ret; |
| } |
| EXPORT_SYMBOL(smb347_config_thermal_charging); |
| |
| static int __devinit smb347_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); |
| int ret, irq_num; |
| uint8_t buf[15]; |
| |
| 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; |
| i2c_set_clientdata(client, charger); |
| |
| /* Restore default setting: APSD Enable & 5/1/HC mode Pin control */ |
| smb347_temp_limit_setting(); |
| smb347_default_setback(); |
| |
| ret = sysfs_create_group(&client->dev.kobj, &smb347_group); |
| if (ret) { |
| dev_err(&client->dev, "smb347_probe: unable to create the sysfs\n"); |
| } |
| |
| mutex_init(&charger->cable_lock); |
| mutex_init(&charger->dockin_lock); |
| mutex_init(&charger->pinctrl_lock); |
| |
| wake_lock_init(&charger->wake_lock_dockin, WAKE_LOCK_SUSPEND, "wake_lock_dockin"); |
| |
| smb347_wq = create_singlethread_workqueue("smb347_wq"); |
| INIT_DELAYED_WORK_DEFERRABLE(&charger->inok_isr_work, inok_isr_work_function); |
| INIT_DELAYED_WORK_DEFERRABLE(&charger->dockin_isr_work, dockin_isr_work_function); |
| INIT_DELAYED_WORK_DEFERRABLE(&charger->cable_det_work, cable_type_detect); |
| |
| wake_lock_init(&charger_wakelock, WAKE_LOCK_SUSPEND, |
| "charger_configuration"); |
| INIT_DELAYED_WORK(&charger->curr_limit_work, |
| smb347_set_curr_limit_work_func); |
| INIT_DELAYED_WORK(&charger->test_fail_clear_work, |
| smb347_test_fail_clear_work_function); |
| charger->curr_limit = UINT_MAX; |
| smb347_set_InputCurrentlimit(charger->client, 900); |
| charger->cur_cable_type = non_cable; |
| charger->old_cable_type = non_cable; |
| charger->test_1800mA_fail = 0; |
| |
| ret = smb347_dockin_irq(charger); |
| if (ret) { |
| dev_err(&client->dev, "%s(): Failed in requesting DOCK_IN# pin isr\n", |
| __func__); |
| goto error; |
| } |
| |
| ret = smb347_inok_irq(charger); |
| if (ret) { |
| dev_err(&client->dev, "%s(): Failed in requesting ACOK# pin isr\n", |
| __func__); |
| goto error; |
| } |
| |
| queue_delayed_work(smb347_wq, &charger->cable_det_work, 0.5*HZ); |
| |
| ret = register_otg_callback(smb347_otg_status, charger); |
| if (ret < 0) |
| goto error; |
| |
| return 0; |
| error: |
| kfree(charger); |
| return ret; |
| } |
| |
| static int __devexit smb347_remove(struct i2c_client *client) |
| { |
| struct smb347_charger *charger = i2c_get_clientdata(client); |
| |
| kfree(charger); |
| return 0; |
| } |
| |
| static int smb347_suspend(struct i2c_client *client) |
| { |
| charger->suspend_ongoing = 1; |
| |
| printk("smb347_suspend+\n"); |
| flush_workqueue(smb347_wq); |
| printk("smb347_suspend-\n"); |
| return 0; |
| } |
| |
| static int smb347_resume(struct i2c_client *client) |
| { |
| charger->suspend_ongoing = 0; |
| |
| printk("smb347_resume+\n"); |
| cable_type_detect(); |
| printk("smb347_resume-\n"); |
| return 0; |
| } |
| |
| |
| static int smb347_shutdown(struct i2c_client *client) |
| { |
| int ret; |
| printk("smb347_shutdown+\n"); |
| |
| /* Disable OTG */ |
| ret = smb347_configure_otg(client, 0); |
| if (ret < 0) |
| dev_err(&client->dev, "%s() error in configuring" |
| "otg..\n", __func__); |
| |
| /* configure charger */ |
| ret = smb347_configure_charger(client, 1); |
| if (ret < 0) |
| dev_err(&client->dev, "%s() error in configuring" |
| "otg..\n", __func__); |
| |
| printk("smb347_shutdown-\n"); |
| return 0; |
| } |
| |
| static const struct i2c_device_id smb347_id[] = { |
| { "smb347", 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, smb347_id); |
| |
| static struct i2c_driver smb347_i2c_driver = { |
| .driver = { |
| .name = "smb347", |
| }, |
| .probe = smb347_probe, |
| .remove = __devexit_p(smb347_remove), |
| .suspend = smb347_suspend, |
| .resume = smb347_resume, |
| .shutdown = smb347_shutdown, |
| .id_table = smb347_id, |
| }; |
| |
| static int __init smb347_init(void) |
| { |
| project_id = grouper_get_project_id(); |
| pcba_ver = grouper_query_pcba_revision(); |
| u32 project_info = grouper_get_project_id(); |
| |
| if (project_info == GROUPER_PROJECT_NAKASI_3G) |
| gpio_dock_in = TEGRA_GPIO_PO5; |
| else |
| gpio_dock_in = TEGRA_GPIO_PU4; |
| |
| SMB_NOTICE("project_id=%x, pcba_ver=%d, dock_in_gpio=%d\n", |
| project_id, pcba_ver, gpio_dock_in); |
| |
| return i2c_add_driver(&smb347_i2c_driver); |
| } |
| module_init(smb347_init); |
| |
| static void __exit smb347_exit(void) |
| { |
| i2c_del_driver(&smb347_i2c_driver); |
| } |
| module_exit(smb347_exit); |
| |
| MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>"); |
| MODULE_DESCRIPTION("smb347 Battery-Charger"); |
| MODULE_LICENSE("GPL"); |
| |