summaryrefslogtreecommitdiff
path: root/drivers/extcon/extcon-palmas.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/extcon/extcon-palmas.c')
-rw-r--r--drivers/extcon/extcon-palmas.c216
1 files changed, 187 insertions, 29 deletions
diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c
index b8defc71d444..db5dd52922e8 100644
--- a/drivers/extcon/extcon-palmas.c
+++ b/drivers/extcon/extcon-palmas.c
@@ -31,10 +31,25 @@
#include <linux/mfd/palmas.h>
#include <linux/of.h>
#include <linux/of_platform.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+
+enum palmas_usb_cable_id {
+ USB_CABLE_INIT,
+ USB_CABLE_VBUS,
+ USB_CABLE_ID_GND,
+ USB_CABLE_ID_A,
+ USB_CABLE_ID_B,
+ USB_CABLE_ID_C,
+ USB_CABLE_ID_FLOAT,
+};
static const char *const palmas_extcon_cable[] = {
[0] = "USB",
[1] = "USB-Host",
+ [2] = "USB-ID-A",
+ [3] = "USB-ID-B",
+ [4] = "USB-ID-C",
NULL,
};
@@ -49,6 +64,145 @@ static void palmas_usb_wakeup(struct palmas *palmas, int enable)
palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 0);
}
+static void palmas_usb_id_int_set(struct palmas_usb *palmas_usb)
+{
+ unsigned int all_int;
+
+ all_int = PALMAS_USB_ID_INT_SRC_ID_GND |
+ PALMAS_USB_ID_INT_SRC_ID_A |
+ PALMAS_USB_ID_INT_SRC_ID_B |
+ PALMAS_USB_ID_INT_SRC_ID_C |
+ PALMAS_USB_ID_INT_SRC_ID_FLOAT;
+ if (palmas_usb->id_linkstat == PALMAS_USB_STATE_ID_FLOAT) {
+ palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
+ PALMAS_USB_ID_INT_EN_HI_SET, all_int);
+ palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
+ PALMAS_USB_ID_INT_EN_LO_CLR, all_int);
+ } else {
+ palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
+ PALMAS_USB_ID_INT_EN_HI_CLR, all_int);
+ palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
+ PALMAS_USB_ID_INT_EN_HI_SET,
+ PALMAS_USB_ID_INT_SRC_ID_FLOAT);
+ palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
+ PALMAS_USB_ID_INT_EN_LO_CLR, all_int);
+ palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
+ PALMAS_USB_ID_INT_EN_LO_SET,
+ all_int ^ PALMAS_USB_ID_INT_SRC_ID_FLOAT);
+ }
+}
+
+static int palmas_usb_id_state_update(struct palmas_usb *palmas_usb)
+{
+ unsigned int id_src;
+ int ret;
+ int new_state;
+ int new_cable_index;
+ int retry = 5;
+
+src_again:
+ ret = palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
+ PALMAS_USB_ID_INT_SRC, &id_src);
+ if (ret < 0) {
+ dev_err(palmas_usb->dev, "ID_INT_SRC read failed: %d\n", ret);
+ return ret;
+ }
+
+ dev_info(palmas_usb->dev, "id-state: 0x%02x\n", id_src);
+ if (!id_src) {
+ dev_err(palmas_usb->dev, "Improper ID state found\n");
+ if (retry--) {
+ msleep(200);
+ goto src_again;
+ }
+ return -EAGAIN;
+ }
+
+ /* If two ID states shows sign then allow for debauncing to settled. */
+ if (id_src & (id_src - 1)) {
+ dev_info(palmas_usb->dev,
+ "ID states are not settled, try later\n");
+ return -EAGAIN;
+ }
+
+ if (id_src & PALMAS_USB_ID_INT_SRC_ID_GND) {
+ new_state = PALMAS_USB_STATE_ID_GND;
+ new_cable_index = 1;
+ } else if (id_src & PALMAS_USB_ID_INT_SRC_ID_A) {
+ new_state = PALMAS_USB_STATE_ID_A;
+ new_cable_index = 2;
+ } else if (id_src & PALMAS_USB_ID_INT_SRC_ID_B) {
+ new_state = PALMAS_USB_STATE_ID_B;
+ new_cable_index = 3;
+ } else if (id_src & PALMAS_USB_ID_INT_SRC_ID_C) {
+ new_state = PALMAS_USB_STATE_ID_C;
+ new_cable_index = 4;
+ } else if (id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT) {
+ new_state = PALMAS_USB_STATE_ID_FLOAT;
+ new_cable_index = -1;
+ } else {
+ dev_info(palmas_usb->dev, "ID_SRC is not valid\n");
+ new_state = PALMAS_USB_STATE_ID_FLOAT;
+ new_cable_index = 0;
+ }
+
+ if (palmas_usb->id_linkstat == new_state) {
+ dev_info(palmas_usb->dev,
+ "No change in ID state: Old %d and New %d\n",
+ palmas_usb->id_linkstat, new_state);
+ palmas_usb_id_int_set(palmas_usb);
+ return 0;
+ }
+
+ if (palmas_usb->cur_cable_index > 0) {
+ dev_info(palmas_usb->dev, "Cable %s detached\n",
+ palmas_extcon_cable[palmas_usb->cur_cable_index]);
+ extcon_set_cable_state(&palmas_usb->edev,
+ palmas_extcon_cable[palmas_usb->cur_cable_index],
+ false);
+ }
+
+ if ((new_cable_index < 0) && (!palmas_usb->cur_cable_index)) {
+ dev_info(palmas_usb->dev, "All cable detached\n");
+ extcon_set_cable_state(&palmas_usb->edev,
+ palmas_extcon_cable[1], false);
+ extcon_set_cable_state(&palmas_usb->edev,
+ palmas_extcon_cable[2], false);
+ extcon_set_cable_state(&palmas_usb->edev,
+ palmas_extcon_cable[3], false);
+ extcon_set_cable_state(&palmas_usb->edev,
+ palmas_extcon_cable[4], false);
+ }
+
+ palmas_usb->cur_cable_index = new_cable_index;
+ palmas_usb->id_linkstat = new_state;
+ if (palmas_usb->cur_cable_index <= 0)
+ goto end;
+
+ dev_info(palmas_usb->dev, "Cable %s attached\n",
+ palmas_extcon_cable[palmas_usb->cur_cable_index]);
+ extcon_set_cable_state(&palmas_usb->edev,
+ palmas_extcon_cable[palmas_usb->cur_cable_index], true);
+
+end:
+ palmas_usb_id_int_set(palmas_usb);
+ return 0;
+}
+
+static void palmas_usb_id_st_wq(struct work_struct *work)
+{
+ struct palmas_usb *palmas_usb;
+ int ret;
+
+ palmas_usb = container_of(work, struct palmas_usb,
+ cable_update_wq.work);
+ ret = palmas_usb_id_state_update(palmas_usb);
+ if (ret == -EAGAIN)
+ schedule_delayed_work(&palmas_usb->cable_update_wq,
+ msecs_to_jiffies(palmas_usb->cable_debaunce_time));
+}
+
+
static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
{
struct palmas_usb *palmas_usb = _palmas_usb;
@@ -90,33 +244,18 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_ID_INT_LATCH_SET, &set);
- dev_info(palmas_usb->dev, "id-irq() ID_INT_LATCH_SET 0x%02x\n", set);
- if (set & PALMAS_USB_ID_INT_SRC_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->id_linkstat = PALMAS_USB_STATE_ID;
- extcon_set_cable_state(&palmas_usb->edev, "USB-Host", true);
- dev_info(palmas_usb->dev, "USB-HOST cable is attached\n");
- } else if (set & PALMAS_USB_ID_INT_SRC_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->id_linkstat = PALMAS_USB_STATE_DISCONNECT;
- extcon_set_cable_state(&palmas_usb->edev, "USB-Host", false);
- dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
- } else if ((palmas_usb->id_linkstat == PALMAS_USB_STATE_ID) &&
- (!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {
- palmas_usb->id_linkstat = PALMAS_USB_STATE_DISCONNECT;
- extcon_set_cable_state(&palmas_usb->edev, "USB-Host", false);
- dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
- }
+ palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
+ PALMAS_USB_ID_INT_LATCH_CLR, set);
+ schedule_delayed_work(&palmas_usb->cable_update_wq,
+ msecs_to_jiffies(palmas_usb->cable_debaunce_time));
return IRQ_HANDLED;
}
static void palmas_enable_irq(struct palmas_usb *palmas_usb)
{
+ int ret;
+
palmas_usb->vbus_linkstat = PALMAS_USB_STATE_INIT;
palmas_usb->id_linkstat = PALMAS_USB_STATE_INIT;
palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
@@ -124,20 +263,21 @@ static void palmas_enable_irq(struct palmas_usb *palmas_usb)
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_USB_ID_CTRL_CLEAR, PALMAS_USB_ID_CTRL_SET_ID_SRC_5U);
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_USB_ID_INT_EN_HI_SET_ID_FLOAT);
+ PALMAS_USB_ID_CTRL_SET, PALMAS_USB_ID_CTRL_SET_ID_SRC_16U |
+ PALMAS_USB_ID_CTRL_SET_ID_ACT_COMP);
if (palmas_usb->enable_vbus_detection)
palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb);
- /* cold plug for host mode needs this delay */
if (palmas_usb->enable_id_detection) {
- msleep(30);
- palmas_id_irq_handler(palmas_usb->id_irq, palmas_usb);
+ /* Wait for the comparator to update status */
+ msleep(palmas_usb->cable_debaunce_time);
+ ret = palmas_usb_id_state_update(palmas_usb);
+ if (ret == -EAGAIN)
+ schedule_delayed_work(&palmas_usb->cable_update_wq,
+ msecs_to_jiffies(palmas_usb->cable_debaunce_time));
}
}
@@ -182,6 +322,7 @@ static int palmas_usb_probe(struct platform_device *pdev)
palmas_usb->palmas = palmas;
palmas_usb->dev = &pdev->dev;
+ palmas_usb->cable_debaunce_time = 300;
palmas_usb->id_otg_irq = palmas_irq_get_virq(palmas, PALMAS_ID_OTG_IRQ);
palmas_usb->id_irq = palmas_irq_get_virq(palmas, PALMAS_ID_IRQ);
@@ -216,6 +357,19 @@ static int palmas_usb_probe(struct platform_device *pdev)
palmas_usb->id_irq, status);
goto fail_extcon;
}
+ status = devm_request_threaded_irq(palmas_usb->dev,
+ palmas_usb->id_otg_irq,
+ NULL, palmas_id_irq_handler,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT | IRQF_EARLY_RESUME,
+ "palmas_usb_id-otg", 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;
+ }
+ INIT_DELAYED_WORK(&palmas_usb->cable_update_wq,
+ palmas_usb_id_st_wq);
}
if (palmas_usb->enable_vbus_detection) {
@@ -238,6 +392,8 @@ static int palmas_usb_probe(struct platform_device *pdev)
fail_extcon:
extcon_dev_unregister(&palmas_usb->edev);
+ if (palmas_usb->enable_id_detection)
+ cancel_delayed_work(&palmas_usb->cable_update_wq);
return status;
}
@@ -248,6 +404,8 @@ static int palmas_usb_remove(struct platform_device *pdev)
extcon_dev_unregister(&palmas_usb->edev);
+ if (palmas_usb->enable_id_detection)
+ cancel_delayed_work(&palmas_usb->cable_update_wq);
return 0;
}