| /* |
| * drivers/power/bq27541_battery.c |
| * |
| * Gas Gauge driver for TI's BQ27541 |
| * |
| * Copyright (c) 2010, 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; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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/init.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/err.h> |
| #include <linux/debugfs.h> |
| #include <linux/power_supply.h> |
| #include <linux/i2c.h> |
| #include <linux/slab.h> |
| #include <linux/gpio.h> |
| #include <linux/delay.h> |
| #include <linux/timer.h> |
| #include <linux/interrupt.h> |
| #include <asm/unaligned.h> |
| #include <linux/miscdevice.h> |
| #include <mach/gpio.h> |
| #include "../../arch/arm/mach-tegra/gpio-names.h" |
| #include "../../arch/arm/mach-tegra/wakeups-t3.h" |
| |
| #define SMBUS_RETRY (0) |
| #define BAT_IN_DET TEGRA_GPIO_PN4 |
| #define GPIOPIN_BATTERY_DETECT BAT_IN_DET |
| #define GPIOPIN_LOW_BATTERY_DETECT TEGRA_GPIO_PS4 |
| #define BATTERY_POLLING_RATE (60) |
| #define DELAY_FOR_CORRECT_CHARGER_STATUS (5) |
| #define TEMP_KELVIN_TO_CELCIUS (2731) |
| #define MAXIMAL_VALID_BATTERY_TEMP (120) |
| #define BATTERY_PROTECTED_VOLT (2800) |
| #define USB_NO_Cable 0 |
| #define USB_DETECT_CABLE 1 |
| #define USB_SHIFT 0 |
| #define AC_SHIFT 1 |
| #define USB_Cable ((1 << (USB_SHIFT)) | (USB_DETECT_CABLE)) |
| #define USB_AC_Adapter ((1 << (AC_SHIFT)) | (USB_DETECT_CABLE)) |
| #define USB_CALBE_DETECT_MASK (USB_Cable | USB_DETECT_CABLE) |
| |
| #define BATTERY_MANUFACTURER_SIZE 12 |
| #define BATTERY_NAME_SIZE 8 |
| |
| /* Battery flags bit definitions */ |
| #define BATT_STS_DSG 0x0001 |
| #define BATT_STS_FC 0x0200 |
| #define BATT_STS_CHG_INH 0x0800 |
| |
| /* Debug Message */ |
| #define BAT_NOTICE(format, arg...) \ |
| printk(KERN_NOTICE "%s " format , __FUNCTION__ , ## arg) |
| |
| #define BAT_ERR(format, arg...) \ |
| printk(KERN_ERR format , ## arg) |
| |
| /* Global variable */ |
| unsigned battery_cable_status = 0; |
| unsigned battery_driver_ready = 0; |
| static int ac_on ; |
| static int usb_on ; |
| unsigned int bq27541_i2c_error; |
| static unsigned int ota_flag = 0; |
| static unsigned int battery_current; |
| static unsigned int battery_remaining_capacity; |
| static atomic_t device_count; |
| static unsigned int bat_check_interval = BATTERY_POLLING_RATE; |
| struct workqueue_struct *battery_work_queue = NULL; |
| |
| /* Functions declaration */ |
| static int bq27541_get_psp(int reg_offset, enum power_supply_property psp,union power_supply_propval *val); |
| static int bq27541_get_property(struct power_supply *psy, |
| enum power_supply_property psp, union power_supply_propval *val); |
| extern unsigned get_usb_cable_status(void); |
| extern int smb347_charger_enable(bool enable); |
| extern int smb347_config_thermal_charging(int temp); |
| |
| module_param(battery_current, uint, 0644); |
| module_param(battery_remaining_capacity, uint, 0644); |
| |
| #define BQ27541_DATA(_psp, _addr, _min_value, _max_value) \ |
| { \ |
| .psp = POWER_SUPPLY_PROP_##_psp, \ |
| .addr = _addr, \ |
| .min_value = _min_value, \ |
| .max_value = _max_value, \ |
| } |
| |
| enum { |
| REG_MANUFACTURER_DATA, |
| REG_STATE_OF_HEALTH, |
| REG_TEMPERATURE, |
| REG_VOLTAGE, |
| REG_CURRENT, |
| REG_TIME_TO_EMPTY, |
| REG_TIME_TO_FULL, |
| REG_STATUS, |
| REG_CAPACITY, |
| REG_SERIAL_NUMBER, |
| REG_MAX |
| }; |
| |
| typedef enum { |
| Charger_Type_Battery = 0, |
| Charger_Type_AC, |
| Charger_Type_USB, |
| Charger_Type_Num, |
| Charger_Type_Force32 = 0x7FFFFFFF |
| } Charger_Type; |
| |
| static struct bq27541_device_data { |
| enum power_supply_property psp; |
| u8 addr; |
| int min_value; |
| int max_value; |
| } bq27541_data[] = { |
| [REG_MANUFACTURER_DATA] = BQ27541_DATA(PRESENT, 0, 0, 65535), |
| [REG_STATE_OF_HEALTH] = BQ27541_DATA(HEALTH, 0, 0, 65535), |
| [REG_TEMPERATURE] = BQ27541_DATA(TEMP, 0x06, 0, 65535), |
| [REG_VOLTAGE] = BQ27541_DATA(VOLTAGE_NOW, 0x08, 0, 6000), |
| [REG_CURRENT] = BQ27541_DATA(CURRENT_NOW, 0x14, -32768, 32767), |
| [REG_TIME_TO_EMPTY] = BQ27541_DATA(TIME_TO_EMPTY_AVG, 0x16, 0, 65535), |
| [REG_TIME_TO_FULL] = BQ27541_DATA(TIME_TO_FULL_AVG, 0x18, 0, 65535), |
| [REG_STATUS] = BQ27541_DATA(STATUS, 0x0a, 0, 65535), |
| [REG_CAPACITY] = BQ27541_DATA(CAPACITY, 0x2c, 0, 100), |
| }; |
| |
| static enum power_supply_property bq27541_properties[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| }; |
| |
| void check_cabe_type(void) |
| { |
| if(battery_cable_status == USB_AC_Adapter) { |
| ac_on = 1; |
| usb_on = 0; |
| } |
| else if(battery_cable_status == USB_Cable) { |
| usb_on = 1; |
| ac_on = 0; |
| } |
| else { |
| ac_on = 0; |
| usb_on = 0; |
| } |
| } |
| |
| static enum power_supply_property power_properties[] = { |
| POWER_SUPPLY_PROP_ONLINE, |
| }; |
| |
| static int power_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_ONLINE: |
| if(psy->type == POWER_SUPPLY_TYPE_MAINS && ac_on) |
| val->intval = 1; |
| else if (psy->type == POWER_SUPPLY_TYPE_USB && usb_on) |
| val->intval = 1; |
| else |
| val->intval = 0; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| return ret; |
| } |
| |
| static char *supply_list[] = { |
| "battery", |
| "ac", |
| #ifndef REMOVE_USB_POWER_SUPPLY |
| "usb", |
| #endif |
| }; |
| |
| static struct power_supply bq27541_supply[] = { |
| { |
| .name = "battery", |
| .type = POWER_SUPPLY_TYPE_BATTERY, |
| .properties = bq27541_properties, |
| .num_properties = ARRAY_SIZE(bq27541_properties), |
| .get_property = bq27541_get_property, |
| }, |
| { |
| .name = "ac", |
| .type = POWER_SUPPLY_TYPE_MAINS, |
| .supplied_to = supply_list, |
| .num_supplicants = ARRAY_SIZE(supply_list), |
| .properties = power_properties, |
| .num_properties = ARRAY_SIZE(power_properties), |
| .get_property = power_get_property, |
| }, |
| #ifndef REMOVE_USB_POWER_SUPPLY |
| { |
| .name = "usb", |
| .type = POWER_SUPPLY_TYPE_USB, |
| .supplied_to = supply_list, |
| .num_supplicants = ARRAY_SIZE(supply_list), |
| .properties =power_properties, |
| .num_properties = ARRAY_SIZE(power_properties), |
| .get_property = power_get_property, |
| }, |
| #endif |
| }; |
| |
| static struct bq27541_device_info { |
| struct i2c_client *client; |
| struct delayed_work battery_stress_test; |
| struct delayed_work status_poll_work; |
| struct delayed_work low_low_bat_work; |
| struct delayed_work shutdown_en_work; |
| struct miscdevice battery_misc; |
| struct wake_lock low_battery_wake_lock; |
| struct wake_lock cable_wake_lock; |
| char device_name[5]; |
| int smbus_status; |
| int battery_present; |
| int low_battery_present; |
| int gpio_battery_detect; |
| int gpio_low_battery_detect; |
| int irq_low_battery_detect; |
| int irq_battery_detect; |
| int bat_status; |
| int bat_temp; |
| int bat_vol; |
| int bat_current; |
| int bat_capacity; |
| int cap_zero_count; |
| int shutdown_disable; |
| unsigned int old_capacity; |
| unsigned int cap_err; |
| unsigned int old_temperature; |
| unsigned int temp_err; |
| unsigned int prj_id; |
| spinlock_t lock; |
| } *bq27541_device; |
| |
| static int bq27541_read_i2c(u8 reg, int *rt_value, int b_single) |
| { |
| struct i2c_client *client = bq27541_device->client; |
| struct i2c_msg msg[1]; |
| unsigned char data[2]; |
| int err; |
| |
| if (!client->adapter) |
| return -ENODEV; |
| |
| msg->addr = client->addr; |
| msg->flags = 0; |
| msg->len = 1; |
| msg->buf = data; |
| |
| data[0] = reg; |
| err = i2c_transfer(client->adapter, msg, 1); |
| |
| if (err >= 0) { |
| if (!b_single) |
| msg->len = 2; |
| else |
| msg->len = 1; |
| |
| msg->flags = I2C_M_RD; |
| err = i2c_transfer(client->adapter, msg, 1); |
| if (err >= 0) { |
| if (!b_single) |
| *rt_value = get_unaligned_le16(data); |
| else |
| *rt_value = data[0]; |
| |
| return 0; |
| } |
| } |
| return err; |
| } |
| |
| int bq27541_smbus_read_data(int reg_offset,int byte,int *rt_value) |
| { |
| s32 ret=-EINVAL; |
| int count=0; |
| |
| do { |
| ret = bq27541_read_i2c(bq27541_data[reg_offset].addr, rt_value, 0); |
| } while((ret<0)&&(++count<=SMBUS_RETRY)); |
| return ret; |
| } |
| |
| int bq27541_smbus_write_data(int reg_offset,int byte, unsigned int value) |
| { |
| s32 ret = -EINVAL; |
| int count=0; |
| |
| do{ |
| if(byte){ |
| ret = i2c_smbus_write_byte_data(bq27541_device->client,bq27541_data[reg_offset].addr,value&0xFF); |
| } |
| else{ |
| ret = i2c_smbus_write_word_data(bq27541_device->client,bq27541_data[reg_offset].addr,value&0xFFFF); |
| } |
| }while((ret<0) && (++count<=SMBUS_RETRY)); |
| return ret; |
| } |
| |
| static ssize_t show_battery_smbus_status(struct device *dev, struct device_attribute *devattr, char *buf) |
| { |
| int status=!bq27541_device->smbus_status; |
| return sprintf(buf, "%d\n", status); |
| } |
| |
| static DEVICE_ATTR(battery_smbus, S_IWUSR | S_IRUGO, show_battery_smbus_status,NULL); |
| |
| static struct attribute *battery_smbus_attributes[] = { |
| |
| &dev_attr_battery_smbus.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group battery_smbus_group = { |
| .attrs = battery_smbus_attributes, |
| }; |
| |
| static int bq27541_battery_current(void) |
| { |
| int ret; |
| int curr = 0; |
| |
| ret = bq27541_read_i2c(bq27541_data[REG_CURRENT].addr, &curr, 0); |
| if (ret) { |
| BAT_ERR("error reading current ret = %x\n", ret); |
| return 0; |
| } |
| |
| curr = (s16)curr; |
| |
| if (curr >= bq27541_data[REG_CURRENT].min_value && |
| curr <= bq27541_data[REG_CURRENT].max_value) { |
| return curr; |
| } else |
| return 0; |
| } |
| |
| static void battery_status_poll(struct work_struct *work) |
| { |
| struct bq27541_device_info *batt_dev = container_of(work, struct bq27541_device_info, status_poll_work.work); |
| |
| if(!battery_driver_ready) |
| BAT_NOTICE("battery driver not ready\n"); |
| |
| power_supply_changed(&bq27541_supply[Charger_Type_Battery]); |
| |
| if (!bq27541_device->temp_err) |
| if (ac_on || usb_on) |
| smb347_config_thermal_charging(bq27541_device->old_temperature/10); |
| |
| /* Schedule next polling */ |
| queue_delayed_work(battery_work_queue, &batt_dev->status_poll_work, bat_check_interval*HZ); |
| } |
| |
| static void low_low_battery_check(struct work_struct *work) |
| { |
| cancel_delayed_work(&bq27541_device->status_poll_work); |
| queue_delayed_work(battery_work_queue,&bq27541_device->status_poll_work, 0.1*HZ); |
| msleep(2000); |
| enable_irq(bq27541_device->irq_low_battery_detect); |
| } |
| |
| static void shutdown_enable_set(struct work_struct *work) |
| { |
| bq27541_device->shutdown_disable = 0; |
| BAT_NOTICE("bq27541_device->shutdown_disable = 0\n"); |
| } |
| |
| static irqreturn_t low_battery_detect_isr(int irq, void *dev_id) |
| { |
| disable_irq_nosync(bq27541_device->irq_low_battery_detect); |
| bq27541_device->low_battery_present = gpio_get_value(bq27541_device->gpio_low_battery_detect); |
| BAT_NOTICE("gpio LL_BAT_T30=%d\n", bq27541_device->low_battery_present); |
| wake_lock_timeout(&bq27541_device->low_battery_wake_lock, 10*HZ); |
| queue_delayed_work(battery_work_queue, &bq27541_device->low_low_bat_work, 0.1*HZ); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int setup_low_battery_irq(void) |
| { |
| unsigned gpio = GPIOPIN_LOW_BATTERY_DETECT; |
| int ret, irq = gpio_to_irq(gpio); |
| |
| ret = gpio_request(gpio, "low_battery_detect"); |
| if (ret < 0) { |
| BAT_ERR("gpio LL_BAT_T30 request failed\n"); |
| goto fail; |
| } |
| |
| tegra_gpio_enable(gpio); |
| |
| ret = gpio_direction_input(gpio); |
| if (ret < 0) { |
| BAT_ERR("gpio LL_BAT_T30 unavaliable for input\n"); |
| goto fail_gpio; |
| } |
| |
| ret = request_irq(irq, low_battery_detect_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "bq27541-battery (low battery)", NULL); |
| if (ret < 0) { |
| BAT_ERR("gpio LL_BAT_T30 irq request failed\n"); |
| goto fail_irq; |
| } |
| |
| bq27541_device->low_battery_present = gpio_get_value(gpio); |
| bq27541_device->irq_low_battery_detect = gpio_to_irq(gpio); |
| BAT_NOTICE("irq=%d, LL_BAT_T30=%d\n", irq, bq27541_device->low_battery_present); |
| |
| enable_irq_wake(bq27541_device->irq_low_battery_detect); |
| |
| fail_irq: |
| fail_gpio: |
| gpio_free(gpio); |
| fail: |
| return ret; |
| } |
| |
| int battery_callback(unsigned usb_cable_state) |
| { |
| int old_cable_status; |
| |
| if(!battery_driver_ready) { |
| BAT_NOTICE("battery driver not ready\n"); |
| return 0; |
| } |
| |
| old_cable_status = battery_cable_status; |
| battery_cable_status = usb_cable_state; |
| |
| printk("========================================================\n"); |
| printk("battery_callback usb_cable_state = %x\n", usb_cable_state) ; |
| printk("========================================================\n"); |
| |
| if (old_cable_status != battery_cable_status) { |
| printk("battery_callback cable_wake_lock 5 sec...\n "); |
| wake_lock_timeout(&bq27541_device->cable_wake_lock, 5*HZ); |
| } |
| check_cabe_type(); |
| |
| if(!battery_cable_status) { |
| if (old_cable_status == USB_AC_Adapter) { |
| power_supply_changed(&bq27541_supply[Charger_Type_AC]); |
| } |
| #ifndef REMOVE_USB_POWER_SUPPLY |
| else if ( old_cable_status == USB_Cable) { |
| power_supply_changed(&bq27541_supply[Charger_Type_USB]); |
| } |
| #endif |
| } |
| #ifndef REMOVE_USB_POWER_SUPPLY |
| else if (battery_cable_status == USB_Cable) { |
| power_supply_changed(&bq27541_supply[Charger_Type_USB]); |
| } |
| #endif |
| else if (battery_cable_status == USB_AC_Adapter) { |
| power_supply_changed(&bq27541_supply[Charger_Type_AC]); |
| } |
| cancel_delayed_work(&bq27541_device->status_poll_work); |
| queue_delayed_work(battery_work_queue, &bq27541_device->status_poll_work, 2*HZ); |
| |
| return 1; |
| } |
| EXPORT_SYMBOL(battery_callback); |
| |
| static int bq27541_get_health(enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| if (psp == POWER_SUPPLY_PROP_PRESENT) { |
| val->intval = 1; |
| } |
| else if (psp == POWER_SUPPLY_PROP_HEALTH) { |
| val->intval = POWER_SUPPLY_HEALTH_GOOD; |
| } |
| return 0; |
| } |
| |
| static int bq27541_get_psp(int reg_offset, enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| s32 ret; |
| int smb_retry=0; |
| int rt_value=0; |
| |
| bq27541_device->smbus_status = bq27541_smbus_read_data(reg_offset, 0, &rt_value); |
| |
| if ((bq27541_device->smbus_status < 0) && (psp != POWER_SUPPLY_PROP_TEMP)) { |
| dev_err(&bq27541_device->client->dev, "%s: i2c read for %d failed\n", __func__, reg_offset); |
| |
| if (bq27541_i2c_error < 3) { |
| bq27541_i2c_error++; |
| if (battery_driver_ready) { |
| cancel_delayed_work(&bq27541_device->status_poll_work); |
| queue_delayed_work(battery_work_queue,&bq27541_device->status_poll_work, 1*HZ); |
| } |
| if(bq27541_i2c_error == 3) { |
| BAT_NOTICE("charger disable !!\n"); |
| smb347_charger_enable(0); |
| } |
| BAT_NOTICE("bq27541_i2c_error=%d\n", bq27541_i2c_error); |
| } |
| return -EINVAL; |
| } else if (bq27541_device->smbus_status >= 0) { |
| if (bq27541_i2c_error) |
| bq27541_i2c_error--; |
| } |
| |
| if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) { |
| if (rt_value >= bq27541_data[REG_VOLTAGE].min_value && |
| rt_value <= bq27541_data[REG_VOLTAGE].max_value) { |
| if (rt_value > BATTERY_PROTECTED_VOLT) { |
| val->intval = bq27541_device->bat_vol = rt_value*1000; |
| bq27541_i2c_error = 0; |
| } else { |
| val->intval = bq27541_device->bat_vol; |
| } |
| } else { |
| val->intval = bq27541_device->bat_vol; |
| } |
| BAT_NOTICE("voltage_now= %u uV\n", val->intval); |
| } |
| if (psp == POWER_SUPPLY_PROP_STATUS) { |
| ret = bq27541_device->bat_status = rt_value; |
| static char *status_text[] = {"Unknown", "Charging", "Discharging", "Not charging", "Full"}; |
| |
| if (ac_on || usb_on) { /* Charging detected */ |
| if (bq27541_device->old_capacity == 100) |
| val->intval = POWER_SUPPLY_STATUS_FULL; |
| else { |
| val->intval = POWER_SUPPLY_STATUS_CHARGING; |
| if (ret & BATT_STS_CHG_INH) { |
| if (ota_flag == 0) { |
| BAT_NOTICE("charger disable !!\n"); |
| smb347_charger_enable(0); |
| ota_flag = 1; |
| } |
| val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| } else { |
| if (ota_flag) { |
| BAT_NOTICE("charger enable !!\n"); |
| smb347_charger_enable(1); |
| ota_flag = 0; |
| } |
| } |
| } |
| } else if (ret & BATT_STS_FC) { /* Full-charged condition reached */ |
| if (!ac_on) |
| val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
| else |
| val->intval = POWER_SUPPLY_STATUS_FULL; |
| } else if (ret & BATT_STS_DSG) { /* Discharging detected */ |
| val->intval = POWER_SUPPLY_STATUS_DISCHARGING; |
| } else { |
| val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| } |
| BAT_NOTICE("status: %s ret= 0x%04x\n", status_text[val->intval], ret); |
| bq27541_i2c_error = 0; |
| } else if (psp == POWER_SUPPLY_PROP_TEMP) { |
| ret = bq27541_device->bat_temp = rt_value; |
| |
| if (bq27541_device->smbus_status >=0) { |
| if (rt_value >= bq27541_data[REG_TEMPERATURE].min_value && |
| rt_value <= bq27541_data[REG_TEMPERATURE].max_value) { |
| ret = (int)(u16)(((rt_value/10) - 273)*10); |
| if (ret>=0 && ret<=1000) { |
| if(bq27541_device->temp_err) |
| bq27541_device->temp_err--; |
| } else { |
| bq27541_device->temp_err++; |
| } |
| } else { |
| bq27541_device->temp_err++; |
| } |
| } else { |
| bq27541_device->temp_err++; |
| } |
| |
| if (bq27541_device->temp_err) { |
| BAT_NOTICE("error: temperature ret=%d, old_temp=%d \n", |
| rt_value, bq27541_device->old_temperature); |
| if (bq27541_device->old_temperature != 0xFF) { |
| ret = bq27541_device->old_temperature; |
| } else { |
| bq27541_device->temp_err--; |
| return -EINVAL; |
| } |
| |
| if (bq27541_device->temp_err > 2) { |
| BAT_NOTICE("error: temp_err > 2, shut down system \n"); |
| WARN_ON(1); |
| ret = MAXIMAL_VALID_BATTERY_TEMP; |
| } |
| } |
| bq27541_device->old_temperature = val->intval = ret; |
| BAT_NOTICE("temperature= %u (0.1¢XC)\n", val->intval); |
| } |
| if (psp == POWER_SUPPLY_PROP_CURRENT_NOW) { |
| val->intval = bq27541_device->bat_current |
| = bq27541_battery_current(); |
| BAT_NOTICE("current = %d mA\n", val->intval); |
| } |
| return 0; |
| } |
| |
| static int bq27541_get_capacity(union power_supply_propval *val) |
| { |
| s32 ret; |
| s32 temp_capacity; |
| int smb_retry=0; |
| int cap = 0; |
| |
| do { |
| bq27541_device->smbus_status = bq27541_smbus_read_data(REG_CAPACITY, 0 ,&bq27541_device->bat_capacity); |
| } while((bq27541_device->smbus_status < 0) && ( ++smb_retry <= SMBUS_RETRY)); |
| |
| if (bq27541_device->smbus_status < 0) { |
| dev_err(&bq27541_device->client->dev, "%s: i2c read for %d " |
| "failed bq27541_device->cap_err=%u\n", __func__, REG_CAPACITY, bq27541_device->cap_err); |
| |
| if(bq27541_device->cap_err>5 || (bq27541_device->old_capacity == 0xFF)) { |
| return -EINVAL; |
| } else { |
| val->intval = bq27541_device->old_capacity; |
| bq27541_device->cap_err++; |
| BAT_NOTICE("cap_err=%u use old capacity=%u\n", bq27541_device->cap_err, val->intval); |
| return 0; |
| } |
| } |
| |
| ret = bq27541_device->bat_capacity; |
| |
| temp_capacity = ((ret >= 100) ? 100 : ret); |
| |
| /* start: for mapping %99 to 100%. Lose 84%*/ |
| if(temp_capacity==99) |
| temp_capacity=100; |
| if(temp_capacity >=84 && temp_capacity <=98) |
| temp_capacity++; |
| /* for mapping %99 to 100% */ |
| |
| /* lose 26% 47% 58%,69%,79% */ |
| if(temp_capacity >70 && temp_capacity <80) |
| temp_capacity-=1; |
| else if(temp_capacity >60&& temp_capacity <=70) |
| temp_capacity-=2; |
| else if(temp_capacity >50&& temp_capacity <=60) |
| temp_capacity-=3; |
| else if(temp_capacity >30&& temp_capacity <=50) |
| temp_capacity-=4; |
| else if(temp_capacity >=0&& temp_capacity <=30) |
| temp_capacity-=5; |
| |
| /*Re-check capacity to avoid that temp_capacity <0*/ |
| temp_capacity = ((temp_capacity <0) ? 0 : temp_capacity); |
| |
| if ((temp_capacity == 0) && (bq27541_device->shutdown_disable == 0) && battery_cable_status) { |
| bq27541_device->cap_zero_count++; |
| if (bq27541_device->cap_zero_count < 2) { |
| msleep(1); |
| bq27541_read_i2c(bq27541_data[REG_CAPACITY].addr, &cap, 0); |
| BAT_NOTICE("temp_capacity=%d, cap=%d, report capacity use: %d \n", |
| temp_capacity, cap, (cap>5) ? bq27541_device->old_capacity : temp_capacity); |
| if (cap > 5) { |
| temp_capacity = bq27541_device->old_capacity; |
| } |
| } else { // >=2 |
| //if (bq27541_device->cap_zero_count >=3) { |
| bq27541_device->cap_zero_count = 0; |
| bq27541_device->shutdown_disable = 1; |
| BAT_NOTICE("cheat cable out to shutdown system !!!\n"); |
| battery_callback(0); |
| //} |
| } |
| } else { |
| bq27541_device->cap_zero_count = 0; |
| } |
| |
| if (temp_capacity <= 4 && bq27541_device->old_capacity > 4) { |
| bat_check_interval = 1; |
| BAT_NOTICE("bat_check_interval=%d \n", bat_check_interval); |
| } else if (temp_capacity <= 15 && bq27541_device->old_capacity > 15) { |
| bat_check_interval = 30; |
| BAT_NOTICE("bat_check_interval=%d \n", bat_check_interval); |
| } else { |
| if ((temp_capacity > 5) && (bat_check_interval == 1)) { |
| bat_check_interval = 30; |
| BAT_NOTICE("bat_check_interval=%d \n", bat_check_interval); |
| } else if (temp_capacity > 16 && bat_check_interval != 60) { |
| bat_check_interval = 60; |
| BAT_NOTICE("bat_check_interval=%d \n", bat_check_interval); |
| } |
| } |
| |
| val->intval = temp_capacity; |
| |
| bq27541_device->old_capacity = val->intval; |
| bq27541_device->cap_err=0; |
| |
| BAT_NOTICE("= %u%% ret= %u\n", val->intval, bq27541_device->bat_capacity); |
| return 0; |
| } |
| |
| static int bq27541_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| u8 count; |
| switch (psp) { |
| case POWER_SUPPLY_PROP_PRESENT: |
| case POWER_SUPPLY_PROP_HEALTH: |
| if (bq27541_get_health(psp, val)) |
| goto error; |
| break; |
| |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| val->intval = POWER_SUPPLY_TECHNOLOGY_LION; |
| break; |
| |
| case POWER_SUPPLY_PROP_CAPACITY: |
| if (bq27541_get_capacity(val)) |
| goto error; |
| break; |
| |
| case POWER_SUPPLY_PROP_STATUS: |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| case POWER_SUPPLY_PROP_TEMP: |
| case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: |
| case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: |
| case POWER_SUPPLY_PROP_SERIAL_NUMBER: |
| for (count = 0; count < REG_MAX; count++) { |
| if (psp == bq27541_data[count].psp) |
| break; |
| } |
| |
| if (bq27541_get_psp(count, psp, val)) |
| return -EINVAL; |
| break; |
| |
| default: |
| dev_err(&bq27541_device->client->dev, |
| "%s: INVALID property psp=%u\n", __func__,psp); |
| return -EINVAL; |
| } |
| |
| return 0; |
| |
| error: |
| |
| return -EINVAL; |
| } |
| |
| static int is_legal_pack(void) |
| { |
| char data[7]; |
| int ret, retry = 3; |
| |
| while(--retry > 0) |
| { |
| ret = i2c_smbus_read_i2c_block_data(bq27541_device->client, 0x63, 7, data); |
| if (ret >= 0) { |
| if(!strncmp(data, "ME370", 5)) { |
| strncpy(bq27541_device->device_name, data, 5); |
| BAT_NOTICE("device name: %s\n", bq27541_device->device_name); |
| return 1; |
| } |
| } |
| } |
| BAT_NOTICE("device name: not found\n"); |
| return 0; |
| } |
| |
| #include "stress_test.c" |
| static int bq27541_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int ret, i=0; |
| |
| BAT_NOTICE("+ client->addr= %02x\n", client->addr); |
| |
| bq27541_device = kzalloc(sizeof(*bq27541_device), GFP_KERNEL); |
| if (!bq27541_device) |
| return -ENOMEM; |
| |
| memset(bq27541_device, 0, sizeof(*bq27541_device)); |
| bq27541_device->client = client; |
| i2c_set_clientdata(client, bq27541_device); |
| bq27541_device->smbus_status = 0; |
| bq27541_device->cap_err = 0; |
| bq27541_device->temp_err = 0; |
| bq27541_device->old_capacity = 0xFF; |
| bq27541_device->old_temperature = 0xFF; |
| bq27541_device->gpio_low_battery_detect = GPIOPIN_LOW_BATTERY_DETECT; |
| bq27541_device->shutdown_disable = 1; |
| bq27541_device->cap_zero_count = 0; |
| |
| if(!is_legal_pack()) { |
| BAT_NOTICE("charger disable !!\n"); |
| smb347_charger_enable(0); |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(bq27541_supply); i++) { |
| ret = power_supply_register(&client->dev, &bq27541_supply[i]); |
| if (ret) { |
| BAT_ERR("Failed to register power supply\n"); |
| while (i--) |
| power_supply_unregister(&bq27541_supply[i]); |
| kfree(bq27541_device); |
| return ret; |
| } |
| } |
| |
| battery_work_queue = create_singlethread_workqueue("battery_workqueue"); |
| INIT_DELAYED_WORK(&bq27541_device->status_poll_work, battery_status_poll); |
| INIT_DELAYED_WORK(&bq27541_device->low_low_bat_work, low_low_battery_check); |
| INIT_DELAYED_WORK(&bq27541_device->battery_stress_test, battery_strees_test); |
| INIT_DELAYED_WORK(&bq27541_device->shutdown_en_work, shutdown_enable_set); |
| cancel_delayed_work(&bq27541_device->status_poll_work); |
| |
| spin_lock_init(&bq27541_device->lock); |
| wake_lock_init(&bq27541_device->low_battery_wake_lock, WAKE_LOCK_SUSPEND, "low_battery_detection"); |
| wake_lock_init(&bq27541_device->cable_wake_lock, WAKE_LOCK_SUSPEND, "cable_state_changed"); |
| |
| /* Register sysfs */ |
| ret = sysfs_create_group(&client->dev.kobj, &battery_smbus_group); |
| if (ret) { |
| dev_err(&client->dev, "bq27541_probe: unable to create the sysfs\n"); |
| } |
| |
| /* Misc device registration */ |
| bq27541_device->battery_misc.minor = MISC_DYNAMIC_MINOR; |
| bq27541_device->battery_misc.name = "battery"; |
| bq27541_device->battery_misc.fops = &battery_fops; |
| ret = misc_register(&bq27541_device->battery_misc); |
| if(ret) { |
| pr_info("Cannot register bq27541 miscdev (err=%d)\n", ret); |
| } |
| |
| setup_low_battery_irq(); |
| |
| battery_driver_ready = 1; |
| |
| battery_cable_status = get_usb_cable_status(); |
| queue_delayed_work(battery_work_queue, &bq27541_device->shutdown_en_work, 40*HZ); |
| queue_delayed_work(battery_work_queue, &bq27541_device->status_poll_work, 15*HZ); |
| |
| BAT_NOTICE("- %s driver registered\n", client->name); |
| |
| return 0; |
| } |
| |
| static int bq27541_remove(struct i2c_client *client) |
| { |
| struct bq27541_device_info *bq27541_device; |
| int i=0; |
| bq27541_device = i2c_get_clientdata(client); |
| for (i = 0; i < ARRAY_SIZE(bq27541_supply); i++) { |
| power_supply_unregister(&bq27541_supply[i]); |
| } |
| if (bq27541_device) { |
| wake_lock_destroy(&bq27541_device->low_battery_wake_lock); |
| wake_lock_destroy(&bq27541_device->cable_wake_lock); |
| kfree(bq27541_device); |
| bq27541_device = NULL; |
| } |
| return 0; |
| } |
| |
| #if defined (CONFIG_PM) |
| static int bq27541_suspend(struct i2c_client *client, pm_message_t state) |
| { |
| cancel_delayed_work_sync(&bq27541_device->status_poll_work); |
| flush_workqueue(battery_work_queue);; |
| return 0; |
| } |
| |
| /* any smbus transaction will wake up pad */ |
| static int bq27541_resume(struct i2c_client *client) |
| { |
| cancel_delayed_work(&bq27541_device->status_poll_work); |
| queue_delayed_work(battery_work_queue,&bq27541_device->status_poll_work, 5*HZ); |
| |
| return 0; |
| } |
| #endif |
| |
| static int bq27541_shutdown(struct i2c_client *client) |
| { |
| BAT_NOTICE("+\n"); |
| bq27541_device->shutdown_disable = 0; |
| BAT_NOTICE("-\n"); |
| return 0; |
| } |
| |
| static const struct i2c_device_id bq27541_id[] = { |
| { "bq27541-battery", 0 }, |
| {}, |
| }; |
| |
| static struct i2c_driver bq27541_battery_driver = { |
| .probe = bq27541_probe, |
| .remove = bq27541_remove, |
| #if defined (CONFIG_PM) |
| .suspend = bq27541_suspend, |
| .resume = bq27541_resume, |
| .shutdown = bq27541_shutdown, |
| #endif |
| .id_table = bq27541_id, |
| .driver = { |
| .name = "bq27541-battery", |
| }, |
| }; |
| static int __init bq27541_battery_init(void) |
| { |
| int ret; |
| |
| ret = i2c_add_driver(&bq27541_battery_driver); |
| if (ret) |
| dev_err(&bq27541_device->client->dev, |
| "%s: i2c_add_driver failed\n", __func__); |
| |
| return ret; |
| } |
| module_init(bq27541_battery_init); |
| |
| static void __exit bq27541_battery_exit(void) |
| { |
| i2c_del_driver(&bq27541_battery_driver); |
| } |
| module_exit(bq27541_battery_exit); |
| |
| MODULE_AUTHOR("NVIDIA Corporation"); |
| MODULE_DESCRIPTION("bq27541 battery monitor driver"); |
| MODULE_LICENSE("GPL"); |