NetBSD/sys/dev/ic/ac97.c

2008 lines
58 KiB
C

/* $NetBSD: ac97.c,v 1.76 2005/06/20 02:49:18 atatat Exp $ */
/* $OpenBSD: ac97.c,v 1.8 2000/07/19 09:01:35 csapuntz Exp $ */
/*
* Copyright (c) 1999, 2000 Constantine Sapuntzakis
*
* Author: Constantine Sapuntzakis <csapuntz@stanford.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE
*/
/* Partially inspired by FreeBSD's sys/dev/pcm/ac97.c. It came with
the following copyright */
/*
* Copyright (c) 1999 Cameron Grant <gandalf@vilnya.demon.co.uk>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ac97.c,v 1.76 2005/06/20 02:49:18 atatat Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/sysctl.h>
#include <sys/audioio.h>
#include <dev/audio_if.h>
#include <dev/ic/ac97reg.h>
#include <dev/ic/ac97var.h>
struct ac97_softc;
struct ac97_source_info;
static int ac97_mixer_get_port(struct ac97_codec_if *, mixer_ctrl_t *);
static int ac97_mixer_set_port(struct ac97_codec_if *, mixer_ctrl_t *);
static void ac97_detach(struct ac97_codec_if *);
static int ac97_query_devinfo(struct ac97_codec_if *, mixer_devinfo_t *);
static int ac97_get_portnum_by_name(struct ac97_codec_if *, const char *,
const char *, const char *);
static void ac97_restore_shadow(struct ac97_codec_if *);
static int ac97_set_rate(struct ac97_codec_if *, int, u_int *);
static void ac97_set_clock(struct ac97_codec_if *, unsigned int);
static uint16_t ac97_get_extcaps(struct ac97_codec_if *);
static int ac97_add_port(struct ac97_softc *,
const struct ac97_source_info *);
static int ac97_str_equal(const char *, const char *);
static int ac97_check_capability(struct ac97_softc *, int);
static void ac97_setup_source_info(struct ac97_softc *);
static void ac97_read(struct ac97_softc *, uint8_t, uint16_t *);
static void ac97_setup_defaults(struct ac97_softc *);
static int ac97_write(struct ac97_softc *, uint8_t, uint16_t);
static void ac97_ad198x_init(struct ac97_softc *);
static void ac97_alc650_init(struct ac97_softc *);
static void ac97_vt1616_init(struct ac97_softc *);
static int ac97_modem_offhook_set(struct ac97_softc *, int, int);
static int ac97_sysctl_verify(SYSCTLFN_ARGS);
#define Ac97Nphone "phone"
#define Ac97Nline1 "line1"
#define Ac97Nline2 "line2"
#define Ac97Nhandset "handset"
static const struct audio_mixer_enum
ac97_on_off = { 2, { { { AudioNoff } , 0 },
{ { AudioNon } , 1 } } };
static const struct audio_mixer_enum
ac97_mic_select = { 2, { { { AudioNmicrophone "0" }, 0 },
{ { AudioNmicrophone "1" }, 1 } } };
static const struct audio_mixer_enum
ac97_mono_select = { 2, { { { AudioNmixerout }, 0 },
{ { AudioNmicrophone }, 1 } } };
static const struct audio_mixer_enum
ac97_source = { 8, { { { AudioNmicrophone } , 0 },
{ { AudioNcd }, 1 },
{ { AudioNvideo }, 2 },
{ { AudioNaux }, 3 },
{ { AudioNline }, 4 },
{ { AudioNmixerout }, 5 },
{ { AudioNmixerout AudioNmono }, 6 },
{ { Ac97Nphone }, 7 } } };
/*
* Due to different values for each source that uses these structures,
* the ac97_query_devinfo function sets delta in mixer_devinfo_t using
* ac97_source_info.bits.
*/
static const struct audio_mixer_value
ac97_volume_stereo = { { AudioNvolume }, 2 };
static const struct audio_mixer_value
ac97_volume_mono = { { AudioNvolume }, 1 };
#define WRAP(a) &a, sizeof(a)
struct ac97_source_info {
const char *class;
const char *device;
const char *qualifier;
int type;
const void *info;
int info_size;
uint8_t reg;
uint16_t default_value;
uint8_t bits:3;
uint8_t ofs:4;
uint8_t mute:1;
uint8_t polarity:1; /* Does 0 == MAX or MIN */
enum {
CHECK_NONE = 0,
CHECK_SURROUND,
CHECK_CENTER,
CHECK_LFE,
CHECK_HEADPHONES,
CHECK_TONE,
CHECK_MIC,
CHECK_LOUDNESS,
CHECK_3D,
CHECK_LINE1,
CHECK_LINE2,
CHECK_HANDSET
} req_feature;
int prev;
int next;
int mixer_class;
};
static const struct ac97_source_info audio_source_info[] = {
{ AudioCinputs, NULL, NULL,
AUDIO_MIXER_CLASS, },
{ AudioCoutputs, NULL, NULL,
AUDIO_MIXER_CLASS, },
{ AudioCrecord, NULL, NULL,
AUDIO_MIXER_CLASS, },
/* Stereo master volume*/
{ AudioCoutputs, AudioNmaster, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_MASTER_VOLUME, 0x8000, 5, 0, 1,
},
/* Mono volume */
{ AudioCoutputs, AudioNmono, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_MASTER_VOLUME_MONO, 0x8000, 6, 0, 1,
},
{ AudioCoutputs, AudioNmono, AudioNsource,
AUDIO_MIXER_ENUM, WRAP(ac97_mono_select),
AC97_REG_GP, 0x0000, 1, 9, 0,
},
/* Headphone volume */
{ AudioCoutputs, AudioNheadphone, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_HEADPHONE_VOLUME, 0x8000, 6, 0, 1, 0, CHECK_HEADPHONES
},
/* Surround volume - logic hard coded for mute */
{ AudioCoutputs, AudioNsurround, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_SURR_MASTER, 0x8080, 5, 0, 1, 0, CHECK_SURROUND
},
/* Center volume*/
{ AudioCoutputs, AudioNcenter, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_CENTER_LFE_MASTER, 0x8080, 5, 0, 0, 0, CHECK_CENTER
},
{ AudioCoutputs, AudioNcenter, AudioNmute,
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
AC97_REG_CENTER_LFE_MASTER, 0x8080, 1, 7, 0, 0, CHECK_CENTER
},
/* LFE volume*/
{ AudioCoutputs, AudioNlfe, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_CENTER_LFE_MASTER, 0x8080, 5, 8, 0, 0, CHECK_LFE
},
{ AudioCoutputs, AudioNlfe, AudioNmute,
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
AC97_REG_CENTER_LFE_MASTER, 0x8080, 1, 15, 0, 0, CHECK_LFE
},
/* Tone - bass */
{ AudioCoutputs, AudioNbass, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_MASTER_TONE, 0x0f0f, 4, 8, 0, 0, CHECK_TONE
},
/* Tone - treble */
{ AudioCoutputs, AudioNtreble, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_MASTER_TONE, 0x0f0f, 4, 0, 0, 0, CHECK_TONE
},
/* PC Beep Volume */
{ AudioCinputs, AudioNspeaker, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_PCBEEP_VOLUME, 0x0000, 4, 1, 1,
},
/* Phone */
{ AudioCinputs, Ac97Nphone, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_PHONE_VOLUME, 0x8008, 5, 0, 1,
},
/* Mic Volume */
{ AudioCinputs, AudioNmicrophone, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_MIC_VOLUME, 0x8008, 5, 0, 1,
},
{ AudioCinputs, AudioNmicrophone, AudioNpreamp,
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
AC97_REG_MIC_VOLUME, 0x8008, 1, 6, 0,
},
{ AudioCinputs, AudioNmicrophone, AudioNsource,
AUDIO_MIXER_ENUM, WRAP(ac97_mic_select),
AC97_REG_GP, 0x0000, 1, 8, 0,
},
/* Line in Volume */
{ AudioCinputs, AudioNline, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_LINEIN_VOLUME, 0x8808, 5, 0, 1,
},
/* CD Volume */
{ AudioCinputs, AudioNcd, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_CD_VOLUME, 0x8808, 5, 0, 1,
},
/* Video Volume */
{ AudioCinputs, AudioNvideo, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_VIDEO_VOLUME, 0x8808, 5, 0, 1,
},
/* AUX volume */
{ AudioCinputs, AudioNaux, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_AUX_VOLUME, 0x8808, 5, 0, 1,
},
/* PCM out volume */
{ AudioCinputs, AudioNdac, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_PCMOUT_VOLUME, 0x8808, 5, 0, 1,
},
/* Record Source - some logic for this is hard coded - see below */
{ AudioCrecord, AudioNsource, NULL,
AUDIO_MIXER_ENUM, WRAP(ac97_source),
AC97_REG_RECORD_SELECT, 0x0000, 3, 0, 0,
},
/* Record Gain */
{ AudioCrecord, AudioNvolume, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_stereo),
AC97_REG_RECORD_GAIN, 0x8000, 4, 0, 1, 1,
},
/* Record Gain mic */
{ AudioCrecord, AudioNmicrophone, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_RECORD_GAIN_MIC, 0x8000, 4, 0, 1, 1, CHECK_MIC
},
/* */
{ AudioCoutputs, AudioNloudness, NULL,
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
AC97_REG_GP, 0x0000, 1, 12, 0, 0, CHECK_LOUDNESS
},
{ AudioCoutputs, AudioNspatial, NULL,
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
AC97_REG_GP, 0x0000, 1, 13, 0, 1, CHECK_3D
},
{ AudioCoutputs, AudioNspatial, "center",
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_3D_CONTROL, 0x0000, 4, 8, 0, 1, CHECK_3D
},
{ AudioCoutputs, AudioNspatial, "depth",
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_3D_CONTROL, 0x0000, 4, 0, 0, 1, CHECK_3D
},
/* Missing features: Simulated Stereo, POP, Loopback mode */
};
static const struct ac97_source_info modem_source_info[] = {
/* Classes */
{ AudioCinputs, NULL, NULL,
AUDIO_MIXER_CLASS, },
{ AudioCoutputs, NULL, NULL,
AUDIO_MIXER_CLASS, },
{ AudioCinputs, Ac97Nline1, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_LINE1_LEVEL, 0x8080, 4, 0, 0, 1, CHECK_LINE1
},
{ AudioCoutputs, Ac97Nline1, NULL,
AUDIO_MIXER_VALUE, WRAP(ac97_volume_mono),
AC97_REG_LINE1_LEVEL, 0x8080, 4, 8, 0, 1, CHECK_LINE1
},
{ AudioCinputs, Ac97Nline1, AudioNmute,
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
AC97_REG_LINE1_LEVEL, 0x8080, 1, 7, 0, 0, CHECK_LINE1
},
{ AudioCoutputs, Ac97Nline1, AudioNmute,
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
AC97_REG_LINE1_LEVEL, 0x8080, 1, 15, 0, 0, CHECK_LINE1
},
};
#define AUDIO_SOURCE_INFO_SIZE \
(sizeof(audio_source_info)/sizeof(audio_source_info[0]))
#define MODEM_SOURCE_INFO_SIZE \
(sizeof(modem_source_info)/sizeof(modem_source_info[0]))
#define SOURCE_INFO_SIZE(as) ((as)->type == AC97_CODEC_TYPE_MODEM ? \
MODEM_SOURCE_INFO_SIZE : AUDIO_SOURCE_INFO_SIZE)
/*
* Check out http://developer.intel.com/pc-supp/platform/ac97/ for
* information on AC-97
*/
struct ac97_softc {
/* ac97_codec_if must be at the first of ac97_softc. */
struct ac97_codec_if codec_if;
struct ac97_host_if *host_if;
#define AUDIO_MAX_SOURCES (2 * AUDIO_SOURCE_INFO_SIZE)
#define MODEM_MAX_SOURCES (2 * MODEM_SOURCE_INFO_SIZE)
struct ac97_source_info audio_source_info[AUDIO_MAX_SOURCES];
struct ac97_source_info modem_source_info[MODEM_MAX_SOURCES];
struct ac97_source_info *source_info;
int num_source_info;
enum ac97_host_flags host_flags;
unsigned int ac97_clock; /* usually 48000 */
#define AC97_STANDARD_CLOCK 48000U
uint16_t power_all;
uint16_t caps; /* -> AC97_REG_RESET */
uint16_t ext_id; /* -> AC97_REG_EXT_AUDIO_ID */
uint16_t ext_mid; /* -> AC97_REG_EXT_MODEM_ID */
uint16_t shadow_reg[128];
int type;
/* sysctl */
struct sysctllog *log;
int offhook_line1_mib;
int offhook_line2_mib;
int offhook_line1;
int offhook_line2;
};
static struct ac97_codec_if_vtbl ac97civ = {
ac97_mixer_get_port,
ac97_mixer_set_port,
ac97_query_devinfo,
ac97_get_portnum_by_name,
ac97_restore_shadow,
ac97_get_extcaps,
ac97_set_rate,
ac97_set_clock,
ac97_detach,
};
static const struct ac97_codecid {
uint32_t id;
uint32_t mask;
const char *name;
void (*init)(struct ac97_softc *);
} ac97codecid[] = {
/*
* Analog Devices SoundMAX
* http://www.soundmax.com/products/information/codecs.html
* http://www.analog.com/productSelection/pdf/AD1881A_0.pdf
* http://www.analog.com/productSelection/pdf/AD1885_0.pdf
* http://www.analog.com/UploadedFiles/Data_Sheets/206585810AD1980_0.pdf
* http://www.analog.com/productSelection/pdf/AD1981A_0.pdf
* http://www.analog.com/productSelection/pdf/AD1981B_0.pdf
* http://www.analog.com/UploadedFiles/Data_Sheets/180644528AD1985_0.pdf
*/
{ AC97_CODEC_ID('A', 'D', 'S', 3),
0xffffffff, "Analog Devices AD1819B" },
{ AC97_CODEC_ID('A', 'D', 'S', 0x40),
0xffffffff, "Analog Devices AD1881" },
{ AC97_CODEC_ID('A', 'D', 'S', 0x48),
0xffffffff, "Analog Devices AD1881A" },
{ AC97_CODEC_ID('A', 'D', 'S', 0x60),
0xffffffff, "Analog Devices AD1885" },
{ AC97_CODEC_ID('A', 'D', 'S', 0x61),
0xffffffff, "Analog Devices AD1886" },
{ AC97_CODEC_ID('A', 'D', 'S', 0x63),
0xffffffff, "Analog Devices AD1886A" },
{ AC97_CODEC_ID('A', 'D', 'S', 0x68),
0xffffffff, "Analog Devices AD1888", ac97_ad198x_init },
{ AC97_CODEC_ID('A', 'D', 'S', 0x70),
0xffffffff, "Analog Devices AD1980", ac97_ad198x_init },
{ AC97_CODEC_ID('A', 'D', 'S', 0x72),
0xffffffff, "Analog Devices AD1981A" },
{ AC97_CODEC_ID('A', 'D', 'S', 0x74),
0xffffffff, "Analog Devices AD1981B" },
{ AC97_CODEC_ID('A', 'D', 'S', 0x75),
0xffffffff, "Analog Devices AD1985", ac97_ad198x_init },
{ AC97_CODEC_ID('A', 'D', 'S', 0),
AC97_VENDOR_ID_MASK, "Analog Devices unknown" },
/*
* Datasheets:
* http://www.asahi-kasei.co.jp/akm/usa/product/ak4541/ek4541.pdf
* http://www.asahi-kasei.co.jp/akm/usa/product/ak4543/ek4543.pdf
* http://www.asahi-kasei.co.jp/akm/usa/product/ak4544a/ek4544a.pdf
* http://www.asahi-kasei.co.jp/akm/usa/product/ak4545/ek4545.pdf
*/
{ AC97_CODEC_ID('A', 'K', 'M', 0),
0xffffffff, "Asahi Kasei AK4540" },
{ AC97_CODEC_ID('A', 'K', 'M', 1),
0xffffffff, "Asahi Kasei AK4542" },
{ AC97_CODEC_ID('A', 'K', 'M', 2),
0xffffffff, "Asahi Kasei AK4541/AK4543" },
{ AC97_CODEC_ID('A', 'K', 'M', 5),
0xffffffff, "Asahi Kasei AK4544" },
{ AC97_CODEC_ID('A', 'K', 'M', 6),
0xffffffff, "Asahi Kasei AK4544A" },
{ AC97_CODEC_ID('A', 'K', 'M', 7),
0xffffffff, "Asahi Kasei AK4545" },
{ AC97_CODEC_ID('A', 'K', 'M', 0),
AC97_VENDOR_ID_MASK, "Asahi Kasei unknown" },
/*
* Realtek & Avance Logic
* http://www.realtek.com.tw/downloads/downloads1-3.aspx?lineid=5&famid=All&series=All&Spec=True
*
* ALC650 and ALC658 support VRA, but it supports only 8000, 11025,
* 12000, 16000, 22050, 24000, 32000, 44100, and 48000 Hz.
*/
{ AC97_CODEC_ID('A', 'L', 'C', 0x00),
0xfffffff0, "Realtek RL5306" },
{ AC97_CODEC_ID('A', 'L', 'C', 0x10),
0xfffffff0, "Realtek RL5382" },
{ AC97_CODEC_ID('A', 'L', 'C', 0x20),
0xfffffff0, "Realtek RL5383/RL5522/ALC100" },
{ AC97_CODEC_ID('A', 'L', 'G', 0x10),
0xffffffff, "Avance Logic ALC200/ALC201" },
{ AC97_CODEC_ID('A', 'L', 'G', 0x20),
0xfffffff0, "Avance Logic ALC650", ac97_alc650_init },
{ AC97_CODEC_ID('A', 'L', 'G', 0x30),
0xffffffff, "Avance Logic ALC101" },
{ AC97_CODEC_ID('A', 'L', 'G', 0x40),
0xffffffff, "Avance Logic ALC202" },
{ AC97_CODEC_ID('A', 'L', 'G', 0x50),
0xffffffff, "Avance Logic ALC250" },
{ AC97_CODEC_ID('A', 'L', 'G', 0x60),
0xfffffff0, "Avance Logic ALC655" },
{ AC97_CODEC_ID('A', 'L', 'G', 0x80),
0xfffffff0, "Avance Logic ALC658" },
{ AC97_CODEC_ID('A', 'L', 'G', 0x90),
0xfffffff0, "Avance Logic ALC850" },
{ AC97_CODEC_ID('A', 'L', 'C', 0),
AC97_VENDOR_ID_MASK, "Realtek unknown" },
{ AC97_CODEC_ID('A', 'L', 'G', 0),
AC97_VENDOR_ID_MASK, "Avance Logic unknown" },
/**
* C-Media Electronics Inc.
* http://www.cmedia.com.tw/doc/CMI9739%206CH%20Audio%20Codec%20SPEC_Ver12.pdf
*/
{ AC97_CODEC_ID('C', 'M', 'I', 0x61),
0xffffffff, "C-Media CMI9739" },
{ AC97_CODEC_ID('C', 'M', 'I', 0),
AC97_VENDOR_ID_MASK, "C-Media unknown" },
/* Cirrus Logic, Crystal series:
* 'C' 'R' 'Y' 0x0[0-7] - CS4297
* 0x1[0-7] - CS4297A
* 0x2[0-7] - CS4298
* 0x2[8-f] - CS4294
* 0x3[0-7] - CS4299
* 0x4[8-f] - CS4201
* 0x5[8-f] - CS4205
* 0x6[0-7] - CS4291
* 0x7[0-7] - CS4202
* Datasheets:
* http://www.cirrus.com/pubs/cs4297A-5.pdf?DocumentID=593
* http://www.cirrus.com/pubs/cs4294.pdf?DocumentID=32
* http://www.cirrus.com/pubs/cs4299-5.pdf?DocumentID=594
* http://www.cirrus.com/pubs/cs4201-2.pdf?DocumentID=492
* http://www.cirrus.com/pubs/cs4205-2.pdf?DocumentID=492
* http://www.cirrus.com/pubs/cs4202-1.pdf?DocumentID=852
*/
{ AC97_CODEC_ID('C', 'R', 'Y', 0x00),
0xfffffff8, "Crystal CS4297", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0x10),
0xfffffff8, "Crystal CS4297A", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0x20),
0xfffffff8, "Crystal CS4298", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0x28),
0xfffffff8, "Crystal CS4294", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0x30),
0xfffffff8, "Crystal CS4299", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0x48),
0xfffffff8, "Crystal CS4201", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0x58),
0xfffffff8, "Crystal CS4205", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0x60),
0xfffffff8, "Crystal CS4291", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0x70),
0xfffffff8, "Crystal CS4202", },
{ AC97_CODEC_ID('C', 'R', 'Y', 0),
AC97_VENDOR_ID_MASK, "Cirrus Logic unknown", },
{ 0x45838308, 0xffffffff, "ESS Technology ES1921", },
{ 0x45838300, AC97_VENDOR_ID_MASK, "ESS Technology unknown", },
{ AC97_CODEC_ID('H', 'R', 'S', 0),
0xffffffff, "Intersil HMP9701", },
{ AC97_CODEC_ID('H', 'R', 'S', 0),
AC97_VENDOR_ID_MASK, "Intersil unknown", },
/*
* IC Ensemble (VIA)
* http://www.viatech.com/en/datasheet/DS1616.pdf
*/
{ AC97_CODEC_ID('I', 'C', 'E', 0x01),
0xffffffff, "ICEnsemble ICE1230/VT1611", },
{ AC97_CODEC_ID('I', 'C', 'E', 0x11),
0xffffffff, "ICEnsemble ICE1232/VT1611A", },
{ AC97_CODEC_ID('I', 'C', 'E', 0x14),
0xffffffff, "ICEnsemble ICE1232A", },
{ AC97_CODEC_ID('I', 'C', 'E', 0x51),
0xffffffff, "VIA Technologies VT1616", ac97_vt1616_init },
{ AC97_CODEC_ID('I', 'C', 'E', 0x52),
0xffffffff, "VIA Technologies VT1616i", ac97_vt1616_init },
{ AC97_CODEC_ID('I', 'C', 'E', 0),
AC97_VENDOR_ID_MASK, "ICEnsemble/VIA unknown", },
{ AC97_CODEC_ID('N', 'S', 'C', 0),
0xffffffff, "National Semiconductor LM454[03568]", },
{ AC97_CODEC_ID('N', 'S', 'C', 49),
0xffffffff, "National Semiconductor LM4549", },
{ AC97_CODEC_ID('N', 'S', 'C', 0),
AC97_VENDOR_ID_MASK, "National Semiconductor unknown", },
{ AC97_CODEC_ID('P', 'S', 'C', 4),
0xffffffff, "Philips Semiconductor UCB1400", },
{ AC97_CODEC_ID('P', 'S', 'C', 0),
AC97_VENDOR_ID_MASK, "Philips Semiconductor unknown", },
{ AC97_CODEC_ID('S', 'I', 'L', 34),
0xffffffff, "Silicon Laboratory Si3036", },
{ AC97_CODEC_ID('S', 'I', 'L', 35),
0xffffffff, "Silicon Laboratory Si3038", },
{ AC97_CODEC_ID('S', 'I', 'L', 0),
AC97_VENDOR_ID_MASK, "Silicon Laboratory unknown", },
{ AC97_CODEC_ID('T', 'R', 'A', 2),
0xffffffff, "TriTech TR28022", },
{ AC97_CODEC_ID('T', 'R', 'A', 3),
0xffffffff, "TriTech TR28023", },
{ AC97_CODEC_ID('T', 'R', 'A', 6),
0xffffffff, "TriTech TR28026", },
{ AC97_CODEC_ID('T', 'R', 'A', 8),
0xffffffff, "TriTech TR28028", },
{ AC97_CODEC_ID('T', 'R', 'A', 35),
0xffffffff, "TriTech TR28602", },
{ AC97_CODEC_ID('T', 'R', 'A', 0),
AC97_VENDOR_ID_MASK, "TriTech unknown", },
{ AC97_CODEC_ID('T', 'X', 'N', 0x20),
0xffffffff, "Texas Instruments TLC320AD9xC", },
{ AC97_CODEC_ID('T', 'X', 'N', 0),
AC97_VENDOR_ID_MASK, "Texas Instruments unknown", },
/*
* VIA
* http://www.viatech.com/en/multimedia/audio.jsp
*/
{ AC97_CODEC_ID('V', 'I', 'A', 0x61),
0xffffffff, "VIA Technologies VT1612A", },
{ AC97_CODEC_ID('V', 'I', 'A', 0),
AC97_VENDOR_ID_MASK, "VIA Technologies unknown", },
{ AC97_CODEC_ID('W', 'E', 'C', 1),
0xffffffff, "Winbond W83971D", },
{ AC97_CODEC_ID('W', 'E', 'C', 0),
AC97_VENDOR_ID_MASK, "Winbond unknown", },
/*
* http://www.wolfsonmicro.com/product_list.asp?cid=64
* http://www.wolfsonmicro.com/download.asp/did.56/WM9701A.pdf - 00
* http://www.wolfsonmicro.com/download.asp/did.57/WM9703.pdf - 03
* http://www.wolfsonmicro.com/download.asp/did.58/WM9704M.pdf - 04
* http://www.wolfsonmicro.com/download.asp/did.59/WM9704Q.pdf - 04
* http://www.wolfsonmicro.com/download.asp/did.184/WM9705_Rev34.pdf - 05
* http://www.wolfsonmicro.com/download.asp/did.60/WM9707.pdf - 03
* http://www.wolfsonmicro.com/download.asp/did.136/WM9708.pdf - 03
* http://www.wolfsonmicro.com/download.asp/did.243/WM9710.pdf - 05
*/
{ AC97_CODEC_ID('W', 'M', 'L', 0),
0xffffffff, "Wolfson WM9701A", },
{ AC97_CODEC_ID('W', 'M', 'L', 3),
0xffffffff, "Wolfson WM9703/WM9707/WM9708", },
{ AC97_CODEC_ID('W', 'M', 'L', 4),
0xffffffff, "Wolfson WM9704", },
{ AC97_CODEC_ID('W', 'M', 'L', 5),
0xffffffff, "Wolfson WM9705/WM9710", },
{ AC97_CODEC_ID('W', 'M', 'L', 0),
AC97_VENDOR_ID_MASK, "Wolfson unknown", },
/*
* http://www.yamaha.co.jp/english/product/lsi/us/products/pcaudio.html
* Datasheets:
* http://www.yamaha.co.jp/english/product/lsi/us/products/pdf/4MF743A20.pdf
* http://www.yamaha.co.jp/english/product/lsi/us/products/pdf/4MF753A20.pdf
*/
{ AC97_CODEC_ID('Y', 'M', 'H', 0),
0xffffffff, "Yamaha YMF743-S", },
{ AC97_CODEC_ID('Y', 'M', 'H', 3),
0xffffffff, "Yamaha YMF753-S", },
{ AC97_CODEC_ID('Y', 'M', 'H', 0),
AC97_VENDOR_ID_MASK, "Yamaha unknown", },
/*
* http://www.sigmatel.com/products/technical_docs.htm
* and
* http://www.sigmatel.com/documents/c-major-brochure-9-0.pdf
*/
{ 0x83847600, 0xffffffff, "SigmaTel STAC9700", },
{ 0x83847604, 0xffffffff, "SigmaTel STAC9701/3/4/5", },
{ 0x83847605, 0xffffffff, "SigmaTel STAC9704", },
{ 0x83847608, 0xffffffff, "SigmaTel STAC9708", },
{ 0x83847609, 0xffffffff, "SigmaTel STAC9721/23", },
{ 0x83847644, 0xffffffff, "SigmaTel STAC9744/45", },
{ 0x83847650, 0xffffffff, "SigmaTel STAC9750/51", },
{ 0x83847656, 0xffffffff, "SigmaTel STAC9756/57", },
{ 0x83847658, 0xffffffff, "SigmaTel STAC9758/59", },
{ 0x83847666, 0xffffffff, "SigmaTel STAC9766/67", },
{ 0x83847684, 0xffffffff, "SigmaTel STAC9783/84", },
{ 0x83847600, AC97_VENDOR_ID_MASK, "SigmaTel unknown", },
/* Conexant AC'97 modems -- good luck finding datasheets! */
{ AC97_CODEC_ID('C', 'X', 'T', 33),
0xffffffff, "Conexant HSD11246", },
{ AC97_CODEC_ID('C', 'X', 'T', 34),
0xffffffff, "Conexant D480 MDC V.92 Modem", },
{ AC97_CODEC_ID('C', 'X', 'T', 0),
AC97_VENDOR_ID_MASK, "Conexant unknown", },
{ 0,
0, NULL, }
};
static const char * const ac97enhancement[] = {
"no 3D stereo",
"Analog Devices Phat Stereo",
"Creative",
"National Semi 3D",
"Yamaha Ymersion",
"BBE 3D",
"Crystal Semi 3D",
"Qsound QXpander",
"Spatializer 3D",
"SRS 3D",
"Platform Tech 3D",
"AKM 3D",
"Aureal",
"AZTECH 3D",
"Binaura 3D",
"ESS Technology",
"Harman International VMAx",
"Nvidea 3D",
"Philips Incredible Sound",
"Texas Instruments' 3D",
"VLSI Technology 3D",
"TriTech 3D",
"Realtek 3D",
"Samsung 3D",
"Wolfson Microelectronics 3D",
"Delta Integration 3D",
"SigmaTel 3D",
"KS Waves 3D",
"Rockwell 3D",
"Unknown 3D",
"Unknown 3D",
"Unknown 3D",
};
static const char * const ac97feature[] = {
"dedicated mic channel",
"reserved",
"tone",
"simulated stereo",
"headphone",
"bass boost",
"18 bit DAC",
"20 bit DAC",
"18 bit ADC",
"20 bit ADC"
};
/* #define AC97_DEBUG 10 */
/* #define AC97_IO_DEBUG */
#ifdef AUDIO_DEBUG
#define DPRINTF(x) if (ac97debug) printf x
#define DPRINTFN(n,x) if (ac97debug>(n)) printf x
#ifdef AC97_DEBUG
int ac97debug = AC97_DEBUG;
#else
int ac97debug = 0;
#endif
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif
#ifdef AC97_IO_DEBUG
static const char *ac97_register_names[0x80 / 2] = {
"RESET", "MASTER_VOLUME", "HEADPHONE_VOLUME", "MASTER_VOLUME_MONO",
"MASTER_TONE", "PCBEEP_VOLUME", "PHONE_VOLUME", "MIC_VOLUME",
"LINEIN_VOLUME", "CD_VOLUME", "VIDEO_VOLUME", "AUX_VOLUME",
"PCMOUT_VOLUME", "RECORD_SELECT", "RECORD_GATIN", "RECORD_GAIN_MIC",
"GP", "3D_CONTROL", "AUDIO_INT", "POWER",
"EXT_AUDIO_ID", "EXT_AUDIO_CTRL", "PCM_FRONT_DAC_RATE", "PCM_SURR_DAC_RATE",
"PCM_LFE_DAC_RATE", "PCM_LR_ADC_RATE", "PCM_MIC_ADC_RATE", "CENTER_LFE_MASTER",
"SURR_MASTER", "SPDIF_CTRL", "EXT_MODEM_ID", "EXT_MODEM_CTRL",
"LINE1_RATE", "LINE2_RATE", "HANDSET_RATE", "LINE1_LEVEL",
"LINE2_LEVEL", "HANDSET_LEVEL", "GPIO_PIN_CONFIG", "GPIO_PIN_POLARITY",
"GPIO_PIN_STICKY", "GPIO_PIN_WAKEUP", "GPIO_PIN_STATUS", "MISC_MODEM_CTRL",
"0x58", "VENDOR-5A", "VENDOR-5C", "VENDOR-5E",
"0x60", "0x62", "0x64", "0x66",
"0x68", "0x6a", "0x6c", "0x6e",
"VENDOR-70", "VENDOR-72", "VENDOR-74", "VENDOR-76",
"VENDOR-78", "VENDOR-7A", "VENDOR_ID1", "VENDOR_ID2"
};
#endif
static void
ac97_read(struct ac97_softc *as, uint8_t reg, uint16_t *val)
{
if (as->host_flags & AC97_HOST_DONT_READ &&
(reg != AC97_REG_VENDOR_ID1 && reg != AC97_REG_VENDOR_ID2 &&
reg != AC97_REG_RESET)) {
*val = as->shadow_reg[reg >> 1];
return;
}
if (as->host_if->read(as->host_if->arg, reg, val)) {
*val = as->shadow_reg[reg >> 1];
}
}
static int
ac97_write(struct ac97_softc *as, uint8_t reg, uint16_t val)
{
#ifndef AC97_IO_DEBUG
as->shadow_reg[reg >> 1] = val;
return as->host_if->write(as->host_if->arg, reg, val);
#else
int ret;
uint16_t actual;
as->shadow_reg[reg >> 1] = val;
ret = as->host_if->write(as->host_if->arg, reg, val);
as->host_if->read(as->host_if->arg, reg, &actual);
if (val != actual && reg < 0x80) {
printf("ac97_write: reg=%s, written=0x%04x, read=0x%04x\n",
ac97_register_names[reg / 2], val, actual);
}
return ret;
#endif
}
static void
ac97_setup_defaults(struct ac97_softc *as)
{
int idx;
const struct ac97_source_info *si;
memset(as->shadow_reg, 0, sizeof(as->shadow_reg));
for (idx = 0; idx < AUDIO_SOURCE_INFO_SIZE; idx++) {
si = &audio_source_info[idx];
ac97_write(as, si->reg, si->default_value);
}
for (idx = 0; idx < MODEM_SOURCE_INFO_SIZE; idx++) {
si = &modem_source_info[idx];
ac97_write(as, si->reg, si->default_value);
}
}
static void
ac97_restore_shadow(struct ac97_codec_if *self)
{
struct ac97_softc *as;
const struct ac97_source_info *si;
int idx;
uint16_t val;
as = (struct ac97_softc *) self;
if (as->type == AC97_CODEC_TYPE_AUDIO) {
/* make sure chip is fully operational */
for (idx = 50000; idx >= 0; idx--) {
ac97_read(as, AC97_REG_POWER, &val);
if ((val & as->power_all) == as->power_all)
break;
DELAY(10);
}
/*
* actually try changing a value!
* The default value of AC97_REG_MASTER_VOLUME is 0x8000.
*/
for (idx = 50000; idx >= 0; idx--) {
ac97_write(as, AC97_REG_MASTER_VOLUME, 0x1010);
ac97_read(as, AC97_REG_MASTER_VOLUME, &val);
if (val == 0x1010)
break;
DELAY(10);
}
}
for (idx = 0; idx < SOURCE_INFO_SIZE(as); idx++) {
if (as->type == AC97_CODEC_TYPE_MODEM)
si = &modem_source_info[idx];
else
si = &audio_source_info[idx];
/* don't "restore" to the reset reg! */
if (si->reg != AC97_REG_RESET)
ac97_write(as, si->reg, as->shadow_reg[si->reg >> 1]);
}
if (as->ext_id & (AC97_EXT_AUDIO_VRA | AC97_EXT_AUDIO_DRA
| AC97_EXT_AUDIO_SPDIF | AC97_EXT_AUDIO_VRM
| AC97_EXT_AUDIO_CDAC | AC97_EXT_AUDIO_SDAC
| AC97_EXT_AUDIO_LDAC)) {
ac97_write(as, AC97_REG_EXT_AUDIO_CTRL,
as->shadow_reg[AC97_REG_EXT_AUDIO_CTRL >> 1]);
}
if (as->ext_mid & (AC97_EXT_MODEM_LINE1 | AC97_EXT_MODEM_LINE2
| AC97_EXT_MODEM_HANDSET | AC97_EXT_MODEM_CID1
| AC97_EXT_MODEM_CID2 | AC97_EXT_MODEM_ID0
| AC97_EXT_MODEM_ID1)) {
ac97_write(as, AC97_REG_EXT_MODEM_CTRL,
as->shadow_reg[AC97_REG_EXT_MODEM_CTRL >> 1]);
}
}
static int
ac97_str_equal(const char *a, const char *b)
{
return (a == b) || (a && b && (!strcmp(a, b)));
}
static int
ac97_check_capability(struct ac97_softc *as, int check)
{
switch (check) {
case CHECK_NONE:
return 1;
case CHECK_SURROUND:
return as->ext_id & AC97_EXT_AUDIO_SDAC;
case CHECK_CENTER:
return as->ext_id & AC97_EXT_AUDIO_CDAC;
case CHECK_LFE:
return as->ext_id & AC97_EXT_AUDIO_LDAC;
case CHECK_HEADPHONES:
return as->caps & AC97_CAPS_HEADPHONES;
case CHECK_TONE:
return as->caps & AC97_CAPS_TONECTRL;
case CHECK_MIC:
return as->caps & AC97_CAPS_MICIN;
case CHECK_LOUDNESS:
return as->caps & AC97_CAPS_LOUDNESS;
case CHECK_3D:
return AC97_CAPS_ENHANCEMENT(as->caps) != 0;
case CHECK_LINE1:
return as->ext_mid & AC97_EXT_MODEM_LINE1;
case CHECK_LINE2:
return as->ext_mid & AC97_EXT_MODEM_LINE2;
case CHECK_HANDSET:
return as->ext_mid & AC97_EXT_MODEM_HANDSET;
default:
printf("%s: internal error: feature=%d\n", __func__, check);
return 0;
}
}
static void
ac97_setup_source_info(struct ac97_softc *as)
{
int idx, ouridx;
struct ac97_source_info *si, *si2;
for (idx = 0, ouridx = 0; idx < SOURCE_INFO_SIZE(as); idx++) {
si = &as->source_info[ouridx];
if (as->type == AC97_CODEC_TYPE_MODEM) {
if (!ac97_check_capability(as,
modem_source_info[idx].req_feature))
continue;
memcpy(si, &modem_source_info[idx], sizeof(*si));
} else {
if (!ac97_check_capability(as,
audio_source_info[idx].req_feature))
continue;
memcpy(si, &audio_source_info[idx], sizeof(*si));
}
switch (si->type) {
case AUDIO_MIXER_CLASS:
si->mixer_class = ouridx;
ouridx++;
break;
case AUDIO_MIXER_VALUE:
/* Todo - Test to see if it works */
ouridx++;
/* Add an entry for mute, if necessary */
if (si->mute) {
si = &as->source_info[ouridx];
if (as->type == AC97_CODEC_TYPE_MODEM)
memcpy(si, &modem_source_info[idx],
sizeof(*si));
else
memcpy(si, &audio_source_info[idx],
sizeof(*si));
si->qualifier = AudioNmute;
si->type = AUDIO_MIXER_ENUM;
si->info = &ac97_on_off;
si->info_size = sizeof(ac97_on_off);
si->bits = 1;
si->ofs = 15;
si->mute = 0;
si->polarity = 0;
ouridx++;
}
break;
case AUDIO_MIXER_ENUM:
/* Todo - Test to see if it works */
ouridx++;
break;
default:
aprint_error ("ac97: shouldn't get here\n");
break;
}
}
as->num_source_info = ouridx;
for (idx = 0; idx < as->num_source_info; idx++) {
int idx2, previdx;
si = &as->source_info[idx];
/* Find mixer class */
for (idx2 = 0; idx2 < as->num_source_info; idx2++) {
si2 = &as->source_info[idx2];
if (si2->type == AUDIO_MIXER_CLASS &&
ac97_str_equal(si->class,
si2->class)) {
si->mixer_class = idx2;
}
}
/* Setup prev and next pointers */
if (si->prev != 0)
continue;
if (si->qualifier)
continue;
si->prev = AUDIO_MIXER_LAST;
previdx = idx;
for (idx2 = 0; idx2 < as->num_source_info; idx2++) {
if (idx2 == idx)
continue;
si2 = &as->source_info[idx2];
if (!si2->prev &&
ac97_str_equal(si->class, si2->class) &&
ac97_str_equal(si->device, si2->device)) {
as->source_info[previdx].next = idx2;
as->source_info[idx2].prev = previdx;
previdx = idx2;
}
}
as->source_info[previdx].next = AUDIO_MIXER_LAST;
}
}
/* backwords compatibility */
int
ac97_attach(struct ac97_host_if *host_if, struct device *sc_dev)
{
return ac97_attach_type(host_if, sc_dev, AC97_CODEC_TYPE_AUDIO);
}
int
ac97_attach_type(struct ac97_host_if *host_if, struct device *sc_dev, int type)
{
struct ac97_softc *as;
int error, i, j;
uint32_t id;
uint16_t id1, id2;
uint16_t extstat, rate;
uint16_t val;
mixer_ctrl_t ctl;
void (*initfunc)(struct ac97_softc *);
#define FLAGBUFLEN 140
char flagbuf[FLAGBUFLEN];
initfunc = NULL;
as = malloc(sizeof(struct ac97_softc), M_DEVBUF, M_WAITOK|M_ZERO);
if (as == NULL)
return ENOMEM;
as->codec_if.vtbl = &ac97civ;
as->host_if = host_if;
as->type = type;
if ((error = host_if->attach(host_if->arg, &as->codec_if))) {
free(as, M_DEVBUF);
return error;
}
if ((error = host_if->reset(host_if->arg))) {
free(as, M_DEVBUF);
return error;
}
if (host_if->flags)
as->host_flags = host_if->flags(host_if->arg);
/*
* Assume codec has all four power bits.
* XXXSCW: what to do for modems?
*/
as->power_all = AC97_POWER_REF | AC97_POWER_ANL | AC97_POWER_DAC |
AC97_POWER_ADC;
if (as->type == AC97_CODEC_TYPE_AUDIO) {
host_if->write(host_if->arg, AC97_REG_RESET, 0);
/*
* Power-up everything except the analogue mixer.
* If this codec doesn't support analogue mixer power-down,
* AC97_POWER_MIXER will read back as zero.
*/
host_if->write(host_if->arg, AC97_REG_POWER, AC97_POWER_MIXER);
ac97_read(as, AC97_REG_POWER, &val);
if ((val & AC97_POWER_MIXER) == 0) {
/* Codec doesn't support analogue mixer power-down */
as->power_all &= ~AC97_POWER_ANL;
}
host_if->write(host_if->arg, AC97_REG_POWER, 0);
for (i = 500000; i >= 0; i--) {
ac97_read(as, AC97_REG_POWER, &val);
if ((val & as->power_all) == as->power_all)
break;
DELAY(1);
}
} else if (as->type == AC97_CODEC_TYPE_MODEM) {
host_if->write(host_if->arg, AC97_REG_EXT_MODEM_ID, 0);
}
ac97_setup_defaults(as);
if (as->type == AC97_CODEC_TYPE_AUDIO)
ac97_read(as, AC97_REG_RESET, &as->caps);
ac97_read(as, AC97_REG_VENDOR_ID1, &id1);
ac97_read(as, AC97_REG_VENDOR_ID2, &id2);
id = (id1 << 16) | id2;
aprint_normal("%s: ac97: ", sc_dev->dv_xname);
for (i = 0; ; i++) {
if (ac97codecid[i].id == 0) {
char pnp[4];
AC97_GET_CODEC_ID(id, pnp);
#define ISASCII(c) ((c) >= ' ' && (c) < 0x7f)
if (ISASCII(pnp[0]) && ISASCII(pnp[1]) &&
ISASCII(pnp[2]))
aprint_normal("%c%c%c%d",
pnp[0], pnp[1], pnp[2], pnp[3]);
else
aprint_normal("unknown (0x%08x)", id);
break;
}
if (ac97codecid[i].id == (id & ac97codecid[i].mask)) {
aprint_normal("%s", ac97codecid[i].name);
if (ac97codecid[i].mask == AC97_VENDOR_ID_MASK) {
aprint_normal(" (0x%08x)", id);
}
initfunc = ac97codecid[i].init;
break;
}
}
aprint_normal(" codec; ");
for (i = j = 0; i < 10; i++) {
if (as->caps & (1 << i)) {
aprint_normal("%s%s", j ? ", " : "", ac97feature[i]);
j++;
}
}
aprint_normal("%s%s\n", j ? ", " : "",
ac97enhancement[AC97_CAPS_ENHANCEMENT(as->caps)]);
as->ac97_clock = AC97_STANDARD_CLOCK;
if (as->type == AC97_CODEC_TYPE_AUDIO) {
ac97_read(as, AC97_REG_EXT_AUDIO_ID, &as->ext_id);
if (as->ext_id != 0) {
/* Print capabilities */
bitmask_snprintf(as->ext_id,
"\20\20SECONDARY10\17SECONDARY01"
"\14AC97_23\13AC97_22\12AMAP\11LDAC\10SDAC"
"\7CDAC\4VRM\3SPDIF\2DRA\1VRA",
flagbuf, FLAGBUFLEN);
aprint_normal("%s: ac97: ext id %s\n", sc_dev->dv_xname,
flagbuf);
/* Print unusual settings */
if (as->ext_id & AC97_EXT_AUDIO_DSA_MASK) {
aprint_normal("%s: ac97: Slot assignment: ",
sc_dev->dv_xname);
switch (as->ext_id & AC97_EXT_AUDIO_DSA_MASK) {
case AC97_EXT_AUDIO_DSA01:
aprint_normal("7&8, 6&9, 10&11.\n");
break;
case AC97_EXT_AUDIO_DSA10:
aprint_normal("6&9, 10&11, 3&4.\n");
break;
case AC97_EXT_AUDIO_DSA11:
aprint_normal("10&11, 3&4, 7&8.\n");
break;
}
}
/* Enable and disable features */
ac97_read(as, AC97_REG_EXT_AUDIO_CTRL, &extstat);
extstat &= ~AC97_EXT_AUDIO_DRA;
if (as->ext_id & AC97_EXT_AUDIO_LDAC)
extstat |= AC97_EXT_AUDIO_LDAC;
if (as->ext_id & AC97_EXT_AUDIO_SDAC)
extstat |= AC97_EXT_AUDIO_SDAC;
if (as->ext_id & AC97_EXT_AUDIO_CDAC)
extstat |= AC97_EXT_AUDIO_CDAC;
if (as->ext_id & AC97_EXT_AUDIO_VRM)
extstat |= AC97_EXT_AUDIO_VRM;
if (as->ext_id & AC97_EXT_AUDIO_SPDIF) {
/* Output the same data as DAC to SPDIF output */
extstat &= ~AC97_EXT_AUDIO_SPSA_MASK;
extstat |= AC97_EXT_AUDIO_SPSA34;
}
if (as->ext_id & AC97_EXT_AUDIO_VRA)
extstat |= AC97_EXT_AUDIO_VRA;
ac97_write(as, AC97_REG_EXT_AUDIO_CTRL, extstat);
if (as->ext_id & AC97_EXT_AUDIO_VRA) {
/* VRA should be enabled. */
/* so it claims to do variable rate, let's make sure */
ac97_write(as, AC97_REG_PCM_FRONT_DAC_RATE,
44100);
ac97_read(as, AC97_REG_PCM_FRONT_DAC_RATE,
&rate);
if (rate != 44100) {
/* We can't believe ext_id */
as->ext_id = 0;
aprint_normal(
"%s: Ignore these capabilities.\n",
sc_dev->dv_xname);
}
/* restore the default value */
ac97_write(as, AC97_REG_PCM_FRONT_DAC_RATE,
AC97_SINGLE_RATE);
}
}
} else if (as->type == AC97_CODEC_TYPE_MODEM) {
const struct sysctlnode *node;
const struct sysctlnode *node_line1;
const struct sysctlnode *node_line2;
uint16_t xrate = 8000;
uint16_t xval, reg;
int err;
ac97_read(as, AC97_REG_EXT_MODEM_ID, &as->ext_mid);
if (as->ext_mid == 0 || as->ext_mid == 0xffff) {
aprint_normal("%s: no modem codec found\n",
sc_dev->dv_xname);
return ENXIO;
}
as->type = AC97_CODEC_TYPE_MODEM;
/* Print capabilities */
bitmask_snprintf(as->ext_mid,
"\20\5CID2\4CID1\3HANDSET\2LINE2\1LINE1",
flagbuf, FLAGBUFLEN);
aprint_normal("%s: ac97: ext mid %s", sc_dev->dv_xname,
flagbuf);
aprint_normal(", %s codec\n",
(as->ext_mid & 0xc000) == 0 ?
"primary" : "secondary");
/* Setup modem and sysctls */
err = sysctl_createv(&as->log, 0, NULL, NULL, 0, CTLTYPE_NODE,
"hw", NULL, NULL, 0, NULL, 0, CTL_HW,
CTL_EOL);
if (err != 0)
goto setup_modem;
err = sysctl_createv(&as->log, 0, NULL, &node, 0,
CTLTYPE_NODE, sc_dev->dv_xname, NULL,
NULL, 0, NULL, 0, CTL_HW, CTL_CREATE,
CTL_EOL);
if (err != 0)
goto setup_modem;
setup_modem:
/* reset */
ac97_write(as, AC97_REG_EXT_MODEM_ID, 1);
/* program rates */
xval = 0xff00 & ~AC97_EXT_MODEM_CTRL_PRA;
if (as->ext_mid & AC97_EXT_MODEM_LINE1) {
ac97_write(as, AC97_REG_LINE1_RATE, xrate);
xval &= ~(AC97_EXT_MODEM_CTRL_PRC |
AC97_EXT_MODEM_CTRL_PRD);
}
if (as->ext_mid & AC97_EXT_MODEM_LINE2) {
ac97_write(as, AC97_REG_LINE2_RATE, xrate);
xval &= ~(AC97_EXT_MODEM_CTRL_PRE |
AC97_EXT_MODEM_CTRL_PRF);
}
if (as->ext_mid & AC97_EXT_MODEM_HANDSET) {
ac97_write(as, AC97_REG_HANDSET_RATE, xrate);
xval &= ~(AC97_EXT_MODEM_CTRL_PRG |
AC97_EXT_MODEM_CTRL_PRH);
}
/* power-up everything */
ac97_write(as, AC97_REG_EXT_MODEM_CTRL, 0);
for (i = 5000; i >= 0; i--) {
ac97_read(as, AC97_REG_EXT_MODEM_CTRL, &reg);
if ((reg & /*XXXval*/0xf) == /*XXXval*/0xf)
break;
DELAY(1);
}
if (i <= 0) {
printf("%s: codec not responding, status=0x%x\n",
sc_dev->dv_xname, reg);
return ENXIO;
}
/* setup sysctls */
if (as->ext_mid & AC97_EXT_MODEM_LINE1) {
ac97_read(as, AC97_REG_GPIO_CFG, &reg);
reg &= ~AC97_GPIO_LINE1_OH;
ac97_write(as, AC97_REG_GPIO_CFG, reg);
ac97_read(as, AC97_REG_GPIO_POLARITY, &reg);
reg &= ~AC97_GPIO_LINE1_OH;
ac97_write(as, AC97_REG_GPIO_POLARITY, reg);
err = sysctl_createv(&as->log, 0, NULL, &node_line1,
CTLFLAG_READWRITE, CTLTYPE_INT,
"line1",
SYSCTL_DESCR("off-hook line1"),
ac97_sysctl_verify, 0, as, 0,
CTL_HW, node->sysctl_num,
CTL_CREATE, CTL_EOL);
if (err != 0)
goto sysctl_err;
as->offhook_line1_mib = node_line1->sysctl_num;
}
if (as->ext_mid & AC97_EXT_MODEM_LINE2) {
ac97_read(as, AC97_REG_GPIO_CFG, &reg);
reg &= ~AC97_GPIO_LINE2_OH;
ac97_write(as, AC97_REG_GPIO_CFG, reg);
ac97_read(as, AC97_REG_GPIO_POLARITY, &reg);
reg &= ~AC97_GPIO_LINE2_OH;
ac97_write(as, AC97_REG_GPIO_POLARITY, reg);
err = sysctl_createv(&as->log, 0, NULL, &node_line2,
CTLFLAG_READWRITE, CTLTYPE_INT,
"line2",
SYSCTL_DESCR("off-hook line2"),
ac97_sysctl_verify, 0, as, 0,
CTL_HW, node->sysctl_num,
CTL_CREATE, CTL_EOL);
if (err != 0)
goto sysctl_err;
as->offhook_line2_mib = node_line2->sysctl_num;
}
sysctl_err:
ac97_write(as, AC97_REG_GPIO_STICKY, 0xffff);
ac97_write(as, AC97_REG_GPIO_WAKEUP, 0x0);
ac97_write(as, AC97_REG_MISC_AFE, 0x0);
}
as->source_info = (as->type == AC97_CODEC_TYPE_MODEM ?
as->modem_source_info : as->audio_source_info);
ac97_setup_source_info(as);
memset(&ctl, 0, sizeof(ctl));
/* disable mutes */
for (i = 0; i < 11; i++) {
static struct {
const char *class, *device;
} d[11] = {
{ AudioCoutputs, AudioNmaster},
{ AudioCoutputs, AudioNheadphone},
{ AudioCoutputs, AudioNsurround},
{ AudioCoutputs, AudioNcenter},
{ AudioCoutputs, AudioNlfe},
{ AudioCinputs, AudioNdac},
{ AudioCinputs, AudioNcd},
{ AudioCinputs, AudioNline},
{ AudioCinputs, AudioNaux},
{ AudioCinputs, AudioNvideo},
{ AudioCrecord, AudioNvolume},
};
ctl.type = AUDIO_MIXER_ENUM;
ctl.un.ord = 0;
ctl.dev = ac97_get_portnum_by_name(&as->codec_if,
d[i].class, d[i].device, AudioNmute);
ac97_mixer_set_port(&as->codec_if, &ctl);
}
ctl.type = AUDIO_MIXER_ENUM;
ctl.un.ord = 0;
ctl.dev = ac97_get_portnum_by_name(&as->codec_if, AudioCrecord,
AudioNsource, NULL);
ac97_mixer_set_port(&as->codec_if, &ctl);
/* set a reasonable default volume */
ctl.type = AUDIO_MIXER_VALUE;
ctl.un.value.num_channels = 2;
ctl.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = \
ctl.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = 127;
ctl.dev = ac97_get_portnum_by_name(&as->codec_if, AudioCoutputs,
AudioNmaster, NULL);
ac97_mixer_set_port(&as->codec_if, &ctl);
ctl.dev = ac97_get_portnum_by_name(&as->codec_if, AudioCoutputs,
AudioNsurround, NULL);
ac97_mixer_set_port(&as->codec_if, &ctl);
ctl.un.value.num_channels = 1;
ctl.dev = ac97_get_portnum_by_name(&as->codec_if, AudioCoutputs,
AudioNcenter, NULL);
ac97_mixer_set_port(&as->codec_if, &ctl);
ctl.dev = ac97_get_portnum_by_name(&as->codec_if, AudioCoutputs,
AudioNlfe, NULL);
ac97_mixer_set_port(&as->codec_if, &ctl);
if (initfunc != NULL)
initfunc(as);
return 0;
}
static void
ac97_detach(struct ac97_codec_if *codec_if)
{
struct ac97_softc *as;
as = (struct ac97_softc *)codec_if;
ac97_write(as, AC97_REG_POWER, AC97_POWER_IN | AC97_POWER_OUT
| AC97_POWER_MIXER | AC97_POWER_MIXER_VREF
| AC97_POWER_ACLINK | AC97_POWER_CLK | AC97_POWER_AUX
| AC97_POWER_EAMP);
free(as, M_DEVBUF);
}
static int
ac97_query_devinfo(struct ac97_codec_if *codec_if, mixer_devinfo_t *dip)
{
struct ac97_softc *as;
struct ac97_source_info *si;
const char *name;
as = (struct ac97_softc *)codec_if;
if (dip->index < as->num_source_info) {
si = &as->source_info[dip->index];
dip->type = si->type;
dip->mixer_class = si->mixer_class;
dip->prev = si->prev;
dip->next = si->next;
if (si->qualifier)
name = si->qualifier;
else if (si->device)
name = si->device;
else if (si->class)
name = si->class;
else
name = 0;
if (name)
strcpy(dip->label.name, name);
memcpy(&dip->un, si->info, si->info_size);
/* Set the delta for volume sources */
if (dip->type == AUDIO_MIXER_VALUE)
dip->un.v.delta = 1 << (8 - si->bits);
return 0;
}
return ENXIO;
}
static int
ac97_mixer_set_port(struct ac97_codec_if *codec_if, mixer_ctrl_t *cp)
{
struct ac97_softc *as;
struct ac97_source_info *si;
uint16_t mask;
uint16_t val, newval;
int error;
as = (struct ac97_softc *)codec_if;
si = &as->source_info[cp->dev];
if (cp->dev < 0 || cp->dev >= as->num_source_info)
return EINVAL;
if (cp->type != si->type)
return EINVAL;
ac97_read(as, si->reg, &val);
DPRINTFN(5, ("read(%x) = %x\n", si->reg, val));
mask = (1 << si->bits) - 1;
switch (cp->type) {
case AUDIO_MIXER_ENUM:
if (cp->un.ord > mask || cp->un.ord < 0)
return EINVAL;
newval = (cp->un.ord << si->ofs);
if (si->reg == AC97_REG_RECORD_SELECT) {
newval |= (newval << (8 + si->ofs));
mask |= (mask << 8);
mask = mask << si->ofs;
} else if (si->reg == AC97_REG_SURR_MASTER) {
newval = cp->un.ord ? 0x8080 : 0x0000;
mask = 0x8080;
} else
mask = mask << si->ofs;
break;
case AUDIO_MIXER_VALUE:
{
const struct audio_mixer_value *value = si->info;
uint16_t l, r, ol, or;
int deltal, deltar;
if ((cp->un.value.num_channels <= 0) ||
(cp->un.value.num_channels > value->num_channels))
return EINVAL;
if (cp->un.value.num_channels == 1) {
l = r = cp->un.value.level[AUDIO_MIXER_LEVEL_MONO];
} else {
if (!(as->host_flags & AC97_HOST_SWAPPED_CHANNELS)) {
l = cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
r = cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
} else { /* left/right is reversed here */
r = cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
l = cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
}
}
if (!si->polarity) {
l = 255 - l;
r = 255 - r;
}
ol = (val >> (8+si->ofs)) & mask;
or = (val >> si->ofs) & mask;
deltal = (ol << (8 - si->bits)) - l;
deltar = (or << (8 - si->bits)) - r;
l = l >> (8 - si->bits);
r = r >> (8 - si->bits);
if (deltal && ol == l)
l += (deltal > 0) ? (l ? -1 : 0) : (l < mask ? 1 : 0);
if (deltar && or == r)
r += (deltar > 0) ? (r ? -1 : 0) : (r < mask ? 1 : 0);
newval = ((r & mask) << si->ofs);
if (value->num_channels == 2) {
newval = newval | ((l & mask) << (si->ofs+8));
mask |= (mask << 8);
}
mask = mask << si->ofs;
break;
}
default:
return EINVAL;
}
error = ac97_write(as, si->reg, (val & ~mask) | newval);
if (error)
return error;
return 0;
}
static int
ac97_get_portnum_by_name(struct ac97_codec_if *codec_if, const char *class,
const char *device, const char *qualifier)
{
struct ac97_softc *as;
int idx;
as = (struct ac97_softc *)codec_if;
for (idx = 0; idx < as->num_source_info; idx++) {
struct ac97_source_info *si = &as->source_info[idx];
if (ac97_str_equal(class, si->class) &&
ac97_str_equal(device, si->device) &&
ac97_str_equal(qualifier, si->qualifier))
return idx;
}
return -1;
}
static int
ac97_mixer_get_port(struct ac97_codec_if *codec_if, mixer_ctrl_t *cp)
{
struct ac97_softc *as;
struct ac97_source_info *si;
uint16_t mask;
uint16_t val;
as = (struct ac97_softc *)codec_if;
si = &as->source_info[cp->dev];
if (cp->dev < 0 || cp->dev >= as->num_source_info)
return EINVAL;
if (cp->type != si->type)
return EINVAL;
ac97_read(as, si->reg, &val);
DPRINTFN(5, ("read(%x) = %x\n", si->reg, val));
mask = (1 << si->bits) - 1;
switch (cp->type) {
case AUDIO_MIXER_ENUM:
cp->un.ord = (val >> si->ofs) & mask;
DPRINTFN(4, ("AUDIO_MIXER_ENUM: %x %d %x %d\n",
val, si->ofs, mask, cp->un.ord));
break;
case AUDIO_MIXER_VALUE:
{
const struct audio_mixer_value *value = si->info;
uint16_t l, r;
if ((cp->un.value.num_channels <= 0) ||
(cp->un.value.num_channels > value->num_channels))
return EINVAL;
if (value->num_channels == 1) {
l = r = (val >> si->ofs) & mask;
} else {
if (!(as->host_flags & AC97_HOST_SWAPPED_CHANNELS)) {
l = (val >> (si->ofs + 8)) & mask;
r = (val >> si->ofs) & mask;
} else { /* host has reversed channels */
r = (val >> (si->ofs + 8)) & mask;
l = (val >> si->ofs) & mask;
}
}
l = (l << (8 - si->bits));
r = (r << (8 - si->bits));
if (!si->polarity) {
l = 255 - l;
r = 255 - r;
}
/* The EAP driver averages l and r for stereo
channels that are requested in MONO mode. Does this
make sense? */
if (cp->un.value.num_channels == 1) {
cp->un.value.level[AUDIO_MIXER_LEVEL_MONO] = l;
} else if (cp->un.value.num_channels == 2) {
cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
}
break;
}
default:
return EINVAL;
}
return 0;
}
static int
ac97_set_rate(struct ac97_codec_if *codec_if, int target, u_int *rate)
{
struct ac97_softc *as;
u_int value;
uint16_t ext_stat;
uint16_t actual;
uint16_t power;
uint16_t power_bit;
as = (struct ac97_softc *)codec_if;
if (target == AC97_REG_PCM_MIC_ADC_RATE) {
if (!(as->ext_id & AC97_EXT_AUDIO_VRM)) {
*rate = AC97_SINGLE_RATE;
return 0;
}
} else {
if (!(as->ext_id & AC97_EXT_AUDIO_VRA)) {
*rate = AC97_SINGLE_RATE;
return 0;
}
}
value = *rate * AC97_STANDARD_CLOCK / as->ac97_clock;
ext_stat = 0;
/*
* PCM_FRONT_DAC_RATE/PCM_SURR_DAC_RATE/PCM_LFE_DAC_RATE
* Check VRA, DRA
* PCM_LR_ADC_RATE
* Check VRA
* PCM_MIC_ADC_RATE
* Check VRM
*/
switch (target) {
case AC97_REG_PCM_FRONT_DAC_RATE:
case AC97_REG_PCM_SURR_DAC_RATE:
case AC97_REG_PCM_LFE_DAC_RATE:
power_bit = AC97_POWER_OUT;
if (!(as->ext_id & AC97_EXT_AUDIO_VRA)) {
*rate = AC97_SINGLE_RATE;
return 0;
}
if (as->ext_id & AC97_EXT_AUDIO_DRA) {
ac97_read(as, AC97_REG_EXT_AUDIO_CTRL, &ext_stat);
if (value > 0x1ffff) {
return EINVAL;
} else if (value > 0xffff) {
/* Enable DRA */
ext_stat |= AC97_EXT_AUDIO_DRA;
ac97_write(as, AC97_REG_EXT_AUDIO_CTRL, ext_stat);
value /= 2;
} else {
/* Disable DRA */
ext_stat &= ~AC97_EXT_AUDIO_DRA;
ac97_write(as, AC97_REG_EXT_AUDIO_CTRL, ext_stat);
}
} else {
if (value > 0xffff)
return EINVAL;
}
break;
case AC97_REG_PCM_LR_ADC_RATE:
power_bit = AC97_POWER_IN;
if (!(as->ext_id & AC97_EXT_AUDIO_VRA)) {
*rate = AC97_SINGLE_RATE;
return 0;
}
if (value > 0xffff)
return EINVAL;
break;
case AC97_REG_PCM_MIC_ADC_RATE:
power_bit = AC97_POWER_IN;
if (!(as->ext_id & AC97_EXT_AUDIO_VRM)) {
*rate = AC97_SINGLE_RATE;
return 0;
}
if (value > 0xffff)
return EINVAL;
break;
default:
printf("%s: Unknown register: 0x%x\n", __func__, target);
return EINVAL;
}
ac97_read(as, AC97_REG_POWER, &power);
ac97_write(as, AC97_REG_POWER, power | power_bit);
ac97_write(as, target, (uint16_t)value);
ac97_read(as, target, &actual);
actual = (uint32_t)actual * as->ac97_clock / AC97_STANDARD_CLOCK;
ac97_write(as, AC97_REG_POWER, power);
if (ext_stat & AC97_EXT_AUDIO_DRA) {
*rate = actual * 2;
} else {
*rate = actual;
}
return 0;
}
static void
ac97_set_clock(struct ac97_codec_if *codec_if, unsigned int clock)
{
struct ac97_softc *as;
as = (struct ac97_softc *)codec_if;
as->ac97_clock = clock;
}
static uint16_t
ac97_get_extcaps(struct ac97_codec_if *codec_if)
{
struct ac97_softc *as;
as = (struct ac97_softc *)codec_if;
return as->ext_id;
}
static int
ac97_add_port(struct ac97_softc *as, const struct ac97_source_info *src)
{
struct ac97_source_info *si;
int ouridx, idx;
if ((as->type == AC97_CODEC_TYPE_AUDIO &&
as->num_source_info >= AUDIO_MAX_SOURCES) ||
(as->type == AC97_CODEC_TYPE_MODEM &&
as->num_source_info >= MODEM_MAX_SOURCES)) {
printf("%s: internal error: increase MAX_SOURCES in %s\n",
__func__, __FILE__);
return -1;
}
if (!ac97_check_capability(as, src->req_feature))
return -1;
ouridx = as->num_source_info;
si = &as->source_info[ouridx];
memcpy(si, src, sizeof(*si));
switch (si->type) {
case AUDIO_MIXER_CLASS:
case AUDIO_MIXER_VALUE:
printf("%s: adding class/value is not supported yet.\n",
__func__);
return -1;
case AUDIO_MIXER_ENUM:
break;
default:
printf("%s: unknown type: %d\n", __func__, si->type);
return -1;
}
as->num_source_info++;
si->mixer_class = ac97_get_portnum_by_name(&as->codec_if, si->class,
NULL, NULL);
/* Find the root of the device */
idx = ac97_get_portnum_by_name(&as->codec_if, si->class,
si->device, NULL);
/* Find the last item */
while (as->source_info[idx].next != AUDIO_MIXER_LAST)
idx = as->source_info[idx].next;
/* Append */
as->source_info[idx].next = ouridx;
si->prev = idx;
si->next = AUDIO_MIXER_LAST;
return 0;
}
/**
* Codec-dependent initialization
*/
#define AD1980_REG_MISC 0x76
#define AD1980_MISC_MBG0 0x0001 /* 0 1888/1980/1981 /1985 */
#define AD1980_MISC_MBG1 0x0002 /* 1 1888/1980/1981 /1985 */
#define AD1980_MISC_VREFD 0x0004 /* 2 1888/1980/1981 /1985 */
#define AD1980_MISC_VREFH 0x0008 /* 3 1888/1980/1981 /1985 */
#define AD1980_MISC_SRU 0x0010 /* 4 1888/1980 /1985 */
#define AD1980_MISC_LOSEL 0x0020 /* 5 1888/1980/1981 /1985 */
#define AD1980_MISC_2CMIC 0x0040 /* 6 1980/1981B/1985 */
#define AD1980_MISC_SPRD 0x0080 /* 7 1888/1980 /1985 */
#define AD1980_MISC_DMIX0 0x0100 /* 8 1888/1980 /1985 */
#define AD1980_MISC_DMIX1 0x0200 /* 9 1888/1980 /1985 */
#define AD1980_MISC_HPSEL 0x0400 /*10 1888/1980 /1985 */
#define AD1980_MISC_CLDIS 0x0800 /*11 1888/1980 /1985 */
#define AD1980_MISC_LODIS 0x1000 /*12 1888/1980/1981 /1985 */
#define AD1980_MISC_MSPLT 0x2000 /*13 1888/1980/1981 /1985 */
#define AD1980_MISC_AC97NC 0x4000 /*14 1888/1980 /1985 */
#define AD1980_MISC_DACZ 0x8000 /*15 1888/1980/1981 /1985 */
#define AD1981_REG_MISC 0x76
#define AD1981_MISC_MADST 0x0010 /* 4 */
#define AD1981A_MISC_MADPD 0x0040 /* 6 */
#define AD1981B_MISC_MADPD 0x0080 /* 7 */
#define AD1981_MISC_FMXE 0x0200 /* 9 */
#define AD1981_MISC_DAM 0x0800 /*11 */
static void
ac97_ad198x_init(struct ac97_softc *as)
{
int i;
uint16_t misc;
ac97_read(as, AD1980_REG_MISC, &misc);
ac97_write(as, AD1980_REG_MISC,
misc | AD1980_MISC_LOSEL | AD1980_MISC_HPSEL);
for (i = 0; i < as->num_source_info; i++) {
if (as->source_info[i].type != AUDIO_MIXER_VALUE)
continue;
if (as->source_info[i].reg == AC97_REG_MASTER_VOLUME)
as->source_info[i].reg = AC97_REG_SURR_MASTER;
else if (as->source_info[i].reg == AC97_REG_SURR_MASTER)
as->source_info[i].reg = AC97_REG_MASTER_VOLUME;
}
}
#define ALC650_REG_MULTI_CHANNEL_CONTROL 0x6a
#define ALC650_MCC_SLOT_MODIFY_MASK 0xc000
#define ALC650_MCC_FRONTDAC_FROM_SPDIFIN 0x2000 /* 13 */
#define ALC650_MCC_SPDIFOUT_FROM_ADC 0x1000 /* 12 */
#define ALC650_MCC_PCM_FROM_SPDIFIN 0x0800 /* 11 */
#define ALC650_MCC_MIC_OR_CENTERLFE 0x0400 /* 10 */
#define ALC650_MCC_LINEIN_OR_SURROUND 0x0200 /* 9 */
#define ALC650_MCC_INDEPENDENT_MASTER_L 0x0080 /* 7 */
#define ALC650_MCC_INDEPENDENT_MASTER_R 0x0040 /* 6 */
#define ALC650_MCC_ANALOG_TO_CENTERLFE 0x0020 /* 5 */
#define ALC650_MCC_ANALOG_TO_SURROUND 0x0010 /* 4 */
#define ALC650_MCC_EXCHANGE_CENTERLFE 0x0008 /* 3 */
#define ALC650_MCC_CENTERLFE_DOWNMIX 0x0004 /* 2 */
#define ALC650_MCC_SURROUND_DOWNMIX 0x0002 /* 1 */
#define ALC650_MCC_LINEOUT_TO_SURROUND 0x0001 /* 0 */
static void
ac97_alc650_init(struct ac97_softc *as)
{
static const struct ac97_source_info sources[6] = {
{ AudioCoutputs, AudioNsurround, "lineinjack",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
ALC650_REG_MULTI_CHANNEL_CONTROL,
0x0000, 1, 9, 0, 0, CHECK_SURROUND },
{ AudioCoutputs, AudioNsurround, "mixtofront",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
ALC650_REG_MULTI_CHANNEL_CONTROL,
0x0000, 1, 1, 0, 0, CHECK_SURROUND },
{ AudioCoutputs, AudioNcenter, "micjack",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
ALC650_REG_MULTI_CHANNEL_CONTROL,
0x0000, 1, 10, 0, 0, CHECK_CENTER },
{ AudioCoutputs, AudioNlfe, "micjack",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
ALC650_REG_MULTI_CHANNEL_CONTROL,
0x0000, 1, 10, 0, 0, CHECK_LFE },
{ AudioCoutputs, AudioNcenter, "mixtofront",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
ALC650_REG_MULTI_CHANNEL_CONTROL,
0x0000, 1, 2, 0, 0, CHECK_CENTER },
{ AudioCoutputs, AudioNlfe, "mixtofront",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
ALC650_REG_MULTI_CHANNEL_CONTROL,
0x0000, 1, 2, 0, 0, CHECK_LFE },
};
ac97_add_port(as, &sources[0]);
ac97_add_port(as, &sources[1]);
ac97_add_port(as, &sources[2]);
ac97_add_port(as, &sources[3]);
ac97_add_port(as, &sources[4]);
ac97_add_port(as, &sources[5]);
}
#define VT1616_REG_IO_CONTROL 0x5a
#define VT1616_IC_LVL (1 << 15)
#define VT1616_IC_LFECENTER_TO_FRONT (1 << 12)
#define VT1616_IC_SURROUND_TO_FRONT (1 << 11)
#define VT1616_IC_BPDC (1 << 10)
#define VT1616_IC_DC (1 << 9)
#define VT1616_IC_IB_MASK 0x000c
static void
ac97_vt1616_init(struct ac97_softc *as)
{
static const struct ac97_source_info sources[3] = {
{ AudioCoutputs, AudioNsurround, "mixtofront",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
VT1616_REG_IO_CONTROL,
0x0000, 1, 11, 0, 0, CHECK_SURROUND },
{ AudioCoutputs, AudioNcenter, "mixtofront",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
VT1616_REG_IO_CONTROL,
0x0000, 1, 12, 0, 0, CHECK_CENTER },
{ AudioCoutputs, AudioNlfe, "mixtofront",
AUDIO_MIXER_ENUM, WRAP(ac97_on_off),
VT1616_REG_IO_CONTROL,
0x0000, 1, 12, 0, 0, CHECK_LFE },
};
ac97_add_port(as, &sources[0]);
ac97_add_port(as, &sources[1]);
ac97_add_port(as, &sources[2]);
}
static int
ac97_modem_offhook_set(struct ac97_softc *as, int line, int newval)
{
uint16_t val;
val = as->shadow_reg[AC97_REG_GPIO_STATUS >> 1];
switch (newval) {
case 0:
val &= ~line;
break;
case 1:
val |= line;
break;
}
ac97_write(as, AC97_REG_GPIO_STATUS, val);
return 0;
}
static int
ac97_sysctl_verify(SYSCTLFN_ARGS)
{
int error, tmp;
struct sysctlnode node;
struct ac97_softc *as;
node = *rnode;
as = rnode->sysctl_data;
if (node.sysctl_num == as->offhook_line1_mib) {
tmp = as->offhook_line1;
node.sysctl_data = &tmp;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (tmp < 0 || tmp > 1)
return EINVAL;
as->offhook_line1 = tmp;
ac97_modem_offhook_set(as, AC97_GPIO_LINE1_OH, tmp);
} else if (node.sysctl_num == as->offhook_line2_mib) {
tmp = as->offhook_line2;
node.sysctl_data = &tmp;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (tmp < 0 || tmp > 1)
return EINVAL;
as->offhook_line2 = tmp;
ac97_modem_offhook_set(as, AC97_GPIO_LINE2_OH, tmp);
}
return 0;
}