1/* 2 * Copyright 2013 Freescale Semiconductor, Inc. 3 * 4 * Based on imx-sgtl5000.c 5 * Copyright 2012 Freescale Semiconductor, Inc. 6 * Copyright 2012 Linaro Ltd. 7 * 8 * The code contained herein is licensed under the GNU General Public 9 * License. You may obtain a copy of the GNU General Public License 10 * Version 2 or later at the following locations: 11 * 12 * http://www.opensource.org/licenses/gpl-license.html 13 * http://www.gnu.org/copyleft/gpl.html 14 */ 15 16#include <linux/module.h> 17#include <linux/of_platform.h> 18#include <linux/i2c.h> 19#include <linux/slab.h> 20#include <linux/clk.h> 21#include <sound/soc.h> 22#include <sound/pcm_params.h> 23#include <sound/soc-dapm.h> 24#include <linux/pinctrl/consumer.h> 25 26#include "../codecs/wm8962.h" 27#include "imx-audmux.h" 28 29#define DAI_NAME_SIZE 32 30 31struct imx_wm8962_data { 32 struct snd_soc_dai_link dai; 33 struct snd_soc_card card; 34 char codec_dai_name[DAI_NAME_SIZE]; 35 char platform_name[DAI_NAME_SIZE]; 36 struct clk *codec_clk; 37 unsigned int clk_frequency; 38}; 39 40struct imx_priv { 41 struct platform_device *pdev; 42}; 43static struct imx_priv card_priv; 44 45static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { 46 SND_SOC_DAPM_HP("Headphone Jack", NULL), 47 SND_SOC_DAPM_SPK("Ext Spk", NULL), 48 SND_SOC_DAPM_MIC("AMIC", NULL), 49 SND_SOC_DAPM_MIC("DMIC", NULL), 50}; 51 52static int sample_rate = 44100; 53static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE; 54 55static int imx_hifi_hw_params(struct snd_pcm_substream *substream, 56 struct snd_pcm_hw_params *params) 57{ 58 sample_rate = params_rate(params); 59 sample_format = params_format(params); 60 61 return 0; 62} 63 64static struct snd_soc_ops imx_hifi_ops = { 65 .hw_params = imx_hifi_hw_params, 66}; 67 68static int imx_wm8962_set_bias_level(struct snd_soc_card *card, 69 struct snd_soc_dapm_context *dapm, 70 enum snd_soc_bias_level level) 71{ 72 struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; 73 struct imx_priv *priv = &card_priv; 74 struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); 75 struct device *dev = &priv->pdev->dev; 76 unsigned int pll_out; 77 int ret; 78 79 if (dapm->dev != codec_dai->dev) 80 return 0; 81 82 switch (level) { 83 case SND_SOC_BIAS_PREPARE: 84 if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { 85 if (sample_format == SNDRV_PCM_FORMAT_S24_LE) 86 pll_out = sample_rate * 384; 87 else 88 pll_out = sample_rate * 256; 89 90 ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, 91 WM8962_FLL_MCLK, data->clk_frequency, 92 pll_out); 93 if (ret < 0) { 94 dev_err(dev, "failed to start FLL: %d\n", ret); 95 return ret; 96 } 97 98 ret = snd_soc_dai_set_sysclk(codec_dai, 99 WM8962_SYSCLK_FLL, pll_out, 100 SND_SOC_CLOCK_IN); 101 if (ret < 0) { 102 dev_err(dev, "failed to set SYSCLK: %d\n", ret); 103 return ret; 104 } 105 } 106 break; 107 108 case SND_SOC_BIAS_STANDBY: 109 if (dapm->bias_level == SND_SOC_BIAS_PREPARE) { 110 ret = snd_soc_dai_set_sysclk(codec_dai, 111 WM8962_SYSCLK_MCLK, data->clk_frequency, 112 SND_SOC_CLOCK_IN); 113 if (ret < 0) { 114 dev_err(dev, 115 "failed to switch away from FLL: %d\n", 116 ret); 117 return ret; 118 } 119 120 ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, 121 0, 0, 0); 122 if (ret < 0) { 123 dev_err(dev, "failed to stop FLL: %d\n", ret); 124 return ret; 125 } 126 } 127 break; 128 129 default: 130 break; 131 } 132 133 return 0; 134} 135 136static int imx_wm8962_late_probe(struct snd_soc_card *card) 137{ 138 struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; 139 struct imx_priv *priv = &card_priv; 140 struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); 141 struct device *dev = &priv->pdev->dev; 142 int ret; 143 144 ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, 145 data->clk_frequency, SND_SOC_CLOCK_IN); 146 if (ret < 0) 147 dev_err(dev, "failed to set sysclk in %s\n", __func__); 148 149 return ret; 150} 151 152static int imx_wm8962_probe(struct platform_device *pdev) 153{ 154 struct device_node *np = pdev->dev.of_node; 155 struct device_node *ssi_np, *codec_np; 156 struct platform_device *ssi_pdev; 157 struct imx_priv *priv = &card_priv; 158 struct i2c_client *codec_dev; 159 struct imx_wm8962_data *data; 160 int int_port, ext_port; 161 int ret; 162 163 priv->pdev = pdev; 164 165 ret = of_property_read_u32(np, "mux-int-port", &int_port); 166 if (ret) { 167 dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); 168 return ret; 169 } 170 ret = of_property_read_u32(np, "mux-ext-port", &ext_port); 171 if (ret) { 172 dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); 173 return ret; 174 } 175 176 /* 177 * The port numbering in the hardware manual starts at 1, while 178 * the audmux API expects it starts at 0. 179 */ 180 int_port--; 181 ext_port--; 182 ret = imx_audmux_v2_configure_port(int_port, 183 IMX_AUDMUX_V2_PTCR_SYN | 184 IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | 185 IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | 186 IMX_AUDMUX_V2_PTCR_TFSDIR | 187 IMX_AUDMUX_V2_PTCR_TCLKDIR, 188 IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); 189 if (ret) { 190 dev_err(&pdev->dev, "audmux internal port setup failed\n"); 191 return ret; 192 } 193 ret = imx_audmux_v2_configure_port(ext_port, 194 IMX_AUDMUX_V2_PTCR_SYN, 195 IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); 196 if (ret) { 197 dev_err(&pdev->dev, "audmux external port setup failed\n"); 198 return ret; 199 } 200 201 ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); 202 codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); 203 if (!ssi_np || !codec_np) { 204 dev_err(&pdev->dev, "phandle missing or invalid\n"); 205 ret = -EINVAL; 206 goto fail; 207 } 208 209 ssi_pdev = of_find_device_by_node(ssi_np); 210 if (!ssi_pdev) { 211 dev_err(&pdev->dev, "failed to find SSI platform device\n"); 212 ret = -EINVAL; 213 goto fail; 214 } 215 codec_dev = of_find_i2c_device_by_node(codec_np); 216 if (!codec_dev || !codec_dev->dev.driver) { 217 dev_err(&pdev->dev, "failed to find codec platform device\n"); 218 ret = -EINVAL; 219 goto fail; 220 } 221 222 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 223 if (!data) { 224 ret = -ENOMEM; 225 goto fail; 226 } 227 228 data->codec_clk = devm_clk_get(&codec_dev->dev, NULL); 229 if (IS_ERR(data->codec_clk)) { 230 ret = PTR_ERR(data->codec_clk); 231 dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); 232 goto fail; 233 } 234 235 data->clk_frequency = clk_get_rate(data->codec_clk); 236 ret = clk_prepare_enable(data->codec_clk); 237 if (ret) { 238 dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret); 239 goto fail; 240 } 241 242 data->dai.name = "HiFi"; 243 data->dai.stream_name = "HiFi"; 244 data->dai.codec_dai_name = "wm8962"; 245 data->dai.codec_of_node = codec_np; 246 data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev); 247 data->dai.platform_of_node = ssi_np; 248 data->dai.ops = &imx_hifi_ops; 249 data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 250 SND_SOC_DAIFMT_CBM_CFM; 251 252 data->card.dev = &pdev->dev; 253 ret = snd_soc_of_parse_card_name(&data->card, "model"); 254 if (ret) 255 goto clk_fail; 256 ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); 257 if (ret) 258 goto clk_fail; 259 data->card.num_links = 1; 260 data->card.owner = THIS_MODULE; 261 data->card.dai_link = &data->dai; 262 data->card.dapm_widgets = imx_wm8962_dapm_widgets; 263 data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets); 264 265 data->card.late_probe = imx_wm8962_late_probe; 266 data->card.set_bias_level = imx_wm8962_set_bias_level; 267 268 platform_set_drvdata(pdev, &data->card); 269 snd_soc_card_set_drvdata(&data->card, data); 270 271 ret = devm_snd_soc_register_card(&pdev->dev, &data->card); 272 if (ret) { 273 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); 274 goto clk_fail; 275 } 276 277 of_node_put(ssi_np); 278 of_node_put(codec_np); 279 280 return 0; 281 282clk_fail: 283 clk_disable_unprepare(data->codec_clk); 284fail: 285 of_node_put(ssi_np); 286 of_node_put(codec_np); 287 288 return ret; 289} 290 291static int imx_wm8962_remove(struct platform_device *pdev) 292{ 293 struct snd_soc_card *card = platform_get_drvdata(pdev); 294 struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); 295 296 if (!IS_ERR(data->codec_clk)) 297 clk_disable_unprepare(data->codec_clk); 298 299 return 0; 300} 301 302static const struct of_device_id imx_wm8962_dt_ids[] = { 303 { .compatible = "fsl,imx-audio-wm8962", }, 304 { /* sentinel */ } 305}; 306MODULE_DEVICE_TABLE(of, imx_wm8962_dt_ids); 307 308static struct platform_driver imx_wm8962_driver = { 309 .driver = { 310 .name = "imx-wm8962", 311 .pm = &snd_soc_pm_ops, 312 .of_match_table = imx_wm8962_dt_ids, 313 }, 314 .probe = imx_wm8962_probe, 315 .remove = imx_wm8962_remove, 316}; 317module_platform_driver(imx_wm8962_driver); 318 319MODULE_AUTHOR("Freescale Semiconductor, Inc."); 320MODULE_DESCRIPTION("Freescale i.MX WM8962 ASoC machine driver"); 321MODULE_LICENSE("GPL v2"); 322MODULE_ALIAS("platform:imx-wm8962"); 323