diff options
Diffstat (limited to 'drivers/extcon/extcon-palmas.c')
-rw-r--r-- | drivers/extcon/extcon-palmas.c | 134 |
1 files changed, 116 insertions, 18 deletions
diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index eebdf2a33..93c30a885 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -28,6 +28,11 @@ #include <linux/mfd/palmas.h> #include <linux/of.h> #include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/workqueue.h> + +#define USB_GPIO_DEBOUNCE_MS 20 /* ms */ static const unsigned int palmas_extcon_cable[] = { EXTCON_USB, @@ -35,8 +40,6 @@ static const unsigned int palmas_extcon_cable[] = { EXTCON_NONE, }; -static const int mutually_exclusive[] = {0x3, 0x0}; - static void palmas_usb_wakeup(struct palmas *palmas, int enable) { if (enable) @@ -120,19 +123,54 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) return IRQ_HANDLED; } +static void palmas_gpio_id_detect(struct work_struct *work) +{ + int id; + struct palmas_usb *palmas_usb = container_of(to_delayed_work(work), + struct palmas_usb, + wq_detectid); + struct extcon_dev *edev = palmas_usb->edev; + + if (!palmas_usb->id_gpiod) + return; + + id = gpiod_get_value_cansleep(palmas_usb->id_gpiod); + + if (id) { + extcon_set_cable_state_(edev, EXTCON_USB_HOST, false); + dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); + } else { + extcon_set_cable_state_(edev, EXTCON_USB_HOST, true); + dev_info(palmas_usb->dev, "USB-HOST cable is attached\n"); + } +} + +static irqreturn_t palmas_gpio_id_irq_handler(int irq, void *_palmas_usb) +{ + struct palmas_usb *palmas_usb = _palmas_usb; + + queue_delayed_work(system_power_efficient_wq, &palmas_usb->wq_detectid, + palmas_usb->sw_debounce_jiffies); + + 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); + if (palmas_usb->enable_id_detection) { + 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_USB_ID_INT_EN_HI_SET_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_USB_ID_INT_EN_HI_SET_ID_FLOAT); + } if (palmas_usb->enable_vbus_detection) palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb); @@ -171,20 +209,37 @@ static int palmas_usb_probe(struct platform_device *pdev) palmas_usb->wakeup = pdata->wakeup; } + palmas_usb->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", + GPIOD_IN); + if (IS_ERR(palmas_usb->id_gpiod)) { + dev_err(&pdev->dev, "failed to get id gpio\n"); + return PTR_ERR(palmas_usb->id_gpiod); + } + + if (palmas_usb->enable_id_detection && palmas_usb->id_gpiod) { + palmas_usb->enable_id_detection = false; + palmas_usb->enable_gpio_id_detection = true; + } + + if (palmas_usb->enable_gpio_id_detection) { + u32 debounce; + + if (of_property_read_u32(node, "debounce-delay-ms", &debounce)) + debounce = USB_GPIO_DEBOUNCE_MS; + + status = gpiod_set_debounce(palmas_usb->id_gpiod, + debounce * 1000); + if (status < 0) + palmas_usb->sw_debounce_jiffies = msecs_to_jiffies(debounce); + } + + INIT_DELAYED_WORK(&palmas_usb->wq_detectid, palmas_gpio_id_detect); + 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, palmas_usb->wakeup); platform_set_drvdata(pdev, palmas_usb); @@ -195,7 +250,6 @@ static int palmas_usb_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to allocate extcon device\n"); return -ENOMEM; } - palmas_usb->edev->mutually_exclusive = mutually_exclusive; status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev); if (status) { @@ -204,6 +258,10 @@ static int palmas_usb_probe(struct platform_device *pdev) } if (palmas_usb->enable_id_detection) { + 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); status = devm_request_threaded_irq(palmas_usb->dev, palmas_usb->id_irq, NULL, palmas_id_irq_handler, @@ -215,9 +273,33 @@ static int palmas_usb_probe(struct platform_device *pdev) palmas_usb->id_irq, status); return status; } + } else if (palmas_usb->enable_gpio_id_detection) { + palmas_usb->gpio_id_irq = gpiod_to_irq(palmas_usb->id_gpiod); + if (palmas_usb->gpio_id_irq < 0) { + dev_err(&pdev->dev, "failed to get id irq\n"); + return palmas_usb->gpio_id_irq; + } + status = devm_request_threaded_irq(&pdev->dev, + palmas_usb->gpio_id_irq, + NULL, + palmas_gpio_id_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "palmas_usb_id", + palmas_usb); + if (status < 0) { + dev_err(&pdev->dev, + "failed to request handler for id irq\n"); + return status; + } } if (palmas_usb->enable_vbus_detection) { + 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); status = devm_request_threaded_irq(palmas_usb->dev, palmas_usb->vbus_irq, NULL, palmas_vbus_irq_handler, @@ -232,10 +314,21 @@ static int palmas_usb_probe(struct platform_device *pdev) } palmas_enable_irq(palmas_usb); + /* perform initial detection */ + palmas_gpio_id_detect(&palmas_usb->wq_detectid.work); device_set_wakeup_capable(&pdev->dev, true); return 0; } +static int palmas_usb_remove(struct platform_device *pdev) +{ + struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&palmas_usb->wq_detectid); + + return 0; +} + #ifdef CONFIG_PM_SLEEP static int palmas_usb_suspend(struct device *dev) { @@ -246,6 +339,8 @@ static int palmas_usb_suspend(struct device *dev) enable_irq_wake(palmas_usb->vbus_irq); if (palmas_usb->enable_id_detection) enable_irq_wake(palmas_usb->id_irq); + if (palmas_usb->enable_gpio_id_detection) + enable_irq_wake(palmas_usb->gpio_id_irq); } return 0; } @@ -259,6 +354,8 @@ static int palmas_usb_resume(struct device *dev) disable_irq_wake(palmas_usb->vbus_irq); if (palmas_usb->enable_id_detection) disable_irq_wake(palmas_usb->id_irq); + if (palmas_usb->enable_gpio_id_detection) + disable_irq_wake(palmas_usb->gpio_id_irq); } return 0; }; @@ -276,6 +373,7 @@ static const struct of_device_id of_palmas_match_tbl[] = { 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, |