/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "msm-pcm-afe.h" #include "msm-pcm-q6.h" #define MIN_PERIOD_SIZE (128 * 2) #define MAX_PERIOD_SIZE (128 * 2 * 2 * 6) static struct snd_pcm_hardware msm_afe_hardware = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000), .rate_min = 8000, .rate_max = 48000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = MAX_PERIOD_SIZE * 32, .period_bytes_min = MIN_PERIOD_SIZE, .period_bytes_max = MAX_PERIOD_SIZE, .periods_min = 32, .periods_max = 384, .fifo_size = 0, }; static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt); static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt); static void q6asm_event_handler(uint32_t opcode, uint32_t token, uint32_t *payload, void *priv) { } static enum hrtimer_restart afe_hrtimer_callback(struct hrtimer *hrt) { struct pcm_afe_info *prtd = container_of(hrt, struct pcm_afe_info, hrt); struct snd_pcm_substream *substream = prtd->substream; struct snd_pcm_runtime *runtime = substream->runtime; if (prtd->start) { pr_debug("sending frame to DSP: poll_time: %d\n", prtd->poll_time); if (prtd->dsp_cnt == runtime->periods) prtd->dsp_cnt = 0; afe_rt_proxy_port_write( (prtd->dma_addr + (prtd->dsp_cnt * snd_pcm_lib_period_bytes(prtd->substream))), snd_pcm_lib_period_bytes(prtd->substream)); prtd->dsp_cnt++; hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time * 1000)); return HRTIMER_RESTART; } else return HRTIMER_NORESTART; } static enum hrtimer_restart afe_hrtimer_rec_callback(struct hrtimer *hrt) { struct pcm_afe_info *prtd = container_of(hrt, struct pcm_afe_info, hrt); struct snd_pcm_substream *substream = prtd->substream; struct snd_pcm_runtime *runtime = substream->runtime; if (prtd->start) { if (prtd->dsp_cnt == runtime->periods) prtd->dsp_cnt = 0; afe_rt_proxy_port_read( (prtd->dma_addr + (prtd->dsp_cnt * snd_pcm_lib_period_bytes(prtd->substream))), snd_pcm_lib_period_bytes(prtd->substream)); prtd->dsp_cnt++; pr_debug("sending frame rec to DSP: poll_time: %d\n", prtd->poll_time); hrtimer_forward_now(hrt, ns_to_ktime(prtd->poll_time * 1000)); return HRTIMER_RESTART; } else return HRTIMER_NORESTART; } static void pcm_afe_process_tx_pkt(uint32_t opcode, uint32_t token, uint32_t *payload, void *priv) { struct pcm_afe_info *prtd = priv; unsigned long dsp_flags; struct snd_pcm_substream *substream = NULL; struct snd_pcm_runtime *runtime = NULL; uint16_t event; if (prtd == NULL) return; substream = prtd->substream; runtime = substream->runtime; pr_debug("%s\n", __func__); spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); switch (opcode) { case AFE_EVENT_RT_PROXY_PORT_STATUS: { event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); switch (event) { case AFE_EVENT_RTPORT_START: { prtd->dsp_cnt = 0; prtd->poll_time = ((unsigned long)(( snd_pcm_lib_period_bytes (prtd->substream) * 1000 * 1000)/ (runtime->rate * runtime->channels * 2))); pr_debug("prtd->poll_time: %d", prtd->poll_time); break; } case AFE_EVENT_RTPORT_STOP: pr_debug("%s: event!=0\n", __func__); prtd->start = 0; snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); break; case AFE_EVENT_RTPORT_LOW_WM: pr_debug("%s: Underrun\n", __func__); break; case AFE_EVENT_RTPORT_HI_WM: pr_debug("%s: Overrun\n", __func__); break; default: break; } break; } case APR_BASIC_RSP_RESULT: { switch (payload[0]) { case AFE_SERVICE_CMD_RTPORT_WR: pr_debug("write done\n"); prtd->pcm_irq_pos += snd_pcm_lib_period_bytes (prtd->substream); snd_pcm_period_elapsed(prtd->substream); break; default: break; } break; } default: break; } spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); } static void pcm_afe_process_rx_pkt(uint32_t opcode, uint32_t token, uint32_t *payload, void *priv) { struct pcm_afe_info *prtd = priv; unsigned long dsp_flags; struct snd_pcm_substream *substream = NULL; struct snd_pcm_runtime *runtime = NULL; uint16_t event; if (prtd == NULL) return; substream = prtd->substream; runtime = substream->runtime; pr_debug("%s\n", __func__); spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); switch (opcode) { case AFE_EVENT_RT_PROXY_PORT_STATUS: { event = (uint16_t)((0xFFFF0000 & payload[0]) >> 0x10); switch (event) { case AFE_EVENT_RTPORT_START: { prtd->dsp_cnt = 0; prtd->poll_time = ((unsigned long)(( snd_pcm_lib_period_bytes(prtd->substream) * 1000 * 1000)/(runtime->rate * runtime->channels * 2))); pr_debug("prtd->poll_time : %d", prtd->poll_time); break; } case AFE_EVENT_RTPORT_STOP: pr_debug("%s: event!=0\n", __func__); prtd->start = 0; snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); break; case AFE_EVENT_RTPORT_LOW_WM: pr_debug("%s: Underrun\n", __func__); break; case AFE_EVENT_RTPORT_HI_WM: pr_debug("%s: Overrun\n", __func__); break; default: break; } break; } case APR_BASIC_RSP_RESULT: { switch (payload[0]) { case AFE_SERVICE_CMD_RTPORT_RD: pr_debug("Read done\n"); prtd->pcm_irq_pos += snd_pcm_lib_period_bytes (prtd->substream); snd_pcm_period_elapsed(prtd->substream); break; default: break; } break; } default: break; } spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); } static int msm_afe_playback_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct pcm_afe_info *prtd = runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *dai = rtd->cpu_dai; int ret = 0; pr_debug("%s: sample_rate=%d\n", __func__, runtime->rate); pr_debug("%s: dai->id =%x\n", __func__, dai->id); ret = afe_register_get_events(dai->id, pcm_afe_process_tx_pkt, prtd); if (ret < 0) { pr_err("afe-pcm:register for events failed\n"); return ret; } pr_debug("%s:success\n", __func__); prtd->prepared++; return ret; } static int msm_afe_capture_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct pcm_afe_info *prtd = runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *dai = rtd->cpu_dai; int ret = 0; pr_debug("%s\n", __func__); pr_debug("%s: dai->id =%x\n", __func__, dai->id); ret = afe_register_get_events(dai->id, pcm_afe_process_rx_pkt, prtd); if (ret < 0) { pr_err("afe-pcm:register for events failed\n"); return ret; } pr_debug("%s:success\n", __func__); prtd->prepared++; return 0; } /* Conventional and unconventional sample rate supported */ static unsigned int supported_sample_rates[] = { 8000, 16000, 48000 }; static struct snd_pcm_hw_constraint_list constraints_sample_rates = { .count = ARRAY_SIZE(supported_sample_rates), .list = supported_sample_rates, .mask = 0, }; static int msm_afe_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct pcm_afe_info *prtd = NULL; int ret = 0; prtd = kzalloc(sizeof(struct pcm_afe_info), GFP_KERNEL); if (prtd == NULL) { pr_err("Failed to allocate memory for msm_audio\n"); return -ENOMEM; } else pr_debug("prtd %x\n", (unsigned int)prtd); mutex_init(&prtd->lock); spin_lock_init(&prtd->dsp_lock); prtd->dsp_cnt = 0; mutex_lock(&prtd->lock); runtime->hw = msm_afe_hardware; prtd->substream = substream; runtime->private_data = prtd; prtd->audio_client = q6asm_audio_client_alloc( (app_cb)q6asm_event_handler, prtd); if (!prtd->audio_client) { pr_debug("%s: Could not allocate memory\n", __func__); mutex_unlock(&prtd->lock); kfree(prtd); return -ENOMEM; } hrtimer_init(&prtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) prtd->hrt.function = afe_hrtimer_callback; else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) prtd->hrt.function = afe_hrtimer_rec_callback; mutex_unlock(&prtd->lock); ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_sample_rates); if (ret < 0) pr_err("snd_pcm_hw_constraint_list failed\n"); /* 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) pr_err("snd_pcm_hw_constraint_integer failed\n"); return 0; } static int msm_afe_close(struct snd_pcm_substream *substream) { int rc = 0; struct snd_dma_buffer *dma_buf; struct snd_pcm_runtime *runtime; struct pcm_afe_info *prtd; struct snd_soc_pcm_runtime *rtd = NULL; struct snd_soc_dai *dai = NULL; int dir = IN; int ret = 0; pr_debug("%s\n", __func__); if (substream == NULL) { pr_err("substream is NULL\n"); return -EINVAL; } rtd = substream->private_data; dai = rtd->cpu_dai; runtime = substream->runtime; prtd = runtime->private_data; mutex_lock(&prtd->lock); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { dir = IN; ret = afe_unregister_get_events(dai->id); if (ret < 0) pr_err("AFE unregister for events failed\n"); } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { dir = OUT; ret = afe_unregister_get_events(dai->id); if (ret < 0) pr_err("AFE unregister for events failed\n"); } hrtimer_cancel(&prtd->hrt); rc = afe_cmd_memory_unmap(runtime->dma_addr); if (rc < 0) pr_err("AFE memory unmap failed\n"); pr_debug("release all buffer\n"); dma_buf = &substream->dma_buffer; if (dma_buf == NULL) { pr_debug("dma_buf is NULL\n"); goto done; } if (dma_buf->area) { dma_buf->area = NULL; } q6asm_audio_client_buf_free_contiguous(dir, prtd->audio_client); done: pr_debug("%s: dai->id =%x\n", __func__, dai->id); q6asm_audio_client_free(prtd->audio_client); mutex_unlock(&prtd->lock); prtd->prepared--; kfree(prtd); return 0; } static int msm_afe_prepare(struct snd_pcm_substream *substream) { int ret = 0; struct snd_pcm_runtime *runtime = substream->runtime; struct pcm_afe_info *prtd = runtime->private_data; prtd->pcm_irq_pos = 0; if (prtd->prepared) return 0; mutex_lock(&prtd->lock); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ret = msm_afe_playback_prepare(substream); else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ret = msm_afe_capture_prepare(substream); mutex_unlock(&prtd->lock); return ret; } static int msm_afe_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) { struct snd_pcm_runtime *runtime = substream->runtime; struct pcm_afe_info *prtd = runtime->private_data; int result = 0; pr_debug("%s\n", __func__); prtd->mmap_flag = 1; if (runtime->dma_addr && runtime->dma_bytes) { vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); result = remap_pfn_range(vma, vma->vm_start, runtime->dma_addr >> PAGE_SHIFT, runtime->dma_bytes, vma->vm_page_prot); } else { pr_err("Physical address or size of buf is NULL"); return -EINVAL; } return result; } static int msm_afe_trigger(struct snd_pcm_substream *substream, int cmd) { int ret = 0; struct snd_pcm_runtime *runtime = substream->runtime; struct pcm_afe_info *prtd = runtime->private_data; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: pr_debug("%s: SNDRV_PCM_TRIGGER_START\n", __func__); prtd->start = 1; hrtimer_start(&prtd->hrt, ns_to_ktime(0), HRTIMER_MODE_REL); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); prtd->start = 0; break; default: ret = -EINVAL; break; } return ret; } static int msm_afe_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dma_buffer *dma_buf = &substream->dma_buffer; struct pcm_afe_info *prtd = runtime->private_data; struct audio_buffer *buf; int dir, ret; pr_debug("%s:\n", __func__); mutex_lock(&prtd->lock); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) dir = IN; else dir = OUT; ret = q6asm_audio_client_buf_alloc_contiguous(dir, prtd->audio_client, runtime->hw.period_bytes_min, runtime->hw.periods_max); if (ret < 0) { pr_err("Audio Start: Buffer Allocation failed rc = %d\n", ret); mutex_unlock(&prtd->lock); return -ENOMEM; } buf = prtd->audio_client->port[dir].buf; if (buf == NULL || buf[0].data == NULL) { mutex_unlock(&prtd->lock); return -ENOMEM; } pr_debug("%s:buf = %p\n", __func__, buf); dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; dma_buf->dev.dev = substream->pcm->card->dev; dma_buf->private_data = NULL; dma_buf->area = buf[0].data; dma_buf->addr = buf[0].phys; dma_buf->bytes = runtime->hw.buffer_bytes_max; if (!dma_buf->area) { pr_err("%s:MSM AFE physical memory allocation failed\n", __func__); mutex_unlock(&prtd->lock); return -ENOMEM; } memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max); prtd->dma_addr = (u32) dma_buf->addr; mutex_unlock(&prtd->lock); snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); ret = afe_cmd_memory_map(dma_buf->addr, dma_buf->bytes); if (ret < 0) pr_err("fail to map memory to DSP\n"); return ret; } static snd_pcm_uframes_t msm_afe_pointer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct pcm_afe_info *prtd = runtime->private_data; if (prtd->pcm_irq_pos >= snd_pcm_lib_buffer_bytes(substream)) prtd->pcm_irq_pos = 0; pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); } static struct snd_pcm_ops msm_afe_ops = { .open = msm_afe_open, .hw_params = msm_afe_hw_params, .trigger = msm_afe_trigger, .close = msm_afe_close, .prepare = msm_afe_prepare, .mmap = msm_afe_mmap, .pointer = msm_afe_pointer, }; static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) { struct snd_card *card = rtd->card->snd_card; int ret = 0; pr_debug("%s\n", __func__); if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = DMA_BIT_MASK(32); return ret; } static int msm_afe_afe_probe(struct snd_soc_platform *platform) { pr_debug("%s\n", __func__); return 0; } static struct snd_soc_platform_driver msm_soc_platform = { .ops = &msm_afe_ops, .pcm_new = msm_asoc_pcm_new, .probe = msm_afe_afe_probe, }; static __devinit int msm_afe_probe(struct platform_device *pdev) { pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); return snd_soc_register_platform(&pdev->dev, &msm_soc_platform); } static int msm_afe_remove(struct platform_device *pdev) { pr_debug("%s\n", __func__); snd_soc_unregister_platform(&pdev->dev); return 0; } static struct platform_driver msm_afe_driver = { .driver = { .name = "msm-pcm-afe", .owner = THIS_MODULE, }, .probe = msm_afe_probe, .remove = __devexit_p(msm_afe_remove), }; static int __init msm_soc_platform_init(void) { pr_debug("%s\n", __func__); return platform_driver_register(&msm_afe_driver); } module_init(msm_soc_platform_init); static void __exit msm_soc_platform_exit(void) { pr_debug("%s\n", __func__); platform_driver_unregister(&msm_afe_driver); } module_exit(msm_soc_platform_exit); MODULE_DESCRIPTION("AFE PCM module platform driver"); MODULE_LICENSE("GPL v2");