Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-1.0+
0002 /*
0003  * OHCI HCD (Host Controller Driver) for USB.
0004  *
0005  * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
0006  * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
0007  * (C) Copyright 2002 Hewlett-Packard Company
0008  *
0009  * USB Bus Glue for Samsung S3C2410
0010  *
0011  * Written by Christopher Hoover <ch@hpl.hp.com>
0012  * Based on fragments of previous driver by Russell King et al.
0013  *
0014  * Modified for S3C2410 from ohci-sa1111.c, ohci-omap.c and ohci-lh7a40.c
0015  *  by Ben Dooks, <ben@simtec.co.uk>
0016  *  Copyright (C) 2004 Simtec Electronics
0017  *
0018  * Thanks to basprog@mail.ru for updates to newer kernels
0019  *
0020  * This file is licenced under the GPL.
0021 */
0022 
0023 #include <linux/clk.h>
0024 #include <linux/io.h>
0025 #include <linux/kernel.h>
0026 #include <linux/module.h>
0027 #include <linux/platform_device.h>
0028 #include <linux/platform_data/usb-ohci-s3c2410.h>
0029 #include <linux/usb.h>
0030 #include <linux/usb/hcd.h>
0031 
0032 #include "ohci.h"
0033 
0034 
0035 #define valid_port(idx) ((idx) == 1 || (idx) == 2)
0036 
0037 /* clock device associated with the hcd */
0038 
0039 
0040 #define DRIVER_DESC "OHCI S3C2410 driver"
0041 
0042 static const char hcd_name[] = "ohci-s3c2410";
0043 
0044 static struct clk *clk;
0045 static struct clk *usb_clk;
0046 
0047 static struct hc_driver __read_mostly ohci_s3c2410_hc_driver;
0048 
0049 /* forward definitions */
0050 
0051 static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc);
0052 
0053 /* conversion functions */
0054 
0055 static struct s3c2410_hcd_info *to_s3c2410_info(struct usb_hcd *hcd)
0056 {
0057     return dev_get_platdata(hcd->self.controller);
0058 }
0059 
0060 static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd)
0061 {
0062     struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
0063 
0064     dev_dbg(&dev->dev, "s3c2410_start_hc:\n");
0065 
0066     clk_prepare_enable(usb_clk);
0067     mdelay(2);          /* let the bus clock stabilise */
0068 
0069     clk_prepare_enable(clk);
0070 
0071     if (info != NULL) {
0072         info->hcd   = hcd;
0073         info->report_oc = s3c2410_hcd_oc;
0074 
0075         if (info->enable_oc != NULL)
0076             (info->enable_oc)(info, 1);
0077     }
0078 }
0079 
0080 static void s3c2410_stop_hc(struct platform_device *dev)
0081 {
0082     struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
0083 
0084     dev_dbg(&dev->dev, "s3c2410_stop_hc:\n");
0085 
0086     if (info != NULL) {
0087         info->report_oc = NULL;
0088         info->hcd   = NULL;
0089 
0090         if (info->enable_oc != NULL)
0091             (info->enable_oc)(info, 0);
0092     }
0093 
0094     clk_disable_unprepare(clk);
0095     clk_disable_unprepare(usb_clk);
0096 }
0097 
0098 /* ohci_s3c2410_hub_status_data
0099  *
0100  * update the status data from the hub with anything that
0101  * has been detected by our system
0102 */
0103 
0104 static int
0105 ohci_s3c2410_hub_status_data(struct usb_hcd *hcd, char *buf)
0106 {
0107     struct s3c2410_hcd_info *info = to_s3c2410_info(hcd);
0108     struct s3c2410_hcd_port *port;
0109     int orig;
0110     int portno;
0111 
0112     orig = ohci_hub_status_data(hcd, buf);
0113 
0114     if (info == NULL)
0115         return orig;
0116 
0117     port = &info->port[0];
0118 
0119     /* mark any changed port as changed */
0120 
0121     for (portno = 0; portno < 2; port++, portno++) {
0122         if (port->oc_changed == 1 &&
0123             port->flags & S3C_HCDFLG_USED) {
0124             dev_dbg(hcd->self.controller,
0125                 "oc change on port %d\n", portno);
0126 
0127             if (orig < 1)
0128                 orig = 1;
0129 
0130             buf[0] |= 1<<(portno+1);
0131         }
0132     }
0133 
0134     return orig;
0135 }
0136 
0137 /* s3c2410_usb_set_power
0138  *
0139  * configure the power on a port, by calling the platform device
0140  * routine registered with the platform device
0141 */
0142 
0143 static void s3c2410_usb_set_power(struct s3c2410_hcd_info *info,
0144                   int port, int to)
0145 {
0146     if (info == NULL)
0147         return;
0148 
0149     if (info->power_control != NULL) {
0150         info->port[port-1].power = to;
0151         (info->power_control)(port-1, to);
0152     }
0153 }
0154 
0155 /* ohci_s3c2410_hub_control
0156  *
0157  * look at control requests to the hub, and see if we need
0158  * to take any action or over-ride the results from the
0159  * request.
0160 */
0161 
0162 static int ohci_s3c2410_hub_control(
0163     struct usb_hcd  *hcd,
0164     u16     typeReq,
0165     u16     wValue,
0166     u16     wIndex,
0167     char        *buf,
0168     u16     wLength)
0169 {
0170     struct s3c2410_hcd_info *info = to_s3c2410_info(hcd);
0171     struct usb_hub_descriptor *desc;
0172     int ret = -EINVAL;
0173     u32 *data = (u32 *)buf;
0174 
0175     dev_dbg(hcd->self.controller,
0176         "s3c2410_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n",
0177         hcd, typeReq, wValue, wIndex, buf, wLength);
0178 
0179     /* if we are only an humble host without any special capabilities
0180      * process the request straight away and exit */
0181 
0182     if (info == NULL) {
0183         ret = ohci_hub_control(hcd, typeReq, wValue,
0184                        wIndex, buf, wLength);
0185         goto out;
0186     }
0187 
0188     /* check the request to see if it needs handling */
0189 
0190     switch (typeReq) {
0191     case SetPortFeature:
0192         if (wValue == USB_PORT_FEAT_POWER) {
0193             dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
0194             s3c2410_usb_set_power(info, wIndex, 1);
0195             goto out;
0196         }
0197         break;
0198 
0199     case ClearPortFeature:
0200         switch (wValue) {
0201         case USB_PORT_FEAT_C_OVER_CURRENT:
0202             dev_dbg(hcd->self.controller,
0203                 "ClearPortFeature: C_OVER_CURRENT\n");
0204 
0205             if (valid_port(wIndex)) {
0206                 info->port[wIndex-1].oc_changed = 0;
0207                 info->port[wIndex-1].oc_status = 0;
0208             }
0209 
0210             goto out;
0211 
0212         case USB_PORT_FEAT_OVER_CURRENT:
0213             dev_dbg(hcd->self.controller,
0214                 "ClearPortFeature: OVER_CURRENT\n");
0215 
0216             if (valid_port(wIndex))
0217                 info->port[wIndex-1].oc_status = 0;
0218 
0219             goto out;
0220 
0221         case USB_PORT_FEAT_POWER:
0222             dev_dbg(hcd->self.controller,
0223                 "ClearPortFeature: POWER\n");
0224 
0225             if (valid_port(wIndex)) {
0226                 s3c2410_usb_set_power(info, wIndex, 0);
0227                 return 0;
0228             }
0229         }
0230         break;
0231     }
0232 
0233     ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
0234     if (ret)
0235         goto out;
0236 
0237     switch (typeReq) {
0238     case GetHubDescriptor:
0239 
0240         /* update the hub's descriptor */
0241 
0242         desc = (struct usb_hub_descriptor *)buf;
0243 
0244         if (info->power_control == NULL)
0245             return ret;
0246 
0247         dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n",
0248             desc->wHubCharacteristics);
0249 
0250         /* remove the old configurations for power-switching, and
0251          * over-current protection, and insert our new configuration
0252          */
0253 
0254         desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM);
0255         desc->wHubCharacteristics |= cpu_to_le16(
0256             HUB_CHAR_INDV_PORT_LPSM);
0257 
0258         if (info->enable_oc) {
0259             desc->wHubCharacteristics &= ~cpu_to_le16(
0260                 HUB_CHAR_OCPM);
0261             desc->wHubCharacteristics |=  cpu_to_le16(
0262                 HUB_CHAR_INDV_PORT_OCPM);
0263         }
0264 
0265         dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n",
0266             desc->wHubCharacteristics);
0267 
0268         return ret;
0269 
0270     case GetPortStatus:
0271         /* check port status */
0272 
0273         dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex);
0274 
0275         if (valid_port(wIndex)) {
0276             if (info->port[wIndex-1].oc_changed)
0277                 *data |= cpu_to_le32(RH_PS_OCIC);
0278 
0279             if (info->port[wIndex-1].oc_status)
0280                 *data |= cpu_to_le32(RH_PS_POCI);
0281         }
0282     }
0283 
0284  out:
0285     return ret;
0286 }
0287 
0288 /* s3c2410_hcd_oc
0289  *
0290  * handle an over-current report
0291 */
0292 
0293 static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc)
0294 {
0295     struct s3c2410_hcd_port *port;
0296     unsigned long flags;
0297     int portno;
0298 
0299     if (info == NULL)
0300         return;
0301 
0302     port = &info->port[0];
0303 
0304     local_irq_save(flags);
0305 
0306     for (portno = 0; portno < 2; port++, portno++) {
0307         if (port_oc & (1<<portno) &&
0308             port->flags & S3C_HCDFLG_USED) {
0309             port->oc_status = 1;
0310             port->oc_changed = 1;
0311 
0312             /* ok, once over-current is detected,
0313                the port needs to be powered down */
0314             s3c2410_usb_set_power(info, portno+1, 0);
0315         }
0316     }
0317 
0318     local_irq_restore(flags);
0319 }
0320 
0321 /* may be called without controller electrically present */
0322 /* may be called with controller, bus, and devices active */
0323 
0324 /*
0325  * ohci_hcd_s3c2410_remove - shutdown processing for HCD
0326  * @dev: USB Host Controller being removed
0327  *
0328  * Context: task context, might sleep
0329  *
0330  * Reverses the effect of ohci_hcd_3c2410_probe(), first invoking
0331  * the HCD's stop() method.  It is always called from a thread
0332  * context, normally "rmmod", "apmd", or something similar.
0333  */
0334 static int
0335 ohci_hcd_s3c2410_remove(struct platform_device *dev)
0336 {
0337     struct usb_hcd *hcd = platform_get_drvdata(dev);
0338 
0339     usb_remove_hcd(hcd);
0340     s3c2410_stop_hc(dev);
0341     usb_put_hcd(hcd);
0342     return 0;
0343 }
0344 
0345 /*
0346  * ohci_hcd_s3c2410_probe - initialize S3C2410-based HCDs
0347  * @dev: USB Host Controller to be probed
0348  *
0349  * Context: task context, might sleep
0350  *
0351  * Allocates basic resources for this USB host controller, and
0352  * then invokes the start() method for the HCD associated with it
0353  * through the hotplug entry's driver_data.
0354  */
0355 static int ohci_hcd_s3c2410_probe(struct platform_device *dev)
0356 {
0357     struct usb_hcd *hcd = NULL;
0358     struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
0359     int retval, irq;
0360 
0361     s3c2410_usb_set_power(info, 1, 1);
0362     s3c2410_usb_set_power(info, 2, 1);
0363 
0364     hcd = usb_create_hcd(&ohci_s3c2410_hc_driver, &dev->dev, "s3c24xx");
0365     if (hcd == NULL)
0366         return -ENOMEM;
0367 
0368     hcd->rsrc_start = dev->resource[0].start;
0369     hcd->rsrc_len   = resource_size(&dev->resource[0]);
0370 
0371     hcd->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]);
0372     if (IS_ERR(hcd->regs)) {
0373         retval = PTR_ERR(hcd->regs);
0374         goto err_put;
0375     }
0376 
0377     clk = devm_clk_get(&dev->dev, "usb-host");
0378     if (IS_ERR(clk)) {
0379         dev_err(&dev->dev, "cannot get usb-host clock\n");
0380         retval = PTR_ERR(clk);
0381         goto err_put;
0382     }
0383 
0384     usb_clk = devm_clk_get(&dev->dev, "usb-bus-host");
0385     if (IS_ERR(usb_clk)) {
0386         dev_err(&dev->dev, "cannot get usb-bus-host clock\n");
0387         retval = PTR_ERR(usb_clk);
0388         goto err_put;
0389     }
0390 
0391     irq = platform_get_irq(dev, 0);
0392     if (irq < 0) {
0393         retval = irq;
0394         goto err_put;
0395     }
0396 
0397     s3c2410_start_hc(dev, hcd);
0398 
0399     retval = usb_add_hcd(hcd, irq, 0);
0400     if (retval != 0)
0401         goto err_ioremap;
0402 
0403     device_wakeup_enable(hcd->self.controller);
0404     return 0;
0405 
0406  err_ioremap:
0407     s3c2410_stop_hc(dev);
0408 
0409  err_put:
0410     usb_put_hcd(hcd);
0411     return retval;
0412 }
0413 
0414 /*-------------------------------------------------------------------------*/
0415 
0416 #ifdef CONFIG_PM
0417 static int ohci_hcd_s3c2410_drv_suspend(struct device *dev)
0418 {
0419     struct usb_hcd *hcd = dev_get_drvdata(dev);
0420     struct platform_device *pdev = to_platform_device(dev);
0421     bool do_wakeup = device_may_wakeup(dev);
0422     int rc = 0;
0423 
0424     rc = ohci_suspend(hcd, do_wakeup);
0425     if (rc)
0426         return rc;
0427 
0428     s3c2410_stop_hc(pdev);
0429 
0430     return rc;
0431 }
0432 
0433 static int ohci_hcd_s3c2410_drv_resume(struct device *dev)
0434 {
0435     struct usb_hcd *hcd = dev_get_drvdata(dev);
0436     struct platform_device *pdev = to_platform_device(dev);
0437 
0438     s3c2410_start_hc(pdev, hcd);
0439 
0440     ohci_resume(hcd, false);
0441 
0442     return 0;
0443 }
0444 #else
0445 #define ohci_hcd_s3c2410_drv_suspend    NULL
0446 #define ohci_hcd_s3c2410_drv_resume NULL
0447 #endif
0448 
0449 static const struct dev_pm_ops ohci_hcd_s3c2410_pm_ops = {
0450     .suspend    = ohci_hcd_s3c2410_drv_suspend,
0451     .resume     = ohci_hcd_s3c2410_drv_resume,
0452 };
0453 
0454 static const struct of_device_id ohci_hcd_s3c2410_dt_ids[] = {
0455     { .compatible = "samsung,s3c2410-ohci" },
0456     { /* sentinel */ }
0457 };
0458 
0459 MODULE_DEVICE_TABLE(of, ohci_hcd_s3c2410_dt_ids);
0460 
0461 static struct platform_driver ohci_hcd_s3c2410_driver = {
0462     .probe      = ohci_hcd_s3c2410_probe,
0463     .remove     = ohci_hcd_s3c2410_remove,
0464     .shutdown   = usb_hcd_platform_shutdown,
0465     .driver     = {
0466         .name   = "s3c2410-ohci",
0467         .pm = &ohci_hcd_s3c2410_pm_ops,
0468         .of_match_table = ohci_hcd_s3c2410_dt_ids,
0469     },
0470 };
0471 
0472 static int __init ohci_s3c2410_init(void)
0473 {
0474     if (usb_disabled())
0475         return -ENODEV;
0476 
0477     pr_info("%s: " DRIVER_DESC "\n", hcd_name);
0478     ohci_init_driver(&ohci_s3c2410_hc_driver, NULL);
0479 
0480     /*
0481      * The Samsung HW has some unusual quirks, which require
0482      * Sumsung-specific workarounds. We override certain hc_driver
0483      * functions here to achieve that. We explicitly do not enhance
0484      * ohci_driver_overrides to allow this more easily, since this
0485      * is an unusual case, and we don't want to encourage others to
0486      * override these functions by making it too easy.
0487      */
0488 
0489     ohci_s3c2410_hc_driver.hub_status_data  = ohci_s3c2410_hub_status_data;
0490     ohci_s3c2410_hc_driver.hub_control  = ohci_s3c2410_hub_control;
0491 
0492     return platform_driver_register(&ohci_hcd_s3c2410_driver);
0493 }
0494 module_init(ohci_s3c2410_init);
0495 
0496 static void __exit ohci_s3c2410_cleanup(void)
0497 {
0498     platform_driver_unregister(&ohci_hcd_s3c2410_driver);
0499 }
0500 module_exit(ohci_s3c2410_cleanup);
0501 
0502 MODULE_DESCRIPTION(DRIVER_DESC);
0503 MODULE_LICENSE("GPL");
0504 MODULE_ALIAS("platform:s3c2410-ohci");