2008-11-15 10:58:32 +03:00
/*
* Modifications by Christian Pellegrin < chripell @ evolware . org >
*
* s3c24xx_uda134x . c - - S3C24XX_UDA134X ALSA SoC Audio board driver
*
* Copyright 2007 Dension Audio Systems Ltd .
* Author : Zoltan Devai
*
* 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/module.h>
# include <linux/clk.h>
# include <linux/mutex.h>
# include <linux/gpio.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/s3c24xx_uda134x.h>
# include <sound/uda134x.h>
2009-03-04 03:49:26 +03:00
# include <plat/regs-iis.h>
2008-11-15 10:58:32 +03:00
2010-11-22 09:35:57 +03:00
# include "dma.h"
2008-11-15 10:58:32 +03:00
# include "s3c24xx-i2s.h"
2008-11-18 15:25:46 +03:00
# include "../codecs/uda134x.h"
2008-11-15 10:58:32 +03:00
/* #define ENFORCE_RATES 1 */
/*
Unfortunately the S3C24XX in master mode has a limited capacity of
generating the clock for the codec . If you define this only rates
that are really available will be enforced . But be careful , most
user level application just want the usual sampling frequencies ( 8 ,
11.025 , 22.050 , 44.1 kHz ) and anyway resampling is a costly
operation for embedded systems . So if you aren ' t very lucky or your
hardware engineer wasn ' t very forward - looking it ' s better to leave
this undefined . If you do so an approximate value for the requested
sampling rate in the range - / + 5 % will be chosen . If this in not
possible an error will be returned .
*/
static struct clk * xtal ;
static struct clk * pclk ;
/* this is need because we don't have a place where to keep the
* pointers to the clocks in each substream . We get the clocks only
* when we are actually using them so we don ' t block stuff like
* frequency change or oscillator power - off */
static int clk_users ;
static DEFINE_MUTEX ( clk_lock ) ;
static unsigned int rates [ 33 * 2 ] ;
# ifdef ENFORCE_RATES
static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
. count = ARRAY_SIZE ( rates ) ,
. list = rates ,
. mask = 0 ,
} ;
# endif
static struct platform_device * s3c24xx_uda134x_snd_device ;
2008-11-19 00:57:17 +03:00
static int s3c24xx_uda134x_startup ( struct snd_pcm_substream * substream )
2008-11-15 10:58:32 +03:00
{
int ret = 0 ;
# ifdef ENFORCE_RATES
2009-08-18 22:18:35 +04:00
struct snd_pcm_runtime * runtime = substream - > runtime ;
2008-11-15 10:58:32 +03:00
# endif
mutex_lock ( & clk_lock ) ;
pr_debug ( " %s %d \n " , __func__ , clk_users ) ;
if ( clk_users = = 0 ) {
xtal = clk_get ( & s3c24xx_uda134x_snd_device - > dev , " xtal " ) ;
if ( ! xtal ) {
printk ( KERN_ERR " %s cannot get xtal \n " , __func__ ) ;
ret = - EBUSY ;
} else {
pclk = clk_get ( & s3c24xx_uda134x_snd_device - > dev ,
" pclk " ) ;
if ( ! pclk ) {
printk ( KERN_ERR " %s cannot get pclk \n " ,
__func__ ) ;
clk_put ( xtal ) ;
ret = - EBUSY ;
}
}
if ( ! ret ) {
int i , j ;
for ( i = 0 ; i < 2 ; i + + ) {
int fs = i ? 256 : 384 ;
rates [ i * 33 ] = clk_get_rate ( xtal ) / fs ;
for ( j = 1 ; j < 33 ; j + + )
rates [ i * 33 + j ] = clk_get_rate ( pclk ) /
( j * fs ) ;
}
}
}
clk_users + = 1 ;
mutex_unlock ( & clk_lock ) ;
if ( ! ret ) {
# ifdef ENFORCE_RATES
ret = snd_pcm_hw_constraint_list ( runtime , 0 ,
SNDRV_PCM_HW_PARAM_RATE ,
& hw_constraints_rates ) ;
if ( ret < 0 )
printk ( KERN_ERR " %s cannot set constraints \n " ,
__func__ ) ;
# endif
}
return ret ;
}
2008-11-19 00:57:17 +03:00
static void s3c24xx_uda134x_shutdown ( struct snd_pcm_substream * substream )
2008-11-15 10:58:32 +03:00
{
mutex_lock ( & clk_lock ) ;
pr_debug ( " %s %d \n " , __func__ , clk_users ) ;
clk_users - = 1 ;
if ( clk_users = = 0 ) {
clk_put ( xtal ) ;
xtal = NULL ;
clk_put ( pclk ) ;
pclk = NULL ;
}
mutex_unlock ( & clk_lock ) ;
}
static int s3c24xx_uda134x_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2010-03-17 23:15:21 +03:00
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
2008-11-15 10:58:32 +03:00
unsigned int clk = 0 ;
int ret = 0 ;
int clk_source , fs_mode ;
unsigned long rate = params_rate ( params ) ;
long err , cerr ;
unsigned int div ;
int i , bi ;
err = 999999 ;
bi = 0 ;
for ( i = 0 ; i < 2 * 33 ; i + + ) {
cerr = rates [ i ] - rate ;
if ( cerr < 0 )
cerr = - cerr ;
if ( cerr < err ) {
err = cerr ;
bi = i ;
}
}
if ( bi / 33 = = 1 )
fs_mode = S3C2410_IISMOD_256FS ;
else
fs_mode = S3C2410_IISMOD_384FS ;
if ( bi % 33 = = 0 ) {
clk_source = S3C24XX_CLKSRC_MPLL ;
div = 1 ;
} else {
clk_source = S3C24XX_CLKSRC_PCLK ;
div = bi % 33 ;
}
pr_debug ( " %s desired rate %lu, %d \n " , __func__ , rate , bi ) ;
clk = ( fs_mode = = S3C2410_IISMOD_384FS ? 384 : 256 ) * rate ;
pr_debug ( " %s will use: %s %s %d sysclk %d err %ld \n " , __func__ ,
fs_mode = = S3C2410_IISMOD_384FS ? " 384FS " : " 256FS " ,
clk_source = = S3C24XX_CLKSRC_MPLL ? " MPLLin " : " PCLK " ,
div , clk , err ) ;
if ( ( err * 100 / rate ) > 5 ) {
printk ( KERN_ERR " S3C24XX_UDA134X: effective frequency "
" too different from desired (%ld%%) \n " ,
err * 100 / rate ) ;
return - EINVAL ;
}
2008-11-19 00:57:17 +03:00
ret = snd_soc_dai_set_fmt ( codec_dai , SND_SOC_DAIFMT_I2S |
2008-11-15 10:58:32 +03:00
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS ) ;
if ( ret < 0 )
return ret ;
2008-11-19 00:57:17 +03:00
ret = snd_soc_dai_set_fmt ( cpu_dai , SND_SOC_DAIFMT_I2S |
2008-11-15 10:58:32 +03:00
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS ) ;
if ( ret < 0 )
return ret ;
2008-11-19 00:57:17 +03:00
ret = snd_soc_dai_set_sysclk ( cpu_dai , clk_source , clk ,
SND_SOC_CLOCK_IN ) ;
2008-11-15 10:58:32 +03:00
if ( ret < 0 )
return ret ;
2008-11-19 00:57:17 +03:00
ret = snd_soc_dai_set_clkdiv ( cpu_dai , S3C24XX_DIV_MCLK , fs_mode ) ;
2008-11-15 10:58:32 +03:00
if ( ret < 0 )
return ret ;
2008-11-19 00:57:17 +03:00
ret = snd_soc_dai_set_clkdiv ( cpu_dai , S3C24XX_DIV_BCLK ,
S3C2410_IISMOD_32FS ) ;
2008-11-15 10:58:32 +03:00
if ( ret < 0 )
return ret ;
2008-11-19 00:57:17 +03:00
ret = snd_soc_dai_set_clkdiv ( cpu_dai , S3C24XX_DIV_PRESCALER ,
S3C24XX_PRESCALE ( div , div ) ) ;
2008-11-15 10:58:32 +03:00
if ( ret < 0 )
return ret ;
/* set the codec system clock for DAC and ADC */
2008-11-19 00:57:17 +03:00
ret = snd_soc_dai_set_sysclk ( codec_dai , 0 , clk ,
SND_SOC_CLOCK_OUT ) ;
2008-11-15 10:58:32 +03:00
if ( ret < 0 )
return ret ;
return 0 ;
}
static struct snd_soc_ops s3c24xx_uda134x_ops = {
. startup = s3c24xx_uda134x_startup ,
. shutdown = s3c24xx_uda134x_shutdown ,
. hw_params = s3c24xx_uda134x_hw_params ,
} ;
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
. name = " UDA134X " ,
. stream_name = " UDA134X " ,
2010-03-17 23:15:21 +03:00
. codec_name = " uda134x-hifi " ,
. codec_dai_name = " uda134x-hifi " ,
. cpu_dai_name = " s3c24xx-i2s " ,
2008-11-15 10:58:32 +03:00
. ops = & s3c24xx_uda134x_ops ,
2010-11-22 09:35:50 +03:00
. platform_name = " samsung-audio " ,
2008-11-15 10:58:32 +03:00
} ;
2008-11-18 23:50:34 +03:00
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
2008-11-15 10:58:32 +03:00
. name = " S3C24XX_UDA134X " ,
. dai_link = & s3c24xx_uda134x_dai_link ,
. num_links = 1 ,
} ;
static struct s3c24xx_uda134x_platform_data * s3c24xx_uda134x_l3_pins ;
static void setdat ( int v )
{
gpio_set_value ( s3c24xx_uda134x_l3_pins - > l3_data , v > 0 ) ;
}
static void setclk ( int v )
{
gpio_set_value ( s3c24xx_uda134x_l3_pins - > l3_clk , v > 0 ) ;
}
static void setmode ( int v )
{
gpio_set_value ( s3c24xx_uda134x_l3_pins - > l3_mode , v > 0 ) ;
}
2010-03-17 23:15:21 +03:00
/* FIXME - This must be codec platform data but in which board file ?? */
2008-11-15 10:58:32 +03:00
static struct uda134x_platform_data s3c24xx_uda134x = {
. l3 = {
. setdat = setdat ,
. setclk = setclk ,
. setmode = setmode ,
. data_hold = 1 ,
. data_setup = 1 ,
. clock_high = 1 ,
. mode_hold = 1 ,
. mode = 1 ,
. mode_setup = 1 ,
} ,
} ;
static int s3c24xx_uda134x_setup_pin ( int pin , char * fun )
{
if ( gpio_request ( pin , " s3c24xx_uda134x " ) < 0 ) {
printk ( KERN_ERR " S3C24XX_UDA134X SoC Audio: "
" l3 %s pin already in use " , fun ) ;
return - EBUSY ;
}
gpio_direction_output ( pin , 0 ) ;
return 0 ;
}
static int s3c24xx_uda134x_probe ( struct platform_device * pdev )
{
int ret ;
printk ( KERN_INFO " S3C24XX_UDA134X SoC Audio driver \n " ) ;
s3c24xx_uda134x_l3_pins = pdev - > dev . platform_data ;
if ( s3c24xx_uda134x_l3_pins = = NULL ) {
printk ( KERN_ERR " S3C24XX_UDA134X SoC Audio: "
" unable to find platform data \n " ) ;
return - ENODEV ;
}
s3c24xx_uda134x . power = s3c24xx_uda134x_l3_pins - > power ;
s3c24xx_uda134x . model = s3c24xx_uda134x_l3_pins - > model ;
if ( s3c24xx_uda134x_setup_pin ( s3c24xx_uda134x_l3_pins - > l3_data ,
" data " ) < 0 )
return - EBUSY ;
if ( s3c24xx_uda134x_setup_pin ( s3c24xx_uda134x_l3_pins - > l3_clk ,
" clk " ) < 0 ) {
gpio_free ( s3c24xx_uda134x_l3_pins - > l3_data ) ;
return - EBUSY ;
}
if ( s3c24xx_uda134x_setup_pin ( s3c24xx_uda134x_l3_pins - > l3_mode ,
" mode " ) < 0 ) {
gpio_free ( s3c24xx_uda134x_l3_pins - > l3_data ) ;
gpio_free ( s3c24xx_uda134x_l3_pins - > l3_clk ) ;
return - EBUSY ;
}
s3c24xx_uda134x_snd_device = platform_device_alloc ( " soc-audio " , - 1 ) ;
if ( ! s3c24xx_uda134x_snd_device ) {
printk ( KERN_ERR " S3C24XX_UDA134X SoC Audio: "
" Unable to register \n " ) ;
return - ENOMEM ;
}
platform_set_drvdata ( s3c24xx_uda134x_snd_device ,
2010-03-17 23:15:21 +03:00
& snd_soc_s3c24xx_uda134x ) ;
2008-11-15 10:58:32 +03:00
ret = platform_device_add ( s3c24xx_uda134x_snd_device ) ;
if ( ret ) {
printk ( KERN_ERR " S3C24XX_UDA134X SoC Audio: Unable to add \n " ) ;
platform_device_put ( s3c24xx_uda134x_snd_device ) ;
}
return ret ;
}
static int s3c24xx_uda134x_remove ( struct platform_device * pdev )
{
platform_device_unregister ( s3c24xx_uda134x_snd_device ) ;
gpio_free ( s3c24xx_uda134x_l3_pins - > l3_data ) ;
gpio_free ( s3c24xx_uda134x_l3_pins - > l3_clk ) ;
gpio_free ( s3c24xx_uda134x_l3_pins - > l3_mode ) ;
return 0 ;
}
static struct platform_driver s3c24xx_uda134x_driver = {
. probe = s3c24xx_uda134x_probe ,
. remove = s3c24xx_uda134x_remove ,
. driver = {
. name = " s3c24xx_uda134x " ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init s3c24xx_uda134x_init ( void )
{
return platform_driver_register ( & s3c24xx_uda134x_driver ) ;
}
static void __exit s3c24xx_uda134x_exit ( void )
{
platform_driver_unregister ( & s3c24xx_uda134x_driver ) ;
}
module_init ( s3c24xx_uda134x_init ) ;
module_exit ( s3c24xx_uda134x_exit ) ;
MODULE_AUTHOR ( " Zoltan Devai, Christian Pellegrin <chripell@evolware.org> " ) ;
MODULE_DESCRIPTION ( " S3C24XX_UDA134X ALSA SoC audio driver " ) ;
MODULE_LICENSE ( " GPL " ) ;