On Sat, Aug 8, 2009 at 1:39 PM, Pedro Sanchez<psanchez@xxxxxxxxxxx> 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@xxxxxxxxxxxxxxxx > http://mailman.alsa-project.org/mailman/listinfo/alsa-devel > > -- Jon Smirl jonsmirl@xxxxxxxxx
/* * Texas Instruments PCM1690 low power audio CODEC * ALSA SoC CODEC driver * * Copyright (C) Jon Smirl <jonsmirl@xxxxxxxxx> */ #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");
/* * Texas Instruments PCM1690 low power audio CODEC * ALSA SoC CODEC driver * * Copyright (C) Jon Smirl <jonsmirl@xxxxxxxxx> */ #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;
_______________________________________________ Alsa-devel mailing list Alsa-devel@xxxxxxxxxxxxxxxx http://mailman.alsa-project.org/mailman/listinfo/alsa-devel