All of lore.kernel.org
 help / color / mirror / Atom feed
* ASoC: at91sam9260-PCM1808/TAS5709-based board driver
@ 2009-08-08 17:39 Pedro Sanchez
  2009-08-09 10:36 ` Mark Brown
  2009-08-09 13:35 ` Jon Smirl
  0 siblings, 2 replies; 17+ messages in thread
From: Pedro Sanchez @ 2009-08-08 17:39 UTC (permalink / raw)
  To: alsa-devel

[-- Attachment #1: Type: text/plain, Size: 1061 bytes --]

Hello,

I have to develop a driver for a custom board based on the at91sam9260 
with a PCM1808/TAS5709 chipset combo. I am addressing the PCM1808 first 
because it is a simpler device. Since I'm new to ASoC I'm looking for 
some help in this list on how to go about writing this driver.

So far I have identified two files that I need to work on (other than 
the needed kconfig modifications). First is the codec driver at 
sound/soc/codecs/PCM1808.[c|h] which is based on the existent pcm3008 
driver; I believe I'm fine with this.

The second file is the machine driver at sound/soc/atmel/myBoard.c. I'm 
using sam9g20_wm8731.c as reference, on which I believe I found a typo 
which I reported in a separated e-mail.

So my first question is, is this two-files step the right approach? am I 
in the right track? I'm using the 2.6.30 kernel.

Other questions will come and I hope I will get some cycles from you to 
help me direct my work. Of course I'll be glad to submit my driver work 
for inclusion in the kernel if it proves useful.

Regards,

-- 
Pedro


[-- Attachment #2: psanchez.vcf --]
[-- Type: text/x-vcard, Size: 309 bytes --]

begin:vcard
fn:Pedro I. Sanchez
n:Sanchez;Pedro I.
org:FOSSTel Engineering Inc.
adr:;;25 Nortoba Crescent;Kanata;ON;K2T 1G(;Canada
email;internet:psanchez@fosstel.com
title:Principal Engineer
tel;work:+1 613 216 7513
tel;cell:+1 613 818 2465
x-mozilla-html:FALSE
url:http://fosstel.com
version:2.1
end:vcard


[-- Attachment #3: Type: text/plain, Size: 160 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-08 17:39 ASoC: at91sam9260-PCM1808/TAS5709-based board driver Pedro Sanchez
@ 2009-08-09 10:36 ` Mark Brown
  2009-08-09 13:35 ` Jon Smirl
  1 sibling, 0 replies; 17+ messages in thread
From: Mark Brown @ 2009-08-09 10:36 UTC (permalink / raw)
  To: Pedro Sanchez; +Cc: alsa-devel

On Sat, Aug 08, 2009 at 01:39:16PM -0400, Pedro Sanchez wrote:

> So my first question is, is this two-files step the right approach?
> am I in the right track? I'm using the 2.6.30 kernel.

Yes, this is the correct approach.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-08 17:39 ASoC: at91sam9260-PCM1808/TAS5709-based board driver Pedro Sanchez
  2009-08-09 10:36 ` Mark Brown
@ 2009-08-09 13:35 ` Jon Smirl
  2009-08-09 13:37   ` Jon Smirl
  1 sibling, 1 reply; 17+ messages in thread
From: Jon Smirl @ 2009-08-09 13:35 UTC (permalink / raw)
  To: Pedro Sanchez; +Cc: alsa-devel

[-- Attachment #1: Type: text/plain, Size: 1439 bytes --]

On Sat, Aug 8, 2009 at 1:39 PM, Pedro Sanchez<psanchez@fosstel.com> wrote:
> Hello,
>
> I have to develop a driver for a custom board based on the at91sam9260 with
> a PCM1808/TAS5709 chipset combo. I am addressing the PCM1808 first because
> it is a simpler device. Since I'm new to ASoC I'm looking for some help in
> this list on how to go about writing this driver.

I attached a PCM1690 driver I'm currently working on.


>
> So far I have identified two files that I need to work on (other than the
> needed kconfig modifications). First is the codec driver at
> sound/soc/codecs/PCM1808.[c|h] which is based on the existent pcm3008
> driver; I believe I'm fine with this.
>
> The second file is the machine driver at sound/soc/atmel/myBoard.c. I'm
> using sam9g20_wm8731.c as reference, on which I believe I found a typo which
> I reported in a separated e-mail.
>
> So my first question is, is this two-files step the right approach? am I in
> the right track? I'm using the 2.6.30 kernel.
>
> Other questions will come and I hope I will get some cycles from you to help
> me direct my work. Of course I'll be glad to submit my driver work for
> inclusion in the kernel if it proves useful.
>
> Regards,
>
> --
> Pedro
>
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
>



-- 
Jon Smirl
jonsmirl@gmail.com

[-- Attachment #2: pcm1690.c --]
[-- Type: text/x-csrc, Size: 8323 bytes --]

/*
 * Texas Instruments PCM1690 low power audio CODEC
 * ALSA SoC CODEC driver
 *
 * Copyright (C) Jon Smirl <jonsmirl@gmail.com>
 */
#define DEBUG

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/device.h>
#include <linux/sysfs.h>
#include <linux/i2c.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/soc-of-simple.h>
#include <sound/tlv.h>
#include <sound/initval.h>

#include "pcm1690.h"

#define PCM1690_REG_MAX 0x10

/* pcm1690 driver private data */
struct pcm1690_priv {
	struct i2c_client *client;
	struct snd_soc_codec codec;

	/* performance shadow of i2c registers */
	u8 reg_cache[PCM1690_REG_MAX];
};

/* ---------------------------------------------------------------------
 * Register access routines
 */

static unsigned int pcm1690_reg_read(struct snd_soc_codec *codec, unsigned int reg)
{
	struct pcm1690_priv *priv = codec->private_data;
	u8 value, r = reg + 0x40; /* HW regs start at 0x40 */
	int rc;
	struct i2c_msg msgs[2] = {
		{.addr = priv->client->addr, .flags = 0, .len = 1, .buf = &r},
		{.addr = priv->client->addr, .flags = I2C_M_RD, .len = 1, .buf = &value}
	};

	rc = i2c_transfer(priv->client->adapter, msgs, ARRAY_SIZE(msgs));
	if (rc != 2) {
		dev_err(&priv->client->dev, "%s: rc=%d reg=%02x rc != size\n",
			__func__, rc, r);
		return -1;
	}
	priv->reg_cache[reg] = value;
	return value;
}

static int pcm1690_reg_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value)
{
	struct pcm1690_priv *priv = codec->private_data;
	u8 buf[2];
	int rc;

	priv->reg_cache[reg] = value;

	buf[0] = reg + 0x40;
	buf[1] = value;

	rc = i2c_master_send(priv->client, buf, sizeof buf);
	if (rc != sizeof buf) {
		dev_err(&priv->client->dev,
			"%s: rc=%d reg=%02x size %02x rc != size\n",
			__func__, rc, reg, sizeof buf);
		return -EIO;
	}
	return 0;
}

/* ---------------------------------------------------------------------
 * Digital Audio Interface Operations
 */
static int pcm1690_hw_params(struct snd_pcm_substream *substream,
			   struct snd_pcm_hw_params *params,
			   struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_device *socdev = rtd->socdev;
	struct snd_soc_codec *codec = socdev->card->codec;
	struct pcm1690_priv *priv = codec->private_data;

	dev_dbg(&priv->client->dev, "pcm1690_hw_params(substream=%p, params=%p)\n",
		substream, params);
	dev_dbg(&priv->client->dev, "rate=%i format=%i\n", params_rate(params),
		params_format(params));

	return 0;
}

/**
 * pcm1690_mute - Mute control to reduce noise when changing audio format
 */
static int pcm1690_mute(struct snd_soc_dai *dai, int mute)
{
	struct snd_soc_codec *codec = dai->codec;
	struct pcm1690_priv *priv = codec->private_data;

	dev_dbg(&priv->client->dev, "pcm1690_mute(dai=%p, mute=%i)\n",
		dai, mute);

	return 0;
}

/* ---------------------------------------------------------------------
 * Digital Audio Interface Definition
 */

static struct snd_soc_dai_ops pcm1690_dai_ops = {
	.hw_params = pcm1690_hw_params,
	.digital_mute = pcm1690_mute,
};

struct snd_soc_dai pcm1690_dai = {
	.name = "pcm1690",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 2,
		.channels_max = 2,
		.rates = PCM1690_RATES,
		.formats = PCM1690_FORMATS,
	},
	.ops = &pcm1690_dai_ops,
};
EXPORT_SYMBOL_GPL(pcm1690_dai);

/* ---------------------------------------------------------------------
 * ALSA controls
 */

static const DECLARE_TLV_DB_LINEAR(master_tlv, -6300, 0);


static const struct snd_kcontrol_new pcm1690_snd_controls[] = {

SOC_SINGLE_TLV("Ch 1 Volume", 0x8, 0, 0x7f, 0, master_tlv),
SOC_SINGLE_TLV("Ch 2 Volume", 0x9, 0, 0x7f, 0, master_tlv),
SOC_SINGLE_TLV("Ch 3 Volume", 0xa, 0, 0x7f, 0, master_tlv),
SOC_SINGLE_TLV("Ch 4 Volume", 0xb, 0, 0x7f, 0, master_tlv),
SOC_SINGLE_TLV("Ch 5 Volume", 0xc, 0, 0x7f, 0, master_tlv),
SOC_SINGLE_TLV("Ch 6 Volume", 0xd, 0, 0x7f, 0, master_tlv),
SOC_SINGLE_TLV("Ch 7 Volume", 0xe, 0, 0x7f, 0, master_tlv),
SOC_SINGLE_TLV("Ch 8 Volume", 0xf, 0, 0x7f, 0, master_tlv),

};


/* ---------------------------------------------------------------------
 * SoC CODEC portion of driver: probe and release routines
 */
static int pcm1690_probe(struct platform_device *pdev)
{
	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
	struct snd_soc_codec *codec;
	struct snd_kcontrol *kcontrol;
	struct pcm1690_priv *priv;
	int i, ret, err;

	dev_info(&pdev->dev, "Probing pcm1690 SoC CODEC driver\n");
	dev_dbg(&pdev->dev, "socdev=%p\n", socdev);
	dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data);

	/* Fetch the relevant pcm1690 private data here (it's already been
	 * stored in the .codec pointer) */
	priv = socdev->codec_data;
	if (priv == NULL) {
		dev_err(&pdev->dev, "pcm1690: missing codec pointer\n");
		return -ENODEV;
	}
	codec = &priv->codec;
	socdev->card->codec = codec;

	dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n",
		&pdev->dev, socdev->dev);
	/* register pcms */
	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
	if (ret < 0) {
		dev_err(&pdev->dev, "pcm1690: failed to create pcms\n");
		return -ENODEV;
	}

	/* register controls */
	dev_dbg(&pdev->dev, "Registering controls\n");
	for (i = 0; i < ARRAY_SIZE(pcm1690_snd_controls); i++) {
		kcontrol = snd_ctl_new1(&pcm1690_snd_controls[i], codec);
		err = snd_ctl_add(codec->card, kcontrol);
		if (err < 0)
			dev_err(&pdev->dev, "pcm1690: failed to create control %x\n", err);
	}

	/* CODEC is setup, we can register the card now */
	dev_dbg(&pdev->dev, "Registering card\n");
	ret = snd_soc_init_card(socdev);
	if (ret < 0) {
		dev_err(&pdev->dev, "pcm1690: failed to register card\n");
		goto card_err;
	}
	return 0;

 card_err:
	snd_soc_free_pcms(socdev);
	return ret;
}

static int pcm1690_remove(struct platform_device *pdev)
{
	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
	snd_soc_free_pcms(socdev);
	return 0;
}

struct snd_soc_codec_device pcm1690_soc_codec_dev = {
	.probe = pcm1690_probe,
	.remove = pcm1690_remove,
};
EXPORT_SYMBOL_GPL(pcm1690_soc_codec_dev);

/* ---------------------------------------------------------------------
 * i2c device portion of driver: probe and release routines and i2c
 * 				 driver registration.
 */
static int pcm1690_i2c_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{
	struct pcm1690_priv *priv;

	dev_dbg(&client->dev, "probing pcm1690 i2c device\n");

	/* Allocate driver data */
	priv = kzalloc(sizeof *priv, GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	/* Initialize the driver data */
	priv->client = client;
	i2c_set_clientdata(client, priv);

	/* Setup what we can in the codec structure so that the register
	 * access functions will work as expected.  More will be filled
	 * out when it is probed by the SoC CODEC part of this driver */
	priv->codec.private_data = priv;
	priv->codec.name = "pcm1690";
	priv->codec.owner = THIS_MODULE;
	priv->codec.dai = &pcm1690_dai;
	priv->codec.num_dai = 1;
	priv->codec.read = pcm1690_reg_read;
	priv->codec.write = pcm1690_reg_write;
	priv->codec.reg_cache_size = PCM1690_REG_MAX;

	mutex_init(&priv->codec.mutex);
	INIT_LIST_HEAD(&priv->codec.dapm_widgets);
	INIT_LIST_HEAD(&priv->codec.dapm_paths);

	dev_dbg(&client->dev, "I2C device initialized\n");
	return 0;
}

static int pcm1690_i2c_remove(struct i2c_client *client)
{
	struct pcm1690_priv *priv = dev_get_drvdata(&client->dev);

	kfree(priv);

	return 0;
}

static const struct i2c_device_id pcm1690_device_id[] = {
	{ "pcm1690", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, pcm1690_device_id);

static struct i2c_driver pcm1690_driver = {
	.driver		= {
		.name	= "pcm1690",
		.owner	= THIS_MODULE,
	},
	.probe		= pcm1690_i2c_probe,
	.remove		= __devexit_p(pcm1690_i2c_remove),
	.id_table	= pcm1690_device_id,
};

static __init int pcm1690_driver_init(void)
{
	snd_soc_register_dai(&pcm1690_dai);
	return i2c_add_driver(&pcm1690_driver);
}

static __exit void pcm1690_driver_exit(void)
{
	i2c_del_driver(&pcm1690_driver);
}

module_init(pcm1690_driver_init);
module_exit(pcm1690_driver_exit);

MODULE_AUTHOR("Jon Smirl");
MODULE_DESCRIPTION("PCM1690 codec module");
MODULE_LICENSE("GPL");

[-- Attachment #3: pcm1690.h --]
[-- Type: text/x-chdr, Size: 479 bytes --]

/*
 * Texas Instruments PCM1690 low power audio CODEC
 * ALSA SoC CODEC driver
 *
 * Copyright (C) Jon Smirl <jonsmirl@gmail.com>
 */


#define PCM1690_RATES (SNDRV_PCM_RATE_32000 |\
				SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
				SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |\
				SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
#define PCM1690_FORMATS SNDRV_PCM_FMTBIT_S32

extern struct snd_soc_dai pcm1690_dai;
extern struct snd_soc_codec_device pcm1690_soc_codec_dev;

[-- Attachment #4: Type: text/plain, Size: 160 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-09 13:35 ` Jon Smirl
@ 2009-08-09 13:37   ` Jon Smirl
  2009-08-11 18:41     ` Pedro I. Sanchez
  0 siblings, 1 reply; 17+ messages in thread
From: Jon Smirl @ 2009-08-09 13:37 UTC (permalink / raw)
  To: Pedro Sanchez; +Cc: alsa-devel

[-- Attachment #1: Type: text/plain, Size: 1645 bytes --]

On Sun, Aug 9, 2009 at 9:35 AM, Jon Smirl<jonsmirl@gmail.com> wrote:
> On Sat, Aug 8, 2009 at 1:39 PM, Pedro Sanchez<psanchez@fosstel.com> wrote:
>> Hello,
>>
>> I have to develop a driver for a custom board based on the at91sam9260 with
>> a PCM1808/TAS5709 chipset combo. I am addressing the PCM1808 first because
>> it is a simpler device. Since I'm new to ASoC I'm looking for some help in
>> this list on how to go about writing this driver.
>
> I attached a PCM1690 driver I'm currently working on.

The attached fabric driver show how to use it.

>
>
>>
>> So far I have identified two files that I need to work on (other than the
>> needed kconfig modifications). First is the codec driver at
>> sound/soc/codecs/PCM1808.[c|h] which is based on the existent pcm3008
>> driver; I believe I'm fine with this.
>>
>> The second file is the machine driver at sound/soc/atmel/myBoard.c. I'm
>> using sam9g20_wm8731.c as reference, on which I believe I found a typo which
>> I reported in a separated e-mail.
>>
>> So my first question is, is this two-files step the right approach? am I in
>> the right track? I'm using the 2.6.30 kernel.
>>
>> Other questions will come and I hope I will get some cycles from you to help
>> me direct my work. Of course I'll be glad to submit my driver work for
>> inclusion in the kernel if it proves useful.
>>
>> Regards,
>>
>> --
>> Pedro
>>
>>
>> _______________________________________________
>> Alsa-devel mailing list
>> Alsa-devel@alsa-project.org
>> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>>
>>
>
>
>
> --
> Jon Smirl
> jonsmirl@gmail.com
>



-- 
Jon Smirl
jonsmirl@gmail.com

[-- Attachment #2: dspeak01-audio-fabric.c --]
[-- Type: text/x-csrc, Size: 4767 bytes --]

/*
 * sound/soc/fsl/dspeak01_fabric.c -- The ALSA glue fabric for Digispeaker dspeak01
 *
 * Copyright 2008 Jon Smirl, Digispeaker
 * Author: Jon Smirl <jonsmirl@gmail.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/of_device.h>
#include <linux/of_platform.h>
#include <linux/of_i2c.h>
#include <linux/of_gpio.h>

#include <sound/soc.h>
#include <sound/soc-of-simple.h>

#include "../codecs/pcm1690.h"
#include "mpc5200_dma.h"
#include "mpc5200_psc_i2s.h"

static int dspeak01_fabric_startup(struct snd_pcm_substream *substream)
{
	printk("dspeak01_fabric_startup\n");
	return 0;
}

static void dspeak01_fabric_shutdown(struct snd_pcm_substream *substream)
{
	printk("dspeak01_fabric_shutdown\n");
}

static int dspeak01_fabric_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
{
	uint rate;
	int ret;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;

	printk("dspeak01_fabric_hw_params\n");

	switch (params_rate(params)) {
	case 11025:
	case 22050:
	case 44100:
	case 88200:
	case 176400:
		rate = 22579200;
		break;
	default:
		rate = 24576000;
		break;
	}

	ret = snd_soc_dai_set_sysclk(cpu_dai, MPC52xx_CLK_INTERNAL, rate, SND_SOC_CLOCK_OUT);
	if (ret < 0)
		return ret;

	return 0;
}

static int dspeak01_fabric_hw_free(struct snd_pcm_substream *substream)
{
	printk("dspeak01_fabric_hw_free\n");
	return 0;
}

static int dspeak01_fabric_prepare(struct snd_pcm_substream *substream)
{
	printk("dspeak01_fabric_prepare\n");
	return 0;
}

static int dspeak01_fabric_trigger(struct snd_pcm_substream *substream, int trigger)
{
	printk("dspeak01_fabric_trigger\n");
	return 0;
}

static struct snd_soc_ops dspeak01_fabric_ops = {
	.startup = dspeak01_fabric_startup,
	.shutdown = dspeak01_fabric_shutdown,
	.hw_params = dspeak01_fabric_hw_params,
	.hw_free = dspeak01_fabric_hw_free,
	.prepare = dspeak01_fabric_prepare,
	.trigger = dspeak01_fabric_trigger,
};

static struct snd_soc_device device;
static struct snd_soc_card card;

static struct snd_soc_dai_link dspeak01_fabric_dai[] = {
{
	.name = "I2S-0",
	.stream_name = "I2S-0",
	.codec_dai = &pcm1690_dai,
	.ops = &dspeak01_fabric_ops,
},
{
	.name = "I2S-1",
	.stream_name = "I2S-1",
	.codec_dai = &pcm1690_dai,
	.ops = &dspeak01_fabric_ops,
},
};

static int dspeak01_fabric_gpio_reset;
static int dspeak01_fabric_gpio_mute;

static __init int dspeak01_fabric_init_gpio(struct of_device *op, struct device_node *np, int i, int state)
{
	int rc;
	int gpio;

	gpio = of_get_gpio(np, i);
	if (gpio >= 0) {
		rc = gpio_request(gpio, dev_name(&op->dev));
		if (rc) {
			dev_err(&op->dev, "can't request Dspeak01 gpio #%d rc %d\n", i, rc);
			return rc;
		}
		gpio_direction_output(gpio, state);
	} else
		if (gpio == -EINVAL) {
			dev_err(&op->dev, "Dspeak01 gpio #%d is invalid\n", i);
		}
	return gpio;
}

static __init int dspeak01_fabric_init(void)
{
	struct of_device *op;
	struct device_node *np;
	struct platform_device *pdev;
	struct i2c_client *i2c_dev;
	int rc;

	if (!machine_is_compatible("digispeaker,dspeak01"))
		return -ENODEV;

	card.platform = &mpc5200_audio_dma_platform;
	card.name = "Digispeaker";
	card.dai_link = dspeak01_fabric_dai;
	dspeak01_fabric_dai[0].cpu_dai = psc_i2s_dai[0];
	dspeak01_fabric_dai[1].cpu_dai = psc_i2s_dai[1];

	card.num_links = ARRAY_SIZE(dspeak01_fabric_dai);

	device.card = &card;
	device.codec_dev = &pcm1690_soc_codec_dev;

	/* Check for the codec handle.   */

	op = to_of_device(psc_i2s_dai[0]->dev);
	np = of_parse_phandle(op->node, "codec-handle", 0);
	if (np) {
		i2c_dev = of_find_i2c_device_by_node(np);
		device.codec_data = i2c_get_clientdata(i2c_dev);

		dspeak01_fabric_gpio_reset = dspeak01_fabric_init_gpio(op, np, 0, 1);
		if (dspeak01_fabric_gpio_reset < 0)
			return dspeak01_fabric_gpio_reset;
		dspeak01_fabric_gpio_mute = dspeak01_fabric_init_gpio(op, np, 1, 1);
		if (dspeak01_fabric_gpio_mute < 0)
			return dspeak01_fabric_gpio_mute;
	}
	pdev = platform_device_alloc("soc-audio", 1);
	if (!pdev) {
		pr_err("dspeak01_fabric_init: platform_device_alloc() failed\n");
		return -ENODEV;
	}

	platform_set_drvdata(pdev, &device);
	device.dev = &pdev->dev;

	rc = platform_device_add(pdev);
	if (rc) {
		pr_err("dspeak01_fabric_init: platform_device_add() failed\n");
		return -ENODEV;
	}
	return 0;
}

static __exit void dspeak01_fabric_exit(void)
{
}

module_init(dspeak01_fabric_init);
module_exit(dspeak01_fabric_exit);


/* Module information */
MODULE_AUTHOR("Jon Smirl");
MODULE_DESCRIPTION("ASOC Digispeaker fabric module");
MODULE_LICENSE("GPL");

[-- Attachment #3: Type: text/plain, Size: 160 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-09 13:37   ` Jon Smirl
@ 2009-08-11 18:41     ` Pedro I. Sanchez
  2009-08-11 18:51       ` Jon Smirl
  2009-08-11 22:41       ` Mark Brown
  0 siblings, 2 replies; 17+ messages in thread
From: Pedro I. Sanchez @ 2009-08-11 18:41 UTC (permalink / raw)
  To: alsa-devel

Thank you John for the sample code. It's going to be very useful 
specially when I get to the TAS5709 part and the I2C interface.

My next question is with regards to clocking on the SSC interface. The 
PCM1808 in my board has a pretty straight forward connection as follows:

SSC-I/F     PCM1808     Comment
   RF  ---->   LRCK      Frame clock
   RK  ---->   BCK       Bit clock
   RD  <----   DOUT      Digital data

The PCM1808 is hardwired to be in slave mode. The two clock lines are 
expected to be provided by the SoC's SSC interface.

I have the following code in my machine driver to request the SSC 
interface and bind the PCM1808 to it:

         /*
          * Request SSC device
          */
         ssc = ssc_request(0);
         if (IS_ERR(ssc)) {
                 printk(KERN_ERR "ASoC: Failed to request SSC 0\n");
                 ret = PTR_ERR(ssc);
                 ssc = NULL;
                 goto err_ssc;
         }
         ssc_p->ssc = ssc;

What I need to do now is to set the SSC's clock divider to feed the 
PCM1808 with the proper clocking. So these are my next questions:

1. How do I specify in the machine driver that my pcm1808 is hardwired 
to slave mode and that I need bit/frame clocks to be provided by the SSC 
interface?

2. How do I set the SSC's clock divider to provide the right clock signals?

Thanks again,

-- 
Pedro


Jon Smirl wrote:
> On Sun, Aug 9, 2009 at 9:35 AM, Jon Smirl<jonsmirl@gmail.com> wrote:
>> On Sat, Aug 8, 2009 at 1:39 PM, Pedro Sanchez<psanchez@fosstel.com> wrote:
>>> Hello,
>>>
>>> I have to develop a driver for a custom board based on the at91sam9260 with
>>> a PCM1808/TAS5709 chipset combo. I am addressing the PCM1808 first because
>>> it is a simpler device. Since I'm new to ASoC I'm looking for some help in
>>> this list on how to go about writing this driver.
>> I attached a PCM1690 driver I'm currently working on.
> 
> The attached fabric driver show how to use it.
> 
>>
>>> So far I have identified two files that I need to work on (other than the
>>> needed kconfig modifications). First is the codec driver at
>>> sound/soc/codecs/PCM1808.[c|h] which is based on the existent pcm3008
>>> driver; I believe I'm fine with this.
>>>
>>> The second file is the machine driver at sound/soc/atmel/myBoard.c. I'm
>>> using sam9g20_wm8731.c as reference, on which I believe I found a typo which
>>> I reported in a separated e-mail.
>>>
>>> So my first question is, is this two-files step the right approach? am I in
>>> the right track? I'm using the 2.6.30 kernel.
>>>
>>> Other questions will come and I hope I will get some cycles from you to help
>>> me direct my work. Of course I'll be glad to submit my driver work for
>>> inclusion in the kernel if it proves useful.
>>>
>>> Regards,
>>>
>>> --
>>> Pedro
>>>
>>>
>>> _______________________________________________
>>> Alsa-devel mailing list
>>> Alsa-devel@alsa-project.org
>>> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>>>
>>>
>>
>>
>> --
>> Jon Smirl
>> jonsmirl@gmail.com
>>
> 
> 
> 

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-11 18:41     ` Pedro I. Sanchez
@ 2009-08-11 18:51       ` Jon Smirl
  2009-08-11 22:41       ` Mark Brown
  1 sibling, 0 replies; 17+ messages in thread
From: Jon Smirl @ 2009-08-11 18:51 UTC (permalink / raw)
  To: Pedro I. Sanchez; +Cc: alsa-devel

[-- Attachment #1: Type: text/plain, Size: 3630 bytes --]

On Tue, Aug 11, 2009 at 2:41 PM, Pedro I. Sanchez<psanchez@fosstel.com> wrote:
> Thank you John for the sample code. It's going to be very useful specially
> when I get to the TAS5709 part and the I2C interface.

I attached a TAS5504 driver. It plays music. You need to use a custom
app to manipulate the rest of the registers.

TAS chips have a complicated variable length register scheme that is
hard to cache.


> My next question is with regards to clocking on the SSC interface. The
> PCM1808 in my board has a pretty straight forward connection as follows:
>
> SSC-I/F     PCM1808     Comment
>  RF  ---->   LRCK      Frame clock
>  RK  ---->   BCK       Bit clock
>  RD  <----   DOUT      Digital data
>
> The PCM1808 is hardwired to be in slave mode. The two clock lines are
> expected to be provided by the SoC's SSC interface.
>
> I have the following code in my machine driver to request the SSC interface
> and bind the PCM1808 to it:
>
>        /*
>         * Request SSC device
>         */
>        ssc = ssc_request(0);
>        if (IS_ERR(ssc)) {
>                printk(KERN_ERR "ASoC: Failed to request SSC 0\n");
>                ret = PTR_ERR(ssc);
>                ssc = NULL;
>                goto err_ssc;
>        }
>        ssc_p->ssc = ssc;
>
> What I need to do now is to set the SSC's clock divider to feed the PCM1808
> with the proper clocking. So these are my next questions:
>
> 1. How do I specify in the machine driver that my pcm1808 is hardwired to
> slave mode and that I need bit/frame clocks to be provided by the SSC
> interface?
>
> 2. How do I set the SSC's clock divider to provide the right clock signals?
>
> Thanks again,
>
> --
> Pedro
>
>
> Jon Smirl wrote:
>>
>> On Sun, Aug 9, 2009 at 9:35 AM, Jon Smirl<jonsmirl@gmail.com> wrote:
>>>
>>> On Sat, Aug 8, 2009 at 1:39 PM, Pedro Sanchez<psanchez@fosstel.com>
>>> wrote:
>>>>
>>>> Hello,
>>>>
>>>> I have to develop a driver for a custom board based on the at91sam9260
>>>> with
>>>> a PCM1808/TAS5709 chipset combo. I am addressing the PCM1808 first
>>>> because
>>>> it is a simpler device. Since I'm new to ASoC I'm looking for some help
>>>> in
>>>> this list on how to go about writing this driver.
>>>
>>> I attached a PCM1690 driver I'm currently working on.
>>
>> The attached fabric driver show how to use it.
>>
>>>
>>>> So far I have identified two files that I need to work on (other than
>>>> the
>>>> needed kconfig modifications). First is the codec driver at
>>>> sound/soc/codecs/PCM1808.[c|h] which is based on the existent pcm3008
>>>> driver; I believe I'm fine with this.
>>>>
>>>> The second file is the machine driver at sound/soc/atmel/myBoard.c. I'm
>>>> using sam9g20_wm8731.c as reference, on which I believe I found a typo
>>>> which
>>>> I reported in a separated e-mail.
>>>>
>>>> So my first question is, is this two-files step the right approach? am I
>>>> in
>>>> the right track? I'm using the 2.6.30 kernel.
>>>>
>>>> Other questions will come and I hope I will get some cycles from you to
>>>> help
>>>> me direct my work. Of course I'll be glad to submit my driver work for
>>>> inclusion in the kernel if it proves useful.
>>>>
>>>> Regards,
>>>>
>>>> --
>>>> Pedro
>>>>
>>>>
>>>> _______________________________________________
>>>> Alsa-devel mailing list
>>>> Alsa-devel@alsa-project.org
>>>> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>>>>
>>>>
>>>
>>>
>>> --
>>> Jon Smirl
>>> jonsmirl@gmail.com
>>>
>>
>>
>>
>
>



-- 
Jon Smirl
jonsmirl@gmail.com

[-- Attachment #2: tas5504.c --]
[-- Type: text/x-csrc, Size: 26816 bytes --]

/*
 * Texas Instruments TAS5504 low power audio CODEC
 * ALSA SoC CODEC driver
 *
 * Copyright (C) Jon Smirl <jonsmirl@gmail.com>
 */
#define DEBUG

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/device.h>
#include <linux/sysfs.h>
#include <linux/i2c.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/soc-of-simple.h>
#include <sound/initval.h>

#include "tas5504.h"

#define TAS_MAX_8B_REG 0x40
#define TAS_REG_MAX 0xe1

#define TAS5504_RATES (SNDRV_PCM_RATE_32000 |\
				SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
				SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |\
				SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
#define TAS5504_FORMATS SNDRV_PCM_FMTBIT_S32

/* Size and number of variable length entries in the cache */
#define TAS_CACHE_1_MAX 41
#define TAS_CACHE_2_MAX 16
#define TAS_CACHE_3_MAX 2
#define TAS_CACHE_4_MAX 8
#define TAS_CACHE_5_MAX 30
#define TAS_CACHE_6_MAX 4

/* Size and offset of specific  entries in the cache */
static struct {
	u8 size;
	u8 offset;
} cache_map[] = {
[0x00]=	{1, 0xff},	/* TAS_REG_CLOCK_CONTROL */
[0x01]=	{1, 0xff},	/* TAS_REG_GENERAL_STATUS */
[0x02]=	{1, 0xff},	/* TAS_REG_ERROR_STATUS */
[0x03]=	{1, 0},		/* TAS_REG_SYS_CONTROL_1 */
[0x04]=	{1, 1},		/* TAS_REG_SYS_CONTROL_2 */
[0x05]=	{1, 2},		/* TAS_REG_CH_CONFIG_1 */
[0x06]=	{1, 3},		/* TAS_REG_CH_CONFIG_2 */
[0x0b]=	{1, 4},		/* TAS_REG_CH_CONFIG_3 */
[0x0c]=	{1, 5},		/* TAS_REG_CH_CONFIG_4 */
[0x0d]=	{1, 6},		/* TAS_REG_HP_CONFIG */
[0x0e]=	{1, 0xff},	/* TAS_REG_SERIAL_CONTROL */
[0x0f]=	{1, 7},		/* TAS_REG_SOFT_MUTE */
[0x14]=	{1, 8},		/* TAS_REG_AUTO_MUTE */
[0x15]=	{1, 9},		/* TAS_REG_AUTO_MUTE_PWM */
[0x16]=	{1, 10},	/* TAS_REG_MODULATE_LIMIT */
[0x1b]=	{1, 11},	/* TAS_REG_IC_DELAY_1 */
[0x1c]=	{1, 12},	/* TAS_REG_IC_DELAY_2 */
[0x21]=	{1, 13},	/* TAS_REG_IC_DELAY_3 */
[0x22]=	{1, 14},	/* TAS_REG_IC_DELAY_4 */
[0x23]=	{1, 15},	/* TAS_REG_IC_OFFSET */
[0x40]=	{1, 16},	/* TAS_REG_BANK_SWITCH */
[0x41]=	{8, 1,},	/* TAS_REG_IN8X4_1 */
[0x42]=	{8, 2,},	/* TAS_REG_IN8X4_2 */
[0x47]=	{8, 3,},	/* TAS_REG_IN8X4_3 */
[0x48]=	{8, TAS_CACHE_2_MAX,},	/* TAS_REG_IN8X4_4 */
[0x49]=	{1, 17},	/* TAS_REG_IPMIX_1_TO_CH4 */
[0x4a]=	{1, 18},	/* TAS_REG_IPMIX_2_TO_CH4 */
[0x4b]=	{1, 19},	/* TAS_REG_IPMIX_3_TO_CH2 */
[0x4c]=	{1, 20},	/* TAS_REG_CH3_BP_BQ2 */
[0x4d]=	{1, 21},	/* TAS_REG_CH3_BP */
[0x4e]=	{1, 22},	/* TAS_REG_IPMIX_4_TO_CH12 */
[0x4f]=	{1, 23},	/* TAS_REG_CH4_BP_BQ2 */
[0x50]=	{1, 24},	/* TAS_REG_CH4_BP */
[0x51]=	{5, 1,},	/* TAS_REG_CH1_BQ_1 */
[0x52]=	{5, 2,},	/* TAS_REG_CH1_BQ_2 */
[0x53]=	{5, 3,},	/* TAS_REG_CH1_BQ_3 */
[0x54]=	{5, 4,},	/* TAS_REG_CH1_BQ_4 */
[0x55]=	{5, 5,},	/* TAS_REG_CH1_BQ_5 */
[0x56]=	{5, 6,},	/* TAS_REG_CH1_BQ_6 */
[0x57]=	{5, 7,},	/* TAS_REG_CH1_BQ_7 */
[0x58]=	{5, 8,},	/* TAS_REG_CH2_BQ_1 */
[0x59]=	{5, 9,},	/* TAS_REG_CH2_BQ_2	 */
[0x5a]=	{5, 10,},	/* TAS_REG_CH2_BQ_3 */
[0x5b]=	{5, 11,},	/* TAS_REG_CH2_BQ_4 */
[0x5c]=	{5, 12,},	/* TAS_REG_CH2_BQ_5 */
[0x5d]=	{5, 13,},	/* TAS_REG_CH2_BQ_6 */
[0x5e]=	{5, 14,},	/* TAS_REG_CH2_BQ_7 */
[0x7b]=	{5, 15,},	/* TAS_REG_CH3_BQ_1 */
[0x7c]=	{5, 16,},	/* TAS_REG_CH3_BQ_2 */
[0x7d]=	{5, 17,},	/* TAS_REG_CH3_BQ_3 */
[0x7e]=	{5, 18,},	/* TAS_REG_CH3_BQ_4 */
[0x7f]=	{5, 19,},	/* TAS_REG_CH3_BQ_5 */
[0x80]=	{5, 20,},	/* TAS_REG_CH3_BQ_6 */
[0x81]=	{5, 21,},	/* TAS_REG_CH3_BQ_7 */
[0x82]=	{5, 22,},	/* TAS_REG_CH4_BQ_1 */
[0x83]=	{5, 23,},	/* TAS_REG_CH4_BQ_2 */
[0x84]=	{5, 24,},	/* TAS_REG_CH4_BQ_3 */
[0x85]=	{5, 25,},	/* TAS_REG_CH4_BQ_4 */
[0x86]=	{5, 26,},	/* TAS_REG_CH4_BQ_5 */
[0x87]=	{5, 27,},	/* TAS_REG_CH4_BQ_6 */
[0x88]=	{5, 28,},	/* TAS_REG_CH4_BQ_7 */
[0x89]=	{2, 1,},	/* TAS_REG_BT_BYPASS_CH1 */
[0x8a]=	{2, 2,},	/* TAS_REG_BT_BYPASS_CH2 */
[0x8f]=	{2, 3,},	/* TAS_REG_BT_BYPASS_CH3 */
[0x90]=	{2, 4,},	/* TAS_REG_BT_BYPASS_CH4 */
[0x91]=	{1, 25,},	/* TAS_REG_LOUDNESS_LG */
[0x92]=	{2, 5,},	/* TAS_REG_LOUDNESS_LO */
[0x93]=	{1, 26,},	/* TAS_REG_LOUDNESS_G */
[0x94]=	{2, 6,},	/* TAS_REG_LOUDNESS_O */
[0x95]=	{5, 29,},	/* TAS_REG_LOUDNESS_BQ */
[0x96]=	{1, 27,},	/* TAS_REG_DRC1_CNTL_123 */
[0x97]=	{1, 28,},	/* TAS_REG_DRC2_CNTL_4 */
[0x98]=	{2, 7,},	/* TAS_REG_DRC1_ENERGY */
[0x99]=	{4, 3,},	/* TAS_REG_DRC1_THRESHOLD */
[0x9a]=	{3, 1,},	/* TAS_REG_DRC1_SLOPE */
[0x9b]=	{4, 4,},	/* TAS_REG_DRC1_OFFSET */
[0x9c]=	{4, 5,},	/* TAS_REG_DRC1_ATTACK */
[0x9d]=	{2, 8,},	/* TAS_REG_DRC2_ENERGY */
[0x9e]=	{4, 6,},	/* TAS_REG_DRC2_THRESHOLD */
[0x9f]=	{2, TAS_CACHE_3_MAX,},	/* TAS_REG_DRC2_SLOPE */
[0xa0]=	{4, 7,},	/* TAS_REG_DRC2_OFFSET */
[0xa1]=	{4, TAS_CACHE_2_MAX,},	/* TAS_REG_DRC2_ATTACK */
[0xa2]=	{2, 9,},	/* TAS_REG_DRC_BYPASS_1 */
[0xa3]=	{2, 10,},	/* TAS_REG_DRC_BYPASS_2 */
[0xa8]=	{2, 11,},	/* TAS_REG_DRC_BYPASS_3 */
[0xa9]=	{2, 12,},	/* TAS_REG_DRC_BYPASS_4 */
[0xaa]=	{2, 13,},	/* TAS_REG_SEL_OP14_S */
[0xab]=	{2, 14,},	/* TAS_REG_SEL_OP14_T */
[0xb0]=	{2, 15,},	/* TAS_REG_SEL_OP14_Y */
[0xb1]=	{2, TAS_CACHE_2_MAX,},	/* TAS_REG_SEL_OP14_Z */
[0xcf]=	{5, TAS_CACHE_5_MAX,},	/* TAS_REG_VOLUME_BQ */
[0xd0]=	{1, 29},	/* TAS_REG_VOL_TB_SLEW */
[0xd1]=	{1, 30},	/* TAS_REG_VOL_CH1 */
[0xd2]=	{1, 31},	/* TAS_REG_VOL_CH2 */
[0xd7]=	{1, 32},	/* TAS_REG_VOL_CH3 */
[0xd8]=	{1, 33},	/* TAS_REG_VOL_CH4 */
[0xd9]=	{1, 34},	/* TAS_REG_VOL_MASTER */
[0xda]=	{1, 35},	/* TAS_REG_BASS_SET */
[0xdb]=	{1, 36},	/* TAS_REG_BASS_INDEX */
[0xdc]=	{1, 37},	/* TAS_REG_TREBLE_SET */
[0xdd]=	{1, 38},	/* TAS_REG_TREBLE_INDEX */
[0xde]=	{1, 39},	/* TAS_REG_AM_MODE */
[0xdf]=	{1, 40},	/* TAS_REG_PSVC */
[0xe0]=	{1, TAS_CACHE_1_MAX,},	/* TAS_REG_GENERAL_CONTROL */
};

/* location of each variable length segment in the cache array */
#define TAS_CACHE_1_BASE 0
#define TAS_CACHE_2_BASE TAS_CACHE_1_BASE + (TAS_CACHE_1_MAX + 1)
#define TAS_CACHE_3_BASE TAS_CACHE_2_BASE + (TAS_CACHE_2_MAX + 1) * 2
#define TAS_CACHE_4_BASE TAS_CACHE_3_BASE + (TAS_CACHE_3_MAX + 1) * 3
#define TAS_CACHE_5_BASE TAS_CACHE_4_BASE + (TAS_CACHE_4_MAX + 1) * 4
#define TAS_CACHE_8_BASE TAS_CACHE_5_BASE + (TAS_CACHE_5_MAX + 1) * 5
#define TAS_CACHE_MAX TAS_CACHE_8_BASE + (TAS_CACHE_5_MAX + 1) * 8

/* tas5504 driver private data */
struct tas5504_priv {
	struct i2c_client *client;
	struct snd_soc_codec codec;

	/* performance shadow of i2c registers */
	u32 reg_cache[TAS_CACHE_MAX];
	u8 valid[TAS_CACHE_MAX];
};

/* ---------------------------------------------------------------------
 * Register access routines
 */

static u32 *tas5504_get_base(struct tas5504_priv *priv, unsigned int reg)
{
	u32 *base;

	switch (cache_map[reg].size) {
	case 1: base = &priv->reg_cache[TAS_CACHE_1_BASE]; break;
	case 2: base = &priv->reg_cache[TAS_CACHE_2_BASE]; break;
	case 3: base = &priv->reg_cache[TAS_CACHE_3_BASE]; break;
	case 4: base = &priv->reg_cache[TAS_CACHE_4_BASE]; break;
	case 5: base = &priv->reg_cache[TAS_CACHE_5_BASE]; break;
	case 8: base = &priv->reg_cache[TAS_CACHE_8_BASE]; break;
	default: return 0;
	}
	base += cache_map[reg].offset * cache_map[reg].size;

	return base;
}

static void tas5504_update_cache(struct tas5504_priv *priv, u8 reg, u32 *value, unsigned int count)
{
	if (cache_map[reg].offset != 0xFF) /* not volatile, cache it */
		priv->valid[reg] = 1;

	memmove(tas5504_get_base(priv, reg), value, cache_map[reg].size * sizeof *value * count);
}

static void tas5504_reg_read(struct snd_soc_codec *codec, u8 reg)
{
	struct tas5504_priv *priv = codec->private_data;
	u8 value[32];
	int rc;
	struct i2c_msg msgs[2] = {
		{.addr = priv->client->addr, .flags = 0, .len = 1, .buf = &reg},
		{.addr = priv->client->addr, .flags = I2C_M_RD, .buf = &value[0]}
	};

	if (reg < TAS_MAX_8B_REG) /* special case 8 bit regs */
		msgs[1].len = 1;
	else
		msgs[1].len = cache_map[reg].size * 4;

	rc = i2c_transfer(priv->client->adapter, msgs, ARRAY_SIZE(msgs));
	if (rc != 2) {
		dev_err(&priv->client->dev, "%s: rc=%d reg=%02x rc != size\n",
			__func__, rc, reg);
		return;
	}
	if (reg < TAS_MAX_8B_REG) /* convert 8b result to 32b */
		*(u32 *)&value[0] = value[0];

	tas5504_update_cache(priv, reg, (u32*)&value[0], cache_map[reg].size);
}

static unsigned int tas5504_reg_read_cache(struct snd_soc_codec *codec,
					 unsigned int reg)
{
	struct tas5504_priv *priv = codec->private_data;

	if (reg > TAS_REG_MAX)
		return -1;

	/* can only read 32 bit registers, 0xFF is volatile 32b */
	if (!((cache_map[reg].size == 1) || (cache_map[reg].size == 0xFF)))
		return -1;

	if (!priv->valid[reg])
		tas5504_reg_read(codec, reg);

	return *tas5504_get_base(priv, reg);
}

static int tas5504_reg_write_long(struct snd_soc_codec *codec, unsigned int reg,
		           u32 *value, unsigned int count)
{
	struct tas5504_priv *priv = codec->private_data;
	u8 buf[33];
	int rc, size;

	memmove(tas5504_get_base(priv, reg), value, count * sizeof *value);

	buf[0] = reg;
	size = 1;
	if (reg < TAS_MAX_8B_REG) {
		buf[1] = *value;
		size += 1;
	} else {
		size += count * sizeof *value;
		memmove(&buf[1], value, size);
	}
	rc = i2c_master_send(priv->client, buf, size);
	if (rc != size) {
		dev_err(&priv->client->dev,
			"%s: rc=%d reg=%02x size %02x rc != size\n",
			__func__, rc, reg, size);
		return -EIO;
	}
	return 0;
}

static int tas5504_reg_write(struct snd_soc_codec *codec, unsigned int reg,
		           unsigned int value)
{
	if (reg > TAS_REG_MAX)
		return -EINVAL;

	/* can only read 32 bit registers, 0xFF is a volatile 32b */
	if (!((cache_map[reg].size == 1) || (cache_map[reg].size == 0xFF)))
		return -EINVAL;

	return tas5504_reg_write_long(codec, reg, &value, 1);
}

/* ---------------------------------------------------------------------
 * Digital Audio Interface Operations
 */
static int tas5504_hw_params(struct snd_pcm_substream *substream,
			   struct snd_pcm_hw_params *params,
			   struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_device *socdev = rtd->socdev;
	struct snd_soc_codec *codec = socdev->card->codec;
	struct tas5504_priv *priv = codec->private_data;

	dev_dbg(&priv->client->dev, "tas5504_hw_params(substream=%p, params=%p)\n",
		substream, params);
	dev_dbg(&priv->client->dev, "rate=%i format=%i\n", params_rate(params),
		params_format(params));

	return 0;
}

/**
 * tas5504_mute - Mute control to reduce noise when changing audio format
 */
static int tas5504_mute(struct snd_soc_dai *dai, int mute)
{
	struct snd_soc_codec *codec = dai->codec;
	struct tas5504_priv *priv = codec->private_data;

	dev_dbg(&priv->client->dev, "tas5504_mute(dai=%p, mute=%i)\n",
		dai, mute);

	return 0;
}

/* ---------------------------------------------------------------------
 * Digital Audio Interface Definition
 */

static struct snd_soc_dai_ops tas5504_dai_ops = {
	.hw_params = tas5504_hw_params,
	.digital_mute = tas5504_mute,
};

struct snd_soc_dai tas5504_dai = {
	.name = "tas5504",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 2,
		.channels_max = 2,
		.rates = TAS5504_RATES,
		.formats = TAS5504_FORMATS,
	},
	.ops = &tas5504_dai_ops,
};
EXPORT_SYMBOL_GPL(tas5504_dai);

/* ---------------------------------------------------------------------
 * ALSA controls
 */

static int tas5504_info_ctl_1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = -1;
	uinfo->value.integer.step = 1;
	return 0;
}

static int tas5504_info_ctl_1_64(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER64;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = -1;
	uinfo->value.integer.step = 1;
	return 0;
}

static int tas5504_info_ctl_2(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 2;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = -1;
	uinfo->value.integer.step = 1;
	return 0;
}

static int tas5504_info_ctl_2_64(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER64;
	uinfo->count = 2;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = -1;
	uinfo->value.integer.step = 1;
	return 0;
}

static int tas5504_info_ctl_3(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 3;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = -1;
	uinfo->value.integer.step = 1;
	return 0;
}

static int tas5504_info_ctl_4(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 4;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = -1;
	uinfo->value.integer.step = 1;
	return 0;
}

static int tas5504_info_ctl_5(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 5;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = -1;
	uinfo->value.integer.step = 1;
	return 0;
}

static int tas5504_info_ctl_8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 8;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = -1;
	uinfo->value.integer.step = 1;
	return 0;
}

static int tas5504_get_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct tas5504_priv *priv = snd_kcontrol_chip(kcontrol);
	struct snd_ctl_elem_info uinfo;
	int reg, count;

	reg = kcontrol->private_value;
	kcontrol->info(kcontrol, &uinfo);
	count = (uinfo.type == SNDRV_CTL_ELEM_TYPE_INTEGER64 ? 2 : 1);
	count *= uinfo.count;

	if (!priv->valid[reg])
		tas5504_reg_read(&priv->codec, reg);

	memmove(tas5504_get_base(priv, reg), &ucontrol->value.integer.value[0], count);
	return 0;
}

static int tas5504_put_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct tas5504_priv *priv = snd_kcontrol_chip(kcontrol);
	struct snd_ctl_elem_info uinfo;
	int reg, count;

	reg = kcontrol->private_value;
	kcontrol->info(kcontrol, &uinfo);
	count = (uinfo.type == SNDRV_CTL_ELEM_TYPE_INTEGER64 ? 2 : 1);
	count *= uinfo.count;

	return tas5504_reg_write_long(&priv->codec, reg, (u32 *)&ucontrol->value.integer.value[0], count);
}

#define TAS_CTL_1(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
  .info = tas5504_info_ctl_1, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

#define TAS_CTL_1R(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READ, \
  .info = tas5504_info_ctl_1, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

#define TAS_CTL_1_64(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
  .info = tas5504_info_ctl_1_64, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

#define TAS_CTL_2(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
  .info = tas5504_info_ctl_2, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

#define TAS_CTL_2_64(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
  .info = tas5504_info_ctl_2_64, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

#define TAS_CTL_3(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
  .info = tas5504_info_ctl_3, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

#define TAS_CTL_4(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
  .info = tas5504_info_ctl_4, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

#define TAS_CTL_5(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
  .info = tas5504_info_ctl_5, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

#define TAS_CTL_8(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
  .name = xname, \
  .index = xindex, \
  .device = 0, \
  .subdevice = xsubdevice, \
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
  .info = tas5504_info_ctl_8, \
  .get = tas5504_get_reg, \
  .put = tas5504_put_reg, \
  .private_value = xregister \
}

static const struct snd_kcontrol_new tas5504_snd_controls[] = {
	TAS_CTL_1R(0x00, 0, 0, "Clock Control"),
	TAS_CTL_1R(0x01, 0, 0, "General Status"),
	TAS_CTL_1(0x02, 0, 0, "Error Status"),
	TAS_CTL_1(0x03, 0, 0, "System Control 1"),
	TAS_CTL_1(0x04, 0, 0, "System Control 2"),
	TAS_CTL_1(0x05, 1, 0, "General Config"),
	TAS_CTL_1(0x06, 2, 0, "General Config"),
	TAS_CTL_1(0x0b, 3, 0, "General Config"),
	TAS_CTL_1(0x0c, 4, 0, "General Config"),
	TAS_CTL_1(0x0d, 0, 0, "Headphone Config"),
	TAS_CTL_1R(0x0e, 0, 0, "Serial Data Interface"),
	TAS_CTL_1(0x0f, 0, 0, "Soft Mute"),
	TAS_CTL_1(0x14, 0, 0, "Auto Mute"),
	TAS_CTL_1(0x15, 0, 0, "Auto Mute PWM"),
	TAS_CTL_1R(0x16, 0, 0, "Modulation"),
	TAS_CTL_1(0x1b, 1, 0, "Interchannel Delay"),
	TAS_CTL_1(0x1c, 2, 0, "Interchannel Delay"),
	TAS_CTL_1(0x21, 3, 0, "Interchannel Delay"),
	TAS_CTL_1(0x22, 4, 0, "Interchannel Delay"),
	TAS_CTL_1(0x23, 0, 0, "Interchannel Offset"),
	TAS_CTL_1(0x40, 4, 0, "Bank Switching"),
	TAS_CTL_8(0x41, 1, 0, "Input Mixer"),
	TAS_CTL_8(0x42, 2, 0, "Input Mixer"),
	TAS_CTL_8(0x47, 3, 0, "Input Mixer"),
	TAS_CTL_8(0x48, 4, 0, "Input Mixer"),
	TAS_CTL_1(0x49, 0, 0, "Bass ipmix_1_to_ch4"),
	TAS_CTL_1(0x4a, 0, 0, "Bass ipmix_2_to_ch4"),
	TAS_CTL_1(0x4b, 0, 0, "Bass ipmix_3_to_ch12"),
	TAS_CTL_1(0x4c, 0, 0, "Bass ch3_bp_bq2"),
	TAS_CTL_1(0x4d, 0, 0, "Bass ch3_bq2"),
	TAS_CTL_1(0x4e, 0, 0, "Bass ipmix_4_to_ch12"),
	TAS_CTL_1(0x4f, 0, 0, "Bass ch4_bp_bq2"),
	TAS_CTL_1(0x50, 0, 0, "Bass ch4_bq2"),
	TAS_CTL_5(0x51, 1, 1, "Biquad"),
	TAS_CTL_5(0x52, 1, 2, "Biquad"),
	TAS_CTL_5(0x53, 1, 3, "Biquad"),
	TAS_CTL_5(0x54, 1, 4, "Biquad"),
	TAS_CTL_5(0x55, 1, 5, "Biquad"),
	TAS_CTL_5(0x56, 1, 6, "Biquad"),
	TAS_CTL_5(0x57, 1, 7, "Biquad"),
	TAS_CTL_5(0x58, 2, 1, "Biquad"),
	TAS_CTL_5(0x59, 2, 2, "Biquad"),
	TAS_CTL_5(0x5a, 2, 3, "Biquad"),
	TAS_CTL_5(0x5b, 2, 4, "Biquad"),
	TAS_CTL_5(0x5c, 2, 5, "Biquad"),
	TAS_CTL_5(0x5d, 2, 6, "Biquad"),
	TAS_CTL_5(0x5e, 2, 7, "Biquad"),
	TAS_CTL_5(0x7b, 3, 1, "Biquad"),
	TAS_CTL_5(0x7c, 3, 2, "Biquad"),
	TAS_CTL_5(0x7d, 3, 3, "Biquad"),
	TAS_CTL_5(0x7e, 3, 4, "Biquad"),
	TAS_CTL_5(0x7f, 3, 5, "Biquad"),
	TAS_CTL_5(0x80, 3, 6, "Biquad"),
	TAS_CTL_5(0x81, 3, 7, "Biquad"),
	TAS_CTL_5(0x82, 4, 1, "Biquad"),
	TAS_CTL_5(0x83, 4, 2, "Biquad"),
	TAS_CTL_5(0x84, 4, 3, "Biquad"),
	TAS_CTL_5(0x85, 4, 4, "Biquad"),
	TAS_CTL_5(0x86, 4, 5, "Biquad"),
	TAS_CTL_5(0x87, 4, 6, "Biquad"),
	TAS_CTL_5(0x88, 4, 7, "Biquad"),
	TAS_CTL_2(0x89, 1, 0, "Bass/Treble Bypass"),
	TAS_CTL_2(0x8a, 2, 0, "Bass/Treble Bypass"),
	TAS_CTL_2(0x8f, 3, 0, "Bass/Treble Bypass"),
	TAS_CTL_2(0x90, 4, 0, "Bass/Treble Bypass"),
	TAS_CTL_1(0x91, 0, 0, "Loudness Log2 Gain"),
	TAS_CTL_1_64(0x92, 0, 0, "Loudness Log2 Offset"),
	TAS_CTL_1(0x93, 0, 0, "Loudness Gain"),
	TAS_CTL_1_64(0x94, 0, 0, "Loudness Offset"),
	TAS_CTL_5(0x95, 0, 0, "Loudness Biquad"),
	TAS_CTL_1(0x96, 0, 0, "DRC Control 1-3"),
	TAS_CTL_1(0x97, 0, 0, "DRC Control 4"),
	TAS_CTL_2(0x97, 0, 0, "DRC Energy"),
	TAS_CTL_2_64(0x97, 0, 0, "DRC Threshold"),
	TAS_CTL_3(0x97, 0, 0, "DRC Slope"),
	TAS_CTL_2_64(0x97, 0, 0, "DRC Offset"),
	TAS_CTL_4(0x97, 0, 0, "DRC Attack/Delay"),
	TAS_CTL_2(0x9d, 4, 0, "DRC Energy"),
	TAS_CTL_2_64(0x9e, 4, 0, "DRC Threshold"),
	TAS_CTL_3(0x9f, 4, 0, "DRC Slope"),
	TAS_CTL_2_64(0xa0, 4, 0, "DRC Offset"),
	TAS_CTL_4(0xa1, 4, 0, "DRC Attack/Delay"),
	TAS_CTL_2(0xa2, 1, 0, "DRC Bypass"),
	TAS_CTL_2(0xa3, 2, 0, "DRC Bypass"),
	TAS_CTL_2(0xa8, 3, 0, "DRC Bypass"),
	TAS_CTL_2(0xa9, 4, 0, "DRC Bypass"),
	TAS_CTL_2(0xaa, 1, 0, "Output Mixer"),
	TAS_CTL_2(0xab, 2, 0, "Output Mixer"),
	TAS_CTL_3(0xb0, 3, 0, "Output Mixer"),
	TAS_CTL_3(0xb1, 4, 0, "Output Mixer"),
	TAS_CTL_5(0xcf, 0, 0, "Volume Biquad"),
	TAS_CTL_1(0xd0, 0, 0, "Volume Slew"),
	TAS_CTL_1(0xd1, 1, 0, "Volume"),
	TAS_CTL_1(0xd2, 2, 0, "Volume"),
	TAS_CTL_1(0xd7, 3, 0, "Volume"),
	TAS_CTL_1(0xd8, 4, 0, "Volume"),
	SOC_SINGLE("PCM Playback Volume", 0xd9, 0, 0x3ff, 1),
	TAS_CTL_1(0xda, 0, 0, "Bass Filter Set"),
	TAS_CTL_1(0xdb, 0, 0, "Bass Filter Index"),
	TAS_CTL_1(0xdc, 0, 0, "Treble Filter Set"),
	TAS_CTL_1(0xdd, 0, 0, "Treble Filter Index"),
	TAS_CTL_1(0xde, 0, 0, "AM Mode"),
	TAS_CTL_1(0xdf, 0, 0, "PSCV Range"),
	TAS_CTL_1(0xe0, 0, 0, "General Control"),
};

/* ---------------------------------------------------------------------
 * SoC CODEC portion of driver: probe and release routines
 */
static int tas5504_probe(struct platform_device *pdev)
{
	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
	struct snd_soc_codec *codec;
	struct snd_kcontrol *kcontrol;
	struct tas5504_priv *priv;
	int i, ret, err;

	dev_info(&pdev->dev, "Probing tas5504 SoC CODEC driver\n");
	dev_dbg(&pdev->dev, "socdev=%p\n", socdev);
	dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data);

	/* Fetch the relevant tas5504 private data here (it's already been
	 * stored in the .codec pointer) */
	priv = socdev->codec_data;
	if (priv == NULL) {
		dev_err(&pdev->dev, "tas5504: missing codec pointer\n");
		return -ENODEV;
	}
	codec = &priv->codec;
	socdev->card->codec = codec;

	dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n",
		&pdev->dev, socdev->dev);
	/* register pcms */
	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
	if (ret < 0) {
		dev_err(&pdev->dev, "tas5504: failed to create pcms\n");
		return -ENODEV;
	}

	/* register controls */
	dev_dbg(&pdev->dev, "Registering controls\n");
	for (i = 0; i < ARRAY_SIZE(tas5504_snd_controls); i++) {
		kcontrol = snd_ctl_new1(&tas5504_snd_controls[i], codec);
		err = snd_ctl_add(codec->card, kcontrol);
		if (err < 0)
			dev_err(&pdev->dev, "tas5504: failed to create control %x\n", err);
	}

	/* CODEC is setup, we can register the card now */
	dev_dbg(&pdev->dev, "Registering card\n");
	ret = snd_soc_init_card(socdev);
	if (ret < 0) {
		dev_err(&pdev->dev, "tas5504: failed to register card\n");
		goto card_err;
	}
	return 0;

 card_err:
	snd_soc_free_pcms(socdev);
	return ret;
}

static int tas5504_remove(struct platform_device *pdev)
{
	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
	snd_soc_free_pcms(socdev);
	return 0;
}

struct snd_soc_codec_device tas5504_soc_codec_dev = {
	.probe = tas5504_probe,
	.remove = tas5504_remove,
};
EXPORT_SYMBOL_GPL(tas5504_soc_codec_dev);

int tas5504_display_register(struct snd_soc_codec *codec, char *buffer, unsigned int limit, unsigned int reg)
{
	struct tas5504_priv *priv = codec->private_data;
	int i, count;
	u32* base;

	reg &= 0xFF;

	if (reg >= TAS_REG_MAX)
		return 0;

	if (cache_map[reg].size == 0) /* sparse reg does not exist */
		return 0;

	if (!priv->valid[reg])	/* ensure it is valid */
		tas5504_reg_read(codec, reg);

	base = tas5504_get_base(priv, reg);

	count = snprintf(buffer, limit, "%2x: ", reg);
	if (reg < TAS_MAX_8B_REG)
		count += snprintf(buffer + count, limit - count, " %.2x", *base);
	else
		for (i = 0; i < cache_map[reg].size; i++, base++)
			count += snprintf(buffer + count, limit - count, " %.8x", *base);
	count += snprintf(buffer + count, limit - count, "\n");

	return count;
}

/* ---------------------------------------------------------------------
 * i2c device portion of driver: probe and release routines and i2c
 * 				 driver registration.
 */
static int tas5504_i2c_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{
	struct tas5504_priv *priv;

	dev_dbg(&client->dev, "probing tas5504 i2c device\n");

	/* Allocate driver data */
	priv = kzalloc(sizeof *priv, GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	/* Initialize the driver data */
	priv->client = client;
	i2c_set_clientdata(client, priv);

	/* Setup what we can in the codec structure so that the register
	 * access functions will work as expected.  More will be filled
	 * out when it is probed by the SoC CODEC part of this driver */
	priv->codec.private_data = priv;
	priv->codec.name = "tas5504";
	priv->codec.owner = THIS_MODULE;
	priv->codec.dai = &tas5504_dai;
	priv->codec.num_dai = 1;
	priv->codec.read = tas5504_reg_read_cache;
	priv->codec.write = tas5504_reg_write;
	priv->codec.reg_cache_size = TAS_REG_MAX;
	priv->codec.display_register = tas5504_display_register;

	mutex_init(&priv->codec.mutex);
	INIT_LIST_HEAD(&priv->codec.dapm_widgets);
	INIT_LIST_HEAD(&priv->codec.dapm_paths);

	dev_dbg(&client->dev, "I2C device initialized\n");
	return 0;
}

static int tas5504_i2c_remove(struct i2c_client *client)
{
	struct tas5504_priv *priv = dev_get_drvdata(&client->dev);

	kfree(priv);

	return 0;
}

static const struct i2c_device_id tas5504_device_id[] = {
	{ "tas5504", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, tas5504_device_id);

static struct i2c_driver tas5504_driver = {
	.driver		= {
		.name	= "tas5504",
		.owner		= THIS_MODULE,
	},
	.probe		= tas5504_i2c_probe,
	.remove		= __devexit_p(tas5504_i2c_remove),
	.id_table	= tas5504_device_id,
};

static __init int tas5504_driver_init(void)
{
	snd_soc_register_dai(&tas5504_dai);
	return i2c_add_driver(&tas5504_driver);
}

static __exit void tas5504_driver_exit(void)
{
	i2c_del_driver(&tas5504_driver);
}

module_init(tas5504_driver_init);
module_exit(tas5504_driver_exit);

MODULE_AUTHOR("Jon Smirl");
MODULE_DESCRIPTION("TAS5504 codec module");
MODULE_LICENSE("GPL");

[-- Attachment #3: tas5504.h --]
[-- Type: text/x-chdr, Size: 6376 bytes --]

/*
 * Texas Instruments TAS5504 low power audio CODEC
 * ALSA SoC CODEC driver
 *
 * Copyright (C) Jon Smirl <jonsmirl@gmail.com>
 */

#define TAS_REG_CLOCK_CONTROL    0x0000
#define TAS_REG_GENERAL_STATUS   0x0001
#define TAS_REG_ERROR_STATUS     0x0002
#define TAS_REG_SYS_CONTROL_1    0x0003
#define TAS_REG_SYS_CONTROL_2    0x0004
#define TAS_REG_CH_CONFIG_1      0x0005
#define TAS_REG_CH_CONFIG_2      0x0006
#define TAS_REG_CH_CONFIG_3      0x000b
#define TAS_REG_CH_CONFIG_4      0x000c
#define TAS_REG_HP_CONFIG        0x000d
#define TAS_REG_SERIAL_CONTROL   0x000e
#define TAS_REG_SOFT_MUTE        0x000f
#define TAS_REG_AUTO_MUTE        0x0014
#define TAS_REG_AUTO_MUTE_PWM    0x0015
#define TAS_REG_MODULATE_LIMIT   0x0016
#define TAS_REG_IC_DELAY_1       0x001b
#define TAS_REG_IC_DELAY_2       0x001c
#define TAS_REG_IC_DELAY_3       0x0021
#define TAS_REG_IC_DELAY_4       0x0022
#define TAS_REG_IC_OFFSET        0x0023

#define TAS_REG_BANK_SWITCH      0x0040
#define TAS_REG_IN8X4_1          0x0041
#define TAS_REG_IN8X4_2          0x0042
#define TAS_REG_IN8X4_3          0x0047
#define TAS_REG_IN8X4_4          0x0048
#define TAS_REG_IPMIX_1_TO_CH4   0x0049
#define TAS_REG_IPMIX_2_TO_CH4   0x004a
#define TAS_REG_IPMIX_3_TO_CH2   0x004b
#define TAS_REG_CH3_BP_BQ2       0x004c
#define TAS_REG_CH3_BP           0x004d
#define TAS_REG_IPMIX_4_TO_CH12  0x004e
#define TAS_REG_CH4_BP_BQ2       0x004f
#define TAS_REG_CH4_BP           0x0050

#define TAS_REG_CH1_BQ_1         0x0051
#define TAS_REG_CH1_BQ_2         0x0052
#define TAS_REG_CH1_BQ_3         0x0053
#define TAS_REG_CH1_BQ_4         0x0054
#define TAS_REG_CH1_BQ_5         0x0055
#define TAS_REG_CH1_BQ_6         0x0056
#define TAS_REG_CH1_BQ_7         0x0057
#define TAS_REG_CH2_BQ_1         0x0058
#define TAS_REG_CH2_BQ_2         0x0059
#define TAS_REG_CH2_BQ_3         0x005a
#define TAS_REG_CH2_BQ_4         0x005b
#define TAS_REG_CH2_BQ_5         0x005c
#define TAS_REG_CH2_BQ_6         0x005d
#define TAS_REG_CH2_BQ_7         0x005e
#define TAS_REG_CH3_BQ_1         0x007b
#define TAS_REG_CH3_BQ_2         0x007c
#define TAS_REG_CH3_BQ_3         0x007d
#define TAS_REG_CH3_BQ_4         0x007e
#define TAS_REG_CH3_BQ_5         0x007f
#define TAS_REG_CH3_BQ_6         0x0080
#define TAS_REG_CH3_BQ_7         0x0081
#define TAS_REG_CH4_BQ_1         0x0082
#define TAS_REG_CH4_BQ_2         0x0083
#define TAS_REG_CH4_BQ_3         0x0084
#define TAS_REG_CH4_BQ_4         0x0085
#define TAS_REG_CH4_BQ_5         0x0086
#define TAS_REG_CH4_BQ_6         0x0087
#define TAS_REG_CH4_BQ_7         0x0088

#define TAS_REG_BT_BYPASS_CH1    0x0089
#define TAS_REG_BT_INLINE_CH1    0x0189
#define TAS_REG_BT_BYPASS_CH2    0x008a
#define TAS_REG_BT_INLINE_CH2    0x018a
#define TAS_REG_BT_BYPASS_CH3    0x008f
#define TAS_REG_BT_INLINE_CH3    0x018f
#define TAS_REG_BT_BYPASS_CH4    0x0090
#define TAS_REG_BT_INLINE_CH4    0x0190
#define TAS_REG_LOUDNESS_LG      0x0091
#define TAS_REG_LOUDNESS_LO_U    0x0092
#define TAS_REG_LOUDNESS_LO_L    0x0192
#define TAS_REG_LOUDNESS_G       0x0093
#define TAS_REG_LOUDNESS_O_U     0x0094
#define TAS_REG_LOUDNESS_O_L     0x0194
#define TAS_REG_LOUDNESS_BQ_B0   0x0095
#define TAS_REG_LOUDNESS_BQ_B1   0x0195
#define TAS_REG_LOUDNESS_BQ_B2   0x0295
#define TAS_REG_LOUDNESS_BQ_A0   0x0395
#define TAS_REG_LOUDNESS_BQ_A1   0x0495
#define TAS_REG_DRC1_CNTL_123    0x0096
#define TAS_REG_DRC2_CNTL_4      0x0097
#define TAS_REG_DRC1_ENERGY      0x0098
#define TAS_REG_DRC1_ENERGY_1E   0x0198
#define TAS_REG_DRC1_THRESH_T1_U 0x0099
#define TAS_REG_DRC1_THRESH_T1_L 0x0199
#define TAS_REG_DRC1_THRESH_T2_U 0x0299
#define TAS_REG_DRC1_THRESH_T2_L 0x0399
#define TAS_REG_DRC1_SLOPE_K0    0x009a
#define TAS_REG_DRC1_SLOPE_K1    0x019a
#define TAS_REG_DRC1_SLOPE_K2    0x029a
#define TAS_REG_DRC1_OFFSET_O1_U 0x009b
#define TAS_REG_DRC1_OFFSET_O1_L 0x019b
#define TAS_REG_DRC1_OFFSET_O2_U 0x029b
#define TAS_REG_DRC1_OFFSET_O2_L 0x039b
#define TAS_REG_DRC1_ATTACK      0x009c
#define TAS_REG_DRC1_ATTACK_1A   0x019c
#define TAS_REG_DRC1_DECAY       0x029c
#define TAS_REG_DRC1_DECAY_1D    0x039c
#define TAS_REG_DRC2_ENERGY      0x009d
#define TAS_REG_DRC2_ENERGY_1E   0x019d
#define TAS_REG_DRC2_THRESH_T1_U 0x019e
#define TAS_REG_DRC2_THRESH_T1_L 0x029e
#define TAS_REG_DRC2_THRESH_T2_U 0x039e
#define TAS_REG_DRC2_THRESH_T2_L 0x049e
#define TAS_REG_DRC2_SLOPE_K0    0x009f
#define TAS_REG_DRC2_SLOPE_K1    0x019f
#define TAS_REG_DRC2_SLOPE_K2    0x029f
#define TAS_REG_DRC2_OFFSET_O1_U 0x00a0
#define TAS_REG_DRC2_OFFSET_O1_L 0x01a0
#define TAS_REG_DRC2_OFFSET_O2_U 0x02a0
#define TAS_REG_DRC2_OFFSET_O2_L 0x03a0
#define TAS_REG_DRC2_ATTACK      0x00a1
#define TAS_REG_DRC2_ATTACK_1A   0x01a1
#define TAS_REG_DRC2_DECAY       0x02a1
#define TAS_REG_DRC2_DECAY_1D    0x03a1
#define TAS_REG_DRC_BYPASS_1     0x00a2
#define TAS_REG_DRC_INLINE_1     0x01a2
#define TAS_REG_DRC_BYPASS_2     0x00a3
#define TAS_REG_DRC_INLINE_2     0x01a3
#define TAS_REG_DRC_BYPASS_3     0x00a8
#define TAS_REG_DRC_INLINE_3     0x01a8
#define TAS_REG_DRC_BYPASS_4     0x00a9
#define TAS_REG_DRC_INLINE_4     0x01a9
#define TAS_REG_SEL_OP14_S       0x00aa
#define TAS_REG_SEL_OP14_S_G     0x01aa
#define TAS_REG_SEL_OP14_T       0x00ab
#define TAS_REG_SEL_OP14_T_G     0x01ab
#define TAS_REG_SEL_OP14_Y       0x00b0
#define TAS_REG_SEL_OP14_Y_G     0x01b0
#define TAS_REG_SEL_OP14_Z       0x00b1
#define TAS_REG_SEL_OP14_Z_G     0x01b1
#define TAS_REG_VOLUME_BQ        0x00cf
#define TAS_REG_VOL_TB_SLEW      0x00d0
#define TAS_REG_VOL_CH1          0x00d1
#define TAS_REG_VOL_CH2          0x00d2
#define TAS_REG_VOL_CH3          0x00d7
#define TAS_REG_VOL_CH4          0x00d8
#define TAS_REG_VOL_MASTER       0x00d9
#define TAS_REG_BASS_SET         0x00da
#define TAS_REG_BASS_INDEX       0x00db
#define TAS_REG_TREBLE_SET       0x00dc
#define TAS_REG_TREBLE_INDEX     0x00dd
#define TAS_REG_AM_MODE          0x00de
#define TAS_REG_PSVC             0x00df
#define TAS_REG_GENERAL_CONTROL  0x00e0

#define TAS_MAX_8B_REG 0x40
#define TAS_REG_MAX 0xe1

#define TAS5504_RATES (SNDRV_PCM_RATE_32000 |\
				SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
				SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |\
				SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
#define TAS5504_FORMATS SNDRV_PCM_FMTBIT_S32

extern struct snd_soc_dai tas5504_dai;
extern struct snd_soc_codec_device tas5504_soc_codec_dev;

[-- Attachment #4: Type: text/plain, Size: 160 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-11 18:41     ` Pedro I. Sanchez
  2009-08-11 18:51       ` Jon Smirl
@ 2009-08-11 22:41       ` Mark Brown
  2009-08-25 17:31         ` Pedro I. Sanchez
  1 sibling, 1 reply; 17+ messages in thread
From: Mark Brown @ 2009-08-11 22:41 UTC (permalink / raw)
  To: Pedro I. Sanchez; +Cc: alsa-devel

On Tue, Aug 11, 2009 at 02:41:57PM -0400, Pedro I. Sanchez wrote:

> 1. How do I specify in the machine driver that my pcm1808 is hardwired 
> to slave mode and that I need bit/frame clocks to be provided by the SSC 
> interface?

Configure the SSC driver so that the CPU is in master mode (CBS_CFS).

> 2. How do I set the SSC's clock divider to provide the right clock signals?

Older versions of the sam9g20 driver ran the SSC in master mode, that
might be useful to refer to.  There's normally a clocking diagram in the
manual for the SoC showing the clocking structure with all the dividers
you can configure.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-11 22:41       ` Mark Brown
@ 2009-08-25 17:31         ` Pedro I. Sanchez
  2009-08-25 18:29           ` Mark Brown
  0 siblings, 1 reply; 17+ messages in thread
From: Pedro I. Sanchez @ 2009-08-25 17:31 UTC (permalink / raw)
  Cc: alsa-devel

Hello,

I've been working on my driver, this time concentrating on the TAS5760. 
I decided to use the WM8731 codec and the sam9g20_wm8731 machine drivers 
as templates.

Things are mostly OK now, my kernel modules install, and the driver can 
read/write the chip registers properly. User space applications can see 
my custom board as a sound card. Tha's progress!

The TAS5709 is a playback-slave-only device The SSC is therefore put in 
master mode and I can see the SSC clock signals (SCLK and LRCLK) on the 
scope when playing a sound file. I can also see the activity on the data 
line. But unfortunately, there is no sound, just a speaker Hisss when I 
play a file. I doubled checked all the volume levels to ensure they are 
not mutted.

However, I noticed that the duty cycle of the SSC clocks is not 50% as I 
would expect it to be. There seem to be much more bits on one channel 
than in the other (my sample file is stereo, 16 bits, 44.1 MHz). The 
sample clock illustrations in the TAS5709 all have the same number of 
bits per channel, but it doesn't say explicitly that this is mandatory. 
 From my understanding of the I2S spec, this is not.

The TAS5709 is supposed to detect the clock and data rates 
automatically. Do you know if this non-50% duty cycle could be the 
reason for the lack of sound? Is there a way to force it to be 50%?

Thanks,

-- 
Pedro

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-25 17:31         ` Pedro I. Sanchez
@ 2009-08-25 18:29           ` Mark Brown
  2009-08-25 19:46             ` Pedro I. Sanchez
  0 siblings, 1 reply; 17+ messages in thread
From: Mark Brown @ 2009-08-25 18:29 UTC (permalink / raw)
  To: Pedro I. Sanchez; +Cc: alsa-devel

On Tue, Aug 25, 2009 at 01:31:57PM -0400, Pedro I. Sanchez wrote:

> The TAS5709 is a playback-slave-only device The SSC is therefore put in 
> master mode and I can see the SSC clock signals (SCLK and LRCLK) on the 
> scope when playing a sound file. I can also see the activity on the data 
> line. But unfortunately, there is no sound, just a speaker Hisss when I 
> play a file. I doubled checked all the volume levels to ensure they are 
> not mutted.

Is there any control in the CODEC?  If so you should check that it is
set up properly.

> However, I noticed that the duty cycle of the SSC clocks is not 50% as I 
> would expect it to be. There seem to be much more bits on one channel 
> than in the other (my sample file is stereo, 16 bits, 44.1 MHz). The 
> sample clock illustrations in the TAS5709 all have the same number of 
> bits per channel, but it doesn't say explicitly that this is mandatory. 
>  From my understanding of the I2S spec, this is not.

> The TAS5709 is supposed to detect the clock and data rates 
> automatically. Do you know if this non-50% duty cycle could be the 
> reason for the lack of sound? Is there a way to force it to be 50%?

If the CODEC is trying to work out what the sample and clock rates are
automatically then it does seem reasonable that any oddities in the
clocks that it's given might confuse it somehow - for example, causing
it to expect more bit clocks than it's actually getting which would
cause it to fail to clock in enough data to do a conversion.

I'd expect that appropriate configuration of the clock dividers for the
SSC it's possible to make it generate more normal looking clocks, I
don't think anyone ever optimised the divider configuration when the
AT91SAM9G20-EK was running the SSC in master mode.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-25 18:29           ` Mark Brown
@ 2009-08-25 19:46             ` Pedro I. Sanchez
  2009-08-26 11:44               ` Mark Brown
  0 siblings, 1 reply; 17+ messages in thread
From: Pedro I. Sanchez @ 2009-08-25 19:46 UTC (permalink / raw)
  Cc: alsa-devel

Mark Brown wrote:
> On Tue, Aug 25, 2009 at 01:31:57PM -0400, Pedro I. Sanchez wrote:
> 
>> The TAS5709 is a playback-slave-only device The SSC is therefore put in 
>> master mode and I can see the SSC clock signals (SCLK and LRCLK) on the 
>> scope when playing a sound file. I can also see the activity on the data 
>> line. But unfortunately, there is no sound, just a speaker Hisss when I 
>> play a file. I doubled checked all the volume levels to ensure they are 
>> not mutted.
> 
> Is there any control in the CODEC?  If so you should check that it is
> set up properly.
> 

There are registers to mute/unmute channels and master volume. For the 
time being I am using my pcm_prepare function to write directly to those 
registers to ensure that they are not muted when a sound file is played.

I haven't "mastered" the definitions of sound controls in ALSA yet, I'll 
do that later.

>> However, I noticed that the duty cycle of the SSC clocks is not 50% as I 
>> would expect it to be. There seem to be much more bits on one channel 
>> than in the other (my sample file is stereo, 16 bits, 44.1 MHz). The 
>> sample clock illustrations in the TAS5709 all have the same number of 
>> bits per channel, but it doesn't say explicitly that this is mandatory. 
>>  From my understanding of the I2S spec, this is not.
> 
>> The TAS5709 is supposed to detect the clock and data rates 
>> automatically. Do you know if this non-50% duty cycle could be the 
>> reason for the lack of sound? Is there a way to force it to be 50%?
> 
> If the CODEC is trying to work out what the sample and clock rates are
> automatically then it does seem reasonable that any oddities in the
> clocks that it's given might confuse it somehow - for example, causing
> it to expect more bit clocks than it's actually getting which would
> cause it to fail to clock in enough data to do a conversion.
> 
> I'd expect that appropriate configuration of the clock dividers for the
> SSC it's possible to make it generate more normal looking clocks, I
> don't think anyone ever optimised the divider configuration when the
> AT91SAM9G20-EK was running the SSC in master mode.

Indeed, as I change the dividers the duty cycle changes. But I am out of 
logic now on how to find the right values. It seems that a high degree 
of black magic is required to find them. This is the code that I am 
using in the machine driver to set them up:

	/* set the MCK divider for BCLK */
	ret = snd_soc_dai_set_clkdiv(cpu_dai, ATMEL_SSC_CMR_DIV, cmr_div);
	if (ret < 0)
		return ret;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		/* set the BCLK divider for DACLRC */
		ret = snd_soc_dai_set_clkdiv(cpu_dai,
						ATMEL_SSC_TCMR_PERIOD, period);
	} else {
		/* set the BCLK divider for ADCLRC */
		ret = snd_soc_dai_set_clkdiv(cpu_dai,
						ATMEL_SSC_RCMR_PERIOD, period);
	}
	if (ret < 0)
		return ret;


The big quest for me now is to figure out what "cmr_div" and "period" 
have to be. They must be different for each sample rate. Any pointers 
would be appreciated.

Thanks,

--
Pedro

> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-25 19:46             ` Pedro I. Sanchez
@ 2009-08-26 11:44               ` Mark Brown
  2009-08-27 19:11                 ` Pedro I. Sanchez
  0 siblings, 1 reply; 17+ messages in thread
From: Mark Brown @ 2009-08-26 11:44 UTC (permalink / raw)
  To: Pedro I. Sanchez; +Cc: alsa-devel

On Tue, Aug 25, 2009 at 03:46:14PM -0400, Pedro I. Sanchez wrote:

> The big quest for me now is to figure out what "cmr_div" and "period" 
> have to be. They must be different for each sample rate. Any pointers 
> would be appreciated.

There should be a clocking diagram in the CPU manual which will show how
things are interrelated.  The period of a clock will be 1/rate - the
length of time taken for a clock cycle.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-26 11:44               ` Mark Brown
@ 2009-08-27 19:11                 ` Pedro I. Sanchez
  2009-08-28 12:15                   ` Mark Brown
  0 siblings, 1 reply; 17+ messages in thread
From: Pedro I. Sanchez @ 2009-08-27 19:11 UTC (permalink / raw)
  To: alsa-devel

> On Tue, Aug 25, 2009 at 03:46:14PM -0400, Pedro I. Sanchez wrote:
>
>> The big quest for me now is to figure out what "cmr_div" and "period"
>> have to be. They must be different for each sample rate. Any pointers
>> would be appreciated.
>
> There should be a clocking diagram in the CPU manual which will show how
> things are interrelated.  The period of a clock will be 1/rate - the
> length of time taken for a clock cycle.
>

I'm still struggling with the clocks but in the mean time I have a related
question. Why is it that if I play a 24-bit encoded play the hw_params()
function in my driver is called with SNDRV_PCM_FORMAT_S16_LE format? Does
ALSA makes an on-the-fly conversion?

$ aplay c2_24.wav
Playing WAVE 'c2_24.wav' : Signed 24 bit Little Endian in 3bytes, Rate
44100 Hz, Mono

... and my ipspeaker machine driver module says:

Aug 27 15:03:15 ubuntu kernel: ipspeaker_hw_params: pcm rate is 44100
Aug 27 15:03:16 ubuntu kernel: ipspeaker_hw_params: PCM format
SNDRV_PCM_FORMAT_S16_LE

I would expect my driver to be told that the format is
SNDRV_PCM_FORMAT_S24_LE. Why is this?

Thanks,

-- 
Pedro

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-27 19:11                 ` Pedro I. Sanchez
@ 2009-08-28 12:15                   ` Mark Brown
  2009-08-28 21:24                     ` Pedro I. Sanchez
  0 siblings, 1 reply; 17+ messages in thread
From: Mark Brown @ 2009-08-28 12:15 UTC (permalink / raw)
  To: Pedro I. Sanchez; +Cc: alsa-devel

[Please note that the standard  thing on Linux mailing lists is to do a
reply to all.]

On Thu, Aug 27, 2009 at 03:11:49PM -0400, Pedro I. Sanchez wrote:

> question. Why is it that if I play a 24-bit encoded play the hw_params()
> function in my driver is called with SNDRV_PCM_FORMAT_S16_LE format? Does
> ALSA makes an on-the-fly conversion?

Yes, ALSA can do on the fly format translations if required to play
things.  This normally happens because the input format is not supported
by the drivers.  Telling the application to use the hardware device
directly will bypass this conversion.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-28 12:15                   ` Mark Brown
@ 2009-08-28 21:24                     ` Pedro I. Sanchez
  2009-08-28 21:52                       ` Pedro I. Sanchez
  2009-08-29  9:18                       ` Mark Brown
  0 siblings, 2 replies; 17+ messages in thread
From: Pedro I. Sanchez @ 2009-08-28 21:24 UTC (permalink / raw)
  To: Mark Brown; +Cc: alsa-devel

Well, it turns out that the TAS5709 documentation doesn't say the whole
story. It seems that after all the chip can only guess the incoming bit
rates in rare occasions and I can't really rely on this. What the chip
really needs is a MCLK signal which is a multiple of the sound file's
sample frequency. This is a third clock line to be provided to that chip in
addition to the regular TF (frame) and TK (bit) clocks from the SSC
interface.

Now I know that I have to output this additional clock line via one of the
gpio pins of the SoC and therefore I have to somehow "attach" one of the
CPU internal timers to one of the SoC gpio pins (PC6, linux gpio102 pin to
be more exact). I have the following (borrowed) code now:

	/* PCK0 provides MCLK */
	at91_set_A_periph(AT91_PIN_PC6, 0);

How is this linking the internal pck0 timer to pin PC6? or is it?

And then I have

        /*
         * Codec MCLK is supplied by PCK0 - set it up.
         */
        mclk = clk_get(NULL, "pck0");
        if (IS_ERR(mclk)) {
                pr_err("%s: Failed to get MCLK\n", __func__);
                ret = PTR_ERR(mclk);
                goto err;
        }

        pllb = clk_get(NULL, "pllb");
        if (IS_ERR(pllb)) {
                pr_err("%s: Failed to get PLLB\n", __func__);
                ret = PTR_ERR(pllb);
                goto err_pllb;
        }
        
        ret = clk_set_parent(mclk, pllb);
        if (ret != 0) {
                pr_err("%s: Failed to set MCLK parent\n", __func__);
                goto err_parent;
        }

        clk_set_rate(mclk, 11289600);
        clk_put(pllb);
        pr_info("%s: MCLK rate %luHz\n", __func__, clk_get_rate(mclk));

... but it prints "MCLK rate 6000000Hz". Where does the 600000 come from, I
don't know. And certainly there is no clock on the PC6 pin :-(


Mark, or someone, any enlightenment on how to proceed would be very much
appreciated.

Thank you,

-- 
Pedro

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-28 21:24                     ` Pedro I. Sanchez
@ 2009-08-28 21:52                       ` Pedro I. Sanchez
  2009-08-29  9:18                       ` Mark Brown
  1 sibling, 0 replies; 17+ messages in thread
From: Pedro I. Sanchez @ 2009-08-28 21:52 UTC (permalink / raw)
  To: Pedro I. Sanchez; +Cc: alsa-devel, Mark Brown

On Fri, 28 Aug 2009 17:24:59 -0400, "Pedro I. Sanchez"
<psanchez@fosstel.com> wrote:
> Well, it turns out that the TAS5709 documentation doesn't say the whole
> story. It seems that after all the chip can only guess the incoming bit
> rates in rare occasions and I can't really rely on this. What the chip
> really needs is a MCLK signal which is a multiple of the sound file's
> sample frequency. This is a third clock line to be provided to that chip
in
> addition to the regular TF (frame) and TK (bit) clocks from the SSC
> interface.
> 
> Now I know that I have to output this additional clock line via one of
the
> gpio pins of the SoC and therefore I have to somehow "attach" one of the
> CPU internal timers to one of the SoC gpio pins (PC6, linux gpio102 pin
to
> be more exact). I have the following (borrowed) code now:
> 
> 	/* PCK0 provides MCLK */
> 	at91_set_A_periph(AT91_PIN_PC6, 0);
> 
> How is this linking the internal pck0 timer to pin PC6? or is it?
> 
> And then I have
> 
>         /*
>          * Codec MCLK is supplied by PCK0 - set it up.
>          */
>         mclk = clk_get(NULL, "pck0");
>         if (IS_ERR(mclk)) {
>                 pr_err("%s: Failed to get MCLK\n", __func__);
>                 ret = PTR_ERR(mclk);
>                 goto err;
>         }
> 
>         pllb = clk_get(NULL, "pllb");
>         if (IS_ERR(pllb)) {
>                 pr_err("%s: Failed to get PLLB\n", __func__);
>                 ret = PTR_ERR(pllb);
>                 goto err_pllb;
>         }
>         
>         ret = clk_set_parent(mclk, pllb);
>         if (ret != 0) {
>                 pr_err("%s: Failed to set MCLK parent\n", __func__);
>                 goto err_parent;
>         }
> 
>         clk_set_rate(mclk, 11289600);
>         clk_put(pllb);
>         pr_info("%s: MCLK rate %luHz\n", __func__, clk_get_rate(mclk));
> 
> ... but it prints "MCLK rate 6000000Hz". Where does the 600000 come from,
I
> don't know. And certainly there is no clock on the PC6 pin :-(
> 
> 
> Mark, or someone, any enlightenment on how to proceed would be very much
> appreciated.
> 
> Thank you,

BTW, I forgot to mention that this board is just a slight variation of the
sam9260ek eval board and I would suspect that the internal wiring of the
SoC is pretty much the same. So understanding how to use the programmable
clocks on the latter should give me the solution for the former.

Thanks,

-- 
Pedro

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-28 21:24                     ` Pedro I. Sanchez
  2009-08-28 21:52                       ` Pedro I. Sanchez
@ 2009-08-29  9:18                       ` Mark Brown
  2009-08-29 16:01                         ` Pedro I. Sanchez
  1 sibling, 1 reply; 17+ messages in thread
From: Mark Brown @ 2009-08-29  9:18 UTC (permalink / raw)
  To: Pedro I. Sanchez; +Cc: alsa-devel

On Fri, Aug 28, 2009 at 05:24:59PM -0400, Pedro I. Sanchez wrote:

> Well, it turns out that the TAS5709 documentation doesn't say the whole
> story. It seems that after all the chip can only guess the incoming bit
> rates in rare occasions and I can't really rely on this. What the chip
> really needs is a MCLK signal which is a multiple of the sound file's
> sample frequency. This is a third clock line to be provided to that chip in
> addition to the regular TF (frame) and TK (bit) clocks from the SSC
> interface.

That's fairly standard for CODECs - some have FLLs or similar which
allow them to generate their master clock from the BCLK or LRCLK but
most need a MCLK too.

> Now I know that I have to output this additional clock line via one of the
> gpio pins of the SoC and therefore I have to somehow "attach" one of the
> CPU internal timers to one of the SoC gpio pins (PC6, linux gpio102 pin to
> be more exact). I have the following (borrowed) code now:

Be careful here.  Generally CODECs require some synchronisation between
the master clock and the other audio clocks - normally the controller
block in the CPU will have a clock it can provide with the intention
that it be used as a master clock.

> 	/* PCK0 provides MCLK */
> 	at91_set_A_periph(AT91_PIN_PC6, 0);

> How is this linking the internal pck0 timer to pin PC6? or is it?

You'd be better off asking the Atmel people on linux-arm-kernel (or some
other Atmel-specific list if there is one) about this - it's more of a
general question about the CPU port than an audio-specific question.

>         ret = clk_set_parent(mclk, pllb);
>         if (ret != 0) {
>                 pr_err("%s: Failed to set MCLK parent\n", __func__);
>                 goto err_parent;
>         }

>         clk_set_rate(mclk, 11289600);
>         clk_put(pllb);
>         pr_info("%s: MCLK rate %luHz\n", __func__, clk_get_rate(mclk));

> ... but it prints "MCLK rate 6000000Hz". Where does the 600000 come from, I
> don't know. And certainly there is no clock on the PC6 pin :-(

It'll be derived from the parent clock plus any dividers in the way.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: ASoC: at91sam9260-PCM1808/TAS5709-based board driver
  2009-08-29  9:18                       ` Mark Brown
@ 2009-08-29 16:01                         ` Pedro I. Sanchez
  0 siblings, 0 replies; 17+ messages in thread
From: Pedro I. Sanchez @ 2009-08-29 16:01 UTC (permalink / raw)
  To: Mark Brown; +Cc: alsa-devel

Mark Brown wrote:
> On Fri, Aug 28, 2009 at 05:24:59PM -0400, Pedro I. Sanchez wrote:
> 
>> Well, it turns out that the TAS5709 documentation doesn't say the whole
>> story. It seems that after all the chip can only guess the incoming bit
>> rates in rare occasions and I can't really rely on this. What the chip
>> really needs is a MCLK signal which is a multiple of the sound file's
>> sample frequency. This is a third clock line to be provided to that chip in
>> addition to the regular TF (frame) and TK (bit) clocks from the SSC
>> interface.
> 
> That's fairly standard for CODECs - some have FLLs or similar which
> allow them to generate their master clock from the BCLK or LRCLK but
> most need a MCLK too.
> 
>> Now I know that I have to output this additional clock line via one of the
>> gpio pins of the SoC and therefore I have to somehow "attach" one of the
>> CPU internal timers to one of the SoC gpio pins (PC6, linux gpio102 pin to
>> be more exact). I have the following (borrowed) code now:
> 
> Be careful here.  Generally CODECs require some synchronisation between
> the master clock and the other audio clocks - normally the controller
> block in the CPU will have a clock it can provide with the intention
> that it be used as a master clock.
> 

Yes, but the manual says, if I still believe in it, that the MCLK needs 
no phase alignment with the bit clock and that in fact it can be 
generated by external circuitry. I'll have to wait and see.

>> 	/* PCK0 provides MCLK */
>> 	at91_set_A_periph(AT91_PIN_PC6, 0);
> 
>> How is this linking the internal pck0 timer to pin PC6? or is it?
> 
> You'd be better off asking the Atmel people on linux-arm-kernel (or some
> other Atmel-specific list if there is one) about this - it's more of a
> general question about the CPU port than an audio-specific question.
>

I was suspecting that, thank you anyway. I will keep my questions on 
this thread on the sound aspects only.

>>         ret = clk_set_parent(mclk, pllb);
>>         if (ret != 0) {
>>                 pr_err("%s: Failed to set MCLK parent\n", __func__);
>>                 goto err_parent;
>>         }
> 
>>         clk_set_rate(mclk, 11289600);
>>         clk_put(pllb);
>>         pr_info("%s: MCLK rate %luHz\n", __func__, clk_get_rate(mclk));
> 
>> ... but it prints "MCLK rate 6000000Hz". Where does the 600000 come from, I
>> don't know. And certainly there is no clock on the PC6 pin :-(
> 
> It'll be derived from the parent clock plus any dividers in the way.

Thanks again,

-- 
Pedro

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2009-08-29 16:01 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-08-08 17:39 ASoC: at91sam9260-PCM1808/TAS5709-based board driver Pedro Sanchez
2009-08-09 10:36 ` Mark Brown
2009-08-09 13:35 ` Jon Smirl
2009-08-09 13:37   ` Jon Smirl
2009-08-11 18:41     ` Pedro I. Sanchez
2009-08-11 18:51       ` Jon Smirl
2009-08-11 22:41       ` Mark Brown
2009-08-25 17:31         ` Pedro I. Sanchez
2009-08-25 18:29           ` Mark Brown
2009-08-25 19:46             ` Pedro I. Sanchez
2009-08-26 11:44               ` Mark Brown
2009-08-27 19:11                 ` Pedro I. Sanchez
2009-08-28 12:15                   ` Mark Brown
2009-08-28 21:24                     ` Pedro I. Sanchez
2009-08-28 21:52                       ` Pedro I. Sanchez
2009-08-29  9:18                       ` Mark Brown
2009-08-29 16:01                         ` Pedro I. Sanchez

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.