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:
AKASH KUMAR
2022-03-24 11:52:54 +05:30
parent 067a3d9ad3
commit 0049758f8b
3 changed files with 287 additions and 0 deletions

View File

@@ -137,6 +137,16 @@ config USB_APPLEDISPLAY
Say Y here if you want to control the backlight of Apple Cinema
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"
config USB_LD

View File

@@ -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_QCOM_DIAG_BRIDGE) += diag_ipc_bridge.o
obj-$(CONFIG_USB_VBUS_EXTCON_GENOA) += vbus-extcon-genoa.o

View 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");