描述:cw2015驱动源码在实际项目中验证过,可以放心使用,驱动代码功能正常。
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/gpio.h>#include <linux/acpi.h>
#include <linux/of.h>
#include <linux/of_gpio.h>#ifdef CONFIG_ARCH_SUNXI
#include <linux/sunxi-gpio.h>
#endif#define CW2015_REG_VERSION 0x00
#define CW2015_REG_VCELL0 0x02
#define CW2015_REG_VCELL1 0x03
#define CW2015_REG_SOC0 0x04
#define CW2015_REG_SOC1 0x05
#define CW2015_REG_RRT_ALRT0 0x06
#define CW2015_REG_RRT_ALRT1 0x07
#define CW2015_REG_CONFIG 0x08
#define CW2015_REG_MODE 0x0A#define CW2015_REG_BATINFO 0x10
#define CW2015_MODE_SLEEP (6)
#define CW2015_MODE_QSTRT (4)
#define CW2015_MODE_POR (0)#define CW2015_SLEEP_STAT_MASK 0xC0
#define CW2015_MANUFACTURER "CellWise"
//#define BAT_LOW_INTERRUPT
#define BAT_LOW_INTERRUPT_PIN "cw,irq"#define UI_FULL 100
#define SIZE_BATINFO 64#define UFG 0x2
/* 电池建模信息,客户拿到自己电池匹配的建模信息后请替换 */
//battery model
static unsigned char cw_bat_config_info[SIZE_BATINFO] = {0x14,0xDB,0x6E,0x67,0x64,0x63,0x61,0x60,
0x60,0x5B,0x59,0x54,0x4F,0x4F,0x40,0x30,
0x27,0x24,0x25,0x28,0x33,0x47,0x55,0x5A,
0x4B,0x5B,0x08,0xF6,0x26,0x46,0x55,0x67,
0x70,0x6E,0x6B,0x6D,0x3D,0x1A,0x7F,0x1A,
0x08,0x1C,0x4A,0x80,0x8E,0x90,0x90,0x32,
0x5D,0x84,0x93,0x97,0x80,0x99,0xC4,0xCB,
0x2F,0x00,0x64,0xA5,0xB5,0x22,0x10,0x19
};struct cw2015_device {
struct i2c_client *client;
struct device *dev;
struct power_supply *charger;
struct delayed_work bat_monitor;struct regmap *rmap;
u32 alrt_threshold;
u32 emerg_threshold;
bool warning_on;int voltage_mv;
struct mutex lock; /* protect state data */
};int g_battery_exist_flag = 0;
//unsigned char if_quickstart =0;
unsigned char reset_loop =0, wakeup_loop =0;static int cw2015_hw_init(struct cw2015_device *cw);
static bool cw2015_is_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case CW2015_REG_VERSION:
case CW2015_REG_VCELL0:
case CW2015_REG_VCELL1:
case CW2015_REG_SOC0:
case CW2015_REG_SOC1:
return false;default:
return true;
}
}static const struct regmap_config cw2015_regmap_config = {
.reg_bits = 8,
.val_bits = 8,.max_register = CW2015_REG_MODE,
.cache_type = REGCACHE_NONE,.volatile_reg = cw2015_is_volatile_reg,
};static int cw2015_por(struct cw2015_device *cw)
{
int ret = -1;printk("Reset cw2015!\n");
regmap_write(cw->rmap, CW2015_REG_MODE, 0xA0); //MODE Register (0x0A): 1100 0000 SLEEP MODE
mdelay(30); //delay 30msregmap_write(cw->rmap, CW2015_REG_MODE, 0x00); //MODE Register (0x0A): 0000 0000 Normal MODE
mdelay(10); //delay 10ms
/* reset */
regmap_write(cw->rmap, CW2015_REG_MODE, 0x0f); //MODE Register (0x0A): 0000 1111 reset MODE
mdelay(10); //delay 10msregmap_write(cw->rmap, CW2015_REG_MODE, 0x00); //MODE Register (0x0A): 0000 0000 Normal MODE
mdelay(10); //delay 10msret = cw2015_hw_init(cw);
return ret;
}#ifdef BAT_LOW_INTERRUPT
/*当一次alrt 事件到来时,cw2015 ic会拉低arlt pin产生中断,这时需要对06寄存器的最高bit位清0,才能让cw2015 ic释放alrt pin 下面函数的作用是释放alrt pin*/
static int cw2015_release_alrt_pin(struct cw2015_device *cw)
{
unsigned int reg_val;
int alrt = 0;
regmap_read(cw->rmap, CW2015_REG_RRT_ALRT0, ®_val);
printk("cw2015_irq_handler_thread val=%08x\n", reg_val);
alrt = reg_val & 0x80;
if (alrt)
{
reg_val = reg_val & 0x7f;
regmap_update_bits(cw->rmap, CW2015_REG_RRT_ALRT0, 0x80, reg_val);
}
return alrt;
}/*下面的函数是我写的一个例子,例子函数的作用是更新新的低电告警值为上次的 -1, 比如我们的代码开始的时候设定的低电告警值是10,那当电量降到10后*/
/*主控处理完中断后,我把新的低电告警值9写在了对应的寄存器中。 ATHD是08寄存器的前5个bit*/
static int cw2015_update_athd(struct cw2015_device *cw)
{
//int ret = 0;
int reg_val;
int new_athd = 0;
printk("cw2015_update_athd\n");
regmap_read(cw->rmap, CW2015_REG_CONFIG, ®_val);
new_athd = (reg_val >> 3) - 1;
if(new_athd <= 0){
new_athd = 0;
}
new_athd = new_athd << 3;//"the new ATHD need set"
reg_val &= 0x07; /* clear ATHD */
reg_val |= new_athd; /* set new ATHD */
regmap_write(cw->rmap, CW2015_REG_CONFIG, reg_val);return 0;
}static irqreturn_t cw2015_alrt_irq_handler_thread(int irq, void *private)
{
int ret;
struct cw2015_device *cw = private;
unsigned int val;printk("cw2015_alrt_irq_handler_thread\n");
g_battery_exist_flag = 1;//usb exist,battery not exist, and output
cw2015_release_alrt_pin(cw);
cw2015_update_athd(cw);return IRQ_HANDLED;
}
#endifstatic ssize_t cw2015_show_alrt_threshold(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct cw2015_device *cw = power_supply_get_drvdata(psy);return scnprintf(buf, PAGE_SIZE, "%u\n", cw->alrt_threshold);
}static DEVICE_ATTR(alrt_threshold, 0444, cw2015_show_alrt_threshold, NULL);
static struct attribute *cw2015_battery_attr[] = {
&dev_attr_alrt_threshold.attr,
NULL,
};static const struct attribute_group cw2015_attr_group = {
.attrs = cw2015_battery_attr,
};static unsigned int c2015_get_bat_voltage(struct cw2015_device *cw)
{
unsigned char vol[2];
unsigned int voltage;regmap_bulk_read(cw->rmap, CW2015_REG_VCELL0, vol, 2);
/*
* 14bit ADC, voltage resolution is 305uV
*/
voltage = (((vol[0] & 0x3f) << 8) + vol[1]) * 305 / 1000;
/*pr_info("h8-0x%x l8-0x%x, voltage=%u\n", vol[0], vol[1], voltage);*/cw->voltage_mv = voltage;
//printk("h8-0x%x l8-0x%x, voltage=%u\n", vol[0], vol[1], voltage);return voltage;
}//每次开机电池电量跳跃比较大,通过多次重启IC,找到一个较为准确的值 ===TODO
static unsigned int cw2015_cap_calibration(struct cw2015_device *cw)
{
int counts = 5;
unsigned char cap[counts];
unsigned char cap_avg = 0;
static unsigned char soc[2];
int i;for(i = 0; i < counts; i++){
regmap_bulk_read(cw->rmap, CW2015_REG_SOC0, soc, 2);
cap[i] = soc[0];
cap_avg = (cap_avg + cap[i])/2;
printk("==real_SOC=%d, cap_avg=%d\n", soc[0],cap_avg);
cw2015_por(cw);
mdelay(2500);}
}
static unsigned int c2015_get_bat_rest(struct cw2015_device *cw)
{
unsigned int mode;
unsigned char soc[2];
unsigned int remainder = 0;
unsigned int real_SOC = 0;
unsigned int digit_SOC = 0;
unsigned int UI_SOC = 0;
unsigned char rrt[2];
int ret = 0;regmap_read(cw->rmap, CW2015_REG_MODE, &mode);
mode &= CW2015_SLEEP_STAT_MASK;
mode >>= CW2015_MODE_SLEEP;
//printk("mode=%02x\n",mode);regmap_bulk_read(cw->rmap, CW2015_REG_SOC0, soc, 2);
real_SOC = soc[0];
digit_SOC = soc[1];
//printk("real_SOC=%02x,digit_SOC=%02x\n", real_SOC,digit_SOC);//add start
/*假设ic出现问题,读取电量不在合理值范围内5次,重启ic。如果中间读到正确的值,那么5次的计数器清0,正确显示*/
#if 1
//if ((real_SOC <= 0) || (real_SOC > 100)) {
if ((real_SOC < 0) || (real_SOC > 100)) {
// "get real_SOC error; real_SOC = %d\n"
reset_loop++;
if (reset_loop >5) {
ret = cw2015_por(cw); //por ic
if(ret)
return -1;
reset_loop =0;
}
return real_SOC;
}else {
reset_loop =0;
}
#endif
// 如果电量为0, 而且处于sleep 模式, 则认为此时电池被拔出,只有系统还在工作; 为了插上电池后能重新恢复电量,隔一段时间尝试唤醒。
if(real_SOC == 0 && mode == 0x03)
{
wakeup_loop++;
if (wakeup_loop >2) {
printk("wake up cw2015!\n");
mode &= (~(0x3 << CW2015_MODE_SLEEP));
regmap_write(cw->rmap, CW2015_REG_MODE, mode);
printk("[%s: %d]trace : -------> %s mode=%02x\n", __FILE__, __LINE__, __FUNCTION__,mode);
wakeup_loop=0;
}
return real_SOC;
}else {
wakeup_loop =0;
}
//add endUI_SOC = real_SOC * 256 + digit_SOC;
UI_SOC = ((unsigned long)UI_SOC * 100)/ (UI_FULL * 256);
remainder = real_SOC * 256 + digit_SOC;
remainder = (((unsigned long)remainder * 100 * 100) / (UI_FULL * 256)) % 100;
//printk("UI_SOC=%u,remainder=%u\n", UI_SOC,remainder);regmap_bulk_read(cw->rmap, CW2015_REG_RRT_ALRT0, rrt, 2);
//printk("rrt[0]=%02x,rrt[1]=%02x\n", rrt[0],rrt[1]);//add start
/*aviod swing*/
if(UI_SOC >= 100){
UI_SOC = 100;
}else if ((0 == UI_SOC) && (10 >= remainder)){
UI_SOC = 0;
}
//add endreturn UI_SOC;
}static int cw2015_power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct cw2015_device *cw = power_supply_get_drvdata(psy);switch (psp) {
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = CW2015_MANUFACTURER;
break;case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = "cw2015";
//cw2015_cap_calibration(cw); //for test nicole
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = c2015_get_bat_voltage(cw);
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = c2015_get_bat_rest(cw);
break;
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX:
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
val->intval = cw->alrt_threshold;
break;
default:
return -EINVAL;
}return 0;
}static int cw2015_power_supply_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
return false;
}static enum power_supply_property cw2015_power_supply_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,/* in percents! */
POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX,/* in percents! */
};static const struct power_supply_desc cw2015_power_supply_desc = {
.name = "cw2015_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = cw2015_power_supply_props,
.num_properties = ARRAY_SIZE(cw2015_power_supply_props),
.get_property = cw2015_power_supply_get_property,
.property_is_writeable = cw2015_power_supply_property_is_writeable,
};static char *cw2015_charger_supplied_to[] = {
"main-battery",
};struct power_supply_config cw2015_psy_cfg = {
.supplied_to = cw2015_charger_supplied_to,
.num_supplicants = ARRAY_SIZE(cw2015_charger_supplied_to),
};static int cw2015_power_supply_init(struct cw2015_device *cw)
{
cw2015_psy_cfg.drv_data = cw,
cw->charger = devm_power_supply_register(cw->dev,
&cw2015_power_supply_desc,
&cw2015_psy_cfg);return PTR_ERR_OR_ZERO(cw->charger);
}#ifdef CONFIG_MISC_AXP_LEDS
extern int axp_leds_control(unsigned int idx, unsigned int duty_ns);
static void cw2015_bat_monitor(struct work_struct *work)
{
struct cw2015_device *cw =
container_of(work, struct cw2015_device, bat_monitor.work);
unsigned int rest;
unsigned int duty_ns;power_supply_changed(cw->charger);
rest = c2015_get_bat_rest(cw);
if (rest <= cw->emerg_threshold) {
/* duty:200ms, period:400ms */
duty_ns = 200000000;
axp_leds_control(0, duty_ns);
cw->warning_on = true;
} else if (rest <= cw->alrt_threshold) {
/* duty:500ms, period:1000ms */
duty_ns = 500000000;
axp_leds_control(0, duty_ns);
cw->warning_on = true;
} else if (cw->warning_on) {
axp_leds_control(0, 0);
cw->warning_on = false;
}schedule_delayed_work(&cw->bat_monitor, msecs_to_jiffies(10*1000));
}
#else
static void cw2015_bat_monitor(struct work_struct *work)
{
struct cw2015_device *cw =
container_of(work, struct cw2015_device, bat_monitor.work);
unsigned int rest;//power_supply_changed(cw->charger);
rest = c2015_get_bat_rest(cw);
//schedule_delayed_work(&cw->bat_monitor, msecs_to_jiffies(10*1000));//10s
schedule_delayed_work(&cw->bat_monitor, msecs_to_jiffies(1*1000));//1s 加快拔插电池或的恢复->6s
}
#endif#ifdef BAT_LOW_INTERRUPT
static int cw2015_irq_probe(struct cw2015_device *cw)
{
struct gpio_desc *irq;irq = devm_gpiod_get(cw->dev, BAT_LOW_INTERRUPT_PIN, GPIOD_IN);
if (IS_ERR(irq)) {
dev_err(cw->dev, "Could not probe irq pin.\n");
return PTR_ERR(irq);
}return gpiod_to_irq(irq);
}
#endifstatic int cw2015_fw_probe(struct cw2015_device *cw)
{
int ret;
u32 property;#if 1
ret = device_property_read_u32(cw->dev, "cw,alrt-threshold",
&property);
if (ret < 0)
return ret;
cw->alrt_threshold = property;ret = device_property_read_u32(cw->dev, "cw,emerg-threshold",
&property);
if (ret < 0)
property = 0;
cw->emerg_threshold = property;
#else
cw->alrt_threshold = 4200;//max vat
cw->emerg_threshold = 3200;//min vat
#endifreturn 0;
}static int cw2015_hw_init(struct cw2015_device *cw)
{
unsigned int mode, value;
unsigned char i;
unsigned int reg_val;regmap_read(cw->rmap, CW2015_REG_MODE, &mode);
mode &= (~(0x3 << CW2015_MODE_SLEEP));mode &= (~(0x3 << CW2015_MODE_QSTRT));
//mode |= (0x3 << CW2015_MODE_QSTRT);
regmap_write(cw->rmap, CW2015_REG_MODE, mode);#if 1
/* update new battery info */
for (i = 0; i < SIZE_BATINFO; i++)
{
reg_val = cw_bat_config_info[i];
regmap_write(cw->rmap, CW2015_REG_BATINFO + i, reg_val);
}
#endif
regmap_write(cw->rmap, CW2015_REG_MODE, 0xf);//MODE_RESTART;
mdelay(10); //delay 10ms
regmap_write(cw->rmap, CW2015_REG_MODE, 0x0);//MODE_NORMAL
mdelay(1); //delay 1ms
printk("[%s: %d]trace : -------> %s mode=%02x\n", __FILE__, __LINE__, __FUNCTION__,mode);
regmap_read(cw->rmap, CW2015_REG_CONFIG, &value);
//regmap_update_bits(cw->rmap, CW2015_REG_CONFIG,0xf8, (cw->alrt_threshold << 3));
regmap_update_bits(cw->rmap, CW2015_REG_CONFIG,0xf8, 0);
regmap_update_bits(cw->rmap, CW2015_REG_CONFIG,0x2, UFG);return 0;
}static int cw2015_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct device *dev = &client->dev;
struct cw2015_device *cw;
int ret = 0;
printk("======cw2015_probe===\n");
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
return -ENODEV;
}cw = devm_kzalloc(dev, sizeof(*cw), GFP_KERNEL);
if (!cw)
return -ENOMEM;cw->client = client;
cw->dev = dev;mutex_init(&cw->lock);
cw->rmap = devm_regmap_init_i2c(client, &cw2015_regmap_config);
if (IS_ERR(cw->rmap)) {
dev_err(dev, "failed to allocate register map\n");
return PTR_ERR(cw->rmap);
}i2c_set_clientdata(client, cw);
ret = cw2015_fw_probe(cw);
if (ret < 0) {
dev_err(dev, "Cannot read device properties.\n");
return ret;
}ret = cw2015_hw_init(cw);
if (ret < 0) {
dev_err(dev, "hw init failed.\n");
return ret;
}ret = cw2015_power_supply_init(cw);
if (ret < 0) {
dev_err(dev, "Failed to register power supply\n");
return ret;
}
#ifdef BAT_LOW_INTERRUPT
client->irq = cw2015_irq_probe(cw);
printk("cw2015 irq num=%d\n",client->irq);
if (client->irq > 0) {
ret = devm_request_threaded_irq(dev, client->irq, NULL,
cw2015_alrt_irq_handler_thread,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT | IRQF_SHARED,
"cw2015", cw);
if (ret) {
dev_err(dev, "request IRQ #%d failed\n", client->irq);
return ret;
}
}
#endifINIT_DELAYED_WORK(&cw->bat_monitor, cw2015_bat_monitor);
schedule_delayed_work(&cw->bat_monitor, msecs_to_jiffies(2 * 1000));ret = sysfs_create_group(&cw->charger->dev.kobj, &cw2015_attr_group);
if (ret < 0) {
dev_err(dev, "Can't create sysfs entries\n");
return ret;
}
printk("[%s: %d]trace : -------> %s\n", __FILE__, __LINE__, __FUNCTION__);
return 0;
}static int cw2015_remove(struct i2c_client *client)
{
struct cw2015_device *cw = i2c_get_clientdata(client);
sysfs_remove_group(&cw->charger->dev.kobj, &cw2015_attr_group);return 0;
}#ifdef CONFIG_PM_SLEEP
static int cw2015_suspend(struct device *dev)
{
struct cw2015_device *cw = dev_get_drvdata(dev);
unsigned int mode;regmap_read(cw->rmap, CW2015_REG_MODE, &mode);
mode &= (~(0x3 << CW2015_MODE_QSTRT));
mode |= (0x3 << CW2015_MODE_SLEEP);
regmap_write(cw->rmap, CW2015_REG_MODE, mode);
printk("[%s: %d]trace : -------> %s mode=%02x\n", __FILE__, __LINE__, __FUNCTION__,mode);
return 0;
}static int cw2015_resume(struct device *dev)
{
struct cw2015_device *cw = dev_get_drvdata(dev);
unsigned int mode;regmap_read(cw->rmap, CW2015_REG_MODE, &mode);
mode &= (~(0x3 << CW2015_MODE_SLEEP));
mode |= (0x3 << CW2015_MODE_QSTRT);
regmap_write(cw->rmap, CW2015_REG_MODE, mode);
printk("[%s: %d]trace : -------> %s mode=%02x\n", __FILE__, __LINE__, __FUNCTION__,mode);
/* signal userspace, maybe state changed while suspended */
power_supply_changed(cw->charger);return 0;
}
#endifstatic const struct dev_pm_ops cw2015_pm = {
SET_SYSTEM_SLEEP_PM_OPS(cw2015_suspend, cw2015_resume)
};static const struct i2c_device_id cw2015_i2c_ids[] = {
{ "cw2015", 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, cw2015_i2c_ids);static const struct of_device_id cw2015_of_match[] = {
{ .compatible = "cellwise,cw2015", },
{ },
};
MODULE_DEVICE_TABLE(of, cw2015_of_match);static struct i2c_driver cw2015_driver = {
.driver = {
.name = "cw2015-battery",
.of_match_table = of_match_ptr(cw2015_of_match),
.pm = &cw2015_pm,
},
.probe = cw2015_probe,
.remove = cw2015_remove,
.id_table = cw2015_i2c_ids,
};
module_i2c_driver(cw2015_driver);MODULE_AUTHOR("ForeverCai <caiyongheng@allwinnertech.com>");
MODULE_DESCRIPTION("cw2015 charger driver");
MODULE_LICENSE("GPL");