mirror of https://gitlab.com/nakst/essence
525 lines
22 KiB
C++
525 lines
22 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
#include <module.h>
|
|
|
|
#define RD_REGISTER_GCAP() controller->pci->ReadBAR16(0, 0x00) // Global capabilities.
|
|
#define RD_REGISTER_VMIN() controller->pci->ReadBAR8(0, 0x02) // Minor version number.
|
|
#define RD_REGISTER_VMAJ() controller->pci->ReadBAR8(0, 0x03) // Major version number.
|
|
#define RD_REGISTER_GCTL() controller->pci->ReadBAR32(0, 0x08) // Global control.
|
|
#define WR_REGISTER_GCTL(x) controller->pci->WriteBAR32(0, 0x08, x)
|
|
#define RD_REGISTER_STATESTS() controller->pci->ReadBAR16(0, 0x0E) // State change status.
|
|
#define RD_REGISTER_INTCTL() controller->pci->ReadBAR32(0, 0x20) // Interrupt control.
|
|
#define WR_REGISTER_INTCTL(x) controller->pci->WriteBAR32(0, 0x20, x)
|
|
#define RD_REGISTER_INTSTS() controller->pci->ReadBAR32(0, 0x24) // Interrupt status.
|
|
#define WR_REGISTER_CORBLBASE(x) controller->pci->WriteBAR32(0, 0x40, x) // CORB base address.
|
|
#define WR_REGISTER_CORBUBASE(x) controller->pci->WriteBAR32(0, 0x44, x)
|
|
#define RD_REGISTER_CORBWP(x) controller->pci->ReadBAR16(0, 0x48) // CORB write pointer.
|
|
#define WR_REGISTER_CORBWP(x) controller->pci->WriteBAR16(0, 0x48, x)
|
|
#define RD_REGISTER_CORBRP(x) controller->pci->ReadBAR16(0, 0x4A) // CORB read pointer.
|
|
#define WR_REGISTER_CORBRP(x) controller->pci->WriteBAR16(0, 0x4A, x)
|
|
#define RD_REGISTER_CORBCTL() controller->pci->ReadBAR8(0, 0x4C) // CORB control.
|
|
#define WR_REGISTER_CORBCTL(x) controller->pci->WriteBAR8(0, 0x4C, x)
|
|
#define RD_REGISTER_CORBSIZE() controller->pci->ReadBAR8(0, 0x4E) // CORB size.
|
|
#define WR_REGISTER_CORBSIZE(x) controller->pci->WriteBAR8(0, 0x4E, x)
|
|
#define WR_REGISTER_RIRBLBASE(x) controller->pci->WriteBAR32(0, 0x50, x) // RIRB base address.
|
|
#define WR_REGISTER_RIRBUBASE(x) controller->pci->WriteBAR32(0, 0x54, x)
|
|
#define RD_REGISTER_RIRBWP(x) controller->pci->ReadBAR16(0, 0x58) // RIRB write pointer.
|
|
#define WR_REGISTER_RIRBWP(x) controller->pci->WriteBAR16(0, 0x58, x)
|
|
#define RD_REGISTER_RINTCNT() controller->pci->ReadBAR16(0, 0x5A) // Response interrupt count.
|
|
#define WR_REGISTER_RINTCNT(x) controller->pci->WriteBAR16(0, 0x5A, x)
|
|
#define RD_REGISTER_RIRBCTL() controller->pci->ReadBAR8(0, 0x5C) // RIRB control.
|
|
#define WR_REGISTER_RIRBCTL(x) controller->pci->WriteBAR8(0, 0x5C, x)
|
|
#define RD_REGISTER_RIRBSTS() controller->pci->ReadBAR8(0, 0x5D) // RIRB status.
|
|
#define WR_REGISTER_RIRBSTS(x) controller->pci->WriteBAR8(0, 0x5D, x)
|
|
#define RD_REGISTER_RIRBSIZE() controller->pci->ReadBAR8(0, 0x5E) // RIRB size.
|
|
#define WR_REGISTER_RIRBSIZE(x) controller->pci->WriteBAR8(0, 0x5E, x)
|
|
#define WR_REGISTER_ImmComOut(x) controller->pci->WriteBAR32(0, 0x60, x) // Immediate command output.
|
|
#define RD_REGISTER_ImmComIn() controller->pci->ReadBAR32(0, 0x64) // Immediate command input.
|
|
#define RD_REGISTER_ImmComStat() controller->pci->ReadBAR16(0, 0x68) // Immediate command status.
|
|
#define WR_REGISTER_ImmComStat(x) controller->pci->WriteBAR16(0, 0x68, x)
|
|
#define RD_REGISTER_SDCTL(n) controller->pci->ReadBAR32(0, 0x80 + 0x20 * (n)) // Stream descriptor control.
|
|
#define WR_REGISTER_SDCTL(n, x) controller->pci->WriteBAR32(0, 0x80 + 0x20 * (n), x)
|
|
#define RD_REGISTER_SDSTS(n) controller->pci->ReadBAR8(0, 0x83 + 0x20 * (n)) // Stream descriptor status.
|
|
#define WR_REGISTER_SDSTS(n, x) controller->pci->WriteBAR8(0, 0x83 + 0x20 * (n), x)
|
|
#define RD_REGISTER_SDLPIB(n) controller->pci->ReadBAR32(0, 0x84 + 0x20 * (n)) // Stream descriptor link position in cyclic buffer.
|
|
#define WR_REGISTER_SDCBL(n, x) controller->pci->WriteBAR32(0, 0x88 + 0x20 * (n), x) // Stream descriptor cyclic buffer length.
|
|
#define RD_REGISTER_SDLVI(n) controller->pci->ReadBAR16(0, 0x8C + 0x20 * (n)) // Stream descriptor last valid index.
|
|
#define WR_REGISTER_SDLVI(n, x) controller->pci->WriteBAR16(0, 0x8C + 0x20 * (n), x)
|
|
#define RD_REGISTER_SDFMT(n) controller->pci->ReadBAR16(0, 0x92 + 0x20 * (n)) // Stream descriptor format.
|
|
#define WR_REGISTER_SDFMT(n, x) controller->pci->WriteBAR16(0, 0x92 + 0x20 * (n), x)
|
|
#define WR_REGISTER_SDBDPL(n, x) controller->pci->WriteBAR32(0, 0x98 + 0x20 * (n), x) // Stream descriptor BDL pointer lower base address.
|
|
#define WR_REGISTER_SDBDPU(n, x) controller->pci->WriteBAR32(0, 0x9C + 0x20 * (n), x) // Stream descriptor BDL pointer upper base address.
|
|
|
|
#define READ_PARAMETER_VENDOR_ID (0xF0000)
|
|
#define READ_PARAMETER_REVISION_ID (0xF0002)
|
|
#define READ_PARAMETER_CHILD_NODES (0xF0004)
|
|
#define READ_PARAMETER_FUNCTION_GROUP_TYPE (0xF0005)
|
|
#define READ_PARAMETER_AUDIO_FUNCTION_CAPABILITIES (0xF0008)
|
|
#define READ_PARAMETER_AUDIO_WIDGET_CAPABILITIES (0xF0009)
|
|
#define READ_PARAMETER_FORMAT_CAPABILITIES (0xF000A)
|
|
#define READ_PARAMETER_STREAM_FORMATS (0xF000B)
|
|
#define READ_PARAMETER_PIN_CAPABILITIES (0xF000C)
|
|
#define READ_PARAMETER_INPUT_AMP_CAPABILITIES (0xF000D)
|
|
#define READ_PARAMETER_CONNECTION_LIST_LENGTH (0xF000E)
|
|
#define READ_PARAMETER_OUTPUT_AMP_CAPABILITIES (0xF0012)
|
|
|
|
#define COMMAND_GET_CONNECTION_LIST_ENTRY(offset) (0xF0200 | (offset))
|
|
#define COMMAND_SET_CONNECTION_SELECT(index) (0x70100 | (index))
|
|
#define COMMAND_GET_AMPLIFIER_GAIN_MUTE(out, left, index) (0xB0000 | ((out) ? (1 << 15) : 0) | ((left) ? (1 << 13) : 0) | (index))
|
|
#define COMMAND_SET_AMPLIFIER_GAIN_MUTE(out, left, index, mute, gain) (0x30000 | ((out) ? (1 << 15) : (1 << 14)) | ((left) ? (1 << 13) : (1 << 12)) \
|
|
| ((index) << 8) | ((mute) ? (1 << 7) : 0) | (gain))
|
|
#define COMMAND_SET_CONVERTER_FORMAT(format) (0x20000 | (format))
|
|
#define COMMAND_SET_STREAM_NUMBER(stream, channel) (0x70600 | ((stream) << 4) | ((channel) << 0))
|
|
#define COMMAND_GET_PIN_WIDGET_CONTROL() (0xF0700)
|
|
#define COMMAND_SET_PIN_WIDGET_CONTROL(control) (0x70700 | (control))
|
|
#define COMMAND_PIN_SENSE() (0xF0900)
|
|
#define COMMAND_GET_PIN_CONFIGURATION() (0xF1C00)
|
|
#define COMMAND_RESET() (0x7FF00)
|
|
|
|
#define HDA_WIDGET_AUDIO_OUTPUT (0)
|
|
#define HDA_WIDGET_AUDIO_INPUT (1)
|
|
#define HDA_WIDGET_PIN_COMPLEX (4)
|
|
|
|
#define PIN_MAYBE_CONNECTED (0)
|
|
#define PIN_UNCONNECTED (1)
|
|
#define PIN_CONNECTED (2)
|
|
|
|
struct HDAWidget : KDevice {
|
|
uint32_t codec;
|
|
uint32_t node;
|
|
uint32_t functionGroup;
|
|
uint32_t type;
|
|
|
|
#define MAXIMUM_INPUTS (32)
|
|
uint8_t inputs[MAXIMUM_INPUTS];
|
|
|
|
union {
|
|
struct {
|
|
uint32_t pinCapabilities, pinConfiguration;
|
|
uint8_t pinIsConnected;
|
|
bool pinIsInput, pinIsOutput;
|
|
};
|
|
};
|
|
};
|
|
|
|
struct HDAController : KDevice {
|
|
KPCIDevice *pci;
|
|
|
|
size_t outputStreamsSupported;
|
|
size_t inputStreamsSupported;
|
|
size_t bidirectionalStreamsSupported;
|
|
|
|
size_t corbEntries, rirbEntries;
|
|
uint8_t *corbVirtual, *rirbVirtual;
|
|
uintptr_t corbPhysical, rirbPhysical;
|
|
uintptr_t corbWritePointer, rirbReadPointer;
|
|
uint32_t rirbLastSolicitedResponse;
|
|
KEvent rirbReceivedSolicitedResponse;
|
|
};
|
|
|
|
static const char *const widgetTypeStrings[] = {
|
|
"Audio output",
|
|
"Audio input",
|
|
"Audio mixer",
|
|
"Audio selector",
|
|
"Pin complex",
|
|
"Power widget",
|
|
"Volume knob",
|
|
"Beep generator",
|
|
};
|
|
|
|
static const char *const portConnectivityStrings[] = {
|
|
"jack",
|
|
"none",
|
|
"integrated",
|
|
"jack and integrated",
|
|
};
|
|
|
|
static const char *const locationHighStrings[] = {
|
|
"external",
|
|
"internal",
|
|
"separate",
|
|
"other",
|
|
};
|
|
|
|
static const char *const locationLowStrings[] = {
|
|
"??",
|
|
"rear",
|
|
"front",
|
|
"left",
|
|
"right",
|
|
"top",
|
|
"bottom",
|
|
"special",
|
|
"special",
|
|
"special",
|
|
"??",
|
|
"??",
|
|
"??",
|
|
"??",
|
|
"??",
|
|
"??",
|
|
};
|
|
|
|
static const char *const defaultDeviceStrings[] = {
|
|
"line out",
|
|
"speaker",
|
|
"HP out",
|
|
"CD",
|
|
"SPDIF out",
|
|
"digital other out",
|
|
"modem line side",
|
|
"modem handset side",
|
|
"line in",
|
|
"AUX",
|
|
"microphone in",
|
|
"telephony",
|
|
"SPDIF in",
|
|
"digital other in",
|
|
"??",
|
|
"??",
|
|
};
|
|
|
|
static const char *const connectionTypeStrings[] = {
|
|
"unknown",
|
|
"1/8\" stereo/mono",
|
|
"1/4\" stereo/mono",
|
|
"ATAPI internal",
|
|
"RCA",
|
|
"optical",
|
|
"other digital",
|
|
"other analog",
|
|
"multichannel analog (DIN)",
|
|
"XLR/Professional",
|
|
"RJ-11 (Modem)",
|
|
"combination",
|
|
"??",
|
|
"??",
|
|
"??",
|
|
"??",
|
|
};
|
|
|
|
static const char *const colorStrings[] = {
|
|
"unknown",
|
|
"black",
|
|
"grey",
|
|
"blue",
|
|
"green",
|
|
"red",
|
|
"orange",
|
|
"yellow",
|
|
"purple",
|
|
"pink",
|
|
"??",
|
|
"??",
|
|
"??",
|
|
"??",
|
|
"white",
|
|
"other",
|
|
};
|
|
|
|
static bool HDAControllerSendCommand(HDAController *controller, uint32_t codec, uint32_t node, uint32_t data, uint32_t *response) {
|
|
// TODO Test wrap-around.
|
|
|
|
uint32_t command = (codec << 28) | (node << 20) | data;
|
|
uintptr_t corbWritePointer = controller->corbWritePointer;
|
|
corbWritePointer = (corbWritePointer + 1) % controller->corbEntries;
|
|
((volatile uint32_t *) controller->corbVirtual)[corbWritePointer] = command;
|
|
WR_REGISTER_CORBWP(ES_ISOLATE_BITS(RD_REGISTER_CORBWP(), 15, 8) | corbWritePointer);
|
|
controller->corbWritePointer = corbWritePointer;
|
|
if (!KEventWait(&controller->rirbReceivedSolicitedResponse, 500 /* half a second timeout */)) return false;
|
|
if (response) *response = controller->rirbLastSolicitedResponse;
|
|
return true;
|
|
}
|
|
|
|
static bool HDAControllerHandleIRQ(uintptr_t, void *context) {
|
|
HDAController *controller = (HDAController *) context;
|
|
uint32_t interruptStatus = RD_REGISTER_INTSTS();
|
|
|
|
if (~interruptStatus & (1 << 31 /* global interrupt status */)) {
|
|
return false;
|
|
}
|
|
|
|
if (interruptStatus & (1 << 30)) {
|
|
uint8_t rirbStatus = RD_REGISTER_RIRBSTS();
|
|
WR_REGISTER_RIRBSTS(rirbStatus);
|
|
|
|
if (rirbStatus & (1 << 0 /* response interrupt */)) {
|
|
uint8_t rirbWritePointer = RD_REGISTER_RIRBWP();
|
|
|
|
while (controller->rirbReadPointer != rirbWritePointer) {
|
|
controller->rirbReadPointer = (controller->rirbReadPointer + 1) % controller->rirbEntries;
|
|
uint32_t response = ((volatile uint32_t *) controller->rirbVirtual)[controller->rirbReadPointer * 2 + 0];
|
|
uint32_t extended = ((volatile uint32_t *) controller->rirbVirtual)[controller->rirbReadPointer * 2 + 1];
|
|
|
|
if (~extended & (1 << 4)) {
|
|
controller->rirbLastSolicitedResponse = response;
|
|
KEventSet(&controller->rirbReceivedSolicitedResponse);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void HDAControllerExploreFunctionGroup(HDAController *controller, uint32_t codec, uint32_t functionGroupNode) {
|
|
uint32_t type, childNodeCount;
|
|
|
|
if (!HDAControllerSendCommand(controller, codec, functionGroupNode, READ_PARAMETER_FUNCTION_GROUP_TYPE, &type)
|
|
|| !HDAControllerSendCommand(controller, codec, functionGroupNode, READ_PARAMETER_CHILD_NODES, &childNodeCount)) {
|
|
return;
|
|
}
|
|
|
|
uint32_t firstChildNode = ES_EXTRACT_BITS(childNodeCount, 23, 16);
|
|
childNodeCount = ES_EXTRACT_BITS(childNodeCount, 7, 0);
|
|
type = ES_EXTRACT_BITS(type, 7, 0);
|
|
|
|
KernelLog(LOG_INFO, "HDA", "found function group", "Found function group with type %d (%z), and child nodes %d to %d.\n",
|
|
type, type == 1 ? "audio" : type == 2 ? "modem" : "??", firstChildNode, firstChildNode + childNodeCount - 1);
|
|
|
|
for (uintptr_t j = firstChildNode; j < firstChildNode + childNodeCount; j++) {
|
|
uint32_t widgetCapabilities;
|
|
|
|
if (!HDAControllerSendCommand(controller, codec, j, READ_PARAMETER_AUDIO_WIDGET_CAPABILITIES, &widgetCapabilities)) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t widgetType = ES_EXTRACT_BITS(widgetCapabilities, 23, 20);
|
|
|
|
HDAWidget *widget = (HDAWidget *) KDeviceCreate("HD Audio widget", controller, sizeof(HDAWidget));
|
|
widget->codec = codec;
|
|
widget->node = j;
|
|
widget->functionGroup = functionGroupNode;
|
|
widget->type = widgetType;
|
|
|
|
KernelLog(LOG_INFO, "HDA", "found widget", "Widget at node %d has type \"%z\".\n",
|
|
widget->node, widgetType >= sizeof(widgetTypeStrings) / sizeof(widgetTypeStrings[0]) ? "Other" : widgetTypeStrings[widgetType]);
|
|
|
|
if (widgetCapabilities & (1 << 8)) {
|
|
uint32_t connectionListLength, connectionList;
|
|
|
|
if (HDAControllerSendCommand(controller, codec, widget->node, READ_PARAMETER_CONNECTION_LIST_LENGTH, &connectionListLength)
|
|
&& (~connectionListLength & (1 << 7) /* long form not supported */)
|
|
&& ES_EXTRACT_BITS(connectionListLength, 6, 0)) {
|
|
uintptr_t index = 0;
|
|
|
|
for (uintptr_t command = 0; command < (connectionListLength + 3) / 4; command++) {
|
|
if (!HDAControllerSendCommand(controller, codec, widget->node, COMMAND_GET_CONNECTION_LIST_ENTRY(command * 4), &connectionList)) {
|
|
break;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < (connectionListLength - command * 4) && index < MAXIMUM_INPUTS; i++) {
|
|
uint8_t entry = connectionList >> (i * 8);
|
|
|
|
if ((entry & 0x80) && index) {
|
|
for (uintptr_t node = widget->inputs[index - 1]; node <= (entry & 0x7F) && index < MAXIMUM_INPUTS; node++) {
|
|
widget->inputs[index++] = node;
|
|
}
|
|
} else {
|
|
widget->inputs[index++] = entry;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < MAXIMUM_INPUTS; i++) {
|
|
if (!widget->inputs[i]) break;
|
|
KernelLog(LOG_INFO, "HDA", "widget connection", "Widget %d has possible input %d.\n", widget->node, widget->inputs[i]);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < controller->children.Length(); i++) {
|
|
HDAWidget *widget = (HDAWidget *) controller->children[i];
|
|
|
|
if (widget->type == HDA_WIDGET_PIN_COMPLEX) {
|
|
if (!HDAControllerSendCommand(controller, codec, widget->node, READ_PARAMETER_PIN_CAPABILITIES, &widget->pinCapabilities)
|
|
|| !HDAControllerSendCommand(controller, codec, widget->node, COMMAND_GET_PIN_CONFIGURATION(), &widget->pinConfiguration)) {
|
|
continue;
|
|
}
|
|
|
|
widget->pinIsOutput = widget->pinCapabilities & (1 << 4);
|
|
widget->pinIsInput = widget->pinCapabilities & (1 << 5);
|
|
|
|
KernelLog(LOG_INFO, "HDA", "pin information", "Pin %d has capabilities %x and configuration %x.%z%z\n",
|
|
widget->node, widget->pinCapabilities, widget->pinConfiguration,
|
|
widget->pinIsOutput ? " Output." : "", widget->pinIsInput ? " Input." : "");
|
|
|
|
if (!widget->pinIsOutput && !widget->pinIsInput) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t portConnectivity = ES_EXTRACT_BITS(widget->pinConfiguration, 31, 30);
|
|
uint32_t location = ES_EXTRACT_BITS(widget->pinConfiguration, 29, 24);
|
|
uint32_t defaultDevice = ES_EXTRACT_BITS(widget->pinConfiguration, 23, 20);
|
|
uint32_t connectionType = ES_EXTRACT_BITS(widget->pinConfiguration, 19, 16);
|
|
uint32_t color = ES_EXTRACT_BITS(widget->pinConfiguration, 15, 12);
|
|
|
|
KernelLog(LOG_INFO, "HDA", "pin information", "Connectivity: %z; location: %z %z; default device: %z; connection type: %z; color: %z.\n",
|
|
portConnectivityStrings[portConnectivity],
|
|
locationHighStrings[ES_EXTRACT_BITS(location, 5, 4)], locationLowStrings[ES_EXTRACT_BITS(location, 3, 0)],
|
|
defaultDeviceStrings[defaultDevice], connectionTypeStrings[connectionType], colorStrings[color]);
|
|
|
|
if (widget->pinCapabilities & (1 << 2)) {
|
|
uint32_t pinSense;
|
|
|
|
if (HDAControllerSendCommand(controller, codec, widget->node, COMMAND_PIN_SENSE(), &pinSense)) {
|
|
widget->pinIsConnected = (pinSense & (1 << 31)) ? PIN_CONNECTED : PIN_UNCONNECTED;
|
|
KernelLog(LOG_INFO, "HDA", "pin sense", "Pin sense: %z.\n", widget->pinIsConnected == PIN_CONNECTED ? "connected" : "unconnected");
|
|
}
|
|
}
|
|
|
|
KDeviceSendConnectedMessage(widget, ES_DEVICE_AUDIO);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void HDAControllerDestroy(KDevice *_controller) {
|
|
HDAController *controller = (HDAController *) _controller;
|
|
if (controller->corbVirtual) MMPhysicalFreeAndUnmap(controller->corbVirtual, controller->corbPhysical);
|
|
if (controller->rirbVirtual) MMPhysicalFreeAndUnmap(controller->rirbVirtual, controller->rirbPhysical);
|
|
// TODO Unregister interrupt handler.
|
|
}
|
|
|
|
static void HDAControllerAttach(KDevice *_parent) {
|
|
HDAController *controller = (HDAController *) KDeviceCreate("HD Audio controller", _parent, sizeof(HDAController));
|
|
|
|
if (!controller) {
|
|
return;
|
|
}
|
|
|
|
controller->destroy = HDAControllerDestroy;
|
|
controller->rirbReceivedSolicitedResponse.autoReset = true;
|
|
|
|
controller->pci = (KPCIDevice *) _parent;
|
|
controller->pci->EnableFeatures(K_PCI_FEATURE_INTERRUPTS | K_PCI_FEATURE_BUSMASTERING_DMA
|
|
| K_PCI_FEATURE_MEMORY_SPACE_ACCESS | K_PCI_FEATURE_BAR_0);
|
|
|
|
uint16_t globalCapabilities = RD_REGISTER_GCAP();
|
|
bool supports64BitAddresses = globalCapabilities & (1 << 0);
|
|
|
|
#ifdef ES_BITS_64
|
|
if (!supports64BitAddresses) {
|
|
KernelLog(LOG_ERROR, "HDA", "controller unsupported", "Controller does not support 64-bit addresses.\n");
|
|
KDeviceDestroy(controller);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
controller->outputStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 15, 12);
|
|
controller->inputStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 11, 8);
|
|
controller->bidirectionalStreamsSupported = ES_EXTRACT_BITS(globalCapabilities, 7, 3);
|
|
|
|
KernelLog(LOG_INFO, "HDA", "global capabilities", "Controller supports %d output streams, %d input streams and %d bidi streams.\n",
|
|
controller->outputStreamsSupported, controller->inputStreamsSupported, controller->bidirectionalStreamsSupported);
|
|
KernelLog(LOG_INFO, "HDA", "version", "Controller reports version %d.%d.\n",
|
|
RD_REGISTER_VMAJ(), RD_REGISTER_VMIN());
|
|
|
|
KTimeout timeout(1000); // The initialisation process shouldn't take more than a second.
|
|
|
|
#define CHECK_TIMEOUT(message) \
|
|
if (timeout.Hit()) { \
|
|
KernelLog(LOG_ERROR, "HDA", "timeout", "Timeout during initialization: " message ".\n"); \
|
|
KDeviceDestroy(controller); \
|
|
return; \
|
|
}
|
|
|
|
// Reset the controller.
|
|
|
|
WR_REGISTER_GCTL(RD_REGISTER_GCTL() & ~(1 << 0 /* CRST */));
|
|
while ((RD_REGISTER_GCTL() & (1 << 0)) && timeout.Hit());
|
|
CHECK_TIMEOUT("clear CRST bit");
|
|
WR_REGISTER_GCTL(RD_REGISTER_GCTL() | (1 << 0 /* CRST */));
|
|
while ((~RD_REGISTER_GCTL() & (1 << 0)) && timeout.Hit());
|
|
CHECK_TIMEOUT("set CRST bit");
|
|
|
|
// Setup CORB/RIRB.
|
|
|
|
uint8_t corbSize = RD_REGISTER_CORBSIZE();
|
|
controller->corbEntries = (corbSize & (1 << 6)) ? 256 : (corbSize & (1 << 5)) ? 16 : (corbSize & (1 << 4)) ? 2 : 0;
|
|
uint8_t rirbSize = RD_REGISTER_RIRBSIZE();
|
|
controller->rirbEntries = (rirbSize & (1 << 6)) ? 256 : (rirbSize & (1 << 5)) ? 16 : (rirbSize & (1 << 4)) ? 2 : 0;
|
|
|
|
if (!controller->corbEntries || !controller->rirbEntries) {
|
|
KernelLog(LOG_ERROR, "HDA", "unsupported", "Controller does not support any recognised CORB/RIRB sizes.\n");
|
|
KDeviceDestroy(controller);
|
|
return;
|
|
}
|
|
|
|
if (!MMPhysicalAllocateAndMap(controller->corbEntries * 4, 128, 0, true,
|
|
MM_REGION_NOT_CACHEABLE, &controller->corbVirtual, &controller->corbPhysical)
|
|
|| !MMPhysicalAllocateAndMap(controller->rirbEntries * 8, 128, 0, true,
|
|
MM_REGION_NOT_CACHEABLE, &controller->rirbVirtual, &controller->rirbPhysical)) {
|
|
KernelLog(LOG_ERROR, "HDA", "insufficient resources", "Could not allocate memory for CORB/RIRB.\n");
|
|
KDeviceDestroy(controller);
|
|
return;
|
|
}
|
|
|
|
WR_REGISTER_CORBSIZE(ES_ISOLATE_BITS(RD_REGISTER_CORBSIZE(), 7, 2) | (controller->corbEntries == 16 ? 1 : controller->corbEntries == 256 ? 2 : 0));
|
|
WR_REGISTER_RIRBSIZE(ES_ISOLATE_BITS(RD_REGISTER_RIRBSIZE(), 7, 2) | (controller->rirbEntries == 16 ? 1 : controller->rirbEntries == 256 ? 2 : 0));
|
|
|
|
WR_REGISTER_CORBLBASE(controller->corbPhysical & 0xFFFFFFFF);
|
|
if (supports64BitAddresses) WR_REGISTER_CORBUBASE(controller->corbPhysical >> 32);
|
|
WR_REGISTER_RIRBLBASE(controller->rirbPhysical & 0xFFFFFFFF);
|
|
if (supports64BitAddresses) WR_REGISTER_RIRBUBASE(controller->rirbPhysical >> 32);
|
|
|
|
WR_REGISTER_RINTCNT(ES_ISOLATE_BITS(RD_REGISTER_RINTCNT(), 15, 8) | 1 /* interrupt after every response */);
|
|
|
|
WR_REGISTER_CORBCTL(ES_ISOLATE_BITS(RD_REGISTER_CORBCTL(), 7, 2) | (1 << 1 /* run */) | (1 << 0 /* interrupt on error */));
|
|
WR_REGISTER_RIRBCTL(ES_ISOLATE_BITS(RD_REGISTER_RIRBCTL(), 7, 3) | (1 << 1 /* run */) | (1 << 0 /* interrupt on response */));
|
|
|
|
if ((~RD_REGISTER_CORBCTL() & (1 << 1)) || (~RD_REGISTER_RIRBCTL() & (1 << 1))) {
|
|
KernelLog(LOG_ERROR, "HDA", "start error", "Could not start the CORB/RIRB.\n");
|
|
KDeviceDestroy(controller);
|
|
return;
|
|
}
|
|
|
|
// Setup interrupts.
|
|
|
|
if (!controller->pci->EnableSingleInterrupt(HDAControllerHandleIRQ, controller, "HDA")) {
|
|
KernelLog(LOG_ERROR, "HDA", "insufficient resources", "Could not register interrupt handler.\n");
|
|
KDeviceDestroy(controller);
|
|
return;
|
|
}
|
|
|
|
WR_REGISTER_INTCTL((1 << 31) | (1 << 30));
|
|
|
|
// Enumerate codecs.
|
|
|
|
uint16_t codecs = RD_REGISTER_STATESTS();
|
|
|
|
for (uintptr_t i = 0; i < 15; i++) {
|
|
if (~codecs & (1 << i)) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t vendorID, childNodeCount;
|
|
|
|
if (HDAControllerSendCommand(controller, i, 0, READ_PARAMETER_VENDOR_ID, &vendorID)
|
|
&& HDAControllerSendCommand(controller, i, 0, READ_PARAMETER_CHILD_NODES, &childNodeCount)) {
|
|
uint32_t firstChildNode = ES_EXTRACT_BITS(childNodeCount, 23, 16);
|
|
childNodeCount = ES_EXTRACT_BITS(childNodeCount, 7, 0);
|
|
|
|
KernelLog(LOG_INFO, "HDA", "found codec", "Found codec with vendor ID %x, and child nodes %d to %d.\n",
|
|
vendorID, firstChildNode, firstChildNode + childNodeCount - 1);
|
|
|
|
for (uintptr_t j = firstChildNode; j < firstChildNode + childNodeCount; j++) {
|
|
HDAControllerExploreFunctionGroup(controller, i, j);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO Enable unsolicited responses.
|
|
// TODO Support hotplugging.
|
|
|
|
KernelLog(LOG_INFO, "HDA", "ready", "Controller %x successfully initialized.\n", controller);
|
|
}
|
|
|
|
KDriver driverHDAudio = {
|
|
.attach = HDAControllerAttach,
|
|
};
|