palm27x.c revision e91fb9137dd235ab959d7675d0e4104974dad5eb
1/* 2 * linux/sound/soc/pxa/palm27x.c 3 * 4 * SoC Audio driver for Palm T|X, T5 and LifeDrive 5 * 6 * based on tosa.c 7 * 8 * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License version 2 as 12 * published by the Free Software Foundation. 13 * 14 */ 15 16#include <linux/module.h> 17#include <linux/moduleparam.h> 18#include <linux/device.h> 19#include <linux/gpio.h> 20#include <linux/interrupt.h> 21#include <linux/irq.h> 22 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/audio.h> 30#include <mach/palmasoc.h> 31 32#include "../codecs/wm9712.h" 33#include "pxa2xx-pcm.h" 34#include "pxa2xx-ac97.h" 35 36static int palm27x_jack_func = 1; 37static int palm27x_spk_func = 1; 38static int palm27x_ep_gpio = -1; 39 40static void palm27x_ext_control(struct snd_soc_codec *codec) 41{ 42 if (!palm27x_spk_func) 43 snd_soc_dapm_enable_pin(codec, "Speaker"); 44 else 45 snd_soc_dapm_disable_pin(codec, "Speaker"); 46 47 if (!palm27x_jack_func) 48 snd_soc_dapm_enable_pin(codec, "Headphone Jack"); 49 else 50 snd_soc_dapm_disable_pin(codec, "Headphone Jack"); 51 52 snd_soc_dapm_sync(codec); 53} 54 55static int palm27x_startup(struct snd_pcm_substream *substream) 56{ 57 struct snd_soc_pcm_runtime *rtd = substream->private_data; 58 struct snd_soc_codec *codec = rtd->socdev->card->codec; 59 60 /* check the jack status at stream startup */ 61 palm27x_ext_control(codec); 62 return 0; 63} 64 65static struct snd_soc_ops palm27x_ops = { 66 .startup = palm27x_startup, 67}; 68 69static irqreturn_t palm27x_interrupt(int irq, void *v) 70{ 71 palm27x_spk_func = gpio_get_value(palm27x_ep_gpio); 72 palm27x_jack_func = !palm27x_spk_func; 73 return IRQ_HANDLED; 74} 75 76static int palm27x_get_jack(struct snd_kcontrol *kcontrol, 77 struct snd_ctl_elem_value *ucontrol) 78{ 79 ucontrol->value.integer.value[0] = palm27x_jack_func; 80 return 0; 81} 82 83static int palm27x_set_jack(struct snd_kcontrol *kcontrol, 84 struct snd_ctl_elem_value *ucontrol) 85{ 86 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 87 88 if (palm27x_jack_func == ucontrol->value.integer.value[0]) 89 return 0; 90 91 palm27x_jack_func = ucontrol->value.integer.value[0]; 92 palm27x_ext_control(codec); 93 return 1; 94} 95 96static int palm27x_get_spk(struct snd_kcontrol *kcontrol, 97 struct snd_ctl_elem_value *ucontrol) 98{ 99 ucontrol->value.integer.value[0] = palm27x_spk_func; 100 return 0; 101} 102 103static int palm27x_set_spk(struct snd_kcontrol *kcontrol, 104 struct snd_ctl_elem_value *ucontrol) 105{ 106 struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 107 108 if (palm27x_spk_func == ucontrol->value.integer.value[0]) 109 return 0; 110 111 palm27x_spk_func = ucontrol->value.integer.value[0]; 112 palm27x_ext_control(codec); 113 return 1; 114} 115 116/* PalmTX machine dapm widgets */ 117static const struct snd_soc_dapm_widget palm27x_dapm_widgets[] = { 118 SND_SOC_DAPM_HP("Headphone Jack", NULL), 119 SND_SOC_DAPM_SPK("Speaker", NULL), 120}; 121 122/* PalmTX audio map */ 123static const struct snd_soc_dapm_route audio_map[] = { 124 /* headphone connected to HPOUTL, HPOUTR */ 125 {"Headphone Jack", NULL, "HPOUTL"}, 126 {"Headphone Jack", NULL, "HPOUTR"}, 127 128 /* ext speaker connected to ROUT2, LOUT2 */ 129 {"Speaker", NULL, "LOUT2"}, 130 {"Speaker", NULL, "ROUT2"}, 131}; 132 133static const char *jack_function[] = {"Headphone", "Off"}; 134static const char *spk_function[] = {"On", "Off"}; 135static const struct soc_enum palm27x_enum[] = { 136 SOC_ENUM_SINGLE_EXT(2, jack_function), 137 SOC_ENUM_SINGLE_EXT(2, spk_function), 138}; 139 140static const struct snd_kcontrol_new palm27x_controls[] = { 141 SOC_ENUM_EXT("Jack Function", palm27x_enum[0], palm27x_get_jack, 142 palm27x_set_jack), 143 SOC_ENUM_EXT("Speaker Function", palm27x_enum[1], palm27x_get_spk, 144 palm27x_set_spk), 145}; 146 147static int palm27x_ac97_init(struct snd_soc_codec *codec) 148{ 149 int err; 150 151 snd_soc_dapm_nc_pin(codec, "OUT3"); 152 snd_soc_dapm_nc_pin(codec, "MONOOUT"); 153 154 /* add palm27x specific controls */ 155 err = snd_soc_add_controls(codec, palm27x_controls, 156 ARRAY_SIZE(palm27x_controls)); 157 if (err < 0) 158 return err; 159 160 /* add palm27x specific widgets */ 161 snd_soc_dapm_new_controls(codec, palm27x_dapm_widgets, 162 ARRAY_SIZE(palm27x_dapm_widgets)); 163 164 /* set up palm27x specific audio path audio_map */ 165 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); 166 167 snd_soc_dapm_sync(codec); 168 return 0; 169} 170 171static struct snd_soc_dai_link palm27x_dai[] = { 172{ 173 .name = "AC97 HiFi", 174 .stream_name = "AC97 HiFi", 175 .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI], 176 .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], 177 .init = palm27x_ac97_init, 178 .ops = &palm27x_ops, 179}, 180{ 181 .name = "AC97 Aux", 182 .stream_name = "AC97 Aux", 183 .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], 184 .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], 185 .ops = &palm27x_ops, 186}, 187}; 188 189static struct snd_soc_card palm27x_asoc = { 190 .name = "Palm/PXA27x", 191 .platform = &pxa2xx_soc_platform, 192 .dai_link = palm27x_dai, 193 .num_links = ARRAY_SIZE(palm27x_dai), 194}; 195 196static struct snd_soc_device palm27x_snd_devdata = { 197 .card = &palm27x_asoc, 198 .codec_dev = &soc_codec_dev_wm9712, 199}; 200 201static struct platform_device *palm27x_snd_device; 202 203static int palm27x_asoc_probe(struct platform_device *pdev) 204{ 205 int ret; 206 207 if (!(machine_is_palmtx() || machine_is_palmt5() || 208 machine_is_palmld())) 209 return -ENODEV; 210 211 if (pdev->dev.platform_data) 212 palm27x_ep_gpio = ((struct palm27x_asoc_info *) 213 (pdev->dev.platform_data))->jack_gpio; 214 215 ret = gpio_request(palm27x_ep_gpio, "Headphone Jack"); 216 if (ret) 217 return ret; 218 ret = gpio_direction_input(palm27x_ep_gpio); 219 if (ret) 220 goto err_alloc; 221 222 if (request_irq(gpio_to_irq(palm27x_ep_gpio), palm27x_interrupt, 223 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 224 "Headphone jack", NULL)) 225 goto err_alloc; 226 227 palm27x_snd_device = platform_device_alloc("soc-audio", -1); 228 if (!palm27x_snd_device) { 229 ret = -ENOMEM; 230 goto err_dev; 231 } 232 233 platform_set_drvdata(palm27x_snd_device, &palm27x_snd_devdata); 234 palm27x_snd_devdata.dev = &palm27x_snd_device->dev; 235 ret = platform_device_add(palm27x_snd_device); 236 237 if (ret != 0) 238 goto put_device; 239 240 return 0; 241 242put_device: 243 platform_device_put(palm27x_snd_device); 244err_dev: 245 free_irq(gpio_to_irq(palm27x_ep_gpio), NULL); 246err_alloc: 247 gpio_free(palm27x_ep_gpio); 248 249 return ret; 250} 251 252static int __devexit palm27x_asoc_remove(struct platform_device *pdev) 253{ 254 free_irq(gpio_to_irq(palm27x_ep_gpio), NULL); 255 gpio_free(palm27x_ep_gpio); 256 platform_device_unregister(palm27x_snd_device); 257 return 0; 258} 259 260static struct platform_driver palm27x_wm9712_driver = { 261 .probe = palm27x_asoc_probe, 262 .remove = __devexit_p(palm27x_asoc_remove), 263 .driver = { 264 .name = "palm27x-asoc", 265 .owner = THIS_MODULE, 266 }, 267}; 268 269static int __init palm27x_asoc_init(void) 270{ 271 return platform_driver_register(&palm27x_wm9712_driver); 272} 273 274static void __exit palm27x_asoc_exit(void) 275{ 276 platform_driver_unregister(&palm27x_wm9712_driver); 277} 278 279module_init(palm27x_asoc_init); 280module_exit(palm27x_asoc_exit); 281 282/* Module information */ 283MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); 284MODULE_DESCRIPTION("ALSA SoC Palm T|X, T5 and LifeDrive"); 285MODULE_LICENSE("GPL"); 286