Sylwester Nawrocki | 4d19f2c | 2019-04-19 12:21:53 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | // |
| 3 | // Modifications by Christian Pellegrin <chripell@evolware.org> |
| 4 | // |
| 5 | // s3c24xx_uda134x.c - S3C24XX_UDA134X ALSA SoC Audio board driver |
| 6 | // |
| 7 | // Copyright 2007 Dension Audio Systems Ltd. |
| 8 | // Author: Zoltan Devai |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 9 | |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 10 | #include <linux/clk.h> |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 11 | #include <linux/gpio.h> |
Paul Gortmaker | da155d5 | 2011-07-15 12:38:28 -0400 | [diff] [blame] | 12 | #include <linux/module.h> |
Seungwhan Youn | 0378b6a | 2011-01-11 07:26:06 +0900 | [diff] [blame] | 13 | |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 14 | #include <sound/soc.h> |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 15 | #include <sound/s3c24xx_uda134x.h> |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 16 | |
Arnd Bergmann | 5d229ce5 | 2013-04-11 19:08:42 +0200 | [diff] [blame] | 17 | #include "regs-iis.h" |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 18 | #include "s3c24xx-i2s.h" |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 19 | |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 20 | struct s3c24xx_uda134x { |
| 21 | struct clk *xtal; |
| 22 | struct clk *pclk; |
| 23 | struct mutex clk_lock; |
| 24 | int clk_users; |
| 25 | }; |
| 26 | |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 27 | /* #define ENFORCE_RATES 1 */ |
| 28 | /* |
| 29 | Unfortunately the S3C24XX in master mode has a limited capacity of |
| 30 | generating the clock for the codec. If you define this only rates |
| 31 | that are really available will be enforced. But be careful, most |
| 32 | user level application just want the usual sampling frequencies (8, |
| 33 | 11.025, 22.050, 44.1 kHz) and anyway resampling is a costly |
| 34 | operation for embedded systems. So if you aren't very lucky or your |
| 35 | hardware engineer wasn't very forward-looking it's better to leave |
| 36 | this undefined. If you do so an approximate value for the requested |
| 37 | sampling rate in the range -/+ 5% will be chosen. If this in not |
| 38 | possible an error will be returned. |
| 39 | */ |
| 40 | |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 41 | static unsigned int rates[33 * 2]; |
| 42 | #ifdef ENFORCE_RATES |
Takashi Iwai | 0994c03 | 2017-06-08 23:37:25 +0200 | [diff] [blame] | 43 | static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 44 | .count = ARRAY_SIZE(rates), |
| 45 | .list = rates, |
| 46 | .mask = 0, |
| 47 | }; |
| 48 | #endif |
| 49 | |
Mark Brown | d0c3663 | 2008-11-18 21:57:17 +0000 | [diff] [blame] | 50 | static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream) |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 51 | { |
Sylwester Nawrocki | 1bc610e | 2016-08-04 11:51:25 +0200 | [diff] [blame] | 52 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 53 | struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card); |
Sylwester Nawrocki | 1bc610e | 2016-08-04 11:51:25 +0200 | [diff] [blame] | 54 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; |
Sylwester Nawrocki | 1bc610e | 2016-08-04 11:51:25 +0200 | [diff] [blame] | 55 | int ret = 0; |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 56 | |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 57 | mutex_lock(&priv->clk_lock); |
Sylwester Nawrocki | 45ef496 | 2016-08-05 11:47:09 +0200 | [diff] [blame] | 58 | |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 59 | if (priv->clk_users == 0) { |
| 60 | priv->xtal = clk_get(rtd->dev, "xtal"); |
| 61 | if (IS_ERR(priv->xtal)) { |
Sylwester Nawrocki | 45ef496 | 2016-08-05 11:47:09 +0200 | [diff] [blame] | 62 | dev_err(rtd->dev, "%s cannot get xtal\n", __func__); |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 63 | ret = PTR_ERR(priv->xtal); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 64 | } else { |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 65 | priv->pclk = clk_get(cpu_dai->dev, "iis"); |
| 66 | if (IS_ERR(priv->pclk)) { |
Sylwester Nawrocki | 45ef496 | 2016-08-05 11:47:09 +0200 | [diff] [blame] | 67 | dev_err(rtd->dev, "%s cannot get pclk\n", |
| 68 | __func__); |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 69 | clk_put(priv->xtal); |
| 70 | ret = PTR_ERR(priv->pclk); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 71 | } |
| 72 | } |
| 73 | if (!ret) { |
| 74 | int i, j; |
| 75 | |
| 76 | for (i = 0; i < 2; i++) { |
| 77 | int fs = i ? 256 : 384; |
| 78 | |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 79 | rates[i*33] = clk_get_rate(priv->xtal) / fs; |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 80 | for (j = 1; j < 33; j++) |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 81 | rates[i*33 + j] = clk_get_rate(priv->pclk) / |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 82 | (j * fs); |
| 83 | } |
| 84 | } |
| 85 | } |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 86 | priv->clk_users += 1; |
| 87 | mutex_unlock(&priv->clk_lock); |
| 88 | |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 89 | if (!ret) { |
| 90 | #ifdef ENFORCE_RATES |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 91 | ret = snd_pcm_hw_constraint_list(substream->runtime, 0, |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 92 | SNDRV_PCM_HW_PARAM_RATE, |
| 93 | &hw_constraints_rates); |
| 94 | if (ret < 0) |
Sylwester Nawrocki | 45ef496 | 2016-08-05 11:47:09 +0200 | [diff] [blame] | 95 | dev_err(rtd->dev, "%s cannot set constraints\n", |
| 96 | __func__); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 97 | #endif |
| 98 | } |
| 99 | return ret; |
| 100 | } |
| 101 | |
Mark Brown | d0c3663 | 2008-11-18 21:57:17 +0000 | [diff] [blame] | 102 | static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream) |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 103 | { |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 104 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| 105 | struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card); |
| 106 | |
| 107 | mutex_lock(&priv->clk_lock); |
| 108 | priv->clk_users -= 1; |
| 109 | if (priv->clk_users == 0) { |
| 110 | clk_put(priv->xtal); |
| 111 | priv->xtal = NULL; |
| 112 | clk_put(priv->pclk); |
| 113 | priv->pclk = NULL; |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 114 | } |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 115 | mutex_unlock(&priv->clk_lock); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 116 | } |
| 117 | |
| 118 | static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream, |
| 119 | struct snd_pcm_hw_params *params) |
| 120 | { |
| 121 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 122 | struct snd_soc_dai *codec_dai = rtd->codec_dai; |
| 123 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 124 | unsigned int clk = 0; |
| 125 | int ret = 0; |
| 126 | int clk_source, fs_mode; |
| 127 | unsigned long rate = params_rate(params); |
| 128 | long err, cerr; |
| 129 | unsigned int div; |
| 130 | int i, bi; |
| 131 | |
| 132 | err = 999999; |
| 133 | bi = 0; |
| 134 | for (i = 0; i < 2*33; i++) { |
| 135 | cerr = rates[i] - rate; |
| 136 | if (cerr < 0) |
| 137 | cerr = -cerr; |
| 138 | if (cerr < err) { |
| 139 | err = cerr; |
| 140 | bi = i; |
| 141 | } |
| 142 | } |
| 143 | if (bi / 33 == 1) |
| 144 | fs_mode = S3C2410_IISMOD_256FS; |
| 145 | else |
| 146 | fs_mode = S3C2410_IISMOD_384FS; |
| 147 | if (bi % 33 == 0) { |
| 148 | clk_source = S3C24XX_CLKSRC_MPLL; |
| 149 | div = 1; |
| 150 | } else { |
| 151 | clk_source = S3C24XX_CLKSRC_PCLK; |
| 152 | div = bi % 33; |
| 153 | } |
Sylwester Nawrocki | 45ef496 | 2016-08-05 11:47:09 +0200 | [diff] [blame] | 154 | |
| 155 | dev_dbg(rtd->dev, "%s desired rate %lu, %d\n", __func__, rate, bi); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 156 | |
| 157 | clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate; |
Sylwester Nawrocki | 45ef496 | 2016-08-05 11:47:09 +0200 | [diff] [blame] | 158 | |
| 159 | dev_dbg(rtd->dev, "%s will use: %s %s %d sysclk %d err %ld\n", __func__, |
| 160 | fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS", |
| 161 | clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK", |
| 162 | div, clk, err); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 163 | |
| 164 | if ((err * 100 / rate) > 5) { |
Sylwester Nawrocki | 45ef496 | 2016-08-05 11:47:09 +0200 | [diff] [blame] | 165 | dev_err(rtd->dev, "effective frequency too different " |
| 166 | "from desired (%ld%%)\n", err * 100 / rate); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 167 | return -EINVAL; |
| 168 | } |
| 169 | |
Mark Brown | d0c3663 | 2008-11-18 21:57:17 +0000 | [diff] [blame] | 170 | ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk, |
| 171 | SND_SOC_CLOCK_IN); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 172 | if (ret < 0) |
| 173 | return ret; |
| 174 | |
Mark Brown | d0c3663 | 2008-11-18 21:57:17 +0000 | [diff] [blame] | 175 | ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 176 | if (ret < 0) |
| 177 | return ret; |
| 178 | |
Mark Brown | d0c3663 | 2008-11-18 21:57:17 +0000 | [diff] [blame] | 179 | ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, |
| 180 | S3C2410_IISMOD_32FS); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 181 | if (ret < 0) |
| 182 | return ret; |
| 183 | |
Mark Brown | d0c3663 | 2008-11-18 21:57:17 +0000 | [diff] [blame] | 184 | ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, |
| 185 | S3C24XX_PRESCALE(div, div)); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 186 | if (ret < 0) |
| 187 | return ret; |
| 188 | |
| 189 | /* set the codec system clock for DAC and ADC */ |
Mark Brown | d0c3663 | 2008-11-18 21:57:17 +0000 | [diff] [blame] | 190 | ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, |
| 191 | SND_SOC_CLOCK_OUT); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 192 | if (ret < 0) |
| 193 | return ret; |
| 194 | |
| 195 | return 0; |
| 196 | } |
| 197 | |
Bhumika Goyal | 2af2363 | 2017-08-16 22:29:29 +0530 | [diff] [blame] | 198 | static const struct snd_soc_ops s3c24xx_uda134x_ops = { |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 199 | .startup = s3c24xx_uda134x_startup, |
| 200 | .shutdown = s3c24xx_uda134x_shutdown, |
| 201 | .hw_params = s3c24xx_uda134x_hw_params, |
| 202 | }; |
| 203 | |
Kuninori Morimoto | bb5e4a0 | 2019-06-06 13:10:06 +0900 | [diff] [blame] | 204 | SND_SOC_DAILINK_DEFS(uda134x, |
| 205 | DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")), |
| 206 | DAILINK_COMP_ARRAY(COMP_CODEC("uda134x-codec", "uda134x-hifi")), |
| 207 | DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis"))); |
| 208 | |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 209 | static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = { |
| 210 | .name = "UDA134X", |
| 211 | .stream_name = "UDA134X", |
Lars-Peter Clausen | 517b9a2 | 2015-01-01 17:16:25 +0100 | [diff] [blame] | 212 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
| 213 | SND_SOC_DAIFMT_CBS_CFS, |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 214 | .ops = &s3c24xx_uda134x_ops, |
Kuninori Morimoto | bb5e4a0 | 2019-06-06 13:10:06 +0900 | [diff] [blame] | 215 | SND_SOC_DAILINK_REG(uda134x), |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 216 | }; |
| 217 | |
Mark Brown | 8750654 | 2008-11-18 20:50:34 +0000 | [diff] [blame] | 218 | static struct snd_soc_card snd_soc_s3c24xx_uda134x = { |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 219 | .name = "S3C24XX_UDA134X", |
Axel Lin | 095d79d | 2011-12-22 10:53:15 +0800 | [diff] [blame] | 220 | .owner = THIS_MODULE, |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 221 | .dai_link = &s3c24xx_uda134x_dai_link, |
| 222 | .num_links = 1, |
| 223 | }; |
| 224 | |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 225 | static int s3c24xx_uda134x_probe(struct platform_device *pdev) |
| 226 | { |
Sylwester Nawrocki | 2840521 | 2016-08-04 15:38:46 +0200 | [diff] [blame] | 227 | struct snd_soc_card *card = &snd_soc_s3c24xx_uda134x; |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 228 | struct s3c24xx_uda134x *priv; |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 229 | int ret; |
| 230 | |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 231 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
| 232 | if (!priv) |
| 233 | return -ENOMEM; |
| 234 | |
| 235 | mutex_init(&priv->clk_lock); |
| 236 | |
Sylwester Nawrocki | 2840521 | 2016-08-04 15:38:46 +0200 | [diff] [blame] | 237 | card->dev = &pdev->dev; |
Sylwester Nawrocki | 892ccf0 | 2016-10-25 12:57:57 +0200 | [diff] [blame] | 238 | snd_soc_card_set_drvdata(card, priv); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 239 | |
Sylwester Nawrocki | 2840521 | 2016-08-04 15:38:46 +0200 | [diff] [blame] | 240 | ret = devm_snd_soc_register_card(&pdev->dev, card); |
| 241 | if (ret) |
| 242 | dev_err(&pdev->dev, "failed to register card: %d\n", ret); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 243 | |
| 244 | return ret; |
| 245 | } |
| 246 | |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 247 | static struct platform_driver s3c24xx_uda134x_driver = { |
| 248 | .probe = s3c24xx_uda134x_probe, |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 249 | .driver = { |
| 250 | .name = "s3c24xx_uda134x", |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 251 | }, |
| 252 | }; |
Mark Brown | e00c3f5 | 2011-11-23 15:20:13 +0000 | [diff] [blame] | 253 | module_platform_driver(s3c24xx_uda134x_driver); |
Christian Pellegrin | 7ad933d | 2008-11-15 08:58:32 +0100 | [diff] [blame] | 254 | |
| 255 | MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>"); |
| 256 | MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver"); |
| 257 | MODULE_LICENSE("GPL"); |