root/sound/soc/intel/keembay/kmb_platform.c
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright (C) 2020 Intel Corporation.
//
// Intel KeemBay Platform driver.
//

#include <linux/bitrev.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#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 = snd_soc_substream_to_rtd(substream);
        struct kmb_i2s_info *kmb_i2s;

        kmb_i2s = snd_soc_dai_get_drvdata(snd_soc_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},
        {}
};
MODULE_DEVICE_TABLE(of, kmb_plat_of_match);

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;
        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 = (struct snd_soc_dai_driver *)device_get_match_data(&pdev->dev);

        /* 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_present(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 <jee.heng.sia@intel.com>");
MODULE_AUTHOR("Sit, Michael Wei Hong <michael.wei.hong.sit@intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:kmb_platform");