489392feb7
No functional change intended. MFC after: 1 week
949 lines
24 KiB
C
949 lines
24 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2016 Alex Teaca <iateaca@FreeBSD.org>
|
|
* 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 ``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.
|
|
*
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <pthread.h>
|
|
#include <pthread_np.h>
|
|
#include <unistd.h>
|
|
|
|
#include "pci_hda.h"
|
|
#include "audio.h"
|
|
|
|
/*
|
|
* HDA Codec defines
|
|
*/
|
|
#define INTEL_VENDORID 0x8086
|
|
|
|
#define HDA_CODEC_SUBSYSTEM_ID ((INTEL_VENDORID << 16) | 0x01)
|
|
#define HDA_CODEC_ROOT_NID 0x00
|
|
#define HDA_CODEC_FG_NID 0x01
|
|
#define HDA_CODEC_AUDIO_OUTPUT_NID 0x02
|
|
#define HDA_CODEC_PIN_OUTPUT_NID 0x03
|
|
#define HDA_CODEC_AUDIO_INPUT_NID 0x04
|
|
#define HDA_CODEC_PIN_INPUT_NID 0x05
|
|
|
|
#define HDA_CODEC_STREAMS_COUNT 0x02
|
|
#define HDA_CODEC_STREAM_OUTPUT 0x00
|
|
#define HDA_CODEC_STREAM_INPUT 0x01
|
|
|
|
#define HDA_CODEC_PARAMS_COUNT 0x14
|
|
#define HDA_CODEC_CONN_LIST_COUNT 0x01
|
|
#define HDA_CODEC_RESPONSE_EX_UNSOL 0x10
|
|
#define HDA_CODEC_RESPONSE_EX_SOL 0x00
|
|
#define HDA_CODEC_AMP_NUMSTEPS 0x4a
|
|
|
|
#define HDA_CODEC_SUPP_STREAM_FORMATS_PCM \
|
|
(1 << HDA_PARAM_SUPP_STREAM_FORMATS_PCM_SHIFT)
|
|
|
|
#define HDA_CODEC_FMT_BASE_MASK (0x01 << 14)
|
|
|
|
#define HDA_CODEC_FMT_MULT_MASK (0x07 << 11)
|
|
#define HDA_CODEC_FMT_MULT_2 (0x01 << 11)
|
|
#define HDA_CODEC_FMT_MULT_3 (0x02 << 11)
|
|
#define HDA_CODEC_FMT_MULT_4 (0x03 << 11)
|
|
|
|
#define HDA_CODEC_FMT_DIV_MASK 0x07
|
|
#define HDA_CODEC_FMT_DIV_SHIFT 8
|
|
|
|
#define HDA_CODEC_FMT_BITS_MASK (0x07 << 4)
|
|
#define HDA_CODEC_FMT_BITS_8 (0x00 << 4)
|
|
#define HDA_CODEC_FMT_BITS_16 (0x01 << 4)
|
|
#define HDA_CODEC_FMT_BITS_24 (0x03 << 4)
|
|
#define HDA_CODEC_FMT_BITS_32 (0x04 << 4)
|
|
|
|
#define HDA_CODEC_FMT_CHAN_MASK (0x0f << 0)
|
|
|
|
#define HDA_CODEC_AUDIO_WCAP_OUTPUT \
|
|
(0x00 << HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_SHIFT)
|
|
#define HDA_CODEC_AUDIO_WCAP_INPUT \
|
|
(0x01 << HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_SHIFT)
|
|
#define HDA_CODEC_AUDIO_WCAP_PIN \
|
|
(0x04 << HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_SHIFT)
|
|
#define HDA_CODEC_AUDIO_WCAP_CONN_LIST \
|
|
(1 << HDA_PARAM_AUDIO_WIDGET_CAP_CONN_LIST_SHIFT)
|
|
#define HDA_CODEC_AUDIO_WCAP_FORMAT_OVR \
|
|
(1 << HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR_SHIFT)
|
|
#define HDA_CODEC_AUDIO_WCAP_AMP_OVR \
|
|
(1 << HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR_SHIFT)
|
|
#define HDA_CODEC_AUDIO_WCAP_OUT_AMP \
|
|
(1 << HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP_SHIFT)
|
|
#define HDA_CODEC_AUDIO_WCAP_IN_AMP \
|
|
(1 << HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP_SHIFT)
|
|
#define HDA_CODEC_AUDIO_WCAP_STEREO \
|
|
(1 << HDA_PARAM_AUDIO_WIDGET_CAP_STEREO_SHIFT)
|
|
|
|
#define HDA_CODEC_PIN_CAP_OUTPUT \
|
|
(1 << HDA_PARAM_PIN_CAP_OUTPUT_CAP_SHIFT)
|
|
#define HDA_CODEC_PIN_CAP_INPUT \
|
|
(1 << HDA_PARAM_PIN_CAP_INPUT_CAP_SHIFT)
|
|
#define HDA_CODEC_PIN_CAP_PRESENCE_DETECT \
|
|
(1 << HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP_SHIFT)
|
|
|
|
#define HDA_CODEC_OUTPUT_AMP_CAP_MUTE_CAP \
|
|
(1 << HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP_SHIFT)
|
|
#define HDA_CODEC_OUTPUT_AMP_CAP_STEPSIZE \
|
|
(0x03 << HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE_SHIFT)
|
|
#define HDA_CODEC_OUTPUT_AMP_CAP_NUMSTEPS \
|
|
(HDA_CODEC_AMP_NUMSTEPS << HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS_SHIFT)
|
|
#define HDA_CODEC_OUTPUT_AMP_CAP_OFFSET \
|
|
(HDA_CODEC_AMP_NUMSTEPS << HDA_PARAM_OUTPUT_AMP_CAP_OFFSET_SHIFT)
|
|
|
|
#define HDA_CODEC_SET_AMP_GAIN_MUTE_MUTE 0x80
|
|
#define HDA_CODEC_SET_AMP_GAIN_MUTE_GAIN_MASK 0x7f
|
|
|
|
#define HDA_CODEC_PIN_SENSE_PRESENCE_PLUGGED (1 << 31)
|
|
#define HDA_CODEC_PIN_WIDGET_CTRL_OUT_ENABLE \
|
|
(1 << HDA_CMD_GET_PIN_WIDGET_CTRL_OUT_ENABLE_SHIFT)
|
|
#define HDA_CODEC_PIN_WIDGET_CTRL_IN_ENABLE \
|
|
(1 << HDA_CMD_GET_PIN_WIDGET_CTRL_IN_ENABLE_SHIFT)
|
|
|
|
#define HDA_CONFIG_DEFAULTCONF_COLOR_BLACK \
|
|
(0x01 << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT)
|
|
#define HDA_CONFIG_DEFAULTCONF_COLOR_RED \
|
|
(0x05 << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT)
|
|
|
|
#define HDA_CODEC_BUF_SIZE HDA_FIFO_SIZE
|
|
|
|
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
|
|
|
|
|
/*
|
|
* HDA Audio Context data structures
|
|
*/
|
|
|
|
typedef void (*transfer_func_t)(void *arg);
|
|
typedef int (*setup_func_t)(void *arg);
|
|
|
|
struct hda_audio_ctxt {
|
|
char name[64];
|
|
uint8_t run;
|
|
uint8_t started;
|
|
void *priv;
|
|
pthread_t tid;
|
|
pthread_mutex_t mtx;
|
|
pthread_cond_t cond;
|
|
setup_func_t do_setup;
|
|
transfer_func_t do_transfer;
|
|
};
|
|
|
|
/*
|
|
* HDA Audio Context module function declarations
|
|
*/
|
|
|
|
static void *hda_audio_ctxt_thr(void *arg);
|
|
static int hda_audio_ctxt_init(struct hda_audio_ctxt *actx, const char *tname,
|
|
transfer_func_t do_transfer, setup_func_t do_setup, void *priv);
|
|
static int hda_audio_ctxt_start(struct hda_audio_ctxt *actx);
|
|
static int hda_audio_ctxt_stop(struct hda_audio_ctxt *actx);
|
|
|
|
/*
|
|
* HDA Codec data structures
|
|
*/
|
|
|
|
struct hda_codec_softc;
|
|
|
|
typedef uint32_t (*verb_func_t)(struct hda_codec_softc *sc, uint16_t verb,
|
|
uint16_t payload);
|
|
|
|
struct hda_codec_stream {
|
|
uint8_t buf[HDA_CODEC_BUF_SIZE];
|
|
uint8_t channel;
|
|
uint16_t fmt;
|
|
uint8_t stream;
|
|
|
|
uint8_t left_gain;
|
|
uint8_t right_gain;
|
|
uint8_t left_mute;
|
|
uint8_t right_mute;
|
|
|
|
struct audio *aud;
|
|
struct hda_audio_ctxt actx;
|
|
};
|
|
|
|
struct hda_codec_softc {
|
|
uint32_t no_nodes;
|
|
uint32_t subsystem_id;
|
|
const uint32_t (*get_parameters)[HDA_CODEC_PARAMS_COUNT];
|
|
const uint8_t (*conn_list)[HDA_CODEC_CONN_LIST_COUNT];
|
|
const uint32_t *conf_default;
|
|
const uint8_t *pin_ctrl_default;
|
|
const verb_func_t *verb_handlers;
|
|
|
|
struct hda_codec_inst *hci;
|
|
struct hda_codec_stream streams[HDA_CODEC_STREAMS_COUNT];
|
|
};
|
|
|
|
/*
|
|
* HDA Codec module function declarations
|
|
*/
|
|
static int hda_codec_init(struct hda_codec_inst *hci, const char *play,
|
|
const char *rec);
|
|
static int hda_codec_reset(struct hda_codec_inst *hci);
|
|
static int hda_codec_command(struct hda_codec_inst *hci, uint32_t cmd_data);
|
|
static int hda_codec_notify(struct hda_codec_inst *hci, uint8_t run,
|
|
uint8_t stream, uint8_t dir);
|
|
|
|
static int hda_codec_parse_format(uint16_t fmt, struct audio_params *params);
|
|
|
|
static uint32_t hda_codec_audio_output_nid(struct hda_codec_softc *sc,
|
|
uint16_t verb, uint16_t payload);
|
|
static void hda_codec_audio_output_do_transfer(void *arg);
|
|
static int hda_codec_audio_output_do_setup(void *arg);
|
|
static uint32_t hda_codec_audio_input_nid(struct hda_codec_softc *sc,
|
|
uint16_t verb, uint16_t payload);
|
|
static void hda_codec_audio_input_do_transfer(void *arg);
|
|
static int hda_codec_audio_input_do_setup(void *arg);
|
|
|
|
static uint32_t hda_codec_audio_inout_nid(struct hda_codec_stream *st,
|
|
uint16_t verb, uint16_t payload);
|
|
|
|
/*
|
|
* HDA Codec global data
|
|
*/
|
|
|
|
#define HDA_CODEC_ROOT_DESC \
|
|
[HDA_CODEC_ROOT_NID] = { \
|
|
[HDA_PARAM_VENDOR_ID] = INTEL_VENDORID, \
|
|
[HDA_PARAM_REVISION_ID] = 0xffff, \
|
|
/* 1 Subnode, StartNid = 1 */ \
|
|
[HDA_PARAM_SUB_NODE_COUNT] = 0x00010001, \
|
|
}, \
|
|
|
|
#define HDA_CODEC_FG_COMMON_DESC \
|
|
[HDA_PARAM_FCT_GRP_TYPE] = HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO,\
|
|
/* B8 - B32, 8.0 - 192.0kHz */ \
|
|
[HDA_PARAM_SUPP_PCM_SIZE_RATE] = (0x1f << 16) | 0x7ff, \
|
|
[HDA_PARAM_SUPP_STREAM_FORMATS] = HDA_CODEC_SUPP_STREAM_FORMATS_PCM,\
|
|
[HDA_PARAM_INPUT_AMP_CAP] = 0x00, /* None */ \
|
|
[HDA_PARAM_OUTPUT_AMP_CAP] = 0x00, /* None */ \
|
|
[HDA_PARAM_GPIO_COUNT] = 0x00, \
|
|
|
|
#define HDA_CODEC_FG_OUTPUT_DESC \
|
|
[HDA_CODEC_FG_NID] = { \
|
|
/* 2 Subnodes, StartNid = 2 */ \
|
|
[HDA_PARAM_SUB_NODE_COUNT] = 0x00020002, \
|
|
HDA_CODEC_FG_COMMON_DESC \
|
|
}, \
|
|
|
|
#define HDA_CODEC_FG_INPUT_DESC \
|
|
[HDA_CODEC_FG_NID] = { \
|
|
/* 2 Subnodes, StartNid = 4 */ \
|
|
[HDA_PARAM_SUB_NODE_COUNT] = 0x00040002, \
|
|
HDA_CODEC_FG_COMMON_DESC \
|
|
}, \
|
|
|
|
#define HDA_CODEC_FG_DUPLEX_DESC \
|
|
[HDA_CODEC_FG_NID] = { \
|
|
/* 4 Subnodes, StartNid = 2 */ \
|
|
[HDA_PARAM_SUB_NODE_COUNT] = 0x00020004, \
|
|
HDA_CODEC_FG_COMMON_DESC \
|
|
}, \
|
|
|
|
#define HDA_CODEC_OUTPUT_DESC \
|
|
[HDA_CODEC_AUDIO_OUTPUT_NID] = { \
|
|
[HDA_PARAM_AUDIO_WIDGET_CAP] = \
|
|
HDA_CODEC_AUDIO_WCAP_OUTPUT | \
|
|
HDA_CODEC_AUDIO_WCAP_FORMAT_OVR | \
|
|
HDA_CODEC_AUDIO_WCAP_AMP_OVR | \
|
|
HDA_CODEC_AUDIO_WCAP_OUT_AMP | \
|
|
HDA_CODEC_AUDIO_WCAP_STEREO, \
|
|
/* B16, 16.0 - 192.0kHz */ \
|
|
[HDA_PARAM_SUPP_PCM_SIZE_RATE] = (0x02 << 16) | 0x7fc, \
|
|
[HDA_PARAM_SUPP_STREAM_FORMATS] = \
|
|
HDA_CODEC_SUPP_STREAM_FORMATS_PCM, \
|
|
[HDA_PARAM_INPUT_AMP_CAP] = 0x00, /* None */ \
|
|
[HDA_PARAM_CONN_LIST_LENGTH] = 0x00, \
|
|
[HDA_PARAM_OUTPUT_AMP_CAP] = \
|
|
HDA_CODEC_OUTPUT_AMP_CAP_MUTE_CAP | \
|
|
HDA_CODEC_OUTPUT_AMP_CAP_STEPSIZE | \
|
|
HDA_CODEC_OUTPUT_AMP_CAP_NUMSTEPS | \
|
|
HDA_CODEC_OUTPUT_AMP_CAP_OFFSET, \
|
|
}, \
|
|
[HDA_CODEC_PIN_OUTPUT_NID] = { \
|
|
[HDA_PARAM_AUDIO_WIDGET_CAP] = \
|
|
HDA_CODEC_AUDIO_WCAP_PIN | \
|
|
HDA_CODEC_AUDIO_WCAP_CONN_LIST | \
|
|
HDA_CODEC_AUDIO_WCAP_STEREO, \
|
|
[HDA_PARAM_PIN_CAP] = HDA_CODEC_PIN_CAP_OUTPUT | \
|
|
HDA_CODEC_PIN_CAP_PRESENCE_DETECT,\
|
|
[HDA_PARAM_INPUT_AMP_CAP] = 0x00, /* None */ \
|
|
[HDA_PARAM_CONN_LIST_LENGTH] = 0x01, \
|
|
[HDA_PARAM_OUTPUT_AMP_CAP] = 0x00, /* None */ \
|
|
}, \
|
|
|
|
#define HDA_CODEC_INPUT_DESC \
|
|
[HDA_CODEC_AUDIO_INPUT_NID] = { \
|
|
[HDA_PARAM_AUDIO_WIDGET_CAP] = \
|
|
HDA_CODEC_AUDIO_WCAP_INPUT | \
|
|
HDA_CODEC_AUDIO_WCAP_CONN_LIST | \
|
|
HDA_CODEC_AUDIO_WCAP_FORMAT_OVR | \
|
|
HDA_CODEC_AUDIO_WCAP_AMP_OVR | \
|
|
HDA_CODEC_AUDIO_WCAP_IN_AMP | \
|
|
HDA_CODEC_AUDIO_WCAP_STEREO, \
|
|
/* B16, 16.0 - 192.0kHz */ \
|
|
[HDA_PARAM_SUPP_PCM_SIZE_RATE] = (0x02 << 16) | 0x7fc, \
|
|
[HDA_PARAM_SUPP_STREAM_FORMATS] = \
|
|
HDA_CODEC_SUPP_STREAM_FORMATS_PCM, \
|
|
[HDA_PARAM_OUTPUT_AMP_CAP] = 0x00, /* None */ \
|
|
[HDA_PARAM_CONN_LIST_LENGTH] = 0x01, \
|
|
[HDA_PARAM_INPUT_AMP_CAP] = \
|
|
HDA_CODEC_OUTPUT_AMP_CAP_MUTE_CAP | \
|
|
HDA_CODEC_OUTPUT_AMP_CAP_STEPSIZE | \
|
|
HDA_CODEC_OUTPUT_AMP_CAP_NUMSTEPS | \
|
|
HDA_CODEC_OUTPUT_AMP_CAP_OFFSET, \
|
|
}, \
|
|
[HDA_CODEC_PIN_INPUT_NID] = { \
|
|
[HDA_PARAM_AUDIO_WIDGET_CAP] = \
|
|
HDA_CODEC_AUDIO_WCAP_PIN | \
|
|
HDA_CODEC_AUDIO_WCAP_STEREO, \
|
|
[HDA_PARAM_PIN_CAP] = HDA_CODEC_PIN_CAP_INPUT | \
|
|
HDA_CODEC_PIN_CAP_PRESENCE_DETECT, \
|
|
[HDA_PARAM_INPUT_AMP_CAP] = 0x00, /* None */ \
|
|
[HDA_PARAM_OUTPUT_AMP_CAP] = 0x00, /* None */ \
|
|
}, \
|
|
|
|
static const uint32_t
|
|
hda_codec_output_parameters[][HDA_CODEC_PARAMS_COUNT] = {
|
|
HDA_CODEC_ROOT_DESC
|
|
HDA_CODEC_FG_OUTPUT_DESC
|
|
HDA_CODEC_OUTPUT_DESC
|
|
};
|
|
|
|
static const uint32_t
|
|
hda_codec_input_parameters[][HDA_CODEC_PARAMS_COUNT] = {
|
|
HDA_CODEC_ROOT_DESC
|
|
HDA_CODEC_FG_INPUT_DESC
|
|
HDA_CODEC_INPUT_DESC
|
|
};
|
|
|
|
static const uint32_t
|
|
hda_codec_duplex_parameters[][HDA_CODEC_PARAMS_COUNT] = {
|
|
HDA_CODEC_ROOT_DESC
|
|
HDA_CODEC_FG_DUPLEX_DESC
|
|
HDA_CODEC_OUTPUT_DESC
|
|
HDA_CODEC_INPUT_DESC
|
|
};
|
|
|
|
#define HDA_CODEC_NODES_COUNT (ARRAY_SIZE(hda_codec_duplex_parameters))
|
|
|
|
static const uint8_t
|
|
hda_codec_conn_list[HDA_CODEC_NODES_COUNT][HDA_CODEC_CONN_LIST_COUNT] = {
|
|
[HDA_CODEC_PIN_OUTPUT_NID] = {HDA_CODEC_AUDIO_OUTPUT_NID},
|
|
[HDA_CODEC_AUDIO_INPUT_NID] = {HDA_CODEC_PIN_INPUT_NID},
|
|
};
|
|
|
|
static const uint32_t
|
|
hda_codec_conf_default[HDA_CODEC_NODES_COUNT] = {
|
|
[HDA_CODEC_PIN_OUTPUT_NID] = \
|
|
HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK |
|
|
HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT |
|
|
HDA_CONFIG_DEFAULTCONF_COLOR_BLACK |
|
|
(0x01 << HDA_CONFIG_DEFAULTCONF_ASSOCIATION_SHIFT),
|
|
[HDA_CODEC_PIN_INPUT_NID] = HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK |
|
|
HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN |
|
|
HDA_CONFIG_DEFAULTCONF_COLOR_RED |
|
|
(0x02 << HDA_CONFIG_DEFAULTCONF_ASSOCIATION_SHIFT),
|
|
};
|
|
|
|
static const uint8_t
|
|
hda_codec_pin_ctrl_default[HDA_CODEC_NODES_COUNT] = {
|
|
[HDA_CODEC_PIN_OUTPUT_NID] = HDA_CODEC_PIN_WIDGET_CTRL_OUT_ENABLE,
|
|
[HDA_CODEC_PIN_INPUT_NID] = HDA_CODEC_PIN_WIDGET_CTRL_IN_ENABLE,
|
|
};
|
|
|
|
static const
|
|
verb_func_t hda_codec_verb_handlers[HDA_CODEC_NODES_COUNT] = {
|
|
[HDA_CODEC_AUDIO_OUTPUT_NID] = hda_codec_audio_output_nid,
|
|
[HDA_CODEC_AUDIO_INPUT_NID] = hda_codec_audio_input_nid,
|
|
};
|
|
|
|
/*
|
|
* HDA Codec module function definitions
|
|
*/
|
|
|
|
static int
|
|
hda_codec_init(struct hda_codec_inst *hci, const char *play,
|
|
const char *rec)
|
|
{
|
|
struct hda_codec_softc *sc = NULL;
|
|
struct hda_codec_stream *st = NULL;
|
|
int err;
|
|
|
|
if (!(play || rec))
|
|
return (-1);
|
|
|
|
sc = calloc(1, sizeof(*sc));
|
|
if (!sc)
|
|
return (-1);
|
|
|
|
if (play && rec)
|
|
sc->get_parameters = hda_codec_duplex_parameters;
|
|
else {
|
|
if (play)
|
|
sc->get_parameters = hda_codec_output_parameters;
|
|
else
|
|
sc->get_parameters = hda_codec_input_parameters;
|
|
}
|
|
sc->subsystem_id = HDA_CODEC_SUBSYSTEM_ID;
|
|
sc->no_nodes = HDA_CODEC_NODES_COUNT;
|
|
sc->conn_list = hda_codec_conn_list;
|
|
sc->conf_default = hda_codec_conf_default;
|
|
sc->pin_ctrl_default = hda_codec_pin_ctrl_default;
|
|
sc->verb_handlers = hda_codec_verb_handlers;
|
|
DPRINTF("HDA Codec nodes: %d", sc->no_nodes);
|
|
|
|
/*
|
|
* Initialize the Audio Output stream
|
|
*/
|
|
if (play) {
|
|
st = &sc->streams[HDA_CODEC_STREAM_OUTPUT];
|
|
|
|
err = hda_audio_ctxt_init(&st->actx, "hda-audio-output",
|
|
hda_codec_audio_output_do_transfer,
|
|
hda_codec_audio_output_do_setup, sc);
|
|
assert(!err);
|
|
|
|
st->aud = audio_init(play, 1);
|
|
if (!st->aud) {
|
|
DPRINTF("Fail to init the output audio player");
|
|
return (-1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize the Audio Input stream
|
|
*/
|
|
if (rec) {
|
|
st = &sc->streams[HDA_CODEC_STREAM_INPUT];
|
|
|
|
err = hda_audio_ctxt_init(&st->actx, "hda-audio-input",
|
|
hda_codec_audio_input_do_transfer,
|
|
hda_codec_audio_input_do_setup, sc);
|
|
assert(!err);
|
|
|
|
st->aud = audio_init(rec, 0);
|
|
if (!st->aud) {
|
|
DPRINTF("Fail to init the input audio player");
|
|
return (-1);
|
|
}
|
|
}
|
|
|
|
sc->hci = hci;
|
|
hci->priv = sc;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hda_codec_reset(struct hda_codec_inst *hci)
|
|
{
|
|
const struct hda_ops *hops = NULL;
|
|
struct hda_codec_softc *sc = NULL;
|
|
struct hda_codec_stream *st = NULL;
|
|
int i;
|
|
|
|
assert(hci);
|
|
|
|
hops = hci->hops;
|
|
assert(hops);
|
|
|
|
sc = (struct hda_codec_softc *)hci->priv;
|
|
assert(sc);
|
|
|
|
for (i = 0; i < HDA_CODEC_STREAMS_COUNT; i++) {
|
|
st = &sc->streams[i];
|
|
st->left_gain = HDA_CODEC_AMP_NUMSTEPS;
|
|
st->right_gain = HDA_CODEC_AMP_NUMSTEPS;
|
|
st->left_mute = HDA_CODEC_SET_AMP_GAIN_MUTE_MUTE;
|
|
st->right_mute = HDA_CODEC_SET_AMP_GAIN_MUTE_MUTE;
|
|
}
|
|
|
|
DPRINTF("cad: 0x%x", hci->cad);
|
|
|
|
if (!hops->signal) {
|
|
DPRINTF("The controller ops does not implement \
|
|
the signal function");
|
|
return (-1);
|
|
}
|
|
|
|
return (hops->signal(hci));
|
|
}
|
|
|
|
static int
|
|
hda_codec_command(struct hda_codec_inst *hci, uint32_t cmd_data)
|
|
{
|
|
const struct hda_ops *hops = NULL;
|
|
struct hda_codec_softc *sc = NULL;
|
|
uint8_t cad = 0, nid = 0;
|
|
uint16_t verb = 0, payload = 0;
|
|
uint32_t res = 0;
|
|
|
|
/* 4 bits */
|
|
cad = (cmd_data >> HDA_CMD_CAD_SHIFT) & 0x0f;
|
|
/* 8 bits */
|
|
nid = (cmd_data >> HDA_CMD_NID_SHIFT) & 0xff;
|
|
|
|
if ((cmd_data & 0x70000) == 0x70000) {
|
|
/* 12 bits */
|
|
verb = (cmd_data >> HDA_CMD_VERB_12BIT_SHIFT) & 0x0fff;
|
|
/* 8 bits */
|
|
payload = cmd_data & 0xff;
|
|
} else {
|
|
/* 4 bits */
|
|
verb = (cmd_data >> HDA_CMD_VERB_4BIT_SHIFT) & 0x0f;
|
|
/* 16 bits */
|
|
payload = cmd_data & 0xffff;
|
|
}
|
|
|
|
assert(cad == hci->cad);
|
|
assert(hci);
|
|
|
|
hops = hci->hops;
|
|
assert(hops);
|
|
|
|
sc = (struct hda_codec_softc *)hci->priv;
|
|
assert(sc);
|
|
|
|
assert(nid < sc->no_nodes);
|
|
|
|
if (!hops->response) {
|
|
DPRINTF("The controller ops does not implement \
|
|
the response function");
|
|
return (-1);
|
|
}
|
|
|
|
switch (verb) {
|
|
case HDA_CMD_VERB_GET_PARAMETER:
|
|
res = sc->get_parameters[nid][payload];
|
|
break;
|
|
case HDA_CMD_VERB_GET_CONN_LIST_ENTRY:
|
|
res = sc->conn_list[nid][0];
|
|
break;
|
|
case HDA_CMD_VERB_GET_PIN_WIDGET_CTRL:
|
|
res = sc->pin_ctrl_default[nid];
|
|
break;
|
|
case HDA_CMD_VERB_GET_PIN_SENSE:
|
|
res = HDA_CODEC_PIN_SENSE_PRESENCE_PLUGGED;
|
|
break;
|
|
case HDA_CMD_VERB_GET_CONFIGURATION_DEFAULT:
|
|
res = sc->conf_default[nid];
|
|
break;
|
|
case HDA_CMD_VERB_GET_SUBSYSTEM_ID:
|
|
res = sc->subsystem_id;
|
|
break;
|
|
default:
|
|
assert(sc->verb_handlers);
|
|
if (sc->verb_handlers[nid])
|
|
res = sc->verb_handlers[nid](sc, verb, payload);
|
|
else
|
|
DPRINTF("Unknown VERB: 0x%x", verb);
|
|
break;
|
|
}
|
|
|
|
DPRINTF("cad: 0x%x nid: 0x%x verb: 0x%x payload: 0x%x response: 0x%x",
|
|
cad, nid, verb, payload, res);
|
|
|
|
return (hops->response(hci, res, HDA_CODEC_RESPONSE_EX_SOL));
|
|
}
|
|
|
|
static int
|
|
hda_codec_notify(struct hda_codec_inst *hci, uint8_t run,
|
|
uint8_t stream, uint8_t dir)
|
|
{
|
|
struct hda_codec_softc *sc = NULL;
|
|
struct hda_codec_stream *st = NULL;
|
|
struct hda_audio_ctxt *actx = NULL;
|
|
int i;
|
|
int err;
|
|
|
|
assert(hci);
|
|
assert(stream);
|
|
|
|
sc = (struct hda_codec_softc *)hci->priv;
|
|
assert(sc);
|
|
|
|
i = dir ? HDA_CODEC_STREAM_OUTPUT : HDA_CODEC_STREAM_INPUT;
|
|
st = &sc->streams[i];
|
|
|
|
DPRINTF("run: %d, stream: 0x%x, st->stream: 0x%x dir: %d",
|
|
run, stream, st->stream, dir);
|
|
|
|
if (stream != st->stream) {
|
|
DPRINTF("Stream not found");
|
|
return (0);
|
|
}
|
|
|
|
actx = &st->actx;
|
|
|
|
if (run)
|
|
err = hda_audio_ctxt_start(actx);
|
|
else
|
|
err = hda_audio_ctxt_stop(actx);
|
|
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
hda_codec_parse_format(uint16_t fmt, struct audio_params *params)
|
|
{
|
|
uint8_t div = 0;
|
|
|
|
assert(params);
|
|
|
|
/* Compute the Sample Rate */
|
|
params->rate = (fmt & HDA_CODEC_FMT_BASE_MASK) ? 44100 : 48000;
|
|
|
|
switch (fmt & HDA_CODEC_FMT_MULT_MASK) {
|
|
case HDA_CODEC_FMT_MULT_2:
|
|
params->rate *= 2;
|
|
break;
|
|
case HDA_CODEC_FMT_MULT_3:
|
|
params->rate *= 3;
|
|
break;
|
|
case HDA_CODEC_FMT_MULT_4:
|
|
params->rate *= 4;
|
|
break;
|
|
}
|
|
|
|
div = (fmt >> HDA_CODEC_FMT_DIV_SHIFT) & HDA_CODEC_FMT_DIV_MASK;
|
|
params->rate /= (div + 1);
|
|
|
|
/* Compute the Bits per Sample */
|
|
switch (fmt & HDA_CODEC_FMT_BITS_MASK) {
|
|
case HDA_CODEC_FMT_BITS_8:
|
|
params->format = AFMT_U8;
|
|
break;
|
|
case HDA_CODEC_FMT_BITS_16:
|
|
params->format = AFMT_S16_LE;
|
|
break;
|
|
case HDA_CODEC_FMT_BITS_24:
|
|
params->format = AFMT_S24_LE;
|
|
break;
|
|
case HDA_CODEC_FMT_BITS_32:
|
|
params->format = AFMT_S32_LE;
|
|
break;
|
|
default:
|
|
DPRINTF("Unknown format bits: 0x%x",
|
|
fmt & HDA_CODEC_FMT_BITS_MASK);
|
|
return (-1);
|
|
}
|
|
|
|
/* Compute the Number of Channels */
|
|
params->channels = (fmt & HDA_CODEC_FMT_CHAN_MASK) + 1;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static uint32_t
|
|
hda_codec_audio_output_nid(struct hda_codec_softc *sc, uint16_t verb,
|
|
uint16_t payload)
|
|
{
|
|
struct hda_codec_stream *st = &sc->streams[HDA_CODEC_STREAM_OUTPUT];
|
|
int res;
|
|
|
|
res = hda_codec_audio_inout_nid(st, verb, payload);
|
|
|
|
return (res);
|
|
}
|
|
|
|
static void
|
|
hda_codec_audio_output_do_transfer(void *arg)
|
|
{
|
|
const struct hda_ops *hops = NULL;
|
|
struct hda_codec_softc *sc = (struct hda_codec_softc *)arg;
|
|
struct hda_codec_inst *hci = NULL;
|
|
struct hda_codec_stream *st = NULL;
|
|
struct audio *aud = NULL;
|
|
int err;
|
|
|
|
hci = sc->hci;
|
|
assert(hci);
|
|
|
|
hops = hci->hops;
|
|
assert(hops);
|
|
|
|
st = &sc->streams[HDA_CODEC_STREAM_OUTPUT];
|
|
aud = st->aud;
|
|
|
|
err = hops->transfer(hci, st->stream, 1, st->buf, sizeof(st->buf));
|
|
if (err)
|
|
return;
|
|
|
|
err = audio_playback(aud, st->buf, sizeof(st->buf));
|
|
assert(!err);
|
|
}
|
|
|
|
static int
|
|
hda_codec_audio_output_do_setup(void *arg)
|
|
{
|
|
struct hda_codec_softc *sc = (struct hda_codec_softc *)arg;
|
|
struct hda_codec_stream *st = NULL;
|
|
struct audio *aud = NULL;
|
|
struct audio_params params;
|
|
int err;
|
|
|
|
st = &sc->streams[HDA_CODEC_STREAM_OUTPUT];
|
|
aud = st->aud;
|
|
|
|
err = hda_codec_parse_format(st->fmt, ¶ms);
|
|
if (err)
|
|
return (-1);
|
|
|
|
DPRINTF("rate: %d, channels: %d, format: 0x%x",
|
|
params.rate, params.channels, params.format);
|
|
|
|
return (audio_set_params(aud, ¶ms));
|
|
}
|
|
|
|
static uint32_t
|
|
hda_codec_audio_input_nid(struct hda_codec_softc *sc, uint16_t verb,
|
|
uint16_t payload)
|
|
{
|
|
struct hda_codec_stream *st = &sc->streams[HDA_CODEC_STREAM_INPUT];
|
|
int res;
|
|
|
|
res = hda_codec_audio_inout_nid(st, verb, payload);
|
|
|
|
return (res);
|
|
}
|
|
|
|
static void
|
|
hda_codec_audio_input_do_transfer(void *arg)
|
|
{
|
|
const struct hda_ops *hops = NULL;
|
|
struct hda_codec_softc *sc = (struct hda_codec_softc *)arg;
|
|
struct hda_codec_inst *hci = NULL;
|
|
struct hda_codec_stream *st = NULL;
|
|
struct audio *aud = NULL;
|
|
int err;
|
|
|
|
hci = sc->hci;
|
|
assert(hci);
|
|
|
|
hops = hci->hops;
|
|
assert(hops);
|
|
|
|
st = &sc->streams[HDA_CODEC_STREAM_INPUT];
|
|
aud = st->aud;
|
|
|
|
err = audio_record(aud, st->buf, sizeof(st->buf));
|
|
assert(!err);
|
|
|
|
hops->transfer(hci, st->stream, 0, st->buf, sizeof(st->buf));
|
|
}
|
|
|
|
static int
|
|
hda_codec_audio_input_do_setup(void *arg)
|
|
{
|
|
struct hda_codec_softc *sc = (struct hda_codec_softc *)arg;
|
|
struct hda_codec_stream *st = NULL;
|
|
struct audio *aud = NULL;
|
|
struct audio_params params;
|
|
int err;
|
|
|
|
st = &sc->streams[HDA_CODEC_STREAM_INPUT];
|
|
aud = st->aud;
|
|
|
|
err = hda_codec_parse_format(st->fmt, ¶ms);
|
|
if (err)
|
|
return (-1);
|
|
|
|
DPRINTF("rate: %d, channels: %d, format: 0x%x",
|
|
params.rate, params.channels, params.format);
|
|
|
|
return (audio_set_params(aud, ¶ms));
|
|
}
|
|
|
|
static uint32_t
|
|
hda_codec_audio_inout_nid(struct hda_codec_stream *st, uint16_t verb,
|
|
uint16_t payload)
|
|
{
|
|
uint32_t res = 0;
|
|
uint8_t mute = 0;
|
|
uint8_t gain = 0;
|
|
|
|
DPRINTF("%s verb: 0x%x, payload, 0x%x", st->actx.name, verb, payload);
|
|
|
|
switch (verb) {
|
|
case HDA_CMD_VERB_GET_CONV_FMT:
|
|
res = st->fmt;
|
|
break;
|
|
case HDA_CMD_VERB_SET_CONV_FMT:
|
|
st->fmt = payload;
|
|
break;
|
|
case HDA_CMD_VERB_GET_AMP_GAIN_MUTE:
|
|
if (payload & HDA_CMD_GET_AMP_GAIN_MUTE_LEFT) {
|
|
res = st->left_gain | st->left_mute;
|
|
DPRINTF("GET_AMP_GAIN_MUTE_LEFT: 0x%x", res);
|
|
} else {
|
|
res = st->right_gain | st->right_mute;
|
|
DPRINTF("GET_AMP_GAIN_MUTE_RIGHT: 0x%x", res);
|
|
}
|
|
break;
|
|
case HDA_CMD_VERB_SET_AMP_GAIN_MUTE:
|
|
mute = payload & HDA_CODEC_SET_AMP_GAIN_MUTE_MUTE;
|
|
gain = payload & HDA_CODEC_SET_AMP_GAIN_MUTE_GAIN_MASK;
|
|
|
|
if (payload & HDA_CMD_SET_AMP_GAIN_MUTE_LEFT) {
|
|
st->left_mute = mute;
|
|
st->left_gain = gain;
|
|
DPRINTF("SET_AMP_GAIN_MUTE_LEFT: \
|
|
mute: 0x%x gain: 0x%x", mute, gain);
|
|
}
|
|
|
|
if (payload & HDA_CMD_SET_AMP_GAIN_MUTE_RIGHT) {
|
|
st->right_mute = mute;
|
|
st->right_gain = gain;
|
|
DPRINTF("SET_AMP_GAIN_MUTE_RIGHT: \
|
|
mute: 0x%x gain: 0x%x", mute, gain);
|
|
}
|
|
break;
|
|
case HDA_CMD_VERB_GET_CONV_STREAM_CHAN:
|
|
res = (st->stream << 4) | st->channel;
|
|
break;
|
|
case HDA_CMD_VERB_SET_CONV_STREAM_CHAN:
|
|
st->channel = payload & 0x0f;
|
|
st->stream = (payload >> 4) & 0x0f;
|
|
DPRINTF("st->channel: 0x%x st->stream: 0x%x",
|
|
st->channel, st->stream);
|
|
if (!st->stream)
|
|
hda_audio_ctxt_stop(&st->actx);
|
|
break;
|
|
default:
|
|
DPRINTF("Unknown VERB: 0x%x", verb);
|
|
break;
|
|
}
|
|
|
|
return (res);
|
|
}
|
|
|
|
static const struct hda_codec_class hda_codec = {
|
|
.name = "hda_codec",
|
|
.init = hda_codec_init,
|
|
.reset = hda_codec_reset,
|
|
.command = hda_codec_command,
|
|
.notify = hda_codec_notify,
|
|
};
|
|
HDA_EMUL_SET(hda_codec);
|
|
|
|
/*
|
|
* HDA Audio Context module function definitions
|
|
*/
|
|
|
|
static void *
|
|
hda_audio_ctxt_thr(void *arg)
|
|
{
|
|
struct hda_audio_ctxt *actx = arg;
|
|
|
|
DPRINTF("Start Thread: %s", actx->name);
|
|
|
|
pthread_mutex_lock(&actx->mtx);
|
|
while (1) {
|
|
while (!actx->run)
|
|
pthread_cond_wait(&actx->cond, &actx->mtx);
|
|
|
|
actx->do_transfer(actx->priv);
|
|
}
|
|
pthread_mutex_unlock(&actx->mtx);
|
|
|
|
pthread_exit(NULL);
|
|
return (NULL);
|
|
}
|
|
|
|
static int
|
|
hda_audio_ctxt_init(struct hda_audio_ctxt *actx, const char *tname,
|
|
transfer_func_t do_transfer, setup_func_t do_setup, void *priv)
|
|
{
|
|
int err;
|
|
|
|
assert(actx);
|
|
assert(tname);
|
|
assert(do_transfer);
|
|
assert(do_setup);
|
|
assert(priv);
|
|
|
|
memset(actx, 0, sizeof(*actx));
|
|
|
|
actx->run = 0;
|
|
actx->do_transfer = do_transfer;
|
|
actx->do_setup = do_setup;
|
|
actx->priv = priv;
|
|
if (strlen(tname) < sizeof(actx->name))
|
|
memcpy(actx->name, tname, strlen(tname) + 1);
|
|
else
|
|
strcpy(actx->name, "unknown");
|
|
|
|
err = pthread_mutex_init(&actx->mtx, NULL);
|
|
assert(!err);
|
|
|
|
err = pthread_cond_init(&actx->cond, NULL);
|
|
assert(!err);
|
|
|
|
err = pthread_create(&actx->tid, NULL, hda_audio_ctxt_thr, actx);
|
|
assert(!err);
|
|
|
|
pthread_set_name_np(actx->tid, tname);
|
|
|
|
actx->started = 1;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hda_audio_ctxt_start(struct hda_audio_ctxt *actx)
|
|
{
|
|
int err = 0;
|
|
|
|
assert(actx);
|
|
assert(actx->started);
|
|
|
|
/* The stream is supposed to be stopped */
|
|
if (actx->run)
|
|
return (-1);
|
|
|
|
pthread_mutex_lock(&actx->mtx);
|
|
err = (* actx->do_setup)(actx->priv);
|
|
if (!err) {
|
|
actx->run = 1;
|
|
pthread_cond_signal(&actx->cond);
|
|
}
|
|
pthread_mutex_unlock(&actx->mtx);
|
|
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
hda_audio_ctxt_stop(struct hda_audio_ctxt *actx)
|
|
{
|
|
actx->run = 0;
|
|
return (0);
|
|
}
|