/* * Copyright (c) 2019 Actions Semiconductor Co., Ltd * * SPDX-License-Identifier: Apache-2.0 */ /** * @file hotplug usb interface */ #ifdef SYS_LOG_DOMAIN #undef SYS_LOG_DOMAIN #endif #define SYS_LOG_DOMAIN "hotpug_manager" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_USB_HOST #include #endif #include #ifdef CONFIG_USB_DEVICE #ifdef CONFIG_PM_DEVICE #include #include static bool usb_wake_unlock; #endif #endif #define USB_HOTPLUG_NONE 0 /* host mode: peripheral device connected */ #define USB_HOTPLUG_A_IN 1 /* host mode: peripheral device disconnected */ #define USB_HOTPLUG_A_OUT 2 /* device mode: connected to host */ #define USB_HOTPLUG_B_IN 3 /* device mode: disconnected from host */ #define USB_HOTPLUG_B_OUT 4 /* device mode: connected to charger */ #define USB_HOTPLUG_C_IN 5 /* device mode: disconnected from charger */ #define USB_HOTPLUG_C_OUT 6 /* usb hotplug suspend: stop detection */ #define USB_HOTPLUG_SUSPEND 7 static uint8_t usb_hotplug_state; #if CONFIG_LOG static char *state_string[] = { "undefined", "b_idle", "b_srp_init", "b_peripheral", "b_wait_acon", "b_host", "a_idle", "a_wait_vrise", "a_wait_bcon", "a_host", "a_suspend", "a_peripheral", "a_wait_vfall", "a_vbus_err" }; #endif static uint8_t otg_state; #ifdef CONFIG_USB_DEVICE /* distinguish pc/charger: nearly 10s */ #define USB_CONNECT_COUNT_MAX (10000/ CONFIG_MONITOR_PERIOD) static uint16_t usb_connect_count; /* optimize: if no_plugin */ #define KEEP_IN_B_IDLE_RETRY 10 static uint8_t keep_in_b_idle; /* doing b_peripheral exit process */ static uint8_t otg_b_peripheral_exiting; #endif /* CONFIG_USB_DEVICE */ #ifdef CONFIG_USB_HOST /* timeout: nearly 1s */ #define OTG_A_WAIT_BCON_MAX (K_SECONDS(1) / CONFIG_MONITOR_PERIOD) static uint8_t otg_a_wait_bcon_count; /* timeout: nearly 4s */ #define OTG_A_IDLE_MAX (K_SECONDS(4) / CONFIG_MONITOR_PERIOD) static uint8_t otg_a_idle_count; /* doing a_host exit process */ static uint8_t otg_a_host_exiting; #endif /* CONFIG_USB_HOST */ #ifdef CONFIG_USB_HOTPLUG_THREAD_ENABLED #define STACK_SZ CONFIG_USB_HOTPLUG_STACKSIZE #define THREAD_PRIO CONFIG_USB_HOTPLUG_PRIORITY static uint8_t usb_hotplug_stack[CONFIG_USB_HOTPLUG_STACKSIZE]; #ifdef CONFIG_USB_HOST static void host_scan_thread(void *p1, void *p2, void *p3) { ARG_UNUSED(p1); ARG_UNUSED(p2); ARG_UNUSED(p3); usbh_scan_device(); } static inline int usb_thread_init_host_scan(void) { k_tid_t tid; usbh_prepare_scan(); /* Start a thread to offload USB scan/enumeration */ tid = os_thread_create(usb_hotplug_stack, STACK_SZ, host_scan_thread, NULL, NULL, NULL, THREAD_PRIO, 0, 0); os_thread_name_set(tid, "host_scan"); return 0; } static inline int usb_thread_exit_host_scan(void) { return usbh_disconnect(); } #endif /* CONFIG_USB_HOST */ #ifdef CONFIG_USB_MASS_STORAGE_SHARE_THREAD int usb_mass_storage_start(void) { k_tid_t tid; SYS_LOG_INF("shared"); tid = os_thread_create(usb_hotplug_stack, STACK_SZ, usb_mass_storage_thread, NULL, NULL, NULL, CONFIG_MASS_STORAGE_PRIORITY, 0, 0); os_thread_name_set(tid, "mass_storage"); return 0; } #endif /* CONFIG_USB_MASS_STORAGE_SHARE_THREAD */ #endif /* CONFIG_USB_HOTPLUG_THREAD_ENABLED */ static int usb_hotplug_dpdm_switch(bool host_mode) { #ifdef BOARD_USB_SWITCH_GPIO_NAME static struct device *usb_switch_gpio_dev; static uint8_t usb_switch_gpio_value; uint32_t value; int ret; if (usb_switch_gpio_dev == NULL) { usb_switch_gpio_dev = device_get_binding(BOARD_USB_SWITCH_GPIO_NAME); gpio_pin_configure(usb_switch_gpio_dev, BOARD_USB_SWITCH_EN_GPIO, GPIO_DIR_OUT); } value = BOARD_USB_SWITCH_HOST_EN_GPIO_VALUE; if (!host_mode) { value = !value; } /* already */ if (value == usb_switch_gpio_value) { return 0; } usb_switch_gpio_value = value; ret = gpio_pin_write(usb_switch_gpio_dev, BOARD_USB_SWITCH_EN_GPIO, value); if (ret) { return ret; } #endif return 0; } static void usb_hotplug_status_cb(enum usb_dc_status_code status, uint8_t *param) { /* Check the USB status and do needed action if required */ switch (status) { case USB_DC_HIGHSPEED: SYS_LOG_DBG("USB HS detected"); usb_disable(); break; case USB_DC_SOF: SYS_LOG_DBG("USB SOF detected"); usb_disable(); break; default: break; } } u8_t usb_hotplug_get_otgstate(void) { return otg_state; } static const struct usb_cfg_data usb_hotplug_config = { .cb_usb_status = usb_hotplug_status_cb, }; static inline int usb_hotplug_detect_device(void) { int ret; ret = usb_enable((struct usb_cfg_data *)&usb_hotplug_config); if (ret < 0) { SYS_LOG_ERR("enable"); return ret; } return 0; } #ifdef CONFIG_USB_HOST static inline int usb_hotplug_host_plugin(void) { usb_thread_init_host_scan(); return 0; } static inline int usb_hotplug_host_plugout(void) { usb_hotplug_state = USB_HOTPLUG_A_OUT; return usb_thread_exit_host_scan(); } #ifdef CONFIG_USB_HOST_STORAGE extern void usb_stor_set_access(bool accessed); extern bool usb_host_storage_enabled(void); int usb_hotplug_disk_accessed(bool access) { usb_stor_set_access(access); #ifdef CONFIG_FS_MANAGER if (access) { fs_manager_disk_init("USB:"); } else { fs_manager_disk_uninit("USB:"); } #endif return 0; } static inline int usb_hotplug_host_storage_plugin(void) { usb_hotplug_state = USB_HOTPLUG_A_IN; return 0; } #endif /* CONFIG_USB_HOST_STORAGE */ #endif /* CONFIG_USB_HOST */ #ifdef CONFIG_USB_DEVICE static inline int usb_hotplug_device_plugin(void) { usb_hotplug_state = USB_HOTPLUG_B_IN; return 0; } static inline int usb_hotplug_device_plugout(void) { usb_hotplug_state = USB_HOTPLUG_B_OUT; return 0; } static inline int usb_hotplug_charger_plugin(void) { usb_hotplug_state = USB_HOTPLUG_C_IN; return 0; } static inline int usb_hotplug_charger_plugout(void) { usb_hotplug_state = USB_HOTPLUG_C_OUT; return 0; } #endif /* CONFIG_USB_DEVICE */ static inline uint8_t usb_hotplug_get_vbus(void) { #ifdef CONFIG_USB_DEVICE return usb_phy_get_vbus(); #else return USB_VBUS_LOW; /* Host-only mode: never enter b_idle */ #endif } static inline void usb_hotplug_update_state(void) { uint8_t vbus = usb_hotplug_get_vbus(); #ifdef CONFIG_USB_DEVICE bool dc_attached; uint8_t i; #endif #ifdef CONFIG_USB_DEVICE #ifdef CONFIG_PM_DEVICE if (system_is_ready()) { if (vbus == USB_VBUS_HIGH) { if (!usb_wake_unlock) { sys_wake_lock_ext(PARTIAL_WAKE_LOCK,USB_WAKE_LOCK_USER); usb_wake_unlock = true; } } else { if (usb_wake_unlock) { sys_wake_unlock_ext(PARTIAL_WAKE_LOCK,USB_WAKE_LOCK_USER); usb_wake_unlock = false; } } } #endif #endif switch (otg_state) { #ifdef CONFIG_USB_DEVICE case OTG_STATE_B_IDLE: if (vbus == USB_VBUS_LOW) { keep_in_b_idle = 0; otg_state = OTG_STATE_A_IDLE; #ifndef CONFIG_USB_HOST usb_phy_reset(); #endif usb_hotplug_charger_plugout(); break; } if (keep_in_b_idle >= KEEP_IN_B_IDLE_RETRY) { break; } usb_phy_reset(); usb_phy_enter_b_idle(); k_sleep(K_MSEC(3)); /* necessary */ repeat: dc_attached = usb_phy_dc_attached(); for (i = 0; i < 4; i++) { k_busy_wait(1000); if (dc_attached != usb_phy_dc_attached()) { SYS_LOG_INF("dc_attached: %d, i: %d", dc_attached, i); goto repeat; } } if (dc_attached) { keep_in_b_idle = 0; otg_state = OTG_STATE_B_WAIT_ACON; usb_hotplug_detect_device(); } else { if (++keep_in_b_idle >= KEEP_IN_B_IDLE_RETRY) { SYS_LOG_INF("b_idle"); } usb_phy_enter_a_idle(); usb_hotplug_charger_plugin(); } break; case OTG_STATE_B_WAIT_ACON: if (vbus == USB_VBUS_LOW) { usb_connect_count = 0; otg_state = OTG_STATE_A_IDLE; /* exit device mode */ usb_disable(); usb_hotplug_charger_plugout(); break; } if (usb_phy_dc_connected()) { usb_connect_count = 0; otg_state = OTG_STATE_B_PERIPHERAL; /* enter device mode */ usb_hotplug_device_plugin(); break; } if (++usb_connect_count >= USB_CONNECT_COUNT_MAX) { SYS_LOG_DBG("connected to charger"); keep_in_b_idle = KEEP_IN_B_IDLE_RETRY; usb_connect_count = 0; otg_state = OTG_STATE_B_IDLE; /* exit device mode */ usb_disable(); usb_hotplug_charger_plugin(); break; } break; case OTG_STATE_B_PERIPHERAL: if (otg_b_peripheral_exiting) { /* wait for exit done */ if (usb_phy_dc_detached()) { otg_state = OTG_STATE_A_IDLE; otg_b_peripheral_exiting = 0; } break; } if (vbus == USB_VBUS_LOW) { /* exit device mode */ usb_hotplug_device_plugout(); otg_b_peripheral_exiting = 1; /* wait for exit done */ if (usb_phy_dc_detached()) { otg_state = OTG_STATE_A_IDLE; otg_b_peripheral_exiting = 0; } break; } if (usb_device_unconfigured()) { usb_hotplug_device_plugout(); otg_b_peripheral_exiting = 1; SYS_LOG_INF("usb unconfigured"); break; } #ifdef CONFIG_USB_MASS_STORAGE if (usb_mass_storage_ejected()) { usb_hotplug_device_plugout(); otg_b_peripheral_exiting = 1; SYS_LOG_INF("usb msc ejected"); break; } #endif break; #endif /* CONFIG_USB_DEVICE */ #ifdef CONFIG_USB_HOST case OTG_STATE_A_IDLE: if (vbus == USB_VBUS_HIGH) { otg_a_idle_count = 0; otg_state = OTG_STATE_B_IDLE; usbh_vbus_set(false); break; } /* Enable Vbus */ usbh_vbus_set(true); if (usb_phy_hc_attached()) { otg_a_idle_count = 0; otg_state = OTG_STATE_A_WAIT_BCON; usb_phy_enter_a_wait_bcon(); break; } /* reset Vbus */ if (++otg_a_idle_count >= OTG_A_IDLE_MAX) { otg_a_idle_count = 0; usbh_vbus_set(false); break; } break; case OTG_STATE_A_WAIT_BCON: if (vbus == USB_VBUS_HIGH) { otg_a_wait_bcon_count = 0; otg_state = OTG_STATE_B_IDLE; /* exit host mode */ usbh_vbus_set(false); break; } if (usb_phy_hc_connected()) { otg_a_wait_bcon_count = 0; otg_state = OTG_STATE_A_HOST; /* enter host mode */ usb_hotplug_host_plugin(); break; } if (++otg_a_wait_bcon_count >= OTG_A_WAIT_BCON_MAX) { otg_a_wait_bcon_count = 0; otg_state = OTG_STATE_A_IDLE; usbh_vbus_set(false); break; } break; case OTG_STATE_A_HOST: if (vbus == USB_VBUS_HIGH) { if (otg_a_host_exiting) { if (!usb_hotplug_host_plugout()) { otg_a_host_exiting = 0; otg_state = OTG_STATE_B_IDLE; } break; } /* exit host mode */ usbh_vbus_set(false); if (usb_hotplug_host_plugout()) { otg_a_host_exiting = 1; } else { otg_state = OTG_STATE_B_IDLE; } break; } /* handle disconnect and connnect quickly */ if (otg_a_host_exiting) { if (!usb_hotplug_host_plugout()) { otg_a_host_exiting = 0; otg_state = OTG_STATE_A_IDLE; } break; } if (usb_phy_hc_disconnected()) { /* exit host mode */ usbh_vbus_set(false); if (usb_hotplug_host_plugout()) { otg_a_host_exiting = 1; } else { otg_state = OTG_STATE_A_IDLE; } break; } #ifdef CONFIG_USB_HOST_STORAGE if (usb_host_storage_enabled()) { usb_hotplug_host_storage_plugin(); break; } #endif break; #else /* CONFIG_USB_HOST */ case OTG_STATE_A_IDLE: if (vbus == USB_VBUS_HIGH) { otg_state = OTG_STATE_B_IDLE; break; } break; #endif default: break; } } static int usb_hotplug_loop(void) { enum usb_otg_state old_state = otg_state; usb_hotplug_update_state(); if (otg_state == old_state) { return 0; } SYS_LOG_INF("%s -> %s", state_string[old_state], state_string[otg_state]); switch (otg_state) { case OTG_STATE_B_IDLE: usb_hotplug_dpdm_switch(false); break; case OTG_STATE_A_IDLE: usb_hotplug_dpdm_switch(true); #ifdef CONFIG_USB_HOST usb_phy_reset(); usb_phy_enter_a_idle(); #endif break; default: break; } return 0; } static inline int usb_hotplug_init(void) { uint8_t vbus = usb_hotplug_get_vbus(); #ifdef CONFIG_USB_DEVICE /* suspend comes before hotplug and keep_in_b_idle maybe not reset */ keep_in_b_idle = 0; #endif if (vbus == USB_VBUS_HIGH) { usb_hotplug_dpdm_switch(false); #ifdef CONFIG_USB_HOST usbh_vbus_set(false); #endif otg_state = OTG_STATE_B_IDLE; } else { usb_hotplug_dpdm_switch(true); #ifdef CONFIG_USB_HOST usb_phy_enter_a_idle(); #endif otg_state = OTG_STATE_A_IDLE; } SYS_LOG_INF("state %s", state_string[otg_state]); /* For a_idle, detect later */ if (otg_state == OTG_STATE_B_IDLE) { usb_hotplug_loop(); } return 0; } /* * Check if USB is attached to charger or host */ bool usb_hotplug_device_mode(void) { if ((otg_state == OTG_STATE_B_WAIT_ACON) || (otg_state == OTG_STATE_B_PERIPHERAL)) { return true; } return false; } int usb_hotplug_suspend(void) { usb_hotplug_state = USB_HOTPLUG_SUSPEND; #ifdef CONFIG_USB_HOST usbh_vbus_set(false); if (otg_state == OTG_STATE_A_HOST) { /* should be fast */ while (usbh_disconnect()) { k_sleep(K_MSEC(10)); SYS_LOG_INF(""); } } #endif usb_phy_exit(); return 0; } int usb_hotplug_resume(void) { usb_hotplug_state = HOTPLUG_NONE; #ifdef CONFIG_USB_HOST if (otg_state == OTG_STATE_A_HOST) { /* broadcast when resume */ struct app_msg msg = {0}; msg.type = MSG_HOTPLUG_EVENT; msg.cmd = HOTPLUG_USB_HOST; msg.value = HOTPLUG_OUT; SYS_LOG_INF("host"); return send_async_msg("main", &msg); } #endif return usb_hotplug_init(); } #ifdef CONFIG_USB_HOST /* check and enumerate USB device directly */ int usb_hotplug_host_check(void) { int timeout = 100; /* 1s */ if (otg_state != OTG_STATE_A_IDLE) { return -EINVAL; } do { usb_hotplug_loop(); switch (otg_state) { case OTG_STATE_A_WAIT_BCON: goto attached; case OTG_STATE_A_IDLE: break; default: goto exit; } k_sleep(K_MSEC(10)); } while (--timeout); /* timeout */ if (timeout == 0) { return -ENODEV; } attached: timeout = 10; /* 1s */ do { usb_hotplug_loop(); switch (otg_state) { case OTG_STATE_A_HOST: usbh_prepare_scan(); usbh_scan_device(); /* fall through */ default: goto exit; case OTG_STATE_A_WAIT_BCON: break; } /* should be same as CONFIG_MONITOR_PERIOD */ k_sleep(K_MSEC(100)); } while (--timeout); /* timeout */ if (timeout == 0) { return -ENODEV; } exit: return 0; } #endif static int hotplug_usb_get_type(void) { switch (usb_hotplug_state) { case USB_HOTPLUG_A_IN: case USB_HOTPLUG_A_OUT: return HOTPLUG_USB_HOST; case USB_HOTPLUG_B_IN: case USB_HOTPLUG_B_OUT: return HOTPLUG_USB_DEVICE; } /* never */ return 0; } static int hotplug_usb_detect(void) { uint8_t old_state = usb_hotplug_state; if (usb_hotplug_state == USB_HOTPLUG_SUSPEND) { return HOTPLUG_NONE; } usb_hotplug_loop(); if (usb_hotplug_state == old_state) { return HOTPLUG_NONE; } switch (usb_hotplug_state) { case USB_HOTPLUG_A_IN: case USB_HOTPLUG_B_IN: return HOTPLUG_IN; case USB_HOTPLUG_A_OUT: case USB_HOTPLUG_B_OUT: return HOTPLUG_OUT; } return HOTPLUG_NONE; } static int hotplug_usb_process(int device_state) { int res = -1; SYS_LOG_INF("%d", usb_hotplug_state); switch (usb_hotplug_state) { case USB_HOTPLUG_A_IN: #ifdef CONFIG_FS_MANAGER res = fs_manager_disk_init("USB:"); #endif break; case USB_HOTPLUG_A_OUT: #ifdef CONFIG_FS_MANAGER res = fs_manager_disk_uninit("USB:"); #endif break; case USB_HOTPLUG_B_IN: case USB_HOTPLUG_B_OUT: res = 0; break; default: break; } return res; } #ifdef CONFIG_USB_DEVICE static int hotplug_usb_device_get_state(void) { if (otg_b_peripheral_exiting == 1) { return HOTPLUG_OUT; } if (otg_state == OTG_STATE_B_PERIPHERAL) { return HOTPLUG_IN; } return HOTPLUG_OUT; } static const struct hotplug_device_t hotplug_usb_device = { .type = HOTPLUG_USB_DEVICE, .get_state = hotplug_usb_device_get_state, .get_type = hotplug_usb_get_type, .hotplug_detect = hotplug_usb_detect, .fs_process = hotplug_usb_process, }; #endif /* CONFIG_USB_DEVICE */ #ifdef CONFIG_USB_HOST static int hotplug_usb_host_get_state(void) { if (otg_state == OTG_STATE_A_HOST) { return HOTPLUG_IN; } return HOTPLUG_OUT; } static int __unused hotplug_usb_host_detect(void) { return HOTPLUG_NONE; } static const struct hotplug_device_t hotplug_usb_host = { .type = HOTPLUG_USB_HOST, .get_state = hotplug_usb_host_get_state, #ifndef CONFIG_USB .get_type = hotplug_usb_get_type, .hotplug_detect = hotplug_usb_detect, .fs_process = hotplug_usb_process, #else .hotplug_detect = hotplug_usb_host_detect, #endif }; #endif /* CONFIG_USB_HOST */ int hotplug_usb_init(void) { #ifdef CONFIG_USB_DEVICE hotplug_device_register(&hotplug_usb_device); #endif #ifdef CONFIG_USB_HOST hotplug_device_register(&hotplug_usb_host); #endif return 0; } #ifdef CONFIG_USB_HOTPLUG static struct k_timer usb_hotplug_timer; static void usb_hotplug_expiry_fn(struct k_timer *timer) { switch (otg_state) { case OTG_STATE_B_WAIT_ACON: case OTG_STATE_B_PERIPHERAL: if (!usb_phy_get_vbus() && !usb_phy_dc_detached()) { usb_phy_dc_disconnect(); } break; default: break; } } #endif /* CONFIG_USB_HOTPLUG */ int usb_hotplug_pre_init(const struct device *dev) { ARG_UNUSED(dev); #ifdef CONFIG_USB_HOTPLUG k_timer_init(&usb_hotplug_timer, usb_hotplug_expiry_fn, NULL); k_timer_start(&usb_hotplug_timer, K_SECONDS(1), K_MSEC(400)); #endif usb_phy_init(); #ifdef CONFIG_USB_HOST /* turn off Vbus by default */ usbh_vbus_set(false); #endif usb_hotplug_init(); return 0; } #define USB_HOTPLUG_PRE_INIT_PRIORITY CONFIG_APPLICATION_INIT_PRIORITY SYS_INIT(usb_hotplug_pre_init, POST_KERNEL, USB_HOTPLUG_PRE_INIT_PRIORITY);