1 /** 2 * Freescale MPC8610HPCD ALSA SoC Fabric driver 3 * 4 * Author: Timur Tabi <timur@freescale.com> 5 * 6 * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed 7 * under the terms of the GNU General Public License version 2. This 8 * program is licensed "as is" without any warranty of any kind, whether 9 * express or implied. 10 */ 11 12 #include <linux/module.h> 13 #include <linux/interrupt.h> 14 #include <linux/of_device.h> 15 #include <linux/of_platform.h> 16 #include <sound/soc.h> 17 #include <asm/immap_86xx.h> 18 19 #include "../codecs/cs4270.h" 20 #include "fsl_dma.h" 21 #include "fsl_ssi.h" 22 23 /** 24 * mpc8610_hpcd_data: fabric-specific ASoC device data 25 * 26 * This structure contains data for a single sound platform device on an 27 * MPC8610 HPCD. Some of the data is taken from the device tree. 28 */ 29 struct mpc8610_hpcd_data { 30 struct snd_soc_device sound_devdata; 31 struct snd_soc_dai_link dai; 32 struct snd_soc_card machine; 33 unsigned int dai_format; 34 unsigned int codec_clk_direction; 35 unsigned int cpu_clk_direction; 36 unsigned int clk_frequency; 37 struct ccsr_guts __iomem *guts; 38 struct ccsr_ssi __iomem *ssi; 39 unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ 40 unsigned int ssi_irq; 41 unsigned int dma_id; /* 0 = DMA1, 1 = DMA2, etc */ 42 unsigned int dma_irq[2]; 43 struct ccsr_dma_channel __iomem *dma[2]; 44 unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ 45 }; 46 47 /** 48 * mpc8610_hpcd_machine_probe: initalize the board 49 * 50 * This function is called when platform_device_add() is called. It is used 51 * to initialize the board-specific hardware. 52 * 53 * Here we program the DMACR and PMUXCR registers. 54 */ 55 static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device) 56 { 57 struct mpc8610_hpcd_data *machine_data = 58 sound_device->dev.platform_data; 59 60 /* Program the signal routing between the SSI and the DMA */ 61 guts_set_dmacr(machine_data->guts, machine_data->dma_id, 62 machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI); 63 guts_set_dmacr(machine_data->guts, machine_data->dma_id, 64 machine_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI); 65 66 guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id, 67 machine_data->dma_channel_id[0], 0); 68 guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id, 69 machine_data->dma_channel_id[1], 0); 70 71 switch (machine_data->ssi_id) { 72 case 0: 73 clrsetbits_be32(&machine_data->guts->pmuxcr, 74 CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI); 75 break; 76 case 1: 77 clrsetbits_be32(&machine_data->guts->pmuxcr, 78 CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI); 79 break; 80 } 81 82 return 0; 83 } 84 85 /** 86 * mpc8610_hpcd_startup: program the board with various hardware parameters 87 * 88 * This function takes board-specific information, like clock frequencies 89 * and serial data formats, and passes that information to the codec and 90 * transport drivers. 91 */ 92 static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream) 93 { 94 struct snd_soc_pcm_runtime *rtd = substream->private_data; 95 struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; 96 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; 97 struct mpc8610_hpcd_data *machine_data = 98 rtd->socdev->dev->platform_data; 99 int ret = 0; 100 101 /* Tell the CPU driver what the serial protocol is. */ 102 ret = snd_soc_dai_set_fmt(cpu_dai, machine_data->dai_format); 103 if (ret < 0) { 104 dev_err(substream->pcm->card->dev, 105 "could not set CPU driver audio format\n"); 106 return ret; 107 } 108 109 /* Tell the codec driver what the serial protocol is. */ 110 ret = snd_soc_dai_set_fmt(codec_dai, machine_data->dai_format); 111 if (ret < 0) { 112 dev_err(substream->pcm->card->dev, 113 "could not set codec driver audio format\n"); 114 return ret; 115 } 116 117 /* 118 * Tell the CPU driver what the clock frequency is, and whether it's a 119 * slave or master. 120 */ 121 ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 122 machine_data->clk_frequency, 123 machine_data->cpu_clk_direction); 124 if (ret < 0) { 125 dev_err(substream->pcm->card->dev, 126 "could not set CPU driver clock parameters\n"); 127 return ret; 128 } 129 130 /* 131 * Tell the codec driver what the MCLK frequency is, and whether it's 132 * a slave or master. 133 */ 134 ret = snd_soc_dai_set_sysclk(codec_dai, 0, 135 machine_data->clk_frequency, 136 machine_data->codec_clk_direction); 137 if (ret < 0) { 138 dev_err(substream->pcm->card->dev, 139 "could not set codec driver clock params\n"); 140 return ret; 141 } 142 143 return 0; 144 } 145 146 /** 147 * mpc8610_hpcd_machine_remove: Remove the sound device 148 * 149 * This function is called to remove the sound device for one SSI. We 150 * de-program the DMACR and PMUXCR register. 151 */ 152 int mpc8610_hpcd_machine_remove(struct platform_device *sound_device) 153 { 154 struct mpc8610_hpcd_data *machine_data = 155 sound_device->dev.platform_data; 156 157 /* Restore the signal routing */ 158 159 guts_set_dmacr(machine_data->guts, machine_data->dma_id, 160 machine_data->dma_channel_id[0], 0); 161 guts_set_dmacr(machine_data->guts, machine_data->dma_id, 162 machine_data->dma_channel_id[1], 0); 163 164 switch (machine_data->ssi_id) { 165 case 0: 166 clrsetbits_be32(&machine_data->guts->pmuxcr, 167 CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA); 168 break; 169 case 1: 170 clrsetbits_be32(&machine_data->guts->pmuxcr, 171 CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA); 172 break; 173 } 174 175 return 0; 176 } 177 178 /** 179 * mpc8610_hpcd_ops: ASoC fabric driver operations 180 */ 181 static struct snd_soc_ops mpc8610_hpcd_ops = { 182 .startup = mpc8610_hpcd_startup, 183 }; 184 185 /** 186 * mpc8610_hpcd_probe: OF probe function for the fabric driver 187 * 188 * This function gets called when an SSI node is found in the device tree. 189 * 190 * Although this is a fabric driver, the SSI node is the "master" node with 191 * respect to audio hardware connections. Therefore, we create a new ASoC 192 * device for each new SSI node that has a codec attached. 193 * 194 * FIXME: Currently, we only support one DMA controller, so if there are 195 * multiple SSI nodes with codecs, only the first will be supported. 196 * 197 * FIXME: Even if we did support multiple DMA controllers, we have no 198 * mechanism for assigning DMA controllers and channels to the individual 199 * SSI devices. We also probably aren't compatible with the generic Elo DMA 200 * device driver. 201 */ 202 static int mpc8610_hpcd_probe(struct of_device *ofdev, 203 const struct of_device_id *match) 204 { 205 struct device_node *np = ofdev->node; 206 struct device_node *codec_np = NULL; 207 struct device_node *guts_np = NULL; 208 struct device_node *dma_np = NULL; 209 struct device_node *dma_channel_np = NULL; 210 const phandle *codec_ph; 211 const char *sprop; 212 const u32 *iprop; 213 struct resource res; 214 struct platform_device *sound_device = NULL; 215 struct mpc8610_hpcd_data *machine_data; 216 struct fsl_ssi_info ssi_info; 217 struct fsl_dma_info dma_info; 218 int ret = -ENODEV; 219 unsigned int playback_dma_channel; 220 unsigned int capture_dma_channel; 221 222 machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL); 223 if (!machine_data) 224 return -ENOMEM; 225 226 memset(&ssi_info, 0, sizeof(ssi_info)); 227 memset(&dma_info, 0, sizeof(dma_info)); 228 229 ssi_info.dev = &ofdev->dev; 230 231 /* 232 * We are only interested in SSIs with a codec phandle in them, so let's 233 * make sure this SSI has one. 234 */ 235 codec_ph = of_get_property(np, "codec-handle", NULL); 236 if (!codec_ph) 237 goto error; 238 239 codec_np = of_find_node_by_phandle(*codec_ph); 240 if (!codec_np) 241 goto error; 242 243 /* The MPC8610 HPCD only knows about the CS4270 codec, so reject 244 anything else. */ 245 if (!of_device_is_compatible(codec_np, "cirrus,cs4270")) 246 goto error; 247 248 /* Get the device ID */ 249 iprop = of_get_property(np, "cell-index", NULL); 250 if (!iprop) { 251 dev_err(&ofdev->dev, "cell-index property not found\n"); 252 ret = -EINVAL; 253 goto error; 254 } 255 machine_data->ssi_id = *iprop; 256 ssi_info.id = *iprop; 257 258 /* Get the serial format and clock direction. */ 259 sprop = of_get_property(np, "fsl,mode", NULL); 260 if (!sprop) { 261 dev_err(&ofdev->dev, "fsl,mode property not found\n"); 262 ret = -EINVAL; 263 goto error; 264 } 265 266 if (strcasecmp(sprop, "i2s-slave") == 0) { 267 machine_data->dai_format = SND_SOC_DAIFMT_I2S; 268 machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; 269 machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; 270 271 /* 272 * In i2s-slave mode, the codec has its own clock source, so we 273 * need to get the frequency from the device tree and pass it to 274 * the codec driver. 275 */ 276 iprop = of_get_property(codec_np, "clock-frequency", NULL); 277 if (!iprop || !*iprop) { 278 dev_err(&ofdev->dev, "codec bus-frequency property " 279 "is missing or invalid\n"); 280 ret = -EINVAL; 281 goto error; 282 } 283 machine_data->clk_frequency = *iprop; 284 } else if (strcasecmp(sprop, "i2s-master") == 0) { 285 machine_data->dai_format = SND_SOC_DAIFMT_I2S; 286 machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; 287 machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; 288 } else if (strcasecmp(sprop, "lj-slave") == 0) { 289 machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J; 290 machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; 291 machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; 292 } else if (strcasecmp(sprop, "lj-master") == 0) { 293 machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J; 294 machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; 295 machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; 296 } else if (strcasecmp(sprop, "rj-slave") == 0) { 297 machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J; 298 machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; 299 machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; 300 } else if (strcasecmp(sprop, "rj-master") == 0) { 301 machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J; 302 machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; 303 machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; 304 } else if (strcasecmp(sprop, "ac97-slave") == 0) { 305 machine_data->dai_format = SND_SOC_DAIFMT_AC97; 306 machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; 307 machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; 308 } else if (strcasecmp(sprop, "ac97-master") == 0) { 309 machine_data->dai_format = SND_SOC_DAIFMT_AC97; 310 machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; 311 machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; 312 } else { 313 dev_err(&ofdev->dev, 314 "unrecognized fsl,mode property \"%s\"\n", sprop); 315 ret = -EINVAL; 316 goto error; 317 } 318 319 if (!machine_data->clk_frequency) { 320 dev_err(&ofdev->dev, "unknown clock frequency\n"); 321 ret = -EINVAL; 322 goto error; 323 } 324 325 /* Read the SSI information from the device tree */ 326 ret = of_address_to_resource(np, 0, &res); 327 if (ret) { 328 dev_err(&ofdev->dev, "could not obtain SSI address\n"); 329 goto error; 330 } 331 if (!res.start) { 332 dev_err(&ofdev->dev, "invalid SSI address\n"); 333 goto error; 334 } 335 ssi_info.ssi_phys = res.start; 336 337 machine_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi)); 338 if (!machine_data->ssi) { 339 dev_err(&ofdev->dev, "could not map SSI address %x\n", 340 ssi_info.ssi_phys); 341 ret = -EINVAL; 342 goto error; 343 } 344 ssi_info.ssi = machine_data->ssi; 345 346 347 /* Get the IRQ of the SSI */ 348 machine_data->ssi_irq = irq_of_parse_and_map(np, 0); 349 if (!machine_data->ssi_irq) { 350 dev_err(&ofdev->dev, "could not get SSI IRQ\n"); 351 ret = -EINVAL; 352 goto error; 353 } 354 ssi_info.irq = machine_data->ssi_irq; 355 356 /* Do we want to use asynchronous mode? */ 357 ssi_info.asynchronous = 358 of_find_property(np, "fsl,ssi-asynchronous", NULL) ? 1 : 0; 359 if (ssi_info.asynchronous) 360 dev_info(&ofdev->dev, "using asynchronous mode\n"); 361 362 /* Map the global utilities registers. */ 363 guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts"); 364 if (!guts_np) { 365 dev_err(&ofdev->dev, "could not obtain address of GUTS\n"); 366 ret = -EINVAL; 367 goto error; 368 } 369 machine_data->guts = of_iomap(guts_np, 0); 370 of_node_put(guts_np); 371 if (!machine_data->guts) { 372 dev_err(&ofdev->dev, "could not map GUTS\n"); 373 ret = -EINVAL; 374 goto error; 375 } 376 377 /* Find the DMA channels to use. Both SSIs need to use the same DMA 378 * controller, so let's use DMA#1. 379 */ 380 for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") { 381 iprop = of_get_property(dma_np, "cell-index", NULL); 382 if (iprop && (*iprop == 0)) { 383 of_node_put(dma_np); 384 break; 385 } 386 } 387 if (!dma_np) { 388 dev_err(&ofdev->dev, "could not find DMA node\n"); 389 ret = -EINVAL; 390 goto error; 391 } 392 machine_data->dma_id = *iprop; 393 394 /* SSI1 needs to use DMA Channels 0 and 1, and SSI2 needs to use DMA 395 * channels 2 and 3. This is just how the MPC8610 is wired 396 * internally. 397 */ 398 playback_dma_channel = (machine_data->ssi_id == 0) ? 0 : 2; 399 capture_dma_channel = (machine_data->ssi_id == 0) ? 1 : 3; 400 401 /* 402 * Find the DMA channels to use. 403 */ 404 while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) { 405 iprop = of_get_property(dma_channel_np, "cell-index", NULL); 406 if (iprop && (*iprop == playback_dma_channel)) { 407 /* dma_channel[0] and dma_irq[0] are for playback */ 408 dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0); 409 dma_info.dma_irq[0] = 410 irq_of_parse_and_map(dma_channel_np, 0); 411 machine_data->dma_channel_id[0] = *iprop; 412 continue; 413 } 414 if (iprop && (*iprop == capture_dma_channel)) { 415 /* dma_channel[1] and dma_irq[1] are for capture */ 416 dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0); 417 dma_info.dma_irq[1] = 418 irq_of_parse_and_map(dma_channel_np, 0); 419 machine_data->dma_channel_id[1] = *iprop; 420 continue; 421 } 422 } 423 if (!dma_info.dma_channel[0] || !dma_info.dma_channel[1] || 424 !dma_info.dma_irq[0] || !dma_info.dma_irq[1]) { 425 dev_err(&ofdev->dev, "could not find DMA channels\n"); 426 ret = -EINVAL; 427 goto error; 428 } 429 430 dma_info.ssi_stx_phys = ssi_info.ssi_phys + 431 offsetof(struct ccsr_ssi, stx0); 432 dma_info.ssi_srx_phys = ssi_info.ssi_phys + 433 offsetof(struct ccsr_ssi, srx0); 434 435 /* We have the DMA information, so tell the DMA driver what it is */ 436 if (!fsl_dma_configure(&dma_info)) { 437 dev_err(&ofdev->dev, "could not instantiate DMA device\n"); 438 ret = -EBUSY; 439 goto error; 440 } 441 442 /* 443 * Initialize our DAI data structure. We should probably get this 444 * information from the device tree. 445 */ 446 machine_data->dai.name = "CS4270"; 447 machine_data->dai.stream_name = "CS4270"; 448 449 machine_data->dai.cpu_dai = fsl_ssi_create_dai(&ssi_info); 450 machine_data->dai.codec_dai = &cs4270_dai; /* The codec_dai we want */ 451 machine_data->dai.ops = &mpc8610_hpcd_ops; 452 453 machine_data->machine.probe = mpc8610_hpcd_machine_probe; 454 machine_data->machine.remove = mpc8610_hpcd_machine_remove; 455 machine_data->machine.name = "MPC8610 HPCD"; 456 machine_data->machine.num_links = 1; 457 machine_data->machine.dai_link = &machine_data->dai; 458 459 /* Allocate a new audio platform device structure */ 460 sound_device = platform_device_alloc("soc-audio", -1); 461 if (!sound_device) { 462 dev_err(&ofdev->dev, "platform device allocation failed\n"); 463 ret = -ENOMEM; 464 goto error; 465 } 466 467 machine_data->sound_devdata.card = &machine_data->machine; 468 machine_data->sound_devdata.codec_dev = &soc_codec_device_cs4270; 469 machine_data->machine.platform = &fsl_soc_platform; 470 471 sound_device->dev.platform_data = machine_data; 472 473 474 /* Set the platform device and ASoC device to point to each other */ 475 platform_set_drvdata(sound_device, &machine_data->sound_devdata); 476 477 machine_data->sound_devdata.dev = &sound_device->dev; 478 479 480 /* Tell ASoC to probe us. This will call mpc8610_hpcd_machine.probe(), 481 if it exists. */ 482 ret = platform_device_add(sound_device); 483 484 if (ret) { 485 dev_err(&ofdev->dev, "platform device add failed\n"); 486 goto error; 487 } 488 489 dev_set_drvdata(&ofdev->dev, sound_device); 490 491 return 0; 492 493 error: 494 of_node_put(codec_np); 495 of_node_put(guts_np); 496 of_node_put(dma_np); 497 of_node_put(dma_channel_np); 498 499 if (sound_device) 500 platform_device_unregister(sound_device); 501 502 if (machine_data->dai.cpu_dai) 503 fsl_ssi_destroy_dai(machine_data->dai.cpu_dai); 504 505 if (ssi_info.ssi) 506 iounmap(ssi_info.ssi); 507 508 if (ssi_info.irq) 509 irq_dispose_mapping(ssi_info.irq); 510 511 if (dma_info.dma_channel[0]) 512 iounmap(dma_info.dma_channel[0]); 513 514 if (dma_info.dma_channel[1]) 515 iounmap(dma_info.dma_channel[1]); 516 517 if (dma_info.dma_irq[0]) 518 irq_dispose_mapping(dma_info.dma_irq[0]); 519 520 if (dma_info.dma_irq[1]) 521 irq_dispose_mapping(dma_info.dma_irq[1]); 522 523 if (machine_data->guts) 524 iounmap(machine_data->guts); 525 526 kfree(machine_data); 527 528 return ret; 529 } 530 531 /** 532 * mpc8610_hpcd_remove: remove the OF device 533 * 534 * This function is called when the OF device is removed. 535 */ 536 static int mpc8610_hpcd_remove(struct of_device *ofdev) 537 { 538 struct platform_device *sound_device = dev_get_drvdata(&ofdev->dev); 539 struct mpc8610_hpcd_data *machine_data = 540 sound_device->dev.platform_data; 541 542 platform_device_unregister(sound_device); 543 544 if (machine_data->dai.cpu_dai) 545 fsl_ssi_destroy_dai(machine_data->dai.cpu_dai); 546 547 if (machine_data->ssi) 548 iounmap(machine_data->ssi); 549 550 if (machine_data->dma[0]) 551 iounmap(machine_data->dma[0]); 552 553 if (machine_data->dma[1]) 554 iounmap(machine_data->dma[1]); 555 556 if (machine_data->dma_irq[0]) 557 irq_dispose_mapping(machine_data->dma_irq[0]); 558 559 if (machine_data->dma_irq[1]) 560 irq_dispose_mapping(machine_data->dma_irq[1]); 561 562 if (machine_data->guts) 563 iounmap(machine_data->guts); 564 565 kfree(machine_data); 566 sound_device->dev.platform_data = NULL; 567 568 dev_set_drvdata(&ofdev->dev, NULL); 569 570 return 0; 571 } 572 573 static struct of_device_id mpc8610_hpcd_match[] = { 574 { 575 .compatible = "fsl,mpc8610-ssi", 576 }, 577 {} 578 }; 579 MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match); 580 581 static struct of_platform_driver mpc8610_hpcd_of_driver = { 582 .owner = THIS_MODULE, 583 .name = "mpc8610_hpcd", 584 .match_table = mpc8610_hpcd_match, 585 .probe = mpc8610_hpcd_probe, 586 .remove = mpc8610_hpcd_remove, 587 }; 588 589 /** 590 * mpc8610_hpcd_init: fabric driver initialization. 591 * 592 * This function is called when this module is loaded. 593 */ 594 static int __init mpc8610_hpcd_init(void) 595 { 596 int ret; 597 598 printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n"); 599 600 ret = of_register_platform_driver(&mpc8610_hpcd_of_driver); 601 602 if (ret) 603 printk(KERN_ERR 604 "mpc8610-hpcd: failed to register platform driver\n"); 605 606 return ret; 607 } 608 609 /** 610 * mpc8610_hpcd_exit: fabric driver exit 611 * 612 * This function is called when this driver is unloaded. 613 */ 614 static void __exit mpc8610_hpcd_exit(void) 615 { 616 of_unregister_platform_driver(&mpc8610_hpcd_of_driver); 617 } 618 619 module_init(mpc8610_hpcd_init); 620 module_exit(mpc8610_hpcd_exit); 621 622 MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); 623 MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver"); 624 MODULE_LICENSE("GPL"); 625