2019-05-19 15:08:20 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2011-07-25 15:44:45 +04:00
/*
* Au1000 / Au1500 / Au1100 I2S controller driver for ASoC
*
* ( c ) 2011 Manuel Lauss < manuel . lauss @ googlemail . com >
*
* Note : clock supplied to the I2S controller must be 256 x samplerate .
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/suspend.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/initval.h>
# include <sound/soc.h>
# include <asm/mach-au1x00/au1000.h>
# include "psc.h"
# define I2S_RXTX 0x00
# define I2S_CFG 0x04
# define I2S_ENABLE 0x08
# define CFG_XU (1 << 25) /* tx underflow */
# define CFG_XO (1 << 24)
# define CFG_RU (1 << 23)
# define CFG_RO (1 << 22)
# define CFG_TR (1 << 21)
# define CFG_TE (1 << 20)
# define CFG_TF (1 << 19)
# define CFG_RR (1 << 18)
# define CFG_RF (1 << 17)
# define CFG_ICK (1 << 12) /* clock invert */
# define CFG_PD (1 << 11) /* set to make I2SDIO INPUT */
# define CFG_LB (1 << 10) /* loopback */
# define CFG_IC (1 << 9) /* word select invert */
# define CFG_FM_I2S (0 << 7) /* I2S format */
# define CFG_FM_LJ (1 << 7) /* left-justified */
# define CFG_FM_RJ (2 << 7) /* right-justified */
# define CFG_FM_MASK (3 << 7)
# define CFG_TN (1 << 6) /* tx fifo en */
# define CFG_RN (1 << 5) /* rx fifo en */
# define CFG_SZ_8 (0x08)
# define CFG_SZ_16 (0x10)
# define CFG_SZ_18 (0x12)
# define CFG_SZ_20 (0x14)
# define CFG_SZ_24 (0x18)
# define CFG_SZ_MASK (0x1f)
# define EN_D (1 << 1) /* DISable */
# define EN_CE (1 << 0) /* clock enable */
/* only limited by clock generator and board design */
# define AU1XI2SC_RATES \
SNDRV_PCM_RATE_CONTINUOUS
# define AU1XI2SC_FMTS \
( SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \
SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \
SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE | \
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \
SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE | \
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \
SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE | \
0 )
static inline unsigned long RD ( struct au1xpsc_audio_data * ctx , int reg )
{
return __raw_readl ( ctx - > mmio + reg ) ;
}
static inline void WR ( struct au1xpsc_audio_data * ctx , int reg , unsigned long v )
{
__raw_writel ( v , ctx - > mmio + reg ) ;
wmb ( ) ;
}
static int au1xi2s_set_fmt ( struct snd_soc_dai * cpu_dai , unsigned int fmt )
{
struct au1xpsc_audio_data * ctx = snd_soc_dai_get_drvdata ( cpu_dai ) ;
unsigned long c ;
int ret ;
ret = - EINVAL ;
c = ctx - > cfg ;
c & = ~ CFG_FM_MASK ;
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
c | = CFG_FM_I2S ;
break ;
case SND_SOC_DAIFMT_MSB :
c | = CFG_FM_RJ ;
break ;
case SND_SOC_DAIFMT_LSB :
c | = CFG_FM_LJ ;
break ;
default :
goto out ;
}
c & = ~ ( CFG_IC | CFG_ICK ) ; /* IB-IF */
switch ( fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_NB_NF :
c | = CFG_IC | CFG_ICK ;
break ;
case SND_SOC_DAIFMT_NB_IF :
c | = CFG_IC ;
break ;
case SND_SOC_DAIFMT_IB_NF :
c | = CFG_ICK ;
break ;
case SND_SOC_DAIFMT_IB_IF :
break ;
default :
goto out ;
}
2021-09-15 20:59:15 +03:00
/* I2S controller only supports provider */
switch ( fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK ) {
2022-05-19 18:42:26 +03:00
case SND_SOC_DAIFMT_BP_FP : /* CODEC consumer */
2011-07-25 15:44:45 +04:00
break ;
default :
goto out ;
}
ret = 0 ;
ctx - > cfg = c ;
out :
return ret ;
}
static int au1xi2s_trigger ( struct snd_pcm_substream * substream ,
int cmd , struct snd_soc_dai * dai )
{
struct au1xpsc_audio_data * ctx = snd_soc_dai_get_drvdata ( dai ) ;
int stype = SUBSTREAM_TYPE ( substream ) ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
/* power up */
WR ( ctx , I2S_ENABLE , EN_D | EN_CE ) ;
WR ( ctx , I2S_ENABLE , EN_CE ) ;
ctx - > cfg | = ( stype = = PCM_TX ) ? CFG_TN : CFG_RN ;
WR ( ctx , I2S_CFG , ctx - > cfg ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
ctx - > cfg & = ~ ( ( stype = = PCM_TX ) ? CFG_TN : CFG_RN ) ;
WR ( ctx , I2S_CFG , ctx - > cfg ) ;
WR ( ctx , I2S_ENABLE , EN_D ) ; /* power off */
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static unsigned long msbits_to_reg ( int msbits )
{
switch ( msbits ) {
case 8 :
return CFG_SZ_8 ;
case 16 :
return CFG_SZ_16 ;
case 18 :
return CFG_SZ_18 ;
case 20 :
return CFG_SZ_20 ;
case 24 :
return CFG_SZ_24 ;
}
return 0 ;
}
static int au1xi2s_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
struct au1xpsc_audio_data * ctx = snd_soc_dai_get_drvdata ( dai ) ;
unsigned long v ;
v = msbits_to_reg ( params - > msbits ) ;
if ( ! v )
return - EINVAL ;
ctx - > cfg & = ~ CFG_SZ_MASK ;
ctx - > cfg | = v ;
return 0 ;
}
static int au1xi2s_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct au1xpsc_audio_data * ctx = snd_soc_dai_get_drvdata ( dai ) ;
snd_soc_dai_set_dma_data ( dai , substream , & ctx - > dmaids [ 0 ] ) ;
return 0 ;
}
2011-11-23 17:11:21 +04:00
static const struct snd_soc_dai_ops au1xi2s_dai_ops = {
2011-07-25 15:44:45 +04:00
. startup = au1xi2s_startup ,
. trigger = au1xi2s_trigger ,
. hw_params = au1xi2s_hw_params ,
2022-05-19 18:42:53 +03:00
. set_fmt = au1xi2s_set_fmt ,
2011-07-25 15:44:45 +04:00
} ;
static struct snd_soc_dai_driver au1xi2s_dai_driver = {
2021-01-15 07:53:18 +03:00
. symmetric_rate = 1 ,
2011-07-25 15:44:45 +04:00
. playback = {
. rates = AU1XI2SC_RATES ,
. formats = AU1XI2SC_FMTS ,
. channels_min = 2 ,
. channels_max = 2 ,
} ,
. capture = {
. rates = AU1XI2SC_RATES ,
. formats = AU1XI2SC_FMTS ,
. channels_min = 2 ,
. channels_max = 2 ,
} ,
. ops = & au1xi2s_dai_ops ,
} ;
2013-03-21 14:28:37 +04:00
static const struct snd_soc_component_driver au1xi2s_component = {
2022-06-23 15:51:41 +03:00
. name = " au1xi2s " ,
. legacy_dai_naming = 1 ,
2013-03-21 14:28:37 +04:00
} ;
2012-12-07 18:26:22 +04:00
static int au1xi2s_drvprobe ( struct platform_device * pdev )
2011-07-25 15:44:45 +04:00
{
2011-10-18 19:06:39 +04:00
struct resource * iores , * dmares ;
2011-07-25 15:44:45 +04:00
struct au1xpsc_audio_data * ctx ;
2011-12-29 20:51:26 +04:00
ctx = devm_kzalloc ( & pdev - > dev , sizeof ( * ctx ) , GFP_KERNEL ) ;
2011-07-25 15:44:45 +04:00
if ( ! ctx )
return - ENOMEM ;
2011-10-18 19:06:39 +04:00
iores = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2011-12-29 20:51:26 +04:00
if ( ! iores )
return - ENODEV ;
2011-07-25 15:44:45 +04:00
2011-12-29 20:51:26 +04:00
if ( ! devm_request_mem_region ( & pdev - > dev , iores - > start ,
resource_size ( iores ) ,
pdev - > name ) )
return - EBUSY ;
2011-07-25 15:44:45 +04:00
2020-01-06 11:43:50 +03:00
ctx - > mmio = devm_ioremap ( & pdev - > dev , iores - > start ,
2011-12-29 20:51:26 +04:00
resource_size ( iores ) ) ;
2011-07-25 15:44:45 +04:00
if ( ! ctx - > mmio )
2011-12-29 20:51:26 +04:00
return - EBUSY ;
2011-07-25 15:44:45 +04:00
2011-10-18 19:06:39 +04:00
dmares = platform_get_resource ( pdev , IORESOURCE_DMA , 0 ) ;
if ( ! dmares )
2011-12-29 20:51:26 +04:00
return - EBUSY ;
2011-10-18 19:06:39 +04:00
ctx - > dmaids [ SNDRV_PCM_STREAM_PLAYBACK ] = dmares - > start ;
2011-07-25 15:44:45 +04:00
2011-10-18 19:06:39 +04:00
dmares = platform_get_resource ( pdev , IORESOURCE_DMA , 1 ) ;
if ( ! dmares )
2011-12-29 20:51:26 +04:00
return - EBUSY ;
2011-10-18 19:06:39 +04:00
ctx - > dmaids [ SNDRV_PCM_STREAM_CAPTURE ] = dmares - > start ;
2011-07-25 15:44:45 +04:00
platform_set_drvdata ( pdev , ctx ) ;
2013-03-21 14:28:37 +04:00
return snd_soc_register_component ( & pdev - > dev , & au1xi2s_component ,
& au1xi2s_dai_driver , 1 ) ;
2011-07-25 15:44:45 +04:00
}
2012-12-07 18:26:22 +04:00
static int au1xi2s_drvremove ( struct platform_device * pdev )
2011-07-25 15:44:45 +04:00
{
struct au1xpsc_audio_data * ctx = platform_get_drvdata ( pdev ) ;
2013-03-21 14:28:37 +04:00
snd_soc_unregister_component ( & pdev - > dev ) ;
2011-07-25 15:44:45 +04:00
WR ( ctx , I2S_ENABLE , EN_D ) ; /* clock off, disable */
return 0 ;
}
# ifdef CONFIG_PM
static int au1xi2s_drvsuspend ( struct device * dev )
{
struct au1xpsc_audio_data * ctx = dev_get_drvdata ( dev ) ;
WR ( ctx , I2S_ENABLE , EN_D ) ; /* clock off, disable */
return 0 ;
}
static int au1xi2s_drvresume ( struct device * dev )
{
return 0 ;
}
static const struct dev_pm_ops au1xi2sc_pmops = {
. suspend = au1xi2s_drvsuspend ,
. resume = au1xi2s_drvresume ,
} ;
# define AU1XI2SC_PMOPS (&au1xi2sc_pmops)
# else
# define AU1XI2SC_PMOPS NULL
# endif
static struct platform_driver au1xi2s_driver = {
. driver = {
. name = " alchemy-i2sc " ,
. pm = AU1XI2SC_PMOPS ,
} ,
. probe = au1xi2s_drvprobe ,
2012-12-07 18:26:22 +04:00
. remove = au1xi2s_drvremove ,
2011-07-25 15:44:45 +04:00
} ;
2011-11-25 06:06:59 +04:00
module_platform_driver ( au1xi2s_driver ) ;
2011-07-25 15:44:45 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Au1000/1500/1100 I2S ASoC driver " ) ;
MODULE_AUTHOR ( " Manuel Lauss " ) ;