2010-11-22 09:37:25 +03:00
/* sound/soc/samsung/i2s.c
2010-11-22 09:36:59 +03:00
*
* ALSA SoC Audio Layer - Samsung I2S Controller driver
*
* Copyright ( c ) 2010 Samsung Electronics Co . Ltd .
2012-02-25 14:54:36 +04:00
* Jaswinder Singh < jassisinghbrar @ gmail . com >
2010-11-22 09:36:59 +03:00
*
* 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 .
*/
2015-01-14 21:42:39 +03:00
# include <dt-bindings/sound/samsung-i2s.h>
2010-11-22 09:36:59 +03:00
# include <linux/delay.h>
# include <linux/slab.h>
# include <linux/clk.h>
2015-01-14 21:42:39 +03:00
# include <linux/clk-provider.h>
2010-11-22 09:36:59 +03:00
# include <linux/io.h>
2011-07-15 20:38:28 +04:00
# include <linux/module.h>
2013-01-18 15:47:01 +04:00
# include <linux/of.h>
# include <linux/of_gpio.h>
2011-12-08 12:45:03 +04:00
# include <linux/pm_runtime.h>
2010-11-22 09:36:59 +03:00
# include <sound/soc.h>
2011-01-11 01:26:06 +03:00
# include <sound/pcm_params.h>
2010-11-22 09:36:59 +03:00
2012-08-24 17:22:12 +04:00
# include <linux/platform_data/asoc-s3c.h>
2010-11-22 09:36:59 +03:00
# include "dma.h"
2011-07-20 12:07:12 +04:00
# include "idma.h"
2010-11-22 09:36:59 +03:00
# include "i2s.h"
2011-06-20 11:36:18 +04:00
# include "i2s-regs.h"
2010-11-22 09:36:59 +03:00
# define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
2013-01-18 15:47:00 +04:00
enum samsung_dai_type {
TYPE_PRI ,
TYPE_SEC ,
} ;
2014-11-07 09:54:40 +03:00
struct samsung_i2s_variant_regs {
unsigned int bfs_off ;
unsigned int rfs_off ;
unsigned int sdf_off ;
unsigned int txr_off ;
unsigned int rclksrc_off ;
unsigned int mss_off ;
unsigned int cdclkcon_off ;
unsigned int lrp_off ;
unsigned int bfs_mask ;
unsigned int rfs_mask ;
unsigned int ftx0cnt_off ;
} ;
2013-01-18 15:47:01 +04:00
struct samsung_i2s_dai_data {
int dai_type ;
2013-08-12 13:49:51 +04:00
u32 quirks ;
2014-11-07 09:54:40 +03:00
const struct samsung_i2s_variant_regs * i2s_variant_regs ;
2013-01-18 15:47:01 +04:00
} ;
2010-11-22 09:36:59 +03:00
struct i2s_dai {
/* Platform device for this DAI */
struct platform_device * pdev ;
2015-01-14 21:42:30 +03:00
/* Memory mapped SFR region */
2010-11-22 09:36:59 +03:00
void __iomem * addr ;
/* Rate of RCLK source clock */
unsigned long rclk_srcrate ;
/* Frame Clock */
unsigned frmclk ;
/*
* Specifically requested RCLK , BCLK by MACHINE Driver .
* 0 indicates CPU driver is free to choose any value .
*/
unsigned rfs , bfs ;
/* I2S Controller's core clock */
struct clk * clk ;
/* Clock for generating I2S signals */
struct clk * op_clk ;
/* Pointer to the Primary_Fifo if this is Sec_Fifo, NULL otherwise */
struct i2s_dai * pri_dai ;
/* Pointer to the Secondary_Fifo if it has one, NULL otherwise */
struct i2s_dai * sec_dai ;
# define DAI_OPENED (1 << 0) /* Dai is opened */
# define DAI_MANAGER (1 << 1) /* Dai is the manager */
unsigned mode ;
/* Driver for this DAI */
struct snd_soc_dai_driver i2s_dai_drv ;
/* DMA parameters */
struct s3c_dma_params dma_playback ;
struct s3c_dma_params dma_capture ;
2011-07-20 12:07:12 +04:00
struct s3c_dma_params idma_playback ;
2010-11-22 09:36:59 +03:00
u32 quirks ;
u32 suspend_i2smod ;
u32 suspend_i2scon ;
u32 suspend_i2spsr ;
2014-11-07 09:54:40 +03:00
const struct samsung_i2s_variant_regs * variant_regs ;
2015-01-14 21:42:35 +03:00
/* Spinlock protecting access to the device's registers */
spinlock_t spinlock ;
spinlock_t * lock ;
2015-01-14 21:42:39 +03:00
/* Below fields are only valid if this is the primary FIFO */
struct clk * clk_table [ 3 ] ;
struct clk_onecell_data clk_data ;
2010-11-22 09:36:59 +03:00
} ;
/* Lock for cross i/f checks */
static DEFINE_SPINLOCK ( lock ) ;
/* If this is the 'overlay' stereo DAI */
static inline bool is_secondary ( struct i2s_dai * i2s )
{
return i2s - > pri_dai ? true : false ;
}
/* If operating in SoC-Slave mode */
static inline bool is_slave ( struct i2s_dai * i2s )
{
2014-11-07 09:54:40 +03:00
u32 mod = readl ( i2s - > addr + I2SMOD ) ;
return ( mod & ( 1 < < i2s - > variant_regs - > mss_off ) ) ? true : false ;
2010-11-22 09:36:59 +03:00
}
/* If this interface of the controller is transmitting data */
static inline bool tx_active ( struct i2s_dai * i2s )
{
u32 active ;
if ( ! i2s )
return false ;
2011-06-10 05:36:54 +04:00
active = readl ( i2s - > addr + I2SCON ) ;
2010-11-22 09:36:59 +03:00
if ( is_secondary ( i2s ) )
active & = CON_TXSDMA_ACTIVE ;
else
active & = CON_TXDMA_ACTIVE ;
return active ? true : false ;
}
2015-01-14 21:42:33 +03:00
/* Return pointer to the other DAI */
static inline struct i2s_dai * get_other_dai ( struct i2s_dai * i2s )
{
return i2s - > pri_dai ? : i2s - > sec_dai ;
}
2010-11-22 09:36:59 +03:00
/* If the other interface of the controller is transmitting data */
static inline bool other_tx_active ( struct i2s_dai * i2s )
{
2015-01-14 21:42:33 +03:00
struct i2s_dai * other = get_other_dai ( i2s ) ;
2010-11-22 09:36:59 +03:00
return tx_active ( other ) ;
}
/* If any interface of the controller is transmitting data */
static inline bool any_tx_active ( struct i2s_dai * i2s )
{
return tx_active ( i2s ) | | other_tx_active ( i2s ) ;
}
/* If this interface of the controller is receiving data */
static inline bool rx_active ( struct i2s_dai * i2s )
{
u32 active ;
if ( ! i2s )
return false ;
2011-06-10 05:36:54 +04:00
active = readl ( i2s - > addr + I2SCON ) & CON_RXDMA_ACTIVE ;
2010-11-22 09:36:59 +03:00
return active ? true : false ;
}
/* If the other interface of the controller is receiving data */
static inline bool other_rx_active ( struct i2s_dai * i2s )
{
2015-01-14 21:42:33 +03:00
struct i2s_dai * other = get_other_dai ( i2s ) ;
2010-11-22 09:36:59 +03:00
return rx_active ( other ) ;
}
/* If any interface of the controller is receiving data */
static inline bool any_rx_active ( struct i2s_dai * i2s )
{
return rx_active ( i2s ) | | other_rx_active ( i2s ) ;
}
/* If the other DAI is transmitting or receiving data */
static inline bool other_active ( struct i2s_dai * i2s )
{
return other_rx_active ( i2s ) | | other_tx_active ( i2s ) ;
}
/* If this DAI is transmitting or receiving data */
static inline bool this_active ( struct i2s_dai * i2s )
{
return tx_active ( i2s ) | | rx_active ( i2s ) ;
}
/* If the controller is active anyway */
static inline bool any_active ( struct i2s_dai * i2s )
{
return this_active ( i2s ) | | other_active ( i2s ) ;
}
static inline struct i2s_dai * to_info ( struct snd_soc_dai * dai )
{
return snd_soc_dai_get_drvdata ( dai ) ;
}
static inline bool is_opened ( struct i2s_dai * i2s )
{
if ( i2s & & ( i2s - > mode & DAI_OPENED ) )
return true ;
else
return false ;
}
static inline bool is_manager ( struct i2s_dai * i2s )
{
if ( is_opened ( i2s ) & & ( i2s - > mode & DAI_MANAGER ) )
return true ;
else
return false ;
}
/* Read RCLK of I2S (in multiples of LRCLK) */
static inline unsigned get_rfs ( struct i2s_dai * i2s )
{
2013-08-12 13:49:52 +04:00
u32 rfs ;
2014-11-07 09:54:40 +03:00
rfs = readl ( i2s - > addr + I2SMOD ) > > i2s - > variant_regs - > rfs_off ;
rfs & = i2s - > variant_regs - > rfs_mask ;
2010-11-22 09:36:59 +03:00
switch ( rfs ) {
2014-11-07 09:54:40 +03:00
case 7 : return 192 ;
case 6 : return 96 ;
case 5 : return 128 ;
case 4 : return 64 ;
2010-11-22 09:36:59 +03:00
case 3 : return 768 ;
case 2 : return 384 ;
case 1 : return 512 ;
default : return 256 ;
}
}
/* Write RCLK of I2S (in multiples of LRCLK) */
static inline void set_rfs ( struct i2s_dai * i2s , unsigned rfs )
{
u32 mod = readl ( i2s - > addr + I2SMOD ) ;
2014-11-07 09:54:40 +03:00
int rfs_shift = i2s - > variant_regs - > rfs_off ;
2010-11-22 09:36:59 +03:00
2014-11-07 09:54:40 +03:00
mod & = ~ ( i2s - > variant_regs - > rfs_mask < < rfs_shift ) ;
2010-11-22 09:36:59 +03:00
switch ( rfs ) {
2014-11-07 09:54:40 +03:00
case 192 :
mod | = ( EXYNOS7_MOD_RCLK_192FS < < rfs_shift ) ;
break ;
case 96 :
mod | = ( EXYNOS7_MOD_RCLK_96FS < < rfs_shift ) ;
break ;
case 128 :
mod | = ( EXYNOS7_MOD_RCLK_128FS < < rfs_shift ) ;
break ;
case 64 :
mod | = ( EXYNOS7_MOD_RCLK_64FS < < rfs_shift ) ;
break ;
2010-11-22 09:36:59 +03:00
case 768 :
2013-07-26 17:36:48 +04:00
mod | = ( MOD_RCLK_768FS < < rfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
case 512 :
2013-07-26 17:36:48 +04:00
mod | = ( MOD_RCLK_512FS < < rfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
case 384 :
2013-07-26 17:36:48 +04:00
mod | = ( MOD_RCLK_384FS < < rfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
default :
2013-07-26 17:36:48 +04:00
mod | = ( MOD_RCLK_256FS < < rfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
}
writel ( mod , i2s - > addr + I2SMOD ) ;
}
/* Read Bit-Clock of I2S (in multiples of LRCLK) */
static inline unsigned get_bfs ( struct i2s_dai * i2s )
{
2013-08-12 13:49:52 +04:00
u32 bfs ;
2014-11-07 09:54:40 +03:00
bfs = readl ( i2s - > addr + I2SMOD ) > > i2s - > variant_regs - > bfs_off ;
bfs & = i2s - > variant_regs - > bfs_mask ;
2010-11-22 09:36:59 +03:00
switch ( bfs ) {
2013-08-12 13:49:52 +04:00
case 8 : return 256 ;
case 7 : return 192 ;
case 6 : return 128 ;
case 5 : return 96 ;
case 4 : return 64 ;
2010-11-22 09:36:59 +03:00
case 3 : return 24 ;
case 2 : return 16 ;
case 1 : return 48 ;
default : return 32 ;
}
}
/* Write Bit-Clock of I2S (in multiples of LRCLK) */
static inline void set_bfs ( struct i2s_dai * i2s , unsigned bfs )
{
u32 mod = readl ( i2s - > addr + I2SMOD ) ;
2013-08-12 13:49:52 +04:00
int tdm = i2s - > quirks & QUIRK_SUPPORTS_TDM ;
2014-11-07 09:54:40 +03:00
int bfs_shift = i2s - > variant_regs - > bfs_off ;
2013-08-12 13:49:52 +04:00
/* Non-TDM I2S controllers do not support BCLK > 48 * FS */
if ( ! tdm & & bfs > 48 ) {
dev_err ( & i2s - > pdev - > dev , " Unsupported BCLK divider \n " ) ;
return ;
}
2010-11-22 09:36:59 +03:00
2014-11-07 09:54:40 +03:00
mod & = ~ ( i2s - > variant_regs - > bfs_mask < < bfs_shift ) ;
2010-11-22 09:36:59 +03:00
switch ( bfs ) {
case 48 :
2013-07-26 17:36:48 +04:00
mod | = ( MOD_BCLK_48FS < < bfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
case 32 :
2013-07-26 17:36:48 +04:00
mod | = ( MOD_BCLK_32FS < < bfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
case 24 :
2013-07-26 17:36:48 +04:00
mod | = ( MOD_BCLK_24FS < < bfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
case 16 :
2013-07-26 17:36:48 +04:00
mod | = ( MOD_BCLK_16FS < < bfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
2013-08-12 13:49:52 +04:00
case 64 :
mod | = ( EXYNOS5420_MOD_BCLK_64FS < < bfs_shift ) ;
break ;
case 96 :
mod | = ( EXYNOS5420_MOD_BCLK_96FS < < bfs_shift ) ;
break ;
case 128 :
mod | = ( EXYNOS5420_MOD_BCLK_128FS < < bfs_shift ) ;
break ;
case 192 :
mod | = ( EXYNOS5420_MOD_BCLK_192FS < < bfs_shift ) ;
break ;
case 256 :
mod | = ( EXYNOS5420_MOD_BCLK_256FS < < bfs_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
default :
dev_err ( & i2s - > pdev - > dev , " Wrong BCLK Divider! \n " ) ;
return ;
}
writel ( mod , i2s - > addr + I2SMOD ) ;
}
/* Sample-Size */
static inline int get_blc ( struct i2s_dai * i2s )
{
int blc = readl ( i2s - > addr + I2SMOD ) ;
blc = ( blc > > 13 ) & 0x3 ;
switch ( blc ) {
case 2 : return 24 ;
case 1 : return 8 ;
default : return 16 ;
}
}
/* TX Channel Control */
static void i2s_txctrl ( struct i2s_dai * i2s , int on )
{
void __iomem * addr = i2s - > addr ;
2014-11-07 09:54:40 +03:00
int txr_off = i2s - > variant_regs - > txr_off ;
2010-11-22 09:36:59 +03:00
u32 con = readl ( addr + I2SCON ) ;
2014-11-07 09:54:40 +03:00
u32 mod = readl ( addr + I2SMOD ) & ~ ( 3 < < txr_off ) ;
2010-11-22 09:36:59 +03:00
if ( on ) {
con | = CON_ACTIVE ;
con & = ~ CON_TXCH_PAUSE ;
if ( is_secondary ( i2s ) ) {
con | = CON_TXSDMA_ACTIVE ;
con & = ~ CON_TXSDMA_PAUSE ;
} else {
con | = CON_TXDMA_ACTIVE ;
con & = ~ CON_TXDMA_PAUSE ;
}
if ( any_rx_active ( i2s ) )
2014-11-07 09:54:40 +03:00
mod | = 2 < < txr_off ;
2010-11-22 09:36:59 +03:00
else
2014-11-07 09:54:40 +03:00
mod | = 0 < < txr_off ;
2010-11-22 09:36:59 +03:00
} else {
if ( is_secondary ( i2s ) ) {
con | = CON_TXSDMA_PAUSE ;
con & = ~ CON_TXSDMA_ACTIVE ;
} else {
con | = CON_TXDMA_PAUSE ;
con & = ~ CON_TXDMA_ACTIVE ;
}
if ( other_tx_active ( i2s ) ) {
writel ( con , addr + I2SCON ) ;
return ;
}
con | = CON_TXCH_PAUSE ;
if ( any_rx_active ( i2s ) )
2014-11-07 09:54:40 +03:00
mod | = 1 < < txr_off ;
2010-11-22 09:36:59 +03:00
else
con & = ~ CON_ACTIVE ;
}
writel ( mod , addr + I2SMOD ) ;
writel ( con , addr + I2SCON ) ;
}
/* RX Channel Control */
static void i2s_rxctrl ( struct i2s_dai * i2s , int on )
{
void __iomem * addr = i2s - > addr ;
2014-11-07 09:54:40 +03:00
int txr_off = i2s - > variant_regs - > txr_off ;
2010-11-22 09:36:59 +03:00
u32 con = readl ( addr + I2SCON ) ;
2014-11-07 09:54:40 +03:00
u32 mod = readl ( addr + I2SMOD ) & ~ ( 3 < < txr_off ) ;
2010-11-22 09:36:59 +03:00
if ( on ) {
con | = CON_RXDMA_ACTIVE | CON_ACTIVE ;
con & = ~ ( CON_RXDMA_PAUSE | CON_RXCH_PAUSE ) ;
if ( any_tx_active ( i2s ) )
2014-11-07 09:54:40 +03:00
mod | = 2 < < txr_off ;
2010-11-22 09:36:59 +03:00
else
2014-11-07 09:54:40 +03:00
mod | = 1 < < txr_off ;
2010-11-22 09:36:59 +03:00
} else {
con | = CON_RXDMA_PAUSE | CON_RXCH_PAUSE ;
con & = ~ CON_RXDMA_ACTIVE ;
if ( any_tx_active ( i2s ) )
2014-11-07 09:54:40 +03:00
mod | = 0 < < txr_off ;
2010-11-22 09:36:59 +03:00
else
con & = ~ CON_ACTIVE ;
}
writel ( mod , addr + I2SMOD ) ;
writel ( con , addr + I2SCON ) ;
}
/* Flush FIFO of an interface */
static inline void i2s_fifo ( struct i2s_dai * i2s , u32 flush )
{
void __iomem * fic ;
u32 val ;
if ( ! i2s )
return ;
if ( is_secondary ( i2s ) )
fic = i2s - > addr + I2SFICS ;
else
fic = i2s - > addr + I2SFIC ;
/* Flush the FIFO */
writel ( readl ( fic ) | flush , fic ) ;
/* Be patient */
val = msecs_to_loops ( 1 ) / 1000 ; /* 1 usec */
while ( - - val )
cpu_relax ( ) ;
writel ( readl ( fic ) & ~ flush , fic ) ;
}
static int i2s_set_sysclk ( struct snd_soc_dai * dai ,
int clk_id , unsigned int rfs , int dir )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2015-01-14 21:42:33 +03:00
struct i2s_dai * other = get_other_dai ( i2s ) ;
2014-11-07 09:54:40 +03:00
const struct samsung_i2s_variant_regs * i2s_regs = i2s - > variant_regs ;
unsigned int cdcon_mask = 1 < < i2s_regs - > cdclkcon_off ;
unsigned int rsrc_mask = 1 < < i2s_regs - > rclksrc_off ;
2015-01-14 21:42:36 +03:00
u32 mod , mask , val = 0 ;
spin_lock ( i2s - > lock ) ;
mod = readl ( i2s - > addr + I2SMOD ) ;
spin_unlock ( i2s - > lock ) ;
2010-11-22 09:36:59 +03:00
switch ( clk_id ) {
2014-05-19 21:30:38 +04:00
case SAMSUNG_I2S_OPCLK :
2015-01-14 21:42:36 +03:00
mask = MOD_OPCLK_MASK ;
val = dir ;
2014-05-19 21:30:38 +04:00
break ;
2010-11-22 09:36:59 +03:00
case SAMSUNG_I2S_CDCLK :
2015-01-14 21:42:36 +03:00
mask = 1 < < i2s_regs - > cdclkcon_off ;
2010-11-22 09:36:59 +03:00
/* Shouldn't matter in GATING(CLOCK_IN) mode */
if ( dir = = SND_SOC_CLOCK_IN )
rfs = 0 ;
2014-09-09 19:51:49 +04:00
if ( ( rfs & & other & & other - > rfs & & ( other - > rfs ! = rfs ) ) | |
2010-11-22 09:36:59 +03:00
( any_active ( i2s ) & &
( ( ( dir = = SND_SOC_CLOCK_IN )
2014-11-07 09:54:40 +03:00
& & ! ( mod & cdcon_mask ) ) | |
2010-11-22 09:36:59 +03:00
( ( dir = = SND_SOC_CLOCK_OUT )
2014-11-07 09:54:40 +03:00
& & ( mod & cdcon_mask ) ) ) ) ) {
2010-11-22 09:36:59 +03:00
dev_err ( & i2s - > pdev - > dev ,
" %s:%d Other DAI busy \n " , __func__ , __LINE__ ) ;
return - EAGAIN ;
}
if ( dir = = SND_SOC_CLOCK_IN )
2015-01-14 21:42:36 +03:00
val = 1 < < i2s_regs - > cdclkcon_off ;
2010-11-22 09:36:59 +03:00
i2s - > rfs = rfs ;
break ;
case SAMSUNG_I2S_RCLKSRC_0 : /* clock corrsponding to IISMOD[10] := 0 */
case SAMSUNG_I2S_RCLKSRC_1 : /* clock corrsponding to IISMOD[10] := 1 */
2015-01-14 21:42:36 +03:00
mask = 1 < < i2s_regs - > rclksrc_off ;
2010-11-22 09:36:59 +03:00
if ( ( i2s - > quirks & QUIRK_NO_MUXPSR )
| | ( clk_id = = SAMSUNG_I2S_RCLKSRC_0 ) )
clk_id = 0 ;
else
clk_id = 1 ;
if ( ! any_active ( i2s ) ) {
2014-05-22 14:10:52 +04:00
if ( i2s - > op_clk & & ! IS_ERR ( i2s - > op_clk ) ) {
2014-11-07 09:54:40 +03:00
if ( ( clk_id & & ! ( mod & rsrc_mask ) ) | |
( ! clk_id & & ( mod & rsrc_mask ) ) ) {
2012-10-03 03:46:58 +04:00
clk_disable_unprepare ( i2s - > op_clk ) ;
2010-11-22 09:36:59 +03:00
clk_put ( i2s - > op_clk ) ;
} else {
2010-12-20 05:05:46 +03:00
i2s - > rclk_srcrate =
clk_get_rate ( i2s - > op_clk ) ;
2010-11-22 09:36:59 +03:00
return 0 ;
}
}
2012-11-28 14:47:48 +04:00
if ( clk_id )
i2s - > op_clk = clk_get ( & i2s - > pdev - > dev ,
" i2s_opclk1 " ) ;
else
i2s - > op_clk = clk_get ( & i2s - > pdev - > dev ,
" i2s_opclk0 " ) ;
2014-05-22 14:10:52 +04:00
if ( WARN_ON ( IS_ERR ( i2s - > op_clk ) ) )
return PTR_ERR ( i2s - > op_clk ) ;
2012-10-03 03:46:58 +04:00
clk_prepare_enable ( i2s - > op_clk ) ;
2010-11-22 09:36:59 +03:00
i2s - > rclk_srcrate = clk_get_rate ( i2s - > op_clk ) ;
/* Over-ride the other's */
if ( other ) {
other - > op_clk = i2s - > op_clk ;
other - > rclk_srcrate = i2s - > rclk_srcrate ;
}
2014-11-07 09:54:40 +03:00
} else if ( ( ! clk_id & & ( mod & rsrc_mask ) )
| | ( clk_id & & ! ( mod & rsrc_mask ) ) ) {
2010-11-22 09:36:59 +03:00
dev_err ( & i2s - > pdev - > dev ,
" %s:%d Other DAI busy \n " , __func__ , __LINE__ ) ;
return - EAGAIN ;
} else {
/* Call can't be on the active DAI */
i2s - > op_clk = other - > op_clk ;
i2s - > rclk_srcrate = other - > rclk_srcrate ;
return 0 ;
}
2015-01-14 21:42:36 +03:00
if ( clk_id = = 1 )
val = 1 < < i2s_regs - > rclksrc_off ;
2014-11-20 13:03:17 +03:00
break ;
2010-11-22 09:36:59 +03:00
default :
dev_err ( & i2s - > pdev - > dev , " We don't serve that! \n " ) ;
return - EINVAL ;
}
2015-01-14 21:42:36 +03:00
spin_lock ( i2s - > lock ) ;
mod = readl ( i2s - > addr + I2SMOD ) ;
mod = ( mod & ~ mask ) | val ;
2010-11-22 09:36:59 +03:00
writel ( mod , i2s - > addr + I2SMOD ) ;
2015-01-14 21:42:36 +03:00
spin_unlock ( i2s - > lock ) ;
2010-11-22 09:36:59 +03:00
return 0 ;
}
static int i2s_set_fmt ( struct snd_soc_dai * dai ,
unsigned int fmt )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2014-11-07 09:54:40 +03:00
int lrp_shift , sdf_shift , sdf_mask , lrp_rlow , mod_slave ;
2015-01-14 21:42:36 +03:00
u32 mod , tmp = 0 ;
2010-11-22 09:36:59 +03:00
2014-11-07 09:54:40 +03:00
lrp_shift = i2s - > variant_regs - > lrp_off ;
sdf_shift = i2s - > variant_regs - > sdf_off ;
mod_slave = 1 < < i2s - > variant_regs - > mss_off ;
2013-08-12 13:49:52 +04:00
2013-07-26 17:36:48 +04:00
sdf_mask = MOD_SDF_MASK < < sdf_shift ;
lrp_rlow = MOD_LR_RLOW < < lrp_shift ;
2010-11-22 09:36:59 +03:00
/* Format is priority */
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_RIGHT_J :
2013-07-26 17:36:48 +04:00
tmp | = lrp_rlow ;
tmp | = ( MOD_SDF_MSB < < sdf_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
case SND_SOC_DAIFMT_LEFT_J :
2013-07-26 17:36:48 +04:00
tmp | = lrp_rlow ;
tmp | = ( MOD_SDF_LSB < < sdf_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
case SND_SOC_DAIFMT_I2S :
2013-07-26 17:36:48 +04:00
tmp | = ( MOD_SDF_IIS < < sdf_shift ) ;
2010-11-22 09:36:59 +03:00
break ;
default :
dev_err ( & i2s - > pdev - > dev , " Format not supported \n " ) ;
return - EINVAL ;
}
/*
* INV flag is relative to the FORMAT flag - if set it simply
* flips the polarity specified by the Standard
*/
switch ( fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_NB_NF :
break ;
case SND_SOC_DAIFMT_NB_IF :
2013-07-26 17:36:48 +04:00
if ( tmp & lrp_rlow )
tmp & = ~ lrp_rlow ;
2010-11-22 09:36:59 +03:00
else
2013-07-26 17:36:48 +04:00
tmp | = lrp_rlow ;
2010-11-22 09:36:59 +03:00
break ;
default :
dev_err ( & i2s - > pdev - > dev , " Polarity not supported \n " ) ;
return - EINVAL ;
}
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
case SND_SOC_DAIFMT_CBM_CFM :
2014-11-07 09:54:40 +03:00
tmp | = mod_slave ;
2010-11-22 09:36:59 +03:00
break ;
case SND_SOC_DAIFMT_CBS_CFS :
/* Set default source clock in Master mode */
if ( i2s - > rclk_srcrate = = 0 )
i2s_set_sysclk ( dai , SAMSUNG_I2S_RCLKSRC_0 ,
0 , SND_SOC_CLOCK_IN ) ;
break ;
default :
dev_err ( & i2s - > pdev - > dev , " master/slave format not supported \n " ) ;
return - EINVAL ;
}
2015-01-14 21:42:36 +03:00
spin_lock ( i2s - > lock ) ;
mod = readl ( i2s - > addr + I2SMOD ) ;
2013-07-26 17:36:48 +04:00
/*
* Don ' t change the I2S mode if any controller is active on this
* channel .
*/
2010-11-22 09:36:59 +03:00
if ( any_active ( i2s ) & &
2014-11-07 09:54:40 +03:00
( ( mod & ( sdf_mask | lrp_rlow | mod_slave ) ) ! = tmp ) ) {
2015-01-14 21:42:36 +03:00
spin_unlock ( i2s - > lock ) ;
2010-11-22 09:36:59 +03:00
dev_err ( & i2s - > pdev - > dev ,
" %s:%d Other DAI busy \n " , __func__ , __LINE__ ) ;
return - EAGAIN ;
}
2014-11-07 09:54:40 +03:00
mod & = ~ ( sdf_mask | lrp_rlow | mod_slave ) ;
2010-11-22 09:36:59 +03:00
mod | = tmp ;
writel ( mod , i2s - > addr + I2SMOD ) ;
2015-01-14 21:42:36 +03:00
spin_unlock ( i2s - > lock ) ;
2010-11-22 09:36:59 +03:00
return 0 ;
}
static int i2s_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params , struct snd_soc_dai * dai )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2015-01-14 21:42:36 +03:00
u32 mod , mask = 0 , val = 0 ;
2010-11-22 09:36:59 +03:00
if ( ! is_secondary ( i2s ) )
2015-01-14 21:42:36 +03:00
mask | = ( MOD_DC2_EN | MOD_DC1_EN ) ;
2010-11-22 09:36:59 +03:00
switch ( params_channels ( params ) ) {
case 6 :
2015-01-14 21:42:36 +03:00
val | = MOD_DC2_EN ;
2010-11-22 09:36:59 +03:00
case 4 :
2015-01-14 21:42:36 +03:00
val | = MOD_DC1_EN ;
2010-11-22 09:36:59 +03:00
break ;
case 2 :
2012-03-16 10:40:53 +04:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
i2s - > dma_playback . dma_size = 4 ;
else
i2s - > dma_capture . dma_size = 4 ;
break ;
case 1 :
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
i2s - > dma_playback . dma_size = 2 ;
else
i2s - > dma_capture . dma_size = 2 ;
2010-11-22 09:36:59 +03:00
break ;
default :
dev_err ( & i2s - > pdev - > dev , " %d channels not supported \n " ,
params_channels ( params ) ) ;
return - EINVAL ;
}
if ( is_secondary ( i2s ) )
2015-01-14 21:42:36 +03:00
mask | = MOD_BLCS_MASK ;
2010-11-22 09:36:59 +03:00
else
2015-01-14 21:42:36 +03:00
mask | = MOD_BLCP_MASK ;
2010-11-22 09:36:59 +03:00
if ( is_manager ( i2s ) )
2015-01-14 21:42:36 +03:00
mask | = MOD_BLC_MASK ;
2010-11-22 09:36:59 +03:00
2014-05-23 16:05:39 +04:00
switch ( params_width ( params ) ) {
case 8 :
2010-11-22 09:36:59 +03:00
if ( is_secondary ( i2s ) )
2015-01-14 21:42:36 +03:00
val | = MOD_BLCS_8BIT ;
2010-11-22 09:36:59 +03:00
else
2015-01-14 21:42:36 +03:00
val | = MOD_BLCP_8BIT ;
2010-11-22 09:36:59 +03:00
if ( is_manager ( i2s ) )
2015-01-14 21:42:36 +03:00
val | = MOD_BLC_8BIT ;
2010-11-22 09:36:59 +03:00
break ;
2014-05-23 16:05:39 +04:00
case 16 :
2010-11-22 09:36:59 +03:00
if ( is_secondary ( i2s ) )
2015-01-14 21:42:36 +03:00
val | = MOD_BLCS_16BIT ;
2010-11-22 09:36:59 +03:00
else
2015-01-14 21:42:36 +03:00
val | = MOD_BLCP_16BIT ;
2010-11-22 09:36:59 +03:00
if ( is_manager ( i2s ) )
2015-01-14 21:42:36 +03:00
val | = MOD_BLC_16BIT ;
2010-11-22 09:36:59 +03:00
break ;
2014-05-23 16:05:39 +04:00
case 24 :
2010-11-22 09:36:59 +03:00
if ( is_secondary ( i2s ) )
2015-01-14 21:42:36 +03:00
val | = MOD_BLCS_24BIT ;
2010-11-22 09:36:59 +03:00
else
2015-01-14 21:42:36 +03:00
val | = MOD_BLCP_24BIT ;
2010-11-22 09:36:59 +03:00
if ( is_manager ( i2s ) )
2015-01-14 21:42:36 +03:00
val | = MOD_BLC_24BIT ;
2010-11-22 09:36:59 +03:00
break ;
default :
dev_err ( & i2s - > pdev - > dev , " Format(%d) not supported \n " ,
params_format ( params ) ) ;
return - EINVAL ;
}
2015-01-14 21:42:36 +03:00
spin_lock ( i2s - > lock ) ;
mod = readl ( i2s - > addr + I2SMOD ) ;
mod = ( mod & ~ mask ) | val ;
2010-11-22 09:36:59 +03:00
writel ( mod , i2s - > addr + I2SMOD ) ;
2015-01-14 21:42:36 +03:00
spin_unlock ( i2s - > lock ) ;
2010-11-22 09:36:59 +03:00
2013-12-05 18:14:52 +04:00
samsung_asoc_init_dma_data ( dai , & i2s - > dma_playback , & i2s - > dma_capture ) ;
2010-11-22 09:36:59 +03:00
i2s - > frmclk = params_rate ( params ) ;
return 0 ;
}
/* We set constraints on the substream acc to the version of I2S */
static int i2s_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2015-01-14 21:42:33 +03:00
struct i2s_dai * other = get_other_dai ( i2s ) ;
2010-11-22 09:36:59 +03:00
unsigned long flags ;
spin_lock_irqsave ( & lock , flags ) ;
i2s - > mode | = DAI_OPENED ;
if ( is_manager ( other ) )
i2s - > mode & = ~ DAI_MANAGER ;
else
i2s - > mode | = DAI_MANAGER ;
2013-01-24 16:35:31 +04:00
if ( ! any_active ( i2s ) & & ( i2s - > quirks & QUIRK_NEED_RSTCLR ) )
writel ( CON_RSTCLR , i2s - > addr + I2SCON ) ;
2010-11-22 09:36:59 +03:00
spin_unlock_irqrestore ( & lock , flags ) ;
return 0 ;
}
static void i2s_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2015-01-14 21:42:33 +03:00
struct i2s_dai * other = get_other_dai ( i2s ) ;
2010-11-22 09:36:59 +03:00
unsigned long flags ;
spin_lock_irqsave ( & lock , flags ) ;
i2s - > mode & = ~ DAI_OPENED ;
i2s - > mode & = ~ DAI_MANAGER ;
2015-01-14 21:42:39 +03:00
if ( is_opened ( other ) )
2010-11-22 09:36:59 +03:00
other - > mode | = DAI_MANAGER ;
2015-01-14 21:42:39 +03:00
2010-11-22 09:36:59 +03:00
/* Reset any constraint on RFS and BFS */
i2s - > rfs = 0 ;
i2s - > bfs = 0 ;
spin_unlock_irqrestore ( & lock , flags ) ;
}
static int config_setup ( struct i2s_dai * i2s )
{
2015-01-14 21:42:33 +03:00
struct i2s_dai * other = get_other_dai ( i2s ) ;
2010-11-22 09:36:59 +03:00
unsigned rfs , bfs , blc ;
u32 psr ;
blc = get_blc ( i2s ) ;
bfs = i2s - > bfs ;
if ( ! bfs & & other )
bfs = other - > bfs ;
/* Select least possible multiple(2) if no constraint set */
if ( ! bfs )
bfs = blc * 2 ;
rfs = i2s - > rfs ;
if ( ! rfs & & other )
rfs = other - > rfs ;
if ( ( rfs = = 256 | | rfs = = 512 ) & & ( blc = = 24 ) ) {
dev_err ( & i2s - > pdev - > dev ,
" %d-RFS not supported for 24-blc \n " , rfs ) ;
return - EINVAL ;
}
if ( ! rfs ) {
if ( bfs = = 16 | | bfs = = 32 )
rfs = 256 ;
else
rfs = 384 ;
}
/* If already setup and running */
if ( any_active ( i2s ) & & ( get_rfs ( i2s ) ! = rfs | | get_bfs ( i2s ) ! = bfs ) ) {
dev_err ( & i2s - > pdev - > dev ,
" %s:%d Other DAI busy \n " , __func__ , __LINE__ ) ;
return - EAGAIN ;
}
set_bfs ( i2s , bfs ) ;
set_rfs ( i2s , rfs ) ;
2013-07-11 11:08:25 +04:00
/* Don't bother with PSR in Slave mode */
if ( is_slave ( i2s ) )
return 0 ;
2010-11-22 09:36:59 +03:00
if ( ! ( i2s - > quirks & QUIRK_NO_MUXPSR ) ) {
psr = i2s - > rclk_srcrate / i2s - > frmclk / rfs ;
writel ( ( ( psr - 1 ) < < 8 ) | PSR_PSREN , i2s - > addr + I2SPSR ) ;
dev_dbg ( & i2s - > pdev - > dev ,
" RCLK_SRC=%luHz PSR=%u, RCLK=%dfs, BCLK=%dfs \n " ,
i2s - > rclk_srcrate , psr , rfs , bfs ) ;
}
return 0 ;
}
static int i2s_trigger ( struct snd_pcm_substream * substream ,
int cmd , struct snd_soc_dai * dai )
{
int capture = ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE ) ;
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct i2s_dai * i2s = to_info ( rtd - > cpu_dai ) ;
unsigned long flags ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
2015-01-14 21:42:35 +03:00
spin_lock_irqsave ( i2s - > lock , flags ) ;
2010-11-22 09:36:59 +03:00
if ( config_setup ( i2s ) ) {
2015-01-14 21:42:35 +03:00
spin_unlock_irqrestore ( i2s - > lock , flags ) ;
2010-11-22 09:36:59 +03:00
return - EINVAL ;
}
if ( capture )
i2s_rxctrl ( i2s , 1 ) ;
else
i2s_txctrl ( i2s , 1 ) ;
2015-01-14 21:42:35 +03:00
spin_unlock_irqrestore ( i2s - > lock , flags ) ;
2010-11-22 09:36:59 +03:00
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
2015-01-14 21:42:35 +03:00
spin_lock_irqsave ( i2s - > lock , flags ) ;
2010-11-22 09:36:59 +03:00
2012-02-25 15:12:34 +04:00
if ( capture ) {
2010-11-22 09:36:59 +03:00
i2s_rxctrl ( i2s , 0 ) ;
2010-12-20 05:05:47 +03:00
i2s_fifo ( i2s , FIC_RXFLUSH ) ;
2012-02-25 15:12:34 +04:00
} else {
i2s_txctrl ( i2s , 0 ) ;
2010-12-20 05:05:47 +03:00
i2s_fifo ( i2s , FIC_TXFLUSH ) ;
2012-02-25 15:12:34 +04:00
}
2010-12-20 05:05:47 +03:00
2015-01-14 21:42:35 +03:00
spin_unlock_irqrestore ( i2s - > lock , flags ) ;
2010-11-22 09:36:59 +03:00
break ;
}
return 0 ;
}
static int i2s_set_clkdiv ( struct snd_soc_dai * dai ,
int div_id , int div )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2015-01-14 21:42:33 +03:00
struct i2s_dai * other = get_other_dai ( i2s ) ;
2010-11-22 09:36:59 +03:00
switch ( div_id ) {
case SAMSUNG_I2S_DIV_BCLK :
if ( ( any_active ( i2s ) & & div & & ( get_bfs ( i2s ) ! = div ) )
| | ( other & & other - > bfs & & ( other - > bfs ! = div ) ) ) {
dev_err ( & i2s - > pdev - > dev ,
" %s:%d Other DAI busy \n " , __func__ , __LINE__ ) ;
return - EAGAIN ;
}
i2s - > bfs = div ;
break ;
default :
dev_err ( & i2s - > pdev - > dev ,
" Invalid clock divider(%d) \n " , div_id ) ;
return - EINVAL ;
}
return 0 ;
}
static snd_pcm_sframes_t
i2s_delay ( struct snd_pcm_substream * substream , struct snd_soc_dai * dai )
{
struct i2s_dai * i2s = to_info ( dai ) ;
u32 reg = readl ( i2s - > addr + I2SFIC ) ;
snd_pcm_sframes_t delay ;
2014-11-07 09:54:40 +03:00
const struct samsung_i2s_variant_regs * i2s_regs = i2s - > variant_regs ;
2010-11-22 09:36:59 +03:00
if ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE )
delay = FIC_RXCOUNT ( reg ) ;
else if ( is_secondary ( i2s ) )
delay = FICS_TXCOUNT ( readl ( i2s - > addr + I2SFICS ) ) ;
else
2014-11-07 09:54:40 +03:00
delay = ( reg > > i2s_regs - > ftx0cnt_off ) & 0x7f ;
2010-11-22 09:36:59 +03:00
return delay ;
}
# ifdef CONFIG_PM
static int i2s_suspend ( struct snd_soc_dai * dai )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2014-07-04 18:05:45 +04:00
i2s - > suspend_i2smod = readl ( i2s - > addr + I2SMOD ) ;
i2s - > suspend_i2scon = readl ( i2s - > addr + I2SCON ) ;
i2s - > suspend_i2spsr = readl ( i2s - > addr + I2SPSR ) ;
2010-11-22 09:36:59 +03:00
return 0 ;
}
static int i2s_resume ( struct snd_soc_dai * dai )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2014-07-04 18:05:45 +04:00
writel ( i2s - > suspend_i2scon , i2s - > addr + I2SCON ) ;
writel ( i2s - > suspend_i2smod , i2s - > addr + I2SMOD ) ;
writel ( i2s - > suspend_i2spsr , i2s - > addr + I2SPSR ) ;
2010-11-22 09:36:59 +03:00
return 0 ;
}
# else
# define i2s_suspend NULL
# define i2s_resume NULL
# endif
static int samsung_i2s_dai_probe ( struct snd_soc_dai * dai )
{
struct i2s_dai * i2s = to_info ( dai ) ;
2015-01-14 21:42:33 +03:00
struct i2s_dai * other = get_other_dai ( i2s ) ;
2015-01-14 21:42:36 +03:00
unsigned long flags ;
2010-11-22 09:36:59 +03:00
2015-01-14 21:42:31 +03:00
if ( is_secondary ( i2s ) ) { /* If this is probe on the secondary DAI */
2013-10-19 18:23:15 +04:00
samsung_asoc_init_dma_data ( dai , & other - > sec_dai - > dma_playback ,
NULL ) ;
2015-01-14 21:42:34 +03:00
} else {
samsung_asoc_init_dma_data ( dai , & i2s - > dma_playback ,
& i2s - > dma_capture ) ;
2013-10-18 00:18:40 +04:00
2015-01-14 21:42:34 +03:00
if ( i2s - > quirks & QUIRK_NEED_RSTCLR )
writel ( CON_RSTCLR , i2s - > addr + I2SCON ) ;
2010-11-22 09:36:59 +03:00
2015-01-14 21:42:34 +03:00
if ( i2s - > quirks & QUIRK_SUPPORTS_IDMA )
idma_reg_addr_init ( i2s - > addr ,
2011-07-20 12:07:12 +04:00
i2s - > sec_dai - > idma_playback . dma_addr ) ;
2015-01-14 21:42:34 +03:00
}
2011-07-20 12:07:12 +04:00
2010-11-22 09:36:59 +03:00
/* Reset any constraint on RFS and BFS */
i2s - > rfs = 0 ;
i2s - > bfs = 0 ;
2014-04-23 12:04:24 +04:00
i2s - > rclk_srcrate = 0 ;
2015-01-14 21:42:36 +03:00
spin_lock_irqsave ( i2s - > lock , flags ) ;
2010-11-22 09:36:59 +03:00
i2s_txctrl ( i2s , 0 ) ;
i2s_rxctrl ( i2s , 0 ) ;
i2s_fifo ( i2s , FIC_TXFLUSH ) ;
i2s_fifo ( other , FIC_TXFLUSH ) ;
i2s_fifo ( i2s , FIC_RXFLUSH ) ;
2015-01-14 21:42:36 +03:00
spin_unlock_irqrestore ( i2s - > lock , flags ) ;
2010-11-22 09:36:59 +03:00
/* Gate CDCLK by default */
if ( ! is_opened ( other ) )
i2s_set_sysclk ( dai , SAMSUNG_I2S_CDCLK ,
0 , SND_SOC_CLOCK_IN ) ;
return 0 ;
}
static int samsung_i2s_dai_remove ( struct snd_soc_dai * dai )
{
struct i2s_dai * i2s = snd_soc_dai_get_drvdata ( dai ) ;
2015-01-14 21:42:32 +03:00
if ( ! is_secondary ( i2s ) ) {
2015-01-14 21:42:36 +03:00
if ( i2s - > quirks & QUIRK_NEED_RSTCLR ) {
spin_lock ( i2s - > lock ) ;
2010-11-22 09:36:59 +03:00
writel ( 0 , i2s - > addr + I2SCON ) ;
2015-01-14 21:42:36 +03:00
spin_unlock ( i2s - > lock ) ;
}
2010-11-22 09:36:59 +03:00
}
return 0 ;
}
2011-11-23 14:40:40 +04:00
static const struct snd_soc_dai_ops samsung_i2s_dai_ops = {
2010-11-22 09:36:59 +03:00
. trigger = i2s_trigger ,
. hw_params = i2s_hw_params ,
. set_fmt = i2s_set_fmt ,
. set_clkdiv = i2s_set_clkdiv ,
. set_sysclk = i2s_set_sysclk ,
. startup = i2s_startup ,
. shutdown = i2s_shutdown ,
. delay = i2s_delay ,
} ;
2013-03-21 14:35:55 +04:00
static const struct snd_soc_component_driver samsung_i2s_component = {
. name = " samsung-i2s " ,
} ;
2010-11-22 09:36:59 +03:00
# define SAMSUNG_I2S_RATES SNDRV_PCM_RATE_8000_96000
# define SAMSUNG_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE )
2012-12-07 18:26:15 +04:00
static struct i2s_dai * i2s_alloc_dai ( struct platform_device * pdev , bool sec )
2010-11-22 09:36:59 +03:00
{
struct i2s_dai * i2s ;
2013-04-02 15:23:02 +04:00
int ret ;
2010-11-22 09:36:59 +03:00
2011-12-04 00:30:37 +04:00
i2s = devm_kzalloc ( & pdev - > dev , sizeof ( struct i2s_dai ) , GFP_KERNEL ) ;
2010-11-22 09:36:59 +03:00
if ( i2s = = NULL )
return NULL ;
i2s - > pdev = pdev ;
i2s - > pri_dai = NULL ;
i2s - > sec_dai = NULL ;
i2s - > i2s_dai_drv . symmetric_rates = 1 ;
i2s - > i2s_dai_drv . probe = samsung_i2s_dai_probe ;
i2s - > i2s_dai_drv . remove = samsung_i2s_dai_remove ;
i2s - > i2s_dai_drv . ops = & samsung_i2s_dai_ops ;
i2s - > i2s_dai_drv . suspend = i2s_suspend ;
i2s - > i2s_dai_drv . resume = i2s_resume ;
2013-09-11 18:27:29 +04:00
i2s - > i2s_dai_drv . playback . channels_min = 1 ;
2010-11-22 09:36:59 +03:00
i2s - > i2s_dai_drv . playback . channels_max = 2 ;
i2s - > i2s_dai_drv . playback . rates = SAMSUNG_I2S_RATES ;
i2s - > i2s_dai_drv . playback . formats = SAMSUNG_I2S_FMTS ;
if ( ! sec ) {
2012-03-16 10:40:53 +04:00
i2s - > i2s_dai_drv . capture . channels_min = 1 ;
2010-11-22 09:36:59 +03:00
i2s - > i2s_dai_drv . capture . channels_max = 2 ;
i2s - > i2s_dai_drv . capture . rates = SAMSUNG_I2S_RATES ;
i2s - > i2s_dai_drv . capture . formats = SAMSUNG_I2S_FMTS ;
2013-04-02 15:23:02 +04:00
dev_set_drvdata ( & i2s - > pdev - > dev , i2s ) ;
2010-11-22 09:36:59 +03:00
} else { /* Create a new platform_device for Secondary */
2013-04-02 15:23:02 +04:00
i2s - > pdev = platform_device_alloc ( " samsung-i2s-sec " , - 1 ) ;
2013-10-25 13:06:24 +04:00
if ( ! i2s - > pdev )
2010-11-22 09:36:59 +03:00
return NULL ;
2013-08-01 14:02:47 +04:00
i2s - > pdev - > dev . parent = & pdev - > dev ;
2013-04-02 15:23:02 +04:00
platform_set_drvdata ( i2s - > pdev , i2s ) ;
ret = platform_device_add ( i2s - > pdev ) ;
if ( ret < 0 )
return NULL ;
}
2010-11-22 09:36:59 +03:00
return i2s ;
}
2013-01-18 15:47:01 +04:00
static const struct of_device_id exynos_i2s_match [ ] ;
2013-08-12 13:49:51 +04:00
static inline const struct samsung_i2s_dai_data * samsung_i2s_get_driver_data (
struct platform_device * pdev )
2013-01-18 15:47:00 +04:00
{
2015-01-14 21:42:28 +03:00
if ( IS_ENABLED ( CONFIG_OF ) & & pdev - > dev . of_node ) {
2013-01-18 15:47:01 +04:00
const struct of_device_id * match ;
match = of_match_node ( exynos_i2s_match , pdev - > dev . of_node ) ;
2015-01-14 21:42:28 +03:00
return match ? match - > data : NULL ;
} else {
2013-08-12 13:49:51 +04:00
return ( struct samsung_i2s_dai_data * )
platform_get_device_id ( pdev ) - > driver_data ;
2015-01-14 21:42:28 +03:00
}
2013-01-18 15:47:00 +04:00
}
2014-12-13 02:42:18 +03:00
# ifdef CONFIG_PM
2013-01-30 16:11:04 +04:00
static int i2s_runtime_suspend ( struct device * dev )
{
struct i2s_dai * i2s = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( i2s - > clk ) ;
return 0 ;
}
static int i2s_runtime_resume ( struct device * dev )
{
struct i2s_dai * i2s = dev_get_drvdata ( dev ) ;
clk_prepare_enable ( i2s - > clk ) ;
return 0 ;
}
2014-12-13 02:42:18 +03:00
# endif /* CONFIG_PM */
2013-01-30 16:11:04 +04:00
2015-01-14 21:42:39 +03:00
static void i2s_unregister_clocks ( struct i2s_dai * i2s )
{
int i ;
for ( i = 0 ; i < i2s - > clk_data . clk_num ; i + + ) {
if ( ! IS_ERR ( i2s - > clk_table [ i ] ) )
clk_unregister ( i2s - > clk_table [ i ] ) ;
}
}
static void i2s_unregister_clock_provider ( struct platform_device * pdev )
{
struct i2s_dai * i2s = dev_get_drvdata ( & pdev - > dev ) ;
of_clk_del_provider ( pdev - > dev . of_node ) ;
i2s_unregister_clocks ( i2s ) ;
}
static int i2s_register_clock_provider ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct i2s_dai * i2s = dev_get_drvdata ( dev ) ;
const char * clk_name [ 2 ] = { " i2s_opclk0 " , " i2s_opclk1 " } ;
const char * p_names [ 2 ] = { NULL } ;
const struct samsung_i2s_variant_regs * reg_info = i2s - > variant_regs ;
struct clk * rclksrc ;
int ret , i ;
/* Register the clock provider only if it's expected in the DTB */
if ( ! of_find_property ( dev - > of_node , " #clock-cells " , NULL ) )
return 0 ;
/* Get the RCLKSRC mux clock parent clock names */
for ( i = 0 ; i < ARRAY_SIZE ( p_names ) ; i + + ) {
rclksrc = clk_get ( dev , clk_name [ i ] ) ;
if ( IS_ERR ( rclksrc ) )
continue ;
p_names [ i ] = __clk_get_name ( rclksrc ) ;
clk_put ( rclksrc ) ;
}
if ( ! ( i2s - > quirks & QUIRK_NO_MUXPSR ) ) {
/* Activate the prescaler */
u32 val = readl ( i2s - > addr + I2SPSR ) ;
writel ( val | PSR_PSREN , i2s - > addr + I2SPSR ) ;
i2s - > clk_table [ CLK_I2S_RCLK_SRC ] = clk_register_mux ( NULL ,
" i2s_rclksrc " , p_names , ARRAY_SIZE ( p_names ) ,
CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT ,
i2s - > addr + I2SMOD , reg_info - > rclksrc_off ,
1 , 0 , i2s - > lock ) ;
i2s - > clk_table [ CLK_I2S_RCLK_PSR ] = clk_register_divider ( NULL ,
" i2s_presc " , " i2s_rclksrc " ,
CLK_SET_RATE_PARENT ,
i2s - > addr + I2SPSR , 8 , 6 , 0 , i2s - > lock ) ;
p_names [ 0 ] = " i2s_presc " ;
i2s - > clk_data . clk_num = 2 ;
}
of_property_read_string_index ( dev - > of_node ,
" clock-output-names " , 0 , & clk_name [ 0 ] ) ;
i2s - > clk_table [ CLK_I2S_CDCLK ] = clk_register_gate ( NULL , clk_name [ 0 ] ,
p_names [ 0 ] , CLK_SET_RATE_PARENT ,
i2s - > addr + I2SMOD , reg_info - > cdclkcon_off ,
CLK_GATE_SET_TO_DISABLE , i2s - > lock ) ;
i2s - > clk_data . clk_num + = 1 ;
i2s - > clk_data . clks = i2s - > clk_table ;
ret = of_clk_add_provider ( dev - > of_node , of_clk_src_onecell_get ,
& i2s - > clk_data ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed to add clock provider: %d \n " , ret ) ;
i2s_unregister_clocks ( i2s ) ;
}
return ret ;
}
2012-12-07 18:26:15 +04:00
static int samsung_i2s_probe ( struct platform_device * pdev )
2010-11-22 09:36:59 +03:00
{
struct i2s_dai * pri_dai , * sec_dai = NULL ;
2013-01-18 15:47:01 +04:00
struct s3c_audio_pdata * i2s_pdata = pdev - > dev . platform_data ;
struct samsung_i2s * i2s_cfg = NULL ;
2010-11-22 09:36:59 +03:00
struct resource * res ;
2013-01-18 15:47:01 +04:00
u32 regs_base , quirks = 0 , idma_addr = 0 ;
struct device_node * np = pdev - > dev . of_node ;
2013-08-12 13:49:51 +04:00
const struct samsung_i2s_dai_data * i2s_dai_data ;
2015-01-14 21:42:32 +03:00
int ret ;
2010-11-22 09:36:59 +03:00
/* Call during Seconday interface registration */
2013-08-12 13:49:51 +04:00
i2s_dai_data = samsung_i2s_get_driver_data ( pdev ) ;
2013-01-18 15:47:00 +04:00
2013-08-12 13:49:51 +04:00
if ( i2s_dai_data - > dai_type = = TYPE_SEC ) {
2010-11-22 09:36:59 +03:00
sec_dai = dev_get_drvdata ( & pdev - > dev ) ;
2013-04-02 15:23:01 +04:00
if ( ! sec_dai ) {
dev_err ( & pdev - > dev , " Unable to get drvdata \n " ) ;
return - EFAULT ;
}
2015-01-14 21:42:29 +03:00
ret = devm_snd_soc_register_component ( & sec_dai - > pdev - > dev ,
2013-09-04 23:37:51 +04:00
& samsung_i2s_component ,
& sec_dai - > i2s_dai_drv , 1 ) ;
2015-01-14 21:42:29 +03:00
if ( ret ! = 0 )
return ret ;
return samsung_asoc_dma_platform_register ( & pdev - > dev ) ;
2010-11-22 09:36:59 +03:00
}
2013-01-18 15:47:01 +04:00
pri_dai = i2s_alloc_dai ( pdev , false ) ;
if ( ! pri_dai ) {
dev_err ( & pdev - > dev , " Unable to alloc I2S_pri \n " ) ;
return - ENOMEM ;
2010-11-22 09:36:59 +03:00
}
2015-01-14 21:42:35 +03:00
spin_lock_init ( & pri_dai - > spinlock ) ;
pri_dai - > lock = & pri_dai - > spinlock ;
2013-01-18 15:47:01 +04:00
if ( ! np ) {
res = platform_get_resource ( pdev , IORESOURCE_DMA , 0 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev ,
" Unable to get I2S-TX dma resource \n " ) ;
return - ENXIO ;
}
pri_dai - > dma_playback . channel = res - > start ;
2010-11-22 09:36:59 +03:00
2013-01-18 15:47:01 +04:00
res = platform_get_resource ( pdev , IORESOURCE_DMA , 1 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev ,
" Unable to get I2S-RX dma resource \n " ) ;
return - ENXIO ;
}
pri_dai - > dma_capture . channel = res - > start ;
2010-11-22 09:36:59 +03:00
2013-01-18 15:47:01 +04:00
if ( i2s_pdata = = NULL ) {
dev_err ( & pdev - > dev , " Can't work without s3c_audio_pdata \n " ) ;
return - EINVAL ;
}
if ( & i2s_pdata - > type )
i2s_cfg = & i2s_pdata - > type . i2s ;
if ( i2s_cfg ) {
quirks = i2s_cfg - > quirks ;
idma_addr = i2s_cfg - > idma_addr ;
}
} else {
2013-08-12 13:49:51 +04:00
quirks = i2s_dai_data - > quirks ;
2013-01-18 15:47:01 +04:00
if ( of_property_read_u32 ( np , " samsung,idma-addr " ,
& idma_addr ) ) {
2014-11-07 09:54:39 +03:00
if ( quirks & QUIRK_SUPPORTS_IDMA ) {
dev_info ( & pdev - > dev , " idma address is not " \
2013-01-18 15:47:01 +04:00
" specified " ) ;
}
}
}
2010-11-22 09:36:59 +03:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2015-01-14 21:42:30 +03:00
pri_dai - > addr = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( pri_dai - > addr ) )
return PTR_ERR ( pri_dai - > addr ) ;
2010-11-22 09:36:59 +03:00
regs_base = res - > start ;
2015-01-14 21:42:31 +03:00
pri_dai - > clk = devm_clk_get ( & pdev - > dev , " iis " ) ;
if ( IS_ERR ( pri_dai - > clk ) ) {
dev_err ( & pdev - > dev , " Failed to get iis clock \n " ) ;
return PTR_ERR ( pri_dai - > clk ) ;
}
2015-01-14 21:42:32 +03:00
ret = clk_prepare_enable ( pri_dai - > clk ) ;
if ( ret ! = 0 ) {
dev_err ( & pdev - > dev , " failed to enable clock: %d \n " , ret ) ;
return ret ;
}
2010-11-22 09:36:59 +03:00
pri_dai - > dma_playback . dma_addr = regs_base + I2STXD ;
pri_dai - > dma_capture . dma_addr = regs_base + I2SRXD ;
2013-01-18 15:47:01 +04:00
pri_dai - > dma_playback . ch_name = " tx " ;
pri_dai - > dma_capture . ch_name = " rx " ;
2010-11-22 09:36:59 +03:00
pri_dai - > dma_playback . dma_size = 4 ;
pri_dai - > dma_capture . dma_size = 4 ;
pri_dai - > quirks = quirks ;
2014-11-07 09:54:40 +03:00
pri_dai - > variant_regs = i2s_dai_data - > i2s_variant_regs ;
2010-11-22 09:36:59 +03:00
if ( quirks & QUIRK_PRI_6CHAN )
pri_dai - > i2s_dai_drv . playback . channels_max = 6 ;
if ( quirks & QUIRK_SEC_DAI ) {
sec_dai = i2s_alloc_dai ( pdev , true ) ;
if ( ! sec_dai ) {
dev_err ( & pdev - > dev , " Unable to alloc I2S_sec \n " ) ;
2015-01-14 21:42:30 +03:00
return - ENOMEM ;
2010-11-22 09:36:59 +03:00
}
2014-12-08 20:45:54 +03:00
2015-01-14 21:42:35 +03:00
sec_dai - > lock = & pri_dai - > spinlock ;
2014-12-08 20:45:54 +03:00
sec_dai - > variant_regs = pri_dai - > variant_regs ;
2010-11-22 09:36:59 +03:00
sec_dai - > dma_playback . dma_addr = regs_base + I2STXDS ;
2013-01-18 15:47:01 +04:00
sec_dai - > dma_playback . ch_name = " tx-sec " ;
if ( ! np ) {
res = platform_get_resource ( pdev , IORESOURCE_DMA , 2 ) ;
if ( res )
sec_dai - > dma_playback . channel = res - > start ;
}
2010-11-22 09:36:59 +03:00
sec_dai - > dma_playback . dma_size = 4 ;
2015-01-14 21:42:30 +03:00
sec_dai - > addr = pri_dai - > addr ;
2015-01-14 21:42:31 +03:00
sec_dai - > clk = pri_dai - > clk ;
2010-11-22 09:36:59 +03:00
sec_dai - > quirks = quirks ;
2013-01-18 15:47:01 +04:00
sec_dai - > idma_playback . dma_addr = idma_addr ;
2010-11-22 09:36:59 +03:00
sec_dai - > pri_dai = pri_dai ;
pri_dai - > sec_dai = sec_dai ;
}
2013-07-02 16:10:28 +04:00
if ( i2s_pdata & & i2s_pdata - > cfg_gpio & & i2s_pdata - > cfg_gpio ( pdev ) ) {
dev_err ( & pdev - > dev , " Unable to configure gpio \n " ) ;
2015-01-14 21:42:30 +03:00
return - EINVAL ;
2010-11-22 09:36:59 +03:00
}
2013-09-04 23:37:51 +04:00
devm_snd_soc_register_component ( & pri_dai - > pdev - > dev ,
& samsung_i2s_component ,
& pri_dai - > i2s_dai_drv , 1 ) ;
2010-11-22 09:36:59 +03:00
2011-12-08 12:45:03 +04:00
pm_runtime_enable ( & pdev - > dev ) ;
2015-01-14 21:42:29 +03:00
ret = samsung_asoc_dma_platform_register ( & pdev - > dev ) ;
if ( ret ! = 0 )
return ret ;
2012-12-07 12:29:21 +04:00
2015-01-14 21:42:39 +03:00
return i2s_register_clock_provider ( pdev ) ;
2010-11-22 09:36:59 +03:00
}
2012-12-07 18:26:15 +04:00
static int samsung_i2s_remove ( struct platform_device * pdev )
2010-11-22 09:36:59 +03:00
{
struct i2s_dai * i2s , * other ;
i2s = dev_get_drvdata ( & pdev - > dev ) ;
2015-01-14 21:42:33 +03:00
other = get_other_dai ( i2s ) ;
2010-11-22 09:36:59 +03:00
if ( other ) {
other - > pri_dai = NULL ;
other - > sec_dai = NULL ;
} else {
2011-12-08 12:45:03 +04:00
pm_runtime_disable ( & pdev - > dev ) ;
2010-11-22 09:36:59 +03:00
}
2015-01-14 21:42:39 +03:00
if ( ! is_secondary ( i2s ) ) {
i2s_unregister_clock_provider ( pdev ) ;
2015-01-14 21:42:32 +03:00
clk_disable_unprepare ( i2s - > clk ) ;
2015-01-14 21:42:39 +03:00
}
2015-01-14 21:42:32 +03:00
2010-11-22 09:36:59 +03:00
i2s - > pri_dai = NULL ;
i2s - > sec_dai = NULL ;
return 0 ;
}
2014-11-07 09:54:40 +03:00
static const struct samsung_i2s_variant_regs i2sv3_regs = {
. bfs_off = 1 ,
. rfs_off = 3 ,
. sdf_off = 5 ,
. txr_off = 8 ,
. rclksrc_off = 10 ,
. mss_off = 11 ,
. cdclkcon_off = 12 ,
. lrp_off = 7 ,
. bfs_mask = 0x3 ,
. rfs_mask = 0x3 ,
. ftx0cnt_off = 8 ,
} ;
static const struct samsung_i2s_variant_regs i2sv6_regs = {
. bfs_off = 0 ,
. rfs_off = 4 ,
. sdf_off = 6 ,
. txr_off = 8 ,
. rclksrc_off = 10 ,
. mss_off = 11 ,
. cdclkcon_off = 12 ,
. lrp_off = 15 ,
. bfs_mask = 0xf ,
. rfs_mask = 0x3 ,
. ftx0cnt_off = 8 ,
} ;
static const struct samsung_i2s_variant_regs i2sv7_regs = {
. bfs_off = 0 ,
. rfs_off = 4 ,
. sdf_off = 7 ,
. txr_off = 9 ,
. rclksrc_off = 11 ,
. mss_off = 12 ,
. cdclkcon_off = 22 ,
. lrp_off = 15 ,
. bfs_mask = 0xf ,
. rfs_mask = 0x7 ,
. ftx0cnt_off = 0 ,
} ;
static const struct samsung_i2s_variant_regs i2sv5_i2s1_regs = {
. bfs_off = 0 ,
. rfs_off = 3 ,
. sdf_off = 6 ,
. txr_off = 8 ,
. rclksrc_off = 10 ,
. mss_off = 11 ,
. cdclkcon_off = 12 ,
. lrp_off = 15 ,
. bfs_mask = 0x7 ,
. rfs_mask = 0x7 ,
. ftx0cnt_off = 8 ,
} ;
2013-08-12 13:49:51 +04:00
static const struct samsung_i2s_dai_data i2sv3_dai_type = {
. dai_type = TYPE_PRI ,
. quirks = QUIRK_NO_MUXPSR ,
2014-11-07 09:54:40 +03:00
. i2s_variant_regs = & i2sv3_regs ,
2013-08-12 13:49:51 +04:00
} ;
static const struct samsung_i2s_dai_data i2sv5_dai_type = {
. dai_type = TYPE_PRI ,
2014-11-07 09:54:39 +03:00
. quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR |
QUIRK_SUPPORTS_IDMA ,
2014-11-07 09:54:40 +03:00
. i2s_variant_regs = & i2sv3_regs ,
2013-08-12 13:49:51 +04:00
} ;
2013-08-12 13:49:52 +04:00
static const struct samsung_i2s_dai_data i2sv6_dai_type = {
. dai_type = TYPE_PRI ,
. quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR |
2014-11-07 09:54:39 +03:00
QUIRK_SUPPORTS_TDM | QUIRK_SUPPORTS_IDMA ,
2014-11-07 09:54:40 +03:00
. i2s_variant_regs = & i2sv6_regs ,
} ;
static const struct samsung_i2s_dai_data i2sv7_dai_type = {
. dai_type = TYPE_PRI ,
. quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR |
QUIRK_SUPPORTS_TDM ,
. i2s_variant_regs = & i2sv7_regs ,
} ;
static const struct samsung_i2s_dai_data i2sv5_dai_type_i2s1 = {
. dai_type = TYPE_PRI ,
. quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR ,
. i2s_variant_regs = & i2sv5_i2s1_regs ,
2013-08-12 13:49:52 +04:00
} ;
2013-08-12 13:49:51 +04:00
static const struct samsung_i2s_dai_data samsung_dai_type_pri = {
. dai_type = TYPE_PRI ,
} ;
static const struct samsung_i2s_dai_data samsung_dai_type_sec = {
. dai_type = TYPE_SEC ,
} ;
2013-01-18 15:47:00 +04:00
static struct platform_device_id samsung_i2s_driver_ids [ ] = {
{
. name = " samsung-i2s " ,
2014-12-05 22:56:17 +03:00
. driver_data = ( kernel_ulong_t ) & i2sv3_dai_type ,
2013-01-18 15:47:00 +04:00
} , {
. name = " samsung-i2s-sec " ,
2013-08-12 13:49:51 +04:00
. driver_data = ( kernel_ulong_t ) & samsung_dai_type_sec ,
2014-12-05 22:56:17 +03:00
} , {
. name = " samsung-i2sv4 " ,
. driver_data = ( kernel_ulong_t ) & i2sv5_dai_type ,
2013-01-18 15:47:00 +04:00
} ,
{ } ,
} ;
2013-04-11 04:05:03 +04:00
MODULE_DEVICE_TABLE ( platform , samsung_i2s_driver_ids ) ;
2013-01-18 15:47:00 +04:00
2013-01-18 15:47:01 +04:00
# ifdef CONFIG_OF
static const struct of_device_id exynos_i2s_match [ ] = {
2013-08-12 13:49:51 +04:00
{
. compatible = " samsung,s3c6410-i2s " ,
. data = & i2sv3_dai_type ,
} , {
. compatible = " samsung,s5pv210-i2s " ,
. data = & i2sv5_dai_type ,
2013-08-12 13:49:52 +04:00
} , {
. compatible = " samsung,exynos5420-i2s " ,
. data = & i2sv6_dai_type ,
2014-11-07 09:54:40 +03:00
} , {
. compatible = " samsung,exynos7-i2s " ,
. data = & i2sv7_dai_type ,
} , {
. compatible = " samsung,exynos7-i2s1 " ,
. data = & i2sv5_dai_type_i2s1 ,
2013-01-18 15:47:01 +04:00
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , exynos_i2s_match ) ;
# endif
2013-01-30 16:11:04 +04:00
static const struct dev_pm_ops samsung_i2s_pm = {
SET_RUNTIME_PM_OPS ( i2s_runtime_suspend ,
i2s_runtime_resume , NULL )
} ;
2010-11-22 09:36:59 +03:00
static struct platform_driver samsung_i2s_driver = {
. probe = samsung_i2s_probe ,
2012-12-07 18:26:15 +04:00
. remove = samsung_i2s_remove ,
2013-01-18 15:47:00 +04:00
. id_table = samsung_i2s_driver_ids ,
2010-11-22 09:36:59 +03:00
. driver = {
. name = " samsung-i2s " ,
2013-01-18 15:47:01 +04:00
. of_match_table = of_match_ptr ( exynos_i2s_match ) ,
2013-01-30 16:11:04 +04:00
. pm = & samsung_i2s_pm ,
2010-11-22 09:36:59 +03:00
} ,
} ;
2011-11-23 19:20:13 +04:00
module_platform_driver ( samsung_i2s_driver ) ;
2010-11-22 09:36:59 +03:00
/* Module information */
2012-02-25 14:54:36 +04:00
MODULE_AUTHOR ( " Jaswinder Singh, <jassisinghbrar@gmail.com> " ) ;
2010-11-22 09:36:59 +03:00
MODULE_DESCRIPTION ( " Samsung I2S Interface " ) ;
MODULE_ALIAS ( " platform:samsung-i2s " ) ;
MODULE_LICENSE ( " GPL " ) ;