Path: blob/master/sound/soc/intel/keembay/kmb_platform.c
26481 views
// SPDX-License-Identifier: GPL-2.0-only1//2// Copyright (C) 2020 Intel Corporation.3//4// Intel KeemBay Platform driver.5//67#include <linux/bitrev.h>8#include <linux/clk.h>9#include <linux/dma-mapping.h>10#include <linux/io.h>11#include <linux/module.h>12#include <linux/of.h>13#include <sound/dmaengine_pcm.h>14#include <sound/pcm.h>15#include <sound/pcm_params.h>16#include <sound/soc.h>17#include "kmb_platform.h"1819#define PERIODS_MIN 220#define PERIODS_MAX 4821#define PERIOD_BYTES_MIN 409622#define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN)23#define TDM_OPERATION 524#define I2S_OPERATION 025#define DATA_WIDTH_CONFIG_BIT 626#define TDM_CHANNEL_CONFIG_BIT 32728static const struct snd_pcm_hardware kmb_pcm_hardware = {29.info = SNDRV_PCM_INFO_INTERLEAVED |30SNDRV_PCM_INFO_MMAP |31SNDRV_PCM_INFO_MMAP_VALID |32SNDRV_PCM_INFO_BATCH |33SNDRV_PCM_INFO_BLOCK_TRANSFER,34.rates = SNDRV_PCM_RATE_8000 |35SNDRV_PCM_RATE_16000 |36SNDRV_PCM_RATE_48000,37.rate_min = 8000,38.rate_max = 48000,39.formats = SNDRV_PCM_FMTBIT_S16_LE |40SNDRV_PCM_FMTBIT_S24_LE |41SNDRV_PCM_FMTBIT_S32_LE |42SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,43.channels_min = 2,44.channels_max = 2,45.buffer_bytes_max = BUFFER_BYTES_MAX,46.period_bytes_min = PERIOD_BYTES_MIN,47.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,48.periods_min = PERIODS_MIN,49.periods_max = PERIODS_MAX,50.fifo_size = 16,51};5253/*54* Convert to ADV7511 HDMI hardware format.55* ADV7511 HDMI chip need parity bit replaced by block start bit and56* with the preamble bits left out.57* ALSA IEC958 subframe format:58* bit 0-3 = preamble (0x8 = block start)59* 4-7 = AUX (=0)60* 8-27 = audio data (without AUX if 24bit sample)61* 28 = validity62* 29 = user data63* 30 = channel status64* 31 = parity65*66* ADV7511 IEC958 subframe format:67* bit 0-23 = audio data68* 24 = validity69* 25 = user data70* 26 = channel status71* 27 = block start72* 28-31 = 073* MSB to LSB bit reverse by software as hardware not supporting it.74*/75static void hdmi_reformat_iec958(struct snd_pcm_runtime *runtime,76struct kmb_i2s_info *kmb_i2s,77unsigned int tx_ptr)78{79u32(*buf)[2] = (void *)runtime->dma_area;80unsigned long temp;81u32 i, j, sample;8283for (i = 0; i < kmb_i2s->fifo_th; i++) {84j = 0;85do {86temp = buf[tx_ptr][j];87/* Replace parity with block start*/88assign_bit(31, &temp, (BIT(3) & temp));89sample = bitrev32(temp);90buf[tx_ptr][j] = sample << 4;91j++;92} while (j < 2);93tx_ptr++;94}95}9697static unsigned int kmb_pcm_tx_fn(struct kmb_i2s_info *kmb_i2s,98struct snd_pcm_runtime *runtime,99unsigned int tx_ptr, bool *period_elapsed)100{101unsigned int period_pos = tx_ptr % runtime->period_size;102void __iomem *i2s_base = kmb_i2s->i2s_base;103void *buf = runtime->dma_area;104int i;105106if (kmb_i2s->iec958_fmt)107hdmi_reformat_iec958(runtime, kmb_i2s, tx_ptr);108109/* KMB i2s uses two separate L/R FIFO */110for (i = 0; i < kmb_i2s->fifo_th; i++) {111if (kmb_i2s->config.data_width == 16) {112writel(((u16(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0));113writel(((u16(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0));114} else {115writel(((u32(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0));116writel(((u32(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0));117}118119period_pos++;120121if (++tx_ptr >= runtime->buffer_size)122tx_ptr = 0;123}124125*period_elapsed = period_pos >= runtime->period_size;126127return tx_ptr;128}129130static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s,131struct snd_pcm_runtime *runtime,132unsigned int rx_ptr, bool *period_elapsed)133{134unsigned int period_pos = rx_ptr % runtime->period_size;135void __iomem *i2s_base = kmb_i2s->i2s_base;136int chan = kmb_i2s->config.chan_nr;137void *buf = runtime->dma_area;138int i, j;139140/* KMB i2s uses two separate L/R FIFO */141for (i = 0; i < kmb_i2s->fifo_th; i++) {142for (j = 0; j < chan / 2; j++) {143if (kmb_i2s->config.data_width == 16) {144((u16 *)buf)[rx_ptr * chan + (j * 2)] =145readl(i2s_base + LRBR_LTHR(j));146((u16 *)buf)[rx_ptr * chan + ((j * 2) + 1)] =147readl(i2s_base + RRBR_RTHR(j));148} else {149((u32 *)buf)[rx_ptr * chan + (j * 2)] =150readl(i2s_base + LRBR_LTHR(j));151((u32 *)buf)[rx_ptr * chan + ((j * 2) + 1)] =152readl(i2s_base + RRBR_RTHR(j));153}154}155period_pos++;156157if (++rx_ptr >= runtime->buffer_size)158rx_ptr = 0;159}160161*period_elapsed = period_pos >= runtime->period_size;162163return rx_ptr;164}165166static inline void kmb_i2s_disable_channels(struct kmb_i2s_info *kmb_i2s,167u32 stream)168{169u32 i;170171/* Disable all channels regardless of configuration*/172if (stream == SNDRV_PCM_STREAM_PLAYBACK) {173for (i = 0; i < MAX_ISR; i++)174writel(0, kmb_i2s->i2s_base + TER(i));175} else {176for (i = 0; i < MAX_ISR; i++)177writel(0, kmb_i2s->i2s_base + RER(i));178}179}180181static inline void kmb_i2s_clear_irqs(struct kmb_i2s_info *kmb_i2s, u32 stream)182{183struct i2s_clk_config_data *config = &kmb_i2s->config;184u32 i;185186if (stream == SNDRV_PCM_STREAM_PLAYBACK) {187for (i = 0; i < config->chan_nr / 2; i++)188readl(kmb_i2s->i2s_base + TOR(i));189} else {190for (i = 0; i < config->chan_nr / 2; i++)191readl(kmb_i2s->i2s_base + ROR(i));192}193}194195static inline void kmb_i2s_irq_trigger(struct kmb_i2s_info *kmb_i2s,196u32 stream, int chan_nr, bool trigger)197{198u32 i, irq;199u32 flag;200201if (stream == SNDRV_PCM_STREAM_PLAYBACK)202flag = TX_INT_FLAG;203else204flag = RX_INT_FLAG;205206for (i = 0; i < chan_nr / 2; i++) {207irq = readl(kmb_i2s->i2s_base + IMR(i));208209if (trigger)210irq = irq & ~flag;211else212irq = irq | flag;213214writel(irq, kmb_i2s->i2s_base + IMR(i));215}216}217218static void kmb_pcm_operation(struct kmb_i2s_info *kmb_i2s, bool playback)219{220struct snd_pcm_substream *substream;221bool period_elapsed;222unsigned int new_ptr;223unsigned int ptr;224225if (playback)226substream = kmb_i2s->tx_substream;227else228substream = kmb_i2s->rx_substream;229230if (!substream || !snd_pcm_running(substream))231return;232233if (playback) {234ptr = kmb_i2s->tx_ptr;235new_ptr = kmb_pcm_tx_fn(kmb_i2s, substream->runtime,236ptr, &period_elapsed);237cmpxchg(&kmb_i2s->tx_ptr, ptr, new_ptr);238} else {239ptr = kmb_i2s->rx_ptr;240new_ptr = kmb_pcm_rx_fn(kmb_i2s, substream->runtime,241ptr, &period_elapsed);242cmpxchg(&kmb_i2s->rx_ptr, ptr, new_ptr);243}244245if (period_elapsed)246snd_pcm_period_elapsed(substream);247}248249static int kmb_pcm_open(struct snd_soc_component *component,250struct snd_pcm_substream *substream)251{252struct snd_pcm_runtime *runtime = substream->runtime;253struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);254struct kmb_i2s_info *kmb_i2s;255256kmb_i2s = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));257snd_soc_set_runtime_hwparams(substream, &kmb_pcm_hardware);258snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);259runtime->private_data = kmb_i2s;260261return 0;262}263264static int kmb_pcm_trigger(struct snd_soc_component *component,265struct snd_pcm_substream *substream, int cmd)266{267struct snd_pcm_runtime *runtime = substream->runtime;268struct kmb_i2s_info *kmb_i2s = runtime->private_data;269270switch (cmd) {271case SNDRV_PCM_TRIGGER_START:272if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {273kmb_i2s->tx_ptr = 0;274kmb_i2s->tx_substream = substream;275} else {276kmb_i2s->rx_ptr = 0;277kmb_i2s->rx_substream = substream;278}279break;280case SNDRV_PCM_TRIGGER_STOP:281if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)282kmb_i2s->tx_substream = NULL;283else284kmb_i2s->rx_substream = NULL;285kmb_i2s->iec958_fmt = false;286break;287default:288return -EINVAL;289}290291return 0;292}293294static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id)295{296struct kmb_i2s_info *kmb_i2s = dev_id;297struct i2s_clk_config_data *config = &kmb_i2s->config;298irqreturn_t ret = IRQ_NONE;299u32 tx_enabled = 0;300u32 isr[4];301int i;302303for (i = 0; i < config->chan_nr / 2; i++)304isr[i] = readl(kmb_i2s->i2s_base + ISR(i));305306kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);307kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);308/* Only check TX interrupt if TX is active */309tx_enabled = readl(kmb_i2s->i2s_base + ITER);310311/*312* Data available. Retrieve samples from FIFO313*/314315/*316* 8 channel audio will have isr[0..2] triggered,317* reading the specific isr based on the audio configuration,318* to avoid reading the buffers too early.319*/320switch (config->chan_nr) {321case 2:322if (isr[0] & ISR_RXDA)323kmb_pcm_operation(kmb_i2s, false);324ret = IRQ_HANDLED;325break;326case 4:327if (isr[1] & ISR_RXDA)328kmb_pcm_operation(kmb_i2s, false);329ret = IRQ_HANDLED;330break;331case 8:332if (isr[3] & ISR_RXDA)333kmb_pcm_operation(kmb_i2s, false);334ret = IRQ_HANDLED;335break;336}337338for (i = 0; i < config->chan_nr / 2; i++) {339/*340* Check if TX fifo is empty. If empty fill FIFO with samples341*/342if ((isr[i] & ISR_TXFE) && tx_enabled) {343kmb_pcm_operation(kmb_i2s, true);344ret = IRQ_HANDLED;345}346347/* Error Handling: TX */348if (isr[i] & ISR_TXFO) {349dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n", i);350ret = IRQ_HANDLED;351}352/* Error Handling: RX */353if (isr[i] & ISR_RXFO) {354dev_dbg(kmb_i2s->dev, "RX overrun (ch_id=%d)\n", i);355ret = IRQ_HANDLED;356}357}358359return ret;360}361362static int kmb_platform_pcm_new(struct snd_soc_component *component,363struct snd_soc_pcm_runtime *soc_runtime)364{365size_t size = kmb_pcm_hardware.buffer_bytes_max;366/* Use SNDRV_DMA_TYPE_CONTINUOUS as KMB doesn't use PCI sg buffer */367snd_pcm_set_managed_buffer_all(soc_runtime->pcm,368SNDRV_DMA_TYPE_CONTINUOUS,369NULL, size, size);370return 0;371}372373static snd_pcm_uframes_t kmb_pcm_pointer(struct snd_soc_component *component,374struct snd_pcm_substream *substream)375{376struct snd_pcm_runtime *runtime = substream->runtime;377struct kmb_i2s_info *kmb_i2s = runtime->private_data;378snd_pcm_uframes_t pos;379380if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)381pos = kmb_i2s->tx_ptr;382else383pos = kmb_i2s->rx_ptr;384385return pos < runtime->buffer_size ? pos : 0;386}387388static const struct snd_soc_component_driver kmb_component = {389.name = "kmb",390.pcm_construct = kmb_platform_pcm_new,391.open = kmb_pcm_open,392.trigger = kmb_pcm_trigger,393.pointer = kmb_pcm_pointer,394.legacy_dai_naming = 1,395};396397static const struct snd_soc_component_driver kmb_component_dma = {398.name = "kmb",399.legacy_dai_naming = 1,400};401402static int kmb_probe(struct snd_soc_dai *cpu_dai)403{404struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);405406if (kmb_i2s->use_pio)407return 0;408409snd_soc_dai_init_dma_data(cpu_dai, &kmb_i2s->play_dma_data,410&kmb_i2s->capture_dma_data);411412return 0;413}414415static inline void kmb_i2s_enable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream)416{417u32 dma_reg;418419dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR);420/* Enable DMA handshake for stream */421if (stream == SNDRV_PCM_STREAM_PLAYBACK)422dma_reg |= I2S_DMAEN_TXBLOCK;423else424dma_reg |= I2S_DMAEN_RXBLOCK;425426writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR);427}428429static inline void kmb_i2s_disable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream)430{431u32 dma_reg;432433dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR);434/* Disable DMA handshake for stream */435if (stream == SNDRV_PCM_STREAM_PLAYBACK) {436dma_reg &= ~I2S_DMAEN_TXBLOCK;437writel(1, kmb_i2s->i2s_base + I2S_RTXDMA);438} else {439dma_reg &= ~I2S_DMAEN_RXBLOCK;440writel(1, kmb_i2s->i2s_base + I2S_RRXDMA);441}442writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR);443}444445static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s,446struct snd_pcm_substream *substream)447{448struct i2s_clk_config_data *config = &kmb_i2s->config;449450/* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */451writel(1, kmb_i2s->i2s_base + IER);452453if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)454writel(1, kmb_i2s->i2s_base + ITER);455else456writel(1, kmb_i2s->i2s_base + IRER);457458if (kmb_i2s->use_pio)459kmb_i2s_irq_trigger(kmb_i2s, substream->stream,460config->chan_nr, true);461else462kmb_i2s_enable_dma(kmb_i2s, substream->stream);463464if (kmb_i2s->clock_provider)465writel(1, kmb_i2s->i2s_base + CER);466else467writel(0, kmb_i2s->i2s_base + CER);468}469470static void kmb_i2s_stop(struct kmb_i2s_info *kmb_i2s,471struct snd_pcm_substream *substream)472{473/* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */474kmb_i2s_clear_irqs(kmb_i2s, substream->stream);475476if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)477writel(0, kmb_i2s->i2s_base + ITER);478else479writel(0, kmb_i2s->i2s_base + IRER);480481kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false);482483if (!kmb_i2s->active) {484writel(0, kmb_i2s->i2s_base + CER);485writel(0, kmb_i2s->i2s_base + IER);486}487}488489static void kmb_disable_clk(void *clk)490{491clk_disable_unprepare(clk);492}493494static int kmb_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)495{496struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);497int ret;498499switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {500case SND_SOC_DAIFMT_BC_FC:501kmb_i2s->clock_provider = false;502ret = 0;503break;504case SND_SOC_DAIFMT_BP_FP:505writel(CLOCK_PROVIDER_MODE, kmb_i2s->pss_base + I2S_GEN_CFG_0);506507ret = clk_prepare_enable(kmb_i2s->clk_i2s);508if (ret < 0)509return ret;510511ret = devm_add_action_or_reset(kmb_i2s->dev, kmb_disable_clk,512kmb_i2s->clk_i2s);513if (ret)514return ret;515516kmb_i2s->clock_provider = true;517break;518default:519return -EINVAL;520}521522return ret;523}524525static int kmb_dai_trigger(struct snd_pcm_substream *substream,526int cmd, struct snd_soc_dai *cpu_dai)527{528struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);529530switch (cmd) {531case SNDRV_PCM_TRIGGER_START:532/* Keep track of i2s activity before turn off533* the i2s interface534*/535kmb_i2s->active++;536kmb_i2s_start(kmb_i2s, substream);537break;538case SNDRV_PCM_TRIGGER_STOP:539kmb_i2s->active--;540if (kmb_i2s->use_pio)541kmb_i2s_stop(kmb_i2s, substream);542break;543default:544return -EINVAL;545}546547return 0;548}549550static void kmb_i2s_config(struct kmb_i2s_info *kmb_i2s, int stream)551{552struct i2s_clk_config_data *config = &kmb_i2s->config;553u32 ch_reg;554555kmb_i2s_disable_channels(kmb_i2s, stream);556557for (ch_reg = 0; ch_reg < config->chan_nr / 2; ch_reg++) {558if (stream == SNDRV_PCM_STREAM_PLAYBACK) {559writel(kmb_i2s->xfer_resolution,560kmb_i2s->i2s_base + TCR(ch_reg));561562writel(kmb_i2s->fifo_th - 1,563kmb_i2s->i2s_base + TFCR(ch_reg));564565writel(1, kmb_i2s->i2s_base + TER(ch_reg));566} else {567writel(kmb_i2s->xfer_resolution,568kmb_i2s->i2s_base + RCR(ch_reg));569570writel(kmb_i2s->fifo_th - 1,571kmb_i2s->i2s_base + RFCR(ch_reg));572573writel(1, kmb_i2s->i2s_base + RER(ch_reg));574}575}576}577578static int kmb_dai_hw_params(struct snd_pcm_substream *substream,579struct snd_pcm_hw_params *hw_params,580struct snd_soc_dai *cpu_dai)581{582struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);583struct i2s_clk_config_data *config = &kmb_i2s->config;584u32 write_val;585int ret;586587switch (params_format(hw_params)) {588case SNDRV_PCM_FORMAT_S16_LE:589config->data_width = 16;590kmb_i2s->ccr = 0x00;591kmb_i2s->xfer_resolution = 0x02;592kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;593kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;594break;595case SNDRV_PCM_FORMAT_S24_LE:596config->data_width = 32;597kmb_i2s->ccr = 0x14;598kmb_i2s->xfer_resolution = 0x05;599kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;600kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;601break;602case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:603kmb_i2s->iec958_fmt = true;604fallthrough;605case SNDRV_PCM_FORMAT_S32_LE:606config->data_width = 32;607kmb_i2s->ccr = 0x10;608kmb_i2s->xfer_resolution = 0x05;609kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;610kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;611break;612default:613dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt");614return -EINVAL;615}616617config->chan_nr = params_channels(hw_params);618619switch (config->chan_nr) {620case 8:621case 4:622/*623* Platform is not capable of providing clocks for624* multi channel audio625*/626if (kmb_i2s->clock_provider)627return -EINVAL;628629write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |630(config->data_width << DATA_WIDTH_CONFIG_BIT) |631TDM_OPERATION;632633writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);634break;635case 2:636/*637* Platform is only capable of providing clocks need for638* 2 channel master mode639*/640if (!(kmb_i2s->clock_provider))641return -EINVAL;642643write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |644(config->data_width << DATA_WIDTH_CONFIG_BIT) |645CLOCK_PROVIDER_MODE | I2S_OPERATION;646647writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);648break;649default:650dev_dbg(kmb_i2s->dev, "channel not supported\n");651return -EINVAL;652}653654kmb_i2s_config(kmb_i2s, substream->stream);655656writel(kmb_i2s->ccr, kmb_i2s->i2s_base + CCR);657658config->sample_rate = params_rate(hw_params);659660if (kmb_i2s->clock_provider) {661/* Only 2 ch supported in Master mode */662u32 bitclk = config->sample_rate * config->data_width * 2;663664ret = clk_set_rate(kmb_i2s->clk_i2s, bitclk);665if (ret) {666dev_err(kmb_i2s->dev,667"Can't set I2S clock rate: %d\n", ret);668return ret;669}670}671672return 0;673}674675static int kmb_dai_prepare(struct snd_pcm_substream *substream,676struct snd_soc_dai *cpu_dai)677{678struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);679680if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)681writel(1, kmb_i2s->i2s_base + TXFFR);682else683writel(1, kmb_i2s->i2s_base + RXFFR);684685return 0;686}687688static int kmb_dai_startup(struct snd_pcm_substream *substream,689struct snd_soc_dai *cpu_dai)690{691struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);692struct snd_dmaengine_dai_dma_data *dma_data;693694if (kmb_i2s->use_pio)695return 0;696697if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)698dma_data = &kmb_i2s->play_dma_data;699else700dma_data = &kmb_i2s->capture_dma_data;701702snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);703704return 0;705}706707static int kmb_dai_hw_free(struct snd_pcm_substream *substream,708struct snd_soc_dai *cpu_dai)709{710struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);711/* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */712if (kmb_i2s->use_pio)713kmb_i2s_clear_irqs(kmb_i2s, substream->stream);714715if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)716writel(0, kmb_i2s->i2s_base + ITER);717else718writel(0, kmb_i2s->i2s_base + IRER);719720if (kmb_i2s->use_pio)721kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false);722else723kmb_i2s_disable_dma(kmb_i2s, substream->stream);724725if (!kmb_i2s->active) {726writel(0, kmb_i2s->i2s_base + CER);727writel(0, kmb_i2s->i2s_base + IER);728}729730return 0;731}732733static const struct snd_soc_dai_ops kmb_dai_ops = {734.probe = kmb_probe,735.startup = kmb_dai_startup,736.trigger = kmb_dai_trigger,737.hw_params = kmb_dai_hw_params,738.hw_free = kmb_dai_hw_free,739.prepare = kmb_dai_prepare,740.set_fmt = kmb_set_dai_fmt,741};742743static struct snd_soc_dai_driver intel_kmb_hdmi_dai[] = {744{745.name = "intel_kmb_hdmi_i2s",746.playback = {747.channels_min = 2,748.channels_max = 2,749.rates = SNDRV_PCM_RATE_48000,750.rate_min = 48000,751.rate_max = 48000,752.formats = (SNDRV_PCM_FMTBIT_S16_LE |753SNDRV_PCM_FMTBIT_S24_LE |754SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE),755},756.ops = &kmb_dai_ops,757},758};759760static struct snd_soc_dai_driver intel_kmb_i2s_dai[] = {761{762.name = "intel_kmb_i2s",763.playback = {764.channels_min = 2,765.channels_max = 2,766.rates = SNDRV_PCM_RATE_8000 |767SNDRV_PCM_RATE_16000 |768SNDRV_PCM_RATE_48000,769.rate_min = 8000,770.rate_max = 48000,771.formats = (SNDRV_PCM_FMTBIT_S32_LE |772SNDRV_PCM_FMTBIT_S24_LE |773SNDRV_PCM_FMTBIT_S16_LE),774},775.capture = {776.channels_min = 2,777.channels_max = 2,778.rates = SNDRV_PCM_RATE_8000 |779SNDRV_PCM_RATE_16000 |780SNDRV_PCM_RATE_48000,781.rate_min = 8000,782.rate_max = 48000,783.formats = (SNDRV_PCM_FMTBIT_S32_LE |784SNDRV_PCM_FMTBIT_S24_LE |785SNDRV_PCM_FMTBIT_S16_LE),786},787.ops = &kmb_dai_ops,788},789};790791static struct snd_soc_dai_driver intel_kmb_tdm_dai[] = {792{793.name = "intel_kmb_tdm",794.capture = {795.channels_min = 4,796.channels_max = 8,797.rates = SNDRV_PCM_RATE_8000 |798SNDRV_PCM_RATE_16000 |799SNDRV_PCM_RATE_48000,800.rate_min = 8000,801.rate_max = 48000,802.formats = (SNDRV_PCM_FMTBIT_S32_LE |803SNDRV_PCM_FMTBIT_S24_LE |804SNDRV_PCM_FMTBIT_S16_LE),805},806.ops = &kmb_dai_ops,807},808};809810static const struct of_device_id kmb_plat_of_match[] = {811{ .compatible = "intel,keembay-i2s", .data = &intel_kmb_i2s_dai},812{ .compatible = "intel,keembay-hdmi-i2s", .data = &intel_kmb_hdmi_dai},813{ .compatible = "intel,keembay-tdm", .data = &intel_kmb_tdm_dai},814{}815};816MODULE_DEVICE_TABLE(of, kmb_plat_of_match);817818static int kmb_plat_dai_probe(struct platform_device *pdev)819{820struct device_node *np = pdev->dev.of_node;821struct snd_soc_dai_driver *kmb_i2s_dai;822struct device *dev = &pdev->dev;823struct kmb_i2s_info *kmb_i2s;824struct resource *res;825int ret, irq;826u32 comp1_reg;827828kmb_i2s = devm_kzalloc(dev, sizeof(*kmb_i2s), GFP_KERNEL);829if (!kmb_i2s)830return -ENOMEM;831832kmb_i2s_dai = (struct snd_soc_dai_driver *)device_get_match_data(&pdev->dev);833834/* Prepare the related clocks */835kmb_i2s->clk_apb = devm_clk_get(dev, "apb_clk");836if (IS_ERR(kmb_i2s->clk_apb)) {837dev_err(dev, "Failed to get apb clock\n");838return PTR_ERR(kmb_i2s->clk_apb);839}840841ret = clk_prepare_enable(kmb_i2s->clk_apb);842if (ret < 0)843return ret;844845ret = devm_add_action_or_reset(dev, kmb_disable_clk, kmb_i2s->clk_apb);846if (ret) {847dev_err(dev, "Failed to add clk_apb reset action\n");848return ret;849}850851kmb_i2s->clk_i2s = devm_clk_get(dev, "osc");852if (IS_ERR(kmb_i2s->clk_i2s)) {853dev_err(dev, "Failed to get osc clock\n");854return PTR_ERR(kmb_i2s->clk_i2s);855}856857kmb_i2s->i2s_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);858if (IS_ERR(kmb_i2s->i2s_base))859return PTR_ERR(kmb_i2s->i2s_base);860861kmb_i2s->pss_base = devm_platform_ioremap_resource(pdev, 1);862if (IS_ERR(kmb_i2s->pss_base))863return PTR_ERR(kmb_i2s->pss_base);864865kmb_i2s->dev = &pdev->dev;866867comp1_reg = readl(kmb_i2s->i2s_base + I2S_COMP_PARAM_1);868869kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2;870871kmb_i2s->use_pio = !of_property_present(np, "dmas");872873if (kmb_i2s->use_pio) {874irq = platform_get_irq_optional(pdev, 0);875if (irq > 0) {876ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0,877pdev->name, kmb_i2s);878if (ret < 0) {879dev_err(dev, "failed to request irq\n");880return ret;881}882}883ret = devm_snd_soc_register_component(dev, &kmb_component,884kmb_i2s_dai, 1);885} else {886kmb_i2s->play_dma_data.addr = res->start + I2S_TXDMA;887kmb_i2s->capture_dma_data.addr = res->start + I2S_RXDMA;888ret = snd_dmaengine_pcm_register(&pdev->dev,889NULL, 0);890if (ret) {891dev_err(&pdev->dev, "could not register dmaengine: %d\n",892ret);893return ret;894}895ret = devm_snd_soc_register_component(dev, &kmb_component_dma,896kmb_i2s_dai, 1);897}898899if (ret) {900dev_err(dev, "not able to register dai\n");901return ret;902}903904/* To ensure none of the channels are enabled at boot up */905kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);906kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);907908dev_set_drvdata(dev, kmb_i2s);909910return ret;911}912913static struct platform_driver kmb_plat_dai_driver = {914.driver = {915.name = "kmb-plat-dai",916.of_match_table = kmb_plat_of_match,917},918.probe = kmb_plat_dai_probe,919};920921module_platform_driver(kmb_plat_dai_driver);922923MODULE_DESCRIPTION("ASoC Intel KeemBay Platform driver");924MODULE_AUTHOR("Sia Jee Heng <[email protected]>");925MODULE_AUTHOR("Sit, Michael Wei Hong <[email protected]>");926MODULE_LICENSE("GPL v2");927MODULE_ALIAS("platform:kmb_platform");928929930