diff options
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 000000000..6fccfb029 --- /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 000000000..a9e97cb31 --- /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 000000000..935016c3a --- /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 000000000..4ea1e6257 --- /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 000000000..cbbdbfb6a --- /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 000000000..b4a1899d1 --- /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); | 
