1/* 2 * corgi.c -- SoC audio for Corgi 3 * 4 * Copyright 2005 Wolfson Microelectronics PLC. 5 * Copyright 2005 Openedhand Ltd. 6 * 7 * Authors: Liam Girdwood <lrg@slimlogic.co.uk> 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#include <linux/module.h> 17#include <linux/moduleparam.h> 18#include <linux/timer.h> 19#include <linux/i2c.h> 20#include <linux/interrupt.h> 21#include <linux/platform_device.h> 22#include <linux/gpio.h> 23#include <sound/core.h> 24#include <sound/pcm.h> 25#include <sound/soc.h> 26#include <sound/soc-dapm.h> 27 28#include <asm/mach-types.h> 29#include <mach/corgi.h> 30#include <mach/audio.h> 31 32#include "../codecs/wm8731.h" 33#include "pxa2xx-pcm.h" 34#include "pxa2xx-i2s.h" 35 36#define CORGI_HP 0 37#define CORGI_MIC 1 38#define CORGI_LINE 2 39#define CORGI_HEADSET 3 40#define CORGI_HP_OFF 4 41#define CORGI_SPK_ON 0 42#define CORGI_SPK_OFF 1 43 44 /* audio clock in Hz - rounded from 12.235MHz */ 45#define CORGI_AUDIO_CLOCK 12288000 46 47static int corgi_jack_func; 48static int corgi_spk_func; 49 50static void corgi_ext_control(struct snd_soc_codec *codec) 51{ 52 /* set up jack connection */ 53 switch (corgi_jack_func) { 54 case CORGI_HP: 55 /* set = unmute headphone */ 56 gpio_set_value(CORGI_GPIO_MUTE_L, 1); 57 gpio_set_value(CORGI_GPIO_MUTE_R, 1); 58 snd_soc_dapm_disable_pin(codec, "Mic Jack"); 59 snd_soc_dapm_disable_pin(codec, "Line Jack"); 60 snd_soc_dapm_enable_pin(codec, "Headphone Jack"); 61 snd_soc_dapm_disable_pin(codec, "Headset Jack"); 62 break; 63 case CORGI_MIC: 64 /* reset = mute headphone */ 65 gpio_set_value(CORGI_GPIO_MUTE_L, 0); 66 gpio_set_value(CORGI_GPIO_MUTE_R, 0); 67 snd_soc_dapm_enable_pin(codec, "Mic Jack"); 68 snd_soc_dapm_disable_pin(codec, "Line Jack"); 69 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); 70 snd_soc_dapm_disable_pin(codec, "Headset Jack"); 71 break; 72 case CORGI_LINE: 73 gpio_set_value(CORGI_GPIO_MUTE_L, 0); 74 gpio_set_value(CORGI_GPIO_MUTE_R, 0); 75 snd_soc_dapm_disable_pin(codec, "Mic Jack"); 76 snd_soc_dapm_enable_pin(codec, "Line Jack"); 77 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); 78 snd_soc_dapm_disable_pin(codec, "Headset Jack"); 79 break; 80 case CORGI_HEADSET: 81 gpio_set_value(CORGI_GPIO_MUTE_L, 0); 82 gpio_set_value(CORGI_GPIO_MUTE_R, 1); 83 snd_soc_dapm_enable_pin(codec, "Mic Jack"); 84 snd_soc_dapm_disable_pin(codec, "Line Jack"); 85 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); 86 snd_soc_dapm_enable_pin(codec, "Headset Jack"); 87 break; 88 } 89 90 if (corgi_spk_func == CORGI_SPK_ON) 91 snd_soc_dapm_enable_pin(codec, "Ext Spk"); 92 else 93 snd_soc_dapm_disable_pin(codec, "Ext Spk"); 94 95 /* signal a DAPM event */ 96 snd_soc_dapm_sync(codec); 97} 98 99static int corgi_startup(struct snd_pcm_substream *substream) 100{ 101 struct snd_soc_pcm_runtime *rtd = substream->private_data; 102 struct snd_soc_codec *codec = rtd->socdev->card->codec; 103 104 /* check the jack status at stream startup */ 105 corgi_ext_control(codec); 106 return 0; 107} 108 109/* we need to unmute the HP at shutdown as the mute burns power on corgi */ 110static void corgi_shutdown(struct snd_pcm_substream *substream) 111{ 112 /* set = unmute headphone */ 113 gpio_set_value(CORGI_GPIO_MUTE_L, 1); 114 gpio_set_value(CORGI_GPIO_MUTE_R, 1); 115} 116 117static int corgi_hw_params(struct snd_pcm_substream *substream, 118 struct snd_pcm_hw_params *params) 119{ 120 struct snd_soc_pcm_runtime *rtd = substream->private_data; 121 struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; 122 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; 123 unsigned int clk = 0; 124 int ret = 0; 125 126 switch (params_rate(params)) { 127 case 8000: 128 case 16000: 129 case 48000: 130 case 96000: 131 clk = 12288000; 132 break; 133 case 11025: 134 case 22050: 135 case 44100: 136 clk = 11289600; 137 break; 138 } 139 140 /* set codec DAI configuration */ 141 ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | 142 SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); 143 if (ret < 0) 144 return ret; 145 146 /* set cpu DAI configuration */ 147 ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | 148 SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); 149 if (ret < 0) 150 return ret; 151 152 /* set the codec system clock for DAC and ADC */ 153 ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, clk, 154 SND_SOC_CLOCK_IN); 155 if (ret < 0) 156 return ret; 157 158 /* set the I2S system clock as input (unused) */ 159 ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, 160 SND_SOC_CLOCK_IN); 161 if (ret < 0) 162 return ret; 163 164 return 0; 165} 166 167static struct snd_soc_ops corgi_ops = { 168 .startup = corgi_startup, 169 .hw_params = corgi_hw_params, 170 .shutdown = corgi_shutdown, 171}; 172 173static int corgi_get_jack(struct snd_kcontrol *kcontrol, 174 struct snd_ctl_elem_value *ucontrol) 175{ 176 ucontrol->value.integer.value[0] = corgi_jack_func; 177 return 0; 178} 179 180static int corgi_set_jack(struct snd_kcontrol *kcontrol, 181 struct snd_ctl_elem_value *ucontrol) 182{ 183 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 184 185 if (corgi_jack_func == ucontrol->value.integer.value[0]) 186 return 0; 187 188 corgi_jack_func = ucontrol->value.integer.value[0]; 189 corgi_ext_control(codec); 190 return 1; 191} 192 193static int corgi_get_spk(struct snd_kcontrol *kcontrol, 194 struct snd_ctl_elem_value *ucontrol) 195{ 196 ucontrol->value.integer.value[0] = corgi_spk_func; 197 return 0; 198} 199 200static int corgi_set_spk(struct snd_kcontrol *kcontrol, 201 struct snd_ctl_elem_value *ucontrol) 202{ 203 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 204 205 if (corgi_spk_func == ucontrol->value.integer.value[0]) 206 return 0; 207 208 corgi_spk_func = ucontrol->value.integer.value[0]; 209 corgi_ext_control(codec); 210 return 1; 211} 212 213static int corgi_amp_event(struct snd_soc_dapm_widget *w, 214 struct snd_kcontrol *k, int event) 215{ 216 gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event)); 217 return 0; 218} 219 220static int corgi_mic_event(struct snd_soc_dapm_widget *w, 221 struct snd_kcontrol *k, int event) 222{ 223 gpio_set_value(CORGI_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event)); 224 return 0; 225} 226 227/* corgi machine dapm widgets */ 228static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { 229SND_SOC_DAPM_HP("Headphone Jack", NULL), 230SND_SOC_DAPM_MIC("Mic Jack", corgi_mic_event), 231SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event), 232SND_SOC_DAPM_LINE("Line Jack", NULL), 233SND_SOC_DAPM_HP("Headset Jack", NULL), 234}; 235 236/* Corgi machine audio map (connections to the codec pins) */ 237static const struct snd_soc_dapm_route audio_map[] = { 238 239 /* headset Jack - in = micin, out = LHPOUT*/ 240 {"Headset Jack", NULL, "LHPOUT"}, 241 242 /* headphone connected to LHPOUT1, RHPOUT1 */ 243 {"Headphone Jack", NULL, "LHPOUT"}, 244 {"Headphone Jack", NULL, "RHPOUT"}, 245 246 /* speaker connected to LOUT, ROUT */ 247 {"Ext Spk", NULL, "ROUT"}, 248 {"Ext Spk", NULL, "LOUT"}, 249 250 /* mic is connected to MICIN (via right channel of headphone jack) */ 251 {"MICIN", NULL, "Mic Jack"}, 252 253 /* Same as the above but no mic bias for line signals */ 254 {"MICIN", NULL, "Line Jack"}, 255}; 256 257static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset", 258 "Off"}; 259static const char *spk_function[] = {"On", "Off"}; 260static const struct soc_enum corgi_enum[] = { 261 SOC_ENUM_SINGLE_EXT(5, jack_function), 262 SOC_ENUM_SINGLE_EXT(2, spk_function), 263}; 264 265static const struct snd_kcontrol_new wm8731_corgi_controls[] = { 266 SOC_ENUM_EXT("Jack Function", corgi_enum[0], corgi_get_jack, 267 corgi_set_jack), 268 SOC_ENUM_EXT("Speaker Function", corgi_enum[1], corgi_get_spk, 269 corgi_set_spk), 270}; 271 272/* 273 * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device 274 */ 275static int corgi_wm8731_init(struct snd_soc_codec *codec) 276{ 277 int err; 278 279 snd_soc_dapm_nc_pin(codec, "LLINEIN"); 280 snd_soc_dapm_nc_pin(codec, "RLINEIN"); 281 282 /* Add corgi specific controls */ 283 err = snd_soc_add_controls(codec, wm8731_corgi_controls, 284 ARRAY_SIZE(wm8731_corgi_controls)); 285 if (err < 0) 286 return err; 287 288 /* Add corgi specific widgets */ 289 snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets, 290 ARRAY_SIZE(wm8731_dapm_widgets)); 291 292 /* Set up corgi specific audio path audio_map */ 293 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); 294 295 snd_soc_dapm_sync(codec); 296 return 0; 297} 298 299/* corgi digital audio interface glue - connects codec <--> CPU */ 300static struct snd_soc_dai_link corgi_dai = { 301 .name = "WM8731", 302 .stream_name = "WM8731", 303 .cpu_dai = &pxa_i2s_dai, 304 .codec_dai = &wm8731_dai, 305 .init = corgi_wm8731_init, 306 .ops = &corgi_ops, 307}; 308 309/* corgi audio machine driver */ 310static struct snd_soc_card snd_soc_corgi = { 311 .name = "Corgi", 312 .platform = &pxa2xx_soc_platform, 313 .dai_link = &corgi_dai, 314 .num_links = 1, 315}; 316 317/* corgi audio subsystem */ 318static struct snd_soc_device corgi_snd_devdata = { 319 .card = &snd_soc_corgi, 320 .codec_dev = &soc_codec_dev_wm8731, 321}; 322 323static struct platform_device *corgi_snd_device; 324 325static int __init corgi_init(void) 326{ 327 int ret; 328 329 if (!(machine_is_corgi() || machine_is_shepherd() || 330 machine_is_husky())) 331 return -ENODEV; 332 333 corgi_snd_device = platform_device_alloc("soc-audio", -1); 334 if (!corgi_snd_device) 335 return -ENOMEM; 336 337 platform_set_drvdata(corgi_snd_device, &corgi_snd_devdata); 338 corgi_snd_devdata.dev = &corgi_snd_device->dev; 339 ret = platform_device_add(corgi_snd_device); 340 341 if (ret) 342 platform_device_put(corgi_snd_device); 343 344 return ret; 345} 346 347static void __exit corgi_exit(void) 348{ 349 platform_device_unregister(corgi_snd_device); 350} 351 352module_init(corgi_init); 353module_exit(corgi_exit); 354 355/* Module information */ 356MODULE_AUTHOR("Richard Purdie"); 357MODULE_DESCRIPTION("ALSA SoC Corgi"); 358MODULE_LICENSE("GPL"); 359