Question: Custom DAI driver for AM35xx using McBSP

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



All,
Please forgive me if I don't get this post quite right. This is my first time posting to the mailing lists.

My team is working on a driver that will be used to interface a cell modem's digital audio interface (DAI) to a McBSP port on an AM3517.  Our goal is to be able to support multiple such interfaces down the road.  For now, however, we will be satisfied just getting the one we have on our demo board (McBSP2) to work 100%.

The driver should be VERY simple:  The cell modem has a 4-wire DAI port that behaves much like any other PCM hook-up (CLK, FS, TX, RX), and we simply want that raw data pulled into userspace via ALSA for handling.  The DAI on the modem has a fixed configuration:


- Clock is master, fixed at 256kHz

- Frame sync is master, fixed 125us frame 
duration (8kHz): 32 bits of total data; first 16 bits are valid (while 
FS is high), following 16 are "don't care" (while FS low)

- 16-bit linear samples, sent MSB first

- "Long Frame" sync:  transmit and receive occur simultaneously while the common FS line is active (high).

- TX data with 0-bit delay; start at the rising edge of the clock, while FS is high.
- RX data with 0-bit delay; sample at the falling edge of the clock, while FS is high.

(We have posted to the TI E2E forums and sought their help, but have yet to receive a response.  Our E2E posts contain much more detail which may be useful to anyone willing to take a look here.  They can be found here: http://e2e.ti.com/support/dsp/sitara_arm174_microprocessors/f/416/t/165965.aspx.)

So, obviously the McBSP has to be configured as a slave and setup to support this fixed signaling standard.  The only thing that I know of that's a little unique here is the "long frame sync".  Most PCM setups I've seen (like i2s) use a "short frame sync" instead.


There is no "CODEC" necessary per se, as all we want to do for now is pull the raw PCM data into userspace for ALSA handling.  So, we have not defined any volume controls, etc.  Similarly, power mangement is not our primary concern.  So, for now, we've ignored DAPM.


The drivers we have pulled together, shown below, are based heavily off other PCM drivers we found (like for S/PDIF or bluetooth headsets) and immediately gave us working playback.  We were able to play audio using the "aplay <wavefile>" command almost immediately.

*** However, when recording ("arecord -f S16_LE -r 8000 -c 1 <wavefile>"), we have never been able to get anything but silence (0x00).  ***


We are presently using a snapshotted version of Linux 3.2-rc6 from the linux-omap tree.  We plan on upgrading as soon as we can straighten this out and prove it works.  We simply don't want to make changes to a working setup mid-debug so as to avoid adding any other issues into the mix.


We have enabled a significant amount of debugging, and as best we can tell, we are getting IRQs from omap_pcm_dma_irq() and we are actually reading nothing but 0s in the data block.  We determined this by dumping the bytes as they came into userspace via snd_pcm_lib_read_transfer().  The data going onto the wire is non-zero, as an o-scope clearly shows bits changing.  We have checked the pin mux maps (even making the line a GPIO output to toggle it to make sure), and about every other setting we can think of.  If we disconnect either the CLK or FS wire from the McBSP, everything stops. So, we know the control lines are being monitored.  What we do not know, is whether or not they're being interpretted correctly.  Our gut tells us they are not.  If we allow our record to run long enough (> 6-10 seconds), we do start receiving an overrun or two, and we've not sorted out the cause of that yet.


Our first expectation is that something is not setup properly in the McBSP.  Since the McBSP is being configured by Linux in our case, we figured we'd take it to the experts here to see if you could identify where we've gone wrong.

THANKS IN ADVANCE FOR YOU HELP!!!


The code below is for the two driver files we have come up with to date.  (Again, our apologies that they are not compliant with the latest combined omap-mcbsp stuff being released as we speak.):

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   celldai-soc.c   >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


/*
 * celldai-soc.c  --  ALSA SoC audio pcm for OMAP3/AM35xx
 *
 *
 * Based on sound/soc/omap/overo.c by Steve Sakoman
 *   and am3517evm.c by Anuj Aggarwal <anuj.aggarwal@xxxxxx>
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */



#include <linux/init.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
/*** includes here down, needed??? ***/
#include <asm/mach-types.h>
#include <mach/hardware.h>
#include <mach/gpio.h>
//#include <plat/mcbsp.h>

#include "omap-mcbsp.h"
//#include "omap-pcm.h"

static struct platform_device *celldai_snd_device;
static struct platform_device *celldai_codec_device;

static int celldai_hw_params(struct snd_pcm_substream *substream,
        struct snd_pcm_hw_params *params)
{
    struct snd_soc_pcm_runtime *rtd = substream->private_data;
    /* we really do not have a "codec", it's an external device with no control & a fixed configuration */
//    struct snd_soc_dai *codec_dai = rtd->codec_dai;
    struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
    unsigned int fmt = 0;
    int ret;

    switch (params_channels(params)) {
    case 1: /* voice only - 8kHz, S16_LE, mono */
        fmt = SND_SOC_DAIFMT_LEFT_J |
            SND_SOC_DAIFMT_IB_NF |
            SND_SOC_DAIFMT_CBM_CFM;
        break;
    default:
        return -EINVAL;
        break;
    }

#if 0  /*** I don't think we'll be configuring anything with the audio device from the kernel ***/
    /* Set codec DAI configuration */
    ret = snd_soc_dai_set_fmt(codec_dai, fmt);
    if (ret < 0) {
        printk(KERN_ERR "can't set codec DAI configuration\n");
        return ret;
    }
    
    /* Set the codec system clock for DAC and ADC */
    ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
                        SND_SOC_CLOCK_IN);
    if (ret < 0) {
        printk(KERN_ERR "can't set codec system clock\n");
        return ret;
    }
#endif

    /* Set cpu DAI configuration */
    ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
    if (ret < 0) {
        printk(KERN_ERR "can't set cpu DAI configuration\n");
        return ret;
    }

#if 0 /*** these next 2 are valid only for mcbsp1 (0 to the driver), 
            as other mcbsp's lack separate CLKR/FSR lines ***/
    /* set cpu CLKR & FSR as inputs (unused) */
    ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_CLKR_SRC_CLKX, 0,
                SND_SOC_CLOCK_IN);
    if (ret < 0) {
        printk(KERN_ERR "can't set CPU system clock OMAP_MCBSP_CLKR_SRC_CLKX\n");
        return ret;
    }

    snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_FSR_SRC_FSX, 0,
                SND_SOC_CLOCK_IN);
    if (ret < 0) {
        printk(KERN_ERR "can't set CPU system clock OMAP_MCBSP_FSR_SRC_FSX\n");
        return ret;
    }
#endif

#if 0  /*** I believe these are NOT needed since McBSP is slave! ***/
    /* Set McBSP clock to external */
    // note, final parameter appears ignored.
    ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKX_EXT, 
                    256 * params_rate(params), SND_SOC_CLOCK_IN);
    if (ret < 0) {
        printk(KERN_ERR "can't set cpu DAI clock source: OMAP_MCBSP_SYSCLK_CLKX_EXT\n");  // was 0
        return ret;
    }

    /* Set cpu DAI master clock divisor */
    ret =    snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, 8);  // was 1
    if (ret < 0) {
        printk(KERN_ERR "can't set cpu DAI clock divider: OMAP_MCBSP_CLKGDV\n");
        return ret;
    }
#endif

    return 0;
}

static struct snd_soc_ops celldai_ops = {
    .hw_params = celldai_hw_params,
};

/* Digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link celldai_dai = {
    .name = "cell_dai",
    .stream_name = "cell_dai",
    .cpu_dai_name = "omap-mcbsp-dai.1",
    .platform_name = "omap-pcm-audio",
    .codec_dai_name = "celldai-codec-dai",
    .codec_name = "celldai-codec",
    .ops = &celldai_ops,
};

/* Audio machine driver */
static struct snd_soc_card snd_soc_celldai = {
    .name = "celldai",
    .owner = THIS_MODULE,
    .dai_link = &celldai_dai,
    .num_links = 1,
};

static int __init celldai_soc_init(void)
{
    int ret = 0;
    printk(KERN_DEBUG "celldai_soc_init\n");

    celldai_codec_device = platform_device_alloc("celldai-codec", -1);
    if (!celldai_codec_device)
        return -ENOMEM;

    ret = platform_device_add(celldai_codec_device);
    if (ret)
        goto err1;

    celldai_snd_device = platform_device_alloc("soc-audio", -1);
    if (!celldai_snd_device) {
        printk(KERN_ERR "Platform device allocation failed\n");
        return -ENOMEM;
    }

    platform_set_drvdata(celldai_snd_device, &snd_soc_celldai);

    ret = platform_device_add(celldai_snd_device);
    if (ret)
        goto err2;

    printk(KERN_INFO "celldai SoC init\n");
    
    return 0;

err1:
    printk(KERN_ERR "Unable to add platform device (codec)\n");
    platform_device_put(celldai_codec_device);
err2:
    printk(KERN_ERR "Unable to add platform device (snd)\n");
    platform_device_put(celldai_snd_device);

    return ret;
}

static void __exit celldai_soc_exit(void)
{
    printk(KERN_DEBUG "celldai_soc_exit\n");
    platform_device_unregister(celldai_snd_device);
    platform_device_unregister(celldai_codec_device);
}

module_init(celldai_soc_init);
module_exit(celldai_soc_exit);

MODULE_AUTHOR("TBD");
MODULE_DESCRIPTION("ALSA SoC - Custom Cell DAI");
MODULE_LICENSE("GPL");

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   celldai-codec.c   
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

/*
 * celldai-codec.c -- SOC codec driver for cell modem
 *
 *
 * based on spdif_transciever.c by Steve Chen <schen@xxxxxxxxxx>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <sound/soc.h>
#include <sound/pcm.h>
#include <sound/initval.h>

#define DRV_NAME "celldai-codec"


/***********************************************************************
 * This device really does not need a CODEC. There definitely is to "encoding"
 * or "decoding" being done here. All that is desired is a passthrough for the raw
 * PCM. Likewise, there is no interconnect to the cell modem (i2c, SPI, etc) 
 * and it is not configurable. This file is really just a stub.
 ***********************************************************************/

static struct snd_soc_codec_driver soc_codec_celldai_codec = {
};

static struct snd_soc_dai_driver pcm_stub_dai = {
    .name        = "celldai-codec-dai",
    .playback     = {
        .stream_name = "Playback",
        .channels_min = 1,
        .channels_max = 1,
        .rates = SNDRV_PCM_RATE_8000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE,
    },
    .capture = {
        .stream_name = "Capture",
        .channels_min = 1,
        .channels_max = 1,
        .rates = SNDRV_PCM_RATE_8000,
        .formats = SNDRV_PCM_FMTBIT_S16_LE,
    },
};

static int celldai_codec_probe(struct platform_device *pdev)
{
    return snd_soc_register_codec(&pdev->dev, &soc_codec_celldai_codec,
            &pcm_stub_dai, 1);
}

static int celldai_codec_remove(struct platform_device *pdev)
{
    snd_soc_unregister_codec(&pdev->dev);
    return 0;
}

static struct platform_driver celldai_codec_driver = {
    .probe        = celldai_codec_probe,
    .remove        = celldai_codec_remove,
    .driver        = {
        .name    = DRV_NAME,
        .owner    = THIS_MODULE,
    },
};

static int __init dit_modinit(void)
{
    return platform_driver_register(&celldai_codec_driver);
}

static void __exit dit_exit(void)
{
    platform_driver_unregister(&celldai_codec_driver);
}

module_init(dit_modinit);
module_exit(dit_exit);

MODULE_AUTHOR("TBD");
MODULE_DESCRIPTION("Custom Cell DAI 'codec' driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux