diff -drupN a/sound/soc/sunxi/sun8iw19-pcm.c b/sound/soc/sunxi/sun8iw19-pcm.c --- a/sound/soc/sunxi/sun8iw19-pcm.c 1970-01-01 03:00:00.000000000 +0300 +++ b/sound/soc/sunxi/sun8iw19-pcm.c 2022-06-12 05:28:14.000000000 +0300 @@ -0,0 +1,551 @@ +/* + * sound\soc\sunxi\sun8iw19-pcm.c + * (C) Copyright 2014-2019 + * AllWinner Technology Co., Ltd. + * wolfgang huang + * yumingfeng + * + * some simple description for this code + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sun8iw19-pcm.h" + +#undef SUN8IW19_PCM_DEBUG + +static u64 sunxi_pcm_mask = DMA_BIT_MASK(32); + +#ifdef CONFIG_SUNXI_ADC_DAUDIO_SYNC +#include "sun8iw19-codec.h" + +void *sunxi_daudio0; +struct regmap *sunxi_codec_regmap; + +struct sunxi_pcm_trigger_spinlock adc_sync_spinlock; + +int adc_sync_flag; +module_param(adc_sync_flag, int, 0444); +MODULE_PARM_DESC(adc_sync_flag, "SUNXI codec for dma engine trigger sync."); + +static int substream_mode; +module_param(substream_mode, int, 0444); +MODULE_PARM_DESC(substream_mode, "SUNXI codec for dma engine trigger sync."); + +/* only for debug */ +static long playback_tv; +module_param(playback_tv, long, 0444); +MODULE_PARM_DESC(playback_tv, "SUNXI codec for dma engine trigger time(us)."); + +static long capture_tv; +module_param(capture_tv, long, 0444); +MODULE_PARM_DESC(capture_tv, "SUNXI codec for dma engine trigger time(us)."); + +static struct timeval play_tv; +static struct timeval cap_tv; + +/* used for mixer control */ +long long sunxi_codec_get_pcm_trigger_playback_tv(void) +{ + long long time_tv = play_tv.tv_sec * 1000000 + play_tv.tv_usec; + playback_tv = time_tv; + return time_tv; +} +EXPORT_SYMBOL_GPL(sunxi_codec_get_pcm_trigger_playback_tv); + +long long sunxi_codec_get_pcm_trigger_capture_tv(void) +{ + long long time_tv = cap_tv.tv_sec * 1000000 + cap_tv.tv_usec; + capture_tv = time_tv; + return time_tv; +} +EXPORT_SYMBOL_GPL(sunxi_codec_get_pcm_trigger_capture_tv); + +void sunxi_codec_set_pcm_trigger_playback_tv(void) +{ + do_gettimeofday(&play_tv); + playback_tv = play_tv.tv_sec * 1000000 + play_tv.tv_usec; +} +EXPORT_SYMBOL_GPL(sunxi_codec_set_pcm_trigger_playback_tv); + +void sunxi_codec_set_pcm_trigger_capture_tv(void) +{ + do_gettimeofday(&cap_tv); + capture_tv = cap_tv.tv_sec * 1000000 + cap_tv.tv_usec; +} +EXPORT_SYMBOL_GPL(sunxi_codec_set_pcm_trigger_capture_tv); + +int sunxi_codec_get_pcm_trigger_substream_mode(void) +{ + return substream_mode; +} +EXPORT_SYMBOL_GPL(sunxi_codec_get_pcm_trigger_substream_mode); + +void sunxi_codec_set_pcm_trigger_substream_mode(int value) +{ + if (!((adc_sync_flag >> ADC_I2S_RUNNING) & 0x1)) { + substream_mode = value; + } else { + pr_err("set the adc sync mode should be stop the record.\n"); + } +} +EXPORT_SYMBOL_GPL(sunxi_codec_set_pcm_trigger_substream_mode); + +void sunxi_codec_set_pcm_adc_sync_flag(int value) +{ + adc_sync_flag = value; +} +EXPORT_SYMBOL_GPL(sunxi_codec_set_pcm_adc_sync_flag); + +int sunxi_codec_get_pcm_adc_sync_flag(void) +{ + return adc_sync_flag; +} +EXPORT_SYMBOL_GPL(sunxi_codec_get_pcm_adc_sync_flag); + +void sunxi_codec_set_pcm_adc_daudio0(void *sunxi_daudio) +{ + sunxi_daudio0 = (struct sunxi_daudio_info *)sunxi_daudio; +} +EXPORT_SYMBOL_GPL(sunxi_codec_set_pcm_adc_daudio0); + +void *sunxi_codec_get_pcm_adc_daudio0(void) +{ + return (void *)sunxi_daudio0; +} +EXPORT_SYMBOL_GPL(sunxi_codec_get_pcm_adc_daudio0); + +void sunxi_codec_set_regmap(struct regmap *codec_regmap) +{ + sunxi_codec_regmap = codec_regmap; +} +EXPORT_SYMBOL_GPL(sunxi_codec_set_regmap); + +/* for adc and i2s rx sync */ +void sunxi_cpudai_adc_drq_enable(bool enable) +{ + if (!sunxi_codec_regmap) { + pr_err("sunxi_codec_regmap is null.\n"); + return; + } + + if (enable) + regmap_update_bits(sunxi_codec_regmap, SUNXI_ADC_FIFOC, + (1 << ADC_DRQ_EN), (1 << ADC_DRQ_EN)); + else + regmap_update_bits(sunxi_codec_regmap, SUNXI_ADC_FIFOC, + (1 << ADC_DRQ_EN), (0 << ADC_DRQ_EN)); +} +EXPORT_SYMBOL_GPL(sunxi_cpudai_adc_drq_enable); + +/* for sync */ +static void audio_trigger_lock_spinlock_init(void *__lock) +{ + struct sunxi_pcm_trigger_spinlock *lock = __lock; + spin_lock_init(&lock->spinlock); +} + +void audio_trigger_lock_spinlock(void) +{ + struct sunxi_pcm_trigger_spinlock *lock = &adc_sync_spinlock; + unsigned long flags; + + spin_lock_irqsave(&lock->spinlock, flags); + lock->spinlock_flags = flags; +} +EXPORT_SYMBOL_GPL(audio_trigger_lock_spinlock); + +void audio_trigger_unlock_spinlock(void) +{ + struct sunxi_pcm_trigger_spinlock *lock = &adc_sync_spinlock; + spin_unlock_irqrestore(&lock->spinlock, lock->spinlock_flags); +} +EXPORT_SYMBOL_GPL(audio_trigger_unlock_spinlock); + +#endif + +static int sunxi_set_runtime_hwparams(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_platform *platform = rtd->platform; + struct device_node *np = platform->component.dev->of_node; + unsigned int temp_val; + size_t temp_long; + int ret = 0; + + snd_soc_platform_get_drvdata(platform); + + runtime->hw.info = SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_PAUSE + | SNDRV_PCM_INFO_RESUME; + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S8 + | SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S20_3LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE; + runtime->hw.rates = SNDRV_PCM_RATE_8000_192000 + | SNDRV_PCM_RATE_KNOT; + runtime->hw.rate_min = 8000; + runtime->hw.channels_min = 1; + runtime->hw.period_bytes_min = 256; + runtime->hw.periods_min = 1; + + ret = of_property_read_u32(np, "rate_max", &temp_val); + if (ret < 0) { + runtime->hw.rate_max = 192000; + pr_err("audio_driver: [%s] hwparams get failed!\n", __func__); + } else { + runtime->hw.rate_max = temp_val; + } + + ret = of_property_read_u32(np, "channels_max", &temp_val); + if (ret < 0) { + runtime->hw.channels_max = 8; + pr_err("audio_driver: [%s] hwparams get failed!\n", __func__); + } else { + runtime->hw.channels_max = temp_val; + } + + ret = of_property_read_u32(np, "buffer_bytes_max", &temp_long); + if (ret < 0) { + runtime->hw.buffer_bytes_max = 1024*256; + pr_err("audio_driver: [%s] hwparams get failed!\n", __func__); + } else { + runtime->hw.buffer_bytes_max = temp_long; + } + + ret = of_property_read_u32(np, "period_bytes_max", &temp_long); + if (ret < 0) { + runtime->hw.period_bytes_max = 1024*128; + pr_err("audio_driver: [%s] hwparams get failed!\n", __func__); + } else { + runtime->hw.period_bytes_max = temp_long; + } + + ret = of_property_read_u32(np, "periods_max", &temp_val); + if (ret < 0) { + runtime->hw.periods_max = 8; + pr_err("audio_driver: [%s] hwparams get failed!\n", __func__); + } else { + runtime->hw.periods_max = temp_val; + } + + ret = of_property_read_u32(np, "fifo_size", &temp_long); + if (ret < 0) { + runtime->hw.fifo_size = 128; + pr_err("audio_driver: [%s] hwparams get failed!\n", __func__); + } else { + runtime->hw.fifo_size = temp_long; + } +#ifdef SUN8IW19_PCM_DEBUG + pr_warn("audio_driver: rate_min: %d, rate_max: %d, channels_min: %d, " + "channels_max: %d\n", + runtime->hw.rate_min, runtime->hw.rate_max, + runtime->hw.channels_min, runtime->hw.channels_max); + + pr_warn("audio_driver: buffer_bytes_max: 0x%x, period_bytes_min: 0x%x" + " period_bytes_max: 0x%x, periods_min: %d, periods_max: %d" + " fifo_size: 0x%x\n\n", + runtime->hw.buffer_bytes_max, runtime->hw.period_bytes_min, + runtime->hw.period_bytes_max, runtime->hw.periods_min, + runtime->hw.periods_max, runtime->hw.fifo_size); +#endif + return 0; +}; + +static int sunxi_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct sunxi_dma_params *dmap; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct dma_chan *chan; + struct dma_slave_config slave_config; + int ret; + + if (strcmp(rtd->card->name, "sun8iw19-codec") == 0) { + dmap = snd_soc_dai_get_dma_data(rtd->codec_dai, substream); + } else { + dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + } + + ret = snd_hwparams_to_dma_slave_config(substream, params, + &slave_config); + if (ret) { + dev_err(dev, "hw params config failed with err %d\n", ret); + return ret; + } + + slave_config.dst_maxburst = dmap->dst_maxburst; + slave_config.src_maxburst = dmap->src_maxburst; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + slave_config.dst_addr = dmap->dma_addr; + slave_config.src_addr_width = slave_config.dst_addr_width; + slave_config.slave_id = sunxi_slave_id(dmap->dma_drq_type_num, + DRQSRC_SDRAM); + } else { + slave_config.src_addr = dmap->dma_addr; + slave_config.dst_addr_width = slave_config.src_addr_width; + slave_config.slave_id = sunxi_slave_id(DRQDST_SDRAM, + dmap->dma_drq_type_num); + } + + chan = snd_dmaengine_pcm_get_chan(substream); + if (chan == NULL) { + pr_err("[%s] dma pcm get chan failed! chan is NULL!\n", __func__); + return -EINVAL; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret < 0) { + dev_err(dev, "dma slave config failed with err %d\n", ret); + return ret; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int sunxi_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int sunxi_pcm_hw_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int sunxi_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_dmaengine_pcm_trigger(substream, + SNDRV_PCM_TRIGGER_START); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + snd_dmaengine_pcm_trigger(substream, + SNDRV_PCM_TRIGGER_STOP); + break; + } + } else { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_dmaengine_pcm_trigger(substream, + SNDRV_PCM_TRIGGER_START); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + snd_dmaengine_pcm_trigger(substream, + SNDRV_PCM_TRIGGER_STOP); + break; + } + } + return 0; +} + +static int sunxi_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + int ret = 0; + + /* Set HW params now that initialization is complete */ + /* dma platform Hw params get from dtsi config */ + ret = sunxi_set_runtime_hwparams(substream); + if (ret < 0) { + pr_err("audio_driver: [%s] dma platform hwparams set failed\n", + __func__); + return ret; + } + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + ret = snd_dmaengine_pcm_open_request_chan(substream, NULL, NULL); + if (ret != 0) { + dev_err(dev, "dmaengine pcm chan request failed or open failed with err %d\n", ret); + pr_err("[%s] maybe the DMA configuration is unenabled\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int sunxi_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = NULL; + + if (substream->runtime != NULL) { + runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + } else { + return -1; + } + +} + +static struct snd_pcm_ops sunxi_pcm_ops = { + .open = sunxi_pcm_open, + .close = snd_dmaengine_pcm_close_release_chan, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sunxi_pcm_hw_params, + .prepare = sunxi_pcm_hw_prepare, + .hw_free = sunxi_pcm_hw_free, + .trigger = sunxi_pcm_trigger, + .pointer = snd_dmaengine_pcm_pointer, + .mmap = sunxi_pcm_mmap, +}; + +static int sunxi_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct snd_soc_platform *platform = rtd->platform; + struct device_node *np = platform->component.dev->of_node; + size_t temp_long; + size_t size = 0; + int ret = 0; + + /* To get the device_node */ + snd_soc_platform_get_drvdata(platform); + + ret = of_property_read_u32(np, "buffer_bytes_max", &temp_long); + if (ret < 0) { + size = 1024 * 256; + pr_err("audio_driver: [%s] buffer_bytes_max get failed!\n", + __func__); + } else { + size = temp_long; + } + + 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 sunxi_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 sunxi_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &sunxi_pcm_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = sunxi_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK, rtd); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = sunxi_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE, rtd); + if (ret) + goto out; + } + +out: + return ret; +} + +static struct snd_soc_platform_driver sunxi_soc_platform = { + .ops = &sunxi_pcm_ops, + .pcm_new = sunxi_pcm_new, + .pcm_free = sunxi_pcm_free_dma_buffers, +}; + +int asoc_dma_platform_register(struct device *dev, unsigned int flags) +{ +#ifdef CONFIG_SUNXI_ADC_DAUDIO_SYNC + if (adc_sync_spinlock.ref_count++ == 0) + audio_trigger_lock_spinlock_init(&adc_sync_spinlock); +#endif + return snd_soc_register_platform(dev, &sunxi_soc_platform); +} +EXPORT_SYMBOL_GPL(asoc_dma_platform_register); + +void asoc_dma_platform_unregister(struct device *dev) +{ + snd_soc_unregister_platform(dev); +} +EXPORT_SYMBOL_GPL(asoc_dma_platform_unregister); + +MODULE_AUTHOR("huangxin, liushaohua"); +MODULE_DESCRIPTION("sunxi ASoC DMA Driver"); +MODULE_LICENSE("GPL");