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/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/* P1022-specific PMUXCR and DMUXCR bit definitions */
  25
  26#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK        0x0001c000
  27#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI   0x00010000
  28#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI         0x00018000
  29
  30#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK       0x00000c00
  31#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI        0x00000000
  32
  33#define CCSR_GUTS_DMUXCR_PAD    1       /* DMA controller/channel set to pad */
  34#define CCSR_GUTS_DMUXCR_SSI    2       /* DMA controller/channel set to SSI */
  35
  36/*
  37 * Set the DMACR register in the GUTS
  38 *
  39 * The DMACR register determines the source of initiated transfers for each
  40 * channel on each DMA controller.  Rather than have a bunch of repetitive
  41 * macros for the bit patterns, we just have a function that calculates
  42 * them.
  43 *
  44 * guts: Pointer to GUTS structure
  45 * co: The DMA controller (0 or 1)
  46 * ch: The channel on the DMA controller (0, 1, 2, or 3)
  47 * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx)
  48 */
  49static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts,
  50        unsigned int co, unsigned int ch, unsigned int device)
  51{
  52        unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch));
  53
  54        clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift);
  55}
  56
  57/* There's only one global utilities register */
  58static phys_addr_t guts_phys;
  59
  60/**
  61 * machine_data: machine-specific ASoC device data
  62 *
  63 * This structure contains data for a single sound platform device on an
  64 * P1022 DS.  Some of the data is taken from the device tree.
  65 */
  66struct machine_data {
  67        struct snd_soc_dai_link dai[2];
  68        struct snd_soc_card card;
  69        unsigned int dai_format;
  70        unsigned int codec_clk_direction;
  71        unsigned int cpu_clk_direction;
  72        unsigned int clk_frequency;
  73        unsigned int ssi_id;            /* 0 = SSI1, 1 = SSI2, etc */
  74        unsigned int dma_id[2];         /* 0 = DMA1, 1 = DMA2, etc */
  75        unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
  76        char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
  77};
  78
  79/**
  80 * p1022_ds_machine_probe: initialize the board
  81 *
  82 * This function is used to initialize the board-specific hardware.
  83 *
  84 * Here we program the DMACR and PMUXCR registers.
  85 */
  86static int p1022_ds_machine_probe(struct snd_soc_card *card)
  87{
  88        struct machine_data *mdata =
  89                container_of(card, struct machine_data, card);
  90        struct ccsr_guts __iomem *guts;
  91
  92        guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
  93        if (!guts) {
  94                dev_err(card->dev, "could not map global utilities\n");
  95                return -ENOMEM;
  96        }
  97
  98        /* Enable SSI Tx signal */
  99        clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK,
 100                        CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI);
 101
 102        /* Enable SSI Rx signal */
 103        clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK,
 104                        CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI);
 105
 106        /* Enable DMA Channel for SSI */
 107        guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0],
 108                        CCSR_GUTS_DMUXCR_SSI);
 109
 110        guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1],
 111                        CCSR_GUTS_DMUXCR_SSI);
 112
 113        iounmap(guts);
 114
 115        return 0;
 116}
 117
 118/**
 119 * p1022_ds_startup: program the board with various hardware parameters
 120 *
 121 * This function takes board-specific information, like clock frequencies
 122 * and serial data formats, and passes that information to the codec and
 123 * transport drivers.
 124 */
 125static int p1022_ds_startup(struct snd_pcm_substream *substream)
 126{
 127        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 128        struct machine_data *mdata =
 129                container_of(rtd->card, struct machine_data, card);
 130        struct device *dev = rtd->card->dev;
 131        int ret = 0;
 132
 133        /* Tell the codec driver what the serial protocol is. */
 134        ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format);
 135        if (ret < 0) {
 136                dev_err(dev, "could not set codec driver audio format\n");
 137                return ret;
 138        }
 139
 140        /*
 141         * Tell the codec driver what the MCLK frequency is, and whether it's
 142         * a slave or master.
 143         */
 144        ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency,
 145                                     mdata->codec_clk_direction);
 146        if (ret < 0) {
 147                dev_err(dev, "could not set codec driver clock params\n");
 148                return ret;
 149        }
 150
 151        return 0;
 152}
 153
 154/**
 155 * p1022_ds_machine_remove: Remove the sound device
 156 *
 157 * This function is called to remove the sound device for one SSI.  We
 158 * de-program the DMACR and PMUXCR register.
 159 */
 160static int p1022_ds_machine_remove(struct snd_soc_card *card)
 161{
 162        struct machine_data *mdata =
 163                container_of(card, struct machine_data, card);
 164        struct ccsr_guts __iomem *guts;
 165
 166        guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
 167        if (!guts) {
 168                dev_err(card->dev, "could not map global utilities\n");
 169                return -ENOMEM;
 170        }
 171
 172        /* Restore the signal routing */
 173        clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK);
 174        clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK);
 175        guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0);
 176        guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0);
 177
 178        iounmap(guts);
 179
 180        return 0;
 181}
 182
 183/**
 184 * p1022_ds_ops: ASoC machine driver operations
 185 */
 186static struct snd_soc_ops p1022_ds_ops = {
 187        .startup = p1022_ds_startup,
 188};
 189
 190/**
 191 * p1022_ds_probe: platform probe function for the machine driver
 192 *
 193 * Although this is a machine driver, the SSI node is the "master" node with
 194 * respect to audio hardware connections.  Therefore, we create a new ASoC
 195 * device for each new SSI node that has a codec attached.
 196 */
 197static int p1022_ds_probe(struct platform_device *pdev)
 198{
 199        struct device *dev = pdev->dev.parent;
 200        /* ssi_pdev is the platform device for the SSI node that probed us */
 201        struct platform_device *ssi_pdev =
 202                container_of(dev, struct 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                .owner = THIS_MODULE,
 401        },
 402};
 403
 404/**
 405 * p1022_ds_init: machine driver initialization.
 406 *
 407 * This function is called when this module is loaded.
 408 */
 409static int __init p1022_ds_init(void)
 410{
 411        struct device_node *guts_np;
 412        struct resource res;
 413
 414        /* Get the physical address of the global utilities registers */
 415        guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
 416        if (of_address_to_resource(guts_np, 0, &res)) {
 417                pr_err("snd-soc-p1022ds: missing/invalid global utils node\n");
 418                of_node_put(guts_np);
 419                return -EINVAL;
 420        }
 421        guts_phys = res.start;
 422        of_node_put(guts_np);
 423
 424        return platform_driver_register(&p1022_ds_driver);
 425}
 426
 427/**
 428 * p1022_ds_exit: machine driver exit
 429 *
 430 * This function is called when this driver is unloaded.
 431 */
 432static void __exit p1022_ds_exit(void)
 433{
 434        platform_driver_unregister(&p1022_ds_driver);
 435}
 436
 437module_init(p1022_ds_init);
 438module_exit(p1022_ds_exit);
 439
 440MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
 441MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver");
 442MODULE_LICENSE("GPL v2");
 443