diff options
29 files changed, 1347 insertions, 156 deletions
diff --git a/arch/arm/mach-s3c2410/dma.c b/arch/arm/mach-s3c2410/dma.c index 2cb02d37885..dbf96e60d99 100644 --- a/arch/arm/mach-s3c2410/dma.c +++ b/arch/arm/mach-s3c2410/dma.c @@ -21,7 +21,7 @@ #include <mach/dma.h> #include <plat/cpu.h> -#include <plat/dma-core.h> +#include <plat/dma-plat.h> #include <plat/regs-serial.h> #include <mach/regs-gpio.h> diff --git a/arch/arm/mach-s3c2410/include/mach/dma.h b/arch/arm/mach-s3c2410/include/mach/dma.h index 1648311c7b4..ebea1182380 100644 --- a/arch/arm/mach-s3c2410/include/mach/dma.h +++ b/arch/arm/mach-s3c2410/include/mach/dma.h @@ -55,9 +55,9 @@ enum dma_ch { /* we have 4 dma channels */ #ifndef CONFIG_CPU_S3C2443 -#define S3C2410_DMA_CHANNELS (4) +#define S3C_DMA_CHANNELS (4) #else -#define S3C2410_DMA_CHANNELS (6) +#define S3C_DMA_CHANNELS (6) #endif /* types */ @@ -110,6 +110,8 @@ enum s3c2410_dma_loadst { * waiting for reloads */ #define S3C2410_DMAF_AUTOSTART (1<<1) /* auto-start if buffer queued */ +#define S3C2410_DMAF_CIRCULAR (0x00) /* circular enqueue not supp. */ + /* dma buffer */ struct s3c2410_dma_buf; @@ -192,10 +194,12 @@ struct s3c2410_dma_chan { struct sys_device dev; }; -/* the currently allocated channel information */ -extern struct s3c2410_dma_chan s3c2410_chans[]; - -/* note, we don't really use dma_device_t at the moment */ typedef unsigned long dma_device_t; + +static int s3c_dma_has_circular(void) +{ + return 0; +} + #endif /* __ASM_ARCH_DMA_H */ diff --git a/arch/arm/mach-s3c2412/dma.c b/arch/arm/mach-s3c2412/dma.c index ad16460cf71..f8d16fc10bc 100644 --- a/arch/arm/mach-s3c2412/dma.c +++ b/arch/arm/mach-s3c2412/dma.c @@ -20,7 +20,7 @@ #include <mach/dma.h> -#include <plat/dma-core.h> +#include <plat/dma-plat.h> #include <plat/cpu.h> #include <plat/regs-serial.h> diff --git a/arch/arm/mach-s3c2440/dma.c b/arch/arm/mach-s3c2440/dma.c index 7f0388bfbeb..e08e081430f 100644 --- a/arch/arm/mach-s3c2440/dma.c +++ b/arch/arm/mach-s3c2440/dma.c @@ -20,7 +20,7 @@ #include <mach/map.h> #include <mach/dma.h> -#include <plat/dma-core.h> +#include <plat/dma-plat.h> #include <plat/cpu.h> #include <plat/regs-serial.h> diff --git a/arch/arm/mach-s3c2443/dma.c b/arch/arm/mach-s3c2443/dma.c index b113b5d211b..397f3b5c0b4 100644 --- a/arch/arm/mach-s3c2443/dma.c +++ b/arch/arm/mach-s3c2443/dma.c @@ -20,7 +20,7 @@ #include <mach/dma.h> -#include <plat/dma-core.h> +#include <plat/dma-plat.h> #include <plat/cpu.h> #include <plat/regs-serial.h> diff --git a/arch/arm/mach-s3c6400/include/mach/dma.h b/arch/arm/mach-s3c6400/include/mach/dma.h index 069dc29376c..0c0d0e05e1a 100644 --- a/arch/arm/mach-s3c6400/include/mach/dma.h +++ b/arch/arm/mach-s3c6400/include/mach/dma.h @@ -11,6 +11,8 @@ #ifndef __ASM_ARCH_DMA_H #define __ASM_ARCH_DMA_H __FILE__ +#define S3C_DMA_CHANNELS (16) + /* see mach-s3c2410/dma.h for notes on dma channel numbers */ /* Note, for the S3C64XX architecture we keep the DMACH_ @@ -56,6 +58,15 @@ enum dma_ch { DMACH_MAX /* the end */ }; +static __inline__ int s3c_dma_has_circular(void) +{ + /* we will be supporting ciruclar buffers as soon as we have DMA + * engine support. + */ + return 1; +} + +#define S3C2410_DMAF_CIRCULAR (1 << 0) #include <plat/dma.h> diff --git a/arch/arm/plat-s3c/Kconfig b/arch/arm/plat-s3c/Kconfig index a0ecea9aa4d..80a411d6f10 100644 --- a/arch/arm/plat-s3c/Kconfig +++ b/arch/arm/plat-s3c/Kconfig @@ -159,6 +159,13 @@ config S3C_GPIO_CFG_S3C64XX Internal configuration to enable S3C64XX style GPIO configuration functions. +# DMA + +config S3C_DMA + bool + help + Internal configuration for S3C DMA core + config S3C_PWM bool help diff --git a/arch/arm/plat-s3c/dev-i2c0.c b/arch/arm/plat-s3c/dev-i2c0.c index fe327074037..92132a55fea 100644 --- a/arch/arm/plat-s3c/dev-i2c0.c +++ b/arch/arm/plat-s3c/dev-i2c0.c @@ -52,7 +52,7 @@ static struct s3c2410_platform_i2c default_i2c_data0 __initdata = { .slave_addr = 0x10, .bus_freq = 100*1000, .max_freq = 400*1000, - .sda_delay = S3C2410_IICLC_SDA_DELAY5 | S3C2410_IICLC_FILTER_ON, + .sda_delay = S3C2410_IICLC_SDA_DELAY15 | S3C2410_IICLC_FILTER_ON, }; void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd) diff --git a/arch/arm/plat-s3c/dma.c b/arch/arm/plat-s3c/dma.c new file mode 100644 index 00000000000..c9db75c06af --- /dev/null +++ b/arch/arm/plat-s3c/dma.c @@ -0,0 +1,86 @@ +/* linux/arch/arm/plat-s3c/dma.c + * + * Copyright (c) 2003-2005,2006,2009 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * S3C DMA core + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +struct s3c2410_dma_buf; + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> + +#include <mach/dma.h> +#include <mach/irqs.h> + +#include <plat/dma-plat.h> + +/* dma channel state information */ +struct s3c2410_dma_chan s3c2410_chans[S3C_DMA_CHANNELS]; +struct s3c2410_dma_chan *s3c_dma_chan_map[DMACH_MAX]; + +/* s3c_dma_lookup_channel + * + * change the dma channel number given into a real dma channel id +*/ + +struct s3c2410_dma_chan *s3c_dma_lookup_channel(unsigned int channel) +{ + if (channel & DMACH_LOW_LEVEL) + return &s3c2410_chans[channel & ~DMACH_LOW_LEVEL]; + else + return s3c_dma_chan_map[channel]; +} + +/* do we need to protect the settings of the fields from + * irq? +*/ + +int s3c2410_dma_set_opfn(unsigned int channel, s3c2410_dma_opfn_t rtn) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + + if (chan == NULL) + return -EINVAL; + + pr_debug("%s: chan=%p, op rtn=%p\n", __func__, chan, rtn); + + chan->op_fn = rtn; + + return 0; +} +EXPORT_SYMBOL(s3c2410_dma_set_opfn); + +int s3c2410_dma_set_buffdone_fn(unsigned int channel, s3c2410_dma_cbfn_t rtn) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + + if (chan == NULL) + return -EINVAL; + + pr_debug("%s: chan=%p, callback rtn=%p\n", __func__, chan, rtn); + + chan->callback_fn = rtn; + + return 0; +} +EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn); + +int s3c2410_dma_setflags(unsigned int channel, unsigned int flags) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + + if (chan == NULL) + return -EINVAL; + + chan->flags = flags; + return 0; +} +EXPORT_SYMBOL(s3c2410_dma_setflags); diff --git a/arch/arm/plat-s3c/include/plat/dma-core.h b/arch/arm/plat-s3c/include/plat/dma-core.h new file mode 100644 index 00000000000..32ff2a92cb3 --- /dev/null +++ b/arch/arm/plat-s3c/include/plat/dma-core.h @@ -0,0 +1,22 @@ +/* arch/arm/plat-s3c/include/plat/dma.h + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * Samsung S3C DMA core support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +extern struct s3c2410_dma_chan *s3c_dma_lookup_channel(unsigned int channel); + +extern struct s3c2410_dma_chan *s3c_dma_chan_map[]; + +/* the currently allocated channel information */ +extern struct s3c2410_dma_chan s3c2410_chans[]; + + diff --git a/arch/arm/plat-s3c/include/plat/dma.h b/arch/arm/plat-s3c/include/plat/dma.h index 34dba98f08e..efd5bd82c85 100644 --- a/arch/arm/plat-s3c/include/plat/dma.h +++ b/arch/arm/plat-s3c/include/plat/dma.h @@ -98,6 +98,7 @@ extern int s3c2410_dma_free(unsigned int channel, struct s3c2410_dma_client *); extern int s3c2410_dma_enqueue(unsigned int channel, void *id, dma_addr_t data, int size); + /* s3c2410_dma_config * * configure the dma channel diff --git a/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h b/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h index 25d4058bcfe..a5600b381d4 100644 --- a/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h +++ b/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h @@ -33,6 +33,9 @@ #define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) #define S3C2412_IISCON_IIS_ACTIVE (1 << 0) +#define S3C64XX_IISMOD_IMS_PCLK (0 << 10) +#define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10) + #define S3C2412_IISMOD_MASTER_INTERNAL (0 << 10) #define S3C2412_IISMOD_MASTER_EXTERNAL (1 << 10) #define S3C2412_IISMOD_SLAVE (2 << 10) diff --git a/arch/arm/plat-s3c24xx/Kconfig b/arch/arm/plat-s3c24xx/Kconfig index af16c1772db..ba2ea854047 100644 --- a/arch/arm/plat-s3c24xx/Kconfig +++ b/arch/arm/plat-s3c24xx/Kconfig @@ -72,6 +72,7 @@ config PM_SIMTEC config S3C2410_DMA bool "S3C2410 DMA support" depends on ARCH_S3C2410 + select S3C_DMA help S3C2410 DMA support. This is needed for drivers like sound which use the S3C2410's DMA system to move data to and from the diff --git a/arch/arm/plat-s3c24xx/dma.c b/arch/arm/plat-s3c24xx/dma.c index 84bbbc60234..3b122f6a7c4 100644 --- a/arch/arm/plat-s3c24xx/dma.c +++ b/arch/arm/plat-s3c24xx/dma.c @@ -44,8 +44,6 @@ static int dma_channels; static struct s3c24xx_dma_selection dma_sel; -/* dma channel state information */ -struct s3c2410_dma_chan s3c2410_chans[S3C2410_DMA_CHANNELS]; /* debugging functions */ @@ -135,21 +133,6 @@ dmadbg_showregs(const char *fname, int line, struct s3c2410_dma_chan *chan) #define dbg_showchan(chan) do { } while(0) #endif /* CONFIG_S3C2410_DMA_DEBUG */ -static struct s3c2410_dma_chan *dma_chan_map[DMACH_MAX]; - -/* lookup_dma_channel - * - * change the dma channel number given into a real dma channel id -*/ - -static struct s3c2410_dma_chan *lookup_dma_channel(unsigned int channel) -{ - if (channel & DMACH_LOW_LEVEL) - return &s3c2410_chans[channel & ~DMACH_LOW_LEVEL]; - else - return dma_chan_map[channel]; -} - /* s3c2410_dma_stats_timeout * * Update DMA stats from timeout info @@ -214,8 +197,6 @@ s3c2410_dma_waitforload(struct s3c2410_dma_chan *chan, int line) return 0; } - - /* s3c2410_dma_loadbuffer * * load a buffer, and update the channel state @@ -453,7 +434,7 @@ s3c2410_dma_canload(struct s3c2410_dma_chan *chan) int s3c2410_dma_enqueue(unsigned int channel, void *id, dma_addr_t data, int size) { - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); struct s3c2410_dma_buf *buf; unsigned long flags; @@ -804,7 +785,7 @@ EXPORT_SYMBOL(s3c2410_dma_request); int s3c2410_dma_free(unsigned int channel, struct s3c2410_dma_client *client) { - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); unsigned long flags; if (chan == NULL) @@ -836,7 +817,7 @@ int s3c2410_dma_free(unsigned int channel, struct s3c2410_dma_client *client) chan->irq_claimed = 0; if (!(channel & DMACH_LOW_LEVEL)) - dma_chan_map[channel] = NULL; + s3c_dma_chan_map[channel] = NULL; local_irq_restore(flags); @@ -995,7 +976,7 @@ static int s3c2410_dma_started(struct s3c2410_dma_chan *chan) int s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op) { - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); if (chan == NULL) return -EINVAL; @@ -1043,7 +1024,7 @@ EXPORT_SYMBOL(s3c2410_dma_ctrl); int s3c2410_dma_config(unsigned int channel, int xferunit) { - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); unsigned int dcon; pr_debug("%s: chan=%d, xfer_unit=%d, dcon=%08x\n", @@ -1114,7 +1095,7 @@ EXPORT_SYMBOL(s3c2410_dma_config); int s3c2410_dma_setflags(unsigned int channel, unsigned int flags) { - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); if (chan == NULL) return -EINVAL; @@ -1129,42 +1110,6 @@ int s3c2410_dma_setflags(unsigned int channel, unsigned int flags) EXPORT_SYMBOL(s3c2410_dma_setflags); -/* do we need to protect the settings of the fields from - * irq? -*/ - -int s3c2410_dma_set_opfn(unsigned int channel, s3c2410_dma_opfn_t rtn) -{ - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); - - if (chan == NULL) - return -EINVAL; - - pr_debug("%s: chan=%p, op rtn=%p\n", __func__, chan, rtn); - - chan->op_fn = rtn; - - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_set_opfn); - -int s3c2410_dma_set_buffdone_fn(unsigned int channel, s3c2410_dma_cbfn_t rtn) -{ - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); - - if (chan == NULL) - return -EINVAL; - - pr_debug("%s: chan=%p, callback rtn=%p\n", __func__, chan, rtn); - - chan->callback_fn = rtn; - - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn); - /* s3c2410_dma_devconfig * * configure the dma source/destination hardware type and address @@ -1179,7 +1124,7 @@ int s3c2410_dma_devconfig(int channel, enum s3c2410_dmasrc source, unsigned long devaddr) { - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); unsigned int hwcfg; if (chan == NULL) @@ -1250,7 +1195,7 @@ EXPORT_SYMBOL(s3c2410_dma_devconfig); int s3c2410_dma_getposition(unsigned int channel, dma_addr_t *src, dma_addr_t *dst) { - struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); if (chan == NULL) return -EINVAL; @@ -1508,7 +1453,7 @@ static struct s3c2410_dma_chan *s3c2410_dma_map_channel(int channel) dmach = &s3c2410_chans[ch]; dmach->map = ch_map; dmach->req_ch = channel; - dma_chan_map[channel] = dmach; + s3c_dma_chan_map[channel] = dmach; /* select the channel */ diff --git a/arch/arm/plat-s3c24xx/include/plat/dma-core.h b/arch/arm/plat-s3c24xx/include/plat/dma-plat.h index 9d00aed2304..9565ead1bc9 100644 --- a/arch/arm/plat-s3c24xx/include/plat/dma-core.h +++ b/arch/arm/plat-s3c24xx/include/plat/dma-plat.h @@ -1,4 +1,4 @@ -/* linux/arch/arm/plat-s3c24xx/include/plat/dma-core.h +/* linux/arch/arm/plat-s3c24xx/include/plat/dma-plat.h * * Copyright (C) 2006 Simtec Electronics * Ben Dooks <ben@simtec.co.uk> @@ -10,8 +10,10 @@ * published by the Free Software Foundation. */ +#include <plat/dma-core.h> + extern struct sysdev_class dma_sysclass; -extern struct s3c2410_dma_chan s3c2410_chans[S3C2410_DMA_CHANNELS]; +extern struct s3c2410_dma_chan s3c2410_chans[S3C_DMA_CHANNELS]; #define DMA_CH_VALID (1<<31) #define DMA_CH_NEVER (1<<30) @@ -31,8 +33,8 @@ struct s3c24xx_dma_map { const char *name; struct s3c24xx_dma_addr hw_addr; - unsigned long channels[S3C2410_DMA_CHANNELS]; - unsigned long channels_rx[S3C2410_DMA_CHANNELS]; + unsigned long channels[S3C_DMA_CHANNELS]; + unsigned long channels_rx[S3C_DMA_CHANNELS]; }; struct s3c24xx_dma_selection { @@ -58,7 +60,7 @@ extern int s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel); */ struct s3c24xx_dma_order_ch { - unsigned int list[S3C2410_DMA_CHANNELS]; /* list of channels */ + unsigned int list[S3C_DMA_CHANNELS]; /* list of channels */ unsigned int flags; /* flags */ }; diff --git a/arch/arm/plat-s3c64xx/Kconfig b/arch/arm/plat-s3c64xx/Kconfig index 38c284bbd3f..a5719001064 100644 --- a/arch/arm/plat-s3c64xx/Kconfig +++ b/arch/arm/plat-s3c64xx/Kconfig @@ -39,6 +39,10 @@ config CPU_S3C6400_CLOCK Common clock support code for the S3C6400 that is shared by other CPUs in the series, such as the S3C6410. +config S3C64XX_DMA + bool "S3C64XX DMA" + select S3C_DMA + # platform specific device setup config S3C64XX_SETUP_I2C0 diff --git a/arch/arm/plat-s3c64xx/Makefile b/arch/arm/plat-s3c64xx/Makefile index a51e8f15990..4ecefb68252 100644 --- a/arch/arm/plat-s3c64xx/Makefile +++ b/arch/arm/plat-s3c64xx/Makefile @@ -19,14 +19,15 @@ obj-y += irq-eint.o obj-y += clock.o obj-y += gpiolib.o -# fake dma support -obj-y += dma-fake.o - # CPU support obj-$(CONFIG_CPU_S3C6400_INIT) += s3c6400-init.o obj-$(CONFIG_CPU_S3C6400_CLOCK) += s3c6400-clock.o +# DMA support + +obj-$(CONFIG_S3C64XX_DMA) += dma.o + # PM support obj-$(CONFIG_PM) += pm.o diff --git a/arch/arm/plat-s3c64xx/dma-fake.c b/arch/arm/plat-s3c64xx/dma-fake.c index 4ec8b4f369c..a062a849ad5 100644 --- a/arch/arm/plat-s3c64xx/dma-fake.c +++ b/arch/arm/plat-s3c64xx/dma-fake.c @@ -34,64 +34,3 @@ int s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op) } EXPORT_SYMBOL(s3c2410_dma_ctrl); - -int s3c2410_dma_enqueue(unsigned int channel, void *id, - dma_addr_t data, int size) -{ - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_enqueue); - -int s3c2410_dma_devconfig(int channel, - enum s3c2410_dmasrc source, - unsigned long devaddr) -{ - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_devconfig); - - -int s3c2410_dma_getposition(unsigned int channel, dma_addr_t *src, dma_addr_t *dst) -{ - if (src != NULL) - *src = 0; - - if (dst != NULL) - *dst = 0; - - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_getposition); - -int s3c2410_dma_config(unsigned int channel, int xferunit) -{ - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_config); - -int s3c2410_dma_free(unsigned int channel, struct s3c2410_dma_client *client) -{ - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_free); - -int s3c2410_dma_request(unsigned int channel, - struct s3c2410_dma_client *client, - void *dev) -{ - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_request); - -int s3c2410_dma_set_buffdone_fn(unsigned int channel, s3c2410_dma_cbfn_t rtn) -{ - return 0; -} - -EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn); diff --git a/arch/arm/plat-s3c64xx/dma.c b/arch/arm/plat-s3c64xx/dma.c new file mode 100644 index 00000000000..a26fd3bfc93 --- /dev/null +++ b/arch/arm/plat-s3c64xx/dma.c @@ -0,0 +1,685 @@ +/* linux/arch/arm/plat-s3c64xx/dma.c + * + * Copyright 2009 Openmoko, Inc. + * Copyright 2009 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * S3C64XX DMA core + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/dmapool.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include <mach/dma.h> +#include <mach/irqs.h> + +#include <plat/dma-plat.h> + +#include <plat/pl080.h> +#include <mach/map.h> +#include <plat/regs-sys.h> + +#define DEBUG + +#undef pr_debug +#define pr_debug(x...) printk(x) + +/* dma channel state information */ + + +struct s3c64xx_dmac { + void __iomem *regs; + struct s3c2410_dma_chan *channels; + enum dma_ch chanbase; +}; + +/* pool to provide LLI buffers */ +static struct dma_pool *dma_pool; + +/* Debug configuration and code */ + +static unsigned char debug_show_buffs = 0; + +static void dbg_showchan(struct s3c2410_dma_chan *chan) +{ + pr_debug("DMA%d: %08x->%08x L %08x C %08x,%08x S %08x\n", + chan->number, + readl(chan->regs + PL080_CH_SRC_ADDR), + readl(chan->regs + PL080_CH_DST_ADDR), + readl(chan->regs + PL080_CH_LLI), + readl(chan->regs + PL080_CH_CONTROL), + readl(chan->regs + PL080S_CH_CONTROL2), + readl(chan->regs + PL080S_CH_CONFIG)); +} + +static void show_lli(struct pl080_lli *lli) +{ + pr_debug("LLI[%p] %08x->%08x, NL %08x C %08x,%08x\n", + lli, lli->src_addr, lli->dst_addr, lli->next_lli, + lli->control0, lli->control1); +} + +static void dbg_showbuffs(struct s3c2410_dma_chan *chan) +{ + struct s3c64xx_dma_buff *ptr; + struct s3c64xx_dma_buff *end; + + pr_debug("DMA%d: buffs next %p, curr %p, end %p\n", + chan->number, chan->next, chan->curr, chan->end); + + ptr = chan->next; + end = chan->end; + + if (debug_show_buffs) { + for (; ptr != NULL; ptr = ptr->next) { + pr_debug("DMA%d: %08x ", + chan->number, ptr->lli_dma); + show_lli(ptr->lli); + } + } +} + +/* End of Debug */ + +static struct s3c2410_dma_chan *s3c64xx_dma_map_channel(unsigned int channel) +{ + struct s3c2410_dma_chan *chan; + unsigned int start, offs; + + start = 0; + + if (channel >= DMACH_PCM1_TX) + start = 8; + + for (offs = 0; offs < 8; offs++) { + chan = &s3c2410_chans[start + offs]; + if (!chan->in_use) + goto found; + } + + return NULL; + +found: + s3c_dma_chan_map[channel] = chan; + return chan; +} + +int s3c2410_dma_config(unsigned int channel, int xferunit) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + + if (chan == NULL) + return -EINVAL; + + switch (xferunit) { + case 1: + chan->hw_width = 0; + break; + case 2: + chan->hw_width = 1; + break; + case 4: + chan->hw_width = 2; + break; + default: + printk(KERN_ERR "%s: illegal width %d\n", __func__, xferunit); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(s3c2410_dma_config); + +static void s3c64xx_dma_fill_lli(struct s3c2410_dma_chan *chan, + struct pl080_lli *lli, + dma_addr_t data, int size) +{ + dma_addr_t src, dst; + u32 control0, control1; + + switch (chan->source) { + case S3C2410_DMASRC_HW: + src = chan->dev_addr; + dst = data; + control0 = PL080_CONTROL_SRC_AHB2; + control0 |= (u32)chan->hw_width << PL080_CONTROL_SWIDTH_SHIFT; + control0 |= 2 << PL080_CONTROL_DWIDTH_SHIFT; + control0 |= PL080_CONTROL_DST_INCR; + break; + + case S3C2410_DMASRC_MEM: + src = data; + dst = chan->dev_addr; + control0 = PL080_CONTROL_DST_AHB2; + control0 |= (u32)chan->hw_width << PL080_CONTROL_DWIDTH_SHIFT; + control0 |= 2 << PL080_CONTROL_SWIDTH_SHIFT; + control0 |= PL080_CONTROL_SRC_INCR; + break; + default: + BUG(); + } + + /* todo - burst control */ + + control1 = size / 4; /* TODO - calculate */ + control0 |= PL080_CONTROL_PROT_SYS; /* always in priv. mode */ + control0 |= PL080_CONTROL_TC_IRQ_EN; /* always fire IRQ */ + + lli->src_addr = src; + lli->dst_addr = dst; + lli->next_lli = 0; + lli->control0 = control0; + lli->control1 = control1; +} + +static void s3c64xx_lli_to_regs(struct s3c2410_dma_chan *chan, + struct pl080_lli *lli) +{ + void __iomem *regs = chan->regs; + + pr_debug("%s: LLI %p => regs\n", __func__, lli); + show_lli(lli); + + writel(lli->src_addr, regs + PL080_CH_SRC_ADDR); + writel(lli->dst_addr, regs + PL080_CH_DST_ADDR); + writel(lli->next_lli, regs + PL080_CH_LLI); + writel(lli->control0, regs + PL080_CH_CONTROL); + writel(lli->control1, regs + PL080S_CH_CONTROL2); +} + +static int s3c64xx_dma_start(struct s3c2410_dma_chan *chan) +{ + struct s3c64xx_dmac *dmac = chan->dmac; + u32 config; + u32 bit = chan->bit; + + dbg_showchan(chan); + + pr_debug("%s: clearing interrupts\n", __func__); + + /* clear interrupts */ + writel(bit, dmac->regs + PL080_TC_CLEAR); + writel(bit, dmac->regs + PL080_ERR_CLEAR); + + pr_debug("%s: starting channel\n", __func__); + + config = readl(chan->regs + PL080S_CH_CONFIG); + config |= PL080_CONFIG_ENABLE; + + pr_debug("%s: writing config %08x\n", __func__, config); + writel(config, chan->regs + PL080S_CH_CONFIG); + + return 0; +} + +static int s3c64xx_dma_stop(struct s3c2410_dma_chan *chan) +{ + u32 config; + int timeout; + + pr_debug("%s: stopping channel\n", __func__); + + dbg_showchan(chan); + + config = readl(chan->regs + PL080S_CH_CONFIG); + config |= PL080_CONFIG_HALT; + writel(config, chan->regs + PL080S_CH_CONFIG); + + timeout = 1000; + do { + config = readl(chan->regs + PL080S_CH_CONFIG); + pr_debug("%s: %d - config %08x\n", __func__, timeout, config); + if (config & PL080_CONFIG_ACTIVE) + udelay(100); + else + break; + } while (--timeout > 0); + + if (config & PL080_CONFIG_ACTIVE) { + printk(KERN_ERR "%s: channel still active\n", __func__); + return -EFAULT; + } + + config = readl(chan->regs + PL080S_CH_CONFIG); + config &= ~PL080_CONFIG_ENABLE; + writel(config, chan->regs + PL080S_CH_CONFIG); + + return 0; +} + +static inline void s3c64xx_dma_bufffdone(struct s3c2410_dma_chan *chan, + struct s3c64xx_dma_buff *buf, + enum s3c2410_dma_buffresult result) +{ + if (chan->callback_fn != NULL) + (chan->callback_fn)(chan, buf->pw, 0, result); +} + +static void s3c64xx_dma_freebuff(struct s3c64xx_dma_buff *buff) +{ + dma_pool_free(dma_pool, buff->lli, buff->lli_dma); + kfree(buff); +} + +static int s3c64xx_dma_flush(struct s3c2410_dma_chan *chan) +{ + struct s3c64xx_dma_buff *buff, *next; + u32 config; + + dbg_showchan(chan); + + pr_debug("%s: flushing channel\n", __func__); + + config = readl(chan->regs + PL080S_CH_CONFIG); + config &= ~PL080_CONFIG_ENABLE; + writel(config, chan->regs + PL080S_CH_CONFIG); + + /* dump all the buffers associated with this channel */ + + for (buff = chan->curr; buff != NULL; buff = next) { + next = buff->next; + pr_debug("%s: buff %p (next %p)\n", __func__, buff, buff->next); + + s3c64xx_dma_bufffdone(chan, buff, S3C2410_RES_ABORT); + s3c64xx_dma_freebuff(buff); + } + + chan->curr = chan->next = chan->end = NULL; + + return 0; +} + +int s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + + WARN_ON(!chan); + if (!chan) + return -EINVAL; + + switch (op) { + case S3C2410_DMAOP_START: + return s3c64xx_dma_start(chan); + + case S3C2410_DMAOP_STOP: + return s3c64xx_dma_stop(chan); + + case S3C2410_DMAOP_FLUSH: + return s3c64xx_dma_flush(chan); + + /* belive PAUSE/RESUME are no-ops */ + case S3C2410_DMAOP_PAUSE: + case S3C2410_DMAOP_RESUME: + case S3C2410_DMAOP_STARTED: + case S3C2410_DMAOP_TIMEOUT: + return 0; + } + + return -ENOENT; +} +EXPORT_SYMBOL(s3c2410_dma_ctrl); + +/* s3c2410_dma_enque + * + */ + +int s3c2410_dma_enqueue(unsigned int channel, void *id, + dma_addr_t data, int size) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + struct s3c64xx_dma_buff *next; + struct s3c64xx_dma_buff *buff; + struct pl080_lli *lli; + int ret; + + WARN_ON(!chan); + if (!chan) + return -EINVAL; + + buff = kzalloc(sizeof(struct s3c64xx_dma_buff), GFP_KERNEL); + if (!buff) { + printk(KERN_ERR "%s: no memory for buffer\n", __func__); + return -ENOMEM; + } + + lli = dma_pool_alloc(dma_pool, GFP_KERNEL, &buff->lli_dma); + if (!lli) { + printk(KERN_ERR "%s: no memory for lli\n", __func__); + ret = -ENOMEM; + goto err_buff; + } + + pr_debug("%s: buff %p, dp %08x lli (%p, %08x) %d\n", + __func__, buff, data, lli, (u32)buff->lli_dma, size); + + buff->lli = lli; + buff->pw = id; + + s3c64xx_dma_fill_lli(chan, lli, data, size); + + if ((next = chan->next) != NULL) { + struct s3c64xx_dma_buff *end = chan->end; + struct pl080_lli *endlli = end->lli; + + pr_debug("enquing onto channel\n"); + + end->next = buff; + endlli->next_lli = buff->lli_dma; + + if (chan->flags & S3C2410_DMAF_CIRCULAR) { + struct s3c64xx_dma_buff *curr = chan->curr; + lli->next_lli = curr->lli_dma; + } + + if (next == chan->curr) { + writel(buff->lli_dma, chan->regs + PL080_CH_LLI); + chan->next = buff; + } + + show_lli(endlli); + chan->end = buff; + } else { + pr_debug("enquing onto empty channel\n"); + + chan->curr = buff; + chan->next = buff; + chan->end = buff; + + s3c64xx_lli_to_regs(chan, lli); + } + + show_lli(lli); + + dbg_showchan(chan); + dbg_showbuffs(chan); + return 0; + +err_buff: + kfree(buff); + return ret; +} + +EXPORT_SYMBOL(s3c2410_dma_enqueue); + + +int s3c2410_dma_devconfig(int channel, + enum s3c2410_dmasrc source, + unsigned long devaddr) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + u32 peripheral; + u32 config = 0; + + printk("%s: channel %d, source %d, dev %08lx, chan %p\n", + __func__, channel, source, devaddr, chan); + + WARN_ON(!chan); + if (!chan) + return -EINVAL; + + peripheral = (chan->peripheral & 0xf); + chan->source = source; + chan->dev_addr = devaddr; + + pr_debug("%s: peripheral %d\n", __func__, peripheral); + + switch (source) { + case S3C2410_DMASRC_HW: + config = 2 << PL080_CONFIG_FLOW_CONTROL_SHIFT; + config |= peripheral << PL080_CONFIG_SRC_SEL_SHIFT; + break; + case S3C2410_DMASRC_MEM: + config = 1 << PL080_CONFIG_FLOW_CONTROL_SHIFT; + config |= peripheral << PL080_CONFIG_DST_SEL_SHIFT; + break; + default: + printk(KERN_ERR "%s: bad source\n", __func__); + return -EINVAL; + } + + /* allow TC and ERR interrupts */ + config |= PL080_CONFIG_TC_IRQ_MASK; + config |= PL080_CONFIG_ERR_IRQ_MASK; + + pr_debug("%s: config %08x\n", __func__, config); + + writel(config, chan->regs + PL080S_CH_CONFIG); + + return 0; +} +EXPORT_SYMBOL(s3c2410_dma_devconfig); + + +int s3c2410_dma_getposition(unsigned int channel, + dma_addr_t *src, dma_addr_t *dst) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + + WARN_ON(!chan); + if (!chan) + return -EINVAL; + + if (src != NULL) + *src = readl(chan->regs + PL080_CH_SRC_ADDR); + + if (dst != NULL) + *dst = readl(chan->regs + PL080_CH_DST_ADDR); + + return 0; +} +EXPORT_SYMBOL(s3c2410_dma_getposition); + +/* s3c2410_request_dma + * + * get control of an dma channel +*/ + +int s3c2410_dma_request(unsigned int channel, + struct s3c2410_dma_client *client, + void *dev) +{ + struct s3c2410_dma_chan *chan; + unsigned long flags; + + pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n", + channel, client->name, dev); + + local_irq_save(flags); + + chan = s3c64xx_dma_map_channel(channel); + if (chan == NULL) { + local_irq_restore(flags); + return -EBUSY; + } + + dbg_showchan(chan); + + chan->client = client; + chan->in_use = 1; + chan->peripheral = channel; + + local_irq_restore(flags); + + /* need to setup */ + + pr_debug("%s: channel initialised, %p\n", __func__, chan); + + return chan->number | DMACH_LOW_LEVEL; +} + +EXPORT_SYMBOL(s3c2410_dma_request); + +/* s3c2410_dma_free + * + * release the given channel back to the system, will stop and flush + * any outstanding transfers, and ensure the channel is ready for the + * next claimant. + * + * Note, although a warning is currently printed if the freeing client + * info is not the same as the registrant's client info, the free is still + * allowed to go through. +*/ + +int s3c2410_dma_free(unsigned int channel, struct s3c2410_dma_client *client) +{ + struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); + unsigned long flags; + + if (chan == NULL) + return -EINVAL; + + local_irq_save(flags); + + if (chan->client != client) { + printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)\n", + channel, chan->client, client); + } + + /* sort out stopping and freeing the channel */ + + + chan->client = NULL; + chan->in_use = 0; + + if (!(channel & DMACH_LOW_LEVEL)) + s3c_dma_chan_map[channel] = NULL; + + local_irq_restore(flags); + + return 0; +} + +EXPORT_SYMBOL(s3c2410_dma_free); + + +static void s3c64xx_dma_tcirq(struct s3c64xx_dmac *dmac, int offs) +{ + struct s3c2410_dma_chan *chan = dmac->channels + offs; + + /* note, we currently do not bother to work out which buffer + * or buffers have been completed since the last tc-irq. */ + + if (chan->callback_fn) + (chan->callback_fn)(chan, chan->curr->pw, 0, S3C2410_RES_OK); +} + +static void s3c64xx_dma_errirq(struct s3c64xx_dmac *dmac, int offs) +{ + printk(KERN_DEBUG "%s: offs %d\n", __func__, offs); +} + +static irqreturn_t s3c64xx_dma_irq(int irq, void *pw) +{ + struct s3c64xx_dmac *dmac = pw; + u32 tcstat, errstat; + u32 bit; + int offs; + + tcstat = readl(dmac->regs + PL080_TC_STATUS); + errstat = readl(dmac->regs + PL080_ERR_STATUS); + + for (offs = 0, bit = 1; offs < 8; offs++, bit <<= 1) { + if (tcstat & bit) { + writel(bit, dmac->regs + PL080_TC_CLEAR); + s3c64xx_dma_tcirq(dmac, offs); + } + + if (errstat & bit) { + s3c64xx_dma_errirq(dmac, offs); + writel(bit, dmac->regs + PL080_ERR_CLEAR); + } + } + + return IRQ_HANDLED; +} + +static int s3c64xx_dma_init1(int chno, enum dma_ch chbase, + int irq, unsigned int base) +{ + struct s3c2410_dma_chan *chptr = &s3c2410_chans[chno]; + struct s3c64xx_dmac *dmac; + void __iomem *regs; + void __iomem *regptr; + int err, ch; + + dmac = kzalloc(sizeof(struct s3c64xx_dmac), GFP_KERNEL); + if (!dmac) { + printk(KERN_ERR "%s: failed to alloc mem\n", __func__); + return -ENOMEM; + } + + regs = ioremap(base, 0x200); + if (!regs) { + printk(KERN_ERR "%s: failed to ioremap()\n", __func__); + err = -ENXIO; + goto err_alloc; + } + + dmac->regs = regs; + dmac->chanbase = chbase; + dmac->channels = chptr; + + err = request_irq(irq, s3c64xx_dma_irq, 0, "DMA", dmac); + if (err < 0) { + printk(KERN_ERR "%s: failed to get irq\n", __func__); + goto err_map; + } + + regptr = regs + PL080_Cx_BASE(0); + + for (ch = 0; ch < 8; ch++, chno++, chptr++) { + printk(KERN_INFO "%s: registering DMA %d (%p)\n", + __func__, chno, regptr); + + chptr->bit = 1 << ch; + chptr->number = chno; + chptr->dmac = dmac; + chptr->regs = regptr; + regptr += PL008_Cx_STRIDE; + } + + /* for the moment, permanently enable the controller */ + writel(PL080_CONFIG_ENABLE, regs + PL080_CONFIG); + + printk(KERN_INFO "PL080: IRQ %d, at %p\n", irq, regs); + + return 0; + +err_map: + iounmap(regs); +err_alloc: + kfree(dmac); + return err; +} + +static int __init s3c64xx_dma_init(void) +{ + printk(KERN_INFO "%s: Registering DMA channels\n", __func__); + + dma_pool = dma_pool_create("DMA-LLI", NULL, 32, 16, 0); + if (!dma_pool) { + printk(KERN_ERR "%s: failed to create pool\n", __func__); + return -ENOMEM; + } + + /* Set all DMA configuration to be DMA, not SDMA */ + writel(0xffffff, S3C_SYSREG(0x110)); + + /* Register standard DMA controlers */ + s3c64xx_dma_init1(0, DMACH_UART0, IRQ_DMA0, 0x75000000); + s3c64xx_dma_init1(8, DMACH_PCM1_TX, IRQ_DMA1, 0x75100000); + + return 0; +} + +arch_initcall(s3c64xx_dma_init); diff --git a/arch/arm/plat-s3c64xx/include/plat/dma-plat.h b/arch/arm/plat-s3c64xx/include/plat/dma-plat.h new file mode 100644 index 00000000000..90baf8287bc --- /dev/null +++ b/arch/arm/plat-s3c64xx/include/plat/dma-plat.h @@ -0,0 +1,70 @@ +/* linux/arch/arm/plat-s3c64xx/include/plat/dma-plat.h + * + * Copyright 2009 Openmoko, Inc. + * Copyright 2009 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * S3C64XX DMA core + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#define DMACH_LOW_LEVEL (1<<28) /* use this to specifiy hardware ch no */ + +struct s3c64xx_dma_buff; + +/** s3c64xx_dma_buff - S3C64XX DMA buffer descriptor + * @next: Pointer to next buffer in queue or ring. + * @pw: Client provided identifier + * @lli: Pointer to hardware descriptor this buffer is associated with. + * @lli_dma: Hardare address of the descriptor. + */ +struct s3c64xx_dma_buff { + struct s3c64xx_dma_buff *next; + + void *pw; + struct pl080_lli *lli; + dma_addr_t lli_dma; +}; + +struct s3c64xx_dmac; + +struct s3c2410_dma_chan { + unsigned char number; /* number of this dma channel */ + unsigned char in_use; /* channel allocated */ + unsigned char bit; /* bit for enable/disable/etc */ + unsigned char hw_width; + unsigned char peripheral; + + unsigned int flags; + enum s3c2410_dmasrc source; + + + dma_addr_t dev_addr; + + struct s3c2410_dma_client *client; + struct s3c64xx_dmac *dmac; /* pointer to controller */ + + void __iomem *regs; + + /* cdriver callbacks */ + s3c2410_dma_cbfn_t callback_fn; /* buffer done callback */ + s3c2410_dma_opfn_t op_fn; /* channel op callback */ + + /* buffer list and information */ + struct s3c64xx_dma_buff *curr; /* current dma buffer */ + struct s3c64xx_dma_buff *next; /* next buffer to load */ + struct s3c64xx_dma_buff *end; /* end of queue */ + + /* note, when channel is running in circular mode, curr is the + * first buffer enqueued, end is the last and curr is where the + * last buffer-done event is set-at. The buffers are not freed + * and the last buffer hardware descriptor points back to the + * first. + */ +}; + +#include <plat/dma-core.h> diff --git a/arch/arm/plat-s3c64xx/include/plat/pl080.h b/arch/arm/plat-s3c64xx/include/plat/pl080.h new file mode 100644 index 00000000000..afe161f475d --- /dev/null +++ b/arch/arm/plat-s3c64xx/include/plat/pl080.h @@ -0,0 +1,110 @@ +/* arch/arm/include/asm/hardware/pl080.h + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * ARM PrimeCell PL080 DMA controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +/* Note, there are some Samsung updates to this controller block which + * make it not entierly compatible with the PL080 specification from + * ARM. When in doubt, check the Samsung documentation first. + * + * The Samsung defines are PL080S, and add an extra controll register, + * the ability to move more than 2^11 counts of data and some extra + * OneNAND features. +*/ + +#define PL080_INT_STATUS (0x00) +#define PL080_TC_STATUS (0x04) +#define PL080_TC_CLEAR (0x08) +#define PL080_ERR_STATUS (0x0C) +#define PL080_ERR_CLEAR (0x10) +#define PL080_RAW_TC_STATUS (0x14) +#define PL080_RAW_ERR_STATUS (0x18) +#define PL080_EN_CHAN (0x1c) +#define PL080_SOFT_BREQ (0x20) +#define PL080_SOFT_SREQ (0x24) +#define PL080_SOFT_LBREQ (0x28) +#define PL080_SOFT_LSREQ (0x2C) + +#define PL080_CONFIG (0x30) +#define PL080_CONFIG_M2_BE (1 << 2) +#define PL080_CONFIG_M1_BE (1 << 1) +#define PL080_CONFIG_ENABLE (1 << 0) + +#define PL080_SYNC (0x34) + +/* Per channel configuration registers */ + +#define PL008_Cx_STRIDE (0x20) +#define PL080_Cx_BASE(x) ((0x100 + (x * 0x20))) +#define PL080_Cx_SRC_ADDR(x) ((0x100 + (x * 0x20))) +#define PL080_Cx_DST_ADDR(x) ((0x104 + (x * 0x20))) +#define PL080_Cx_LLI(x) ((0x108 + (x * 0x20))) +#define PL080_Cx_CONTROL(x) ((0x10C + (x * 0x20))) +#define PL080_Cx_CONFIG(x) ((0x110 + (x * 0x20))) +#define PL080S_Cx_CONTROL2(x) ((0x110 + (x * 0x20))) +#define PL080S_Cx_CONFIG(x) ((0x114 + (x * 0x20))) + +#define PL080_CH_SRC_ADDR (0x00) +#define PL080_CH_DST_ADDR (0x04) +#define PL080_CH_LLI (0x08) +#define PL080_CH_CONTROL (0x0C) +#define PL080_CH_CONFIG (0x10) +#define PL080S_CH_CONTROL2 (0x10) +#define PL080S_CH_CONFIG (0x14) + +#define PL080_LLI_ADDR_MASK (0x3fffffff << 2) +#define PL080_LLI_ADDR_SHIFT (2) +#define PL080_LLI_LM_AHB2 (1 << 0) + +#define PL080_CONTROL_TC_IRQ_EN (1 << 31) +#define PL080_CONTROL_PROT_MASK (0x7 << 28) +#define PL080_CONTROL_PROT_SHIFT (28) +#define PL080_CONTROL_PROT_SYS (1 << 28) +#define PL080_CONTROL_DST_INCR (1 << 27) +#define PL080_CONTROL_SRC_INCR (1 << 26) +#define PL080_CONTROL_DST_AHB2 (1 << 25) +#define PL080_CONTROL_SRC_AHB2 (1 << 24) +#define PL080_CONTROL_DWIDTH_MASK (0x7 << 21) +#define PL080_CONTROL_DWIDTH_SHIFT (21) +#define PL080_CONTROL_SWIDTH_MASK (0x7 << 18) +#define PL080_CONTROL_SWIDTH_SHIFT (18) +#define PL080_CONTROL_DB_SIZE_MASK (0x7 << 15) +#define PL080_CONTROL_DB_SIZE_SHIFT (15) +#define PL080_CONTROL_SB_SIZE_MASK (0x7 << 12) +#define PL080_CONTROL_SB_SIZE_SHIFT (12) +#define PL080_CONTROL_TRANSFER_SIZE_MASK (0xfff << 0) +#define PL080_CONTROL_TRANSFER_SIZE_SHIFT (0) + +#define PL080_CONFIG_HALT (1 << 18) +#define PL080_CONFIG_ACTIVE (1 << 17) +#define PL080_CONFIG_LOCK (1 << 16) +#define PL080_CONFIG_TC_IRQ_MASK (1 << 15) +#define PL080_CONFIG_ERR_IRQ_MASK (1 << 14) +#define PL080_CONFIG_FLOW_CONTROL_MASK (0x7 << 11) +#define PL080_CONFIG_FLOW_CONTROL_SHIFT (11) +#define PL080_CONFIG_DST_SEL_MASK (0xf << 6) +#define PL080_CONFIG_DST_SEL_SHIFT (6) +#define PL080_CONFIG_SRC_SEL_MASK (0xf << 1) +#define PL080_CONFIG_SRC_SEL_SHIFT (1) +#define PL080_CONFIG_ENABLE (1 << 0) + + +/* DMA linked list chain structure */ + +struct pl080_lli { + u32 src_addr; + u32 dst_addr; + u32 next_lli; + u32 control0; + u32 control1; +}; + diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index efbd4449bd3..6edd3447f5a 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -73,6 +73,8 @@ static inline void wm8731_write_reg_cache(struct snd_soc_codec *codec, u16 *cache = codec->reg_cache; if (reg >= WM8731_CACHEREGNUM) return; + + printk(KERN_INFO "%s: reg %d, val %04x\n", __func__, reg, value); cache[reg] = value; } @@ -84,6 +86,8 @@ static int wm8731_write(struct snd_soc_codec *codec, unsigned int reg, { u8 data[2]; + printk(KERN_INFO "%s: reg %d val %04x\n", __func__, reg, value); + /* data is * D15..D9 WM8731 register offset * D8...D0 register data diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 11dcdfa663f..1ed87482b42 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -84,3 +84,11 @@ config SND_S3C24XX_SOC_S3C24XX_UDA134X depends on SND_S3C24XX_SOC select SND_S3C24XX_SOC_I2S select SND_SOC_UDA134X + +config SND_S3C64XX_SOC_SMDK6410_WM8731 + tristate "SoC I2S Audio support for WM8731 added to an SMDK6410" + depends on SND_S3C24XX_SOC + select SND_S3C64XX_SOC_I2S + select SND_SOC_WM8731 + help + Support for an WM8731 add-on board on I2S channel 0 on an SMDK6410 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index c1ff0e4bcde..08bd5874633 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -21,6 +21,7 @@ snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o snd-soc-neo1973-gta02-wm8753-objs := neo1973_gta02_wm8753.o snd-soc-om-gta03-wm8753-objs := om_gta03_wm8753.o +snd-soc-smdk6410-wm8731-objs := smdk6410-wm8731.o obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o @@ -29,3 +30,4 @@ obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o obj-$(CONFIG_SND_S3C24XX_SOC_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_GTA02_WM8753) += snd-soc-neo1973-gta02-wm8753.o obj-$(CONFIG_SND_S3C24XX_SOC_OM_GTA03_WM8753) += snd-soc-om-gta03-wm8753.o +obj-$(CONFIG_SND_S3C64XX_SOC_SMDK6410_WM8731) += snd-soc-smdk6410-wm8731.o diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 1dda7c85622..8a57f77d9a2 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -257,6 +257,25 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod = readl(i2s->regs + S3C2412_IISMOD); DBG("hw_params r: IISMOD: %x \n", iismod); +#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) +#define IISMOD_MASTER_MASK S3C2412_IISMOD_MASTER_MASK +#define IISMOD_SLAVE S3C2412_IISMOD_SLAVE +#define IISMOD_MASTER S3C2412_IISMOD_MASTER_INTERNAL +#endif + +#if defined(CONFIG_CPU_S3C64XX) +/* From Rev1.1 datasheet, we have two master and two slave modes: + * IMS[11:10]: + * 00 = master mode, fed from PCLK + * 01 = master mode, fed from CLKAUDIO + * 10 = slave mode, using PCLK + * 11 = slave mode, using I2SCLK + */ +#define IISMOD_MASTER_MASK (1 << 11) +#define IISMOD_SLAVE (1 << 11) +#define IISMOD_MASTER (0x0) +#endif + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: i2s->master = 0; diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c index e797e862724..7458f00dfab 100644 --- a/sound/soc/s3c24xx/s3c24xx-pcm.c +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -82,11 +82,19 @@ static void s3c24xx_pcm_enqueue(struct snd_pcm_substream *substream) { struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; dma_addr_t pos = prtd->dma_pos; + unsigned int limit; int ret; DBG("Entered %s\n", __func__); - while (prtd->dma_loaded < prtd->dma_limit) { + if (s3c_dma_has_circular()) { + limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period; + } else + limit = prtd->dma_limit; + + printk(KERN_INFO "%s: loaded %d, limit %d\n", __func__, prtd->dma_loaded, limit); + + while (prtd->dma_loaded < limit) { unsigned long len = prtd->dma_period; DBG("dma_loaded: %d\n", prtd->dma_loaded); @@ -130,7 +138,7 @@ static void s3c24xx_audio_buffdone(struct s3c2410_dma_chan *channel, snd_pcm_period_elapsed(substream); spin_lock(&prtd->lock); - if (prtd->state & ST_RUNNING) { + if (prtd->state & ST_RUNNING && !s3c_dma_has_circular()) { prtd->dma_loaded--; s3c24xx_pcm_enqueue(substream); } @@ -171,6 +179,11 @@ static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream, DBG(KERN_ERR "failed to get dma channel: %d\n", ret); return ret; } + + /* use the circular buffering if we have it available. */ + if (s3c_dma_has_circular()) + s3c2410_dma_setflags(prtd->params->channel, + S3C2410_DMAF_CIRCULAR); } s3c2410_dma_set_buffdone_fn(prtd->params->channel, diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 2109b21ef9f..3334c747cc1 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -49,11 +49,13 @@ static struct s3c2410_dma_client s3c64xx_dma_client_in = { static struct s3c24xx_pcm_dma_params s3c64xx_i2s_pcm_stereo_out[2] = { [0] = { + .channel = DMACH_I2S0_OUT, .client = &s3c64xx_dma_client_out, .dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISTXD, .dma_size = 4, }, [1] = { + .channel = DMACH_I2S1_OUT, .client = &s3c64xx_dma_client_out, .dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISTXD, .dma_size = 4, @@ -62,11 +64,13 @@ static struct s3c24xx_pcm_dma_params s3c64xx_i2s_pcm_stereo_out[2] = { static struct s3c24xx_pcm_dma_params s3c64xx_i2s_pcm_stereo_in[2] = { [0] = { + .channel = DMACH_I2S0_IN, .client = &s3c64xx_dma_client_in, .dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISRXD, .dma_size = 4, }, [1] = { + .channel = DMACH_I2S1_IN, .client = &s3c64xx_dma_client_in, .dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISRXD, .dma_size = 4, @@ -75,16 +79,33 @@ static struct s3c24xx_pcm_dma_params s3c64xx_i2s_pcm_stereo_in[2] = { static struct s3c_i2sv2_info s3c64xx_i2s[2]; -static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, - int clk_id, unsigned int freq, int dir) +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) { - /* TODO */ - return 0; + return cpu_dai->private_data; } -static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) { - return cpu_dai->private_data; + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + switch (clk_id) { + case S3C64XX_CLKSRC_PCLK: + iismod &= ~S3C64XX_IISMOD_IMS_SYSMUX; + break; + + case S3C64XX_CLKSRC_MUX: + iismod |= S3C64XX_IISMOD_IMS_SYSMUX; + break; + + default: + return -EINVAL; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + + return 0; } unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *dai) @@ -95,6 +116,11 @@ unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *dai) } EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clockrate); +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return cpu_dai->private_data; +} + static int s3c64xx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { @@ -119,9 +145,9 @@ static int s3c64xx_i2s_probe(struct platform_device *pdev, i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id]; i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id]; - i2s->iis_cclk = clk_get(dev, "i2sclk"); - if (i2s->iis_cclk == NULL) { - dev_err(dev, "failed to get i2sclk"); + i2s->iis_cclk = clk_get(dev, "audio-bus"); + if (IS_ERR(i2s->iis_cclk)) { + dev_err(dev, "failed to get audio-bus"); iounmap(i2s->regs); return -ENODEV; } diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index add574607ce..b7ffe3c38b6 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -21,7 +21,8 @@ #define S3C64XX_DIV_RCLK S3C_I2SV2_DIV_RCLK #define S3C64XX_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER -/* todo - define clock sources */ +#define S3C64XX_CLKSRC_PCLK (0) +#define S3C64XX_CLKSRC_MUX (1) extern struct snd_soc_dai s3c64xx_i2s_dai; diff --git a/sound/soc/s3c24xx/smdk6410-wm8731.c b/sound/soc/s3c24xx/smdk6410-wm8731.c new file mode 100644 index 00000000000..e11d5d48a5c --- /dev/null +++ b/sound/soc/s3c24xx/smdk6410-wm8731.c @@ -0,0 +1,227 @@ + + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> + +#include "../codecs/wm8731.h" +#include "s3c64xx-i2s.h" + +static struct platform_device *socdev; + + + +static void wm_shutdown(struct snd_pcm_substream *substream) +{ + printk(KERN_INFO "%s: substream %p\n", __func__, substream); +} + +static int wm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int fmt; + int ret; + + printk(KERN_INFO "%s: (%p,%p)\n", __func__, substream, params); + printk(KERN_INFO "%s: dai: cpu %p, codec %p\n", __func__, cpu_dai, codec_dai); + + //fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM; + fmt |= SND_SOC_DAIFMT_I2S; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret < 0) + return ret; + + if (fmt == (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM)) { + unsigned long iis_clkrate; + + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_MUX, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + printk(KERN_ERR "%s: cpu set_sysclk err\n", __func__); + return ret; + } + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C64XX_DIV_PRESCALER, 1); + if (ret < 0) { + printk(KERN_ERR "%s: codec clkdiv err\n", __func__); + return ret; + } + + iis_clkrate = s3c64xx_i2s_get_clockrate(cpu_dai) / 2; + printk(KERN_INFO "%s: clockrate %ld\n", __func__, iis_clkrate); + + iis_clkrate = 12000000; //tmphack// + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, + iis_clkrate, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "%s: codec sysclk err\n", __func__); + return ret; + } + + } else { + /* TODO */ + BUG(); + } + + return 0; +} + +static int wm_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_MUX, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + printk(KERN_ERR "%s: cpu set_sysclk err\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C64XX_DIV_PRESCALER, 1); + if (ret < 0) { + printk(KERN_ERR "%s: cpu set_clkdiv err\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, + 12000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "%s: codec sysclk err\n", __func__); + return ret; + } + + return 0; +} + +static struct snd_soc_ops wm_ops = { + .startup = wm_startup, + .hw_params = wm_hw_params, + .shutdown = wm_shutdown, +}; + +static const struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_INPUT("Line In"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* headphone connected to LHPOUT1, RHPOUT1 */ + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"Line Out", NULL, "LOUT" }, + {"Line Out", NULL, "ROUT" }, + + {"LLINEIN", NULL, "Line In" }, + {"RLINEIN", NULL, "Line In" }, +}; + +static int wm_init(struct snd_soc_codec *codec) +{ + printk(KERN_DEBUG "%s: codec %p\n", __func__, codec); + + snd_soc_dapm_new_controls(codec, widgets, ARRAY_SIZE(widgets)); + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +#include "s3c24xx-pcm.h" + +static struct snd_soc_dai_link wm_dai_link = { + .name = "WM8731", + .stream_name = "WM8731", + .cpu_dai = &s3c64xx_i2s_dai, + .codec_dai = &wm8731_dai, + .init = wm_init, + .ops = &wm_ops, +}; + +static struct snd_soc_card wm_card = { + .name = "SMDK6410-WM8731", + .dai_link = &wm_dai_link, + .platform = &s3c24xx_soc_platform, + .num_links = 1, +}; + +struct wm8731_setup_data wm_setup = { + .i2c_bus = 0, + .i2c_address = 0x1a, +}; + +static struct snd_soc_device wm_snd_devdata = { + .card = &wm_card, + .codec_dev = &soc_codec_dev_wm8731, + .codec_data = &wm_setup, +}; + +static int __init smdk6410_wm8731_init(void) +{ + int ret; + + printk(KERN_INFO "%s: welcome\n", __func__); + + if (!machine_is_smdk6410()) { + printk(KERN_INFO "%s: for SMDK6410s\n", __func__); + return -ENOENT; + } + + socdev = platform_device_alloc("soc-audio", 0); + if (!socdev) { + printk(KERN_ERR "%s: no device\n", __func__); + return -ENOMEM; + } + + platform_set_drvdata(socdev, &wm_snd_devdata); + + wm_snd_devdata.dev = &socdev->dev; + + ret = platform_device_add(socdev); + if (ret) { + printk(KERN_ERR "%s: failed to add\n", __func__); + goto err_dev; + } + + printk(KERN_INFO "%s: succesfull\n", __func__); + return 0; + +err_dev: + platform_device_put(socdev); + return ret; +} + +module_init(smdk6410_wm8731_init); +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_LICENSE("GPL"); |