extcon: Add Type-C Virtual PD driver
This commit is contained in:
parent
24460d5023
commit
567571663d
3 changed files with 440 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
429
drivers/extcon/extcon-pd-virtual.c
Normal file
429
drivers/extcon/extcon-pd-virtual.c
Normal file
|
|
@ -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 <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/extcon-provider.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
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");
|
||||
Loading…
Add table
Add a link
Reference in a new issue