1 /** 2 * Freescale P1022DS ALSA SoC Machine driver 3 * 4 * Author: Timur Tabi <timur@freescale.com> 5 * 6 * Copyright 2010 Freescale Semiconductor, Inc. 7 * 8 * This file is licensed under the terms of the GNU General Public License 9 * version 2. This program is licensed "as is" without any warranty of any 10 * kind, whether express or implied. 11 */ 12 13 #include <linux/module.h> 14 #include <linux/interrupt.h> 15 #include <linux/of_device.h> 16 #include <linux/slab.h> 17 #include <sound/soc.h> 18 #include <asm/fsl_guts.h> 19 20 #include "fsl_dma.h" 21 #include "fsl_ssi.h" 22 #include "fsl_utils.h" 23 24 /* P1022-specific PMUXCR and DMUXCR bit definitions */ 25 26 #define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 27 #define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 28 #define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 29 30 #define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 31 #define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 32 33 #define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ 34 #define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ 35 36 /* 37 * Set the DMACR register in the GUTS 38 * 39 * The DMACR register determines the source of initiated transfers for each 40 * channel on each DMA controller. Rather than have a bunch of repetitive 41 * macros for the bit patterns, we just have a function that calculates 42 * them. 43 * 44 * guts: Pointer to GUTS structure 45 * co: The DMA controller (0 or 1) 46 * ch: The channel on the DMA controller (0, 1, 2, or 3) 47 * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) 48 */ 49 static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts, 50 unsigned int co, unsigned int ch, unsigned int device) 51 { 52 unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); 53 54 clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); 55 } 56 57 /* There's only one global utilities register */ 58 static phys_addr_t guts_phys; 59 60 /** 61 * machine_data: machine-specific ASoC device data 62 * 63 * This structure contains data for a single sound platform device on an 64 * P1022 DS. Some of the data is taken from the device tree. 65 */ 66 struct machine_data { 67 struct snd_soc_dai_link dai[2]; 68 struct snd_soc_card card; 69 unsigned int dai_format; 70 unsigned int codec_clk_direction; 71 unsigned int cpu_clk_direction; 72 unsigned int clk_frequency; 73 unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ 74 unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ 75 unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ 76 char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ 77 }; 78 79 /** 80 * p1022_ds_machine_probe: initialize the board 81 * 82 * This function is used to initialize the board-specific hardware. 83 * 84 * Here we program the DMACR and PMUXCR registers. 85 */ 86 static int p1022_ds_machine_probe(struct snd_soc_card *card) 87 { 88 struct machine_data *mdata = 89 container_of(card, struct machine_data, card); 90 struct ccsr_guts __iomem *guts; 91 92 guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); 93 if (!guts) { 94 dev_err(card->dev, "could not map global utilities\n"); 95 return -ENOMEM; 96 } 97 98 /* Enable SSI Tx signal */ 99 clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, 100 CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); 101 102 /* Enable SSI Rx signal */ 103 clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, 104 CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); 105 106 /* Enable DMA Channel for SSI */ 107 guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 108 CCSR_GUTS_DMUXCR_SSI); 109 110 guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 111 CCSR_GUTS_DMUXCR_SSI); 112 113 iounmap(guts); 114 115 return 0; 116 } 117 118 /** 119 * p1022_ds_startup: program the board with various hardware parameters 120 * 121 * This function takes board-specific information, like clock frequencies 122 * and serial data formats, and passes that information to the codec and 123 * transport drivers. 124 */ 125 static int p1022_ds_startup(struct snd_pcm_substream *substream) 126 { 127 struct snd_soc_pcm_runtime *rtd = substream->private_data; 128 struct machine_data *mdata = 129 container_of(rtd->card, struct machine_data, card); 130 struct device *dev = rtd->card->dev; 131 int ret = 0; 132 133 /* Tell the codec driver what the serial protocol is. */ 134 ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); 135 if (ret < 0) { 136 dev_err(dev, "could not set codec driver audio format\n"); 137 return ret; 138 } 139 140 /* 141 * Tell the codec driver what the MCLK frequency is, and whether it's 142 * a slave or master. 143 */ 144 ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency, 145 mdata->codec_clk_direction); 146 if (ret < 0) { 147 dev_err(dev, "could not set codec driver clock params\n"); 148 return ret; 149 } 150 151 return 0; 152 } 153 154 /** 155 * p1022_ds_machine_remove: Remove the sound device 156 * 157 * This function is called to remove the sound device for one SSI. We 158 * de-program the DMACR and PMUXCR register. 159 */ 160 static int p1022_ds_machine_remove(struct snd_soc_card *card) 161 { 162 struct machine_data *mdata = 163 container_of(card, struct machine_data, card); 164 struct ccsr_guts __iomem *guts; 165 166 guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); 167 if (!guts) { 168 dev_err(card->dev, "could not map global utilities\n"); 169 return -ENOMEM; 170 } 171 172 /* Restore the signal routing */ 173 clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); 174 clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); 175 guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0); 176 guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0); 177 178 iounmap(guts); 179 180 return 0; 181 } 182 183 /** 184 * p1022_ds_ops: ASoC machine driver operations 185 */ 186 static struct snd_soc_ops p1022_ds_ops = { 187 .startup = p1022_ds_startup, 188 }; 189 190 /** 191 * p1022_ds_probe: platform probe function for the machine driver 192 * 193 * Although this is a machine driver, the SSI node is the "master" node with 194 * respect to audio hardware connections. Therefore, we create a new ASoC 195 * device for each new SSI node that has a codec attached. 196 */ 197 static int p1022_ds_probe(struct platform_device *pdev) 198 { 199 struct device *dev = pdev->dev.parent; 200 /* ssi_pdev is the platform device for the SSI node that probed us */ 201 struct platform_device *ssi_pdev = 202 container_of(dev, struct platform_device, dev); 203 struct device_node *np = ssi_pdev->dev.of_node; 204 struct device_node *codec_np = NULL; 205 struct machine_data *mdata; 206 int ret = -ENODEV; 207 const char *sprop; 208 const u32 *iprop; 209 210 /* Find the codec node for this SSI. */ 211 codec_np = of_parse_phandle(np, "codec-handle", 0); 212 if (!codec_np) { 213 dev_err(dev, "could not find codec node\n"); 214 return -EINVAL; 215 } 216 217 mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); 218 if (!mdata) { 219 ret = -ENOMEM; 220 goto error_put; 221 } 222 223 mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); 224 mdata->dai[0].ops = &p1022_ds_ops; 225 226 /* ASoC core can match codec with device node */ 227 mdata->dai[0].codec_of_node = codec_np; 228 229 /* We register two DAIs per SSI, one for playback and the other for 230 * capture. We support codecs that have separate DAIs for both playback 231 * and capture. 232 */ 233 memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); 234 235 /* The DAI names from the codec (snd_soc_dai_driver.name) */ 236 mdata->dai[0].codec_dai_name = "wm8776-hifi-playback"; 237 mdata->dai[1].codec_dai_name = "wm8776-hifi-capture"; 238 239 /* Get the device ID */ 240 iprop = of_get_property(np, "cell-index", NULL); 241 if (!iprop) { 242 dev_err(&pdev->dev, "cell-index property not found\n"); 243 ret = -EINVAL; 244 goto error; 245 } 246 mdata->ssi_id = be32_to_cpup(iprop); 247 248 /* Get the serial format and clock direction. */ 249 sprop = of_get_property(np, "fsl,mode", NULL); 250 if (!sprop) { 251 dev_err(&pdev->dev, "fsl,mode property not found\n"); 252 ret = -EINVAL; 253 goto error; 254 } 255 256 if (strcasecmp(sprop, "i2s-slave") == 0) { 257 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 258 SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; 259 mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; 260 mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; 261 262 /* In i2s-slave mode, the codec has its own clock source, so we 263 * need to get the frequency from the device tree and pass it to 264 * the codec driver. 265 */ 266 iprop = of_get_property(codec_np, "clock-frequency", NULL); 267 if (!iprop || !*iprop) { 268 dev_err(&pdev->dev, "codec bus-frequency " 269 "property is missing or invalid\n"); 270 ret = -EINVAL; 271 goto error; 272 } 273 mdata->clk_frequency = be32_to_cpup(iprop); 274 } else if (strcasecmp(sprop, "i2s-master") == 0) { 275 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 276 SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; 277 mdata->codec_clk_direction = SND_SOC_CLOCK_IN; 278 mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; 279 } else if (strcasecmp(sprop, "lj-slave") == 0) { 280 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 281 SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; 282 mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; 283 mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; 284 } else if (strcasecmp(sprop, "lj-master") == 0) { 285 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 286 SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; 287 mdata->codec_clk_direction = SND_SOC_CLOCK_IN; 288 mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; 289 } else if (strcasecmp(sprop, "rj-slave") == 0) { 290 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 291 SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; 292 mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; 293 mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; 294 } else if (strcasecmp(sprop, "rj-master") == 0) { 295 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 296 SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; 297 mdata->codec_clk_direction = SND_SOC_CLOCK_IN; 298 mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; 299 } else if (strcasecmp(sprop, "ac97-slave") == 0) { 300 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 301 SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; 302 mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; 303 mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; 304 } else if (strcasecmp(sprop, "ac97-master") == 0) { 305 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 306 SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; 307 mdata->codec_clk_direction = SND_SOC_CLOCK_IN; 308 mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; 309 } else { 310 dev_err(&pdev->dev, 311 "unrecognized fsl,mode property '%s'\n", sprop); 312 ret = -EINVAL; 313 goto error; 314 } 315 316 if (!mdata->clk_frequency) { 317 dev_err(&pdev->dev, "unknown clock frequency\n"); 318 ret = -EINVAL; 319 goto error; 320 } 321 322 /* Find the playback DMA channel to use. */ 323 mdata->dai[0].platform_name = mdata->platform_name[0]; 324 ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], 325 &mdata->dma_channel_id[0], 326 &mdata->dma_id[0]); 327 if (ret) { 328 dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); 329 goto error; 330 } 331 332 /* Find the capture DMA channel to use. */ 333 mdata->dai[1].platform_name = mdata->platform_name[1]; 334 ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], 335 &mdata->dma_channel_id[1], 336 &mdata->dma_id[1]); 337 if (ret) { 338 dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); 339 goto error; 340 } 341 342 /* Initialize our DAI data structure. */ 343 mdata->dai[0].stream_name = "playback"; 344 mdata->dai[1].stream_name = "capture"; 345 mdata->dai[0].name = mdata->dai[0].stream_name; 346 mdata->dai[1].name = mdata->dai[1].stream_name; 347 348 mdata->card.probe = p1022_ds_machine_probe; 349 mdata->card.remove = p1022_ds_machine_remove; 350 mdata->card.name = pdev->name; /* The platform driver name */ 351 mdata->card.owner = THIS_MODULE; 352 mdata->card.dev = &pdev->dev; 353 mdata->card.num_links = 2; 354 mdata->card.dai_link = mdata->dai; 355 356 /* Register with ASoC */ 357 ret = snd_soc_register_card(&mdata->card); 358 if (ret) { 359 dev_err(&pdev->dev, "could not register card\n"); 360 goto error; 361 } 362 363 of_node_put(codec_np); 364 365 return 0; 366 367 error: 368 kfree(mdata); 369 error_put: 370 of_node_put(codec_np); 371 return ret; 372 } 373 374 /** 375 * p1022_ds_remove: remove the platform device 376 * 377 * This function is called when the platform device is removed. 378 */ 379 static int __devexit p1022_ds_remove(struct platform_device *pdev) 380 { 381 struct snd_soc_card *card = platform_get_drvdata(pdev); 382 struct machine_data *mdata = 383 container_of(card, struct machine_data, card); 384 385 snd_soc_unregister_card(card); 386 kfree(mdata); 387 388 return 0; 389 } 390 391 static struct platform_driver p1022_ds_driver = { 392 .probe = p1022_ds_probe, 393 .remove = __devexit_p(p1022_ds_remove), 394 .driver = { 395 /* 396 * The name must match 'compatible' property in the device tree, 397 * in lowercase letters. 398 */ 399 .name = "snd-soc-p1022ds", 400 .owner = THIS_MODULE, 401 }, 402 }; 403 404 /** 405 * p1022_ds_init: machine driver initialization. 406 * 407 * This function is called when this module is loaded. 408 */ 409 static int __init p1022_ds_init(void) 410 { 411 struct device_node *guts_np; 412 struct resource res; 413 414 /* Get the physical address of the global utilities registers */ 415 guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); 416 if (of_address_to_resource(guts_np, 0, &res)) { 417 pr_err("snd-soc-p1022ds: missing/invalid global utils node\n"); 418 of_node_put(guts_np); 419 return -EINVAL; 420 } 421 guts_phys = res.start; 422 of_node_put(guts_np); 423 424 return platform_driver_register(&p1022_ds_driver); 425 } 426 427 /** 428 * p1022_ds_exit: machine driver exit 429 * 430 * This function is called when this driver is unloaded. 431 */ 432 static void __exit p1022_ds_exit(void) 433 { 434 platform_driver_unregister(&p1022_ds_driver); 435 } 436 437 module_init(p1022_ds_init); 438 module_exit(p1022_ds_exit); 439 440 MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); 441 MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver"); 442 MODULE_LICENSE("GPL v2"); 443