Merge android13-gs-pixel-5.10-24Q1 into android13-gs-pixel-5.10-24Q2

SBMerger: 571992243
Change-Id: I477ad9ffd6730003387e679c404e4332e91a059d
Signed-off-by: SecurityBot <android-nexus-securitybot@system.gserviceaccount.com>
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 <poomarin@google.com>
+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 <poomarin@google.com>
+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 <poomarin@google.com>
+Description:
+		Reading from this file provides total count of voice-call and VoIP-call
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/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 @@
 	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 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);
+	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;
 	}
-
-	if (rate_active > scale) {
-		rate_active = scale;
-		rate_enable = scale;
-	}
-
-	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;
-
+	buf[--length] = 0;
 	mutex_unlock(&priv->lock);
-	return counts;
+	return length;
+}
+
+/*
+ * 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);
+	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 @@
 	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 @@
 		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 @@
 		break;
 
 		case AMCS_OP_CCA_INCREASE:
-			mutex_lock(&priv->lock);
-			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];
-			}
-			mutex_unlock(&priv->lock);
 			ret = 0;
-		break;
+			if (params.val[0] == AMCS_OP2_SET) {
+				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);
+			}
+			break;
+
+		case AMCS_OP_CALL_COUNT_INCREASE:
+			ret = 0;
+			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(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 @@
 	&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 @@
 	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,
 };
 
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 @@
 }
 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 @@
 	&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 @@
 		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 @@
 					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 @@
 			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 @@
 			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 @@
 		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 @@
 		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 @@
 	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 @@
 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 @@
 	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 @@
 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 @@
 	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 @@
 	/* 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 @@
 	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 @@
 	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 @@
 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 @@
 
 	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 @@
 	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 @@
 	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_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__ */