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