MX51平台mc13892电池驱动分析

本文介绍在freescale imx51平台上移植Android电池信息驱动的过程,包括关键代码解析及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文是给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&register3,获得置位的中断数目,

* 然后在线程中调用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");

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值