1*d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2007b6a54SMihai Serban /* 3007b6a54SMihai Serban * wm8524.c -- WM8524 ALSA SoC Audio driver 4007b6a54SMihai Serban * 5007b6a54SMihai Serban * Copyright 2009 Wolfson Microelectronics plc 6007b6a54SMihai Serban * Copyright 2017 NXP 7007b6a54SMihai Serban * 8007b6a54SMihai Serban * Based on WM8523 ALSA SoC Audio driver written by Mark Brown 9007b6a54SMihai Serban */ 10007b6a54SMihai Serban 11007b6a54SMihai Serban #include <linux/module.h> 12007b6a54SMihai Serban #include <linux/moduleparam.h> 13007b6a54SMihai Serban #include <linux/init.h> 14007b6a54SMihai Serban #include <linux/delay.h> 15007b6a54SMihai Serban #include <linux/slab.h> 16007b6a54SMihai Serban #include <linux/gpio/consumer.h> 17007b6a54SMihai Serban #include <linux/of_device.h> 18007b6a54SMihai Serban #include <sound/core.h> 19007b6a54SMihai Serban #include <sound/pcm.h> 20007b6a54SMihai Serban #include <sound/pcm_params.h> 21007b6a54SMihai Serban #include <sound/soc.h> 22007b6a54SMihai Serban #include <sound/initval.h> 23007b6a54SMihai Serban 24007b6a54SMihai Serban #define WM8524_NUM_RATES 7 25007b6a54SMihai Serban 26007b6a54SMihai Serban /* codec private data */ 27007b6a54SMihai Serban struct wm8524_priv { 28007b6a54SMihai Serban struct gpio_desc *mute; 29007b6a54SMihai Serban unsigned int sysclk; 30007b6a54SMihai Serban unsigned int rate_constraint_list[WM8524_NUM_RATES]; 31007b6a54SMihai Serban struct snd_pcm_hw_constraint_list rate_constraint; 32007b6a54SMihai Serban }; 33007b6a54SMihai Serban 34007b6a54SMihai Serban 35007b6a54SMihai Serban static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = { 36007b6a54SMihai Serban SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), 37007b6a54SMihai Serban SND_SOC_DAPM_OUTPUT("LINEVOUTL"), 38007b6a54SMihai Serban SND_SOC_DAPM_OUTPUT("LINEVOUTR"), 39007b6a54SMihai Serban }; 40007b6a54SMihai Serban 41007b6a54SMihai Serban static const struct snd_soc_dapm_route wm8524_dapm_routes[] = { 42007b6a54SMihai Serban { "LINEVOUTL", NULL, "DAC" }, 43007b6a54SMihai Serban { "LINEVOUTR", NULL, "DAC" }, 44007b6a54SMihai Serban }; 45007b6a54SMihai Serban 46007b6a54SMihai Serban static const struct { 47007b6a54SMihai Serban int value; 48007b6a54SMihai Serban int ratio; 49007b6a54SMihai Serban } lrclk_ratios[WM8524_NUM_RATES] = { 50007b6a54SMihai Serban { 1, 128 }, 51007b6a54SMihai Serban { 2, 192 }, 52007b6a54SMihai Serban { 3, 256 }, 53007b6a54SMihai Serban { 4, 384 }, 54007b6a54SMihai Serban { 5, 512 }, 55007b6a54SMihai Serban { 6, 768 }, 56007b6a54SMihai Serban { 7, 1152 }, 57007b6a54SMihai Serban }; 58007b6a54SMihai Serban 59007b6a54SMihai Serban static int wm8524_startup(struct snd_pcm_substream *substream, 60007b6a54SMihai Serban struct snd_soc_dai *dai) 61007b6a54SMihai Serban { 623b60cf10SKuninori Morimoto struct snd_soc_component *component = dai->component; 633b60cf10SKuninori Morimoto struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 64007b6a54SMihai Serban 65007b6a54SMihai Serban /* The set of sample rates that can be supported depends on the 66007b6a54SMihai Serban * MCLK supplied to the CODEC - enforce this. 67007b6a54SMihai Serban */ 68007b6a54SMihai Serban if (!wm8524->sysclk) { 693b60cf10SKuninori Morimoto dev_err(component->dev, 70007b6a54SMihai Serban "No MCLK configured, call set_sysclk() on init\n"); 71007b6a54SMihai Serban return -EINVAL; 72007b6a54SMihai Serban } 73007b6a54SMihai Serban 74007b6a54SMihai Serban snd_pcm_hw_constraint_list(substream->runtime, 0, 75007b6a54SMihai Serban SNDRV_PCM_HW_PARAM_RATE, 76007b6a54SMihai Serban &wm8524->rate_constraint); 77007b6a54SMihai Serban 78007b6a54SMihai Serban gpiod_set_value_cansleep(wm8524->mute, 1); 79007b6a54SMihai Serban 80007b6a54SMihai Serban return 0; 81007b6a54SMihai Serban } 82007b6a54SMihai Serban 83007b6a54SMihai Serban static void wm8524_shutdown(struct snd_pcm_substream *substream, 84007b6a54SMihai Serban struct snd_soc_dai *dai) 85007b6a54SMihai Serban { 863b60cf10SKuninori Morimoto struct snd_soc_component *component = dai->component; 873b60cf10SKuninori Morimoto struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 88007b6a54SMihai Serban 89007b6a54SMihai Serban gpiod_set_value_cansleep(wm8524->mute, 0); 90007b6a54SMihai Serban } 91007b6a54SMihai Serban 92007b6a54SMihai Serban static int wm8524_set_dai_sysclk(struct snd_soc_dai *codec_dai, 93007b6a54SMihai Serban int clk_id, unsigned int freq, int dir) 94007b6a54SMihai Serban { 953b60cf10SKuninori Morimoto struct snd_soc_component *component = codec_dai->component; 963b60cf10SKuninori Morimoto struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 97007b6a54SMihai Serban unsigned int val; 98007b6a54SMihai Serban int i, j = 0; 99007b6a54SMihai Serban 100007b6a54SMihai Serban wm8524->sysclk = freq; 101007b6a54SMihai Serban 102007b6a54SMihai Serban wm8524->rate_constraint.count = 0; 103007b6a54SMihai Serban for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { 104007b6a54SMihai Serban val = freq / lrclk_ratios[i].ratio; 105007b6a54SMihai Serban /* Check that it's a standard rate since core can't 106007b6a54SMihai Serban * cope with others and having the odd rates confuses 107007b6a54SMihai Serban * constraint matching. 108007b6a54SMihai Serban */ 109007b6a54SMihai Serban switch (val) { 110007b6a54SMihai Serban case 8000: 111007b6a54SMihai Serban case 32000: 112007b6a54SMihai Serban case 44100: 113007b6a54SMihai Serban case 48000: 114007b6a54SMihai Serban case 88200: 115007b6a54SMihai Serban case 96000: 116007b6a54SMihai Serban case 176400: 117007b6a54SMihai Serban case 192000: 1183b60cf10SKuninori Morimoto dev_dbg(component->dev, "Supported sample rate: %dHz\n", 119007b6a54SMihai Serban val); 120007b6a54SMihai Serban wm8524->rate_constraint_list[j++] = val; 121007b6a54SMihai Serban wm8524->rate_constraint.count++; 122007b6a54SMihai Serban break; 123007b6a54SMihai Serban default: 1243b60cf10SKuninori Morimoto dev_dbg(component->dev, "Skipping sample rate: %dHz\n", 125007b6a54SMihai Serban val); 126007b6a54SMihai Serban } 127007b6a54SMihai Serban } 128007b6a54SMihai Serban 129007b6a54SMihai Serban /* Need at least one supported rate... */ 130007b6a54SMihai Serban if (wm8524->rate_constraint.count == 0) 131007b6a54SMihai Serban return -EINVAL; 132007b6a54SMihai Serban 133007b6a54SMihai Serban return 0; 134007b6a54SMihai Serban } 135007b6a54SMihai Serban 136007b6a54SMihai Serban static int wm8524_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) 137007b6a54SMihai Serban { 138007b6a54SMihai Serban fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK | 139007b6a54SMihai Serban SND_SOC_DAIFMT_MASTER_MASK); 140007b6a54SMihai Serban 141007b6a54SMihai Serban if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 142007b6a54SMihai Serban SND_SOC_DAIFMT_CBS_CFS)) { 143007b6a54SMihai Serban dev_err(codec_dai->dev, "Invalid DAI format\n"); 144007b6a54SMihai Serban return -EINVAL; 145007b6a54SMihai Serban } 146007b6a54SMihai Serban 147007b6a54SMihai Serban return 0; 148007b6a54SMihai Serban } 149007b6a54SMihai Serban 150007b6a54SMihai Serban static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream) 151007b6a54SMihai Serban { 1523b60cf10SKuninori Morimoto struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(dai->component); 153007b6a54SMihai Serban 154007b6a54SMihai Serban if (wm8524->mute) 155007b6a54SMihai Serban gpiod_set_value_cansleep(wm8524->mute, mute); 156007b6a54SMihai Serban 157007b6a54SMihai Serban return 0; 158007b6a54SMihai Serban } 159007b6a54SMihai Serban 160007b6a54SMihai Serban #define WM8524_RATES SNDRV_PCM_RATE_8000_192000 161007b6a54SMihai Serban 162007b6a54SMihai Serban #define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) 163007b6a54SMihai Serban 164007b6a54SMihai Serban static const struct snd_soc_dai_ops wm8524_dai_ops = { 165007b6a54SMihai Serban .startup = wm8524_startup, 166007b6a54SMihai Serban .shutdown = wm8524_shutdown, 167007b6a54SMihai Serban .set_sysclk = wm8524_set_dai_sysclk, 168007b6a54SMihai Serban .set_fmt = wm8524_set_fmt, 169007b6a54SMihai Serban .mute_stream = wm8524_mute_stream, 170007b6a54SMihai Serban }; 171007b6a54SMihai Serban 172007b6a54SMihai Serban static struct snd_soc_dai_driver wm8524_dai = { 173007b6a54SMihai Serban .name = "wm8524-hifi", 174007b6a54SMihai Serban .playback = { 175007b6a54SMihai Serban .stream_name = "Playback", 176007b6a54SMihai Serban .channels_min = 2, 177007b6a54SMihai Serban .channels_max = 2, 178007b6a54SMihai Serban .rates = WM8524_RATES, 179007b6a54SMihai Serban .formats = WM8524_FORMATS, 180007b6a54SMihai Serban }, 181007b6a54SMihai Serban .ops = &wm8524_dai_ops, 182007b6a54SMihai Serban }; 183007b6a54SMihai Serban 1843b60cf10SKuninori Morimoto static int wm8524_probe(struct snd_soc_component *component) 185007b6a54SMihai Serban { 1863b60cf10SKuninori Morimoto struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); 187007b6a54SMihai Serban 188007b6a54SMihai Serban wm8524->rate_constraint.list = &wm8524->rate_constraint_list[0]; 189007b6a54SMihai Serban wm8524->rate_constraint.count = 190007b6a54SMihai Serban ARRAY_SIZE(wm8524->rate_constraint_list); 191007b6a54SMihai Serban 192007b6a54SMihai Serban return 0; 193007b6a54SMihai Serban } 194007b6a54SMihai Serban 1953b60cf10SKuninori Morimoto static const struct snd_soc_component_driver soc_component_dev_wm8524 = { 196007b6a54SMihai Serban .probe = wm8524_probe, 197007b6a54SMihai Serban .dapm_widgets = wm8524_dapm_widgets, 198007b6a54SMihai Serban .num_dapm_widgets = ARRAY_SIZE(wm8524_dapm_widgets), 199007b6a54SMihai Serban .dapm_routes = wm8524_dapm_routes, 200007b6a54SMihai Serban .num_dapm_routes = ARRAY_SIZE(wm8524_dapm_routes), 2013b60cf10SKuninori Morimoto .idle_bias_on = 1, 2023b60cf10SKuninori Morimoto .use_pmdown_time = 1, 2033b60cf10SKuninori Morimoto .endianness = 1, 2043b60cf10SKuninori Morimoto .non_legacy_dai_naming = 1, 205007b6a54SMihai Serban }; 206007b6a54SMihai Serban 207007b6a54SMihai Serban static const struct of_device_id wm8524_of_match[] = { 208007b6a54SMihai Serban { .compatible = "wlf,wm8524" }, 209007b6a54SMihai Serban { /* sentinel*/ } 210007b6a54SMihai Serban }; 211007b6a54SMihai Serban MODULE_DEVICE_TABLE(of, wm8524_of_match); 212007b6a54SMihai Serban 213007b6a54SMihai Serban static int wm8524_codec_probe(struct platform_device *pdev) 214007b6a54SMihai Serban { 215007b6a54SMihai Serban struct wm8524_priv *wm8524; 216007b6a54SMihai Serban int ret; 217007b6a54SMihai Serban 218007b6a54SMihai Serban wm8524 = devm_kzalloc(&pdev->dev, sizeof(struct wm8524_priv), 219007b6a54SMihai Serban GFP_KERNEL); 220007b6a54SMihai Serban if (wm8524 == NULL) 221007b6a54SMihai Serban return -ENOMEM; 222007b6a54SMihai Serban 223007b6a54SMihai Serban platform_set_drvdata(pdev, wm8524); 224007b6a54SMihai Serban 225007b6a54SMihai Serban wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW); 226007b6a54SMihai Serban if (IS_ERR(wm8524->mute)) { 227007b6a54SMihai Serban ret = PTR_ERR(wm8524->mute); 228007b6a54SMihai Serban dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret); 229007b6a54SMihai Serban return ret; 230007b6a54SMihai Serban } 231007b6a54SMihai Serban 2323b60cf10SKuninori Morimoto ret = devm_snd_soc_register_component(&pdev->dev, 2333b60cf10SKuninori Morimoto &soc_component_dev_wm8524, &wm8524_dai, 1); 2346cbcad07SKuninori Morimoto if (ret < 0) 2353b60cf10SKuninori Morimoto dev_err(&pdev->dev, "Failed to register component: %d\n", ret); 236007b6a54SMihai Serban 237007b6a54SMihai Serban return ret; 238007b6a54SMihai Serban } 239007b6a54SMihai Serban 240007b6a54SMihai Serban static struct platform_driver wm8524_codec_driver = { 241007b6a54SMihai Serban .probe = wm8524_codec_probe, 242007b6a54SMihai Serban .driver = { 243007b6a54SMihai Serban .name = "wm8524-codec", 244007b6a54SMihai Serban .of_match_table = wm8524_of_match, 245007b6a54SMihai Serban }, 246007b6a54SMihai Serban }; 247007b6a54SMihai Serban module_platform_driver(wm8524_codec_driver); 248007b6a54SMihai Serban 249007b6a54SMihai Serban MODULE_DESCRIPTION("ASoC WM8524 driver"); 250007b6a54SMihai Serban MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>"); 251007b6a54SMihai Serban MODULE_ALIAS("platform:wm8524-codec"); 252007b6a54SMihai Serban MODULE_LICENSE("GPL"); 253