diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/sh/drivers |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'arch/sh/drivers')
27 files changed, 4072 insertions, 0 deletions
diff --git a/arch/sh/drivers/Makefile b/arch/sh/drivers/Makefile new file mode 100644 index 00000000000..bd6726cde39 --- /dev/null +++ b/arch/sh/drivers/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Linux SuperH-specific device drivers. +# + +obj-$(CONFIG_PCI) += pci/ +obj-$(CONFIG_SH_DMA) += dma/ + diff --git a/arch/sh/drivers/dma/Kconfig b/arch/sh/drivers/dma/Kconfig new file mode 100644 index 00000000000..0f15216cd39 --- /dev/null +++ b/arch/sh/drivers/dma/Kconfig @@ -0,0 +1,55 @@ +menu "DMA support" + +config SH_DMA + bool "DMA controller (DMAC) support" + help + Selecting this option will provide same API as PC's Direct Memory + Access Controller(8237A) for SuperH DMAC. + + If unsure, say N. + +config NR_ONCHIP_DMA_CHANNELS + depends on SH_DMA + int "Number of on-chip DMAC channels" + default "4" + help + This allows you to specify the number of channels that the on-chip + DMAC supports. This will be 4 for SH7750/SH7751 and 8 for the + SH7750R/SH7751R. + +config NR_DMA_CHANNELS_BOOL + depends on SH_DMA + bool "Override default number of maximum DMA channels" + help + This allows you to forcibly update the maximum number of supported + DMA channels for a given board. If this is unset, this will default + to the number of channels that the on-chip DMAC has. + +config NR_DMA_CHANNELS + int "Maximum number of DMA channels" + depends on SH_DMA && NR_DMA_CHANNELS_BOOL + default NR_ONCHIP_DMA_CHANNELS + help + This allows you to specify the maximum number of DMA channels to + support. Setting this to a higher value allows for cascading DMACs + with additional channels. + +config DMA_PAGE_OPS + bool "Use DMAC for page copy/clear" + depends on SH_DMA && BROKEN + help + Selecting this option will use a dual-address mode configured channel + in the SH DMAC for copy_page()/clear_page(). Primarily a performance + hack. + +config DMA_PAGE_OPS_CHANNEL + depends on DMA_PAGE_OPS + int "DMA channel for sh memory-manager page copy/clear" + default "3" + help + This allows the specification of the dual address dma channel, + in case channel 3 is unavailable. On the SH4, channels 1,2, and 3 + are dual-address capable. + +endmenu + diff --git a/arch/sh/drivers/dma/Makefile b/arch/sh/drivers/dma/Makefile new file mode 100644 index 00000000000..065d4c90970 --- /dev/null +++ b/arch/sh/drivers/dma/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the SuperH DMA specific kernel interface routines under Linux. +# + +obj-y += dma-api.o dma-isa.o +obj-$(CONFIG_SYSFS) += dma-sysfs.o +obj-$(CONFIG_SH_DMA) += dma-sh.o +obj-$(CONFIG_SH_DREAMCAST) += dma-pvr2.o dma-g2.o + diff --git a/arch/sh/drivers/dma/dma-api.c b/arch/sh/drivers/dma/dma-api.c new file mode 100644 index 00000000000..96e3036ec2b --- /dev/null +++ b/arch/sh/drivers/dma/dma-api.c @@ -0,0 +1,292 @@ +/* + * arch/sh/drivers/dma/dma-api.c + * + * SuperH-specific DMA management API + * + * Copyright (C) 2003, 2004 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/proc_fs.h> +#include <linux/list.h> +#include <asm/dma.h> + +DEFINE_SPINLOCK(dma_spin_lock); +static LIST_HEAD(registered_dmac_list); + +/* + * A brief note about the reasons for this API as it stands. + * + * For starters, the old ISA DMA API didn't work for us for a number of + * reasons, for one, the vast majority of channels on the SH DMAC are + * dual-address mode only, and both the new and the old DMA APIs are after the + * concept of managing a DMA buffer, which doesn't overly fit this model very + * well. In addition to which, the new API is largely geared at IOMMUs and + * GARTs, and doesn't even support the channel notion very well. + * + * The other thing that's a marginal issue, is the sheer number of random DMA + * engines that are present (ie, in boards like the Dreamcast), some of which + * cascade off of the SH DMAC, and others do not. As such, there was a real + * need for a scalable subsystem that could deal with both single and + * dual-address mode usage, in addition to interoperating with cascaded DMACs. + * + * There really isn't any reason why this needs to be SH specific, though I'm + * not aware of too many other processors (with the exception of some MIPS) + * that have the same concept of a dual address mode, or any real desire to + * actually make use of the DMAC even if such a subsystem were exposed + * elsewhere. + * + * The idea for this was derived from the ARM port, which acted as an excellent + * reference when trying to address these issues. + * + * It should also be noted that the decision to add Yet Another DMA API(tm) to + * the kernel wasn't made easily, and was only decided upon after conferring + * with jejb with regards to the state of the old and new APIs as they applied + * to these circumstances. Philip Blundell was also a great help in figuring + * out some single-address mode DMA semantics that were otherwise rather + * confusing. + */ + +struct dma_info *get_dma_info(unsigned int chan) +{ + struct list_head *pos, *tmp; + unsigned int total = 0; + + /* + * Look for each DMAC's range to determine who the owner of + * the channel is. + */ + list_for_each_safe(pos, tmp, ®istered_dmac_list) { + struct dma_info *info = list_entry(pos, struct dma_info, list); + + total += info->nr_channels; + if (chan > total) + continue; + + return info; + } + + return NULL; +} + +struct dma_channel *get_dma_channel(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + + if (!info) + return ERR_PTR(-EINVAL); + + return info->channels + chan; +} + +int get_dma_residue(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + if (info->ops->get_residue) + return info->ops->get_residue(channel); + + return 0; +} + +int request_dma(unsigned int chan, const char *dev_id) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + down(&channel->sem); + + if (!info->ops || chan >= MAX_DMA_CHANNELS) { + up(&channel->sem); + return -EINVAL; + } + + atomic_set(&channel->busy, 1); + + strlcpy(channel->dev_id, dev_id, sizeof(channel->dev_id)); + + up(&channel->sem); + + if (info->ops->request) + return info->ops->request(channel); + + return 0; +} + +void free_dma(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + if (info->ops->free) + info->ops->free(channel); + + atomic_set(&channel->busy, 0); +} + +void dma_wait_for_completion(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + if (channel->flags & DMA_TEI_CAPABLE) { + wait_event(channel->wait_queue, + (info->ops->get_residue(channel) == 0)); + return; + } + + while (info->ops->get_residue(channel)) + cpu_relax(); +} + +void dma_configure_channel(unsigned int chan, unsigned long flags) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + if (info->ops->configure) + info->ops->configure(channel, flags); +} + +int dma_xfer(unsigned int chan, unsigned long from, + unsigned long to, size_t size, unsigned int mode) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + channel->sar = from; + channel->dar = to; + channel->count = size; + channel->mode = mode; + + return info->ops->xfer(channel); +} + +#ifdef CONFIG_PROC_FS +static int dma_read_proc(char *buf, char **start, off_t off, + int len, int *eof, void *data) +{ + struct list_head *pos, *tmp; + char *p = buf; + + if (list_empty(®istered_dmac_list)) + return 0; + + /* + * Iterate over each registered DMAC + */ + list_for_each_safe(pos, tmp, ®istered_dmac_list) { + struct dma_info *info = list_entry(pos, struct dma_info, list); + int i; + + /* + * Iterate over each channel + */ + for (i = 0; i < info->nr_channels; i++) { + struct dma_channel *channel = info->channels + i; + + if (!(channel->flags & DMA_CONFIGURED)) + continue; + + p += sprintf(p, "%2d: %14s %s\n", i, + info->name, channel->dev_id); + } + } + + return p - buf; +} +#endif + + +int __init register_dmac(struct dma_info *info) +{ + int i; + + INIT_LIST_HEAD(&info->list); + + printk(KERN_INFO "DMA: Registering %s handler (%d channel%s).\n", + info->name, info->nr_channels, + info->nr_channels > 1 ? "s" : ""); + + BUG_ON((info->flags & DMAC_CHANNELS_CONFIGURED) && !info->channels); + + /* + * Don't touch pre-configured channels + */ + if (!(info->flags & DMAC_CHANNELS_CONFIGURED)) { + unsigned int size; + + size = sizeof(struct dma_channel) * info->nr_channels; + + info->channels = kmalloc(size, GFP_KERNEL); + if (!info->channels) + return -ENOMEM; + + memset(info->channels, 0, size); + } + + for (i = 0; i < info->nr_channels; i++) { + struct dma_channel *chan = info->channels + i; + + chan->chan = i; + + memcpy(chan->dev_id, "Unused", 7); + + if (info->flags & DMAC_CHANNELS_TEI_CAPABLE) + chan->flags |= DMA_TEI_CAPABLE; + + init_MUTEX(&chan->sem); + init_waitqueue_head(&chan->wait_queue); + +#ifdef CONFIG_SYSFS + dma_create_sysfs_files(chan); +#endif + } + + list_add(&info->list, ®istered_dmac_list); + + return 0; +} + +void __exit unregister_dmac(struct dma_info *info) +{ + if (!(info->flags & DMAC_CHANNELS_CONFIGURED)) + kfree(info->channels); + + list_del(&info->list); +} + +static int __init dma_api_init(void) +{ + printk("DMA: Registering DMA API.\n"); + +#ifdef CONFIG_PROC_FS + create_proc_read_entry("dma", 0, 0, dma_read_proc, 0); +#endif + + return 0; +} + +subsys_initcall(dma_api_init); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("DMA API for SuperH"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(request_dma); +EXPORT_SYMBOL(free_dma); +EXPORT_SYMBOL(register_dmac); +EXPORT_SYMBOL(get_dma_residue); +EXPORT_SYMBOL(get_dma_info); +EXPORT_SYMBOL(get_dma_channel); +EXPORT_SYMBOL(dma_xfer); +EXPORT_SYMBOL(dma_wait_for_completion); +EXPORT_SYMBOL(dma_configure_channel); + diff --git a/arch/sh/drivers/dma/dma-g2.c b/arch/sh/drivers/dma/dma-g2.c new file mode 100644 index 00000000000..231e3f6fb28 --- /dev/null +++ b/arch/sh/drivers/dma/dma-g2.c @@ -0,0 +1,171 @@ +/* + * arch/sh/drivers/dma/dma-g2.c + * + * G2 bus DMA support + * + * Copyright (C) 2003, 2004 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> + +#include <asm/mach/sysasic.h> +#include <asm/mach/dma.h> +#include <asm/dma.h> + +struct g2_channel { + unsigned long g2_addr; /* G2 bus address */ + unsigned long root_addr; /* Root bus (SH-4) address */ + unsigned long size; /* Size (in bytes), 32-byte aligned */ + unsigned long direction; /* Transfer direction */ + unsigned long ctrl; /* Transfer control */ + unsigned long chan_enable; /* Channel enable */ + unsigned long xfer_enable; /* Transfer enable */ + unsigned long xfer_stat; /* Transfer status */ +} __attribute__ ((aligned(32))); + +struct g2_status { + unsigned long g2_addr; + unsigned long root_addr; + unsigned long size; + unsigned long status; +} __attribute__ ((aligned(16))); + +struct g2_dma_info { + struct g2_channel channel[G2_NR_DMA_CHANNELS]; + unsigned long pad1[G2_NR_DMA_CHANNELS]; + unsigned long wait_state; + unsigned long pad2[10]; + unsigned long magic; + struct g2_status status[G2_NR_DMA_CHANNELS]; +} __attribute__ ((aligned(256))); + +static volatile struct g2_dma_info *g2_dma = (volatile struct g2_dma_info *)0xa05f7800; + +static irqreturn_t g2_dma_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + /* FIXME: Do some meaningful completion work here.. */ + return IRQ_HANDLED; +} + +static struct irqaction g2_dma_irq = { + .name = "g2 DMA handler", + .handler = g2_dma_interrupt, + .flags = SA_INTERRUPT, +}; + +static int g2_enable_dma(struct dma_channel *chan) +{ + unsigned int chan_nr = chan->chan; + + g2_dma->channel[chan_nr].chan_enable = 1; + g2_dma->channel[chan_nr].xfer_enable = 1; + + return 0; +} + +static int g2_disable_dma(struct dma_channel *chan) +{ + unsigned int chan_nr = chan->chan; + + g2_dma->channel[chan_nr].chan_enable = 0; + g2_dma->channel[chan_nr].xfer_enable = 0; + + return 0; +} + +static int g2_xfer_dma(struct dma_channel *chan) +{ + unsigned int chan_nr = chan->chan; + + if (chan->sar & 31) { + printk("g2dma: unaligned source 0x%lx\n", chan->sar); + return -EINVAL; + } + + if (chan->dar & 31) { + printk("g2dma: unaligned dest 0x%lx\n", chan->dar); + return -EINVAL; + } + + /* Align the count */ + if (chan->count & 31) + chan->count = (chan->count + (32 - 1)) & ~(32 - 1); + + /* Fixup destination */ + chan->dar += 0xa0800000; + + /* Fixup direction */ + chan->mode = !chan->mode; + + flush_icache_range((unsigned long)chan->sar, chan->count); + + g2_disable_dma(chan); + + g2_dma->channel[chan_nr].g2_addr = chan->dar & 0x1fffffe0; + g2_dma->channel[chan_nr].root_addr = chan->sar & 0x1fffffe0; + g2_dma->channel[chan_nr].size = (chan->count & ~31) | 0x80000000; + g2_dma->channel[chan_nr].direction = chan->mode; + + /* + * bit 0 - ??? + * bit 1 - if set, generate a hardware event on transfer completion + * bit 2 - ??? something to do with suspend? + */ + g2_dma->channel[chan_nr].ctrl = 5; /* ?? */ + + g2_enable_dma(chan); + + /* debug cruft */ + pr_debug("count, sar, dar, mode, ctrl, chan, xfer: %ld, 0x%08lx, " + "0x%08lx, %ld, %ld, %ld, %ld\n", + g2_dma->channel[chan_nr].size, + g2_dma->channel[chan_nr].root_addr, + g2_dma->channel[chan_nr].g2_addr, + g2_dma->channel[chan_nr].direction, + g2_dma->channel[chan_nr].ctrl, + g2_dma->channel[chan_nr].chan_enable, + g2_dma->channel[chan_nr].xfer_enable); + + return 0; +} + +static struct dma_ops g2_dma_ops = { + .xfer = g2_xfer_dma, +}; + +static struct dma_info g2_dma_info = { + .name = "G2 DMA", + .nr_channels = 4, + .ops = &g2_dma_ops, + .flags = DMAC_CHANNELS_TEI_CAPABLE, +}; + +static int __init g2_dma_init(void) +{ + setup_irq(HW_EVENT_G2_DMA, &g2_dma_irq); + + /* Magic */ + g2_dma->wait_state = 27; + g2_dma->magic = 0x4659404f; + + return register_dmac(&g2_dma_info); +} + +static void __exit g2_dma_exit(void) +{ + free_irq(HW_EVENT_G2_DMA, 0); +} + +subsys_initcall(g2_dma_init); +module_exit(g2_dma_exit); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("G2 bus DMA driver"); +MODULE_LICENSE("GPL"); + diff --git a/arch/sh/drivers/dma/dma-isa.c b/arch/sh/drivers/dma/dma-isa.c new file mode 100644 index 00000000000..1c9bc45b8bc --- /dev/null +++ b/arch/sh/drivers/dma/dma-isa.c @@ -0,0 +1,106 @@ +/* + * arch/sh/drivers/dma/dma-isa.c + * + * Generic ISA DMA wrapper for SH DMA API + * + * Copyright (C) 2003, 2004 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <asm/dma.h> + +/* + * This implements a small wrapper set to make code using the old ISA DMA API + * work with the SH DMA API. Since most of the work in the new API happens + * at ops->xfer() time, we simply use the various set_dma_xxx() routines to + * fill in per-channel info, and then hand hand this off to ops->xfer() at + * enable_dma() time. + * + * For channels that are doing on-demand data transfer via cascading, the + * channel itself will still need to be configured through the new API. As + * such, this code is meant for only the simplest of tasks (and shouldn't be + * used in any new drivers at all). + * + * It should also be noted that various functions here are labelled as + * being deprecated. This is due to the fact that the ops->xfer() method is + * the preferred way of doing things (as well as just grabbing the spinlock + * directly). As such, any users of this interface will be warned rather + * loudly. + */ + +unsigned long __deprecated claim_dma_lock(void) +{ + unsigned long flags; + + spin_lock_irqsave(&dma_spin_lock, flags); + + return flags; +} +EXPORT_SYMBOL(claim_dma_lock); + +void __deprecated release_dma_lock(unsigned long flags) +{ + spin_unlock_irqrestore(&dma_spin_lock, flags); +} +EXPORT_SYMBOL(release_dma_lock); + +void __deprecated disable_dma(unsigned int chan) +{ + /* Nothing */ +} +EXPORT_SYMBOL(disable_dma); + +void __deprecated enable_dma(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + info->ops->xfer(channel); +} +EXPORT_SYMBOL(enable_dma); + +void clear_dma_ff(unsigned int chan) +{ + /* Nothing */ +} +EXPORT_SYMBOL(clear_dma_ff); + +void set_dma_mode(unsigned int chan, char mode) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + channel->mode = mode; +} +EXPORT_SYMBOL(set_dma_mode); + +void set_dma_addr(unsigned int chan, unsigned int addr) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + /* + * Single address mode is the only thing supported through + * this interface. + */ + if ((channel->mode & DMA_MODE_MASK) == DMA_MODE_READ) { + channel->sar = addr; + } else { + channel->dar = addr; + } +} +EXPORT_SYMBOL(set_dma_addr); + +void set_dma_count(unsigned int chan, unsigned int count) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = &info->channels[chan]; + + channel->count = count; +} +EXPORT_SYMBOL(set_dma_count); + diff --git a/arch/sh/drivers/dma/dma-pvr2.c b/arch/sh/drivers/dma/dma-pvr2.c new file mode 100644 index 00000000000..2e1d58f2d1b --- /dev/null +++ b/arch/sh/drivers/dma/dma-pvr2.c @@ -0,0 +1,109 @@ +/* + * arch/sh/boards/dreamcast/dma-pvr2.c + * + * NEC PowerVR 2 (Dreamcast) DMA support + * + * Copyright (C) 2003, 2004 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <asm/mach/sysasic.h> +#include <asm/mach/dma.h> +#include <asm/dma.h> +#include <asm/io.h> + +static unsigned int xfer_complete = 0; +static int count = 0; + +static irqreturn_t pvr2_dma_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + if (get_dma_residue(PVR2_CASCADE_CHAN)) { + printk(KERN_WARNING "DMA: SH DMAC did not complete transfer " + "on channel %d, waiting..\n", PVR2_CASCADE_CHAN); + dma_wait_for_completion(PVR2_CASCADE_CHAN); + } + + if (count++ < 10) + pr_debug("Got a pvr2 dma interrupt for channel %d\n", + irq - HW_EVENT_PVR2_DMA); + + xfer_complete = 1; + + return IRQ_HANDLED; +} + +static int pvr2_request_dma(struct dma_channel *chan) +{ + if (ctrl_inl(PVR2_DMA_MODE) != 0) + return -EBUSY; + + ctrl_outl(0, PVR2_DMA_LMMODE0); + + return 0; +} + +static int pvr2_get_dma_residue(struct dma_channel *chan) +{ + return xfer_complete == 0; +} + +static int pvr2_xfer_dma(struct dma_channel *chan) +{ + if (chan->sar || !chan->dar) + return -EINVAL; + + xfer_complete = 0; + + ctrl_outl(chan->dar, PVR2_DMA_ADDR); + ctrl_outl(chan->count, PVR2_DMA_COUNT); + ctrl_outl(chan->mode & DMA_MODE_MASK, PVR2_DMA_MODE); + + return 0; +} + +static struct irqaction pvr2_dma_irq = { + .name = "pvr2 DMA handler", + .handler = pvr2_dma_interrupt, + .flags = SA_INTERRUPT, +}; + +static struct dma_ops pvr2_dma_ops = { + .request = pvr2_request_dma, + .get_residue = pvr2_get_dma_residue, + .xfer = pvr2_xfer_dma, +}; + +static struct dma_info pvr2_dma_info = { + .name = "PowerVR 2 DMA", + .nr_channels = 1, + .ops = &pvr2_dma_ops, + .flags = DMAC_CHANNELS_TEI_CAPABLE, +}; + +static int __init pvr2_dma_init(void) +{ + setup_irq(HW_EVENT_PVR2_DMA, &pvr2_dma_irq); + request_dma(PVR2_CASCADE_CHAN, "pvr2 cascade"); + + return register_dmac(&pvr2_dma_info); +} + +static void __exit pvr2_dma_exit(void) +{ + free_dma(PVR2_CASCADE_CHAN); + free_irq(HW_EVENT_PVR2_DMA, 0); +} + +subsys_initcall(pvr2_dma_init); +module_exit(pvr2_dma_exit); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("NEC PowerVR 2 DMA driver"); +MODULE_LICENSE("GPL"); + diff --git a/arch/sh/drivers/dma/dma-sh.c b/arch/sh/drivers/dma/dma-sh.c new file mode 100644 index 00000000000..31dacd4444b --- /dev/null +++ b/arch/sh/drivers/dma/dma-sh.c @@ -0,0 +1,267 @@ +/* + * arch/sh/drivers/dma/dma-sh.c + * + * SuperH On-chip DMAC Support + * + * Copyright (C) 2000 Takashi YOSHII + * Copyright (C) 2003, 2004 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <asm/signal.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/io.h> +#include "dma-sh.h" + +/* + * The SuperH DMAC supports a number of transmit sizes, we list them here, + * with their respective values as they appear in the CHCR registers. + * + * Defaults to a 64-bit transfer size. + */ +enum { + XMIT_SZ_64BIT, + XMIT_SZ_8BIT, + XMIT_SZ_16BIT, + XMIT_SZ_32BIT, + XMIT_SZ_256BIT, +}; + +/* + * The DMA count is defined as the number of bytes to transfer. + */ +static unsigned int ts_shift[] = { + [XMIT_SZ_64BIT] = 3, + [XMIT_SZ_8BIT] = 0, + [XMIT_SZ_16BIT] = 1, + [XMIT_SZ_32BIT] = 2, + [XMIT_SZ_256BIT] = 5, +}; + +static inline unsigned int get_dmte_irq(unsigned int chan) +{ + unsigned int irq; + + /* + * Normally we could just do DMTE0_IRQ + chan outright, though in the + * case of the 7751R, the DMTE IRQs for channels > 4 start right above + * the SCIF + */ + + if (chan < 4) { + irq = DMTE0_IRQ + chan; + } else { + irq = DMTE4_IRQ + chan - 4; + } + + return irq; +} + +/* + * We determine the correct shift size based off of the CHCR transmit size + * for the given channel. Since we know that it will take: + * + * info->count >> ts_shift[transmit_size] + * + * iterations to complete the transfer. + */ +static inline unsigned int calc_xmit_shift(struct dma_channel *chan) +{ + u32 chcr = ctrl_inl(CHCR[chan->chan]); + + chcr >>= 4; + + return ts_shift[chcr & 0x0007]; +} + +/* + * The transfer end interrupt must read the chcr register to end the + * hardware interrupt active condition. + * Besides that it needs to waken any waiting process, which should handle + * setting up the next transfer. + */ +static irqreturn_t dma_tei(int irq, void *dev_id, struct pt_regs *regs) +{ + struct dma_channel *chan = (struct dma_channel *)dev_id; + u32 chcr; + + chcr = ctrl_inl(CHCR[chan->chan]); + + if (!(chcr & CHCR_TE)) + return IRQ_NONE; + + chcr &= ~(CHCR_IE | CHCR_DE); + ctrl_outl(chcr, CHCR[chan->chan]); + + wake_up(&chan->wait_queue); + + return IRQ_HANDLED; +} + +static int sh_dmac_request_dma(struct dma_channel *chan) +{ + return request_irq(get_dmte_irq(chan->chan), dma_tei, + SA_INTERRUPT, "DMAC Transfer End", chan); +} + +static void sh_dmac_free_dma(struct dma_channel *chan) +{ + free_irq(get_dmte_irq(chan->chan), chan); +} + +static void sh_dmac_configure_channel(struct dma_channel *chan, unsigned long chcr) +{ + if (!chcr) + chcr = RS_DUAL; + + ctrl_outl(chcr, CHCR[chan->chan]); + + chan->flags |= DMA_CONFIGURED; +} + +static void sh_dmac_enable_dma(struct dma_channel *chan) +{ + int irq = get_dmte_irq(chan->chan); + u32 chcr; + + chcr = ctrl_inl(CHCR[chan->chan]); + chcr |= CHCR_DE | CHCR_IE; + ctrl_outl(chcr, CHCR[chan->chan]); + + enable_irq(irq); +} + +static void sh_dmac_disable_dma(struct dma_channel *chan) +{ + int irq = get_dmte_irq(chan->chan); + u32 chcr; + + disable_irq(irq); + + chcr = ctrl_inl(CHCR[chan->chan]); + chcr &= ~(CHCR_DE | CHCR_TE | CHCR_IE); + ctrl_outl(chcr, CHCR[chan->chan]); +} + +static int sh_dmac_xfer_dma(struct dma_channel *chan) +{ + /* + * If we haven't pre-configured the channel with special flags, use + * the defaults. + */ + if (!(chan->flags & DMA_CONFIGURED)) + sh_dmac_configure_channel(chan, 0); + + sh_dmac_disable_dma(chan); + + /* + * Single-address mode usage note! + * + * It's important that we don't accidentally write any value to SAR/DAR + * (this includes 0) that hasn't been directly specified by the user if + * we're in single-address mode. + * + * In this case, only one address can be defined, anything else will + * result in a DMA address error interrupt (at least on the SH-4), + * which will subsequently halt the transfer. + * + * Channel 2 on the Dreamcast is a special case, as this is used for + * cascading to the PVR2 DMAC. In this case, we still need to write + * SAR and DAR, regardless of value, in order for cascading to work. + */ + if (chan->sar || (mach_is_dreamcast() && chan->chan == 2)) + ctrl_outl(chan->sar, SAR[chan->chan]); + if (chan->dar || (mach_is_dreamcast() && chan->chan == 2)) + ctrl_outl(chan->dar, DAR[chan->chan]); + + ctrl_outl(chan->count >> calc_xmit_shift(chan), DMATCR[chan->chan]); + + sh_dmac_enable_dma(chan); + + return 0; +} + +static int sh_dmac_get_dma_residue(struct dma_channel *chan) +{ + if (!(ctrl_inl(CHCR[chan->chan]) & CHCR_DE)) + return 0; + + return ctrl_inl(DMATCR[chan->chan]) << calc_xmit_shift(chan); +} + +#if defined(CONFIG_CPU_SH4) +static irqreturn_t dma_err(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned long dmaor = ctrl_inl(DMAOR); + + printk("DMAE: DMAOR=%lx\n", dmaor); + + ctrl_outl(ctrl_inl(DMAOR)&~DMAOR_NMIF, DMAOR); + ctrl_outl(ctrl_inl(DMAOR)&~DMAOR_AE, DMAOR); + ctrl_outl(ctrl_inl(DMAOR)|DMAOR_DME, DMAOR); + + disable_irq(irq); + + return IRQ_HANDLED; +} +#endif + +static struct dma_ops sh_dmac_ops = { + .request = sh_dmac_request_dma, + .free = sh_dmac_free_dma, + .get_residue = sh_dmac_get_dma_residue, + .xfer = sh_dmac_xfer_dma, + .configure = sh_dmac_configure_channel, +}; + +static struct dma_info sh_dmac_info = { + .name = "SuperH DMAC", + .nr_channels = 4, + .ops = &sh_dmac_ops, + .flags = DMAC_CHANNELS_TEI_CAPABLE, +}; + +static int __init sh_dmac_init(void) +{ + struct dma_info *info = &sh_dmac_info; + int i; + +#ifdef CONFIG_CPU_SH4 + make_ipr_irq(DMAE_IRQ, DMA_IPR_ADDR, DMA_IPR_POS, DMA_PRIORITY); + i = request_irq(DMAE_IRQ, dma_err, SA_INTERRUPT, "DMAC Address Error", 0); + if (i < 0) + return i; +#endif + + for (i = 0; i < info->nr_channels; i++) { + int irq = get_dmte_irq(i); + + make_ipr_irq(irq, DMA_IPR_ADDR, DMA_IPR_POS, DMA_PRIORITY); + } + + ctrl_outl(0x8000 | DMAOR_DME, DMAOR); + + return register_dmac(info); +} + +static void __exit sh_dmac_exit(void) +{ +#ifdef CONFIG_CPU_SH4 + free_irq(DMAE_IRQ, 0); +#endif +} + +subsys_initcall(sh_dmac_init); +module_exit(sh_dmac_exit); + +MODULE_LICENSE("GPL"); + diff --git a/arch/sh/drivers/dma/dma-sh.h b/arch/sh/drivers/dma/dma-sh.h new file mode 100644 index 00000000000..dd9d547539a --- /dev/null +++ b/arch/sh/drivers/dma/dma-sh.h @@ -0,0 +1,52 @@ +/* + * arch/sh/drivers/dma/dma-sh.h + * + * Copyright (C) 2000 Takashi YOSHII + * Copyright (C) 2003 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#ifndef __DMA_SH_H +#define __DMA_SH_H + +/* Definitions for the SuperH DMAC */ +#define REQ_L 0x00000000 +#define REQ_E 0x00080000 +#define RACK_H 0x00000000 +#define RACK_L 0x00040000 +#define ACK_R 0x00000000 +#define ACK_W 0x00020000 +#define ACK_H 0x00000000 +#define ACK_L 0x00010000 +#define DM_INC 0x00004000 +#define DM_DEC 0x00008000 +#define SM_INC 0x00001000 +#define SM_DEC 0x00002000 +#define RS_IN 0x00000200 +#define RS_OUT 0x00000300 +#define TM_BURST 0x0000080 +#define TS_8 0x00000010 +#define TS_16 0x00000020 +#define TS_32 0x00000030 +#define TS_64 0x00000000 +#define TS_BLK 0x00000040 +#define CHCR_DE 0x00000001 +#define CHCR_TE 0x00000002 +#define CHCR_IE 0x00000004 + +/* Define the default configuration for dual address memory-memory transfer. + * The 0x400 value represents auto-request, external->external. + */ +#define RS_DUAL (DM_INC | SM_INC | 0x400 | TS_32) + +#define DMAOR_COD 0x00000008 +#define DMAOR_AE 0x00000004 +#define DMAOR_NMIF 0x00000002 +#define DMAOR_DME 0x00000001 + +#define MAX_DMAC_CHANNELS (CONFIG_NR_ONCHIP_DMA_CHANNELS) + +#endif /* __DMA_SH_H */ + diff --git a/arch/sh/drivers/dma/dma-sysfs.c b/arch/sh/drivers/dma/dma-sysfs.c new file mode 100644 index 00000000000..71a6d4e7809 --- /dev/null +++ b/arch/sh/drivers/dma/dma-sysfs.c @@ -0,0 +1,133 @@ +/* + * arch/sh/drivers/dma/dma-sysfs.c + * + * sysfs interface for SH DMA API + * + * Copyright (C) 2004 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sysdev.h> +#include <linux/module.h> +#include <asm/dma.h> + +static struct sysdev_class dma_sysclass = { + set_kset_name("dma"), +}; + +EXPORT_SYMBOL(dma_sysclass); + +static ssize_t dma_show_devices(struct sys_device *dev, char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < MAX_DMA_CHANNELS; i++) { + struct dma_info *info = get_dma_info(i); + struct dma_channel *channel = &info->channels[i]; + + len += sprintf(buf + len, "%2d: %14s %s\n", + channel->chan, info->name, + channel->dev_id); + } + + return len; +} + +static SYSDEV_ATTR(devices, S_IRUGO, dma_show_devices, NULL); + +static int __init dma_sysclass_init(void) +{ + int ret; + + ret = sysdev_class_register(&dma_sysclass); + if (ret == 0) + sysfs_create_file(&dma_sysclass.kset.kobj, &attr_devices.attr); + + return ret; +} + +postcore_initcall(dma_sysclass_init); + +static ssize_t dma_show_dev_id(struct sys_device *dev, char *buf) +{ + struct dma_channel *channel = to_dma_channel(dev); + return sprintf(buf, "%s\n", channel->dev_id); +} + +static ssize_t dma_store_dev_id(struct sys_device *dev, + const char *buf, size_t count) +{ + struct dma_channel *channel = to_dma_channel(dev); + strcpy(channel->dev_id, buf); + return count; +} + +static SYSDEV_ATTR(dev_id, S_IRUGO | S_IWUSR, dma_show_dev_id, dma_store_dev_id); + +static ssize_t dma_store_config(struct sys_device *dev, + const char *buf, size_t count) +{ + struct dma_channel *channel = to_dma_channel(dev); + unsigned long config; + + config = simple_strtoul(buf, NULL, 0); + dma_configure_channel(channel->chan, config); + + return count; +} + +static SYSDEV_ATTR(config, S_IWUSR, NULL, dma_store_config); + +static ssize_t dma_show_mode(struct sys_device *dev, char *buf) +{ + struct dma_channel *channel = to_dma_channel(dev); + return sprintf(buf, "0x%08x\n", channel->mode); +} + +static ssize_t dma_store_mode(struct sys_device *dev, + const char *buf, size_t count) +{ + struct dma_channel *channel = to_dma_channel(dev); + channel->mode = simple_strtoul(buf, NULL, 0); + return count; +} + +static SYSDEV_ATTR(mode, S_IRUGO | S_IWUSR, dma_show_mode, dma_store_mode); + +#define dma_ro_attr(field, fmt) \ +static ssize_t dma_show_##field(struct sys_device *dev, char *buf) \ +{ \ + struct dma_channel *channel = to_dma_channel(dev); \ + return sprintf(buf, fmt, channel->field); \ +} \ +static SYSDEV_ATTR(field, S_IRUGO, dma_show_##field, NULL); + +dma_ro_attr(count, "0x%08x\n"); +dma_ro_attr(flags, "0x%08lx\n"); + +int __init dma_create_sysfs_files(struct dma_channel *chan) +{ + struct sys_device *dev = &chan->dev; + int ret; + + dev->id = chan->chan; + dev->cls = &dma_sysclass; + + ret = sysdev_register(dev); + if (ret) + return ret; + + sysdev_create_file(dev, &attr_dev_id); + sysdev_create_file(dev, &attr_count); + sysdev_create_file(dev, &attr_mode); + sysdev_create_file(dev, &attr_flags); + sysdev_create_file(dev, &attr_config); + + return 0; +} + diff --git a/arch/sh/drivers/pci/Kconfig b/arch/sh/drivers/pci/Kconfig new file mode 100644 index 00000000000..6d1cbbe6745 --- /dev/null +++ b/arch/sh/drivers/pci/Kconfig @@ -0,0 +1,41 @@ +config PCI + bool "PCI support" + help + Find out whether you have a PCI motherboard. PCI is the name of a + bus system, i.e. the way the CPU talks to the other stuff inside + your box. If you have PCI, say Y, otherwise N. + + The PCI-HOWTO, available from + <http://www.tldp.org/docs.html#howto>, contains valuable + information about which PCI hardware does work under Linux and which + doesn't. + +config SH_PCIDMA_NONCOHERENT + bool "Cache and PCI noncoherent" + depends on PCI + default y + help + Enable this option if your platform does not have a CPU cache which + remains coherent with PCI DMA. It is safest to say 'Y', although you + will see better performance if you can say 'N', because the PCI DMA + code will not have to flush the CPU's caches. If you have a PCI host + bridge integrated with your SH CPU, refer carefully to the chip specs + to see if you can say 'N' here. Otherwise, leave it as 'Y'. + +# This is also board-specific +config PCI_AUTO + bool + depends on PCI + default y + +config PCI_AUTO_UPDATE_RESOURCES + bool + depends on PCI_AUTO + default y if !SH_DREAMCAST + help + Selecting this option will cause the PCI auto code to leave your + BAR values alone. Otherwise they will be updated automatically. If + for some reason, you have a board that simply refuses to work + with its resources updated beyond what they are when the device + is powered up, set this to N. Everyone else will want this as Y. + diff --git a/arch/sh/drivers/pci/Makefile b/arch/sh/drivers/pci/Makefile new file mode 100644 index 00000000000..365bc16a4a8 --- /dev/null +++ b/arch/sh/drivers/pci/Makefile @@ -0,0 +1,16 @@ +# +# Makefile for the PCI specific kernel interface routines under Linux. +# + +obj-y += pci.o +obj-$(CONFIG_PCI_AUTO) += pci-auto.o + +obj-$(CONFIG_CPU_SUBTYPE_ST40STB1) += pci-st40.o +obj-$(CONFIG_CPU_SUBTYPE_SH7751) += pci-sh7751.o + +obj-$(CONFIG_SH_DREAMCAST) += ops-dreamcast.o fixups-dreamcast.o \ + dma-dreamcast.o +obj-$(CONFIG_SH_SECUREEDGE5410) += ops-snapgear.o +obj-$(CONFIG_SH_BIGSUR) += ops-bigsur.o +obj-$(CONFIG_SH_RTS7751R2D) += ops-rts7751r2d.o fixups-rts7751r2d.o +obj-$(CONFIG_SH_SH03) += ops-sh03.o fixups-sh03.o diff --git a/arch/sh/drivers/pci/dma-dreamcast.c b/arch/sh/drivers/pci/dma-dreamcast.c new file mode 100644 index 00000000000..83de7ef4e7d --- /dev/null +++ b/arch/sh/drivers/pci/dma-dreamcast.c @@ -0,0 +1,71 @@ +/* + * arch/sh/pci/dma-dreamcast.c + * + * PCI DMA support for the Sega Dreamcast + * + * Copyright (C) 2001, 2002 M. R. Brown + * Copyright (C) 2002, 2003 Paul Mundt + * + * This file originally bore the message (with enclosed-$): + * Id: pci.c,v 1.3 2003/05/04 19:29:46 lethal Exp + * Dreamcast PCI: Supports SEGA Broadband Adaptor only. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/device.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/mach/pci.h> + +static int gapspci_dma_used = 0; + +void *dreamcast_consistent_alloc(struct device *dev, size_t size, + dma_addr_t *dma_handle, int flag) +{ + unsigned long buf; + + if (dev && dev->bus != &pci_bus_type) + return NULL; + + if (gapspci_dma_used + size > GAPSPCI_DMA_SIZE) + return ERR_PTR(-EINVAL); + + buf = GAPSPCI_DMA_BASE + gapspci_dma_used; + + gapspci_dma_used = PAGE_ALIGN(gapspci_dma_used+size); + + *dma_handle = (dma_addr_t)buf; + + buf = P2SEGADDR(buf); + + /* Flush the dcache before we hand off the buffer */ + dma_cache_wback_inv((void *)buf, size); + + return (void *)buf; +} + +int dreamcast_consistent_free(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + if (dev && dev->bus != &pci_bus_type) + return -EINVAL; + + /* XXX */ + gapspci_dma_used = 0; + + return 0; +} + diff --git a/arch/sh/drivers/pci/fixups-dreamcast.c b/arch/sh/drivers/pci/fixups-dreamcast.c new file mode 100644 index 00000000000..cf30e2fa51b --- /dev/null +++ b/arch/sh/drivers/pci/fixups-dreamcast.c @@ -0,0 +1,81 @@ +/* + * arch/sh/pci/fixups-dreamcast.c + * + * PCI fixups for the Sega Dreamcast + * + * Copyright (C) 2001, 2002 M. R. Brown + * Copyright (C) 2002, 2003 Paul Mundt + * + * This file originally bore the message (with enclosed-$): + * Id: pci.c,v 1.3 2003/05/04 19:29:46 lethal Exp + * Dreamcast PCI: Supports SEGA Broadband Adaptor only. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/pci.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/mach/pci.h> + +static void __init gapspci_fixup_resources(struct pci_dev *dev) +{ + struct pci_channel *p = board_pci_channels; + + printk(KERN_NOTICE "PCI: Fixing up device %s\n", pci_name(dev)); + + switch (dev->device) { + case PCI_DEVICE_ID_SEGA_BBA: + /* + * We also assume that dev->devfn == 0 + */ + dev->resource[1].start = p->io_resource->start + 0x100; + dev->resource[1].end = dev->resource[1].start + 0x200 - 1; + break; + default: + printk("PCI: Failed resource fixup\n"); + } +} + +DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, gapspci_fixup_resources); + +void __init pcibios_fixup_bus(struct pci_bus *bus) +{ + /* + * We don't have any sub bus to fix up, and this is a rather + * stupid place to put general device fixups. Don't do it. + * Use the pcibios_fixups table or suffer the consequences. + */ +} + +void __init pcibios_fixup_irqs(void) +{ + struct pci_dev *dev = 0; + + for_each_pci_dev(dev) { + /* + * The interrupt routing semantics here are quite trivial. + * + * We basically only support one interrupt, so we only bother + * updating a device's interrupt line with this single shared + * interrupt. Keeps routing quite simple, doesn't it? + */ + printk(KERN_NOTICE "PCI: Fixing up IRQ routing for device %s\n", + pci_name(dev)); + + dev->irq = GAPSPCI_IRQ; + + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); + } +} + diff --git a/arch/sh/drivers/pci/fixups-rts7751r2d.c b/arch/sh/drivers/pci/fixups-rts7751r2d.c new file mode 100644 index 00000000000..0c590fc7a08 --- /dev/null +++ b/arch/sh/drivers/pci/fixups-rts7751r2d.c @@ -0,0 +1,43 @@ +/* + * arch/sh/drivers/pci/fixups-rts7751r2d.c + * + * RTS7751R2D PCI fixups + * + * Copyright (C) 2003 Lineo uSolutions, Inc. + * Copyright (C) 2004 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include "pci-sh7751.h" +#include <asm/io.h> + +#define PCIMCR_MRSET_OFF 0xBFFFFFFF +#define PCIMCR_RFSH_OFF 0xFFFFFFFB + +int pci_fixup_pcic(void) +{ + unsigned long bcr1, mcr; + + bcr1 = inl(SH7751_BCR1); + bcr1 |= 0x40080000; /* Enable Bit 19 BREQEN, set PCIC to slave */ + outl(bcr1, PCI_REG(SH7751_PCIBCR1)); + + /* Enable all interrupts, so we known what to fix */ + outl(0x0000c3ff, PCI_REG(SH7751_PCIINTM)); + outl(0x0000380f, PCI_REG(SH7751_PCIAINTM)); + + outl(0xfb900047, PCI_REG(SH7751_PCICONF1)); + outl(0xab000001, PCI_REG(SH7751_PCICONF4)); + + mcr = inl(SH7751_MCR); + mcr = (mcr & PCIMCR_MRSET_OFF) & PCIMCR_RFSH_OFF; + outl(mcr, PCI_REG(SH7751_PCIMCR)); + + outl(0x0c000000, PCI_REG(SH7751_PCICONF5)); + outl(0xd0000000, PCI_REG(SH7751_PCICONF6)); + outl(0x0c000000, PCI_REG(SH7751_PCILAR0)); + outl(0x00000000, PCI_REG(SH7751_PCILAR1)); + return 0; +} diff --git a/arch/sh/drivers/pci/fixups-sh03.c b/arch/sh/drivers/pci/fixups-sh03.c new file mode 100644 index 00000000000..57ac26c2171 --- /dev/null +++ b/arch/sh/drivers/pci/fixups-sh03.c @@ -0,0 +1,61 @@ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/pci.h> + +/* + * IRQ functions + */ + +int __init pcibios_map_platform_irq(u8 slot, u8 pin, struct pci_dev *dev) +{ + int irq; + + if (dev->bus->number == 0) { + switch (slot) { + case 4: return 5; /* eth0 */ + case 8: return 5; /* eth1 */ + case 6: return 2; /* PCI bridge */ + default: + printk("PCI: Bad IRQ mapping request for slot %d\n", slot); + return 2; + } + } else { + switch (pin) { + case 0: irq = 2; break; + case 1: irq = 2; break; + case 2: irq = 2; break; + case 3: irq = 2; break; + case 4: irq = 2; break; + default: irq = -1; break; + } + } + return irq; +} + +static u8 __init sh03_no_swizzle(struct pci_dev *dev, u8 *pin) +{ + /* no swizzling */ + return PCI_SLOT(dev->devfn); +} + +static int sh03_pci_lookup_irq(struct pci_dev *dev, u8 slot, u8 pin) +{ + int irq = -1; + + /* now lookup the actual IRQ on a platform specific basis (pci-'platform'.c) */ + irq = pcibios_map_platform_irq(slot, pin, dev); + if( irq < 0 ) { + pr_debug("PCI: Error mapping IRQ on device %s\n", pci_name(dev)); + return irq; + } + + pr_debug("Setting IRQ for slot %s to %d\n", pci_name(dev), irq); + + return irq; +} + +void __init pcibios_fixup_irqs(void) +{ + pci_fixup_irqs(sh03_no_swizzle, sh03_pci_lookup_irq); +} diff --git a/arch/sh/drivers/pci/ops-bigsur.c b/arch/sh/drivers/pci/ops-bigsur.c new file mode 100644 index 00000000000..9b43da67804 --- /dev/null +++ b/arch/sh/drivers/pci/ops-bigsur.c @@ -0,0 +1,88 @@ +/* + * linux/arch/sh/kernel/pci-bigsur.c + * + * By Dustin McIntire (dustin@sensoria.com) (c)2001 + * + * Ported to new API by Paul Mundt <lethal@linux-sh.org>. + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * PCI initialization for the Hitachi Big Sur Evaluation Board + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pci.h> + +#include <asm/io.h> +#include "pci-sh7751.h" +#include <asm/bigsur/bigsur.h> + +#define BIGSUR_PCI_IO 0x4000 +#define BIGSUR_PCI_MEM 0xfd000000 + +static struct resource sh7751_io_resource = { + .name = "SH7751 IO", + .start = BIGSUR_PCI_IO, + .end = BIGSUR_PCI_IO + (64*1024) - 1, + .flags = IORESOURCE_IO, +}; + +static struct resource sh7751_mem_resource = { + .name = "SH7751 mem", + .start = BIGSUR_PCI_MEM, + .end = BIGSUR_PCI_MEM + (64*1024*1024) - 1, + .flags = IORESOURCE_MEM, +}; + +extern struct pci_ops sh7751_pci_ops; + +struct pci_channel board_pci_channels[] = { + { &sh7751_pci_ops, &sh7751_io_resource, &sh7751_mem_resource, 0, 0xff }, + { 0, } +}; + +static struct sh7751_pci_address_map sh7751_pci_map = { + .window0 = { + .base = SH7751_CS3_BASE_ADDR, + .size = BIGSUR_LSR0_SIZE, + }, + + .window1 = { + .base = SH7751_CS3_BASE_ADDR, + .size = BIGSUR_LSR1_SIZE, + }, +}; + +/* + * Initialize the Big Sur PCI interface + * Setup hardware to be Central Funtion + * Copy the BSR regs to the PCI interface + * Setup PCI windows into local RAM + */ +int __init pcibios_init_platform(void) +{ + return sh7751_pcic_init(&sh7751_pci_map); +} + +int pcibios_map_platform_irq(u8 slot, u8 pin) +{ + /* + * The Big Sur can be used in a CPCI chassis, but the SH7751 PCI + * interface is on the wrong end of the board so that it can also + * support a V320 CPI interface chip... Therefor the IRQ mapping is + * somewhat use dependent... I'l assume a linear map for now, i.e. + * INTA=slot0,pin0... INTD=slot3,pin0... + */ + int irq = (slot + pin-1) % 4 + BIGSUR_SH7751_PCI_IRQ_BASE; + + PCIDBG(2, "PCI: Mapping Big Sur IRQ for slot %d, pin %c to irq %d\n", + slot, pin-1+'A', irq); + + return irq; +} + diff --git a/arch/sh/drivers/pci/ops-dreamcast.c b/arch/sh/drivers/pci/ops-dreamcast.c new file mode 100644 index 00000000000..69af80b93e3 --- /dev/null +++ b/arch/sh/drivers/pci/ops-dreamcast.c @@ -0,0 +1,169 @@ +/* + * arch/sh/pci/ops-dreamcast.c + * + * PCI operations for the Sega Dreamcast + * + * Copyright (C) 2001, 2002 M. R. Brown + * Copyright (C) 2002, 2003 Paul Mundt + * + * This file originally bore the message (with enclosed-$): + * Id: pci.c,v 1.3 2003/05/04 19:29:46 lethal Exp + * Dreamcast PCI: Supports SEGA Broadband Adaptor only. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/pci.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/mach/pci.h> + +static struct resource gapspci_io_resource = { + .name = "GAPSPCI IO", + .start = GAPSPCI_BBA_CONFIG, + .end = GAPSPCI_BBA_CONFIG + GAPSPCI_BBA_CONFIG_SIZE - 1, + .flags = IORESOURCE_IO, +}; + +static struct resource gapspci_mem_resource = { + .name = "GAPSPCI mem", + .start = GAPSPCI_DMA_BASE, + .end = GAPSPCI_DMA_BASE + GAPSPCI_DMA_SIZE - 1, + .flags = IORESOURCE_MEM, +}; + +static struct pci_ops gapspci_pci_ops; + +struct pci_channel board_pci_channels[] = { + { &gapspci_pci_ops, &gapspci_io_resource, + &gapspci_mem_resource, 0, 1 }, + { 0, } +}; + +/* + * The !gapspci_config_access case really shouldn't happen, ever, unless + * someone implicitly messes around with the last devfn value.. otherwise we + * only support a single device anyways, and if we didn't have a BBA, we + * wouldn't make it terribly far through the PCI setup anyways. + * + * Also, we could very easily support both Type 0 and Type 1 configurations + * here, but since it doesn't seem that there is any such implementation in + * existance, we don't bother. + * + * I suppose if someone actually gets around to ripping the chip out of + * the BBA and hanging some more devices off of it, then this might be + * something to take into consideration. However, due to the cost of the BBA, + * and the general lack of activity by DC hardware hackers, this doesn't seem + * likely to happen anytime soon. + */ +static int gapspci_config_access(unsigned char bus, unsigned int devfn) +{ + return (bus == 0) && (devfn == 0); +} + +/* + * We can also actually read and write in b/w/l sizes! Thankfully this part + * was at least done right, and we don't have to do the stupid masking and + * shifting that we do on the 7751! Small wonders never cease to amaze. + */ +static int gapspci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val) +{ + *val = 0xffffffff; + + if (!gapspci_config_access(bus->number, devfn)) + return PCIBIOS_DEVICE_NOT_FOUND; + + switch (size) { + case 1: *val = inb(GAPSPCI_BBA_CONFIG+where); break; + case 2: *val = inw(GAPSPCI_BBA_CONFIG+where); break; + case 4: *val = inl(GAPSPCI_BBA_CONFIG+where); break; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int gapspci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val) +{ + if (!gapspci_config_access(bus->number, devfn)) + return PCIBIOS_DEVICE_NOT_FOUND; + + switch (size) { + case 1: outb(( u8)val, GAPSPCI_BBA_CONFIG+where); break; + case 2: outw((u16)val, GAPSPCI_BBA_CONFIG+where); break; + case 4: outl((u32)val, GAPSPCI_BBA_CONFIG+where); break; + } + + return PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops gapspci_pci_ops = { + .read = gapspci_read, + .write = gapspci_write, +}; + +/* + * gapspci init + */ + +int __init gapspci_init(void) +{ + char idbuf[16]; + int i; + + /* + * FIXME: All of this wants documenting to some degree, + * even some basic register definitions would be nice. + * + * I haven't seen anything this ugly since.. maple. + */ + + for (i=0; i<16; i++) + idbuf[i] = inb(GAPSPCI_REGS+i); + + if (strncmp(idbuf, "GAPSPCI_BRIDGE_2", 16)) + return -ENODEV; + + outl(0x5a14a501, GAPSPCI_REGS+0x18); + + for (i=0; i<1000000; i++) + ; + + if (inl(GAPSPCI_REGS+0x18) != 1) + return -EINVAL; + + outl(0x01000000, GAPSPCI_REGS+0x20); + outl(0x01000000, GAPSPCI_REGS+0x24); + + outl(GAPSPCI_DMA_BASE, GAPSPCI_REGS+0x28); + outl(GAPSPCI_DMA_BASE+GAPSPCI_DMA_SIZE, GAPSPCI_REGS+0x2c); + + outl(1, GAPSPCI_REGS+0x14); + outl(1, GAPSPCI_REGS+0x34); + + /* Setting Broadband Adapter */ + outw(0xf900, GAPSPCI_BBA_CONFIG+0x06); + outl(0x00000000, GAPSPCI_BBA_CONFIG+0x30); + outb(0x00, GAPSPCI_BBA_CONFIG+0x3c); + outb(0xf0, GAPSPCI_BBA_CONFIG+0x0d); + outw(0x0006, GAPSPCI_BBA_CONFIG+0x04); + outl(0x00002001, GAPSPCI_BBA_CONFIG+0x10); + outl(0x01000000, GAPSPCI_BBA_CONFIG+0x14); + + return 0; +} + +/* Haven't done anything here as yet */ +char * __devinit pcibios_setup(char *str) +{ + return str; +} diff --git a/arch/sh/drivers/pci/ops-rts7751r2d.c b/arch/sh/drivers/pci/ops-rts7751r2d.c new file mode 100644 index 00000000000..beafa11f4d0 --- /dev/null +++ b/arch/sh/drivers/pci/ops-rts7751r2d.c @@ -0,0 +1,79 @@ +/* + * linux/arch/sh/kernel/pci-rts7751r2d.c + * + * Author: Ian DaSilva (idasilva@mvista.com) + * + * Highly leveraged from pci-bigsur.c, written by Dustin McIntire. + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * PCI initialization for the Renesas SH7751R RTS7751R2D board + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/module.h> + +#include <asm/io.h> +#include "pci-sh7751.h" +#include <asm/rts7751r2d/rts7751r2d.h> + +int __init pcibios_map_platform_irq(u8 slot, u8 pin) +{ + switch (slot) { + case 0: return IRQ_PCISLOT1; /* PCI Extend slot #1 */ + case 1: return IRQ_PCISLOT2; /* PCI Extend slot #2 */ + case 2: return IRQ_PCMCIA; /* PCI Cardbus Bridge */ + case 3: return IRQ_PCIETH; /* Realtek Ethernet controller */ + default: + printk("PCI: Bad IRQ mapping request for slot %d\n", slot); + return -1; + } +} + +static struct resource sh7751_io_resource = { + .name = "SH7751_IO", + .start = 0x4000, + .end = 0x4000 + SH7751_PCI_IO_SIZE - 1, + .flags = IORESOURCE_IO +}; + +static struct resource sh7751_mem_resource = { + .name = "SH7751_mem", + .start = SH7751_PCI_MEMORY_BASE, + .end = SH7751_PCI_MEMORY_BASE + SH7751_PCI_MEM_SIZE - 1, + .flags = IORESOURCE_MEM +}; + +extern struct pci_ops sh7751_pci_ops; + +struct pci_channel board_pci_channels[] = { + { &sh7751_pci_ops, &sh7751_io_resource, &sh7751_mem_resource, 0, 0xff }, + { NULL, NULL, NULL, 0, 0 }, +}; +EXPORT_SYMBOL(board_pci_channels); + +static struct sh7751_pci_address_map sh7751_pci_map = { + .window0 = { + .base = SH7751_CS3_BASE_ADDR, + .size = 0x04000000, + }, + + .window1 = { + .base = 0x00000000, /* Unused */ + .size = 0x00000000, /* Unused */ + }, + + .flags = SH7751_PCIC_NO_RESET, +}; + +int __init pcibios_init_platform(void) +{ + return sh7751_pcic_init(&sh7751_pci_map); +} + diff --git a/arch/sh/drivers/pci/ops-sh03.c b/arch/sh/drivers/pci/ops-sh03.c new file mode 100644 index 00000000000..df219973234 --- /dev/null +++ b/arch/sh/drivers/pci/ops-sh03.c @@ -0,0 +1,45 @@ +/* + * linux/arch/sh/drivers/pci/ops-sh03.c + * + * PCI initialization for the Interface CTP/PCI-SH03 board + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <asm/io.h> +#include "pci-sh7751.h" + +/* + * Description: This function sets up and initializes the pcic, sets + * up the BARS, maps the DRAM into the address space etc, etc. + */ +int __init pcibios_init_platform(void) +{ + return 1; +} + +static struct resource sh7751_io_resource = { + .name = "SH03 IO", + .start = SH7751_PCI_IO_BASE, + .end = SH7751_PCI_IO_BASE + SH7751_PCI_IO_SIZE - 1, + .flags = IORESOURCE_IO +}; + +static struct resource sh7751_mem_resource = { + .name = "SH03 mem", + .start = SH7751_PCI_MEMORY_BASE, + .end = SH7751_PCI_MEMORY_BASE + SH7751_PCI_MEM_SIZE - 1, + .flags = IORESOURCE_MEM +}; + +extern struct pci_ops sh7751_pci_ops; + +struct pci_channel board_pci_channels[] = { + { &sh7751_pci_ops, &sh7751_io_resource, &sh7751_mem_resource, 0, 0xff }, + { NULL, NULL, NULL, 0, 0 }, +}; + diff --git a/arch/sh/drivers/pci/ops-snapgear.c b/arch/sh/drivers/pci/ops-snapgear.c new file mode 100644 index 00000000000..6fdb9765c99 --- /dev/null +++ b/arch/sh/drivers/pci/ops-snapgear.c @@ -0,0 +1,102 @@ +/* + * arch/sh/drivers/pci/ops-snapgear.c + * + * Author: David McCullough <davidm@snapgear.com> + * + * Ported to new API by Paul Mundt <lethal@linux-sh.org> + * + * Highly leveraged from pci-bigsur.c, written by Dustin McIntire. + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * PCI initialization for the SnapGear boards + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pci.h> + +#include <asm/io.h> +#include "pci-sh7751.h" + +#define SNAPGEAR_PCI_IO 0x4000 +#define SNAPGEAR_PCI_MEM 0xfd000000 + +/* PCI: default LOCAL memory window sizes (seen from PCI bus) */ +#define SNAPGEAR_LSR0_SIZE (64*(1<<20)) //64MB +#define SNAPGEAR_LSR1_SIZE (64*(1<<20)) //64MB + +static struct resource sh7751_io_resource = { + .name = "SH7751 IO", + .start = SNAPGEAR_PCI_IO, + .end = SNAPGEAR_PCI_IO + (64*1024) - 1, /* 64KiB I/O */ + .flags = IORESOURCE_IO, +}; + +static struct resource sh7751_mem_resource = { + .name = "SH7751 mem", + .start = SNAPGEAR_PCI_MEM, + .end = SNAPGEAR_PCI_MEM + (64*1024*1024) - 1, /* 64MiB mem */ + .flags = IORESOURCE_MEM, +}; + +extern struct pci_ops sh7751_pci_ops; + +struct pci_channel board_pci_channels[] = { + { &sh7751_pci_ops, &sh7751_io_resource, &sh7751_mem_resource, 0, 0xff }, + { 0, } +}; + +static struct sh7751_pci_address_map sh7751_pci_map = { + .window0 = { + .base = SH7751_CS2_BASE_ADDR, + .size = SNAPGEAR_LSR0_SIZE, + }, + + .window1 = { + .base = SH7751_CS2_BASE_ADDR, + .size = SNAPGEAR_LSR1_SIZE, + }, + + .flags = SH7751_PCIC_NO_RESET, +}; + +/* + * Initialize the SnapGear PCI interface + * Setup hardware to be Central Funtion + * Copy the BSR regs to the PCI interface + * Setup PCI windows into local RAM + */ +int __init pcibios_init_platform(void) +{ + return sh7751_pcic_init(&sh7751_pci_map); +} + +int __init pcibios_map_platform_irq(u8 slot, u8 pin) +{ + int irq = -1; + + switch (slot) { + case 8: /* the PCI bridge */ break; + case 11: irq = 8; break; /* USB */ + case 12: irq = 11; break; /* PCMCIA */ + case 13: irq = 5; break; /* eth0 */ + case 14: irq = 8; break; /* eth1 */ + case 15: irq = 11; break; /* safenet (unused) */ + } + + printk("PCI: Mapping SnapGear IRQ for slot %d, pin %c to irq %d\n", + slot, pin - 1 + 'A', irq); + + return irq; +} + +void __init pcibios_fixup(void) +{ + /* Nothing to fixup .. */ +} + diff --git a/arch/sh/drivers/pci/pci-auto.c b/arch/sh/drivers/pci/pci-auto.c new file mode 100644 index 00000000000..4cef4d1d8c8 --- /dev/null +++ b/arch/sh/drivers/pci/pci-auto.c @@ -0,0 +1,555 @@ +/* + * PCI autoconfiguration library + * + * Author: Matt Porter <mporter@mvista.com> + * + * Copyright 2000, 2001 MontaVista Software Inc. + * Copyright 2001 Bradley D. LaRonde <brad@ltc.com> + * Copyright 2003 Paul Mundt <lethal@linux-sh.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +/* + * Modified for MIPS by Jun Sun, jsun@mvista.com + * + * . Simplify the interface between pci_auto and the rest: a single function. + * . Assign resources from low address to upper address. + * . change most int to u32. + * + * Further modified to include it as mips generic code, ppopov@mvista.com. + * + * 2001-10-26 Bradley D. LaRonde <brad@ltc.com> + * - Add a top_bus argument to the "early config" functions so that + * they can set a fake parent bus pointer to convince the underlying + * pci ops to use type 1 configuration for sub busses. + * - Set bridge base and limit registers correctly. + * - Align io and memory base properly before and after bridge setup. + * - Don't fall through to pci_setup_bars for bridge. + * - Reformat the debug output to look more like lspci's output. + * + * Cloned for SuperH by M. R. Brown, mrbrown@0xd6.org + * + * 2003-08-05 Paul Mundt <lethal@linux-sh.org> + * - Don't update the BAR values on systems that already have valid addresses + * and don't want these updated for whatever reason, by way of a new config + * option check. However, we still read in the old BAR values so that they + * can still be reported through the debug output. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/pci.h> + +#undef DEBUG +#ifdef DEBUG +#define DBG(x...) printk(x) +#else +#define DBG(x...) +#endif + +/* + * These functions are used early on before PCI scanning is done + * and all of the pci_dev and pci_bus structures have been created. + */ +static struct pci_dev *fake_pci_dev(struct pci_channel *hose, + int top_bus, int busnr, int devfn) +{ + static struct pci_dev dev; + static struct pci_bus bus; + + dev.bus = &bus; + dev.sysdata = hose; + dev.devfn = devfn; + bus.number = busnr; + bus.ops = hose->pci_ops; + + if(busnr != top_bus) + /* Fake a parent bus structure. */ + bus.parent = &bus; + else + bus.parent = NULL; + + return &dev; +} + +#define EARLY_PCI_OP(rw, size, type) \ +int early_##rw##_config_##size(struct pci_channel *hose, \ + int top_bus, int bus, int devfn, int offset, type value) \ +{ \ + return pci_##rw##_config_##size( \ + fake_pci_dev(hose, top_bus, bus, devfn), \ + offset, value); \ +} + +EARLY_PCI_OP(read, byte, u8 *) +EARLY_PCI_OP(read, word, u16 *) +EARLY_PCI_OP(read, dword, u32 *) +EARLY_PCI_OP(write, byte, u8) +EARLY_PCI_OP(write, word, u16) +EARLY_PCI_OP(write, dword, u32) + +static struct resource *io_resource_inuse; +static struct resource *mem_resource_inuse; + +static u32 pciauto_lower_iospc; +static u32 pciauto_upper_iospc; + +static u32 pciauto_lower_memspc; +static u32 pciauto_upper_memspc; + +static void __init +pciauto_setup_bars(struct pci_channel *hose, + int top_bus, + int current_bus, + int pci_devfn, + int bar_limit) +{ + u32 bar_response, bar_size, bar_value; + u32 bar, addr_mask, bar_nr = 0; + u32 * upper_limit; + u32 * lower_limit; + int found_mem64 = 0; + + for (bar = PCI_BASE_ADDRESS_0; bar <= bar_limit; bar+=4) { +#if !defined(CONFIG_SH_HS7751RVOIP) && !defined(CONFIG_SH_RTS7751R2D) + u32 bar_addr; + + /* Read the old BAR value */ + early_read_config_dword(hose, top_bus, + current_bus, + pci_devfn, + bar, + &bar_addr); +#endif + + /* Tickle the BAR and get the response */ + early_write_config_dword(hose, top_bus, + current_bus, + pci_devfn, + bar, + 0xffffffff); + + early_read_config_dword(hose, top_bus, + current_bus, + pci_devfn, + bar, + &bar_response); + +#if !defined(CONFIG_SH_HS7751RVOIP) && !defined(CONFIG_SH_RTS7751R2D) + /* + * Write the old BAR value back out, only update the BAR + * if we implicitly want resources to be updated, which + * is done by the generic code further down. -- PFM. + */ + early_write_config_dword(hose, top_bus, + current_bus, + pci_devfn, + bar, + bar_addr); +#endif + + /* If BAR is not implemented go to the next BAR */ + if (!bar_response) + continue; + + /* + * Workaround for a BAR that doesn't use its upper word, + * like the ALi 1535D+ PCI DC-97 Controller Modem (M5457). + * bdl <brad@ltc.com> + */ + if (!(bar_response & 0xffff0000)) + bar_response |= 0xffff0000; + +retry: + /* Check the BAR type and set our address mask */ + if (bar_response & PCI_BASE_ADDRESS_SPACE) { + addr_mask = PCI_BASE_ADDRESS_IO_MASK; + upper_limit = &pciauto_upper_iospc; + lower_limit = &pciauto_lower_iospc; + DBG(" I/O"); + } else { + if ((bar_response & PCI_BASE_ADDRESS_MEM_TYPE_MASK) == + PCI_BASE_ADDRESS_MEM_TYPE_64) + found_mem64 = 1; + + addr_mask = PCI_BASE_ADDRESS_MEM_MASK; + upper_limit = &pciauto_upper_memspc; + lower_limit = &pciauto_lower_memspc; + DBG(" Mem"); + } + + + /* Calculate requested size */ + bar_size = ~(bar_response & addr_mask) + 1; + + /* Allocate a base address */ + bar_value = ((*lower_limit - 1) & ~(bar_size - 1)) + bar_size; + + if ((bar_value + bar_size) > *upper_limit) { + if (bar_response & PCI_BASE_ADDRESS_SPACE) { + if (io_resource_inuse->child) { + io_resource_inuse = + io_resource_inuse->child; + pciauto_lower_iospc = + io_resource_inuse->start; + pciauto_upper_iospc = + io_resource_inuse->end + 1; + goto retry; + } + + } else { + if (mem_resource_inuse->child) { + mem_resource_inuse = + mem_resource_inuse->child; + pciauto_lower_memspc = + mem_resource_inuse->start; + pciauto_upper_memspc = + mem_resource_inuse->end + 1; + goto retry; + } + } + DBG(" unavailable -- skipping, value %x size %x\n", + bar_value, bar_size); + continue; + } + +#ifdef CONFIG_PCI_AUTO_UPDATE_RESOURCES + /* Write it out and update our limit */ + early_write_config_dword(hose, top_bus, current_bus, pci_devfn, + bar, bar_value); +#endif + + *lower_limit = bar_value + bar_size; + + /* + * If we are a 64-bit decoder then increment to the + * upper 32 bits of the bar and force it to locate + * in the lower 4GB of memory. + */ + if (found_mem64) { + bar += 4; + early_write_config_dword(hose, top_bus, + current_bus, + pci_devfn, + bar, + 0x00000000); + } + + DBG(" at 0x%.8x [size=0x%x]\n", bar_value, bar_size); + + bar_nr++; + } + +} + +static void __init +pciauto_prescan_setup_bridge(struct pci_channel *hose, + int top_bus, + int current_bus, + int pci_devfn, + int sub_bus) +{ + /* Configure bus number registers */ + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_PRIMARY_BUS, current_bus); + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_SECONDARY_BUS, sub_bus + 1); + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_SUBORDINATE_BUS, 0xff); + + /* Align memory and I/O to 1MB and 4KB boundaries. */ + pciauto_lower_memspc = (pciauto_lower_memspc + (0x100000 - 1)) + & ~(0x100000 - 1); + pciauto_lower_iospc = (pciauto_lower_iospc + (0x1000 - 1)) + & ~(0x1000 - 1); + + /* Set base (lower limit) of address range behind bridge. */ + early_write_config_word(hose, top_bus, current_bus, pci_devfn, + PCI_MEMORY_BASE, pciauto_lower_memspc >> 16); + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_IO_BASE, (pciauto_lower_iospc & 0x0000f000) >> 8); + early_write_config_word(hose, top_bus, current_bus, pci_devfn, + PCI_IO_BASE_UPPER16, pciauto_lower_iospc >> 16); + + /* We don't support prefetchable memory for now, so disable */ + early_write_config_word(hose, top_bus, current_bus, pci_devfn, + PCI_PREF_MEMORY_BASE, 0); + early_write_config_word(hose, top_bus, current_bus, pci_devfn, + PCI_PREF_MEMORY_LIMIT, 0); +} + +static void __init +pciauto_postscan_setup_bridge(struct pci_channel *hose, + int top_bus, + int current_bus, + int pci_devfn, + int sub_bus) +{ + u32 temp; + + /* + * [jsun] we always bump up baselines a little, so that if there + * nothing behind P2P bridge, we don't wind up overlapping IO/MEM + * spaces. + */ + pciauto_lower_memspc += 1; + pciauto_lower_iospc += 1; + + /* Configure bus number registers */ + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_SUBORDINATE_BUS, sub_bus); + + /* Set upper limit of address range behind bridge. */ + early_write_config_word(hose, top_bus, current_bus, pci_devfn, + PCI_MEMORY_LIMIT, pciauto_lower_memspc >> 16); + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_IO_LIMIT, (pciauto_lower_iospc & 0x0000f000) >> 8); + early_write_config_word(hose, top_bus, current_bus, pci_devfn, + PCI_IO_LIMIT_UPPER16, pciauto_lower_iospc >> 16); + + /* Align memory and I/O to 1MB and 4KB boundaries. */ + pciauto_lower_memspc = (pciauto_lower_memspc + (0x100000 - 1)) + & ~(0x100000 - 1); + pciauto_lower_iospc = (pciauto_lower_iospc + (0x1000 - 1)) + & ~(0x1000 - 1); + + /* Enable memory and I/O accesses, enable bus master */ + early_read_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_COMMAND, &temp); + early_write_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_COMMAND, temp | PCI_COMMAND_IO | PCI_COMMAND_MEMORY + | PCI_COMMAND_MASTER); +} + +static void __init +pciauto_prescan_setup_cardbus_bridge(struct pci_channel *hose, + int top_bus, + int current_bus, + int pci_devfn, + int sub_bus) +{ + /* Configure bus number registers */ + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_PRIMARY_BUS, current_bus); + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_SECONDARY_BUS, sub_bus + 1); + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_SUBORDINATE_BUS, 0xff); + + /* Align memory and I/O to 4KB and 4 byte boundaries. */ + pciauto_lower_memspc = (pciauto_lower_memspc + (0x1000 - 1)) + & ~(0x1000 - 1); + pciauto_lower_iospc = (pciauto_lower_iospc + (0x4 - 1)) + & ~(0x4 - 1); + + early_write_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_CB_MEMORY_BASE_0, pciauto_lower_memspc); + early_write_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_CB_IO_BASE_0, pciauto_lower_iospc); +} + +static void __init +pciauto_postscan_setup_cardbus_bridge(struct pci_channel *hose, + int top_bus, + int current_bus, + int pci_devfn, + int sub_bus) +{ + u32 temp; + +#if !defined(CONFIG_SH_HS7751RVOIP) && !defined(CONFIG_SH_RTS7751R2D) + /* + * [jsun] we always bump up baselines a little, so that if there + * nothing behind P2P bridge, we don't wind up overlapping IO/MEM + * spaces. + */ + pciauto_lower_memspc += 1; + pciauto_lower_iospc += 1; +#endif + + /* + * Configure subordinate bus number. The PCI subsystem + * bus scan will renumber buses (reserving three additional + * for this PCI<->CardBus bridge for the case where a CardBus + * adapter contains a P2P or CB2CB bridge. + */ + + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_SUBORDINATE_BUS, sub_bus); + + /* + * Reserve an additional 4MB for mem space and 16KB for + * I/O space. This should cover any additional space + * requirement of unusual CardBus devices with + * additional bridges that can consume more address space. + * + * Although pcmcia-cs currently will reprogram bridge + * windows, the goal is to add an option to leave them + * alone and use the bridge window ranges as the regions + * that are searched for free resources upon hot-insertion + * of a device. This will allow a PCI<->CardBus bridge + * configured by this routine to happily live behind a + * P2P bridge in a system. + */ +#if defined(CONFIG_SH_HS7751RVOIP) || defined(CONFIG_SH_RTS7751R2D) + pciauto_lower_memspc += 0x00400000; + pciauto_lower_iospc += 0x00004000; +#endif + + /* Align memory and I/O to 4KB and 4 byte boundaries. */ + pciauto_lower_memspc = (pciauto_lower_memspc + (0x1000 - 1)) + & ~(0x1000 - 1); + pciauto_lower_iospc = (pciauto_lower_iospc + (0x4 - 1)) + & ~(0x4 - 1); + /* Set up memory and I/O filter limits, assume 32-bit I/O space */ + early_write_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_CB_MEMORY_LIMIT_0, pciauto_lower_memspc - 1); + early_write_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_CB_IO_LIMIT_0, pciauto_lower_iospc - 1); + + /* Enable memory and I/O accesses, enable bus master */ + early_read_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_COMMAND, &temp); + early_write_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_COMMAND, temp | PCI_COMMAND_IO | PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER); +} + +#define PCIAUTO_IDE_MODE_MASK 0x05 + +static int __init +pciauto_bus_scan(struct pci_channel *hose, int top_bus, int current_bus) +{ + int sub_bus; + u32 pci_devfn, pci_class, cmdstat, found_multi=0; + unsigned short vid, did; + unsigned char header_type; + int devfn_start = 0; + int devfn_stop = 0xff; + + sub_bus = current_bus; + + if (hose->first_devfn) + devfn_start = hose->first_devfn; + if (hose->last_devfn) + devfn_stop = hose->last_devfn; + + for (pci_devfn=devfn_start; pci_devfn<devfn_stop; pci_devfn++) { + + if (PCI_FUNC(pci_devfn) && !found_multi) + continue; + + early_read_config_word(hose, top_bus, current_bus, pci_devfn, + PCI_VENDOR_ID, &vid); + + if (vid == 0xffff) continue; + + early_read_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_HEADER_TYPE, &header_type); + + if (!PCI_FUNC(pci_devfn)) + found_multi = header_type & 0x80; + + early_read_config_word(hose, top_bus, current_bus, pci_devfn, + PCI_DEVICE_ID, &did); + + early_read_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_CLASS_REVISION, &pci_class); + + DBG("%.2x:%.2x.%x Class %.4x: %.4x:%.4x", + current_bus, PCI_SLOT(pci_devfn), PCI_FUNC(pci_devfn), + pci_class >> 16, vid, did); + if (pci_class & 0xff) + DBG(" (rev %.2x)", pci_class & 0xff); + DBG("\n"); + + if ((pci_class >> 16) == PCI_CLASS_BRIDGE_PCI) { + DBG(" Bridge: primary=%.2x, secondary=%.2x\n", + current_bus, sub_bus + 1); +#if defined(CONFIG_SH_HS7751RVOIP) || defined(CONFIG_SH_RTS7751R2D) + pciauto_setup_bars(hose, top_bus, current_bus, pci_devfn, PCI_BASE_ADDRESS_1); +#endif + pciauto_prescan_setup_bridge(hose, top_bus, current_bus, + pci_devfn, sub_bus); + DBG("Scanning sub bus %.2x, I/O 0x%.8x, Mem 0x%.8x\n", + sub_bus + 1, + pciauto_lower_iospc, pciauto_lower_memspc); + sub_bus = pciauto_bus_scan(hose, top_bus, sub_bus+1); + DBG("Back to bus %.2x\n", current_bus); + pciauto_postscan_setup_bridge(hose, top_bus, current_bus, + pci_devfn, sub_bus); + continue; + } else if ((pci_class >> 16) == PCI_CLASS_BRIDGE_CARDBUS) { + DBG(" CARDBUS Bridge: primary=%.2x, secondary=%.2x\n", + current_bus, sub_bus + 1); + DBG("PCI Autoconfig: Found CardBus bridge, device %d function %d\n", PCI_SLOT(pci_devfn), PCI_FUNC(pci_devfn)); + /* Place CardBus Socket/ExCA registers */ + pciauto_setup_bars(hose, top_bus, current_bus, pci_devfn, PCI_BASE_ADDRESS_0); + + pciauto_prescan_setup_cardbus_bridge(hose, top_bus, + current_bus, pci_devfn, sub_bus); + + DBG("Scanning sub bus %.2x, I/O 0x%.8x, Mem 0x%.8x\n", + sub_bus + 1, + pciauto_lower_iospc, pciauto_lower_memspc); + sub_bus = pciauto_bus_scan(hose, top_bus, sub_bus+1); + DBG("Back to bus %.2x, sub_bus is %x\n", current_bus, sub_bus); + pciauto_postscan_setup_cardbus_bridge(hose, top_bus, + current_bus, pci_devfn, sub_bus); + continue; + } else if ((pci_class >> 16) == PCI_CLASS_STORAGE_IDE) { + + unsigned char prg_iface; + + early_read_config_byte(hose, top_bus, current_bus, + pci_devfn, PCI_CLASS_PROG, &prg_iface); + if (!(prg_iface & PCIAUTO_IDE_MODE_MASK)) { + DBG("Skipping legacy mode IDE controller\n"); + continue; + } + } + + /* + * Found a peripheral, enable some standard + * settings + */ + early_read_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_COMMAND, &cmdstat); + early_write_config_dword(hose, top_bus, current_bus, pci_devfn, + PCI_COMMAND, cmdstat | PCI_COMMAND_IO | + PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER); +#if !defined(CONFIG_SH_HS7751RVOIP) && !defined(CONFIG_SH_RTS7751R2D) + early_write_config_byte(hose, top_bus, current_bus, pci_devfn, + PCI_LATENCY_TIMER, 0x80); +#endif + + /* Allocate PCI I/O and/or memory space */ + pciauto_setup_bars(hose, top_bus, current_bus, pci_devfn, PCI_BASE_ADDRESS_5); + } + return sub_bus; +} + +int __init +pciauto_assign_resources(int busno, struct pci_channel *hose) +{ + /* setup resource limits */ + io_resource_inuse = hose->io_resource; + mem_resource_inuse = hose->mem_resource; + + pciauto_lower_iospc = io_resource_inuse->start; + pciauto_upper_iospc = io_resource_inuse->end + 1; + pciauto_lower_memspc = mem_resource_inuse->start; + pciauto_upper_memspc = mem_resource_inuse->end + 1; + DBG("Autoconfig PCI channel 0x%p\n", hose); + DBG("Scanning bus %.2x, I/O 0x%.8x:0x%.8x, Mem 0x%.8x:0x%.8x\n", + busno, pciauto_lower_iospc, pciauto_upper_iospc, + pciauto_lower_memspc, pciauto_upper_memspc); + + return pciauto_bus_scan(hose, busno, busno); +} diff --git a/arch/sh/drivers/pci/pci-sh7751.c b/arch/sh/drivers/pci/pci-sh7751.c new file mode 100644 index 00000000000..30b14ac7ae5 --- /dev/null +++ b/arch/sh/drivers/pci/pci-sh7751.c @@ -0,0 +1,417 @@ +/* + * Low-Level PCI Support for the SH7751 + * + * Dustin McIntire (dustin@sensoria.com) + * Derived from arch/i386/kernel/pci-*.c which bore the message: + * (c) 1999--2000 Martin Mares <mj@ucw.cz> + * + * Ported to the new API by Paul Mundt <lethal@linux-sh.org> + * With cleanup by Paul van Gool <pvangool@mimotech.com> + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + */ + +#undef DEBUG + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/errno.h> +#include <linux/irq.h> +#include <linux/delay.h> + +#include <asm/machvec.h> +#include <asm/io.h> +#include "pci-sh7751.h" + +static unsigned int pci_probe = PCI_PROBE_CONF1; +extern int pci_fixup_pcic(void); + +void pcibios_fixup_irqs(void) __attribute__ ((weak)); + +/* + * Direct access to PCI hardware... + */ + +#define CONFIG_CMD(bus, devfn, where) (0x80000000 | (bus->number << 16) | (devfn << 8) | (where & ~3)) + +/* + * Functions for accessing PCI configuration space with type 1 accesses + */ +static int sh7751_pci_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + unsigned long flags; + u32 data; + + /* + * PCIPDR may only be accessed as 32 bit words, + * so we must do byte alignment by hand + */ + local_irq_save(flags); + outl(CONFIG_CMD(bus,devfn,where), PCI_REG(SH7751_PCIPAR)); + data = inl(PCI_REG(SH7751_PCIPDR)); + local_irq_restore(flags); + + switch (size) { + case 1: + *val = (data >> ((where & 3) << 3)) & 0xff; + break; + case 2: + *val = (data >> ((where & 2) << 3)) & 0xffff; + break; + case 4: + *val = data; + break; + default: + return PCIBIOS_FUNC_NOT_SUPPORTED; + } + + return PCIBIOS_SUCCESSFUL; +} + +/* + * Since SH7751 only does 32bit access we'll have to do a read, + * mask,write operation. + * We'll allow an odd byte offset, though it should be illegal. + */ +static int sh7751_pci_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + unsigned long flags; + int shift; + u32 data; + + local_irq_save(flags); + outl(CONFIG_CMD(bus,devfn,where), PCI_REG(SH7751_PCIPAR)); + data = inl(PCI_REG(SH7751_PCIPDR)); + local_irq_restore(flags); + + switch (size) { + case 1: + shift = (where & 3) << 3; + data &= ~(0xff << shift); + data |= ((val & 0xff) << shift); + break; + case 2: + shift = (where & 2) << 3; + data &= ~(0xffff << shift); + data |= ((val & 0xffff) << shift); + break; + case 4: + data = val; + break; + default: + return PCIBIOS_FUNC_NOT_SUPPORTED; + } + + outl(data, PCI_REG(SH7751_PCIPDR)); + + return PCIBIOS_SUCCESSFUL; +} + +#undef CONFIG_CMD + +struct pci_ops sh7751_pci_ops = { + .read = sh7751_pci_read, + .write = sh7751_pci_write, +}; + +static int __init pci_check_direct(void) +{ + unsigned int tmp, id; + + /* check for SH7751/SH7751R hardware */ + id = inl(SH7751_PCIREG_BASE+SH7751_PCICONF0); + if (id != ((SH7751_DEVICE_ID << 16) | SH7751_VENDOR_ID) && + id != ((SH7751R_DEVICE_ID << 16) | SH7751_VENDOR_ID)) { + pr_debug("PCI: This is not an SH7751(R) (%x)\n", id); + return -ENODEV; + } + + /* + * Check if configuration works. + */ + if (pci_probe & PCI_PROBE_CONF1) { + tmp = inl (PCI_REG(SH7751_PCIPAR)); + outl (0x80000000, PCI_REG(SH7751_PCIPAR)); + if (inl (PCI_REG(SH7751_PCIPAR)) == 0x80000000) { + outl (tmp, PCI_REG(SH7751_PCIPAR)); + printk(KERN_INFO "PCI: Using configuration type 1\n"); + request_region(PCI_REG(SH7751_PCIPAR), 8, "PCI conf1"); + return 0; + } + outl (tmp, PCI_REG(SH7751_PCIPAR)); + } + + pr_debug("PCI: pci_check_direct failed\n"); + return -EINVAL; +} + +/***************************************************************************************/ + +/* + * Handle bus scanning and fixups .... + */ + +static void __init pci_fixup_ide_bases(struct pci_dev *d) +{ + int i; + + /* + * PCI IDE controllers use non-standard I/O port decoding, respect it. + */ + if ((d->class >> 8) != PCI_CLASS_STORAGE_IDE) + return; + pr_debug("PCI: IDE base address fixup for %s\n", pci_name(d)); + for(i=0; i<4; i++) { + struct resource *r = &d->resource[i]; + if ((r->start & ~0x80) == 0x374) { + r->start |= 2; + r->end = r->start; + } + } +} + +DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, pci_fixup_ide_bases); + +/* + * Called after each bus is probed, but before its children + * are examined. + */ + +void __init pcibios_fixup_bus(struct pci_bus *b) +{ + pci_read_bridge_bases(b); +} + +/* + * Initialization. Try all known PCI access methods. Note that we support + * using both PCI BIOS and direct access: in such cases, we use I/O ports + * to access config space. + * + * Note that the platform specific initialization (BSC registers, and memory + * space mapping) will be called via the machine vectors (sh_mv.mv_pci_init()) if it + * exitst and via the platform defined function pcibios_init_platform(). + * See pci_bigsur.c for implementation; + * + * The BIOS version of the pci functions is not yet implemented but it is left + * in for completeness. Currently an error will be genereated at compile time. + */ + +static int __init sh7751_pci_init(void) +{ + int ret; + + pr_debug("PCI: Starting intialization.\n"); + if ((ret = pci_check_direct()) != 0) + return ret; + + return pcibios_init_platform(); +} + +subsys_initcall(sh7751_pci_init); + +static int __init __area_sdram_check(unsigned int area) +{ + u32 word; + + word = inl(SH7751_BCR1); + /* check BCR for SDRAM in area */ + if(((word >> area) & 1) == 0) { + printk("PCI: Area %d is not configured for SDRAM. BCR1=0x%x\n", + area, word); + return 0; + } + outl(word, PCI_REG(SH7751_PCIBCR1)); + + word = (u16)inw(SH7751_BCR2); + /* check BCR2 for 32bit SDRAM interface*/ + if(((word >> (area << 1)) & 0x3) != 0x3) { + printk("PCI: Area %d is not 32 bit SDRAM. BCR2=0x%x\n", + area, word); + return 0; + } + outl(word, PCI_REG(SH7751_PCIBCR2)); + + return 1; +} + +int __init sh7751_pcic_init(struct sh7751_pci_address_map *map) +{ + u32 reg; + u32 word; + + /* Set the BCR's to enable PCI access */ + reg = inl(SH7751_BCR1); + reg |= 0x80000; + outl(reg, SH7751_BCR1); + + /* Turn the clocks back on (not done in reset)*/ + outl(0, PCI_REG(SH7751_PCICLKR)); + /* Clear Powerdown IRQ's (not done in reset) */ + word = SH7751_PCIPINT_D3 | SH7751_PCIPINT_D0; + outl(word, PCI_REG(SH7751_PCIPINT)); + + /* + * This code is unused for some boards as it is done in the + * bootloader and doing it here means the MAC addresses loaded + * by the bootloader get lost. + */ + if (!(map->flags & SH7751_PCIC_NO_RESET)) { + /* toggle PCI reset pin */ + word = SH7751_PCICR_PREFIX | SH7751_PCICR_PRST; + outl(word,PCI_REG(SH7751_PCICR)); + /* Wait for a long time... not 1 sec. but long enough */ + mdelay(100); + word = SH7751_PCICR_PREFIX; + outl(word,PCI_REG(SH7751_PCICR)); + } + + /* set the command/status bits to: + * Wait Cycle Control + Parity Enable + Bus Master + + * Mem space enable + */ + word = SH7751_PCICONF1_WCC | SH7751_PCICONF1_PER | + SH7751_PCICONF1_BUM | SH7751_PCICONF1_MES; + outl(word, PCI_REG(SH7751_PCICONF1)); + + /* define this host as the host bridge */ + word = SH7751_PCI_HOST_BRIDGE << 24; + outl(word, PCI_REG(SH7751_PCICONF2)); + + /* Set IO and Mem windows to local address + * Make PCI and local address the same for easy 1 to 1 mapping + * Window0 = map->window0.size @ non-cached area base = SDRAM + * Window1 = map->window1.size @ cached area base = SDRAM + */ + word = map->window0.size - 1; + outl(word, PCI_REG(SH7751_PCILSR0)); + word = map->window1.size - 1; + outl(word, PCI_REG(SH7751_PCILSR1)); + /* Set the values on window 0 PCI config registers */ + word = P2SEGADDR(map->window0.base); + outl(word, PCI_REG(SH7751_PCILAR0)); + outl(word, PCI_REG(SH7751_PCICONF5)); + /* Set the values on window 1 PCI config registers */ + word = PHYSADDR(map->window1.base); + outl(word, PCI_REG(SH7751_PCILAR1)); + outl(word, PCI_REG(SH7751_PCICONF6)); + + /* Set the local 16MB PCI memory space window to + * the lowest PCI mapped address + */ + word = PCIBIOS_MIN_MEM & SH7751_PCIMBR_MASK; + PCIDBG(2,"PCI: Setting upper bits of Memory window to 0x%x\n", word); + outl(word , PCI_REG(SH7751_PCIMBR)); + + /* Map IO space into PCI IO window + * The IO window is 64K-PCIBIOS_MIN_IO in size + * IO addresses will be translated to the + * PCI IO window base address + */ + PCIDBG(3,"PCI: Mapping IO address 0x%x - 0x%x to base 0x%x\n", PCIBIOS_MIN_IO, + (64*1024), SH7751_PCI_IO_BASE+PCIBIOS_MIN_IO); + + /* + * XXX: For now, leave this board-specific. In the event we have other + * boards that need to do similar work, this can be wrapped. + */ +#ifdef CONFIG_SH_BIGSUR + bigsur_port_map(PCIBIOS_MIN_IO, (64*1024), SH7751_PCI_IO_BASE+PCIBIOS_MIN_IO,0); +#endif + + /* Make sure the MSB's of IO window are set to access PCI space correctly */ + word = PCIBIOS_MIN_IO & SH7751_PCIIOBR_MASK; + PCIDBG(2,"PCI: Setting upper bits of IO window to 0x%x\n", word); + outl(word, PCI_REG(SH7751_PCIIOBR)); + + /* Set PCI WCRx, BCRx's, copy from BSC locations */ + + /* check BCR for SDRAM in specified area */ + switch (map->window0.base) { + case SH7751_CS0_BASE_ADDR: word = __area_sdram_check(0); break; + case SH7751_CS1_BASE_ADDR: word = __area_sdram_check(1); break; + case SH7751_CS2_BASE_ADDR: word = __area_sdram_check(2); break; + case SH7751_CS3_BASE_ADDR: word = __area_sdram_check(3); break; + case SH7751_CS4_BASE_ADDR: word = __area_sdram_check(4); break; + case SH7751_CS5_BASE_ADDR: word = __area_sdram_check(5); break; + case SH7751_CS6_BASE_ADDR: word = __area_sdram_check(6); break; + } + + if (!word) + return 0; + + /* configure the wait control registers */ + word = inl(SH7751_WCR1); + outl(word, PCI_REG(SH7751_PCIWCR1)); + word = inl(SH7751_WCR2); + outl(word, PCI_REG(SH7751_PCIWCR2)); + word = inl(SH7751_WCR3); + outl(word, PCI_REG(SH7751_PCIWCR3)); + word = inl(SH7751_MCR); + outl(word, PCI_REG(SH7751_PCIMCR)); + + /* NOTE: I'm ignoring the PCI error IRQs for now.. + * TODO: add support for the internal error interrupts and + * DMA interrupts... + */ + +#ifdef CONFIG_SH_RTS7751R2D + pci_fixup_pcic(); +#endif + + /* SH7751 init done, set central function init complete */ + /* use round robin mode to stop a device starving/overruning */ + word = SH7751_PCICR_PREFIX | SH7751_PCICR_CFIN | SH7751_PCICR_ARBM; + outl(word,PCI_REG(SH7751_PCICR)); + + return 1; +} + +char * __init pcibios_setup(char *str) +{ + if (!strcmp(str, "off")) { + pci_probe = 0; + return NULL; + } + + return str; +} + +/* + * IRQ functions + */ +static u8 __init sh7751_no_swizzle(struct pci_dev *dev, u8 *pin) +{ + /* no swizzling */ + return PCI_SLOT(dev->devfn); +} + +static int sh7751_pci_lookup_irq(struct pci_dev *dev, u8 slot, u8 pin) +{ + int irq = -1; + + /* now lookup the actual IRQ on a platform specific basis (pci-'platform'.c) */ + irq = pcibios_map_platform_irq(slot,pin); + if( irq < 0 ) { + pr_debug("PCI: Error mapping IRQ on device %s\n", pci_name(dev)); + return irq; + } + + pr_debug("Setting IRQ for slot %s to %d\n", pci_name(dev), irq); + + return irq; +} + +void __init pcibios_fixup_irqs(void) +{ + pci_fixup_irqs(sh7751_no_swizzle, sh7751_pci_lookup_irq); +} + diff --git a/arch/sh/drivers/pci/pci-sh7751.h b/arch/sh/drivers/pci/pci-sh7751.h new file mode 100644 index 00000000000..1fee5cae10d --- /dev/null +++ b/arch/sh/drivers/pci/pci-sh7751.h @@ -0,0 +1,303 @@ +/* + * Low-Level PCI Support for SH7751 targets + * + * Dustin McIntire (dustin@sensoria.com) (c) 2001 + * Paul Mundt (lethal@linux-sh.org) (c) 2003 + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + */ + +#ifndef _PCI_SH7751_H_ +#define _PCI_SH7751_H_ + +#include <linux/pci.h> + +/* set debug level 4=verbose...1=terse */ +//#define DEBUG_PCI 3 +#undef DEBUG_PCI + +#ifdef DEBUG_PCI +#define PCIDBG(n, x...) { if(DEBUG_PCI>=n) printk(x); } +#else +#define PCIDBG(n, x...) +#endif + +/* startup values */ +#define PCI_PROBE_BIOS 1 +#define PCI_PROBE_CONF1 2 +#define PCI_PROBE_CONF2 4 +#define PCI_NO_SORT 0x100 +#define PCI_BIOS_SORT 0x200 +#define PCI_NO_CHECKS 0x400 +#define PCI_ASSIGN_ROMS 0x1000 +#define PCI_BIOS_IRQ_SCAN 0x2000 + +/* Platform Specific Values */ +#define SH7751_VENDOR_ID 0x1054 +#define SH7751_DEVICE_ID 0x3505 +#define SH7751R_DEVICE_ID 0x350e + +/* SH7751 Specific Values */ +#define SH7751_PCI_CONFIG_BASE 0xFD000000 /* Config space base addr */ +#define SH7751_PCI_CONFIG_SIZE 0x1000000 /* Config space size */ +#define SH7751_PCI_MEMORY_BASE 0xFD000000 /* Memory space base addr */ +#define SH7751_PCI_MEM_SIZE 0x01000000 /* Size of Memory window */ +#define SH7751_PCI_IO_BASE 0xFE240000 /* IO space base address */ +#define SH7751_PCI_IO_SIZE 0x40000 /* Size of IO window */ + +#define SH7751_PCIREG_BASE 0xFE200000 /* PCI regs base address */ +#define PCI_REG(n) (SH7751_PCIREG_BASE+ n) + +#define SH7751_PCICONF0 0x0 /* PCI Config Reg 0 */ + #define SH7751_PCICONF0_DEVID 0xFFFF0000 /* Device ID */ + #define SH7751_PCICONF0_VNDID 0x0000FFFF /* Vendor ID */ +#define SH7751_PCICONF1 0x4 /* PCI Config Reg 1 */ + #define SH7751_PCICONF1_DPE 0x80000000 /* Data Parity Error */ + #define SH7751_PCICONF1_SSE 0x40000000 /* System Error Status */ + #define SH7751_PCICONF1_RMA 0x20000000 /* Master Abort */ + #define SH7751_PCICONF1_RTA 0x10000000 /* Target Abort Rx Status */ + #define SH7751_PCICONF1_STA 0x08000000 /* Target Abort Exec Status */ + #define SH7751_PCICONF1_DEV 0x06000000 /* Timing Status */ + #define SH7751_PCICONF1_DPD 0x01000000 /* Data Parity Status */ + #define SH7751_PCICONF1_FBBC 0x00800000 /* Back 2 Back Status */ + #define SH7751_PCICONF1_UDF 0x00400000 /* User Defined Status */ + #define SH7751_PCICONF1_66M 0x00200000 /* 66Mhz Operation Status */ + #define SH7751_PCICONF1_PM 0x00100000 /* Power Management Status */ + #define SH7751_PCICONF1_PBBE 0x00000200 /* Back 2 Back Control */ + #define SH7751_PCICONF1_SER 0x00000100 /* SERR Output Control */ + #define SH7751_PCICONF1_WCC 0x00000080 /* Wait Cycle Control */ + #define SH7751_PCICONF1_PER 0x00000040 /* Parity Error Response */ + #define SH7751_PCICONF1_VPS 0x00000020 /* VGA Pallet Snoop */ + #define SH7751_PCICONF1_MWIE 0x00000010 /* Memory Write+Invalidate */ + #define SH7751_PCICONF1_SPC 0x00000008 /* Special Cycle Control */ + #define SH7751_PCICONF1_BUM 0x00000004 /* Bus Master Control */ + #define SH7751_PCICONF1_MES 0x00000002 /* Memory Space Control */ + #define SH7751_PCICONF1_IOS 0x00000001 /* I/O Space Control */ +#define SH7751_PCICONF2 0x8 /* PCI Config Reg 2 */ + #define SH7751_PCICONF2_BCC 0xFF000000 /* Base Class Code */ + #define SH7751_PCICONF2_SCC 0x00FF0000 /* Sub-Class Code */ + #define SH7751_PCICONF2_RLPI 0x0000FF00 /* Programming Interface */ + #define SH7751_PCICONF2_REV 0x000000FF /* Revision ID */ +#define SH7751_PCICONF3 0xC /* PCI Config Reg 3 */ + #define SH7751_PCICONF3_BIST7 0x80000000 /* Bist Supported */ + #define SH7751_PCICONF3_BIST6 0x40000000 /* Bist Executing */ + #define SH7751_PCICONF3_BIST3_0 0x0F000000 /* Bist Passed */ + #define SH7751_PCICONF3_HD7 0x00800000 /* Single Funtion device */ + #define SH7751_PCICONF3_HD6_0 0x007F0000 /* Configuration Layout */ + #define SH7751_PCICONF3_LAT 0x0000FF00 /* Latency Timer */ + #define SH7751_PCICONF3_CLS 0x000000FF /* Cache Line Size */ +#define SH7751_PCICONF4 0x10 /* PCI Config Reg 4 */ + #define SH7751_PCICONF4_BASE 0xFFFFFFFC /* I/O Space Base Addr */ + #define SH7751_PCICONF4_ASI 0x00000001 /* Address Space Type */ +#define SH7751_PCICONF5 0x14 /* PCI Config Reg 5 */ + #define SH7751_PCICONF5_BASE 0xFFFFFFF0 /* Mem Space Base Addr */ + #define SH7751_PCICONF5_LAP 0x00000008 /* Prefetch Enabled */ + #define SH7751_PCICONF5_LAT 0x00000006 /* Local Memory type */ + #define SH7751_PCICONF5_ASI 0x00000001 /* Address Space Type */ +#define SH7751_PCICONF6 0x18 /* PCI Config Reg 6 */ + #define SH7751_PCICONF6_BASE 0xFFFFFFF0 /* Mem Space Base Addr */ + #define SH7751_PCICONF6_LAP 0x00000008 /* Prefetch Enabled */ + #define SH7751_PCICONF6_LAT 0x00000006 /* Local Memory type */ + #define SH7751_PCICONF6_ASI 0x00000001 /* Address Space Type */ +/* PCICONF7 - PCICONF10 are undefined */ +#define SH7751_PCICONF11 0x2C /* PCI Config Reg 11 */ + #define SH7751_PCICONF11_SSID 0xFFFF0000 /* Subsystem ID */ + #define SH7751_PCICONF11_SVID 0x0000FFFF /* Subsystem Vendor ID */ +/* PCICONF12 is undefined */ +#define SH7751_PCICONF13 0x34 /* PCI Config Reg 13 */ + #define SH7751_PCICONF13_CPTR 0x000000FF /* PM function pointer */ +/* PCICONF14 is undefined */ +#define SH7751_PCICONF15 0x3C /* PCI Config Reg 15 */ + #define SH7751_PCICONF15_IPIN 0x000000FF /* Interrupt Pin */ +#define SH7751_PCICONF16 0x40 /* PCI Config Reg 16 */ + #define SH7751_PCICONF16_PMES 0xF8000000 /* PME Support */ + #define SH7751_PCICONF16_D2S 0x04000000 /* D2 Support */ + #define SH7751_PCICONF16_D1S 0x02000000 /* D1 Support */ + #define SH7751_PCICONF16_DSI 0x00200000 /* Bit Device Init. */ + #define SH7751_PCICONF16_PMCK 0x00080000 /* Clock for PME req. */ + #define SH7751_PCICONF16_VER 0x00070000 /* PM Version */ + #define SH7751_PCICONF16_NIP 0x0000FF00 /* Next Item Pointer */ + #define SH7751_PCICONF16_CID 0x000000FF /* Capability Identifier */ +#define SH7751_PCICONF17 0x44 /* PCI Config Reg 17 */ + #define SH7751_PCICONF17_DATA 0xFF000000 /* Data field for PM */ + #define SH7751_PCICONF17_PMES 0x00800000 /* PME Status */ + #define SH7751_PCICONF17_DSCL 0x00600000 /* Data Scaling Value */ + #define SH7751_PCICONF17_DSEL 0x001E0000 /* Data Select */ + #define SH7751_PCICONF17_PMEN 0x00010000 /* PME Enable */ + #define SH7751_PCICONF17_PWST 0x00000003 /* Power State */ +/* SH7715 Internal PCI Registers */ +#define SH7751_PCICR 0x100 /* PCI Control Register */ + #define SH7751_PCICR_PREFIX 0xA5000000 /* CR prefix for write */ + #define SH7751_PCICR_TRSB 0x00000200 /* Target Read Single */ + #define SH7751_PCICR_BSWP 0x00000100 /* Target Byte Swap */ + #define SH7751_PCICR_PLUP 0x00000080 /* Enable PCI Pullup */ + #define SH7751_PCICR_ARBM 0x00000040 /* PCI Arbitration Mode */ + #define SH7751_PCICR_MD 0x00000030 /* MD9 and MD10 status */ + #define SH7751_PCICR_SERR 0x00000008 /* SERR output assert */ + #define SH7751_PCICR_INTA 0x00000004 /* INTA output assert */ + #define SH7751_PCICR_PRST 0x00000002 /* PCI Reset Assert */ + #define SH7751_PCICR_CFIN 0x00000001 /* Central Fun. Init Done */ +#define SH7751_PCILSR0 0x104 /* PCI Local Space Register0 */ +#define SH7751_PCILSR1 0x108 /* PCI Local Space Register1 */ +#define SH7751_PCILAR0 0x10C /* PCI Local Address Register1 */ +#define SH7751_PCILAR1 0x110 /* PCI Local Address Register1 */ +#define SH7751_PCIINT 0x114 /* PCI Interrupt Register */ + #define SH7751_PCIINT_MLCK 0x00008000 /* Master Lock Error */ + #define SH7751_PCIINT_TABT 0x00004000 /* Target Abort Error */ + #define SH7751_PCIINT_TRET 0x00000200 /* Target Retry Error */ + #define SH7751_PCIINT_MFDE 0x00000100 /* Master Func. Disable Error */ + #define SH7751_PCIINT_PRTY 0x00000080 /* Address Parity Error */ + #define SH7751_PCIINT_SERR 0x00000040 /* SERR Detection Error */ + #define SH7751_PCIINT_TWDP 0x00000020 /* Tgt. Write Parity Error */ + #define SH7751_PCIINT_TRDP 0x00000010 /* Tgt. Read Parity Error Det. */ + #define SH7751_PCIINT_MTABT 0x00000008 /* Master-Tgt. Abort Error */ + #define SH7751_PCIINT_MMABT 0x00000004 /* Master-Master Abort Error */ + #define SH7751_PCIINT_MWPD 0x00000002 /* Master Write PERR Detect */ + #define SH7751_PCIINT_MRPD 0x00000002 /* Master Read PERR Detect */ +#define SH7751_PCIINTM 0x118 /* PCI Interrupt Mask Register */ +#define SH7751_PCIALR 0x11C /* Error Address Register */ +#define SH7751_PCICLR 0x120 /* Error Command/Data Register */ + #define SH7751_PCICLR_MPIO 0x80000000 /* Error Command/Data Register */ + #define SH7751_PCICLR_MDMA0 0x40000000 /* DMA0 Transfer Error */ + #define SH7751_PCICLR_MDMA1 0x20000000 /* DMA1 Transfer Error */ + #define SH7751_PCICLR_MDMA2 0x10000000 /* DMA2 Transfer Error */ + #define SH7751_PCICLR_MDMA3 0x08000000 /* DMA3 Transfer Error */ + #define SH7751_PCICLR_TGT 0x04000000 /* Target Transfer Error */ + #define SH7751_PCICLR_CMDL 0x0000000F /* PCI Command at Error */ +#define SH7751_PCIAINT 0x130 /* Arbiter Interrupt Register */ + #define SH7751_PCIAINT_MBKN 0x00002000 /* Master Broken Interrupt */ + #define SH7751_PCIAINT_TBTO 0x00001000 /* Target Bus Time Out */ + #define SH7751_PCIAINT_MBTO 0x00001000 /* Master Bus Time Out */ + #define SH7751_PCIAINT_TABT 0x00000008 /* Target Abort */ + #define SH7751_PCIAINT_MABT 0x00000004 /* Master Abort */ + #define SH7751_PCIAINT_RDPE 0x00000002 /* Read Data Parity Error */ + #define SH7751_PCIAINT_WDPE 0x00000002 /* Write Data Parity Error */ +#define SH7751_PCIAINTM 0x134 /* Arbiter Int. Mask Register */ +#define SH7751_PCIBMLR 0x138 /* Error Bus Master Register */ + #define SH7751_PCIBMLR_REQ4 0x00000010 /* REQ4 bus master at error */ + #define SH7751_PCIBMLR_REQ3 0x00000008 /* REQ3 bus master at error */ + #define SH7751_PCIBMLR_REQ2 0x00000004 /* REQ2 bus master at error */ + #define SH7751_PCIBMLR_REQ1 0x00000002 /* REQ1 bus master at error */ + #define SH7751_PCIBMLR_REQ0 0x00000001 /* REQ0 bus master at error */ +#define SH7751_PCIDMABT 0x140 /* DMA Transfer Arb. Register */ + #define SH7751_PCIDMABT_RRBN 0x00000001 /* DMA Arbitor Round-Robin */ +#define SH7751_PCIDPA0 0x180 /* DMA0 Transfer Addr. Register */ +#define SH7751_PCIDLA0 0x184 /* DMA0 Local Addr. Register */ +#define SH7751_PCIDTC0 0x188 /* DMA0 Transfer Cnt. Register */ +#define SH7751_PCIDCR0 0x18C /* DMA0 Control Register */ + #define SH7751_PCIDCR_ALGN 0x00000600 /* DMA Alignment Mode */ + #define SH7751_PCIDCR_MAST 0x00000100 /* DMA Termination Type */ + #define SH7751_PCIDCR_INTM 0x00000080 /* DMA Interrupt Done Mask*/ + #define SH7751_PCIDCR_INTS 0x00000040 /* DMA Interrupt Done Status */ + #define SH7751_PCIDCR_LHLD 0x00000020 /* Local Address Control */ + #define SH7751_PCIDCR_PHLD 0x00000010 /* PCI Address Control*/ + #define SH7751_PCIDCR_IOSEL 0x00000008 /* PCI Address Space Type */ + #define SH7751_PCIDCR_DIR 0x00000004 /* DMA Transfer Direction */ + #define SH7751_PCIDCR_STOP 0x00000002 /* Force DMA Stop */ + #define SH7751_PCIDCR_STRT 0x00000001 /* DMA Start */ +#define SH7751_PCIDPA1 0x190 /* DMA1 Transfer Addr. Register */ +#define SH7751_PCIDLA1 0x194 /* DMA1 Local Addr. Register */ +#define SH7751_PCIDTC1 0x198 /* DMA1 Transfer Cnt. Register */ +#define SH7751_PCIDCR1 0x19C /* DMA1 Control Register */ +#define SH7751_PCIDPA2 0x1A0 /* DMA2 Transfer Addr. Register */ +#define SH7751_PCIDLA2 0x1A4 /* DMA2 Local Addr. Register */ +#define SH7751_PCIDTC2 0x1A8 /* DMA2 Transfer Cnt. Register */ +#define SH7751_PCIDCR2 0x1AC /* DMA2 Control Register */ +#define SH7751_PCIDPA3 0x1B0 /* DMA3 Transfer Addr. Register */ +#define SH7751_PCIDLA3 0x1B4 /* DMA3 Local Addr. Register */ +#define SH7751_PCIDTC3 0x1B8 /* DMA3 Transfer Cnt. Register */ +#define SH7751_PCIDCR3 0x1BC /* DMA3 Control Register */ +#define SH7751_PCIPAR 0x1C0 /* PIO Address Register */ + #define SH7751_PCIPAR_CFGEN 0x80000000 /* Configuration Enable */ + #define SH7751_PCIPAR_BUSNO 0x00FF0000 /* Config. Bus Number */ + #define SH7751_PCIPAR_DEVNO 0x0000FF00 /* Config. Device Number */ + #define SH7751_PCIPAR_REGAD 0x000000FC /* Register Address Number */ +#define SH7751_PCIMBR 0x1C4 /* Memory Base Address Register */ + #define SH7751_PCIMBR_MASK 0xFF000000 /* Memory Space Mask */ + #define SH7751_PCIMBR_LOCK 0x00000001 /* Lock Memory Space */ +#define SH7751_PCIIOBR 0x1C8 /* I/O Base Address Register */ + #define SH7751_PCIIOBR_MASK 0xFFFC0000 /* IO Space Mask */ + #define SH7751_PCIIOBR_LOCK 0x00000001 /* Lock IO Space */ +#define SH7751_PCIPINT 0x1CC /* Power Mgmnt Int. Register */ + #define SH7751_PCIPINT_D3 0x00000002 /* D3 Pwr Mgmt. Interrupt */ + #define SH7751_PCIPINT_D0 0x00000001 /* D0 Pwr Mgmt. Interrupt */ +#define SH7751_PCIPINTM 0x1D0 /* Power Mgmnt Mask Register */ +#define SH7751_PCICLKR 0x1D4 /* Clock Ctrl. Register */ + #define SH7751_PCICLKR_PCSTP 0x00000002 /* PCI Clock Stop */ + #define SH7751_PCICLKR_BCSTP 0x00000002 /* BCLK Clock Stop */ +/* For definitions of BCR, MCR see ... */ +#define SH7751_PCIBCR1 0x1E0 /* Memory BCR1 Register */ +#define SH7751_PCIBCR2 0x1E4 /* Memory BCR2 Register */ +#define SH7751_PCIWCR1 0x1E8 /* Wait Control 1 Register */ +#define SH7751_PCIWCR2 0x1EC /* Wait Control 2 Register */ +#define SH7751_PCIWCR3 0x1F0 /* Wait Control 3 Register */ +#define SH7751_PCIMCR 0x1F4 /* Memory Control Register */ +#define SH7751_PCIBCR3 0x1f8 /* Memory BCR3 Register */ +#define SH7751_PCIPCTR 0x200 /* Port Control Register */ + #define SH7751_PCIPCTR_P2EN 0x000400000 /* Port 2 Enable */ + #define SH7751_PCIPCTR_P1EN 0x000200000 /* Port 1 Enable */ + #define SH7751_PCIPCTR_P0EN 0x000100000 /* Port 0 Enable */ + #define SH7751_PCIPCTR_P2UP 0x000000020 /* Port2 Pull Up Enable */ + #define SH7751_PCIPCTR_P2IO 0x000000010 /* Port2 Output Enable */ + #define SH7751_PCIPCTR_P1UP 0x000000008 /* Port1 Pull Up Enable */ + #define SH7751_PCIPCTR_P1IO 0x000000004 /* Port1 Output Enable */ + #define SH7751_PCIPCTR_P0UP 0x000000002 /* Port0 Pull Up Enable */ + #define SH7751_PCIPCTR_P0IO 0x000000001 /* Port0 Output Enable */ +#define SH7751_PCIPDTR 0x204 /* Port Data Register */ + #define SH7751_PCIPDTR_PB5 0x000000020 /* Port 5 Enable */ + #define SH7751_PCIPDTR_PB4 0x000000010 /* Port 4 Enable */ + #define SH7751_PCIPDTR_PB3 0x000000008 /* Port 3 Enable */ + #define SH7751_PCIPDTR_PB2 0x000000004 /* Port 2 Enable */ + #define SH7751_PCIPDTR_PB1 0x000000002 /* Port 1 Enable */ + #define SH7751_PCIPDTR_PB0 0x000000001 /* Port 0 Enable */ +#define SH7751_PCIPDR 0x220 /* Port IO Data Register */ + +/* Memory Control Registers */ +#define SH7751_BCR1 0xFF800000 /* Memory BCR1 Register */ +#define SH7751_BCR2 0xFF800004 /* Memory BCR2 Register */ +#define SH7751_BCR3 0xFF800050 /* Memory BCR3 Register */ +#define SH7751_BCR4 0xFE0A00F0 /* Memory BCR4 Register */ +#define SH7751_WCR1 0xFF800008 /* Wait Control 1 Register */ +#define SH7751_WCR2 0xFF80000C /* Wait Control 2 Register */ +#define SH7751_WCR3 0xFF800010 /* Wait Control 3 Register */ +#define SH7751_MCR 0xFF800014 /* Memory Control Register */ + +/* General Memory Config Addresses */ +#define SH7751_CS0_BASE_ADDR 0x0 +#define SH7751_MEM_REGION_SIZE 0x04000000 +#define SH7751_CS1_BASE_ADDR (SH7751_CS0_BASE_ADDR + SH7751_MEM_REGION_SIZE) +#define SH7751_CS2_BASE_ADDR (SH7751_CS1_BASE_ADDR + SH7751_MEM_REGION_SIZE) +#define SH7751_CS3_BASE_ADDR (SH7751_CS2_BASE_ADDR + SH7751_MEM_REGION_SIZE) +#define SH7751_CS4_BASE_ADDR (SH7751_CS3_BASE_ADDR + SH7751_MEM_REGION_SIZE) +#define SH7751_CS5_BASE_ADDR (SH7751_CS4_BASE_ADDR + SH7751_MEM_REGION_SIZE) +#define SH7751_CS6_BASE_ADDR (SH7751_CS5_BASE_ADDR + SH7751_MEM_REGION_SIZE) + +/* General PCI values */ +#define SH7751_PCI_HOST_BRIDGE 0x6 + +/* Flags */ +#define SH7751_PCIC_NO_RESET 0x0001 + +/* External functions defined per platform i.e. Big Sur, SE... (these could be routed + * through the machine vectors... */ +extern int pcibios_init_platform(void); +extern int pcibios_map_platform_irq(u8 slot, u8 pin); + +struct sh7751_pci_address_space { + unsigned long base; + unsigned long size; +}; + +struct sh7751_pci_address_map { + struct sh7751_pci_address_space window0; + struct sh7751_pci_address_space window1; + unsigned long flags; +}; + +/* arch/sh/drivers/pci/pci-sh7751.c */ +extern int sh7751_pcic_init(struct sh7751_pci_address_map *map); + +#endif /* _PCI_SH7751_H_ */ + diff --git a/arch/sh/drivers/pci/pci-st40.c b/arch/sh/drivers/pci/pci-st40.c new file mode 100644 index 00000000000..cb675213115 --- /dev/null +++ b/arch/sh/drivers/pci/pci-st40.c @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2001 David J. Mckay (david.mckay@st.com) + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Support functions for the ST40 PCI hardware. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <asm/pci.h> +#include <linux/irq.h> +#include <linux/interrupt.h> /* irqreturn_t */ + +#include "pci-st40.h" + +/* This is in P2 of course */ +#define ST40PCI_BASE_ADDRESS (0xb0000000) +#define ST40PCI_MEM_ADDRESS (ST40PCI_BASE_ADDRESS+0x0) +#define ST40PCI_IO_ADDRESS (ST40PCI_BASE_ADDRESS+0x06000000) +#define ST40PCI_REG_ADDRESS (ST40PCI_BASE_ADDRESS+0x07000000) + +#define ST40PCI_REG(x) (ST40PCI_REG_ADDRESS+(ST40PCI_##x)) +#define ST40PCI_REG_INDEXED(reg, index) \ + (ST40PCI_REG(reg##0) + \ + ((ST40PCI_REG(reg##1) - ST40PCI_REG(reg##0))*index)) + +#define ST40PCI_WRITE(reg,val) writel((val),ST40PCI_REG(reg)) +#define ST40PCI_WRITE_SHORT(reg,val) writew((val),ST40PCI_REG(reg)) +#define ST40PCI_WRITE_BYTE(reg,val) writeb((val),ST40PCI_REG(reg)) +#define ST40PCI_WRITE_INDEXED(reg, index, val) \ + writel((val), ST40PCI_REG_INDEXED(reg, index)); + +#define ST40PCI_READ(reg) readl(ST40PCI_REG(reg)) +#define ST40PCI_READ_SHORT(reg) readw(ST40PCI_REG(reg)) +#define ST40PCI_READ_BYTE(reg) readb(ST40PCI_REG(reg)) + +#define ST40PCI_SERR_IRQ 64 +#define ST40PCI_ERR_IRQ 65 + + +/* Macros to extract PLL params */ +#define PLL_MDIV(reg) ( ((unsigned)reg) & 0xff ) +#define PLL_NDIV(reg) ( (((unsigned)reg)>>8) & 0xff ) +#define PLL_PDIV(reg) ( (((unsigned)reg)>>16) & 0x3 ) +#define PLL_SETUP(reg) ( (((unsigned)reg)>>19) & 0x1ff ) + +/* Build up the appropriate settings */ +#define PLL_SET(mdiv,ndiv,pdiv,setup) \ +( ((mdiv)&0xff) | (((ndiv)&0xff)<<8) | (((pdiv)&3)<<16)| (((setup)&0x1ff)<<19)) + +#define PLLPCICR (0xbb040000+0x10) + +#define PLLPCICR_POWERON (1<<28) +#define PLLPCICR_OUT_EN (1<<29) +#define PLLPCICR_LOCKSELECT (1<<30) +#define PLLPCICR_LOCK (1<<31) + + +#define PLL_25MHZ 0x793c8512 +#define PLL_33MHZ PLL_SET(18,88,3,295) + +static void pci_set_rbar_region(unsigned int region, unsigned long localAddr, + unsigned long pciOffset, unsigned long regionSize); + +/* + * The pcibios_map_platform_irq function is defined in the appropriate + * board specific code and referenced here + */ +extern int __init pcibios_map_platform_irq(struct pci_dev *dev, u8 slot, u8 pin); + +static __init void SetPCIPLL(void) +{ + { + /* Lets play with the PLL values */ + unsigned long pll1cr1; + unsigned long mdiv, ndiv, pdiv; + unsigned long muxcr; + unsigned int muxcr_ratios[4] = { 8, 16, 21, 1 }; + unsigned int freq; + +#define CLKGENA 0xbb040000 +#define CLKGENA_PLL2_MUXCR CLKGENA + 0x48 + pll1cr1 = ctrl_inl(PLLPCICR); + printk("PLL1CR1 %08lx\n", pll1cr1); + mdiv = PLL_MDIV(pll1cr1); + ndiv = PLL_NDIV(pll1cr1); + pdiv = PLL_PDIV(pll1cr1); + printk("mdiv %02lx ndiv %02lx pdiv %02lx\n", mdiv, ndiv, pdiv); + freq = ((2*27*ndiv)/mdiv) / (1 << pdiv); + printk("PLL freq %dMHz\n", freq); + muxcr = ctrl_inl(CLKGENA_PLL2_MUXCR); + printk("PCI freq %dMhz\n", freq / muxcr_ratios[muxcr & 3]); + } +} + + +struct pci_err { + unsigned mask; + const char *error_string; +}; + +static struct pci_err int_error[]={ + { INT_MNLTDIM,"MNLTDIM: Master non-lock transfer"}, + { INT_TTADI, "TTADI: Illegal byte enable in I/O transfer"}, + { INT_TMTO, "TMTO: Target memory read/write timeout"}, + { INT_MDEI, "MDEI: Master function disable error"}, + { INT_APEDI, "APEDI: Address parity error"}, + { INT_SDI, "SDI: SERR detected"}, + { INT_DPEITW, "DPEITW: Data parity error target write"}, + { INT_PEDITR, "PEDITR: PERR detected"}, + { INT_TADIM, "TADIM: Target abort detected"}, + { INT_MADIM, "MADIM: Master abort detected"}, + { INT_MWPDI, "MWPDI: PERR from target at data write"}, + { INT_MRDPEI, "MRDPEI: Master read data parity error"} +}; +#define NUM_PCI_INT_ERRS (sizeof(int_error)/sizeof(struct pci_err)) + +static struct pci_err aint_error[]={ + { AINT_MBI, "MBI: Master broken"}, + { AINT_TBTOI, "TBTOI: Target bus timeout"}, + { AINT_MBTOI, "MBTOI: Master bus timeout"}, + { AINT_TAI, "TAI: Target abort"}, + { AINT_MAI, "MAI: Master abort"}, + { AINT_RDPEI, "RDPEI: Read data parity"}, + { AINT_WDPE, "WDPE: Write data parity"} +}; + +#define NUM_PCI_AINT_ERRS (sizeof(aint_error)/sizeof(struct pci_err)) + +static void print_pci_errors(unsigned reg,struct pci_err *error,int num_errors) +{ + int i; + + for(i=0;i<num_errors;i++) { + if(reg & error[i].mask) { + printk("%s\n",error[i].error_string); + } + } + +} + + +static char * pci_commands[16]={ + "Int Ack", + "Special Cycle", + "I/O Read", + "I/O Write", + "Reserved", + "Reserved", + "Memory Read", + "Memory Write", + "Reserved", + "Reserved", + "Configuration Read", + "Configuration Write", + "Memory Read Multiple", + "Dual Address Cycle", + "Memory Read Line", + "Memory Write-and-Invalidate" +}; + +static irqreturn_t st40_pci_irq(int irq, void *dev_instance, struct pt_regs *regs) +{ + unsigned pci_int, pci_air, pci_cir, pci_aint; + static int count=0; + + + pci_int = ST40PCI_READ(INT);pci_aint = ST40PCI_READ(AINT); + pci_cir = ST40PCI_READ(CIR);pci_air = ST40PCI_READ(AIR); + + /* Reset state to stop multiple interrupts */ + ST40PCI_WRITE(INT, ~0); ST40PCI_WRITE(AINT, ~0); + + + if(++count>1) return IRQ_HANDLED; + + printk("** PCI ERROR **\n"); + + if(pci_int) { + printk("** INT register status\n"); + print_pci_errors(pci_int,int_error,NUM_PCI_INT_ERRS); + } + + if(pci_aint) { + printk("** AINT register status\n"); + print_pci_errors(pci_aint,aint_error,NUM_PCI_AINT_ERRS); + } + + printk("** Address and command info\n"); + + printk("** Command %s : Address 0x%x\n", + pci_commands[pci_cir&0xf],pci_air); + + if(pci_cir&CIR_PIOTEM) { + printk("CIR_PIOTEM:PIO transfer error for master\n"); + } + if(pci_cir&CIR_RWTET) { + printk("CIR_RWTET:Read/Write transfer error for target\n"); + } + + return IRQ_HANDLED; +} + + +/* Rounds a number UP to the nearest power of two. Used for + * sizing the PCI window. + */ +static u32 r2p2(u32 num) +{ + int i = 31; + u32 tmp = num; + + if (num == 0) + return 0; + + do { + if (tmp & (1 << 31)) + break; + i--; + tmp <<= 1; + } while (i >= 0); + + tmp = 1 << i; + /* If the original number isn't a power of 2, round it up */ + if (tmp != num) + tmp <<= 1; + + return tmp; +} + +static void __init pci_fixup_ide_bases(struct pci_dev *d) +{ + int i; + + /* + * PCI IDE controllers use non-standard I/O port decoding, respect it. + */ + if ((d->class >> 8) != PCI_CLASS_STORAGE_IDE) + return; + printk("PCI: IDE base address fixup for %s\n", pci_name(d)); + for(i=0; i<4; i++) { + struct resource *r = &d->resource[i]; + if ((r->start & ~0x80) == 0x374) { + r->start |= 2; + r->end = r->start; + } + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, pci_fixup_ide_bases); + +int __init st40pci_init(unsigned memStart, unsigned memSize) +{ + u32 lsr0; + + SetPCIPLL(); + + /* Initialises the ST40 pci subsystem, performing a reset, then programming + * up the address space decoders appropriately + */ + + /* Should reset core here as well methink */ + + ST40PCI_WRITE(CR, CR_LOCK_MASK | CR_SOFT_RESET); + + /* Loop while core resets */ + while (ST40PCI_READ(CR) & CR_SOFT_RESET); + + /* Switch off interrupts */ + ST40PCI_WRITE(INTM, 0); + ST40PCI_WRITE(AINT, 0); + + /* Now, lets reset all the cards on the bus with extreme prejudice */ + ST40PCI_WRITE(CR, CR_LOCK_MASK | CR_RSTCTL); + udelay(250); + + /* Set bus active, take it out of reset */ + ST40PCI_WRITE(CR, CR_LOCK_MASK | CR_BMAM | CR_CFINT | CR_PFCS | CR_PFE); + + /* The PCI spec says that no access must be made to the bus until 1 second + * after reset. This seem ludicrously long, but some delay is needed here + */ + mdelay(1000); + + /* Switch off interrupts */ + ST40PCI_WRITE(INTM, 0); + ST40PCI_WRITE(AINT, 0); + + /* Allow it to be a master */ + + ST40PCI_WRITE_SHORT(CSR_CMD, + PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | + PCI_COMMAND_IO); + + /* Accesse to the 0xb0000000 -> 0xb6000000 area will go through to 0x10000000 -> 0x16000000 + * on the PCI bus. This allows a nice 1-1 bus to phys mapping. + */ + + + ST40PCI_WRITE(MBR, 0x10000000); + /* Always set the max size 128M (actually, it is only 96MB wide) */ + ST40PCI_WRITE(MBMR, 0x07ff0000); + + /* I/O addresses are mapped at 0xb6000000 -> 0xb7000000. These are changed to 0, to + * allow cards that have legacy io such as vga to function correctly. This gives a + * maximum of 64K of io/space as only the bottom 16 bits of the address are copied + * over to the bus when the transaction is made. 64K of io space is more than enough + */ + ST40PCI_WRITE(IOBR, 0x0); + /* Set up the 64K window */ + ST40PCI_WRITE(IOBMR, 0x0); + + /* Now we set up the mbars so the PCI bus can see the local memory */ + /* Expose a 256M window starting at PCI address 0... */ + ST40PCI_WRITE(CSR_MBAR0, 0); + ST40PCI_WRITE(LSR0, 0x0fff0001); + + /* ... and set up the initial incomming window to expose all of RAM */ + pci_set_rbar_region(7, memStart, memStart, memSize); + + /* Maximise timeout values */ + ST40PCI_WRITE_BYTE(CSR_TRDY, 0xff); + ST40PCI_WRITE_BYTE(CSR_RETRY, 0xff); + ST40PCI_WRITE_BYTE(CSR_MIT, 0xff); + + ST40PCI_WRITE_BYTE(PERF,PERF_MASTER_WRITE_POSTING); + + return 1; +} + +char * __init pcibios_setup(char *str) +{ + return str; +} + + +#define SET_CONFIG_BITS(bus,devfn,where)\ + (((bus) << 16) | ((devfn) << 8) | ((where) & ~3) | (bus!=0)) + +#define CONFIG_CMD(bus, devfn, where) SET_CONFIG_BITS(bus->number,devfn,where) + + +static int CheckForMasterAbort(void) +{ + if (ST40PCI_READ(INT) & INT_MADIM) { + /* Should we clear config space version as well ??? */ + ST40PCI_WRITE(INT, INT_MADIM); + ST40PCI_WRITE_SHORT(CSR_STATUS, 0); + return 1; + } + + return 0; +} + +/* Write to config register */ +static int st40pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 * val) +{ + ST40PCI_WRITE(PAR, CONFIG_CMD(bus, devfn, where)); + switch (size) { + case 1: + *val = (u8)ST40PCI_READ_BYTE(PDR + (where & 3)); + break; + case 2: + *val = (u16)ST40PCI_READ_SHORT(PDR + (where & 2)); + break; + case 4: + *val = ST40PCI_READ(PDR); + break; + } + + if (CheckForMasterAbort()){ + switch (size) { + case 1: + *val = (u8)0xff; + break; + case 2: + *val = (u16)0xffff; + break; + case 4: + *val = 0xffffffff; + break; + } + } + + return PCIBIOS_SUCCESSFUL; +} + +static int st40pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val) +{ + ST40PCI_WRITE(PAR, CONFIG_CMD(bus, devfn, where)); + + switch (size) { + case 1: + ST40PCI_WRITE_BYTE(PDR + (where & 3), (u8)val); + break; + case 2: + ST40PCI_WRITE_SHORT(PDR + (where & 2), (u16)val); + break; + case 4: + ST40PCI_WRITE(PDR, val); + break; + } + + CheckForMasterAbort(); + + return PCIBIOS_SUCCESSFUL; +} + +struct pci_ops st40pci_config_ops = { + .read = st40pci_read, + .write = st40pci_write, +}; + + +/* Everything hangs off this */ +static struct pci_bus *pci_root_bus; + + +static u8 __init no_swizzle(struct pci_dev *dev, u8 * pin) +{ + return PCI_SLOT(dev->devfn); +} + + +static int __init pcibios_init(void) +{ + extern unsigned long memory_start, memory_end; + + printk(KERN_ALERT "pci-st40.c: pcibios_init\n"); + + if (sh_mv.mv_init_pci != NULL) { + sh_mv.mv_init_pci(); + } + + /* The pci subsytem needs to know where memory is and how much + * of it there is. I've simply made these globals. A better mechanism + * is probably needed. + */ + st40pci_init(PHYSADDR(memory_start), + PHYSADDR(memory_end) - PHYSADDR(memory_start)); + + if (request_irq(ST40PCI_ERR_IRQ, st40_pci_irq, + SA_INTERRUPT, "st40pci", NULL)) { + printk(KERN_ERR "st40pci: Cannot hook interrupt\n"); + return -EIO; + } + + /* Enable the PCI interrupts on the device */ + ST40PCI_WRITE(INTM, ~0); + ST40PCI_WRITE(AINT, ~0); + + /* Map the io address apprioately */ +#ifdef CONFIG_HD64465 + hd64465_port_map(PCIBIOS_MIN_IO, (64 * 1024) - PCIBIOS_MIN_IO + 1, + ST40_IO_ADDR + PCIBIOS_MIN_IO, 0); +#endif + + /* ok, do the scan man */ + pci_root_bus = pci_scan_bus(0, &st40pci_config_ops, NULL); + pci_assign_unassigned_resources(); + pci_fixup_irqs(no_swizzle, pcibios_map_platform_irq); + + return 0; +} + +subsys_initcall(pcibios_init); + +void __init pcibios_fixup_bus(struct pci_bus *bus) +{ +} + +/* + * Publish a region of local address space over the PCI bus + * to other devices. + */ +static void pci_set_rbar_region(unsigned int region, unsigned long localAddr, + unsigned long pciOffset, unsigned long regionSize) +{ + unsigned long mask; + + if (region > 7) + return; + + if (regionSize > (512 * 1024 * 1024)) + return; + + mask = r2p2(regionSize) - 0x10000; + + /* Diable the region (in case currently in use, should never happen) */ + ST40PCI_WRITE_INDEXED(RSR, region, 0); + + /* Start of local address space to publish */ + ST40PCI_WRITE_INDEXED(RLAR, region, PHYSADDR(localAddr) ); + + /* Start of region in PCI address space as an offset from MBAR0 */ + ST40PCI_WRITE_INDEXED(RBAR, region, pciOffset); + + /* Size of region */ + ST40PCI_WRITE_INDEXED(RSR, region, mask | 1); +} + diff --git a/arch/sh/drivers/pci/pci-st40.h b/arch/sh/drivers/pci/pci-st40.h new file mode 100644 index 00000000000..d729e0c2d5f --- /dev/null +++ b/arch/sh/drivers/pci/pci-st40.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2001 David J. Mckay (david.mckay@st.com) + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Defintions for the ST40 PCI hardware. + */ + +#ifndef __PCI_ST40_H__ +#define __PCI_ST40_H__ + +#define ST40PCI_VCR_STATUS 0x00 + +#define ST40PCI_VCR_VERSION 0x08 + +#define ST40PCI_CR 0x10 + +#define CR_SOFT_RESET (1<<12) +#define CR_PFCS (1<<11) +#define CR_PFE (1<<9) +#define CR_BMAM (1<<6) +#define CR_HOST (1<<5) +#define CR_CLKEN (1<<4) +#define CR_SOCS (1<<3) +#define CR_IOCS (1<<2) +#define CR_RSTCTL (1<<1) +#define CR_CFINT (1<<0) +#define CR_LOCK_MASK 0x5a000000 + + +#define ST40PCI_LSR0 0X14 +#define ST40PCI_LAR0 0x1c + +#define ST40PCI_INT 0x24 +#define INT_MNLTDIM (1<<15) +#define INT_TTADI (1<<14) +#define INT_TMTO (1<<9) +#define INT_MDEI (1<<8) +#define INT_APEDI (1<<7) +#define INT_SDI (1<<6) +#define INT_DPEITW (1<<5) +#define INT_PEDITR (1<<4) +#define INT_TADIM (1<<3) +#define INT_MADIM (1<<2) +#define INT_MWPDI (1<<1) +#define INT_MRDPEI (1<<0) + + +#define ST40PCI_INTM 0x28 +#define ST40PCI_AIR 0x2c + +#define ST40PCI_CIR 0x30 +#define CIR_PIOTEM (1<<31) +#define CIR_RWTET (1<<26) + +#define ST40PCI_AINT 0x40 +#define AINT_MBI (1<<13) +#define AINT_TBTOI (1<<12) +#define AINT_MBTOI (1<<11) +#define AINT_TAI (1<<3) +#define AINT_MAI (1<<2) +#define AINT_RDPEI (1<<1) +#define AINT_WDPE (1<<0) + +#define ST40PCI_AINTM 0x44 +#define ST40PCI_BMIR 0x48 +#define ST40PCI_PAR 0x4c +#define ST40PCI_MBR 0x50 +#define ST40PCI_IOBR 0x54 +#define ST40PCI_PINT 0x58 +#define ST40PCI_PINTM 0x5c +#define ST40PCI_MBMR 0x70 +#define ST40PCI_IOBMR 0x74 +#define ST40PCI_PDR 0x78 + +/* H8 specific registers start here */ +#define ST40PCI_WCBAR 0x7c +#define ST40PCI_LOCCFG_UNLOCK 0x34 + +#define ST40PCI_RBAR0 0x100 +#define ST40PCI_RSR0 0x104 +#define ST40PCI_RLAR0 0x108 + +#define ST40PCI_RBAR1 0x110 +#define ST40PCI_RSR1 0x114 +#define ST40PCI_RLAR1 0x118 + + +#define ST40PCI_RBAR2 0x120 +#define ST40PCI_RSR2 0x124 +#define ST40PCI_RLAR2 0x128 + +#define ST40PCI_RBAR3 0x130 +#define ST40PCI_RSR3 0x134 +#define ST40PCI_RLAR3 0x138 + +#define ST40PCI_RBAR4 0x140 +#define ST40PCI_RSR4 0x144 +#define ST40PCI_RLAR4 0x148 + +#define ST40PCI_RBAR5 0x150 +#define ST40PCI_RSR5 0x154 +#define ST40PCI_RLAR5 0x158 + +#define ST40PCI_RBAR6 0x160 +#define ST40PCI_RSR6 0x164 +#define ST40PCI_RLAR6 0x168 + +#define ST40PCI_RBAR7 0x170 +#define ST40PCI_RSR7 0x174 +#define ST40PCI_RLAR7 0x178 + + +#define ST40PCI_RBAR(n) (0x100+(0x10*(n))) +#define ST40PCI_RSR(n) (0x104+(0x10*(n))) +#define ST40PCI_RLAR(n) (0x108+(0x10*(n))) + +#define ST40PCI_PERF 0x80 +#define PERF_MASTER_WRITE_POSTING (1<<4) +/* H8 specific registers end here */ + + +/* These are configs space registers */ +#define ST40PCI_CSR_VID 0x10000 +#define ST40PCI_CSR_DID 0x10002 +#define ST40PCI_CSR_CMD 0x10004 +#define ST40PCI_CSR_STATUS 0x10006 +#define ST40PCI_CSR_MBAR0 0x10010 +#define ST40PCI_CSR_TRDY 0x10040 +#define ST40PCI_CSR_RETRY 0x10041 +#define ST40PCI_CSR_MIT 0x1000d + +#define ST40_IO_ADDR 0xb6000000 + +#endif /* __PCI_ST40_H__ */ diff --git a/arch/sh/drivers/pci/pci.c b/arch/sh/drivers/pci/pci.c new file mode 100644 index 00000000000..c1669905abe --- /dev/null +++ b/arch/sh/drivers/pci/pci.c @@ -0,0 +1,155 @@ +/* arch/sh/kernel/pci.c + * $Id: pci.c,v 1.1 2003/08/24 19:15:45 lethal Exp $ + * + * Copyright (c) 2002 M. R. Brown <mrbrown@linux-sh.org> + * + * + * These functions are collected here to reduce duplication of common + * code amongst the many platform-specific PCI support code files. + * + * These routines require the following board-specific routines: + * void pcibios_fixup_irqs(); + * + * See include/asm-sh/pci.h for more information. + */ + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/init.h> + +static int __init pcibios_init(void) +{ + struct pci_channel *p; + struct pci_bus *bus; + int busno; + +#ifdef CONFIG_PCI_AUTO + /* assign resources */ + busno = 0; + for (p = board_pci_channels; p->pci_ops != NULL; p++) { + busno = pciauto_assign_resources(busno, p) + 1; + } +#endif + + /* scan the buses */ + busno = 0; + for (p= board_pci_channels; p->pci_ops != NULL; p++) { + bus = pci_scan_bus(busno, p->pci_ops, p); + busno = bus->subordinate+1; + } + + /* board-specific fixups */ + pcibios_fixup_irqs(); + + return 0; +} + +subsys_initcall(pcibios_init); + +void +pcibios_update_resource(struct pci_dev *dev, struct resource *root, + struct resource *res, int resource) +{ + u32 new, check; + int reg; + + new = res->start | (res->flags & PCI_REGION_FLAG_MASK); + if (resource < 6) { + reg = PCI_BASE_ADDRESS_0 + 4*resource; + } else if (resource == PCI_ROM_RESOURCE) { + res->flags |= IORESOURCE_ROM_ENABLE; + new |= PCI_ROM_ADDRESS_ENABLE; + reg = dev->rom_base_reg; + } else { + /* Somebody might have asked allocation of a non-standard resource */ + return; + } + + pci_write_config_dword(dev, reg, new); + pci_read_config_dword(dev, reg, &check); + if ((new ^ check) & ((new & PCI_BASE_ADDRESS_SPACE_IO) ? PCI_BASE_ADDRESS_IO_MASK : PCI_BASE_ADDRESS_MEM_MASK)) { + printk(KERN_ERR "PCI: Error while updating region " + "%s/%d (%08x != %08x)\n", pci_name(dev), resource, + new, check); + } +} + +void pcibios_align_resource(void *data, struct resource *res, + unsigned long size, unsigned long align) + __attribute__ ((weak)); + +/* + * We need to avoid collisions with `mirrored' VGA ports + * and other strange ISA hardware, so we always want the + * addresses to be allocated in the 0x000-0x0ff region + * modulo 0x400. + */ +void pcibios_align_resource(void *data, struct resource *res, + unsigned long size, unsigned long align) +{ + if (res->flags & IORESOURCE_IO) { + unsigned long start = res->start; + + if (start & 0x300) { + start = (start + 0x3ff) & ~0x3ff; + res->start = start; + } + } +} + +int pcibios_enable_device(struct pci_dev *dev, int mask) +{ + u16 cmd, old_cmd; + int idx; + struct resource *r; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + old_cmd = cmd; + for(idx=0; idx<6; idx++) { + if (!(mask & (1 << idx))) + continue; + r = &dev->resource[idx]; + if (!r->start && r->end) { + printk(KERN_ERR "PCI: Device %s not available because " + "of resource collisions\n", pci_name(dev)); + return -EINVAL; + } + if (r->flags & IORESOURCE_IO) + cmd |= PCI_COMMAND_IO; + if (r->flags & IORESOURCE_MEM) + cmd |= PCI_COMMAND_MEMORY; + } + if (dev->resource[PCI_ROM_RESOURCE].start) + cmd |= PCI_COMMAND_MEMORY; + if (cmd != old_cmd) { + printk(KERN_INFO "PCI: Enabling device %s (%04x -> %04x)\n", + pci_name(dev), old_cmd, cmd); + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + return 0; +} + +/* + * If we set up a device for bus mastering, we need to check and set + * the latency timer as it may not be properly set. + */ +unsigned int pcibios_max_latency = 255; + +void pcibios_set_master(struct pci_dev *dev) +{ + u8 lat; + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat); + if (lat < 16) + lat = (64 <= pcibios_max_latency) ? 64 : pcibios_max_latency; + else if (lat > pcibios_max_latency) + lat = pcibios_max_latency; + else + return; + printk(KERN_INFO "PCI: Setting latency timer of device %s to %d\n", pci_name(dev), lat); + pci_write_config_byte(dev, PCI_LATENCY_TIMER, lat); +} + +void __init pcibios_update_irq(struct pci_dev *dev, int irq) +{ + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq); +} |