diff options
author | Kyle Tso <kyletso@google.com> | 2023-02-06 23:01:48 +0800 |
---|---|---|
committer | Kyle Tso <kyletso@google.com> | 2023-05-23 20:23:59 +0800 |
commit | 88ff5e56b4667cf17eab289d15601dfc618101f2 (patch) | |
tree | 6b45af2cc6808a0ceaa55c7e088b9fcb5c1d800b | |
parent | 80e2b699bced34fda938ba94712ca41543d198a8 (diff) | |
download | tangorpro-88ff5e56b4667cf17eab289d15601dfc618101f2.tar.gz |
pogo_transport: Refactor to state machine based implementation
Bug: 250518260
Bug: 250518309
Change-Id: Ia057a13929f4ef6ad291af3545292811083dc3ae
Signed-off-by: Kyle Tso <kyletso@google.com>
(cherry picked from commit 1199631a48d79d5e56cf4041181d2ae6c9472330)
-rw-r--r-- | pogo/pogo_transport.c | 1270 |
1 files changed, 1244 insertions, 26 deletions
diff --git a/pogo/pogo_transport.c b/pogo/pogo_transport.c index 7e2a1d9..03b7cc0 100644 --- a/pogo/pogo_transport.c +++ b/pogo/pogo_transport.c @@ -20,6 +20,7 @@ #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/regulator/consumer.h> +#include <linux/spinlock.h> #include <linux/usb.h> #include <linux/usb/tcpm.h> #include <misc/gvotable.h> @@ -39,10 +40,92 @@ #define KEEP_USB_PATH 2 #define KEEP_HUB_PATH 2 +#define DEFAULT_STATE_MACHINE_ENABLE false #define POGO_VOTER "POGO" #define SSPHY_RESTART_EL "SSPHY_RESTART" +/* + * State Description: + * INVALID_STATE, + * (A) STANDBY, // Nothing attached, hub disabled + * DOCKING_DEBOUNCED, // STANDBY -> DOCK_HUB, pogo gpio + * STANDBY_ACC_DEBOUNCED, // STANDBY -> ACC_DIRECT, acc gpio + * (B) DOCK_HUB, // Dock online, hub enabled + * (C) DOCK_DEVICE_HUB, // Dock online, usb device online, hub enabled + * (H) DOCK_AUDIO_HUB, // Dock online, usb audio online, hub enabled + * (I) AUDIO_HUB, // Usb audio online, hub enabled + * AUDIO_HUB_DOCKING_DEBOUNCED, // AUDIO_HUB -> DOCK_AUDIO_HUB, pogo gpio + * AUDIO_HUB_ACC_DEBOUNCED, // AUDIO_HUB -> ACC_AUDIO_HUB, acc gpio + * (D) DEVICE_HUB, // Usb device online, hub enabled + * DEVICE_HUB_DOCKING_DEBOUNCED, // DEVICE_HUB -> DOCK_DEVICE_HUB, pogo gpio + * DEVICE_HUB_ACC_DEBOUNCED, // DEVICE_HUB -> ACC_DEVICE_HUB, acc gpio + * (E) DEVICE_DIRECT, // Usb device online, hub disabled + * DEVICE_DOCKING_DEBOUNCED, // DEVICE_DIRECT -> DOCK_DEVICE_HUB, pogo gpio + * DEVICE_DIRECT_ACC_DEBOUNCED, // DEVICE_DIRECT -> ACC_DEVICE_HUB, acc gpio + * (F) AUDIO_DIRECT, // Usb audio online, hub disabled + * AUDIO_DIRECT_DOCKING_DEBOUNCED, // AUDIO_DIRECT -> AUDIO_DIRECT_DOCK_OFFLINE, pogo gpio + * AUDIO_DIRECT_ACC_DEBOUNCED, // AUDIO_DIRECT -> ACC_DEVICE_HUB, acc gpio + * (G) AUDIO_DIRECT_DOCK_OFFLINE, // Usb audio online, dock offline, hub disabled + * (J) HOST_DIRECT, // Usb host online, hub disabled + * HOST_DIRECT_DOCKING_DEBOUNCED, // HOST_DIRECT -> HOST_DIRECT_DOCK_OFFLINE, pogo gpio + * (K) HOST_DIRECT_DOCK_OFFLINE, // Usb host online, dock offline, hub disabled + * HOST_DIRECT_ACC_DEBOUNCED, // HOST_DIRECT -> HOST_DIRECT_ACC_OFFLINE, acc gpio + * (L) DOCK_HUB_HOST_OFFLINE, // Dock online, usb host offline, hub enabled + * (M) ACC_DIRECT, // Acc online, hub disabled + * (N) ACC_DEVICE_HUB, // Acc online, usb device online, hub enabled + * (O) ACC_HUB, // Acc online, hub enabled + * (P) ACC_AUDIO_HUB, // Acc online, usb audio online, hub enabled + * (Q) LID_CLOSE, + * (R) HOST_DIRECT_ACC_OFFLINE, // Usb host online, acc offline, hub disabled + * (S) ACC_DIRECT_HOST_OFFLINE, // Acc online, usb host offline + */ + +#define FOREACH_STATE(S) \ + S(INVALID_STATE), \ + S(STANDBY), \ + S(DOCKING_DEBOUNCED), \ + S(STANDBY_ACC_DEBOUNCED), \ + S(DOCK_HUB), \ + S(DOCK_DEVICE_HUB), \ + S(DOCK_AUDIO_HUB), \ + S(AUDIO_HUB), \ + S(AUDIO_HUB_DOCKING_DEBOUNCED), \ + S(AUDIO_HUB_ACC_DEBOUNCED), \ + S(DEVICE_HUB), \ + S(DEVICE_HUB_DOCKING_DEBOUNCED), \ + S(DEVICE_HUB_ACC_DEBOUNCED), \ + S(DEVICE_DIRECT), \ + S(DEVICE_DOCKING_DEBOUNCED), \ + S(DEVICE_DIRECT_ACC_DEBOUNCED), \ + S(AUDIO_DIRECT), \ + S(AUDIO_DIRECT_DOCKING_DEBOUNCED), \ + S(AUDIO_DIRECT_ACC_DEBOUNCED), \ + S(AUDIO_DIRECT_DOCK_OFFLINE), \ + S(HOST_DIRECT), \ + S(HOST_DIRECT_DOCKING_DEBOUNCED), \ + S(HOST_DIRECT_DOCK_OFFLINE), \ + S(HOST_DIRECT_ACC_DEBOUNCED), \ + S(DOCK_HUB_HOST_OFFLINE), \ + S(ACC_DIRECT), \ + S(ACC_DEVICE_HUB), \ + S(ACC_HUB), \ + S(ACC_AUDIO_HUB), \ + S(LID_CLOSE), \ + S(HOST_DIRECT_ACC_OFFLINE), \ + S(ACC_DIRECT_HOST_OFFLINE) + +#define GENERATE_ENUM(e) e +#define GENERATE_STRING(s) #s + +enum pogo_state { + FOREACH_STATE(GENERATE_ENUM) +}; + +static const char * const pogo_states[] = { + FOREACH_STATE(GENERATE_STRING) +}; + enum pogo_event_type { /* Reported when docking status changes */ EVENT_DOCKING, @@ -73,6 +156,16 @@ enum pogo_event_type { EVENT_ORIENTATION_CHANGED, }; +#define EVENT_POGO_IRQ BIT(0) +#define EVENT_USBC_DATA_CHANGE BIT(1) +#define EVENT_ENABLE_USB_DATA BIT(2) +#define EVENT_HES_H1S_CHANGED BIT(3) +#define EVENT_ACC_GPIO_ACTIVE BIT(4) +#define EVENT_ACC_CONNECTED BIT(5) +#define EVENT_AUDIO_DEV_ATTACHED BIT(6) +#define EVENT_USBC_ORIENTATION BIT(7) +#define EVENT_LAST_EVENT_TYPE BIT(63) + static bool modparam_force_usb; module_param_named(force_usb, modparam_force_usb, bool, 0644); MODULE_PARM_DESC(force_usb, "Force enabling usb path over pogo"); @@ -82,6 +175,11 @@ static int modparam_pogo_accessory_enable; module_param_named(pogo_accessory_enable, modparam_pogo_accessory_enable, int, 0644); MODULE_PARM_DESC(pogo_accessory_enable, "Enabling accessory detection over pogo"); +/* Set to 1 (enable) or 2 (disable) to override the default value */ +static int modparam_state_machine_enable; +module_param_named(state_machine_enable, modparam_state_machine_enable, int, 0644); +MODULE_PARM_DESC(state_machine_enable, "Enabling pogo state machine transition"); + struct pogo_event { struct kthread_delayed_work work; struct pogo_transport *pogo_transport; @@ -106,6 +204,11 @@ enum pogo_accessory_detection { ENABLED }; +struct pogo_transport_udev_ids { + __le16 vendor; + __le16 product; +}; + struct pogo_transport { struct device *dev; struct max77759_plat *chip; @@ -154,8 +257,21 @@ struct pogo_transport { * Only applicable for debugfs capable builds. */ bool mock_hid_connected; - struct kthread_worker *wq; + struct kthread_worker *wq; + struct kthread_delayed_work state_machine; + struct kthread_work event_work; + enum pogo_state prev_state; + enum pogo_state state; + enum pogo_state delayed_state; + unsigned long delayed_runtime; + unsigned long delay_ms; + unsigned long event_map; + bool state_machine_running; + bool state_machine_enabled; + spinlock_t pogo_event_lock; + + /* Register the notifier from USB core */ struct notifier_block udev_nb; /* To read voltage at the pogo pins */ @@ -177,6 +293,10 @@ struct pogo_transport { /* Orientation of USB-C, 0:TYPEC_POLARITY_CC1 1:TYPEC_POLARITY_CC2 */ enum typec_cc_polarity polarity; + + /* Cache values from the Type-C driver */ + enum typec_data_role usbc_data_role; + bool usbc_data_active; }; static const unsigned int pogo_extcon_cable[] = { @@ -185,6 +305,32 @@ static const unsigned int pogo_extcon_cable[] = { EXTCON_NONE, }; +/* + * list of USB VID:PID pair of udevs which are audio docks with pogo interfaces + * Both VID and PID are required in each entry. + */ +static const struct pogo_transport_udev_ids audio_dock_ids[] = { + { + .vendor = cpu_to_le16(0x18d1), + .product = cpu_to_le16(0x9480), + }, + { }, +}; + +/* Return true if @vid and @pid pair is found in @list. Otherwise, return false. */ +static bool pogo_transport_match_udev(const struct pogo_transport_udev_ids *list, const u16 vid, + const u16 pid) +{ + if (list) { + while (list->vendor && list->product) { + if (list->vendor == cpu_to_le16(vid) && list->product == cpu_to_le16(pid)) + return true; + list++; + } + } + return false; +} + static void pogo_transport_event(struct pogo_transport *pogo_transport, enum pogo_event_type event_type, int delay_ms); @@ -226,6 +372,7 @@ static void ssphy_restart_control(struct pogo_transport *pogo_transport, bool en return; } + logbuffer_log(pogo_transport->log, "ssphy_restart_control %u", enable); gvotable_cast_long_vote(pogo_transport->ssphy_restart_votable, POGO_VOTER, enable, enable); } @@ -752,6 +899,967 @@ static void pogo_transport_event(struct pogo_transport *pogo_transport, kthread_mod_delayed_work(pogo_transport->wq, &evt->work, msecs_to_jiffies(delay_ms)); } +/*-------------------------------------------------------------------------*/ +/* State Machine Functions */ +/*-------------------------------------------------------------------------*/ + +/* + * State transition + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_set_state(struct pogo_transport *pogo_transport, enum pogo_state state, + unsigned int delay_ms) +{ + if (delay_ms) { + logbuffer_log(pogo_transport->log, "pending state change %s -> %s @ %u ms", + pogo_states[pogo_transport->state], pogo_states[state], delay_ms); + pogo_transport->delayed_state = state; + kthread_mod_delayed_work(pogo_transport->wq, &pogo_transport->state_machine, + msecs_to_jiffies(delay_ms)); + pogo_transport->delayed_runtime = jiffies + msecs_to_jiffies(delay_ms); + pogo_transport->delay_ms = delay_ms; + } else { + logbuffer_logk(pogo_transport->log, LOGLEVEL_INFO, "state change %s -> %s", + pogo_states[pogo_transport->state], pogo_states[state]); + pogo_transport->delayed_state = INVALID_STATE; + pogo_transport->prev_state = pogo_transport->state; + pogo_transport->state = state; + + if (!pogo_transport->state_machine_running) + kthread_mod_delayed_work(pogo_transport->wq, &pogo_transport->state_machine, + 0); + } +} + +/* + * Accessory Detection regulator control + * - Return -ENXIO if Accessory Detection regulator does not exist + * - Return 0 if @enable is the same as the status of the regulator + * - Otherwise, return the return value from regulator_enable or regulator_disable + */ +static int pogo_transport_acc_regulator(struct pogo_transport *pogo_transport, bool enable) +{ + int ret; + + if (!pogo_transport->acc_detect_ldo) + return -ENXIO; + + if (regulator_is_enabled(pogo_transport->acc_detect_ldo) == enable) + return 0; + + if (enable) + ret = regulator_enable(pogo_transport->acc_detect_ldo); + else + ret = regulator_disable(pogo_transport->acc_detect_ldo); + + return ret; +} + +/* + * Call this function to: + * - Disable POGO Vout by voting 0 to charger_mode_votable + * - Disable the regulator for Accessory Detection Logic + * - Disable Accessory Detection IRQ + * - Enable POGO Voltage Detection IRQ + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_reset_acc_detection(struct pogo_transport *pogo_transport) +{ + int ret; + + ret = gvotable_cast_long_vote(pogo_transport->charger_mode_votable, POGO_VOTER, + GBMS_POGO_VOUT, 0); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to unvote VOUT, ret %d", __func__, + ret); + + ret = pogo_transport_acc_regulator(pogo_transport, false); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to disable acc_detect %d", __func__, + ret); + + if (pogo_transport->acc_irq_enabled) { + disable_irq(pogo_transport->pogo_acc_irq); + pogo_transport->acc_irq_enabled = false; + } + + if (!pogo_transport->pogo_irq_enabled) { + enable_irq(pogo_transport->pogo_irq); + pogo_transport->pogo_irq_enabled = true; + } +} + +/* + * This function implements the actions unon entering each state. + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_run_state_machine(struct pogo_transport *pogo_transport) +{ + bool acc_detected = gpio_get_value(pogo_transport->pogo_acc_gpio); + bool docked = !gpio_get_value(pogo_transport->pogo_gpio); + struct max77759_plat *chip = pogo_transport->chip; + int ret; + + switch (pogo_transport->state) { + case STANDBY: + /* DATA_STATUS_ENABLED */ + break; + case DOCKING_DEBOUNCED: + if (docked) { + update_extcon_dev(pogo_transport, true, true); + switch_to_hub_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, DOCK_HUB, 0); + } else { + pogo_transport_set_state(pogo_transport, STANDBY, 0); + } + break; + case DOCK_HUB: + /* clear Dock dock detected notification */ + /* Clear accessory detected notification */ + /* DATA_STATUS_DISABLED_DEVICE_DOCK */ + break; + case DEVICE_DOCKING_DEBOUNCED: + if (docked) { + update_extcon_dev(pogo_transport, true, true); + switch_to_hub_locked(pogo_transport); + /* switch_to_hub_locked cleared data_active, set it here */ + chip->data_active = true; + pogo_transport_set_state(pogo_transport, DOCK_DEVICE_HUB, 0); + } else { + pogo_transport_set_state(pogo_transport, DEVICE_DIRECT, 0); + } + break; + case DOCK_DEVICE_HUB: + /* DATA_STATUS_DISABLED_DEVICE_DOCK */ + break; + case DEVICE_HUB_DOCKING_DEBOUNCED: + if (docked) { + update_extcon_dev(pogo_transport, true, true); + pogo_transport_set_state(pogo_transport, DOCK_DEVICE_HUB, 0); + } else { + pogo_transport_set_state(pogo_transport, DEVICE_HUB, 0); + } + break; + case AUDIO_DIRECT_DOCKING_DEBOUNCED: + if (docked) { + update_extcon_dev(pogo_transport, true, true); + pogo_transport_set_state(pogo_transport, AUDIO_DIRECT_DOCK_OFFLINE, 0); + } else { + pogo_transport_set_state(pogo_transport, AUDIO_DIRECT, 0); + } + break; + case AUDIO_DIRECT_DOCK_OFFLINE: + /* Push Dock dock detected notification */ + break; + case AUDIO_HUB_DOCKING_DEBOUNCED: + if (docked) { + update_extcon_dev(pogo_transport, true, true); + pogo_transport_set_state(pogo_transport, DOCK_AUDIO_HUB, 0); + } else { + pogo_transport_set_state(pogo_transport, AUDIO_HUB, 0); + } + break; + case HOST_DIRECT: + /* DATA_STATUS_ENABLED */ + /* Clear Pogo accessory Detected */ + /* Clear USB accessory detected notification */ + break; + case HOST_DIRECT_DOCKING_DEBOUNCED: + if (docked) { + update_extcon_dev(pogo_transport, true, true); + pogo_transport_set_state(pogo_transport, HOST_DIRECT_DOCK_OFFLINE, 0); + } else { + pogo_transport_set_state(pogo_transport, HOST_DIRECT, 0); + } + break; + case HOST_DIRECT_DOCK_OFFLINE: + /* Push Dock dock detected notification */ + break; + case DOCK_HUB_HOST_OFFLINE: + /* Push accessory detected notification */ + break; + case STANDBY_ACC_DEBOUNCED: + case DEVICE_DIRECT_ACC_DEBOUNCED: + case DEVICE_HUB_ACC_DEBOUNCED: + case AUDIO_DIRECT_ACC_DEBOUNCED: + case AUDIO_HUB_ACC_DEBOUNCED: + case HOST_DIRECT_ACC_DEBOUNCED: + /* debounce fail; leave the IRQ and regulator enabled and do nothing */ + if (!acc_detected) + break; + + /* + * Disable the IRQ to ignore the noise after POGO Vout is enabled. It will be + * re-enabled when HES reports the attach event. + */ + if (pogo_transport->acc_irq_enabled) { + disable_irq(pogo_transport->pogo_acc_irq); + pogo_transport->acc_irq_enabled = false; + } + + /* TODO: queue work for gvotable cast vote if it takes too much time */ + ret = gvotable_cast_long_vote(pogo_transport->charger_mode_votable, POGO_VOTER, + GBMS_POGO_VOUT, 1); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to vote VOUT, ret %d", + __func__, ret); + break; + case ACC_DIRECT: + /* Clear Pogo accessory Detected */ + /* Clear USB accessory detected notification */ + break; + case ACC_DEVICE_HUB: + /* DATA_STATUS_DISABLED_DEVICE_DOCK */ + break; + case HOST_DIRECT_ACC_OFFLINE: + /* Push Pogo accessory Detected */ + break; + default: + break; + } +} + +/* Main loop of the State Machine */ +static void pogo_transport_state_machine_work(struct kthread_work *work) +{ + struct pogo_transport *pogo_transport = + container_of(container_of(work, struct kthread_delayed_work, work), + struct pogo_transport, state_machine); + struct max77759_plat *chip = pogo_transport->chip; + enum pogo_state prev_state; + + mutex_lock(&chip->data_path_lock); + pogo_transport->state_machine_running = true; + + if (pogo_transport->delayed_state) { + logbuffer_logk(pogo_transport->log, LOGLEVEL_INFO, + "state change %s -> %s [delayed %ld ms]", + pogo_states[pogo_transport->state], + pogo_states[pogo_transport->delayed_state], + pogo_transport->delay_ms); + pogo_transport->prev_state = pogo_transport->state; + pogo_transport->state = pogo_transport->delayed_state; + pogo_transport->delayed_state = INVALID_STATE; + } + + do { + prev_state = pogo_transport->state; + pogo_transport_run_state_machine(pogo_transport); + } while (pogo_transport->state != prev_state && !pogo_transport->delayed_state); + + pogo_transport->state_machine_running = false; + mutex_unlock(&chip->data_path_lock); +} + +/* + * Called when POGO Voltage Detection IRQ is active + * - Triggered from event: EVENT_POGO_IRQ + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_pogo_irq_active(struct pogo_transport *pogo_transport) +{ + switch (pogo_transport->state) { + case STANDBY: + pogo_transport_set_state(pogo_transport, DOCKING_DEBOUNCED, POGO_PSY_DEBOUNCE_MS); + break; + case DEVICE_HUB: + pogo_transport_set_state(pogo_transport, DEVICE_HUB_DOCKING_DEBOUNCED, + POGO_PSY_DEBOUNCE_MS); + break; + case DEVICE_DIRECT: + pogo_transport_set_state(pogo_transport, DEVICE_DOCKING_DEBOUNCED, + POGO_PSY_DEBOUNCE_MS); + break; + case AUDIO_DIRECT: + pogo_transport_set_state(pogo_transport, AUDIO_DIRECT_DOCKING_DEBOUNCED, + POGO_PSY_DEBOUNCE_MS); + break; + case AUDIO_HUB: + pogo_transport_set_state(pogo_transport, AUDIO_HUB_DOCKING_DEBOUNCED, + POGO_PSY_DEBOUNCE_MS); + break; + case HOST_DIRECT: + pogo_transport_set_state(pogo_transport, HOST_DIRECT_DOCKING_DEBOUNCED, + POGO_PSY_DEBOUNCE_MS); + break; + default: + break; + } +} + +/* + * Called when POGO Voltage Detection IRQ is standby + * - Triggered from event: EVENT_POGO_IRQ + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_pogo_irq_standby(struct pogo_transport *pogo_transport) +{ + switch (pogo_transport->state) { + case STANDBY: + update_extcon_dev(pogo_transport, false, false); + pogo_transport_set_state(pogo_transport, STANDBY, 0); + break; + case DOCK_HUB: + update_extcon_dev(pogo_transport, false, false); + switch_to_usbc_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, STANDBY, 0); + break; + case DOCK_DEVICE_HUB: + update_extcon_dev(pogo_transport, false, false); + pogo_transport_set_state(pogo_transport, DEVICE_HUB, 0); + break; + case DOCK_AUDIO_HUB: + update_extcon_dev(pogo_transport, false, false); + pogo_transport_set_state(pogo_transport, AUDIO_HUB, 0); + break; + case AUDIO_DIRECT_DOCK_OFFLINE: + update_extcon_dev(pogo_transport, false, false); + pogo_transport_set_state(pogo_transport, AUDIO_DIRECT, 0); + break; + case HOST_DIRECT_DOCK_OFFLINE: + update_extcon_dev(pogo_transport, false, false); + pogo_transport_set_state(pogo_transport, HOST_DIRECT, 0); + break; + case DOCK_HUB_HOST_OFFLINE: + update_extcon_dev(pogo_transport, false, false); + switch_to_usbc_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, HOST_DIRECT, 0); + break; + default: + break; + } +} + +/* + * Called when USB-C port enters Host Mode + * - Triggered from event: EVENT_USBC_DATA_CHANGE + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_usbc_host_on(struct pogo_transport *pogo_transport) +{ + struct max77759_plat *chip = pogo_transport->chip; + + switch (pogo_transport->state) { + case STANDBY: + pogo_transport_set_state(pogo_transport, DEVICE_DIRECT, 0); + break; + case DOCK_HUB: + /* Set data_active since USB-C device is attached */ + chip->data_active = true; + pogo_transport_set_state(pogo_transport, DOCK_DEVICE_HUB, 0); + break; + case ACC_DIRECT: + switch_to_hub_locked(pogo_transport); + /* Set data_active since USB-C device is attached */ + chip->data_active = true; + pogo_transport_set_state(pogo_transport, ACC_DEVICE_HUB, 0); + break; + case ACC_HUB: + /* Set data_active since USB-C device is attached */ + chip->data_active = true; + pogo_transport_set_state(pogo_transport, ACC_DEVICE_HUB, 0); + break; + default: + break; + } +} + +/* + * Called when USB-C port leaves Host Mode + * - Triggered from event: EVENT_USBC_DATA_CHANGE + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_usbc_host_off(struct pogo_transport *pogo_transport) +{ + struct max77759_plat *chip = pogo_transport->chip; + + switch (pogo_transport->state) { + case DOCK_DEVICE_HUB: + /* Clear data_active since USB-C device is detached */ + chip->data_active = false; + pogo_transport_set_state(pogo_transport, DOCK_HUB, 0); + break; + case DEVICE_HUB: + switch_to_usbc_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, STANDBY, 0); + break; + case DEVICE_DIRECT: + case AUDIO_DIRECT: + pogo_transport_set_state(pogo_transport, STANDBY, 0); + break; + case AUDIO_DIRECT_DOCK_OFFLINE: + switch_to_hub_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, DOCK_HUB, 0); + break; + case DOCK_AUDIO_HUB: + /* Clear data_active since USB-C device is detached */ + chip->data_active = false; + pogo_transport_set_state(pogo_transport, DOCK_HUB, 0); + break; + case AUDIO_HUB: + switch_to_usbc_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, STANDBY, 0); + break; + case ACC_DEVICE_HUB: + case ACC_AUDIO_HUB: + /* Clear data_active since USB-C device is detached */ + chip->data_active = false; + pogo_transport_set_state(pogo_transport, ACC_HUB, 0); + break; + default: + break; + } +} + +/* + * Called when USB-C port enters Device Mode + * - Triggered from event: EVENT_USBC_DATA_CHANGE + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_usbc_device_on(struct pogo_transport *pogo_transport) +{ + struct max77759_plat *chip = pogo_transport->chip; + + switch (pogo_transport->state) { + case STANDBY: + pogo_transport_set_state(pogo_transport, HOST_DIRECT, 0); + break; + case DOCK_HUB: + /* + * Set data_active so that once USB-C cable is detached later, Type-C stack is able + * to call back for the data changed event + */ + chip->data_active = true; + pogo_transport_set_state(pogo_transport, DOCK_HUB_HOST_OFFLINE, 0); + break; + case ACC_DIRECT: + /* + * Set data_active so that once USB-C cable is detached later, Type-C stack is able + * to call back for the data changed event + */ + chip->data_active = true; + pogo_transport_set_state(pogo_transport, ACC_DIRECT_HOST_OFFLINE, 0); + break; + case ACC_HUB: + switch_to_pogo_locked(pogo_transport); + /* + * Set data_active so that once USB-C cable is detached later, Type-C stack is able + * to call back for the data changed event + */ + chip->data_active = true; + pogo_transport_set_state(pogo_transport, ACC_DIRECT_HOST_OFFLINE, 0); + break; + default: + break; + } +} + +/* + * Called when USB-C port leaves Device Mode + * - Triggered from event: EVENT_USBC_DATA_CHANGE + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_usbc_device_off(struct pogo_transport *pogo_transport) +{ + struct max77759_plat *chip = pogo_transport->chip; + + switch (pogo_transport->state) { + case HOST_DIRECT: + pogo_transport_set_state(pogo_transport, STANDBY, 0); + break; + case HOST_DIRECT_DOCK_OFFLINE: + switch_to_hub_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, DOCK_HUB, 0); + break; + case DOCK_HUB_HOST_OFFLINE: + /* + * Clear data_active so that Type-C stack is able to call back for the data changed + * event + */ + chip->data_active = false; + pogo_transport_set_state(pogo_transport, DOCK_HUB, 0); + break; + case HOST_DIRECT_ACC_OFFLINE: + switch_to_pogo_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, ACC_DIRECT, 0); + break; + case ACC_DIRECT_HOST_OFFLINE: + /* + * Clear data_active so that Type-C stack is able to call back for the data changed + * event + */ + chip->data_active = false; + pogo_transport_set_state(pogo_transport, ACC_DIRECT, 0); + break; + default: + break; + } +} + +/* + * Called when device attribute "move_data_to_usb" is written to 1 + * - Triggered from event: EVENT_ENABLE_USB_DATA + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_enable_usb_data(struct pogo_transport *pogo_transport) +{ + struct max77759_plat *chip = pogo_transport->chip; + + switch (pogo_transport->state) { + case DOCK_HUB_HOST_OFFLINE: + /* + * Clear data_active so that Type-C stack is able to call back for the data changed + * event later + */ + chip->data_active = false; + switch_to_usbc_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, HOST_DIRECT_DOCK_OFFLINE, 0); + break; + case ACC_DIRECT_HOST_OFFLINE: + /* + * Clear data_active so that Type-C stack is able to call back for the data changed + * event later + */ + chip->data_active = false; + switch_to_usbc_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, HOST_DIRECT_ACC_OFFLINE, 0); + break; + default: + return; + } +} + +/* + * Call this function to: + * - Disable Accessory Detection IRQ + * - Disable POGO Voltage Detection IRQ + * - Enable POGO Vout by voting 1 to charger_mode_votable + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_skip_acc_detection(struct pogo_transport *pogo_transport) +{ + int ret; + + logbuffer_log(pogo_transport->log, "%s: Skip enabling comparator logic, enable vout", + __func__); + + if (pogo_transport->acc_irq_enabled) { + disable_irq(pogo_transport->pogo_acc_irq); + pogo_transport->acc_irq_enabled = false; + } + + if (pogo_transport->pogo_irq_enabled) { + disable_irq(pogo_transport->pogo_irq); + pogo_transport->pogo_irq_enabled = false; + } + + ret = gvotable_cast_long_vote(pogo_transport->charger_mode_votable, + POGO_VOTER, GBMS_POGO_VOUT, 1); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to vote VOUT, ret %d", __func__, + ret); +} + +/* + * Called when device attribute "hall1_s" is written to non-zero + * - If accessory_detection_enabled == ENABLED, it won't involve the State transition. + * Enable the Accessory Detection IRQ and the regulator for the later detection process. + * - If accessory_detection_enabled == HALL_ONLY, transition to related ACC states + * - Triggered from event: EVENT_HES_H1S_CHANGED + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_hes_acc_detected(struct pogo_transport *pogo_transport) +{ + int ret; + + /* Disable OVP to prevent the voltage going through POGO_VIN */ + if (pogo_transport->pogo_ovp_en_gpio >= 0) + gpio_set_value_cansleep(pogo_transport->pogo_ovp_en_gpio, + !pogo_transport->pogo_ovp_en_active_state); + + if (pogo_transport->accessory_detection_enabled == ENABLED) { + if (!pogo_transport->acc_irq_enabled) { + enable_irq(pogo_transport->pogo_acc_irq); + pogo_transport->acc_irq_enabled = true; + } + + ret = pogo_transport_acc_regulator(pogo_transport, true); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to enable acc_detect %d", + __func__, ret); + } else if (pogo_transport->accessory_detection_enabled == HALL_ONLY) { + switch (pogo_transport->state) { + case STANDBY: + pogo_transport_skip_acc_detection(pogo_transport); + if (!pogo_transport->mfg_acc_test) + switch_to_pogo_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, ACC_DIRECT, 0); + break; + case DEVICE_DIRECT: + case AUDIO_DIRECT: + pogo_transport_skip_acc_detection(pogo_transport); + switch_to_hub_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, ACC_DEVICE_HUB, 0); + break; + case DEVICE_HUB: + pogo_transport_skip_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, ACC_DEVICE_HUB, 0); + break; + case AUDIO_HUB: + pogo_transport_skip_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, ACC_AUDIO_HUB, 0); + break; + case HOST_DIRECT: + pogo_transport_skip_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, HOST_DIRECT_ACC_OFFLINE, 0); + break; + default: + break; + } + } +} + +/* + * Called when device attribute "hall1_s" is written to 0 + * - Triggered from event: EVENT_HES_H1S_CHANGED + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_hes_acc_detached(struct pogo_transport *pogo_transport) +{ + struct max77759_plat *chip = pogo_transport->chip; + + switch (pogo_transport->state) { + case STANDBY_ACC_DEBOUNCED: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, STANDBY, 0); + break; + case DEVICE_DIRECT_ACC_DEBOUNCED: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, DEVICE_DIRECT, 0); + break; + case DEVICE_HUB_ACC_DEBOUNCED: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, DEVICE_HUB, 0); + break; + case AUDIO_DIRECT_ACC_DEBOUNCED: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, AUDIO_DIRECT, 0); + break; + case AUDIO_HUB_ACC_DEBOUNCED: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, AUDIO_HUB, 0); + break; + case HOST_DIRECT_ACC_DEBOUNCED: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, HOST_DIRECT, 0); + break; + case ACC_DIRECT: + case ACC_HUB: + pogo_transport_reset_acc_detection(pogo_transport); + switch_to_usbc_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, STANDBY, 0); + break; + case ACC_DEVICE_HUB: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, DEVICE_HUB, 0); + break; + case ACC_AUDIO_HUB: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, AUDIO_HUB, 0); + break; + case HOST_DIRECT_ACC_OFFLINE: + pogo_transport_reset_acc_detection(pogo_transport); + pogo_transport_set_state(pogo_transport, HOST_DIRECT, 0); + break; + case ACC_DIRECT_HOST_OFFLINE: + pogo_transport_reset_acc_detection(pogo_transport); + /* Clear data_active so that Type-C stack is able to enable the USB data later */ + chip->data_active = false; + switch_to_usbc_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, HOST_DIRECT, 0); + break; + default: + break; + } +} + +/* + * Called when Accessory Detection IRQ is active + * - Triggered from event: EVENT_ACC_GPIO_ACTIVE + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_acc_debouncing(struct pogo_transport *pogo_transport) +{ + switch (pogo_transport->state) { + case STANDBY: + case STANDBY_ACC_DEBOUNCED: + pogo_transport_set_state(pogo_transport, STANDBY_ACC_DEBOUNCED, + pogo_transport->pogo_acc_gpio_debounce_ms); + break; + case DEVICE_DIRECT: + case DEVICE_DIRECT_ACC_DEBOUNCED: + pogo_transport_set_state(pogo_transport, DEVICE_DIRECT_ACC_DEBOUNCED, + pogo_transport->pogo_acc_gpio_debounce_ms); + break; + case DEVICE_HUB: + case DEVICE_HUB_ACC_DEBOUNCED: + pogo_transport_set_state(pogo_transport, DEVICE_HUB_ACC_DEBOUNCED, + pogo_transport->pogo_acc_gpio_debounce_ms); + break; + case AUDIO_DIRECT: + case AUDIO_DIRECT_ACC_DEBOUNCED: + pogo_transport_set_state(pogo_transport, AUDIO_DIRECT_ACC_DEBOUNCED, + pogo_transport->pogo_acc_gpio_debounce_ms); + break; + case AUDIO_HUB: + case AUDIO_HUB_ACC_DEBOUNCED: + pogo_transport_set_state(pogo_transport, AUDIO_HUB_ACC_DEBOUNCED, + pogo_transport->pogo_acc_gpio_debounce_ms); + break; + case HOST_DIRECT: + case HOST_DIRECT_ACC_DEBOUNCED: + pogo_transport_set_state(pogo_transport, HOST_DIRECT_ACC_DEBOUNCED, + pogo_transport->pogo_acc_gpio_debounce_ms); + break; + default: + break; + } +} + +/* + * Called when POGO Voltage Detection IRQ is active while Accessory Detection regulator is enabled. + * - Triggered from event: EVENT_ACC_CONNECTED + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_acc_connected(struct pogo_transport *pogo_transport) +{ + struct max77759_plat *chip = pogo_transport->chip; + int ret; + + /* + * FIXME: is it possible that when acc regulator is enabled and pogo irq become active + * because 12V input through pogo pin? e.g. keep magnet closed to the device and then + * docking on korlan? + */ + + switch (pogo_transport->state) { + case STANDBY_ACC_DEBOUNCED: + ret = pogo_transport_acc_regulator(pogo_transport, false); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to disable acc_detect %d", + __func__, ret); + + if (!pogo_transport->mfg_acc_test) + switch_to_pogo_locked(pogo_transport); + pogo_transport_set_state(pogo_transport, ACC_DIRECT, 0); + break; + case DEVICE_DIRECT_ACC_DEBOUNCED: + case AUDIO_DIRECT_ACC_DEBOUNCED: + ret = pogo_transport_acc_regulator(pogo_transport, false); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to disable acc_detect %d", + __func__, ret); + + switch_to_hub_locked(pogo_transport); + chip->data_active = true; + pogo_transport_set_state(pogo_transport, ACC_DEVICE_HUB, 0); + break; + case DEVICE_HUB_ACC_DEBOUNCED: + ret = pogo_transport_acc_regulator(pogo_transport, false); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to disable acc_detect %d", + __func__, ret); + + pogo_transport_set_state(pogo_transport, ACC_DEVICE_HUB, 0); + break; + case AUDIO_HUB_ACC_DEBOUNCED: + ret = pogo_transport_acc_regulator(pogo_transport, false); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to disable acc_detect %d", + __func__, ret); + + pogo_transport_set_state(pogo_transport, ACC_AUDIO_HUB, 0); + break; + case HOST_DIRECT_ACC_DEBOUNCED: + ret = pogo_transport_acc_regulator(pogo_transport, false); + if (ret) + logbuffer_log(pogo_transport->log, "%s: Failed to disable acc_detect %d", + __func__, ret); + + pogo_transport_set_state(pogo_transport, HOST_DIRECT_ACC_OFFLINE, 0); + break; + default: + break; + } +} + +/* + * Called when a USB device with AUDIO Class is enumerated. + * - Triggered from event: EVENT_AUDIO_DEV_ATTACHED + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_audio_dev_attached(struct pogo_transport *pogo_transport) +{ + switch (pogo_transport->state) { + case DOCK_DEVICE_HUB: + pogo_transport_set_state(pogo_transport, DOCK_AUDIO_HUB, 0); + break; + case DEVICE_DIRECT: + pogo_transport_set_state(pogo_transport, AUDIO_DIRECT, 0); + break; + case ACC_DEVICE_HUB: + pogo_transport_set_state(pogo_transport, ACC_AUDIO_HUB, 0); + break; + default: + break; + } +} + +/* + * Called when the detected orientation on USB-C port is changed. + * - Triggered from event: EVENT_USBC_ORIENTATION + * + * This function is guarded by (max77759_plat)->data_path_lock + */ +static void pogo_transport_usbc_orientation_changed(struct pogo_transport *pogo_transport) +{ + /* + * TODO: It is possible that USB-C is toggling between CC2 and Open. We may need to wait for + * the orientation being settled and then update the ssphy. + */ + switch (pogo_transport->state) { + /* usbc being connected while hub is enabled */ + case DOCK_HUB: + case ACC_HUB: + /* usbc being disconnected while hub is enabled */ + case DOCK_DEVICE_HUB: + case DOCK_AUDIO_HUB: + case DOCK_HUB_HOST_OFFLINE: + case ACC_DEVICE_HUB: + case ACC_AUDIO_HUB: + pogo_transport_update_polarity(pogo_transport, (int)pogo_transport->polarity, true); + ssphy_restart_control(pogo_transport, true); + break; + default: + break; + } +} + +static void pogo_transport_event_handler(struct kthread_work *work) +{ + struct pogo_transport *pogo_transport = container_of(work, struct pogo_transport, + event_work); + struct max77759_plat *chip = pogo_transport->chip; + unsigned long events; + + mutex_lock(&chip->data_path_lock); + spin_lock(&pogo_transport->pogo_event_lock); + while (pogo_transport->event_map) { + events = pogo_transport->event_map; + pogo_transport->event_map = 0; + + spin_unlock(&pogo_transport->pogo_event_lock); + + if (events & EVENT_POGO_IRQ) { + int pogo_gpio = gpio_get_value(pogo_transport->pogo_gpio); + + logbuffer_log(pogo_transport->log, "EV:POGO_IRQ %s", pogo_gpio ? + "STANDBY" : "ACTIVE"); + if (pogo_gpio) + pogo_transport_pogo_irq_standby(pogo_transport); + else + pogo_transport_pogo_irq_active(pogo_transport); + } + if (events & EVENT_USBC_ORIENTATION) { + logbuffer_log(pogo_transport->log, "EV:ORIENTATION %u", + pogo_transport->polarity); + pogo_transport_usbc_orientation_changed(pogo_transport); + } + if (events & EVENT_USBC_DATA_CHANGE) { + logbuffer_log(pogo_transport->log, "EV:DATA_CHANGE usbc-role %u usbc-active %u", + pogo_transport->usbc_data_role, + pogo_transport->usbc_data_active); + if (pogo_transport->usbc_data_role == TYPEC_HOST) { + if (pogo_transport->usbc_data_active) + pogo_transport_usbc_host_on(pogo_transport); + else + pogo_transport_usbc_host_off(pogo_transport); + } else { + if (pogo_transport->usbc_data_active) + pogo_transport_usbc_device_on(pogo_transport); + else + pogo_transport_usbc_device_off(pogo_transport); + } + } + if (events & EVENT_ENABLE_USB_DATA) { + logbuffer_log(pogo_transport->log, "EV:ENABLE_USB"); + pogo_transport_enable_usb_data(pogo_transport); + } + if (events & EVENT_HES_H1S_CHANGED) { + logbuffer_log(pogo_transport->log, "EV:H1S state %d", + pogo_transport->hall1_s_state); + if (pogo_transport->hall1_s_state) + pogo_transport_hes_acc_detected(pogo_transport); + else + pogo_transport_hes_acc_detached(pogo_transport); + } + if (events & EVENT_ACC_GPIO_ACTIVE) { + logbuffer_log(pogo_transport->log, "EV:ACC_GPIO_ACTIVE"); + pogo_transport_acc_debouncing(pogo_transport); + } + if (events & EVENT_ACC_CONNECTED) { + logbuffer_log(pogo_transport->log, "EV:ACC_CONNECTED"); + pogo_transport_acc_connected(pogo_transport); + } + if (events & EVENT_AUDIO_DEV_ATTACHED) { + logbuffer_log(pogo_transport->log, "EV:AUDIO_ATTACHED"); + pogo_transport_audio_dev_attached(pogo_transport); + } + + spin_lock(&pogo_transport->pogo_event_lock); + } + spin_unlock(&pogo_transport->pogo_event_lock); + mutex_unlock(&chip->data_path_lock); +} + +static void pogo_transport_queue_event(struct pogo_transport *pogo_transport, unsigned long event) +{ + unsigned long flags; + + pm_wakeup_event(pogo_transport->dev, POGO_TIMEOUT_MS); + /* + * Print the event number derived from the bit position; e.g. BIT(0) -> 0 + * Note that ffs() only return the least significant set bit. + */ + logbuffer_log(pogo_transport->log, "QUEUE EVENT %d", ffs((int)event) - 1); + + spin_lock_irqsave(&pogo_transport->pogo_event_lock, flags); + pogo_transport->event_map |= event; + spin_unlock_irqrestore(&pogo_transport->pogo_event_lock, flags); + + kthread_queue_work(pogo_transport->wq, &pogo_transport->event_work); +} + +/*-------------------------------------------------------------------------*/ +/* Events triggering */ +/*-------------------------------------------------------------------------*/ + static irqreturn_t pogo_acc_irq(int irq, void *dev_id) { struct pogo_transport *pogo_transport = dev_id; @@ -760,6 +1868,12 @@ static irqreturn_t pogo_acc_irq(int irq, void *dev_id) logbuffer_log(pogo_transport->log, "Pogo acc threaded irq running, acc_detect %u", pogo_acc_gpio); + if (pogo_transport->state_machine_enabled) { + if (pogo_acc_gpio) + pogo_transport_queue_event(pogo_transport, EVENT_ACC_GPIO_ACTIVE); + return IRQ_HANDLED; + } + if (pogo_acc_gpio) pogo_transport_event(pogo_transport, EVENT_POGO_ACC_DEBOUNCED, pogo_transport->pogo_acc_gpio_debounce_ms); @@ -792,7 +1906,10 @@ static irqreturn_t pogo_irq(int irq, void *dev_id) /* disable the irq to prevent the interrupt storm after pogo 5v out */ disable_irq_nosync(pogo_transport->pogo_irq); pogo_transport->pogo_irq_enabled = false; - pogo_transport_event(pogo_transport, EVENT_POGO_ACC_CONNECTED, 0); + if (pogo_transport->state_machine_enabled) + pogo_transport_queue_event(pogo_transport, EVENT_ACC_CONNECTED); + else + pogo_transport_event(pogo_transport, EVENT_POGO_ACC_CONNECTED, 0); } return IRQ_HANDLED; } @@ -812,21 +1929,37 @@ static irqreturn_t pogo_irq(int irq, void *dev_id) __func__, ret); } - /* - * Signal pogo status change event. - * Debounce on docking to differentiate between different docks by - * reading power supply voltage. - */ - pogo_transport_event(pogo_transport, EVENT_DOCKING, !pogo_gpio ? POGO_PSY_DEBOUNCE_MS : 0); + if (pogo_transport->state_machine_enabled) + pogo_transport_queue_event(pogo_transport, EVENT_POGO_IRQ); + else + pogo_transport_event(pogo_transport, EVENT_DOCKING, !pogo_gpio ? + POGO_PSY_DEBOUNCE_MS : 0); return IRQ_HANDLED; } -static void data_active_changed(void *data) +static irqreturn_t pogo_isr(int irq, void *dev_id) +{ + struct pogo_transport *pogo_transport = dev_id; + + logbuffer_log(pogo_transport->log, "POGO IRQ triggered"); + pm_wakeup_event(pogo_transport->dev, POGO_TIMEOUT_MS); + + return IRQ_WAKE_THREAD; +} + +static void data_active_changed(void *data, enum typec_data_role role, bool active) { struct pogo_transport *pogo_transport = data; - logbuffer_log(pogo_transport->log, "data active changed"); - pogo_transport_event(pogo_transport, EVENT_DATA_ACTIVE_CHANGED, 0); + logbuffer_log(pogo_transport->log, "%s: role %u active %d", __func__, role, active); + + pogo_transport->usbc_data_role = role; + pogo_transport->usbc_data_active = active; + + if (pogo_transport->state_machine_enabled) + pogo_transport_queue_event(pogo_transport, EVENT_USBC_DATA_CHANGE); + else + pogo_transport_event(pogo_transport, EVENT_DATA_ACTIVE_CHANGED, 0); } static void orientation_changed(void *data) @@ -836,20 +1969,14 @@ static void orientation_changed(void *data) if (pogo_transport->polarity != chip->polarity) { pogo_transport->polarity = chip->polarity; - pogo_transport_event(pogo_transport, EVENT_ORIENTATION_CHANGED, 0); + if (pogo_transport->state_machine_enabled) + pogo_transport_queue_event(pogo_transport, EVENT_USBC_ORIENTATION); + else + pogo_transport_event(pogo_transport, EVENT_ORIENTATION_CHANGED, 0); } } -static irqreturn_t pogo_isr(int irq, void *dev_id) -{ - struct pogo_transport *pogo_transport = dev_id; - - logbuffer_log(pogo_transport->log, "POGO IRQ triggered"); - pm_wakeup_event(pogo_transport->dev, POGO_TIMEOUT_MS); - - return IRQ_WAKE_THREAD; -} - +/* Called when a USB hub/device (exclude root hub) is enumerated */ static void pogo_transport_udev_add(struct pogo_transport *pogo_transport, struct usb_device *udev) { struct usb_interface_descriptor *desc; @@ -857,6 +1984,11 @@ static void pogo_transport_udev_add(struct pogo_transport *pogo_transport, struc bool audio_dev = false; int i; + /* Don't proceed to the event handling if the udev is an Audio Dock. Skip here. */ + if (pogo_transport_match_udev(audio_dock_ids, le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct))) + return; + config = udev->config; for (i = 0; i < config->desc.bNumInterfaces; i++) { desc = &config->intf_cache[i]->altsetting->desc; @@ -870,6 +2002,9 @@ static void pogo_transport_udev_add(struct pogo_transport *pogo_transport, struc le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct), audio_dev ? "(audio)" : ""); + + if (audio_dev && pogo_transport->state_machine_enabled) + pogo_transport_queue_event(pogo_transport, EVENT_AUDIO_DEV_ATTACHED); } /* notifier callback from usb core */ @@ -905,6 +2040,11 @@ static int mock_hid_connected_set(void *data, u64 val) { struct pogo_transport *pogo_transport = data; + if (pogo_transport->state_machine_enabled) { + logbuffer_log(pogo_transport->log, "state machine enabled; ignore mock hid"); + return 0; + } + pogo_transport->mock_hid_connected = !!val; logbuffer_log(pogo_transport->log, "%s: %u", __func__, pogo_transport->mock_hid_connected); @@ -929,6 +2069,10 @@ static int mock_hid_connected_get(void *data, u64 *val) DEFINE_SIMPLE_ATTRIBUTE(mock_hid_connected_fops, mock_hid_connected_get, mock_hid_connected_set, "%llu\n"); +/*-------------------------------------------------------------------------*/ +/* Initialization */ +/*-------------------------------------------------------------------------*/ + static void pogo_transport_init_debugfs(struct pogo_transport *pogo_transport) { struct dentry *dentry; @@ -1269,6 +2413,8 @@ static int pogo_transport_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, pogo_transport); + spin_lock_init(&pogo_transport->pogo_event_lock); + pogo_transport->wq = kthread_create_worker(0, "wq-pogo-transport"); if (IS_ERR_OR_NULL(pogo_transport->wq)) { ret = PTR_ERR(pogo_transport->wq); @@ -1277,6 +2423,9 @@ static int pogo_transport_probe(struct platform_device *pdev) kthread_init_delayed_work(&pogo_transport->pogo_accessory_debounce_work, process_debounce_event); + kthread_init_delayed_work(&pogo_transport->state_machine, + pogo_transport_state_machine_work); + kthread_init_work(&pogo_transport->event_work, pogo_transport_event_handler); dn = dev_of_node(pogo_transport->dev); if (!dn) { @@ -1347,6 +2496,28 @@ static int pogo_transport_probe(struct platform_device *pdev) goto psy_put; } + /* + * modparam_state_machine_enable + * 0 or unset: If property "legacy-event-driven" is found in device tree, disable the state + * machine. Otherwise, enable/disable the state machine based on + * DEFAULT_STATE_MACHINE_ENABLE. + * 1: Enable the state machine + * 2: Disable the state machine + */ + if (modparam_state_machine_enable == 1) { + pogo_transport->state_machine_enabled = true; + } else if (modparam_state_machine_enable == 2) { + pogo_transport->state_machine_enabled = false; + } else { + if (of_property_read_bool(pogo_transport->dev->of_node, "legacy-event-driven")) + pogo_transport->state_machine_enabled = false; + else + pogo_transport->state_machine_enabled = DEFAULT_STATE_MACHINE_ENABLE; + } + + if (pogo_transport->state_machine_enabled) + pogo_transport_set_state(pogo_transport, STANDBY, 0); + if (modparam_pogo_accessory_enable) { ret = init_acc_gpio(pogo_transport); if (ret) @@ -1383,6 +2554,7 @@ static int pogo_transport_probe(struct platform_device *pdev) /* run once in case orientation has changed before registering the callback */ orientation_changed((void *)pogo_transport); dev_info(&pdev->dev, "force usb:%d\n", modparam_force_usb ? 1 : 0); + dev_info(&pdev->dev, "state machine:%u\n", pogo_transport->state_machine_enabled); put_device(&data_client->dev); of_node_put(data_np); return 0; @@ -1404,6 +2576,7 @@ static int pogo_transport_remove(struct platform_device *pdev) { struct pogo_transport *pogo_transport = platform_get_drvdata(pdev); struct dentry *dentry; + int ret; usb_unregister_notify(&pogo_transport->udev_nb); @@ -1420,6 +2593,10 @@ static int pogo_transport_remove(struct platform_device *pdev) if (pogo_transport->hub_ldo && regulator_is_enabled(pogo_transport->hub_ldo) > 0) regulator_disable(pogo_transport->hub_ldo); + ret = pogo_transport_acc_regulator(pogo_transport, false); + if (ret) + dev_err(pogo_transport->dev, "%s: Failed to disable acc ldo %d\n", __func__, ret); + if (pogo_transport->acc_detect_ldo && regulator_is_enabled(pogo_transport->acc_detect_ldo) > 0) regulator_disable(pogo_transport->acc_detect_ldo); @@ -1437,6 +2614,10 @@ static int pogo_transport_remove(struct platform_device *pdev) return 0; } +/*-------------------------------------------------------------------------*/ +/* Event triggering part.2 */ +/*-------------------------------------------------------------------------*/ + #define POGO_TRANSPORT_RO_ATTR(_name) \ static ssize_t _name##_show(struct device *dev, struct device_attribute *attr, char *buf) \ { \ @@ -1459,7 +2640,10 @@ static ssize_t move_data_to_usb_store(struct device *dev, struct device_attribut if (enable != 1) return -EINVAL; - pogo_transport_event(pogo_transport, EVENT_MOVE_DATA_TO_USB, 0); + if (pogo_transport->state_machine_enabled) + pogo_transport_queue_event(pogo_transport, EVENT_ENABLE_USB_DATA); + else + pogo_transport_event(pogo_transport, EVENT_MOVE_DATA_TO_USB, 0); return size; } @@ -1474,6 +2658,12 @@ static ssize_t force_pogo_store(struct device *dev, struct device_attribute *att if (kstrtobool(buf, &force_pogo)) return -EINVAL; + /* TODO: implement force_pogo feature when state machine is enabled */ + if (pogo_transport->state_machine_enabled) { + logbuffer_log(pogo_transport->log, "state machine enabled; ignore force_pogo"); + return size; + } + pogo_transport->force_pogo = force_pogo; if (force_pogo) pogo_transport_event(pogo_transport, EVENT_MOVE_DATA_TO_POGO, 0); @@ -1494,6 +2684,11 @@ static ssize_t enable_hub_store(struct device *dev, struct device_attribute *att struct pogo_transport *pogo_transport = dev_get_drvdata(dev); u8 enable_hub; + if (pogo_transport->state_machine_enabled) { + logbuffer_log(pogo_transport->log, "state machine enabled; ignore enable_hub"); + return size; + } + if (!pogo_transport->hub_embedded) return size; @@ -1539,7 +2734,8 @@ static ssize_t hall1_s_store(struct device *dev, struct device_attribute *attr, return size; if (!pogo_transport->accessory_detection_enabled) { - dev_info(pogo_transport->dev, "Accessory detection disabled\n"); + logbuffer_logk(pogo_transport->log, LOGLEVEL_INFO, "%s:Accessory detection disabled", + __func__); return size; } @@ -1560,8 +2756,14 @@ static ssize_t hall1_s_store(struct device *dev, struct device_attribute *attr, else pogo_transport->mfg_acc_test = false; - dev_info(pogo_transport->dev, "accessory detection %u, mfg %u\n", enable_acc_detect, - pogo_transport->mfg_acc_test); + logbuffer_log(pogo_transport->log, "H1S: accessory detection %u, mfg %u", enable_acc_detect, + pogo_transport->mfg_acc_test); + + if (pogo_transport->state_machine_enabled) { + pogo_transport_queue_event(pogo_transport, EVENT_HES_H1S_CHANGED); + return size; + } + if (enable_acc_detect) pogo_transport_event(pogo_transport, EVENT_HALL_SENSOR_ACC_DETECTED, 0); else @@ -1574,7 +2776,15 @@ static DEVICE_ATTR_WO(hall1_s); static ssize_t hall1_n_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + struct pogo_transport *pogo_transport = dev_get_drvdata(dev); + u8 data; + /* Reserved for HES1 Malfunction detection */ + + if (kstrtou8(buf, 0, &data)) + return -EINVAL; + + logbuffer_log(pogo_transport->log, "H1N: %u", data); return size; } static DEVICE_ATTR_WO(hall1_n); @@ -1582,7 +2792,15 @@ static DEVICE_ATTR_WO(hall1_n); static ssize_t hall2_s_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + struct pogo_transport *pogo_transport = dev_get_drvdata(dev); + u8 data; + /* Reserved for keyboard status detection */ + + if (kstrtou8(buf, 0, &data)) + return -EINVAL; + + logbuffer_log(pogo_transport->log, "H2S: %u", data); return size; } static DEVICE_ATTR_WO(hall2_s); |