diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /arch/sh/drivers/dma | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/sh/drivers/dma')
-rw-r--r-- | arch/sh/drivers/dma/Kconfig | 74 | ||||
-rw-r--r-- | arch/sh/drivers/dma/Makefile | 9 | ||||
-rw-r--r-- | arch/sh/drivers/dma/dma-api.c | 417 | ||||
-rw-r--r-- | arch/sh/drivers/dma/dma-g2.c | 197 | ||||
-rw-r--r-- | arch/sh/drivers/dma/dma-pvr2.c | 102 | ||||
-rw-r--r-- | arch/sh/drivers/dma/dma-sh.c | 425 | ||||
-rw-r--r-- | arch/sh/drivers/dma/dma-sysfs.c | 164 | ||||
-rw-r--r-- | arch/sh/drivers/dma/dmabrg.c | 196 |
8 files changed, 1584 insertions, 0 deletions
diff --git a/arch/sh/drivers/dma/Kconfig b/arch/sh/drivers/dma/Kconfig new file mode 100644 index 000000000..7d54f284c --- /dev/null +++ b/arch/sh/drivers/dma/Kconfig @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0 +menu "DMA support" + + +config SH_DMA + bool "SuperH on-chip DMA controller (DMAC) support" + depends on CPU_SH3 || CPU_SH4 + default n + +config SH_DMA_IRQ_MULTI + bool + depends on SH_DMA + default y if CPU_SUBTYPE_SH7750 || CPU_SUBTYPE_SH7751 || \ + CPU_SUBTYPE_SH7750S || CPU_SUBTYPE_SH7750R || \ + CPU_SUBTYPE_SH7751R || CPU_SUBTYPE_SH7091 || \ + CPU_SUBTYPE_SH7763 || CPU_SUBTYPE_SH7780 || \ + CPU_SUBTYPE_SH7785 || CPU_SUBTYPE_SH7760 + +config SH_DMA_API + depends on SH_DMA + bool "SuperH DMA API support" + default n + help + SH_DMA_API always enabled DMA API of used SuperH. + If you want to use DMA ENGINE, you must not enable this. + Please enable DMA_ENGINE and SH_DMAE. + +config NR_ONCHIP_DMA_CHANNELS + int + depends on SH_DMA + default "4" if CPU_SUBTYPE_SH7750 || CPU_SUBTYPE_SH7751 || \ + CPU_SUBTYPE_SH7750S || CPU_SUBTYPE_SH7091 + default "8" if CPU_SUBTYPE_SH7750R || CPU_SUBTYPE_SH7751R || \ + CPU_SUBTYPE_SH7760 + default "12" if CPU_SUBTYPE_SH7723 || CPU_SUBTYPE_SH7780 || \ + CPU_SUBTYPE_SH7785 || CPU_SUBTYPE_SH7724 + default "6" + help + This allows you to specify the number of channels that the on-chip + DMAC supports. This will be 4 for SH7750/SH7751/Sh7750S/SH7091 and 8 for the + SH7750R/SH7751R/SH7760, 12 for the SH7723/SH7780/SH7785/SH7724, default is 6. + +config SH_DMABRG + bool "SH7760 DMABRG support" + depends on CPU_SUBTYPE_SH7760 + help + The DMABRG does data transfers from main memory to Audio/USB units + of the SH7760. + Say Y if you want to use Audio/USB DMA on your SH7760 board. + +config PVR2_DMA + tristate "PowerVR 2 DMAC support" + depends on SH_DREAMCAST && SH_DMA + help + Selecting this will enable support for the PVR2 DMA controller. + As this chains off of the on-chip DMAC, that must also be + enabled by default. + + This is primarily used by the pvr2fb framebuffer driver for + certain optimizations, but is not necessary for functionality. + + If in doubt, say N. + +config G2_DMA + tristate "G2 Bus DMA support" + depends on SH_DREAMCAST && SH_DMA_API + help + This enables support for the DMA controller for the Dreamcast's + G2 bus. Drivers that want this will generally enable this on + their own. + + If in doubt, say N. + +endmenu diff --git a/arch/sh/drivers/dma/Makefile b/arch/sh/drivers/dma/Makefile new file mode 100644 index 000000000..d2fdd5620 --- /dev/null +++ b/arch/sh/drivers/dma/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the SuperH DMA specific kernel interface routines under Linux. +# + +obj-$(CONFIG_SH_DMA_API) += dma-sh.o dma-api.o dma-sysfs.o +obj-$(CONFIG_PVR2_DMA) += dma-pvr2.o +obj-$(CONFIG_G2_DMA) += dma-g2.o +obj-$(CONFIG_SH_DMABRG) += dmabrg.o diff --git a/arch/sh/drivers/dma/dma-api.c b/arch/sh/drivers/dma/dma-api.c new file mode 100644 index 000000000..ab9170494 --- /dev/null +++ b/arch/sh/drivers/dma/dma-api.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * arch/sh/drivers/dma/dma-api.c + * + * SuperH-specific DMA management API + * + * Copyright (C) 2003, 2004, 2005 Paul Mundt + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <asm/dma.h> + +DEFINE_SPINLOCK(dma_spin_lock); +static LIST_HEAD(registered_dmac_list); + +struct dma_info *get_dma_info(unsigned int chan) +{ + struct dma_info *info; + + /* + * Look for each DMAC's range to determine who the owner of + * the channel is. + */ + list_for_each_entry(info, ®istered_dmac_list, list) { + if ((chan < info->first_vchannel_nr) || + (chan >= info->first_vchannel_nr + info->nr_channels)) + continue; + + return info; + } + + return NULL; +} +EXPORT_SYMBOL(get_dma_info); + +struct dma_info *get_dma_info_by_name(const char *dmac_name) +{ + struct dma_info *info; + + list_for_each_entry(info, ®istered_dmac_list, list) { + if (dmac_name && (strcmp(dmac_name, info->name) != 0)) + continue; + else + return info; + } + + return NULL; +} +EXPORT_SYMBOL(get_dma_info_by_name); + +static unsigned int get_nr_channels(void) +{ + struct dma_info *info; + unsigned int nr = 0; + + if (unlikely(list_empty(®istered_dmac_list))) + return nr; + + list_for_each_entry(info, ®istered_dmac_list, list) + nr += info->nr_channels; + + return nr; +} + +struct dma_channel *get_dma_channel(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel; + int i; + + if (unlikely(!info)) + return ERR_PTR(-EINVAL); + + for (i = 0; i < info->nr_channels; i++) { + channel = &info->channels[i]; + if (channel->vchan == chan) + return channel; + } + + return NULL; +} +EXPORT_SYMBOL(get_dma_channel); + +int get_dma_residue(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = get_dma_channel(chan); + + if (info->ops->get_residue) + return info->ops->get_residue(channel); + + return 0; +} +EXPORT_SYMBOL(get_dma_residue); + +static int search_cap(const char **haystack, const char *needle) +{ + const char **p; + + for (p = haystack; *p; p++) + if (strcmp(*p, needle) == 0) + return 1; + + return 0; +} + +/** + * request_dma_bycap - Allocate a DMA channel based on its capabilities + * @dmac: List of DMA controllers to search + * @caps: List of capabilities + * + * Search all channels of all DMA controllers to find a channel which + * matches the requested capabilities. The result is the channel + * number if a match is found, or %-ENODEV if no match is found. + * + * Note that not all DMA controllers export capabilities, in which + * case they can never be allocated using this API, and so + * request_dma() must be used specifying the channel number. + */ +int request_dma_bycap(const char **dmac, const char **caps, const char *dev_id) +{ + unsigned int found = 0; + struct dma_info *info; + const char **p; + int i; + + BUG_ON(!dmac || !caps); + + list_for_each_entry(info, ®istered_dmac_list, list) + if (strcmp(*dmac, info->name) == 0) { + found = 1; + break; + } + + if (!found) + return -ENODEV; + + for (i = 0; i < info->nr_channels; i++) { + struct dma_channel *channel = &info->channels[i]; + + if (unlikely(!channel->caps)) + continue; + + for (p = caps; *p; p++) { + if (!search_cap(channel->caps, *p)) + break; + if (request_dma(channel->chan, dev_id) == 0) + return channel->chan; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL(request_dma_bycap); + +int dmac_search_free_channel(const char *dev_id) +{ + struct dma_channel *channel = { 0 }; + struct dma_info *info = get_dma_info(0); + int i; + + for (i = 0; i < info->nr_channels; i++) { + channel = &info->channels[i]; + if (unlikely(!channel)) + return -ENODEV; + + if (atomic_read(&channel->busy) == 0) + break; + } + + if (info->ops->request) { + int result = info->ops->request(channel); + if (result) + return result; + + atomic_set(&channel->busy, 1); + return channel->chan; + } + + return -ENOSYS; +} + +int request_dma(unsigned int chan, const char *dev_id) +{ + struct dma_channel *channel = { 0 }; + struct dma_info *info = get_dma_info(chan); + int result; + + channel = get_dma_channel(chan); + if (atomic_xchg(&channel->busy, 1)) + return -EBUSY; + + strlcpy(channel->dev_id, dev_id, sizeof(channel->dev_id)); + + if (info->ops->request) { + result = info->ops->request(channel); + if (result) + atomic_set(&channel->busy, 0); + + return result; + } + + return 0; +} +EXPORT_SYMBOL(request_dma); + +void free_dma(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = get_dma_channel(chan); + + if (info->ops->free) + info->ops->free(channel); + + atomic_set(&channel->busy, 0); +} +EXPORT_SYMBOL(free_dma); + +void dma_wait_for_completion(unsigned int chan) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = get_dma_channel(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(); +} +EXPORT_SYMBOL(dma_wait_for_completion); + +int register_chan_caps(const char *dmac, struct dma_chan_caps *caps) +{ + struct dma_info *info; + unsigned int found = 0; + int i; + + list_for_each_entry(info, ®istered_dmac_list, list) + if (strcmp(dmac, info->name) == 0) { + found = 1; + break; + } + + if (unlikely(!found)) + return -ENODEV; + + for (i = 0; i < info->nr_channels; i++, caps++) { + struct dma_channel *channel; + + if ((info->first_channel_nr + i) != caps->ch_num) + return -EINVAL; + + channel = &info->channels[i]; + channel->caps = caps->caplist; + } + + return 0; +} +EXPORT_SYMBOL(register_chan_caps); + +void dma_configure_channel(unsigned int chan, unsigned long flags) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = get_dma_channel(chan); + + if (info->ops->configure) + info->ops->configure(channel, flags); +} +EXPORT_SYMBOL(dma_configure_channel); + +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 = get_dma_channel(chan); + + channel->sar = from; + channel->dar = to; + channel->count = size; + channel->mode = mode; + + return info->ops->xfer(channel); +} +EXPORT_SYMBOL(dma_xfer); + +int dma_extend(unsigned int chan, unsigned long op, void *param) +{ + struct dma_info *info = get_dma_info(chan); + struct dma_channel *channel = get_dma_channel(chan); + + if (info->ops->extend) + return info->ops->extend(channel, op, param); + + return -ENOSYS; +} +EXPORT_SYMBOL(dma_extend); + +static int dma_proc_show(struct seq_file *m, void *v) +{ + struct dma_info *info = v; + + if (list_empty(®istered_dmac_list)) + return 0; + + /* + * Iterate over each registered DMAC + */ + list_for_each_entry(info, ®istered_dmac_list, 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; + + seq_printf(m, "%2d: %14s %s\n", i, + info->name, channel->dev_id); + } + } + + return 0; +} + +int register_dmac(struct dma_info *info) +{ + unsigned int total_channels, 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); + + info->pdev = platform_device_register_simple(info->name, -1, + NULL, 0); + if (IS_ERR(info->pdev)) + return PTR_ERR(info->pdev); + + /* + * 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 = kzalloc(size, GFP_KERNEL); + if (!info->channels) + return -ENOMEM; + } + + total_channels = get_nr_channels(); + info->first_vchannel_nr = total_channels; + for (i = 0; i < info->nr_channels; i++) { + struct dma_channel *chan = &info->channels[i]; + + atomic_set(&chan->busy, 0); + + chan->chan = info->first_channel_nr + i; + chan->vchan = info->first_channel_nr + i + total_channels; + + memcpy(chan->dev_id, "Unused", 7); + + if (info->flags & DMAC_CHANNELS_TEI_CAPABLE) + chan->flags |= DMA_TEI_CAPABLE; + + init_waitqueue_head(&chan->wait_queue); + dma_create_sysfs_files(chan, info); + } + + list_add(&info->list, ®istered_dmac_list); + + return 0; +} +EXPORT_SYMBOL(register_dmac); + +void unregister_dmac(struct dma_info *info) +{ + unsigned int i; + + for (i = 0; i < info->nr_channels; i++) + dma_remove_sysfs_files(info->channels + i, info); + + if (!(info->flags & DMAC_CHANNELS_CONFIGURED)) + kfree(info->channels); + + list_del(&info->list); + platform_device_unregister(info->pdev); +} +EXPORT_SYMBOL(unregister_dmac); + +static int __init dma_api_init(void) +{ + printk(KERN_NOTICE "DMA: Registering DMA API.\n"); + return proc_create_single("dma", 0, NULL, dma_proc_show) ? 0 : -ENOMEM; +} +subsys_initcall(dma_api_init); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("DMA API for SuperH"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/sh/drivers/dma/dma-g2.c b/arch/sh/drivers/dma/dma-g2.c new file mode 100644 index 000000000..52a8ae5e3 --- /dev/null +++ b/arch/sh/drivers/dma/dma-g2.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * arch/sh/drivers/dma/dma-g2.c + * + * G2 bus DMA support + * + * Copyright (C) 2003 - 2006 Paul Mundt + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <asm/cacheflush.h> +#include <mach/sysasic.h> +#include <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; + +#define g2_bytes_remaining(i) \ + ((g2_dma->channel[i].size - \ + g2_dma->status[i].size) & 0x0fffffff) + +static irqreturn_t g2_dma_interrupt(int irq, void *dev_id) +{ + int i; + + for (i = 0; i < G2_NR_DMA_CHANNELS; i++) { + if (g2_dma->status[i].status & 0x20000000) { + unsigned int bytes = g2_bytes_remaining(i); + + if (likely(bytes == 0)) { + struct dma_info *info = dev_id; + struct dma_channel *chan = info->channels + i; + + wake_up(&chan->wait_queue); + + return IRQ_HANDLED; + } + } + } + + return IRQ_NONE; +} + +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 int g2_get_residue(struct dma_channel *chan) +{ + return g2_bytes_remaining(chan->chan); +} + +static struct dma_ops g2_dma_ops = { + .xfer = g2_xfer_dma, + .get_residue = g2_get_residue, +}; + +static struct dma_info g2_dma_info = { + .name = "g2_dmac", + .nr_channels = 4, + .ops = &g2_dma_ops, + .flags = DMAC_CHANNELS_TEI_CAPABLE, +}; + +static int __init g2_dma_init(void) +{ + int ret; + + ret = request_irq(HW_EVENT_G2_DMA, g2_dma_interrupt, 0, + "g2 DMA handler", &g2_dma_info); + if (unlikely(ret)) + return -EINVAL; + + /* Magic */ + g2_dma->wait_state = 27; + g2_dma->magic = 0x4659404f; + + ret = register_dmac(&g2_dma_info); + if (unlikely(ret != 0)) + free_irq(HW_EVENT_G2_DMA, &g2_dma_info); + + return ret; +} + +static void __exit g2_dma_exit(void) +{ + free_irq(HW_EVENT_G2_DMA, &g2_dma_info); + unregister_dmac(&g2_dma_info); +} + +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 v2"); diff --git a/arch/sh/drivers/dma/dma-pvr2.c b/arch/sh/drivers/dma/dma-pvr2.c new file mode 100644 index 000000000..21c347543 --- /dev/null +++ b/arch/sh/drivers/dma/dma-pvr2.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * arch/sh/drivers/dma/dma-pvr2.c + * + * NEC PowerVR 2 (Dreamcast) DMA support + * + * Copyright (C) 2003, 2004 Paul Mundt + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <mach/sysasic.h> +#include <mach/dma.h> +#include <asm/dma.h> +#include <asm/io.h> + +static unsigned int xfer_complete; +static int count; + +static irqreturn_t pvr2_dma_interrupt(int irq, void *dev_id) +{ + 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 (__raw_readl(PVR2_DMA_MODE) != 0) + return -EBUSY; + + __raw_writel(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; + + __raw_writel(chan->dar, PVR2_DMA_ADDR); + __raw_writel(chan->count, PVR2_DMA_COUNT); + __raw_writel(chan->mode & DMA_MODE_MASK, PVR2_DMA_MODE); + + return 0; +} + +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 = "pvr2_dmac", + .nr_channels = 1, + .ops = &pvr2_dma_ops, + .flags = DMAC_CHANNELS_TEI_CAPABLE, +}; + +static int __init pvr2_dma_init(void) +{ + if (request_irq(HW_EVENT_PVR2_DMA, pvr2_dma_interrupt, 0, + "pvr2 DMA handler", NULL)) + pr_err("Failed to register pvr2 DMA handler interrupt\n"); + 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); + unregister_dmac(&pvr2_dma_info); +} + +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 v2"); diff --git a/arch/sh/drivers/dma/dma-sh.c b/arch/sh/drivers/dma/dma-sh.c new file mode 100644 index 000000000..306fba156 --- /dev/null +++ b/arch/sh/drivers/dma/dma-sh.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * arch/sh/drivers/dma/dma-sh.c + * + * SuperH On-chip DMAC Support + * + * Copyright (C) 2000 Takashi YOSHII + * Copyright (C) 2003, 2004 Paul Mundt + * Copyright (C) 2005 Andriy Skulysh + */ +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/io.h> +#include <mach-dreamcast/mach/dma.h> +#include <asm/dma.h> +#include <asm/dma-register.h> +#include <cpu/dma-register.h> +#include <cpu/dma.h> + +/* + * Some of the SoCs feature two DMAC modules. In such a case, the channels are + * distributed equally among them. + */ +#ifdef SH_DMAC_BASE1 +#define SH_DMAC_NR_MD_CH (CONFIG_NR_ONCHIP_DMA_CHANNELS / 2) +#else +#define SH_DMAC_NR_MD_CH CONFIG_NR_ONCHIP_DMA_CHANNELS +#endif + +#define SH_DMAC_CH_SZ 0x10 + +/* + * 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 | RS_AUTO | TS_INDEX2VAL(XMIT_SZ_32BIT)) + +static unsigned long dma_find_base(unsigned int chan) +{ + unsigned long base = SH_DMAC_BASE0; + +#ifdef SH_DMAC_BASE1 + if (chan >= SH_DMAC_NR_MD_CH) + base = SH_DMAC_BASE1; +#endif + + return base; +} + +static unsigned long dma_base_addr(unsigned int chan) +{ + unsigned long base = dma_find_base(chan); + + chan = (chan % SH_DMAC_NR_MD_CH) * SH_DMAC_CH_SZ; + + /* DMAOR is placed inside the channel register space. Step over it. */ + if (chan >= DMAOR) + base += SH_DMAC_CH_SZ; + + return base + chan; +} + +#ifdef CONFIG_SH_DMA_IRQ_MULTI +static inline unsigned int get_dmte_irq(unsigned int chan) +{ + return chan >= 6 ? DMTE6_IRQ : DMTE0_IRQ; +} +#else + +static unsigned int dmte_irq_map[] = { + DMTE0_IRQ, DMTE0_IRQ + 1, DMTE0_IRQ + 2, DMTE0_IRQ + 3, + +#ifdef DMTE4_IRQ + DMTE4_IRQ, DMTE4_IRQ + 1, +#endif + +#ifdef DMTE6_IRQ + DMTE6_IRQ, DMTE6_IRQ + 1, +#endif + +#ifdef DMTE8_IRQ + DMTE8_IRQ, DMTE9_IRQ, DMTE10_IRQ, DMTE11_IRQ, +#endif +}; + +static inline unsigned int get_dmte_irq(unsigned int chan) +{ + return dmte_irq_map[chan]; +} +#endif + +/* + * 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 unsigned int ts_shift[] = TS_SHIFT; + +static inline unsigned int calc_xmit_shift(struct dma_channel *chan) +{ + u32 chcr = __raw_readl(dma_base_addr(chan->chan) + CHCR); + int cnt = ((chcr & CHCR_TS_LOW_MASK) >> CHCR_TS_LOW_SHIFT) | + ((chcr & CHCR_TS_HIGH_MASK) >> CHCR_TS_HIGH_SHIFT); + + return ts_shift[cnt]; +} + +/* + * 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 dma_channel *chan = dev_id; + u32 chcr; + + chcr = __raw_readl(dma_base_addr(chan->chan) + CHCR); + + if (!(chcr & CHCR_TE)) + return IRQ_NONE; + + chcr &= ~(CHCR_IE | CHCR_DE); + __raw_writel(chcr, (dma_base_addr(chan->chan) + CHCR)); + + wake_up(&chan->wait_queue); + + return IRQ_HANDLED; +} + +static int sh_dmac_request_dma(struct dma_channel *chan) +{ + if (unlikely(!(chan->flags & DMA_TEI_CAPABLE))) + return 0; + + return request_irq(get_dmte_irq(chan->chan), dma_tei, IRQF_SHARED, + chan->dev_id, chan); +} + +static void sh_dmac_free_dma(struct dma_channel *chan) +{ + free_irq(get_dmte_irq(chan->chan), chan); +} + +static int +sh_dmac_configure_channel(struct dma_channel *chan, unsigned long chcr) +{ + if (!chcr) + chcr = RS_DUAL | CHCR_IE; + + if (chcr & CHCR_IE) { + chcr &= ~CHCR_IE; + chan->flags |= DMA_TEI_CAPABLE; + } else { + chan->flags &= ~DMA_TEI_CAPABLE; + } + + __raw_writel(chcr, (dma_base_addr(chan->chan) + CHCR)); + + chan->flags |= DMA_CONFIGURED; + return 0; +} + +static void sh_dmac_enable_dma(struct dma_channel *chan) +{ + int irq; + u32 chcr; + + chcr = __raw_readl(dma_base_addr(chan->chan) + CHCR); + chcr |= CHCR_DE; + + if (chan->flags & DMA_TEI_CAPABLE) + chcr |= CHCR_IE; + + __raw_writel(chcr, (dma_base_addr(chan->chan) + CHCR)); + + if (chan->flags & DMA_TEI_CAPABLE) { + irq = get_dmte_irq(chan->chan); + enable_irq(irq); + } +} + +static void sh_dmac_disable_dma(struct dma_channel *chan) +{ + int irq; + u32 chcr; + + if (chan->flags & DMA_TEI_CAPABLE) { + irq = get_dmte_irq(chan->chan); + disable_irq(irq); + } + + chcr = __raw_readl(dma_base_addr(chan->chan) + CHCR); + chcr &= ~(CHCR_DE | CHCR_TE | CHCR_IE); + __raw_writel(chcr, (dma_base_addr(chan->chan) + CHCR)); +} + +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 (unlikely(!(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 == PVR2_CASCADE_CHAN)) + __raw_writel(chan->sar, (dma_base_addr(chan->chan) + SAR)); + if (chan->dar || (mach_is_dreamcast() && + chan->chan == PVR2_CASCADE_CHAN)) + __raw_writel(chan->dar, (dma_base_addr(chan->chan) + DAR)); + + __raw_writel(chan->count >> calc_xmit_shift(chan), + (dma_base_addr(chan->chan) + TCR)); + + sh_dmac_enable_dma(chan); + + return 0; +} + +static int sh_dmac_get_dma_residue(struct dma_channel *chan) +{ + if (!(__raw_readl(dma_base_addr(chan->chan) + CHCR) & CHCR_DE)) + return 0; + + return __raw_readl(dma_base_addr(chan->chan) + TCR) + << calc_xmit_shift(chan); +} + +/* + * DMAOR handling + */ +#if defined(CONFIG_CPU_SUBTYPE_SH7723) || \ + defined(CONFIG_CPU_SUBTYPE_SH7724) || \ + defined(CONFIG_CPU_SUBTYPE_SH7780) || \ + defined(CONFIG_CPU_SUBTYPE_SH7785) +#define NR_DMAOR 2 +#else +#define NR_DMAOR 1 +#endif + +#define dmaor_read_reg(n) __raw_readw(dma_find_base((n) * \ + SH_DMAC_NR_MD_CH) + DMAOR) +#define dmaor_write_reg(n, data) __raw_writew(data, \ + dma_find_base((n) * \ + SH_DMAC_NR_MD_CH) + DMAOR) + +static inline int dmaor_reset(int no) +{ + unsigned long dmaor = dmaor_read_reg(no); + + /* Try to clear the error flags first, incase they are set */ + dmaor &= ~(DMAOR_NMIF | DMAOR_AE); + dmaor_write_reg(no, dmaor); + + dmaor |= DMAOR_INIT; + dmaor_write_reg(no, dmaor); + + /* See if we got an error again */ + if ((dmaor_read_reg(no) & (DMAOR_AE | DMAOR_NMIF))) { + printk(KERN_ERR "dma-sh: Can't initialize DMAOR.\n"); + return -EINVAL; + } + + return 0; +} + +/* + * DMAE handling + */ +#ifdef CONFIG_CPU_SH4 + +#if defined(DMAE1_IRQ) +#define NR_DMAE 2 +#else +#define NR_DMAE 1 +#endif + +static const char *dmae_name[] = { + "DMAC Address Error0", + "DMAC Address Error1" +}; + +#ifdef CONFIG_SH_DMA_IRQ_MULTI +static inline unsigned int get_dma_error_irq(int n) +{ + return get_dmte_irq(n * 6); +} +#else + +static unsigned int dmae_irq_map[] = { + DMAE0_IRQ, + +#ifdef DMAE1_IRQ + DMAE1_IRQ, +#endif +}; + +static inline unsigned int get_dma_error_irq(int n) +{ + return dmae_irq_map[n]; +} +#endif + +static irqreturn_t dma_err(int irq, void *dummy) +{ + int i; + + for (i = 0; i < NR_DMAOR; i++) + dmaor_reset(i); + + disable_irq(irq); + + return IRQ_HANDLED; +} + +static int dmae_irq_init(void) +{ + int n; + + for (n = 0; n < NR_DMAE; n++) { + int i = request_irq(get_dma_error_irq(n), dma_err, + IRQF_SHARED, dmae_name[n], (void *)dmae_name[n]); + if (unlikely(i < 0)) { + printk(KERN_ERR "%s request_irq fail\n", dmae_name[n]); + return i; + } + } + + return 0; +} + +static void dmae_irq_free(void) +{ + int n; + + for (n = 0; n < NR_DMAE; n++) + free_irq(get_dma_error_irq(n), NULL); +} +#else +static inline int dmae_irq_init(void) +{ + return 0; +} + +static void dmae_irq_free(void) +{ +} +#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 = "sh_dmac", + .nr_channels = CONFIG_NR_ONCHIP_DMA_CHANNELS, + .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, rc; + + /* + * Initialize DMAE, for parts that support it. + */ + rc = dmae_irq_init(); + if (unlikely(rc != 0)) + return rc; + + /* + * Initialize DMAOR, and clean up any error flags that may have + * been set. + */ + for (i = 0; i < NR_DMAOR; i++) { + rc = dmaor_reset(i); + if (unlikely(rc != 0)) + return rc; + } + + return register_dmac(info); +} + +static void __exit sh_dmac_exit(void) +{ + dmae_irq_free(); + unregister_dmac(&sh_dmac_info); +} + +subsys_initcall(sh_dmac_init); +module_exit(sh_dmac_exit); + +MODULE_AUTHOR("Takashi YOSHII, Paul Mundt, Andriy Skulysh"); +MODULE_DESCRIPTION("SuperH On-Chip DMAC Support"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/sh/drivers/dma/dma-sysfs.c b/arch/sh/drivers/dma/dma-sysfs.c new file mode 100644 index 000000000..8ef318150 --- /dev/null +++ b/arch/sh/drivers/dma/dma-sysfs.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * arch/sh/drivers/dma/dma-sysfs.c + * + * sysfs interface for SH DMA API + * + * Copyright (C) 2004 - 2006 Paul Mundt + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/string.h> +#include <asm/dma.h> + +static struct bus_type dma_subsys = { + .name = "dma", + .dev_name = "dma", +}; + +static ssize_t dma_show_devices(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < 16; i++) { + struct dma_info *info = get_dma_info(i); + struct dma_channel *channel = get_dma_channel(i); + + if (unlikely(!info) || !channel) + continue; + + len += sprintf(buf + len, "%2d: %14s %s\n", + channel->chan, info->name, + channel->dev_id); + } + + return len; +} + +static DEVICE_ATTR(devices, S_IRUGO, dma_show_devices, NULL); + +static int __init dma_subsys_init(void) +{ + int ret; + + ret = subsys_system_register(&dma_subsys, NULL); + if (unlikely(ret)) + return ret; + + return device_create_file(dma_subsys.dev_root, &dev_attr_devices); +} +postcore_initcall(dma_subsys_init); + +static ssize_t dma_show_dev_id(struct device *dev, + struct device_attribute *attr, 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 device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dma_channel *channel = to_dma_channel(dev); + strcpy(channel->dev_id, buf); + return count; +} + +static DEVICE_ATTR(dev_id, S_IRUGO | S_IWUSR, dma_show_dev_id, dma_store_dev_id); + +static ssize_t dma_store_config(struct device *dev, + struct device_attribute *attr, + 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->vchan, config); + + return count; +} + +static DEVICE_ATTR(config, S_IWUSR, NULL, dma_store_config); + +static ssize_t dma_show_mode(struct device *dev, + struct device_attribute *attr, 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 device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dma_channel *channel = to_dma_channel(dev); + channel->mode = simple_strtoul(buf, NULL, 0); + return count; +} + +static DEVICE_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 device *dev, \ + struct device_attribute *attr, char *buf)\ +{ \ + struct dma_channel *channel = to_dma_channel(dev); \ + return sprintf(buf, fmt, channel->field); \ +} \ +static DEVICE_ATTR(field, S_IRUGO, dma_show_##field, NULL); + +dma_ro_attr(count, "0x%08x\n"); +dma_ro_attr(flags, "0x%08lx\n"); + +int dma_create_sysfs_files(struct dma_channel *chan, struct dma_info *info) +{ + struct device *dev = &chan->dev; + char name[16]; + int ret; + + dev->id = chan->vchan; + dev->bus = &dma_subsys; + + ret = device_register(dev); + if (ret) + return ret; + + ret |= device_create_file(dev, &dev_attr_dev_id); + ret |= device_create_file(dev, &dev_attr_count); + ret |= device_create_file(dev, &dev_attr_mode); + ret |= device_create_file(dev, &dev_attr_flags); + ret |= device_create_file(dev, &dev_attr_config); + + if (unlikely(ret)) { + dev_err(&info->pdev->dev, "Failed creating attrs\n"); + return ret; + } + + snprintf(name, sizeof(name), "dma%d", chan->chan); + return sysfs_create_link(&info->pdev->dev.kobj, &dev->kobj, name); +} + +void dma_remove_sysfs_files(struct dma_channel *chan, struct dma_info *info) +{ + struct device *dev = &chan->dev; + char name[16]; + + device_remove_file(dev, &dev_attr_dev_id); + device_remove_file(dev, &dev_attr_count); + device_remove_file(dev, &dev_attr_mode); + device_remove_file(dev, &dev_attr_flags); + device_remove_file(dev, &dev_attr_config); + + snprintf(name, sizeof(name), "dma%d", chan->chan); + sysfs_remove_link(&info->pdev->dev.kobj, name); + + device_unregister(dev); +} diff --git a/arch/sh/drivers/dma/dmabrg.c b/arch/sh/drivers/dma/dmabrg.c new file mode 100644 index 000000000..5b2c1fd25 --- /dev/null +++ b/arch/sh/drivers/dma/dmabrg.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SH7760 DMABRG IRQ handling + * + * (c) 2007 MSC Vertriebsges.m.b.H, Manuel Lauss <mlau@msc-ge.com> + */ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <asm/dma.h> +#include <asm/dmabrg.h> +#include <asm/io.h> + +/* + * The DMABRG is a special DMA unit within the SH7760. It does transfers + * from USB-SRAM/Audio units to main memory (and also the LCDC; but that + * part is sensibly placed in the LCDC registers and requires no irqs) + * It has 3 IRQ lines which trigger 10 events, and works independently + * from the traditional SH DMAC (although it blocks usage of DMAC 0) + * + * BRGIRQID | component | dir | meaning | source + * ----------------------------------------------------- + * 0 | USB-DMA | ... | xfer done | DMABRGI1 + * 1 | USB-UAE | ... | USB addr err.| DMABRGI0 + * 2 | HAC0/SSI0 | play| all done | DMABRGI1 + * 3 | HAC0/SSI0 | play| half done | DMABRGI2 + * 4 | HAC0/SSI0 | rec | all done | DMABRGI1 + * 5 | HAC0/SSI0 | rec | half done | DMABRGI2 + * 6 | HAC1/SSI1 | play| all done | DMABRGI1 + * 7 | HAC1/SSI1 | play| half done | DMABRGI2 + * 8 | HAC1/SSI1 | rec | all done | DMABRGI1 + * 9 | HAC1/SSI1 | rec | half done | DMABRGI2 + * + * all can be enabled/disabled in the DMABRGCR register, + * as well as checked if they occurred. + * + * DMABRGI0 services USB DMA Address errors, but it still must be + * enabled/acked in the DMABRGCR register. USB-DMA complete indicator + * is grouped together with the audio buffer end indicators, too bad... + * + * DMABRGCR: Bits 31-24: audio-dma ENABLE flags, + * Bits 23-16: audio-dma STATUS flags, + * Bits 9-8: USB error/xfer ENABLE, + * Bits 1-0: USB error/xfer STATUS. + * Ack an IRQ by writing 0 to the STATUS flag. + * Mask IRQ by writing 0 to ENABLE flag. + * + * Usage is almost like with any other IRQ: + * dmabrg_request_irq(BRGIRQID, handler, data) + * dmabrg_free_irq(BRGIRQID) + * + * handler prototype: void brgirqhandler(void *data) + */ + +#define DMARSRA 0xfe090000 +#define DMAOR 0xffa00040 +#define DMACHCR0 0xffa0000c +#define DMABRGCR 0xfe3c0000 + +#define DMAOR_BRG 0x0000c000 +#define DMAOR_DMEN 0x00000001 + +#define DMABRGI0 68 +#define DMABRGI1 69 +#define DMABRGI2 70 + +struct dmabrg_handler { + void (*handler)(void *); + void *data; +} *dmabrg_handlers; + +static inline void dmabrg_call_handler(int i) +{ + dmabrg_handlers[i].handler(dmabrg_handlers[i].data); +} + +/* + * main DMABRG irq handler. It acks irqs and then + * handles every set and unmasked bit sequentially. + * No locking and no validity checks; it should be + * as fast as possible (audio!) + */ +static irqreturn_t dmabrg_irq(int irq, void *data) +{ + unsigned long dcr; + unsigned int i; + + dcr = __raw_readl(DMABRGCR); + __raw_writel(dcr & ~0x00ff0003, DMABRGCR); /* ack all */ + dcr &= dcr >> 8; /* ignore masked */ + + /* USB stuff, get it out of the way first */ + if (dcr & 1) + dmabrg_call_handler(DMABRGIRQ_USBDMA); + if (dcr & 2) + dmabrg_call_handler(DMABRGIRQ_USBDMAERR); + + /* Audio */ + dcr >>= 16; + while (dcr) { + i = __ffs(dcr); + dcr &= dcr - 1; + dmabrg_call_handler(i + DMABRGIRQ_A0TXF); + } + return IRQ_HANDLED; +} + +static void dmabrg_disable_irq(unsigned int dmairq) +{ + unsigned long dcr; + dcr = __raw_readl(DMABRGCR); + dcr &= ~(1 << ((dmairq > 1) ? dmairq + 22 : dmairq + 8)); + __raw_writel(dcr, DMABRGCR); +} + +static void dmabrg_enable_irq(unsigned int dmairq) +{ + unsigned long dcr; + dcr = __raw_readl(DMABRGCR); + dcr |= (1 << ((dmairq > 1) ? dmairq + 22 : dmairq + 8)); + __raw_writel(dcr, DMABRGCR); +} + +int dmabrg_request_irq(unsigned int dmairq, void(*handler)(void*), + void *data) +{ + if ((dmairq > 9) || !handler) + return -ENOENT; + if (dmabrg_handlers[dmairq].handler) + return -EBUSY; + + dmabrg_handlers[dmairq].handler = handler; + dmabrg_handlers[dmairq].data = data; + + dmabrg_enable_irq(dmairq); + return 0; +} +EXPORT_SYMBOL_GPL(dmabrg_request_irq); + +void dmabrg_free_irq(unsigned int dmairq) +{ + if (likely(dmairq < 10)) { + dmabrg_disable_irq(dmairq); + dmabrg_handlers[dmairq].handler = NULL; + dmabrg_handlers[dmairq].data = NULL; + } +} +EXPORT_SYMBOL_GPL(dmabrg_free_irq); + +static int __init dmabrg_init(void) +{ + unsigned long or; + int ret; + + dmabrg_handlers = kcalloc(10, sizeof(struct dmabrg_handler), + GFP_KERNEL); + if (!dmabrg_handlers) + return -ENOMEM; + +#ifdef CONFIG_SH_DMA + /* request DMAC channel 0 before anyone else can get it */ + ret = request_dma(0, "DMAC 0 (DMABRG)"); + if (ret < 0) + printk(KERN_INFO "DMABRG: DMAC ch0 not reserved!\n"); +#endif + + __raw_writel(0, DMABRGCR); + __raw_writel(0, DMACHCR0); + __raw_writel(0x94000000, DMARSRA); /* enable DMABRG in DMAC 0 */ + + /* enable DMABRG mode, enable the DMAC */ + or = __raw_readl(DMAOR); + __raw_writel(or | DMAOR_BRG | DMAOR_DMEN, DMAOR); + + ret = request_irq(DMABRGI0, dmabrg_irq, 0, + "DMABRG USB address error", NULL); + if (ret) + goto out0; + + ret = request_irq(DMABRGI1, dmabrg_irq, 0, + "DMABRG Transfer End", NULL); + if (ret) + goto out1; + + ret = request_irq(DMABRGI2, dmabrg_irq, 0, + "DMABRG Transfer Half", NULL); + if (ret == 0) + return ret; + + free_irq(DMABRGI1, NULL); +out1: free_irq(DMABRGI0, NULL); +out0: kfree(dmabrg_handlers); + return ret; +} +subsys_initcall(dmabrg_init); |