You can subscribe to this list here.
2000 |
Jan
|
Feb
|
Mar
|
Apr
(12) |
May
(82) |
Jun
(72) |
Jul
(39) |
Aug
(104) |
Sep
(61) |
Oct
(55) |
Nov
(101) |
Dec
(48) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2001 |
Jan
(52) |
Feb
(67) |
Mar
(18) |
Apr
(16) |
May
(33) |
Jun
(12) |
Jul
(102) |
Aug
(168) |
Sep
(65) |
Oct
(60) |
Nov
(43) |
Dec
(121) |
2002 |
Jan
(69) |
Feb
(32) |
Mar
(90) |
Apr
(59) |
May
(45) |
Jun
(43) |
Jul
(33) |
Aug
(21) |
Sep
(11) |
Oct
(20) |
Nov
(26) |
Dec
(3) |
2003 |
Jan
(12) |
Feb
(18) |
Mar
(11) |
Apr
(11) |
May
(41) |
Jun
(76) |
Jul
(77) |
Aug
(15) |
Sep
(38) |
Oct
(56) |
Nov
(19) |
Dec
(39) |
2004 |
Jan
(17) |
Feb
(52) |
Mar
(36) |
Apr
(34) |
May
(48) |
Jun
(85) |
Jul
(38) |
Aug
(42) |
Sep
(41) |
Oct
(77) |
Nov
(27) |
Dec
(19) |
2005 |
Jan
(32) |
Feb
(35) |
Mar
(29) |
Apr
(8) |
May
(7) |
Jun
(31) |
Jul
(46) |
Aug
(93) |
Sep
(65) |
Oct
(85) |
Nov
(219) |
Dec
(47) |
2006 |
Jan
(170) |
Feb
(103) |
Mar
(49) |
Apr
(43) |
May
(45) |
Jun
(29) |
Jul
(77) |
Aug
(82) |
Sep
(43) |
Oct
(45) |
Nov
(26) |
Dec
(85) |
2007 |
Jan
(42) |
Feb
(48) |
Mar
(64) |
Apr
(31) |
May
(88) |
Jun
(53) |
Jul
(175) |
Aug
(212) |
Sep
(91) |
Oct
(103) |
Nov
(110) |
Dec
(5) |
2008 |
Jan
(20) |
Feb
(11) |
Mar
(1) |
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
(5) |
Sep
(3) |
Oct
(12) |
Nov
|
Dec
|
From: Kristoffer E. <kri...@gm...> - 2007-05-16 00:10:30
|
Got the manual from Valeriy, so im happy :) Thanks to everyone for sending suggestions and relevant docs, and of course to Valeriy. On Tue, 15 May 2007 19:58:50 +0200 "Kristoffer Ericson" <kri...@gm...> wrote: > Greetings, > > Been trying to get hold of the manual for hd64461. I already got a copy of > the sh7709 manual so only missing this. > Had no luck searching through renesas or hitachi's webpages, only broken > links. > > So anyone out there with a copy, please let me know. > > Best wishes > Kristoffer Ericson > |
From: Kristoffer E. <kri...@gm...> - 2007-05-15 17:58:55
|
Greetings, Been trying to get hold of the manual for hd64461. I already got a copy of the sh7709 manual so only missing this. Had no luck searching through renesas or hitachi's webpages, only broken links. So anyone out there with a copy, please let me know. Best wishes Kristoffer Ericson |
From: Kristoffer E. <kri...@gm...> - 2007-05-15 10:49:58
|
If the irq.chip handler is at fault then that would explain my pcmcia issues also. I've added the hd64461 pcmcia code, but get strange unexpected irq's even though they should be registerd. Thanks for info, know where to start looking. 2007/5/15, Paul Mundt <le...@li...>: > > On Mon, May 14, 2007 at 03:58:44PM -0700, Kristoffer Ericson wrote: > > Bugtracking the hp6xx_apm functions and ran into some question marks. > This > > happend after the apm merger so its not a big suprise. Doesn't seem to > be > > many systems using apm in arch/sh though. > > > Yes, we've started on the SH7722 work tying in to the clock framework, > but not quite there yet. hp6xx is the only in-tree user of the code at > the moment, so you get to run in to all of these problems before anyone > else :-) > > > Problems: > > 1. Once powerbutton is hit IRQ 32 is generated and never stops > > Best approach? Clear request bit? But will that stop anything since a > > new request will be created before the power button can be released. > > Sounds like it's not acked and masked properly, start debugging the > irq_chip handler that's got this IRQ. > > > 2. mutex_lock(&state_lock); from apm-emulation.c seems to freeze the > > machine. adding a ifndef config_cpu_subsystem.. > > makes it run abit longer. I noticed that the mutex lock wasn't in the > > "old" version. Suggestions? > > If it's freezing on a mutex_lock() it means it's blocked while trying to > acquire the mutex, meaning someone got it beforehand and is never > releasing it. Some of this could be related to your call path, I'd > suggest turning on mutex debugging and lockdep and see if anything > tumbles out. > > > 3. Getting apm: apm queue event overflowed before it freezes (freezes > > probably due to some mutex locks I've missed), > > this would indicate that more than one request is sent by the apm > > interrupt handler? > > That seems likely, and would also explain the mutex_lock() re-try before > unlock. Debugging the interrupt handler first makes the most sense. > > > 4. PM_DISK_FIRMWARE is removed, but im not sure if that will be an issue > > since the pm is handled by board specific code which doesn't require > > anything to be saved. It simply turns off all clocks besides tmu/rtc and > > drops power to lcd/pcmcia. Ram shouldn't be cleared. > > > This should not impact you at all, we were basically just abusing the > definition of PM_DISK_FIRMWARE anyways. The current scheme should be more > fundamentally correct. > > > In short I need a good way for the interrupt handler to make sure only > one > > request is sent and someway to get rid off / fix the mutex_lock. It > would > > get ugly with alot of ifdefs but dont see any other solution currently. > > If the IRQ is not being masked and acked, that's a serious bug, and one > that needs to be corrected. It's not worth wasting any time trying to > limit the number of requests it sends if it's constantly firing anyways > (ie, you're never going to go to sleep with IRQs running wild). > |
From: Paul M. <le...@li...> - 2007-05-14 23:27:33
|
On Mon, May 14, 2007 at 03:58:44PM -0700, Kristoffer Ericson wrote: > Bugtracking the hp6xx_apm functions and ran into some question marks. This > happend after the apm merger so its not a big suprise. Doesn't seem to be > many systems using apm in arch/sh though. > Yes, we've started on the SH7722 work tying in to the clock framework, but not quite there yet. hp6xx is the only in-tree user of the code at the moment, so you get to run in to all of these problems before anyone else :-) > Problems: > 1. Once powerbutton is hit IRQ 32 is generated and never stops > Best approach? Clear request bit? But will that stop anything since a > new request will be created before the power button can be released. Sounds like it's not acked and masked properly, start debugging the irq_chip handler that's got this IRQ. > 2. mutex_lock(&state_lock); from apm-emulation.c seems to freeze the > machine. adding a ifndef config_cpu_subsystem.. > makes it run abit longer. I noticed that the mutex lock wasn't in the > "old" version. Suggestions? If it's freezing on a mutex_lock() it means it's blocked while trying to acquire the mutex, meaning someone got it beforehand and is never releasing it. Some of this could be related to your call path, I'd suggest turning on mutex debugging and lockdep and see if anything tumbles out. > 3. Getting apm: apm queue event overflowed before it freezes (freezes > probably due to some mutex locks I've missed), > this would indicate that more than one request is sent by the apm > interrupt handler? That seems likely, and would also explain the mutex_lock() re-try before unlock. Debugging the interrupt handler first makes the most sense. > 4. PM_DISK_FIRMWARE is removed, but im not sure if that will be an issue > since the pm is handled by board specific code which doesn't require > anything to be saved. It simply turns off all clocks besides tmu/rtc and > drops power to lcd/pcmcia. Ram shouldn't be cleared. > This should not impact you at all, we were basically just abusing the definition of PM_DISK_FIRMWARE anyways. The current scheme should be more fundamentally correct. > In short I need a good way for the interrupt handler to make sure only one > request is sent and someway to get rid off / fix the mutex_lock. It would > get ugly with alot of ifdefs but dont see any other solution currently. If the IRQ is not being masked and acked, that's a serious bug, and one that needs to be corrected. It's not worth wasting any time trying to limit the number of requests it sends if it's constantly firing anyways (ie, you're never going to go to sleep with IRQs running wild). |
From: Kristoffer E. <kri...@gm...> - 2007-05-14 22:58:55
|
Bugtracking the hp6xx_apm functions and ran into some question marks. This happend after the apm merger so its not a big suprise. Doesn't seem to be many systems using apm in arch/sh though. Problems: 1. Once powerbutton is hit IRQ 32 is generated and never stops Best approach? Clear request bit? But will that stop anything since a new request will be created before the power button can be released. 2. mutex_lock(&state_lock); from apm-emulation.c seems to freeze the machine. adding a ifndef config_cpu_subsystem.. makes it run abit longer. I noticed that the mutex lock wasn't in the "old" version. Suggestions? 3. Getting apm: apm queue event overflowed before it freezes (freezes probably due to some mutex locks I've missed), this would indicate that more than one request is sent by the apm interrupt handler? 4. PM_DISK_FIRMWARE is removed, but im not sure if that will be an issue since the pm is handled by board specific code which doesn't require anything to be saved. It simply turns off all clocks besides tmu/rtc and drops power to lcd/pcmcia. Ram shouldn't be cleared. In short I need a good way for the interrupt handler to make sure only one request is sent and someway to get rid off / fix the mutex_lock. It would get ugly with alot of ifdefs but dont see any other solution currently. |
From: Takashi I. <ti...@su...> - 2007-05-14 16:44:41
|
At Mon, 14 May 2007 14:23:57 +0200, Manuel Lauss wrote: > > ALSA ASoC support for SH7760 > > This patch adds ALSA ASoC drivers for the Audio interfaces > of the SH7760 SoC: > > Add driver for the SH7760 DMA engine (dmabrg) > Add AC97 driver for HAC unit(s) found on SH7760/SH7780 > Add I2S driver for SSI unit(s) found on SH7760/SH7780 > Add a generic SH7760-AC97 machine driver. > Hook it all up with the build system. > > > sound/soc/Kconfig | 1 + > sound/soc/Makefile | 3 +- > sound/soc/sh/Kconfig | 39 +++++ > sound/soc/sh/Makefile | 14 ++ > sound/soc/sh/dma-sh7760.c | 354 +++++++++++++++++++++++++++++++++++++++ > sound/soc/sh/hac.c | 322 +++++++++++++++++++++++++++++++++++ > sound/soc/sh/sh7760-ac97.c | 92 ++++++++++ > sound/soc/sh/ssi.c | 400 ++++++++++++++++++++++++++++++++++++++++++++ > 8 files changed, 1224 insertions(+), 1 deletions(-) > > > Signed-off-by: Manuel Lauss <ma...@ro...> Thanks. I applied it to ALSA HG tree now. Takashi |
From: Manuel L. <ma...@ro...> - 2007-05-14 12:24:05
|
ALSA ASoC support for SH7760 This patch adds ALSA ASoC drivers for the Audio interfaces of the SH7760 SoC: Add driver for the SH7760 DMA engine (dmabrg) Add AC97 driver for HAC unit(s) found on SH7760/SH7780 Add I2S driver for SSI unit(s) found on SH7760/SH7780 Add a generic SH7760-AC97 machine driver. Hook it all up with the build system. sound/soc/Kconfig | 1 + sound/soc/Makefile | 3 +- sound/soc/sh/Kconfig | 39 +++++ sound/soc/sh/Makefile | 14 ++ sound/soc/sh/dma-sh7760.c | 354 +++++++++++++++++++++++++++++++++++++++ sound/soc/sh/hac.c | 322 +++++++++++++++++++++++++++++++++++ sound/soc/sh/sh7760-ac97.c | 92 ++++++++++ sound/soc/sh/ssi.c | 400 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1224 insertions(+), 1 deletions(-) Signed-off-by: Manuel Lauss <ma...@ro...> diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index dccaa4b..3e3df26 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -26,6 +26,7 @@ menu "SoC Platforms" depends on SND_SOC source "sound/soc/at91/Kconfig" source "sound/soc/pxa/Kconfig" +source "sound/soc/sh/Kconfig" endmenu # Supported codecs diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 98e6f49..e1c9a70 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,4 +1,5 @@ snd-soc-core-objs := soc-core.o soc-dapm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o -obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ +obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ sh/ + diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig new file mode 100644 index 0000000..a332e51 --- /dev/null +++ b/sound/soc/sh/Kconfig @@ -0,0 +1,39 @@ +menu "SoC Audio support for SuperH" + +config SND_SOC_PCM_SH7760 + tristate "SoC Audio support for Renesas SH7760" + depends on CPU_SUBTYPE_SH7760 && SND_SOC + select SH_DMABRG + help + Enable this option for SH7760 AC97/I2S audio support. + + +## +## Audio unit modules +## + +config SND_SOC_SH4_HAC + select AC97_BUS + select SND_SOC_AC97_BUS + select SND_AC97_CODEC + tristate + +config SND_SOC_SH4_SSI + tristate + + + +## +## Boards +## + +config SND_SH7760_AC97 + tristate "SH7760 AC97 sound support" + depends on CPU_SUBTYPE_SH7760 && SND_SOC_PCM_SH7760 + select SND_SOC_SH4_HAC + select SND_SOC_AC97_CODEC + help + This option enables generic sound support for the first + AC97 unit of the SH7760. + +endmenu diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile new file mode 100644 index 0000000..a8e8ab8 --- /dev/null +++ b/sound/soc/sh/Makefile @@ -0,0 +1,14 @@ +## DMA engines +snd-soc-dma-sh7760-objs := dma-sh7760.o +obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o + +## audio units found on some SH-4 +snd-soc-hac-objs := hac.o +snd-soc-ssi-objs := ssi.o +obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o +obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o + +## boards +snd-soc-sh7760-ac97-objs := sh7760-ac97.o + +obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c new file mode 100644 index 0000000..cdee374 --- /dev/null +++ b/sound/soc/sh/dma-sh7760.c @@ -0,0 +1,354 @@ +/* + * SH7760 ("camelot") DMABRG audio DMA unit support + * + * Copyright (C) 2007 Manuel Lauss <ma...@ro...> + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which + * trigger an interrupt when one half of the programmed transfer size + * has been xmitted. + * + * FIXME: little-endian only for now + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <asm/dmabrg.h> + + +/* registers and bits */ +#define BRGATXSAR 0x00 +#define BRGARXDAR 0x04 +#define BRGATXTCR 0x08 +#define BRGARXTCR 0x0C +#define BRGACR 0x10 +#define BRGATXTCNT 0x14 +#define BRGARXTCNT 0x18 + +#define ACR_RAR (1 << 18) +#define ACR_RDS (1 << 17) +#define ACR_RDE (1 << 16) +#define ACR_TAR (1 << 2) +#define ACR_TDS (1 << 1) +#define ACR_TDE (1 << 0) + +/* receiver/transmitter data alignment */ +#define ACR_RAM_NONE (0 << 24) +#define ACR_RAM_4BYTE (1 << 24) +#define ACR_RAM_2WORD (2 << 24) +#define ACR_TAM_NONE (0 << 8) +#define ACR_TAM_4BYTE (1 << 8) +#define ACR_TAM_2WORD (2 << 8) + + +struct camelot_pcm { + unsigned long mmio; /* DMABRG audio channel control reg MMIO */ + unsigned int txid; /* ID of first DMABRG IRQ for this unit */ + + struct snd_pcm_substream *tx_ss; + unsigned long tx_period_size; + unsigned int tx_period; + + struct snd_pcm_substream *rx_ss; + unsigned long rx_period_size; + unsigned int rx_period; + +} cam_pcm_data[2] = { + { + .mmio = 0xFE3C0040, + .txid = DMABRGIRQ_A0TXF, + }, + { + .mmio = 0xFE3C0060, + .txid = DMABRGIRQ_A1TXF, + }, +}; + +#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x))) + +/* + * set a minimum of 16kb per period, to avoid interrupt-"storm" and + * resulting skipping. In general, the bigger the minimum size, the + * better for overall system performance. (The SH7760 is a puny CPU + * with a slow SDRAM interface and poor internal bus bandwidth, + * *especially* when the LCDC is active). The minimum for the DMAC + * is 8 bytes; 16kbytes are enough to get skip-free playback of a + * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain + * reasonable responsiveness in MPlayer. + */ +#define DMABRG_PERIOD_MIN 16 * 1024 +#define DMABRG_PERIOD_MAX 0x03fffffc +#define DMABRG_PREALLOC_BUFFER 32 * 1024 +#define DMABRG_PREALLOC_BUFFER_MAX 32 * 1024 + +/* support everything the SSI supports */ +#define DMABRG_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define DMABRG_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +static struct snd_pcm_hardware camelot_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = DMABRG_FMTS, + .rates = DMABRG_RATES, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 8, /* max of the SSI */ + .buffer_bytes_max = DMABRG_PERIOD_MAX, + .period_bytes_min = DMABRG_PERIOD_MIN, + .period_bytes_max = DMABRG_PERIOD_MAX / 2, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 128, +}; + +static void camelot_txdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->tx_period ^= 1; + snd_pcm_period_elapsed(cam->tx_ss); +} + +static void camelot_rxdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->rx_period ^= 1; + snd_pcm_period_elapsed(cam->rx_ss); +} + +static int camelot_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret, dmairq; + + snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware); + + /* DMABRG buffer half/full events */ + dmairq = (recv) ? cam->txid + 2 : cam->txid; + if (recv) { + cam->rx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->dai->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam); + } else { + cam->tx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_txdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->dai->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam); + } + return 0; +} + +static int camelot_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int dmairq; + + dmairq = (recv) ? cam->txid + 2 : cam->txid; + + if (recv) + cam->rx_ss = NULL; + else + cam->tx_ss = NULL; + + dmabrg_free_irq(dmairq + 1); + dmabrg_free_irq(dmairq); + + return 0; +} + +static int camelot_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + + if (recv) { + cam->rx_period_size = params_period_bytes(hw_params); + cam->rx_period = 0; + } else { + cam->tx_period_size = params_period_bytes(hw_params); + cam->tx_period = 0; + } + return 0; +} + +static int camelot_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int camelot_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + + pr_debug("PCM data: addr 0x%08ulx len %d\n", + (u32)runtime->dma_addr, runtime->dma_bytes); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area; + BRGREG(BRGATXTCR) = runtime->dma_bytes; + } else { + BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area; + BRGREG(BRGARXTCR) = runtime->dma_bytes; + } + + return 0; +} + +static inline void dmabrg_play_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: XFER start, auto-addr-reload */ + BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD; +} + +static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data transmission */ + BRGREG(BRGACR) = acr | ACR_TDS; +} + +static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: recv start, auto-reload */ + BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD; +} + +static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data receiver */ + BRGREG(BRGACR) = acr | ACR_RDS; +} + +static int camelot_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (recv) + dmabrg_rec_dma_start(cam); + else + dmabrg_play_dma_start(cam); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (recv) + dmabrg_rec_dma_stop(cam); + else + dmabrg_play_dma_stop(cam); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t camelot_pos(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + unsigned long pos; + + /* cannot use the DMABRG pointer register: under load, by the + * time ALSA comes around to read the register, it is already + * far ahead (or worse, already done with the fragment) of the + * position at the time the IRQ was triggered, which results in + * fast-playback sound in my test application (ScummVM) + */ + if (recv) + pos = cam->rx_period ? cam->rx_period_size : 0; + else + pos = cam->tx_period ? cam->tx_period_size : 0; + + return bytes_to_frames(runtime, pos); +} + +static struct snd_pcm_ops camelot_pcm_ops = { + .open = camelot_pcm_open, + .close = camelot_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = camelot_hw_params, + .hw_free = camelot_hw_free, + .prepare = camelot_prepare, + .trigger = camelot_trigger, + .pointer = camelot_pos, +}; + +static void camelot_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int camelot_pcm_new(struct snd_card *card, + struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel + * in MMAP mode (i.e. aplay -M) + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX); + + return 0; +} + +struct snd_soc_platform sh7760_soc_platform = { + .name = "sh7760-pcm", + .pcm_ops = &camelot_pcm_ops, + .pcm_new = camelot_pcm_new, + .pcm_free = camelot_pcm_free, +}; +EXPORT_SYMBOL_GPL(sh7760_soc_platform); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c new file mode 100644 index 0000000..8e3f039 --- /dev/null +++ b/sound/soc/sh/hac.c @@ -0,0 +1,322 @@ +/* + * Hitachi Audio Controller (AC97) support for SH7760/SH7780 + * + * Copyright (c) 2007 Manuel Lauss <ma...@ro...> + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * dont forget to set IPSEL/OMSEL register bits (in your board code) to + * enable HAC output pins! + */ + +/* BIG FAT FIXME: although the SH7760 has 2 independent AC97 units, only + * the FIRST can be used since ASoC does not pass any information to the + * ac97_read/write() functions regarding WHICH unit to use. You'll have + * to edit the code a bit to use the other AC97 unit. --mlau + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +/* regs and bits */ +#define HACCR 0x08 +#define HACCSAR 0x20 +#define HACCSDR 0x24 +#define HACPCML 0x28 +#define HACPCMR 0x2C +#define HACTIER 0x50 +#define HACTSR 0x54 +#define HACRIER 0x58 +#define HACRSR 0x5C +#define HACACR 0x60 + +#define CR_CR (1 << 15) /* "codec-ready" indicator */ +#define CR_CDRT (1 << 11) /* cold reset */ +#define CR_WMRT (1 << 10) /* warm reset */ +#define CR_B9 (1 << 9) /* the mysterious "bit 9" */ +#define CR_ST (1 << 5) /* AC97 link start bit */ + +#define CSAR_RD (1 << 19) /* AC97 data read bit */ +#define CSAR_WR (0) + +#define TSR_CMDAMT (1 << 31) +#define TSR_CMDDMT (1 << 30) + +#define RSR_STARY (1 << 22) +#define RSR_STDRY (1 << 21) + +#define ACR_DMARX16 (1 << 30) +#define ACR_DMATX16 (1 << 29) +#define ACR_TX12ATOM (1 << 26) +#define ACR_DMARX20 ((1 << 24) | (1 << 22)) +#define ACR_DMATX20 ((1 << 23) | (1 << 21)) + +#define CSDR_SHIFT 4 +#define CSDR_MASK (0xffff << CSDR_SHIFT) +#define CSAR_SHIFT 12 +#define CSAR_MASK (0x7f << CSAR_SHIFT) + +#define AC97_WRITE_RETRY 1 +#define AC97_READ_RETRY 5 + +/* manual-suggested AC97 codec access timeouts (us) */ +#define TMO_E1 500 /* 21 < E1 < 1000 */ +#define TMO_E2 13 /* 13 < E2 */ +#define TMO_E3 21 /* 21 < E3 */ +#define TMO_E4 500 /* 21 < E4 < 1000 */ + +struct hac_priv { + unsigned long mmio; /* HAC base address */ +} hac_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE240000, + }, + { + .mmio = 0xFE250000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE40000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +#define HACREG(reg) (*(unsigned long *)(hac->mmio + (reg))) + +/* + * AC97 read/write flow as outlined in the SH7760 manual (pages 903-906) + */ +static int hac_get_codec_data(struct hac_priv *hac, unsigned short r, + unsigned short *v) +{ + unsigned int to1, to2, i; + unsigned short adr; + + for (i = 0; i < AC97_READ_RETRY; ++i) { + *v = 0; + /* wait for HAC to receive something from the codec */ + for (to1 = TMO_E4; + to1 && !(HACREG(HACRSR) & RSR_STARY); + --to1) + udelay(1); + for (to2 = TMO_E4; + to2 && !(HACREG(HACRSR) & RSR_STDRY); + --to2) + udelay(1); + + if (!to1 && !to2) + return 0; /* codec comm is down */ + + adr = ((HACREG(HACCSAR) & CSAR_MASK) >> CSAR_SHIFT); + *v = ((HACREG(HACCSDR) & CSDR_MASK) >> CSDR_SHIFT); + + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + + if (r == adr) + break; + + /* manual says: wait at least 21 usec before retrying */ + udelay(21); + } + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + return (i < AC97_READ_RETRY); +} + +static unsigned short hac_read_codec_aux(struct hac_priv *hac, + unsigned short reg) +{ + unsigned short val; + unsigned int i, to; + + for (i = 0; i < AC97_READ_RETRY; i++) { + /* send_read_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDAMT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) | CSAR_RD; + local_irq_enable(); + + for (to = TMO_E3; + to && !(HACREG(HACTSR) & TSR_CMDAMT); + --to) + udelay(1); + + HACREG(HACTSR) &= ~TSR_CMDAMT; + val = 0; + if (hac_get_codec_data(hac, reg, &val) != 0) + break; + } + + if (i == AC97_READ_RETRY) + return ~0; + + return val; +} + +static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int i, to; + /* write_codec_aux */ + for (i = 0; i < AC97_WRITE_RETRY; i++) { + /* send_write_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT); + HACREG(HACCSDR) = (val << CSDR_SHIFT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) & (~CSAR_RD); + local_irq_enable(); + + /* poll-wait for CMDAMT and CMDDMT */ + for (to = TMO_E1; + to && !(HACREG(HACTSR) & (TSR_CMDAMT|TSR_CMDDMT)); + --to) + udelay(1); + + HACREG(HACTSR) &= ~(TSR_CMDAMT | TSR_CMDDMT); + if (to) + break; + /* timeout, try again */ + } +} + +static unsigned short hac_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + return hac_read_codec_aux(hac, reg); +} + +static void hac_ac97_warmrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int tmo; + + HACREG(HACCR) = CR_WMRT | CR_ST | CR_B9; + msleep(10); + HACREG(HACCR) = CR_ST | CR_B9; + for (tmo = 1000; (tmo > 0) && !(HACREG(HACCR) & CR_CR); tmo--) + udelay(1); + + if (!tmo) + printk(KERN_INFO "hac: reset: AC97 link down!\n"); + /* settings this bit lets us have a conversation with codec */ + HACREG(HACACR) |= ACR_TX12ATOM; +} + +static void hac_ac97_coldrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac; + hac = &hac_cpu_data[unit_id]; + + HACREG(HACCR) = 0; + HACREG(HACCR) = CR_CDRT | CR_ST | CR_B9; + msleep(10); + hac_ac97_warmrst(ac97); +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = hac_ac97_read, + .write = hac_ac97_write, + .reset = hac_ac97_coldrst, + .warm_reset = hac_ac97_warmrst, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int hac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hac_priv *hac = &hac_cpu_data[rtd->dai->cpu_dai->id]; + int d = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + switch (params->msbits) { + case 16: + HACREG(HACACR) |= d ? ACR_DMARX16 : ACR_DMATX16; + HACREG(HACACR) &= d ? ~ACR_DMARX20 : ~ACR_DMATX20; + break; + case 20: + HACREG(HACACR) &= d ? ~ACR_DMARX16 : ~ACR_DMATX16; + HACREG(HACACR) |= d ? ACR_DMARX20 : ACR_DMATX20; + break; + default: + pr_debug("hac: invalid depth %d bit\n", params->msbits); + return -EINVAL; + break; + } + + return 0; +} + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define AC97_FMTS \ + SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_cpu_dai sh4_hac_dai[] = { +{ + .name = "HAC0", + .id = 0, + .type = SND_SOC_DAI_AC97, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = hac_hw_params, + }, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "HAC1", + .id = 1, + .type = SND_SOC_DAI_AC97, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = hac_hw_params, + }, + +}, +#endif +}; +EXPORT_SYMBOL_GPL(sh4_hac_dai); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip HAC (AC97) audio driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/sound/soc/sh/sh7760-ac97.c b/sound/soc/sh/sh7760-ac97.c new file mode 100644 index 0000000..5563f14 --- /dev/null +++ b/sound/soc/sh/sh7760-ac97.c @@ -0,0 +1,92 @@ +/* + * Generic AC97 sound support for SH7760 + * + * (c) 2007 Manuel Lauss + * + * Licensed under the GPLv2. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/io.h> + +#include "../codecs/ac97.h" + +#define IPSEL 0xFE400034 + +/* platform specific structs can be declared here */ +extern struct snd_soc_cpu_dai sh4_hac_dai[2]; +extern struct snd_soc_platform sh7760_soc_platform; + +static int machine_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link sh7760_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &sh4_hac_dai[0], /* HAC0 */ + .codec_dai = &ac97_dai, + .init = machine_init, + .ops = NULL, +}; + +static struct snd_soc_machine sh7760_ac97_soc_machine = { + .name = "SH7760 AC97", + .dai_link = &sh7760_ac97_dai, + .num_links = 1, +}; + +static struct snd_soc_device sh7760_ac97_snd_devdata = { + .machine = &sh7760_ac97_soc_machine, + .platform = &sh7760_soc_platform, + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct platform_device *sh7760_ac97_snd_device; + +static int __init sh7760_ac97_init(void) +{ + int ret; + unsigned short ipsel; + + /* enable both AC97 controllers in pinmux reg */ + ipsel = ctrl_inw(IPSEL); + ctrl_outw(ipsel | (3 << 10), IPSEL); + + ret = -ENOMEM; + sh7760_ac97_snd_device = platform_device_alloc("soc-audio", -1); + if (!sh7760_ac97_snd_device) + goto out; + + platform_set_drvdata(sh7760_ac97_snd_device, + &sh7760_ac97_snd_devdata); + sh7760_ac97_snd_devdata.dev = &sh7760_ac97_snd_device->dev; + ret = platform_device_add(sh7760_ac97_snd_device); + + if (ret) + platform_device_put(sh7760_ac97_snd_device); + +out: + return ret; +} + +static void __exit sh7760_ac97_exit(void) +{ + platform_device_unregister(sh7760_ac97_snd_device); +} + +module_init(sh7760_ac97_init); +module_exit(sh7760_ac97_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Generic SH7760 AC97 sound machine"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c new file mode 100644 index 0000000..b72bc31 --- /dev/null +++ b/sound/soc/sh/ssi.c @@ -0,0 +1,400 @@ +/* + * Serial Sound Interface (I2S) support for SH7760/SH7780 + * + * Copyright (c) 2007 Manuel Lauss <ma...@ro...> + * + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * dont forget to set IPSEL/OMSEL register bits (in your board code) to + * enable SSI output pins! + */ + +/* + * LIMITATIONS: + * The SSI unit has only one physical data line, so full duplex is + * impossible. This can be remedied on the SH7760 by using the + * other SSI unit for recording; however the SH7780 has only 1 SSI + * unit, and its pins are shared with the AC97 unit, among others. + * + * FEATURES: + * The SSI features "compressed mode": in this mode it continuously + * streams PCM data over the I2S lines and uses LRCK as a handshake + * signal. Can be used to send compressed data (AC3/DTS) to a DSP. + * The number of bits sent over the wire in a frame can be adjusted + * and can be independent from the actual sample bit depth. This is + * useful to support TDM mode codecs like the AD1939 which have a + * fixed TDM slot size, regardless of sample resolution. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/io.h> + +#define SSICR 0x00 +#define SSISR 0x04 + +#define CR_DMAEN (1 << 28) +#define CR_CHNL_SHIFT 22 +#define CR_CHNL_MASK (3 << CR_CHNL_SHIFT) +#define CR_DWL_SHIFT 19 +#define CR_DWL_MASK (7 << CR_DWL_SHIFT) +#define CR_SWL_SHIFT 16 +#define CR_SWL_MASK (7 << CR_SWL_SHIFT) +#define CR_SCK_MASTER (1 << 15) /* bitclock master bit */ +#define CR_SWS_MASTER (1 << 14) /* wordselect master bit */ +#define CR_SCKP (1 << 13) /* I2Sclock polarity */ +#define CR_SWSP (1 << 12) /* LRCK polarity */ +#define CR_SPDP (1 << 11) +#define CR_SDTA (1 << 10) /* i2s alignment (msb/lsb) */ +#define CR_PDTA (1 << 9) /* fifo data alignment */ +#define CR_DEL (1 << 8) /* delay data by 1 i2sclk */ +#define CR_BREN (1 << 7) /* clock gating in burst mode */ +#define CR_CKDIV_SHIFT 4 +#define CR_CKDIV_MASK (7 << CR_CKDIV_SHIFT) /* bitclock divider */ +#define CR_MUTE (1 << 3) /* SSI mute */ +#define CR_CPEN (1 << 2) /* compressed mode */ +#define CR_TRMD (1 << 1) /* transmit/receive select */ +#define CR_EN (1 << 0) /* enable SSI */ + +#define SSIREG(reg) (*(unsigned long *)(ssi->mmio + (reg))) + +struct ssi_priv { + unsigned long mmio; + unsigned long sysclk; + int inuse; +} ssi_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE680000, + }, + { + .mmio = 0xFE690000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE70000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +/* + * track usage of the SSI; it is simplex-only so prevent attempts of + * concurrent playback + capture. FIXME: any locking required? + */ +static int ssi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + if (ssi->inuse) { + pr_debug("ssi: already in use!\n"); + return -EBUSY; + } else + ssi->inuse = 1; + return 0; +} + +static void ssi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + + ssi->inuse = 0; +} + +static int ssi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + SSIREG(SSICR) |= CR_DMAEN | CR_EN; + break; + case SNDRV_PCM_TRIGGER_STOP: + SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + unsigned long ssicr = SSIREG(SSICR); + unsigned int bits, channels, swl, recv, i; + + channels = params_channels(params); + bits = params->msbits; + recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; + + pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr); + pr_debug("bits: %d channels: %d\n", bits, channels); + + ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA | + CR_SWL_MASK); + + /* direction (send/receive) */ + if (!recv) + ssicr |= CR_TRMD; /* transmit */ + + /* channels */ + if ((channels < 2) || (channels > 8) || (channels & 1)) { + pr_debug("ssi: invalid number of channels\n"); + return -EINVAL; + } + ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT; + + /* DATA WORD LENGTH (DWL): databits in audio sample */ + i = 0; + switch (bits) { + case 32: ++i; + case 24: ++i; + case 22: ++i; + case 20: ++i; + case 18: ++i; + case 16: ++i; + ssicr |= i << CR_DWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid sample width\n"); + return -EINVAL; + } + + /* + * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S + * wires. This is usually bits_per_sample x channels/2; i.e. in + * Stereo mode the SWL equals DWL. SWL can be bigger than the + * product of (channels_per_slot x samplebits), e.g. for codecs + * like the AD1939 which only accept 32bit wide TDM slots. For + * "standard" I2S operation we set SWL = chans / 2 * DWL here. + * Waiting for ASoC to get TDM support ;-) + */ + if ((bits > 16) && (bits <= 24)) { + bits = 24; /* these are padded by the SSI */ + /*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */ + } + i = 0; + swl = (bits * channels) / 2; + switch (swl) { + case 256: ++i; + case 128: ++i; + case 64: ++i; + case 48: ++i; + case 32: ++i; + case 16: ++i; + ssicr |= i << CR_SWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid system word length computed\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + + pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr); + return 0; +} + +static int ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id]; + + ssi->sysclk = freq; + + return 0; +} + +/* + * This divider is used to generate the SSI_SCK (I2S bitclock) from the + * clock at the HAC_BIT_CLK ("oversampling clock") pin. + */ +static int ssi_set_clkdiv(struct snd_soc_cpu_dai *dai, int did, int div) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr; + int i; + + i = 0; + ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK; + switch (div) { + case 16: ++i; + case 8: ++i; + case 4: ++i; + case 2: ++i; + SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT); + case 1: break; + default: + pr_debug("ssi: invalid sck divider %d\n", div); + return -EINVAL; + } + + return 0; +} + +static int ssi_set_fmt(struct snd_soc_cpu_dai *dai, unsigned int fmt) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr = SSIREG(SSICR); + + pr_debug("ssi_set_fmt()\nssicr was 0x%08lx\n", ssicr); + + ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP | + CR_SWS_MASTER | CR_SCK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + ssicr |= CR_DEL | CR_PDTA; + break; + case SND_SOC_DAIFMT_LEFT_J: + ssicr |= CR_DEL; + break; + default: + pr_debug("ssi: unsupported format\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + break; + case SND_SOC_DAIFMT_GATED: + ssicr |= CR_BREN; + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + ssicr |= CR_SCKP; /* sample data at low clkedge */ + break; + case SND_SOC_DAIFMT_NB_IF: + ssicr |= CR_SCKP | CR_SWSP; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + ssicr |= CR_SWSP; /* word select starts low */ + break; + default: + pr_debug("ssi: invalid inversion\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + ssicr |= CR_SCK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: + ssicr |= CR_SWS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ssicr |= CR_SWS_MASTER | CR_SCK_MASTER; + break; + default: + pr_debug("ssi: invalid master/slave configuration\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr); + + return 0; +} + +/* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in + * Master mode, so really this is board specific; the SSI can do any + * rate with the right bitclk and divider settings. + */ +#define SSI_RATES \ + SNDRV_PCM_RATE_8000_192000 + +/* the SSI can do 8-32 bit samples, with 8 possible channels */ +#define SSI_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +struct snd_soc_cpu_dai sh4_ssi_dai[] = { +{ + .name = "SSI0", + .id = 0, + .type = SND_SOC_DAI_I2S, + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + }, + .dai_ops = { + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, + }, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "SSI1", + .id = 1, + .type = SND_SOC_DAI_I2S, + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + }, + .dai_ops = { + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, + }, +}, +#endif +}; +EXPORT_SYMBOL_GPL(sh4_ssi_dai); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); |
From: Takashi I. <ti...@su...> - 2007-05-14 09:21:49
|
At Thu, 10 May 2007 17:42:13 +0200, Manuel Lauss wrote: > > Hello, > > here goes attempt #3 for the SH7760 ASoC patch. > Changes v2 -> v3 > - added a generic demo board for AC97 > > Changes v1 -> v2 > - incorporate Paul Mundt's suggestions > - work around an oops when aplay is used in mmap mode ('aplay -M') > by changing the allocation type to SNDRV_DMA_TYPE_CONTINUOUS > > > As soon as I get Linux working on the EDOSK7780 board, I'll complete > SH7780 support as well. The driver seems to be in a good shape, and I'd like to make it it into ALSA tree (unless you have any plan to change it radically). Could you give a changelog and a sign-off? Thanks, Takashi |
From: Paul M. <le...@li...> - 2007-05-12 08:49:58
|
On Sat, May 12, 2007 at 05:15:01PM +0900, kogiidena wrote: > Please apply following patch. > > Deletion of unused macro and unused variable definition > in gio.h and iodata_landisk.h. > > Signed-off-by: kogiidena <kog...@eg...> > Applied, thanks. |
From: Paul M. <le...@li...> - 2007-05-12 08:49:47
|
On Sat, May 12, 2007 at 05:14:47PM +0900, kogiidena wrote: > Please apply following patch. > > added rtc-rs5c313 support. > > Signed-off-by: kogiidena <kog...@eg...> > Applied, thanks. |
From: kogiidena <kog...@eg...> - 2007-05-12 08:15:05
|
Hi Paul-san Please apply following patch. Deletion of unused macro and unused variable definition in gio.h and iodata_landisk.h. Signed-off-by: kogiidena <kog...@eg...> kogiidena --- diff -urpN OLD/include/asm-sh/landisk/gio.h NEW/include/asm-sh/landisk/gio.h --- OLD/include/asm-sh/landisk/gio.h 2007-04-28 06:49:26.000000000 +0900 +++ NEW/include/asm-sh/landisk/gio.h 2007-05-12 16:47:10.000000000 +0900 @@ -29,16 +29,8 @@ #define GIODRV_IOCGGIODATA4 _IOR(GIODRV_IOC_MAGIC, 6, unsigned long *) #define GIODRV_IOCSGIOSETADDR _IOW(GIODRV_IOC_MAGIC, 7, unsigned long *) #define GIODRV_IOCHARDRESET _IO(GIODRV_IOC_MAGIC, 8) /* debugging tool */ - -#define GIODRV_IOCSGIO_LED _IOW(GIODRV_IOC_MAGIC, 9, unsigned long *) -#define GIODRV_IOCGGIO_LED _IOR(GIODRV_IOC_MAGIC, 10, unsigned long *) -#define GIODRV_IOCSGIO_BUZZER _IOW(GIODRV_IOC_MAGIC, 11, unsigned long *) -#define GIODRV_IOCGGIO_LANDISK _IOR(GIODRV_IOC_MAGIC, 14, unsigned long *) -#define GIODRV_IOCGGIO_BTN _IOR(GIODRV_IOC_MAGIC, 22, unsigned long *) -#define GIODRV_IOCSGIO_BTNPID _IOW(GIODRV_IOC_MAGIC, 23, unsigned long *) -#define GIODRV_IOCGGIO_BTNPID _IOR(GIODRV_IOC_MAGIC, 24, unsigned long *) - #define GIODRV_IOC_MAXNR 8 + #define GIO_READ 0x00000000 #define GIO_WRITE 0x00000001 diff -urpN OLD/include/asm-sh/landisk/iodata_landisk.h NEW/include/asm-sh/landisk/iodata_landisk.h --- OLD/include/asm-sh/landisk/iodata_landisk.h 2007-04-28 06:49:26.000000000 +0900 +++ NEW/include/asm-sh/landisk/iodata_landisk.h 2007-05-12 16:51:36.000000000 +0900 @@ -22,16 +22,6 @@ /* 2003.10.31 I-O DATA NSD NWG add. for shutdown port clear */ #define PA_PWRINT_CLR 0xb0000006 /* Shutdown Interrupt clear Register */ -#define PA_LCD_CLRDSP 0x00 /* LCD Clear Display Offset */ -#define PA_LCD_RTNHOME 0x00 /* LCD Return Home Offset */ -#define PA_LCD_ENTMODE 0x00 /* LCD Entry Mode Offset */ -#define PA_LCD_DSPCTL 0x00 /* LCD Display ON/OFF Control Offset */ -#define PA_LCD_FUNC 0x00 /* LCD Function Set Offset */ -#define PA_LCD_CGRAM 0x00 /* LCD Set CGRAM Address Offset */ -#define PA_LCD_DDRAM 0x00 /* LCD Set DDRAM Address Offset */ -#define PA_LCD_RDFLAG 0x01 /* LCD Read Busy Flag Offset */ -#define PA_LCD_WTDATA 0x02 /* LCD Write Datat to RAM Offset */ -#define PA_LCD_RDDATA 0x03 /* LCD Read Data from RAM Offset */ #define PA_PIDE_OFFSET 0x40 /* CF IDE Offset */ #define PA_SIDE_OFFSET 0x40 /* HDD IDE Offset */ @@ -45,33 +35,6 @@ #define IRQ_BUTTON 12 /* USL-5P Button IRQ */ #define IRQ_FAULT 13 /* USL-5P Fault IRQ */ -#define SHUTDOWN_BTN_MAJOR 99 /* Shutdown button device major no. */ - -#define SHUTDOWN_LOOP_CNT 5 /* Shutdown button Detection loop */ -#define SHUTDOWN_DELAY 200 /* Shutdown button delay value(ms) */ - - -/* added by kogiidena */ -/* - * landisk_ledparam - * - * led ------10 -6543210 -6543210 -6543210 - * |000000..|0.......|0.......|U.......| - * | HARD |fastblik| blink | on | - * - * led0: power U:update flag - * led1: error - * led2: usb1 - * led3: usb2 - * led4: usb3 - * led5: usb4 - * led6: usb5 - * - */ -extern int landisk_ledparam; /* from setup.c */ -extern int landisk_buzzerparam; /* from setup.c */ -extern int landisk_arch; /* from setup.c */ - #define __IO_PREFIX landisk #include <asm/io_generic.h> |
From: kogiidena <kog...@eg...> - 2007-05-12 08:15:03
|
Hi Paul-san Please apply following patch. added rtc-rs5c313 support. Signed-off-by: kogiidena <kog...@eg...> kogiidena --- diff -urpN OLD/arch/sh/boards/landisk/setup.c NEW/arch/sh/boards/landisk/setup.c --- OLD/arch/sh/boards/landisk/setup.c 2007-05-10 19:05:31.000000000 +0900 +++ NEW/arch/sh/boards/landisk/setup.c 2007-05-12 16:40:46.000000000 +0900 @@ -44,8 +44,14 @@ static struct platform_device cf_ide_dev }, }; +static struct platform_device rtc_device = { + .name = "rs5c313", + .id = -1, +}; + static struct platform_device *landisk_devices[] __initdata = { &cf_ide_device, + &rtc_device, }; static int __init landisk_devices_setup(void) |
From: Paul M. <le...@li...> - 2007-05-12 02:18:40
|
On Thu, May 10, 2007 at 05:10:03PM -0700, Kristoffer Ericson wrote: > Trivial fix to follow the DECLARE_WORK changes, this makes the HD64461 > touchscreen driver work properly again.As pointed out by David Howells > <dho...@re...> > > signed-off-by: Kristoffer Ericson <Kri...@gm...> > Applied, thanks. |
From: Liam G. <lg...@op...> - 2007-05-11 12:19:49
|
On Thu, 2007-05-10 at 17:42 +0200, Manuel Lauss wrote: > Hello, > > here goes attempt #3 for the SH7760 ASoC patch. > Changes v2 -> v3 > - added a generic demo board for AC97 > > Changes v1 -> v2 > - incorporate Paul Mundt's suggestions > - work around an oops when aplay is used in mmap mode ('aplay -M') > by changing the allocation type to SNDRV_DMA_TYPE_CONTINUOUS > Ack. Liam |
From: Cmmn Ml <cm...@ya...> - 2007-05-11 09:29:57
|
Hi Paul, Thanks for the reply, as I'm still in the process of getting the problem fixed. Let me allow to brief you about the problem I'm having. As you have mentioned about my configuration file, it is based on SE7705 defconfig file. The board I'm working on is virtually the same as the Solution Engine board. On this board 2.6.16.9 kernel (with sh--stable--2.6.16--base-0 patch) is running well without any problem other than a problem I was experiencing with JFFS2 file system (trying to copy a directory which is larger than around 2 MB makes the system to crash). Because of that I'm trying to use 2.6.21-rc7 kernel to see whether the JFFS2 problem is fixed in it. To build the 2.6.21-rc7 kernel for SH7705, I had to add a patch from 2.6-git added by Nobuhiro (http://git.kernel.org/?p=linux/kernel/git/lethal/sh-2.6.git;a=commit;h=5452d25e28f365938b36f3a972fe2a1bb69a4c3e). This patched 2.6.21-rc7 kernel boots up fine, but fails to start the /sbin/init and stops running. To check at least the timer interrupt is handled after this point, I added a printk into the timer interrupt handler. With this, when the kernel boots up, after the timer interrupt is registered, I'm getting the printk messages for each timer interrupt as expected. As you suggested that makes me sure that the timer IRQ has no problem. But at the end of the initial kernel bootup process, when it tries to start the system console by calling run_init_process("/sbin/init"), everything goes wrong. As I mentioned, after calling run_init_process("/sbin/init") function, the kernel hangs at that point and there after I'm not getting the printk message from the timer interrupt handler. That means after this point, the kernel is not in a running state. Based on all the above observations, I suspect that we may have a problem in the kernel where it tries to start the /sbin/init. I'm wondering to know that can it be a possibility that there may be a problem in the kernel when it tries to start /sbin/init ? if so where should I look to fix it up, and is there any one who has successfully used the 2.6.21-rc7 kernel on SH7705 without this problem ? If any one of you can give me some thoughts about this, that will be a great help for me to fix it up. Thanks in advance. Cmmn Paul Mundt <le...@li...> wrote: On Wed, May 09, 2007 at 07:33:58AM -0700, Cmmn Ml wrote: > As a summary what happens is the kernel boots up fine up to the point > where run_init_process("/sbin/init") is called. After the > run_init_process("/sbin/init") function is called, kernel hangs itself > and even the timer interrupt is not handled. As I feel the kernel fails > to start a user space process and puts it self to a not running state. > If you're never getting the timer interrupt, then there's certainly something terribly wrong with the IRQ handling. That has to be fixed first. These should already be handled via the IPR map though, are you dealing with a custom board? Do you have any unusual TMU configuration? Also make sure that the timer IRQ is set to the proper vector.. You need to be getting interrupts before you start worrying about userspace problems. Your config looks pretty close to the SE7705 defconfig, so the timer should certainly not be causing you any difficulty. --------------------------------- Be a better Heartthrob. Get better relationship answers from someone who knows. Yahoo! Answers - Check it out. |
From: Paul M. <le...@li...> - 2007-05-11 02:30:13
|
Hi Saito-san, On Fri, May 11, 2007 at 11:07:26AM +0900, Hideo Saito wrote: > On Thu, 10 May 2007 14:15:26 +0900, Paul Mundt wrote: > > No, consider the use case where a platform implements pm_idle() and > > depends on certain conditions to be met to determine what sort of > > power-save state can be entered. It's still necessary to have the logic > > in default_idle() and to have that exported so it can be called back in > > to from pm_idle(), should the latter not have any meaningful work it can > > do at the time. > > I see, but I think following change in default_idle() does not need. > > + } else > + while (!need_resched()) > + cpu_relax(); > > how about following change? > > + } else > cpu_relax(); > That's fine as well. I shoved the while (!need_resched()) in there so we wouldn't have to retest hlt_counter in the common case. hlt/nohlt is something that's specified at boot, or by the board setup code, it's not something that warrants a lot of retesting once we're booted. |
From: Hideo S. <sa...@de...> - 2007-05-11 02:07:47
|
Hi Paul, On Thu, 10 May 2007 14:15:26 +0900, Paul Mundt wrote: > No, consider the use case where a platform implements pm_idle() and > depends on certain conditions to be met to determine what sort of > power-save state can be entered. It's still necessary to have the logic > in default_idle() and to have that exported so it can be called back in > to from pm_idle(), should the latter not have any meaningful work it can > do at the time. I see, but I think following change in default_idle() does not need. + } else + while (!need_resched()) + cpu_relax(); how about following change? + } else cpu_relax(); > In any event, if the previous patches fixes the problems you've been > seeing, I'll queue it up. It works fine with dynamic ticks on SH7785 at > least (both with and without hlt).. Yes, my testing is also done successfully. |
From: Kristoffer E. <kri...@gm...> - 2007-05-10 19:10:45
|
Trivial fix to follow the DECLARE_WORK changes, this makes the HD64461 touchscreen driver work properly again.As pointed out by David Howells <dho...@re...> signed-off-by: Kristoffer Ericson <Kri...@gm...> diff --git a/drivers/input/touchscreen/hp680_ts_input.c b/drivers/input/touchscreen/hp680_ts_input.c index 61c1502..1a15475 100644 --- a/drivers/input/touchscreen/hp680_ts_input.c +++ b/drivers/input/touchscreen/hp680_ts_input.c @@ -1,7 +1,6 @@ #include <linux/input.h> #include <linux/module.h> #include <linux/init.h> - #include <linux/interrupt.h> #include <asm/io.h> #include <asm/delay.h> @@ -18,12 +17,12 @@ #define PHDR 0xa400012e #define SCPDR 0xa4000136 -static void do_softint(void *data); +static void do_softint(struct work_struct *work); static struct input_dev *hp680_ts_dev; -static DECLARE_WORK(work, do_softint); +static DECLARE_DELAYED_WORK(work, do_softint); -static void do_softint(void *data) +static void do_softint(struct work_struct *work) { int absx = 0, absy = 0; u8 scpdr; |
From: Manuel L. <ma...@ro...> - 2007-05-10 15:42:19
|
Hello, here goes attempt #3 for the SH7760 ASoC patch. Changes v2 -> v3 - added a generic demo board for AC97 Changes v1 -> v2 - incorporate Paul Mundt's suggestions - work around an oops when aplay is used in mmap mode ('aplay -M') by changing the allocation type to SNDRV_DMA_TYPE_CONTINUOUS As soon as I get Linux working on the EDOSK7780 board, I'll complete SH7780 support as well. Thanks, Manuel Lauss --- ALSA ASoC support for SH7760 - SH7760 DMA engine (dmabrg) - AC97 driver for HAC unit(s) found on SH7760/SH7780 - I2S driver for SSI unit(s) found on SH7760/SH7780 - generic SH7760-AC97 "demoboard" sound/soc/Kconfig | 1 + sound/soc/Makefile | 3 +- sound/soc/sh/Kconfig | 39 +++++ sound/soc/sh/Makefile | 14 ++ sound/soc/sh/dma-sh7760.c | 354 +++++++++++++++++++++++++++++++++++++++ sound/soc/sh/hac.c | 322 +++++++++++++++++++++++++++++++++++ sound/soc/sh/sh7760-ac97.c | 92 ++++++++++ sound/soc/sh/ssi.c | 400 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1224 insertions(+), 1 deletions(-) Signed-off-by: Manuel Lauss <ma...@ro...> diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index dccaa4b..3e3df26 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -26,6 +26,7 @@ menu "SoC Platforms" depends on SND_SOC source "sound/soc/at91/Kconfig" source "sound/soc/pxa/Kconfig" +source "sound/soc/sh/Kconfig" endmenu # Supported codecs diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 98e6f49..e1c9a70 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,4 +1,5 @@ snd-soc-core-objs := soc-core.o soc-dapm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o -obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ +obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ sh/ + diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig new file mode 100644 index 0000000..a332e51 --- /dev/null +++ b/sound/soc/sh/Kconfig @@ -0,0 +1,39 @@ +menu "SoC Audio support for SuperH" + +config SND_SOC_PCM_SH7760 + tristate "SoC Audio support for Renesas SH7760" + depends on CPU_SUBTYPE_SH7760 && SND_SOC + select SH_DMABRG + help + Enable this option for SH7760 AC97/I2S audio support. + + +## +## Audio unit modules +## + +config SND_SOC_SH4_HAC + select AC97_BUS + select SND_SOC_AC97_BUS + select SND_AC97_CODEC + tristate + +config SND_SOC_SH4_SSI + tristate + + + +## +## Boards +## + +config SND_SH7760_AC97 + tristate "SH7760 AC97 sound support" + depends on CPU_SUBTYPE_SH7760 && SND_SOC_PCM_SH7760 + select SND_SOC_SH4_HAC + select SND_SOC_AC97_CODEC + help + This option enables generic sound support for the first + AC97 unit of the SH7760. + +endmenu diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile new file mode 100644 index 0000000..a8e8ab8 --- /dev/null +++ b/sound/soc/sh/Makefile @@ -0,0 +1,14 @@ +## DMA engines +snd-soc-dma-sh7760-objs := dma-sh7760.o +obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o + +## audio units found on some SH-4 +snd-soc-hac-objs := hac.o +snd-soc-ssi-objs := ssi.o +obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o +obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o + +## boards +snd-soc-sh7760-ac97-objs := sh7760-ac97.o + +obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c new file mode 100644 index 0000000..cdee374 --- /dev/null +++ b/sound/soc/sh/dma-sh7760.c @@ -0,0 +1,354 @@ +/* + * SH7760 ("camelot") DMABRG audio DMA unit support + * + * Copyright (C) 2007 Manuel Lauss <ma...@ro...> + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which + * trigger an interrupt when one half of the programmed transfer size + * has been xmitted. + * + * FIXME: little-endian only for now + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <asm/dmabrg.h> + + +/* registers and bits */ +#define BRGATXSAR 0x00 +#define BRGARXDAR 0x04 +#define BRGATXTCR 0x08 +#define BRGARXTCR 0x0C +#define BRGACR 0x10 +#define BRGATXTCNT 0x14 +#define BRGARXTCNT 0x18 + +#define ACR_RAR (1 << 18) +#define ACR_RDS (1 << 17) +#define ACR_RDE (1 << 16) +#define ACR_TAR (1 << 2) +#define ACR_TDS (1 << 1) +#define ACR_TDE (1 << 0) + +/* receiver/transmitter data alignment */ +#define ACR_RAM_NONE (0 << 24) +#define ACR_RAM_4BYTE (1 << 24) +#define ACR_RAM_2WORD (2 << 24) +#define ACR_TAM_NONE (0 << 8) +#define ACR_TAM_4BYTE (1 << 8) +#define ACR_TAM_2WORD (2 << 8) + + +struct camelot_pcm { + unsigned long mmio; /* DMABRG audio channel control reg MMIO */ + unsigned int txid; /* ID of first DMABRG IRQ for this unit */ + + struct snd_pcm_substream *tx_ss; + unsigned long tx_period_size; + unsigned int tx_period; + + struct snd_pcm_substream *rx_ss; + unsigned long rx_period_size; + unsigned int rx_period; + +} cam_pcm_data[2] = { + { + .mmio = 0xFE3C0040, + .txid = DMABRGIRQ_A0TXF, + }, + { + .mmio = 0xFE3C0060, + .txid = DMABRGIRQ_A1TXF, + }, +}; + +#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x))) + +/* + * set a minimum of 16kb per period, to avoid interrupt-"storm" and + * resulting skipping. In general, the bigger the minimum size, the + * better for overall system performance. (The SH7760 is a puny CPU + * with a slow SDRAM interface and poor internal bus bandwidth, + * *especially* when the LCDC is active). The minimum for the DMAC + * is 8 bytes; 16kbytes are enough to get skip-free playback of a + * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain + * reasonable responsiveness in MPlayer. + */ +#define DMABRG_PERIOD_MIN 16 * 1024 +#define DMABRG_PERIOD_MAX 0x03fffffc +#define DMABRG_PREALLOC_BUFFER 32 * 1024 +#define DMABRG_PREALLOC_BUFFER_MAX 32 * 1024 + +/* support everything the SSI supports */ +#define DMABRG_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define DMABRG_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +static struct snd_pcm_hardware camelot_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = DMABRG_FMTS, + .rates = DMABRG_RATES, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 8, /* max of the SSI */ + .buffer_bytes_max = DMABRG_PERIOD_MAX, + .period_bytes_min = DMABRG_PERIOD_MIN, + .period_bytes_max = DMABRG_PERIOD_MAX / 2, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 128, +}; + +static void camelot_txdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->tx_period ^= 1; + snd_pcm_period_elapsed(cam->tx_ss); +} + +static void camelot_rxdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->rx_period ^= 1; + snd_pcm_period_elapsed(cam->rx_ss); +} + +static int camelot_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret, dmairq; + + snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware); + + /* DMABRG buffer half/full events */ + dmairq = (recv) ? cam->txid + 2 : cam->txid; + if (recv) { + cam->rx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->dai->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam); + } else { + cam->tx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_txdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->dai->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam); + } + return 0; +} + +static int camelot_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int dmairq; + + dmairq = (recv) ? cam->txid + 2 : cam->txid; + + if (recv) + cam->rx_ss = NULL; + else + cam->tx_ss = NULL; + + dmabrg_free_irq(dmairq + 1); + dmabrg_free_irq(dmairq); + + return 0; +} + +static int camelot_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + + if (recv) { + cam->rx_period_size = params_period_bytes(hw_params); + cam->rx_period = 0; + } else { + cam->tx_period_size = params_period_bytes(hw_params); + cam->tx_period = 0; + } + return 0; +} + +static int camelot_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int camelot_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + + pr_debug("PCM data: addr 0x%08ulx len %d\n", + (u32)runtime->dma_addr, runtime->dma_bytes); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area; + BRGREG(BRGATXTCR) = runtime->dma_bytes; + } else { + BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area; + BRGREG(BRGARXTCR) = runtime->dma_bytes; + } + + return 0; +} + +static inline void dmabrg_play_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: XFER start, auto-addr-reload */ + BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD; +} + +static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data transmission */ + BRGREG(BRGACR) = acr | ACR_TDS; +} + +static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: recv start, auto-reload */ + BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD; +} + +static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data receiver */ + BRGREG(BRGACR) = acr | ACR_RDS; +} + +static int camelot_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (recv) + dmabrg_rec_dma_start(cam); + else + dmabrg_play_dma_start(cam); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (recv) + dmabrg_rec_dma_stop(cam); + else + dmabrg_play_dma_stop(cam); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t camelot_pos(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + unsigned long pos; + + /* cannot use the DMABRG pointer register: under load, by the + * time ALSA comes around to read the register, it is already + * far ahead (or worse, already done with the fragment) of the + * position at the time the IRQ was triggered, which results in + * fast-playback sound in my test application (ScummVM) + */ + if (recv) + pos = cam->rx_period ? cam->rx_period_size : 0; + else + pos = cam->tx_period ? cam->tx_period_size : 0; + + return bytes_to_frames(runtime, pos); +} + +static struct snd_pcm_ops camelot_pcm_ops = { + .open = camelot_pcm_open, + .close = camelot_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = camelot_hw_params, + .hw_free = camelot_hw_free, + .prepare = camelot_prepare, + .trigger = camelot_trigger, + .pointer = camelot_pos, +}; + +static void camelot_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int camelot_pcm_new(struct snd_card *card, + struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel + * in MMAP mode (i.e. aplay -M) + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX); + + return 0; +} + +struct snd_soc_platform sh7760_soc_platform = { + .name = "sh7760-pcm", + .pcm_ops = &camelot_pcm_ops, + .pcm_new = camelot_pcm_new, + .pcm_free = camelot_pcm_free, +}; +EXPORT_SYMBOL_GPL(sh7760_soc_platform); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c new file mode 100644 index 0000000..8e3f039 --- /dev/null +++ b/sound/soc/sh/hac.c @@ -0,0 +1,322 @@ +/* + * Hitachi Audio Controller (AC97) support for SH7760/SH7780 + * + * Copyright (c) 2007 Manuel Lauss <ma...@ro...> + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * dont forget to set IPSEL/OMSEL register bits (in your board code) to + * enable HAC output pins! + */ + +/* BIG FAT FIXME: although the SH7760 has 2 independent AC97 units, only + * the FIRST can be used since ASoC does not pass any information to the + * ac97_read/write() functions regarding WHICH unit to use. You'll have + * to edit the code a bit to use the other AC97 unit. --mlau + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +/* regs and bits */ +#define HACCR 0x08 +#define HACCSAR 0x20 +#define HACCSDR 0x24 +#define HACPCML 0x28 +#define HACPCMR 0x2C +#define HACTIER 0x50 +#define HACTSR 0x54 +#define HACRIER 0x58 +#define HACRSR 0x5C +#define HACACR 0x60 + +#define CR_CR (1 << 15) /* "codec-ready" indicator */ +#define CR_CDRT (1 << 11) /* cold reset */ +#define CR_WMRT (1 << 10) /* warm reset */ +#define CR_B9 (1 << 9) /* the mysterious "bit 9" */ +#define CR_ST (1 << 5) /* AC97 link start bit */ + +#define CSAR_RD (1 << 19) /* AC97 data read bit */ +#define CSAR_WR (0) + +#define TSR_CMDAMT (1 << 31) +#define TSR_CMDDMT (1 << 30) + +#define RSR_STARY (1 << 22) +#define RSR_STDRY (1 << 21) + +#define ACR_DMARX16 (1 << 30) +#define ACR_DMATX16 (1 << 29) +#define ACR_TX12ATOM (1 << 26) +#define ACR_DMARX20 ((1 << 24) | (1 << 22)) +#define ACR_DMATX20 ((1 << 23) | (1 << 21)) + +#define CSDR_SHIFT 4 +#define CSDR_MASK (0xffff << CSDR_SHIFT) +#define CSAR_SHIFT 12 +#define CSAR_MASK (0x7f << CSAR_SHIFT) + +#define AC97_WRITE_RETRY 1 +#define AC97_READ_RETRY 5 + +/* manual-suggested AC97 codec access timeouts (us) */ +#define TMO_E1 500 /* 21 < E1 < 1000 */ +#define TMO_E2 13 /* 13 < E2 */ +#define TMO_E3 21 /* 21 < E3 */ +#define TMO_E4 500 /* 21 < E4 < 1000 */ + +struct hac_priv { + unsigned long mmio; /* HAC base address */ +} hac_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE240000, + }, + { + .mmio = 0xFE250000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE40000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +#define HACREG(reg) (*(unsigned long *)(hac->mmio + (reg))) + +/* + * AC97 read/write flow as outlined in the SH7760 manual (pages 903-906) + */ +static int hac_get_codec_data(struct hac_priv *hac, unsigned short r, + unsigned short *v) +{ + unsigned int to1, to2, i; + unsigned short adr; + + for (i = 0; i < AC97_READ_RETRY; ++i) { + *v = 0; + /* wait for HAC to receive something from the codec */ + for (to1 = TMO_E4; + to1 && !(HACREG(HACRSR) & RSR_STARY); + --to1) + udelay(1); + for (to2 = TMO_E4; + to2 && !(HACREG(HACRSR) & RSR_STDRY); + --to2) + udelay(1); + + if (!to1 && !to2) + return 0; /* codec comm is down */ + + adr = ((HACREG(HACCSAR) & CSAR_MASK) >> CSAR_SHIFT); + *v = ((HACREG(HACCSDR) & CSDR_MASK) >> CSDR_SHIFT); + + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + + if (r == adr) + break; + + /* manual says: wait at least 21 usec before retrying */ + udelay(21); + } + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + return (i < AC97_READ_RETRY); +} + +static unsigned short hac_read_codec_aux(struct hac_priv *hac, + unsigned short reg) +{ + unsigned short val; + unsigned int i, to; + + for (i = 0; i < AC97_READ_RETRY; i++) { + /* send_read_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDAMT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) | CSAR_RD; + local_irq_enable(); + + for (to = TMO_E3; + to && !(HACREG(HACTSR) & TSR_CMDAMT); + --to) + udelay(1); + + HACREG(HACTSR) &= ~TSR_CMDAMT; + val = 0; + if (hac_get_codec_data(hac, reg, &val) != 0) + break; + } + + if (i == AC97_READ_RETRY) + return ~0; + + return val; +} + +static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int i, to; + /* write_codec_aux */ + for (i = 0; i < AC97_WRITE_RETRY; i++) { + /* send_write_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT); + HACREG(HACCSDR) = (val << CSDR_SHIFT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) & (~CSAR_RD); + local_irq_enable(); + + /* poll-wait for CMDAMT and CMDDMT */ + for (to = TMO_E1; + to && !(HACREG(HACTSR) & (TSR_CMDAMT|TSR_CMDDMT)); + --to) + udelay(1); + + HACREG(HACTSR) &= ~(TSR_CMDAMT | TSR_CMDDMT); + if (to) + break; + /* timeout, try again */ + } +} + +static unsigned short hac_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + return hac_read_codec_aux(hac, reg); +} + +static void hac_ac97_warmrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int tmo; + + HACREG(HACCR) = CR_WMRT | CR_ST | CR_B9; + msleep(10); + HACREG(HACCR) = CR_ST | CR_B9; + for (tmo = 1000; (tmo > 0) && !(HACREG(HACCR) & CR_CR); tmo--) + udelay(1); + + if (!tmo) + printk(KERN_INFO "hac: reset: AC97 link down!\n"); + /* settings this bit lets us have a conversation with codec */ + HACREG(HACACR) |= ACR_TX12ATOM; +} + +static void hac_ac97_coldrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac; + hac = &hac_cpu_data[unit_id]; + + HACREG(HACCR) = 0; + HACREG(HACCR) = CR_CDRT | CR_ST | CR_B9; + msleep(10); + hac_ac97_warmrst(ac97); +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = hac_ac97_read, + .write = hac_ac97_write, + .reset = hac_ac97_coldrst, + .warm_reset = hac_ac97_warmrst, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int hac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hac_priv *hac = &hac_cpu_data[rtd->dai->cpu_dai->id]; + int d = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + switch (params->msbits) { + case 16: + HACREG(HACACR) |= d ? ACR_DMARX16 : ACR_DMATX16; + HACREG(HACACR) &= d ? ~ACR_DMARX20 : ~ACR_DMATX20; + break; + case 20: + HACREG(HACACR) &= d ? ~ACR_DMARX16 : ~ACR_DMATX16; + HACREG(HACACR) |= d ? ACR_DMARX20 : ACR_DMATX20; + break; + default: + pr_debug("hac: invalid depth %d bit\n", params->msbits); + return -EINVAL; + break; + } + + return 0; +} + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define AC97_FMTS \ + SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_cpu_dai sh4_hac_dai[] = { +{ + .name = "HAC0", + .id = 0, + .type = SND_SOC_DAI_AC97, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = hac_hw_params, + }, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "HAC1", + .id = 1, + .type = SND_SOC_DAI_AC97, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = hac_hw_params, + }, + +}, +#endif +}; +EXPORT_SYMBOL_GPL(sh4_hac_dai); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip HAC (AC97) audio driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/sound/soc/sh/sh7760-ac97.c b/sound/soc/sh/sh7760-ac97.c new file mode 100644 index 0000000..5563f14 --- /dev/null +++ b/sound/soc/sh/sh7760-ac97.c @@ -0,0 +1,92 @@ +/* + * Generic AC97 sound support for SH7760 + * + * (c) 2007 Manuel Lauss + * + * Licensed under the GPLv2. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/io.h> + +#include "../codecs/ac97.h" + +#define IPSEL 0xFE400034 + +/* platform specific structs can be declared here */ +extern struct snd_soc_cpu_dai sh4_hac_dai[2]; +extern struct snd_soc_platform sh7760_soc_platform; + +static int machine_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +static struct snd_soc_dai_link sh7760_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &sh4_hac_dai[0], /* HAC0 */ + .codec_dai = &ac97_dai, + .init = machine_init, + .ops = NULL, +}; + +static struct snd_soc_machine sh7760_ac97_soc_machine = { + .name = "SH7760 AC97", + .dai_link = &sh7760_ac97_dai, + .num_links = 1, +}; + +static struct snd_soc_device sh7760_ac97_snd_devdata = { + .machine = &sh7760_ac97_soc_machine, + .platform = &sh7760_soc_platform, + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct platform_device *sh7760_ac97_snd_device; + +static int __init sh7760_ac97_init(void) +{ + int ret; + unsigned short ipsel; + + /* enable both AC97 controllers in pinmux reg */ + ipsel = ctrl_inw(IPSEL); + ctrl_outw(ipsel | (3 << 10), IPSEL); + + ret = -ENOMEM; + sh7760_ac97_snd_device = platform_device_alloc("soc-audio", -1); + if (!sh7760_ac97_snd_device) + goto out; + + platform_set_drvdata(sh7760_ac97_snd_device, + &sh7760_ac97_snd_devdata); + sh7760_ac97_snd_devdata.dev = &sh7760_ac97_snd_device->dev; + ret = platform_device_add(sh7760_ac97_snd_device); + + if (ret) + platform_device_put(sh7760_ac97_snd_device); + +out: + return ret; +} + +static void __exit sh7760_ac97_exit(void) +{ + platform_device_unregister(sh7760_ac97_snd_device); +} + +module_init(sh7760_ac97_init); +module_exit(sh7760_ac97_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Generic SH7760 AC97 sound machine"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c new file mode 100644 index 0000000..b72bc31 --- /dev/null +++ b/sound/soc/sh/ssi.c @@ -0,0 +1,400 @@ +/* + * Serial Sound Interface (I2S) support for SH7760/SH7780 + * + * Copyright (c) 2007 Manuel Lauss <ma...@ro...> + * + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * dont forget to set IPSEL/OMSEL register bits (in your board code) to + * enable SSI output pins! + */ + +/* + * LIMITATIONS: + * The SSI unit has only one physical data line, so full duplex is + * impossible. This can be remedied on the SH7760 by using the + * other SSI unit for recording; however the SH7780 has only 1 SSI + * unit, and its pins are shared with the AC97 unit, among others. + * + * FEATURES: + * The SSI features "compressed mode": in this mode it continuously + * streams PCM data over the I2S lines and uses LRCK as a handshake + * signal. Can be used to send compressed data (AC3/DTS) to a DSP. + * The number of bits sent over the wire in a frame can be adjusted + * and can be independent from the actual sample bit depth. This is + * useful to support TDM mode codecs like the AD1939 which have a + * fixed TDM slot size, regardless of sample resolution. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/io.h> + +#define SSICR 0x00 +#define SSISR 0x04 + +#define CR_DMAEN (1 << 28) +#define CR_CHNL_SHIFT 22 +#define CR_CHNL_MASK (3 << CR_CHNL_SHIFT) +#define CR_DWL_SHIFT 19 +#define CR_DWL_MASK (7 << CR_DWL_SHIFT) +#define CR_SWL_SHIFT 16 +#define CR_SWL_MASK (7 << CR_SWL_SHIFT) +#define CR_SCK_MASTER (1 << 15) /* bitclock master bit */ +#define CR_SWS_MASTER (1 << 14) /* wordselect master bit */ +#define CR_SCKP (1 << 13) /* I2Sclock polarity */ +#define CR_SWSP (1 << 12) /* LRCK polarity */ +#define CR_SPDP (1 << 11) +#define CR_SDTA (1 << 10) /* i2s alignment (msb/lsb) */ +#define CR_PDTA (1 << 9) /* fifo data alignment */ +#define CR_DEL (1 << 8) /* delay data by 1 i2sclk */ +#define CR_BREN (1 << 7) /* clock gating in burst mode */ +#define CR_CKDIV_SHIFT 4 +#define CR_CKDIV_MASK (7 << CR_CKDIV_SHIFT) /* bitclock divider */ +#define CR_MUTE (1 << 3) /* SSI mute */ +#define CR_CPEN (1 << 2) /* compressed mode */ +#define CR_TRMD (1 << 1) /* transmit/receive select */ +#define CR_EN (1 << 0) /* enable SSI */ + +#define SSIREG(reg) (*(unsigned long *)(ssi->mmio + (reg))) + +struct ssi_priv { + unsigned long mmio; + unsigned long sysclk; + int inuse; +} ssi_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE680000, + }, + { + .mmio = 0xFE690000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE70000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +/* + * track usage of the SSI; it is simplex-only so prevent attempts of + * concurrent playback + capture. FIXME: any locking required? + */ +static int ssi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + if (ssi->inuse) { + pr_debug("ssi: already in use!\n"); + return -EBUSY; + } else + ssi->inuse = 1; + return 0; +} + +static void ssi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + + ssi->inuse = 0; +} + +static int ssi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + SSIREG(SSICR) |= CR_DMAEN | CR_EN; + break; + case SNDRV_PCM_TRIGGER_STOP: + SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + unsigned long ssicr = SSIREG(SSICR); + unsigned int bits, channels, swl, recv, i; + + channels = params_channels(params); + bits = params->msbits; + recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; + + pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr); + pr_debug("bits: %d channels: %d\n", bits, channels); + + ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA | + CR_SWL_MASK); + + /* direction (send/receive) */ + if (!recv) + ssicr |= CR_TRMD; /* transmit */ + + /* channels */ + if ((channels < 2) || (channels > 8) || (channels & 1)) { + pr_debug("ssi: invalid number of channels\n"); + return -EINVAL; + } + ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT; + + /* DATA WORD LENGTH (DWL): databits in audio sample */ + i = 0; + switch (bits) { + case 32: ++i; + case 24: ++i; + case 22: ++i; + case 20: ++i; + case 18: ++i; + case 16: ++i; + ssicr |= i << CR_DWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid sample width\n"); + return -EINVAL; + } + + /* + * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S + * wires. This is usually bits_per_sample x channels/2; i.e. in + * Stereo mode the SWL equals DWL. SWL can be bigger than the + * product of (channels_per_slot x samplebits), e.g. for codecs + * like the AD1939 which only accept 32bit wide TDM slots. For + * "standard" I2S operation we set SWL = chans / 2 * DWL here. + * Waiting for ASoC to get TDM support ;-) + */ + if ((bits > 16) && (bits <= 24)) { + bits = 24; /* these are padded by the SSI */ + /*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */ + } + i = 0; + swl = (bits * channels) / 2; + switch (swl) { + case 256: ++i; + case 128: ++i; + case 64: ++i; + case 48: ++i; + case 32: ++i; + case 16: ++i; + ssicr |= i << CR_SWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid system word length computed\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + + pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr); + return 0; +} + +static int ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id]; + + ssi->sysclk = freq; + + return 0; +} + +/* + * This divider is used to generate the SSI_SCK (I2S bitclock) from the + * clock at the HAC_BIT_CLK ("oversampling clock") pin. + */ +static int ssi_set_clkdiv(struct snd_soc_cpu_dai *dai, int did, int div) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr; + int i; + + i = 0; + ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK; + switch (div) { + case 16: ++i; + case 8: ++i; + case 4: ++i; + case 2: ++i; + SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT); + case 1: break; + default: + pr_debug("ssi: invalid sck divider %d\n", div); + return -EINVAL; + } + + return 0; +} + +static int ssi_set_fmt(struct snd_soc_cpu_dai *dai, unsigned int fmt) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr = SSIREG(SSICR); + + pr_debug("ssi_set_fmt()\nssicr was 0x%08lx\n", ssicr); + + ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP | + CR_SWS_MASTER | CR_SCK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + ssicr |= CR_DEL | CR_PDTA; + break; + case SND_SOC_DAIFMT_LEFT_J: + ssicr |= CR_DEL; + break; + default: + pr_debug("ssi: unsupported format\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + break; + case SND_SOC_DAIFMT_GATED: + ssicr |= CR_BREN; + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + ssicr |= CR_SCKP; /* sample data at low clkedge */ + break; + case SND_SOC_DAIFMT_NB_IF: + ssicr |= CR_SCKP | CR_SWSP; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + ssicr |= CR_SWSP; /* word select starts low */ + break; + default: + pr_debug("ssi: invalid inversion\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + ssicr |= CR_SCK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: + ssicr |= CR_SWS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ssicr |= CR_SWS_MASTER | CR_SCK_MASTER; + break; + default: + pr_debug("ssi: invalid master/slave configuration\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr); + + return 0; +} + +/* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in + * Master mode, so really this is board specific; the SSI can do any + * rate with the right bitclk and divider settings. + */ +#define SSI_RATES \ + SNDRV_PCM_RATE_8000_192000 + +/* the SSI can do 8-32 bit samples, with 8 possible channels */ +#define SSI_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +struct snd_soc_cpu_dai sh4_ssi_dai[] = { +{ + .name = "SSI0", + .id = 0, + .type = SND_SOC_DAI_I2S, + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + }, + .dai_ops = { + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, + }, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "SSI1", + .id = 1, + .type = SND_SOC_DAI_I2S, + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + }, + .dai_ops = { + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, + }, +}, +#endif +}; +EXPORT_SYMBOL_GPL(sh4_ssi_dai); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); |
From: Manuel L. <ma...@ro...> - 2007-05-10 13:07:43
|
On Thu, May 10, 2007 at 09:58:17PM +0900, Paul Mundt wrote: > On Thu, May 10, 2007 at 02:08:33PM +0200, Manuel Lauss wrote: > > +## > > +## Audio unit modules > > +## > > + > > +config SND_SOC_SH4_HAC > > + select AC97_BUS > > + select SND_SOC_AC97_BUS > > + select SND_AC97_CODEC > > + tristate > > + > > +config SND_SOC_SH4_SSI > > + tristate > > + > This might seem like a silly question, but how are these two supposed to > be enabled? At the very least you should have some dependencies on the > CPU subtypes that support these blocks. > > > + > > + > > +## > > +## Boards > > +## > > + > Or are you expecting that the board-specific code will simply select > these? That was the idea; the other ASoC boards do similar things. > Are you planning on submitting a stub that enables these? Or is this just > to be a placeholder until some board wants them? I suppose I can submit my AC97 board code, it does not depend on anything that is not already submitted. I'll add the ac97 board code to the patch and re-submit it. Thanks, Manuel Lauss |
From: Paul M. <le...@li...> - 2007-05-10 12:58:50
|
On Thu, May 10, 2007 at 02:08:33PM +0200, Manuel Lauss wrote: > +## > +## Audio unit modules > +## > + > +config SND_SOC_SH4_HAC > + select AC97_BUS > + select SND_SOC_AC97_BUS > + select SND_AC97_CODEC > + tristate > + > +config SND_SOC_SH4_SSI > + tristate > + This might seem like a silly question, but how are these two supposed to be enabled? At the very least you should have some dependencies on the CPU subtypes that support these blocks. > + > + > +## > +## Boards > +## > + Or are you expecting that the board-specific code will simply select these? Are you planning on submitting a stub that enables these? Or is this just to be a placeholder until some board wants them? |
From: Manuel L. <ma...@ro...> - 2007-05-10 12:08:41
|
Hello, here goes round 2 of the SH7760 ASoC patch. Changes since last submission: - incorporate Paul Mundt's suggestions - work around an oops when aplay is used in mmap mode ('aplay -M') by changing the allocation type to SNDRV_DMA_TYPE_CONTINUOUS Thanks, Manuel Lauss --- ALSA ASoC support for SH7760 - AC97 driver for HAC unit(s) found on SH7760/SH7780 - I2S driver for SSI unit(s) found on SH7760/SH7780 - SH7760 DMA engine (dmabrg) sound/soc/Kconfig | 1 + sound/soc/Makefile | 3 +- sound/soc/sh/Kconfig | 30 ++++ sound/soc/sh/Makefile | 11 ++ sound/soc/sh/dma-sh7760.c | 350 +++++++++++++++++++++++++++++++++++++++ sound/soc/sh/hac.c | 323 ++++++++++++++++++++++++++++++++++++ sound/soc/sh/ssi.c | 400 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1117 insertions(+), 1 deletions(-) Signed-off-by: Manuel Lauss <ma...@ro...> diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index dccaa4b..3e3df26 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -26,6 +26,7 @@ menu "SoC Platforms" depends on SND_SOC source "sound/soc/at91/Kconfig" source "sound/soc/pxa/Kconfig" +source "sound/soc/sh/Kconfig" endmenu # Supported codecs diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 98e6f49..e1c9a70 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,4 +1,5 @@ snd-soc-core-objs := soc-core.o soc-dapm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o -obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ +obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ sh/ + diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig new file mode 100644 index 0000000..6c90c2c --- /dev/null +++ b/sound/soc/sh/Kconfig @@ -0,0 +1,30 @@ +menu "SoC Audio support for SuperH" + +config SND_SOC_PCM_SH7760 + tristate "SoC Audio support for Renesas SH7760" + depends on CPU_SUBTYPE_SH7760 && SND_SOC + select SH_DMABRG + help + Enable this option for SH7760 AC97/I2S audio support. + + +## +## Audio unit modules +## + +config SND_SOC_SH4_HAC + select AC97_BUS + select SND_SOC_AC97_BUS + select SND_AC97_CODEC + tristate + +config SND_SOC_SH4_SSI + tristate + + + +## +## Boards +## + +endmenu diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile new file mode 100644 index 0000000..32bf713 --- /dev/null +++ b/sound/soc/sh/Makefile @@ -0,0 +1,11 @@ +## DMA engines +snd-soc-dma-sh7760-objs := dma-sh7760.o +obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o + +## audio units found on some SH-4 +snd-soc-hac-objs := hac.o +snd-soc-ssi-objs := ssi.o +obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o +obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o + +## boards diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c new file mode 100644 index 0000000..d495cf5 --- /dev/null +++ b/sound/soc/sh/dma-sh7760.c @@ -0,0 +1,350 @@ +/* + * SH7760 ("camelot") DMABRG audio DMA unit support + * + * Copyright (C) 2007 Manuel Lauss <ma...@ro...> + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which + * trigger an interrupt when one half of the programmed transfer size + * has been xmitted. + * + * FIXME: little-endian only for now + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <asm/dmabrg.h> + + +/* registers and bits */ +#define BRGATXSAR 0x00 +#define BRGARXDAR 0x04 +#define BRGATXTCR 0x08 +#define BRGARXTCR 0x0C +#define BRGACR 0x10 +#define BRGATXTCNT 0x14 +#define BRGARXTCNT 0x18 + +#define ACR_RAR (1 << 18) +#define ACR_RDS (1 << 17) +#define ACR_RDE (1 << 16) +#define ACR_TAR (1 << 2) +#define ACR_TDS (1 << 1) +#define ACR_TDE (1 << 0) + +/* receiver/transmitter data alignment */ +#define ACR_RAM_NONE (0 << 24) +#define ACR_RAM_4BYTE (1 << 24) +#define ACR_RAM_2WORD (2 << 24) +#define ACR_TAM_NONE (0 << 8) +#define ACR_TAM_4BYTE (1 << 8) +#define ACR_TAM_2WORD (2 << 8) + + +struct camelot_pcm { + unsigned long mmio; /* DMABRG audio channel control reg MMIO */ + unsigned int txid; /* ID of first DMABRG IRQ for this unit */ + + struct snd_pcm_substream *tx_ss; + unsigned long tx_period_size; + unsigned int tx_period; + + struct snd_pcm_substream *rx_ss; + unsigned long rx_period_size; + unsigned int rx_period; + +} cam_pcm_data[2] = { + { + .mmio = 0xFE3C0040, + .txid = DMABRGIRQ_A0TXF, + }, + { + .mmio = 0xFE3C0060, + .txid = DMABRGIRQ_A1TXF, + }, +}; + +#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x))) + +/* + * set a minimum of 16kb per period, to avoid interrupt-"storm" and + * resulting skipping. In general, the bigger the minimum size, the + * better for overall system performance. (The SH7760 is a puny CPU + * with a slow SDRAM interface and poor internal bus bandwidth, + * *especially* when the LCDC is active). The minimum for the DMAC + * is 8 bytes; 16kbytes are enough to get skip-free playback of + * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain + * reasonable responsiveness in e.g. MPlayer. + */ +#define DMABRG_PERIOD_MIN 16 * 1024 +#define DMABRG_PERIOD_MAX 0x03fffffc +#define DMABRG_PREALLOC_BUFFER 32 * 1024 +#define DMABRG_PREALLOC_BUFFER_MAX 32 * 1024 + +/* support everything the SSI supports */ +#define DMABRG_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define DMABRG_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +static struct snd_pcm_hardware camelot_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = DMABRG_FMTS, + .rates = DMABRG_RATES, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 8, /* max of the SSI */ + .buffer_bytes_max = DMABRG_PERIOD_MAX, + .period_bytes_min = DMABRG_PERIOD_MIN, + .period_bytes_max = DMABRG_PERIOD_MAX / 2, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 128, +}; + +static void camelot_txdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->tx_period ^= 1; + snd_pcm_period_elapsed(cam->tx_ss); +} + +static void camelot_rxdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->rx_period ^= 1; + snd_pcm_period_elapsed(cam->rx_ss); +} + +static int camelot_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret, dmairq; + + snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware); + + /* DMABRG buffer half/full events */ + dmairq = (recv) ? cam->txid + 2 : cam->txid; + if (recv) { + cam->rx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->dai->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam); + } else { + cam->tx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_txdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->dai->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam); + } + return 0; +} + +static int camelot_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int dmairq; + + dmairq = (recv) ? cam->txid + 2 : cam->txid; + + if (recv) + cam->rx_ss = NULL; + else + cam->tx_ss = NULL; + + dmabrg_free_irq(dmairq + 1); + dmabrg_free_irq(dmairq); + + return 0; +} + +static int camelot_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + + if (recv) { + cam->rx_period_size = params_period_bytes(hw_params); + cam->rx_period = 0; + } else { + cam->tx_period_size = params_period_bytes(hw_params); + cam->tx_period = 0; + } + return 0; +} + +static int camelot_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int camelot_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + + pr_debug("PCM data: addr 0x%08lx len %d\n", + (u32)runtime->dma_addr, runtime->dma_bytes); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + BRGREG(BRGATXSAR) = runtime->dma_area; + BRGREG(BRGATXTCR) = runtime->dma_bytes; + } else { + BRGREG(BRGARXDAR) = runtime->dma_area; + BRGREG(BRGARXTCR) = runtime->dma_bytes; + } + + return 0; +} + +static inline void dmabrg_play_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: XFER start, auto-addr-reload */ + BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD; +} + +static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data transmission */ + BRGREG(BRGACR) = acr | ACR_TDS; +} + +static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: recv start, auto-reload */ + BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD; +} + +static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data receiver */ + BRGREG(BRGACR) = acr | ACR_RDS; +} + +static int camelot_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (recv) + dmabrg_rec_dma_start(cam); + else + dmabrg_play_dma_start(cam); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (recv) + dmabrg_rec_dma_stop(cam); + else + dmabrg_play_dma_stop(cam); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t camelot_pos(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->dai->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + unsigned long pos; + + /* cannot use the DMABRG pointer register: under load, by the + * time ALSA comes around to read the register, it is already + * far ahead (or worse, already done with the fragment) of the + * position at the time the IRQ was triggered, which results in + * fast-playback sound in my test application (ScummVM) + */ + if (recv) + pos = cam->rx_period ? cam->rx_period_size : 0; + else + pos = cam->tx_period ? cam->tx_period_size : 0; + + return bytes_to_frames(runtime, pos); +} + +static struct snd_pcm_ops camelot_pcm_ops = { + .open = camelot_pcm_open, + .close = camelot_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = camelot_hw_params, + .hw_free = camelot_hw_free, + .prepare = camelot_prepare, + .trigger = camelot_trigger, + .pointer = camelot_pos, +}; + +static void camelot_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int camelot_pcm_new(struct snd_card *card, + struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, GFP_KERNEL, + DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX); + + return 0; +} + +struct snd_soc_platform sh7760_soc_platform = { + .name = "sh7760-pcm", + .pcm_ops = &camelot_pcm_ops, + .pcm_new = camelot_pcm_new, + .pcm_free = camelot_pcm_free, +}; +EXPORT_SYMBOL_GPL(sh7760_soc_platform); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c new file mode 100644 index 0000000..696ecb8 --- /dev/null +++ b/sound/soc/sh/hac.c @@ -0,0 +1,323 @@ +/* + * Hitachi Audio Controller (AC97) support for SH7760/SH7780 + * + * Copyright (c) 2007 Manuel Lauss <ma...@ro...> + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * dont forget to set IPSEL/OMSEL register bits (in your board code) to + * enable HAC output pins! + */ + +/* + * BIG FAT FIXME: although the SH7760 has 2 independent AC97 units, only + * the FIRST can be used since ASoC does not pass any information to the + * ac97_read/write() functions regarding WHICH unit to use. You'll have + * to edit the code a bit to use the other AC97 unit. --mlau + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +/* regs and bits */ +#define HACCR 0x08 +#define HACCSAR 0x20 +#define HACCSDR 0x24 +#define HACPCML 0x28 +#define HACPCMR 0x2C +#define HACTIER 0x50 +#define HACTSR 0x54 +#define HACRIER 0x58 +#define HACRSR 0x5C +#define HACACR 0x60 + +#define CR_CR (1 << 15) /* "codec-ready" indicator */ +#define CR_CDRT (1 << 11) /* cold reset */ +#define CR_WMRT (1 << 10) /* warm reset */ +#define CR_B9 (1 << 9) /* the mysterious "bit 9" */ +#define CR_ST (1 << 5) /* AC97 link start bit */ + +#define CSAR_RD (1 << 19) /* AC97 data read bit */ +#define CSAR_WR (0) + +#define TSR_CMDAMT (1 << 31) +#define TSR_CMDDMT (1 << 30) + +#define RSR_STARY (1 << 22) +#define RSR_STDRY (1 << 21) + +#define ACR_DMARX16 (1 << 30) +#define ACR_DMATX16 (1 << 29) +#define ACR_TX12ATOM (1 << 26) +#define ACR_DMARX20 ((1 << 24) | (1 << 22)) +#define ACR_DMATX20 ((1 << 23) | (1 << 21)) + +#define CSDR_SHIFT 4 +#define CSDR_MASK (0xffff << CSDR_SHIFT) +#define CSAR_SHIFT 12 +#define CSAR_MASK (0x7f << CSAR_SHIFT) + +#define AC97_WRITE_RETRY 1 +#define AC97_READ_RETRY 5 + +/* manual-suggested AC97 codec access timeouts (us) */ +#define TMO_E1 500 /* 21 < E1 < 1000 */ +#define TMO_E2 13 /* 13 < E2 */ +#define TMO_E3 21 /* 21 < E3 */ +#define TMO_E4 500 /* 21 < E4 < 1000 */ + +struct hac_priv { + unsigned long mmio; /* HAC base address */ +} hac_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE240000, + }, + { + .mmio = 0xFE250000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE40000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +#define HACREG(reg) (*(unsigned long *)(hac->mmio + (reg))) + +/* + * AC97 read/write flow as outlined in the SH7760 manual (pages 903-906) + */ +static int hac_get_codec_data(struct hac_priv *hac, unsigned short r, + unsigned short *v) +{ + unsigned int to1, to2, i; + unsigned short adr; + + for (i = 0; i < AC97_READ_RETRY; ++i) { + *v = 0; + /* wait for HAC to receive something from the codec */ + for (to1 = TMO_E4; + to1 && !(HACREG(HACRSR) & RSR_STARY); + --to1) + udelay(1); + for (to2 = TMO_E4; + to2 && !(HACREG(HACRSR) & RSR_STDRY); + --to2) + udelay(1); + + if (!to1 && !to2) + return 0; /* codec comm is down */ + + adr = ((HACREG(HACCSAR) & CSAR_MASK) >> CSAR_SHIFT); + *v = ((HACREG(HACCSDR) & CSDR_MASK) >> CSDR_SHIFT); + + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + + if (r == adr) + break; + + /* manual says: wait at least 21 usec before retrying */ + udelay(21); + } + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + return (i < AC97_READ_RETRY); +} + +static unsigned short hac_read_codec_aux(struct hac_priv *hac, + unsigned short reg) +{ + unsigned short val; + unsigned int i, to; + + for (i = 0; i < AC97_READ_RETRY; i++) { + /* send_read_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDAMT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) | CSAR_RD; + local_irq_enable(); + + for (to = TMO_E3; + to && !(HACREG(HACTSR) & TSR_CMDAMT); + --to) + udelay(1); + + HACREG(HACTSR) &= ~TSR_CMDAMT; + val = 0; + if (hac_get_codec_data(hac, reg, &val) != 0) + break; + } + + if (i == AC97_READ_RETRY) + return ~0; + + return val; +} + +static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int i, to; + /* write_codec_aux */ + for (i = 0; i < AC97_WRITE_RETRY; i++) { + /* send_write_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT); + HACREG(HACCSDR) = (val << CSDR_SHIFT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) & (~CSAR_RD); + local_irq_enable(); + + /* poll-wait for CMDAMT and CMDDMT */ + for (to = TMO_E1; + to && !(HACREG(HACTSR) & (TSR_CMDAMT|TSR_CMDDMT)); + --to) + udelay(1); + + HACREG(HACTSR) &= ~(TSR_CMDAMT | TSR_CMDDMT); + if (to) + break; + /* timeout, try again */ + } +} + +static unsigned short hac_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + return hac_read_codec_aux(hac, reg); +} + +static void hac_ac97_warmrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int tmo; + + HACREG(HACCR) = CR_WMRT | CR_ST | CR_B9; + msleep(10); + HACREG(HACCR) = CR_ST | CR_B9; + for (tmo = 1000; (tmo > 0) && !(HACREG(HACCR) & CR_CR); tmo--) + udelay(1); + + if (!tmo) + printk(KERN_INFO "hac: reset: AC97 link down!\n"); + /* settings this bit lets us have a conversation with codec */ + HACREG(HACACR) |= ACR_TX12ATOM; +} + +static void hac_ac97_coldrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac; + hac = &hac_cpu_data[unit_id]; + + HACREG(HACCR) = 0; + HACREG(HACCR) = CR_CDRT | CR_ST | CR_B9; + msleep(10); + hac_ac97_warmrst(ac97); +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = hac_ac97_read, + .write = hac_ac97_write, + .reset = hac_ac97_coldrst, + .warm_reset = hac_ac97_warmrst, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int hac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hac_priv *hac = &hac_cpu_data[rtd->dai->cpu_dai->id]; + int d = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + switch (params->msbits) { + case 16: + HACREG(HACACR) |= d ? ACR_DMARX16 : ACR_DMATX16; + HACREG(HACACR) &= d ? ~ACR_DMARX20 : ~ACR_DMATX20; + break; + case 20: + HACREG(HACACR) &= d ? ~ACR_DMARX16 : ~ACR_DMATX16; + HACREG(HACACR) |= d ? ACR_DMARX20 : ACR_DMATX20; + break; + default: + pr_debug("hac: invalid depth %d bit\n", params->msbits); + return -EINVAL; + break; + } + + return 0; +} + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define AC97_FMTS \ + SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_cpu_dai sh4_hac_dai[] = { +{ + .name = "HAC0", + .id = 0, + .type = SND_SOC_DAI_AC97, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = hac_hw_params, + }, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "HAC1", + .id = 1, + .type = SND_SOC_DAI_AC97, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .hw_params = hac_hw_params, + }, + +}, +#endif +}; +EXPORT_SYMBOL_GPL(sh4_hac_dai); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip HAC (AC97) audio driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c new file mode 100644 index 0000000..d69bf63 --- /dev/null +++ b/sound/soc/sh/ssi.c @@ -0,0 +1,400 @@ +/* + * Serial Sound Interface (I2S) support for SH7760/SH7780 + * + * Copyright (c) 2007 Manuel Lauss <ma...@ro...> + * + * licensed under the terms outlined in the file COPYING at the root + * of the linux kernel sources. + * + * dont forget to set IPSEL/OMSEL register bits (in your board code) to + * enable SSI output pins! + */ + +/* + * LIMITATIONS: + * The SSI unit has only one physical data line, so full duplex is + * impossible. This can be remedied on the SH7760 by using the + * other SSI unit for recording; however the SH7780 has only 1 SSI + * unit, and its pins are shared with the AC97 unit, among others. + * + * FEATURES: + * The SSI features "compressed mode": in this mode it continuously + * streams PCM data over the I2S lines and uses LRCK as a handshake + * signal. Can be used to send compressed data (AC3/DTS) to a DSP. + * The number of bits sent over the wire in a frame can be adjusted + * and can be independent from the actual sample bit depth. This is + * useful to support TDM mode codecs like the AD1939 which have a + * fixed TDM slot size, regardless of sample resolution. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/io.h> + +#define SSICR 0x00 +#define SSISR 0x04 + +#define CR_DMAEN (1 << 28) +#define CR_CHNL_SHIFT 22 +#define CR_CHNL_MASK (3 << CR_CHNL_SHIFT) +#define CR_DWL_SHIFT 19 +#define CR_DWL_MASK (7 << CR_DWL_SHIFT) +#define CR_SWL_SHIFT 16 +#define CR_SWL_MASK (7 << CR_SWL_SHIFT) +#define CR_SCK_MASTER (1 << 15) /* bitclock master bit */ +#define CR_SWS_MASTER (1 << 14) /* wordselect master bit */ +#define CR_SCKP (1 << 13) /* I2Sclock polarity */ +#define CR_SWSP (1 << 12) /* LRCK polarity */ +#define CR_SPDP (1 << 11) +#define CR_SDTA (1 << 10) /* i2s alignment (msb/lsb) */ +#define CR_PDTA (1 << 9) /* fifo data alignment */ +#define CR_DEL (1 << 8) /* delay data by 1 i2sclk */ +#define CR_BREN (1 << 7) /* clock gating in burst mode */ +#define CR_CKDIV_SHIFT 4 +#define CR_CKDIV_MASK (7 << CR_CKDIV_SHIFT) /* bitclock divider */ +#define CR_MUTE (1 << 3) /* SSI mute */ +#define CR_CPEN (1 << 2) /* compressed mode */ +#define CR_TRMD (1 << 1) /* transmit/receive select */ +#define CR_EN (1 << 0) /* enable SSI */ + +#define SSIREG(reg) (*(unsigned long *)(ssi->mmio + (reg))) + +struct ssi_priv { + unsigned long mmio; + unsigned long sysclk; + int inuse; +} ssi_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE680000, + }, + { + .mmio = 0xFE690000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE70000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +/* + * track usage of the SSI; it is simplex-only so prevent attempts of + * concurrent playback + capture. FIXME: any locking required? + */ +static int ssi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + if (ssi->inuse) { + pr_debug("ssi: already in use!\n"); + return -EBUSY; + } else + ssi->inuse = 1; + return 0; +} + +static void ssi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + + ssi->inuse = 0; +} + +static int ssi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + SSIREG(SSICR) |= CR_DMAEN | CR_EN; + break; + case SNDRV_PCM_TRIGGER_STOP: + SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; + unsigned long ssicr = SSIREG(SSICR); + unsigned int bits, channels, swl, recv, i; + + channels = params_channels(params); + bits = params->msbits; + recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; + + pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr); + pr_debug("bits: %d channels: %d\n", bits, channels); + + ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA | + CR_SWL_MASK); + + /* direction (send/receive) */ + if (!recv) + ssicr |= CR_TRMD; /* transmit */ + + /* channels */ + if ((channels < 2) || (channels > 8) || (channels & 1)) { + pr_debug("ssi: invalid number of channels\n"); + return -EINVAL; + } + ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT; + + /* DATA WORD LENGTH (DWL): databits in audio sample */ + i = 0; + switch (bits) { + case 32: ++i; + case 24: ++i; + case 22: ++i; + case 20: ++i; + case 18: ++i; + case 16: ++i; + ssicr |= i << CR_DWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid sample width\n"); + return -EINVAL; + } + + /* + * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S + * wires. This is usually bits_per_sample x channels/2; i.e. in + * Stereo mode the SWL equals DWL. SWL can be bigger than the + * product of (channels_per_slot x samplebits), e.g. for codecs + * like the AD1939 which only accept 32bit wide TDM slots. For + * "standard" I2S operation we set SWL = chans / 2 * DWL here. + * Waiting for ASoC to get TDM support ;-) + */ + if ((bits > 16) && (bits <= 24)) { + bits = 24; /* these are padded by the SSI */ + /*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */ + } + i = 0; + swl = (bits * channels) / 2; + switch (swl) { + case 256: ++i; + case 128: ++i; + case 64: ++i; + case 48: ++i; + case 32: ++i; + case 16: ++i; + ssicr |= i << CR_SWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid system word length computed\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + + pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr); + return 0; +} + +static int ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id]; + + ssi->sysclk = freq; + + return 0; +} + +/* + * This divider is used to generate the SSI_SCK (I2S bitclock) from the + * clock at the HAC_BIT_CLK ("oversampling clock") pin. + */ +static int ssi_set_clkdiv(struct snd_soc_cpu_dai *dai, int did, int div) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr; + int i; + + i = 0; + ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK; + switch (div) { + case 16: ++i; + case 8: ++i; + case 4: ++i; + case 2: ++i; + SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT); + case 1: break; + default: + pr_debug("ssi: invalid sck divider %d\n", div); + return -EINVAL; + } + + return 0; +} + +static int ssi_set_fmt(struct snd_soc_cpu_dai *dai, unsigned int fmt) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr = SSIREG(SSICR); + + pr_debug("ssi_set_fmt()\nssicr was 0x%08lx\n", ssicr); + + ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP | + CR_SWS_MASTER | CR_SCK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + ssicr |= CR_DEL | CR_PDTA; + break; + case SND_SOC_DAIFMT_LEFT_J: + ssicr |= CR_DEL; + break; + default: + pr_debug("ssi: unsupported format\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + break; + case SND_SOC_DAIFMT_GATED: + ssicr |= CR_BREN; + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + ssicr |= CR_SCKP; /* sample data at low clkedge */ + break; + case SND_SOC_DAIFMT_NB_IF: + ssicr |= CR_SCKP | CR_SWSP; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + ssicr |= CR_SWSP; /* word select starts low */ + break; + default: + pr_debug("ssi: invalid inversion\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + ssicr |= CR_SCK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: + ssicr |= CR_SWS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ssicr |= CR_SWS_MASTER | CR_SCK_MASTER; + break; + default: + pr_debug("ssi: invalid master/slave configuration\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr); + + return 0; +} + +/* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in + * Master mode, so really this is board specific; the SSI can do any + * rate with the right bitclk and divider settings. + */ +#define SSI_RATES \ + SNDRV_PCM_RATE_8000_192000 + +/* the SSI can do 8-32 bit samples, with 8 possible channels */ +#define SSI_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +struct snd_soc_cpu_dai sh4_ssi_dai[] = { +{ + .name = "SSI0", + .id = 0, + .type = SND_SOC_DAI_I2S, + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + }, + .dai_ops = { + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, + }, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "SSI1", + .id = 1, + .type = SND_SOC_DAI_I2S, + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + }, + .dai_ops = { + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, + }, +}, +#endif +}; +EXPORT_SYMBOL_GPL(sh4_ssi_dai); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver"); +MODULE_AUTHOR("Manuel Lauss <ma...@ro...>"); |
From: Paul M. <le...@li...> - 2007-05-10 09:31:51
|
On Wed, May 09, 2007 at 07:33:58AM -0700, Cmmn Ml wrote: > As a summary what happens is the kernel boots up fine up to the point > where run_init_process("/sbin/init") is called. After the > run_init_process("/sbin/init") function is called, kernel hangs itself > and even the timer interrupt is not handled. As I feel the kernel fails > to start a user space process and puts it self to a not running state. > If you're never getting the timer interrupt, then there's certainly something terribly wrong with the IRQ handling. That has to be fixed first. These should already be handled via the IPR map though, are you dealing with a custom board? Do you have any unusual TMU configuration? Also make sure that the timer IRQ is set to the proper vector.. You need to be getting interrupts before you start worrying about userspace problems. Your config looks pretty close to the SE7705 defconfig, so the timer should certainly not be causing you any difficulty. |
From: Paul M. <le...@li...> - 2007-05-10 07:22:22
|
On Thu, May 10, 2007 at 04:04:34PM +0900, SUGIOKA Toshinobu wrote: > Syscall number from '__NR_add_key' to '__NR_inotify_rm_watch' > have been changed in 2.6.21 > > So, glibc is now incompatible on latest kernel. > Why these are changed ? > inotify was there first, and libinotify has been using that number for some time. It was accidentally renumbered earlier when the key syscalls went in, so this is just correcting that behaviour. |
From: SUGIOKA T. <su...@it...> - 2007-05-10 07:04:41
|
Hi, Syscall number from '__NR_add_key' to '__NR_inotify_rm_watch' have been changed in 2.6.21 So, glibc is now incompatible on latest kernel. Why these are changed ? SUGIOKA Toshinobu |