linux/drivers/usb/host/ohci-s3c2410.c
<<
>>
Prefs
   1/*
   2 * OHCI HCD (Host Controller Driver) for USB.
   3 *
   4 * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
   5 * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
   6 * (C) Copyright 2002 Hewlett-Packard Company
   7 *
   8 * USB Bus Glue for Samsung S3C2410
   9 *
  10 * Written by Christopher Hoover <ch@hpl.hp.com>
  11 * Based on fragments of previous driver by Russell King et al.
  12 *
  13 * Modified for S3C2410 from ohci-sa1111.c, ohci-omap.c and ohci-lh7a40.c
  14 *      by Ben Dooks, <ben@simtec.co.uk>
  15 *      Copyright (C) 2004 Simtec Electronics
  16 *
  17 * Thanks to basprog@mail.ru for updates to newer kernels
  18 *
  19 * This file is licenced under the GPL.
  20*/
  21
  22#include <linux/clk.h>
  23#include <linux/io.h>
  24#include <linux/kernel.h>
  25#include <linux/module.h>
  26#include <linux/platform_device.h>
  27#include <linux/platform_data/usb-ohci-s3c2410.h>
  28#include <linux/usb.h>
  29#include <linux/usb/hcd.h>
  30
  31#include "ohci.h"
  32
  33
  34#define valid_port(idx) ((idx) == 1 || (idx) == 2)
  35
  36/* clock device associated with the hcd */
  37
  38
  39#define DRIVER_DESC "OHCI S3C2410 driver"
  40
  41static const char hcd_name[] = "ohci-s3c2410";
  42
  43static struct clk *clk;
  44static struct clk *usb_clk;
  45
  46/* forward definitions */
  47
  48static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc);
  49
  50/* conversion functions */
  51
  52static struct s3c2410_hcd_info *to_s3c2410_info(struct usb_hcd *hcd)
  53{
  54        return dev_get_platdata(hcd->self.controller);
  55}
  56
  57static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd)
  58{
  59        struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
  60
  61        dev_dbg(&dev->dev, "s3c2410_start_hc:\n");
  62
  63        clk_prepare_enable(usb_clk);
  64        mdelay(2);                      /* let the bus clock stabilise */
  65
  66        clk_prepare_enable(clk);
  67
  68        if (info != NULL) {
  69                info->hcd       = hcd;
  70                info->report_oc = s3c2410_hcd_oc;
  71
  72                if (info->enable_oc != NULL)
  73                        (info->enable_oc)(info, 1);
  74        }
  75}
  76
  77static void s3c2410_stop_hc(struct platform_device *dev)
  78{
  79        struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
  80
  81        dev_dbg(&dev->dev, "s3c2410_stop_hc:\n");
  82
  83        if (info != NULL) {
  84                info->report_oc = NULL;
  85                info->hcd       = NULL;
  86
  87                if (info->enable_oc != NULL)
  88                        (info->enable_oc)(info, 0);
  89        }
  90
  91        clk_disable_unprepare(clk);
  92        clk_disable_unprepare(usb_clk);
  93}
  94
  95/* ohci_s3c2410_hub_status_data
  96 *
  97 * update the status data from the hub with anything that
  98 * has been detected by our system
  99*/
 100
 101static int
 102ohci_s3c2410_hub_status_data(struct usb_hcd *hcd, char *buf)
 103{
 104        struct s3c2410_hcd_info *info = to_s3c2410_info(hcd);
 105        struct s3c2410_hcd_port *port;
 106        int orig;
 107        int portno;
 108
 109        orig = ohci_hub_status_data(hcd, buf);
 110
 111        if (info == NULL)
 112                return orig;
 113
 114        port = &info->port[0];
 115
 116        /* mark any changed port as changed */
 117
 118        for (portno = 0; portno < 2; port++, portno++) {
 119                if (port->oc_changed == 1 &&
 120                    port->flags & S3C_HCDFLG_USED) {
 121                        dev_dbg(hcd->self.controller,
 122                                "oc change on port %d\n", portno);
 123
 124                        if (orig < 1)
 125                                orig = 1;
 126
 127                        buf[0] |= 1<<(portno+1);
 128                }
 129        }
 130
 131        return orig;
 132}
 133
 134/* s3c2410_usb_set_power
 135 *
 136 * configure the power on a port, by calling the platform device
 137 * routine registered with the platform device
 138*/
 139
 140static void s3c2410_usb_set_power(struct s3c2410_hcd_info *info,
 141                                  int port, int to)
 142{
 143        if (info == NULL)
 144                return;
 145
 146        if (info->power_control != NULL) {
 147                info->port[port-1].power = to;
 148                (info->power_control)(port-1, to);
 149        }
 150}
 151
 152/* ohci_s3c2410_hub_control
 153 *
 154 * look at control requests to the hub, and see if we need
 155 * to take any action or over-ride the results from the
 156 * request.
 157*/
 158
 159static int ohci_s3c2410_hub_control(
 160        struct usb_hcd  *hcd,
 161        u16             typeReq,
 162        u16             wValue,
 163        u16             wIndex,
 164        char            *buf,
 165        u16             wLength)
 166{
 167        struct s3c2410_hcd_info *info = to_s3c2410_info(hcd);
 168        struct usb_hub_descriptor *desc;
 169        int ret = -EINVAL;
 170        u32 *data = (u32 *)buf;
 171
 172        dev_dbg(hcd->self.controller,
 173                "s3c2410_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n",
 174                hcd, typeReq, wValue, wIndex, buf, wLength);
 175
 176        /* if we are only an humble host without any special capabilities
 177         * process the request straight away and exit */
 178
 179        if (info == NULL) {
 180                ret = ohci_hub_control(hcd, typeReq, wValue,
 181                                       wIndex, buf, wLength);
 182                goto out;
 183        }
 184
 185        /* check the request to see if it needs handling */
 186
 187        switch (typeReq) {
 188        case SetPortFeature:
 189                if (wValue == USB_PORT_FEAT_POWER) {
 190                        dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
 191                        s3c2410_usb_set_power(info, wIndex, 1);
 192                        goto out;
 193                }
 194                break;
 195
 196        case ClearPortFeature:
 197                switch (wValue) {
 198                case USB_PORT_FEAT_C_OVER_CURRENT:
 199                        dev_dbg(hcd->self.controller,
 200                                "ClearPortFeature: C_OVER_CURRENT\n");
 201
 202                        if (valid_port(wIndex)) {
 203                                info->port[wIndex-1].oc_changed = 0;
 204                                info->port[wIndex-1].oc_status = 0;
 205                        }
 206
 207                        goto out;
 208
 209                case USB_PORT_FEAT_OVER_CURRENT:
 210                        dev_dbg(hcd->self.controller,
 211                                "ClearPortFeature: OVER_CURRENT\n");
 212
 213                        if (valid_port(wIndex))
 214                                info->port[wIndex-1].oc_status = 0;
 215
 216                        goto out;
 217
 218                case USB_PORT_FEAT_POWER:
 219                        dev_dbg(hcd->self.controller,
 220                                "ClearPortFeature: POWER\n");
 221
 222                        if (valid_port(wIndex)) {
 223                                s3c2410_usb_set_power(info, wIndex, 0);
 224                                return 0;
 225                        }
 226                }
 227                break;
 228        }
 229
 230        ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
 231        if (ret)
 232                goto out;
 233
 234        switch (typeReq) {
 235        case GetHubDescriptor:
 236
 237                /* update the hub's descriptor */
 238
 239                desc = (struct usb_hub_descriptor *)buf;
 240
 241                if (info->power_control == NULL)
 242                        return ret;
 243
 244                dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n",
 245                        desc->wHubCharacteristics);
 246
 247                /* remove the old configurations for power-switching, and
 248                 * over-current protection, and insert our new configuration
 249                 */
 250
 251                desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM);
 252                desc->wHubCharacteristics |= cpu_to_le16(0x0001);
 253
 254                if (info->enable_oc) {
 255                        desc->wHubCharacteristics &= ~cpu_to_le16(
 256                                HUB_CHAR_OCPM);
 257                        desc->wHubCharacteristics |=  cpu_to_le16(
 258                                0x0008 |
 259                                0x0001);
 260                }
 261
 262                dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n",
 263                        desc->wHubCharacteristics);
 264
 265                return ret;
 266
 267        case GetPortStatus:
 268                /* check port status */
 269
 270                dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex);
 271
 272                if (valid_port(wIndex)) {
 273                        if (info->port[wIndex-1].oc_changed)
 274                                *data |= cpu_to_le32(RH_PS_OCIC);
 275
 276                        if (info->port[wIndex-1].oc_status)
 277                                *data |= cpu_to_le32(RH_PS_POCI);
 278                }
 279        }
 280
 281 out:
 282        return ret;
 283}
 284
 285/* s3c2410_hcd_oc
 286 *
 287 * handle an over-current report
 288*/
 289
 290static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc)
 291{
 292        struct s3c2410_hcd_port *port;
 293        struct usb_hcd *hcd;
 294        unsigned long flags;
 295        int portno;
 296
 297        if (info == NULL)
 298                return;
 299
 300        port = &info->port[0];
 301        hcd = info->hcd;
 302
 303        local_irq_save(flags);
 304
 305        for (portno = 0; portno < 2; port++, portno++) {
 306                if (port_oc & (1<<portno) &&
 307                    port->flags & S3C_HCDFLG_USED) {
 308                        port->oc_status = 1;
 309                        port->oc_changed = 1;
 310
 311                        /* ok, once over-current is detected,
 312                           the port needs to be powered down */
 313                        s3c2410_usb_set_power(info, portno+1, 0);
 314                }
 315        }
 316
 317        local_irq_restore(flags);
 318}
 319
 320/* may be called without controller electrically present */
 321/* may be called with controller, bus, and devices active */
 322
 323/*
 324 * usb_hcd_s3c2410_remove - shutdown processing for HCD
 325 * @dev: USB Host Controller being removed
 326 * Context: !in_interrupt()
 327 *
 328 * Reverses the effect of usb_hcd_3c2410_probe(), first invoking
 329 * the HCD's stop() method.  It is always called from a thread
 330 * context, normally "rmmod", "apmd", or something similar.
 331 *
 332*/
 333
 334static void
 335usb_hcd_s3c2410_remove(struct usb_hcd *hcd, struct platform_device *dev)
 336{
 337        usb_remove_hcd(hcd);
 338        s3c2410_stop_hc(dev);
 339        usb_put_hcd(hcd);
 340}
 341
 342/**
 343 * usb_hcd_s3c2410_probe - initialize S3C2410-based HCDs
 344 * Context: !in_interrupt()
 345 *
 346 * Allocates basic resources for this USB host controller, and
 347 * then invokes the start() method for the HCD associated with it
 348 * through the hotplug entry's driver_data.
 349 *
 350 */
 351static int usb_hcd_s3c2410_probe(const struct hc_driver *driver,
 352                                  struct platform_device *dev)
 353{
 354        struct usb_hcd *hcd = NULL;
 355        struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
 356        int retval;
 357
 358        s3c2410_usb_set_power(info, 1, 1);
 359        s3c2410_usb_set_power(info, 2, 1);
 360
 361        hcd = usb_create_hcd(driver, &dev->dev, "s3c24xx");
 362        if (hcd == NULL)
 363                return -ENOMEM;
 364
 365        hcd->rsrc_start = dev->resource[0].start;
 366        hcd->rsrc_len   = resource_size(&dev->resource[0]);
 367
 368        hcd->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]);
 369        if (IS_ERR(hcd->regs)) {
 370                retval = PTR_ERR(hcd->regs);
 371                goto err_put;
 372        }
 373
 374        clk = devm_clk_get(&dev->dev, "usb-host");
 375        if (IS_ERR(clk)) {
 376                dev_err(&dev->dev, "cannot get usb-host clock\n");
 377                retval = PTR_ERR(clk);
 378                goto err_put;
 379        }
 380
 381        usb_clk = devm_clk_get(&dev->dev, "usb-bus-host");
 382        if (IS_ERR(usb_clk)) {
 383                dev_err(&dev->dev, "cannot get usb-bus-host clock\n");
 384                retval = PTR_ERR(usb_clk);
 385                goto err_put;
 386        }
 387
 388        s3c2410_start_hc(dev, hcd);
 389
 390        retval = usb_add_hcd(hcd, dev->resource[1].start, 0);
 391        if (retval != 0)
 392                goto err_ioremap;
 393
 394        device_wakeup_enable(hcd->self.controller);
 395        return 0;
 396
 397 err_ioremap:
 398        s3c2410_stop_hc(dev);
 399
 400 err_put:
 401        usb_put_hcd(hcd);
 402        return retval;
 403}
 404
 405/*-------------------------------------------------------------------------*/
 406
 407static struct hc_driver __read_mostly ohci_s3c2410_hc_driver;
 408
 409static int ohci_hcd_s3c2410_drv_probe(struct platform_device *pdev)
 410{
 411        return usb_hcd_s3c2410_probe(&ohci_s3c2410_hc_driver, pdev);
 412}
 413
 414static int ohci_hcd_s3c2410_drv_remove(struct platform_device *pdev)
 415{
 416        struct usb_hcd *hcd = platform_get_drvdata(pdev);
 417
 418        usb_hcd_s3c2410_remove(hcd, pdev);
 419        return 0;
 420}
 421
 422#ifdef CONFIG_PM
 423static int ohci_hcd_s3c2410_drv_suspend(struct device *dev)
 424{
 425        struct usb_hcd *hcd = dev_get_drvdata(dev);
 426        struct platform_device *pdev = to_platform_device(dev);
 427        bool do_wakeup = device_may_wakeup(dev);
 428        int rc = 0;
 429
 430        rc = ohci_suspend(hcd, do_wakeup);
 431        if (rc)
 432                return rc;
 433
 434        s3c2410_stop_hc(pdev);
 435
 436        return rc;
 437}
 438
 439static int ohci_hcd_s3c2410_drv_resume(struct device *dev)
 440{
 441        struct usb_hcd *hcd = dev_get_drvdata(dev);
 442        struct platform_device *pdev = to_platform_device(dev);
 443
 444        s3c2410_start_hc(pdev, hcd);
 445
 446        ohci_resume(hcd, false);
 447
 448        return 0;
 449}
 450#else
 451#define ohci_hcd_s3c2410_drv_suspend    NULL
 452#define ohci_hcd_s3c2410_drv_resume     NULL
 453#endif
 454
 455static const struct dev_pm_ops ohci_hcd_s3c2410_pm_ops = {
 456        .suspend        = ohci_hcd_s3c2410_drv_suspend,
 457        .resume         = ohci_hcd_s3c2410_drv_resume,
 458};
 459
 460static struct platform_driver ohci_hcd_s3c2410_driver = {
 461        .probe          = ohci_hcd_s3c2410_drv_probe,
 462        .remove         = ohci_hcd_s3c2410_drv_remove,
 463        .shutdown       = usb_hcd_platform_shutdown,
 464        .driver         = {
 465                .name   = "s3c2410-ohci",
 466                .pm     = &ohci_hcd_s3c2410_pm_ops,
 467        },
 468};
 469
 470static int __init ohci_s3c2410_init(void)
 471{
 472        if (usb_disabled())
 473                return -ENODEV;
 474
 475        pr_info("%s: " DRIVER_DESC "\n", hcd_name);
 476        ohci_init_driver(&ohci_s3c2410_hc_driver, NULL);
 477
 478        /*
 479         * The Samsung HW has some unusual quirks, which require
 480         * Sumsung-specific workarounds. We override certain hc_driver
 481         * functions here to achieve that. We explicitly do not enhance
 482         * ohci_driver_overrides to allow this more easily, since this
 483         * is an unusual case, and we don't want to encourage others to
 484         * override these functions by making it too easy.
 485         */
 486
 487        ohci_s3c2410_hc_driver.hub_status_data  = ohci_s3c2410_hub_status_data;
 488        ohci_s3c2410_hc_driver.hub_control      = ohci_s3c2410_hub_control;
 489
 490        return platform_driver_register(&ohci_hcd_s3c2410_driver);
 491}
 492module_init(ohci_s3c2410_init);
 493
 494static void __exit ohci_s3c2410_cleanup(void)
 495{
 496        platform_driver_unregister(&ohci_hcd_s3c2410_driver);
 497}
 498module_exit(ohci_s3c2410_cleanup);
 499
 500MODULE_DESCRIPTION(DRIVER_DESC);
 501MODULE_LICENSE("GPL");
 502MODULE_ALIAS("platform:s3c2410-ohci");
 503