linux/sound/soc/blackfin/bf5xx-tdm.c
<<
>>
Prefs
   1/*
   2 * File:         sound/soc/blackfin/bf5xx-tdm.c
   3 * Author:       Barry Song <Barry.Song@analog.com>
   4 *
   5 * Created:      Thurs June 04 2009
   6 * Description:  Blackfin I2S(TDM) CPU DAI driver
   7 *              Even though TDM mode can be as part of I2S DAI, but there
   8 *              are so much difference in configuration and data flow,
   9 *              it's very ugly to integrate I2S and TDM into a module
  10 *
  11 * Modified:
  12 *               Copyright 2009 Analog Devices Inc.
  13 *
  14 * Bugs:         Enter bugs at http://blackfin.uclinux.org/
  15 *
  16 * This program is free software; you can redistribute it and/or modify
  17 * it under the terms of the GNU General Public License as published by
  18 * the Free Software Foundation; either version 2 of the License, or
  19 * (at your option) any later version.
  20 *
  21 * This program is distributed in the hope that it will be useful,
  22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  24 * GNU General Public License for more details.
  25 *
  26 * You should have received a copy of the GNU General Public License
  27 * along with this program; if not, see the file COPYING, or write
  28 * to the Free Software Foundation, Inc.,
  29 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  30 */
  31
  32#include <linux/init.h>
  33#include <linux/module.h>
  34#include <linux/device.h>
  35#include <sound/core.h>
  36#include <sound/pcm.h>
  37#include <sound/pcm_params.h>
  38#include <sound/initval.h>
  39#include <sound/soc.h>
  40
  41#include <asm/irq.h>
  42#include <asm/portmux.h>
  43#include <linux/mutex.h>
  44#include <linux/gpio.h>
  45
  46#include "bf5xx-sport.h"
  47#include "bf5xx-tdm.h"
  48
  49static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai,
  50        unsigned int fmt)
  51{
  52        int ret = 0;
  53
  54        /* interface format:support TDM,slave mode */
  55        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
  56        case SND_SOC_DAIFMT_DSP_A:
  57                break;
  58        default:
  59                printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
  60                ret = -EINVAL;
  61                break;
  62        }
  63
  64        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
  65        case SND_SOC_DAIFMT_CBM_CFM:
  66                break;
  67        case SND_SOC_DAIFMT_CBS_CFS:
  68        case SND_SOC_DAIFMT_CBM_CFS:
  69        case SND_SOC_DAIFMT_CBS_CFM:
  70                ret = -EINVAL;
  71                break;
  72        default:
  73                printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
  74                ret = -EINVAL;
  75                break;
  76        }
  77
  78        return ret;
  79}
  80
  81static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream,
  82        struct snd_pcm_hw_params *params,
  83        struct snd_soc_dai *dai)
  84{
  85        struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
  86        struct bf5xx_tdm_port *bf5xx_tdm = sport_handle->private_data;
  87        int ret = 0;
  88
  89        bf5xx_tdm->tcr2 &= ~0x1f;
  90        bf5xx_tdm->rcr2 &= ~0x1f;
  91        switch (params_format(params)) {
  92        case SNDRV_PCM_FORMAT_S32_LE:
  93                bf5xx_tdm->tcr2 |= 31;
  94                bf5xx_tdm->rcr2 |= 31;
  95                sport_handle->wdsize = 4;
  96                break;
  97                /* at present, we only support 32bit transfer */
  98        default:
  99                pr_err("not supported PCM format yet\n");
 100                return -EINVAL;
 101                break;
 102        }
 103
 104        if (!bf5xx_tdm->configured) {
 105                /*
 106                 * TX and RX are not independent,they are enabled at the
 107                 * same time, even if only one side is running. So, we
 108                 * need to configure both of them at the time when the first
 109                 * stream is opened.
 110                 *
 111                 * CPU DAI:slave mode.
 112                 */
 113                ret = sport_config_rx(sport_handle, bf5xx_tdm->rcr1,
 114                        bf5xx_tdm->rcr2, 0, 0);
 115                if (ret) {
 116                        pr_err("SPORT is busy!\n");
 117                        return -EBUSY;
 118                }
 119
 120                ret = sport_config_tx(sport_handle, bf5xx_tdm->tcr1,
 121                        bf5xx_tdm->tcr2, 0, 0);
 122                if (ret) {
 123                        pr_err("SPORT is busy!\n");
 124                        return -EBUSY;
 125                }
 126
 127                bf5xx_tdm->configured = 1;
 128        }
 129
 130        return 0;
 131}
 132
 133static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream,
 134        struct snd_soc_dai *dai)
 135{
 136        struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
 137        struct bf5xx_tdm_port *bf5xx_tdm = sport_handle->private_data;
 138
 139        /* No active stream, SPORT is allowed to be configured again. */
 140        if (!dai->active)
 141                bf5xx_tdm->configured = 0;
 142}
 143
 144static int bf5xx_tdm_set_channel_map(struct snd_soc_dai *dai,
 145                unsigned int tx_num, unsigned int *tx_slot,
 146                unsigned int rx_num, unsigned int *rx_slot)
 147{
 148        struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
 149        struct bf5xx_tdm_port *bf5xx_tdm = sport_handle->private_data;
 150        int i;
 151        unsigned int slot;
 152        unsigned int tx_mapped = 0, rx_mapped = 0;
 153
 154        if ((tx_num > BFIN_TDM_DAI_MAX_SLOTS) ||
 155                        (rx_num > BFIN_TDM_DAI_MAX_SLOTS))
 156                return -EINVAL;
 157
 158        for (i = 0; i < tx_num; i++) {
 159                slot = tx_slot[i];
 160                if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
 161                                (!(tx_mapped & (1 << slot)))) {
 162                        bf5xx_tdm->tx_map[i] = slot;
 163                        tx_mapped |= 1 << slot;
 164                } else
 165                        return -EINVAL;
 166        }
 167        for (i = 0; i < rx_num; i++) {
 168                slot = rx_slot[i];
 169                if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
 170                                (!(rx_mapped & (1 << slot)))) {
 171                        bf5xx_tdm->rx_map[i] = slot;
 172                        rx_mapped |= 1 << slot;
 173                } else
 174                        return -EINVAL;
 175        }
 176
 177        return 0;
 178}
 179
 180#ifdef CONFIG_PM
 181static int bf5xx_tdm_suspend(struct snd_soc_dai *dai)
 182{
 183        struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
 184
 185        if (dai->playback_active)
 186                sport_tx_stop(sport);
 187        if (dai->capture_active)
 188                sport_rx_stop(sport);
 189
 190        /* isolate sync/clock pins from codec while sports resume */
 191        peripheral_free_list(sport->pin_req);
 192
 193        return 0;
 194}
 195
 196static int bf5xx_tdm_resume(struct snd_soc_dai *dai)
 197{
 198        int ret;
 199        struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
 200
 201        ret = sport_set_multichannel(sport, 8, 0xFF, 1);
 202        if (ret) {
 203                pr_err("SPORT is busy!\n");
 204                ret = -EBUSY;
 205        }
 206
 207        ret = sport_config_rx(sport, 0, 0x1F, 0, 0);
 208        if (ret) {
 209                pr_err("SPORT is busy!\n");
 210                ret = -EBUSY;
 211        }
 212
 213        ret = sport_config_tx(sport, 0, 0x1F, 0, 0);
 214        if (ret) {
 215                pr_err("SPORT is busy!\n");
 216                ret = -EBUSY;
 217        }
 218
 219        peripheral_request_list(sport->pin_req, "soc-audio");
 220
 221        return 0;
 222}
 223
 224#else
 225#define bf5xx_tdm_suspend      NULL
 226#define bf5xx_tdm_resume       NULL
 227#endif
 228
 229static const struct snd_soc_dai_ops bf5xx_tdm_dai_ops = {
 230        .hw_params      = bf5xx_tdm_hw_params,
 231        .set_fmt        = bf5xx_tdm_set_dai_fmt,
 232        .shutdown       = bf5xx_tdm_shutdown,
 233        .set_channel_map   = bf5xx_tdm_set_channel_map,
 234};
 235
 236static struct snd_soc_dai_driver bf5xx_tdm_dai = {
 237        .suspend = bf5xx_tdm_suspend,
 238        .resume = bf5xx_tdm_resume,
 239        .playback = {
 240                .channels_min = 2,
 241                .channels_max = 8,
 242                .rates = SNDRV_PCM_RATE_48000,
 243                .formats = SNDRV_PCM_FMTBIT_S32_LE,},
 244        .capture = {
 245                .channels_min = 2,
 246                .channels_max = 8,
 247                .rates = SNDRV_PCM_RATE_48000,
 248                .formats = SNDRV_PCM_FMTBIT_S32_LE,},
 249        .ops = &bf5xx_tdm_dai_ops,
 250};
 251
 252static int bfin_tdm_probe(struct platform_device *pdev)
 253{
 254        struct sport_device *sport_handle;
 255        int ret;
 256
 257        /* configure SPORT for TDM */
 258        sport_handle = sport_init(pdev, 4, 8 * sizeof(u32),
 259                sizeof(struct bf5xx_tdm_port));
 260        if (!sport_handle)
 261                return -ENODEV;
 262
 263        /* SPORT works in TDM mode */
 264        ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1);
 265        if (ret) {
 266                pr_err("SPORT is busy!\n");
 267                ret = -EBUSY;
 268                goto sport_config_err;
 269        }
 270
 271        ret = sport_config_rx(sport_handle, 0, 0x1F, 0, 0);
 272        if (ret) {
 273                pr_err("SPORT is busy!\n");
 274                ret = -EBUSY;
 275                goto sport_config_err;
 276        }
 277
 278        ret = sport_config_tx(sport_handle, 0, 0x1F, 0, 0);
 279        if (ret) {
 280                pr_err("SPORT is busy!\n");
 281                ret = -EBUSY;
 282                goto sport_config_err;
 283        }
 284
 285        ret = snd_soc_register_dai(&pdev->dev, &bf5xx_tdm_dai);
 286        if (ret) {
 287                pr_err("Failed to register DAI: %d\n", ret);
 288                goto sport_config_err;
 289        }
 290
 291        return 0;
 292
 293sport_config_err:
 294        sport_done(sport_handle);
 295        return ret;
 296}
 297
 298static int bfin_tdm_remove(struct platform_device *pdev)
 299{
 300        struct sport_device *sport_handle = platform_get_drvdata(pdev);
 301
 302        snd_soc_unregister_dai(&pdev->dev);
 303        sport_done(sport_handle);
 304
 305        return 0;
 306}
 307
 308static struct platform_driver bfin_tdm_driver = {
 309        .probe  = bfin_tdm_probe,
 310        .remove = bfin_tdm_remove,
 311        .driver = {
 312                .name   = "bfin-tdm",
 313                .owner  = THIS_MODULE,
 314        },
 315};
 316
 317module_platform_driver(bfin_tdm_driver);
 318
 319/* Module information */
 320MODULE_AUTHOR("Barry Song");
 321MODULE_DESCRIPTION("TDM driver for ADI Blackfin");
 322MODULE_LICENSE("GPL");
 323
 324