linux/drivers/usb/phy/phy-tahvo.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Tahvo USB transceiver driver
   4 *
   5 * Copyright (C) 2005-2006 Nokia Corporation
   6 *
   7 * Parts copied from isp1301_omap.c.
   8 * Copyright (C) 2004 Texas Instruments
   9 * Copyright (C) 2004 David Brownell
  10 *
  11 * Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs.
  12 * Modified for Retu/Tahvo MFD by Aaro Koskinen.
  13 */
  14
  15#include <linux/io.h>
  16#include <linux/clk.h>
  17#include <linux/usb.h>
  18#include <linux/extcon-provider.h>
  19#include <linux/kernel.h>
  20#include <linux/module.h>
  21#include <linux/usb/otg.h>
  22#include <linux/mfd/retu.h>
  23#include <linux/usb/gadget.h>
  24#include <linux/platform_device.h>
  25
  26#define DRIVER_NAME     "tahvo-usb"
  27
  28#define TAHVO_REG_IDSR  0x02
  29#define TAHVO_REG_USBR  0x06
  30
  31#define USBR_SLAVE_CONTROL      (1 << 8)
  32#define USBR_VPPVIO_SW          (1 << 7)
  33#define USBR_SPEED              (1 << 6)
  34#define USBR_REGOUT             (1 << 5)
  35#define USBR_MASTER_SW2         (1 << 4)
  36#define USBR_MASTER_SW1         (1 << 3)
  37#define USBR_SLAVE_SW           (1 << 2)
  38#define USBR_NSUSPEND           (1 << 1)
  39#define USBR_SEMODE             (1 << 0)
  40
  41#define TAHVO_MODE_HOST         0
  42#define TAHVO_MODE_PERIPHERAL   1
  43
  44struct tahvo_usb {
  45        struct platform_device  *pt_dev;
  46        struct usb_phy          phy;
  47        int                     vbus_state;
  48        struct mutex            serialize;
  49        struct clk              *ick;
  50        int                     irq;
  51        int                     tahvo_mode;
  52        struct extcon_dev       *extcon;
  53};
  54
  55static const unsigned int tahvo_cable[] = {
  56        EXTCON_USB,
  57        EXTCON_USB_HOST,
  58
  59        EXTCON_NONE,
  60};
  61
  62static ssize_t vbus_show(struct device *device,
  63                               struct device_attribute *attr, char *buf)
  64{
  65        struct tahvo_usb *tu = dev_get_drvdata(device);
  66        return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off");
  67}
  68static DEVICE_ATTR_RO(vbus);
  69
  70static void check_vbus_state(struct tahvo_usb *tu)
  71{
  72        struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
  73        int reg, prev_state;
  74
  75        reg = retu_read(rdev, TAHVO_REG_IDSR);
  76        if (reg & TAHVO_STAT_VBUS) {
  77                switch (tu->phy.otg->state) {
  78                case OTG_STATE_B_IDLE:
  79                        /* Enable the gadget driver */
  80                        if (tu->phy.otg->gadget)
  81                                usb_gadget_vbus_connect(tu->phy.otg->gadget);
  82                        tu->phy.otg->state = OTG_STATE_B_PERIPHERAL;
  83                        usb_phy_set_event(&tu->phy, USB_EVENT_ENUMERATED);
  84                        break;
  85                case OTG_STATE_A_IDLE:
  86                        /*
  87                         * Session is now valid assuming the USB hub is driving
  88                         * Vbus.
  89                         */
  90                        tu->phy.otg->state = OTG_STATE_A_HOST;
  91                        break;
  92                default:
  93                        break;
  94                }
  95                dev_info(&tu->pt_dev->dev, "USB cable connected\n");
  96        } else {
  97                switch (tu->phy.otg->state) {
  98                case OTG_STATE_B_PERIPHERAL:
  99                        if (tu->phy.otg->gadget)
 100                                usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
 101                        tu->phy.otg->state = OTG_STATE_B_IDLE;
 102                        usb_phy_set_event(&tu->phy, USB_EVENT_NONE);
 103                        break;
 104                case OTG_STATE_A_HOST:
 105                        tu->phy.otg->state = OTG_STATE_A_IDLE;
 106                        break;
 107                default:
 108                        break;
 109                }
 110                dev_info(&tu->pt_dev->dev, "USB cable disconnected\n");
 111        }
 112
 113        prev_state = tu->vbus_state;
 114        tu->vbus_state = reg & TAHVO_STAT_VBUS;
 115        if (prev_state != tu->vbus_state) {
 116                extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
 117                sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state");
 118        }
 119}
 120
 121static void tahvo_usb_become_host(struct tahvo_usb *tu)
 122{
 123        struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
 124
 125        extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, true);
 126
 127        /* Power up the transceiver in USB host mode */
 128        retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND |
 129                   USBR_MASTER_SW2 | USBR_MASTER_SW1);
 130        tu->phy.otg->state = OTG_STATE_A_IDLE;
 131
 132        check_vbus_state(tu);
 133}
 134
 135static void tahvo_usb_stop_host(struct tahvo_usb *tu)
 136{
 137        tu->phy.otg->state = OTG_STATE_A_IDLE;
 138}
 139
 140static void tahvo_usb_become_peripheral(struct tahvo_usb *tu)
 141{
 142        struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
 143
 144        extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, false);
 145
 146        /* Power up transceiver and set it in USB peripheral mode */
 147        retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT |
 148                   USBR_NSUSPEND | USBR_SLAVE_SW);
 149        tu->phy.otg->state = OTG_STATE_B_IDLE;
 150
 151        check_vbus_state(tu);
 152}
 153
 154static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu)
 155{
 156        if (tu->phy.otg->gadget)
 157                usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
 158        tu->phy.otg->state = OTG_STATE_B_IDLE;
 159}
 160
 161static void tahvo_usb_power_off(struct tahvo_usb *tu)
 162{
 163        struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
 164
 165        /* Disable gadget controller if any */
 166        if (tu->phy.otg->gadget)
 167                usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
 168
 169        /* Power off transceiver */
 170        retu_write(rdev, TAHVO_REG_USBR, 0);
 171        tu->phy.otg->state = OTG_STATE_UNDEFINED;
 172}
 173
 174static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend)
 175{
 176        struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy);
 177        struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
 178        u16 w;
 179
 180        dev_dbg(&tu->pt_dev->dev, "%s\n", __func__);
 181
 182        w = retu_read(rdev, TAHVO_REG_USBR);
 183        if (suspend)
 184                w &= ~USBR_NSUSPEND;
 185        else
 186                w |= USBR_NSUSPEND;
 187        retu_write(rdev, TAHVO_REG_USBR, w);
 188
 189        return 0;
 190}
 191
 192static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
 193{
 194        struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
 195                                            phy);
 196
 197        dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, host);
 198
 199        mutex_lock(&tu->serialize);
 200
 201        if (host == NULL) {
 202                if (tu->tahvo_mode == TAHVO_MODE_HOST)
 203                        tahvo_usb_power_off(tu);
 204                otg->host = NULL;
 205                mutex_unlock(&tu->serialize);
 206                return 0;
 207        }
 208
 209        if (tu->tahvo_mode == TAHVO_MODE_HOST) {
 210                otg->host = NULL;
 211                tahvo_usb_become_host(tu);
 212        }
 213
 214        otg->host = host;
 215
 216        mutex_unlock(&tu->serialize);
 217
 218        return 0;
 219}
 220
 221static int tahvo_usb_set_peripheral(struct usb_otg *otg,
 222                                    struct usb_gadget *gadget)
 223{
 224        struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb,
 225                                            phy);
 226
 227        dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, gadget);
 228
 229        mutex_lock(&tu->serialize);
 230
 231        if (!gadget) {
 232                if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
 233                        tahvo_usb_power_off(tu);
 234                tu->phy.otg->gadget = NULL;
 235                mutex_unlock(&tu->serialize);
 236                return 0;
 237        }
 238
 239        tu->phy.otg->gadget = gadget;
 240        if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
 241                tahvo_usb_become_peripheral(tu);
 242
 243        mutex_unlock(&tu->serialize);
 244
 245        return 0;
 246}
 247
 248static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu)
 249{
 250        struct tahvo_usb *tu = _tu;
 251
 252        mutex_lock(&tu->serialize);
 253        check_vbus_state(tu);
 254        mutex_unlock(&tu->serialize);
 255
 256        return IRQ_HANDLED;
 257}
 258
 259static ssize_t otg_mode_show(struct device *device,
 260                             struct device_attribute *attr, char *buf)
 261{
 262        struct tahvo_usb *tu = dev_get_drvdata(device);
 263
 264        switch (tu->tahvo_mode) {
 265        case TAHVO_MODE_HOST:
 266                return sprintf(buf, "host\n");
 267        case TAHVO_MODE_PERIPHERAL:
 268                return sprintf(buf, "peripheral\n");
 269        }
 270
 271        return -EINVAL;
 272}
 273
 274static ssize_t otg_mode_store(struct device *device,
 275                              struct device_attribute *attr,
 276                              const char *buf, size_t count)
 277{
 278        struct tahvo_usb *tu = dev_get_drvdata(device);
 279        int r;
 280
 281        mutex_lock(&tu->serialize);
 282        if (count >= 4 && strncmp(buf, "host", 4) == 0) {
 283                if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
 284                        tahvo_usb_stop_peripheral(tu);
 285                tu->tahvo_mode = TAHVO_MODE_HOST;
 286                if (tu->phy.otg->host) {
 287                        dev_info(device, "HOST mode: host controller present\n");
 288                        tahvo_usb_become_host(tu);
 289                } else {
 290                        dev_info(device, "HOST mode: no host controller, powering off\n");
 291                        tahvo_usb_power_off(tu);
 292                }
 293                r = strlen(buf);
 294        } else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) {
 295                if (tu->tahvo_mode == TAHVO_MODE_HOST)
 296                        tahvo_usb_stop_host(tu);
 297                tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
 298                if (tu->phy.otg->gadget) {
 299                        dev_info(device, "PERIPHERAL mode: gadget driver present\n");
 300                        tahvo_usb_become_peripheral(tu);
 301                } else {
 302                        dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n");
 303                        tahvo_usb_power_off(tu);
 304                }
 305                r = strlen(buf);
 306        } else {
 307                r = -EINVAL;
 308        }
 309        mutex_unlock(&tu->serialize);
 310
 311        return r;
 312}
 313static DEVICE_ATTR_RW(otg_mode);
 314
 315static struct attribute *tahvo_attrs[] = {
 316        &dev_attr_vbus.attr,
 317        &dev_attr_otg_mode.attr,
 318        NULL
 319};
 320ATTRIBUTE_GROUPS(tahvo);
 321
 322static int tahvo_usb_probe(struct platform_device *pdev)
 323{
 324        struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
 325        struct tahvo_usb *tu;
 326        int ret;
 327
 328        tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL);
 329        if (!tu)
 330                return -ENOMEM;
 331
 332        tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg),
 333                                   GFP_KERNEL);
 334        if (!tu->phy.otg)
 335                return -ENOMEM;
 336
 337        tu->pt_dev = pdev;
 338
 339        /* Default mode */
 340#ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT
 341        tu->tahvo_mode = TAHVO_MODE_HOST;
 342#else
 343        tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
 344#endif
 345
 346        mutex_init(&tu->serialize);
 347
 348        tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick");
 349        if (!IS_ERR(tu->ick))
 350                clk_enable(tu->ick);
 351
 352        /*
 353         * Set initial state, so that we generate kevents only on state changes.
 354         */
 355        tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS;
 356
 357        tu->extcon = devm_extcon_dev_allocate(&pdev->dev, tahvo_cable);
 358        if (IS_ERR(tu->extcon)) {
 359                dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
 360                ret = PTR_ERR(tu->extcon);
 361                goto err_disable_clk;
 362        }
 363
 364        ret = devm_extcon_dev_register(&pdev->dev, tu->extcon);
 365        if (ret) {
 366                dev_err(&pdev->dev, "could not register extcon device: %d\n",
 367                        ret);
 368                goto err_disable_clk;
 369        }
 370
 371        /* Set the initial cable state. */
 372        extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST,
 373                               tu->tahvo_mode == TAHVO_MODE_HOST);
 374        extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state);
 375
 376        /* Create OTG interface */
 377        tahvo_usb_power_off(tu);
 378        tu->phy.dev = &pdev->dev;
 379        tu->phy.otg->state = OTG_STATE_UNDEFINED;
 380        tu->phy.label = DRIVER_NAME;
 381        tu->phy.set_suspend = tahvo_usb_set_suspend;
 382
 383        tu->phy.otg->usb_phy = &tu->phy;
 384        tu->phy.otg->set_host = tahvo_usb_set_host;
 385        tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral;
 386
 387        ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2);
 388        if (ret < 0) {
 389                dev_err(&pdev->dev, "cannot register USB transceiver: %d\n",
 390                        ret);
 391                goto err_disable_clk;
 392        }
 393
 394        dev_set_drvdata(&pdev->dev, tu);
 395
 396        tu->irq = platform_get_irq(pdev, 0);
 397        ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt,
 398                                   IRQF_ONESHOT,
 399                                   "tahvo-vbus", tu);
 400        if (ret) {
 401                dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n",
 402                        ret);
 403                goto err_remove_phy;
 404        }
 405
 406        return 0;
 407
 408err_remove_phy:
 409        usb_remove_phy(&tu->phy);
 410err_disable_clk:
 411        if (!IS_ERR(tu->ick))
 412                clk_disable(tu->ick);
 413
 414        return ret;
 415}
 416
 417static int tahvo_usb_remove(struct platform_device *pdev)
 418{
 419        struct tahvo_usb *tu = platform_get_drvdata(pdev);
 420
 421        free_irq(tu->irq, tu);
 422        usb_remove_phy(&tu->phy);
 423        if (!IS_ERR(tu->ick))
 424                clk_disable(tu->ick);
 425
 426        return 0;
 427}
 428
 429static struct platform_driver tahvo_usb_driver = {
 430        .probe          = tahvo_usb_probe,
 431        .remove         = tahvo_usb_remove,
 432        .driver         = {
 433                .name   = "tahvo-usb",
 434                .dev_groups = tahvo_groups,
 435        },
 436};
 437module_platform_driver(tahvo_usb_driver);
 438
 439MODULE_DESCRIPTION("Tahvo USB transceiver driver");
 440MODULE_LICENSE("GPL");
 441MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs");
 442MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
 443