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 (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))
 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                set_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
 275        }
 276}
 277
 278/**
 279 * c67x00_hcd_start: Host controller start hook
 280 */
 281static int c67x00_hcd_start(struct usb_hcd *hcd)
 282{
 283        hcd->uses_new_polling = 1;
 284        hcd->state = HC_STATE_RUNNING;
 285        hcd->poll_rh = 1;
 286
 287        return 0;
 288}
 289
 290/**
 291 * c67x00_hcd_stop: Host controller stop hook
 292 */
 293static void c67x00_hcd_stop(struct usb_hcd *hcd)
 294{
 295        /* Nothing to do */
 296}
 297
 298static int c67x00_hcd_get_frame(struct usb_hcd *hcd)
 299{
 300        struct c67x00_hcd *c67x00 = hcd_to_c67x00_hcd(hcd);
 301        u16 temp_val;
 302
 303        dev_dbg(c67x00_hcd_dev(c67x00), "%s\n", __func__);
 304        temp_val = c67x00_ll_husb_get_frame(c67x00->sie);
 305        temp_val &= HOST_FRAME_MASK;
 306        return temp_val ? (temp_val - 1) : HOST_FRAME_MASK;
 307}
 308
 309static struct hc_driver c67x00_hc_driver = {
 310        .description    = "c67x00-hcd",
 311        .product_desc   = "Cypress C67X00 Host Controller",
 312        .hcd_priv_size  = sizeof(struct c67x00_hcd),
 313        .flags          = HCD_USB11 | HCD_MEMORY,
 314
 315        /*
 316         * basic lifecycle operations
 317         */
 318        .start          = c67x00_hcd_start,
 319        .stop           = c67x00_hcd_stop,
 320
 321        /*
 322         * managing i/o requests and associated device resources
 323         */
 324        .urb_enqueue    = c67x00_urb_enqueue,
 325        .urb_dequeue    = c67x00_urb_dequeue,
 326        .endpoint_disable = c67x00_endpoint_disable,
 327
 328        /*
 329         * scheduling support
 330         */
 331        .get_frame_number = c67x00_hcd_get_frame,
 332
 333        /*
 334         * root hub support
 335         */
 336        .hub_status_data = c67x00_hub_status_data,
 337        .hub_control    = c67x00_hub_control,
 338};
 339
 340/* ---------------------------------------------------------------------
 341 * Setup/Teardown routines
 342 */
 343
 344int c67x00_hcd_probe(struct c67x00_sie *sie)
 345{
 346        struct c67x00_hcd *c67x00;
 347        struct usb_hcd *hcd;
 348        unsigned long flags;
 349        int retval;
 350
 351        if (usb_disabled())
 352                return -ENODEV;
 353
 354        hcd = usb_create_hcd(&c67x00_hc_driver, sie_dev(sie), "c67x00_sie");
 355        if (!hcd) {
 356                retval = -ENOMEM;
 357                goto err0;
 358        }
 359        c67x00 = hcd_to_c67x00_hcd(hcd);
 360
 361        spin_lock_init(&c67x00->lock);
 362        c67x00->sie = sie;
 363
 364        INIT_LIST_HEAD(&c67x00->list[PIPE_ISOCHRONOUS]);
 365        INIT_LIST_HEAD(&c67x00->list[PIPE_INTERRUPT]);
 366        INIT_LIST_HEAD(&c67x00->list[PIPE_CONTROL]);
 367        INIT_LIST_HEAD(&c67x00->list[PIPE_BULK]);
 368        c67x00->urb_count = 0;
 369        INIT_LIST_HEAD(&c67x00->td_list);
 370        c67x00->td_base_addr = CY_HCD_BUF_ADDR + SIE_TD_OFFSET(sie->sie_num);
 371        c67x00->buf_base_addr = CY_HCD_BUF_ADDR + SIE_BUF_OFFSET(sie->sie_num);
 372        c67x00->max_frame_bw = MAX_FRAME_BW_STD;
 373
 374        c67x00_ll_husb_init_host_port(sie);
 375
 376        init_completion(&c67x00->endpoint_disable);
 377        retval = c67x00_sched_start_scheduler(c67x00);
 378        if (retval)
 379                goto err1;
 380
 381        retval = usb_add_hcd(hcd, 0, 0);
 382        if (retval) {
 383                dev_dbg(sie_dev(sie), "%s: usb_add_hcd returned %d\n",
 384                        __func__, retval);
 385                goto err2;
 386        }
 387
 388        spin_lock_irqsave(&sie->lock, flags);
 389        sie->private_data = c67x00;
 390        sie->irq = c67x00_hcd_irq;
 391        spin_unlock_irqrestore(&sie->lock, flags);
 392
 393        return retval;
 394
 395 err2:
 396        c67x00_sched_stop_scheduler(c67x00);
 397 err1:
 398        usb_put_hcd(hcd);
 399 err0:
 400        return retval;
 401}
 402
 403/* may be called with controller, bus, and devices active */
 404void c67x00_hcd_remove(struct c67x00_sie *sie)
 405{
 406        struct c67x00_hcd *c67x00 = sie->private_data;
 407        struct usb_hcd *hcd = c67x00_hcd_to_hcd(c67x00);
 408
 409        c67x00_sched_stop_scheduler(c67x00);
 410        usb_remove_hcd(hcd);
 411        usb_put_hcd(hcd);
 412}
 413