162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * USB GPIO Based Connection Detection Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2019 MediaTek Inc. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Some code borrowed from drivers/extcon/extcon-usb-gpio.c 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/device.h> 1362306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1462306a36Sopenharmony_ci#include <linux/interrupt.h> 1562306a36Sopenharmony_ci#include <linux/irq.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/of.h> 1862306a36Sopenharmony_ci#include <linux/pinctrl/consumer.h> 1962306a36Sopenharmony_ci#include <linux/platform_device.h> 2062306a36Sopenharmony_ci#include <linux/power_supply.h> 2162306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 2262306a36Sopenharmony_ci#include <linux/usb/role.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define USB_GPIO_DEB_MS 20 /* ms */ 2562306a36Sopenharmony_ci#define USB_GPIO_DEB_US ((USB_GPIO_DEB_MS) * 1000) /* us */ 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define USB_CONN_IRQF \ 2862306a36Sopenharmony_ci (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT) 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistruct usb_conn_info { 3162306a36Sopenharmony_ci struct device *dev; 3262306a36Sopenharmony_ci struct usb_role_switch *role_sw; 3362306a36Sopenharmony_ci enum usb_role last_role; 3462306a36Sopenharmony_ci struct regulator *vbus; 3562306a36Sopenharmony_ci struct delayed_work dw_det; 3662306a36Sopenharmony_ci unsigned long debounce_jiffies; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci struct gpio_desc *id_gpiod; 3962306a36Sopenharmony_ci struct gpio_desc *vbus_gpiod; 4062306a36Sopenharmony_ci int id_irq; 4162306a36Sopenharmony_ci int vbus_irq; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci struct power_supply_desc desc; 4462306a36Sopenharmony_ci struct power_supply *charger; 4562306a36Sopenharmony_ci bool initial_detection; 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/* 4962306a36Sopenharmony_ci * "DEVICE" = VBUS and "HOST" = !ID, so we have: 5062306a36Sopenharmony_ci * Both "DEVICE" and "HOST" can't be set as active at the same time 5162306a36Sopenharmony_ci * so if "HOST" is active (i.e. ID is 0) we keep "DEVICE" inactive 5262306a36Sopenharmony_ci * even if VBUS is on. 5362306a36Sopenharmony_ci * 5462306a36Sopenharmony_ci * Role | ID | VBUS 5562306a36Sopenharmony_ci * ------------------------------------ 5662306a36Sopenharmony_ci * [1] DEVICE | H | H 5762306a36Sopenharmony_ci * [2] NONE | H | L 5862306a36Sopenharmony_ci * [3] HOST | L | H 5962306a36Sopenharmony_ci * [4] HOST | L | L 6062306a36Sopenharmony_ci * 6162306a36Sopenharmony_ci * In case we have only one of these signals: 6262306a36Sopenharmony_ci * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1 6362306a36Sopenharmony_ci * - ID only - we want to distinguish between [1] and [4], so VBUS = ID 6462306a36Sopenharmony_ci */ 6562306a36Sopenharmony_cistatic void usb_conn_detect_cable(struct work_struct *work) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci struct usb_conn_info *info; 6862306a36Sopenharmony_ci enum usb_role role; 6962306a36Sopenharmony_ci int id, vbus, ret; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci info = container_of(to_delayed_work(work), 7262306a36Sopenharmony_ci struct usb_conn_info, dw_det); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci /* check ID and VBUS */ 7562306a36Sopenharmony_ci id = info->id_gpiod ? 7662306a36Sopenharmony_ci gpiod_get_value_cansleep(info->id_gpiod) : 1; 7762306a36Sopenharmony_ci vbus = info->vbus_gpiod ? 7862306a36Sopenharmony_ci gpiod_get_value_cansleep(info->vbus_gpiod) : id; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (!id) 8162306a36Sopenharmony_ci role = USB_ROLE_HOST; 8262306a36Sopenharmony_ci else if (vbus) 8362306a36Sopenharmony_ci role = USB_ROLE_DEVICE; 8462306a36Sopenharmony_ci else 8562306a36Sopenharmony_ci role = USB_ROLE_NONE; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci dev_dbg(info->dev, "role %s -> %s, gpios: id %d, vbus %d\n", 8862306a36Sopenharmony_ci usb_role_string(info->last_role), usb_role_string(role), id, vbus); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci if (!info->initial_detection && info->last_role == role) { 9162306a36Sopenharmony_ci dev_warn(info->dev, "repeated role: %s\n", usb_role_string(role)); 9262306a36Sopenharmony_ci return; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci info->initial_detection = false; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (info->last_role == USB_ROLE_HOST && info->vbus) 9862306a36Sopenharmony_ci regulator_disable(info->vbus); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci ret = usb_role_switch_set_role(info->role_sw, role); 10162306a36Sopenharmony_ci if (ret) 10262306a36Sopenharmony_ci dev_err(info->dev, "failed to set role: %d\n", ret); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (role == USB_ROLE_HOST && info->vbus) { 10562306a36Sopenharmony_ci ret = regulator_enable(info->vbus); 10662306a36Sopenharmony_ci if (ret) 10762306a36Sopenharmony_ci dev_err(info->dev, "enable vbus regulator failed\n"); 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci info->last_role = role; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (info->vbus) 11362306a36Sopenharmony_ci dev_dbg(info->dev, "vbus regulator is %s\n", 11462306a36Sopenharmony_ci regulator_is_enabled(info->vbus) ? "enabled" : "disabled"); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci power_supply_changed(info->charger); 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic void usb_conn_queue_dwork(struct usb_conn_info *info, 12062306a36Sopenharmony_ci unsigned long delay) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci queue_delayed_work(system_power_efficient_wq, &info->dw_det, delay); 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic irqreturn_t usb_conn_isr(int irq, void *dev_id) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci struct usb_conn_info *info = dev_id; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci usb_conn_queue_dwork(info, info->debounce_jiffies); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci return IRQ_HANDLED; 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic enum power_supply_property usb_charger_properties[] = { 13562306a36Sopenharmony_ci POWER_SUPPLY_PROP_ONLINE, 13662306a36Sopenharmony_ci}; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic int usb_charger_get_property(struct power_supply *psy, 13962306a36Sopenharmony_ci enum power_supply_property psp, 14062306a36Sopenharmony_ci union power_supply_propval *val) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct usb_conn_info *info = power_supply_get_drvdata(psy); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci switch (psp) { 14562306a36Sopenharmony_ci case POWER_SUPPLY_PROP_ONLINE: 14662306a36Sopenharmony_ci val->intval = info->last_role == USB_ROLE_DEVICE; 14762306a36Sopenharmony_ci break; 14862306a36Sopenharmony_ci default: 14962306a36Sopenharmony_ci return -EINVAL; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci return 0; 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic int usb_conn_psy_register(struct usb_conn_info *info) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci struct device *dev = info->dev; 15862306a36Sopenharmony_ci struct power_supply_desc *desc = &info->desc; 15962306a36Sopenharmony_ci struct power_supply_config cfg = { 16062306a36Sopenharmony_ci .of_node = dev->of_node, 16162306a36Sopenharmony_ci }; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci desc->name = "usb-charger"; 16462306a36Sopenharmony_ci desc->properties = usb_charger_properties; 16562306a36Sopenharmony_ci desc->num_properties = ARRAY_SIZE(usb_charger_properties); 16662306a36Sopenharmony_ci desc->get_property = usb_charger_get_property; 16762306a36Sopenharmony_ci desc->type = POWER_SUPPLY_TYPE_USB; 16862306a36Sopenharmony_ci cfg.drv_data = info; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci info->charger = devm_power_supply_register(dev, desc, &cfg); 17162306a36Sopenharmony_ci if (IS_ERR(info->charger)) 17262306a36Sopenharmony_ci dev_err(dev, "Unable to register charger\n"); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(info->charger); 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_cistatic int usb_conn_probe(struct platform_device *pdev) 17862306a36Sopenharmony_ci{ 17962306a36Sopenharmony_ci struct device *dev = &pdev->dev; 18062306a36Sopenharmony_ci struct usb_conn_info *info; 18162306a36Sopenharmony_ci int ret = 0; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); 18462306a36Sopenharmony_ci if (!info) 18562306a36Sopenharmony_ci return -ENOMEM; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci info->dev = dev; 18862306a36Sopenharmony_ci info->id_gpiod = devm_gpiod_get_optional(dev, "id", GPIOD_IN); 18962306a36Sopenharmony_ci if (IS_ERR(info->id_gpiod)) 19062306a36Sopenharmony_ci return PTR_ERR(info->id_gpiod); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci info->vbus_gpiod = devm_gpiod_get_optional(dev, "vbus", GPIOD_IN); 19362306a36Sopenharmony_ci if (IS_ERR(info->vbus_gpiod)) 19462306a36Sopenharmony_ci return PTR_ERR(info->vbus_gpiod); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (!info->id_gpiod && !info->vbus_gpiod) { 19762306a36Sopenharmony_ci dev_err(dev, "failed to get gpios\n"); 19862306a36Sopenharmony_ci return -ENODEV; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci if (info->id_gpiod) 20262306a36Sopenharmony_ci ret = gpiod_set_debounce(info->id_gpiod, USB_GPIO_DEB_US); 20362306a36Sopenharmony_ci if (!ret && info->vbus_gpiod) 20462306a36Sopenharmony_ci ret = gpiod_set_debounce(info->vbus_gpiod, USB_GPIO_DEB_US); 20562306a36Sopenharmony_ci if (ret < 0) 20662306a36Sopenharmony_ci info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEB_MS); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci INIT_DELAYED_WORK(&info->dw_det, usb_conn_detect_cable); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci info->vbus = devm_regulator_get_optional(dev, "vbus"); 21162306a36Sopenharmony_ci if (PTR_ERR(info->vbus) == -ENODEV) 21262306a36Sopenharmony_ci info->vbus = NULL; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci if (IS_ERR(info->vbus)) 21562306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(info->vbus), "failed to get vbus\n"); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci info->role_sw = usb_role_switch_get(dev); 21862306a36Sopenharmony_ci if (IS_ERR(info->role_sw)) 21962306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(info->role_sw), 22062306a36Sopenharmony_ci "failed to get role switch\n"); 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci ret = usb_conn_psy_register(info); 22362306a36Sopenharmony_ci if (ret) 22462306a36Sopenharmony_ci goto put_role_sw; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci if (info->id_gpiod) { 22762306a36Sopenharmony_ci info->id_irq = gpiod_to_irq(info->id_gpiod); 22862306a36Sopenharmony_ci if (info->id_irq < 0) { 22962306a36Sopenharmony_ci dev_err(dev, "failed to get ID IRQ\n"); 23062306a36Sopenharmony_ci ret = info->id_irq; 23162306a36Sopenharmony_ci goto put_role_sw; 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci ret = devm_request_threaded_irq(dev, info->id_irq, NULL, 23562306a36Sopenharmony_ci usb_conn_isr, USB_CONN_IRQF, 23662306a36Sopenharmony_ci pdev->name, info); 23762306a36Sopenharmony_ci if (ret < 0) { 23862306a36Sopenharmony_ci dev_err(dev, "failed to request ID IRQ\n"); 23962306a36Sopenharmony_ci goto put_role_sw; 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (info->vbus_gpiod) { 24462306a36Sopenharmony_ci info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); 24562306a36Sopenharmony_ci if (info->vbus_irq < 0) { 24662306a36Sopenharmony_ci dev_err(dev, "failed to get VBUS IRQ\n"); 24762306a36Sopenharmony_ci ret = info->vbus_irq; 24862306a36Sopenharmony_ci goto put_role_sw; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, 25262306a36Sopenharmony_ci usb_conn_isr, USB_CONN_IRQF, 25362306a36Sopenharmony_ci pdev->name, info); 25462306a36Sopenharmony_ci if (ret < 0) { 25562306a36Sopenharmony_ci dev_err(dev, "failed to request VBUS IRQ\n"); 25662306a36Sopenharmony_ci goto put_role_sw; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci platform_set_drvdata(pdev, info); 26162306a36Sopenharmony_ci device_set_wakeup_capable(&pdev->dev, true); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci /* Perform initial detection */ 26462306a36Sopenharmony_ci info->initial_detection = true; 26562306a36Sopenharmony_ci usb_conn_queue_dwork(info, 0); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return 0; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ciput_role_sw: 27062306a36Sopenharmony_ci usb_role_switch_put(info->role_sw); 27162306a36Sopenharmony_ci return ret; 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_cistatic void usb_conn_remove(struct platform_device *pdev) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci struct usb_conn_info *info = platform_get_drvdata(pdev); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci cancel_delayed_work_sync(&info->dw_det); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci if (info->last_role == USB_ROLE_HOST && info->vbus) 28162306a36Sopenharmony_ci regulator_disable(info->vbus); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci usb_role_switch_put(info->role_sw); 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cistatic int __maybe_unused usb_conn_suspend(struct device *dev) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci struct usb_conn_info *info = dev_get_drvdata(dev); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci if (device_may_wakeup(dev)) { 29162306a36Sopenharmony_ci if (info->id_gpiod) 29262306a36Sopenharmony_ci enable_irq_wake(info->id_irq); 29362306a36Sopenharmony_ci if (info->vbus_gpiod) 29462306a36Sopenharmony_ci enable_irq_wake(info->vbus_irq); 29562306a36Sopenharmony_ci return 0; 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci if (info->id_gpiod) 29962306a36Sopenharmony_ci disable_irq(info->id_irq); 30062306a36Sopenharmony_ci if (info->vbus_gpiod) 30162306a36Sopenharmony_ci disable_irq(info->vbus_irq); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci pinctrl_pm_select_sleep_state(dev); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci return 0; 30662306a36Sopenharmony_ci} 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cistatic int __maybe_unused usb_conn_resume(struct device *dev) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci struct usb_conn_info *info = dev_get_drvdata(dev); 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci if (device_may_wakeup(dev)) { 31362306a36Sopenharmony_ci if (info->id_gpiod) 31462306a36Sopenharmony_ci disable_irq_wake(info->id_irq); 31562306a36Sopenharmony_ci if (info->vbus_gpiod) 31662306a36Sopenharmony_ci disable_irq_wake(info->vbus_irq); 31762306a36Sopenharmony_ci return 0; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci pinctrl_pm_select_default_state(dev); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (info->id_gpiod) 32362306a36Sopenharmony_ci enable_irq(info->id_irq); 32462306a36Sopenharmony_ci if (info->vbus_gpiod) 32562306a36Sopenharmony_ci enable_irq(info->vbus_irq); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci usb_conn_queue_dwork(info, 0); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci return 0; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(usb_conn_pm_ops, 33362306a36Sopenharmony_ci usb_conn_suspend, usb_conn_resume); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic const struct of_device_id usb_conn_dt_match[] = { 33662306a36Sopenharmony_ci { .compatible = "gpio-usb-b-connector", }, 33762306a36Sopenharmony_ci { } 33862306a36Sopenharmony_ci}; 33962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, usb_conn_dt_match); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_cistatic struct platform_driver usb_conn_driver = { 34262306a36Sopenharmony_ci .probe = usb_conn_probe, 34362306a36Sopenharmony_ci .remove_new = usb_conn_remove, 34462306a36Sopenharmony_ci .driver = { 34562306a36Sopenharmony_ci .name = "usb-conn-gpio", 34662306a36Sopenharmony_ci .pm = &usb_conn_pm_ops, 34762306a36Sopenharmony_ci .of_match_table = usb_conn_dt_match, 34862306a36Sopenharmony_ci }, 34962306a36Sopenharmony_ci}; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cimodule_platform_driver(usb_conn_driver); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ciMODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>"); 35462306a36Sopenharmony_ciMODULE_DESCRIPTION("USB GPIO based connection detection driver"); 35562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 356