18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// imx-ssi.c -- ALSA Soc Audio Layer 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> 68c2ecf20Sopenharmony_ci// 78c2ecf20Sopenharmony_ci// This code is based on code copyrighted by Freescale, 88c2ecf20Sopenharmony_ci// Liam Girdwood, Javier Martin and probably others. 98c2ecf20Sopenharmony_ci// 108c2ecf20Sopenharmony_ci// The i.MX SSI core has some nasty limitations in AC97 mode. While most 118c2ecf20Sopenharmony_ci// sane processor vendors have a FIFO per AC97 slot, the i.MX has only 128c2ecf20Sopenharmony_ci// one FIFO which combines all valid receive slots. We cannot even select 138c2ecf20Sopenharmony_ci// which slots we want to receive. The WM9712 with which this driver 148c2ecf20Sopenharmony_ci// was developed with always sends GPIO status data in slot 12 which 158c2ecf20Sopenharmony_ci// we receive in our (PCM-) data stream. The only chance we have is to 168c2ecf20Sopenharmony_ci// manually skip this data in the FIQ handler. With sampling rates different 178c2ecf20Sopenharmony_ci// from 48000Hz not every frame has valid receive data, so the ratio 188c2ecf20Sopenharmony_ci// between pcm data and GPIO status data changes. Our FIQ handler is not 198c2ecf20Sopenharmony_ci// able to handle this, hence this driver only works with 48000Hz sampling 208c2ecf20Sopenharmony_ci// rate. 218c2ecf20Sopenharmony_ci// Reading and writing AC97 registers is another challenge. The core 228c2ecf20Sopenharmony_ci// provides us status bits when the read register is updated with *another* 238c2ecf20Sopenharmony_ci// value. When we read the same register two times (and the register still 248c2ecf20Sopenharmony_ci// contains the same value) these status bits are not set. We work 258c2ecf20Sopenharmony_ci// around this by not polling these bits but only wait a fixed delay. 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#include <linux/clk.h> 288c2ecf20Sopenharmony_ci#include <linux/delay.h> 298c2ecf20Sopenharmony_ci#include <linux/device.h> 308c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 318c2ecf20Sopenharmony_ci#include <linux/init.h> 328c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 338c2ecf20Sopenharmony_ci#include <linux/module.h> 348c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 358c2ecf20Sopenharmony_ci#include <linux/slab.h> 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#include <sound/core.h> 388c2ecf20Sopenharmony_ci#include <sound/initval.h> 398c2ecf20Sopenharmony_ci#include <sound/pcm.h> 408c2ecf20Sopenharmony_ci#include <sound/pcm_params.h> 418c2ecf20Sopenharmony_ci#include <sound/soc.h> 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#include <linux/platform_data/asoc-imx-ssi.h> 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#include "imx-ssi.h" 468c2ecf20Sopenharmony_ci#include "fsl_utils.h" 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV) 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/* 518c2ecf20Sopenharmony_ci * SSI Network Mode or TDM slots configuration. 528c2ecf20Sopenharmony_ci * Should only be called when port is inactive (i.e. SSIEN = 0). 538c2ecf20Sopenharmony_ci */ 548c2ecf20Sopenharmony_cistatic int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, 558c2ecf20Sopenharmony_ci unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 588c2ecf20Sopenharmony_ci u32 sccr; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci sccr = readl(ssi->base + SSI_STCCR); 618c2ecf20Sopenharmony_ci sccr &= ~SSI_STCCR_DC_MASK; 628c2ecf20Sopenharmony_ci sccr |= SSI_STCCR_DC(slots - 1); 638c2ecf20Sopenharmony_ci writel(sccr, ssi->base + SSI_STCCR); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci sccr = readl(ssi->base + SSI_SRCCR); 668c2ecf20Sopenharmony_ci sccr &= ~SSI_STCCR_DC_MASK; 678c2ecf20Sopenharmony_ci sccr |= SSI_STCCR_DC(slots - 1); 688c2ecf20Sopenharmony_ci writel(sccr, ssi->base + SSI_SRCCR); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci writel(~tx_mask, ssi->base + SSI_STMSK); 718c2ecf20Sopenharmony_ci writel(~rx_mask, ssi->base + SSI_SRMSK); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci return 0; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci/* 778c2ecf20Sopenharmony_ci * SSI DAI format configuration. 788c2ecf20Sopenharmony_ci * Should only be called when port is inactive (i.e. SSIEN = 0). 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_cistatic int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 838c2ecf20Sopenharmony_ci u32 strcr = 0, scr; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* DAI mode */ 888c2ecf20Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 898c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_I2S: 908c2ecf20Sopenharmony_ci /* data on rising edge of bclk, frame low 1clk before data */ 918c2ecf20Sopenharmony_ci strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSI | 928c2ecf20Sopenharmony_ci SSI_STCR_TEFS; 938c2ecf20Sopenharmony_ci scr |= SSI_SCR_NET; 948c2ecf20Sopenharmony_ci if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) { 958c2ecf20Sopenharmony_ci scr &= ~SSI_I2S_MODE_MASK; 968c2ecf20Sopenharmony_ci scr |= SSI_SCR_I2S_MODE_SLAVE; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci break; 998c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_LEFT_J: 1008c2ecf20Sopenharmony_ci /* data on rising edge of bclk, frame high with data */ 1018c2ecf20Sopenharmony_ci strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP; 1028c2ecf20Sopenharmony_ci break; 1038c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_DSP_B: 1048c2ecf20Sopenharmony_ci /* data on rising edge of bclk, frame high with data */ 1058c2ecf20Sopenharmony_ci strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSL; 1068c2ecf20Sopenharmony_ci break; 1078c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_DSP_A: 1088c2ecf20Sopenharmony_ci /* data on rising edge of bclk, frame high 1clk before data */ 1098c2ecf20Sopenharmony_ci strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSL | 1108c2ecf20Sopenharmony_ci SSI_STCR_TEFS; 1118c2ecf20Sopenharmony_ci break; 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci /* DAI clock inversion */ 1158c2ecf20Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 1168c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_IB_IF: 1178c2ecf20Sopenharmony_ci strcr ^= SSI_STCR_TSCKP | SSI_STCR_TFSI; 1188c2ecf20Sopenharmony_ci break; 1198c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_IB_NF: 1208c2ecf20Sopenharmony_ci strcr ^= SSI_STCR_TSCKP; 1218c2ecf20Sopenharmony_ci break; 1228c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_NB_IF: 1238c2ecf20Sopenharmony_ci strcr ^= SSI_STCR_TFSI; 1248c2ecf20Sopenharmony_ci break; 1258c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_NB_NF: 1268c2ecf20Sopenharmony_ci break; 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci /* DAI clock master masks */ 1308c2ecf20Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 1318c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_CBM_CFM: 1328c2ecf20Sopenharmony_ci break; 1338c2ecf20Sopenharmony_ci default: 1348c2ecf20Sopenharmony_ci /* Master mode not implemented, needs handling of clocks. */ 1358c2ecf20Sopenharmony_ci return -EINVAL; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci strcr |= SSI_STCR_TFEN0; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (ssi->flags & IMX_SSI_NET) 1418c2ecf20Sopenharmony_ci scr |= SSI_SCR_NET; 1428c2ecf20Sopenharmony_ci if (ssi->flags & IMX_SSI_SYN) 1438c2ecf20Sopenharmony_ci scr |= SSI_SCR_SYN; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci writel(strcr, ssi->base + SSI_STCR); 1468c2ecf20Sopenharmony_ci writel(strcr, ssi->base + SSI_SRCR); 1478c2ecf20Sopenharmony_ci writel(scr, ssi->base + SSI_SCR); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci return 0; 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci/* 1538c2ecf20Sopenharmony_ci * SSI system clock configuration. 1548c2ecf20Sopenharmony_ci * Should only be called when port is inactive (i.e. SSIEN = 0). 1558c2ecf20Sopenharmony_ci */ 1568c2ecf20Sopenharmony_cistatic int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, 1578c2ecf20Sopenharmony_ci int clk_id, unsigned int freq, int dir) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 1608c2ecf20Sopenharmony_ci u32 scr; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci scr = readl(ssi->base + SSI_SCR); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci switch (clk_id) { 1658c2ecf20Sopenharmony_ci case IMX_SSP_SYS_CLK: 1668c2ecf20Sopenharmony_ci if (dir == SND_SOC_CLOCK_OUT) 1678c2ecf20Sopenharmony_ci scr |= SSI_SCR_SYS_CLK_EN; 1688c2ecf20Sopenharmony_ci else 1698c2ecf20Sopenharmony_ci scr &= ~SSI_SCR_SYS_CLK_EN; 1708c2ecf20Sopenharmony_ci break; 1718c2ecf20Sopenharmony_ci default: 1728c2ecf20Sopenharmony_ci return -EINVAL; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci writel(scr, ssi->base + SSI_SCR); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci return 0; 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci/* 1818c2ecf20Sopenharmony_ci * SSI Clock dividers 1828c2ecf20Sopenharmony_ci * Should only be called when port is inactive (i.e. SSIEN = 0). 1838c2ecf20Sopenharmony_ci */ 1848c2ecf20Sopenharmony_cistatic int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, 1858c2ecf20Sopenharmony_ci int div_id, int div) 1868c2ecf20Sopenharmony_ci{ 1878c2ecf20Sopenharmony_ci struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 1888c2ecf20Sopenharmony_ci u32 stccr, srccr; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci stccr = readl(ssi->base + SSI_STCCR); 1918c2ecf20Sopenharmony_ci srccr = readl(ssi->base + SSI_SRCCR); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci switch (div_id) { 1948c2ecf20Sopenharmony_ci case IMX_SSI_TX_DIV_2: 1958c2ecf20Sopenharmony_ci stccr &= ~SSI_STCCR_DIV2; 1968c2ecf20Sopenharmony_ci stccr |= div; 1978c2ecf20Sopenharmony_ci break; 1988c2ecf20Sopenharmony_ci case IMX_SSI_TX_DIV_PSR: 1998c2ecf20Sopenharmony_ci stccr &= ~SSI_STCCR_PSR; 2008c2ecf20Sopenharmony_ci stccr |= div; 2018c2ecf20Sopenharmony_ci break; 2028c2ecf20Sopenharmony_ci case IMX_SSI_TX_DIV_PM: 2038c2ecf20Sopenharmony_ci stccr &= ~0xff; 2048c2ecf20Sopenharmony_ci stccr |= SSI_STCCR_PM(div); 2058c2ecf20Sopenharmony_ci break; 2068c2ecf20Sopenharmony_ci case IMX_SSI_RX_DIV_2: 2078c2ecf20Sopenharmony_ci stccr &= ~SSI_STCCR_DIV2; 2088c2ecf20Sopenharmony_ci stccr |= div; 2098c2ecf20Sopenharmony_ci break; 2108c2ecf20Sopenharmony_ci case IMX_SSI_RX_DIV_PSR: 2118c2ecf20Sopenharmony_ci stccr &= ~SSI_STCCR_PSR; 2128c2ecf20Sopenharmony_ci stccr |= div; 2138c2ecf20Sopenharmony_ci break; 2148c2ecf20Sopenharmony_ci case IMX_SSI_RX_DIV_PM: 2158c2ecf20Sopenharmony_ci stccr &= ~0xff; 2168c2ecf20Sopenharmony_ci stccr |= SSI_STCCR_PM(div); 2178c2ecf20Sopenharmony_ci break; 2188c2ecf20Sopenharmony_ci default: 2198c2ecf20Sopenharmony_ci return -EINVAL; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci writel(stccr, ssi->base + SSI_STCCR); 2238c2ecf20Sopenharmony_ci writel(srccr, ssi->base + SSI_SRCCR); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci return 0; 2268c2ecf20Sopenharmony_ci} 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci/* 2298c2ecf20Sopenharmony_ci * Should only be called when port is inactive (i.e. SSIEN = 0), 2308c2ecf20Sopenharmony_ci * although can be called multiple times by upper layers. 2318c2ecf20Sopenharmony_ci */ 2328c2ecf20Sopenharmony_cistatic int imx_ssi_hw_params(struct snd_pcm_substream *substream, 2338c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *params, 2348c2ecf20Sopenharmony_ci struct snd_soc_dai *cpu_dai) 2358c2ecf20Sopenharmony_ci{ 2368c2ecf20Sopenharmony_ci struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 2378c2ecf20Sopenharmony_ci u32 reg, sccr; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci /* Tx/Rx config */ 2408c2ecf20Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 2418c2ecf20Sopenharmony_ci reg = SSI_STCCR; 2428c2ecf20Sopenharmony_ci else 2438c2ecf20Sopenharmony_ci reg = SSI_SRCCR; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci if (ssi->flags & IMX_SSI_SYN) 2468c2ecf20Sopenharmony_ci reg = SSI_STCCR; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci /* DAI data (word) size */ 2518c2ecf20Sopenharmony_ci switch (params_format(params)) { 2528c2ecf20Sopenharmony_ci case SNDRV_PCM_FORMAT_S16_LE: 2538c2ecf20Sopenharmony_ci sccr |= SSI_SRCCR_WL(16); 2548c2ecf20Sopenharmony_ci break; 2558c2ecf20Sopenharmony_ci case SNDRV_PCM_FORMAT_S20_3LE: 2568c2ecf20Sopenharmony_ci sccr |= SSI_SRCCR_WL(20); 2578c2ecf20Sopenharmony_ci break; 2588c2ecf20Sopenharmony_ci case SNDRV_PCM_FORMAT_S24_LE: 2598c2ecf20Sopenharmony_ci sccr |= SSI_SRCCR_WL(24); 2608c2ecf20Sopenharmony_ci break; 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci writel(sccr, ssi->base + reg); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci return 0; 2668c2ecf20Sopenharmony_ci} 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cistatic int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd, 2698c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai); 2728c2ecf20Sopenharmony_ci unsigned int sier_bits, sier; 2738c2ecf20Sopenharmony_ci unsigned int scr; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci scr = readl(ssi->base + SSI_SCR); 2768c2ecf20Sopenharmony_ci sier = readl(ssi->base + SSI_SIER); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 2798c2ecf20Sopenharmony_ci if (ssi->flags & IMX_SSI_DMA) 2808c2ecf20Sopenharmony_ci sier_bits = SSI_SIER_TDMAE; 2818c2ecf20Sopenharmony_ci else 2828c2ecf20Sopenharmony_ci sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN; 2838c2ecf20Sopenharmony_ci } else { 2848c2ecf20Sopenharmony_ci if (ssi->flags & IMX_SSI_DMA) 2858c2ecf20Sopenharmony_ci sier_bits = SSI_SIER_RDMAE; 2868c2ecf20Sopenharmony_ci else 2878c2ecf20Sopenharmony_ci sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci switch (cmd) { 2918c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 2928c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_RESUME: 2938c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 2948c2ecf20Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 2958c2ecf20Sopenharmony_ci scr |= SSI_SCR_TE; 2968c2ecf20Sopenharmony_ci else 2978c2ecf20Sopenharmony_ci scr |= SSI_SCR_RE; 2988c2ecf20Sopenharmony_ci sier |= sier_bits; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci scr |= SSI_SCR_SSIEN; 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci break; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 3058c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_SUSPEND: 3068c2ecf20Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 3078c2ecf20Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 3088c2ecf20Sopenharmony_ci scr &= ~SSI_SCR_TE; 3098c2ecf20Sopenharmony_ci else 3108c2ecf20Sopenharmony_ci scr &= ~SSI_SCR_RE; 3118c2ecf20Sopenharmony_ci sier &= ~sier_bits; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci if (!(scr & (SSI_SCR_TE | SSI_SCR_RE))) 3148c2ecf20Sopenharmony_ci scr &= ~SSI_SCR_SSIEN; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci break; 3178c2ecf20Sopenharmony_ci default: 3188c2ecf20Sopenharmony_ci return -EINVAL; 3198c2ecf20Sopenharmony_ci } 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci if (!(ssi->flags & IMX_SSI_USE_AC97)) 3228c2ecf20Sopenharmony_ci /* rx/tx are always enabled to access ac97 registers */ 3238c2ecf20Sopenharmony_ci writel(scr, ssi->base + SSI_SCR); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci writel(sier, ssi->base + SSI_SIER); 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci return 0; 3288c2ecf20Sopenharmony_ci} 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = { 3318c2ecf20Sopenharmony_ci .hw_params = imx_ssi_hw_params, 3328c2ecf20Sopenharmony_ci .set_fmt = imx_ssi_set_dai_fmt, 3338c2ecf20Sopenharmony_ci .set_clkdiv = imx_ssi_set_dai_clkdiv, 3348c2ecf20Sopenharmony_ci .set_sysclk = imx_ssi_set_dai_sysclk, 3358c2ecf20Sopenharmony_ci .set_tdm_slot = imx_ssi_set_dai_tdm_slot, 3368c2ecf20Sopenharmony_ci .trigger = imx_ssi_trigger, 3378c2ecf20Sopenharmony_ci}; 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_cistatic int imx_ssi_dai_probe(struct snd_soc_dai *dai) 3408c2ecf20Sopenharmony_ci{ 3418c2ecf20Sopenharmony_ci struct imx_ssi *ssi = dev_get_drvdata(dai->dev); 3428c2ecf20Sopenharmony_ci uint32_t val; 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci snd_soc_dai_set_drvdata(dai, ssi); 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.maxburst) | 3478c2ecf20Sopenharmony_ci SSI_SFCSR_RFWM0(ssi->dma_params_rx.maxburst); 3488c2ecf20Sopenharmony_ci writel(val, ssi->base + SSI_SFCSR); 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci /* Tx/Rx config */ 3518c2ecf20Sopenharmony_ci dai->playback_dma_data = &ssi->dma_params_tx; 3528c2ecf20Sopenharmony_ci dai->capture_dma_data = &ssi->dma_params_rx; 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci return 0; 3558c2ecf20Sopenharmony_ci} 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver imx_ssi_dai = { 3588c2ecf20Sopenharmony_ci .probe = imx_ssi_dai_probe, 3598c2ecf20Sopenharmony_ci .playback = { 3608c2ecf20Sopenharmony_ci .channels_min = 1, 3618c2ecf20Sopenharmony_ci .channels_max = 2, 3628c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_8000_96000, 3638c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 3648c2ecf20Sopenharmony_ci }, 3658c2ecf20Sopenharmony_ci .capture = { 3668c2ecf20Sopenharmony_ci .channels_min = 1, 3678c2ecf20Sopenharmony_ci .channels_max = 2, 3688c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_8000_96000, 3698c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 3708c2ecf20Sopenharmony_ci }, 3718c2ecf20Sopenharmony_ci .ops = &imx_ssi_pcm_dai_ops, 3728c2ecf20Sopenharmony_ci}; 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver imx_ac97_dai = { 3758c2ecf20Sopenharmony_ci .probe = imx_ssi_dai_probe, 3768c2ecf20Sopenharmony_ci .playback = { 3778c2ecf20Sopenharmony_ci .stream_name = "AC97 Playback", 3788c2ecf20Sopenharmony_ci .channels_min = 2, 3798c2ecf20Sopenharmony_ci .channels_max = 2, 3808c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_8000_48000, 3818c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 3828c2ecf20Sopenharmony_ci }, 3838c2ecf20Sopenharmony_ci .capture = { 3848c2ecf20Sopenharmony_ci .stream_name = "AC97 Capture", 3858c2ecf20Sopenharmony_ci .channels_min = 2, 3868c2ecf20Sopenharmony_ci .channels_max = 2, 3878c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_48000, 3888c2ecf20Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, 3898c2ecf20Sopenharmony_ci }, 3908c2ecf20Sopenharmony_ci .ops = &imx_ssi_pcm_dai_ops, 3918c2ecf20Sopenharmony_ci}; 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver imx_component = { 3948c2ecf20Sopenharmony_ci .name = DRV_NAME, 3958c2ecf20Sopenharmony_ci}; 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_cistatic void setup_channel_to_ac97(struct imx_ssi *imx_ssi) 3988c2ecf20Sopenharmony_ci{ 3998c2ecf20Sopenharmony_ci void __iomem *base = imx_ssi->base; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci writel(0x0, base + SSI_SCR); 4028c2ecf20Sopenharmony_ci writel(0x0, base + SSI_STCR); 4038c2ecf20Sopenharmony_ci writel(0x0, base + SSI_SRCR); 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR); 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci writel(SSI_SFCSR_RFWM0(8) | 4088c2ecf20Sopenharmony_ci SSI_SFCSR_TFWM0(8) | 4098c2ecf20Sopenharmony_ci SSI_SFCSR_RFWM1(8) | 4108c2ecf20Sopenharmony_ci SSI_SFCSR_TFWM1(8), base + SSI_SFCSR); 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR); 4138c2ecf20Sopenharmony_ci writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR); 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR); 4168c2ecf20Sopenharmony_ci writel(SSI_SOR_WAIT(3), base + SSI_SOR); 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN | 4198c2ecf20Sopenharmony_ci SSI_SCR_TE | SSI_SCR_RE, 4208c2ecf20Sopenharmony_ci base + SSI_SCR); 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci writel(SSI_SACNT_DEFAULT, base + SSI_SACNT); 4238c2ecf20Sopenharmony_ci writel(0xff, base + SSI_SACCDIS); 4248c2ecf20Sopenharmony_ci writel(0x300, base + SSI_SACCEN); 4258c2ecf20Sopenharmony_ci} 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_cistatic struct imx_ssi *ac97_ssi; 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_cistatic void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, 4308c2ecf20Sopenharmony_ci unsigned short val) 4318c2ecf20Sopenharmony_ci{ 4328c2ecf20Sopenharmony_ci struct imx_ssi *imx_ssi = ac97_ssi; 4338c2ecf20Sopenharmony_ci void __iomem *base = imx_ssi->base; 4348c2ecf20Sopenharmony_ci unsigned int lreg; 4358c2ecf20Sopenharmony_ci unsigned int lval; 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci if (reg > 0x7f) 4388c2ecf20Sopenharmony_ci return; 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci lreg = reg << 12; 4438c2ecf20Sopenharmony_ci writel(lreg, base + SSI_SACADD); 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci lval = val << 4; 4468c2ecf20Sopenharmony_ci writel(lval , base + SSI_SACDAT); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT); 4498c2ecf20Sopenharmony_ci udelay(100); 4508c2ecf20Sopenharmony_ci} 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_cistatic unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97, 4538c2ecf20Sopenharmony_ci unsigned short reg) 4548c2ecf20Sopenharmony_ci{ 4558c2ecf20Sopenharmony_ci struct imx_ssi *imx_ssi = ac97_ssi; 4568c2ecf20Sopenharmony_ci void __iomem *base = imx_ssi->base; 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci unsigned short val = -1; 4598c2ecf20Sopenharmony_ci unsigned int lreg; 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci lreg = (reg & 0x7f) << 12 ; 4628c2ecf20Sopenharmony_ci writel(lreg, base + SSI_SACADD); 4638c2ecf20Sopenharmony_ci writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT); 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci udelay(100); 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci val = (readl(base + SSI_SACDAT) >> 4) & 0xffff; 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci return val; 4728c2ecf20Sopenharmony_ci} 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_cistatic void imx_ssi_ac97_reset(struct snd_ac97 *ac97) 4758c2ecf20Sopenharmony_ci{ 4768c2ecf20Sopenharmony_ci struct imx_ssi *imx_ssi = ac97_ssi; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci if (imx_ssi->ac97_reset) 4798c2ecf20Sopenharmony_ci imx_ssi->ac97_reset(ac97); 4808c2ecf20Sopenharmony_ci /* First read sometimes fails, do a dummy read */ 4818c2ecf20Sopenharmony_ci imx_ssi_ac97_read(ac97, 0); 4828c2ecf20Sopenharmony_ci} 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_cistatic void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97) 4858c2ecf20Sopenharmony_ci{ 4868c2ecf20Sopenharmony_ci struct imx_ssi *imx_ssi = ac97_ssi; 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci if (imx_ssi->ac97_warm_reset) 4898c2ecf20Sopenharmony_ci imx_ssi->ac97_warm_reset(ac97); 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci /* First read sometimes fails, do a dummy read */ 4928c2ecf20Sopenharmony_ci imx_ssi_ac97_read(ac97, 0); 4938c2ecf20Sopenharmony_ci} 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_cistatic struct snd_ac97_bus_ops imx_ssi_ac97_ops = { 4968c2ecf20Sopenharmony_ci .read = imx_ssi_ac97_read, 4978c2ecf20Sopenharmony_ci .write = imx_ssi_ac97_write, 4988c2ecf20Sopenharmony_ci .reset = imx_ssi_ac97_reset, 4998c2ecf20Sopenharmony_ci .warm_reset = imx_ssi_ac97_warm_reset 5008c2ecf20Sopenharmony_ci}; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_cistatic int imx_ssi_probe(struct platform_device *pdev) 5038c2ecf20Sopenharmony_ci{ 5048c2ecf20Sopenharmony_ci struct resource *res; 5058c2ecf20Sopenharmony_ci struct imx_ssi *ssi; 5068c2ecf20Sopenharmony_ci struct imx_ssi_platform_data *pdata = pdev->dev.platform_data; 5078c2ecf20Sopenharmony_ci int ret = 0; 5088c2ecf20Sopenharmony_ci struct snd_soc_dai_driver *dai; 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci ssi = devm_kzalloc(&pdev->dev, sizeof(*ssi), GFP_KERNEL); 5118c2ecf20Sopenharmony_ci if (!ssi) 5128c2ecf20Sopenharmony_ci return -ENOMEM; 5138c2ecf20Sopenharmony_ci dev_set_drvdata(&pdev->dev, ssi); 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci if (pdata) { 5168c2ecf20Sopenharmony_ci ssi->ac97_reset = pdata->ac97_reset; 5178c2ecf20Sopenharmony_ci ssi->ac97_warm_reset = pdata->ac97_warm_reset; 5188c2ecf20Sopenharmony_ci ssi->flags = pdata->flags; 5198c2ecf20Sopenharmony_ci } 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci ssi->irq = platform_get_irq(pdev, 0); 5228c2ecf20Sopenharmony_ci if (ssi->irq < 0) 5238c2ecf20Sopenharmony_ci return ssi->irq; 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_ci ssi->clk = devm_clk_get(&pdev->dev, NULL); 5268c2ecf20Sopenharmony_ci if (IS_ERR(ssi->clk)) { 5278c2ecf20Sopenharmony_ci ret = PTR_ERR(ssi->clk); 5288c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Cannot get the clock: %d\n", 5298c2ecf20Sopenharmony_ci ret); 5308c2ecf20Sopenharmony_ci goto failed_clk; 5318c2ecf20Sopenharmony_ci } 5328c2ecf20Sopenharmony_ci ret = clk_prepare_enable(ssi->clk); 5338c2ecf20Sopenharmony_ci if (ret) 5348c2ecf20Sopenharmony_ci goto failed_clk; 5358c2ecf20Sopenharmony_ci 5368c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 5378c2ecf20Sopenharmony_ci ssi->base = devm_ioremap_resource(&pdev->dev, res); 5388c2ecf20Sopenharmony_ci if (IS_ERR(ssi->base)) { 5398c2ecf20Sopenharmony_ci ret = PTR_ERR(ssi->base); 5408c2ecf20Sopenharmony_ci goto failed_register; 5418c2ecf20Sopenharmony_ci } 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci if (ssi->flags & IMX_SSI_USE_AC97) { 5448c2ecf20Sopenharmony_ci if (ac97_ssi) { 5458c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "AC'97 SSI already registered\n"); 5468c2ecf20Sopenharmony_ci ret = -EBUSY; 5478c2ecf20Sopenharmony_ci goto failed_register; 5488c2ecf20Sopenharmony_ci } 5498c2ecf20Sopenharmony_ci ac97_ssi = ssi; 5508c2ecf20Sopenharmony_ci setup_channel_to_ac97(ssi); 5518c2ecf20Sopenharmony_ci dai = &imx_ac97_dai; 5528c2ecf20Sopenharmony_ci } else 5538c2ecf20Sopenharmony_ci dai = &imx_ssi_dai; 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci writel(0x0, ssi->base + SSI_SIER); 5568c2ecf20Sopenharmony_ci 5578c2ecf20Sopenharmony_ci ssi->dma_params_rx.addr = res->start + SSI_SRX0; 5588c2ecf20Sopenharmony_ci ssi->dma_params_tx.addr = res->start + SSI_STX0; 5598c2ecf20Sopenharmony_ci 5608c2ecf20Sopenharmony_ci ssi->dma_params_tx.maxburst = 6; 5618c2ecf20Sopenharmony_ci ssi->dma_params_rx.maxburst = 4; 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_ci ssi->dma_params_tx.filter_data = &ssi->filter_data_tx; 5648c2ecf20Sopenharmony_ci ssi->dma_params_rx.filter_data = &ssi->filter_data_rx; 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0"); 5678c2ecf20Sopenharmony_ci if (res) { 5688c2ecf20Sopenharmony_ci imx_pcm_dma_params_init_data(&ssi->filter_data_tx, res->start, 5698c2ecf20Sopenharmony_ci IMX_DMATYPE_SSI); 5708c2ecf20Sopenharmony_ci } 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0"); 5738c2ecf20Sopenharmony_ci if (res) { 5748c2ecf20Sopenharmony_ci imx_pcm_dma_params_init_data(&ssi->filter_data_rx, res->start, 5758c2ecf20Sopenharmony_ci IMX_DMATYPE_SSI); 5768c2ecf20Sopenharmony_ci } 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, ssi); 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci ret = snd_soc_set_ac97_ops(&imx_ssi_ac97_ops); 5818c2ecf20Sopenharmony_ci if (ret != 0) { 5828c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret); 5838c2ecf20Sopenharmony_ci goto failed_register; 5848c2ecf20Sopenharmony_ci } 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_ci ret = snd_soc_register_component(&pdev->dev, &imx_component, 5878c2ecf20Sopenharmony_ci dai, 1); 5888c2ecf20Sopenharmony_ci if (ret) { 5898c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "register DAI failed\n"); 5908c2ecf20Sopenharmony_ci goto failed_register; 5918c2ecf20Sopenharmony_ci } 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci ssi->fiq_params.irq = ssi->irq; 5948c2ecf20Sopenharmony_ci ssi->fiq_params.base = ssi->base; 5958c2ecf20Sopenharmony_ci ssi->fiq_params.dma_params_rx = &ssi->dma_params_rx; 5968c2ecf20Sopenharmony_ci ssi->fiq_params.dma_params_tx = &ssi->dma_params_tx; 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci ssi->fiq_init = imx_pcm_fiq_init(pdev, &ssi->fiq_params); 5998c2ecf20Sopenharmony_ci ssi->dma_init = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE); 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ci if (ssi->fiq_init && ssi->dma_init) { 6028c2ecf20Sopenharmony_ci ret = ssi->fiq_init; 6038c2ecf20Sopenharmony_ci goto failed_pcm; 6048c2ecf20Sopenharmony_ci } 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci return 0; 6078c2ecf20Sopenharmony_ci 6088c2ecf20Sopenharmony_cifailed_pcm: 6098c2ecf20Sopenharmony_ci snd_soc_unregister_component(&pdev->dev); 6108c2ecf20Sopenharmony_cifailed_register: 6118c2ecf20Sopenharmony_ci clk_disable_unprepare(ssi->clk); 6128c2ecf20Sopenharmony_cifailed_clk: 6138c2ecf20Sopenharmony_ci snd_soc_set_ac97_ops(NULL); 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_ci return ret; 6168c2ecf20Sopenharmony_ci} 6178c2ecf20Sopenharmony_ci 6188c2ecf20Sopenharmony_cistatic int imx_ssi_remove(struct platform_device *pdev) 6198c2ecf20Sopenharmony_ci{ 6208c2ecf20Sopenharmony_ci struct imx_ssi *ssi = platform_get_drvdata(pdev); 6218c2ecf20Sopenharmony_ci 6228c2ecf20Sopenharmony_ci if (!ssi->fiq_init) 6238c2ecf20Sopenharmony_ci imx_pcm_fiq_exit(pdev); 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_ci snd_soc_unregister_component(&pdev->dev); 6268c2ecf20Sopenharmony_ci 6278c2ecf20Sopenharmony_ci if (ssi->flags & IMX_SSI_USE_AC97) 6288c2ecf20Sopenharmony_ci ac97_ssi = NULL; 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_ci clk_disable_unprepare(ssi->clk); 6318c2ecf20Sopenharmony_ci snd_soc_set_ac97_ops(NULL); 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci return 0; 6348c2ecf20Sopenharmony_ci} 6358c2ecf20Sopenharmony_ci 6368c2ecf20Sopenharmony_cistatic struct platform_driver imx_ssi_driver = { 6378c2ecf20Sopenharmony_ci .probe = imx_ssi_probe, 6388c2ecf20Sopenharmony_ci .remove = imx_ssi_remove, 6398c2ecf20Sopenharmony_ci 6408c2ecf20Sopenharmony_ci .driver = { 6418c2ecf20Sopenharmony_ci .name = "imx-ssi", 6428c2ecf20Sopenharmony_ci }, 6438c2ecf20Sopenharmony_ci}; 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_cimodule_platform_driver(imx_ssi_driver); 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_ci/* Module information */ 6488c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>"); 6498c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface"); 6508c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 6518c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:imx-ssi"); 652