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