PCM3168 alsa driver for beaglebone black

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

 



Hi:
I'm working in a project of making an audio cape for beaglebone black with pcm3168 codec... so far i've written the driver i've attached based on pcm1681.c available in linux kernel.
Im a noob in this subjets, so i would like to know how can now make it work in my custom system for the beaglebone. 
I followed the guide in this link http://processors.wiki.ti.com/index.php/Sitara_Linux_Audio_DAC_Example, but it makes me write the ops and hw structures that i already have in my pcm3168.c file, in the davinci_evm.c file.

The drivers compiles and is detected at boot time, as it says in the guide. But when i type aplay command to play some wav, the message "ALSA lib pcm.c:2267:(snd_pcm_open_noupdate) Unknown PCM default
aplay: main:722: audio open error: No such file or directory" appears, so i'm missing something...

I don't knwo if this post belongs to this list, but any help would be great...
If some docummentation about writing a driver for an embedded system is available, please tell me... 
Also, if someone can suggest me some guidelines of work i would be grateful, cause i just can't get organized with the information i found

Thanks very Much... 

Pablo

 		 	   		  
/*
 * PCM3168 ASoC codec driver
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>

#define PCM3168_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE  |		\
			     SNDRV_PCM_FMTBIT_S24_LE)

#define PCM3168_PCM_RATES   (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
			     SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100  | \
			     SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200  | \
			     SNDRV_PCM_RATE_96000)

#define PCM3168_DEEMPH_CONTROL	0x46
#define PCM3168_ATT_CONTROL(X) (X <=6 ? X : X+9)
#define PCM3168_SOFT_MUTE_CONTROL	0x44
#define PCM3168_DAC_FMT_CONTROL	0x41

#define PCM3168_DEEMPH_MASK	0x01
#define PCM3168_DEEMPH_RATE_MASK	0x30

static const struct reg_default pcm3168_reg_defaults[] = {
	{ 0x40,	0xC2 },
	{ 0x41,	0x06 },
	{ 0x42,	0x00 },
	{ 0x43,	0x00 },
	{ 0x44,	0x00 },
	{ 0x46,	0x18 },
	{ 0x47,	0xff },
	{ 0x48,	0xff },
	{ 0x49,	0xff },
	{ 0x4A,	0xff },
	{ 0x4B,	0xff },
	{ 0x4C,	0xff },
	{ 0x4D,	0xff },
	{ 0x4E,	0xff },
	{ 0x4F,	0xff },
	{ 0x50,	0x02 },
	{ 0x51,	0x06 },
	{ 0x52,	0x00 },
	{ 0x53,	0x00 },
	{ 0x54,	0x00 },
	{ 0x55,	0x00 },
	{ 0x57,	0x00 },
	{ 0x58,	0xD7 },
	{ 0x59,	0xD7 },
	{ 0x5A,	0xD7 },
	{ 0x5B,	0xD7 },
	{ 0x5C,	0xD7 },
	{ 0x5D,	0xD7 },
	{ 0x5E,	0xD7 },
};

static bool pcm3168_accessible_reg(struct device *dev, unsigned int reg)
{
	return !((reg < 0x40) || (reg > 0x5E));
}

static bool pcm3168_writeable_reg(struct device *dev, unsigned register reg)
{
	return pcm3168_accessible_reg(dev, reg) &&
		(reg != 0x45) && (reg != 56);
}

struct pcm3168_private {
	struct regmap *regmap;
	unsigned int format;
	/* Current deemphasis status */
	unsigned int deemph;
	/* Current rate for deemphasis control */
	unsigned int rate;
};

static const int pcm3168_deemph[] = {0, 44100, 48000, 32000 };

static int pcm3168_set_deemph(struct snd_soc_codec *codec)
{
	struct pcm3168_private *priv = snd_soc_codec_get_drvdata(codec);
	int i = 0, val = -1;

	if (priv->deemph)
		for (i = 0; i < ARRAY_SIZE(pcm3168_deemph); i++)
			if (pcm3168_deemph[i] == priv->rate)
				val = i;

		return regmap_update_bits(priv->regmap, PCM3168_DEEMPH_CONTROL,
				   PCM3168_DEEMPH_RATE_MASK, val << 4);

}

static int pcm3168_get_deemph(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
	struct pcm3168_private *priv = snd_soc_codec_get_drvdata(codec);

	ucontrol->value.integer.value[0] = priv->deemph;

	return 0;
}

static int pcm3168_put_deemph(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
	struct pcm3168_private *priv = snd_soc_codec_get_drvdata(codec);

	priv->deemph = ucontrol->value.integer.value[0];

	return pcm3168_set_deemph(codec);
}

static int pcm3168_set_dai_fmt(struct snd_soc_dai *codec_dai,
			      unsigned int format)
{
	struct snd_soc_codec *codec = codec_dai->codec;
	struct pcm3168_private *priv = snd_soc_codec_get_drvdata(codec);
	priv->format = format;
	return 0;
}

static int pcm3168_digital_mute(struct snd_soc_dai *dai, int mute)
{
	struct snd_soc_codec *codec = dai->codec;
	struct pcm3168_private *priv = snd_soc_codec_get_drvdata(codec);
	int val=mute;
	return regmap_write(priv->regmap, PCM3168_SOFT_MUTE_CONTROL, val);
}

static int pcm3168_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *params,
			     struct snd_soc_dai *dai)
{
	struct snd_soc_codec *codec = dai->codec;
	struct pcm3168_private *priv = snd_soc_codec_get_drvdata(codec);
	int val = 0, ret;

	priv->rate = params_rate(params);

	switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_RIGHT_J:
		switch (params_width(params)) {
		case 24:
			val = 0x02;
			break;
		case 16:
			val = 0x03;
			break;
		default:
			return -EINVAL;
		}
		break;
	case SND_SOC_DAIFMT_I2S:
		val = 0x00;
		break;
	case SND_SOC_DAIFMT_LEFT_J:
		val = 0x01;
	default:
		dev_err(codec->dev, "Invalid DAI format\n");
		return -EINVAL;
	}

	ret = regmap_update_bits(priv->regmap, PCM3168_DAC_FMT_CONTROL, 0x0f, val);
	if (ret < 0)
		return ret;

	return pcm3168_set_deemph(codec);
}

static const struct snd_soc_dai_ops pcm3168_dai_ops = {
	.set_fmt	= pcm3168_set_dai_fmt,
	.hw_params	= pcm3168_hw_params,
	.digital_mute	= pcm3168_digital_mute,
};

static const struct snd_soc_dapm_widget pcm3168_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("VOUT1"),
SND_SOC_DAPM_OUTPUT("VOUT2"),
SND_SOC_DAPM_OUTPUT("VOUT3"),
SND_SOC_DAPM_OUTPUT("VOUT4"),
SND_SOC_DAPM_OUTPUT("VOUT5"),
SND_SOC_DAPM_OUTPUT("VOUT6"),
SND_SOC_DAPM_OUTPUT("VOUT7"),
SND_SOC_DAPM_OUTPUT("VOUT8"),

SND_SOC_DAPM_INPUT("VIN1"),
SND_SOC_DAPM_INPUT("VIN2"),
SND_SOC_DAPM_INPUT("VIN3"),
SND_SOC_DAPM_INPUT("VIN4"),
SND_SOC_DAPM_INPUT("VIN5"),
SND_SOC_DAPM_INPUT("VIN6"),
};

static const struct snd_soc_dapm_route pcm3168_dapm_routes[] = {
	{ "VOUT1", NULL, "Playback" },
	{ "VOUT2", NULL, "Playback" },
	{ "VOUT3", NULL, "Playback" },
	{ "VOUT4", NULL, "Playback" },
	{ "VOUT5", NULL, "Playback" },
	{ "VOUT6", NULL, "Playback" },
	{ "VOUT7", NULL, "Playback" },
	{ "VOUT8", NULL, "Playback" },

	{ "VIN1", NULL, "Capture" },
	{ "VIN2", NULL, "Capture" },
	{ "VIN3", NULL, "Capture" },
	{ "VIN4", NULL, "Capture" },
	{ "VIN5", NULL, "Capture" },
	{ "VIN6", NULL, "Capture" },

};

static const DECLARE_TLV_DB_SCALE(pcm3168_dac_tlv, -6350, 50, 1);
static const DECLARE_TLV_DB_SCALE(pcm3168_adc_tlv, -6350, 50, 1);

static const struct snd_kcontrol_new pcm3168_controls[] = {
	SOC_DOUBLE_R_TLV("Channel 1/2 Playback Volume",
			PCM3168_ATT_CONTROL(1), PCM3168_ATT_CONTROL(2), 0,
			0x7f, 0, pcm3168_dac_tlv),
	SOC_DOUBLE_R_TLV("Channel 3/4 Playback Volume",
			PCM3168_ATT_CONTROL(3), PCM3168_ATT_CONTROL(4), 0,
			0x7f, 0, pcm3168_dac_tlv),
	SOC_DOUBLE_R_TLV("Channel 5/6 Playback Volume",
			PCM3168_ATT_CONTROL(5), PCM3168_ATT_CONTROL(6), 0,
			0x7f, 0, pcm3168_dac_tlv),
	SOC_DOUBLE_R_TLV("Channel 7/8 Playback Volume",
			PCM3168_ATT_CONTROL(7), PCM3168_ATT_CONTROL(8), 0,
			0x7f, 0, pcm3168_dac_tlv),

	SOC_DOUBLE_R_TLV("Channel 1/2 Capture Volume",
			PCM3168_ATT_CONTROL(9), PCM3168_ATT_CONTROL(10), 0,
			0x7f, 0, pcm3168_adc_tlv),
	SOC_DOUBLE_R_TLV("Channel 3/4 Capture Volume",
			PCM3168_ATT_CONTROL(11), PCM3168_ATT_CONTROL(12), 0,
			0x7f, 0, pcm3168_adc_tlv),
	SOC_DOUBLE_R_TLV("Channel 5/6 Capture Volume",
			PCM3168_ATT_CONTROL(13), PCM3168_ATT_CONTROL(14), 0,
			0x7f, 0, pcm3168_adc_tlv),

	SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0,
			    pcm3168_get_deemph, pcm3168_put_deemph),
};

static struct snd_soc_dai_driver pcm3168_dai = {
	.name = "pcm3168-hifi",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 2,
		.channels_max = 8,
		.rates = PCM3168_PCM_RATES,
		.formats = PCM3168_PCM_FORMATS,
	},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 6,
		.rates = PCM3168_PCM_RATES,
		.formats = PCM3168_PCM_FORMATS,
	},
	.ops = &pcm3168_dai_ops,
};

#ifdef CONFIG_OF
static const struct of_device_id pcm3168_dt_ids[] = {
	{ .compatible = "ti,pcm3168", },
	{ }
};
MODULE_DEVICE_TABLE(of, pcm3168_dt_ids);
#endif

static const struct regmap_config pcm3168_regmap = {
	.reg_bits		= 8,
	.val_bits		= 8,
	.max_register		= 0x5E,
	.reg_defaults		= pcm3168_reg_defaults,
	.num_reg_defaults	= ARRAY_SIZE(pcm3168_reg_defaults),
	.writeable_reg		= pcm3168_writeable_reg,
	.readable_reg		= pcm3168_accessible_reg,
};

static struct snd_soc_codec_driver soc_codec_dev_pcm3168 = {
	.controls		= pcm3168_controls,
	.num_controls		= ARRAY_SIZE(pcm3168_controls),
	.dapm_widgets		= pcm3168_dapm_widgets,
	.num_dapm_widgets	= ARRAY_SIZE(pcm3168_dapm_widgets),
	.dapm_routes		= pcm3168_dapm_routes,
	.num_dapm_routes	= ARRAY_SIZE(pcm3168_dapm_routes),
};

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

static int pcm3168_i2c_probe(struct i2c_client *client,
			      const struct i2c_device_id *id)
{
	int ret;
	struct pcm3168_private *priv;

	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->regmap = devm_regmap_init_i2c(client, &pcm3168_regmap);
	if (IS_ERR(priv->regmap)) {
		ret = PTR_ERR(priv->regmap);
		dev_err(&client->dev, "Failed to create regmap: %d\n", ret);
		return ret;
	}

	i2c_set_clientdata(client, priv);

	return snd_soc_register_codec(&client->dev, &soc_codec_dev_pcm3168,
		&pcm3168_dai, 1);
}

static int pcm3168_i2c_remove(struct i2c_client *client)
{
	snd_soc_unregister_codec(&client->dev);
	return 0;
}

static struct i2c_driver pcm3168_i2c_driver = {
	.driver = {
		.name	= "pcm3168",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(pcm3168_dt_ids),
	},
	.id_table	= pcm3168_i2c_id,
	.probe		= pcm3168_i2c_probe,
	.remove		= pcm3168_i2c_remove,
};

module_i2c_driver(pcm3168_i2c_driver);

MODULE_DESCRIPTION("Texas Instruments PCM3168 ALSA SoC Codec Driver");
MODULE_AUTHOR("Pablo Fonovich");
MODULE_LICENSE("GPL");
_______________________________________________
Alsa-devel mailing list
Alsa-devel@xxxxxxxxxxxxxxxx
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

[Index of Archives]     [ALSA User]     [Linux Audio Users]     [Kernel Archive]     [Asterisk PBX]     [Photo Sharing]     [Linux Sound]     [Video 4 Linux]     [Gimp]     [Yosemite News]

  Powered by Linux