source: mainline/uspace/drv/audio/hdaudio/hdaudio.c

Last change on this file was a64970e1, checked in by Jiri Svoboda <jiri@…>, 4 months ago

Implement quiesce in HD Audio and SB16 drivers.

  • Property mode set to 100644
File size: 10.1 KB
Line 
1/*
2 * Copyright (c) 2025 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup hdaudio
30 * @{
31 */
32/** @file High Definition Audio driver
33 */
34
35#include <assert.h>
36#include <bitops.h>
37#include <ddi.h>
38#include <device/hw_res.h>
39#include <device/hw_res_parsed.h>
40#include <stdio.h>
41#include <errno.h>
42#include <str_error.h>
43#include <ddf/driver.h>
44#include <ddf/interrupt.h>
45#include <ddf/log.h>
46
47#include "hdactl.h"
48#include "hdaudio.h"
49#include "pcm_iface.h"
50#include "spec/regs.h"
51
52#define NAME "hdaudio"
53
54static errno_t hda_dev_add(ddf_dev_t *dev);
55static errno_t hda_dev_remove(ddf_dev_t *dev);
56static errno_t hda_dev_gone(ddf_dev_t *dev);
57static errno_t hda_dev_quiesce(ddf_dev_t *dev);
58static errno_t hda_fun_online(ddf_fun_t *fun);
59static errno_t hda_fun_offline(ddf_fun_t *fun);
60
61static void hdaudio_interrupt(ipc_call_t *, void *);
62
63static driver_ops_t driver_ops = {
64 .dev_add = hda_dev_add,
65 .dev_remove = hda_dev_remove,
66 .dev_gone = hda_dev_gone,
67 .dev_quiesce = hda_dev_quiesce,
68 .fun_online = hda_fun_online,
69 .fun_offline = hda_fun_offline
70};
71
72static driver_t hda_driver = {
73 .name = NAME,
74 .driver_ops = &driver_ops
75};
76
77ddf_dev_ops_t hda_pcm_ops = {
78 .interfaces[AUDIO_PCM_BUFFER_IFACE] = &hda_pcm_iface
79};
80
81irq_pio_range_t hdaudio_irq_pio_ranges[] = {
82 {
83 .base = 0,
84 .size = 8192
85 }
86};
87
88irq_cmd_t hdaudio_irq_commands[] = {
89 /* 0 */
90 {
91 .cmd = CMD_PIO_READ_8,
92 .addr = NULL, /* rirbsts */
93 .dstarg = 2
94 },
95 /* 1 */
96 {
97 .cmd = CMD_AND,
98 .value = BIT_V(uint8_t, rirbsts_intfl),
99 .srcarg = 2,
100 .dstarg = 3
101 },
102 /* 2 */
103 {
104 .cmd = CMD_PREDICATE,
105 .value = 2,
106 .srcarg = 3
107 },
108 /* 3 */
109 {
110 .cmd = CMD_PIO_WRITE_8,
111 .addr = NULL, /* rirbsts */
112 .value = BIT_V(uint8_t, rirbsts_intfl)
113 },
114 /* 4 */
115 {
116 .cmd = CMD_ACCEPT
117 }
118};
119
120irq_cmd_t hdaudio_irq_commands_sdesc[] = {
121 /* 0 */
122 {
123 .cmd = CMD_PIO_READ_32,
124 .addr = NULL, /* intsts */
125 .dstarg = 2
126 },
127 /* 1 */
128 {
129 .cmd = CMD_AND,
130 .value = 0, /* 1 << idx */
131 .srcarg = 2,
132 .dstarg = 3,
133 },
134 /* 2 */
135 {
136 .cmd = CMD_PREDICATE,
137 .value = 2,
138 .srcarg = 3
139 },
140 /* 3 */
141 {
142 .cmd = CMD_PIO_WRITE_8,
143 .addr = NULL, /* sdesc[x].sts */
144 .value = BIT_V(uint8_t, sdsts_bcis)
145 },
146 /* 4 */
147 {
148 .cmd = CMD_ACCEPT
149 }
150};
151
152static errno_t hda_dev_add(ddf_dev_t *dev)
153{
154 ddf_fun_t *fun_pcm = NULL;
155 bool bound = false;
156 hda_t *hda = NULL;
157 hw_res_list_parsed_t res;
158 irq_code_t irq_code;
159 irq_cmd_t *cmds = NULL;
160 size_t ncmds_base;
161 size_t ncmds_sdesc;
162 size_t ncmds;
163 int i;
164 void *regs = NULL;
165 errno_t rc;
166
167 ddf_msg(LVL_DEBUG, "hda_dev_add()");
168 hw_res_list_parsed_init(&res);
169
170 hda = ddf_dev_data_alloc(dev, sizeof(hda_t));
171 if (hda == NULL) {
172 ddf_msg(LVL_ERROR, "Failed allocating soft state.\n");
173 rc = ENOMEM;
174 goto error;
175 }
176
177 fibril_mutex_initialize(&hda->lock);
178
179 ddf_msg(LVL_DEBUG, "create parent sess");
180 hda->parent_sess = ddf_dev_parent_sess_get(dev);
181 if (hda->parent_sess == NULL) {
182 ddf_msg(LVL_ERROR, "Failed connecting parent driver.\n");
183 rc = ENOMEM;
184 goto error;
185 }
186
187 ddf_msg(LVL_DEBUG, "get HW res list");
188 rc = hw_res_get_list_parsed(hda->parent_sess, &res, 0);
189 if (rc != EOK) {
190 ddf_msg(LVL_ERROR, "Failed getting resource list.\n");
191 goto error;
192 }
193
194 if (res.mem_ranges.count != 1) {
195 ddf_msg(LVL_ERROR, "Expected exactly one memory range.\n");
196 rc = EINVAL;
197 goto error;
198 }
199
200 hda->rwbase = RNGABS(res.mem_ranges.ranges[0]);
201 hda->rwsize = RNGSZ(res.mem_ranges.ranges[0]);
202
203 ddf_msg(LVL_DEBUG, "hda reg base: %" PRIx64,
204 RNGABS(res.mem_ranges.ranges[0]));
205
206 if (hda->rwsize < sizeof(hda_regs_t)) {
207 ddf_msg(LVL_ERROR, "Memory range is too small.");
208 rc = EINVAL;
209 goto error;
210 }
211
212 ddf_msg(LVL_DEBUG, "enable PIO");
213 rc = pio_enable((void *)(uintptr_t)hda->rwbase, hda->rwsize, &regs);
214 if (rc != EOK) {
215 ddf_msg(LVL_ERROR, "Error enabling PIO range.");
216 goto error;
217 }
218
219 hda->regs = (hda_regs_t *)regs;
220
221 ddf_msg(LVL_DEBUG, "IRQs: %zu", res.irqs.count);
222 if (res.irqs.count != 1) {
223 ddf_msg(LVL_ERROR, "Unexpected IRQ count %zu (!= 1)",
224 res.irqs.count);
225 goto error;
226 }
227 ddf_msg(LVL_DEBUG, "interrupt no: %d", res.irqs.irqs[0]);
228
229 ncmds_base = sizeof(hdaudio_irq_commands) / sizeof(irq_cmd_t);
230 ncmds_sdesc = sizeof(hdaudio_irq_commands_sdesc) / sizeof(irq_cmd_t);
231 ncmds = ncmds_base + 30 * ncmds_sdesc;
232
233 cmds = calloc(ncmds, sizeof(irq_cmd_t));
234 if (cmds == NULL) {
235 ddf_msg(LVL_ERROR, "Out of memory");
236 goto error;
237 }
238
239 irq_code.rangecount = sizeof(hdaudio_irq_pio_ranges) /
240 sizeof(irq_pio_range_t);
241 irq_code.ranges = hdaudio_irq_pio_ranges;
242 irq_code.cmdcount = ncmds;
243 irq_code.cmds = cmds;
244
245 hda_regs_t *rphys = (hda_regs_t *)(uintptr_t)hda->rwbase;
246 hdaudio_irq_pio_ranges[0].base = (uintptr_t)hda->rwbase;
247
248 memcpy(cmds, hdaudio_irq_commands, sizeof(hdaudio_irq_commands));
249 cmds[0].addr = (void *)&rphys->rirbsts;
250 cmds[3].addr = (void *)&rphys->rirbsts;
251
252 for (i = 0; i < 30; i++) {
253 memcpy(&cmds[ncmds_base + i * ncmds_sdesc],
254 hdaudio_irq_commands_sdesc, sizeof(hdaudio_irq_commands_sdesc));
255 cmds[ncmds_base + i * ncmds_sdesc + 0].addr = (void *)&rphys->intsts;
256 cmds[ncmds_base + i * ncmds_sdesc + 1].value = BIT_V(uint32_t, i);
257 cmds[ncmds_base + i * ncmds_sdesc + 3].addr = (void *)&rphys->sdesc[i].sts;
258 }
259
260 ddf_msg(LVL_DEBUG, "range0.base=%zu", hdaudio_irq_pio_ranges[0].base);
261
262 rc = hw_res_enable_interrupt(hda->parent_sess, res.irqs.irqs[0]);
263 if (rc != EOK) {
264 ddf_msg(LVL_ERROR, "Failed enabling interrupt.: %s", str_error(rc));
265 goto error;
266 }
267
268 cap_irq_handle_t irq_cap;
269 rc = register_interrupt_handler(dev, res.irqs.irqs[0],
270 hdaudio_interrupt, (void *)hda, &irq_code, &irq_cap);
271 if (rc != EOK) {
272 ddf_msg(LVL_ERROR, "Failed registering interrupt handler: %s",
273 str_error_name(rc));
274 goto error;
275 }
276
277 free(cmds);
278 cmds = NULL;
279
280 if (hda_ctl_init(hda) == NULL) {
281 rc = EIO;
282 goto error;
283 }
284
285 ddf_msg(LVL_DEBUG, "create function");
286 fun_pcm = ddf_fun_create(dev, fun_exposed, "pcm");
287 if (fun_pcm == NULL) {
288 ddf_msg(LVL_ERROR, "Failed creating function 'pcm'.");
289 rc = ENOMEM;
290 goto error;
291 }
292
293 hda->fun_pcm = fun_pcm;
294
295 ddf_fun_set_ops(fun_pcm, &hda_pcm_ops);
296
297 rc = ddf_fun_bind(fun_pcm);
298 if (rc != EOK) {
299 ddf_msg(LVL_ERROR, "Failed binding function 'pcm'.");
300 ddf_fun_destroy(fun_pcm);
301 goto error;
302 }
303
304 bound = true;
305
306 rc = ddf_fun_add_to_category(fun_pcm, "audio-pcm");
307 if (rc != EOK) {
308 ddf_msg(LVL_ERROR, "Failed adding function to audio-pcm category.");
309 goto error;
310 }
311
312 hw_res_list_parsed_clean(&res);
313 return EOK;
314error:
315 if (bound)
316 ddf_fun_unbind(fun_pcm);
317 if (fun_pcm != NULL)
318 ddf_fun_destroy(fun_pcm);
319 if (hda != NULL) {
320 if (hda->ctl != NULL)
321 hda_ctl_fini(hda->ctl);
322 }
323 free(cmds);
324 // pio_disable(regs);
325 hw_res_list_parsed_clean(&res);
326
327 ddf_msg(LVL_DEBUG, "Failing hda_dev_add() -> %s", str_error_name(rc));
328 return rc;
329}
330
331static errno_t hda_dev_remove(ddf_dev_t *dev)
332{
333 hda_t *hda = (hda_t *)ddf_dev_data_get(dev);
334 errno_t rc;
335
336 ddf_msg(LVL_DEBUG, "hda_dev_remove(%p)", dev);
337
338 if (hda->fun_pcm != NULL) {
339 rc = ddf_fun_offline(hda->fun_pcm);
340 if (rc != EOK)
341 return rc;
342
343 rc = ddf_fun_unbind(hda->fun_pcm);
344 if (rc != EOK)
345 return rc;
346 }
347
348 hda_ctl_fini(hda->ctl);
349 // pio_disable(regs);
350 return EOK;
351}
352
353static errno_t hda_dev_gone(ddf_dev_t *dev)
354{
355 hda_t *hda = (hda_t *)ddf_dev_data_get(dev);
356 errno_t rc;
357
358 ddf_msg(LVL_DEBUG, "hda_dev_remove(%p)", dev);
359
360 if (hda->fun_pcm != NULL) {
361 rc = ddf_fun_unbind(hda->fun_pcm);
362 if (rc != EOK)
363 return rc;
364 }
365
366 return EOK;
367}
368
369static errno_t hda_dev_quiesce(ddf_dev_t *dev)
370{
371 hda_t *hda = (hda_t *)ddf_dev_data_get(dev);
372
373 ddf_msg(LVL_DEBUG, "hda_dev_quiesce(%p)", dev);
374
375 hda_ctl_quiesce(hda->ctl);
376 return EOK;
377}
378
379static errno_t hda_fun_online(ddf_fun_t *fun)
380{
381 ddf_msg(LVL_DEBUG, "hda_fun_online()");
382 return ddf_fun_online(fun);
383}
384
385static errno_t hda_fun_offline(ddf_fun_t *fun)
386{
387 ddf_msg(LVL_DEBUG, "hda_fun_offline()");
388 return ddf_fun_offline(fun);
389}
390
391/** HD Audio interrupt handler.
392 *
393 * @param icall IRQ event notification
394 * @param arg Argument (hda_t *)
395 */
396static void hdaudio_interrupt(ipc_call_t *icall, void *arg)
397{
398 hda_t *hda = (hda_t *)arg;
399
400 if (0)
401 ddf_msg(LVL_NOTE, "## interrupt ##");
402 hda_ctl_interrupt(hda->ctl);
403
404 if (ipc_get_arg3(icall) != 0) {
405 /* Buffer completed */
406 hda_lock(hda);
407 if (hda->playing) {
408 hda_pcm_event(hda, PCM_EVENT_FRAMES_PLAYED);
409 hda_pcm_event(hda, PCM_EVENT_FRAMES_PLAYED);
410 hda_pcm_event(hda, PCM_EVENT_FRAMES_PLAYED);
411 hda_pcm_event(hda, PCM_EVENT_FRAMES_PLAYED);
412 } else if (hda->capturing) {
413 hda_pcm_event(hda, PCM_EVENT_FRAMES_CAPTURED);
414 hda_pcm_event(hda, PCM_EVENT_FRAMES_CAPTURED);
415 hda_pcm_event(hda, PCM_EVENT_FRAMES_CAPTURED);
416 hda_pcm_event(hda, PCM_EVENT_FRAMES_CAPTURED);
417 }
418
419 hda_unlock(hda);
420 }
421}
422
423void hda_lock(hda_t *hda)
424{
425 fibril_mutex_lock(&hda->lock);
426}
427
428void hda_unlock(hda_t *hda)
429{
430 fibril_mutex_unlock(&hda->lock);
431}
432
433int main(int argc, char *argv[])
434{
435 printf(NAME ": High Definition Audio driver\n");
436 ddf_log_init(NAME);
437 return ddf_driver_main(&hda_driver);
438}
439
440/** @}
441 */
Note: See TracBrowser for help on using the repository browser.