forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
11 KiB
516 lines
11 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* eisa_enumerator.c - provide support for EISA adapters in PA-RISC machines |
|
* |
|
* Copyright (c) 2002 Daniel Engstrom <[email protected]> |
|
*/ |
|
|
|
#include <linux/ioport.h> |
|
#include <linux/init.h> |
|
#include <linux/kernel.h> |
|
#include <linux/slab.h> |
|
#include <asm/io.h> |
|
#include <linux/uaccess.h> |
|
#include <asm/byteorder.h> |
|
|
|
#include <asm/eisa_bus.h> |
|
#include <asm/eisa_eeprom.h> |
|
|
|
|
|
/* |
|
* Todo: |
|
* |
|
* PORT init with MASK attr and other size than byte |
|
* MEMORY with other decode than 20 bit |
|
* CRC stuff |
|
* FREEFORM stuff |
|
*/ |
|
|
|
#define EPI 0xc80 |
|
#define NUM_SLOT 16 |
|
#define SLOT2PORT(x) (x<<12) |
|
|
|
|
|
/* macros to handle unaligned accesses and |
|
* byte swapping. The data in the EEPROM is |
|
* little-endian on the big-endian PAROSC */ |
|
#define get_8(x) (*(u_int8_t*)(x)) |
|
|
|
static inline u_int16_t get_16(const unsigned char *x) |
|
{ |
|
return (x[1] << 8) | x[0]; |
|
} |
|
|
|
static inline u_int32_t get_32(const unsigned char *x) |
|
{ |
|
return (x[3] << 24) | (x[2] << 16) | (x[1] << 8) | x[0]; |
|
} |
|
|
|
static inline u_int32_t get_24(const unsigned char *x) |
|
{ |
|
return (x[2] << 24) | (x[1] << 16) | (x[0] << 8); |
|
} |
|
|
|
static void print_eisa_id(char *s, u_int32_t id) |
|
{ |
|
char vendor[4]; |
|
int rev; |
|
int device; |
|
|
|
rev = id & 0xff; |
|
id >>= 8; |
|
device = id & 0xff; |
|
id >>= 8; |
|
vendor[3] = '\0'; |
|
vendor[2] = '@' + (id & 0x1f); |
|
id >>= 5; |
|
vendor[1] = '@' + (id & 0x1f); |
|
id >>= 5; |
|
vendor[0] = '@' + (id & 0x1f); |
|
id >>= 5; |
|
|
|
sprintf(s, "%s%02X%02X", vendor, device, rev); |
|
} |
|
|
|
static int configure_memory(const unsigned char *buf, |
|
struct resource *mem_parent, |
|
char *name) |
|
{ |
|
int len; |
|
u_int8_t c; |
|
int i; |
|
struct resource *res; |
|
|
|
len=0; |
|
|
|
for (i=0;i<HPEE_MEMORY_MAX_ENT;i++) { |
|
c = get_8(buf+len); |
|
|
|
if (NULL != (res = kzalloc(sizeof(struct resource), GFP_KERNEL))) { |
|
int result; |
|
|
|
res->name = name; |
|
res->start = mem_parent->start + get_24(buf+len+2); |
|
res->end = res->start + get_16(buf+len+5)*1024; |
|
res->flags = IORESOURCE_MEM; |
|
pr_cont("memory %pR ", res); |
|
result = request_resource(mem_parent, res); |
|
if (result < 0) { |
|
printk(KERN_ERR "EISA Enumerator: failed to claim EISA Bus address space!\n"); |
|
return result; |
|
} |
|
} |
|
|
|
len+=7; |
|
|
|
if (!(c & HPEE_MEMORY_MORE)) { |
|
break; |
|
} |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
static int configure_irq(const unsigned char *buf) |
|
{ |
|
int len; |
|
u_int8_t c; |
|
int i; |
|
|
|
len=0; |
|
|
|
for (i=0;i<HPEE_IRQ_MAX_ENT;i++) { |
|
c = get_8(buf+len); |
|
|
|
pr_cont("IRQ %d ", c & HPEE_IRQ_CHANNEL_MASK); |
|
if (c & HPEE_IRQ_TRIG_LEVEL) { |
|
eisa_make_irq_level(c & HPEE_IRQ_CHANNEL_MASK); |
|
} else { |
|
eisa_make_irq_edge(c & HPEE_IRQ_CHANNEL_MASK); |
|
} |
|
|
|
len+=2; |
|
/* hpux seems to allow for |
|
* two bytes of irq data but only defines one of |
|
* them, I think */ |
|
if (!(c & HPEE_IRQ_MORE)) { |
|
break; |
|
} |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
static int configure_dma(const unsigned char *buf) |
|
{ |
|
int len; |
|
u_int8_t c; |
|
int i; |
|
|
|
len=0; |
|
|
|
for (i=0;i<HPEE_DMA_MAX_ENT;i++) { |
|
c = get_8(buf+len); |
|
pr_cont("DMA %d ", c&HPEE_DMA_CHANNEL_MASK); |
|
/* fixme: maybe initialize the dma channel withthe timing ? */ |
|
len+=2; |
|
if (!(c & HPEE_DMA_MORE)) { |
|
break; |
|
} |
|
} |
|
|
|
return len; |
|
} |
|
|
|
static int configure_port(const unsigned char *buf, struct resource *io_parent, |
|
char *board) |
|
{ |
|
int len; |
|
u_int8_t c; |
|
int i; |
|
struct resource *res; |
|
int result; |
|
|
|
len=0; |
|
|
|
for (i=0;i<HPEE_PORT_MAX_ENT;i++) { |
|
c = get_8(buf+len); |
|
|
|
if (NULL != (res = kzalloc(sizeof(struct resource), GFP_KERNEL))) { |
|
res->name = board; |
|
res->start = get_16(buf+len+1); |
|
res->end = get_16(buf+len+1)+(c&HPEE_PORT_SIZE_MASK)+1; |
|
res->flags = IORESOURCE_IO; |
|
pr_cont("ioports %pR ", res); |
|
result = request_resource(io_parent, res); |
|
if (result < 0) { |
|
printk(KERN_ERR "EISA Enumerator: failed to claim EISA Bus address space!\n"); |
|
return result; |
|
} |
|
} |
|
|
|
len+=3; |
|
if (!(c & HPEE_PORT_MORE)) { |
|
break; |
|
} |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
/* byte 1 and 2 is the port number to write |
|
* and at byte 3 the value to write starts. |
|
* I assume that there are and- and or- masks |
|
* here when HPEE_PORT_INIT_MASK is set but I have |
|
* not yet encountered this. */ |
|
static int configure_port_init(const unsigned char *buf) |
|
{ |
|
int len=0; |
|
u_int8_t c; |
|
|
|
while (len<HPEE_PORT_INIT_MAX_LEN) { |
|
int s=0; |
|
c = get_8(buf+len); |
|
|
|
switch (c & HPEE_PORT_INIT_WIDTH_MASK) { |
|
case HPEE_PORT_INIT_WIDTH_BYTE: |
|
s=1; |
|
if (c & HPEE_PORT_INIT_MASK) { |
|
printk(KERN_WARNING "port_init: unverified mask attribute\n"); |
|
outb((inb(get_16(buf+len+1) & |
|
get_8(buf+len+3)) | |
|
get_8(buf+len+4)), get_16(buf+len+1)); |
|
|
|
} else { |
|
outb(get_8(buf+len+3), get_16(buf+len+1)); |
|
|
|
} |
|
break; |
|
case HPEE_PORT_INIT_WIDTH_WORD: |
|
s=2; |
|
if (c & HPEE_PORT_INIT_MASK) { |
|
printk(KERN_WARNING "port_init: unverified mask attribute\n"); |
|
outw((inw(get_16(buf+len+1)) & |
|
get_16(buf+len+3)) | |
|
get_16(buf+len+5), |
|
get_16(buf+len+1)); |
|
} else { |
|
outw(cpu_to_le16(get_16(buf+len+3)), get_16(buf+len+1)); |
|
} |
|
break; |
|
case HPEE_PORT_INIT_WIDTH_DWORD: |
|
s=4; |
|
if (c & HPEE_PORT_INIT_MASK) { |
|
printk(KERN_WARNING "port_init: unverified mask attribute\n"); |
|
outl((inl(get_16(buf+len+1) & |
|
get_32(buf+len+3)) | |
|
get_32(buf+len+7)), get_16(buf+len+1)); |
|
} else { |
|
outl(cpu_to_le32(get_32(buf+len+3)), get_16(buf+len+1)); |
|
} |
|
|
|
break; |
|
default: |
|
printk(KERN_ERR "Invalid port init word %02x\n", c); |
|
return 0; |
|
} |
|
|
|
if (c & HPEE_PORT_INIT_MASK) { |
|
s*=2; |
|
} |
|
|
|
len+=s+3; |
|
if (!(c & HPEE_PORT_INIT_MORE)) { |
|
break; |
|
} |
|
} |
|
|
|
return len; |
|
} |
|
|
|
static int configure_choise(const unsigned char *buf, u_int8_t *info) |
|
{ |
|
int len; |
|
|
|
/* theis record contain the value of the functions |
|
* configuration choises and an info byte which |
|
* describes which other records to expect in this |
|
* function */ |
|
len = get_8(buf); |
|
*info=get_8(buf+len+1); |
|
|
|
return len+2; |
|
} |
|
|
|
static int configure_type_string(const unsigned char *buf) |
|
{ |
|
int len; |
|
|
|
/* just skip past the type field */ |
|
len = get_8(buf); |
|
if (len > 80) { |
|
printk(KERN_ERR "eisa_enumerator: type info field too long (%d, max is 80)\n", len); |
|
} |
|
|
|
return 1+len; |
|
} |
|
|
|
static int configure_function(const unsigned char *buf, int *more) |
|
{ |
|
/* the init field seems to be a two-byte field |
|
* which is non-zero if there are an other function following |
|
* I think it is the length of the function def |
|
*/ |
|
*more = get_16(buf); |
|
|
|
return 2; |
|
} |
|
|
|
static int parse_slot_config(int slot, |
|
const unsigned char *buf, |
|
struct eeprom_eisa_slot_info *es, |
|
struct resource *io_parent, |
|
struct resource *mem_parent) |
|
{ |
|
int res=0; |
|
int function_len; |
|
unsigned int pos=0; |
|
unsigned int maxlen; |
|
int num_func=0; |
|
u_int8_t flags; |
|
int p0; |
|
|
|
char *board; |
|
int id_string_used=0; |
|
|
|
if (NULL == (board = kmalloc(8, GFP_KERNEL))) { |
|
return -1; |
|
} |
|
print_eisa_id(board, es->eisa_slot_id); |
|
printk(KERN_INFO "EISA slot %d: %s %s ", |
|
slot, board, es->flags&HPEE_FLAG_BOARD_IS_ISA ? "ISA" : "EISA"); |
|
|
|
maxlen = es->config_data_length < HPEE_MAX_LENGTH ? |
|
es->config_data_length : HPEE_MAX_LENGTH; |
|
while ((pos < maxlen) && (num_func <= es->num_functions)) { |
|
pos+=configure_function(buf+pos, &function_len); |
|
|
|
if (!function_len) { |
|
break; |
|
} |
|
num_func++; |
|
p0 = pos; |
|
pos += configure_choise(buf+pos, &flags); |
|
|
|
if (flags & HPEE_FUNCTION_INFO_F_DISABLED) { |
|
/* function disabled, skip silently */ |
|
pos = p0 + function_len; |
|
continue; |
|
} |
|
if (flags & HPEE_FUNCTION_INFO_CFG_FREE_FORM) { |
|
/* I have no idea how to handle this */ |
|
printk("function %d have free-form configuration, skipping ", |
|
num_func); |
|
pos = p0 + function_len; |
|
continue; |
|
} |
|
|
|
/* the ordering of the sections need |
|
* more investigation. |
|
* Currently I think that memory comaed before IRQ |
|
* I assume the order is LSB to MSB in the |
|
* info flags |
|
* eg type, memory, irq, dma, port, HPEE_PORT_init |
|
*/ |
|
|
|
if (flags & HPEE_FUNCTION_INFO_HAVE_TYPE) { |
|
pos += configure_type_string(buf+pos); |
|
} |
|
|
|
if (flags & HPEE_FUNCTION_INFO_HAVE_MEMORY) { |
|
id_string_used=1; |
|
pos += configure_memory(buf+pos, mem_parent, board); |
|
} |
|
|
|
if (flags & HPEE_FUNCTION_INFO_HAVE_IRQ) { |
|
pos += configure_irq(buf+pos); |
|
} |
|
|
|
if (flags & HPEE_FUNCTION_INFO_HAVE_DMA) { |
|
pos += configure_dma(buf+pos); |
|
} |
|
|
|
if (flags & HPEE_FUNCTION_INFO_HAVE_PORT) { |
|
id_string_used=1; |
|
pos += configure_port(buf+pos, io_parent, board); |
|
} |
|
|
|
if (flags & HPEE_FUNCTION_INFO_HAVE_PORT_INIT) { |
|
pos += configure_port_init(buf+pos); |
|
} |
|
|
|
if (p0 + function_len < pos) { |
|
printk(KERN_ERR "eisa_enumerator: function %d length mis-match " |
|
"got %d, expected %d\n", |
|
num_func, pos-p0, function_len); |
|
res=-1; |
|
break; |
|
} |
|
pos = p0 + function_len; |
|
} |
|
pr_cont("\n"); |
|
if (!id_string_used) { |
|
kfree(board); |
|
} |
|
|
|
if (pos != es->config_data_length) { |
|
printk(KERN_ERR "eisa_enumerator: config data length mis-match got %d, expected %d\n", |
|
pos, es->config_data_length); |
|
res=-1; |
|
} |
|
|
|
if (num_func != es->num_functions) { |
|
printk(KERN_ERR "eisa_enumerator: number of functions mis-match got %d, expected %d\n", |
|
num_func, es->num_functions); |
|
res=-2; |
|
} |
|
|
|
return res; |
|
|
|
} |
|
|
|
static int init_slot(int slot, struct eeprom_eisa_slot_info *es) |
|
{ |
|
unsigned int id; |
|
|
|
char id_string[8]; |
|
|
|
if (!(es->slot_info&HPEE_SLOT_INFO_NO_READID)) { |
|
/* try to read the id of the board in the slot */ |
|
id = le32_to_cpu(inl(SLOT2PORT(slot)+EPI)); |
|
|
|
if (0xffffffff == id) { |
|
/* Maybe we didn't expect a card to be here... */ |
|
if (es->eisa_slot_id == 0xffffffff) |
|
return -1; |
|
|
|
/* this board is not here or it does not |
|
* support readid |
|
*/ |
|
printk(KERN_ERR "EISA slot %d a configured board was not detected (", |
|
slot); |
|
|
|
print_eisa_id(id_string, es->eisa_slot_id); |
|
printk(" expected %s)\n", id_string); |
|
|
|
return -1; |
|
|
|
} |
|
if (es->eisa_slot_id != id) { |
|
print_eisa_id(id_string, id); |
|
printk(KERN_ERR "EISA slot %d id mis-match: got %s", |
|
slot, id_string); |
|
|
|
print_eisa_id(id_string, es->eisa_slot_id); |
|
printk(" expected %s\n", id_string); |
|
|
|
return -1; |
|
|
|
} |
|
} |
|
|
|
/* now: we need to enable the board if |
|
* it supports enabling and run through |
|
* the port init sction if present |
|
* and finally record any interrupt polarity |
|
*/ |
|
if (es->slot_features & HPEE_SLOT_FEATURES_ENABLE) { |
|
/* enable board */ |
|
outb(0x01| inb(SLOT2PORT(slot)+EPI+4), |
|
SLOT2PORT(slot)+EPI+4); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
int eisa_enumerator(unsigned long eeprom_addr, |
|
struct resource *io_parent, struct resource *mem_parent) |
|
{ |
|
int i; |
|
struct eeprom_header *eh; |
|
static char eeprom_buf[HPEE_MAX_LENGTH]; |
|
|
|
for (i=0; i < HPEE_MAX_LENGTH; i++) { |
|
eeprom_buf[i] = gsc_readb(eeprom_addr+i); |
|
} |
|
|
|
printk(KERN_INFO "Enumerating EISA bus\n"); |
|
|
|
eh = (struct eeprom_header*)(eeprom_buf); |
|
for (i=0;i<eh->num_slots;i++) { |
|
struct eeprom_eisa_slot_info *es; |
|
|
|
es = (struct eeprom_eisa_slot_info*) |
|
(&eeprom_buf[HPEE_SLOT_INFO(i)]); |
|
|
|
if (-1==init_slot(i+1, es)) { |
|
continue; |
|
} |
|
|
|
if (es->config_data_offset < HPEE_MAX_LENGTH) { |
|
if (parse_slot_config(i+1, &eeprom_buf[es->config_data_offset], |
|
es, io_parent, mem_parent)) { |
|
return -1; |
|
} |
|
} else { |
|
printk (KERN_WARNING "EISA EEPROM offset 0x%x out of range\n",es->config_data_offset); |
|
return -1; |
|
} |
|
} |
|
return eh->num_slots; |
|
} |
|
|
|
|