Commit 77620101 authored by Daniel Petrini, David Cohen, Anderson Briglia's avatar Daniel Petrini, David Cohen, Anderson Briglia Committed by Tony Lindgren

[PATCH] ARM: OMAP: Alsa driver for osk board - new version (3)

Alsa Driver for omap osk5912
parent 88b0a2c8
......@@ -33,4 +33,16 @@ config SND_PXA2XX_AC97
Say Y or M if you want to support any AC97 codec attached to
the PXA2xx AC97 interface.
config SND_OMAP_AIC23
tristate "OMAP AIC23 alsa driver (osk5912)"
depends on ARCH_OMAP && SND
select SND_PCM
select SENSORS_TLV320AIC23
help
Say Y here if you have a OSK platform board
and want to use its AIC23 audio chip.
To compile this driver as a module, choose M here: the module
will be called snd-omap-aic23.
endmenu
......@@ -6,8 +6,10 @@ snd-sa11xx-uda1341-objs := sa11xx-uda1341.o
snd-aaci-objs := aaci.o devdma.o
snd-pxa2xx-pcm-objs := pxa2xx-pcm.o
snd-pxa2xx-ac97-objs := pxa2xx-ac97.o
snd-omap-aic23-objs := omap-aic23.o omap-alsa-dma.o omap-alsa-mixer.o
obj-$(CONFIG_SND_SA11XX_UDA1341) += snd-sa11xx-uda1341.o
obj-$(CONFIG_SND_ARMAACI) += snd-aaci.o
obj-$(CONFIG_SND_PXA2XX_PCM) += snd-pxa2xx-pcm.o
obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o
obj-$(CONFIG_SND_OMAP_AIC23) += snd-omap-aic23.o
/*
* sound/arm/omap-aic23.c
*
* Alsa Driver for AIC23 codec on OSK5912 platform board
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Written by Daniel Petrini, David Cohen, Anderson Briglia
* {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br
*
* Based on sa11xx-uda1341.c,
* Copyright (C) 2002 Tomas Kasparek <tomas.kasparek@seznam.cz>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
* History:
*
* 2005-07-29 INdT Kernel Team - Alsa driver for omap osk. Creation of new
* file omap-aic23.c
*/
#include <linux/config.h>
#include <sound/driver.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/slab.h>
#ifdef CONFIG_PM
#include <linux/pm.h>
#endif
#include <asm/hardware.h>
#include <asm/mach-types.h>
#include <asm/arch/dma.h>
#include <asm/arch/aic23.h>
#include <asm/hardware/clock.h>
#include <asm/arch/mcbsp.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/memalloc.h>
#include "omap-alsa-dma.h"
#include "omap-aic23.h"
#undef DEBUG
#ifdef DEBUG
#define ADEBUG() printk("XXX Alsa debug f:%s, l:%d\n", __FUNCTION__, __LINE__)
#else
#define ADEBUG() /* nop */
#endif
/* Define to set the AIC23 as the master w.r.t McBSP */
#define AIC23_MASTER
/*
* AUDIO related MACROS
*/
#define DEFAULT_BITPERSAMPLE 16
#define AUDIO_RATE_DEFAULT 44100
#define AUDIO_MCBSP OMAP_MCBSP1
#define NUMBER_SAMPLE_RATES_SUPPORTED 10
MODULE_AUTHOR("Daniel Petrini, David Cohen, Anderson Briglia - INdT");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("OMAP AIC23 driver for ALSA");
MODULE_SUPPORTED_DEVICE("{{AIC23,OMAP AIC23}}");
MODULE_ALIAS("omap_mcbsp.1");
static char *id = NULL;
MODULE_PARM_DESC(id, "OMAP OSK ALSA Driver for AIC23 chip.");
static struct snd_card_omap_aic23 *omap_aic23 = NULL;
static struct clk *aic23_mclk = 0;
struct sample_rate_rate_reg_info {
u8 control; /* SR3, SR2, SR1, SR0 and BOSR */
u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */
};
/*
* DAC USB-mode sampling rates (MCLK = 12 MHz)
* The rates and rate_reg_into MUST be in the same order
*/
static unsigned int rates[] = {
4000, 8000, 16000, 22050,
24000, 32000, 44100,
48000, 88200, 96000,
};
static const struct sample_rate_rate_reg_info
rate_reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = {
{0x06, 1}, /* 4000 */
{0x06, 0}, /* 8000 */
{0x0C, 1}, /* 16000 */
{0x11, 1}, /* 22050 */
{0x00, 1}, /* 24000 */
{0x0C, 0}, /* 32000 */
{0x11, 0}, /* 44100 */
{0x00, 0}, /* 48000 */
{0x1F, 0}, /* 88200 */
{0x0E, 0}, /* 96000 */
};
/*
* mcbsp configuration structure
*/
static struct omap_mcbsp_reg_cfg initial_config_mcbsp = {
.spcr2 = FREE | FRST | GRST | XRST | XINTM(3),
.spcr1 = RINTM(3) | RRST,
.rcr2 = RPHASE | RFRLEN2(OMAP_MCBSP_WORD_8) |
RWDLEN2(OMAP_MCBSP_WORD_16) | RDATDLY(0),
.rcr1 = RFRLEN1(OMAP_MCBSP_WORD_8) | RWDLEN1(OMAP_MCBSP_WORD_16),
.xcr2 = XPHASE | XFRLEN2(OMAP_MCBSP_WORD_8) |
XWDLEN2(OMAP_MCBSP_WORD_16) | XDATDLY(0) | XFIG,
.xcr1 = XFRLEN1(OMAP_MCBSP_WORD_8) | XWDLEN1(OMAP_MCBSP_WORD_16),
.srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1),
.srgr2 = GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1),
#ifndef AIC23_MASTER
/* configure McBSP to be the I2S master */
.pcr0 = FSXM | FSRM | CLKXM | CLKRM | CLKXP | CLKRP,
#else
/* configure McBSP to be the I2S slave */
.pcr0 = CLKXP | CLKRP,
#endif /* AIC23_MASTER */
};
static snd_pcm_hw_constraint_list_t hw_constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
/*
* Codec/mcbsp init and configuration section
* codec dependent code.
*/
extern int tlv320aic23_write_value(u8 reg, u16 value);
/* TLV320AIC23 is a write only device */
__inline__ void audio_aic23_write(u8 address, u16 data)
{
tlv320aic23_write_value(address, data);
}
/*
* Sample rate changing
*/
static void omap_aic23_set_samplerate(struct snd_card_omap_aic23
*omap_aic23, long rate)
{
u8 count = 0;
u16 data = 0;
/* Fix the rate if it has a wrong value */
if (rate >= 96000)
rate = 96000;
else if (rate >= 88200)
rate = 88200;
else if (rate >= 48000)
rate = 48000;
else if (rate >= 44100)
rate = 44100;
else if (rate >= 32000)
rate = 32000;
else if (rate >= 24000)
rate = 24000;
else if (rate >= 22050)
rate = 22050;
else if (rate >= 16000)
rate = 16000;
else if (rate >= 8000)
rate = 8000;
else
rate = 4000;
/* Search for the right sample rate */
/* Verify what happens if the rate is not supported
* now it goes to 96Khz */
while ((rates[count] != rate) &&
(count < (NUMBER_SAMPLE_RATES_SUPPORTED - 1))) {
count++;
}
data = (rate_reg_info[count].divider << CLKIN_SHIFT) |
(rate_reg_info[count].control << BOSR_SHIFT) | USB_CLK_ON;
audio_aic23_write(SAMPLE_RATE_CONTROL_ADDR, data);
omap_aic23->samplerate = rate;
}
static inline void aic23_configure(void)
{
/* Reset codec */
audio_aic23_write(RESET_CONTROL_ADDR, 0);
/* Initialize the AIC23 internal state */
/* Analog audio path control, DAC selected, delete INSEL_MIC for line in */
audio_aic23_write(ANALOG_AUDIO_CONTROL_ADDR, DEFAULT_ANALOG_AUDIO_CONTROL);
/* Digital audio path control, de-emphasis control 44.1kHz */
audio_aic23_write(DIGITAL_AUDIO_CONTROL_ADDR, DEEMP_44K);
/* Digital audio interface, master/slave mode, I2S, 16 bit */
#ifdef AIC23_MASTER
audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR,
MS_MASTER | IWL_16 | FOR_DSP);
#else
audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, IWL_16 | FOR_DSP);
#endif
/* Enable digital interface */
audio_aic23_write(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON);
}
static void omap_aic23_audio_init(struct snd_card_omap_aic23 *omap_aic23)
{
/* Setup DMA stuff */
omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK].id = "Alsa AIC23 out";
omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id =
SNDRV_PCM_STREAM_PLAYBACK;
omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK].dma_dev =
OMAP_DMA_MCBSP1_TX;
omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE].id = "Alsa AIC23 in";
omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE].stream_id =
SNDRV_PCM_STREAM_CAPTURE;
omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE].dma_dev =
OMAP_DMA_MCBSP1_RX;
/* configuring the McBSP */
omap_mcbsp_request(AUDIO_MCBSP);
/* if configured, then stop mcbsp */
omap_mcbsp_stop(AUDIO_MCBSP);
omap_mcbsp_config(AUDIO_MCBSP, &initial_config_mcbsp);
omap_mcbsp_start(AUDIO_MCBSP);
aic23_configure();
}
/*
* DMA functions
* Depends on omap-aic23-dma.c functions and (omap) dma.c
*
*/
#define DMA_BUF_SIZE 1024 * 8
static int audio_dma_request(struct audio_stream *s,
void (*callback) (void *))
{
int err;
err = omap_request_sound_dma(s->dma_dev, s->id, s, &s->lch);
if (err < 0)
printk(KERN_ERR "unable to grab audio dma 0x%x\n",
s->dma_dev);
return err;
}
static int audio_dma_free(struct audio_stream *s)
{
int err = 0;
err = omap_free_sound_dma(s, &s->lch);
if (err < 0)
printk(KERN_ERR "Unable to free audio dma channels!\n");
return err;
}
/*
* This function should calculate the current position of the dma in the
* buffer. It will help alsa middle layer to continue update the buffer.
* Its correctness is crucial for good functioning.
*/
static u_int audio_get_dma_pos(struct audio_stream *s)
{
snd_pcm_substream_t *substream = s->stream;
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned int offset;
unsigned long flags;
dma_addr_t count;
ADEBUG();
/* this must be called w/ interrupts locked as requested in dma.c */
spin_lock_irqsave(&s->dma_lock, flags);
/* For the current period let's see where we are */
count = omap_get_dma_src_addr_counter(s->lch[s->dma_q_head]);
spin_unlock_irqrestore(&s->dma_lock, flags);
/* Now, the position related to the end of that period */
offset = bytes_to_frames(runtime, s->offset) - bytes_to_frames(runtime, count);
if (offset >= runtime->buffer_size || offset < 0)
offset = 0;
return offset;
}
/*
* this stops the dma and clears the dma ptrs
*/
static void audio_stop_dma(struct audio_stream *s)
{
unsigned long flags;
ADEBUG();
spin_lock_irqsave(&s->dma_lock, flags);
s->active = 0;
s->period = 0;
s->periods = 0;
/* this stops the dma channel and clears the buffer ptrs */
omap_audio_stop_dma(s);
omap_clear_sound_dma(s);
spin_unlock_irqrestore(&s->dma_lock, flags);
}
/*
* Main dma routine, requests dma according where you are in main alsa buffer
*/
static void audio_process_dma(struct audio_stream *s)
{
snd_pcm_substream_t *substream = s->stream;
snd_pcm_runtime_t *runtime;
unsigned int dma_size;
unsigned int offset;
int ret;
runtime = substream->runtime;
if (s->active) {
dma_size = frames_to_bytes(runtime, runtime->period_size);
offset = dma_size * s->period;
snd_assert(dma_size <= DMA_BUF_SIZE,);
ret =
omap_start_sound_dma(s,
(dma_addr_t) runtime->dma_area +
offset, dma_size);
if (ret) {
printk(KERN_ERR
"audio_process_dma: cannot queue DMA buffer (%i)\n",
ret);
return;
}
s->period++;
s->period %= runtime->periods;
s->periods++;
s->offset = offset;
}
}
/*
* This is called when dma IRQ occurs at the end of each transmited block
*/
void audio_dma_callback(void *data)
{
struct audio_stream *s = data;
/*
* If we are getting a callback for an active stream then we inform
* the PCM middle layer we've finished a period
*/
if (s->active)
snd_pcm_period_elapsed(s->stream);
spin_lock(&s->dma_lock);
if (s->periods > 0) {
s->periods--;
}
audio_process_dma(s);
spin_unlock(&s->dma_lock);
}
/*
* Alsa section
* PCM settings and callbacks
*/
static int snd_omap_aic23_trigger(snd_pcm_substream_t * substream, int cmd)
{
struct snd_card_omap_aic23 *chip =
snd_pcm_substream_chip(substream);
int stream_id = substream->pstr->stream;
struct audio_stream *s = &chip->s[stream_id];
int err = 0;
ADEBUG();
/* note local interrupts are already disabled in the midlevel code */
spin_lock(&s->dma_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* requested stream startup */
s->active = 1;
audio_process_dma(s);
break;
case SNDRV_PCM_TRIGGER_STOP:
/* requested stream shutdown */
audio_stop_dma(s);
break;
default:
err = -EINVAL;
break;
}
spin_unlock(&s->dma_lock);
return err;
}
static int snd_omap_aic23_prepare(snd_pcm_substream_t * substream)
{
struct snd_card_omap_aic23 *chip =
snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
struct audio_stream *s = &chip->s[substream->pstr->stream];
/* set requested samplerate */
omap_aic23_set_samplerate(chip, runtime->rate);
s->period = 0;
s->periods = 0;
return 0;
}
static snd_pcm_uframes_t snd_omap_aic23_pointer(snd_pcm_substream_t *
substream)
{
struct snd_card_omap_aic23 *chip =
snd_pcm_substream_chip(substream);
return audio_get_dma_pos(&chip->s[substream->pstr->stream]);
}
/* Hardware capabilities */
static snd_pcm_hardware_t snd_omap_aic23_capture = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 96000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
static snd_pcm_hardware_t snd_omap_aic23_playback = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 96000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
static int snd_card_omap_aic23_open(snd_pcm_substream_t * substream)
{
struct snd_card_omap_aic23 *chip =
snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int stream_id = substream->pstr->stream;
int err;
ADEBUG();
chip->s[stream_id].stream = substream;
omap_aic23_clock_on();
if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = snd_omap_aic23_playback;
else
runtime->hw = snd_omap_aic23_capture;
if ((err =
snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS)) <
0)
return err;
if ((err =
snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&hw_constraints_rates)) < 0)
return err;
return 0;
}
static int snd_card_omap_aic23_close(snd_pcm_substream_t * substream)
{
struct snd_card_omap_aic23 *chip =
snd_pcm_substream_chip(substream);
ADEBUG();
omap_aic23_clock_off();
chip->s[substream->pstr->stream].stream = NULL;
return 0;
}
/* HW params & free */
static int snd_omap_aic23_hw_params(snd_pcm_substream_t * substream,
snd_pcm_hw_params_t * hw_params)
{
return snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
}
static int snd_omap_aic23_hw_free(snd_pcm_substream_t * substream)
{
return snd_pcm_lib_free_pages(substream);
}
/* pcm operations */
static snd_pcm_ops_t snd_card_omap_aic23_playback_ops = {
.open = snd_card_omap_aic23_open,
.close = snd_card_omap_aic23_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_omap_aic23_hw_params,
.hw_free = snd_omap_aic23_hw_free,
.prepare = snd_omap_aic23_prepare,
.trigger = snd_omap_aic23_trigger,
.pointer = snd_omap_aic23_pointer,
};
static snd_pcm_ops_t snd_card_omap_aic23_capture_ops = {
.open = snd_card_omap_aic23_open,
.close = snd_card_omap_aic23_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_omap_aic23_hw_params,
.hw_free = snd_omap_aic23_hw_free,
.prepare = snd_omap_aic23_prepare,
.trigger = snd_omap_aic23_trigger,
.pointer = snd_omap_aic23_pointer,
};
/*
* Alsa init and exit section
*
* Inits pcm alsa structures, allocate the alsa buffer, suspend, resume
*/
static int __init snd_card_omap_aic23_pcm(struct snd_card_omap_aic23
*omap_aic23, int device)
{
snd_pcm_t *pcm;
int err;
ADEBUG();
if ((err =
snd_pcm_new(omap_aic23->card, "AIC23 PCM", device, 1, 1,
&pcm)) < 0)
return err;
/* sets up initial buffer with continuous allocation */
snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data
(GFP_KERNEL),
128 * 1024, 128 * 1024);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_card_omap_aic23_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_card_omap_aic23_capture_ops);
pcm->private_data = omap_aic23;
pcm->info_flags = 0;
strcpy(pcm->name, "omap aic23 pcm");
omap_aic23_audio_init(omap_aic23);
/* setup DMA controller */
audio_dma_request(&omap_aic23->s[SNDRV_PCM_STREAM_PLAYBACK],
audio_dma_callback);
audio_dma_request(&omap_aic23->s[SNDRV_PCM_STREAM_CAPTURE],
audio_dma_callback);
omap_aic23->pcm = pcm;
return 0;
}
#ifdef CONFIG_PM
static int snd_omap_aic23_suspend(snd_card_t * card, pm_message_t state)
{
struct snd_card_omap_aic23 *chip = card->pm_private_data;
ADEBUG();
if (chip->card->power_state != SNDRV_CTL_POWER_D3hot) {
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
snd_pcm_suspend_all(chip->pcm);
/* Mutes and turn clock off */
omap_aic23_clock_off();
snd_omap_suspend_mixer();
}
return 0;
}
/*
* Prepare hardware for resume
*/
static int snd_omap_aic23_resume(snd_card_t * card)
{
struct snd_card_omap_aic23 *chip = card->pm_private_data;
ADEBUG();
if (chip->card->power_state != SNDRV_CTL_POWER_D0) {
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
omap_aic23_clock_on();
snd_omap_resume_mixer();
}
return 0;
}
/*
* Driver suspend/resume - calls alsa functions. Some hints from aaci.c
*/
static int omap_aic23_suspend(struct device *dev, pm_message_t state, u32 level)
{
snd_card_t *card = dev_get_drvdata(dev);
if (card->power_state != SNDRV_CTL_POWER_D3hot) {
snd_omap_aic23_suspend(card, 0);
}
return 0;
}
static int omap_aic23_resume(struct device *dev, u32 level)
{
snd_card_t *card = dev_get_drvdata(dev);
if (card->power_state != SNDRV_CTL_POWER_D0) {
snd_omap_aic23_resume(card);
}
return 0;
}
#else
#define snd_omap_aic23_suspend NULL
#define snd_omap_aic23_resume NULL
#define omap_aic23_suspend NULL
#define omap_aic23_resume NULL
#endif /* CONFIG_PM */
/*
*/
void snd_omap_aic23_free(snd_card_t * card)
{
struct snd_card_omap_aic23 *chip = card->private_data;
ADEBUG();
/*
* Turn off codec after it is done.
* Can't do it immediately, since it may still have
* buffered data.
*/
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(2);
omap_mcbsp_stop(AUDIO_MCBSP);
omap_mcbsp_free(AUDIO_MCBSP);
audio_aic23_write(RESET_CONTROL_ADDR, 0);
audio_aic23_write(POWER_DOWN_CONTROL_ADDR, 0xff);
audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]);
audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]);
}
/*
* Omap MCBSP clock configuration
*
* Here we have some functions that allows clock to be enabled and
* disabled only when needed. Besides doing clock configuration
* it allows turn on/turn off audio when necessary.
*/
#define CODEC_CLOCK 12000000
#define AUDIO_RATE_DEFAULT 44100
/*
* Do clock framework mclk search
*/
static __init void omap_aic23_clock_setup(void)
{
aic23_mclk = clk_get(0, "mclk");
}
/*
* Do some sanity check, set clock rate, starts it and
* turn codec audio on
*/
int omap_aic23_clock_on(void)
{
if (clk_get_usecount(aic23_mclk) > 0) {
/* MCLK is already in use */
printk(KERN_WARNING
"MCLK in use at %d Hz. We change it to %d Hz\n",
(uint) clk_get_rate(aic23_mclk),
CODEC_CLOCK);
}
if (clk_set_rate(aic23_mclk, CODEC_CLOCK)) {
printk(KERN_ERR
"Cannot set MCLK for AIC23 CODEC\n");
return -ECANCELED;
}
clk_use(aic23_mclk);
printk(KERN_DEBUG
"MCLK = %d [%d], usecount = %d\n",
(uint) clk_get_rate(aic23_mclk), CODEC_CLOCK,
clk_get_usecount(aic23_mclk));
/* Now turn the audio on */
audio_aic23_write(POWER_DOWN_CONTROL_ADDR,
~DEVICE_POWER_OFF & ~OUT_OFF & ~DAC_OFF &
~ADC_OFF & ~MIC_OFF & ~LINE_OFF);
return 0;
}
/*
* Do some sanity check, turn clock off and then turn
* codec audio off
*/
int omap_aic23_clock_off(void)
{
if (clk_get_usecount(aic23_mclk) > 0) {
if (clk_get_rate(aic23_mclk) != CODEC_CLOCK) {
printk(KERN_WARNING
"MCLK for audio should be %d Hz. But is %d Hz\n",
(uint) clk_get_rate(aic23_mclk),
CODEC_CLOCK);
}
clk_unuse(aic23_mclk);
}
audio_aic23_write(POWER_DOWN_CONTROL_ADDR,
DEVICE_POWER_OFF | OUT_OFF | DAC_OFF |
ADC_OFF | MIC_OFF | LINE_OFF);
return 0;
}
/* module init & exit */
/*
* Inits alsa soudcard structure
*/
static int __init snd_omap_aic23_probe(struct device *dev)
{
int err = 0;
snd_card_t *card;
ADEBUG();
/* gets clock from clock framework */
omap_aic23_clock_setup();
/* register the soundcard */
card = snd_card_new(-1, id, THIS_MODULE, sizeof(omap_aic23));
if (card == NULL)
return -ENOMEM;
omap_aic23 = kcalloc(1, sizeof(*omap_aic23), GFP_KERNEL);
if (omap_aic23 == NULL)
return -ENOMEM;
card->private_data = (void *) omap_aic23;
card->private_free = snd_omap_aic23_free;
omap_aic23->card = card;
omap_aic23->samplerate = AUDIO_RATE_DEFAULT;
spin_lock_init(&omap_aic23->s[0].dma_lock);
spin_lock_init(&omap_aic23->s[1].dma_lock);
/* mixer */
if ((err = snd_omap_mixer(omap_aic23)) < 0)
goto nodev;
/* PCM */
if ((err = snd_card_omap_aic23_pcm(omap_aic23, 0)) < 0)
goto nodev;
snd_card_set_pm_callback(card, snd_omap_aic23_suspend,
snd_omap_aic23_resume, omap_aic23);
strcpy(card->driver, "AIC23");
strcpy(card->shortname, "OSK AIC23");
sprintf(card->longname, "OMAP OSK with AIC23");
snd_omap_init_mixer();
if ((err = snd_card_register(card)) == 0) {
printk(KERN_INFO "OSK audio support initialized\n");
dev_set_drvdata(dev, card);
return 0;
}
nodev:
snd_omap_aic23_free(card);
return err;
}
static int snd_omap_aic23_remove(struct device *dev)
{
snd_card_t *card = dev_get_drvdata(dev);
struct snd_card_omap_aic23 *chip = card->private_data;
snd_card_free(card);
omap_aic23 = NULL;
card->private_data = NULL;
kfree(chip);
dev_set_drvdata(dev, NULL);
return 0;
}
static struct device_driver omap_alsa_driver = {
.name = "omap_mcbsp",
.bus = &platform_bus_type,
.probe = snd_omap_aic23_probe,
.remove = snd_omap_aic23_remove,
.suspend = omap_aic23_suspend,
.resume = omap_aic23_resume,
};
static int __init omap_aic23_init(void)
{
int err;
ADEBUG();
err = driver_register(&omap_alsa_driver);
return err;
}
static void __exit omap_aic23_exit(void)
{
ADEBUG();
driver_unregister(&omap_alsa_driver);
}
module_init(omap_aic23_init);
module_exit(omap_aic23_exit);
/*
* sound/arm/omap-aic23.h
*
* Alsa Driver for AIC23 codec on OSK5912 platform board
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Written by Daniel Petrini, David Cohen, Anderson Briglia
* {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
* History
* -------
*
* 2005/07/25 INdT-10LE Kernel Team - Alsa driver for omap osk,
* original version based in sa1100 driver
* and omap oss driver.
*
*/
#ifndef __OMAP_AIC23_H
#define __OMAP_AIC23_H
#include <sound/driver.h>
#include <asm/arch/dma.h>
#include <sound/core.h>
#include <sound/pcm.h>
#define DEFAULT_OUTPUT_VOLUME 0x60
#define DEFAULT_INPUT_VOLUME 0x00 /* 0 ==> mute line in */
#define OUTPUT_VOLUME_MIN LHV_MIN
#define OUTPUT_VOLUME_MAX LHV_MAX
#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN)
#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MAX
#define INPUT_VOLUME_MIN LIV_MIN
#define INPUT_VOLUME_MAX LIV_MAX
#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN)
#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX
#define SIDETONE_MASK 0x1c0
#define SIDETONE_0 0x100
#define SIDETONE_6 0x000
#define SIDETONE_9 0x040
#define SIDETONE_12 0x080
#define SIDETONE_18 0x0c0
#define DEFAULT_ANALOG_AUDIO_CONTROL DAC_SELECTED | STE_ENABLED | BYPASS_ON | INSEL_MIC | MICB_20DB
/*
* Buffer management for alsa and dma
*/
struct audio_stream {
char *id; /* identification string */
int stream_id; /* numeric identification */
int dma_dev; /* dma number of that device */
int *lch; /* Chain of channels this stream is linked to */
char started; /* to store if the chain was started or not */
int dma_q_head; /* DMA Channel Q Head */
int dma_q_tail; /* DMA Channel Q Tail */
char dma_q_count; /* DMA Channel Q Count */
int active:1; /* we are using this stream for transfer now */
int period; /* current transfer period */
int periods; /* current count of periods registerd in the DMA engine */
spinlock_t dma_lock; /* for locking in DMA operations */
snd_pcm_substream_t *stream; /* the pcm stream */
unsigned linked:1; /* dma channels linked */
int offset; /* store start position of the last period in the alsa buffer */
};
/*
* Alsa card structure for aic23
*/
struct snd_card_omap_aic23 {
snd_card_t *card;
snd_pcm_t *pcm;
long samplerate;
struct audio_stream s[2]; /* playback & capture */
};
/*********** Function Prototypes *************************/
void audio_dma_callback(void *);
int snd_omap_mixer(struct snd_card_omap_aic23 *);
void snd_omap_init_mixer(void);
/* Clock functions */
int omap_aic23_clock_on(void);
int omap_aic23_clock_off(void);
#ifdef CONFIG_PM
void snd_omap_suspend_mixer(void);
void snd_omap_resume_mixer(void);
#endif
#endif
/*
* sound/arm/omap-alsa-dma.c
*
* Common audio DMA handling for the OMAP processors
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History:
*
* 2004-06-07 Sriram Kannan - Created new file from omap_audio_dma_intfc.c. This file
* will contain only the DMA interface and buffer handling of OMAP
* audio driver.
*
* 2004-06-22 Sriram Kannan - removed legacy code (auto-init). Self-linking of DMA logical channel.
*
* 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms
*
* 2004-11-01 Nishanth Menon - 16xx platform code base modified to support multi channel chaining.
*
* 2004-12-15 Nishanth Menon - Improved 16xx platform channel logic introduced - tasklets, queue handling updated
*
* 2005-07-19 INdT Kernel Team - Alsa port. Creation of new file omap-alsa-dma.c based in
* omap-audio-dma-intfc.c oss file. Support for aic23 codec.
* Removal of buffer handling (Alsa does that), modifications
* in dma handling and port to alsa structures.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/sysrq.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/semaphore.h>
#include <asm/arch/dma.h>
#include "omap-alsa-dma.h"
#include <asm/arch/mcbsp.h>
#include "omap-aic23.h"
#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(ARGS...) printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS)
#define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__)
#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n)
#else
#define DPRINTK( x... )
#define FN_IN
#define FN_OUT(x)
#endif
#define ERR(ARGS...) printk(KERN_ERR "{%s}-ERROR: ", __FUNCTION__);printk(ARGS);
/* Channel Queue Handling macros
* tail always points to the current free entry
* Head always points to the current entry being used
* end is either head or tail
*/
#define AUDIO_QUEUE_INIT(s) s->dma_q_head = s->dma_q_tail = s->dma_q_count = 0;
#define AUDIO_QUEUE_FULL(s) (nr_linked_channels == s->dma_q_count)
#define AUDIO_QUEUE_LAST(s) (1 == s->dma_q_count)
#define AUDIO_QUEUE_EMPTY(s) (0 == s->dma_q_count)
#define __AUDIO_INCREMENT_QUEUE(end) ((end)=((end)+1) % nr_linked_channels)
#define AUDIO_INCREMENT_HEAD(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_head); s->dma_q_count--;
#define AUDIO_INCREMENT_TAIL(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_tail); s->dma_q_count++;
/* DMA buffer fragmentation sizes */
#define MAX_DMA_SIZE 0x1000000 /* todo: sync with alsa */
//#define CUT_DMA_SIZE 0x1000
/* TODO: To be moved to more appropriate location */
#define DCSR_ERROR 0x3
#define DCSR_END_BLOCK (1 << 5)
#define DCSR_SYNC_SET (1 << 6)
#define DCCR_FS (1 << 5)
#define DCCR_PRIO (1 << 6)
#define DCCR_EN (1 << 7)
#define DCCR_AI (1 << 8)
#define DCCR_REPEAT (1 << 9)
/* if 0 the channel works in 3.1 compatible mode*/
#define DCCR_N31COMP (1 << 10)
#define DCCR_EP (1 << 11)
#define DCCR_SRC_AMODE_BIT 12
#define DCCR_SRC_AMODE_MASK (0x3<<12)
#define DCCR_DST_AMODE_BIT 14
#define DCCR_DST_AMODE_MASK (0x3<<14)
#define AMODE_CONST 0x0
#define AMODE_POST_INC 0x1
#define AMODE_SINGLE_INDEX 0x2
#define AMODE_DOUBLE_INDEX 0x3
/**************************** DATA STRUCTURES *****************************************/
static spinlock_t dma_list_lock = SPIN_LOCK_UNLOCKED;
static char nr_linked_channels = 1;
/*********************************** MODULE SPECIFIC FUNCTIONS ***********************/
static void sound_dma_irq_handler(int lch, u16 ch_status, void *data);
static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
u_int dma_size);
static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
u_int dma_size);
static int audio_start_dma_chain(struct audio_stream * s);
/***************************************************************************************
*
* DMA channel requests
*
**************************************************************************************/
static void omap_sound_dma_link_lch(void *data)
{
struct audio_stream *s = (struct audio_stream *) data;
int *chan = s->lch;
int i;
FN_IN;
if (s->linked) {
FN_OUT(1);
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
int nex_chan =
((nr_linked_channels - 1 ==
i) ? chan[0] : chan[i + 1]);
omap_dma_link_lch(cur_chan, nex_chan);
}
s->linked = 1;
FN_OUT(0);
}
int omap_request_sound_dma(int device_id, const char *device_name,
void *data, int **channels)
{
int i, err = 0;
int *chan = NULL;
FN_IN;
if (unlikely((NULL == channels) || (NULL == device_name))) {
BUG();
return -EPERM;
}
/* Try allocate memory for the num channels */
*channels =
(int *) kmalloc(sizeof(int) * nr_linked_channels, GFP_KERNEL);
chan = *channels;
if (NULL == chan) {
ERR("No Memory for channel allocs!\n");
FN_OUT(-ENOMEM);
return -ENOMEM;
}
spin_lock(&dma_list_lock);
for (i = 0; i < nr_linked_channels; i++) {
err =
omap_request_dma(device_id, device_name,
sound_dma_irq_handler, data,
&chan[i]);
/* Handle Failure condition here */
if (err < 0) {
int j;
for (j = 0; j < i; j++) {
omap_free_dma(chan[j]);
}
spin_unlock(&dma_list_lock);
kfree(chan);
*channels = NULL;
ERR("Error in requesting channel %d=0x%x\n", i,
err);
FN_OUT(err);
return err;
}
}
/* Chain the channels together */
if (!cpu_is_omap1510())
omap_sound_dma_link_lch(data);
spin_unlock(&dma_list_lock);
FN_OUT(0);
return 0;
}
/***************************************************************************************
*
* DMA channel requests Freeing
*
**************************************************************************************/
static void omap_sound_dma_unlink_lch(void *data)
{
struct audio_stream *s = (struct audio_stream *) data;
int *chan = s->lch;
int i;
FN_IN;
if (!s->linked) {
FN_OUT(1);
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
int nex_chan =
((nr_linked_channels - 1 ==
i) ? chan[0] : chan[i + 1]);
omap_dma_unlink_lch(cur_chan, nex_chan);
}
s->linked = 0;
FN_OUT(0);
}
int omap_free_sound_dma(void *data, int **channels)
{
int i;
int *chan = NULL;
FN_IN;
if (unlikely(NULL == channels)) {
BUG();
return -EPERM;
}
if (unlikely(NULL == *channels)) {
BUG();
return -EPERM;
}
chan = (*channels);
if (!cpu_is_omap1510())
omap_sound_dma_unlink_lch(data);
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
omap_stop_dma(cur_chan);
omap_free_dma(cur_chan);
}
kfree(*channels);
*channels = NULL;
FN_OUT(0);
return 0;
}
/***************************************************************************************
*
* Stop all the DMA channels of the stream
*
**************************************************************************************/
void omap_audio_stop_dma(struct audio_stream *s)
{
int *chan = s->lch;
int i;
FN_IN;
if (unlikely(NULL == chan)) {
BUG();
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
omap_stop_dma(cur_chan);
}
s->started = 0;
FN_OUT(0);
return;
}
/***************************************************************************************
*
* Clear any pending transfers
*
**************************************************************************************/
void omap_clear_sound_dma(struct audio_stream * s)
{
FN_IN;
omap_clear_dma(s->lch[s->dma_q_head]);
FN_OUT(0);
return;
}
/*********************************** MODULE FUNCTIONS DEFINTIONS ***********************/
#ifdef OMAP1610_MCBSP1_BASE
#undef OMAP1610_MCBSP1_BASE
#endif
#define OMAP1610_MCBSP1_BASE 0xE1011000
/***************************************************************************************
*
* DMA related functions
*
**************************************************************************************/
static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
u_int dma_size)
{
int dt = 0x1; /* data type 16 */
int cen = 32; /* Stereo */
int cfn = dma_size / (2 * cen);
FN_IN;
omap_set_dma_dest_params(channel, 0x05, 0x00,
(OMAP1610_MCBSP1_BASE + 0x806));
omap_set_dma_src_params(channel, 0x00, 0x01, dma_ptr);
omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00);
FN_OUT(0);
return 0;
}
static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
u_int dma_size)
{
int dt = 0x1; /* data type 16 */
int cen = 32; /* stereo */
int cfn = dma_size / (2 * cen);
FN_IN;
omap_set_dma_src_params(channel, 0x05, 0x00,
(OMAP1610_MCBSP1_BASE + 0x802));
omap_set_dma_dest_params(channel, 0x00, 0x01, dma_ptr);
omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00);
FN_OUT(0);
return 0;
}
static int audio_start_dma_chain(struct audio_stream *s)
{
int channel = s->lch[s->dma_q_head];
FN_IN;
if (!s->started) {
omap_start_dma(channel);
s->started = 1;
}
/* else the dma itself will progress forward with out our help */
FN_OUT(0);
return 0;
}
/* Start DMA -
* Do the initial set of work to initialize all the channels as required.
* We shall then initate a transfer
*/
int omap_start_sound_dma(struct audio_stream *s, dma_addr_t dma_ptr,
u_int dma_size)
{
int ret = -EPERM;
FN_IN;
if (unlikely(dma_size > MAX_DMA_SIZE)) {
ERR("DmaSoundDma: Start: overflowed %d-%d\n", dma_size,
MAX_DMA_SIZE);
return -EOVERFLOW;
}
//if (AUDIO_QUEUE_FULL(s)) {
// ret = -2;
// goto sound_out;
//}
if (s->stream_id == SNDRV_PCM_STREAM_PLAYBACK) {
/*playback */
ret =
audio_set_dma_params_play(s->lch[s->dma_q_tail],
dma_ptr, dma_size);
} else {
ret =
audio_set_dma_params_capture(s->lch[s->dma_q_tail],
dma_ptr, dma_size);
}
if (ret != 0) {
ret = -3; /* indicate queue full */
goto sound_out;
}
AUDIO_INCREMENT_TAIL(s);
ret = audio_start_dma_chain(s);
if (ret) {
ERR("dma start failed");
}
sound_out:
FN_OUT(ret);
return ret;
}
/*
* ISRs have to be short and smart..
* Here we call alsa handling, after some error checking
*/
static void sound_dma_irq_handler(int sound_curr_lch, u16 ch_status,
void *data)
{
int dma_status = ch_status;
struct audio_stream *s = (struct audio_stream *) data;
FN_IN;
/*
* some register checkings
*/
DPRINTK("lch=%d,status=0x%x, dma_status=%d, data=%p\n",
sound_curr_lch, ch_status, dma_status, data);
if (dma_status & (DCSR_ERROR)) {
omap_writew(omap_readw(OMAP_DMA_CCR(sound_curr_lch)) &
~DCCR_EN, OMAP_DMA_CCR(sound_curr_lch));
ERR("DCSR_ERROR!\n");
FN_OUT(-1);
return;
}
if (ch_status & DCSR_END_BLOCK)
audio_dma_callback(s);
FN_OUT(0);
return;
}
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION
("Common DMA handling for Audio driver on OMAP processors");
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(omap_start_sound_dma);
EXPORT_SYMBOL(omap_clear_sound_dma);
EXPORT_SYMBOL(omap_request_sound_dma);
EXPORT_SYMBOL(omap_free_sound_dma);
EXPORT_SYMBOL(omap_audio_stop_dma);
/*
* linux/sound/arm/omap-alsa-dma.h
*
* Common audio DMA handling for the OMAP processors
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History:
*
*
* 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms
*
* 2005/07/25 INdT Kernel Team - Renamed to omap-alsa-dma.h. Ported to Alsa.
*/
#ifndef __OMAP_AUDIO_ALSA_DMA_H
#define __OMAP_AUDIO_ALSA_DMA_H
/************************** INCLUDES *************************************/
#include "omap-aic23.h"
/************************** GLOBAL MACROS *************************************/
/* Provide the Macro interfaces common across platforms */
#define DMA_REQUEST(e,s, cb) {e=omap_request_sound_dma(s->dma_dev, s->id, s, &s->lch);}
#define DMA_FREE(s) omap_free_sound_dma(s, &s->lch)
#define DMA_CLEAR(s) omap_clear_sound_dma(s)
/************************** GLOBAL DATA STRUCTURES *********************************/
typedef void (*dma_callback_t) (int lch, u16 ch_status, void *data);
/**************** ARCH SPECIFIC FUNCIONS *******************************************/
void omap_clear_sound_dma(struct audio_stream * s);
int omap_request_sound_dma(int device_id, const char *device_name,
void *data, int **channels);
int omap_free_sound_dma(void *data, int **channels);
int omap_start_sound_dma(struct audio_stream *s, dma_addr_t dma_ptr,
u_int dma_size);
void omap_audio_stop_dma(struct audio_stream *s);
#endif
/*
* sound/arm/omap-alsa-mixer.c
*
* Alsa Driver Mixer for generic codecs for omap boards
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Written by David Cohen, Daniel Petrini
* {david.cohen, daniel.petrini}@indt.org.br
*
* Based on es1688_lib.c,
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
* History:
*
* 2005-08-02 INdT Kernel Team - Alsa mixer driver for omap osk. Creation of new
* file omap-alsa-mixer.c. Initial version
* with aic23 codec for osk5912
*/
#include <linux/config.h>
#include <sound/driver.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <asm/hardware.h>
#include <asm/mach-types.h>
#include <asm/arch/dma.h>
#include <asm/arch/aic23.h>
#include "omap-aic23.h"
#include <sound/initval.h>
#include <sound/control.h>
MODULE_AUTHOR("David Cohen, Daniel Petrini - INdT");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("OMAP Alsa mixer driver for ALSA");
/*
* Codec dependent region
*/
/* Codec AIC23 */
#ifdef CONFIG_SENSORS_TLV320AIC23
extern __inline__ void audio_aic23_write(u8, u16);
#define MIXER_NAME "Mixer AIC23"
#define SND_OMAP_WRITE(reg, val) audio_aic23_write(reg, val)
#endif
/* Callback Functions */
#define OMAP_BOOL(xname, xindex, reg, reg_index, mask, invert) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.index = xindex, \
.info = snd_omap_info_bool, \
.get = snd_omap_get_bool, \
.put = snd_omap_put_bool, \
.private_value = reg | (reg_index << 8) | (invert << 10) | (mask << 12) \
}
#define OMAP_MUX(xname, reg, reg_index, mask) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.info = snd_omap_info_mux, \
.get = snd_omap_get_mux, \
.put = snd_omap_put_mux, \
.private_value = reg | (reg_index << 8) | (mask << 10) \
}
#define OMAP_SINGLE(xname, xindex, reg, reg_index, reg_val, mask) \
{\
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.index = xindex, \
.info = snd_omap_info_single, \
.get = snd_omap_get_single, \
.put = snd_omap_put_single, \
.private_value = reg | (reg_val << 8) | (reg_index << 16) | (mask << 18) \
}
#define OMAP_DOUBLE(xname, xindex, left_reg, right_reg, reg_index, mask) \
{\
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.index = xindex, \
.info = snd_omap_info_double, \
.get = snd_omap_get_double, \
.put = snd_omap_put_double, \
.private_value = left_reg | (right_reg << 8) | (reg_index << 16) | (mask << 18) \
}
/* Local Registers */
enum snd_device_index {
PCM_INDEX = 0,
LINE_INDEX,
AAC_INDEX, /* Analog Audio Control: reg = l_reg */
};
struct {
u16 l_reg;
u16 r_reg;
u8 sw;
} omap_regs[3];
#ifdef CONFIG_PM
struct {
u16 l_reg;
u16 r_reg;
u8 sw;
} omap_pm_regs[3];
#endif
u16 snd_sidetone[6] = {
SIDETONE_18,
SIDETONE_12,
SIDETONE_9,
SIDETONE_6,
SIDETONE_0,
0
};
/* Begin Bool Functions */
static int snd_omap_info_bool(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int snd_omap_get_bool(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
int mic_index = (kcontrol->private_value >> 8) & 0x03;
u16 mask = (kcontrol->private_value >> 12) & 0xff;
int invert = (kcontrol->private_value >> 10) & 0x03;
if (invert)
ucontrol->value.integer.value[0] = (omap_regs[mic_index].l_reg & mask) ? 0 : 1;
else
ucontrol->value.integer.value[0] = (omap_regs[mic_index].l_reg & mask) ? 1 : 0;
return 0;
}
static int snd_omap_put_bool(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
int mic_index = (kcontrol->private_value >> 8) & 0x03;
u16 mask = (kcontrol->private_value >> 12) & 0xff;
u16 reg = kcontrol->private_value & 0xff;
int invert = (kcontrol->private_value >> 10) & 0x03;
int changed = 1;
if (ucontrol->value.integer.value[0]) /* XOR */
if (invert)
omap_regs[mic_index].l_reg &= ~mask;
else
omap_regs[mic_index].l_reg |= mask;
else
if (invert)
omap_regs[mic_index].l_reg |= mask;
else
omap_regs[mic_index].l_reg &= ~mask;
SND_OMAP_WRITE(reg, omap_regs[mic_index].l_reg);
return changed;
}
/* End Bool Functions */
/* Begin Mux Functions */
static int snd_omap_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
/* Mic = 0
* Line = 1 */
static char *texts[2] = { "Mic", "Line" };
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 snd_omap_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
u16 mask = (kcontrol->private_value >> 10) & 0xff;
int mux_index = (kcontrol->private_value >> 8) & 0x03;
ucontrol->value.enumerated.item[0] = (omap_regs[mux_index].l_reg & mask) ? 0 /* Mic */ : 1 /* Line */;
return 0;
}
static int snd_omap_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
u16 reg = kcontrol->private_value & 0xff;
u16 mask = (kcontrol->private_value >> 10) & 0xff;
int mux_index = (kcontrol->private_value >> 8) & 0x03;
int changed = 1;
if (!ucontrol->value.integer.value[0])
omap_regs[mux_index].l_reg |= mask; /* AIC23: Mic */
else
omap_regs[mux_index].l_reg &= ~mask; /* AIC23: Line */
SND_OMAP_WRITE(reg, omap_regs[mux_index].l_reg);
return changed;
}
/* End Mux Functions */
/* Begin Single Functions */
static int snd_omap_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
int mask = (kcontrol->private_value >> 18) & 0xff;
int reg_val = (kcontrol->private_value >> 8) & 0xff;
uinfo->type = mask ? SNDRV_CTL_ELEM_TYPE_INTEGER : SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = reg_val-1;
return 0;
}
static int snd_omap_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
u16 reg_val = (kcontrol->private_value >> 8) & 0xff;
ucontrol->value.integer.value[0] = snd_sidetone[reg_val];
return 0;
}
static int snd_omap_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
u16 reg_index = (kcontrol->private_value >> 16) & 0x03;
u16 mask = (kcontrol->private_value >> 18) & 0x1ff;
u16 reg = kcontrol->private_value & 0xff;
u16 reg_val = (kcontrol->private_value >> 8) & 0xff;
int changed = 0;
/* Volume */
if ((omap_regs[reg_index].l_reg != (ucontrol->value.integer.value[0] & mask)))
{
changed = 1;
omap_regs[reg_index].l_reg &= ~mask;
omap_regs[reg_index].l_reg |= snd_sidetone[ucontrol->value.integer.value[0]];
snd_sidetone[reg_val] = ucontrol->value.integer.value[0];
SND_OMAP_WRITE(reg, omap_regs[reg_index].l_reg);
}
else
changed = 0;
return changed;
}
/* End Single Functions */
/* Begin Double Functions */
static int snd_omap_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
{
/* mask == 0 : Switch
* mask != 0 : Volume */
int mask = (kcontrol->private_value >> 18) & 0xff;
uinfo->type = mask ? SNDRV_CTL_ELEM_TYPE_INTEGER : SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = mask ? 2 : 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask ? mask : 1;
return 0;
}
static int snd_omap_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
/* mask == 0 : Switch
* mask != 0 : Volume */
int mask = (kcontrol->private_value >> 18) & 0xff;
int vol_index = (kcontrol->private_value >> 16) & 0x03;
if (!mask)
/* Switch */
ucontrol->value.integer.value[0] = omap_regs[vol_index].sw;
else
{
/* Volume */
ucontrol->value.integer.value[0] = omap_regs[vol_index].l_reg;
ucontrol->value.integer.value[1] = omap_regs[vol_index].r_reg;
}
return 0;
}
static int snd_omap_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
/* mask == 0 : Switch
* mask != 0 : Volume */
int vol_index = (kcontrol->private_value >> 16) & 0x03;
int mask = (kcontrol->private_value >> 18) & 0xff;
int left_reg = kcontrol->private_value & 0xff;
int right_reg = (kcontrol->private_value >> 8) & 0xff;
int changed = 0;
if (!mask)
{
/* Switch */
if (!ucontrol->value.integer.value[0])
{
SND_OMAP_WRITE(left_reg, 0x00);
SND_OMAP_WRITE(right_reg, 0x00);
}
else
{
SND_OMAP_WRITE(left_reg, omap_regs[vol_index].l_reg);
SND_OMAP_WRITE(right_reg, omap_regs[vol_index].r_reg);
}
changed = 1;
omap_regs[vol_index].sw = ucontrol->value.integer.value[0];
}
else
{
/* Volume */
if ((omap_regs[vol_index].l_reg != (ucontrol->value.integer.value[0] & mask)) ||
(omap_regs[vol_index].r_reg != (ucontrol->value.integer.value[1] & mask)))
{
changed = 1;
omap_regs[vol_index].l_reg &= ~mask;
omap_regs[vol_index].r_reg &= ~mask;
omap_regs[vol_index].l_reg |= (ucontrol->value.integer.value[0] & mask);
omap_regs[vol_index].r_reg |= (ucontrol->value.integer.value[1] & mask);
if (omap_regs[vol_index].sw)
{
/* write to registers only if sw is actived */
SND_OMAP_WRITE(left_reg, omap_regs[vol_index].l_reg);
SND_OMAP_WRITE(right_reg, omap_regs[vol_index].r_reg);
}
}
else
changed = 0;
}
return changed;
}
/* End Double Functions */
static snd_kcontrol_new_t snd_omap_controls[] = {
OMAP_DOUBLE("PCM Playback Switch", 0, LEFT_CHANNEL_VOLUME_ADDR, RIGHT_CHANNEL_VOLUME_ADDR,
PCM_INDEX, 0x00),
OMAP_DOUBLE("PCM Playback Volume", 0, LEFT_CHANNEL_VOLUME_ADDR, RIGHT_CHANNEL_VOLUME_ADDR,
PCM_INDEX, OUTPUT_VOLUME_MASK),
OMAP_BOOL("Line Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX, BYPASS_ON, 0),
OMAP_DOUBLE("Line Capture Switch", 0, LEFT_LINE_VOLUME_ADDR, RIGHT_LINE_VOLUME_ADDR,
LINE_INDEX, 0x00),
OMAP_DOUBLE("Line Capture Volume", 0, LEFT_LINE_VOLUME_ADDR, RIGHT_LINE_VOLUME_ADDR,
LINE_INDEX, INPUT_VOLUME_MASK),
OMAP_BOOL("Mic Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX, STE_ENABLED, 0),
OMAP_SINGLE("Mic Playback Volume", 0, ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX, 5, SIDETONE_MASK),
OMAP_BOOL("Mic Capture Switch", 0, ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX, MICM_MUTED, 1),
OMAP_BOOL("Mic Booster Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX, MICB_20DB, 0),
OMAP_MUX("Capture Source", ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX, INSEL_MIC),
};
void snd_omap_init_mixer(void)
{
u16 vol_reg;
/* Line's default values */
omap_regs[LINE_INDEX].l_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK;
omap_regs[LINE_INDEX].r_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK;
omap_regs[LINE_INDEX].sw = 0;
SND_OMAP_WRITE(LEFT_LINE_VOLUME_ADDR, DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK);
SND_OMAP_WRITE(RIGHT_LINE_VOLUME_ADDR, DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK);
/* Analog Audio Control's default values */
omap_regs[AAC_INDEX].l_reg = DEFAULT_ANALOG_AUDIO_CONTROL;
/* Headphone's default values */
vol_reg = LZC_ON;
vol_reg &= ~OUTPUT_VOLUME_MASK;
vol_reg |= DEFAULT_OUTPUT_VOLUME;
omap_regs[PCM_INDEX].l_reg = DEFAULT_OUTPUT_VOLUME;
omap_regs[PCM_INDEX].r_reg = DEFAULT_OUTPUT_VOLUME;
omap_regs[PCM_INDEX].sw = 1;
SND_OMAP_WRITE(LEFT_CHANNEL_VOLUME_ADDR, vol_reg);
SND_OMAP_WRITE(RIGHT_CHANNEL_VOLUME_ADDR, vol_reg);
}
#ifdef CONFIG_PM
void snd_omap_suspend_mixer(void)
{
/* Saves current values to wake-up correctly */
omap_pm_regs[LINE_INDEX].l_reg = omap_regs[LINE_INDEX].l_reg;
omap_pm_regs[LINE_INDEX].r_reg = omap_regs[LINE_INDEX].l_reg;
omap_pm_regs[LINE_INDEX].sw = omap_regs[LINE_INDEX].sw;
omap_pm_regs[AAC_INDEX].l_reg = omap_regs[AAC_INDEX].l_reg;
omap_pm_regs[PCM_INDEX].l_reg = omap_regs[PCM_INDEX].l_reg;
omap_pm_regs[PCM_INDEX].r_reg = omap_regs[PCM_INDEX].r_reg;
omap_pm_regs[PCM_INDEX].sw = omap_regs[PCM_INDEX].sw;
}
void snd_omap_resume_mixer(void)
{
/* Line's saved values */
omap_regs[LINE_INDEX].l_reg = omap_pm_regs[LINE_INDEX].l_reg;
omap_regs[LINE_INDEX].r_reg = omap_pm_regs[LINE_INDEX].l_reg;
omap_regs[LINE_INDEX].sw = omap_pm_regs[LINE_INDEX].sw;
SND_OMAP_WRITE(LEFT_LINE_VOLUME_ADDR, omap_pm_regs[LINE_INDEX].l_reg);
SND_OMAP_WRITE(RIGHT_LINE_VOLUME_ADDR, omap_pm_regs[LINE_INDEX].l_reg);
/* Analog Audio Control's saved values */
omap_regs[AAC_INDEX].l_reg = omap_pm_regs[AAC_INDEX].l_reg;
SND_OMAP_WRITE(ANALOG_AUDIO_CONTROL_ADDR, omap_regs[AAC_INDEX].l_reg);
/* Headphone's saved values */
omap_regs[PCM_INDEX].l_reg = omap_pm_regs[PCM_INDEX].l_reg;
omap_regs[PCM_INDEX].r_reg = omap_pm_regs[PCM_INDEX].r_reg;
omap_regs[PCM_INDEX].sw = omap_pm_regs[PCM_INDEX].sw;
SND_OMAP_WRITE(LEFT_CHANNEL_VOLUME_ADDR, omap_pm_regs[PCM_INDEX].l_reg);
SND_OMAP_WRITE(RIGHT_CHANNEL_VOLUME_ADDR, omap_pm_regs[PCM_INDEX].r_reg);
}
#endif
int snd_omap_mixer(struct snd_card_omap_aic23 *chip)
{
snd_card_t *card;
unsigned int idx;
int err;
snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
card = chip->card;
strcpy(card->mixername, MIXER_NAME);
/* Registering alsa mixer controls */
for (idx = 0; idx < ARRAY_SIZE(snd_omap_controls); idx++)
if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_omap_controls[idx], chip))) < 0)
return err;
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment