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 <linux/of_i2c.h>
  18#include <sound/soc.h>
  19#include <asm/fsl_guts.h>
  20
  21#include "fsl_dma.h"
  22#include "fsl_ssi.h"
  23
  24/* There's only one global utilities register */
  25static phys_addr_t guts_phys;
  26
  27#define DAI_NAME_SIZE   32
  28
  29/**
  30 * mpc8610_hpcd_data: machine-specific ASoC device data
  31 *
  32 * This structure contains data for a single sound platform device on an
  33 * MPC8610 HPCD.  Some of the data is taken from the device tree.
  34 */
  35struct mpc8610_hpcd_data {
  36        struct snd_soc_dai_link dai[2];
  37        struct snd_soc_card card;
  38        unsigned int dai_format;
  39        unsigned int codec_clk_direction;
  40        unsigned int cpu_clk_direction;
  41        unsigned int clk_frequency;
  42        unsigned int ssi_id;            /* 0 = SSI1, 1 = SSI2, etc */
  43        unsigned int dma_id[2];         /* 0 = DMA1, 1 = DMA2, etc */
  44        unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
  45        char codec_dai_name[DAI_NAME_SIZE];
  46        char codec_name[DAI_NAME_SIZE];
  47        char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
  48};
  49
  50/**
  51 * mpc8610_hpcd_machine_probe: initialize the board
  52 *
  53 * This function is used to initialize the board-specific hardware.
  54 *
  55 * Here we program the DMACR and PMUXCR registers.
  56 */
  57static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card)
  58{
  59        struct mpc8610_hpcd_data *machine_data =
  60                container_of(card, struct mpc8610_hpcd_data, card);
  61        struct ccsr_guts_86xx __iomem *guts;
  62
  63        guts = ioremap(guts_phys, sizeof(struct ccsr_guts_86xx));
  64        if (!guts) {
  65                dev_err(card->dev, "could not map global utilities\n");
  66                return -ENOMEM;
  67        }
  68
  69        /* Program the signal routing between the SSI and the DMA */
  70        guts_set_dmacr(guts, machine_data->dma_id[0],
  71                       machine_data->dma_channel_id[0],
  72                       CCSR_GUTS_DMACR_DEV_SSI);
  73        guts_set_dmacr(guts, machine_data->dma_id[1],
  74                       machine_data->dma_channel_id[1],
  75                       CCSR_GUTS_DMACR_DEV_SSI);
  76
  77        guts_set_pmuxcr_dma(guts, machine_data->dma_id[0],
  78                            machine_data->dma_channel_id[0], 0);
  79        guts_set_pmuxcr_dma(guts, machine_data->dma_id[1],
  80                            machine_data->dma_channel_id[1], 0);
  81
  82        switch (machine_data->ssi_id) {
  83        case 0:
  84                clrsetbits_be32(&guts->pmuxcr,
  85                        CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
  86                break;
  87        case 1:
  88                clrsetbits_be32(&guts->pmuxcr,
  89                        CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
  90                break;
  91        }
  92
  93        iounmap(guts);
  94
  95        return 0;
  96}
  97
  98/**
  99 * mpc8610_hpcd_startup: program the board with various hardware parameters
 100 *
 101 * This function takes board-specific information, like clock frequencies
 102 * and serial data formats, and passes that information to the codec and
 103 * transport drivers.
 104 */
 105static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
 106{
 107        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 108        struct mpc8610_hpcd_data *machine_data =
 109                container_of(rtd->card, struct mpc8610_hpcd_data, card);
 110        struct device *dev = rtd->card->dev;
 111        int ret = 0;
 112
 113        /* Tell the codec driver what the serial protocol is. */
 114        ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format);
 115        if (ret < 0) {
 116                dev_err(dev, "could not set codec driver audio format\n");
 117                return ret;
 118        }
 119
 120        /*
 121         * Tell the codec driver what the MCLK frequency is, and whether it's
 122         * a slave or master.
 123         */
 124        ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0,
 125                                     machine_data->clk_frequency,
 126                                     machine_data->codec_clk_direction);
 127        if (ret < 0) {
 128                dev_err(dev, "could not set codec driver clock params\n");
 129                return ret;
 130        }
 131
 132        return 0;
 133}
 134
 135/**
 136 * mpc8610_hpcd_machine_remove: Remove the sound device
 137 *
 138 * This function is called to remove the sound device for one SSI.  We
 139 * de-program the DMACR and PMUXCR register.
 140 */
 141static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card)
 142{
 143        struct mpc8610_hpcd_data *machine_data =
 144                container_of(card, struct mpc8610_hpcd_data, card);
 145        struct ccsr_guts_86xx __iomem *guts;
 146
 147        guts = ioremap(guts_phys, sizeof(struct ccsr_guts_86xx));
 148        if (!guts) {
 149                dev_err(card->dev, "could not map global utilities\n");
 150                return -ENOMEM;
 151        }
 152
 153        /* Restore the signal routing */
 154
 155        guts_set_dmacr(guts, machine_data->dma_id[0],
 156                       machine_data->dma_channel_id[0], 0);
 157        guts_set_dmacr(guts, machine_data->dma_id[1],
 158                       machine_data->dma_channel_id[1], 0);
 159
 160        switch (machine_data->ssi_id) {
 161        case 0:
 162                clrsetbits_be32(&guts->pmuxcr,
 163                        CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
 164                break;
 165        case 1:
 166                clrsetbits_be32(&guts->pmuxcr,
 167                        CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
 168                break;
 169        }
 170
 171        iounmap(guts);
 172
 173        return 0;
 174}
 175
 176/**
 177 * mpc8610_hpcd_ops: ASoC machine driver operations
 178 */
 179static struct snd_soc_ops mpc8610_hpcd_ops = {
 180        .startup = mpc8610_hpcd_startup,
 181};
 182
 183/**
 184 * get_node_by_phandle_name - get a node by its phandle name
 185 *
 186 * This function takes a node, the name of a property in that node, and a
 187 * compatible string.  Assuming the property is a phandle to another node,
 188 * it returns that node, (optionally) if that node is compatible.
 189 *
 190 * If the property is not a phandle, or the node it points to is not compatible
 191 * with the specific string, then NULL is returned.
 192 */
 193static struct device_node *get_node_by_phandle_name(struct device_node *np,
 194                                               const char *name,
 195                                               const char *compatible)
 196{
 197        const phandle *ph;
 198        int len;
 199
 200        ph = of_get_property(np, name, &len);
 201        if (!ph || (len != sizeof(phandle)))
 202                return NULL;
 203
 204        np = of_find_node_by_phandle(*ph);
 205        if (!np)
 206                return NULL;
 207
 208        if (compatible && !of_device_is_compatible(np, compatible)) {
 209                of_node_put(np);
 210                return NULL;
 211        }
 212
 213        return np;
 214}
 215
 216/**
 217 * get_parent_cell_index -- return the cell-index of the parent of a node
 218 *
 219 * Return the value of the cell-index property of the parent of the given
 220 * node.  This is used for DMA channel nodes that need to know the DMA ID
 221 * of the controller they are on.
 222 */
 223static int get_parent_cell_index(struct device_node *np)
 224{
 225        struct device_node *parent = of_get_parent(np);
 226        const u32 *iprop;
 227
 228        if (!parent)
 229                return -1;
 230
 231        iprop = of_get_property(parent, "cell-index", NULL);
 232        of_node_put(parent);
 233
 234        if (!iprop)
 235                return -1;
 236
 237        return be32_to_cpup(iprop);
 238}
 239
 240/**
 241 * codec_node_dev_name - determine the dev_name for a codec node
 242 *
 243 * This function determines the dev_name for an I2C node.  This is the name
 244 * that would be returned by dev_name() if this device_node were part of a
 245 * 'struct device'  It's ugly and hackish, but it works.
 246 *
 247 * The dev_name for such devices include the bus number and I2C address. For
 248 * example, "cs4270-codec.0-004f".
 249 */
 250static int codec_node_dev_name(struct device_node *np, char *buf, size_t len)
 251{
 252        const u32 *iprop;
 253        int addr;
 254        char temp[DAI_NAME_SIZE];
 255        struct i2c_client *i2c;
 256
 257        of_modalias_node(np, temp, DAI_NAME_SIZE);
 258
 259        iprop = of_get_property(np, "reg", NULL);
 260        if (!iprop)
 261                return -EINVAL;
 262
 263        addr = be32_to_cpup(iprop);
 264
 265        /* We need the adapter number */
 266        i2c = of_find_i2c_device_by_node(np);
 267        if (!i2c)
 268                return -ENODEV;
 269
 270        snprintf(buf, len, "%s-codec.%u-%04x", temp, i2c->adapter->nr, addr);
 271
 272        return 0;
 273}
 274
 275static int get_dma_channel(struct device_node *ssi_np,
 276                           const char *compatible,
 277                           struct snd_soc_dai_link *dai,
 278                           unsigned int *dma_channel_id,
 279                           unsigned int *dma_id)
 280{
 281        struct resource res;
 282        struct device_node *dma_channel_np;
 283        const u32 *iprop;
 284        int ret;
 285
 286        dma_channel_np = get_node_by_phandle_name(ssi_np, compatible,
 287                                                  "fsl,ssi-dma-channel");
 288        if (!dma_channel_np)
 289                return -EINVAL;
 290
 291        /* Determine the dev_name for the device_node.  This code mimics the
 292         * behavior of of_device_make_bus_id(). We need this because ASoC uses
 293         * the dev_name() of the device to match the platform (DMA) device with
 294         * the CPU (SSI) device.  It's all ugly and hackish, but it works (for
 295         * now).
 296         *
 297         * dai->platform name should already point to an allocated buffer.
 298         */
 299        ret = of_address_to_resource(dma_channel_np, 0, &res);
 300        if (ret)
 301                return ret;
 302        snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s",
 303                 (unsigned long long) res.start, dma_channel_np->name);
 304
 305        iprop = of_get_property(dma_channel_np, "cell-index", NULL);
 306        if (!iprop) {
 307                of_node_put(dma_channel_np);
 308                return -EINVAL;
 309        }
 310
 311        *dma_channel_id = be32_to_cpup(iprop);
 312        *dma_id = get_parent_cell_index(dma_channel_np);
 313        of_node_put(dma_channel_np);
 314
 315        return 0;
 316}
 317
 318/**
 319 * mpc8610_hpcd_probe: platform probe function for the machine driver
 320 *
 321 * Although this is a machine driver, the SSI node is the "master" node with
 322 * respect to audio hardware connections.  Therefore, we create a new ASoC
 323 * device for each new SSI node that has a codec attached.
 324 */
 325static int mpc8610_hpcd_probe(struct platform_device *pdev)
 326{
 327        struct device *dev = pdev->dev.parent;
 328        /* ssi_pdev is the platform device for the SSI node that probed us */
 329        struct platform_device *ssi_pdev =
 330                container_of(dev, struct platform_device, dev);
 331        struct device_node *np = ssi_pdev->dev.of_node;
 332        struct device_node *codec_np = NULL;
 333        struct platform_device *sound_device = NULL;
 334        struct mpc8610_hpcd_data *machine_data;
 335        int ret = -ENODEV;
 336        const char *sprop;
 337        const u32 *iprop;
 338
 339        /* We are only interested in SSIs with a codec phandle in them,
 340         * so let's make sure this SSI has one. The MPC8610 HPCD only
 341         * knows about the CS4270 codec, so reject anything else.
 342         */
 343        codec_np = get_node_by_phandle_name(np, "codec-handle",
 344                                            "cirrus,cs4270");
 345        if (!codec_np) {
 346                dev_err(dev, "invalid codec node\n");
 347                return -EINVAL;
 348        }
 349
 350        machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
 351        if (!machine_data) {
 352                ret = -ENOMEM;
 353                goto error_alloc;
 354        }
 355
 356        machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
 357        machine_data->dai[0].ops = &mpc8610_hpcd_ops;
 358
 359        /* Determine the codec name, it will be used as the codec DAI name */
 360        ret = codec_node_dev_name(codec_np, machine_data->codec_name,
 361                                  DAI_NAME_SIZE);
 362        if (ret) {
 363                dev_err(&pdev->dev, "invalid codec node %s\n",
 364                        codec_np->full_name);
 365                ret = -EINVAL;
 366                goto error;
 367        }
 368        machine_data->dai[0].codec_name = machine_data->codec_name;
 369
 370        /* The DAI name from the codec (snd_soc_dai_driver.name) */
 371        machine_data->dai[0].codec_dai_name = "cs4270-hifi";
 372
 373        /* We register two DAIs per SSI, one for playback and the other for
 374         * capture.  Currently, we only support codecs that have one DAI for
 375         * both playback and capture.
 376         */
 377        memcpy(&machine_data->dai[1], &machine_data->dai[0],
 378               sizeof(struct snd_soc_dai_link));
 379
 380        /* Get the device ID */
 381        iprop = of_get_property(np, "cell-index", NULL);
 382        if (!iprop) {
 383                dev_err(&pdev->dev, "cell-index property not found\n");
 384                ret = -EINVAL;
 385                goto error;
 386        }
 387        machine_data->ssi_id = be32_to_cpup(iprop);
 388
 389        /* Get the serial format and clock direction. */
 390        sprop = of_get_property(np, "fsl,mode", NULL);
 391        if (!sprop) {
 392                dev_err(&pdev->dev, "fsl,mode property not found\n");
 393                ret = -EINVAL;
 394                goto error;
 395        }
 396
 397        if (strcasecmp(sprop, "i2s-slave") == 0) {
 398                machine_data->dai_format =
 399                        SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM;
 400                machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
 401                machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
 402
 403                /* In i2s-slave mode, the codec has its own clock source, so we
 404                 * need to get the frequency from the device tree and pass it to
 405                 * the codec driver.
 406                 */
 407                iprop = of_get_property(codec_np, "clock-frequency", NULL);
 408                if (!iprop || !*iprop) {
 409                        dev_err(&pdev->dev, "codec bus-frequency "
 410                                "property is missing or invalid\n");
 411                        ret = -EINVAL;
 412                        goto error;
 413                }
 414                machine_data->clk_frequency = be32_to_cpup(iprop);
 415        } else if (strcasecmp(sprop, "i2s-master") == 0) {
 416                machine_data->dai_format =
 417                        SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
 418                machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
 419                machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
 420        } else if (strcasecmp(sprop, "lj-slave") == 0) {
 421                machine_data->dai_format =
 422                        SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM;
 423                machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
 424                machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
 425        } else if (strcasecmp(sprop, "lj-master") == 0) {
 426                machine_data->dai_format =
 427                        SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS;
 428                machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
 429                machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
 430        } else if (strcasecmp(sprop, "rj-slave") == 0) {
 431                machine_data->dai_format =
 432                        SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM;
 433                machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
 434                machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
 435        } else if (strcasecmp(sprop, "rj-master") == 0) {
 436                machine_data->dai_format =
 437                        SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS;
 438                machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
 439                machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
 440        } else if (strcasecmp(sprop, "ac97-slave") == 0) {
 441                machine_data->dai_format =
 442                        SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM;
 443                machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
 444                machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
 445        } else if (strcasecmp(sprop, "ac97-master") == 0) {
 446                machine_data->dai_format =
 447                        SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS;
 448                machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
 449                machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
 450        } else {
 451                dev_err(&pdev->dev,
 452                        "unrecognized fsl,mode property '%s'\n", sprop);
 453                ret = -EINVAL;
 454                goto error;
 455        }
 456
 457        if (!machine_data->clk_frequency) {
 458                dev_err(&pdev->dev, "unknown clock frequency\n");
 459                ret = -EINVAL;
 460                goto error;
 461        }
 462
 463        /* Find the playback DMA channel to use. */
 464        machine_data->dai[0].platform_name = machine_data->platform_name[0];
 465        ret = get_dma_channel(np, "fsl,playback-dma", &machine_data->dai[0],
 466                              &machine_data->dma_channel_id[0],
 467                              &machine_data->dma_id[0]);
 468        if (ret) {
 469                dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
 470                goto error;
 471        }
 472
 473        /* Find the capture DMA channel to use. */
 474        machine_data->dai[1].platform_name = machine_data->platform_name[1];
 475        ret = get_dma_channel(np, "fsl,capture-dma", &machine_data->dai[1],
 476                              &machine_data->dma_channel_id[1],
 477                              &machine_data->dma_id[1]);
 478        if (ret) {
 479                dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
 480                goto error;
 481        }
 482
 483        /* Initialize our DAI data structure.  */
 484        machine_data->dai[0].stream_name = "playback";
 485        machine_data->dai[1].stream_name = "capture";
 486        machine_data->dai[0].name = machine_data->dai[0].stream_name;
 487        machine_data->dai[1].name = machine_data->dai[1].stream_name;
 488
 489        machine_data->card.probe = mpc8610_hpcd_machine_probe;
 490        machine_data->card.remove = mpc8610_hpcd_machine_remove;
 491        machine_data->card.name = pdev->name; /* The platform driver name */
 492        machine_data->card.num_links = 2;
 493        machine_data->card.dai_link = machine_data->dai;
 494
 495        /* Allocate a new audio platform device structure */
 496        sound_device = platform_device_alloc("soc-audio", -1);
 497        if (!sound_device) {
 498                dev_err(&pdev->dev, "platform device alloc failed\n");
 499                ret = -ENOMEM;
 500                goto error;
 501        }
 502
 503        /* Associate the card data with the sound device */
 504        platform_set_drvdata(sound_device, &machine_data->card);
 505
 506        /* Register with ASoC */
 507        ret = platform_device_add(sound_device);
 508        if (ret) {
 509                dev_err(&pdev->dev, "platform device add failed\n");
 510                goto error_sound;
 511        }
 512        dev_set_drvdata(&pdev->dev, sound_device);
 513
 514        of_node_put(codec_np);
 515
 516        return 0;
 517
 518error_sound:
 519        platform_device_put(sound_device);
 520error:
 521        kfree(machine_data);
 522error_alloc:
 523        of_node_put(codec_np);
 524        return ret;
 525}
 526
 527/**
 528 * mpc8610_hpcd_remove: remove the platform device
 529 *
 530 * This function is called when the platform device is removed.
 531 */
 532static int __devexit mpc8610_hpcd_remove(struct platform_device *pdev)
 533{
 534        struct platform_device *sound_device = dev_get_drvdata(&pdev->dev);
 535        struct snd_soc_card *card = platform_get_drvdata(sound_device);
 536        struct mpc8610_hpcd_data *machine_data =
 537                container_of(card, struct mpc8610_hpcd_data, card);
 538
 539        platform_device_unregister(sound_device);
 540
 541        kfree(machine_data);
 542        sound_device->dev.platform_data = NULL;
 543
 544        dev_set_drvdata(&pdev->dev, NULL);
 545
 546        return 0;
 547}
 548
 549static struct platform_driver mpc8610_hpcd_driver = {
 550        .probe = mpc8610_hpcd_probe,
 551        .remove = __devexit_p(mpc8610_hpcd_remove),
 552        .driver = {
 553                /* The name must match the 'model' property in the device tree,
 554                 * in lowercase letters.
 555                 */
 556                .name = "snd-soc-mpc8610hpcd",
 557                .owner = THIS_MODULE,
 558        },
 559};
 560
 561/**
 562 * mpc8610_hpcd_init: machine driver initialization.
 563 *
 564 * This function is called when this module is loaded.
 565 */
 566static int __init mpc8610_hpcd_init(void)
 567{
 568        struct device_node *guts_np;
 569        struct resource res;
 570
 571        pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n");
 572
 573        /* Get the physical address of the global utilities registers */
 574        guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
 575        if (of_address_to_resource(guts_np, 0, &res)) {
 576                pr_err("mpc8610-hpcd: missing/invalid global utilities node\n");
 577                return -EINVAL;
 578        }
 579        guts_phys = res.start;
 580
 581        return platform_driver_register(&mpc8610_hpcd_driver);
 582}
 583
 584/**
 585 * mpc8610_hpcd_exit: machine driver exit
 586 *
 587 * This function is called when this driver is unloaded.
 588 */
 589static void __exit mpc8610_hpcd_exit(void)
 590{
 591        platform_driver_unregister(&mpc8610_hpcd_driver);
 592}
 593
 594module_init(mpc8610_hpcd_init);
 595module_exit(mpc8610_hpcd_exit);
 596
 597MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
 598MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver");
 599MODULE_LICENSE("GPL v2");
 600