MX51平台mc13892电池驱动分析

    技术2022-05-20  55

    本文是给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");


    最新回复(0)