diff options
Diffstat (limited to 'drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c')
-rwxr-xr-x | drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c | 782 |
1 files changed, 782 insertions, 0 deletions
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c new file mode 100755 index 00000000..433ff15c --- /dev/null +++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c @@ -0,0 +1,782 @@ +/* + * Copyright (c) 2012, uPI Semiconductor Corp. All Rights Reserved. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/power_supply.h> +#include <linux/idr.h> +#include <linux/reboot.h> +#include <linux/notifier.h> +#include <linux/jiffies.h> +#include <linux/err.h> +#include <asm/unaligned.h> +#include <linux/wakelock.h> +#include <linux/version.h> + +#include <linux/power/wmt_battery.h> +#include <mach/wmt_env.h> + +#include "ug31xx_gauge.h" +#include "uG31xx_API.h" + +/* Functions Declaration */ +static int ug31xx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); +static int ug31xx_update_psp(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); +static void ug31xx_external_power_changed(struct power_supply *psy); + +#define UG31XX_CHECK_FILE_CNT_THRD (20) + +/* Global Variables */ +static struct ug31xx_gauge *ug31 = NULL; +static char chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD; +static char *pGGB = NULL; +static GG_DEVICE_INFO gauge_dev_info; +static GG_CAPACITY gauge_dev_capacity; +static struct workqueue_struct *ug31xx_gauge_wq = NULL; +static int charger_dc_in_before_suspend = 0; +static bool ug3105_in_suspend = false; +static bool ug3105_charger_enabled = true; +static unsigned char force_reset = 0; +static unsigned char enable_debug = 0; + +static __attribute__((unused)) char *chg_status[] = { + "Unknown", + "Charging", + "Discharging", + "Not charging", + "Full" +}; + +static drv_status_t drv_status = DRV_NOT_READY; + +#include "ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h" +#include "ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h" +#include "ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h" +#include "ggb/ug31xx_ggb_data_wms7320_20130718_200031.h" +#include "ggb/ug31xx_ggb_data_cw500_20130801_103638.h" +#include "ggb/ug31xx_ggb_data_mp718_20131004_070110.h" +#include "ggb/ug31xx_ggb_data_t73v_20131120_001204.h" + +enum { + UG31XX_ID_3105, + UG31XX_ID_3102, +}; + +struct ggb_info { + char *name; + uint8_t *data; +}; + +/* Extern Function */ +static struct ggb_info ggb_arrays[] = { + { + .name = "wms8309wm8", + .data = FactoryGGBXFile_wms8309_wm8, + }, { + .name = "wms7320", + .data = FactoryGGBXFile_wms7320, + }, { + .name = "wms8309c7_3900mAh", + .data = FactoryGGBXFile_wms8309_c7_3900mAh, + }, { + .name = "wms8309c7_3000mAh", + .data = FactoryGGBXFile_wms8309_c7_3000mAh, + }, { + .name = "cw500", + .data = FactoryGGBXFile_cw500, + }, { + .name = "mp718", + .data = FactoryGGBXFile_mp718, + }, { + .name = "t73v", + .data = FactoryGGBXFile_t73v, + }, +}; + +struct charger_param { + int id; + int i2c; + int et; + int temp_range[2]; + struct ggb_info *ggb; +}; + +static struct charger_param charger_param; + +static bool inline export_external_termperature(void) +{ + return !!charger_param.et; +} + +static int parse_battery_param(void) +{ + char env[] = "wmt.battery.param"; + char buf[64]; + size_t l = sizeof(buf); + int i; + + if (wmt_getsyspara(env, buf, &l)) + return -EINVAL; + + if (!prefixcmp(buf, "ug3105:")) { + charger_param.id = UG31XX_ID_3105; + } else if (!prefixcmp(buf, "ug3102:")) { + charger_param.id = UG31XX_ID_3102; + } else { + return -EINVAL; + } + + i = sscanf(buf + 7, "%d:%d:%d:%d", + &charger_param.i2c, &charger_param.et, + &charger_param.temp_range[0], + &charger_param.temp_range[1]); + if (i < 4) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(ggb_arrays); i++) { + if (strstr(buf, ggb_arrays[i].name)) { + charger_param.ggb = &ggb_arrays[i]; + break; + } + } + if (i == ARRAY_SIZE(ggb_arrays)) + charger_param.ggb = &ggb_arrays[0]; + + pr_info("%s i2c%d, use %s temperature, range [%d, %d], ggb %s\n", + charger_param.id == UG31XX_ID_3105 ? "ug3105" : "ug3102", + charger_param.i2c, + charger_param.et ? "External" : "Internal", + charger_param.temp_range[0], + charger_param.temp_range[1], + charger_param.ggb->name); + + return 0; +} + +/* + * WMT MCE: Use gpio3 on ug31xx as a switch to control charger. + */ +static void hw_charging_set(bool enable) +{ + if (charger_param.id == UG31XX_ID_3105) { + API_I2C_SingleWrite(0, 0, 0, 0x16, enable ? 0x2 : 0x0); + } else if (charger_param.id == UG31XX_ID_3102) { + u8 data = 0; + API_I2C_SingleRead(0, 0, 0 ,REG_CTRL1, &data); + if (enable) + data |= CTRL1_IO1DATA; + else + data &= ~CTRL1_IO1DATA; + API_I2C_SingleWrite(0, 0, 0, REG_CTRL1, data); + } + ug3105_charger_enabled = enable; +} + +static void inline hw_charging_disable(void) +{ + hw_charging_set(false); +} + +static void inline hw_charging_enable(void) +{ + hw_charging_set(true); +} + +static bool is_charging_full(void) +{ + return (ug3105_charger_enabled == true) && charger_is_full(); +} + +static enum power_supply_property ug31xx_batt_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, +}; + +static struct power_supply ug31xx_supply[] = { + { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = ug31xx_batt_props, + .num_properties = ARRAY_SIZE(ug31xx_batt_props), + .get_property = ug31xx_battery_get_property, + .external_power_changed = ug31xx_external_power_changed, + }, +}; + +static int ug31xx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (ug31xx_update_psp(psy, psp, val)) + return -EINVAL; + break; + default: + return -EINVAL; + } + return ret; +} + +static int ug31xx_update_psp(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ +// int rtn; + + if (drv_status != DRV_INIT_OK) { + GAUGE_err("Gauge driver not init finish\n"); + return -EINVAL; + } + + if (psp == POWER_SUPPLY_PROP_TEMP) { + if (export_external_termperature()) + ug31->batt_temp = gauge_dev_info.ET; + else + ug31->batt_temp = gauge_dev_info.IT; + val->intval = ug31->batt_temp; + GAUGE_notice("I.Temperature=%d, E.T=%d\n", gauge_dev_info.IT, gauge_dev_info.ET); + } + + if (psp == POWER_SUPPLY_PROP_CAPACITY) { + if (ug31->batt_status == POWER_SUPPLY_STATUS_FULL) + ug31->batt_capacity = 100; + else + ug31->batt_capacity = gauge_dev_capacity.RSOC; + + val->intval = ug31->batt_capacity; + GAUGE_notice("Capacity=%d %%\n", val->intval); + } + + if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) { + val->intval = ug31->batt_volt = gauge_dev_info.voltage_mV; + GAUGE_notice("Voltage=%d mV\n", val->intval); + } + + if (psp == POWER_SUPPLY_PROP_CURRENT_NOW) { + val->intval = ug31->batt_current = gauge_dev_info.AveCurrent_mA; + GAUGE_notice("Current=%d mA\n", val->intval); + } + + if (psp == POWER_SUPPLY_PROP_STATUS) { + int status = charger_get_status(); + if (status < 0) + return status; + + if (status == POWER_SUPPLY_STATUS_CHARGING && ug31->batt_capacity == 100) + status = POWER_SUPPLY_STATUS_FULL; + + ug31->batt_status = status; + val->intval = ug31->batt_status; + GAUGE_notice("Status=%s\n", chg_status[val->intval]); + } + + if (psp == POWER_SUPPLY_PROP_CHARGE_NOW) { + val->intval = ug31->batt_charge_now = gauge_dev_capacity.NAC; + GAUGE_notice("Charge_Now=%d mAh\n", val->intval); + } + + if (psp == POWER_SUPPLY_PROP_CHARGE_FULL) { + val->intval =ug31->batt_charge_full = gauge_dev_capacity.LMD; + GAUGE_notice("Charge_Full=%d mAh\n", val->intval); + } + +#if 0 + mutex_lock(&ug31->info_update_lock); + chk_backup_file_cnt = chk_backup_file_cnt + 1; + UG31_LOGI("chk_backup_file_cnt %d\n", chk_backup_file_cnt); + if (chk_backup_file_cnt > UG31XX_CHECK_FILE_CNT_THRD) { + rtn = upiGG_CheckBackupFile(pGGB); + if(rtn == UPI_CHECK_BACKUP_FILE_MISMATCH) + { + force_reset = 1; + UG31_LOGI("[%s]: Force reset due to version dismatched.\n", __func__); + } + else if(rtn == UPI_CHECK_BACKUP_FILE_EXIST) + { + chk_backup_file_cnt = 0; + UG31_LOGI("[%s]: Backup file existed -> no need to check frequently.\n", __func__) + } + else + { + chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD; + UG31_LOGI("[%s]: Backup file not existed -> need to check frequently.\n", __func__) + } + UG31_LOGI("[%s]: Check backup file status = %d\n", __func__, rtn); + } + mutex_unlock(&ug31->info_update_lock); +#endif + return 0; +} + +static int ug31xx_powersupply_init(struct i2c_client *client) +{ + int i, ret; + for (i = 0; i < ARRAY_SIZE(ug31xx_supply); i++) { + ret = power_supply_register(&client->dev, &ug31xx_supply[i]); + if (ret) { + GAUGE_err("Failed to register power supply\n"); + while (i--) + power_supply_unregister(&ug31xx_supply[i]); + return ret; + } + } + return 0; +} + +static void ug31xx_external_power_changed(struct power_supply *psy) +{ + if (ug3105_in_suspend == false) + queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ); +} + +#define UG31XX_INITIAL_RETRY_CNT (3) +#define UG31XX_INITIAL_LOCK_ENABLE (1) +#define UG31XX_INITIAL_LOCK_DISABLE (0) +#define UG31XX_INITIAL_FIX_T_COUNT (30) + +static int ug31_gauge_info_reset(int enable_lock) +{ + int retry; + int gg_status; + + force_reset = 0; + + if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE) + { + mutex_lock(&ug31->info_update_lock); + } + + retry = 0; + while(retry < UG31XX_INITIAL_RETRY_CNT) + { + gg_status = UG_SUCCESS; + if(pGGB != NULL) + { + gg_status = upiGG_UnInitial(&pGGB); + pGGB = NULL; + GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status); + } + + if(gg_status == UG_SUCCESS) + { + gg_status = upiGG_Initial(&pGGB, + (GGBX_FILE_HEADER *)charger_param.ggb->data, + force_reset, UG31XX_INITIAL_FIX_T_COUNT); + + if(gg_status == UG_INIT_SUCCESS) + { + chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD; + if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE) + { + mutex_unlock(&ug31->info_update_lock); + } + return (0); + } + } + + retry = retry + 1; + GAUGE_err("GGB file read and init fail count = %d (%d)\n", retry, gg_status); + } + + GAUGE_err("Reset uG31xx fail.\n"); + if(pGGB != NULL) + { + gg_status = upiGG_UnInitial(&pGGB); + pGGB = NULL; + GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status); + } + + if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE) + { + mutex_unlock(&ug31->info_update_lock); + } + return (-1); +} + +static void ug31_gauge_info_work_func(struct work_struct *work) +{ + int gg_status = 0, retry = 3; + struct power_supply *psy = &ug31xx_supply[PWR_SUPPLY_BATTERY]; + int batt_status = ug31->batt_status; + struct ug31xx_gauge *ug31_dev; + + ug31_dev = container_of(work, struct ug31xx_gauge, ug31_gauge_info_work.work); + + if(enable_debug == 0) + { + upiGG_DebugSwitch(_UPI_FALSE_); + UG31_LOGI("Set upiGG_DebugSwitch to FALSE\n"); + } + else + { + upiGG_DebugSwitch(_UPI_TRUE_); + UG31_LOGI("Set upiGG_DebugSwitch to TRUE\n"); + } + + UG31_LOGI("Update gauge info!!\n"); + + mutex_lock(&ug31->info_update_lock); + while (retry-- > 0) { + gg_status = upiGG_ReadDeviceInfo(pGGB,&gauge_dev_info); + if (gg_status == UG_READ_DEVICE_INFO_SUCCESS) + goto read_dev_info_ok; + } + GAUGE_err("Read device info fail. gg_status=%d\n", gg_status); + if(gg_status == UG_MEAS_FAIL_BATTERY_REMOVED) + { + gauge_dev_capacity.NAC = 0; + gauge_dev_capacity.LMD = 0; + gauge_dev_capacity.RSOC = 0; + } + goto read_dev_info_fail; + +read_dev_info_ok: + if(gauge_dev_capacity.Ready != UG_CAP_DATA_READY) + { + if(gauge_dev_info.ET > UG31XX_MAX_TEMPERATURE_BEFORE_READY) + { + gauge_dev_info.ET = UG31XX_MAX_TEMPERATURE_BEFORE_READY; + } + if(gauge_dev_info.ET < UG31XX_MIN_TEMPERATURE_BEFORE_READY) + { + gauge_dev_info.ET = UG31XX_MIN_TEMPERATURE_BEFORE_READY; + } + } + + upiGG_ReadCapacity(pGGB,&gauge_dev_capacity); + UG31_LOGI("Gauge info updated !!\n"); + +read_dev_info_fail: + mutex_unlock(&ug31->info_update_lock); + + if(force_reset == 1) + { + UG31_LOGI("Reset whole uG31xx driver.\n"); + gg_status = ug31_gauge_info_reset(UG31XX_INITIAL_LOCK_ENABLE); + if(gg_status != 0) + { + goto set_polling_time; + } + gauge_dev_capacity.Ready = UG_CAP_DATA_READY; + } + + /* Disable charging by temperture */ + + if (charger_param.id == UG31XX_ID_3105) { + if (export_external_termperature()) { + if (gauge_dev_info.ET < charger_param.temp_range[0] || + gauge_dev_info.ET > charger_param.temp_range[1]) + hw_charging_disable(); + else + hw_charging_enable(); + } + } else if (charger_param.id == UG31XX_ID_3102) { + hw_charging_enable(); + } + + if (wmt_charger_is_dc_plugin() && power_supply_am_i_supplied(psy)) { + if (gauge_dev_capacity.RSOC == 100) + batt_status = POWER_SUPPLY_STATUS_FULL; + else if (is_charging_full() && ug31->batt_capacity >= 90) { + batt_status = POWER_SUPPLY_STATUS_FULL; + gauge_dev_capacity.RSOC = 100; + mutex_lock(&ug31->info_update_lock); + upiGG_ForceTaper(pGGB, 1, 1, 1); + mutex_unlock(&ug31->info_update_lock); + } else + batt_status = POWER_SUPPLY_STATUS_CHARGING; + } else + batt_status = POWER_SUPPLY_STATUS_DISCHARGING; + + /* Report uevent while capacity changed */ + if (ug31->batt_capacity != gauge_dev_capacity.RSOC || + ug31->batt_status != batt_status) { + GAUGE_notice("Capacity changed: %d -> %d\n", ug31->batt_capacity, gauge_dev_capacity.RSOC); + GAUGE_notice("Status changed: %d -> %d\n", ug31->batt_status, batt_status); + power_supply_changed(psy); + } + +set_polling_time: + if (gauge_dev_capacity.Ready == UG_CAP_DATA_READY) { + queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 5*HZ); + } else if(force_reset == 1) { + queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 0*HZ); + } else { + queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 1*HZ); + } +} + +static int __devinit ug31xx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int gg_status; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + ug31 = kzalloc(sizeof(*ug31), GFP_KERNEL); + if (!ug31) + return -ENOMEM; + + ug31->client = client; + ug31->dev = &client->dev; + + i2c_set_clientdata(client, ug31); + ug31xx_i2c_client_set(ug31->client); + + gauge_dev_capacity.RSOC = 50; + + /* get GGB file */ + GAUGE_notice("[UPI]: Force to reset uG3105 driver (%d)\n", force_reset); + gg_status = ug31_gauge_info_reset(UG31XX_INITIAL_LOCK_DISABLE); + if(gg_status != 0) + { + goto ggb_init_fail; + } + + mutex_init(&ug31->info_update_lock); + wake_lock_init(&ug31->cable_wake_lock, WAKE_LOCK_SUSPEND, "cable_state_changed"); + + ug31xx_gauge_wq = create_singlethread_workqueue("ug31xx_gauge_work_queue"); + INIT_DELAYED_WORK(&ug31->ug31_gauge_info_work, ug31_gauge_info_work_func); + queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ); + + /* power supply registration */ + if (ug31xx_powersupply_init(client)) + goto pwr_supply_fail; + + force_reset = 0; + drv_status = DRV_INIT_OK; + + GAUGE_notice(" Driver %s registered done\n", client->name); + + return 0; + +pwr_supply_fail: + kfree(ug31); +ggb_init_fail: + if(!pGGB) { + upiGG_UnInitial(&pGGB); + } + force_reset = 0; + return gg_status; +} + +static int __devexit ug31xx_i2c_remove(struct i2c_client *client) +{ + struct ug31xx_gauge *ug31_dev; + int i = 0, gg_status; + + for (i = 0; i < ARRAY_SIZE(ug31xx_supply); i++) { + power_supply_unregister(&ug31xx_supply[i]); + } + + cancel_delayed_work_sync(&ug31->ug31_gauge_info_work); + destroy_workqueue(ug31xx_gauge_wq); + wake_lock_destroy(&ug31->cable_wake_lock); + + gg_status = upiGG_UnInitial(&pGGB); + GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status); + + ug31_dev = i2c_get_clientdata(client); + if (ug31_dev) { + kfree(ug31_dev); + } + return 0; +} + +static int ug31xx_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + int gg_status; + + GAUGE_notice("ug31xx_i2c_suspend() start\n"); + ug3105_in_suspend = true; + + cancel_delayed_work_sync(&ug31->ug31_gauge_info_work); + flush_workqueue(ug31xx_gauge_wq); + + mutex_lock(&ug31->info_update_lock); + gg_status = upiGG_PreSuspend(pGGB); + if (gg_status != UG_READ_DEVICE_INFO_SUCCESS) { + GAUGE_err("Fail in suspend. gg_status=0x%02x\n", gg_status); + } else + GAUGE_notice("Driver suspend. gg_status=0x%02x\n", gg_status); + mutex_unlock(&ug31->info_update_lock); + + charger_dc_in_before_suspend = wmt_charger_is_dc_plugin(); + + if (charger_param.id == UG31XX_ID_3102) { + hw_charging_enable(); + } + + GAUGE_notice("ug31xx_i2c_suspend() end\n"); + return 0; +} + +static int ug31xx_i2c_resume(struct i2c_client *client) +{ + int gg_status; + + GAUGE_notice("ug31xx_i2c_resume() start\n"); + + mutex_lock(&ug31->info_update_lock); + gg_status = upiGG_Wakeup(pGGB, charger_dc_in_before_suspend); + if (gg_status != UG_READ_DEVICE_INFO_SUCCESS) { + GAUGE_err("Fail in resume. gg_status=0x%02x\n", gg_status); + if(gg_status == UG_MEAS_FAIL_BATTERY_REMOVED) + { + gauge_dev_capacity.NAC = 0; + gauge_dev_capacity.LMD = 0; + gauge_dev_capacity.RSOC = 0; + } + } else { + GAUGE_notice("Driver resume. gg_status=0x%02x\n", gg_status); + } + mutex_unlock(&ug31->info_update_lock); + + /// [AT-PM] : Check charger status ; 08/14/2013 + mutex_lock(&ug31->info_update_lock); + upiGG_ForceTaper(pGGB, is_charging_full(), charger_dc_in_before_suspend, wmt_charger_is_dc_plugin()); + mutex_unlock(&ug31->info_update_lock); + + ug3105_in_suspend = false; + + cancel_delayed_work(&ug31->ug31_gauge_info_work); + queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ); + + GAUGE_notice("ug31xx_i2c_resume() end\n"); + return 0; +} + +void ug31xx_i2c_shutdown(struct i2c_client *client) +{ + int gg_status; + + cancel_delayed_work(&ug31->ug31_gauge_info_work); + mutex_lock(&ug31->info_update_lock); + gg_status = upiGG_PrePowerOff(pGGB); + mutex_unlock(&ug31->info_update_lock); + GAUGE_notice("Driver shutdown. gg_status=0x%02x\n", gg_status); +} + +static const struct i2c_device_id ug31xx_i2c_id[] = { + { UG31XX_DEV_NAME, 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, ug31xx_i2c_id); + +static struct i2c_driver ug31xx_i2c_driver = { + .driver = { + .name = UG31XX_DEV_NAME, + .owner = THIS_MODULE, + }, + .probe = ug31xx_i2c_probe, + .remove = __devexit_p(ug31xx_i2c_remove), + .suspend = ug31xx_i2c_suspend, + .resume = ug31xx_i2c_resume, + .shutdown = ug31xx_i2c_shutdown, + .id_table = ug31xx_i2c_id, +}; + +static struct i2c_board_info ug31xx_i2c_board_info = { + .type = "ug31xx-gauge", + .flags = 0x00, + .addr = 0x70, + .platform_data = NULL, + .archdata = NULL, + .irq = -1, +}; + +static struct i2c_client *i2c_client; +static struct i2c_adapter *i2c_adap; + +static int __init ug31xx_i2c_init(void) +{ + int ret; + + ret = parse_battery_param(); + if (ret) + return ret; + + i2c_adap = i2c_get_adapter(charger_param.i2c); + if (!i2c_adap) { + pr_err("Cannot get i2c adapter %d\n", charger_param.i2c); + ret = -ENODEV; + goto err1; + } + + i2c_client = i2c_new_device(i2c_adap, &ug31xx_i2c_board_info); + if (!i2c_client) { + pr_err("Unable to add I2C device for 0x%x\n", + ug31xx_i2c_board_info.addr); + ret = -ENODEV; + goto err2; + } + + ret = i2c_add_driver(&ug31xx_i2c_driver); + if (ret) + goto err3; + + return 0; + +err3: i2c_unregister_device(i2c_client); +err2: i2c_put_adapter(i2c_adap); +err1: + return ret; +} +module_init(ug31xx_i2c_init); + +static void __exit ug31xx_i2c_exit(void) +{ + i2c_put_adapter(i2c_adap); + i2c_del_driver(&ug31xx_i2c_driver); + i2c_unregister_device(i2c_client); +} +module_exit(ug31xx_i2c_exit); + +MODULE_DESCRIPTION("ug31xx gauge driver"); +MODULE_LICENSE("GPL"); + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12) +MODULE_PARM (force_reset, "b"); +MODULE_PARM (enable_debug, "b"); +#else +module_param (force_reset, byte, 0); +module_param (enable_debug, byte, 0644); +#endif +MODULE_PARM_DESC(force_reset, "Set 1 to force reset driver as first power on."); +MODULE_PARM_DESC(enable_debug, "Set 1 to enable dumping debug message."); |