2013-12-17 11:24:38 +08:00
/*
* Freescale ALSA SoC Digital Audio Interface ( SAI ) driver .
*
* Copyright 2012 - 2013 Freescale Semiconductor , Inc .
*
* This program is free software , you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation , either version 2 of the License , or ( at your
* option ) any later version .
*
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/dmaengine.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/slab.h>
# include <sound/core.h>
# include <sound/dmaengine_pcm.h>
# include <sound/pcm_params.h>
# include "fsl_sai.h"
static inline u32 sai_readl ( struct fsl_sai * sai ,
const void __iomem * addr )
{
u32 val ;
val = __raw_readl ( addr ) ;
if ( likely ( sai - > big_endian_regs ) )
val = be32_to_cpu ( val ) ;
else
val = le32_to_cpu ( val ) ;
rmb ( ) ;
return val ;
}
static inline void sai_writel ( struct fsl_sai * sai ,
u32 val , void __iomem * addr )
{
wmb ( ) ;
if ( likely ( sai - > big_endian_regs ) )
val = cpu_to_be32 ( val ) ;
else
val = cpu_to_le32 ( val ) ;
__raw_writel ( val , addr ) ;
}
static int fsl_sai_set_dai_sysclk_tr ( struct snd_soc_dai * cpu_dai ,
int clk_id , unsigned int freq , int fsl_dir )
{
struct fsl_sai * sai = snd_soc_dai_get_drvdata ( cpu_dai ) ;
2013-12-20 16:41:05 +08:00
u32 val_cr2 , reg_cr2 ;
2013-12-17 11:24:38 +08:00
if ( fsl_dir = = FSL_FMT_TRANSMITTER )
reg_cr2 = FSL_SAI_TCR2 ;
else
reg_cr2 = FSL_SAI_RCR2 ;
val_cr2 = sai_readl ( sai , sai - > base + reg_cr2 ) ;
2014-01-08 16:13:05 +08:00
val_cr2 & = ~ FSL_SAI_CR2_MSEL_MASK ;
2013-12-17 11:24:38 +08:00
switch ( clk_id ) {
case FSL_SAI_CLK_BUS :
val_cr2 | = FSL_SAI_CR2_MSEL_BUS ;
break ;
case FSL_SAI_CLK_MAST1 :
val_cr2 | = FSL_SAI_CR2_MSEL_MCLK1 ;
break ;
case FSL_SAI_CLK_MAST2 :
val_cr2 | = FSL_SAI_CR2_MSEL_MCLK2 ;
break ;
case FSL_SAI_CLK_MAST3 :
val_cr2 | = FSL_SAI_CR2_MSEL_MCLK3 ;
break ;
default :
return - EINVAL ;
}
2014-01-08 16:13:05 +08:00
2013-12-17 11:24:38 +08:00
sai_writel ( sai , val_cr2 , sai - > base + reg_cr2 ) ;
return 0 ;
}
static int fsl_sai_set_dai_sysclk ( struct snd_soc_dai * cpu_dai ,
int clk_id , unsigned int freq , int dir )
{
struct fsl_sai * sai = snd_soc_dai_get_drvdata ( cpu_dai ) ;
2013-12-20 16:41:05 +08:00
int ret ;
2013-12-17 11:24:38 +08:00
if ( dir = = SND_SOC_CLOCK_IN )
return 0 ;
ret = clk_prepare_enable ( sai - > clk ) ;
if ( ret )
return ret ;
ret = fsl_sai_set_dai_sysclk_tr ( cpu_dai , clk_id , freq ,
FSL_FMT_TRANSMITTER ) ;
if ( ret ) {
2013-12-20 16:41:04 +08:00
dev_err ( cpu_dai - > dev , " Cannot set tx sysclk: %d \n " , ret ) ;
2013-12-20 16:41:00 +08:00
goto err_clk ;
2013-12-17 11:24:38 +08:00
}
ret = fsl_sai_set_dai_sysclk_tr ( cpu_dai , clk_id , freq ,
FSL_FMT_RECEIVER ) ;
if ( ret ) {
2013-12-20 16:41:04 +08:00
dev_err ( cpu_dai - > dev , " Cannot set rx sysclk: %d \n " , ret ) ;
2013-12-20 16:41:00 +08:00
goto err_clk ;
2013-12-17 11:24:38 +08:00
}
2013-12-20 16:41:00 +08:00
err_clk :
2013-12-17 11:24:38 +08:00
clk_disable_unprepare ( sai - > clk ) ;
2013-12-20 16:41:00 +08:00
return ret ;
2013-12-17 11:24:38 +08:00
}
static int fsl_sai_set_dai_fmt_tr ( struct snd_soc_dai * cpu_dai ,
unsigned int fmt , int fsl_dir )
{
struct fsl_sai * sai = snd_soc_dai_get_drvdata ( cpu_dai ) ;
2013-12-25 12:40:04 +08:00
u32 val_cr2 , val_cr4 , reg_cr2 , reg_cr4 ;
2013-12-17 11:24:38 +08:00
if ( fsl_dir = = FSL_FMT_TRANSMITTER ) {
reg_cr2 = FSL_SAI_TCR2 ;
reg_cr4 = FSL_SAI_TCR4 ;
} else {
reg_cr2 = FSL_SAI_RCR2 ;
reg_cr4 = FSL_SAI_RCR4 ;
}
val_cr2 = sai_readl ( sai , sai - > base + reg_cr2 ) ;
val_cr4 = sai_readl ( sai , sai - > base + reg_cr4 ) ;
if ( sai - > big_endian_data )
val_cr4 & = ~ FSL_SAI_CR4_MF ;
2013-12-31 15:33:22 +08:00
else
val_cr4 | = FSL_SAI_CR4_MF ;
2013-12-17 11:24:38 +08:00
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
val_cr4 | = FSL_SAI_CR4_FSE ;
break ;
default :
return - EINVAL ;
}
switch ( fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_IB_IF :
val_cr4 | = FSL_SAI_CR4_FSP ;
val_cr2 & = ~ FSL_SAI_CR2_BCP ;
break ;
case SND_SOC_DAIFMT_IB_NF :
val_cr4 & = ~ FSL_SAI_CR4_FSP ;
val_cr2 & = ~ FSL_SAI_CR2_BCP ;
break ;
case SND_SOC_DAIFMT_NB_IF :
val_cr4 | = FSL_SAI_CR4_FSP ;
val_cr2 | = FSL_SAI_CR2_BCP ;
break ;
case SND_SOC_DAIFMT_NB_NF :
val_cr4 & = ~ FSL_SAI_CR4_FSP ;
val_cr2 | = FSL_SAI_CR2_BCP ;
break ;
default :
return - EINVAL ;
}
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
case SND_SOC_DAIFMT_CBS_CFS :
val_cr2 | = FSL_SAI_CR2_BCD_MSTR ;
val_cr4 | = FSL_SAI_CR4_FSD_MSTR ;
break ;
case SND_SOC_DAIFMT_CBM_CFM :
val_cr2 & = ~ FSL_SAI_CR2_BCD_MSTR ;
val_cr4 & = ~ FSL_SAI_CR4_FSD_MSTR ;
break ;
default :
return - EINVAL ;
}
sai_writel ( sai , val_cr2 , sai - > base + reg_cr2 ) ;
sai_writel ( sai , val_cr4 , sai - > base + reg_cr4 ) ;
return 0 ;
}
static int fsl_sai_set_dai_fmt ( struct snd_soc_dai * cpu_dai , unsigned int fmt )
{
struct fsl_sai * sai = snd_soc_dai_get_drvdata ( cpu_dai ) ;
2013-12-20 16:41:05 +08:00
int ret ;
2013-12-17 11:24:38 +08:00
ret = clk_prepare_enable ( sai - > clk ) ;
if ( ret )
return ret ;
ret = fsl_sai_set_dai_fmt_tr ( cpu_dai , fmt , FSL_FMT_TRANSMITTER ) ;
if ( ret ) {
2013-12-20 16:41:04 +08:00
dev_err ( cpu_dai - > dev , " Cannot set tx format: %d \n " , ret ) ;
2013-12-20 16:41:00 +08:00
goto err_clk ;
2013-12-17 11:24:38 +08:00
}
ret = fsl_sai_set_dai_fmt_tr ( cpu_dai , fmt , FSL_FMT_RECEIVER ) ;
if ( ret ) {
2013-12-20 16:41:04 +08:00
dev_err ( cpu_dai - > dev , " Cannot set rx format: %d \n " , ret ) ;
2013-12-20 16:41:00 +08:00
goto err_clk ;
2013-12-17 11:24:38 +08:00
}
2013-12-20 16:41:00 +08:00
err_clk :
2013-12-17 11:24:38 +08:00
clk_disable_unprepare ( sai - > clk ) ;
2013-12-20 16:41:00 +08:00
return ret ;
2013-12-17 11:24:38 +08:00
}
static int fsl_sai_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * cpu_dai )
{
2013-12-20 16:41:05 +08:00
struct fsl_sai * sai = snd_soc_dai_get_drvdata ( cpu_dai ) ;
2013-12-20 16:41:01 +08:00
u32 val_cr4 , val_cr5 , val_mr , reg_cr4 , reg_cr5 , reg_mr ;
2013-12-17 11:24:38 +08:00
unsigned int channels = params_channels ( params ) ;
2013-12-20 16:41:01 +08:00
u32 word_width = snd_pcm_format_width ( params_format ( params ) ) ;
2013-12-17 11:24:38 +08:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
reg_cr4 = FSL_SAI_TCR4 ;
reg_cr5 = FSL_SAI_TCR5 ;
reg_mr = FSL_SAI_TMR ;
} else {
reg_cr4 = FSL_SAI_RCR4 ;
reg_cr5 = FSL_SAI_RCR5 ;
reg_mr = FSL_SAI_RMR ;
}
val_cr4 = sai_readl ( sai , sai - > base + reg_cr4 ) ;
val_cr4 & = ~ FSL_SAI_CR4_SYWD_MASK ;
val_cr4 & = ~ FSL_SAI_CR4_FRSZ_MASK ;
val_cr5 = sai_readl ( sai , sai - > base + reg_cr5 ) ;
val_cr5 & = ~ FSL_SAI_CR5_WNW_MASK ;
val_cr5 & = ~ FSL_SAI_CR5_W0W_MASK ;
val_cr5 & = ~ FSL_SAI_CR5_FBT_MASK ;
val_cr4 | = FSL_SAI_CR4_SYWD ( word_width ) ;
val_cr5 | = FSL_SAI_CR5_WNW ( word_width ) ;
val_cr5 | = FSL_SAI_CR5_W0W ( word_width ) ;
2013-12-31 15:33:21 +08:00
val_cr5 & = ~ FSL_SAI_CR5_FBT_MASK ;
2013-12-17 11:24:38 +08:00
if ( sai - > big_endian_data )
val_cr5 | = FSL_SAI_CR5_FBT ( 0 ) ;
2013-12-31 15:33:22 +08:00
else
val_cr5 | = FSL_SAI_CR5_FBT ( word_width - 1 ) ;
2013-12-17 11:24:38 +08:00
val_cr4 | = FSL_SAI_CR4_FRSZ ( channels ) ;
2013-12-20 16:41:02 +08:00
val_mr = ~ 0UL - ( ( 1 < < channels ) - 1 ) ;
2013-12-17 11:24:38 +08:00
sai_writel ( sai , val_cr4 , sai - > base + reg_cr4 ) ;
sai_writel ( sai , val_cr5 , sai - > base + reg_cr5 ) ;
sai_writel ( sai , val_mr , sai - > base + reg_mr ) ;
return 0 ;
}
static int fsl_sai_trigger ( struct snd_pcm_substream * substream , int cmd ,
struct snd_soc_dai * cpu_dai )
{
struct fsl_sai * sai = snd_soc_dai_get_drvdata ( cpu_dai ) ;
2013-12-31 15:33:21 +08:00
u32 tcsr , rcsr , val_cr2 , val_cr3 , reg_cr3 ;
val_cr2 = sai_readl ( sai , sai - > base + FSL_SAI_TCR2 ) ;
val_cr2 & = ~ FSL_SAI_CR2_SYNC ;
sai_writel ( sai , val_cr2 , sai - > base + FSL_SAI_TCR2 ) ;
val_cr2 = sai_readl ( sai , sai - > base + FSL_SAI_RCR2 ) ;
val_cr2 | = FSL_SAI_CR2_SYNC ;
sai_writel ( sai , val_cr2 , sai - > base + FSL_SAI_RCR2 ) ;
2013-12-17 11:24:38 +08:00
tcsr = sai_readl ( sai , sai - > base + FSL_SAI_TCSR ) ;
rcsr = sai_readl ( sai , sai - > base + FSL_SAI_RCSR ) ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
tcsr | = FSL_SAI_CSR_FRDE ;
rcsr & = ~ FSL_SAI_CSR_FRDE ;
2013-12-25 12:40:04 +08:00
reg_cr3 = FSL_SAI_TCR3 ;
2013-12-17 11:24:38 +08:00
} else {
rcsr | = FSL_SAI_CSR_FRDE ;
tcsr & = ~ FSL_SAI_CSR_FRDE ;
2013-12-25 12:40:04 +08:00
reg_cr3 = FSL_SAI_RCR3 ;
2013-12-17 11:24:38 +08:00
}
2013-12-25 12:40:04 +08:00
val_cr3 = sai_readl ( sai , sai - > base + reg_cr3 ) ;
2013-12-17 11:24:38 +08:00
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
tcsr | = FSL_SAI_CSR_TERE ;
rcsr | = FSL_SAI_CSR_TERE ;
2013-12-25 12:40:04 +08:00
val_cr3 | = FSL_SAI_CR3_TRCE ;
sai_writel ( sai , val_cr3 , sai - > base + reg_cr3 ) ;
2013-12-17 11:24:38 +08:00
sai_writel ( sai , rcsr , sai - > base + FSL_SAI_RCSR ) ;
sai_writel ( sai , tcsr , sai - > base + FSL_SAI_TCSR ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
if ( ! ( cpu_dai - > playback_active | | cpu_dai - > capture_active ) ) {
tcsr & = ~ FSL_SAI_CSR_TERE ;
rcsr & = ~ FSL_SAI_CSR_TERE ;
}
2013-12-25 12:40:04 +08:00
val_cr3 & = ~ FSL_SAI_CR3_TRCE ;
2013-12-17 11:24:38 +08:00
sai_writel ( sai , tcsr , sai - > base + FSL_SAI_TCSR ) ;
sai_writel ( sai , rcsr , sai - > base + FSL_SAI_RCSR ) ;
2013-12-25 12:40:04 +08:00
sai_writel ( sai , val_cr3 , sai - > base + reg_cr3 ) ;
2013-12-17 11:24:38 +08:00
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static int fsl_sai_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * cpu_dai )
{
struct fsl_sai * sai = snd_soc_dai_get_drvdata ( cpu_dai ) ;
2013-12-20 16:41:03 +08:00
return clk_prepare_enable ( sai - > clk ) ;
2013-12-17 11:24:38 +08:00
}
static void fsl_sai_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * cpu_dai )
{
struct fsl_sai * sai = snd_soc_dai_get_drvdata ( cpu_dai ) ;
clk_disable_unprepare ( sai - > clk ) ;
}
static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
. set_sysclk = fsl_sai_set_dai_sysclk ,
. set_fmt = fsl_sai_set_dai_fmt ,
. hw_params = fsl_sai_hw_params ,
. trigger = fsl_sai_trigger ,
. startup = fsl_sai_startup ,
. shutdown = fsl_sai_shutdown ,
} ;
static int fsl_sai_dai_probe ( struct snd_soc_dai * cpu_dai )
{
struct fsl_sai * sai = dev_get_drvdata ( cpu_dai - > dev ) ;
2013-12-25 11:20:14 +08:00
int ret ;
ret = clk_prepare_enable ( sai - > clk ) ;
if ( ret )
return ret ;
sai_writel ( sai , 0x0 , sai - > base + FSL_SAI_RCSR ) ;
sai_writel ( sai , 0x0 , sai - > base + FSL_SAI_TCSR ) ;
sai_writel ( sai , FSL_SAI_MAXBURST_TX * 2 , sai - > base + FSL_SAI_TCR1 ) ;
sai_writel ( sai , FSL_SAI_MAXBURST_RX - 1 , sai - > base + FSL_SAI_RCR1 ) ;
clk_disable_unprepare ( sai - > clk ) ;
2013-12-17 11:24:38 +08:00
2013-12-20 12:35:33 +08:00
snd_soc_dai_init_dma_data ( cpu_dai , & sai - > dma_params_tx ,
& sai - > dma_params_rx ) ;
2013-12-17 11:24:38 +08:00
snd_soc_dai_set_drvdata ( cpu_dai , sai ) ;
return 0 ;
}
static struct snd_soc_dai_driver fsl_sai_dai = {
. probe = fsl_sai_dai_probe ,
. playback = {
. channels_min = 1 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_96000 ,
. formats = FSL_SAI_FORMATS ,
} ,
. capture = {
. channels_min = 1 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_96000 ,
. formats = FSL_SAI_FORMATS ,
} ,
. ops = & fsl_sai_pcm_dai_ops ,
} ;
static const struct snd_soc_component_driver fsl_component = {
. name = " fsl-sai " ,
} ;
static int fsl_sai_probe ( struct platform_device * pdev )
{
2013-12-20 16:41:05 +08:00
struct device_node * np = pdev - > dev . of_node ;
2013-12-17 11:24:38 +08:00
struct fsl_sai * sai ;
struct resource * res ;
2013-12-20 16:41:05 +08:00
int ret ;
2013-12-17 11:24:38 +08:00
sai = devm_kzalloc ( & pdev - > dev , sizeof ( * sai ) , GFP_KERNEL ) ;
if ( ! sai )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
sai - > base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( sai - > base ) )
return PTR_ERR ( sai - > base ) ;
sai - > clk = devm_clk_get ( & pdev - > dev , " sai " ) ;
if ( IS_ERR ( sai - > clk ) ) {
dev_err ( & pdev - > dev , " Cannot get SAI's clock \n " ) ;
return PTR_ERR ( sai - > clk ) ;
}
sai - > dma_params_rx . addr = res - > start + FSL_SAI_RDR ;
sai - > dma_params_tx . addr = res - > start + FSL_SAI_TDR ;
sai - > dma_params_rx . maxburst = FSL_SAI_MAXBURST_RX ;
sai - > dma_params_tx . maxburst = FSL_SAI_MAXBURST_TX ;
sai - > big_endian_regs = of_property_read_bool ( np , " big-endian-regs " ) ;
sai - > big_endian_data = of_property_read_bool ( np , " big-endian-data " ) ;
platform_set_drvdata ( pdev , sai ) ;
ret = devm_snd_soc_register_component ( & pdev - > dev , & fsl_component ,
& fsl_sai_dai , 1 ) ;
if ( ret )
return ret ;
2013-12-20 12:30:26 +08:00
return devm_snd_dmaengine_pcm_register ( & pdev - > dev , NULL ,
2013-12-17 11:24:38 +08:00
SND_DMAENGINE_PCM_FLAG_NO_RESIDUE ) ;
}
static const struct of_device_id fsl_sai_ids [ ] = {
{ . compatible = " fsl,vf610-sai " , } ,
{ /* sentinel */ }
} ;
static struct platform_driver fsl_sai_driver = {
. probe = fsl_sai_probe ,
. driver = {
. name = " fsl-sai " ,
. owner = THIS_MODULE ,
. of_match_table = fsl_sai_ids ,
} ,
} ;
module_platform_driver ( fsl_sai_driver ) ;
MODULE_DESCRIPTION ( " Freescale Soc SAI Interface " ) ;
MODULE_AUTHOR ( " Xiubo Li, <Li.Xiubo@freescale.com> " ) ;
MODULE_ALIAS ( " platform:fsl-sai " ) ;
MODULE_LICENSE ( " GPL " ) ;