1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * wm8524.c -- WM8524 ALSA SoC Audio driver 4 * 5 * Copyright 2009 Wolfson Microelectronics plc 6 * Copyright 2017 NXP 7 * 8 * Based on WM8523 ALSA SoC Audio driver written by Mark Brown 9 */ 10 11 #include <linux/mod_devicetable.h> 12 #include <linux/module.h> 13 #include <linux/moduleparam.h> 14 #include <linux/init.h> 15 #include <linux/delay.h> 16 #include <linux/slab.h> 17 #include <linux/gpio/consumer.h> 18 #include <sound/core.h> 19 #include <sound/pcm.h> 20 #include <sound/pcm_params.h> 21 #include <sound/soc.h> 22 #include <sound/initval.h> 23 24 #define WM8524_NUM_RATES 12 25 26 /* codec private data */ 27 struct wm8524_priv { 28 struct gpio_desc *mute; 29 unsigned int sysclk; 30 unsigned int rate_constraint_list[WM8524_NUM_RATES]; 31 struct snd_pcm_hw_constraint_list rate_constraint; 32 }; 33 34 35 static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = { 36 SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), 37 SND_SOC_DAPM_OUTPUT("LINEVOUTL"), 38 SND_SOC_DAPM_OUTPUT("LINEVOUTR"), 39 }; 40 41 static const struct snd_soc_dapm_route wm8524_dapm_routes[] = { 42 { "LINEVOUTL", NULL, "DAC" }, 43 { "LINEVOUTR", NULL, "DAC" }, 44 }; 45 46 static const struct { 47 int value; 48 int ratio; 49 } lrclk_ratios[] = { 50 { 1, 128 }, 51 { 2, 192 }, 52 { 3, 256 }, 53 { 4, 384 }, 54 { 5, 512 }, 55 { 6, 768 }, 56 { 7, 1152 }, 57 }; 58 59 static int wm8524_startup(struct snd_pcm_substream *substream, 60 struct snd_soc_dai *dai) 61 { 62 struct snd_soc_component *component = dai->component; 63 struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 64 65 /* The set of sample rates that can be supported depends on the 66 * MCLK supplied to the CODEC. 67 */ 68 if (wm8524->sysclk) 69 snd_pcm_hw_constraint_list(substream->runtime, 0, 70 SNDRV_PCM_HW_PARAM_RATE, 71 &wm8524->rate_constraint); 72 73 gpiod_set_value_cansleep(wm8524->mute, 1); 74 75 return 0; 76 } 77 78 static void wm8524_shutdown(struct snd_pcm_substream *substream, 79 struct snd_soc_dai *dai) 80 { 81 struct snd_soc_component *component = dai->component; 82 struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 83 84 gpiod_set_value_cansleep(wm8524->mute, 0); 85 } 86 87 static int wm8524_set_dai_sysclk(struct snd_soc_dai *codec_dai, 88 int clk_id, unsigned int freq, int dir) 89 { 90 struct snd_soc_component *component = codec_dai->component; 91 struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 92 unsigned int val; 93 int i, j = 0; 94 95 wm8524->rate_constraint.count = 0; 96 wm8524->sysclk = freq; 97 if (!wm8524->sysclk) 98 return 0; 99 100 for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { 101 val = freq / lrclk_ratios[i].ratio; 102 /* Check that it's a standard rate since core can't 103 * cope with others and having the odd rates confuses 104 * constraint matching. 105 */ 106 switch (val) { 107 case 8000: 108 case 11025: 109 case 16000: 110 case 22050: 111 case 32000: 112 case 44100: 113 case 48000: 114 case 64000: 115 case 88200: 116 case 96000: 117 case 176400: 118 case 192000: 119 dev_dbg(component->dev, "Supported sample rate: %dHz\n", 120 val); 121 wm8524->rate_constraint_list[j++] = val; 122 wm8524->rate_constraint.count++; 123 break; 124 default: 125 dev_dbg(component->dev, "Skipping sample rate: %dHz\n", 126 val); 127 } 128 } 129 130 /* Need at least one supported rate... */ 131 if (wm8524->rate_constraint.count == 0) 132 return -EINVAL; 133 134 return 0; 135 } 136 137 static int wm8524_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) 138 { 139 fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK | 140 SND_SOC_DAIFMT_MASTER_MASK); 141 142 if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 143 SND_SOC_DAIFMT_CBC_CFC)) { 144 dev_err(codec_dai->dev, "Invalid DAI format\n"); 145 return -EINVAL; 146 } 147 148 return 0; 149 } 150 151 static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream) 152 { 153 struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(dai->component); 154 155 if (wm8524->mute) 156 gpiod_set_value_cansleep(wm8524->mute, mute); 157 158 return 0; 159 } 160 161 static int wm8524_hw_params(struct snd_pcm_substream *substream, 162 struct snd_pcm_hw_params *params, 163 struct snd_soc_dai *dai) 164 { 165 struct snd_soc_component *component = dai->component; 166 struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 167 int i; 168 169 /* If sysclk is not configured, no need to check the rate */ 170 if (!wm8524->sysclk) 171 return 0; 172 173 /* Find a supported LRCLK rate */ 174 for (i = 0; i < wm8524->rate_constraint.count; i++) { 175 if (wm8524->rate_constraint.list[i] == params_rate(params)) 176 break; 177 } 178 179 if (i == wm8524->rate_constraint.count) { 180 dev_err(component->dev, "LRCLK %d unsupported with MCLK %d\n", 181 params_rate(params), wm8524->sysclk); 182 return -EINVAL; 183 } 184 185 return 0; 186 } 187 188 #define WM8524_RATES SNDRV_PCM_RATE_8000_192000 189 190 #define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ 191 SNDRV_PCM_FMTBIT_S24_LE |\ 192 SNDRV_PCM_FMTBIT_S32_LE) 193 194 static const struct snd_soc_dai_ops wm8524_dai_ops = { 195 .startup = wm8524_startup, 196 .shutdown = wm8524_shutdown, 197 .set_sysclk = wm8524_set_dai_sysclk, 198 .set_fmt = wm8524_set_fmt, 199 .mute_stream = wm8524_mute_stream, 200 .hw_params = wm8524_hw_params, 201 }; 202 203 static struct snd_soc_dai_driver wm8524_dai = { 204 .name = "wm8524-hifi", 205 .playback = { 206 .stream_name = "Playback", 207 .channels_min = 2, 208 .channels_max = 2, 209 .rates = WM8524_RATES, 210 .formats = WM8524_FORMATS, 211 }, 212 .ops = &wm8524_dai_ops, 213 }; 214 215 static int wm8524_probe(struct snd_soc_component *component) 216 { 217 struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 218 219 wm8524->rate_constraint.list = &wm8524->rate_constraint_list[0]; 220 wm8524->rate_constraint.count = 221 ARRAY_SIZE(wm8524->rate_constraint_list); 222 223 return 0; 224 } 225 226 static const struct snd_soc_component_driver soc_component_dev_wm8524 = { 227 .probe = wm8524_probe, 228 .dapm_widgets = wm8524_dapm_widgets, 229 .num_dapm_widgets = ARRAY_SIZE(wm8524_dapm_widgets), 230 .dapm_routes = wm8524_dapm_routes, 231 .num_dapm_routes = ARRAY_SIZE(wm8524_dapm_routes), 232 .idle_bias_on = 1, 233 .use_pmdown_time = 1, 234 .endianness = 1, 235 }; 236 237 static const struct of_device_id wm8524_of_match[] = { 238 { .compatible = "wlf,wm8524" }, 239 { /* sentinel*/ } 240 }; 241 MODULE_DEVICE_TABLE(of, wm8524_of_match); 242 243 static int wm8524_codec_probe(struct platform_device *pdev) 244 { 245 struct wm8524_priv *wm8524; 246 int ret; 247 248 wm8524 = devm_kzalloc(&pdev->dev, sizeof(struct wm8524_priv), 249 GFP_KERNEL); 250 if (wm8524 == NULL) 251 return -ENOMEM; 252 253 platform_set_drvdata(pdev, wm8524); 254 255 wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW); 256 if (IS_ERR(wm8524->mute)) { 257 ret = PTR_ERR(wm8524->mute); 258 dev_err_probe(&pdev->dev, ret, "Failed to get mute line\n"); 259 return ret; 260 } 261 262 ret = devm_snd_soc_register_component(&pdev->dev, 263 &soc_component_dev_wm8524, &wm8524_dai, 1); 264 if (ret < 0) 265 dev_err(&pdev->dev, "Failed to register component: %d\n", ret); 266 267 return ret; 268 } 269 270 static struct platform_driver wm8524_codec_driver = { 271 .probe = wm8524_codec_probe, 272 .driver = { 273 .name = "wm8524-codec", 274 .of_match_table = wm8524_of_match, 275 }, 276 }; 277 module_platform_driver(wm8524_codec_driver); 278 279 MODULE_DESCRIPTION("ASoC WM8524 driver"); 280 MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); 281 MODULE_ALIAS("platform:wm8524-codec"); 282 MODULE_LICENSE("GPL"); 283