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