| /* |
| * BQ27425 battery driver |
| * |
| * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. |
| * |
| * 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. |
| * |
| * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED |
| * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
| * |
| */ |
| |
| /* |
| * Datasheets: |
| * http://focus.ti.com/docs/prod/folders/print/bq27000.html |
| * http://focus.ti.com/docs/prod/folders/print/bq27500.html |
| */ |
| |
| #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/idr.h> |
| #include <linux/i2c.h> |
| #include <linux/slab.h> |
| #include <linux/firmware.h> |
| #include <linux/mfd/88pm80x.h> |
| #include <asm/unaligned.h> |
| #include <plat/pm.h> |
| |
| #define BQ27425_FIRMWARE_ENABLE |
| |
| #define BQ27425_REG_CTRL 0x00 |
| /* CTRL set bit */ |
| #define DEVICE_TYPE (0x0001) |
| #define FW_VERSION (0x0002) |
| #define HW_VERSION (0x0003) |
| #define PREV_MACWRIT (0x0007) |
| #define BAT_INSERT (0x000c) |
| #define BAT_REMOVE (0x000d) |
| #define SET_HIBERNATE (0x0011) |
| #define CLEAR_HIBERNATE (0x0012) |
| #define SET_CFGUPDATE (0x0013) |
| #define FACTORY_RESTORE (0x0015) |
| #define SEALED (0x0020) |
| #define RESET (0x0041) |
| #define SOFT_RESET (0x0042) |
| |
| #define BQ27425_REG_TEMP 0x02 |
| #define BQ27425_REG_VOLT 0x04 |
| #define BQ27425_REG_FLAGS 0x06 |
| /*flags set bit*/ |
| #define FLAGS_DSG (1<<0) |
| #define FLAGS_BAT_DET (1<<3) |
| #define FLAGS_CHG (1<<8) |
| #define FLAGS_FC (1<<9) |
| #define FLAGS_UT (1<<14) |
| #define FLAGS_OT (1<<15) |
| |
| #define BQ27425_REG_NAC 0x08 |
| #define BQ27425_REG_FAC 0x0a |
| #define BQ27425_REG_RM 0x0c |
| #define BQ27425_REG_FCC 0x0e |
| #define BQ27425_REG_AI 0x10 |
| #define BQ27425_REG_SI 0x12 /* Nominal available capaciy */ |
| #define BQ27425_REG_MLI 0x14 /* Last measured discharge */ |
| #define BQ27425_REG_AP 0x18 /* Cycle count total */ |
| #define BQ27425_REG_SOC 0x1c /* Available enery */ |
| #define BQ27425_REG_ITEMP 0x1e /* RemainingCapacity */ |
| #define BQ27425_REG_SOH 0x20 |
| |
| #define KELVIN_VAL (2732) |
| |
| #define NORMAL_POLL_TIME (60) |
| #define POWER_CHANGE_POLL_TIME (2) |
| #define DETECT_CHARGING_TIME (10) |
| |
| struct bq27425_device_info; |
| |
| struct bq27425_reg_cache { |
| short cntl; |
| short temp; /*temperature;*/ |
| short volt; /*voltage;*/ |
| short nac; /*NominalAvailableCapacity */ |
| short fac; /*FullAvailableCapacit*/ |
| short rm; /*RemainingCapacity*/ |
| short fcc; /*FullChargeCapacity*/ |
| short si; /*StandbyCurrent*/ |
| short ai; /*averagecurrent*/ |
| short mli; /*MaxLoadCurrent*/ |
| short ap; /*AveragePower*/ |
| short soc; /*StateOfCharge*/ |
| short itemp; /*IntTemperature*/ |
| short soh; /*StateofHealth*/ |
| unsigned short flags; |
| short health; |
| short status; |
| short present; |
| }; |
| |
| struct bq27425_device_info { |
| struct device *dev; |
| struct i2c_client *client; |
| #ifdef BQ27425_FIRMWARE_ENABLE |
| struct i2c_client *update; |
| int update_addr; |
| #endif |
| int id; |
| int irq; |
| |
| struct bq27425_reg_cache cache; |
| int charge_design_full; |
| |
| struct delayed_work work; |
| struct delayed_work test_work; |
| |
| struct power_supply bat; |
| |
| struct mutex lock; |
| }; |
| |
| static enum power_supply_property bq27425_battery_props[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| 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_HEALTH, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| }; |
| |
| static char *usb_supplied[] = { |
| "usb", |
| }; |
| |
| static char *charger_supplied[] = { |
| "ac", |
| }; |
| |
| static unsigned int poll_interval = NORMAL_POLL_TIME; |
| module_param(poll_interval, uint, 0644); |
| MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - " \ |
| "0 disables polling"); |
| |
| static int bq27425_battery_status(struct bq27425_device_info *di); |
| |
| |
| static int bq27425_read_i2c(struct bq27425_device_info *di, u8 reg, u16 *val) |
| { |
| struct i2c_client *client = to_i2c_client(di->dev); |
| struct i2c_msg msg[2]; |
| unsigned char data[2]; |
| int ret = 0; |
| |
| if (!client->adapter) |
| return -ENODEV; |
| |
| msg[0].addr = client->addr; |
| msg[0].flags = 0; |
| msg[0].buf = ® |
| msg[0].len = sizeof(reg); |
| msg[1].addr = client->addr; |
| msg[1].flags = I2C_M_RD; |
| msg[1].buf = data; |
| msg[1].len = 2; |
| |
| ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); |
| if (ret < 0) |
| return ret; |
| |
| |
| *val = get_unaligned_le16(data); |
| |
| return ret; |
| } |
| |
| static int bq27425_write_i2c(struct bq27425_device_info *di, u8 reg, void *src) |
| { |
| struct i2c_client *client = to_i2c_client(di->dev); |
| unsigned char data[3]; |
| int ret; |
| |
| data[0] = (unsigned char)reg; |
| memcpy(&data[1], src, 2); |
| |
| |
| ret = i2c_master_send(client, data, 3); |
| if (ret < 0) |
| return ret; |
| |
| return ret; |
| } |
| |
| static int bq27425_get_cntl(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_CTRL, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| #if 0 |
| static int bq27425_set_cntl(struct bq27425_device_info *di, u16 data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_write_i2c(di, BQ27425_REG_CTRL, &data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| #endif |
| |
| static int bq27425_get_temp(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| u16 val; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_TEMP, &val); |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| *data = val - KELVIN_VAL; |
| |
| return ret; |
| } |
| |
| #if 0 |
| static int bq27425_set_temp(struct bq27425_device_info *di, u16 data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_write_i2c(di, BQ27425_REG_TEMP, &data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| #endif |
| |
| static int bq27425_get_volt(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_VOLT, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_flags(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_FLAGS, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_nac(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_NAC, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_fac(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_FAC, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_rm(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_RM, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_fcc(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_FCC, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_ai(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_AI, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_si(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_SI, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_mli(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_MLI, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_ap(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_AP, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_soc(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_SOC, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_itemp(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_ITEMP, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static int bq27425_get_soh(struct bq27425_device_info *di, u16 *data) |
| { |
| int ret = 0; |
| |
| ret = bq27425_read_i2c(di, BQ27425_REG_SOH, data); |
| |
| if (ret < 0) |
| dev_err(di->dev, "error reading relative State-of-Charge\n"); |
| |
| return ret; |
| } |
| |
| static void bq27425_get_status(struct bq27425_device_info *di) |
| { |
| int ret = 0; |
| u16 data; |
| |
| ret = bq27425_get_flags(di, &data); |
| if (ret < 0) { |
| printk(KERN_ERR "get flags failed!\n"); |
| /* if i2c error, set fake value */ |
| di->cache.health = POWER_SUPPLY_HEALTH_GOOD; |
| di->cache.present = 0; |
| di->cache.status = POWER_SUPPLY_STATUS_DISCHARGING; |
| di->cache.volt = 4000; |
| return; |
| } else { |
| di->cache.flags = data; |
| if ((FLAGS_UT & di->cache.flags) || |
| (FLAGS_OT & di->cache.flags)) |
| di->cache.health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| else |
| di->cache.health = POWER_SUPPLY_HEALTH_GOOD; |
| di->cache.status = bq27425_battery_status(di); |
| di->cache.present = (di->cache.flags & FLAGS_BAT_DET) ? 1 : 0; |
| } |
| |
| ret = bq27425_get_cntl(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get cntl failed!\n"); |
| di->cache.cntl = data; |
| |
| ret = bq27425_get_temp(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get temp failed!\n"); |
| di->cache.temp = data; /*temperature;*/ |
| |
| ret = bq27425_get_volt(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get volt failed!\n"); |
| di->cache.volt = data; /*voltage;*/ |
| |
| ret = bq27425_get_nac(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get nac failed!\n"); |
| di->cache.nac = data; |
| |
| ret = bq27425_get_fac(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get fac failed!\n"); |
| di->cache.fac = data; |
| |
| ret = bq27425_get_rm(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get rm failed!\n"); |
| di->cache.rm = data; /*RemainingCapacity*/ |
| |
| ret = bq27425_get_fcc(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get fcc failed!\n"); |
| di->cache.fcc = data; |
| |
| ret = bq27425_get_si(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get si failed!\n"); |
| di->cache.si = data; |
| |
| ret = bq27425_get_ai(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get ai failed!\n"); |
| di->cache.ai = data; |
| |
| ret = bq27425_get_mli(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get mli failed!\n"); |
| di->cache.mli = data; |
| |
| ret = bq27425_get_ap(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get ap failed!\n"); |
| di->cache.ap = data; |
| |
| ret = bq27425_get_soc(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get soc failed!\n"); |
| di->cache.soc = data; |
| |
| ret = bq27425_get_itemp(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get di failed!\n"); |
| di->cache.itemp = data; |
| |
| ret = bq27425_get_soh(di, &data); |
| if (ret < 0) |
| printk(KERN_ERR "get soh failed!\n"); |
| di->cache.soh = data; |
| } |
| |
| static void bq27425_update(struct bq27425_device_info *di) |
| { |
| static struct bq27425_reg_cache cache = {0, }; |
| |
| bq27425_get_status(di); |
| |
| if (cache.soc != di->cache.soc || |
| cache.status != di->cache.status) { |
| cache = di->cache; |
| power_supply_changed(&di->bat); |
| } |
| } |
| |
| int is_charger_usb_online(void) |
| { |
| struct power_supply *psy; |
| union power_supply_propval data; |
| int online = 0; |
| int ret = 0; |
| |
| psy = power_supply_get_by_name(usb_supplied[0]); |
| BUG_ON(!psy); |
| |
| ret = psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &data); |
| BUG_ON(ret); |
| online = data.intval; |
| |
| if (!online) { |
| psy = power_supply_get_by_name(charger_supplied[0]); |
| BUG_ON(!psy); |
| |
| ret = psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &data); |
| BUG_ON(ret); |
| online = data.intval; |
| } |
| |
| return online; |
| } |
| |
| static void bq27425_battery_poll(struct work_struct *work) |
| { |
| int online; |
| struct bq27425_device_info *di = |
| container_of(work, struct bq27425_device_info, work.work); |
| |
| bq27425_update(di); |
| |
| printk(KERN_INFO "%s capacity: %d, voltag: %d, current: %d,\ |
| status: %d, present: %d\n", __func__, |
| di->cache.soc, di->cache.volt, |
| di->cache.ai, di->cache.status, di->cache.present); |
| |
| online = is_charger_usb_online(); |
| |
| if ((di->cache.status == POWER_SUPPLY_STATUS_DISCHARGING) && |
| online) |
| poll_interval = DETECT_CHARGING_TIME; |
| else |
| poll_interval = NORMAL_POLL_TIME; |
| |
| if (poll_interval > 0) { |
| /* The timer does not have to be accurate. */ |
| set_timer_slack(&di->work.timer, poll_interval * HZ / 4); |
| schedule_delayed_work(&di->work, poll_interval * HZ); |
| } |
| } |
| |
| |
| static int bq27425_battery_status(struct bq27425_device_info *di) |
| { |
| int status; |
| |
| if (di->cache.flags & FLAGS_DSG) |
| status = POWER_SUPPLY_STATUS_DISCHARGING; |
| else if (di->cache.flags & FLAGS_FC) |
| status = POWER_SUPPLY_STATUS_FULL; |
| else if (di->cache.flags & FLAGS_CHG) |
| status = POWER_SUPPLY_STATUS_CHARGING; |
| else |
| status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| |
| return status; |
| } |
| |
| #define to_bq27425_device_info(x) container_of((x), \ |
| struct bq27425_device_info, bat); |
| |
| static int bq27425_battery_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| int ret = 0; |
| struct bq27425_device_info *di = to_bq27425_device_info(psy); |
| |
| mutex_lock(&di->lock); |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = di->cache.status; |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = di->cache.health; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| val->intval = di->cache.volt * 1000; |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = di->cache.present; |
| break; |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| val->intval = di->cache.ai; |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| if (di->cache.present) |
| val->intval = di->cache.soc; |
| else { |
| /*report fake capacity*/ |
| val->intval = 80; |
| printk(KERN_WARNING "No battery fake capacity!!!\n"); |
| } |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| val->intval = di->cache.temp; |
| break; |
| default: |
| ret = -ENODEV; |
| break; |
| } |
| mutex_unlock(&di->lock); |
| |
| return ret; |
| } |
| |
| static void bq27425_external_power_changed(struct power_supply *psy) |
| { |
| struct bq27425_device_info *di = to_bq27425_device_info(psy); |
| |
| cancel_delayed_work_sync(&di->work); |
| set_timer_slack(&di->work.timer, POWER_CHANGE_POLL_TIME * HZ / 4); |
| schedule_delayed_work(&di->work, POWER_CHANGE_POLL_TIME * HZ); |
| } |
| |
| static int bq27425_powersupply_init(struct bq27425_device_info *di) |
| { |
| int ret; |
| unsigned short ctrl_cmd, ver_id; |
| int gpadc1_meas1; |
| |
| di->bat.type = POWER_SUPPLY_TYPE_BATTERY; |
| di->bat.properties = bq27425_battery_props; |
| di->bat.num_properties = ARRAY_SIZE(bq27425_battery_props); |
| di->bat.get_property = bq27425_battery_get_property; |
| di->bat.external_power_changed = bq27425_external_power_changed; |
| |
| ret = pm80x_codec_reg_read( |
| (PM80X_BASE_PAGE << 8) | PM800_POWER_DOWN_LOG1); |
| printk(KERN_INFO "PM800_POWER_DOWN_LOG1: 0x%x\n", ret); |
| ret = pm80x_codec_reg_read( |
| (PM80X_BASE_PAGE << 8) | PM800_POWER_DOWN_LOG2); |
| printk(KERN_INFO "PM800_POWER_DOWN_LOG2: 0x%x\n", ret); |
| |
| /*clean power-down log register */ |
| ret = pm80x_codec_reg_write( |
| (PM80X_BASE_PAGE << 8) | PM800_POWER_DOWN_LOG1, 0xff); |
| ret = pm80x_codec_reg_write( |
| (PM80X_BASE_PAGE << 8) | PM800_POWER_DOWN_LOG2, 0xff); |
| |
| ret = pm80x_codec_reg_read((PM80X_GPADC_PAGE << 8) | PM800_INT_ENA_3); |
| ret |= PM800_GPADC_ODD_6UA; |
| ret = pm80x_codec_reg_write( |
| ((PM80X_GPADC_PAGE << 8) | PM800_INT_ENA_3), ret); |
| |
| ret = pm80x_codec_reg_read((PM80X_GPADC_PAGE << 8) | PM800_STATUS_2); |
| ret |= PM800_GPADC1_MEAS_EN; |
| ret = pm80x_codec_reg_write( |
| ((PM80X_GPADC_PAGE << 8) | PM800_STATUS_2), ret); |
| |
| ret = pm80x_codec_reg_read( |
| (PM80X_GPADC_PAGE << 8) | PM800_INT_STATUS4); |
| ret |= PM800_GPADC_BD_GP1_EN; |
| ret = pm80x_codec_reg_write( |
| ((PM80X_GPADC_PAGE << 8) | PM800_INT_STATUS4), ret); |
| |
| msleep(20); |
| |
| gpadc1_meas1 = pm80x_codec_reg_read((PM80X_GPADC_PAGE << 8) | |
| PM800_GPADC1_MEAS1); |
| printk(KERN_INFO "%s PM800_GPADC1_MEAS1: 0x%x\n", __func__, |
| gpadc1_meas1); |
| |
| ret = pm80x_codec_reg_read((PM80X_BASE_PAGE << 8) | PM800_STATUS_1); |
| printk(KERN_INFO "PM800_STATUS_1: 0x%x\n", ret); |
| |
| if (!(gpadc1_meas1 & 0xf)) { |
| printk(KERN_INFO "Battery is detected!\n"); |
| ctrl_cmd = BAT_INSERT; |
| bq27425_write_i2c(di, BQ27425_REG_CTRL, &ctrl_cmd); |
| di->cache.present = 1; |
| } else { |
| ctrl_cmd = BAT_REMOVE; |
| bq27425_write_i2c(di, BQ27425_REG_CTRL, &ctrl_cmd); |
| di->cache.present = 0; |
| } |
| |
| ctrl_cmd = FW_VERSION; |
| bq27425_write_i2c(di, BQ27425_REG_CTRL, &ctrl_cmd); |
| ret = bq27425_read_i2c(di, BQ27425_REG_CTRL, &ver_id); |
| printk(KERN_NOTICE "Firmware version: 0x%x\n", ver_id); |
| |
| |
| ret = power_supply_register(di->dev, &di->bat); |
| if (ret) { |
| dev_err(di->dev, "failed to register battery: %d\n", ret); |
| return ret; |
| } |
| |
| bq27425_update(di); |
| |
| INIT_DELAYED_WORK(&di->work, bq27425_battery_poll); |
| return 0; |
| } |
| |
| static void bq27425_powersupply_unregister(struct bq27425_device_info *di) |
| { |
| cancel_delayed_work_sync(&di->work); |
| |
| power_supply_unregister(&di->bat); |
| |
| mutex_destroy(&di->lock); |
| } |
| |
| static irqreturn_t bq27425_irq_handler(int irq, void *data) |
| { |
| struct bq27425_device_info *di = data; |
| schedule_delayed_work(&di->work, 0); |
| return IRQ_HANDLED; |
| } |
| |
| static DEFINE_MUTEX(battery_mutex); |
| |
| #ifdef BQ27425_FIRMWARE_ENABLE |
| |
| #define BATTERY_FIRMWARE_PATH "battery/firmware.img" |
| |
| /*command type*/ |
| enum bq27425_flag_type { |
| I2C_WRITE = 0, |
| I2C_READ, |
| I2C_COMPARE, |
| I2C_WAIT, |
| }; |
| |
| struct bq27425_updata_data { |
| enum bq27425_flag_type flag; |
| int i2c_addr; |
| int reg; |
| unsigned char data[50]; |
| int data_num; |
| int wait_time; |
| int read_num; |
| }; |
| |
| static inline int bq27425_update_read(struct i2c_client *i2c, |
| int reg, int bytes, void *dest) |
| { |
| int ret; |
| |
| if (bytes > 1) |
| ret = i2c_smbus_read_i2c_block_data(i2c, reg, bytes, dest); |
| else { |
| ret = i2c_smbus_read_byte_data(i2c, reg); |
| if (ret < 0) |
| return ret; |
| *(unsigned char *)dest = (unsigned char)ret; |
| } |
| return ret; |
| } |
| |
| static inline int bq27425_update_write(struct i2c_client *i2c, |
| int reg, int bytes, void *src) |
| { |
| unsigned char buf[bytes + 1]; |
| int ret; |
| |
| buf[0] = (unsigned char)reg; |
| memcpy(&buf[1], src, bytes); |
| |
| ret = i2c_master_send(i2c, buf, bytes + 1); |
| if (ret < 0) |
| return ret; |
| return 0; |
| } |
| |
| /* execute the command that got from firmware*/ |
| static int exec_cmd(struct bq27425_device_info *di, |
| struct bq27425_updata_data *data_s) |
| { |
| struct i2c_client *client = di->client; |
| struct i2c_client *update_client; |
| int i; |
| unsigned char temp[10]; |
| |
| if (data_s->i2c_addr != client->addr) { |
| if (di->update_addr != data_s->i2c_addr) { |
| di->update_addr = data_s->i2c_addr; |
| di->update = i2c_new_dummy(client->adapter, |
| di->update_addr); |
| i2c_set_clientdata(di->update, di); |
| } |
| update_client = di->update; |
| } else |
| update_client = client; |
| |
| if (data_s->flag == I2C_WRITE) { |
| printk(KERN_INFO "write reg: 0x%x, num: %d, ", |
| data_s->reg, data_s->data_num); |
| for (i = 0; i < data_s->data_num; i++) |
| printk(KERN_INFO |
| "data[%d] = 0x%x, ", |
| i, data_s->data[i]); |
| printk("\n"); |
| bq27425_update_write(update_client, data_s->reg, |
| data_s->data_num, data_s->data); |
| } |
| |
| if (data_s->flag == I2C_READ) { |
| printk(KERN_INFO "Read reg: 0x%x, read count: %d\n", |
| data_s->reg, data_s->read_num); |
| bq27425_update_read(update_client, data_s->reg, |
| data_s->read_num, data_s->data); |
| } |
| |
| if (data_s->flag == I2C_COMPARE) { |
| printk(KERN_INFO "compare reg: 0x%x, ", data_s->reg); |
| for (i = 0; i < data_s->data_num; i++) |
| printk(KERN_INFO |
| "data[%d] = 0x%x, ", |
| i, data_s->data[i]); |
| printk(KERN_INFO "\n"); |
| |
| bq27425_update_read(update_client, data_s->reg, |
| data_s->data_num, temp); |
| for (i = 0; i < data_s->data_num; i++) { |
| if (data_s->data[i] != temp[i]) |
| printk(KERN_INFO |
| "0x%x not equal 0x%x\n", |
| temp[i], data_s->data[i]); |
| } |
| } |
| |
| if (data_s->flag == I2C_WAIT) { |
| printk(KERN_INFO "wait time: %d\n", data_s->wait_time); |
| msleep(data_s->wait_time); |
| } |
| |
| return 0; |
| } |
| |
| static void *get_new_start(char *cmd, int split) |
| { |
| while (1) { |
| if (*cmd == split && *(cmd+1) != split) |
| break; |
| cmd++; |
| } |
| return cmd+1; |
| } |
| |
| static int is_alpha(char c) |
| { |
| if (('a' <= c && 'z' >= c) || |
| ('A' <= c && 'Z' >= c) || |
| ('0' <= c && '9' >= c)) |
| return 1; |
| else |
| return 0; |
| } |
| |
| /*firmware data |
| W: AA 00 14 04 |
| W: AA 00 72 36 |
| W: AA 00 FF FF |
| W: AA 00 FF FF |
| X: 1000 |
| W: AA 00 00 0F |
| X: 1000 |
| W: 16 00 1F 00 00 00 D2 FF |
| W: 16 64 F0 01 |
| X: 1 |
| C: 16 66 00 |
| |
| Write command: |
| "W:" instructs the I2C master to write one or more bytes to a given I2C |
| address and given register address. The I2C address format used |
| throughout this document is based on an 8-bit representation of the |
| address. The format of this sequence is: |
| "W: I2CAddr RegAddr Byte0 Byte1 Byte2...". |
| For example, the following: |
| W: AA 55 AB CD EF 00 |
| |
| Read command: |
| "R:" instructs the I2C master to read a given number of bytes from a |
| given I2C address and given register address. |
| The format of this sequence is: |
| "R: I2CAddr RegAddr NumBytes" |
| For example, the following: |
| R: AA 55 100 |
| |
| Read and Compare Command |
| "C:" The data presented with this command matches the data read exactly, |
| or the operation ceases with an error indication to the user. |
| The format of this sequence is: |
| "C: i2cAddr RegAddr Byte0 Byte1 Byte2". |
| An example of this command is as follows: |
| C: AA 55 AB CD EF 00 |
| |
| Wait command |
| "X:" The wait command indicates that the host waits a minimum of the |
| given number of milliseconds before continuing to the next row of |
| the flash stream. |
| For example, the following: |
| X: 200 |
| */ |
| /*parse one command and fill the command struct*/ |
| static void parse_one_cmd(char *cmd, |
| struct bq27425_updata_data *data_s) |
| { |
| char temp[50]; |
| char *p_cmd, *p; |
| |
| p_cmd = cmd; |
| |
| if ('W' == *p_cmd || 'w' == *p_cmd) |
| data_s->flag = I2C_WRITE; |
| if ('R' == *p_cmd || 'r' == *p_cmd) |
| data_s->flag = I2C_READ; |
| if ('C' == *p_cmd || 'c' == *p_cmd) |
| data_s->flag = I2C_COMPARE; |
| if ('X' == *p_cmd || 'x' == *p_cmd) |
| data_s->flag = I2C_WAIT; |
| |
| p_cmd = get_new_start(p_cmd, 0x20); |
| |
| if (data_s->flag == I2C_WRITE || data_s->flag == I2C_READ |
| || data_s->flag == I2C_COMPARE) { |
| for (p = temp;;) { |
| if (*p_cmd == 0x20 || *p_cmd == 0x0d) |
| break; |
| *p++ = *p_cmd++; |
| } |
| *p = 0; |
| data_s->i2c_addr = simple_strtol(temp, NULL, 16)/2; |
| |
| p_cmd = get_new_start(p_cmd, 0x20); |
| |
| for (p = temp;;) { |
| if (*p_cmd == 0x20 || *p_cmd == 0x0d) |
| break; |
| *p++ = *p_cmd++; |
| } |
| *p = 0; |
| data_s->reg = simple_strtol(temp, NULL, 16); |
| |
| p_cmd = get_new_start(p_cmd, 0x20); |
| |
| for (p = temp, data_s->data_num = 0;; p_cmd++) { |
| if (*p_cmd == 0 || *p_cmd == 0x0d) { |
| *p = 0; |
| data_s->data[data_s->data_num++] = |
| simple_strtol(temp, NULL, 16); |
| p = temp; |
| break; |
| } |
| if (*p_cmd == 0x20) { |
| *p = 0; |
| data_s->data[data_s->data_num++] = |
| simple_strtol(temp, NULL, 16); |
| p = temp; |
| continue; |
| } |
| *p++ = *p_cmd; |
| } |
| *p = 0; |
| |
| if (data_s->flag == I2C_READ) |
| data_s->read_num = (int)simple_strtol(temp, NULL, 16); |
| } |
| |
| if (data_s->flag == I2C_WAIT) { |
| for (p = temp;;) { |
| if (*p_cmd == 0x20 || *p_cmd == 0x0d) |
| break; |
| *p++ = *p_cmd++; |
| } |
| *p = 0; |
| data_s->wait_time = (int)simple_strtol(temp, NULL, 10); |
| } |
| } |
| |
| /*Get one line from firmware data*/ |
| static char *get_one_cmd(char *dest, char *src) |
| { |
| char *p_src, *p_dest; |
| |
| p_src = src; |
| p_dest = dest; |
| |
| for (;; p_src++) { |
| if (*p_src == 0 || *p_src == 0x0d) |
| break; |
| *p_dest++ = *p_src; |
| } |
| |
| while (1) { |
| if (is_alpha(*p_src)) |
| break; |
| if (0 == *p_src) { |
| printk(KERN_INFO "End of the file !\n"); |
| break; |
| } |
| p_src++; |
| } |
| return p_src; |
| } |
| |
| static int update_firmware(struct bq27425_device_info *di, |
| void *data) |
| { |
| char one_cmd[50]; |
| char *p; |
| struct bq27425_updata_data cmd; |
| |
| p = data; |
| while (*p) { |
| memset(one_cmd, 0, 50); |
| p = get_one_cmd(one_cmd, p); |
| parse_one_cmd(one_cmd, &cmd); |
| exec_cmd( di, &cmd); |
| } |
| return 0; |
| } |
| |
| static ssize_t firmware_show_attrs(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| return 0; |
| } |
| |
| /*interface for userspace to update firmware*/ |
| static ssize_t firmware_update_attrs(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct bq27425_device_info *di = dev_get_drvdata(dev); |
| const struct firmware *fw; |
| int retval; |
| void *firmware_data; |
| size_t firmware_size; |
| char cmd[10]; |
| |
| sscanf(buf, "%s", cmd); |
| |
| if (memcmp(cmd, "update", strlen(cmd))) { |
| printk(KERN_ERR "Error command!\n"); |
| return count; |
| } |
| |
| retval = request_firmware(&fw, BATTERY_FIRMWARE_PATH, di->dev); |
| if (retval) { |
| printk(KERN_ERR |
| "firmware_sample_driver: Firmware not available\n"); |
| return count; |
| } |
| |
| firmware_size = fw->size; |
| firmware_data = kmalloc(firmware_size + 1, GFP_KERNEL); |
| memset(firmware_data, 0, firmware_size + 1); |
| memcpy(firmware_data, fw->data, firmware_size); |
| |
| release_firmware(fw); |
| |
| update_firmware(di, firmware_data); |
| kfree(firmware_data); |
| |
| return count; |
| } |
| |
| |
| static DEVICE_ATTR(update, S_IRUSR|S_IWUSR, firmware_show_attrs,\ |
| firmware_update_attrs); |
| #endif |
| |
| static void bq27425_test_work(struct work_struct *work) |
| { |
| struct bq27425_device_info *di = |
| container_of(work, struct bq27425_device_info, test_work.work); |
| |
| printk(KERN_INFO "%s soc: %d\n", __func__, di->cache.soc); |
| if (100 == di->cache.soc) |
| di->cache.soc = 10; |
| else |
| di->cache.soc += 5; |
| if (100 < di->cache.soc) |
| di->cache.soc = 100; |
| power_supply_changed(&di->bat); |
| |
| set_timer_slack(&di->test_work.timer, HZ*1); |
| schedule_delayed_work(&di->test_work, HZ*4); |
| } |
| |
| static ssize_t capacity_show_attrs(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct bq27425_device_info *di = dev_get_drvdata(dev); |
| |
| disable_irq(di->irq); |
| |
| cancel_delayed_work_sync(&di->work); |
| |
| INIT_DELAYED_WORK(&di->test_work, bq27425_test_work); |
| set_timer_slack(&di->test_work.timer, HZ*1); |
| schedule_delayed_work(&di->test_work, HZ*4); |
| |
| return 0; |
| } |
| |
| |
| static ssize_t capacity_change_attrs(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct bq27425_device_info *di = dev_get_drvdata(dev); |
| |
| cancel_delayed_work_sync(&di->test_work); |
| |
| enable_irq(di->irq); |
| |
| if (poll_interval > 0) { |
| /* The timer does not have to be accurate. */ |
| set_timer_slack(&di->work.timer, poll_interval * HZ / 4); |
| schedule_delayed_work(&di->work, poll_interval * HZ); |
| } |
| return count; |
| } |
| |
| static DEVICE_ATTR(test_capacity, S_IRUSR|S_IWUSR, \ |
| capacity_show_attrs, capacity_change_attrs); |
| |
| static struct attribute *battery_attributes[] = { |
| &dev_attr_test_capacity.attr, |
| #ifdef BQ27425_FIRMWARE_ENABLE |
| &dev_attr_update.attr, |
| #endif |
| NULL, |
| }; |
| |
| static struct attribute_group battery_attr_group = { |
| .attrs = battery_attributes, |
| }; |
| |
| static int bq27425_battery_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct bq27425_device_info *di; |
| int retval = 0; |
| int ret; |
| |
| di = kzalloc(sizeof(*di), GFP_KERNEL); |
| if (!di) { |
| dev_err(&client->dev, "failed to allocate device info data\n"); |
| retval = -ENOMEM; |
| goto batt_failed_1; |
| } |
| |
| di->client = client; |
| di->dev = &client->dev; |
| di->bat.name = "battery"; |
| di->irq = client->irq; |
| |
| mutex_init(&di->lock); |
| |
| if (bq27425_powersupply_init(di)) |
| goto batt_failed_1; |
| |
| i2c_set_clientdata(client, di); |
| |
| ret = request_irq(di->irq, bq27425_irq_handler, |
| IRQF_DISABLED | IRQF_TRIGGER_FALLING, |
| "bq27425", di); |
| if (ret < 0) |
| goto batt_irq_faild; |
| |
| ret = sysfs_create_group(&di->dev->kobj, &battery_attr_group); |
| dev_set_drvdata(di->dev, di); |
| |
| return 0; |
| batt_irq_faild: |
| free_irq(di->irq, di); |
| batt_failed_1: |
| kfree(di); |
| |
| return retval; |
| } |
| |
| static int bq27425_battery_remove(struct i2c_client *client) |
| { |
| struct bq27425_device_info *di = i2c_get_clientdata(client); |
| |
| bq27425_powersupply_unregister(di); |
| |
| kfree(di); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id bq27425_id[] = { |
| { "bq27425", 0}, /* bq27200 is same as bq27000, but with i2c */ |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, bq27425_id); |
| |
| static struct i2c_driver bq27425_battery_driver = { |
| .driver = { |
| .name = "bq27425", |
| }, |
| .probe = bq27425_battery_probe, |
| .remove = bq27425_battery_remove, |
| .id_table = bq27425_id, |
| }; |
| |
| static int __init bq27425_battery_i2c_init(void) |
| { |
| int ret; |
| ret = i2c_add_driver(&bq27425_battery_driver); |
| |
| if (ret) |
| printk(KERN_ERR "Unable to register BQ27425 i2c driver\n"); |
| |
| return ret; |
| } |
| |
| module_init(bq27425_battery_i2c_init); |
| |
| static inline void bq27425_battery_i2c_exit(void) |
| { |
| i2c_del_driver(&bq27425_battery_driver); |
| } |
| module_exit(bq27425_battery_i2c_exit); |
| |
| /* |
| * Module stuff |
| */ |
| |
| MODULE_AUTHOR(""); |
| MODULE_DESCRIPTION("BQ27425 battery monitor driver"); |
| MODULE_LICENSE("GPL"); |