qcom: genoa_extcon_notifier: Add genoa extcon notifier driver
Add support for genoa extcon notifier driver which helps to provide extcon notification to internal drivers based on vbus detect on genoa msm VBUS_DETECT gpio GPIO19, which is connected to VBUS of USB type C connector. The driver handles interrupt on GPIO 19 and register extcon device for USB and USB_HOST such that dwc3-msm.c can register VBUS/ID notifier callbacks for cable connection status. This driver in addition drives GPIOS based on connect D+/D- lines to Genoa or USB type C connector. Change-Id: I700f7d98916b96e702bff7937b4600da4a55c331 Signed-off-by: AKASH KUMAR <quic_akakum@quicinc.com>
This commit is contained in:
@@ -137,6 +137,16 @@ config USB_APPLEDISPLAY
|
|||||||
Say Y here if you want to control the backlight of Apple Cinema
|
Say Y here if you want to control the backlight of Apple Cinema
|
||||||
Displays over USB. This driver provides a sysfs interface.
|
Displays over USB. This driver provides a sysfs interface.
|
||||||
|
|
||||||
|
config USB_VBUS_EXTCON_GENOA
|
||||||
|
tristate "USB Vbus detect genoa driver"
|
||||||
|
help
|
||||||
|
Say Y here if you want to support USB SW switch to device or
|
||||||
|
host mode.
|
||||||
|
|
||||||
|
This driver is for genoa USB devices that used to send extcon
|
||||||
|
notification to USB glue driver to role switch between host
|
||||||
|
and peripheral mode based GPIO state of genoa msm.
|
||||||
|
|
||||||
source "drivers/usb/misc/sisusbvga/Kconfig"
|
source "drivers/usb/misc/sisusbvga/Kconfig"
|
||||||
|
|
||||||
config USB_LD
|
config USB_LD
|
||||||
|
|||||||
@@ -34,3 +34,4 @@ obj-$(CONFIG_USB_REDRIVER_NB7VPQ904M) += ssusb-redriver-nb7vpq904m.o
|
|||||||
|
|
||||||
obj-$(CONFIG_USB_QTI_KS_BRIDGE) += ks_bridge.o
|
obj-$(CONFIG_USB_QTI_KS_BRIDGE) += ks_bridge.o
|
||||||
obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE) += diag_ipc_bridge.o
|
obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE) += diag_ipc_bridge.o
|
||||||
|
obj-$(CONFIG_USB_VBUS_EXTCON_GENOA) += vbus-extcon-genoa.o
|
||||||
|
|||||||
276
drivers/usb/misc/vbus-extcon-genoa.c
Normal file
276
drivers/usb/misc/vbus-extcon-genoa.c
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/extcon.h>
|
||||||
|
#include <linux/extcon-provider.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/errno.h>
|
||||||
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct genoa_st - platform device data for genoa
|
||||||
|
* extcon driver.
|
||||||
|
* @dev: pointer to the genoa device.
|
||||||
|
* @extcon: pointer to the extcon device.
|
||||||
|
* @vbus_det_gpio: variable to monitor VBUS_DET GPIO which is used for
|
||||||
|
* vbus notifications. If
|
||||||
|
* 0: device mode cable is disconnected so msm can run
|
||||||
|
* in host mode to activate Genoa use case.
|
||||||
|
* 1: device mode cable to windows host PC is
|
||||||
|
* connected as VBUS_DET GPIO is connected to VBUS.
|
||||||
|
* @usb_id_gpio: variable to drive USB_ID GPIO which is the id
|
||||||
|
* pin for USB port,If
|
||||||
|
* 1: device mode cable is disconnected so msm will
|
||||||
|
* be in host mode.
|
||||||
|
* 0: device mode cable is connected so msm will
|
||||||
|
* be in peripheral mode.
|
||||||
|
* @vbus_det_gpio_irq: variable used as an irq line to trigger interrupt
|
||||||
|
* based on VBUS_DET GPIO rising/falling events.
|
||||||
|
* @genoa_usb_oe_n_gpio: variable to drive USB_OE_N GPIO if device mode cable is
|
||||||
|
* connected to genoa/Windows Host PC or disconnected.
|
||||||
|
* drive it
|
||||||
|
* HIGH: switch will be disabled.
|
||||||
|
* LOW: switch will be enabled.
|
||||||
|
*/
|
||||||
|
struct genoa_st {
|
||||||
|
struct extcon_dev *extcon;
|
||||||
|
unsigned int vbus_det_gpio;
|
||||||
|
unsigned int usb_id_gpio;
|
||||||
|
unsigned int vbus_det_gpio_irq;
|
||||||
|
unsigned int genoa_usb_oe_n_gpio;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* USB external connector */
|
||||||
|
static const unsigned int genoa_extcon_cable[] = {
|
||||||
|
EXTCON_USB,
|
||||||
|
EXTCON_USB_HOST,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct genoa_st *gpst;
|
||||||
|
|
||||||
|
static void genoa_peripheral_mode_init(void)
|
||||||
|
{
|
||||||
|
gpio_set_value_cansleep(gpst->genoa_usb_oe_n_gpio, 0);
|
||||||
|
gpio_set_value_cansleep(gpst->usb_id_gpio, 0);
|
||||||
|
extcon_set_state_sync(gpst->extcon, EXTCON_USB_HOST, false);
|
||||||
|
extcon_set_state_sync(gpst->extcon, EXTCON_USB, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void genoa_host_mode_init(void)
|
||||||
|
{
|
||||||
|
gpio_set_value_cansleep(gpst->genoa_usb_oe_n_gpio, 0);
|
||||||
|
gpio_set_value_cansleep(gpst->usb_id_gpio, 1);
|
||||||
|
extcon_set_state_sync(gpst->extcon, EXTCON_USB, false);
|
||||||
|
extcon_set_state_sync(gpst->extcon, EXTCON_USB_HOST, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* genoa_vbus_det_gpio_isr will be called when user manually
|
||||||
|
* connects/disconnects device mode cable and set peripheral
|
||||||
|
* or host mode accordingly based on VBUS_DET GPIO.
|
||||||
|
*/
|
||||||
|
static irqreturn_t genoa_vbus_det_gpio_isr(int irq, void *dev_id)
|
||||||
|
{
|
||||||
|
if (gpio_get_value(gpst->vbus_det_gpio))
|
||||||
|
genoa_peripheral_mode_init();
|
||||||
|
else
|
||||||
|
genoa_host_mode_init();
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int genoa_populate_dt_info(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device_node *np = pdev->dev.of_node;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = of_get_named_gpio(np, "genoa_vbus_det", 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev, "couldn't find genoa vbus det gpio %d\n",
|
||||||
|
ret);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
gpst->vbus_det_gpio = ret;
|
||||||
|
|
||||||
|
ret = of_get_named_gpio(np, "genoa_usb_id", 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev, "couldn't find genoa usb id gpio %d\n",
|
||||||
|
ret);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
gpst->usb_id_gpio = ret;
|
||||||
|
|
||||||
|
ret = of_get_named_gpio(np, "genoa_usb_oe_n", 1);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev, "couldn't find genoa usb_oe_n gpio %d\n",
|
||||||
|
ret);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
gpst->genoa_usb_oe_n_gpio = ret;
|
||||||
|
|
||||||
|
dev_info(&pdev->dev, "genoa vbus_det_gpio:%d usb_id_gpio:%d genoa_usb_oe_n_gpio:%d gpio\n",
|
||||||
|
gpst->vbus_det_gpio, gpst->usb_id_gpio,
|
||||||
|
gpst->genoa_usb_oe_n_gpio);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
if (gpio_get_value(gpst->vbus_det_gpio))
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "peripheral mode\n");
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "host mode\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
if (sysfs_streq(buf, "peripheral"))
|
||||||
|
genoa_peripheral_mode_init();
|
||||||
|
else if (sysfs_streq(buf, "host"))
|
||||||
|
genoa_host_mode_init();
|
||||||
|
else
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR_RW(mode);
|
||||||
|
|
||||||
|
static int genoa_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (pdev->dev.of_node) {
|
||||||
|
gpst = devm_kzalloc(&pdev->dev, sizeof(*gpst), GFP_KERNEL);
|
||||||
|
if (!gpst)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = genoa_populate_dt_info(pdev);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"couldn't populate dt info err: %d\n",
|
||||||
|
ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"couldn't find of node for genoa device\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, gpst);
|
||||||
|
|
||||||
|
/* configure vbus det gpio as input*/
|
||||||
|
ret = devm_gpio_request_one(&pdev->dev, gpst->vbus_det_gpio, GPIOF_IN,
|
||||||
|
"genoa_vbus_det_gpio");
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"failed to configure input direction for gpio %d err:%d\n",
|
||||||
|
gpst->vbus_det_gpio, ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* configure usb id gpio as output*/
|
||||||
|
ret = devm_gpio_request_one(&pdev->dev, gpst->usb_id_gpio,
|
||||||
|
GPIOF_DIR_OUT, "genoa_usb_id_gpio");
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"failed to configure input direction for gpio %d err:%d\n",
|
||||||
|
gpst->vbus_det_gpio, ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* configure usb_oe_n gpio as output*/
|
||||||
|
ret = devm_gpio_request_one(&pdev->dev, gpst->genoa_usb_oe_n_gpio,
|
||||||
|
GPIOF_OUT_INIT_HIGH, "genoa_usb_oe_n_gpio");
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"failed to configure input direction for gpio %d err:%d\n",
|
||||||
|
gpst->vbus_det_gpio, ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = gpio_to_irq(gpst->vbus_det_gpio);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev, "couldn't find genoa vbus det irq err:%d\n",
|
||||||
|
ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
gpst->vbus_det_gpio_irq = ret;
|
||||||
|
|
||||||
|
gpst->extcon = devm_extcon_dev_allocate(&pdev->dev, genoa_extcon_cable);
|
||||||
|
if (IS_ERR(gpst->extcon)) {
|
||||||
|
dev_err(&pdev->dev, "%s: failed to allocate extcon device\n",
|
||||||
|
__func__);
|
||||||
|
return PTR_ERR(gpst->extcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = devm_extcon_dev_register(&pdev->dev, gpst->extcon);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "%s: failed to register extcon device %d\n",
|
||||||
|
__func__, ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = devm_request_irq(&pdev->dev, gpst->vbus_det_gpio_irq,
|
||||||
|
genoa_vbus_det_gpio_isr,
|
||||||
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||||
|
"vbus-det-irq-trigger", NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(&pdev->dev, "couldn't acquire genoa vbus det irq %d\n",
|
||||||
|
ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*setting the state while bootup*/
|
||||||
|
if (gpio_get_value(gpst->vbus_det_gpio))
|
||||||
|
genoa_peripheral_mode_init();
|
||||||
|
else
|
||||||
|
genoa_host_mode_init();
|
||||||
|
|
||||||
|
device_create_file(&pdev->dev, &dev_attr_mode);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int genoa_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
device_remove_file(&pdev->dev, &dev_attr_mode);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const struct of_device_id genoa_dt_match[] = {
|
||||||
|
{.compatible = "qcom,genoa-extcon"},
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, genoa_dt_match);
|
||||||
|
|
||||||
|
static struct platform_driver genoa_driver = {
|
||||||
|
.probe = genoa_probe,
|
||||||
|
.remove = genoa_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "genoa-msm-vbus-det",
|
||||||
|
.of_match_table = genoa_dt_match,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(genoa_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("QTI genoa vbus detect driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
Reference in New Issue
Block a user