/*
 * dm9000.c
 *
 * Davicom DM9000A driver for LWIP
 *
 * Author: HY Chu, March 2006
 */

#define USE_ARP_SEMAPHORE

#include "dm9000.h" 

#ifdef ALT_DEBUG
#define	VERB(msg) printf msg
#else
#define	VERB(msg) do { } while (0)
#endif

inline void dm9k_idxout(alt_avalon_dm9k_if * pdev, u_char val)
{
	IOWR(pdev->base_addr, pdev->index_offset, val);
}

inline u_char dm9k_idxin(alt_avalon_dm9k_if * pdev)
{
	return (u_char)(IORD(pdev->base_addr, pdev->index_offset) & 0xff);
}

inline void dm9k_datout(alt_avalon_dm9k_if * pdev, u_char val)
{
	IOWR(pdev->base_addr, pdev->data_offset, val);
}

inline u_char dm9k_datin(alt_avalon_dm9k_if * pdev)
{
	return (u_char)(IORD(pdev->base_addr, pdev->data_offset) & 0xff );
}

inline void dm9k_datoutw(alt_avalon_dm9k_if * pdev, u_short val)
{
	IOWR(pdev->base_addr, pdev->data_offset, val);
}

inline u_short dm9k_datinw(alt_avalon_dm9k_if * pdev)
{
	return (u_short)(IORD(pdev->base_addr, pdev->data_offset) & 0xffff );
}

inline void dm9k_datoutl(alt_avalon_dm9k_if * pdev, u_long val)
{
	IOWR(pdev->base_addr, pdev->data_offset, val);
}

inline u_long dm9k_datinl(alt_avalon_dm9k_if * pdev)
{
	return (u_long)(IORD(pdev->base_addr, pdev->data_offset));
}

inline u_char dm9k_regin(alt_avalon_dm9k_if * pdev, u_char regidx)
{
	dm9k_idxout(pdev, regidx);
	return dm9k_datin(pdev);
}

inline void dm9k_regout(alt_avalon_dm9k_if * pdev, u_char regidx, u_char val)
{
	dm9k_idxout(pdev, regidx);
	dm9k_datout(pdev, val);
}

inline void dm9k_meminw(alt_avalon_dm9k_if * pdev, u_short * pbuffer, int len)
{
	dm9k_idxout(pdev, 0xf2);
	for(len=(len+1)/2;len--;) *pbuffer++ = dm9k_datinw(pdev);
}

inline void dm9k_memoutw(alt_avalon_dm9k_if * pdev, u_short * pbuffer, int len)
{
	dm9k_idxout(pdev, 0xf8);
	for(len=(len+1)/2;len--;) dm9k_datoutw(pdev, *pbuffer++);
}

int dm9k_identify(alt_avalon_dm9k_if * pdev)
{
	// vid(29:28) = 0A:46
	if((dm9k_regin(pdev, 0x28) != 0x46) || (dm9k_regin(pdev, 0x29) != 0x0a)) return -1;
	// pid(2B:2A) = 90:00
	if((dm9k_regin(pdev, 0x2A) != 0x00) || (dm9k_regin(pdev, 0x2B) != 0x90)) return -1;

	return 0;
}


err_t alt_avalon_dm9k_init(struct netif *netif)
{
	int i;
	alt_avalon_dm9k_if * pdev = (alt_avalon_dm9k_if*)netif->state;
	pdev->lwip_dev_list.dev.netif = netif;

	// check private data's availability	
	if (!pdev) return ERR_IF;

	// identify our device
	if(dm9k_identify(pdev) == -1) return ERR_IF;
	
	// specify callback routines
	netif->name[0] = 'd';
	netif->name[1] = 'm';
	netif->output = dm9k_output;
	netif->linkoutput = dm9k_link_output;
	
	// set physical address
	// 
	pdev->hwaddr[5] = IORD(SWITCH_PIO_BASE, 0) & 0xFF;
	for(i=0;i<(netif->hwaddr_len = 6);i++) netif->hwaddr[i] = pdev->hwaddr[i];
	
	// dm9k initialization
	if(dm9k_init(pdev) != ERR_OK || 
		!(pdev->arp_semaphore = sys_sem_new(1)) ||
		!(pdev->tx_semaphore = sys_sem_new(pdev->dm9k_tx_space))
		)
	{
		free(netif->state);
		return ERR_IF;
	} // of dm9k initialization
	
	etharp_init();
	
	return ERR_OK;
}

void alt_avalon_dm9k_rx(alt_lwip_dev * paltdev)
{
	alt_avalon_dm9k_if * pdev = (alt_avalon_dm9k_if*)(paltdev->netif->state);
	
	// save current index register
	volatile u_char idxold = dm9k_idxin(pdev);
	
	for(;dm9k_input(paltdev->netif););
	
	// resotre old idx register
	dm9k_idxout(pdev, idxold);
}


err_t dm9k_init(alt_avalon_dm9k_if * pdev)
{
	int i;
	
	// 1. power on internal PHY
	dm9k_regout(pdev, 0x1f, 0x00);
	usleep(5000);
	
	// 2. software reset 
	dm9k_regout(pdev, 0x00, 0x03);
	usleep(5000);
	
	// 3. clear Network Stautus Register(NSR) by reading out
	dm9k_regin(pdev, 0x01);
	
	// 4. set MAC address
	for(i=0;i<6;i++) dm9k_regout(pdev, 0x10+i, pdev->hwaddr[i]);
	
	// 4.1 set hash table
	// for(i=0;i<6;i++) dm9k_regout(pdev, 0x16+i, 0xff);
	
	// 5. clear Interrupt Status Register (NSR)
	dm9k_regout(pdev, 0xfe, 0x00);
		
	// 6. register interrupt handler
	alt_irq_register(pdev->irq, (void*)pdev, (void*)dm9k_isr); 

	pdev->dm9k_linked = (dm9k_regin(pdev, 0x01) & 0x40) >> 6;
	
	// 7. interrupt enable, 5: Link, 1: TX, 0: RX
	dm9k_regout(pdev, 0xff, 0xbf);
	//dm9k_regout(pdev, 0xff, ((1<<1)|(1<<0)));
	
	// 8. RX start, 5: Discard long, 4: Discard CRC, 3: All multicast, 2: Runt, 1: Promiscuous, 0: RX enable
	dm9k_regout(pdev, 0x05, ((1<<5)|(1<<4)|(1<<3)|(1<<0)));
	
	return ERR_OK;
}

void dm9k_isr(void * pvoid)
{
	volatile u_char ist, nsr;
	u_char curridx;
	alt_avalon_dm9k_if * pdev = (alt_avalon_dm9k_if*)pvoid;
	
	// save current index port value
	curridx = dm9k_idxin(pdev);

	// 1. handle RX activity
	// read/clear interrupt status register
	dm9k_regout(pdev, 0xfe, (ist = dm9k_regin(pdev, 0xfe)));

{
	if(ist & (1<<2)) VERB(("<ROS>"));
	if(ist & (1<<3)) VERB(("<ROOS>"));
}
	
	// check rx availability, roos(3), ros(2), rx(0)
	if(ist & ((1<<3)|(1<<2)|(1<<0))) sys_mbox_post(rx_mbox, &(pdev->lwip_dev_list.dev));
	
	// 2. check tx completeness
	// read/clear network status register
	dm9k_regout(pdev, 0x01, (nsr = dm9k_regin(pdev, 0x01)));
	
	// update link status
	pdev->dm9k_linked = (nsr & 0x40) >> 6;

	// check completeness
	if(nsr & 0x08) sys_sem_signal(pdev->tx_semaphore);
	if(nsr & 0x04) sys_sem_signal(pdev->tx_semaphore);

	// restore index register
	dm9k_idxout(pdev, curridx);
} // void dm9k_isr()

err_t dm9k_output(struct netif *netif, struct pbuf *p, struct ip_addr *ipaddr)
{
	err_t err;
	alt_avalon_dm9k_if * pdev = (alt_avalon_dm9k_if*)netif->state;
	
	/* 
	* resolve hardware address, then send (or queue) packet 
	* The code which updates the ARP tables does not appear to be thread safe
	* so I've added a MUTEX around all calls to the arp code
	* 
	*/
	
	WAIT_ARP_SEMAPHORE(pdev);
	err = etharp_output(netif, ipaddr, p);
	SIGNAL_ARP_SEMAPHORE(pdev);
	
	return err;
}

err_t dm9k_link_output(struct netif *netif, struct pbuf *p)
{
	alt_avalon_dm9k_if * pdev = (alt_avalon_dm9k_if*)netif->state;
	
	// wait tx resource availability
	sys_sem_wait(pdev->tx_semaphore);

	// fill packet to MAC
	dm9k_memoutw(pdev, (u_short*)(p->payload), p->tot_len);
	
	// specify the length
	dm9k_regout(pdev, 0xfc, (u_char)(p->tot_len & 0xff));
	dm9k_regout(pdev, 0xfd, (u_char)((p->tot_len & 0xff00) >> 8));

	// kick start
	dm9k_regout(pdev, 0x02, 0x01);

	return ERR_OK;
}

int dm9k_input(struct netif *netif)
{
	struct eth_hdr  *ethhdr;
	struct pbuf     *p;
	alt_avalon_dm9k_if * pdev = (alt_avalon_dm9k_if*)netif->state;

	/* move received packet into a new pbuf */
	if(!(p = dm9k_link_input(pdev))) return 0;

	ethhdr = (struct eth_hdr*)(p->payload);

	switch (htons(ethhdr->type)) {
	/* IP packet? */
	case ETHTYPE_IP:
		/* 
		* update ARP table 
		*/

		WAIT_ARP_SEMAPHORE(pdev);
		etharp_ip_input(netif, p);
		SIGNAL_ARP_SEMAPHORE(pdev);
		
		/* skip Ethernet header */
		pbuf_header(p, -(s16_t)sizeof(struct eth_hdr));

		/* pass to network layer */
		netif->input(p, netif);
		
		break;

	case ETHTYPE_ARP:
		/* 
		* pass p to ARP module  
		* 
		*/
		WAIT_ARP_SEMAPHORE(pdev);
		etharp_arp_input(netif, (struct eth_addr *)&netif->hwaddr, p);
		SIGNAL_ARP_SEMAPHORE(pdev);
		break;

	default:
		pbuf_free(p);
		p = NULL;
		break;
	} // of packet type switch
	
	return 1;
} // of alt_avalon_dm9k_rx

struct pbuf * dm9k_link_input(alt_avalon_dm9k_if * pdev)
{
	u_char saveidx;
	
	struct pbuf *p, *q;
	volatile u_short pklen, status;
	u_short remains;
	
	saveidx = dm9k_idxin(pdev);
	
	// check RX availability
	// first, dummy read
	dm9k_idxout(pdev, 0xf0);
	status = dm9k_datinw(pdev);
	// re-read again
	status = dm9k_datinw(pdev);
	if((status & 0xff) != 0x01) 
	{
		dm9k_idxout(pdev, saveidx);
		return NULL;
	}

	// now, read status word and set pointer to increment automatically
	dm9k_idxout(pdev, 0xf2);
	status = dm9k_datinw(pdev);
	
	// read packet length, eliminate 4 byte CRC
	pklen = dm9k_datinw(pdev) - 4;
	
	// allocate buffer
	if(!(p = pbuf_alloc(PBUF_RAW, pklen, PBUF_POOL)))
	{
		// OOOPS!
		// handle pointer adjustment, can add ptr directly, too
		VERB(("<oop>"));
		
		dm9k_idxout(pdev, 0xf2);
		for(remains = (pklen+4+1)/2; remains--;) dm9k_datinw(pdev);
		
		dm9k_idxout(pdev, saveidx);
		return NULL;
	}
	
	// fill packet per chunk
	for(remains=pklen, q = p; remains && q; q = q->next)
	{
		dm9k_meminw(pdev, q->payload, q->len);
		remains -= q->len;
	} // of fill loop

	// finally, read out the 4-byte CRC
	dm9k_datinw(pdev);
	dm9k_datinw(pdev);

#if LINK_STATS
	lwip_stats.link.recv++;
#endif /* LINK_STATS */      

	dm9k_idxout(pdev, saveidx);
	return p;

}
