162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci// imx-pcm-fiq.c -- ALSA Soc Audio Layer 362306a36Sopenharmony_ci// 462306a36Sopenharmony_ci// Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> 562306a36Sopenharmony_ci// 662306a36Sopenharmony_ci// This code is based on code copyrighted by Freescale, 762306a36Sopenharmony_ci// Liam Girdwood, Javier Martin and probably others. 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/clk.h> 1062306a36Sopenharmony_ci#include <linux/delay.h> 1162306a36Sopenharmony_ci#include <linux/device.h> 1262306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/interrupt.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include <sound/core.h> 2062306a36Sopenharmony_ci#include <sound/dmaengine_pcm.h> 2162306a36Sopenharmony_ci#include <sound/initval.h> 2262306a36Sopenharmony_ci#include <sound/pcm.h> 2362306a36Sopenharmony_ci#include <sound/pcm_params.h> 2462306a36Sopenharmony_ci#include <sound/soc.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include <asm/fiq.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include <linux/platform_data/asoc-imx-ssi.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#include "imx-ssi.h" 3162306a36Sopenharmony_ci#include "imx-pcm.h" 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistruct imx_pcm_runtime_data { 3462306a36Sopenharmony_ci unsigned int period; 3562306a36Sopenharmony_ci int periods; 3662306a36Sopenharmony_ci unsigned long offset; 3762306a36Sopenharmony_ci struct hrtimer hrt; 3862306a36Sopenharmony_ci int poll_time_ns; 3962306a36Sopenharmony_ci struct snd_pcm_substream *substream; 4062306a36Sopenharmony_ci atomic_t playing; 4162306a36Sopenharmony_ci atomic_t capturing; 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci struct imx_pcm_runtime_data *iprtd = 4762306a36Sopenharmony_ci container_of(hrt, struct imx_pcm_runtime_data, hrt); 4862306a36Sopenharmony_ci struct snd_pcm_substream *substream = iprtd->substream; 4962306a36Sopenharmony_ci struct pt_regs regs; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci if (!atomic_read(&iprtd->playing) && !atomic_read(&iprtd->capturing)) 5262306a36Sopenharmony_ci return HRTIMER_NORESTART; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci get_fiq_regs(®s); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 5762306a36Sopenharmony_ci iprtd->offset = regs.ARM_r8 & 0xffff; 5862306a36Sopenharmony_ci else 5962306a36Sopenharmony_ci iprtd->offset = regs.ARM_r9 & 0xffff; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci snd_pcm_period_elapsed(substream); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns)); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return HRTIMER_RESTART; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic struct fiq_handler fh = { 6962306a36Sopenharmony_ci .name = DRV_NAME, 7062306a36Sopenharmony_ci}; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic int snd_imx_pcm_hw_params(struct snd_soc_component *component, 7362306a36Sopenharmony_ci struct snd_pcm_substream *substream, 7462306a36Sopenharmony_ci struct snd_pcm_hw_params *params) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 7762306a36Sopenharmony_ci struct imx_pcm_runtime_data *iprtd = runtime->private_data; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci iprtd->periods = params_periods(params); 8062306a36Sopenharmony_ci iprtd->period = params_period_bytes(params); 8162306a36Sopenharmony_ci iprtd->offset = 0; 8262306a36Sopenharmony_ci iprtd->poll_time_ns = 1000000000 / params_rate(params) * 8362306a36Sopenharmony_ci params_period_size(params); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int snd_imx_pcm_prepare(struct snd_soc_component *component, 8962306a36Sopenharmony_ci struct snd_pcm_substream *substream) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 9262306a36Sopenharmony_ci struct imx_pcm_runtime_data *iprtd = runtime->private_data; 9362306a36Sopenharmony_ci struct pt_regs regs; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci get_fiq_regs(®s); 9662306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 9762306a36Sopenharmony_ci regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16; 9862306a36Sopenharmony_ci else 9962306a36Sopenharmony_ci regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci set_fiq_regs(®s); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return 0; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic int imx_pcm_fiq; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic int snd_imx_pcm_trigger(struct snd_soc_component *component, 10962306a36Sopenharmony_ci struct snd_pcm_substream *substream, int cmd) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 11262306a36Sopenharmony_ci struct imx_pcm_runtime_data *iprtd = runtime->private_data; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci switch (cmd) { 11562306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 11662306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_RESUME: 11762306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 11862306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 11962306a36Sopenharmony_ci atomic_set(&iprtd->playing, 1); 12062306a36Sopenharmony_ci else 12162306a36Sopenharmony_ci atomic_set(&iprtd->capturing, 1); 12262306a36Sopenharmony_ci hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns), 12362306a36Sopenharmony_ci HRTIMER_MODE_REL); 12462306a36Sopenharmony_ci enable_fiq(imx_pcm_fiq); 12562306a36Sopenharmony_ci break; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 12862306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_SUSPEND: 12962306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 13062306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 13162306a36Sopenharmony_ci atomic_set(&iprtd->playing, 0); 13262306a36Sopenharmony_ci else 13362306a36Sopenharmony_ci atomic_set(&iprtd->capturing, 0); 13462306a36Sopenharmony_ci if (!atomic_read(&iprtd->playing) && 13562306a36Sopenharmony_ci !atomic_read(&iprtd->capturing)) 13662306a36Sopenharmony_ci disable_fiq(imx_pcm_fiq); 13762306a36Sopenharmony_ci break; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci default: 14062306a36Sopenharmony_ci return -EINVAL; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return 0; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic snd_pcm_uframes_t 14762306a36Sopenharmony_cisnd_imx_pcm_pointer(struct snd_soc_component *component, 14862306a36Sopenharmony_ci struct snd_pcm_substream *substream) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 15162306a36Sopenharmony_ci struct imx_pcm_runtime_data *iprtd = runtime->private_data; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return bytes_to_frames(substream->runtime, iprtd->offset); 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic const struct snd_pcm_hardware snd_imx_hardware = { 15762306a36Sopenharmony_ci .info = SNDRV_PCM_INFO_INTERLEAVED | 15862306a36Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 15962306a36Sopenharmony_ci SNDRV_PCM_INFO_MMAP | 16062306a36Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 16162306a36Sopenharmony_ci SNDRV_PCM_INFO_PAUSE | 16262306a36Sopenharmony_ci SNDRV_PCM_INFO_RESUME, 16362306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 16462306a36Sopenharmony_ci .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, 16562306a36Sopenharmony_ci .period_bytes_min = 128, 16662306a36Sopenharmony_ci .period_bytes_max = 16 * 1024, 16762306a36Sopenharmony_ci .periods_min = 4, 16862306a36Sopenharmony_ci .periods_max = 255, 16962306a36Sopenharmony_ci .fifo_size = 0, 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic int snd_imx_open(struct snd_soc_component *component, 17362306a36Sopenharmony_ci struct snd_pcm_substream *substream) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 17662306a36Sopenharmony_ci struct imx_pcm_runtime_data *iprtd; 17762306a36Sopenharmony_ci int ret; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); 18062306a36Sopenharmony_ci if (iprtd == NULL) 18162306a36Sopenharmony_ci return -ENOMEM; 18262306a36Sopenharmony_ci runtime->private_data = iprtd; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci iprtd->substream = substream; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci atomic_set(&iprtd->playing, 0); 18762306a36Sopenharmony_ci atomic_set(&iprtd->capturing, 0); 18862306a36Sopenharmony_ci hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 18962306a36Sopenharmony_ci iprtd->hrt.function = snd_hrtimer_callback; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci ret = snd_pcm_hw_constraint_integer(substream->runtime, 19262306a36Sopenharmony_ci SNDRV_PCM_HW_PARAM_PERIODS); 19362306a36Sopenharmony_ci if (ret < 0) { 19462306a36Sopenharmony_ci kfree(iprtd); 19562306a36Sopenharmony_ci return ret; 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); 19962306a36Sopenharmony_ci return 0; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic int snd_imx_close(struct snd_soc_component *component, 20362306a36Sopenharmony_ci struct snd_pcm_substream *substream) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 20662306a36Sopenharmony_ci struct imx_pcm_runtime_data *iprtd = runtime->private_data; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci hrtimer_cancel(&iprtd->hrt); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci kfree(iprtd); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci return 0; 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct snd_card *card = rtd->card->snd_card; 21862306a36Sopenharmony_ci struct snd_pcm *pcm = rtd->pcm; 21962306a36Sopenharmony_ci int ret; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); 22262306a36Sopenharmony_ci if (ret) 22362306a36Sopenharmony_ci return ret; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC, 22662306a36Sopenharmony_ci pcm->card->dev, 22762306a36Sopenharmony_ci IMX_SSI_DMABUF_SIZE); 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic int ssi_irq; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int snd_imx_pcm_new(struct snd_soc_component *component, 23362306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci struct snd_pcm *pcm = rtd->pcm; 23662306a36Sopenharmony_ci struct snd_pcm_substream *substream; 23762306a36Sopenharmony_ci int ret; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci ret = imx_pcm_new(rtd); 24062306a36Sopenharmony_ci if (ret) 24162306a36Sopenharmony_ci return ret; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; 24462306a36Sopenharmony_ci if (substream) { 24562306a36Sopenharmony_ci struct snd_dma_buffer *buf = &substream->dma_buffer; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci imx_ssi_fiq_tx_buffer = (unsigned long)buf->area; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; 25162306a36Sopenharmony_ci if (substream) { 25262306a36Sopenharmony_ci struct snd_dma_buffer *buf = &substream->dma_buffer; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci imx_ssi_fiq_rx_buffer = (unsigned long)buf->area; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci set_fiq_handler(&imx_ssi_fiq_start, 25862306a36Sopenharmony_ci &imx_ssi_fiq_end - &imx_ssi_fiq_start); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci return 0; 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic void snd_imx_pcm_free(struct snd_soc_component *component, 26462306a36Sopenharmony_ci struct snd_pcm *pcm) 26562306a36Sopenharmony_ci{ 26662306a36Sopenharmony_ci mxc_set_irq_fiq(ssi_irq, 0); 26762306a36Sopenharmony_ci release_fiq(&fh); 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic const struct snd_soc_component_driver imx_soc_component_fiq = { 27162306a36Sopenharmony_ci .open = snd_imx_open, 27262306a36Sopenharmony_ci .close = snd_imx_close, 27362306a36Sopenharmony_ci .hw_params = snd_imx_pcm_hw_params, 27462306a36Sopenharmony_ci .prepare = snd_imx_pcm_prepare, 27562306a36Sopenharmony_ci .trigger = snd_imx_pcm_trigger, 27662306a36Sopenharmony_ci .pointer = snd_imx_pcm_pointer, 27762306a36Sopenharmony_ci .pcm_construct = snd_imx_pcm_new, 27862306a36Sopenharmony_ci .pcm_destruct = snd_imx_pcm_free, 27962306a36Sopenharmony_ci}; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ciint imx_pcm_fiq_init(struct platform_device *pdev, 28262306a36Sopenharmony_ci struct imx_pcm_fiq_params *params) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci int ret; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci ret = claim_fiq(&fh); 28762306a36Sopenharmony_ci if (ret) { 28862306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to claim fiq: %d", ret); 28962306a36Sopenharmony_ci return ret; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci mxc_set_irq_fiq(params->irq, 1); 29362306a36Sopenharmony_ci ssi_irq = params->irq; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci imx_pcm_fiq = params->irq; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci imx_ssi_fiq_base = (unsigned long)params->base; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci params->dma_params_tx->maxburst = 4; 30062306a36Sopenharmony_ci params->dma_params_rx->maxburst = 6; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci ret = devm_snd_soc_register_component(&pdev->dev, &imx_soc_component_fiq, 30362306a36Sopenharmony_ci NULL, 0); 30462306a36Sopenharmony_ci if (ret) 30562306a36Sopenharmony_ci goto failed_register; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci return 0; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cifailed_register: 31062306a36Sopenharmony_ci mxc_set_irq_fiq(ssi_irq, 0); 31162306a36Sopenharmony_ci release_fiq(&fh); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci return ret; 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(imx_pcm_fiq_init); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_civoid imx_pcm_fiq_exit(struct platform_device *pdev) 31862306a36Sopenharmony_ci{ 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(imx_pcm_fiq_exit); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 323