forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
6.8 KiB
266 lines
6.8 KiB
/* |
|
* ALSA SoC Synopsys PIO PCM for I2S driver |
|
* |
|
* sound/soc/dwc/designware_pcm.c |
|
* |
|
* Copyright (C) 2016 Synopsys |
|
* Jose Abreu <[email protected]> |
|
* |
|
* This file is licensed under the terms of the GNU General Public |
|
* License version 2. This program is licensed "as is" without any |
|
* warranty of any kind, whether express or implied. |
|
*/ |
|
|
|
#include <linux/io.h> |
|
#include <linux/rcupdate.h> |
|
#include <sound/pcm.h> |
|
#include <sound/pcm_params.h> |
|
#include "local.h" |
|
|
|
#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) |
|
#define PERIOD_BYTES_MIN 4096 |
|
#define PERIODS_MIN 2 |
|
|
|
#define dw_pcm_tx_fn(sample_bits) \ |
|
static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \ |
|
struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \ |
|
bool *period_elapsed) \ |
|
{ \ |
|
const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ |
|
unsigned int period_pos = tx_ptr % runtime->period_size; \ |
|
int i; \ |
|
\ |
|
for (i = 0; i < dev->fifo_th; i++) { \ |
|
iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \ |
|
iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \ |
|
period_pos++; \ |
|
if (++tx_ptr >= runtime->buffer_size) \ |
|
tx_ptr = 0; \ |
|
} \ |
|
*period_elapsed = period_pos >= runtime->period_size; \ |
|
return tx_ptr; \ |
|
} |
|
|
|
#define dw_pcm_rx_fn(sample_bits) \ |
|
static unsigned int dw_pcm_rx_##sample_bits(struct dw_i2s_dev *dev, \ |
|
struct snd_pcm_runtime *runtime, unsigned int rx_ptr, \ |
|
bool *period_elapsed) \ |
|
{ \ |
|
u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ |
|
unsigned int period_pos = rx_ptr % runtime->period_size; \ |
|
int i; \ |
|
\ |
|
for (i = 0; i < dev->fifo_th; i++) { \ |
|
p[rx_ptr][0] = ioread32(dev->i2s_base + LRBR_LTHR(0)); \ |
|
p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \ |
|
period_pos++; \ |
|
if (++rx_ptr >= runtime->buffer_size) \ |
|
rx_ptr = 0; \ |
|
} \ |
|
*period_elapsed = period_pos >= runtime->period_size; \ |
|
return rx_ptr; \ |
|
} |
|
|
|
dw_pcm_tx_fn(16); |
|
dw_pcm_tx_fn(32); |
|
dw_pcm_rx_fn(16); |
|
dw_pcm_rx_fn(32); |
|
|
|
#undef dw_pcm_tx_fn |
|
#undef dw_pcm_rx_fn |
|
|
|
static const struct snd_pcm_hardware dw_pcm_hardware = { |
|
.info = SNDRV_PCM_INFO_INTERLEAVED | |
|
SNDRV_PCM_INFO_MMAP | |
|
SNDRV_PCM_INFO_MMAP_VALID | |
|
SNDRV_PCM_INFO_BLOCK_TRANSFER, |
|
.rates = SNDRV_PCM_RATE_32000 | |
|
SNDRV_PCM_RATE_44100 | |
|
SNDRV_PCM_RATE_48000, |
|
.rate_min = 32000, |
|
.rate_max = 48000, |
|
.formats = SNDRV_PCM_FMTBIT_S16_LE | |
|
SNDRV_PCM_FMTBIT_S24_LE | |
|
SNDRV_PCM_FMTBIT_S32_LE, |
|
.channels_min = 2, |
|
.channels_max = 2, |
|
.buffer_bytes_max = BUFFER_BYTES_MAX, |
|
.period_bytes_min = PERIOD_BYTES_MIN, |
|
.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, |
|
.periods_min = PERIODS_MIN, |
|
.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, |
|
.fifo_size = 16, |
|
}; |
|
|
|
static void dw_pcm_transfer(struct dw_i2s_dev *dev, bool push) |
|
{ |
|
struct snd_pcm_substream *substream; |
|
bool active, period_elapsed; |
|
|
|
rcu_read_lock(); |
|
if (push) |
|
substream = rcu_dereference(dev->tx_substream); |
|
else |
|
substream = rcu_dereference(dev->rx_substream); |
|
active = substream && snd_pcm_running(substream); |
|
if (active) { |
|
unsigned int ptr; |
|
unsigned int new_ptr; |
|
|
|
if (push) { |
|
ptr = READ_ONCE(dev->tx_ptr); |
|
new_ptr = dev->tx_fn(dev, substream->runtime, ptr, |
|
&period_elapsed); |
|
cmpxchg(&dev->tx_ptr, ptr, new_ptr); |
|
} else { |
|
ptr = READ_ONCE(dev->rx_ptr); |
|
new_ptr = dev->rx_fn(dev, substream->runtime, ptr, |
|
&period_elapsed); |
|
cmpxchg(&dev->rx_ptr, ptr, new_ptr); |
|
} |
|
|
|
if (period_elapsed) |
|
snd_pcm_period_elapsed(substream); |
|
} |
|
rcu_read_unlock(); |
|
} |
|
|
|
void dw_pcm_push_tx(struct dw_i2s_dev *dev) |
|
{ |
|
dw_pcm_transfer(dev, true); |
|
} |
|
|
|
void dw_pcm_pop_rx(struct dw_i2s_dev *dev) |
|
{ |
|
dw_pcm_transfer(dev, false); |
|
} |
|
|
|
static int dw_pcm_open(struct snd_soc_component *component, |
|
struct snd_pcm_substream *substream) |
|
{ |
|
struct snd_pcm_runtime *runtime = substream->runtime; |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); |
|
|
|
snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware); |
|
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
|
runtime->private_data = dev; |
|
|
|
return 0; |
|
} |
|
|
|
static int dw_pcm_close(struct snd_soc_component *component, |
|
struct snd_pcm_substream *substream) |
|
{ |
|
synchronize_rcu(); |
|
return 0; |
|
} |
|
|
|
static int dw_pcm_hw_params(struct snd_soc_component *component, |
|
struct snd_pcm_substream *substream, |
|
struct snd_pcm_hw_params *hw_params) |
|
{ |
|
struct snd_pcm_runtime *runtime = substream->runtime; |
|
struct dw_i2s_dev *dev = runtime->private_data; |
|
|
|
switch (params_channels(hw_params)) { |
|
case 2: |
|
break; |
|
default: |
|
dev_err(dev->dev, "invalid channels number\n"); |
|
return -EINVAL; |
|
} |
|
|
|
switch (params_format(hw_params)) { |
|
case SNDRV_PCM_FORMAT_S16_LE: |
|
dev->tx_fn = dw_pcm_tx_16; |
|
dev->rx_fn = dw_pcm_rx_16; |
|
break; |
|
case SNDRV_PCM_FORMAT_S24_LE: |
|
case SNDRV_PCM_FORMAT_S32_LE: |
|
dev->tx_fn = dw_pcm_tx_32; |
|
dev->rx_fn = dw_pcm_rx_32; |
|
break; |
|
default: |
|
dev_err(dev->dev, "invalid format\n"); |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int dw_pcm_trigger(struct snd_soc_component *component, |
|
struct snd_pcm_substream *substream, int cmd) |
|
{ |
|
struct snd_pcm_runtime *runtime = substream->runtime; |
|
struct dw_i2s_dev *dev = runtime->private_data; |
|
int ret = 0; |
|
|
|
switch (cmd) { |
|
case SNDRV_PCM_TRIGGER_START: |
|
case SNDRV_PCM_TRIGGER_RESUME: |
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
|
WRITE_ONCE(dev->tx_ptr, 0); |
|
rcu_assign_pointer(dev->tx_substream, substream); |
|
} else { |
|
WRITE_ONCE(dev->rx_ptr, 0); |
|
rcu_assign_pointer(dev->rx_substream, substream); |
|
} |
|
break; |
|
case SNDRV_PCM_TRIGGER_STOP: |
|
case SNDRV_PCM_TRIGGER_SUSPEND: |
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
|
rcu_assign_pointer(dev->tx_substream, NULL); |
|
else |
|
rcu_assign_pointer(dev->rx_substream, NULL); |
|
break; |
|
default: |
|
ret = -EINVAL; |
|
break; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static snd_pcm_uframes_t dw_pcm_pointer(struct snd_soc_component *component, |
|
struct snd_pcm_substream *substream) |
|
{ |
|
struct snd_pcm_runtime *runtime = substream->runtime; |
|
struct dw_i2s_dev *dev = runtime->private_data; |
|
snd_pcm_uframes_t pos; |
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
|
pos = READ_ONCE(dev->tx_ptr); |
|
else |
|
pos = READ_ONCE(dev->rx_ptr); |
|
|
|
return pos < runtime->buffer_size ? pos : 0; |
|
} |
|
|
|
static int dw_pcm_new(struct snd_soc_component *component, |
|
struct snd_soc_pcm_runtime *rtd) |
|
{ |
|
size_t size = dw_pcm_hardware.buffer_bytes_max; |
|
|
|
snd_pcm_set_managed_buffer_all(rtd->pcm, |
|
SNDRV_DMA_TYPE_CONTINUOUS, |
|
NULL, size, size); |
|
return 0; |
|
} |
|
|
|
static const struct snd_soc_component_driver dw_pcm_component = { |
|
.open = dw_pcm_open, |
|
.close = dw_pcm_close, |
|
.hw_params = dw_pcm_hw_params, |
|
.trigger = dw_pcm_trigger, |
|
.pointer = dw_pcm_pointer, |
|
.pcm_construct = dw_pcm_new, |
|
}; |
|
|
|
int dw_pcm_register(struct platform_device *pdev) |
|
{ |
|
return devm_snd_soc_register_component(&pdev->dev, &dw_pcm_component, |
|
NULL, 0); |
|
}
|
|
|