#define pr_fmt(x) KBUILD_MODNAME ": " x "\n" #include #include #include #include "linux/irq.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "linux/mfd/axp2101.h" #include "axp2101_charger.h" #define AXP2585_VBAT_MAX (8000) #define AXP2585_VBAT_MIN (2000) #define AXP2585_SOC_MAX (100) #define AXP2585_SOC_MIN (0) #define AXP2585_MASK_VBUS_STATE BIT(1) #define AXP2585_MODE_RSTGAUGE BIT(3) #define AXP2585_MODE_RSTMCU BIT(2) #define AXP2585_MAX_PARAM 512 #define AXP2585_BROMUP_EN BIT(0) #define AXP2585_CFG_UPDATE_MARK BIT(4) #define AXP2585_CHARGING_TRI (0) #define AXP2585_CHARGING_PRE (1) #define AXP2585_CHARGING_CC (2) #define AXP2585_CHARGING_CV (3) #define AXP2585_CHARGING_DONE (4) #define AXP2585_CHARGING_NCHG (5) #define AXP2585_MANUFACTURER "xpower,axp2585" #define AXP2585_ADC_BATVOL_ENABLE (1 << 4) #define AXP2585_ADC_TSVOL_ENABLE (1 << 5) #define AXP2585_ADC_BATCUR_ENABLE (1 << 6) #define AXP2585_ADC_DIETMP_ENABLE (1 << 7) struct axp2585_device_info { char *name; struct device *dev; struct regmap *regmap; struct power_supply *bat; struct delayed_work bat_chk; struct axp_config_info dts_info; bool axp2585_version_d; struct notifier_block notifier; }; RAW_NOTIFIER_HEAD(axp2585_notifier_list); #ifdef CONFIG_AXP2585_LOW_WARNING_LED #define LOW_WARNING_EVENT 0x5A #endif static int axp2585_get_rest_vol(struct axp2585_device_info *di) { u8 temp_val[2]; u8 ocv_percent = 0; u8 coulomb_percent = 0; int batt_max_cap, coulumb_counter; int rest_vol; struct regmap *map = di->regmap; u32 val, tmp[2]; bool bat_charging; regmap_read(map, AXP2585_CAP, &val); if (!(val & 0x80)) return 0; rest_vol = (int) (val & 0x7F); regmap_read(map, 0xe4, &tmp[0]); if (tmp[0] & 0x80) ocv_percent = tmp[0] & 0x7f; regmap_read(map, 0xe5, &tmp[0]); if (tmp[0] & 0x80) coulomb_percent = tmp[0] & 0x7f; regmap_read(map, AXP2585_STATUS, &val); bat_charging = ((((val & (7 << 2)) > 0)) && ((val & (7 << 2)) < 0x14)) ? 1 : 0; if (ocv_percent == 100 && bat_charging == 0 && rest_vol == 99 && (val & BIT(1))) { regmap_update_bits(map, AXP2585_COULOMB_CTL, 0x00, 0x80); regmap_update_bits(map, AXP2585_COULOMB_CTL, 0x80, 0x80); rest_vol = 100; } regmap_bulk_read(map, 0xe2, temp_val, 2); coulumb_counter = (((temp_val[0] & 0x7f) << 8) + temp_val[1]) * 1456 / 1000; regmap_bulk_read(map, 0xe0, temp_val, 2); batt_max_cap = (((temp_val[0] & 0x7f) << 8) + temp_val[1]) * 1456 / 1000; return rest_vol; } static int axp2585_get_coulumb_counter(struct axp2585_device_info *di) { u8 temp_val[2]; struct regmap *map = di->regmap; int coulumb_counter; regmap_bulk_read(map, 0xe2, temp_val, 2); coulumb_counter = (((temp_val[0] & 0x7f) << 8) + temp_val[1]) * 1456; return coulumb_counter; } static int axp2585_get_bat(struct axp2585_device_info *di) { u32 val; struct regmap *regmap = di->regmap; if (di->dts_info.pmu_bat_unused == 0) { if (di->axp2585_version_d) { regmap_read(regmap, 0x00, &val); if (!(val & 0x02)) return 1; regmap_read(regmap, 0xbc, &val); if (val & 0xff) return 1; regmap_read(regmap, 0xbd, &val); if (val & 0xff) return 1; return 0; } else { regmap_read(regmap, 0x02, &val); if ((val & (3 << 3)) == 0x18) return 1; else return 0; } } else { return 0; } } static int axp2585_get_direction(struct axp2585_device_info *di) { u32 val; struct regmap *regmap = di->regmap; regmap_read(regmap, 0x02, &val); if (val & 0x01) return 1; else return 0; } static int axp2585_get_bat_status(struct power_supply *psy, union power_supply_propval *val) { struct axp2585_device_info *di = power_supply_get_drvdata(psy); struct regmap *regmap = di->regmap; unsigned int data; int ret; bool bat_det, bat_charging; unsigned int rest_vol; bool usb_valid; ret = regmap_read(regmap, AXP2585_STATUS, &data); if (ret < 0) { dev_dbg(&psy->dev, "error read AXP210X_COM_STAT1\n"); return ret; } bat_charging = ((((data & (7 << 2)) > 0)) && ((data & (7 << 2)) < 0x14)) ? 1 : 0; usb_valid = data & (1 << 1); bat_det = axp2585_get_bat(di); rest_vol = axp2585_get_rest_vol(di); if (bat_det) { if (rest_vol == 100) val->intval = POWER_SUPPLY_STATUS_FULL; else if (bat_charging) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; } else { val->intval = POWER_SUPPLY_STATUS_UNKNOWN; } return 0; } static int axp2585_get_bat_health(void) { return POWER_SUPPLY_HEALTH_GOOD; } static inline int axp2585_icharge_to_mA(u32 reg) { return (int)((((reg >> 8) << 4) | (reg & 0x000f)) << 1); } static int axp2585_get_ibat(struct axp2585_device_info *di) { struct regmap *map = di->regmap; u8 tmp[2]; u32 res; int ret; ret = regmap_bulk_read(map, AXP2585_IBATH_REG, tmp, 2); if (ret < 0) return ret; res = (tmp[0] << 8) | tmp[1]; return axp2585_icharge_to_mA(res); } static inline int axp2585_vbat_to_mV(u32 reg) { return ((int)(((reg >> 8) << 4) | (reg & 0x000F))) * 1200 / 1000; } static int axp2585_get_vbat(struct axp2585_device_info *di) { struct regmap *map = di->regmap; u8 tmp[2]; u32 res; int ret; ret = regmap_bulk_read(map, AXP2585_VBATH_REG, tmp, 2); if (ret < 0) return ret; res = (tmp[0] << 8) | tmp[1]; return axp2585_vbat_to_mV(res); } static inline int axp2585_ibat_to_mA(u32 reg) { return (int)((((reg >> 8) << 4) | (reg & 0x000f)) << 1); } static int axp2585_get_disibat(struct axp2585_device_info *di) { struct regmap *map = di->regmap; u8 tmp[2]; u32 res; int ret; ret = regmap_bulk_read(map, AXP2585_DISIBATH_REG, tmp, 2); if (ret < 0) return ret; res = (tmp[0] << 8) | tmp[1]; return axp2585_ibat_to_mA(res); } static inline int axp_vts_to_mV(u16 reg) { return ((int)(((reg >> 8) << 4) | (reg & 0x000F))) * 800 / 1000; } static inline int axp_vts_to_temp(int data, const struct axp_config_info *axp_config) { int temp; if (data < 80 || axp_config->pmu_bat_temp_enable != 0) 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 int axp2585_get_bat_temp(struct axp2585_device_info *di) { unsigned char temp_val[2]; unsigned short ts_res; int bat_temp_mv, bat_temp; int ret = 0; struct regmap *regmap = di->regmap; ret = regmap_bulk_read(regmap, AXP803_VTS_RES, temp_val, 2); if (ret < 0) return ret; ts_res = ((unsigned short) temp_val[0] << 8) | temp_val[1]; bat_temp_mv = axp_vts_to_mV(ts_res); bat_temp = axp_vts_to_temp(bat_temp_mv, &di->dts_info); pr_debug("bat_temp: %d\n", bat_temp); return bat_temp; } static enum power_supply_property axp2585_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_STATUS, 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_ONLINE, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_TEMP, }; static int axp2585_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { int ret = 0; struct axp2585_device_info *di = power_supply_get_drvdata(psy); u8 temp_val[2]; switch (psp) { case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = psy->desc->name; break; case POWER_SUPPLY_PROP_STATUS: ret = axp2585_get_bat_status(psy, val); break; case POWER_SUPPLY_PROP_HEALTH: val->intval = axp2585_get_bat_health(); break; case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_CHARGE_COUNTER: val->intval = axp2585_get_coulumb_counter(di); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: val->intval = di->dts_info.pmu_init_chgvol; break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: val->intval = di->dts_info.pmu_pwroff_vol; break; case POWER_SUPPLY_PROP_ONLINE: val->intval = axp2585_get_direction(di); break; case POWER_SUPPLY_PROP_PRESENT: val->intval = axp2585_get_bat(di); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = axp2585_get_vbat(di) * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = (axp2585_get_ibat(di) - axp2585_get_disibat(di)) * 1000; break; case POWER_SUPPLY_PROP_CHARGE_FULL: ret = regmap_bulk_read(di->regmap, AXP2585_BATCAP0, temp_val, 2); if (ret < 0) return ret; val->intval = (((temp_val[0] & 0x7f) << 8) + temp_val[1]) * 1456; break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = axp2585_get_rest_vol(di); break; case POWER_SUPPLY_PROP_TEMP: val->intval = axp2585_get_bat_temp(di) * 10; break; default: return -EINVAL; } return ret; } static int axp2585_register_battery(struct axp2585_device_info *di) { int ret = 0; struct power_supply_desc *psy_desc; struct power_supply_config psy_cfg = { .of_node = di->dev->of_node, .drv_data = di, }; psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL); if (!psy_desc) return -ENOMEM; psy_desc->name = "battery"; psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; psy_desc->properties = axp2585_props; psy_desc->num_properties = ARRAY_SIZE(axp2585_props); psy_desc->get_property = axp2585_get_property; psy_desc->use_for_apm = 1; di->bat = power_supply_register(di->dev, psy_desc, &psy_cfg); if (IS_ERR(di->bat)) { dev_err(di->dev, "failed to register battery\n"); ret = PTR_ERR(di->bat); goto err1; } return ret; err1: power_supply_unregister(di->bat); return ret; } static void axp2585_teardown_battery(struct axp2585_device_info *di) { if (di->bat) power_supply_unregister(di->bat); } /* show pwm-led when battery capacity low. */ #ifdef CONFIG_AXP2585_LOW_WARNING_LED irqreturn_t axp2585_irq_handler_low_warn1(int irq, void *data) { struct axp2585_device_info *di = data; pr_debug("%s: enter interrupt %d\n", __func__, irq); pr_debug("==========battery capacity low warning!!==========\n"); pr_debug("=============need to charge!!=============\n"); power_supply_changed(di->bat); /* call notifier to show pwm-led*/ raw_notifier_call_chain(&axp2585_notifier_list, LOW_WARNING_EVENT, NULL); return IRQ_HANDLED; } #endif static irqreturn_t axp2585_irq_handler_thread(int irq, void *data) { struct axp2585_device_info *di = data; pr_debug("%s: enter interrupt %d\n", __func__, irq); power_supply_changed(di->bat); return IRQ_HANDLED; } enum axp2585_virq_index { AXP2585_VIRQ_BAT_IN, AXP2585_VIRQ_BAT_OUT, AXP2585_VIRQ_CHARGING, AXP2585_VIRQ_CHARGE_OVER, AXP2585_VIRQ_LOW_WARNING1, AXP2585_VIRQ_LOW_WARNING2, AXP2585_VIRQ_TC_IN, AXP2585_VIRQ_TC_OUT, AXP2585_VIRQ_BAT_UNTEMP_WORK, AXP2585_VIRQ_BAT_OVTEMP_WORK, AXP2585_VIRQ_BAT_UNTEMP_CHG, AXP2585_VIRQ_BAT_OVTEMP_CHG, AXP2585_VIRQ_MAX_VIRQ, }; static struct axp_interrupts axp_charger_irq[] = { [AXP2585_VIRQ_BAT_IN] = { "bat in", axp2585_irq_handler_thread }, [AXP2585_VIRQ_BAT_OUT] = { "bat out", axp2585_irq_handler_thread }, [AXP2585_VIRQ_CHARGING] = { "charging", axp2585_irq_handler_thread }, [AXP2585_VIRQ_CHARGE_OVER] = { "charge over", axp2585_irq_handler_thread }, #ifdef CONFIG_AXP2585_LOW_WARNING_LED [AXP2585_VIRQ_LOW_WARNING1] = { "low warning1", axp2585_irq_handler_low_warn1 }, #else [AXP2585_VIRQ_LOW_WARNING1] = { "low warning1", axp2585_irq_handler_thread }, #endif [AXP2585_VIRQ_LOW_WARNING2] = { "low warning2", axp2585_irq_handler_thread }, [AXP2585_VIRQ_TC_IN] = { "tc in", axp2585_irq_handler_thread }, [AXP2585_VIRQ_TC_OUT] = { "tc out", axp2585_irq_handler_thread }, [AXP2585_VIRQ_BAT_UNTEMP_WORK] = { "bat untemp work", axp2585_irq_handler_thread }, [AXP2585_VIRQ_BAT_OVTEMP_WORK] = { "bat ovtemp work", axp2585_irq_handler_thread }, [AXP2585_VIRQ_BAT_UNTEMP_CHG] = { "bat untemp chg", axp2585_irq_handler_thread }, [AXP2585_VIRQ_BAT_OVTEMP_CHG] = { "bat ovtemp chg", axp2585_irq_handler_thread }, }; static int axp2585_notifier_event(struct notifier_block *nb, unsigned long event, void *v) { switch (event) { #ifdef CONFIG_AXP2585_LOW_WARNING_LED int axp_leds_show(void); case LOW_WARNING_EVENT: axp_leds_show(); break; #endif default: pr_err("axp2585: unknow notifier event\n"); break; } return NOTIFY_DONE; } static int axp2585_notifier_init(struct axp2585_device_info *di) { di->notifier.notifier_call = axp2585_notifier_event; raw_notifier_chain_register(&axp2585_notifier_list, &di->notifier); return 0; } static void axp2585_set_chg_cur(struct regmap *regmap, u32 cur) { u8 tmp = 0; tmp = cur / 64; if (tmp > 0x3f) tmp = 0x3f; regmap_update_bits(regmap, 0x8b, 0x3f, tmp); } static void axp2585_charger_init(struct axp2585_device_info *di) { unsigned char ocv_cap[32]; unsigned int val; int cur_coulomb_counter, rdc; int i; int update_min_times[8] = {30, 60, 120, 164, 0, 5, 10, 20}; int ocv_cou_adjust_time[4] = {60, 120, 15, 30}; struct regmap *regmap = di->regmap; struct axp_config_info *dinfo = &di->dts_info; /* set chg time */ if (dinfo->pmu_init_chg_pretime < 40) dinfo->pmu_init_chg_pretime = 40; val = (dinfo->pmu_init_chg_pretime - 40)/10; if (val >= 3) val = 3; val = 0x80 + (val << 5); regmap_update_bits(regmap, 0x8e, 0xe0, val); if (dinfo->pmu_init_chg_csttime <= 60 * 5) val = 0; else if (dinfo->pmu_init_chg_csttime <= 60 * 8) val = 1; else if (dinfo->pmu_init_chg_csttime <= 60 * 12) val = 2; else if (dinfo->pmu_init_chg_csttime <= 60 * 20) val = 3; else val = 3; val = (val << 1) + 0x01; regmap_update_bits(regmap, 0x8d, 0x07, val); /* adc set */ val = AXP2585_ADC_BATVOL_ENABLE | AXP2585_ADC_BATCUR_ENABLE; if (dinfo->pmu_bat_temp_enable != 0) val = val | AXP2585_ADC_TSVOL_ENABLE; regmap_update_bits(regmap, AXP2585_ADC_CONTROL, AXP2585_ADC_BATVOL_ENABLE | AXP2585_ADC_BATCUR_ENABLE | AXP2585_ADC_TSVOL_ENABLE, val); regmap_read(regmap, AXP2585_TS_PIN_CONTROL, &val); switch (dinfo->pmu_init_adc_freq / 100) { case 1: val &= ~(3 << 5); break; case 2: val &= ~(3 << 5); val |= 1 << 5; break; case 4: val &= ~(3 << 5); val |= 2 << 5; break; case 8: val &= ~(3 << 5); val |= 3 << 5; break; default: break; } if (dinfo->pmu_bat_temp_enable != 0) val &= ~(1 << 7); regmap_write(regmap, AXP2585_TS_PIN_CONTROL, val); /* bat para */ regmap_write(regmap, AXP2585_WARNING_LEVEL, ((dinfo->pmu_battery_warning_level1 - 5) << 4) + dinfo->pmu_battery_warning_level2); if (dinfo->pmu_init_chgvol < 3840) dinfo->pmu_init_chgvol = 3840; val = (dinfo->pmu_init_chgvol - 3840)/16; if (val > 0x30) val = 0x30; val <<= 2; regmap_update_bits(regmap, AXP2585_CHARGE_CONTROL2, 0xfc, val); ocv_cap[0] = dinfo->pmu_bat_para1; ocv_cap[1] = dinfo->pmu_bat_para2; ocv_cap[2] = dinfo->pmu_bat_para3; ocv_cap[3] = dinfo->pmu_bat_para4; ocv_cap[4] = dinfo->pmu_bat_para5; ocv_cap[5] = dinfo->pmu_bat_para6; ocv_cap[6] = dinfo->pmu_bat_para7; ocv_cap[7] = dinfo->pmu_bat_para8; ocv_cap[8] = dinfo->pmu_bat_para9; ocv_cap[9] = dinfo->pmu_bat_para10; ocv_cap[10] = dinfo->pmu_bat_para11; ocv_cap[11] = dinfo->pmu_bat_para12; ocv_cap[12] = dinfo->pmu_bat_para13; ocv_cap[13] = dinfo->pmu_bat_para14; ocv_cap[14] = dinfo->pmu_bat_para15; ocv_cap[15] = dinfo->pmu_bat_para16; ocv_cap[16] = dinfo->pmu_bat_para17; ocv_cap[17] = dinfo->pmu_bat_para18; ocv_cap[18] = dinfo->pmu_bat_para19; ocv_cap[19] = dinfo->pmu_bat_para20; ocv_cap[20] = dinfo->pmu_bat_para21; ocv_cap[21] = dinfo->pmu_bat_para22; ocv_cap[22] = dinfo->pmu_bat_para23; ocv_cap[23] = dinfo->pmu_bat_para24; ocv_cap[24] = dinfo->pmu_bat_para25; ocv_cap[25] = dinfo->pmu_bat_para26; ocv_cap[26] = dinfo->pmu_bat_para27; ocv_cap[27] = dinfo->pmu_bat_para28; ocv_cap[28] = dinfo->pmu_bat_para29; ocv_cap[29] = dinfo->pmu_bat_para30; ocv_cap[30] = dinfo->pmu_bat_para31; ocv_cap[31] = dinfo->pmu_bat_para32; regmap_bulk_write(regmap, 0xC0, ocv_cap, 32); /* Init CHGLED function */ if (dinfo->pmu_chgled_func) regmap_update_bits(regmap, 0x90, 0x80, 0x80); else regmap_update_bits(regmap, 0x90, 0x80, 0x00); regmap_update_bits(regmap, 0x90, 0x07, dinfo->pmu_chgled_type); /* init battery capacity correct function */ if (dinfo->pmu_batt_cap_correct) regmap_update_bits(regmap, 0xb8, 0x20, 0x20); else regmap_update_bits(regmap, 0xb8, 0x20, 0x0); /* battery detect enable */ if (!(di->axp2585_version_d)) { if (dinfo->pmu_batdeten) regmap_update_bits(regmap, 0x8e, 0x08, 0x08); else regmap_update_bits(regmap, 0x8e, 0x08, 0x0); } /* RDC initial */ regmap_read(regmap, AXP2585_RDC0, &val); if ((dinfo->pmu_battery_rdc) && (!(val & 0x40))) { rdc = (dinfo->pmu_battery_rdc * 10000 + 5371) / 10742; regmap_write(regmap, AXP2585_RDC0, ((rdc >> 8)& 0x1F)|0x80); regmap_write(regmap, AXP2585_RDC1, rdc & 0x00FF); } regmap_read(regmap, AXP2585_BATCAP0, &val); if ((dinfo->pmu_battery_cap) && (!(val & 0x80))) { cur_coulomb_counter = dinfo->pmu_battery_cap * 1000 / 1456; regmap_write(regmap, AXP2585_BATCAP0, ((cur_coulomb_counter >> 8) | 0x80)); regmap_write(regmap, AXP2585_BATCAP1, cur_coulomb_counter & 0x00FF); } else if (!dinfo->pmu_battery_cap) { regmap_write(regmap, AXP2585_BATCAP0, 0x00); regmap_write(regmap, AXP2585_BATCAP1, 0x00); } if (dinfo->pmu_bat_temp_enable != 0) { regmap_write(regmap, AXP2585_VLTF_CHARGE, dinfo->pmu_bat_charge_ltf * 10 / 128); regmap_write(regmap, AXP2585_VHTF_CHARGE, dinfo->pmu_bat_charge_htf * 10 / 128); regmap_write(regmap, AXP2585_VLTF_WORK, dinfo->pmu_bat_shutdown_ltf * 10 / 128); regmap_write(regmap, AXP2585_VHTF_WORK, dinfo->pmu_bat_shutdown_htf * 10 / 128); } /*enable fast charge */ regmap_update_bits(regmap, 0x31, 0x04, 0x04); /*set POR time as 16s*/ regmap_update_bits(regmap, AXP2585_POK_SET, 0x30, 0x30); /* avoid the timer counter error*/ regmap_update_bits(regmap, AXP2585_TIMER2_SET, 0x10, 0x0); for (i = 0; i < ARRAY_SIZE(update_min_times); i++) { if (update_min_times[i] == dinfo->pmu_update_min_time) break; } regmap_update_bits(regmap, AXP2585_ADJUST_PARA, 0x7, i); /*initial the ocv_cou_adjust_time*/ for (i = 0; i < ARRAY_SIZE(ocv_cou_adjust_time); i++) { if (ocv_cou_adjust_time[i] == dinfo->pmu_ocv_cou_adjust_time) break; } i <<= 6; regmap_update_bits(regmap, AXP2585_ADJUST_PARA1, 0xc0, i); /* init the chg runtime_cur */ axp2585_set_chg_cur(regmap, dinfo->pmu_runtime_chgcur); /*type-c cc logic init*/ #ifdef CONFIG_TYPE_C regmap_update_bits(regmap, AXP2585_CC_EN, 0x02, 0x02); regmap_update_bits(regmap, AXP2585_CC_LOW_POWER_CTRL, 0x04, 0x00); regmap_update_bits(regmap, AXP2585_CC_MODE_CTRL, 0x03, 0x03); #endif } #define AXP_OF_PROP_READ(name, def_value)\ do {\ if (of_property_read_u32(node, #name, &axp_config->name))\ axp_config->name = def_value;\ } while (0) int axp2585_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_ac_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->wakeup_bat_in = of_property_read_bool(node, "wakeup_bat_in"); axp_config->wakeup_bat_out = of_property_read_bool(node, "wakeup_bat_out"); axp_config->wakeup_bat_charging = of_property_read_bool(node, "wakeup_bat_charging"); axp_config->wakeup_bat_charge_over = of_property_read_bool(node, "wakeup_bat_charge_over"); axp_config->wakeup_low_warning1 = of_property_read_bool(node, "wakeup_low_warning1"); axp_config->wakeup_low_warning2 = of_property_read_bool(node, "wakeup_low_warning2"); axp_config->wakeup_bat_untemp_work = of_property_read_bool(node, "wakeup_bat_untemp_work"); axp_config->wakeup_bat_ovtemp_work = of_property_read_bool(node, "wakeup_bat_ovtemp_work"); axp_config->wakeup_untemp_chg = of_property_read_bool(node, "wakeup_bat_untemp_chg"); axp_config->wakeup_ovtemp_chg = of_property_read_bool(node, "wakeup_bat_ovtemp_chg"); // axp_config->wakeup_tc_in = // of_property_read_bool(node, "wakeup_tc_in"); // axp_config->wakeup_tc_out = // of_property_read_bool(node, "wakeup_tc_out"); return 0; } static void axp2585_parse_device_tree(struct axp2585_device_info *di) { int ret; struct axp_config_info *cfg; /* set input current limit */ if (!di->dev->of_node) { pr_info("can not find device tree\n"); return; } cfg = &di->dts_info; ret = axp2585_charger_dt_parse(di->dev->of_node, cfg); if (ret) { pr_info("can not parse device tree err\n"); return; } } static void axp2585_bat_power_monitor(struct work_struct *work) { struct axp2585_device_info *di = container_of(work, typeof(*di), bat_chk.work); power_supply_changed(di->bat); schedule_delayed_work(&di->bat_chk, msecs_to_jiffies(10*1000)); } static int axp2585_battery_probe(struct platform_device *pdev) { int ret = 0; int i = 0, irq; unsigned int val; struct axp2585_device_info *di; struct axp20x_dev *axp_dev = dev_get_drvdata(pdev->dev.parent); struct device_node *node = pdev->dev.of_node; if (!of_device_is_available(node)) { pr_err("axp2585-battery device is not configed\n"); return -ENODEV; } if (!axp_dev->irq) { pr_err("can not register axp2585-charger without irq\n"); return -EINVAL; } di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); if (di == NULL) { pr_err("axp2585_device_info alloc failed\n"); ret = -ENOMEM; goto err; } di->name = "axp2585_power"; di->dev = &pdev->dev; di->regmap = axp_dev->regmap; /* for device tree parse */ axp2585_parse_device_tree(di); /* check bmu1760 d or c version */ regmap_read(di->regmap, 0x03, &val); di->axp2585_version_d = (val & 0x20); axp2585_charger_init(di); ret = axp2585_register_battery(di); if (ret < 0) { dev_err(di->dev, "axp210x register battery dev fail!\n"); goto err; } for (i = 0; i < ARRAY_SIZE(axp_charger_irq); i++) { irq = platform_get_irq_byname(pdev, axp_charger_irq[i].name); if (irq < 0) continue; irq = regmap_irq_get_virq(axp_dev->regmap_irqc, irq); if (irq < 0) { dev_err(&pdev->dev, "can not get irq\n"); return irq; } /* we use this variable to suspend irq */ axp_charger_irq[i].irq = irq; ret = devm_request_any_context_irq(&pdev->dev, irq, axp_charger_irq[i].isr, 0, axp_charger_irq[i].name, di); if (ret < 0) { dev_err(&pdev->dev, "failed to request %s IRQ %d: %d\n", axp_charger_irq[i].name, irq, ret); return ret; } else { ret = 0; } dev_dbg(&pdev->dev, "Requested %s IRQ %d: %d\n", axp_charger_irq[i].name, irq, ret); } platform_set_drvdata(pdev, di); INIT_DELAYED_WORK(&di->bat_chk, axp2585_bat_power_monitor); schedule_delayed_work(&di->bat_chk, msecs_to_jiffies(10 * 1000)); axp2585_notifier_init(di); return ret; err: pr_err("%s,probe fail, ret = %d\n", __func__, ret); return ret; } static int axp2585_charger_remove(struct platform_device *pdev) { struct axp2585_device_info *di = platform_get_drvdata(pdev); dev_dbg(&pdev->dev, "==============AXP2585 unegister==============\n"); axp2585_teardown_battery(di); dev_dbg(&pdev->dev, "axp210x teardown battery dev\n"); return 0; } static void axp2585_icchg_set(struct axp2585_device_info *di, int mA) { mA = mA / 64; if (mA > 0x3f) mA = 0x3f; /* bit 5:0 is the ctrl bit */ regmap_update_bits(di->regmap, AXP2585_ICC_CFG, GENMASK(5, 0), mA); } static inline void axp2585_irq_set(unsigned int irq, bool enable) { if (enable) enable_irq(irq); else disable_irq(irq); } static void axp2585_virq_dts_set(struct axp2585_device_info *di, bool enable) { struct axp_config_info *dts_info = &di->dts_info; if (!dts_info->wakeup_bat_in) axp2585_irq_set(axp_charger_irq[AXP2585_VIRQ_BAT_IN].irq, enable); if (!dts_info->wakeup_bat_out) axp2585_irq_set(axp_charger_irq[AXP2585_VIRQ_BAT_OUT].irq, enable); if (!dts_info->wakeup_bat_charging) axp2585_irq_set(axp_charger_irq[AXP2585_VIRQ_CHARGING].irq, enable); if (!dts_info->wakeup_bat_charge_over) axp2585_irq_set(axp_charger_irq[AXP2585_VIRQ_CHARGE_OVER].irq, enable); if (!dts_info->wakeup_low_warning1) axp2585_irq_set(axp_charger_irq[AXP2585_VIRQ_LOW_WARNING1].irq, enable); if (!dts_info->wakeup_low_warning2) axp2585_irq_set(axp_charger_irq[AXP2585_VIRQ_LOW_WARNING2].irq, enable); // if (!dts_info->wakeup_tc_in) // axp2585_irq_set(axp_charger_irq[AXP2585_VIRQ_TC_IN].irq, // enable); // if (!dts_info->wakeup_tc_out) // axp2585_irq_set(axp_charger_irq[AXP2585_VIRQ_TC_OUT].irq, // enable); if (!dts_info->wakeup_bat_untemp_work) axp2585_irq_set( axp_charger_irq[AXP2585_VIRQ_BAT_UNTEMP_WORK].irq, enable); if (!dts_info->wakeup_bat_ovtemp_work) axp2585_irq_set( axp_charger_irq[AXP2585_VIRQ_BAT_OVTEMP_WORK].irq, enable); if (!dts_info->wakeup_untemp_chg) axp2585_irq_set( axp_charger_irq[AXP2585_VIRQ_BAT_UNTEMP_CHG].irq, enable); if (!dts_info->wakeup_ovtemp_chg) axp2585_irq_set( axp_charger_irq[AXP2585_VIRQ_BAT_OVTEMP_CHG].irq, enable); } static void axp2585_shutdown(struct platform_device *p) { struct axp2585_device_info *di = platform_get_drvdata(p); cancel_delayed_work_sync(&di->bat_chk); axp2585_icchg_set(di, di->dts_info.pmu_shutdown_chgcur); } static int axp2585_suspend(struct platform_device *p, pm_message_t state) { struct axp2585_device_info *di = platform_get_drvdata(p); cancel_delayed_work_sync(&di->bat_chk); axp2585_icchg_set(di, di->dts_info.pmu_suspend_chgcur); axp2585_virq_dts_set(di, false); return 0; } static int axp2585_resume(struct platform_device *p) { struct axp2585_device_info *di = platform_get_drvdata(p); schedule_delayed_work(&di->bat_chk, 0); axp2585_icchg_set(di, di->dts_info.pmu_runtime_chgcur); axp2585_virq_dts_set(di, true); return 0; } static const struct of_device_id axp2585_power_of_match[] = { { .compatible = "x-powers,axp2585-battery-power-supply", }, {} }; MODULE_DEVICE_TABLE(of, axp2585_power_of_match); static struct platform_driver axp2585_charger_driver = { .driver = { .name = "axp2585-battery-power-supply", .of_match_table = axp2585_power_of_match, }, .probe = axp2585_battery_probe, .remove = axp2585_charger_remove, .shutdown = axp2585_shutdown, .suspend = axp2585_suspend, .resume = axp2585_resume, }; module_platform_driver(axp2585_charger_driver); MODULE_AUTHOR("wangxiaoliang "); MODULE_DESCRIPTION("axp2585 i2c driver"); MODULE_LICENSE("GPL");