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/clk.h>
# include <linux/gpio.h>
2011-07-15 20:38:28 +04:00
# include <linux/module.h>
2011-01-11 01:26:06 +03:00
2008-11-15 10:58:32 +03:00
# include <sound/soc.h>
# include <sound/s3c24xx_uda134x.h>
2013-04-11 21:08:42 +04:00
# include "regs-iis.h"
2008-11-15 10:58:32 +03:00
# include "s3c24xx-i2s.h"
/* #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
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 ) {
2016-08-04 16:38:46 +03:00
xtal = clk_get ( rtd - > dev , " xtal " ) ;
2011-09-15 06:36:54 +04:00
if ( IS_ERR ( xtal ) ) {
2008-11-15 10:58:32 +03:00
printk ( KERN_ERR " %s cannot get xtal \n " , __func__ ) ;
2011-09-15 06:36:54 +04:00
ret = PTR_ERR ( xtal ) ;
2008-11-15 10:58:32 +03:00
} else {
pclk = clk_get ( & s3c24xx_uda134x_snd_device - > dev ,
" pclk " ) ;
2011-09-15 06:36:54 +04:00
if ( IS_ERR ( pclk ) ) {
2008-11-15 10:58:32 +03:00
printk ( KERN_ERR " %s cannot get pclk \n " ,
__func__ ) ;
clk_put ( xtal ) ;
2011-09-15 06:36:54 +04:00
ret = PTR_ERR ( pclk ) ;
2008-11-15 10:58:32 +03:00
}
}
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_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 " ,
2011-03-09 23:46:20 +03:00
. codec_name = " uda134x-codec " ,
2010-03-17 23:15:21 +03:00
. codec_dai_name = " uda134x-hifi " ,
2011-01-25 00:12:42 +03:00
. cpu_dai_name = " s3c24xx-iis " ,
2015-01-01 19:16:25 +03:00
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS ,
2008-11-15 10:58:32 +03:00
. ops = & s3c24xx_uda134x_ops ,
2012-12-07 12:29:21 +04:00
. platform_name = " s3c24xx-iis " ,
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 " ,
2011-12-22 06:53:15 +04:00
. owner = THIS_MODULE ,
2008-11-15 10:58:32 +03:00
. dai_link = & s3c24xx_uda134x_dai_link ,
. num_links = 1 ,
} ;
static int s3c24xx_uda134x_probe ( struct platform_device * pdev )
{
2016-08-04 16:38:46 +03:00
struct snd_soc_card * card = & snd_soc_s3c24xx_uda134x ;
2008-11-15 10:58:32 +03:00
int ret ;
2016-08-04 16:38:46 +03:00
platform_set_drvdata ( pdev , card ) ;
card - > dev = & pdev - > dev ;
2008-11-15 10:58:32 +03:00
2016-08-04 16:38:46 +03:00
ret = devm_snd_soc_register_card ( & pdev - > dev , card ) ;
if ( ret )
dev_err ( & pdev - > dev , " failed to register card: %d \n " , ret ) ;
2008-11-15 10:58:32 +03:00
return ret ;
}
static struct platform_driver s3c24xx_uda134x_driver = {
. probe = s3c24xx_uda134x_probe ,
. driver = {
. name = " s3c24xx_uda134x " ,
} ,
} ;
2011-11-23 19:20:13 +04:00
module_platform_driver ( s3c24xx_uda134x_driver ) ;
2008-11-15 10:58:32 +03:00
MODULE_AUTHOR ( " Zoltan Devai, Christian Pellegrin <chripell@evolware.org> " ) ;
MODULE_DESCRIPTION ( " S3C24XX_UDA134X ALSA SoC audio driver " ) ;
MODULE_LICENSE ( " GPL " ) ;