diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/extcon/Kconfig | 7 | ||||
-rw-r--r-- | drivers/extcon/Makefile | 1 | ||||
-rw-r--r-- | drivers/extcon/extcon-usb-gpio.c | 308 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci.h | 6 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 13 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg.c | 19 | ||||
-rw-r--r-- | drivers/usb/host/ehci-msm.c | 10 | ||||
-rw-r--r-- | drivers/usb/phy/phy-msm-usb.c | 104 |
8 files changed, 464 insertions, 4 deletions
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 6a1f7de6fa54..e4c01ab046f7 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -93,4 +93,11 @@ config EXTCON_SM5502 Silicon Mitus SM5502. The SM5502 is a USB port accessory detector and switch. +config EXTCON_USB_GPIO + tristate "USB GPIO extcon support" + depends on GPIOLIB + help + Say Y here to enable GPIO based USB cable detection extcon support. + Used typically if GPIO is used for USB ID pin detection. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 0370b42e5a27..6a08a98dc069 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o +obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c new file mode 100644 index 000000000000..439ca9970db9 --- /dev/null +++ b/drivers/extcon/extcon-usb-gpio.c @@ -0,0 +1,308 @@ +/** + * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com + * Author: Roger Quadros <rogerq@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/extcon.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#define USB_GPIO_DEBOUNCE_MS 20 /* ms */ + +struct usb_extcon_info { + struct device *dev; + struct extcon_dev *edev; + + struct gpio_desc *id_gpiod; + struct gpio_desc *vbus_gpiod; + int id_irq; + int vbus_irq; + + unsigned long debounce_jiffies; + struct delayed_work wq_detcable; +}; + +/* List of detectable cables */ +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_USB_HOST, + + EXTCON_CABLE_END, +}; + +static const char *usb_extcon_cable[] = { + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_USB_HOST] = "USB-HOST", + NULL, +}; + +/* + * "USB" = VBUS and "USB-HOST" = !ID, so we have: + * + * State | ID | VBUS + * ---------------------------------------- + * [1] USB | H | H + * [2] none | H | L + * [3] USB & USB-HOST | L | H + * [4] USB-HOST | L | L + * + * In case we have only one of these signals: + * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. + * - ID only - we want to distinguish between [1] and [4], so VBUS = ID. + */ + +static void usb_extcon_detect_cable(struct work_struct *work) +{ + int id; + int vbus; + struct usb_extcon_info *info = container_of(to_delayed_work(work), + struct usb_extcon_info, + wq_detcable); + + /* check ID and VBUS and update cable state */ + + id = info->id_gpiod ? + gpiod_get_value_cansleep(info->id_gpiod) : 1; + + vbus = info->vbus_gpiod ? + gpiod_get_value_cansleep(info->vbus_gpiod) : id; + + /* at first we clean states which are no longer active */ + if (id) + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB_HOST], false); + if (!vbus) + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB], false); + + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB_HOST], !id); + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB], vbus); +} + +static irqreturn_t usb_irq_handler(int irq, void *dev_id) +{ + struct usb_extcon_info *info = dev_id; + + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + info->debounce_jiffies); + + return IRQ_HANDLED; +} + +static int usb_extcon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct usb_extcon_info *info; + u32 debounce; + int ret; + + if (!np) + return -EINVAL; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = dev; + + ret = of_property_read_u32(np, "debounce", &debounce); + if (ret < 0) + debounce = USB_GPIO_DEBOUNCE_MS; + + info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id"); + info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus"); + + if (!info->id_gpiod && !info->vbus_gpiod) { + dev_err(dev, "failed to get gpios\n"); + return -ENODEV; + } + + info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); + if (IS_ERR(info->edev)) { + dev_err(dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + + ret = devm_extcon_dev_register(dev, info->edev); + if (ret < 0) { + dev_err(dev, "failed to register extcon device\n"); + return ret; + } + + if (info->id_gpiod) + ret = gpiod_set_debounce(info->id_gpiod, debounce * 1000); + if (!ret && info->vbus_gpiod) { + ret = gpiod_set_debounce(info->vbus_gpiod, debounce * 1000); + if (ret < 0 && info->id_gpiod) + gpiod_set_debounce(info->vbus_gpiod, 0); + } + if (ret < 0) + info->debounce_jiffies = msecs_to_jiffies(debounce); + + INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); + + if (info->id_gpiod) { + info->id_irq = gpiod_to_irq(info->id_gpiod); + if (info->id_irq < 0) { + dev_err(dev, "failed to get ID IRQ\n"); + return info->id_irq; + } + ret = devm_request_threaded_irq(dev, info->id_irq, NULL, + usb_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + pdev->name, info); + if (ret < 0) { + dev_err(dev, "failed to request handler for ID IRQ\n"); + return ret; + } + } + if (info->vbus_gpiod) { + info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); + if (info->vbus_irq < 0) { + dev_err(dev, "failed to get VBUS IRQ\n"); + return info->vbus_irq; + } + ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, + usb_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + pdev->name, info); + if (ret < 0) { + dev_err(dev, "failed to request handler for VBUS IRQ\n"); + return ret; + } + } + + platform_set_drvdata(pdev, info); + device_init_wakeup(dev, 1); + + /* Perform initial detection */ + usb_extcon_detect_cable(&info->wq_detcable.work); + + return 0; +} + +static int usb_extcon_remove(struct platform_device *pdev) +{ + struct usb_extcon_info *info = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&info->wq_detcable); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int usb_extcon_suspend(struct device *dev) +{ + struct usb_extcon_info *info = dev_get_drvdata(dev); + int ret = 0; + + if (device_may_wakeup(dev)) { + if (info->id_gpiod) { + ret = enable_irq_wake(info->id_irq); + if (ret) + return ret; + } + if (info->vbus_gpiod) { + ret = enable_irq_wake(info->vbus_irq); + if (ret) + goto err; + } + } + + /* + * We don't want to process any IRQs after this point + * as GPIOs used behind I2C subsystem might not be + * accessible until resume completes. So disable IRQ. + */ + if (info->id_gpiod) + disable_irq(info->id_irq); + if (info->vbus_gpiod) + disable_irq(info->vbus_irq); + + return ret; + +err: + if (info->id_gpiod) + disable_irq_wake(info->id_irq); + return ret; +} + +static int usb_extcon_resume(struct device *dev) +{ + struct usb_extcon_info *info = dev_get_drvdata(dev); + int ret = 0; + + if (device_may_wakeup(dev)) { + if (info->id_gpiod) { + ret = disable_irq_wake(info->id_irq); + if (ret) + return ret; + } + if (info->vbus_gpiod) { + ret = disable_irq_wake(info->vbus_irq); + if (ret) + goto err; + } + } + + if (info->id_gpiod) + enable_irq(info->id_irq); + if (info->vbus_gpiod) + enable_irq(info->vbus_irq); + + return ret; + +err: + if (info->id_gpiod) + enable_irq_wake(info->id_irq); + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, + usb_extcon_suspend, usb_extcon_resume); + +static struct of_device_id usb_extcon_dt_match[] = { + { .compatible = "linux,extcon-usb-gpio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); + +static struct platform_driver usb_extcon_driver = { + .probe = usb_extcon_probe, + .remove = usb_extcon_remove, + .driver = { + .name = "extcon-usb-gpio", + .pm = &usb_extcon_pm_ops, + .of_match_table = usb_extcon_dt_match, + }, +}; + +module_platform_driver(usb_extcon_driver); + +MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); +MODULE_DESCRIPTION("USB GPIO extcon driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 65913d48f0c8..0cfb398052d2 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -13,6 +13,7 @@ #ifndef __DRIVERS_USB_CHIPIDEA_CI_H #define __DRIVERS_USB_CHIPIDEA_CI_H +#include <linux/extcon.h> #include <linux/list.h> #include <linux/irqreturn.h> #include <linux/usb.h> @@ -169,6 +170,8 @@ struct hw_bank { * @b_sess_valid_event: indicates there is a vbus event, and handled * at ci_otg_work * @imx28_write_fix: Freescale imx28 needs swp instruction for writing + * @edev_vbus: extcon device. Used to read VBUS signal state + * @edev_id: extcon device. Used to read ID signal state */ struct ci_hdrc { struct device *dev; @@ -211,6 +214,9 @@ struct ci_hdrc { bool id_event; bool b_sess_valid_event; bool imx28_write_fix; + + struct extcon_dev *edev_vbus; + struct extcon_dev *edev_id; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index a57dc8866fc5..9aca7e66a5d1 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -47,6 +47,7 @@ #include <linux/delay.h> #include <linux/device.h> #include <linux/dma-mapping.h> +#include <linux/extcon.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/module.h> @@ -702,6 +703,18 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci->usb_phy = NULL; } + if (of_property_read_bool(dev->parent->of_node, "extcon")) { + + /* Each one of them is not mandatory */ + ci->edev_vbus = extcon_get_edev_by_phandle(dev->parent, 0); + if (IS_ERR(ci->edev_vbus) && PTR_ERR(ci->edev_vbus) != -ENODEV) + return PTR_ERR(ci->edev_vbus); + + ci->edev_id = extcon_get_edev_by_phandle(dev->parent, 1); + if (IS_ERR(ci->edev_id) && PTR_ERR(ci->edev_id) != -ENODEV) + return PTR_ERR(ci->edev_id); + } + ret = ci_usb_phy_init(ci); if (ret) { dev_err(dev, "unable to init phy: %d\n", ret); diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index a048b08b9d4d..495ca37fc60b 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -30,7 +30,24 @@ */ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) { - return hw_read(ci, OP_OTGSC, mask); + u32 val = hw_read(ci, OP_OTGSC, mask); + + if ((mask & OTGSC_BSV) && !IS_ERR(ci->edev_vbus)) { + if (extcon_get_cable_state(ci->edev_vbus, "USB")) + val |= OTGSC_BSV; + else + val &= ~OTGSC_BSV; + } + + if ((mask & OTGSC_ID) && !IS_ERR(ci->edev_id)) { + if (extcon_get_cable_state(ci->edev_id, "USB-HOST")) + val |= OTGSC_ID; + else + val &= ~OTGSC_ID; + } + + val &= mask; + return val; } /** diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c index 9db74ca7e5b9..2af19aa27733 100644 --- a/drivers/usb/host/ehci-msm.c +++ b/drivers/usb/host/ehci-msm.c @@ -88,13 +88,17 @@ static int ehci_msm_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - hcd->regs = devm_ioremap_resource(&pdev->dev, res); + if (!res) + return -ENODEV; + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + hcd->regs = devm_ioremap_nocache(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len); if (IS_ERR(hcd->regs)) { ret = PTR_ERR(hcd->regs); goto put_hcd; } - hcd->rsrc_start = res->start; - hcd->rsrc_len = resource_size(res); /* * OTG driver takes care of PHY initialization, clock management, diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c index 000fd892455f..43ffbbea32ac 100644 --- a/drivers/usb/phy/phy-msm-usb.c +++ b/drivers/usb/phy/phy-msm-usb.c @@ -431,6 +431,27 @@ static int msm_phy_init(struct usb_phy *phy) ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_FALL); } + /* workaround for rx buffer collision issue */ + val = readl(USB_GENCONFIG); + val &= ~GENCONFIG_TXFIFO_IDLE_FORCE_DISABLE; + writel(val, USB_GENCONFIG); + + val = ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT; + ulpi_write(phy, val, ULPI_SET(ULPI_MISC_A)); + + val = readl(USB_GENCONFIG_2); + val |= GEN2_SESS_VLD_CTRL_EN; + writel(val, USB_GENCONFIG_2); + + val = readl(USB_USBCMD); + val |= USBCMD_SESS_VLD_CTRL; + writel(val, USB_USBCMD); + + val = ulpi_read(phy, ULPI_FUNC_CTRL); + val &= ~ULPI_FUNC_CTRL_OPMODE_MASK; + val |= ULPI_FUNC_CTRL_OPMODE_NORMAL; + ulpi_write(phy, val, ULPI_FUNC_CTRL); + if (motg->phy_number) writel(readl(USB_PHY_CTRL2) | BIT(16), USB_PHY_CTRL2); @@ -1445,6 +1466,40 @@ static const struct of_device_id msm_otg_dt_match[] = { }; MODULE_DEVICE_TABLE(of, msm_otg_dt_match); +static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct msm_otg *motg = container_of(nb, struct msm_otg, vbus_nb); + + dev_dbg(motg->phy.dev, "USB/VBUS is %d\n", (int)event); + + if (event) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + + schedule_work(&motg->sm_work); + + return NOTIFY_DONE; +} + +static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct msm_otg *motg = container_of(nb, struct msm_otg, id_nb); + + dev_dbg(motg->phy.dev, "USB-HOST/ID is %d\n", (int)event); + + if (event) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + + schedule_work(&motg->sm_work); + + return NOTIFY_DONE; +} + static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg) { struct msm_otg_platform_data *pdata; @@ -1496,6 +1551,55 @@ static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg) motg->vdd_levels[VDD_LEVEL_MAX] = tmp[VDD_LEVEL_MAX]; } + if (of_property_read_bool(node, "extcon")) { + struct extcon_dev *ext_id, *ext_vbus; + + /* Each one of them is not mandatory */ + ext_vbus = extcon_get_edev_by_phandle(&pdev->dev, 0); + if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV) + return PTR_ERR(ext_vbus); + + ext_id = extcon_get_edev_by_phandle(&pdev->dev, 1); + if (IS_ERR(ext_id) && PTR_ERR(ext_id) != -ENODEV) + return PTR_ERR(ext_id); + + if (IS_ERR(ext_vbus)) { + dev_dbg(&pdev->dev, "no VBUS extcon\n"); + } else { + motg->vbus_nb.notifier_call = msm_otg_vbus_notifier; + ret = extcon_register_interest(&motg->vbus_cable, ext_vbus->name, + "USB", &motg->vbus_nb); + if (ret < 0) { + dev_err(&pdev->dev, "register VBUS notifier failed\n"); + return ret; + } else { + ret = extcon_get_cable_state(ext_vbus, "USB"); + if (ret) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + } + } + + if (IS_ERR(ext_id)) { + dev_dbg(&pdev->dev, "no ID extcon\n"); + } else { + motg->id_nb.notifier_call = msm_otg_id_notifier; + ret = extcon_register_interest(&motg->id_cable, ext_id->name, + "USB", &motg->id_nb); + if (ret < 0) { + dev_err(&pdev->dev, "register ID notifier failed\n"); + return ret; + } else { + ret = extcon_get_cable_state(ext_id, "USB-HOST"); + if (ret) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + } + } + } + prop = of_find_property(node, "qcom,phy-init-sequence", &len); if (!prop || !len) return 0; |