linux/drivers/usb/c67x00/c67x00-hcd.c
<<
>>
Prefs
   1/*
   2 * c67x00-hcd.c: Cypress C67X00 USB Host Controller Driver
   3 *
   4 * Copyright (C) 2006-2008 Barco N.V.
   5 *    Derived from the Cypress cy7c67200/300 ezusb linux driver and
   6 *    based on multiple host controller drivers inside the linux kernel.
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License as published by
  10 * the Free Software Foundation; either version 2 of the License, or
  11 * (at your option) any later version.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  21 * MA  02110-1301  USA.
  22 */
  23
  24#include <linux/device.h>
  25#include <linux/platform_device.h>
  26#include <linux/usb.h>
  27
  28#include "c67x00.h"
  29#include "c67x00-hcd.h"
  30
  31/* --------------------------------------------------------------------------
  32 * Root Hub Support
  33 */
  34
  35static __u8 c67x00_hub_des[] = {
  36        0x09,                   /*  __u8  bLength; */
  37        0x29,                   /*  __u8  bDescriptorType; Hub-descriptor */
  38        0x02,                   /*  __u8  bNbrPorts; */
  39        0x00,                   /* __u16  wHubCharacteristics; */
  40        0x00,                   /*   (per-port OC, no power switching) */
  41        0x32,                   /*  __u8  bPwrOn2pwrGood; 2ms */
  42        0x00,                   /*  __u8  bHubContrCurrent; 0 mA */
  43        0x00,                   /*  __u8  DeviceRemovable; ** 7 Ports max ** */
  44        0xff,                   /*  __u8  PortPwrCtrlMask; ** 7 ports max ** */
  45};
  46
  47static void c67x00_hub_reset_host_port(struct c67x00_sie *sie, int port)
  48{
  49        struct c67x00_hcd *c67x00 = sie->private_data;
  50        unsigned long flags;
  51
  52        c67x00_ll_husb_reset(sie, port);
  53
  54        spin_lock_irqsave(&c67x00->lock, flags);
  55        c67x00_ll_husb_reset_port(sie, port);
  56        spin_unlock_irqrestore(&c67x00->lock, flags);
  57
  58        c67x00_ll_set_husb_eot(sie->dev, DEFAULT_EOT);
  59}
  60
  61static int c67x00_hub_status_data(struct usb_hcd *hcd, char *buf)
  62{
  63        struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd);
  64        struct c67x00_sie *sie = c67x00->sie;
  65        u16 status;
  66        int i;
  67
  68        *buf = 0;
  69        status = c67x00_ll_usb_get_status(sie);
  70        for (i = 0; i < C67X00_PORTS; i++)
  71                if (status & PORT_CONNECT_CHANGE(i))
  72                        *buf |= (1 << i);
  73
  74        /* bit 0 denotes hub change, b1..n port change */
  75        *buf <<= 1;
  76
  77        return !!*buf;
  78}
  79
  80static int c67x00_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
  81                              u16 wIndex, char *buf, u16 wLength)
  82{
  83        struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd);
  84        struct c67x00_sie *sie = c67x00->sie;
  85        u16 status, usb_status;
  86        int len = 0;
  87        unsigned int port = wIndex-1;
  88        u16 wPortChange, wPortStatus;
  89
  90        switch (typeReq) {
  91
  92        case GetHubStatus:
  93                *(__le32 *) buf = cpu_to_le32(0);
  94                len = 4;                /* hub power */
  95                break;
  96
  97        case GetPortStatus:
  98                if (wIndex > C67X00_PORTS)
  99                        return -EPIPE;
 100
 101                status = c67x00_ll_usb_get_status(sie);
 102                usb_status = c67x00_ll_get_usb_ctl(sie);
 103
 104                wPortChange = 0;
 105                if (status & PORT_CONNECT_CHANGE(port))
 106                        wPortChange |= USB_PORT_STAT_C_CONNECTION;
 107
 108                wPortStatus = USB_PORT_STAT_POWER;
 109                if (!(status & PORT_SE0_STATUS(port)))
 110                        wPortStatus |= USB_PORT_STAT_CONNECTION;
 111                if (usb_status & LOW_SPEED_PORT(port)) {
 112                        wPortStatus |= USB_PORT_STAT_LOW_SPEED;
 113                        c67x00->low_speed_ports |= (1 << port);
 114                } else
 115                        c67x00->low_speed_ports &= ~(1 << port);
 116
 117                if (usb_status & SOF_EOP_EN(port))
 118                        wPortStatus |= USB_PORT_STAT_ENABLE;
 119
 120                *(__le16 *) buf = cpu_to_le16(wPortStatus);
 121                *(__le16 *) (buf + 2) = cpu_to_le16(wPortChange);
 122                len = 4;
 123                break;
 124
 125        case SetHubFeature:     /* We don't implement these */
 126        case ClearHubFeature:
 127                switch (wValue) {
 128                case C_HUB_OVER_CURRENT:
 129                case C_HUB_LOCAL_POWER:
 130                        len = 0;
 131                        break;
 132
 133                default:
 134                        return -EPIPE;
 135                }
 136                break;
 137
 138        case SetPortFeature:
 139                if (wIndex > C67X00_PORTS)
 140                        return -EPIPE;
 141
 142                switch (wValue) {
 143                case USB_PORT_FEAT_SUSPEND:
 144                        dev_dbg(c67x00_hcd_dev(c67x00),
 145                                "SetPortFeature %d (SUSPEND)\n", port);
 146                        len = 0;
 147                        break;
 148
 149                case USB_PORT_FEAT_RESET:
 150                        c67x00_hub_reset_host_port(sie, port);
 151                        len = 0;
 152                        break;
 153
 154                case USB_PORT_FEAT_POWER:
 155                        /* Power always enabled */
 156                        len = 0;
 157                        break;
 158
 159                default:
 160                        dev_dbg(c67x00_hcd_dev(c67x00),
 161                                "%s: SetPortFeature %d (0x%04x) Error!\n",
 162                                __func__, port, wValue);
 163                        return -EPIPE;
 164                }
 165                break;
 166
 167        case ClearPortFeature:
 168                if (wIndex > C67X00_PORTS)
 169                        return -EPIPE;
 170
 171                switch (wValue) {
 172                case USB_PORT_FEAT_ENABLE:
 173                        /* Reset the port so that the c67x00 also notices the
 174                         * disconnect */
 175                        c67x00_hub_reset_host_port(sie, port);
 176                        len = 0;
 177                        break;
 178
 179                case USB_PORT_FEAT_C_ENABLE:
 180                        dev_dbg(c67x00_hcd_dev(c67x00),
 181                                "ClearPortFeature (%d): C_ENABLE\n", port);
 182                        len = 0;
 183                        break;
 184
 185                case USB_PORT_FEAT_SUSPEND:
 186                        dev_dbg(c67x00_hcd_dev(c67x00),
 187                                "ClearPortFeature (%d): SUSPEND\n", port);
 188                        len = 0;
 189                        break;
 190
 191                case USB_PORT_FEAT_C_SUSPEND:
 192                        dev_dbg(c67x00_hcd_dev(c67x00),
 193                                "ClearPortFeature (%d): C_SUSPEND\n", port);
 194                        len = 0;
 195                        break;
 196
 197                case USB_PORT_FEAT_POWER:
 198                        dev_dbg(c67x00_hcd_dev(c67x00),
 199                                "ClearPortFeature (%d): POWER\n", port);
 200                        return -EPIPE;
 201
 202                case USB_PORT_FEAT_C_CONNECTION:
 203                        c67x00_ll_usb_clear_status(sie,
 204                                                   PORT_CONNECT_CHANGE(port));
 205                        len = 0;
 206                        break;
 207
 208                case USB_PORT_FEAT_C_OVER_CURRENT:
 209                        dev_dbg(c67x00_hcd_dev(c67x00),
 210                                "ClearPortFeature (%d): OVER_CURRENT\n", port);
 211                        len = 0;
 212                        break;
 213
 214                case USB_PORT_FEAT_C_RESET:
 215                        dev_dbg(c67x00_hcd_dev(c67x00),
 216                                "ClearPortFeature (%d): C_RESET\n", port);
 217                        len = 0;
 218                        break;
 219
 220                default:
 221                        dev_dbg(c67x00_hcd_dev(c67x00),
 222                                "%s: ClearPortFeature %d (0x%04x) Error!\n",
 223                                __func__, port, wValue);
 224                        return -EPIPE;
 225                }
 226                break;
 227
 228        case GetHubDescriptor:
 229                len = min_t(unsigned int, sizeof(c67x00_hub_des), wLength);
 230                memcpy(buf, c67x00_hub_des, len);
 231                break;
 232
 233        default:
 234                dev_dbg(c67x00_hcd_dev(c67x00), "%s: unknown\n", __func__);
 235                return -EPIPE;
 236        }
 237
 238        return 0;
 239}
 240
 241/* ---------------------------------------------------------------------
 242 * Main part of host controller driver
 243 */
 244
 245/**
 246 * c67x00_hcd_irq
 247 *
 248 * This function is called from the interrupt handler in c67x00-drv.c
 249 */
 250static void c67x00_hcd_irq(struct c67x00_sie *sie, u16 int_status, u16 msg)
 251{
 252        struct c67x00_hcd *c67x00 = sie->private_data;
 253        struct usb_hcd *hcd = c67x00_hcd_to_hcd(c67x00);
 254
 255        /* Handle sie message flags */
 256        if (msg) {
 257                if (msg & HUSB_TDListDone)
 258                        c67x00_sched_kick(c67x00);
 259                else
 260                        dev_warn(c67x00_hcd_dev(c67x00),
 261                                 "Unknown SIE msg flag(s): 0x%04x\n", msg);
 262        }
 263
 264        if (unlikely(hcd->state == HC_STATE_HALT))
 265                return;
 266
 267        if (!HCD_HW_ACCESSIBLE(hcd))
 268                return;
 269
 270        /* Handle Start of frame events */
 271        if (int_status & SOFEOP_FLG(sie->sie_num)) {
 272                c67x00_ll_usb_clear_status(sie, SOF_EOP_IRQ_FLG);
 273                c67x00_sched_kick(c67x00);
 274        }
 275}
 276
 277/**
 278 * c67x00_hcd_start: Host controller start hook
 279 */
 280static int c67x00_hcd_start(struct usb_hcd *hcd)
 281{
 282        hcd->uses_new_polling = 1;
 283        hcd->state = HC_STATE_RUNNING;
 284        set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
 285
 286        return 0;
 287}
 288
 289/**
 290 * c67x00_hcd_stop: Host controller stop hook
 291 */
 292static void c67x00_hcd_stop(struct usb_hcd *hcd)
 293{
 294        /* Nothing to do */
 295}
 296
 297static int c67x00_hcd_get_frame(struct usb_hcd *hcd)
 298{
 299        struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd);
 300        u16 temp_val;
 301
 302        dev_dbg(c67x00_hcd_dev(c67x00), "%s\n", __func__);
 303        temp_val = c67x00_ll_husb_get_frame(c67x00->sie);
 304        temp_val &= HOST_FRAME_MASK;
 305        return temp_val ? (temp_val - 1) : HOST_FRAME_MASK;
 306}
 307
 308static struct hc_driver c67x00_hc_driver = {
 309        .description    = "c67x00-hcd",
 310        .product_desc   = "Cypress C67X00 Host Controller",
 311        .hcd_priv_size  = sizeof(struct c67x00_hcd),
 312        .flags          = HCD_USB11 | HCD_MEMORY,
 313
 314        /*
 315         * basic lifecycle operations
 316         */
 317        .start          = c67x00_hcd_start,
 318        .stop           = c67x00_hcd_stop,
 319
 320        /*
 321         * managing i/o requests and associated device resources
 322         */
 323        .urb_enqueue    = c67x00_urb_enqueue,
 324        .urb_dequeue    = c67x00_urb_dequeue,
 325        .endpoint_disable = c67x00_endpoint_disable,
 326
 327        /*
 328         * scheduling support
 329         */
 330        .get_frame_number = c67x00_hcd_get_frame,
 331
 332        /*
 333         * root hub support
 334         */
 335        .hub_status_data = c67x00_hub_status_data,
 336        .hub_control    = c67x00_hub_control,
 337};
 338
 339/* ---------------------------------------------------------------------
 340 * Setup/Teardown routines
 341 */
 342
 343int c67x00_hcd_probe(struct c67x00_sie *sie)
 344{
 345        struct c67x00_hcd *c67x00;
 346        struct usb_hcd *hcd;
 347        unsigned long flags;
 348        int retval;
 349
 350        if (usb_disabled())
 351                return -ENODEV;
 352
 353        hcd = usb_create_hcd(&c67x00_hc_driver, sie_dev(sie), "c67x00_sie");
 354        if (!hcd) {
 355                retval = -ENOMEM;
 356                goto err0;
 357        }
 358        c67x00 = hcd_to_c67x00_hcd(hcd);
 359
 360        spin_lock_init(&c67x00->lock);
 361        c67x00->sie = sie;
 362
 363        INIT_LIST_HEAD(&c67x00->list[PIPE_ISOCHRONOUS]);
 364        INIT_LIST_HEAD(&c67x00->list[PIPE_INTERRUPT]);
 365        INIT_LIST_HEAD(&c67x00->list[PIPE_CONTROL]);
 366        INIT_LIST_HEAD(&c67x00->list[PIPE_BULK]);
 367        c67x00->urb_count = 0;
 368        INIT_LIST_HEAD(&c67x00->td_list);
 369        c67x00->td_base_addr = CY_HCD_BUF_ADDR + SIE_TD_OFFSET(sie->sie_num);
 370        c67x00->buf_base_addr = CY_HCD_BUF_ADDR + SIE_BUF_OFFSET(sie->sie_num);
 371        c67x00->max_frame_bw = MAX_FRAME_BW_STD;
 372
 373        c67x00_ll_husb_init_host_port(sie);
 374
 375        init_completion(&c67x00->endpoint_disable);
 376        retval = c67x00_sched_start_scheduler(c67x00);
 377        if (retval)
 378                goto err1;
 379
 380        retval = usb_add_hcd(hcd, 0, 0);
 381        if (retval) {
 382                dev_dbg(sie_dev(sie), "%s: usb_add_hcd returned %d\n",
 383                        __func__, retval);
 384                goto err2;
 385        }
 386
 387        spin_lock_irqsave(&sie->lock, flags);
 388        sie->private_data = c67x00;
 389        sie->irq = c67x00_hcd_irq;
 390        spin_unlock_irqrestore(&sie->lock, flags);
 391
 392        return retval;
 393
 394 err2:
 395        c67x00_sched_stop_scheduler(c67x00);
 396 err1:
 397        usb_put_hcd(hcd);
 398 err0:
 399        return retval;
 400}
 401
 402/* may be called with controller, bus, and devices active */
 403void c67x00_hcd_remove(struct c67x00_sie *sie)
 404{
 405        struct c67x00_hcd *c67x00 = sie->private_data;
 406        struct usb_hcd *hcd = c67x00_hcd_to_hcd(c67x00);
 407
 408        c67x00_sched_stop_scheduler(c67x00);
 409        usb_remove_hcd(hcd);
 410        usb_put_hcd(hcd);
 411}
 412