From 7e82f4b0c4e0a040a3dc567a001621d31970ef13 Mon Sep 17 00:00:00 2001 From: xuhuicong Date: Fri, 1 Mar 2013 14:25:43 +0800 Subject: [PATCH] add cw2015 battery gauge driver --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/cw2015_battery.c | 694 +++++++++++++++++++++++++++ include/linux/power/cw2015_battery.h | 30 ++ 4 files changed, 732 insertions(+) create mode 100644 drivers/power/cw2015_battery.c create mode 100644 include/linux/power/cw2015_battery.h diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 241ad79f862c..ebc243b1163c 100755 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -329,6 +329,13 @@ config BATTERY_RK30_ADC_FAC help Say Y to enable support for the battery on the RK30. +config CW2015_BATTERY + tristate "CW2015 battery driver" + help + Say Y to enable support for the cw2015 on the Rockchip + + + config BATTERY_RK30_AC_CHARGE tristate "RK30 AC CHARGE" depends on BATTERY_RK30_ADC||BATTERY_RK30_ADC_FAC diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 88affea3013b..3d81549d5a6d 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -46,3 +46,4 @@ obj-$(CONFIG_BATTERY_RK29_ADC) += rk29_adc_battery.o obj-$(CONFIG_BATTERY_RK30_ADC) += rk30_adc_battery.o obj-$(CONFIG_PLAT_RK) += rk29_charger_display.o obj-$(CONFIG_BATTERY_RK30_ADC_FAC) += rk30_factory_adc_battery.o +obj-$(CONFIG_CW2015_BATTERY) += cw2015_battery.o diff --git a/drivers/power/cw2015_battery.c b/drivers/power/cw2015_battery.c new file mode 100644 index 000000000000..da3dcafc0fa6 --- /dev/null +++ b/drivers/power/cw2015_battery.c @@ -0,0 +1,694 @@ +/* + * RockChip ADC Battery Driver + * Copyright (C) 2012, RockChip + * + * Authors: xuhuicong + * + * This program 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 +#include +#include +#include +#include +#include +#include +#include + +#define REG_VERSION 0x0 +#define REG_VCELL 0x2 +#define REG_SOC 0x4 +#define REG_RRT_ALERT 0x6 +#define REG_CONFIG 0x8 +#define REG_MODE 0xA +#define REG_BATINFO 0x10 + +#define MODE_SLEEP_MASK (0x3<<6) +#define MODE_SLEEP (0x3<<6) +#define MODE_NORMAL (0x0<<6) +#define MODE_QUICK_START (0x3<<4) +#define MODE_RESTART (0xf<<0) + +#define CONFIG_UPDATE_FLG (0x1<<1) +#define ATHD (0xa<<3) //ATHD =10% + +#define CW_I2C_SPEED 100000 // default i2c speed set 100khz + +#define Enable_BATDRV_LOG 0 + +#if Enable_BATDRV_LOG +#define xprintk(format, arg...) printk("func: %s\n" format, __FUNCTION__, ##arg) +#else +#define xprintk(format, ...) +#endif + +struct cw_battery { + struct i2c_client *client; + struct workqueue_struct *battery_workqueue; + struct delayed_work battery_delay_work; + const struct cw_bat_platform_data *plat_data; + + struct power_supply rk_bat; + struct power_supply rk_ac; + struct power_supply rk_usb; + + int dc_online; + int usb_online; + int capacity; + int voltage; + int status; + int time_to_empty; + + int bat_change; +}; + +static int cw_read(struct i2c_client *client, u8 reg, u8 buf[]) +{ + int ret; + ret = i2c_master_reg8_recv(client, reg, buf, 1, CW_I2C_SPEED); + return ret; +} + +static int cw_write(struct i2c_client *client, u8 reg, u8 const buf[]) +{ + int ret; + ret = i2c_master_reg8_send(client, reg, buf, 1, CW_I2C_SPEED); + return ret; +} + +static int cw_update_config_info(struct cw_battery *cw_bat) +{ + int ret; + u8 reg_val; + int i; + + printk("func: %s-------\n", __func__); + /* make sure no in sleep mode */ + ret = cw_read(cw_bat->client, REG_MODE, ®_val); + if (ret < 0) + return ret; + + if((reg_val & MODE_SLEEP_MASK) == MODE_SLEEP) { + printk("Error, device in sleep mode, cannot update battery info\n"); + return -1; + } + + /* update new battery info */ + + for (i = 0; i < SIZE_BATINFO; i++) { + printk("cw_bat->plat_data->cw_bat_config_info[%d] = 0x%x\n", i, \ + cw_bat->plat_data->cw_bat_config_info[i]); + ret = cw_write(cw_bat->client, REG_BATINFO + i, &cw_bat->plat_data->cw_bat_config_info[i]); + + if (ret < 0) + return ret; + } + + /* readback & check */ + for (i = 0; i < SIZE_BATINFO; i++) { + ret = cw_read(cw_bat->client, REG_BATINFO + i, ®_val); + if (reg_val != cw_bat->plat_data->cw_bat_config_info[i]) + return -1; + } + + + /* set alert info */ + ret = cw_read(cw_bat->client, REG_CONFIG, ®_val); + if (ret < 0) + return ret; + + if ((reg_val & 0xf8) != ATHD) { + printk("the new ATHD have not set\n"); + reg_val &= 0x07; // clear ATHD + reg_val |= ATHD; // set ATHD + ret = cw_write(cw_bat->client, REG_CONFIG, ®_val); + if (ret < 0) + return ret; + } + + /* check 2015 for ATHD & update_flag */ + ret = cw_read(cw_bat->client, REG_CONFIG, ®_val); + if (ret < 0) + return ret; + + if (!(reg_val & CONFIG_UPDATE_FLG)) { + printk("update flag for new battery info have not set..\n"); + } + + if ((reg_val & 0xf8) != ATHD) { + printk("the new ATHD have not set..\n"); + } + + /* reset */ + ret = cw_read(cw_bat->client, REG_MODE, ®_val); + if (ret < 0) + return ret; + + reg_val |= MODE_RESTART; + ret = cw_write(cw_bat->client, REG_MODE, ®_val); + if (ret < 0) + return ret; + + msleep(10); + ret = cw_write(cw_bat->client, REG_MODE, ®_val); + if (ret < 0) + return ret; + + return 0; +} + +static int cw_init(struct cw_battery *cw_bat) +{ + int ret; + int i; + u8 reg_val = MODE_SLEEP; +#if 0 + ret = cw_read(cw_bat->client, REG_MODE, ®_val); + if (ret < 0) + return ret; +#endif + if ((reg_val & MODE_SLEEP_MASK) == MODE_SLEEP) { + reg_val = MODE_NORMAL; + ret = cw_write(cw_bat->client, REG_MODE, ®_val); + if (ret < 0) + return ret; + } + + ret = cw_read(cw_bat->client, REG_CONFIG, ®_val); + if (ret < 0) + return ret; + + if ((reg_val & 0xf8) != ATHD) { + printk("the new ATHD have not set\n"); + reg_val &= 0x07; // clear ATHD + reg_val |= ATHD; // set ATHD + ret = cw_write(cw_bat->client, REG_CONFIG, ®_val); + if (ret < 0) + return ret; + } + + ret = cw_read(cw_bat->client, REG_CONFIG, ®_val); + if (ret < 0) + return ret; + + if (!(reg_val & CONFIG_UPDATE_FLG)) { + printk("update flag for new battery info have not set\n"); + ret = cw_update_config_info(cw_bat); + if (ret < 0) + return ret; + } else { + for(i = 0; i < SIZE_BATINFO; i++) { + ret = cw_read(cw_bat->client, (REG_BATINFO + i), ®_val); + if (ret < 0) + return ret; + + if (cw_bat->plat_data->cw_bat_config_info[i] != reg_val) + break; + } + + if (i != SIZE_BATINFO) { + printk("update flag for new battery info have not set\n"); + ret = cw_update_config_info(cw_bat); + if (ret < 0) + return ret; + } + } + + for (i = 0; i < 30; i++) { + ret = cw_read(cw_bat->client, REG_SOC, ®_val); + if (ret < 0) + return ret; + else if (ret != 0xff) + break; + + msleep(100); + if (i > 25) + printk("cw2015 input unvalid power error\n"); + + } + + return 0; +} + + +static int cw_get_capacity(struct cw_battery *cw_bat) +{ + + int ret; + u8 reg_val; + ret = cw_read(cw_bat->client, REG_SOC, ®_val); + if (ret < 0) + return ret; + + return reg_val; + return 0; + +} + +static int cw_get_vol(struct cw_battery *cw_bat) +{ + int ret; + u8 reg_val; + u16 value16; + int voltage; + + ret = cw_read(cw_bat->client, REG_VCELL, ®_val); + if (ret < 0) + return ret; + + value16 = reg_val; + + ret = cw_read(cw_bat->client, REG_VCELL + 1, ®_val); + if (ret < 0) + return ret; + + value16 = (value16 << 8) + value16; + voltage = value16 * 312 / 1024; + + return voltage; + +} + +static int cw_get_time_to_empty(struct cw_battery *cw_bat) +{ + int ret; + u8 reg_val; + u16 value16; + + ret = cw_read(cw_bat->client, REG_RRT_ALERT, ®_val); + if (ret < 0) + return ret; + + value16 = reg_val; + + ret = cw_read(cw_bat->client, REG_RRT_ALERT + 1, ®_val); + if (ret < 0) + return ret; + + value16 = ((value16 << 8) + reg_val) & 0x1fff; + return value16; +} + +static void rk_bat_update_capacity(struct cw_battery *cw_bat) +{ + int ret; + ret = cw_get_capacity(cw_bat); + if ((ret >= 0) && (ret <= 100) && (cw_bat->capacity != ret)) { + cw_bat->capacity = ret; + cw_bat->bat_change = 1; + } +} + +static void rk_bat_update_vol(struct cw_battery *cw_bat) +{ + int ret; + ret = cw_get_vol(cw_bat); + if ((ret >= 0) && (cw_bat->voltage != ret)) { + cw_bat->voltage = ret; + cw_bat->bat_change = 1; + } +} + +static void rk_bat_update_status(struct cw_battery *cw_bat) +{ + int status; + if ((cw_bat->dc_online == 1) || (cw_bat->usb_online == 1)) { + if (cw_bat->capacity >= 100) + status=POWER_SUPPLY_STATUS_FULL; + else + status=POWER_SUPPLY_STATUS_CHARGING; + } else { + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + if (cw_bat->status != status) { + cw_bat->status = status; + cw_bat->bat_change = 1; + } +} + +static void rk_bat_update_time_to_empty(struct cw_battery *cw_bat) +{ + int ret; + ret = cw_get_time_to_empty(cw_bat); + if ((ret >= 0) && (cw_bat->time_to_empty != ret)) { + cw_bat->time_to_empty = ret; + cw_bat->bat_change = 1; + } + +} + +static int rk_ac_update_online(struct cw_battery *cw_bat) +{ + if (gpio_get_value(cw_bat->plat_data->dc_det_pin) == cw_bat->plat_data->dc_det_level) { + if (cw_bat->dc_online != 1) { + cw_bat->dc_online = 1; + return 1; + } + } else { + if (cw_bat->dc_online != 0) { + cw_bat->dc_online = 0; + return 1; + } + } + + return 0; +} + +static int rk_usb_update_online(struct cw_battery *cw_bat) +{ + cw_bat->usb_online = 0; + return 0; +} + +static void cw_bat_work(struct work_struct *work) +{ + struct delayed_work *delay_work; + struct cw_battery *cw_bat; + int ret; + + delay_work = container_of(work, struct delayed_work, work); + cw_bat = container_of(delay_work, struct cw_battery, battery_delay_work); + + rk_bat_update_capacity(cw_bat); + rk_bat_update_vol(cw_bat); + rk_bat_update_status(cw_bat); + rk_bat_update_time_to_empty(cw_bat); + + + xprintk("cw_bat->bat_change = %d, cw_bat->time_to_empty = %d, cw_bat->capacity = %d, cw_bat->voltage = %d\n", cw_bat->bat_change, cw_bat->time_to_empty, cw_bat->capacity, cw_bat->voltage); + if (cw_bat->bat_change) { + power_supply_changed(&cw_bat->rk_bat); + cw_bat->bat_change = 0; + } + + + ret = rk_ac_update_online(cw_bat); + if (ret == 1) { + power_supply_changed(&cw_bat->rk_ac); + } + + ret = rk_usb_update_online(cw_bat); + if (ret == 1) { + power_supply_changed(&cw_bat->rk_usb); + } + + queue_delayed_work(cw_bat->battery_workqueue, &cw_bat->battery_delay_work, msecs_to_jiffies(1000)); +} + +static int rk_usb_get_property (struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct cw_battery *cw_bat; + + cw_bat = container_of(psy, struct cw_battery, rk_usb); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = cw_bat->usb_online; + break; + default: + break; + } + return ret; +} + +static enum power_supply_property rk_usb_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + + +static int rk_ac_get_property (struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct cw_battery *cw_bat; + + cw_bat = container_of(psy, struct cw_battery, rk_ac); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = cw_bat->dc_online; + break; + default: + break; + } + return ret; +} + +static enum power_supply_property rk_ac_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int rk_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct cw_battery *cw_bat; + + cw_bat = container_of(psy, struct cw_battery, rk_bat); + switch (psp) { + + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = cw_bat->capacity; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = cw_bat->status; + break; + + case POWER_SUPPLY_PROP_HEALTH: + val->intval= POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = cw_bat->voltage <= 0 ? 0 : 1; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = cw_bat->voltage; + break; + + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + val->intval = cw_bat->time_to_empty; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + + default: + break; + } + return ret; +} + +static enum power_supply_property rk_battery_properties[] = { + POWER_SUPPLY_PROP_CAPACITY, + +}; + +static int cw_bat_gpio_init(struct cw_battery *cw_bat) +{ + + int ret; + gpio_free(cw_bat->plat_data->dc_det_pin); + if (cw_bat->plat_data->dc_det_pin != INVALID_GPIO) { + ret = gpio_request(cw_bat->plat_data->dc_det_pin, NULL); + if (ret) { + printk("failed to request dc_det_pin gpio\n"); + goto request_dc_det_pin_fail; + } + + gpio_pull_updown(cw_bat->plat_data->dc_det_pin, GPIOPullUp); + ret = gpio_direction_input(cw_bat->plat_data->dc_det_pin); + if (ret) { + printk("failed to set dc_det_pin input\n"); + goto request_bat_low_pin_fail; + } + } + if (cw_bat->plat_data->bat_low_pin != INVALID_GPIO) { + ret = gpio_request(cw_bat->plat_data->bat_low_pin, NULL); + if (ret) { + printk("failed to request bat_low_pin gpio\n"); + goto request_bat_low_pin_fail; + } + + gpio_pull_updown(cw_bat->plat_data->bat_low_pin, GPIOPullUp); + ret = gpio_direction_input(cw_bat->plat_data->bat_low_pin); + if (ret) { + printk("failed to set bat_low_pin input\n"); + goto request_chg_ok_pin_fail; + } + } + if (cw_bat->plat_data->chg_ok_pin != INVALID_GPIO) { + ret = gpio_request(cw_bat->plat_data->chg_ok_pin, NULL); + if (ret) { + printk("failed to request chg_ok_pin gpio\n"); + goto request_chg_ok_pin_fail; + } + + gpio_pull_updown(cw_bat->plat_data->chg_ok_pin, GPIOPullUp); + ret = gpio_direction_input(cw_bat->plat_data->chg_ok_pin); + if (ret) { + printk("failed to set chg_ok_pin input\n"); + gpio_free(cw_bat->plat_data->chg_ok_pin); + goto request_chg_ok_pin_fail; + } + } + return 0; + + +request_chg_ok_pin_fail: + gpio_free(cw_bat->plat_data->bat_low_pin); +request_bat_low_pin_fail: + gpio_free(cw_bat->plat_data->dc_det_pin); +request_dc_det_pin_fail: + return ret; + +} + +static int cw_bat_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct cw_battery *cw_bat; + int ret; + + xprintk(); + + cw_bat = devm_kzalloc(&client->dev, sizeof(*cw_bat), GFP_KERNEL); + if (!cw_bat) { + printk("fail to allocate memory\n"); + return -ENOMEM; + } + + i2c_set_clientdata(client, cw_bat); + cw_bat->plat_data = client->dev.platform_data; + ret = cw_bat_gpio_init(cw_bat); + //if (ret) + // return ret; + + cw_bat->client = client; + ret = cw_init(cw_bat); + if (ret) + return ret; + + cw_bat->rk_bat.name = "rk-bat"; + cw_bat->rk_bat.type = POWER_SUPPLY_TYPE_BATTERY; + cw_bat->rk_bat.properties = rk_battery_properties; + cw_bat->rk_bat.num_properties = ARRAY_SIZE(rk_battery_properties); + cw_bat->rk_bat.get_property = rk_battery_get_property; + ret = power_supply_register(&client->dev, &cw_bat->rk_bat); + if(ret < 0) { + printk("power supply register rk_bat error\n"); + goto rk_bat_register_fail; + } + + cw_bat->rk_ac.name = "rk-ac"; + cw_bat->rk_ac.type = POWER_SUPPLY_TYPE_MAINS; + cw_bat->rk_ac.properties = rk_ac_properties; + cw_bat->rk_ac.num_properties = ARRAY_SIZE(rk_ac_properties); + cw_bat->rk_ac.get_property = rk_ac_get_property; + ret = power_supply_register(&client->dev, &cw_bat->rk_ac); + if(ret < 0) { + printk("power supply register rk_ac error\n"); + goto rk_ac_register_fail; + } + + cw_bat->rk_usb.name = "rk-usb"; + cw_bat->rk_usb.type = POWER_SUPPLY_TYPE_USB; + cw_bat->rk_usb.properties = rk_usb_properties; + cw_bat->rk_usb.num_properties = ARRAY_SIZE(rk_usb_properties); + cw_bat->rk_usb.get_property = rk_usb_get_property; + ret = power_supply_register(&client->dev, &cw_bat->rk_usb); + if(ret < 0) { + printk("power supply register rk_ac error\n"); + goto rk_usb_register_fail; + } + + cw_bat->dc_online = 0; + cw_bat->usb_online = 0; + cw_bat->capacity = 50; + cw_bat->voltage = 0; + cw_bat->status = 0; + cw_bat->time_to_empty = 0; + cw_bat->bat_change = 0; + + cw_bat->battery_workqueue = create_singlethread_workqueue("rk_battery"); + INIT_DELAYED_WORK(&cw_bat->battery_delay_work, cw_bat_work); + queue_delayed_work(cw_bat->battery_workqueue, &cw_bat->battery_delay_work, msecs_to_jiffies(1000)); + + return 0; + +rk_usb_register_fail: + power_supply_unregister(&cw_bat->rk_bat); +rk_ac_register_fail: + power_supply_unregister(&cw_bat->rk_ac); +rk_bat_register_fail: + return ret; +} + +static int cw_bat_remove(struct i2c_client *client) +{ + struct cw_battery *cw_bat = i2c_get_clientdata(client); + xprintk(); + cancel_delayed_work(&cw_bat->battery_delay_work); + return 0; +} + +static int cw_bat_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cw_battery *cw_bat = i2c_get_clientdata(client); + xprintk(); + cancel_delayed_work(&cw_bat->battery_delay_work); + return 0; +} + +static int cw_bat_resume(struct i2c_client *client) +{ + struct cw_battery *cw_bat = i2c_get_clientdata(client); + xprintk(); + queue_delayed_work(cw_bat->battery_workqueue, &cw_bat->battery_delay_work, msecs_to_jiffies(100)); + return 0; +} + +static const struct i2c_device_id cw_id[] = { + { "cw201x", 0 }, +}; +MODULE_DEVICE_TABLE(i2c, cw_id); + + +static struct i2c_driver cw_bat_driver = { + .driver = { + .name = "cw201x", + }, + + .probe = cw_bat_probe, + .remove = cw_bat_remove, + .suspend = cw_bat_suspend, + .resume = cw_bat_resume, + .id_table = cw_id, +}; + +static int __init cw_bat_init(void) +{ + xprintk(); + return i2c_add_driver(&cw_bat_driver); +} + +static void __exit cw_bat_exit(void) +{ + xprintk(); + i2c_del_driver(&cw_bat_driver); +} + +module_init(cw_bat_init); +module_exit(cw_bat_exit); + +MODULE_AUTHOR("xhc"); +MODULE_DESCRIPTION("cw2015 battery driver"); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/power/cw2015_battery.h b/include/linux/power/cw2015_battery.h new file mode 100644 index 000000000000..a3acd2882a65 --- /dev/null +++ b/include/linux/power/cw2015_battery.h @@ -0,0 +1,30 @@ +/* + * RockChip ADC Battery Driver + * Copyright (C) 2012, RockChip + * + * Authors: xuhuicong + * + * Based on rk30_adc_battery.c + + * This program 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. + * + */ + +#ifndef CW2015_BATTERY_H +#define CW2015_BATTERY_H + +#define SIZE_BATINFO 64 + +struct cw_bat_platform_data { + int dc_det_pin; + int dc_det_level; + int bat_low_pin; + int bat_low_level; + int chg_ok_pin; + int chg_ok_level; + u8* cw_bat_config_info; +}; + +#endif -- 2.34.1