tosa.c (6654B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * tosa.c -- SoC audio for Tosa 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 * GPIO's 12 * 1 - Jack Insertion 13 * 5 - Hookswitch (headset answer/hang up switch) 14 */ 15 16#include <linux/module.h> 17#include <linux/moduleparam.h> 18#include <linux/device.h> 19#include <linux/gpio/consumer.h> 20 21#include <sound/core.h> 22#include <sound/pcm.h> 23#include <sound/soc.h> 24 25#include <asm/mach-types.h> 26#include <linux/platform_data/asoc-pxa.h> 27 28#define TOSA_HP 0 29#define TOSA_MIC_INT 1 30#define TOSA_HEADSET 2 31#define TOSA_HP_OFF 3 32#define TOSA_SPK_ON 0 33#define TOSA_SPK_OFF 1 34 35static struct gpio_desc *tosa_mute; 36static int tosa_jack_func; 37static int tosa_spk_func; 38 39static void tosa_ext_control(struct snd_soc_dapm_context *dapm) 40{ 41 42 snd_soc_dapm_mutex_lock(dapm); 43 44 /* set up jack connection */ 45 switch (tosa_jack_func) { 46 case TOSA_HP: 47 snd_soc_dapm_disable_pin_unlocked(dapm, "Mic (Internal)"); 48 snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); 49 snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); 50 break; 51 case TOSA_MIC_INT: 52 snd_soc_dapm_enable_pin_unlocked(dapm, "Mic (Internal)"); 53 snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); 54 snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); 55 break; 56 case TOSA_HEADSET: 57 snd_soc_dapm_disable_pin_unlocked(dapm, "Mic (Internal)"); 58 snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); 59 snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Jack"); 60 break; 61 } 62 63 if (tosa_spk_func == TOSA_SPK_ON) 64 snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker"); 65 else 66 snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker"); 67 68 snd_soc_dapm_sync_unlocked(dapm); 69 70 snd_soc_dapm_mutex_unlock(dapm); 71} 72 73static int tosa_startup(struct snd_pcm_substream *substream) 74{ 75 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 76 77 /* check the jack status at stream startup */ 78 tosa_ext_control(&rtd->card->dapm); 79 80 return 0; 81} 82 83static const struct snd_soc_ops tosa_ops = { 84 .startup = tosa_startup, 85}; 86 87static int tosa_get_jack(struct snd_kcontrol *kcontrol, 88 struct snd_ctl_elem_value *ucontrol) 89{ 90 ucontrol->value.enumerated.item[0] = tosa_jack_func; 91 return 0; 92} 93 94static int tosa_set_jack(struct snd_kcontrol *kcontrol, 95 struct snd_ctl_elem_value *ucontrol) 96{ 97 struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); 98 99 if (tosa_jack_func == ucontrol->value.enumerated.item[0]) 100 return 0; 101 102 tosa_jack_func = ucontrol->value.enumerated.item[0]; 103 tosa_ext_control(&card->dapm); 104 return 1; 105} 106 107static int tosa_get_spk(struct snd_kcontrol *kcontrol, 108 struct snd_ctl_elem_value *ucontrol) 109{ 110 ucontrol->value.enumerated.item[0] = tosa_spk_func; 111 return 0; 112} 113 114static int tosa_set_spk(struct snd_kcontrol *kcontrol, 115 struct snd_ctl_elem_value *ucontrol) 116{ 117 struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); 118 119 if (tosa_spk_func == ucontrol->value.enumerated.item[0]) 120 return 0; 121 122 tosa_spk_func = ucontrol->value.enumerated.item[0]; 123 tosa_ext_control(&card->dapm); 124 return 1; 125} 126 127/* tosa dapm event handlers */ 128static int tosa_hp_event(struct snd_soc_dapm_widget *w, 129 struct snd_kcontrol *k, int event) 130{ 131 gpiod_set_value(tosa_mute, SND_SOC_DAPM_EVENT_ON(event) ? 1 : 0); 132 return 0; 133} 134 135/* tosa machine dapm widgets */ 136static const struct snd_soc_dapm_widget tosa_dapm_widgets[] = { 137SND_SOC_DAPM_HP("Headphone Jack", tosa_hp_event), 138SND_SOC_DAPM_HP("Headset Jack", NULL), 139SND_SOC_DAPM_MIC("Mic (Internal)", NULL), 140SND_SOC_DAPM_SPK("Speaker", NULL), 141}; 142 143/* tosa audio map */ 144static const struct snd_soc_dapm_route audio_map[] = { 145 146 /* headphone connected to HPOUTL, HPOUTR */ 147 {"Headphone Jack", NULL, "HPOUTL"}, 148 {"Headphone Jack", NULL, "HPOUTR"}, 149 150 /* ext speaker connected to LOUT2, ROUT2 */ 151 {"Speaker", NULL, "LOUT2"}, 152 {"Speaker", NULL, "ROUT2"}, 153 154 /* internal mic is connected to mic1, mic2 differential - with bias */ 155 {"MIC1", NULL, "Mic Bias"}, 156 {"MIC2", NULL, "Mic Bias"}, 157 {"Mic Bias", NULL, "Mic (Internal)"}, 158 159 /* headset is connected to HPOUTR, and LINEINR with bias */ 160 {"Headset Jack", NULL, "HPOUTR"}, 161 {"LINEINR", NULL, "Mic Bias"}, 162 {"Mic Bias", NULL, "Headset Jack"}, 163}; 164 165static const char * const jack_function[] = {"Headphone", "Mic", "Line", 166 "Headset", "Off"}; 167static const char * const spk_function[] = {"On", "Off"}; 168static const struct soc_enum tosa_enum[] = { 169 SOC_ENUM_SINGLE_EXT(5, jack_function), 170 SOC_ENUM_SINGLE_EXT(2, spk_function), 171}; 172 173static const struct snd_kcontrol_new tosa_controls[] = { 174 SOC_ENUM_EXT("Jack Function", tosa_enum[0], tosa_get_jack, 175 tosa_set_jack), 176 SOC_ENUM_EXT("Speaker Function", tosa_enum[1], tosa_get_spk, 177 tosa_set_spk), 178}; 179 180SND_SOC_DAILINK_DEFS(ac97, 181 DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97")), 182 DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")), 183 DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio"))); 184 185SND_SOC_DAILINK_DEFS(ac97_aux, 186 DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97-aux")), 187 DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-aux")), 188 DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio"))); 189 190static struct snd_soc_dai_link tosa_dai[] = { 191{ 192 .name = "AC97", 193 .stream_name = "AC97 HiFi", 194 .ops = &tosa_ops, 195 SND_SOC_DAILINK_REG(ac97), 196}, 197{ 198 .name = "AC97 Aux", 199 .stream_name = "AC97 Aux", 200 .ops = &tosa_ops, 201 SND_SOC_DAILINK_REG(ac97_aux), 202}, 203}; 204 205static struct snd_soc_card tosa = { 206 .name = "Tosa", 207 .owner = THIS_MODULE, 208 .dai_link = tosa_dai, 209 .num_links = ARRAY_SIZE(tosa_dai), 210 211 .controls = tosa_controls, 212 .num_controls = ARRAY_SIZE(tosa_controls), 213 .dapm_widgets = tosa_dapm_widgets, 214 .num_dapm_widgets = ARRAY_SIZE(tosa_dapm_widgets), 215 .dapm_routes = audio_map, 216 .num_dapm_routes = ARRAY_SIZE(audio_map), 217 .fully_routed = true, 218}; 219 220static int tosa_probe(struct platform_device *pdev) 221{ 222 struct snd_soc_card *card = ⤩ 223 int ret; 224 225 tosa_mute = devm_gpiod_get(&pdev->dev, NULL, GPIOD_OUT_LOW); 226 if (IS_ERR(tosa_mute)) 227 return dev_err_probe(&pdev->dev, PTR_ERR(tosa_mute), 228 "failed to get L_MUTE GPIO\n"); 229 gpiod_set_consumer_name(tosa_mute, "Headphone Jack"); 230 231 card->dev = &pdev->dev; 232 233 ret = devm_snd_soc_register_card(&pdev->dev, card); 234 if (ret) { 235 dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", 236 ret); 237 } 238 return ret; 239} 240 241static struct platform_driver tosa_driver = { 242 .driver = { 243 .name = "tosa-audio", 244 .pm = &snd_soc_pm_ops, 245 }, 246 .probe = tosa_probe, 247}; 248 249module_platform_driver(tosa_driver); 250 251/* Module information */ 252MODULE_AUTHOR("Richard Purdie"); 253MODULE_DESCRIPTION("ALSA SoC Tosa"); 254MODULE_LICENSE("GPL"); 255MODULE_ALIAS("platform:tosa-audio");