CoolPi-Armbian-Rockchip-RK3.../drivers/power/supply/rockchip_charger_manager.c

3108 lines
86 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* This driver enables to monitor battery health and control charger
*
* Copyright (c) 2022 Rockchip Electronics Co., Ltd.
*
* Author: Xu Shengfei <xsf@rock-chips.com>
*
* This driver enables to monitor battery health and control charger
* during suspend-to-mem.
* Charger manager depends on other devices. Register this later than
* the depending devices.
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/rtc.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/sysfs.h>
#include <linux/of.h>
#include <linux/thermal.h>
#include <../drivers/extcon/extcon.h>
#include <linux/power_supply.h>
#include <linux/extcon.h>
#include <linux/alarmtimer.h>
static int dbg_enable;
module_param_named(dbg_level, dbg_enable, int, 0644);
#define CM_DBG(args...) \
do { \
if (dbg_enable) { \
pr_info(args); \
} \
} while (0)
/*
* Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
* delayed works so that we can run delayed works with CM_JIFFIES_SMALL
* without any delays.
*/
#define CM_JIFFIES_SMALL (2)
/* If y is valid (> 0) and smaller than x, do x = y */
#define CM_MIN_VALID(x, y) (x = (((y > 0) && ((x) > (y))) ? (y) : (x)))
#define PM_WORK_RUN_INTERVAL 300
#define TAPER_TIMEOUT (5000 / PM_WORK_RUN_INTERVAL)
#define IBUS_CHANGE_TIMEOUT (500 / PM_WORK_RUN_INTERVAL)
#define FC_VOLTAGE_STEP (20 * 1000)
#define FC_CURRENT_STEP (50 * 1000)
#define FC_VOLTAGE_THRESHOLD_VALUE (50 * 1000)
#define USB_SDP_INPUT_CURRENT (500 * 1000)
#define USB_DCP_INPUT_CURRENT (2000 * 1000)
#define USB_CDP_INPUT_CURRENT (1500 * 1000)
#define USB_TYPE_C_INPUT_CURRENT (900 * 1000)
#define USB_PPS_INPUT_CURRENT (100 * 1000) /* (1000 * 1000) */
#define SW_FC_CURRENT_STEP (100 * 1000)
#define CP_HIGH_TEMPERATURE 80
#define CP_WARM_TEMPERATURE 55
#define CHARGE_MODE 4
enum polling_modes {
CM_POLL_DISABLE = 0,
CM_POLL_ALWAYS,
CM_POLL_EXTERNAL_POWER_ONLY,
CM_POLL_CHARGING_ONLY,
};
enum jeita_status {
CM_JEITA_COLD,
CM_JEITA_COOL,
CM_JEITA_GOOD,
CM_JEITA_WARM,
CM_JEITA_HOT,
};
enum cm_batt_temp {
CM_BATT_OK = 0,
CM_BATT_OVERHEAT,
CM_BATT_COLD,
};
enum cm_charge_type {
CHARGE_TYPE_DISCHARGE = 0,
CHARGE_TYPE_NORMAL,
CHARGE_TYPE_TYPE_C,
CHARGE_TYPE_PD,
CHARGE_TYPE_PPS,
};
enum cm_pps_state {
PPS_PM_STATE_ENTRY,
PPS_PM_STATE_FC_ENTRY,
PPS_PM_STATE_FC_ENTRY_1,
PPS_PM_STATE_FC_ENTRY_2,
PPS_PM_STATE_FC_TUNE,
PPS_PM_STATE_FC_JEITA,
PPS_PM_STATE_FC_EXIT,
};
enum cm_cp_mode {
FORWARD_4_1_CHARGER_MODE,
FORWARD_2_1_CHARGER_MODE,
FORWARD_1_1_CHARGER_MODE,
FORWARD_1_1_CHARGER_MODE1,
REVERSE_1_4_CONVERTER_MODE,
REVERSE_1_2_CONVERTER_MODE,
REVERSE_1_1_CONVERTER_MODE,
REVERSE_1_1_CONVERTER_MODE1,
};
struct fastcharge_config {
/*bat volt loop limit*/
int vbat_lmt;
int ibat_lmt;
int vbat_now;
int ibat_now;
int pcb_resistance;
int vbus_lmt;
int ibus_lmt;
int vbus_cp_lmt;
int ibus_cp_lmt;
int vbus_cp;
int ibus_cp;
int work_mode_cp;
int charger_mode;
int adapter_volt_max_lmt;
int adapter_curr_max_lmt;
int adaper_power_lmt;
int adaper_power_init_flag;
int adapter_volt_required;
int adapter_curr_required;
int cable_resistance;
bool jeita_charge_support;
bool jeita_enable_charge;
int jeita_status;
int jeita_status_changed;
int jeita_temperature;
int jeita_charge_current;
int jeita_charge_voltage;
int vbus_dcdc_lmt;
int ibus_dcdc_lmt;
int sw_input_current;
int sw_charge_current;
int sw_charge_voltage;
int sw_charge_current_max;
int sw_charge_status;
/* overvoltage protection */
int sw_ovp_flag;
int min_vbat_for_cp;
int fc_taper_current;
int fc_steps;
int charge_type;
/* disable switching charger during flash charge*/
bool fc_disable_sw;
bool fc_charge_error;
};
struct fuel_gauge_info {
int vbat_now;
int ibat_now;
int bat_res;
int bat_temperature;
};
struct chargepump_info {
bool charge_enabled;
bool batt_pres;
bool vbus_pres;
/* alarm/fault status */
bool bat_ovp_fault;
bool bat_ocp_fault;
bool bus_ovp_fault;
bool bus_ocp_fault;
bool bat_ovp_alarm;
bool bat_ocp_alarm;
bool bus_ovp_alarm;
bool bus_ocp_alarm;
bool bat_ucp_alarm;
bool bat_therm_alarm;
bool bus_therm_alarm;
bool die_therm_alarm;
bool bat_therm_fault;
bool bus_therm_fault;
bool die_therm_fault;
bool therm_shutdown_flag;
bool therm_shutdown_stat;
bool vbat_reg;
bool ibat_reg;
int vout_volt;
int vbat_volt;
int vbus_volt;
int vbus_max;
int ibat_curr;
int ibus_curr;
int ibus_max;
int vbus_error_low;
int vbus_error_high;
int bat_temp;
int bus_temp;
int die_temp;
int cp_alarm_status;
int cp_fault_status;
int request_voltage;
int request_current;
};
struct jeita_charge_info {
int temp_down;
int temp_up;
int chrg_current;
int chrg_voltage;
};
/**
* struct charger_desc
* @psy_name: the name of power-supply-class for charger manager
* @polling_mode:
* Determine which polling mode will be used
* @polling_interval_ms: interval in millisecond at which
* charger manager will monitor battery health
* @psy_charger_stat: the names of power-supply for chargers
* @psy_fuel_gauge: the name of power-supply for fuel gauge
* @temp_min : Minimum battery temperature for charging.
* @temp_max : Maximum battery temperature for charging.
* @temp_diff : Temperature difference to restart charging.
* @measure_battery_temp:
* true: measure battery temperature
* false: measure ambient temperature
* @charging_max_duration_ms: Maximum possible duration for charging
* If whole charging duration exceed 'charging_max_duration_ms',
* cm stop charging.
* @discharging_max_duration_ms:
* Maximum possible duration for discharging with charger cable
* after full-batt. If discharging duration exceed 'discharging
* max_duration_ms', cm start charging.
*/
struct charger_desc {
const char *psy_name;
enum polling_modes polling_mode;
unsigned int polling_interval_ms;
unsigned int polling_force_enable;
struct power_supply_battery_info info;
const char *psy_charger_stat;
const char *psy_charger_pump_stat;
const char *psy_fuel_gauge;
const char *thermal_zone;
struct power_supply *tcpm_psy;
struct extcon_dev *extcon_dev;
struct jeita_charge_info *jeita_charge_table;
int jeita_charge_table_count;
bool measure_battery_temp;
u32 charging_max_duration_ms;
u32 discharging_max_duration_ms;
int support_mult_mode;
struct gpio_desc *gpiod_dc_det;
bool support_dc_charger;
int dc_det_level;
int dc_charger_status;
};
/**
* struct charger_manager
* @entry: entry for list
* @dev: device pointer
* @desc: instance of charger_desc
* @fuel_gauge: power_supply for fuel gauge
* @charger_stat: array of power_supply for chargers
* @tzd_batt : thermal zone device for battery
* @fc_charger_enabled: the state of charger
* @emergency_stop:
* When setting true, stop charging
* @status_save_ext_pwr_inserted:
* saved status of external power before entering suspend-to-RAM
* @status_save_batt:
* saved status of battery before entering suspend-to-RAM
* @charging_start_time: saved start time of enabling charging
* @charging_end_time: saved end time of disabling charging
* @battery_status: Current battery status
*/
struct charger_manager {
struct list_head entry;
struct device *dev;
struct charger_desc *desc;
struct workqueue_struct *cm_wq; /* init at driver add */
struct delayed_work cm_monitor_work; /* init at driver add */
struct delayed_work cm_jeita_work; /* init at driver add */
struct delayed_work dc_work;
/* The state of charger cable */
bool attached;
bool fc_charger_enabled;
bool fc_charger_poller_status;
enum cm_pps_state state;
int emergency_stop;
/* The rockchip-charger-manager use Extcon framework */
struct work_struct wq;
struct notifier_block nb;
struct chargepump_info cp;
struct fuel_gauge_info fg_info;
struct fastcharge_config *fc_config;
/* About in-suspend (suspend-again) monitoring */
int fc2_taper_timer;
int is_fast_charge;
int is_charge;
unsigned long next_polling;
u64 charging_start_time;
u64 charging_end_time;
int battery_status;
/* chargepump high temperature */
int cp_htemp_status;
};
enum {
PM_ALGO_RET_OK,
PM_ALGO_RET_THERM_FAULT,
PM_ALGO_RET_OTHER_FAULT,
PM_ALGO_RET_CHG_DISABLED,
PM_ALGO_RET_TAPER_DONE,
};
enum data_source {
CM_BATTERY_PRESENT,
CM_NO_BATTERY,
CM_FUEL_GAUGE,
CM_CHARGER_STAT,
};
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
static const unsigned char *pm_debug_str[] = {
"PPS_PM_STATE_ENTRY",
"PPS_PM_STATE_FC_ENTRY",
"PPS_PM_STATE_FC_ENTRY_1",
"PPS_PM_STATE_FC_ENTRY_2",
"PPS_PM_STATE_FC_TUNE",
"PPS_PM_STATE_FC_JEITA",
"PPS_PM_STATE_FC_EXIT"
};
#endif
static struct {
const char *name;
u64 extcon_type;
} extcon_mapping[] = {
/* Current textual representations */
{ "SDP", EXTCON_CHG_USB_SDP },
{ "DCP", EXTCON_CHG_USB_DCP },
{ "CDP", EXTCON_CHG_USB_CDP },
};
struct power_supply_battery_info bat_default_info = {
.precharge_voltage_max_uv = 3600 * 1000, /* microVolts */
.charge_term_current_ua = 100 * 1000, /* microAmps */
.charge_restart_voltage_uv = 4100 * 1000, /* microVolts */
.constant_charge_current_max_ua = 1000 * 1000, /* microAmps */
.constant_charge_voltage_max_uv = 4200 * 1000, /* microVolts */
.factory_internal_resistance_uohm = 100, /* microOhms */
};
static struct fastcharge_config fc_config_parameter = {
.vbat_lmt = 4400 * 1000,
.ibat_lmt = 8000 * 1000,
.ibus_dcdc_lmt = 1000 * 1000,
.vbus_cp_lmt = 9500 * 1000,
.ibus_cp_lmt = 4000 * 1000,
.fc_taper_current = 500 * 1000,/* disable cp */
.fc_steps = 1, /* 20mV step */
.adaper_power_lmt = 45000 * 1000,/* mV * mA */
.min_vbat_for_cp = 3600,
.fc_disable_sw = true,
};
/**
* get_battery_temperature - Get the temperature level of the battery
* @cm: the Charger Manager representing the battery.
* @temperature: the temperature level returned.
*
* Returns 0 if there is no error.
* Returns a negative value on error.
*/
static int get_battery_temperature(struct charger_manager *cm,
int *temperature)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
if (!psy) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
return -ENODEV;
}
ret = power_supply_get_property(psy,
POWER_SUPPLY_PROP_TEMP,
&val);
power_supply_put(psy);
if (ret) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "failed to get %s POWER_SUPPLY_PROP_TEMP\n",
cm->desc->psy_fuel_gauge);
return ret;
}
*temperature = val.intval;
return 0;
}
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
/**
* get_battery_voltage - Get the voltage level of the battery
* @cm: the Charger Manager representing the battery.
* @uV: the voltage level returned.
*
* Returns 0 if there is no error.
* Returns a negative value on error.
*/
static int get_battery_voltage(struct charger_manager *cm, int *uV)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
/* If at least one of them has one, it's yes. */
psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
if (!psy) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
return -ENODEV;
}
ret = power_supply_get_property(psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
power_supply_put(psy);
if (ret) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "failed to get %s POWER_SUPPLY_PROP_VOLTAGE_NOW\n",
cm->desc->psy_fuel_gauge);
return ret;
}
*uV = val.intval;
return 0;
}
/**
* get_battery_current - Get the current level of the battery
* @cm: the Charger Manager representing the battery.
* @uA: the current level returned.
*
* Returns 0 if there is no error.
* Returns a negative value on error.
*/
static int get_battery_current(struct charger_manager *cm, int *uA)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
/* If at least one of them has one, it's yes. */
psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
if (!psy) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
return -ENODEV;
}
ret = power_supply_get_property(psy,
POWER_SUPPLY_PROP_CURRENT_NOW,
&val);
power_supply_put(psy);
if (ret) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "failed to get %s POWER_SUPPLY_PROP_CURRENT_NOW\n",
cm->desc->psy_fuel_gauge);
return ret;
}
*uA = val.intval;
return 0;
}
/**
* is_ext_pwr_online - See if an external power source is attached to charge
* @cm: the Charger Manager representing the battery.
*
* Returns true if at least one of the chargers of the battery has an external
* power source attached to charge the battery regardless of whether it is
* actually charging or not.
*/
static bool is_ext_pwr_online(struct charger_manager *cm)
{
union power_supply_propval val;
struct power_supply *psy;
bool online = false;
int ret;
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
return online;
}
ret = power_supply_get_property(psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
power_supply_put(psy);
if (ret) {
dev_err(cm->dev, "failed to get %s POWER_SUPPLY_PROP_ONLINE\n",
cm->desc->psy_charger_stat);
return ret;
}
if (val.intval)
online = true;
return online;
}
#endif
static int set_sw_charger_enable(struct charger_manager *cm)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
return -ENODEV;
}
val.intval = 0x01;
ret = power_supply_set_property(psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
power_supply_put(psy);
if (ret) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "failed to set %s POWER_SUPPLY_PROP_ONLINE\n",
cm->desc->psy_charger_stat);
return ret;
}
cm->fc_config->sw_charge_status = 1;
return 0;
}
static int set_sw_charger_disable(struct charger_manager *cm)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
return -ENODEV;
}
val.intval = 0x00;
ret = power_supply_set_property(psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
power_supply_put(psy);
if (ret) {
dev_err(cm->dev, "failed to get %s POWER_SUPPLY_PROP_ONLINE\n",
cm->desc->psy_charger_stat);
return ret;
}
cm->fc_config->sw_charge_status = 0;
return 0;
}
static int set_sw_charger_input_limit_voltage(struct charger_manager *cm,
int input_voltage_uv)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev,
"Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
return -ENODEV;
}
val.intval = input_voltage_uv;
ret = power_supply_set_property(psy,
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
&val);
power_supply_put(psy);
if (ret) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev,
"failed to set %s POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT\n",
cm->desc->psy_charger_stat);
return ret;
}
cm->fc_config->vbus_dcdc_lmt = input_voltage_uv;
return 0;
}
static int set_sw_charger_input_limit_current(struct charger_manager *cm,
int input_current_ua)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
return -ENODEV;
}
val.intval = input_current_ua;
ret = power_supply_set_property(psy,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
&val);
power_supply_put(psy);
if (ret) {
cm->fc_config->fc_charge_error = true;
dev_err(cm->dev, "failed to set %s POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT\n",
cm->desc->psy_charger_stat);
return ret;
}
cm->fc_config->ibus_dcdc_lmt = input_current_ua;
return 0;
}
static int set_sw_charger_voltage(struct charger_manager *cm,
int voltage_uv)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
CM_DBG("POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: sw: %d\n", voltage_uv);
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
cm->fc_config->fc_charge_error = true;
return -ENODEV;
}
val.intval = voltage_uv;
ret = power_supply_set_property(psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
&val);
power_supply_put(psy);
if (ret) {
dev_err(cm->dev, "failed to set %s POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE\n",
cm->desc->psy_charger_stat);
cm->fc_config->fc_charge_error = true;
return ret;
}
cm->fc_config->sw_charge_voltage = voltage_uv;
return 0;
}
static int set_sw_charger_current(struct charger_manager *cm,
int current_ua)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
CM_DBG("POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: sw: %d\n", current_ua);
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
cm->fc_config->fc_charge_error = true;
return -ENODEV;
}
val.intval = current_ua;
ret = power_supply_set_property(psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
&val);
power_supply_put(psy);
if (ret) {
dev_err(cm->dev, "failed to set %s POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT\n",
cm->desc->psy_charger_stat);
cm->fc_config->fc_charge_error = true;
return ret;
}
return 0;
}
static int get_sw_charger_current_max(struct charger_manager *cm, int *uA)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
cm->fc_config->fc_charge_error = true;
return -ENODEV;
}
ret = power_supply_get_property(psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&val);
power_supply_put(psy);
if (ret) {
dev_err(cm->dev, "failed to get %s POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX\n",
cm->desc->psy_charger_stat);
cm->fc_config->fc_charge_error = true;
return ret;
}
*uA = val.intval;
return 0;
}
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
static int get_sw_charger_input_limit_current(struct charger_manager *cm, int *uA)
{
union power_supply_propval val;
struct power_supply *psy;
int ret;
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
if (!psy) {
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_stat);
cm->fc_config->fc_charge_error = true;
return -ENODEV;
}
ret = power_supply_get_property(psy,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
&val);
power_supply_put(psy);
if (ret) {
dev_err(cm->dev, "failed to get %s POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT\n",
cm->desc->psy_charger_stat);
cm->fc_config->fc_charge_error = true;
return ret;
}
*uA = val.intval;
return 0;
}
static int set_cp_mode(struct charger_manager *cm, int cp_mode)
{
union power_supply_propval val;
struct power_supply *cp_psy;
int ret;
cp_psy = power_supply_get_by_name(cm->desc->psy_charger_pump_stat);
if (!cp_psy) {
dev_err(cm->dev,
"Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_pump_stat);
cm->fc_config->fc_charge_error = true;
return -ENODEV;
}
val.intval = cp_mode;
ret = power_supply_set_property(cp_psy,
POWER_SUPPLY_PROP_CP_WORK_MODE,
&val);
power_supply_put(cp_psy);
if (ret) {
dev_err(cm->dev,
"failed to disable %s POWER_SUPPLY_PROP_CP_WORK_MODE\n",
cm->desc->psy_charger_pump_stat);
cm->fc_config->fc_charge_error = true;
return ret;
}
return 0;
}
static int set_cp_enable(struct charger_manager *cm)
{
union power_supply_propval val;
struct power_supply *cp_psy;
int ret;
if (cm->desc->support_mult_mode)
set_cp_mode(cm, cm->fc_config->work_mode_cp);
cp_psy = power_supply_get_by_name(cm->desc->psy_charger_pump_stat);
if (!cp_psy) {
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_pump_stat);
cm->fc_config->fc_charge_error = true;
return -ENODEV;
}
val.intval = 1;
ret = power_supply_set_property(cp_psy,
POWER_SUPPLY_PROP_CP_CHARGING_ENABLED,
&val);
power_supply_put(cp_psy);
if (ret) {
dev_err(cm->dev, "failed to enable %s POWER_SUPPLY_PROP_CP_CHARGING_ENABLED\n",
cm->desc->psy_charger_pump_stat);
cm->fc_config->fc_charge_error = true;
return ret;
}
cm->cp.charge_enabled = 1;
return 0;
}
static int set_cp_disable(struct charger_manager *cm)
{
union power_supply_propval val;
struct power_supply *cp_psy;
int ret;
cp_psy = power_supply_get_by_name(cm->desc->psy_charger_pump_stat);
if (!cp_psy) {
dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
cm->desc->psy_charger_pump_stat);
cm->fc_config->fc_charge_error = true;
return -ENODEV;
}
val.intval = 0;
ret = power_supply_set_property(cp_psy,
POWER_SUPPLY_PROP_CP_CHARGING_ENABLED,
&val);
power_supply_put(cp_psy);
if (ret) {
dev_err(cm->dev, "failed to disable %s POWER_SUPPLY_PROP_CP_CHARGING_ENABLED\n",
cm->desc->psy_charger_pump_stat);
cm->fc_config->fc_charge_error = true;
return ret;
}
cm->cp.charge_enabled = 0;
return 0;
}
/**
* is_polling_required - Return true if need to continue polling for this CM.
* @cm: the Charger Manager representing the battery.
*/
static bool is_polling_required(struct charger_manager *cm)
{
CM_DBG("cm->desc->polling_mode: %d\n", cm->desc->polling_mode);
switch (cm->desc->polling_mode) {
case CM_POLL_DISABLE:
return false;
case CM_POLL_ALWAYS:
return true;
case CM_POLL_EXTERNAL_POWER_ONLY:
return is_ext_pwr_online(cm);
case CM_POLL_CHARGING_ONLY:
return cm->attached && cm->fc_charger_enabled;
default:
dev_warn(cm->dev, "Incorrect polling_mode (%d)\n",
cm->desc->polling_mode);
}
return false;
}
#endif
static void cm_update_jeita_charge_info(struct charger_manager *cm)
{
struct power_supply_battery_info info;
int temperature;
int i, count;
int ret;
if (!cm->desc->measure_battery_temp)
return;
info = cm->desc->info;
ret = get_battery_temperature(cm, &temperature);
if (ret) {
/* cm->fc_config->jeita_charge_support = false; */
cm->fc_config->jeita_enable_charge = false;
return;
}
count = cm->desc->jeita_charge_table_count;
cm->fc_config->jeita_charge_support = true;
cm->fc_config->jeita_temperature = temperature;
if (temperature / 10 < cm->desc->jeita_charge_table[0].temp_down) {
cm->fc_config->jeita_charge_current = 0;
cm->fc_config->jeita_charge_voltage = info.constant_charge_voltage_max_uv;
cm->fc_config->jeita_enable_charge = false;
cm->fc_config->jeita_status = CM_JEITA_COLD;
return;
}
if (temperature / 10 > cm->desc->jeita_charge_table[count - 1].temp_up) {
cm->fc_config->jeita_charge_current = 0;
cm->fc_config->jeita_charge_voltage = info.constant_charge_voltage_max_uv;
cm->fc_config->jeita_enable_charge = false;
cm->fc_config->jeita_status = CM_JEITA_HOT;
return;
}
if (!cm->fc_config->jeita_enable_charge)
if ((temperature / 10 > cm->desc->jeita_charge_table[0].temp_down + 3) &&
(temperature / 10 < cm->desc->jeita_charge_table[count - 1].temp_up - 3))
cm->fc_config->jeita_enable_charge = true;
for (i = 0; i < count; i++) {
if ((temperature / 10 > cm->desc->jeita_charge_table[i].temp_down) &&
(temperature / 10 <= cm->desc->jeita_charge_table[i].temp_up)) {
cm->fc_config->jeita_charge_current =
cm->desc->jeita_charge_table[i].chrg_current;
cm->fc_config->jeita_charge_voltage =
cm->desc->jeita_charge_table[i].chrg_voltage;
cm->fc_config->jeita_status = CM_JEITA_COOL + i;
return;
}
}
}
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
static void cm_update_charge_pump_status(struct charger_manager *cm)
{
union power_supply_propval val = {0,};
struct power_supply *cp_psy;
int ret;
cp_psy = power_supply_get_by_name(cm->desc->psy_charger_pump_stat);
if (!cp_psy) {
dev_err(cm->dev, "Cannot find power supply:%s\n",
cm->desc->psy_charger_pump_stat);
return;
}
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_VOUT,
&val);
if (!ret)
cm->cp.vout_volt = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
if (!ret)
cm->cp.vbat_volt = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CURRENT_NOW,
&val);
if (!ret)
cm->cp.ibat_curr = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_VBUS,
&val);
if (!ret)
cm->cp.vbus_volt = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_IBUS,
&val);
if (!ret)
cm->cp.ibus_curr = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
&val);
if (!ret)
cm->cp.ibus_max = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
&val);
if (!ret)
cm->cp.vbus_max = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_VBUS_HERROR_STATUS,
&val);
if (!ret)
cm->cp.vbus_error_high = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_VBUS_LERROR_STATUS,
&val);
if (!ret)
cm->cp.vbus_error_low = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BAT_TEMPERATURE,
&val);
if (!ret)
cm->cp.bat_temp = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BUS_TEMPERATURE,
&val);
if (!ret)
cm->cp.bus_temp = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_DIE_TEMPERATURE,
&val);
if (!ret)
cm->cp.die_temp = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BAT_OVP_ALARM,
&val);
if (!ret)
cm->cp.bat_ovp_alarm = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BAT_OCP_ALARM,
&val);
if (!ret)
cm->cp.bat_ocp_alarm = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BAT_UCP_ALARM,
&val);
if (!ret)
cm->cp.bat_ucp_alarm = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BUS_OVP_ALARM,
&val);
if (!ret)
cm->cp.bus_ovp_alarm = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BUS_OCP_ALARM,
&val);
if (!ret)
cm->cp.bus_ocp_alarm = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BAT_THERM_ALARM,
&val);
if (!ret)
cm->cp.bat_therm_alarm = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BUS_THERM_ALARM,
&val);
if (!ret)
cm->cp.bus_therm_alarm = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_DIE_THERM_ALARM,
&val);
if (!ret)
cm->cp.die_therm_alarm = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BAT_OVP_FAULT,
&val);
if (!ret)
cm->cp.bat_ovp_fault = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BAT_OCP_FAULT,
&val);
if (!ret)
cm->cp.bat_ocp_fault = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BUS_OVP_FAULT,
&val);
if (!ret)
cm->cp.bus_ovp_fault = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BUS_OCP_FAULT,
&val);
if (!ret)
cm->cp.bus_ocp_fault = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BAT_THERM_FAULT,
&val);
if (!ret)
cm->cp.bat_therm_fault = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_BUS_THERM_FAULT,
&val);
if (!ret)
cm->cp.bus_therm_fault = val.intval;
ret = power_supply_get_property(cp_psy,
POWER_SUPPLY_PROP_CP_DIE_THERM_FAULT,
&val);
if (!ret)
cm->cp.die_therm_fault = val.intval;
power_supply_put(cp_psy);
CM_DBG("cm->cp.cp_alarm_status:0x%x, cm->cp.cp_fault_status:0x%x\n",
cm->cp.cp_alarm_status, cm->cp.cp_fault_status);
}
static void cm_charge_pump_move_state(struct charger_manager *cm,
enum cm_pps_state state)
{
CM_DBG("state change:%s -> %s\n",
pm_debug_str[cm->state], pm_debug_str[state]);
cm->state = state;
}
static int cm_charge_limit_update(struct charger_manager *cm)
{
struct fastcharge_config *fc_config;
struct power_supply_battery_info info;
union power_supply_propval val;
int ibus_dcdc_lmt;
int ret = 0;
info = cm->desc->info;
fc_config = cm->fc_config;
if (fc_config->jeita_charge_support) {
fc_config->vbat_lmt = min(info.constant_charge_voltage_max_uv,
fc_config->jeita_charge_voltage);
fc_config->ibat_lmt = min(info.constant_charge_current_max_ua,
fc_config->jeita_charge_current);
} else {
fc_config->vbat_lmt = info.constant_charge_voltage_max_uv;
fc_config->ibat_lmt = info.constant_charge_current_max_ua;
}
ret = get_battery_voltage(cm, &fc_config->vbat_now);
if (ret)
return ret;
if (fc_config->vbat_now <= 0)
fc_config->vbat_now = cm->cp.vbat_volt;
fc_config->vbat_now = min(fc_config->vbat_now, cm->cp.vbat_volt);
ret = get_battery_current(cm, &fc_config->ibat_now);
if (ret)
return ret;
if ((fc_config->vbat_now >= fc_config->vbat_lmt/* - FC_VOLTAGE_THRESHOLD_VALUE */) &&
(fc_config->sw_ovp_flag == 0))
fc_config->sw_ovp_flag = 1;
ret = get_sw_charger_current_max(cm, &cm->fc_config->sw_charge_current_max);
if (ret)
return ret;
ret = get_sw_charger_input_limit_current(cm, &ibus_dcdc_lmt);
if (ret)
return ret;
if (ibus_dcdc_lmt != 0)
fc_config->ibus_dcdc_lmt = ibus_dcdc_lmt;
fc_config->vbus_cp_lmt = cm->cp.vbus_max;
fc_config->ibus_cp_lmt = cm->cp.ibus_max;
fc_config->vbus_cp = cm->cp.vbus_volt;
fc_config->ibus_cp = cm->cp.ibus_curr;
fc_config->adapter_curr_required = cm->cp.request_current;
fc_config->adapter_volt_required = cm->cp.request_voltage;
if (fc_config->ibus_cp > 1000 * 1000)
fc_config->cable_resistance =
(fc_config->adapter_volt_required - fc_config->vbus_cp) /
((fc_config->ibus_cp + fc_config->ibus_dcdc_lmt) / 1000);
ret = power_supply_get_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_VOLTAGE_MAX\n", __LINE__);
return ret;
}
fc_config->adapter_volt_max_lmt = val.intval;
ret = power_supply_get_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
fc_config->adapter_curr_max_lmt = val.intval;
ret = power_supply_get_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_INPUT_POWER_LIMIT,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_INPUT_POWER_LIMIT\n", __LINE__);
return ret;
}
fc_config->adaper_power_lmt =
(fc_config->adapter_volt_max_lmt / 1000) * (fc_config->adapter_curr_max_lmt / 1000);
if (cm->desc->support_mult_mode) {
if (fc_config->adapter_volt_max_lmt > 4 * info.constant_charge_voltage_max_uv) {
cm->fc_config->work_mode_cp = FORWARD_4_1_CHARGER_MODE;
cm->fc_config->charger_mode = 4;
} else {
cm->fc_config->work_mode_cp = FORWARD_2_1_CHARGER_MODE;
cm->fc_config->charger_mode = 2;
}
} else {
cm->fc_config->charger_mode = 2;
}
fc_config->ibus_lmt = fc_config->ibat_lmt >> 1;
CM_DBG("jeita: jeita_charge_voltage: %d\n"
"jeita_charge_current: %d\n"
"jeita_temperature: %d\n",
fc_config->jeita_charge_voltage,
fc_config->jeita_charge_current,
fc_config->jeita_temperature);
CM_DBG("battery info:\n");
CM_DBG("battery info:: charge-full-design-microamp-hours: %d\n",
info.charge_full_design_uah);
CM_DBG("battery info:: factory_internal_resistance_uohm: %d\n",
info.factory_internal_resistance_uohm);
CM_DBG("battery info:: charge_term_current_ua: %d\n",
info.charge_term_current_ua);
CM_DBG("battery info:: constant_charge_voltage_max_uv: %d\n",
info.constant_charge_voltage_max_uv);
CM_DBG("battery info:: constant_charge_current_max_ua: %d\n",
info.constant_charge_current_max_ua);
CM_DBG("battery info:: precharge_current_ua: %d\n",
info.precharge_current_ua);
CM_DBG("battery info:: precharge-upper-limit-microvolt: %d\n",
info.precharge_voltage_max_uv);
CM_DBG("charge type: %d\n", fc_config->charge_type);
CM_DBG("vbat_lmt: %d\n"
"ibat_lmt: %d\n"
"vbat_now: %d\n"
"ibat_now: %d\n"
"die_temp:%d\n"
"cp_ibat: %d\n"
"cm->cp.vbat_volt: %d\n"
"vbus_cp_lmt: %d\n"
"ibus_cp_lmt: %d\n"
"vbus_cp: %d\n"
"ibus_cp: %d\n"
"ibus_lmt: %d\n"
"adapter_volt_max_lmt: %d\n"
"adapter_curr_max_lmt: %d\n"
"adapter_curr_required: %d\n"
"adapter_volt_required: %d\n"
"adaper_power_lmt: %d\n"
"cable_resistance: %d\n"
"support_mult_mode:%d\n"
"work_mode_cp: %d\n"
"charger_mode:%d\n",
fc_config->vbat_lmt,
fc_config->ibat_lmt,
fc_config->vbat_now,
fc_config->ibat_now,
cm->cp.die_temp,
cm->cp.ibat_curr,
cm->cp.vbat_volt,
fc_config->vbus_cp_lmt,
fc_config->ibus_cp_lmt,
fc_config->vbus_cp,
fc_config->ibus_cp,
fc_config->ibus_lmt,
fc_config->adapter_volt_max_lmt,
fc_config->adapter_curr_max_lmt,
fc_config->adapter_curr_required,
fc_config->adapter_volt_required,
fc_config->adaper_power_lmt,
fc_config->cable_resistance,
cm->desc->support_mult_mode,
fc_config->work_mode_cp,
fc_config->charger_mode);
CM_DBG("sw_input_current: %d\n"
"sw_charge_current: %d\n"
"sw_charge_voltage: %d\n"
"sw_charge_status: %d\n"
"sw_ovp_flag:%d\n"
"sw_charge_current_max: %d\n",
fc_config->sw_input_current,
fc_config->sw_charge_current,
fc_config->sw_charge_voltage,
fc_config->sw_charge_status,
fc_config->sw_ovp_flag,
fc_config->sw_charge_current_max);
return 0;
}
static void cm_debug_information(struct charger_manager *cm)
{
struct fastcharge_config *fc_config;
fc_config = cm->fc_config;
CM_DBG("BUCK CHARGER: input_current: %d\n"
"charge_current: %d\n"
"charge_voltage: %d\n",
fc_config->sw_input_current,
fc_config->sw_charge_current,
fc_config->sw_charge_voltage);
CM_DBG("Adapter: request current: %duA, request voltage: %duV\n"
"ChargePump: vbus: %duV, ibus: %duA, vout_volt: %duV, vbat_volt: %duV, ibat: %duA, temp: %d\n"
"Battery: voltage: %duV, current: %duA, temperature: %d\n",
fc_config->adapter_curr_required, fc_config->adapter_volt_required,
fc_config->vbus_cp, fc_config->ibus_cp, cm->cp.vout_volt,
cm->cp.vbat_volt, cm->cp.ibat_curr, cm->cp.die_temp,
fc_config->vbat_now, fc_config->ibat_now, fc_config->jeita_temperature / 10);
}
static int cm_charge_pump_algo(struct charger_manager *cm)
{
struct fastcharge_config *fc_config;
int step_ibus_total = 0;
int sw_ctrl_steps = 0;
int hw_ctrl_steps = 1;
int step_bat_reg = 0;
int step_vbat = 0;
int step_ibus = 0;
int step_ibat = 0;
int ibus_total = 0;
int fc2_steps = 1;
int fc_steps_add = 2;
int step_ui = 0;
int ibus_limit;
int ibat_lmt;
int steps;
fc_config = cm->fc_config;
ibat_lmt = fc_config->ibat_lmt;/* fc_config->ibat_lmt / 2; */
if ((cm->cp.die_temp > CP_HIGH_TEMPERATURE) && (cm->cp_htemp_status == 0)) {
ibat_lmt = fc_config->ibat_lmt / 2;
CM_DBG("die_temp > 80: ibat_lmt: %d, die_temp: %d\n",
ibat_lmt, cm->cp.die_temp);
cm->cp_htemp_status = 1;
} else {
if ((cm->cp_htemp_status == 1) && (cm->cp.die_temp < CP_WARM_TEMPERATURE)) {
CM_DBG("(die_temp_support == 1) && (cm->cp.die_temp < 50) ibat_lmt: %d, die_temp: %d\n",
ibat_lmt, cm->cp.die_temp);
ibat_lmt = fc_config->ibat_lmt;
cm->cp_htemp_status = 0;
} else if (cm->cp_htemp_status == 1) {
CM_DBG("die_temp_support == 1 ibat_lmt: %d, die_temp: %d\n",
ibat_lmt, cm->cp.die_temp);
ibat_lmt = fc_config->ibat_lmt * 1 / 2;
} else {
ibat_lmt = fc_config->ibat_lmt;
CM_DBG("dibat_lmt = fc_config->ibat_lmt; ibat_lmt: %d, die_temp: %d\n",
ibat_lmt, cm->cp.die_temp);
}
CM_DBG("ibat_lmt: %d, die_temp: %d\n", ibat_lmt, cm->cp.die_temp);
}
/* battery voltage loop*/
if (fc_config->vbat_now > fc_config->vbat_lmt - FC_VOLTAGE_THRESHOLD_VALUE)
step_vbat = -fc2_steps;
else if (fc_config->vbat_now < fc_config->vbat_lmt - FC_VOLTAGE_THRESHOLD_VALUE - 10 * 1000)
step_vbat = fc_steps_add;
/* battery charge current loop*/
if (fc_config->ibat_now < ibat_lmt - 100 * 1000)
step_ibat = fc_steps_add;
else if (fc_config->ibat_now > ibat_lmt)
step_ibat = -fc2_steps;
/* bus total current loop */
ibus_limit = fc_config->adapter_curr_required; /* fc_config->ibus_lmt ; */
ibus_total = fc_config->ibus_cp + fc_config->ibus_dcdc_lmt;/* CP + DCDC */
if (ibus_total < ibus_limit - FC_CURRENT_STEP)
step_ibus_total = fc_steps_add;
else if (ibus_total > ibus_limit)
step_ibus_total = -fc2_steps;
CM_DBG("ibus_limit: %d cm->cp.ibus_max: %d\n", ibus_limit, cm->cp.ibus_max);
/* bus current loop*/
/* charge pump bus current loop */
if (fc_config->ibus_cp < fc_config->adapter_curr_required - 50 * 1000)
/* fc_config->adapter_curr_required - fc_config->ibus_dcdc_lmt - 100 * 1000) */
step_ibus = fc_steps_add;
else if (fc_config->ibus_cp > fc_config->adapter_curr_required - fc_config->ibus_dcdc_lmt)
step_ibus = -fc2_steps;
if (cm->cp.ibus_curr > fc_config->ibus_cp_lmt)
step_ibus = 0;
CM_DBG("cm->cp.ibus_curr: %d,: fc_config->ibus_cp_lmt%d\n",
cm->cp.ibus_curr, fc_config->ibus_cp_lmt);
/* the adaper voltage loop */
if (cm->cp.request_voltage + FC_VOLTAGE_STEP < fc_config->adapter_volt_max_lmt)
step_bat_reg = fc_steps_add;
/* the adapter power negotiation loop*/
if ((fc_config->adapter_volt_required / 1000) * (fc_config->adapter_curr_required / 1000) <= fc_config->adaper_power_lmt)
step_ui = fc_steps_add;
else
cm->cp.request_current -= FC_CURRENT_STEP;
cm->cp.request_current = min(fc_config->adapter_curr_max_lmt, cm->cp.request_current);
sw_ctrl_steps = min(min(step_vbat, step_ibus), min(step_ibat, step_ibus_total));
sw_ctrl_steps = min(sw_ctrl_steps, min(step_bat_reg, step_ui));
/* hardware alarm loop */
if (cm->cp.bat_ocp_alarm
|| cm->cp.bus_ocp_alarm || cm->cp.bus_ovp_alarm)
hw_ctrl_steps = -fc2_steps;
else
hw_ctrl_steps = fc_steps_add;
if (cm->cp.bat_therm_fault ||
(!fc_config->jeita_enable_charge && fc_config->jeita_charge_support)) {
/* battery overheat, stop charge*/
CM_DBG("bat_therm_fault:%d, jeita_enable_charge: %d\n",
cm->cp.bat_therm_fault, fc_config->jeita_enable_charge);
return PM_ALGO_RET_THERM_FAULT;
} else if (cm->cp.bat_ocp_fault || cm->cp.bus_ocp_fault ||
cm->cp.bat_ovp_fault || cm->cp.bus_ovp_fault) {
CM_DBG("bat_ocp_fault:%d\n"
"bus_ocp_fault:%d\n"
"bat_ovp_fault:%d\n"
"bus_ovp_fault:%d\n",
cm->cp.bat_ocp_fault,
cm->cp.bus_ocp_fault,
cm->cp.bat_ovp_fault,
cm->cp.bus_ovp_fault);
return PM_ALGO_RET_OTHER_FAULT;
} else if (cm->cp.vbus_error_low || cm->cp.vbus_error_high) {
/* go to switch, and try to ramp up*/
pr_info("cp.charge_enabled:%d %d %d\n",
cm->cp.charge_enabled, cm->cp.vbus_error_low, cm->cp.vbus_error_high);
return PM_ALGO_RET_CHG_DISABLED;
}
/* charge pump taper charge */
if ((fc_config->vbat_now > (fc_config->vbat_lmt - FC_VOLTAGE_THRESHOLD_VALUE - FC_VOLTAGE_STEP)) &&
(fc_config->ibus_cp < fc_config->fc_taper_current)) {
if (cm->fc2_taper_timer++ > TAPER_TIMEOUT) {
CM_DBG("charge pump taper charging done\n");
cm->fc2_taper_timer = 0;
return PM_ALGO_RET_TAPER_DONE;
}
} else {
cm->fc2_taper_timer = 0;
}
steps = min(sw_ctrl_steps, hw_ctrl_steps);
cm->cp.request_voltage += steps * FC_VOLTAGE_STEP;
CM_DBG(">>>>>>step_bat_reg: %d\n", step_bat_reg);
CM_DBG(">>>>>>step_vbat: %d\n", step_vbat);
CM_DBG(">>>>>>step_ibat: %d\n", step_ibat);
CM_DBG(">>>>>>ibat_lmt: %d\n", ibat_lmt);
CM_DBG(">>>>>>step_ibus: %d\n", step_ibus);
CM_DBG(">>>>>>step_ibus_total: %d\n", step_ibus_total);
CM_DBG(">>>>>>step_ui: %d\n", step_ui);
CM_DBG(">>>>>>sw_ctrl_steps: %d\n", sw_ctrl_steps);
CM_DBG(">>>>>>hw_ctrl_steps: %d\n", hw_ctrl_steps);
CM_DBG(">>>>>>steps: %d\n", steps);
CM_DBG(">>>>>>%d %d %d %d sw %d hw %d all %d\n",
step_bat_reg, step_vbat, step_ibat,
step_ibus, sw_ctrl_steps, hw_ctrl_steps, steps);
CM_DBG(">>>>>> cm->cp.charge_enabled: %d\n", cm->cp.charge_enabled);
return PM_ALGO_RET_OK;
}
static void cm_sw_fast_charge_algo(struct charger_manager *cm)
{
struct fastcharge_config *fc_config;
/* over-temperature protection */
int step_otp_charge = 0;
/* over-current protection */
int step_ocp_charge = 0;
/* over-voltage protection */
int step_ovp_charge = 0;
int charge_voltage;
int charge_current;
int sw_fc_steps = 1;
int steps;
int ret;
fc_config = cm->fc_config;
if (fc_config->jeita_charge_support && !fc_config->jeita_enable_charge)
step_otp_charge = -sw_fc_steps;
else
step_otp_charge = sw_fc_steps;
if (fc_config->ibat_lmt / 2 < cm->cp.request_current)
step_ocp_charge = -sw_fc_steps;
else
step_ocp_charge = sw_fc_steps;
if (fc_config->sw_ovp_flag == 1) {
if (fc_config->vbat_now >= fc_config->vbat_lmt)/* FC_VOLTAGE_THRESHOLD_VALUE */
step_ovp_charge = -sw_fc_steps;
else
step_ovp_charge = 0;
} else
step_ovp_charge = sw_fc_steps;
if (fc_config->jeita_charge_support)
charge_current = min(min(fc_config->jeita_charge_current, fc_config->ibat_lmt),
fc_config->sw_charge_current);
else
charge_current = min(fc_config->ibat_lmt, fc_config->sw_charge_current);
charge_voltage = fc_config->vbat_lmt + 200 * 1000;
ret = set_sw_charger_voltage(cm, charge_voltage);
if (ret)
return;
steps = min(min(step_otp_charge, step_ocp_charge), step_ovp_charge);
charge_current += steps * SW_FC_CURRENT_STEP;
CM_DBG(">>>>>>charge_current: %d\n", charge_current);
if ((fc_config->sw_charge_current >= SW_FC_CURRENT_STEP) &&
(charge_current <= fc_config->sw_charge_current_max)) {
fc_config->sw_charge_current = charge_current;
if (steps == 1) {
ret = set_sw_charger_enable(cm);
if (ret)
return;
}
ret = set_sw_charger_current(cm, fc_config->sw_charge_current);
if (ret)
return;
} else {
if (charge_current < SW_FC_CURRENT_STEP) {
ret = set_sw_charger_disable(cm);
if (ret)
return;
}
}
CM_DBG(">>>>>>>fast_charge_algo:setp: %d\n"
">>>>>> step_otp_charge: %d\n"
">>>>>>>step_ocp_charge: %d\n"
">>>>>>>step_ovp_charge: %d\n"
">>>>>>>sw_charge_status: %d\n",
steps,
step_otp_charge,
step_ocp_charge,
step_ovp_charge,
fc_config->sw_charge_status);
}
static int cm_charge_pump_sm(struct charger_manager *cm)
{
struct power_supply_battery_info info;
struct fastcharge_config *fc_config;
union power_supply_propval val;
static int tune_vbus_retry;
int vbus_volt_init_up = 0;
int adapter_volt_max;
static bool recover;
int bat_voltage;
int bat_res = 0;
int ibat_max;
int ret;
info = cm->desc->info;
fc_config = cm->fc_config;
switch (cm->state) {
case PPS_PM_STATE_ENTRY:
recover = false;
if (fc_config->jeita_charge_support && !fc_config->jeita_enable_charge) {
CM_DBG("the temperature: %d.%d\n", fc_config->jeita_temperature / 10,
fc_config->jeita_temperature % 10);
ret = power_supply_get_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_VOLTAGE_MAX\n", __LINE__);
return ret;
}
ret = set_sw_charger_input_limit_current(cm, val.intval);
if (ret)
return ret;
if (cm->fc_config->sw_charge_status) {
ret = set_sw_charger_disable(cm);
if (ret)
return ret;
}
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_JEITA);
cm->desc->polling_interval_ms = 3000;
return 0;
}
if (fc_config->jeita_charge_support) {
ret = set_sw_charger_voltage(cm, fc_config->vbat_lmt);
if (ret)
return ret;
fc_config->sw_charge_current = min(fc_config->jeita_charge_current,
fc_config->sw_charge_current_max);
ret = set_sw_charger_current(cm, fc_config->sw_charge_current);
if (ret)
return ret;
if (!cm->fc_config->sw_charge_status) {
ret = set_sw_charger_enable(cm);
if (ret)
return ret;
}
} else {
ret = set_sw_charger_voltage(cm, fc_config->vbat_lmt);
if (ret)
return ret;
fc_config->sw_charge_current = fc_config->sw_charge_current_max;
ret = set_sw_charger_current(cm, fc_config->sw_charge_current);
if (ret)
return ret;
if (!cm->fc_config->sw_charge_status) {
ret = set_sw_charger_enable(cm);
if (ret)
return ret;
}
}
if (cm->cp.vbat_volt < info.precharge_voltage_max_uv) {
ret = power_supply_get_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
ret = set_sw_charger_input_limit_current(cm, val.intval);
if (ret)
return ret;
CM_DBG("batt_volt-%d, waiting... > %d\n",
cm->cp.vbat_volt, info.precharge_voltage_max_uv);
} else if (cm->cp.vbat_volt > cm->fc_config->vbat_lmt - 100 * 1000) {
pr_info("batt_volt-%d is too high for cp, charging with switch charger(%duv)\n",
cm->cp.vbat_volt, cm->fc_config->vbat_lmt - 100 * 1000);
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_EXIT);
} else {
pr_info("batt_volt-%d is ok, start flash charging\n",
cm->cp.vbat_volt);
cm->desc->polling_interval_ms = 300;
val.intval = 2;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_ONLINE\n", __LINE__);
return ret;
}
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_ENTRY);
}
break;
case PPS_PM_STATE_FC_ENTRY:
CM_DBG("PPS_PM_STATE_FC_ENTRY:\n");
ret = set_sw_charger_input_limit_current(cm, USB_PPS_INPUT_CURRENT);
if (ret)
return ret;
mdelay(10);
ret = power_supply_get_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
CM_DBG("CURRENT_MAX:%d\n", val.intval);
cm->cp.request_current = min(cm->cp.ibus_max + fc_config->ibus_dcdc_lmt, val.intval);
bat_voltage = fc_config->vbat_now - fc_config->ibat_now / 1000 * info.factory_internal_resistance_uohm;
ibat_max = min(fc_config->ibat_lmt, fc_config->charger_mode * cm->cp.request_current);
vbus_volt_init_up = ibat_max / 1000 * info.factory_internal_resistance_uohm;
/* cm->cp.request_voltage = bat_voltage * 2 + 2 * vbus_volt_init_up + ibat_max / 2 * 100 / 1000; */
cm->cp.request_voltage = bat_voltage * fc_config->charger_mode /*Vbat2Vbus*/
/* battery Impedance to vbus / 2 */
+ vbus_volt_init_up / 2 * fc_config->charger_mode
/* cable resistance to Vbus */
+ ibat_max / 2 / fc_config->charger_mode * 100 / 1000;
CM_DBG("cm->cp.request_voltage: %d cm->cp.vbat_volt: %d, vbus_volt_init_up: %d, bat_voltage: %d\n",
cm->cp.request_voltage, cm->cp.vbat_volt, vbus_volt_init_up, bat_voltage);
adapter_volt_max = fc_config->adaper_power_lmt / (cm->cp.request_current / 1000);
cm->cp.request_voltage = min(cm->cp.request_voltage, adapter_volt_max * 1000);
ret = set_sw_charger_voltage(cm, cm->fc_config->vbat_lmt + 200 * 1000);
if (ret)
return ret;
if (fc_config->jeita_charge_support) {
fc_config->sw_charge_current = min(fc_config->jeita_charge_current,
fc_config->sw_charge_current_max);
ret = set_sw_charger_current(cm, fc_config->sw_charge_current);
if (ret)
return ret;
}
if ((fc_config->jeita_charge_support && !fc_config->jeita_enable_charge) ||
(fc_config->ibat_lmt / 2 < cm->cp.request_current) ||
(fc_config->sw_ovp_flag)) {
CM_DBG("PPS_PM_STATE_ENTRY: disable SW charge\n");
if (cm->fc_config->sw_charge_status) {
ret = set_sw_charger_disable(cm);
if (ret)
return ret;
}
} else {
CM_DBG("PPS_PM_STATE_ENTRY: enable SW charge\n");
if (!cm->fc_config->sw_charge_status &&
fc_config->jeita_charge_support &&
fc_config->jeita_enable_charge) {
ret = set_sw_charger_enable(cm);
if (ret)
return ret;
}
}
val.intval = cm->cp.request_voltage;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_VOLTAGE_NOW\n", __LINE__);
return ret;
}
val.intval = cm->cp.request_current;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_NOW,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_CURRENT_NOW\n", __LINE__);
return ret;
}
CM_DBG("request_voltage:%d, request_current:%d\n",
cm->cp.request_voltage, cm->cp.request_current);
if (!cm->cp.charge_enabled) {
ret = set_cp_enable(cm);
if (ret)
return ret;
}
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_ENTRY_1);
cm->desc->polling_interval_ms = 300;
tune_vbus_retry = 0;
break;
case PPS_PM_STATE_FC_ENTRY_1:
CM_DBG("PPS_PM_STATE_FC_ENTRY_1: old cm->cp.request_voltage: %d\n",
cm->cp.request_voltage);
cm->cp.request_voltage += cm->cp.request_voltage - cm->cp.vbus_volt;
adapter_volt_max = fc_config->adaper_power_lmt / (cm->cp.request_current / 1000);
cm->cp.request_voltage = min(cm->cp.request_voltage, adapter_volt_max * 1000);
ret = set_sw_charger_voltage(cm, cm->fc_config->vbat_lmt + 200 * 1000);
if (ret)
return ret;
if (fc_config->jeita_charge_support) {
fc_config->sw_charge_current = min(fc_config->jeita_charge_current,
fc_config->sw_charge_current_max);
ret = set_sw_charger_current(cm, fc_config->sw_charge_current);
if (ret)
return ret;
}
if ((fc_config->jeita_charge_support && !fc_config->jeita_enable_charge) ||
(fc_config->ibat_lmt / 2 < cm->cp.request_current) ||
(fc_config->sw_ovp_flag)) {
CM_DBG("PPS_PM_STATE_ENTRY: disable SW charge\n");
if (cm->fc_config->sw_charge_status) {
ret = set_sw_charger_disable(cm);
if (ret)
return ret;
}
} else {
CM_DBG("PPS_PM_STATE_ENTRY: enable SW charge\n");
if (!cm->fc_config->sw_charge_status &&
fc_config->jeita_charge_support &&
fc_config->jeita_enable_charge) {
ret = set_sw_charger_enable(cm);
if (ret)
return ret;
}
}
val.intval = cm->cp.request_voltage;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_VOLTAGE_NOW\n", __LINE__);
return ret;
}
val.intval = cm->cp.request_current;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_NOW,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_CURRENT_NOW\n", __LINE__);
return ret;
}
cm->desc->polling_interval_ms = 300;
CM_DBG("PPS_PM_STATE_FC_ENTRY_1: request_voltage:%d, request_current:%d\n",
cm->cp.request_voltage, cm->cp.request_current);
CM_DBG("PPS_PM_STATE_FC_ENTRY_1: cm->cp.vbus_volt: %d vbus_volt_init_up: %d\n"
"bat_res: %d, factory_internal_resistance_uohm: %d\n",
cm->cp.vbus_volt, vbus_volt_init_up, bat_res,
info.factory_internal_resistance_uohm);
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_ENTRY_2);
break;
case PPS_PM_STATE_FC_ENTRY_2:
CM_DBG("PPS_PM_STATE_FC_ENTRY_2: request_voltage:%d,vbus_volt: %d, %d\n",
cm->cp.request_voltage, cm->cp.vbus_volt, cm->cp.vbat_volt);
ret = set_sw_charger_voltage(cm, cm->fc_config->vbat_lmt + 200 * 1000);
if (ret)
return ret;
if (fc_config->jeita_charge_support) {
fc_config->sw_charge_current = min(fc_config->jeita_charge_current,
fc_config->sw_charge_current_max);
ret = set_sw_charger_current(cm, fc_config->sw_charge_current);
if (ret)
return ret;
}
if ((fc_config->jeita_charge_support && !fc_config->jeita_enable_charge) ||
(fc_config->ibat_lmt / 2 < cm->cp.request_current) ||
(fc_config->sw_ovp_flag)) {
CM_DBG("PPS_PM_STATE_ENTRY: disable SW charge\n");
if (cm->fc_config->sw_charge_status) {
ret = set_sw_charger_disable(cm);
if (ret)
return ret;
}
} else {
CM_DBG("PPS_PM_STATE_ENTRY: enable SW charge\n");
if (!cm->fc_config->sw_charge_status &&
fc_config->jeita_charge_support &&
fc_config->jeita_enable_charge) {
ret = set_sw_charger_enable(cm);
if (ret)
return ret;
}
}
if (cm->cp.vbus_error_low) {
tune_vbus_retry++;
cm->cp.request_voltage += FC_VOLTAGE_STEP * 2;
val.intval = cm->cp.request_voltage;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_VOLTAGE_NOW\n", __LINE__);
return ret;
}
ret = set_cp_enable(cm);
if (ret)
return ret;
CM_DBG("vbus_error_low: request_voltage:%d, request_current:%d\n",
cm->cp.request_voltage, cm->cp.request_current);
} else if (cm->cp.vbus_error_high) {
tune_vbus_retry++;
cm->cp.request_voltage -= FC_VOLTAGE_STEP * 2;
val.intval = cm->cp.request_voltage;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_VOLTAGE_NOW\n", __LINE__);
return ret;
}
ret = set_cp_enable(cm);
if (ret)
return ret;
CM_DBG("vbus_error_high: request_voltage:%d, request_current:%d\n",
cm->cp.request_voltage, cm->cp.request_current);
} else {
CM_DBG("adapter volt tune ok, retry %d times\n", tune_vbus_retry);
cm->fc2_taper_timer = 0;
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_TUNE);
break;
}
if (tune_vbus_retry > 25) {
CM_DBG("Failed to tune adapter volt into valid range.\n");
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_EXIT);
}
cm->desc->polling_interval_ms = 100;
break;
case PPS_PM_STATE_FC_TUNE:
cm->fc_config->charge_type = CHARGE_TYPE_PPS;
CM_DBG("PPS_PM_STATE_FC_TUNE:\n");
ret = cm_charge_pump_algo(cm);
if (ret == PM_ALGO_RET_THERM_FAULT) {
CM_DBG("Move to stop charging\n");
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_JEITA);
} else if (ret == PM_ALGO_RET_OTHER_FAULT || ret == PM_ALGO_RET_TAPER_DONE) {
CM_DBG("Move to switch charging\n");
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_EXIT);
} else if (ret == PM_ALGO_RET_CHG_DISABLED) {
CM_DBG("Move to switch charging, will try to recover flash charging\n");
recover = true;
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_EXIT);
} else {
if (!cm->cp.charge_enabled) {
ret = set_cp_enable(cm);
if (ret)
return ret;
}
cm_sw_fast_charge_algo(cm);
cm->cp.request_voltage = min(cm->cp.request_voltage, cm->cp.vbus_max);
CM_DBG("cm->cp.vbat_volt: %d\n", cm->cp.vbat_volt);
val.intval = cm->cp.request_voltage;
CM_DBG("cm->cp.request_voltage: %d\n", cm->cp.request_voltage);
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_VOLTAGE_NOW\n", __LINE__);
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_EXIT);
return ret;
}
val.intval = cm->cp.request_current;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_NOW,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_NOW\n", __LINE__);
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_EXIT);
return ret;
}
cm->desc->polling_interval_ms = 1000;
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_TUNE);
}
break;
case PPS_PM_STATE_FC_JEITA:
CM_DBG("PPS_PM_STATE_FC_JEITA:%d\n", cm->fc_config->jeita_temperature);
if (!cm->cp.charge_enabled) {
ret = power_supply_get_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
ret = set_sw_charger_input_limit_current(cm, val.intval);
if (ret)
return ret;
}
if (!cm->fc_config->jeita_enable_charge && cm->cp.charge_enabled) {
ret = set_cp_disable(cm);
if (ret)
return ret;
if (cm->fc_config->sw_charge_status) {
ret = set_sw_charger_disable(cm);
if (ret)
return ret;
}
ret = set_sw_charger_voltage(cm, cm->fc_config->vbat_lmt);
if (ret)
return ret;
if (fc_config->jeita_charge_support) {
fc_config->sw_charge_current = min(fc_config->jeita_charge_current,
fc_config->sw_charge_current_max);
ret = set_sw_charger_current(cm, fc_config->sw_charge_current);
if (ret)
return ret;
}
val.intval = 1;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_ONLINE\n", __LINE__);
return ret;
}
cm_charge_pump_move_state(cm, PPS_PM_STATE_FC_JEITA);
}
if (cm->fc_config->jeita_enable_charge) {
CM_DBG("EXIT PPS_PM_STATE_FC_JEITA:%d\n", cm->fc_config->jeita_temperature);
cm_charge_pump_move_state(cm, PPS_PM_STATE_ENTRY);
}
break;
case PPS_PM_STATE_FC_EXIT:
CM_DBG("PPS_PM_STATE_FC_EXIT:\n");
if (cm->cp.charge_enabled) {
ret = set_cp_disable(cm);
if (ret)
return ret;
if (cm->fc_config->sw_charge_status) {
ret = set_sw_charger_disable(cm);
if (ret)
return ret;
}
ret = set_sw_charger_voltage(cm, cm->fc_config->vbat_lmt);
if (ret)
return ret;
if (fc_config->jeita_charge_support) {
fc_config->sw_charge_current = min(fc_config->jeita_charge_current,
fc_config->sw_charge_current_max);
ret = set_sw_charger_current(cm, fc_config->sw_charge_current);
if (ret)
return ret;
}
if ((!cm->fc_config->sw_charge_status) &&
(fc_config->jeita_charge_support && fc_config->jeita_enable_charge)) {
ret = set_sw_charger_enable(cm);
if (ret)
return ret;
}
} else {
ret = power_supply_get_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
ret = set_sw_charger_input_limit_current(cm, val.intval);
if (ret)
return ret;
}
val.intval = 1;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_ONLINE\n", __LINE__);
return ret;
}
cm->fc_config->charge_type = CHARGE_TYPE_PD;
if (recover)
cm_charge_pump_move_state(cm, PPS_PM_STATE_ENTRY);
break;
}
return 0;
}
static void cm_disable_charge(struct charger_manager *cm);
/**
* _cm_monitor - Monitor the temperature and return true for exceptions.
* @cm: the Charger Manager representing the battery.
*
* Returns true if there is an event to notify for the battery.
* (True if the status of "emergency_stop" changes)
*/
static void _cm_monitor(struct charger_manager *cm)
{
cm_update_charge_pump_status(cm);
cm_update_jeita_charge_info(cm);
cm_charge_limit_update(cm);
cm_debug_information(cm);
cm_charge_pump_sm(cm);
}
/**
* _setup_polling - Setup the next instance of polling.
* @work: work_struct of the function _setup_polling.
*/
static void _setup_polling(struct charger_manager *cm)
{
unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */
unsigned long min = ULONG_MAX;
bool keep_polling = false;
unsigned long _next_polling;
if ((is_polling_required(cm) && cm->desc->polling_interval_ms) ||
(cm->attached && cm->fc_charger_enabled)) {
CM_DBG("cm->attached: %d\n", cm->attached);
keep_polling = true;
if (min > cm->desc->polling_interval_ms)
min = cm->desc->polling_interval_ms;
}
CM_DBG("keep_polling: %d\n", keep_polling);
polling_jiffy = msecs_to_jiffies(min);
if (polling_jiffy <= CM_JIFFIES_SMALL)
polling_jiffy = CM_JIFFIES_SMALL + 1;
if (!keep_polling)
polling_jiffy = ULONG_MAX;
if (polling_jiffy == ULONG_MAX)
goto out;
WARN(cm->cm_wq == NULL, "rockchip-charger-manager: workqueue not initialized\n");
/*
* Use mod_delayed_work() iff the next polling interval should
* occur before the currently scheduled one. If @cm_monitor_work
* isn't active, the end result is the same, so no need to worry
* about stale @next_polling.
*/
_next_polling = jiffies + polling_jiffy;
if (time_before(_next_polling, cm->next_polling)) {
queue_delayed_work(cm->cm_wq, &cm->cm_monitor_work, polling_jiffy);
cm->next_polling = _next_polling;
} else {
if (queue_delayed_work(cm->cm_wq, &cm->cm_monitor_work, polling_jiffy))
cm->next_polling = _next_polling;
}
out:
return;
}
/**
* cm_monitor_poller - The Monitor / Poller.
* @work: work_struct of the function cm_monitor_poller
*
* During non-suspended state, cm_monitor_poller is used to
* poll and monitor the batteries.
*/
static void cm_monitor_poller(struct work_struct *work)
{
struct charger_manager *cm = container_of(work,
struct charger_manager,
cm_monitor_work.work);
cm->fc_charger_poller_status = false;
if (cm->attached)
_cm_monitor(cm);
if (cm->attached)
_setup_polling(cm);
cm->fc_charger_poller_status = true;
}
#endif
static void cm_jeita_poller(struct work_struct *work)
{
struct charger_manager *cm = container_of(work,
struct charger_manager,
cm_jeita_work.work);
static int status = CM_JEITA_GOOD;
int ret;
cm_update_jeita_charge_info(cm);
ret = get_sw_charger_current_max(cm, &cm->fc_config->sw_charge_current_max);
if (ret)
return;
if (cm->fc_config->jeita_status != status) {
if (cm->fc_config->charge_type != CHARGE_TYPE_PPS) {
ret = set_sw_charger_voltage(cm, cm->fc_config->jeita_charge_voltage);
if (ret)
return;
}
if ((cm->fc_config->jeita_status == CM_JEITA_COLD) ||
(cm->fc_config->jeita_status == CM_JEITA_HOT)) {
if (cm->fc_config->sw_charge_status) {
ret = set_sw_charger_disable(cm);
if (ret)
return;
}
} else {
cm->fc_config->sw_charge_current = min(cm->fc_config->jeita_charge_current,
cm->fc_config->sw_charge_current_max);
ret = set_sw_charger_current(cm, cm->fc_config->sw_charge_current);
if (ret)
return;
if (!cm->fc_config->sw_charge_status) {
ret = set_sw_charger_enable(cm);
if (ret)
return;
}
}
status = cm->fc_config->jeita_status;
} else {
if (cm->fc_config->charge_type != CHARGE_TYPE_PPS) {
ret = set_sw_charger_voltage(cm, cm->fc_config->jeita_charge_voltage);
if (ret)
return;
}
cm->fc_config->sw_charge_current = min(cm->fc_config->jeita_charge_current,
cm->fc_config->sw_charge_current_max);
ret = set_sw_charger_current(cm, cm->fc_config->sw_charge_current);
if (ret)
return;
if (!cm->fc_config->sw_charge_status && cm->fc_config->jeita_enable_charge) {
ret = set_sw_charger_enable(cm);
if (ret)
return;
}
}
if (cm->attached || cm->desc->dc_charger_status)
queue_delayed_work(cm->cm_wq,
&cm->cm_jeita_work,
msecs_to_jiffies(5 * 1000));
else
status = CM_JEITA_GOOD;
}
/**
* charger_extcon_work - enable/disable charger according to
* the state of charger cable
*
* @work: work_struct of the function charger_extcon_work.
*/
static void charger_extcon_work(struct work_struct *work)
{
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
struct charger_manager *cm = container_of(work,
struct charger_manager,
wq);
cancel_delayed_work(&cm->cm_monitor_work);
queue_delayed_work(cm->cm_wq, &cm->cm_monitor_work, 0);
#endif
}
static int cm_pd_adapter_det(struct charger_manager *cm)
{
struct charger_desc *desc = cm->desc;
struct fastcharge_config *fc_config;
union power_supply_propval val;
int ret;
fc_config = cm->fc_config;
cm->is_fast_charge = 1;
CM_DBG("POWER_SUPPLY_USB_TYPE_PD\n");
fc_config->charge_type = CHARGE_TYPE_PD;
ret = power_supply_get_property(desc->tcpm_psy,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
fc_config->adapter_volt_max_lmt = val.intval;
ret = power_supply_get_property(desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAXn", __LINE__);
return ret;
}
fc_config->adapter_curr_max_lmt = val.intval;
CM_DBG("adapter_curr_max_lmt:%d\n", fc_config->adapter_curr_max_lmt);
set_sw_charger_input_limit_current(cm, fc_config->adapter_curr_max_lmt);
ret = set_sw_charger_enable(cm);
return ret;
}
static int cm_pps_adapter_det(struct charger_manager *cm)
{
struct charger_desc *desc = cm->desc;
struct fastcharge_config *fc_config;
union power_supply_propval val;
int ret;
fc_config = cm->fc_config;
cm->is_fast_charge = 1;
CM_DBG("POWER_SUPPLY_USB_TYPE_PD_PPS\n");
if (desc->psy_charger_pump_stat != NULL) {
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
fc_config->charge_type = CHARGE_TYPE_PPS;
CM_DBG("charge_type: %d, %d\n", cm->fc_config->charge_type, __LINE__);
cm_charge_pump_move_state(cm, PPS_PM_STATE_ENTRY);
/*
* Setup work for controlling charger
* according to charger cable.
*/
if (cm->fc_charger_enabled)
queue_delayed_work(cm->cm_wq, &cm->cm_monitor_work, 1500);
else
queue_delayed_work(cm->cm_wq, &cm->cm_monitor_work, 300);
cm->fc_charger_enabled = 1;
#endif
} else {
if (cm->fc_config->jeita_charge_support) {
cancel_delayed_work(&cm->cm_jeita_work);
queue_delayed_work(cm->cm_wq, &cm->cm_jeita_work, 1000);
}
val.intval = 1;
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to set POWER_SUPPLY_PROP_ONLINE\n", __LINE__);
return ret;
}
CM_DBG("POWER_SUPPLY_USB_TYPE_PD\n");
fc_config->charge_type = CHARGE_TYPE_PD;
ret = power_supply_get_property(desc->tcpm_psy,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
fc_config->adapter_volt_max_lmt = val.intval;
ret = power_supply_get_property(desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAXn", __LINE__);
return ret;
}
fc_config->adapter_curr_max_lmt = val.intval;
CM_DBG("adapter_curr_max_lmt:%d\n", fc_config->adapter_curr_max_lmt);
set_sw_charger_input_limit_current(cm, fc_config->adapter_curr_max_lmt);
ret = set_sw_charger_enable(cm);
return ret;
}
return 0;
}
static int cm_normal_adapter_det(struct charger_manager *cm)
{
struct charger_desc *desc = cm->desc;
struct fastcharge_config *fc_config;
union power_supply_propval val;
int ret;
fc_config = cm->fc_config;
fc_config->adaper_power_init_flag = 0;
ret = power_supply_get_property(desc->tcpm_psy,
POWER_SUPPLY_PROP_CURRENT_MAX,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
if ((val.intval == 1500 * 1000) ||
(val.intval == 3000 * 1000) ||
(val.intval == 900 * 1000)) {
CM_DBG("POWER_SUPPLY_USB_TYPE_C\n");
fc_config->charge_type = CHARGE_TYPE_TYPE_C;
fc_config->adapter_curr_max_lmt = val.intval;
if (!cm->desc->dc_charger_status) {
ret = set_sw_charger_input_limit_current(cm, USB_TYPE_C_INPUT_CURRENT);
if (ret)
return ret;
}
CM_DBG("USB-TYPE: POWER_SUPPLY_USB_TYPE_C\n");
} else {
if (extcon_get_state(desc->extcon_dev, EXTCON_CHG_USB_SDP) > 0) {
CM_DBG("EXTCON_CHG_USB_SDP\n");
fc_config->charge_type = CHARGE_TYPE_NORMAL;
if (!cm->desc->dc_charger_status) {
ret = set_sw_charger_input_limit_current(cm, USB_SDP_INPUT_CURRENT);
if (ret)
return ret;
}
} else if (extcon_get_state(desc->extcon_dev, EXTCON_CHG_USB_DCP) > 0) {
CM_DBG("EXTCON_CHG_USB_DCP\n");
fc_config->charge_type = CHARGE_TYPE_NORMAL;
if (!cm->desc->dc_charger_status) {
ret = set_sw_charger_input_limit_current(cm, USB_DCP_INPUT_CURRENT);
if (ret)
return ret;
}
} else if (extcon_get_state(desc->extcon_dev, EXTCON_CHG_USB_CDP) > 0) {
CM_DBG("EXTCON_CHG_USB_CDP:%duA dc_charger_status: %d\n",
USB_CDP_INPUT_CURRENT, cm->desc->dc_charger_status);
fc_config->charge_type = CHARGE_TYPE_NORMAL;
if (!cm->desc->dc_charger_status) {
ret = set_sw_charger_input_limit_current(cm, USB_CDP_INPUT_CURRENT);
if (ret)
return ret;
}
}
}
ret = set_sw_charger_enable(cm);
return ret;
}
static void cm_adapter_disattach(struct charger_manager *cm)
{
struct fastcharge_config *fc_config;
fc_config = cm->fc_config;
cm->is_charge = 0;
cm->is_fast_charge = 0;
fc_config->charge_type = CHARGE_TYPE_DISCHARGE;
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
cm_charge_limit_update(cm);
#endif
if (!cm->desc->dc_charger_status) {
set_sw_charger_input_limit_current(cm, USB_SDP_INPUT_CURRENT);
set_sw_charger_disable(cm);
set_sw_charger_voltage(cm, cm->fc_config->vbat_lmt);
pm_relax(cm->dev);
} else {
queue_delayed_work(cm->cm_wq, &cm->dc_work, msecs_to_jiffies(500));
}
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
cm_charge_pump_move_state(cm, PPS_PM_STATE_ENTRY);
if (cm->cp.charge_enabled)
set_cp_disable(cm);
#endif
cm->fc_charger_enabled = 0;
cm->fc_config->sw_ovp_flag = 0;
cm->fc_config->fc_charge_error = false;
if (cm->desc->measure_battery_temp)
cm->fc_config->jeita_enable_charge = true;
CM_DBG("%s, %d disattached\n", __func__, cm->attached);
}
/**
* charger_extcon_notifier - receive the state of charger cable
* when registered cable is attached or detached.
*
* @self: the notifier block of the charger_extcon_notifier.
* @event: the cable state.
* @ptr: the data pointer of notifier block.
*/
static int charger_extcon_notifier(struct notifier_block *self,
unsigned long event,
void *ptr)
{
struct charger_manager *cm =
container_of(self, struct charger_manager, nb);
struct charger_desc *desc = cm->desc;
struct fastcharge_config *fc_config;
union power_supply_propval val;
int tcpm_wait = 0;
int ret;
/*
* The newly state of charger cable.
* If cable is attached, cable->attached is true.
*/
cm->attached = event;
fc_config = cm->fc_config;
CM_DBG("%s, %d\n", __func__, cm->attached);
if (event) {
cm->is_charge = 1;
pm_stay_awake(cm->dev);
if (extcon_get_state(desc->extcon_dev, EXTCON_CHG_USB_DCP) > 0) {
CM_DBG("EXTCON_CHG_USB_DCP\n");
ret = power_supply_get_property(desc->tcpm_psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
while (tcpm_wait++ < 30) {
if (!val.intval)
break;
ret = power_supply_get_property(desc->tcpm_psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
if (!val.intval)
break;
msleep(20);
}
}
ret = power_supply_get_property(desc->tcpm_psy,
POWER_SUPPLY_PROP_USB_TYPE,
&val);
if (ret) {
dev_err(cm->dev, "[%d] failed to get POWER_SUPPLY_PROP_CURRENT_MAX\n", __LINE__);
return ret;
}
switch (val.intval) {
case POWER_SUPPLY_USB_TYPE_PD:
ret = cm_pd_adapter_det(cm);
CM_DBG("USB-TYPE: POWER_SUPPLY_USB_TYPE_PD\n");
if (ret)
return NOTIFY_BAD;
break;
case POWER_SUPPLY_USB_TYPE_PD_PPS:
ret = cm_pps_adapter_det(cm);
if (ret)
return NOTIFY_BAD;
CM_DBG("USB-TYPE: POWER_SUPPLY_USB_TYPE_PD\n");
break;
default:
ret = cm_normal_adapter_det(cm);
if (ret)
return NOTIFY_BAD;
break;
}
if (val.intval != POWER_SUPPLY_USB_TYPE_PD_PPS) {
if (cm->fc_config->jeita_charge_support) {
cancel_delayed_work(&cm->cm_jeita_work);
queue_delayed_work(cm->cm_wq, &cm->cm_jeita_work, 0);
}
}
} else {
cm_adapter_disattach(cm);
}
return NOTIFY_DONE;
}
static void cm_disable_charge(struct charger_manager *cm)
{
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
cancel_delayed_work_sync(&cm->cm_monitor_work);
#endif
cancel_delayed_work_sync(&cm->cm_jeita_work);
cancel_delayed_work_sync(&cm->dc_work);
cm_adapter_disattach(cm);
}
static void cm_enable_charge(struct charger_manager *cm)
{
set_sw_charger_enable(cm);
charger_extcon_notifier(&cm->nb, 1, NULL);
queue_delayed_work(cm->cm_wq, &cm->dc_work, msecs_to_jiffies(500));
}
static ssize_t chg_en_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return 0;
}
static ssize_t chg_en_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct charger_manager *cm = dev_get_drvdata(dev);
int ret = 0;
bool en = 0;
ret = kstrtobool(buf, &en);
if (ret) {
pr_err("Unknown command\n");
return ret;
}
if (en)
cm_enable_charge(cm);
else
cm_disable_charge(cm);
return count;
}
static DEVICE_ATTR_RW(chg_en);
static void chg_en_create_device_node(struct device *dev)
{
device_create_file(dev, &dev_attr_chg_en);
}
/**
* charger_extcon_init - register external connector to use it
* as the charger cable
*
* @cm: the Charger Manager representing the battery.
* @cable: the Charger cable representing the external connector.
*/
static int charger_extcon_init(struct charger_manager *cm)
{
struct charger_desc *desc = cm->desc;
u64 extcon_type = EXTCON_NONE;
unsigned long event;
int ret, i;
/*
* Charger manager use Extcon framework to identify
* the charger cable among various external connector
* cable (e.g., TA, USB, MHL, Dock).
*/
INIT_WORK(&cm->wq, charger_extcon_work);
cm->nb.notifier_call = charger_extcon_notifier;
if (IS_ERR_OR_NULL(desc->extcon_dev)) {
pr_err("Cannot find extcon_dev\n");
if (desc->extcon_dev == NULL)
return -EPROBE_DEFER;
else
return PTR_ERR(desc->extcon_dev);
}
for (i = 0; i < ARRAY_SIZE(extcon_mapping); i++) {
extcon_type = extcon_mapping[i].extcon_type;
if (extcon_type == EXTCON_NONE) {
pr_err("Cannot find cable for type %s", extcon_mapping[i].name);
return -EINVAL;
}
ret = devm_extcon_register_notifier(cm->dev,
desc->extcon_dev,
extcon_type,
&cm->nb);
if (ret < 0) {
pr_err("Cannot register extcon_dev for %s\n", extcon_mapping[i].name);
return ret;
}
CM_DBG("%s: %s\n", desc->extcon_dev->name, extcon_mapping[i].name);
event = extcon_get_state(desc->extcon_dev, extcon_type);
if (event)
charger_extcon_notifier(&cm->nb, event, NULL);
}
return 0;
}
static const struct of_device_id charger_manager_match[] = {
{
.compatible = "rockchip-charger-manager",
},
{},
};
MODULE_DEVICE_TABLE(of, charger_manager_match);
static struct charger_desc *of_cm_parse_desc(struct device *dev)
{
struct device_node *np = dev->of_node;
u32 poll_mode = CM_POLL_DISABLE;
struct charger_desc *desc;
const __be32 *list;
int size, count, i;
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
return ERR_PTR(-ENOMEM);
of_property_read_u32(np, "cm-poll-mode", &poll_mode);
desc->polling_mode = poll_mode;
of_property_read_u32(np, "cm-poll-interval", &desc->polling_interval_ms);
of_property_read_u32(np, "cm-cp-support-mult-mode", &desc->support_mult_mode);
of_property_read_string(np, "cm-chargers", &desc->psy_charger_stat);
of_property_read_string(np, "cm-charge-pump", &desc->psy_charger_pump_stat);
of_property_read_string(np, "cm-fuel-gauge", &desc->psy_fuel_gauge);
CM_DBG("cm-chargers: %s\n", desc->psy_charger_stat);
CM_DBG("cm-charge-pumps: %s\n", desc->psy_charger_pump_stat);
CM_DBG("cm-fuel-gauge: %s\n", desc->psy_fuel_gauge);
desc->tcpm_psy = power_supply_get_by_phandle(np, "cm-chargers-phandle");
if (IS_ERR_OR_NULL(desc->tcpm_psy)) {
CM_DBG("cm-chargers-phandle is error\n");
return ERR_PTR(-ENOMEM);
}
CM_DBG("tcpm_psy name : %s\n", desc->tcpm_psy->desc->name);
desc->extcon_dev = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR_OR_NULL(desc->extcon_dev)) {
CM_DBG("CM: get extcon_edev error\n");
return ERR_PTR(-ENOMEM);
}
if (of_find_property(np, "cm-jeita-temp-charge-table", &size)) {
list = of_get_property(np, "cm-jeita-temp-charge-table", &size);
size /= sizeof(u32);
if (!size || (size % 4)) {
dev_err(dev, "invalid temperature_chrg_table: size=%d\n", size);
return ERR_PTR(-ENOMEM);
}
count = size / 4;
desc->jeita_charge_table_count = count;
if (count < 1) {
desc->measure_battery_temp = false;
goto out;
}
desc->jeita_charge_table = devm_kzalloc(dev,
count * sizeof(*desc->jeita_charge_table),
GFP_KERNEL);
if (!desc->jeita_charge_table)
return ERR_PTR(-ENOMEM);
for (i = 0; i < count; i++) {
/* temperature */
desc->jeita_charge_table[i].temp_down = be32_to_cpu(*list++);
desc->jeita_charge_table[i].temp_up = be32_to_cpu(*list++);
desc->jeita_charge_table[i].chrg_current = be32_to_cpu(*list++);
desc->jeita_charge_table[i].chrg_voltage = be32_to_cpu(*list++);
CM_DBG("temp%d: [%d, %d], chrg_current=%d, chrg_voltage: %d\n",
i, desc->jeita_charge_table[i].temp_down,
desc->jeita_charge_table[i].temp_up,
desc->jeita_charge_table[i].chrg_current,
desc->jeita_charge_table[i].chrg_voltage);
}
/* the charge must be done externally to fully comply with
* the JEITA safety guidelines if this flag is set!
*/
desc->measure_battery_temp = true;
}
desc->gpiod_dc_det = devm_gpiod_get_optional(dev, "dc-det", GPIOD_IN);
if (IS_ERR_OR_NULL(desc->gpiod_dc_det)) {
desc->support_dc_charger = false;
dev_warn(dev, "Failed to request GPIO dc detection!\n");
} else {
desc->support_dc_charger = true;
CM_DBG("support dc charge\n");
}
out:
return desc;
}
static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev)
{
if (pdev->dev.of_node)
return of_cm_parse_desc(&pdev->dev);
return dev_get_platdata(&pdev->dev);
}
static void cm_dc_det_work(struct work_struct *work)
{
struct charger_manager *cm = container_of(work,
struct charger_manager,
dc_work.work);
struct power_supply_battery_info info;
info = cm->desc->info;
if (!gpiod_get_value(cm->desc->gpiod_dc_det)) {
if (cm->fc_config->charge_type <= CHARGE_TYPE_TYPE_C) {
set_sw_charger_input_limit_current(cm, USB_DCP_INPUT_CURRENT);
set_sw_charger_input_limit_voltage(cm,
info.constant_charge_voltage_max_uv);
set_sw_charger_enable(cm);
}
cm->desc->dc_charger_status = 1;
if (cm->fc_config->jeita_charge_support) {
cancel_delayed_work(&cm->cm_jeita_work);
queue_delayed_work(cm->cm_wq, &cm->cm_jeita_work, 0);
}
CM_DBG("DC DET PIN charging\n");
} else {
cm->desc->dc_charger_status = 0;
set_sw_charger_disable(cm);
set_sw_charger_input_limit_current(cm, USB_PPS_INPUT_CURRENT);
/* mdelay(1500); */
if ((cm->fc_config->charge_type > CHARGE_TYPE_DISCHARGE) &&
(cm->fc_config->charge_type <= CHARGE_TYPE_PD)) {
CM_DBG("DC DET (CHARGE_TYPE_DISCHARGE , CHARGE_TYPE_PD]\n");
charger_extcon_notifier(&cm->nb, 1, NULL);
} else if (cm->fc_config->charge_type == CHARGE_TYPE_PPS) {
CM_DBG("DC DET PIN discharging: USB_PPS_INPUT_CURRENT\n");
} else {
CM_DBG("DC DET PIN discharging: USB_SDP_INPUT_CURRENT\n");
set_sw_charger_input_limit_current(cm, USB_SDP_INPUT_CURRENT);
charger_extcon_notifier(&cm->nb, 0, NULL);
}
CM_DBG("DC DET PIN discharging\n");
}
}
static irqreturn_t cm_dc_irq_handler_thread(int irq, void *cm_dc_charge)
{
struct charger_manager *cm = (struct charger_manager *)cm_dc_charge;
if (gpiod_get_value(cm->desc->gpiod_dc_det))
irq_set_irq_type(irq, IRQF_TRIGGER_LOW);
else
irq_set_irq_type(irq, IRQF_TRIGGER_HIGH);
if (!gpiod_get_value(cm->desc->gpiod_dc_det)) {
pm_stay_awake(cm->dev);
cm->desc->dc_charger_status = 1;
} else
cm->desc->dc_charger_status = 0;
if (cm->fc_config->charge_type <= CHARGE_TYPE_TYPE_C)
queue_delayed_work(cm->cm_wq, &cm->dc_work, msecs_to_jiffies(500));
return IRQ_HANDLED;
}
static int cm_dc_init(struct charger_manager *cm)
{
unsigned long irq_flags;
int dc_det_irq;
int ret, level;
if (!cm->desc->support_dc_charger)
return 0;
level = gpiod_get_value(cm->desc->gpiod_dc_det);
if (level) {
irq_flags = IRQF_TRIGGER_LOW;
} else {
irq_flags = IRQF_TRIGGER_HIGH;
queue_delayed_work(cm->cm_wq, &cm->dc_work, msecs_to_jiffies(500));
}
dc_det_irq = gpiod_to_irq(cm->desc->gpiod_dc_det);
ret = devm_request_threaded_irq(cm->dev, dc_det_irq, NULL,
cm_dc_irq_handler_thread,
irq_flags | IRQF_ONESHOT, "charge_dc_irq", cm);
if (ret != 0) {
dev_err(cm->dev, "fail to request dc_det_irq\n");
return ret;
}
gpiod_set_debounce(cm->desc->gpiod_dc_det, 100 * 1000);
enable_irq_wake(dc_det_irq);
return 0;
}
static int charger_manager_probe(struct platform_device *pdev)
{
struct charger_desc *desc = cm_get_drv_data(pdev);
struct power_supply_battery_info *info;
struct power_supply *psy;
struct charger_manager *cm;
int ret;
if (IS_ERR(desc)) {
dev_err(&pdev->dev, "No platform data (desc) found\n");
return PTR_ERR(desc);
}
cm = devm_kzalloc(&pdev->dev, sizeof(*cm), GFP_KERNEL);
if (!cm)
return -ENOMEM;
/* Basic Values. Unspecified are Null or 0 */
cm->dev = &pdev->dev;
cm->desc = desc;
if (!desc->psy_charger_stat) {
dev_err(&pdev->dev, "No power supply defined\n");
return -EINVAL;
}
if (!desc->psy_fuel_gauge) {
dev_err(&pdev->dev, "No fuel gauge power supply defined\n");
return -EINVAL;
}
if (!desc->psy_charger_pump_stat)
dev_err(&pdev->dev, "Cannot find charge pump power supply\n");
if (cm->desc->polling_mode != CM_POLL_DISABLE &&
(desc->polling_interval_ms == 0 ||
msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL)) {
dev_err(&pdev->dev, "polling_interval_ms is too small\n");
return -EINVAL;
}
platform_set_drvdata(pdev, cm);
psy = power_supply_get_by_name(cm->desc->psy_charger_stat);
ret = power_supply_get_battery_info(psy, &info);
if (ret < 0) {
info = &bat_default_info;
desc->info = bat_default_info;
dev_err(&pdev->dev, "failed to get battery information\n");
} else
desc->info = *info;
cm->fc_config = &fc_config_parameter;
ret = get_sw_charger_current_max(cm, &cm->fc_config->sw_charge_current_max);
if (ret)
return -EINVAL;
if (desc->measure_battery_temp) {
cm->fc_config->jeita_charge_support = true;
cm->fc_config->jeita_enable_charge = true;
}
/*
* Charger-manager is capable of waking up the system
* from sleep when event is happened through
* cm_notify_event()
*/
device_init_wakeup(&pdev->dev, true);
device_set_wakeup_capable(&pdev->dev, false);
CM_DBG("battery info:\n");
CM_DBG("INFO: charge-full-design-microamp-hours: %d\n",
info->charge_full_design_uah);
CM_DBG("INFO:factory_internal_resistance_uohm: %d\n",
info->factory_internal_resistance_uohm);
CM_DBG("charge_term_current_ua: %d\n",
info->charge_term_current_ua);
CM_DBG("constant_charge_voltage_max_uv: %d\n",
info->constant_charge_voltage_max_uv);
CM_DBG("constant_charge_current_max_ua: %d\n",
info->constant_charge_current_max_ua);
CM_DBG("precharge_current_ua: %d\n",
info->precharge_current_ua);
CM_DBG("precharge-upper-limit-microvolt: %d\n",
info->precharge_voltage_max_uv);
cm->cm_wq = alloc_ordered_workqueue("%s",
WQ_MEM_RECLAIM | WQ_FREEZABLE,
"cm-charger-wq");
if (unlikely(!cm->cm_wq)) {
dev_err(&pdev->dev, "failed to alloc ordered charger manager wq\n");
return -ENOMEM;
}
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
INIT_DELAYED_WORK(&cm->cm_monitor_work,
cm_monitor_poller);
#endif
INIT_DELAYED_WORK(&cm->cm_jeita_work,
cm_jeita_poller);
INIT_DELAYED_WORK(&cm->dc_work,
cm_dc_det_work);
/* Register extcon device for charger cable */
ret = charger_extcon_init(cm);
if (ret < 0) {
dev_err(&pdev->dev, "Cannot initialize extcon device\n");
goto err_reg_extcon;
}
cm_dc_init(cm);
chg_en_create_device_node(&pdev->dev);
CM_DBG("charger manager ok!!!");
return 0;
err_reg_extcon:
return ret;
}
static int charger_manager_suspend_noirq(struct device *dev)
{
struct charger_manager *cm = dev_get_drvdata(dev);
if (cm->desc->support_dc_charger)
gpiod_set_debounce(cm->desc->gpiod_dc_det, 0);
return 0;
}
static int charger_manager_resume_noirq(struct device *dev)
{
struct charger_manager *cm = dev_get_drvdata(dev);
if (cm->desc->support_dc_charger)
gpiod_set_debounce(cm->desc->gpiod_dc_det, 100 * 1000);
return 0;
}
static const struct dev_pm_ops charger_manager_pm = {
.suspend_noirq = charger_manager_suspend_noirq,
.resume_noirq = charger_manager_resume_noirq,
};
static int charger_manager_remove(struct platform_device *pdev)
{
struct charger_manager *cm = platform_get_drvdata(pdev);
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
cancel_delayed_work_sync(&cm->cm_monitor_work);
#endif
cancel_delayed_work_sync(&cm->cm_jeita_work);
cancel_delayed_work_sync(&cm->dc_work);
return 0;
}
static void charger_manager_shutdown(struct platform_device *pdev)
{
struct charger_manager *cm = platform_get_drvdata(pdev);
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
union power_supply_propval val;
int ret;
cancel_delayed_work_sync(&cm->cm_monitor_work);
#endif
cancel_delayed_work_sync(&cm->cm_jeita_work);
cancel_delayed_work_sync(&cm->dc_work);
CM_DBG("charger manager shutdown\n");
#if defined(CONFIG_ROCKCHIP_CHARGER_MANAGER_CHARGE_PUMP)
CM_DBG("now charge_type: %d, CHARGE_TYPE_PPS:%d\n",
cm->fc_config->charge_type, CHARGE_TYPE_PPS);
if (cm->fc_config->charge_type == CHARGE_TYPE_PPS) {
CM_DBG("PPS_CHARGE_SHUT_DOWN:\n");
set_cp_disable(cm);
val.intval = 1;/* pps --> pd */
ret = power_supply_set_property(cm->desc->tcpm_psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
if (ret)
dev_err(cm->dev, "failed to switch form PPS MODE to PD mode\n");
}
#endif
}
static const struct platform_device_id charger_manager_id[] = {
{ "fast-charger-manager", 0 },
{ },
};
MODULE_DEVICE_TABLE(platform, charger_manager_id);
static struct platform_driver charger_manager_driver = {
.driver = {
.name = "fast-charger-manager",
.pm = &charger_manager_pm,
.of_match_table = charger_manager_match,
},
.probe = charger_manager_probe,
.remove = charger_manager_remove,
.shutdown = charger_manager_shutdown,
.id_table = charger_manager_id,
};
static int __init charger_manager_init(void)
{
return platform_driver_register(&charger_manager_driver);
}
late_initcall(charger_manager_init);
static void __exit charger_manager_cleanup(void)
{
platform_driver_unregister(&charger_manager_driver);
}
module_exit(charger_manager_cleanup);
MODULE_AUTHOR("Xu Shengfei <xsf@rock-chips.com>");
MODULE_DESCRIPTION("Charger Manager");
MODULE_LICENSE("GPL");