diff options
Diffstat (limited to 'sound/soc/intel/keembay/kmb_platform.c')
| -rw-r--r-- | sound/soc/intel/keembay/kmb_platform.c | 668 | 
1 files changed, 668 insertions, 0 deletions
| diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c new file mode 100644 index 000000000000..16f9fc4c663d --- /dev/null +++ b/sound/soc/intel/keembay/kmb_platform.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2020 Intel Corporation. +// +// Intel KeemBay Platform driver. +// + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.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		1 +#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, +	.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, +}; + +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; + +	/* 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; +	void *buf = runtime->dma_area; +	int i; + +	/* KMB i2s uses two separate L/R FIFO */ +	for (i = 0; i < kmb_i2s->fifo_th; i++) { +		if (kmb_i2s->config.data_width == 16) { +			((u16(*)[2])buf)[rx_ptr][0] = readl(i2s_base + LRBR_LTHR(0)); +			((u16(*)[2])buf)[rx_ptr][1] = readl(i2s_base + RRBR_RTHR(0)); +		} else { +			((u32(*)[2])buf)[rx_ptr][0] = readl(i2s_base + LRBR_LTHR(0)); +			((u32(*)[2])buf)[rx_ptr][1] = readl(i2s_base + RRBR_RTHR(0)); +		} + +		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; +		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 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); + +	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)) { +			kmb_pcm_operation(kmb_i2s, true); +			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 */ +		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, +}; + +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); + +	kmb_i2s_irq_trigger(kmb_i2s, substream->stream, config->chan_nr, true); + +	if (kmb_i2s->master) +		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_MASTER_MASK) { +	case SND_SOC_DAIFMT_CBM_CFM: +		kmb_i2s->master = false; +		ret = 0; +		break; +	case SND_SOC_DAIFMT_CBS_CFS: +		writel(MASTER_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->master = 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--; +		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 register_val, 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; +		break; +	case SNDRV_PCM_FORMAT_S24_LE: +		config->data_width = 24; +		kmb_i2s->ccr = 0x08; +		kmb_i2s->xfer_resolution = 0x04; +		break; +	case SNDRV_PCM_FORMAT_S32_LE: +		config->data_width = 32; +		kmb_i2s->ccr = 0x10; +		kmb_i2s->xfer_resolution = 0x05; +		break; +	default: +		dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt"); +		return -EINVAL; +	} + +	config->chan_nr = params_channels(hw_params); + +	switch (config->chan_nr) { +	/* TODO: This switch case will handle up to TDM8 in the near future */ +	case TWO_CHANNEL_SUPPORT: +		write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) | +				(config->data_width << DATA_WIDTH_CONFIG_BIT) | +				MASTER_MODE | I2S_OPERATION; + +		writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0); + +		register_val = readl(kmb_i2s->pss_base + I2S_GEN_CFG_0); +		dev_dbg(kmb_i2s->dev, "pss register = 0x%X", register_val); +		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->master) { +		/* 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 struct snd_soc_dai_ops kmb_dai_ops = { +	.trigger	= kmb_dai_trigger, +	.hw_params	= kmb_dai_hw_params, +	.prepare	= kmb_dai_prepare, +	.set_fmt	= kmb_set_dai_fmt, +}; + +static struct snd_soc_dai_driver intel_kmb_platform_dai[] = { +	{ +		.name = "kmb-plat-dai", +		.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, +			/* +			 * .channels_max will be overwritten +			 * if provided by Device Tree +			 */ +			.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 int kmb_plat_dai_probe(struct platform_device *pdev) +{ +	struct snd_soc_dai_driver *kmb_i2s_dai; +	struct device *dev = &pdev->dev; +	struct kmb_i2s_info *kmb_i2s; +	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; + +	kmb_i2s_dai->ops = &kmb_dai_ops; + +	/* 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_ioremap_resource(pdev, 0); +	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; + +	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; +		} +	} + +	comp1_reg = readl(kmb_i2s->i2s_base + I2S_COMP_PARAM_1); + +	kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2; + +	ret = devm_snd_soc_register_component(dev, &kmb_component, +					      intel_kmb_platform_dai, +				ARRAY_SIZE(intel_kmb_platform_dai)); +	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 const struct of_device_id kmb_plat_of_match[] = { +	{ .compatible = "intel,keembay-i2s", }, +	{} +}; + +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"); | 
