0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
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
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
0050
0051 static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc);
0052
0053
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);
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
0099
0100
0101
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
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
0138
0139
0140
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
0156
0157
0158
0159
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
0180
0181
0182 if (info == NULL) {
0183 ret = ohci_hub_control(hcd, typeReq, wValue,
0184 wIndex, buf, wLength);
0185 goto out;
0186 }
0187
0188
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
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
0251
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
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
0289
0290
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
0313
0314 s3c2410_usb_set_power(info, portno+1, 0);
0315 }
0316 }
0317
0318 local_irq_restore(flags);
0319 }
0320
0321
0322
0323
0324
0325
0326
0327
0328
0329
0330
0331
0332
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
0347
0348
0349
0350
0351
0352
0353
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 { }
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
0482
0483
0484
0485
0486
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");