diff --git a/src/add-ons/kernel/drivers/audio/Jamfile b/src/add-ons/kernel/drivers/audio/Jamfile index 1f4ac62a2e..d1d28f3f1b 100644 --- a/src/add-ons/kernel/drivers/audio/Jamfile +++ b/src/add-ons/kernel/drivers/audio/Jamfile @@ -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 sis7018 ; SubInclude HAIKU_TOP src add-ons kernel drivers audio usb_audio ; +SubInclude HAIKU_TOP src add-ons kernel drivers audio hda ; + diff --git a/src/add-ons/kernel/drivers/audio/hda/Jamfile b/src/add-ons/kernel/drivers/audio/hda/Jamfile new file mode 100644 index 0000000000..e735689be8 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/Jamfile @@ -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 ; diff --git a/src/add-ons/kernel/drivers/audio/hda/driver.c b/src/add-ons/kernel/drivers/audio/hda/driver.c new file mode 100644 index 0000000000..dc9af63d0a --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/driver.c @@ -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; +} diff --git a/src/add-ons/kernel/drivers/audio/hda/driver.h b/src/add-ons/kernel/drivers/audio/hda/driver.h new file mode 100644 index 0000000000..49d9662f22 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/driver.h @@ -0,0 +1,163 @@ +#ifndef _HDA_H_ +#define _HDA_H_ + +#include +#include +#include + +#include +#include + +#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_ */ diff --git a/src/add-ons/kernel/drivers/audio/hda/hda_codec.c b/src/add-ons/kernel/drivers/audio/hda/hda_codec.c new file mode 100644 index 0000000000..b3cd17239b --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/hda_codec.c @@ -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; +} diff --git a/src/add-ons/kernel/drivers/audio/hda/hda_codec_defs.h b/src/add-ons/kernel/drivers/audio/hda/hda_codec_defs.h new file mode 100644 index 0000000000..f4ab1ae992 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/hda_codec_defs.h @@ -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 */ diff --git a/src/add-ons/kernel/drivers/audio/hda/hda_controller.c b/src/add-ons/kernel/drivers/audio/hda/hda_controller.c new file mode 100644 index 0000000000..55dd146c57 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/hda_controller.c @@ -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; + } + } +} diff --git a/src/add-ons/kernel/drivers/audio/hda/hda_controller_defs.h b/src/add-ons/kernel/drivers/audio/hda/hda_controller_defs.h new file mode 100644 index 0000000000..38d963eaa2 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/hda_controller_defs.h @@ -0,0 +1,128 @@ +#ifndef HDAC_REGS_H +#define HDAC_REGS_H + +#include + +/* 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 */ diff --git a/src/add-ons/kernel/drivers/audio/hda/hda_multi_audio.c b/src/add-ons/kernel/drivers/audio/hda/hda_multi_audio.c new file mode 100644 index 0000000000..746aba1717 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/hda_multi_audio.c @@ -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; +} diff --git a/src/add-ons/kernel/drivers/audio/hda/hooks.c b/src/add-ons/kernel/drivers/audio/hda/hooks.c new file mode 100644 index 0000000000..ac79788689 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/hda/hooks.c @@ -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 */ +};