forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
6.5 KiB
257 lines
6.5 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
// |
|
// Modifications by Christian Pellegrin <[email protected]> |
|
// |
|
// s3c24xx_uda134x.c - S3C24XX_UDA134X ALSA SoC Audio board driver |
|
// |
|
// Copyright 2007 Dension Audio Systems Ltd. |
|
// Author: Zoltan Devai |
|
|
|
#include <linux/clk.h> |
|
#include <linux/gpio.h> |
|
#include <linux/module.h> |
|
|
|
#include <sound/soc.h> |
|
#include <sound/s3c24xx_uda134x.h> |
|
|
|
#include "regs-iis.h" |
|
#include "s3c24xx-i2s.h" |
|
|
|
struct s3c24xx_uda134x { |
|
struct clk *xtal; |
|
struct clk *pclk; |
|
struct mutex clk_lock; |
|
int clk_users; |
|
}; |
|
|
|
/* #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 unsigned int rates[33 * 2]; |
|
#ifdef ENFORCE_RATES |
|
static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { |
|
.count = ARRAY_SIZE(rates), |
|
.list = rates, |
|
.mask = 0, |
|
}; |
|
#endif |
|
|
|
static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream) |
|
{ |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card); |
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); |
|
int ret = 0; |
|
|
|
mutex_lock(&priv->clk_lock); |
|
|
|
if (priv->clk_users == 0) { |
|
priv->xtal = clk_get(rtd->dev, "xtal"); |
|
if (IS_ERR(priv->xtal)) { |
|
dev_err(rtd->dev, "%s cannot get xtal\n", __func__); |
|
ret = PTR_ERR(priv->xtal); |
|
} else { |
|
priv->pclk = clk_get(cpu_dai->dev, "iis"); |
|
if (IS_ERR(priv->pclk)) { |
|
dev_err(rtd->dev, "%s cannot get pclk\n", |
|
__func__); |
|
clk_put(priv->xtal); |
|
ret = PTR_ERR(priv->pclk); |
|
} |
|
} |
|
if (!ret) { |
|
int i, j; |
|
|
|
for (i = 0; i < 2; i++) { |
|
int fs = i ? 256 : 384; |
|
|
|
rates[i*33] = clk_get_rate(priv->xtal) / fs; |
|
for (j = 1; j < 33; j++) |
|
rates[i*33 + j] = clk_get_rate(priv->pclk) / |
|
(j * fs); |
|
} |
|
} |
|
} |
|
priv->clk_users += 1; |
|
mutex_unlock(&priv->clk_lock); |
|
|
|
if (!ret) { |
|
#ifdef ENFORCE_RATES |
|
ret = snd_pcm_hw_constraint_list(substream->runtime, 0, |
|
SNDRV_PCM_HW_PARAM_RATE, |
|
&hw_constraints_rates); |
|
if (ret < 0) |
|
dev_err(rtd->dev, "%s cannot set constraints\n", |
|
__func__); |
|
#endif |
|
} |
|
return ret; |
|
} |
|
|
|
static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream) |
|
{ |
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
|
struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card); |
|
|
|
mutex_lock(&priv->clk_lock); |
|
priv->clk_users -= 1; |
|
if (priv->clk_users == 0) { |
|
clk_put(priv->xtal); |
|
priv->xtal = NULL; |
|
clk_put(priv->pclk); |
|
priv->pclk = NULL; |
|
} |
|
mutex_unlock(&priv->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 = asoc_substream_to_rtd(substream); |
|
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); |
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); |
|
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; |
|
} |
|
|
|
dev_dbg(rtd->dev, "%s desired rate %lu, %d\n", __func__, rate, bi); |
|
|
|
clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate; |
|
|
|
dev_dbg(rtd->dev, "%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) { |
|
dev_err(rtd->dev, "effective frequency too different " |
|
"from desired (%ld%%)\n", err * 100 / rate); |
|
return -EINVAL; |
|
} |
|
|
|
ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk, |
|
SND_SOC_CLOCK_IN); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, |
|
S3C2410_IISMOD_32FS); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, |
|
S3C24XX_PRESCALE(div, div)); |
|
if (ret < 0) |
|
return ret; |
|
|
|
/* set the codec system clock for DAC and ADC */ |
|
ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, |
|
SND_SOC_CLOCK_OUT); |
|
if (ret < 0) |
|
return ret; |
|
|
|
return 0; |
|
} |
|
|
|
static const struct snd_soc_ops s3c24xx_uda134x_ops = { |
|
.startup = s3c24xx_uda134x_startup, |
|
.shutdown = s3c24xx_uda134x_shutdown, |
|
.hw_params = s3c24xx_uda134x_hw_params, |
|
}; |
|
|
|
SND_SOC_DAILINK_DEFS(uda134x, |
|
DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")), |
|
DAILINK_COMP_ARRAY(COMP_CODEC("uda134x-codec", "uda134x-hifi")), |
|
DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis"))); |
|
|
|
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = { |
|
.name = "UDA134X", |
|
.stream_name = "UDA134X", |
|
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
|
SND_SOC_DAIFMT_CBS_CFS, |
|
.ops = &s3c24xx_uda134x_ops, |
|
SND_SOC_DAILINK_REG(uda134x), |
|
}; |
|
|
|
static struct snd_soc_card snd_soc_s3c24xx_uda134x = { |
|
.name = "S3C24XX_UDA134X", |
|
.owner = THIS_MODULE, |
|
.dai_link = &s3c24xx_uda134x_dai_link, |
|
.num_links = 1, |
|
}; |
|
|
|
static int s3c24xx_uda134x_probe(struct platform_device *pdev) |
|
{ |
|
struct snd_soc_card *card = &snd_soc_s3c24xx_uda134x; |
|
struct s3c24xx_uda134x *priv; |
|
int ret; |
|
|
|
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
mutex_init(&priv->clk_lock); |
|
|
|
card->dev = &pdev->dev; |
|
snd_soc_card_set_drvdata(card, priv); |
|
|
|
ret = devm_snd_soc_register_card(&pdev->dev, card); |
|
if (ret) |
|
dev_err(&pdev->dev, "failed to register card: %d\n", ret); |
|
|
|
return ret; |
|
} |
|
|
|
static struct platform_driver s3c24xx_uda134x_driver = { |
|
.probe = s3c24xx_uda134x_probe, |
|
.driver = { |
|
.name = "s3c24xx_uda134x", |
|
}, |
|
}; |
|
module_platform_driver(s3c24xx_uda134x_driver); |
|
|
|
MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <[email protected]>"); |
|
MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver"); |
|
MODULE_LICENSE("GPL");
|
|
|