summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTai Kuo <taikuo@google.com>2023-12-12 15:17:32 +0800
committerTreehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com>2023-12-13 21:56:55 +0000
commit300b6add9dbb4f366a88ec73961471095303c137 (patch)
tree43e0a24d5763da1e46bb0605f28b1c321bef57c8
parentffeecf95e0f6cf80314c3bcf14d551b0db6a0f55 (diff)
downloadamplifiers-300b6add9dbb4f366a88ec73961471095303c137.tar.gz
Detection: 1. Check effects_in_flight count for vibe_state hang issue. (Trigger reset at cs40l26_resume since cs40l26_suspend might have issue during the controller bus off or system powering off sequence.) 2. Check I2C errors at the end of functions cs40l26_set_gain_worker, cs40l26_vibe_start_worker, cs40l26_vibe_stop_worker, cs40l26_upload_effect, cs40l26_erase_effect and cs40l26_resume. Reset sequence: Toggle hardware reset pin, reload the current firmware, reset the state flag and counter, and update vibe_state driver attribute. Reset cooldown period: Pause further reset recovery if reset is conducted for 10 times in 5 minutes. Resume reset recovery after 5 minutes. Bug: 299023920 Bug: 281146462 Test: Vibe_state can be recovered. Change-Id: I3aaf8191932cfd5ca0fd5f8eee88adb5ffd21527 Signed-off-by: Tai Kuo <taikuo@google.com> (cherry picked from commit 5c931c62a0861c25ae4f79e4d4b91abc036f2895)
-rw-r--r--Documentation/ABI/testing/sysfs-driver-input-cs40l2611
-rw-r--r--cs40l26/cs40l26-sysfs.c42
-rw-r--r--cs40l26/cs40l26.c267
-rw-r--r--cs40l26/cs40l26.h29
4 files changed, 348 insertions, 1 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-input-cs40l26 b/Documentation/ABI/testing/sysfs-driver-input-cs40l26
new file mode 100644
index 0000000..e641417
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-input-cs40l26
@@ -0,0 +1,11 @@
+What: /sys/class/input/input(x)/device/default/reset
+Date: December 2023
+Contact: Tai Kuo <taikuo@google.com>
+Description:
+ Hardware reset trigger.
+
+ Access: Read, Write
+
+ Valid values: Represented as integer
+ 0: Make a reset decision and trigger reset if needed.
+ 1: Manual reset
diff --git a/cs40l26/cs40l26-sysfs.c b/cs40l26/cs40l26-sysfs.c
index 34a2a35..bb6f3d8 100644
--- a/cs40l26/cs40l26-sysfs.c
+++ b/cs40l26/cs40l26-sysfs.c
@@ -817,6 +817,45 @@ err_mutex:
}
static DEVICE_ATTR_RW(vpbr_thld);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+static ssize_t reset_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+
+ dev_info(cs40l26->dev, "Reset: Event: %d; Count: %d; Time: (%lld,%lld).\n",
+ cs40l26->reset_event, cs40l26->reset_count, cs40l26->reset_time_s,
+ cs40l26->reset_time_e);
+ return sysfs_emit(buf, "%d\n", cs40l26->reset_event);
+}
+
+static ssize_t reset_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ int ret;
+ int choice;
+
+ ret = kstrtou32(buf, 10, &choice);
+ if (ret)
+ return ret;
+
+ if (choice == 0) {
+ cs40l26_make_reset_decision(cs40l26, __func__);
+ } else if (choice == 1) {
+ cs40l26->reset_event = CS40L26_RESET_EVENT_NONEED;
+ cs40l26->reset_count = 0;
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->reset_work);
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_RW(reset);
+#endif
+
static struct attribute *cs40l26_dev_attrs[] = {
&dev_attr_num_waves.attr,
&dev_attr_die_temp.attr,
@@ -834,6 +873,9 @@ static struct attribute *cs40l26_dev_attrs[] = {
&dev_attr_redc_comp_enable.attr,
&dev_attr_swap_firmware.attr,
&dev_attr_vpbr_thld.attr,
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ &dev_attr_reset.attr,
+#endif
NULL,
};
diff --git a/cs40l26/cs40l26.c b/cs40l26/cs40l26.c
index 02ffdc3..4dfdc85 100644
--- a/cs40l26/cs40l26.c
+++ b/cs40l26/cs40l26.c
@@ -466,6 +466,10 @@ int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
ATRACE_BEGIN("CS40L26_PM_STATE_WAKEUP");
ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
cmd, CS40L26_DSP_MBOX_RESET);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (ret)
+ dev_err(dev, "CS40L26_PM_STATE_WAKEUP failed");
+#endif
if (ret)
return ret;
@@ -479,11 +483,19 @@ int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
CS40L26_DSP_VIRTUAL1_MBOX_1,
cmd, CS40L26_DSP_MBOX_RESET);
if (ret)
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ break;
+#else
return ret;
+#endif
ret = cs40l26_dsp_state_get(cs40l26, &curr_state);
if (ret)
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ break;
+#else
return ret;
+#endif
if (curr_state == CS40L26_DSP_STATE_ACTIVE)
break;
@@ -491,7 +503,11 @@ int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
if (curr_state == CS40L26_DSP_STATE_STANDBY) {
ret = cs40l26_check_pm_lock(cs40l26, &dsp_lock);
if (ret)
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ break;
+#else
return ret;
+#endif
if (dsp_lock)
break;
@@ -499,6 +515,13 @@ int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
usleep_range(5000, 5100);
}
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (ret) {
+ dev_err(dev, "CS40L26_PM_STATE_PREVENT_HIBERNATE failed");
+ return ret;
+ }
+#endif
+
if (i == CS40L26_DSP_STATE_ATTEMPTS) {
dev_err(cs40l26->dev, "DSP not starting\n");
return -ETIMEDOUT;
@@ -511,6 +534,10 @@ int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
cs40l26->wksrc_sts = 0x00;
ret = cs40l26_dsp_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
cmd);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (ret)
+ dev_err(dev, "CS40L26_PM_STATE_ALLOW_HIBERNATE failed");
+#endif
if (ret)
return ret;
@@ -519,6 +546,10 @@ int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26,
cs40l26->wksrc_sts = 0x00;
ret = cs40l26_ack_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1,
cmd, CS40L26_DSP_MBOX_RESET);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (ret)
+ dev_err(dev, "CS40L26_PM_STATE_SHUTDOWN failed");
+#endif
break;
default:
@@ -887,6 +918,14 @@ void cs40l26_vibe_state_update(struct cs40l26_private *cs40l26,
case CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK:
case CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER:
cs40l26_remove_asp_scaling(cs40l26);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (cs40l26->effects_in_flight > 0) {
+ cs40l26->reset_event = CS40L26_RESET_EVENT_TRIGGER;
+ dev_err(cs40l26->dev,
+ "Invalid effects_in_flight (%d)! Reset at the next chip resume.",
+ cs40l26->effects_in_flight);
+ }
+#endif
cs40l26->effects_in_flight = cs40l26->effects_in_flight <= 0 ? 1 :
cs40l26->effects_in_flight + 1;
break;
@@ -1918,6 +1957,10 @@ static void cs40l26_set_gain_worker(struct work_struct *work)
err_mutex:
mutex_unlock(&cs40l26->lock);
cs40l26_pm_exit(cs40l26->dev);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (ret < 0)
+ cs40l26_make_reset_decision(cs40l26, __func__);
+#endif
}
static void cs40l26_vibe_start_worker(struct work_struct *work)
@@ -2011,6 +2054,10 @@ err_mutex:
mutex_unlock(&cs40l26->lock);
cs40l26_pm_exit(dev);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (ret < 0)
+ cs40l26_make_reset_decision(cs40l26, __func__);
+#endif
}
static void cs40l26_vibe_stop_worker(struct work_struct *work)
@@ -2061,6 +2108,10 @@ static void cs40l26_vibe_stop_worker(struct work_struct *work)
mutex_exit:
mutex_unlock(&cs40l26->lock);
cs40l26_pm_exit(cs40l26->dev);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (ret < 0)
+ cs40l26_make_reset_decision(cs40l26, __func__);
+#endif
}
static void cs40l26_set_gain(struct input_dev *dev, u16 gain)
@@ -2961,8 +3012,11 @@ out_free:
memset(&cs40l26->upload_effect, 0, sizeof(struct ff_effect));
kfree(cs40l26->raw_custom_data);
cs40l26->raw_custom_data = NULL;
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (ret < 0)
+ cs40l26_make_reset_decision(cs40l26, __func__);
ATRACE_END();
-
+#endif
return ret;
}
@@ -3120,7 +3174,11 @@ static int cs40l26_erase_effect(struct input_dev *dev, int effect_id)
/* Wait for erase to finish */
flush_work(&cs40l26->erase_work);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ if (cs40l26->erase_ret < 0)
+ cs40l26_make_reset_decision(cs40l26, __func__);
ATRACE_END();
+#endif
return cs40l26->erase_ret;
}
@@ -4836,6 +4894,195 @@ static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
return cs40l26_no_wait_ram_indices_get(cs40l26, np);
}
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+static void cs40l26_reset_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 = container_of(work,
+ struct cs40l26_private, reset_work);
+ struct device *dev = cs40l26->dev;
+ int error;
+ u32 id;
+
+ if (IS_ERR_OR_NULL(cs40l26->reset_gpio)) {
+ dev_dbg(dev, "Invalid reset GPIO\n");
+ return;
+ }
+
+ dev_dbg(dev, "Reset start: Event: %d; Count: %d.",
+ cs40l26->reset_event, cs40l26->reset_count);
+
+ /* cs40l26_remove(cs40l26) */
+ if (cs40l26->fw_loaded)
+ disable_irq(cs40l26->irq);
+
+ if (cs40l26->vibe_workqueue) {
+ cancel_work_sync(&cs40l26->vibe_start_work);
+ cancel_work_sync(&cs40l26->vibe_stop_work);
+ cancel_work_sync(&cs40l26->set_gain_work);
+ cancel_work_sync(&cs40l26->upload_work);
+ cancel_work_sync(&cs40l26->erase_work);
+ }
+
+ /* Skip power off since REFCLK is shared and cannot be disabled. */
+
+ gpiod_set_value_cansleep(cs40l26->reset_gpio, 0);
+
+ /* cs40l26_probe(cs40l26, pdata) */
+ if (cs40l26->dev->of_node) {
+ error = cs40l26_handle_platform_data(cs40l26);
+ if (error)
+ goto err;
+ } else
+ dev_err(dev, "No DTSI to reset platform data\n");
+
+ /* Skip power on since REFCLK is shared and cannot be disabled. */
+
+ usleep_range(CS40L26_MIN_RESET_PULSE_WIDTH,
+ CS40L26_MIN_RESET_PULSE_WIDTH + 100);
+
+ gpiod_set_value_cansleep(cs40l26->reset_gpio, 1);
+
+ usleep_range(CS40L26_CONTROL_PORT_READY_DELAY,
+ CS40L26_CONTROL_PORT_READY_DELAY + 100);
+
+ /*
+ * The DSP may lock up if a haptic effect is triggered via
+ * GPI event or control port and the PLL is set to closed-loop.
+ *
+ * Set PLL to open-loop and remove any default GPI mappings
+ * to prevent this while the driver is loading and configuring RAM
+ * firmware.
+ */
+
+ error = cs40l26_set_pll_loop(cs40l26, CS40L26_PLL_REFCLK_SET_OPEN_LOOP);
+ if (error)
+ goto err;
+
+ error = cs40l26_erase_gpi_mapping(cs40l26, CS40L26_GPIO_MAP_A_PRESS);
+ if (error)
+ goto err;
+
+ error = cs40l26_erase_gpi_mapping(cs40l26, CS40L26_GPIO_MAP_A_RELEASE);
+ if (error)
+ goto err;
+
+ error = cs40l26_part_num_resolve(cs40l26);
+ if (error)
+ goto err;
+
+ /* Set LRA to high-z to avoid fault conditions */
+ error = regmap_update_bits(cs40l26->regmap, CS40L26_TST_DAC_MSM_CONFIG,
+ CS40L26_SPK_DEFAULT_HIZ_MASK, 1 <<
+ CS40L26_SPK_DEFAULT_HIZ_SHIFT);
+ if (error) {
+ dev_err(dev, "Failed to set LRA to HI-Z\n");
+ goto err;
+ }
+
+ /* Load firmware at cs40l26_fw_swap() */
+ cs40l26->fw_defer = false;
+ if (cs40l26->calib_fw)
+ id = CS40L26_FW_CALIB_ID;
+ else
+ id = CS40L26_FW_ID;
+
+ if (cs40l26->fw_loaded)
+ enable_irq(cs40l26->irq);
+
+ error = cs40l26_fw_swap(cs40l26, id);
+ if (error)
+ goto err;
+
+ /* Reset vibe_state and counter/flag */
+ cs40l26->effects_in_flight = 0;
+ cs40l26->asp_enable = false;
+ cs40l26->vibe_state = CS40L26_VIBE_STATE_STOPPED;
+ sysfs_notify(&cs40l26->dev->kobj, "default", "vibe_state");
+
+ cs40l26->reset_event = CS40L26_RESET_EVENT_NONEED;
+ cs40l26->reset_count++;
+
+ dev_info(dev, "Reset end: Event: %d; Count: %d.",
+ cs40l26->reset_event, cs40l26->reset_count);
+ return;
+
+err:
+ cs40l26->reset_event = CS40L26_RESET_EVENT_FAILED;
+ cs40l26->reset_time_s = ktime_get_real_seconds();
+ dev_err(dev, "Reset end: Fatal error at count: %d.", cs40l26->reset_count);
+}
+
+static bool cs40l26_handle_reset_boundary_condition(struct cs40l26_private *cs40l26)
+{
+ time64_t delta_sec = 0;
+
+ cs40l26->reset_time_e = ktime_get_real_seconds();
+ delta_sec = cs40l26->reset_time_e - cs40l26->reset_time_s;
+
+ if (delta_sec > CS40L26_RESET_COOLDOWN_TIMEOUT_SEC || delta_sec < 0 ||
+ cs40l26->reset_count == 0) {
+ dev_info(cs40l26->dev, "Reset event: %d. Back to default.", cs40l26->reset_event);
+ cs40l26->reset_event = CS40L26_RESET_EVENT_ONGOING;
+ cs40l26->reset_time_s = cs40l26->reset_time_e;
+ cs40l26->reset_count = 0;
+ return true;
+ }
+
+ return false;
+}
+
+void cs40l26_make_reset_decision(struct cs40l26_private *cs40l26, const char *func)
+{
+ struct device *dev = cs40l26->dev;
+ bool trigger = false;
+
+ switch (cs40l26->reset_event) {
+ case CS40L26_RESET_EVENT_NONEED:
+ if (cs40l26_handle_reset_boundary_condition(cs40l26)) {
+ trigger = true;
+ break;
+ }
+
+ /*
+ * Implies the following conditions are true:
+ * 0 < cs40l26->reset_count && elapsed time <= CS40L26_RESET_COOLDOWN_TIMEOUT_SEC
+ */
+ if (cs40l26->reset_count < CS40L26_RESET_MAX_COUNT) {
+ cs40l26->reset_event = CS40L26_RESET_EVENT_ONGOING;
+ trigger = true;
+ } else {
+ /* Enters the cooldown mode if reset too many times in a period. */
+ cs40l26->reset_event = CS40L26_RESET_EVENT_COOLDOWN;
+ cs40l26->reset_time_s = cs40l26->reset_time_e;
+ }
+ break;
+ case CS40L26_RESET_EVENT_TRIGGER:
+ cs40l26->reset_count = 0;
+ cs40l26_handle_reset_boundary_condition(cs40l26);
+ trigger = true;
+ break;
+ case CS40L26_RESET_EVENT_ONGOING:
+ break;
+ case CS40L26_RESET_EVENT_FAILED:
+ fallthrough;
+ case CS40L26_RESET_EVENT_COOLDOWN:
+ if (cs40l26_handle_reset_boundary_condition(cs40l26))
+ trigger = true;
+
+ break;
+ default:
+ dev_err(dev, "Invalid reset event!");
+ }
+
+ if (trigger) {
+ dev_info(dev, "Queue reset work after %s", func);
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->reset_work);
+ } else
+ dev_info(dev, "Reset event: %d. Skip this trigger from %s.", cs40l26->reset_event,
+ func);
+}
+#endif
+
int cs40l26_probe(struct cs40l26_private *cs40l26,
struct cs40l26_platform_data *pdata)
{
@@ -4856,6 +5103,12 @@ int cs40l26_probe(struct cs40l26_private *cs40l26,
INIT_WORK(&cs40l26->set_gain_work, cs40l26_set_gain_worker);
INIT_WORK(&cs40l26->upload_work, cs40l26_upload_worker);
INIT_WORK(&cs40l26->erase_work, cs40l26_erase_worker);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ INIT_WORK(&cs40l26->reset_work, cs40l26_reset_worker);
+ cs40l26->reset_event = CS40L26_RESET_EVENT_NONEED;
+ cs40l26->reset_time_e = ktime_get_real_seconds();
+ cs40l26->reset_time_s = cs40l26->reset_time_e;
+#endif
ret = devm_regulator_bulk_get(dev, CS40L26_NUM_SUPPLIES,
cs40l26_supplies);
@@ -5108,6 +5361,9 @@ EXPORT_SYMBOL(cs40l26_resume_error_handle);
int cs40l26_resume(struct device *dev)
{
struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ int error;
+#endif
if (!cs40l26->pm_ready) {
dev_dbg(dev, "Resume call ignored\n");
@@ -5116,8 +5372,17 @@ int cs40l26_resume(struct device *dev)
dev_dbg(cs40l26->dev, "%s: Disabling hibernation\n", __func__);
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ error = cs40l26_pm_state_transition(cs40l26,
+ CS40L26_PM_STATE_PREVENT_HIBERNATE);
+ if (error < 0 || cs40l26->reset_event == CS40L26_RESET_EVENT_TRIGGER)
+ cs40l26_make_reset_decision(cs40l26, __func__);
+
+ return error;
+#else
return cs40l26_pm_state_transition(cs40l26,
CS40L26_PM_STATE_PREVENT_HIBERNATE);
+#endif
}
EXPORT_SYMBOL(cs40l26_resume);
diff --git a/cs40l26/cs40l26.h b/cs40l26/cs40l26.h
index 05f0458..13f24db 100644
--- a/cs40l26/cs40l26.h
+++ b/cs40l26/cs40l26.h
@@ -42,6 +42,9 @@
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+#include <linux/timekeeping.h>
+#endif
#include "cl_dsp.h"
#include "../../../gs-google/drivers/soc/google/vh/kernel/systrace.h"
@@ -668,6 +671,12 @@
#define CS40L26_TEST_KEY_UNLOCK_CODE1 0x00000055
#define CS40L26_TEST_KEY_UNLOCK_CODE2 0x000000AA
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+/* Reset Recovery */
+#define CS40L26_RESET_MAX_COUNT 10
+#define CS40L26_RESET_COOLDOWN_TIMEOUT_SEC 300
+#endif
+
/* DSP State */
#define CS40L26_DSP_STATE_HIBERNATE 0
#define CS40L26_DSP_STATE_SHUTDOWN 1
@@ -1432,6 +1441,16 @@ enum cs40l26_pm_state {
CS40L26_PM_STATE_SHUTDOWN,
};
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+enum cs40l26_reset_event {
+ CS40L26_RESET_EVENT_NONEED,
+ CS40L26_RESET_EVENT_TRIGGER,
+ CS40L26_RESET_EVENT_ONGOING,
+ CS40L26_RESET_EVENT_COOLDOWN,
+ CS40L26_RESET_EVENT_FAILED,
+};
+#endif
+
/* structs */
struct cs40l26_owt_section {
@@ -1568,6 +1587,13 @@ struct cs40l26_private {
bool dbg_fw_ym;
struct cl_dsp_debugfs *cl_dsp_db;
#endif
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+ struct work_struct reset_work;
+ enum cs40l26_reset_event reset_event;
+ u8 reset_count;
+ time64_t reset_time_s;
+ time64_t reset_time_e;
+#endif
};
struct cs40l26_codec {
@@ -1659,5 +1685,8 @@ void cs40l26_debugfs_init(struct cs40l26_private *cs40l26);
void cs40l26_debugfs_cleanup(struct cs40l26_private *cs40l26);
#endif
+#if IS_ENABLED(CONFIG_GOOG_CUST)
+void cs40l26_make_reset_decision(struct cs40l26_private *cs40l26, const char *func);
+#endif
#endif /* __CS40L26_H__ */