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