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