diff options
author | Deanna Phillips <deanna@cvs.openbsd.org> | 2007-05-02 17:01:23 +0000 |
---|---|---|
committer | Deanna Phillips <deanna@cvs.openbsd.org> | 2007-05-02 17:01:23 +0000 |
commit | 8b7eb2d1e05de1e3232d0e89b6d7e16850c06b3a (patch) | |
tree | 41ea9c590fdbde20244a234f09c260ed48cef6cf /sys | |
parent | 8687332e2e6df8dc45b5dff09630faf38673b2b1 (diff) |
- Add support for handling unsolicited events (based on NetBSD).
- The STAC9200 codec was mistakenly referred to as STAC9220. Change
this to STAC9200 and add a link to the datasheet.
- Add a new target, MI_TARGET_PINCTRL, to azalia_generic_mixer_set()
that allows us to turn pins on and off.
- Add an unsolicited event handler for STAC9200 that will toggle the
headphone and speaker pins. This means the speaker will now mute and
unmute based on headphone presence.
- Spelling: PRESENSE -> PRESENCE
Tested by ajacoutot@, tedu@ and krw@, ok krw@.
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/pci/azalia.c | 122 | ||||
-rw-r--r-- | sys/dev/pci/azalia.h | 20 | ||||
-rw-r--r-- | sys/dev/pci/azalia_codec.c | 126 |
3 files changed, 242 insertions, 26 deletions
diff --git a/sys/dev/pci/azalia.c b/sys/dev/pci/azalia.c index 8db07fd6b32..c3c543cea9f 100644 --- a/sys/dev/pci/azalia.c +++ b/sys/dev/pci/azalia.c @@ -1,4 +1,4 @@ -/* $OpenBSD: azalia.c,v 1.24 2006/09/21 09:52:45 fgsch Exp $ */ +/* $OpenBSD: azalia.c,v 1.25 2007/05/02 17:01:22 deanna Exp $ */ /* $NetBSD: azalia.c,v 1.20 2006/05/07 08:31:44 kent Exp $ */ /*- @@ -195,6 +195,11 @@ typedef struct azalia_t { azalia_dma_t rirb_dma; int rirb_size; int rirb_rp; +#define UNSOLQ_SIZE 256 + rirb_entry_t *unsolq; + int unsolq_wp; + int unsolq_rp; + boolean_t unsolq_kick; boolean_t ok64; int nistreams, nostreams, nbstreams; @@ -222,9 +227,11 @@ int azalia_init_corb(azalia_t *); int azalia_delete_corb(azalia_t *); int azalia_init_rirb(azalia_t *); int azalia_delete_rirb(azalia_t *); -int azalia_set_command(const azalia_t *, nid_t, int, uint32_t, +int azalia_set_command(azalia_t *, nid_t, int, uint32_t, uint32_t); int azalia_get_response(azalia_t *, uint32_t *); +void azalia_rirb_kick_unsol_events(azalia_t *); +void azalia_rirb_intr(azalia_t *); int azalia_alloc_dmamem(azalia_t *, size_t, size_t, azalia_dma_t *); int azalia_free_dmamem(const azalia_t *, azalia_dma_t*); @@ -475,7 +482,7 @@ azalia_intr(void *v) azalia_t *az = v; int ret = 0; uint32_t intsts; - uint8_t rirbsts; + uint8_t rirbsts, rirbctl; intsts = AZ_READ_4(az, INTSTS); if (intsts == 0) @@ -488,10 +495,14 @@ azalia_intr(void *v) ret += azalia_stream_intr(&az->rstream, intsts); #endif + rirbctl = AZ_READ_1(az, RIRBCTL); + rirbsts = AZ_READ_1(az, RIRBSTS); + if (intsts & HDA_INTCTL_CIE) { - rirbsts = AZ_READ_1(az, RIRBSTS); - AZ_WRITE_1(az, RIRBSTS, rirbsts | - HDA_RIRBSTS_RIRBOIS | HDA_RIRBSTS_RINTFL); + if (rirbctl & HDA_RIRBCTL_RINTCTL) { + if (rirbsts & HDA_RIRBSTS_RINTFL) + azalia_rirb_intr(az); + } } return (1); @@ -547,6 +558,10 @@ azalia_attach(azalia_t *az) return ETIMEDOUT; } + /* enable unsolicited response */ + gctl = AZ_READ_4(az, GCTL); + AZ_WRITE_4(az, GCTL, gctl | HDA_GCTL_UNSOL); + /* 4.3 Codec discovery */ DELAY(1000); statests = AZ_READ_2(az, STATESTS); @@ -770,6 +785,20 @@ azalia_init_rirb(azalia_t *az) DPRINTF(("%s: RIRB allocation succeeded.\n", __func__)); + /* setup the unsolicited response queue */ + az->unsolq_rp = 0; + az->unsolq_wp = 0; + az->unsolq_kick = FALSE; + az->unsolq = malloc(sizeof(rirb_entry_t) * UNSOLQ_SIZE, + M_DEVBUF, M_NOWAIT); + if (az->unsolq == NULL) { + DPRINTF(("%s: can't allocate unsolicited response queue.\n", + XNAME(az))); + azalia_free_dmamem(az, &az->rirb_dma); + return ENOMEM; + } + bzero(az->unsolq, sizeof(rirb_entry_t) * UNSOLQ_SIZE); + /* reset the write pointer */ rirbwp = AZ_READ_2(az, RIRBWP); AZ_WRITE_2(az, RIRBWP, rirbwp | HDA_RIRBWP_RIRBWPRST); @@ -794,6 +823,10 @@ azalia_delete_rirb(azalia_t *az) int i; uint8_t rirbctl; + if (az->unsolq != NULL) { + free(az->unsolq, M_DEVBUF); + az->unsolq = NULL; + } if (az->rirb_dma.addr == NULL) return 0; /* stop the RIRB */ @@ -810,13 +843,14 @@ azalia_delete_rirb(azalia_t *az) } int -azalia_set_command(const azalia_t *az, int caddr, nid_t nid, uint32_t control, +azalia_set_command(azalia_t *az, int caddr, nid_t nid, uint32_t control, uint32_t param) { corb_entry_t *corb; int wp; uint32_t verb; uint16_t corbwp; + uint8_t rirbctl; #ifdef DIAGNOSTIC if ((AZ_READ_1(az, CORBCTL) & HDA_CORBCTL_CORBRUN) == 0) { @@ -831,6 +865,14 @@ azalia_set_command(const azalia_t *az, int caddr, nid_t nid, uint32_t control, if (++wp >= az->corb_size) wp = 0; corb[wp] = verb; + + /* disable RIRB interrupts */ + rirbctl = AZ_READ_1(az, RIRBCTL); + if (rirbctl & HDA_RIRBCTL_RINTCTL) { + AZ_WRITE_1(az, RIRBCTL, rirbctl & ~HDA_RIRBCTL_RINTCTL); + azalia_rirb_intr(az); + } + AZ_WRITE_2(az, CORBWP, (corbwp & ~HDA_CORBWP_CORBWP) | wp); #if 0 DPRINTF(("%s: caddr=%d nid=%d control=0x%x param=0x%x verb=0x%8.8x wp=%d\n", @@ -845,6 +887,7 @@ azalia_get_response(azalia_t *az, uint32_t *result) const rirb_entry_t *rirb; int i; uint16_t wp; + uint8_t rirbctl; #ifdef DIAGNOSTIC if ((AZ_READ_1(az, RIRBCTL) & HDA_RIRBCTL_RIRBDMAEN) == 0) { @@ -866,14 +909,17 @@ azalia_get_response(azalia_t *az, uint32_t *result) for (;;) { if (++az->rirb_rp >= az->rirb_size) az->rirb_rp = 0; - if (rirb[az->rirb_rp].resp_ex & RIRB_UNSOLICITED_RESPONSE) { - DPRINTF(("%s: unsolicited response\n", __func__)); + if (rirb[az->rirb_rp].resp_ex & RIRB_RESP_UNSOL) { + az->unsolq[az->unsolq_wp].resp = rirb[az->rirb_rp].resp; + az->unsolq[az->unsolq_wp++].resp_ex = rirb[az->rirb_rp].resp_ex; + az->unsolq_wp %= UNSOLQ_SIZE; } else break; } if (result != NULL) *result = rirb[az->rirb_rp].resp; + azalia_rirb_kick_unsol_events(az); #if 0 for (i = 0; i < 16 /*az->rirb_size*/; i++) { DPRINTF(("rirb[%d] 0x%8.8x:0x%8.8x ", i, rirb[i].resp, rirb[i].resp_ex)); @@ -881,9 +927,67 @@ azalia_get_response(azalia_t *az, uint32_t *result) DPRINTF(("\n")); } #endif + + /* re-enable RIRB interrupts */ + rirbctl = AZ_READ_1(az, RIRBCTL); + AZ_WRITE_1(az, RIRBCTL, rirbctl | HDA_RIRBCTL_RINTCTL); + return 0; } +void +azalia_rirb_kick_unsol_events(azalia_t *az) +{ + if (az->unsolq_kick) + return; + az->unsolq_kick = TRUE; + while (az->unsolq_rp != az->unsolq_wp) { + int i; + int tag; + codec_t *codec; + i = RIRB_RESP_CODEC(az->unsolq[az->unsolq_rp].resp_ex); + tag = RIRB_UNSOL_TAG(az->unsolq[az->unsolq_rp].resp); + codec = &az->codecs[i]; + DPRINTF(("%s: codec#=%d tag=%d\n", __func__, i, tag)); + az->unsolq_rp++; + az->unsolq_rp %= UNSOLQ_SIZE; + if (codec->unsol_event != NULL) + codec->unsol_event(codec, tag); + } + az->unsolq_kick = FALSE; +} + +void +azalia_rirb_intr(azalia_t *az) +{ + const rirb_entry_t *rirb; + uint16_t wp, rp; + uint8_t rirbsts; + + rirbsts = AZ_READ_1(az, RIRBSTS); + + wp = AZ_READ_2(az, RIRBWP) & HDA_RIRBWP_RIRBWP; + if (rp == wp) + return; /* interrupted but no data in RIRB */ + rirb = (rirb_entry_t*)az->rirb_dma.addr; + while (az->rirb_rp != wp) { + if (++az->rirb_rp >= az->rirb_size) + az->rirb_rp = 0; + if (rirb[az->rirb_rp].resp_ex & RIRB_RESP_UNSOL) { + az->unsolq[az->unsolq_wp].resp = rirb[az->rirb_rp].resp; + az->unsolq[az->unsolq_wp++].resp_ex = rirb[az->rirb_rp].resp_ex; + az->unsolq_wp %= UNSOLQ_SIZE; + } else { + break; + } + } + + azalia_rirb_kick_unsol_events(az); + + AZ_WRITE_1(az, RIRBSTS, + rirbsts | HDA_RIRBSTS_RIRBOIS | HDA_RIRBSTS_RINTFL); +} + int azalia_alloc_dmamem(azalia_t *az, size_t size, size_t align, azalia_dma_t *d) { diff --git a/sys/dev/pci/azalia.h b/sys/dev/pci/azalia.h index c4e445dbbc7..edc6d8f3a8f 100644 --- a/sys/dev/pci/azalia.h +++ b/sys/dev/pci/azalia.h @@ -1,4 +1,4 @@ -/* $OpenBSD: azalia.h,v 1.9 2006/06/23 23:26:20 brad Exp $ */ +/* $OpenBSD: azalia.h,v 1.10 2007/05/02 17:01:22 deanna Exp $ */ /* $NetBSD: azalia.h,v 1.6 2006/01/16 14:15:26 kent Exp $ */ /*- @@ -60,7 +60,7 @@ #define HDA_OUTPAY 0x004 /* 2 */ #define HDA_INPAY 0x006 /* 2 */ #define HDA_GCTL 0x008 /* 4 */ -#define HDA_GCTL_UNSOL 0x00000080 +#define HDA_GCTL_UNSOL 0x00000100 #define HDA_GCTL_FCNTRL 0x00000002 #define HDA_GCTL_CRST 0x00000001 #define HDA_WAKEEN 0x00c /* 2 */ @@ -342,7 +342,7 @@ #define CORB_UNSOL_ENABLE 0x80 #define CORB_UNSOL_TAG(x) (x & 0x3f) #define CORB_GET_PIN_SENSE 0xf09 -#define CORB_PS_PRESENSE 0x80000000 +#define CORB_PS_PRESENCE 0x80000000 #define CORB_PS_IMPEDANCE(x) (x & 0x7fffffff) #define CORB_EXECUTE_PIN_SENSE 0x709 #define CORB_PS_RIGHT 0x1 @@ -449,7 +449,9 @@ typedef uint32_t corb_entry_t; typedef struct { uint32_t resp; uint32_t resp_ex; -#define RIRB_UNSOLICITED_RESPONSE (1 << 4) +#define RIRB_UNSOL_TAG(resp) ((resp) >> 26) +#define RIRB_RESP_UNSOL (1 << 4) +#define RIRB_RESP_CODEC(ex) ((ex) & 0xf) } __packed rirb_entry_t; @@ -505,9 +507,10 @@ typedef struct { #define MI_TARGET_CONNLIST 0x101 #define MI_TARGET_PINDIR 0x102 /* for bidirectional pin */ #define MI_TARGET_PINBOOST 0x103 /* for headphone pin */ -#define MI_TARGET_DAC 0x104 -#define MI_TARGET_ADC 0x105 -#define MI_TARGET_VOLUME 0x106 +#define MI_TARGET_PINCTRL 0x104 /* for enabling/disabling pins */ +#define MI_TARGET_DAC 0x105 +#define MI_TARGET_ADC 0x106 +#define MI_TARGET_VOLUME 0x107 } mixer_item_t; #define VALID_WIDGET_NID(nid, codec) (nid == (codec)->audiofunc || \ @@ -532,6 +535,7 @@ typedef struct codec_t { int (*mixer_delete)(struct codec_t *); int (*set_port)(struct codec_t *, mixer_ctrl_t *); int (*get_port)(struct codec_t *, mixer_ctrl_t *); + int (*unsol_event)(struct codec_t *, int); struct azalia_t *az; uint32_t vid; /* codec vendor/device ID */ @@ -556,6 +560,8 @@ typedef struct codec_t { struct audio_format* formats; int nformats; struct audio_encoding_set *encodings; + + uint32_t *extra; } codec_t; diff --git a/sys/dev/pci/azalia_codec.c b/sys/dev/pci/azalia_codec.c index cb47be57c36..c2fb80fc434 100644 --- a/sys/dev/pci/azalia_codec.c +++ b/sys/dev/pci/azalia_codec.c @@ -1,4 +1,4 @@ -/* $OpenBSD: azalia_codec.c,v 1.18 2006/07/23 20:23:51 brad Exp $ */ +/* $OpenBSD: azalia_codec.c,v 1.19 2007/05/02 17:01:22 deanna Exp $ */ /* $NetBSD: azalia_codec.c,v 1.8 2006/05/10 11:17:27 kent Exp $ */ /*- @@ -100,8 +100,8 @@ int azalia_ad1981hd_mixer_init(codec_t *); int azalia_cmi9880_init_dacgroup(codec_t *); int azalia_cmi9880_mixer_init(codec_t *); int azalia_stac9221_init_dacgroup(codec_t *); -int azalia_stac9220_mixer_init(codec_t *); - +int azalia_stac9200_mixer_init(codec_t *); +int azalia_stac9200_unsol_event(codec_t *, int); int azalia_codec_init_vtbl(codec_t *this) @@ -158,8 +158,13 @@ azalia_codec_init_vtbl(codec_t *this) this->init_dacgroup = azalia_stac9221_init_dacgroup; break; case 0x83847690: - this->name = "Sigmatel STAC9220"; - this->mixer_init = azalia_stac9220_mixer_init; + /* http://www.idt.com/products/getDoc.cfm?docID=17812077 */ + this->name = "Sigmatel STAC9200"; + this->mixer_init = azalia_stac9200_mixer_init; + this->unsol_event = azalia_stac9200_unsol_event; + break; + case 0x83847691: + this->name = "Sigmatel STAC9200D"; break; } return 0; @@ -1189,6 +1194,28 @@ azalia_generic_mixer_set(codec_t *this, nid_t nid, int target, const mixer_ctrl_ return err; } + /* + * pin control: enable/disable. for bidirectional pins, set + * direction with MI_TARGET_PINDIR after enabling. + */ + + else if (target == MI_TARGET_PINCTRL) { + if (mc->un.ord >= 2) + return EINVAL; + err = this->comresp(this, nid, + CORB_GET_PIN_WIDGET_CONTROL, 0, &result); + if (err) + return err; + if (mc->un.ord == 0) /* disable */ + result &= ~(CORB_PWC_OUTPUT | CORB_PWC_INPUT); + else + result |= CORB_PWC_OUTPUT | CORB_PWC_INPUT; + err = this->comresp(this, nid, + CORB_SET_PIN_WIDGET_CONTROL, result, &result); + if (err) + return err; + } + /* pin headphone-boost */ else if (target == MI_TARGET_PINBOOST) { if (mc->un.ord >= 2) @@ -2087,10 +2114,10 @@ azalia_stac9221_init_dacgroup(codec_t *this) } /* ---------------------------------------------------------------- - * Sigmatel STAC9220 + * Sigmatel STAC9200 * ---------------------------------------------------------------- */ -static const mixer_item_t stac9220_mixer_items[] = { +static const mixer_item_t stac9200_mixer_items[] = { {{AZ_CLASS_INPUT, {AudioCinputs}, AUDIO_MIXER_CLASS, AZ_CLASS_INPUT, 0, 0}, 0}, {{AZ_CLASS_OUTPUT, {AudioCoutputs}, AUDIO_MIXER_CLASS, AZ_CLASS_OUTPUT, 0, 0}, 0}, {{AZ_CLASS_RECORD, {AudioCrecord}, AUDIO_MIXER_CLASS, AZ_CLASS_RECORD, 0, 0}, 0}, @@ -2133,11 +2160,13 @@ static const mixer_item_t stac9220_mixer_items[] = { }; int -azalia_stac9220_mixer_init(codec_t *this) +azalia_stac9200_mixer_init(codec_t *this) { mixer_ctrl_t mc; + int err; + uint32_t value; - this->nmixers = sizeof(stac9220_mixer_items) / sizeof(mixer_item_t); + this->nmixers = sizeof(stac9200_mixer_items) / sizeof(mixer_item_t); this->mixers = malloc(sizeof(mixer_item_t) * this->nmixers, M_DEVBUF, M_NOWAIT); if (this->mixers == NULL) { @@ -2145,7 +2174,7 @@ azalia_stac9220_mixer_init(codec_t *this) return ENOMEM; } bzero(this->mixers, sizeof(mixer_item_t) * this->maxmixers); - memcpy(this->mixers, stac9220_mixer_items, + memcpy(this->mixers, stac9200_mixer_items, sizeof(mixer_item_t) * this->nmixers); azalia_generic_mixer_fix_indexes(this); azalia_generic_mixer_default(this); @@ -2164,5 +2193,82 @@ azalia_stac9220_mixer_init(codec_t *this) mc.un.value.level[1] = mc.un.value.level[0]; azalia_generic_mixer_set(this, 0x0c, MI_TARGET_OUTAMP, &mc); +#define STAC9200_EVENT_HP 0 +#define STAC9200_NID_HP 0x0d +#define STAC9200_NID_SPEAKER 0x0e + + /* register hp unsolicited event */ + this->comresp(this, STAC9200_NID_HP, + CORB_SET_UNSOLICITED_RESPONSE, + CORB_UNSOL_ENABLE | STAC9200_EVENT_HP, NULL); + + /* make initial hp vs speaker choice */ + + err = this->comresp(this, STAC9200_NID_HP, + CORB_GET_PIN_SENSE, 0, &value); + if (err) + return err; + if (value & CORB_PS_PRESENCE) { + mc.un.ord = 0; + azalia_generic_mixer_set(this, STAC9200_NID_SPEAKER, + MI_TARGET_PINCTRL, &mc); + mc.un.ord = 1; + azalia_generic_mixer_set(this, STAC9200_NID_HP, + MI_TARGET_PINCTRL, &mc); + azalia_generic_mixer_set(this, STAC9200_NID_HP, + MI_TARGET_PINDIR, &mc); + } else { + mc.un.ord = 0; + azalia_generic_mixer_set(this, STAC9200_NID_HP, + MI_TARGET_PINCTRL, &mc); + mc.un.ord = 1; + azalia_generic_mixer_set(this, STAC9200_NID_SPEAKER, + MI_TARGET_PINCTRL, &mc); + azalia_generic_mixer_set(this, STAC9200_NID_SPEAKER, + MI_TARGET_PINDIR, &mc); + } + return 0; +} +int +azalia_stac9200_unsol_event(codec_t *this, int tag) +{ + int err; + uint32_t value; + mixer_ctrl_t mc; + + switch (tag) { + case STAC9200_EVENT_HP: + err = this->comresp(this, STAC9200_NID_HP, + CORB_GET_PIN_SENSE, 0, &value); + if (err) + return err; + if (value & CORB_PS_PRESENCE) { + DPRINTF(("%s: headphone inserted\n", __func__)); + mc.un.ord = 0; /* disable */ + azalia_generic_mixer_set(this, + STAC9200_NID_SPEAKER, + MI_TARGET_PINCTRL, &mc); + mc.un.ord = 1; /* enable and direction output */ + azalia_generic_mixer_set(this, STAC9200_NID_HP, + MI_TARGET_PINCTRL, &mc); + azalia_generic_mixer_set(this, STAC9200_NID_HP, + MI_TARGET_PINDIR, &mc); + } else { + DPRINTF(("%s: headphone pulled\n", __func__)); + mc.un.ord = 0; + azalia_generic_mixer_set(this, STAC9200_NID_HP, + MI_TARGET_PINCTRL, &mc); + mc.un.ord = 1; + azalia_generic_mixer_set(this, + STAC9200_NID_SPEAKER, + MI_TARGET_PINCTRL, &mc); + azalia_generic_mixer_set(this, + STAC9200_NID_SPEAKER, + MI_TARGET_PINDIR, &mc); + } + break; + default: + DPRINTF(("%s: unknown tag: %d\n", __func__, tag)); + } return 0; } |