linux/sound/soc/blackfin/bf5xx-i2s.c
<<
>>
Prefs
   1/*
   2 * File:         sound/soc/blackfin/bf5xx-i2s.c
   3 * Author:       Cliff Cai <Cliff.Cai@analog.com>
   4 *
   5 * Created:      Tue June 06 2008
   6 * Description:  Blackfin I2S CPU DAI driver
   7 *
   8 * Modified:
   9 *               Copyright 2008 Analog Devices Inc.
  10 *
  11 * Bugs:         Enter bugs at http://blackfin.uclinux.org/
  12 *
  13 * This program is free software; you can redistribute it and/or modify
  14 * it under the terms of the GNU General Public License as published by
  15 * the Free Software Foundation; either version 2 of the License, or
  16 * (at your option) any later version.
  17 *
  18 * This program is distributed in the hope that it will be useful,
  19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21 * GNU General Public License for more details.
  22 *
  23 * You should have received a copy of the GNU General Public License
  24 * along with this program; if not, see the file COPYING, or write
  25 * to the Free Software Foundation, Inc.,
  26 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  27 */
  28
  29#include <linux/init.h>
  30#include <linux/module.h>
  31#include <linux/device.h>
  32#include <linux/delay.h>
  33#include <sound/core.h>
  34#include <sound/pcm.h>
  35#include <sound/pcm_params.h>
  36#include <sound/initval.h>
  37#include <sound/soc.h>
  38
  39#include <asm/irq.h>
  40#include <asm/portmux.h>
  41#include <linux/mutex.h>
  42#include <linux/gpio.h>
  43
  44#include "bf5xx-sport.h"
  45#include "bf5xx-i2s.h"
  46
  47struct bf5xx_i2s_port {
  48        u16 tcr1;
  49        u16 rcr1;
  50        u16 tcr2;
  51        u16 rcr2;
  52        int counter;
  53};
  54
  55static struct bf5xx_i2s_port bf5xx_i2s;
  56static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
  57
  58static struct sport_param sport_params[2] = {
  59        {
  60                .dma_rx_chan    = CH_SPORT0_RX,
  61                .dma_tx_chan    = CH_SPORT0_TX,
  62                .err_irq        = IRQ_SPORT0_ERROR,
  63                .regs           = (struct sport_register *)SPORT0_TCR1,
  64        },
  65        {
  66                .dma_rx_chan    = CH_SPORT1_RX,
  67                .dma_tx_chan    = CH_SPORT1_TX,
  68                .err_irq        = IRQ_SPORT1_ERROR,
  69                .regs           = (struct sport_register *)SPORT1_TCR1,
  70        }
  71};
  72
  73/*
  74 * Setting the TFS pin selector for SPORT 0 based on whether the selected
  75 * port id F or G. If the port is F then no conflict should exist for the
  76 * TFS. When Port G is selected and EMAC then there is a conflict between
  77 * the PHY interrupt line and TFS.  Current settings prevent the conflict
  78 * by ignoring the TFS pin when Port G is selected. This allows both
  79 * ssm2602 using Port G and EMAC concurrently.
  80 */
  81#ifdef CONFIG_BF527_SPORT0_PORTF
  82#define LOCAL_SPORT0_TFS (P_SPORT0_TFS)
  83#else
  84#define LOCAL_SPORT0_TFS (0)
  85#endif
  86
  87static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS,
  88                P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0},
  89                {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI,
  90                P_SPORT1_RSCLK, P_SPORT1_TFS, 0} };
  91
  92static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
  93                unsigned int fmt)
  94{
  95        int ret = 0;
  96
  97        /* interface format:support I2S,slave mode */
  98        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
  99        case SND_SOC_DAIFMT_I2S:
 100                bf5xx_i2s.tcr1 |= TFSR | TCKFE;
 101                bf5xx_i2s.rcr1 |= RFSR | RCKFE;
 102                bf5xx_i2s.tcr2 |= TSFSE;
 103                bf5xx_i2s.rcr2 |= RSFSE;
 104                break;
 105        case SND_SOC_DAIFMT_DSP_A:
 106                bf5xx_i2s.tcr1 |= TFSR;
 107                bf5xx_i2s.rcr1 |= RFSR;
 108                break;
 109        case SND_SOC_DAIFMT_LEFT_J:
 110                ret = -EINVAL;
 111                break;
 112        default:
 113                printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
 114                ret = -EINVAL;
 115                break;
 116        }
 117
 118        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 119        case SND_SOC_DAIFMT_CBM_CFM:
 120                break;
 121        case SND_SOC_DAIFMT_CBS_CFS:
 122        case SND_SOC_DAIFMT_CBM_CFS:
 123        case SND_SOC_DAIFMT_CBS_CFM:
 124                ret = -EINVAL;
 125                break;
 126        default:
 127                printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
 128                ret = -EINVAL;
 129                break;
 130        }
 131
 132        return ret;
 133}
 134
 135static int bf5xx_i2s_startup(struct snd_pcm_substream *substream)
 136{
 137        pr_debug("%s enter\n", __func__);
 138
 139        /*this counter is used for counting how many pcm streams are opened*/
 140        bf5xx_i2s.counter++;
 141        return 0;
 142}
 143
 144static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream,
 145                                struct snd_pcm_hw_params *params)
 146{
 147        int ret = 0;
 148
 149        bf5xx_i2s.tcr2 &= ~0x1f;
 150        bf5xx_i2s.rcr2 &= ~0x1f;
 151        switch (params_format(params)) {
 152        case SNDRV_PCM_FORMAT_S16_LE:
 153                bf5xx_i2s.tcr2 |= 15;
 154                bf5xx_i2s.rcr2 |= 15;
 155                sport_handle->wdsize = 2;
 156                break;
 157        case SNDRV_PCM_FORMAT_S24_LE:
 158                bf5xx_i2s.tcr2 |= 23;
 159                bf5xx_i2s.rcr2 |= 23;
 160                sport_handle->wdsize = 3;
 161                break;
 162        case SNDRV_PCM_FORMAT_S32_LE:
 163                bf5xx_i2s.tcr2 |= 31;
 164                bf5xx_i2s.rcr2 |= 31;
 165                sport_handle->wdsize = 4;
 166                break;
 167        }
 168
 169        if (bf5xx_i2s.counter == 1) {
 170                /*
 171                 * TX and RX are not independent,they are enabled at the
 172                 * same time, even if only one side is running. So, we
 173                 * need to configure both of them at the time when the first
 174                 * stream is opened.
 175                 *
 176                 * CPU DAI:slave mode.
 177                 */
 178                ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1,
 179                                      bf5xx_i2s.rcr2, 0, 0);
 180                if (ret) {
 181                        pr_err("SPORT is busy!\n");
 182                        return -EBUSY;
 183                }
 184
 185                ret = sport_config_tx(sport_handle, bf5xx_i2s.tcr1,
 186                                      bf5xx_i2s.tcr2, 0, 0);
 187                if (ret) {
 188                        pr_err("SPORT is busy!\n");
 189                        return -EBUSY;
 190                }
 191        }
 192
 193        return 0;
 194}
 195
 196static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream)
 197{
 198        pr_debug("%s enter\n", __func__);
 199        bf5xx_i2s.counter--;
 200}
 201
 202static int bf5xx_i2s_probe(struct platform_device *pdev,
 203                           struct snd_soc_dai *dai)
 204{
 205        pr_debug("%s enter\n", __func__);
 206        if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
 207                pr_err("Requesting Peripherals failed\n");
 208                return -EFAULT;
 209        }
 210
 211        /* request DMA for SPORT */
 212        sport_handle = sport_init(&sport_params[sport_num], 4, \
 213                        2 * sizeof(u32), NULL);
 214        if (!sport_handle) {
 215                peripheral_free_list(&sport_req[sport_num][0]);
 216                return -ENODEV;
 217        }
 218
 219        return 0;
 220}
 221
 222static void bf5xx_i2s_remove(struct platform_device *pdev,
 223                           struct snd_soc_dai *dai)
 224{
 225        pr_debug("%s enter\n", __func__);
 226        peripheral_free_list(&sport_req[sport_num][0]);
 227}
 228
 229#ifdef CONFIG_PM
 230static int bf5xx_i2s_suspend(struct platform_device *dev,
 231                             struct snd_soc_dai *dai)
 232{
 233        struct sport_device *sport =
 234                (struct sport_device *)dai->private_data;
 235
 236        pr_debug("%s : sport %d\n", __func__, dai->id);
 237        if (!dai->active)
 238                return 0;
 239        if (dai->capture.active)
 240                sport_rx_stop(sport);
 241        if (dai->playback.active)
 242                sport_tx_stop(sport);
 243        return 0;
 244}
 245
 246static int bf5xx_i2s_resume(struct platform_device *pdev,
 247                            struct snd_soc_dai *dai)
 248{
 249        int ret;
 250        struct sport_device *sport =
 251                (struct sport_device *)dai->private_data;
 252
 253        pr_debug("%s : sport %d\n", __func__, dai->id);
 254        if (!dai->active)
 255                return 0;
 256
 257        ret = sport_config_rx(sport_handle, RFSR | RCKFE, RSFSE|0x1f, 0, 0);
 258        if (ret) {
 259                pr_err("SPORT is busy!\n");
 260                return -EBUSY;
 261        }
 262
 263        ret = sport_config_tx(sport_handle, TFSR | TCKFE, TSFSE|0x1f, 0, 0);
 264        if (ret) {
 265                pr_err("SPORT is busy!\n");
 266                return -EBUSY;
 267        }
 268
 269        if (dai->capture.active)
 270                sport_rx_start(sport);
 271        if (dai->playback.active)
 272                sport_tx_start(sport);
 273        return 0;
 274}
 275
 276#else
 277#define bf5xx_i2s_suspend       NULL
 278#define bf5xx_i2s_resume        NULL
 279#endif
 280
 281#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
 282                SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
 283                SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
 284                SNDRV_PCM_RATE_96000)
 285
 286#define BF5XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
 287        SNDRV_PCM_FMTBIT_S32_LE)
 288
 289struct snd_soc_dai bf5xx_i2s_dai = {
 290        .name = "bf5xx-i2s",
 291        .id = 0,
 292        .type = SND_SOC_DAI_I2S,
 293        .probe = bf5xx_i2s_probe,
 294        .remove = bf5xx_i2s_remove,
 295        .suspend = bf5xx_i2s_suspend,
 296        .resume = bf5xx_i2s_resume,
 297        .playback = {
 298                .channels_min = 1,
 299                .channels_max = 2,
 300                .rates = BF5XX_I2S_RATES,
 301                .formats = BF5XX_I2S_FORMATS,},
 302        .capture = {
 303                .channels_min = 1,
 304                .channels_max = 2,
 305                .rates = BF5XX_I2S_RATES,
 306                .formats = BF5XX_I2S_FORMATS,},
 307        .ops = {
 308                .startup   = bf5xx_i2s_startup,
 309                .shutdown  = bf5xx_i2s_shutdown,
 310                .hw_params = bf5xx_i2s_hw_params,},
 311        .dai_ops = {
 312                .set_fmt = bf5xx_i2s_set_dai_fmt,
 313        },
 314};
 315EXPORT_SYMBOL_GPL(bf5xx_i2s_dai);
 316
 317/* Module information */
 318MODULE_AUTHOR("Cliff Cai");
 319MODULE_DESCRIPTION("I2S driver for ADI Blackfin");
 320MODULE_LICENSE("GPL");
 321
 322
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.