Commit dd0111dc authored by Mark Brown's avatar Mark Brown

Merge remote-tracking branches 'asoc/topic/dpcm', 'asoc/topic/dt',...

Merge remote-tracking branches 'asoc/topic/dpcm', 'asoc/topic/dt', 'asoc/topic/dwc' and 'asoc/topic/fsl' into asoc-next
...@@ -12,6 +12,10 @@ Required properties: ...@@ -12,6 +12,10 @@ Required properties:
one for receive. one for receive.
- dma-names : "tx" for the transmit channel, "rx" for the receive channel. - dma-names : "tx" for the transmit channel, "rx" for the receive channel.
Optional properties:
- interrupts: The interrupt line number for the I2S controller. Add this
parameter if the I2S controller that you are using does not support DMA.
For more details on the 'dma', 'dma-names', 'clock' and 'clock-names' For more details on the 'dma', 'dma-names', 'clock' and 'clock-names'
properties please check: properties please check:
* resource-names.txt * resource-names.txt
......
...@@ -58,7 +58,7 @@ Required properties: ...@@ -58,7 +58,7 @@ Required properties:
* DMIC (stands for Digital Microphone Jack) * DMIC (stands for Digital Microphone Jack)
Note: The "Mic Jack" and "AMIC" are redundant while Note: The "Mic Jack" and "AMIC" are redundant while
coexsiting in order to support the old bindings coexisting in order to support the old bindings
of wm8962 and sgtl5000. of wm8962 and sgtl5000.
Optional properties: Optional properties:
......
...@@ -3,7 +3,7 @@ ASoC Machine Driver ...@@ -3,7 +3,7 @@ ASoC Machine Driver
The ASoC machine (or board) driver is the code that glues together all the The ASoC machine (or board) driver is the code that glues together all the
component drivers (e.g. codecs, platforms and DAIs). It also describes the component drivers (e.g. codecs, platforms and DAIs). It also describes the
relationships between each componnent which include audio paths, GPIOs, relationships between each component which include audio paths, GPIOs,
interrupts, clocking, jacks and voltage regulators. interrupts, clocking, jacks and voltage regulators.
The machine driver can contain codec and platform specific code. It registers The machine driver can contain codec and platform specific code. It registers
......
...@@ -358,6 +358,7 @@ struct snd_soc_dapm_context; ...@@ -358,6 +358,7 @@ struct snd_soc_dapm_context;
struct regulator; struct regulator;
struct snd_soc_dapm_widget_list; struct snd_soc_dapm_widget_list;
struct snd_soc_dapm_update; struct snd_soc_dapm_update;
enum snd_soc_dapm_direction;
int dapm_regulator_event(struct snd_soc_dapm_widget *w, int dapm_regulator_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event); struct snd_kcontrol *kcontrol, int event);
...@@ -454,7 +455,9 @@ void dapm_mark_endpoints_dirty(struct snd_soc_card *card); ...@@ -454,7 +455,9 @@ void dapm_mark_endpoints_dirty(struct snd_soc_card *card);
/* dapm path query */ /* dapm path query */
int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
struct snd_soc_dapm_widget_list **list); struct snd_soc_dapm_widget_list **list,
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
enum snd_soc_dapm_direction));
struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm( struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm(
struct snd_kcontrol *kcontrol); struct snd_kcontrol *kcontrol);
......
...@@ -7,4 +7,13 @@ config SND_DESIGNWARE_I2S ...@@ -7,4 +7,13 @@ config SND_DESIGNWARE_I2S
Synopsys desigwnware I2S device. The device supports upto Synopsys desigwnware I2S device. The device supports upto
maximum of 8 channels each for play and record. maximum of 8 channels each for play and record.
config SND_DESIGNWARE_PCM
tristate "PCM PIO extension for I2S driver"
depends on SND_DESIGNWARE_I2S
help
Say Y, M or N if you want to add a custom ALSA extension that registers
a PCM and uses PIO to transfer data.
This functionality is specially suited for I2S devices that don't have
DMA support.
# SYNOPSYS Platform Support # SYNOPSYS Platform Support
obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o
ifdef CONFIG_SND_DESIGNWARE_PCM
obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_pcm.o
endif
...@@ -24,90 +24,7 @@ ...@@ -24,90 +24,7 @@
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
#include <sound/soc.h> #include <sound/soc.h>
#include <sound/dmaengine_pcm.h> #include <sound/dmaengine_pcm.h>
#include "local.h"
/* common register for all channel */
#define IER 0x000
#define IRER 0x004
#define ITER 0x008
#define CER 0x00C
#define CCR 0x010
#define RXFFR 0x014
#define TXFFR 0x018
/* I2STxRxRegisters for all channels */
#define LRBR_LTHR(x) (0x40 * x + 0x020)
#define RRBR_RTHR(x) (0x40 * x + 0x024)
#define RER(x) (0x40 * x + 0x028)
#define TER(x) (0x40 * x + 0x02C)
#define RCR(x) (0x40 * x + 0x030)
#define TCR(x) (0x40 * x + 0x034)
#define ISR(x) (0x40 * x + 0x038)
#define IMR(x) (0x40 * x + 0x03C)
#define ROR(x) (0x40 * x + 0x040)
#define TOR(x) (0x40 * x + 0x044)
#define RFCR(x) (0x40 * x + 0x048)
#define TFCR(x) (0x40 * x + 0x04C)
#define RFF(x) (0x40 * x + 0x050)
#define TFF(x) (0x40 * x + 0x054)
/* I2SCOMPRegisters */
#define I2S_COMP_PARAM_2 0x01F0
#define I2S_COMP_PARAM_1 0x01F4
#define I2S_COMP_VERSION 0x01F8
#define I2S_COMP_TYPE 0x01FC
/*
* Component parameter register fields - define the I2S block's
* configuration.
*/
#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25)
#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22)
#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19)
#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16)
#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9)
#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7)
#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6)
#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5)
#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4)
#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2)
#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0)
#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10)
#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7)
#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3)
#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0)
/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
#define COMP_MAX_WORDSIZE (1 << 3)
#define COMP_MAX_DATA_WIDTH (1 << 2)
#define MAX_CHANNEL_NUM 8
#define MIN_CHANNEL_NUM 2
union dw_i2s_snd_dma_data {
struct i2s_dma_data pd;
struct snd_dmaengine_dai_dma_data dt;
};
struct dw_i2s_dev {
void __iomem *i2s_base;
struct clk *clk;
int active;
unsigned int capability;
unsigned int quirks;
unsigned int i2s_reg_comp1;
unsigned int i2s_reg_comp2;
struct device *dev;
u32 ccr;
u32 xfer_resolution;
u32 fifo_th;
/* data related to DMA transfers b/w i2s and DMAC */
union dw_i2s_snd_dma_data play_dma_data;
union dw_i2s_snd_dma_data capture_dma_data;
struct i2s_clk_config_data config;
int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
};
static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val) static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
{ {
...@@ -145,26 +62,100 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream) ...@@ -145,26 +62,100 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream)
} }
} }
static void i2s_start(struct dw_i2s_dev *dev, static inline void i2s_disable_irqs(struct dw_i2s_dev *dev, u32 stream,
struct snd_pcm_substream *substream) int chan_nr)
{ {
struct i2s_clk_config_data *config = &dev->config;
u32 i, irq; u32 i, irq;
i2s_write_reg(dev->i2s_base, IER, 1);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
for (i = 0; i < (config->chan_nr / 2); i++) { for (i = 0; i < (chan_nr / 2); i++) {
irq = i2s_read_reg(dev->i2s_base, IMR(i));
i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30);
}
} else {
for (i = 0; i < (chan_nr / 2); i++) {
irq = i2s_read_reg(dev->i2s_base, IMR(i));
i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03);
}
}
}
static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream,
int chan_nr)
{
u32 i, irq;
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
for (i = 0; i < (chan_nr / 2); i++) {
irq = i2s_read_reg(dev->i2s_base, IMR(i)); irq = i2s_read_reg(dev->i2s_base, IMR(i));
i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30); i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
} }
i2s_write_reg(dev->i2s_base, ITER, 1);
} else { } else {
for (i = 0; i < (config->chan_nr / 2); i++) { for (i = 0; i < (chan_nr / 2); i++) {
irq = i2s_read_reg(dev->i2s_base, IMR(i)); irq = i2s_read_reg(dev->i2s_base, IMR(i));
i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03); i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
} }
i2s_write_reg(dev->i2s_base, IRER, 1);
} }
}
static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
{
struct dw_i2s_dev *dev = dev_id;
bool irq_valid = false;
u32 isr[4];
int i;
for (i = 0; i < 4; i++)
isr[i] = i2s_read_reg(dev->i2s_base, ISR(i));
i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
for (i = 0; i < 4; i++) {
/*
* Check if TX fifo is empty. If empty fill FIFO with samples
* NOTE: Only two channels supported
*/
if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) {
dw_pcm_push_tx(dev);
irq_valid = true;
}
/* Data available. Record mode not supported in PIO mode */
if (isr[i] & ISR_RXDA)
irq_valid = true;
/* Error Handling: TX */
if (isr[i] & ISR_TXFO) {
dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i);
irq_valid = true;
}
/* Error Handling: TX */
if (isr[i] & ISR_RXFO) {
dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i);
irq_valid = true;
}
}
if (irq_valid)
return IRQ_HANDLED;
else
return IRQ_NONE;
}
static void i2s_start(struct dw_i2s_dev *dev,
struct snd_pcm_substream *substream)
{
struct i2s_clk_config_data *config = &dev->config;
i2s_write_reg(dev->i2s_base, IER, 1);
i2s_enable_irqs(dev, substream->stream, config->chan_nr);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
i2s_write_reg(dev->i2s_base, ITER, 1);
else
i2s_write_reg(dev->i2s_base, IRER, 1);
i2s_write_reg(dev->i2s_base, CER, 1); i2s_write_reg(dev->i2s_base, CER, 1);
} }
...@@ -172,24 +163,14 @@ static void i2s_start(struct dw_i2s_dev *dev, ...@@ -172,24 +163,14 @@ static void i2s_start(struct dw_i2s_dev *dev,
static void i2s_stop(struct dw_i2s_dev *dev, static void i2s_stop(struct dw_i2s_dev *dev,
struct snd_pcm_substream *substream) struct snd_pcm_substream *substream)
{ {
u32 i = 0, irq;
i2s_clear_irqs(dev, substream->stream); i2s_clear_irqs(dev, substream->stream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
i2s_write_reg(dev->i2s_base, ITER, 0); i2s_write_reg(dev->i2s_base, ITER, 0);
else
for (i = 0; i < 4; i++) {
irq = i2s_read_reg(dev->i2s_base, IMR(i));
i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30);
}
} else {
i2s_write_reg(dev->i2s_base, IRER, 0); i2s_write_reg(dev->i2s_base, IRER, 0);
for (i = 0; i < 4; i++) { i2s_disable_irqs(dev, substream->stream, 8);
irq = i2s_read_reg(dev->i2s_base, IMR(i));
i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03);
}
}
if (!dev->active) { if (!dev->active) {
i2s_write_reg(dev->i2s_base, CER, 0); i2s_write_reg(dev->i2s_base, CER, 0);
...@@ -223,7 +204,7 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream, ...@@ -223,7 +204,7 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream,
static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
{ {
u32 ch_reg, irq; u32 ch_reg;
struct i2s_clk_config_data *config = &dev->config; struct i2s_clk_config_data *config = &dev->config;
...@@ -235,16 +216,12 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) ...@@ -235,16 +216,12 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
dev->xfer_resolution); dev->xfer_resolution);
i2s_write_reg(dev->i2s_base, TFCR(ch_reg), i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
dev->fifo_th - 1); dev->fifo_th - 1);
irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30);
i2s_write_reg(dev->i2s_base, TER(ch_reg), 1); i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
} else { } else {
i2s_write_reg(dev->i2s_base, RCR(ch_reg), i2s_write_reg(dev->i2s_base, RCR(ch_reg),
dev->xfer_resolution); dev->xfer_resolution);
i2s_write_reg(dev->i2s_base, RFCR(ch_reg), i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
dev->fifo_th - 1); dev->fifo_th - 1);
irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03);
i2s_write_reg(dev->i2s_base, RER(ch_reg), 1); i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
} }
...@@ -278,7 +255,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, ...@@ -278,7 +255,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
break; break;
default: default:
dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt"); dev_err(dev->dev, "designware-i2s: unsupported PCM fmt");
return -EINVAL; return -EINVAL;
} }
...@@ -626,7 +603,7 @@ static int dw_i2s_probe(struct platform_device *pdev) ...@@ -626,7 +603,7 @@ static int dw_i2s_probe(struct platform_device *pdev)
const struct i2s_platform_data *pdata = pdev->dev.platform_data; const struct i2s_platform_data *pdata = pdev->dev.platform_data;
struct dw_i2s_dev *dev; struct dw_i2s_dev *dev;
struct resource *res; struct resource *res;
int ret; int ret, irq;
struct snd_soc_dai_driver *dw_i2s_dai; struct snd_soc_dai_driver *dw_i2s_dai;
const char *clk_id; const char *clk_id;
...@@ -651,6 +628,16 @@ static int dw_i2s_probe(struct platform_device *pdev) ...@@ -651,6 +628,16 @@ static int dw_i2s_probe(struct platform_device *pdev)
dev->dev = &pdev->dev; dev->dev = &pdev->dev;
irq = platform_get_irq(pdev, 0);
if (irq >= 0) {
ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0,
pdev->name, dev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request irq\n");
return ret;
}
}
dev->i2s_reg_comp1 = I2S_COMP_PARAM_1; dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
dev->i2s_reg_comp2 = I2S_COMP_PARAM_2; dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
if (pdata) { if (pdata) {
...@@ -697,12 +684,24 @@ static int dw_i2s_probe(struct platform_device *pdev) ...@@ -697,12 +684,24 @@ static int dw_i2s_probe(struct platform_device *pdev)
if (!pdata) { if (!pdata) {
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret == -EPROBE_DEFER) {
dev_err(&pdev->dev,
"failed to register PCM, deferring probe\n");
return ret;
} else if (ret) {
dev_err(&pdev->dev,
"Could not register DMA PCM: %d\n"
"falling back to PIO mode\n", ret);
ret = dw_pcm_register(pdev);
if (ret) { if (ret) {
dev_err(&pdev->dev, dev_err(&pdev->dev,
"Could not register PCM: %d\n", ret); "Could not register PIO PCM: %d\n",
ret);
goto err_clk_disable; goto err_clk_disable;
} }
} }
}
pm_runtime_enable(&pdev->dev); pm_runtime_enable(&pdev->dev);
return 0; return 0;
......
/*
* ALSA SoC Synopsys PIO PCM for I2S driver
*
* sound/soc/dwc/designware_pcm.c
*
* Copyright (C) 2016 Synopsys
* Jose Abreu <joabreu@synopsys.com>
*
* 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; \
}
dw_pcm_tx_fn(16);
dw_pcm_tx_fn(32);
#undef dw_pcm_tx_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_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,
};
void dw_pcm_push_tx(struct dw_i2s_dev *dev)
{
struct snd_pcm_substream *tx_substream;
bool tx_active, period_elapsed;
rcu_read_lock();
tx_substream = rcu_dereference(dev->tx_substream);
tx_active = tx_substream && snd_pcm_running(tx_substream);
if (tx_active) {
unsigned int tx_ptr = READ_ONCE(dev->tx_ptr);
unsigned int new_tx_ptr = dev->tx_fn(dev, tx_substream->runtime,
tx_ptr, &period_elapsed);
cmpxchg(&dev->tx_ptr, tx_ptr, new_tx_ptr);
if (period_elapsed)
snd_pcm_period_elapsed(tx_substream);
}
rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(dw_pcm_push_tx);
static int dw_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
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_pcm_substream *substream)
{
synchronize_rcu();
return 0;
}
static int dw_pcm_hw_params(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;
int ret;
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;
break;
case SNDRV_PCM_FORMAT_S32_LE:
dev->tx_fn = dw_pcm_tx_32;
break;
default:
dev_err(dev->dev, "invalid format\n");
return -EINVAL;
}
if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) {
dev_err(dev->dev, "only playback is available\n");
return -EINVAL;
}
ret = snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
if (ret < 0)
return ret;
else
return 0;
}
static int dw_pcm_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
static int dw_pcm_trigger(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:
WRITE_ONCE(dev->tx_ptr, 0);
rcu_assign_pointer(dev->tx_substream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
rcu_assign_pointer(dev->tx_substream, NULL);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static snd_pcm_uframes_t dw_pcm_pointer(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 = READ_ONCE(dev->tx_ptr);
return pos < runtime->buffer_size ? pos : 0;
}
static int dw_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
size_t size = dw_pcm_hardware.buffer_bytes_max;
return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL), size, size);
}
static void dw_pcm_free(struct snd_pcm *pcm)
{
snd_pcm_lib_preallocate_free_for_all(pcm);
}
static const struct snd_pcm_ops dw_pcm_ops = {
.open = dw_pcm_open,
.close = dw_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = dw_pcm_hw_params,
.hw_free = dw_pcm_hw_free,
.trigger = dw_pcm_trigger,
.pointer = dw_pcm_pointer,
};
static const struct snd_soc_platform_driver dw_pcm_platform = {
.pcm_new = dw_pcm_new,
.pcm_free = dw_pcm_free,
.ops = &dw_pcm_ops,
};
int dw_pcm_register(struct platform_device *pdev)
{
return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform);
}
EXPORT_SYMBOL_GPL(dw_pcm_register);
/*
* Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com)
*
* 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.
*/
#ifndef __DESIGNWARE_LOCAL_H
#define __DESIGNWARE_LOCAL_H
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/types.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm.h>
#include <sound/designware_i2s.h>
/* common register for all channel */
#define IER 0x000
#define IRER 0x004
#define ITER 0x008
#define CER 0x00C
#define CCR 0x010
#define RXFFR 0x014
#define TXFFR 0x018
/* Interrupt status register fields */
#define ISR_TXFO BIT(5)
#define ISR_TXFE BIT(4)
#define ISR_RXFO BIT(1)
#define ISR_RXDA BIT(0)
/* I2STxRxRegisters for all channels */
#define LRBR_LTHR(x) (0x40 * x + 0x020)
#define RRBR_RTHR(x) (0x40 * x + 0x024)
#define RER(x) (0x40 * x + 0x028)
#define TER(x) (0x40 * x + 0x02C)
#define RCR(x) (0x40 * x + 0x030)
#define TCR(x) (0x40 * x + 0x034)
#define ISR(x) (0x40 * x + 0x038)
#define IMR(x) (0x40 * x + 0x03C)
#define ROR(x) (0x40 * x + 0x040)
#define TOR(x) (0x40 * x + 0x044)
#define RFCR(x) (0x40 * x + 0x048)
#define TFCR(x) (0x40 * x + 0x04C)
#define RFF(x) (0x40 * x + 0x050)
#define TFF(x) (0x40 * x + 0x054)
/* I2SCOMPRegisters */
#define I2S_COMP_PARAM_2 0x01F0
#define I2S_COMP_PARAM_1 0x01F4
#define I2S_COMP_VERSION 0x01F8
#define I2S_COMP_TYPE 0x01FC
/*
* Component parameter register fields - define the I2S block's
* configuration.
*/
#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25)
#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22)
#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19)
#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16)
#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9)
#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7)
#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6)
#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5)
#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4)
#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2)
#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0)
#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10)
#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7)
#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3)
#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0)
/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */
#define COMP_MAX_WORDSIZE (1 << 3)
#define COMP_MAX_DATA_WIDTH (1 << 2)
#define MAX_CHANNEL_NUM 8
#define MIN_CHANNEL_NUM 2
union dw_i2s_snd_dma_data {
struct i2s_dma_data pd;
struct snd_dmaengine_dai_dma_data dt;
};
struct dw_i2s_dev {
void __iomem *i2s_base;
struct clk *clk;
int active;
unsigned int capability;
unsigned int quirks;
unsigned int i2s_reg_comp1;
unsigned int i2s_reg_comp2;
struct device *dev;
u32 ccr;
u32 xfer_resolution;
u32 fifo_th;
/* data related to DMA transfers b/w i2s and DMAC */
union dw_i2s_snd_dma_data play_dma_data;
union dw_i2s_snd_dma_data capture_dma_data;
struct i2s_clk_config_data config;
int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
/* data related to PIO transfers (TX) */
bool use_pio;
struct snd_pcm_substream __rcu *tx_substream;
unsigned int (*tx_fn)(struct dw_i2s_dev *dev,
struct snd_pcm_runtime *runtime, unsigned int tx_ptr,
bool *period_elapsed);
unsigned int tx_ptr;
};
#if IS_ENABLED(CONFIG_SND_DESIGNWARE_PCM)
void dw_pcm_push_tx(struct dw_i2s_dev *dev);
int dw_pcm_register(struct platform_device *pdev);
#else
void dw_pcm_push_tx(struct dw_i2s_dev *dev) { }
int dw_pcm_register(struct platform_device *pdev)
{
return -EINVAL;
}
#endif
#endif
...@@ -4,6 +4,7 @@ comment "Common SoC Audio options for Freescale CPUs:" ...@@ -4,6 +4,7 @@ comment "Common SoC Audio options for Freescale CPUs:"
config SND_SOC_FSL_ASRC config SND_SOC_FSL_ASRC
tristate "Asynchronous Sample Rate Converter (ASRC) module support" tristate "Asynchronous Sample Rate Converter (ASRC) module support"
depends on HAS_DMA
select REGMAP_MMIO select REGMAP_MMIO
select SND_SOC_GENERIC_DMAENGINE_PCM select SND_SOC_GENERIC_DMAENGINE_PCM
help help
......
...@@ -1073,7 +1073,11 @@ static int dapm_widget_list_create(struct snd_soc_dapm_widget_list **list, ...@@ -1073,7 +1073,11 @@ static int dapm_widget_list_create(struct snd_soc_dapm_widget_list **list,
*/ */
static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
struct list_head *list, enum snd_soc_dapm_direction dir, struct list_head *list, enum snd_soc_dapm_direction dir,
int (*fn)(struct snd_soc_dapm_widget *, struct list_head *)) int (*fn)(struct snd_soc_dapm_widget *, struct list_head *,
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
enum snd_soc_dapm_direction)),
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
enum snd_soc_dapm_direction))
{ {
enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir);
struct snd_soc_dapm_path *path; struct snd_soc_dapm_path *path;
...@@ -1088,6 +1092,11 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, ...@@ -1088,6 +1092,11 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
if (list) if (list)
list_add_tail(&widget->work_list, list); list_add_tail(&widget->work_list, list);
if (custom_stop_condition && custom_stop_condition(widget, dir)) {
widget->endpoints[dir] = 1;
return widget->endpoints[dir];
}
if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) { if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) {
widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget); widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget);
return widget->endpoints[dir]; return widget->endpoints[dir];
...@@ -1106,7 +1115,7 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, ...@@ -1106,7 +1115,7 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
if (path->connect) { if (path->connect) {
path->walking = 1; path->walking = 1;
con += fn(path->node[dir], list); con += fn(path->node[dir], list, custom_stop_condition);
path->walking = 0; path->walking = 0;
} }
} }
...@@ -1119,23 +1128,37 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, ...@@ -1119,23 +1128,37 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget,
/* /*
* Recursively check for a completed path to an active or physically connected * Recursively check for a completed path to an active or physically connected
* output widget. Returns number of complete paths. * output widget. Returns number of complete paths.
*
* Optionally, can be supplied with a function acting as a stopping condition.
* This function takes the dapm widget currently being examined and the walk
* direction as an arguments, it should return true if the walk should be
* stopped and false otherwise.
*/ */
static int is_connected_output_ep(struct snd_soc_dapm_widget *widget, static int is_connected_output_ep(struct snd_soc_dapm_widget *widget,
struct list_head *list) struct list_head *list,
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i,
enum snd_soc_dapm_direction))
{ {
return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT, return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT,
is_connected_output_ep); is_connected_output_ep, custom_stop_condition);
} }
/* /*
* Recursively check for a completed path to an active or physically connected * Recursively check for a completed path to an active or physically connected
* input widget. Returns number of complete paths. * input widget. Returns number of complete paths.
*
* Optionally, can be supplied with a function acting as a stopping condition.
* This function takes the dapm widget currently being examined and the walk
* direction as an arguments, it should return true if the walk should be
* stopped and false otherwise.
*/ */
static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
struct list_head *list) struct list_head *list,
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i,
enum snd_soc_dapm_direction))
{ {
return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN, return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN,
is_connected_input_ep); is_connected_input_ep, custom_stop_condition);
} }
/** /**
...@@ -1143,15 +1166,24 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, ...@@ -1143,15 +1166,24 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
* @dai: the soc DAI. * @dai: the soc DAI.
* @stream: stream direction. * @stream: stream direction.
* @list: list of active widgets for this stream. * @list: list of active widgets for this stream.
* @custom_stop_condition: (optional) a function meant to stop the widget graph
* walk based on custom logic.
* *
* Queries DAPM graph as to whether an valid audio stream path exists for * Queries DAPM graph as to whether an valid audio stream path exists for
* the initial stream specified by name. This takes into account * the initial stream specified by name. This takes into account
* current mixer and mux kcontrol settings. Creates list of valid widgets. * current mixer and mux kcontrol settings. Creates list of valid widgets.
* *
* Optionally, can be supplied with a function acting as a stopping condition.
* This function takes the dapm widget currently being examined and the walk
* direction as an arguments, it should return true if the walk should be
* stopped and false otherwise.
*
* Returns the number of valid paths or negative error. * Returns the number of valid paths or negative error.
*/ */
int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
struct snd_soc_dapm_widget_list **list) struct snd_soc_dapm_widget_list **list,
bool (*custom_stop_condition)(struct snd_soc_dapm_widget *,
enum snd_soc_dapm_direction))
{ {
struct snd_soc_card *card = dai->component->card; struct snd_soc_card *card = dai->component->card;
struct snd_soc_dapm_widget *w; struct snd_soc_dapm_widget *w;
...@@ -1171,9 +1203,11 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, ...@@ -1171,9 +1203,11 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
} }
if (stream == SNDRV_PCM_STREAM_PLAYBACK) if (stream == SNDRV_PCM_STREAM_PLAYBACK)
paths = is_connected_output_ep(dai->playback_widget, &widgets); paths = is_connected_output_ep(dai->playback_widget, &widgets,
custom_stop_condition);
else else
paths = is_connected_input_ep(dai->capture_widget, &widgets); paths = is_connected_input_ep(dai->capture_widget, &widgets,
custom_stop_condition);
/* Drop starting point */ /* Drop starting point */
list_del(widgets.next); list_del(widgets.next);
...@@ -1268,8 +1302,8 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) ...@@ -1268,8 +1302,8 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
DAPM_UPDATE_STAT(w, power_checks); DAPM_UPDATE_STAT(w, power_checks);
in = is_connected_input_ep(w, NULL); in = is_connected_input_ep(w, NULL, NULL);
out = is_connected_output_ep(w, NULL); out = is_connected_output_ep(w, NULL, NULL);
return out != 0 && in != 0; return out != 0 && in != 0;
} }
...@@ -1928,8 +1962,8 @@ static ssize_t dapm_widget_power_read_file(struct file *file, ...@@ -1928,8 +1962,8 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
in = 0; in = 0;
out = 0; out = 0;
} else { } else {
in = is_connected_input_ep(w, NULL); in = is_connected_input_ep(w, NULL, NULL);
out = is_connected_output_ep(w, NULL); out = is_connected_output_ep(w, NULL, NULL);
} }
ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d", ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d",
......
...@@ -1287,6 +1287,46 @@ static int widget_in_list(struct snd_soc_dapm_widget_list *list, ...@@ -1287,6 +1287,46 @@ static int widget_in_list(struct snd_soc_dapm_widget_list *list,
return 0; return 0;
} }
static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget,
enum snd_soc_dapm_direction dir)
{
struct snd_soc_card *card = widget->dapm->card;
struct snd_soc_pcm_runtime *rtd;
int i;
if (dir == SND_SOC_DAPM_DIR_OUT) {
list_for_each_entry(rtd, &card->rtd_list, list) {
if (!rtd->dai_link->no_pcm)
continue;
if (rtd->cpu_dai->playback_widget == widget)
return true;
for (i = 0; i < rtd->num_codecs; ++i) {
struct snd_soc_dai *dai = rtd->codec_dais[i];
if (dai->playback_widget == widget)
return true;
}
}
} else { /* SND_SOC_DAPM_DIR_IN */
list_for_each_entry(rtd, &card->rtd_list, list) {
if (!rtd->dai_link->no_pcm)
continue;
if (rtd->cpu_dai->capture_widget == widget)
return true;
for (i = 0; i < rtd->num_codecs; ++i) {
struct snd_soc_dai *dai = rtd->codec_dais[i];
if (dai->capture_widget == widget)
return true;
}
}
}
return false;
}
int dpcm_path_get(struct snd_soc_pcm_runtime *fe, int dpcm_path_get(struct snd_soc_pcm_runtime *fe,
int stream, struct snd_soc_dapm_widget_list **list) int stream, struct snd_soc_dapm_widget_list **list)
{ {
...@@ -1294,7 +1334,8 @@ int dpcm_path_get(struct snd_soc_pcm_runtime *fe, ...@@ -1294,7 +1334,8 @@ int dpcm_path_get(struct snd_soc_pcm_runtime *fe,
int paths; int paths;
/* get number of valid DAI paths and their widgets */ /* get number of valid DAI paths and their widgets */
paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list); paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list,
dpcm_end_walk_at_be);
dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths, dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths,
stream ? "capture" : "playback"); stream ? "capture" : "playback");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment