summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrei Ciubotariu <aciubotariu@google.com>2023-11-12 14:22:41 -0800
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-12-21 09:50:56 +0000
commit4c613912d3c629f99f00090262b79fcf2d427f0d (patch)
tree186e699e5c0125116fd2cf0cc12b6ecb07622cae
parent3746b4e8cb43097e4f27bbb89735eb9fa41207f8 (diff)
downloadmsm-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.c284
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;