root/drivers/usb/host/ohci-s3c2410.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. to_s3c2410_info
  2. s3c2410_start_hc
  3. s3c2410_stop_hc
  4. ohci_s3c2410_hub_status_data
  5. s3c2410_usb_set_power
  6. ohci_s3c2410_hub_control
  7. s3c2410_hcd_oc
  8. ohci_hcd_s3c2410_remove
  9. ohci_hcd_s3c2410_probe
  10. ohci_hcd_s3c2410_drv_suspend
  11. ohci_hcd_s3c2410_drv_resume
  12. ohci_s3c2410_init
  13. ohci_s3c2410_cleanup

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

/* [<][>][^][v][top][bottom][index][help] */