*** WARNING: UNSTABLE CODE AHEAD :P ***

This is the first (sort-of) working incarnation of the High Definition Audio driver. This
driver still has quite some way to go before coming to a point that we could call it stable
and useful, so please use with care. It has been developed on R5, so it might even become
useful for people still stuck on R5.

This driver was inspired by both the BSD work on HDA support, as well as our own dr_evil's 
work on the ich_ac97 driver.

Before you start bugging me about completing the driver, or adding features, please capture
the serial debug output and mail it to ithamar AT unet DOT nl, including hardware details,
and success/failure descriptions.

As a last note, DasJott, if you're reading this, the first music it played was... 
'Prodigy: Smack Your Bitch Up' :)



git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21127 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ithamar R. Adema 2007-05-13 17:04:45 +00:00
parent 830d967717
commit 89db0a5bf7
10 changed files with 1853 additions and 0 deletions

View File

@ -6,3 +6,5 @@ SubInclude HAIKU_TOP src add-ons kernel drivers audio emuxki ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio module_driver ; SubInclude HAIKU_TOP src add-ons kernel drivers audio module_driver ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio sis7018 ; SubInclude HAIKU_TOP src add-ons kernel drivers audio sis7018 ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio usb_audio ; SubInclude HAIKU_TOP src add-ons kernel drivers audio usb_audio ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio hda ;

View File

@ -0,0 +1,18 @@
SubDir HAIKU_TOP src add-ons kernel drivers audio hda ;
SetSubDirSupportedPlatformsBeOSCompatible ;
UsePrivateHeaders media ;
KernelAddon hda :
driver.c
hooks.c
hda_multi_audio.c
hda_controller.c
hda_codec.c
;
Package haiku-hda-cvs :
hda :
boot home config add-ons kernel drivers bin ;
PackageDriverSymLink haiku-hda-cvs : audio multi hda ;

View File

@ -0,0 +1,102 @@
#include "driver.h"
hda_controller cards[MAXCARDS];
uint32 num_cards;
pci_module_info* pci;
status_t
init_hardware(void)
{
pci_info pcii;
status_t rc;
long i;
if ((rc=get_module(B_PCI_MODULE_NAME, (module_info**)&pci)) == B_OK) {
for (i=0; pci->get_nth_pci_info(i,&pcii) == B_OK; i++) {
if (pcii.class_base == PCI_multimedia && pcii.class_sub == PCI_hd_audio) {
put_module(B_PCI_MODULE_NAME);
pci = NULL;
return B_OK;
}
}
}
put_module(B_PCI_MODULE_NAME);
pci = NULL;
return ENODEV;
}
status_t
init_driver (void)
{
char path[B_PATH_NAME_LENGTH];
pci_info pcii;
status_t rc;
long i;
num_cards = 0;
if ((rc=get_module(B_PCI_MODULE_NAME, (module_info**)&pci)) == B_OK) {
for (i=0; pci->get_nth_pci_info(i,&pcii) == B_OK; i++) {
if (pcii.class_base == PCI_multimedia && pcii.class_sub == PCI_hd_audio) {
cards[num_cards].pcii = pcii;
cards[num_cards].opened = 0;
sprintf(path, DEVFS_PATH_FORMAT, num_cards);
cards[num_cards++].devfs_path = strdup(path);
dprintf("HDA: Detected controller @ PCI:%d:%d:%d, IRQ:%d, type %04x/%04x\n",
pcii.bus, pcii.device, pcii.function,
pcii.u.h0.interrupt_line,
pcii.vendor_id, pcii.device_id);
}
}
} else {
return rc;
}
if (num_cards == 0) {
put_module(B_PCI_MODULE_NAME);
pci = NULL;
return ENODEV;
}
return B_OK;
}
void
uninit_driver (void)
{
long i;
for (i=0; i < num_cards; i++) {
free((void*)cards[i].devfs_path);
cards[i].devfs_path = NULL;
}
if (pci != NULL) {
put_module(B_PCI_MODULE_NAME);
pci = NULL;
}
}
const char**
publish_devices()
{
static const char* devs[MAXCARDS+1];
long i;
for (i=0; i < num_cards; i++)
devs[i] = cards[i].devfs_path;
devs[i] = NULL;
return devs;
}
device_hooks*
find_device(const char* name)
{
return &driver_hooks;
}

View File

@ -0,0 +1,163 @@
#ifndef _HDA_H_
#define _HDA_H_
#include <drivers/KernelExport.h>
#include <drivers/Drivers.h>
#include <drivers/PCI.h>
#include <string.h>
#include <stdlib.h>
#include "multi_audio.h"
#include "hda_controller_defs.h"
#include "hda_codec_defs.h"
#define MAXCARDS 4
/* values for the class_sub field for class_base = 0x04 (multimedia device) */
#define PCI_hd_audio 3
#define DEVFS_PATH_FORMAT "audio/multi/hda/%lu"
#define MAXWORK 16
#define HDA_MAXCODECS 15
#define HDA_MAXSTREAMS 16
#define MAX_CODEC_RESPONSES 10
#define MAXINPUTS 32
typedef struct hda_controller_s hda_controller;
#define STRMAXBUF 10
#define STRMINBUF 2
enum {
STRM_PLAYBACK,
STRM_RECORD
};
typedef struct hda_stream_info_s {
uint32 id; /* HDA controller stream # */
uint32 off; /* HDA I/O/B descriptor offset */
bool running; /* Is this stream active? */
uint32 pin_wid; /* PIN Widget ID */
uint32 io_wid; /* Input/Output Converter Widget ID */
uint32 samplerate;
uint32 sampleformat;
uint32 num_buffers;
uint32 num_channels;
uint32 buffer_length; /* size of buffer in samples */
uint32 sample_size;
void* buffers[STRMAXBUF]; /* Virtual addresses for buffer */
uint32 buffers_pa[STRMAXBUF]; /* Physical addresses for buffer */
sem_id buffer_ready_sem;
bigtime_t played_real_time;
uint32 played_frames_count;
area_id buffer_area;
area_id bdl_area;
uint32 bdl_pa; /* BDL physical address */
} hda_stream;
typedef struct hda_codec_s {
uint16 vendor_id;
uint16 product_id;
uint8 hda_rev;
uint16 rev_stepping;
uint8 addr;
sem_id response_sem;
uint32 responses[MAX_CODEC_RESPONSES];
uint32 response_count;
/* Multi Audio API data */
hda_stream* playback_stream;
hda_stream* record_stream;
/* Function Group Data */
uint32 afg_nid;
uint32 afg_wid_start, afg_wid_count;
uint32 afg_deffmts;
uint32 afg_defrates;
uint32 afg_defpm;
struct {
uint32 num_inputs;
uint32 active_input;
uint32 inputs[MAXINPUTS];
uint32 flags;
hda_widget_type type;
uint32 pm;
union {
struct {
uint32 formats;
uint32 rates;
} output;
struct {
uint32 formats;
uint32 rates;
} input;
struct {
} mixer;
struct {
uint32 output : 1;
uint32 input : 1;
pin_dev_type device;
} pin;
} d;
} *afg_widgets;
struct hda_controller_s* ctrlr;
} hda_codec;
struct hda_controller_s {
pci_info pcii;
vuint32 opened;
const char* devfs_path;
area_id regs_area;
vuint8* regs;
uint32 irq;
uint16 codecsts;
uint32 num_input_streams;
uint32 num_output_streams;
uint32 num_bidir_streams;
uint32 corblen;
uint32 rirblen;
uint32 rirbrp;
uint32 corbwp;
area_id rb_area;
corb_t* corb;
rirb_t* rirb;
hda_codec* codecs[HDA_MAXCODECS];
uint32 num_codecs;
hda_stream* streams[HDA_MAXSTREAMS];
};
extern device_hooks driver_hooks;
extern pci_module_info* pci;
extern hda_controller cards[MAXCARDS];
extern uint32 num_cards;
status_t hda_hw_init(hda_controller* ctrlr);
void hda_hw_stop(hda_controller* ctrlr);
void hda_hw_uninit(hda_controller* ctrlr);
hda_codec* hda_codec_new(hda_controller* ctrlr, uint32 cad);
status_t hda_send_verbs(hda_codec* codec, corb_t* verbs, uint32* responses, int count);
status_t multi_audio_control(void* cookie, uint32 op, void* arg, size_t len);
hda_stream* hda_stream_alloc(hda_controller* ctrlr, int type);
status_t hda_stream_setup_buffers(hda_codec* codec, hda_stream* s, const char* desc);
status_t hda_stream_start(hda_controller* ctrlr, hda_stream* s);
status_t hda_stream_stop(hda_controller* ctrlr, hda_stream* s);
status_t hda_stream_check_intr(hda_controller* ctrlr, hda_stream* s);
#endif /* _HDA_H_ */

View File

@ -0,0 +1,411 @@
#include "driver.h"
#include "hda_codec_defs.h"
const char* portcon[] = {
"Jack", "None", "Fixed", "Dual"
};
const char* defdev[] = {
"Line Out", "Speaker", "HP Out", "CD", "SPDIF out", "Digital Other Out", "Modem Line Side",
"Modem Hand Side", "Line In", "AUX", "Mic In", "Telephony", "SPDIF In", "Digital Other In",
"Reserved", "Other"
};
const char* conntype[] = {
"N/A", "1/8\"", "1/4\"", "ATAPI internal", "RCA", "Optical", "Other Digital", "Other Analog",
"Multichannel Analog (DIN)", "XLR/Professional", "RJ-11 (Modem)", "Combination", "-", "-", "-", "Other"
};
const char* jcolor[] = {
"N/A", "Black", "Grey", "Blue", "Green", "Red", "Orange", "Yellow", "Purple", "Pink",
"-", "-", "-", "-", "White", "Other"
};
static status_t
hda_widget_get_pm_support(hda_codec* codec, uint32 nid, uint32* pm)
{
corb_t verb = MAKE_VERB(codec->addr,nid,VID_GET_PARAM,PID_POWERSTATE_SUPPORT);
status_t rc;
uint32 resp;
if ((rc=hda_send_verbs(codec, &verb, &resp, 1)) == B_OK) {
*pm = 0;
/* FIXME: Define constants for powermanagement modes */
if (resp & (1 << 0)) ;
if (resp & (1 << 1)) ;
if (resp & (1 << 2)) ;
if (resp & (1 << 3)) ;
}
return rc;
}
static status_t
hda_widget_get_stream_support(hda_codec* codec, uint32 nid, uint32* fmts, uint32* rates)
{
corb_t verbs[2];
uint32 resp[2];
status_t rc;
verbs[0] = MAKE_VERB(codec->addr,nid,VID_GET_PARAM,PID_STREAM_SUPPORT);
verbs[1] = MAKE_VERB(codec->addr,nid,VID_GET_PARAM,PID_PCM_SUPPORT);
if ((rc=hda_send_verbs(codec, verbs, resp, 2)) == B_OK) {
*fmts = 0; *rates = 0;
if (resp[2] & (1 << 0)) {
if (resp[1] & (1 << 0)) *rates |= B_SR_8000;
if (resp[1] & (1 << 1)) *rates |= B_SR_11025;
if (resp[1] & (1 << 2)) *rates |= B_SR_16000;
if (resp[1] & (1 << 3)) *rates |= B_SR_22050;
if (resp[1] & (1 << 4)) *rates |= B_SR_32000;
if (resp[1] & (1 << 5)) *rates |= B_SR_44100;
if (resp[1] & (1 << 6)) *rates |= B_SR_48000;
if (resp[1] & (1 << 7)) *rates |= B_SR_88200;
if (resp[1] & (1 << 8)) *rates |= B_SR_96000;
if (resp[1] & (1 << 9)) *rates |= B_SR_176400;
if (resp[1] & (1 << 10)) *rates |= B_SR_192000;
if (resp[1] & (1 << 11)) *rates |= B_SR_384000;
if (resp[1] & (1<<16)) *fmts |= B_FMT_8BIT_S;
if (resp[1] & (1<<17)) *fmts |= B_FMT_16BIT;
if (resp[1] & (1<<18)) *fmts |= B_FMT_18BIT;
if (resp[1] & (1<<19)) *fmts |= B_FMT_24BIT;
if (resp[1] & (1<<20)) *fmts |= B_FMT_32BIT;
}
if (resp[0] & (1 << 1)) *fmts |= B_FMT_FLOAT;
if (resp[0] & (1 << 2)) /* Sort out how to handle AC3 */;
}
return rc;
}
static status_t
hda_widget_get_amplifier_capabilities(hda_codec* codec, uint32 nid)
{
status_t rc;
corb_t verb;
uint32 resp;
verb = MAKE_VERB(codec->addr,nid,VID_GET_PARAM,PID_OUTPUT_AMP_CAP);
if ((rc=hda_send_verbs(codec, &verb, &resp, 1)) == B_OK && resp != 0) {
dprintf("\tAMP: Mute: %s, step size: %ld, # steps: %ld, offset: %ld\n",
(resp & (1 << 31)) ? "supported" : "N/A",
(resp >> 16) & 0x7F,
(resp >> 8) & 0x7F,
resp & 0x7F);
}
return rc;
}
static status_t
hda_codec_parse_afg(hda_codec* codec, uint32 afg_nid)
{
corb_t verbs[6];
uint32 resp[6];
uint32 widx;
hda_widget_get_stream_support(codec, afg_nid, &codec->afg_deffmts, &codec->afg_defrates);
hda_widget_get_pm_support(codec, afg_nid, &codec->afg_defpm);
verbs[0] = MAKE_VERB(codec->addr,afg_nid,VID_GET_PARAM,PID_AUDIO_FG_CAP);
verbs[1] = MAKE_VERB(codec->addr,afg_nid,VID_GET_PARAM,PID_GPIO_COUNT);
verbs[2] = MAKE_VERB(codec->addr,afg_nid,VID_GET_PARAM,PID_SUBORD_NODE_COUNT);
if (hda_send_verbs(codec, verbs, resp, 3) == B_OK) {
dprintf("%s: Output delay: %ld samples, Input delay: %ld samples, Beep Generator: %s\n", __func__,
resp[0] & 0xf, (resp[0] >> 8) & 0xf, (resp[0] & (1 << 16)) ? "yes" : "no");
dprintf("%s: #GPIO: %ld, #GPO: %ld, #GPI: %ld, unsol: %s, wake: %s\n", __func__,
resp[4] & 0xFF, (resp[1] >> 8) & 0xFF, (resp[1] >> 16) & 0xFF,
(resp[1] & (1 << 30)) ? "yes" : "no",
(resp[1] & (1 << 31)) ? "yes" : "no");
codec->afg_wid_start = resp[2] >> 16;
codec->afg_wid_count = resp[2] & 0xFF;
codec->afg_widgets = calloc(sizeof(*codec->afg_widgets), codec->afg_wid_count);
if (codec->afg_widgets == NULL) {
dprintf("ERROR: Not enough memory!\n");
return B_NO_MEMORY;
}
/* Only now mark the AFG as found and used */
codec->afg_nid = afg_nid;
/* Iterate over all Widgets and collect info */
for (widx=0; widx < codec->afg_wid_count; widx++) {
uint32 wid = codec->afg_wid_start + widx;
char buf[256];
int off;
verbs[0] = MAKE_VERB(codec->addr,wid,VID_GET_PARAM,PID_AUDIO_WIDGET_CAP);
verbs[1] = MAKE_VERB(codec->addr,wid,VID_GET_PARAM,PID_CONNLIST_LEN);
hda_send_verbs(codec, verbs, resp, 2);
codec->afg_widgets[widx].type = resp[0] >> 20;
codec->afg_widgets[widx].num_inputs = resp[1] & 0x7F;
off = 0;
if (resp[0] & (1 << 11)) off += sprintf(buf+off, "[L-R Swap] ");
if (resp[0] & (1 << 10)) off += sprintf(buf+off, "[Power] ");
if (resp[0] & (1 << 9)) off += sprintf(buf+off, "[Digital] ");
if (resp[0] & (1 << 7)) off += sprintf(buf+off, "[Unsol Capable] ");
if (resp[0] & (1 << 6)) off += sprintf(buf+off, "[Proc Widget] ");
if (resp[0] & (1 << 5)) off += sprintf(buf+off, "[Stripe] ");
if (resp[0] & (1 << 4)) off += sprintf(buf+off, "[Format Override] ");
if (resp[0] & (1 << 3)) off += sprintf(buf+off, "[Amp Param Override] ");
if (resp[0] & (1 << 2)) off += sprintf(buf+off, "[Out Amp] ");
if (resp[0] & (1 << 1)) off += sprintf(buf+off, "[In Amp] ");
if (resp[0] & (1 << 0)) off += sprintf(buf+off, "[Stereo] ");
switch(codec->afg_widgets[widx].type) {
case WT_AUDIO_OUTPUT:
dprintf("%ld:\tAudio Output\n", wid);
hda_widget_get_stream_support(codec, wid,
&codec->afg_widgets[widx].d.input.formats,
&codec->afg_widgets[widx].d.input.rates);
hda_widget_get_amplifier_capabilities(codec, wid);
break;
case WT_AUDIO_INPUT:
dprintf("%ld:\tAudio Input\n", wid);
hda_widget_get_stream_support(codec, wid,
&codec->afg_widgets[widx].d.input.formats,
&codec->afg_widgets[widx].d.input.rates);
hda_widget_get_amplifier_capabilities(codec, wid);
break;
case WT_AUDIO_MIXER:
dprintf("%ld:\tAudio Mixer\n", wid);
hda_widget_get_amplifier_capabilities(codec, wid);
break;
case WT_AUDIO_SELECTOR:
dprintf("%ld:\tAudio Selector\n", wid);
hda_widget_get_amplifier_capabilities(codec, wid);
break;
case WT_PIN_COMPLEX:
dprintf("%ld:\tPin Complex\n", wid);
verbs[0] = MAKE_VERB(codec->addr,wid,VID_GET_PARAM,PID_PIN_CAP);
if (hda_send_verbs(codec, verbs, resp, 1) == B_OK) {
codec->afg_widgets[widx].d.pin.input = resp[0] & (1 << 5);
codec->afg_widgets[widx].d.pin.output = resp[0] & (1 << 4);
}
verbs[0] = MAKE_VERB(codec->addr,wid,VID_GET_CFGDEFAULT,0);
if (hda_send_verbs(codec, verbs, resp, 1) == B_OK) {
codec->afg_widgets[widx].d.pin.device = (resp[0] >> 20) & 0xF;
dprintf("\t%s, %s, %s, %s\n",
portcon[resp[0] >> 30],
defdev[codec->afg_widgets[widx].d.pin.device],
conntype[(resp[0] >> 16) & 0xF],
jcolor[(resp[0] >> 12) & 0xF]);
}
hda_widget_get_amplifier_capabilities(codec, wid);
break;
case WT_POWER:
dprintf("%ld:\tPower\n", wid);
break;
case WT_VOLUME_KNOB:
dprintf("%ld:\tVolume Knob\n", wid);
break;
case WT_BEEP_GENERATOR:
dprintf("%ld:\tBeep Generator\n", wid);
break;
case WT_VENDOR_DEFINED:
dprintf("%ld:\tVendor Defined\n", wid);
break;
default: /* Reserved */
break;
}
dprintf("\t%s\n", buf);
hda_widget_get_pm_support(codec, wid, &codec->afg_widgets[widx].pm);
if (codec->afg_widgets[widx].num_inputs) {
int idx;
off = 0;
if (codec->afg_widgets[widx].num_inputs > 1) {
verbs[0] = MAKE_VERB(codec->addr,wid,VID_GET_CONNSEL,0);
if (hda_send_verbs(codec, verbs, resp, 1) == B_OK)
codec->afg_widgets[widx].active_input = resp[0] & 0xFF;
else
codec->afg_widgets[widx].active_input = -1;
} else
codec->afg_widgets[widx].active_input = -1;
for (idx=0; idx < codec->afg_widgets[widx].num_inputs; idx ++) {
if (!(idx % 4)) {
verbs[0] = MAKE_VERB(codec->addr,wid,VID_GET_CONNLENTRY,idx);
if (hda_send_verbs(codec, verbs, resp, 1) != B_OK) {
dprintf("%s: Error parsing inputs for widget %ld!\n", __func__, wid);
break;
}
}
if (idx != codec->afg_widgets[widx].active_input)
off += sprintf(buf+off, "%ld ", (resp[0] >> (8*(idx%4))) & 0xFF);
else
off += sprintf(buf+off, "(%ld) ", (resp[0] >> (8*(idx%4))) & 0xFF);
codec->afg_widgets[widx].inputs[idx] = (resp[0] >> (8*(idx%4))) & 0xFF;
}
dprintf("\t[ %s]\n", buf);
}
}
}
return B_OK;
}
static uint32
hda_codec_afg_find_dac_path(hda_codec* codec, uint32 wid, uint32 depth)
{
int widx = wid - codec->afg_wid_start;
int idx;
switch(codec->afg_widgets[widx].type) {
case WT_AUDIO_OUTPUT:
return wid;
case WT_AUDIO_MIXER:
for (idx=0; idx < codec->afg_widgets[widx].num_inputs; idx++) {
if (hda_codec_afg_find_dac_path(codec, codec->afg_widgets[widx].inputs[idx], depth +1)) {
if (codec->afg_widgets[widx].active_input == -1)
codec->afg_widgets[widx].active_input = idx;
return codec->afg_widgets[widx].inputs[idx];
}
}
break;
case WT_AUDIO_SELECTOR:
{
int idx = codec->afg_widgets[widx].active_input;
if (idx != -1) {
uint32 wid = codec->afg_widgets[widx].inputs[idx];
if (hda_codec_afg_find_dac_path(codec, wid, depth +1)) {
return wid;
}
}
}
break;
default:
break;
}
return 0;
}
static void
hda_codec_audiofg_new(hda_codec* codec, uint32 afg_nid)
{
uint32 idx;
/* FIXME: Bail if this isn't the first Audio Function Group we find... */
if (codec->afg_nid != 0) {
dprintf("SORRY: This driver currently only supports a single Audio Function Group!\n");
return;
}
/* Parse all widgets in Audio Function Group */
hda_codec_parse_afg(codec, afg_nid);
/* Try to locate all output channels */
for (idx=0; idx < codec->afg_wid_count; idx++) {
uint32 iidx, output_wid = 0;
if (codec->afg_widgets[idx].type != WT_PIN_COMPLEX)
continue;
if (!codec->afg_widgets[idx].d.pin.output)
continue;
if (codec->afg_widgets[idx].d.pin.device != PIN_DEV_HP_OUT &&
codec->afg_widgets[idx].d.pin.device != PIN_DEV_SPEAKER &&
codec->afg_widgets[idx].d.pin.device != PIN_DEV_LINE_OUT)
continue;
iidx = codec->afg_widgets[idx].active_input;
if (iidx != -1) {
output_wid = hda_codec_afg_find_dac_path(codec, codec->afg_widgets[idx].inputs[iidx], 0);
} else {
for (iidx=0; iidx < codec->afg_widgets[idx].num_inputs; iidx++) {
output_wid = hda_codec_afg_find_dac_path(codec, codec->afg_widgets[idx].inputs[iidx], 0);
if (output_wid) {
corb_t verb = MAKE_VERB(codec->addr,idx+codec->afg_wid_start,VID_SET_CONNSEL,iidx);
uint32 resp;
if (hda_send_verbs(codec, &verb, &resp, 1) == B_OK)
break;
}
}
}
if (output_wid) {
corb_t verb;
uint32 resp;
codec->playback_stream->pin_wid = idx + codec->afg_wid_start;
codec->playback_stream->io_wid = output_wid;
dprintf("%s: Found output PIN (%s) connected to output CONV wid:%ld\n",
__func__, defdev[codec->afg_widgets[idx].d.pin.device], output_wid);
/* FIXME: Force Pin Widget to unmute */
verb = MAKE_VERB(codec->addr, codec->playback_stream->pin_wid,
VID_SET_AMPGAINMUTE, (1 << 15) | (1 << 13) | (1 << 12));
hda_send_verbs(codec, &verb, &resp, 1);
break;
}
}
}
hda_codec*
hda_codec_new(hda_controller* ctrlr, uint32 cad)
{
hda_codec* codec = calloc(sizeof(hda_codec),1);
if (codec) {
uint32 responses[3];
corb_t verbs[3];
status_t rc;
uint32 nid;
codec->ctrlr = ctrlr;
codec->addr = cad;
codec->response_count = 0;
codec->response_sem = create_sem(0, "hda_codec_response_sem");
codec->afg_nid = 0;
ctrlr->codecs[cad] = codec;
/* Setup playback/record streams for Multi Audio API */
codec->playback_stream = hda_stream_alloc(ctrlr, STRM_PLAYBACK);
codec->record_stream = hda_stream_alloc(ctrlr, STRM_RECORD);
verbs[0] = MAKE_VERB(cad,0,VID_GET_PARAM,PID_VENDORID);
verbs[1] = MAKE_VERB(cad,0,VID_GET_PARAM,PID_REVISIONID);
verbs[2] = MAKE_VERB(cad,0,VID_GET_PARAM,PID_SUBORD_NODE_COUNT);
if (hda_send_verbs(codec, verbs, responses, 3) == B_OK) {
dprintf("Codec %ld Vendor: %04lx Product: %04lx\n",
cad, responses[0] >> 16, responses[0] & 0xFFFF);
for (nid=responses[2] >> 16;
nid < (responses[2] >> 16) + (responses[2] & 0xFF);
nid++) {
uint32 resp;
verbs[0] = MAKE_VERB(cad,nid,VID_GET_PARAM,PID_FUNCGRP_TYPE);
if ((rc=hda_send_verbs(codec, verbs, &resp, 1)) == B_OK && (resp&0xFF) == 1) {
/* Found an Audio Function Group! */
hda_codec_audiofg_new(codec, nid);
} else
dprintf("%s: FG %ld: %s\n", __func__, nid, strerror(rc));
}
}
}
return codec;
}

View File

@ -0,0 +1,130 @@
#ifndef HDA_CODEC_H
#define HDA_CODEC_H
typedef enum {
WT_AUDIO_OUTPUT = 0,
WT_AUDIO_INPUT = 1,
WT_AUDIO_MIXER = 2,
WT_AUDIO_SELECTOR = 3,
WT_PIN_COMPLEX = 4,
WT_POWER = 5,
WT_VOLUME_KNOB = 6,
WT_BEEP_GENERATOR = 7,
WT_VENDOR_DEFINED = 15
} hda_widget_type;
typedef enum {
PIN_DEV_LINE_OUT = 0,
PIN_DEV_SPEAKER,
PIN_DEV_HP_OUT,
PIN_DEV_CD,
PIN_DEV_SPDIF_OUT,
PIN_DEV_DIGITAL_OTHER_OUT,
PIN_DEV_MODEM_LINE_SIDE,
PIN_DEV_MODEM_HAND_SIDE,
PIN_DEV_LINE_IN,
PIN_DEV_AUX,
PIN_DEV_MIC_IN,
PIN_DEV_TELEPHONY,
PIN_DEV_SPDIF_IN,
PIN_DEV_DIGITAL_OTHER_IN,
PIN_DEV_RESERVED,
PIN_DEV_OTHER
} pin_dev_type;
/* Verb Helper Macro */
#define MAKE_VERB(cad,nid,vid,payl) (((cad)<<28)|((nid)<<20)|(vid)|(payl))
/* Verb IDs */
#define VID_GET_PARAM 0xF0000
#define VID_GET_CONNSEL 0xF0100
#define VID_SET_CONNSEL 0x70100
#define VID_GET_CONNLENTRY 0xF0200
#define VID_GET_PROCSTATE 0xF0300
#define VID_SET_PROCSTATE 0x70300
#define VID_GET_COEFFIDX 0xD0000
#define VID_SET_COEFFIDX 0x50000
#define VID_GET_PROCCOEFF 0xC0000
#define VID_SET_PROCCOEFF 0x40000
#define VID_GET_AMPGAINMUTE 0xB0000
#define VID_SET_AMPGAINMUTE 0x30000
#define VID_GET_CONVFORMAT 0xA0000
#define VID_SET_CONVFORMAT 0x20000
#define VID_GET_DIGCVTCTRL 0xF0D00
#define VID_SET_DIGCVTCTRL1 0x70D00
#define VID_SET_DIGCVTCTRL2 0x70E00
#define VID_GET_POWERSTATE 0xF0500
#define VID_SET_POWERSTATE 0x70500
#define VID_GET_CVTSTRCHN 0xF0600
#define VID_SET_CVTSTRCHN 0x70600
#define VID_GET_SDISELECT 0xF0400
#define VID_SET_SDISELECT 0x70400
#define VID_GET_PINWCTRL 0xF0700
#define VID_SET_PINWCTRL 0x70700
#define VID_GET_UNSOLRESP 0xF0800
#define VID_SET_UNSOLRESP 0x70800
#define VID_GET_PINSENSE 0xF0900
#define VID_SET_PINSENSE 0x70900
#define VID_GET_EAPDBTL_EN 0xF0C00
#define VID_SET_EAPDBTL_EN 0x70C00
#define VID_GET_GPIDATA 0xF1000
#define VID_SET_GPIDATA 0x71000
#define VID_GET_GPIWAKE_EN 0xF1100
#define VID_SET_GPIWAKE_EN 0x71100
#define VID_GET_GPIUNSOL 0xF1200
#define VID_SET_GPIUNSOL 0x71200
#define VID_GET_GPISTICKY 0xF1300
#define VID_SET_GPISTICKY 0x71300
#define VID_GET_GPODATA 0xF1400
#define VID_SET_GPODATA 0x71400
#define VID_GET_GPIODATA 0xF1500
#define VID_SET_GPIODATA 0x71500
#define VID_GET_GPIO_EN 0xF1600
#define VID_SET_GPIO_EN 0x71600
#define VID_GET_GPIO_DIR 0xF1700
#define VID_SET_GPIO_DIR 0x71700
#define VID_GET_GPIOWAKE_EN 0xF1800
#define VID_SET_GPIOWAKE_EN 0x71800
#define VID_GET_GPIOUNSOL_EN 0xF1900
#define VID_SET_GPIOUNSOL_EN 0x71900
#define VID_GET_GPIOSTICKY 0xF1A00
#define VID_SET_GPIOSTICKY 0x71A00
#define VID_GET_BEEPGEN 0xF0A00
#define VID_SET_BEEPGEN 0x70A00
#define VID_GET_VOLUMEKNOB 0xF0F00
#define VID_SET_VOLUMEKNOB 0x70F00
#define VID_GET_SUBSYSTEMID 0xF2000
#define VID_SET_SUBSYSTEMID1 0x72000
#define VID_SET_SUBSYSTEMID2 0x72100
#define VID_SET_SUBSYSTEMID3 0x72200
#define VID_SET_SUBSYSTEMID4 0x72300
#define VID_GET_CFGDEFAULT 0xF1C00
#define VID_SET_CFGDEFAULT1 0x71C00
#define VID_SET_CFGDEFAULT2 0x71D00
#define VID_SET_CFGDEFAULT3 0x71E00
#define VID_SET_CFGDEFAULT4 0x71F00
#define VID_GET_STRIPECTRL 0xF2400
#define VID_SET_STRIPECTRL 0x72000
#define VID_FUNCTION_RESET 0x7FF00
/* Parameter IDs */
#define PID_VENDORID 0x00
#define PID_REVISIONID 0x02
#define PID_SUBORD_NODE_COUNT 0x04
#define PID_FUNCGRP_TYPE 0x05
#define PID_AUDIO_FG_CAP 0x08
#define PID_AUDIO_WIDGET_CAP 0x09
#define PID_PCM_SUPPORT 0x0A
#define PID_STREAM_SUPPORT 0x0B
#define PID_PIN_CAP 0x0C
#define PID_INPUT_AMP_CAP 0x0D
#define PID_CONNLIST_LEN 0x0E
#define PID_POWERSTATE_SUPPORT 0x0F
#define PID_PROCESSING_CAP 0x10
#define PID_GPIO_COUNT 0x11
#define PID_OUTPUT_AMP_CAP 0x12
#define PID_VOLUMEKNOB_CAP 0x13
#endif /* HDA_CODEC_H */

View File

@ -0,0 +1,537 @@
#include "driver.h"
#include "hda_controller_defs.h"
#include "hda_codec_defs.h"
#include "driver.h"
hda_stream*
hda_stream_alloc(hda_controller* ctrlr, int type)
{
hda_stream* s = calloc(1, sizeof(hda_stream));
if (s != NULL) {
s->buffer_area = B_ERROR;
s->bdl_area = B_ERROR;
switch(type) {
case STRM_PLAYBACK:
s->buffer_ready_sem = create_sem(0, "hda_playback_sem");
s->id = 1;
s->off = (ctrlr->num_input_streams * HDAC_SDSIZE);
ctrlr->streams[ctrlr->num_input_streams] = s;
break;
case STRM_RECORD:
s->buffer_area = B_ERROR;
s->bdl_area = B_ERROR;
s->buffer_ready_sem = create_sem(0, "hda_record_sem");
s->id = 2;
s->off = 0;
ctrlr->streams[0] = s;
break;
default:
dprintf("%s: Unknown stream type %d!\n", __func__, type);
free(s);
s = NULL;
break;
}
}
return s;
}
status_t
hda_stream_start(hda_controller* ctrlr, hda_stream* s)
{
OREG8(ctrlr,s->off,CTL0) |= CTL0_RUN;
while(!(OREG8(ctrlr,s->off,CTL0) & CTL0_RUN))
snooze(1);
s->running = true;
return B_OK;
}
status_t
hda_stream_check_intr(hda_controller* ctrlr, hda_stream* s)
{
if (s->running) {
uint8 sts = OREG8(ctrlr,s->off,STS);
if (sts) {
OREG8(ctrlr,s->off,STS) = sts;
s->played_real_time = system_time();
s->played_frames_count += s->buffer_length;
release_sem_etc(s->buffer_ready_sem, 1, B_DO_NOT_RESCHEDULE);
}
}
return B_OK;
}
status_t
hda_stream_stop(hda_controller* ctrlr, hda_stream* s)
{
OREG8(ctrlr,s->off,CTL0) &= ~CTL0_RUN;
while(OREG8(ctrlr,s->off,CTL0) & CTL0_RUN)
snooze(1);
s->running = false;
return B_OK;
}
status_t
hda_stream_setup_buffers(hda_codec* codec, hda_stream* s, const char* desc)
{
uint32 buffer_size, buffer_pa, alloc;
uint32 response[2], idx;
physical_entry pe;
bdl_entry_t* bdl;
corb_t verb[2];
uint8* buffer;
status_t rc;
uint16 wfmt;
/* Clear previously allocated memory */
if (s->buffer_area >= B_OK) {
delete_area(s->buffer_area);
s->buffer_area = B_ERROR;
}
if (s->bdl_area >= B_OK) {
delete_area(s->bdl_area);
s->bdl_area = B_ERROR;
}
/* Calculate size of buffer (aligned to 128 bytes) */
buffer_size = s->sample_size * s->num_channels * s->buffer_length;
buffer_size = (buffer_size + 127) & (~127);
/* Calculate total size of all buffers (aligned to size of B_PAGE_SIZE) */
alloc = buffer_size * s->num_buffers;
alloc = (alloc + B_PAGE_SIZE - 1) & (~(B_PAGE_SIZE -1));
/* Allocate memory for buffers */
s->buffer_area = create_area("hda_buffers", (void**)&buffer, B_ANY_KERNEL_ADDRESS, alloc, B_CONTIGUOUS, B_READ_AREA | B_WRITE_AREA);
if (s->buffer_area < B_OK)
return s->buffer_area;
/* Get the physical address of memory */
rc = get_memory_map(buffer, alloc, &pe, 1);
if (rc != B_OK) {
delete_area(s->buffer_area);
return rc;
}
buffer_pa = (uint32)pe.address;
dprintf("%s(%s): Allocated %lu bytes for %ld buffers\n", __func__, desc,
alloc, s->num_buffers);
/* Store pointers (both virtual/physical) */
for (idx=0; idx < s->num_buffers; idx++) {
s->buffers[idx] = buffer + (idx*buffer_size);
s->buffers_pa[idx] = buffer_pa + (idx*buffer_size);
}
/* Now allocate BDL for buffer range */
alloc = s->num_buffers * sizeof(bdl_entry_t);
alloc = (alloc + B_PAGE_SIZE - 1) & (~(B_PAGE_SIZE -1));
s->bdl_area = create_area("hda_bdl", (void**)&bdl, B_ANY_KERNEL_ADDRESS, alloc, B_CONTIGUOUS, 0);
if (s->bdl_area < B_OK) {
delete_area(s->buffer_area);
return s->bdl_area;
}
/* Get the physical address of memory */
rc = get_memory_map(bdl, alloc, &pe, 1);
if (rc != B_OK) {
delete_area(s->buffer_area);
delete_area(s->bdl_area);
return rc;
}
s->bdl_pa = (uint32)pe.address;
dprintf("%s(%s): Allocated %ld bytes for %ld BDLEs\n", __func__, desc,
alloc, s->num_buffers);
/* Setup BDL entries */
for (idx=0; idx < s->num_buffers; idx++, bdl++) {
bdl->address = buffer_pa + (idx*buffer_size);
bdl->length = buffer_size;
bdl->ioc = 1;
}
/* Configure stream registers */
wfmt = s->num_channels -1;
switch(s->sampleformat) {
case B_FMT_8BIT_S: wfmt |= (0 << 4); break;
case B_FMT_16BIT: wfmt |= (1 << 4); break;
case B_FMT_24BIT: wfmt |= (3 << 4); break;
case B_FMT_32BIT: wfmt |= (4 << 4); break;
default: dprintf("%s: Invalid sample format: 0x%lx\n", __func__, s->sampleformat); break;
}
switch(s->samplerate) {
case B_SR_8000: wfmt |= (7 << 8); break;
case B_SR_11025: wfmt |= (67 << 8); break;
case B_SR_16000: wfmt |= (2 << 8); break;
case B_SR_22050: wfmt |= (65 << 8); break;
case B_SR_32000: wfmt |= (10 << 8); break;
case B_SR_44100: wfmt |= (64 << 8); break;
case B_SR_48000: wfmt |= (0 << 8); break;
case B_SR_88200: wfmt |= (72 << 8); break;
case B_SR_96000: wfmt |= (8 << 8); break;
case B_SR_176400: wfmt |= (88 << 8); break;
case B_SR_192000: wfmt |= (24 << 8); break;
default: dprintf("%s: Invalid sample rate: 0x%lx\n", __func__, s->samplerate); break;
}
OREG16(codec->ctrlr,s->off,FMT) = wfmt;
OREG32(codec->ctrlr,s->off,BDPL) = s->bdl_pa;
OREG32(codec->ctrlr,s->off,BDPU) = 0;
OREG16(codec->ctrlr,s->off,LVI) = s->num_buffers -1;
OREG32(codec->ctrlr,s->off,CBL) = s->num_channels * s->num_buffers * s->sample_size;
OREG8(codec->ctrlr,s->off,CTL0) = CTL0_IOCE | CTL0_FEIE | CTL0_DEIE;
OREG8(codec->ctrlr,s->off,CTL2) = s->id << 4;
verb[0] = MAKE_VERB(codec->addr, s->io_wid, VID_SET_CONVFORMAT, wfmt);
verb[1] = MAKE_VERB(codec->addr, s->io_wid, VID_SET_CVTSTRCHN, s->id << 4);
rc = hda_send_verbs(codec, verb, response, 2);
return rc;
}
//#pragma mark -
status_t
hda_send_verbs(hda_codec* codec, corb_t* verbs, uint32* responses, int count)
{
corb_t* corb = codec->ctrlr->corb;
status_t rc;
codec->response_count = 0;
memcpy(corb+(codec->ctrlr->corbwp +1), verbs, sizeof(corb_t)*count);
REG16(codec->ctrlr,CORBWP) = (codec->ctrlr->corbwp += count);
rc = acquire_sem_etc(codec->response_sem, count, B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT, 1000ULL * 50);
if (rc == B_OK)
memcpy(responses, codec->responses, count*sizeof(uint32));
return rc;
}
static int32
hda_interrupt_handler(hda_controller* ctrlr)
{
int32 rc = B_UNHANDLED_INTERRUPT;
/* Check if this interrupt is ours */
uint32 intsts = REG32(ctrlr, INTSTS);
if (intsts & INTSTS_GIS) {
rc = B_HANDLED_INTERRUPT;
/* Controller or stream related? */
if (intsts & INTSTS_CIS) {
uint32 statests = REG16(ctrlr,STATESTS);
uint8 rirbsts = REG8(ctrlr,RIRBSTS);
uint8 corbsts = REG8(ctrlr,CORBSTS);
if (statests) {
/* Detected Codec state change */
REG16(ctrlr,STATESTS) = statests;
ctrlr->codecsts = statests;
}
/* Check for incoming responses */
if (rirbsts) {
REG8(ctrlr,RIRBSTS) = rirbsts;
if (rirbsts & RIRBSTS_RINTFL) {
uint16 rirbwp = REG16(ctrlr,RIRBWP);
while(ctrlr->rirbrp <= rirbwp) {
uint32 resp_ex = ctrlr->rirb[ctrlr->rirbrp].resp_ex;
uint32 cad = resp_ex & HDA_MAXCODECS;
hda_codec* codec = ctrlr->codecs[cad];
if (resp_ex & RESP_EX_UNSOL) {
dprintf("%s: Usolicited response: %08lx/%08lx\n", __func__,
ctrlr->rirb[ctrlr->rirbrp].response, resp_ex);
} else if (codec) {
/* Store responses in codec */
codec->responses[codec->response_count++] = ctrlr->rirb[ctrlr->rirbrp].response;
release_sem_etc(codec->response_sem, 1, B_DO_NOT_RESCHEDULE);
rc = B_INVOKE_SCHEDULER;
} else {
dprintf("%s: Response for unknown codec %ld: %08lx/%08lx\n", __func__, cad,
ctrlr->rirb[ctrlr->rirbrp].response, resp_ex);
}
++ctrlr->rirbrp;
}
}
if (rirbsts & RIRBSTS_OIS)
dprintf("%s: RIRB Overflow\n", __func__);
}
/* Check for sending errors */
if (corbsts) {
REG8(ctrlr,CORBSTS) = corbsts;
if (corbsts & CORBSTS_MEI)
dprintf("%s: CORB Memory Error!\n", __func__);
}
}
if (intsts & ~(INTSTS_CIS|INTSTS_GIS)) {
int idx;
for (idx=0; idx < HDA_MAXSTREAMS; idx++) {
if (intsts & (1 << idx)) {
if (ctrlr->streams[idx])
hda_stream_check_intr(ctrlr, ctrlr->streams[idx]);
else
dprintf("%s: Stream interrupt for unconfigured stream %d!\n", __func__, idx);
}
}
}
/* NOTE: See HDA001 => CIS/GIS cannot be cleared! */
}
return rc;
}
static status_t
hda_hw_start(hda_controller* ctrlr)
{
int timeout = 10;
/* Put controller out of reset mode */
REG32(ctrlr,GCTL) |= GCTL_CRST;
do {
snooze(100);
} while(--timeout && !(REG32(ctrlr,GCTL) & GCTL_CRST));
return timeout ? B_OK : B_TIMED_OUT;
}
static status_t
hda_hw_corb_rirb_init(hda_controller* ctrlr)
{
uint32 memsz, rirboff;
uint8 corbsz, rirbsz;
status_t rc = B_OK;
physical_entry pe;
/* Determine and set size of CORB */
corbsz = REG8(ctrlr,CORBSIZE);
if (corbsz & CORBSIZE_CAP_256E) {
ctrlr->corblen = 256;
REG8(ctrlr,CORBSIZE) = CORBSIZE_SZ_256E;
} else if (corbsz & CORBSIZE_CAP_16E) {
ctrlr->corblen = 16;
REG8(ctrlr,CORBSIZE) = CORBSIZE_SZ_16E;
} else if (corbsz & CORBSIZE_CAP_2E) {
ctrlr->corblen = 2;
REG8(ctrlr,CORBSIZE) = CORBSIZE_SZ_2E;
}
/* Determine and set size of RIRB */
rirbsz = REG8(ctrlr,RIRBSIZE);
if (rirbsz & RIRBSIZE_CAP_256E) {
ctrlr->rirblen = 256;
REG8(ctrlr,RIRBSIZE) = RIRBSIZE_SZ_256E;
} else if (rirbsz & RIRBSIZE_CAP_16E) {
ctrlr->rirblen = 16;
REG8(ctrlr,RIRBSIZE) = RIRBSIZE_SZ_16E;
} else if (rirbsz & RIRBSIZE_CAP_2E) {
ctrlr->rirblen = 2;
REG8(ctrlr,RIRBSIZE) = RIRBSIZE_SZ_2E;
}
/* Determine rirb offset in memory and total size of corb+alignment+rirb */
rirboff = (ctrlr->corblen * sizeof(corb_t) + 0x7f) & ~0x7f;
memsz = ((B_PAGE_SIZE -1) +
rirboff +
(ctrlr->rirblen * sizeof(rirb_t))) & ~(B_PAGE_SIZE-1);
/* Allocate memory area */
ctrlr->rb_area = create_area("hda_corb_rirb",
(void**)&ctrlr->corb, B_ANY_KERNEL_ADDRESS, memsz, B_CONTIGUOUS, 0);
if (ctrlr->rb_area < 0) {
return ctrlr->rb_area;
}
/* Rirb is after corb+aligment */
ctrlr->rirb = (rirb_t*)(((uint8*)ctrlr->corb)+rirboff);
if ((rc=get_memory_map(ctrlr->corb, memsz, &pe, 1)) != B_OK) {
delete_area(ctrlr->rb_area);
return ctrlr->rb_area;
}
/* Program CORB/RIRB for these locations */
REG32(ctrlr,CORBLBASE) = (uint32)pe.address;
REG32(ctrlr,RIRBLBASE) = (uint32)pe.address + rirboff;
/* Reset CORB read pointer */
/* NOTE: See HDA011 for corrected procedure! */
REG16(ctrlr,CORBRP) = CORBRP_RST;
do {
snooze(10);
} while( !(REG16(ctrlr,CORBRP) & CORBRP_RST) );
REG16(ctrlr,CORBRP) = 0;
/* Reset RIRB write pointer */
REG16(ctrlr,RIRBWP) = RIRBWP_RST;
/* Generate interrupt for every response */
REG16(ctrlr,RINTCNT) = 1;
/* Setup cached read/write indices */
ctrlr->rirbrp = 1;
ctrlr->corbwp = 0;
/* Gentlemen, start your engines... */
REG8(ctrlr,CORBCTL) = CORBCTL_RUN | CORBCTL_MEIE;
REG8(ctrlr,RIRBCTL) = RIRBCTL_DMAEN | RIRBCTL_OIC | RIRBCTL_RINTCTL;
return B_OK;
}
//#pragma mark -
/* Setup hardware for use; detect codecs; etc */
status_t
hda_hw_init(hda_controller* ctrlr)
{
status_t rc;
uint16 gcap;
uint32 idx;
/* Map MMIO registers */
ctrlr->regs_area =
map_physical_memory("hda_hw_regs", (void*)ctrlr->pcii.u.h0.base_registers[0],
ctrlr->pcii.u.h0.base_register_sizes[0], B_ANY_KERNEL_ADDRESS, 0,
(void**)&ctrlr->regs);
if (ctrlr->regs_area < B_OK) {
rc = ctrlr->regs_area;
goto error;
}
/* Absolute minimum hw is online; we can now install interrupt handler */
ctrlr->irq = ctrlr->pcii.u.h0.interrupt_line;
rc = install_io_interrupt_handler(ctrlr->irq,
(interrupt_handler)hda_interrupt_handler, ctrlr, 0);
if (rc != B_OK)
goto no_irq;
/* show some hw features */
gcap = REG16(ctrlr,GCAP);
dprintf("HDA: HDA v%d.%d, O:%d/I:%d/B:%d, #SDO:%d, 64bit:%s\n",
REG8(ctrlr,VMAJ), REG8(ctrlr,VMIN),
GCAP_OSS(gcap), GCAP_ISS(gcap), GCAP_BSS(gcap),
GCAP_NSDO(gcap) ? GCAP_NSDO(gcap) *2 : 1,
gcap & GCAP_64OK ? "yes" : "no" );
ctrlr->num_input_streams = GCAP_OSS(gcap);
ctrlr->num_output_streams = GCAP_ISS(gcap);
ctrlr->num_bidir_streams = GCAP_BSS(gcap);
/* Get controller into valid state */
rc = hda_hw_start(ctrlr);
if (rc != B_OK)
goto reset_failed;
/* Setup CORB/RIRB */
rc = hda_hw_corb_rirb_init(ctrlr);
if (rc != B_OK)
goto corb_rirb_failed;
REG16(ctrlr,WAKEEN) = 0x7fff;
/* Enable controller interrupts */
REG32(ctrlr,INTCTL) = INTCTL_GIE | INTCTL_CIE | 0xffff;
/* Wait for codecs to warm up */
snooze(1000);
if (!ctrlr->codecsts) {
rc = ENODEV;
goto corb_rirb_failed;
}
for (idx=0; idx < HDA_MAXCODECS; idx++)
if (ctrlr->codecsts & (1 << idx))
hda_codec_new(ctrlr, idx);
return B_OK;
corb_rirb_failed:
REG32(ctrlr,INTCTL) = 0;
reset_failed:
remove_io_interrupt_handler(ctrlr->irq,
(interrupt_handler)hda_interrupt_handler,
ctrlr);
no_irq:
delete_area(ctrlr->regs_area);
ctrlr->regs_area = B_ERROR;
ctrlr->regs = NULL;
error:
dprintf("ERROR: %s(%ld)\n", strerror(rc), rc);
return rc;
}
/* Stop any activity */
void
hda_hw_stop(hda_controller* ctrlr)
{
int idx;
/* Stop all audio streams */
for (idx=0; idx < HDA_MAXSTREAMS; idx++)
if (ctrlr->streams[idx] && ctrlr->streams[idx]->running)
hda_stream_stop(ctrlr, ctrlr->streams[idx]);
}
/* Free resources */
void
hda_hw_uninit(hda_controller* ctrlr)
{
if (ctrlr != NULL) {
/* Stop all audio streams */
hda_hw_stop(ctrlr);
/* Stop CORB/RIRB */
REG8(ctrlr,CORBCTL) = 0;
REG8(ctrlr,RIRBCTL) = 0;
/* Disable interrupts and remove interrupt handler */
REG32(ctrlr,INTCTL) = 0;
REG32(ctrlr,GCTL) &= ~GCTL_CRST;
remove_io_interrupt_handler(ctrlr->irq,
(interrupt_handler)hda_interrupt_handler,
ctrlr);
/* Unmap registers */
if (ctrlr->regs_area >= 0) {
delete_area(ctrlr->regs_area);
ctrlr->regs_area = B_ERROR;
ctrlr->regs = NULL;
}
}
}

View File

@ -0,0 +1,128 @@
#ifndef HDAC_REGS_H
#define HDAC_REGS_H
#include <SupportDefs.h>
/* Accessors for HDA controller registers */
#define REG32(ctrlr,reg) (*(vuint32*)((ctrlr)->regs + HDAC_##reg))
#define REG16(ctrlr,reg) (*(vuint16*)((ctrlr)->regs + HDAC_##reg))
#define REG8(ctrlr,reg) (*((ctrlr)->regs + HDAC_##reg))
#define OREG32(ctrlr,s,reg) (*(vuint32*)((ctrlr)->regs + HDAC_SDBASE + (s) + HDAC_SD_##reg))
#define OREG16(ctrlr,s,reg) (*(vuint16*)((ctrlr)->regs + HDAC_SDBASE + (s) + HDAC_SD_##reg))
#define OREG8(ctrlr,s,reg) (*((ctrlr)->regs + HDAC_SDBASE + (s) + HDAC_SD_##reg))
/* Register definitions */
#define HDAC_GCAP 0x00 /* 16bits */
#define GCAP_OSS(gcap) (((gcap) >> 12) & 15)
#define GCAP_ISS(gcap) (((gcap) >> 8) & 15)
#define GCAP_BSS(gcap) (((gcap) >> 3) & 15)
#define GCAP_NSDO(gcap) (((gcap) >> 1) & 3)
#define GCAP_64OK ((gcap) & 1)
#define HDAC_VMIN 0x02 /* 8bits */
#define HDAC_VMAJ 0x03 /* 8bits */
#define HDAC_GCTL 0x08 /* 32bits */
#define GCTL_UNSOL (1 << 8) /* Accept Unsolicited responses */
#define GCTL_FCNTRL (1 << 1) /* Flush Control */
#define GCTL_CRST (1 << 0) /* Controller Reset */
#define HDAC_WAKEEN 0x0c /* 16bits */
#define HDAC_STATESTS 0x0e /* 16bits */
#define HDAC_INTCTL 0x20 /* 32bits */
#define INTCTL_GIE (1 << 31) /* Global Interrupt Enable */
#define INTCTL_CIE (1 << 30) /* Controller Interrupt Enable */
#define HDAC_INTSTS 0x24 /* 32bits */
#define INTSTS_GIS (1 << 31) /* Global Interrupt Status */
#define INTSTS_CIS (1 << 30) /* Controller Interrupt Status */
#define HDAC_CORBLBASE 0x40 /* 32bits */
#define HDAC_CORBUBASE 0x44 /* 32bits */
#define HDAC_CORBWP 0x48 /* 16bits */
#define HDAC_CORBRP 0x4a /* 16bits */
#define CORBRP_RST (1 << 15)
#define HDAC_CORBCTL 0x4c /* 8bits */
#define CORBCTL_RUN (1 << 1)
#define CORBCTL_MEIE (1 << 0)
#define HDAC_CORBSTS 0x4d /* 8bits */
#define CORBSTS_MEI (1 << 0)
#define HDAC_CORBSIZE 0x4e /* 8bits */
#define CORBSIZE_CAP_2E (1 << 4)
#define CORBSIZE_CAP_16E (1 << 5)
#define CORBSIZE_CAP_256E (1 << 6)
#define CORBSIZE_SZ_2E 0
#define CORBSIZE_SZ_16E (1 << 0)
#define CORBSIZE_SZ_256E (1 << 1)
#define HDAC_RIRBLBASE 0x50 /* 32bits */
#define HDAC_RIRBUBASE 0x54 /* 32bits */
#define HDAC_RIRBWP 0x58 /* 16bits */
#define RIRBWP_RST (1 << 15)
#define HDAC_RINTCNT 0x5a /* 16bits */
#define HDAC_RIRBCTL 0x5c /* 8bits */
#define RIRBCTL_OIC (1 << 2)
#define RIRBCTL_DMAEN (1 << 1)
#define RIRBCTL_RINTCTL (1 << 0)
#define HDAC_RIRBSTS 0x5d
#define RIRBSTS_OIS (1 << 2)
#define RIRBSTS_RINTFL (1 << 0)
#define HDAC_RIRBSIZE 0x5e /* 8bits */
#define RIRBSIZE_CAP_2E (1 << 4)
#define RIRBSIZE_CAP_16E (1 << 5)
#define RIRBSIZE_CAP_256E (1 << 6)
#define RIRBSIZE_SZ_2E 0
#define RIRBSIZE_SZ_16E (1 << 0)
#define RIRBSIZE_SZ_256E (1 << 1)
#define HDAC_SDBASE 0x80
#define HDAC_SDSIZE 0x20
#define HDAC_SD_CTL0 0x00 /* 8bits */
#define CTL0_SRST (1 << 0)
#define CTL0_RUN (1 << 1)
#define CTL0_IOCE (1 << 2)
#define CTL0_FEIE (1 << 3)
#define CTL0_DEIE (1 << 4)
#define HDAC_SD_CTL1 0x01 /* 8bits */
#define HDAC_SD_CTL2 0x02 /* 8bits */
#define CTL2_DIR (1 << 3)
#define CTL2_TP (1 << 2)
#define HDAC_SD_STS 0x03 /* 8bits */
#define STS_BCIS (1 << 2)
#define STS_FIFOE (1 << 3)
#define STS_DESE (1 << 4)
#define STS_FIFORDY (1 << 5)
#define HDAC_SD_LPIB 0x04 /* 32bits */
#define HDAC_SD_CBL 0x08 /* 32bits */
#define HDAC_SD_LVI 0x0C /* 16bits */
#define HDAC_SD_FIFOS 0x10 /* 16bits */
#define HDAC_SD_FMT 0x12 /* 16bits */
#define HDAC_SD_BDPL 0x18 /* 32bits */
#define HDAC_SD_BDPU 0x1C /* 32bits */
typedef uint32 corb_t;
typedef struct {
uint32 response;
uint32 resp_ex;
#define RESP_EX_UNSOL (1 << 4)
} rirb_t;
typedef struct {
uint64 address;
uint32 length;
uint32 ioc;
} bdl_entry_t;
#endif /* HDAC_REGS_H */

View File

@ -0,0 +1,280 @@
#include "multi_audio.h"
#include "driver.h"
multi_channel_info chans[] = {
{ 0, B_MULTI_OUTPUT_CHANNEL, B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, 0 },
{ 1, B_MULTI_OUTPUT_CHANNEL, B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, 0 },
{ 2, B_MULTI_INPUT_CHANNEL, B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, 0 },
{ 3, B_MULTI_INPUT_CHANNEL, B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, 0 },
{ 4, B_MULTI_OUTPUT_BUS, B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, B_CHANNEL_MINI_JACK_STEREO },
{ 5, B_MULTI_OUTPUT_BUS, B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, B_CHANNEL_MINI_JACK_STEREO },
{ 6, B_MULTI_INPUT_BUS, B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, B_CHANNEL_MINI_JACK_STEREO },
{ 7, B_MULTI_INPUT_BUS, B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, B_CHANNEL_MINI_JACK_STEREO },
};
static int32
format2size(uint32 format)
{
switch(format) {
case B_FMT_8BIT_S:
case B_FMT_16BIT:
return 2;
case B_FMT_18BIT:
case B_FMT_24BIT:
case B_FMT_32BIT:
return 4;
case B_FMT_FLOAT:
return 8;
default:
return -1;
}
}
static status_t
get_description(hda_codec* codec, multi_description* data)
{
data->interface_version = B_CURRENT_INTERFACE_VERSION;
data->interface_minimum = B_CURRENT_INTERFACE_VERSION;
strcpy(data->friendly_name,"HD Audio");
strcpy(data->vendor_info,"Haiku");
data->output_channel_count = 2;
data->input_channel_count = 2;
data->output_bus_channel_count = 2;
data->input_bus_channel_count = 2;
data->aux_bus_channel_count = 0;
dprintf("%s: request_channel_count: %ld\n", __func__, data->request_channel_count);
if (data->request_channel_count >= (int)(sizeof(chans) / sizeof(chans[0]))) {
memcpy(data->channels,&chans,sizeof(chans));
}
/* determine output/input rates */
data->output_rates = codec->afg_defrates;
data->input_rates = codec->afg_defrates;
/* force existance of 48kHz if variable rates are not supported */
if (data->output_rates == 0)
data->output_rates = B_SR_48000;
if (data->input_rates == 0)
data->input_rates = B_SR_48000;
data->max_cvsr_rate = 0;
data->min_cvsr_rate = 0;
data->output_formats = codec->afg_deffmts;
data->input_formats = codec->afg_deffmts;
data->lock_sources = B_MULTI_LOCK_INTERNAL;
data->timecode_sources = 0;
data->interface_flags = B_MULTI_INTERFACE_PLAYBACK /* | B_MULTI_INTERFACE_RECORD */;
data->start_latency = 30000;
strcpy(data->control_panel,"");
return B_OK;
}
static status_t
get_enabled_channels(hda_codec* codec, multi_channel_enable* data)
{
B_SET_CHANNEL(data->enable_bits, 0, true);
B_SET_CHANNEL(data->enable_bits, 1, true);
B_SET_CHANNEL(data->enable_bits, 2, true);
B_SET_CHANNEL(data->enable_bits, 3, true);
data->lock_source = B_MULTI_LOCK_INTERNAL;
return B_OK;
}
static status_t
get_global_format(hda_codec* codec, multi_format_info* data)
{
data->output_latency = 0;
data->input_latency = 0;
data->timecode_kind = 0;
data->output.format = codec->playback_stream->sampleformat;
data->output.rate = codec->playback_stream->samplerate;
data->input.format = codec->record_stream->sampleformat;
data->input.rate = codec->record_stream->sampleformat;
return B_OK;
}
static status_t
set_global_format(hda_codec* codec, multi_format_info* data)
{
codec->playback_stream->sampleformat = data->output.format;
codec->playback_stream->samplerate = data->output.rate;
codec->playback_stream->sample_size = format2size(codec->playback_stream->sampleformat);
codec->record_stream->samplerate = data->input.rate;
codec->record_stream->sampleformat = data->input.format;
codec->record_stream->sample_size = format2size(codec->record_stream->sampleformat);
return B_OK;
}
static status_t
list_mix_controls(hda_codec* codec, multi_mix_control_info * data)
{
return B_OK;
}
static status_t
list_mix_connections(hda_codec* codec, multi_mix_connection_info * data)
{
data->actual_count = 0;
return B_OK;
}
static status_t
list_mix_channels(hda_codec* codec, multi_mix_channel_info *data)
{
return B_OK;
}
static status_t
get_buffers(hda_codec* codec, multi_buffer_list* data)
{
uint32 playback_sample_size = codec->playback_stream->sample_size;
uint32 record_sample_size = codec->record_stream->sample_size;
uint32 cidx, bidx;
status_t rc;
dprintf("%s: playback: %ld buffers, %ld channels, %ld samples\n", __func__,
data->request_playback_buffers, data->request_playback_channels, data->request_playback_buffer_size);
dprintf("%s: record: %ld buffers, %ld channels, %ld samples\n", __func__,
data->request_record_buffers, data->request_record_channels, data->request_record_buffer_size);
if (data->request_playback_buffers > STRMAXBUF ||
data->request_playback_buffers < STRMINBUF ||
data->request_record_buffers > STRMAXBUF ||
data->request_record_buffers < STRMINBUF) {
return B_BAD_VALUE;
}
data->flags = 0;
/* Copy the requested settings into the streams */
codec->playback_stream->num_buffers = data->request_playback_buffers;
codec->playback_stream->num_channels = data->request_playback_channels;
codec->playback_stream->buffer_length = data->request_playback_buffer_size;
if ((rc=hda_stream_setup_buffers(codec, codec->playback_stream, "Playback")) != B_OK) {
dprintf("%s: Error setting up playback buffers (%s)\n", __func__, strerror(rc));
return rc;
}
codec->record_stream->num_buffers = data->request_record_buffers;
codec->record_stream->num_channels = data->request_record_channels;
codec->record_stream->buffer_length = data->request_record_buffer_size;
if ((rc=hda_stream_setup_buffers(codec, codec->record_stream, "Recording")) != B_OK) {
dprintf("%s: Error setting up recording buffers (%s)\n", __func__, strerror(rc));
return rc;
}
/* Setup data structure for multi_audio API... */
data->return_playback_buffers = data->request_playback_buffers;
data->return_playback_channels = data->request_playback_channels;
data->return_playback_buffer_size = data->request_playback_buffer_size; /* frames */
for (bidx=0; bidx < data->return_playback_buffers; bidx++) {
for (cidx=0; cidx < data->return_playback_channels; cidx++) {
data->playback_buffers[bidx][cidx].base = codec->playback_stream->buffers[bidx] + (playback_sample_size * cidx);
data->playback_buffers[bidx][cidx].stride = playback_sample_size * data->return_playback_channels;
}
}
data->return_record_buffers = data->request_record_buffers;
data->return_record_channels = data->request_record_channels;
data->return_record_buffer_size = data->request_record_buffer_size; /* frames */
for (bidx=0; bidx < data->return_record_buffers; bidx++) {
for (cidx=0; cidx < data->return_record_channels; cidx++) {
data->record_buffers[bidx][cidx].base = codec->record_stream->buffers[bidx] + (record_sample_size * cidx);
data->record_buffers[bidx][cidx].stride = record_sample_size * data->return_record_channels;
}
}
return B_OK;
}
static status_t
buffer_exchange(hda_codec* codec, multi_buffer_info* data)
{
static int debug_buffers_exchanged = 0;
status_t rc;
if (!codec->playback_stream->running)
hda_stream_start(codec->ctrlr, codec->playback_stream);
// if (!codec->record_stream->running)
// hda_stream_start(codec->ctrlr, codec->record_stream);
/* do playback */
rc=acquire_sem(codec->playback_stream->buffer_ready_sem);
if (rc != B_OK) return rc;
data->played_real_time = codec->playback_stream->played_real_time;
data->played_frames_count = codec->playback_stream->played_frames_count;
/* do record */
data->record_buffer_cycle = 0;
data->recorded_frames_count = 0;
debug_buffers_exchanged++;
if (((debug_buffers_exchanged % 100) == 1) && (debug_buffers_exchanged < 1111)) {
dprintf("%s: %d buffers processed\n", __func__, debug_buffers_exchanged);
}
return B_OK;
}
static status_t
buffer_force_stop(hda_codec* codec)
{
hda_stream_stop(codec->ctrlr, codec->playback_stream);
hda_stream_stop(codec->ctrlr, codec->record_stream);
delete_sem(codec->playback_stream->buffer_ready_sem);
// delete_sem(codec->record_stream->buffer_ready_sem);
return B_OK;
}
status_t
multi_audio_control(void* cookie, uint32 op, void* arg, size_t len)
{
hda_codec* codec = (hda_codec*)cookie;
switch(op) {
case B_MULTI_GET_DESCRIPTION: return get_description(codec, arg);
case B_MULTI_GET_EVENT_INFO: return B_ERROR;
case B_MULTI_SET_EVENT_INFO: return B_ERROR;
case B_MULTI_GET_EVENT: return B_ERROR;
case B_MULTI_GET_ENABLED_CHANNELS: return get_enabled_channels(codec, arg);
case B_MULTI_SET_ENABLED_CHANNELS: return B_OK;
case B_MULTI_GET_GLOBAL_FORMAT: return get_global_format(codec, arg);
case B_MULTI_SET_GLOBAL_FORMAT: return set_global_format(codec, arg);
case B_MULTI_GET_CHANNEL_FORMATS: return B_ERROR;
case B_MULTI_SET_CHANNEL_FORMATS: return B_ERROR;
case B_MULTI_GET_MIX: return B_ERROR;
case B_MULTI_SET_MIX: return B_ERROR;
case B_MULTI_LIST_MIX_CHANNELS: return list_mix_channels(codec, arg);
case B_MULTI_LIST_MIX_CONTROLS: return list_mix_controls(codec, arg);
case B_MULTI_LIST_MIX_CONNECTIONS: return list_mix_connections(codec, arg);
case B_MULTI_GET_BUFFERS: return get_buffers(codec, arg);
case B_MULTI_SET_BUFFERS: return B_ERROR;
case B_MULTI_SET_START_TIME: return B_ERROR;
case B_MULTI_BUFFER_EXCHANGE: return buffer_exchange(codec, arg);
case B_MULTI_BUFFER_FORCE_STOP: return buffer_force_stop(codec);
}
return B_BAD_VALUE;
}

View File

@ -0,0 +1,82 @@
#include "driver.h"
static status_t
hda_open (const char *name, uint32 flags, void** cookie)
{
hda_controller* hc = NULL;
status_t rc = B_OK;
long i;
for(i=0; i < num_cards; i++) {
if (strcmp(cards[i].devfs_path, name) == 0) {
hc = &cards[i];
}
}
if (hc == NULL)
return ENODEV;
if (hc->opened)
return B_BUSY;
rc = hda_hw_init(hc);
if (rc != B_OK)
return rc;
hc->opened++;
*cookie = hc;
return B_OK;
}
static status_t
hda_read (void* cookie, off_t position, void *buf, size_t* num_bytes)
{
*num_bytes = 0; /* tell caller nothing was read */
return B_IO_ERROR;
}
static status_t
hda_write (void* cookie, off_t position, const void* buffer, size_t* num_bytes)
{
*num_bytes = 0; /* tell caller nothing was written */
return B_IO_ERROR;
}
static status_t
hda_control (void* cookie, uint32 op, void* arg, size_t len)
{
hda_controller* hc = (hda_controller*)cookie;
if (hc->codecs[0])
return multi_audio_control(hc->codecs[0], op, arg, len);
return B_BAD_VALUE;
}
static status_t
hda_close (void* cookie)
{
hda_controller* hc = (hda_controller*)cookie;
hda_hw_stop(hc);
--hc->opened;
return B_OK;
}
static status_t
hda_free (void* cookie)
{
hda_controller* hc = (hda_controller*)cookie;
hda_hw_uninit(hc);
return B_OK;
}
device_hooks driver_hooks = {
hda_open, /* -> open entry point */
hda_close, /* -> close entry point */
hda_free, /* -> free cookie */
hda_control, /* -> control entry point */
hda_read, /* -> read entry point */
hda_write /* -> write entry point */
};