/* * drivers/power/axp/axp-charger.c * (C) Copyright 2010-2016 * Allwinner Technology Co., Ltd. * Pannan * * axp charger APIs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "axp-core.h" #include "axp-charger.h" static int axp_power_key; static enum AW_CHARGE_TYPE axp_usbcurflag = CHARGE_AC; static enum AW_CHARGE_TYPE axp_usbvolflag = CHARGE_AC; static struct axp_adc_res adc; static bool battery_initialized; static struct axp_config_info *axp_config_obj; static int plug_debounce; static DEFINE_SPINLOCK(axp_powerkey_lock); void axp_powerkey_set(int value) { spin_lock(&axp_powerkey_lock); axp_power_key = value; spin_unlock(&axp_powerkey_lock); } EXPORT_SYMBOL_GPL(axp_powerkey_set); int axp_powerkey_get(void) { int value; spin_lock(&axp_powerkey_lock); value = axp_power_key; spin_unlock(&axp_powerkey_lock); return value; } EXPORT_SYMBOL_GPL(axp_powerkey_get); int axp_usbvol(enum AW_CHARGE_TYPE type) { axp_usbvolflag = type; return 0; } EXPORT_SYMBOL_GPL(axp_usbvol); int axp_usbcur(enum AW_CHARGE_TYPE type) { axp_usbcurflag = type; return 0; } EXPORT_SYMBOL_GPL(axp_usbcur); static inline void axp_read_adc(struct axp_charger_dev *chg_dev, struct axp_adc_res *adc) { u8 tmp[2]; struct axp_regmap *map = chg_dev->chip->regmap; axp_regmap_reads(map, chg_dev->spy_info->batt->bat_temp_offset, 2, tmp); adc->ts_res = ((u16) tmp[0] << 8) | tmp[1]; } static inline s32 axp_vts_to_temp(s32 data, const struct axp_config_info *axp_config) { s32 temp; if (data < 80 || !axp_config->pmu_bat_temp_enable) return 30; else if (data < axp_config->pmu_bat_temp_para16) return 80; else if (data <= axp_config->pmu_bat_temp_para15) { temp = 70 + (axp_config->pmu_bat_temp_para15-data)*10/ (axp_config->pmu_bat_temp_para15-axp_config->pmu_bat_temp_para16); } else if (data <= axp_config->pmu_bat_temp_para14) { temp = 60 + (axp_config->pmu_bat_temp_para14-data)*10/ (axp_config->pmu_bat_temp_para14-axp_config->pmu_bat_temp_para15); } else if (data <= axp_config->pmu_bat_temp_para13) { temp = 55 + (axp_config->pmu_bat_temp_para13-data)*5/ (axp_config->pmu_bat_temp_para13-axp_config->pmu_bat_temp_para14); } else if (data <= axp_config->pmu_bat_temp_para12) { temp = 50 + (axp_config->pmu_bat_temp_para12-data)*5/ (axp_config->pmu_bat_temp_para12-axp_config->pmu_bat_temp_para13); } else if (data <= axp_config->pmu_bat_temp_para11) { temp = 45 + (axp_config->pmu_bat_temp_para11-data)*5/ (axp_config->pmu_bat_temp_para11-axp_config->pmu_bat_temp_para12); } else if (data <= axp_config->pmu_bat_temp_para10) { temp = 40 + (axp_config->pmu_bat_temp_para10-data)*5/ (axp_config->pmu_bat_temp_para10-axp_config->pmu_bat_temp_para11); } else if (data <= axp_config->pmu_bat_temp_para9) { temp = 30 + (axp_config->pmu_bat_temp_para9-data)*10/ (axp_config->pmu_bat_temp_para9-axp_config->pmu_bat_temp_para10); } else if (data <= axp_config->pmu_bat_temp_para8) { temp = 20 + (axp_config->pmu_bat_temp_para8-data)*10/ (axp_config->pmu_bat_temp_para8-axp_config->pmu_bat_temp_para9); } else if (data <= axp_config->pmu_bat_temp_para7) { temp = 10 + (axp_config->pmu_bat_temp_para7-data)*10/ (axp_config->pmu_bat_temp_para7-axp_config->pmu_bat_temp_para8); } else if (data <= axp_config->pmu_bat_temp_para6) { temp = 5 + (axp_config->pmu_bat_temp_para6-data)*5/ (axp_config->pmu_bat_temp_para6-axp_config->pmu_bat_temp_para7); } else if (data <= axp_config->pmu_bat_temp_para5) { temp = 0 + (axp_config->pmu_bat_temp_para5-data)*5/ (axp_config->pmu_bat_temp_para5-axp_config->pmu_bat_temp_para6); } else if (data <= axp_config->pmu_bat_temp_para4) { temp = -5 + (axp_config->pmu_bat_temp_para4-data)*5/ (axp_config->pmu_bat_temp_para4-axp_config->pmu_bat_temp_para5); } else if (data <= axp_config->pmu_bat_temp_para3) { temp = -10 + (axp_config->pmu_bat_temp_para3-data)*5/ (axp_config->pmu_bat_temp_para3-axp_config->pmu_bat_temp_para4); } else if (data <= axp_config->pmu_bat_temp_para2) { temp = -15 + (axp_config->pmu_bat_temp_para2-data)*5/ (axp_config->pmu_bat_temp_para2-axp_config->pmu_bat_temp_para3); } else if (data <= axp_config->pmu_bat_temp_para1) { temp = -25 + (axp_config->pmu_bat_temp_para1-data)*10/ (axp_config->pmu_bat_temp_para1-axp_config->pmu_bat_temp_para2); } else temp = -25; return temp; } static inline s32 axp_vts_to_mV(u16 reg) { return ((s32)(((reg >> 8) << 4) | (reg & 0x000F))) * 800 / 1000; } static inline void axp_update_ictemp_status(struct axp_charger_dev *chg_dev) { u16 tmp; u8 temp_val[2]; struct axp_regmap *map = chg_dev->chip->regmap; axp_regmap_reads(map, chg_dev->pmic_temp_offset, 2, temp_val); tmp = (temp_val[0] << 4) + (temp_val[1] & 0x0F); chg_dev->ic_temp = (s32) tmp * 1063 / 10000 - 2667 / 10; } static inline void axp_update_temp_status(struct axp_charger_dev *chg_dev) { u16 tmp; u8 temp_val[2]; s32 bat_temp_mv; struct axp_regmap *map = chg_dev->chip->regmap; chg_dev->adc = &adc; axp_read_adc(chg_dev, &adc); axp_regmap_reads(map, chg_dev->pmic_temp_offset, 2, temp_val); tmp = (temp_val[0] << 4) + (temp_val[1] & 0x0F); chg_dev->ic_temp = (s32) tmp * 1063 / 10000 - 2667 / 10; tmp = chg_dev->adc->ts_res; bat_temp_mv = axp_vts_to_mV(tmp); chg_dev->bat_temp = axp_vts_to_temp(bat_temp_mv, axp_config_obj); } /* * acin not presence + vbus no presence -> battery presence */ static int pwrsrc_parse_bat_det(struct axp_battery_info *batt, u8 val) { if (!(val & ((1 << batt->acpresent_bit) | (1 << batt->vbuspresent_bit)))) return 1; else return 0; } static int det_parse_bat_det(struct axp_battery_info *batt, u8 val) { if ((val & (1 << batt->det_bit)) && (val & (1 << batt->det_valid_bit))) return 1; else return 0; } void axp_charger_update_state(struct axp_charger_dev *chg_dev) { u8 val; u8 pwrsrc; struct axp_ac_info *ac = chg_dev->spy_info->ac; struct axp_usb_info *usb = chg_dev->spy_info->usb; struct axp_battery_info *batt = chg_dev->spy_info->batt; struct axp_regmap *map = chg_dev->chip->regmap; #ifdef TYPE_C struct axp_tc_info *tc = chg_dev->spy_info->tc; #endif /*sleep 10ms for adapter stable*/ msleep(10); axp_regmap_read(map, batt->det_offset, &val); axp_regmap_read(map, batt->pwrsrc_offset, &pwrsrc); mutex_lock(&chg_dev->charger_lock); if (batt->det_unused == 0) { if (batt->det_valid == 1) { chg_dev->bat_det = pwrsrc_parse_bat_det(batt, pwrsrc); if (chg_dev->bat_det == 0) chg_dev->bat_det = det_parse_bat_det(batt, val); } else if (batt->det_valid == 0) { chg_dev->bat_det = (val & 1 << batt->det_bit) ? 1 : 0; } } else if (batt->det_unused == 1) { chg_dev->bat_det = 0; } mutex_unlock(&chg_dev->charger_lock); axp_regmap_read(map, ac->det_offset, &val); mutex_lock(&chg_dev->charger_lock); chg_dev->ac_det = (val & 1 << ac->det_bit) ? 1 : 0; mutex_unlock(&chg_dev->charger_lock); if (usb->det_unused == 0) { axp_regmap_read(map, usb->det_offset, &val); mutex_lock(&chg_dev->charger_lock); chg_dev->usb_det = (val & 1 << usb->det_bit) ? 1 : 0; mutex_unlock(&chg_dev->charger_lock); } else if (usb->det_unused == 1) { chg_dev->usb_det = 0; } axp_regmap_read(map, ac->valid_offset, &val); mutex_lock(&chg_dev->charger_lock); chg_dev->ac_valid = (val & 1 << ac->valid_bit) ? 1 : 0; mutex_unlock(&chg_dev->charger_lock); if (usb->det_unused == 0) { axp_regmap_read(map, usb->valid_offset, &val); mutex_lock(&chg_dev->charger_lock); chg_dev->usb_valid = (val & 1 << usb->valid_bit) ? 1 : 0; mutex_unlock(&chg_dev->charger_lock); } else if (usb->det_unused == 1) { chg_dev->usb_valid = 0; } #ifdef TYPE_C if (tc->det_unused == 0) { axp_regmap_read(map, tc->det_offset, &val); mutex_lock(&chg_dev->charger_lock); chg_dev->tc_det = (val & 1 << tc->det_bit) ? 1 : 0; mutex_unlock(&chg_dev->charger_lock); } else if (tc->det_unused == 1) { chg_dev->tc_det = 0; } if (tc->det_unused == 0) { axp_regmap_read(map, tc->valid_offset, &val); mutex_lock(&chg_dev->charger_lock); chg_dev->tc_valid = (val & 1 << tc->valid_bit) ? 1 : 0; mutex_unlock(&chg_dev->charger_lock); } else if (tc->det_unused == 1) { chg_dev->tc_valid = 0; } chg_dev->ext_valid = (chg_dev->ac_det || chg_dev->usb_det || chg_dev->tc_det); #else chg_dev->ext_valid = (chg_dev->ac_det || chg_dev->usb_det); #endif axp_regmap_read(map, ac->in_short_offset, &val); mutex_lock(&chg_dev->charger_lock); #ifdef AXP2585 chg_dev->in_short = (val & 1 << ac->in_short_bit) ? 1 : 0; #else chg_dev->in_short = 1; #endif if (!chg_dev->in_short) chg_dev->ac_charging = chg_dev->ac_valid; mutex_unlock(&chg_dev->charger_lock); axp_regmap_read(map, batt->cur_direction_offset, &val); mutex_lock(&chg_dev->charger_lock); if (val & 1 << batt->cur_direction_bit) chg_dev->bat_current_direction = 1; else chg_dev->bat_current_direction = 0; mutex_unlock(&chg_dev->charger_lock); axp_regmap_read(map, batt->chgstat_offset, &val); mutex_lock(&chg_dev->charger_lock); #ifdef AXP2585 chg_dev->charging = (((val & (7 << batt->chgstat_bit)) > 0) && ((val & (7 << batt->chgstat_bit)) < 0x14)) ? 1 : 0; #else chg_dev->charging = (val & 1 << batt->chgstat_bit) ? 1 : 0; #endif mutex_unlock(&chg_dev->charger_lock); } void axp_charger_update_value(struct axp_charger_dev *chg_dev) { struct axp_ac_info *ac = chg_dev->spy_info->ac; struct axp_usb_info *usb = chg_dev->spy_info->usb; struct axp_battery_info *batt = chg_dev->spy_info->batt; int bat_vol, bat_cur, bat_discur, ac_vol, ac_cur, usb_vol, usb_cur; #ifdef TYPE_C /* struct axp_tc_info *tc = chg_dev->spy_info->tc; int tc_vol, tc_cur; */ #endif bat_vol = batt->get_vbat(chg_dev); bat_cur = batt->get_ibat(chg_dev); bat_discur = batt->get_disibat(chg_dev); ac_vol = ac->get_ac_voltage(chg_dev); ac_cur = ac->get_ac_current(chg_dev); usb_vol = usb->get_usb_voltage(chg_dev); usb_cur = usb->get_usb_current(chg_dev); #ifdef TYPE_C /*tc_vol = tc->get_tc_voltage(chg_dev); tc_cur = tc->get_tc_current(chg_dev);*/ #endif mutex_lock(&chg_dev->charger_lock); chg_dev->bat_vol = bat_vol; chg_dev->bat_cur = bat_cur; chg_dev->bat_discur = bat_discur; chg_dev->ac_vol = ac_vol; chg_dev->ac_cur = ac_cur; chg_dev->usb_vol = usb_vol; chg_dev->usb_cur = usb_cur; #ifdef TYPE_C /* chg_dev->tc_vol = tc_vol; chg_dev->tc_cur = tc_cur; */ #endif mutex_unlock(&chg_dev->charger_lock); } static void axp_usb_ac_check_status(struct axp_charger_dev *chg_dev) { chg_dev->usb_pc_charging = (((CHARGE_USB_20 == axp_usbcurflag) || (CHARGE_USB_30 == axp_usbcurflag)) && (chg_dev->ext_valid)); chg_dev->usb_adapter_charging = ((0 == chg_dev->ac_valid) && (CHARGE_USB_20 != axp_usbcurflag) && (CHARGE_USB_30 != axp_usbcurflag) && (chg_dev->ext_valid)); if (chg_dev->in_short) chg_dev->ac_charging = ((chg_dev->usb_adapter_charging == 0) && (chg_dev->usb_pc_charging == 0) && (chg_dev->ext_valid)); else chg_dev->ac_charging = chg_dev->ac_valid; power_supply_changed(chg_dev->ac); power_supply_changed(chg_dev->usb); AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "ac_charging=%d\n", chg_dev->ac_charging); AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "usb_pc_charging=%d\n", chg_dev->usb_pc_charging); AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "usb_adapter_charging=%d\n", chg_dev->usb_adapter_charging); AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "usb_det=%d ac_det=%d\n", chg_dev->usb_det, chg_dev->ac_det); } static void axp_charger_update_usb_state(unsigned long data) { struct axp_charger_dev *chg_dev = (struct axp_charger_dev *)data; axp_usb_ac_check_status(chg_dev); if (chg_dev->bat_det) schedule_delayed_work(&(chg_dev->usbwork), 0); } static void axp_usb(struct work_struct *work) { struct axp_charger_dev *chg_dev = container_of(work, struct axp_charger_dev, usbwork.work); struct axp_usb_info *usb = chg_dev->spy_info->usb; struct axp_ac_info *ac = chg_dev->spy_info->ac; AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "[axp_usb] axp_usbcurflag = %d\n", axp_usbcurflag); axp_charger_update_state(chg_dev); if (chg_dev->in_short) { /* usb and ac in short*/ if (!chg_dev->usb_valid) { /*usb or usb adapter can not be used*/ AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "USB not insert!\n"); usb->set_usb_ihold(chg_dev, 500); } else if (CHARGE_USB_20 == axp_usbcurflag) { if (usb->usb_pc_cur) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_cur %d mA\n", usb->usb_pc_cur); usb->set_usb_ihold(chg_dev, usb->usb_pc_cur); } else { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_cur 500 mA\n"); usb->set_usb_ihold(chg_dev, 500); } } else if (CHARGE_USB_30 == axp_usbcurflag) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_cur 900 mA\n"); usb->set_usb_ihold(chg_dev, 900); } else { /* usb adapter */ if (usb->usb_ad_cur) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_ad_cur %d mA\n", usb->usb_ad_cur); } else { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_ad_cur no limit\n"); } usb->set_usb_ihold(chg_dev, usb->usb_ad_cur); } if (CHARGE_USB_20 == axp_usbvolflag) { if (usb->usb_pc_vol) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_vol %d mV\n", usb->usb_pc_vol); usb->set_usb_vhold(chg_dev, usb->usb_pc_vol); } } else if (CHARGE_USB_30 == axp_usbvolflag) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_vol 4700 mV\n"); usb->set_usb_vhold(chg_dev, 4700); } else { if (usb->usb_ad_vol) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_ad_vol %d mV\n", usb->usb_ad_vol); usb->set_usb_vhold(chg_dev, usb->usb_ad_vol); } } } else { if (!chg_dev->ac_valid && !chg_dev->usb_valid) { /*usb and ac can not be used*/ AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "AC and USB not insert!\n"); usb->set_usb_ihold(chg_dev, 500); } else if (CHARGE_USB_20 == axp_usbcurflag) { if (usb->usb_pc_cur) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_cur %d mA\n", usb->usb_pc_cur); usb->set_usb_ihold(chg_dev, usb->usb_pc_cur); } else { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_cur 500 mA\n"); usb->set_usb_ihold(chg_dev, 500); } } else if (CHARGE_USB_30 == axp_usbcurflag) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_cur 900 mA\n"); usb->set_usb_ihold(chg_dev, 900); } else { if ((usb->usb_ad_cur)) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set adapter cur %d mA\n", usb->usb_ad_cur); } else { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set adapter cur no limit\n"); } usb->set_usb_ihold(chg_dev, usb->usb_ad_cur); } if (CHARGE_USB_20 == axp_usbvolflag) { if (usb->usb_pc_vol) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_vol %d mV\n", usb->usb_pc_vol); usb->set_usb_vhold(chg_dev, usb->usb_pc_vol); } } else if (CHARGE_USB_30 == axp_usbvolflag) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set usb_pc_vol 4700 mV\n"); usb->set_usb_vhold(chg_dev, 4700); } else { if (ac->ac_vol) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "set ac_vol %d mV\n", ac->ac_vol); ac->set_ac_vhold(chg_dev, ac->ac_vol); } } } } void axp_battery_update_vol(struct axp_charger_dev *chg_dev) { s32 rest_vol = 0; struct axp_battery_info *batt = chg_dev->spy_info->batt; rest_vol = batt->get_rest_cap(chg_dev); mutex_lock(&chg_dev->charger_lock); if (rest_vol > 100) { AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "AXP rest_vol = %d\n", rest_vol); chg_dev->rest_vol = 100; } else { chg_dev->rest_vol = rest_vol; } mutex_unlock(&chg_dev->charger_lock); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->rest_vol = %d\n", chg_dev->rest_vol); } static enum power_supply_property axp_battery_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_CHARGE_COUNTER, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_TEMP, }; static enum power_supply_property axp_ac_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, }; static enum power_supply_property axp_usb_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, }; #ifdef TYPE_C static enum power_supply_property axp_tc_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, }; #endif static void axp_battery_check_status(struct axp_charger_dev *chg_dev, union power_supply_propval *val) { if (chg_dev->bat_det) { if (chg_dev->ext_valid) { #ifdef TYPE_C if (chg_dev->rest_vol == 96) #else if (chg_dev->rest_vol == 100) #endif val->intval = POWER_SUPPLY_STATUS_FULL; else if (chg_dev->charging) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; } else { val->intval = POWER_SUPPLY_STATUS_DISCHARGING; } } else { val->intval = POWER_SUPPLY_STATUS_FULL; } } static void axp_battery_check_health(struct axp_charger_dev *chg_dev, union power_supply_propval *val) { struct axp_battery_info *batt = chg_dev->spy_info->batt; val->intval = batt->get_bat_health(chg_dev); } static s32 axp_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct axp_charger_dev *chg_dev = power_supply_get_drvdata(psy); s32 ret = 0; switch (psp) { case POWER_SUPPLY_PROP_STATUS: axp_battery_check_status(chg_dev, val); break; case POWER_SUPPLY_PROP_HEALTH: axp_battery_check_health(chg_dev, val); break; case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = chg_dev->battery_info->technology; break; case POWER_SUPPLY_PROP_CHARGE_COUNTER: val->intval = chg_dev->coulumb_counter * 1000; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: val->intval = chg_dev->battery_info->voltage_max_design; break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: val->intval = chg_dev->battery_info->voltage_min_design; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = chg_dev->bat_vol * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = (chg_dev->bat_cur - chg_dev->bat_discur) * 1000; break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = psy->desc->name; break; case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: val->intval = chg_dev->battery_info->energy_full_design; break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = chg_dev->rest_vol; break; case POWER_SUPPLY_PROP_ONLINE: { /* in order to get hardware state, * we must update charger state now. * by sunny at 2012-12-23 11:06:15. */ axp_charger_update_state(chg_dev); val->intval = !chg_dev->bat_current_direction; break; } case POWER_SUPPLY_PROP_PRESENT: val->intval = chg_dev->bat_det; break; case POWER_SUPPLY_PROP_TEMP: val->intval = chg_dev->bat_temp * 10; break; default: ret = -EINVAL; break; } return ret; } static s32 axp_ac_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct axp_charger_dev *chg_dev = power_supply_get_drvdata(psy); s32 ret = 0; switch (psp) { case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = psy->desc->name; break; case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_ONLINE: val->intval = (chg_dev->ac_charging || chg_dev->usb_adapter_charging); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = chg_dev->ac_vol * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = chg_dev->ac_cur * 1000; break; default: ret = -EINVAL; break; } return ret; } static s32 axp_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct axp_charger_dev *chg_dev = power_supply_get_drvdata(psy); s32 ret = 0; switch (psp) { case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = psy->desc->name; break; case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_ONLINE: val->intval = chg_dev->usb_pc_charging; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = chg_dev->usb_vol * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = chg_dev->usb_cur * 1000; break; default: ret = -EINVAL; break; } return ret; } #ifdef TYPE_C static s32 axp_tc_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct axp_charger_dev *chg_dev = power_supply_get_drvdata(psy); s32 ret = 0; switch (psp) { case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = psy->desc->name; break; case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_ONLINE: val->intval = chg_dev->tc_valid; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = chg_dev->tc_vol * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = chg_dev->tc_cur * 1000; break; default: ret = -EINVAL; break; } return ret; } #endif static char *supply_list[] = { "battery", }; static const struct power_supply_desc batt_desc = { .name = "battery", .type = POWER_SUPPLY_TYPE_BATTERY, .get_property = axp_battery_get_property, .properties = axp_battery_props, .num_properties = ARRAY_SIZE(axp_battery_props), .use_for_apm = 1, }; static const struct power_supply_desc ac_desc = { .name = "ac", .type = POWER_SUPPLY_TYPE_MAINS, .get_property = axp_ac_get_property, .properties = axp_ac_props, .num_properties = ARRAY_SIZE(axp_ac_props), }; static const struct power_supply_desc usb_desc = { .name = "usb", .type = POWER_SUPPLY_TYPE_USB, .get_property = axp_usb_get_property, .properties = axp_usb_props, .num_properties = ARRAY_SIZE(axp_usb_props), }; #ifdef TYPE_C static const struct power_supply_desc tc_desc = { .name = "tc", .type = POWER_SUPPLY_TYPE_USB_TYPE_C, .get_property = axp_tc_get_property, .properties = axp_tc_props, .num_properties = ARRAY_SIZE(axp_tc_props), }; #endif static struct power_supply_config psy_cfg = { .supplied_to = supply_list, .num_supplicants = ARRAY_SIZE(supply_list), }; static void axp_charging_monitor(struct work_struct *work) { struct axp_charger_dev *chg_dev = container_of(work, struct axp_charger_dev, work.work); struct power_supply_config psy_cfg = {}; static s32 pre_rest_vol; static bool pre_bat_curr_dir; axp_charger_update_state(chg_dev); /* if no battery exist, then return */ if (!chg_dev->bat_det) { axp_update_ictemp_status(chg_dev); AXP_DEBUG(AXP_MISC, chg_dev->chip->pmu_num, "charger->ic_temp = %d\n", chg_dev->ic_temp); schedule_delayed_work(&chg_dev->work, chg_dev->interval); return; } /* if battery hadn't been detectd before, register it as power supply * now */ if (!battery_initialized) { psy_cfg.drv_data = chg_dev; chg_dev->batt = power_supply_register(chg_dev->dev, &batt_desc, &psy_cfg); battery_initialized = true; } axp_charger_update_value(chg_dev); axp_update_temp_status(chg_dev); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->ic_temp = %d\n", chg_dev->ic_temp); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->bat_temp = %d\n", chg_dev->bat_temp); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->bat_vol = %d\n", chg_dev->bat_vol); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->bat_cur = %d\n", chg_dev->bat_cur); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->bat_discur = %d\n", chg_dev->bat_discur); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->is_charging = %d\n", chg_dev->charging); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->bat_current_direction = %d\n", chg_dev->bat_current_direction); AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "charger->ext_valid = %d\n", chg_dev->ext_valid); if (!plug_debounce) { if (chg_dev->private_debug) chg_dev->private_debug(chg_dev); } else { plug_debounce = 0; } axp_battery_update_vol(chg_dev); /* if battery volume changed, inform uevent */ if ((chg_dev->rest_vol - pre_rest_vol) || (chg_dev->bat_current_direction != pre_bat_curr_dir) ) { AXP_DEBUG(AXP_SPLY, chg_dev->chip->pmu_num, "battery vol change: %d->%d\n", pre_rest_vol, chg_dev->rest_vol); pre_rest_vol = chg_dev->rest_vol; pre_bat_curr_dir = chg_dev->bat_current_direction; power_supply_changed(chg_dev->batt); } /* reschedule for the next time */ schedule_delayed_work(&chg_dev->work, chg_dev->interval); } void axp_change(struct axp_charger_dev *chg_dev) { AXP_DEBUG(AXP_INT, chg_dev->chip->pmu_num, "battery state change\n"); axp_charger_update_state(chg_dev); axp_charger_update_value(chg_dev); if (chg_dev->bat_det && battery_initialized) power_supply_changed(chg_dev->batt); } EXPORT_SYMBOL_GPL(axp_change); void axp_usbac_in(struct axp_charger_dev *chg_dev) { struct axp_usb_info *usb = chg_dev->spy_info->usb; axp_usbcur(CHARGE_AC); axp_usbvol(CHARGE_AC); AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "axp ac/usb in!\n"); if (timer_pending(&chg_dev->usb_status_timer)) del_timer_sync(&chg_dev->usb_status_timer); /* must limit the current now, * and will again fix it while usb/ac detect finished! */ if (usb->usb_pc_cur) usb->set_usb_ihold(chg_dev, usb->usb_pc_cur); else usb->set_usb_ihold(chg_dev, 500); plug_debounce = 1; /* this is about 3.5s, * while the flag set in usb drivers after usb plugged */ axp_charger_update_state(chg_dev); mod_timer(&chg_dev->usb_status_timer, jiffies + msecs_to_jiffies(5000)); axp_usb_ac_check_status(chg_dev); } EXPORT_SYMBOL_GPL(axp_usbac_in); void axp_usbac_out(struct axp_charger_dev *chg_dev) { AXP_DEBUG(AXP_CHG, chg_dev->chip->pmu_num, "axp ac/usb out!\n"); if (timer_pending(&chg_dev->usb_status_timer)) del_timer_sync(&chg_dev->usb_status_timer); /* if we plugged usb & ac at the same time, * then unpluged ac quickly while the usb driver * do not finished detecting, * the charger type is error!So delay the charger type report 2s */ mod_timer(&chg_dev->usb_status_timer, jiffies + msecs_to_jiffies(2000)); axp_usb_ac_check_status(chg_dev); } EXPORT_SYMBOL_GPL(axp_usbac_out); void axp_capchange(struct axp_charger_dev *chg_dev) { struct power_supply_config psy_cfg = {}; AXP_DEBUG(AXP_INT, chg_dev->chip->pmu_num, "battery change\n"); axp_charger_update_state(chg_dev); axp_charger_update_value(chg_dev); axp_battery_update_vol(chg_dev); if (chg_dev->bat_det) { AXP_DEBUG(AXP_INT, chg_dev->chip->pmu_num, "rest_vol = %d\n", chg_dev->rest_vol); if (!battery_initialized) { psy_cfg.drv_data = chg_dev; chg_dev->batt = power_supply_register(chg_dev->dev, &batt_desc, &psy_cfg); schedule_delayed_work(&chg_dev->usbwork, 0); schedule_delayed_work(&chg_dev->work, 0); power_supply_changed(chg_dev->batt); battery_initialized = true; } } else { if (battery_initialized) { cancel_delayed_work_sync(&chg_dev->work); cancel_delayed_work_sync(&chg_dev->usbwork); power_supply_unregister(chg_dev->batt); chg_dev->batt = NULL; battery_initialized = false; } } } EXPORT_SYMBOL_GPL(axp_capchange); irqreturn_t axp_usb_in_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_usb_connect = 1; axp_change(chg_dev); axp_usbac_in(chg_dev); return IRQ_HANDLED; } irqreturn_t axp_usb_out_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_usb_connect = 0; axp_change(chg_dev); axp_usbac_out(chg_dev); return IRQ_HANDLED; } irqreturn_t axp_ac_in_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_change(chg_dev); axp_usbac_in(chg_dev); return IRQ_HANDLED; } irqreturn_t axp_ac_out_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_change(chg_dev); axp_usbac_out(chg_dev); return IRQ_HANDLED; } irqreturn_t axp_capchange_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_capchange(chg_dev); return IRQ_HANDLED; } irqreturn_t axp_change_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_change(chg_dev); return IRQ_HANDLED; } irqreturn_t axp_low_warning1_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_change(chg_dev); return IRQ_HANDLED; } irqreturn_t axp_low_warning2_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_change(chg_dev); return IRQ_HANDLED; } #ifdef TYPE_C irqreturn_t axp_tc_in_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_change(chg_dev); /*axp_usbac_in(chg_dev);*/ return IRQ_HANDLED; } irqreturn_t axp_tc_out_isr(int irq, void *data) { struct axp_charger_dev *chg_dev = data; axp_change(chg_dev); /*axp_usbac_out(chg_dev);*/ return IRQ_HANDLED; } #endif void axp_charger_suspend(struct axp_charger_dev *chg_dev) { struct axp_battery_info *batt = chg_dev->spy_info->batt; axp_charger_update_state(chg_dev); if (chg_dev->bat_det) { schedule_delayed_work(&chg_dev->usbwork, 0); flush_delayed_work(&chg_dev->usbwork); cancel_delayed_work_sync(&chg_dev->work); cancel_delayed_work_sync(&chg_dev->usbwork); batt->set_chg_cur(chg_dev, batt->suspend_chgcur); } } EXPORT_SYMBOL_GPL(axp_charger_suspend); void axp_charger_resume(struct axp_charger_dev *chg_dev) { struct axp_battery_info *batt = chg_dev->spy_info->batt; axp_charger_update_state(chg_dev); axp_charger_update_value(chg_dev); axp_battery_update_vol(chg_dev); batt->set_chg_cur(chg_dev, batt->runtime_chgcur); power_supply_changed(chg_dev->ac); power_supply_changed(chg_dev->usb); if (chg_dev->bat_det) { power_supply_changed(chg_dev->batt); schedule_delayed_work(&chg_dev->work, chg_dev->interval); schedule_delayed_work(&chg_dev->usbwork, msecs_to_jiffies(7 * 1000)); } } EXPORT_SYMBOL_GPL(axp_charger_resume); void axp_charger_shutdown(struct axp_charger_dev *chg_dev) { struct axp_battery_info *batt = chg_dev->spy_info->batt; axp_charger_update_state(chg_dev); if (chg_dev->bat_det) { schedule_delayed_work(&chg_dev->usbwork, 0); flush_delayed_work(&chg_dev->usbwork); cancel_delayed_work_sync(&chg_dev->work); cancel_delayed_work_sync(&chg_dev->usbwork); batt->set_chg_cur(chg_dev, batt->shutdown_chgcur); } } EXPORT_SYMBOL_GPL(axp_charger_shutdown); struct axp_charger_dev *axp_power_supply_register(struct device *dev, struct axp_dev *axp_dev, struct power_supply_info *battery_info, struct axp_supply_info *info) { struct axp_charger_dev *chg_dev; chg_dev = devm_kzalloc(dev, sizeof(*chg_dev), GFP_KERNEL); if (chg_dev == NULL) return NULL; chg_dev->dev = dev; chg_dev->spy_info = info; chg_dev->chip = axp_dev; chg_dev->battery_info = battery_info; psy_cfg.drv_data = chg_dev; mutex_init(&chg_dev->charger_lock); axp_charger_update_state(chg_dev); if (chg_dev->bat_det) { chg_dev->batt = power_supply_register(dev, &batt_desc, &psy_cfg); if (IS_ERR(chg_dev->batt)) goto err_ps_register; battery_initialized = true; } chg_dev->ac = power_supply_register(dev, &ac_desc, &psy_cfg); if (IS_ERR(chg_dev->ac)) { if (chg_dev->bat_det) { power_supply_unregister(chg_dev->batt); chg_dev->batt = NULL; goto err_ps_register; } } chg_dev->usb = power_supply_register(dev, &usb_desc, &psy_cfg); if (IS_ERR(chg_dev->usb)) { power_supply_unregister(chg_dev->ac); chg_dev->ac = NULL; if (chg_dev->bat_det) { power_supply_unregister(chg_dev->batt); chg_dev->batt = NULL; goto err_ps_register; } } #ifdef TYPE_C chg_dev->tc = power_supply_register(dev, &tc_desc, &psy_cfg); if (IS_ERR(chg_dev->tc)) { power_supply_unregister(chg_dev->ac); power_supply_unregister(chg_dev->usb); chg_dev->ac = NULL; chg_dev->usb = NULL; if (chg_dev->bat_det) { power_supply_unregister(chg_dev->batt); chg_dev->batt = NULL; goto err_ps_register; } } if (info->tc->tc_vol && info->tc->set_tc_vhold) info->tc->set_tc_vhold(chg_dev, info->tc->tc_vol); #endif if (info->ac->ac_vol && info->ac->set_ac_vhold) info->ac->set_ac_vhold(chg_dev, info->ac->ac_vol); if (info->usb->usb_pc_vol && info->usb->set_usb_vhold) info->usb->set_usb_vhold(chg_dev, info->usb->usb_pc_vol); if (info->batt->runtime_chgcur && info->batt->set_chg_cur) info->batt->set_chg_cur(chg_dev, info->batt->runtime_chgcur); setup_timer(&chg_dev->usb_status_timer, axp_charger_update_usb_state, (unsigned long)chg_dev); INIT_DELAYED_WORK(&(chg_dev->usbwork), axp_usb); axp_usb_ac_check_status(chg_dev); axp_battery_update_vol(chg_dev); chg_dev->interval = msecs_to_jiffies(10 * 1000); INIT_DELAYED_WORK(&chg_dev->work, axp_charging_monitor); schedule_delayed_work(&chg_dev->work, chg_dev->interval); if (timer_pending(&chg_dev->usb_status_timer)) del_timer_sync(&chg_dev->usb_status_timer); mod_timer(&chg_dev->usb_status_timer, jiffies + msecs_to_jiffies(20 * 1000)); return chg_dev; err_ps_register: return NULL; } EXPORT_SYMBOL_GPL(axp_power_supply_register); void axp_power_supply_unregister(struct axp_charger_dev *chg_dev) { del_timer_sync(&chg_dev->usb_status_timer); power_supply_unregister(chg_dev->usb); chg_dev->batt = NULL; power_supply_unregister(chg_dev->ac); chg_dev->ac = NULL; if (chg_dev->bat_det && battery_initialized) { cancel_delayed_work_sync(&chg_dev->work); cancel_delayed_work_sync(&chg_dev->usbwork); power_supply_unregister(chg_dev->batt); chg_dev->batt = NULL; battery_initialized = false; } } EXPORT_SYMBOL_GPL(axp_power_supply_unregister); int axp_charger_dt_parse(struct device_node *node, struct axp_config_info *axp_config) { if (!of_device_is_available(node)) { pr_err("%s: failed\n", __func__); return -1; } AXP_OF_PROP_READ(pmu_battery_rdc, BATRDC); AXP_OF_PROP_READ(pmu_battery_cap, 4000); AXP_OF_PROP_READ(pmu_batdeten, 1); AXP_OF_PROP_READ(pmu_chg_ic_temp, 0); AXP_OF_PROP_READ(pmu_runtime_chgcur, INTCHGCUR / 1000); AXP_OF_PROP_READ(pmu_suspend_chgcur, 1200); AXP_OF_PROP_READ(pmu_shutdown_chgcur, 1200); AXP_OF_PROP_READ(pmu_init_chgvol, INTCHGVOL / 1000); AXP_OF_PROP_READ(pmu_init_chgend_rate, INTCHGENDRATE); AXP_OF_PROP_READ(pmu_init_chg_enabled, 1); AXP_OF_PROP_READ(pmu_init_bc_en, 0); AXP_OF_PROP_READ(pmu_init_adc_freq, INTADCFREQ); AXP_OF_PROP_READ(pmu_init_adcts_freq, INTADCFREQC); AXP_OF_PROP_READ(pmu_init_chg_pretime, INTCHGPRETIME); AXP_OF_PROP_READ(pmu_init_chg_csttime, INTCHGCSTTIME); AXP_OF_PROP_READ(pmu_batt_cap_correct, 1); AXP_OF_PROP_READ(pmu_chg_end_on_en, 0); AXP_OF_PROP_READ(ocv_coulumb_100, 0); AXP_OF_PROP_READ(pmu_bat_para1, OCVREG0); AXP_OF_PROP_READ(pmu_bat_para2, OCVREG1); AXP_OF_PROP_READ(pmu_bat_para3, OCVREG2); AXP_OF_PROP_READ(pmu_bat_para4, OCVREG3); AXP_OF_PROP_READ(pmu_bat_para5, OCVREG4); AXP_OF_PROP_READ(pmu_bat_para6, OCVREG5); AXP_OF_PROP_READ(pmu_bat_para7, OCVREG6); AXP_OF_PROP_READ(pmu_bat_para8, OCVREG7); AXP_OF_PROP_READ(pmu_bat_para9, OCVREG8); AXP_OF_PROP_READ(pmu_bat_para10, OCVREG9); AXP_OF_PROP_READ(pmu_bat_para11, OCVREGA); AXP_OF_PROP_READ(pmu_bat_para12, OCVREGB); AXP_OF_PROP_READ(pmu_bat_para13, OCVREGC); AXP_OF_PROP_READ(pmu_bat_para14, OCVREGD); AXP_OF_PROP_READ(pmu_bat_para15, OCVREGE); AXP_OF_PROP_READ(pmu_bat_para16, OCVREGF); AXP_OF_PROP_READ(pmu_bat_para17, OCVREG10); AXP_OF_PROP_READ(pmu_bat_para18, OCVREG11); AXP_OF_PROP_READ(pmu_bat_para19, OCVREG12); AXP_OF_PROP_READ(pmu_bat_para20, OCVREG13); AXP_OF_PROP_READ(pmu_bat_para21, OCVREG14); AXP_OF_PROP_READ(pmu_bat_para22, OCVREG15); AXP_OF_PROP_READ(pmu_bat_para23, OCVREG16); AXP_OF_PROP_READ(pmu_bat_para24, OCVREG17); AXP_OF_PROP_READ(pmu_bat_para25, OCVREG18); AXP_OF_PROP_READ(pmu_bat_para26, OCVREG19); AXP_OF_PROP_READ(pmu_bat_para27, OCVREG1A); AXP_OF_PROP_READ(pmu_bat_para28, OCVREG1B); AXP_OF_PROP_READ(pmu_bat_para29, OCVREG1C); AXP_OF_PROP_READ(pmu_bat_para30, OCVREG1D); AXP_OF_PROP_READ(pmu_bat_para31, OCVREG1E); AXP_OF_PROP_READ(pmu_bat_para32, OCVREG1F); AXP_OF_PROP_READ(pmu_ac_vol, 4400); AXP_OF_PROP_READ(pmu_usbpc_vol, 4400); AXP_OF_PROP_READ(pmu_ac_cur, 0); AXP_OF_PROP_READ(pmu_usbpc_cur, 0); AXP_OF_PROP_READ(pmu_pwroff_vol, 3300); AXP_OF_PROP_READ(pmu_pwron_vol, 2900); AXP_OF_PROP_READ(pmu_battery_warning_level1, 15); AXP_OF_PROP_READ(pmu_battery_warning_level2, 0); AXP_OF_PROP_READ(pmu_restvol_adjust_time, 30); AXP_OF_PROP_READ(pmu_ocv_cou_adjust_time, 60); AXP_OF_PROP_READ(pmu_chgled_func, 0); AXP_OF_PROP_READ(pmu_chgled_type, 0); AXP_OF_PROP_READ(pmu_bat_temp_enable, 0); AXP_OF_PROP_READ(pmu_bat_charge_ltf, 0xA5); AXP_OF_PROP_READ(pmu_bat_charge_htf, 0x1F); AXP_OF_PROP_READ(pmu_bat_shutdown_ltf, 0xFC); AXP_OF_PROP_READ(pmu_bat_shutdown_htf, 0x16); AXP_OF_PROP_READ(pmu_bat_temp_para1, 0); AXP_OF_PROP_READ(pmu_bat_temp_para2, 0); AXP_OF_PROP_READ(pmu_bat_temp_para3, 0); AXP_OF_PROP_READ(pmu_bat_temp_para4, 0); AXP_OF_PROP_READ(pmu_bat_temp_para5, 0); AXP_OF_PROP_READ(pmu_bat_temp_para6, 0); AXP_OF_PROP_READ(pmu_bat_temp_para7, 0); AXP_OF_PROP_READ(pmu_bat_temp_para8, 0); AXP_OF_PROP_READ(pmu_bat_temp_para9, 0); AXP_OF_PROP_READ(pmu_bat_temp_para10, 0); AXP_OF_PROP_READ(pmu_bat_temp_para11, 0); AXP_OF_PROP_READ(pmu_bat_temp_para12, 0); AXP_OF_PROP_READ(pmu_bat_temp_para13, 0); AXP_OF_PROP_READ(pmu_bat_temp_para14, 0); AXP_OF_PROP_READ(pmu_bat_temp_para15, 0); AXP_OF_PROP_READ(pmu_bat_temp_para16, 0); AXP_OF_PROP_READ(pmu_bat_unused, 0); AXP_OF_PROP_READ(power_start, 0); AXP_OF_PROP_READ(pmu_ocv_en, 1); AXP_OF_PROP_READ(pmu_cou_en, 1); AXP_OF_PROP_READ(pmu_update_min_time, UPDATEMINTIME); axp_config_obj = axp_config; return 0; } EXPORT_SYMBOL_GPL(axp_charger_dt_parse); MODULE_DESCRIPTION("ALLWINNERTECH axp charger"); MODULE_AUTHOR("pannan"); MODULE_LICENSE("GPL");