I had to make two little change to make it compile: snd_ep = msm_rpc_connect_compatible(snd_rpc_ids.prog, became: snd_ep = msm_rpc_connect(snd_rpc_ids.prog, and I also changed snd_rpc_ids.vers(with ifdefs) to a known and working magick number: I also had to change from ARCH_MSM_ARM11 to ARCH_MSM in the configuration it still has serious runtime problems such as: *Can produce kernel oops under theses conditions: start alsamixer and if the second bar is on 0 or 4, so it can play music with aplay,then increase the routing alsamixer bar to the max. Then decrease the routing bar to 4 or less Then It may have a null pointer problem That bug could be because it tries to route to route to speakers and handset at the same time(SND_DEVICE_HEADSET_AND_SPEAKER in android): that is to say it could be the same bug than here: http://gitorious.org/replicant/msm7k/commit/370d37a088368ca8cc478e76c928a1ce6589495e but I need time to verify that *can pannick(reboots the phone) if you send things to /dev/dsp when the oss emulation is activated *only aplay works(mplayer,gstreamer don't work) for mplayer an ioctl didn't return...so it get stuck before playing. The explanation of the bug can be found here: http://mailman.alsa-project.org/pipermail/alsa-devel/2009-November/022697.html Note the following things: *this patch depends on,and doesn't contain arch/arm/mach-msm/qdsp5 *I removed the support for more recents chips because of code-size issue in a mailing list Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo@xxxxxxxxxx> --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/msm/Kconfig | 23 ++ sound/soc/msm/Makefile | 11 + sound/soc/msm/msm-dai.c | 143 ++++++++++ sound/soc/msm/msm-pcm.c | 643 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/msm/msm-pcm.h | 200 ++++++++++++++ sound/soc/msm/msm7201.c | 337 ++++++++++++++++++++++++ sound/soc/msm/msm7k-pcm.c | 574 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1933 insertions(+), 0 deletions(-) create mode 100644 sound/soc/msm/Kconfig create mode 100644 sound/soc/msm/Makefile create mode 100644 sound/soc/msm/msm-dai.c create mode 100644 sound/soc/msm/msm-pcm.c create mode 100644 sound/soc/msm/msm-pcm.h create mode 100644 sound/soc/msm/msm7201.c create mode 100644 sound/soc/msm/msm7k-pcm.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index ef025c6..4b1a48f 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -32,6 +32,7 @@ source "sound/soc/omap/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/msm/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 86a9b1f..ea754e5 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += msm/ diff --git a/sound/soc/msm/Kconfig b/sound/soc/msm/Kconfig new file mode 100644 index 0000000..70f2f76 --- /dev/null +++ b/sound/soc/msm/Kconfig @@ -0,0 +1,23 @@ +menu "MSM SoC Audio support" + +config SND_MSM_SOC + tristate "SoC Audio for the MSM series chips" + depends on ARCH_MSM && SND_SOC + select MSM_ADSP + help + To add support for ALSA PCM driver for MSM board. + +config SND_MSM_DAI_SOC + tristate "SoC CPU/CODEC DAI for the MSM chip" + depends on SND_MSM_SOC || SND_QSD_SOC + help + To add support for ALSA PCM driver for MSM board. + +config SND_MSM_SOC_MSM7K + tristate "SoC Audio support for MSM7K" + depends on SND_MSM_SOC + help + To add support for SoC audio on msm7k for msm72x1 or msm7x27 + + +endmenu diff --git a/sound/soc/msm/Makefile b/sound/soc/msm/Makefile new file mode 100644 index 0000000..d2b38d3 --- /dev/null +++ b/sound/soc/msm/Makefile @@ -0,0 +1,11 @@ +# MSM CPU/CODEC DAI Support +snd-soc-msm-dai-objs := msm-dai.o +obj-$(CONFIG_SND_MSM_DAI_SOC) += snd-soc-msm-dai.o + +# MSM Platform Support +snd-soc-msm-objs := msm-pcm.o msm7k-pcm.o +obj-$(CONFIG_SND_MSM_SOC) += snd-soc-msm.o + +# MSM Machine Support +snd-soc-msm7k-objs := msm7201.o +obj-$(CONFIG_SND_MSM_SOC_MSM7K) += snd-soc-msm7k.o diff --git a/sound/soc/msm/msm-dai.c b/sound/soc/msm/msm-dai.c new file mode 100644 index 0000000..564e7fe --- /dev/null +++ b/sound/soc/msm/msm-dai.c @@ -0,0 +1,143 @@ +/* sound/soc/msm/msm-dai.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * Derived from msm-pcm.c and msm7201.c. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include "msm-pcm.h" + +struct snd_soc_dai msm_dais[] = { +{ + .name = "CODEC_DAI", + .playback = { + .stream_name = "Playback", + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .formats = USE_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rates = USE_RATE, + .formats = USE_FORMATS, + }, +}, +{ + .name = "CPU_DAI", + .id = 0, + .playback = { + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .formats = USE_FORMATS, + }, + .capture = { + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rates = USE_RATE, + .formats = USE_FORMATS, + }, +}, +}; +EXPORT_SYMBOL_GPL(msm_dais); + +int msm_pcm_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_soc_codec *codec; + int ret; + + struct snd_soc_device *socdev = platform_get_drvdata(devptr); + + printk(KERN_ERR "msm_soc: create pcms\n"); + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + codec->name = "MSM-CARD"; + codec->owner = THIS_MODULE; + socdev->codec = codec; + mutex_init(&codec->mutex); + + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "msm_soc: failed to create pcms\n"); + goto __nopcm; + } + + card = socdev->codec->card; + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "msm_soc: failed to register card\n"); + goto __nodev; + } + + return 0; + +__nodev: + snd_soc_free_pcms(socdev); +__nopcm: + kfree(codec); + return ret; +} + +struct snd_soc_codec_device soc_codec_dev_msm = { + .probe = msm_pcm_probe, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_msm); + + +static int __init msm_dai_init(void) +{ + return snd_soc_register_dais(msm_dais, ARRAY_SIZE(msm_dais)); +} + +static void __exit msm_dai_exit(void) +{ + snd_soc_unregister_dais(msm_dais, ARRAY_SIZE(msm_dais)); +} + +module_init(msm_dai_init); +module_exit(msm_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm.c b/sound/soc/msm/msm-pcm.c new file mode 100644 index 0000000..90e200d --- /dev/null +++ b/sound/soc/msm/msm-pcm.c @@ -0,0 +1,643 @@ +/* sound/soc/msm/msm-pcm.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <asm/dma.h> +#include <linux/dma-mapping.h> + +#include "msm-pcm.h" + +#define MAX_DATA_SIZE 496 +#define AUDPP_ALSA_DECODER (-1) + +#define DB_TABLE_INDEX (50) + +#define audio_send_queue_recbs(prtd, cmd, len) \ + msm_adsp_write(prtd->audrec, QDSP_uPAudRecBitStreamQueue, cmd, len) +#define audio_send_queue_rec(prtd, cmd, len) \ + msm_adsp_write(prtd->audrec, QDSP_uPAudRecCmdQueue, cmd, len) + +int intcnt; +static int audio_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len); + +struct audio_frame { + uint16_t count_low; + uint16_t count_high; + uint16_t bytes; + uint16_t unknown; + unsigned char samples[]; +} __attribute__ ((packed)); + +/* Table contains dB to raw value mapping */ +static const unsigned decoder_db_table[] = { + + 31 , /* -50 dB */ + 35 , 39 , 44 , 50 , 56 , + 63 , 70 , 79 , 89 , 99 , + 112 , 125 , 141 , 158 , 177 , + 199 , 223 , 251 , 281 , 316 , + 354 , 398 , 446 , 501 , 562 , + 630 , 707 , 794 , 891 , 999 , + 1122 , 1258 , 1412 , 1584 , 1778 , + 1995 , 2238 , 2511 , 2818 , 3162 , + 3548 , 3981 , 4466 , 5011 , 5623 , + 6309 , 7079 , 7943 , 8912 , 10000 , + 11220 , 12589 , 14125 , 15848 , 17782 , + 19952 , 22387 , 25118 , 28183 , 31622 , + 35481 , 39810 , 44668 , 50118 , 56234 , + 63095 , 70794 , 79432 , 89125 , 100000 , + 112201 , 125892 , 141253 , 158489 , 177827 , + 199526 , 223872 , 251188 , 281838 , 316227 , + 354813 , 398107 , 446683 , 501187 , 562341 , + 630957 , 707945 , 794328 , 891250 , 1000000 , + 1122018 , 1258925 , 1412537 , 1584893 , 1778279 , + 1995262 , 2238721 , 2511886 , 2818382 , 3162277 , + 3548133 /* 51 dB */ + +}; + +static unsigned compute_db_raw(int db) +{ + unsigned reg_val = 0; /* Computed result for correspondent db */ + /* Check if the given db is out of range */ + if (db <= MIN_DB) + return 0; + else if (db > MAX_DB) + db = MAX_DB; /* If db is too high then set to max */ + reg_val = decoder_db_table[DB_TABLE_INDEX+db]; + return reg_val; +} + +int msm_audio_volume_update(unsigned id, + int volume, int pan) +{ + unsigned vol_raw; + + vol_raw = compute_db_raw(volume); + printk(KERN_INFO "volume: %8x vol_raw: %8x \n", volume, vol_raw); + return audpp_set_volume_and_pan(id, vol_raw, pan); +} +EXPORT_SYMBOL(msm_audio_volume_update); + +void alsa_dsp_event(void *data, unsigned id, uint16_t *msg) +{ + struct msm_audio *prtd = data; + struct buffer *frame; + unsigned long flag; + + switch (id) { + case AUDPP_MSG_STATUS_MSG: + break; + case AUDPP_MSG_SPA_BANDS: + break; + case AUDPP_MSG_HOST_PCM_INTF_MSG:{ + unsigned id = msg[2]; + unsigned idx = msg[3] - 1; + if (id != AUDPP_MSG_HOSTPCM_ID_ARM_RX) { + printk(KERN_ERR "bogus id\n"); + break; + } + if (idx > 1) { + printk(KERN_ERR "bogus buffer idx\n"); + break; + } + /* Update with actual sent buffer size */ + if (prtd->out[idx].used != BUF_INVALID_LEN) + prtd->pcm_irq_pos += prtd->out[idx].used; + + if (prtd->pcm_irq_pos > prtd->pcm_size) + prtd->pcm_irq_pos = prtd->pcm_count; + + if (prtd->ops->playback) + prtd->ops->playback(prtd); + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + if (prtd->running) { + prtd->out[idx].used = 0; + frame = prtd->out + prtd->out_tail; + if (frame->used) { + audio_dsp_send_buffer(prtd, + prtd->out_tail, + frame->used); + prtd->out_tail ^= 1; + } else { + prtd->out_needed++; + } + wake_up(&the_locks.write_wait); + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + break; + } + case AUDPP_MSG_PCMDMAMISSED: + printk(KERN_ERR "alsa_dsp_event: PCMDMAMISSED %d\n", msg[0]); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + prtd->out_needed = 0; + prtd->running = 1; + audio_dsp_out_enable(prtd, 1); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + prtd->running = 0; + } else { + printk(KERN_ERR "alsa_dsp_event:CFG_MSG=%d\n", msg[0]); + } + break; + case EVENT_MSG_ID: + printk(KERN_INFO"alsa_dsp_event: arm9 event\n"); + break; + default: + printk(KERN_ERR "alsa_dsp_event: UNKNOWN (%d)\n", id); + } +} + +void alsa_audpre_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + uint16_t msg[MAX_DATA_SIZE/2]; + + if (len > MAX_DATA_SIZE) { + printk(KERN_ERR"audpre: event too large(%d bytes)\n", len); + return; + } + getevent(msg, len); + + switch (id) { + case AUDPREPROC_MSG_CMD_CFG_DONE_MSG: + break; + case AUDPREPROC_MSG_ERROR_MSG_ID: + printk(KERN_ERR "audpre: err_index %d\n", msg[0]); + break; + case EVENT_MSG_ID: + printk(KERN_INFO"audpre: arm9 event\n"); + break; + default: + printk(KERN_ERR "audpre: unknown event %d\n", id); + } +} + +void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct msm_audio *prtd = data; + unsigned long flag; + uint16_t msg[MAX_DATA_SIZE/2]; + + if (len > MAX_DATA_SIZE) { + printk(KERN_ERR"audrec: event/msg too large(%d bytes)\n", len); + return; + } + getevent(msg, len); + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: + if (msg[0] & AUDREC_MSG_CFG_DONE_TYPE_0_UPDATE) { + if (msg[0] & AUDREC_MSG_CFG_DONE_TYPE_0_ENA) + audrec_encoder_config(prtd); + else + prtd->running = 0; + } + break; + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG:{ + prtd->running = 1; + break; + } + case AUDREC_MSG_FATAL_ERR_MSG: + printk(KERN_ERR "audrec: ERROR %x\n", msg[0]); + break; + case AUDREC_MSG_PACKET_READY_MSG: + alsa_get_dsp_frames(prtd); + ++intcnt; + if (prtd->channel_mode == 1) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } else if ((prtd->channel_mode == 0) && (intcnt % 2 == 0)) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } + break; + case EVENT_MSG_ID: + printk(KERN_INFO"audrec: arm9 event\n"); + break; + default: + printk(KERN_ERR "audrec: unknown event %d\n", id); + } +} + +struct msm_adsp_ops aud_pre_adsp_ops = { + .event = alsa_audpre_dsp_event, +}; + +struct msm_adsp_ops aud_rec_adsp_ops = { + .event = audrec_dsp_event, +}; + +int alsa_adsp_configure(struct msm_audio *prtd) +{ + int ret, i; + + if (prtd->dir == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->data = prtd->playback_substream->dma_buffer.area; + prtd->phys = prtd->playback_substream->dma_buffer.addr; + } + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) { + prtd->data = prtd->capture_substream->dma_buffer.area; + prtd->phys = prtd->capture_substream->dma_buffer.addr; + } + if (!prtd->data) { + ret = -ENOMEM; + goto err1; + } + + ret = audmgr_open(&prtd->audmgr); + if (ret) + goto err2; + if (prtd->dir == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->out_buffer_size = PLAYBACK_DMASZ; + prtd->out_sample_rate = 44100; + prtd->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + prtd->out_weight = 100; + + prtd->out[0].data = prtd->data + 0; + prtd->out[0].addr = prtd->phys + 0; + prtd->out[0].size = BUFSZ; + prtd->out[1].data = prtd->data + BUFSZ; + prtd->out[1].addr = prtd->phys + BUFSZ; + prtd->out[1].size = BUFSZ; + } + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) { + prtd->samp_rate = RPC_AUD_DEF_SAMPLE_RATE_44100; + prtd->samp_rate_index = AUDREC_CMD_SAMP_RATE_INDX_44100; + prtd->channel_mode = AUDREC_CMD_STEREO_MODE_STEREO; + prtd->buffer_size = STEREO_DATA_SIZE; + prtd->type = AUDREC_CMD_TYPE_0_INDEX_WAV; + prtd->tx_agc_cfg.cmd_id = AUDPREPROC_CMD_CFG_AGC_PARAMS; + prtd->ns_cfg.cmd_id = AUDPREPROC_CMD_CFG_NS_PARAMS; + prtd->iir_cfg.cmd_id = + AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS; + + ret = msm_adsp_get("AUDPREPROCTASK", + &prtd->audpre, &aud_pre_adsp_ops, prtd); + if (ret) + goto err3; + ret = msm_adsp_get("AUDRECTASK", + &prtd->audrec, &aud_rec_adsp_ops, prtd); + if (ret) { + msm_adsp_put(prtd->audpre); + goto err3; + } + prtd->dsp_cnt = 0; + prtd->in_head = 0; + prtd->in_tail = 0; + prtd->in_count = 0; + for (i = 0; i < FRAME_NUM; i++) { + prtd->in[i].size = 0; + prtd->in[i].read = 0; + } + } + + return 0; + +err3: + audmgr_close(&prtd->audmgr); + +err2: + prtd->data = NULL; +err1: + return ret; +} +EXPORT_SYMBOL(alsa_adsp_configure); + +int alsa_audio_configure(struct msm_audio *prtd) +{ + struct audmgr_config cfg; + int rc; + + if (prtd->enabled) + return 0; + + /* refuse to start if we're not ready with first buffer */ + if (!prtd->out[0].used) + return -EIO; + + cfg.tx_rate = 0; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_HOST_PCM; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + rc = audmgr_enable(&prtd->audmgr, &cfg); + if (rc < 0) + return rc; + + if (audpp_enable(AUDPP_ALSA_DECODER, alsa_dsp_event, prtd)) { + printk(KERN_ERR "audio: audpp_enable() failed\n"); + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + + prtd->enabled = 1; + return 0; +} +EXPORT_SYMBOL(alsa_audio_configure); + +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + int rc = 0; + + mutex_lock(&the_locks.write_lock); + while (count > 0) { + frame = prtd->out + prtd->out_head; + rc = wait_event_interruptible(the_locks.write_wait, + (frame->used == 0) + || (prtd->stopped)); + if (rc < 0) + break; + if (prtd->stopped) { + rc = -EBUSY; + break; + } + xfer = count > frame->size ? frame->size : count; + if (copy_from_user(frame->data, buf, xfer)) { + rc = -EFAULT; + break; + } + frame->used = xfer; + prtd->out_head ^= 1; + count -= xfer; + buf += xfer; + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + frame = prtd->out + prtd->out_tail; + if (frame->used && prtd->out_needed) { + audio_dsp_send_buffer(prtd, prtd->out_tail, + frame->used); + prtd->out_tail ^= 1; + prtd->out_needed--; + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + } + mutex_unlock(&the_locks.write_lock); + if (buf > start) + return buf - start; + return rc; +} +EXPORT_SYMBOL(alsa_send_buffer); + +int alsa_audio_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + mutex_lock(&the_locks.lock); + prtd->enabled = 0; + audio_dsp_out_enable(prtd, 0); + wake_up(&the_locks.write_wait); + audpp_disable(AUDPP_ALSA_DECODER, prtd); + audmgr_disable(&prtd->audmgr); + prtd->out_needed = 0; + mutex_unlock(&the_locks.lock); + } + return 0; +} +EXPORT_SYMBOL(alsa_audio_disable); + +int alsa_audrec_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + mutex_lock(&the_locks.lock); + prtd->enabled = 0; + alsa_rec_dsp_enable(prtd, 0); + wake_up(&the_locks.read_wait); + msm_adsp_disable(prtd->audpre); + msm_adsp_disable(prtd->audrec); + audmgr_disable(&prtd->audmgr); + prtd->out_needed = 0; + prtd->opened = 0; + mutex_unlock(&the_locks.lock); + } + return 0; +} +EXPORT_SYMBOL(alsa_audrec_disable); + +static int audio_dsp_read_buffer(struct msm_audio *prtd, uint32_t read_cnt) +{ + audrec_cmd_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PACKET_EXT_PTR; + /* Both WAV and AAC use AUDREC_CMD_TYPE_0 */ + cmd.type = AUDREC_CMD_TYPE_0; + cmd.curr_rec_count_msw = read_cnt >> 16; + cmd.curr_rec_count_lsw = read_cnt; + + return audio_send_queue_recbs(prtd, &cmd, sizeof(cmd)); +} + +int audrec_encoder_config(struct msm_audio *prtd) +{ + audrec_cmd_arec0param_cfg cmd; + uint16_t *data = (void *)prtd->data; + unsigned n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_AREC0PARAM_CFG; + cmd.ptr_to_extpkt_buffer_msw = prtd->phys >> 16; + cmd.ptr_to_extpkt_buffer_lsw = prtd->phys; + cmd.buf_len = FRAME_NUM; /* Both WAV and AAC use 8 frames */ + cmd.samp_rate_index = prtd->samp_rate_index; + /* 0 for mono, 1 for stereo */ + cmd.stereo_mode = prtd->channel_mode; + cmd.rec_quality = 0x1C00; + + /* prepare buffer pointers: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + */ + + for (n = 0; n < FRAME_NUM; n++) { + prtd->in[n].data = data + 4; + data += (4 + (prtd->channel_mode ? 2048 : 1024)); + } + + return audio_send_queue_rec(prtd, &cmd, sizeof(cmd)); +} + +int audio_dsp_out_enable(struct msm_audio *prtd, int yes) +{ + audpp_cmd_pcm_intf cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_PCM_INTF_2; + cmd.object_num = AUDPP_CMD_PCM_INTF_OBJECT_NUM; + cmd.config = AUDPP_CMD_PCM_INTF_CONFIG_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + + if (yes) { + cmd.write_buf1LSW = prtd->out[0].addr; + cmd.write_buf1MSW = prtd->out[0].addr >> 16; + cmd.write_buf1_len = 0; + cmd.write_buf2LSW = prtd->out[1].addr; + cmd.write_buf2MSW = prtd->out[1].addr >> 16; + cmd.write_buf2_len = prtd->out[1].used; + cmd.arm_to_rx_flag = AUDPP_CMD_PCM_INTF_ENA_V; + cmd.weight_decoder_to_rx = prtd->out_weight; + cmd.weight_arm_to_rx = 1; + cmd.partition_number_arm_to_dsp = 0; + cmd.sample_rate = prtd->out_sample_rate; + cmd.channel_mode = prtd->out_channel_mode; + } + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + + mutex_lock(&the_locks.read_lock); + while (count > 0) { + rc = wait_event_interruptible(the_locks.read_wait, + (prtd->in_count > 0) + || prtd->stopped); + if (rc < 0) + break; + + if (prtd->stopped) { + rc = -EBUSY; + break; + } + + index = prtd->in_tail; + data = (uint8_t *) prtd->in[index].data; + size = prtd->in[index].size; + if (count >= size) { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (index != prtd->in_tail) { + /* overrun: data is invalid, we need to retry */ + spin_unlock_irqrestore(&the_locks.read_dsp_lock, + flag); + continue; + } + prtd->in[index].size = 0; + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + prtd->in_count--; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + count -= size; + buf += size; + } else { + break; + } + } + mutex_unlock(&the_locks.read_lock); + return rc; +} +EXPORT_SYMBOL(alsa_buffer_read); + +static int audio_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len) +{ + audpp_cmd_pcm_intf_send_buffer cmd; + cmd.cmd_id = AUDPP_CMD_PCM_INTF_2; + cmd.host_pcm_object = AUDPP_CMD_PCM_INTF_OBJECT_NUM; + cmd.config = AUDPP_CMD_PCM_INTF_BUFFER_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + cmd.dsp_to_arm_buf_id = 0; + cmd.arm_to_dsp_buf_id = idx + 1; + cmd.arm_to_dsp_buf_len = len; + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +int alsa_rec_dsp_enable(struct msm_audio *prtd, int enable) +{ + audrec_cmd_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_CFG; + cmd.type_0 = enable ? AUDREC_CMD_TYPE_0_ENA : AUDREC_CMD_TYPE_0_DIS; + cmd.type_0 |= (AUDREC_CMD_TYPE_0_UPDATE | prtd->type); + cmd.type_1 = 0; + + return audio_send_queue_rec(prtd, &cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(alsa_rec_dsp_enable); + +void alsa_get_dsp_frames(struct msm_audio *prtd) +{ + struct audio_frame *frame; + uint32_t index = 0; + unsigned long flag; + + if (prtd->type == AUDREC_CMD_TYPE_0_INDEX_WAV) { + index = prtd->in_head; + + frame = + (void *)(((char *)prtd->in[index].data) - sizeof(*frame)); + + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->in[index].size = frame->bytes; + + prtd->in_head = (prtd->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (prtd->in_head == prtd->in_tail) + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + else + prtd->in_count++; + + audio_dsp_read_buffer(prtd, prtd->dsp_cnt++); + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + wake_up(&the_locks.read_wait); + } else { + /* TODO AAC not supported yet. */ + } +} +EXPORT_SYMBOL(alsa_get_dsp_frames); diff --git a/sound/soc/msm/msm-pcm.h b/sound/soc/msm/msm-pcm.h new file mode 100644 index 0000000..7563ef0 --- /dev/null +++ b/sound/soc/msm/msm-pcm.h @@ -0,0 +1,200 @@ +/* sound/soc/msm/msm-pcm.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H + + +#include <mach/qdsp5/qdsp5audppcmdi.h> +#include <mach/qdsp5/qdsp5audppmsg.h> +#include <mach/qdsp5/qdsp5audreccmdi.h> +#include <mach/qdsp5/qdsp5audrecmsg.h> +#include <mach/qdsp5/qdsp5audpreproccmdi.h> +#include <mach/qdsp5/qdsp5audpreprocmsg.h> + +#include <../arch/arm/mach-msm/qdsp5/adsp.h> +#include <../arch/arm/mach-msm/qdsp5/audmgr.h> + + +#define FRAME_NUM (8) +#define FRAME_SIZE (2052 * 2) +#define MONO_DATA_SIZE (2048) +#define STEREO_DATA_SIZE (MONO_DATA_SIZE * 2) +#define CAPTURE_DMASZ (FRAME_SIZE * FRAME_NUM) + +#define BUFSZ (960 * 5) +#define PLAYBACK_DMASZ (BUFSZ * 2) + +#define MSM_PLAYBACK_DEFAULT_VOLUME 0 /* 0dB */ +#define MSM_PLAYBACK_DEFAULT_PAN 0 + +#define USE_FORMATS SNDRV_PCM_FMTBIT_S16_LE +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +/* Support unconventional sample rates 12000, 24000 as well */ +#define USE_RATE \ + (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) +#define USE_RATE_MIN 8000 +#define USE_RATE_MAX 48000 +#define MAX_BUFFER_PLAYBACK_SIZE \ + (4800*4) +/* 2048 frames (Mono), 1024 frames (Stereo) */ +#define CAPTURE_SIZE 4096 +#define MAX_BUFFER_CAPTURE_SIZE (4096*4) +#define MAX_PERIOD_SIZE BUFSZ +#define USE_PERIODS_MAX 1024 +#define USE_PERIODS_MIN 1 + + +#define MAX_DB (16) +#define MIN_DB (-50) +#define PCMPLAYBACK_DECODERID 5 + +/* 0xFFFFFFFF Indicates not to be used for audio data copy */ +#define BUF_INVALID_LEN 0xFFFFFFFF + +extern int copy_count; +extern int intcnt; + +struct msm_volume { + bool update; + int volume; /* Volume parameter, in dB Scale */ + int pan; +}; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + struct mutex lock; + struct mutex write_lock; + struct mutex read_lock; + spinlock_t read_dsp_lock; + spinlock_t write_dsp_lock; + spinlock_t mixer_lock; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; +}; + +extern struct audio_locks the_locks; + +struct msm_audio_event_callbacks { + /* event is called from interrupt context when a message + * arrives from the DSP. + */ + void (*playback)(void *); + void (*capture)(void *); +}; + + +struct msm_audio { + struct buffer out[2]; + struct buffer_rec in[8]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + atomic_t out_bytes; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + + struct audmgr audmgr; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int pcm_buf_pos; /* position in buffer */ + + struct msm_adsp_module *audpre; + struct msm_adsp_module *audrec; + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t type; /* 0 for PCM ,1 for AAC */ + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + unsigned short samp_rate_index; + + /* audpre settings */ + audpreproc_cmd_cfg_agc_params tx_agc_cfg; + audpreproc_cmd_cfg_ns_params ns_cfg; + /* For different sample rate, the coeff might be different. * + * All the coeff should be passed from user space */ + audpreproc_cmd_cfg_iir_tuning_filter_params iir_cfg; + + struct msm_audio_event_callbacks *ops; + + int dir; + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ +}; + + + +/* platform data */ +extern int audio_dsp_out_enable(struct msm_audio *prtd, int yes); +extern struct snd_soc_platform msm_soc_platform; +extern struct snd_soc_dai msm_dais[2]; +extern struct snd_soc_codec_device soc_codec_dev_msm; + +int audrec_encoder_config(struct msm_audio *prtd); +extern void alsa_get_dsp_frames(struct msm_audio *prtd); +extern int alsa_rec_dsp_enable(struct msm_audio *prtd, int enable); +extern int alsa_audrec_disable(struct msm_audio *prtd); +extern int alsa_audio_configure(struct msm_audio *prtd); +extern int alsa_audio_disable(struct msm_audio *prtd); +extern int alsa_adsp_configure(struct msm_audio *prtd); +extern int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos); +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos); +int msm_audio_volume_update(unsigned id, + int volume, int pan); +extern struct audio_locks the_locks; +extern struct msm_volume msm_vol_ctl; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm7201.c b/sound/soc/msm/msm7201.c new file mode 100644 index 0000000..977fbac --- /dev/null +++ b/sound/soc/msm/msm7201.c @@ -0,0 +1,337 @@ +/* linux/sound/soc/msm/msm7201.c + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <asm/dma.h> +#include <linux/dma-mapping.h> + +#include "msm-pcm.h" +#include <asm/mach-types.h> +#include <mach/msm_rpcrouter.h> + +static struct msm_rpc_endpoint *snd_ep; + +struct msm_snd_rpc_ids { + unsigned long prog; + unsigned long vers; + unsigned long rpc_set_snd_device; + int device; +}; + +static struct msm_snd_rpc_ids snd_rpc_ids; + +static struct platform_device *msm_audio_snd_device; + +static int snd_msm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Volume Param, in dB */ + uinfo->value.integer.min = MIN_DB; + uinfo->value.integer.max = MAX_DB; + return 0; +} + +static int snd_msm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + spin_lock_irq(&the_locks.mixer_lock); + ucontrol->value.integer.value[0] = msm_vol_ctl.volume; + spin_unlock_irq(&the_locks.mixer_lock); + return 0; +} + +static int snd_msm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + int volume; + + volume = ucontrol->value.integer.value[0]; + spin_lock_irq(&the_locks.mixer_lock); + change = (msm_vol_ctl.volume != volume); + if (change) { + msm_vol_ctl.update = 1; + msm_vol_ctl.volume = volume; + } + spin_unlock_irq(&the_locks.mixer_lock); + return change; +} + +static int snd_msm_device_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Device */ + + /* + * The number of devices supported is 26 (0 to 25) + */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 25; + return 0; +} + +static int snd_msm_device_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = (uint32_t)snd_rpc_ids.device; + return 0; +} + +int msm_snd_init_rpc_ids(void) +{ + snd_rpc_ids.prog = 0x30000002; +#ifdef CONFIG_MSM_AMSS_VERSION_6225 + //TODO: complete for other versions + snd_rpc_ids.vers = 0xaa2b1a44; +#else + //seem a new magich number...not in arch/arm/mach-msm and it seem to be for a new amss version + snd_rpc_ids.vers = 0x00020001; +#endif + /* + * The magic number 2 corresponds to the rpc call + * index for snd_set_device + */ + snd_rpc_ids.rpc_set_snd_device = 2; + return 0; +} + +int msm_snd_rpc_connect(void) +{ + if (snd_ep) { + printk(KERN_INFO "%s: snd_ep already connected\n", __func__); + return 0; + } + + /* Initialize rpc ids */ + if (msm_snd_init_rpc_ids()) { + printk(KERN_ERR "%s: snd rpc ids initialization failed\n" + , __func__); + return -ENODATA; + } + + snd_ep = msm_rpc_connect(snd_rpc_ids.prog, + snd_rpc_ids.vers, 0); + if (IS_ERR(snd_ep)) { + printk(KERN_ERR "%s: failed (compatible VERS = %ld)\n", + __func__, snd_rpc_ids.vers); + snd_ep = NULL; + return -EAGAIN; + } + return 0; +} + +int msm_snd_rpc_close(void) +{ + int rc = 0; + + if (IS_ERR(snd_ep)) { + printk(KERN_ERR "%s: snd handle unavailable, rc = %ld\n", + __func__, PTR_ERR(snd_ep)); + return -EAGAIN; + } + + rc = msm_rpc_close(snd_ep); + snd_ep = NULL; + + if (rc < 0) { + printk(KERN_ERR "%s: close rpc failed! rc = %d\n", + __func__, rc); + return -EAGAIN; + } else + printk(KERN_INFO "rpc close success\n"); + + return rc; +} + +static int snd_msm_device_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct snd_start_req { + struct rpc_request_hdr hdr; + uint32_t rpc_snd_device; + uint32_t snd_mute_ear_mute; + uint32_t snd_mute_mic_mute; + uint32_t callback_ptr; + uint32_t client_data; + } req; + + snd_rpc_ids.device = (int)ucontrol->value.integer.value[0]; + req.hdr.type = 0; + req.hdr.rpc_vers = 2; + + req.rpc_snd_device = cpu_to_be32(snd_rpc_ids.device); + req.snd_mute_ear_mute = cpu_to_be32(1); + req.snd_mute_mic_mute = cpu_to_be32(0); + req.callback_ptr = -1; + req.client_data = cpu_to_be32(0); + + req.hdr.prog = snd_rpc_ids.prog; + req.hdr.vers = snd_rpc_ids.vers; + + rc = msm_rpc_call(snd_ep, snd_rpc_ids.rpc_set_snd_device , + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + printk(KERN_ERR "%s: snd rpc call failed! rc = %d\n", + __func__, rc); + } else + printk(KERN_INFO "snd device connected \n"); + + return rc; +} + +/* Supported range -50dB to 18dB */ +static const DECLARE_TLV_DB_LINEAR(db_scale_linear, -5000, 1800); + +#define MSM_EXT(xname, xindex, fp_info, fp_get, fp_put, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, .index = xindex, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ +} + +#define MSM_EXT_TLV(xname, xindex, fp_info, fp_get, fp_put, addr, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = (SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE), \ + .name = xname, .index = xindex, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, .tlv.p = tlv_array, \ + .private_value = addr, \ +} + +static struct snd_kcontrol_new snd_msm_controls[] = { + MSM_EXT_TLV("PCM Playback Volume", 0, snd_msm_volume_info, \ + snd_msm_volume_get, snd_msm_volume_put, 0, db_scale_linear), + MSM_EXT("device", 1, snd_msm_device_info, snd_msm_device_get, \ + snd_msm_device_put, 0), +}; + +static int msm_new_mixer(struct snd_card *card) +{ + unsigned int idx; + int err; + + printk(KERN_ERR "msm_soc:ALSA MSM Mixer Setting"); + strcpy(card->mixername, "MSM Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_msm_controls); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_msm_controls[idx], NULL)); + if (err < 0) + return err; + } + return 0; +} + +static int msm_soc_dai_init(struct snd_soc_codec *codec) +{ + + int ret = 0; + ret = msm_new_mixer(codec->card); + if (ret < 0) { + printk(KERN_ERR "msm_soc:ALSA MSM Mixer Fail"); + } + + return ret; +} + + +static struct snd_soc_dai_link msm_dai = { + .name = "ASOC", + .stream_name = "ASOC", + .codec_dai = &msm_dais[0], + .cpu_dai = &msm_dais[1], + .init = msm_soc_dai_init, +}; + +struct snd_soc_card snd_soc_card_msm = { + .name = "msm-audio", + .dai_link = &msm_dai, + .num_links = 1, + .platform = &msm_soc_platform, +}; + +/* msm_audio audio subsystem */ +static struct snd_soc_device msm_audio_snd_devdata = { + .card = &snd_soc_card_msm, + .codec_dev = &soc_codec_dev_msm, +}; + + +static int __init msm_audio_init(void) +{ + int ret; + + msm_audio_snd_device = platform_device_alloc("soc-audio", -1); + if (!msm_audio_snd_device) + return -ENOMEM; + + platform_set_drvdata(msm_audio_snd_device, &msm_audio_snd_devdata); + msm_audio_snd_devdata.dev = &msm_audio_snd_device->dev; + ret = platform_device_add(msm_audio_snd_device); + if (ret) { + platform_device_put(msm_audio_snd_device); + return ret; + } + mutex_init(&the_locks.lock); + mutex_init(&the_locks.write_lock); + mutex_init(&the_locks.read_lock); + spin_lock_init(&the_locks.read_dsp_lock); + spin_lock_init(&the_locks.write_dsp_lock); + spin_lock_init(&the_locks.mixer_lock); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + msm_vol_ctl.volume = MSM_PLAYBACK_DEFAULT_VOLUME; + msm_vol_ctl.pan = MSM_PLAYBACK_DEFAULT_PAN; + + ret = msm_snd_rpc_connect(); + + return ret; +} + +static void __exit msm_audio_exit(void) +{ + msm_snd_rpc_close(); + platform_device_unregister(msm_audio_snd_device); +} + +module_init(msm_audio_init); +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7k-pcm.c b/sound/soc/msm/msm7k-pcm.c new file mode 100644 index 0000000..38e8283 --- /dev/null +++ b/sound/soc/msm/msm7k-pcm.c @@ -0,0 +1,574 @@ +/* linux/sound/soc/msm/msm7k-pcm.c + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + + + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <asm/dma.h> +#include <linux/dma-mapping.h> + +#include "msm-pcm.h" + +#define SND_DRIVER "snd_msm" +#define MAX_PCM_DEVICES SNDRV_CARDS +#define MAX_PCM_SUBSTREAMS 1 + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +int copy_count; + +struct audio_locks the_locks; +EXPORT_SYMBOL(the_locks); +struct msm_volume msm_vol_ctl; +EXPORT_SYMBOL(msm_vol_ctl); + + +static unsigned convert_dsp_samp_index(unsigned index) +{ + switch (index) { + case 48000: + return AUDREC_CMD_SAMP_RATE_INDX_48000; + case 44100: + return AUDREC_CMD_SAMP_RATE_INDX_44100; + case 32000: + return AUDREC_CMD_SAMP_RATE_INDX_32000; + case 24000: + return AUDREC_CMD_SAMP_RATE_INDX_24000; + case 22050: + return AUDREC_CMD_SAMP_RATE_INDX_22050; + case 16000: + return AUDREC_CMD_SAMP_RATE_INDX_16000; + case 12000: + return AUDREC_CMD_SAMP_RATE_INDX_12000; + case 11025: + return AUDREC_CMD_SAMP_RATE_INDX_11025; + case 8000: + return AUDREC_CMD_SAMP_RATE_INDX_8000; + default: + return AUDREC_CMD_SAMP_RATE_INDX_44100; + } +} + +static unsigned convert_samp_rate(unsigned hz) +{ + switch (hz) { + case 48000: + return RPC_AUD_DEF_SAMPLE_RATE_48000; + case 44100: + return RPC_AUD_DEF_SAMPLE_RATE_44100; + case 32000: + return RPC_AUD_DEF_SAMPLE_RATE_32000; + case 24000: + return RPC_AUD_DEF_SAMPLE_RATE_24000; + case 22050: + return RPC_AUD_DEF_SAMPLE_RATE_22050; + case 16000: + return RPC_AUD_DEF_SAMPLE_RATE_16000; + case 12000: + return RPC_AUD_DEF_SAMPLE_RATE_12000; + case 11025: + return RPC_AUD_DEF_SAMPLE_RATE_11025; + case 8000: + return RPC_AUD_DEF_SAMPLE_RATE_8000; + default: + return RPC_AUD_DEF_SAMPLE_RATE_44100; + } +} + +static struct snd_pcm_hardware msm_pcm_playback_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_PLAYBACK_SIZE, + .period_bytes_min = 64, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_capture_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_CAPTURE_SIZE, + .period_bytes_min = CAPTURE_SIZE, + .period_bytes_max = CAPTURE_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void playback_event_handler(void *data) +{ + struct msm_audio *prtd = data; + snd_pcm_period_elapsed(prtd->playback_substream); +} + +static void capture_event_handler(void *data) +{ + struct msm_audio *prtd = data; + snd_pcm_period_elapsed(prtd->capture_substream); +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->out_sample_rate = runtime->rate; + prtd->out_channel_mode = runtime->channels; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct audmgr_config cfg; + int rc; + + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = convert_samp_rate(runtime->rate); + prtd->samp_rate_index = convert_dsp_samp_index(runtime->rate); + prtd->channel_mode = (runtime->channels - 1); + prtd->buffer_size = prtd->channel_mode ? STEREO_DATA_SIZE : \ + MONO_DATA_SIZE; + + if (prtd->enabled == 1) + return 0; + + prtd->type = AUDREC_CMD_TYPE_0_INDEX_WAV; + + cfg.tx_rate = convert_samp_rate(runtime->rate); + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&prtd->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(prtd->audpre)) { + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + if (msm_adsp_enable(prtd->audrec)) { + msm_adsp_disable(prtd->audpre); + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + prtd->enabled = 1; + alsa_rec_dsp_enable(prtd, 1); + + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos == prtd->pcm_size) + prtd->pcm_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int rc = 0, rc1 = 0, rc2 = 0; + int fbytes = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + int monofbytes = 0; + char *bufferp = NULL; + + fbytes = frames_to_bytes(runtime, frames); + monofbytes = fbytes / 2; + if (runtime->channels == 2) { + rc = alsa_buffer_read(prtd, buf, fbytes, NULL); + } else { + bufferp = buf; + rc1 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + bufferp = buf + monofbytes ; + rc2 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + rc = rc1 + rc2; + } + prtd->pcm_buf_pos += fbytes; + return rc; +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + alsa_audrec_disable(prtd); + audmgr_close(&prtd->audmgr); + msm_adsp_put(prtd->audrec); + msm_adsp_put(prtd->audpre); + kfree(prtd); + + return 0; +} + +struct msm_audio_event_callbacks snd_msm_audio_ops = { + .playback = playback_event_handler, + .capture = capture_event_handler, +}; + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd; + int ret = 0; + + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + return ret; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msm_vol_ctl.update = 1; /* Update Volume, with Cached value */ + runtime->hw = msm_pcm_playback_hardware; + prtd->dir = SNDRV_PCM_STREAM_PLAYBACK; + prtd->playback_substream = substream; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_capture_hardware; + prtd->dir = SNDRV_PCM_STREAM_CAPTURE; + prtd->capture_substream = substream; + } + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + goto out; + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + prtd->ops = &snd_msm_audio_ops; + prtd->out[0].used = BUF_INVALID_LEN; + prtd->out_head = 1; /* point to second buffer on startup */ + runtime->private_data = prtd; + + ret = alsa_adsp_configure(prtd); + if (ret) + goto out; + copy_count = 0; + return 0; + + out: + kfree(prtd); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int rc = 1; + int fbytes = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + rc = alsa_send_buffer(prtd, buf, fbytes, NULL); + ++copy_count; + prtd->pcm_buf_pos += fbytes; + if (copy_count == 1) { + mutex_lock(&the_locks.lock); + alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + } + if ((prtd->running) && (msm_vol_ctl.update)) { + rc = msm_audio_volume_update(PCMPLAYBACK_DECODERID, + msm_vol_ctl.volume, msm_vol_ctl.pan); + msm_vol_ctl.update = 0; + } + + return rc; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + alsa_audio_disable(prtd); + audmgr_close(&prtd->audmgr); + kfree(prtd); + + return 0; +} + + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (substream->pcm->device & 1) { + runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED; + runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; + } + return 0; + +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, +}; + + + +static int msm_pcm_remove(struct platform_device *devptr) +{ + struct snd_soc_device *socdev = platform_get_drvdata(devptr); + snd_soc_free_pcms(socdev); + kfree(socdev->codec); + platform_set_drvdata(devptr, NULL); + return 0; +} + +static int pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + if (!stream) + size = PLAYBACK_DMASZ; + else + size = CAPTURE_DMASZ; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void msm_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int msm_pcm_new(struct snd_card *card, + struct snd_soc_dai *codec_dai, + struct snd_pcm *pcm) +{ + int ret; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (codec_dai->playback.channels_min) { + ret = pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (codec_dai->capture.channels_min) { + ret = pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + msm_pcm_free_dma_buffers(pcm); + } + return ret; +} + +struct snd_soc_platform msm_soc_platform = { + .name = "msm-audio", + .remove = msm_pcm_remove, + .pcm_ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL(msm_soc_platform); + +static int __init msm_soc_platform_init(void) +{ + return snd_soc_register_platform(&msm_soc_platform); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&msm_soc_platform); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2");
_______________________________________________ Alsa-devel mailing list Alsa-devel@xxxxxxxxxxxxxxxx http://mailman.alsa-project.org/mailman/listinfo/alsa-devel