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