This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM335x Audio with WM8580 Codec



Hello,

I am kind of lost when it comes to this linux alsa stuff.  Basically i am trying to get a WM8580 Codec to work on a custom AM335x board in the latest version of the Sitara SDK (linux-3.2.0-psp06.00.00.00).  I have gotten to the point where it can find the card on startup and i can play a wav file, but unfortunately when i play it all that comes out is static.  Now the static that comes out sounds the same each time i play a certain file telling me that it is sending the same data to the Codec but maybe in the wrong format or something?

I'll walk through what i have thus far.  Here is my hardware setup:

As far as software goes i basically modified the board file to setup the pins for using MCASP0 and I2C0 for communication with the Codec:

static u8 evm_iis_serializer_direction0[] = {
	TX_MODE,	RX_MODE,	INACTIVE_MODE,	INACTIVE_MODE,	
	INACTIVE_MODE,	INACTIVE_MODE,	INACTIVE_MODE,	INACTIVE_MODE,
	INACTIVE_MODE,	INACTIVE_MODE,	INACTIVE_MODE,	INACTIVE_MODE,
	INACTIVE_MODE,	INACTIVE_MODE,	INACTIVE_MODE,	INACTIVE_MODE,
};

static struct snd_platform_data evm_snd_data0 = {
	.tx_dma_offset	= 0x46000000,	/* McASP0 */
	.rx_dma_offset	= 0x46000000,
	.op_mode	= DAVINCI_MCASP_IIS_MODE,
	.num_serializer	= ARRAY_SIZE(evm_iis_serializer_direction0),
	.tdm_slots	= 2,
	.serial_dir	= evm_iis_serializer_direction0,
	.asp_chan_q	= EVENTQ_2,
	.version	= MCASP_VERSION_3,
	.txnumevt	= 32,
	.rxnumevt	= 32,
	.get_context_loss_count	= omap_pm_get_dev_context_loss_count,
};

/* Module pin mux for I2C0 */
static struct pinmux_config i2c0_pin_mux[] = {
	{"i2c0_sda.i2c0_sda", OMAP_MUX_MODE0 | AM33XX_SLEWCTRL_SLOW | AM33XX_PULL_ENBL | AM33XX_INPUT_EN},	//!< I2C0 SDA
	{"i2c0_scl.i2c0_scl", OMAP_MUX_MODE0 | AM33XX_SLEWCTRL_SLOW | AM33XX_PULL_ENBL | AM33XX_INPUT_EN},	//!< I2C0 SCL
	{NULL, 0},
};

/* Module pin mux for I2C2 */
static struct pinmux_config i2c2_pin_mux[] = {
	{"uart1_ctsn.i2c2_sda", OMAP_MUX_MODE3 | AM33XX_SLEWCTRL_SLOW | AM33XX_PULL_UP | AM33XX_INPUT_EN},	//!< I2C2 SDA
	{"uart1_rtsn.i2c2_scl", OMAP_MUX_MODE3 | AM33XX_SLEWCTRL_SLOW | AM33XX_PULL_UP | AM33XX_INPUT_EN},	//!< I2C2 SCL
	{NULL, 0},
};

/* Module pin mux for mcasp0 */
static struct pinmux_config mcasp0_pin_mux[] = {
	{"mcasp0_aclkx.mcasp0_aclkx", OMAP_MUX_MODE0 | AM33XX_PIN_INPUT_PULLDOWN},	//!< MCASP BCLK
	{"mcasp0_fsx.mcasp0_fsx", OMAP_MUX_MODE0 | AM33XX_PIN_INPUT_PULLDOWN},		//!< MCASP FRAME SYNC
	{"mcasp0_axr0.mcasp0_axr0", OMAP_MUX_MODE0 | AM33XX_PIN_INPUT_PULLDOWN},	//!< MCASP DATA OUT
	{"mcasp0_axr1.mcasp0_axr1", OMAP_MUX_MODE0 | AM33XX_PIN_INPUT_PULLDOWN},	//!< MCASP DATA IN
	{"gpmc_a0.gpio1_16", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT_PULLDOWN},			//!< AUDIO OUT-TO-IN
	//{"xdma_event_intr0.gpio0_19", OMAP_MUX_MODE7 | AM33XX_PIN_INPUT_PULLUP},	//!< AUDIO MCLK
	//{"gpmc_csn2.gpio1_31", OMAP_MUX_MODE7 | AM33XX_PIN_OUTPUT_PULLUP},		//!< AUDIO CS
	{NULL, 0},
};

static struct i2c_board_info __initdata evm_i2c0_boardinfo[] = {
	{
		I2C_BOARD_INFO("wm8580", 0x1b),
	},
	{
		I2C_BOARD_INFO("tps65910", TPS65910_I2C_ID1),
		.platform_data  = &am335x_tps65910_info,
	},
};

/* Setup McASP 0 */
static void mcasp0_init(int profile)
{
	/* Configure McASP */
	setup_pin_mux(mcasp0_pin_mux);
	am335x_register_mcasp(&evm_snd_data0, 0);
	return;
}

Then i pretty much replaced sound/soc/davinci/davinci-evm.c with new code for this coded (I used sound/soc/samsung/smdk_wm8580.c as an example):

/*
 * ASoC driver for TI DAVINCI EVM platform
 *
 * Author:      Vladimir Barinov, <vbarinov@embeddedalley.com>
 * Copyright:   (C) 2007 MontaVista Software, Inc., <source@mvista.com>
 *
 * 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.
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>

#include <asm/dma.h>
#include <asm/mach-types.h>

#include <asm/hardware/asp.h>
#include <mach/edma.h>
#if defined(CONFIG_MACH_AM335XEVM)
#include <mach/board-am335xevm.h>
#endif

#include "../codecs/wm8580.h"
#include "davinci-pcm.h"
#include "davinci-i2s.h"
#include "davinci-mcasp.h"

/* Board has a 12MHZ crystal attached to WM8580 */
#define EVM_WM8580_FREQ 12000000
#define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF)
//#define AUDIO_FORMAT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM)

volatile int myTestSpeed;
static int evm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct snd_soc_dai *codec_dai = rtd->codec_dai;
	unsigned int pll_out;
	int rfs;
	int ret;

	/* The Fvco for WM8580 PLLs must fall within [90,100]MHz.
	 * This criterion can't be met if we request PLL output
	 * as {8000x256, 64000x256, 11025x256}Hz.
	 * As a wayout, we rather change rfs to a minimum value that
	 * results in (params_rate(params) * rfs), and itself, acceptable
	 * to both - the CODEC and the CPU.
	 */
	myTestSpeed = params_rate(params);
	switch (myTestSpeed) {
	case 16000:
	case 22050:
	case 32000:
	case 44100:
	case 48000:
	case 88200:
	case 96000:
		rfs = 256;
		break;
	case 64000:
		rfs = 384;
		break;
	case 8000:
	case 11025:
		rfs = 512;
		break;
	default:
		return -EINVAL;
	}
	pll_out = params_rate(params) * rfs;

	/* Set the Codec DAI configuration */
	ret = snd_soc_dai_set_fmt(codec_dai, AUDIO_FORMAT);
	if (ret < 0)
		return ret;

	/* Set the AP DAI configuration */
	ret = snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT);
	if (ret < 0)
		return ret;

	/* Set WM8580 to drive MCLK from its PLLA */
	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK, WM8580_CLKSRC_PLLA);
	if (ret < 0)
		return ret;

	ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0, EVM_WM8580_FREQ, pll_out);
	if (ret < 0)
		return ret;

	ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA, pll_out, SND_SOC_CLOCK_IN);
	if (ret < 0)
		return ret;

	return 0;
}

static struct snd_soc_ops evm_ops = {
	.hw_params = evm_hw_params,
};

/* SMDK Playback widgets */
static const struct snd_soc_dapm_widget evm_wm8580_dapm_widgets[] = {
	SND_SOC_DAPM_HP("Front", NULL),
	SND_SOC_DAPM_HP("Center+Sub", NULL),
	SND_SOC_DAPM_HP("Rear", NULL),

	SND_SOC_DAPM_MIC("MicIn", NULL),
	SND_SOC_DAPM_LINE("LineIn", NULL),
};

/* SMDK-PAIFTX connections */
static const struct snd_soc_dapm_route evm_wm8580_audio_map[] = {
	/* MicIn feeds AINL */
	{"AINL", NULL, "MicIn"},

	/* LineIn feeds AINL/R */
	{"AINL", NULL, "LineIn"},
	{"AINR", NULL, "LineIn"},

	/* Front Left/Right are fed VOUT1L/R */
	{"Front", NULL, "VOUT1L"},
	{"Front", NULL, "VOUT1R"},

	/* Center/Sub are fed VOUT2L/R */
	{"Center+Sub", NULL, "VOUT2L"},
	{"Center+Sub", NULL, "VOUT2R"},

	/* Rear Left/Right are fed VOUT3L/R */
	{"Rear", NULL, "VOUT3L"},
	{"Rear", NULL, "VOUT3R"},
};

static int evm_wm8580_init_paiftx(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_codec *codec = rtd->codec;
	struct snd_soc_dapm_context *dapm = &codec->dapm;

	/* Enabling the microphone requires the fitting of a 0R
	 * resistor to connect the line from the microphone jack.
	 */
	snd_soc_dapm_disable_pin(dapm, "MicIn");

	return 0;
}

enum {
	PRI_PLAYBACK = 0,
	PRI_CAPTURE,
	SEC_PLAYBACK,
};

static struct snd_soc_dai_link evm_dai[] = {
	[PRI_PLAYBACK] = { /* Primary Playback i/f */
		.name = "WM8580 PAIF RX",
		.stream_name = "Playback",
		.cpu_dai_name = "davinci-mcasp.0",
		.codec_dai_name = "wm8580-hifi-playback",
		.platform_name = "davinci-pcm-audio",
		.codec_name = "wm8580.1-001b",
		.ops = &evm_ops,
	},
	[PRI_CAPTURE] = { /* Primary Capture i/f */
		.name = "WM8580 PAIF TX",
		.stream_name = "Capture",
		.cpu_dai_name = "davinci-mcasp.0",
		.codec_dai_name = "wm8580-hifi-capture",
		.platform_name = "davinci-pcm-audio",
		.codec_name = "wm8580.1-001b",
		.init = evm_wm8580_init_paiftx,
		.ops = &evm_ops,
	},
	[SEC_PLAYBACK] = { /* Sec_Fifo Playback i/f */
		.name = "Sec_FIFO TX",
		.stream_name = "Playback",
		.cpu_dai_name = "davinci-mcasp.0",
		.codec_dai_name = "wm8580-hifi-playback",
		.platform_name = "davinci-pcm-audio",
		.codec_name = "wm8580.1-001b",
		.ops = &evm_ops,
	},
};

static struct snd_soc_card evm_snd_soc_card = {
	.name = "AM335X WM8580",
	.dai_link = evm_dai,
	.num_links = 2,

	.dapm_widgets = evm_wm8580_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(evm_wm8580_dapm_widgets),
	.dapm_routes = evm_wm8580_audio_map,
	.num_dapm_routes = ARRAY_SIZE(evm_wm8580_audio_map),
};

static struct platform_device *evm_snd_device;

static int __init evm_init(void)
{
	int ret;

	evm_snd_device = platform_device_alloc("soc-audio", -1);
	if (!evm_snd_device)
		return -ENOMEM;

	platform_set_drvdata(evm_snd_device, &evm_snd_soc_card);
	ret = platform_device_add(evm_snd_device);
	if (ret)
		platform_device_put(evm_snd_device);

	return ret;
}

static void __exit evm_exit(void)
{
	platform_device_unregister(evm_snd_device);
}

module_init(evm_init);
module_exit(evm_exit);

MODULE_AUTHOR("Vladimir Barinov");
MODULE_DESCRIPTION("TI DAVINCI EVM ASoC driver");
MODULE_LICENSE("GPL");

After those changes i was able to see the soundcard and play files from linux; however, like i said the sound that comes out is just all static.  I am basically just looking for suggestions of where to start because i have no idea of what to do from here to track down the problem : /.  So any suggestions or assistance would be greatly appreciated!!!

  • Hello Jarrod,

    Is your codec meant to be the I2S master?  If so, have you verified that is generating the proper bit clock and frame sync and that those signals are reaching the McASP pins?  Additionally, have you checked to see if your frame sync has the form of the format that you intend to use(DSP mode versus I2S mode, for example)?

    If your clocks are being generated correctly, have you scoped the serial data lines to see if there is activity?

    While you are playing your wav file, can you do a cat /proc/asound/card0/pcm0p/sub0/status a couple of times to see if you see the pointer values changing?  I suppose this would be a test that tells us that same thing as checking the actual I2S lines, but it's worth trying out.

    Regards,

    Josh

  • Thanks for the suggestions!

    Yes the Codec is meant to be the master.

    I had scoped the clock and sync lines earlier on when i was still working through getting the code setup and they were fine then.  However, i guess i did not recheck them after i finished code modifications because in scoping them now i found an interesting thing.  The clock looked like this (yellow is frame sync and the blue is the bit clock):

    Which says to me there is clock collision.  I dug into the issue more and found that the clkout on the codec was enabled and i was running the codec's mclk from its PLLA.  And since the clkout and mclk pins are shorted together this caused the crazy clock above.  Originally we had planned to have multiple codes and the clkout was just sourcing the mclk of all codecs, but that changed.  Anyways i see possible solutions as:

    1. Remove the short between clkout and mclk

    2. Set the clkout pin output to NONE (tri-state)

    3. Source mclk from the pin

    For now i chose to go with option 2 and simply added this line to my evm_hw_params function in sound/soc/davinci/davinci-evm.c:

    	/* Make nothing drive the clkout pin */
    	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_CLKOUTSRC, WM8580_CLKSRC_NONE);
    	if (ret < 0)
    		return ret;

    Now my clock looks good:

    And my sound files are playing as desired : ).  Now just to work through getting all the ALSA channel mapping stuff setup.  Thanks for your help!!! : )

  • Jarrod,

    I'm glad to hear you made some progress.

    One thing to point out is that by looking at your framesync, it looks like you are configuring your codec to use something like DSP mode, whereas the default state of the mcasp driver is to be in I2S mode.  Have you resolved this possible issue?

    Regards,

    Josh

  • Josh,

    Yes I have the codec in DSP_B mode.  And according to TRM it looks like the MCASP modes are either TDM or DIT and I2S is basically a 2 slot TDM mode.  Judging by the timing diagrams it looks like DSP B would comply with this TDM scheme.  The frame sync low-to-high transition would indicate the start of a new frame at which point N slots would follow.  So even though the DSP frame sync length is only 1 bit clock, it shouldn't matter because slot still starts at the same point at the transition correct?

    AM335X MCASP Timing:

    My sound is playing great right now as a simple 2 slot setup.  When i increase the slots to support the other channels i would imagine it should continue to work as the frame syncs will occur as they do currently and more slots will be just be transmitted between.  I guess i will find out for sure as i add in the other slots : )