omap-abe-twl6040.c 9.0 KB
Newer Older
1
/*
2 3
 * omap-abe-twl6040.c  --  SoC audio for TI OMAP based boards with ABE and
 *			   twl6040 codec
4
 *
5
 * Author: Misael Lopez Cruz <misael.lopez@ti.com>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * 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.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <linux/clk.h>
#include <linux/platform_device.h>
25
#include <linux/mfd/twl6040.h>
26
#include <linux/platform_data/omap-abe-twl6040.h>
27
#include <linux/module.h>
28

29 30 31
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
32
#include <sound/jack.h>
33

34
#include "omap-dmic.h"
35
#include "omap-mcpdm.h"
36 37 38
#include "omap-pcm.h"
#include "../codecs/twl6040.h"

39 40 41 42 43
struct abe_twl6040 {
	int	jack_detection;	/* board can detect jack events */
	int	mclk_freq;	/* MCLK frequency speed for twl6040 */
};

44
static int omap_abe_hw_params(struct snd_pcm_substream *substream,
45 46 47
	struct snd_pcm_hw_params *params)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
48
	struct snd_soc_dai *codec_dai = rtd->codec_dai;
49 50
	struct snd_soc_codec *codec = rtd->codec;
	struct snd_soc_card *card = codec->card;
51
	struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card);
52 53 54
	int clk_id, freq;
	int ret;

55 56
	clk_id = twl6040_get_clk_id(rtd->codec);
	if (clk_id == TWL6040_SYSCLK_SEL_HPPLL)
57
		freq = priv->mclk_freq;
58
	else if (clk_id == TWL6040_SYSCLK_SEL_LPPLL)
59
		freq = 32768;
60 61
	else
		return -EINVAL;
62 63 64 65 66 67 68 69

	/* set the codec mclk */
	ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, freq,
				SND_SOC_CLOCK_IN);
	if (ret) {
		printk(KERN_ERR "can't set codec system clock\n");
		return ret;
	}
70
	return ret;
71 72
}

73 74
static struct snd_soc_ops omap_abe_ops = {
	.hw_params = omap_abe_hw_params,
75 76
};

77
static int omap_abe_dmic_hw_params(struct snd_pcm_substream *substream,
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
	struct snd_pcm_hw_params *params)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	int ret = 0;

	ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_DMIC_SYSCLK_PAD_CLKS,
				     19200000, SND_SOC_CLOCK_IN);
	if (ret < 0) {
		printk(KERN_ERR "can't set DMIC cpu system clock\n");
		return ret;
	}
	ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_DMIC_ABE_DMIC_CLK, 2400000,
				     SND_SOC_CLOCK_OUT);
	if (ret < 0) {
		printk(KERN_ERR "can't set DMIC output clock\n");
		return ret;
	}
	return 0;
}

99 100
static struct snd_soc_ops omap_abe_dmic_ops = {
	.hw_params = omap_abe_dmic_hw_params,
101 102
};

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
/* Headset jack */
static struct snd_soc_jack hs_jack;

/*Headset jack detection DAPM pins */
static struct snd_soc_jack_pin hs_jack_pins[] = {
	{
		.pin = "Headset Mic",
		.mask = SND_JACK_MICROPHONE,
	},
	{
		.pin = "Headset Stereophone",
		.mask = SND_JACK_HEADPHONE,
	},
};

118
/* SDP4430 machine DAPM */
119
static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
120
	/* Outputs */
121
	SND_SOC_DAPM_HP("Headset Stereophone", NULL),
122
	SND_SOC_DAPM_SPK("Earphone Spk", NULL),
123 124 125 126 127 128 129 130 131
	SND_SOC_DAPM_SPK("Ext Spk", NULL),
	SND_SOC_DAPM_LINE("Line Out", NULL),
	SND_SOC_DAPM_SPK("Vibrator", NULL),

	/* Inputs */
	SND_SOC_DAPM_MIC("Headset Mic", NULL),
	SND_SOC_DAPM_MIC("Main Handset Mic", NULL),
	SND_SOC_DAPM_MIC("Sub Handset Mic", NULL),
	SND_SOC_DAPM_LINE("Line In", NULL),
132 133 134

	/* Digital microphones */
	SND_SOC_DAPM_MIC("Digital Mic", NULL),
135 136 137
};

static const struct snd_soc_dapm_route audio_map[] = {
138 139 140 141 142
	/* Routings for outputs */
	{"Headset Stereophone", NULL, "HSOL"},
	{"Headset Stereophone", NULL, "HSOR"},

	{"Earphone Spk", NULL, "EP"},
143 144 145 146

	{"Ext Spk", NULL, "HFL"},
	{"Ext Spk", NULL, "HFR"},

147 148 149 150 151 152 153
	{"Line Out", NULL, "AUXL"},
	{"Line Out", NULL, "AUXR"},

	{"Vibrator", NULL, "VIBRAL"},
	{"Vibrator", NULL, "VIBRAR"},

	/* Routings for inputs */
154 155
	{"HSMIC", NULL, "Headset Mic"},
	{"Headset Mic", NULL, "Headset Mic Bias"},
156

157 158
	{"MAINMIC", NULL, "Main Handset Mic"},
	{"Main Handset Mic", NULL, "Main Mic Bias"},
159

160 161
	{"SUBMIC", NULL, "Sub Handset Mic"},
	{"Sub Handset Mic", NULL, "Main Mic Bias"},
162

163 164
	{"AFML", NULL, "Line In"},
	{"AFMR", NULL, "Line In"},
165 166
};

167 168 169 170 171 172 173
static inline void twl6040_disconnect_pin(struct snd_soc_dapm_context *dapm,
					  int connected, char *pin)
{
	if (!connected)
		snd_soc_dapm_disable_pin(dapm, pin);
}

174
static int omap_abe_twl6040_init(struct snd_soc_pcm_runtime *rtd)
175
{
176
	struct snd_soc_codec *codec = rtd->codec;
177 178 179
	struct snd_soc_card *card = codec->card;
	struct snd_soc_dapm_context *dapm = &codec->dapm;
	struct omap_abe_twl6040_data *pdata = dev_get_platdata(card->dev);
180
	struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card);
181 182 183 184 185 186 187 188 189 190 191 192 193
	int hs_trim;
	int ret = 0;

	/* Disable not connected paths if not used */
	twl6040_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone");
	twl6040_disconnect_pin(dapm, pdata->has_hf, "Ext Spk");
	twl6040_disconnect_pin(dapm, pdata->has_ep, "Earphone Spk");
	twl6040_disconnect_pin(dapm, pdata->has_aux, "Line Out");
	twl6040_disconnect_pin(dapm, pdata->has_vibra, "Vinrator");
	twl6040_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic");
	twl6040_disconnect_pin(dapm, pdata->has_mainmic, "Main Handset Mic");
	twl6040_disconnect_pin(dapm, pdata->has_submic, "Sub Handset Mic");
	twl6040_disconnect_pin(dapm, pdata->has_afm, "Line In");
194

195 196 197 198 199 200 201 202
	/*
	 * Configure McPDM offset cancellation based on the HSOTRIM value from
	 * twl6040.
	 */
	hs_trim = twl6040_get_trim_value(codec, TWL6040_TRIM_HSOTRIM);
	omap_mcpdm_configure_dn_offsets(rtd, TWL6040_HSF_TRIM_LEFT(hs_trim),
					TWL6040_HSF_TRIM_RIGHT(hs_trim));

203
	/* Headset jack detection only if it is supported */
204
	if (priv->jack_detection) {
205 206 207 208
		ret = snd_soc_jack_new(codec, "Headset Jack",
					SND_JACK_HEADSET, &hs_jack);
		if (ret)
			return ret;
209

210 211
		ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
					hs_jack_pins);
212
		twl6040_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADSET);
213
	}
214 215 216 217

	return ret;
}

218
static const struct snd_soc_dapm_route dmic_audio_map[] = {
219 220
	{"DMic", NULL, "Digital Mic"},
	{"Digital Mic", NULL, "Digital Mic1 Bias"},
221 222
};

223
static int omap_abe_dmic_init(struct snd_soc_pcm_runtime *rtd)
224 225 226 227 228 229 230 231
{
	struct snd_soc_codec *codec = rtd->codec;
	struct snd_soc_dapm_context *dapm = &codec->dapm;

	return snd_soc_dapm_add_routes(dapm, dmic_audio_map,
				ARRAY_SIZE(dmic_audio_map));
}

232
/* Digital audio interface glue - connects codec <--> CPU */
233
static struct snd_soc_dai_link abe_twl6040_dai_links[] = {
234 235 236 237 238 239 240
	{
		.name = "TWL6040",
		.stream_name = "TWL6040",
		.cpu_dai_name = "omap-mcpdm",
		.codec_dai_name = "twl6040-legacy",
		.platform_name = "omap-pcm-audio",
		.codec_name = "twl6040-codec",
241 242
		.init = omap_abe_twl6040_init,
		.ops = &omap_abe_ops,
243 244 245 246 247 248 249 250
	},
	{
		.name = "DMIC",
		.stream_name = "DMIC Capture",
		.cpu_dai_name = "omap-dmic",
		.codec_dai_name = "dmic-hifi",
		.platform_name = "omap-pcm-audio",
		.codec_name = "dmic-codec",
251 252
		.init = omap_abe_dmic_init,
		.ops = &omap_abe_dmic_ops,
253
	},
254 255 256
};

/* Audio machine driver */
257
static struct snd_soc_card omap_abe_card = {
258
	.owner = THIS_MODULE,
259

260 261
	.dapm_widgets = twl6040_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(twl6040_dapm_widgets),
262 263
	.dapm_routes = audio_map,
	.num_dapm_routes = ARRAY_SIZE(audio_map),
264 265
};

266
static __devinit int omap_abe_probe(struct platform_device *pdev)
267
{
268 269
	struct omap_abe_twl6040_data *pdata = dev_get_platdata(&pdev->dev);
	struct snd_soc_card *card = &omap_abe_card;
270
	struct abe_twl6040 *priv;
271
	int num_links = 0;
272 273
	int ret;

274
	card->dev = &pdev->dev;
275

276 277 278
	if (!pdata) {
		dev_err(&pdev->dev, "Missing pdata\n");
		return -ENODEV;
279 280
	}

281 282 283 284
	priv = devm_kzalloc(&pdev->dev, sizeof(struct abe_twl6040), GFP_KERNEL);
	if (priv == NULL)
		return -ENOMEM;

285 286 287 288 289 290
	if (pdata->card_name) {
		card->name = pdata->card_name;
	} else {
		dev_err(&pdev->dev, "Card name is not provided\n");
		return -ENODEV;
	}
291

292 293 294 295 296
	priv->jack_detection = pdata->jack_detection;
	priv->mclk_freq = pdata->mclk_freq;


	if (!priv->mclk_freq) {
297 298 299 300
		dev_err(&pdev->dev, "MCLK frequency missing\n");
		return -ENODEV;
	}

301 302 303 304 305 306 307
	if (pdata->has_dmic)
		num_links = 2;
	else
		num_links = 1;

	card->dai_link = abe_twl6040_dai_links;
	card->num_links = num_links;
308

309 310
	snd_soc_card_set_drvdata(card, priv);

311
	ret = snd_soc_register_card(card);
312
	if (ret)
313 314
		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
			ret);
315 316 317 318

	return ret;
}

319
static int __devexit omap_abe_remove(struct platform_device *pdev)
320
{
321 322 323 324 325
	struct snd_soc_card *card = platform_get_drvdata(pdev);

	snd_soc_unregister_card(card);

	return 0;
326
}
327 328 329 330 331 332 333 334 335 336 337 338

static struct platform_driver omap_abe_driver = {
	.driver = {
		.name = "omap-abe-twl6040",
		.owner = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
	},
	.probe = omap_abe_probe,
	.remove = __devexit_p(omap_abe_remove),
};

module_platform_driver(omap_abe_driver);
339

340
MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>");
341
MODULE_DESCRIPTION("ALSA SoC for OMAP boards with ABE and twl6040 codec");
342
MODULE_LICENSE("GPL");
343
MODULE_ALIAS("platform:omap-abe-twl6040");