2009-08-20 16:01:05 +04:00
/*
* Fifo - attached Serial Interface ( FSI ) support for SH7724
*
* Copyright ( C ) 2009 Renesas Solutions Corp .
* Kuninori Morimoto < morimoto . kuninori @ renesas . com >
*
* Based on ssi . c
* Copyright ( c ) 2007 Manuel Lauss < mano @ roarinelk . homelinux . net >
*
* 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 .
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/delay.h>
# include <linux/list.h>
2009-11-30 14:24:48 +03:00
# include <linux/pm_runtime.h>
2009-08-20 16:01:05 +04:00
# include <linux/io.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/initval.h>
# include <sound/soc.h>
# include <sound/pcm_params.h>
# include <sound/sh_fsi.h>
# include <asm/atomic.h>
# define DO_FMT 0x0000
# define DOFF_CTL 0x0004
# define DOFF_ST 0x0008
# define DI_FMT 0x000C
# define DIFF_CTL 0x0010
# define DIFF_ST 0x0014
# define CKG1 0x0018
# define CKG2 0x001C
# define DIDT 0x0020
# define DODT 0x0024
# define MUTE_ST 0x0028
# define REG_END MUTE_ST
# define INT_ST 0x0200
# define IEMSK 0x0204
# define IMSK 0x0208
# define MUTE 0x020C
# define CLK_RST 0x0210
# define SOFT_RST 0x0214
# define MREG_START INT_ST
# define MREG_END SOFT_RST
/* DO_FMT */
/* DI_FMT */
# define CR_FMT(param) ((param) << 4)
# define CR_MONO 0x0
# define CR_MONO_D 0x1
# define CR_PCM 0x2
# define CR_I2S 0x3
# define CR_TDM 0x4
# define CR_TDM_D 0x5
/* DOFF_CTL */
/* DIFF_CTL */
# define IRQ_HALF 0x00100000
# define FIFO_CLR 0x00000001
/* DOFF_ST */
# define ERR_OVER 0x00000010
# define ERR_UNDER 0x00000001
/* CLK_RST */
# define B_CLK 0x00000010
# define A_CLK 0x00000001
/* INT_ST */
# define INT_B_IN (1 << 12)
# define INT_B_OUT (1 << 8)
# define INT_A_IN (1 << 4)
# define INT_A_OUT (1 << 0)
# define FSI_RATES SNDRV_PCM_RATE_8000_96000
# define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
/************************************************************************
struct
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct fsi_priv {
void __iomem * base ;
struct snd_pcm_substream * substream ;
2009-12-02 09:11:08 +03:00
struct fsi_master * master ;
2009-08-20 16:01:05 +04:00
int fifo_max ;
int chan ;
int byte_offset ;
int period_len ;
int buffer_len ;
int periods ;
} ;
struct fsi_master {
void __iomem * base ;
int irq ;
struct fsi_priv fsia ;
struct fsi_priv fsib ;
struct sh_fsi_platform_info * info ;
} ;
/************************************************************************
basic read write function
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static int __fsi_reg_write ( u32 reg , u32 data )
{
/* valid data area is 24bit */
data & = 0x00ffffff ;
return ctrl_outl ( data , reg ) ;
}
static u32 __fsi_reg_read ( u32 reg )
{
return ctrl_inl ( reg ) ;
}
static int __fsi_reg_mask_set ( u32 reg , u32 mask , u32 data )
{
u32 val = __fsi_reg_read ( reg ) ;
val & = ~ mask ;
val | = data & mask ;
return __fsi_reg_write ( reg , val ) ;
}
static int fsi_reg_write ( struct fsi_priv * fsi , u32 reg , u32 data )
{
if ( reg > REG_END )
return - 1 ;
return __fsi_reg_write ( ( u32 ) ( fsi - > base + reg ) , data ) ;
}
static u32 fsi_reg_read ( struct fsi_priv * fsi , u32 reg )
{
if ( reg > REG_END )
return 0 ;
return __fsi_reg_read ( ( u32 ) ( fsi - > base + reg ) ) ;
}
static int fsi_reg_mask_set ( struct fsi_priv * fsi , u32 reg , u32 mask , u32 data )
{
if ( reg > REG_END )
return - 1 ;
return __fsi_reg_mask_set ( ( u32 ) ( fsi - > base + reg ) , mask , data ) ;
}
2009-12-02 09:11:08 +03:00
static int fsi_master_write ( struct fsi_master * master , u32 reg , u32 data )
2009-08-20 16:01:05 +04:00
{
if ( ( reg < MREG_START ) | |
( reg > MREG_END ) )
return - 1 ;
return __fsi_reg_write ( ( u32 ) ( master - > base + reg ) , data ) ;
}
2009-12-02 09:11:08 +03:00
static u32 fsi_master_read ( struct fsi_master * master , u32 reg )
2009-08-20 16:01:05 +04:00
{
if ( ( reg < MREG_START ) | |
( reg > MREG_END ) )
return 0 ;
return __fsi_reg_read ( ( u32 ) ( master - > base + reg ) ) ;
}
2009-12-02 09:11:08 +03:00
static int fsi_master_mask_set ( struct fsi_master * master ,
u32 reg , u32 mask , u32 data )
2009-08-20 16:01:05 +04:00
{
if ( ( reg < MREG_START ) | |
( reg > MREG_END ) )
return - 1 ;
return __fsi_reg_mask_set ( ( u32 ) ( master - > base + reg ) , mask , data ) ;
}
/************************************************************************
basic function
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-12-02 09:11:08 +03:00
static struct fsi_master * fsi_get_master ( struct fsi_priv * fsi )
2009-08-20 16:01:05 +04:00
{
2009-12-02 09:11:08 +03:00
return fsi - > master ;
2009-08-20 16:01:05 +04:00
}
static int fsi_is_port_a ( struct fsi_priv * fsi )
{
2009-12-02 09:11:08 +03:00
return fsi - > master - > base = = fsi - > base ;
}
2009-08-20 16:01:05 +04:00
2009-12-02 09:11:08 +03:00
static struct fsi_priv * fsi_get_priv ( struct snd_pcm_substream * substream )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_dai_link * machine = rtd - > dai ;
struct snd_soc_dai * dai = machine - > cpu_dai ;
2009-08-20 16:01:05 +04:00
2009-12-02 09:11:08 +03:00
return dai - > private_data ;
2009-08-20 16:01:05 +04:00
}
static u32 fsi_get_info_flags ( struct fsi_priv * fsi )
{
int is_porta = fsi_is_port_a ( fsi ) ;
2009-12-02 09:11:08 +03:00
struct fsi_master * master = fsi_get_master ( fsi ) ;
2009-08-20 16:01:05 +04:00
return is_porta ? master - > info - > porta_flags :
master - > info - > portb_flags ;
}
static int fsi_is_master_mode ( struct fsi_priv * fsi , int is_play )
{
u32 mode ;
u32 flags = fsi_get_info_flags ( fsi ) ;
mode = is_play ? SH_FSI_OUT_SLAVE_MODE : SH_FSI_IN_SLAVE_MODE ;
/* return
* 1 : master mode
* 0 : slave mode
*/
return ( mode & flags ) ! = mode ;
}
static u32 fsi_port_ab_io_bit ( struct fsi_priv * fsi , int is_play )
{
int is_porta = fsi_is_port_a ( fsi ) ;
u32 data ;
if ( is_porta )
data = is_play ? ( 1 < < 0 ) : ( 1 < < 4 ) ;
else
data = is_play ? ( 1 < < 8 ) : ( 1 < < 12 ) ;
return data ;
}
static void fsi_stream_push ( struct fsi_priv * fsi ,
struct snd_pcm_substream * substream ,
u32 buffer_len ,
u32 period_len )
{
fsi - > substream = substream ;
fsi - > buffer_len = buffer_len ;
fsi - > period_len = period_len ;
fsi - > byte_offset = 0 ;
fsi - > periods = 0 ;
}
static void fsi_stream_pop ( struct fsi_priv * fsi )
{
fsi - > substream = NULL ;
fsi - > buffer_len = 0 ;
fsi - > period_len = 0 ;
fsi - > byte_offset = 0 ;
fsi - > periods = 0 ;
}
static int fsi_get_fifo_residue ( struct fsi_priv * fsi , int is_play )
{
u32 status ;
u32 reg = is_play ? DOFF_ST : DIFF_ST ;
int residue ;
status = fsi_reg_read ( fsi , reg ) ;
residue = 0x1ff & ( status > > 8 ) ;
residue * = fsi - > chan ;
return residue ;
}
/************************************************************************
ctrl function
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void fsi_irq_enable ( struct fsi_priv * fsi , int is_play )
{
u32 data = fsi_port_ab_io_bit ( fsi , is_play ) ;
2009-12-02 09:11:08 +03:00
struct fsi_master * master = fsi_get_master ( fsi ) ;
2009-08-20 16:01:05 +04:00
2009-12-02 09:11:08 +03:00
fsi_master_mask_set ( master , IMSK , data , data ) ;
fsi_master_mask_set ( master , IEMSK , data , data ) ;
2009-08-20 16:01:05 +04:00
}
static void fsi_irq_disable ( struct fsi_priv * fsi , int is_play )
{
u32 data = fsi_port_ab_io_bit ( fsi , is_play ) ;
2009-12-02 09:11:08 +03:00
struct fsi_master * master = fsi_get_master ( fsi ) ;
2009-08-20 16:01:05 +04:00
2009-12-02 09:11:08 +03:00
fsi_master_mask_set ( master , IMSK , data , 0 ) ;
fsi_master_mask_set ( master , IEMSK , data , 0 ) ;
2009-08-20 16:01:05 +04:00
}
static void fsi_clk_ctrl ( struct fsi_priv * fsi , int enable )
{
u32 val = fsi_is_port_a ( fsi ) ? ( 1 < < 0 ) : ( 1 < < 4 ) ;
2009-12-02 09:11:08 +03:00
struct fsi_master * master = fsi_get_master ( fsi ) ;
2009-08-20 16:01:05 +04:00
if ( enable )
2009-12-02 09:11:08 +03:00
fsi_master_mask_set ( master , CLK_RST , val , val ) ;
2009-08-20 16:01:05 +04:00
else
2009-12-02 09:11:08 +03:00
fsi_master_mask_set ( master , CLK_RST , val , 0 ) ;
2009-08-20 16:01:05 +04:00
}
static void fsi_irq_init ( struct fsi_priv * fsi , int is_play )
{
u32 data ;
u32 ctrl ;
data = fsi_port_ab_io_bit ( fsi , is_play ) ;
ctrl = is_play ? DOFF_CTL : DIFF_CTL ;
/* set IMSK */
fsi_irq_disable ( fsi , is_play ) ;
/* set interrupt generation factor */
fsi_reg_write ( fsi , ctrl , IRQ_HALF ) ;
/* clear FIFO */
fsi_reg_mask_set ( fsi , ctrl , FIFO_CLR , FIFO_CLR ) ;
/* clear interrupt factor */
2009-12-02 09:11:08 +03:00
fsi_master_mask_set ( fsi_get_master ( fsi ) , INT_ST , data , 0 ) ;
2009-08-20 16:01:05 +04:00
}
2009-12-02 09:11:08 +03:00
static void fsi_soft_all_reset ( struct fsi_master * master )
2009-08-20 16:01:05 +04:00
{
2009-12-02 09:11:08 +03:00
u32 status = fsi_master_read ( master , SOFT_RST ) ;
2009-08-20 16:01:05 +04:00
/* port AB reset */
status & = 0x000000ff ;
2009-12-02 09:11:08 +03:00
fsi_master_write ( master , SOFT_RST , status ) ;
2009-08-20 16:01:05 +04:00
mdelay ( 10 ) ;
/* soft reset */
status & = 0x000000f0 ;
2009-12-02 09:11:08 +03:00
fsi_master_write ( master , SOFT_RST , status ) ;
2009-08-20 16:01:05 +04:00
status | = 0x00000001 ;
2009-12-02 09:11:08 +03:00
fsi_master_write ( master , SOFT_RST , status ) ;
2009-08-20 16:01:05 +04:00
mdelay ( 10 ) ;
}
/* playback interrupt */
static int fsi_data_push ( struct fsi_priv * fsi )
{
struct snd_pcm_runtime * runtime ;
struct snd_pcm_substream * substream = NULL ;
int send ;
int fifo_free ;
int width ;
2009-10-30 06:02:39 +03:00
u8 * start ;
2009-12-28 08:09:05 +03:00
int i , over_period ;
2009-08-20 16:01:05 +04:00
if ( ! fsi | |
! fsi - > substream | |
! fsi - > substream - > runtime )
return - EINVAL ;
2009-12-28 08:09:05 +03:00
over_period = 0 ;
substream = fsi - > substream ;
runtime = substream - > runtime ;
2009-08-20 16:01:05 +04:00
/* FSI FIFO has limit.
* So , this driver can not send periods data at a time
*/
if ( fsi - > byte_offset > =
fsi - > period_len * ( fsi - > periods + 1 ) ) {
2009-12-28 08:09:05 +03:00
over_period = 1 ;
2009-08-20 16:01:05 +04:00
fsi - > periods = ( fsi - > periods + 1 ) % runtime - > periods ;
if ( 0 = = fsi - > periods )
fsi - > byte_offset = 0 ;
}
/* get 1 channel data width */
width = frames_to_bytes ( runtime , 1 ) / fsi - > chan ;
/* get send size for alsa */
send = ( fsi - > buffer_len - fsi - > byte_offset ) / width ;
/* get FIFO free size */
fifo_free = ( fsi - > fifo_max * fsi - > chan ) - fsi_get_fifo_residue ( fsi , 1 ) ;
/* size check */
if ( fifo_free < send )
send = fifo_free ;
2009-10-30 06:02:39 +03:00
start = runtime - > dma_area ;
start + = fsi - > byte_offset ;
switch ( width ) {
case 2 :
for ( i = 0 ; i < send ; i + + )
fsi_reg_write ( fsi , DODT ,
( ( u32 ) * ( ( u16 * ) start + i ) < < 8 ) ) ;
break ;
case 4 :
for ( i = 0 ; i < send ; i + + )
fsi_reg_write ( fsi , DODT , * ( ( u32 * ) start + i ) ) ;
break ;
default :
2009-08-20 16:01:05 +04:00
return - EINVAL ;
2009-10-30 06:02:39 +03:00
}
2009-08-20 16:01:05 +04:00
fsi - > byte_offset + = send * width ;
fsi_irq_enable ( fsi , 1 ) ;
2009-12-28 08:09:05 +03:00
if ( over_period )
2009-08-20 16:01:05 +04:00
snd_pcm_period_elapsed ( substream ) ;
return 0 ;
}
2009-10-30 06:02:44 +03:00
static int fsi_data_pop ( struct fsi_priv * fsi )
{
struct snd_pcm_runtime * runtime ;
struct snd_pcm_substream * substream = NULL ;
int free ;
int fifo_fill ;
int width ;
u8 * start ;
2009-12-28 08:09:05 +03:00
int i , over_period ;
2009-10-30 06:02:44 +03:00
if ( ! fsi | |
! fsi - > substream | |
! fsi - > substream - > runtime )
return - EINVAL ;
2009-12-28 08:09:05 +03:00
over_period = 0 ;
substream = fsi - > substream ;
runtime = substream - > runtime ;
2009-10-30 06:02:44 +03:00
/* FSI FIFO has limit.
* So , this driver can not send periods data at a time
*/
if ( fsi - > byte_offset > =
fsi - > period_len * ( fsi - > periods + 1 ) ) {
2009-12-28 08:09:05 +03:00
over_period = 1 ;
2009-10-30 06:02:44 +03:00
fsi - > periods = ( fsi - > periods + 1 ) % runtime - > periods ;
if ( 0 = = fsi - > periods )
fsi - > byte_offset = 0 ;
}
/* get 1 channel data width */
width = frames_to_bytes ( runtime , 1 ) / fsi - > chan ;
/* get free space for alsa */
free = ( fsi - > buffer_len - fsi - > byte_offset ) / width ;
/* get recv size */
fifo_fill = fsi_get_fifo_residue ( fsi , 0 ) ;
if ( free < fifo_fill )
fifo_fill = free ;
start = runtime - > dma_area ;
start + = fsi - > byte_offset ;
switch ( width ) {
case 2 :
for ( i = 0 ; i < fifo_fill ; i + + )
* ( ( u16 * ) start + i ) =
( u16 ) ( fsi_reg_read ( fsi , DIDT ) > > 8 ) ;
break ;
case 4 :
for ( i = 0 ; i < fifo_fill ; i + + )
* ( ( u32 * ) start + i ) = fsi_reg_read ( fsi , DIDT ) ;
break ;
default :
return - EINVAL ;
}
fsi - > byte_offset + = fifo_fill * width ;
fsi_irq_enable ( fsi , 0 ) ;
2009-12-28 08:09:05 +03:00
if ( over_period )
2009-10-30 06:02:44 +03:00
snd_pcm_period_elapsed ( substream ) ;
return 0 ;
}
2009-08-20 16:01:05 +04:00
static irqreturn_t fsi_interrupt ( int irq , void * data )
{
2009-12-02 09:11:08 +03:00
struct fsi_master * master = data ;
u32 status = fsi_master_read ( master , SOFT_RST ) & ~ 0x00000010 ;
u32 int_st = fsi_master_read ( master , INT_ST ) ;
2009-08-20 16:01:05 +04:00
/* clear irq status */
2009-12-02 09:11:08 +03:00
fsi_master_write ( master , SOFT_RST , status ) ;
fsi_master_write ( master , SOFT_RST , status | 0x00000010 ) ;
2009-08-20 16:01:05 +04:00
if ( int_st & INT_A_OUT )
fsi_data_push ( & master - > fsia ) ;
if ( int_st & INT_B_OUT )
fsi_data_push ( & master - > fsib ) ;
2009-10-30 06:02:44 +03:00
if ( int_st & INT_A_IN )
fsi_data_pop ( & master - > fsia ) ;
if ( int_st & INT_B_IN )
fsi_data_pop ( & master - > fsib ) ;
2009-08-20 16:01:05 +04:00
2009-12-02 09:11:08 +03:00
fsi_master_write ( master , INT_ST , 0x0000000 ) ;
2009-08-20 16:01:05 +04:00
return IRQ_HANDLED ;
}
/************************************************************************
dai ops
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static int fsi_dai_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
2009-12-02 09:11:08 +03:00
struct fsi_priv * fsi = fsi_get_priv ( substream ) ;
2009-08-20 16:01:05 +04:00
const char * msg ;
u32 flags = fsi_get_info_flags ( fsi ) ;
u32 fmt ;
u32 reg ;
u32 data ;
int is_play = ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) ;
int is_master ;
int ret = 0 ;
2009-11-30 14:24:48 +03:00
pm_runtime_get_sync ( dai - > dev ) ;
2009-08-20 16:01:05 +04:00
/* CKG1 */
data = is_play ? ( 1 < < 0 ) : ( 1 < < 4 ) ;
is_master = fsi_is_master_mode ( fsi , is_play ) ;
if ( is_master )
fsi_reg_mask_set ( fsi , CKG1 , data , data ) ;
else
fsi_reg_mask_set ( fsi , CKG1 , data , 0 ) ;
/* clock inversion (CKG2) */
data = 0 ;
switch ( SH_FSI_INVERSION_MASK & flags ) {
case SH_FSI_LRM_INV :
data = 1 < < 12 ;
break ;
case SH_FSI_BRM_INV :
data = 1 < < 8 ;
break ;
case SH_FSI_LRS_INV :
data = 1 < < 4 ;
break ;
case SH_FSI_BRS_INV :
data = 1 < < 0 ;
break ;
}
fsi_reg_write ( fsi , CKG2 , data ) ;
/* do fmt, di fmt */
data = 0 ;
reg = is_play ? DO_FMT : DI_FMT ;
fmt = is_play ? SH_FSI_GET_OFMT ( flags ) : SH_FSI_GET_IFMT ( flags ) ;
switch ( fmt ) {
case SH_FSI_FMT_MONO :
msg = " MONO " ;
data = CR_FMT ( CR_MONO ) ;
fsi - > chan = 1 ;
break ;
case SH_FSI_FMT_MONO_DELAY :
msg = " MONO Delay " ;
data = CR_FMT ( CR_MONO_D ) ;
fsi - > chan = 1 ;
break ;
case SH_FSI_FMT_PCM :
msg = " PCM " ;
data = CR_FMT ( CR_PCM ) ;
fsi - > chan = 2 ;
break ;
case SH_FSI_FMT_I2S :
msg = " I2S " ;
data = CR_FMT ( CR_I2S ) ;
fsi - > chan = 2 ;
break ;
case SH_FSI_FMT_TDM :
msg = " TDM " ;
data = CR_FMT ( CR_TDM ) | ( fsi - > chan - 1 ) ;
fsi - > chan = is_play ?
SH_FSI_GET_CH_O ( flags ) : SH_FSI_GET_CH_I ( flags ) ;
break ;
case SH_FSI_FMT_TDM_DELAY :
msg = " TDM Delay " ;
data = CR_FMT ( CR_TDM_D ) | ( fsi - > chan - 1 ) ;
fsi - > chan = is_play ?
SH_FSI_GET_CH_O ( flags ) : SH_FSI_GET_CH_I ( flags ) ;
break ;
default :
dev_err ( dai - > dev , " unknown format. \n " ) ;
return - EINVAL ;
}
switch ( fsi - > chan ) {
case 1 :
fsi - > fifo_max = 256 ;
break ;
case 2 :
fsi - > fifo_max = 128 ;
break ;
case 3 :
case 4 :
fsi - > fifo_max = 64 ;
break ;
case 5 :
case 6 :
case 7 :
case 8 :
fsi - > fifo_max = 32 ;
break ;
default :
dev_err ( dai - > dev , " channel size error. \n " ) ;
return - EINVAL ;
}
fsi_reg_write ( fsi , reg , data ) ;
/*
* clear clk reset if master mode
*/
if ( is_master )
fsi_clk_ctrl ( fsi , 1 ) ;
/* irq setting */
fsi_irq_init ( fsi , is_play ) ;
return ret ;
}
static void fsi_dai_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
2009-12-02 09:11:08 +03:00
struct fsi_priv * fsi = fsi_get_priv ( substream ) ;
2009-08-20 16:01:05 +04:00
int is_play = substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ;
fsi_irq_disable ( fsi , is_play ) ;
fsi_clk_ctrl ( fsi , 0 ) ;
2009-11-30 14:24:48 +03:00
pm_runtime_put_sync ( dai - > dev ) ;
2009-08-20 16:01:05 +04:00
}
static int fsi_dai_trigger ( struct snd_pcm_substream * substream , int cmd ,
struct snd_soc_dai * dai )
{
2009-12-02 09:11:08 +03:00
struct fsi_priv * fsi = fsi_get_priv ( substream ) ;
2009-08-20 16:01:05 +04:00
struct snd_pcm_runtime * runtime = substream - > runtime ;
int is_play = substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ;
int ret = 0 ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
fsi_stream_push ( fsi , substream ,
frames_to_bytes ( runtime , runtime - > buffer_size ) ,
frames_to_bytes ( runtime , runtime - > period_size ) ) ;
2009-10-30 06:02:44 +03:00
ret = is_play ? fsi_data_push ( fsi ) : fsi_data_pop ( fsi ) ;
2009-08-20 16:01:05 +04:00
break ;
case SNDRV_PCM_TRIGGER_STOP :
fsi_irq_disable ( fsi , is_play ) ;
fsi_stream_pop ( fsi ) ;
break ;
}
return ret ;
}
static struct snd_soc_dai_ops fsi_dai_ops = {
. startup = fsi_dai_startup ,
. shutdown = fsi_dai_shutdown ,
. trigger = fsi_dai_trigger ,
} ;
/************************************************************************
pcm ops
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static struct snd_pcm_hardware fsi_pcm_hardware = {
. info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE ,
. formats = FSI_FMTS ,
. rates = FSI_RATES ,
. rate_min = 8000 ,
. rate_max = 192000 ,
. channels_min = 1 ,
. channels_max = 2 ,
. buffer_bytes_max = 64 * 1024 ,
. period_bytes_min = 32 ,
. period_bytes_max = 8192 ,
. periods_min = 1 ,
. periods_max = 32 ,
. fifo_size = 256 ,
} ;
static int fsi_pcm_open ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
int ret = 0 ;
snd_soc_set_runtime_hwparams ( substream , & fsi_pcm_hardware ) ;
ret = snd_pcm_hw_constraint_integer ( runtime ,
SNDRV_PCM_HW_PARAM_PERIODS ) ;
return ret ;
}
static int fsi_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * hw_params )
{
return snd_pcm_lib_malloc_pages ( substream ,
params_buffer_bytes ( hw_params ) ) ;
}
static int fsi_hw_free ( struct snd_pcm_substream * substream )
{
return snd_pcm_lib_free_pages ( substream ) ;
}
static snd_pcm_uframes_t fsi_pointer ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
2009-12-02 09:11:08 +03:00
struct fsi_priv * fsi = fsi_get_priv ( substream ) ;
2009-08-20 16:01:05 +04:00
long location ;
2009-10-30 06:02:39 +03:00
location = ( fsi - > byte_offset - 1 ) ;
2009-08-20 16:01:05 +04:00
if ( location < 0 )
location = 0 ;
return bytes_to_frames ( runtime , location ) ;
}
static struct snd_pcm_ops fsi_pcm_ops = {
. open = fsi_pcm_open ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = fsi_hw_params ,
. hw_free = fsi_hw_free ,
. pointer = fsi_pointer ,
} ;
/************************************************************************
snd_soc_platform
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define PREALLOC_BUFFER (32 * 1024)
# define PREALLOC_BUFFER_MAX (32 * 1024)
static void fsi_pcm_free ( struct snd_pcm * pcm )
{
snd_pcm_lib_preallocate_free_for_all ( pcm ) ;
}
static int fsi_pcm_new ( struct snd_card * card ,
struct snd_soc_dai * dai ,
struct snd_pcm * pcm )
{
/*
* dont use SNDRV_DMA_TYPE_DEV , since it will oops the SH kernel
* in MMAP mode ( i . e . aplay - M )
*/
return snd_pcm_lib_preallocate_pages_for_all (
pcm ,
SNDRV_DMA_TYPE_CONTINUOUS ,
snd_dma_continuous_data ( GFP_KERNEL ) ,
PREALLOC_BUFFER , PREALLOC_BUFFER_MAX ) ;
}
/************************************************************************
alsa struct
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct snd_soc_dai fsi_soc_dai [ ] = {
{
. name = " FSIA " ,
. id = 0 ,
. playback = {
. rates = FSI_RATES ,
. formats = FSI_FMTS ,
. channels_min = 1 ,
. channels_max = 8 ,
} ,
2009-10-30 06:02:44 +03:00
. capture = {
. rates = FSI_RATES ,
. formats = FSI_FMTS ,
. channels_min = 1 ,
. channels_max = 8 ,
} ,
2009-08-20 16:01:05 +04:00
. ops = & fsi_dai_ops ,
} ,
{
. name = " FSIB " ,
. id = 1 ,
. playback = {
. rates = FSI_RATES ,
. formats = FSI_FMTS ,
. channels_min = 1 ,
. channels_max = 8 ,
} ,
2009-10-30 06:02:44 +03:00
. capture = {
. rates = FSI_RATES ,
. formats = FSI_FMTS ,
. channels_min = 1 ,
. channels_max = 8 ,
} ,
2009-08-20 16:01:05 +04:00
. ops = & fsi_dai_ops ,
} ,
} ;
EXPORT_SYMBOL_GPL ( fsi_soc_dai ) ;
struct snd_soc_platform fsi_soc_platform = {
. name = " fsi-pcm " ,
. pcm_ops = & fsi_pcm_ops ,
. pcm_new = fsi_pcm_new ,
. pcm_free = fsi_pcm_free ,
} ;
EXPORT_SYMBOL_GPL ( fsi_soc_platform ) ;
/************************************************************************
platform function
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static int fsi_probe ( struct platform_device * pdev )
{
2009-12-02 09:11:08 +03:00
struct fsi_master * master ;
2009-08-20 16:01:05 +04:00
struct resource * res ;
unsigned int irq ;
int ret ;
2009-12-02 09:11:08 +03:00
if ( 0 ! = pdev - > id ) {
dev_err ( & pdev - > dev , " current fsi support id 0 only now \n " ) ;
return - ENODEV ;
}
2009-08-20 16:01:05 +04:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( ! res | | ! irq ) {
dev_err ( & pdev - > dev , " Not enough FSI platform resources. \n " ) ;
ret = - ENODEV ;
goto exit ;
}
master = kzalloc ( sizeof ( * master ) , GFP_KERNEL ) ;
if ( ! master ) {
dev_err ( & pdev - > dev , " Could not allocate master \n " ) ;
ret = - ENOMEM ;
goto exit ;
}
master - > base = ioremap_nocache ( res - > start , resource_size ( res ) ) ;
if ( ! master - > base ) {
ret = - ENXIO ;
dev_err ( & pdev - > dev , " Unable to ioremap FSI registers. \n " ) ;
goto exit_kfree ;
}
master - > irq = irq ;
master - > info = pdev - > dev . platform_data ;
master - > fsia . base = master - > base ;
2009-12-02 09:11:08 +03:00
master - > fsia . master = master ;
2009-08-20 16:01:05 +04:00
master - > fsib . base = master - > base + 0x40 ;
2009-12-02 09:11:08 +03:00
master - > fsib . master = master ;
2009-08-20 16:01:05 +04:00
2009-11-30 14:24:48 +03:00
pm_runtime_enable ( & pdev - > dev ) ;
pm_runtime_resume ( & pdev - > dev ) ;
2009-08-20 16:01:05 +04:00
fsi_soc_dai [ 0 ] . dev = & pdev - > dev ;
2009-12-02 09:11:08 +03:00
fsi_soc_dai [ 0 ] . private_data = & master - > fsia ;
2009-08-20 16:01:05 +04:00
fsi_soc_dai [ 1 ] . dev = & pdev - > dev ;
2009-12-02 09:11:08 +03:00
fsi_soc_dai [ 1 ] . private_data = & master - > fsib ;
2009-08-20 16:01:05 +04:00
2009-12-02 09:11:08 +03:00
fsi_soft_all_reset ( master ) ;
2009-08-20 16:01:05 +04:00
ret = request_irq ( irq , & fsi_interrupt , IRQF_DISABLED , " fsi " , master ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " irq request err \n " ) ;
2009-10-30 06:02:39 +03:00
goto exit_iounmap ;
2009-08-20 16:01:05 +04:00
}
ret = snd_soc_register_platform ( & fsi_soc_platform ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " cannot snd soc register \n " ) ;
goto exit_free_irq ;
}
return snd_soc_register_dais ( fsi_soc_dai , ARRAY_SIZE ( fsi_soc_dai ) ) ;
exit_free_irq :
free_irq ( irq , master ) ;
exit_iounmap :
iounmap ( master - > base ) ;
2009-11-30 14:24:48 +03:00
pm_runtime_disable ( & pdev - > dev ) ;
2009-08-20 16:01:05 +04:00
exit_kfree :
kfree ( master ) ;
master = NULL ;
exit :
return ret ;
}
static int fsi_remove ( struct platform_device * pdev )
{
2009-12-02 09:11:08 +03:00
struct fsi_master * master ;
master = fsi_get_master ( fsi_soc_dai [ 0 ] . private_data ) ;
2009-08-20 16:01:05 +04:00
snd_soc_unregister_dais ( fsi_soc_dai , ARRAY_SIZE ( fsi_soc_dai ) ) ;
snd_soc_unregister_platform ( & fsi_soc_platform ) ;
2009-11-30 14:24:48 +03:00
pm_runtime_disable ( & pdev - > dev ) ;
2009-08-20 16:01:05 +04:00
free_irq ( master - > irq , master ) ;
iounmap ( master - > base ) ;
kfree ( master ) ;
2009-12-02 09:11:08 +03:00
fsi_soc_dai [ 0 ] . dev = NULL ;
fsi_soc_dai [ 0 ] . private_data = NULL ;
fsi_soc_dai [ 1 ] . dev = NULL ;
fsi_soc_dai [ 1 ] . private_data = NULL ;
2009-08-20 16:01:05 +04:00
return 0 ;
}
2009-11-30 14:24:48 +03:00
static int fsi_runtime_nop ( struct device * dev )
{
/* Runtime PM callback shared between ->runtime_suspend()
* and - > runtime_resume ( ) . Simply returns success .
*
* This driver re - initializes all registers after
* pm_runtime_get_sync ( ) anyway so there is no need
* to save and restore registers here .
*/
return 0 ;
}
static struct dev_pm_ops fsi_pm_ops = {
. runtime_suspend = fsi_runtime_nop ,
. runtime_resume = fsi_runtime_nop ,
} ;
2009-08-20 16:01:05 +04:00
static struct platform_driver fsi_driver = {
. driver = {
. name = " sh_fsi " ,
2009-11-30 14:24:48 +03:00
. pm = & fsi_pm_ops ,
2009-08-20 16:01:05 +04:00
} ,
. probe = fsi_probe ,
. remove = fsi_remove ,
} ;
static int __init fsi_mobile_init ( void )
{
return platform_driver_register ( & fsi_driver ) ;
}
static void __exit fsi_mobile_exit ( void )
{
platform_driver_unregister ( & fsi_driver ) ;
}
module_init ( fsi_mobile_init ) ;
module_exit ( fsi_mobile_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " SuperH onchip FSI audio driver " ) ;
MODULE_AUTHOR ( " Kuninori Morimoto <morimoto.kuninori@renesas.com> " ) ;