From ace9429bb58fd418f0c81d4c2835699bddf6bde6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:27:49 +0200 Subject: Adding upstream version 6.6.15. Signed-off-by: Daniel Baumann --- sound/soc/intel/keembay/kmb_platform.c | 938 +++++++++++++++++++++++++++++++++ 1 file changed, 938 insertions(+) create mode 100644 sound/soc/intel/keembay/kmb_platform.c (limited to 'sound/soc/intel/keembay/kmb_platform.c') diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c new file mode 100644 index 000000000..6b06b7b5e --- /dev/null +++ b/sound/soc/intel/keembay/kmb_platform.c @@ -0,0 +1,938 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2020 Intel Corporation. +// +// Intel KeemBay Platform driver. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kmb_platform.h" + +#define PERIODS_MIN 2 +#define PERIODS_MAX 48 +#define PERIOD_BYTES_MIN 4096 +#define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN) +#define TDM_OPERATION 5 +#define I2S_OPERATION 0 +#define DATA_WIDTH_CONFIG_BIT 6 +#define TDM_CHANNEL_CONFIG_BIT 3 + +static const struct snd_pcm_hardware kmb_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_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 = PERIODS_MAX, + .fifo_size = 16, +}; + +/* + * Convert to ADV7511 HDMI hardware format. + * ADV7511 HDMI chip need parity bit replaced by block start bit and + * with the preamble bits left out. + * ALSA IEC958 subframe format: + * bit 0-3 = preamble (0x8 = block start) + * 4-7 = AUX (=0) + * 8-27 = audio data (without AUX if 24bit sample) + * 28 = validity + * 29 = user data + * 30 = channel status + * 31 = parity + * + * ADV7511 IEC958 subframe format: + * bit 0-23 = audio data + * 24 = validity + * 25 = user data + * 26 = channel status + * 27 = block start + * 28-31 = 0 + * MSB to LSB bit reverse by software as hardware not supporting it. + */ +static void hdmi_reformat_iec958(struct snd_pcm_runtime *runtime, + struct kmb_i2s_info *kmb_i2s, + unsigned int tx_ptr) +{ + u32(*buf)[2] = (void *)runtime->dma_area; + unsigned long temp; + u32 i, j, sample; + + for (i = 0; i < kmb_i2s->fifo_th; i++) { + j = 0; + do { + temp = buf[tx_ptr][j]; + /* Replace parity with block start*/ + assign_bit(31, &temp, (BIT(3) & temp)); + sample = bitrev32(temp); + buf[tx_ptr][j] = sample << 4; + j++; + } while (j < 2); + tx_ptr++; + } +} + +static unsigned int kmb_pcm_tx_fn(struct kmb_i2s_info *kmb_i2s, + struct snd_pcm_runtime *runtime, + unsigned int tx_ptr, bool *period_elapsed) +{ + unsigned int period_pos = tx_ptr % runtime->period_size; + void __iomem *i2s_base = kmb_i2s->i2s_base; + void *buf = runtime->dma_area; + int i; + + if (kmb_i2s->iec958_fmt) + hdmi_reformat_iec958(runtime, kmb_i2s, tx_ptr); + + /* KMB i2s uses two separate L/R FIFO */ + for (i = 0; i < kmb_i2s->fifo_th; i++) { + if (kmb_i2s->config.data_width == 16) { + writel(((u16(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0)); + writel(((u16(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0)); + } else { + writel(((u32(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0)); + writel(((u32(*)[2])buf)[tx_ptr][1], 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; +} + +static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s, + struct snd_pcm_runtime *runtime, + unsigned int rx_ptr, bool *period_elapsed) +{ + unsigned int period_pos = rx_ptr % runtime->period_size; + void __iomem *i2s_base = kmb_i2s->i2s_base; + int chan = kmb_i2s->config.chan_nr; + void *buf = runtime->dma_area; + int i, j; + + /* KMB i2s uses two separate L/R FIFO */ + for (i = 0; i < kmb_i2s->fifo_th; i++) { + for (j = 0; j < chan / 2; j++) { + if (kmb_i2s->config.data_width == 16) { + ((u16 *)buf)[rx_ptr * chan + (j * 2)] = + readl(i2s_base + LRBR_LTHR(j)); + ((u16 *)buf)[rx_ptr * chan + ((j * 2) + 1)] = + 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++; + + if (++rx_ptr >= runtime->buffer_size) + rx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + + return rx_ptr; +} + +static inline void kmb_i2s_disable_channels(struct kmb_i2s_info *kmb_i2s, + u32 stream) +{ + u32 i; + + /* Disable all channels regardless of configuration*/ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < MAX_ISR; i++) + writel(0, kmb_i2s->i2s_base + TER(i)); + } else { + for (i = 0; i < MAX_ISR; i++) + writel(0, kmb_i2s->i2s_base + RER(i)); + } +} + +static inline void kmb_i2s_clear_irqs(struct kmb_i2s_info *kmb_i2s, u32 stream) +{ + struct i2s_clk_config_data *config = &kmb_i2s->config; + u32 i; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < config->chan_nr / 2; i++) + readl(kmb_i2s->i2s_base + TOR(i)); + } else { + for (i = 0; i < config->chan_nr / 2; i++) + readl(kmb_i2s->i2s_base + ROR(i)); + } +} + +static inline void kmb_i2s_irq_trigger(struct kmb_i2s_info *kmb_i2s, + u32 stream, int chan_nr, bool trigger) +{ + u32 i, irq; + u32 flag; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + flag = TX_INT_FLAG; + else + flag = RX_INT_FLAG; + + for (i = 0; i < chan_nr / 2; i++) { + irq = readl(kmb_i2s->i2s_base + IMR(i)); + + if (trigger) + irq = irq & ~flag; + else + irq = irq | flag; + + writel(irq, kmb_i2s->i2s_base + IMR(i)); + } +} + +static void kmb_pcm_operation(struct kmb_i2s_info *kmb_i2s, bool playback) +{ + struct snd_pcm_substream *substream; + bool period_elapsed; + unsigned int new_ptr; + unsigned int ptr; + + if (playback) + substream = kmb_i2s->tx_substream; + else + substream = kmb_i2s->rx_substream; + + if (!substream || !snd_pcm_running(substream)) + return; + + if (playback) { + ptr = kmb_i2s->tx_ptr; + new_ptr = kmb_pcm_tx_fn(kmb_i2s, substream->runtime, + ptr, &period_elapsed); + cmpxchg(&kmb_i2s->tx_ptr, ptr, new_ptr); + } else { + ptr = kmb_i2s->rx_ptr; + new_ptr = kmb_pcm_rx_fn(kmb_i2s, substream->runtime, + ptr, &period_elapsed); + cmpxchg(&kmb_i2s->rx_ptr, ptr, new_ptr); + } + + if (period_elapsed) + snd_pcm_period_elapsed(substream); +} + +static int kmb_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 kmb_i2s_info *kmb_i2s; + + kmb_i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + snd_soc_set_runtime_hwparams(substream, &kmb_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = kmb_i2s; + + return 0; +} + +static int kmb_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct kmb_i2s_info *kmb_i2s = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + kmb_i2s->tx_ptr = 0; + kmb_i2s->tx_substream = substream; + } else { + kmb_i2s->rx_ptr = 0; + kmb_i2s->rx_substream = substream; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + kmb_i2s->tx_substream = NULL; + else + kmb_i2s->rx_substream = NULL; + kmb_i2s->iec958_fmt = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id) +{ + struct kmb_i2s_info *kmb_i2s = dev_id; + struct i2s_clk_config_data *config = &kmb_i2s->config; + irqreturn_t ret = IRQ_NONE; + u32 tx_enabled = 0; + u32 isr[4]; + int i; + + for (i = 0; i < config->chan_nr / 2; i++) + isr[i] = readl(kmb_i2s->i2s_base + ISR(i)); + + kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK); + 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++) { + /* + * Check if TX fifo is empty. If empty fill FIFO with samples + */ + if ((isr[i] & ISR_TXFE) && tx_enabled) { + kmb_pcm_operation(kmb_i2s, true); + ret = IRQ_HANDLED; + } + + /* Error Handling: TX */ + if (isr[i] & ISR_TXFO) { + dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n", i); + ret = IRQ_HANDLED; + } + /* Error Handling: RX */ + if (isr[i] & ISR_RXFO) { + dev_dbg(kmb_i2s->dev, "RX overrun (ch_id=%d)\n", i); + ret = IRQ_HANDLED; + } + } + + return ret; +} + +static int kmb_platform_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *soc_runtime) +{ + size_t size = kmb_pcm_hardware.buffer_bytes_max; + /* Use SNDRV_DMA_TYPE_CONTINUOUS as KMB doesn't use PCI sg buffer */ + snd_pcm_set_managed_buffer_all(soc_runtime->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, size, size); + return 0; +} + +static snd_pcm_uframes_t kmb_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct kmb_i2s_info *kmb_i2s = runtime->private_data; + snd_pcm_uframes_t pos; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pos = kmb_i2s->tx_ptr; + else + pos = kmb_i2s->rx_ptr; + + return pos < runtime->buffer_size ? pos : 0; +} + +static const struct snd_soc_component_driver kmb_component = { + .name = "kmb", + .pcm_construct = kmb_platform_pcm_new, + .open = kmb_pcm_open, + .trigger = kmb_pcm_trigger, + .pointer = kmb_pcm_pointer, + .legacy_dai_naming = 1, +}; + +static const struct snd_soc_component_driver kmb_component_dma = { + .name = "kmb", + .legacy_dai_naming = 1, +}; + +static int kmb_probe(struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + + if (kmb_i2s->use_pio) + return 0; + + snd_soc_dai_init_dma_data(cpu_dai, &kmb_i2s->play_dma_data, + &kmb_i2s->capture_dma_data); + + return 0; +} + +static inline void kmb_i2s_enable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream) +{ + u32 dma_reg; + + dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR); + /* Enable DMA handshake for stream */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_reg |= I2S_DMAEN_TXBLOCK; + else + dma_reg |= I2S_DMAEN_RXBLOCK; + + writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR); +} + +static inline void kmb_i2s_disable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream) +{ + u32 dma_reg; + + dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR); + /* Disable DMA handshake for stream */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_reg &= ~I2S_DMAEN_TXBLOCK; + writel(1, kmb_i2s->i2s_base + I2S_RTXDMA); + } else { + dma_reg &= ~I2S_DMAEN_RXBLOCK; + writel(1, kmb_i2s->i2s_base + I2S_RRXDMA); + } + writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR); +} + +static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s, + struct snd_pcm_substream *substream) +{ + struct i2s_clk_config_data *config = &kmb_i2s->config; + + /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ + writel(1, kmb_i2s->i2s_base + IER); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(1, kmb_i2s->i2s_base + ITER); + else + writel(1, kmb_i2s->i2s_base + IRER); + + if (kmb_i2s->use_pio) + kmb_i2s_irq_trigger(kmb_i2s, substream->stream, + config->chan_nr, true); + else + kmb_i2s_enable_dma(kmb_i2s, substream->stream); + + if (kmb_i2s->clock_provider) + writel(1, kmb_i2s->i2s_base + CER); + else + writel(0, kmb_i2s->i2s_base + CER); +} + +static void kmb_i2s_stop(struct kmb_i2s_info *kmb_i2s, + struct snd_pcm_substream *substream) +{ + /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ + kmb_i2s_clear_irqs(kmb_i2s, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(0, kmb_i2s->i2s_base + ITER); + else + writel(0, kmb_i2s->i2s_base + IRER); + + kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false); + + if (!kmb_i2s->active) { + writel(0, kmb_i2s->i2s_base + CER); + writel(0, kmb_i2s->i2s_base + IER); + } +} + +static void kmb_disable_clk(void *clk) +{ + clk_disable_unprepare(clk); +} + +static int kmb_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BC_FC: + kmb_i2s->clock_provider = false; + ret = 0; + break; + case SND_SOC_DAIFMT_BP_FP: + writel(CLOCK_PROVIDER_MODE, kmb_i2s->pss_base + I2S_GEN_CFG_0); + + ret = clk_prepare_enable(kmb_i2s->clk_i2s); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(kmb_i2s->dev, kmb_disable_clk, + kmb_i2s->clk_i2s); + if (ret) + return ret; + + kmb_i2s->clock_provider = true; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int kmb_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* Keep track of i2s activity before turn off + * the i2s interface + */ + kmb_i2s->active++; + kmb_i2s_start(kmb_i2s, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + kmb_i2s->active--; + if (kmb_i2s->use_pio) + kmb_i2s_stop(kmb_i2s, substream); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void kmb_i2s_config(struct kmb_i2s_info *kmb_i2s, int stream) +{ + struct i2s_clk_config_data *config = &kmb_i2s->config; + u32 ch_reg; + + kmb_i2s_disable_channels(kmb_i2s, stream); + + for (ch_reg = 0; ch_reg < config->chan_nr / 2; ch_reg++) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + writel(kmb_i2s->xfer_resolution, + kmb_i2s->i2s_base + TCR(ch_reg)); + + writel(kmb_i2s->fifo_th - 1, + kmb_i2s->i2s_base + TFCR(ch_reg)); + + writel(1, kmb_i2s->i2s_base + TER(ch_reg)); + } else { + writel(kmb_i2s->xfer_resolution, + kmb_i2s->i2s_base + RCR(ch_reg)); + + writel(kmb_i2s->fifo_th - 1, + kmb_i2s->i2s_base + RFCR(ch_reg)); + + writel(1, kmb_i2s->i2s_base + RER(ch_reg)); + } + } +} + +static int kmb_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + struct i2s_clk_config_data *config = &kmb_i2s->config; + u32 write_val; + int ret; + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + config->data_width = 16; + kmb_i2s->ccr = 0x00; + kmb_i2s->xfer_resolution = 0x02; + kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case SNDRV_PCM_FORMAT_S24_LE: + config->data_width = 32; + kmb_i2s->ccr = 0x14; + kmb_i2s->xfer_resolution = 0x05; + kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + kmb_i2s->iec958_fmt = true; + fallthrough; + case SNDRV_PCM_FORMAT_S32_LE: + config->data_width = 32; + kmb_i2s->ccr = 0x10; + kmb_i2s->xfer_resolution = 0x05; + kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt"); + return -EINVAL; + } + + config->chan_nr = params_channels(hw_params); + + switch (config->chan_nr) { + case 8: + case 4: + /* + * Platform is not capable of providing clocks for + * multi channel audio + */ + if (kmb_i2s->clock_provider) + return -EINVAL; + + write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) | + (config->data_width << DATA_WIDTH_CONFIG_BIT) | + TDM_OPERATION; + + 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->clock_provider)) + return -EINVAL; + + write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) | + (config->data_width << DATA_WIDTH_CONFIG_BIT) | + CLOCK_PROVIDER_MODE | I2S_OPERATION; + + writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0); + break; + default: + dev_dbg(kmb_i2s->dev, "channel not supported\n"); + return -EINVAL; + } + + kmb_i2s_config(kmb_i2s, substream->stream); + + writel(kmb_i2s->ccr, kmb_i2s->i2s_base + CCR); + + config->sample_rate = params_rate(hw_params); + + if (kmb_i2s->clock_provider) { + /* Only 2 ch supported in Master mode */ + u32 bitclk = config->sample_rate * config->data_width * 2; + + ret = clk_set_rate(kmb_i2s->clk_i2s, bitclk); + if (ret) { + dev_err(kmb_i2s->dev, + "Can't set I2S clock rate: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int kmb_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(1, kmb_i2s->i2s_base + TXFFR); + else + writel(1, kmb_i2s->i2s_base + RXFFR); + + return 0; +} + +static int kmb_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + struct snd_dmaengine_dai_dma_data *dma_data; + + if (kmb_i2s->use_pio) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &kmb_i2s->play_dma_data; + else + dma_data = &kmb_i2s->capture_dma_data; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + return 0; +} + +static int kmb_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai); + /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */ + if (kmb_i2s->use_pio) + kmb_i2s_clear_irqs(kmb_i2s, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(0, kmb_i2s->i2s_base + ITER); + else + writel(0, kmb_i2s->i2s_base + IRER); + + if (kmb_i2s->use_pio) + kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false); + else + kmb_i2s_disable_dma(kmb_i2s, substream->stream); + + if (!kmb_i2s->active) { + writel(0, kmb_i2s->i2s_base + CER); + writel(0, kmb_i2s->i2s_base + IER); + } + + return 0; +} + +static const struct snd_soc_dai_ops kmb_dai_ops = { + .probe = kmb_probe, + .startup = kmb_dai_startup, + .trigger = kmb_dai_trigger, + .hw_params = kmb_dai_hw_params, + .hw_free = kmb_dai_hw_free, + .prepare = kmb_dai_prepare, + .set_fmt = kmb_set_dai_fmt, +}; + +static struct snd_soc_dai_driver intel_kmb_hdmi_dai[] = { + { + .name = "intel_kmb_hdmi_i2s", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE), + }, + .ops = &kmb_dai_ops, + }, +}; + +static struct snd_soc_dai_driver intel_kmb_i2s_dai[] = { + { + .name = "intel_kmb_i2s", + .playback = { + .channels_min = 2, + .channels_max = 2, + .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), + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .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 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-hdmi-i2s", .data = &intel_kmb_hdmi_dai}, + { .compatible = "intel,keembay-tdm", .data = &intel_kmb_tdm_dai}, + {} +}; + +static int kmb_plat_dai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_dai_driver *kmb_i2s_dai; + const struct of_device_id *match; + struct device *dev = &pdev->dev; + struct kmb_i2s_info *kmb_i2s; + struct resource *res; + int ret, irq; + u32 comp1_reg; + + kmb_i2s = devm_kzalloc(dev, sizeof(*kmb_i2s), GFP_KERNEL); + if (!kmb_i2s) + return -ENOMEM; + + kmb_i2s_dai = devm_kzalloc(dev, sizeof(*kmb_i2s_dai), GFP_KERNEL); + if (!kmb_i2s_dai) + return -ENOMEM; + + 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 */ + kmb_i2s->clk_apb = devm_clk_get(dev, "apb_clk"); + if (IS_ERR(kmb_i2s->clk_apb)) { + dev_err(dev, "Failed to get apb clock\n"); + return PTR_ERR(kmb_i2s->clk_apb); + } + + ret = clk_prepare_enable(kmb_i2s->clk_apb); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, kmb_disable_clk, kmb_i2s->clk_apb); + if (ret) { + dev_err(dev, "Failed to add clk_apb reset action\n"); + return ret; + } + + kmb_i2s->clk_i2s = devm_clk_get(dev, "osc"); + if (IS_ERR(kmb_i2s->clk_i2s)) { + dev_err(dev, "Failed to get osc clock\n"); + return PTR_ERR(kmb_i2s->clk_i2s); + } + + kmb_i2s->i2s_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(kmb_i2s->i2s_base)) + return PTR_ERR(kmb_i2s->i2s_base); + + kmb_i2s->pss_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(kmb_i2s->pss_base)) + return PTR_ERR(kmb_i2s->pss_base); + + kmb_i2s->dev = &pdev->dev; + + comp1_reg = readl(kmb_i2s->i2s_base + I2S_COMP_PARAM_1); + + kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2; + + kmb_i2s->use_pio = !(of_property_read_bool(np, "dmas")); + + if (kmb_i2s->use_pio) { + irq = platform_get_irq_optional(pdev, 0); + if (irq > 0) { + ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0, + pdev->name, kmb_i2s); + if (ret < 0) { + dev_err(dev, "failed to request irq\n"); + return ret; + } + } + ret = devm_snd_soc_register_component(dev, &kmb_component, + kmb_i2s_dai, 1); + } else { + kmb_i2s->play_dma_data.addr = res->start + I2S_TXDMA; + kmb_i2s->capture_dma_data.addr = res->start + I2S_RXDMA; + ret = snd_dmaengine_pcm_register(&pdev->dev, + NULL, 0); + if (ret) { + dev_err(&pdev->dev, "could not register dmaengine: %d\n", + ret); + return ret; + } + ret = devm_snd_soc_register_component(dev, &kmb_component_dma, + kmb_i2s_dai, 1); + } + + if (ret) { + dev_err(dev, "not able to register dai\n"); + return ret; + } + + /* To ensure none of the channels are enabled at boot up */ + kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK); + kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE); + + dev_set_drvdata(dev, kmb_i2s); + + return ret; +} + +static struct platform_driver kmb_plat_dai_driver = { + .driver = { + .name = "kmb-plat-dai", + .of_match_table = kmb_plat_of_match, + }, + .probe = kmb_plat_dai_probe, +}; + +module_platform_driver(kmb_plat_dai_driver); + +MODULE_DESCRIPTION("ASoC Intel KeemBay Platform driver"); +MODULE_AUTHOR("Sia Jee Heng "); +MODULE_AUTHOR("Sit, Michael Wei Hong "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:kmb_platform"); -- cgit v1.2.3