summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/extcon/Kconfig7
-rw-r--r--drivers/extcon/Makefile1
-rw-r--r--drivers/extcon/extcon-usb-gpio.c308
-rw-r--r--drivers/usb/chipidea/ci.h6
-rw-r--r--drivers/usb/chipidea/core.c13
-rw-r--r--drivers/usb/chipidea/otg.c19
-rw-r--r--drivers/usb/host/ehci-msm.c10
-rw-r--r--drivers/usb/phy/phy-msm-usb.c104
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;