| /* | 
 |  * 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("<4>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 insw_ns(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_ns(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(insw_ns); | 
 | EXPORT_SYMBOL(insl_ns); | 
 | 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); | 
 | } |