pcm1681.c 9.2 KB
Newer Older
M
Marek Belisko 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * PCM1681 ASoC codec driver
 *
 * Copyright (c) StreamUnlimited GmbH 2013
 *	Marek Belisko <marek.belisko@streamunlimited.com>
 *
 * 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>
24
#include <linux/of.h>
M
Marek Belisko 已提交
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
#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 PCM1681_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE  |		\
			     SNDRV_PCM_FMTBIT_S24_LE)

#define PCM1681_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 | SNDRV_PCM_RATE_192000)

#define PCM1681_SOFT_MUTE_ALL		0xff
#define PCM1681_DEEMPH_RATE_MASK	0x18
#define PCM1681_DEEMPH_MASK		0x01

#define PCM1681_ATT_CONTROL(X)	(X <= 6 ? X : X + 9) /* Attenuation level */
#define PCM1681_SOFT_MUTE	0x07	/* Soft mute control register */
#define PCM1681_DAC_CONTROL	0x08	/* DAC operation control */
#define PCM1681_FMT_CONTROL	0x09	/* Audio interface data format */
#define PCM1681_DEEMPH_CONTROL	0x0a	/* De-emphasis control */
#define PCM1681_ZERO_DETECT_STATUS	0x0e	/* Zero detect status reg */

static const struct reg_default pcm1681_reg_defaults[] = {
	{ 0x01,	0xff },
	{ 0x02,	0xff },
	{ 0x03,	0xff },
	{ 0x04,	0xff },
	{ 0x05,	0xff },
	{ 0x06,	0xff },
	{ 0x07,	0x00 },
	{ 0x08,	0x00 },
	{ 0x09,	0x06 },
	{ 0x0A,	0x00 },
	{ 0x0B,	0xff },
	{ 0x0C,	0x0f },
	{ 0x0D,	0x00 },
	{ 0x10,	0xff },
	{ 0x11,	0xff },
	{ 0x12,	0x00 },
	{ 0x13,	0x00 },
};

static bool pcm1681_accessible_reg(struct device *dev, unsigned int reg)
{
	return !((reg == 0x00) || (reg == 0x0f));
}

static bool pcm1681_writeable_reg(struct device *dev, unsigned register reg)
{
	return pcm1681_accessible_reg(dev, reg) &&
		(reg != PCM1681_ZERO_DETECT_STATUS);
}

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

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

static int pcm1681_set_deemph(struct snd_soc_codec *codec)
{
	struct pcm1681_private *priv = snd_soc_codec_get_drvdata(codec);
	int i = 0, val = -1, enable = 0;

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

	if (val != -1) {
		regmap_update_bits(priv->regmap, PCM1681_DEEMPH_CONTROL,
					PCM1681_DEEMPH_RATE_MASK, val);
		enable = 1;
	} else
		enable = 0;

	/* enable/disable deemphasis functionality */
	return regmap_update_bits(priv->regmap, PCM1681_DEEMPH_CONTROL,
					PCM1681_DEEMPH_MASK, enable);
}

static int pcm1681_get_deemph(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
118
	struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
M
Marek Belisko 已提交
119 120
	struct pcm1681_private *priv = snd_soc_codec_get_drvdata(codec);

121
	ucontrol->value.integer.value[0] = priv->deemph;
M
Marek Belisko 已提交
122 123 124 125 126 127 128

	return 0;
}

static int pcm1681_put_deemph(struct snd_kcontrol *kcontrol,
			      struct snd_ctl_elem_value *ucontrol)
{
129
	struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
M
Marek Belisko 已提交
130 131
	struct pcm1681_private *priv = snd_soc_codec_get_drvdata(codec);

132
	priv->deemph = ucontrol->value.integer.value[0];
M
Marek Belisko 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179

	return pcm1681_set_deemph(codec);
}

static int pcm1681_set_dai_fmt(struct snd_soc_dai *codec_dai,
			      unsigned int format)
{
	struct snd_soc_codec *codec = codec_dai->codec;
	struct pcm1681_private *priv = snd_soc_codec_get_drvdata(codec);

	/* The PCM1681 can only be slave to all clocks */
	if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) {
		dev_err(codec->dev, "Invalid clocking mode\n");
		return -EINVAL;
	}

	priv->format = format;

	return 0;
}

static int pcm1681_digital_mute(struct snd_soc_dai *dai, int mute)
{
	struct snd_soc_codec *codec = dai->codec;
	struct pcm1681_private *priv = snd_soc_codec_get_drvdata(codec);
	int val;

	if (mute)
		val = PCM1681_SOFT_MUTE_ALL;
	else
		val = 0;

	return regmap_write(priv->regmap, PCM1681_SOFT_MUTE, val);
}

static int pcm1681_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 pcm1681_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:
180 181 182 183 184 185 186 187 188 189
		switch (params_width(params)) {
		case 24:
			val = 0;
			break;
		case 16:
			val = 3;
			break;
		default:
			return -EINVAL;
		}
M
Marek Belisko 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
		break;
	case SND_SOC_DAIFMT_I2S:
		val = 0x04;
		break;
	case SND_SOC_DAIFMT_LEFT_J:
		val = 0x05;
		break;
	default:
		dev_err(codec->dev, "Invalid DAI format\n");
		return -EINVAL;
	}

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

	return pcm1681_set_deemph(codec);
}

static const struct snd_soc_dai_ops pcm1681_dai_ops = {
	.set_fmt	= pcm1681_set_dai_fmt,
	.hw_params	= pcm1681_hw_params,
	.digital_mute	= pcm1681_digital_mute,
};

M
Mark Brown 已提交
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
static const struct snd_soc_dapm_widget pcm1681_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"),
};

static const struct snd_soc_dapm_route pcm1681_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" },
};

M
Marek Belisko 已提交
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
static const DECLARE_TLV_DB_SCALE(pcm1681_dac_tlv, -6350, 50, 1);

static const struct snd_kcontrol_new pcm1681_controls[] = {
	SOC_DOUBLE_R_TLV("Channel 1/2 Playback Volume",
			PCM1681_ATT_CONTROL(1), PCM1681_ATT_CONTROL(2), 0,
			0x7f, 0, pcm1681_dac_tlv),
	SOC_DOUBLE_R_TLV("Channel 3/4 Playback Volume",
			PCM1681_ATT_CONTROL(3), PCM1681_ATT_CONTROL(4), 0,
			0x7f, 0, pcm1681_dac_tlv),
	SOC_DOUBLE_R_TLV("Channel 5/6 Playback Volume",
			PCM1681_ATT_CONTROL(5), PCM1681_ATT_CONTROL(6), 0,
			0x7f, 0, pcm1681_dac_tlv),
	SOC_DOUBLE_R_TLV("Channel 7/8 Playback Volume",
			PCM1681_ATT_CONTROL(7), PCM1681_ATT_CONTROL(8), 0,
			0x7f, 0, pcm1681_dac_tlv),
	SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0,
			    pcm1681_get_deemph, pcm1681_put_deemph),
};

M
Mark Brown 已提交
256
static struct snd_soc_dai_driver pcm1681_dai = {
M
Marek Belisko 已提交
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
	.name = "pcm1681-hifi",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 2,
		.channels_max = 8,
		.rates = PCM1681_PCM_RATES,
		.formats = PCM1681_PCM_FORMATS,
	},
	.ops = &pcm1681_dai_ops,
};

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

static const struct regmap_config pcm1681_regmap = {
	.reg_bits		= 8,
	.val_bits		= 8,
A
Axel Lin 已提交
279
	.max_register		= 0x13,
M
Marek Belisko 已提交
280 281 282 283 284 285 286 287 288
	.reg_defaults		= pcm1681_reg_defaults,
	.num_reg_defaults	= ARRAY_SIZE(pcm1681_reg_defaults),
	.writeable_reg		= pcm1681_writeable_reg,
	.readable_reg		= pcm1681_accessible_reg,
};

static struct snd_soc_codec_driver soc_codec_dev_pcm1681 = {
	.controls		= pcm1681_controls,
	.num_controls		= ARRAY_SIZE(pcm1681_controls),
M
Mark Brown 已提交
289 290 291 292
	.dapm_widgets		= pcm1681_dapm_widgets,
	.num_dapm_widgets	= ARRAY_SIZE(pcm1681_dapm_widgets),
	.dapm_routes		= pcm1681_dapm_routes,
	.num_dapm_routes	= ARRAY_SIZE(pcm1681_dapm_routes),
M
Marek Belisko 已提交
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
};

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

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

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

	priv->regmap = devm_regmap_init_i2c(client, &pcm1681_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_pcm1681,
		&pcm1681_dai, 1);
}

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

static struct i2c_driver pcm1681_i2c_driver = {
	.driver = {
		.name	= "pcm1681",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(pcm1681_dt_ids),
	},
	.id_table	= pcm1681_i2c_id,
	.probe		= pcm1681_i2c_probe,
	.remove		= pcm1681_i2c_remove,
};

module_i2c_driver(pcm1681_i2c_driver);

MODULE_DESCRIPTION("Texas Instruments PCM1681 ALSA SoC Codec Driver");
MODULE_AUTHOR("Marek Belisko <marek.belisko@streamunlimited.com>");
MODULE_LICENSE("GPL");