本文是给android移植电池信息驱动时的总结,基于freescale imx51平台。有错误之处敬请斧正。后续慢慢更新。
#define BAT_VOLTAGE_UNIT_UV 4692#define BAT_CURRENT_UNIT_UA 5870#define CHG_VOLTAGE_UINT_UV 23474#define CHG_MIN_CURRENT_UA 3500
#define COULOMB_TO_UAH(c) (10000 * c / 36)
enum chg_setting { TRICKLE_CHG_EN, LOW_POWER_BOOT_ACK, BAT_TH_CHECK_DIS, BATTFET_CTL_EN, BATTFET_CTL, REV_MOD_EN, PLIM_DIS, CHG_LED_EN, RESTART_CHG_STAT, AUTO_CHG_DIS, CYCLING_DIS, VI_PROGRAM_EN};
/* Flag used to indicate if Charger workaround is active. */int chg_wa_is_active;/* Flag used to indicate if Charger workaround timer is on. */int chg_wa_timer;int disable_chg_timer;struct workqueue_struct *chg_wq;struct delayed_work chg_work;
static int pmic_set_chg_current(unsigned short curr){ unsigned int mask; unsigned int value;
value = BITFVAL(BIT_CHG_CURR, curr); mask = BITFMASK(BIT_CHG_CURR); CHECK_ERROR(pmic_write_reg(REG_CHARGE, value, mask));
return 0;}
static int pmic_set_chg_misc(enum chg_setting type, unsigned short flag){
unsigned int reg_value = 0; unsigned int mask = 0;
switch (type) { case TRICKLE_CHG_EN: reg_value = BITFVAL(TRICKLE_CHG_EN, flag); mask = BITFMASK(TRICKLE_CHG_EN); break; case LOW_POWER_BOOT_ACK: reg_value = BITFVAL(LOW_POWER_BOOT_ACK, flag); mask = BITFMASK(LOW_POWER_BOOT_ACK); break; case BAT_TH_CHECK_DIS: reg_value = BITFVAL(BAT_TH_CHECK_DIS, flag); mask = BITFMASK(BAT_TH_CHECK_DIS); break; case BATTFET_CTL_EN: reg_value = BITFVAL(BATTFET_CTL_EN, flag); mask = BITFMASK(BATTFET_CTL_EN); break; case BATTFET_CTL: reg_value = BITFVAL(BATTFET_CTL, flag); mask = BITFMASK(BATTFET_CTL); break; case REV_MOD_EN: reg_value = BITFVAL(REV_MOD_EN, flag); mask = BITFMASK(REV_MOD_EN); break; case PLIM_DIS: reg_value = BITFVAL(PLIM_DIS, flag); mask = BITFMASK(PLIM_DIS); break; case CHG_LED_EN: reg_value = BITFVAL(CHG_LED_EN, flag); mask = BITFMASK(CHG_LED_EN); break; case RESTART_CHG_STAT: reg_value = BITFVAL(RESTART_CHG_STAT, flag); mask = BITFMASK(RESTART_CHG_STAT); break; case AUTO_CHG_DIS: reg_value = BITFVAL(AUTO_CHG_DIS, flag); mask = BITFMASK(AUTO_CHG_DIS); break; case CYCLING_DIS: reg_value = BITFVAL(CYCLING_DIS, flag); mask = BITFMASK(CYCLING_DIS); break; case VI_PROGRAM_EN: reg_value = BITFVAL(VI_PROGRAM_EN, flag); mask = BITFMASK(VI_PROGRAM_EN); break; default: return PMIC_PARAMETER_ERROR; }
CHECK_ERROR(pmic_write_reg(REG_CHARGE, reg_value, mask));
return 0;}
static int pmic_get_batt_voltage(unsigned short *voltage){ t_channel channel; unsigned short result[8];
channel = BATTERY_VOLTAGE; CHECK_ERROR(pmic_adc_convert(channel, result)); *voltage = result[0];
return 0;}
static int pmic_get_batt_current(unsigned short *curr){ t_channel channel; unsigned short result[8];
channel = BATTERY_CURRENT; CHECK_ERROR(pmic_adc_convert(channel, result)); *curr = result[0];
return 0;}
static int coulomb_counter_calibration;static unsigned int coulomb_counter_start_time_msecs;
static int pmic_start_coulomb_counter(void){ /* set scaler */ CHECK_ERROR(pmic_write_reg(REG_ACC1, ACC_COULOMB_PER_LSB * ACC_ONEC_VALUE, BITFMASK(ACC1_ONEC)));
CHECK_ERROR(pmic_write_reg( REG_ACC0, ACC_START_COUNTER, ACC_CONTROL_BIT_MASK)); coulomb_counter_start_time_msecs = jiffies_to_msecs(jiffies); pr_debug("coulomb counter start time %u/n", coulomb_counter_start_time_msecs); return 0;}
static int pmic_stop_coulomb_counter(void){ CHECK_ERROR(pmic_write_reg( REG_ACC0, ACC_STOP_COUNTER, ACC_CONTROL_BIT_MASK)); return 0;}
static int pmic_calibrate_coulomb_counter(void){ int ret; unsigned int value;
/* set scaler */ CHECK_ERROR(pmic_write_reg(REG_ACC1, 0x1, BITFMASK(ACC1_ONEC)));
CHECK_ERROR(pmic_write_reg( REG_ACC0, ACC_CALIBRATION, ACC_CONTROL_BIT_MASK)); msleep(ACC_CALIBRATION_DURATION_MSECS);
ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); if (ret != 0) return -1; value = BITFEXT(value, ACC_CCOUT); pr_debug("calibrate value = %x/n", value); coulomb_counter_calibration = (int)((s16)((u16) value)); pr_debug("coulomb_counter_calibration = %d/n", coulomb_counter_calibration);
return 0;
}
static int pmic_get_charger_coulomb(int *coulomb){ int ret; unsigned int value; int calibration; unsigned int time_diff_msec;
ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); if (ret != 0) return -1; value = BITFEXT(value, ACC_CCOUT); pr_debug("counter value = %x/n", value); *coulomb = ((s16)((u16)value)) * ACC_COULOMB_PER_LSB;
if (abs(*coulomb) >= ACC_COULOMB_PER_LSB) { /* calibrate */ time_diff_msec = jiffies_to_msecs(jiffies); time_diff_msec = (time_diff_msec > coulomb_counter_start_time_msecs) ? (time_diff_msec - coulomb_counter_start_time_msecs) : (0xffffffff - coulomb_counter_start_time_msecs + time_diff_msec); calibration = coulomb_counter_calibration * (int)time_diff_msec / (ACC_ONEC_VALUE * ACC_CALIBRATION_DURATION_MSECS); *coulomb -= calibration; }
return 0;}
static int pmic_restart_charging(void){ pmic_set_chg_misc(BAT_TH_CHECK_DIS, 1); pmic_set_chg_misc(AUTO_CHG_DIS, 0); pmic_set_chg_misc(VI_PROGRAM_EN, 1); pmic_set_chg_current(0x8); pmic_set_chg_misc(RESTART_CHG_STAT, 1); pmic_set_chg_misc(PLIM_DIS, 3); return 0;}
struct mc13892_dev_info { struct device *dev;
unsigned short voltage_raw; int voltage_uV; unsigned short current_raw; int current_uA; int battery_status; int full_counter; int charger_online; int charger_voltage_uV; int accum_current_uAh;
struct power_supply bat; struct power_supply charger;
struct workqueue_struct *monitor_wqueue; struct delayed_work monitor_work;};
#define mc13892_SENSER 25#define to_mc13892_dev_info(x) container_of((x), struct mc13892_dev_info, / bat);
static enum power_supply_property mc13892_battery_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_STATUS,};
static enum power_supply_property mc13892_charger_props[] = { POWER_SUPPLY_PROP_ONLINE,};
static int pmic_get_chg_value(unsigned int *value){ t_channel channel; unsigned short result[8], max1 = 0, min1 = 0, max2 = 0, min2 = 0, i; unsigned int average = 0, average1 = 0, average2 = 0;
channel = CHARGE_CURRENT; CHECK_ERROR(pmic_adc_convert(channel, result));
for (i = 0; i < 8; i++) { if ((result[i] & 0x200) != 0) { result[i] = 0x400 - result[i]; average2 += result[i]; if ((max2 == 0) || (max2 < result[i])) max2 = result[i]; if ((min2 == 0) || (min2 > result[i])) min2 = result[i]; } else { average1 += result[i]; if ((max1 == 0) || (max1 < result[i])) max1 = result[i]; if ((min1 == 0) || (min1 > result[i])) min1 = result[i]; } }
if (max1 != 0) { average1 -= max1; if (max2 != 0) average2 -= max2; else average1 -= min1; } else average2 -= max2 + min2;
if (average1 >= average2) { average = (average1 - average2) / 6; *value = average; } else { average = (average2 - average1) / 6; *value = ((~average) + 1) & 0x3FF; }
return 0;}
static void chg_thread(struct work_struct *work){ int ret; unsigned int value = 0; int dets;
if (disable_chg_timer) { disable_chg_timer = 0; pmic_set_chg_current(0x8); queue_delayed_work(chg_wq, &chg_work, 100); chg_wa_timer = 1; return; }
ret = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_DETS));
if (ret == 0) { dets = BITFEXT(value, BIT_CHG_DETS); pr_debug("dets=%d/n", dets);
if (dets == 1) { pmic_get_chg_value(&value); pr_debug("average value=%d/n", value); if ((value <= 3) | ((value & 0x200) != 0)) { pr_debug("%s: Disable the charger/n", __func__); pmic_set_chg_current(0); disable_chg_timer = 1; }
queue_delayed_work(chg_wq, &chg_work, 100); chg_wa_timer = 1; } }}
static int mc13892_charger_update_status(struct mc13892_dev_info *di){ int ret; unsigned int value; int online;
ret = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_DETS));
if (ret == 0) { online = BITFEXT(value, BIT_CHG_DETS); if (online != di->charger_online) { di->charger_online = online; dev_info(di->charger.dev, "charger status: %s/n", online ? "online" : "offline"); power_supply_changed(&di->charger);
cancel_delayed_work(&di->monitor_work); queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ / 10); if (online) { pmic_start_coulomb_counter(); pmic_restart_charging(); queue_delayed_work(chg_wq, &chg_work, 100); chg_wa_timer = 1; } else { cancel_delayed_work(&chg_work); chg_wa_timer = 0; pmic_stop_coulomb_counter(); } } }
return ret;}
static int mc13892_charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val){ struct mc13892_dev_info *di = container_of((psy), struct mc13892_dev_info, charger);
switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = di->charger_online; return 0; default: break; } return -EINVAL;}
static int mc13892_battery_read_status(struct mc13892_dev_info *di){ int retval; int coulomb; retval = pmic_get_batt_voltage(&(di->voltage_raw)); if (retval == 0) di->voltage_uV = di->voltage_raw * BAT_VOLTAGE_UNIT_UV;
retval = pmic_get_batt_current(&(di->current_raw)); if (retval == 0) { if (di->current_raw & 0x200) di->current_uA = (0x1FF - (di->current_raw & 0x1FF)) * BAT_CURRENT_UNIT_UA * (-1); else di->current_uA = (di->current_raw & 0x1FF) * BAT_CURRENT_UNIT_UA; } retval = pmic_get_charger_coulomb(&coulomb); if (retval == 0) di->accum_current_uAh = COULOMB_TO_UAH(coulomb);
return retval;}
static void mc13892_battery_update_status(struct mc13892_dev_info *di){ unsigned int value; int retval; int old_battery_status = di->battery_status;
if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) di->full_counter = 0;
if (di->charger_online) { retval = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_CURRS));
if (retval == 0) { value = BITFEXT(value, BIT_CHG_CURRS); if (value) di->battery_status = POWER_SUPPLY_STATUS_CHARGING; else di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; }
if (di->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING) di->full_counter++; else di->full_counter = 0; } else { di->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; di->full_counter = 0; }
dev_dbg(di->bat.dev, "bat status: %d/n", di->battery_status);
if (old_battery_status != POWER_SUPPLY_STATUS_UNKNOWN && di->battery_status != old_battery_status) power_supply_changed(&di->bat);}
static void mc13892_battery_work(struct work_struct *work){ struct mc13892_dev_info *di = container_of(work, struct mc13892_dev_info, monitor_work.work); const int interval = HZ * 60;
dev_dbg(di->dev, "%s/n", __func__);
mc13892_battery_update_status(di); queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval);}
static void charger_online_event_callback(void *para){ struct mc13892_dev_info *di = (struct mc13892_dev_info *) para; pr_info("/n/n DETECTED charger plug/unplug event/n"); mc13892_charger_update_status(di);}
static int mc13892_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val){ struct mc13892_dev_info *di = to_mc13892_dev_info(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) { mc13892_charger_update_status(di);
mc13892_battery_update_status(di); //在battery_status状态unknown时,执行这两个函数可获得battery_status,赋给di->battery_status
} val->intval = di->battery_status; return 0; default: break; }
mc13892_battery_read_status(di); //更新一次battery信息
switch (psp) { case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = di->voltage_uV; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = di->current_uA; break; case POWER_SUPPLY_PROP_CHARGE_NOW: val->intval = di->accum_current_uAh; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: val->intval = 3800000; break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: //可以在此函数中增加其他power_supply_prop_*的信息 val->intval = 3300000; break; default: return -EINVAL; }
return 0;}
static ssize_t chg_wa_enable_show(struct device *dev, struct device_attribute *attr, char *buf){ if (chg_wa_is_active & chg_wa_timer) return sprintf(buf, "Charger LED workaround timer is on/n"); else return sprintf(buf, "Charger LED workaround timer is off/n");}
static ssize_t chg_wa_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size){ if (strstr(buf, "1") != NULL) { if (chg_wa_is_active) { if (chg_wa_timer) printk(KERN_INFO "Charger timer is already on/n"); else { queue_delayed_work(chg_wq, &chg_work, 100); chg_wa_timer = 1; printk(KERN_INFO "Turned on the timer/n"); } } } else if (strstr(buf, "0") != NULL) { if (chg_wa_is_active) { if (chg_wa_timer) { cancel_delayed_work(&chg_work); chg_wa_timer = 0; printk(KERN_INFO "Turned off charger timer/n"); } else { printk(KERN_INFO "The Charger workaround timer is off/n"); } } }
return size;}
static DEVICE_ATTR(enable, 0644, chg_wa_enable_show, chg_wa_enable_store);
static int pmic_battery_remove(struct platform_device *pdev){ pmic_event_callback_t bat_event_callback; struct mc13892_dev_info *di = platform_get_drvdata(pdev);
bat_event_callback.func = charger_online_event_callback; bat_event_callback.param = (void *) di; pmic_event_unsubscribe(EVENT_CHGDETI, bat_event_callback);
cancel_rearming_delayed_workqueue(di->monitor_wqueue, &di->monitor_work); cancel_rearming_delayed_workqueue(chg_wq, &chg_work); destroy_workqueue(di->monitor_wqueue); destroy_workqueue(chg_wq); chg_wa_timer = 0; chg_wa_is_active = 0; disable_chg_timer = 0; power_supply_unregister(&di->bat); power_supply_unregister(&di->charger);
kfree(di);
return 0;}
static int pmic_battery_probe(struct platform_device *pdev){ int retval = 0; struct mc13892_dev_info *di; pmic_event_callback_t bat_event_callback; pmic_version_t pmic_version;
/* Only apply battery driver for MC13892 V2.0 due to ENGR108085 */ pmic_version = pmic_get_version(); //做版本检测 if (pmic_version.revision < 20) { pr_debug("Battery driver is only applied for MC13892 V2.0/n"); return -1; }
if (machine_is_mx50_arm2()) { pr_debug("mc13892 charger is not used for this platform/n"); return -1; }
di = kzalloc(sizeof(*di), GFP_KERNEL); if (!di) { retval = -ENOMEM; goto di_alloc_failed; }
/*di结构体成员初始化*/
platform_set_drvdata(pdev, di);
di->charger.name = "mc13892_charger"; // 此名字会在sys下反映出来
di->charger.type = POWER_SUPPLY_TYPE_MAINS; di->charger.properties = mc13892_charger_props; di->charger.num_properties = ARRAY_SIZE(mc13892_charger_props); di->charger.get_property = mc13892_charger_get_property; retval = power_supply_register(&pdev->dev, &di->charger); //向系统注册 if (retval) { dev_err(di->dev, "failed to register charger/n"); goto charger_failed; }
/*********************************************************此工作队列(mxc_chg工作)并未被直接运行,这个工作队列的执行* 应依赖于回调函数 charger_online_event_callback()的执行* 在charger_online_event_callback()中执行mc13892_charger_update_status()* 进而执行 queue_delayed_work(),启动此工作队列*************************************************************/ INIT_DELAYED_WORK(&chg_work, chg_thread); chg_wq = create_singlethread_workqueue("mxc_chg"); if (!chg_wq) { retval = -ESRCH; goto workqueue_failed; }
/***************************************************** mc13892_battery_work()的执行是立即的,* 在初始化后便执行queue_delayed_work(),将其延时10s执行* mc13892_battery_work()执行到最后又运行queue_delayed_work()* 通过这种机制实现了一个循环执行********************************************************/ INIT_DELAYED_WORK(&di->monitor_work, mc13892_battery_work); di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); if (!di->monitor_wqueue) { retval = -ESRCH; goto workqueue_failed; } queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 10);
di->dev = &pdev->dev; di->bat.name = "mc13892_bat"; di->bat.type = POWER_SUPPLY_TYPE_BATTERY; di->bat.properties = mc13892_battery_props; di->bat.num_properties = ARRAY_SIZE(mc13892_battery_props); di->bat.get_property = mc13892_battery_get_property; //此函数可获得电池的一些属性信息,根本上调用kobject_uevent反馈给上层 di->bat.use_for_apm = 1;
di->battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
retval = power_supply_register(&pdev->dev, &di->bat); //依然要注册给系统 if (retval) { dev_err(di->dev, "failed to register battery/n"); goto batt_failed; }
/*********************************************************************************************************
* 在pmic_common.c中 pmic_start_event_thread()调用kthread_run()起一个线程 pmic_event_thread_func()
* pmic_event_thread_func()这个线程由while(1)循环构成,永不退出,不断调用 pmic_get_active_events(),
* 这个函数在mc13892.c中实现,通过读mc13892的中断状态寄存器register0®ister3,获得置位的中断数目,
* 然后在线程中调用pmic_event_callback()去调用与相应中断对应的回调函数(注册在一个链表中)
* 即是说:mc13892中断引起事件,内核通过一个不断轮询的线程获得置位的中断,判断是何事件发生,进而调用相应的callback function
***********************************************************************************************************/
bat_event_callback.func = charger_online_event_callback; //注册回调函数 bat_event_callback.param = (void *) di; //回调函数的参数 pmic_event_subscribe(EVENT_CHGDETI, bat_event_callback); //将EVENT_CHGDETI事件的回调函数bat_event_callback注册进系统
/********************************************
* 为kobj指定自己的属性 dev_attr_enable.attr
*********************************************/
retval = sysfs_create_file(&pdev->dev.kobj, &dev_attr_enable.attr);
if (retval) { printk(KERN_ERR "Battery: Unable to register sysdev entry for Battery"); goto workqueue_failed; } chg_wa_is_active = 1; chg_wa_timer = 0; disable_chg_timer = 0;
pmic_stop_coulomb_counter(); pmic_calibrate_coulomb_counter(); goto success;
workqueue_failed: power_supply_unregister(&di->charger);charger_failed: power_supply_unregister(&di->bat);batt_failed: kfree(di);di_alloc_failed:success: dev_dbg(di->dev, "%s battery probed!/n", __func__); return retval;
return 0;}
static struct platform_driver pmic_battery_driver_ldm = { .driver = { .name = "pmic_battery", .bus = &platform_bus_type, }, .probe = pmic_battery_probe, .remove = pmic_battery_remove,};
static int __init pmic_battery_init(void){ pr_debug("PMIC Battery driver loading.../n"); return platform_driver_register(&pmic_battery_driver_ldm);}
static void __exit pmic_battery_exit(void){ platform_driver_unregister(&pmic_battery_driver_ldm); pr_debug("PMIC Battery driver successfully unloaded/n");}
module_init(pmic_battery_init);module_exit(pmic_battery_exit);
MODULE_DESCRIPTION("pmic_battery driver");MODULE_AUTHOR("Freescale Semiconductor, Inc.");MODULE_LICENSE("GPL");