summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorDeanna Phillips <deanna@cvs.openbsd.org>2007-05-02 17:01:23 +0000
committerDeanna Phillips <deanna@cvs.openbsd.org>2007-05-02 17:01:23 +0000
commit8b7eb2d1e05de1e3232d0e89b6d7e16850c06b3a (patch)
tree41ea9c590fdbde20244a234f09c260ed48cef6cf /sys
parent8687332e2e6df8dc45b5dff09630faf38673b2b1 (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.c122
-rw-r--r--sys/dev/pci/azalia.h20
-rw-r--r--sys/dev/pci/azalia_codec.c126
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;
}