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_address.h> 16 #include <linux/of_device.h> 17 #include <linux/slab.h> 18 #include <sound/soc.h> 19 #include <asm/fsl_guts.h> 20 21 #include "fsl_dma.h" 22 #include "fsl_ssi.h" 23 #include "fsl_utils.h" 24 25 /* P1022-specific PMUXCR and DMUXCR bit definitions */ 26 27 #define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 28 #define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 29 #define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 30 31 #define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 32 #define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 33 34 #define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ 35 #define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ 36 37 /* 38 * Set the DMACR register in the GUTS 39 * 40 * The DMACR register determines the source of initiated transfers for each 41 * channel on each DMA controller. Rather than have a bunch of repetitive 42 * macros for the bit patterns, we just have a function that calculates 43 * them. 44 * 45 * guts: Pointer to GUTS structure 46 * co: The DMA controller (0 or 1) 47 * ch: The channel on the DMA controller (0, 1, 2, or 3) 48 * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) 49 */ 50 static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts, 51 unsigned int co, unsigned int ch, unsigned int device) 52 { 53 unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); 54 55 clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); 56 } 57 58 /* There's only one global utilities register */ 59 static phys_addr_t guts_phys; 60 61 /** 62 * machine_data: machine-specific ASoC device data 63 * 64 * This structure contains data for a single sound platform device on an 65 * P1022 DS. Some of the data is taken from the device tree. 66 */ 67 struct machine_data { 68 struct snd_soc_dai_link dai[2]; 69 struct snd_soc_card card; 70 unsigned int dai_format; 71 unsigned int codec_clk_direction; 72 unsigned int cpu_clk_direction; 73 unsigned int clk_frequency; 74 unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ 75 unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ 76 unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ 77 char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ 78 }; 79 80 /** 81 * p1022_ds_machine_probe: initialize the board 82 * 83 * This function is used to initialize the board-specific hardware. 84 * 85 * Here we program the DMACR and PMUXCR registers. 86 */ 87 static int p1022_ds_machine_probe(struct snd_soc_card *card) 88 { 89 struct machine_data *mdata = 90 container_of(card, struct machine_data, card); 91 struct ccsr_guts __iomem *guts; 92 93 guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); 94 if (!guts) { 95 dev_err(card->dev, "could not map global utilities\n"); 96 return -ENOMEM; 97 } 98 99 /* Enable SSI Tx signal */ 100 clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, 101 CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); 102 103 /* Enable SSI Rx signal */ 104 clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, 105 CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); 106 107 /* Enable DMA Channel for SSI */ 108 guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 109 CCSR_GUTS_DMUXCR_SSI); 110 111 guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 112 CCSR_GUTS_DMUXCR_SSI); 113 114 iounmap(guts); 115 116 return 0; 117 } 118 119 /** 120 * p1022_ds_startup: program the board with various hardware parameters 121 * 122 * This function takes board-specific information, like clock frequencies 123 * and serial data formats, and passes that information to the codec and 124 * transport drivers. 125 */ 126 static int p1022_ds_startup(struct snd_pcm_substream *substream) 127 { 128 struct snd_soc_pcm_runtime *rtd = substream->private_data; 129 struct machine_data *mdata = 130 container_of(rtd->card, struct machine_data, card); 131 struct device *dev = rtd->card->dev; 132 int ret = 0; 133 134 /* Tell the codec driver what the serial protocol is. */ 135 ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); 136 if (ret < 0) { 137 dev_err(dev, "could not set codec driver audio format\n"); 138 return ret; 139 } 140 141 /* 142 * Tell the codec driver what the MCLK frequency is, and whether it's 143 * a slave or master. 144 */ 145 ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency, 146 mdata->codec_clk_direction); 147 if (ret < 0) { 148 dev_err(dev, "could not set codec driver clock params\n"); 149 return ret; 150 } 151 152 return 0; 153 } 154 155 /** 156 * p1022_ds_machine_remove: Remove the sound device 157 * 158 * This function is called to remove the sound device for one SSI. We 159 * de-program the DMACR and PMUXCR register. 160 */ 161 static int p1022_ds_machine_remove(struct snd_soc_card *card) 162 { 163 struct machine_data *mdata = 164 container_of(card, struct machine_data, card); 165 struct ccsr_guts __iomem *guts; 166 167 guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); 168 if (!guts) { 169 dev_err(card->dev, "could not map global utilities\n"); 170 return -ENOMEM; 171 } 172 173 /* Restore the signal routing */ 174 clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); 175 clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); 176 guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0); 177 guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0); 178 179 iounmap(guts); 180 181 return 0; 182 } 183 184 /** 185 * p1022_ds_ops: ASoC machine driver operations 186 */ 187 static struct snd_soc_ops p1022_ds_ops = { 188 .startup = p1022_ds_startup, 189 }; 190 191 /** 192 * p1022_ds_probe: platform probe function for the machine driver 193 * 194 * Although this is a machine driver, the SSI node is the "master" node with 195 * respect to audio hardware connections. Therefore, we create a new ASoC 196 * device for each new SSI node that has a codec attached. 197 */ 198 static int p1022_ds_probe(struct platform_device *pdev) 199 { 200 struct device *dev = pdev->dev.parent; 201 /* ssi_pdev is the platform device for the SSI node that probed us */ 202 struct platform_device *ssi_pdev = 203 container_of(dev, struct platform_device, dev); 204 struct device_node *np = ssi_pdev->dev.of_node; 205 struct device_node *codec_np = 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.owner = THIS_MODULE; 353 mdata->card.dev = &pdev->dev; 354 mdata->card.num_links = 2; 355 mdata->card.dai_link = mdata->dai; 356 357 /* Register with ASoC */ 358 ret = snd_soc_register_card(&mdata->card); 359 if (ret) { 360 dev_err(&pdev->dev, "could not register card\n"); 361 goto error; 362 } 363 364 of_node_put(codec_np); 365 366 return 0; 367 368 error: 369 kfree(mdata); 370 error_put: 371 of_node_put(codec_np); 372 return ret; 373 } 374 375 /** 376 * p1022_ds_remove: remove the platform device 377 * 378 * This function is called when the platform device is removed. 379 */ 380 static int p1022_ds_remove(struct platform_device *pdev) 381 { 382 struct snd_soc_card *card = platform_get_drvdata(pdev); 383 struct machine_data *mdata = 384 container_of(card, struct machine_data, card); 385 386 snd_soc_unregister_card(card); 387 kfree(mdata); 388 389 return 0; 390 } 391 392 static struct platform_driver p1022_ds_driver = { 393 .probe = p1022_ds_probe, 394 .remove = p1022_ds_remove, 395 .driver = { 396 /* 397 * The name must match 'compatible' property in the device tree, 398 * in lowercase letters. 399 */ 400 .name = "snd-soc-p1022ds", 401 .owner = THIS_MODULE, 402 }, 403 }; 404 405 /** 406 * p1022_ds_init: machine driver initialization. 407 * 408 * This function is called when this module is loaded. 409 */ 410 static int __init p1022_ds_init(void) 411 { 412 struct device_node *guts_np; 413 struct resource res; 414 415 /* Get the physical address of the global utilities registers */ 416 guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); 417 if (of_address_to_resource(guts_np, 0, &res)) { 418 pr_err("snd-soc-p1022ds: missing/invalid global utils node\n"); 419 of_node_put(guts_np); 420 return -EINVAL; 421 } 422 guts_phys = res.start; 423 of_node_put(guts_np); 424 425 return platform_driver_register(&p1022_ds_driver); 426 } 427 428 /** 429 * p1022_ds_exit: machine driver exit 430 * 431 * This function is called when this driver is unloaded. 432 */ 433 static void __exit p1022_ds_exit(void) 434 { 435 platform_driver_unregister(&p1022_ds_driver); 436 } 437 438 module_init(p1022_ds_init); 439 module_exit(p1022_ds_exit); 440 441 MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); 442 MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver"); 443 MODULE_LICENSE("GPL v2"); 444