509 lines
12 KiB
C
509 lines
12 KiB
C
/*
|
|
* OpenBIOS SBus driver
|
|
*
|
|
* (C) 2004 Stefan Reinauer
|
|
* (C) 2005 Ed Schouten <ed@fxq.nl>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "libopenbios/bindings.h"
|
|
#include "kernel/kernel.h"
|
|
#include "libc/byteorder.h"
|
|
#include "libc/vsprintf.h"
|
|
#include "drivers/drivers.h"
|
|
#include "libopenbios/ofmem.h"
|
|
#include "libopenbios/video.h"
|
|
|
|
#define SBUS_REGS 0x28
|
|
#define SBUS_SLOTS 16
|
|
#define APC_REGS 0x10
|
|
#define APC_OFFSET 0x0a000000ULL
|
|
#define CS4231_REGS 0x40
|
|
#define CS4231_OFFSET 0x0c000000ULL
|
|
#define MACIO_ESPDMA 0x00400000ULL /* ESP DMA controller */
|
|
#define MACIO_ESP 0x00800000ULL /* ESP SCSI */
|
|
#define SS600MP_ESPDMA 0x00081000ULL
|
|
#define SS600MP_ESP 0x00080000ULL
|
|
#define SS600MP_LEBUFFER (SS600MP_ESPDMA + 0x10) // XXX should be 0x40000
|
|
#define LEDMA_REGS 0x4
|
|
#define LE_REGS 0x20
|
|
|
|
#ifdef CONFIG_DEBUG_SBUS
|
|
#define DPRINTF(fmt, args...) \
|
|
do { printk(fmt , ##args); } while (0)
|
|
#else
|
|
#define DPRINTF(fmt, args...)
|
|
#endif
|
|
|
|
typedef struct le_private {
|
|
uint32_t *dmaregs;
|
|
uint32_t *regs;
|
|
} le_private_t;
|
|
|
|
static void
|
|
ob_sbus_node_init(uint64_t base)
|
|
{
|
|
void *regs;
|
|
|
|
push_str("/iommu/sbus");
|
|
fword("find-device");
|
|
|
|
PUSH(base >> 32);
|
|
fword("encode-int");
|
|
PUSH(base & 0xffffffff);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(SBUS_REGS);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("reg");
|
|
fword("property");
|
|
|
|
regs = (void *)ofmem_map_io(base, SBUS_REGS);
|
|
PUSH((unsigned long)regs);
|
|
fword("encode-int");
|
|
push_str("address");
|
|
fword("property");
|
|
}
|
|
|
|
static void
|
|
ob_le_init(unsigned int slot, uint64_t base, unsigned long leoffset, unsigned long dmaoffset)
|
|
{
|
|
le_private_t *le;
|
|
|
|
le = malloc(sizeof(le_private_t));
|
|
if (!le) {
|
|
DPRINTF("Can't allocate LANCE private structure\n");
|
|
return;
|
|
}
|
|
|
|
/* Get the IO region for DMA registers */
|
|
le->dmaregs = (void *)ofmem_map_io(base + (uint64_t)dmaoffset, LEDMA_REGS);
|
|
if (le->dmaregs == NULL) {
|
|
DPRINTF("Can't map LANCE DMA registers\n");
|
|
return;
|
|
}
|
|
|
|
/* Now it appears that the Solaris kernel forgets to set up the LANCE DMA mapping
|
|
and so it must inherit the one from OpenBIOS. The symptom of this is that the
|
|
LANCE DMA base addr register is still zero, and so we start sending network
|
|
packets containing random areas of memory.
|
|
|
|
The correct fix for this should be to use dvma_alloc() to grab a section of
|
|
memory and point the LANCE DMA buffers to use that instead; this gets
|
|
slightly further but still crashes. Time-consuming investigation on various
|
|
hacked versions of QEMU seems to indicate that Solaris always assumes the LANCE
|
|
DMA base address is fixed 0xff000000 when setting up the IOMMU for the LANCE
|
|
card. Hence we imitate this behaviour here. */
|
|
le->dmaregs[3] = 0xff000000;
|
|
|
|
push_str("/iommu/sbus/ledma");
|
|
fword("find-device");
|
|
PUSH(slot);
|
|
fword("encode-int");
|
|
PUSH(dmaoffset);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(0x00000020);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("reg");
|
|
fword("property");
|
|
|
|
/* Get the IO region for Lance registers */
|
|
le->regs = (void *)ofmem_map_io(base + (uint64_t)leoffset, LE_REGS);
|
|
if (le->regs == NULL) {
|
|
DPRINTF("Can't map LANCE registers\n");
|
|
return;
|
|
}
|
|
|
|
push_str("/iommu/sbus/ledma/le");
|
|
fword("find-device");
|
|
PUSH(slot);
|
|
fword("encode-int");
|
|
PUSH(leoffset);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(0x00000004);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("reg");
|
|
fword("property");
|
|
}
|
|
|
|
uint16_t graphic_depth;
|
|
|
|
#if !defined(CONFIG_QEMU)
|
|
static void
|
|
ob_tcx_init(unsigned int slot, const char *path)
|
|
{
|
|
char buf[6];
|
|
|
|
printk("No display device located during SBus probe - falling back to internal TCX driver\n");
|
|
|
|
/* Make the sbus node the current instance and active package for probing */
|
|
feval("active-package my-self");
|
|
push_str("/iommu/sbus");
|
|
feval("2dup find-device open-dev to my-self");
|
|
|
|
fword("new-device");
|
|
PUSH(0);
|
|
PUSH(0);
|
|
snprintf(buf, 6, "%x,0", slot);
|
|
push_str(buf);
|
|
fword("set-args");
|
|
feval("['] tcx-driver-fcode 2 cells + 1 byte-load");
|
|
fword("finish-device");
|
|
|
|
/* Restore */
|
|
feval("to my-self active-package!");
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
ob_apc_init(unsigned int slot, unsigned long base)
|
|
{
|
|
push_str("/iommu/sbus");
|
|
fword("find-device");
|
|
fword("new-device");
|
|
|
|
push_str("power-management");
|
|
fword("device-name");
|
|
|
|
PUSH(slot);
|
|
fword("encode-int");
|
|
PUSH(base);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(APC_REGS);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("reg");
|
|
fword("property");
|
|
|
|
fword("finish-device");
|
|
}
|
|
|
|
static void
|
|
ob_cs4231_init(unsigned int slot)
|
|
{
|
|
push_str("/iommu/sbus");
|
|
fword("find-device");
|
|
fword("new-device");
|
|
|
|
push_str("SUNW,CS4231");
|
|
fword("device-name");
|
|
|
|
push_str("serial");
|
|
fword("device-type");
|
|
|
|
PUSH(slot);
|
|
fword("encode-int");
|
|
PUSH(CS4231_OFFSET);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(CS4231_REGS);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("reg");
|
|
fword("property");
|
|
|
|
PUSH(5);
|
|
fword("encode-int");
|
|
PUSH(0);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("intr");
|
|
fword("property");
|
|
|
|
PUSH(5);
|
|
fword("encode-int");
|
|
push_str("interrupts");
|
|
fword("property");
|
|
|
|
push_str("audio");
|
|
fword("encode-string");
|
|
push_str("alias");
|
|
fword("property");
|
|
|
|
fword("finish-device");
|
|
}
|
|
|
|
static void
|
|
ob_macio_init(unsigned int slot, uint64_t base, unsigned long offset)
|
|
{
|
|
// All devices were integrated to NCR89C100, see
|
|
// http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
|
|
|
|
// NCR 53c9x, aka ESP. See
|
|
// http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR53C9X.txt
|
|
#ifdef CONFIG_DRIVER_ESP
|
|
ob_esp_init(slot, base, offset + MACIO_ESP, offset + MACIO_ESPDMA);
|
|
#endif
|
|
|
|
// NCR 92C990, Am7990, Lance. See http://www.amd.com
|
|
ob_le_init(slot, base, offset + 0x00c00000, offset + 0x00400010);
|
|
|
|
// Parallel port
|
|
//ob_bpp_init(base);
|
|
}
|
|
|
|
static void
|
|
sbus_probe_self(unsigned int slot, unsigned long offset)
|
|
{
|
|
/* Wrapper for calling probe-self in Forth. This is mainly because some
|
|
drivers don't handle properties correctly when the sbus node is set
|
|
as the current instance during probe. */
|
|
char buf[6];
|
|
|
|
printk("Probing SBus slot %d offset %ld\n", slot, offset);
|
|
|
|
/* Make the sbus node the current instance and active package for probing */
|
|
feval("active-package my-self");
|
|
push_str("/iommu/sbus");
|
|
feval("open-dev to my-self");
|
|
|
|
PUSH(0);
|
|
PUSH(0);
|
|
snprintf(buf, 6, "%x,%lx", slot, offset);
|
|
push_str(buf);
|
|
fword("2dup");
|
|
fword("probe-self-sbus");
|
|
|
|
/* Restore */
|
|
feval("to my-self active-package!");
|
|
}
|
|
|
|
static int
|
|
sbus_probe_sucess(void)
|
|
{
|
|
/* Return true if the last sbus_probe_self() resulted in
|
|
the successful detection and execution of FCode */
|
|
fword("probe-fcode?");
|
|
return POP();
|
|
}
|
|
|
|
static void
|
|
sbus_probe_slot_ss5(unsigned int slot, uint64_t base)
|
|
{
|
|
/* Probe the slot */
|
|
sbus_probe_self(slot, 0);
|
|
|
|
/* If the device was successfully created by FCode then do nothing */
|
|
if (sbus_probe_sucess()) {
|
|
return;
|
|
}
|
|
|
|
switch(slot) {
|
|
#if !defined(CONFIG_QEMU)
|
|
/* QEMU always uses the FCode driver */
|
|
case 3: // SUNW,tcx
|
|
ob_tcx_init(slot, "/iommu/sbus/SUNW,tcx");
|
|
break;
|
|
#endif
|
|
case 4:
|
|
// SUNW,CS4231
|
|
ob_cs4231_init(slot);
|
|
// Power management (APC)
|
|
ob_apc_init(slot, APC_OFFSET);
|
|
break;
|
|
case 5: // MACIO: le, esp, bpp
|
|
ob_macio_init(slot, base, 0x08000000);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sbus_probe_slot_ss10(unsigned int slot, uint64_t base)
|
|
{
|
|
/* Probe the slot */
|
|
sbus_probe_self(slot, 0);
|
|
|
|
/* If the device was successfully created by FCode then do nothing */
|
|
if (sbus_probe_sucess()) {
|
|
return;
|
|
}
|
|
|
|
switch(slot) {
|
|
#if !defined(CONFIG_QEMU)
|
|
/* QEMU always uses the FCode driver */
|
|
case 2: // SUNW,tcx
|
|
ob_tcx_init(slot, "/iommu/sbus/SUNW,tcx");
|
|
break;
|
|
#endif
|
|
case 0xf: // le, esp, bpp, power-management
|
|
ob_macio_init(slot, base, 0);
|
|
// Power management (APC) XXX should not exist
|
|
ob_apc_init(slot, APC_OFFSET);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sbus_probe_slot_ss600mp(unsigned int slot, uint64_t base)
|
|
{
|
|
/* Probe the slot */
|
|
sbus_probe_self(slot, 0);
|
|
|
|
/* If the device was successfully created by FCode then do nothing */
|
|
if (sbus_probe_sucess()) {
|
|
return;
|
|
}
|
|
|
|
switch(slot) {
|
|
#if !defined(CONFIG_QEMU)
|
|
/* QEMU always uses the FCode driver */
|
|
case 2: // SUNW,tcx
|
|
ob_tcx_init(slot, "/iommu/sbus/SUNW,tcx");
|
|
break;
|
|
#endif
|
|
case 0xf: // le, esp, bpp, power-management
|
|
#ifdef CONFIG_DRIVER_ESP
|
|
ob_esp_init(slot, base, SS600MP_ESP, SS600MP_ESPDMA);
|
|
#endif
|
|
// NCR 92C990, Am7990, Lance. See http://www.amd.com
|
|
ob_le_init(slot, base, 0x00060000, SS600MP_LEBUFFER);
|
|
// Power management (APC) XXX should not exist
|
|
ob_apc_init(slot, APC_OFFSET);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct sbus_offset {
|
|
int slot, type;
|
|
uint64_t base;
|
|
unsigned long size;
|
|
};
|
|
|
|
static const struct sbus_offset sbus_offsets_ss5[SBUS_SLOTS] = {
|
|
{ 0, 0, 0x20000000, 0x10000000,},
|
|
{ 1, 0, 0x30000000, 0x10000000,},
|
|
{ 2, 0, 0x40000000, 0x10000000,},
|
|
{ 3, 0, 0x50000000, 0x10000000,},
|
|
{ 4, 0, 0x60000000, 0x10000000,},
|
|
{ 5, 0, 0x70000000, 0x10000000,},
|
|
};
|
|
|
|
/* Shared with ss600mp */
|
|
static const struct sbus_offset sbus_offsets_ss10[SBUS_SLOTS] = {
|
|
{ 0, 0, 0xe00000000ULL, 0x10000000,},
|
|
{ 1, 0, 0xe10000000ULL, 0x10000000,},
|
|
{ 2, 0, 0xe20000000ULL, 0x10000000,},
|
|
{ 3, 0, 0xe30000000ULL, 0x10000000,},
|
|
[0xf] = { 0xf, 0, 0xef0000000ULL, 0x10000000,},
|
|
};
|
|
|
|
static void
|
|
ob_add_sbus_range(const struct sbus_offset *range, int notfirst)
|
|
{
|
|
if (!notfirst) {
|
|
push_str("/iommu/sbus");
|
|
fword("find-device");
|
|
}
|
|
PUSH(range->slot);
|
|
fword("encode-int");
|
|
if (notfirst)
|
|
fword("encode+");
|
|
PUSH(range->type);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(range->base >> 32);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(range->base & 0xffffffff);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(range->size);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
}
|
|
|
|
static int
|
|
ob_sbus_init_ss5(void)
|
|
{
|
|
unsigned int slot;
|
|
int notfirst = 0;
|
|
|
|
for (slot = 0; slot < SBUS_SLOTS; slot++) {
|
|
if (sbus_offsets_ss5[slot].size > 0)
|
|
ob_add_sbus_range(&sbus_offsets_ss5[slot], notfirst++);
|
|
}
|
|
push_str("ranges");
|
|
fword("property");
|
|
|
|
for (slot = 0; slot < SBUS_SLOTS; slot++) {
|
|
if (sbus_offsets_ss5[slot].size > 0)
|
|
sbus_probe_slot_ss5(slot, sbus_offsets_ss5[slot].base);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ob_sbus_init_ss10(void)
|
|
{
|
|
unsigned int slot;
|
|
int notfirst = 0;
|
|
|
|
for (slot = 0; slot < SBUS_SLOTS; slot++) {
|
|
if (sbus_offsets_ss10[slot].size > 0)
|
|
ob_add_sbus_range(&sbus_offsets_ss10[slot], notfirst++);
|
|
}
|
|
push_str("ranges");
|
|
fword("property");
|
|
|
|
for (slot = 0; slot < SBUS_SLOTS; slot++) {
|
|
if (sbus_offsets_ss10[slot].size > 0)
|
|
sbus_probe_slot_ss10(slot, sbus_offsets_ss10[slot].base);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ob_sbus_init_ss600mp(void)
|
|
{
|
|
unsigned int slot;
|
|
int notfirst = 0;
|
|
|
|
for (slot = 0; slot < SBUS_SLOTS; slot++) {
|
|
if (sbus_offsets_ss10[slot].size > 0)
|
|
ob_add_sbus_range(&sbus_offsets_ss10[slot], notfirst++);
|
|
}
|
|
push_str("ranges");
|
|
fword("property");
|
|
|
|
for (slot = 0; slot < SBUS_SLOTS; slot++) {
|
|
if (sbus_offsets_ss10[slot].size > 0)
|
|
sbus_probe_slot_ss600mp(slot, sbus_offsets_ss10[slot].base);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ob_sbus_init(uint64_t base, int machine_id)
|
|
{
|
|
ob_sbus_node_init(base);
|
|
|
|
switch (machine_id) {
|
|
case 66:
|
|
return ob_sbus_init_ss600mp();
|
|
case 64 ... 65:
|
|
return ob_sbus_init_ss10();
|
|
case 32 ... 63:
|
|
return ob_sbus_init_ss5();
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|