Commit 9c3bab3c authored by Michael Sit Wei Hong's avatar Michael Sit Wei Hong Committed by Mark Brown

ASoC: Intel: KMB: Enable TDM audio capture

Enable I2S TDM audio capture for Intel Keem Bay platform.
The I2S TDM will support 4 channel and 8 channel audio capture only.
4 channel and 8 channel audio capture operates only in slave mode.
Signed-off-by: default avatarMichael Sit Wei Hong <michael.wei.hong.sit@intel.com>
Reviewed-by: default avatarSia Jee Heng <jee.heng.sia@intel.com>
Reviewed-by: default avatarPierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20200811041836.999-2-michael.wei.hong.sit@intel.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent 549ade57
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <sound/pcm.h> #include <sound/pcm.h>
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
#include <sound/soc.h> #include <sound/soc.h>
...@@ -17,7 +19,7 @@ ...@@ -17,7 +19,7 @@
#define PERIODS_MAX 48 #define PERIODS_MAX 48
#define PERIOD_BYTES_MIN 4096 #define PERIOD_BYTES_MIN 4096
#define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN) #define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN)
#define TDM_OPERATION 1 #define TDM_OPERATION 5
#define I2S_OPERATION 0 #define I2S_OPERATION 0
#define DATA_WIDTH_CONFIG_BIT 6 #define DATA_WIDTH_CONFIG_BIT 6
#define TDM_CHANNEL_CONFIG_BIT 3 #define TDM_CHANNEL_CONFIG_BIT 3
...@@ -82,19 +84,25 @@ static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s, ...@@ -82,19 +84,25 @@ static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s,
{ {
unsigned int period_pos = rx_ptr % runtime->period_size; unsigned int period_pos = rx_ptr % runtime->period_size;
void __iomem *i2s_base = kmb_i2s->i2s_base; void __iomem *i2s_base = kmb_i2s->i2s_base;
int chan = kmb_i2s->config.chan_nr;
void *buf = runtime->dma_area; void *buf = runtime->dma_area;
int i; int i, j;
/* KMB i2s uses two separate L/R FIFO */ /* KMB i2s uses two separate L/R FIFO */
for (i = 0; i < kmb_i2s->fifo_th; i++) { for (i = 0; i < kmb_i2s->fifo_th; i++) {
if (kmb_i2s->config.data_width == 16) { for (j = 0; j < chan / 2; j++) {
((u16(*)[2])buf)[rx_ptr][0] = readl(i2s_base + LRBR_LTHR(0)); if (kmb_i2s->config.data_width == 16) {
((u16(*)[2])buf)[rx_ptr][1] = readl(i2s_base + RRBR_RTHR(0)); ((u16 *)buf)[rx_ptr * chan + (j * 2)] =
} else { readl(i2s_base + LRBR_LTHR(j));
((u32(*)[2])buf)[rx_ptr][0] = readl(i2s_base + LRBR_LTHR(0)); ((u16 *)buf)[rx_ptr * chan + ((j * 2) + 1)] =
((u32(*)[2])buf)[rx_ptr][1] = readl(i2s_base + RRBR_RTHR(0)); readl(i2s_base + RRBR_RTHR(j));
} else {
((u32 *)buf)[rx_ptr * chan + (j * 2)] =
readl(i2s_base + LRBR_LTHR(j));
((u32 *)buf)[rx_ptr * chan + ((j * 2) + 1)] =
readl(i2s_base + RRBR_RTHR(j));
}
} }
period_pos++; period_pos++;
if (++rx_ptr >= runtime->buffer_size) if (++rx_ptr >= runtime->buffer_size)
...@@ -238,6 +246,7 @@ static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id) ...@@ -238,6 +246,7 @@ static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id)
struct kmb_i2s_info *kmb_i2s = dev_id; struct kmb_i2s_info *kmb_i2s = dev_id;
struct i2s_clk_config_data *config = &kmb_i2s->config; struct i2s_clk_config_data *config = &kmb_i2s->config;
irqreturn_t ret = IRQ_NONE; irqreturn_t ret = IRQ_NONE;
u32 tx_enabled = 0;
u32 isr[4]; u32 isr[4];
int i; int i;
...@@ -246,22 +255,45 @@ static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id) ...@@ -246,22 +255,45 @@ static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id)
kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK); kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);
kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE); kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);
/* Only check TX interrupt if TX is active */
tx_enabled = readl(kmb_i2s->i2s_base + ITER);
/*
* Data available. Retrieve samples from FIFO
*/
/*
* 8 channel audio will have isr[0..2] triggered,
* reading the specific isr based on the audio configuration,
* to avoid reading the buffers too early.
*/
switch (config->chan_nr) {
case 2:
if (isr[0] & ISR_RXDA)
kmb_pcm_operation(kmb_i2s, false);
ret = IRQ_HANDLED;
break;
case 4:
if (isr[1] & ISR_RXDA)
kmb_pcm_operation(kmb_i2s, false);
ret = IRQ_HANDLED;
break;
case 8:
if (isr[3] & ISR_RXDA)
kmb_pcm_operation(kmb_i2s, false);
ret = IRQ_HANDLED;
break;
}
for (i = 0; i < config->chan_nr / 2; i++) { for (i = 0; i < config->chan_nr / 2; i++) {
/* /*
* Check if TX fifo is empty. If empty fill FIFO with samples * Check if TX fifo is empty. If empty fill FIFO with samples
*/ */
if ((isr[i] & ISR_TXFE)) { if ((isr[i] & ISR_TXFE) && tx_enabled) {
kmb_pcm_operation(kmb_i2s, true); kmb_pcm_operation(kmb_i2s, true);
ret = IRQ_HANDLED; ret = IRQ_HANDLED;
} }
/*
* Data available. Retrieve samples from FIFO
*/
if ((isr[i] & ISR_RXDA)) {
kmb_pcm_operation(kmb_i2s, false);
ret = IRQ_HANDLED;
}
/* Error Handling: TX */ /* Error Handling: TX */
if (isr[i] & ISR_TXFO) { if (isr[i] & ISR_TXFO) {
dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n", i); dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n", i);
...@@ -445,7 +477,7 @@ static int kmb_dai_hw_params(struct snd_pcm_substream *substream, ...@@ -445,7 +477,7 @@ static int kmb_dai_hw_params(struct snd_pcm_substream *substream,
{ {
struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
struct i2s_clk_config_data *config = &kmb_i2s->config; struct i2s_clk_config_data *config = &kmb_i2s->config;
u32 register_val, write_val; u32 write_val;
int ret; int ret;
switch (params_format(hw_params)) { switch (params_format(hw_params)) {
...@@ -472,16 +504,34 @@ static int kmb_dai_hw_params(struct snd_pcm_substream *substream, ...@@ -472,16 +504,34 @@ static int kmb_dai_hw_params(struct snd_pcm_substream *substream,
config->chan_nr = params_channels(hw_params); config->chan_nr = params_channels(hw_params);
switch (config->chan_nr) { switch (config->chan_nr) {
/* TODO: This switch case will handle up to TDM8 in the near future */ case 8:
case TWO_CHANNEL_SUPPORT: case 4:
/*
* Platform is not capable of providing clocks for
* multi channel audio
*/
if (kmb_i2s->master)
return -EINVAL;
write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) | write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |
(config->data_width << DATA_WIDTH_CONFIG_BIT) | (config->data_width << DATA_WIDTH_CONFIG_BIT) |
MASTER_MODE | I2S_OPERATION; !MASTER_MODE | TDM_OPERATION;
writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0); writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);
break;
case 2:
/*
* Platform is only capable of providing clocks need for
* 2 channel master mode
*/
if (!(kmb_i2s->master))
return -EINVAL;
write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |
(config->data_width << DATA_WIDTH_CONFIG_BIT) |
MASTER_MODE | I2S_OPERATION;
register_val = readl(kmb_i2s->pss_base + I2S_GEN_CFG_0); writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);
dev_dbg(kmb_i2s->dev, "pss register = 0x%X", register_val);
break; break;
default: default:
dev_dbg(kmb_i2s->dev, "channel not supported\n"); dev_dbg(kmb_i2s->dev, "channel not supported\n");
...@@ -529,9 +579,9 @@ static struct snd_soc_dai_ops kmb_dai_ops = { ...@@ -529,9 +579,9 @@ static struct snd_soc_dai_ops kmb_dai_ops = {
.set_fmt = kmb_set_dai_fmt, .set_fmt = kmb_set_dai_fmt,
}; };
static struct snd_soc_dai_driver intel_kmb_platform_dai[] = { static struct snd_soc_dai_driver intel_kmb_i2s_dai[] = {
{ {
.name = "kmb-plat-dai", .name = "intel_kmb_i2s",
.playback = { .playback = {
.channels_min = 2, .channels_min = 2,
.channels_max = 2, .channels_max = 2,
...@@ -547,10 +597,6 @@ static struct snd_soc_dai_driver intel_kmb_platform_dai[] = { ...@@ -547,10 +597,6 @@ static struct snd_soc_dai_driver intel_kmb_platform_dai[] = {
.capture = { .capture = {
.channels_min = 2, .channels_min = 2,
.channels_max = 2, .channels_max = 2,
/*
* .channels_max will be overwritten
* if provided by Device Tree
*/
.rates = SNDRV_PCM_RATE_8000 | .rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000, SNDRV_PCM_RATE_48000,
...@@ -564,9 +610,35 @@ static struct snd_soc_dai_driver intel_kmb_platform_dai[] = { ...@@ -564,9 +610,35 @@ static struct snd_soc_dai_driver intel_kmb_platform_dai[] = {
}, },
}; };
static struct snd_soc_dai_driver intel_kmb_tdm_dai[] = {
{
.name = "intel_kmb_tdm",
.capture = {
.channels_min = 4,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000,
.rate_min = 8000,
.rate_max = 48000,
.formats = (SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S16_LE),
},
.ops = &kmb_dai_ops,
},
};
static const struct of_device_id kmb_plat_of_match[] = {
{ .compatible = "intel,keembay-i2s", .data = &intel_kmb_i2s_dai},
{ .compatible = "intel,keembay-tdm", .data = &intel_kmb_tdm_dai},
{}
};
static int kmb_plat_dai_probe(struct platform_device *pdev) static int kmb_plat_dai_probe(struct platform_device *pdev)
{ {
struct snd_soc_dai_driver *kmb_i2s_dai; struct snd_soc_dai_driver *kmb_i2s_dai;
const struct of_device_id *match;
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct kmb_i2s_info *kmb_i2s; struct kmb_i2s_info *kmb_i2s;
int ret, irq; int ret, irq;
...@@ -580,7 +652,12 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) ...@@ -580,7 +652,12 @@ static int kmb_plat_dai_probe(struct platform_device *pdev)
if (!kmb_i2s_dai) if (!kmb_i2s_dai)
return -ENOMEM; return -ENOMEM;
kmb_i2s_dai->ops = &kmb_dai_ops; match = of_match_device(kmb_plat_of_match, &pdev->dev);
if (!match) {
dev_err(&pdev->dev, "Error: No device match found\n");
return -ENODEV;
}
kmb_i2s_dai = (struct snd_soc_dai_driver *) match->data;
/* Prepare the related clocks */ /* Prepare the related clocks */
kmb_i2s->clk_apb = devm_clk_get(dev, "apb_clk"); kmb_i2s->clk_apb = devm_clk_get(dev, "apb_clk");
...@@ -630,8 +707,7 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) ...@@ -630,8 +707,7 @@ static int kmb_plat_dai_probe(struct platform_device *pdev)
kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2; kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2;
ret = devm_snd_soc_register_component(dev, &kmb_component, ret = devm_snd_soc_register_component(dev, &kmb_component,
intel_kmb_platform_dai, kmb_i2s_dai, 1);
ARRAY_SIZE(intel_kmb_platform_dai));
if (ret) { if (ret) {
dev_err(dev, "not able to register dai\n"); dev_err(dev, "not able to register dai\n");
return ret; return ret;
...@@ -646,11 +722,6 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) ...@@ -646,11 +722,6 @@ static int kmb_plat_dai_probe(struct platform_device *pdev)
return ret; return ret;
} }
static const struct of_device_id kmb_plat_of_match[] = {
{ .compatible = "intel,keembay-i2s", },
{}
};
static struct platform_driver kmb_plat_dai_driver = { static struct platform_driver kmb_plat_dai_driver = {
.driver = { .driver = {
.name = "kmb-plat-dai", .name = "kmb-plat-dai",
......
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