Commit b1f254e3 authored by Graeme Gregory's avatar Graeme Gregory Committed by Greg Kroah-Hartman

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: default avatarGraeme Gregory <gg@slimlogic.co.uk>
Signed-off-by: default avatarMoiz Sonasath <m-sonasath@ti.com>
Signed-off-by: default avatarRuchika Kharwar <ruchika@ti.com>
Signed-off-by: default avatarKishon Vijay Abraham I <kishon@ti.com>
Signed-off-by: default avatarGeorge Cherian <george.cherian@ti.com>
[kishon@ti.com: adapted palmas usb driver to use the extcon framework]
Signed-off-by: default avatarSebastien Guiriec <s-guiriec@ti.com>
Signed-off-by: default avatarChanwoo Choi <cw00.choi@samsung.com>
Signed-off-by: default avatarMyungjoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 9c8a013a
EXTCON FOR TWL CHIPS
PALMAS USB COMPARATOR
Required Properties:
- compatible : Should be "ti,palmas-usb" or "ti,twl6035-usb"
- vbus-supply : phandle to the regulator device tree node.
Optional Properties:
- ti,wakeup : To enable the wakeup comparator in probe
palmas-usb {
compatible = "ti,twl6035-usb", "ti,palmas-usb";
vbus-supply = <&smps10_reg>;
ti,wakeup;
};
...@@ -53,4 +53,11 @@ config EXTCON_ARIZONA ...@@ -53,4 +53,11 @@ config EXTCON_ARIZONA
with Wolfson Arizona devices. These are audio CODECs with with Wolfson Arizona devices. These are audio CODECs with
advanced audio accessory detection support. 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 endif # MULTISTATE_SWITCH
...@@ -8,3 +8,4 @@ obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o ...@@ -8,3 +8,4 @@ obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o
obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o
/*
* 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);
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/regulator/driver.h> #include <linux/regulator/driver.h>
#include <linux/extcon.h>
#include <linux/usb/phy_companion.h>
#define PALMAS_NUM_CLIENTS 3 #define PALMAS_NUM_CLIENTS 3
...@@ -37,6 +39,12 @@ struct palmas_gpadc; ...@@ -37,6 +39,12 @@ struct palmas_gpadc;
struct palmas_resource; struct palmas_resource;
struct palmas_usb; struct palmas_usb;
enum palmas_usb_state {
PALMAS_USB_STATE_DISCONNECT,
PALMAS_USB_STATE_VBUS,
PALMAS_USB_STATE_ID,
};
struct palmas { struct palmas {
struct device *dev; struct device *dev;
...@@ -180,9 +188,6 @@ struct palmas_pmic_platform_data { ...@@ -180,9 +188,6 @@ struct palmas_pmic_platform_data {
}; };
struct palmas_usb_platform_data { struct palmas_usb_platform_data {
/* Set this if platform wishes its own vbus control */
int no_control_vbus;
/* Do we enable the wakeup comparator on probe */ /* Do we enable the wakeup comparator on probe */
int wakeup; int wakeup;
}; };
...@@ -350,22 +355,19 @@ struct palmas_usb { ...@@ -350,22 +355,19 @@ struct palmas_usb {
struct palmas *palmas; struct palmas *palmas;
struct device *dev; struct device *dev;
/* for vbus reporting with irqs disabled */ struct extcon_dev edev;
spinlock_t lock;
struct regulator *vbus_reg;
/* used to set vbus, in atomic path */ /* used to set vbus, in atomic path */
struct work_struct set_vbus_work; struct work_struct set_vbus_work;
int irq1; int id_otg_irq;
int irq2; int id_irq;
int irq3; int vbus_otg_irq;
int irq4; int vbus_irq;
int vbus_enable; int vbus_enable;
u8 linkstat; enum palmas_usb_state linkstat;
}; };
#define comparator_to_palmas(x) container_of((x), struct palmas_usb, comparator) #define comparator_to_palmas(x) container_of((x), struct palmas_usb, comparator)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment