/*
* SGI IOC3 master driver and IRQ demuxer
*
* Copyright (c) 2005 Stanislaw Skowronek <skylark@linux-mips.org>
* Heavily based on similar work by:
* Brent Casavant <bcasavan@sgi.com> - IOC4 master driver
* Pat Gefre <pfg@sgi.com> - IOC3 serial port IRQ demuxer
*/
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/ioc3.h>
#include <linux/rwsem.h>
#define IOC3_PCI_SIZE 0x100000
static LIST_HEAD(ioc3_devices);
static int ioc3_counter;
static DECLARE_RWSEM(ioc3_devices_rwsem);
static struct ioc3_submodule *ioc3_submodules[IOC3_MAX_SUBMODULES];
static struct ioc3_submodule *ioc3_ethernet;
static rwlock_t ioc3_submodules_lock = RW_LOCK_UNLOCKED;
/* NIC probing code */
#define GPCR_MLAN_EN 0x00200000 /* enable MCR to pin 8 */
static inline unsigned mcr_pack(unsigned pulse, unsigned sample)
{
return (pulse << 10) | (sample << 2);
}
static int nic_wait(struct ioc3_driver_data *idd)
{
unsigned mcr;
do {
mcr = readl(&idd->vma->mcr);
} while (!(mcr & 2));
return mcr & 1;
}
static int nic_reset(struct ioc3_driver_data *idd)
{
int presence;
unsigned long flags;
local_irq_save(flags);
writel(mcr_pack(500, 65), &idd->vma->mcr);
presence = nic_wait(idd);
local_irq_restore(flags);
udelay(500);
return presence;
}
static int nic_read_bit(struct ioc3_driver_data *idd)
{
int result;
unsigned long flags;
local_irq_save(flags);
writel(mcr_pack(6, 13), &idd->vma->mcr);
result = nic_wait(idd);
local_irq_restore(flags);
udelay(500);
return result;
}
static void nic_write_bit(struct ioc3_driver_data *idd, int bit)
{
if (bit)
writel(mcr_pack(6, 110), &idd->vma->mcr);
else
writel(mcr_pack(80, 30), &idd->vma->mcr);
nic_wait(idd);
}
static unsigned nic_read_byte(struct ioc3_driver_data *idd)
{
unsigned result = 0;
int i;
for (i = 0; i < 8; i++)
result = (result >> 1) | (nic_read_bit(idd) << 7);
return result;
}
static void nic_write_byte(struct ioc3_driver_data *idd, int byte)
{
int i, bit;
for (i = 8; i; i--) {
bit = byte & 1;
byte >>= 1;
nic_write_bit(idd, bit);
}
}
static unsigned long
nic_find(struct ioc3_driver_data *idd, int *last, unsigned long addr)
{
int a, b, index, disc;
nic_reset(idd);
/* Search ROM. */
nic_write_byte(idd, 0xF0);
/* Algorithm from ``Book of iButton Standards''. */
for (index = 0, disc = 0; index < 64; index++) {
a = nic_read_bit(idd);
b = nic_read_bit(idd);
if (a && b) {
printk(KERN_WARNING "IOC3 NIC search failed.\n");
*last = 0;
return 0;
}
if (!a && !b) {
if (index == *last) {
addr |= 1UL << index;
} else if (index > *last) {
addr &= ~(1UL << index);
disc = index;
} else if ((addr & (1UL << index)) == 0)
disc = index;
nic_write_bit(idd, (addr>>index)&1);
continue;
} else {
if (a)
addr |= 1UL << index;
else
addr &= ~(1UL << index);
nic_write_bit(idd, a);
continue;
}
}
*last = disc;
return addr;
}
static void nic_addr(struct ioc3_driver_data *idd, unsigned long addr)
{
int index;
nic_reset(idd);
nic_write_byte(idd, 0xF0);
for (index = 0; index < 64; index++) {
nic_read_bit(idd);
nic_read_bit(idd);
nic_write_bit(idd, (addr>>index)&1);
}
}
static void crc16_byte(unsigned int *crc, unsigned char db)
{
int i;
for(i=0;i<8;i++) {
*crc <<= 1;
if((db^(*crc>>16)) & 1)
*crc ^= 0x8005;