Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.11 323 lines 8.3 kB view raw
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/of_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 = platform_get_drvdata(priv->pdev); 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 dapm->bias_level = level; 134 135 return 0; 136} 137 138static int imx_wm8962_late_probe(struct snd_soc_card *card) 139{ 140 struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; 141 struct imx_priv *priv = &card_priv; 142 struct imx_wm8962_data *data = platform_get_drvdata(priv->pdev); 143 struct device *dev = &priv->pdev->dev; 144 int ret; 145 146 ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, 147 data->clk_frequency, SND_SOC_CLOCK_IN); 148 if (ret < 0) 149 dev_err(dev, "failed to set sysclk in %s\n", __func__); 150 151 return ret; 152} 153 154static int imx_wm8962_probe(struct platform_device *pdev) 155{ 156 struct device_node *np = pdev->dev.of_node; 157 struct device_node *ssi_np, *codec_np; 158 struct platform_device *ssi_pdev; 159 struct imx_priv *priv = &card_priv; 160 struct i2c_client *codec_dev; 161 struct imx_wm8962_data *data; 162 int int_port, ext_port; 163 int ret; 164 165 priv->pdev = pdev; 166 167 ret = of_property_read_u32(np, "mux-int-port", &int_port); 168 if (ret) { 169 dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); 170 return ret; 171 } 172 ret = of_property_read_u32(np, "mux-ext-port", &ext_port); 173 if (ret) { 174 dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); 175 return ret; 176 } 177 178 /* 179 * The port numbering in the hardware manual starts at 1, while 180 * the audmux API expects it starts at 0. 181 */ 182 int_port--; 183 ext_port--; 184 ret = imx_audmux_v2_configure_port(int_port, 185 IMX_AUDMUX_V2_PTCR_SYN | 186 IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | 187 IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | 188 IMX_AUDMUX_V2_PTCR_TFSDIR | 189 IMX_AUDMUX_V2_PTCR_TCLKDIR, 190 IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); 191 if (ret) { 192 dev_err(&pdev->dev, "audmux internal port setup failed\n"); 193 return ret; 194 } 195 imx_audmux_v2_configure_port(ext_port, 196 IMX_AUDMUX_V2_PTCR_SYN, 197 IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); 198 if (ret) { 199 dev_err(&pdev->dev, "audmux external port setup failed\n"); 200 return ret; 201 } 202 203 ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); 204 codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); 205 if (!ssi_np || !codec_np) { 206 dev_err(&pdev->dev, "phandle missing or invalid\n"); 207 ret = -EINVAL; 208 goto fail; 209 } 210 211 ssi_pdev = of_find_device_by_node(ssi_np); 212 if (!ssi_pdev) { 213 dev_err(&pdev->dev, "failed to find SSI platform device\n"); 214 ret = -EINVAL; 215 goto fail; 216 } 217 codec_dev = of_find_i2c_device_by_node(codec_np); 218 if (!codec_dev || !codec_dev->driver) { 219 dev_err(&pdev->dev, "failed to find codec platform device\n"); 220 return -EINVAL; 221 } 222 223 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 224 if (!data) { 225 ret = -ENOMEM; 226 goto fail; 227 } 228 229 data->codec_clk = devm_clk_get(&codec_dev->dev, NULL); 230 if (IS_ERR(data->codec_clk)) { 231 ret = PTR_ERR(data->codec_clk); 232 dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); 233 goto fail; 234 } 235 236 data->clk_frequency = clk_get_rate(data->codec_clk); 237 ret = clk_prepare_enable(data->codec_clk); 238 if (ret) { 239 dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret); 240 goto fail; 241 } 242 243 data->dai.name = "HiFi"; 244 data->dai.stream_name = "HiFi"; 245 data->dai.codec_dai_name = "wm8962"; 246 data->dai.codec_of_node = codec_np; 247 data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev); 248 data->dai.platform_of_node = ssi_np; 249 data->dai.ops = &imx_hifi_ops; 250 data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 251 SND_SOC_DAIFMT_CBM_CFM; 252 253 data->card.dev = &pdev->dev; 254 ret = snd_soc_of_parse_card_name(&data->card, "model"); 255 if (ret) 256 goto clk_fail; 257 ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); 258 if (ret) 259 goto clk_fail; 260 data->card.num_links = 1; 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 ret = snd_soc_register_card(&data->card); 269 if (ret) { 270 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); 271 goto clk_fail; 272 } 273 274 platform_set_drvdata(pdev, data); 275 of_node_put(ssi_np); 276 of_node_put(codec_np); 277 278 return 0; 279 280clk_fail: 281 if (!IS_ERR(data->codec_clk)) 282 clk_disable_unprepare(data->codec_clk); 283fail: 284 if (ssi_np) 285 of_node_put(ssi_np); 286 if (codec_np) 287 of_node_put(codec_np); 288 289 return ret; 290} 291 292static int imx_wm8962_remove(struct platform_device *pdev) 293{ 294 struct imx_wm8962_data *data = platform_get_drvdata(pdev); 295 296 if (!IS_ERR(data->codec_clk)) 297 clk_disable_unprepare(data->codec_clk); 298 snd_soc_unregister_card(&data->card); 299 300 return 0; 301} 302 303static const struct of_device_id imx_wm8962_dt_ids[] = { 304 { .compatible = "fsl,imx-audio-wm8962", }, 305 { /* sentinel */ } 306}; 307MODULE_DEVICE_TABLE(of, imx_wm8962_dt_ids); 308 309static struct platform_driver imx_wm8962_driver = { 310 .driver = { 311 .name = "imx-wm8962", 312 .owner = THIS_MODULE, 313 .of_match_table = imx_wm8962_dt_ids, 314 }, 315 .probe = imx_wm8962_probe, 316 .remove = imx_wm8962_remove, 317}; 318module_platform_driver(imx_wm8962_driver); 319 320MODULE_AUTHOR("Freescale Semiconductor, Inc."); 321MODULE_DESCRIPTION("Freescale i.MX WM8962 ASoC machine driver"); 322MODULE_LICENSE("GPL v2"); 323MODULE_ALIAS("platform:imx-wm8962");