diff mbox series

[2/2] extcon: usb-gpio: Add the GPIO level trigger support

Message ID 99c5c7c3a67768a0715e3b89b80430b75ad25a0f.1489995966.git.baolin.wang@linaro.org
State New
Headers show
Series None | expand

Commit Message

(Exiting) Baolin Wang March 20, 2017, 7:59 a.m. UTC
GPIOs may need level trigger to detect VBUS/ID on some platforms, thus
we should add GPIO level trigger to support this situation.

Signed-off-by: Baolin Wang <baolin.wang@linaro.org>

---
 drivers/extcon/extcon-usb-gpio.c |   81 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 77 insertions(+), 4 deletions(-)

-- 
1.7.9.5
diff mbox series

Patch

diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c
index a5e1882..cc89e25 100644
--- a/drivers/extcon/extcon-usb-gpio.c
+++ b/drivers/extcon/extcon-usb-gpio.c
@@ -39,6 +39,8 @@  struct usb_extcon_info {
 	struct gpio_desc *vbus_gpiod;
 	int id_irq;
 	int vbus_irq;
+	int level_trigger;
+	int trigger_irq;
 
 	unsigned long debounce_jiffies;
 	struct delayed_work wq_detcable;
@@ -70,6 +72,7 @@  struct usb_extcon_info {
 static void usb_extcon_detect_cable(struct work_struct *work)
 {
 	int id, vbus;
+	unsigned int trigger;
 	struct usb_extcon_info *info = container_of(to_delayed_work(work),
 						    struct usb_extcon_info,
 						    wq_detcable);
@@ -80,6 +83,53 @@  static void usb_extcon_detect_cable(struct work_struct *work)
 	vbus = info->vbus_gpiod ?
 		gpiod_get_value_cansleep(info->vbus_gpiod) : id;
 
+	if (info->level_trigger) {
+		if (info->trigger_irq == info->id_irq && info->id_gpiod) {
+			trigger = irqd_get_trigger_type(
+					irq_get_irq_data(info->id_irq));
+
+			/* Ignore incorrect ID trigger */
+			if (((trigger & IRQF_TRIGGER_LOW) && (id > 0)) ||
+			    ((trigger & IRQF_TRIGGER_HIGH) && (id == 0))) {
+				enable_irq(info->id_irq);
+				return;
+			}
+
+			if (id) {
+				trigger &= ~IRQF_TRIGGER_HIGH;
+				trigger |= IRQF_TRIGGER_LOW;
+			} else {
+				trigger &= ~IRQF_TRIGGER_LOW;
+				trigger |= IRQF_TRIGGER_HIGH;
+			}
+
+			irq_set_irq_type(info->id_irq, trigger);
+			enable_irq(info->id_irq);
+		} else if (info->trigger_irq == info->vbus_irq &&
+			   info->vbus_gpiod) {
+			trigger = irqd_get_trigger_type(
+					irq_get_irq_data(info->vbus_irq));
+
+			/* Ignore incorrect VBUS trigger */
+			if (((trigger & IRQF_TRIGGER_LOW) && (vbus > 0)) ||
+			    ((trigger & IRQF_TRIGGER_HIGH) && (vbus == 0))) {
+				enable_irq(info->vbus_irq);
+				return;
+			}
+
+			if (vbus) {
+				trigger &= ~IRQF_TRIGGER_HIGH;
+				trigger |= IRQF_TRIGGER_LOW;
+			} else {
+				trigger &= ~IRQF_TRIGGER_LOW;
+				trigger |= IRQF_TRIGGER_HIGH;
+			}
+
+			irq_set_irq_type(info->vbus_irq, trigger);
+			enable_irq(info->vbus_irq);
+		}
+	}
+
 	/* at first we clean states which are no longer active */
 	if (id)
 		extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false);
@@ -98,6 +148,11 @@  static irqreturn_t usb_irq_handler(int irq, void *dev_id)
 {
 	struct usb_extcon_info *info = dev_id;
 
+	if (info->level_trigger) {
+		info->trigger_irq = irq;
+		disable_irq_nosync(irq);
+	}
+
 	queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
 			   info->debounce_jiffies);
 
@@ -109,7 +164,9 @@  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;
+	unsigned long id_irqflags, vbus_irqflags;
 	int ret;
+	int id_active_low, vbus_active_low;
 
 	if (!np && !ACPI_HANDLE(dev))
 		return -EINVAL;
@@ -134,6 +191,14 @@  static int usb_extcon_probe(struct platform_device *pdev)
 	if (IS_ERR(info->vbus_gpiod))
 		return PTR_ERR(info->vbus_gpiod);
 
+	info->level_trigger = of_property_read_bool(np,
+					"extcon-gpio,level-trigger");
+	info->trigger_irq = -1;
+	id_active_low = info->id_gpiod ?
+		gpiod_is_active_low(info->id_gpiod) : 0;
+	vbus_active_low = info->vbus_gpiod ?
+		gpiod_is_active_low(info->vbus_gpiod) : 0;
+
 	info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
 	if (IS_ERR(info->edev)) {
 		dev_err(dev, "failed to allocate extcon device\n");
@@ -158,6 +223,16 @@  static int usb_extcon_probe(struct platform_device *pdev)
 
 	INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable);
 
+	if (info->level_trigger) {
+		id_irqflags = id_active_low ?
+			IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW;
+		vbus_irqflags = vbus_active_low ?
+			IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH;
+	} else {
+		id_irqflags = vbus_irqflags = IRQF_TRIGGER_RISING |
+			IRQF_TRIGGER_FALLING;
+	}
+
 	if (info->id_gpiod) {
 		info->id_irq = gpiod_to_irq(info->id_gpiod);
 		if (info->id_irq < 0) {
@@ -167,8 +242,7 @@  static int usb_extcon_probe(struct platform_device *pdev)
 
 		ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
 						usb_irq_handler,
-						IRQF_TRIGGER_RISING |
-						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+						id_irqflags | IRQF_ONESHOT,
 						pdev->name, info);
 		if (ret < 0) {
 			dev_err(dev, "failed to request handler for ID IRQ\n");
@@ -185,8 +259,7 @@  static int usb_extcon_probe(struct platform_device *pdev)
 
 		ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
 						usb_irq_handler,
-						IRQF_TRIGGER_RISING |
-						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+						vbus_irqflags | IRQF_ONESHOT,
 						pdev->name, info);
 		if (ret < 0) {
 			dev_err(dev, "failed to request handler for VBUS IRQ\n");