456 строки
12 KiB
C
456 строки
12 KiB
C
/*
|
|
* Workaround for device erratum PCI 9.
|
|
* See Motorola's "XPC826xA Family Device Errata Reference."
|
|
* The erratum applies to all 8260 family Hip4 processors. It is scheduled
|
|
* to be fixed in HiP4 Rev C. Erratum PCI 9 states that a simultaneous PCI
|
|
* inbound write transaction and PCI outbound read transaction can result in a
|
|
* bus deadlock. The suggested workaround is to use the IDMA controller to
|
|
* perform all reads from PCI configuration, memory, and I/O space.
|
|
*
|
|
* Author: andy_lowe@mvista.com
|
|
*
|
|
* 2003 (c) MontaVista Software, Inc. This file is licensed under
|
|
* the terms of the GNU General Public License version 2. This program
|
|
* is licensed "as is" without any warranty of any kind, whether express
|
|
* or implied.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/types.h>
|
|
#include <linux/string.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/pci-bridge.h>
|
|
#include <asm/machdep.h>
|
|
#include <asm/byteorder.h>
|
|
#include <asm/mpc8260.h>
|
|
#include <asm/immap_cpm2.h>
|
|
#include <asm/cpm2.h>
|
|
|
|
#include "m82xx_pci.h"
|
|
|
|
#ifdef CONFIG_8260_PCI9
|
|
/*#include <asm/mpc8260_pci9.h>*/ /* included in asm/io.h */
|
|
|
|
#define IDMA_XFER_BUF_SIZE 64 /* size of the IDMA transfer buffer */
|
|
|
|
/* define a structure for the IDMA dpram usage */
|
|
typedef struct idma_dpram_s {
|
|
idma_t pram; /* IDMA parameter RAM */
|
|
u_char xfer_buf[IDMA_XFER_BUF_SIZE]; /* IDMA transfer buffer */
|
|
idma_bd_t bd; /* buffer descriptor */
|
|
} idma_dpram_t;
|
|
|
|
/* define offsets relative to start of IDMA dpram */
|
|
#define IDMA_XFER_BUF_OFFSET (sizeof(idma_t))
|
|
#define IDMA_BD_OFFSET (sizeof(idma_t) + IDMA_XFER_BUF_SIZE)
|
|
|
|
/* define globals */
|
|
static volatile idma_dpram_t *idma_dpram;
|
|
|
|
/* Exactly one of CONFIG_8260_PCI9_IDMAn must be defined,
|
|
* where n is 1, 2, 3, or 4. This selects the IDMA channel used for
|
|
* the PCI9 workaround.
|
|
*/
|
|
#ifdef CONFIG_8260_PCI9_IDMA1
|
|
#define IDMA_CHAN 0
|
|
#define PROFF_IDMA PROFF_IDMA1_BASE
|
|
#define IDMA_PAGE CPM_CR_IDMA1_PAGE
|
|
#define IDMA_SBLOCK CPM_CR_IDMA1_SBLOCK
|
|
#endif
|
|
#ifdef CONFIG_8260_PCI9_IDMA2
|
|
#define IDMA_CHAN 1
|
|
#define PROFF_IDMA PROFF_IDMA2_BASE
|
|
#define IDMA_PAGE CPM_CR_IDMA2_PAGE
|
|
#define IDMA_SBLOCK CPM_CR_IDMA2_SBLOCK
|
|
#endif
|
|
#ifdef CONFIG_8260_PCI9_IDMA3
|
|
#define IDMA_CHAN 2
|
|
#define PROFF_IDMA PROFF_IDMA3_BASE
|
|
#define IDMA_PAGE CPM_CR_IDMA3_PAGE
|
|
#define IDMA_SBLOCK CPM_CR_IDMA3_SBLOCK
|
|
#endif
|
|
#ifdef CONFIG_8260_PCI9_IDMA4
|
|
#define IDMA_CHAN 3
|
|
#define PROFF_IDMA PROFF_IDMA4_BASE
|
|
#define IDMA_PAGE CPM_CR_IDMA4_PAGE
|
|
#define IDMA_SBLOCK CPM_CR_IDMA4_SBLOCK
|
|
#endif
|
|
|
|
void idma_pci9_init(void)
|
|
{
|
|
uint dpram_offset;
|
|
volatile idma_t *pram;
|
|
volatile im_idma_t *idma_reg;
|
|
volatile cpm2_map_t *immap = cpm2_immr;
|
|
|
|
/* allocate IDMA dpram */
|
|
dpram_offset = cpm_dpalloc(sizeof(idma_dpram_t), 64);
|
|
idma_dpram = cpm_dpram_addr(dpram_offset);
|
|
|
|
/* initialize the IDMA parameter RAM */
|
|
memset((void *)idma_dpram, 0, sizeof(idma_dpram_t));
|
|
pram = &idma_dpram->pram;
|
|
pram->ibase = dpram_offset + IDMA_BD_OFFSET;
|
|
pram->dpr_buf = dpram_offset + IDMA_XFER_BUF_OFFSET;
|
|
pram->ss_max = 32;
|
|
pram->dts = 32;
|
|
|
|
/* initialize the IDMA_BASE pointer to the IDMA parameter RAM */
|
|
*((ushort *) &immap->im_dprambase[PROFF_IDMA]) = dpram_offset;
|
|
|
|
/* initialize the IDMA registers */
|
|
idma_reg = (volatile im_idma_t *) &immap->im_sdma.sdma_idsr1;
|
|
idma_reg[IDMA_CHAN].idmr = 0; /* mask all IDMA interrupts */
|
|
idma_reg[IDMA_CHAN].idsr = 0xff; /* clear all event flags */
|
|
|
|
printk(KERN_WARNING
|
|
"Using IDMA%d for MPC8260 device erratum PCI 9 workaround\n",
|
|
IDMA_CHAN + 1);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Use the IDMA controller to transfer data from I/O memory to local RAM.
|
|
* The src address must be a physical address suitable for use by the DMA
|
|
* controller with no translation. The dst address must be a kernel virtual
|
|
* address. The dst address is translated to a physical address via
|
|
* virt_to_phys().
|
|
* The sinc argument specifies whether or not the source address is incremented
|
|
* by the DMA controller. The source address is incremented if and only if sinc
|
|
* is non-zero. The destination address is always incremented since the
|
|
* destination is always host RAM.
|
|
*/
|
|
static void
|
|
idma_pci9_read(u8 *dst, u8 *src, int bytes, int unit_size, int sinc)
|
|
{
|
|
unsigned long flags;
|
|
volatile idma_t *pram = &idma_dpram->pram;
|
|
volatile idma_bd_t *bd = &idma_dpram->bd;
|
|
volatile cpm2_map_t *immap = cpm2_immr;
|
|
|
|
local_irq_save(flags);
|
|
|
|
/* initialize IDMA parameter RAM for this transfer */
|
|
if (sinc)
|
|
pram->dcm = IDMA_DCM_DMA_WRAP_64 | IDMA_DCM_SINC
|
|
| IDMA_DCM_DINC | IDMA_DCM_SD_MEM2MEM;
|
|
else
|
|
pram->dcm = IDMA_DCM_DMA_WRAP_64 | IDMA_DCM_DINC
|
|
| IDMA_DCM_SD_MEM2MEM;
|
|
pram->ibdptr = pram->ibase;
|
|
pram->sts = unit_size;
|
|
pram->istate = 0;
|
|
|
|
/* initialize the buffer descriptor */
|
|
bd->dst = virt_to_phys(dst);
|
|
bd->src = (uint) src;
|
|
bd->len = bytes;
|
|
bd->flags = IDMA_BD_V | IDMA_BD_W | IDMA_BD_I | IDMA_BD_L | IDMA_BD_DGBL
|
|
| IDMA_BD_DBO_BE | IDMA_BD_SBO_BE | IDMA_BD_SDTB;
|
|
|
|
/* issue the START_IDMA command to the CP */
|
|
while (immap->im_cpm.cp_cpcr & CPM_CR_FLG);
|
|
immap->im_cpm.cp_cpcr = mk_cr_cmd(IDMA_PAGE, IDMA_SBLOCK, 0,
|
|
CPM_CR_START_IDMA) | CPM_CR_FLG;
|
|
while (immap->im_cpm.cp_cpcr & CPM_CR_FLG);
|
|
|
|
/* wait for transfer to complete */
|
|
while(bd->flags & IDMA_BD_V);
|
|
|
|
local_irq_restore(flags);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Use the IDMA controller to transfer data from I/O memory to local RAM.
|
|
* The dst address must be a physical address suitable for use by the DMA
|
|
* controller with no translation. The src address must be a kernel virtual
|
|
* address. The src address is translated to a physical address via
|
|
* virt_to_phys().
|
|
* The dinc argument specifies whether or not the dest address is incremented
|
|
* by the DMA controller. The source address is incremented if and only if sinc
|
|
* is non-zero. The source address is always incremented since the
|
|
* source is always host RAM.
|
|
*/
|
|
static void
|
|
idma_pci9_write(u8 *dst, u8 *src, int bytes, int unit_size, int dinc)
|
|
{
|
|
unsigned long flags;
|
|
volatile idma_t *pram = &idma_dpram->pram;
|
|
volatile idma_bd_t *bd = &idma_dpram->bd;
|
|
volatile cpm2_map_t *immap = cpm2_immr;
|
|
|
|
local_irq_save(flags);
|
|
|
|
/* initialize IDMA parameter RAM for this transfer */
|
|
if (dinc)
|
|
pram->dcm = IDMA_DCM_DMA_WRAP_64 | IDMA_DCM_SINC
|
|
| IDMA_DCM_DINC | IDMA_DCM_SD_MEM2MEM;
|
|
else
|
|
pram->dcm = IDMA_DCM_DMA_WRAP_64 | IDMA_DCM_SINC
|
|
| IDMA_DCM_SD_MEM2MEM;
|
|
pram->ibdptr = pram->ibase;
|
|
pram->sts = unit_size;
|
|
pram->istate = 0;
|
|
|
|
/* initialize the buffer descriptor */
|
|
bd->dst = (uint) dst;
|
|
bd->src = virt_to_phys(src);
|
|
bd->len = bytes;
|
|
bd->flags = IDMA_BD_V | IDMA_BD_W | IDMA_BD_I | IDMA_BD_L | IDMA_BD_DGBL
|
|
| IDMA_BD_DBO_BE | IDMA_BD_SBO_BE | IDMA_BD_SDTB;
|
|
|
|
/* issue the START_IDMA command to the CP */
|
|
while (immap->im_cpm.cp_cpcr & CPM_CR_FLG);
|
|
immap->im_cpm.cp_cpcr = mk_cr_cmd(IDMA_PAGE, IDMA_SBLOCK, 0,
|
|
CPM_CR_START_IDMA) | CPM_CR_FLG;
|
|
while (immap->im_cpm.cp_cpcr & CPM_CR_FLG);
|
|
|
|
/* wait for transfer to complete */
|
|
while(bd->flags & IDMA_BD_V);
|
|
|
|
local_irq_restore(flags);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Same as idma_pci9_read, but 16-bit little-endian byte swapping is performed
|
|
* if the unit_size is 2, and 32-bit little-endian byte swapping is performed if
|
|
* the unit_size is 4.
|
|
*/
|
|
static void
|
|
idma_pci9_read_le(u8 *dst, u8 *src, int bytes, int unit_size, int sinc)
|
|
{
|
|
int i;
|
|
u8 *p;
|
|
|
|
idma_pci9_read(dst, src, bytes, unit_size, sinc);
|
|
switch(unit_size) {
|
|
case 2:
|
|
for (i = 0, p = dst; i < bytes; i += 2, p += 2)
|
|
swab16s((u16 *) p);
|
|
break;
|
|
case 4:
|
|
for (i = 0, p = dst; i < bytes; i += 4, p += 4)
|
|
swab32s((u32 *) p);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(idma_pci9_init);
|
|
EXPORT_SYMBOL(idma_pci9_read);
|
|
EXPORT_SYMBOL(idma_pci9_read_le);
|
|
|
|
static inline int is_pci_mem(unsigned long addr)
|
|
{
|
|
if (addr >= M82xx_PCI_LOWER_MMIO &&
|
|
addr <= M82xx_PCI_UPPER_MMIO)
|
|
return 1;
|
|
if (addr >= M82xx_PCI_LOWER_MEM &&
|
|
addr <= M82xx_PCI_UPPER_MEM)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
#define is_pci_mem(pa) ( (pa > 0x80000000) && (pa < 0xc0000000))
|
|
int readb(volatile unsigned char *addr)
|
|
{
|
|
u8 val;
|
|
unsigned long pa = iopa((unsigned long) addr);
|
|
|
|
if (!is_pci_mem(pa))
|
|
return in_8(addr);
|
|
|
|
idma_pci9_read((u8 *)&val, (u8 *)pa, sizeof(val), sizeof(val), 0);
|
|
return val;
|
|
}
|
|
|
|
int readw(volatile unsigned short *addr)
|
|
{
|
|
u16 val;
|
|
unsigned long pa = iopa((unsigned long) addr);
|
|
|
|
if (!is_pci_mem(pa))
|
|
return in_le16(addr);
|
|
|
|
idma_pci9_read((u8 *)&val, (u8 *)pa, sizeof(val), sizeof(val), 0);
|
|
return swab16(val);
|
|
}
|
|
|
|
unsigned readl(volatile unsigned *addr)
|
|
{
|
|
u32 val;
|
|
unsigned long pa = iopa((unsigned long) addr);
|
|
|
|
if (!is_pci_mem(pa))
|
|
return in_le32(addr);
|
|
|
|
idma_pci9_read((u8 *)&val, (u8 *)pa, sizeof(val), sizeof(val), 0);
|
|
return swab32(val);
|
|
}
|
|
|
|
int inb(unsigned port)
|
|
{
|
|
u8 val;
|
|
u8 *addr = (u8 *)(port + _IO_BASE);
|
|
|
|
idma_pci9_read((u8 *)&val, (u8 *)addr, sizeof(val), sizeof(val), 0);
|
|
return val;
|
|
}
|
|
|
|
int inw(unsigned port)
|
|
{
|
|
u16 val;
|
|
u8 *addr = (u8 *)(port + _IO_BASE);
|
|
|
|
idma_pci9_read((u8 *)&val, (u8 *)addr, sizeof(val), sizeof(val), 0);
|
|
return swab16(val);
|
|
}
|
|
|
|
unsigned inl(unsigned port)
|
|
{
|
|
u32 val;
|
|
u8 *addr = (u8 *)(port + _IO_BASE);
|
|
|
|
idma_pci9_read((u8 *)&val, (u8 *)addr, sizeof(val), sizeof(val), 0);
|
|
return swab32(val);
|
|
}
|
|
|
|
void insb(unsigned port, void *buf, int ns)
|
|
{
|
|
u8 *addr = (u8 *)(port + _IO_BASE);
|
|
|
|
idma_pci9_read((u8 *)buf, (u8 *)addr, ns*sizeof(u8), sizeof(u8), 0);
|
|
}
|
|
|
|
void insw(unsigned port, void *buf, int ns)
|
|
{
|
|
u8 *addr = (u8 *)(port + _IO_BASE);
|
|
|
|
idma_pci9_read((u8 *)buf, (u8 *)addr, ns*sizeof(u16), sizeof(u16), 0);
|
|
}
|
|
|
|
void insl(unsigned port, void *buf, int nl)
|
|
{
|
|
u8 *addr = (u8 *)(port + _IO_BASE);
|
|
|
|
idma_pci9_read((u8 *)buf, (u8 *)addr, nl*sizeof(u32), sizeof(u32), 0);
|
|
}
|
|
|
|
void *memcpy_fromio(void *dest, unsigned long src, size_t count)
|
|
{
|
|
unsigned long pa = iopa((unsigned long) src);
|
|
|
|
if (is_pci_mem(pa))
|
|
idma_pci9_read((u8 *)dest, (u8 *)pa, count, 32, 1);
|
|
else
|
|
memcpy(dest, (void *)src, count);
|
|
return dest;
|
|
}
|
|
|
|
EXPORT_SYMBOL(readb);
|
|
EXPORT_SYMBOL(readw);
|
|
EXPORT_SYMBOL(readl);
|
|
EXPORT_SYMBOL(inb);
|
|
EXPORT_SYMBOL(inw);
|
|
EXPORT_SYMBOL(inl);
|
|
EXPORT_SYMBOL(insb);
|
|
EXPORT_SYMBOL(insw);
|
|
EXPORT_SYMBOL(insl);
|
|
EXPORT_SYMBOL(memcpy_fromio);
|
|
|
|
#endif /* ifdef CONFIG_8260_PCI9 */
|
|
|
|
/* Indirect PCI routines adapted from arch/ppc/kernel/indirect_pci.c.
|
|
* Copyright (C) 1998 Gabriel Paubert.
|
|
*/
|
|
#ifndef CONFIG_8260_PCI9
|
|
#define cfg_read(val, addr, type, op) *val = op((type)(addr))
|
|
#else
|
|
#define cfg_read(val, addr, type, op) \
|
|
idma_pci9_read_le((u8*)(val),(u8*)(addr),sizeof(*(val)),sizeof(*(val)),0)
|
|
#endif
|
|
|
|
#define cfg_write(val, addr, type, op) op((type *)(addr), (val))
|
|
|
|
static int indirect_write_config(struct pci_bus *pbus, unsigned int devfn, int where,
|
|
int size, u32 value)
|
|
{
|
|
struct pci_controller *hose = pbus->sysdata;
|
|
u8 cfg_type = 0;
|
|
if (ppc_md.pci_exclude_device)
|
|
if (ppc_md.pci_exclude_device(pbus->number, devfn))
|
|
return PCIBIOS_DEVICE_NOT_FOUND;
|
|
|
|
if (hose->set_cfg_type)
|
|
if (pbus->number != hose->first_busno)
|
|
cfg_type = 1;
|
|
|
|
out_be32(hose->cfg_addr,
|
|
(((where & 0xfc) | cfg_type) << 24) | (devfn << 16)
|
|
| ((pbus->number - hose->bus_offset) << 8) | 0x80);
|
|
|
|
switch (size)
|
|
{
|
|
case 1:
|
|
cfg_write(value, hose->cfg_data + (where & 3), u8, out_8);
|
|
break;
|
|
case 2:
|
|
cfg_write(value, hose->cfg_data + (where & 2), u16, out_le16);
|
|
break;
|
|
case 4:
|
|
cfg_write(value, hose->cfg_data + (where & 0), u32, out_le32);
|
|
break;
|
|
}
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
static int indirect_read_config(struct pci_bus *pbus, unsigned int devfn, int where,
|
|
int size, u32 *value)
|
|
{
|
|
struct pci_controller *hose = pbus->sysdata;
|
|
u8 cfg_type = 0;
|
|
if (ppc_md.pci_exclude_device)
|
|
if (ppc_md.pci_exclude_device(pbus->number, devfn))
|
|
return PCIBIOS_DEVICE_NOT_FOUND;
|
|
|
|
if (hose->set_cfg_type)
|
|
if (pbus->number != hose->first_busno)
|
|
cfg_type = 1;
|
|
|
|
out_be32(hose->cfg_addr,
|
|
(((where & 0xfc) | cfg_type) << 24) | (devfn << 16)
|
|
| ((pbus->number - hose->bus_offset) << 8) | 0x80);
|
|
|
|
switch (size)
|
|
{
|
|
case 1:
|
|
cfg_read(value, hose->cfg_data + (where & 3), u8 *, in_8);
|
|
break;
|
|
case 2:
|
|
cfg_read(value, hose->cfg_data + (where & 2), u16 *, in_le16);
|
|
break;
|
|
case 4:
|
|
cfg_read(value, hose->cfg_data + (where & 0), u32 *, in_le32);
|
|
break;
|
|
}
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
static struct pci_ops indirect_pci_ops =
|
|
{
|
|
.read = indirect_read_config,
|
|
.write = indirect_write_config,
|
|
};
|
|
|
|
void
|
|
setup_m8260_indirect_pci(struct pci_controller* hose, u32 cfg_addr, u32 cfg_data)
|
|
{
|
|
hose->ops = &indirect_pci_ops;
|
|
hose->cfg_addr = (unsigned int *) ioremap(cfg_addr, 4);
|
|
hose->cfg_data = (unsigned char *) ioremap(cfg_data, 4);
|
|
}
|