18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * OLPC XO-1 additional sound features 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright © 2006 Jaya Kumar <jayakumar.lkml@gmail.com> 68c2ecf20Sopenharmony_ci * Copyright © 2007-2008 Andres Salomon <dilinger@debian.org> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci#include <sound/core.h> 98c2ecf20Sopenharmony_ci#include <sound/info.h> 108c2ecf20Sopenharmony_ci#include <sound/control.h> 118c2ecf20Sopenharmony_ci#include <sound/ac97_codec.h> 128c2ecf20Sopenharmony_ci#include <linux/gpio.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <asm/olpc.h> 158c2ecf20Sopenharmony_ci#include "cs5535audio.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define DRV_NAME "cs5535audio-olpc" 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* 208c2ecf20Sopenharmony_ci * OLPC has an additional feature on top of the regular AD1888 codec features. 218c2ecf20Sopenharmony_ci * It has an Analog Input mode that is switched into (after disabling the 228c2ecf20Sopenharmony_ci * High Pass Filter) via GPIO. It is supported on B2 and later models. 238c2ecf20Sopenharmony_ci */ 248c2ecf20Sopenharmony_civoid olpc_analog_input(struct snd_ac97 *ac97, int on) 258c2ecf20Sopenharmony_ci{ 268c2ecf20Sopenharmony_ci int err; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci if (!machine_is_olpc()) 298c2ecf20Sopenharmony_ci return; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci /* update the High Pass Filter (via AC97_AD_TEST2) */ 328c2ecf20Sopenharmony_ci err = snd_ac97_update_bits(ac97, AC97_AD_TEST2, 338c2ecf20Sopenharmony_ci 1 << AC97_AD_HPFD_SHIFT, on << AC97_AD_HPFD_SHIFT); 348c2ecf20Sopenharmony_ci if (err < 0) { 358c2ecf20Sopenharmony_ci dev_err(ac97->bus->card->dev, 368c2ecf20Sopenharmony_ci "setting High Pass Filter - %d\n", err); 378c2ecf20Sopenharmony_ci return; 388c2ecf20Sopenharmony_ci } 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci /* set Analog Input through GPIO */ 418c2ecf20Sopenharmony_ci gpio_set_value(OLPC_GPIO_MIC_AC, on); 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci/* 458c2ecf20Sopenharmony_ci * OLPC XO-1's V_REFOUT is a mic bias enable. 468c2ecf20Sopenharmony_ci */ 478c2ecf20Sopenharmony_civoid olpc_mic_bias(struct snd_ac97 *ac97, int on) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci int err; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci if (!machine_is_olpc()) 528c2ecf20Sopenharmony_ci return; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci on = on ? 0 : 1; 558c2ecf20Sopenharmony_ci err = snd_ac97_update_bits(ac97, AC97_AD_MISC, 568c2ecf20Sopenharmony_ci 1 << AC97_AD_VREFD_SHIFT, on << AC97_AD_VREFD_SHIFT); 578c2ecf20Sopenharmony_ci if (err < 0) 588c2ecf20Sopenharmony_ci dev_err(ac97->bus->card->dev, "setting MIC Bias - %d\n", err); 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int olpc_dc_info(struct snd_kcontrol *kctl, 628c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; 658c2ecf20Sopenharmony_ci uinfo->count = 1; 668c2ecf20Sopenharmony_ci uinfo->value.integer.min = 0; 678c2ecf20Sopenharmony_ci uinfo->value.integer.max = 1; 688c2ecf20Sopenharmony_ci return 0; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic int olpc_dc_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci v->value.integer.value[0] = gpio_get_value(OLPC_GPIO_MIC_AC); 748c2ecf20Sopenharmony_ci return 0; 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic int olpc_dc_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci olpc_analog_input(cs5535au->ac97, v->value.integer.value[0]); 828c2ecf20Sopenharmony_ci return 1; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic int olpc_mic_info(struct snd_kcontrol *kctl, 868c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; 898c2ecf20Sopenharmony_ci uinfo->count = 1; 908c2ecf20Sopenharmony_ci uinfo->value.integer.min = 0; 918c2ecf20Sopenharmony_ci uinfo->value.integer.max = 1; 928c2ecf20Sopenharmony_ci return 0; 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic int olpc_mic_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); 988c2ecf20Sopenharmony_ci struct snd_ac97 *ac97 = cs5535au->ac97; 998c2ecf20Sopenharmony_ci int i; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci i = (snd_ac97_read(ac97, AC97_AD_MISC) >> AC97_AD_VREFD_SHIFT) & 0x1; 1028c2ecf20Sopenharmony_ci v->value.integer.value[0] = i ? 0 : 1; 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic int olpc_mic_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci olpc_mic_bias(cs5535au->ac97, v->value.integer.value[0]); 1118c2ecf20Sopenharmony_ci return 1; 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new olpc_cs5535audio_ctls[] = { 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 1178c2ecf20Sopenharmony_ci .name = "DC Mode Enable", 1188c2ecf20Sopenharmony_ci .info = olpc_dc_info, 1198c2ecf20Sopenharmony_ci .get = olpc_dc_get, 1208c2ecf20Sopenharmony_ci .put = olpc_dc_put, 1218c2ecf20Sopenharmony_ci .private_value = 0, 1228c2ecf20Sopenharmony_ci}, 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 1258c2ecf20Sopenharmony_ci .name = "MIC Bias Enable", 1268c2ecf20Sopenharmony_ci .info = olpc_mic_info, 1278c2ecf20Sopenharmony_ci .get = olpc_mic_get, 1288c2ecf20Sopenharmony_ci .put = olpc_mic_put, 1298c2ecf20Sopenharmony_ci .private_value = 0, 1308c2ecf20Sopenharmony_ci}, 1318c2ecf20Sopenharmony_ci}; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_civoid olpc_prequirks(struct snd_card *card, 1348c2ecf20Sopenharmony_ci struct snd_ac97_template *ac97) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci if (!machine_is_olpc()) 1378c2ecf20Sopenharmony_ci return; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci /* invert EAPD if on an OLPC B3 or higher */ 1408c2ecf20Sopenharmony_ci if (olpc_board_at_least(olpc_board_pre(0xb3))) 1418c2ecf20Sopenharmony_ci ac97->scaps |= AC97_SCAP_INV_EAPD; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ciint olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct snd_ctl_elem_id elem; 1478c2ecf20Sopenharmony_ci int i, err; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci if (!machine_is_olpc()) 1508c2ecf20Sopenharmony_ci return 0; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci if (gpio_request(OLPC_GPIO_MIC_AC, DRV_NAME)) { 1538c2ecf20Sopenharmony_ci dev_err(card->dev, "unable to allocate MIC GPIO\n"); 1548c2ecf20Sopenharmony_ci return -EIO; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci gpio_direction_output(OLPC_GPIO_MIC_AC, 0); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* drop the original AD1888 HPF control */ 1598c2ecf20Sopenharmony_ci memset(&elem, 0, sizeof(elem)); 1608c2ecf20Sopenharmony_ci elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 1618c2ecf20Sopenharmony_ci strlcpy(elem.name, "High Pass Filter Enable", sizeof(elem.name)); 1628c2ecf20Sopenharmony_ci snd_ctl_remove_id(card, &elem); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* drop the original V_REFOUT control */ 1658c2ecf20Sopenharmony_ci memset(&elem, 0, sizeof(elem)); 1668c2ecf20Sopenharmony_ci elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 1678c2ecf20Sopenharmony_ci strlcpy(elem.name, "V_REFOUT Enable", sizeof(elem.name)); 1688c2ecf20Sopenharmony_ci snd_ctl_remove_id(card, &elem); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* add the OLPC-specific controls */ 1718c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(olpc_cs5535audio_ctls); i++) { 1728c2ecf20Sopenharmony_ci err = snd_ctl_add(card, snd_ctl_new1(&olpc_cs5535audio_ctls[i], 1738c2ecf20Sopenharmony_ci ac97->private_data)); 1748c2ecf20Sopenharmony_ci if (err < 0) { 1758c2ecf20Sopenharmony_ci gpio_free(OLPC_GPIO_MIC_AC); 1768c2ecf20Sopenharmony_ci return err; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci /* turn off the mic by default */ 1818c2ecf20Sopenharmony_ci olpc_mic_bias(ac97, 0); 1828c2ecf20Sopenharmony_ci return 0; 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_civoid olpc_quirks_cleanup(void) 1868c2ecf20Sopenharmony_ci{ 1878c2ecf20Sopenharmony_ci gpio_free(OLPC_GPIO_MIC_AC); 1888c2ecf20Sopenharmony_ci} 189