前段時間比較煩躁,各種不想學習不想工作,於是休息了幾天。這幾天又下來任務了--調試充電電路和電池電量檢測電路,於是又開始工作,順便把調試過程記錄下來。
平台: cpu 飛思卡爾imx6q 4核
充電芯片 MAX8903
電量檢測芯片 MAX11801
android版本 android4.0
一、電量檢測
我們用的電池電量檢測芯片MAX11801其實是一款電阻觸摸屏的驅動芯片,它外帶一個AD采集引腳,因此我們用這個引腳來檢測電池電壓。MAX11801電源為3.3V而電池電壓范圍可能是0~4.2V,因此我們需要給電池電壓分壓。我們所用的電路如下
知道了硬件電路下面來 添加這個芯片的驅動,這是一個i2c的芯片,因此首先在board文件中添加i2c設備
I2C_BOARD_INFO("max11801", 0x48), .platform_data = (void *)&max11801_mode, .irq = gpio_to_irq(SABRESD_TS_INT), },
然后添加這個芯片的驅動文件放在/drivers/input/touchiscreen/max11801_ts.c
對於這個驅動文件我們只要讀取出AD的值就可以了,對於觸摸屏部分我們並不需要,因此主要是下面幾個函數
static u32 max11801_dcm_sample_aux(struct i2c_client *client) { u8 temp_buf; int ret; int aux = 0; u32 sample_data = 0; /* AUX_measurement*/ max11801_dcm_write_command(client, AUX_measurement);//發送AD采集命令 mdelay(5); ret = i2c_smbus_read_i2c_block_data(client, FIFO_RD_AUX_MSB, //讀取高字節數據 1, &temp_buf); if (ret < 1) printk(KERN_DEBUG "FIFO_RD_AUX_MSB read fails\n"); else aux_buf[0] = temp_buf; mdelay(5); ret = i2c_smbus_read_i2c_block_data(client, FIFO_RD_AUX_LSB, //讀取低字節數據 1, &temp_buf); if (ret < 1) printk(KERN_DEBUG "FIFO_RD_AUX_LSB read fails\n"); else aux_buf[1] = temp_buf; aux = (aux_buf[0] << 4) + //視最低4位無效並去掉 (aux_buf[1] >> 4); /* 10k和18.7k並聯后電阻 R=18.7*10/(18.7+10)=6.516 V(aux) = V(bat)*6.516/(6.516+18.7) V(aux) = aux*3300/0xfff V(bat) = aux*1386880/444717 */ sample_data = (aux*1386880)/444717; //計算出電池電壓 return sample_data; } u32 max11801_read_adc(void) { u32 adc_data; adc_data = max11801_dcm_sample_aux(max11801_client); // printk("----%s %d\n",__func__,adc_data); //lijianzhang return adc_data; } EXPORT_SYMBOL_GPL(max11801_read_adc);
由於電池電量檢測的驅動非常簡單,而且和充電驅動關系非常密切,因此一般都卸載充電驅動里面,我們也是這么做的。下面的代碼都是從充電驅動中摘出來的,因此當大家看到,一些設備文件和函數參數類型 都是充電驅動中的 時候不要太奇怪。
通過上面的max11801_read_adc函數我們已經得到了理論計算的電池的電壓,但實際應用中由於分壓電阻誤差,焊接問題等,這個電壓會有一定的誤差因此需要一個校正函數
u32 calibration_voltage(struct max8903_data *data) { int volt[ADC_SAMPLE_COUNT]; u32 voltage_data; int i; for (i = 0; i < ADC_SAMPLE_COUNT; i++) { //多次采樣,防止AD誤差 if (data->charger_online == 0 && data->usb_charger_online == 0) { /* ADC offset when battery is discharger*/ volt[i] = max11801_read_adc()-offset_discharger; //沒有充電情況下 電壓誤差 } else { if (data->charger_online == 1) volt[i] = max11801_read_adc()-offset_charger;//DC充電式 電壓誤差 else if (data->usb_charger_online == 1) volt[i] = max11801_read_adc()-offset_usb_charger;//usb充電 電壓誤差 else if (data->charger_online == 1 && data->usb_charger_online == 1) volt[i] = max11801_read_adc()-offset_charger; } } sort(volt, i, 4, cmp_func, NULL);//對電壓排序 for (i = 0; i < ADC_SAMPLE_COUNT; i++) pr_debug("volt_sorted[%2d]: %d\n", i, volt[i]); /* get the average of second max/min of remained. */ voltage_data = (volt[2] + volt[ADC_SAMPLE_COUNT - 3]) / 2;//去掉最大值最小值 並對剩余數據求平均 return voltage_data; }
從上面函數我們讀取到了正確的電壓值。電池電壓是隨時變化的,我們要檢測電池電量,必須隨時采集,因此用一個定時器來做這件事情,代碼如下:
INIT_DELAYED_WORK(&data->work, max8903_battery_work); schedule_delayed_work(&data->work, data->interval);
電壓采集完成后就是將電壓上報出去,上報的過程是:我們讀取到電壓變化->告訴android端電池電壓變化了->android會通過power_supply設備文件來讀取具體的電壓值。
我們來看定時器回調函數
static void max8903_battery_work(struct work_struct *work) { struct max8903_data *data; data = container_of(work, struct max8903_data, work.work); data->interval = HZ * BATTERY_UPDATE_INTERVAL; max8903_charger_update_status(data); //檢測充電狀態 max8903_battery_update_status(data); //檢測電池狀態 /* reschedule for the next time */ schedule_delayed_work(&data->work, data->interval);//定時器繼續 }
檢測電池狀態函數
static void max8903_battery_update_status(struct max8903_data *data) { int temp; static int temp_last; bool changed_flag; changed_flag = false; mutex_lock(&data->work_lock); temp = calibration_voltage(data); if (temp_last == 0) { data->voltage_uV = temp; temp_last = temp; } if (data->charger_online == 0 && temp_last != 0) {//DC充電狀態 if (temp < temp_last) { temp_last = temp; data->voltage_uV = temp; } else { data->voltage_uV = temp_last; } } if (data->charger_online == 1 || data->usb_charger_online == 1) {//USB充電狀態和DC充電狀態 data->voltage_uV = temp; temp_last = temp; } data->percent = calibrate_battery_capability_percent(data);//計算電量的百分比 if (data->percent != data->old_percent) { //電池電壓有變化 data->old_percent = data->percent; changed_flag = true; } if (changed_flag) { //如果有變化 changed_flag = false; power_supply_changed(&data->bat);//告訴android端 電池電量改變了 } /* because boot time gap between led framwork and charger framwork,when system boots with charger attatched, charger led framwork loses the first charger online event,add once extra power_supply_changed can fix this issure */ if (data->first_delay_count < 200) { data->first_delay_count = data->first_delay_count + 1 ; power_supply_changed(&data->bat); } mutex_unlock(&data->work_lock); }
這里我們看到了 power_supply_changed(&data->bat);告訴android端 電池電量改變了,那么下一步android來讀取具體電壓,就涉及到了power_supply設備文件。
來看設備文件的建立過程
data->bat.name = "max8903-charger"; data->bat.type = POWER_SUPPLY_TYPE_BATTERY; data->bat.properties = max8903_battery_props; data->bat.num_properties = ARRAY_SIZE(max8903_battery_props); data->bat.get_property = max8903_battery_get_property; data->bat.use_for_apm = 1; retval = power_supply_register(&pdev->dev, &data->bat);//注冊設備文件 if (retval) { dev_err(data->dev, "failed to register battery\n"); goto battery_failed; }
這里注冊了一個名為max8903-charger的 power_supply設備文件,這個設備文件包含了ARRAY_SIZE(max8903_battery_props)個操作分別為
static enum power_supply_property max8903_battery_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW,//當前電壓 POWER_SUPPLY_PROP_STATUS, //當前充電狀態 POWER_SUPPLY_PROP_PRESENT, //不太清除 POWER_SUPPLY_PROP_CAPACITY, //電量百分比 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,//電池極限電壓 最大值 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,//電池極限電壓 最小值 POWER_SUPPLY_PROP_HEALTH, //電池健康狀態 POWER_SUPPLY_PROP_CAPACITY_LEVEL,//電量水平,low或者normal };
這些狀態是通過max8903_battery_get_property()這個函數來讀取的
static int max8903_battery_get_property(struct power_supply *bat, enum power_supply_property psp, union power_supply_propval *val) { struct max8903_data *di = container_of(bat, struct max8903_data, bat); switch (psp) { case POWER_SUPPLY_PROP_STATUS: val->intval = POWER_SUPPLY_STATUS_UNKNOWN; if (gpio_get_value(di->pdata->chg) == 0) { di->battery_status = POWER_SUPPLY_STATUS_CHARGING; //正在充電 } else if (di->ta_in && gpio_get_value(di->pdata->chg) == 1) { if (di->percent >= 99) di->battery_status = POWER_SUPPLY_STATUS_FULL;//電量大於99就充滿了 else di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; } else if (di->usb_in && gpio_get_value(di->pdata->chg) == 1) { if (di->percent >= 99) di->battery_status = POWER_SUPPLY_STATUS_FULL; else di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING; } val->intval = di->battery_status; return 0; default: break; } switch (psp) { case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = di->voltage_uV; break; case POWER_SUPPLY_PROP_CHARGE_NOW: val->intval = 0; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: val->intval = HIGH_VOLT_THRESHOLD; break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: val->intval = LOW_VOLT_THRESHOLD; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = di->percent < 0 ? 0 : (di->percent > 100 ? 100 : di->percent); break; case POWER_SUPPLY_PROP_HEALTH: val->intval = POWER_SUPPLY_HEALTH_GOOD; if (di->fault) val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; break; case POWER_SUPPLY_PROP_CAPACITY_LEVEL: if (di->battery_status == POWER_SUPPLY_STATUS_FULL) val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; else if (di->percent <= 15) val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;//電量小於15%就報低電量 else val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;//否則就報正常 break; default: return -EINVAL; } return 0; }
當我們注冊設備文件以后,可以在/sys/devices/platform/max8903-charger.1/power_supply/max8903-charger目錄下找到其設備文件如下
我們通過cat命令就可以隨時查看電池狀態。
二、電池電壓校正參數
上面我們知道根據硬件實際情況不同,AD采集出來的電池電壓需要校正參數。也就是
static int offset_discharger;
static int offset_charger;
static int offset_usb_charger;
對於這三個參數,當然我們可以在驅動力寫死,但是為了以后的兼容性我們可以通過android上層來設置,當我們設備出廠時候,通過一配置文件方便的來修改這三個參數,下面我們就來介紹一下,怎么用設備文件和腳本,來修改者三個參數:
我們用的是sys文件系統的設備文件,創建代碼為
ret = device_create_file(&pdev->dev, &max8903_discharger_dev_attr); if (ret) dev_err(&pdev->dev, "create device file failed!\n"); ret = device_create_file(&pdev->dev, &max8903_charger_dev_attr); if (ret) dev_err(&pdev->dev, "create device file failed!\n"); ret = device_create_file(&pdev->dev, &max8903_usb_charger_dev_attr); if (ret) dev_err(&pdev->dev, "create device file failed!\n");
設備文件的實現代碼為
static ssize_t max8903_voltage_offset_discharger_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "read offset_discharger:%04d\n", offset_discharger); } static ssize_t max8903_voltage_offset_discharger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { offset_discharger = simple_strtoul(buf, NULL, 10); pr_info("read offset_discharger:%04d\n", offset_discharger); return count; } static ssize_t max8903_voltage_offset_charger_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "read offset_charger:%04d\n", offset_charger); } static ssize_t max8903_voltage_offset_charger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { offset_charger = simple_strtoul(buf, NULL, 10); pr_info("read offset_charger:%04d\n", offset_charger); return count; } static ssize_t max8903_voltage_offset_usb_charger_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "read offset_usb_charger:%04d\n", offset_usb_charger); } static ssize_t max8903_voltage_offset_usb_charger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { offset_usb_charger = simple_strtoul(buf, NULL, 10); pr_info("read offset_charger:%04d\n", offset_usb_charger); return count; } static struct device_attribute max8903_discharger_dev_attr = { .attr = { .name = "max8903_ctl_offset_discharger", .mode = S_IRUSR | S_IWUSR, }, .show = max8903_voltage_offset_discharger_show, .store = max8903_voltage_offset_discharger_store, }; static struct device_attribute max8903_charger_dev_attr = { .attr = { .name = "max8903_ctl_offset_charger", .mode = S_IRUSR | S_IWUSR, }, .show = max8903_voltage_offset_charger_show, .store = max8903_voltage_offset_charger_store, }; static struct device_attribute max8903_usb_charger_dev_attr = { .attr = { .name = "max8903_ctl_offset_usb_charger", .mode = S_IRUSR | S_IWUSR, }, .show = max8903_voltage_offset_usb_charger_show, .store = max8903_voltage_offset_usb_charger_store, };
這樣,我們就可以在/sys/devices/platform/max8903-charger.1目錄下看到這樣三個設備文件
我們用cat命令可以讀出當前值,用echo "500">>max8903_ctl_offset_charger 可以修改當前值
這樣我們就可以在系統啟動的時候,用腳本來自動修改者三個值,我用的辦法是在init.rc的on boot階段增加這么三行
#battery charge write /sys/devices/platform/max8903-charger.1/max8903_ctl_offset_charger 150 write /sys/devices/platform/max8903-charger.1/max8903_ctl_offset_discharger 200 write /sys/devices/platform/max8903-charger.1/max8903_ctl_offset_usb_charger 250
當然大家也可以把這三行命令寫在另外一個腳本里,然后init.rc中調用
三、電池充電
電池充電的電路
一共有4個引腳輸出到cpu中:
CHG_FLT1_B 電池檢測錯誤
UOK_B usb插入
DOK_BDC插入
CHG_STATUS1_B 充電狀態
對於充電狀態的檢測過程,和電量檢測基本相同, 檢測到狀態變化->告訴android層發生變化->android層通過設備文件來讀取變化值
知道了這些我們來看驅動,首先在board文件中添加max8903設備
static struct max8903_pdata charger1_data = { .dok = SABRESD_CHARGE_DOK_B, .uok = SABRESD_CHARGE_UOK_B, .chg = CHARGE_STATE2, .flt = CHARGE_STATE1, .dcm_always_high = true, .dc_valid = true, .usb_valid = true, }; static struct platform_device sabresd_max8903_charger_1 = { .name = "max8903-charger", .id = 1, .dev = { .platform_data = &charger1_data, }, };
platform_device_register(&sabresd_max8903_charger_1);
然后在/derivers/power/目錄下添加驅動文件。充電狀態的變化都是IO電平的變化,我們來看驅動是怎么處理這4個io的,首先在probe函數中
申請IO
if (pdata->dc_valid) { if (pdata->dok && gpio_is_valid(pdata->dok)) { gpio = pdata->dok; /* PULL_UPed Interrupt */ /* set DOK gpio input */ ret = gpio_request(gpio, "max8903-DOK"); if (ret) { printk(KERN_ERR"request max8903-DOK error!!\n"); goto err; } else { gpio_direction_input(gpio); } ta_in = gpio_get_value(gpio) ? 0 : 1; } else if (pdata->dok && gpio_is_valid(pdata->dok) && pdata->dcm_always_high) { ta_in = pdata->dok; /* PULL_UPed Interrupt */ ta_in = gpio_get_value(gpio) ? 0 : 1; } else { dev_err(dev, "When DC is wired, DOK and DCM should" " be wired as well." " or set dcm always high\n"); ret = -EINVAL; goto err; } } if (pdata->usb_valid) { if (pdata->uok && gpio_is_valid(pdata->uok)) { gpio = pdata->uok; /* set UOK gpio input */ ret = gpio_request(gpio, "max8903-UOK"); if (ret) { printk(KERN_ERR"request max8903-UOK error!!\n"); goto err; } else { gpio_direction_input(gpio); } usb_in = gpio_get_value(gpio) ? 0 : 1; } else { dev_err(dev, "When USB is wired, UOK should be wired." "as well.\n"); ret = -EINVAL; goto err; } } if (pdata->chg) { if (!gpio_is_valid(pdata->chg)) { dev_err(dev, "Invalid pin: chg.\n"); ret = -EINVAL; goto err; } /* set CHG gpio input */ ret = gpio_request(pdata->chg, "max8903-CHG"); if (ret) { printk(KERN_ERR"request max8903-CHG error!!\n"); goto err; } else { gpio_direction_input(pdata->chg); } } if (pdata->flt) { if (!gpio_is_valid(pdata->flt)) { dev_err(dev, "Invalid pin: flt.\n"); ret = -EINVAL; goto err; } /* set FLT gpio input */ ret = gpio_request(pdata->flt, "max8903-FLT"); if (ret) { printk(KERN_ERR"request max8903-FLT error!!\n"); goto err; } else { gpio_direction_input(pdata->flt); } } if (pdata->usus) { if (!gpio_is_valid(pdata->usus)) { dev_err(dev, "Invalid pin: usus.\n"); ret = -EINVAL; goto err; } }
注冊DC充電的設備文件
mutex_init(&data->work_lock); data->fault = false; data->ta_in = ta_in; data->usb_in = usb_in; data->psy.name = "max8903-ac"; data->psy.type = POWER_SUPPLY_TYPE_MAINS; data->psy.get_property = max8903_get_property; data->psy.properties = max8903_charger_props; data->psy.num_properties = ARRAY_SIZE(max8903_charger_props); ret = power_supply_register(dev, &data->psy); if (ret) { dev_err(dev, "failed: power supply register.\n"); goto err_psy; }
注冊USB充電的設備文件
data->usb.name = "max8903-usb"; data->usb.type = POWER_SUPPLY_TYPE_USB; data->usb.get_property = max8903_get_usb_property; data->usb.properties = max8903_charger_props; data->usb.num_properties = ARRAY_SIZE(max8903_charger_props); ret = power_supply_register(dev, &data->usb); if (ret) { dev_err(dev, "failed: power supply register.\n"); goto err_psy; }
這兩個設備文件都只有一個操作:檢測充電器是否在線
static enum power_supply_property max8903_charger_props[] = { POWER_SUPPLY_PROP_ONLINE, };
操作函數也很簡單
static int max8903_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct max8903_data *data = container_of(psy, struct max8903_data, psy); switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = 0; if (data->ta_in) val->intval = 1; data->charger_online = val->intval; break; default: return -EINVAL; } return 0; } static int max8903_get_usb_property(struct power_supply *usb, enum power_supply_property psp, union power_supply_propval *val) { struct max8903_data *data = container_of(usb, struct max8903_data, usb); switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = 0; if (data->usb_in) val->intval = 1; data->usb_charger_online = val->intval; break; default: return -EINVAL; } return 0; }
我們可以通過/sys/devices/platform/max8903-charger.1/power_supply/max8903-ac 目錄和/sys/devices/platform/max8903-charger.1/power_supply/max8903-usb目錄下的設備文件來訪問充電器的狀態
接下來是IO中斷
if (pdata->dc_valid) { ret = request_threaded_irq(gpio_to_irq(pdata->dok), NULL, max8903_dcin, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "MAX8903 DC IN", data); if (ret) { dev_err(dev, "Cannot request irq %d for DC (%d)\n", gpio_to_irq(pdata->dok), ret); goto err_usb_irq; } } if (pdata->usb_valid) { ret = request_threaded_irq(gpio_to_irq(pdata->uok), NULL, max8903_usbin, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "MAX8903 USB IN", data); if (ret) { dev_err(dev, "Cannot request irq %d for USB (%d)\n", gpio_to_irq(pdata->uok), ret); goto err_dc_irq; } } if (pdata->flt) { ret = request_threaded_irq(gpio_to_irq(pdata->flt), NULL, max8903_fault, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "MAX8903 Fault", data); if (ret) { dev_err(dev, "Cannot request irq %d for Fault (%d)\n", gpio_to_irq(pdata->flt), ret); goto err_flt_irq; } } if (pdata->chg) { ret = request_threaded_irq(gpio_to_irq(pdata->chg), NULL, max8903_chg, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "MAX8903 Fault", data); if (ret) { dev_err(dev, "Cannot request irq %d for Fault (%d)\n", gpio_to_irq(pdata->flt), ret); goto err_chg_irq; } }
這4個IO的中斷處理函數很類似
static irqreturn_t max8903_dcin(int irq, void *_data) { struct max8903_data *data = _data; struct max8903_pdata *pdata = data->pdata; bool ta_in; ta_in = gpio_get_value(pdata->dok) ? false : true; //保存當前dok值 if (ta_in == data->ta_in) return IRQ_HANDLED; data->ta_in = ta_in; pr_info("TA(DC-IN) Charger %s.\n", ta_in ? "Connected" : "Disconnected"); max8903_charger_update_status(data); max8903_battery_update_status(data); power_supply_changed(&data->psy); //報告狀態改變 power_supply_changed(&data->bat); return IRQ_HANDLED; } static irqreturn_t max8903_usbin(int irq, void *_data) { struct max8903_data *data = _data; struct max8903_pdata *pdata = data->pdata; bool usb_in; usb_in = gpio_get_value(pdata->uok) ? false : true; //保存當前uok值 if (usb_in == data->usb_in) return IRQ_HANDLED; data->usb_in = usb_in; max8903_charger_update_status(data); max8903_battery_update_status(data); pr_info("USB Charger %s.\n", usb_in ? "Connected" : "Disconnected"); power_supply_changed(&data->bat); power_supply_changed(&data->usb); //報告狀態改變 return IRQ_HANDLED; } static irqreturn_t max8903_fault(int irq, void *_data) { struct max8903_data *data = _data; struct max8903_pdata *pdata = data->pdata; bool fault; fault = gpio_get_value(pdata->flt) ? false : true; //保存當前電池錯誤值 if (fault == data->fault) return IRQ_HANDLED; data->fault = fault; if (fault) dev_err(data->dev, "Charger suffers a fault and stops.\n"); else dev_err(data->dev, "Charger recovered from a fault.\n"); max8903_charger_update_status(data); max8903_battery_update_status(data); power_supply_changed(&data->psy); power_supply_changed(&data->bat); power_supply_changed(&data->usb); //報告狀態改變 return IRQ_HANDLED; } static irqreturn_t max8903_chg(int irq, void *_data) { struct max8903_data *data = _data; struct max8903_pdata *pdata = data->pdata; int chg_state; chg_state = gpio_get_value(pdata->chg) ? false : true;//保存電池充電狀態 if (chg_state == data->chg_state) return IRQ_HANDLED; data->chg_state = chg_state; max8903_charger_update_status(data); max8903_battery_update_status(data); power_supply_changed(&data->psy); power_supply_changed(&data->bat); power_supply_changed(&data->usb);//報告狀態改變 return IRQ_HANDLED; }
到了這里電池充電的流程就走完了。