/*
 * Alpha IO and memory functions.. Just expand the inlines in the header
 * files..
 */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>

#include <asm/io.h>

unsigned int _inb(unsigned long addr)
{
	return __inb(addr);
}

unsigned int _inw(unsigned long addr)
{
	return __inw(addr);
}

unsigned int _inl(unsigned long addr)
{
	return __inl(addr);
}


void _outb(unsigned char b, unsigned long addr)
{
	__outb(b, addr);
}

void _outw(unsigned short b, unsigned long addr)
{
	__outw(b, addr);
}

void _outl(unsigned int b, unsigned long addr)
{
	__outl(b, addr);
}

unsigned long ___raw_readb(unsigned long addr)
{
	return __readb(addr);
}

unsigned long ___raw_readw(unsigned long addr)
{
	return __readw(addr);
}

unsigned long ___raw_readl(unsigned long addr)
{
	return __readl(addr);
}

unsigned long ___raw_readq(unsigned long addr)
{
	return __readq(addr);
}

unsigned long _readb(unsigned long addr)
{
	unsigned long r = __readb(addr);
	mb();
	return r;
}

unsigned long _readw(unsigned long addr)
{
	unsigned long r = __readw(addr);
	mb();
	return r;
}

unsigned long _readl(unsigned long addr)
{
	unsigned long r = __readl(addr);
	mb();
	return r;
}

unsigned long _readq(unsigned long addr)
{
	unsigned long r = __readq(addr);
	mb();
	return r;
}

void ___raw_writeb(unsigned char b, unsigned long addr)
{
	__writeb(b, addr);
}

void ___raw_writew(unsigned short b, unsigned long addr)
{
	__writew(b, addr);
}

void ___raw_writel(unsigned int b, unsigned long addr)
{
	__writel(b, addr);
}

void ___raw_writeq(unsigned long b, unsigned long addr)
{
	__writeq(b, addr);
}

void _writeb(unsigned char b, unsigned long addr)
{
	__writeb(b, addr);
	mb();
}

void _writew(unsigned short b, unsigned long addr)
{
	__writew(b, addr);
	mb();
}

void _writel(unsigned int b, unsigned long addr)
{
	__writel(b, addr);
	mb();
}

void _writeq(unsigned long b, unsigned long addr)
{
	__writeq(b, addr);
	mb();
}

/*
 * Read COUNT 8-bit bytes from port PORT into memory starting at
 * SRC.
 */
void insb (unsigned long port, void *dst, unsigned long count)
{
	while (((unsigned long)dst) & 0x3) {
		if (!count)
			return;
		count--;
		*(unsigned char *) dst = inb(port);
		((unsigned char *) dst)++;
	}

	while (count >= 4) {
		unsigned int w;
		count -= 4;
		w = inb(port);
		w |= inb(port) << 8;
		w |= inb(port) << 16;
		w |= inb(port) << 24;
		*(unsigned int *) dst = w;
		((unsigned int *) dst)++;
	}

	while (count) {
		--count;
		*(unsigned char *) dst = inb(port);
		((unsigned char *) dst)++;
	}
}


/*
 * Read COUNT 16-bit words from port PORT into memory starting at
 * SRC.  SRC must be at least short aligned.  This is used by the
 * IDE driver to read disk sectors.  Performance is important, but
 * the interfaces seems to be slow: just using the inlined version
 * of the inw() breaks things.
 */
void insw (unsigned long port, void *dst, unsigned long count)
{
	if (((unsigned long)dst) & 0x3) {
		if (((unsigned long)dst) & 0x1) {
			panic("insw: memory not short aligned");
		}
		if (!count)
			return;
		count--;
		*(unsigned short* ) dst = inw(port);
		((unsigned short *) dst)++;
	}

	while (count >= 2) {
		unsigned int w;
		count -= 2;
		w = inw(port);
		w |= inw(port) << 16;
		*(unsigned int *) dst = w;
		((unsigned int *) dst)++;
	}

	if (count) {
		*(unsigned short*) dst = inw(port);
	}
}


/*
 * Read COUNT 32-bit words from port PORT into memory starting at
 * SRC. Now works with any alignment in SRC. Performance is important,
 * but the interfaces seems to be slow: just using the inlined version
 * of the inl() breaks things.
 */
void insl (unsigned long port, void *dst, unsigned long count)
{
	unsigned int l = 0, l2;
	
	if (!count)
		return;
	
	switch (((unsigned long) dst) & 0x3)
	{
	 case 0x00:			/* Buffer 32-bit aligned */
		while (count--)
		{
			*(unsigned int *) dst = inl(port);
			((unsigned int *) dst)++;
		}
		break;
	
	/* Assuming little endian Alphas in cases 0x01 -- 0x03 ... */
	
	 case 0x02:			/* Buffer 16-bit aligned */
		--count;
		
		l = inl(port);
		*(unsigned short *) dst = l;
		((unsigned short *) dst)++;
		
		while (count--)
		{
			l2 = inl(port);
			*(unsigned int *) dst = l >> 16 | l2 << 16;
			((unsigned int *) dst)++;
			l = l2;
		}
		*(unsigned short *) dst = l >> 16;
		break;
	 case 0x01:			/* Buffer 8-bit aligned */
		--count;
		
		l = inl(port);
		*(unsigned char *) dst = l;
		((unsigned char *) dst)++;
		*(unsigned short *) dst = l >> 8;
		((unsigned short *) dst)++;
		while (count--)
		{
			l2 = inl(port);
			*(unsigned int *) dst = l >> 24 | l2 << 8;
			((unsigned int *) dst)++;
			l = l2;
		}
		*(unsigned char *) dst = l >> 24;
		break;
	 case 0x03:			/* Buffer 8-bit aligned */
		--count;
		
		l = inl(port);
		*(unsigned char *) dst = l;
		((unsigned char *) dst)++;
		while (count--)
		{
			l2 = inl(port);
			*(unsigned int *) dst = l << 24 | l2 >> 8;
			((unsigned int *) dst)++;
			l = l2;
		}
		*(unsigned short *) dst = l >> 8;
		((unsigned short *) dst)++;
		*(unsigned char *) dst = l >> 24;
		break;
	}
}


/*
 * Like insb but in the opposite direction.
 * Don't worry as much about doing aligned memory transfers:
 * doing byte reads the "slow" way isn't nearly as slow as
 * doing byte writes the slow way (no r-m-w cycle).
 */
void outsb(unsigned long port, const void * src, unsigned long count)
{
	while (count) {
		count--;
		outb(*(char *)src, port);
		((char *) src)++;
	}
}

/*
 * Like insw but in the opposite direction.  This is used by the IDE
 * driver to write disk sectors.  Performance is important, but the
 * interfaces seems to be slow: just using the inlined version of the
 * outw() breaks things.
 */
void outsw (unsigned long port, const void *src, unsigned long count)
{
	if (((unsigned long)src) & 0x3) {
		if (((unsigned long)src) & 0x1) {
			panic("outsw: memory not short aligned");
		}
		outw(*(unsigned short*)src, port);
		((unsigned short *) src)++;
		--count;
	}

	while (count >= 2) {
		unsigned int w;
		count -= 2;
		w = *(unsigned int *) src;
		((unsigned int *) src)++;
		outw(w >>  0, port);
		outw(w >> 16, port);
	}

	if (count) {
		outw(*(unsigned short *) src, port);
	}
}


/*
 * Like insl but in the opposite direction.  This is used by the IDE
 * driver to write disk sectors.  Works with any alignment in SRC.
 *  Performance is important, but the interfaces seems to be slow:
 * just using the inlined version of the outl() breaks things.
 */
void outsl (unsigned long port, const void *src, unsigned long count)
{
	unsigned int l = 0, l2;
	
	if (!count)
		return;
	
	switch (((unsigned long) src) & 0x3)
	{
	 case 0x00:			/* Buffer 32-bit aligned */
		while (count--)
		{
			outl(*(unsigned int *) src, port);
			((unsigned int *) src)++;
		}
		break;
	
	/* Assuming little endian Alphas in cases 0x01 -- 0x03 ... */
	
	 case 0x02:			/* Buffer 16-bit aligned */
		--count;
		
		l = *(unsigned short *) src << 16;
		((unsigned short *) src)++;
		
		while (count--)
		{
			l2 = *(unsigned int *) src;
			((unsigned int *) src)++;
			outl (l >> 16 | l2 << 16, port);
			l = l2;
		}
		l2 = *(unsigned short *) src;
		outl (l >> 16 | l2 << 16, port);
		break;
	 case 0x01:			/* Buffer 8-bit aligned */
		--count;
		
		l  = *(unsigned char *) src << 8;
		((unsigned char *) src)++;
		l |= *(unsigned short *) src << 16;
		((unsigned short *) src)++;
		while (count--)
		{
			l2 = *(unsigned int *) src;
			((unsigned int *) src)++;
			outl (l >> 8 | l2 << 24, port);
			l = l2;
		}
		l2 = *(unsigned char *) src;
		outl (l >> 8 | l2 << 24, port);
		break;
	 case 0x03:			/* Buffer 8-bit aligned */
		--count;
		
		l  = *(unsigned char *) src << 24;
		((unsigned char *) src)++;
		while (count--)
		{
			l2 = *(unsigned int *) src;
			((unsigned int *) src)++;
			outl (l >> 24 | l2 << 8, port);
			l = l2;
		}
		l2  = *(unsigned short *) src;
		((unsigned short *) src)++;
		l2 |= *(unsigned char *) src << 16;
		outl (l >> 24 | l2 << 8, port);
		break;
	}
}


/*
 * Copy data from IO memory space to "real" memory space.
 * This needs to be optimized.
 */
void _memcpy_fromio(void * to, unsigned long from, long count)
{
	/* Optimize co-aligned transfers.  Everything else gets handled
	   a byte at a time. */

	if (count >= 8 && ((long)to & 7) == (from & 7)) {
		count -= 8;
		do {
			*(u64 *)to = __raw_readq(from);
			count -= 8;
			to += 8;
			from += 8;
		} while (count >= 0);
		count += 8;
	}

	if (count >= 4 && ((long)to & 3) == (from & 3)) {
		count -= 4;
		do {
			*(u32 *)to = __raw_readl(from);
			count -= 4;
			to += 4;
			from += 4;
		} while (count >= 0);
		count += 4;
	}
		
	if (count >= 2 && ((long)to & 1) == (from & 1)) {
		count -= 2;
		do {
			*(u16 *)to = __raw_readw(from);
			count -= 2;
			to += 2;
			from += 2;
		} while (count >= 0);
		count += 2;
	}

	while (count > 0) {
		*(u8 *) to = __raw_readb(from);
		count--;
		to++;
		from++;
	}
}

/*
 * Copy data from "real" memory space to IO memory space.
 * This needs to be optimized.
 */
void _memcpy_toio(unsigned long to, const void * from, long count)
{
	/* Optimize co-aligned transfers.  Everything else gets handled
	   a byte at a time. */
	/* FIXME -- align FROM.  */

	if (count >= 8 && (to & 7) == ((long)from & 7)) {
		count -= 8;
		do {
			__raw_writeq(*(const u64 *)from, to);
			count -= 8;
			to += 8;
			from += 8;
		} while (count >= 0);
		count += 8;
	}

	if (count >= 4 && (to & 3) == ((long)from & 3)) {
		count -= 4;
		do {
			__raw_writel(*(const u32 *)from, to);
			count -= 4;
			to += 4;
			from += 4;
		} while (count >= 0);
		count += 4;
	}
		
	if (count >= 2 && (to & 1) == ((long)from & 1)) {
		count -= 2;
		do {
			__raw_writew(*(const u16 *)from, to);
			count -= 2;
			to += 2;
			from += 2;
		} while (count >= 0);
		count += 2;
	}

	while (count > 0) {
		__raw_writeb(*(const u8 *) from, to);
		count--;
		to++;
		from++;
	}
	mb();
}

/*
 * "memset" on IO memory space.
 */
void _memset_c_io(unsigned long to, unsigned long c, long count)
{
	/* Handle any initial odd byte */
	if (count > 0 && (to & 1)) {
		__raw_writeb(c, to);
		to++;
		count--;
	}

	/* Handle any initial odd halfword */
	if (count >= 2 && (to & 2)) {
		__raw_writew(c, to);
		to += 2;
		count -= 2;
	}

	/* Handle any initial odd word */
	if (count >= 4 && (to & 4)) {
		__raw_writel(c, to);
		to += 4;
		count -= 4;
	}

	/* Handle all full-sized quadwords: we're aligned
	   (or have a small count) */
	count -= 8;
	if (count >= 0) {
		do {
			__raw_writeq(c, to);
			to += 8;
			count -= 8;
		} while (count >= 0);
	}
	count += 8;

	/* The tail is word-aligned if we still have count >= 4 */
	if (count >= 4) {
		__raw_writel(c, to);
		to += 4;
		count -= 4;
	}

	/* The tail is half-word aligned if we have count >= 2 */
	if (count >= 2) {
		__raw_writew(c, to);
		to += 2;
		count -= 2;
	}

	/* And finally, one last byte.. */
	if (count) {
		__raw_writeb(c, to);
	}
	mb();
}

void
scr_memcpyw(u16 *d, const u16 *s, unsigned int count)
{
	if (! __is_ioaddr((unsigned long) s)) {
		/* Source is memory.  */
		if (! __is_ioaddr((unsigned long) d))
			memcpy(d, s, count);
		else
			memcpy_toio(d, s, count);
	} else {
		/* Source is screen.  */
		if (! __is_ioaddr((unsigned long) d))
			memcpy_fromio(d, s, count);
		else {
			/* FIXME: Should handle unaligned ops and
			   operation widening.  */
			count /= 2;
			while (count--) {
				u16 tmp = __raw_readw((unsigned long)(s++));
				__raw_writew(tmp, (unsigned long)(d++));
			}
		}
	}
}
