tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Realtek ALC268 support in azalia_codec



I attach a first attempt at supporting the ALC268.

I am not sure if my computer's audio configuration is
significantly different to others, in which case the
patch may not be appropriate.

Some comments

  1) By default (on my computer) the earphones use the second DAC,
     and the speakers use the first DAC.

     This means that if earphones are inserted, an automatic switch
     to the second dac would be required. This does not seem to be
     well supported or perhaps should not be done. I am unsure whether
     this should be possible, and decided against it.

  2) I have chosen to set the earphones and speakers to use the same
     DAC and consequently the same volume control, and then emulated
     separate volume controls. If different DACS were used for headphones
     and speakers this would not be necessary.

--
Kind regards,

Yorick Hardy

--- sys/dev/pci/azalia_codec.c.orig     2008-09-10 19:40:52.000000000 +0200
+++ sys/dev/pci/azalia_codec.c  2008-11-10 11:10:28.000000000 +0200
@@ -117,6 +117,11 @@
 static int     alc260_get_port(codec_t *, mixer_ctrl_t *);
 static int     alc260_unsol_event(codec_t *, int);
 static int     alc262_init_widget(const codec_t *, widget_t *, nid_t);
+static int     alc268_init_dacgroup(codec_t *);
+static int     alc268_mixer_init(codec_t *);
+static int     alc268_set_port(codec_t *, mixer_ctrl_t *);
+static int     alc268_get_port(codec_t *, mixer_ctrl_t *);
+static int     alc268_unsol_event(codec_t *, int);
 static int     alc662_init_dacgroup(codec_t *);
 static int     alc861_init_dacgroup(codec_t *);
 static int     alc861vdgr_init_dacgroup(codec_t *);
@@ -198,6 +203,12 @@
                break;
        case 0x10ec0268:
                this->name = "Realtek ALC268";
+               this->mixer_init = alc268_mixer_init;
+               this->init_dacgroup = alc268_init_dacgroup;
+               this->get_port = alc268_get_port;
+               this->set_port = alc268_set_port;
+               this->unsol_event = alc268_unsol_event;
+               extra_size = 1;
                break;
        case 0x10ec0662:
                this->name = "Realtek ALC662-GR";
@@ -2374,6 +2385,321 @@
 }
 
 /* ----------------------------------------------------------------
+ * Realtek ALC268
+ * ---------------------------------------------------------------- */
+
+#define ALC268_EVENT_HP                        0
+#define GET_ALC268_HP_VOLL(x)          ((*((x)->extra) & 0x000000ff) >> 0)
+#define GET_ALC268_HP_VOLR(x)          ((*((x)->extra) & 0x0000ff00) >> 8)
+#define GET_ALC268_SP_VOLL(x)          ((*((x)->extra) & 0x00ff0000) >> 16)
+#define GET_ALC268_SP_VOLR(x)          ((*((x)->extra) & 0xff000000) >> 24)
+#define SET_ALC268_HP_VOLL(x, v)       \
+       *((x)->extra) = (*((x)->extra) & 0xffffff00) | (((v) & 0xff) << 0)
+#define SET_ALC268_HP_VOLR(x, v)       \
+       *((x)->extra) = (*((x)->extra) & 0xffff00ff) | (((v) & 0xff) << 8)
+#define SET_ALC268_SP_VOLL(x, v)       \
+       *((x)->extra) = (*((x)->extra) & 0xff00ffff) | (((v) & 0xff) << 16)
+#define SET_ALC268_SP_VOLR(x, v)       \
+       *((x)->extra) = (*((x)->extra) & 0x00ffffff) | (((v) & 0xff) << 24)
+
+static const mixer_item_t alc268_mixer_items[] = {
+       AZ_MIXER_CLASSES,
+       {{0, {AudioNmaster, 0}, AUDIO_MIXER_VALUE, AZ_CLASS_OUTPUT, 0, 0,
+         .un.v={{"", 0}, 1, MIXER_DELTA(64)}}, 0x02, MI_TARGET_OUTAMP},
+       {{0, {AudioNmaster".mute", 0}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+         0, 0, ENUM_OFFON}, 0x14, MI_TARGET_OUTAMP},
+       {{0, {AudioNspeaker, 0}, AUDIO_MIXER_VALUE, AZ_CLASS_OUTPUT, 0, 0,
+         .un.v={{"", 0}, 1, MIXER_DELTA(64)}}, 0x02, MI_TARGET_OUTAMP},
+       {{0, {AudioNspeaker".mute", 0}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+         0, 0, ENUM_OFFON}, 0x14, MI_TARGET_OUTAMP},
+       {{0, {AudioNheadphone, 0}, AUDIO_MIXER_VALUE, AZ_CLASS_OUTPUT, 0, 0,
+         .un.v={{"", 0}, 1, MIXER_DELTA(64)}}, 0x02, MI_TARGET_OUTAMP},
+       {{0, {AudioNheadphone".mute", 0}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+         0, 0, ENUM_OFFON}, 0x15, MI_TARGET_OUTAMP},
+       {{0, {"mono.mute", 0}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT, 0, 0,
+         ENUM_OFFON}, 0x16, MI_TARGET_OUTAMP},
+       {{0, {AudioNline, 0}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, 0, 0,
+         .un.v={{"", 0}, 1, MIXER_DELTA(2)}}, 0x1a, MI_TARGET_INAMP(0)},
+       {{0, {AudioNline".mute", 0}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, 0, 0,
+         ENUM_OFFON}, 0x1a, MI_TARGET_OUTAMP},
+       {{0, {AudioNmicrophone"1", 0}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, 0, 0,
+         .un.v={{"", 0}, 1, MIXER_DELTA(2)}}, 0x18, MI_TARGET_INAMP(0)},
+       {{0, {AudioNmicrophone"1.mute", 0}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+         0, 0, ENUM_OFFON}, 0x18, MI_TARGET_OUTAMP},
+       {{0, {AudioNmicrophone"2", 0}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT, 0, 0,
+         .un.v={{"", 0}, 1, MIXER_DELTA(2)}}, 0x19, MI_TARGET_INAMP(0)},
+       {{0, {"mux1", 0}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0,
+         .un.v={{"", 0}, 1, MIXER_DELTA(31)}}, 0x24, MI_TARGET_OUTAMP},
+       {{0, {"mux1.mute", 0}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+         ENUM_OFFON}, 0x24, MI_TARGET_OUTAMP},
+       {{0, {"mux1."AudioNsource, 0}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+         .un.e={7, {{{AudioNmicrophone"1", 0}, 0},
+                    {{AudioNmicrophone"2", 0}, 1},
+                    {{AudioNline, 0}, 2},
+                    {{AudioNcd, 0}, 3},
+                    {{AudioNspeaker, 0}, 4},
+                    {{AudioNheadphone, 0}, 5},
+                    {{"digital-"AudioNmicrophone, 0}, 6}}}},
+        0x24, MI_TARGET_CONNLIST},
+       {{0, {"mux2", 0}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0,
+         .un.v={{"", 0}, 1, MIXER_DELTA(31)}}, 0x23, MI_TARGET_OUTAMP},
+       {{0, {"mux2.mute", 0}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+         ENUM_OFFON}, 0x23, MI_TARGET_OUTAMP},
+       {{0, {"mux2."AudioNsource, 0}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+         .un.e={7, {{{AudioNmicrophone"1", 0}, 0},
+                    {{AudioNmicrophone"2", 0}, 1},
+                    {{AudioNline, 0}, 2},
+                    {{AudioNcd, 0}, 3},
+                    {{AudioNspeaker, 0}, 4},
+                    {{AudioNheadphone, 0}, 5},
+                    {{"digital-"AudioNmicrophone, 0}, 6}}}},
+        0x23, MI_TARGET_CONNLIST},
+       {{0, {AudioNmode, 0}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+         .un.e={2, {{{AudioNmicrophone, 0}, 0}, {{AudioNline, 0}, 1}}}}, 0,
+         MI_TARGET_ADC},
+       {{0, {AudioNmode, 0}, AUDIO_MIXER_ENUM, AZ_CLASS_PLAYBACK, 0, 0,
+         .un.e={3, {{{AudioNspeaker, 0}, 0}, {{AudioNheadphone, 0}, 1},
+                   {{AzaliaNdigital, 0}, 2}}}}, 0,
+         MI_TARGET_DAC}
+};
+
+static int
+alc268_mixer_init(codec_t *this)
+{
+       const mixer_item_t *mi;
+       mixer_ctrl_t mc;
+       uint32_t value;
+
+       this->nmixers = __arraycount(alc268_mixer_items);
+       mi = alc268_mixer_items;
+
+       this->mixers = malloc(sizeof(mixer_item_t) * this->nmixers,
+           M_DEVBUF, M_NOWAIT);
+       if (this->mixers == NULL) {
+               aprint_error("%s: out of memory in %s\n", XNAME(this), 
__func__);
+               return ENOMEM;
+       }
+       memcpy(this->mixers, mi, sizeof(mixer_item_t) * this->nmixers);
+       generic_mixer_fix_indexes(this);
+       generic_mixer_default(this);
+
+       mc.dev = -1;            /* no need for generic_mixer_set() */
+       mc.type = AUDIO_MIXER_ENUM;
+       mc.un.ord = 1;          /* pindir: output */
+       generic_mixer_set(this, 0x14, MI_TARGET_PINDIR, &mc); /* lineout */
+       generic_mixer_set(this, 0x15, MI_TARGET_PINDIR, &mc); /* headphones */
+       generic_mixer_set(this, 0x15, MI_TARGET_EAPD, &mc);
+       generic_mixer_set(this, 0x0f, MI_TARGET_INAMP(0), &mc);
+       mc.un.ord = 0;          /* pindir: input */
+       generic_mixer_set(this, 0x18, MI_TARGET_PINDIR, &mc); /* mic1 */
+       generic_mixer_set(this, 0x19, MI_TARGET_PINDIR, &mc); /* mic2 */
+       generic_mixer_set(this, 0x1a, MI_TARGET_PINDIR, &mc); /* line1 */
+       mc.un.ord = 0;          /* mute: off */
+       generic_mixer_set(this, 0x14, MI_TARGET_OUTAMP, &mc);
+       generic_mixer_set(this, 0x15, MI_TARGET_OUTAMP, &mc);
+       generic_mixer_set(this, 0x18, MI_TARGET_INAMP(0), &mc);
+       generic_mixer_set(this, 0x19, MI_TARGET_INAMP(0), &mc);
+       generic_mixer_set(this, 0x1a, MI_TARGET_INAMP(0), &mc);
+
+       /* setup a unsolicited event for the headphones */
+       this->comresp(this, 0x15, CORB_SET_UNSOLICITED_RESPONSE,
+                           CORB_UNSOL_ENABLE | ALC268_EVENT_HP, NULL);
+       /* If the headphone presents, mute the internal speaker */
+       this->comresp(this, 0x15, CORB_GET_PIN_SENSE, 0, &value);
+       mc.un.ord = value & CORB_PS_PRESENCE ? 1 : 0;
+       generic_mixer_set(this, 0x0f, MI_TARGET_INAMP(0), &mc);
+       generic_mixer_set(this, 0x0f, MI_TARGET_INAMP(1), &mc);
+        mc.un.ord = 1 - mc.un.ord;
+       generic_mixer_set(this, 0x10, MI_TARGET_INAMP(1), &mc);
+       generic_mixer_set(this, 0x10, MI_TARGET_INAMP(2), &mc);
+
+       mc.type = AUDIO_MIXER_VALUE;
+       generic_mixer_get(this, 0x02, MI_TARGET_OUTAMP, &mc);
+       SET_ALC268_HP_VOLL(this, mc.un.value.level[0]);
+       SET_ALC268_HP_VOLR(this, mc.un.value.level[1]);
+       SET_ALC268_SP_VOLL(this, mc.un.value.level[0]);
+       SET_ALC268_SP_VOLR(this, mc.un.value.level[1]);
+
+       return 0;
+}
+
+static int
+alc268_init_dacgroup(codec_t *this)
+{
+       static const convgroupset_t dacs = {
+               -1, 3,
+               {{1, {0x02}},   /* analog 2ch */
+                {1, {0x03}},   /* analog 2ch */
+                {1, {0x06}}}}; /* digital */
+       static const convgroupset_t adcs = {
+               -1, 3,
+               {{1, {0x07}},   /* mic */
+                {1, {0x08}}}}; /* line */
+
+       this->dacs = dacs;
+       this->adcs = adcs;
+       return 0;
+}
+
+static int
+alc268_set_port(codec_t *this, mixer_ctrl_t *mc)
+{
+       int i, err, nid;
+       const mixer_item_t *m;
+       mixer_ctrl_t hp;
+
+       if (mc->dev >= this->nmixers)
+               return ENXIO;
+       m = &this->mixers[mc->dev];
+       if (mc->type != m->devinfo.type)
+               return EINVAL;
+       if (mc->type == AUDIO_MIXER_CLASS)
+               return 0;
+
+       nid = m->nid;
+       if (mc->type == AUDIO_MIXER_ENUM &&
+            m->nid == 0x14 && m->target == MI_TARGET_OUTAMP &&
+           !strcmp(m->devinfo.label.name, AudioNmaster".mute")) {
+               hp.dev = -1;
+               hp.type = AUDIO_MIXER_ENUM;
+               generic_mixer_get(this, 0x10, MI_TARGET_INAMP(2), &hp);
+               if (hp.un.ord == 0) nid++; /* headphones active */
+       }
+       if (mc->type == AUDIO_MIXER_VALUE &&
+            m->nid == 0x02 && m->target == MI_TARGET_OUTAMP) {
+               for(i = 0; i<mc->un.value.num_channels; i++) {
+                       if (!generic_mixer_validate_value(this, m->nid,
+                                                        m->target,
+                                                        mc->un.value.level[i]))
+                               return EINVAL;
+               }
+               hp.dev = -1;
+               hp.type = AUDIO_MIXER_ENUM;
+               generic_mixer_get(this, 0x10, MI_TARGET_INAMP(2), &hp);
+               i = (mc->un.value.num_channels > 1) ? 1 : 0;
+               if (!strcmp(m->devinfo.label.name, AudioNheadphone) ||
+                   (!strcmp(m->devinfo.label.name, AudioNmaster) && hp.un.ord 
== 0)) {
+                       SET_ALC268_HP_VOLL(this, mc->un.value.level[0]);
+                       SET_ALC268_HP_VOLR(this, mc->un.value.level[i]);
+                       if (hp.un.ord == 1) return 0; /* not active */
+                       err = generic_mixer_set(this, m->nid, m->target, mc);
+                       mc->un.value.num_channels = 2;
+                       mc->un.value.level[0] = GET_ALC268_HP_VOLL(this);
+                       mc->un.value.level[1] = GET_ALC268_HP_VOLR(this);
+                       return err;
+               }
+               if (!strcmp(m->devinfo.label.name, AudioNspeaker) ||
+                   (!strcmp(m->devinfo.label.name, AudioNmaster) && hp.un.ord 
== 1)) {
+                       SET_ALC268_SP_VOLL(this, mc->un.value.level[0]);
+                       SET_ALC268_SP_VOLR(this, mc->un.value.level[i]);
+                       if (hp.un.ord == 0) return 0; /* not active */
+                       err = generic_mixer_set(this, m->nid, m->target, mc);
+                       mc->un.value.num_channels = 2;
+                       mc->un.value.level[0] = GET_ALC268_SP_VOLL(this);
+                       mc->un.value.level[1] = GET_ALC268_SP_VOLR(this);
+                       return err;
+               }
+       }
+
+       return generic_mixer_set(this, nid, m->target, mc);
+}
+
+static int
+alc268_get_port(codec_t *this, mixer_ctrl_t *mc)
+{
+       int nid;
+       const mixer_item_t *m;
+       mixer_ctrl_t hp;
+
+       if (mc->dev >= this->nmixers)
+               return ENXIO;
+       m = &this->mixers[mc->dev];
+       mc->type = m->devinfo.type;
+       if (mc->type == AUDIO_MIXER_CLASS)
+               return 0;
+
+       nid = m->nid;
+       if (mc->type == AUDIO_MIXER_ENUM &&
+            m->nid == 0x14 && m->target == MI_TARGET_OUTAMP &&
+           !strcmp(m->devinfo.label.name, AudioNmaster".mute")) {
+               hp.dev = -1;
+               hp.type = AUDIO_MIXER_ENUM;
+               generic_mixer_get(this, 0x10, MI_TARGET_INAMP(2), &hp);
+               if (hp.un.ord == 0) nid++; /* headphones active */
+       }
+       if (mc->type == AUDIO_MIXER_VALUE &&
+            m->nid == 0x02 && m->target == MI_TARGET_OUTAMP) {
+               if (!strcmp(m->devinfo.label.name, AudioNheadphone) || 
+                   (!strcmp(m->devinfo.label.name, AudioNmaster) && hp.un.ord 
== 0)) {
+                       mc->un.value.num_channels = 2;
+                       mc->un.value.level[0] = GET_ALC268_HP_VOLL(this);
+                       mc->un.value.level[1] = GET_ALC268_HP_VOLR(this);
+                       return 0;
+               }
+               if (!strcmp(m->devinfo.label.name, AudioNspeaker) ||
+                   (!strcmp(m->devinfo.label.name, AudioNmaster) && hp.un.ord 
== 1)) {
+                       mc->un.value.num_channels = 2;
+                       mc->un.value.level[0] = GET_ALC268_SP_VOLL(this);
+                       mc->un.value.level[1] = GET_ALC268_SP_VOLR(this);
+                       return 0;
+               }
+       }
+
+       return generic_mixer_get(this, nid, m->target, mc);
+}
+
+static int
+alc268_unsol_event(codec_t *this, int tag)
+{
+       int err;
+       uint32_t value;
+       mixer_ctrl_t mc;
+
+       switch (tag) {
+       case ALC268_EVENT_HP:
+               err = this->comresp(this, 0x15, CORB_GET_PIN_SENSE, 0, &value);
+               if (err)
+                       break;
+               mc.dev = -1;
+if (value & CORB_PS_PRESENCE) {
+                       DPRINTF(("%s: headphone has been inserted.\n", 
__func__));
+                       mc.type = AUDIO_MIXER_ENUM;
+                       mc.un.ord = 1;
+                       generic_mixer_set(this, 0x0f, MI_TARGET_INAMP(0), &mc);
+                       generic_mixer_set(this, 0x0f, MI_TARGET_INAMP(1), &mc);
+                       mc.type = AUDIO_MIXER_VALUE;
+                       mc.un.value.num_channels = 2;
+                       mc.un.value.level[0] = GET_ALC268_HP_VOLL(this);
+                       mc.un.value.level[1] = GET_ALC268_HP_VOLR(this);
+                       generic_mixer_set(this, 0x02, MI_TARGET_OUTAMP, &mc);
+                       mc.type = AUDIO_MIXER_ENUM;
+                       mc.un.ord = 0;
+                       generic_mixer_set(this, 0x10, MI_TARGET_INAMP(1), &mc);
+                       generic_mixer_set(this, 0x10, MI_TARGET_INAMP(2), &mc);
+               } else {
+                       DPRINTF(("%s: headphone has been pulled out.\n", 
__func__));
+                       mc.type = AUDIO_MIXER_ENUM;
+                       mc.un.ord = 1;
+                       generic_mixer_set(this, 0x10, MI_TARGET_INAMP(1), &mc);
+                       generic_mixer_set(this, 0x10, MI_TARGET_INAMP(2), &mc);
+                       mc.type = AUDIO_MIXER_VALUE;
+                       mc.un.value.num_channels = 2;
+                       mc.un.value.level[0] = GET_ALC268_SP_VOLL(this);
+                       mc.un.value.level[1] = GET_ALC268_SP_VOLR(this);
+                       generic_mixer_set(this, 0x02, MI_TARGET_OUTAMP, &mc);
+                       mc.type = AUDIO_MIXER_ENUM;
+                       mc.un.ord = 0;
+                       generic_mixer_set(this, 0x0f, MI_TARGET_INAMP(0), &mc);
+                       generic_mixer_set(this, 0x0f, MI_TARGET_INAMP(1), &mc);
+               }
+               break;
+       default:
+               printf("%s: unknown tag: %d\n", __func__, tag);
+       }
+       return 0;
+}
+
+/* ----------------------------------------------------------------
  * Realtek ALC861
  * ---------------------------------------------------------------- */
 


Home | Main Index | Thread Index | Old Index