Created
March 10, 2020 14:18
-
-
Save TobleMiner/8f3cf62a993ee3310b0fe14d1d8405af to your computer and use it in GitHub Desktop.
CellWise Android GPL driver example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <linux/module.h> | |
#include <linux/init.h> | |
#include <linux/fs.h> | |
#include <linux/uaccess.h> | |
#include <linux/platform_device.h> | |
#include <linux/power_supply.h> | |
#include <linux/workqueue.h> | |
#include <linux/i2c.h> | |
#include <linux/kernel.h> | |
#include <linux/delay.h> | |
#include <linux/time.h> | |
#include <linux/slab.h> | |
#include <linux/version.h> | |
#define CWFG_ENABLE_LOG 1 //CHANGE Customer need to change this for enable/disable log | |
#define CWFG_I2C_BUSNUM 2 //CHANGE Customer need to change this number according to the principle of hardware | |
#define DOUBLE_SERIES_BATTERY 0 | |
/* | |
#define USB_CHARGING_FILE "/sys/class/power_supply/usb/online" // Chaman | |
#define DC_CHARGING_FILE "/sys/class/power_supply/ac/online" | |
*/ | |
#define CW_PROPERTIES "cw-bat" | |
#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_VTEMPL 0xC | |
#define REG_VTEMPH 0xD | |
#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 (0x0<<3) // ATHD = 0% | |
#define queue_delayed_work_time 8000 | |
#define BATTERY_CAPACITY_ERROR 40*1000 | |
#define BATTERY_CHARGING_ZERO 1800*1000 | |
#define UI_FULL 100 | |
#define DECIMAL_MAX 70 | |
#define DECIMAL_MIN 30 | |
#define CHARGING_ON 1 | |
#define NO_CHARGING 0 | |
#define cw_printk(fmt, arg...) \ | |
({ \ | |
if(CWFG_ENABLE_LOG){ \ | |
printk("FG_CW2015 : %s : " fmt, __FUNCTION__ ,##arg); \ | |
}else{} \ | |
}) //need check by Chaman | |
#define CWFG_NAME "cw2015" | |
#define SIZE_BATINFO 64 | |
static unsigned char config_info[SIZE_BATINFO] = { | |
0x17, 0x67, 0x66, 0x6C, 0x6A, 0x69, 0x64, 0x5E, | |
0x65, 0x6B, 0x4E, 0x52, 0x4F, 0x4F, 0x46, 0x3C, | |
0x35, 0x2B, 0x24, 0x20, 0x21, 0x2F, 0x42, 0x4C, | |
0x24, 0x4A, 0x0B, 0x85, 0x31, 0x51, 0x57, 0x6D, | |
0x77, 0x6B, 0x6A, 0x6F, 0x40, 0x1C, 0x7C, 0x42, | |
0x0F, 0x31, 0x1E, 0x50, 0x86, 0x95, 0x97, 0x27, | |
0x57, 0x73, 0x95, 0xC3, 0x80, 0xD8, 0xFF, 0xCB, | |
0x2F, 0x7D, 0x72, 0xA5, 0xB5, 0xC1, 0x73, 0x09, | |
}; | |
static struct power_supply *chrg_usb_psy; | |
static struct power_supply *chrg_ac_psy; | |
#ifdef CONFIG_PM | |
static struct timespec suspend_time_before; | |
static struct timespec after; | |
static int suspend_resume_mark = 0; | |
#endif | |
struct cw_battery { | |
struct i2c_client *client; | |
struct workqueue_struct *cwfg_workqueue; | |
struct delayed_work battery_delay_work; | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) | |
struct power_supply cw_bat; | |
#else | |
struct power_supply *cw_bat; | |
#endif | |
int charger_mode; | |
int capacity; | |
int voltage; | |
int status; | |
int change; | |
//int alt; | |
}; | |
int g_cw2015_capacity = 0; | |
int g_cw2015_vol = 0; | |
/*Define CW2015 iic read function*/ | |
static int cw_read(struct i2c_client *client, unsigned char reg, unsigned char buf[]) | |
{ | |
int ret = 0; | |
ret = i2c_smbus_read_i2c_block_data( client, reg, 1, buf); | |
return ret; | |
} | |
/*Define CW2015 iic write function*/ | |
static int cw_write(struct i2c_client *client, unsigned char reg, unsigned char const buf[]) | |
{ | |
int ret = 0; | |
ret = i2c_smbus_write_i2c_block_data( client, reg, 1, &buf[0] ); | |
return ret; | |
} | |
/*Define CW2015 iic read word function*/ | |
static int cw_read_word(struct i2c_client *client, unsigned char reg, unsigned char buf[]) | |
{ | |
int ret = 0; | |
ret = i2c_smbus_read_i2c_block_data( client, reg, 2, buf ); | |
return ret; | |
} | |
/*CW2015 update profile function, Often called during initialization*/ | |
int cw_update_config_info(struct cw_battery *cw_bat) | |
{ | |
int ret; | |
unsigned char reg_val; | |
int i; | |
unsigned char reset_val; | |
cw_printk("\n"); | |
cw_printk("[FGADC] test config_info = 0x%x\n",config_info[0]); | |
printk(KERN_INFO "%s\n", __func__); | |
// make sure no in sleep mode | |
ret = cw_read(cw_bat->client, REG_MODE, ®_val); | |
if(ret < 0) { | |
return ret; | |
} | |
reset_val = reg_val; | |
if((reg_val & MODE_SLEEP_MASK) == MODE_SLEEP) { | |
return -1; | |
} | |
// update new battery info | |
for (i = 0; i < SIZE_BATINFO; i++) { | |
printk(KERN_INFO "%X\n", config_info[i]); | |
ret = cw_write(cw_bat->client, REG_BATINFO + i, &config_info[i]); | |
if(ret < 0) | |
return ret; | |
} | |
reg_val = 0x00; | |
reg_val |= CONFIG_UPDATE_FLG; // set UPDATE_FLAG | |
reg_val &= 0x07; // clear ATHD | |
reg_val |= ATHD; // set ATHD | |
ret = cw_write(cw_bat->client, REG_CONFIG, ®_val); | |
if(ret < 0) | |
return ret; | |
msleep(50); | |
// reset | |
reg_val = 0x00; | |
reset_val &= ~(MODE_RESTART); | |
reg_val = reset_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, &reset_val); | |
if(ret < 0) return ret; | |
msleep(100); | |
cw_printk("cw2015 update config success!\n"); | |
return 0; | |
} | |
static int cw_init_data(struct cw_battery *cw_bat) | |
{ | |
unsigned char reg_SOC[2]; | |
int real_SOC = 0; | |
int digit_SOC = 0; | |
int ret; | |
int UI_SOC = 0; | |
ret = cw_read_word(cw_bat->client, REG_SOC, reg_SOC); | |
if (ret < 0) | |
return ret; | |
real_SOC = reg_SOC[0]; | |
digit_SOC = reg_SOC[1]; | |
UI_SOC = ((real_SOC * 256 + digit_SOC) * 100)/ (UI_FULL*256); | |
cw_printk("[%d]: real_SOC = %d, digit_SOC = %d\n", __LINE__, real_SOC, digit_SOC); | |
cw_update_vol(cw_bat); | |
cw_update_charge_status(cw_bat); | |
cw_bat->capacity = UI_SOC; | |
cw_update_status(cw_bat); | |
return 0; | |
} | |
/*CW2015 init function, Often called during initialization*/ | |
static int cw_init(struct cw_battery *cw_bat) | |
{ | |
int ret; | |
int i; | |
unsigned char reg_val = MODE_SLEEP; | |
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) { | |
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)) { | |
cw_printk("update config flg is true, need update config\n"); | |
ret = cw_update_config_info(cw_bat); | |
if (ret < 0) { | |
printk("%s : update config fail\n", __func__); | |
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; | |
printk(KERN_INFO "%X\n", reg_val); | |
if (config_info[i] != reg_val) | |
break; | |
} | |
if (i != SIZE_BATINFO) { | |
cw_printk("config didn't match, need update config\n"); | |
ret = cw_update_config_info(cw_bat); | |
if (ret < 0){ | |
return ret; | |
} | |
} | |
} | |
msleep(10); | |
for (i = 0; i < 30; i++) { | |
ret = cw_read(cw_bat->client, REG_SOC, ®_val); | |
if (ret < 0) | |
return ret; | |
else if (reg_val <= 0x64) | |
break; | |
msleep(120); | |
} | |
if (i >= 30 ){ | |
reg_val = MODE_SLEEP; | |
ret = cw_write(cw_bat->client, REG_MODE, ®_val); | |
cw_printk("cw2015 input unvalid power error, cw2015 join sleep mode\n"); | |
return -1; | |
} | |
cw_printk("cw2015 init success!\n"); | |
return 0; | |
} | |
/*Functions:< check_chrg_usb_psy check_chrg_ac_psy get_chrg_psy get_charge_state > for Get Charger Status from outside*/ | |
static int check_chrg_usb_psy(struct device *dev, void *data) | |
{ | |
struct power_supply *psy = dev_get_drvdata(dev); | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) | |
if (psy->type == POWER_SUPPLY_TYPE_USB) { | |
#else | |
if (psy->desc->type == POWER_SUPPLY_TYPE_USB) { | |
#endif | |
chrg_usb_psy = psy; | |
return 1; | |
} | |
return 0; | |
} | |
static int check_chrg_ac_psy(struct device *dev, void *data) | |
{ | |
struct power_supply *psy = dev_get_drvdata(dev); | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) | |
if (psy->type == POWER_SUPPLY_TYPE_MAINS) { | |
#else | |
if (psy->desc->type == POWER_SUPPLY_TYPE_MAINS) { | |
#endif | |
chrg_ac_psy = psy; | |
return 1; | |
} | |
return 0; | |
} | |
static void get_chrg_psy(void) | |
{ | |
if(!chrg_usb_psy) | |
class_for_each_device(power_supply_class, NULL, NULL, check_chrg_usb_psy); | |
if(!chrg_ac_psy) | |
class_for_each_device(power_supply_class, NULL, NULL, check_chrg_ac_psy); | |
} | |
static int get_charge_state(void) | |
{ | |
union power_supply_propval val; | |
int ret = -ENODEV; | |
int usb_online = 0; | |
int ac_online = 0; | |
if (!chrg_usb_psy || !chrg_ac_psy) | |
get_chrg_psy(); | |
if(chrg_usb_psy) { | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) | |
ret = chrg_usb_psy->get_property(chrg_usb_psy, POWER_SUPPLY_PROP_ONLINE, &val); | |
#else | |
ret = chrg_usb_psy->desc->get_property(chrg_usb_psy, POWER_SUPPLY_PROP_ONLINE, &val); | |
#endif | |
if (!ret) | |
usb_online = val.intval; | |
} | |
if(chrg_ac_psy) { | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) | |
ret = chrg_ac_psy->get_property(chrg_ac_psy, POWER_SUPPLY_PROP_ONLINE, &val); | |
#else | |
ret = chrg_ac_psy->desc->get_property(chrg_ac_psy, POWER_SUPPLY_PROP_ONLINE, &val); | |
#endif | |
if (!ret) | |
ac_online = val.intval; | |
} | |
if(!chrg_usb_psy){ | |
cw_printk("Usb online didn't find\n"); | |
} | |
if(!chrg_ac_psy){ | |
cw_printk("Ac online didn't find\n"); | |
} | |
cw_printk("ac_online = %d usb_online = %d\n", ac_online, usb_online); | |
if(ac_online || usb_online){ | |
return 1; | |
} | |
return 0; | |
} | |
static int cw_por(struct cw_battery *cw_bat) | |
{ | |
int ret; | |
unsigned char reset_val; | |
reset_val = MODE_SLEEP; | |
ret = cw_write(cw_bat->client, REG_MODE, &reset_val); | |
if (ret < 0) | |
return ret; | |
reset_val = MODE_NORMAL; | |
msleep(10); | |
ret = cw_write(cw_bat->client, REG_MODE, &reset_val); | |
if (ret < 0) | |
return ret; | |
ret = cw_init(cw_bat); | |
if (ret) | |
return ret; | |
return 0; | |
} | |
static int cw_get_capacity(struct cw_battery *cw_bat) | |
{ | |
int ui_100 = UI_FULL; | |
int remainder = 0; | |
int real_SOC = 0; | |
int digit_SOC = 0; | |
int UI_SOC = 0; | |
//int cw_capacity; | |
int ret; | |
unsigned char reg_val[2]; | |
//unsigned char reg_0x4f; | |
//unsigned char temp_value; | |
static int reset_loop = 0; | |
static int charging_zero_loop = 0; | |
ret = cw_read_word(cw_bat->client, REG_SOC, reg_val); | |
if (ret < 0) | |
return ret; | |
/* | |
ret = cw_read(cw_bat->client, REG_LAST_FULL_CHARGE, ®_0x4f); | |
if (ret < 0) | |
return ret; | |
*/ | |
real_SOC = reg_val[0]; | |
digit_SOC = reg_val[1]; | |
printk(KERN_INFO "CW2015[%d]: real_SOC = %d, digit_SOC = %d\n", __LINE__, real_SOC, digit_SOC); | |
/*case 1 : avoid IC error, read SOC > 100*/ | |
if ((real_SOC < 0) || (real_SOC > 100)) { | |
cw_printk("Error: real_SOC = %d\n", real_SOC); | |
reset_loop++; | |
if (reset_loop > (BATTERY_CAPACITY_ERROR / queue_delayed_work_time)){ | |
cw_por(cw_bat); | |
reset_loop =0; | |
} | |
return cw_bat->capacity; //cw_capacity Chaman change because I think customer didn't want to get error capacity. | |
}else { | |
reset_loop =0; | |
} | |
/*case 2 : avoid IC error, battery SOC is 0% when long time charging*/ | |
if((cw_bat->charger_mode > 0) &&(real_SOC == 0)) | |
{ | |
charging_zero_loop++; | |
if (charging_zero_loop > BATTERY_CHARGING_ZERO / queue_delayed_work_time) { | |
cw_por(cw_bat); | |
charging_zero_loop = 0; | |
} | |
}else if(charging_zero_loop != 0){ | |
charging_zero_loop = 0; | |
} | |
UI_SOC = ((real_SOC * 256 + digit_SOC) * 100)/ (ui_100*256); | |
remainder = (((real_SOC * 256 + digit_SOC) * 100 * 100) / (ui_100*256)) % 100; | |
cw_printk(KERN_INFO "CW2015[%d]: ui_100 = %d, UI_SOC = %d, remainder = %d\n", __LINE__, ui_100, UI_SOC, remainder); | |
/*case 3 : aviod swing*/ | |
if(UI_SOC >= 100){ | |
printk(KERN_INFO "CW2015[%d]: UI_SOC = %d larger 100!!!!\n", __LINE__, UI_SOC); | |
UI_SOC = 100; | |
}else{ | |
if((remainder > 70 || remainder < 30) && UI_SOC >= cw_bat->capacity - 1 && UI_SOC <= cw_bat->capacity + 1) | |
{ | |
UI_SOC = cw_bat->capacity; | |
printk(KERN_INFO "CW2015[%d]: UI_SOC = %d, cw_bat->capacity = %d\n", __LINE__, UI_SOC, cw_bat->capacity); | |
} | |
} | |
#ifdef CONFIG_PM | |
if(suspend_resume_mark == 1) | |
suspend_resume_mark = 0; | |
#endif | |
return UI_SOC; | |
} | |
/*This function called when get voltage from cw2015*/ | |
static int cw_get_voltage(struct cw_battery *cw_bat) | |
{ | |
int ret; | |
unsigned char reg_val[2]; | |
u16 value16, value16_1, value16_2, value16_3; | |
int voltage; | |
ret = cw_read_word(cw_bat->client, REG_VCELL, reg_val); | |
if(ret < 0) { | |
return ret; | |
} | |
value16 = (reg_val[0] << 8) + reg_val[1]; | |
ret = cw_read_word(cw_bat->client, REG_VCELL, reg_val); | |
if(ret < 0) { | |
return ret; | |
} | |
value16_1 = (reg_val[0] << 8) + reg_val[1]; | |
ret = cw_read_word(cw_bat->client, REG_VCELL, reg_val); | |
if(ret < 0) { | |
return ret; | |
} | |
value16_2 = (reg_val[0] << 8) + reg_val[1]; | |
if(value16 > value16_1) { | |
value16_3 = value16; | |
value16 = value16_1; | |
value16_1 = value16_3; | |
} | |
if(value16_1 > value16_2) { | |
value16_3 =value16_1; | |
value16_1 =value16_2; | |
value16_2 =value16_3; | |
} | |
if(value16 >value16_1) { | |
value16_3 =value16; | |
value16 =value16_1; | |
value16_1 =value16_3; | |
} | |
voltage = value16_1 * 625 / 2048; | |
if(DOUBLE_SERIES_BATTERY) | |
voltage = voltage * 2; | |
return voltage; | |
} | |
static void cw_update_charge_status(struct cw_battery *cw_bat) | |
{ | |
int cw_charger_mode; | |
cw_charger_mode = get_charge_state(); | |
if(cw_bat->charger_mode != cw_charger_mode){ | |
cw_bat->charger_mode = cw_charger_mode; | |
cw_bat->change = 1; | |
} | |
} | |
static void cw_update_capacity(struct cw_battery *cw_bat) | |
{ | |
int cw_capacity; | |
cw_capacity = cw_get_capacity(cw_bat); | |
if ((cw_capacity >= 0) && (cw_capacity <= 100) && (cw_bat->capacity != cw_capacity)) { | |
cw_bat->capacity = cw_capacity; | |
cw_bat->change = 1; | |
} | |
} | |
static void cw_update_vol(struct cw_battery *cw_bat) | |
{ | |
int ret; | |
ret = cw_get_voltage(cw_bat); | |
if ((ret >= 0) && (cw_bat->voltage != ret)) { | |
cw_bat->voltage = ret; | |
cw_bat->change = 1; | |
} | |
} | |
static void cw_update_status(struct cw_battery *cw_bat) | |
{ | |
int status; | |
if (cw_bat->charger_mode > 0) { | |
if (cw_bat->capacity >= 100) | |
status = POWER_SUPPLY_STATUS_FULL; | |
else | |
status = POWER_SUPPLY_STATUS_CHARGING; | |
} else { | |
status = POWER_SUPPLY_STATUS_DISCHARGING; | |
} | |
if (cw_bat->status != status) { | |
cw_bat->status = status; | |
cw_bat->change = 1; | |
} | |
} | |
static void cw_bat_work(struct work_struct *work) | |
{ | |
struct delayed_work *delay_work; | |
struct cw_battery *cw_bat; | |
/*Add for battery swap start*/ | |
int ret; | |
unsigned char reg_val; | |
//u16 value16; | |
int i = 0; | |
/*Add for battery swap end*/ | |
delay_work = container_of(work, struct delayed_work, work); | |
cw_bat = container_of(delay_work, struct cw_battery, battery_delay_work); | |
/*Add for battery swap start*/ | |
ret = cw_read(cw_bat->client, REG_MODE, ®_val); | |
if(ret < 0){ | |
//battery is out , you can send new battery capacity vol here what you want set | |
//for example | |
cw_bat->capacity = 100; | |
cw_bat->voltage = 4200; | |
cw_bat->change = 1; | |
}else{ | |
if((reg_val & MODE_SLEEP_MASK) == MODE_SLEEP){ | |
for(i = 0; i < 5; i++){ | |
if(cw_por(cw_bat) == 0) | |
break; | |
} | |
} | |
cw_update_vol(cw_bat); | |
cw_update_charge_status(cw_bat); | |
cw_update_capacity(cw_bat); | |
cw_update_status(cw_bat); | |
} | |
/*Add for battery swap end*/ | |
cw_printk("charger_mod = %d\n", cw_bat->charger_mode); | |
cw_printk("status = %d\n", cw_bat->status); | |
cw_printk("capacity = %d\n", cw_bat->capacity); | |
cw_printk("voltage = %d\n", cw_bat->voltage); | |
#ifdef CONFIG_PM | |
if(suspend_resume_mark == 1) | |
suspend_resume_mark = 0; | |
#endif | |
#ifdef CW_PROPERTIES | |
if (cw_bat->change == 1){ | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) | |
power_supply_changed(&cw_bat->cw_bat); | |
#else | |
power_supply_changed(cw_bat->cw_bat); | |
#endif | |
cw_bat->change = 0; | |
} | |
#endif | |
g_cw2015_capacity = cw_bat->capacity; | |
g_cw2015_vol = cw_bat->voltage; | |
queue_delayed_work(cw_bat->cwfg_workqueue, &cw_bat->battery_delay_work, msecs_to_jiffies(queue_delayed_work_time)); | |
} | |
#ifdef CW_PROPERTIES | |
static int cw_battery_get_property(struct power_supply *psy, | |
enum power_supply_property psp, | |
union power_supply_propval *val) | |
{ | |
int ret = 0; | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) | |
struct cw_battery *cw_bat; | |
cw_bat = container_of(psy, struct cw_battery, cw_bat); | |
#else | |
struct cw_battery *cw_bat = power_supply_get_drvdata(psy); | |
#endif | |
switch (psp) { | |
case POWER_SUPPLY_PROP_CAPACITY: | |
val->intval = cw_bat->capacity; | |
break; | |
/* | |
case POWER_SUPPLY_PROP_STATUS: //Chaman charger ic will give a real value | |
val->intval = cw_bat->status; | |
break; | |
*/ | |
case POWER_SUPPLY_PROP_HEALTH: //Chaman charger ic will give a real value | |
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 * 1000; | |
break; | |
case POWER_SUPPLY_PROP_TECHNOLOGY: //Chaman this value no need | |
val->intval = POWER_SUPPLY_TECHNOLOGY_LION; | |
break; | |
default: | |
ret = -EINVAL; | |
break; | |
} | |
return ret; | |
} | |
static enum power_supply_property cw_battery_properties[] = { | |
POWER_SUPPLY_PROP_CAPACITY, | |
//POWER_SUPPLY_PROP_STATUS, | |
POWER_SUPPLY_PROP_HEALTH, | |
POWER_SUPPLY_PROP_PRESENT, | |
POWER_SUPPLY_PROP_VOLTAGE_NOW, | |
POWER_SUPPLY_PROP_TECHNOLOGY, | |
}; | |
#endif | |
static int cw2015_probe(struct i2c_client *client, const struct i2c_device_id *id) | |
{ | |
int ret; | |
int loop = 0; | |
struct cw_battery *cw_bat; | |
#ifdef CW_PROPERTIES | |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) | |
struct power_supply_desc *psy_desc; | |
struct power_supply_config psy_cfg = {0}; | |
#endif | |
#endif | |
//struct device *dev; | |
cw_printk("\n"); | |
cw_bat = devm_kzalloc(&client->dev, sizeof(*cw_bat), GFP_KERNEL); | |
if (!cw_bat) { | |
cw_printk("cw_bat create fail!\n"); | |
return -ENOMEM; | |
} | |
i2c_set_clientdata(client, cw_bat); | |
cw_bat->client = client; | |
cw_bat->capacity = 1; | |
cw_bat->voltage = 0; | |
cw_bat->status = 0; | |
cw_bat->charger_mode = NO_CHARGING; | |
cw_bat->change = 0; | |
ret = cw_init(cw_bat); | |
while ((loop++ < 3) && (ret != 0)) { | |
msleep(200); | |
ret = cw_init(cw_bat); | |
} | |
if (ret) { | |
printk("%s : cw2015 init fail!\n", __func__); | |
return ret; | |
} | |
ret = cw_init_data(cw_bat); | |
if (ret) { | |
printk("%s : cw2015 init data fail!\n", __func__); | |
return ret; | |
} | |
#ifdef CW_PROPERTIES | |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) | |
cw_bat->cw_bat.name = CW_PROPERTIES; | |
cw_bat->cw_bat.type = POWER_SUPPLY_TYPE_BATTERY; | |
cw_bat->cw_bat.properties = cw_battery_properties; | |
cw_bat->cw_bat.num_properties = ARRAY_SIZE(cw_battery_properties); | |
cw_bat->cw_bat.get_property = cw_battery_get_property; | |
ret = power_supply_register(&client->dev, &cw_bat->cw_bat); | |
if(ret < 0) { | |
power_supply_unregister(&cw_bat->cw_bat); | |
return ret; | |
} | |
#else | |
psy_desc = devm_kzalloc(&client->dev, sizeof(*psy_desc), GFP_KERNEL); | |
if (!psy_desc) | |
return -ENOMEM; | |
psy_cfg.drv_data = cw_bat; | |
psy_desc->name = CW_PROPERTIES; | |
psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; | |
psy_desc->properties = cw_battery_properties; | |
psy_desc->num_properties = ARRAY_SIZE(cw_battery_properties); | |
psy_desc->get_property = cw_battery_get_property; | |
cw_bat->cw_bat = power_supply_register(&client->dev, psy_desc, &psy_cfg); | |
if(IS_ERR(cw_bat->cw_bat)) { | |
ret = PTR_ERR(cw_bat->cw_bat); | |
printk(KERN_ERR"failed to register battery: %d\n", ret); | |
return ret; | |
} | |
#endif | |
#endif | |
cw_bat->cwfg_workqueue = create_singlethread_workqueue("cwfg_gauge"); | |
INIT_DELAYED_WORK(&cw_bat->battery_delay_work, cw_bat_work); | |
queue_delayed_work(cw_bat->cwfg_workqueue, &cw_bat->battery_delay_work , msecs_to_jiffies(50)); | |
cw_printk("cw2015 driver probe success!\n"); | |
return 0; | |
} | |
/* | |
static int cw2015_detect(struct i2c_client *client, struct i2c_board_info *info) | |
{ | |
cw_printk("\n"); | |
strcpy(info->type, CWFG_NAME); | |
return 0; | |
} | |
*/ | |
#ifdef CONFIG_PM | |
static int cw_bat_suspend(struct device *dev) | |
{ | |
struct i2c_client *client = to_i2c_client(dev); | |
struct cw_battery *cw_bat = i2c_get_clientdata(client); | |
read_persistent_clock(&suspend_time_before); | |
cancel_delayed_work(&cw_bat->battery_delay_work); | |
return 0; | |
} | |
static int cw_bat_resume(struct device *dev) | |
{ | |
struct i2c_client *client = to_i2c_client(dev); | |
struct cw_battery *cw_bat = i2c_get_clientdata(client); | |
suspend_resume_mark = 1; | |
read_persistent_clock(&after); | |
after = timespec_sub(after, suspend_time_before); | |
queue_delayed_work(cw_bat->cwfg_workqueue, &cw_bat->battery_delay_work, msecs_to_jiffies(2)); | |
return 0; | |
} | |
static const struct dev_pm_ops cw_bat_pm_ops = { | |
.suspend = cw_bat_suspend, | |
.resume = cw_bat_resume, | |
}; | |
#endif | |
static int cw2015_remove(struct i2c_client *client) | |
{ | |
cw_printk("\n"); | |
return 0; | |
} | |
static const struct i2c_device_id cw2015_id_table[] = { | |
{CWFG_NAME, 0}, | |
{} | |
}; | |
static struct of_device_id cw2015_match_table[] = { | |
{ .compatible = "cellwise,cw2015", }, | |
{ }, | |
}; | |
static struct i2c_driver cw2015_driver = { | |
.driver = { | |
.name = CWFG_NAME, | |
#ifdef CONFIG_PM | |
.pm = &cw_bat_pm_ops, | |
#endif | |
.owner = THIS_MODULE, | |
.of_match_table = cw2015_match_table, | |
}, | |
.probe = cw2015_probe, | |
.remove = cw2015_remove, | |
//.detect = cw2015_detect, | |
.id_table = cw2015_id_table, | |
}; | |
/* | |
static struct i2c_board_info __initdata fgadc_dev = { | |
I2C_BOARD_INFO(CWFG_NAME, 0x62) | |
}; | |
*/ | |
static int __init cw215_init(void) | |
{ | |
//struct i2c_client *client; | |
///struct i2c_adapter *i2c_adp; | |
cw_printk("\n"); | |
//i2c_register_board_info(CWFG_I2C_BUSNUM, &fgadc_dev, 1); | |
//i2c_adp = i2c_get_adapter(CWFG_I2C_BUSNUM); | |
//client = i2c_new_device(i2c_adp, &fgadc_dev); | |
i2c_add_driver(&cw2015_driver); | |
return 0; | |
} | |
/* | |
//Add to dsti file | |
cw2015@62 { | |
compatible = "cellwise,cw2015"; | |
reg = <0x62>; | |
} | |
*/ | |
static void __exit cw215_exit(void) | |
{ | |
i2c_del_driver(&cw2015_driver); | |
} | |
module_init(cw215_init); | |
module_exit(cw215_exit); | |
MODULE_AUTHOR("Chaman Qi"); | |
MODULE_DESCRIPTION("CW2015 FGADC Device Driver V3.0"); | |
MODULE_LICENSE("GPL"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment