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