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