1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * OLPC XO-1 additional sound features 4 * 5 * Copyright © 2006 Jaya Kumar <jayakumar.lkml@gmail.com> 6 * Copyright © 2007-2008 Andres Salomon <dilinger@debian.org> 7 */ 8 #include <sound/core.h> 9 #include <sound/info.h> 10 #include <sound/control.h> 11 #include <sound/ac97_codec.h> 12 #include <linux/gpio.h> 13 14 #include <asm/olpc.h> 15 #include "cs5535audio.h" 16 17 #define DRV_NAME "cs5535audio-olpc" 18 19 /* 20 * OLPC has an additional feature on top of the regular AD1888 codec features. 21 * It has an Analog Input mode that is switched into (after disabling the 22 * High Pass Filter) via GPIO. It is supported on B2 and later models. 23 */ 24 void olpc_analog_input(struct snd_ac97 *ac97, int on) 25 { 26 int err; 27 28 if (!machine_is_olpc()) 29 return; 30 31 /* update the High Pass Filter (via AC97_AD_TEST2) */ 32 err = snd_ac97_update_bits(ac97, AC97_AD_TEST2, 33 1 << AC97_AD_HPFD_SHIFT, on << AC97_AD_HPFD_SHIFT); 34 if (err < 0) { 35 dev_err(ac97->bus->card->dev, 36 "setting High Pass Filter - %d\n", err); 37 return; 38 } 39 40 /* set Analog Input through GPIO */ 41 gpio_set_value(OLPC_GPIO_MIC_AC, on); 42 } 43 44 /* 45 * OLPC XO-1's V_REFOUT is a mic bias enable. 46 */ 47 void olpc_mic_bias(struct snd_ac97 *ac97, int on) 48 { 49 int err; 50 51 if (!machine_is_olpc()) 52 return; 53 54 on = on ? 0 : 1; 55 err = snd_ac97_update_bits(ac97, AC97_AD_MISC, 56 1 << AC97_AD_VREFD_SHIFT, on << AC97_AD_VREFD_SHIFT); 57 if (err < 0) 58 dev_err(ac97->bus->card->dev, "setting MIC Bias - %d\n", err); 59 } 60 61 static int olpc_dc_info(struct snd_kcontrol *kctl, 62 struct snd_ctl_elem_info *uinfo) 63 { 64 uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; 65 uinfo->count = 1; 66 uinfo->value.integer.min = 0; 67 uinfo->value.integer.max = 1; 68 return 0; 69 } 70 71 static int olpc_dc_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) 72 { 73 v->value.integer.value[0] = gpio_get_value(OLPC_GPIO_MIC_AC); 74 return 0; 75 } 76 77 static int olpc_dc_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) 78 { 79 struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); 80 81 olpc_analog_input(cs5535au->ac97, v->value.integer.value[0]); 82 return 1; 83 } 84 85 static int olpc_mic_info(struct snd_kcontrol *kctl, 86 struct snd_ctl_elem_info *uinfo) 87 { 88 uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; 89 uinfo->count = 1; 90 uinfo->value.integer.min = 0; 91 uinfo->value.integer.max = 1; 92 return 0; 93 } 94 95 static int olpc_mic_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) 96 { 97 struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); 98 struct snd_ac97 *ac97 = cs5535au->ac97; 99 int i; 100 101 i = (snd_ac97_read(ac97, AC97_AD_MISC) >> AC97_AD_VREFD_SHIFT) & 0x1; 102 v->value.integer.value[0] = i ? 0 : 1; 103 return 0; 104 } 105 106 static int olpc_mic_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) 107 { 108 struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); 109 110 olpc_mic_bias(cs5535au->ac97, v->value.integer.value[0]); 111 return 1; 112 } 113 114 static const struct snd_kcontrol_new olpc_cs5535audio_ctls[] = { 115 { 116 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 117 .name = "DC Mode Enable", 118 .info = olpc_dc_info, 119 .get = olpc_dc_get, 120 .put = olpc_dc_put, 121 .private_value = 0, 122 }, 123 { 124 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 125 .name = "MIC Bias Enable", 126 .info = olpc_mic_info, 127 .get = olpc_mic_get, 128 .put = olpc_mic_put, 129 .private_value = 0, 130 }, 131 }; 132 133 void olpc_prequirks(struct snd_card *card, 134 struct snd_ac97_template *ac97) 135 { 136 if (!machine_is_olpc()) 137 return; 138 139 /* invert EAPD if on an OLPC B3 or higher */ 140 if (olpc_board_at_least(olpc_board_pre(0xb3))) 141 ac97->scaps |= AC97_SCAP_INV_EAPD; 142 } 143 144 int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97) 145 { 146 struct snd_ctl_elem_id elem; 147 int i, err; 148 149 if (!machine_is_olpc()) 150 return 0; 151 152 if (gpio_request(OLPC_GPIO_MIC_AC, DRV_NAME)) { 153 dev_err(card->dev, "unable to allocate MIC GPIO\n"); 154 return -EIO; 155 } 156 gpio_direction_output(OLPC_GPIO_MIC_AC, 0); 157 158 /* drop the original AD1888 HPF control */ 159 memset(&elem, 0, sizeof(elem)); 160 elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 161 strlcpy(elem.name, "High Pass Filter Enable", sizeof(elem.name)); 162 snd_ctl_remove_id(card, &elem); 163 164 /* drop the original V_REFOUT control */ 165 memset(&elem, 0, sizeof(elem)); 166 elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 167 strlcpy(elem.name, "V_REFOUT Enable", sizeof(elem.name)); 168 snd_ctl_remove_id(card, &elem); 169 170 /* add the OLPC-specific controls */ 171 for (i = 0; i < ARRAY_SIZE(olpc_cs5535audio_ctls); i++) { 172 err = snd_ctl_add(card, snd_ctl_new1(&olpc_cs5535audio_ctls[i], 173 ac97->private_data)); 174 if (err < 0) { 175 gpio_free(OLPC_GPIO_MIC_AC); 176 return err; 177 } 178 } 179 180 /* turn off the mic by default */ 181 olpc_mic_bias(ac97, 0); 182 return 0; 183 } 184 185 void olpc_quirks_cleanup(void) 186 { 187 gpio_free(OLPC_GPIO_MIC_AC); 188 } 189