summaryrefslogtreecommitdiffstats
path: root/sound/pci/cs5535audio/cs5535audio_olpc.c
blob: e6a44507d55700ef689d6b98438452a53b008a24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/*
 * OLPC XO-1 additional sound features
 *
 * Copyright © 2006  Jaya Kumar <jayakumar.lkml@gmail.com>
 * Copyright © 2007-2008  Andres Salomon <dilinger@debian.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */
#include <sound/core.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/ac97_codec.h>
#include <linux/gpio.h>

#include <asm/olpc.h>
#include "cs5535audio.h"

#define DRV_NAME "cs5535audio-olpc"

/*
 * OLPC has an additional feature on top of the regular AD1888 codec features.
 * It has an Analog Input mode that is switched into (after disabling the
 * High Pass Filter) via GPIO.  It is supported on B2 and later models.
 */
void olpc_analog_input(struct snd_ac97 *ac97, int on)
{
	int err;

	if (!machine_is_olpc())
		return;

	/* update the High Pass Filter (via AC97_AD_TEST2) */
	err = snd_ac97_update_bits(ac97, AC97_AD_TEST2,
			1 << AC97_AD_HPFD_SHIFT, on << AC97_AD_HPFD_SHIFT);
	if (err < 0) {
		snd_printk(KERN_ERR "setting High Pass Filter - %d\n", err);
		return;
	}

	/* set Analog Input through GPIO */
	gpio_set_value(OLPC_GPIO_MIC_AC, on);
}

/*
 * OLPC XO-1's V_REFOUT is a mic bias enable.
 */
void olpc_mic_bias(struct snd_ac97 *ac97, int on)
{
	int err;

	if (!machine_is_olpc())
		return;

	on = on ? 0 : 1;
	err = snd_ac97_update_bits(ac97, AC97_AD_MISC,
			1 << AC97_AD_VREFD_SHIFT, on << AC97_AD_VREFD_SHIFT);
	if (err < 0)
		snd_printk(KERN_ERR "setting MIC Bias - %d\n", err);
}

static int olpc_dc_info(struct snd_kcontrol *kctl,
		struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;
	return 0;
}

static int olpc_dc_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
{
	v->value.integer.value[0] = gpio_get_value(OLPC_GPIO_MIC_AC);
	return 0;
}

static int olpc_dc_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
{
	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);

	olpc_analog_input(cs5535au->ac97, v->value.integer.value[0]);
	return 1;
}

static int olpc_mic_info(struct snd_kcontrol *kctl,
		struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;
	return 0;
}

static int olpc_mic_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
{
	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);
	struct snd_ac97 *ac97 = cs5535au->ac97;
	int i;

	i = (snd_ac97_read(ac97, AC97_AD_MISC) >> AC97_AD_VREFD_SHIFT) & 0x1;
	v->value.integer.value[0] = i ? 0 : 1;
	return 0;
}

static int olpc_mic_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
{
	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);

	olpc_mic_bias(cs5535au->ac97, v->value.integer.value[0]);
	return 1;
}

static struct snd_kcontrol_new olpc_cs5535audio_ctls[] = {
{
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "DC Mode Enable",
	.info = olpc_dc_info,
	.get = olpc_dc_get,
	.put = olpc_dc_put,
	.private_value = 0,
},
{
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "MIC Bias Enable",
	.info = olpc_mic_info,
	.get = olpc_mic_get,
	.put = olpc_mic_put,
	.private_value = 0,
},
};

void olpc_prequirks(struct snd_card *card,
		    struct snd_ac97_template *ac97)
{
	if (!machine_is_olpc())
		return;

	/* invert EAPD if on an OLPC B3 or higher */
	if (olpc_board_at_least(olpc_board_pre(0xb3)))
		ac97->scaps |= AC97_SCAP_INV_EAPD;
}

int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97)
{
	struct snd_ctl_elem_id elem;
	int i, err;

	if (!machine_is_olpc())
		return 0;

	if (gpio_request(OLPC_GPIO_MIC_AC, DRV_NAME)) {
		printk(KERN_ERR DRV_NAME ": unable to allocate MIC GPIO\n");
		return -EIO;
	}
	gpio_direction_output(OLPC_GPIO_MIC_AC, 0);

	/* drop the original AD1888 HPF control */
	memset(&elem, 0, sizeof(elem));
	elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
	strlcpy(elem.name, "High Pass Filter Enable", sizeof(elem.name));
	snd_ctl_remove_id(card, &elem);

	/* drop the original V_REFOUT control */
	memset(&elem, 0, sizeof(elem));
	elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
	strlcpy(elem.name, "V_REFOUT Enable", sizeof(elem.name));
	snd_ctl_remove_id(card, &elem);

	/* add the OLPC-specific controls */
	for (i = 0; i < ARRAY_SIZE(olpc_cs5535audio_ctls); i++) {
		err = snd_ctl_add(card, snd_ctl_new1(&olpc_cs5535audio_ctls[i],
				ac97->private_data));
		if (err < 0) {
			gpio_free(OLPC_GPIO_MIC_AC);
			return err;
		}
	}

	/* turn off the mic by default */
	olpc_mic_bias(ac97, 0);
	return 0;
}

void olpc_quirks_cleanup(void)
{
	gpio_free(OLPC_GPIO_MIC_AC);
}