2008-09-05 18:21:37 +08:00
/*
* bf5xx - ac97 . c - - AC97 support for the ADI blackfin chip .
*
* Author : Roy Huang
* Created : 11 th . June 2007
* Copyright : Analog Device Inc .
*
* 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/interrupt.h>
# include <linux/wait.h>
# include <linux/delay.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/ac97_codec.h>
# include <sound/initval.h>
# include <sound/soc.h>
# include <asm/irq.h>
# include <asm/portmux.h>
# include <linux/mutex.h>
# include <linux/gpio.h>
# include "bf5xx-sport.h"
# include "bf5xx-ac97.h"
static int * cmd_count ;
static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM ;
2009-02-06 18:12:34 +08:00
# define SPORT_REQ(x) \
[ x ] = { P_SPORT # # x # # _TFS , P_SPORT # # x # # _DTPRI , P_SPORT # # x # # _TSCLK , \
P_SPORT # # x # # _RFS , P_SPORT # # x # # _DRPRI , P_SPORT # # x # # _RSCLK , 0 }
2008-11-18 16:18:17 +08:00
static u16 sport_req [ ] [ 7 ] = {
2009-02-06 18:12:34 +08:00
# ifdef SPORT0_TCR1
SPORT_REQ ( 0 ) ,
2008-11-18 16:18:17 +08:00
# endif
2009-02-06 18:12:34 +08:00
# ifdef SPORT1_TCR1
SPORT_REQ ( 1 ) ,
2008-11-18 16:18:17 +08:00
# endif
2009-02-06 18:12:34 +08:00
# ifdef SPORT2_TCR1
SPORT_REQ ( 2 ) ,
2008-11-18 16:18:17 +08:00
# endif
2009-02-06 18:12:34 +08:00
# ifdef SPORT3_TCR1
SPORT_REQ ( 3 ) ,
# endif
} ;
2008-11-18 16:18:17 +08:00
2009-02-06 18:12:34 +08:00
# define SPORT_PARAMS(x) \
[ x ] = { \
. dma_rx_chan = CH_SPORT # # x # # _RX , \
. dma_tx_chan = CH_SPORT # # x # # _TX , \
. err_irq = IRQ_SPORT # # x # # _ERROR , \
. regs = ( struct sport_register * ) SPORT # # x # # _TCR1 , \
}
2008-09-05 18:21:37 +08:00
static struct sport_param sport_params [ 4 ] = {
2009-02-06 18:12:34 +08:00
# ifdef SPORT0_TCR1
SPORT_PARAMS ( 0 ) ,
2008-11-18 16:18:17 +08:00
# endif
2009-02-06 18:12:34 +08:00
# ifdef SPORT1_TCR1
SPORT_PARAMS ( 1 ) ,
2008-11-18 16:18:17 +08:00
# endif
2009-02-06 18:12:34 +08:00
# ifdef SPORT2_TCR1
SPORT_PARAMS ( 2 ) ,
# endif
# ifdef SPORT3_TCR1
SPORT_PARAMS ( 3 ) ,
2008-09-05 18:21:37 +08:00
# endif
2008-11-18 16:18:17 +08:00
} ;
2008-09-05 18:21:37 +08:00
2008-11-18 16:18:17 +08:00
void bf5xx_pcm_to_ac97 ( struct ac97_frame * dst , const __u16 * src ,
size_t count , unsigned int chan_mask )
2008-09-05 18:21:37 +08:00
{
while ( count - - ) {
2008-11-18 16:18:17 +08:00
dst - > ac97_tag = TAG_VALID ;
if ( chan_mask & SP_FL ) {
dst - > ac97_pcm_r = * src + + ;
dst - > ac97_tag | = TAG_PCM_RIGHT ;
}
if ( chan_mask & SP_FR ) {
dst - > ac97_pcm_l = * src + + ;
dst - > ac97_tag | = TAG_PCM_LEFT ;
}
# if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
if ( chan_mask & SP_SR ) {
dst - > ac97_sl = * src + + ;
dst - > ac97_tag | = TAG_PCM_SL ;
}
if ( chan_mask & SP_SL ) {
dst - > ac97_sr = * src + + ;
dst - > ac97_tag | = TAG_PCM_SR ;
}
if ( chan_mask & SP_LFE ) {
dst - > ac97_lfe = * src + + ;
dst - > ac97_tag | = TAG_PCM_LFE ;
}
if ( chan_mask & SP_FC ) {
dst - > ac97_center = * src + + ;
dst - > ac97_tag | = TAG_PCM_CENTER ;
}
# endif
dst + + ;
2008-09-05 18:21:37 +08:00
}
}
EXPORT_SYMBOL ( bf5xx_pcm_to_ac97 ) ;
2008-11-18 16:18:17 +08:00
void bf5xx_ac97_to_pcm ( const struct ac97_frame * src , __u16 * dst ,
2008-09-05 18:21:37 +08:00
size_t count )
{
2008-11-18 16:18:17 +08:00
while ( count - - ) {
* ( dst + + ) = src - > ac97_pcm_l ;
* ( dst + + ) = src - > ac97_pcm_r ;
src + + ;
}
2008-09-05 18:21:37 +08:00
}
EXPORT_SYMBOL ( bf5xx_ac97_to_pcm ) ;
static unsigned int sport_tx_curr_frag ( struct sport_device * sport )
{
2008-11-18 16:18:17 +08:00
return sport - > tx_curr_frag = sport_curr_offset_tx ( sport ) /
2008-09-05 18:21:37 +08:00
sport - > tx_fragsize ;
}
static void enqueue_cmd ( struct snd_ac97 * ac97 , __u16 addr , __u16 data )
{
struct sport_device * sport = sport_handle ;
int nextfrag = sport_tx_curr_frag ( sport ) ;
struct ac97_frame * nextwrite ;
sport_incfrag ( sport , & nextfrag , 1 ) ;
2008-11-18 16:18:17 +08:00
nextwrite = ( struct ac97_frame * ) ( sport - > tx_buf +
2008-09-05 18:21:37 +08:00
nextfrag * sport - > tx_fragsize ) ;
pr_debug ( " sport->tx_buf:%p, nextfrag:0x%x nextwrite:%p, cmd_count:%d \n " ,
sport - > tx_buf , nextfrag , nextwrite , cmd_count [ nextfrag ] ) ;
nextwrite [ cmd_count [ nextfrag ] ] . ac97_tag | = TAG_CMD ;
nextwrite [ cmd_count [ nextfrag ] ] . ac97_addr = addr ;
nextwrite [ cmd_count [ nextfrag ] ] . ac97_data = data ;
+ + cmd_count [ nextfrag ] ;
pr_debug ( " ac97_sport: Inserting %02x/%04x into fragment %d \n " ,
addr > > 8 , data , nextfrag ) ;
}
static unsigned short bf5xx_ac97_read ( struct snd_ac97 * ac97 ,
unsigned short reg )
{
struct ac97_frame out_frame [ 2 ] , in_frame [ 2 ] ;
pr_debug ( " %s enter 0x%x \n " , __func__ , reg ) ;
/* When dma descriptor is enabled, the register should not be read */
if ( sport_handle - > tx_run | | sport_handle - > rx_run ) {
pr_err ( " Could you send a mail to cliff.cai@analog.com "
" to report this? \n " ) ;
return - EFAULT ;
}
memset ( & out_frame , 0 , 2 * sizeof ( struct ac97_frame ) ) ;
memset ( & in_frame , 0 , 2 * sizeof ( struct ac97_frame ) ) ;
out_frame [ 0 ] . ac97_tag = TAG_VALID | TAG_CMD ;
out_frame [ 0 ] . ac97_addr = ( ( reg < < 8 ) | 0x8000 ) ;
sport_send_and_recv ( sport_handle , ( unsigned char * ) & out_frame ,
( unsigned char * ) & in_frame ,
2 * sizeof ( struct ac97_frame ) ) ;
return in_frame [ 1 ] . ac97_data ;
}
void bf5xx_ac97_write ( struct snd_ac97 * ac97 , unsigned short reg ,
unsigned short val )
{
pr_debug ( " %s enter 0x%x:0x%04x \n " , __func__ , reg , val ) ;
if ( sport_handle - > tx_run ) {
enqueue_cmd ( ac97 , ( reg < < 8 ) , val ) ; /* write */
enqueue_cmd ( ac97 , ( reg < < 8 ) | 0x8000 , 0 ) ; /* read back */
} else {
struct ac97_frame frame ;
memset ( & frame , 0 , sizeof ( struct ac97_frame ) ) ;
frame . ac97_tag = TAG_VALID | TAG_CMD ;
frame . ac97_addr = ( reg < < 8 ) ;
frame . ac97_data = val ;
sport_send_and_recv ( sport_handle , ( unsigned char * ) & frame , \
NULL , sizeof ( struct ac97_frame ) ) ;
}
}
static void bf5xx_ac97_warm_reset ( struct snd_ac97 * ac97 )
{
# if defined(CONFIG_BF54x) || defined(CONFIG_BF561) || \
( defined ( BF537_FAMILY ) & & ( CONFIG_SND_BF5XX_SPORT_NUM = = 1 ) )
# define CONCAT(a, b, c) a ## b ## c
# define BFIN_SPORT_RFS(x) CONCAT(P_SPORT, x, _RFS)
u16 per = BFIN_SPORT_RFS ( CONFIG_SND_BF5XX_SPORT_NUM ) ;
u16 gpio = P_IDENT ( BFIN_SPORT_RFS ( CONFIG_SND_BF5XX_SPORT_NUM ) ) ;
pr_debug ( " %s enter \n " , __func__ ) ;
peripheral_free ( per ) ;
gpio_request ( gpio , " bf5xx-ac97 " ) ;
gpio_direction_output ( gpio , 1 ) ;
udelay ( 2 ) ;
gpio_set_value ( gpio , 0 ) ;
udelay ( 1 ) ;
gpio_free ( gpio ) ;
peripheral_request ( per , " soc-audio " ) ;
# else
pr_info ( " %s: Not implemented \n " , __func__ ) ;
# endif
}
static void bf5xx_ac97_cold_reset ( struct snd_ac97 * ac97 )
{
# ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
pr_debug ( " %s enter \n " , __func__ ) ;
/* It is specified for bf548-ezkit */
gpio_set_value ( CONFIG_SND_BF5XX_RESET_GPIO_NUM , 0 ) ;
/* Keep reset pin low for 1 ms */
mdelay ( 1 ) ;
gpio_set_value ( CONFIG_SND_BF5XX_RESET_GPIO_NUM , 1 ) ;
/* Wait for bit clock recover */
mdelay ( 1 ) ;
# else
pr_info ( " %s: Not implemented \n " , __func__ ) ;
# endif
}
struct snd_ac97_bus_ops soc_ac97_ops = {
. read = bf5xx_ac97_read ,
. write = bf5xx_ac97_write ,
. warm_reset = bf5xx_ac97_warm_reset ,
. reset = bf5xx_ac97_cold_reset ,
} ;
EXPORT_SYMBOL_GPL ( soc_ac97_ops ) ;
# ifdef CONFIG_PM
2008-12-03 18:21:52 +00:00
static int bf5xx_ac97_suspend ( struct snd_soc_dai * dai )
2008-09-05 18:21:37 +08:00
{
struct sport_device * sport =
( struct sport_device * ) dai - > private_data ;
pr_debug ( " %s : sport %d \n " , __func__ , dai - > id ) ;
if ( ! dai - > active )
return 0 ;
if ( dai - > capture . active )
sport_rx_stop ( sport ) ;
if ( dai - > playback . active )
sport_tx_stop ( sport ) ;
return 0 ;
}
2008-12-03 18:21:52 +00:00
static int bf5xx_ac97_resume ( struct snd_soc_dai * dai )
2008-09-05 18:21:37 +08:00
{
int ret ;
struct sport_device * sport =
( struct sport_device * ) dai - > private_data ;
pr_debug ( " %s : sport %d \n " , __func__ , dai - > id ) ;
if ( ! dai - > active )
return 0 ;
ret = sport_set_multichannel ( sport_handle , 16 , 0x1F , 1 ) ;
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
return - EBUSY ;
}
ret = sport_config_rx ( sport_handle , IRFS , 0xF , 0 , ( 16 * 16 - 1 ) ) ;
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
return - EBUSY ;
}
ret = sport_config_tx ( sport_handle , ITFS , 0xF , 0 , ( 16 * 16 - 1 ) ) ;
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
return - EBUSY ;
}
if ( dai - > capture . active )
sport_rx_start ( sport ) ;
if ( dai - > playback . active )
sport_tx_start ( sport ) ;
return 0 ;
}
# else
# define bf5xx_ac97_suspend NULL
# define bf5xx_ac97_resume NULL
# endif
static int bf5xx_ac97_probe ( struct platform_device * pdev ,
struct snd_soc_dai * dai )
{
2008-11-18 16:18:17 +08:00
int ret = 0 ;
2008-09-05 18:21:37 +08:00
cmd_count = ( int * ) get_zeroed_page ( GFP_KERNEL ) ;
if ( cmd_count = = NULL )
return - ENOMEM ;
2009-02-06 18:12:34 +08:00
if ( peripheral_request_list ( sport_req [ sport_num ] , " soc-audio " ) ) {
2008-09-05 18:21:37 +08:00
pr_err ( " Requesting Peripherals failed \n " ) ;
2008-11-18 16:18:17 +08:00
ret = - EFAULT ;
goto peripheral_err ;
2009-02-06 18:12:34 +08:00
}
2008-09-05 18:21:37 +08:00
# ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
/* Request PB3 as reset pin */
if ( gpio_request ( CONFIG_SND_BF5XX_RESET_GPIO_NUM , " SND_AD198x RESET " ) ) {
pr_err ( " Failed to request GPIO_%d for reset \n " ,
CONFIG_SND_BF5XX_RESET_GPIO_NUM ) ;
2008-11-18 16:18:17 +08:00
ret = - 1 ;
goto gpio_err ;
2008-09-05 18:21:37 +08:00
}
gpio_direction_output ( CONFIG_SND_BF5XX_RESET_GPIO_NUM , 1 ) ;
# endif
sport_handle = sport_init ( & sport_params [ sport_num ] , 2 , \
sizeof ( struct ac97_frame ) , NULL ) ;
if ( ! sport_handle ) {
2008-11-18 16:18:17 +08:00
ret = - ENODEV ;
goto sport_err ;
2008-09-05 18:21:37 +08:00
}
/*SPORT works in TDM mode to simulate AC97 transfers*/
ret = sport_set_multichannel ( sport_handle , 16 , 0x1F , 1 ) ;
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
2008-11-18 16:18:17 +08:00
ret = - EBUSY ;
goto sport_config_err ;
2008-09-05 18:21:37 +08:00
}
ret = sport_config_rx ( sport_handle , IRFS , 0xF , 0 , ( 16 * 16 - 1 ) ) ;
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
2008-11-18 16:18:17 +08:00
ret = - EBUSY ;
goto sport_config_err ;
2008-09-05 18:21:37 +08:00
}
ret = sport_config_tx ( sport_handle , ITFS , 0xF , 0 , ( 16 * 16 - 1 ) ) ;
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
2008-11-18 16:18:17 +08:00
ret = - EBUSY ;
goto sport_config_err ;
}
2008-11-18 16:18:19 +08:00
return 0 ;
2008-11-18 16:18:17 +08:00
sport_config_err :
kfree ( sport_handle ) ;
sport_err :
2008-09-05 18:21:37 +08:00
# ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
2008-11-18 16:18:17 +08:00
gpio_free ( CONFIG_SND_BF5XX_RESET_GPIO_NUM ) ;
gpio_err :
2009-03-06 15:53:28 +08:00
# endif
2009-02-06 18:12:34 +08:00
peripheral_free_list ( sport_req [ sport_num ] ) ;
2008-11-18 16:18:17 +08:00
peripheral_err :
free_page ( ( unsigned long ) cmd_count ) ;
cmd_count = NULL ;
return ret ;
2008-09-05 18:21:37 +08:00
}
static void bf5xx_ac97_remove ( struct platform_device * pdev ,
struct snd_soc_dai * dai )
{
free_page ( ( unsigned long ) cmd_count ) ;
cmd_count = NULL ;
2009-02-06 18:12:34 +08:00
peripheral_free_list ( sport_req [ sport_num ] ) ;
2008-09-05 18:21:37 +08:00
# ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
gpio_free ( CONFIG_SND_BF5XX_RESET_GPIO_NUM ) ;
# endif
}
struct snd_soc_dai bfin_ac97_dai = {
. name = " bf5xx-ac97 " ,
. id = 0 ,
2008-11-24 18:01:05 +00:00
. ac97_control = 1 ,
2008-09-05 18:21:37 +08:00
. probe = bf5xx_ac97_probe ,
. remove = bf5xx_ac97_remove ,
. suspend = bf5xx_ac97_suspend ,
. resume = bf5xx_ac97_resume ,
. playback = {
. stream_name = " AC97 Playback " ,
. channels_min = 2 ,
2008-11-18 16:18:17 +08:00
# if defined(CONFIG_SND_BF5XX_MULTICHAN_SUPPORT)
. channels_max = 6 ,
# else
2008-09-05 18:21:37 +08:00
. channels_max = 2 ,
2008-11-18 16:18:17 +08:00
# endif
2008-09-05 18:21:37 +08:00
. rates = SNDRV_PCM_RATE_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE , } ,
. capture = {
. stream_name = " AC97 Capture " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE , } ,
} ;
EXPORT_SYMBOL_GPL ( bfin_ac97_dai ) ;
2008-12-10 07:47:22 +01:00
static int __init bfin_ac97_init ( void )
2008-12-03 19:26:35 +00:00
{
return snd_soc_register_dai ( & bfin_ac97_dai ) ;
}
module_init ( bfin_ac97_init ) ;
static void __exit bfin_ac97_exit ( void )
{
snd_soc_unregister_dai ( & bfin_ac97_dai ) ;
}
module_exit ( bfin_ac97_exit ) ;
2008-09-05 18:21:37 +08:00
MODULE_AUTHOR ( " Roy Huang " ) ;
MODULE_DESCRIPTION ( " AC97 driver for ADI Blackfin " ) ;
MODULE_LICENSE ( " GPL " ) ;