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