linux/drivers/usb/host/ohci-jz4740.c
<<
>>
Prefs
   1/*
   2 *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
   3 *
   4 *  This program is free software; you can redistribute it and/or modify it
   5 *  under  the terms of the GNU General  Public License as published by the
   6 *  Free Software Foundation;  either version 2 of the License, or (at your
   7 *  option) any later version.
   8 *
   9 *  You should have received a copy of the  GNU General Public License along
  10 *  with this program; if not, write  to the Free Software Foundation, Inc.,
  11 *  675 Mass Ave, Cambridge, MA 02139, USA.
  12 *
  13 */
  14
  15#include <linux/platform_device.h>
  16#include <linux/clk.h>
  17#include <linux/regulator/consumer.h>
  18
  19struct jz4740_ohci_hcd {
  20        struct ohci_hcd ohci_hcd;
  21
  22        struct regulator *vbus;
  23        bool vbus_enabled;
  24        struct clk *clk;
  25};
  26
  27static inline struct jz4740_ohci_hcd *hcd_to_jz4740_hcd(struct usb_hcd *hcd)
  28{
  29        return (struct jz4740_ohci_hcd *)(hcd->hcd_priv);
  30}
  31
  32static inline struct usb_hcd *jz4740_hcd_to_hcd(struct jz4740_ohci_hcd *jz4740_ohci)
  33{
  34        return container_of((void *)jz4740_ohci, struct usb_hcd, hcd_priv);
  35}
  36
  37static int ohci_jz4740_start(struct usb_hcd *hcd)
  38{
  39        struct ohci_hcd *ohci = hcd_to_ohci(hcd);
  40        int     ret;
  41
  42        ret = ohci_init(ohci);
  43        if (ret < 0)
  44                return ret;
  45
  46        ohci->num_ports = 1;
  47
  48        ret = ohci_run(ohci);
  49        if (ret < 0) {
  50                dev_err(hcd->self.controller, "Can not start %s",
  51                        hcd->self.bus_name);
  52                ohci_stop(hcd);
  53                return ret;
  54        }
  55        return 0;
  56}
  57
  58static int ohci_jz4740_set_vbus_power(struct jz4740_ohci_hcd *jz4740_ohci,
  59        bool enabled)
  60{
  61        int ret = 0;
  62
  63        if (!jz4740_ohci->vbus)
  64                return 0;
  65
  66        if (enabled && !jz4740_ohci->vbus_enabled) {
  67                ret = regulator_enable(jz4740_ohci->vbus);
  68                if (ret)
  69                        dev_err(jz4740_hcd_to_hcd(jz4740_ohci)->self.controller,
  70                                "Could not power vbus\n");
  71        } else if (!enabled && jz4740_ohci->vbus_enabled) {
  72                ret = regulator_disable(jz4740_ohci->vbus);
  73        }
  74
  75        if (ret == 0)
  76                jz4740_ohci->vbus_enabled = enabled;
  77
  78        return ret;
  79}
  80
  81static int ohci_jz4740_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
  82        u16 wIndex, char *buf, u16 wLength)
  83{
  84        struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd);
  85        int ret = 0;
  86
  87        switch (typeReq) {
  88        case SetPortFeature:
  89                if (wValue == USB_PORT_FEAT_POWER)
  90                        ret = ohci_jz4740_set_vbus_power(jz4740_ohci, true);
  91                break;
  92        case ClearPortFeature:
  93                if (wValue == USB_PORT_FEAT_POWER)
  94                        ret = ohci_jz4740_set_vbus_power(jz4740_ohci, false);
  95                break;
  96        }
  97
  98        if (ret)
  99                return ret;
 100
 101        return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
 102}
 103
 104
 105static const struct hc_driver ohci_jz4740_hc_driver = {
 106        .description =          hcd_name,
 107        .product_desc =         "JZ4740 OHCI",
 108        .hcd_priv_size =        sizeof(struct jz4740_ohci_hcd),
 109
 110        /*
 111         * generic hardware linkage
 112         */
 113        .irq =                  ohci_irq,
 114        .flags =                HCD_USB11 | HCD_MEMORY,
 115
 116        /*
 117         * basic lifecycle operations
 118         */
 119        .start =                ohci_jz4740_start,
 120        .stop =                 ohci_stop,
 121        .shutdown =             ohci_shutdown,
 122
 123        /*
 124         * managing i/o requests and associated device resources
 125         */
 126        .urb_enqueue =          ohci_urb_enqueue,
 127        .urb_dequeue =          ohci_urb_dequeue,
 128        .endpoint_disable =     ohci_endpoint_disable,
 129
 130        /*
 131         * scheduling support
 132         */
 133        .get_frame_number =     ohci_get_frame,
 134
 135        /*
 136         * root hub support
 137         */
 138        .hub_status_data =      ohci_hub_status_data,
 139        .hub_control =          ohci_jz4740_hub_control,
 140#ifdef  CONFIG_PM
 141        .bus_suspend =          ohci_bus_suspend,
 142        .bus_resume =           ohci_bus_resume,
 143#endif
 144        .start_port_reset =     ohci_start_port_reset,
 145};
 146
 147
 148static int jz4740_ohci_probe(struct platform_device *pdev)
 149{
 150        int ret;
 151        struct usb_hcd *hcd;
 152        struct jz4740_ohci_hcd *jz4740_ohci;
 153        struct resource *res;
 154        int irq;
 155
 156        irq = platform_get_irq(pdev, 0);
 157        if (irq < 0) {
 158                dev_err(&pdev->dev, "Failed to get platform irq\n");
 159                return irq;
 160        }
 161
 162        hcd = usb_create_hcd(&ohci_jz4740_hc_driver, &pdev->dev, "jz4740");
 163        if (!hcd) {
 164                dev_err(&pdev->dev, "Failed to create hcd.\n");
 165                return -ENOMEM;
 166        }
 167
 168        jz4740_ohci = hcd_to_jz4740_hcd(hcd);
 169
 170        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 171        hcd->regs = devm_ioremap_resource(&pdev->dev, res);
 172        if (IS_ERR(hcd->regs)) {
 173                ret = PTR_ERR(hcd->regs);
 174                goto err_free;
 175        }
 176        hcd->rsrc_start = res->start;
 177        hcd->rsrc_len = resource_size(res);
 178
 179        jz4740_ohci->clk = devm_clk_get(&pdev->dev, "uhc");
 180        if (IS_ERR(jz4740_ohci->clk)) {
 181                ret = PTR_ERR(jz4740_ohci->clk);
 182                dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
 183                goto err_free;
 184        }
 185
 186        jz4740_ohci->vbus = devm_regulator_get(&pdev->dev, "vbus");
 187        if (IS_ERR(jz4740_ohci->vbus))
 188                jz4740_ohci->vbus = NULL;
 189
 190
 191        clk_set_rate(jz4740_ohci->clk, 48000000);
 192        clk_enable(jz4740_ohci->clk);
 193        if (jz4740_ohci->vbus)
 194                ohci_jz4740_set_vbus_power(jz4740_ohci, true);
 195
 196        platform_set_drvdata(pdev, hcd);
 197
 198        ohci_hcd_init(hcd_to_ohci(hcd));
 199
 200        ret = usb_add_hcd(hcd, irq, 0);
 201        if (ret) {
 202                dev_err(&pdev->dev, "Failed to add hcd: %d\n", ret);
 203                goto err_disable;
 204        }
 205        device_wakeup_enable(hcd->self.controller);
 206
 207        return 0;
 208
 209err_disable:
 210        if (jz4740_ohci->vbus)
 211                regulator_disable(jz4740_ohci->vbus);
 212        clk_disable(jz4740_ohci->clk);
 213
 214err_free:
 215        usb_put_hcd(hcd);
 216
 217        return ret;
 218}
 219
 220static int jz4740_ohci_remove(struct platform_device *pdev)
 221{
 222        struct usb_hcd *hcd = platform_get_drvdata(pdev);
 223        struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd);
 224
 225        usb_remove_hcd(hcd);
 226
 227        if (jz4740_ohci->vbus)
 228                regulator_disable(jz4740_ohci->vbus);
 229
 230        clk_disable(jz4740_ohci->clk);
 231
 232        usb_put_hcd(hcd);
 233
 234        return 0;
 235}
 236
 237static struct platform_driver ohci_hcd_jz4740_driver = {
 238        .probe = jz4740_ohci_probe,
 239        .remove = jz4740_ohci_remove,
 240        .driver = {
 241                .name = "jz4740-ohci",
 242        },
 243};
 244
 245MODULE_ALIAS("platform:jz4740-ohci");
 246