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