forked from Minki/linux
ad7ad57c61
Fully unify all of the DMA ops so that subordinate bus types to the DMA operation providers (such as ebus, isa, of_device) can work transparently. Basically, we just make sure that for every system device we create, the dev->archdata 'iommu' and 'stc' fields are filled in. Then we have two platform variants of the DMA ops, one for SUN4U which actually programs the real hardware, and one for SUN4V which makes hypervisor calls. This also fixes the crashes in parport_pc on sparc64, reported by Meelis Roos. Signed-off-by: David S. Miller <davem@davemloft.net>
547 lines
13 KiB
C
547 lines
13 KiB
C
/* $Id: ebus.c,v 1.64 2001/11/08 04:41:33 davem Exp $
|
|
* ebus.c: PCI to EBus bridge device.
|
|
*
|
|
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
|
|
* Copyright (C) 1999 David S. Miller (davem@redhat.com)
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include <asm/system.h>
|
|
#include <asm/page.h>
|
|
#include <asm/ebus.h>
|
|
#include <asm/oplib.h>
|
|
#include <asm/prom.h>
|
|
#include <asm/of_device.h>
|
|
#include <asm/bpp.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/io.h>
|
|
|
|
/* EBUS dma library. */
|
|
|
|
#define EBDMA_CSR 0x00UL /* Control/Status */
|
|
#define EBDMA_ADDR 0x04UL /* DMA Address */
|
|
#define EBDMA_COUNT 0x08UL /* DMA Count */
|
|
|
|
#define EBDMA_CSR_INT_PEND 0x00000001
|
|
#define EBDMA_CSR_ERR_PEND 0x00000002
|
|
#define EBDMA_CSR_DRAIN 0x00000004
|
|
#define EBDMA_CSR_INT_EN 0x00000010
|
|
#define EBDMA_CSR_RESET 0x00000080
|
|
#define EBDMA_CSR_WRITE 0x00000100
|
|
#define EBDMA_CSR_EN_DMA 0x00000200
|
|
#define EBDMA_CSR_CYC_PEND 0x00000400
|
|
#define EBDMA_CSR_DIAG_RD_DONE 0x00000800
|
|
#define EBDMA_CSR_DIAG_WR_DONE 0x00001000
|
|
#define EBDMA_CSR_EN_CNT 0x00002000
|
|
#define EBDMA_CSR_TC 0x00004000
|
|
#define EBDMA_CSR_DIS_CSR_DRN 0x00010000
|
|
#define EBDMA_CSR_BURST_SZ_MASK 0x000c0000
|
|
#define EBDMA_CSR_BURST_SZ_1 0x00080000
|
|
#define EBDMA_CSR_BURST_SZ_4 0x00000000
|
|
#define EBDMA_CSR_BURST_SZ_8 0x00040000
|
|
#define EBDMA_CSR_BURST_SZ_16 0x000c0000
|
|
#define EBDMA_CSR_DIAG_EN 0x00100000
|
|
#define EBDMA_CSR_DIS_ERR_PEND 0x00400000
|
|
#define EBDMA_CSR_TCI_DIS 0x00800000
|
|
#define EBDMA_CSR_EN_NEXT 0x01000000
|
|
#define EBDMA_CSR_DMA_ON 0x02000000
|
|
#define EBDMA_CSR_A_LOADED 0x04000000
|
|
#define EBDMA_CSR_NA_LOADED 0x08000000
|
|
#define EBDMA_CSR_DEV_ID_MASK 0xf0000000
|
|
|
|
#define EBUS_DMA_RESET_TIMEOUT 10000
|
|
|
|
static void __ebus_dma_reset(struct ebus_dma_info *p, int no_drain)
|
|
{
|
|
int i;
|
|
u32 val = 0;
|
|
|
|
writel(EBDMA_CSR_RESET, p->regs + EBDMA_CSR);
|
|
udelay(1);
|
|
|
|
if (no_drain)
|
|
return;
|
|
|
|
for (i = EBUS_DMA_RESET_TIMEOUT; i > 0; i--) {
|
|
val = readl(p->regs + EBDMA_CSR);
|
|
|
|
if (!(val & (EBDMA_CSR_DRAIN | EBDMA_CSR_CYC_PEND)))
|
|
break;
|
|
udelay(10);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t ebus_dma_irq(int irq, void *dev_id)
|
|
{
|
|
struct ebus_dma_info *p = dev_id;
|
|
unsigned long flags;
|
|
u32 csr = 0;
|
|
|
|
spin_lock_irqsave(&p->lock, flags);
|
|
csr = readl(p->regs + EBDMA_CSR);
|
|
writel(csr, p->regs + EBDMA_CSR);
|
|
spin_unlock_irqrestore(&p->lock, flags);
|
|
|
|
if (csr & EBDMA_CSR_ERR_PEND) {
|
|
printk(KERN_CRIT "ebus_dma(%s): DMA error!\n", p->name);
|
|
p->callback(p, EBUS_DMA_EVENT_ERROR, p->client_cookie);
|
|
return IRQ_HANDLED;
|
|
} else if (csr & EBDMA_CSR_INT_PEND) {
|
|
p->callback(p,
|
|
(csr & EBDMA_CSR_TC) ?
|
|
EBUS_DMA_EVENT_DMA : EBUS_DMA_EVENT_DEVICE,
|
|
p->client_cookie);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
return IRQ_NONE;
|
|
|
|
}
|
|
|
|
int ebus_dma_register(struct ebus_dma_info *p)
|
|
{
|
|
u32 csr;
|
|
|
|
if (!p->regs)
|
|
return -EINVAL;
|
|
if (p->flags & ~(EBUS_DMA_FLAG_USE_EBDMA_HANDLER |
|
|
EBUS_DMA_FLAG_TCI_DISABLE))
|
|
return -EINVAL;
|
|
if ((p->flags & EBUS_DMA_FLAG_USE_EBDMA_HANDLER) && !p->callback)
|
|
return -EINVAL;
|
|
if (!strlen(p->name))
|
|
return -EINVAL;
|
|
|
|
__ebus_dma_reset(p, 1);
|
|
|
|
csr = EBDMA_CSR_BURST_SZ_16 | EBDMA_CSR_EN_CNT;
|
|
|
|
if (p->flags & EBUS_DMA_FLAG_TCI_DISABLE)
|
|
csr |= EBDMA_CSR_TCI_DIS;
|
|
|
|
writel(csr, p->regs + EBDMA_CSR);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ebus_dma_register);
|
|
|
|
int ebus_dma_irq_enable(struct ebus_dma_info *p, int on)
|
|
{
|
|
unsigned long flags;
|
|
u32 csr;
|
|
|
|
if (on) {
|
|
if (p->flags & EBUS_DMA_FLAG_USE_EBDMA_HANDLER) {
|
|
if (request_irq(p->irq, ebus_dma_irq, IRQF_SHARED, p->name, p))
|
|
return -EBUSY;
|
|
}
|
|
|
|
spin_lock_irqsave(&p->lock, flags);
|
|
csr = readl(p->regs + EBDMA_CSR);
|
|
csr |= EBDMA_CSR_INT_EN;
|
|
writel(csr, p->regs + EBDMA_CSR);
|
|
spin_unlock_irqrestore(&p->lock, flags);
|
|
} else {
|
|
spin_lock_irqsave(&p->lock, flags);
|
|
csr = readl(p->regs + EBDMA_CSR);
|
|
csr &= ~EBDMA_CSR_INT_EN;
|
|
writel(csr, p->regs + EBDMA_CSR);
|
|
spin_unlock_irqrestore(&p->lock, flags);
|
|
|
|
if (p->flags & EBUS_DMA_FLAG_USE_EBDMA_HANDLER) {
|
|
free_irq(p->irq, p);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ebus_dma_irq_enable);
|
|
|
|
void ebus_dma_unregister(struct ebus_dma_info *p)
|
|
{
|
|
unsigned long flags;
|
|
u32 csr;
|
|
int irq_on = 0;
|
|
|
|
spin_lock_irqsave(&p->lock, flags);
|
|
csr = readl(p->regs + EBDMA_CSR);
|
|
if (csr & EBDMA_CSR_INT_EN) {
|
|
csr &= ~EBDMA_CSR_INT_EN;
|
|
writel(csr, p->regs + EBDMA_CSR);
|
|
irq_on = 1;
|
|
}
|
|
spin_unlock_irqrestore(&p->lock, flags);
|
|
|
|
if (irq_on)
|
|
free_irq(p->irq, p);
|
|
}
|
|
EXPORT_SYMBOL(ebus_dma_unregister);
|
|
|
|
int ebus_dma_request(struct ebus_dma_info *p, dma_addr_t bus_addr, size_t len)
|
|
{
|
|
unsigned long flags;
|
|
u32 csr;
|
|
int err;
|
|
|
|
if (len >= (1 << 24))
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&p->lock, flags);
|
|
csr = readl(p->regs + EBDMA_CSR);
|
|
err = -EINVAL;
|
|
if (!(csr & EBDMA_CSR_EN_DMA))
|
|
goto out;
|
|
err = -EBUSY;
|
|
if (csr & EBDMA_CSR_NA_LOADED)
|
|
goto out;
|
|
|
|
writel(len, p->regs + EBDMA_COUNT);
|
|
writel(bus_addr, p->regs + EBDMA_ADDR);
|
|
err = 0;
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&p->lock, flags);
|
|
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL(ebus_dma_request);
|
|
|
|
void ebus_dma_prepare(struct ebus_dma_info *p, int write)
|
|
{
|
|
unsigned long flags;
|
|
u32 csr;
|
|
|
|
spin_lock_irqsave(&p->lock, flags);
|
|
__ebus_dma_reset(p, 0);
|
|
|
|
csr = (EBDMA_CSR_INT_EN |
|
|
EBDMA_CSR_EN_CNT |
|
|
EBDMA_CSR_BURST_SZ_16 |
|
|
EBDMA_CSR_EN_NEXT);
|
|
|
|
if (write)
|
|
csr |= EBDMA_CSR_WRITE;
|
|
if (p->flags & EBUS_DMA_FLAG_TCI_DISABLE)
|
|
csr |= EBDMA_CSR_TCI_DIS;
|
|
|
|
writel(csr, p->regs + EBDMA_CSR);
|
|
|
|
spin_unlock_irqrestore(&p->lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(ebus_dma_prepare);
|
|
|
|
unsigned int ebus_dma_residue(struct ebus_dma_info *p)
|
|
{
|
|
return readl(p->regs + EBDMA_COUNT);
|
|
}
|
|
EXPORT_SYMBOL(ebus_dma_residue);
|
|
|
|
unsigned int ebus_dma_addr(struct ebus_dma_info *p)
|
|
{
|
|
return readl(p->regs + EBDMA_ADDR);
|
|
}
|
|
EXPORT_SYMBOL(ebus_dma_addr);
|
|
|
|
void ebus_dma_enable(struct ebus_dma_info *p, int on)
|
|
{
|
|
unsigned long flags;
|
|
u32 orig_csr, csr;
|
|
|
|
spin_lock_irqsave(&p->lock, flags);
|
|
orig_csr = csr = readl(p->regs + EBDMA_CSR);
|
|
if (on)
|
|
csr |= EBDMA_CSR_EN_DMA;
|
|
else
|
|
csr &= ~EBDMA_CSR_EN_DMA;
|
|
if ((orig_csr & EBDMA_CSR_EN_DMA) !=
|
|
(csr & EBDMA_CSR_EN_DMA))
|
|
writel(csr, p->regs + EBDMA_CSR);
|
|
spin_unlock_irqrestore(&p->lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(ebus_dma_enable);
|
|
|
|
struct linux_ebus *ebus_chain = NULL;
|
|
|
|
static inline void *ebus_alloc(size_t size)
|
|
{
|
|
void *mem;
|
|
|
|
mem = kzalloc(size, GFP_ATOMIC);
|
|
if (!mem)
|
|
panic("ebus_alloc: out of memory");
|
|
return mem;
|
|
}
|
|
|
|
static void __init fill_ebus_child(struct device_node *dp,
|
|
struct linux_ebus_child *dev,
|
|
int non_standard_regs)
|
|
{
|
|
struct of_device *op;
|
|
const int *regs;
|
|
int i, len;
|
|
|
|
dev->prom_node = dp;
|
|
printk(" (%s)", dp->name);
|
|
|
|
regs = of_get_property(dp, "reg", &len);
|
|
if (!regs)
|
|
dev->num_addrs = 0;
|
|
else
|
|
dev->num_addrs = len / sizeof(regs[0]);
|
|
|
|
if (non_standard_regs) {
|
|
/* This is to handle reg properties which are not
|
|
* in the parent relative format. One example are
|
|
* children of the i2c device on CompactPCI systems.
|
|
*
|
|
* So, for such devices we just record the property
|
|
* raw in the child resources.
|
|
*/
|
|
for (i = 0; i < dev->num_addrs; i++)
|
|
dev->resource[i].start = regs[i];
|
|
} else {
|
|
for (i = 0; i < dev->num_addrs; i++) {
|
|
int rnum = regs[i];
|
|
if (rnum >= dev->parent->num_addrs) {
|
|
prom_printf("UGH: property for %s was %d, need < %d\n",
|
|
dp->name, len, dev->parent->num_addrs);
|
|
prom_halt();
|
|
}
|
|
dev->resource[i].start = dev->parent->resource[i].start;
|
|
dev->resource[i].end = dev->parent->resource[i].end;
|
|
dev->resource[i].flags = IORESOURCE_MEM;
|
|
dev->resource[i].name = dp->name;
|
|
}
|
|
}
|
|
|
|
op = of_find_device_by_node(dp);
|
|
if (!op) {
|
|
dev->num_irqs = 0;
|
|
} else {
|
|
dev->num_irqs = op->num_irqs;
|
|
for (i = 0; i < dev->num_irqs; i++)
|
|
dev->irqs[i] = op->irqs[i];
|
|
}
|
|
|
|
if (!dev->num_irqs) {
|
|
/*
|
|
* Oh, well, some PROMs don't export interrupts
|
|
* property to children of EBus devices...
|
|
*
|
|
* Be smart about PS/2 keyboard and mouse.
|
|
*/
|
|
if (!strcmp(dev->parent->prom_node->name, "8042")) {
|
|
if (!strcmp(dev->prom_node->name, "kb_ps2")) {
|
|
dev->num_irqs = 1;
|
|
dev->irqs[0] = dev->parent->irqs[0];
|
|
} else {
|
|
dev->num_irqs = 1;
|
|
dev->irqs[0] = dev->parent->irqs[1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int __init child_regs_nonstandard(struct linux_ebus_device *dev)
|
|
{
|
|
if (!strcmp(dev->prom_node->name, "i2c") ||
|
|
!strcmp(dev->prom_node->name, "SUNW,lombus"))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void __init fill_ebus_device(struct device_node *dp, struct linux_ebus_device *dev)
|
|
{
|
|
struct linux_ebus_child *child;
|
|
struct dev_archdata *sd;
|
|
struct of_device *op;
|
|
int i, len;
|
|
|
|
dev->prom_node = dp;
|
|
|
|
printk(" [%s", dp->name);
|
|
|
|
op = of_find_device_by_node(dp);
|
|
if (!op) {
|
|
dev->num_addrs = 0;
|
|
dev->num_irqs = 0;
|
|
} else {
|
|
(void) of_get_property(dp, "reg", &len);
|
|
dev->num_addrs = len / sizeof(struct linux_prom_registers);
|
|
|
|
for (i = 0; i < dev->num_addrs; i++)
|
|
memcpy(&dev->resource[i],
|
|
&op->resource[i],
|
|
sizeof(struct resource));
|
|
|
|
dev->num_irqs = op->num_irqs;
|
|
for (i = 0; i < dev->num_irqs; i++)
|
|
dev->irqs[i] = op->irqs[i];
|
|
}
|
|
|
|
sd = &dev->ofdev.dev.archdata;
|
|
sd->prom_node = dp;
|
|
sd->op = &dev->ofdev;
|
|
sd->iommu = dev->bus->ofdev.dev.parent->archdata.iommu;
|
|
sd->stc = dev->bus->ofdev.dev.parent->archdata.stc;
|
|
|
|
dev->ofdev.node = dp;
|
|
dev->ofdev.dev.parent = &dev->bus->ofdev.dev;
|
|
dev->ofdev.dev.bus = &ebus_bus_type;
|
|
sprintf(dev->ofdev.dev.bus_id, "ebus[%08x]", dp->node);
|
|
|
|
/* Register with core */
|
|
if (of_device_register(&dev->ofdev) != 0)
|
|
printk(KERN_DEBUG "ebus: device registration error for %s!\n",
|
|
dp->path_component_name);
|
|
|
|
dp = dp->child;
|
|
if (dp) {
|
|
printk(" ->");
|
|
dev->children = ebus_alloc(sizeof(struct linux_ebus_child));
|
|
|
|
child = dev->children;
|
|
child->next = NULL;
|
|
child->parent = dev;
|
|
child->bus = dev->bus;
|
|
fill_ebus_child(dp, child,
|
|
child_regs_nonstandard(dev));
|
|
|
|
while ((dp = dp->sibling) != NULL) {
|
|
child->next = ebus_alloc(sizeof(struct linux_ebus_child));
|
|
|
|
child = child->next;
|
|
child->next = NULL;
|
|
child->parent = dev;
|
|
child->bus = dev->bus;
|
|
fill_ebus_child(dp, child,
|
|
child_regs_nonstandard(dev));
|
|
}
|
|
}
|
|
printk("]");
|
|
}
|
|
|
|
static struct pci_dev *find_next_ebus(struct pci_dev *start, int *is_rio_p)
|
|
{
|
|
struct pci_dev *pdev = start;
|
|
|
|
while ((pdev = pci_get_device(PCI_VENDOR_ID_SUN, PCI_ANY_ID, pdev)))
|
|
if (pdev->device == PCI_DEVICE_ID_SUN_EBUS ||
|
|
pdev->device == PCI_DEVICE_ID_SUN_RIO_EBUS)
|
|
break;
|
|
|
|
*is_rio_p = !!(pdev && (pdev->device == PCI_DEVICE_ID_SUN_RIO_EBUS));
|
|
|
|
return pdev;
|
|
}
|
|
|
|
void __init ebus_init(void)
|
|
{
|
|
struct linux_ebus_device *dev;
|
|
struct linux_ebus *ebus;
|
|
struct pci_dev *pdev;
|
|
struct device_node *dp;
|
|
int is_rio;
|
|
int num_ebus = 0;
|
|
|
|
pdev = find_next_ebus(NULL, &is_rio);
|
|
if (!pdev) {
|
|
printk("ebus: No EBus's found.\n");
|
|
return;
|
|
}
|
|
|
|
dp = pci_device_to_OF_node(pdev);
|
|
|
|
ebus_chain = ebus = ebus_alloc(sizeof(struct linux_ebus));
|
|
ebus->next = NULL;
|
|
ebus->is_rio = is_rio;
|
|
|
|
while (dp) {
|
|
struct device_node *child;
|
|
|
|
/* SUNW,pci-qfe uses four empty ebuses on it.
|
|
I think we should not consider them here,
|
|
as they have half of the properties this
|
|
code expects and once we do PCI hot-plug,
|
|
we'd have to tweak with the ebus_chain
|
|
in the runtime after initialization. -jj */
|
|
if (!dp->child) {
|
|
pdev = find_next_ebus(pdev, &is_rio);
|
|
if (!pdev) {
|
|
if (ebus == ebus_chain) {
|
|
ebus_chain = NULL;
|
|
printk("ebus: No EBus's found.\n");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
ebus->is_rio = is_rio;
|
|
dp = pci_device_to_OF_node(pdev);
|
|
continue;
|
|
}
|
|
printk("ebus%d:", num_ebus);
|
|
|
|
ebus->index = num_ebus;
|
|
ebus->prom_node = dp;
|
|
ebus->self = pdev;
|
|
|
|
ebus->ofdev.node = dp;
|
|
ebus->ofdev.dev.parent = &pdev->dev;
|
|
ebus->ofdev.dev.bus = &ebus_bus_type;
|
|
sprintf(ebus->ofdev.dev.bus_id, "ebus%d", num_ebus);
|
|
|
|
/* Register with core */
|
|
if (of_device_register(&ebus->ofdev) != 0)
|
|
printk(KERN_DEBUG "ebus: device registration error for %s!\n",
|
|
dp->path_component_name);
|
|
|
|
|
|
child = dp->child;
|
|
if (!child)
|
|
goto next_ebus;
|
|
|
|
ebus->devices = ebus_alloc(sizeof(struct linux_ebus_device));
|
|
|
|
dev = ebus->devices;
|
|
dev->next = NULL;
|
|
dev->children = NULL;
|
|
dev->bus = ebus;
|
|
fill_ebus_device(child, dev);
|
|
|
|
while ((child = child->sibling) != NULL) {
|
|
dev->next = ebus_alloc(sizeof(struct linux_ebus_device));
|
|
|
|
dev = dev->next;
|
|
dev->next = NULL;
|
|
dev->children = NULL;
|
|
dev->bus = ebus;
|
|
fill_ebus_device(child, dev);
|
|
}
|
|
|
|
next_ebus:
|
|
printk("\n");
|
|
|
|
pdev = find_next_ebus(pdev, &is_rio);
|
|
if (!pdev)
|
|
break;
|
|
|
|
dp = pci_device_to_OF_node(pdev);
|
|
|
|
ebus->next = ebus_alloc(sizeof(struct linux_ebus));
|
|
ebus = ebus->next;
|
|
ebus->next = NULL;
|
|
ebus->is_rio = is_rio;
|
|
++num_ebus;
|
|
}
|
|
pci_dev_put(pdev); /* XXX for the case, when ebusnd is 0, is it OK? */
|
|
}
|