2020-06-09 14:06:21 +08:00
// 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>
2020-08-11 12:18:35 +08:00
# include <linux/of.h>
# include <linux/of_device.h>
2020-06-09 14:06:21 +08:00
# 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)
2020-08-11 12:18:35 +08:00
# define TDM_OPERATION 5
2020-06-09 14:06:21 +08:00
# 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 ,
2020-07-30 13:53:16 +08:00
. rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000 ,
. rate_min = 8000 ,
2020-06-09 14:06:21 +08:00
. 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 ;
2020-08-11 12:18:35 +08:00
int chan = kmb_i2s - > config . chan_nr ;
2020-06-09 14:06:21 +08:00
void * buf = runtime - > dma_area ;
2020-08-11 12:18:35 +08:00
int i , j ;
2020-06-09 14:06:21 +08:00
/* KMB i2s uses two separate L/R FIFO */
for ( i = 0 ; i < kmb_i2s - > fifo_th ; i + + ) {
2020-08-11 12:18:35 +08:00
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 ) ) ;
}
2020-06-09 14:06:21 +08:00
}
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 ;
2020-07-30 13:53:17 +08:00
/* Disable all channels regardless of configuration*/
2020-06-09 14:06:21 +08:00
if ( stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
2020-07-30 13:53:17 +08:00
for ( i = 0 ; i < MAX_ISR ; i + + )
2020-06-09 14:06:21 +08:00
writel ( 0 , kmb_i2s - > i2s_base + TER ( i ) ) ;
} else {
2020-07-30 13:53:17 +08:00
for ( i = 0 ; i < MAX_ISR ; i + + )
2020-06-09 14:06:21 +08:00
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 ;
2020-07-27 10:08:42 +09:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2020-06-09 14:06:21 +08:00
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 ;
2020-08-11 12:18:35 +08:00
u32 tx_enabled = 0 ;
2020-06-09 14:06:21 +08:00
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 ) ;
2020-08-11 12:18:35 +08:00
/* 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 ;
}
2020-06-09 14:06:21 +08:00
for ( i = 0 ; i < config - > chan_nr / 2 ; i + + ) {
/*
* Check if TX fifo is empty . If empty fill FIFO with samples
*/
2020-08-11 12:18:35 +08:00
if ( ( isr [ i ] & ISR_TXFE ) & & tx_enabled ) {
2020-06-09 14:06:21 +08:00
kmb_pcm_operation ( kmb_i2s , true ) ;
ret = IRQ_HANDLED ;
}
2020-08-11 12:18:35 +08:00
2020-06-09 14:06:21 +08:00
/* 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 ;
2020-08-11 12:18:35 +08:00
u32 write_val ;
2020-06-09 14:06:21 +08:00
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 ) {
2020-08-11 12:18:35 +08:00
case 8 :
case 4 :
/*
* Platform is not capable of providing clocks for
* multi channel audio
*/
if ( kmb_i2s - > master )
return - EINVAL ;
2020-06-09 14:06:21 +08:00
write_val = ( ( config - > chan_nr / 2 ) < < TDM_CHANNEL_CONFIG_BIT ) |
( config - > data_width < < DATA_WIDTH_CONFIG_BIT ) |
2020-09-04 10:09:04 +08:00
TDM_OPERATION ;
2020-06-09 14:06:21 +08:00
writel ( write_val , kmb_i2s - > pss_base + I2S_GEN_CFG_0 ) ;
2020-08-11 12:18:35 +08:00
break ;
case 2 :
/*
* Platform is only capable of providing clocks need for
* 2 channel master mode
*/
if ( ! ( kmb_i2s - > master ) )
return - EINVAL ;
write_val = ( ( config - > chan_nr / 2 ) < < TDM_CHANNEL_CONFIG_BIT ) |
( config - > data_width < < DATA_WIDTH_CONFIG_BIT ) |
MASTER_MODE | I2S_OPERATION ;
2020-06-09 14:06:21 +08:00
2020-08-11 12:18:35 +08:00
writel ( write_val , kmb_i2s - > pss_base + I2S_GEN_CFG_0 ) ;
2020-06-09 14:06:21 +08:00
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 ,
} ;
2020-08-11 12:18:35 +08:00
static struct snd_soc_dai_driver intel_kmb_i2s_dai [ ] = {
2020-06-09 14:06:21 +08:00
{
2020-08-11 12:18:35 +08:00
. name = " intel_kmb_i2s " ,
2020-06-09 14:06:21 +08:00
. playback = {
. channels_min = 2 ,
. channels_max = 2 ,
2020-07-30 13:53:16 +08:00
. rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000 ,
. rate_min = 8000 ,
2020-06-09 14:06:21 +08:00
. 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 ,
2020-07-30 13:53:16 +08:00
. rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_48000 ,
. rate_min = 8000 ,
2020-06-09 14:06:21 +08:00
. rate_max = 48000 ,
. formats = ( SNDRV_PCM_FMTBIT_S32_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S16_LE ) ,
} ,
. ops = & kmb_dai_ops ,
} ,
} ;
2020-08-11 12:18:35 +08:00
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-tdm " , . data = & intel_kmb_tdm_dai } ,
{ }
} ;
2020-06-09 14:06:21 +08:00
static int kmb_plat_dai_probe ( struct platform_device * pdev )
{
struct snd_soc_dai_driver * kmb_i2s_dai ;
2020-08-11 12:18:35 +08:00
const struct of_device_id * match ;
2020-06-09 14:06:21 +08:00
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 ;
2020-08-11 12:18:35 +08:00
match = of_match_device ( kmb_plat_of_match , & pdev - > dev ) ;
if ( ! match ) {
dev_err ( & pdev - > dev , " Error: No device match found \n " ) ;
return - ENODEV ;
}
kmb_i2s_dai = ( struct snd_soc_dai_driver * ) match - > data ;
2020-06-09 14:06:21 +08:00
/* 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 ,
2020-08-11 12:18:35 +08:00
kmb_i2s_dai , 1 ) ;
2020-06-09 14:06:21 +08:00
if ( ret ) {
dev_err ( dev , " not able to register dai \n " ) ;
return ret ;
}
2020-07-30 13:53:17 +08:00
/* 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 ) ;
2020-06-09 14:06:21 +08:00
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 " ) ;