CW2015 linux 锂电池驱动源码

linux 驱动


描述:cw2015驱动源码在实际项目中验证过,可以放心使用,驱动代码功能正常。


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 30ms

    regmap_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 10ms

    regmap_write(cw->rmap, CW2015_REG_MODE, 0x00); //MODE Register (0x0A): 0000 0000 Normal MODE
    mdelay(10); //delay 10ms

    ret = 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, &reg_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, &reg_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;
}
#endif

static 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 end

    UI_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 end

    return 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);
}
#endif

static 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
#endif

    return 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;
        }
    }
#endif

    INIT_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;
}
#endif

static 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");


总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值