// 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 * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include <../drivers/extcon/extcon.h> #include #include #include 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 "); MODULE_DESCRIPTION("Charger Manager"); MODULE_LICENSE("GPL");