/* One hour expressed in seconds */ #define ONE_HOUR_IN_SECONDS 3600
/* Five minutes expressed in seconds */ #define FIVE_MINUTES_IN_SECONDS 300
/* * This is the battery capacity limit that will trigger a new * full charging cycle in the case where maintenance charging * has been disabled
*/ #define AB8500_RECHARGE_CAP 95
struct ab8500_chargalg_charger_info { enum ab8500_chargers conn_chg; enum ab8500_chargers prev_conn_chg; enum ab8500_chargers online_chg; enum ab8500_chargers prev_online_chg; enum ab8500_chargers charger_type; bool usb_chg_ok; bool ac_chg_ok; int usb_volt_uv; int usb_curr_ua; int ac_volt_uv; int ac_curr_ua; int usb_vset_uv; int usb_iset_ua; int ac_vset_uv; int ac_iset_ua;
};
struct ab8500_chargalg_battery_data { int temp; int volt_uv; int avg_curr_ua; int inst_curr_ua; int percent;
};
/** * struct ab8500_charge_curr_maximization - Charger maximization parameters * @original_iset_ua: the non optimized/maximised charger current * @current_iset_ua: the charging current used at this moment * @condition_cnt: number of iterations needed before a new charger current is set * @max_current_ua: maximum charger current * @wait_cnt: to avoid too fast current step down in case of charger * voltage collapse, we insert this delay between step * down * @level: tells in how many steps the charging current has been increased
*/ struct ab8500_charge_curr_maximization { int original_iset_ua; int current_iset_ua; int condition_cnt; int max_current_ua; int wait_cnt;
u8 level;
};
/** * struct ab8500_chargalg - ab8500 Charging algorithm device information * @dev: pointer to the structure device * @charge_status: battery operating status * @eoc_cnt: counter used to determine end-of_charge * @maintenance_chg: indicate if maintenance charge is active * @t_hyst_norm temperature hysteresis when the temperature has been * over or under normal limits * @t_hyst_lowhigh temperature hysteresis when the temperature has been * over or under the high or low limits * @charge_state: current state of the charging algorithm * @ccm charging current maximization parameters * @chg_info: information about connected charger types * @batt_data: data of the battery * @bm: Platform specific battery management information * @parent: pointer to the struct ab8500 * @chargalg_psy: structure that holds the battery properties exposed by * the charging algorithm * @events: structure for information about events triggered * @chargalg_wq: work queue for running the charging algorithm * @chargalg_periodic_work: work to run the charging algorithm periodically * @chargalg_wd_work: work to kick the charger watchdog periodically * @chargalg_work: work to run the charging algorithm instantly * @safety_timer: charging safety timer * @maintenance_timer: maintenance charging timer * @chargalg_kobject: structure of type kobject
*/ struct ab8500_chargalg { struct device *dev; int charge_status; int eoc_cnt; bool maintenance_chg; int t_hyst_norm; int t_hyst_lowhigh; enum ab8500_chargalg_states charge_state; struct ab8500_charge_curr_maximization ccm; struct ab8500_chargalg_charger_info chg_info; struct ab8500_chargalg_battery_data batt_data; struct ab8500 *parent; struct ab8500_bm_data *bm; struct power_supply *chargalg_psy; struct ux500_charger *ac_chg; struct ux500_charger *usb_chg; struct ab8500_chargalg_events events; struct workqueue_struct *chargalg_wq; struct delayed_work chargalg_periodic_work; struct delayed_work chargalg_wd_work; struct work_struct chargalg_work; struct hrtimer safety_timer; struct hrtimer maintenance_timer; struct kobject chargalg_kobject;
};
/** * ab8500_chargalg_safety_timer_expired() - Expiration of the safety timer * @timer: pointer to the hrtimer structure * * This function gets called when the safety timer for the charger * expires
*/ staticenum hrtimer_restart
ab8500_chargalg_safety_timer_expired(struct hrtimer *timer)
{ struct ab8500_chargalg *di = container_of(timer, struct ab8500_chargalg,
safety_timer);
dev_err(di->dev, "Safety timer expired\n");
di->events.safety_timer_expired = true;
/* Trigger execution of the algorithm instantly */
queue_work(di->chargalg_wq, &di->chargalg_work);
return HRTIMER_NORESTART;
}
/** * ab8500_chargalg_maintenance_timer_expired() - Expiration of * the maintenance timer * @timer: pointer to the timer structure * * This function gets called when the maintenance timer * expires
*/ staticenum hrtimer_restart
ab8500_chargalg_maintenance_timer_expired(struct hrtimer *timer)
{
/* Trigger execution of the algorithm instantly */
queue_work(di->chargalg_wq, &di->chargalg_work);
return HRTIMER_NORESTART;
}
/** * ab8500_chargalg_state_to() - Change charge state * @di: pointer to the ab8500_chargalg structure * * This function gets called when a charge state change should occur
*/ staticvoid ab8500_chargalg_state_to(struct ab8500_chargalg *di, enum ab8500_chargalg_states state)
{
dev_dbg(di->dev, "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n",
di->charge_state == state ? "NO" : "YES",
di->charge_state,
states[di->charge_state],
state,
states[state]);
/** * ab8500_chargalg_check_charger_connection() - Check charger connection change * @di: pointer to the ab8500_chargalg structure * * This function will check if there is a change in the charger connection * and change charge state accordingly. AC has precedence over USB.
*/ staticint ab8500_chargalg_check_charger_connection(struct ab8500_chargalg *di)
{ if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg) { /* Charger state changed since last update */ if (di->chg_info.conn_chg & AC_CHG) {
dev_info(di->dev, "Charging source is AC\n"); if (di->chg_info.charger_type != AC_CHG) {
di->chg_info.charger_type = AC_CHG;
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
}
} elseif (di->chg_info.conn_chg & USB_CHG) {
dev_info(di->dev, "Charging source is USB\n");
di->chg_info.charger_type = USB_CHG;
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
} else {
dev_dbg(di->dev, "Charging source is OFF\n");
di->chg_info.charger_type = NO_CHG;
ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT);
}
di->chg_info.prev_conn_chg = di->chg_info.conn_chg;
} return di->chg_info.conn_chg;
}
/** * ab8500_chargalg_start_safety_timer() - Start charging safety timer * @di: pointer to the ab8500_chargalg structure * * The safety timer is used to avoid overcharging of old or bad batteries. * There are different timers for AC and USB
*/ staticvoid ab8500_chargalg_start_safety_timer(struct ab8500_chargalg *di)
{ /* Charger-dependent expiration time in hours*/ int timer_expiration = 0;
switch (di->chg_info.charger_type) { case AC_CHG:
timer_expiration = di->bm->main_safety_tmr_h; break;
case USB_CHG:
timer_expiration = di->bm->usb_safety_tmr_h; break;
default:
dev_err(di->dev, "Unknown charger to charge from\n"); break;
}
/** * ab8500_chargalg_stop_safety_timer() - Stop charging safety timer * @di: pointer to the ab8500_chargalg structure * * The safety timer is stopped whenever the NORMAL state is exited
*/ staticvoid ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di)
{ if (hrtimer_try_to_cancel(&di->safety_timer) >= 0)
di->events.safety_timer_expired = false;
}
/** * ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer * @di: pointer to the ab8500_chargalg structure * @duration: duration of the maintenance timer in minutes * * The maintenance timer is used to maintain the charge in the battery once * the battery is considered full. These timers are chosen to match the * discharge curve of the battery
*/ staticvoid ab8500_chargalg_start_maintenance_timer(struct ab8500_chargalg *di, int duration)
{ /* Set a timer in minutes with a 30 second range */
hrtimer_set_expires_range(&di->maintenance_timer,
ktime_set(duration * 60, 0),
ktime_set(30, 0));
di->events.maintenance_timer_expired = false;
hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL);
}
/** * ab8500_chargalg_stop_maintenance_timer() - Stop maintenance timer * @di: pointer to the ab8500_chargalg structure * * The maintenance timer is stopped whenever maintenance ends or when another * state is entered
*/ staticvoid ab8500_chargalg_stop_maintenance_timer(struct ab8500_chargalg *di)
{ if (hrtimer_try_to_cancel(&di->maintenance_timer) >= 0)
di->events.maintenance_timer_expired = false;
}
/** * ab8500_chargalg_kick_watchdog() - Kick charger watchdog * @di: pointer to the ab8500_chargalg structure * * The charger watchdog have to be kicked periodically whenever the charger is * on, else the ABB will reset the system
*/ staticint ab8500_chargalg_kick_watchdog(struct ab8500_chargalg *di)
{ /* Check if charger exists and kick watchdog if charging */ if (di->ac_chg && di->ac_chg->ops.kick_wd &&
di->chg_info.online_chg & AC_CHG) { return di->ac_chg->ops.kick_wd(di->ac_chg);
} elseif (di->usb_chg && di->usb_chg->ops.kick_wd &&
di->chg_info.online_chg & USB_CHG) return di->usb_chg->ops.kick_wd(di->usb_chg);
return -ENXIO;
}
/** * ab8500_chargalg_ac_en() - Turn on/off the AC charger * @di: pointer to the ab8500_chargalg structure * @enable: charger on/off * @vset_uv: requested charger output voltage in microvolt * @iset_ua: requested charger output current in microampere * * The AC charger will be turned on/off with the requested charge voltage and * current
*/ staticint ab8500_chargalg_ac_en(struct ab8500_chargalg *di, int enable, int vset_uv, int iset_ua)
{ if (!di->ac_chg || !di->ac_chg->ops.enable) return -ENXIO;
/* Select maximum of what both the charger and the battery supports */ if (di->ac_chg->max_out_volt_uv)
vset_uv = min(vset_uv, di->ac_chg->max_out_volt_uv); if (di->ac_chg->max_out_curr_ua)
iset_ua = min(iset_ua, di->ac_chg->max_out_curr_ua);
/** * ab8500_chargalg_usb_en() - Turn on/off the USB charger * @di: pointer to the ab8500_chargalg structure * @enable: charger on/off * @vset_uv: requested charger output voltage in microvolt * @iset_ua: requested charger output current in microampere * * The USB charger will be turned on/off with the requested charge voltage and * current
*/ staticint ab8500_chargalg_usb_en(struct ab8500_chargalg *di, int enable, int vset_uv, int iset_ua)
{ if (!di->usb_chg || !di->usb_chg->ops.enable) return -ENXIO;
/* Select maximum of what both the charger and the battery supports */ if (di->usb_chg->max_out_volt_uv)
vset_uv = min(vset_uv, di->usb_chg->max_out_volt_uv); if (di->usb_chg->max_out_curr_ua)
iset_ua = min(iset_ua, di->usb_chg->max_out_curr_ua);
/** * ab8500_chargalg_update_chg_curr() - Update charger current * @di: pointer to the ab8500_chargalg structure * @iset_ua: requested charger output current in microampere * * The charger output current will be updated for the charger * that is currently in use
*/ staticint ab8500_chargalg_update_chg_curr(struct ab8500_chargalg *di, int iset_ua)
{ /* Check if charger exists and update current if charging */ if (di->ac_chg && di->ac_chg->ops.update_curr &&
di->chg_info.charger_type & AC_CHG) { /* * Select maximum of what both the charger * and the battery supports
*/ if (di->ac_chg->max_out_curr_ua)
iset_ua = min(iset_ua, di->ac_chg->max_out_curr_ua);
di->chg_info.ac_iset_ua = iset_ua;
return di->ac_chg->ops.update_curr(di->ac_chg, iset_ua);
} elseif (di->usb_chg && di->usb_chg->ops.update_curr &&
di->chg_info.charger_type & USB_CHG) { /* * Select maximum of what both the charger * and the battery supports
*/ if (di->usb_chg->max_out_curr_ua)
iset_ua = min(iset_ua, di->usb_chg->max_out_curr_ua);
/** * ab8500_chargalg_stop_charging() - Stop charging * @di: pointer to the ab8500_chargalg structure * * This function is called from any state where charging should be stopped. * All charging is disabled and all status parameters and timers are changed * accordingly
*/ staticvoid ab8500_chargalg_stop_charging(struct ab8500_chargalg *di)
{
ab8500_chargalg_ac_en(di, false, 0, 0);
ab8500_chargalg_usb_en(di, false, 0, 0);
ab8500_chargalg_stop_safety_timer(di);
ab8500_chargalg_stop_maintenance_timer(di);
di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
di->maintenance_chg = false;
cancel_delayed_work(&di->chargalg_wd_work);
power_supply_changed(di->chargalg_psy);
}
/** * ab8500_chargalg_hold_charging() - Pauses charging * @di: pointer to the ab8500_chargalg structure * * This function is called in the case where maintenance charging has been * disabled and instead a battery voltage mode is entered to check when the * battery voltage has reached a certain recharge voltage
*/ staticvoid ab8500_chargalg_hold_charging(struct ab8500_chargalg *di)
{
ab8500_chargalg_ac_en(di, false, 0, 0);
ab8500_chargalg_usb_en(di, false, 0, 0);
ab8500_chargalg_stop_safety_timer(di);
ab8500_chargalg_stop_maintenance_timer(di);
di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
di->maintenance_chg = false;
cancel_delayed_work(&di->chargalg_wd_work);
power_supply_changed(di->chargalg_psy);
}
/** * ab8500_chargalg_start_charging() - Start the charger * @di: pointer to the ab8500_chargalg structure * @vset_uv: requested charger output voltage in microvolt * @iset_ua: requested charger output current in microampere * * A charger will be enabled depending on the requested charger type that was * detected previously.
*/ staticvoid ab8500_chargalg_start_charging(struct ab8500_chargalg *di, int vset_uv, int iset_ua)
{ switch (di->chg_info.charger_type) { case AC_CHG:
dev_dbg(di->dev, "AC parameters: Vset %d, Ich %d\n", vset_uv, iset_ua);
ab8500_chargalg_usb_en(di, false, 0, 0);
ab8500_chargalg_ac_en(di, true, vset_uv, iset_ua); break;
case USB_CHG:
dev_dbg(di->dev, "USB parameters: Vset %d, Ich %d\n", vset_uv, iset_ua);
ab8500_chargalg_ac_en(di, false, 0, 0);
ab8500_chargalg_usb_en(di, true, vset_uv, iset_ua); break;
default:
dev_err(di->dev, "Unknown charger to charge from\n"); break;
}
}
/** * ab8500_chargalg_check_temp() - Check battery temperature ranges * @di: pointer to the ab8500_chargalg structure * * The battery temperature is checked against the predefined limits and the * charge state is changed accordingly
*/ staticvoid ab8500_chargalg_check_temp(struct ab8500_chargalg *di)
{ struct power_supply_battery_info *bi = di->bm->bi;
/** * ab8500_chargalg_check_charger_voltage() - Check charger voltage * @di: pointer to the ab8500_chargalg structure * * Charger voltage is checked against maximum limit
*/ staticvoid ab8500_chargalg_check_charger_voltage(struct ab8500_chargalg *di)
{ if (di->chg_info.usb_volt_uv > di->bm->chg_params->usb_volt_max_uv)
di->chg_info.usb_chg_ok = false; else
di->chg_info.usb_chg_ok = true;
/** * ab8500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled * @di: pointer to the ab8500_chargalg structure * * End-of-charge criteria is fulfilled when the battery voltage is above a * certain limit and the battery current is below a certain limit for a * predefined number of consecutive seconds. If true, the battery is full
*/ staticvoid ab8500_chargalg_end_of_charge(struct ab8500_chargalg *di)
{ if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING &&
di->charge_state == STATE_NORMAL &&
!di->maintenance_chg && (di->batt_data.volt_uv >=
di->bm->bi->voltage_max_design_uv ||
di->events.usb_cv_active || di->events.ac_cv_active) &&
di->batt_data.avg_curr_ua <
di->bm->bi->charge_term_current_ua &&
di->batt_data.avg_curr_ua > 0) { if (++di->eoc_cnt >= EOC_COND_CNT) {
di->eoc_cnt = 0;
di->charge_status = POWER_SUPPLY_STATUS_FULL;
di->maintenance_chg = true;
dev_dbg(di->dev, "EOC reached!\n");
power_supply_changed(di->chargalg_psy);
} else {
dev_dbg(di->dev, " EOC limit reached for the %d" " time, out of %d before EOC\n",
di->eoc_cnt,
EOC_COND_CNT);
}
} else {
di->eoc_cnt = 0;
}
}
/** * ab8500_chargalg_chg_curr_maxim - increases the charger current to * compensate for the system load * @di pointer to the ab8500_chargalg structure * * This maximization function is used to raise the charger current to get the * battery current as close to the optimal value as possible. The battery * current during charging is affected by the system load
*/ staticenum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di)
{
if (!di->bm->maxi->ena_maxi) return MAXIM_RET_NOACTION;
if (di->events.vbus_collapsed) {
dev_dbg(di->dev, "Charger voltage has collapsed %d\n",
di->ccm.wait_cnt); if (di->ccm.wait_cnt == 0) {
dev_dbg(di->dev, "lowering current\n");
di->ccm.wait_cnt++;
di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
di->ccm.max_current_ua = di->ccm.current_iset_ua;
di->ccm.current_iset_ua = di->ccm.max_current_ua;
di->ccm.level--; return MAXIM_RET_CHANGE;
} else {
dev_dbg(di->dev, "waiting\n"); /* Let's go in here twice before lowering curr again */
di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3; return MAXIM_RET_NOACTION;
}
}
ret = ab8500_chargalg_chg_curr_maxim(di); switch (ret) { case MAXIM_RET_CHANGE:
result = ab8500_chargalg_update_chg_curr(di,
di->ccm.current_iset_ua); if (result)
dev_err(di->dev, "failed to set chg curr\n"); break; case MAXIM_RET_IBAT_TOO_HIGH:
result = ab8500_chargalg_update_chg_curr(di,
bi->constant_charge_current_max_ua); if (result)
dev_err(di->dev, "failed to set chg curr\n"); break;
case MAXIM_RET_NOACTION: default: /* Do nothing..*/ break;
}
}
psy = (struct power_supply *)data;
di = power_supply_get_drvdata(psy); /* For all psy where the driver name appears in any supplied_to */
j = match_string(supplicants, ext->num_supplicants, psy->desc->name); if (j < 0) return 0;
/* * If external is not registering 'POWER_SUPPLY_PROP_CAPACITY' to its * property because of handling that sysfs entry on its own, this is * the place to get the battery capacity.
*/ if (!power_supply_get_property(ext, POWER_SUPPLY_PROP_CAPACITY, &ret)) {
di->batt_data.percent = ret.intval;
capacity_updated = true;
}
/* Go through all properties for the psy */ for (j = 0; j < ext->desc->num_properties; j++) { enum power_supply_property prop;
prop = ext->desc->properties[j];
/* * Initialize chargers if not already done.
* The ab8500_charger*/ if (!di->ac_chg &&
ext->desc->type == POWER_SUPPLY_TYPE_MAINS)
di->ac_chg = psy_to_ux500_charger(ext); elseif (!di->usb_chg &&
ext->desc->type == POWER_SUPPLY_TYPE_USB)
di->usb_chg = psy_to_ux500_charger(ext);
if (power_supply_get_property(ext, prop, &ret)) continue; switch (prop) { case POWER_SUPPLY_PROP_PRESENT: switch (ext->desc->type) { case POWER_SUPPLY_TYPE_BATTERY: /* Battery present */ if (ret.intval)
di->events.batt_rem = false; /* Battery removed */ else
di->events.batt_rem = true; break; case POWER_SUPPLY_TYPE_MAINS: /* AC disconnected */ if (!ret.intval &&
(di->chg_info.conn_chg & AC_CHG)) {
di->chg_info.prev_conn_chg =
di->chg_info.conn_chg;
di->chg_info.conn_chg &= ~AC_CHG;
} /* AC connected */ elseif (ret.intval &&
!(di->chg_info.conn_chg & AC_CHG)) {
di->chg_info.prev_conn_chg =
di->chg_info.conn_chg;
di->chg_info.conn_chg |= AC_CHG;
} break; case POWER_SUPPLY_TYPE_USB: /* USB disconnected */ if (!ret.intval &&
(di->chg_info.conn_chg & USB_CHG)) {
di->chg_info.prev_conn_chg =
di->chg_info.conn_chg;
di->chg_info.conn_chg &= ~USB_CHG;
} /* USB connected */ elseif (ret.intval &&
!(di->chg_info.conn_chg & USB_CHG)) {
di->chg_info.prev_conn_chg =
di->chg_info.conn_chg;
di->chg_info.conn_chg |= USB_CHG;
} break; default: break;
} break;
case POWER_SUPPLY_PROP_ONLINE: switch (ext->desc->type) { case POWER_SUPPLY_TYPE_BATTERY: break; case POWER_SUPPLY_TYPE_MAINS: /* AC offline */ if (!ret.intval &&
(di->chg_info.online_chg & AC_CHG)) {
di->chg_info.prev_online_chg =
di->chg_info.online_chg;
di->chg_info.online_chg &= ~AC_CHG;
} /* AC online */ elseif (ret.intval &&
!(di->chg_info.online_chg & AC_CHG)) {
di->chg_info.prev_online_chg =
di->chg_info.online_chg;
di->chg_info.online_chg |= AC_CHG;
queue_delayed_work(di->chargalg_wq,
&di->chargalg_wd_work, 0);
} break; case POWER_SUPPLY_TYPE_USB: /* USB offline */ if (!ret.intval &&
(di->chg_info.online_chg & USB_CHG)) {
di->chg_info.prev_online_chg =
di->chg_info.online_chg;
di->chg_info.online_chg &= ~USB_CHG;
} /* USB online */ elseif (ret.intval &&
!(di->chg_info.online_chg & USB_CHG)) {
di->chg_info.prev_online_chg =
di->chg_info.online_chg;
di->chg_info.online_chg |= USB_CHG;
queue_delayed_work(di->chargalg_wq,
&di->chargalg_wd_work, 0);
} break; default: break;
} break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW: switch (ext->desc->type) { case POWER_SUPPLY_TYPE_BATTERY:
di->batt_data.volt_uv = ret.intval; break; case POWER_SUPPLY_TYPE_MAINS:
di->chg_info.ac_volt_uv = ret.intval; break; case POWER_SUPPLY_TYPE_USB:
di->chg_info.usb_volt_uv = ret.intval; break; default: break;
} break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG: switch (ext->desc->type) { case POWER_SUPPLY_TYPE_MAINS: /* AVG is used to indicate when we are
* in CV mode */ if (ret.intval)
di->events.ac_cv_active = true; else
di->events.ac_cv_active = false;
break; case POWER_SUPPLY_TYPE_USB: /* AVG is used to indicate when we are
* in CV mode */ if (ret.intval)
di->events.usb_cv_active = true; else
di->events.usb_cv_active = false;
break; default: break;
} break;
case POWER_SUPPLY_PROP_TECHNOLOGY: switch (ext->desc->type) { case POWER_SUPPLY_TYPE_BATTERY: if (ret.intval)
di->events.batt_unknown = false; else
di->events.batt_unknown = true;
break; default: break;
} break;
case POWER_SUPPLY_PROP_TEMP:
di->batt_data.temp = ret.intval / 10; break;
case POWER_SUPPLY_PROP_CURRENT_NOW: switch (ext->desc->type) { case POWER_SUPPLY_TYPE_MAINS:
di->chg_info.ac_curr_ua = ret.intval; break; case POWER_SUPPLY_TYPE_USB:
di->chg_info.usb_curr_ua = ret.intval; break; case POWER_SUPPLY_TYPE_BATTERY:
di->batt_data.inst_curr_ua = ret.intval; break; default: break;
} break;
case POWER_SUPPLY_PROP_CURRENT_AVG: switch (ext->desc->type) { case POWER_SUPPLY_TYPE_BATTERY:
di->batt_data.avg_curr_ua = ret.intval; break; case POWER_SUPPLY_TYPE_USB: if (ret.intval)
di->events.vbus_collapsed = true; else
di->events.vbus_collapsed = false; break; default: break;
} break; case POWER_SUPPLY_PROP_CAPACITY: if (!capacity_updated)
di->batt_data.percent = ret.intval; break; default: break;
}
} return 0;
}
/** * ab8500_chargalg_external_power_changed() - callback for power supply changes * @psy: pointer to the structure power_supply * * This function is the entry point of the pointer external_power_changed * of the structure power_supply. * This function gets executed when there is a change in any external power * supply that this driver needs to be notified of.
*/ staticvoid ab8500_chargalg_external_power_changed(struct power_supply *psy)
{ struct ab8500_chargalg *di = power_supply_get_drvdata(psy);
/* * Trigger execution of the algorithm instantly and read * all power_supply properties there instead
*/ if (di->chargalg_wq)
queue_work(di->chargalg_wq, &di->chargalg_work);
}
/** * ab8500_chargalg_time_to_restart() - time to restart CC/CV charging? * @di: charging algorithm state * * This checks if the voltage or capacity of the battery has fallen so * low that we need to restart the CC/CV charge cycle.
*/ staticbool ab8500_chargalg_time_to_restart(struct ab8500_chargalg *di)
{ struct power_supply_battery_info *bi = di->bm->bi;
/* Sanity check - these need to have some reasonable values */ if (!di->batt_data.volt_uv || !di->batt_data.percent) returnfalse;
/* Some batteries tell us at which voltage we should restart charging */ if (bi->charge_restart_voltage_uv > 0) { if (di->batt_data.volt_uv <= bi->charge_restart_voltage_uv) returntrue; /* Else we restart as we reach a certain capacity */
} else { if (di->batt_data.percent <= AB8500_RECHARGE_CAP) returntrue;
}
returnfalse;
}
/** * ab8500_chargalg_algorithm() - Main function for the algorithm * @di: pointer to the ab8500_chargalg structure * * This is the main control function for the charging algorithm. * It is called periodically or when something happens that will * trigger a state change
*/ staticvoid ab8500_chargalg_algorithm(struct ab8500_chargalg *di)
{ conststruct power_supply_maintenance_charge_table *mt; struct power_supply_battery_info *bi = di->bm->bi; int charger_status; int ret;
/* Collect data from all power_supply class devices */
power_supply_for_each_psy(di->chargalg_psy, ab8500_chargalg_get_ext_psy_data);
if (is_ab8500(di->parent)) {
ret = ab8500_chargalg_check_charger_enable(di); if (ret < 0)
dev_err(di->dev, "Checking charger is enabled error" ": Returned Value %d\n", ret);
}
/* * First check if we have a charger connected. * Also we don't allow charging of unknown batteries if configured * this way
*/ if (!charger_status ||
(di->events.batt_unknown && !di->bm->chg_unknown_bat)) { if (di->charge_state != STATE_HANDHELD) {
di->events.safety_timer_expired = false;
ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT);
}
}
/* Safety timer expiration */ elseif (di->events.safety_timer_expired) { if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED)
ab8500_chargalg_state_to(di,
STATE_SAFETY_TIMER_EXPIRED_INIT);
} /* * Check if any interrupts has occurred * that will prevent us from charging
*/
/* Battery removed */ elseif (di->events.batt_rem) { if (di->charge_state != STATE_BATT_REMOVED)
ab8500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT);
} /* Main or USB charger not ok. */ elseif (di->events.mainextchnotok || di->events.usbchargernotok) { /* * If vbus_collapsed is set, we have to lower the charger * current, which is done in the normal state below
*/ if (di->charge_state != STATE_CHG_NOT_OK &&
!di->events.vbus_collapsed)
ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT);
} /* VBUS, Main or VBAT OVV. */ elseif (di->events.vbus_ovv ||
di->events.main_ovv ||
di->events.batt_ovv ||
!di->chg_info.usb_chg_ok ||
!di->chg_info.ac_chg_ok) { if (di->charge_state != STATE_OVV_PROTECT)
ab8500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT);
} /* USB Thermal, stop charging */ elseif (di->events.main_thermal_prot ||
di->events.usb_thermal_prot) { if (di->charge_state != STATE_HW_TEMP_PROTECT)
ab8500_chargalg_state_to(di,
STATE_HW_TEMP_PROTECT_INIT);
} /* Battery temp over/under */ elseif (di->events.btemp_underover) { if (di->charge_state != STATE_TEMP_UNDEROVER)
ab8500_chargalg_state_to(di,
STATE_TEMP_UNDEROVER_INIT);
} /* Watchdog expired */ elseif (di->events.ac_wd_expired ||
di->events.usb_wd_expired) { if (di->charge_state != STATE_WD_EXPIRED)
ab8500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT);
} /* Battery temp high/low */ elseif (di->events.btemp_low || di->events.btemp_high) { if (di->charge_state != STATE_TEMP_LOWHIGH)
ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT);
}
case STATE_BATT_REMOVED_INIT:
ab8500_chargalg_stop_charging(di);
ab8500_chargalg_state_to(di, STATE_BATT_REMOVED);
fallthrough;
case STATE_BATT_REMOVED: if (!di->events.batt_rem)
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
case STATE_HW_TEMP_PROTECT_INIT:
ab8500_chargalg_stop_charging(di);
ab8500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT);
fallthrough;
case STATE_HW_TEMP_PROTECT: if (!di->events.main_thermal_prot &&
!di->events.usb_thermal_prot)
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
case STATE_OVV_PROTECT_INIT:
ab8500_chargalg_stop_charging(di);
ab8500_chargalg_state_to(di, STATE_OVV_PROTECT);
fallthrough;
case STATE_OVV_PROTECT: if (!di->events.vbus_ovv &&
!di->events.main_ovv &&
!di->events.batt_ovv &&
di->chg_info.usb_chg_ok &&
di->chg_info.ac_chg_ok)
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
case STATE_CHG_NOT_OK_INIT:
ab8500_chargalg_stop_charging(di);
ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK);
fallthrough;
case STATE_CHG_NOT_OK: if (!di->events.mainextchnotok &&
!di->events.usbchargernotok)
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
case STATE_SAFETY_TIMER_EXPIRED_INIT:
ab8500_chargalg_stop_charging(di);
ab8500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED);
fallthrough;
case STATE_SAFETY_TIMER_EXPIRED: /* We exit this state when charger is removed */ break;
case STATE_NORMAL_INIT: if (bi->constant_charge_current_max_ua == 0) /* "charging" with 0 uA */
ab8500_chargalg_stop_charging(di); else {
ab8500_chargalg_start_charging(di,
bi->constant_charge_voltage_max_uv,
bi->constant_charge_current_max_ua);
}
case STATE_NORMAL:
handle_maxim_chg_curr(di); if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
di->maintenance_chg) { /* * The battery is fully charged, check if we support * maintenance charging else go back to waiting for * the recharge voltage limit.
*/ if (!power_supply_supports_maintenance_charging(bi))
ab8500_chargalg_state_to(di,
STATE_WAIT_FOR_RECHARGE_INIT); else
ab8500_chargalg_state_to(di,
STATE_MAINTENANCE_A_INIT);
} break;
/* This state will be used when the maintenance state is disabled */ case STATE_WAIT_FOR_RECHARGE_INIT:
ab8500_chargalg_hold_charging(di);
ab8500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE);
fallthrough;
case STATE_WAIT_FOR_RECHARGE: if (ab8500_chargalg_time_to_restart(di))
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
case STATE_MAINTENANCE_A_INIT:
mt = power_supply_get_maintenance_charging_setting(bi, 0); if (!mt) { /* No maintenance A state, go back to normal */
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
power_supply_changed(di->chargalg_psy); break;
}
ab8500_chargalg_stop_safety_timer(di);
ab8500_chargalg_start_maintenance_timer(di,
mt->charge_safety_timer_minutes);
ab8500_chargalg_start_charging(di,
mt->charge_voltage_max_uv,
mt->charge_current_max_ua);
ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A);
power_supply_changed(di->chargalg_psy);
fallthrough;
case STATE_MAINTENANCE_A: if (di->events.maintenance_timer_expired) {
ab8500_chargalg_stop_maintenance_timer(di);
ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT);
} /* * This happens if the voltage drops too quickly during * maintenance charging, especially in older batteries.
*/ if (ab8500_chargalg_time_to_restart(di)) {
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
dev_info(di->dev, "restarted charging from maintenance state A - battery getting old?\n");
} break;
case STATE_MAINTENANCE_B_INIT:
mt = power_supply_get_maintenance_charging_setting(bi, 1); if (!mt) { /* No maintenance B state, go back to normal */
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
power_supply_changed(di->chargalg_psy); break;
}
ab8500_chargalg_start_maintenance_timer(di,
mt->charge_safety_timer_minutes);
ab8500_chargalg_start_charging(di,
mt->charge_voltage_max_uv,
mt->charge_current_max_ua);
ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B);
power_supply_changed(di->chargalg_psy);
fallthrough;
case STATE_MAINTENANCE_B: if (di->events.maintenance_timer_expired) {
ab8500_chargalg_stop_maintenance_timer(di);
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
} /* * This happens if the voltage drops too quickly during * maintenance charging, especially in older batteries.
*/ if (ab8500_chargalg_time_to_restart(di)) {
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT);
dev_info(di->dev, "restarted charging from maintenance state B - battery getting old?\n");
} break;
case STATE_TEMP_LOWHIGH_INIT: if (di->events.btemp_low) {
ab8500_chargalg_start_charging(di,
bi->alert_low_temp_charge_voltage_uv,
bi->alert_low_temp_charge_current_ua);
} elseif (di->events.btemp_high) {
ab8500_chargalg_start_charging(di,
bi->alert_high_temp_charge_voltage_uv,
bi->alert_high_temp_charge_current_ua);
} else {
dev_err(di->dev, "neither low or high temp event occurred\n");
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
}
ab8500_chargalg_stop_maintenance_timer(di);
di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH);
power_supply_changed(di->chargalg_psy);
fallthrough;
case STATE_TEMP_LOWHIGH: if (!di->events.btemp_low && !di->events.btemp_high)
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
case STATE_WD_EXPIRED_INIT:
ab8500_chargalg_stop_charging(di);
ab8500_chargalg_state_to(di, STATE_WD_EXPIRED);
fallthrough;
case STATE_WD_EXPIRED: if (!di->events.ac_wd_expired &&
!di->events.usb_wd_expired)
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
case STATE_TEMP_UNDEROVER_INIT:
ab8500_chargalg_stop_charging(di);
ab8500_chargalg_state_to(di, STATE_TEMP_UNDEROVER);
fallthrough;
case STATE_TEMP_UNDEROVER: if (!di->events.btemp_underover)
ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break;
}
/* Start charging directly if the new state is a charge state */ if (di->charge_state == STATE_NORMAL_INIT ||
di->charge_state == STATE_MAINTENANCE_A_INIT ||
di->charge_state == STATE_MAINTENANCE_B_INIT)
queue_work(di->chargalg_wq, &di->chargalg_work);
}
/** * ab8500_chargalg_periodic_work() - Periodic work for the algorithm * @work: pointer to the work_struct structure * * Work queue function for the charging algorithm
*/ staticvoid ab8500_chargalg_periodic_work(struct work_struct *work)
{ struct ab8500_chargalg *di = container_of(work, struct ab8500_chargalg, chargalg_periodic_work.work);
ab8500_chargalg_algorithm(di);
/* * If a charger is connected then the battery has to be monitored * frequently, else the work can be delayed.
*/ if (di->chg_info.conn_chg)
queue_delayed_work(di->chargalg_wq,
&di->chargalg_periodic_work,
di->bm->interval_charging * HZ); else
queue_delayed_work(di->chargalg_wq,
&di->chargalg_periodic_work,
di->bm->interval_not_charging * HZ);
}
/** * ab8500_chargalg_wd_work() - periodic work to kick the charger watchdog * @work: pointer to the work_struct structure * * Work queue function for kicking the charger watchdog
*/ staticvoid ab8500_chargalg_wd_work(struct work_struct *work)
{ int ret; struct ab8500_chargalg *di = container_of(work, struct ab8500_chargalg, chargalg_wd_work.work);
ret = ab8500_chargalg_kick_watchdog(di); if (ret < 0)
dev_err(di->dev, "failed to kick watchdog\n");
/** * ab8500_chargalg_work() - Work to run the charging algorithm instantly * @work: pointer to the work_struct structure * * Work queue function for calling the charging algorithm
*/ staticvoid ab8500_chargalg_work(struct work_struct *work)
{ struct ab8500_chargalg *di = container_of(work, struct ab8500_chargalg, chargalg_work);
ab8500_chargalg_algorithm(di);
}
/** * ab8500_chargalg_get_property() - get the chargalg properties * @psy: pointer to the power_supply structure * @psp: pointer to the power_supply_property structure * @val: pointer to the power_supply_propval union * * This function gets called when an application tries to get the * chargalg properties by reading the sysfs files. * status: charging/discharging/full/unknown * health: health of the battery * Returns error code in case of failure else 0 on success
*/ staticint ab8500_chargalg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
{ struct ab8500_chargalg *di = power_supply_get_drvdata(psy);
/* Kick charger watchdog if charging (any charger online) */ if (di->chg_info.online_chg)
queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0);
/* * Run the charging algorithm directly to be sure we don't * do it too seldom
*/
queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
/* Create a work queue for the chargalg */
di->chargalg_wq = alloc_ordered_workqueue("ab8500_chargalg_wq",
WQ_MEM_RECLAIM); if (di->chargalg_wq == NULL) {
dev_err(di->dev, "failed to create work queue\n"); return -ENOMEM;
}
/* Run the charging algorithm */
queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
/* Init work for chargalg */
INIT_DEFERRABLE_WORK(&di->chargalg_periodic_work,
ab8500_chargalg_periodic_work);
INIT_DEFERRABLE_WORK(&di->chargalg_wd_work,
ab8500_chargalg_wd_work);
/* Init work for chargalg */
INIT_WORK(&di->chargalg_work, ab8500_chargalg_work);
/* To detect charger at startup */
di->chg_info.prev_conn_chg = -1;
/* Register chargalg power supply class */
di->chargalg_psy = devm_power_supply_register(di->dev,
&ab8500_chargalg_desc,
&psy_cfg); if (IS_ERR(di->chargalg_psy)) {
dev_err(di->dev, "failed to register chargalg psy\n"); return PTR_ERR(di->chargalg_psy);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.