From 0049758f8bf2f4f29cdabcef34a44e2149c7ae53 Mon Sep 17 00:00:00 2001 From: AKASH KUMAR Date: Thu, 24 Mar 2022 11:52:54 +0530 Subject: [PATCH] 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 --- drivers/usb/misc/Kconfig | 10 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/vbus-extcon-genoa.c | 276 +++++++++++++++++++++++++++ 3 files changed, 287 insertions(+) create mode 100644 drivers/usb/misc/vbus-extcon-genoa.c diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 2218c170efab..2edfbf7bef1d 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -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 diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 51bf965965e0..a648346dc8cd 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -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 diff --git a/drivers/usb/misc/vbus-extcon-genoa.c b/drivers/usb/misc/vbus-extcon-genoa.c new file mode 100644 index 000000000000..6a756f637b7b --- /dev/null +++ b/drivers/usb/misc/vbus-extcon-genoa.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * 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");