diff options
Diffstat (limited to 'drivers/usb/otg')
-rw-r--r-- | drivers/usb/otg/Kconfig | 10 | ||||
-rw-r--r-- | drivers/usb/otg/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/otg/gpio_vbus.c | 42 | ||||
-rw-r--r-- | drivers/usb/otg/nop-usb-xceiv.c | 180 | ||||
-rw-r--r-- | drivers/usb/otg/twl4030-usb.c | 73 |
5 files changed, 285 insertions, 21 deletions
diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index ee55b44..aa884d0 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -43,7 +43,7 @@ config ISP1301_OMAP config TWL4030_USB tristate "TWL4030 USB Transceiver Driver" - depends on TWL4030_CORE + depends on TWL4030_CORE && REGULATOR_TWL4030 select USB_OTG_UTILS help Enable this to support the USB OTG transceiver on TWL4030 @@ -51,4 +51,12 @@ config TWL4030_USB This transceiver supports high and full speed devices plus, in host mode, low speed. +config NOP_USB_XCEIV + tristate "NOP USB Transceiver Driver" + select USB_OTG_UTILS + help + this driver is to be used by all the usb transceiver which are either + built-in with usb ip or which are autonomous and doesn't require any + phy programming such as ISP1x04 etc. + endif # USB || OTG diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index d73c7cf..2081678 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += otg.o obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o +obj-$(CONFIG_NOP_USB_XCEIV) += nop-usb-xceiv.o ccflags-$(CONFIG_USB_DEBUG) += -DDEBUG ccflags-$(CONFIG_USB_GADGET_DEBUG) += -DDEBUG diff --git a/drivers/usb/otg/gpio_vbus.c b/drivers/usb/otg/gpio_vbus.c index 63a6036..1c26c94 100644 --- a/drivers/usb/otg/gpio_vbus.c +++ b/drivers/usb/otg/gpio_vbus.c @@ -13,6 +13,7 @@ #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/usb.h> +#include <linux/workqueue.h> #include <linux/regulator/consumer.h> @@ -34,6 +35,7 @@ struct gpio_vbus_data { struct regulator *vbus_draw; int vbus_draw_enabled; unsigned mA; + struct work_struct work; }; @@ -76,24 +78,26 @@ static void set_vbus_draw(struct gpio_vbus_data *gpio_vbus, unsigned mA) gpio_vbus->mA = mA; } -/* VBUS change IRQ handler */ -static irqreturn_t gpio_vbus_irq(int irq, void *data) +static int is_vbus_powered(struct gpio_vbus_mach_info *pdata) { - struct platform_device *pdev = data; - struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; - struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); - int gpio, vbus; + int vbus; vbus = gpio_get_value(pdata->gpio_vbus); if (pdata->gpio_vbus_inverted) vbus = !vbus; - dev_dbg(&pdev->dev, "VBUS %s (gadget: %s)\n", - vbus ? "supplied" : "inactive", - gpio_vbus->otg.gadget ? gpio_vbus->otg.gadget->name : "none"); + return vbus; +} + +static void gpio_vbus_work(struct work_struct *work) +{ + struct gpio_vbus_data *gpio_vbus = + container_of(work, struct gpio_vbus_data, work); + struct gpio_vbus_mach_info *pdata = gpio_vbus->dev->platform_data; + int gpio; if (!gpio_vbus->otg.gadget) - return IRQ_HANDLED; + return; /* Peripheral controllers which manage the pullup themselves won't have * gpio_pullup configured here. If it's configured here, we'll do what @@ -101,7 +105,7 @@ static irqreturn_t gpio_vbus_irq(int irq, void *data) * that may complicate usb_gadget_{,dis}connect() support. */ gpio = pdata->gpio_pullup; - if (vbus) { + if (is_vbus_powered(pdata)) { gpio_vbus->otg.state = OTG_STATE_B_PERIPHERAL; usb_gadget_vbus_connect(gpio_vbus->otg.gadget); @@ -121,6 +125,21 @@ static irqreturn_t gpio_vbus_irq(int irq, void *data) usb_gadget_vbus_disconnect(gpio_vbus->otg.gadget); gpio_vbus->otg.state = OTG_STATE_B_IDLE; } +} + +/* VBUS change IRQ handler */ +static irqreturn_t gpio_vbus_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; + struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "VBUS %s (gadget: %s)\n", + is_vbus_powered(pdata) ? "supplied" : "inactive", + gpio_vbus->otg.gadget ? gpio_vbus->otg.gadget->name : "none"); + + if (gpio_vbus->otg.gadget) + schedule_work(&gpio_vbus->work); return IRQ_HANDLED; } @@ -257,6 +276,7 @@ static int __init gpio_vbus_probe(struct platform_device *pdev) irq, err); goto err_irq; } + INIT_WORK(&gpio_vbus->work, gpio_vbus_work); /* only active when a gadget is registered */ err = otg_set_transceiver(&gpio_vbus->otg); diff --git a/drivers/usb/otg/nop-usb-xceiv.c b/drivers/usb/otg/nop-usb-xceiv.c new file mode 100644 index 0000000..4b933f6 --- /dev/null +++ b/drivers/usb/otg/nop-usb-xceiv.c @@ -0,0 +1,180 @@ +/* + * drivers/usb/otg/nop-usb-xceiv.c + * + * NOP USB transceiver for all USB transceiver which are either built-in + * into USB IP or which are mostly autonomous. + * + * Copyright (C) 2009 Texas Instruments Inc + * Author: Ajay Kumar Gupta <ajay.gupta@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Current status: + * this is to add "nop" transceiver for all those phy which is + * autonomous such as isp1504 etc. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/usb/otg.h> + +struct nop_usb_xceiv { + struct otg_transceiver otg; + struct device *dev; +}; + +static u64 nop_xceiv_dmamask = DMA_32BIT_MASK; + +static struct platform_device nop_xceiv_device = { + .name = "nop_usb_xceiv", + .id = -1, + .dev = { + .dma_mask = &nop_xceiv_dmamask, + .coherent_dma_mask = DMA_32BIT_MASK, + .platform_data = NULL, + }, +}; + +void usb_nop_xceiv_register(void) +{ + if (platform_device_register(&nop_xceiv_device) < 0) { + printk(KERN_ERR "Unable to register usb nop transceiver\n"); + return; + } +} + +void usb_nop_xceiv_unregister(void) +{ + platform_device_unregister(&nop_xceiv_device); +} + +static inline struct nop_usb_xceiv *xceiv_to_nop(struct otg_transceiver *x) +{ + return container_of(x, struct nop_usb_xceiv, otg); +} + +static int nop_set_suspend(struct otg_transceiver *x, int suspend) +{ + return 0; +} + +static int nop_set_peripheral(struct otg_transceiver *x, + struct usb_gadget *gadget) +{ + struct nop_usb_xceiv *nop; + + if (!x) + return -ENODEV; + + nop = xceiv_to_nop(x); + + if (!gadget) { + nop->otg.gadget = NULL; + return -ENODEV; + } + + nop->otg.gadget = gadget; + nop->otg.state = OTG_STATE_B_IDLE; + return 0; +} + +static int nop_set_host(struct otg_transceiver *x, struct usb_bus *host) +{ + struct nop_usb_xceiv *nop; + + if (!x) + return -ENODEV; + + nop = xceiv_to_nop(x); + + if (!host) { + nop->otg.host = NULL; + return -ENODEV; + } + + nop->otg.host = host; + return 0; +} + +static int __devinit nop_usb_xceiv_probe(struct platform_device *pdev) +{ + struct nop_usb_xceiv *nop; + int err; + + nop = kzalloc(sizeof *nop, GFP_KERNEL); + if (!nop) + return -ENOMEM; + + nop->dev = &pdev->dev; + nop->otg.dev = nop->dev; + nop->otg.label = "nop-xceiv"; + nop->otg.state = OTG_STATE_UNDEFINED; + nop->otg.set_host = nop_set_host; + nop->otg.set_peripheral = nop_set_peripheral; + nop->otg.set_suspend = nop_set_suspend; + + err = otg_set_transceiver(&nop->otg); + if (err) { + dev_err(&pdev->dev, "can't register transceiver, err: %d\n", + err); + goto exit; + } + + platform_set_drvdata(pdev, nop); + + return 0; +exit: + kfree(nop); + return err; +} + +static int __devexit nop_usb_xceiv_remove(struct platform_device *pdev) +{ + struct nop_usb_xceiv *nop = platform_get_drvdata(pdev); + + otg_set_transceiver(NULL); + + platform_set_drvdata(pdev, NULL); + kfree(nop); + + return 0; +} + +static struct platform_driver nop_usb_xceiv_driver = { + .probe = nop_usb_xceiv_probe, + .remove = __devexit_p(nop_usb_xceiv_remove), + .driver = { + .name = "nop_usb_xceiv", + .owner = THIS_MODULE, + }, +}; + +static int __init nop_usb_xceiv_init(void) +{ + return platform_driver_register(&nop_usb_xceiv_driver); +} +subsys_initcall(nop_usb_xceiv_init); + +static void __exit nop_usb_xceiv_exit(void) +{ + platform_driver_unregister(&nop_usb_xceiv_driver); +} +module_exit(nop_usb_xceiv_exit); + +MODULE_ALIAS("platform:nop_usb_xceiv"); +MODULE_AUTHOR("Texas Instruments Inc"); +MODULE_DESCRIPTION("NOP USB Transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c index 416e441..d9478d0 100644 --- a/drivers/usb/otg/twl4030-usb.c +++ b/drivers/usb/otg/twl4030-usb.c @@ -34,6 +34,8 @@ #include <linux/delay.h> #include <linux/usb/otg.h> #include <linux/i2c/twl4030.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> /* Register defines */ @@ -246,6 +248,11 @@ struct twl4030_usb { struct otg_transceiver otg; struct device *dev; + /* TWL4030 internal USB regulator supplies */ + struct regulator *usb1v5; + struct regulator *usb1v8; + struct regulator *usb3v1; + /* for vbus reporting with irqs disabled */ spinlock_t lock; @@ -434,6 +441,18 @@ static void twl4030_phy_power(struct twl4030_usb *twl, int on) pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); if (on) { + regulator_enable(twl->usb3v1); + regulator_enable(twl->usb1v8); + /* + * Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP + * in twl4030) resets the VUSB_DEDICATED2 register. This reset + * enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to + * SLEEP. We work around this by clearing the bit after usv3v1 + * is re-activated. This ensures that VUSB3V1 is really active. + */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, + VUSB_DEDICATED2); + regulator_enable(twl->usb1v5); pwr &= ~PHY_PWR_PHYPWD; WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); twl4030_usb_write(twl, PHY_CLK_CTRL, @@ -443,6 +462,9 @@ static void twl4030_phy_power(struct twl4030_usb *twl, int on) } else { pwr |= PHY_PWR_PHYPWD; WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); + regulator_disable(twl->usb1v5); + regulator_disable(twl->usb1v8); + regulator_disable(twl->usb3v1); } } @@ -468,7 +490,7 @@ static void twl4030_phy_resume(struct twl4030_usb *twl) twl->asleep = 0; } -static void twl4030_usb_ldo_init(struct twl4030_usb *twl) +static int twl4030_usb_ldo_init(struct twl4030_usb *twl) { /* Enable writing to power configuration registers */ twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0xC0, PROTECT_KEY); @@ -480,20 +502,45 @@ static void twl4030_usb_ldo_init(struct twl4030_usb *twl) /* input to VUSB3V1 LDO is from VBAT, not VBUS */ twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); - /* turn on 3.1V regulator */ - twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB3V1_DEV_GRP); + /* Initialize 3.1V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP); + + twl->usb3v1 = regulator_get(twl->dev, "usb3v1"); + if (IS_ERR(twl->usb3v1)) + return -ENODEV; + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE); - /* turn on 1.5V regulator */ - twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB1V5_DEV_GRP); + /* Initialize 1.5V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP); + + twl->usb1v5 = regulator_get(twl->dev, "usb1v5"); + if (IS_ERR(twl->usb1v5)) + goto fail1; + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE); - /* turn on 1.8V regulator */ - twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB1V8_DEV_GRP); + /* Initialize 1.8V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP); + + twl->usb1v8 = regulator_get(twl->dev, "usb1v8"); + if (IS_ERR(twl->usb1v8)) + goto fail2; + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE); /* disable access to power configuration registers */ twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, PROTECT_KEY); + + return 0; + +fail2: + regulator_put(twl->usb1v5); + twl->usb1v5 = NULL; +fail1: + regulator_put(twl->usb3v1); + twl->usb3v1 = NULL; + return -ENODEV; } static ssize_t twl4030_usb_vbus_show(struct device *dev, @@ -598,7 +645,7 @@ static int __init twl4030_usb_probe(struct platform_device *pdev) { struct twl4030_usb_data *pdata = pdev->dev.platform_data; struct twl4030_usb *twl; - int status; + int status, err; if (!pdata) { dev_dbg(&pdev->dev, "platform_data not available\n"); @@ -622,7 +669,12 @@ static int __init twl4030_usb_probe(struct platform_device *pdev) /* init spinlock for workqueue */ spin_lock_init(&twl->lock); - twl4030_usb_ldo_init(twl); + err = twl4030_usb_ldo_init(twl); + if (err) { + dev_err(&pdev->dev, "ldo init failed\n"); + kfree(twl); + return err; + } otg_set_transceiver(&twl->otg); platform_set_drvdata(pdev, twl); @@ -688,6 +740,9 @@ static int __exit twl4030_usb_remove(struct platform_device *pdev) twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); twl4030_phy_power(twl, 0); + regulator_put(twl->usb1v5); + regulator_put(twl->usb1v8); + regulator_put(twl->usb3v1); kfree(twl); |