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/fsl/guts.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/* 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 const 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 = to_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        },
 392};
 393
 394/**
 395 * mpc8610_hpcd_init: machine driver initialization.
 396 *
 397 * This function is called when this module is loaded.
 398 */
 399static int __init mpc8610_hpcd_init(void)
 400{
 401        struct device_node *guts_np;
 402        struct resource res;
 403
 404        pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n");
 405
 406        /* Get the physical address of the global utilities registers */
 407        guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
 408        if (of_address_to_resource(guts_np, 0, &res)) {
 409                pr_err("mpc8610-hpcd: missing/invalid global utilities node\n");
 410                return -EINVAL;
 411        }
 412        guts_phys = res.start;
 413
 414        return platform_driver_register(&mpc8610_hpcd_driver);
 415}
 416
 417/**
 418 * mpc8610_hpcd_exit: machine driver exit
 419 *
 420 * This function is called when this driver is unloaded.
 421 */
 422static void __exit mpc8610_hpcd_exit(void)
 423{
 424        platform_driver_unregister(&mpc8610_hpcd_driver);
 425}
 426
 427module_init(mpc8610_hpcd_init);
 428module_exit(mpc8610_hpcd_exit);
 429
 430MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
 431MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver");
 432MODULE_LICENSE("GPL v2");
 433