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