Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/hda/codecs/senarytech.c
51976 views
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
* HD audio codec driver for Senary HDA audio codec
4
*
5
* Initially based on conexant.c
6
*/
7
8
#include <linux/init.h>
9
#include <linux/delay.h>
10
#include <linux/slab.h>
11
#include <linux/module.h>
12
#include <sound/core.h>
13
#include <sound/jack.h>
14
15
#include <sound/hda_codec.h>
16
#include "hda_local.h"
17
#include "hda_auto_parser.h"
18
#include "hda_beep.h"
19
#include "hda_jack.h"
20
#include "generic.h"
21
22
/* GPIO node ID */
23
#define SENARY_GPIO_NODE 0x01
24
25
struct senary_spec {
26
struct hda_gen_spec gen;
27
28
/* extra EAPD pins */
29
unsigned int num_eapds;
30
hda_nid_t eapds[4];
31
hda_nid_t mute_led_eapd;
32
33
unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */
34
35
int mute_led_polarity;
36
unsigned int gpio_led;
37
unsigned int gpio_mute_led_mask;
38
unsigned int gpio_mic_led_mask;
39
};
40
41
#ifdef CONFIG_SND_HDA_INPUT_BEEP
42
/* additional beep mixers; private_value will be overwritten */
43
static const struct snd_kcontrol_new senary_beep_mixer[] = {
44
HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0, 1, 0, HDA_OUTPUT),
45
HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch", 0, 1, 0, HDA_OUTPUT),
46
};
47
48
static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid,
49
int idx, int dir)
50
{
51
struct snd_kcontrol_new *knew;
52
unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir);
53
int i;
54
55
spec->gen.beep_nid = nid;
56
for (i = 0; i < ARRAY_SIZE(senary_beep_mixer); i++) {
57
knew = snd_hda_gen_add_kctl(&spec->gen, NULL,
58
&senary_beep_mixer[i]);
59
if (!knew)
60
return -ENOMEM;
61
knew->private_value = beep_amp;
62
}
63
return 0;
64
}
65
66
static int senary_auto_parse_beep(struct hda_codec *codec)
67
{
68
struct senary_spec *spec = codec->spec;
69
hda_nid_t nid;
70
71
for_each_hda_codec_node(nid, codec)
72
if ((get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP) &&
73
(get_wcaps(codec, nid) & (AC_WCAP_OUT_AMP | AC_WCAP_AMP_OVRD)))
74
return set_beep_amp(spec, nid, 0, HDA_OUTPUT);
75
return 0;
76
}
77
#else
78
#define senary_auto_parse_beep(codec) 0
79
#endif
80
81
/* parse EAPDs */
82
static void senary_auto_parse_eapd(struct hda_codec *codec)
83
{
84
struct senary_spec *spec = codec->spec;
85
hda_nid_t nid;
86
87
for_each_hda_codec_node(nid, codec) {
88
if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
89
continue;
90
if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD))
91
continue;
92
spec->eapds[spec->num_eapds++] = nid;
93
if (spec->num_eapds >= ARRAY_SIZE(spec->eapds))
94
break;
95
}
96
}
97
98
static void senary_auto_turn_eapd(struct hda_codec *codec, int num_pins,
99
const hda_nid_t *pins, bool on)
100
{
101
int i;
102
103
for (i = 0; i < num_pins; i++) {
104
if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD)
105
snd_hda_codec_write(codec, pins[i], 0,
106
AC_VERB_SET_EAPD_BTLENABLE,
107
on ? 0x02 : 0);
108
}
109
}
110
111
/* turn on/off EAPD according to Master switch */
112
static void senary_auto_vmaster_hook(void *private_data, int enabled)
113
{
114
struct hda_codec *codec = private_data;
115
struct senary_spec *spec = codec->spec;
116
117
senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, enabled);
118
}
119
120
static void senary_init_gpio_led(struct hda_codec *codec)
121
{
122
struct senary_spec *spec = codec->spec;
123
unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask;
124
125
if (mask) {
126
snd_hda_codec_write(codec, SENARY_GPIO_NODE, 0, AC_VERB_SET_GPIO_MASK,
127
mask);
128
snd_hda_codec_write(codec, SENARY_GPIO_NODE, 0, AC_VERB_SET_GPIO_DIRECTION,
129
mask);
130
snd_hda_codec_write(codec, SENARY_GPIO_NODE, 0, AC_VERB_SET_GPIO_DATA,
131
spec->gpio_led);
132
}
133
}
134
135
static int senary_init(struct hda_codec *codec)
136
{
137
snd_hda_gen_init(codec);
138
senary_init_gpio_led(codec);
139
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
140
141
return 0;
142
}
143
144
static void senary_shutdown(struct hda_codec *codec)
145
{
146
struct senary_spec *spec = codec->spec;
147
148
/* Turn the problematic codec into D3 to avoid spurious noises
149
* from the internal speaker during (and after) reboot
150
*/
151
senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, false);
152
}
153
154
static void senary_remove(struct hda_codec *codec)
155
{
156
senary_shutdown(codec);
157
snd_hda_gen_remove(codec);
158
}
159
160
static int senary_suspend(struct hda_codec *codec)
161
{
162
senary_shutdown(codec);
163
return 0;
164
}
165
166
static int senary_probe(struct hda_codec *codec, const struct hda_device_id *id)
167
{
168
struct senary_spec *spec;
169
int err;
170
171
codec_info(codec, "%s: BIOS auto-probing.\n", codec->core.chip_name);
172
173
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
174
if (!spec)
175
return -ENOMEM;
176
snd_hda_gen_spec_init(&spec->gen);
177
codec->spec = spec;
178
179
senary_auto_parse_eapd(codec);
180
spec->gen.own_eapd_ctl = 1;
181
182
if (!spec->gen.vmaster_mute.hook)
183
spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook;
184
185
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
186
187
err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL,
188
spec->parse_flags);
189
if (err < 0)
190
goto error;
191
192
err = senary_auto_parse_beep(codec);
193
if (err < 0)
194
goto error;
195
196
err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
197
if (err < 0)
198
goto error;
199
200
/* Some laptops with Senary chips show stalls in S3 resume,
201
* which falls into the single-cmd mode.
202
* Better to make reset, then.
203
*/
204
if (!codec->bus->core.sync_write) {
205
codec_info(codec,
206
"Enable sync_write for stable communication\n");
207
codec->bus->core.sync_write = 1;
208
codec->bus->allow_bus_reset = 1;
209
}
210
211
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
212
213
return 0;
214
215
error:
216
senary_remove(codec);
217
return err;
218
}
219
220
static const struct hda_codec_ops senary_codec_ops = {
221
.probe = senary_probe,
222
.remove = senary_remove,
223
.build_controls = snd_hda_gen_build_controls,
224
.build_pcms = snd_hda_gen_build_pcms,
225
.init = senary_init,
226
.unsol_event = snd_hda_jack_unsol_event,
227
.suspend = senary_suspend,
228
.check_power_status = snd_hda_gen_check_power_status,
229
.stream_pm = snd_hda_gen_stream_pm,
230
};
231
232
/*
233
*/
234
235
static const struct hda_device_id snd_hda_id_senary[] = {
236
HDA_CODEC_ID(0x1fa86186, "SN6186"),
237
{} /* terminator */
238
};
239
MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_senary);
240
241
MODULE_LICENSE("GPL");
242
MODULE_DESCRIPTION("Senarytech HD-audio codec");
243
244
static struct hda_codec_driver senary_driver = {
245
.id = snd_hda_id_senary,
246
.ops = &senary_codec_ops,
247
};
248
249
module_hda_codec_driver(senary_driver);
250
251