/* * Copyright (c) 2022 Jiri Svoboda * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup hdaudio * @{ */ /** @file High Definition Audio stream */ #include #include #include #include #include #include #include #include #include #include "hdactl.h" #include "hdaudio.h" #include "regif.h" #include "spec/bdl.h" #include "stream.h" errno_t hda_stream_buffers_alloc(hda_t *hda, hda_stream_buffers_t **rbufs) { void *bdl; void *buffer; uintptr_t buffer_phys; hda_stream_buffers_t *bufs = NULL; size_t i; //size_t j, k; errno_t rc; bufs = calloc(1, sizeof(hda_stream_buffers_t)); if (bufs == NULL) { rc = ENOMEM; goto error; } bufs->nbuffers = 4; bufs->bufsize = 16384; /* * BDL must be aligned to 128 bytes. If 64OK is not set, * it must be within the 32-bit address space. */ bdl = AS_AREA_ANY; rc = dmamem_map_anonymous(bufs->nbuffers * sizeof(hda_buffer_desc_t), hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE, 0, &bufs->bdl_phys, &bdl); if (rc != EOK) goto error; bufs->bdl = bdl; /* Allocate arrays of buffer pointers */ bufs->buf = calloc(bufs->nbuffers, sizeof(void *)); if (bufs->buf == NULL) goto error; bufs->buf_phys = calloc(bufs->nbuffers, sizeof(uintptr_t)); if (bufs->buf_phys == NULL) goto error; /* Allocate buffers */ #if 0 for (i = 0; i < bufs->nbuffers; i++) { buffer = AS_AREA_ANY; rc = dmamem_map_anonymous(bufs->bufsize, bufs->hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE, 0, &buffer_phys, &buffer); if (rc != EOK) goto error; ddf_msg(LVL_DEBUG, "Stream buf phys=0x%llx virt=%p", (unsigned long long)buffer_phys, buffer); bufs->buf[i] = buffer; bufs->buf_phys[i] = buffer_phys; k = 0; for (j = 0; j < bufs->bufsize / 2; j++) { int16_t *bp = bufs->buf[i]; bp[j] = (k > 128) ? -100 : 100; ++k; if (k >= 256) k = 0; } } #endif /* audio_pcm_iface requires a single contiguous buffer */ buffer = AS_AREA_ANY; rc = dmamem_map_anonymous(bufs->bufsize * bufs->nbuffers, hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE, 0, &buffer_phys, &buffer); if (rc != EOK) { ddf_msg(LVL_DEBUG, "dmamem_map_anon -> %s", str_error_name(rc)); goto error; } for (i = 0; i < bufs->nbuffers; i++) { bufs->buf[i] = buffer + i * bufs->bufsize; bufs->buf_phys[i] = buffer_phys + i * bufs->bufsize; ddf_msg(LVL_DEBUG, "Stream buf phys=0x%llx virt=%p", (long long unsigned)(uintptr_t)bufs->buf[i], (void *)bufs->buf_phys[i]); } /* Fill in BDL */ for (i = 0; i < bufs->nbuffers; i++) { bufs->bdl[i].address = host2uint64_t_le(bufs->buf_phys[i]); bufs->bdl[i].length = host2uint32_t_le(bufs->bufsize); bufs->bdl[i].flags = BIT_V(uint32_t, bdf_ioc); } *rbufs = bufs; return EOK; error: hda_stream_buffers_free(bufs); return ENOMEM; } void hda_stream_buffers_free(hda_stream_buffers_t *bufs) { if (bufs == NULL) return; /* XXX */ free(bufs); } static void hda_stream_desc_configure(hda_stream_t *stream) { hda_sdesc_regs_t *sdregs; hda_stream_buffers_t *bufs = stream->buffers; uint8_t ctl1; uint8_t ctl3; /* Stream ID */ ctl3 = (stream->sid << 4); /* Interrupt on buffer completion */ ctl1 = BIT_V(uint8_t, sdctl1_ioce); sdregs = &stream->hda->regs->sdesc[stream->sdid]; hda_reg8_write(&sdregs->ctl3, ctl3); hda_reg8_write(&sdregs->ctl1, ctl1); hda_reg32_write(&sdregs->cbl, bufs->nbuffers * bufs->bufsize); hda_reg16_write(&sdregs->lvi, bufs->nbuffers - 1); hda_reg16_write(&sdregs->fmt, stream->fmt); hda_reg32_write(&sdregs->bdpl, LOWER32(bufs->bdl_phys)); hda_reg32_write(&sdregs->bdpu, UPPER32(bufs->bdl_phys)); } static void hda_stream_set_run(hda_stream_t *stream, bool run) { uint32_t ctl; hda_sdesc_regs_t *sdregs; sdregs = &stream->hda->regs->sdesc[stream->sdid]; ctl = hda_reg8_read(&sdregs->ctl1); if (run) ctl = ctl | BIT_V(uint8_t, sdctl1_run); else ctl = ctl & ~BIT_V(uint8_t, sdctl1_run); hda_reg8_write(&sdregs->ctl1, ctl); } static void hda_stream_reset_noinit(hda_stream_t *stream) { uint32_t ctl; hda_sdesc_regs_t *sdregs; sdregs = &stream->hda->regs->sdesc[stream->sdid]; ctl = hda_reg8_read(&sdregs->ctl1); ctl = ctl | BIT_V(uint8_t, sdctl1_srst); hda_reg8_write(&sdregs->ctl1, ctl); fibril_usleep(100 * 1000); ctl = hda_reg8_read(&sdregs->ctl1); ctl = ctl & ~BIT_V(uint8_t, sdctl1_srst); hda_reg8_write(&sdregs->ctl1, ctl); fibril_usleep(100 * 1000); } hda_stream_t *hda_stream_create(hda_t *hda, hda_stream_dir_t dir, hda_stream_buffers_t *bufs, uint32_t fmt) { hda_stream_t *stream; uint8_t sdid; stream = calloc(1, sizeof(hda_stream_t)); if (stream == NULL) return NULL; sdid = 0; switch (dir) { case sdir_input: sdid = 0; /* XXX Allocate - first input SDESC */ break; case sdir_output: sdid = hda->ctl->iss; /* XXX Allocate - First output SDESC */ break; case sdir_bidi: sdid = hda->ctl->iss + hda->ctl->oss; /* XXX Allocate - First bidi SDESC */ break; } stream->hda = hda; stream->dir = dir; stream->sid = 1; /* XXX Allocate this */ stream->sdid = sdid; stream->fmt = fmt; stream->buffers = bufs; ddf_msg(LVL_DEBUG, "snum=%d sdidx=%d", stream->sid, stream->sdid); ddf_msg(LVL_DEBUG, "Configure stream descriptor"); hda_stream_desc_configure(stream); return stream; } void hda_stream_destroy(hda_stream_t *stream) { ddf_msg(LVL_DEBUG, "hda_stream_destroy()"); hda_stream_reset_noinit(stream); free(stream); } void hda_stream_start(hda_stream_t *stream) { ddf_msg(LVL_DEBUG, "hda_stream_start()"); hda_stream_set_run(stream, true); } void hda_stream_stop(hda_stream_t *stream) { ddf_msg(LVL_DEBUG, "hda_stream_stop()"); hda_stream_set_run(stream, false); } void hda_stream_reset(hda_stream_t *stream) { ddf_msg(LVL_DEBUG, "hda_stream_reset()"); hda_stream_reset_noinit(stream); hda_stream_desc_configure(stream); } /** @} */