diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index e54e5e64d9ab..28f97e0b8aab 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -191,4 +191,14 @@ config EXTCON_USBC_TUSB320 Say Y here to enable support for USB Type C cable detection extcon support using a TUSB320. +config EXTCON_USBC_VIRTUAL_PD + tristate "Virtual Type-C PD EXTCON support" + depends on GPIOLIB || COMPILE_TEST + help + Say Y here to enable Virtual Type-C PD extcon driver support, if + hardware platform designed Type-C modes separately. + + Example, of designing Display Port separately from Type-C Altmode + instead of accessing Altmode Display Port in Type-C connector. + endif diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 1b390d934ca9..306019ae9c37 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o +obj-$(CONFIG_EXTCON_USBC_VIRTUAL_PD) += extcon-pd-virtual.o \ No newline at end of file diff --git a/drivers/extcon/extcon-pd-virtual.c b/drivers/extcon/extcon-pd-virtual.c new file mode 100644 index 000000000000..b186ea5eb285 --- /dev/null +++ b/drivers/extcon/extcon-pd-virtual.c @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + * Copyright (c) 2019 Radxa Limited + * Copyright (c) 2019 Amarula Solutions(India) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct virtual_pd { + struct extcon_dev *extcon; + struct gpio_desc *gpio_irq; + struct regulator *dp_pwr; + struct device *dev; + bool flip; + bool usb_ss; + bool enable; + u8 mode; + int irq; + int enable_irq; + u8 plug_state; + struct workqueue_struct *virtual_pd_wq; + spinlock_t irq_lock; + struct delayed_work irq_work; + int shake_lev; +}; + +static const unsigned int vpd_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_USB_VBUS_EN, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, +/* + FIXME: There's no real pd phy, control the charging is very + dangerous, just rely on the BC detection. We don't use slow + and fast. +*/ + EXTCON_CHG_USB_SLOW, + EXTCON_CHG_USB_FAST, + EXTCON_DISP_DP, + EXTCON_NONE, +}; + +enum vpd_mode { + VPD_DFP = 0, + VPD_UFP, + VPD_DP, + VPD_DP_UFP, +}; + +static void vpd_set_vbus_enable(struct virtual_pd *vpd, bool enable) +{ + extcon_set_state(vpd->extcon, EXTCON_USB_VBUS_EN, enable); + extcon_sync(vpd->extcon, EXTCON_USB_VBUS_EN); +} + +static void vpd_extcon_notify(struct virtual_pd *vpd, bool flip, bool usb_ss, + bool dfp, bool ufp, bool dp) +{ + union extcon_property_value property; + + property.intval = flip; + extcon_set_property(vpd->extcon, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY, property); + extcon_set_property(vpd->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY, property); + extcon_set_property(vpd->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_TYPEC_POLARITY, property); + + property.intval = usb_ss; + extcon_set_property(vpd->extcon, EXTCON_USB, + EXTCON_PROP_USB_SS, property); + extcon_set_property(vpd->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS, property); + extcon_set_property(vpd->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_SS, property); + extcon_set_state(vpd->extcon, EXTCON_USB, ufp); + extcon_set_state(vpd->extcon, EXTCON_USB_HOST, dfp); + extcon_set_state(vpd->extcon, EXTCON_DISP_DP, dp); + extcon_sync(vpd->extcon, EXTCON_USB); + extcon_sync(vpd->extcon, EXTCON_USB_HOST); + extcon_sync(vpd->extcon, EXTCON_DISP_DP); +} + +static void vpd_extcon_notify_set(struct virtual_pd *vpd) +{ + bool flip = vpd->flip, usb_ss = vpd->usb_ss; + bool dfp = 0, ufp = 0, dp = 0; + + switch (vpd->mode) { + case VPD_DFP: + dfp = 1; + break; + case VPD_DP: + dp = 1; + dfp = 1; + break; + case VPD_DP_UFP: + dp = 1; + ufp = 1; + break; + case VPD_UFP: + /* fall through */ + default: + ufp = 1; + break; + } + + vpd_set_vbus_enable(vpd, !ufp); + vpd_extcon_notify(vpd, flip, usb_ss, dfp, ufp, dp); +} + +static void vpd_extcon_notify_clr(struct virtual_pd *vpd) +{ + vpd_set_vbus_enable(vpd, 0); + vpd_extcon_notify(vpd, vpd->flip, vpd->usb_ss, 0, 0, 0); +} + +void vpd_irq_disable(struct virtual_pd *vpd) +{ + unsigned long irqflags = 0; + + spin_lock_irqsave(&vpd->irq_lock, irqflags); + if (!vpd->enable_irq) { + disable_irq_nosync(vpd->irq); + vpd->enable_irq = 1; + } else { + dev_warn(vpd->dev, "irq have already disabled\n"); + } + spin_unlock_irqrestore(&vpd->irq_lock, irqflags); +} + +void vpd_irq_enable(struct virtual_pd *vpd) +{ + unsigned long irqflags = 0; + + spin_lock_irqsave(&vpd->irq_lock, irqflags); + if (vpd->enable_irq) { + enable_irq(vpd->irq); + vpd->enable_irq = 0; + } + spin_unlock_irqrestore(&vpd->irq_lock, irqflags); +} + +static void extcon_pd_delay_irq_work(struct work_struct *work) +{ + struct virtual_pd *vpd = + container_of(work, struct virtual_pd, irq_work.work); + int lev; + + lev = gpiod_get_raw_value(vpd->gpio_irq); + + if (vpd->shake_lev != lev) { + vpd_irq_enable(vpd); + return; + } + + switch (vpd->plug_state) { + case 1: + if (lev == 0) { + vpd->enable = false; + vpd_extcon_notify_clr(vpd); + vpd->plug_state=0; + } + break; + case 0: + if (lev == 1) { + vpd->enable = true; + vpd_extcon_notify_set(vpd); + vpd->plug_state=1; + } + break; + default: + break; + } + vpd_irq_enable(vpd); +} + +static irqreturn_t dp_det_irq_handler(int irq, void *dev_id) +{ + struct virtual_pd *vpd = dev_id; + int lev; + lev = gpiod_get_raw_value(vpd->gpio_irq); + vpd->shake_lev = lev; + schedule_delayed_work(&vpd->irq_work, msecs_to_jiffies(10)); + vpd_irq_disable(vpd); + return IRQ_HANDLED; +} + +static void vpd_extcon_init(struct virtual_pd *vpd) +{ + struct device *dev = vpd->dev; + u32 tmp = 0; + int ret = 0; + + ret = device_property_read_u32(dev, "vpd,init-flip", &tmp); + if (ret < 0) + vpd->flip = 0; + else + vpd->flip = tmp; + dev_dbg(dev, "init-flip = %d\n", vpd->flip); + + ret = device_property_read_u32(dev, "vpd,init-ss", &tmp); + if (ret < 0) + vpd->usb_ss = 0; + else + vpd->usb_ss = tmp; + dev_dbg(dev, "init-ss = %d\n", vpd->usb_ss); + + ret = device_property_read_u32(dev, "vpd,init-mode", &tmp); + if (ret < 0) + vpd->mode = 0; + else + vpd->mode = tmp; + dev_dbg(dev, "init-mode = %d\n", vpd->mode); + + if(gpiod_get_raw_value(vpd->gpio_irq)) { + vpd_extcon_notify_set(vpd); + vpd->plug_state=1; + } +} + +static int vpd_extcon_probe(struct platform_device *pdev) +{ + struct virtual_pd *vpd; + struct device *dev = &pdev->dev; + int ret = 0; + + dev_info(dev, "probe start\n"); + + vpd = devm_kzalloc(dev, sizeof(*vpd), GFP_KERNEL); + if (!vpd) + return -ENOMEM; + + vpd->dev = dev; + dev_set_drvdata(dev, vpd); + vpd->enable = 1; + + vpd->extcon = devm_extcon_dev_allocate(dev, vpd_cable); + if (IS_ERR(vpd->extcon)) { + dev_err(dev, "allocat extcon failed\n"); + return PTR_ERR(vpd->extcon); + } + + ret = devm_extcon_dev_register(dev, vpd->extcon); + if (ret) { + dev_err(dev, "register extcon failed: %d\n", ret); + return ret; + } + + vpd->gpio_irq = devm_gpiod_get_optional(dev,"hpd", GPIOD_IN); + if (IS_ERR(vpd->gpio_irq)) { + dev_warn(dev, "maybe miss named GPIO for hpd\n"); + vpd->gpio_irq = NULL; + } + + vpd->dp_pwr = devm_regulator_get_optional(dev, "dp-pwr"); + if (IS_ERR(vpd->dp_pwr)) { + dev_warn(dev, "failed to get dp-pwr\n"); + vpd->dp_pwr = NULL; + } + + ret = regulator_enable(vpd->dp_pwr); + if (ret) + dev_warn(dev, "failed to enable dp-pwr\n"); + + ret = extcon_set_property_capability(vpd->extcon, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY); + if (ret) { + dev_err(dev, + "set USB property capability failed: %d\n", ret); + return ret; + } + + ret = extcon_set_property_capability(vpd->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY); + if (ret) { + dev_err(dev, + "set USB_HOST property capability failed: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(vpd->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_TYPEC_POLARITY); + if (ret) { + dev_err(dev, + "set DISP_DP property capability failed: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(vpd->extcon, EXTCON_USB, + EXTCON_PROP_USB_SS); + if (ret) { + dev_err(dev, + "set USB USB_SS property capability failed: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(vpd->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS); + if (ret) { + dev_err(dev, + "set USB_HOST USB_SS property capability failed: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(vpd->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_SS); + if (ret) { + dev_err(dev, + "set DISP_DP USB_SS property capability failed: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(vpd->extcon, EXTCON_CHG_USB_FAST, + EXTCON_PROP_USB_TYPEC_POLARITY); + if (ret) { + dev_err(dev, + "set USB_PD property capability failed: %d\n", ret); + return ret; + } + + vpd_extcon_init(vpd); + INIT_DELAYED_WORK(&vpd->irq_work, extcon_pd_delay_irq_work); + + vpd->irq=gpiod_to_irq(vpd->gpio_irq); + if (vpd->irq){ + ret = devm_request_threaded_irq(dev, + vpd->irq, + NULL, + dp_det_irq_handler, + IRQF_TRIGGER_FALLING |IRQF_TRIGGER_RISING | IRQF_ONESHOT , + NULL, + vpd); + } + else + dev_err(dev,"gpio can not be irq !\n"); + + vpd->virtual_pd_wq = create_workqueue("virtual_pd_wq"); + + dev_info(dev, "probe success\n"); + + return 0; +} + +static int vpd_extcon_remove(struct platform_device *pdev) +{ + struct virtual_pd *vpd = platform_get_drvdata(pdev); + + regulator_disable(vpd->dp_pwr); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int vpd_extcon_suspend(struct device *dev) +{ + struct virtual_pd *vpd = dev_get_drvdata(dev); + + int lev=0; + lev = gpiod_get_raw_value(vpd->gpio_irq); + cancel_delayed_work_sync(&vpd->irq_work); + vpd_irq_disable(vpd); + return 0; +} + +static int vpd_extcon_resume(struct device *dev) +{ + struct virtual_pd *vpd = dev_get_drvdata(dev); + vpd_irq_enable(vpd); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(vpd_extcon_pm_ops, + vpd_extcon_suspend, vpd_extcon_resume); + +static const struct of_device_id vpd_extcon_dt_match[] = { + { .compatible = "linux,extcon-pd-virtual", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vpd_extcon_dt_match); + +static struct platform_driver vpd_extcon_driver = { + .probe = vpd_extcon_probe, + .remove = vpd_extcon_remove, + .driver = { + .name = "extcon-pd-virtual", + .pm = &vpd_extcon_pm_ops, + .of_match_table = vpd_extcon_dt_match, + }, +}; + +static int __init __vpd_extcon_init(void) +{ + return platform_driver_register(&vpd_extcon_driver); +} + +static void __exit __vpd_extcon_exit(void) +{ + platform_driver_unregister(&vpd_extcon_driver); +} + +module_init(__vpd_extcon_init); +module_exit(__vpd_extcon_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rockchip"); +MODULE_DESCRIPTION("Virtual Typec-pd extcon driver"); \ No newline at end of file