corgi.c (9193B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * corgi.c -- SoC audio for Corgi 4 * 5 * Copyright 2005 Wolfson Microelectronics PLC. 6 * Copyright 2005 Openedhand Ltd. 7 * 8 * Authors: Liam Girdwood <lrg@slimlogic.co.uk> 9 * Richard Purdie <richard@openedhand.com> 10 */ 11 12#include <linux/module.h> 13#include <linux/moduleparam.h> 14#include <linux/timer.h> 15#include <linux/i2c.h> 16#include <linux/interrupt.h> 17#include <linux/platform_device.h> 18#include <linux/gpio.h> 19#include <sound/core.h> 20#include <sound/pcm.h> 21#include <sound/soc.h> 22 23#include <asm/mach-types.h> 24#include <linux/platform_data/asoc-pxa.h> 25 26#include "../codecs/wm8731.h" 27#include "pxa2xx-i2s.h" 28 29#define CORGI_HP 0 30#define CORGI_MIC 1 31#define CORGI_LINE 2 32#define CORGI_HEADSET 3 33#define CORGI_HP_OFF 4 34#define CORGI_SPK_ON 0 35#define CORGI_SPK_OFF 1 36 37 /* audio clock in Hz - rounded from 12.235MHz */ 38#define CORGI_AUDIO_CLOCK 12288000 39 40static int corgi_jack_func; 41static int corgi_spk_func; 42 43static struct gpio_desc *gpiod_mute_l, *gpiod_mute_r, 44 *gpiod_apm_on, *gpiod_mic_bias; 45 46static void corgi_ext_control(struct snd_soc_dapm_context *dapm) 47{ 48 snd_soc_dapm_mutex_lock(dapm); 49 50 /* set up jack connection */ 51 switch (corgi_jack_func) { 52 case CORGI_HP: 53 /* set = unmute headphone */ 54 gpiod_set_value(gpiod_mute_l, 1); 55 gpiod_set_value(gpiod_mute_r, 1); 56 snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack"); 57 snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); 58 snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); 59 snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); 60 break; 61 case CORGI_MIC: 62 /* reset = mute headphone */ 63 gpiod_set_value(gpiod_mute_l, 0); 64 gpiod_set_value(gpiod_mute_r, 0); 65 snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack"); 66 snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); 67 snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); 68 snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); 69 break; 70 case CORGI_LINE: 71 gpiod_set_value(gpiod_mute_l, 0); 72 gpiod_set_value(gpiod_mute_r, 0); 73 snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack"); 74 snd_soc_dapm_enable_pin_unlocked(dapm, "Line Jack"); 75 snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); 76 snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); 77 break; 78 case CORGI_HEADSET: 79 gpiod_set_value(gpiod_mute_l, 0); 80 gpiod_set_value(gpiod_mute_r, 1); 81 snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack"); 82 snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); 83 snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); 84 snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Jack"); 85 break; 86 } 87 88 if (corgi_spk_func == CORGI_SPK_ON) 89 snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk"); 90 else 91 snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk"); 92 93 /* signal a DAPM event */ 94 snd_soc_dapm_sync_unlocked(dapm); 95 96 snd_soc_dapm_mutex_unlock(dapm); 97} 98 99static int corgi_startup(struct snd_pcm_substream *substream) 100{ 101 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 102 103 /* check the jack status at stream startup */ 104 corgi_ext_control(&rtd->card->dapm); 105 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 gpiod_set_value(gpiod_mute_l, 1); 114 gpiod_set_value(gpiod_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 = asoc_substream_to_rtd(substream); 121 struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); 122 struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); 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 the codec system clock for DAC and ADC */ 141 ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk, 142 SND_SOC_CLOCK_IN); 143 if (ret < 0) 144 return ret; 145 146 /* set the I2S system clock as input (unused) */ 147 ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, 148 SND_SOC_CLOCK_IN); 149 if (ret < 0) 150 return ret; 151 152 return 0; 153} 154 155static const struct snd_soc_ops corgi_ops = { 156 .startup = corgi_startup, 157 .hw_params = corgi_hw_params, 158 .shutdown = corgi_shutdown, 159}; 160 161static int corgi_get_jack(struct snd_kcontrol *kcontrol, 162 struct snd_ctl_elem_value *ucontrol) 163{ 164 ucontrol->value.enumerated.item[0] = corgi_jack_func; 165 return 0; 166} 167 168static int corgi_set_jack(struct snd_kcontrol *kcontrol, 169 struct snd_ctl_elem_value *ucontrol) 170{ 171 struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); 172 173 if (corgi_jack_func == ucontrol->value.enumerated.item[0]) 174 return 0; 175 176 corgi_jack_func = ucontrol->value.enumerated.item[0]; 177 corgi_ext_control(&card->dapm); 178 return 1; 179} 180 181static int corgi_get_spk(struct snd_kcontrol *kcontrol, 182 struct snd_ctl_elem_value *ucontrol) 183{ 184 ucontrol->value.enumerated.item[0] = corgi_spk_func; 185 return 0; 186} 187 188static int corgi_set_spk(struct snd_kcontrol *kcontrol, 189 struct snd_ctl_elem_value *ucontrol) 190{ 191 struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); 192 193 if (corgi_spk_func == ucontrol->value.enumerated.item[0]) 194 return 0; 195 196 corgi_spk_func = ucontrol->value.enumerated.item[0]; 197 corgi_ext_control(&card->dapm); 198 return 1; 199} 200 201static int corgi_amp_event(struct snd_soc_dapm_widget *w, 202 struct snd_kcontrol *k, int event) 203{ 204 gpiod_set_value(gpiod_apm_on, SND_SOC_DAPM_EVENT_ON(event)); 205 return 0; 206} 207 208static int corgi_mic_event(struct snd_soc_dapm_widget *w, 209 struct snd_kcontrol *k, int event) 210{ 211 gpiod_set_value(gpiod_mic_bias, SND_SOC_DAPM_EVENT_ON(event)); 212 return 0; 213} 214 215/* corgi machine dapm widgets */ 216static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { 217SND_SOC_DAPM_HP("Headphone Jack", NULL), 218SND_SOC_DAPM_MIC("Mic Jack", corgi_mic_event), 219SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event), 220SND_SOC_DAPM_LINE("Line Jack", NULL), 221SND_SOC_DAPM_HP("Headset Jack", NULL), 222}; 223 224/* Corgi machine audio map (connections to the codec pins) */ 225static const struct snd_soc_dapm_route corgi_audio_map[] = { 226 227 /* headset Jack - in = micin, out = LHPOUT*/ 228 {"Headset Jack", NULL, "LHPOUT"}, 229 230 /* headphone connected to LHPOUT1, RHPOUT1 */ 231 {"Headphone Jack", NULL, "LHPOUT"}, 232 {"Headphone Jack", NULL, "RHPOUT"}, 233 234 /* speaker connected to LOUT, ROUT */ 235 {"Ext Spk", NULL, "ROUT"}, 236 {"Ext Spk", NULL, "LOUT"}, 237 238 /* mic is connected to MICIN (via right channel of headphone jack) */ 239 {"MICIN", NULL, "Mic Jack"}, 240 241 /* Same as the above but no mic bias for line signals */ 242 {"MICIN", NULL, "Line Jack"}, 243}; 244 245static const char * const jack_function[] = {"Headphone", "Mic", "Line", 246 "Headset", "Off"}; 247static const char * const spk_function[] = {"On", "Off"}; 248static const struct soc_enum corgi_enum[] = { 249 SOC_ENUM_SINGLE_EXT(5, jack_function), 250 SOC_ENUM_SINGLE_EXT(2, spk_function), 251}; 252 253static const struct snd_kcontrol_new wm8731_corgi_controls[] = { 254 SOC_ENUM_EXT("Jack Function", corgi_enum[0], corgi_get_jack, 255 corgi_set_jack), 256 SOC_ENUM_EXT("Speaker Function", corgi_enum[1], corgi_get_spk, 257 corgi_set_spk), 258}; 259 260/* corgi digital audio interface glue - connects codec <--> CPU */ 261SND_SOC_DAILINK_DEFS(wm8731, 262 DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-i2s")), 263 DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.0-001b", "wm8731-hifi")), 264 DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio"))); 265 266static struct snd_soc_dai_link corgi_dai = { 267 .name = "WM8731", 268 .stream_name = "WM8731", 269 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 270 SND_SOC_DAIFMT_CBS_CFS, 271 .ops = &corgi_ops, 272 SND_SOC_DAILINK_REG(wm8731), 273}; 274 275/* corgi audio machine driver */ 276static struct snd_soc_card corgi = { 277 .name = "Corgi", 278 .owner = THIS_MODULE, 279 .dai_link = &corgi_dai, 280 .num_links = 1, 281 282 .controls = wm8731_corgi_controls, 283 .num_controls = ARRAY_SIZE(wm8731_corgi_controls), 284 .dapm_widgets = wm8731_dapm_widgets, 285 .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets), 286 .dapm_routes = corgi_audio_map, 287 .num_dapm_routes = ARRAY_SIZE(corgi_audio_map), 288 .fully_routed = true, 289}; 290 291static int corgi_probe(struct platform_device *pdev) 292{ 293 struct snd_soc_card *card = &corgi; 294 int ret; 295 296 card->dev = &pdev->dev; 297 298 gpiod_mute_l = devm_gpiod_get(&pdev->dev, "mute-l", GPIOD_OUT_HIGH); 299 if (IS_ERR(gpiod_mute_l)) 300 return PTR_ERR(gpiod_mute_l); 301 gpiod_mute_r = devm_gpiod_get(&pdev->dev, "mute-r", GPIOD_OUT_HIGH); 302 if (IS_ERR(gpiod_mute_r)) 303 return PTR_ERR(gpiod_mute_r); 304 gpiod_apm_on = devm_gpiod_get(&pdev->dev, "apm-on", GPIOD_OUT_LOW); 305 if (IS_ERR(gpiod_apm_on)) 306 return PTR_ERR(gpiod_apm_on); 307 gpiod_mic_bias = devm_gpiod_get(&pdev->dev, "mic-bias", GPIOD_OUT_LOW); 308 if (IS_ERR(gpiod_mic_bias)) 309 return PTR_ERR(gpiod_mic_bias); 310 311 ret = devm_snd_soc_register_card(&pdev->dev, card); 312 if (ret) 313 dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", 314 ret); 315 return ret; 316} 317 318static struct platform_driver corgi_driver = { 319 .driver = { 320 .name = "corgi-audio", 321 .pm = &snd_soc_pm_ops, 322 }, 323 .probe = corgi_probe, 324}; 325 326module_platform_driver(corgi_driver); 327 328/* Module information */ 329MODULE_AUTHOR("Richard Purdie"); 330MODULE_DESCRIPTION("ALSA SoC Corgi"); 331MODULE_LICENSE("GPL"); 332MODULE_ALIAS("platform:corgi-audio");