Add limited support for the etrax ethernet controller.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4429 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
		
							parent
							
								
									1ba13a5dfc
								
							
						
					
					
						commit
						a3ea5df588
					
				
							
								
								
									
										453
									
								
								hw/etraxfs_eth.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										453
									
								
								hw/etraxfs_eth.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,453 @@ | ||||
| /*
 | ||||
|  * QEMU ETRAX Ethernet Controller. | ||||
|  * | ||||
|  * Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB. | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | ||||
|  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include "hw.h" | ||||
| #include "net.h" | ||||
| 
 | ||||
| #include "etraxfs_dma.h" | ||||
| 
 | ||||
| #define D(x) | ||||
| 
 | ||||
| #define R_STAT            0x2c | ||||
| #define RW_MGM_CTRL       0x28 | ||||
| #define FS_ETH_MAX_REGS   0x5c | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| struct qemu_phy | ||||
| { | ||||
| 	uint32_t regs[32]; | ||||
| 
 | ||||
| 	unsigned int (*read)(struct qemu_phy *phy, unsigned int req); | ||||
| 	void (*write)(struct qemu_phy *phy, unsigned int req, unsigned int data); | ||||
| }; | ||||
| 
 | ||||
| static unsigned int tdk_read(struct qemu_phy *phy, unsigned int req) | ||||
| { | ||||
| 	int regnum; | ||||
| 	unsigned r = 0; | ||||
| 
 | ||||
| 	regnum = req & 0x1f; | ||||
| 
 | ||||
| 	switch (regnum) { | ||||
| 		case 1: | ||||
| 			/* MR1.  */ | ||||
| 			/* Speeds and modes.  */ | ||||
| 			r |= (1 << 13) | (1 << 14); | ||||
| 			r |= (1 << 11) | (1 << 12); | ||||
| 			r |= (1 << 5); /* Autoneg complete.  */ | ||||
| 			r |= (1 << 3); /* Autoneg able.  */ | ||||
| 			r |= (1 << 2); /* Link.  */ | ||||
| 			break; | ||||
| 		default: | ||||
| 			r = phy->regs[regnum]; | ||||
| 			break; | ||||
| 	} | ||||
| 	D(printf("%s %x = reg[%d]\n", __func__, r, regnum)); | ||||
| 	return r; | ||||
| } | ||||
| 
 | ||||
| static void  | ||||
| tdk_write(struct qemu_phy *phy, unsigned int req, unsigned int data) | ||||
| { | ||||
| 	int regnum; | ||||
| 
 | ||||
| 	regnum = req & 0x1f; | ||||
| 	D(printf("%s reg[%d] = %x\n", __func__, regnum, data)); | ||||
| 	switch (regnum) { | ||||
| 		default: | ||||
| 			phy->regs[regnum] = data; | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void  | ||||
| tdk_init(struct qemu_phy *phy) | ||||
| { | ||||
| 	phy->read = tdk_read; | ||||
| 	phy->write = tdk_write; | ||||
| } | ||||
| 
 | ||||
| struct qemu_mdio | ||||
| { | ||||
| 	/* bus.  */ | ||||
| 	int mdc; | ||||
| 	int mdio; | ||||
| 
 | ||||
| 	/* decoder.  */ | ||||
| 	enum { | ||||
| 		PREAMBLE, | ||||
| 		SOF, | ||||
| 		OPC, | ||||
| 		ADDR, | ||||
| 		REQ, | ||||
| 		TURNAROUND, | ||||
| 		DATA | ||||
| 	} state; | ||||
| 	unsigned int drive; | ||||
| 
 | ||||
| 	unsigned int cnt; | ||||
| 	unsigned int addr; | ||||
| 	unsigned int opc; | ||||
| 	unsigned int req; | ||||
| 	unsigned int data; | ||||
| 
 | ||||
| 	struct qemu_phy *devs[32]; | ||||
| }; | ||||
| 
 | ||||
| static void  | ||||
| mdio_attach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr) | ||||
| { | ||||
| 	bus->devs[addr & 0x1f] = phy; | ||||
| } | ||||
| 
 | ||||
| static void  | ||||
| mdio_detach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr) | ||||
| { | ||||
| 	bus->devs[addr & 0x1f] = NULL;	 | ||||
| } | ||||
| 
 | ||||
| static void mdio_read_req(struct qemu_mdio *bus) | ||||
| { | ||||
| 	struct qemu_phy *phy; | ||||
| 
 | ||||
| 	phy = bus->devs[bus->addr]; | ||||
| 	if (phy && phy->read) | ||||
| 		bus->data = phy->read(phy, bus->req); | ||||
| 	else  | ||||
| 		bus->data = 0xffff; | ||||
| } | ||||
| 
 | ||||
| static void mdio_write_req(struct qemu_mdio *bus) | ||||
| { | ||||
| 	struct qemu_phy *phy; | ||||
| 
 | ||||
| 	phy = bus->devs[bus->addr]; | ||||
| 	if (phy && phy->write) | ||||
| 		phy->write(phy, bus->req, bus->data); | ||||
| } | ||||
| 
 | ||||
| static void mdio_cycle(struct qemu_mdio *bus) | ||||
| { | ||||
| 	bus->cnt++; | ||||
| 
 | ||||
| 	D(printf("mdc=%d mdio=%d state=%d cnt=%d drv=%d\n", | ||||
| 		bus->mdc, bus->mdio, bus->state, bus->cnt, bus->drive)); | ||||
| #if 0 | ||||
| 	if (bus->mdc) | ||||
| 		printf("%d", bus->mdio); | ||||
| #endif | ||||
| 	switch (bus->state) | ||||
| 	{ | ||||
| 		case PREAMBLE: | ||||
| 			if (bus->mdc) { | ||||
| 				if (bus->cnt >= (32 * 2) && !bus->mdio) { | ||||
| 					bus->cnt = 0; | ||||
| 					bus->state = SOF; | ||||
| 					bus->data = 0; | ||||
| 				} | ||||
| 			} | ||||
| 			break; | ||||
| 		case SOF: | ||||
| 			if (bus->mdc) { | ||||
| 				if (bus->mdio != 1) | ||||
| 					printf("WARNING: no SOF\n"); | ||||
| 				if (bus->cnt == 1*2) { | ||||
| 					bus->cnt = 0; | ||||
| 					bus->opc = 0; | ||||
| 					bus->state = OPC; | ||||
| 				} | ||||
| 			} | ||||
| 			break; | ||||
| 		case OPC: | ||||
| 			if (bus->mdc) { | ||||
| 				bus->opc <<= 1; | ||||
| 				bus->opc |= bus->mdio & 1; | ||||
| 				if (bus->cnt == 2*2) { | ||||
| 					bus->cnt = 0; | ||||
| 					bus->addr = 0; | ||||
| 					bus->state = ADDR; | ||||
| 				} | ||||
| 			} | ||||
| 			break; | ||||
| 		case ADDR: | ||||
| 			if (bus->mdc) { | ||||
| 				bus->addr <<= 1; | ||||
| 				bus->addr |= bus->mdio & 1; | ||||
| 
 | ||||
| 				if (bus->cnt == 5*2) { | ||||
| 					bus->cnt = 0; | ||||
| 					bus->req = 0; | ||||
| 					bus->state = REQ; | ||||
| 				} | ||||
| 			} | ||||
| 			break; | ||||
| 		case REQ: | ||||
| 			if (bus->mdc) { | ||||
| 				bus->req <<= 1; | ||||
| 				bus->req |= bus->mdio & 1; | ||||
| 				if (bus->cnt == 5*2) { | ||||
| 					bus->cnt = 0; | ||||
| 					bus->state = TURNAROUND; | ||||
| 				} | ||||
| 			} | ||||
| 			break; | ||||
| 		case TURNAROUND: | ||||
| 			if (bus->mdc && bus->cnt == 2*2) { | ||||
| 				bus->mdio = 0; | ||||
| 				bus->cnt = 0; | ||||
| 
 | ||||
| 				if (bus->opc == 2) { | ||||
| 					bus->drive = 1; | ||||
| 					mdio_read_req(bus); | ||||
| 					bus->mdio = bus->data & 1; | ||||
| 				} | ||||
| 				bus->state = DATA; | ||||
| 			} | ||||
| 			break; | ||||
| 		case DATA:			 | ||||
| 			if (!bus->mdc) { | ||||
| 				if (bus->drive) { | ||||
| 					bus->mdio = bus->data & 1; | ||||
| 					bus->data >>= 1; | ||||
| 				} | ||||
| 			} else { | ||||
| 				if (!bus->drive) { | ||||
| 					bus->data <<= 1; | ||||
| 					bus->data |= bus->mdio; | ||||
| 				} | ||||
| 				if (bus->cnt == 16 * 2) { | ||||
| 					bus->cnt = 0; | ||||
| 					bus->state = PREAMBLE; | ||||
| 					mdio_write_req(bus); | ||||
| 				} | ||||
| 			} | ||||
| 			break; | ||||
| 		default: | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| struct fs_eth | ||||
| { | ||||
|         CPUState *env; | ||||
| 	qemu_irq *irq; | ||||
|         target_phys_addr_t base; | ||||
| 	VLANClientState *vc; | ||||
| 	uint8_t macaddr[6]; | ||||
| 	int ethregs; | ||||
| 
 | ||||
| 	uint32_t regs[FS_ETH_MAX_REGS]; | ||||
| 
 | ||||
| 	unsigned char rx_fifo[1536]; | ||||
| 	int rx_fifo_len; | ||||
| 	int rx_fifo_pos; | ||||
| 
 | ||||
| 	struct etraxfs_dma_client *dma_out; | ||||
| 	struct etraxfs_dma_client *dma_in; | ||||
| 
 | ||||
| 	/* MDIO bus.  */ | ||||
| 	struct qemu_mdio mdio_bus; | ||||
| 	/* PHY.  */ | ||||
| 	struct qemu_phy phy; | ||||
| }; | ||||
| 
 | ||||
| static uint32_t eth_rinvalid (void *opaque, target_phys_addr_t addr) | ||||
| { | ||||
|         struct fs_eth *eth = opaque; | ||||
|         CPUState *env = eth->env; | ||||
|         cpu_abort(env, "Unsupported short access. reg=%x pc=%x.\n",  | ||||
|                   addr, env->pc); | ||||
|         return 0; | ||||
| } | ||||
| 
 | ||||
| static uint32_t eth_readl (void *opaque, target_phys_addr_t addr) | ||||
| { | ||||
|         struct fs_eth *eth = opaque; | ||||
|         D(CPUState *env = eth->env); | ||||
|         uint32_t r = 0; | ||||
| 
 | ||||
|         /* Make addr relative to this instances base.  */ | ||||
|         addr -= eth->base; | ||||
|         switch (addr) { | ||||
| 		case R_STAT: | ||||
| 			/* Attach an MDIO/PHY abstraction.  */ | ||||
| 			r = eth->mdio_bus.mdio & 1; | ||||
| 			break; | ||||
|         default: | ||||
| 		r = eth->regs[addr]; | ||||
|                 D(printf ("%s %x p=%x\n", __func__, addr, env->pc)); | ||||
|                 break; | ||||
|         } | ||||
|         return r; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| eth_winvalid (void *opaque, target_phys_addr_t addr, uint32_t value) | ||||
| { | ||||
|         struct fs_eth *eth = opaque; | ||||
|         CPUState *env = eth->env; | ||||
|         cpu_abort(env, "Unsupported short access. reg=%x pc=%x.\n",  | ||||
|                   addr, env->pc); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| eth_writel (void *opaque, target_phys_addr_t addr, uint32_t value) | ||||
| { | ||||
|         struct fs_eth *eth = opaque; | ||||
|         CPUState *env = eth->env; | ||||
| 
 | ||||
|         /* Make addr relative to this instances base.  */ | ||||
|         addr -= eth->base; | ||||
|         switch (addr) | ||||
|         { | ||||
| 		case RW_MGM_CTRL: | ||||
| 			/* Attach an MDIO/PHY abstraction.  */ | ||||
| 			if (value & 2) | ||||
| 				eth->mdio_bus.mdio = value & 1; | ||||
| 			if (eth->mdio_bus.mdc != (value & 4)) | ||||
| 				mdio_cycle(ð->mdio_bus); | ||||
| 			eth->mdio_bus.mdc = !!(value & 4); | ||||
| 			break; | ||||
| 
 | ||||
|                 default: | ||||
|                         printf ("%s %x %x pc=%x\n", | ||||
|                                 __func__, addr, value, env->pc); | ||||
|                         break; | ||||
|         } | ||||
| } | ||||
| 
 | ||||
| static int eth_can_receive(void *opaque) | ||||
| { | ||||
| 	struct fs_eth *eth = opaque; | ||||
| 	int r; | ||||
| 
 | ||||
| 	r = eth->rx_fifo_len == 0; | ||||
| 	if (!r) { | ||||
| 		/* TODO: signal fifo overrun.  */ | ||||
| 		printf("PACKET LOSS!\n"); | ||||
| 	} | ||||
| 	return r; | ||||
| } | ||||
| 
 | ||||
| static void eth_receive(void *opaque, const uint8_t *buf, int size) | ||||
| { | ||||
| 	struct fs_eth *eth = opaque; | ||||
| 	if (size > sizeof(eth->rx_fifo)) { | ||||
| 		/* TODO: signal error.  */ | ||||
| 	} else { | ||||
| 		memcpy(eth->rx_fifo, buf, size); | ||||
| 		/* +4, HW passes the CRC to sw.  */ | ||||
| 		eth->rx_fifo_len = size + 4; | ||||
| 		eth->rx_fifo_pos = 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void eth_rx_pull(void *opaque) | ||||
| { | ||||
| 	struct fs_eth *eth = opaque; | ||||
| 	int len; | ||||
| 	if (eth->rx_fifo_len) {		 | ||||
| 		D(printf("%s %d\n", __func__, eth->rx_fifo_len)); | ||||
| #if 0 | ||||
| 		{ | ||||
| 			int i; | ||||
| 			for (i = 0; i < 32; i++) | ||||
| 				printf("%2.2x", eth->rx_fifo[i]); | ||||
| 			printf("\n"); | ||||
| 		} | ||||
| #endif | ||||
| 		len = etraxfs_dmac_input(eth->dma_in, | ||||
| 					 eth->rx_fifo + eth->rx_fifo_pos,  | ||||
| 					 eth->rx_fifo_len, 1); | ||||
| 		eth->rx_fifo_len -= len; | ||||
| 		eth->rx_fifo_pos += len; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static int eth_tx_push(void *opaque, unsigned char *buf, int len) | ||||
| { | ||||
| 	struct fs_eth *eth = opaque; | ||||
| 
 | ||||
| 	D(printf("%s buf=%p len=%d\n", __func__, buf, len)); | ||||
| 	qemu_send_packet(eth->vc, buf, len); | ||||
| 	return len; | ||||
| } | ||||
| 
 | ||||
| static CPUReadMemoryFunc *eth_read[] = { | ||||
|     ð_rinvalid, | ||||
|     ð_rinvalid, | ||||
|     ð_readl, | ||||
| }; | ||||
| 
 | ||||
| static CPUWriteMemoryFunc *eth_write[] = { | ||||
|     ð_winvalid, | ||||
|     ð_winvalid, | ||||
|     ð_writel, | ||||
| }; | ||||
| 
 | ||||
| void *etraxfs_eth_init(NICInfo *nd, CPUState *env,  | ||||
| 		       qemu_irq *irq, target_phys_addr_t base) | ||||
| { | ||||
| 	struct etraxfs_dma_client *dma = NULL;	 | ||||
| 	struct fs_eth *eth = NULL; | ||||
| 
 | ||||
| 	dma = qemu_mallocz(sizeof *dma * 2); | ||||
| 	if (!dma) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	eth = qemu_mallocz(sizeof *eth); | ||||
| 	if (!eth) | ||||
| 		goto err; | ||||
| 
 | ||||
| 	dma[0].client.push = eth_tx_push; | ||||
| 	dma[0].client.opaque = eth; | ||||
| 	dma[1].client.opaque = eth; | ||||
| 	dma[1].client.pull = eth_rx_pull; | ||||
| 
 | ||||
| 	eth->env = env; | ||||
| 	eth->base = base; | ||||
| 	eth->irq = irq; | ||||
| 	eth->dma_out = dma; | ||||
| 	eth->dma_in = dma + 1; | ||||
| 	memcpy(eth->macaddr, nd->macaddr, 6); | ||||
| 
 | ||||
| 	/* Connect the phy.  */ | ||||
| 	tdk_init(ð->phy); | ||||
| 	mdio_attach(ð->mdio_bus, ð->phy, 0x1); | ||||
| 
 | ||||
| 	eth->ethregs = cpu_register_io_memory(0, eth_read, eth_write, eth); | ||||
| 	cpu_register_physical_memory (base, 0x5c, eth->ethregs); | ||||
| 
 | ||||
| 	eth->vc = qemu_new_vlan_client(nd->vlan,  | ||||
| 				       eth_receive, eth_can_receive, eth); | ||||
| 
 | ||||
| 	return dma; | ||||
|   err: | ||||
| 	qemu_free(eth); | ||||
| 	qemu_free(dma); | ||||
| 	return NULL; | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 edgar_igl
						edgar_igl