diff options
author | Andrei Ciubotariu <aciubotariu@google.com> | 2023-11-12 14:22:41 -0800 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-21 09:50:56 +0000 |
commit | 4c613912d3c629f99f00090262b79fcf2d427f0d (patch) | |
tree | 186e699e5c0125116fd2cf0cc12b6ecb07622cae | |
parent | 3746b4e8cb43097e4f27bbb89735eb9fa41207f8 (diff) | |
download | msm-4c613912d3c629f99f00090262b79fcf2d427f0d.tar.gz |
google-smblite-hvdcp: Add core HVDCP boosting functionality
Add core functionality needed to perform HVDCP voltage boosting such
that the charger voltage stays below a voltage celing after the
boosting process is complete.
Bug: 308351023
Signed-off-by: Andrei Ciubotariu <aciubotariu@google.com>
(cherry picked from https://partner-android-review.googlesource.com/q/commit:09a2d44eeefbeeed688ebbc592a9527cf94de74e)
(cherry picked from commit 4ac20cd937e4de35374c58ea6ed834fb9071a84c)
(cherry picked from https://partner-android-review.googlesource.com/q/commit:94c4bacad155ba3ffffdde31cb4f88900246d2c3)
Merged-In: I323225111c98dc8405a6297d8fc5f78d9c58e091
Change-Id: I323225111c98dc8405a6297d8fc5f78d9c58e091
-rw-r--r-- | drivers/power/supply/google/google-smblite-hvdcp.c | 284 |
1 files changed, 275 insertions, 9 deletions
diff --git a/drivers/power/supply/google/google-smblite-hvdcp.c b/drivers/power/supply/google/google-smblite-hvdcp.c index 44bf89d..74611be 100644 --- a/drivers/power/supply/google/google-smblite-hvdcp.c +++ b/drivers/power/supply/google/google-smblite-hvdcp.c @@ -1,8 +1,32 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2023 Google LLC */ +/* +Controller state transitions illustrated below in graphviz format: + + digraph { + OFF -> USBIN_SUSPEND_WAIT [label="HVDCP request"] + + USBIN_SUSPEND_WAIT -> ENTER_IDLE_WAIT [label="usbin suspended"] + + ENTER_IDLE_WAIT -> NEGOTIATION [label="Charger in QC3.0 idle mode"] + + NEGOTIATION -> NEGOTIATION [label="Voltage adjusted"] + NEGOTIATION -> SETTLE_MONITOR [label="Voltage within target"] + + SETTLE_MONITOR -> NEGOTIATION [label="Voltage changed"] + SETTLE_MONITOR -> USBIN_RESUME_WAIT [label="Voltage remained consistent"] + + USBIN_RESUME_WAIT -> DONE [label="usbin resumed"] + DONE -> OFF [label="unplug"] + } +*/ + #include <linux/alarmtimer.h> #include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/ktime.h> +#include <linux/limits.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/notifier.h> @@ -16,12 +40,22 @@ #define GOOGLE_HVDCP_VOTER "GOOGLE_HVDCP_VOTER" #define USBIN_SUSPENDED_MAX_UA 10000 +#define HVDCP_PULSE_DELTA_UV 200000 #define NUM_VOLTAGE_READS 15 #define PMIC_USBIN_SUSPENDED_ICL 0 #define VBUS_MEAS_ERROR_UV 30000 +#define SUSPEND_WAIT_MS 50 +#define VOLTAGE_CHECK_RETRY_MS 50 +#define WAIT_DISABLED U32_MAX #define STATES \ - X(OFF) + X(OFF) \ + X(USBIN_SUSPEND_WAIT) \ + X(ENTER_IDLE_WAIT) \ + X(NEGOTIATION) \ + X(SETTLE_MONITOR) \ + X(USBIN_RESUME_WAIT) \ + X(DONE) #define CMDS \ X(IDLE) \ @@ -39,6 +73,7 @@ enum cmd { #define X(state) HVDCP_##state, enum hvdcp_state { STATES + NUM_HVDCP_STATES }; #undef X @@ -54,6 +89,31 @@ static const char *cmd_str[] = { }; #undef X +struct state_data { + unsigned int elapsed_ms; + ktime_t last_elapsed_check_kt; + + unsigned int wait_ms; + ktime_t last_state_work_kt; +}; + +struct session_data { + /* + * Slow waits are enabled if a charger voltage change occurs while + * in SETTLE_MONITOR state. Spacing out voltage increment / decrement + * commands seems to help in avoiding extra voltage changes after the + * command is sent. + */ + bool slow_waits; +}; + +struct cmd_wait_info { + u16 typ; + + /* Used when session_data slow_waits == true */ + u16 slow; +}; + struct hvdcp { struct mutex lock; struct notifier_block hvdcp_req_nb; @@ -61,6 +121,13 @@ struct hvdcp { struct work_struct controller_work; struct alarm controller_alarm; enum hvdcp_state state; + struct state_data state_data; + struct session_data sesh_data; + struct cmd_wait_info cmd_waits[NUM_CMDS]; + u16 renegotiation_wait_ms; + u16 cmd_retry_wait_ms; + u16 settle_monitor_ms; + u32 voltage_ceiling; struct device *dev; struct power_supply *shim_psy; struct smblite_shim *smblite_shim; @@ -68,13 +135,21 @@ struct hvdcp { struct gvotable_election *fake_psy_online_votable; }; -static int run_cmd(struct hvdcp *hvdcp, enum cmd cmd) +static inline uint16_t get_cmd_wait(struct hvdcp *hvdcp, enum cmd cmd) +{ + return hvdcp->sesh_data.slow_waits + ? hvdcp->cmd_waits[cmd].slow : hvdcp->cmd_waits[cmd].typ; +} + +static int run_cmd(struct hvdcp *hvdcp, enum cmd cmd, unsigned int *wait_ms_out) { int ret; + *wait_ms_out = get_cmd_wait(hvdcp, cmd); + dev_info(hvdcp->dev, "Running cmd %s\n", cmd_str[cmd]); - if (smblite_shim_is_boost_en(hvdcp->smblite_shim->chg)) { + if (smblite_lib_is_boost_en(hvdcp->smblite_shim->chg)) { dev_err(hvdcp->dev, "Boost mode en (pre-cmd)\n"); return -EAGAIN; } @@ -103,7 +178,7 @@ static int run_cmd(struct hvdcp *hvdcp, enum cmd cmd) * If boost is active, the PMIC does not toggle data lines for HVDCP * commands */ - if (smblite_shim_is_boost_en(hvdcp->smblite_shim->chg)) { + if (smblite_lib_is_boost_en(hvdcp->smblite_shim->chg)) { dev_err(hvdcp->dev, "Boost mode en (post-cmd)\n"); return -EAGAIN; } @@ -196,14 +271,36 @@ static int read_voltage(struct hvdcp *hvdcp, int *voltage_out) return ret; } +static void clear_state_data_locked(struct state_data *data) +{ + data->elapsed_ms = 0; + data->last_elapsed_check_kt = ktime_get_boottime(); + + data->wait_ms = 0; + data->last_state_work_kt = 0; +} + +static void update_elapsed_time_locked(struct state_data *data) +{ + ktime_t now = ktime_get_boottime(); + data->elapsed_ms += ktime_ms_delta(now, data->last_elapsed_check_kt); + data->last_elapsed_check_kt = now; +} + static void update_state_locked(struct hvdcp *hvdcp, enum hvdcp_state new_state) { + struct state_data *state_data = &hvdcp->state_data; + + update_elapsed_time_locked(state_data); + hvdcp->state_data.last_state_work_kt = ktime_get_boottime(); + if (new_state != hvdcp->state) { dev_info(hvdcp->dev, "State change: %s -> %s\n", state_str[hvdcp->state], state_str[new_state]); hvdcp->state = new_state; + clear_state_data_locked(state_data); } } @@ -212,6 +309,8 @@ static void reset_state_locked(struct hvdcp *hvdcp) update_state_locked(hvdcp, HVDCP_OFF); alarm_try_to_cancel(&hvdcp->controller_alarm); suspend_usbin(hvdcp, false); + hvdcp->sesh_data.slow_waits = false; + clear_state_data_locked(&hvdcp->state_data); set_voltage_max(0); } @@ -219,15 +318,153 @@ static void controller_work(struct work_struct *work) { struct hvdcp *hvdcp = container_of(work, struct hvdcp, controller_work); int voltage; + int delta; int ret; + enum hvdcp_state new_state; + enum cmd cmd; + union power_supply_propval plugged; + struct session_data *sesh_data = &hvdcp->sesh_data; + struct state_data *state_data = &hvdcp->state_data; + unsigned int wait_ms = WAIT_DISABLED; + unsigned int waited_dur_ms; + ktime_t now; dev_dbg(hvdcp->dev, "entry\n"); - /* For testing only */ - ret = read_voltage(hvdcp, &voltage); - dev_dbg(hvdcp->dev, "Voltage: %u (ret %d). USB suspend: %u\n", - voltage, ret, is_usbin_suspended(hvdcp)); - run_cmd(hvdcp, CMD_FORCE_5V); + mutex_lock(&hvdcp->lock); + update_elapsed_time_locked(state_data); + + ret = smblite_lib_get_prop_usb_present(hvdcp->smblite_shim->chg, + &plugged); + if ((ret != 0) || !plugged.intval) { + reset_state_locked(hvdcp); + mutex_unlock(&hvdcp->lock); + return; + } + + new_state = hvdcp->state; + + now = ktime_get_boottime(); + waited_dur_ms = ktime_ms_delta(now, state_data->last_state_work_kt); + if (waited_dur_ms < state_data->wait_ms) { + int corrected_wait_ms = (state_data->wait_ms - waited_dur_ms); + dev_dbg(hvdcp->dev, "Need to wait %u ms before handling %s", + corrected_wait_ms, state_str[hvdcp->state]); + wait_ms = corrected_wait_ms; + goto out; + } + + switch (hvdcp->state) { + case HVDCP_OFF: + /* + * Suspend the charging path so that: + * - We can read an accurate charger voltage without drops due + * to cable resistance. + * - If the charger boosts to over the voltage ceiling, the + * charging puck won't shut down the eletrical path. + * If the puck's overvoltage threshold is reached, it will + * clamp the voltage and excess power will be dissipated + * in the form of heat. If a significant amount of power is + * dissipated, the generated heat will trip the thermal + * protection and the puck will temporarily shut down. + */ + ret = suspend_usbin(hvdcp, true); + if (ret == 0) + new_state = HVDCP_USBIN_SUSPEND_WAIT; + wait_ms = SUSPEND_WAIT_MS; + break; + case HVDCP_USBIN_SUSPEND_WAIT: + if (!is_usbin_suspended(hvdcp)) { + wait_ms = HVDCP_USBIN_SUSPEND_WAIT; + break; + } + new_state = HVDCP_ENTER_IDLE_WAIT; + wait_ms = 0; + break; + case HVDCP_ENTER_IDLE_WAIT: + ret = run_cmd(hvdcp, CMD_IDLE, &wait_ms); + if (ret != 0) { + wait_ms = hvdcp->cmd_retry_wait_ms; + break; + } + new_state = HVDCP_NEGOTIATION; + break; + case HVDCP_NEGOTIATION: + ret = read_voltage(hvdcp, &voltage); + if (ret != 0) { + wait_ms = VOLTAGE_CHECK_RETRY_MS; + break; + } + delta = (hvdcp->voltage_ceiling - voltage); + if ((delta >= 0) && (delta < HVDCP_PULSE_DELTA_UV)) { + new_state = HVDCP_SETTLE_MONITOR; + wait_ms = VOLTAGE_CHECK_RETRY_MS; + break; + } + cmd = (delta > 0) ? CMD_INCREMENT : CMD_DECREMENT; + ret = run_cmd(hvdcp, cmd, &wait_ms); + if (ret != 0) { + wait_ms = hvdcp->cmd_retry_wait_ms; + break; + } + break; + case HVDCP_SETTLE_MONITOR: + ret = read_voltage(hvdcp, &voltage); + if (ret != 0) { + wait_ms = VOLTAGE_CHECK_RETRY_MS; + break; + } + if (voltage > hvdcp->voltage_ceiling) { + dev_dbg(hvdcp->dev, "%u uV over %u uV ceiling!\n", + voltage, hvdcp->voltage_ceiling); + new_state = HVDCP_NEGOTIATION; + sesh_data->slow_waits = true; + wait_ms = hvdcp->renegotiation_wait_ms; + break; + } + + delta = (hvdcp->voltage_ceiling - voltage); + if (delta > (HVDCP_PULSE_DELTA_UV + 20000)) { + dev_dbg(hvdcp->dev, "Delta %d uV larger than pulse!\n", + delta); + new_state = HVDCP_NEGOTIATION; + sesh_data->slow_waits = true; + wait_ms = hvdcp->renegotiation_wait_ms; + break; + } + + if (state_data->elapsed_ms < hvdcp->settle_monitor_ms) { + wait_ms = VOLTAGE_CHECK_RETRY_MS; + break; + } + + set_voltage_max(voltage); + new_state = HVDCP_USBIN_RESUME_WAIT; + wait_ms = 0; + break; + case HVDCP_USBIN_RESUME_WAIT: + ret = suspend_usbin(hvdcp, false); + if (ret != 0) { + wait_ms = SUSPEND_WAIT_MS; + break; + } + new_state = HVDCP_DONE; + default: + break; + } + +out: + update_state_locked(hvdcp, new_state); + mutex_unlock(&hvdcp->lock); + + if (wait_ms != WAIT_DISABLED) { + dev_dbg(hvdcp->dev, "Next controller run in %d ms\n", + wait_ms); + state_data->wait_ms = wait_ms; + alarm_try_to_cancel(&hvdcp->controller_alarm); + alarm_start_relative(&hvdcp->controller_alarm, + ms_to_ktime(wait_ms)); + } } static enum alarmtimer_restart @@ -260,6 +497,17 @@ static int plugin_notify(struct notifier_block *nb, unsigned long plugged, struct hvdcp *hvdcp = container_of(nb, struct hvdcp, plugin_nb); dev_dbg(hvdcp->dev, "%s: %u\n", __func__, plugged); + + /* + * Only care about when device is unplugged. For plugin, we rely on + * the hvdcp req notifier to start hvdcp negotiation. + */ + if (((enum smblite_shim_plug_sts)plugged) == SMBLITE_SHIM_UNPLUGGED) { + mutex_lock(&hvdcp->lock); + reset_state_locked(hvdcp); + mutex_unlock(&hvdcp->lock); + } + return NOTIFY_OK; } @@ -368,6 +616,24 @@ static int hvdcp_probe(struct platform_device *pdev) alarm_init(&hvdcp->controller_alarm, ALARM_BOOTTIME, controller_alarm_expired); + hvdcp->cmd_waits[CMD_IDLE].typ = 100; + hvdcp->cmd_waits[CMD_IDLE].slow = 2500; + + hvdcp->cmd_waits[CMD_FORCE_5V].typ = 150; + hvdcp->cmd_waits[CMD_FORCE_5V].slow = 2500; + + hvdcp->cmd_waits[CMD_INCREMENT].typ = 150; + hvdcp->cmd_waits[CMD_INCREMENT].slow = 2500; + + hvdcp->cmd_waits[CMD_DECREMENT].typ = 150; + hvdcp->cmd_waits[CMD_DECREMENT].slow = 2500; + + hvdcp->renegotiation_wait_ms = 2500; + hvdcp->cmd_retry_wait_ms = 2500; + hvdcp->settle_monitor_ms = 6000; + + hvdcp->voltage_ceiling = 5500000; + reset_state_locked(hvdcp); hvdcp->plugin_nb.notifier_call = plugin_notify; |