OpenWrt – Rev 1

Subversion Repositories:
Rev:
/*
 * ADM5120 HCD (Host Controller Driver) for USB
 *
 * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
 *
 * This file was derived from: drivers/usb/host/ohci.h
 *   (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
 *   (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License version 2 as published
 *  by the Free Software Foundation.
 *
 */

/*
 * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to
 * __leXX (normally) or __beXX (given OHCI_BIG_ENDIAN), depending on the
 * host controller implementation.
 */
typedef __u32 __bitwise __hc32;
typedef __u16 __bitwise __hc16;

/*
 * OHCI Endpoint Descriptor (ED) ... holds TD queue
 * See OHCI spec, section 4.2
 *
 * This is a "Queue Head" for those transfers, which is why
 * both EHCI and UHCI call similar structures a "QH".
 */

#define TD_DATALEN_MAX  4096

#define ED_ALIGN        16
#define ED_MASK ((u32)~(ED_ALIGN-1))    /* strip hw status in low addr bits */

struct ed {
        /* first fields are hardware-specified */
        __hc32                  hwINFO;      /* endpoint config bitmap */
        /* info bits defined by hcd */
#define ED_DEQUEUE      (1 << 27)
        /* info bits defined by the hardware */
#define ED_MPS_SHIFT    16
#define ED_MPS_MASK     ((1 << 11)-1)
#define ED_MPS_GET(x)   (((x) >> ED_MPS_SHIFT) & ED_MPS_MASK)
#define ED_ISO          (1 << 15)               /* isochronous endpoint */
#define ED_SKIP         (1 << 14)
#define ED_SPEED_FULL   (1 << 13)               /* fullspeed device */
#define ED_INT          (1 << 11)               /* interrupt endpoint */
#define ED_EN_SHIFT     7                       /* endpoint shift */
#define ED_EN_MASK      ((1 << 4)-1)            /* endpoint mask */
#define ED_EN_GET(x)    (((x) >> ED_EN_SHIFT) & ED_EN_MASK)
#define ED_FA_MASK      ((1 << 7)-1)            /* function address mask */
#define ED_FA_GET(x)    ((x) & ED_FA_MASK)
        __hc32                  hwTailP;        /* tail of TD list */
        __hc32                  hwHeadP;        /* head of TD list (hc r/w) */
#define ED_C            (0x02)                  /* toggle carry */
#define ED_H            (0x01)                  /* halted */
        __hc32                  hwNextED;       /* next ED in list */

        /* rest are purely for the driver's use */
        dma_addr_t              dma;            /* addr of ED */
        struct td               *dummy;         /* next TD to activate */

        struct list_head        urb_list;       /* list of our URBs */

        /* host's view of schedule */
        struct ed               *ed_next;       /* on schedule list */
        struct ed               *ed_prev;       /* for non-interrupt EDs */
        struct ed               *ed_rm_next;    /* on rm list */
        struct list_head        td_list;        /* "shadow list" of our TDs */

        /* create --> IDLE --> OPER --> ... --> IDLE --> destroy
         * usually:  OPER --> UNLINK --> (IDLE | OPER) --> ...
         */
        u8                      state;          /* ED_{IDLE,UNLINK,OPER} */
#define ED_IDLE         0x00            /* NOT linked to HC */
#define ED_UNLINK       0x01            /* being unlinked from hc */
#define ED_OPER         0x02            /* IS linked to hc */

        u8                      type;           /* PIPE_{BULK,...} */

        /* periodic scheduling params (for intr and iso) */
        u8                      branch;
        u16                     interval;
        u16                     load;
        u16                     last_iso;       /* iso only */

        /* HC may see EDs on rm_list until next frame (frame_no == tick) */
        u16                     tick;
} __attribute__ ((aligned(ED_ALIGN)));

/*
 * OHCI Transfer Descriptor (TD) ... one per transfer segment
 * See OHCI spec, sections 4.3.1 (general = control/bulk/interrupt)
 * and 4.3.2 (iso)
 */

#define TD_ALIGN        32
#define TD_MASK ((u32)~(TD_ALIGN-1))    /* strip hw status in low addr bits */

struct td {
        /* first fields are hardware-specified */
        __hc32          hwINFO;         /* transfer info bitmask */

        /* hwINFO bits */
#define TD_OWN          (1 << 31)               /* owner of the descriptor */
#define TD_CC_SHIFT     27                      /* condition code */
#define TD_CC_MASK      0xf
#define TD_CC           (TD_CC_MASK << TD_CC_SHIFT)
#define TD_CC_GET(x)    (((x) >> TD_CC_SHIFT) & TD_CC_MASK)

#define TD_EC_SHIFT     25                      /* error count */
#define TD_EC_MASK      0x3
#define TD_EC           (TD_EC_MASK << TD_EC_SHIFT)
#define TD_EC_GET(x)    ((x >> TD_EC_SHIFT) & TD_EC_MASK)
#define TD_T_SHIFT      23                      /* data toggle state */
#define TD_T_MASK       0x3
#define TD_T            (TD_T_MASK << TD_T_SHIFT)
#define TD_T_DATA0      (0x2 << TD_T_SHIFT)     /* DATA0 */
#define TD_T_DATA1      (0x3 << TD_T_SHIFT)     /* DATA1 */
#define TD_T_CARRY      (0x0 << TD_T_SHIFT)     /* uses ED_C */
#define TD_T_GET(x)     (((x) >> TD_T_SHIFT) & TD_T_MASK)
#define TD_DP_SHIFT     21                      /* direction/pid */
#define TD_DP_MASK      0x3
#define TD_DP           (TD_DP_MASK << TD_DP_SHIFT)
#define TD_DP_GET       (((x) >> TD_DP_SHIFT) & TD_DP_MASK)
#define TD_DP_SETUP     (0x0 << TD_DP_SHIFT)    /* SETUP pid */
#define TD_DP_OUT       (0x1 << TD_DP_SHIFT)    /* OUT pid */
#define TD_DP_IN        (0x2 << TD_DP_SHIFT)    /* IN pid */
#define TD_ISI_SHIFT    8                       /* Interrupt Service Interval */
#define TD_ISI_MASK     0x3f
#define TD_ISI_GET(x)   (((x) >> TD_ISI_SHIFT) & TD_ISI_MASK)
#define TD_FN_MASK      0x3f                    /* frame number */
#define TD_FN_GET(x)    ((x) & TD_FN_MASK)

        __hc32          hwDBP;          /* Data Buffer Pointer (or 0) */
        __hc32          hwCBL;          /* Controller/Buffer Length */

        /* hwCBL bits */
#define TD_BL_MASK      0xffff          /* buffer length */
#define TD_BL_GET(x)    ((x) & TD_BL_MASK)
#define TD_IE           (1 << 16)       /* interrupt enable */
        __hc32          hwNextTD;       /* Next TD Pointer */

        /* rest are purely for the driver's use */
        __u8            index;
        struct ed       *ed;
        struct td       *td_hash;       /* dma-->td hashtable */
        struct td       *next_dl_td;
        struct urb      *urb;

        dma_addr_t      td_dma;         /* addr of this TD */
        dma_addr_t      data_dma;       /* addr of data it points to */

        struct list_head td_list;       /* "shadow list", TDs on same ED */

        u32             flags;
#define TD_FLAG_DONE    (1 << 17)       /* retired to done list */
#define TD_FLAG_ISO     (1 << 16)       /* copy of ED_ISO */
} __attribute__ ((aligned(TD_ALIGN)));  /* c/b/i need 16; only iso needs 32 */

/*
 * Hardware transfer status codes -- CC from td->hwINFO
 */
#define TD_CC_NOERROR           0x00
#define TD_CC_CRC               0x01
#define TD_CC_BITSTUFFING       0x02
#define TD_CC_DATATOGGLEM       0x03
#define TD_CC_STALL             0x04
#define TD_CC_DEVNOTRESP        0x05
#define TD_CC_PIDCHECKFAIL      0x06
#define TD_CC_UNEXPECTEDPID     0x07
#define TD_CC_DATAOVERRUN       0x08
#define TD_CC_DATAUNDERRUN      0x09
    /* 0x0A, 0x0B reserved for hardware */
#define TD_CC_BUFFEROVERRUN     0x0C
#define TD_CC_BUFFERUNDERRUN    0x0D
    /* 0x0E, 0x0F reserved for HCD */
#define TD_CC_HCD0              0x0E
#define TD_CC_NOTACCESSED       0x0F

/*
 * preshifted status codes
 */
#define TD_SCC_NOTACCESSED      (TD_CC_NOTACCESSED << TD_CC_SHIFT)


/* map OHCI TD status codes (CC) to errno values */
static const int cc_to_error[16] = {
        /* No  Error  */        0,
        /* CRC Error  */        -EILSEQ,
        /* Bit Stuff  */        -EPROTO,
        /* Data Togg  */        -EILSEQ,
        /* Stall      */        -EPIPE,
        /* DevNotResp */        -ETIME,
        /* PIDCheck   */        -EPROTO,
        /* UnExpPID   */        -EPROTO,
        /* DataOver   */        -EOVERFLOW,
        /* DataUnder  */        -EREMOTEIO,
        /* (for hw)   */        -EIO,
        /* (for hw)   */        -EIO,
        /* BufferOver */        -ECOMM,
        /* BuffUnder  */        -ENOSR,
        /* (for HCD)  */        -EALREADY,
        /* (for HCD)  */        -EALREADY
};

#define NUM_INTS        32

/*
 * This is the structure of the OHCI controller's memory mapped I/O region.
 * You must use readl() and writel() (in <asm/io.h>) to access these fields!!
 * Layout is in section 7 (and appendix B) of the spec.
 */
struct admhcd_regs {
        __hc32  gencontrol;     /* General Control */
        __hc32  int_status;     /* Interrupt Status */
        __hc32  int_enable;     /* Interrupt Enable */
        __hc32  reserved00;
        __hc32  host_control;   /* Host General Control */
        __hc32  reserved01;
        __hc32  fminterval;     /* Frame Interval */
        __hc32  fmnumber;       /* Frame Number */
        __hc32  reserved02;
        __hc32  reserved03;
        __hc32  reserved04;
        __hc32  reserved05;
        __hc32  reserved06;
        __hc32  reserved07;
        __hc32  reserved08;
        __hc32  reserved09;
        __hc32  reserved10;
        __hc32  reserved11;
        __hc32  reserved12;
        __hc32  reserved13;
        __hc32  reserved14;
        __hc32  reserved15;
        __hc32  reserved16;
        __hc32  reserved17;
        __hc32  reserved18;
        __hc32  reserved19;
        __hc32  reserved20;
        __hc32  reserved21;
        __hc32  lsthresh;       /* Low Speed Threshold */
        __hc32  rhdesc;         /* Root Hub Descriptor */
#define MAX_ROOT_PORTS  2
        __hc32  portstatus[MAX_ROOT_PORTS]; /* Port Status */
        __hc32  hosthead;       /* Host Descriptor Head */
} __attribute__ ((aligned(32)));

/*
 * General Control register bits
 */
#define ADMHC_CTRL_UHFE (1 << 0)        /* USB Host Function Enable */
#define ADMHC_CTRL_SIR  (1 << 1)        /* Software Interrupt request */
#define ADMHC_CTRL_DMAA (1 << 2)        /* DMA Arbitration Control */
#define ADMHC_CTRL_SR   (1 << 3)        /* Software Reset */

/*
 * Host General Control register bits
 */
#define ADMHC_HC_BUSS           0x3             /* USB bus state */
#define   ADMHC_BUSS_RESET      0x0
#define   ADMHC_BUSS_RESUME     0x1
#define   ADMHC_BUSS_OPER       0x2
#define   ADMHC_BUSS_SUSPEND    0x3
#define ADMHC_HC_DMAE           (1 << 2)        /* DMA enable */

/*
 * Interrupt Status/Enable register bits
 */
#define ADMHC_INTR_SOFI (1 << 4)        /* start of frame */
#define ADMHC_INTR_RESI (1 << 5)        /* resume detected */
#define ADMHC_INTR_6    (1 << 6)        /* unknown */
#define ADMHC_INTR_7    (1 << 7)        /* unknown */
#define ADMHC_INTR_BABI (1 << 8)        /* babble detected */
#define ADMHC_INTR_INSM (1 << 9)        /* root hub status change */
#define ADMHC_INTR_SO   (1 << 10)       /* scheduling overrun */
#define ADMHC_INTR_FNO  (1 << 11)       /* frame number overflow */
#define ADMHC_INTR_TDC  (1 << 20)       /* transfer descriptor completed */
#define ADMHC_INTR_SWI  (1 << 29)       /* software interrupt */
#define ADMHC_INTR_FATI (1 << 30)       /* fatal error */
#define ADMHC_INTR_INTA (1 << 31)       /* interrupt active */

#define ADMHC_INTR_MIE  (1 << 31)       /* master interrupt enable */

/*
 * SOF Frame Interval register bits
 */
#define ADMHC_SFI_FI_MASK       ((1 << 14)-1)   /* Frame Interval value */
#define ADMHC_SFI_FSLDP_SHIFT   16
#define ADMHC_SFI_FSLDP_MASK    ((1 << 15)-1)
#define ADMHC_SFI_FIT           (1 << 31)       /* Frame Interval Toggle */

/*
 * SOF Frame Number register bits
 */
#define ADMHC_SFN_FN_MASK       ((1 << 16)-1)   /* Frame Number Mask */
#define ADMHC_SFN_FR_SHIFT      16              /* Frame Remaining Shift */
#define ADMHC_SFN_FR_MASK       ((1 << 14)-1)   /* Frame Remaining Mask */
#define ADMHC_SFN_FRT           (1 << 31)       /* Frame Remaining Toggle */

/*
 * Root Hub Descriptor register bits
 */
#define ADMHC_RH_NUMP   0xff            /* number of ports */
#define ADMHC_RH_PSM    (1 << 8)        /* power switching mode */
#define ADMHC_RH_NPS    (1 << 9)        /* no power switching */
#define ADMHC_RH_OCPM   (1 << 10)       /* over current protection mode */
#define ADMHC_RH_NOCP   (1 << 11)       /* no over current protection */
#define ADMHC_RH_PPCM   (0xff << 16)    /* port power control */

#define ADMHC_RH_LPS    (1 << 24)       /* local power switch */
#define ADMHC_RH_OCI    (1 << 25)       /* over current indicator */

/* status change bits */
#define ADMHC_RH_LPSC   (1 << 26)       /* local power switch change */
#define ADMHC_RH_OCIC   (1 << 27)       /* over current indicator change */

#define ADMHC_RH_DRWE   (1 << 28)       /* device remote wakeup enable */
#define ADMHC_RH_CRWE   (1 << 29)       /* clear remote wakeup enable */

#define ADMHC_RH_CGP    (1 << 24)       /* clear global power */
#define ADMHC_RH_SGP    (1 << 26)       /* set global power */

/*
 * Port Status register bits
 */
#define ADMHC_PS_CCS    (1 << 0)        /* current connect status */
#define ADMHC_PS_PES    (1 << 1)        /* port enable status */
#define ADMHC_PS_PSS    (1 << 2)        /* port suspend status */
#define ADMHC_PS_POCI   (1 << 3)        /* port over current indicator */
#define ADMHC_PS_PRS    (1 << 4)        /* port reset status */
#define ADMHC_PS_PPS    (1 << 8)        /* port power status */
#define ADMHC_PS_LSDA   (1 << 9)        /* low speed device attached */

/* status change bits */
#define ADMHC_PS_CSC    (1 << 16)       /* connect status change */
#define ADMHC_PS_PESC   (1 << 17)       /* port enable status change */
#define ADMHC_PS_PSSC   (1 << 18)       /* port suspend status change */
#define ADMHC_PS_OCIC   (1 << 19)       /* over current indicator change */
#define ADMHC_PS_PRSC   (1 << 20)       /* port reset status change */

/* port feature bits */
#define ADMHC_PS_CPE    (1 << 0)        /* clear port enable */
#define ADMHC_PS_SPE    (1 << 1)        /* set port enable */
#define ADMHC_PS_SPS    (1 << 2)        /* set port suspend */
#define ADMHC_PS_CPS    (1 << 3)        /* clear suspend status */
#define ADMHC_PS_SPR    (1 << 4)        /* set port reset */
#define ADMHC_PS_SPP    (1 << 8)        /* set port power */
#define ADMHC_PS_CPP    (1 << 9)        /* clear port power */

/*
 * the POTPGT value is not defined in the ADMHC, so define a dummy value
 */
#define ADMHC_POTPGT    2               /* in ms */

/* hcd-private per-urb state */
struct urb_priv {
        struct ed               *ed;
        struct list_head        pending;        /* URBs on the same ED */

        u32                     td_cnt;         /* # tds in this request */
        u32                     td_idx;         /* index of the current td */
        struct td               *td[0];         /* all TDs in this request */
};

#define TD_HASH_SIZE    64    /* power'o'two */
/* sizeof (struct td) ~= 64 == 2^6 ... */
#define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 6)) % TD_HASH_SIZE)

/*
 * This is the full ADMHCD controller description
 *
 * Note how the "proper" USB information is just
 * a subset of what the full implementation needs. (Linus)
 */

struct admhcd {
        spinlock_t              lock;

        /*
         * I/O memory used to communicate with the HC (dma-consistent)
         */
        struct admhcd_regs __iomem *regs;

        /*
         * hcd adds to schedule for a live hc any time, but removals finish
         * only at the start of the next frame.
         */

        struct ed               *ed_head;
        struct ed               *ed_tails[4];

        struct ed               *ed_rm_list;    /* to be removed */

        struct ed               *periodic[NUM_INTS];    /* shadow int_table */

#if 0   /* TODO: remove? */
        /*
         * OTG controllers and transceivers need software interaction;
         * other external transceivers should be software-transparent
         */
        struct otg_transceiver  *transceiver;
        void (*start_hnp)(struct admhcd *ahcd);
#endif

        /*
         * memory management for queue data structures
         */
        struct dma_pool         *td_cache;
        struct dma_pool         *ed_cache;
        struct td               *td_hash[TD_HASH_SIZE];
        struct list_head        pending;

        /*
         * driver state
         */
        int                     num_ports;
        int                     load[NUM_INTS];
        u32                     host_control;   /* copy of the host_control reg */
        unsigned long           next_statechange;       /* suspend/resume */
        u32                     fminterval;             /* saved register */
        unsigned                autostop:1;     /* rh auto stopping/stopped */

        unsigned long           flags;          /* for HC bugs */
#define OHCI_QUIRK_AMD756       0x01                    /* erratum #4 */
#define OHCI_QUIRK_SUPERIO      0x02                    /* natsemi */
#define OHCI_QUIRK_INITRESET    0x04                    /* SiS, OPTi, ... */
#define OHCI_QUIRK_BE_DESC      0x08                    /* BE descriptors */
#define OHCI_QUIRK_BE_MMIO      0x10                    /* BE registers */
#define OHCI_QUIRK_ZFMICRO      0x20                    /* Compaq ZFMicro chipset*/
        /* there are also chip quirks/bugs in init logic */

#ifdef DEBUG
        struct dentry           *debug_dir;
        struct dentry           *debug_async;
        struct dentry           *debug_periodic;
        struct dentry           *debug_registers;
#endif
};

/* convert between an hcd pointer and the corresponding ahcd_hcd */
static inline struct admhcd *hcd_to_admhcd(struct usb_hcd *hcd)
{
        return (struct admhcd *)(hcd->hcd_priv);
}
static inline struct usb_hcd *admhcd_to_hcd(const struct admhcd *ahcd)
{
        return container_of((void *)ahcd, struct usb_hcd, hcd_priv);
}

/*-------------------------------------------------------------------------*/

#ifndef DEBUG
#define STUB_DEBUG_FILES
#endif  /* DEBUG */

#ifdef DEBUG
#       define admhc_dbg(ahcd, fmt, args...) \
                printk(KERN_DEBUG "adm5120-hcd: " fmt, ## args)
#else
#       define admhc_dbg(ahcd, fmt, args...) do { } while (0)
#endif

#define admhc_err(ahcd, fmt, args...) \
        printk(KERN_ERR "adm5120-hcd: " fmt, ## args)
#define admhc_info(ahcd, fmt, args...) \
        printk(KERN_INFO "adm5120-hcd: " fmt, ## args)
#define admhc_warn(ahcd, fmt, args...) \
        printk(KERN_WARNING "adm5120-hcd: " fmt, ## args)

#ifdef ADMHC_VERBOSE_DEBUG
#       define admhc_vdbg admhc_dbg
#else
#       define admhc_vdbg(ahcd, fmt, args...) do { } while (0)
#endif

/*-------------------------------------------------------------------------*/

/*
 * While most USB host controllers implement their registers and
 * in-memory communication descriptors in little-endian format,
 * a minority (notably the IBM STB04XXX and the Motorola MPC5200
 * processors) implement them in big endian format.
 *
 * In addition some more exotic implementations like the Toshiba
 * Spider (aka SCC) cell southbridge are "mixed" endian, that is,
 * they have a different endianness for registers vs. in-memory
 * descriptors.
 *
 * This attempts to support either format at compile time without a
 * runtime penalty, or both formats with the additional overhead
 * of checking a flag bit.
 *
 * That leads to some tricky Kconfig rules howevber. There are
 * different defaults based on some arch/ppc platforms, though
 * the basic rules are:
 *
 * Controller type              Kconfig options needed
 * ---------------              ----------------------
 * little endian                CONFIG_USB_ADMHC_LITTLE_ENDIAN
 *
 * fully big endian             CONFIG_USB_ADMHC_BIG_ENDIAN_DESC _and_
 *                              CONFIG_USB_ADMHC_BIG_ENDIAN_MMIO
 *
 * mixed endian                 CONFIG_USB_ADMHC_LITTLE_ENDIAN _and_
 *                              CONFIG_USB_OHCI_BIG_ENDIAN_{MMIO,DESC}
 *
 * (If you have a mixed endian controller, you -must- also define
 * CONFIG_USB_ADMHC_LITTLE_ENDIAN or things will not work when building
 * both your mixed endian and a fully big endian controller support in
 * the same kernel image).
 */

#ifdef CONFIG_USB_ADMHC_BIG_ENDIAN_DESC
#ifdef CONFIG_USB_ADMHC_LITTLE_ENDIAN
#define big_endian_desc(ahcd)   (ahcd->flags & OHCI_QUIRK_BE_DESC)
#else
#define big_endian_desc(ahcd)   1               /* only big endian */
#endif
#else
#define big_endian_desc(ahcd)   0                /* only little endian */
#endif

#ifdef CONFIG_USB_ADMHC_BIG_ENDIAN_MMIO
#ifdef CONFIG_USB_ADMHC_LITTLE_ENDIAN
#define big_endian_mmio(ahcd)   (ahcd->flags & OHCI_QUIRK_BE_MMIO)
#else
#define big_endian_mmio(ahcd)   1               /* only big endian */
#endif
#else
#define big_endian_mmio(ahcd)   0                /* only little endian */
#endif

/*
 * Big-endian read/write functions are arch-specific.
 * Other arches can be added if/when they're needed.
 *
 */
static inline unsigned int admhc_readl(const struct admhcd *ahcd,
        __hc32 __iomem *regs)
{
#ifdef CONFIG_USB_ADMHC_BIG_ENDIAN_MMIO
        return big_endian_mmio(ahcd) ?
                readl_be(regs) :
                readl(regs);
#else
        return readl(regs);
#endif
}

static inline void admhc_writel(const struct admhcd *ahcd,
        const unsigned int val, __hc32 __iomem *regs)
{
#ifdef CONFIG_USB_ADMHC_BIG_ENDIAN_MMIO
        big_endian_mmio(ahcd) ?
                writel_be(val, regs) :
                writel(val, regs);
#else
                writel(val, regs);
#endif
}

static inline void admhc_writel_flush(const struct admhcd *ahcd)
{
#if 0
        /* TODO: remove? */
        (void) admhc_readl(ahcd, &ahcd->regs->gencontrol);
#endif
}


/*-------------------------------------------------------------------------*/

/* cpu to ahcd */
static inline __hc16 cpu_to_hc16(const struct admhcd *ahcd, const u16 x)
{
        return big_endian_desc(ahcd) ?
                (__force __hc16)cpu_to_be16(x) :
                (__force __hc16)cpu_to_le16(x);
}

static inline __hc16 cpu_to_hc16p(const struct admhcd *ahcd, const u16 *x)
{
        return big_endian_desc(ahcd) ?
                cpu_to_be16p(x) :
                cpu_to_le16p(x);
}

static inline __hc32 cpu_to_hc32(const struct admhcd *ahcd, const u32 x)
{
        return big_endian_desc(ahcd) ?
                (__force __hc32)cpu_to_be32(x) :
                (__force __hc32)cpu_to_le32(x);
}

static inline __hc32 cpu_to_hc32p(const struct admhcd *ahcd, const u32 *x)
{
        return big_endian_desc(ahcd) ?
                cpu_to_be32p(x) :
                cpu_to_le32p(x);
}

/* ahcd to cpu */
static inline u16 hc16_to_cpu(const struct admhcd *ahcd, const __hc16 x)
{
        return big_endian_desc(ahcd) ?
                be16_to_cpu((__force __be16)x) :
                le16_to_cpu((__force __le16)x);
}

static inline u16 hc16_to_cpup(const struct admhcd *ahcd, const __hc16 *x)
{
        return big_endian_desc(ahcd) ?
                be16_to_cpup((__force __be16 *)x) :
                le16_to_cpup((__force __le16 *)x);
}

static inline u32 hc32_to_cpu(const struct admhcd *ahcd, const __hc32 x)
{
        return big_endian_desc(ahcd) ?
                be32_to_cpu((__force __be32)x) :
                le32_to_cpu((__force __le32)x);
}

static inline u32 hc32_to_cpup(const struct admhcd *ahcd, const __hc32 *x)
{
        return big_endian_desc(ahcd) ?
                be32_to_cpup((__force __be32 *)x) :
                le32_to_cpup((__force __le32 *)x);
}

/*-------------------------------------------------------------------------*/

static inline u16 admhc_frame_no(const struct admhcd *ahcd)
{
        u32     t;

        t = admhc_readl(ahcd, &ahcd->regs->fmnumber) & ADMHC_SFN_FN_MASK;
        return (u16)t;
}

static inline u16 admhc_frame_remain(const struct admhcd *ahcd)
{
        u32     t;

        t = admhc_readl(ahcd, &ahcd->regs->fmnumber) >> ADMHC_SFN_FR_SHIFT;
        t &= ADMHC_SFN_FR_MASK;
        return (u16)t;
}

/*-------------------------------------------------------------------------*/

static inline void admhc_disable(struct admhcd *ahcd)
{
        admhcd_to_hcd(ahcd)->state = HC_STATE_HALT;
}

#define FI              0x2edf          /* 12000 bits per frame (-1) */
#define FSLDP(fi)       (0x7fff & ((6 * ((fi) - 1200)) / 7))
#define FIT             ADMHC_SFI_FIT
#define LSTHRESH        0x628           /* lowspeed bit threshold */

static inline void periodic_reinit(struct admhcd *ahcd)
{
#if 0
        u32     fi = ahcd->fminterval & ADMHC_SFI_FI_MASK;
        u32     fit = admhc_readl(ahcd, &ahcd->regs->fminterval) & FIT;

        /* TODO: adjust FSLargestDataPacket value too? */
        admhc_writel(ahcd, (fit ^ FIT) | ahcd->fminterval,
                                        &ahcd->regs->fminterval);
#else
        u32     fit = admhc_readl(ahcd, &ahcd->regs->fminterval) & FIT;

        /* TODO: adjust FSLargestDataPacket value too? */
        admhc_writel(ahcd, (fit ^ FIT) | ahcd->fminterval,
                                        &ahcd->regs->fminterval);
#endif
}

static inline u32 admhc_read_rhdesc(struct admhcd *ahcd)
{
        return admhc_readl(ahcd, &ahcd->regs->rhdesc);
}

static inline u32 admhc_read_portstatus(struct admhcd *ahcd, int port)
{
        return admhc_readl(ahcd, &ahcd->regs->portstatus[port]);
}

static inline void admhc_write_portstatus(struct admhcd *ahcd, int port,
                u32 value)
{
        admhc_writel(ahcd, value, &ahcd->regs->portstatus[port]);
}

static inline void roothub_write_status(struct admhcd *ahcd, u32 value)
{
        /* FIXME: read-only bits must be masked out */
        admhc_writel(ahcd, value, &ahcd->regs->rhdesc);
}

static inline void admhc_intr_disable(struct admhcd *ahcd, u32 ints)
{
        u32     t;

        t = admhc_readl(ahcd, &ahcd->regs->int_enable);
        t &= ~(ints);
        admhc_writel(ahcd, t, &ahcd->regs->int_enable);
        /* TODO: flush writes ?*/
}

static inline void admhc_intr_enable(struct admhcd *ahcd, u32 ints)
{
        u32     t;

        t = admhc_readl(ahcd, &ahcd->regs->int_enable);
        t |= ints;
        admhc_writel(ahcd, t, &ahcd->regs->int_enable);
        /* TODO: flush writes ?*/
}

static inline void admhc_intr_ack(struct admhcd *ahcd, u32 ints)
{
        admhc_writel(ahcd, ints, &ahcd->regs->int_status);
}

static inline void admhc_dma_enable(struct admhcd *ahcd)
{
        u32 t;

        t = admhc_readl(ahcd, &ahcd->regs->host_control);
        if (t & ADMHC_HC_DMAE)
                return;

        t |= ADMHC_HC_DMAE;
        admhc_writel(ahcd, t, &ahcd->regs->host_control);
        admhc_vdbg(ahcd, "DMA enabled\n");
}

static inline void admhc_dma_disable(struct admhcd *ahcd)
{
        u32 t;

        t = admhc_readl(ahcd, &ahcd->regs->host_control);
        if (!(t & ADMHC_HC_DMAE))
                return;

        t &= ~ADMHC_HC_DMAE;
        admhc_writel(ahcd, t, &ahcd->regs->host_control);
        admhc_vdbg(ahcd, "DMA disabled\n");
}