From ffeecf95e0f6cf80314c3bcf14d551b0db6a0f55 Mon Sep 17 00:00:00 2001 From: Poomarin Phloyphisut Date: Tue, 22 Aug 2023 14:24:39 +0000 Subject: amcs: update CCA sysfs Rename cca_rate_read_once to cca_count_read_once Ex: cat /sys/devices/platform/audiometrics/cca_count_read_once_show Ex: result 10 15 20 25 means active CCA are 10, 15, 20, and 25 Bug: 289857250 Test: local testing Change-Id: I20ab587395adb89bb66aeb898154b107f5c9a518 Signed-off-by: poomarin --- .../testing/sysfs-devices-platform-audiometrics | 20 ++++ audiometrics/audiometrics.c | 132 ++++++++++++--------- audiometrics/uapi/audiometrics_api.h | 11 ++ 3 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-devices-platform-audiometrics diff --git a/Documentation/ABI/testing/sysfs-devices-platform-audiometrics b/Documentation/ABI/testing/sysfs-devices-platform-audiometrics new file mode 100644 index 0000000..ce3cb75 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-platform-audiometrics @@ -0,0 +1,20 @@ +What: /sys/devices/platform/audiometrics/cca_show +Date: Aug, 2023 +KernelVersion: 5.15 +Contact: poomarin +Description: + Reading from this file provides CCA count but not resets count value. + +What: /sys/devices/platform/audiometrics/cca_count_read_once_show +Date: Aug, 2023 +KernelVersion: 5.15 +Contact: poomarin +Description: + Reading from this file provides CCA count and resets count value. + +What: /sys/devices/platform/audiometrics/call_count +Date: Jul, 2023 +KernelVersion: 5.15 +Contact: poomarin +Description: + Reading from this file provides total count of voice-call and VoIP-call diff --git a/audiometrics/audiometrics.c b/audiometrics/audiometrics.c index 9b702ad..b310496 100644 --- a/audiometrics/audiometrics.c +++ b/audiometrics/audiometrics.c @@ -27,6 +27,8 @@ #define AUDIOMETRIC_CH_LENGTH 16 #define AMCS_MAX_MINOR (1U) #define AMCS_CDEV_NAME "amcs" +#define CCA_SOURCE_MAX 2 +#define CCA_SOURCE_VOICE 1 static struct platform_device *amcs_pdev; @@ -70,9 +72,11 @@ struct audio_sz_type { uint32_t mic_broken_degrade; uint32_t ams_count; uint32_t cs_count; - uint32_t cca_active; - uint32_t cca_enable; - uint32_t cca_cs; + uint32_t cca_active[CCA_SOURCE_MAX]; + uint32_t cca_enable[CCA_SOURCE_MAX]; + uint32_t cca_cs[CCA_SOURCE_MAX]; + int32_t voice_call_count; + int32_t voip_call_count; }; struct audiometrics_priv_type { @@ -409,62 +413,55 @@ static ssize_t ams_rate_read_once_show(struct device *dev, static ssize_t cca_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct audiometrics_priv_type *priv; - int counts; - - if (IS_ERR_OR_NULL(dev)) - return -ENODEV; - - priv = dev_get_drvdata(dev); - - if (IS_ERR_OR_NULL(priv)) - return -ENODEV; + struct audiometrics_priv_type *priv = dev_get_drvdata(dev); + int length; mutex_lock(&priv->lock); - counts = scnprintf(buf, PAGE_SIZE, "%u,%u,%u", priv->sz.cca_active, - priv->sz.cca_enable, priv->sz.cca_cs); + length = sysfs_emit(buf, "%u %u %u", priv->sz.cca_active[CCA_SOURCE_VOICE], + priv->sz.cca_enable[CCA_SOURCE_VOICE], priv->sz.cca_cs[CCA_SOURCE_VOICE]); mutex_unlock(&priv->lock); - return counts; + return length; } -static ssize_t cca_rate_read_once_show(struct device *dev, +static ssize_t cca_count_read_once_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct audiometrics_priv_type *priv; - int counts; - uint rate_active = 0, rate_enable = 0; - const int scale = 100; - - - if (IS_ERR_OR_NULL(dev)) - return -ENODEV; - - priv = dev_get_drvdata(dev); - - if (IS_ERR_OR_NULL(priv)) - return -ENODEV; + struct audiometrics_priv_type *priv = dev_get_drvdata(dev); + int i, length; mutex_lock(&priv->lock); - - if (priv->sz.cca_cs) { - rate_active = (priv->sz.cca_active * scale / priv->sz.cca_cs); - rate_enable = (priv->sz.cca_enable * scale / priv->sz.cca_cs); - } - - if (rate_active > scale) { - rate_active = scale; - rate_enable = scale; + length = 0; + for (i = 0; i < CCA_SOURCE_MAX; i++) { + length += sysfs_emit_at(buf, length, "%u %u ", priv->sz.cca_active[i], + priv->sz.cca_enable[i]); + priv->sz.cca_active[i] = 0; + priv->sz.cca_enable[i] = 0; } + buf[--length] = 0; + mutex_unlock(&priv->lock); + return length; +} - counts = scnprintf(buf, PAGE_SIZE, "%u,%u", rate_active, rate_enable); - - priv->sz.cca_active = 0; - priv->sz.cca_enable = 0; - priv->sz.cca_cs = 0; +/* + * Report call counts including voice-call and VoIP-call. + * Ex: result 10 20 + * + * means there are 10 voice-call and 20 VoIP-call. + */ +static ssize_t call_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct audiometrics_priv_type *priv = dev_get_drvdata(dev); + int length = 0; + mutex_lock(&priv->lock); + length = sysfs_emit(buf, "%d %d", priv->sz.voice_call_count, + priv->sz.voip_call_count); mutex_unlock(&priv->lock); - return counts; + priv->sz.voice_call_count = 0; + priv->sz.voip_call_count = 0; + return length; } static int amcs_cdev_open(struct inode *inode, struct file *file) @@ -487,6 +484,7 @@ static long amcs_cdev_unlocked_ioctl(struct file *file, unsigned int cmd, unsign long ret = -EINVAL; int i = 0; struct amcs_params params; + uint32_t cca_source; dev_dbg(priv->device, "%s cmd = 0x%x", __func__, cmd); @@ -620,13 +618,13 @@ static long amcs_cdev_unlocked_ioctl(struct file *file, unsigned int cmd, unsign case AMCS_OP_CCA: mutex_lock(&priv->lock); if (params.val[0] == AMCS_OP2_GET) { - params.val[1] = priv->sz.cca_active; - params.val[2] = priv->sz.cca_enable; - params.val[3] = priv->sz.cca_cs; + params.val[1] = priv->sz.cca_active[CCA_SOURCE_VOICE]; + params.val[2] = priv->sz.cca_enable[CCA_SOURCE_VOICE]; + params.val[3] = priv->sz.cca_cs[CCA_SOURCE_VOICE]; } else if (params.val[0] == AMCS_OP2_SET) { - priv->sz.cca_active = params.val[1]; - priv->sz.cca_enable = params.val[2]; - priv->sz.cca_cs = params.val[3]; + priv->sz.cca_active[CCA_SOURCE_VOICE] = params.val[1]; + priv->sz.cca_enable[CCA_SOURCE_VOICE] = params.val[2]; + priv->sz.cca_cs[CCA_SOURCE_VOICE] = params.val[3]; } mutex_unlock(&priv->lock); @@ -637,15 +635,29 @@ static long amcs_cdev_unlocked_ioctl(struct file *file, unsigned int cmd, unsign break; case AMCS_OP_CCA_INCREASE: - mutex_lock(&priv->lock); + ret = 0; if (params.val[0] == AMCS_OP2_SET) { - priv->sz.cca_active += params.val[1]; - priv->sz.cca_enable += params.val[2]; - priv->sz.cca_cs += params.val[3]; + cca_source = params.val[4]; + if (cca_source >= CCA_SOURCE_MAX) { + ret = -EINVAL; + break; + } + + mutex_lock(&priv->lock); + priv->sz.cca_active[cca_source] += params.val[1]; + priv->sz.cca_enable[cca_source] += params.val[2]; + priv->sz.cca_cs[cca_source] += params.val[3]; + mutex_unlock(&priv->lock); } - mutex_unlock(&priv->lock); + break; + + case AMCS_OP_CALL_COUNT_INCREASE: ret = 0; - break; + if (params.val[0]) + priv->sz.voice_call_count++; + else + priv->sz.voip_call_count++; + break; default: dev_warn(priv->device, "%s, unsupported op = %d\n", __func__, params.op); @@ -709,7 +721,8 @@ static DEVICE_ATTR_RO(codec_crashed_counter); static DEVICE_ATTR_RO(ams_cs); static DEVICE_ATTR_RO(ams_rate_read_once); static DEVICE_ATTR_RO(cca); -static DEVICE_ATTR_RO(cca_rate_read_once); +static DEVICE_ATTR_RO(cca_count_read_once); +static DEVICE_ATTR_RO(call_count); static struct attribute *audiometrics_fs_attrs[] = { @@ -726,7 +739,8 @@ static struct attribute *audiometrics_fs_attrs[] = { &dev_attr_ams_cs.attr, &dev_attr_ams_rate_read_once.attr, &dev_attr_cca.attr, - &dev_attr_cca_rate_read_once.attr, + &dev_attr_cca_count_read_once.attr, + &dev_attr_call_count.attr, NULL, }; diff --git a/audiometrics/uapi/audiometrics_api.h b/audiometrics/uapi/audiometrics_api.h index 7877fd7..1a313f1 100644 --- a/audiometrics/uapi/audiometrics_api.h +++ b/audiometrics/uapi/audiometrics_api.h @@ -45,6 +45,17 @@ enum amcs_params_op { AMCS_OP_AMS_INCREASE, AMCS_OP_CCA, AMCS_OP_CCA_INCREASE, + AMCS_OP_VOICE_INFO_NOISE_LEVEL, + AMCS_OP_ADD_PCM_LATENCY, + AMCS_OP_PCM_ACTIVE_COUNT_INCREASE, + AMCS_OP_OFFLOAD_EFFECT_DURATION, + AMCS_OP_SOFTWARE_RESTART_INCREASE, + AMCS_OP_DSP_RECORD_USAGE_DURATION_INCREASE, + AMCS_OP_DSP_RECORD_USAGE_COUNT_INCREASE, + AMCS_OP_BT_ACTIVE_DURATION_INCREASE, + AMCS_OP_WAVES_VOLUME_INCREASE, + AMCS_OP_ADAPTED_INFO_FEATURE, + AMCS_OP_CALL_COUNT_INCREASE, AMCS_OP_PARAMS_MAX, }; -- cgit v1.2.3 From 300b6add9dbb4f366a88ec73961471095303c137 Mon Sep 17 00:00:00 2001 From: Tai Kuo Date: Tue, 12 Dec 2023 15:17:32 +0800 Subject: cs40l26: haptics IC reset recovery 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 (cherry picked from commit 5c931c62a0861c25ae4f79e4d4b91abc036f2895) --- .../ABI/testing/sysfs-driver-input-cs40l26 | 11 + cs40l26/cs40l26-sysfs.c | 42 ++++ cs40l26/cs40l26.c | 267 ++++++++++++++++++++- cs40l26/cs40l26.h | 29 +++ 4 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-driver-input-cs40l26 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 +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 #include #include +#if IS_ENABLED(CONFIG_GOOG_CUST) +#include +#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 { @@ -1658,6 +1684,9 @@ extern struct attribute_group cs40l26_dev_attr_dbc_group; 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__ */ -- cgit v1.2.3