blob: 999a1c5e3783ceff0ab6ae509ae11394664f02dd [file] [log] [blame]
/*
* FAN540x Switching Charger with USB-OTG Boost Regualtor driver.
*
* Copyright (c) 2012 Marvell Technology Ltd.
* Yunfan Zhang <yfzhang@marvell.com>
*
* This package 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/module.h>
#include <linux/param.h>
#include <linux/jiffies.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/notifier.h>
#include <linux/gpio.h>
#include <linux/power/fan540x_charger.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <plat/usb.h>
/* Register Address */
#define FAN540x_CNTL0 0x00
#define FAN540x_CNTL1 0x01
#define FAN540x_OREG 0x02
#define FAN540x_IC_INFO 0x03
#define FAN540x_IBAT 0x04
#define FAN540x_SP_CHG 0x05 /* Only for fan5403-05 */
#define FAN540x_SAFETY 0x06 /* Only for fan5403-05 */
#define FAN540x_MONITOR 0x10
/* Register Bit Map */
/* CONTROL0: 0x00 */
#define CNTL0_TMR_RST (1 << 7)
#define CNTL0_OTG (1 << 7) /* Same bit as TMR_RST, RO */
#define CNTL0_EN_STAT (1 << 6)
#define CNTL0_STAT_MASK (0x3 << 4)
#define CNTL0_STAT_SHIFT 4
#define CNTL0_BOOST (1 << 3)
#define CNTL0_FAULT_MASK (0x7 << 0)
#define CNTL0_FAULT_SHIFT 0
/* CONTROL1: 0x01 */
#define CNTL1_INLIM_MASK (0x3 << 6)
#define CNTL1_INLIM_SHIFT 6
#define CNTL1_LOWV_MASK (0x3 << 4)
#define CNTL1_LOWV_SHIFT 4
#define CNTL1_TE (1 << 3)
#define CNTL1_CE_N (1 << 2)
#define CNTL1_HZ_MODE (1 << 1)
#define CNTL1_OPA_MODE (1 << 0)
/* OREG: 0x02 */
#define OREG_VOREG_MASK (0x3F << 2)
#define OREG_VOREG_SHIFT 2
#define OREG_OTG_PL (1 << 1)
#define OREG_OTG_EN (1 << 0)
/* IC_INFO: 0x03 */
#define INFO_VENDOR_MASK (0x7 << 5)
#define INFO_VENDOR_SHIFT 5
#define INFO_PN_MASK (0x3 << 3)
#define INFO_PN_SHIFT 3
#define INFO_REV_MASK (0x7 << 0)
#define INFO_REV_SHIFT 0
/* IBAT: 0x04 */
#define IBAT_RESET (1 << 7)
#define IBAT_IOCHG_MASK (0x7 << 4)
#define IBAT_IOCHG_SHIFT 4
#define IBAT_ITERM_MASK (0x7 << 0)
#define IBAT_ITERM_SHIFT 0
/* SP_CHG: 0x05 */
#define SPCHG_DIS_VERG (1 << 6)
#define SPCHG_IO_LEVEL (1 << 5)
#define SPCHG_SP (1 << 4)
#define SPCHG_EN_LEVEL (1 << 3)
#define SPCHG_VSP_MASK (0x7 << 0)
#define SPCHG_VSP_SHIFT 0
/* SAFETY: 0x06 */
#define SFT_ISAFE_MASK (0x7 << 4)
#define SFT_ISAFE_SHIFT 4
#define SFT_VSAFE_MASK (0xF << 0)
#define SFT_VSAFE_SHIFT 0
/* MONITOR: 0x10 */
#define MONT_ITERM_CMP (1 << 7)
#define MONT_VBAT_CMP (1 << 6)
#define MONT_LINCHG (1 << 5)
#define MONT_T_120 (1 << 4)
#define MONT_ICHG (1 << 3)
#define MONT_IBUS (1 << 2)
#define MONT_VBUS_VALID (1 << 1)
#define MONT_CV (1 << 0)
/* Default max monitor interval: should < 32s */
#define DEFAULT_MAX_MONITOR_INTERVAL 30
/* VOREG: Default cell's fully charged float voltage */
#define DEFAULT_VOREG 4100 /* 4.1V: (4100 - 3500)/20=0x1E */
/* Input Current Limit */
enum {
CUR_INLIM_100MA = 0,
CUR_INLIM_500MA,
CUR_INLIM_800MA,
CUR_INLIM_NO, /* No limit */
};
/* Charge current limit: Rsns = 68m Ohm */
enum {
CUR_CHG_550MA = 0,
CUR_CHG_650MA,
CUR_CHG_750MA,
CUR_CHG_850MA,
CUR_CHG_950MA,
CUR_CHG_1050MA,
CUR_CHG_1150MA,
CUR_CHG_1250MA,
};
enum {
DEV_ID_FAN5400 = 0,
DEV_ID_FAN5401,
DEV_ID_FAN5402,
DEV_ID_FAN5403,
DEV_ID_FAN5404,
DEV_ID_FAN5405,
};
struct fan540x_device_info {
struct device *dev;
struct i2c_client *client;
struct power_supply ac;
struct power_supply usb;
struct notifier_block chg_notif;
struct delayed_work chg_monitor_work;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *fan540x_dump;
#endif
int ac_chg_online;
int usb_chg_online;
int is_charging;
int voreg;
int input_cur;
int chg_cur;
int monitor_interval;
unsigned supply_type; /* power supply type */
int gpio_dis;
bool gpio_dis_active_low;
};
/* Global device info */
static struct fan540x_device_info *g_fan540x_di;
/* Return negative errno, else success */
static int fan540x_read_reg(struct i2c_client *i2c, u8 reg)
{
int ret = 0;
if (!i2c)
return -EINVAL;
ret = i2c_smbus_read_byte_data(i2c, reg);
if (ret < 0)
dev_err(&i2c->dev,
"i2c read fail: can't read from %02x: %d\n", reg, ret);
return ret;
}
/* Return negative errno, else zero on success */
static int fan540x_write_reg(struct i2c_client *i2c, u8 reg, u8 data)
{
int ret = 0;
if (!i2c)
return -EINVAL;
ret = i2c_smbus_write_byte_data(i2c, reg, data);
if (ret < 0) {
dev_err(&i2c->dev,
"i2c write fail: can't write %02x to %02x: %d\n",
data, reg, ret);
return ret;
}
return 0;
}
/* Return negative errno, else zero on success */
static int fan540x_set_bits(struct i2c_client *i2c, u8 reg,
u8 mask, u8 data)
{
int ret = 0;
ret = fan540x_read_reg(i2c, reg);
if (ret < 0)
return ret;
/* NOTE: Avoid to reset charge paramters by IBAT_RESET bit */
if (reg == FAN540x_IBAT)
ret &= ~IBAT_RESET;
ret &= ~mask;
ret |= data;
ret = fan540x_write_reg(i2c, reg, ret);
return ret;
}
/* Enable OTG Vbus output */
int fan540x_set_vbus(int on)
{
struct fan540x_device_info *di = g_fan540x_di;
struct i2c_client *i2c;
int ret;
if (!di)
return -EINVAL;
i2c = di->client;
/* Read CNTL0 to clear Fault */
fan540x_read_reg(i2c, FAN540x_CNTL0);
/* Clear HZ MODE */
ret = fan540x_set_bits(i2c, FAN540x_CNTL1, CNTL1_HZ_MODE, 0);
if (ret)
return ret;
/* Set boost mode if on, else charge mode */
return fan540x_set_bits(i2c, FAN540x_CNTL1, CNTL1_OPA_MODE,
on ? CNTL1_OPA_MODE : 0);
}
EXPORT_SYMBOL(fan540x_set_vbus);
static int fan540x_set_voreg(struct fan540x_device_info *di, int voreg)
{
return fan540x_set_bits(di->client, FAN540x_OREG,
OREG_VOREG_MASK, voreg << OREG_VOREG_SHIFT);
}
static int fan540x_set_input_cur(struct fan540x_device_info *di, int cur)
{
return fan540x_set_bits(di->client, FAN540x_CNTL1,
CNTL1_INLIM_MASK, cur << CNTL1_INLIM_SHIFT);
}
static int fan540x_set_chg_cur(struct fan540x_device_info *di, int cur)
{
return fan540x_set_bits(di->client, FAN540x_IBAT,
IBAT_IOCHG_MASK, cur << IBAT_IOCHG_SHIFT);
}
static int fan540x_get_voreg(struct fan540x_device_info *di)
{
int ret;
ret = fan540x_read_reg(di->client, FAN540x_OREG);
if (ret < 0)
return ret;
return (ret & OREG_VOREG_MASK) >> OREG_VOREG_SHIFT;
}
static int fan540x_get_input_cur(struct fan540x_device_info *di)
{
int ret;
ret = fan540x_read_reg(di->client, FAN540x_CNTL1);
if (ret < 0)
return ret;
return (ret & CNTL1_INLIM_MASK) >> CNTL1_INLIM_SHIFT;
}
static int fan540x_get_chg_cur(struct fan540x_device_info *di)
{
int ret;
ret = fan540x_read_reg(di->client, FAN540x_IBAT);
if (ret < 0)
return ret;
return (ret & IBAT_IOCHG_MASK) >> IBAT_IOCHG_SHIFT;
}
static int fan540x_reset_reg(struct fan540x_device_info *di)
{
int ret = 0;
ret = fan540x_read_reg(di->client, FAN540x_IBAT);
if (ret < 0)
return ret;
ret |= IBAT_RESET;
ret = fan540x_write_reg(di->client, FAN540x_IBAT, ret);
return ret;
}
#ifdef CONFIG_PROC_FS
static int fan540x_proc_read(char *buf, char **buffer_location,
off_t offset, int buffer_length,
int *zero, void *data)
{
struct fan540x_device_info *di = data;
int cnt = 0, ret = 0;
if (offset > 0)
return 0;
/* Regsiter maps */
ret = fan540x_read_reg(di->client, FAN540x_CNTL0);
cnt += sprintf(buf + cnt, "[0x00]CNTL0: 0x%x\n", ret);
ret = fan540x_read_reg(di->client, FAN540x_CNTL1);
cnt += sprintf(buf + cnt, "[0x01]CONTL1: 0x%x\n", ret);
ret = fan540x_read_reg(di->client, FAN540x_OREG);
cnt += sprintf(buf + cnt, "[0x02]OREG: 0x%x\n", ret);
ret = fan540x_read_reg(di->client, FAN540x_IC_INFO);
cnt += sprintf(buf + cnt, "[0x03]ICINFO: 0x%x\n", ret);
ret = fan540x_read_reg(di->client, FAN540x_IBAT);
cnt += sprintf(buf + cnt, "[0x04]IBAT: 0x%x\n", ret);
ret = fan540x_read_reg(di->client, FAN540x_SP_CHG);
cnt += sprintf(buf + cnt, "[0x05]SP_CHG: 0x%x\n", ret);
ret = fan540x_read_reg(di->client, FAN540x_SAFETY);
cnt += sprintf(buf + cnt, "[0x06]SAFETY: 0x%x\n", ret);
ret = fan540x_read_reg(di->client, FAN540x_MONITOR);
cnt += sprintf(buf + cnt, "[0x10]MONITOR: 0x%x\n", ret);
/* Charge parameters */
ret = fan540x_get_voreg(di);
cnt += sprintf(buf + cnt, "VOREG = 0x%x\n", ret);
ret = fan540x_get_input_cur(di);
cnt += sprintf(buf + cnt, "INLIM = 0x%x\n", ret);
ret = fan540x_get_chg_cur(di);
cnt += sprintf(buf + cnt, "IOCHG = 0x%x\n", ret);
return cnt;
}
#endif /* CONFIG_PROC_FS */
static int fan540x_charger_enable(struct fan540x_device_info *di)
{
struct i2c_client *i2c = di->client;
/* Read CNTL0 to clear Fault */
fan540x_read_reg(i2c, FAN540x_CNTL0);
/* Clear Boost Mode */
fan540x_set_bits(i2c, FAN540x_CNTL1, CNTL1_OPA_MODE, 0);
/* Clear HZ Mode */
fan540x_set_bits(i2c, FAN540x_CNTL1, CNTL1_HZ_MODE, 0);
/* Set VOREG */
fan540x_set_voreg(di, di->voreg);
/* Clear IO_LEVEL, charge current controlled by IOCHARGE */
fan540x_set_bits(i2c, FAN540x_SP_CHG, SPCHG_IO_LEVEL, 0);
/* Set input current limit */
fan540x_set_input_cur(di, di->input_cur);
/* Set charge current limit */
fan540x_set_chg_cur(di, di->chg_cur);
/* Enable Charging */
if (gpio_is_valid(di->gpio_dis)) {
/* Deassert disable pin */
gpio_direction_output(di->gpio_dis,
di->gpio_dis_active_low ? 1 : 0);
}
/* Assert CE_N: enable charger */
fan540x_set_bits(i2c, FAN540x_CNTL1, CNTL1_CE_N, 0);
pr_info("FAN540x charger: Enabled!\n");
return 0;
}
static int fan540x_charger_disable(struct fan540x_device_info *di)
{
struct i2c_client *i2c = di->client;
/* Disable Charging */
if (gpio_is_valid(di->gpio_dis)) {
/* Assert disable pin */
gpio_direction_output(di->gpio_dis,
di->gpio_dis_active_low ? 0 : 1);
}
/* Deassert CE_N: disable charger */
fan540x_set_bits(i2c, FAN540x_CNTL1, CNTL1_CE_N, CNTL1_CE_N);
pr_info("FAN540x charger: Disabled!\n");
return 0;
}
static int fan540x_chg_notifier_callback(struct notifier_block *nb,
unsigned long event, void *data)
{
struct fan540x_device_info *di =
container_of(nb, struct fan540x_device_info, chg_notif);
/* Parse vbus event passed by usb driver */
switch (event) {
case DEFAULT_CHARGER:
case VBUS_CHARGER:
/* Standard Downstream Port */
di->supply_type = POWER_SUPPLY_TYPE_USB;
di->input_cur = CUR_INLIM_NO;
di->chg_cur = CUR_CHG_1250MA;
/* Charger states */
di->usb_chg_online = 1;
di->ac_chg_online = 0;
break;
case AC_CHARGER_STANDARD:
/* Dedicated Charging Port */
di->supply_type = POWER_SUPPLY_TYPE_USB_DCP;
di->input_cur = CUR_INLIM_NO;
di->chg_cur = CUR_CHG_1250MA;
/* Charger states */
di->usb_chg_online = 0;
di->ac_chg_online = 1;
break;
case AC_CHARGER_OTHER:
/* Adapter Charger */
di->supply_type = POWER_SUPPLY_TYPE_MAINS;
di->input_cur = CUR_INLIM_NO;
di->chg_cur = CUR_CHG_1250MA;
/* Charger states */
di->usb_chg_online = 0;
di->ac_chg_online = 1;
break;
case NULL_CHARGER:
default:
/* No charger */
di->supply_type = POWER_SUPPLY_TYPE_BATTERY;
/* Charger states */
di->usb_chg_online = 0;
di->ac_chg_online = 0;
break;
}
if (di->supply_type == POWER_SUPPLY_TYPE_BATTERY) {
/* Stop charging */
fan540x_charger_disable(di);
} else {
/* Start charing */
fan540x_charger_enable(di);
}
power_supply_changed(&di->usb);
power_supply_changed(&di->ac);
return NOTIFY_OK;
}
static void chg_monitor_work_func(struct work_struct *work)
{
struct fan540x_device_info *di =
container_of(work, struct fan540x_device_info,
chg_monitor_work.work);
/* Reset 32s timer */
fan540x_set_bits(di->client, FAN540x_CNTL0,
CNTL0_TMR_RST, CNTL0_TMR_RST);
schedule_delayed_work(&di->chg_monitor_work, HZ * di->monitor_interval);
}
/* MUST and ONLY before any other register is written */
static int fan540x_safety_reg_init(struct fan540x_device_info *di)
{
int ret;
u8 data = 0x70; /* ISAFE:1.25A, VSAFE:4.2V */
ret = fan540x_read_reg(di->client, FAN540x_SAFETY);
if (ret)
return ret;
/* If it is, return */
if (ret == data)
return 0;
/* Set VSAFE and ISAFE */
ret = fan540x_write_reg(di->client, FAN540x_SAFETY, data);
if (ret)
return ret;
/* Verify */
ret = fan540x_read_reg(di->client, FAN540x_SAFETY);
if (ret != data) {
pr_err("%s: failed to init SAFETY register\n", __func__);
return -1;
}
return 0;
}
static int fan540x_charger_setup(struct fan540x_device_info *di,
struct fan540x_charger_pdata *pdata)
{
struct i2c_client *i2c = di->client;
int ret;
/* Get chip information */
ret = fan540x_read_reg(i2c, FAN540x_IC_INFO);
if (ret < 0)
return ret;
dev_info(di->dev,
"Vendor Code: 0x%x;Part Number: %d;Revision: 1.%d\n",
(ret & INFO_VENDOR_MASK) >> INFO_VENDOR_SHIFT,
(ret & INFO_PN_MASK) >> INFO_PN_SHIFT,
(ret & INFO_REV_MASK) >> INFO_REV_SHIFT);
/* Update ISAFE and VSAFE */
fan540x_safety_reg_init(di);
/* Reset charge paramters firstly */
fan540x_reset_reg(di);
/* Set Vlowv as 3.4V */
fan540x_set_bits(di->client, FAN540x_CNTL1,
CNTL1_LOWV_MASK, 0x0 << CNTL1_LOWV_SHIFT);
/* Init monitor interval: seconds */
if ((pdata->monitor_interval <= 0) || (pdata->monitor_interval > 30)) {
dev_err(di->dev, "monitor interval is out of range,"
"using default value: %ds\n",
DEFAULT_MAX_MONITOR_INTERVAL);
di->monitor_interval = DEFAULT_MAX_MONITOR_INTERVAL;
} else
di->monitor_interval = pdata->monitor_interval;
/* Init VOREG */
if (pdata->voreg >= 3500)
di->voreg = (pdata->voreg - 3500) / 20;
else
di->voreg = (DEFAULT_VOREG - 3500) / 20;
/* Init disable gpio */
di->gpio_dis = pdata->gpio_dis;
di->gpio_dis_active_low = pdata->gpio_dis_active_low;
if (gpio_is_valid(di->gpio_dis)) {
if (di->gpio_dis == 0)
dev_warn(di->dev, "Warning: Using GPIO0 for disable pin!\n");
ret = gpio_request(di->gpio_dis, "Charge Disable");
if (ret) {
dev_err(di->dev,
"failed to request GPIO%d\n", di->gpio_dis);
return ret;
}
/* Disbale by default */
gpio_direction_output(di->gpio_dis,
di->gpio_dis_active_low ? 0 : 1);
}
return 0;
}
static enum power_supply_property fan540x_ac_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static int fan540x_ac_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct fan540x_device_info *di =
container_of(psy, struct fan540x_device_info, ac);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = di->ac_chg_online;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property fan540x_usb_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static int fan540x_usb_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct fan540x_device_info *di =
container_of(psy, struct fan540x_device_info, usb);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = di->usb_chg_online;
break;
default:
return -EINVAL;
}
return 0;
}
static char *fan540x_supplied_to[] = {
"fan4010-battery",
};
static int fan540x_powersupply_init(struct fan540x_device_info *di,
struct fan540x_charger_pdata *pdata)
{
int ret = 0;
if (pdata->supplied_to) {
di->ac.supplied_to = pdata->supplied_to;
di->ac.num_supplicants = pdata->num_supplicants;
di->usb.supplied_to = pdata->supplied_to;
di->usb.num_supplicants = pdata->num_supplicants;
} else {
di->ac.supplied_to = fan540x_supplied_to;
di->ac.num_supplicants = ARRAY_SIZE(fan540x_supplied_to);
di->usb.supplied_to = fan540x_supplied_to;
di->usb.num_supplicants = ARRAY_SIZE(fan540x_supplied_to);
}
/* register ac charger props */
di->ac.name = "fan540x-chg-ac";
di->ac.type = POWER_SUPPLY_TYPE_MAINS;
di->ac.properties = fan540x_ac_props;
di->ac.num_properties = ARRAY_SIZE(fan540x_ac_props);
di->ac.get_property = fan540x_ac_get_property;
ret = power_supply_register(di->dev, &di->ac);
if (ret)
goto err_reg_ac;
/* register usb charger props */
di->usb.name = "fan540x-chg-usb";
di->usb.type = POWER_SUPPLY_TYPE_USB;
di->usb.properties = fan540x_usb_props;
di->usb.num_properties = ARRAY_SIZE(fan540x_usb_props);
di->usb.get_property = fan540x_usb_get_property;
ret = power_supply_register(di->dev, &di->usb);
if (ret)
goto err_reg_usb;
return ret;
err_reg_usb:
power_supply_unregister(&di->ac);
err_reg_ac:
return ret;
}
static int fan540x_charger_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct fan540x_device_info *di;
struct fan540x_charger_pdata *pdata;
int ret = 0;
pdata = client->dev.platform_data;
if (!pdata) {
dev_err(&client->dev, "missing platform data\n");
return -EINVAL;
}
di = kzalloc(sizeof(*di), GFP_KERNEL);
if (!di) {
dev_err(&client->dev, "failed to allocate device info data\n");
return -ENOMEM;
}
di->client = client;
di->dev = &client->dev;
i2c_set_clientdata(client, di);
ret = fan540x_charger_setup(di, pdata);
if (ret) {
dev_err(&client->dev, "failed to setup charger\n");
goto err_chg_setup;
}
/* Register power supply device for ac and usb */
ret = fan540x_powersupply_init(di, pdata);
if (ret) {
dev_err(&client->dev, "failed to register ac/usb\n");
goto err_power_supply_reg;
}
/* Init global device info */
g_fan540x_di = di;
/* Init delayed work queue */
INIT_DELAYED_WORK(&di->chg_monitor_work, chg_monitor_work_func);
/* Register charger event notifier */
di->chg_notif.notifier_call = fan540x_chg_notifier_callback;
#ifdef CONFIG_USB_PXA_U2O
mv_udc_register_client(&di->chg_notif);
#endif
schedule_delayed_work(&di->chg_monitor_work, HZ / 100);
#ifdef CONFIG_PROC_FS
di->fan540x_dump = create_proc_entry("fan540x_charger", 0666, NULL);
if (di->fan540x_dump) {
di->fan540x_dump->read_proc = fan540x_proc_read;
di->fan540x_dump->data = di;
} else
dev_err(&client->dev, "failed to create proc entry\n");
#endif
dev_info(&client->dev, "charger is enabled\n");
return 0;
err_power_supply_reg:
if (gpio_is_valid(di->gpio_dis))
gpio_free(di->gpio_dis);
err_chg_setup:
kfree(di);
return ret;
}
static int fan540x_charger_remove(struct i2c_client *client)
{
struct fan540x_device_info *di = i2c_get_clientdata(client);
if (di->fan540x_dump)
remove_proc_entry("fan540x_charger", NULL);
if (gpio_is_valid(di->gpio_dis))
gpio_free(di->gpio_dis);
cancel_delayed_work(&di->chg_monitor_work);
power_supply_unregister(&di->usb);
power_supply_unregister(&di->ac);
kfree(di);
return 0;
}
#ifdef CONFIG_PM
static int fan540x_charger_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct fan540x_device_info *di = i2c_get_clientdata(client);
cancel_delayed_work_sync(&di->chg_monitor_work);
return 0;
}
static int fan540x_charger_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct fan540x_device_info *di = i2c_get_clientdata(client);
schedule_delayed_work(&di->chg_monitor_work, HZ / 100);
return 0;
}
static const struct dev_pm_ops fan540x_pm_ops = {
.suspend = fan540x_charger_suspend,
.resume = fan540x_charger_resume,
};
#endif
static void fan540x_charger_shutdown(struct i2c_client *client)
{
struct fan540x_device_info *di = i2c_get_clientdata(client);
/* Disable Charger */
fan540x_charger_disable(di);
}
static const struct i2c_device_id fan540x_id[] = {
{"fan5400", DEV_ID_FAN5400},
{"fan5401", DEV_ID_FAN5401},
{"fan5402", DEV_ID_FAN5402},
{"fan5403", DEV_ID_FAN5403},
{"fan5404", DEV_ID_FAN5404},
{"fan5405", DEV_ID_FAN5405},
{},
};
MODULE_DEVICE_TABLE(i2c, fan540x_id);
static struct i2c_driver fan540x_charger_driver = {
.driver = {
.name = "fan540x-charger",
#ifdef CONFIG_PM
.pm = &fan540x_pm_ops,
#endif
},
.probe = fan540x_charger_probe,
.remove = __devexit_p(fan540x_charger_remove),
.shutdown = fan540x_charger_shutdown,
.id_table = fan540x_id,
};
static int __init fan540x_charger_init(void)
{
int ret;
ret = i2c_add_driver(&fan540x_charger_driver);
if (ret)
pr_err("Unable to register fan540x charger driver!\n");
return ret;
}
module_init(fan540x_charger_init);
static void __exit fan540x_charger_exit(void)
{
i2c_del_driver(&fan540x_charger_driver);
}
module_exit(fan540x_charger_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("FAN540X Charger Driver");
MODULE_AUTHOR("Yunfan Zhang <yfzhang@marvell.com>");
MODULE_ALIAS("fan540x-charger");