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 platform_device *sound_device = NULL; 206 struct machine_data *mdata; 207 int ret = -ENODEV; 208 const char *sprop; 209 const u32 *iprop; 210 211 /* Find the codec node for this SSI. */ 212 codec_np = of_parse_phandle(np, "codec-handle", 0); 213 if (!codec_np) { 214 dev_err(dev, "could not find codec node\n"); 215 return -EINVAL; 216 } 217 218 mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); 219 if (!mdata) { 220 ret = -ENOMEM; 221 goto error_put; 222 } 223 224 mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); 225 mdata->dai[0].ops = &p1022_ds_ops; 226 227 /* ASoC core can match codec with device node */ 228 mdata->dai[0].codec_of_node = codec_np; 229 230 /* We register two DAIs per SSI, one for playback and the other for 231 * capture. We support codecs that have separate DAIs for both playback 232 * and capture. 233 */ 234 memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); 235 236 /* The DAI names from the codec (snd_soc_dai_driver.name) */ 237 mdata->dai[0].codec_dai_name = "wm8776-hifi-playback"; 238 mdata->dai[1].codec_dai_name = "wm8776-hifi-capture"; 239 240 /* Get the device ID */ 241 iprop = of_get_property(np, "cell-index", NULL); 242 if (!iprop) { 243 dev_err(&pdev->dev, "cell-index property not found\n"); 244 ret = -EINVAL; 245 goto error; 246 } 247 mdata->ssi_id = be32_to_cpup(iprop); 248 249 /* Get the serial format and clock direction. */ 250 sprop = of_get_property(np, "fsl,mode", NULL); 251 if (!sprop) { 252 dev_err(&pdev->dev, "fsl,mode property not found\n"); 253 ret = -EINVAL; 254 goto error; 255 } 256 257 if (strcasecmp(sprop, "i2s-slave") == 0) { 258 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 259 SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; 260 mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; 261 mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; 262 263 /* In i2s-slave mode, the codec has its own clock source, so we 264 * need to get the frequency from the device tree and pass it to 265 * the codec driver. 266 */ 267 iprop = of_get_property(codec_np, "clock-frequency", NULL); 268 if (!iprop || !*iprop) { 269 dev_err(&pdev->dev, "codec bus-frequency " 270 "property is missing or invalid\n"); 271 ret = -EINVAL; 272 goto error; 273 } 274 mdata->clk_frequency = be32_to_cpup(iprop); 275 } else if (strcasecmp(sprop, "i2s-master") == 0) { 276 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 277 SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; 278 mdata->codec_clk_direction = SND_SOC_CLOCK_IN; 279 mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; 280 } else if (strcasecmp(sprop, "lj-slave") == 0) { 281 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 282 SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; 283 mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; 284 mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; 285 } else if (strcasecmp(sprop, "lj-master") == 0) { 286 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 287 SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; 288 mdata->codec_clk_direction = SND_SOC_CLOCK_IN; 289 mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; 290 } else if (strcasecmp(sprop, "rj-slave") == 0) { 291 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 292 SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; 293 mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; 294 mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; 295 } else if (strcasecmp(sprop, "rj-master") == 0) { 296 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 297 SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; 298 mdata->codec_clk_direction = SND_SOC_CLOCK_IN; 299 mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; 300 } else if (strcasecmp(sprop, "ac97-slave") == 0) { 301 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 302 SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; 303 mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; 304 mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; 305 } else if (strcasecmp(sprop, "ac97-master") == 0) { 306 mdata->dai_format = SND_SOC_DAIFMT_NB_NF | 307 SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; 308 mdata->codec_clk_direction = SND_SOC_CLOCK_IN; 309 mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; 310 } else { 311 dev_err(&pdev->dev, 312 "unrecognized fsl,mode property '%s'\n", sprop); 313 ret = -EINVAL; 314 goto error; 315 } 316 317 if (!mdata->clk_frequency) { 318 dev_err(&pdev->dev, "unknown clock frequency\n"); 319 ret = -EINVAL; 320 goto error; 321 } 322 323 /* Find the playback DMA channel to use. */ 324 mdata->dai[0].platform_name = mdata->platform_name[0]; 325 ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], 326 &mdata->dma_channel_id[0], 327 &mdata->dma_id[0]); 328 if (ret) { 329 dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); 330 goto error; 331 } 332 333 /* Find the capture DMA channel to use. */ 334 mdata->dai[1].platform_name = mdata->platform_name[1]; 335 ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], 336 &mdata->dma_channel_id[1], 337 &mdata->dma_id[1]); 338 if (ret) { 339 dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); 340 goto error; 341 } 342 343 /* Initialize our DAI data structure. */ 344 mdata->dai[0].stream_name = "playback"; 345 mdata->dai[1].stream_name = "capture"; 346 mdata->dai[0].name = mdata->dai[0].stream_name; 347 mdata->dai[1].name = mdata->dai[1].stream_name; 348 349 mdata->card.probe = p1022_ds_machine_probe; 350 mdata->card.remove = p1022_ds_machine_remove; 351 mdata->card.name = pdev->name; /* The platform driver name */ 352 mdata->card.num_links = 2; 353 mdata->card.dai_link = mdata->dai; 354 355 /* Allocate a new audio platform device structure */ 356 sound_device = platform_device_alloc("soc-audio", -1); 357 if (!sound_device) { 358 dev_err(&pdev->dev, "platform device alloc failed\n"); 359 ret = -ENOMEM; 360 goto error; 361 } 362 363 /* Associate the card data with the sound device */ 364 platform_set_drvdata(sound_device, &mdata->card); 365 366 /* Register with ASoC */ 367 ret = platform_device_add(sound_device); 368 if (ret) { 369 dev_err(&pdev->dev, "platform device add failed\n"); 370 goto error; 371 } 372 dev_set_drvdata(&pdev->dev, sound_device); 373 374 of_node_put(codec_np); 375 376 return 0; 377 378 error: 379 if (sound_device) 380 platform_device_put(sound_device); 381 382 kfree(mdata); 383 error_put: 384 of_node_put(codec_np); 385 return ret; 386 } 387 388 /** 389 * p1022_ds_remove: remove the platform device 390 * 391 * This function is called when the platform device is removed. 392 */ 393 static int __devexit p1022_ds_remove(struct platform_device *pdev) 394 { 395 struct platform_device *sound_device = dev_get_drvdata(&pdev->dev); 396 struct snd_soc_card *card = platform_get_drvdata(sound_device); 397 struct machine_data *mdata = 398 container_of(card, struct machine_data, card); 399 400 platform_device_unregister(sound_device); 401 402 kfree(mdata); 403 sound_device->dev.platform_data = NULL; 404 405 dev_set_drvdata(&pdev->dev, NULL); 406 407 return 0; 408 } 409 410 static struct platform_driver p1022_ds_driver = { 411 .probe = p1022_ds_probe, 412 .remove = __devexit_p(p1022_ds_remove), 413 .driver = { 414 /* 415 * The name must match 'compatible' property in the device tree, 416 * in lowercase letters. 417 */ 418 .name = "snd-soc-p1022ds", 419 .owner = THIS_MODULE, 420 }, 421 }; 422 423 /** 424 * p1022_ds_init: machine driver initialization. 425 * 426 * This function is called when this module is loaded. 427 */ 428 static int __init p1022_ds_init(void) 429 { 430 struct device_node *guts_np; 431 struct resource res; 432 433 /* Get the physical address of the global utilities registers */ 434 guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); 435 if (of_address_to_resource(guts_np, 0, &res)) { 436 pr_err("snd-soc-p1022ds: missing/invalid global utils node\n"); 437 of_node_put(guts_np); 438 return -EINVAL; 439 } 440 guts_phys = res.start; 441 of_node_put(guts_np); 442 443 return platform_driver_register(&p1022_ds_driver); 444 } 445 446 /** 447 * p1022_ds_exit: machine driver exit 448 * 449 * This function is called when this driver is unloaded. 450 */ 451 static void __exit p1022_ds_exit(void) 452 { 453 platform_driver_unregister(&p1022_ds_driver); 454 } 455 456 module_init(p1022_ds_init); 457 module_exit(p1022_ds_exit); 458 459 MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); 460 MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver"); 461 MODULE_LICENSE("GPL v2"); 462