Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/hda/core/ext/controller.c
26481 views
1
// SPDX-License-Identifier: GPL-2.0-only
2
/*
3
* hdac-ext-controller.c - HD-audio extended controller functions.
4
*
5
* Copyright (C) 2014-2015 Intel Corp
6
* Author: Jeeja KP <[email protected]>
7
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8
*
9
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10
*/
11
12
#include <linux/bitfield.h>
13
#include <linux/delay.h>
14
#include <linux/slab.h>
15
#include <sound/hda_register.h>
16
#include <sound/hdaudio_ext.h>
17
18
/*
19
* processing pipe helpers - these helpers are useful for dealing with HDA
20
* new capability of processing pipelines
21
*/
22
23
/**
24
* snd_hdac_ext_bus_ppcap_enable - enable/disable processing pipe capability
25
* @bus: the pointer to HDAC bus object
26
* @enable: flag to turn on/off the capability
27
*/
28
void snd_hdac_ext_bus_ppcap_enable(struct hdac_bus *bus, bool enable)
29
{
30
31
if (!bus->ppcap) {
32
dev_err(bus->dev, "Address of PP capability is NULL");
33
return;
34
}
35
36
if (enable)
37
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
38
AZX_PPCTL_GPROCEN, AZX_PPCTL_GPROCEN);
39
else
40
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
41
AZX_PPCTL_GPROCEN, 0);
42
}
43
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_enable);
44
45
/**
46
* snd_hdac_ext_bus_ppcap_int_enable - ppcap interrupt enable/disable
47
* @bus: the pointer to HDAC bus object
48
* @enable: flag to enable/disable interrupt
49
*/
50
void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_bus *bus, bool enable)
51
{
52
53
if (!bus->ppcap) {
54
dev_err(bus->dev, "Address of PP capability is NULL\n");
55
return;
56
}
57
58
if (enable)
59
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
60
AZX_PPCTL_PIE, AZX_PPCTL_PIE);
61
else
62
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
63
AZX_PPCTL_PIE, 0);
64
}
65
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_int_enable);
66
67
/*
68
* Multilink helpers - these helpers are useful for dealing with HDA
69
* new multilink capability
70
*/
71
72
/**
73
* snd_hdac_ext_bus_get_ml_capabilities - get multilink capability
74
* @bus: the pointer to HDAC bus object
75
*
76
* This will parse all links and read the mlink capabilities and add them
77
* in hlink_list of extended hdac bus
78
* Note: this will be freed on bus exit by driver
79
*/
80
int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus)
81
{
82
int idx;
83
u32 link_count;
84
struct hdac_ext_link *hlink;
85
u32 leptr;
86
87
link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
88
89
dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count);
90
91
for (idx = 0; idx < link_count; idx++) {
92
hlink = kzalloc(sizeof(*hlink), GFP_KERNEL);
93
if (!hlink)
94
return -ENOMEM;
95
hlink->index = idx;
96
hlink->bus = bus;
97
hlink->ml_addr = bus->mlcap + AZX_ML_BASE +
98
(AZX_ML_INTERVAL * idx);
99
hlink->lcaps = readl(hlink->ml_addr + AZX_REG_ML_LCAP);
100
hlink->lsdiid = readw(hlink->ml_addr + AZX_REG_ML_LSDIID);
101
hlink->slcount = FIELD_GET(AZX_ML_HDA_LCAP_SLCOUNT, hlink->lcaps) + 1;
102
103
if (hdac_ext_link_alt(hlink)) {
104
leptr = readl(hlink->ml_addr + AZX_REG_ML_LEPTR);
105
hlink->id = FIELD_GET(AZX_REG_ML_LEPTR_ID, leptr);
106
}
107
108
/* since link in On, update the ref */
109
hlink->ref_count = 1;
110
111
list_add_tail(&hlink->list, &bus->hlink_list);
112
}
113
114
return 0;
115
}
116
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_ml_capabilities);
117
118
/**
119
* snd_hdac_ext_link_free_all- free hdac extended link objects
120
*
121
* @bus: the pointer to HDAC bus object
122
*/
123
124
void snd_hdac_ext_link_free_all(struct hdac_bus *bus)
125
{
126
struct hdac_ext_link *hlink;
127
128
while (!list_empty(&bus->hlink_list)) {
129
hlink = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list);
130
list_del(&hlink->list);
131
kfree(hlink);
132
}
133
}
134
EXPORT_SYMBOL_GPL(snd_hdac_ext_link_free_all);
135
136
struct hdac_ext_link *snd_hdac_ext_bus_get_hlink_by_id(struct hdac_bus *bus, u32 id)
137
{
138
struct hdac_ext_link *hlink;
139
140
list_for_each_entry(hlink, &bus->hlink_list, list)
141
if (hdac_ext_link_alt(hlink) && hlink->id == id)
142
return hlink;
143
return NULL;
144
}
145
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_hlink_by_id);
146
147
/**
148
* snd_hdac_ext_bus_get_hlink_by_addr - get hlink at specified address
149
* @bus: hlink's parent bus device
150
* @addr: codec device address
151
*
152
* Returns hlink object or NULL if matching hlink is not found.
153
*/
154
struct hdac_ext_link *snd_hdac_ext_bus_get_hlink_by_addr(struct hdac_bus *bus, int addr)
155
{
156
struct hdac_ext_link *hlink;
157
158
list_for_each_entry(hlink, &bus->hlink_list, list)
159
if (hlink->lsdiid & (0x1 << addr))
160
return hlink;
161
return NULL;
162
}
163
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_hlink_by_addr);
164
165
/**
166
* snd_hdac_ext_bus_get_hlink_by_name - get hlink based on codec name
167
* @bus: the pointer to HDAC bus object
168
* @codec_name: codec name
169
*/
170
struct hdac_ext_link *snd_hdac_ext_bus_get_hlink_by_name(struct hdac_bus *bus,
171
const char *codec_name)
172
{
173
int bus_idx, addr;
174
175
if (sscanf(codec_name, "ehdaudio%dD%d", &bus_idx, &addr) != 2)
176
return NULL;
177
if (bus->idx != bus_idx)
178
return NULL;
179
if (addr < 0 || addr > 31)
180
return NULL;
181
182
return snd_hdac_ext_bus_get_hlink_by_addr(bus, addr);
183
}
184
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_hlink_by_name);
185
186
static int check_hdac_link_power_active(struct hdac_ext_link *hlink, bool enable)
187
{
188
int timeout;
189
u32 val;
190
int mask = (1 << AZX_ML_LCTL_CPA_SHIFT);
191
192
udelay(3);
193
timeout = 150;
194
195
do {
196
val = readl(hlink->ml_addr + AZX_REG_ML_LCTL);
197
if (enable) {
198
if (((val & mask) >> AZX_ML_LCTL_CPA_SHIFT))
199
return 0;
200
} else {
201
if (!((val & mask) >> AZX_ML_LCTL_CPA_SHIFT))
202
return 0;
203
}
204
udelay(3);
205
} while (--timeout);
206
207
return -EIO;
208
}
209
210
/**
211
* snd_hdac_ext_bus_link_power_up -power up hda link
212
* @hlink: HD-audio extended link
213
*/
214
int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *hlink)
215
{
216
snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL,
217
AZX_ML_LCTL_SPA, AZX_ML_LCTL_SPA);
218
219
return check_hdac_link_power_active(hlink, true);
220
}
221
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up);
222
223
/**
224
* snd_hdac_ext_bus_link_power_down -power down hda link
225
* @hlink: HD-audio extended link
226
*/
227
int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *hlink)
228
{
229
snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL, AZX_ML_LCTL_SPA, 0);
230
231
return check_hdac_link_power_active(hlink, false);
232
}
233
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down);
234
235
/**
236
* snd_hdac_ext_bus_link_power_up_all -power up all hda link
237
* @bus: the pointer to HDAC bus object
238
*/
239
int snd_hdac_ext_bus_link_power_up_all(struct hdac_bus *bus)
240
{
241
struct hdac_ext_link *hlink = NULL;
242
int ret;
243
244
list_for_each_entry(hlink, &bus->hlink_list, list) {
245
ret = snd_hdac_ext_bus_link_power_up(hlink);
246
if (ret < 0)
247
return ret;
248
}
249
250
return 0;
251
}
252
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up_all);
253
254
/**
255
* snd_hdac_ext_bus_link_power_down_all -power down all hda link
256
* @bus: the pointer to HDAC bus object
257
*/
258
int snd_hdac_ext_bus_link_power_down_all(struct hdac_bus *bus)
259
{
260
struct hdac_ext_link *hlink = NULL;
261
int ret;
262
263
list_for_each_entry(hlink, &bus->hlink_list, list) {
264
ret = snd_hdac_ext_bus_link_power_down(hlink);
265
if (ret < 0)
266
return ret;
267
}
268
269
return 0;
270
}
271
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down_all);
272
273
/**
274
* snd_hdac_ext_bus_link_set_stream_id - maps stream id to link output
275
* @link: HD-audio ext link to set up
276
* @stream: stream id
277
*/
278
void snd_hdac_ext_bus_link_set_stream_id(struct hdac_ext_link *link,
279
int stream)
280
{
281
snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 1 << stream);
282
}
283
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_set_stream_id);
284
285
/**
286
* snd_hdac_ext_bus_link_clear_stream_id - maps stream id to link output
287
* @link: HD-audio ext link to set up
288
* @stream: stream id
289
*/
290
void snd_hdac_ext_bus_link_clear_stream_id(struct hdac_ext_link *link,
291
int stream)
292
{
293
snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 0);
294
}
295
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_clear_stream_id);
296
297
int snd_hdac_ext_bus_link_get(struct hdac_bus *bus,
298
struct hdac_ext_link *hlink)
299
{
300
unsigned long codec_mask;
301
int ret = 0;
302
303
mutex_lock(&bus->lock);
304
305
/*
306
* if we move from 0 to 1, count will be 1 so power up this link
307
* as well, also check the dma status and trigger that
308
*/
309
if (++hlink->ref_count == 1) {
310
if (!bus->cmd_dma_state) {
311
snd_hdac_bus_init_cmd_io(bus);
312
bus->cmd_dma_state = true;
313
}
314
315
ret = snd_hdac_ext_bus_link_power_up(hlink);
316
317
/*
318
* clear the register to invalidate all the output streams
319
*/
320
snd_hdac_updatew(hlink->ml_addr, AZX_REG_ML_LOSIDV,
321
AZX_ML_LOSIDV_STREAM_MASK, 0);
322
/*
323
* wait for 521usec for codec to report status
324
* HDA spec section 4.3 - Codec Discovery
325
*/
326
udelay(521);
327
codec_mask = snd_hdac_chip_readw(bus, STATESTS);
328
dev_dbg(bus->dev, "codec_mask = 0x%lx\n", codec_mask);
329
snd_hdac_chip_writew(bus, STATESTS, codec_mask);
330
if (!bus->codec_mask)
331
bus->codec_mask = codec_mask;
332
}
333
334
mutex_unlock(&bus->lock);
335
return ret;
336
}
337
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_get);
338
339
int snd_hdac_ext_bus_link_put(struct hdac_bus *bus,
340
struct hdac_ext_link *hlink)
341
{
342
int ret = 0;
343
struct hdac_ext_link *hlink_tmp;
344
bool link_up = false;
345
346
mutex_lock(&bus->lock);
347
348
/*
349
* if we move from 1 to 0, count will be 0
350
* so power down this link as well
351
*/
352
if (--hlink->ref_count == 0) {
353
ret = snd_hdac_ext_bus_link_power_down(hlink);
354
355
/*
356
* now check if all links are off, if so turn off
357
* cmd dma as well
358
*/
359
list_for_each_entry(hlink_tmp, &bus->hlink_list, list) {
360
if (hlink_tmp->ref_count) {
361
link_up = true;
362
break;
363
}
364
}
365
366
if (!link_up) {
367
snd_hdac_bus_stop_cmd_io(bus);
368
bus->cmd_dma_state = false;
369
}
370
}
371
372
mutex_unlock(&bus->lock);
373
return ret;
374
}
375
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_put);
376
377
static void hdac_ext_codec_link_up(struct hdac_device *codec)
378
{
379
const char *devname = dev_name(&codec->dev);
380
struct hdac_ext_link *hlink =
381
snd_hdac_ext_bus_get_hlink_by_name(codec->bus, devname);
382
383
if (hlink)
384
snd_hdac_ext_bus_link_get(codec->bus, hlink);
385
}
386
387
static void hdac_ext_codec_link_down(struct hdac_device *codec)
388
{
389
const char *devname = dev_name(&codec->dev);
390
struct hdac_ext_link *hlink =
391
snd_hdac_ext_bus_get_hlink_by_name(codec->bus, devname);
392
393
if (hlink)
394
snd_hdac_ext_bus_link_put(codec->bus, hlink);
395
}
396
397
void snd_hdac_ext_bus_link_power(struct hdac_device *codec, bool enable)
398
{
399
struct hdac_bus *bus = codec->bus;
400
bool oldstate = test_bit(codec->addr, &bus->codec_powered);
401
402
if (enable == oldstate)
403
return;
404
405
snd_hdac_bus_link_power(codec, enable);
406
407
if (enable)
408
hdac_ext_codec_link_up(codec);
409
else
410
hdac_ext_codec_link_down(codec);
411
}
412
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power);
413
414