Linux Audio

Check our new training course

In-person Linux kernel drivers training

Jun 16-20, 2025
Register
Loading...
Note: File does not exist in v4.10.11.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * omap3pandora.c  --  SoC audio for Pandora Handheld Console
  4 *
  5 * Author: GraÅžvydas Ignotas <notasas@gmail.com>
  6 */
  7
  8#include <linux/clk.h>
  9#include <linux/platform_device.h>
 10#include <linux/gpio.h>
 11#include <linux/delay.h>
 12#include <linux/regulator/consumer.h>
 13#include <linux/module.h>
 14
 15#include <sound/core.h>
 16#include <sound/pcm.h>
 17#include <sound/soc.h>
 18
 19#include <asm/mach-types.h>
 20#include <linux/platform_data/asoc-ti-mcbsp.h>
 21
 22#include "omap-mcbsp.h"
 23
 24#define OMAP3_PANDORA_DAC_POWER_GPIO	118
 25#define OMAP3_PANDORA_AMP_POWER_GPIO	14
 26
 27#define PREFIX "ASoC omap3pandora: "
 28
 29static struct regulator *omap3pandora_dac_reg;
 30
 31static int omap3pandora_hw_params(struct snd_pcm_substream *substream,
 32	struct snd_pcm_hw_params *params)
 33{
 34	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
 35	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
 36	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
 37	int ret;
 38
 39	/* Set the codec system clock for DAC and ADC */
 40	ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000,
 41					    SND_SOC_CLOCK_IN);
 42	if (ret < 0) {
 43		pr_err(PREFIX "can't set codec system clock\n");
 44		return ret;
 45	}
 46
 47	/* Set McBSP clock to external */
 48	ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_EXT,
 49				     256 * params_rate(params),
 50				     SND_SOC_CLOCK_IN);
 51	if (ret < 0) {
 52		pr_err(PREFIX "can't set cpu system clock\n");
 53		return ret;
 54	}
 55
 56	ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, 8);
 57	if (ret < 0) {
 58		pr_err(PREFIX "can't set SRG clock divider\n");
 59		return ret;
 60	}
 61
 62	return 0;
 63}
 64
 65static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w,
 66	struct snd_kcontrol *k, int event)
 67{
 68	int ret;
 69
 70	/*
 71	 * The PCM1773 DAC datasheet requires 1ms delay between switching
 72	 * VCC power on/off and /PD pin high/low
 73	 */
 74	if (SND_SOC_DAPM_EVENT_ON(event)) {
 75		ret = regulator_enable(omap3pandora_dac_reg);
 76		if (ret) {
 77			dev_err(w->dapm->dev, "Failed to power DAC: %d\n", ret);
 78			return ret;
 79		}
 80		mdelay(1);
 81		gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1);
 82	} else {
 83		gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
 84		mdelay(1);
 85		regulator_disable(omap3pandora_dac_reg);
 86	}
 87
 88	return 0;
 89}
 90
 91static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w,
 92	struct snd_kcontrol *k, int event)
 93{
 94	if (SND_SOC_DAPM_EVENT_ON(event))
 95		gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 1);
 96	else
 97		gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
 98
 99	return 0;
100}
101
102/*
103 * Audio paths on Pandora board:
104 *
105 *  |O| ---> PCM DAC +-> AMP -> Headphone Jack
106 *  |M|         A    +--------> Line Out
107 *  |A| <~~clk~~+
108 *  |P| <--- TWL4030 <--------- Line In and MICs
109 */
110static const struct snd_soc_dapm_widget omap3pandora_dapm_widgets[] = {
111	SND_SOC_DAPM_DAC_E("PCM DAC", "HiFi Playback", SND_SOC_NOPM,
112			   0, 0, omap3pandora_dac_event,
113			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
114	SND_SOC_DAPM_PGA_E("Headphone Amplifier", SND_SOC_NOPM,
115			   0, 0, NULL, 0, omap3pandora_hp_event,
116			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
117	SND_SOC_DAPM_HP("Headphone Jack", NULL),
118	SND_SOC_DAPM_LINE("Line Out", NULL),
119
120	SND_SOC_DAPM_MIC("Mic (internal)", NULL),
121	SND_SOC_DAPM_MIC("Mic (external)", NULL),
122	SND_SOC_DAPM_LINE("Line In", NULL),
123};
124
125static const struct snd_soc_dapm_route omap3pandora_map[] = {
126	{"PCM DAC", NULL, "APLL Enable"},
127	{"Headphone Amplifier", NULL, "PCM DAC"},
128	{"Line Out", NULL, "PCM DAC"},
129	{"Headphone Jack", NULL, "Headphone Amplifier"},
130
131	{"AUXL", NULL, "Line In"},
132	{"AUXR", NULL, "Line In"},
133
134	{"MAINMIC", NULL, "Mic (internal)"},
135	{"Mic (internal)", NULL, "Mic Bias 1"},
136
137	{"SUBMIC", NULL, "Mic (external)"},
138	{"Mic (external)", NULL, "Mic Bias 2"},
139};
140
141static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)
142{
143	struct snd_soc_dapm_context *dapm = &rtd->card->dapm;
144
145	/* All TWL4030 output pins are floating */
146	snd_soc_dapm_nc_pin(dapm, "EARPIECE");
147	snd_soc_dapm_nc_pin(dapm, "PREDRIVEL");
148	snd_soc_dapm_nc_pin(dapm, "PREDRIVER");
149	snd_soc_dapm_nc_pin(dapm, "HSOL");
150	snd_soc_dapm_nc_pin(dapm, "HSOR");
151	snd_soc_dapm_nc_pin(dapm, "CARKITL");
152	snd_soc_dapm_nc_pin(dapm, "CARKITR");
153	snd_soc_dapm_nc_pin(dapm, "HFL");
154	snd_soc_dapm_nc_pin(dapm, "HFR");
155	snd_soc_dapm_nc_pin(dapm, "VIBRA");
156
157	return 0;
158}
159
160static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)
161{
162	struct snd_soc_dapm_context *dapm = &rtd->card->dapm;
163
164	/* Not comnnected */
165	snd_soc_dapm_nc_pin(dapm, "HSMIC");
166	snd_soc_dapm_nc_pin(dapm, "CARKITMIC");
167	snd_soc_dapm_nc_pin(dapm, "DIGIMIC0");
168	snd_soc_dapm_nc_pin(dapm, "DIGIMIC1");
169
170	return 0;
171}
172
173static const struct snd_soc_ops omap3pandora_ops = {
174	.hw_params = omap3pandora_hw_params,
175};
176
177/* Digital audio interface glue - connects codec <--> CPU */
178SND_SOC_DAILINK_DEFS(out,
179	DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.2")),
180	DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-hifi")),
181	DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.2")));
182
183SND_SOC_DAILINK_DEFS(in,
184	DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.4")),
185	DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec", "twl4030-hifi")),
186	DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.4")));
187
188static struct snd_soc_dai_link omap3pandora_dai[] = {
189	{
190		.name = "PCM1773",
191		.stream_name = "HiFi Out",
192		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
193			   SND_SOC_DAIFMT_CBS_CFS,
194		.ops = &omap3pandora_ops,
195		.init = omap3pandora_out_init,
196		SND_SOC_DAILINK_REG(out),
197	}, {
198		.name = "TWL4030",
199		.stream_name = "Line/Mic In",
200		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
201			   SND_SOC_DAIFMT_CBS_CFS,
202		.ops = &omap3pandora_ops,
203		.init = omap3pandora_in_init,
204		SND_SOC_DAILINK_REG(in),
205	}
206};
207
208/* SoC card */
209static struct snd_soc_card snd_soc_card_omap3pandora = {
210	.name = "omap3pandora",
211	.owner = THIS_MODULE,
212	.dai_link = omap3pandora_dai,
213	.num_links = ARRAY_SIZE(omap3pandora_dai),
214
215	.dapm_widgets = omap3pandora_dapm_widgets,
216	.num_dapm_widgets = ARRAY_SIZE(omap3pandora_dapm_widgets),
217	.dapm_routes = omap3pandora_map,
218	.num_dapm_routes = ARRAY_SIZE(omap3pandora_map),
219};
220
221static struct platform_device *omap3pandora_snd_device;
222
223static int __init omap3pandora_soc_init(void)
224{
225	int ret;
226
227	if (!machine_is_omap3_pandora())
228		return -ENODEV;
229
230	pr_info("OMAP3 Pandora SoC init\n");
231
232	ret = gpio_request(OMAP3_PANDORA_DAC_POWER_GPIO, "dac_power");
233	if (ret) {
234		pr_err(PREFIX "Failed to get DAC power GPIO\n");
235		return ret;
236	}
237
238	ret = gpio_direction_output(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
239	if (ret) {
240		pr_err(PREFIX "Failed to set DAC power GPIO direction\n");
241		goto fail0;
242	}
243
244	ret = gpio_request(OMAP3_PANDORA_AMP_POWER_GPIO, "amp_power");
245	if (ret) {
246		pr_err(PREFIX "Failed to get amp power GPIO\n");
247		goto fail0;
248	}
249
250	ret = gpio_direction_output(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
251	if (ret) {
252		pr_err(PREFIX "Failed to set amp power GPIO direction\n");
253		goto fail1;
254	}
255
256	omap3pandora_snd_device = platform_device_alloc("soc-audio", -1);
257	if (omap3pandora_snd_device == NULL) {
258		pr_err(PREFIX "Platform device allocation failed\n");
259		ret = -ENOMEM;
260		goto fail1;
261	}
262
263	platform_set_drvdata(omap3pandora_snd_device, &snd_soc_card_omap3pandora);
264
265	ret = platform_device_add(omap3pandora_snd_device);
266	if (ret) {
267		pr_err(PREFIX "Unable to add platform device\n");
268		goto fail2;
269	}
270
271	omap3pandora_dac_reg = regulator_get(&omap3pandora_snd_device->dev, "vcc");
272	if (IS_ERR(omap3pandora_dac_reg)) {
273		pr_err(PREFIX "Failed to get DAC regulator from %s: %ld\n",
274			dev_name(&omap3pandora_snd_device->dev),
275			PTR_ERR(omap3pandora_dac_reg));
276		ret = PTR_ERR(omap3pandora_dac_reg);
277		goto fail3;
278	}
279
280	return 0;
281
282fail3:
283	platform_device_del(omap3pandora_snd_device);
284fail2:
285	platform_device_put(omap3pandora_snd_device);
286fail1:
287	gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
288fail0:
289	gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
290	return ret;
291}
292module_init(omap3pandora_soc_init);
293
294static void __exit omap3pandora_soc_exit(void)
295{
296	regulator_put(omap3pandora_dac_reg);
297	platform_device_unregister(omap3pandora_snd_device);
298	gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
299	gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
300}
301module_exit(omap3pandora_soc_exit);
302
303MODULE_AUTHOR("Grazvydas Ignotas <notasas@gmail.com>");
304MODULE_DESCRIPTION("ALSA SoC OMAP3 Pandora");
305MODULE_LICENSE("GPL");