mirror of https://github.com/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.
404 lines
9.2 KiB
404 lines
9.2 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* arch/arm/plat-iop/pci.c |
|
* |
|
* PCI support for the Intel IOP32X and IOP33X processors |
|
* |
|
* Author: Rory Bolt <[email protected]> |
|
* Copyright (C) 2002 Rory Bolt |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/pci.h> |
|
#include <linux/slab.h> |
|
#include <linux/mm.h> |
|
#include <linux/init.h> |
|
#include <linux/ioport.h> |
|
#include <linux/io.h> |
|
#include <asm/irq.h> |
|
#include <asm/signal.h> |
|
#include <asm/mach/pci.h> |
|
#include "hardware.h" |
|
#include "iop3xx.h" |
|
|
|
// #define DEBUG |
|
|
|
#ifdef DEBUG |
|
#define DBG(x...) printk(x) |
|
#else |
|
#define DBG(x...) do { } while (0) |
|
#endif |
|
|
|
/* |
|
* This routine builds either a type0 or type1 configuration command. If the |
|
* bus is on the 803xx then a type0 made, else a type1 is created. |
|
*/ |
|
static u32 iop3xx_cfg_address(struct pci_bus *bus, int devfn, int where) |
|
{ |
|
struct pci_sys_data *sys = bus->sysdata; |
|
u32 addr; |
|
|
|
if (sys->busnr == bus->number) |
|
addr = 1 << (PCI_SLOT(devfn) + 16) | (PCI_SLOT(devfn) << 11); |
|
else |
|
addr = bus->number << 16 | PCI_SLOT(devfn) << 11 | 1; |
|
|
|
addr |= PCI_FUNC(devfn) << 8 | (where & ~3); |
|
|
|
return addr; |
|
} |
|
|
|
/* |
|
* This routine checks the status of the last configuration cycle. If an error |
|
* was detected it returns a 1, else it returns a 0. The errors being checked |
|
* are parity, master abort, target abort (master and target). These types of |
|
* errors occur during a config cycle where there is no device, like during |
|
* the discovery stage. |
|
*/ |
|
static int iop3xx_pci_status(void) |
|
{ |
|
unsigned int status; |
|
int ret = 0; |
|
|
|
/* |
|
* Check the status registers. |
|
*/ |
|
status = *IOP3XX_ATUSR; |
|
if (status & 0xf900) { |
|
DBG("\t\t\tPCI: P0 - status = 0x%08x\n", status); |
|
*IOP3XX_ATUSR = status & 0xf900; |
|
ret = 1; |
|
} |
|
|
|
status = *IOP3XX_ATUISR; |
|
if (status & 0x679f) { |
|
DBG("\t\t\tPCI: P1 - status = 0x%08x\n", status); |
|
*IOP3XX_ATUISR = status & 0x679f; |
|
ret = 1; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* Simply write the address register and read the configuration |
|
* data. Note that the 4 nops ensure that we are able to handle |
|
* a delayed abort (in theory.) |
|
*/ |
|
static u32 iop3xx_read(unsigned long addr) |
|
{ |
|
u32 val; |
|
|
|
__asm__ __volatile__( |
|
"str %1, [%2]\n\t" |
|
"ldr %0, [%3]\n\t" |
|
"nop\n\t" |
|
"nop\n\t" |
|
"nop\n\t" |
|
"nop\n\t" |
|
: "=r" (val) |
|
: "r" (addr), "r" (IOP3XX_OCCAR), "r" (IOP3XX_OCCDR)); |
|
|
|
return val; |
|
} |
|
|
|
/* |
|
* The read routines must check the error status of the last configuration |
|
* cycle. If there was an error, the routine returns all hex f's. |
|
*/ |
|
static int |
|
iop3xx_read_config(struct pci_bus *bus, unsigned int devfn, int where, |
|
int size, u32 *value) |
|
{ |
|
unsigned long addr = iop3xx_cfg_address(bus, devfn, where); |
|
u32 val = iop3xx_read(addr) >> ((where & 3) * 8); |
|
|
|
if (iop3xx_pci_status()) |
|
val = 0xffffffff; |
|
|
|
*value = val; |
|
|
|
return PCIBIOS_SUCCESSFUL; |
|
} |
|
|
|
static int |
|
iop3xx_write_config(struct pci_bus *bus, unsigned int devfn, int where, |
|
int size, u32 value) |
|
{ |
|
unsigned long addr = iop3xx_cfg_address(bus, devfn, where); |
|
u32 val; |
|
|
|
if (size != 4) { |
|
val = iop3xx_read(addr); |
|
if (iop3xx_pci_status()) |
|
return PCIBIOS_SUCCESSFUL; |
|
|
|
where = (where & 3) * 8; |
|
|
|
if (size == 1) |
|
val &= ~(0xff << where); |
|
else |
|
val &= ~(0xffff << where); |
|
|
|
*IOP3XX_OCCDR = val | value << where; |
|
} else { |
|
asm volatile( |
|
"str %1, [%2]\n\t" |
|
"str %0, [%3]\n\t" |
|
"nop\n\t" |
|
"nop\n\t" |
|
"nop\n\t" |
|
"nop\n\t" |
|
: |
|
: "r" (value), "r" (addr), |
|
"r" (IOP3XX_OCCAR), "r" (IOP3XX_OCCDR)); |
|
} |
|
|
|
return PCIBIOS_SUCCESSFUL; |
|
} |
|
|
|
struct pci_ops iop3xx_ops = { |
|
.read = iop3xx_read_config, |
|
.write = iop3xx_write_config, |
|
}; |
|
|
|
/* |
|
* When a PCI device does not exist during config cycles, the 80200 gets a |
|
* bus error instead of returning 0xffffffff. This handler simply returns. |
|
*/ |
|
static int |
|
iop3xx_pci_abort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) |
|
{ |
|
DBG("PCI abort: address = 0x%08lx fsr = 0x%03x PC = 0x%08lx LR = 0x%08lx\n", |
|
addr, fsr, regs->ARM_pc, regs->ARM_lr); |
|
|
|
/* |
|
* If it was an imprecise abort, then we need to correct the |
|
* return address to be _after_ the instruction. |
|
*/ |
|
if (fsr & (1 << 10)) |
|
regs->ARM_pc += 4; |
|
|
|
return 0; |
|
} |
|
|
|
int iop3xx_pci_setup(int nr, struct pci_sys_data *sys) |
|
{ |
|
struct resource *res; |
|
struct resource realio; |
|
|
|
if (nr != 0) |
|
return 0; |
|
|
|
res = kzalloc(sizeof(struct resource), GFP_KERNEL); |
|
if (!res) |
|
panic("PCI: unable to alloc resources"); |
|
|
|
res->start = IOP3XX_PCI_LOWER_MEM_PA; |
|
res->end = IOP3XX_PCI_LOWER_MEM_PA + IOP3XX_PCI_MEM_WINDOW_SIZE - 1; |
|
res->name = "IOP3XX PCI Memory Space"; |
|
res->flags = IORESOURCE_MEM; |
|
request_resource(&iomem_resource, res); |
|
|
|
/* |
|
* Use whatever translation is already setup. |
|
*/ |
|
sys->mem_offset = IOP3XX_PCI_LOWER_MEM_PA - *IOP3XX_OMWTVR0; |
|
|
|
pci_add_resource_offset(&sys->resources, res, sys->mem_offset); |
|
|
|
realio.start = 0; |
|
realio.end = realio.start + SZ_64K - 1; |
|
pci_remap_iospace(&realio, IOP3XX_PCI_LOWER_IO_PA); |
|
|
|
return 1; |
|
} |
|
|
|
void __init iop3xx_atu_setup(void) |
|
{ |
|
/* BAR 0 ( Disabled ) */ |
|
*IOP3XX_IAUBAR0 = 0x0; |
|
*IOP3XX_IABAR0 = 0x0; |
|
*IOP3XX_IATVR0 = 0x0; |
|
*IOP3XX_IALR0 = 0x0; |
|
|
|
/* BAR 1 ( Disabled ) */ |
|
*IOP3XX_IAUBAR1 = 0x0; |
|
*IOP3XX_IABAR1 = 0x0; |
|
*IOP3XX_IALR1 = 0x0; |
|
|
|
/* BAR 2 (1:1 mapping with Physical RAM) */ |
|
/* Set limit and enable */ |
|
*IOP3XX_IALR2 = ~((u32)IOP3XX_MAX_RAM_SIZE - 1) & ~0x1; |
|
*IOP3XX_IAUBAR2 = 0x0; |
|
|
|
/* Align the inbound bar with the base of memory */ |
|
*IOP3XX_IABAR2 = PHYS_OFFSET | |
|
PCI_BASE_ADDRESS_MEM_TYPE_64 | |
|
PCI_BASE_ADDRESS_MEM_PREFETCH; |
|
|
|
*IOP3XX_IATVR2 = PHYS_OFFSET; |
|
|
|
/* Outbound window 0 */ |
|
*IOP3XX_OMWTVR0 = IOP3XX_PCI_LOWER_MEM_BA; |
|
*IOP3XX_OUMWTVR0 = 0; |
|
|
|
/* Outbound window 1 */ |
|
*IOP3XX_OMWTVR1 = IOP3XX_PCI_LOWER_MEM_BA + |
|
IOP3XX_PCI_MEM_WINDOW_SIZE / 2; |
|
*IOP3XX_OUMWTVR1 = 0; |
|
|
|
/* BAR 3 ( Disabled ) */ |
|
*IOP3XX_IAUBAR3 = 0x0; |
|
*IOP3XX_IABAR3 = 0x0; |
|
*IOP3XX_IATVR3 = 0x0; |
|
*IOP3XX_IALR3 = 0x0; |
|
|
|
/* Setup the I/O Bar |
|
*/ |
|
*IOP3XX_OIOWTVR = IOP3XX_PCI_LOWER_IO_BA; |
|
|
|
/* Enable inbound and outbound cycles |
|
*/ |
|
*IOP3XX_ATUCMD |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | |
|
PCI_COMMAND_PARITY | PCI_COMMAND_SERR; |
|
*IOP3XX_ATUCR |= IOP3XX_ATUCR_OUT_EN; |
|
} |
|
|
|
void __init iop3xx_atu_disable(void) |
|
{ |
|
*IOP3XX_ATUCMD = 0; |
|
*IOP3XX_ATUCR = 0; |
|
|
|
/* wait for cycles to quiesce */ |
|
while (*IOP3XX_PCSR & (IOP3XX_PCSR_OUT_Q_BUSY | |
|
IOP3XX_PCSR_IN_Q_BUSY)) |
|
cpu_relax(); |
|
|
|
/* BAR 0 ( Disabled ) */ |
|
*IOP3XX_IAUBAR0 = 0x0; |
|
*IOP3XX_IABAR0 = 0x0; |
|
*IOP3XX_IATVR0 = 0x0; |
|
*IOP3XX_IALR0 = 0x0; |
|
|
|
/* BAR 1 ( Disabled ) */ |
|
*IOP3XX_IAUBAR1 = 0x0; |
|
*IOP3XX_IABAR1 = 0x0; |
|
*IOP3XX_IALR1 = 0x0; |
|
|
|
/* BAR 2 ( Disabled ) */ |
|
*IOP3XX_IAUBAR2 = 0x0; |
|
*IOP3XX_IABAR2 = 0x0; |
|
*IOP3XX_IATVR2 = 0x0; |
|
*IOP3XX_IALR2 = 0x0; |
|
|
|
/* BAR 3 ( Disabled ) */ |
|
*IOP3XX_IAUBAR3 = 0x0; |
|
*IOP3XX_IABAR3 = 0x0; |
|
*IOP3XX_IATVR3 = 0x0; |
|
*IOP3XX_IALR3 = 0x0; |
|
|
|
/* Clear the outbound windows */ |
|
*IOP3XX_OIOWTVR = 0; |
|
|
|
/* Outbound window 0 */ |
|
*IOP3XX_OMWTVR0 = 0; |
|
*IOP3XX_OUMWTVR0 = 0; |
|
|
|
/* Outbound window 1 */ |
|
*IOP3XX_OMWTVR1 = 0; |
|
*IOP3XX_OUMWTVR1 = 0; |
|
} |
|
|
|
/* Flag to determine whether the ATU is initialized and the PCI bus scanned */ |
|
int init_atu; |
|
|
|
int iop3xx_get_init_atu(void) { |
|
/* check if default has been overridden */ |
|
if (init_atu != IOP3XX_INIT_ATU_DEFAULT) |
|
return init_atu; |
|
else |
|
return IOP3XX_INIT_ATU_DISABLE; |
|
} |
|
|
|
static void __init iop3xx_atu_debug(void) |
|
{ |
|
DBG("PCI: Intel IOP3xx PCI init.\n"); |
|
DBG("PCI: Outbound memory window 0: PCI 0x%08x%08x\n", |
|
*IOP3XX_OUMWTVR0, *IOP3XX_OMWTVR0); |
|
DBG("PCI: Outbound memory window 1: PCI 0x%08x%08x\n", |
|
*IOP3XX_OUMWTVR1, *IOP3XX_OMWTVR1); |
|
DBG("PCI: Outbound IO window: PCI 0x%08x\n", |
|
*IOP3XX_OIOWTVR); |
|
|
|
DBG("PCI: Inbound memory window 0: PCI 0x%08x%08x 0x%08x -> 0x%08x\n", |
|
*IOP3XX_IAUBAR0, *IOP3XX_IABAR0, *IOP3XX_IALR0, *IOP3XX_IATVR0); |
|
DBG("PCI: Inbound memory window 1: PCI 0x%08x%08x 0x%08x\n", |
|
*IOP3XX_IAUBAR1, *IOP3XX_IABAR1, *IOP3XX_IALR1); |
|
DBG("PCI: Inbound memory window 2: PCI 0x%08x%08x 0x%08x -> 0x%08x\n", |
|
*IOP3XX_IAUBAR2, *IOP3XX_IABAR2, *IOP3XX_IALR2, *IOP3XX_IATVR2); |
|
DBG("PCI: Inbound memory window 3: PCI 0x%08x%08x 0x%08x -> 0x%08x\n", |
|
*IOP3XX_IAUBAR3, *IOP3XX_IABAR3, *IOP3XX_IALR3, *IOP3XX_IATVR3); |
|
|
|
DBG("PCI: Expansion ROM window: PCI 0x%08x%08x 0x%08x -> 0x%08x\n", |
|
0, *IOP3XX_ERBAR, *IOP3XX_ERLR, *IOP3XX_ERTVR); |
|
|
|
DBG("ATU: IOP3XX_ATUCMD=0x%04x\n", *IOP3XX_ATUCMD); |
|
DBG("ATU: IOP3XX_ATUCR=0x%08x\n", *IOP3XX_ATUCR); |
|
|
|
hook_fault_code(16+6, iop3xx_pci_abort, SIGBUS, 0, "imprecise external abort"); |
|
} |
|
|
|
/* for platforms that might be host-bus-adapters */ |
|
void __init iop3xx_pci_preinit_cond(void) |
|
{ |
|
if (iop3xx_get_init_atu() == IOP3XX_INIT_ATU_ENABLE) { |
|
iop3xx_atu_disable(); |
|
iop3xx_atu_setup(); |
|
iop3xx_atu_debug(); |
|
} |
|
} |
|
|
|
void __init iop3xx_pci_preinit(void) |
|
{ |
|
pcibios_min_mem = 0; |
|
|
|
iop3xx_atu_disable(); |
|
iop3xx_atu_setup(); |
|
iop3xx_atu_debug(); |
|
} |
|
|
|
/* allow init_atu to be user overridden */ |
|
static int __init iop3xx_init_atu_setup(char *str) |
|
{ |
|
init_atu = IOP3XX_INIT_ATU_DEFAULT; |
|
if (str) { |
|
while (*str != '\0') { |
|
switch (*str) { |
|
case 'y': |
|
case 'Y': |
|
init_atu = IOP3XX_INIT_ATU_ENABLE; |
|
break; |
|
case 'n': |
|
case 'N': |
|
init_atu = IOP3XX_INIT_ATU_DISABLE; |
|
break; |
|
case ',': |
|
case '=': |
|
break; |
|
default: |
|
printk(KERN_DEBUG "\"%s\" malformed at " |
|
"character: \'%c\'", |
|
__func__, |
|
*str); |
|
*(str + 1) = '\0'; |
|
} |
|
str++; |
|
} |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
__setup("iop3xx_init_atu", iop3xx_init_atu_setup); |
|
|
|
|