diff options
| author | Graeme Gregory <gg@slimlogic.co.uk> | 2013-05-28 10:50:11 +0900 | 
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-06-17 16:08:36 -0700 | 
| commit | b1f254e35d85535b17af2786d06fe88f15f304f7 (patch) | |
| tree | f20a4fc680fa2af1793ab11e11c09a6d23acad40 /drivers/extcon | |
| parent | 9c8a013af49cb82a1a47bede56e8ce5efd93b734 (diff) | |
| download | linux-b1f254e35d85535b17af2786d06fe88f15f304f7.tar.bz2 | |
extcon: Palmas Extcon Driver
This is the driver for the USB comparator built into the palmas chip. It
handles the various USB OTG events that can be generated by cable
insertion/removal.
Signed-off-by: Graeme Gregory <gg@slimlogic.co.uk>
Signed-off-by: Moiz Sonasath <m-sonasath@ti.com>
Signed-off-by: Ruchika Kharwar <ruchika@ti.com>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
Signed-off-by: George Cherian <george.cherian@ti.com>
[kishon@ti.com: adapted palmas usb driver to use the extcon framework]
Signed-off-by: Sebastien Guiriec <s-guiriec@ti.com>
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
Signed-off-by: Myungjoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/extcon')
| -rw-r--r-- | drivers/extcon/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/extcon/Makefile | 1 | ||||
| -rw-r--r-- | drivers/extcon/extcon-palmas.c | 246 | 
3 files changed, 254 insertions, 0 deletions
| diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 3297301a42d4..63f454e20576 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -53,4 +53,11 @@ config EXTCON_ARIZONA  	  with Wolfson Arizona devices. These are audio CODECs with  	  advanced audio accessory detection support. +config EXTCON_PALMAS +	tristate "Palmas USB EXTCON support" +	depends on MFD_PALMAS +	help +	  Say Y here to enable support for USB peripheral and USB host +	  detection by palmas usb. +  endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index f98a3c4d46e0..540e2c3a4431 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_EXTCON_ADC_JACK)	+= extcon-adc-jack.o  obj-$(CONFIG_EXTCON_MAX77693)	+= extcon-max77693.o  obj-$(CONFIG_EXTCON_MAX8997)	+= extcon-max8997.o  obj-$(CONFIG_EXTCON_ARIZONA)	+= extcon-arizona.o +obj-$(CONFIG_EXTCON_PALMAS)	+= extcon-palmas.o diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c new file mode 100644 index 000000000000..b752a0ad7b63 --- /dev/null +++ b/drivers/extcon/extcon-palmas.c @@ -0,0 +1,246 @@ +/* + * Palmas USB transceiver driver + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.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. + * + * Author: Graeme Gregory <gg@slimlogic.co.uk> + * Author: Kishon Vijay Abraham I <kishon@ti.com> + * + * Based on twl6030_usb.c + * + * Author: Hema HK <hemahk@ti.com> + * + * 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/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mfd/palmas.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +static const char *palmas_extcon_cable[] = { +	[0] = "USB", +	[1] = "USB-HOST", +	NULL, +}; + +static const int mutually_exclusive[] = {0x3, 0x0}; + +static void palmas_usb_wakeup(struct palmas *palmas, int enable) +{ +	if (enable) +		palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, +			PALMAS_USB_WAKEUP_ID_WK_UP_COMP); +	else +		palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 0); +} + +static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) +{ +	struct palmas_usb *palmas_usb = _palmas_usb; +	unsigned int vbus_line_state; + +	palmas_read(palmas_usb->palmas, PALMAS_INTERRUPT_BASE, +		PALMAS_INT3_LINE_STATE, &vbus_line_state); + +	if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) { +		if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) { +			palmas_usb->linkstat = PALMAS_USB_STATE_VBUS; +			extcon_set_cable_state(&palmas_usb->edev, "USB", true); +		} else { +			dev_dbg(palmas_usb->dev, +				"Spurious connect event detected\n"); +		} +	} else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) { +		if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) { +			palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; +			extcon_set_cable_state(&palmas_usb->edev, "USB", false); +		} else { +			dev_dbg(palmas_usb->dev, +				"Spurious disconnect event detected\n"); +		} +	} + +	return IRQ_HANDLED; +} + +static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) +{ +	unsigned int set; +	struct palmas_usb *palmas_usb = _palmas_usb; + +	palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +		PALMAS_USB_ID_INT_LATCH_SET, &set); + +	if (set & PALMAS_USB_ID_INT_SRC_ID_GND) { +		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +			PALMAS_USB_ID_INT_EN_HI_SET, +			PALMAS_USB_ID_INT_EN_HI_SET_ID_FLOAT); +		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +			PALMAS_USB_ID_INT_EN_HI_CLR, +			PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND); +		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +			PALMAS_USB_ID_INT_LATCH_CLR, +			PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND); +		palmas_usb->linkstat = PALMAS_USB_STATE_ID; +		extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); +	} else if (set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) { +		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +			PALMAS_USB_ID_INT_EN_HI_SET, +			PALMAS_USB_ID_INT_EN_HI_SET_ID_GND); +		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +			PALMAS_USB_ID_INT_EN_HI_CLR, +			PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT); +		palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +			PALMAS_USB_ID_INT_LATCH_CLR, +			PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT); +		palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; +		extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); +	} + +	return IRQ_HANDLED; +} + +static void palmas_enable_irq(struct palmas_usb *palmas_usb) +{ +	palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +		PALMAS_USB_VBUS_CTRL_SET, +		PALMAS_USB_VBUS_CTRL_SET_VBUS_ACT_COMP); + +	palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +		PALMAS_USB_ID_CTRL_SET, PALMAS_USB_ID_CTRL_SET_ID_ACT_COMP); + +	palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, +		PALMAS_USB_ID_INT_EN_HI_SET, +		PALMAS_USB_ID_INT_EN_HI_SET_ID_GND); + +	palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb); + +	/* cold plug for host mode needs this delay */ +	msleep(30); +	palmas_id_irq_handler(palmas_usb->id_irq, palmas_usb); +} + +static int palmas_usb_probe(struct platform_device *pdev) +{ +	struct palmas *palmas = dev_get_drvdata(pdev->dev.parent); +	struct palmas_usb_platform_data	*pdata = pdev->dev.platform_data; +	struct device_node *node = pdev->dev.of_node; +	struct palmas_usb *palmas_usb; +	int status; + +	if (node && !pdata) { +		pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + +		if (!pdata) +			return -ENOMEM; + +		pdata->wakeup = of_property_read_bool(node, "ti,wakeup"); +	} else if (!pdata) { +		return -EINVAL; +	} + +	palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL); +	if (!palmas_usb) +		return -ENOMEM; + +	palmas->usb = palmas_usb; +	palmas_usb->palmas = palmas; + +	palmas_usb->dev	 = &pdev->dev; + +	palmas_usb->id_otg_irq = regmap_irq_get_virq(palmas->irq_data, +						PALMAS_ID_OTG_IRQ); +	palmas_usb->id_irq = regmap_irq_get_virq(palmas->irq_data, +						PALMAS_ID_IRQ); +	palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data, +						PALMAS_VBUS_OTG_IRQ); +	palmas_usb->vbus_irq = regmap_irq_get_virq(palmas->irq_data, +						PALMAS_VBUS_IRQ); + +	palmas_usb_wakeup(palmas, pdata->wakeup); + +	platform_set_drvdata(pdev, palmas_usb); + +	palmas_usb->edev.name = "palmas-usb"; +	palmas_usb->edev.supported_cable = palmas_extcon_cable; +	palmas_usb->edev.mutually_exclusive = mutually_exclusive; + +	status = extcon_dev_register(&palmas_usb->edev, palmas_usb->dev); +	if (status) { +		dev_err(&pdev->dev, "failed to register extcon device\n"); +		return status; +	} + +	status = devm_request_threaded_irq(palmas_usb->dev, palmas_usb->id_irq, +			NULL, palmas_id_irq_handler, +			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, +			"palmas_usb_id", palmas_usb); +	if (status < 0) { +		dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", +					palmas_usb->id_irq, status); +		goto fail_extcon; +	} + +	status = devm_request_threaded_irq(palmas_usb->dev, +			palmas_usb->vbus_irq, NULL, palmas_vbus_irq_handler, +			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, +			"palmas_usb_vbus", palmas_usb); +	if (status < 0) { +		dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", +					palmas_usb->vbus_irq, status); +		goto fail_extcon; +	} + +	palmas_enable_irq(palmas_usb); + +	return 0; + +fail_extcon: +	extcon_dev_unregister(&palmas_usb->edev); + +	return status; +} + +static int palmas_usb_remove(struct platform_device *pdev) +{ +	struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); + +	extcon_dev_unregister(&palmas_usb->edev); + +	return 0; +} + +static struct of_device_id of_palmas_match_tbl[] = { +	{ .compatible = "ti,palmas-usb", }, +	{ .compatible = "ti,twl6035-usb", }, +	{ /* end */ } +}; + +static struct platform_driver palmas_usb_driver = { +	.probe = palmas_usb_probe, +	.remove = palmas_usb_remove, +	.driver = { +		.name = "palmas-usb", +		.of_match_table = of_palmas_match_tbl, +		.owner = THIS_MODULE, +	}, +}; + +module_platform_driver(palmas_usb_driver); + +MODULE_ALIAS("platform:palmas-usb"); +MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("Palmas USB transceiver driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, of_palmas_match_tbl); |