diff options
author | Tai Kuo <taikuo@google.com> | 2023-12-12 15:17:32 +0800 |
---|---|---|
committer | Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> | 2023-12-13 21:56:55 +0000 |
commit | 300b6add9dbb4f366a88ec73961471095303c137 (patch) | |
tree | 43e0a24d5763da1e46bb0605f28b1c321bef57c8 | |
parent | ffeecf95e0f6cf80314c3bcf14d551b0db6a0f55 (diff) | |
download | amplifiers-300b6add9dbb4f366a88ec73961471095303c137.tar.gz |
cs40l26: haptics IC reset recoveryandroid-u-qpr2-beta-3_r0.7android-u-qpr2-beta-3_r0.6android-u-qpr2-beta-3_r0.5android-u-qpr2-beta-3_r0.4android-u-qpr2-beta-3_r0.3android-u-qpr2-beta-3_r0.2android-u-qpr2-beta-3.1_r0.7android-u-qpr2-beta-3.1_r0.5android-u-qpr2-beta-3.1_r0.4android-u-qpr2-beta-3.1_r0.3android-u-qpr2-beta-3.1_r0.2android-u-qpr2-beta-3.1_r0.1android-14.0.0_r0.76android-14.0.0_r0.75android-14.0.0_r0.74android-14.0.0_r0.73android-14.0.0_r0.72android-14.0.0_r0.71android-14.0.0_r0.66android-14.0.0_r0.65android-14.0.0_r0.64android-14.0.0_r0.63android-14.0.0_r0.62android-14.0.0_r0.61android-14.0.0_r0.56android-14.0.0_r0.55android-14.0.0_r0.54android-14.0.0_r0.53android-14.0.0_r0.52android-14.0.0_r0.51android-gs-tangorpro-5.10-android14-qpr2-betaandroid-gs-tangorpro-5.10-android14-qpr2android-gs-raviole-5.10-android14-qpr2-betaandroid-gs-raviole-5.10-android14-qpr2android-gs-pantah-5.10-android14-qpr2-betaandroid-gs-pantah-5.10-android14-qpr2android-gs-lynx-5.10-android14-qpr2-betaandroid-gs-lynx-5.10-android14-qpr2android-gs-felix-5.10-android14-qpr2-betaandroid-gs-felix-5.10-android14-qpr2android-gs-bluejay-5.10-android14-qpr2-betaandroid-gs-bluejay-5.10-android14-qpr2
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-cs40l26 | 11 | ||||
-rw-r--r-- | cs40l26/cs40l26-sysfs.c | 42 | ||||
-rw-r--r-- | cs40l26/cs40l26.c | 267 | ||||
-rw-r--r-- | cs40l26/cs40l26.h | 29 |
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__ */ |