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