1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // Freescale MPC8610HPCD ALSA SoC Machine driver 4 // 5 // Author: Timur Tabi <timur@freescale.com> 6 // 7 // Copyright 2007-2010 Freescale Semiconductor, Inc. 8 9 #include <linux/module.h> 10 #include <linux/interrupt.h> 11 #include <linux/fsl/guts.h> 12 #include <linux/of_address.h> 13 #include <linux/of_device.h> 14 #include <linux/slab.h> 15 #include <sound/soc.h> 16 17 #include "fsl_dma.h" 18 #include "fsl_ssi.h" 19 #include "fsl_utils.h" 20 21 /* There's only one global utilities register */ 22 static phys_addr_t guts_phys; 23 24 /** 25 * mpc8610_hpcd_data: machine-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 */ 30 struct mpc8610_hpcd_data { 31 struct snd_soc_dai_link dai[2]; 32 struct snd_soc_card card; 33 unsigned int dai_format; 34 unsigned int codec_clk_direction; 35 unsigned int cpu_clk_direction; 36 unsigned int clk_frequency; 37 unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ 38 unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ 39 unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ 40 char codec_dai_name[DAI_NAME_SIZE]; 41 char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ 42 }; 43 44 /** 45 * mpc8610_hpcd_machine_probe: initialize the board 46 * 47 * This function is used to initialize the board-specific hardware. 48 * 49 * Here we program the DMACR and PMUXCR registers. 50 */ 51 static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card) 52 { 53 struct mpc8610_hpcd_data *machine_data = 54 container_of(card, struct mpc8610_hpcd_data, card); 55 struct ccsr_guts __iomem *guts; 56 57 guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); 58 if (!guts) { 59 dev_err(card->dev, "could not map global utilities\n"); 60 return -ENOMEM; 61 } 62 63 /* Program the signal routing between the SSI and the DMA */ 64 guts_set_dmacr(guts, machine_data->dma_id[0], 65 machine_data->dma_channel_id[0], 66 CCSR_GUTS_DMACR_DEV_SSI); 67 guts_set_dmacr(guts, machine_data->dma_id[1], 68 machine_data->dma_channel_id[1], 69 CCSR_GUTS_DMACR_DEV_SSI); 70 71 guts_set_pmuxcr_dma(guts, machine_data->dma_id[0], 72 machine_data->dma_channel_id[0], 0); 73 guts_set_pmuxcr_dma(guts, machine_data->dma_id[1], 74 machine_data->dma_channel_id[1], 0); 75 76 switch (machine_data->ssi_id) { 77 case 0: 78 clrsetbits_be32(&guts->pmuxcr, 79 CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI); 80 break; 81 case 1: 82 clrsetbits_be32(&guts->pmuxcr, 83 CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI); 84 break; 85 } 86 87 iounmap(guts); 88 89 return 0; 90 } 91 92 /** 93 * mpc8610_hpcd_startup: program the board with various hardware parameters 94 * 95 * This function takes board-specific information, like clock frequencies 96 * and serial data formats, and passes that information to the codec and 97 * transport drivers. 98 */ 99 static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream) 100 { 101 struct snd_soc_pcm_runtime *rtd = substream->private_data; 102 struct mpc8610_hpcd_data *machine_data = 103 container_of(rtd->card, struct mpc8610_hpcd_data, card); 104 struct device *dev = rtd->card->dev; 105 int ret = 0; 106 107 /* Tell the codec driver what the serial protocol is. */ 108 ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format); 109 if (ret < 0) { 110 dev_err(dev, "could not set codec driver audio format\n"); 111 return ret; 112 } 113 114 /* 115 * Tell the codec driver what the MCLK frequency is, and whether it's 116 * a slave or master. 117 */ 118 ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, 119 machine_data->clk_frequency, 120 machine_data->codec_clk_direction); 121 if (ret < 0) { 122 dev_err(dev, "could not set codec driver clock params\n"); 123 return ret; 124 } 125 126 return 0; 127 } 128 129 /** 130 * mpc8610_hpcd_machine_remove: Remove the sound device 131 * 132 * This function is called to remove the sound device for one SSI. We 133 * de-program the DMACR and PMUXCR register. 134 */ 135 static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card) 136 { 137 struct mpc8610_hpcd_data *machine_data = 138 container_of(card, struct mpc8610_hpcd_data, card); 139 struct ccsr_guts __iomem *guts; 140 141 guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); 142 if (!guts) { 143 dev_err(card->dev, "could not map global utilities\n"); 144 return -ENOMEM; 145 } 146 147 /* Restore the signal routing */ 148 149 guts_set_dmacr(guts, machine_data->dma_id[0], 150 machine_data->dma_channel_id[0], 0); 151 guts_set_dmacr(guts, machine_data->dma_id[1], 152 machine_data->dma_channel_id[1], 0); 153 154 switch (machine_data->ssi_id) { 155 case 0: 156 clrsetbits_be32(&guts->pmuxcr, 157 CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA); 158 break; 159 case 1: 160 clrsetbits_be32(&guts->pmuxcr, 161 CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA); 162 break; 163 } 164 165 iounmap(guts); 166 167 return 0; 168 } 169 170 /** 171 * mpc8610_hpcd_ops: ASoC machine driver operations 172 */ 173 static const struct snd_soc_ops mpc8610_hpcd_ops = { 174 .startup = mpc8610_hpcd_startup, 175 }; 176 177 /** 178 * mpc8610_hpcd_probe: platform probe function for the machine driver 179 * 180 * Although this is a machine driver, the SSI node is the "master" node with 181 * respect to audio hardware connections. Therefore, we create a new ASoC 182 * device for each new SSI node that has a codec attached. 183 */ 184 static int mpc8610_hpcd_probe(struct platform_device *pdev) 185 { 186 struct device *dev = pdev->dev.parent; 187 /* ssi_pdev is the platform device for the SSI node that probed us */ 188 struct platform_device *ssi_pdev = to_platform_device(dev); 189 struct device_node *np = ssi_pdev->dev.of_node; 190 struct device_node *codec_np = NULL; 191 struct mpc8610_hpcd_data *machine_data; 192 struct snd_soc_dai_link_component *comp; 193 int ret = -ENODEV; 194 const char *sprop; 195 const u32 *iprop; 196 197 /* Find the codec node for this SSI. */ 198 codec_np = of_parse_phandle(np, "codec-handle", 0); 199 if (!codec_np) { 200 dev_err(dev, "invalid codec node\n"); 201 return -EINVAL; 202 } 203 204 machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL); 205 if (!machine_data) { 206 ret = -ENOMEM; 207 goto error_alloc; 208 } 209 210 comp = devm_kzalloc(&pdev->dev, 6 * sizeof(*comp), GFP_KERNEL); 211 if (!comp) { 212 ret = -ENOMEM; 213 goto error_alloc; 214 } 215 216 machine_data->dai[0].cpus = &comp[0]; 217 machine_data->dai[0].codecs = &comp[1]; 218 machine_data->dai[0].platforms = &comp[2]; 219 220 machine_data->dai[0].num_cpus = 1; 221 machine_data->dai[0].num_codecs = 1; 222 machine_data->dai[0].num_platforms = 1; 223 224 machine_data->dai[1].cpus = &comp[3]; 225 machine_data->dai[1].codecs = &comp[4]; 226 machine_data->dai[1].platforms = &comp[5]; 227 228 machine_data->dai[1].num_cpus = 1; 229 machine_data->dai[1].num_codecs = 1; 230 machine_data->dai[1].num_platforms = 1; 231 232 machine_data->dai[0].cpus->dai_name = dev_name(&ssi_pdev->dev); 233 machine_data->dai[0].ops = &mpc8610_hpcd_ops; 234 235 /* ASoC core can match codec with device node */ 236 machine_data->dai[0].codecs->of_node = codec_np; 237 238 /* The DAI name from the codec (snd_soc_dai_driver.name) */ 239 machine_data->dai[0].codecs->dai_name = "cs4270-hifi"; 240 241 /* We register two DAIs per SSI, one for playback and the other for 242 * capture. Currently, we only support codecs that have one DAI for 243 * both playback and capture. 244 */ 245 memcpy(&machine_data->dai[1], &machine_data->dai[0], 246 sizeof(struct snd_soc_dai_link)); 247 248 /* Get the device ID */ 249 iprop = of_get_property(np, "cell-index", NULL); 250 if (!iprop) { 251 dev_err(&pdev->dev, "cell-index property not found\n"); 252 ret = -EINVAL; 253 goto error; 254 } 255 machine_data->ssi_id = be32_to_cpup(iprop); 256 257 /* Get the serial format and clock direction. */ 258 sprop = of_get_property(np, "fsl,mode", NULL); 259 if (!sprop) { 260 dev_err(&pdev->dev, "fsl,mode property not found\n"); 261 ret = -EINVAL; 262 goto error; 263 } 264 265 if (strcasecmp(sprop, "i2s-slave") == 0) { 266 machine_data->dai_format = 267 SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; 268 machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; 269 machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; 270 271 /* In i2s-slave mode, the codec has its own clock source, so we 272 * need to get the frequency from the device tree and pass it to 273 * the codec driver. 274 */ 275 iprop = of_get_property(codec_np, "clock-frequency", NULL); 276 if (!iprop || !*iprop) { 277 dev_err(&pdev->dev, "codec bus-frequency " 278 "property is missing or invalid\n"); 279 ret = -EINVAL; 280 goto error; 281 } 282 machine_data->clk_frequency = be32_to_cpup(iprop); 283 } else if (strcasecmp(sprop, "i2s-master") == 0) { 284 machine_data->dai_format = 285 SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; 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, "lj-slave") == 0) { 289 machine_data->dai_format = 290 SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; 291 machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; 292 machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; 293 } else if (strcasecmp(sprop, "lj-master") == 0) { 294 machine_data->dai_format = 295 SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; 296 machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; 297 machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; 298 } else if (strcasecmp(sprop, "rj-slave") == 0) { 299 machine_data->dai_format = 300 SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; 301 machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; 302 machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; 303 } else if (strcasecmp(sprop, "rj-master") == 0) { 304 machine_data->dai_format = 305 SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; 306 machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; 307 machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; 308 } else if (strcasecmp(sprop, "ac97-slave") == 0) { 309 machine_data->dai_format = 310 SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; 311 machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; 312 machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; 313 } else if (strcasecmp(sprop, "ac97-master") == 0) { 314 machine_data->dai_format = 315 SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; 316 machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; 317 machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; 318 } else { 319 dev_err(&pdev->dev, 320 "unrecognized fsl,mode property '%s'\n", sprop); 321 ret = -EINVAL; 322 goto error; 323 } 324 325 if (!machine_data->clk_frequency) { 326 dev_err(&pdev->dev, "unknown clock frequency\n"); 327 ret = -EINVAL; 328 goto error; 329 } 330 331 /* Find the playback DMA channel to use. */ 332 machine_data->dai[0].platforms->name = machine_data->platform_name[0]; 333 ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", 334 &machine_data->dai[0], 335 &machine_data->dma_channel_id[0], 336 &machine_data->dma_id[0]); 337 if (ret) { 338 dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); 339 goto error; 340 } 341 342 /* Find the capture DMA channel to use. */ 343 machine_data->dai[1].platforms->name = machine_data->platform_name[1]; 344 ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", 345 &machine_data->dai[1], 346 &machine_data->dma_channel_id[1], 347 &machine_data->dma_id[1]); 348 if (ret) { 349 dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); 350 goto error; 351 } 352 353 /* Initialize our DAI data structure. */ 354 machine_data->dai[0].stream_name = "playback"; 355 machine_data->dai[1].stream_name = "capture"; 356 machine_data->dai[0].name = machine_data->dai[0].stream_name; 357 machine_data->dai[1].name = machine_data->dai[1].stream_name; 358 359 machine_data->card.probe = mpc8610_hpcd_machine_probe; 360 machine_data->card.remove = mpc8610_hpcd_machine_remove; 361 machine_data->card.name = pdev->name; /* The platform driver name */ 362 machine_data->card.owner = THIS_MODULE; 363 machine_data->card.dev = &pdev->dev; 364 machine_data->card.num_links = 2; 365 machine_data->card.dai_link = machine_data->dai; 366 367 /* Register with ASoC */ 368 ret = snd_soc_register_card(&machine_data->card); 369 if (ret) { 370 dev_err(&pdev->dev, "could not register card\n"); 371 goto error; 372 } 373 374 of_node_put(codec_np); 375 376 return 0; 377 378 error: 379 kfree(machine_data); 380 error_alloc: 381 of_node_put(codec_np); 382 return ret; 383 } 384 385 /** 386 * mpc8610_hpcd_remove: remove the platform device 387 * 388 * This function is called when the platform device is removed. 389 */ 390 static int mpc8610_hpcd_remove(struct platform_device *pdev) 391 { 392 struct snd_soc_card *card = platform_get_drvdata(pdev); 393 struct mpc8610_hpcd_data *machine_data = 394 container_of(card, struct mpc8610_hpcd_data, card); 395 396 snd_soc_unregister_card(card); 397 kfree(machine_data); 398 399 return 0; 400 } 401 402 static struct platform_driver mpc8610_hpcd_driver = { 403 .probe = mpc8610_hpcd_probe, 404 .remove = mpc8610_hpcd_remove, 405 .driver = { 406 /* The name must match 'compatible' property in the device tree, 407 * in lowercase letters. 408 */ 409 .name = "snd-soc-mpc8610hpcd", 410 }, 411 }; 412 413 /** 414 * mpc8610_hpcd_init: machine driver initialization. 415 * 416 * This function is called when this module is loaded. 417 */ 418 static int __init mpc8610_hpcd_init(void) 419 { 420 struct device_node *guts_np; 421 struct resource res; 422 423 pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n"); 424 425 /* Get the physical address of the global utilities registers */ 426 guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts"); 427 if (of_address_to_resource(guts_np, 0, &res)) { 428 pr_err("mpc8610-hpcd: missing/invalid global utilities node\n"); 429 return -EINVAL; 430 } 431 guts_phys = res.start; 432 433 return platform_driver_register(&mpc8610_hpcd_driver); 434 } 435 436 /** 437 * mpc8610_hpcd_exit: machine driver exit 438 * 439 * This function is called when this driver is unloaded. 440 */ 441 static void __exit mpc8610_hpcd_exit(void) 442 { 443 platform_driver_unregister(&mpc8610_hpcd_driver); 444 } 445 446 module_init(mpc8610_hpcd_init); 447 module_exit(mpc8610_hpcd_exit); 448 449 MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); 450 MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver"); 451 MODULE_LICENSE("GPL v2"); 452