diff options
author | Florian Fainelli <florian@openwrt.org> | 2007-03-19 17:34:37 +0000 |
---|---|---|
committer | Florian Fainelli <florian@openwrt.org> | 2007-03-19 17:34:37 +0000 |
commit | d9dfd7341b4e1febcf37c7a973c1f2f13b60f54d (patch) | |
tree | 4a250ad2718a62180df562861b83777f2c4c5a85 /target/linux/adm5120-2.6/files/drivers | |
parent | f04dd67969a38e54002c20828acf93090c4d10a4 (diff) | |
download | upstream-d9dfd7341b4e1febcf37c7a973c1f2f13b60f54d.tar.gz upstream-d9dfd7341b4e1febcf37c7a973c1f2f13b60f54d.tar.bz2 upstream-d9dfd7341b4e1febcf37c7a973c1f2f13b60f54d.zip |
Preliminary ADM5120 support, marked as broken
SVN-Revision: 6614
Diffstat (limited to 'target/linux/adm5120-2.6/files/drivers')
6 files changed, 2035 insertions, 0 deletions
diff --git a/target/linux/adm5120-2.6/files/drivers/char/adm5120_gpio.c b/target/linux/adm5120-2.6/files/drivers/char/adm5120_gpio.c new file mode 100644 index 0000000000..6fccfb029e --- /dev/null +++ b/target/linux/adm5120-2.6/files/drivers/char/adm5120_gpio.c @@ -0,0 +1,58 @@ +/* + * ADM5120 LED (GPIO) driver + * + * Copyright (C) Jeroen Vreeken (pe1rxq@amsat.org), 2005 + */ + +#include <linux/autoconf.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> + +#define LED_MINOR 151 +#define GPIO_IO ((unsigned long *)0xb20000b8) + +static ssize_t adm5120_led_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + unsigned char val; + + if (!len || get_user(val, data)) + return -EFAULT; + *GPIO_IO=(*GPIO_IO & 0x00ffffff) | (val<<24); + return 1; +} + +static struct file_operations adm5120_led_fops = { + .owner = THIS_MODULE, + .write = adm5120_led_write, +}; + +static struct miscdevice adm5120_led_device = { + LED_MINOR, + "led", + &adm5120_led_fops, +}; + +static int __init adm5120_led_init(void) +{ + printk(KERN_INFO "ADM5120 LED & GPIO driver\n"); + if (misc_register(&adm5120_led_device)) { + printk(KERN_WARNING "Couldn't register device %d\n", LED_MINOR); + return -EBUSY; + } + return 0; +} + +static void __exit adm5120_led_exit(void) +{ + misc_deregister(&adm5120_led_device); +} + +module_init(adm5120_led_init); +module_exit(adm5120_led_exit); + +MODULE_DESCRIPTION("ADM5120 LED and GPIO driver"); +MODULE_AUTHOR("Jeroen Vreeken"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/adm5120-2.6/files/drivers/mtd/maps/adm5120_mtd.c b/target/linux/adm5120-2.6/files/drivers/mtd/maps/adm5120_mtd.c new file mode 100644 index 0000000000..a9e97cb318 --- /dev/null +++ b/target/linux/adm5120-2.6/files/drivers/mtd/maps/adm5120_mtd.c @@ -0,0 +1,55 @@ +/* + * Flash device on the ADM5120 board + * + * Copyright Jeroen Vreeken (pe1rxq@amsat.org), 2005 + * + * 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. + */ + +#include <linux/autoconf.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> + +#define FLASH_PHYS_ADDR 0x1FC00000 +#define FLASH_SIZE 0x200000 + +static struct mtd_info *adm5120_mtd; + +static struct map_info adm5120_mtd_map = { + .name = "ADM5120", + .size = FLASH_SIZE, + .bankwidth = 2, + .phys = FLASH_PHYS_ADDR, +}; + +static int __init adm5120_mtd_init(void) +{ + printk(KERN_INFO "ADM5120 board flash (0x%x at 0x%x)\n", FLASH_SIZE, + FLASH_PHYS_ADDR); + adm5120_mtd_map.virt = ioremap_nocache(FLASH_PHYS_ADDR, FLASH_SIZE); + simple_map_init(&adm5120_mtd_map); + adm5120_mtd = do_map_probe("cfi_probe", &adm5120_mtd_map); + if (adm5120_mtd) { + adm5120_mtd->owner = THIS_MODULE; + add_mtd_device(adm5120_mtd); + return 0; + } + return -ENXIO; +} + +static void __exit adm5120_mtd_exit(void) +{ + del_mtd_device(adm5120_mtd); + map_destroy(adm5120_mtd); +} + +module_init(adm5120_mtd_init); +module_exit(adm5120_mtd_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jeroen Vreeken (pe1rxq@amsat.org)"); +MODULE_DESCRIPTION("MTD map driver for ADM5120 boards"); diff --git a/target/linux/adm5120-2.6/files/drivers/net/adm5120sw.c b/target/linux/adm5120-2.6/files/drivers/net/adm5120sw.c new file mode 100644 index 0000000000..935016c3a1 --- /dev/null +++ b/target/linux/adm5120-2.6/files/drivers/net/adm5120sw.c @@ -0,0 +1,502 @@ +/* + * ADM5120 built in ethernet switch driver + * + * Copyright Jeroen Vreeken (pe1rxq@amsat.org), 2005 + * + * Inspiration for this driver came from the original ADMtek 2.4 + * driver, Copyright ADMtek Inc. + */ +#include <linux/autoconf.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <asm/mipsregs.h> +#include <asm/irq.h> +#include <asm/io.h> +#include "adm5120sw.h" + +MODULE_AUTHOR("Jeroen Vreeken (pe1rxq@amsat.org)"); +MODULE_DESCRIPTION("ADM5120 ethernet switch driver"); +MODULE_LICENSE("GPL"); + +/* + * The ADM5120 uses an internal matrix to determine which ports + * belong to which VLAN. + * The default generates a VLAN (and device) for each port + * (including MII port) and the CPU port is part of all of them. + * + * Another example, one big switch and everything mapped to eth0: + * 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00 + */ +static unsigned char vlan_matrix[SW_DEVS] = { + 0x41, 0x42, 0x44, 0x48, 0x50, 0x60 +}; + +static int adm5120_nrdevs; + +static struct net_device *adm5120_devs[SW_DEVS]; +static struct adm5120_dma + adm5120_dma_txh_v[ADM5120_DMA_TXH] __attribute__((aligned(16))), + adm5120_dma_txl_v[ADM5120_DMA_TXL] __attribute__((aligned(16))), + adm5120_dma_rxh_v[ADM5120_DMA_RXH] __attribute__((aligned(16))), + adm5120_dma_rxl_v[ADM5120_DMA_RXL] __attribute__((aligned(16))), + *adm5120_dma_txh, + *adm5120_dma_txl, + *adm5120_dma_rxh, + *adm5120_dma_rxl; +static struct sk_buff + *adm5120_skb_rxh[ADM5120_DMA_RXH], + *adm5120_skb_rxl[ADM5120_DMA_RXL], + *adm5120_skb_txh[ADM5120_DMA_TXH], + *adm5120_skb_txl[ADM5120_DMA_TXL]; +static int adm5120_rxhi = 0; +static int adm5120_rxli = 0; +/* We don't use high priority tx for now */ +/*static int adm5120_txhi = 0;*/ +static int adm5120_txli = 0; +static int adm5120_txhit = 0; +static int adm5120_txlit = 0; +static int adm5120_if_open = 0; + +static inline void adm5120_set_reg(unsigned int reg, unsigned long val) +{ + *(volatile unsigned long*)(SW_BASE+reg) = val; +} + +static inline unsigned long adm5120_get_reg(unsigned int reg) +{ + return *(volatile unsigned long*)(SW_BASE+reg); +} + +static inline void adm5120_rxfixup(struct adm5120_dma *dma, + struct sk_buff **skbl, int num) +{ + int i; + + /* Resubmit the entire ring */ + for (i=0; i<num; i++) { + dma[i].status = 0; + dma[i].cntl = 0; + dma[i].len = ADM5120_DMA_RXSIZE; + dma[i].data = ADM5120_DMA_ADDR(skbl[i]->data) | + ADM5120_DMA_OWN | (i==num-1 ? ADM5120_DMA_RINGEND : 0); + } +} + +static inline void adm5120_rx(struct adm5120_dma *dma, struct sk_buff **skbl, + int *index, int num) +{ + struct sk_buff *skb, *skbn; + struct adm5120_sw *priv; + struct net_device *dev; + int port, vlan, len; + + while (!(dma[*index].data & ADM5120_DMA_OWN)) { + port = (dma[*index].status & ADM5120_DMA_PORTID); + port >>= ADM5120_DMA_PORTSHIFT; + for (vlan = 0; vlan < adm5120_nrdevs; vlan++) { + if ((1<<port) & vlan_matrix[vlan]) + break; + } + if (vlan == adm5120_nrdevs) + vlan = 0; + dev = adm5120_devs[vlan]; + skb = skbl[*index]; + len = (dma[*index].status & ADM5120_DMA_LEN); + len >>= ADM5120_DMA_LENSHIFT; + len -= ETH_FCS; + + priv = netdev_priv(dev); + if (len <= 0 || len > ADM5120_DMA_RXSIZE || + dma[*index].status & ADM5120_DMA_FCSERR) { + priv->stats.rx_errors++; + skbn = NULL; + } else { + skbn = dev_alloc_skb(ADM5120_DMA_RXSIZE+16); + if (skbn) { + skb_put(skb, len); + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_UNNECESSARY; + dev->last_rx = jiffies; + priv->stats.rx_packets++; + priv->stats.rx_bytes+=len; + skb_reserve(skbn, 2); + skbl[*index] = skbn; + } else { + printk(KERN_INFO "%s recycling!\n", dev->name); + } + } + + dma[*index].status = 0; + dma[*index].cntl = 0; + dma[*index].len = ADM5120_DMA_RXSIZE; + dma[*index].data = ADM5120_DMA_ADDR(skbl[*index]->data) | + ADM5120_DMA_OWN | + (num-1==*index ? ADM5120_DMA_RINGEND : 0); + if (num == ++*index) + *index = 0; + if (skbn) + netif_rx(skb); + } +} + +static inline void adm5120_tx(struct adm5120_dma *dma, struct sk_buff **skbl, + int *index, int num) +{ + while((dma[*index].data & ADM5120_DMA_OWN) == 0 && skbl[*index]) { + dev_kfree_skb_irq(skbl[*index]); + skbl[*index] = NULL; + if (++*index == num) + *index = 0; + } +} + +irqreturn_t adm5120_sw_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned long intreg; + + adm5120_set_reg(ADM5120_INT_MASK, + adm5120_get_reg(ADM5120_INT_MASK) | ADM5120_INTHANDLE); + + intreg = adm5120_get_reg(ADM5120_INT_ST); + adm5120_set_reg(ADM5120_INT_ST, intreg); + + if (intreg & ADM5120_INT_RXH) + adm5120_rx(adm5120_dma_rxh, adm5120_skb_rxh, &adm5120_rxhi, + ADM5120_DMA_RXH); + if (intreg & ADM5120_INT_HFULL) + adm5120_rxfixup(adm5120_dma_rxh, adm5120_skb_rxh, + ADM5120_DMA_RXH); + if (intreg & ADM5120_INT_RXL) + adm5120_rx(adm5120_dma_rxl, adm5120_skb_rxl, &adm5120_rxli, + ADM5120_DMA_RXL); + if (intreg & ADM5120_INT_LFULL) + adm5120_rxfixup(adm5120_dma_rxl, adm5120_skb_rxl, + ADM5120_DMA_RXL); + if (intreg & ADM5120_INT_TXH) + adm5120_tx(adm5120_dma_txh, adm5120_skb_txh, &adm5120_txhit, + ADM5120_DMA_TXH); + if (intreg & ADM5120_INT_TXL) + adm5120_tx(adm5120_dma_txl, adm5120_skb_txl, &adm5120_txlit, + ADM5120_DMA_TXL); + + adm5120_set_reg(ADM5120_INT_MASK, + adm5120_get_reg(ADM5120_INT_MASK) & ~ADM5120_INTHANDLE); + + return IRQ_HANDLED; +} + +static void adm5120_set_vlan(char *matrix) +{ + unsigned long val; + + val = matrix[0] + (matrix[1]<<8) + (matrix[2]<<16) + (matrix[3]<<24); + adm5120_set_reg(ADM5120_VLAN_GI, val); + val = matrix[4] + (matrix[5]<<8); + adm5120_set_reg(ADM5120_VLAN_GII, val); +} + +static int adm5120_sw_open(struct net_device *dev) +{ + if (!adm5120_if_open++) + adm5120_set_reg(ADM5120_INT_MASK, + adm5120_get_reg(ADM5120_INT_MASK) & ~ADM5120_INTHANDLE); + netif_start_queue(dev); + return 0; +} + +static int adm5120_sw_stop(struct net_device *dev) +{ + netif_stop_queue(dev); + if (!--adm5120_if_open) + adm5120_set_reg(ADM5120_INT_MASK, + adm5120_get_reg(ADM5120_INT_MASK) | ADM5120_INTMASKALL); + return 0; +} + +static int adm5120_sw_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct adm5120_dma *dma = adm5120_dma_txl; + struct sk_buff **skbl = adm5120_skb_txl; + struct adm5120_sw *priv = netdev_priv(dev); + int *index = &adm5120_txli; + int num = ADM5120_DMA_TXL; + int trigger = ADM5120_SEND_TRIG_L; + + dev->trans_start = jiffies; + if (dma[*index].data & ADM5120_DMA_OWN) { + dev_kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + + dma[*index].data = ADM5120_DMA_ADDR(skb->data) | ADM5120_DMA_OWN; + if (*index == num-1) + dma[*index].data |= ADM5120_DMA_RINGEND; + dma[*index].status = + ((skb->len<ETH_ZLEN?ETH_ZLEN:skb->len) << ADM5120_DMA_LENSHIFT) | + (0x1 << priv->port); + dma[*index].len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + skbl[*index]=skb; + + if (++*index == num) + *index = 0; + adm5120_set_reg(ADM5120_SEND_TRIG, trigger); + + return 0; +} + +static void adm5120_tx_timeout(struct net_device *dev) +{ + netif_wake_queue(dev); +} + +static struct net_device_stats *adm5120_sw_stats(struct net_device *dev) +{ + return &((struct adm5120_sw *)netdev_priv(dev))->stats; +} + +static void adm5120_set_multicast_list(struct net_device *dev) +{ + struct adm5120_sw *priv = netdev_priv(dev); + int portmask; + + portmask = vlan_matrix[priv->port] & 0x3f; + + if (dev->flags & IFF_PROMISC) + adm5120_set_reg(ADM5120_CPUP_CONF, + adm5120_get_reg(ADM5120_CPUP_CONF) & + ~((portmask << ADM5120_DISUNSHIFT) & ADM5120_DISUNALL)); + else + adm5120_set_reg(ADM5120_CPUP_CONF, + adm5120_get_reg(ADM5120_CPUP_CONF) | + (portmask << ADM5120_DISUNSHIFT)); + + if (dev->flags & IFF_PROMISC || dev->flags & IFF_ALLMULTI || + dev->mc_count) + adm5120_set_reg(ADM5120_CPUP_CONF, + adm5120_get_reg(ADM5120_CPUP_CONF) & + ~((portmask << ADM5120_DISMCSHIFT) & ADM5120_DISMCALL)); + else + adm5120_set_reg(ADM5120_CPUP_CONF, + adm5120_get_reg(ADM5120_CPUP_CONF) | + (portmask << ADM5120_DISMCSHIFT)); +} + +static void adm5120_write_mac(struct net_device *dev) +{ + struct adm5120_sw *priv = netdev_priv(dev); + unsigned char *mac = dev->dev_addr; + + adm5120_set_reg(ADM5120_MAC_WT1, + mac[2] | (mac[3]<<8) | (mac[4]<<16) | (mac[5]<<24)); + adm5120_set_reg(ADM5120_MAC_WT0, (priv->port<<3) | + (mac[0]<<16) | (mac[1]<<24) | ADM5120_MAC_WRITE | ADM5120_VLAN_EN); + + while (!(adm5120_get_reg(ADM5120_MAC_WT0) & ADM5120_MAC_WRITE_DONE)); +} + +static int adm5120_sw_set_mac_address(struct net_device *dev, void *p) +{ + struct sockaddr *addr = p; + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + adm5120_write_mac(dev); + return 0; +} + +static int adm5120_do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + int err; + struct adm5120_info info; + struct adm5120_sw *priv = netdev_priv(dev); + + switch(cmd) { + case SIOCGADMINFO: + info.magic = 0x5120; + info.ports = adm5120_nrdevs; + info.vlan = priv->port; + err = copy_to_user(rq->ifr_data, &info, sizeof(info)); + if (err) + return -EFAULT; + break; + case SIOCSMATRIX: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + err = copy_from_user(vlan_matrix, rq->ifr_data, + sizeof(vlan_matrix)); + if (err) + return -EFAULT; + adm5120_set_vlan(vlan_matrix); + break; + case SIOCGMATRIX: + err = copy_to_user(rq->ifr_data, vlan_matrix, + sizeof(vlan_matrix)); + if (err) + return -EFAULT; + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static void adm5120_dma_tx_init(struct adm5120_dma *dma, struct sk_buff **skb, + int num) +{ + memset(dma, 0, sizeof(struct adm5120_dma)*num); + dma[num-1].data |= ADM5120_DMA_RINGEND; + memset(skb, 0, sizeof(struct skb*)*num); +} + +static void adm5120_dma_rx_init(struct adm5120_dma *dma, struct sk_buff **skb, + int num) +{ + int i; + + memset(dma, 0, sizeof(struct adm5120_dma)*num); + for (i=0; i<num; i++) { + skb[i] = dev_alloc_skb(ADM5120_DMA_RXSIZE+16); + if (!skb[i]) { + i=num; + break; + } + skb_reserve(skb[i], 2); + dma[i].data = ADM5120_DMA_ADDR(skb[i]->data) | ADM5120_DMA_OWN; + dma[i].cntl = 0; + dma[i].len = ADM5120_DMA_RXSIZE; + dma[i].status = 0; + } + dma[i-1].data |= ADM5120_DMA_RINGEND; +} + +static int __init adm5120_sw_init(void) +{ + int i, err; + struct net_device *dev; + + err = request_irq(SW_IRQ, adm5120_sw_irq, 0, "ethernet switch", NULL); + if (err) + goto out; + + /* MII port? */ + if (adm5120_get_reg(ADM5120_CODE) & ADM5120_CODE_PQFP) + adm5120_nrdevs = 5; + else + adm5120_nrdevs = 6; + + adm5120_set_reg(ADM5120_CPUP_CONF, + ADM5120_DISCCPUPORT | ADM5120_CRC_PADDING | + ADM5120_DISUNALL | ADM5120_DISMCALL); + adm5120_set_reg(ADM5120_PORT_CONF0, ADM5120_ENMC | ADM5120_ENBP); + + adm5120_set_reg(ADM5120_PHY_CNTL2, adm5120_get_reg(ADM5120_PHY_CNTL2) | + ADM5120_AUTONEG | ADM5120_NORMAL | ADM5120_AUTOMDIX); + adm5120_set_reg(ADM5120_PHY_CNTL3, adm5120_get_reg(ADM5120_PHY_CNTL3) | + ADM5120_PHY_NTH); + + adm5120_set_reg(ADM5120_INT_MASK, ADM5120_INTMASKALL); + adm5120_set_reg(ADM5120_INT_ST, ADM5120_INTMASKALL); + + adm5120_dma_txh = (void *)KSEG1ADDR((u32)adm5120_dma_txh_v); + adm5120_dma_txl = (void *)KSEG1ADDR((u32)adm5120_dma_txl_v); + adm5120_dma_rxh = (void *)KSEG1ADDR((u32)adm5120_dma_rxh_v); + adm5120_dma_rxl = (void *)KSEG1ADDR((u32)adm5120_dma_rxl_v); + + adm5120_dma_tx_init(adm5120_dma_txh, adm5120_skb_txh, ADM5120_DMA_TXH); + adm5120_dma_tx_init(adm5120_dma_txl, adm5120_skb_txl, ADM5120_DMA_TXL); + adm5120_dma_rx_init(adm5120_dma_rxh, adm5120_skb_rxh, ADM5120_DMA_RXH); + adm5120_dma_rx_init(adm5120_dma_rxl, adm5120_skb_rxl, ADM5120_DMA_RXL); + adm5120_set_reg(ADM5120_SEND_HBADDR, KSEG1ADDR(adm5120_dma_txh)); + adm5120_set_reg(ADM5120_SEND_LBADDR, KSEG1ADDR(adm5120_dma_txl)); + adm5120_set_reg(ADM5120_RECEIVE_HBADDR, KSEG1ADDR(adm5120_dma_rxh)); + adm5120_set_reg(ADM5120_RECEIVE_LBADDR, KSEG1ADDR(adm5120_dma_rxl)); + + adm5120_set_vlan(vlan_matrix); + + for (i=0; i<adm5120_nrdevs; i++) { + adm5120_devs[i] = alloc_etherdev(sizeof(struct adm5120_sw)); + if (!adm5120_devs[i]) { + err = -ENOMEM; + goto out_int; + } + + dev = adm5120_devs[i]; + SET_MODULE_OWNER(dev); + memset(netdev_priv(dev), 0, sizeof(struct adm5120_sw)); + ((struct adm5120_sw*)netdev_priv(dev))->port = i; + dev->base_addr = SW_BASE; + dev->irq = SW_IRQ; + dev->open = adm5120_sw_open; + dev->hard_start_xmit = adm5120_sw_tx; + dev->stop = adm5120_sw_stop; + dev->get_stats = adm5120_sw_stats; + dev->set_multicast_list = adm5120_set_multicast_list; + dev->do_ioctl = adm5120_do_ioctl; + dev->tx_timeout = adm5120_tx_timeout; + dev->watchdog_timeo = ETH_TX_TIMEOUT; + dev->set_mac_address = adm5120_sw_set_mac_address; + /* HACK alert!!! In the original admtek driver it is asumed + that you can read the MAC addressess from flash, but edimax + decided to leave that space intentionally blank... + */ + memcpy(dev->dev_addr, "\x00\x50\xfc\x11\x22\x01", 6); + dev->dev_addr[5] += i; + adm5120_write_mac(dev); + + if ((err = register_netdev(dev))) { + free_netdev(dev); + goto out_int; + } + printk(KERN_INFO "%s: ADM5120 switch port%d\n", dev->name, i); + } + adm5120_set_reg(ADM5120_CPUP_CONF, + ADM5120_CRC_PADDING | ADM5120_DISUNALL | ADM5120_DISMCALL); + + return 0; + +out_int: + /* Undo everything that did succeed */ + for (; i; i--) { + unregister_netdev(adm5120_devs[i-1]); + free_netdev(adm5120_devs[i-1]); + } + free_irq(SW_IRQ, NULL); +out: + printk(KERN_ERR "ADM5120 Ethernet switch init failed\n"); + return err; +} + +static void __exit adm5120_sw_exit(void) +{ + int i; + + for (i = 0; i < adm5120_nrdevs; i++) { + unregister_netdev(adm5120_devs[i]); + free_netdev(adm5120_devs[i-1]); + } + + free_irq(SW_IRQ, NULL); + + for (i = 0; i < ADM5120_DMA_RXH; i++) { + if (!adm5120_skb_rxh[i]) + break; + kfree_skb(adm5120_skb_rxh[i]); + } + for (i = 0; i < ADM5120_DMA_RXL; i++) { + if (!adm5120_skb_rxl[i]) + break; + kfree_skb(adm5120_skb_rxl[i]); + } +} + +module_init(adm5120_sw_init); +module_exit(adm5120_sw_exit); diff --git a/target/linux/adm5120-2.6/files/drivers/net/adm5120sw.h b/target/linux/adm5120-2.6/files/drivers/net/adm5120sw.h new file mode 100644 index 0000000000..4ea1e6257c --- /dev/null +++ b/target/linux/adm5120-2.6/files/drivers/net/adm5120sw.h @@ -0,0 +1,106 @@ +/* + * Defines for ADM5120 built in ethernet switch driver + * + * Copyright Jeroen Vreeken (pe1rxq@amsat.org), 2005 + * + * Values come from ADM5120 datasheet and original ADMtek 2.4 driver, + * Copyright ADMtek Inc. + */ + +#ifndef _INCLUDE_ADM5120SW_H_ +#define _INCLUDE_ADM5120SW_H_ + +#define SW_BASE KSEG1ADDR(0x12000000) +#define SW_DEVS 6 +#define SW_IRQ 9 + +#define ETH_TX_TIMEOUT HZ/4 +#define ETH_FCS 4; + +#define ADM5120_CODE 0x00 /* CPU description */ +#define ADM5120_CODE_PQFP 0x20000000 /* package type */ +#define ADM5120_CPUP_CONF 0x24 /* CPU port config */ +#define ADM5120_DISCCPUPORT 0x00000001 /* disable cpu port */ +#define ADM5120_CRC_PADDING 0x00000002 /* software crc */ +#define ADM5120_DISUNSHIFT 9 +#define ADM5120_DISUNALL 0x00007e00 /* disable unknown from all */ +#define ADM5120_DISMCSHIFT 16 +#define ADM5120_DISMCALL 0x003f0000 /* disable multicast from all */ +#define ADM5120_PORT_CONF0 0x28 +#define ADM5120_ENMC 0x00003f00 /* Enable MC routing (ex cpu) */ +#define ADM5120_ENBP 0x003f0000 /* Enable Back Pressure */ +#define ADM5120_VLAN_GI 0x40 /* VLAN settings */ +#define ADM5120_VLAN_GII 0x44 +#define ADM5120_SEND_TRIG 0x48 +#define ADM5120_SEND_TRIG_L 0x00000001 +#define ADM5120_SEND_TRIG_H 0x00000002 +#define ADM5120_MAC_WT0 0x58 +#define ADM5120_MAC_WRITE 0x00000001 +#define ADM5120_MAC_WRITE_DONE 0x00000002 +#define ADM5120_VLAN_EN 0x00000040 +#define ADM5120_MAC_WT1 0x5c +#define ADM5120_PHY_CNTL2 0x7c +#define ADM5120_AUTONEG 0x0000001f /* Auto negotiate */ +#define ADM5120_NORMAL 0x01f00000 /* PHY normal mode */ +#define ADM5120_AUTOMDIX 0x3e000000 /* Auto MDIX */ +#define ADM5120_PHY_CNTL3 0x80 +#define ADM5120_PHY_NTH 0x00000400 +#define ADM5120_INT_ST 0xb0 +#define ADM5120_INT_RXH 0x0000004 +#define ADM5120_INT_RXL 0x0000008 +#define ADM5120_INT_HFULL 0x0000010 +#define ADM5120_INT_LFULL 0x0000020 +#define ADM5120_INT_TXH 0x0000001 +#define ADM5120_INT_TXL 0x0000002 +#define ADM5120_INT_MASK 0xb4 +#define ADM5120_INTMASKALL 0x1FDEFFF /* All interrupts */ +#define ADM5120_INTHANDLE (ADM5120_INT_RXH | ADM5120_INT_RXL | \ + ADM5120_INT_HFULL | ADM5120_INT_LFULL | \ + ADM5120_INT_TXH | ADM5120_INT_TXL) +#define ADM5120_SEND_HBADDR 0xd0 +#define ADM5120_SEND_LBADDR 0xd4 +#define ADM5120_RECEIVE_HBADDR 0xd8 +#define ADM5120_RECEIVE_LBADDR 0xdc + +struct adm5120_dma { + u32 data; + u32 cntl; + u32 len; + u32 status; +} __attribute__ ((packed)); + +#define ADM5120_DMA_MASK 0x00ffffff +#define ADM5120_DMA_OWN 0x80000000 /* buffer owner */ +#define ADM5120_DMA_RINGEND 0x10000000 /* Last in DMA ring */ + +#define ADM5120_DMA_ADDR(ptr) ((u32)(ptr) & ADM5120_DMA_MASK) +#define ADM5120_DMA_PORTID 0x00007000 +#define ADM5120_DMA_PORTSHIFT 12 +#define ADM5120_DMA_LEN 0x07ff0000 +#define ADM5120_DMA_LENSHIFT 16 +#define ADM5120_DMA_FCSERR 0x00000008 + +#define ADM5120_DMA_TXH 16 +#define ADM5120_DMA_TXL 64 +#define ADM5120_DMA_RXH 16 +#define ADM5120_DMA_RXL 8 + +#define ADM5120_DMA_RXSIZE 1550 +#define ADM5120_DMA_EXTRA 20 + +struct adm5120_sw { + int port; + struct net_device_stats stats; +}; + +#define SIOCSMATRIX SIOCDEVPRIVATE +#define SIOCGMATRIX SIOCDEVPRIVATE+1 +#define SIOCGADMINFO SIOCDEVPRIVATE+2 + +struct adm5120_info { + u16 magic; + u16 ports; + u16 vlan; +}; + +#endif /* _INCLUDE_ADM5120SW_H_ */ diff --git a/target/linux/adm5120-2.6/files/drivers/serial/adm5120_uart.c b/target/linux/adm5120-2.6/files/drivers/serial/adm5120_uart.c new file mode 100644 index 0000000000..cbbdbfb6ab --- /dev/null +++ b/target/linux/adm5120-2.6/files/drivers/serial/adm5120_uart.c @@ -0,0 +1,524 @@ +/* + * Serial driver for ADM5120 SoC + * + * Derived from drivers/serial/uart00.c + * Copyright 2001 Altera Corporation + * + * Some pieces are derived from the ADMtek 2.4 serial driver. + * Copyright (C) ADMtek Incorporated, 2003 + * daniell@admtek.com.tw + * Which again was derived from drivers/char/serial.c + * Copyright (C) Linus Torvalds et al. + * + * Copyright Jeroen Vreeken (pe1rxq@amsat.org), 2005 + */ + +#include <linux/autoconf.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/console.h> + +#define ADM5120_UART_BASE0 0x12600000 +#define ADM5120_UART_BASE1 0x12800000 +#define ADM5120_UART_SIZE 0x20 + +#define ADM5120_UART_IRQ0 1 +#define ADM5120_UART_IRQ1 2 + +#define ADM5120_UART_REG(base, reg) \ + (*(volatile u32 *)KSEG1ADDR((base)+(reg))) + +#define ADM5120_UARTCLK_FREQ 62500000 +#define ADM5120_UART_BAUDDIV(rate) ((unsigned long)(ADM5120_UARTCLK_FREQ/(16*(_rate)) - 1)) + +#define ADM5120_UART_BAUD115200 ADM5120_UART_BAUDDIV(115200) + +#define ADM5120_UART_DATA 0x00 +#define ADM5120_UART_RS 0x04 +#define ADM5120_UART_LCR_H 0x08 +#define ADM5120_UART_LCR_M 0x0c +#define ADM5120_UART_LCR_L 0x10 +#define ADM5120_UART_CR 0x14 +#define ADM5120_UART_FR 0x18 +#define ADM5120_UART_IR 0x1c + +#define ADM5120_UART_FE 0x01 +#define ADM5120_UART_PE 0x02 +#define ADM5120_UART_BE 0x04 +#define ADM5120_UART_OE 0x08 +#define ADM5120_UART_ERR 0x0f +#define ADM5120_UART_FIFO_EN 0x10 +#define ADM5120_UART_EN 0x01 +#define ADM5120_UART_TIE 0x20 +#define ADM5120_UART_RIE 0x50 +#define ADM5120_UART_IE 0x78 +#define ADM5120_UART_CTS 0x01 +#define ADM5120_UART_DSR 0x02 +#define ADM5120_UART_DCD 0x04 +#define ADM5120_UART_TXFF 0x20 +#define ADM5120_UART_TXFE 0x80 +#define ADM5120_UART_RXFE 0x10 +#define ADM5120_UART_BRK 0x01 +#define ADM5120_UART_PEN 0x02 +#define ADM5120_UART_EPS 0x04 +#define ADM5120_UART_STP2 0x08 +#define ADM5120_UART_W5 0x00 +#define ADM5120_UART_W6 0x20 +#define ADM5120_UART_W7 0x40 +#define ADM5120_UART_W8 0x60 +#define ADM5120_UART_MIS 0x01 +#define ADM5120_UART_RIS 0x02 +#define ADM5120_UART_TIS 0x04 +#define ADM5120_UART_RTIS 0x08 + +static void adm5120ser_stop_tx(struct uart_port *port) +{ + ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) &= ~ADM5120_UART_TIE; +} + +static void adm5120ser_irq_rx(struct uart_port *port) +{ + struct tty_struct *tty = port->info->tty; + unsigned int status, ch, rds, flg, ignored = 0; + + status = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR); + while (!(status & ADM5120_UART_RXFE)) { + /* + * We need to read rds before reading the + * character from the fifo + */ + rds = ADM5120_UART_REG(port->iobase, ADM5120_UART_RS); + ch = ADM5120_UART_REG(port->iobase, ADM5120_UART_DATA); + port->icount.rx++; + + if (tty->low_latency) + tty_flip_buffer_push(tty); + + flg = TTY_NORMAL; + + /* + * Note that the error handling code is + * out of the main execution path + */ + if (rds & ADM5120_UART_ERR) + goto handle_error; + if (uart_handle_sysrq_char(port, ch)) + goto ignore_char; + + error_return: + tty_insert_flip_char(tty, ch, flg); + + ignore_char: + status = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR); + } + out: + tty_flip_buffer_push(tty); + return; + + handle_error: + ADM5120_UART_REG(port->iobase, ADM5120_UART_RS) = 0xff; + if (rds & ADM5120_UART_BE) { + port->icount.brk++; + if (uart_handle_break(port)) + goto ignore_char; + } else if (rds & ADM5120_UART_PE) + port->icount.parity++; + else if (rds & ADM5120_UART_FE) + port->icount.frame++; + if (rds & ADM5120_UART_OE) + port->icount.overrun++; + + if (rds & port->ignore_status_mask) { + if (++ignored > 100) + goto out; + goto ignore_char; + } + rds &= port->read_status_mask; + + if (rds & ADM5120_UART_BE) + flg = TTY_BREAK; + else if (rds & ADM5120_UART_PE) + flg = TTY_PARITY; + else if (rds & ADM5120_UART_FE) + flg = TTY_FRAME; + + if (rds & ADM5120_UART_OE) { + /* + * CHECK: does overrun affect the current character? + * ASSUMPTION: it does not. + */ + tty_insert_flip_char(tty, ch, flg); + ch = 0; + flg = TTY_OVERRUN; + } +#ifdef CONFIG_MAGIC_SYSRQ + port->sysrq = 0; +#endif + goto error_return; +} + +static void adm5120ser_irq_tx(struct uart_port *port) +{ + struct circ_buf *xmit = &port->info->xmit; + int count; + + if (port->x_char) { + ADM5120_UART_REG(port->iobase, ADM5120_UART_DATA) = + port->x_char; + port->icount.tx++; + port->x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + adm5120ser_stop_tx(port); + return; + } + + count = port->fifosize >> 1; + do { + ADM5120_UART_REG(port->iobase, ADM5120_UART_DATA) = + xmit->buf[xmit->tail]; + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + if (uart_circ_empty(xmit)) + break; + } while (--count > 0); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + adm5120ser_stop_tx(port); +} + +static void adm5120ser_irq_modem(struct uart_port *port) +{ + unsigned int status; + + status = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR); + + if (status & ADM5120_UART_DCD) + uart_handle_dcd_change(port, status & ADM5120_UART_DCD); + + if (status & ADM5120_UART_DSR) + port->icount.dsr++; + + if (status & ADM5120_UART_CTS) + uart_handle_cts_change(port, status & ADM5120_UART_CTS); + + wake_up_interruptible(&port->info->delta_msr_wait); +} + +static irqreturn_t adm5120ser_irq(int irq, void *dev_id) +{ + struct uart_port *port = dev_id; + unsigned long ir = ADM5120_UART_REG(port->iobase, ADM5120_UART_IR); + + if (ir & (ADM5120_UART_RIS | ADM5120_UART_RTIS)) + adm5120ser_irq_rx(port); + if (ir & ADM5120_UART_TIS) + adm5120ser_irq_tx(port); + if (ir & ADM5120_UART_MIS) { + adm5120ser_irq_modem(port); + ADM5120_UART_REG(port->iobase, ADM5120_UART_IR) = 0xff; + } + + return IRQ_HANDLED; +} + +static unsigned int adm5120ser_tx_empty(struct uart_port *port) +{ + unsigned int fr = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR); + return (fr & ADM5120_UART_TXFE) ? TIOCSER_TEMT : 0; +} + +static void adm5120ser_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static unsigned int adm5120ser_get_mctrl(struct uart_port *port) +{ + unsigned int result = 0; + unsigned int fr = ADM5120_UART_REG(port->iobase, ADM5120_UART_FR); + + if (fr & ADM5120_UART_CTS) + result |= TIOCM_CTS; + if (fr & ADM5120_UART_DSR) + result |= TIOCM_DSR; + if (fr & ADM5120_UART_DCD) + result |= TIOCM_CAR; + return result; +} + +static void adm5120ser_start_tx(struct uart_port *port) +{ + ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) |= ADM5120_UART_TIE; +} + +static void adm5120ser_stop_rx(struct uart_port *port) +{ + ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) &= ~ADM5120_UART_RIE; +} + +static void adm5120ser_enable_ms(struct uart_port *port) +{ +} + +static void adm5120ser_break_ctl(struct uart_port *port, int break_state) +{ + unsigned long flags; + unsigned long lcrh; + + spin_lock_irqsave(&port->lock, flags); + lcrh = ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_H); + if (break_state == -1) + lcrh |= ADM5120_UART_BRK; + else + lcrh &= ~ADM5120_UART_BRK; + ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_H) = lcrh; + spin_unlock_irqrestore(&port->lock, flags); +} + +static int adm5120ser_startup(struct uart_port *port) +{ + int ret; + + ret = request_irq(port->irq, adm5120ser_irq, 0, "ADM5120 UART", port); + if (ret) { + printk(KERN_ERR "Couldn't get irq %d\n", port->irq); + return ret; + } + ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_H) |= + ADM5120_UART_FIFO_EN; + ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) |= + ADM5120_UART_EN | ADM5120_UART_IE; + return 0; +} + +static void adm5120ser_shutdown(struct uart_port *port) +{ + ADM5120_UART_REG(port->iobase, ADM5120_UART_CR) &= ~ADM5120_UART_IE; + free_irq(port->irq, port); +} + +static void adm5120ser_set_termios(struct uart_port *port, + struct termios *termios, struct termios *old) +{ + unsigned int baud, quot, lcrh; + unsigned long flags; + + termios->c_cflag |= CREAD; + + baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); + quot = uart_get_divisor(port, baud); + + lcrh = ADM5120_UART_FIFO_EN; + switch (termios->c_cflag & CSIZE) { + case CS5: + lcrh |= ADM5120_UART_W5; + break; + case CS6: + lcrh |= ADM5120_UART_W6; + break; + case CS7: + lcrh |= ADM5120_UART_W7; + break; + default: + lcrh |= ADM5120_UART_W8; + break; + } + if (termios->c_cflag & CSTOPB) + lcrh |= ADM5120_UART_STP2; + if (termios->c_cflag & PARENB) { + lcrh |= ADM5120_UART_PEN; + if (!(termios->c_cflag & PARODD)) + lcrh |= ADM5120_UART_EPS; + } + + spin_lock_irqsave(port->lock, flags); + + ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_H) = lcrh; + + /* + * Update the per-port timeout. + */ + uart_update_timeout(port, termios->c_cflag, baud); + + port->read_status_mask = ADM5120_UART_OE; + if (termios->c_iflag & INPCK) + port->read_status_mask |= ADM5120_UART_FE | ADM5120_UART_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= ADM5120_UART_BE; + + /* + * Characters to ignore + */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= ADM5120_UART_FE | ADM5120_UART_PE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= ADM5120_UART_BE; + /* + * If we're ignoring parity and break indicators, + * ignore overruns to (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= ADM5120_UART_OE; + } + + quot = ADM5120_UART_BAUD115200; + ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_L) = quot & 0xff; + ADM5120_UART_REG(port->iobase, ADM5120_UART_LCR_M) = quot >> 8; + + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *adm5120ser_type(struct uart_port *port) +{ + return port->type == PORT_ADM5120 ? "ADM5120" : NULL; +} + +static void adm5120ser_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_ADM5120; +} + +static void adm5120ser_release_port(struct uart_port *port) +{ + release_mem_region(port->iobase, ADM5120_UART_SIZE); +} + +static int adm5120ser_request_port(struct uart_port *port) +{ + return request_mem_region(port->iobase, ADM5120_UART_SIZE, + "adm5120-uart") != NULL ? 0 : -EBUSY; +} + +static struct uart_ops adm5120ser_ops = { + .tx_empty = adm5120ser_tx_empty, + .set_mctrl = adm5120ser_set_mctrl, + .get_mctrl = adm5120ser_get_mctrl, + .stop_tx = adm5120ser_stop_tx, + .start_tx = adm5120ser_start_tx, + .stop_rx = adm5120ser_stop_rx, + .enable_ms = adm5120ser_enable_ms, + .break_ctl = adm5120ser_break_ctl, + .startup = adm5120ser_startup, + .shutdown = adm5120ser_shutdown, + .set_termios = adm5120ser_set_termios, + .type = adm5120ser_type, + .config_port = adm5120ser_config_port, + .release_port = adm5120ser_release_port, + .request_port = adm5120ser_request_port, +}; + +static void adm5120console_put(const char c) +{ + while ((ADM5120_UART_REG(ADM5120_UART_BASE0, ADM5120_UART_FR) & + ADM5120_UART_TXFF) != 0); + ADM5120_UART_REG(ADM5120_UART_BASE0, ADM5120_UART_DATA) = c; +} + +static void adm5120console_write(struct console *con, const char *s, + unsigned int count) +{ + while (count--) { + if (*s == '\n') + adm5120console_put('\r'); + adm5120console_put(*s); + s++; + } +} + +static int __init adm5120console_setup(struct console *con, char *options) +{ + /* Set to 115200 baud, 8N1 and enable FIFO */ + ADM5120_UART_REG(ADM5120_UART_BASE0, ADM5120_UART_LCR_L) = + ADM5120_UART_BAUD115200 & 0xff; + ADM5120_UART_REG(ADM5120_UART_BASE0, ADM5120_UART_LCR_M) = + ADM5120_UART_BAUD115200 >> 8; + ADM5120_UART_REG(ADM5120_UART_BASE0, ADM5120_UART_LCR_H) = + ADM5120_UART_W8 | ADM5120_UART_FIFO_EN; + /* Enable port */ + ADM5120_UART_REG(ADM5120_UART_BASE0, ADM5120_UART_CR) = + ADM5120_UART_EN; + + return 0; +} + +static struct uart_driver adm5120ser_reg; + +static struct console adm5120_serconsole = { + .name = "ttyS", + .write = adm5120console_write, + .device = uart_console_device, + .setup = adm5120console_setup, + .flags = CON_PRINTBUFFER, + .cflag = B115200 | CS8 | CREAD, + .index = 0, + .data = &adm5120ser_reg, +}; + +static int __init adm5120console_init(void) +{ + register_console(&adm5120_serconsole); + return 0; +} + +console_initcall(adm5120console_init); + + +static struct uart_port adm5120ser_ports[] = { + { + .iobase = ADM5120_UART_BASE0, + .irq = ADM5120_UART_IRQ0, + .uartclk = ADM5120_UARTCLK_FREQ, + .fifosize = 16, + .ops = &adm5120ser_ops, + .line = 0, + .flags = ASYNC_BOOT_AUTOCONF, + }, +#if (CONFIG_ADM5120_NR_UARTS > 1) + { + .iobase = ADM5120_UART_BASE1, + .irq = ADM5120_UART_IRQ1, + .uartclk = ADM5120_UARTCLK_FREQ, + .fifosize = 16, + .ops = &adm5120ser_ops, + .line = 1, + .flags = ASYNC_BOOT_AUTOCONF, + }, +#endif +}; + +static struct uart_driver adm5120ser_reg = { + .owner = THIS_MODULE, + .driver_name = "ttyS", + .dev_name = "ttyS", + .major = TTY_MAJOR, + .minor = 64, + .nr = CONFIG_ADM5120_NR_UARTS, + .cons = &adm5120_serconsole, +}; + +static int __init adm5120ser_init(void) +{ + int ret, i; + + ret = uart_register_driver(&adm5120ser_reg); + if (!ret) { + for (i = 0; i < CONFIG_ADM5120_NR_UARTS; i++) + uart_add_one_port(&adm5120ser_reg, &adm5120ser_ports[i]); + } + + return ret; +} + +__initcall(adm5120ser_init); diff --git a/target/linux/adm5120-2.6/files/drivers/usb/host/adm5120-hcd.c b/target/linux/adm5120-2.6/files/drivers/usb/host/adm5120-hcd.c new file mode 100644 index 0000000000..b4a1899d1d --- /dev/null +++ b/target/linux/adm5120-2.6/files/drivers/usb/host/adm5120-hcd.c @@ -0,0 +1,790 @@ +/* + * HCD driver for ADM5120 SoC + * + * Copyright (C) 2005 Jeroen Vreeken (pe1rxq@amsat.org) + * + * Based on the ADMtek 2.4 driver + * (C) Copyright 2003 Junius Chen <juniusc@admtek.com.tw> + * Which again was based on the ohci and uhci drivers. + */ + +#include <linux/autoconf.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <linux/platform_device.h> + +#include "../core/hcd.h" + +MODULE_DESCRIPTION("ADM5120 USB Host Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jeroen Vreeken (pe1rxq@amsat.org)"); + +#define ADMHCD_REG_CONTROL 0x00 +#define ADMHCD_REG_INTSTATUS 0x04 +#define ADMHCD_REG_INTENABLE 0x08 +#define ADMHCD_REG_HOSTCONTROL 0x10 +#define ADMHCD_REG_FMINTERVAL 0x18 +#define ADMHCD_REG_FMNUMBER 0x1c +#define ADMHCD_REG_LSTHRESH 0x70 +#define ADMHCD_REG_RHDESCR 0x74 +#define ADMHCD_REG_PORTSTATUS0 0x78 +#define ADMHCD_REG_PORTSTATUS1 0x7c +#define ADMHCD_REG_HOSTHEAD 0x80 + + +#define ADMHCD_NUMPORTS 2 + +#define ADMHCD_HOST_EN 0x00000001 /* Host enable */ +#define ADMHCD_SW_INTREQ 0x00000002 /* request software int */ +#define ADMHCD_SW_RESET 0x00000008 /* Reset */ + +#define ADMHCD_INT_TD 0x00100000 /* TD completed */ +#define ADMHCD_INT_SW 0x20000000 /* software interrupt */ +#define ADMHCD_INT_FATAL 0x40000000 /* Fatal interrupt */ +#define ADMHCD_INT_ACT 0x80000000 /* Interrupt active */ + +#define ADMHCD_STATE_RST 0x00000000 /* bus state reset */ +#define ADMHCD_STATE_RES 0x00000001 /* bus state resume */ +#define ADMHCD_STATE_OP 0x00000002 /* bus state operational */ +#define ADMHCD_STATE_SUS 0x00000003 /* bus state suspended */ +#define ADMHCD_DMA_EN 0x00000004 /* enable dma engine */ + +#define ADMHCD_NPS 0x00000020 /* No Power Switch */ +#define ADMHCD_LPSC 0x04000000 /* Local power switch change */ + +#define ADMHCD_CCS 0x00000001 /* current connect status */ +#define ADMHCD_PES 0x00000002 /* port enable status */ +#define ADMHCD_PSS 0x00000004 /* port suspend status */ +#define ADMHCD_POCI 0x00000008 /* port overcurrent indicator */ +#define ADMHCD_PRS 0x00000010 /* port reset status */ +#define ADMHCD_PPS 0x00000100 /* port power status */ +#define ADMHCD_LSDA 0x00000200 /* low speed device attached */ +#define ADMHCD_CSC 0x00010000 /* connect status change */ +#define ADMHCD_PESC 0x00020000 /* enable status change */ +#define ADMHCD_PSSC 0x00040000 /* suspend status change */ +#define ADMHCD_OCIC 0x00080000 /* overcurrent change*/ +#define ADMHCD_PRSC 0x00100000 /* reset status change */ + + +struct admhcd_ed { + /* Don't change first four, they used for DMA */ + u32 control; + struct admhcd_td *tail; + struct admhcd_td *head; + struct admhcd_ed *next; + /* the rest is for the driver only: */ + struct admhcd_td *cur; + struct usb_host_endpoint *ep; + struct urb *urb; + struct admhcd_ed *real; +} __attribute__ ((packed)); + +#define ADMHCD_ED_EPSHIFT 7 /* Shift for endpoint number */ +#define ADMHCD_ED_INT 0x00000800 /* Is this an int endpoint */ +#define ADMHCD_ED_SPEED 0x00002000 /* Is it a high speed dev? */ +#define ADMHCD_ED_SKIP 0x00004000 /* Skip this ED */ +#define ADMHCD_ED_FORMAT 0x00008000 /* Is this an isoc endpoint */ +#define ADMHCD_ED_MAXSHIFT 16 /* Shift for max packet size */ + +struct admhcd_td { + /* Don't change first four, they are used for DMA */ + u32 control; + u32 buffer; + u32 buflen; + struct admhcd_td *next; + /* the rest is for the driver only: */ + struct urb *urb; + struct admhcd_td *real; +} __attribute__ ((packed)); + +#define ADMHCD_TD_OWN 0x80000000 +#define ADMHCD_TD_TOGGLE 0x00000000 +#define ADMHCD_TD_DATA0 0x01000000 +#define ADMHCD_TD_DATA1 0x01800000 +#define ADMHCD_TD_OUT 0x00200000 +#define ADMHCD_TD_IN 0x00400000 +#define ADMHCD_TD_SETUP 0x00000000 +#define ADMHCD_TD_ISO 0x00010000 +#define ADMHCD_TD_R 0x00040000 +#define ADMHCD_TD_INTEN 0x00010000 + +static int admhcd_td_err[16] = { + 0, /* No */ + -EREMOTEIO, /* CRC */ + -EREMOTEIO, /* bit stuff */ + -EREMOTEIO, /* data toggle */ + -EPIPE, /* stall */ + -ETIMEDOUT, /* timeout */ + -EPROTO, /* pid err */ + -EPROTO, /* unexpected pid */ + -EREMOTEIO, /* data overrun */ + -EREMOTEIO, /* data underrun */ + -ETIMEDOUT, /* 1010 */ + -ETIMEDOUT, /* 1011 */ + -EREMOTEIO, /* buffer overrun */ + -EREMOTEIO, /* buffer underrun */ + -ETIMEDOUT, /* 1110 */ + -ETIMEDOUT, /* 1111 */ +}; + +#define ADMHCD_TD_ERRMASK 0x38000000 +#define ADMHCD_TD_ERRSHIFT 27 + +#define TD(td) ((struct admhcd_td *)(((u32)(td)) & ~0xf)) +#define ED(ed) ((struct admhcd_ed *)(((u32)(ed)) & ~0xf)) + +struct admhcd { + u32 base; + u32 dma_en; + spinlock_t lock; + unsigned long flags; +}; + +#define hcd_to_admhcd(hcd) ((struct admhcd *)(hcd)->hcd_priv) + +static char hcd_name[] = "adm5120-hcd"; + +static u32 admhcd_reg_get(struct admhcd *ahcd, int reg) +{ + return *(volatile u32 *)KSEG1ADDR(ahcd->base+reg); +} + +static void admhcd_reg_set(struct admhcd *ahcd, int reg, u32 val) +{ + *(volatile u32 *)KSEG1ADDR(ahcd->base+reg) = val; +} + +static void admhcd_lock(struct admhcd *ahcd) +{ + spin_lock_irqsave(&ahcd->lock, ahcd->flags); + ahcd->dma_en = admhcd_reg_get(ahcd, ADMHCD_REG_HOSTCONTROL) & + ADMHCD_DMA_EN; + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, ADMHCD_STATE_OP); +} + +static void admhcd_unlock(struct admhcd *ahcd) +{ + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, + ADMHCD_STATE_OP | ahcd->dma_en); + spin_unlock_irqrestore(&ahcd->lock, ahcd->flags); +} + +static struct admhcd_td *admhcd_td_alloc(struct admhcd_ed *ed, struct urb *urb) +{ + struct admhcd_td *tdn, *td; + + tdn = kmalloc(sizeof(struct admhcd_td), GFP_ATOMIC); + if (!tdn) + return NULL; + tdn->real = tdn; + tdn = (struct admhcd_td *)KSEG1ADDR(tdn); + memset(tdn, 0, sizeof(struct admhcd_td)); + if (ed->cur == NULL) { + ed->cur = tdn; + ed->head = tdn; + ed->tail = tdn; + td = tdn; + } else { + /* Supply back the old tail and link in new td as tail */ + td = TD(ed->tail); + TD(ed->tail)->next = tdn; + ed->tail = tdn; + } + td->urb = urb; + + return td; +} + +static void admhcd_td_free(struct admhcd_ed *ed, struct urb *urb) +{ + struct admhcd_td *td, **tdp; + + if (urb == NULL) + ed->control |= ADMHCD_ED_SKIP; + tdp = &ed->cur; + td = ed->cur; + do { + if (td->urb == urb) + break; + tdp = &td->next; + td = TD(td->next); + } while (td); + while (td && td->urb == urb) { + *tdp = TD(td->next); + kfree(td->real); + td = *tdp; + } +} + +/* Find an endpoint's descriptor, if needed allocate a new one and link it + in the DMA chain + */ +static struct admhcd_ed *admhcd_get_ed(struct admhcd *ahcd, + struct usb_host_endpoint *ep, struct urb *urb) +{ + struct admhcd_ed *hosthead; + struct admhcd_ed *found = NULL, *ed = NULL; + unsigned int pipe = urb->pipe; + + admhcd_lock(ahcd); + hosthead = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD); + if (hosthead) { + for (ed = hosthead;; ed = ED(ed->next)) { + if (ed->ep == ep) { + found = ed; + break; + } + if (ED(ed->next) == hosthead) + break; + } + } + if (!found) { + found = kmalloc(sizeof(struct admhcd_ed), GFP_ATOMIC); + if (!found) + goto out; + memset(found, 0, sizeof(struct admhcd_ed)); + found->real = found; + found->ep = ep; + found = (struct admhcd_ed *)KSEG1ADDR(found); + found->control = usb_pipedevice(pipe) | + (usb_pipeendpoint(pipe) << ADMHCD_ED_EPSHIFT) | + (usb_pipeint(pipe) ? ADMHCD_ED_INT : 0) | + (urb->dev->speed == USB_SPEED_FULL ? ADMHCD_ED_SPEED : 0) | + (usb_pipeisoc(pipe) ? ADMHCD_ED_FORMAT : 0) | + (usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)) << ADMHCD_ED_MAXSHIFT); + /* Alloc first dummy td */ + admhcd_td_alloc(found, NULL); + if (hosthead) { + found->next = hosthead; + ed->next = found; + } else { + found->next = found; + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, (u32)found); + } + } +out: + admhcd_unlock(ahcd); + return found; +} + +static struct admhcd_td *admhcd_td_fill(u32 control, struct admhcd_td *td, + dma_addr_t data, int len) +{ + td->buffer = data; + td->buflen = len; + td->control = control; + return TD(td->next); +} + +static void admhcd_ed_start(struct admhcd *ahcd, struct admhcd_ed *ed) +{ + struct admhcd_td *td = ed->cur; + + if (ed->urb) + return; + if (td->urb) { + ed->urb = td->urb; + while (1) { + td->control |= ADMHCD_TD_OWN; + if (TD(td->next)->urb != td->urb) { + td->buflen |= ADMHCD_TD_INTEN; + break; + } + td = TD(td->next); + } + } + ed->head = TD(ed->head); + ahcd->dma_en |= ADMHCD_DMA_EN; +} + +static irqreturn_t adm5120hcd_irq(int irq, void *ptr, struct pt_regs *regs) +{ + struct usb_hcd *hcd = (struct usb_hcd *)ptr; + struct admhcd *ahcd = hcd_to_admhcd(hcd); + u32 intstatus; + + intstatus = admhcd_reg_get(ahcd, ADMHCD_REG_INTSTATUS); + if (intstatus & ADMHCD_INT_FATAL) { + admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_FATAL); + // + } + if (intstatus & ADMHCD_INT_SW) { + admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_SW); + // + } + if (intstatus & ADMHCD_INT_TD) { + struct admhcd_ed *ed, *head; + + admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, ADMHCD_INT_TD); + + head = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD); + ed = head; + if (ed) do { + /* Is it a finished TD? */ + if (ed->urb && !(ed->cur->control & ADMHCD_TD_OWN)) { + struct admhcd_td *td; + int error; + + td = ed->cur; + error = (td->control & ADMHCD_TD_ERRMASK) >> + ADMHCD_TD_ERRSHIFT; + ed->urb->status = admhcd_td_err[error]; + admhcd_td_free(ed, ed->urb); + // Calculate real length!!! + ed->urb->actual_length = ed->urb->transfer_buffer_length; + ed->urb->hcpriv = NULL; + usb_hcd_giveback_urb(hcd, ed->urb); + ed->urb = NULL; + } + admhcd_ed_start(ahcd, ed); + ed = ED(ed->next); + } while (ed != head); + } + + return IRQ_HANDLED; +} + +static int admhcd_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep, + struct urb *urb, int mem_flags) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + struct admhcd_ed *ed; + struct admhcd_td *td; + int size = 0, i, zero = 0, ret = 0; + unsigned int pipe = urb->pipe, toggle = 0; + dma_addr_t data = (dma_addr_t)urb->transfer_buffer; + int data_len = urb->transfer_buffer_length; + + ed = admhcd_get_ed(ahcd, ep, urb); + if (!ed) + return -ENOMEM; + + switch(usb_pipetype(pipe)) { + case PIPE_CONTROL: + size = 2; + case PIPE_INTERRUPT: + case PIPE_BULK: + default: + size += urb->transfer_buffer_length / 4096; + if (urb->transfer_buffer_length % 4096) + size++; + if (size == 0) + size++; + else if (urb->transfer_flags & URB_ZERO_PACKET && + !(urb->transfer_buffer_length % + usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)))) { + size++; + zero = 1; + } + break; + case PIPE_ISOCHRONOUS: + size = urb->number_of_packets; + break; + } + + admhcd_lock(ahcd); + /* Remember the first td */ + td = admhcd_td_alloc(ed, urb); + if (!td) { + ret = -ENOMEM; + goto out; + } + /* Allocate additionall tds first */ + for (i = 1; i < size; i++) { + if (admhcd_td_alloc(ed, urb) == NULL) { + admhcd_td_free(ed, urb); + ret = -ENOMEM; + goto out; + } + } + + if (usb_gettoggle(urb->dev, usb_pipeendpoint(pipe), usb_pipeout(pipe))) + toggle = ADMHCD_TD_TOGGLE; + else { + toggle = ADMHCD_TD_DATA0; + usb_settoggle(urb->dev, usb_pipeendpoint(pipe), + usb_pipeout(pipe), 1); + } + + switch(usb_pipetype(pipe)) { + case PIPE_CONTROL: + td = admhcd_td_fill(ADMHCD_TD_SETUP | ADMHCD_TD_DATA0, + td, (dma_addr_t)urb->setup_packet, 8); + while (data_len > 0) { + td = admhcd_td_fill(ADMHCD_TD_DATA1 + | ADMHCD_TD_R | + (usb_pipeout(pipe) ? + ADMHCD_TD_OUT : ADMHCD_TD_IN), td, + data, data_len % 4097); + data_len -= 4096; + } + admhcd_td_fill(ADMHCD_TD_DATA1 | (usb_pipeout(pipe) ? + ADMHCD_TD_IN : ADMHCD_TD_OUT), td, + data, 0); + break; + case PIPE_INTERRUPT: + case PIPE_BULK: + //info ok for interrupt? + i = 0; + while(data_len > 4096) { + td = admhcd_td_fill((usb_pipeout(pipe) ? + ADMHCD_TD_OUT : + ADMHCD_TD_IN | ADMHCD_TD_R) | + (i ? ADMHCD_TD_TOGGLE : toggle), td, + data, 4096); + data += 4096; + data_len -= 4096; + i++; + } + td = admhcd_td_fill((usb_pipeout(pipe) ? + ADMHCD_TD_OUT : ADMHCD_TD_IN) | + (i ? ADMHCD_TD_TOGGLE : toggle), td, data, data_len); + i++; + if (zero) + admhcd_td_fill((usb_pipeout(pipe) ? + ADMHCD_TD_OUT : ADMHCD_TD_IN) | + (i ? ADMHCD_TD_TOGGLE : toggle), td, 0, 0); + break; + case PIPE_ISOCHRONOUS: + for (i = 0; i < urb->number_of_packets; i++) { + td = admhcd_td_fill(ADMHCD_TD_ISO | + ((urb->start_frame + i) & 0xffff), td, + data + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].length); + } + break; + } + urb->hcpriv = ed; + admhcd_ed_start(ahcd, ed); +out: + admhcd_unlock(ahcd); + return ret; +} + +static int admhcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + struct admhcd_ed *ed; + + admhcd_lock(ahcd); + + ed = urb->hcpriv; + if (ed && ed->urb != urb) + admhcd_td_free(ed, urb); + + admhcd_unlock(ahcd); + return 0; +} + +static void admhcd_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + struct admhcd_ed *ed, *edt, *head; + + admhcd_lock(ahcd); + + head = (struct admhcd_ed *)admhcd_reg_get(ahcd, ADMHCD_REG_HOSTHEAD); + if (!head) + goto out; + for (ed = head; ED(ed->next) != head; ed = ED(ed->next)) + if (ed->ep == ep) + break; + if (ed->ep != ep) + goto out; + while (ed->cur) + admhcd_td_free(ed, ed->cur->urb); + if (head == ed) { + if (ED(ed->next) == ed) { + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, 0); + ahcd->dma_en = 0; + goto out_free; + } + head = ED(ed->next); + for (edt = head; ED(edt->next) != head; edt = ED(edt->next)); + edt->next = ED(ed->next); + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, (u32)ed->next); + goto out_free; + } + for (edt = head; edt->next != ed; edt = edt->next); + edt->next = ed->next; +out_free: + kfree(ed->real); +out: + admhcd_unlock(ahcd); +} + +static int admhcd_get_frame_number(struct usb_hcd *hcd) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + + return admhcd_reg_get(ahcd, ADMHCD_REG_FMNUMBER) & 0x0000ffff; +} + +static int admhcd_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + int port; + + *buf = 0; + for (port = 0; port < ADMHCD_NUMPORTS; port++) { + if (admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4) & + (ADMHCD_CSC | ADMHCD_PESC | ADMHCD_PSSC | ADMHCD_OCIC | + ADMHCD_PRSC)) + *buf |= (1 << (port + 1)); + } + return !!*buf; +} + +static __u8 root_hub_hub_des[] = { + 0x09, /* __u8 bLength; */ + 0x29, /* __u8 bDescriptorType; Hub-descriptor */ + 0x02, /* __u8 bNbrPorts; */ + 0x0a, 0x00, /* __u16 wHubCharacteristics; */ + 0x01, /* __u8 bPwrOn2pwrGood; 2ms */ + 0x00, /* __u8 bHubContrCurrent; 0mA */ + 0x00, /* __u8 DeviceRemovable; */ + 0xff, /* __u8 PortPwrCtrlMask; */ +}; + +static int admhcd_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct admhcd *ahcd = hcd_to_admhcd(hcd); + int retval = 0, len; + unsigned int port = wIndex -1; + + switch (typeReq) { + + case GetHubStatus: + *(__le32 *)buf = cpu_to_le32(0); + break; + case GetPortStatus: + if (port >= ADMHCD_NUMPORTS) + goto err; + *(__le32 *)buf = cpu_to_le32( + admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4)); + break; + case SetHubFeature: /* We don't implement these */ + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto err; + } + case SetPortFeature: + if (port >= ADMHCD_NUMPORTS) + goto err; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_PSS); + break; + case USB_PORT_FEAT_RESET: + if (admhcd_reg_get(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4) + & ADMHCD_CCS) { + admhcd_reg_set(ahcd, + ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_PRS | ADMHCD_CSC); + mdelay(50); + admhcd_reg_set(ahcd, + ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_PES | ADMHCD_CSC); + } + break; + case USB_PORT_FEAT_POWER: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_PPS); + break; + default: + goto err; + } + break; + case ClearPortFeature: + if (port >= ADMHCD_NUMPORTS) + goto err; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_CCS); + break; + case USB_PORT_FEAT_C_ENABLE: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_PESC); + break; + case USB_PORT_FEAT_SUSPEND: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_POCI); + break; + case USB_PORT_FEAT_C_SUSPEND: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_PSSC); + case USB_PORT_FEAT_POWER: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_LSDA); + break; + case USB_PORT_FEAT_C_CONNECTION: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_CSC); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_OCIC); + break; + case USB_PORT_FEAT_C_RESET: + admhcd_reg_set(ahcd, ADMHCD_REG_PORTSTATUS0 + port*4, + ADMHCD_PRSC); + break; + default: + goto err; + } + break; + case GetHubDescriptor: + len = min_t(unsigned int, sizeof(root_hub_hub_des), wLength); + memcpy(buf, root_hub_hub_des, len); + break; + default: +err: + retval = -EPIPE; + } + + return retval; +} + +static struct hc_driver adm5120_hc_driver = { + .description = hcd_name, + .product_desc = "ADM5120 HCD", + .hcd_priv_size = sizeof(struct admhcd), + .flags = HCD_USB11, + .urb_enqueue = admhcd_urb_enqueue, + .urb_dequeue = admhcd_urb_dequeue, + .endpoint_disable = admhcd_endpoint_disable, + .get_frame_number = admhcd_get_frame_number, + .hub_status_data = admhcd_hub_status_data, + .hub_control = admhcd_hub_control, +}; + +static int __init adm5120hcd_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct admhcd *ahcd; + struct usb_device *udev; + int err = 0; + + if (!request_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start, hcd_name)) { + pr_debug("couldn't request mem\n"); + err = -EBUSY; + goto out; + } + + //hcd = usb_create_hcd(&adm5120_hc_driver, pdev, pdev->bus_id); + if (!hcd) + goto out_mem; + + ahcd = hcd_to_admhcd(hcd); + dev_set_drvdata(pdev, ahcd); + hcd->self.controller = pdev; + //hcd->self.bus_name = pdev->bus_id; + hcd->irq = pdev->resource[1].start; + hcd->regs = (void *)pdev->resource[0].start; + hcd->product_desc = hcd_name; + ahcd->base = pdev->resource[0].start; + + if (request_irq(pdev->resource[1].start, adm5120hcd_irq, 0, hcd_name, + hcd)) { + pr_debug("couldn't request irq\n"); + err = -EBUSY; + goto out_hcd; + } + + //err = usb_register_bus(&hcd->self); + //if (err < 0) + // goto out_irq; + + spin_lock_init(&ahcd->lock); + + admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, 0); + mdelay(10); + admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_SW_RESET); + while (admhcd_reg_get(ahcd, ADMHCD_REG_CONTROL) & ADMHCD_SW_RESET) + mdelay(1); + + admhcd_reg_set(ahcd, ADMHCD_REG_CONTROL, ADMHCD_HOST_EN); + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTHEAD, 0x00000000); + admhcd_reg_set(ahcd, ADMHCD_REG_FMINTERVAL, 0x20002edf); + admhcd_reg_set(ahcd, ADMHCD_REG_LSTHRESH, 0x628); + admhcd_reg_set(ahcd, ADMHCD_REG_INTENABLE, + ADMHCD_INT_ACT | ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD); + admhcd_reg_set(ahcd, ADMHCD_REG_INTSTATUS, + ADMHCD_INT_ACT | ADMHCD_INT_FATAL | ADMHCD_INT_SW | ADMHCD_INT_TD); + admhcd_reg_set(ahcd, ADMHCD_REG_RHDESCR, ADMHCD_NPS | ADMHCD_LPSC); + admhcd_reg_set(ahcd, ADMHCD_REG_HOSTCONTROL, ADMHCD_STATE_OP); + + udev = usb_alloc_dev(NULL, &hcd->self, 0); + if (!udev) { + err = -ENOMEM; + goto out_bus; + } + + udev->speed = USB_SPEED_FULL; + hcd->state = HC_STATE_RUNNING; + + //err = hcd_register_root(udev, hcd); + //if (err != 0) { + // usb_put_dev(udev); + // goto out_dev; + //} + + return 0; + +out_dev: + usb_put_dev(udev); +out_bus: + //usb_deregister_bus(&hcd->self); +out_irq: + free_irq(pdev->resource[1].start, hcd); +out_hcd: + usb_put_hcd(hcd); +out_mem: + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start); +out: + return err; +} + +static int __init_or_module adm5120hcd_remove(struct platform_device *pdev) +{ + device_init_wakeup(&pdev->dev, 0); +} + +static struct platform_driver adm5120hcd_driver = { + .probe = adm5120hcd_probe, + .remove = adm5120hcd_remove, + .driver = { + .name = "adm5120-hcd", + .owner = THIS_MODULE, + }, +}; + +static int __init adm5120hcd_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + return platform_driver_register(&adm5120hcd_driver); +} + +static void __exit adm5120hcd_exit(void) +{ + platform_driver_unregister(&adm5120hcd_driver); +} + +module_init(adm5120hcd_init); +module_exit(adm5120hcd_exit); |