From d37d2d880efdc0ce515df4155ddafef3835d1b7f Mon Sep 17 00:00:00 2001 From: Alison Wang Date: Thu, 4 Aug 2011 09:59:41 +0800 Subject: [PATCH 09/52] Add ALSA driver for MCF5445x Add ALSA driver for MCF54451 and MCF54455. Signed-off-by: Alison Wang --- sound/Kconfig | 6 +- sound/Makefile | 1 + sound/coldfire/Kconfig | 14 + sound/coldfire/Makefile | 6 + sound/coldfire/coldfire-codec-spi.c | 93 ++ sound/coldfire/snd-coldfire.c | 1664 +++++++++++++++++++++++++++++++++++ sound/coldfire/snd-coldfire.h | 15 + 7 files changed, 1795 insertions(+), 4 deletions(-) create mode 100644 sound/coldfire/Kconfig create mode 100644 sound/coldfire/Makefile create mode 100644 sound/coldfire/coldfire-codec-spi.c create mode 100644 sound/coldfire/snd-coldfire.c create mode 100644 sound/coldfire/snd-coldfire.h --- a/sound/Kconfig +++ b/sound/Kconfig @@ -59,8 +59,6 @@ config SOUND_OSS_CORE_PRECLAIM source "sound/oss/dmasound/Kconfig" -if !M68K - menuconfig SND tristate "Advanced Linux Sound Architecture" help @@ -85,6 +83,8 @@ source "sound/aoa/Kconfig" source "sound/arm/Kconfig" +source "sound/coldfire/Kconfig" + source "sound/atmel/Kconfig" source "sound/spi/Kconfig" @@ -121,8 +121,6 @@ source "sound/oss/Kconfig" endif # SOUND_PRIME -endif # !M68K - endif # SOUND # AC97_BUS is used from both sound and ucb1400 --- a/sound/Makefile +++ b/sound/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_DMASOUND) += oss/ obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ obj-$(CONFIG_SND_AOA) += aoa/ +obj-$(CONFIG_SND_COLDFIRE) += coldfire/ # This one must be compilable even if sound is configured out obj-$(CONFIG_AC97_BUS) += ac97_bus.o --- /dev/null +++ b/sound/coldfire/Kconfig @@ -0,0 +1,14 @@ + +menu "ALSA for Coldfire" + +config SND_COLDFIRE + bool "Coldfire sound devices" + depends on SND + select SND_PCM + select SSIAUDIO_USE_EDMA + default y + help + Support for sound devices specific to Coldfire architectures. + +endmenu + --- /dev/null +++ b/sound/coldfire/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for Coldfire ALSA +# + +obj-y += snd-coldfire.o coldfire-codec-spi.o + --- /dev/null +++ b/sound/coldfire/coldfire-codec-spi.c @@ -0,0 +1,93 @@ +/* + * linux/sound/coldfire/coldfire-codec-spi.c + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Author: Kurt Mahan + * + * Simple SPI interface for the CODEC. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MCF_CODEC_SPI_DRIVER_NAME "mcf_codec_spi" + +static struct spi_device *mcf_codec_spi; + +/* + * Write CODEC register via SPI + */ +int mcf_codec_spi_write(u8 addr, u16 data) +{ + u16 spi_word; + + if (mcf_codec_spi == NULL) + return -ENODEV; + + spi_word = ((addr & 0x7F)<<9)|(data & 0x1FF); + return spi_write(mcf_codec_spi, (const u8 *)&spi_word, + sizeof(spi_word)); +} +EXPORT_SYMBOL(mcf_codec_spi_write); + +static int __devinit mcf_codec_spi_probe(struct spi_device *spi) +{ + spi->dev.power.power_state = PMSG_ON; + mcf_codec_spi = spi; + + return 0; +} + +static int __devexit mcf_codec_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static int mcf_codec_spi_suspend(struct spi_device *spi, pm_message_t message) +{ + return 0; +} + +static int mcf_codec_spi_resume(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver mcf_codec_spi_driver = { + .driver = { + .name = MCF_CODEC_SPI_DRIVER_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mcf_codec_spi_probe, + .remove = __devexit_p(mcf_codec_spi_remove), + .suspend = mcf_codec_spi_suspend, + .resume = mcf_codec_spi_resume, +}; + +static int __init mcf_codec_spi_init(void) +{ + return spi_register_driver(&mcf_codec_spi_driver); +} +module_init(mcf_codec_spi_init); + +static void __exit mcf_codec_spi_exit(void) +{ + spi_unregister_driver(&mcf_codec_spi_driver); +} +module_exit(mcf_codec_spi_exit); + + +MODULE_DESCRIPTION("Coldfire Codec SPI driver"); +MODULE_AUTHOR("Kurt Mahan, Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/sound/coldfire/snd-coldfire.c @@ -0,0 +1,1664 @@ +/* + * linux/sound/coldfire/snd-coldfire.c + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Author: York Sun + * Alison Wang + * + * Coldfire ALSA driver based on SSI and TLV320A + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + *************************************************************************** + * + * NOTE: This driver was tested on MCF5445x, MCF5301x, MCF5227x, MCF532x and + * MCF537x platforms. + * */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "snd-coldfire.h" + +#if defined(CONFIG_M5445X) +#include +#endif + +#define CF_ALSA_DEBUG 0 +#if CF_ALSA_DEBUG +#define DBG(fmt, args...) printk(KERN_INFO "[%s] " fmt , \ + __func__, ## args) +#else +#define DBG(fmt, args...) do {} while (0) +#endif + +#define SOUND_CARD_NAME "Coldfire ALSA" +#define MAX_BUFFER_SIZE (32*1024) + +/* eDMA channel for SSI channel 0,1 TX,RX */ +#define DMA_TX_TCD0 MCF_EDMA_CHAN_TIMER2 +#define DMA_TX_TCD1 MCF_EDMA_CHAN_TIMER3 +#define DMA_RX_TCD0 MCF_EDMA_CHAN_TIMER0 +#define DMA_RX_TCD1 MCF_EDMA_CHAN_TIMER1 + +#define CODEC_LEFT_IN_REG (0x00) +#define CODEC_RIGHT_IN_REG (0x01) +#define CODEC_LEFT_HP_VOL_REG (0x02) +#define CODEC_RIGHT_HP_VOL_REG (0x03) +#define CODEC_ANALOG_APATH_REG (0x04) +#define CODEC_DIGITAL_APATH_REG (0x05) +#define CODEC_POWER_DOWN_REG (0x06) +#define CODEC_DIGITAL_IF_FMT_REG (0x07) +#define CODEC_SAMPLE_RATE_REG (0x08) +#define CODEC_DIGITAL_IF_ACT_REG (0x09) +#define CODEC_RESET_REG (0x0f) + +#define TLV320AIC23_CODEC_SAMPLE_RATE_REG (0x08) +#define TLV320AIC23_CODEC_SAMPLERATE_REG_8KHZ (0x0C) +#define TLV320AIC23_CODEC_SAMPLERATE_REG_11KHZ (0x0C) +#define TLV320AIC23_CODEC_SAMPLERATE_REG_16KHZ (0x58) +#define TLV320AIC23_CODEC_SAMPLERATE_REG_22KHZ (0x62) +#define TLV320AIC23_CODEC_SAMPLERATE_REG_44KHZ (0x22) +#define TLV320AIC23_CODEC_SAMPLERATE_REG_48KHZ (0x00) + +#define MCF_SSI_AUDIO_IRQ_LEVEL (5) +#define TLV320A_VOL_MAX 0x07F +#define TLV320A_VOL_MIN 0x030 +#define TLV320A_VOL_INIT 0x065 +#define TLV320A_LINEIN_MAX 0x1F +#define TLV320A_LINEIN_INIT 0x17 +#define TLV320A_ANALOGPATH_INIT 0x11 + +/* Codec settings */ +#define MCF_SSI_AUDIO_MCLK_1 (12288000U) /*Hz*/ +#define MCF_SSI_AUDIO_MCLK_2 (16934400U) /*Hz*/ +#define MCF_SSI_AUDIO_MCLK_3 (14112000U) /*Hz*/ +#define MCF_SSI_AUDIO_MCLK_4 (18432000U) /*Hz*/ + +#define MCF_SSI_AUDIO_SSDIV_VALUE_1 \ + ((((u32)MCF_CLK*2)/MCF_SSI_AUDIO_MCLK_1)+ \ + (((((u32)MCF_CLK*2*10)/MCF_SSI_AUDIO_MCLK_1)%10) > 5)) + +#define MCF_SSI_AUDIO_SSDIV_VALUE_2 \ + ((((u32)MCF_CLK*2)/MCF_SSI_AUDIO_MCLK_2)+ \ + (((((u32)MCF_CLK*2*10)/MCF_SSI_AUDIO_MCLK_2)%10) > 5)) + +#define MCF_SSI_AUDIO_SSDIV_VALUE_3 \ + ((((u32)MCF_CLK*2)/MCF_SSI_AUDIO_MCLK_3)+ \ + (((((u32)MCF_CLK*2*10)/MCF_SSI_AUDIO_MCLK_3)%10) > 5)) + +#define MCF_SSI_AUDIO_SSDIV_VALUE_4 \ + ((((u32)MCF_CLK*2)/MCF_SSI_AUDIO_MCLK_4)+ \ + (((((u32)MCF_CLK*2*10)/MCF_SSI_AUDIO_MCLK_4)%10) > 5)) + +#define SNDRV_COLDFIRE_PCM_PLAYBACK_FORMATS SNDRV_PCM_FMTBIT_S16_BE +#define SNDRV_COLDFIRE_PCM_CAPTURE_FORMATS SNDRV_PCM_FMTBIT_S16_BE + +#define RXFWM 2 +#define TXFWM 2 +#define HW_PERIODS_BYTES_MIN 4096 +#define HW_PERIODS_BYTES_STEP 4096 + +#define INPUT_MICROPHONE 0 +#define INPUT_LINEIN 1 +#define NUM_TCDS 4 + +static char *id; +static struct platform_device *device; +static int g_tx_dmaing; +static int g_rx_dmaing; +static unsigned char g_mastervol, g_lineinvol, g_analogpath; + +/** Use 4 TCDs for scatter/gather address + * to setup dma chain, one TCD per period + * so that we don't need change them on the fly + */ + +/** + * Link Descriptor + * + * must be aligned on a 32-byte boundary. + */ +struct dma_tcd { + __be32 saddr; /* source address */ + __be16 attr; /* transfer attribute */ + __be16 soffset; /* source offset */ + __be32 nbytes; /* minor byte count */ + __be32 slast; /* last source address adjust */ + __be32 daddr; /* dest address */ + __be16 citer; /* current minor looplink, major count */ + __be16 doffset; /* dest offset */ + __be32 dlast_sga; /* last dest addr adjust, scatter/gather addr*/ + __be16 biter; /* begging minor looklink, major count */ + __be16 csr; /* control and status */ +} __packed; + +/** dma_private: p-substream DMA data + * + * The tcd[] array is first because it needs to be aligned on a 32-byte + * boundary, so putting it first will ensure alignment without padding the + * structure. + * + * @tcd[]: array of TCDs + */ +struct dma_private { + struct dma_tcd tcd0[NUM_TCDS]; + struct dma_tcd tcd1[NUM_TCDS]; + dma_addr_t tcd_buf_phys; /* physical address of dma_private */ + dma_addr_t dma_buf_phys; + dma_addr_t dma_buf_next; + dma_addr_t dma_buf_end; + size_t period_size; + unsigned int num_periods; +}; + +struct tlv320a_audio_device { + struct spi_device *spi; + u32 speed; + u32 stereo; + u32 bits; + u32 format; + u8 isopen; + u8 dmaing; + u8 ssi_enabled; + u8 channel; + spinlock_t lock; + u8 *audio_buf; +}; + +/* chip specific define */ +struct chip_spec { + struct snd_card *card; + struct snd_pcm *pcm; + struct tlv320a_audio_device *audio_device; + u32 offset; + void *mixer_data; +}; + +/* hardware definition */ +static struct snd_pcm_hardware snd_coldfire_playback_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | +#if defined(CONFIG_MMU) + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID| +#endif + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = SNDRV_COLDFIRE_PCM_PLAYBACK_FORMATS, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = HW_PERIODS_BYTES_MIN, + .period_bytes_max = MAX_BUFFER_SIZE/NUM_TCDS, + .periods_min = NUM_TCDS, + .periods_max = NUM_TCDS, + .fifo_size = 0, +}; + +/* hardware definition */ +static struct snd_pcm_hardware snd_coldfire_capture_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | +#if defined(CONFIG_MMU) + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID| +#endif + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = SNDRV_COLDFIRE_PCM_CAPTURE_FORMATS, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = HW_PERIODS_BYTES_MIN, + .period_bytes_max = MAX_BUFFER_SIZE/NUM_TCDS, + .periods_min = NUM_TCDS, + .periods_max = NUM_TCDS, + .fifo_size = 0, +}; + +static unsigned int rates[] = {8000, 11025, 16000, 22000, + 22050, 44000, 44100, 48000}; + +/* hw constraints */ +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static inline void ssi_audio_dma_playback_start(void) +{ + g_tx_dmaing = 1; + mcf_edma_start_transfer(DMA_TX_TCD0); + mcf_edma_start_transfer(DMA_TX_TCD1); +} + +static inline void ssi_audio_dma_capture_start(void) +{ + g_rx_dmaing = 1; + mcf_edma_start_transfer(DMA_RX_TCD0); + mcf_edma_start_transfer(DMA_RX_TCD1); +} + +static inline void ssi_audio_dma_playback_stop(void) +{ + g_tx_dmaing = 0; + mcf_edma_stop_transfer(DMA_TX_TCD0); + mcf_edma_stop_transfer(DMA_TX_TCD1); +} + +inline void ssi_audio_dma_capture_stop(void) +{ + g_rx_dmaing = 0; + mcf_edma_stop_transfer(DMA_RX_TCD0); + mcf_edma_stop_transfer(DMA_RX_TCD1); +} + +/** + * fill_tcd_params - Fill transfer control descriptor (TCD) + * @base: base address of TCD + * @source: source address + * @dest: destination address + * @attr: attributes + * @soff: source offset + * @nbytes: number of bytes to be transfered in minor loop + * @slast: last source address adjustment + * @citer: major loop count + * @biter: beginning minor loop count + * @doff: destination offset + * @dlast_sga: last destination address adjustment + * @major_int: generate interrupt after each major loop + * @disable_req: disable DMA request after major loop + * @enable_sg: enable scatter/gather address + */ +void fill_tcd_params(u32 base, u32 source, u32 dest, + u32 attr, u32 soff, u32 nbytes, u32 slast, + u32 citer, u32 biter, u32 doff, u32 dlast_sga, + int major_int, int disable_req, int enable_sg) +{ + struct dma_tcd *tcd = (struct dma_tcd *) base; + + tcd->saddr = source; + tcd->attr = attr; + tcd->soffset = soff; + tcd->nbytes = nbytes; + tcd->slast = slast; + tcd->daddr = dest; + tcd->citer = citer & 0x7fff; + tcd->doffset = doff; + tcd->dlast_sga = dlast_sga; + tcd->biter = biter & 0x7fff; + tcd->csr = ((major_int) ? 0x2 : 0) | + ((disable_req) ? 0x8 : 0) | + ((enable_sg) ? 0x10 : 0); +} + +static int +ssi_audio_dma_playback_config(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_private *dma_private = runtime->private_data; + u32 size = frames_to_bytes(runtime, runtime->period_size); + u32 offset, soffset, daddr0, daddr1, attr, sga0, sga1; + u32 i, nbyte, major_loops; + + if ((runtime->channels < 1) || (runtime->channels > 2)) { + printk(KERN_ERR "Error on channels = %d\n", runtime->channels); + return -EINVAL; + } + + dma_private->dma_buf_phys = runtime->dma_addr; + dma_private->dma_buf_next = dma_private->dma_buf_phys; + dma_private->dma_buf_end = dma_private->dma_buf_phys + + runtime->periods * size; + + if (runtime->format == SNDRV_PCM_FORMAT_S16_BE) { + nbyte = 2 * TXFWM; + soffset = 2 * runtime->channels; + daddr0 = (u32)&MCF_SSI_TX0 + 2; + daddr1 = (u32)&MCF_SSI_TX1 + 2; + attr = MCF_EDMA_TCD_ATTR_SSIZE_16BIT | + MCF_EDMA_TCD_ATTR_DSIZE_16BIT; + } else { + printk(KERN_ERR "Not supported PCM format %x\n", + runtime->format); + return -EINVAL; + } + + major_loops = size/nbyte/runtime->channels; + sga0 = (u32)dma_private->tcd_buf_phys; + sga1 = (u32)dma_private->tcd_buf_phys + + 4 * sizeof(struct dma_tcd); + +#if defined(CONFIG_M5301x) || defined(CONFIG_M5445X) + MCF_EDMA_TCD10_CSR = 0; + MCF_EDMA_TCD11_CSR = 0; +#else + MCF_EDMA_TCD11_CSR = 0; + MCF_EDMA_TCD12_CSR = 0; +#endif + offset = (runtime->channels - 1) * 2; + mcf_edma_set_tcd_params(DMA_TX_TCD0, + (u32)dma_private->dma_buf_next, + daddr0, + attr, + soffset, + nbyte, + 0, /* slast */ + major_loops, /* citer */ + major_loops, /* biter */ + 0, /* dest offset */ + sga0, + 1, /* major_int */ + 0); /* enable dma request after */ + + mcf_edma_set_tcd_params(DMA_TX_TCD1, + (u32)dma_private->dma_buf_next + offset, + daddr1, + attr, + soffset, + nbyte, + 0, /* slast */ + major_loops, /* citer */ + major_loops, /* biter */ + 0, /* dest offset */ + sga1, + 0, /* major_int */ + 0); /* enable dma request after */ + + while (!(MCF_EDMA_TCD_CSR(DMA_TX_TCD0) & MCF_EDMA_TCD_CSR_E_SG)) + MCF_EDMA_TCD_CSR(DMA_TX_TCD0) |= MCF_EDMA_TCD_CSR_E_SG; + while (!(MCF_EDMA_TCD_CSR(DMA_TX_TCD1) & MCF_EDMA_TCD_CSR_E_SG)) + MCF_EDMA_TCD_CSR(DMA_TX_TCD1) |= MCF_EDMA_TCD_CSR_E_SG; + + for (i = 0; i < NUM_TCDS; i++) { + dma_private->dma_buf_next += size; + if (dma_private->dma_buf_next >= dma_private->dma_buf_end) + dma_private->dma_buf_next = dma_private->dma_buf_phys; + sga0 = (u32)dma_private->tcd_buf_phys + + ((i+1)%NUM_TCDS) * sizeof(struct dma_tcd); + sga1 = (u32)dma_private->tcd_buf_phys + + ((i+1)%NUM_TCDS + 4) * sizeof(struct dma_tcd); + DBG("sga0 = 0x%x, sga1 = 0x%x.\n", sga0, sga1); + fill_tcd_params((u32)&dma_private->tcd0[i], + (u32)dma_private->dma_buf_next, + daddr0, + attr, + soffset, + nbyte, + 0, /* slast */ + major_loops, /* citer */ + major_loops, /* biter */ + 0, /* dest offset */ + sga0, + 1, /* major_int */ + 0, /* enable dma request after */ + 1); /* enable scatter/gather */ + + fill_tcd_params((u32)&dma_private->tcd1[i], + (u32)dma_private->dma_buf_next + offset, + daddr1, + attr, + soffset, + nbyte, + 0, /* slast */ + major_loops, /* citer */ + major_loops, /* biter */ + 0, /* dest offset */ + sga1, + 0, /* major_int */ + 0, /* enable dma request after */ + 1); /* enable scatter/gather */ + } + + return 0; +} + +static int +ssi_audio_dma_capture_config(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_private *dma_private = runtime->private_data; + u32 size = frames_to_bytes(runtime, runtime->period_size); + u32 offset, saddr0, saddr1, doffset, attr, sga0, sga1; + int i, nbyte, major_loops; + + if ((runtime->channels < 1) || (runtime->channels > 2)) { + printk(KERN_ERR "Error on channels = %d\n", runtime->channels); + return -EINVAL; + } + + dma_private->dma_buf_phys = runtime->dma_addr; + dma_private->dma_buf_next = dma_private->dma_buf_phys; + dma_private->dma_buf_end = dma_private->dma_buf_phys + + runtime->periods * size; + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_BE: + saddr0 = (u32)&MCF_SSI_RX0 + 2; + saddr1 = (u32)&MCF_SSI_RX1 + 2; + nbyte = 2 * RXFWM; + doffset = 2 * runtime->channels; + attr = MCF_EDMA_TCD_ATTR_SSIZE_16BIT | + MCF_EDMA_TCD_ATTR_DSIZE_16BIT; + break; + default: + printk(KERN_ERR "Not supported PCM format %x\n", + runtime->format); + return -EINVAL; + } + + major_loops = size/nbyte/runtime->channels; + sga0 = (u32)dma_private->tcd_buf_phys; + sga1 = (u32)dma_private->tcd_buf_phys + + 4 * sizeof(struct dma_tcd); + +#if defined(CONFIG_M5301x) || defined(CONFIG_M5445X) + MCF_EDMA_TCD8_CSR = 0; + MCF_EDMA_TCD9_CSR = 0; +#else + MCF_EDMA_TCD9_CSR = 0; + MCF_EDMA_TCD10_CSR = 0; +#endif + offset = (runtime->channels - 1) * 2; + mcf_edma_set_tcd_params(DMA_RX_TCD0, + saddr0, + (u32)dma_private->dma_buf_next, + attr, + 0, /* source offset */ + nbyte, + 0, /* slast */ + major_loops, /* citer */ + major_loops, /* biter */ + doffset, + sga0, + 1, /* major_int */ + 0); /* enable dma request after */ + + mcf_edma_set_tcd_params(DMA_RX_TCD1, + saddr1, + (u32)dma_private->dma_buf_next + offset, + attr, + 0, /* source offset */ + nbyte, + 0, /* slast */ + major_loops, /* citer */ + major_loops, /* biter */ + doffset, + sga1, + 0, /* major_int */ + 0); /* enable dma request after */ + + while (!(MCF_EDMA_TCD_CSR(DMA_RX_TCD0) & MCF_EDMA_TCD_CSR_E_SG)) + MCF_EDMA_TCD_CSR(DMA_RX_TCD0) |= MCF_EDMA_TCD_CSR_E_SG; + while (!(MCF_EDMA_TCD_CSR(DMA_RX_TCD1) & MCF_EDMA_TCD_CSR_E_SG)) + MCF_EDMA_TCD_CSR(DMA_RX_TCD1) |= MCF_EDMA_TCD_CSR_E_SG; + + for (i = 0; i < NUM_TCDS; i++) { + dma_private->dma_buf_next += size; + if (dma_private->dma_buf_next >= dma_private->dma_buf_end) + dma_private->dma_buf_next = dma_private->dma_buf_phys; + sga0 = (u32)dma_private->tcd_buf_phys + + ((i+1)%NUM_TCDS) * sizeof(struct dma_tcd); + sga1 = (u32)dma_private->tcd_buf_phys + + ((i+1)%NUM_TCDS + 4) * sizeof(struct dma_tcd); + fill_tcd_params((u32)&dma_private->tcd0[i], + saddr0, + (u32)dma_private->dma_buf_next, + attr, + 0, /* source offset */ + nbyte, + 0, /* slast */ + major_loops, /* citer */ + major_loops, /* biter */ + doffset, + sga0, + 1, /* major_int */ + 0, /* enable dma request after */ + 1); /* enable scatter/gather */ + fill_tcd_params((u32)&dma_private->tcd1[i], + saddr1, + (u32)dma_private->dma_buf_next + offset, + attr, + 0, /* source offset */ + nbyte, + 0, /* slast */ + major_loops, /* citer */ + major_loops, /* biter */ + doffset, + sga1, + 0, /* major_int */ + 0, /* enable dma request after */ + 1); /* enable scatter/gather */ + } + return 0; +} + +static inline void ssi_audio_enable_ssi_playback(void) +{ + MCF_SSI_CR |= MCF_SSI_CR_SSI_EN | MCF_SSI_CR_TE; +} + +static inline void ssi_audio_enable_ssi_capture(void) +{ + MCF_SSI_CR |= MCF_SSI_CR_SSI_EN | MCF_SSI_CR_RE; +} + +static inline void ssi_audio_disable_ssi(void) +{ + MCF_SSI_CR &= ~(MCF_SSI_CR_TE | MCF_SSI_CR_RE | MCF_SSI_CR_SSI_EN); +} + +static inline void ssi_audio_disable_ssi_playback(void) +{ + MCF_SSI_CR &= ~MCF_SSI_CR_TE; +} + +static inline void ssi_audio_disable_ssi_capture(void) +{ + MCF_SSI_CR &= ~MCF_SSI_CR_RE; +} + +static irqreturn_t ssi_audio_dma_playback_handler(int channel, void *dev_id) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + + substream = (struct snd_pcm_substream *)dev_id; + runtime = substream->runtime; + + /* inform ALSA middle layer about transfer status */ + snd_pcm_period_elapsed(substream); + mcf_edma_confirm_interrupt_handled(DMA_TX_TCD0); + mcf_edma_confirm_interrupt_handled(DMA_TX_TCD1); + + return IRQ_HANDLED; +} + +static irqreturn_t ssi_audio_dma_capture_handler(int channel, void *dev_id) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + + substream = (struct snd_pcm_substream *)dev_id; + runtime = substream->runtime; + + /* inform ALSA middle layer about transfer status */ + snd_pcm_period_elapsed(substream); + mcf_edma_confirm_interrupt_handled(DMA_RX_TCD0); + mcf_edma_confirm_interrupt_handled(DMA_RX_TCD1); + + return IRQ_HANDLED; +} + +int ssi_audio_dma_request_playback_channel(struct snd_pcm_substream *substream) +{ + int err; + struct chip_spec *chip = snd_pcm_substream_chip(substream); + + /* request eDMA channel */ + err = mcf_edma_request_channel(DMA_TX_TCD0, + ssi_audio_dma_playback_handler, + NULL, + MCF_SSI_AUDIO_IRQ_LEVEL, + substream, + &(chip->audio_device->lock), + id); + if (err < 0) + return err; + err = mcf_edma_request_channel(DMA_TX_TCD1, + ssi_audio_dma_playback_handler, + NULL, + MCF_SSI_AUDIO_IRQ_LEVEL, + substream, + &(chip->audio_device->lock), + id); + return err; +} + +int ssi_audio_dma_request_capture_channel(struct snd_pcm_substream *substream) +{ + int err; + struct chip_spec *chip = snd_pcm_substream_chip(substream); + + /* request 2 eDMA channels for two fifo */ + err = mcf_edma_request_channel(DMA_RX_TCD0, + ssi_audio_dma_capture_handler, + NULL, + MCF_SSI_AUDIO_IRQ_LEVEL, + substream, + &(chip->audio_device->lock), + id); + if (err < 0) + return err; + err = mcf_edma_request_channel(DMA_RX_TCD1, + ssi_audio_dma_capture_handler, + NULL, + MCF_SSI_AUDIO_IRQ_LEVEL, + substream, + &(chip->audio_device->lock), + id); + return err; +} + +static inline void ssi_audio_init_dma(void) +{ + /* SSI DMA Signals mapped to DMA request */ + MCF_CCM_MISCCR &= ~MCF_CCM_MISCCR_TIM_DMA; +} + +static void ssi_audio_adjust_codec_speed(struct snd_pcm_substream *substream) +{ + ssi_audio_disable_ssi(); + + if (substream->runtime->format == SNDRV_PCM_FORMAT_S16_BE) { + MCF_SSI_CCR = MCF_SSI_CCR_WL(7) | /* 16 bit word length */ + MCF_SSI_CCR_DC(1); /* Frame rate divider */ + } + + switch (substream->runtime->rate) { + case 8000: +#if defined(CONFIG_M532x) || defined(CONFIG_M537x) + MCF_CCM_CDR = (MCF_CCM_CDR | MCF_CCM_CDR_SSIDIV(0x20)) + | MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_1); +#else + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0xFF)) | + MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_1); +#endif + MCF_SSI_CCR |= MCF_SSI_CCR_PM(11); + mcf_codec_spi_write(TLV320AIC23_CODEC_SAMPLE_RATE_REG, + TLV320AIC23_CODEC_SAMPLERATE_REG_8KHZ); + break; + case 11000: + case 11025: +#if defined(CONFIG_M532x) || defined(CONFIG_M537x) + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0x3F)) | + MCF_CCM_CDR_SSIDIV(0x2B); + MCF_SSI_CCR |= MCF_SSI_CCR_PM(11); +#else + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0xFF)) | + MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_3); + MCF_SSI_CCR |= MCF_SSI_CCR_PM(9); +#endif + mcf_codec_spi_write(TLV320AIC23_CODEC_SAMPLE_RATE_REG, + TLV320AIC23_CODEC_SAMPLERATE_REG_11KHZ); + break; + case 16000: +#if defined(CONFIG_M532x) || defined(CONFIG_M537x) + MCF_CCM_CDR = (MCF_CCM_CDR | MCF_CCM_CDR_SSIDIV(0x20)) + | MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_1); +#else + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0xFF)) | + MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_1); +#endif + MCF_SSI_CCR |= MCF_SSI_CCR_PM(5); + mcf_codec_spi_write(TLV320AIC23_CODEC_SAMPLE_RATE_REG, + TLV320AIC23_CODEC_SAMPLERATE_REG_16KHZ); + break; + case 22000: + case 22050: +#if defined(CONFIG_M532x) || defined(CONFIG_M537x) + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0x3F)) | + MCF_CCM_CDR_SSIDIV(0x2B); + MCF_SSI_CCR |= MCF_SSI_CCR_PM(5); +#else + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0xFF)) | + MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_3); + MCF_SSI_CCR |= MCF_SSI_CCR_PM(4); +#endif + mcf_codec_spi_write(TLV320AIC23_CODEC_SAMPLE_RATE_REG, + TLV320AIC23_CODEC_SAMPLERATE_REG_22KHZ); + break; + case 48000: +#if defined(CONFIG_M532x) || defined(CONFIG_M537x) + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0x3F)) | + MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_4); + MCF_SSI_CCR |= MCF_SSI_CCR_PM(3); +#else + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0xFF)) | + MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_1); + MCF_SSI_CCR |= MCF_SSI_CCR_PM(1); +#endif + mcf_codec_spi_write(TLV320AIC23_CODEC_SAMPLE_RATE_REG, + TLV320AIC23_CODEC_SAMPLERATE_REG_48KHZ); + break; + case 44000: + case 44100: + default: +#if defined(CONFIG_M532x) || defined(CONFIG_M537x) + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0x3F)) | + MCF_CCM_CDR_SSIDIV(0x2B); +#else + MCF_CCM_CDR = (MCF_CCM_CDR & ~MCF_CCM_CDR_SSIDIV(0xFF)) | + MCF_CCM_CDR_SSIDIV(MCF_SSI_AUDIO_SSDIV_VALUE_2); +#endif + MCF_SSI_CCR |= MCF_SSI_CCR_PM(2); + mcf_codec_spi_write(TLV320AIC23_CODEC_SAMPLE_RATE_REG, + TLV320AIC23_CODEC_SAMPLERATE_REG_44KHZ); + break; + } + DBG("MCF_CCM_CDR = 0x%x, MCF_SSI_CCR = 0x%x.\n", + MCF_CCM_CDR, MCF_SSI_CCR); +} + +static void ssi_audio_codec_reset(void) +{ + mcf_codec_spi_write(CODEC_RESET_REG, 0); /* reset the audio chip */ + udelay(2500); /* wait for reset */ +} + +static void +ssi_audio_init_codec_for_playback(struct snd_pcm_substream *substream) +{ + mcf_codec_spi_write(CODEC_LEFT_IN_REG, g_lineinvol); + mcf_codec_spi_write(CODEC_RIGHT_IN_REG, g_lineinvol); + mcf_codec_spi_write(CODEC_POWER_DOWN_REG, 0x060); + mcf_codec_spi_write(CODEC_DIGITAL_IF_FMT_REG, 0x02); + mcf_codec_spi_write(CODEC_DIGITAL_APATH_REG, 0x006); + mcf_codec_spi_write(CODEC_DIGITAL_IF_ACT_REG, 0x001); + mcf_codec_spi_write(CODEC_ANALOG_APATH_REG, g_analogpath); + mcf_codec_spi_write(CODEC_LEFT_HP_VOL_REG, g_mastervol); + mcf_codec_spi_write(CODEC_RIGHT_HP_VOL_REG, g_mastervol); +} + +static void +ssi_audio_init_codec_for_capture(struct snd_pcm_substream *substream) +{ + mcf_codec_spi_write(CODEC_LEFT_IN_REG, g_lineinvol); + mcf_codec_spi_write(CODEC_RIGHT_IN_REG, g_lineinvol); + mcf_codec_spi_write(CODEC_POWER_DOWN_REG, 0x060); + mcf_codec_spi_write(CODEC_DIGITAL_IF_FMT_REG, 0x02); + mcf_codec_spi_write(CODEC_DIGITAL_APATH_REG, 0x006); + mcf_codec_spi_write(CODEC_DIGITAL_IF_ACT_REG, 0x001); + mcf_codec_spi_write(CODEC_ANALOG_APATH_REG, g_analogpath); + mcf_codec_spi_write(CODEC_LEFT_HP_VOL_REG, g_mastervol); + mcf_codec_spi_write(CODEC_RIGHT_HP_VOL_REG, g_mastervol); +} + +static void ssi_audio_chip_init(void) +{ + int chip_initialized = 0; + if (chip_initialized == 1) + return; + + ssi_audio_init_dma(); + /* Enable the SSI pins */ +#if defined(CONFIG_M5227x) + MCF_GPIO_PAR_UART = (MCF_GPIO_PAR_UART + &~MCF_GPIO_PAR_UART_PAR_U1TXD(0xF) + &~MCF_GPIO_PAR_UART_PAR_U1RXD(0xF) + &~MCF_GPIO_PAR_UART_PAR_U1RTS(0xF) + &~MCF_GPIO_PAR_UART_PAR_U1CTS(0xF)) + | MCF_GPIO_PAR_UART_PAR_U1CTS_SSI_BCLK + | MCF_GPIO_PAR_UART_PAR_U1RTS_SSI_FS + | MCF_GPIO_PAR_UART_PAR_U1RXD_SSI_RXD + | MCF_GPIO_PAR_UART_PAR_U1TXD_SSI_TXD; + + MCF_GPIO_PAR_TIMER = (MCF_GPIO_PAR_TIMER + &~MCF_GPIO_PAR_TIMER_PAR_T3IN(0xF)) + | MCF_GPIO_PAR_TIMER_PAR_T3IN_SSI_MCLK; +#endif +#if defined(CONFIG_M532x) + MCF_GPIO_PAR_SSI = (0 + | MCF_GPIO_PAR_SSI_PAR_MCLK + | MCF_GPIO_PAR_SSI_PAR_TXD(3) + | MCF_GPIO_PAR_SSI_PAR_RXD(3) + | MCF_GPIO_PAR_SSI_PAR_FS(3) + | MCF_GPIO_PAR_SSI_PAR_BCLK(3)); +#endif +#if defined(CONFIG_M537x) + MCF_GPIO_PAR_UART = (MCF_GPIO_PAR_UART + &~MCF_GPIO_PAR_UART_PAR_UTXD1(0xF) + &~MCF_GPIO_PAR_UART_PAR_URXD1(0xF) + &~MCF_GPIO_PAR_UART_PAR_URTS1(0xF) + &~MCF_GPIO_PAR_UART_PAR_UCTS1(0xF)) + | MCF_GPIO_PAR_UART_PAR_UCTS1_SSI_BCLK + | MCF_GPIO_PAR_UART_PAR_URTS1_SSI_FS + | MCF_GPIO_PAR_UART_PAR_URXD1_SSI_RXD + | MCF_GPIO_PAR_UART_PAR_UTXD1_SSI_TXD; + + MCF_GPIO_PAR_IRQ = MCF_GPIO_PAR_IRQ_PAR_IRQ4(1); +#endif +#if defined(CONFIG_M5301x) + MCF_GPIO_PAR_SSIH = (MCF_GPIO_PAR_SSIH_PAR_RXD_SSI_RXD | + MCF_GPIO_PAR_SSIH_PAR_TXD_SSI_TXD | + MCF_GPIO_PAR_SSIH_PAR_FS_SSI_FS | + MCF_GPIO_PAR_SSIH_PAR_MCLK_SSI_MCLK); + MCF_GPIO_PAR_SSIL = MCF_GPIO_PAR_SSIL_PAR_BCLK_SSI_BCLK; +#endif +#if defined(CONFIG_M5445X) + MCF_GPIO_PAR_SSI = (MCF_GPIO_PAR_SSI_MCLK | + MCF_GPIO_PAR_SSI_STXD_STXD | + MCF_GPIO_PAR_SSI_SRXD_SRXD | + MCF_GPIO_PAR_SSI_FS_FS | + MCF_GPIO_PAR_SSI_BCLK_BCLK); +#endif + chip_initialized = 1; +} + +static void ssi_audio_init_ssi_playback(void) +{ + /* Issue a SSI reset */ + MCF_SSI_CR &= ~MCF_SSI_CR_SSI_EN; + + /* SSI module uses internal CPU clock */ + MCF_CCM_MISCCR |= MCF_CCM_MISCCR_SSI_SRC; +#if defined(CONFIG_M5445X) || defined(CONFIG_M532x) || defined(CONFIG_M537x) \ + || defined(CONFIG_M5227x) + MCF_CCM_MISCCR |= MCF_CCM_MISCCR_SSI_PUE | MCF_CCM_MISCCR_SSI_PUS; +#endif +#if defined(CONFIG_M5301x) + MCF_GPIO_PCRH |= MCF_GPIO_PCRH_SSI_PUS | MCF_GPIO_PCRH_SSI_PUE; +#endif + MCF_SSI_CR = MCF_SSI_CR_CIS | + MCF_SSI_CR_TCH | /* Enable two channel mode */ + MCF_SSI_CR_MCE | /* clock out on SSI_MCLK pin */ + MCF_SSI_CR_I2S_MASTER | /* I2S master mode */ + MCF_SSI_CR_SYN | /* Enable synchronous mode */ + MCF_SSI_CR_NET; /* Auto set by I2S Master */ + + MCF_SSI_TCR = 0 | + /* internally generated bit clock */ + MCF_SSI_TCR_TXDIR | + /* internally generated frame sync */ + MCF_SSI_TCR_TFDIR | + /* Clock data on falling edge of bit clock */ + MCF_SSI_TCR_TSCKP | + /* Frame sync active low */ + MCF_SSI_TCR_TFSI | + /* TX frame sync 1 bit before data */ + MCF_SSI_TCR_TEFS | + /* TX FIFO 0 enabled */ + MCF_SSI_TCR_TFEN0 | + /* TX FIFO 1 enabled */ + MCF_SSI_TCR_TFEN1 | + MCF_SSI_TCR_TXBIT0; + + MCF_SSI_FCSR = MCF_SSI_FCSR_TFWM0(TXFWM) | MCF_SSI_FCSR_TFWM1(TXFWM); + + MCF_SSI_IER = MCF_SSI_IER_TDMAE | /* DMA request enabled */ + MCF_SSI_IER_TFE0 | + MCF_SSI_IER_TFE1; /* set by reset actually*/ +} + +static void ssi_audio_init_ssi_capture(void) +{ + /* Issue a SSI reset */ + MCF_SSI_CR &= ~MCF_SSI_CR_SSI_EN; + + /* SSI module uses internal CPU clock */ + MCF_CCM_MISCCR |= MCF_CCM_MISCCR_SSI_SRC; +#if defined(CONFIG_M5445X) || defined(CONFIG_M532x) || defined(CONFIG_M537x) \ + || defined(CONFIG_M5227x) + MCF_CCM_MISCCR |= MCF_CCM_MISCCR_SSI_PUE | MCF_CCM_MISCCR_SSI_PUS; +#endif +#if defined(CONFIG_M5301x) + MCF_GPIO_PCRH |= MCF_GPIO_PCRH_SSI_PUS | MCF_GPIO_PCRH_SSI_PUE; +#endif + MCF_SSI_CR = MCF_SSI_CR_CIS | + MCF_SSI_CR_TCH | /* Enable two channel mode */ + MCF_SSI_CR_MCE | /* clock out on SSI_MCLK pin */ + MCF_SSI_CR_I2S_MASTER | /* I2S master mode */ + MCF_SSI_CR_SYN | /* Enable synchronous mode */ + MCF_SSI_CR_NET; /* Auto set by I2S Master */ + + MCF_SSI_TCR = 0 | + /* internally generated bit clock */ + MCF_SSI_TCR_TXDIR | + /* internally generated frame sync */ + MCF_SSI_TCR_TFDIR | + /* Clock data on falling edge of bit clock */ + MCF_SSI_TCR_TSCKP | + /* Frame sync active low */ + MCF_SSI_TCR_TFSI | + /* TX frame sync 1 bit before data */ + MCF_SSI_TCR_TEFS | + /* TX FIFO 0 enabled */ + MCF_SSI_TCR_TFEN0 | + /* TX FIFO 1 enabled */ + MCF_SSI_TCR_TFEN1 | + MCF_SSI_TCR_TXBIT0; + + MCF_SSI_RCR = 0 | + /* Clock data on rising edge of bit clock */ + MCF_SSI_RCR_RSCKP | + /* Frame sync active low */ + MCF_SSI_RCR_RFSI | + /* RX frame sync 1 bit before data */ + MCF_SSI_RCR_REFS | + /* RX FIFO 0 enabled */ + MCF_SSI_RCR_RFEN0 | + /* RX FIFO 1 enabled */ + MCF_SSI_RCR_RFEN1 | + MCF_SSI_RCR_RXBIT0; /* Auto set by I2S Master */ + + MCF_SSI_FCSR = MCF_SSI_FCSR_RFWM0(RXFWM) | MCF_SSI_FCSR_RFWM1(RXFWM); + + /* interrupts */ + MCF_SSI_IER = MCF_SSI_IER_RDMAE | /* DMA request enabled */ + MCF_SSI_IER_RFF0 | /* rx FIFO 0 full */ + MCF_SSI_IER_RFF1; /* rx FIFO 1 full */ +} + +static int snd_coldfire_playback_open(struct snd_pcm_substream *substream) +{ + int err; + struct chip_spec *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_private *dma_private; + dma_addr_t tcd_buf_phys; + + runtime->hw = snd_coldfire_playback_hw; + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) { + printk(KERN_ERR "invalid buffer size\n"); + return err; + } + /* to make sure period_bytes is the multiple of size of minor loops */ + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + HW_PERIODS_BYTES_STEP); + if (err < 0) { + printk(KERN_ERR "Error setting period_bytes step, " + "err=%d\n", err); + return err; + } + err = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + if (err < 0) { + printk(KERN_ERR "Error setting rate constraints, " + "err=%d\n", err); + return err; + } + ssi_audio_chip_init(); + ssi_audio_init_ssi_playback(); + ssi_audio_init_codec_for_playback(substream); + err = ssi_audio_dma_request_playback_channel(substream); + if (err < 0) { + printk(KERN_ERR "Error requesting dma channel, err=%d\n", err); + return err; + } + + dma_private = dma_alloc_coherent(substream->pcm->dev, + sizeof(struct dma_private), &tcd_buf_phys, GFP_KERNEL); + + if (!dma_private) { + dev_err(substream->pcm->card->dev, + "can't allocate DMA private data\n"); + return -ENOMEM; + } + + dma_private->tcd_buf_phys = tcd_buf_phys; + runtime->private_data = dma_private; + + chip->offset = 0; + g_tx_dmaing = 0; + return 0; +} + +static int snd_coldfire_capture_open(struct snd_pcm_substream *substream) +{ + int err; + struct chip_spec *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_private *dma_private; + dma_addr_t tcd_buf_phys; + + runtime->hw = snd_coldfire_capture_hw; + + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) { + printk(KERN_ERR "invalid buffer size\n"); + return err; + } + /* to make sure period_bytes is the multiple of size of minor loops */ + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + HW_PERIODS_BYTES_STEP); + if (err < 0) { + printk(KERN_ERR "Error setting period_bytes step, " + "err=%d\n", err); + return err; + } + err = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + if (err < 0) { + printk(KERN_ERR "Error setting pcm_hw_constraint_list, " + "err=%d\n", err); + return err; + } + + ssi_audio_chip_init(); + ssi_audio_init_ssi_capture(); + ssi_audio_init_codec_for_capture(substream); + err = ssi_audio_dma_request_capture_channel(substream); + if (err < 0) { + printk(KERN_ERR "Error requesting dma channel, err=%d\n", err); + return err; + } + + dma_private = dma_alloc_coherent(substream->pcm->dev, + sizeof(struct dma_private), &tcd_buf_phys, GFP_KERNEL); + + if (!dma_private) { + dev_err(substream->pcm->card->dev, + "can't allocate DMA private data\n"); + return -ENOMEM; + } + dma_private->tcd_buf_phys = tcd_buf_phys; + dma_private->dma_buf_phys = substream->dma_buffer.addr; + + runtime->private_data = dma_private; + + chip->offset = 0; + g_rx_dmaing = 0; + return 0; +} + +static int snd_coldfire_playback_close(struct snd_pcm_substream *substream) +{ + struct dma_private *dma_private = substream->runtime->private_data; + + ssi_audio_dma_playback_stop(); + mcf_edma_free_channel(DMA_TX_TCD0, substream); + mcf_edma_free_channel(DMA_TX_TCD1, substream); + if (dma_private) { + dma_free_coherent(substream->pcm->dev, + sizeof(struct dma_private), + dma_private, dma_private->tcd_buf_phys); + substream->runtime->private_data = NULL; + } + ssi_audio_disable_ssi(); + return 0; +} + +static int snd_coldfire_capture_close(struct snd_pcm_substream *substream) +{ + struct dma_private *dma_private = substream->runtime->private_data; + + ssi_audio_dma_capture_stop(); + mcf_edma_free_channel(DMA_RX_TCD0, substream); + mcf_edma_free_channel(DMA_RX_TCD1, substream); + /* Deallocate the fsl_dma_private structure */ + if (dma_private) { + dma_free_coherent(substream->pcm->dev, + sizeof(struct dma_private), + dma_private, dma_private->tcd_buf_phys); + substream->runtime->private_data = NULL; + } + ssi_audio_disable_ssi(); + return 0; +} + +static int snd_coldfire_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int err; + + /* set runtime buffer */ + err = snd_pcm_lib_malloc_pages( + substream, params_buffer_bytes(hw_params)); + if (err < 0) + printk(KERN_ERR "Error allocating pages, err=%d\n", err); + return err; +} + +static int snd_coldfire_pcm_hw_free(struct snd_pcm_substream *substream) +{ + /* free the memory if was newly allocated */ + return snd_pcm_lib_free_pages(substream); +} + +static int +snd_coldfire_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + int err; + + if (g_tx_dmaing == 1) + return 0; + + ssi_audio_adjust_codec_speed(substream); + err = ssi_audio_dma_playback_config(substream); + if (err < 0) { + printk(KERN_ERR "Error configuring playback, " + "err=%d\n", err); + return err; + } + + ssi_audio_dma_playback_start(); + return 0; +} + +static int snd_coldfire_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + int err; + + if (g_rx_dmaing == 1) + return 0; + + ssi_audio_adjust_codec_speed(substream); + err = ssi_audio_dma_capture_config(substream); + if (err < 0) { + printk(KERN_ERR "Error configuring capture, " + "err=%d\n", err); + return err; + } + ssi_audio_dma_capture_start(); + + return 0; +} + +static int +snd_coldfire_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ssi_audio_enable_ssi_playback(); + break; + case SNDRV_PCM_TRIGGER_STOP: + ssi_audio_disable_ssi_playback(); + break; + default: + printk(KERN_ERR "Unsupported trigger command, cmd=%d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int +snd_coldfire_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ssi_audio_enable_ssi_capture(); + break; + case SNDRV_PCM_TRIGGER_STOP: + ssi_audio_disable_ssi_capture(); + break; + default: + printk(KERN_ERR "Unsupported trigger command, cmd=%d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +snd_coldfire_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_private *dma_private = runtime->private_data; + snd_pcm_uframes_t pointer; + u32 offset; + + offset = (u32)(MCF_EDMA_TCD_SADDR(DMA_TX_TCD0) - + dma_private->dma_buf_phys); + if (runtime->format == SNDRV_PCM_FORMAT_S16_BE) + pointer = offset / (runtime->channels == 1 ? 2 : 4); + else + pointer = 0; + + return pointer; +} + +static snd_pcm_uframes_t +snd_coldfire_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_private *dma_private = runtime->private_data; + snd_pcm_uframes_t pointer; + u32 offset; + + offset = (u32)(MCF_EDMA_TCD_DADDR(DMA_RX_TCD0) - + dma_private->dma_buf_phys); + if (runtime->format == SNDRV_PCM_FORMAT_S16_BE) + pointer = offset / (runtime->channels == 1 ? 2 : 4); + else + pointer = 0; + + return pointer; +} + +static struct snd_pcm_ops snd_coldfire_playback_ops = { + .open = snd_coldfire_playback_open, + .close = snd_coldfire_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_coldfire_pcm_hw_params, + .hw_free = snd_coldfire_pcm_hw_free, + .prepare = snd_coldfire_pcm_playback_prepare, + .trigger = snd_coldfire_pcm_playback_trigger, + .pointer = snd_coldfire_pcm_playback_pointer, +}; + +static struct snd_pcm_ops snd_coldfire_capture_ops = { + .open = snd_coldfire_capture_open, + .close = snd_coldfire_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_coldfire_pcm_hw_params, + .hw_free = snd_coldfire_pcm_hw_free, + .prepare = snd_coldfire_pcm_capture_prepare, + .trigger = snd_coldfire_pcm_capture_trigger, + .pointer = snd_coldfire_pcm_capture_pointer, +}; + +static int snd_coldfire_new_pcm(struct chip_spec *chip) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "coldfire", 0, 1, 1, + &pcm); + if (err < 0) { + printk(KERN_ERR "Error creating new pcm, err=%d\n", err); + return err; + } + pcm->private_data = chip; + strncpy(pcm->name, SOUND_CARD_NAME, sizeof(pcm->name)); + chip->pcm = pcm; + pcm->info_flags = 0; + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_coldfire_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_coldfire_capture_ops); + /* pre-allocation of buffers */ + err = snd_pcm_lib_preallocate_pages_for_all( + pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + MAX_BUFFER_SIZE, + MAX_BUFFER_SIZE); + + if (!pcm->streams[0].substream->dma_buffer.addr) + pcm->streams[0].substream->dma_buffer.addr = + virt_to_phys(pcm->streams[0].substream->dma_buffer.area); + if (!pcm->streams[1].substream->dma_buffer.addr) + pcm->streams[1].substream->dma_buffer.addr = + virt_to_phys(pcm->streams[1].substream->dma_buffer.area); + + if (err) { + printk(KERN_ERR + "Can't pre-allocate DMA buffer (size=%u)\n", + MAX_BUFFER_SIZE); + return -ENOMEM; + } + + chip->audio_device = + kmalloc(sizeof(struct tlv320a_audio_device), GFP_DMA); + + if (!chip->audio_device) { + snd_pcm_lib_preallocate_free_for_all(pcm); + printk(KERN_ERR + "Can't allocate buffer for audio device\n"); + return -ENOMEM; + } + + return 0; +} + +static int tlv320a_set_out_volume(unsigned char value) +{ + unsigned char data; + + if (value > TLV320A_VOL_MAX) + data = TLV320A_VOL_MAX; + else + data = value; + + if (mcf_codec_spi_write(CODEC_LEFT_HP_VOL_REG, data) < 0) + return -EINVAL; + + if (mcf_codec_spi_write(CODEC_RIGHT_HP_VOL_REG, data) < 0) + return -EINVAL; + + return 0; +} + +static int tlv320a_info_out_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = TLV320A_VOL_MIN; + uinfo->value.integer.max = TLV320A_VOL_MAX; + return 0; +} + +static int tlv320a_get_out_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = g_mastervol; + return 0; +} + +static int tlv320a_put_out_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned char vol; + int change; + + vol = ucontrol->value.integer.value[0]; + + if (vol > TLV320A_VOL_MAX) + return -EINVAL; + + change = (g_mastervol != vol); + if (change) { + g_mastervol = vol; + tlv320a_set_out_volume(vol); + } + return change; +} + +static int tlv320a_set_linein_volume(unsigned char value) +{ + unsigned char data; + + if (value > TLV320A_LINEIN_MAX) + data = TLV320A_LINEIN_MAX; + else + data = value; + + if (mcf_codec_spi_write(CODEC_LEFT_IN_REG, data) < 0) + return -EINVAL; + + if (mcf_codec_spi_write(CODEC_RIGHT_IN_REG, data) < 0) + return -EINVAL; + + return 0; +} + +static int tlv320a_info_linein_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = TLV320A_LINEIN_MAX; + return 0; +} + +static int tlv320a_get_linein_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = g_lineinvol; + return 0; +} + +static int tlv320a_put_linein_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned char vol; + int change; + + vol = ucontrol->value.integer.value[0]; + + if (vol > TLV320A_LINEIN_MAX) + return -EINVAL; + + change = (g_lineinvol != vol); + if (change) { + g_lineinvol = vol; + tlv320a_set_linein_volume(vol); + } + return change; +} + +#define tlv320a_info_mic_boost snd_ctl_boolean_mono_info +static int tlv320a_get_mic_boost(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = ((g_analogpath & 0x1) == 1); + return 0; +} + +static int tlv320a_put_mic_boost(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int oldboost, newboost; + u8 data; + if (ucontrol->value.integer.value[0] == 1) + newboost = 1; + else + newboost = 0; + oldboost = g_analogpath & 0x1; + + if (oldboost == newboost) + return 0; + data = (g_analogpath & 0xfe) | (newboost & 0x1); + if (mcf_codec_spi_write(CODEC_ANALOG_APATH_REG, data) < 0) + return -EINVAL; + g_analogpath = data; + return 1; +} + +static int tlv320a_info_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[] = { "Line-In", "Microphone" }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + return 0; +} + +static int tlv320a_get_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + ucontrol->value.enumerated.item[0] = ((g_analogpath & 0x4) == 0x4); + return 0; +} + +static int tlv320a_put_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int oldinput, newinput; + u8 data; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + + oldinput = (g_analogpath & 0x4) ? INPUT_MICROPHONE : INPUT_LINEIN; + + if (ucontrol->value.enumerated.item[0]) + newinput = INPUT_MICROPHONE; + else + newinput = INPUT_LINEIN; + if (oldinput == newinput) + return 0; + data = (g_analogpath & 0xfb) | + (newinput == INPUT_MICROPHONE ? 0x4 : 0); + if (mcf_codec_spi_write(CODEC_ANALOG_APATH_REG, data) < 0) + return -EINVAL; + g_analogpath = data; + return 1; +} + +static struct snd_kcontrol_new tlv320_mixer_out __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "play volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tlv320a_info_out_volume, + .get = tlv320a_get_out_volume, + .put = tlv320a_put_out_volume, +}; + +static struct snd_kcontrol_new tlv320_mixer_linein __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "record volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tlv320a_info_linein_volume, + .get = tlv320a_get_linein_volume, + .put = tlv320a_put_linein_volume, +}; + +static struct snd_kcontrol_new tlv320_mixer_capture_source __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "record source", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tlv320a_info_capture_source, + .get = tlv320a_get_capture_source, + .put = tlv320a_put_capture_source, +}; + +static struct snd_kcontrol_new tlv320_mixer_mic_boost __devinitdata = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "mic Boost", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tlv320a_info_mic_boost, + .get = tlv320a_get_mic_boost, + .put = tlv320a_put_mic_boost, +}; + +static int __devinit coldfire_alsa_audio_probe(struct platform_device *dev) +{ + struct snd_card *card; + struct chip_spec *chip; + int err; + + err = snd_card_create(-1, id, THIS_MODULE, + sizeof(struct chip_spec), &card); + if (err < 0) + return -ENOMEM; + + chip = card->private_data; + + chip->card = card; + card->dev = &dev->dev; + + err = snd_coldfire_new_pcm(chip); + if (err < 0) + return -ENOMEM; + + strcpy(card->driver, "coldfire"); + strcpy(card->shortname, "Coldfire-TLV320A"); + sprintf(card->longname, "Freescale Coldfire with TLV320A"); + + err = snd_card_register(card); + if (err == 0) { + pr_debug(KERN_INFO "Coldfire audio support initialized\n"); + platform_set_drvdata(dev, card); + } + + strcpy(chip->card->mixername, "TLV320A Volume"); + err = snd_ctl_add(chip->card, snd_ctl_new1(&tlv320_mixer_out, chip)); + if (err) + goto error; + err = snd_ctl_add(chip->card, snd_ctl_new1(&tlv320_mixer_linein, chip)); + if (err) + goto error; + err = snd_ctl_add(chip->card, + snd_ctl_new1(&tlv320_mixer_capture_source, + chip)); + if (err) + goto error; + err = snd_ctl_add(chip->card, + snd_ctl_new1(&tlv320_mixer_mic_boost, + chip)); + if (err) + goto error; + g_mastervol = TLV320A_VOL_INIT; + g_lineinvol = TLV320A_LINEIN_INIT; + g_analogpath = TLV320A_ANALOGPATH_INIT; + ssi_audio_codec_reset(); + return 0; +error: + kfree(card->private_data); + snd_card_free(card); + platform_set_drvdata(dev, NULL); + return err; +} + +static int coldfire_alsa_audio_remove(struct platform_device *dev) +{ + struct snd_card *card; + + card = platform_get_drvdata(dev); + kfree(card->private_data); + snd_card_free(card); + platform_set_drvdata(dev, NULL); + + return 0; +} + +static struct platform_driver coldfire_alsa_audio_driver = { + .probe = coldfire_alsa_audio_probe, + .remove = coldfire_alsa_audio_remove, + .driver = { + .name = SOUND_CARD_NAME, + }, +}; + +static int __init coldfire_alsa_audio_init(void) +{ + int err; + err = platform_driver_register(&coldfire_alsa_audio_driver); + if (err < 0) + return err; + + device = platform_device_register_simple(SOUND_CARD_NAME, -1, NULL, 0); + if (!IS_ERR(device)) { + if (platform_get_drvdata(device)) + return 0; + platform_device_unregister(device); + platform_driver_unregister(&coldfire_alsa_audio_driver); + err = -ENODEV; + } else + err = PTR_ERR(device); + + platform_driver_unregister(&coldfire_alsa_audio_driver); + return err; +} + +static void __exit coldfire_alsa_audio_exit(void) +{ + platform_device_unregister(device); + platform_driver_unregister(&coldfire_alsa_audio_driver); +} + +module_init(coldfire_alsa_audio_init); +module_exit(coldfire_alsa_audio_exit); + +MODULE_DESCRIPTION("Coldfire driver for ALSA"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{TLV320A}}"); + +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for Coldfire + TLV320A soundcard."); --- /dev/null +++ b/sound/coldfire/snd-coldfire.h @@ -0,0 +1,15 @@ +/* + * linux/sound/coldfire/snd-coldfire.h + * + * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * ALSA driver for Coldfire SSI + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +extern int mcf_codec_spi_write(u8 addr, u16 data); +