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
RevLine 
[b229062]1/*
[a64970e1]2 * Copyright (c) 2025 Jiri Svoboda
[b229062]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>
[a333b7f]36#include <bitops.h>
[b229062]37#include <ddi.h>
[d51838f]38#include <device/hw_res.h>
[b229062]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>
[a333b7f]44#include <ddf/interrupt.h>
[b229062]45#include <ddf/log.h>
46
47#include "hdactl.h"
48#include "hdaudio.h"
[93c3163]49#include "pcm_iface.h"
[7978d1e7]50#include "spec/regs.h"
[b229062]51
52#define NAME "hdaudio"
53
[b7fd2a0]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);
[a64970e1]57static errno_t hda_dev_quiesce(ddf_dev_t *dev);
[b7fd2a0]58static errno_t hda_fun_online(ddf_fun_t *fun);
59static errno_t hda_fun_offline(ddf_fun_t *fun);
[b229062]60
[60744cb]61static void hdaudio_interrupt(ipc_call_t *, void *);
[a333b7f]62
[b229062]63static driver_ops_t driver_ops = {
[a64970e1]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
[b229062]70};
71
72static driver_t hda_driver = {
73 .name = NAME,
74 .driver_ops = &driver_ops
75};
76
[93c3163]77ddf_dev_ops_t hda_pcm_ops = {
78 .interfaces[AUDIO_PCM_BUFFER_IFACE] = &hda_pcm_iface
79};
80
[a333b7f]81irq_pio_range_t hdaudio_irq_pio_ranges[] = {
82 {
83 .base = 0,
84 .size = 8192
85 }
86};
87
88irq_cmd_t hdaudio_irq_commands[] = {
[31ccd42a]89 /* 0 */
[a333b7f]90 {
91 .cmd = CMD_PIO_READ_8,
[903eff5]92 .addr = NULL, /* rirbsts */
[a333b7f]93 .dstarg = 2
94 },
[31ccd42a]95 /* 1 */
[a333b7f]96 {
97 .cmd = CMD_AND,
98 .value = BIT_V(uint8_t, rirbsts_intfl),
99 .srcarg = 2,
100 .dstarg = 3
101 },
[31ccd42a]102 /* 2 */
[a333b7f]103 {
104 .cmd = CMD_PREDICATE,
105 .value = 2,
106 .srcarg = 3
107 },
[31ccd42a]108 /* 3 */
[a333b7f]109 {
110 .cmd = CMD_PIO_WRITE_8,
[903eff5]111 .addr = NULL, /* rirbsts */
[31ccd42a]112 .value = BIT_V(uint8_t, rirbsts_intfl)
[a333b7f]113 },
[31ccd42a]114 /* 4 */
[a333b7f]115 {
116 .cmd = CMD_ACCEPT
117 }
118};
119
[31ccd42a]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 */
[0d59ea7e]144 .value = BIT_V(uint8_t, sdsts_bcis)
[31ccd42a]145 },
146 /* 4 */
147 {
148 .cmd = CMD_ACCEPT
149 }
[a333b7f]150};
151
[b7fd2a0]152static errno_t hda_dev_add(ddf_dev_t *dev)
[b229062]153{
[de16f89]154 ddf_fun_t *fun_pcm = NULL;
[4f87a85a]155 bool bound = false;
[b229062]156 hda_t *hda = NULL;
157 hw_res_list_parsed_t res;
[31ccd42a]158 irq_code_t irq_code;
[de16f89]159 irq_cmd_t *cmds = NULL;
[31ccd42a]160 size_t ncmds_base;
161 size_t ncmds_sdesc;
162 size_t ncmds;
163 int i;
[de16f89]164 void *regs = NULL;
[b7fd2a0]165 errno_t rc;
[b229062]166
[cf78637]167 ddf_msg(LVL_DEBUG, "hda_dev_add()");
[de16f89]168 hw_res_list_parsed_init(&res);
[b229062]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
[57a2208]177 fibril_mutex_initialize(&hda->lock);
178
[cf78637]179 ddf_msg(LVL_DEBUG, "create parent sess");
[2fd26bb]180 hda->parent_sess = ddf_dev_parent_sess_get(dev);
[b229062]181 if (hda->parent_sess == NULL) {
182 ddf_msg(LVL_ERROR, "Failed connecting parent driver.\n");
183 rc = ENOMEM;
184 goto error;
185 }
186
[cf78637]187 ddf_msg(LVL_DEBUG, "get HW res list");
[b229062]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
[cf78637]203 ddf_msg(LVL_DEBUG, "hda reg base: %" PRIx64,
[3bacee1]204 RNGABS(res.mem_ranges.ranges[0]));
[a333b7f]205
[b229062]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
[cf78637]212 ddf_msg(LVL_DEBUG, "enable PIO");
[b229062]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
[cf78637]221 ddf_msg(LVL_DEBUG, "IRQs: %zu", res.irqs.count);
[a333b7f]222 if (res.irqs.count != 1) {
[149dd52d]223 ddf_msg(LVL_ERROR, "Unexpected IRQ count %zu (!= 1)",
[a333b7f]224 res.irqs.count);
225 goto error;
226 }
[cf78637]227 ddf_msg(LVL_DEBUG, "interrupt no: %d", res.irqs.irqs[0]);
[a333b7f]228
[31ccd42a]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
[a333b7f]245 hda_regs_t *rphys = (hda_regs_t *)(uintptr_t)hda->rwbase;
246 hdaudio_irq_pio_ranges[0].base = (uintptr_t)hda->rwbase;
[31ccd42a]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
[cf78637]260 ddf_msg(LVL_DEBUG, "range0.base=%zu", hdaudio_irq_pio_ranges[0].base);
[a333b7f]261
[d51838f]262 rc = hw_res_enable_interrupt(hda->parent_sess, res.irqs.irqs[0]);
[1e92bc3]263 if (rc != EOK) {
[c1694b6b]264 ddf_msg(LVL_ERROR, "Failed enabling interrupt.: %s", str_error(rc));
[1e92bc3]265 goto error;
266 }
267
[eadaeae8]268 cap_irq_handle_t irq_cap;
[071a1ddb]269 rc = register_interrupt_handler(dev, res.irqs.irqs[0],
[60744cb]270 hdaudio_interrupt, (void *)hda, &irq_code, &irq_cap);
[071a1ddb]271 if (rc != EOK) {
[dd8ab1c]272 ddf_msg(LVL_ERROR, "Failed registering interrupt handler: %s",
273 str_error_name(rc));
[a333b7f]274 goto error;
275 }
276
[de16f89]277 free(cmds);
278 cmds = NULL;
279
[7978d1e7]280 if (hda_ctl_init(hda) == NULL) {
[b229062]281 rc = EIO;
282 goto error;
283 }
284
[cf78637]285 ddf_msg(LVL_DEBUG, "create function");
[93c3163]286 fun_pcm = ddf_fun_create(dev, fun_exposed, "pcm");
287 if (fun_pcm == NULL) {
288 ddf_msg(LVL_ERROR, "Failed creating function 'pcm'.");
[b229062]289 rc = ENOMEM;
290 goto error;
291 }
292
[93c3163]293 hda->fun_pcm = fun_pcm;
294
295 ddf_fun_set_ops(fun_pcm, &hda_pcm_ops);
[b229062]296
[93c3163]297 rc = ddf_fun_bind(fun_pcm);
[b229062]298 if (rc != EOK) {
[93c3163]299 ddf_msg(LVL_ERROR, "Failed binding function 'pcm'.");
300 ddf_fun_destroy(fun_pcm);
[b229062]301 goto error;
302 }
303
[4f87a85a]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 }
[de16f89]311
312 hw_res_list_parsed_clean(&res);
[b229062]313 return EOK;
314error:
[4f87a85a]315 if (bound)
316 ddf_fun_unbind(fun_pcm);
[de16f89]317 if (fun_pcm != NULL)
318 ddf_fun_destroy(fun_pcm);
[b229062]319 if (hda != NULL) {
320 if (hda->ctl != NULL)
321 hda_ctl_fini(hda->ctl);
322 }
[de16f89]323 free(cmds);
324 // pio_disable(regs);
325 hw_res_list_parsed_clean(&res);
[b229062]326
[cf78637]327 ddf_msg(LVL_DEBUG, "Failing hda_dev_add() -> %s", str_error_name(rc));
[b229062]328 return rc;
329}
330
[b7fd2a0]331static errno_t hda_dev_remove(ddf_dev_t *dev)
[b229062]332{
333 hda_t *hda = (hda_t *)ddf_dev_data_get(dev);
[b7fd2a0]334 errno_t rc;
[b229062]335
336 ddf_msg(LVL_DEBUG, "hda_dev_remove(%p)", dev);
337
[93c3163]338 if (hda->fun_pcm != NULL) {
339 rc = ddf_fun_offline(hda->fun_pcm);
[b229062]340 if (rc != EOK)
341 return rc;
342
[93c3163]343 rc = ddf_fun_unbind(hda->fun_pcm);
[b229062]344 if (rc != EOK)
345 return rc;
346 }
347
[de16f89]348 hda_ctl_fini(hda->ctl);
349 // pio_disable(regs);
[b229062]350 return EOK;
351}
352
[b7fd2a0]353static errno_t hda_dev_gone(ddf_dev_t *dev)
[b229062]354{
355 hda_t *hda = (hda_t *)ddf_dev_data_get(dev);
[b7fd2a0]356 errno_t rc;
[b229062]357
358 ddf_msg(LVL_DEBUG, "hda_dev_remove(%p)", dev);
359
[93c3163]360 if (hda->fun_pcm != NULL) {
361 rc = ddf_fun_unbind(hda->fun_pcm);
[b229062]362 if (rc != EOK)
363 return rc;
364 }
365
366 return EOK;
367}
368
[a64970e1]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
[b7fd2a0]379static errno_t hda_fun_online(ddf_fun_t *fun)
[b229062]380{
381 ddf_msg(LVL_DEBUG, "hda_fun_online()");
382 return ddf_fun_online(fun);
383}
384
[b7fd2a0]385static errno_t hda_fun_offline(ddf_fun_t *fun)
[b229062]386{
387 ddf_msg(LVL_DEBUG, "hda_fun_offline()");
388 return ddf_fun_offline(fun);
389}
390
[60744cb]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)
[a333b7f]397{
[60744cb]398 hda_t *hda = (hda_t *)arg;
[a333b7f]399
[3bacee1]400 if (0)
401 ddf_msg(LVL_NOTE, "## interrupt ##");
[57a2208]402 hda_ctl_interrupt(hda->ctl);
403
[fafb8e5]404 if (ipc_get_arg3(icall) != 0) {
[57a2208]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);
[0e4c5f0]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);
[57a2208]417 }
[0e4c5f0]418
[57a2208]419 hda_unlock(hda);
[159c722d]420 }
[57a2208]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);
[a333b7f]431}
432
[b229062]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.