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/err.h>
  27#include <linux/mfd/palmas.h>
  28#include <linux/of.h>
  29#include <linux/of_platform.h>
  30
  31static const char *palmas_extcon_cable[] = {
  32        [0] = "USB",
  33        [1] = "USB-HOST",
  34        NULL,
  35};
  36
  37static const int mutually_exclusive[] = {0x3, 0x0};
  38
  39static void palmas_usb_wakeup(struct palmas *palmas, int enable)
  40{
  41        if (enable)
  42                palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP,
  43                        PALMAS_USB_WAKEUP_ID_WK_UP_COMP);
  44        else
  45                palmas_write(palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_WAKEUP, 0);
  46}
  47
  48static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
  49{
  50        struct palmas_usb *palmas_usb = _palmas_usb;
  51        unsigned int vbus_line_state;
  52
  53        palmas_read(palmas_usb->palmas, PALMAS_INTERRUPT_BASE,
  54                PALMAS_INT3_LINE_STATE, &vbus_line_state);
  55
  56        if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) {
  57                if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) {
  58                        palmas_usb->linkstat = PALMAS_USB_STATE_VBUS;
  59                        extcon_set_cable_state(&palmas_usb->edev, "USB", true);
  60                        dev_info(palmas_usb->dev, "USB cable is attached\n");
  61                } else {
  62                        dev_dbg(palmas_usb->dev,
  63                                "Spurious connect event detected\n");
  64                }
  65        } else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) {
  66                if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) {
  67                        palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
  68                        extcon_set_cable_state(&palmas_usb->edev, "USB", false);
  69                        dev_info(palmas_usb->dev, "USB cable is detached\n");
  70                } else {
  71                        dev_dbg(palmas_usb->dev,
  72                                "Spurious disconnect event detected\n");
  73                }
  74        }
  75
  76        return IRQ_HANDLED;
  77}
  78
  79static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
  80{
  81        unsigned int set;
  82        struct palmas_usb *palmas_usb = _palmas_usb;
  83
  84        palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
  85                PALMAS_USB_ID_INT_LATCH_SET, &set);
  86
  87        if (set & PALMAS_USB_ID_INT_SRC_ID_GND) {
  88                palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
  89                        PALMAS_USB_ID_INT_LATCH_CLR,
  90                        PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND);
  91                palmas_usb->linkstat = PALMAS_USB_STATE_ID;
  92                extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true);
  93                dev_info(palmas_usb->dev, "USB-HOST cable is attached\n");
  94        } else if (set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) {
  95                palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
  96                        PALMAS_USB_ID_INT_LATCH_CLR,
  97                        PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT);
  98                palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
  99                extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false);
 100                dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
 101        } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) &&
 102                                (!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {
 103                palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
 104                extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false);
 105                dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
 106        }
 107
 108        return IRQ_HANDLED;
 109}
 110
 111static void palmas_enable_irq(struct palmas_usb *palmas_usb)
 112{
 113        palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
 114                PALMAS_USB_VBUS_CTRL_SET,
 115                PALMAS_USB_VBUS_CTRL_SET_VBUS_ACT_COMP);
 116
 117        palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
 118                PALMAS_USB_ID_CTRL_SET, PALMAS_USB_ID_CTRL_SET_ID_ACT_COMP);
 119
 120        palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
 121                PALMAS_USB_ID_INT_EN_HI_SET,
 122                PALMAS_USB_ID_INT_EN_HI_SET_ID_GND |
 123                PALMAS_USB_ID_INT_EN_HI_SET_ID_FLOAT);
 124
 125        if (palmas_usb->enable_vbus_detection)
 126                palmas_vbus_irq_handler(palmas_usb->vbus_irq, palmas_usb);
 127
 128        /* cold plug for host mode needs this delay */
 129        if (palmas_usb->enable_id_detection) {
 130                msleep(30);
 131                palmas_id_irq_handler(palmas_usb->id_irq, palmas_usb);
 132        }
 133}
 134
 135static int palmas_usb_probe(struct platform_device *pdev)
 136{
 137        struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
 138        struct palmas_usb_platform_data *pdata = pdev->dev.platform_data;
 139        struct device_node *node = pdev->dev.of_node;
 140        struct palmas_usb *palmas_usb;
 141        int status;
 142
 143        palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL);
 144        if (!palmas_usb)
 145                return -ENOMEM;
 146
 147        if (node && !pdata) {
 148                palmas_usb->wakeup = of_property_read_bool(node, "ti,wakeup");
 149                palmas_usb->enable_id_detection = of_property_read_bool(node,
 150                                                "ti,enable-id-detection");
 151                palmas_usb->enable_vbus_detection = of_property_read_bool(node,
 152                                                "ti,enable-vbus-detection");
 153        } else {
 154                palmas_usb->wakeup = true;
 155                palmas_usb->enable_id_detection = true;
 156                palmas_usb->enable_vbus_detection = true;
 157
 158                if (pdata)
 159                        palmas_usb->wakeup = pdata->wakeup;
 160        }
 161
 162        palmas->usb = palmas_usb;
 163        palmas_usb->palmas = palmas;
 164
 165        palmas_usb->dev  = &pdev->dev;
 166
 167        palmas_usb->id_otg_irq = regmap_irq_get_virq(palmas->irq_data,
 168                                                PALMAS_ID_OTG_IRQ);
 169        palmas_usb->id_irq = regmap_irq_get_virq(palmas->irq_data,
 170                                                PALMAS_ID_IRQ);
 171        palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,
 172                                                PALMAS_VBUS_OTG_IRQ);
 173        palmas_usb->vbus_irq = regmap_irq_get_virq(palmas->irq_data,
 174                                                PALMAS_VBUS_IRQ);
 175
 176        palmas_usb_wakeup(palmas, palmas_usb->wakeup);
 177
 178        platform_set_drvdata(pdev, palmas_usb);
 179
 180        palmas_usb->edev.supported_cable = palmas_extcon_cable;
 181        palmas_usb->edev.mutually_exclusive = mutually_exclusive;
 182
 183        status = extcon_dev_register(&palmas_usb->edev, palmas_usb->dev);
 184        if (status) {
 185                dev_err(&pdev->dev, "failed to register extcon device\n");
 186                return status;
 187        }
 188
 189        if (palmas_usb->enable_id_detection) {
 190                status = devm_request_threaded_irq(palmas_usb->dev,
 191                                palmas_usb->id_irq,
 192                                NULL, palmas_id_irq_handler,
 193                                IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
 194                                IRQF_ONESHOT | IRQF_EARLY_RESUME,
 195                                "palmas_usb_id", palmas_usb);
 196                if (status < 0) {
 197                        dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
 198                                        palmas_usb->id_irq, status);
 199                        goto fail_extcon;
 200                }
 201        }
 202
 203        if (palmas_usb->enable_vbus_detection) {
 204                status = devm_request_threaded_irq(palmas_usb->dev,
 205                                palmas_usb->vbus_irq, NULL,
 206                                palmas_vbus_irq_handler,
 207                                IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
 208                                IRQF_ONESHOT | IRQF_EARLY_RESUME,
 209                                "palmas_usb_vbus", palmas_usb);
 210                if (status < 0) {
 211                        dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
 212                                        palmas_usb->vbus_irq, status);
 213                        goto fail_extcon;
 214                }
 215        }
 216
 217        palmas_enable_irq(palmas_usb);
 218        device_set_wakeup_capable(&pdev->dev, true);
 219        return 0;
 220
 221fail_extcon:
 222        extcon_dev_unregister(&palmas_usb->edev);
 223
 224        return status;
 225}
 226
 227static int palmas_usb_remove(struct platform_device *pdev)
 228{
 229        struct palmas_usb *palmas_usb = platform_get_drvdata(pdev);
 230
 231        extcon_dev_unregister(&palmas_usb->edev);
 232
 233        return 0;
 234}
 235
 236#ifdef CONFIG_PM_SLEEP
 237static int palmas_usb_suspend(struct device *dev)
 238{
 239        struct palmas_usb *palmas_usb = dev_get_drvdata(dev);
 240
 241        if (device_may_wakeup(dev)) {
 242                if (palmas_usb->enable_vbus_detection)
 243                        enable_irq_wake(palmas_usb->vbus_irq);
 244                if (palmas_usb->enable_id_detection)
 245                        enable_irq_wake(palmas_usb->id_irq);
 246        }
 247        return 0;
 248}
 249
 250static int palmas_usb_resume(struct device *dev)
 251{
 252        struct palmas_usb *palmas_usb = dev_get_drvdata(dev);
 253
 254        if (device_may_wakeup(dev)) {
 255                if (palmas_usb->enable_vbus_detection)
 256                        disable_irq_wake(palmas_usb->vbus_irq);
 257                if (palmas_usb->enable_id_detection)
 258                        disable_irq_wake(palmas_usb->id_irq);
 259        }
 260        return 0;
 261};
 262#endif
 263
 264static const struct dev_pm_ops palmas_pm_ops = {
 265        SET_SYSTEM_SLEEP_PM_OPS(palmas_usb_suspend,
 266                                palmas_usb_resume)
 267};
 268
 269static struct of_device_id of_palmas_match_tbl[] = {
 270        { .compatible = "ti,palmas-usb", },
 271        { .compatible = "ti,twl6035-usb", },
 272        { /* end */ }
 273};
 274
 275static struct platform_driver palmas_usb_driver = {
 276        .probe = palmas_usb_probe,
 277        .remove = palmas_usb_remove,
 278        .driver = {
 279                .name = "palmas-usb",
 280                .of_match_table = of_palmas_match_tbl,
 281                .owner = THIS_MODULE,
 282                .pm = &palmas_pm_ops,
 283        },
 284};
 285
 286module_platform_driver(palmas_usb_driver);
 287
 288MODULE_ALIAS("platform:palmas-usb");
 289MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>");
 290MODULE_DESCRIPTION("Palmas USB transceiver driver");
 291MODULE_LICENSE("GPL");
 292MODULE_DEVICE_TABLE(of, of_palmas_match_tbl);
 293
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.