| Liam Girdwood | 6e24dd9 | 2006-10-12 14:33:45 +0200 | [diff] [blame] | 1 | /* | 
 | 2 |  * poodle.c  --  SoC audio for Poodle | 
 | 3 |  * | 
 | 4 |  * Copyright 2005 Wolfson Microelectronics PLC. | 
 | 5 |  * Copyright 2005 Openedhand Ltd. | 
 | 6 |  * | 
 | 7 |  * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> | 
 | 8 |  *          Richard Purdie <richard@openedhand.com> | 
 | 9 |  * | 
 | 10 |  *  This program is free software; you can redistribute  it and/or modify it | 
 | 11 |  *  under  the terms of  the GNU General  Public License as published by the | 
 | 12 |  *  Free Software Foundation;  either version 2 of the  License, or (at your | 
 | 13 |  *  option) any later version. | 
 | 14 |  * | 
 | 15 |  */ | 
 | 16 |  | 
 | 17 | #include <linux/module.h> | 
 | 18 | #include <linux/moduleparam.h> | 
 | 19 | #include <linux/timer.h> | 
 | 20 | #include <linux/interrupt.h> | 
 | 21 | #include <linux/platform_device.h> | 
| Liam Girdwood | 6e24dd9 | 2006-10-12 14:33:45 +0200 | [diff] [blame] | 22 | #include <sound/core.h> | 
 | 23 | #include <sound/pcm.h> | 
 | 24 | #include <sound/soc.h> | 
 | 25 | #include <sound/soc-dapm.h> | 
 | 26 |  | 
 | 27 | #include <asm/mach-types.h> | 
 | 28 | #include <asm/hardware/locomo.h> | 
 | 29 | #include <asm/arch/pxa-regs.h> | 
 | 30 | #include <asm/arch/hardware.h> | 
 | 31 | #include <asm/arch/poodle.h> | 
 | 32 | #include <asm/arch/audio.h> | 
 | 33 |  | 
 | 34 | #include "../codecs/wm8731.h" | 
 | 35 | #include "pxa2xx-pcm.h" | 
| Liam Girdwood | 73f40dc | 2007-02-02 17:23:42 +0100 | [diff] [blame] | 36 | #include "pxa2xx-i2s.h" | 
| Liam Girdwood | 6e24dd9 | 2006-10-12 14:33:45 +0200 | [diff] [blame] | 37 |  | 
 | 38 | #define POODLE_HP        1 | 
 | 39 | #define POODLE_HP_OFF    0 | 
 | 40 | #define POODLE_SPK_ON    1 | 
 | 41 | #define POODLE_SPK_OFF   0 | 
 | 42 |  | 
 | 43 |  /* audio clock in Hz - rounded from 12.235MHz */ | 
 | 44 | #define POODLE_AUDIO_CLOCK 12288000 | 
 | 45 |  | 
 | 46 | static int poodle_jack_func; | 
 | 47 | static int poodle_spk_func; | 
 | 48 |  | 
 | 49 | static void poodle_ext_control(struct snd_soc_codec *codec) | 
 | 50 | { | 
 | 51 | 	int spk = 0; | 
 | 52 |  | 
 | 53 | 	/* set up jack connection */ | 
 | 54 | 	if (poodle_jack_func == POODLE_HP) { | 
 | 55 | 		/* set = unmute headphone */ | 
 | 56 | 		locomo_gpio_write(&poodle_locomo_device.dev, | 
 | 57 | 			POODLE_LOCOMO_GPIO_MUTE_L, 1); | 
 | 58 | 		locomo_gpio_write(&poodle_locomo_device.dev, | 
 | 59 | 			POODLE_LOCOMO_GPIO_MUTE_R, 1); | 
 | 60 | 		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); | 
 | 61 | 	} else { | 
 | 62 | 		locomo_gpio_write(&poodle_locomo_device.dev, | 
 | 63 | 			POODLE_LOCOMO_GPIO_MUTE_L, 0); | 
 | 64 | 		locomo_gpio_write(&poodle_locomo_device.dev, | 
 | 65 | 			POODLE_LOCOMO_GPIO_MUTE_R, 0); | 
 | 66 | 		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); | 
 | 67 | 	} | 
 | 68 |  | 
 | 69 | 	if (poodle_spk_func == POODLE_SPK_ON) | 
 | 70 | 		spk = 1; | 
 | 71 |  | 
 | 72 | 	/* set the enpoints to their new connetion states */ | 
 | 73 | 	snd_soc_dapm_set_endpoint(codec, "Ext Spk", spk); | 
 | 74 |  | 
 | 75 | 	/* signal a DAPM event */ | 
 | 76 | 	snd_soc_dapm_sync_endpoints(codec); | 
 | 77 | } | 
 | 78 |  | 
 | 79 | static int poodle_startup(struct snd_pcm_substream *substream) | 
 | 80 | { | 
 | 81 | 	struct snd_soc_pcm_runtime *rtd = substream->private_data; | 
 | 82 | 	struct snd_soc_codec *codec = rtd->socdev->codec; | 
 | 83 |  | 
 | 84 | 	/* check the jack status at stream startup */ | 
 | 85 | 	poodle_ext_control(codec); | 
 | 86 | 	return 0; | 
 | 87 | } | 
 | 88 |  | 
 | 89 | /* we need to unmute the HP at shutdown as the mute burns power on poodle */ | 
 | 90 | static int poodle_shutdown(struct snd_pcm_substream *substream) | 
 | 91 | { | 
 | 92 | 	struct snd_soc_pcm_runtime *rtd = substream->private_data; | 
 | 93 | 	struct snd_soc_codec *codec = rtd->socdev->codec; | 
 | 94 |  | 
 | 95 | 	/* set = unmute headphone */ | 
 | 96 | 	locomo_gpio_write(&poodle_locomo_device.dev, | 
 | 97 | 		POODLE_LOCOMO_GPIO_MUTE_L, 1); | 
 | 98 | 	locomo_gpio_write(&poodle_locomo_device.dev, | 
 | 99 | 		POODLE_LOCOMO_GPIO_MUTE_R, 1); | 
 | 100 | 	return 0; | 
 | 101 | } | 
 | 102 |  | 
| Liam Girdwood | 73f40dc | 2007-02-02 17:23:42 +0100 | [diff] [blame] | 103 | static int poodle_hw_params(struct snd_pcm_substream *substream, | 
 | 104 | 	struct snd_pcm_hw_params *params) | 
 | 105 | { | 
 | 106 | 	struct snd_soc_pcm_runtime *rtd = substream->private_data; | 
 | 107 | 	struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; | 
 | 108 | 	struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; | 
 | 109 | 	unsigned int clk = 0; | 
 | 110 | 	int ret = 0; | 
 | 111 |  | 
 | 112 | 	switch (params_rate(params)) { | 
 | 113 | 	case 8000: | 
 | 114 | 	case 16000: | 
 | 115 | 	case 48000: | 
 | 116 | 	case 96000: | 
 | 117 | 		clk = 12288000; | 
 | 118 | 		break; | 
 | 119 | 	case 11025: | 
 | 120 | 	case 22050: | 
 | 121 | 	case 44100: | 
 | 122 | 		clk = 11289600; | 
 | 123 | 		break; | 
 | 124 | 	} | 
 | 125 |  | 
 | 126 | 	/* set codec DAI configuration */ | 
 | 127 | 	ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | | 
 | 128 | 		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | 
 | 129 | 	if (ret < 0) | 
 | 130 | 		return ret; | 
 | 131 |  | 
 | 132 | 	/* set cpu DAI configuration */ | 
 | 133 | 	ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | | 
 | 134 | 		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | 
 | 135 | 	if (ret < 0) | 
 | 136 | 		return ret; | 
 | 137 |  | 
 | 138 | 	/* set the codec system clock for DAC and ADC */ | 
 | 139 | 	ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8731_SYSCLK, clk, | 
 | 140 | 		SND_SOC_CLOCK_IN); | 
 | 141 | 	if (ret < 0) | 
 | 142 | 		return ret; | 
 | 143 |  | 
 | 144 | 	/* set the I2S system clock as input (unused) */ | 
 | 145 | 	ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, | 
 | 146 | 		SND_SOC_CLOCK_IN); | 
 | 147 | 	if (ret < 0) | 
 | 148 | 		return ret; | 
 | 149 |  | 
 | 150 | 	return 0; | 
 | 151 | } | 
 | 152 |  | 
| Liam Girdwood | 6e24dd9 | 2006-10-12 14:33:45 +0200 | [diff] [blame] | 153 | static struct snd_soc_ops poodle_ops = { | 
 | 154 | 	.startup = poodle_startup, | 
| Liam Girdwood | 73f40dc | 2007-02-02 17:23:42 +0100 | [diff] [blame] | 155 | 	.hw_params = poodle_hw_params, | 
| Liam Girdwood | 6e24dd9 | 2006-10-12 14:33:45 +0200 | [diff] [blame] | 156 | 	.shutdown = poodle_shutdown, | 
 | 157 | }; | 
 | 158 |  | 
 | 159 | static int poodle_get_jack(struct snd_kcontrol *kcontrol, | 
 | 160 | 	struct snd_ctl_elem_value *ucontrol) | 
 | 161 | { | 
 | 162 | 	ucontrol->value.integer.value[0] = poodle_jack_func; | 
 | 163 | 	return 0; | 
 | 164 | } | 
 | 165 |  | 
 | 166 | static int poodle_set_jack(struct snd_kcontrol *kcontrol, | 
 | 167 | 	struct snd_ctl_elem_value *ucontrol) | 
 | 168 | { | 
 | 169 | 	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol); | 
 | 170 |  | 
 | 171 | 	if (poodle_jack_func == ucontrol->value.integer.value[0]) | 
 | 172 | 		return 0; | 
 | 173 |  | 
 | 174 | 	poodle_jack_func = ucontrol->value.integer.value[0]; | 
 | 175 | 	poodle_ext_control(codec); | 
 | 176 | 	return 1; | 
 | 177 | } | 
 | 178 |  | 
 | 179 | static int poodle_get_spk(struct snd_kcontrol *kcontrol, | 
 | 180 | 	struct snd_ctl_elem_value *ucontrol) | 
 | 181 | { | 
 | 182 | 	ucontrol->value.integer.value[0] = poodle_spk_func; | 
 | 183 | 	return 0; | 
 | 184 | } | 
 | 185 |  | 
 | 186 | static int poodle_set_spk(struct snd_kcontrol *kcontrol, | 
 | 187 | 	struct snd_ctl_elem_value *ucontrol) | 
 | 188 | { | 
 | 189 | 	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol); | 
 | 190 |  | 
 | 191 | 	if (poodle_spk_func == ucontrol->value.integer.value[0]) | 
 | 192 | 		return 0; | 
 | 193 |  | 
 | 194 | 	poodle_spk_func = ucontrol->value.integer.value[0]; | 
 | 195 | 	poodle_ext_control(codec); | 
 | 196 | 	return 1; | 
 | 197 | } | 
 | 198 |  | 
 | 199 | static int poodle_amp_event(struct snd_soc_dapm_widget *w, int event) | 
 | 200 | { | 
 | 201 | 	if (SND_SOC_DAPM_EVENT_ON(event)) | 
 | 202 | 		locomo_gpio_write(&poodle_locomo_device.dev, | 
 | 203 | 			POODLE_LOCOMO_GPIO_AMP_ON, 0); | 
 | 204 | 	else | 
 | 205 | 		locomo_gpio_write(&poodle_locomo_device.dev, | 
 | 206 | 			POODLE_LOCOMO_GPIO_AMP_ON, 1); | 
 | 207 |  | 
 | 208 | 	return 0; | 
 | 209 | } | 
 | 210 |  | 
 | 211 | /* poodle machine dapm widgets */ | 
 | 212 | static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { | 
 | 213 | SND_SOC_DAPM_HP("Headphone Jack", NULL), | 
 | 214 | SND_SOC_DAPM_SPK("Ext Spk", poodle_amp_event), | 
 | 215 | }; | 
 | 216 |  | 
 | 217 | /* Corgi machine audio_mapnections to the codec pins */ | 
 | 218 | static const char *audio_map[][3] = { | 
 | 219 |  | 
 | 220 | 	/* headphone connected to LHPOUT1, RHPOUT1 */ | 
 | 221 | 	{"Headphone Jack", NULL, "LHPOUT"}, | 
 | 222 | 	{"Headphone Jack", NULL, "RHPOUT"}, | 
 | 223 |  | 
 | 224 | 	/* speaker connected to LOUT, ROUT */ | 
 | 225 | 	{"Ext Spk", NULL, "ROUT"}, | 
 | 226 | 	{"Ext Spk", NULL, "LOUT"}, | 
 | 227 |  | 
 | 228 | 	{NULL, NULL, NULL}, | 
 | 229 | }; | 
 | 230 |  | 
 | 231 | static const char *jack_function[] = {"Off", "Headphone"}; | 
 | 232 | static const char *spk_function[] = {"Off", "On"}; | 
 | 233 | static const struct soc_enum poodle_enum[] = { | 
 | 234 | 	SOC_ENUM_SINGLE_EXT(2, jack_function), | 
 | 235 | 	SOC_ENUM_SINGLE_EXT(2, spk_function), | 
 | 236 | }; | 
 | 237 |  | 
 | 238 | static const snd_kcontrol_new_t wm8731_poodle_controls[] = { | 
 | 239 | 	SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack, | 
 | 240 | 		poodle_set_jack), | 
 | 241 | 	SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk, | 
 | 242 | 		poodle_set_spk), | 
 | 243 | }; | 
 | 244 |  | 
 | 245 | /* | 
 | 246 |  * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device | 
 | 247 |  */ | 
 | 248 | static int poodle_wm8731_init(struct snd_soc_codec *codec) | 
 | 249 | { | 
 | 250 | 	int i, err; | 
 | 251 |  | 
 | 252 | 	snd_soc_dapm_set_endpoint(codec, "LLINEIN", 0); | 
 | 253 | 	snd_soc_dapm_set_endpoint(codec, "RLINEIN", 0); | 
 | 254 | 	snd_soc_dapm_set_endpoint(codec, "MICIN", 1); | 
 | 255 |  | 
 | 256 | 	/* Add poodle specific controls */ | 
 | 257 | 	for (i = 0; i < ARRAY_SIZE(wm8731_poodle_controls); i++) { | 
 | 258 | 		err = snd_ctl_add(codec->card, | 
 | 259 | 			snd_soc_cnew(&wm8731_poodle_controls[i],codec, NULL)); | 
 | 260 | 		if (err < 0) | 
 | 261 | 			return err; | 
 | 262 | 	} | 
 | 263 |  | 
 | 264 | 	/* Add poodle specific widgets */ | 
 | 265 | 	for (i = 0; i < ARRAY_SIZE(wm8731_dapm_widgets); i++) { | 
 | 266 | 		snd_soc_dapm_new_control(codec, &wm8731_dapm_widgets[i]); | 
 | 267 | 	} | 
 | 268 |  | 
 | 269 | 	/* Set up poodle specific audio path audio_map */ | 
 | 270 | 	for (i = 0; audio_map[i][0] != NULL; i++) { | 
 | 271 | 		snd_soc_dapm_connect_input(codec, audio_map[i][0], | 
 | 272 | 			audio_map[i][1], audio_map[i][2]); | 
 | 273 | 	} | 
 | 274 |  | 
 | 275 | 	snd_soc_dapm_sync_endpoints(codec); | 
 | 276 | 	return 0; | 
 | 277 | } | 
 | 278 |  | 
| Liam Girdwood | 6e24dd9 | 2006-10-12 14:33:45 +0200 | [diff] [blame] | 279 | /* poodle digital audio interface glue - connects codec <--> CPU */ | 
 | 280 | static struct snd_soc_dai_link poodle_dai = { | 
 | 281 | 	.name = "WM8731", | 
 | 282 | 	.stream_name = "WM8731", | 
 | 283 | 	.cpu_dai = &pxa_i2s_dai, | 
 | 284 | 	.codec_dai = &wm8731_dai, | 
 | 285 | 	.init = poodle_wm8731_init, | 
| Liam Girdwood | 73f40dc | 2007-02-02 17:23:42 +0100 | [diff] [blame] | 286 | 	.ops = &poodle_ops, | 
| Liam Girdwood | 6e24dd9 | 2006-10-12 14:33:45 +0200 | [diff] [blame] | 287 | }; | 
 | 288 |  | 
 | 289 | /* poodle audio machine driver */ | 
 | 290 | static struct snd_soc_machine snd_soc_machine_poodle = { | 
 | 291 | 	.name = "Poodle", | 
 | 292 | 	.dai_link = &poodle_dai, | 
 | 293 | 	.num_links = 1, | 
| Liam Girdwood | 6e24dd9 | 2006-10-12 14:33:45 +0200 | [diff] [blame] | 294 | }; | 
 | 295 |  | 
 | 296 | /* poodle audio private data */ | 
 | 297 | static struct wm8731_setup_data poodle_wm8731_setup = { | 
 | 298 | 	.i2c_address = 0x1b, | 
 | 299 | }; | 
 | 300 |  | 
 | 301 | /* poodle audio subsystem */ | 
 | 302 | static struct snd_soc_device poodle_snd_devdata = { | 
 | 303 | 	.machine = &snd_soc_machine_poodle, | 
 | 304 | 	.platform = &pxa2xx_soc_platform, | 
 | 305 | 	.codec_dev = &soc_codec_dev_wm8731, | 
 | 306 | 	.codec_data = &poodle_wm8731_setup, | 
 | 307 | }; | 
 | 308 |  | 
 | 309 | static struct platform_device *poodle_snd_device; | 
 | 310 |  | 
 | 311 | static int __init poodle_init(void) | 
 | 312 | { | 
 | 313 | 	int ret; | 
 | 314 |  | 
 | 315 | 	if (!machine_is_poodle()) | 
 | 316 | 		return -ENODEV; | 
 | 317 |  | 
 | 318 | 	locomo_gpio_set_dir(&poodle_locomo_device.dev, | 
 | 319 | 		POODLE_LOCOMO_GPIO_AMP_ON, 0); | 
 | 320 | 	/* should we mute HP at startup - burning power ?*/ | 
 | 321 | 	locomo_gpio_set_dir(&poodle_locomo_device.dev, | 
 | 322 | 		POODLE_LOCOMO_GPIO_MUTE_L, 0); | 
 | 323 | 	locomo_gpio_set_dir(&poodle_locomo_device.dev, | 
 | 324 | 		POODLE_LOCOMO_GPIO_MUTE_R, 0); | 
 | 325 |  | 
 | 326 | 	poodle_snd_device = platform_device_alloc("soc-audio", -1); | 
 | 327 | 	if (!poodle_snd_device) | 
 | 328 | 		return -ENOMEM; | 
 | 329 |  | 
 | 330 | 	platform_set_drvdata(poodle_snd_device, &poodle_snd_devdata); | 
 | 331 | 	poodle_snd_devdata.dev = &poodle_snd_device->dev; | 
 | 332 | 	ret = platform_device_add(poodle_snd_device); | 
 | 333 |  | 
 | 334 | 	if (ret) | 
 | 335 | 		platform_device_put(poodle_snd_device); | 
 | 336 |  | 
 | 337 | 	return ret; | 
 | 338 | } | 
 | 339 |  | 
 | 340 | static void __exit poodle_exit(void) | 
 | 341 | { | 
 | 342 | 	platform_device_unregister(poodle_snd_device); | 
 | 343 | } | 
 | 344 |  | 
 | 345 | module_init(poodle_init); | 
 | 346 | module_exit(poodle_exit); | 
 | 347 |  | 
 | 348 | /* Module information */ | 
 | 349 | MODULE_AUTHOR("Richard Purdie"); | 
 | 350 | MODULE_DESCRIPTION("ALSA SoC Poodle"); | 
 | 351 | MODULE_LICENSE("GPL"); |