| // SPDX-License-Identifier: GPL-2.0 |
| // |
| // 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 |
| |
| #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 <chripell@evolware.org>"); |
| MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver"); |
| MODULE_LICENSE("GPL"); |