Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Xilinx"
config SND_SOC_XILINX_I2S
tristate "Audio support for the Xilinx I2S"
help
Select this option to enable Xilinx I2S Audio. This enables
I2S playback and capture using xilinx soft IP. In transmitter
mode, IP receives audio in AES format, extracts PCM and sends
PCM data. In receiver mode, IP receives PCM audio and
encapsulates PCM in AES format and sends AES data.
config SND_SOC_XILINX_AUDIO_FORMATTER
tristate "Audio support for the Xilinx audio formatter"
help
Select this option to enable Xilinx audio formatter
support. This provides DMA platform device support for
audio functionality.
config SND_SOC_XILINX_SPDIF
tristate "Audio support for the Xilinx SPDIF"
help
Select this option to enable Xilinx SPDIF Audio.
This provides playback and capture of SPDIF audio in
AES format.
endmenu
+7
View File
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
snd-soc-xlnx-i2s-y := xlnx_i2s.o
obj-$(CONFIG_SND_SOC_XILINX_I2S) += snd-soc-xlnx-i2s.o
snd-soc-xlnx-formatter-pcm-y := xlnx_formatter_pcm.o
obj-$(CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER) += snd-soc-xlnx-formatter-pcm.o
snd-soc-xlnx-spdif-y := xlnx_spdif.o
obj-$(CONFIG_SND_SOC_XILINX_SPDIF) += snd-soc-xlnx-spdif.o
@@ -0,0 +1,725 @@
// SPDX-License-Identifier: GPL-2.0
//
// Xilinx ASoC audio formatter support
//
// Copyright (C) 2018 Xilinx, Inc.
//
// Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/sizes.h>
#include <sound/asoundef.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#define DRV_NAME "xlnx_formatter_pcm"
#define XLNX_S2MM_OFFSET 0
#define XLNX_MM2S_OFFSET 0x100
#define XLNX_AUD_CORE_CONFIG 0x4
#define XLNX_AUD_CTRL 0x10
#define XLNX_AUD_STS 0x14
#define AUD_CTRL_RESET_MASK BIT(1)
#define AUD_CFG_MM2S_MASK BIT(15)
#define AUD_CFG_S2MM_MASK BIT(31)
#define XLNX_AUD_FS_MULTIPLIER 0x18
#define XLNX_AUD_PERIOD_CONFIG 0x1C
#define XLNX_AUD_BUFF_ADDR_LSB 0x20
#define XLNX_AUD_BUFF_ADDR_MSB 0x24
#define XLNX_AUD_XFER_COUNT 0x28
#define XLNX_AUD_CH_STS_START 0x2C
#define XLNX_BYTES_PER_CH 0x44
#define XLNX_AUD_ALIGN_BYTES 64
#define AUD_STS_IOC_IRQ_MASK BIT(31)
#define AUD_STS_CH_STS_MASK BIT(29)
#define AUD_CTRL_IOC_IRQ_MASK BIT(13)
#define AUD_CTRL_TOUT_IRQ_MASK BIT(14)
#define AUD_CTRL_DMA_EN_MASK BIT(0)
#define CFG_MM2S_CH_MASK GENMASK(11, 8)
#define CFG_MM2S_CH_SHIFT 8
#define CFG_MM2S_XFER_MASK GENMASK(14, 13)
#define CFG_MM2S_XFER_SHIFT 13
#define CFG_MM2S_PKG_MASK BIT(12)
#define CFG_S2MM_CH_MASK GENMASK(27, 24)
#define CFG_S2MM_CH_SHIFT 24
#define CFG_S2MM_XFER_MASK GENMASK(30, 29)
#define CFG_S2MM_XFER_SHIFT 29
#define CFG_S2MM_PKG_MASK BIT(28)
#define AUD_CTRL_DATA_WIDTH_SHIFT 16
#define AUD_CTRL_ACTIVE_CH_SHIFT 19
#define PERIOD_CFG_PERIODS_SHIFT 16
#define PERIODS_MIN 2
#define PERIODS_MAX 6
#define PERIOD_BYTES_MIN 192
#define PERIOD_BYTES_MAX (50 * 1024)
#define XLNX_PARAM_UNKNOWN 0
enum bit_depth {
BIT_DEPTH_8,
BIT_DEPTH_16,
BIT_DEPTH_20,
BIT_DEPTH_24,
BIT_DEPTH_32,
};
struct xlnx_pcm_drv_data {
void __iomem *mmio;
bool s2mm_presence;
bool mm2s_presence;
int s2mm_irq;
int mm2s_irq;
struct snd_pcm_substream *play_stream;
struct snd_pcm_substream *capture_stream;
struct clk *axi_clk;
unsigned int sysclk;
};
/*
* struct xlnx_pcm_stream_param - stream configuration
* @mmio: base address offset
* @interleaved: audio channels arrangement in buffer
* @xfer_mode: data formatting mode during transfer
* @ch_limit: Maximum channels supported
* @buffer_size: stream ring buffer size
*/
struct xlnx_pcm_stream_param {
void __iomem *mmio;
bool interleaved;
u32 xfer_mode;
u32 ch_limit;
u64 buffer_size;
};
static const struct snd_pcm_hardware xlnx_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.rate_min = 8000,
.rate_max = 192000,
.buffer_bytes_max = PERIODS_MAX * PERIOD_BYTES_MAX,
.period_bytes_min = PERIOD_BYTES_MIN,
.period_bytes_max = PERIOD_BYTES_MAX,
.periods_min = PERIODS_MIN,
.periods_max = PERIODS_MAX,
};
enum {
AES_TO_AES,
AES_TO_PCM,
PCM_TO_PCM,
PCM_TO_AES
};
static void xlnx_parse_aes_params(u32 chsts_reg1_val, u32 chsts_reg2_val,
struct device *dev)
{
u32 padded, srate, bit_depth, status[2];
if (chsts_reg1_val & IEC958_AES0_PROFESSIONAL) {
status[0] = chsts_reg1_val & 0xff;
status[1] = (chsts_reg1_val >> 16) & 0xff;
switch (status[0] & IEC958_AES0_PRO_FS) {
case IEC958_AES0_PRO_FS_44100:
srate = 44100;
break;
case IEC958_AES0_PRO_FS_48000:
srate = 48000;
break;
case IEC958_AES0_PRO_FS_32000:
srate = 32000;
break;
case IEC958_AES0_PRO_FS_NOTID:
default:
srate = XLNX_PARAM_UNKNOWN;
break;
}
switch (status[1] & IEC958_AES2_PRO_SBITS) {
case IEC958_AES2_PRO_WORDLEN_NOTID:
case IEC958_AES2_PRO_SBITS_20:
padded = 0;
break;
case IEC958_AES2_PRO_SBITS_24:
padded = 4;
break;
default:
bit_depth = XLNX_PARAM_UNKNOWN;
goto log_params;
}
switch (status[1] & IEC958_AES2_PRO_WORDLEN) {
case IEC958_AES2_PRO_WORDLEN_20_16:
bit_depth = 16 + padded;
break;
case IEC958_AES2_PRO_WORDLEN_22_18:
bit_depth = 18 + padded;
break;
case IEC958_AES2_PRO_WORDLEN_23_19:
bit_depth = 19 + padded;
break;
case IEC958_AES2_PRO_WORDLEN_24_20:
bit_depth = 20 + padded;
break;
case IEC958_AES2_PRO_WORDLEN_NOTID:
default:
bit_depth = XLNX_PARAM_UNKNOWN;
break;
}
} else {
status[0] = (chsts_reg1_val >> 24) & 0xff;
status[1] = chsts_reg2_val & 0xff;
switch (status[0] & IEC958_AES3_CON_FS) {
case IEC958_AES3_CON_FS_44100:
srate = 44100;
break;
case IEC958_AES3_CON_FS_48000:
srate = 48000;
break;
case IEC958_AES3_CON_FS_32000:
srate = 32000;
break;
default:
srate = XLNX_PARAM_UNKNOWN;
break;
}
if (status[1] & IEC958_AES4_CON_MAX_WORDLEN_24)
padded = 4;
else
padded = 0;
switch (status[1] & IEC958_AES4_CON_WORDLEN) {
case IEC958_AES4_CON_WORDLEN_20_16:
bit_depth = 16 + padded;
break;
case IEC958_AES4_CON_WORDLEN_22_18:
bit_depth = 18 + padded;
break;
case IEC958_AES4_CON_WORDLEN_23_19:
bit_depth = 19 + padded;
break;
case IEC958_AES4_CON_WORDLEN_24_20:
bit_depth = 20 + padded;
break;
case IEC958_AES4_CON_WORDLEN_21_17:
bit_depth = 17 + padded;
break;
case IEC958_AES4_CON_WORDLEN_NOTID:
default:
bit_depth = XLNX_PARAM_UNKNOWN;
break;
}
}
log_params:
if (srate != XLNX_PARAM_UNKNOWN)
dev_info(dev, "sample rate = %d\n", srate);
else
dev_info(dev, "sample rate = unknown\n");
if (bit_depth != XLNX_PARAM_UNKNOWN)
dev_info(dev, "bit_depth = %d\n", bit_depth);
else
dev_info(dev, "bit_depth = unknown\n");
}
static int xlnx_formatter_pcm_reset(void __iomem *mmio_base)
{
u32 val, retries = 0;
val = readl(mmio_base + XLNX_AUD_CTRL);
val |= AUD_CTRL_RESET_MASK;
writel(val, mmio_base + XLNX_AUD_CTRL);
val = readl(mmio_base + XLNX_AUD_CTRL);
/* Poll for maximum timeout of approximately 100ms (1 * 100)*/
while ((val & AUD_CTRL_RESET_MASK) && (retries < 100)) {
mdelay(1);
retries++;
val = readl(mmio_base + XLNX_AUD_CTRL);
}
if (val & AUD_CTRL_RESET_MASK)
return -ENODEV;
return 0;
}
static void xlnx_formatter_disable_irqs(void __iomem *mmio_base, int stream)
{
u32 val;
val = readl(mmio_base + XLNX_AUD_CTRL);
val &= ~AUD_CTRL_IOC_IRQ_MASK;
if (stream == SNDRV_PCM_STREAM_CAPTURE)
val &= ~AUD_CTRL_TOUT_IRQ_MASK;
writel(val, mmio_base + XLNX_AUD_CTRL);
}
static irqreturn_t xlnx_mm2s_irq_handler(int irq, void *arg)
{
u32 val;
void __iomem *reg;
struct xlnx_pcm_drv_data *adata = arg;
reg = adata->mmio + XLNX_MM2S_OFFSET + XLNX_AUD_STS;
val = readl(reg);
if (val & AUD_STS_IOC_IRQ_MASK) {
writel(val & AUD_STS_IOC_IRQ_MASK, reg);
if (adata->play_stream)
snd_pcm_period_elapsed(adata->play_stream);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static irqreturn_t xlnx_s2mm_irq_handler(int irq, void *arg)
{
u32 val;
void __iomem *reg;
struct xlnx_pcm_drv_data *adata = arg;
reg = adata->mmio + XLNX_S2MM_OFFSET + XLNX_AUD_STS;
val = readl(reg);
if (val & AUD_STS_IOC_IRQ_MASK) {
writel(val & AUD_STS_IOC_IRQ_MASK, reg);
if (adata->capture_stream)
snd_pcm_period_elapsed(adata->capture_stream);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int xlnx_formatter_set_sysclk(struct snd_soc_component *component,
int clk_id, int source, unsigned int freq, int dir)
{
struct xlnx_pcm_drv_data *adata = dev_get_drvdata(component->dev);
adata->sysclk = freq;
return 0;
}
static int xlnx_formatter_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
int err;
u32 val, data_format_mode;
u32 ch_count_mask, ch_count_shift, data_xfer_mode, data_xfer_shift;
struct xlnx_pcm_stream_param *stream_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct xlnx_pcm_drv_data *adata = dev_get_drvdata(component->dev);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
!adata->mm2s_presence)
return -ENODEV;
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE &&
!adata->s2mm_presence)
return -ENODEV;
stream_data = kzalloc_obj(*stream_data);
if (!stream_data)
return -ENOMEM;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ch_count_mask = CFG_MM2S_CH_MASK;
ch_count_shift = CFG_MM2S_CH_SHIFT;
data_xfer_mode = CFG_MM2S_XFER_MASK;
data_xfer_shift = CFG_MM2S_XFER_SHIFT;
data_format_mode = CFG_MM2S_PKG_MASK;
stream_data->mmio = adata->mmio + XLNX_MM2S_OFFSET;
adata->play_stream = substream;
} else {
ch_count_mask = CFG_S2MM_CH_MASK;
ch_count_shift = CFG_S2MM_CH_SHIFT;
data_xfer_mode = CFG_S2MM_XFER_MASK;
data_xfer_shift = CFG_S2MM_XFER_SHIFT;
data_format_mode = CFG_S2MM_PKG_MASK;
stream_data->mmio = adata->mmio + XLNX_S2MM_OFFSET;
adata->capture_stream = substream;
}
val = readl(adata->mmio + XLNX_AUD_CORE_CONFIG);
if (!(val & data_format_mode))
stream_data->interleaved = true;
stream_data->xfer_mode = (val & data_xfer_mode) >> data_xfer_shift;
stream_data->ch_limit = (val & ch_count_mask) >> ch_count_shift;
dev_info(component->dev,
"stream %d : format = %d mode = %d ch_limit = %d\n",
substream->stream, stream_data->interleaved,
stream_data->xfer_mode, stream_data->ch_limit);
snd_soc_set_runtime_hwparams(substream, &xlnx_pcm_hardware);
runtime->private_data = stream_data;
/* Resize the period bytes as divisible by 64 */
err = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
XLNX_AUD_ALIGN_BYTES);
if (err) {
dev_err(component->dev,
"Unable to set constraint on period bytes\n");
return err;
}
/* Resize the buffer bytes as divisible by 64 */
err = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
XLNX_AUD_ALIGN_BYTES);
if (err) {
dev_err(component->dev,
"Unable to set constraint on buffer bytes\n");
return err;
}
/* Set periods as integer multiple */
err = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0) {
dev_err(component->dev,
"Unable to set constraint on periods to be integer\n");
return err;
}
/* enable DMA IOC irq */
val = readl(stream_data->mmio + XLNX_AUD_CTRL);
val |= AUD_CTRL_IOC_IRQ_MASK;
writel(val, stream_data->mmio + XLNX_AUD_CTRL);
return 0;
}
static int xlnx_formatter_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
int ret;
struct xlnx_pcm_stream_param *stream_data =
substream->runtime->private_data;
ret = xlnx_formatter_pcm_reset(stream_data->mmio);
if (ret) {
dev_err(component->dev, "audio formatter reset failed\n");
goto err_reset;
}
xlnx_formatter_disable_irqs(stream_data->mmio, substream->stream);
err_reset:
kfree(stream_data);
return 0;
}
static snd_pcm_uframes_t
xlnx_formatter_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
u32 pos;
struct snd_pcm_runtime *runtime = substream->runtime;
struct xlnx_pcm_stream_param *stream_data = runtime->private_data;
pos = readl(stream_data->mmio + XLNX_AUD_XFER_COUNT);
if (pos >= stream_data->buffer_size)
pos = 0;
return bytes_to_frames(runtime, pos);
}
static int xlnx_formatter_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
u32 low, high, active_ch, val, bytes_per_ch, bits_per_sample;
u32 aes_reg1_val, aes_reg2_val;
u64 size;
struct snd_pcm_runtime *runtime = substream->runtime;
struct xlnx_pcm_stream_param *stream_data = runtime->private_data;
struct xlnx_pcm_drv_data *adata = dev_get_drvdata(component->dev);
active_ch = params_channels(params);
if (active_ch > stream_data->ch_limit)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
adata->sysclk) {
unsigned int mclk_fs = adata->sysclk / params_rate(params);
if (adata->sysclk % params_rate(params) != 0) {
dev_warn(component->dev, "sysclk %u not divisible by rate %u\n",
adata->sysclk, params_rate(params));
return -EINVAL;
}
writel(mclk_fs, stream_data->mmio + XLNX_AUD_FS_MULTIPLIER);
}
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE &&
stream_data->xfer_mode == AES_TO_PCM) {
val = readl(stream_data->mmio + XLNX_AUD_STS);
if (val & AUD_STS_CH_STS_MASK) {
aes_reg1_val = readl(stream_data->mmio +
XLNX_AUD_CH_STS_START);
aes_reg2_val = readl(stream_data->mmio +
XLNX_AUD_CH_STS_START + 0x4);
xlnx_parse_aes_params(aes_reg1_val, aes_reg2_val,
component->dev);
}
}
size = params_buffer_bytes(params);
stream_data->buffer_size = size;
low = lower_32_bits(runtime->dma_addr);
high = upper_32_bits(runtime->dma_addr);
writel(low, stream_data->mmio + XLNX_AUD_BUFF_ADDR_LSB);
writel(high, stream_data->mmio + XLNX_AUD_BUFF_ADDR_MSB);
val = readl(stream_data->mmio + XLNX_AUD_CTRL);
bits_per_sample = params_width(params);
switch (bits_per_sample) {
case 8:
val |= (BIT_DEPTH_8 << AUD_CTRL_DATA_WIDTH_SHIFT);
break;
case 16:
val |= (BIT_DEPTH_16 << AUD_CTRL_DATA_WIDTH_SHIFT);
break;
case 20:
val |= (BIT_DEPTH_20 << AUD_CTRL_DATA_WIDTH_SHIFT);
break;
case 24:
val |= (BIT_DEPTH_24 << AUD_CTRL_DATA_WIDTH_SHIFT);
break;
case 32:
val |= (BIT_DEPTH_32 << AUD_CTRL_DATA_WIDTH_SHIFT);
break;
default:
return -EINVAL;
}
val |= active_ch << AUD_CTRL_ACTIVE_CH_SHIFT;
writel(val, stream_data->mmio + XLNX_AUD_CTRL);
val = (params_periods(params) << PERIOD_CFG_PERIODS_SHIFT)
| params_period_bytes(params);
writel(val, stream_data->mmio + XLNX_AUD_PERIOD_CONFIG);
bytes_per_ch = DIV_ROUND_UP(params_period_bytes(params), active_ch);
writel(bytes_per_ch, stream_data->mmio + XLNX_BYTES_PER_CH);
return 0;
}
static int xlnx_formatter_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
int cmd)
{
u32 val;
struct xlnx_pcm_stream_param *stream_data =
substream->runtime->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
val = readl(stream_data->mmio + XLNX_AUD_CTRL);
val |= AUD_CTRL_DMA_EN_MASK;
writel(val, stream_data->mmio + XLNX_AUD_CTRL);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
val = readl(stream_data->mmio + XLNX_AUD_CTRL);
val &= ~AUD_CTRL_DMA_EN_MASK;
writel(val, stream_data->mmio + XLNX_AUD_CTRL);
break;
}
return 0;
}
static int xlnx_formatter_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
snd_pcm_set_managed_buffer_all(rtd->pcm,
SNDRV_DMA_TYPE_DEV, component->dev,
xlnx_pcm_hardware.buffer_bytes_max,
xlnx_pcm_hardware.buffer_bytes_max);
return 0;
}
static const struct snd_soc_component_driver xlnx_asoc_component = {
.name = DRV_NAME,
.set_sysclk = xlnx_formatter_set_sysclk,
.open = xlnx_formatter_pcm_open,
.close = xlnx_formatter_pcm_close,
.hw_params = xlnx_formatter_pcm_hw_params,
.trigger = xlnx_formatter_pcm_trigger,
.pointer = xlnx_formatter_pcm_pointer,
.pcm_new = xlnx_formatter_pcm_new,
};
static int xlnx_formatter_pcm_probe(struct platform_device *pdev)
{
int ret;
u32 val;
struct xlnx_pcm_drv_data *aud_drv_data;
struct device *dev = &pdev->dev;
aud_drv_data = devm_kzalloc(dev, sizeof(*aud_drv_data), GFP_KERNEL);
if (!aud_drv_data)
return -ENOMEM;
aud_drv_data->axi_clk = devm_clk_get(dev, "s_axi_lite_aclk");
if (IS_ERR(aud_drv_data->axi_clk)) {
ret = PTR_ERR(aud_drv_data->axi_clk);
dev_err(dev, "failed to get s_axi_lite_aclk(%d)\n", ret);
return ret;
}
ret = clk_prepare_enable(aud_drv_data->axi_clk);
if (ret) {
dev_err(dev,
"failed to enable s_axi_lite_aclk(%d)\n", ret);
return ret;
}
aud_drv_data->mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(aud_drv_data->mmio)) {
dev_err(dev, "audio formatter ioremap failed\n");
ret = PTR_ERR(aud_drv_data->mmio);
goto clk_err;
}
val = readl(aud_drv_data->mmio + XLNX_AUD_CORE_CONFIG);
if (val & AUD_CFG_MM2S_MASK) {
aud_drv_data->mm2s_presence = true;
ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio +
XLNX_MM2S_OFFSET);
if (ret) {
dev_err(dev, "audio formatter reset failed\n");
goto clk_err;
}
xlnx_formatter_disable_irqs(aud_drv_data->mmio +
XLNX_MM2S_OFFSET,
SNDRV_PCM_STREAM_PLAYBACK);
aud_drv_data->mm2s_irq = platform_get_irq_byname(pdev,
"irq_mm2s");
if (aud_drv_data->mm2s_irq < 0) {
ret = aud_drv_data->mm2s_irq;
goto clk_err;
}
ret = devm_request_irq(dev, aud_drv_data->mm2s_irq,
xlnx_mm2s_irq_handler, 0,
"xlnx_formatter_pcm_mm2s_irq", aud_drv_data);
if (ret) {
dev_err(dev, "xlnx audio mm2s irq request failed\n");
goto clk_err;
}
}
if (val & AUD_CFG_S2MM_MASK) {
aud_drv_data->s2mm_presence = true;
ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio +
XLNX_S2MM_OFFSET);
if (ret) {
dev_err(dev, "audio formatter reset failed\n");
goto clk_err;
}
xlnx_formatter_disable_irqs(aud_drv_data->mmio +
XLNX_S2MM_OFFSET,
SNDRV_PCM_STREAM_CAPTURE);
aud_drv_data->s2mm_irq = platform_get_irq_byname(pdev,
"irq_s2mm");
if (aud_drv_data->s2mm_irq < 0) {
ret = aud_drv_data->s2mm_irq;
goto clk_err;
}
ret = devm_request_irq(dev, aud_drv_data->s2mm_irq,
xlnx_s2mm_irq_handler, 0,
"xlnx_formatter_pcm_s2mm_irq",
aud_drv_data);
if (ret) {
dev_err(dev, "xlnx audio s2mm irq request failed\n");
goto clk_err;
}
}
dev_set_drvdata(dev, aud_drv_data);
ret = devm_snd_soc_register_component(dev, &xlnx_asoc_component,
NULL, 0);
if (ret) {
dev_err(dev, "pcm platform device register failed\n");
goto clk_err;
}
return 0;
clk_err:
clk_disable_unprepare(aud_drv_data->axi_clk);
return ret;
}
static void xlnx_formatter_pcm_remove(struct platform_device *pdev)
{
int ret = 0;
struct xlnx_pcm_drv_data *adata = dev_get_drvdata(&pdev->dev);
if (adata->s2mm_presence)
ret = xlnx_formatter_pcm_reset(adata->mmio + XLNX_S2MM_OFFSET);
/* Try MM2S reset, even if S2MM reset fails */
if (adata->mm2s_presence)
ret = xlnx_formatter_pcm_reset(adata->mmio + XLNX_MM2S_OFFSET);
if (ret)
dev_err(&pdev->dev, "audio formatter reset failed\n");
clk_disable_unprepare(adata->axi_clk);
}
static const struct of_device_id xlnx_formatter_pcm_of_match[] = {
{ .compatible = "xlnx,audio-formatter-1.0"},
{},
};
MODULE_DEVICE_TABLE(of, xlnx_formatter_pcm_of_match);
static struct platform_driver xlnx_formatter_pcm_driver = {
.probe = xlnx_formatter_pcm_probe,
.remove = xlnx_formatter_pcm_remove,
.driver = {
.name = DRV_NAME,
.of_match_table = xlnx_formatter_pcm_of_match,
},
};
module_platform_driver(xlnx_formatter_pcm_driver);
MODULE_DESCRIPTION("ASoC driver for Xilinx audio formatter");
MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");
MODULE_LICENSE("GPL v2");
+259
View File
@@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0
//
// Xilinx ASoC I2S audio support
//
// Copyright (C) 2018 Xilinx, Inc.
//
// Author: Praveen Vuppala <praveenv@xilinx.com>
// Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define DRV_NAME "xlnx_i2s"
#define I2S_CORE_CTRL_OFFSET 0x08
#define I2S_CORE_CTRL_32BIT_LRCLK BIT(3)
#define I2S_CORE_CTRL_ENABLE BIT(0)
#define I2S_I2STIM_OFFSET 0x20
#define I2S_CH0_OFFSET 0x30
#define I2S_I2STIM_VALID_MASK GENMASK(7, 0)
struct xlnx_i2s_drv_data {
struct snd_soc_dai_driver dai_drv;
void __iomem *base;
unsigned int sysclk;
u32 data_width;
u32 channels;
bool is_32bit_lrclk;
struct snd_ratnum ratnum;
struct snd_pcm_hw_constraint_ratnums rate_constraints;
};
static int xlnx_i2s_set_sclkout_div(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(cpu_dai);
if (!div || (div & ~I2S_I2STIM_VALID_MASK))
return -EINVAL;
drv_data->sysclk = 0;
writel(div, drv_data->base + I2S_I2STIM_OFFSET);
return 0;
}
static int xlnx_i2s_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(dai);
drv_data->sysclk = freq;
if (freq) {
unsigned int bits_per_sample;
if (drv_data->is_32bit_lrclk)
bits_per_sample = 32;
else
bits_per_sample = drv_data->data_width;
drv_data->ratnum.num = freq / (bits_per_sample * drv_data->channels) / 2;
drv_data->ratnum.den_step = 1;
drv_data->ratnum.den_min = 1;
drv_data->ratnum.den_max = 255;
drv_data->rate_constraints.rats = &drv_data->ratnum;
drv_data->rate_constraints.nrats = 1;
}
return 0;
}
static int xlnx_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(dai);
if (drv_data->sysclk)
return snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&drv_data->rate_constraints);
return 0;
}
static int xlnx_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *i2s_dai)
{
u32 reg_off, chan_id;
struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(i2s_dai);
if (drv_data->sysclk) {
unsigned int bits_per_sample, sclk, sclk_div;
if (drv_data->is_32bit_lrclk)
bits_per_sample = 32;
else
bits_per_sample = drv_data->data_width;
sclk = params_rate(params) * bits_per_sample * params_channels(params);
sclk_div = drv_data->sysclk / sclk / 2;
if ((drv_data->sysclk % sclk != 0) ||
!sclk_div || (sclk_div & ~I2S_I2STIM_VALID_MASK)) {
dev_warn(i2s_dai->dev, "invalid SCLK divisor for sysclk %u and sclk %u\n",
drv_data->sysclk, sclk);
return -EINVAL;
}
writel(sclk_div, drv_data->base + I2S_I2STIM_OFFSET);
}
chan_id = params_channels(params) / 2;
while (chan_id > 0) {
reg_off = I2S_CH0_OFFSET + ((chan_id - 1) * 4);
writel(chan_id, drv_data->base + reg_off);
chan_id--;
}
return 0;
}
static int xlnx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *i2s_dai)
{
struct xlnx_i2s_drv_data *drv_data = snd_soc_dai_get_drvdata(i2s_dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
writel(I2S_CORE_CTRL_ENABLE, drv_data->base + I2S_CORE_CTRL_OFFSET);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
writel(0, drv_data->base + I2S_CORE_CTRL_OFFSET);
break;
default:
return -EINVAL;
}
return 0;
}
static const struct snd_soc_dai_ops xlnx_i2s_dai_ops = {
.trigger = xlnx_i2s_trigger,
.set_sysclk = xlnx_i2s_set_sysclk,
.set_clkdiv = xlnx_i2s_set_sclkout_div,
.startup = xlnx_i2s_startup,
.hw_params = xlnx_i2s_hw_params
};
static const struct snd_soc_component_driver xlnx_i2s_component = {
.name = DRV_NAME,
.legacy_dai_naming = 1,
};
static const struct of_device_id xlnx_i2s_of_match[] = {
{ .compatible = "xlnx,i2s-transmitter-1.0", },
{ .compatible = "xlnx,i2s-receiver-1.0", },
{},
};
MODULE_DEVICE_TABLE(of, xlnx_i2s_of_match);
static int xlnx_i2s_probe(struct platform_device *pdev)
{
struct xlnx_i2s_drv_data *drv_data;
int ret;
u32 format;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL);
if (!drv_data)
return -ENOMEM;
drv_data->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(drv_data->base))
return PTR_ERR(drv_data->base);
ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels);
if (ret < 0) {
dev_err(dev, "cannot get supported channels\n");
return ret;
}
drv_data->channels *= 2;
ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width);
if (ret < 0) {
dev_err(dev, "cannot get data width\n");
return ret;
}
switch (drv_data->data_width) {
case 16:
format = SNDRV_PCM_FMTBIT_S16_LE;
break;
case 24:
format = SNDRV_PCM_FMTBIT_S24_LE;
break;
default:
return -EINVAL;
}
if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) {
drv_data->dai_drv.name = "xlnx_i2s_playback";
drv_data->dai_drv.playback.stream_name = "Playback";
drv_data->dai_drv.playback.formats = format;
drv_data->dai_drv.playback.channels_min = drv_data->channels;
drv_data->dai_drv.playback.channels_max = drv_data->channels;
drv_data->dai_drv.playback.rates = SNDRV_PCM_RATE_8000_192000;
drv_data->dai_drv.ops = &xlnx_i2s_dai_ops;
} else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) {
drv_data->dai_drv.name = "xlnx_i2s_capture";
drv_data->dai_drv.capture.stream_name = "Capture";
drv_data->dai_drv.capture.formats = format;
drv_data->dai_drv.capture.channels_min = drv_data->channels;
drv_data->dai_drv.capture.channels_max = drv_data->channels;
drv_data->dai_drv.capture.rates = SNDRV_PCM_RATE_8000_192000;
drv_data->dai_drv.ops = &xlnx_i2s_dai_ops;
} else {
return -ENODEV;
}
drv_data->is_32bit_lrclk = readl(drv_data->base + I2S_CORE_CTRL_OFFSET) &
I2S_CORE_CTRL_32BIT_LRCLK;
dev_set_drvdata(&pdev->dev, drv_data);
ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component,
&drv_data->dai_drv, 1);
if (ret) {
dev_err(&pdev->dev, "i2s component registration failed\n");
return ret;
}
dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name);
return ret;
}
static struct platform_driver xlnx_i2s_aud_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = xlnx_i2s_of_match,
},
.probe = xlnx_i2s_probe,
};
module_platform_driver(xlnx_i2s_aud_driver);
MODULE_DESCRIPTION("ASoC driver for Xilinx I2S audio");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Praveen Vuppala <praveenv@xilinx.com>");
MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");
+318
View File
@@ -0,0 +1,318 @@
// SPDX-License-Identifier: GPL-2.0
//
// Xilinx ASoC SPDIF audio support
//
// Copyright (C) 2018 Xilinx, Inc.
//
// Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>
//
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define XLNX_SPDIF_RATES \
(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \
SNDRV_PCM_RATE_192000)
#define XLNX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
#define XSPDIF_IRQ_STS_REG 0x20
#define XSPDIF_IRQ_ENABLE_REG 0x28
#define XSPDIF_SOFT_RESET_REG 0x40
#define XSPDIF_CONTROL_REG 0x44
#define XSPDIF_CHAN_0_STS_REG 0x4C
#define XSPDIF_GLOBAL_IRQ_ENABLE_REG 0x1C
#define XSPDIF_CH_A_USER_DATA_REG_0 0x64
#define XSPDIF_CORE_ENABLE_MASK BIT(0)
#define XSPDIF_FIFO_FLUSH_MASK BIT(1)
#define XSPDIF_CH_STS_MASK BIT(5)
#define XSPDIF_GLOBAL_IRQ_ENABLE BIT(31)
#define XSPDIF_CLOCK_CONFIG_BITS_MASK GENMASK(5, 2)
#define XSPDIF_CLOCK_CONFIG_BITS_SHIFT 2
#define XSPDIF_SOFT_RESET_VALUE 0xA
#define MAX_CHANNELS 2
#define AES_SAMPLE_WIDTH 32
#define CH_STATUS_UPDATE_TIMEOUT 40
struct spdif_dev_data {
u32 mode;
u32 aclk;
bool rx_chsts_updated;
void __iomem *base;
struct clk *axi_clk;
wait_queue_head_t chsts_q;
};
static irqreturn_t xlnx_spdifrx_irq_handler(int irq, void *arg)
{
u32 val;
struct spdif_dev_data *ctx = arg;
val = readl(ctx->base + XSPDIF_IRQ_STS_REG);
if (val & XSPDIF_CH_STS_MASK) {
writel(val & XSPDIF_CH_STS_MASK,
ctx->base + XSPDIF_IRQ_STS_REG);
val = readl(ctx->base +
XSPDIF_IRQ_ENABLE_REG);
writel(val & ~XSPDIF_CH_STS_MASK,
ctx->base + XSPDIF_IRQ_ENABLE_REG);
ctx->rx_chsts_updated = true;
wake_up_interruptible(&ctx->chsts_q);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int xlnx_spdif_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
u32 val;
struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
val = readl(ctx->base + XSPDIF_CONTROL_REG);
val |= XSPDIF_FIFO_FLUSH_MASK;
writel(val, ctx->base + XSPDIF_CONTROL_REG);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
writel(XSPDIF_CH_STS_MASK,
ctx->base + XSPDIF_IRQ_ENABLE_REG);
writel(XSPDIF_GLOBAL_IRQ_ENABLE,
ctx->base + XSPDIF_GLOBAL_IRQ_ENABLE_REG);
}
return 0;
}
static void xlnx_spdif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG);
}
static int xlnx_spdif_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
u32 val, clk_div, clk_cfg;
struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
clk_div = DIV_ROUND_CLOSEST(ctx->aclk, MAX_CHANNELS * AES_SAMPLE_WIDTH *
params_rate(params));
switch (clk_div) {
case 4:
clk_cfg = 0;
break;
case 8:
clk_cfg = 1;
break;
case 16:
clk_cfg = 2;
break;
case 24:
clk_cfg = 3;
break;
case 32:
clk_cfg = 4;
break;
case 48:
clk_cfg = 5;
break;
case 64:
clk_cfg = 6;
break;
default:
return -EINVAL;
}
val = readl(ctx->base + XSPDIF_CONTROL_REG);
val &= ~XSPDIF_CLOCK_CONFIG_BITS_MASK;
val |= clk_cfg << XSPDIF_CLOCK_CONFIG_BITS_SHIFT;
writel(val, ctx->base + XSPDIF_CONTROL_REG);
return 0;
}
static int rx_stream_detect(struct snd_soc_dai *dai)
{
int err;
struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
unsigned long jiffies = msecs_to_jiffies(CH_STATUS_UPDATE_TIMEOUT);
/* start capture only if stream is detected within 40ms timeout */
err = wait_event_interruptible_timeout(ctx->chsts_q,
ctx->rx_chsts_updated,
jiffies);
if (!err) {
dev_err(dai->dev, "No streaming audio detected!\n");
return -EINVAL;
}
ctx->rx_chsts_updated = false;
return 0;
}
static int xlnx_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
u32 val;
int ret = 0;
struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);
val = readl(ctx->base + XSPDIF_CONTROL_REG);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
val |= XSPDIF_CORE_ENABLE_MASK;
writel(val, ctx->base + XSPDIF_CONTROL_REG);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
ret = rx_stream_detect(dai);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
val &= ~XSPDIF_CORE_ENABLE_MASK;
writel(val, ctx->base + XSPDIF_CONTROL_REG);
break;
default:
ret = -EINVAL;
}
return ret;
}
static const struct snd_soc_dai_ops xlnx_spdif_dai_ops = {
.startup = xlnx_spdif_startup,
.shutdown = xlnx_spdif_shutdown,
.trigger = xlnx_spdif_trigger,
.hw_params = xlnx_spdif_hw_params,
};
static struct snd_soc_dai_driver xlnx_spdif_tx_dai = {
.name = "xlnx_spdif_tx",
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = XLNX_SPDIF_RATES,
.formats = XLNX_SPDIF_FORMATS,
},
.ops = &xlnx_spdif_dai_ops,
};
static struct snd_soc_dai_driver xlnx_spdif_rx_dai = {
.name = "xlnx_spdif_rx",
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = XLNX_SPDIF_RATES,
.formats = XLNX_SPDIF_FORMATS,
},
.ops = &xlnx_spdif_dai_ops,
};
static const struct snd_soc_component_driver xlnx_spdif_component = {
.name = "xlnx-spdif",
.legacy_dai_naming = 1,
};
static const struct of_device_id xlnx_spdif_of_match[] = {
{ .compatible = "xlnx,spdif-2.0", },
{},
};
MODULE_DEVICE_TABLE(of, xlnx_spdif_of_match);
static int xlnx_spdif_probe(struct platform_device *pdev)
{
int ret;
struct snd_soc_dai_driver *dai_drv;
struct spdif_dev_data *ctx;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->axi_clk = devm_clk_get_enabled(dev, "s_axi_aclk");
if (IS_ERR(ctx->axi_clk)) {
ret = PTR_ERR(ctx->axi_clk);
dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret);
return ret;
}
ctx->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ctx->base))
return PTR_ERR(ctx->base);
ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode);
if (ret < 0) {
dev_err(dev, "cannot get SPDIF mode\n");
return ret;
}
if (ctx->mode) {
dai_drv = &xlnx_spdif_tx_dai;
} else {
ret = platform_get_irq(pdev, 0);
if (ret < 0)
return ret;
ret = devm_request_irq(dev, ret,
xlnx_spdifrx_irq_handler,
0, "XLNX_SPDIF_RX", ctx);
if (ret) {
dev_err(dev, "spdif rx irq request failed\n");
return -ENODEV;
}
init_waitqueue_head(&ctx->chsts_q);
dai_drv = &xlnx_spdif_rx_dai;
}
ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk);
if (ret < 0) {
dev_err(dev, "cannot get aud_clk_i value\n");
return ret;
}
dev_set_drvdata(dev, ctx);
ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component,
dai_drv, 1);
if (ret) {
dev_err(dev, "SPDIF component registration failed\n");
return ret;
}
writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG);
dev_info(dev, "%s DAI registered\n", dai_drv->name);
return 0;
}
static struct platform_driver xlnx_spdif_driver = {
.driver = {
.name = "xlnx-spdif",
.of_match_table = xlnx_spdif_of_match,
},
.probe = xlnx_spdif_probe,
};
module_platform_driver(xlnx_spdif_driver);
MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");
MODULE_DESCRIPTION("XILINX SPDIF driver");
MODULE_LICENSE("GPL v2");