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 <taikuo@google.com>
(cherry picked from commit 5c931c62a0861c25ae4f79e4d4b91abc036f2895)
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 @@
 }
 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__ */