2014-07-05 19:13:03 +08:00
/* sound/soc/rockchip/rockchip_i2s.c
*
* ALSA SoC Audio Layer - Rockchip I2S Controller driver
*
* Copyright ( c ) 2014 Rockchip Electronics Co . Ltd .
* Author : Jianqun < jay . xu @ rock - chips . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
2014-07-11 19:40:05 +08:00
# include <linux/module.h>
2014-07-05 19:13:03 +08:00
# include <linux/delay.h>
# include <linux/of_gpio.h>
# include <linux/clk.h>
# include <linux/pm_runtime.h>
# include <linux/regmap.h>
# include <sound/pcm_params.h>
# include <sound/dmaengine_pcm.h>
# include "rockchip_i2s.h"
# define DRV_NAME "rockchip-i2s"
struct rk_i2s_dev {
struct device * dev ;
struct clk * hclk ;
struct clk * mclk ;
struct snd_dmaengine_dai_dma_data capture_dma_data ;
struct snd_dmaengine_dai_dma_data playback_dma_data ;
struct regmap * regmap ;
/*
* Used to indicate the tx / rx status .
* I2S controller hopes to start the tx and rx together ,
* also to stop them when they are both try to stop .
*/
bool tx_start ;
bool rx_start ;
} ;
static int i2s_runtime_suspend ( struct device * dev )
{
struct rk_i2s_dev * i2s = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( i2s - > mclk ) ;
return 0 ;
}
static int i2s_runtime_resume ( struct device * dev )
{
struct rk_i2s_dev * i2s = dev_get_drvdata ( dev ) ;
int ret ;
ret = clk_prepare_enable ( i2s - > mclk ) ;
if ( ret ) {
dev_err ( i2s - > dev , " clock enable failed %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
static inline struct rk_i2s_dev * to_info ( struct snd_soc_dai * dai )
{
return snd_soc_dai_get_drvdata ( dai ) ;
}
static void rockchip_snd_txctrl ( struct rk_i2s_dev * i2s , int on )
{
unsigned int val = 0 ;
int retry = 10 ;
if ( on ) {
regmap_update_bits ( i2s - > regmap , I2S_DMACR ,
I2S_DMACR_TDE_ENABLE , I2S_DMACR_TDE_ENABLE ) ;
regmap_update_bits ( i2s - > regmap , I2S_XFER ,
I2S_XFER_TXS_START | I2S_XFER_RXS_START ,
I2S_XFER_TXS_START | I2S_XFER_RXS_START ) ;
i2s - > tx_start = true ;
} else {
i2s - > tx_start = false ;
regmap_update_bits ( i2s - > regmap , I2S_DMACR ,
2014-07-12 09:02:13 +08:00
I2S_DMACR_TDE_ENABLE , I2S_DMACR_TDE_DISABLE ) ;
2014-07-05 19:13:03 +08:00
if ( ! i2s - > rx_start ) {
regmap_update_bits ( i2s - > regmap , I2S_XFER ,
I2S_XFER_TXS_START |
I2S_XFER_RXS_START ,
I2S_XFER_TXS_STOP |
I2S_XFER_RXS_STOP ) ;
regmap_update_bits ( i2s - > regmap , I2S_CLR ,
2014-07-12 09:02:13 +08:00
I2S_CLR_TXC | I2S_CLR_RXC ,
I2S_CLR_TXC | I2S_CLR_RXC ) ;
2014-07-05 19:13:03 +08:00
regmap_read ( i2s - > regmap , I2S_CLR , & val ) ;
/* Should wait for clear operation to finish */
while ( val ) {
regmap_read ( i2s - > regmap , I2S_CLR , & val ) ;
retry - - ;
2014-10-08 00:58:51 -07:00
if ( ! retry ) {
2014-07-05 19:13:03 +08:00
dev_warn ( i2s - > dev , " fail to clear \n " ) ;
2014-10-08 00:58:51 -07:00
break ;
}
2014-07-05 19:13:03 +08:00
}
}
}
}
static void rockchip_snd_rxctrl ( struct rk_i2s_dev * i2s , int on )
{
unsigned int val = 0 ;
int retry = 10 ;
if ( on ) {
regmap_update_bits ( i2s - > regmap , I2S_DMACR ,
I2S_DMACR_RDE_ENABLE , I2S_DMACR_RDE_ENABLE ) ;
regmap_update_bits ( i2s - > regmap , I2S_XFER ,
I2S_XFER_TXS_START | I2S_XFER_RXS_START ,
I2S_XFER_TXS_START | I2S_XFER_RXS_START ) ;
i2s - > rx_start = true ;
} else {
i2s - > rx_start = false ;
regmap_update_bits ( i2s - > regmap , I2S_DMACR ,
I2S_DMACR_RDE_ENABLE , I2S_DMACR_RDE_DISABLE ) ;
if ( ! i2s - > tx_start ) {
regmap_update_bits ( i2s - > regmap , I2S_XFER ,
I2S_XFER_TXS_START |
I2S_XFER_RXS_START ,
I2S_XFER_TXS_STOP |
I2S_XFER_RXS_STOP ) ;
regmap_update_bits ( i2s - > regmap , I2S_CLR ,
2014-07-12 09:02:13 +08:00
I2S_CLR_TXC | I2S_CLR_RXC ,
I2S_CLR_TXC | I2S_CLR_RXC ) ;
2014-07-05 19:13:03 +08:00
regmap_read ( i2s - > regmap , I2S_CLR , & val ) ;
/* Should wait for clear operation to finish */
while ( val ) {
regmap_read ( i2s - > regmap , I2S_CLR , & val ) ;
retry - - ;
2014-10-29 17:45:51 +08:00
if ( ! retry ) {
2014-07-05 19:13:03 +08:00
dev_warn ( i2s - > dev , " fail to clear \n " ) ;
2014-10-29 17:45:51 +08:00
break ;
}
2014-07-05 19:13:03 +08:00
}
}
}
}
static int rockchip_i2s_set_fmt ( struct snd_soc_dai * cpu_dai ,
unsigned int fmt )
{
struct rk_i2s_dev * i2s = to_info ( cpu_dai ) ;
unsigned int mask = 0 , val = 0 ;
2014-09-13 08:41:03 +08:00
mask = I2S_CKR_MSS_MASK ;
2014-07-05 19:13:03 +08:00
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
case SND_SOC_DAIFMT_CBS_CFS :
2014-09-13 08:41:03 +08:00
/* Set source clock in Master mode */
val = I2S_CKR_MSS_MASTER ;
2014-07-05 19:13:03 +08:00
break ;
case SND_SOC_DAIFMT_CBM_CFM :
2014-09-13 08:41:03 +08:00
val = I2S_CKR_MSS_SLAVE ;
2014-07-05 19:13:03 +08:00
break ;
default :
return - EINVAL ;
}
regmap_update_bits ( i2s - > regmap , I2S_CKR , mask , val ) ;
mask = I2S_TXCR_IBM_MASK ;
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_RIGHT_J :
val = I2S_TXCR_IBM_RSJM ;
break ;
case SND_SOC_DAIFMT_LEFT_J :
val = I2S_TXCR_IBM_LSJM ;
break ;
case SND_SOC_DAIFMT_I2S :
val = I2S_TXCR_IBM_NORMAL ;
break ;
default :
return - EINVAL ;
}
regmap_update_bits ( i2s - > regmap , I2S_TXCR , mask , val ) ;
mask = I2S_RXCR_IBM_MASK ;
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_RIGHT_J :
val = I2S_RXCR_IBM_RSJM ;
break ;
case SND_SOC_DAIFMT_LEFT_J :
val = I2S_RXCR_IBM_LSJM ;
break ;
case SND_SOC_DAIFMT_I2S :
val = I2S_RXCR_IBM_NORMAL ;
break ;
default :
return - EINVAL ;
}
regmap_update_bits ( i2s - > regmap , I2S_RXCR , mask , val ) ;
return 0 ;
}
static int rockchip_i2s_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
struct rk_i2s_dev * i2s = to_info ( dai ) ;
unsigned int val = 0 ;
switch ( params_format ( params ) ) {
case SNDRV_PCM_FORMAT_S8 :
val | = I2S_TXCR_VDW ( 8 ) ;
break ;
case SNDRV_PCM_FORMAT_S16_LE :
val | = I2S_TXCR_VDW ( 16 ) ;
break ;
case SNDRV_PCM_FORMAT_S20_3LE :
val | = I2S_TXCR_VDW ( 20 ) ;
break ;
case SNDRV_PCM_FORMAT_S24_LE :
val | = I2S_TXCR_VDW ( 24 ) ;
break ;
default :
return - EINVAL ;
}
regmap_update_bits ( i2s - > regmap , I2S_TXCR , I2S_TXCR_VDW_MASK , val ) ;
regmap_update_bits ( i2s - > regmap , I2S_RXCR , I2S_RXCR_VDW_MASK , val ) ;
return 0 ;
}
static int rockchip_i2s_trigger ( struct snd_pcm_substream * substream ,
int cmd , struct snd_soc_dai * dai )
{
struct rk_i2s_dev * i2s = to_info ( dai ) ;
int ret = 0 ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
if ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE )
rockchip_snd_rxctrl ( i2s , 1 ) ;
else
rockchip_snd_txctrl ( i2s , 1 ) ;
break ;
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
if ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE )
rockchip_snd_rxctrl ( i2s , 0 ) ;
else
rockchip_snd_txctrl ( i2s , 0 ) ;
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
static int rockchip_i2s_set_sysclk ( struct snd_soc_dai * cpu_dai , int clk_id ,
unsigned int freq , int dir )
{
struct rk_i2s_dev * i2s = to_info ( cpu_dai ) ;
int ret ;
ret = clk_set_rate ( i2s - > mclk , freq ) ;
if ( ret )
dev_err ( i2s - > dev , " Fail to set mclk %d \n " , ret ) ;
return ret ;
}
2014-09-13 08:41:38 +08:00
static int rockchip_i2s_dai_probe ( struct snd_soc_dai * dai )
{
struct rk_i2s_dev * i2s = snd_soc_dai_get_drvdata ( dai ) ;
dai - > capture_dma_data = & i2s - > capture_dma_data ;
dai - > playback_dma_data = & i2s - > playback_dma_data ;
return 0 ;
}
2014-07-05 19:13:03 +08:00
static const struct snd_soc_dai_ops rockchip_i2s_dai_ops = {
. hw_params = rockchip_i2s_hw_params ,
. set_sysclk = rockchip_i2s_set_sysclk ,
. set_fmt = rockchip_i2s_set_fmt ,
. trigger = rockchip_i2s_trigger ,
} ;
static struct snd_soc_dai_driver rockchip_i2s_dai = {
2014-09-13 08:41:38 +08:00
. probe = rockchip_i2s_dai_probe ,
2014-07-05 19:13:03 +08:00
. playback = {
2014-09-13 08:41:38 +08:00
. stream_name = " Playback " ,
2014-07-05 19:13:03 +08:00
. channels_min = 2 ,
. channels_max = 8 ,
. rates = SNDRV_PCM_RATE_8000_192000 ,
. formats = ( SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S24_LE ) ,
} ,
. capture = {
2014-09-13 08:41:38 +08:00
. stream_name = " Capture " ,
2014-07-05 19:13:03 +08:00
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_192000 ,
. formats = ( SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S24_LE ) ,
} ,
. ops = & rockchip_i2s_dai_ops ,
} ;
static const struct snd_soc_component_driver rockchip_i2s_component = {
. name = DRV_NAME ,
} ;
static bool rockchip_i2s_wr_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case I2S_TXCR :
case I2S_RXCR :
case I2S_CKR :
case I2S_DMACR :
case I2S_INTCR :
case I2S_XFER :
case I2S_CLR :
case I2S_TXDR :
return true ;
default :
return false ;
}
}
static bool rockchip_i2s_rd_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case I2S_TXCR :
case I2S_RXCR :
case I2S_CKR :
case I2S_DMACR :
case I2S_INTCR :
case I2S_XFER :
case I2S_CLR :
case I2S_RXDR :
2014-09-13 08:42:12 +08:00
case I2S_FIFOLR :
case I2S_INTSR :
2014-07-05 19:13:03 +08:00
return true ;
default :
return false ;
}
}
static bool rockchip_i2s_volatile_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
case I2S_INTSR :
2014-09-13 08:42:12 +08:00
case I2S_CLR :
2014-07-05 19:13:03 +08:00
return true ;
default :
return false ;
}
}
static bool rockchip_i2s_precious_reg ( struct device * dev , unsigned int reg )
{
switch ( reg ) {
default :
return false ;
}
}
static const struct regmap_config rockchip_i2s_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = I2S_RXDR ,
. writeable_reg = rockchip_i2s_wr_reg ,
. readable_reg = rockchip_i2s_rd_reg ,
. volatile_reg = rockchip_i2s_volatile_reg ,
. precious_reg = rockchip_i2s_precious_reg ,
. cache_type = REGCACHE_FLAT ,
} ;
static int rockchip_i2s_probe ( struct platform_device * pdev )
{
struct rk_i2s_dev * i2s ;
struct resource * res ;
void __iomem * regs ;
int ret ;
i2s = devm_kzalloc ( & pdev - > dev , sizeof ( * i2s ) , GFP_KERNEL ) ;
if ( ! i2s ) {
dev_err ( & pdev - > dev , " Can't allocate rk_i2s_dev \n " ) ;
return - ENOMEM ;
}
/* try to prepare related clocks */
i2s - > hclk = devm_clk_get ( & pdev - > dev , " i2s_hclk " ) ;
if ( IS_ERR ( i2s - > hclk ) ) {
dev_err ( & pdev - > dev , " Can't retrieve i2s bus clock \n " ) ;
return PTR_ERR ( i2s - > hclk ) ;
}
2014-09-13 08:43:13 +08:00
ret = clk_prepare_enable ( i2s - > hclk ) ;
if ( ret ) {
dev_err ( i2s - > dev , " hclock enable failed %d \n " , ret ) ;
return ret ;
}
2014-07-05 19:13:03 +08:00
i2s - > mclk = devm_clk_get ( & pdev - > dev , " i2s_clk " ) ;
if ( IS_ERR ( i2s - > mclk ) ) {
dev_err ( & pdev - > dev , " Can't retrieve i2s master clock \n " ) ;
return PTR_ERR ( i2s - > mclk ) ;
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
2014-07-28 21:21:00 +08:00
if ( IS_ERR ( regs ) )
2014-07-05 19:13:03 +08:00
return PTR_ERR ( regs ) ;
i2s - > regmap = devm_regmap_init_mmio ( & pdev - > dev , regs ,
& rockchip_i2s_regmap_config ) ;
if ( IS_ERR ( i2s - > regmap ) ) {
dev_err ( & pdev - > dev ,
" Failed to initialise managed register map \n " ) ;
return PTR_ERR ( i2s - > regmap ) ;
}
i2s - > playback_dma_data . addr = res - > start + I2S_TXDR ;
i2s - > playback_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES ;
i2s - > playback_dma_data . maxburst = 16 ;
i2s - > capture_dma_data . addr = res - > start + I2S_RXDR ;
i2s - > capture_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES ;
i2s - > capture_dma_data . maxburst = 16 ;
i2s - > dev = & pdev - > dev ;
dev_set_drvdata ( & pdev - > dev , i2s ) ;
pm_runtime_enable ( & pdev - > dev ) ;
if ( ! pm_runtime_enabled ( & pdev - > dev ) ) {
ret = i2s_runtime_resume ( & pdev - > dev ) ;
if ( ret )
goto err_pm_disable ;
}
ret = devm_snd_soc_register_component ( & pdev - > dev ,
& rockchip_i2s_component ,
& rockchip_i2s_dai , 1 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Could not register DAI \n " ) ;
goto err_suspend ;
}
ret = snd_dmaengine_pcm_register ( & pdev - > dev , NULL , 0 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Could not register PCM \n " ) ;
goto err_pcm_register ;
}
return 0 ;
err_pcm_register :
snd_dmaengine_pcm_unregister ( & pdev - > dev ) ;
err_suspend :
if ( ! pm_runtime_status_suspended ( & pdev - > dev ) )
i2s_runtime_suspend ( & pdev - > dev ) ;
err_pm_disable :
pm_runtime_disable ( & pdev - > dev ) ;
return ret ;
}
static int rockchip_i2s_remove ( struct platform_device * pdev )
{
struct rk_i2s_dev * i2s = dev_get_drvdata ( & pdev - > dev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
if ( ! pm_runtime_status_suspended ( & pdev - > dev ) )
i2s_runtime_suspend ( & pdev - > dev ) ;
clk_disable_unprepare ( i2s - > mclk ) ;
clk_disable_unprepare ( i2s - > hclk ) ;
snd_dmaengine_pcm_unregister ( & pdev - > dev ) ;
snd_soc_unregister_component ( & pdev - > dev ) ;
return 0 ;
}
static const struct of_device_id rockchip_i2s_match [ ] = {
{ . compatible = " rockchip,rk3066-i2s " , } ,
{ } ,
} ;
static const struct dev_pm_ops rockchip_i2s_pm_ops = {
SET_RUNTIME_PM_OPS ( i2s_runtime_suspend , i2s_runtime_resume ,
NULL )
} ;
static struct platform_driver rockchip_i2s_driver = {
. probe = rockchip_i2s_probe ,
. remove = rockchip_i2s_remove ,
. driver = {
. name = DRV_NAME ,
. of_match_table = of_match_ptr ( rockchip_i2s_match ) ,
. pm = & rockchip_i2s_pm_ops ,
} ,
} ;
module_platform_driver ( rockchip_i2s_driver ) ;
MODULE_DESCRIPTION ( " ROCKCHIP IIS ASoC Interface " ) ;
MODULE_AUTHOR ( " jianqun <jay.xu@rock-chips.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;
MODULE_DEVICE_TABLE ( of , rockchip_i2s_match ) ;