434 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Palmas USB transceiver driver4 *5 * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com6 * Author: Graeme Gregory <gg@slimlogic.co.uk>7 * Author: Kishon Vijay Abraham I <kishon@ti.com>8 * Based on twl6030_usb.c9 * Author: Hema HK <hemahk@ti.com>10 */11 12#include <linux/devm-helpers.h>13#include <linux/module.h>14#include <linux/interrupt.h>15#include <linux/platform_device.h>16#include <linux/slab.h>17#include <linux/err.h>18#include <linux/mfd/palmas.h>19#include <linux/of.h>20#include <linux/of_platform.h>21#include <linux/gpio/consumer.h>22#include <linux/workqueue.h>23 24#define USB_GPIO_DEBOUNCE_MS 20 /* ms */25 26static const unsigned int palmas_extcon_cable[] = {27 EXTCON_USB,28 EXTCON_USB_HOST,29 EXTCON_NONE,30};31 32static void palmas_usb_wakeup(struct palmas *palmas, int enable)33{34 if (enable)35 palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP,36 PALMAS_USB_WAKEUP_ID_WK_UP_COMP);37 else38 palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 0);39}40 41static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)42{43 struct palmas_usb *palmas_usb = _palmas_usb;44 struct extcon_dev *edev = palmas_usb->edev;45 unsigned int vbus_line_state;46 47 palmas_read(palmas_usb->palmas, PALMAS_INTERRUPT_BASE,48 PALMAS_INT3_LINE_STATE, &vbus_line_state);49 50 if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) {51 if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) {52 palmas_usb->linkstat = PALMAS_USB_STATE_VBUS;53 extcon_set_state_sync(edev, EXTCON_USB, true);54 dev_dbg(palmas_usb->dev, "USB cable is attached\n");55 } else {56 dev_dbg(palmas_usb->dev,57 "Spurious connect event detected\n");58 }59 } else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) {60 if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) {61 palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;62 extcon_set_state_sync(edev, EXTCON_USB, false);63 dev_dbg(palmas_usb->dev, "USB cable is detached\n");64 } else {65 dev_dbg(palmas_usb->dev,66 "Spurious disconnect event detected\n");67 }68 }69 70 return IRQ_HANDLED;71}72 73static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)74{75 unsigned int set, id_src;76 struct palmas_usb *palmas_usb = _palmas_usb;77 struct extcon_dev *edev = palmas_usb->edev;78 79 palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,80 PALMAS_USB_ID_INT_LATCH_SET, &set);81 palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,82 PALMAS_USB_ID_INT_SRC, &id_src);83 84 if ((set & PALMAS_USB_ID_INT_SRC_ID_GND) &&85 (id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {86 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,87 PALMAS_USB_ID_INT_LATCH_CLR,88 PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND);89 palmas_usb->linkstat = PALMAS_USB_STATE_ID;90 extcon_set_state_sync(edev, EXTCON_USB_HOST, true);91 dev_dbg(palmas_usb->dev, "USB-HOST cable is attached\n");92 } else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) &&93 (id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) {94 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,95 PALMAS_USB_ID_INT_LATCH_CLR,96 PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT);97 palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;98 extcon_set_state_sync(edev, EXTCON_USB_HOST, false);99 dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");100 } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) &&101 (!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {102 palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;103 extcon_set_state_sync(edev, EXTCON_USB_HOST, false);104 dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");105 } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) &&106 (id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {107 palmas_usb->linkstat = PALMAS_USB_STATE_ID;108 extcon_set_state_sync(edev, EXTCON_USB_HOST, true);109 dev_dbg(palmas_usb->dev, "USB-HOST cable is attached\n");110 }111 112 return IRQ_HANDLED;113}114 115static void palmas_gpio_id_detect(struct work_struct *work)116{117 int id;118 struct palmas_usb *palmas_usb = container_of(to_delayed_work(work),119 struct palmas_usb,120 wq_detectid);121 struct extcon_dev *edev = palmas_usb->edev;122 123 if (!palmas_usb->id_gpiod)124 return;125 126 id = gpiod_get_value_cansleep(palmas_usb->id_gpiod);127 128 if (id) {129 extcon_set_state_sync(edev, EXTCON_USB_HOST, false);130 dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");131 } else {132 extcon_set_state_sync(edev, EXTCON_USB_HOST, true);133 dev_dbg(palmas_usb->dev, "USB-HOST cable is attached\n");134 }135}136 137static irqreturn_t palmas_gpio_id_irq_handler(int irq, void *_palmas_usb)138{139 struct palmas_usb *palmas_usb = _palmas_usb;140 141 queue_delayed_work(system_power_efficient_wq, &palmas_usb->wq_detectid,142 palmas_usb->sw_debounce_jiffies);143 144 return IRQ_HANDLED;145}146 147static void palmas_enable_irq(struct palmas_usb *palmas_usb)148{149 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,150 PALMAS_USB_VBUS_CTRL_SET,151 PALMAS_USB_VBUS_CTRL_SET_VBUS_ACT_COMP);152 153 if (palmas_usb->enable_id_detection) {154 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,155 PALMAS_USB_ID_CTRL_SET,156 PALMAS_USB_ID_CTRL_SET_ID_ACT_COMP);157 158 palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,159 PALMAS_USB_ID_INT_EN_HI_SET,160 PALMAS_USB_ID_INT_EN_HI_SET_ID_GND |161 PALMAS_USB_ID_INT_EN_HI_SET_ID_FLOAT);162 }163 164 if (palmas_usb->enable_vbus_detection)165 palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb);166 167 /* cold plug for host mode needs this delay */168 if (palmas_usb->enable_id_detection) {169 msleep(30);170 palmas_id_irq_handler(palmas_usb->id_irq, palmas_usb);171 }172}173 174static int palmas_usb_probe(struct platform_device *pdev)175{176 struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);177 struct palmas_usb_platform_data *pdata = dev_get_platdata(&pdev->dev);178 struct device_node *node = pdev->dev.of_node;179 struct palmas_usb *palmas_usb;180 int status;181 182 if (!palmas) {183 dev_err(&pdev->dev, "failed to get valid parent\n");184 return -EINVAL;185 }186 187 palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL);188 if (!palmas_usb)189 return -ENOMEM;190 191 if (node && !pdata) {192 palmas_usb->wakeup = of_property_read_bool(node, "ti,wakeup");193 palmas_usb->enable_id_detection = of_property_read_bool(node,194 "ti,enable-id-detection");195 palmas_usb->enable_vbus_detection = of_property_read_bool(node,196 "ti,enable-vbus-detection");197 } else {198 palmas_usb->wakeup = true;199 palmas_usb->enable_id_detection = true;200 palmas_usb->enable_vbus_detection = true;201 202 if (pdata)203 palmas_usb->wakeup = pdata->wakeup;204 }205 206 palmas_usb->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id",207 GPIOD_IN);208 if (IS_ERR(palmas_usb->id_gpiod))209 return dev_err_probe(&pdev->dev, PTR_ERR(palmas_usb->id_gpiod),210 "failed to get id gpio\n");211 212 palmas_usb->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus",213 GPIOD_IN);214 if (IS_ERR(palmas_usb->vbus_gpiod))215 return dev_err_probe(&pdev->dev, PTR_ERR(palmas_usb->vbus_gpiod),216 "failed to get id gpio\n");217 218 if (palmas_usb->enable_id_detection && palmas_usb->id_gpiod) {219 palmas_usb->enable_id_detection = false;220 palmas_usb->enable_gpio_id_detection = true;221 }222 223 if (palmas_usb->enable_vbus_detection && palmas_usb->vbus_gpiod) {224 palmas_usb->enable_vbus_detection = false;225 palmas_usb->enable_gpio_vbus_detection = true;226 }227 228 if (palmas_usb->enable_gpio_id_detection) {229 u32 debounce;230 231 if (of_property_read_u32(node, "debounce-delay-ms", &debounce))232 debounce = USB_GPIO_DEBOUNCE_MS;233 234 status = gpiod_set_debounce(palmas_usb->id_gpiod,235 debounce * 1000);236 if (status < 0)237 palmas_usb->sw_debounce_jiffies = msecs_to_jiffies(debounce);238 }239 240 status = devm_delayed_work_autocancel(&pdev->dev,241 &palmas_usb->wq_detectid,242 palmas_gpio_id_detect);243 if (status)244 return status;245 246 palmas->usb = palmas_usb;247 palmas_usb->palmas = palmas;248 249 palmas_usb->dev = &pdev->dev;250 251 palmas_usb_wakeup(palmas, palmas_usb->wakeup);252 253 platform_set_drvdata(pdev, palmas_usb);254 255 palmas_usb->edev = devm_extcon_dev_allocate(&pdev->dev,256 palmas_extcon_cable);257 if (IS_ERR(palmas_usb->edev)) {258 dev_err(&pdev->dev, "failed to allocate extcon device\n");259 return -ENOMEM;260 }261 262 status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev);263 if (status) {264 dev_err(&pdev->dev, "failed to register extcon device\n");265 return status;266 }267 268 if (palmas_usb->enable_id_detection) {269 palmas_usb->id_otg_irq = regmap_irq_get_virq(palmas->irq_data,270 PALMAS_ID_OTG_IRQ);271 palmas_usb->id_irq = regmap_irq_get_virq(palmas->irq_data,272 PALMAS_ID_IRQ);273 status = devm_request_threaded_irq(palmas_usb->dev,274 palmas_usb->id_irq,275 NULL, palmas_id_irq_handler,276 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |277 IRQF_ONESHOT,278 "palmas_usb_id", palmas_usb);279 if (status < 0) {280 dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",281 palmas_usb->id_irq, status);282 return status;283 }284 } else if (palmas_usb->enable_gpio_id_detection) {285 palmas_usb->gpio_id_irq = gpiod_to_irq(palmas_usb->id_gpiod);286 if (palmas_usb->gpio_id_irq < 0) {287 dev_err(&pdev->dev, "failed to get id irq\n");288 return palmas_usb->gpio_id_irq;289 }290 status = devm_request_threaded_irq(&pdev->dev,291 palmas_usb->gpio_id_irq,292 NULL,293 palmas_gpio_id_irq_handler,294 IRQF_TRIGGER_RISING |295 IRQF_TRIGGER_FALLING |296 IRQF_ONESHOT,297 "palmas_usb_id",298 palmas_usb);299 if (status < 0) {300 dev_err(&pdev->dev,301 "failed to request handler for id irq\n");302 return status;303 }304 }305 306 if (palmas_usb->enable_vbus_detection) {307 palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,308 PALMAS_VBUS_OTG_IRQ);309 palmas_usb->vbus_irq = regmap_irq_get_virq(palmas->irq_data,310 PALMAS_VBUS_IRQ);311 status = devm_request_threaded_irq(palmas_usb->dev,312 palmas_usb->vbus_irq, NULL,313 palmas_vbus_irq_handler,314 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |315 IRQF_ONESHOT,316 "palmas_usb_vbus", palmas_usb);317 if (status < 0) {318 dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",319 palmas_usb->vbus_irq, status);320 return status;321 }322 } else if (palmas_usb->enable_gpio_vbus_detection) {323 /* remux GPIO_1 as VBUSDET */324 status = palmas_update_bits(palmas,325 PALMAS_PU_PD_OD_BASE,326 PALMAS_PRIMARY_SECONDARY_PAD1,327 PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_MASK,328 (1 << PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_SHIFT));329 if (status < 0) {330 dev_err(&pdev->dev, "can't remux GPIO1\n");331 return status;332 }333 334 palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,335 PALMAS_VBUS_OTG_IRQ);336 palmas_usb->gpio_vbus_irq = gpiod_to_irq(palmas_usb->vbus_gpiod);337 if (palmas_usb->gpio_vbus_irq < 0) {338 dev_err(&pdev->dev, "failed to get vbus irq\n");339 return palmas_usb->gpio_vbus_irq;340 }341 status = devm_request_threaded_irq(&pdev->dev,342 palmas_usb->gpio_vbus_irq,343 NULL,344 palmas_vbus_irq_handler,345 IRQF_TRIGGER_FALLING |346 IRQF_TRIGGER_RISING |347 IRQF_ONESHOT,348 "palmas_usb_vbus",349 palmas_usb);350 if (status < 0) {351 dev_err(&pdev->dev,352 "failed to request handler for vbus irq\n");353 return status;354 }355 }356 357 palmas_enable_irq(palmas_usb);358 /* perform initial detection */359 if (palmas_usb->enable_gpio_vbus_detection)360 palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb);361 palmas_gpio_id_detect(&palmas_usb->wq_detectid.work);362 device_set_wakeup_capable(&pdev->dev, true);363 return 0;364}365 366#ifdef CONFIG_PM_SLEEP367static int palmas_usb_suspend(struct device *dev)368{369 struct palmas_usb *palmas_usb = dev_get_drvdata(dev);370 371 if (device_may_wakeup(dev)) {372 if (palmas_usb->enable_vbus_detection)373 enable_irq_wake(palmas_usb->vbus_irq);374 if (palmas_usb->enable_gpio_vbus_detection)375 enable_irq_wake(palmas_usb->gpio_vbus_irq);376 if (palmas_usb->enable_id_detection)377 enable_irq_wake(palmas_usb->id_irq);378 if (palmas_usb->enable_gpio_id_detection)379 enable_irq_wake(palmas_usb->gpio_id_irq);380 }381 return 0;382}383 384static int palmas_usb_resume(struct device *dev)385{386 struct palmas_usb *palmas_usb = dev_get_drvdata(dev);387 388 if (device_may_wakeup(dev)) {389 if (palmas_usb->enable_vbus_detection)390 disable_irq_wake(palmas_usb->vbus_irq);391 if (palmas_usb->enable_gpio_vbus_detection)392 disable_irq_wake(palmas_usb->gpio_vbus_irq);393 if (palmas_usb->enable_id_detection)394 disable_irq_wake(palmas_usb->id_irq);395 if (palmas_usb->enable_gpio_id_detection)396 disable_irq_wake(palmas_usb->gpio_id_irq);397 }398 399 /* check if GPIO states changed while suspend/resume */400 if (palmas_usb->enable_gpio_vbus_detection)401 palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb);402 palmas_gpio_id_detect(&palmas_usb->wq_detectid.work);403 404 return 0;405};406#endif407 408static SIMPLE_DEV_PM_OPS(palmas_pm_ops, palmas_usb_suspend, palmas_usb_resume);409 410static const struct of_device_id of_palmas_match_tbl[] = {411 { .compatible = "ti,palmas-usb", },412 { .compatible = "ti,palmas-usb-vid", },413 { .compatible = "ti,twl6035-usb", },414 { .compatible = "ti,twl6035-usb-vid", },415 { /* end */ }416};417 418static struct platform_driver palmas_usb_driver = {419 .probe = palmas_usb_probe,420 .driver = {421 .name = "palmas-usb",422 .of_match_table = of_palmas_match_tbl,423 .pm = &palmas_pm_ops,424 },425};426 427module_platform_driver(palmas_usb_driver);428 429MODULE_ALIAS("platform:palmas-usb");430MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>");431MODULE_DESCRIPTION("Palmas USB transceiver driver");432MODULE_LICENSE("GPL");433MODULE_DEVICE_TABLE(of, of_palmas_match_tbl);434