/* * Copyright (C) 2011 Samsung Electronics Co. Ltd. * Inchul Im * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #define pr_fmt(fmt) "usb_notifier: " fmt #include #include #ifdef CONFIG_OF #include #include #endif #if IS_ENABLED(CONFIG_IFCONN_NOTIFIER) #include #include #endif #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) #include #endif #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) || IS_ENABLED(CONFIG_IFCONN_NOTIFIER) #include "muic.h" #include "muic_notifier.h" #endif #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) #include #endif #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) #include #endif #include "sec_battery_common.h" #include "usb_notifier.h" #include "usb_notify.h" #include #include #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) #include #define OTG_DISABLED_DELAY_TIME (300000*HZ/1000) /*5 min*/ #endif struct usb_notifier_platform_data { #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) || IS_ENABLED(CONFIG_IFCONN_NOTIFIER) struct notifier_block ccic_usb_nb; int is_host; #endif #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) || IS_ENABLED(CONFIG_IFCONN_NOTIFIER) struct notifier_block muic_usb_nb; #endif #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) || IS_ENABLED(CONFIG_IFCONN_NOTIFIER) struct notifier_block vbus_nb; #endif int gpio_redriver_en; int can_disable_usb; #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) struct delayed_work usb_ldo_work; #define USB_LDOCONTROL_EXYNOS8895 1 /* legacy */ #define USB_LDOCONTROL_EXYNOS9810 2 #define USB_LDOCONTROL_EXYNOS9820 3 #define USB_LDOCONTROL_EXYNOS9830 4 unsigned int usb_ldocontrol; unsigned int usb_ldo_onoff; bool usb_ldo_off_working; const char *hs_vdd; const char *ss_vdd; const char *dp_vdd; #endif #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) struct delayed_work usbid_work; struct timer_list otg_disabled_timer; struct wakeup_source *switch_wakelock; #endif int host_wake_lock_enable; int device_wake_lock_enable; }; #ifdef CONFIG_OF static void of_get_usb_redriver_dt(struct device_node *np, struct usb_notifier_platform_data *pdata) { int gpio = 0; gpio = of_get_named_gpio(np, "gpios_redriver_en", 0); if (!gpio_is_valid(gpio)) { pdata->gpio_redriver_en = -1; pr_err("%s: usb30_redriver_en: Invalied gpio pins\n", __func__); } else pdata->gpio_redriver_en = gpio; pr_info("%s, gpios_redriver_en %d\n", __func__, gpio); pdata->can_disable_usb = !(of_property_read_bool(np, "samsung,unsupport-disable-usb")); pr_info("%s, can_disable_usb %d\n", __func__, pdata->can_disable_usb); #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) if (of_property_read_string(np, "hs-regulator", &pdata->hs_vdd) < 0) { pr_err("%s - get hs_vdd error\n", __func__); pdata->hs_vdd = NULL; } if (of_property_read_string(np, "ss-regulator", &pdata->ss_vdd) < 0) { pr_err("%s - get ss_vdd error\n", __func__); pdata->ss_vdd = NULL; } if (of_property_read_string(np, "dp-regulator", &pdata->dp_vdd) < 0) { pr_err("%s - get dp_vdd error\n", __func__); pdata->dp_vdd = NULL; } if (of_property_read_u32(np, "usb-ldocontrol", &pdata->usb_ldocontrol)) pdata->usb_ldocontrol = 0; pr_info("%s, usb_ldocontrol %d\n", __func__, pdata->usb_ldocontrol); #endif pr_info("%s, host_wake_lock_enable %d ,device_wake_lock_enable %d\n", __func__, pdata->host_wake_lock_enable, pdata->device_wake_lock_enable); } static int of_usb_notifier_dt(struct device *dev, struct usb_notifier_platform_data *pdata) { struct device_node *np = dev->of_node; if (!np) return -EINVAL; of_get_usb_redriver_dt(np, pdata); return 0; } #endif static struct device_node *exynos_udc_parse_dt(void) { struct platform_device *pdev = NULL; struct device *dev = NULL; struct device_node *np = NULL; /** * For previous chips such as Exynos7420 and Exynos7890 */ np = of_find_compatible_node(NULL, NULL, "samsung,exynos5-dwusb3"); if (np) goto find; np = of_find_compatible_node(NULL, NULL, "samsung,usb-notifier"); if (!np) { pr_err("%s: failed to get the usb-notifier device node\n", __func__); goto err; } pdev = of_find_device_by_node(np); if (!pdev) { pr_err("%s: failed to get platform_device\n", __func__); goto err; } dev = &pdev->dev; np = of_parse_phandle(dev->of_node, "udc", 0); if (!np) { pr_err("%s: device is not available\n", __func__); goto err; } find: return np; err: return NULL; } #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) static void usb_hs_regulator_onoff(struct usb_notifier_platform_data *pdata, unsigned int onoff) { struct regulator *avdd33_usb; int ret; if (!pdata->hs_vdd) { pr_err("%s hs_vdd is null\n", __func__); return; } avdd33_usb = regulator_get(NULL, pdata->hs_vdd); if (IS_ERR(avdd33_usb) || avdd33_usb == NULL) { pr_err("%s - avdd33_usb regulator_get fail\n", __func__); return; } if (onoff) { ret = regulator_enable(avdd33_usb); if (ret) { pr_err("%s - enable avdd33_usb ldo enable failed, ret=%d\n", __func__, ret); } } else { ret = regulator_disable(avdd33_usb); if (ret) { pr_err("%s - enable avdd33_usb ldo disable failed, ret=%d\n", __func__, ret); } } regulator_put(avdd33_usb); } static void usb_ss_regulator_onoff(struct usb_notifier_platform_data *pdata, bool onoff) { struct regulator *vdd085_usb; int ret; if (!pdata->ss_vdd) { pr_err("%s ss_vdd is null\n", __func__); return; } vdd085_usb = regulator_get(NULL, pdata->ss_vdd); if (IS_ERR(vdd085_usb) || vdd085_usb == NULL) { pr_err("%s - vdd085_usb regulator_get fail\n", __func__); return; } if (onoff) { ret = regulator_enable(vdd085_usb); if (ret) { pr_err("%s - enable vdd085_usb ldo enable failed, ret=%d\n", __func__, ret); } } else { ret = regulator_disable(vdd085_usb); if (ret) { pr_err("%s - enable vdd085_usb ldo disable failed, ret=%d\n", __func__, ret); } } regulator_put(vdd085_usb); } static void usb_dp_regulator_onoff(struct usb_notifier_platform_data *pdata, unsigned int onoff) { struct regulator *vdd3p3_dp; int ret; if (!pdata->dp_vdd) { pr_err("%s dp_vdd is null\n", __func__); return; } vdd3p3_dp = regulator_get(NULL, pdata->dp_vdd); if (IS_ERR(vdd3p3_dp) || vdd3p3_dp == NULL) { pr_err("%s - dp_3p3 regulator_get fail\n", __func__); return; } if (onoff) { ret = regulator_enable(vdd3p3_dp); if (ret) { pr_err("%s - enable dp_3p3 ldo enable failed, ret=%d\n", __func__, ret); } } else { ret = regulator_disable(vdd3p3_dp); if (ret) { pr_err("%s - enable dp_3p3 ldo disable failed, ret=%d\n", __func__, ret); } } regulator_put(vdd3p3_dp); } extern int exynos_usbdrd_ldo_manual_control(bool on); static void usb_regulator_onoff(void *data, unsigned int onoff) { struct usb_notifier_platform_data *pdata = (struct usb_notifier_platform_data *)(data); pr_info("usb: %s - Turn %s (ldocontrol=%d, usb_ldo_off_working=%d)\n", __func__, onoff ? "on":"off", pdata->usb_ldocontrol, pdata->usb_ldo_off_working); if (pdata->usb_ldocontrol == USB_LDOCONTROL_EXYNOS8895) { if (onoff) { if (!pdata->usb_ldo_onoff) { if (pdata->usb_ldo_off_working) { cancel_delayed_work_sync(&pdata->usb_ldo_work); pdata->usb_ldo_off_working = false; } else { usb_ss_regulator_onoff(pdata, onoff); usb_dp_regulator_onoff(pdata, onoff); usb_hs_regulator_onoff(pdata, onoff); } } else { pr_err("%s already on\n", __func__); } } else { if (pdata->usb_ldo_onoff) { pdata->usb_ldo_off_working = true; schedule_delayed_work(&pdata->usb_ldo_work, msecs_to_jiffies(2000)); } else { pr_err("%s already off\n", __func__); } } pdata->usb_ldo_onoff = onoff; } else if (pdata->usb_ldocontrol >= USB_LDOCONTROL_EXYNOS9820) { exynos_usbdrd_ldo_manual_control(onoff); } else { pr_err("%s: can't control\n", __func__); } } static void usb_ldo_off_control(struct work_struct *work) { struct usb_notifier_platform_data *pdata = container_of(work, struct usb_notifier_platform_data, usb_ldo_work.work); pr_info("usb: %s - working=%d\n", __func__, pdata->usb_ldo_off_working); if (pdata->usb_ldo_off_working) { pdata->usb_ldo_off_working = false; usb_hs_regulator_onoff(pdata, false); usb_dp_regulator_onoff(pdata, false); usb_ss_regulator_onoff(pdata, false); } } #endif static void check_usb_vbus_state(int state) { struct device_node *np = NULL; struct platform_device *pdev = NULL; int ret = 0; pr_info("%s vbus state = %d\n", __func__, state); np = exynos_udc_parse_dt(); if (np) { pdev = of_find_device_by_node(np); of_node_put(np); if (pdev) { pr_info("%s: get the %s platform_device\n", __func__, pdev->name); ret = dwc3_exynos_vbus_event(&pdev->dev, state); if (!ret) goto end; else pr_info("%s: dwc3_exynos_vbus_event error return %d\n", __func__, ret); } } pr_err("%s: failed to get the platform_device\n", __func__); end: return; } static void check_usb_id_state(int state) { struct device_node *np = NULL; struct platform_device *pdev = NULL; int ret = 0; pr_info("%s id state = %d\n", __func__, state); np = exynos_udc_parse_dt(); if (np) { pdev = of_find_device_by_node(np); of_node_put(np); if (pdev) { pr_info("%s: get the %s platform_device\n", __func__, pdev->name); ret = dwc3_exynos_id_event(&pdev->dev, state); if (!ret) goto end; else pr_info("%s: dwc3_exynos_id_event error return %d\n", __func__, ret); } } pr_err("%s: failed to get the platform_device\n", __func__); end: return; } #if IS_ENABLED(CONFIG_IFCONN_NOTIFIER) static int ccic_usb_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { struct ifconn_notifier_template usb_status = *(struct ifconn_notifier_template *)data; struct otg_notify *o_notify = get_otg_notify(); struct usb_notifier_platform_data *pdata = container_of(nb, struct usb_notifier_platform_data, ccic_usb_nb); if(usb_status.dest != IFCONN_NOTIFY_USB) { return 0; } switch (usb_status.event){ case IFCONN_NOTIFY_EVENT_USB_ATTACH_DFP: pr_info("%s: Turn On Host(DFP)\n", __func__); #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) o_notify->muic_hw_switch = OTG_NOTI_ATTACH; #else send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 1); #endif pdata->is_host = 1; break; case IFCONN_NOTIFY_EVENT_USB_ATTACH_UFP: pr_info("%s: Turn On Device(UFP)\n", __func__); send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 1); if(is_blocked(o_notify, NOTIFY_BLOCK_TYPE_CLIENT)) return -EPERM; break; case IFCONN_NOTIFY_EVENT_DETACH: if(pdata->is_host) { pr_info("%s: Turn Off Host(DFP)\n", __func__); #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) o_notify->muic_hw_switch = OTG_NOTI_DETACH; #else send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 0); #endif pdata->is_host = 0; } else { pr_info("%s: Turn Off Device(UFP)\n", __func__); send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 0); } break; default: pr_info("%s: unsupported DRP type : %d.\n", __func__, usb_status.drp); break; } return 0; } static int muic_usb_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { struct ifconn_notifier_template *p_noti = (struct ifconn_notifier_template *)data; muic_attached_dev_t attached_dev = (muic_attached_dev_t)p_noti->event; struct otg_notify *o_notify = get_otg_notify(); pr_info("%s action=%lu, attached_dev=%d\n", __func__, action, attached_dev); switch (attached_dev) { #if IS_ENABLED(CONFIG_IFCONN_NOTIFIER) case ATTACHED_DEV_TA_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_CHARGER, 0); else if (action == IFCONN_NOTIFY_ID_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_CHARGER, 1); else pr_err("%s - ACTION Error!\n", __func__); break; #endif case ATTACHED_DEV_USB_MUIC: case ATTACHED_DEV_CDP_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: case ATTACHED_DEV_JIG_USB_OFF_MUIC: case ATTACHED_DEV_JIG_USB_ON_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 0); else if (action == IFCONN_NOTIFY_ID_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_OTG_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) { #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) o_notify->muic_hw_switch = OTG_NOTI_ATTACH; #else send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 0); #endif } else if (action == IFCONN_NOTIFY_ID_ATTACH) { #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) o_notify->muic_hw_switch = OTG_NOTI_DETACH; #else send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 1); #endif } else { pr_err("%s - ACTION Error!\n", __func__); } break; case ATTACHED_DEV_HMT_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_HMT, 0); else if (action == IFCONN_NOTIFY_ID_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_HMT, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) pr_info("%s - USB_HOST_TEST_DETACHED\n", __func__); else if (action == IFCONN_NOTIFY_ID_ATTACH) pr_info("%s - USB_HOST_TEST_ATTACHED\n", __func__); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_SMARTDOCK_TA_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_SMARTDOCK_TA, 0); else if (action == IFCONN_NOTIFY_ID_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_SMARTDOCK_TA, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_SMARTDOCK_USB_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) send_otg_notify (o_notify, NOTIFY_EVENT_SMARTDOCK_USB, 0); else if (action == IFCONN_NOTIFY_ID_ATTACH) send_otg_notify (o_notify, NOTIFY_EVENT_SMARTDOCK_USB, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_AUDIODOCK_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_AUDIODOCK, 0); else if (action == IFCONN_NOTIFY_ID_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_AUDIODOCK, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_UNIVERSAL_MMDOCK_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_MMDOCK, 0); else if (action == IFCONN_NOTIFY_ID_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_MMDOCK, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_USB_LANHUB_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_LANHUB, 0); else if (action == IFCONN_NOTIFY_ID_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_LANHUB, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_GAMEPAD_MUIC: if (action == IFCONN_NOTIFY_ID_DETACH) { send_otg_notify(o_notify, NOTIFY_EVENT_GAMEPAD, 0); } else if (action == IFCONN_NOTIFY_ID_ATTACH) { send_otg_notify(o_notify, NOTIFY_EVENT_GAMEPAD, 1); } else pr_err("%s - ACTION Error!\n", __func__); break; default: break; } return 0; } static int vbus_handle_notification(struct notifier_block *nb, unsigned long cmd, void *data) { struct ifconn_notifier_template *p_noti = (struct ifconn_notifier_template *)data; ifconn_notifier_vbus_t vbus_type = p_noti->vbus_type; struct otg_notify *o_notify; o_notify = get_otg_notify(); pr_info("%s cmd=%lu, vbus_type=%s\n", __func__, cmd, vbus_type == IFCONN_NOTIFY_VBUS_HIGH ? "HIGH" : "LOW"); if (vbus_type == IFCONN_NOTIFY_VBUS_HIGH) { send_otg_notify(o_notify, NOTIFY_EVENT_VBUSPOWER, 1); } else if (vbus_type == IFCONN_NOTIFY_VBUS_LOW) { send_otg_notify(o_notify, NOTIFY_EVENT_VBUSPOWER, 0); } return 0; } static int otg_accessory_power(bool enable) { #if !IS_ENABLED(CONFIG_CCIC_S2MM005) && !IS_ENABLED(CONFIG_CCIC_S2MU106) && !IS_ENABLED(CONFIG_CCIC_S2MU107) u8 on = (u8)!!enable; union power_supply_propval val; pr_info("otg accessory power = %d\n", on); val.intval = enable; psy_do_property("otg", set, POWER_SUPPLY_PROP_ONLINE, val); #endif return 0; } #else #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) static int ccic_usb_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { CC_NOTI_USB_STATUS_TYPEDEF usb_status = *(CC_NOTI_USB_STATUS_TYPEDEF *)data; struct otg_notify *o_notify = get_otg_notify(); struct usb_notifier_platform_data *pdata = container_of(nb, struct usb_notifier_platform_data, ccic_usb_nb); if (usb_status.dest != CCIC_NOTIFY_DEV_USB) return 0; switch (usb_status.drp) { case USB_STATUS_NOTIFY_ATTACH_DFP: pr_info("%s: Turn On Host(DFP)\n", __func__); #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) o_notify->muic_hw_switch = OTG_NOTI_ATTACH; #else send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 1); #endif pdata->is_host = 1; break; case USB_STATUS_NOTIFY_ATTACH_UFP: pr_info("%s: Turn On Device(UFP)\n", __func__); send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 1); if (is_blocked(o_notify, NOTIFY_BLOCK_TYPE_CLIENT)) return -EPERM; break; case USB_STATUS_NOTIFY_DETACH: if (pdata->is_host) { pr_info("%s: Turn Off Host(DFP)\n", __func__); #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) o_notify->muic_hw_switch = OTG_NOTI_DETACH; #else send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 0); #endif pdata->is_host = 0; } else { pr_info("%s: Turn Off Device(UFP)\n", __func__); send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 0); } break; default: pr_info("%s: unsupported DRP type : %d.\n", __func__, usb_status.drp); break; } return 0; } #endif #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) static int muic_usb_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { struct otg_notify *o_notify = get_otg_notify(); #ifdef CONFIG_CCIC_NOTIFIER CC_NOTI_ATTACH_TYPEDEF *p_noti = (CC_NOTI_ATTACH_TYPEDEF *)data; muic_attached_dev_t attached_dev = p_noti->cable_type; pr_info("%s action=%lu, attached_dev=%d\n", __func__, action, attached_dev); switch (attached_dev) { case ATTACHED_DEV_USB_MUIC: case ATTACHED_DEV_CDP_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_USB_CABLE, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_USB_CABLE, 1); else pr_err("%s - ACTION Error!\n", __func__); break; default: break; } #else muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data; pr_info("%s action=%lu, attached_dev=%d\n", __func__, action, attached_dev); switch (attached_dev) { case ATTACHED_DEV_USB_MUIC: case ATTACHED_DEV_CDP_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_USB_CABLE, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_USB_CABLE, 1); else ; case ATTACHED_DEV_JIG_USB_OFF_MUIC: case ATTACHED_DEV_JIG_USB_ON_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_OTG_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) { #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) o_notify->muic_hw_switch = OTG_NOTI_DETACH; #else send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 0); #endif } else if (action == MUIC_NOTIFY_CMD_ATTACH) { #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) o_notify->muic_hw_switch = OTG_NOTI_ATTACH; #else send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 1); #endif } else { pr_err("%s - ACTION Error!\n", __func__); } break; case ATTACHED_DEV_HMT_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_HMT, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_HMT, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) pr_info("%s - USB_HOST_TEST_DETACHED\n", __func__); else if (action == MUIC_NOTIFY_CMD_ATTACH) pr_info("%s - USB_HOST_TEST_ATTACHED\n", __func__); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_SMARTDOCK_TA_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_SMARTDOCK_TA, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_SMARTDOCK_TA, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_SMARTDOCK_USB_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify (o_notify, NOTIFY_EVENT_SMARTDOCK_USB, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify (o_notify, NOTIFY_EVENT_SMARTDOCK_USB, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_AUDIODOCK_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_AUDIODOCK, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_AUDIODOCK, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_UNIVERSAL_MMDOCK_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_MMDOCK, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_MMDOCK, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_USB_LANHUB_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_LANHUB, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_LANHUB, 1); else pr_err("%s - ACTION Error!\n", __func__); break; case ATTACHED_DEV_GAMEPAD_MUIC: if (action == MUIC_NOTIFY_CMD_DETACH) send_otg_notify(o_notify, NOTIFY_EVENT_GAMEPAD, 0); else if (action == MUIC_NOTIFY_CMD_ATTACH) send_otg_notify(o_notify, NOTIFY_EVENT_GAMEPAD, 1); else pr_err("%s - ACTION Error!\n", __func__); break; default: break; } #endif return 0; } #endif #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) static int vbus_handle_notification(struct notifier_block *nb, unsigned long cmd, void *data) { vbus_status_t vbus_type = *(vbus_status_t *)data; struct otg_notify *o_notify; o_notify = get_otg_notify(); pr_info("%s cmd=%lu, vbus_type=%s\n", __func__, cmd, vbus_type == STATUS_VBUS_HIGH ? "HIGH" : "LOW"); switch (vbus_type) { case STATUS_VBUS_HIGH: send_otg_notify(o_notify, NOTIFY_EVENT_VBUSPOWER, 1); break; case STATUS_VBUS_LOW: send_otg_notify(o_notify, NOTIFY_EVENT_VBUSPOWER, 0); break; default: break; } return 0; } #endif static int otg_accessory_power(bool enable) { u8 on = (u8)!!enable; union power_supply_propval val; pr_info("otg accessory power = %d\n", on); val.intval = enable; psy_do_property("otg", set, POWER_SUPPLY_PROP_ONLINE, val); return 0; } #endif static int set_online(int event, int state) { union power_supply_propval val; struct device_node *np_charger = NULL; char *charger_name; if (event == NOTIFY_EVENT_SMTD_EXT_CURRENT) pr_info("request smartdock charging current = %s\n", state ? "1000mA" : "1700mA"); else if (event == NOTIFY_EVENT_MMD_EXT_CURRENT) pr_info("request mmdock charging current = %s\n", state ? "900mA" : "1400mA"); np_charger = of_find_node_by_name(NULL, "battery"); if (!np_charger) { pr_err("%s: failed to get the battery device node\n", __func__); return 0; } if (!of_property_read_string(np_charger, "battery,charger_name", (char const **)&charger_name)) { pr_info("%s: charger_name = %s\n", __func__, charger_name); } else { pr_err("%s: failed to get the charger name\n", __func__); return 0; } /* for KNOX DT charging */ pr_info("Knox Desktop connection state = %s\n", state ? "Connected" : "Disconnected"); #if IS_ENABLED(CONFIG_IFCONN_NOTIFIER) if (state) val.intval = POWER_SUPPLY_TYPE_SMART_NOTG; else val.intval = POWER_SUPPLY_TYPE_BATTERY; #else if (state) val.intval = SEC_BATTERY_CABLE_SMART_NOTG; else val.intval = SEC_BATTERY_CABLE_NONE; #endif psy_do_property("battery", set, POWER_SUPPLY_PROP_ONLINE, val); return 0; } static int exynos_set_host(bool enable) { if (!enable) { pr_info("%s USB_HOST_DETACHED\n", __func__); #ifdef CONFIG_OF check_usb_id_state(1); #endif } else { pr_info("%s USB_HOST_ATTACHED\n", __func__); #ifdef CONFIG_OF check_usb_id_state(0); #endif } return 0; } #ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE extern void set_ncm_ready(bool ready); #endif static int exynos_set_peripheral(bool enable) { if (enable) { pr_info("%s usb attached\n", __func__); check_usb_vbus_state(1); } else { pr_info("%s usb detached\n", __func__); check_usb_vbus_state(0); #ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE set_ncm_ready(false); #endif } return 0; } #if IS_ENABLED(CONFIG_IFCONN_NOTIFIER) static int exynos_set_charger(bool enable) { if (enable) { pr_info("%s - revert usb notification\n", __func__); check_usb_vbus_state(0); } return 0; } #endif #if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) static int usb_set_chg_current(int state) { union power_supply_propval val; struct device_node *np_charger = NULL; char *charger_name; np_charger = of_find_node_by_name(NULL, "battery"); if (!np_charger) { pr_err("%s: failed to get the battery device node\n", __func__); return 0; } if (!of_property_read_string(np_charger, "battery,charger_name", (char const **)&charger_name)) { pr_info("%s: charger_name = %s\n", __func__, charger_name); } else { pr_err("%s: failed to get the charger name\n", __func__); return 0; } /* current setting */ pr_info("usb : charing current set = %d\n", state); switch (state) { case NOTIFY_USB_SUSPENDED: val.intval = USB_CURRENT_SUSPENDED; break; case NOTIFY_USB_UNCONFIGURED: val.intval = USB_CURRENT_UNCONFIGURED; break; case NOTIFY_USB_CONFIGURED: val.intval = USB_CURRENT_HIGH_SPEED; break; default: val.intval = USB_CURRENT_HIGH_SPEED; break; } psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_USB_CONFIGURE, val); return 0; } #endif static struct otg_notify dwc_lsi_notify = { .vbus_drive = otg_accessory_power, .set_host = exynos_set_host, .set_peripheral = exynos_set_peripheral, #if IS_ENABLED(CONFIG_IFCONN_NOTIFIER) .set_charger = exynos_set_charger, .charger_detect = 0, .usb_noti_done = 0, #endif .vbus_detect_gpio = -1, .is_host_wakelock = 0, .is_wakelock = 0, .booting_delay_sec = 12, #if !IS_ENABLED(CONFIG_CCIC_NOTIFIER) .auto_drive_vbus = NOTIFY_OP_POST, #endif .disable_control = 1, .device_check_sec = 3, .set_battcall = set_online, #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) .set_ldo_onoff = usb_regulator_onoff, #endif #if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) .set_chg_current = usb_set_chg_current, #endif .pre_peri_delay_us = 6, }; #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) static void otg_disabled_func(struct timer_list *t) { struct usb_notifier_platform_data *pdata = container_of(t, struct usb_notifier_platform_data, otg_disabled_timer); struct otg_notify *o_notify = get_otg_notify(); if (!o_notify->muic_hw_switch) { pr_info("%s: otg connection timeout!!\n", __func__); o_notify->muic_sw_switch = SW_SWITCH_OFF; } cancel_delayed_work(&pdata->usbid_work); #if 0 if (pdata->switch_wakelock->active) { pr_info("%s: wake unlock!!\n", __func__); __pm_relax(pdata->switch_wakelock); } #endif } static void otg_usbid_work(struct work_struct *w) { struct usb_notifier_platform_data *pdata = container_of(w, struct usb_notifier_platform_data, usbid_work.work); struct otg_notify *o_notify = get_otg_notify(); union power_supply_propval value; static int pre_notify; if (o_notify->muic_sw_switch == SW_SWITCH_OFF) { pr_info("%s: sw switch off\n", __func__); del_timer_sync(&pdata->otg_disabled_timer); exynos_set_host(0); value.intval = false; #if 0 if (pdata->switch_wakelock->active) { pr_info("%s: wake unlock!!\n", __func__); __pm_relax(pdata->switch_wakelock); } #endif return; } if (pre_notify != o_notify->muic_hw_switch) { if (o_notify->muic_hw_switch == OTG_NOTI_ATTACH) { pr_info("%s: hw switch attached\n", __func__); value.intval = true; exynos_set_host(1); } else { pr_info("%s: hw switch detached\n", __func__); mod_timer(&pdata->otg_disabled_timer, jiffies + OTG_DISABLED_DELAY_TIME); value.intval = false; exynos_set_host(0); } pr_info("%s: change otg_hw_switch state! [%08X->%08X]\n", __func__, pre_notify, o_notify->muic_hw_switch); pre_notify = o_notify->muic_hw_switch; } else { goto restart; } restart: schedule_delayed_work(&pdata->usbid_work, msecs_to_jiffies(500)); } static ssize_t otg_host_mode_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { char value[32]; struct otg_notify *o_notify = get_otg_notify(); struct usb_notifier_platform_data *pdata = dev->platform_data;; if (!o_notify) return -ENODEV; if (sscanf(buf, "%s", value) != 1) return -EINVAL; if (!strncmp(value, "enabled", 6)) { if(o_notify->muic_sw_switch == SW_SWITCH_ON){ pr_info("%s: otg switch already enabled!\n", __func__); return size; } pr_info("%s: otg switch enabled\n", __func__); o_notify->muic_sw_switch = SW_SWITCH_ON; __pm_stay_awake(pdata->switch_wakelock); timer_setup(&pdata->otg_disabled_timer, otg_disabled_func, 0); pdata->otg_disabled_timer.expires = jiffies + OTG_DISABLED_DELAY_TIME; //pdata->otg_disabled_timer.function = otg_disabled_func; add_timer(&pdata->otg_disabled_timer); } else if (!strncmp(value, "disabled", 6)) { pr_info("%s: otg switch disabled\n", __func__); o_notify->muic_sw_switch = SW_SWITCH_OFF; } else { return -EINVAL; } schedule_delayed_work(&pdata->usbid_work, msecs_to_jiffies(0)); return size; } static ssize_t otg_host_mode_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct otg_notify *o_notify = get_otg_notify(); ssize_t size; if (!o_notify) return -ENODEV; if (o_notify->muic_sw_switch) size = snprintf(buf, PAGE_SIZE, "%s", "enabled\n"); else size = snprintf(buf, PAGE_SIZE, "%s", "disabled\n"); return size; } static DEVICE_ATTR(host_mode, S_IRUGO | S_IWUSR, otg_host_mode_enable_show, otg_host_mode_enable_store); #endif static int usb_notifier_probe(struct platform_device *pdev) { struct usb_notifier_platform_data *pdata = NULL; int ret = 0; if (pdev->dev.of_node) { pdata = devm_kzalloc(&pdev->dev, sizeof(struct usb_notifier_platform_data), GFP_KERNEL); if (!pdata) { dev_err(&pdev->dev, "Failed to allocate memory\n"); return -ENOMEM; } ret = of_usb_notifier_dt(&pdev->dev, pdata); if (ret < 0) { dev_err(&pdev->dev, "Failed to get device of_node\n"); return ret; } pdev->dev.platform_data = pdata; } else pdata = pdev->dev.platform_data; dwc_lsi_notify.redriver_en_gpio = pdata->gpio_redriver_en; dwc_lsi_notify.disable_control = pdata->can_disable_usb; dwc_lsi_notify.is_host_wakelock = pdata->host_wake_lock_enable; dwc_lsi_notify.is_wakelock = pdata->device_wake_lock_enable; set_otg_notify(&dwc_lsi_notify); set_notify_data(&dwc_lsi_notify, pdata); #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) pdata->usb_ldo_onoff = 0; INIT_DELAYED_WORK(&pdata->usb_ldo_work, usb_ldo_off_control); #endif #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) pdata->is_host = 0; #ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER manager_notifier_register(&pdata->ccic_usb_nb, ccic_usb_handle_notification, MANAGER_NOTIFY_CCIC_USB); #else ccic_notifier_register(&pdata->ccic_usb_nb, ccic_usb_handle_notification, CCIC_NOTIFY_DEV_USB); #endif #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) muic_notifier_register(&pdata->muic_usb_nb, muic_usb_handle_notification, MUIC_NOTIFY_DEV_USB); #endif #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) vbus_notifier_register(&pdata->vbus_nb, vbus_handle_notification, VBUS_NOTIFY_DEV_USB); #endif #else #if IS_ENABLED(CONFIG_IFCONN_NOTIFIER) ifconn_notifier_register(&pdata->muic_usb_nb, muic_usb_handle_notification, IFCONN_NOTIFY_USB, IFCONN_NOTIFY_MUIC); ifconn_notifier_register(&pdata->ccic_usb_nb, ccic_usb_handle_notification, IFCONN_NOTIFY_USB, IFCONN_NOTIFY_CCIC); ifconn_notifier_register(&pdata->vbus_nb, vbus_handle_notification, IFCONN_NOTIFY_USB, IFCONN_NOTIFY_VBUS); #endif #endif #if IS_ENABLED(CONFIG_USB_OTG_SW_SWITCH) device_create_file(&pdev->dev, &dev_attr_host_mode); INIT_DELAYED_WORK(&pdata->usbid_work, otg_usbid_work); wakeup_source_register(&pdev->dev, "otg_sw_switch"); #endif dev_info(&pdev->dev, "usb notifier probe\n"); return 0; } static int usb_notifier_remove(struct platform_device *pdev) { #if IS_ENABLED(CONFIG_IFCONN_NOTIFIER) ifconn_notifier_unregister(IFCONN_NOTIFY_USB, IFCONN_NOTIFY_CCIC); ifconn_notifier_unregister(IFCONN_NOTIFY_USB, IFCONN_NOTIFY_MUIC); ifconn_notifier_unregister(IFCONN_NOTIFY_USB, IFCONN_NOTIFY_VBUS); #else #if IS_ENABLED(CONFIG_CCIC_NOTIFIER) #ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER manager_notifier_unregister(&pdata->ccic_usb_nb); #else ccic_notifier_unregister(&pdata->ccic_usb_nb); #endif #elif IS_ENABLED(CONFIG_MUIC_NOTIFIER) muic_notifier_unregister(&pdata->muic_usb_nb); #endif #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) vbus_notifier_unregister(&pdata->vbus_nb); #endif #endif return 0; } #ifdef CONFIG_OF static const struct of_device_id usb_notifier_dt_ids[] = { { .compatible = "samsung,usb-notifier", }, { }, }; MODULE_DEVICE_TABLE(of, usb_notifier_dt_ids); #endif static struct platform_driver usb_notifier_driver = { .probe = usb_notifier_probe, .remove = usb_notifier_remove, .driver = { .name = "usb_notifier", .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = of_match_ptr(usb_notifier_dt_ids), #endif }, }; static int __init usb_notifier_init(void) { return platform_driver_register(&usb_notifier_driver); } static void __exit usb_notifier_exit(void) { platform_driver_unregister(&usb_notifier_driver); } late_initcall(usb_notifier_init); module_exit(usb_notifier_exit); MODULE_AUTHOR("inchul.im "); MODULE_DESCRIPTION("USB notifier"); MODULE_LICENSE("GPL");