1/* 2 * tosa.c -- SoC audio for Tosa 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 * GPIO's 16 * 1 - Jack Insertion 17 * 5 - Hookswitch (headset answer/hang up switch) 18 * 19 */ 20 21#include <linux/module.h> 22#include <linux/moduleparam.h> 23#include <linux/device.h> 24#include <linux/gpio.h> 25 26#include <sound/core.h> 27#include <sound/pcm.h> 28#include <sound/soc.h> 29#include <sound/soc-dapm.h> 30 31#include <asm/mach-types.h> 32#include <mach/tosa.h> 33#include <mach/audio.h> 34 35#include "../codecs/wm9712.h" 36#include "pxa2xx-pcm.h" 37#include "pxa2xx-ac97.h" 38 39static struct snd_soc_card tosa; 40 41#define TOSA_HP 0 42#define TOSA_MIC_INT 1 43#define TOSA_HEADSET 2 44#define TOSA_HP_OFF 3 45#define TOSA_SPK_ON 0 46#define TOSA_SPK_OFF 1 47 48static int tosa_jack_func; 49static int tosa_spk_func; 50 51static void tosa_ext_control(struct snd_soc_codec *codec) 52{ 53 /* set up jack connection */ 54 switch (tosa_jack_func) { 55 case TOSA_HP: 56 snd_soc_dapm_disable_pin(codec, "Mic (Internal)"); 57 snd_soc_dapm_enable_pin(codec, "Headphone Jack"); 58 snd_soc_dapm_disable_pin(codec, "Headset Jack"); 59 break; 60 case TOSA_MIC_INT: 61 snd_soc_dapm_enable_pin(codec, "Mic (Internal)"); 62 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); 63 snd_soc_dapm_disable_pin(codec, "Headset Jack"); 64 break; 65 case TOSA_HEADSET: 66 snd_soc_dapm_disable_pin(codec, "Mic (Internal)"); 67 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); 68 snd_soc_dapm_enable_pin(codec, "Headset Jack"); 69 break; 70 } 71 72 if (tosa_spk_func == TOSA_SPK_ON) 73 snd_soc_dapm_enable_pin(codec, "Speaker"); 74 else 75 snd_soc_dapm_disable_pin(codec, "Speaker"); 76 77 snd_soc_dapm_sync(codec); 78} 79 80static int tosa_startup(struct snd_pcm_substream *substream) 81{ 82 struct snd_soc_pcm_runtime *rtd = substream->private_data; 83 struct snd_soc_codec *codec = rtd->socdev->card->codec; 84 85 /* check the jack status at stream startup */ 86 tosa_ext_control(codec); 87 return 0; 88} 89 90static struct snd_soc_ops tosa_ops = { 91 .startup = tosa_startup, 92}; 93 94static int tosa_get_jack(struct snd_kcontrol *kcontrol, 95 struct snd_ctl_elem_value *ucontrol) 96{ 97 ucontrol->value.integer.value[0] = tosa_jack_func; 98 return 0; 99} 100 101static int tosa_set_jack(struct snd_kcontrol *kcontrol, 102 struct snd_ctl_elem_value *ucontrol) 103{ 104 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 105 106 if (tosa_jack_func == ucontrol->value.integer.value[0]) 107 return 0; 108 109 tosa_jack_func = ucontrol->value.integer.value[0]; 110 tosa_ext_control(codec); 111 return 1; 112} 113 114static int tosa_get_spk(struct snd_kcontrol *kcontrol, 115 struct snd_ctl_elem_value *ucontrol) 116{ 117 ucontrol->value.integer.value[0] = tosa_spk_func; 118 return 0; 119} 120 121static int tosa_set_spk(struct snd_kcontrol *kcontrol, 122 struct snd_ctl_elem_value *ucontrol) 123{ 124 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 125 126 if (tosa_spk_func == ucontrol->value.integer.value[0]) 127 return 0; 128 129 tosa_spk_func = ucontrol->value.integer.value[0]; 130 tosa_ext_control(codec); 131 return 1; 132} 133 134/* tosa dapm event handlers */ 135static int tosa_hp_event(struct snd_soc_dapm_widget *w, 136 struct snd_kcontrol *k, int event) 137{ 138 gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 :0); 139 return 0; 140} 141 142/* tosa machine dapm widgets */ 143static const struct snd_soc_dapm_widget tosa_dapm_widgets[] = { 144SND_SOC_DAPM_HP("Headphone Jack", tosa_hp_event), 145SND_SOC_DAPM_HP("Headset Jack", NULL), 146SND_SOC_DAPM_MIC("Mic (Internal)", NULL), 147SND_SOC_DAPM_SPK("Speaker", NULL), 148}; 149 150/* tosa audio map */ 151static const struct snd_soc_dapm_route audio_map[] = { 152 153 /* headphone connected to HPOUTL, HPOUTR */ 154 {"Headphone Jack", NULL, "HPOUTL"}, 155 {"Headphone Jack", NULL, "HPOUTR"}, 156 157 /* ext speaker connected to LOUT2, ROUT2 */ 158 {"Speaker", NULL, "LOUT2"}, 159 {"Speaker", NULL, "ROUT2"}, 160 161 /* internal mic is connected to mic1, mic2 differential - with bias */ 162 {"MIC1", NULL, "Mic Bias"}, 163 {"MIC2", NULL, "Mic Bias"}, 164 {"Mic Bias", NULL, "Mic (Internal)"}, 165 166 /* headset is connected to HPOUTR, and LINEINR with bias */ 167 {"Headset Jack", NULL, "HPOUTR"}, 168 {"LINEINR", NULL, "Mic Bias"}, 169 {"Mic Bias", NULL, "Headset Jack"}, 170}; 171 172static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset", 173 "Off"}; 174static const char *spk_function[] = {"On", "Off"}; 175static const struct soc_enum tosa_enum[] = { 176 SOC_ENUM_SINGLE_EXT(5, jack_function), 177 SOC_ENUM_SINGLE_EXT(2, spk_function), 178}; 179 180static const struct snd_kcontrol_new tosa_controls[] = { 181 SOC_ENUM_EXT("Jack Function", tosa_enum[0], tosa_get_jack, 182 tosa_set_jack), 183 SOC_ENUM_EXT("Speaker Function", tosa_enum[1], tosa_get_spk, 184 tosa_set_spk), 185}; 186 187static int tosa_ac97_init(struct snd_soc_codec *codec) 188{ 189 int err; 190 191 snd_soc_dapm_nc_pin(codec, "OUT3"); 192 snd_soc_dapm_nc_pin(codec, "MONOOUT"); 193 194 /* add tosa specific controls */ 195 err = snd_soc_add_controls(codec, tosa_controls, 196 ARRAY_SIZE(tosa_controls)); 197 if (err < 0) 198 return err; 199 200 /* add tosa specific widgets */ 201 snd_soc_dapm_new_controls(codec, tosa_dapm_widgets, 202 ARRAY_SIZE(tosa_dapm_widgets)); 203 204 /* set up tosa specific audio path audio_map */ 205 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); 206 207 snd_soc_dapm_sync(codec); 208 return 0; 209} 210 211static struct snd_soc_dai_link tosa_dai[] = { 212{ 213 .name = "AC97", 214 .stream_name = "AC97 HiFi", 215 .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI], 216 .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], 217 .init = tosa_ac97_init, 218 .ops = &tosa_ops, 219}, 220{ 221 .name = "AC97 Aux", 222 .stream_name = "AC97 Aux", 223 .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], 224 .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], 225 .ops = &tosa_ops, 226}, 227}; 228 229static int tosa_probe(struct platform_device *dev) 230{ 231 int ret; 232 233 ret = gpio_request(TOSA_GPIO_L_MUTE, "Headphone Jack"); 234 if (ret) 235 return ret; 236 ret = gpio_direction_output(TOSA_GPIO_L_MUTE, 0); 237 if (ret) 238 gpio_free(TOSA_GPIO_L_MUTE); 239 240 return ret; 241} 242 243static int tosa_remove(struct platform_device *dev) 244{ 245 gpio_free(TOSA_GPIO_L_MUTE); 246 return 0; 247} 248 249static struct snd_soc_card tosa = { 250 .name = "Tosa", 251 .platform = &pxa2xx_soc_platform, 252 .dai_link = tosa_dai, 253 .num_links = ARRAY_SIZE(tosa_dai), 254 .probe = tosa_probe, 255 .remove = tosa_remove, 256}; 257 258static struct snd_soc_device tosa_snd_devdata = { 259 .card = &tosa, 260 .codec_dev = &soc_codec_dev_wm9712, 261}; 262 263static struct platform_device *tosa_snd_device; 264 265static int __init tosa_init(void) 266{ 267 int ret; 268 269 if (!machine_is_tosa()) 270 return -ENODEV; 271 272 tosa_snd_device = platform_device_alloc("soc-audio", -1); 273 if (!tosa_snd_device) { 274 ret = -ENOMEM; 275 goto err_alloc; 276 } 277 278 platform_set_drvdata(tosa_snd_device, &tosa_snd_devdata); 279 tosa_snd_devdata.dev = &tosa_snd_device->dev; 280 ret = platform_device_add(tosa_snd_device); 281 282 if (!ret) 283 return 0; 284 285 platform_device_put(tosa_snd_device); 286 287err_alloc: 288 return ret; 289} 290 291static void __exit tosa_exit(void) 292{ 293 platform_device_unregister(tosa_snd_device); 294} 295 296module_init(tosa_init); 297module_exit(tosa_exit); 298 299/* Module information */ 300MODULE_AUTHOR("Richard Purdie"); 301MODULE_DESCRIPTION("ALSA SoC Tosa"); 302MODULE_LICENSE("GPL"); 303