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