88880fd4cf
got jumbled around during some other cleanups and was causing audio failures on some guests. PR: 239341 Reported by: shamaz.mazum@gmail.com
1332 lines
31 KiB
C
1332 lines
31 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2016 Alex Teaca <iateaca@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. 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.
|
|
*
|
|
* 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 OR CONTRIBUTORS 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.
|
|
*
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <time.h>
|
|
|
|
#include "pci_hda.h"
|
|
#include "bhyverun.h"
|
|
#include "pci_emul.h"
|
|
#include "hdac_reg.h"
|
|
|
|
/*
|
|
* HDA defines
|
|
*/
|
|
#define PCIR_HDCTL 0x40
|
|
#define INTEL_VENDORID 0x8086
|
|
#define HDA_INTEL_82801G 0x27d8
|
|
|
|
#define HDA_IOSS_NO 0x08
|
|
#define HDA_OSS_NO 0x04
|
|
#define HDA_ISS_NO 0x04
|
|
#define HDA_CODEC_MAX 0x0f
|
|
#define HDA_LAST_OFFSET \
|
|
(0x2084 + ((HDA_ISS_NO) * 0x20) + ((HDA_OSS_NO) * 0x20))
|
|
#define HDA_SET_REG_TABLE_SZ \
|
|
(0x80 + ((HDA_ISS_NO) * 0x20) + ((HDA_OSS_NO) * 0x20))
|
|
#define HDA_CORB_ENTRY_LEN 0x04
|
|
#define HDA_RIRB_ENTRY_LEN 0x08
|
|
#define HDA_BDL_ENTRY_LEN 0x10
|
|
#define HDA_DMA_PIB_ENTRY_LEN 0x08
|
|
#define HDA_STREAM_TAGS_CNT 0x10
|
|
#define HDA_STREAM_REGS_BASE 0x80
|
|
#define HDA_STREAM_REGS_LEN 0x20
|
|
|
|
#define HDA_DMA_ACCESS_LEN (sizeof(uint32_t))
|
|
#define HDA_BDL_MAX_LEN 0x0100
|
|
|
|
#define HDAC_SDSTS_FIFORDY (1 << 5)
|
|
|
|
#define HDA_RIRBSTS_IRQ_MASK (HDAC_RIRBSTS_RINTFL | HDAC_RIRBSTS_RIRBOIS)
|
|
#define HDA_STATESTS_IRQ_MASK ((1 << HDA_CODEC_MAX) - 1)
|
|
#define HDA_SDSTS_IRQ_MASK \
|
|
(HDAC_SDSTS_DESE | HDAC_SDSTS_FIFOE | HDAC_SDSTS_BCIS)
|
|
|
|
/*
|
|
* HDA data structures
|
|
*/
|
|
|
|
struct hda_softc;
|
|
|
|
typedef void (*hda_set_reg_handler)(struct hda_softc *sc, uint32_t offset,
|
|
uint32_t old);
|
|
|
|
struct hda_bdle {
|
|
uint32_t addrl;
|
|
uint32_t addrh;
|
|
uint32_t len;
|
|
uint32_t ioc;
|
|
} __packed;
|
|
|
|
struct hda_bdle_desc {
|
|
void *addr;
|
|
uint8_t ioc;
|
|
uint32_t len;
|
|
};
|
|
|
|
struct hda_codec_cmd_ctl {
|
|
char *name;
|
|
void *dma_vaddr;
|
|
uint8_t run;
|
|
uint16_t rp;
|
|
uint16_t size;
|
|
uint16_t wp;
|
|
};
|
|
|
|
struct hda_stream_desc {
|
|
uint8_t dir;
|
|
uint8_t run;
|
|
uint8_t stream;
|
|
|
|
/* bp is the no. of bytes transferred in the current bdle */
|
|
uint32_t bp;
|
|
/* be is the no. of bdles transferred in the bdl */
|
|
uint32_t be;
|
|
|
|
uint32_t bdl_cnt;
|
|
struct hda_bdle_desc bdl[HDA_BDL_MAX_LEN];
|
|
};
|
|
|
|
struct hda_softc {
|
|
struct pci_devinst *pci_dev;
|
|
uint32_t regs[HDA_LAST_OFFSET];
|
|
|
|
uint8_t lintr;
|
|
uint8_t rirb_cnt;
|
|
uint64_t wall_clock_start;
|
|
|
|
struct hda_codec_cmd_ctl corb;
|
|
struct hda_codec_cmd_ctl rirb;
|
|
|
|
uint8_t codecs_no;
|
|
struct hda_codec_inst *codecs[HDA_CODEC_MAX];
|
|
|
|
/* Base Address of the DMA Position Buffer */
|
|
void *dma_pib_vaddr;
|
|
|
|
struct hda_stream_desc streams[HDA_IOSS_NO];
|
|
/* 2 tables for output and input */
|
|
uint8_t stream_map[2][HDA_STREAM_TAGS_CNT];
|
|
};
|
|
|
|
/*
|
|
* HDA module function declarations
|
|
*/
|
|
static inline void hda_set_reg_by_offset(struct hda_softc *sc, uint32_t offset,
|
|
uint32_t value);
|
|
static inline uint32_t hda_get_reg_by_offset(struct hda_softc *sc,
|
|
uint32_t offset);
|
|
static inline void hda_set_field_by_offset(struct hda_softc *sc,
|
|
uint32_t offset, uint32_t mask, uint32_t value);
|
|
|
|
static uint8_t hda_parse_config(const char *opts, const char *key, char *val);
|
|
static struct hda_softc *hda_init(const char *opts);
|
|
static void hda_update_intr(struct hda_softc *sc);
|
|
static void hda_response_interrupt(struct hda_softc *sc);
|
|
static int hda_codec_constructor(struct hda_softc *sc,
|
|
struct hda_codec_class *codec, const char *play, const char *rec,
|
|
const char *opts);
|
|
static struct hda_codec_class *hda_find_codec_class(const char *name);
|
|
|
|
static int hda_send_command(struct hda_softc *sc, uint32_t verb);
|
|
static int hda_notify_codecs(struct hda_softc *sc, uint8_t run,
|
|
uint8_t stream, uint8_t dir);
|
|
static void hda_reset(struct hda_softc *sc);
|
|
static void hda_reset_regs(struct hda_softc *sc);
|
|
static void hda_stream_reset(struct hda_softc *sc, uint8_t stream_ind);
|
|
static int hda_stream_start(struct hda_softc *sc, uint8_t stream_ind);
|
|
static int hda_stream_stop(struct hda_softc *sc, uint8_t stream_ind);
|
|
static uint32_t hda_read(struct hda_softc *sc, uint32_t offset);
|
|
static int hda_write(struct hda_softc *sc, uint32_t offset, uint8_t size,
|
|
uint32_t value);
|
|
|
|
static inline void hda_print_cmd_ctl_data(struct hda_codec_cmd_ctl *p);
|
|
static int hda_corb_start(struct hda_softc *sc);
|
|
static int hda_corb_run(struct hda_softc *sc);
|
|
static int hda_rirb_start(struct hda_softc *sc);
|
|
|
|
static void *hda_dma_get_vaddr(struct hda_softc *sc, uint64_t dma_paddr,
|
|
size_t len);
|
|
static void hda_dma_st_dword(void *dma_vaddr, uint32_t data);
|
|
static uint32_t hda_dma_ld_dword(void *dma_vaddr);
|
|
|
|
static inline uint8_t hda_get_stream_by_offsets(uint32_t offset,
|
|
uint8_t reg_offset);
|
|
static inline uint32_t hda_get_offset_stream(uint8_t stream_ind);
|
|
|
|
static void hda_set_gctl(struct hda_softc *sc, uint32_t offset, uint32_t old);
|
|
static void hda_set_statests(struct hda_softc *sc, uint32_t offset,
|
|
uint32_t old);
|
|
static void hda_set_corbwp(struct hda_softc *sc, uint32_t offset, uint32_t old);
|
|
static void hda_set_corbctl(struct hda_softc *sc, uint32_t offset,
|
|
uint32_t old);
|
|
static void hda_set_rirbctl(struct hda_softc *sc, uint32_t offset,
|
|
uint32_t old);
|
|
static void hda_set_rirbsts(struct hda_softc *sc, uint32_t offset,
|
|
uint32_t old);
|
|
static void hda_set_dpiblbase(struct hda_softc *sc, uint32_t offset,
|
|
uint32_t old);
|
|
static void hda_set_sdctl(struct hda_softc *sc, uint32_t offset, uint32_t old);
|
|
static void hda_set_sdctl2(struct hda_softc *sc, uint32_t offset, uint32_t old);
|
|
static void hda_set_sdsts(struct hda_softc *sc, uint32_t offset, uint32_t old);
|
|
|
|
static int hda_signal_state_change(struct hda_codec_inst *hci);
|
|
static int hda_response(struct hda_codec_inst *hci, uint32_t response,
|
|
uint8_t unsol);
|
|
static int hda_transfer(struct hda_codec_inst *hci, uint8_t stream,
|
|
uint8_t dir, void *buf, size_t count);
|
|
|
|
static void hda_set_pib(struct hda_softc *sc, uint8_t stream_ind, uint32_t pib);
|
|
static uint64_t hda_get_clock_ns(void);
|
|
|
|
/*
|
|
* PCI HDA function declarations
|
|
*/
|
|
static int pci_hda_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts);
|
|
static void pci_hda_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
|
|
int baridx, uint64_t offset, int size, uint64_t value);
|
|
static uint64_t pci_hda_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
|
|
int baridx, uint64_t offset, int size);
|
|
/*
|
|
* HDA global data
|
|
*/
|
|
|
|
static const hda_set_reg_handler hda_set_reg_table[] = {
|
|
[HDAC_GCTL] = hda_set_gctl,
|
|
[HDAC_STATESTS] = hda_set_statests,
|
|
[HDAC_CORBWP] = hda_set_corbwp,
|
|
[HDAC_CORBCTL] = hda_set_corbctl,
|
|
[HDAC_RIRBCTL] = hda_set_rirbctl,
|
|
[HDAC_RIRBSTS] = hda_set_rirbsts,
|
|
[HDAC_DPIBLBASE] = hda_set_dpiblbase,
|
|
|
|
#define HDAC_ISTREAM(n, iss, oss) \
|
|
[_HDAC_ISDCTL(n, iss, oss)] = hda_set_sdctl, \
|
|
[_HDAC_ISDCTL(n, iss, oss) + 2] = hda_set_sdctl2, \
|
|
[_HDAC_ISDSTS(n, iss, oss)] = hda_set_sdsts, \
|
|
|
|
#define HDAC_OSTREAM(n, iss, oss) \
|
|
[_HDAC_OSDCTL(n, iss, oss)] = hda_set_sdctl, \
|
|
[_HDAC_OSDCTL(n, iss, oss) + 2] = hda_set_sdctl2, \
|
|
[_HDAC_OSDSTS(n, iss, oss)] = hda_set_sdsts, \
|
|
|
|
HDAC_ISTREAM(0, HDA_ISS_NO, HDA_OSS_NO)
|
|
HDAC_ISTREAM(1, HDA_ISS_NO, HDA_OSS_NO)
|
|
HDAC_ISTREAM(2, HDA_ISS_NO, HDA_OSS_NO)
|
|
HDAC_ISTREAM(3, HDA_ISS_NO, HDA_OSS_NO)
|
|
|
|
HDAC_OSTREAM(0, HDA_ISS_NO, HDA_OSS_NO)
|
|
HDAC_OSTREAM(1, HDA_ISS_NO, HDA_OSS_NO)
|
|
HDAC_OSTREAM(2, HDA_ISS_NO, HDA_OSS_NO)
|
|
HDAC_OSTREAM(3, HDA_ISS_NO, HDA_OSS_NO)
|
|
|
|
[HDA_SET_REG_TABLE_SZ] = NULL,
|
|
};
|
|
|
|
static const uint16_t hda_corb_sizes[] = {
|
|
[HDAC_CORBSIZE_CORBSIZE_2] = 2,
|
|
[HDAC_CORBSIZE_CORBSIZE_16] = 16,
|
|
[HDAC_CORBSIZE_CORBSIZE_256] = 256,
|
|
[HDAC_CORBSIZE_CORBSIZE_MASK] = 0,
|
|
};
|
|
|
|
static const uint16_t hda_rirb_sizes[] = {
|
|
[HDAC_RIRBSIZE_RIRBSIZE_2] = 2,
|
|
[HDAC_RIRBSIZE_RIRBSIZE_16] = 16,
|
|
[HDAC_RIRBSIZE_RIRBSIZE_256] = 256,
|
|
[HDAC_RIRBSIZE_RIRBSIZE_MASK] = 0,
|
|
};
|
|
|
|
static struct hda_ops hops = {
|
|
.signal = hda_signal_state_change,
|
|
.response = hda_response,
|
|
.transfer = hda_transfer,
|
|
};
|
|
|
|
struct pci_devemu pci_de_hda = {
|
|
.pe_emu = "hda",
|
|
.pe_init = pci_hda_init,
|
|
.pe_barwrite = pci_hda_write,
|
|
.pe_barread = pci_hda_read
|
|
};
|
|
|
|
PCI_EMUL_SET(pci_de_hda);
|
|
|
|
SET_DECLARE(hda_codec_class_set, struct hda_codec_class);
|
|
|
|
#if DEBUG_HDA == 1
|
|
FILE *dbg;
|
|
#endif
|
|
|
|
/*
|
|
* HDA module function definitions
|
|
*/
|
|
|
|
static inline void
|
|
hda_set_reg_by_offset(struct hda_softc *sc, uint32_t offset, uint32_t value)
|
|
{
|
|
assert(offset < HDA_LAST_OFFSET);
|
|
sc->regs[offset] = value;
|
|
}
|
|
|
|
static inline uint32_t
|
|
hda_get_reg_by_offset(struct hda_softc *sc, uint32_t offset)
|
|
{
|
|
assert(offset < HDA_LAST_OFFSET);
|
|
return sc->regs[offset];
|
|
}
|
|
|
|
static inline void
|
|
hda_set_field_by_offset(struct hda_softc *sc, uint32_t offset,
|
|
uint32_t mask, uint32_t value)
|
|
{
|
|
uint32_t reg_value = 0;
|
|
|
|
reg_value = hda_get_reg_by_offset(sc, offset);
|
|
|
|
reg_value &= ~mask;
|
|
reg_value |= (value & mask);
|
|
|
|
hda_set_reg_by_offset(sc, offset, reg_value);
|
|
}
|
|
|
|
static uint8_t
|
|
hda_parse_config(const char *opts, const char *key, char *val)
|
|
{
|
|
char buf[64];
|
|
char *s = buf;
|
|
char *tmp = NULL;
|
|
size_t len;
|
|
int i;
|
|
|
|
if (!opts)
|
|
return (0);
|
|
|
|
len = strlen(opts);
|
|
if (len >= sizeof(buf)) {
|
|
DPRINTF("Opts too big\n");
|
|
return (0);
|
|
}
|
|
|
|
DPRINTF("opts: %s\n", opts);
|
|
|
|
strcpy(buf, opts);
|
|
|
|
for (i = 0; i < len; i++)
|
|
if (buf[i] == ',') {
|
|
buf[i] = 0;
|
|
tmp = buf + i + 1;
|
|
break;
|
|
}
|
|
|
|
if (!memcmp(s, key, strlen(key))) {
|
|
strncpy(val, s + strlen(key), 64);
|
|
return (1);
|
|
}
|
|
|
|
if (!tmp)
|
|
return (0);
|
|
|
|
s = tmp;
|
|
if (!memcmp(s, key, strlen(key))) {
|
|
strncpy(val, s + strlen(key), 64);
|
|
return (1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static struct hda_softc *
|
|
hda_init(const char *opts)
|
|
{
|
|
struct hda_softc *sc = NULL;
|
|
struct hda_codec_class *codec = NULL;
|
|
char play[64];
|
|
char rec[64];
|
|
int err, p, r;
|
|
|
|
#if DEBUG_HDA == 1
|
|
dbg = fopen("/tmp/bhyve_hda.log", "w+");
|
|
#endif
|
|
|
|
DPRINTF("opts: %s\n", opts);
|
|
|
|
sc = calloc(1, sizeof(*sc));
|
|
if (!sc)
|
|
return (NULL);
|
|
|
|
hda_reset_regs(sc);
|
|
|
|
/*
|
|
* TODO search all the codecs declared in opts
|
|
* For now we play with one single codec
|
|
*/
|
|
codec = hda_find_codec_class("hda_codec");
|
|
if (codec) {
|
|
p = hda_parse_config(opts, "play=", play);
|
|
r = hda_parse_config(opts, "rec=", rec);
|
|
DPRINTF("play: %s rec: %s\n", play, rec);
|
|
if (p | r) {
|
|
err = hda_codec_constructor(sc, codec, p ? \
|
|
play : NULL, r ? rec : NULL, NULL);
|
|
assert(!err);
|
|
}
|
|
}
|
|
|
|
return (sc);
|
|
}
|
|
|
|
static void
|
|
hda_update_intr(struct hda_softc *sc)
|
|
{
|
|
struct pci_devinst *pi = sc->pci_dev;
|
|
uint32_t intctl = hda_get_reg_by_offset(sc, HDAC_INTCTL);
|
|
uint32_t intsts = 0;
|
|
uint32_t sdsts = 0;
|
|
uint32_t rirbsts = 0;
|
|
uint32_t wakeen = 0;
|
|
uint32_t statests = 0;
|
|
uint32_t off = 0;
|
|
int i;
|
|
|
|
/* update the CIS bits */
|
|
rirbsts = hda_get_reg_by_offset(sc, HDAC_RIRBSTS);
|
|
if (rirbsts & (HDAC_RIRBSTS_RINTFL | HDAC_RIRBSTS_RIRBOIS))
|
|
intsts |= HDAC_INTSTS_CIS;
|
|
|
|
wakeen = hda_get_reg_by_offset(sc, HDAC_WAKEEN);
|
|
statests = hda_get_reg_by_offset(sc, HDAC_STATESTS);
|
|
if (statests & wakeen)
|
|
intsts |= HDAC_INTSTS_CIS;
|
|
|
|
/* update the SIS bits */
|
|
for (i = 0; i < HDA_IOSS_NO; i++) {
|
|
off = hda_get_offset_stream(i);
|
|
sdsts = hda_get_reg_by_offset(sc, off + HDAC_SDSTS);
|
|
if (sdsts & HDAC_SDSTS_BCIS)
|
|
intsts |= (1 << i);
|
|
}
|
|
|
|
/* update the GIS bit */
|
|
if (intsts)
|
|
intsts |= HDAC_INTSTS_GIS;
|
|
|
|
hda_set_reg_by_offset(sc, HDAC_INTSTS, intsts);
|
|
|
|
if ((intctl & HDAC_INTCTL_GIE) && ((intsts & \
|
|
~HDAC_INTSTS_GIS) & intctl)) {
|
|
if (!sc->lintr) {
|
|
pci_lintr_assert(pi);
|
|
sc->lintr = 1;
|
|
}
|
|
} else {
|
|
if (sc->lintr) {
|
|
pci_lintr_deassert(pi);
|
|
sc->lintr = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hda_response_interrupt(struct hda_softc *sc)
|
|
{
|
|
uint8_t rirbctl = hda_get_reg_by_offset(sc, HDAC_RIRBCTL);
|
|
|
|
if ((rirbctl & HDAC_RIRBCTL_RINTCTL) && sc->rirb_cnt) {
|
|
sc->rirb_cnt = 0;
|
|
hda_set_field_by_offset(sc, HDAC_RIRBSTS, HDAC_RIRBSTS_RINTFL,
|
|
HDAC_RIRBSTS_RINTFL);
|
|
hda_update_intr(sc);
|
|
}
|
|
}
|
|
|
|
static int
|
|
hda_codec_constructor(struct hda_softc *sc, struct hda_codec_class *codec,
|
|
const char *play, const char *rec, const char *opts)
|
|
{
|
|
struct hda_codec_inst *hci = NULL;
|
|
|
|
if (sc->codecs_no >= HDA_CODEC_MAX)
|
|
return (-1);
|
|
|
|
hci = calloc(1, sizeof(struct hda_codec_inst));
|
|
if (!hci)
|
|
return (-1);
|
|
|
|
hci->hda = sc;
|
|
hci->hops = &hops;
|
|
hci->cad = sc->codecs_no;
|
|
hci->codec = codec;
|
|
|
|
sc->codecs[sc->codecs_no++] = hci;
|
|
|
|
if (!codec->init) {
|
|
DPRINTF("This codec does not implement the init function\n");
|
|
return (-1);
|
|
}
|
|
|
|
return (codec->init(hci, play, rec, opts));
|
|
}
|
|
|
|
static struct hda_codec_class *
|
|
hda_find_codec_class(const char *name)
|
|
{
|
|
struct hda_codec_class **pdpp = NULL, *pdp = NULL;
|
|
|
|
SET_FOREACH(pdpp, hda_codec_class_set) {
|
|
pdp = *pdpp;
|
|
if (!strcmp(pdp->name, name)) {
|
|
return (pdp);
|
|
}
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static int
|
|
hda_send_command(struct hda_softc *sc, uint32_t verb)
|
|
{
|
|
struct hda_codec_inst *hci = NULL;
|
|
struct hda_codec_class *codec = NULL;
|
|
uint8_t cad = (verb >> HDA_CMD_CAD_SHIFT) & 0x0f;
|
|
|
|
hci = sc->codecs[cad];
|
|
if (!hci)
|
|
return (-1);
|
|
|
|
DPRINTF("cad: 0x%x verb: 0x%x\n", cad, verb);
|
|
|
|
codec = hci->codec;
|
|
assert(codec);
|
|
|
|
if (!codec->command) {
|
|
DPRINTF("This codec does not implement the command function\n");
|
|
return (-1);
|
|
}
|
|
|
|
return (codec->command(hci, verb));
|
|
}
|
|
|
|
static int
|
|
hda_notify_codecs(struct hda_softc *sc, uint8_t run, uint8_t stream,
|
|
uint8_t dir)
|
|
{
|
|
struct hda_codec_inst *hci = NULL;
|
|
struct hda_codec_class *codec = NULL;
|
|
int err;
|
|
int i;
|
|
|
|
/* Notify each codec */
|
|
for (i = 0; i < sc->codecs_no; i++) {
|
|
hci = sc->codecs[i];
|
|
assert(hci);
|
|
|
|
codec = hci->codec;
|
|
assert(codec);
|
|
|
|
if (codec->notify) {
|
|
err = codec->notify(hci, run, stream, dir);
|
|
if (!err)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (i == sc->codecs_no ? (-1) : 0);
|
|
}
|
|
|
|
static void
|
|
hda_reset(struct hda_softc *sc)
|
|
{
|
|
int i;
|
|
struct hda_codec_inst *hci = NULL;
|
|
struct hda_codec_class *codec = NULL;
|
|
|
|
hda_reset_regs(sc);
|
|
|
|
/* Reset each codec */
|
|
for (i = 0; i < sc->codecs_no; i++) {
|
|
hci = sc->codecs[i];
|
|
assert(hci);
|
|
|
|
codec = hci->codec;
|
|
assert(codec);
|
|
|
|
if (codec->reset)
|
|
codec->reset(hci);
|
|
}
|
|
|
|
sc->wall_clock_start = hda_get_clock_ns();
|
|
}
|
|
|
|
static void
|
|
hda_reset_regs(struct hda_softc *sc)
|
|
{
|
|
uint32_t off = 0;
|
|
uint8_t i;
|
|
|
|
DPRINTF("Reset the HDA controller registers ...\n");
|
|
|
|
memset(sc->regs, 0, sizeof(sc->regs));
|
|
|
|
hda_set_reg_by_offset(sc, HDAC_GCAP,
|
|
HDAC_GCAP_64OK |
|
|
(HDA_ISS_NO << HDAC_GCAP_ISS_SHIFT) |
|
|
(HDA_OSS_NO << HDAC_GCAP_OSS_SHIFT));
|
|
hda_set_reg_by_offset(sc, HDAC_VMAJ, 0x01);
|
|
hda_set_reg_by_offset(sc, HDAC_OUTPAY, 0x3c);
|
|
hda_set_reg_by_offset(sc, HDAC_INPAY, 0x1d);
|
|
hda_set_reg_by_offset(sc, HDAC_CORBSIZE,
|
|
HDAC_CORBSIZE_CORBSZCAP_256 | HDAC_CORBSIZE_CORBSIZE_256);
|
|
hda_set_reg_by_offset(sc, HDAC_RIRBSIZE,
|
|
HDAC_RIRBSIZE_RIRBSZCAP_256 | HDAC_RIRBSIZE_RIRBSIZE_256);
|
|
|
|
for (i = 0; i < HDA_IOSS_NO; i++) {
|
|
off = hda_get_offset_stream(i);
|
|
hda_set_reg_by_offset(sc, off + HDAC_SDFIFOS, HDA_FIFO_SIZE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hda_stream_reset(struct hda_softc *sc, uint8_t stream_ind)
|
|
{
|
|
struct hda_stream_desc *st = &sc->streams[stream_ind];
|
|
uint32_t off = hda_get_offset_stream(stream_ind);
|
|
|
|
DPRINTF("Reset the HDA stream: 0x%x\n", stream_ind);
|
|
|
|
/* Reset the Stream Descriptor registers */
|
|
memset(sc->regs + HDA_STREAM_REGS_BASE + off, 0, HDA_STREAM_REGS_LEN);
|
|
|
|
/* Reset the Stream Descriptor */
|
|
memset(st, 0, sizeof(*st));
|
|
|
|
hda_set_field_by_offset(sc, off + HDAC_SDSTS,
|
|
HDAC_SDSTS_FIFORDY, HDAC_SDSTS_FIFORDY);
|
|
hda_set_field_by_offset(sc, off + HDAC_SDCTL0,
|
|
HDAC_SDCTL_SRST, HDAC_SDCTL_SRST);
|
|
}
|
|
|
|
static int
|
|
hda_stream_start(struct hda_softc *sc, uint8_t stream_ind)
|
|
{
|
|
struct hda_stream_desc *st = &sc->streams[stream_ind];
|
|
struct hda_bdle_desc *bdle_desc = NULL;
|
|
struct hda_bdle *bdle = NULL;
|
|
uint32_t lvi = 0;
|
|
uint32_t bdl_cnt = 0;
|
|
uint64_t bdpl = 0;
|
|
uint64_t bdpu = 0;
|
|
uint64_t bdl_paddr = 0;
|
|
void *bdl_vaddr = NULL;
|
|
uint32_t bdle_sz = 0;
|
|
uint64_t bdle_addrl = 0;
|
|
uint64_t bdle_addrh = 0;
|
|
uint64_t bdle_paddr = 0;
|
|
void *bdle_vaddr = NULL;
|
|
uint32_t off = hda_get_offset_stream(stream_ind);
|
|
uint32_t sdctl = 0;
|
|
uint8_t strm = 0;
|
|
uint8_t dir = 0;
|
|
int i;
|
|
|
|
assert(!st->run);
|
|
|
|
lvi = hda_get_reg_by_offset(sc, off + HDAC_SDLVI);
|
|
bdpl = hda_get_reg_by_offset(sc, off + HDAC_SDBDPL);
|
|
bdpu = hda_get_reg_by_offset(sc, off + HDAC_SDBDPU);
|
|
|
|
bdl_cnt = lvi + 1;
|
|
assert(bdl_cnt <= HDA_BDL_MAX_LEN);
|
|
|
|
bdl_paddr = bdpl | (bdpu << 32);
|
|
bdl_vaddr = hda_dma_get_vaddr(sc, bdl_paddr,
|
|
HDA_BDL_ENTRY_LEN * bdl_cnt);
|
|
if (!bdl_vaddr) {
|
|
DPRINTF("Fail to get the guest virtual address\n");
|
|
return (-1);
|
|
}
|
|
|
|
DPRINTF("stream: 0x%x bdl_cnt: 0x%x bdl_paddr: 0x%lx\n",
|
|
stream_ind, bdl_cnt, bdl_paddr);
|
|
|
|
st->bdl_cnt = bdl_cnt;
|
|
|
|
bdle = (struct hda_bdle *)bdl_vaddr;
|
|
for (i = 0; i < bdl_cnt; i++, bdle++) {
|
|
bdle_sz = bdle->len;
|
|
assert(!(bdle_sz % HDA_DMA_ACCESS_LEN));
|
|
|
|
bdle_addrl = bdle->addrl;
|
|
bdle_addrh = bdle->addrh;
|
|
|
|
bdle_paddr = bdle_addrl | (bdle_addrh << 32);
|
|
bdle_vaddr = hda_dma_get_vaddr(sc, bdle_paddr, bdle_sz);
|
|
if (!bdle_vaddr) {
|
|
DPRINTF("Fail to get the guest virtual address\n");
|
|
return (-1);
|
|
}
|
|
|
|
bdle_desc = &st->bdl[i];
|
|
bdle_desc->addr = bdle_vaddr;
|
|
bdle_desc->len = bdle_sz;
|
|
bdle_desc->ioc = bdle->ioc;
|
|
|
|
DPRINTF("bdle: 0x%x bdle_sz: 0x%x\n", i, bdle_sz);
|
|
}
|
|
|
|
sdctl = hda_get_reg_by_offset(sc, off + HDAC_SDCTL0);
|
|
strm = (sdctl >> 20) & 0x0f;
|
|
dir = stream_ind >= HDA_ISS_NO;
|
|
|
|
DPRINTF("strm: 0x%x, dir: 0x%x\n", strm, dir);
|
|
|
|
sc->stream_map[dir][strm] = stream_ind;
|
|
st->stream = strm;
|
|
st->dir = dir;
|
|
st->bp = 0;
|
|
st->be = 0;
|
|
|
|
hda_set_pib(sc, stream_ind, 0);
|
|
|
|
st->run = 1;
|
|
|
|
hda_notify_codecs(sc, 1, strm, dir);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hda_stream_stop(struct hda_softc *sc, uint8_t stream_ind)
|
|
{
|
|
struct hda_stream_desc *st = &sc->streams[stream_ind];
|
|
uint8_t strm = st->stream;
|
|
uint8_t dir = st->dir;
|
|
|
|
DPRINTF("stream: 0x%x, strm: 0x%x, dir: 0x%x\n", stream_ind, strm, dir);
|
|
|
|
st->run = 0;
|
|
|
|
hda_notify_codecs(sc, 0, strm, dir);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static uint32_t
|
|
hda_read(struct hda_softc *sc, uint32_t offset)
|
|
{
|
|
if (offset == HDAC_WALCLK)
|
|
return (24 * (hda_get_clock_ns() - \
|
|
sc->wall_clock_start) / 1000);
|
|
|
|
return (hda_get_reg_by_offset(sc, offset));
|
|
}
|
|
|
|
static int
|
|
hda_write(struct hda_softc *sc, uint32_t offset, uint8_t size, uint32_t value)
|
|
{
|
|
uint32_t old = hda_get_reg_by_offset(sc, offset);
|
|
uint32_t masks[] = {0x00000000, 0x000000ff, 0x0000ffff,
|
|
0x00ffffff, 0xffffffff};
|
|
hda_set_reg_handler set_reg_handler = hda_set_reg_table[offset];
|
|
|
|
hda_set_field_by_offset(sc, offset, masks[size], value);
|
|
|
|
if (set_reg_handler)
|
|
set_reg_handler(sc, offset, old);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static inline void
|
|
hda_print_cmd_ctl_data(struct hda_codec_cmd_ctl *p)
|
|
{
|
|
#if DEBUG_HDA == 1
|
|
char *name = p->name;
|
|
#endif
|
|
DPRINTF("%s size: %d\n", name, p->size);
|
|
DPRINTF("%s dma_vaddr: %p\n", name, p->dma_vaddr);
|
|
DPRINTF("%s wp: 0x%x\n", name, p->wp);
|
|
DPRINTF("%s rp: 0x%x\n", name, p->rp);
|
|
}
|
|
|
|
static int
|
|
hda_corb_start(struct hda_softc *sc)
|
|
{
|
|
struct hda_codec_cmd_ctl *corb = &sc->corb;
|
|
uint8_t corbsize = 0;
|
|
uint64_t corblbase = 0;
|
|
uint64_t corbubase = 0;
|
|
uint64_t corbpaddr = 0;
|
|
|
|
corb->name = "CORB";
|
|
|
|
corbsize = hda_get_reg_by_offset(sc, HDAC_CORBSIZE) & \
|
|
HDAC_CORBSIZE_CORBSIZE_MASK;
|
|
corb->size = hda_corb_sizes[corbsize];
|
|
|
|
if (!corb->size) {
|
|
DPRINTF("Invalid corb size\n");
|
|
return (-1);
|
|
}
|
|
|
|
corblbase = hda_get_reg_by_offset(sc, HDAC_CORBLBASE);
|
|
corbubase = hda_get_reg_by_offset(sc, HDAC_CORBUBASE);
|
|
|
|
corbpaddr = corblbase | (corbubase << 32);
|
|
DPRINTF("CORB dma_paddr: %p\n", (void *)corbpaddr);
|
|
|
|
corb->dma_vaddr = hda_dma_get_vaddr(sc, corbpaddr,
|
|
HDA_CORB_ENTRY_LEN * corb->size);
|
|
if (!corb->dma_vaddr) {
|
|
DPRINTF("Fail to get the guest virtual address\n");
|
|
return (-1);
|
|
}
|
|
|
|
corb->wp = hda_get_reg_by_offset(sc, HDAC_CORBWP);
|
|
corb->rp = hda_get_reg_by_offset(sc, HDAC_CORBRP);
|
|
|
|
corb->run = 1;
|
|
|
|
hda_print_cmd_ctl_data(corb);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hda_corb_run(struct hda_softc *sc)
|
|
{
|
|
struct hda_codec_cmd_ctl *corb = &sc->corb;
|
|
uint32_t verb = 0;
|
|
int err;
|
|
|
|
corb->wp = hda_get_reg_by_offset(sc, HDAC_CORBWP);
|
|
|
|
while (corb->rp != corb->wp && corb->run) {
|
|
corb->rp++;
|
|
corb->rp %= corb->size;
|
|
|
|
verb = hda_dma_ld_dword(corb->dma_vaddr + \
|
|
HDA_CORB_ENTRY_LEN * corb->rp);
|
|
|
|
err = hda_send_command(sc, verb);
|
|
assert(!err);
|
|
}
|
|
|
|
hda_set_reg_by_offset(sc, HDAC_CORBRP, corb->rp);
|
|
|
|
if (corb->run)
|
|
hda_response_interrupt(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hda_rirb_start(struct hda_softc *sc)
|
|
{
|
|
struct hda_codec_cmd_ctl *rirb = &sc->rirb;
|
|
uint8_t rirbsize = 0;
|
|
uint64_t rirblbase = 0;
|
|
uint64_t rirbubase = 0;
|
|
uint64_t rirbpaddr = 0;
|
|
|
|
rirb->name = "RIRB";
|
|
|
|
rirbsize = hda_get_reg_by_offset(sc, HDAC_RIRBSIZE) & \
|
|
HDAC_RIRBSIZE_RIRBSIZE_MASK;
|
|
rirb->size = hda_rirb_sizes[rirbsize];
|
|
|
|
if (!rirb->size) {
|
|
DPRINTF("Invalid rirb size\n");
|
|
return (-1);
|
|
}
|
|
|
|
rirblbase = hda_get_reg_by_offset(sc, HDAC_RIRBLBASE);
|
|
rirbubase = hda_get_reg_by_offset(sc, HDAC_RIRBUBASE);
|
|
|
|
rirbpaddr = rirblbase | (rirbubase << 32);
|
|
DPRINTF("RIRB dma_paddr: %p\n", (void *)rirbpaddr);
|
|
|
|
rirb->dma_vaddr = hda_dma_get_vaddr(sc, rirbpaddr,
|
|
HDA_RIRB_ENTRY_LEN * rirb->size);
|
|
if (!rirb->dma_vaddr) {
|
|
DPRINTF("Fail to get the guest virtual address\n");
|
|
return (-1);
|
|
}
|
|
|
|
rirb->wp = hda_get_reg_by_offset(sc, HDAC_RIRBWP);
|
|
rirb->rp = 0x0000;
|
|
|
|
rirb->run = 1;
|
|
|
|
hda_print_cmd_ctl_data(rirb);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void *
|
|
hda_dma_get_vaddr(struct hda_softc *sc, uint64_t dma_paddr, size_t len)
|
|
{
|
|
struct pci_devinst *pi = sc->pci_dev;
|
|
|
|
assert(pi);
|
|
|
|
return (paddr_guest2host(pi->pi_vmctx, (uintptr_t)dma_paddr, len));
|
|
}
|
|
|
|
static void
|
|
hda_dma_st_dword(void *dma_vaddr, uint32_t data)
|
|
{
|
|
*(uint32_t*)dma_vaddr = data;
|
|
}
|
|
|
|
static uint32_t
|
|
hda_dma_ld_dword(void *dma_vaddr)
|
|
{
|
|
return (*(uint32_t*)dma_vaddr);
|
|
}
|
|
|
|
static inline uint8_t
|
|
hda_get_stream_by_offsets(uint32_t offset, uint8_t reg_offset)
|
|
{
|
|
uint8_t stream_ind = (offset - reg_offset) >> 5;
|
|
|
|
assert(stream_ind < HDA_IOSS_NO);
|
|
|
|
return (stream_ind);
|
|
}
|
|
|
|
static inline uint32_t
|
|
hda_get_offset_stream(uint8_t stream_ind)
|
|
{
|
|
return (stream_ind << 5);
|
|
}
|
|
|
|
static void
|
|
hda_set_gctl(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
|
|
if (!(value & HDAC_GCTL_CRST)) {
|
|
hda_reset(sc);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hda_set_statests(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
|
|
hda_set_reg_by_offset(sc, offset, old);
|
|
|
|
/* clear the corresponding bits written by the software (guest) */
|
|
hda_set_field_by_offset(sc, offset, value & HDA_STATESTS_IRQ_MASK, 0);
|
|
|
|
hda_update_intr(sc);
|
|
}
|
|
|
|
static void
|
|
hda_set_corbwp(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
hda_corb_run(sc);
|
|
}
|
|
|
|
static void
|
|
hda_set_corbctl(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
int err;
|
|
struct hda_codec_cmd_ctl *corb = NULL;
|
|
|
|
if (value & HDAC_CORBCTL_CORBRUN) {
|
|
if (!(old & HDAC_CORBCTL_CORBRUN)) {
|
|
err = hda_corb_start(sc);
|
|
assert(!err);
|
|
}
|
|
} else {
|
|
corb = &sc->corb;
|
|
memset(corb, 0, sizeof(*corb));
|
|
}
|
|
|
|
hda_corb_run(sc);
|
|
}
|
|
|
|
static void
|
|
hda_set_rirbctl(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
int err;
|
|
struct hda_codec_cmd_ctl *rirb = NULL;
|
|
|
|
if (value & HDAC_RIRBCTL_RIRBDMAEN) {
|
|
err = hda_rirb_start(sc);
|
|
assert(!err);
|
|
} else {
|
|
rirb = &sc->rirb;
|
|
memset(rirb, 0, sizeof(*rirb));
|
|
}
|
|
}
|
|
|
|
static void
|
|
hda_set_rirbsts(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
|
|
hda_set_reg_by_offset(sc, offset, old);
|
|
|
|
/* clear the corresponding bits written by the software (guest) */
|
|
hda_set_field_by_offset(sc, offset, value & HDA_RIRBSTS_IRQ_MASK, 0);
|
|
|
|
hda_update_intr(sc);
|
|
}
|
|
|
|
static void
|
|
hda_set_dpiblbase(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
uint64_t dpiblbase = 0;
|
|
uint64_t dpibubase = 0;
|
|
uint64_t dpibpaddr = 0;
|
|
|
|
if ((value & HDAC_DPLBASE_DPLBASE_DMAPBE) != (old & \
|
|
HDAC_DPLBASE_DPLBASE_DMAPBE)) {
|
|
if (value & HDAC_DPLBASE_DPLBASE_DMAPBE) {
|
|
dpiblbase = value & HDAC_DPLBASE_DPLBASE_MASK;
|
|
dpibubase = hda_get_reg_by_offset(sc, HDAC_DPIBUBASE);
|
|
|
|
dpibpaddr = dpiblbase | (dpibubase << 32);
|
|
DPRINTF("DMA Position In Buffer dma_paddr: %p\n",
|
|
(void *)dpibpaddr);
|
|
|
|
sc->dma_pib_vaddr = hda_dma_get_vaddr(sc, dpibpaddr,
|
|
HDA_DMA_PIB_ENTRY_LEN * HDA_IOSS_NO);
|
|
if (!sc->dma_pib_vaddr) {
|
|
DPRINTF("Fail to get the guest \
|
|
virtual address\n");
|
|
assert(0);
|
|
}
|
|
} else {
|
|
DPRINTF("DMA Position In Buffer Reset\n");
|
|
sc->dma_pib_vaddr = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hda_set_sdctl(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint8_t stream_ind = hda_get_stream_by_offsets(offset, HDAC_SDCTL0);
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
int err;
|
|
|
|
DPRINTF("stream_ind: 0x%x old: 0x%x value: 0x%x\n",
|
|
stream_ind, old, value);
|
|
|
|
if (value & HDAC_SDCTL_SRST) {
|
|
hda_stream_reset(sc, stream_ind);
|
|
}
|
|
|
|
if ((value & HDAC_SDCTL_RUN) != (old & HDAC_SDCTL_RUN)) {
|
|
if (value & HDAC_SDCTL_RUN) {
|
|
err = hda_stream_start(sc, stream_ind);
|
|
assert(!err);
|
|
} else {
|
|
err = hda_stream_stop(sc, stream_ind);
|
|
assert(!err);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
hda_set_sdctl2(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
|
|
hda_set_field_by_offset(sc, offset - 2, 0x00ff0000, value << 16);
|
|
}
|
|
|
|
static void
|
|
hda_set_sdsts(struct hda_softc *sc, uint32_t offset, uint32_t old)
|
|
{
|
|
uint32_t value = hda_get_reg_by_offset(sc, offset);
|
|
|
|
hda_set_reg_by_offset(sc, offset, old);
|
|
|
|
/* clear the corresponding bits written by the software (guest) */
|
|
hda_set_field_by_offset(sc, offset, value & HDA_SDSTS_IRQ_MASK, 0);
|
|
|
|
hda_update_intr(sc);
|
|
}
|
|
|
|
static int
|
|
hda_signal_state_change(struct hda_codec_inst *hci)
|
|
{
|
|
struct hda_softc *sc = NULL;
|
|
uint32_t sdiwake = 0;
|
|
|
|
assert(hci);
|
|
assert(hci->hda);
|
|
|
|
DPRINTF("cad: 0x%x\n", hci->cad);
|
|
|
|
sc = hci->hda;
|
|
sdiwake = 1 << hci->cad;
|
|
|
|
hda_set_field_by_offset(sc, HDAC_STATESTS, sdiwake, sdiwake);
|
|
hda_update_intr(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hda_response(struct hda_codec_inst *hci, uint32_t response, uint8_t unsol)
|
|
{
|
|
struct hda_softc *sc = NULL;
|
|
struct hda_codec_cmd_ctl *rirb = NULL;
|
|
uint32_t response_ex = 0;
|
|
uint8_t rintcnt = 0;
|
|
|
|
assert(hci);
|
|
assert(hci->cad <= HDA_CODEC_MAX);
|
|
|
|
response_ex = hci->cad | unsol;
|
|
|
|
sc = hci->hda;
|
|
assert(sc);
|
|
|
|
rirb = &sc->rirb;
|
|
|
|
if (rirb->run) {
|
|
rirb->wp++;
|
|
rirb->wp %= rirb->size;
|
|
|
|
hda_dma_st_dword(rirb->dma_vaddr + HDA_RIRB_ENTRY_LEN * \
|
|
rirb->wp, response);
|
|
hda_dma_st_dword(rirb->dma_vaddr + HDA_RIRB_ENTRY_LEN * \
|
|
rirb->wp + 0x04, response_ex);
|
|
|
|
hda_set_reg_by_offset(sc, HDAC_RIRBWP, rirb->wp);
|
|
|
|
sc->rirb_cnt++;
|
|
}
|
|
|
|
rintcnt = hda_get_reg_by_offset(sc, HDAC_RINTCNT);
|
|
if (sc->rirb_cnt == rintcnt)
|
|
hda_response_interrupt(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hda_transfer(struct hda_codec_inst *hci, uint8_t stream, uint8_t dir,
|
|
void *buf, size_t count)
|
|
{
|
|
struct hda_softc *sc = NULL;
|
|
struct hda_stream_desc *st = NULL;
|
|
struct hda_bdle_desc *bdl = NULL;
|
|
struct hda_bdle_desc *bdle_desc = NULL;
|
|
uint8_t stream_ind = 0;
|
|
uint32_t lpib = 0;
|
|
uint32_t off = 0;
|
|
size_t left = 0;
|
|
uint8_t irq = 0;
|
|
|
|
assert(hci);
|
|
assert(hci->hda);
|
|
assert(buf);
|
|
assert(!(count % HDA_DMA_ACCESS_LEN));
|
|
|
|
if (!stream) {
|
|
DPRINTF("Invalid stream\n");
|
|
return (-1);
|
|
}
|
|
|
|
sc = hci->hda;
|
|
|
|
assert(stream < HDA_STREAM_TAGS_CNT);
|
|
stream_ind = sc->stream_map[dir][stream];
|
|
|
|
if (!dir)
|
|
assert(stream_ind < HDA_ISS_NO);
|
|
else
|
|
assert(stream_ind >= HDA_ISS_NO && stream_ind < HDA_IOSS_NO);
|
|
|
|
st = &sc->streams[stream_ind];
|
|
if (!st->run) {
|
|
DPRINTF("Stream 0x%x stopped\n", stream);
|
|
return (-1);
|
|
}
|
|
|
|
assert(st->stream == stream);
|
|
|
|
off = hda_get_offset_stream(stream_ind);
|
|
|
|
lpib = hda_get_reg_by_offset(sc, off + HDAC_SDLPIB);
|
|
|
|
bdl = st->bdl;
|
|
|
|
assert(st->be < st->bdl_cnt);
|
|
assert(st->bp < bdl[st->be].len);
|
|
|
|
left = count;
|
|
while (left) {
|
|
bdle_desc = &bdl[st->be];
|
|
|
|
if (dir)
|
|
*(uint32_t *)buf = \
|
|
hda_dma_ld_dword(bdle_desc->addr + st->bp);
|
|
else
|
|
hda_dma_st_dword(bdle_desc->addr + st->bp,
|
|
*(uint32_t *)buf);
|
|
|
|
buf += HDA_DMA_ACCESS_LEN;
|
|
st->bp += HDA_DMA_ACCESS_LEN;
|
|
lpib += HDA_DMA_ACCESS_LEN;
|
|
left -= HDA_DMA_ACCESS_LEN;
|
|
|
|
if (st->bp == bdle_desc->len) {
|
|
st->bp = 0;
|
|
if (bdle_desc->ioc)
|
|
irq = 1;
|
|
st->be++;
|
|
if (st->be == st->bdl_cnt) {
|
|
st->be = 0;
|
|
lpib = 0;
|
|
}
|
|
bdle_desc = &bdl[st->be];
|
|
}
|
|
}
|
|
|
|
hda_set_pib(sc, stream_ind, lpib);
|
|
|
|
if (irq) {
|
|
hda_set_field_by_offset(sc, off + HDAC_SDSTS,
|
|
HDAC_SDSTS_BCIS, HDAC_SDSTS_BCIS);
|
|
hda_update_intr(sc);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
hda_set_pib(struct hda_softc *sc, uint8_t stream_ind, uint32_t pib)
|
|
{
|
|
uint32_t off = hda_get_offset_stream(stream_ind);
|
|
|
|
hda_set_reg_by_offset(sc, off + HDAC_SDLPIB, pib);
|
|
/* LPIB Alias */
|
|
hda_set_reg_by_offset(sc, 0x2000 + off + HDAC_SDLPIB, pib);
|
|
if (sc->dma_pib_vaddr)
|
|
*(uint32_t *)(sc->dma_pib_vaddr + stream_ind * \
|
|
HDA_DMA_PIB_ENTRY_LEN) = pib;
|
|
}
|
|
|
|
static uint64_t hda_get_clock_ns(void)
|
|
{
|
|
struct timespec ts;
|
|
int err;
|
|
|
|
err = clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
assert(!err);
|
|
|
|
return (ts.tv_sec * 1000000000LL + ts.tv_nsec);
|
|
}
|
|
|
|
/*
|
|
* PCI HDA function definitions
|
|
*/
|
|
static int
|
|
pci_hda_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
|
|
{
|
|
struct hda_softc *sc = NULL;
|
|
|
|
assert(ctx != NULL);
|
|
assert(pi != NULL);
|
|
|
|
pci_set_cfgdata16(pi, PCIR_VENDOR, INTEL_VENDORID);
|
|
pci_set_cfgdata16(pi, PCIR_DEVICE, HDA_INTEL_82801G);
|
|
|
|
pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_MULTIMEDIA_HDA);
|
|
pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_MULTIMEDIA);
|
|
|
|
/* select the Intel HDA mode */
|
|
pci_set_cfgdata8(pi, PCIR_HDCTL, 0x01);
|
|
|
|
/* allocate one BAR register for the Memory address offsets */
|
|
pci_emul_alloc_bar(pi, 0, PCIBAR_MEM32, HDA_LAST_OFFSET);
|
|
|
|
/* allocate an IRQ pin for our slot */
|
|
pci_lintr_request(pi);
|
|
|
|
sc = hda_init(opts);
|
|
if (!sc)
|
|
return (-1);
|
|
|
|
sc->pci_dev = pi;
|
|
pi->pi_arg = sc;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
pci_hda_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
|
|
int baridx, uint64_t offset, int size, uint64_t value)
|
|
{
|
|
struct hda_softc *sc = pi->pi_arg;
|
|
int err;
|
|
|
|
assert(sc);
|
|
assert(baridx == 0);
|
|
assert(size <= 4);
|
|
|
|
DPRINTF("offset: 0x%lx value: 0x%lx\n", offset, value);
|
|
|
|
err = hda_write(sc, offset, size, value);
|
|
assert(!err);
|
|
}
|
|
|
|
static uint64_t
|
|
pci_hda_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
|
|
int baridx, uint64_t offset, int size)
|
|
{
|
|
struct hda_softc *sc = pi->pi_arg;
|
|
uint64_t value = 0;
|
|
|
|
assert(sc);
|
|
assert(baridx == 0);
|
|
assert(size <= 4);
|
|
|
|
value = hda_read(sc, offset);
|
|
|
|
DPRINTF("offset: 0x%lx value: 0x%lx\n", offset, value);
|
|
|
|
return (value);
|
|
}
|