Sylwester Nawrocki | ebf4c42 | 2019-04-19 12:21:58 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | // |
| 3 | // ASoC machine driver for Snow boards |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 4 | |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 5 | #include <linux/clk.h> |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 6 | #include <linux/module.h> |
| 7 | #include <linux/platform_device.h> |
| 8 | #include <linux/of.h> |
| 9 | #include <linux/of_device.h> |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 10 | #include <sound/pcm_params.h> |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 11 | #include <sound/soc.h> |
| 12 | |
| 13 | #include "i2s.h" |
| 14 | |
| 15 | #define FIN_PLL_RATE 24000000 |
| 16 | |
Kuninori Morimoto | 3dfc3e9 | 2019-06-28 10:48:23 +0900 | [diff] [blame] | 17 | SND_SOC_DAILINK_DEFS(links, |
| 18 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 19 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 20 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 21 | |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 22 | struct snow_priv { |
| 23 | struct snd_soc_dai_link dai_link; |
| 24 | struct clk *clk_i2s_bus; |
| 25 | }; |
| 26 | |
| 27 | static int snow_card_hw_params(struct snd_pcm_substream *substream, |
| 28 | struct snd_pcm_hw_params *params) |
| 29 | { |
| 30 | static const unsigned int pll_rate[] = { |
| 31 | 73728000U, 67737602U, 49152000U, 45158401U, 32768001U |
| 32 | }; |
Kuninori Morimoto | c101ce8 | 2020-07-20 10:18:14 +0900 | [diff] [blame] | 33 | struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 34 | struct snow_priv *priv = snd_soc_card_get_drvdata(rtd->card); |
| 35 | int bfs, psr, rfs, bitwidth; |
| 36 | unsigned long int rclk; |
| 37 | long int freq = -EINVAL; |
| 38 | int ret, i; |
| 39 | |
| 40 | bitwidth = snd_pcm_format_width(params_format(params)); |
| 41 | if (bitwidth < 0) { |
| 42 | dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth); |
| 43 | return bitwidth; |
| 44 | } |
| 45 | |
| 46 | if (bitwidth != 16 && bitwidth != 24) { |
| 47 | dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth); |
| 48 | return -EINVAL; |
| 49 | } |
| 50 | |
| 51 | bfs = 2 * bitwidth; |
| 52 | |
| 53 | switch (params_rate(params)) { |
| 54 | case 16000: |
| 55 | case 22050: |
| 56 | case 24000: |
| 57 | case 32000: |
| 58 | case 44100: |
| 59 | case 48000: |
| 60 | case 88200: |
| 61 | case 96000: |
| 62 | rfs = 8 * bfs; |
| 63 | break; |
| 64 | case 64000: |
| 65 | rfs = 384; |
| 66 | break; |
| 67 | case 8000: |
| 68 | case 11025: |
| 69 | case 12000: |
| 70 | rfs = 16 * bfs; |
| 71 | break; |
| 72 | default: |
| 73 | return -EINVAL; |
| 74 | } |
| 75 | |
| 76 | rclk = params_rate(params) * rfs; |
| 77 | |
| 78 | for (psr = 8; psr > 0; psr /= 2) { |
| 79 | for (i = 0; i < ARRAY_SIZE(pll_rate); i++) { |
| 80 | if ((pll_rate[i] - rclk * psr) <= 2) { |
| 81 | freq = pll_rate[i]; |
| 82 | break; |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | if (freq < 0) { |
| 87 | dev_err(rtd->card->dev, "Unsupported RCLK rate: %lu\n", rclk); |
| 88 | return -EINVAL; |
| 89 | } |
| 90 | |
| 91 | ret = clk_set_rate(priv->clk_i2s_bus, freq); |
| 92 | if (ret < 0) { |
| 93 | dev_err(rtd->card->dev, "I2S bus clock rate set failed\n"); |
| 94 | return ret; |
| 95 | } |
| 96 | |
| 97 | return 0; |
| 98 | } |
| 99 | |
| 100 | static const struct snd_soc_ops snow_card_ops = { |
| 101 | .hw_params = snow_card_hw_params, |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 102 | }; |
| 103 | |
| 104 | static int snow_late_probe(struct snd_soc_card *card) |
| 105 | { |
Mengdong Lin | 5015920 | 2015-11-18 02:34:01 -0500 | [diff] [blame] | 106 | struct snd_soc_pcm_runtime *rtd; |
| 107 | struct snd_soc_dai *codec_dai; |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 108 | |
Kuninori Morimoto | 4468189 | 2019-12-10 09:34:08 +0900 | [diff] [blame] | 109 | rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]); |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 110 | |
| 111 | /* In the multi-codec case codec_dais 0 is MAX98095 and 1 is HDMI. */ |
Pierre-Louis Bossart | c856cef | 2021-02-19 17:09:16 -0600 | [diff] [blame] | 112 | codec_dai = asoc_rtd_to_codec(rtd, 0); |
Mengdong Lin | 5015920 | 2015-11-18 02:34:01 -0500 | [diff] [blame] | 113 | |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 114 | /* Set the MCLK rate for the codec */ |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 115 | return snd_soc_dai_set_sysclk(codec_dai, 0, |
| 116 | FIN_PLL_RATE, SND_SOC_CLOCK_IN); |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 117 | } |
| 118 | |
| 119 | static struct snd_soc_card snow_snd = { |
| 120 | .name = "Snow-I2S", |
Axel Lin | 54d8697 | 2015-08-21 20:59:21 +0800 | [diff] [blame] | 121 | .owner = THIS_MODULE, |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 122 | .late_probe = snow_late_probe, |
| 123 | }; |
| 124 | |
| 125 | static int snow_probe(struct platform_device *pdev) |
| 126 | { |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 127 | struct device *dev = &pdev->dev; |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 128 | struct snd_soc_card *card = &snow_snd; |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 129 | struct device_node *cpu, *codec; |
| 130 | struct snd_soc_dai_link *link; |
| 131 | struct snow_priv *priv; |
| 132 | int ret; |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 133 | |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 134 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| 135 | if (!priv) |
| 136 | return -ENOMEM; |
| 137 | |
| 138 | link = &priv->dai_link; |
| 139 | |
| 140 | link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
| 141 | SND_SOC_DAIFMT_CBS_CFS; |
| 142 | |
| 143 | link->name = "Primary"; |
| 144 | link->stream_name = link->name; |
| 145 | |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 146 | link->cpus = links_cpus; |
| 147 | link->num_cpus = ARRAY_SIZE(links_cpus); |
| 148 | link->codecs = links_codecs; |
| 149 | link->num_codecs = ARRAY_SIZE(links_codecs); |
Kuninori Morimoto | 3dfc3e9 | 2019-06-28 10:48:23 +0900 | [diff] [blame] | 150 | link->platforms = links_platforms; |
| 151 | link->num_platforms = ARRAY_SIZE(links_platforms); |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 152 | |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 153 | card->dai_link = link; |
| 154 | card->num_links = 1; |
| 155 | card->dev = dev; |
| 156 | |
| 157 | /* Try new DT bindings with HDMI support first. */ |
| 158 | cpu = of_get_child_by_name(dev->of_node, "cpu"); |
| 159 | |
| 160 | if (cpu) { |
| 161 | link->ops = &snow_card_ops; |
| 162 | |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 163 | link->cpus->of_node = of_parse_phandle(cpu, "sound-dai", 0); |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 164 | of_node_put(cpu); |
| 165 | |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 166 | if (!link->cpus->of_node) { |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 167 | dev_err(dev, "Failed parsing cpu/sound-dai property\n"); |
| 168 | return -EINVAL; |
| 169 | } |
| 170 | |
| 171 | codec = of_get_child_by_name(dev->of_node, "codec"); |
| 172 | ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); |
| 173 | of_node_put(codec); |
| 174 | |
| 175 | if (ret < 0) { |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 176 | of_node_put(link->cpus->of_node); |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 177 | dev_err(dev, "Failed parsing codec node\n"); |
| 178 | return ret; |
| 179 | } |
| 180 | |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 181 | priv->clk_i2s_bus = of_clk_get_by_name(link->cpus->of_node, |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 182 | "i2s_opclk0"); |
| 183 | if (IS_ERR(priv->clk_i2s_bus)) { |
| 184 | snd_soc_of_put_dai_link_codecs(link); |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 185 | of_node_put(link->cpus->of_node); |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 186 | return PTR_ERR(priv->clk_i2s_bus); |
| 187 | } |
| 188 | } else { |
Julia Lawall | 40faaca | 2020-10-11 11:19:37 +0200 | [diff] [blame] | 189 | link->codecs->dai_name = "HiFi"; |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 190 | |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 191 | link->cpus->of_node = of_parse_phandle(dev->of_node, |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 192 | "samsung,i2s-controller", 0); |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 193 | if (!link->cpus->of_node) { |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 194 | dev_err(dev, "i2s-controller property parse error\n"); |
| 195 | return -EINVAL; |
| 196 | } |
| 197 | |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 198 | link->codecs->of_node = of_parse_phandle(dev->of_node, |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 199 | "samsung,audio-codec", 0); |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 200 | if (!link->codecs->of_node) { |
| 201 | of_node_put(link->cpus->of_node); |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 202 | dev_err(dev, "audio-codec property parse error\n"); |
| 203 | return -EINVAL; |
| 204 | } |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 205 | } |
| 206 | |
Kuninori Morimoto | 3dfc3e9 | 2019-06-28 10:48:23 +0900 | [diff] [blame] | 207 | link->platforms->of_node = link->cpus->of_node; |
| 208 | |
Tushar Behera | 00ad93e | 2014-07-04 14:22:59 +0530 | [diff] [blame] | 209 | /* Update card-name if provided through DT, else use default name */ |
| 210 | snd_soc_of_parse_card_name(card, "samsung,model"); |
| 211 | |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 212 | snd_soc_card_set_drvdata(card, priv); |
| 213 | |
| 214 | ret = devm_snd_soc_register_card(dev, card); |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 215 | if (ret) { |
Marek Szyprowski | 1a1b374 | 2020-02-28 11:11:20 +0100 | [diff] [blame] | 216 | if (ret != -EPROBE_DEFER) |
| 217 | dev_err(&pdev->dev, |
| 218 | "snd_soc_register_card failed (%d)\n", ret); |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 219 | return ret; |
| 220 | } |
| 221 | |
| 222 | return ret; |
| 223 | } |
| 224 | |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 225 | static int snow_remove(struct platform_device *pdev) |
| 226 | { |
| 227 | struct snow_priv *priv = platform_get_drvdata(pdev); |
| 228 | struct snd_soc_dai_link *link = &priv->dai_link; |
| 229 | |
Kuninori Morimoto | db1623f | 2019-06-06 13:09:56 +0900 | [diff] [blame] | 230 | of_node_put(link->cpus->of_node); |
| 231 | of_node_put(link->codecs->of_node); |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 232 | snd_soc_of_put_dai_link_codecs(link); |
| 233 | |
| 234 | clk_put(priv->clk_i2s_bus); |
| 235 | |
| 236 | return 0; |
| 237 | } |
| 238 | |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 239 | static const struct of_device_id snow_of_match[] = { |
| 240 | { .compatible = "google,snow-audio-max98090", }, |
Tushar Behera | 46aed59 | 2014-06-20 13:33:16 +0530 | [diff] [blame] | 241 | { .compatible = "google,snow-audio-max98091", }, |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 242 | { .compatible = "google,snow-audio-max98095", }, |
| 243 | {}, |
| 244 | }; |
Andreas Färber | 62e6a3b | 2014-11-05 17:44:52 +0100 | [diff] [blame] | 245 | MODULE_DEVICE_TABLE(of, snow_of_match); |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 246 | |
| 247 | static struct platform_driver snow_driver = { |
| 248 | .driver = { |
| 249 | .name = "snow-audio", |
Tushar Behera | deeaa68 | 2014-05-14 08:49:06 +0530 | [diff] [blame] | 250 | .pm = &snd_soc_pm_ops, |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 251 | .of_match_table = snow_of_match, |
| 252 | }, |
| 253 | .probe = snow_probe, |
Sylwester Nawrocki | aff8d2b | 2018-03-12 19:49:39 +0100 | [diff] [blame] | 254 | .remove = snow_remove, |
Tushar Behera | 31c26a6 | 2014-04-28 10:14:39 +0530 | [diff] [blame] | 255 | }; |
| 256 | |
| 257 | module_platform_driver(snow_driver); |
| 258 | |
| 259 | MODULE_DESCRIPTION("ALSA SoC Audio machine driver for Snow"); |
| 260 | MODULE_LICENSE("GPL"); |