Commit 7440c4f2 authored by Jaroslav Kysela's avatar Jaroslav Kysela

[ALSA] Add Intel HDA driver

Documentation,PCI drivers,HDA generic driver,HDA Codec driver
HDA Intel driver
Added a new Intel High-Definition audio driver.
The driver consists of two separate modules: the generic support
module for HD codecs (snd-hda-codec), and the driver for Intel ICH6/7
chipset (snd-hda-intel).  The snd-hda-intel was called formerly
snd-azx in the ALSA 1.0.8 rlease.
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent e775f763
......@@ -599,6 +599,37 @@ Module parameters
Module supports up to 8 cards and autoprobe.
Module snd-hda-intel
--------------------
Module for Intel HD Audio (ICH6, ICH6M, ICH7)
model - force the model name
Module supports up to 8 cards.
Each codec may have a model table for different configurations.
If your machine isn't listed there, the default (usually minimal)
configuration is set up. You can pass "model=<name>" option to
specify a certain model in such a case. There are different
models depending on the codec chip.
Model name Description
---------- -----------
ALC880
3stack 3-jack in back and a headphone out
3stack-digout 3-jack in back, a HP out and a SPDIF out
5stack 5-jack in back, 2-jack in front
5stack-digout 5-jack in back, 2-jack in front, a SPDIF out
w810 3-jack
CMI9880
minimal 3-jack in back
min_fp 3-jack in back, 2-jack in front
full 6-jack in back, 2-jack in front
full_dig 6-jack in back, 2-jack in front, SPDIF I/O
allout 5-jack in back, 2-jack in front, SPDIF out
Module snd-hdsp
---------------
......
Notes on Universal Interface for Intel High Definition Audio Codec
------------------------------------------------------------------
Takashi Iwai <tiwai@suse.de>
[Still a draft version]
General
=======
The snd-hda-codec module supports the generic access function for the
High Definition (HD) audio codecs. It's designed to be independent
from the controller code like ac97 codec module. The real accessors
from/to the controller must be implemented in the lowlevel driver.
The structure of this module is similar with ac97_codec module.
Each codec chip belongs to a bus class which communicates with the
controller.
Initialization of Bus Instance
==============================
The card driver has to create struct hda_bus at first. The template
struct should be filled and passed to the constructor:
struct hda_bus_template {
void *private_data;
struct pci_dev *pci;
const char *modelname;
struct hda_bus_ops ops;
};
The card driver can set and use the private_data field to retrieve its
own data in callback functions. The pci field is used when the patch
needs to check the PCI subsystem IDs, so on. For non-PCI system, it
doesn't have to be set, of course.
The modelname field specifies the board's specific configuration. The
string is passed to the codec parser, and it depends on the parser how
the string is used.
These fields, private_data, pci and modelname are all optional.
The ops field contains the callback functions as the following:
struct hda_bus_ops {
int (*command)(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int parm);
unsigned int (*get_response)(struct hda_codec *codec);
void (*private_free)(struct hda_bus *);
};
The command callback is called when the codec module needs to send a
VERB to the controller. It's always a single command.
The get_response callback is called when the codec requires the answer
for the last command. These two callbacks are mandatory and have to
be given.
The last, private_free callback, is optional. It's called in the
destructor to release any necessary data in the lowlevel driver.
The bus instance is created via snd_hda_bus_new(). You need to pass
the card instance, the template, and the pointer to store the
resultant bus instance.
int snd_hda_bus_new(snd_card_t *card, const struct hda_bus_template *temp,
struct hda_bus **busp);
It returns zero if successful. A negative return value means any
error during creation.
Creation of Codec Instance
==========================
Each codec chip on the board is then created on the BUS instance.
To create a codec instance, call snd_hda_codec_new().
int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
struct hda_codec **codecp);
The first argument is the BUS instance, the second argument is the
address of the codec, and the last one is the pointer to store the
resultant codec instance (can be NULL if not needed).
The codec is stored in a linked list of bus instance. You can follow
the codec list like:
struct list_head *p;
struct hda_codec *codec;
list_for_each(p, &bus->codec_list) {
codec = list_entry(p, struct hda_codec, list);
...
}
The codec isn't initialized at this stage properly. The
initialization sequence is called when the controls are built later.
Codec Access
============
To access codec, use snd_codec_read() and snd_codec_write().
snd_hda_param_read() is for reading parameters.
For writing a sequence of verbs, use snd_hda_sequence_write().
To retrieve the number of sub nodes connected to the given node, use
snd_hda_get_sub_nodes(). The connection list can be obtained via
snd_hda_get_connections() call.
When an unsolicited event happens, pass the event via
snd_hda_queue_unsol_event() so that the codec routines will process it
later.
(Mixer) Controls
================
To create mixer controls of all codecs, call
snd_hda_build_controls(). It then builds the mixers and does
initialization stuff on each codec.
PCM Stuff
=========
snd_hda_build_pcms() gives the necessary information to create PCM
streams. When it's called, each codec belonging to the bus stores
codec->num_pcms and codec->pcm_info fields. The num_pcms indicates
the number of elements in pcm_info array. The card driver is supposed
to traverse the codec linked list, read the pcm information in
pcm_info array, and build pcm instances according to them.
The pcm_info array contains the following record:
/* PCM information for each substream */
struct hda_pcm_stream {
unsigned int substreams; /* number of substreams, 0 = not exist */
unsigned int channels_min; /* min. number of channels */
unsigned int channels_max; /* max. number of channels */
hda_nid_t nid; /* default NID to query rates/formats/bps, or set up */
u32 rates; /* supported rates */
u64 formats; /* supported formats (SNDRV_PCM_FMTBIT_) */
unsigned int maxbps; /* supported max. bit per sample */
struct hda_pcm_ops ops;
};
/* for PCM creation */
struct hda_pcm {
char *name;
struct hda_pcm_stream stream[2];
};
The name can be passed to snd_pcm_new(). The stream field contains
the information for playback (SNDRV_PCM_STREAM_PLAYBACK = 0) and
capture (SNDRV_PCM_STREAM_CAPTURE = 1) directions. The card driver
should pass substreams to snd_pcm_new() for the number of substreams
to create.
The channels_min, channels_max, rates and formats should be copied to
runtime->hw record. They and maxbps fields are used also to compute
the format value for the HDA codec and controller. Call
snd_hda_calc_stream_format() to get the format value.
The ops field contains the following callback functions:
struct hda_pcm_ops {
int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec,
snd_pcm_substream_t *substream);
int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec,
snd_pcm_substream_t *substream);
int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec,
unsigned int stream_tag, unsigned int format,
snd_pcm_substream_t *substream);
int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec,
snd_pcm_substream_t *substream);
};
All are non-NULL, so you can call them safely without NULL check.
The open callback should be called in PCM open after runtime->hw is
set up. It may override some setting and constraints additionally.
Similarly, the close callback should be called in the PCM close.
The prepare callback should be called in PCM prepare. This will set
up the codec chip properly for the operation. The cleanup should be
called in hw_free to clean up the configuration.
The caller should check the return value, at least for open and
prepare callbacks. When a negative value is returned, some error
occurred.
Proc Files
==========
Each codec dumps the widget node information in
/proc/asound/card*/codec#* file. This information would be really
helpful for debugging. Please provide its contents together with the
bug report.
Power Management
================
It's simple:
Call snd_hda_suspend() in the PM suspend callback.
Call snd_hda_resume() in the PM resume callback.
Codec Preset (Patch)
====================
To set up and handle the codec functionality fully, each codec may
have a codec preset (patch). It's defined in struct hda_codec_preset:
struct hda_codec_preset {
unsigned int id;
unsigned int mask;
unsigned int subs;
unsigned int subs_mask;
unsigned int rev;
const char *name;
int (*patch)(struct hda_codec *codec);
};
When the codec id and codec subsystem id match with the given id and
subs fields bitwise (with bitmask mask and subs_mask), the callback
patch is called. The patch callback should initialize the codec and
set the codec->patch_ops field. This is defined as below:
struct hda_codec_ops {
int (*build_controls)(struct hda_codec *codec);
int (*build_pcms)(struct hda_codec *codec);
int (*init)(struct hda_codec *codec);
void (*free)(struct hda_codec *codec);
void (*unsol_event)(struct hda_codec *codec, unsigned int res);
#ifdef CONFIG_PM
int (*suspend)(struct hda_codec *codec, unsigned int state);
int (*resume)(struct hda_codec *codec, unsigned int state);
#endif
};
The build_controls callback is called from snd_hda_build_controls().
Similarly, the build_pcms callback is called from
snd_hda_build_pcms(). The init callback is called after
build_controls to initialize the hardware.
The free callback is called as a destructor.
The unsol_event callback is called when an unsolicited event is
received.
The suspend and resume callbacks are for power management.
Each entry can be NULL if not necessary to be called.
Generic Parser
==============
When the device doesn't match with any given presets, the widgets are
parsed via th generic parser (hda_generic.c). Its support is
limited: no multi-channel support, for example.
Digital I/O
===========
Call snd_hda_create_spdif_out_ctls() from the patch to create controls
related with SPDIF out. In the patch resume callback, call
snd_hda_resume_spdif().
Helper Functions
================
snd_hda_get_codec_name() stores the codec name on the given string.
snd_hda_check_board_config() can be used to obtain the configuration
information matching with the device. Define the table with struct
hda_board_config entries (zero-terminated), and pass it to the
function. The function checks the modelname given as a module
parameter, and PCI subsystem IDs. If the matching entry is found, it
returns the config field value.
snd_hda_add_new_ctls() can be used to create and add control entries.
Pass the zero-terminated array of snd_kcontrol_new_t. The same array
can be passed to snd_hda_resume_ctls() for resume.
Note that this will call control->put callback of these entries. So,
put callback should check codec->in_resume and force to restore the
given value if it's non-zero even if the value is identical with the
cached value.
Macros HDA_CODEC_VOLUME(), HDA_CODEC_MUTE() and their variables can be
used for the entry of snd_kcontrol_new_t.
The input MUX helper callbacks for such a control are provided, too:
snd_hda_input_mux_info() and snd_hda_input_mux_put(). See
patch_realtek.c for example.
......@@ -514,5 +514,15 @@ config SND_VX222
To compile this driver as a module, choose M here: the module
will be called snd-vx222.
endmenu
config SND_HDA_INTEL
tristate "Intel HD Audio"
depends on SND
select SND_PCM
help
Say Y here to include support for Intel "High Definition
Audio" (Azalia) motherboard devices.
To compile this driver as a module, choose M here: the module
will be called snd-hda-intel.
endmenu
......@@ -53,6 +53,7 @@ obj-$(CONFIG_SND) += \
ca0106/ \
cs46xx/ \
emu10k1/ \
hda/ \
ice1712/ \
korg1212/ \
mixart/ \
......
snd-hda-intel-objs := hda_intel.o
snd-hda-codec-objs := hda_codec.o hda_generic.o patch_realtek.o patch_cmedia.o
ifdef CONFIG_PROC_FS
snd-hda-codec-objs += hda_proc.o
endif
obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o snd-hda-codec.o
/*
* Universal Interface for Intel High Definition Audio Codec
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*
*
* This driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This driver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include "hda_codec.h"
#include <sound/asoundef.h>
#include <sound/initval.h>
#include "hda_local.h"
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("Universal interface for High Definition Audio Codec");
MODULE_LICENSE("GPL");
/*
* vendor / preset table
*/
struct hda_vendor_id {
unsigned int id;
const char *name;
};
/* codec vendor labels */
static struct hda_vendor_id hda_vendor_ids[] = {
{ 0x10ec, "Realtek" },
{ 0x434d, "C-Media" },
{} /* terminator */
};
/* codec presets */
#include "hda_patch.h"
/**
* snd_hda_codec_read - send a command and get the response
* @codec: the HDA codec
* @nid: NID to send the command
* @direct: direct flag
* @verb: the verb to send
* @parm: the parameter for the verb
*
* Send a single command and read the corresponding response.
*
* Returns the obtained response value, or -1 for an error.
*/
unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int parm)
{
unsigned int res;
down(&codec->bus->cmd_mutex);
if (! codec->bus->ops.command(codec, nid, direct, verb, parm))
res = codec->bus->ops.get_response(codec);
else
res = (unsigned int)-1;
up(&codec->bus->cmd_mutex);
return res;
}
/**
* snd_hda_codec_write - send a single command without waiting for response
* @codec: the HDA codec
* @nid: NID to send the command
* @direct: direct flag
* @verb: the verb to send
* @parm: the parameter for the verb
*
* Send a single command without waiting for response.
*
* Returns 0 if successful, or a negative error code.
*/
int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int parm)
{
int err;
down(&codec->bus->cmd_mutex);
err = codec->bus->ops.command(codec, nid, direct, verb, parm);
up(&codec->bus->cmd_mutex);
return err;
}
/**
* snd_hda_sequence_write - sequence writes
* @codec: the HDA codec
* @seq: VERB array to send
*
* Send the commands sequentially from the given array.
* The array must be terminated with NID=0.
*/
void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq)
{
for (; seq->nid; seq++)
snd_hda_codec_write(codec, seq->nid, 0, seq->verb, seq->param);
}
/**
* snd_hda_get_sub_nodes - get the range of sub nodes
* @codec: the HDA codec
* @nid: NID to parse
* @start_id: the pointer to store the start NID
*
* Parse the NID and store the start NID of its sub-nodes.
* Returns the number of sub-nodes.
*/
int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid, hda_nid_t *start_id)
{
unsigned int parm;
parm = snd_hda_param_read(codec, nid, AC_PAR_NODE_COUNT);
*start_id = (parm >> 16) & 0x7fff;
return (int)(parm & 0x7fff);
}
/**
* snd_hda_get_connections - get connection list
* @codec: the HDA codec
* @nid: NID to parse
* @conn_list: connection list array
* @max_conns: max. number of connections to store
*
* Parses the connection list of the given widget and stores the list
* of NIDs.
*
* Returns the number of connections, or a negative error code.
*/
int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
hda_nid_t *conn_list, int max_conns)
{
unsigned int parm;
int i, j, conn_len, num_tupples, conns;
unsigned int shift, num_elems, mask;
snd_assert(conn_list && max_conns > 0, return -EINVAL);
parm = snd_hda_param_read(codec, nid, AC_PAR_CONNLIST_LEN);
if (parm & AC_CLIST_LONG) {
/* long form */
shift = 16;
num_elems = 2;
} else {
/* short form */
shift = 8;
num_elems = 4;
}
conn_len = parm & AC_CLIST_LENGTH;
num_tupples = num_elems / 2;
mask = (1 << (shift-1)) - 1;
if (! conn_len)
return 0; /* no connection */
if (conn_len == 1) {
/* single connection */
parm = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_LIST, 0);
conn_list[0] = parm & mask;
return 1;
}
/* multi connection */
conns = 0;
for (i = 0; i < conn_len; i += num_elems) {
parm = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_LIST, i);
for (j = 0; j < num_tupples; j++) {
int range_val;
hda_nid_t val1, val2, n;
range_val = parm & (1 << (shift-1)); /* ranges */
val1 = parm & mask;
parm >>= shift;
val2 = parm & mask;
parm >>= shift;
if (range_val) {
/* ranges between val1 and val2 */
if (val1 > val2) {
snd_printk(KERN_WARNING "hda_codec: invalid dep_range_val %x:%x\n", val1, val2);
continue;
}
for (n = val1; n <= val2; n++) {
if (conns >= max_conns)
return -EINVAL;
conn_list[conns++] = n;
}
} else {
if (! val1)
break;
if (conns >= max_conns)
return -EINVAL;
conn_list[conns++] = val1;
if (! val2)
break;
if (conns >= max_conns)
return -EINVAL;
conn_list[conns++] = val2;
}
}
}
return conns;
}
/**
* snd_hda_queue_unsol_event - add an unsolicited event to queue
* @bus: the BUS
* @res: unsolicited event (lower 32bit of RIRB entry)
* @res_ex: codec addr and flags (upper 32bit or RIRB entry)
*
* Adds the given event to the queue. The events are processed in
* the workqueue asynchronously. Call this function in the interrupt
* hanlder when RIRB receives an unsolicited event.
*
* Returns 0 if successful, or a negative error code.
*/
int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex)
{
struct hda_bus_unsolicited *unsol;
unsigned int wp;
if ((unsol = bus->unsol) == NULL)
return 0;
wp = (unsol->wp + 1) % HDA_UNSOL_QUEUE_SIZE;
unsol->wp = wp;
wp <<= 1;
unsol->queue[wp] = res;
unsol->queue[wp + 1] = res_ex;
queue_work(unsol->workq, &unsol->work);
return 0;
}
/*
* process queueud unsolicited events
*/
static void process_unsol_events(void *data)
{
struct hda_bus *bus = data;
struct hda_bus_unsolicited *unsol = bus->unsol;
struct hda_codec *codec;
unsigned int rp, caddr, res;
while (unsol->rp != unsol->wp) {
rp = (unsol->rp + 1) % HDA_UNSOL_QUEUE_SIZE;
unsol->rp = rp;
rp <<= 1;
res = unsol->queue[rp];
caddr = unsol->queue[rp + 1];
if (! (caddr & (1 << 4))) /* no unsolicited event? */
continue;
codec = bus->caddr_tbl[caddr & 0x0f];
if (codec && codec->patch_ops.unsol_event)
codec->patch_ops.unsol_event(codec, res);
}
}
/*
* initialize unsolicited queue
*/
static int init_unsol_queue(struct hda_bus *bus)
{
struct hda_bus_unsolicited *unsol;
unsol = kcalloc(1, sizeof(*unsol), GFP_KERNEL);
if (! unsol) {
snd_printk(KERN_ERR "hda_codec: can't allocate unsolicited queue\n");
return -ENOMEM;
}
unsol->workq = create_workqueue("hda_codec");
if (! unsol->workq) {
snd_printk(KERN_ERR "hda_codec: can't create workqueue\n");
kfree(unsol);
return -ENOMEM;
}
INIT_WORK(&unsol->work, process_unsol_events, bus);
bus->unsol = unsol;
return 0;
}
/*
* destructor
*/
static void snd_hda_codec_free(struct hda_codec *codec);
static int snd_hda_bus_free(struct hda_bus *bus)
{
struct list_head *p, *n;
if (! bus)
return 0;
if (bus->unsol) {
destroy_workqueue(bus->unsol->workq);
kfree(bus->unsol);
}
list_for_each_safe(p, n, &bus->codec_list) {
struct hda_codec *codec = list_entry(p, struct hda_codec, list);
snd_hda_codec_free(codec);
}
if (bus->ops.private_free)
bus->ops.private_free(bus);
kfree(bus);
return 0;
}
static int snd_hda_bus_dev_free(snd_device_t *device)
{
struct hda_bus *bus = device->device_data;
return snd_hda_bus_free(bus);
}
/**
* snd_hda_bus_new - create a HDA bus
* @card: the card entry
* @temp: the template for hda_bus information
* @busp: the pointer to store the created bus instance
*
* Returns 0 if successful, or a negative error code.
*/
int snd_hda_bus_new(snd_card_t *card, const struct hda_bus_template *temp,
struct hda_bus **busp)
{
struct hda_bus *bus;
int err;
static snd_device_ops_t dev_ops = {
.dev_free = snd_hda_bus_dev_free,
};
snd_assert(temp, return -EINVAL);
snd_assert(temp->ops.command && temp->ops.get_response, return -EINVAL);
if (busp)
*busp = NULL;
bus = kcalloc(1, sizeof(*bus), GFP_KERNEL);
if (bus == NULL) {
snd_printk(KERN_ERR "can't allocate struct hda_bus\n");
return -ENOMEM;
}
bus->card = card;
bus->private_data = temp->private_data;
bus->pci = temp->pci;
bus->modelname = temp->modelname;
bus->ops = temp->ops;
init_MUTEX(&bus->cmd_mutex);
INIT_LIST_HEAD(&bus->codec_list);
init_unsol_queue(bus);
if ((err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)) < 0) {
snd_hda_bus_free(bus);
return err;
}
if (busp)
*busp = bus;
return 0;
}
/*
* find a matching codec preset
*/
static const struct hda_codec_preset *find_codec_preset(struct hda_codec *codec)
{
const struct hda_codec_preset **tbl, *preset;
for (tbl = hda_preset_tables; *tbl; tbl++) {
for (preset = *tbl; preset->id; preset++) {
u32 mask = preset->mask;
if (! mask)
mask = ~0;
if (preset->id == (codec->vendor_id & mask))
return preset;
}
}
return NULL;
}
/*
* snd_hda_get_codec_name - store the codec name
*/
void snd_hda_get_codec_name(struct hda_codec *codec,
char *name, int namelen)
{
const struct hda_vendor_id *c;
const char *vendor = NULL;
u16 vendor_id = codec->vendor_id >> 16;
char tmp[16];
for (c = hda_vendor_ids; c->id; c++) {
if (c->id == vendor_id) {
vendor = c->name;
break;
}
}
if (! vendor) {
sprintf(tmp, "Generic %04x", vendor_id);
vendor = tmp;
}
if (codec->preset && codec->preset->name)
snprintf(name, namelen, "%s %s", vendor, codec->preset->name);
else
snprintf(name, namelen, "%s ID %x", vendor, codec->vendor_id & 0xffff);
}
/*
* look for an AFG node
*
* return 0 if not found
*/
static int look_for_afg_node(struct hda_codec *codec)
{
int i, total_nodes;
hda_nid_t nid;
total_nodes = snd_hda_get_sub_nodes(codec, AC_NODE_ROOT, &nid);
for (i = 0; i < total_nodes; i++, nid++) {
if (snd_hda_param_read(codec, nid, AC_PAR_FUNCTION_TYPE) == AC_GRP_AUDIO_FUNCTION)
return nid;
}
return 0;
}
/*
* codec destructor
*/
static void snd_hda_codec_free(struct hda_codec *codec)
{
if (! codec)
return;
list_del(&codec->list);
codec->bus->caddr_tbl[codec->addr] = NULL;
if (codec->patch_ops.free)
codec->patch_ops.free(codec);
kfree(codec);
}
static void init_amp_hash(struct hda_codec *codec);
/**
* snd_hda_codec_new - create a HDA codec
* @bus: the bus to assign
* @codec_addr: the codec address
* @codecp: the pointer to store the generated codec
*
* Returns 0 if successful, or a negative error code.
*/
int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
struct hda_codec **codecp)
{
struct hda_codec *codec;
char component[13];
int err;
snd_assert(bus, return -EINVAL);
snd_assert(codec_addr <= HDA_MAX_CODEC_ADDRESS, return -EINVAL);
if (bus->caddr_tbl[codec_addr]) {
snd_printk(KERN_ERR "hda_codec: address 0x%x is already occupied\n", codec_addr);
return -EBUSY;
}
codec = kcalloc(1, sizeof(*codec), GFP_KERNEL);
if (codec == NULL) {
snd_printk(KERN_ERR "can't allocate struct hda_codec\n");
return -ENOMEM;
}
codec->bus = bus;
codec->addr = codec_addr;
init_MUTEX(&codec->spdif_mutex);
init_amp_hash(codec);
list_add_tail(&codec->list, &bus->codec_list);
bus->caddr_tbl[codec_addr] = codec;
codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_VENDOR_ID);
codec->subsystem_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_SUBSYSTEM_ID);
codec->revision_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_REV_ID);
/* FIXME: support for multiple AFGs? */
codec->afg = look_for_afg_node(codec);
if (! codec->afg) {
snd_printk(KERN_ERR "hda_codec: no AFG node found\n");
snd_hda_codec_free(codec);
return -ENODEV;
}
codec->preset = find_codec_preset(codec);
if (! *bus->card->mixername)
snd_hda_get_codec_name(codec, bus->card->mixername,
sizeof(bus->card->mixername));
if (codec->preset && codec->preset->patch)
err = codec->preset->patch(codec);
else
err = snd_hda_parse_generic_codec(codec);
if (err < 0) {
snd_hda_codec_free(codec);
return err;
}
snd_hda_codec_proc_new(codec);
sprintf(component, "HDA:%08x", codec->vendor_id);
snd_component_add(codec->bus->card, component);
if (codecp)
*codecp = codec;
return 0;
}
/**
* snd_hda_codec_setup_stream - set up the codec for streaming
* @codec: the CODEC to set up
* @nid: the NID to set up
* @stream_tag: stream tag to pass, it's between 0x1 and 0xf.
* @channel_id: channel id to pass, zero based.
* @format: stream format.
*/
void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stream_tag,
int channel_id, int format)
{
snd_printdd("hda_codec_setup_stream: NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n",
nid, stream_tag, channel_id, format);
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID,
(stream_tag << 4) | channel_id);
msleep(1);
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, format);
}
/*
* amp access functions
*/
#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + (idx) * 32 + (dir) * 64)
#define INFO_AMP_CAPS (1<<0)
#define INFO_AMP_VOL (1<<1)
/* initialize the hash table */
static void init_amp_hash(struct hda_codec *codec)
{
memset(codec->amp_hash, 0xff, sizeof(codec->amp_hash));
codec->num_amp_entries = 0;
}
/* query the hash. allocate an entry if not found. */
static struct hda_amp_info *get_alloc_amp_hash(struct hda_codec *codec, u32 key)
{
u16 idx = key % (u16)ARRAY_SIZE(codec->amp_hash);
u16 cur = codec->amp_hash[idx];
struct hda_amp_info *info;
while (cur != 0xffff) {
info = &codec->amp_info[cur];
if (info->key == key)
return info;
cur = info->next;
}
/* add a new hash entry */
if (codec->num_amp_entries >= ARRAY_SIZE(codec->amp_info)) {
snd_printk(KERN_ERR "hda_codec: Tooooo many amps!\n");
return NULL;
}
cur = codec->num_amp_entries++;
info = &codec->amp_info[cur];
info->key = key;
info->status = 0; /* not initialized yet */
info->next = codec->amp_hash[idx];
codec->amp_hash[idx] = cur;
return info;
}
/*
* query AMP capabilities for the given widget and direction
*/
static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
{
struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, 0));
if (! info)
return 0;
if (! (info->status & INFO_AMP_CAPS)) {
info->amp_caps = snd_hda_param_read(codec, nid, direction == HDA_OUTPUT ?
AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
info->status |= INFO_AMP_CAPS;
}
return info->amp_caps;
}
/*
* read the current volume to info
* if the cache exists, read from the cache.
*/
static void get_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
hda_nid_t nid, int ch, int direction, int index)
{
u32 val, parm;
if (info->status & (INFO_AMP_VOL << ch))
return;
parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT;
parm |= direction == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
parm |= index;
val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE, parm);
info->vol[ch] = val & 0xff;
info->status |= INFO_AMP_VOL << ch;
}
/*
* write the current volume in info to the h/w
*/
static void put_vol_mute(struct hda_codec *codec,
hda_nid_t nid, int ch, int direction, int index, int val)
{
u32 parm;
parm = ch ? AC_AMP_SET_RIGHT : AC_AMP_SET_LEFT;
parm |= direction == HDA_OUTPUT ? AC_AMP_SET_OUTPUT : AC_AMP_SET_INPUT;
parm |= index << AC_AMP_SET_INDEX_SHIFT;
parm |= val;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm);
}
/*
* read/write AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit.
*/
int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int index)
{
struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index));
if (! info)
return 0;
get_vol_mute(codec, info, nid, ch, direction, index);
return info->vol[ch];
}
int snd_hda_codec_amp_write(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int idx, int val)
{
struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx));
if (! info)
return 0;
get_vol_mute(codec, info, nid, ch, direction, idx);
if (info->vol[ch] == val && ! codec->in_resume)
return 0;
put_vol_mute(codec, nid, ch, direction, idx, val);
info->vol[ch] = val;
return 1;
}
/*
* AMP control callbacks
*/
/* retrieve parameters from private_value */
#define get_amp_nid(kc) ((kc)->private_value & 0xffff)
#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)
#define get_amp_direction(kc) (((kc)->private_value >> 18) & 0x1)
#define get_amp_index(kc) (((kc)->private_value >> 19) & 0xf)
/* volume */
int snd_hda_mixer_amp_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
u16 nid = get_amp_nid(kcontrol);
u8 chs = get_amp_channels(kcontrol);
int dir = get_amp_direction(kcontrol);
u32 caps;
caps = query_amp_caps(codec, nid, dir);
caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; /* num steps */
if (! caps) {
printk(KERN_WARNING "hda_codec: num_steps = 0 for NID=0x%x\n", nid);
return -EINVAL;
}
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = chs == 3 ? 2 : 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = caps;
return 0;
}
int snd_hda_mixer_amp_volume_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
hda_nid_t nid = get_amp_nid(kcontrol);
int chs = get_amp_channels(kcontrol);
int dir = get_amp_direction(kcontrol);
int idx = get_amp_index(kcontrol);
long *valp = ucontrol->value.integer.value;
if (chs & 1)
*valp++ = snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x7f;
if (chs & 2)
*valp = snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x7f;
return 0;
}
int snd_hda_mixer_amp_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
hda_nid_t nid = get_amp_nid(kcontrol);
int chs = get_amp_channels(kcontrol);
int dir = get_amp_direction(kcontrol);
int idx = get_amp_index(kcontrol);
int val;
long *valp = ucontrol->value.integer.value;
int change = 0;
if (chs & 1) {
val = *valp & 0x7f;
val |= snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x80;
change = snd_hda_codec_amp_write(codec, nid, 0, dir, idx, val);
valp++;
}
if (chs & 2) {
val = *valp & 0x7f;
val |= snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x80;
change |= snd_hda_codec_amp_write(codec, nid, 1, dir, idx, val);
}
return change;
}
/* switch */
int snd_hda_mixer_amp_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
int chs = get_amp_channels(kcontrol);
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = chs == 3 ? 2 : 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
int snd_hda_mixer_amp_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
hda_nid_t nid = get_amp_nid(kcontrol);
int chs = get_amp_channels(kcontrol);
int dir = get_amp_direction(kcontrol);
int idx = get_amp_index(kcontrol);
long *valp = ucontrol->value.integer.value;
if (chs & 1)
*valp++ = (snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x80) ? 0 : 1;
if (chs & 2)
*valp = (snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x80) ? 0 : 1;
return 0;
}
int snd_hda_mixer_amp_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
hda_nid_t nid = get_amp_nid(kcontrol);
int chs = get_amp_channels(kcontrol);
int dir = get_amp_direction(kcontrol);
int idx = get_amp_index(kcontrol);
int val;
long *valp = ucontrol->value.integer.value;
int change = 0;
if (chs & 1) {
val = snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x7f;
val |= *valp ? 0 : 0x80;
change = snd_hda_codec_amp_write(codec, nid, 0, dir, idx, val);
valp++;
}
if (chs & 2) {
val = snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x7f;
val |= *valp ? 0 : 0x80;
change = snd_hda_codec_amp_write(codec, nid, 1, dir, idx, val);
}
return change;
}
/*
* SPDIF out controls
*/
static int snd_hda_spdif_mask_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int snd_hda_spdif_cmask_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
IEC958_AES0_NONAUDIO |
IEC958_AES0_CON_EMPHASIS_5015 |
IEC958_AES0_CON_NOT_COPYRIGHT;
ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY |
IEC958_AES1_CON_ORIGINAL;
return 0;
}
static int snd_hda_spdif_pmask_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
IEC958_AES0_NONAUDIO |
IEC958_AES0_PRO_EMPHASIS_5015;
return 0;
}
static int snd_hda_spdif_default_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
ucontrol->value.iec958.status[0] = codec->spdif_status & 0xff;
ucontrol->value.iec958.status[1] = (codec->spdif_status >> 8) & 0xff;
ucontrol->value.iec958.status[2] = (codec->spdif_status >> 16) & 0xff;
ucontrol->value.iec958.status[3] = (codec->spdif_status >> 24) & 0xff;
return 0;
}
static int snd_hda_spdif_default_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
hda_nid_t nid = kcontrol->private_value;
unsigned int sbits;
unsigned short val;
int change;
val = 0;
down(&codec->spdif_mutex);
sbits = codec->spdif_status & 1;
sbits |= ucontrol->value.iec958.status[0] & (IEC958_AES0_PROFESSIONAL|
IEC958_AES0_NONAUDIO);
if (sbits & IEC958_AES0_PROFESSIONAL)
val = 1 << 6;
if (sbits & IEC958_AES0_NONAUDIO)
val |= 1 << 5;
if (sbits & IEC958_AES0_PROFESSIONAL) {
sbits |= ucontrol->value.iec958.status[0] & IEC958_AES0_PRO_EMPHASIS;
if ((sbits & IEC958_AES0_PRO_EMPHASIS) == IEC958_AES0_PRO_EMPHASIS_5015)
val |= 1 << 3;
} else {
sbits |= ucontrol->value.iec958.status[0] & (IEC958_AES0_CON_EMPHASIS|
IEC958_AES0_CON_NOT_COPYRIGHT);
if ((sbits & IEC958_AES0_CON_EMPHASIS) == IEC958_AES0_CON_EMPHASIS_5015)
val |= 1 << 3;
if (! (sbits & IEC958_AES0_CON_NOT_COPYRIGHT))
val |= 1 << 4;
sbits |= ucontrol->value.iec958.status[1] << 8;
if (sbits & (IEC958_AES1_CON_ORIGINAL << 8))
val |= 1 << 7;
val |= sbits & (IEC958_AES1_CON_CATEGORY << 8);
}
change = codec->spdif_status != sbits;
codec->spdif_status = sbits;
if (change) {
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, val & 0xff);
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_2, val >> 8);
}
up(&codec->spdif_mutex);
return change;
}
static int snd_hda_spdif_out_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int snd_hda_spdif_out_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = codec->spdif_status & 1;
return 0;
}
static int snd_hda_spdif_out_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
hda_nid_t nid = kcontrol->private_value;
unsigned int sbits;
int change;
down(&codec->spdif_mutex);
sbits = codec->spdif_status & ~1;
if (ucontrol->value.integer.value[0])
sbits |= 1;
change = codec->spdif_status != sbits;
if (change) {
codec->spdif_status = sbits;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, sbits & 0xff);
}
up(&codec->spdif_mutex);
return change;
}
static snd_kcontrol_new_t dig_mixes[] = {
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
.info = snd_hda_spdif_mask_info,
.get = snd_hda_spdif_cmask_get,
},
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK),
.info = snd_hda_spdif_mask_info,
.get = snd_hda_spdif_pmask_get,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = snd_hda_spdif_mask_info,
.get = snd_hda_spdif_default_get,
.put = snd_hda_spdif_default_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),
.info = snd_hda_spdif_out_switch_info,
.get = snd_hda_spdif_out_switch_get,
.put = snd_hda_spdif_out_switch_put,
},
{ } /* end */
};
/**
* snd_hda_create_spdif_out_ctls - create SPDIF-related controls
* @codec: the HDA codec
* @nid: audio out widget NID
*
* Creates controls related with the SPDIF output.
* Called from each patch supporting the SPDIF out.
*
* Returns 0 if successful, or a negative error code.
*/
int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid)
{
int err;
snd_kcontrol_t *kctl;
snd_kcontrol_new_t *dig_mix;
for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) {
kctl = snd_ctl_new1(dig_mix, codec);
kctl->private_value = nid;
if ((err = snd_ctl_add(codec->bus->card, kctl)) < 0)
return err;
}
#if 0
/* not enabled, consumer, audio, no emphasis, original, PCM coder */
codec->spdif_status = (1 << 7) | (0x02 << 8);
#else
codec->spdif_status = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT, 0);
#endif
return 0;
}
/**
* snd_hda_build_controls - build mixer controls
* @bus: the BUS
*
* Creates mixer controls for each codec included in the bus.
*
* Returns 0 if successful, otherwise a negative error code.
*/
int snd_hda_build_controls(struct hda_bus *bus)
{
struct list_head *p;
/* build controls */
list_for_each(p, &bus->codec_list) {
struct hda_codec *codec = list_entry(p, struct hda_codec, list);
int err;
if (! codec->patch_ops.build_controls)
continue;
err = codec->patch_ops.build_controls(codec);
if (err < 0)
return err;
}
/* initialize */
list_for_each(p, &bus->codec_list) {
struct hda_codec *codec = list_entry(p, struct hda_codec, list);
int err;
if (! codec->patch_ops.init)
continue;
err = codec->patch_ops.init(codec);
if (err < 0)
return err;
}
return 0;
}
/*
* stream formats
*/
static unsigned int rate_bits[][3] = {
/* rate in Hz, ALSA rate bitmask, HDA format value */
{ 8000, SNDRV_PCM_RATE_8000, 0x0500 }, /* 1/6 x 48 */
{ 11025, SNDRV_PCM_RATE_11025, 0x4300 }, /* 1/4 x 44 */
{ 16000, SNDRV_PCM_RATE_16000, 0x0200 }, /* 1/3 x 48 */
{ 22050, SNDRV_PCM_RATE_22050, 0x4100 }, /* 1/2 x 44 */
{ 32000, SNDRV_PCM_RATE_32000, 0x0a00 }, /* 2/3 x 48 */
{ 44100, SNDRV_PCM_RATE_44100, 0x4000 }, /* 44 */
{ 48000, SNDRV_PCM_RATE_48000, 0x0000 }, /* 48 */
{ 88200, SNDRV_PCM_RATE_88200, 0x4800 }, /* 2 x 44 */
{ 96000, SNDRV_PCM_RATE_96000, 0x0800 }, /* 2 x 48 */
{ 176400, SNDRV_PCM_RATE_176400, 0x5800 },/* 4 x 44 */
{ 192000, SNDRV_PCM_RATE_192000, 0x1800 }, /* 4 x 48 */
{ 0 }
};
/**
* snd_hda_calc_stream_format - calculate format bitset
* @rate: the sample rate
* @channels: the number of channels
* @format: the PCM format (SNDRV_PCM_FORMAT_XXX)
* @maxbps: the max. bps
*
* Calculate the format bitset from the given rate, channels and th PCM format.
*
* Return zero if invalid.
*/
unsigned int snd_hda_calc_stream_format(unsigned int rate,
unsigned int channels,
unsigned int format,
unsigned int maxbps)
{
int i;
unsigned int val = 0;
for (i = 0; rate_bits[i][0]; i++)
if (rate_bits[i][0] == rate) {
val = rate_bits[i][2];
break;
}
if (! rate_bits[i][0]) {
snd_printdd("invalid rate %d\n", rate);
return 0;
}
if (channels == 0 || channels > 8) {
snd_printdd("invalid channels %d\n", channels);
return 0;
}
val |= channels - 1;
switch (snd_pcm_format_width(format)) {
case 8: val |= 0x00; break;
case 16: val |= 0x10; break;
case 20:
case 24:
case 32:
if (maxbps >= 32)
val |= 0x40;
else if (maxbps >= 24)
val |= 0x30;
else
val |= 0x20;
break;
default:
snd_printdd("invalid format width %d\n", snd_pcm_format_width(format));
return 0;
}
return val;
}
/**
* snd_hda_query_supported_pcm - query the supported PCM rates and formats
* @codec: the HDA codec
* @nid: NID to query
* @ratesp: the pointer to store the detected rate bitflags
* @formatsp: the pointer to store the detected formats
* @bpsp: the pointer to store the detected format widths
*
* Queries the supported PCM rates and formats. The NULL @ratesp, @formatsp
* or @bsps argument is ignored.
*
* Returns 0 if successful, otherwise a negative error code.
*/
int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
u32 *ratesp, u64 *formatsp, unsigned int *bpsp)
{
int i;
unsigned int val, streams;
val = 0;
if (nid != codec->afg &&
snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_FORMAT_OVRD) {
val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
if (val == -1)
return -EIO;
}
if (! val)
val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM);
if (ratesp) {
u32 rates = 0;
for (i = 0; rate_bits[i][0]; i++) {
if (val & (1 << i))
rates |= rate_bits[i][1];
}
*ratesp = rates;
}
if (formatsp || bpsp) {
u64 formats = 0;
unsigned int bps;
unsigned int wcaps;
wcaps = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
if (streams == -1)
return -EIO;
if (! streams) {
streams = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM);
if (streams == -1)
return -EIO;
}
bps = 0;
if (streams & AC_SUPFMT_PCM) {
if (val & AC_SUPPCM_BITS_8) {
formats |= SNDRV_PCM_FMTBIT_U8;
bps = 8;
}
if (val & AC_SUPPCM_BITS_16) {
formats |= SNDRV_PCM_FMTBIT_S16_LE;
bps = 16;
}
if (wcaps & AC_WCAP_DIGITAL) {
if (val & AC_SUPPCM_BITS_32)
formats |= SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE;
if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24))
formats |= SNDRV_PCM_FMTBIT_S32_LE;
if (val & AC_SUPPCM_BITS_24)
bps = 24;
else if (val & AC_SUPPCM_BITS_20)
bps = 20;
} else if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24|AC_SUPPCM_BITS_32)) {
formats |= SNDRV_PCM_FMTBIT_S32_LE;
if (val & AC_SUPPCM_BITS_32)
bps = 32;
else if (val & AC_SUPPCM_BITS_20)
bps = 20;
else if (val & AC_SUPPCM_BITS_24)
bps = 24;
}
}
else if (streams == AC_SUPFMT_FLOAT32) { /* should be exclusive */
formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
bps = 32;
} else if (streams == AC_SUPFMT_AC3) { /* should be exclusive */
/* temporary hack: we have still no proper support
* for the direct AC3 stream...
*/
formats |= SNDRV_PCM_FMTBIT_U8;
bps = 8;
}
if (formatsp)
*formatsp = formats;
if (bpsp)
*bpsp = bps;
}
return 0;
}
/**
* snd_hda_is_supported_format - check whether the given node supports the format val
*
* Returns 1 if supported, 0 if not.
*/
int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
unsigned int format)
{
int i;
unsigned int val = 0, rate;
if (nid != codec->afg &&
snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_FORMAT_OVRD) {
val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
if (val == -1)
return 0;
}
if (! val) {
val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM);
if (val == -1)
return 0;
}
rate = format & 0xffff;
for (i = 0; rate_bits[i][0]; i++)
if (rate_bits[i][2] == rate) {
if (val & (1 << i))
break;
return 0;
}
if (! rate_bits[i][0])
return 0;
val = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
if (val == -1)
return 0;
if (! val && nid != codec->afg)
val = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM);
if (! val || val == -1)
return 0;
if (val & AC_SUPFMT_PCM) {
switch (format & 0xf0) {
case 0x00:
if (! (val & AC_SUPPCM_BITS_8))
return 0;
break;
case 0x10:
if (! (val & AC_SUPPCM_BITS_16))
return 0;
break;
case 0x20:
if (! (val & AC_SUPPCM_BITS_20))
return 0;
break;
case 0x30:
if (! (val & AC_SUPPCM_BITS_24))
return 0;
break;
case 0x40:
if (! (val & AC_SUPPCM_BITS_32))
return 0;
break;
default:
return 0;
}
} else {
/* FIXME: check for float32 and AC3? */
}
return 1;
}
/*
* PCM stuff
*/
static int hda_pcm_default_open_close(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
return 0;
}
static int hda_pcm_default_prepare(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
unsigned int stream_tag,
unsigned int format,
snd_pcm_substream_t *substream)
{
snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
return 0;
}
static int hda_pcm_default_cleanup(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
snd_hda_codec_setup_stream(codec, hinfo->nid, 0, 0, 0);
return 0;
}
static int set_pcm_default_values(struct hda_codec *codec, struct hda_pcm_stream *info)
{
if (info->nid) {
/* query support PCM information from the given NID */
if (! info->rates || ! info->formats)
snd_hda_query_supported_pcm(codec, info->nid,
info->rates ? NULL : &info->rates,
info->formats ? NULL : &info->formats,
info->maxbps ? NULL : &info->maxbps);
}
if (info->ops.open == NULL)
info->ops.open = hda_pcm_default_open_close;
if (info->ops.close == NULL)
info->ops.close = hda_pcm_default_open_close;
if (info->ops.prepare == NULL) {
snd_assert(info->nid, return -EINVAL);
info->ops.prepare = hda_pcm_default_prepare;
}
if (info->ops.prepare == NULL) {
snd_assert(info->nid, return -EINVAL);
info->ops.prepare = hda_pcm_default_prepare;
}
if (info->ops.cleanup == NULL) {
snd_assert(info->nid, return -EINVAL);
info->ops.cleanup = hda_pcm_default_cleanup;
}
return 0;
}
/**
* snd_hda_build_pcms - build PCM information
* @bus: the BUS
*
* Create PCM information for each codec included in the bus.
*
* The build_pcms codec patch is requested to set up codec->num_pcms and
* codec->pcm_info properly. The array is referred by the top-level driver
* to create its PCM instances.
* The allocated codec->pcm_info should be released in codec->patch_ops.free
* callback.
*
* At least, substreams, channels_min and channels_max must be filled for
* each stream. substreams = 0 indicates that the stream doesn't exist.
* When rates and/or formats are zero, the supported values are queried
* from the given nid. The nid is used also by the default ops.prepare
* and ops.cleanup callbacks.
*
* The driver needs to call ops.open in its open callback. Similarly,
* ops.close is supposed to be called in the close callback.
* ops.prepare should be called in the prepare or hw_params callback
* with the proper parameters for set up.
* ops.cleanup should be called in hw_free for clean up of streams.
*
* This function returns 0 if successfull, or a negative error code.
*/
int snd_hda_build_pcms(struct hda_bus *bus)
{
struct list_head *p;
list_for_each(p, &bus->codec_list) {
struct hda_codec *codec = list_entry(p, struct hda_codec, list);
unsigned int pcm, s;
int err;
if (! codec->patch_ops.build_pcms)
continue;
err = codec->patch_ops.build_pcms(codec);
if (err < 0)
return err;
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
for (s = 0; s < 2; s++) {
struct hda_pcm_stream *info;
info = &codec->pcm_info[pcm].stream[s];
if (! info->substreams)
continue;
err = set_pcm_default_values(codec, info);
if (err < 0)
return err;
}
}
}
return 0;
}
/**
* snd_hda_check_board_config - compare the current codec with the config table
* @codec: the HDA codec
* @tbl: configuration table, terminated by null entries
*
* Compares the modelname or PCI subsystem id of the current codec with the
* given configuration table. If a matching entry is found, returns its
* config value (supposed to be 0 or positive).
*
* If no entries are matching, the function returns a negative value.
*/
int snd_hda_check_board_config(struct hda_codec *codec, struct hda_board_config *tbl)
{
struct hda_board_config *c;
if (codec->bus->modelname) {
for (c = tbl; c->modelname || c->pci_vendor; c++) {
if (c->modelname &&
! strcmp(codec->bus->modelname, c->modelname)) {
snd_printd(KERN_INFO "hda_codec: model '%s' is selected\n", c->modelname);
return c->config;
}
}
}
if (codec->bus->pci) {
u16 subsystem_vendor, subsystem_device;
pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_VENDOR_ID, &subsystem_vendor);
pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_ID, &subsystem_device);
for (c = tbl; c->modelname || c->pci_vendor; c++) {
if (c->pci_vendor == subsystem_vendor &&
c->pci_device == subsystem_device)
return c->config;
}
}
return -1;
}
/**
* snd_hda_add_new_ctls - create controls from the array
* @codec: the HDA codec
* @knew: the array of snd_kcontrol_new_t
*
* This helper function creates and add new controls in the given array.
* The array must be terminated with an empty entry as terminator.
*
* Returns 0 if successful, or a negative error code.
*/
int snd_hda_add_new_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew)
{
int err;
for (; knew->name; knew++) {
err = snd_ctl_add(codec->bus->card, snd_ctl_new1(knew, codec));
if (err < 0)
return err;
}
return 0;
}
/*
* input MUX helper
*/
int snd_hda_input_mux_info(const struct hda_input_mux *imux, snd_ctl_elem_info_t *uinfo)
{
unsigned int index;
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = imux->num_items;
index = uinfo->value.enumerated.item;
if (index >= imux->num_items)
index = imux->num_items - 1;
strcpy(uinfo->value.enumerated.name, imux->items[index].label);
return 0;
}
int snd_hda_input_mux_put(struct hda_codec *codec, const struct hda_input_mux *imux,
snd_ctl_elem_value_t *ucontrol, hda_nid_t nid,
unsigned int *cur_val)
{
unsigned int idx;
idx = ucontrol->value.enumerated.item[0];
if (idx >= imux->num_items)
idx = imux->num_items - 1;
if (*cur_val == idx && ! codec->in_resume)
return 0;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL,
imux->items[idx].index);
*cur_val = idx;
return 1;
}
/*
* Multi-channel / digital-out PCM helper functions
*/
/*
* open the digital out in the exclusive mode
*/
int snd_hda_multi_out_dig_open(struct hda_codec *codec, struct hda_multi_out *mout)
{
down(&codec->spdif_mutex);
if (mout->dig_out_used) {
up(&codec->spdif_mutex);
return -EBUSY; /* already being used */
}
mout->dig_out_used = HDA_DIG_EXCLUSIVE;
up(&codec->spdif_mutex);
return 0;
}
/*
* release the digital out
*/
int snd_hda_multi_out_dig_close(struct hda_codec *codec, struct hda_multi_out *mout)
{
down(&codec->spdif_mutex);
mout->dig_out_used = 0;
up(&codec->spdif_mutex);
return 0;
}
/*
* set up more restrictions for analog out
*/
int snd_hda_multi_out_analog_open(struct hda_codec *codec, struct hda_multi_out *mout,
snd_pcm_substream_t *substream)
{
substream->runtime->hw.channels_max = mout->max_channels;
return snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS, 2);
}
/*
* set up the i/o for analog out
* when the digital out is available, copy the front out to digital out, too.
*/
int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, struct hda_multi_out *mout,
unsigned int stream_tag,
unsigned int format,
snd_pcm_substream_t *substream)
{
hda_nid_t *nids = mout->dac_nids;
int chs = substream->runtime->channels;
int i;
down(&codec->spdif_mutex);
if (mout->dig_out_nid && mout->dig_out_used != HDA_DIG_EXCLUSIVE) {
if (chs == 2 &&
snd_hda_is_supported_format(codec, mout->dig_out_nid, format)) {
mout->dig_out_used = HDA_DIG_ANALOG_DUP;
/* setup digital receiver */
snd_hda_codec_setup_stream(codec, mout->dig_out_nid,
stream_tag, 0, format);
if (codec->spdif_status & AC_DIG1_NONAUDIO) {
/* non-audio SPDIF out, turning off all DACs */
for (i = 0; i < mout->num_dacs; i++)
snd_hda_codec_setup_stream(codec, nids[i], 0, 0, 0);
if (mout->hp_nid)
snd_hda_codec_setup_stream(codec, mout->hp_nid,0, 0, 0);
up(&codec->spdif_mutex);
return 0;
}
} else {
mout->dig_out_used = 0;
snd_hda_codec_setup_stream(codec, mout->dig_out_nid, 0, 0, 0);
}
}
up(&codec->spdif_mutex);
/* front */
snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag, 0, format);
if (mout->hp_nid)
/* headphone out will just decode front left/right (stereo) */
snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag, 0, format);
/* surrounds */
for (i = 0; i < mout->num_dacs; i++) {
if (i == HDA_REAR && chs == 2) /* copy front to rear */
snd_hda_codec_setup_stream(codec, nids[i], stream_tag, 0, format);
else if (chs >= (i + 1) * 2) /* independent out */
snd_hda_codec_setup_stream(codec, nids[i], stream_tag, i * 2,
format);
}
return 0;
}
/*
* clean up the setting for analog out
*/
int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, struct hda_multi_out *mout)
{
hda_nid_t *nids = mout->dac_nids;
int i;
for (i = 0; i < mout->num_dacs; i++)
snd_hda_codec_setup_stream(codec, nids[i], 0, 0, 0);
if (mout->hp_nid)
snd_hda_codec_setup_stream(codec, mout->hp_nid, 0, 0, 0);
down(&codec->spdif_mutex);
if (mout->dig_out_nid && mout->dig_out_used == HDA_DIG_ANALOG_DUP) {
snd_hda_codec_setup_stream(codec, mout->dig_out_nid, 0, 0, 0);
mout->dig_out_used = 0;
}
up(&codec->spdif_mutex);
return 0;
}
#ifdef CONFIG_PM
/*
* power management
*/
/**
* snd_hda_suspend - suspend the codecs
* @bus: the HDA bus
* @state: suspsend state
*
* Returns 0 if successful.
*/
int snd_hda_suspend(struct hda_bus *bus, unsigned int state)
{
struct list_head *p;
/* FIXME: should handle power widget capabilities */
list_for_each(p, &bus->codec_list) {
struct hda_codec *codec = list_entry(p, struct hda_codec, list);
if (codec->patch_ops.suspend)
codec->patch_ops.suspend(codec, state);
}
return 0;
}
/**
* snd_hda_resume - resume the codecs
* @bus: the HDA bus
* @state: resume state
*
* Returns 0 if successful.
*/
int snd_hda_resume(struct hda_bus *bus, unsigned int state)
{
struct list_head *p;
list_for_each(p, &bus->codec_list) {
struct hda_codec *codec = list_entry(p, struct hda_codec, list);
if (codec->patch_ops.resume)
codec->patch_ops.resume(codec, state);
}
return 0;
}
/**
* snd_hda_resume_ctls - resume controls in the new control list
* @codec: the HDA codec
* @knew: the array of snd_kcontrol_new_t
*
* This function resumes the mixer controls in the snd_kcontrol_new_t array,
* originally for snd_hda_add_new_ctls().
* The array must be terminated with an empty entry as terminator.
*/
int snd_hda_resume_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew)
{
snd_ctl_elem_value_t *val;
val = kmalloc(sizeof(*val), GFP_KERNEL);
if (! val)
return -ENOMEM;
codec->in_resume = 1;
for (; knew->name; knew++) {
int i, count;
count = knew->count ? knew->count : 1;
for (i = 0; i < count; i++) {
memset(val, 0, sizeof(*val));
val->id.iface = knew->iface;
val->id.device = knew->device;
val->id.subdevice = knew->subdevice;
strcpy(val->id.name, knew->name);
val->id.index = knew->index ? knew->index : i;
/* Assume that get callback reads only from cache,
* not accessing to the real hardware
*/
if (snd_ctl_elem_read(codec->bus->card, val) < 0)
continue;
snd_ctl_elem_write(codec->bus->card, NULL, val);
}
}
codec->in_resume = 0;
kfree(val);
return 0;
}
/**
* snd_hda_resume_spdi_out - resume the digital I/O
* @codec: the HDA codec
*/
int snd_hda_resume_spdif_out(struct hda_codec *codec)
{
return snd_hda_resume_ctls(codec, dig_mixes);
}
#endif
/*
* symbols exported for controller modules
*/
EXPORT_SYMBOL(snd_hda_codec_read);
EXPORT_SYMBOL(snd_hda_codec_write);
EXPORT_SYMBOL(snd_hda_sequence_write);
EXPORT_SYMBOL(snd_hda_get_sub_nodes);
EXPORT_SYMBOL(snd_hda_queue_unsol_event);
EXPORT_SYMBOL(snd_hda_bus_new);
EXPORT_SYMBOL(snd_hda_codec_new);
EXPORT_SYMBOL(snd_hda_codec_setup_stream);
EXPORT_SYMBOL(snd_hda_calc_stream_format);
EXPORT_SYMBOL(snd_hda_build_pcms);
EXPORT_SYMBOL(snd_hda_build_controls);
#ifdef CONFIG_PM
EXPORT_SYMBOL(snd_hda_suspend);
EXPORT_SYMBOL(snd_hda_resume);
#endif
/*
* INIT part
*/
static int __init alsa_hda_init(void)
{
return 0;
}
static void __exit alsa_hda_exit(void)
{
}
module_init(alsa_hda_init)
module_exit(alsa_hda_exit)
/*
* Universal Interface for Intel High Definition Audio Codec
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef __SOUND_HDA_CODEC_H
#define __SOUND_HDA_CODEC_H
#include <sound/info.h>
#include <sound/control.h>
#include <sound/pcm.h>
/*
* nodes
*/
#define AC_NODE_ROOT 0x00
/*
* function group types
*/
enum {
AC_GRP_AUDIO_FUNCTION = 0x01,
AC_GRP_MODEM_FUNCTION = 0x02,
};
/*
* widget types
*/
enum {
AC_WID_AUD_OUT, /* Audio Out */
AC_WID_AUD_IN, /* Audio In */
AC_WID_AUD_MIX, /* Audio Mixer */
AC_WID_AUD_SEL, /* Audio Selector */
AC_WID_PIN, /* Pin Complex */
AC_WID_POWER, /* Power */
AC_WID_VOL_KNB, /* Volume Knob */
AC_WID_BEEP, /* Beep Generator */
AC_WID_VENDOR = 0x0f /* Vendor specific */
};
/*
* GET verbs
*/
#define AC_VERB_GET_STREAM_FORMAT 0x0a00
#define AC_VERB_GET_AMP_GAIN_MUTE 0x0b00
#define AC_VERB_GET_PROC_COEF 0x0c00
#define AC_VERB_GET_COEF_INDEX 0x0d00
#define AC_VERB_PARAMETERS 0x0f00
#define AC_VERB_GET_CONNECT_SEL 0x0f01
#define AC_VERB_GET_CONNECT_LIST 0x0f02
#define AC_VERB_GET_PROC_STATE 0x0f03
#define AC_VERB_GET_SDI_SELECT 0x0f04
#define AC_VERB_GET_POWER_STATE 0x0f05
#define AC_VERB_GET_CONV 0x0f06
#define AC_VERB_GET_PIN_WIDGET_CONTROL 0x0f07
#define AC_VERB_GET_UNSOLICITED_RESPONSE 0x0f08
#define AC_VERB_GET_PIN_SENSE 0x0f09
#define AC_VERB_GET_BEEP_CONTROL 0x0f0a
#define AC_VERB_GET_EAPD_BTLENABLE 0x0f0c
#define AC_VERB_GET_DIGI_CONVERT 0x0f0d
#define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f
/* f10-f1a: GPIO */
#define AC_VERB_GET_CONFIG_DEFAULT 0x0f1c
/*
* SET verbs
*/
#define AC_VERB_SET_STREAM_FORMAT 0x200
#define AC_VERB_SET_AMP_GAIN_MUTE 0x300
#define AC_VERB_SET_PROC_COEF 0x400
#define AC_VERB_SET_COEF_INDEX 0x500
#define AC_VERB_SET_CONNECT_SEL 0x701
#define AC_VERB_SET_PROC_STATE 0x703
#define AC_VERB_SET_SDI_SELECT 0x704
#define AC_VERB_SET_POWER_STATE 0x705
#define AC_VERB_SET_CHANNEL_STREAMID 0x706
#define AC_VERB_SET_PIN_WIDGET_CONTROL 0x707
#define AC_VERB_SET_UNSOLICITED_ENABLE 0x708
#define AC_VERB_SET_PIN_SENSE 0x709
#define AC_VERB_SET_BEEP_CONTROL 0x70a
#define AC_VERB_SET_EAPD_BTLENALBE 0x70c
#define AC_VERB_SET_DIGI_CONVERT_1 0x70d
#define AC_VERB_SET_DIGI_CONVERT_2 0x70e
#define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f
#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 0x71c
#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_1 0x71d
#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_2 0x71e
#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_3 0x71f
#define AC_VERB_SET_CODEC_RESET 0x7ff
/*
* Parameter IDs
*/
#define AC_PAR_VENDOR_ID 0x00
#define AC_PAR_SUBSYSTEM_ID 0x01
#define AC_PAR_REV_ID 0x02
#define AC_PAR_NODE_COUNT 0x04
#define AC_PAR_FUNCTION_TYPE 0x05
#define AC_PAR_AUDIO_FG_CAP 0x08
#define AC_PAR_AUDIO_WIDGET_CAP 0x09
#define AC_PAR_PCM 0x0a
#define AC_PAR_STREAM 0x0b
#define AC_PAR_PIN_CAP 0x0c
#define AC_PAR_AMP_IN_CAP 0x0d
#define AC_PAR_CONNLIST_LEN 0x0e
#define AC_PAR_POWER_STATE 0x0f
#define AC_PAR_PROC_CAP 0x10
#define AC_PAR_GPIO_CAP 0x11
#define AC_PAR_AMP_OUT_CAP 0x12
/*
* AC_VERB_PARAMETERS results (32bit)
*/
/* Function Group Type */
#define AC_FGT_TYPE (0xff<<0)
#define AC_FGT_TYPE_SHIFT 0
#define AC_FGT_UNSOL_CAP (1<<8)
/* Audio Function Group Capabilities */
#define AC_AFG_OUT_DELAY (0xf<<0)
#define AC_AFG_IN_DELAY (0xf<<8)
#define AC_AFG_BEEP_GEN (1<<16)
/* Audio Widget Capabilities */
#define AC_WCAP_STEREO (1<<0) /* stereo I/O */
#define AC_WCAP_IN_AMP (1<<1) /* AMP-in present */
#define AC_WCAP_OUT_AMP (1<<2) /* AMP-out present */
#define AC_WCAP_AMP_OVRD (1<<3) /* AMP-parameter override */
#define AC_WCAP_FORMAT_OVRD (1<<4) /* format override */
#define AC_WCAP_STRIPE (1<<5) /* stripe */
#define AC_WCAP_PROC_WID (1<<6) /* Proc Widget */
#define AC_WCAP_UNSOL_CAP (1<<7) /* Unsol capable */
#define AC_WCAP_CONN_LIST (1<<8) /* connection list */
#define AC_WCAP_DIGITAL (1<<9) /* digital I/O */
#define AC_WCAP_POWER (1<<10) /* power control */
#define AC_WCAP_LR_SWAP (1<<11) /* L/R swap */
#define AC_WCAP_DELAY (0xf<<16)
#define AC_WCAP_DELAY_SHIFT 16
#define AC_WCAP_TYPE (0xf<<20)
#define AC_WCAP_TYPE_SHIFT 20
/* supported PCM rates and bits */
#define AC_SUPPCM_RATES (0xfff << 0)
#define AC_SUPPCM_BITS_8 (1<<16)
#define AC_SUPPCM_BITS_16 (1<<17)
#define AC_SUPPCM_BITS_20 (1<<18)
#define AC_SUPPCM_BITS_24 (1<<19)
#define AC_SUPPCM_BITS_32 (1<<20)
/* supported PCM stream format */
#define AC_SUPFMT_PCM (1<<0)
#define AC_SUPFMT_FLOAT32 (1<<1)
#define AC_SUPFMT_AC3 (1<<2)
/* Pin widget capabilies */
#define AC_PINCAP_IMP_SENSE (1<<0) /* impedance sense capable */
#define AC_PINCAP_TRIG_REQ (1<<1) /* trigger required */
#define AC_PINCAP_PRES_DETECT (1<<2) /* presence detect capable */
#define AC_PINCAP_HP_DRV (1<<3) /* headphone drive capable */
#define AC_PINCAP_OUT (1<<4) /* output capable */
#define AC_PINCAP_IN (1<<5) /* input capable */
#define AC_PINCAP_BALANCE (1<<6) /* balanced I/O capable */
#define AC_PINCAP_VREF (7<<8)
#define AC_PINCAP_VREF_SHIFT 8
#define AC_PINCAP_EAPD (1<<16) /* EAPD capable */
/* Vref status (used in pin cap and pin ctl) */
#define AC_PIN_VREF_HIZ (1<<0) /* Hi-Z */
#define AC_PIN_VREF_50 (1<<1) /* 50% */
#define AC_PIN_VREF_GRD (1<<2) /* ground */
#define AC_PIN_VREF_80 (1<<4) /* 80% */
#define AC_PIN_VREF_100 (1<<5) /* 100% */
/* Amplifier capabilities */
#define AC_AMPCAP_OFFSET (0x7f<<0) /* 0dB offset */
#define AC_AMPCAP_OFFSET_SHIFT 0
#define AC_AMPCAP_NUM_STEPS (0x7f<<8) /* number of steps */
#define AC_AMPCAP_NUM_STEPS_SHIFT 8
#define AC_AMPCAP_STEP_SIZE (0x7f<<16) /* step size 0-32dB in 0.25dB */
#define AC_AMPCAP_STEP_SIZE_SHIFT 16
#define AC_AMPCAP_MUTE (1<<31) /* mute capable */
#define AC_AMPCAP_MUTE_SHIFT 31
/* Connection list */
#define AC_CLIST_LENGTH (0x7f<<0)
#define AC_CLIST_LONG (1<<7)
/* Supported power status */
#define AC_PWRST_D0SUP (1<<0)
#define AC_PWRST_D1SUP (1<<1)
#define AC_PWRST_D2SUP (1<<2)
#define AC_PWRST_D3SUP (1<<3)
/* Processing capabilies */
#define AC_PCAP_BENIGN (1<<0)
#define AC_PCAP_NUM_COEF (0xff<<8)
/* Volume knobs capabilities */
#define AC_KNBCAP_NUM_STEPS (0x7f<<0)
#define AC_KNBCAP_DELTA (1<<8)
/*
* Control Parameters
*/
/* Amp gain/mute */
#define AC_AMP_MUTE (1<<8)
#define AC_AMP_GAIN (0x7f)
#define AC_AMP_GET_INDEX (0xf<<0)
#define AC_AMP_GET_LEFT (1<<13)
#define AC_AMP_GET_RIGHT (0<<13)
#define AC_AMP_GET_OUTPUT (1<<15)
#define AC_AMP_GET_INPUT (0<<15)
#define AC_AMP_SET_INDEX (0xf<<8)
#define AC_AMP_SET_INDEX_SHIFT 8
#define AC_AMP_SET_RIGHT (1<<12)
#define AC_AMP_SET_LEFT (1<<13)
#define AC_AMP_SET_INPUT (1<<14)
#define AC_AMP_SET_OUTPUT (1<<15)
/* DIGITAL1 bits */
#define AC_DIG1_ENABLE (1<<0)
#define AC_DIG1_V (1<<1)
#define AC_DIG1_VCFG (1<<2)
#define AC_DIG1_EMPHASIS (1<<3)
#define AC_DIG1_COPYRIGHT (1<<4)
#define AC_DIG1_NONAUDIO (1<<5)
#define AC_DIG1_PROFESSIONAL (1<<6)
#define AC_DIG1_LEVEL (1<<7)
/* Pin widget control - 8bit */
#define AC_PINCTL_VREFEN (0x7<<0)
#define AC_PINCTL_IN_EN (1<<5)
#define AC_PINCTL_OUT_EN (1<<6)
#define AC_PINCTL_HP_EN (1<<7)
/* configuration default - 32bit */
#define AC_DEFCFG_SEQUENCE (0xf<<0)
#define AC_DEFCFG_DEF_ASSOC (0xf<<4)
#define AC_DEFCFG_MISC (0xf<<8)
#define AC_DEFCFG_COLOR (0xf<<12)
#define AC_DEFCFG_COLOR_SHIFT 12
#define AC_DEFCFG_CONN_TYPE (0xf<<16)
#define AC_DEFCFG_CONN_TYPE_SHIFT 16
#define AC_DEFCFG_DEVICE (0xf<<20)
#define AC_DEFCFG_DEVICE_SHIFT 20
#define AC_DEFCFG_LOCATION (0x3f<<24)
#define AC_DEFCFG_LOCATION_SHIFT 24
#define AC_DEFCFG_PORT_CONN (0x3<<30)
#define AC_DEFCFG_PORT_CONN_SHIFT 30
/* device device types (0x0-0xf) */
enum {
AC_JACK_LINE_OUT,
AC_JACK_SPEAKER,
AC_JACK_HP_OUT,
AC_JACK_CD,
AC_JACK_SPDIF_OUT,
AC_JACK_DIG_OTHER_OUT,
AC_JACK_MODEM_LINE_SIDE,
AC_JACK_MODEM_HAND_SIDE,
AC_JACK_LINE_IN,
AC_JACK_AUX,
AC_JACK_MIC_IN,
AC_JACK_TELEPHONY,
AC_JACK_SPDIF_IN,
AC_JACK_DIG_OTHER_IN,
AC_JACK_OTHER = 0xf,
};
/* jack connection types (0x0-0xf) */
enum {
AC_JACK_CONN_UNKNOWN,
AC_JACK_CONN_1_8,
AC_JACK_CONN_1_4,
AC_JACK_CONN_ATAPI,
AC_JACK_CONN_RCA,
AC_JACK_CONN_OPTICAL,
AC_JACK_CONN_OTHER_DIGITAL,
AC_JACK_CONN_OTHER_ANALOG,
AC_JACK_CONN_DIN,
AC_JACK_CONN_XLR,
AC_JACK_CONN_RJ11,
AC_JACK_CONN_COMB,
AC_JACK_CONN_OTHER = 0xf,
};
/* jack colors (0x0-0xf) */
enum {
AC_JACK_COLOR_UNKNOWN,
AC_JACK_COLOR_BLACK,
AC_JACK_COLOR_GREY,
AC_JACK_COLOR_BLUE,
AC_JACK_COLOR_GREEN,
AC_JACK_COLOR_RED,
AC_JACK_COLOR_ORANGE,
AC_JACK_COLOR_YELLOW,
AC_JACK_COLOR_PURPLE,
AC_JACK_COLOR_PINK,
AC_JACK_COLOR_WHITE = 0xe,
AC_JACK_COLOR_OTHER,
};
/* Jack location (0x0-0x3f) */
/* common case */
enum {
AC_JACK_LOC_NONE,
AC_JACK_LOC_REAR,
AC_JACK_LOC_FRONT,
AC_JACK_LOC_LEFT,
AC_JACK_LOC_RIGHT,
AC_JACK_LOC_TOP,
AC_JACK_LOC_BOTTOM,
};
/* bits 4-5 */
enum {
AC_JACK_LOC_EXTERNAL = 0x00,
AC_JACK_LOC_INTERNAL = 0x10,
AC_JACK_LOC_SEPARATE = 0x20,
AC_JACK_LOC_OTHER = 0x30,
};
enum {
/* external on primary chasis */
AC_JACK_LOC_REAR_PANEL = 0x07,
AC_JACK_LOC_DRIVE_BAY,
/* internal */
AC_JACK_LOC_RISER = 0x17,
AC_JACK_LOC_HDMI,
AC_JACK_LOC_ATAPI,
/* others */
AC_JACK_LOC_MOBILE_IN = 0x37,
AC_JACK_LOC_MOBILE_OUT,
};
/* Port connectivity (0-3) */
enum {
AC_JACK_PORT_COMPLEX,
AC_JACK_PORT_NONE,
AC_JACK_PORT_FIXED,
AC_JACK_PORT_BOTH,
};
/* max. connections to a widget */
#define HDA_MAX_CONNECTIONS 16
/* max. codec address */
#define HDA_MAX_CODEC_ADDRESS 0x0f
/*
* Structures
*/
struct hda_bus;
struct hda_codec;
struct hda_pcm;
struct hda_pcm_stream;
struct hda_bus_unsolicited;
/* NID type */
typedef u16 hda_nid_t;
/* bus operators */
struct hda_bus_ops {
/* send a single command */
int (*command)(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int parm);
/* get a response from the last command */
unsigned int (*get_response)(struct hda_codec *codec);
/* free the private data */
void (*private_free)(struct hda_bus *);
};
/* template to pass to the bus constructor */
struct hda_bus_template {
void *private_data;
struct pci_dev *pci;
const char *modelname;
struct hda_bus_ops ops;
};
/*
* codec bus
*
* each controller needs to creata a hda_bus to assign the accessor.
* A hda_bus contains several codecs in the list codec_list.
*/
struct hda_bus {
snd_card_t *card;
/* copied from template */
void *private_data;
struct pci_dev *pci;
const char *modelname;
struct hda_bus_ops ops;
/* codec linked list */
struct list_head codec_list;
struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS]; /* caddr -> codec */
struct semaphore cmd_mutex;
/* unsolicited event queue */
struct hda_bus_unsolicited *unsol;
snd_info_entry_t *proc;
};
/*
* codec preset
*
* Known codecs have the patch to build and set up the controls/PCMs
* better than the generic parser.
*/
struct hda_codec_preset {
unsigned int id;
unsigned int mask;
unsigned int subs;
unsigned int subs_mask;
unsigned int rev;
const char *name;
int (*patch)(struct hda_codec *codec);
};
/* ops set by the preset patch */
struct hda_codec_ops {
int (*build_controls)(struct hda_codec *codec);
int (*build_pcms)(struct hda_codec *codec);
int (*init)(struct hda_codec *codec);
void (*free)(struct hda_codec *codec);
void (*unsol_event)(struct hda_codec *codec, unsigned int res);
#ifdef CONFIG_PM
int (*suspend)(struct hda_codec *codec, unsigned int state);
int (*resume)(struct hda_codec *codec, unsigned int state);
#endif
};
/* record for amp information cache */
struct hda_amp_info {
u32 key; /* hash key */
u32 amp_caps; /* amp capabilities */
u16 vol[2]; /* current volume & mute*/
u16 status; /* update flag */
u16 next; /* next link */
};
/* PCM callbacks */
struct hda_pcm_ops {
int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec,
snd_pcm_substream_t *substream);
int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec,
snd_pcm_substream_t *substream);
int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec,
unsigned int stream_tag, unsigned int format,
snd_pcm_substream_t *substream);
int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec,
snd_pcm_substream_t *substream);
};
/* PCM information for each substream */
struct hda_pcm_stream {
unsigned int substreams; /* number of substreams, 0 = not exist */
unsigned int channels_min; /* min. number of channels */
unsigned int channels_max; /* max. number of channels */
hda_nid_t nid; /* default NID to query rates/formats/bps, or set up */
u32 rates; /* supported rates */
u64 formats; /* supported formats (SNDRV_PCM_FMTBIT_) */
unsigned int maxbps; /* supported max. bit per sample */
struct hda_pcm_ops ops;
};
/* for PCM creation */
struct hda_pcm {
char *name;
struct hda_pcm_stream stream[2];
};
/* codec information */
struct hda_codec {
struct hda_bus *bus;
unsigned int addr; /* codec addr*/
struct list_head list; /* list point */
hda_nid_t afg; /* AFG node id */
/* ids */
u32 vendor_id;
u32 subsystem_id;
u32 revision_id;
/* detected preset */
const struct hda_codec_preset *preset;
/* set by patch */
struct hda_codec_ops patch_ops;
/* resume phase - all controls should update even if
* the values are not changed
*/
unsigned int in_resume;
/* PCM to create, set by patch_ops.build_pcms callback */
unsigned int num_pcms;
struct hda_pcm *pcm_info;
/* codec specific info */
void *spec;
/* hash for amp access */
u16 amp_hash[32];
int num_amp_entries;
struct hda_amp_info amp_info[128]; /* big enough? */
struct semaphore spdif_mutex;
unsigned int spdif_status;
};
/* direction */
enum {
HDA_INPUT, HDA_OUTPUT
};
/*
* constructors
*/
int snd_hda_bus_new(snd_card_t *card, const struct hda_bus_template *temp,
struct hda_bus **busp);
int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
struct hda_codec **codecp);
/*
* low level functions
*/
unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int parm);
int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int parm);
#define snd_hda_param_read(codec, nid, param) snd_hda_codec_read(codec, nid, 0, AC_VERB_PARAMETERS, param)
int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid, hda_nid_t *start_id);
int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid, hda_nid_t *conn_list, int max_conns);
struct hda_verb {
hda_nid_t nid;
u32 verb;
u32 param;
};
void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq);
/* unsolicited event */
int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex);
/*
* Mixer
*/
int snd_hda_build_controls(struct hda_bus *bus);
/*
* PCM
*/
int snd_hda_build_pcms(struct hda_bus *bus);
void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stream_tag,
int channel_id, int format);
unsigned int snd_hda_calc_stream_format(unsigned int rate, unsigned int channels,
unsigned int format, unsigned int maxbps);
int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
u32 *ratesp, u64 *formatsp, unsigned int *bpsp);
int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
unsigned int format);
/*
* Misc
*/
void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen);
/*
* power management
*/
#ifdef CONFIG_PM
int snd_hda_suspend(struct hda_bus *bus, unsigned int state);
int snd_hda_resume(struct hda_bus *bus, unsigned int state);
#endif
#endif /* __SOUND_HDA_CODEC_H */
/*
* Universal Interface for Intel High Definition Audio Codec
*
* Generic widget tree parser
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*
* This driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This driver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <sound/core.h>
#include "hda_codec.h"
#include "hda_local.h"
/* widget node for parsing */
struct hda_gnode {
hda_nid_t nid; /* NID of this widget */
unsigned short nconns; /* number of input connections */
hda_nid_t conn_list[HDA_MAX_CONNECTIONS]; /* input connections */
unsigned int wid_caps; /* widget capabilities */
unsigned char type; /* widget type */
unsigned char pin_ctl; /* pin controls */
unsigned char checked; /* the flag indicates that the node is already parsed */
unsigned int pin_caps; /* pin widget capabilities */
unsigned int def_cfg; /* default configuration */
unsigned int amp_out_caps; /* AMP out capabilities */
unsigned int amp_in_caps; /* AMP in capabilities */
struct list_head list;
};
/* pathc-specific record */
struct hda_gspec {
struct hda_gnode *dac_node; /* DAC node */
struct hda_gnode *out_pin_node; /* Output pin (Line-Out) node */
struct hda_gnode *pcm_vol_node; /* Node for PCM volume */
unsigned int pcm_vol_index; /* connection of PCM volume */
struct hda_gnode *adc_node; /* ADC node */
struct hda_gnode *cap_vol_node; /* Node for capture volume */
unsigned int cur_cap_src; /* current capture source */
struct hda_input_mux input_mux;
char cap_labels[HDA_MAX_NUM_INPUTS][16];
unsigned int def_amp_in_caps;
unsigned int def_amp_out_caps;
struct hda_pcm pcm_rec; /* PCM information */
struct list_head nid_list; /* list of widgets */
};
/*
* retrieve the default device type from the default config value
*/
#define get_defcfg_type(node) (((node)->def_cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT)
#define get_defcfg_location(node) (((node)->def_cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT)
/*
* destructor
*/
static void snd_hda_generic_free(struct hda_codec *codec)
{
struct hda_gspec *spec = codec->spec;
struct list_head *p, *n;
if (! spec)
return;
/* free all widgets */
list_for_each_safe(p, n, &spec->nid_list) {
struct hda_gnode *node = list_entry(p, struct hda_gnode, list);
kfree(node);
}
kfree(spec);
}
/*
* add a new widget node and read its attributes
*/
static int add_new_node(struct hda_codec *codec, struct hda_gspec *spec, hda_nid_t nid)
{
struct hda_gnode *node;
int nconns;
node = kcalloc(1, sizeof(*node), GFP_KERNEL);
if (node == NULL)
return -ENOMEM;
node->nid = nid;
nconns = snd_hda_get_connections(codec, nid, node->conn_list, HDA_MAX_CONNECTIONS);
if (nconns < 0) {
kfree(node);
return nconns;
}
node->nconns = nconns;
node->wid_caps = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
node->type = (node->wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
if (node->type == AC_WID_PIN) {
node->pin_caps = snd_hda_param_read(codec, node->nid, AC_PAR_PIN_CAP);
node->pin_ctl = snd_hda_codec_read(codec, node->nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
node->def_cfg = snd_hda_codec_read(codec, node->nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
}
if (node->wid_caps & AC_WCAP_OUT_AMP) {
if (node->wid_caps & AC_WCAP_AMP_OVRD)
node->amp_out_caps = snd_hda_param_read(codec, node->nid, AC_PAR_AMP_OUT_CAP);
if (! node->amp_out_caps)
node->amp_out_caps = spec->def_amp_out_caps;
}
if (node->wid_caps & AC_WCAP_IN_AMP) {
if (node->wid_caps & AC_WCAP_AMP_OVRD)
node->amp_in_caps = snd_hda_param_read(codec, node->nid, AC_PAR_AMP_IN_CAP);
if (! node->amp_in_caps)
node->amp_in_caps = spec->def_amp_in_caps;
}
list_add_tail(&node->list, &spec->nid_list);
return 0;
}
/*
* build the AFG subtree
*/
static int build_afg_tree(struct hda_codec *codec)
{
struct hda_gspec *spec = codec->spec;
int i, nodes, err;
hda_nid_t nid;
snd_assert(spec, return -EINVAL);
spec->def_amp_out_caps = snd_hda_param_read(codec, codec->afg, AC_PAR_AMP_OUT_CAP);
spec->def_amp_in_caps = snd_hda_param_read(codec, codec->afg, AC_PAR_AMP_IN_CAP);
nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
if (! nid || nodes < 0) {
printk(KERN_ERR "Invalid AFG subtree\n");
return -EINVAL;
}
/* parse all nodes belonging to the AFG */
for (i = 0; i < nodes; i++, nid++) {
if ((err = add_new_node(codec, spec, nid)) < 0)
return err;
}
return 0;
}
/*
* look for the node record for the given NID
*/
/* FIXME: should avoid the braindead linear search */
static struct hda_gnode *hda_get_node(struct hda_gspec *spec, hda_nid_t nid)
{
struct list_head *p;
struct hda_gnode *node;
list_for_each(p, &spec->nid_list) {
node = list_entry(p, struct hda_gnode, list);
if (node->nid == nid)
return node;
}
return NULL;
}
/*
* unmute (and set max vol) the output amplifier
*/
static int unmute_output(struct hda_codec *codec, struct hda_gnode *node)
{
unsigned int val, ofs;
snd_printdd("UNMUTE OUT: NID=0x%x\n", node->nid);
val = (node->amp_out_caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
ofs = (node->amp_out_caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
if (val >= ofs)
val -= ofs;
val |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
val |= AC_AMP_SET_OUTPUT;
return snd_hda_codec_write(codec, node->nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, val);
}
/*
* unmute (and set max vol) the input amplifier
*/
static int unmute_input(struct hda_codec *codec, struct hda_gnode *node, unsigned int index)
{
unsigned int val, ofs;
snd_printdd("UNMUTE IN: NID=0x%x IDX=0x%x\n", node->nid, index);
val = (node->amp_in_caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
ofs = (node->amp_in_caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
if (val >= ofs)
val -= ofs;
val |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
val |= AC_AMP_SET_INPUT;
// awk added - fixed to allow unmuting of indexed amps
val |= index << AC_AMP_SET_INDEX_SHIFT;
return snd_hda_codec_write(codec, node->nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, val);
}
/*
* select the input connection of the given node.
*/
static int select_input_connection(struct hda_codec *codec, struct hda_gnode *node,
unsigned int index)
{
snd_printdd("CONNECT: NID=0x%x IDX=0x%x\n", node->nid, index);
return snd_hda_codec_write(codec, node->nid, 0, AC_VERB_SET_CONNECT_SEL, index);
}
/*
* clear checked flag of each node in the node list
*/
static void clear_check_flags(struct hda_gspec *spec)
{
struct list_head *p;
struct hda_gnode *node;
list_for_each(p, &spec->nid_list) {
node = list_entry(p, struct hda_gnode, list);
node->checked = 0;
}
}
/*
* parse the output path recursively until reach to an audio output widget
*
* returns 0 if not found, 1 if found, or a negative error code.
*/
static int parse_output_path(struct hda_codec *codec, struct hda_gspec *spec,
struct hda_gnode *node)
{
int i, err;
struct hda_gnode *child;
if (node->checked)
return 0;
node->checked = 1;
if (node->type == AC_WID_AUD_OUT) {
snd_printdd("AUD_OUT found %x\n", node->nid);
if (spec->dac_node) {
/* already DAC node is assigned, just unmute & connect */
return node == spec->dac_node;
}
spec->dac_node = node;
if (node->wid_caps & AC_WCAP_OUT_AMP) {
spec->pcm_vol_node = node;
spec->pcm_vol_index = 0;
}
return 1; /* found */
}
for (i = 0; i < node->nconns; i++) {
child = hda_get_node(spec, node->conn_list[i]);
if (! child)
continue;
err = parse_output_path(codec, spec, child);
if (err < 0)
return err;
else if (err > 0) {
/* found one,
* select the path, unmute both input and output
*/
if (node->nconns > 1)
select_input_connection(codec, node, i);
unmute_input(codec, node, i);
unmute_output(codec, node);
if (! spec->pcm_vol_node) {
if (node->wid_caps & AC_WCAP_IN_AMP) {
spec->pcm_vol_node = node;
spec->pcm_vol_index = i;
} else if (node->wid_caps & AC_WCAP_OUT_AMP) {
spec->pcm_vol_node = node;
spec->pcm_vol_index = 0;
}
}
return 1;
}
}
return 0;
}
/*
* Look for the output PIN widget with the given jack type
* and parse the output path to that PIN.
*
* Returns the PIN node when the path to DAC is established.
*/
static struct hda_gnode *parse_output_jack(struct hda_codec *codec,
struct hda_gspec *spec,
int jack_type)
{
struct list_head *p;
struct hda_gnode *node;
int err;
list_for_each(p, &spec->nid_list) {
node = list_entry(p, struct hda_gnode, list);
if (node->type != AC_WID_PIN)
continue;
/* output capable? */
if (! (node->pin_caps & AC_PINCAP_OUT))
continue;
if (jack_type >= 0) {
if (jack_type != get_defcfg_type(node))
continue;
} else {
/* output as default? */
if (! (node->pin_ctl & AC_PINCTL_OUT_EN))
continue;
}
clear_check_flags(spec);
err = parse_output_path(codec, spec, node);
if (err < 0)
return NULL;
else if (err > 0) {
/* unmute the PIN output */
unmute_output(codec, node);
/* set PIN-Out enable */
snd_hda_codec_write(codec, node->nid, 0,
AC_VERB_SET_PIN_WIDGET_CONTROL,
AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN);
return node;
}
}
return NULL;
}
/*
* parse outputs
*/
static int parse_output(struct hda_codec *codec)
{
struct hda_gspec *spec = codec->spec;
struct hda_gnode *node;
/*
* Look for the output PIN widget
*/
/* first, look for the line-out pin */
node = parse_output_jack(codec, spec, AC_JACK_LINE_OUT);
if (node) /* found, remember the PIN node */
spec->out_pin_node = node;
/* look for the HP-out pin */
node = parse_output_jack(codec, spec, AC_JACK_HP_OUT);
if (node) {
if (! spec->out_pin_node)
spec->out_pin_node = node;
}
if (! spec->out_pin_node) {
/* no line-out or HP pins found,
* then choose for the first output pin
*/
spec->out_pin_node = parse_output_jack(codec, spec, -1);
if (! spec->out_pin_node)
snd_printd("hda_generic: no proper output path found\n");
}
return 0;
}
/*
* input MUX
*/
/* control callbacks */
static int capture_source_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_gspec *spec = codec->spec;
return snd_hda_input_mux_info(&spec->input_mux, uinfo);
}
static int capture_source_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_gspec *spec = codec->spec;
ucontrol->value.enumerated.item[0] = spec->cur_cap_src;
return 0;
}
static int capture_source_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_gspec *spec = codec->spec;
return snd_hda_input_mux_put(codec, &spec->input_mux, ucontrol,
spec->adc_node->nid, &spec->cur_cap_src);
}
/*
* return the string name of the given input PIN widget
*/
static const char *get_input_type(struct hda_gnode *node, unsigned int *pinctl)
{
unsigned int location = get_defcfg_location(node);
switch (get_defcfg_type(node)) {
case AC_JACK_LINE_IN:
if ((location & 0x0f) == AC_JACK_LOC_FRONT)
return "Front Line";
return "Line";
case AC_JACK_CD:
if (pinctl)
*pinctl |= AC_PIN_VREF_GRD;
return "CD";
case AC_JACK_AUX:
if ((location & 0x0f) == AC_JACK_LOC_FRONT)
return "Front Aux";
return "Aux";
case AC_JACK_MIC_IN:
if ((location & 0x0f) == AC_JACK_LOC_FRONT)
return "Front Mic";
return "Mic";
case AC_JACK_SPDIF_IN:
return "SPDIF";
case AC_JACK_DIG_OTHER_IN:
return "Digital";
}
return NULL;
}
/*
* parse the nodes recursively until reach to the input PIN
*
* returns 0 if not found, 1 if found, or a negative error code.
*/
static int parse_adc_sub_nodes(struct hda_codec *codec, struct hda_gspec *spec,
struct hda_gnode *node)
{
int i, err;
unsigned int pinctl;
char *label;
const char *type;
if (node->checked)
return 0;
node->checked = 1;
if (node->type != AC_WID_PIN) {
for (i = 0; i < node->nconns; i++) {
struct hda_gnode *child;
child = hda_get_node(spec, node->conn_list[i]);
if (! child)
continue;
err = parse_adc_sub_nodes(codec, spec, child);
if (err < 0)
return err;
if (err > 0) {
/* found one,
* select the path, unmute both input and output
*/
if (node->nconns > 1)
select_input_connection(codec, node, i);
unmute_input(codec, node, i);
unmute_output(codec, node);
return err;
}
}
return 0;
}
/* input capable? */
if (! (node->pin_caps & AC_PINCAP_IN))
return 0;
if (spec->input_mux.num_items >= HDA_MAX_NUM_INPUTS) {
snd_printk(KERN_ERR "hda_generic: Too many items for capture\n");
return -EINVAL;
}
pinctl = AC_PINCTL_IN_EN;
/* create a proper capture source label */
type = get_input_type(node, &pinctl);
if (! type) {
/* input as default? */
if (! (node->pin_ctl & AC_PINCTL_IN_EN))
return 0;
type = "Input";
}
label = spec->cap_labels[spec->input_mux.num_items];
strcpy(label, type);
spec->input_mux.items[spec->input_mux.num_items].label = label;
/* unmute the PIN external input */
unmute_input(codec, node, 0); /* index = 0? */
/* set PIN-In enable */
snd_hda_codec_write(codec, node->nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pinctl);
return 1; /* found */
}
/*
* parse input
*/
static int parse_input_path(struct hda_codec *codec, struct hda_gnode *adc_node)
{
struct hda_gspec *spec = codec->spec;
struct hda_gnode *node;
int i, err;
snd_printdd("AUD_IN = %x\n", adc_node->nid);
clear_check_flags(spec);
// awk added - fixed no recording due to muted widget
unmute_input(codec, adc_node, 0);
/*
* check each connection of the ADC
* if it reaches to a proper input PIN, add the path as the
* input path.
*/
for (i = 0; i < adc_node->nconns; i++) {
node = hda_get_node(spec, adc_node->conn_list[i]);
if (! node)
continue;
err = parse_adc_sub_nodes(codec, spec, node);
if (err < 0)
return err;
else if (err > 0) {
struct hda_input_mux_item *csrc = &spec->input_mux.items[spec->input_mux.num_items];
char *buf = spec->cap_labels[spec->input_mux.num_items];
int ocap;
for (ocap = 0; ocap < spec->input_mux.num_items; ocap++) {
if (! strcmp(buf, spec->cap_labels[ocap])) {
/* same label already exists,
* put the index number to be unique
*/
sprintf(buf, "%s %d", spec->cap_labels[ocap],
spec->input_mux.num_items);
}
}
csrc->index = i;
spec->input_mux.num_items++;
}
}
if (! spec->input_mux.num_items)
return 0; /* no input path found... */
snd_printdd("[Capture Source] NID=0x%x, #SRC=%d\n", adc_node->nid, spec->input_mux.num_items);
for (i = 0; i < spec->input_mux.num_items; i++)
snd_printdd(" [%s] IDX=0x%x\n", spec->input_mux.items[i].label,
spec->input_mux.items[i].index);
spec->adc_node = adc_node;
return 1;
}
/*
* parse input
*/
static int parse_input(struct hda_codec *codec)
{
struct hda_gspec *spec = codec->spec;
struct list_head *p;
struct hda_gnode *node;
int err;
/*
* At first we look for an audio input widget.
* If it reaches to certain input PINs, we take it as the
* input path.
*/
list_for_each(p, &spec->nid_list) {
node = list_entry(p, struct hda_gnode, list);
if (node->type == AC_WID_AUD_IN) {
err = parse_input_path(codec, node);
if (err < 0)
return err;
else if (err > 0)
return 0;
}
}
snd_printd("hda_generic: no proper input path found\n");
return 0;
}
/*
* create mixer controls if possible
*/
#define DIR_OUT 0x1
#define DIR_IN 0x2
static int create_mixer(struct hda_codec *codec, struct hda_gnode *node,
unsigned int index, const char *type, const char *dir_sfx)
{
char name[32];
int err;
int created = 0;
if (type)
sprintf(name, "%s %s Switch", type, dir_sfx);
else
sprintf(name, "%s Switch", dir_sfx);
if ((node->wid_caps & AC_WCAP_IN_AMP) &&
(node->amp_in_caps & AC_AMPCAP_MUTE)) {
snd_kcontrol_new_t knew =
HDA_CODEC_MUTE(name, node->nid, index, HDA_INPUT);
snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index);
if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
return err;
created = 1;
} else if ((node->wid_caps & AC_WCAP_OUT_AMP) &&
(node->amp_out_caps & AC_AMPCAP_MUTE)) {
snd_kcontrol_new_t knew =
HDA_CODEC_MUTE(name, node->nid, 0, HDA_OUTPUT);
snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid);
if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
return err;
created = 1;
}
if (type)
sprintf(name, "%s %s Volume", type, dir_sfx);
else
sprintf(name, "%s Volume", dir_sfx);
if ((node->wid_caps & AC_WCAP_IN_AMP) &&
(node->amp_in_caps & AC_AMPCAP_NUM_STEPS)) {
snd_kcontrol_new_t knew =
HDA_CODEC_VOLUME(name, node->nid, index, HDA_INPUT);
snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index);
if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
return err;
created = 1;
} else if ((node->wid_caps & AC_WCAP_OUT_AMP) &&
(node->amp_out_caps & AC_AMPCAP_NUM_STEPS)) {
snd_kcontrol_new_t knew =
HDA_CODEC_VOLUME(name, node->nid, 0, HDA_OUTPUT);
snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid);
if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
return err;
created = 1;
}
return created;
}
/*
* check whether the controls with the given name and direction suffix already exist
*/
static int check_existing_control(struct hda_codec *codec, const char *type, const char *dir)
{
snd_ctl_elem_id_t id;
memset(&id, 0, sizeof(id));
sprintf(id.name, "%s %s Volume", type, dir);
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
if (snd_ctl_find_id(codec->bus->card, &id))
return 1;
sprintf(id.name, "%s %s Switch", type, dir);
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
if (snd_ctl_find_id(codec->bus->card, &id))
return 1;
return 0;
}
/*
* build output mixer controls
*/
static int build_output_controls(struct hda_codec *codec)
{
struct hda_gspec *spec = codec->spec;
int err;
err = create_mixer(codec, spec->pcm_vol_node, spec->pcm_vol_index,
"PCM", "Playback");
if (err < 0)
return err;
return 0;
}
/* create capture volume/switch */
static int build_input_controls(struct hda_codec *codec)
{
struct hda_gspec *spec = codec->spec;
struct hda_gnode *adc_node = spec->adc_node;
int err;
if (! adc_node)
return 0; /* not found */
/* create capture volume and switch controls if the ADC has an amp */
err = create_mixer(codec, adc_node, 0, NULL, "Capture");
/* create input MUX if multiple sources are available */
if (spec->input_mux.num_items > 1) {
static snd_kcontrol_new_t cap_sel = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Source",
.info = capture_source_info,
.get = capture_source_get,
.put = capture_source_put,
};
if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&cap_sel, codec))) < 0)
return err;
spec->cur_cap_src = 0;
select_input_connection(codec, adc_node, spec->input_mux.items[0].index);
}
return 0;
}
/*
* parse the nodes recursively until reach to the output PIN.
*
* returns 0 - if not found,
* 1 - if found, but no mixer is created
* 2 - if found and mixer was already created, (just skip)
* a negative error code
*/
static int parse_loopback_path(struct hda_codec *codec, struct hda_gspec *spec,
struct hda_gnode *node, struct hda_gnode *dest_node,
const char *type)
{
int i, err;
if (node->checked)
return 0;
node->checked = 1;
if (node == dest_node) {
/* loopback connection found */
return 1;
}
for (i = 0; i < node->nconns; i++) {
struct hda_gnode *child = hda_get_node(spec, node->conn_list[i]);
if (! child)
continue;
err = parse_loopback_path(codec, spec, child, dest_node, type);
if (err < 0)
return err;
else if (err >= 1) {
if (err == 1) {
err = create_mixer(codec, node, i, type, "Playback");
if (err < 0)
return err;
if (err > 0)
return 2; /* ok, created */
/* not created, maybe in the lower path */
err = 1;
}
/* connect and unmute */
if (node->nconns > 1)
select_input_connection(codec, node, i);
unmute_input(codec, node, i);
unmute_output(codec, node);
return err;
}
}
return 0;
}
/*
* parse the tree and build the loopback controls
*/
static int build_loopback_controls(struct hda_codec *codec)
{
struct hda_gspec *spec = codec->spec;
struct list_head *p;
struct hda_gnode *node;
int err;
const char *type;
if (! spec->out_pin_node)
return 0;
list_for_each(p, &spec->nid_list) {
node = list_entry(p, struct hda_gnode, list);
if (node->type != AC_WID_PIN)
continue;
/* input capable? */
if (! (node->pin_caps & AC_PINCAP_IN))
return 0;
type = get_input_type(node, NULL);
if (type) {
if (check_existing_control(codec, type, "Playback"))
continue;
clear_check_flags(spec);
err = parse_loopback_path(codec, spec, spec->out_pin_node,
node, type);
if (err < 0)
return err;
if (! err)
continue;
}
}
return 0;
}
/*
* build mixer controls
*/
static int build_generic_controls(struct hda_codec *codec)
{
int err;
if ((err = build_input_controls(codec)) < 0 ||
(err = build_output_controls(codec)) < 0 ||
(err = build_loopback_controls(codec)) < 0)
return err;
return 0;
}
/*
* PCM
*/
static struct hda_pcm_stream generic_pcm_playback = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
};
static int build_generic_pcms(struct hda_codec *codec)
{
struct hda_gspec *spec = codec->spec;
struct hda_pcm *info = &spec->pcm_rec;
if (! spec->dac_node && ! spec->adc_node) {
snd_printd("hda_generic: no PCM found\n");
return 0;
}
codec->num_pcms = 1;
codec->pcm_info = info;
info->name = "HDA Generic";
if (spec->dac_node) {
info->stream[0] = generic_pcm_playback;
info->stream[0].nid = spec->dac_node->nid;
}
if (spec->adc_node) {
info->stream[1] = generic_pcm_playback;
info->stream[1].nid = spec->adc_node->nid;
}
return 0;
}
/*
*/
static struct hda_codec_ops generic_patch_ops = {
.build_controls = build_generic_controls,
.build_pcms = build_generic_pcms,
.free = snd_hda_generic_free,
};
/*
* the generic parser
*/
int snd_hda_parse_generic_codec(struct hda_codec *codec)
{
struct hda_gspec *spec;
int err;
spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
if (spec == NULL) {
printk(KERN_ERR "hda_generic: can't allocate spec\n");
return -ENOMEM;
}
codec->spec = spec;
INIT_LIST_HEAD(&spec->nid_list);
if ((err = build_afg_tree(codec)) < 0)
goto error;
if ((err = parse_input(codec)) < 0 ||
(err = parse_output(codec)) < 0)
goto error;
codec->patch_ops = generic_patch_ops;
return 0;
error:
snd_hda_generic_free(codec);
return err;
}
/*
*
* hda_intel.c - Implementation of primary alsa driver code base for Intel HD Audio.
*
* Copyright(c) 2004 Intel Corporation. All rights reserved.
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
* PeiSen Hou <pshou@realtek.com.tw>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* CONTACTS:
*
* Matt Jared matt.jared@intel.com
* Andy Kopp andy.kopp@intel.com
* Dan Kogan dan.d.kogan@intel.com
*
* CHANGES:
*
* 2004.12.01 Major rewrite by tiwai, merged the work of pshou
*
*/
#include <sound/driver.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <sound/core.h>
#include <sound/initval.h>
#include "hda_codec.h"
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static char *model[SNDRV_CARDS];
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Intel HD audio interface.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for Intel HD audio interface.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable Intel HD audio interface.");
module_param_array(model, charp, NULL, 0444);
MODULE_PARM_DESC(model, "Use the given board model.");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Intel, ICH6},"
"{Intel, ICH6M},"
"{Intel, ICH7}}");
MODULE_DESCRIPTION("Intel HDA driver");
#define SFX "hda-intel: "
/*
* registers
*/
#define ICH6_REG_GCAP 0x00
#define ICH6_REG_VMIN 0x02
#define ICH6_REG_VMAJ 0x03
#define ICH6_REG_OUTPAY 0x04
#define ICH6_REG_INPAY 0x06
#define ICH6_REG_GCTL 0x08
#define ICH6_REG_WAKEEN 0x0c
#define ICH6_REG_STATESTS 0x0e
#define ICH6_REG_GSTS 0x10
#define ICH6_REG_INTCTL 0x20
#define ICH6_REG_INTSTS 0x24
#define ICH6_REG_WALCLK 0x30
#define ICH6_REG_SYNC 0x34
#define ICH6_REG_CORBLBASE 0x40
#define ICH6_REG_CORBUBASE 0x44
#define ICH6_REG_CORBWP 0x48
#define ICH6_REG_CORBRP 0x4A
#define ICH6_REG_CORBCTL 0x4c
#define ICH6_REG_CORBSTS 0x4d
#define ICH6_REG_CORBSIZE 0x4e
#define ICH6_REG_RIRBLBASE 0x50
#define ICH6_REG_RIRBUBASE 0x54
#define ICH6_REG_RIRBWP 0x58
#define ICH6_REG_RINTCNT 0x5a
#define ICH6_REG_RIRBCTL 0x5c
#define ICH6_REG_RIRBSTS 0x5d
#define ICH6_REG_RIRBSIZE 0x5e
#define ICH6_REG_IC 0x60
#define ICH6_REG_IR 0x64
#define ICH6_REG_IRS 0x68
#define ICH6_IRS_VALID (1<<1)
#define ICH6_IRS_BUSY (1<<0)
#define ICH6_REG_DPLBASE 0x70
#define ICH6_REG_DPUBASE 0x74
#define ICH6_DPLBASE_ENABLE 0x1 /* Enable position buffer */
/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
/* stream register offsets from stream base */
#define ICH6_REG_SD_CTL 0x00
#define ICH6_REG_SD_STS 0x03
#define ICH6_REG_SD_LPIB 0x04
#define ICH6_REG_SD_CBL 0x08
#define ICH6_REG_SD_LVI 0x0c
#define ICH6_REG_SD_FIFOW 0x0e
#define ICH6_REG_SD_FIFOSIZE 0x10
#define ICH6_REG_SD_FORMAT 0x12
#define ICH6_REG_SD_BDLPL 0x18
#define ICH6_REG_SD_BDLPU 0x1c
/* PCI space */
#define ICH6_PCIREG_TCSEL 0x44
/*
* other constants
*/
/* max number of SDs */
#define MAX_ICH6_DEV 8
/* max number of fragments - we may use more if allocating more pages for BDL */
#define AZX_MAX_FRAG (PAGE_SIZE / (MAX_ICH6_DEV * 16))
/* max buffer size - no h/w limit, you can increase as you like */
#define AZX_MAX_BUF_SIZE (1024*1024*1024)
/* max number of PCM devics per card */
#define AZX_MAX_PCMS 8
/* RIRB int mask: overrun[2], response[0] */
#define RIRB_INT_RESPONSE 0x01
#define RIRB_INT_OVERRUN 0x04
#define RIRB_INT_MASK 0x05
/* STATESTS int mask: SD2,SD1,SD0 */
#define STATESTS_INT_MASK 0x07
#define AZX_MAX_CODECS 3
/* SD_CTL bits */
#define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */
#define SD_CTL_DMA_START 0x02 /* stream DMA start bit */
#define SD_CTL_STREAM_TAG_MASK (0xf << 20)
#define SD_CTL_STREAM_TAG_SHIFT 20
/* SD_CTL and SD_STS */
#define SD_INT_DESC_ERR 0x10 /* descriptor error interrupt */
#define SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */
#define SD_INT_COMPLETE 0x04 /* completion interrupt */
#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|SD_INT_COMPLETE)
/* SD_STS */
#define SD_STS_FIFO_READY 0x20 /* FIFO ready */
/* INTCTL and INTSTS */
#define ICH6_INT_ALL_STREAM 0xff /* all stream interrupts */
#define ICH6_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */
#define ICH6_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */
/* GCTL reset bit */
#define ICH6_GCTL_RESET (1<<0)
/* CORB/RIRB control, read/write pointer */
#define ICH6_RBCTL_DMA_EN 0x02 /* enable DMA */
#define ICH6_RBCTL_IRQ_EN 0x01 /* enable IRQ */
#define ICH6_RBRWP_CLR 0x8000 /* read/write pointer clear */
/* below are so far hardcoded - should read registers in future */
#define ICH6_MAX_CORB_ENTRIES 256
#define ICH6_MAX_RIRB_ENTRIES 256
/*
* Use CORB/RIRB for communication from/to codecs.
* This is the way recommended by Intel (see below).
*/
#define USE_CORB_RIRB
/*
* Define this if use the position buffer instead of reading SD_LPIB
* It's not used as default since SD_LPIB seems to give more accurate position
*/
/* #define USE_POSBUF */
/*
*/
typedef struct snd_azx azx_t;
typedef struct snd_azx_rb azx_rb_t;
typedef struct snd_azx_dev azx_dev_t;
struct snd_azx_dev {
u32 *bdl; /* virtual address of the BDL */
dma_addr_t bdl_addr; /* physical address of the BDL */
volatile u32 *posbuf; /* position buffer pointer */
unsigned int bufsize; /* size of the play buffer in bytes */
unsigned int fragsize; /* size of each period in bytes */
unsigned int frags; /* number for period in the play buffer */
unsigned int fifo_size; /* FIFO size */
void __iomem *sd_addr; /* stream descriptor pointer */
u32 sd_int_sta_mask; /* stream int status mask */
/* pcm support */
snd_pcm_substream_t *substream; /* assigned substream, set in PCM open */
unsigned int format_val; /* format value to be set in the controller and the codec */
unsigned char stream_tag; /* assigned stream */
unsigned char index; /* stream index */
unsigned int opened: 1;
unsigned int running: 1;
};
/* CORB/RIRB */
struct snd_azx_rb {
u32 *buf; /* CORB/RIRB buffer
* Each CORB entry is 4byte, RIRB is 8byte
*/
dma_addr_t addr; /* physical address of CORB/RIRB buffer */
/* for RIRB */
unsigned short rp, wp; /* read/write pointers */
int cmds; /* number of pending requests */
u32 res; /* last read value */
};
struct snd_azx {
snd_card_t *card;
struct pci_dev *pci;
/* pci resources */
unsigned long addr;
void __iomem *remap_addr;
int irq;
/* locks */
spinlock_t reg_lock;
struct semaphore open_mutex;
/* streams */
azx_dev_t azx_dev[MAX_ICH6_DEV];
/* PCM */
unsigned int pcm_devs;
snd_pcm_t *pcm[AZX_MAX_PCMS];
/* HD codec */
unsigned short codec_mask;
struct hda_bus *bus;
/* CORB/RIRB */
azx_rb_t corb;
azx_rb_t rirb;
/* BDL, CORB/RIRB and position buffers */
struct snd_dma_buffer bdl;
struct snd_dma_buffer rb;
struct snd_dma_buffer posbuf;
};
/*
* macros for easy use
*/
#define azx_writel(chip,reg,value) \
writel(value, (chip)->remap_addr + ICH6_REG_##reg)
#define azx_readl(chip,reg) \
readl((chip)->remap_addr + ICH6_REG_##reg)
#define azx_writew(chip,reg,value) \
writew(value, (chip)->remap_addr + ICH6_REG_##reg)
#define azx_readw(chip,reg) \
readw((chip)->remap_addr + ICH6_REG_##reg)
#define azx_writeb(chip,reg,value) \
writeb(value, (chip)->remap_addr + ICH6_REG_##reg)
#define azx_readb(chip,reg) \
readb((chip)->remap_addr + ICH6_REG_##reg)
#define azx_sd_writel(dev,reg,value) \
writel(value, (dev)->sd_addr + ICH6_REG_##reg)
#define azx_sd_readl(dev,reg) \
readl((dev)->sd_addr + ICH6_REG_##reg)
#define azx_sd_writew(dev,reg,value) \
writew(value, (dev)->sd_addr + ICH6_REG_##reg)
#define azx_sd_readw(dev,reg) \
readw((dev)->sd_addr + ICH6_REG_##reg)
#define azx_sd_writeb(dev,reg,value) \
writeb(value, (dev)->sd_addr + ICH6_REG_##reg)
#define azx_sd_readb(dev,reg) \
readb((dev)->sd_addr + ICH6_REG_##reg)
/* for pcm support */
#define get_azx_dev(substream) (azx_dev_t*)(substream->runtime->private_data)
/* Get the upper 32bit of the given dma_addr_t
* Compiler should optimize and eliminate the code if dma_addr_t is 32bit
*/
#define upper_32bit(addr) (sizeof(addr) > 4 ? (u32)((addr) >> 32) : (u32)0)
/*
* Interface for HD codec
*/
#ifdef USE_CORB_RIRB
/*
* CORB / RIRB interface
*/
static int azx_alloc_cmd_io(azx_t *chip)
{
int err;
/* single page (at least 4096 bytes) must suffice for both ringbuffes */
err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
PAGE_SIZE, &chip->rb);
if (err < 0) {
snd_printk(KERN_ERR SFX "cannot allocate CORB/RIRB\n");
return err;
}
return 0;
}
static void azx_init_cmd_io(azx_t *chip)
{
/* CORB set up */
chip->corb.addr = chip->rb.addr;
chip->corb.buf = (u32 *)chip->rb.area;
azx_writel(chip, CORBLBASE, (u32)chip->corb.addr);
azx_writel(chip, CORBUBASE, upper_32bit(chip->corb.addr));
/* set the corb write pointer to 0 */
azx_writew(chip, CORBWP, 0);
/* reset the corb hw read pointer */
azx_writew(chip, CORBRP, ICH6_RBRWP_CLR);
/* enable corb dma */
azx_writeb(chip, CORBCTL, ICH6_RBCTL_DMA_EN);
/* RIRB set up */
chip->rirb.addr = chip->rb.addr + 2048;
chip->rirb.buf = (u32 *)(chip->rb.area + 2048);
azx_writel(chip, RIRBLBASE, (u32)chip->rirb.addr);
azx_writel(chip, RIRBUBASE, upper_32bit(chip->rirb.addr));
/* reset the rirb hw write pointer */
azx_writew(chip, RIRBWP, ICH6_RBRWP_CLR);
/* set N=1, get RIRB response interrupt for new entry */
azx_writew(chip, RINTCNT, 1);
/* enable rirb dma and response irq */
azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN
#ifdef USE_CORB_RIRB
| ICH6_RBCTL_IRQ_EN
#endif
);
chip->rirb.rp = chip->rirb.cmds = 0;
}
static void azx_free_cmd_io(azx_t *chip)
{
/* disable ringbuffer DMAs */
azx_writeb(chip, RIRBCTL, 0);
azx_writeb(chip, CORBCTL, 0);
}
/* send a command */
static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int para)
{
azx_t *chip = codec->bus->private_data;
unsigned int wp;
u32 val;
val = (u32)(codec->addr & 0x0f) << 28;
val |= (u32)direct << 27;
val |= (u32)nid << 20;
val |= verb << 8;
val |= para;
/* add command to corb */
wp = azx_readb(chip, CORBWP);
wp++;
wp %= ICH6_MAX_CORB_ENTRIES;
spin_lock_irq(&chip->reg_lock);
chip->rirb.cmds++;
chip->corb.buf[wp] = cpu_to_le32(val);
azx_writel(chip, CORBWP, wp);
spin_unlock_irq(&chip->reg_lock);
return 0;
}
#define ICH6_RIRB_EX_UNSOL_EV (1<<4)
/* retrieve RIRB entry - called from interrupt handler */
static void azx_update_rirb(azx_t *chip)
{
unsigned int rp, wp;
u32 res, res_ex;
wp = azx_readb(chip, RIRBWP);
if (wp == chip->rirb.wp)
return;
chip->rirb.wp = wp;
while (chip->rirb.rp != wp) {
chip->rirb.rp++;
chip->rirb.rp %= ICH6_MAX_RIRB_ENTRIES;
rp = chip->rirb.rp << 1; /* an RIRB entry is 8-bytes */
res_ex = le32_to_cpu(chip->rirb.buf[rp + 1]);
res = le32_to_cpu(chip->rirb.buf[rp]);
if (res_ex & ICH6_RIRB_EX_UNSOL_EV)
snd_hda_queue_unsol_event(chip->bus, res, res_ex);
else if (chip->rirb.cmds) {
chip->rirb.cmds--;
chip->rirb.res = res;
}
}
}
/* receive a response */
static unsigned int azx_get_response(struct hda_codec *codec)
{
azx_t *chip = codec->bus->private_data;
int timeout = 50;
while (chip->rirb.cmds) {
if (! --timeout) {
snd_printk(KERN_ERR "azx_get_response timeout\n");
chip->rirb.rp = azx_readb(chip, RIRBWP);
chip->rirb.cmds = 0;
return -1;
}
msleep(1);
}
return chip->rirb.res; /* the last value */
}
#else
/*
* Use the single immediate command instead of CORB/RIRB for simplicity
*
* Note: according to Intel, this is not preferred use. The command was
* intended for the BIOS only, and may get confused with unsolicited
* responses. So, we shouldn't use it for normal operation from the
* driver.
* I left the codes, however, for debugging/testing purposes.
*/
#define azx_alloc_cmd_io(chip) 0
#define azx_init_cmd_io(chip)
#define azx_free_cmd_io(chip)
/* send a command */
static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct,
unsigned int verb, unsigned int para)
{
azx_t *chip = codec->bus->private_data;
u32 val;
int timeout = 50;
val = (u32)(codec->addr & 0x0f) << 28;
val |= (u32)direct << 27;
val |= (u32)nid << 20;
val |= verb << 8;
val |= para;
while (timeout--) {
/* check ICB busy bit */
if (! (azx_readw(chip, IRS) & ICH6_IRS_BUSY)) {
/* Clear IRV valid bit */
azx_writew(chip, IRS, azx_readw(chip, IRS) | ICH6_IRS_VALID);
azx_writel(chip, IC, val);
azx_writew(chip, IRS, azx_readw(chip, IRS) | ICH6_IRS_BUSY);
return 0;
}
udelay(1);
}
snd_printd(SFX "send_cmd timeout: IRS=0x%x, val=0x%x\n", azx_readw(chip, IRS), val);
return -EIO;
}
/* receive a response */
static unsigned int azx_get_response(struct hda_codec *codec)
{
azx_t *chip = codec->bus->private_data;
int timeout = 50;
while (timeout--) {
/* check IRV busy bit */
if (azx_readw(chip, IRS) & ICH6_IRS_VALID)
return azx_readl(chip, IR);
udelay(1);
}
snd_printd(SFX "get_response timeout: IRS=0x%x\n", azx_readw(chip, IRS));
return (unsigned int)-1;
}
#define azx_update_rirb(chip)
#endif /* USE_CORB_RIRB */
/* reset codec link */
static int azx_reset(azx_t *chip)
{
int count;
/* reset controller */
azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~ICH6_GCTL_RESET);
count = 50;
while (azx_readb(chip, GCTL) && --count)
msleep(1);
/* delay for >= 100us for codec PLL to settle per spec
* Rev 0.9 section 5.5.1
*/
msleep(1);
/* Bring controller out of reset */
azx_writeb(chip, GCTL, azx_readb(chip, GCTL) | ICH6_GCTL_RESET);
count = 50;
while (! azx_readb(chip, GCTL) && --count)
msleep(1);
/* Brent Chartrand said to wait >= 540us for codecs to intialize */
msleep(1);
/* check to see if controller is ready */
if (! azx_readb(chip, GCTL)) {
snd_printd("azx_reset: controller not ready!\n");
return -EBUSY;
}
/* detect codecs */
if (! chip->codec_mask) {
chip->codec_mask = azx_readw(chip, STATESTS);
snd_printdd("codec_mask = 0x%x\n", chip->codec_mask);
}
return 0;
}
/*
* Lowlevel interface
*/
/* enable interrupts */
static void azx_int_enable(azx_t *chip)
{
/* enable controller CIE and GIE */
azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) |
ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN);
}
/* disable interrupts */
static void azx_int_disable(azx_t *chip)
{
int i;
/* disable interrupts in stream descriptor */
for (i = 0; i < MAX_ICH6_DEV; i++) {
azx_dev_t *azx_dev = &chip->azx_dev[i];
azx_sd_writeb(azx_dev, SD_CTL,
azx_sd_readb(azx_dev, SD_CTL) & ~SD_INT_MASK);
}
/* disable SIE for all streams */
azx_writeb(chip, INTCTL, 0);
/* disable controller CIE and GIE */
azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) &
~(ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN));
}
/* clear interrupts */
static void azx_int_clear(azx_t *chip)
{
int i;
/* clear stream status */
for (i = 0; i < MAX_ICH6_DEV; i++) {
azx_dev_t *azx_dev = &chip->azx_dev[i];
azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
}
/* clear STATESTS */
azx_writeb(chip, STATESTS, STATESTS_INT_MASK);
/* clear rirb status */
azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
/* clear int status */
azx_writel(chip, INTSTS, ICH6_INT_CTRL_EN | ICH6_INT_ALL_STREAM);
}
/* start a stream */
static void azx_stream_start(azx_t *chip, azx_dev_t *azx_dev)
{
/* enable SIE */
azx_writeb(chip, INTCTL,
azx_readb(chip, INTCTL) | (1 << azx_dev->index));
/* set DMA start and interrupt mask */
azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) |
SD_CTL_DMA_START | SD_INT_MASK);
}
/* stop a stream */
static void azx_stream_stop(azx_t *chip, azx_dev_t *azx_dev)
{
/* stop DMA */
azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) &
~(SD_CTL_DMA_START | SD_INT_MASK));
azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */
/* disable SIE */
azx_writeb(chip, INTCTL,
azx_readb(chip, INTCTL) & ~(1 << azx_dev->index));
}
/*
* initialize the chip
*/
static void azx_init_chip(azx_t *chip)
{
unsigned char tcsel_reg;
/* Clear bits 0-2 of PCI register TCSEL (at offset 0x44)
* TCSEL == Traffic Class Select Register, which sets PCI express QOS
* Ensuring these bits are 0 clears playback static on some HD Audio codecs
*/
pci_read_config_byte (chip->pci, ICH6_PCIREG_TCSEL, &tcsel_reg);
pci_write_config_byte(chip->pci, ICH6_PCIREG_TCSEL, tcsel_reg & 0xf8);
/* reset controller */
azx_reset(chip);
/* initialize interrupts */
azx_int_clear(chip);
azx_int_enable(chip);
/* initialize the codec command I/O */
azx_init_cmd_io(chip);
#ifdef USE_POSBUF
/* program the position buffer */
azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr);
azx_writel(chip, DPUBASE, upper_32bit(chip->posbuf.addr));
#endif
}
/*
* interrupt handler
*/
static irqreturn_t azx_interrupt(int irq, void* dev_id, struct pt_regs *regs)
{
azx_t *chip = dev_id;
azx_dev_t *azx_dev;
u32 status;
int i;
spin_lock(&chip->reg_lock);
status = azx_readl(chip, INTSTS);
if (status == 0) {
spin_unlock(&chip->reg_lock);
return IRQ_NONE;
}
for (i = 0; i < MAX_ICH6_DEV; i++) {
azx_dev = &chip->azx_dev[i];
if (status & azx_dev->sd_int_sta_mask) {
azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
if (azx_dev->substream && azx_dev->running) {
spin_unlock(&chip->reg_lock);
snd_pcm_period_elapsed(azx_dev->substream);
spin_lock(&chip->reg_lock);
}
}
}
/* clear rirb int */
status = azx_readb(chip, RIRBSTS);
if (status & RIRB_INT_MASK) {
if (status & RIRB_INT_RESPONSE)
azx_update_rirb(chip);
azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
}
#if 0
/* clear state status int */
if (azx_readb(chip, STATESTS) & 0x04)
azx_writeb(chip, STATESTS, 0x04);
#endif
spin_unlock(&chip->reg_lock);
return IRQ_HANDLED;
}
/*
* set up BDL entries
*/
static void azx_setup_periods(azx_dev_t *azx_dev)
{
u32 *bdl = azx_dev->bdl;
dma_addr_t dma_addr = azx_dev->substream->runtime->dma_addr;
int idx;
/* reset BDL address */
azx_sd_writel(azx_dev, SD_BDLPL, 0);
azx_sd_writel(azx_dev, SD_BDLPU, 0);
/* program the initial BDL entries */
for (idx = 0; idx < azx_dev->frags; idx++) {
unsigned int off = idx << 2; /* 4 dword step */
dma_addr_t addr = dma_addr + idx * azx_dev->fragsize;
/* program the address field of the BDL entry */
bdl[off] = cpu_to_le32((u32)addr);
bdl[off+1] = cpu_to_le32(upper_32bit(addr));
/* program the size field of the BDL entry */
bdl[off+2] = cpu_to_le32(azx_dev->fragsize);
/* program the IOC to enable interrupt when buffer completes */
bdl[off+3] = cpu_to_le32(0x01);
}
}
/*
* set up the SD for streaming
*/
static int azx_setup_controller(azx_t *chip, azx_dev_t *azx_dev)
{
unsigned char val;
int timeout;
/* make sure the run bit is zero for SD */
azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) & ~SD_CTL_DMA_START);
/* reset stream */
azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) | SD_CTL_STREAM_RESET);
udelay(3);
timeout = 300;
while (!((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) &&
--timeout)
;
val &= ~SD_CTL_STREAM_RESET;
azx_sd_writeb(azx_dev, SD_CTL, val);
udelay(3);
timeout = 300;
/* waiting for hardware to report that the stream is out of reset */
while (((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) &&
--timeout)
;
/* program the stream_tag */
azx_sd_writel(azx_dev, SD_CTL,
(azx_sd_readl(azx_dev, SD_CTL) & ~SD_CTL_STREAM_TAG_MASK) |
(azx_dev->stream_tag << SD_CTL_STREAM_TAG_SHIFT));
/* program the length of samples in cyclic buffer */
azx_sd_writel(azx_dev, SD_CBL, azx_dev->bufsize);
/* program the stream format */
/* this value needs to be the same as the one programmed */
azx_sd_writew(azx_dev, SD_FORMAT, azx_dev->format_val);
/* program the stream LVI (last valid index) of the BDL */
azx_sd_writew(azx_dev, SD_LVI, azx_dev->frags - 1);
/* program the BDL address */
/* lower BDL address */
azx_sd_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl_addr);
/* upper BDL address */
azx_sd_writel(azx_dev, SD_BDLPU, upper_32bit(azx_dev->bdl_addr));
#ifdef USE_POSBUF
/* enable the position buffer */
if (! (azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE))
azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr | ICH6_DPLBASE_ENABLE);
#endif
/* set the interrupt enable bits in the descriptor control register */
azx_sd_writel(azx_dev, SD_CTL, azx_sd_readl(azx_dev, SD_CTL) | SD_INT_MASK);
return 0;
}
/*
* Codec initialization
*/
static int __devinit azx_codec_create(azx_t *chip, const char *model)
{
struct hda_bus_template bus_temp;
int c, codecs, err;
memset(&bus_temp, 0, sizeof(bus_temp));
bus_temp.private_data = chip;
bus_temp.modelname = model;
bus_temp.pci = chip->pci;
bus_temp.ops.command = azx_send_cmd;
bus_temp.ops.get_response = azx_get_response;
if ((err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus)) < 0)
return err;
codecs = 0;
for (c = 0; c < AZX_MAX_CODECS; c++) {
if (chip->codec_mask & (1 << c)) {
err = snd_hda_codec_new(chip->bus, c, NULL);
if (err < 0)
continue;
codecs++;
}
}
if (! codecs) {
snd_printk(KERN_ERR SFX "no codecs initialized\n");
return -ENXIO;
}
return 0;
}
/*
* PCM support
*/
/* assign a stream for the PCM */
static inline azx_dev_t *azx_assign_device(azx_t *chip, int stream)
{
int dev, i;
dev = stream == SNDRV_PCM_STREAM_PLAYBACK ? 4 : 0;
for (i = 0; i < 4; i++, dev++)
if (! chip->azx_dev[dev].opened) {
chip->azx_dev[dev].opened = 1;
return &chip->azx_dev[dev];
}
return NULL;
}
/* release the assigned stream */
static inline void azx_release_device(azx_dev_t *azx_dev)
{
azx_dev->opened = 0;
}
static snd_pcm_hardware_t azx_pcm_hw = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = AZX_MAX_BUF_SIZE,
.period_bytes_min = 128,
.period_bytes_max = AZX_MAX_BUF_SIZE / 2,
.periods_min = 2,
.periods_max = AZX_MAX_FRAG,
.fifo_size = 0,
};
struct azx_pcm {
azx_t *chip;
struct hda_codec *codec;
struct hda_pcm_stream *hinfo[2];
};
static int azx_pcm_open(snd_pcm_substream_t *substream)
{
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
azx_t *chip = apcm->chip;
azx_dev_t *azx_dev;
snd_pcm_runtime_t *runtime = substream->runtime;
unsigned long flags;
int err;
down(&chip->open_mutex);
azx_dev = azx_assign_device(chip, substream->stream);
if (azx_dev == NULL) {
up(&chip->open_mutex);
return -EBUSY;
}
runtime->hw = azx_pcm_hw;
runtime->hw.channels_min = hinfo->channels_min;
runtime->hw.channels_max = hinfo->channels_max;
runtime->hw.formats = hinfo->formats;
runtime->hw.rates = hinfo->rates;
snd_pcm_limit_hw_rates(runtime);
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
if ((err = hinfo->ops.open(hinfo, apcm->codec, substream)) < 0) {
azx_release_device(azx_dev);
up(&chip->open_mutex);
return err;
}
spin_lock_irqsave(&chip->reg_lock, flags);
azx_dev->substream = substream;
azx_dev->running = 0;
spin_unlock_irqrestore(&chip->reg_lock, flags);
runtime->private_data = azx_dev;
up(&chip->open_mutex);
return 0;
}
static int azx_pcm_close(snd_pcm_substream_t *substream)
{
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
azx_t *chip = apcm->chip;
azx_dev_t *azx_dev = get_azx_dev(substream);
unsigned long flags;
down(&chip->open_mutex);
spin_lock_irqsave(&chip->reg_lock, flags);
azx_dev->substream = NULL;
azx_dev->running = 0;
spin_unlock_irqrestore(&chip->reg_lock, flags);
azx_release_device(azx_dev);
hinfo->ops.close(hinfo, apcm->codec, substream);
up(&chip->open_mutex);
return 0;
}
static int azx_pcm_hw_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}
static int azx_pcm_hw_free(snd_pcm_substream_t *substream)
{
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
azx_dev_t *azx_dev = get_azx_dev(substream);
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
/* reset BDL address */
azx_sd_writel(azx_dev, SD_BDLPL, 0);
azx_sd_writel(azx_dev, SD_BDLPU, 0);
azx_sd_writel(azx_dev, SD_CTL, 0);
hinfo->ops.cleanup(hinfo, apcm->codec, substream);
return snd_pcm_lib_free_pages(substream);
}
static int azx_pcm_prepare(snd_pcm_substream_t *substream)
{
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
azx_t *chip = apcm->chip;
azx_dev_t *azx_dev = get_azx_dev(substream);
struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
snd_pcm_runtime_t *runtime = substream->runtime;
azx_dev->bufsize = snd_pcm_lib_buffer_bytes(substream);
azx_dev->fragsize = snd_pcm_lib_period_bytes(substream);
azx_dev->frags = azx_dev->bufsize / azx_dev->fragsize;
azx_dev->format_val = snd_hda_calc_stream_format(runtime->rate,
runtime->channels,
runtime->format,
hinfo->maxbps);
if (! azx_dev->format_val) {
snd_printk(KERN_ERR SFX "invalid format_val, rate=%d, ch=%d, format=%d\n",
runtime->rate, runtime->channels, runtime->format);
return -EINVAL;
}
snd_printdd("azx_pcm_prepare: bufsize=0x%x, fragsize=0x%x, format=0x%x\n",
azx_dev->bufsize, azx_dev->fragsize, azx_dev->format_val);
azx_setup_periods(azx_dev);
azx_setup_controller(chip, azx_dev);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1;
else
azx_dev->fifo_size = 0;
return hinfo->ops.prepare(hinfo, apcm->codec, azx_dev->stream_tag,
azx_dev->format_val, substream);
}
static int azx_pcm_trigger(snd_pcm_substream_t *substream, int cmd)
{
struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
azx_dev_t *azx_dev = get_azx_dev(substream);
azx_t *chip = apcm->chip;
int err = 0;
spin_lock(&chip->reg_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
azx_stream_start(chip, azx_dev);
azx_dev->running = 1;
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_STOP:
azx_stream_stop(chip, azx_dev);
azx_dev->running = 0;
break;
default:
err = -EINVAL;
}
spin_unlock(&chip->reg_lock);
if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH ||
cmd == SNDRV_PCM_TRIGGER_STOP) {
int timeout = 5000;
while (azx_sd_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START && --timeout)
;
}
return err;
}
static snd_pcm_uframes_t azx_pcm_pointer(snd_pcm_substream_t *substream)
{
azx_dev_t *azx_dev = get_azx_dev(substream);
unsigned int pos;
#ifdef USE_POSBUF
/* use the position buffer */
pos = *azx_dev->posbuf;
#else
/* read LPIB */
pos = azx_sd_readl(azx_dev, SD_LPIB) + azx_dev->fifo_size;
#endif
if (pos >= azx_dev->bufsize)
pos = 0;
return bytes_to_frames(substream->runtime, pos);
}
static snd_pcm_ops_t azx_pcm_ops = {
.open = azx_pcm_open,
.close = azx_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = azx_pcm_hw_params,
.hw_free = azx_pcm_hw_free,
.prepare = azx_pcm_prepare,
.trigger = azx_pcm_trigger,
.pointer = azx_pcm_pointer,
};
static void azx_pcm_free(snd_pcm_t *pcm)
{
kfree(pcm->private_data);
}
static int __devinit create_codec_pcm(azx_t *chip, struct hda_codec *codec,
struct hda_pcm *cpcm, int pcm_dev)
{
int err;
snd_pcm_t *pcm;
struct azx_pcm *apcm;
snd_assert(cpcm->stream[0].substreams || cpcm->stream[1].substreams, return -EINVAL);
snd_assert(cpcm->name, return -EINVAL);
err = snd_pcm_new(chip->card, cpcm->name, pcm_dev,
cpcm->stream[0].substreams, cpcm->stream[1].substreams,
&pcm);
if (err < 0)
return err;
strcpy(pcm->name, cpcm->name);
apcm = kmalloc(sizeof(*apcm), GFP_KERNEL);
if (apcm == NULL)
return -ENOMEM;
apcm->chip = chip;
apcm->codec = codec;
apcm->hinfo[0] = &cpcm->stream[0];
apcm->hinfo[1] = &cpcm->stream[1];
pcm->private_data = apcm;
pcm->private_free = azx_pcm_free;
if (cpcm->stream[0].substreams)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &azx_pcm_ops);
if (cpcm->stream[1].substreams)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &azx_pcm_ops);
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(chip->pci),
1024 * 64, 1024 * 128);
chip->pcm[pcm_dev] = pcm;
return 0;
}
static int __devinit azx_pcm_create(azx_t *chip)
{
struct list_head *p;
struct hda_codec *codec;
int c, err;
int pcm_dev;
if ((err = snd_hda_build_pcms(chip->bus)) < 0)
return err;
pcm_dev = 0;
list_for_each(p, &chip->bus->codec_list) {
codec = list_entry(p, struct hda_codec, list);
for (c = 0; c < codec->num_pcms; c++) {
if (pcm_dev >= AZX_MAX_PCMS) {
snd_printk(KERN_ERR SFX "Too many PCMs\n");
return -EINVAL;
}
err = create_codec_pcm(chip, codec, &codec->pcm_info[c], pcm_dev);
if (err < 0)
return err;
pcm_dev++;
}
}
return 0;
}
/*
* mixer creation - all stuff is implemented in hda module
*/
static int __devinit azx_mixer_create(azx_t *chip)
{
return snd_hda_build_controls(chip->bus);
}
/*
* initialize SD streams
*/
static int __devinit azx_init_stream(azx_t *chip)
{
int i;
/* initialize each stream (aka device)
* assign the starting bdl address to each stream (device) and initialize
*/
for (i = 0; i < MAX_ICH6_DEV; i++) {
unsigned int off = sizeof(u32) * (i * AZX_MAX_FRAG * 4);
azx_dev_t *azx_dev = &chip->azx_dev[i];
azx_dev->bdl = (u32 *)(chip->bdl.area + off);
azx_dev->bdl_addr = chip->bdl.addr + off;
#ifdef USE_POSBUF
azx_dev->posbuf = (volatile u32 *)(chip->posbuf.area + i * 8);
#endif
/* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
azx_dev->sd_addr = chip->remap_addr + (0x20 * i + 0x80);
/* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */
azx_dev->sd_int_sta_mask = 1 << i;
/* stream tag: must be non-zero and unique */
azx_dev->index = i;
azx_dev->stream_tag = i + 1;
}
return 0;
}
#ifdef CONFIG_PM
/*
* power management
*/
static int azx_suspend(snd_card_t *card, unsigned int state)
{
azx_t *chip = card->pm_private_data;
int i;
for (i = 0; i < chip->pcm_devs; i++)
if (chip->pcm[i])
snd_pcm_suspend_all(chip->pcm[i]);
snd_hda_suspend(chip->bus, state);
azx_free_cmd_io(chip);
pci_disable_device(chip->pci);
return 0;
}
static int azx_resume(snd_card_t *card, unsigned int state)
{
azx_t *chip = card->pm_private_data;
pci_enable_device(chip->pci);
pci_set_master(chip->pci);
azx_init_chip(chip);
snd_hda_resume(chip->bus, state);
return 0;
}
#endif /* CONFIG_PM */
/*
* destructor
*/
static int azx_free(azx_t *chip)
{
if (chip->remap_addr) {
int i;
for (i = 0; i < MAX_ICH6_DEV; i++)
azx_stream_stop(chip, &chip->azx_dev[i]);
/* disable interrupts */
azx_int_disable(chip);
azx_int_clear(chip);
/* disable CORB/RIRB */
azx_free_cmd_io(chip);
/* disable position buffer */
azx_writel(chip, DPLBASE, 0);
azx_writel(chip, DPUBASE, 0);
/* wait a little for interrupts to finish */
msleep(1);
iounmap(chip->remap_addr);
}
if (chip->irq >= 0)
free_irq(chip->irq, (void*)chip);
if (chip->bdl.area)
snd_dma_free_pages(&chip->bdl);
if (chip->rb.area)
snd_dma_free_pages(&chip->rb);
#ifdef USE_POSBUF
if (chip->posbuf.area)
snd_dma_free_pages(&chip->posbuf);
#endif
pci_release_regions(chip->pci);
pci_disable_device(chip->pci);
kfree(chip);
return 0;
}
static int azx_dev_free(snd_device_t *device)
{
return azx_free(device->device_data);
}
/*
* constructor
*/
static int __devinit azx_create(snd_card_t *card, struct pci_dev *pci, azx_t **rchip)
{
azx_t *chip;
int err = 0;
static snd_device_ops_t ops = {
.dev_free = azx_dev_free,
};
*rchip = NULL;
if ((err = pci_enable_device(pci)) < 0)
return err;
chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
if (NULL == chip) {
snd_printk(KERN_ERR SFX "cannot allocate chip\n");
pci_disable_device(pci);
return -ENOMEM;
}
spin_lock_init(&chip->reg_lock);
init_MUTEX(&chip->open_mutex);
chip->card = card;
chip->pci = pci;
chip->irq = -1;
if ((err = pci_request_regions(pci, "ICH HD audio")) < 0) {
kfree(chip);
pci_disable_device(pci);
return err;
}
chip->addr = pci_resource_start(pci,0);
chip->remap_addr = ioremap_nocache(chip->addr, pci_resource_len(pci,0));
if (chip->remap_addr == NULL) {
snd_printk(KERN_ERR SFX "ioremap error\n");
err = -ENXIO;
goto errout;
}
if (request_irq(pci->irq, azx_interrupt, SA_INTERRUPT|SA_SHIRQ,
"HDA Intel", (void*)chip)) {
snd_printk(KERN_ERR SFX "unable to grab IRQ %d\n", pci->irq);
err = -EBUSY;
goto errout;
}
chip->irq = pci->irq;
pci_set_master(pci);
synchronize_irq(chip->irq);
/* allocate memory for the BDL for each stream */
if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
PAGE_SIZE, &chip->bdl)) < 0) {
snd_printk(KERN_ERR SFX "cannot allocate BDL\n");
goto errout;
}
#ifdef USE_POSBUF
/* allocate memory for the position buffer */
if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
MAX_ICH6_DEV * 8, &chip->posbuf)) < 0) {
snd_printk(KERN_ERR SFX "cannot allocate posbuf\n");
goto errout;
}
#endif
/* allocate CORB/RIRB */
if ((err = azx_alloc_cmd_io(chip)) < 0)
goto errout;
/* initialize streams */
azx_init_stream(chip);
/* initialize chip */
azx_init_chip(chip);
/* codec detection */
if (! chip->codec_mask) {
snd_printk(KERN_ERR SFX "no codecs found!\n");
err = -ENODEV;
goto errout;
}
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) <0) {
snd_printk(KERN_ERR SFX "Error creating device [card]!\n");
goto errout;
}
*rchip = chip;
return 0;
errout:
azx_free(chip);
return err;
}
static int __devinit azx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
static int dev;
snd_card_t *card;
azx_t *chip;
int err = 0;
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (! enable[dev]) {
dev++;
return -ENOENT;
}
card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
if (NULL == card) {
snd_printk(KERN_ERR SFX "Error creating card!\n");
return -ENOMEM;
}
if ((err = azx_create(card, pci, &chip)) < 0) {
snd_card_free(card);
return err;
}
strcpy(card->driver, "HDA-Intel");
strcpy(card->shortname, "HDA Intel");
sprintf(card->longname, "%s at 0x%lx irq %i", card->shortname, chip->addr, chip->irq);
/* create codec instances */
if ((err = azx_codec_create(chip, model[dev])) < 0) {
snd_card_free(card);
return err;
}
/* create PCM streams */
if ((err = azx_pcm_create(chip)) < 0) {
snd_card_free(card);
return err;
}
/* create mixer controls */
if ((err = azx_mixer_create(chip)) < 0) {
snd_card_free(card);
return err;
}
snd_card_set_pm_callback(card, azx_suspend, azx_resume, chip);
snd_card_set_dev(card, &pci->dev);
if ((err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
pci_set_drvdata(pci, card);
dev++;
return err;
}
static void __devexit azx_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
pci_set_drvdata(pci, NULL);
}
/* PCI IDs */
static struct pci_device_id azx_ids[] = {
{ 0x8086, 0x2668, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ICH6 */
{ 0x8086, 0x27d8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ICH7 */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, azx_ids);
/* pci_driver definition */
static struct pci_driver driver = {
.name = "HDA Intel",
.id_table = azx_ids,
.probe = azx_probe,
.remove = __devexit_p(azx_remove),
SND_PCI_PM_CALLBACKS
};
static int __init alsa_card_azx_init(void)
{
return pci_module_init(&driver);
}
static void __exit alsa_card_azx_exit(void)
{
pci_unregister_driver(&driver);
}
module_init(alsa_card_azx_init)
module_exit(alsa_card_azx_exit)
/*
* Universal Interface for Intel High Definition Audio Codec
*
* Local helper functions
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef __SOUND_HDA_LOCAL_H
#define __SOUND_HDA_LOCAL_H
/*
* for mixer controls
*/
#define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) ((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19))
#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
.info = snd_hda_mixer_amp_volume_info, \
.get = snd_hda_mixer_amp_volume_get, \
.put = snd_hda_mixer_amp_volume_put, \
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \
HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \
HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, 3, xindex, direction)
#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \
HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction)
#define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
.info = snd_hda_mixer_amp_switch_info, \
.get = snd_hda_mixer_amp_switch_get, \
.put = snd_hda_mixer_amp_switch_put, \
.private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \
HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \
HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, 3, xindex, direction)
#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \
HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction)
int snd_hda_mixer_amp_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo);
int snd_hda_mixer_amp_volume_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol);
int snd_hda_mixer_amp_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol);
int snd_hda_mixer_amp_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo);
int snd_hda_mixer_amp_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol);
int snd_hda_mixer_amp_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol);
int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid);
/*
* input MUX helper
*/
#define HDA_MAX_NUM_INPUTS 8
struct hda_input_mux_item {
const char *label;
unsigned int index;
};
struct hda_input_mux {
unsigned int num_items;
struct hda_input_mux_item items[HDA_MAX_NUM_INPUTS];
};
int snd_hda_input_mux_info(const struct hda_input_mux *imux, snd_ctl_elem_info_t *uinfo);
int snd_hda_input_mux_put(struct hda_codec *codec, const struct hda_input_mux *imux,
snd_ctl_elem_value_t *ucontrol, hda_nid_t nid,
unsigned int *cur_val);
/*
* Multi-channel / digital-out PCM helper
*/
enum { HDA_FRONT, HDA_REAR, HDA_CLFE, HDA_SIDE }; /* index for dac_nidx */
enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */
struct hda_multi_out {
int num_dacs; /* # of DACs, must be more than 1 */
hda_nid_t *dac_nids; /* DAC list */
hda_nid_t hp_nid; /* optional DAC for HP, 0 when not exists */
hda_nid_t dig_out_nid; /* digital out audio widget */
int max_channels; /* currently supported analog channels */
int dig_out_used; /* current usage of digital out (HDA_DIG_XXX) */
};
int snd_hda_multi_out_dig_open(struct hda_codec *codec, struct hda_multi_out *mout);
int snd_hda_multi_out_dig_close(struct hda_codec *codec, struct hda_multi_out *mout);
int snd_hda_multi_out_analog_open(struct hda_codec *codec, struct hda_multi_out *mout,
snd_pcm_substream_t *substream);
int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, struct hda_multi_out *mout,
unsigned int stream_tag,
unsigned int format,
snd_pcm_substream_t *substream);
int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, struct hda_multi_out *mout);
/*
* generic codec parser
*/
int snd_hda_parse_generic_codec(struct hda_codec *codec);
/*
* generic proc interface
*/
#ifdef CONFIG_PROC_FS
int snd_hda_codec_proc_new(struct hda_codec *codec);
#else
static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; }
#endif
/*
* Misc
*/
struct hda_board_config {
const char *modelname;
int config;
unsigned short pci_vendor;
unsigned short pci_device;
};
int snd_hda_check_board_config(struct hda_codec *codec, struct hda_board_config *tbl);
int snd_hda_add_new_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew);
/*
* power management
*/
#ifdef CONFIG_PM
int snd_hda_resume_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew);
int snd_hda_resume_spdif_out(struct hda_codec *codec);
#endif
/*
* unsolicited event handler
*/
#define HDA_UNSOL_QUEUE_SIZE 64
struct hda_bus_unsolicited {
/* ring buffer */
u32 queue[HDA_UNSOL_QUEUE_SIZE * 2];
unsigned int rp, wp;
/* workqueue */
struct workqueue_struct *workq;
struct work_struct work;
};
#endif /* __SOUND_HDA_LOCAL_H */
/*
* HDA Patches - included by hda_codec.c
*/
/* Realtek codecs */
extern struct hda_codec_preset snd_hda_preset_realtek[];
/* C-Media codecs */
extern struct hda_codec_preset snd_hda_preset_cmedia[];
static const struct hda_codec_preset *hda_preset_tables[] = {
snd_hda_preset_realtek,
snd_hda_preset_cmedia,
NULL
};
/*
* Universal Interface for Intel High Definition Audio Codec
*
* Generic proc interface
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*
*
* This driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This driver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <sound/core.h>
#include "hda_codec.h"
static const char *get_wid_type_name(unsigned int wid_value)
{
static char *names[16] = {
[AC_WID_AUD_OUT] = "Audio Output",
[AC_WID_AUD_IN] = "Audio Input",
[AC_WID_AUD_MIX] = "Audio Mixer",
[AC_WID_AUD_SEL] = "Audio Selector",
[AC_WID_PIN] = "Pin Complex",
[AC_WID_POWER] = "Power Widget",
[AC_WID_VOL_KNB] = "Volume Knob Widget",
[AC_WID_BEEP] = "Beep Generator Widget",
[AC_WID_VENDOR] = "Vendor Defined Widget",
};
wid_value &= 0xf;
if (names[wid_value])
return names[wid_value];
else
return "UNKOWN Widget";
}
static void print_amp_caps(snd_info_buffer_t *buffer,
struct hda_codec *codec, hda_nid_t nid, int dir)
{
unsigned int caps;
if (dir == HDA_OUTPUT)
caps = snd_hda_param_read(codec, nid, AC_PAR_AMP_OUT_CAP);
else
caps = snd_hda_param_read(codec, nid, AC_PAR_AMP_IN_CAP);
if (caps == -1 || caps == 0) {
snd_iprintf(buffer, "N/A\n");
return;
}
snd_iprintf(buffer, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, mute=%x\n",
caps & AC_AMPCAP_OFFSET,
(caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT,
(caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT,
(caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT);
}
static void print_amp_vals(snd_info_buffer_t *buffer,
struct hda_codec *codec, hda_nid_t nid,
int dir, int stereo)
{
unsigned int val;
if (stereo) {
val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE,
AC_AMP_GET_LEFT |
(dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT :
AC_AMP_GET_INPUT));
snd_iprintf(buffer, "0x%02x ", val);
}
val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE,
AC_AMP_GET_RIGHT |
(dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT :
AC_AMP_GET_INPUT));
snd_iprintf(buffer, "0x%02x\n", val);
}
static void print_pcm_caps(snd_info_buffer_t *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
unsigned int pcm = snd_hda_param_read(codec, nid, AC_PAR_PCM);
unsigned int stream = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
if (pcm == -1 || stream == -1) {
snd_iprintf(buffer, "N/A\n");
return;
}
snd_iprintf(buffer, "rates 0x%03x, bits 0x%02x, types 0x%x\n",
pcm & AC_SUPPCM_RATES, (pcm >> 16) & 0xff, stream & 0xf);
}
static const char *get_jack_location(u32 cfg)
{
static char *bases[7] = {
"N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom",
};
static unsigned char specials_idx[] = {
0x07, 0x08,
0x17, 0x18, 0x19,
0x37, 0x38
};
static char *specials[] = {
"Rear Panel", "Drive Bar",
"Riser", "HDMI", "ATAPI",
"Mobile-In", "Mobile-Out"
};
int i;
cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
if ((cfg & 0x0f) < 7)
return bases[cfg & 0x0f];
for (i = 0; i < ARRAY_SIZE(specials_idx); i++) {
if (cfg == specials_idx[i])
return specials[i];
}
return "UNKNOWN";
}
static const char *get_jack_connection(u32 cfg)
{
static char *names[16] = {
"Unknown", "1/8", "1/4", "ATAPI",
"RCA", "Optical","Digital", "Analog",
"DIN", "XLR", "RJ11", "Comb",
NULL, NULL, NULL, "Other"
};
cfg = (cfg & AC_DEFCFG_CONN_TYPE) >> AC_DEFCFG_CONN_TYPE_SHIFT;
if (names[cfg])
return names[cfg];
else
return "UNKNOWN";
}
static const char *get_jack_color(u32 cfg)
{
static char *names[16] = {
"Unknown", "Black", "Grey", "Blue",
"Green", "Red", "Orange", "Yellow",
"Purple", "Pink", NULL, NULL,
NULL, NULL, "White", "Other",
};
cfg = (cfg & AC_DEFCFG_COLOR) >> AC_DEFCFG_COLOR_SHIFT;
if (names[cfg])
return names[cfg];
else
return "UNKNOWN";
}
static void print_pin_caps(snd_info_buffer_t *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
static char *jack_types[16] = {
"Line Out", "Speaker", "HP Out", "CD",
"SPDIF Out", "Digital Out", "Modem Line", "Modem Hand",
"Line In", "Aux", "Mic", "Telephony",
"SPDIF In", "Digitial In", "Reserved", "Other"
};
static char *jack_locations[4] = { "Ext", "Int", "Sep", "Oth" };
unsigned int caps;
caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
snd_iprintf(buffer, " Pincap 0x08%x:", caps);
if (caps & AC_PINCAP_IN)
snd_iprintf(buffer, " IN");
if (caps & AC_PINCAP_OUT)
snd_iprintf(buffer, " OUT");
if (caps & AC_PINCAP_HP_DRV)
snd_iprintf(buffer, " HP");
snd_iprintf(buffer, "\n");
caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
snd_iprintf(buffer, " Pin Default 0x%08x: %s at %s %s\n", caps,
jack_types[(caps & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT],
jack_locations[(caps >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3],
get_jack_location(caps));
snd_iprintf(buffer, " Conn = %s, Color = %s\n",
get_jack_connection(caps),
get_jack_color(caps));
}
static void print_codec_info(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
{
struct hda_codec *codec = entry->private_data;
char buf[32];
hda_nid_t nid;
int i, nodes;
snd_hda_get_codec_name(codec, buf, sizeof(buf));
snd_iprintf(buffer, "Codec: %s\n", buf);
snd_iprintf(buffer, "Address: %d\n", codec->addr);
snd_iprintf(buffer, "Vendor Id: 0x%x\n", codec->vendor_id);
snd_iprintf(buffer, "Subsystem Id: 0x%x\n", codec->subsystem_id);
snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id);
snd_iprintf(buffer, "Default PCM: ");
print_pcm_caps(buffer, codec, codec->afg);
snd_iprintf(buffer, "Default Amp-In caps: ");
print_amp_caps(buffer, codec, codec->afg, HDA_INPUT);
snd_iprintf(buffer, "Default Amp-Out caps: ");
print_amp_caps(buffer, codec, codec->afg, HDA_OUTPUT);
nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
if (! nid || nodes < 0) {
snd_iprintf(buffer, "Invalid AFG subtree\n");
return;
}
for (i = 0; i < nodes; i++, nid++) {
unsigned int wid_caps = snd_hda_param_read(codec, nid,
AC_PAR_AUDIO_WIDGET_CAP);
unsigned int wid_type = (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
snd_iprintf(buffer, "Node 0x%02x [%s] wcaps 0x%x:", nid,
get_wid_type_name(wid_type), wid_caps);
if (wid_caps & AC_WCAP_STEREO)
snd_iprintf(buffer, " Stereo");
else
snd_iprintf(buffer, " Mono");
if (wid_caps & AC_WCAP_DIGITAL)
snd_iprintf(buffer, " Digital");
if (wid_caps & AC_WCAP_IN_AMP)
snd_iprintf(buffer, " Amp-In");
if (wid_caps & AC_WCAP_OUT_AMP)
snd_iprintf(buffer, " Amp-Out");
snd_iprintf(buffer, "\n");
if (wid_caps & AC_WCAP_IN_AMP) {
snd_iprintf(buffer, " Amp-In caps: ");
print_amp_caps(buffer, codec, nid, HDA_INPUT);
snd_iprintf(buffer, " Amp-In vals: ");
print_amp_vals(buffer, codec, nid, HDA_INPUT,
wid_caps & AC_WCAP_STEREO);
}
if (wid_caps & AC_WCAP_OUT_AMP) {
snd_iprintf(buffer, " Amp-Out caps: ");
print_amp_caps(buffer, codec, nid, HDA_OUTPUT);
snd_iprintf(buffer, " Amp-Out vals: ");
print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
wid_caps & AC_WCAP_STEREO);
}
if (wid_type == AC_WID_PIN) {
unsigned int pinctls;
print_pin_caps(buffer, codec, nid);
pinctls = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
snd_iprintf(buffer, " Pin-ctls: 0x%02x:", pinctls);
if (pinctls & AC_PINCTL_IN_EN)
snd_iprintf(buffer, " IN");
if (pinctls & AC_PINCTL_OUT_EN)
snd_iprintf(buffer, " OUT");
if (pinctls & AC_PINCTL_HP_EN)
snd_iprintf(buffer, " HP");
snd_iprintf(buffer, "\n");
}
if ((wid_type == AC_WID_AUD_OUT || wid_type == AC_WID_AUD_IN) &&
(wid_caps & AC_WCAP_FORMAT_OVRD)) {
snd_iprintf(buffer, " PCM: ");
print_pcm_caps(buffer, codec, nid);
}
if (wid_caps & AC_WCAP_CONN_LIST) {
hda_nid_t conn[HDA_MAX_CONNECTIONS];
int c, conn_len;
conn_len = snd_hda_get_connections(codec, nid, conn,
HDA_MAX_CONNECTIONS);
snd_iprintf(buffer, " Connection: %d\n", conn_len);
snd_iprintf(buffer, " ");
for (c = 0; c < conn_len; c++)
snd_iprintf(buffer, " 0x%02x", conn[c]);
snd_iprintf(buffer, "\n");
}
}
}
/*
* create a proc read
*/
int snd_hda_codec_proc_new(struct hda_codec *codec)
{
char name[32];
snd_info_entry_t *entry;
int err;
snprintf(name, sizeof(name), "codec#%d", codec->addr);
err = snd_card_proc_new(codec->bus->card, name, &entry);
if (err < 0)
return err;
snd_info_set_text_ops(entry, codec, 32 * 1024, print_codec_info);
return 0;
}
/*
* Universal Interface for Intel High Definition Audio Codec
*
* HD audio interface patch for C-Media CMI9880
*
* Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
*
*
* This driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This driver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <sound/core.h>
#include "hda_codec.h"
#include "hda_local.h"
/* board config type */
enum {
CMI_MINIMAL, /* back 3-jack */
CMI_MIN_FP, /* back 3-jack + front-panel 2-jack */
CMI_FULL, /* back 6-jack + front-panel 2-jack */
CMI_FULL_DIG, /* back 6-jack + front-panel 2-jack + digital I/O */
CMI_ALLOUT, /* back 5-jack + front-panel 2-jack + digital out */
};
struct cmi_spec {
int board_config;
unsigned int surr_switch: 1; /* switchable line,mic */
unsigned int no_line_in: 1; /* no line-in (5-jack) */
unsigned int front_panel: 1; /* has front-panel 2-jack */
/* playback */
struct hda_multi_out multiout;
/* capture */
hda_nid_t *adc_nids;
hda_nid_t dig_in_nid;
/* capture source */
const struct hda_input_mux *input_mux;
unsigned int cur_mux[2];
/* channel mode */
unsigned int num_ch_modes;
unsigned int cur_ch_mode;
const struct cmi_channel_mode *channel_modes;
struct hda_pcm pcm_rec[2]; /* PCM information */
};
/*
* input MUX
*/
static int cmi_mux_enum_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct cmi_spec *spec = codec->spec;
return snd_hda_input_mux_info(spec->input_mux, uinfo);
}
static int cmi_mux_enum_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct cmi_spec *spec = codec->spec;
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
return 0;
}
static int cmi_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct cmi_spec *spec = codec->spec;
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
spec->adc_nids[adc_idx], &spec->cur_mux[adc_idx]);
}
/*
* shared line-in, mic for surrounds
*/
/* 3-stack / 2 channel */
static struct hda_verb cmi9880_ch2_init[] = {
/* set line-in PIN for input */
{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
/* set mic PIN for input, also enable vref */
{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
/* route front PCM (DAC1) to HP */
{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
{}
};
/* 3-stack / 6 channel */
static struct hda_verb cmi9880_ch6_init[] = {
/* set line-in PIN for input */
{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
/* set mic PIN for input, also enable vref */
{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
/* route front PCM (DAC1) to HP */
{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
{}
};
/* 3-stack+front / 8 channel */
static struct hda_verb cmi9880_ch8_init[] = {
/* set line-in PIN for input */
{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
/* set mic PIN for input, also enable vref */
{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
/* route rear-surround PCM (DAC4) to HP */
{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x03 },
{}
};
struct cmi_channel_mode {
unsigned int channels;
const struct hda_verb *sequence;
};
static struct cmi_channel_mode cmi9880_channel_modes[3] = {
{ 2, cmi9880_ch2_init },
{ 6, cmi9880_ch6_init },
{ 8, cmi9880_ch8_init },
};
static int cmi_ch_mode_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct cmi_spec *spec = codec->spec;
snd_assert(spec->channel_modes, return -EINVAL);
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = spec->num_ch_modes;
if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
sprintf(uinfo->value.enumerated.name, "%dch",
spec->channel_modes[uinfo->value.enumerated.item].channels);
return 0;
}
static int cmi_ch_mode_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct cmi_spec *spec = codec->spec;
ucontrol->value.enumerated.item[0] = spec->cur_ch_mode;
return 0;
}
static int cmi_ch_mode_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct cmi_spec *spec = codec->spec;
snd_assert(spec->channel_modes, return -EINVAL);
if (ucontrol->value.enumerated.item[0] >= spec->num_ch_modes)
ucontrol->value.enumerated.item[0] = spec->num_ch_modes;
if (ucontrol->value.enumerated.item[0] == spec->cur_ch_mode &&
! codec->in_resume)
return 0;
spec->cur_ch_mode = ucontrol->value.enumerated.item[0];
snd_hda_sequence_write(codec, spec->channel_modes[spec->cur_ch_mode].sequence);
spec->multiout.max_channels = spec->channel_modes[spec->cur_ch_mode].channels;
return 1;
}
/*
*/
static snd_kcontrol_new_t cmi9880_basic_mixer[] = {
/* CMI9880 has no playback volumes! */
HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 0x0, HDA_OUTPUT), /* front */
HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x05, 1, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x05, 2, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Side Playback Switch", 0x06, 0x0, HDA_OUTPUT),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
/* The multiple "Capture Source" controls confuse alsamixer
* So call somewhat different..
* FIXME: the controls appear in the "playback" view!
*/
/* .name = "Capture Source", */
.name = "Input Source",
.count = 2,
.info = cmi_mux_enum_info,
.get = cmi_mux_enum_get,
.put = cmi_mux_enum_put,
},
HDA_CODEC_VOLUME("Capture Volume", 0x08, 0, HDA_INPUT),
HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0, HDA_INPUT),
HDA_CODEC_MUTE("Capture Switch", 0x08, 0, HDA_INPUT),
HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0, HDA_INPUT),
HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x23, 0, HDA_OUTPUT),
HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x23, 0, HDA_OUTPUT),
{ } /* end */
};
/*
* shared I/O pins
*/
static snd_kcontrol_new_t cmi9880_ch_mode_mixer[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Channel Mode",
.info = cmi_ch_mode_info,
.get = cmi_ch_mode_get,
.put = cmi_ch_mode_put,
},
{ } /* end */
};
/* AUD-in selections:
* 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x1f 0x20
*/
static struct hda_input_mux cmi9880_basic_mux = {
.num_items = 4,
.items = {
{ "Front Mic", 0x5 },
{ "Rear Mic", 0x2 },
{ "Line", 0x1 },
{ "CD", 0x7 },
}
};
static struct hda_input_mux cmi9880_no_line_mux = {
.num_items = 3,
.items = {
{ "Front Mic", 0x5 },
{ "Rear Mic", 0x2 },
{ "CD", 0x7 },
}
};
/* front, rear, clfe, rear_surr */
static hda_nid_t cmi9880_dac_nids[4] = {
0x03, 0x04, 0x05, 0x06
};
/* ADC0, ADC1 */
static hda_nid_t cmi9880_adc_nids[2] = {
0x08, 0x09
};
#define CMI_DIG_OUT_NID 0x07
#define CMI_DIG_IN_NID 0x0a
/*
*/
static struct hda_verb cmi9880_basic_init[] = {
/* port-D for line out (rear panel) */
{ 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* port-E for HP out (front panel) */
{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* route front PCM to HP */
{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
/* port-A for surround (rear panel) */
{ 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* port-G for CLFE (rear panel) */
{ 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* port-H for side (rear panel) */
{ 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* port-C for line-in (rear panel) */
{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
/* port-B for mic-in (rear panel) with vref */
{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
/* port-F for mic-in (front panel) with vref */
{ 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
/* CD-in */
{ 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
/* route front mic to ADC1/2 */
{ 0x08, AC_VERB_SET_CONNECT_SEL, 0x05 },
{ 0x09, AC_VERB_SET_CONNECT_SEL, 0x05 },
{} /* terminator */
};
static struct hda_verb cmi9880_allout_init[] = {
/* port-D for line out (rear panel) */
{ 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* port-E for HP out (front panel) */
{ 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* route front PCM to HP */
{ 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
/* port-A for side (rear panel) */
{ 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* port-G for CLFE (rear panel) */
{ 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* port-C for surround (rear panel) */
{ 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
/* port-B for mic-in (rear panel) with vref */
{ 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
/* port-F for mic-in (front panel) with vref */
{ 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
/* CD-in */
{ 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
/* route front mic to ADC1/2 */
{ 0x08, AC_VERB_SET_CONNECT_SEL, 0x05 },
{ 0x09, AC_VERB_SET_CONNECT_SEL, 0x05 },
{} /* terminator */
};
/*
*/
static int cmi9880_build_controls(struct hda_codec *codec)
{
struct cmi_spec *spec = codec->spec;
int err;
err = snd_hda_add_new_ctls(codec, cmi9880_basic_mixer);
if (err < 0)
return err;
if (spec->surr_switch) {
err = snd_hda_add_new_ctls(codec, cmi9880_ch_mode_mixer);
if (err < 0)
return err;
}
if (spec->multiout.dig_out_nid) {
err = snd_hda_create_spdif_out_ctls(codec, CMI_DIG_OUT_NID);
if (err < 0)
return err;
}
/* TODO: digital-in */
return 0;
}
static int cmi9880_init(struct hda_codec *codec)
{
struct cmi_spec *spec = codec->spec;
if (spec->board_config == CMI_ALLOUT)
snd_hda_sequence_write(codec, cmi9880_allout_init);
else
snd_hda_sequence_write(codec, cmi9880_basic_init);
return 0;
}
#ifdef CONFIG_PM
/*
* resume
*/
static int cmi9880_resume(struct hda_codec *codec, unsigned int state)
{
struct cmi_spec *spec = codec->spec;
cmi9880_init(codec);
snd_hda_resume_ctls(codec, cmi9880_basic_mixer);
if (spec->surr_switch)
snd_hda_resume_ctls(codec, cmi9880_ch_mode_mixer);
if (spec->multiout.dig_out_nid)
snd_hda_resume_spdif_out(codec);
return 0;
}
#endif
/*
* Analog playback callbacks
*/
static int cmi9880_playback_pcm_open(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct cmi_spec *spec = codec->spec;
return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
}
static int cmi9880_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
unsigned int stream_tag,
unsigned int format,
snd_pcm_substream_t *substream)
{
struct cmi_spec *spec = codec->spec;
return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
format, substream);
}
static int cmi9880_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct cmi_spec *spec = codec->spec;
return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
}
/*
* Digital out
*/
static int cmi9880_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct cmi_spec *spec = codec->spec;
return snd_hda_multi_out_dig_open(codec, &spec->multiout);
}
static int cmi9880_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct cmi_spec *spec = codec->spec;
return snd_hda_multi_out_dig_close(codec, &spec->multiout);
}
/*
* Analog capture
*/
static int cmi9880_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
unsigned int stream_tag,
unsigned int format,
snd_pcm_substream_t *substream)
{
struct cmi_spec *spec = codec->spec;
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
stream_tag, 0, format);
return 0;
}
static int cmi9880_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct cmi_spec *spec = codec->spec;
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], 0, 0, 0);
return 0;
}
/*
*/
static struct hda_pcm_stream cmi9880_pcm_analog_playback = {
.substreams = 1,
.channels_min = 2,
.channels_max = 8,
.nid = 0x03, /* NID to query formats and rates */
.ops = {
.open = cmi9880_playback_pcm_open,
.prepare = cmi9880_playback_pcm_prepare,
.cleanup = cmi9880_playback_pcm_cleanup
},
};
static struct hda_pcm_stream cmi9880_pcm_analog_capture = {
.substreams = 2,
.channels_min = 2,
.channels_max = 2,
.nid = 0x08, /* NID to query formats and rates */
.ops = {
.prepare = cmi9880_capture_pcm_prepare,
.cleanup = cmi9880_capture_pcm_cleanup
},
};
static struct hda_pcm_stream cmi9880_pcm_digital_playback = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
/* NID is set in cmi9880_build_pcms */
.ops = {
.open = cmi9880_dig_playback_pcm_open,
.close = cmi9880_dig_playback_pcm_close
},
};
static struct hda_pcm_stream cmi9880_pcm_digital_capture = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
/* NID is set in cmi9880_build_pcms */
};
static int cmi9880_build_pcms(struct hda_codec *codec)
{
struct cmi_spec *spec = codec->spec;
struct hda_pcm *info = spec->pcm_rec;
codec->num_pcms = 1;
codec->pcm_info = info;
info->name = "CMI9880";
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = cmi9880_pcm_analog_playback;
info->stream[SNDRV_PCM_STREAM_CAPTURE] = cmi9880_pcm_analog_capture;
if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
codec->num_pcms++;
info++;
info->name = "CMI9880 Digital";
if (spec->multiout.dig_out_nid) {
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = cmi9880_pcm_digital_playback;
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
}
if (spec->dig_in_nid) {
info->stream[SNDRV_PCM_STREAM_CAPTURE] = cmi9880_pcm_digital_capture;
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
}
}
return 0;
}
static void cmi9880_free(struct hda_codec *codec)
{
kfree(codec->spec);
}
/*
*/
static struct hda_board_config cmi9880_cfg_tbl[] = {
{ .modelname = "minimal", .config = CMI_MINIMAL },
{ .modelname = "min_fp", .config = CMI_MIN_FP },
{ .modelname = "full", .config = CMI_FULL },
{ .modelname = "full_dig", .config = CMI_FULL_DIG },
{ .modelname = "allout", .config = CMI_ALLOUT },
{} /* terminator */
};
static struct hda_codec_ops cmi9880_patch_ops = {
.build_controls = cmi9880_build_controls,
.build_pcms = cmi9880_build_pcms,
.init = cmi9880_init,
.free = cmi9880_free,
#ifdef CONFIG_PM
.resume = cmi9880_resume,
#endif
};
static int patch_cmi9880(struct hda_codec *codec)
{
struct cmi_spec *spec;
spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
if (spec == NULL)
return -ENOMEM;
codec->spec = spec;
spec->board_config = snd_hda_check_board_config(codec, cmi9880_cfg_tbl);
if (spec->board_config < 0) {
snd_printd(KERN_INFO "hda_codec: Unknown model for CMI9880\n");
spec->board_config = CMI_MINIMAL;
}
switch (spec->board_config) {
case CMI_MINIMAL:
case CMI_MIN_FP:
spec->surr_switch = 1;
if (spec->board_config == CMI_MINIMAL)
spec->num_ch_modes = 2;
else {
spec->front_panel = 1;
spec->num_ch_modes = 3;
}
spec->channel_modes = cmi9880_channel_modes;
spec->multiout.max_channels = cmi9880_channel_modes[0].channels;
spec->input_mux = &cmi9880_basic_mux;
break;
case CMI_FULL:
case CMI_FULL_DIG:
spec->front_panel = 1;
spec->multiout.max_channels = 8;
spec->input_mux = &cmi9880_basic_mux;
if (spec->board_config == CMI_FULL_DIG) {
spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
spec->dig_in_nid = CMI_DIG_IN_NID;
}
break;
case CMI_ALLOUT:
spec->front_panel = 1;
spec->multiout.max_channels = 8;
spec->no_line_in = 1;
spec->input_mux = &cmi9880_no_line_mux;
spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
break;
}
spec->multiout.num_dacs = 4;
spec->multiout.dac_nids = cmi9880_dac_nids;
spec->adc_nids = cmi9880_adc_nids;
codec->patch_ops = cmi9880_patch_ops;
return 0;
}
/*
* patch entries
*/
struct hda_codec_preset snd_hda_preset_cmedia[] = {
{ .id = 0x434d4980, .name = "CMI9880", .patch = patch_cmi9880 },
{} /* terminator */
};
/*
* Universal Interface for Intel High Definition Audio Codec
*
* HD audio interface patch for ALC 260/880 codecs
*
* Copyright (c) 2004 PeiSen Hou <pshou@realtek.com.tw>
* Takashi Iwai <tiwai@suse.de>
*
* This driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This driver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <sound/core.h>
#include "hda_codec.h"
#include "hda_local.h"
/* ALC880 board config type */
enum {
ALC880_MINIMAL,
ALC880_3ST,
ALC880_3ST_DIG,
ALC880_5ST,
ALC880_5ST_DIG,
ALC880_W810,
};
struct alc_spec {
/* codec parameterization */
unsigned int front_panel: 1;
snd_kcontrol_new_t* mixers[2];
unsigned int num_mixers;
struct hda_verb *init_verbs;
char* stream_name_analog;
struct hda_pcm_stream *stream_analog_playback;
struct hda_pcm_stream *stream_analog_capture;
char* stream_name_digital;
struct hda_pcm_stream *stream_digital_playback;
struct hda_pcm_stream *stream_digital_capture;
/* playback */
struct hda_multi_out multiout;
/* capture */
unsigned int num_adc_nids;
hda_nid_t *adc_nids;
hda_nid_t dig_in_nid;
/* capture source */
const struct hda_input_mux *input_mux;
unsigned int cur_mux[3];
/* channel model */
const struct alc_channel_mode *channel_mode;
int num_channel_mode;
/* PCM information */
struct hda_pcm pcm_rec[2];
};
/* DAC/ADC assignment */
static hda_nid_t alc880_dac_nids[4] = {
/* front, rear, clfe, rear_surr */
0x02, 0x05, 0x04, 0x03
};
static hda_nid_t alc880_w810_dac_nids[3] = {
/* front, rear/surround, clfe */
0x02, 0x03, 0x04
};
static hda_nid_t alc880_adc_nids[3] = {
/* ADC0-2 */
0x07, 0x08, 0x09,
};
#define ALC880_DIGOUT_NID 0x06
#define ALC880_DIGIN_NID 0x0a
static hda_nid_t alc260_dac_nids[1] = {
/* front */
0x02,
};
static hda_nid_t alc260_adc_nids[2] = {
/* ADC0-1 */
0x04, 0x05,
};
#define ALC260_DIGOUT_NID 0x03
#define ALC260_DIGIN_NID 0x06
static struct hda_input_mux alc880_capture_source = {
.num_items = 4,
.items = {
{ "Mic-1", 0x0 },
{ "Mic-2", 0x3 },
{ "Line", 0x2 },
{ "CD", 0x4 },
},
};
static struct hda_input_mux alc260_capture_source = {
.num_items = 3,
.items = {
{ "Mic", 0x0 },
{ "Line", 0x2 },
{ "CD", 0x4 },
},
};
/*
* input MUX handling
*/
static int alc_mux_enum_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;
return snd_hda_input_mux_info(spec->input_mux, uinfo);
}
static int alc_mux_enum_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
return 0;
}
static int alc_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;
unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
spec->adc_nids[adc_idx], &spec->cur_mux[adc_idx]);
}
/*
* channel mode setting
*/
struct alc_channel_mode {
int channels;
const struct hda_verb *sequence;
};
/*
* channel source setting (2/6 channel selection for 3-stack)
*/
/*
* set the path ways for 2 channel output
* need to set the codec line out and mic 1 pin widgets to inputs
*/
static struct hda_verb alc880_threestack_ch2_init[] = {
/* set pin widget 1Ah (line in) for input */
{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
/* set pin widget 18h (mic1) for input, for mic also enable the vref */
{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
/* mute the output for Line In PW */
{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
/* mute for Mic1 PW */
{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
{ } /* end */
};
/*
* 6ch mode
* need to set the codec line out and mic 1 pin widgets to outputs
*/
static struct hda_verb alc880_threestack_ch6_init[] = {
/* set pin widget 1Ah (line in) for output */
{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
/* set pin widget 18h (mic1) for output */
{ 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
/* unmute the output for Line In PW */
{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
/* unmute for Mic1 PW */
{ 0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
/* for rear channel output using Line In 1
* set select widget connection (nid = 0x12) - to summer node
* for rear NID = 0x0f...offset 3 in connection list
*/
{ 0x12, AC_VERB_SET_CONNECT_SEL, 0x3 },
/* for Mic1 - retask for center/lfe */
/* set select widget connection (nid = 0x10) - to summer node for
* front CLFE NID = 0x0e...offset 2 in connection list
*/
{ 0x10, AC_VERB_SET_CONNECT_SEL, 0x2 },
{ } /* end */
};
static struct alc_channel_mode alc880_threestack_modes[2] = {
{ 2, alc880_threestack_ch2_init },
{ 6, alc880_threestack_ch6_init },
};
/*
* channel source setting (6/8 channel selection for 5-stack)
*/
/* set the path ways for 6 channel output
* need to set the codec line out and mic 1 pin widgets to inputs
*/
static struct hda_verb alc880_fivestack_ch6_init[] = {
/* set pin widget 1Ah (line in) for input */
{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
/* mute the output for Line In PW */
{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
{ } /* end */
};
/* need to set the codec line out and mic 1 pin widgets to outputs */
static struct hda_verb alc880_fivestack_ch8_init[] = {
/* set pin widget 1Ah (line in) for output */
{ 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
/* unmute the output for Line In PW */
{ 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
/* output for surround channel output using Line In 1 */
/* set select widget connection (nid = 0x12) - to summer node
* for surr_rear NID = 0x0d...offset 1 in connection list
*/
{ 0x12, AC_VERB_SET_CONNECT_SEL, 0x1 },
{ } /* end */
};
static struct alc_channel_mode alc880_fivestack_modes[2] = {
{ 6, alc880_fivestack_ch6_init },
{ 8, alc880_fivestack_ch8_init },
};
/*
* channel source setting for W810 system
*
* W810 has rear IO for:
* Front (DAC 02)
* Surround (DAC 03)
* Center/LFE (DAC 04)
* Digital out (06)
*
* The system also has a pair of internal speakers, and a headphone jack.
* These are both connected to Line2 on the codec, hence to DAC 02.
*
* There is a variable resistor to control the speaker or headphone
* volume. This is a hardware-only device without a software API.
*
* Plugging headphones in will disable the internal speakers. This is
* implemented in hardware, not via the driver using jack sense. In
* a similar fashion, plugging into the rear socket marked "front" will
* disable both the speakers and headphones.
*
* For input, there's a microphone jack, and an "audio in" jack.
* These may not do anything useful with this driver yet, because I
* haven't setup any initialization verbs for these yet...
*/
static struct alc_channel_mode alc880_w810_modes[1] = {
{ 6, NULL }
};
/*
*/
static int alc880_ch_mode_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;
snd_assert(spec->channel_mode, return -ENXIO);
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item >= 2)
uinfo->value.enumerated.item = 1;
sprintf(uinfo->value.enumerated.name, "%dch",
spec->channel_mode[uinfo->value.enumerated.item].channels);
return 0;
}
static int alc880_ch_mode_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;
snd_assert(spec->channel_mode, return -ENXIO);
ucontrol->value.enumerated.item[0] =
(spec->multiout.max_channels == spec->channel_mode[0].channels) ? 0 : 1;
return 0;
}
static int alc880_ch_mode_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct alc_spec *spec = codec->spec;
int mode;
snd_assert(spec->channel_mode, return -ENXIO);
mode = ucontrol->value.enumerated.item[0] ? 1 : 0;
if (spec->multiout.max_channels == spec->channel_mode[mode].channels &&
! codec->in_resume)
return 0;
/* change the current channel setting */
spec->multiout.max_channels = spec->channel_mode[mode].channels;
if (spec->channel_mode[mode].sequence)
snd_hda_sequence_write(codec, spec->channel_mode[mode].sequence);
return 1;
}
/*
*/
static snd_kcontrol_new_t alc880_base_mixer[] = {
HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME("Surround Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Surround Playback Switch", 0x1a, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x18, 1, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x18, 2, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
HDA_CODEC_VOLUME("Mic-1 Playback Volume", 0x0b, 0x0, HDA_INPUT),
HDA_CODEC_MUTE("Mic-1 Playback Switch", 0x0b, 0x0, HDA_INPUT),
HDA_CODEC_VOLUME("Mic-2 Playback Volume", 0x0b, 0x3, HDA_INPUT),
HDA_CODEC_MUTE("Mic-2 Playback Switch", 0x0b, 0x3, HDA_INPUT),
HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT),
HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT),
HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Headphone Playback Switch", 0x19, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME("Capture Volume", 0x07, 0x0, HDA_INPUT),
HDA_CODEC_MUTE("Capture Switch", 0x07, 0x0, HDA_INPUT),
HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x08, 0x0, HDA_INPUT),
HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x08, 0x0, HDA_INPUT),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
/* The multiple "Capture Source" controls confuse alsamixer
* So call somewhat different..
* FIXME: the controls appear in the "playback" view!
*/
/* .name = "Capture Source", */
.name = "Input Source",
.count = 2,
.info = alc_mux_enum_info,
.get = alc_mux_enum_get,
.put = alc_mux_enum_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Channel Mode",
.info = alc880_ch_mode_info,
.get = alc880_ch_mode_get,
.put = alc880_ch_mode_put,
},
{ } /* end */
};
static snd_kcontrol_new_t alc880_side_mixer[] = {
HDA_CODEC_VOLUME("Side Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Side Playback Switch", 0x19, 0x0, HDA_OUTPUT),
{ } /* end */
};
static snd_kcontrol_new_t alc880_w810_base_mixer[] = {
HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Surround Playback Switch", 0x15, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x16, 1, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME("Capture Volume", 0x07, 0x0, HDA_INPUT),
HDA_CODEC_MUTE("Capture Switch", 0x07, 0x0, HDA_INPUT),
HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x08, 0x0, HDA_INPUT),
HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x08, 0x0, HDA_INPUT),
HDA_CODEC_VOLUME_IDX("Capture Volume", 2, 0x09, 0x0, HDA_INPUT),
HDA_CODEC_MUTE_IDX("Capture Switch", 2, 0x09, 0x0, HDA_INPUT),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
/* The multiple "Capture Source" controls confuse alsamixer
* So call somewhat different..
* FIXME: the controls appear in the "playback" view!
*/
/* .name = "Capture Source", */
.name = "Input Source",
.count = 3,
.info = alc_mux_enum_info,
.get = alc_mux_enum_get,
.put = alc_mux_enum_put,
},
{ } /* end */
};
/*
*/
static int alc_build_controls(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
int err;
int i;
for (i = 0; i < spec->num_mixers; i++) {
err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
if (err < 0)
return err;
}
if (spec->multiout.dig_out_nid) {
err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid);
if (err < 0)
return err;
}
return 0;
}
/*
* initialize the codec volumes, etc
*/
static struct hda_verb alc880_init_verbs_three_stack[] = {
/* Line In pin widget for input */
{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
/* CD pin widget for input */
{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
/* Mic1 (rear panel) pin widget for input and vref at 80% */
{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
/* Mic2 (front panel) pin widget for input and vref at 80% */
{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
/* unmute amp left and right */
{0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
/* set connection select to line in (default select for this ADC) */
{0x07, AC_VERB_SET_CONNECT_SEL, 0x02},
/* unmute front mixer amp left (volume = 0) */
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* mute pin widget amp left and right (no gain on this amp) */
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* unmute rear mixer amp left and right (volume = 0) */
{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* mute pin widget amp left and right (no gain on this amp) */
{0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* unmute rear mixer amp left and right (volume = 0) */
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* mute pin widget amp left and right (no gain on this amp) */
{0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* using rear surround as the path for headphone output */
/* unmute rear surround mixer amp left and right (volume = 0) */
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* PASD 3 stack boards use the Mic 2 as the headphone output */
/* need to program the selector associated with the Mic 2 pin widget to
* surround path (index 0x01) for headphone output */
{0x11, AC_VERB_SET_CONNECT_SEL, 0x01},
/* mute pin widget amp left and right (no gain on this amp) */
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* need to retask the Mic 2 pin widget to output */
{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) for mixer widget(nid=0x0B)
* to support the input path of analog loopback
* Note: PASD motherboards uses the Line In 2 as the input for front panel
* mic (mic 2)
*/
/* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & Line In 2 = 0x03 */
/* unmute CD */
{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))},
/* unmute Line In */
{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))},
/* unmute Mic 1 */
{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
/* unmute Line In 2 (for PASD boards Mic 2) */
{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))},
/* Unmute input amps for the line out paths to support the output path of
* analog loopback
* the mixers on the output path has 2 inputs, one from the DAC and one
* from the mixer
*/
/* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
/* Unmute Front out path */
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
/* Unmute Surround (used as HP) out path */
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
/* Unmute C/LFE out path */
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
/* Unmute rear Surround out path */
{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
{ }
};
static struct hda_verb alc880_init_verbs_five_stack[] = {
/* Line In pin widget for input */
{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
/* CD pin widget for input */
{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
/* Mic1 (rear panel) pin widget for input and vref at 80% */
{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
/* Mic2 (front panel) pin widget for input and vref at 80% */
{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
/* unmute amp left and right */
{0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
/* set connection select to line in (default select for this ADC) */
{0x07, AC_VERB_SET_CONNECT_SEL, 0x02},
/* unmute front mixer amp left and right (volume = 0) */
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* mute pin widget amp left and right (no gain on this amp) */
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* five rear and clfe */
/* unmute rear mixer amp left and right (volume = 0) */
{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* mute pin widget amp left and right (no gain on this amp) */
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* unmute clfe mixer amp left and right (volume = 0) */
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* mute pin widget amp left and right (no gain on this amp) */
{0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* using rear surround as the path for headphone output */
/* unmute rear surround mixer amp left and right (volume = 0) */
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* PASD 3 stack boards use the Mic 2 as the headphone output */
/* need to program the selector associated with the Mic 2 pin widget to
* surround path (index 0x01) for headphone output
*/
{0x11, AC_VERB_SET_CONNECT_SEL, 0x01},
/* mute pin widget amp left and right (no gain on this amp) */
{0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* need to retask the Mic 2 pin widget to output */
{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
/* Unmute input amps (CD, Line In, Mic 1 & Mic 2) for mixer
* widget(nid=0x0B) to support the input path of analog loopback
*/
/* Note: PASD motherboards uses the Line In 2 as the input for front panel mic (mic 2) */
/* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & Line In 2 = 0x03*/
/* unmute CD */
{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))},
/* unmute Line In */
{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))},
/* unmute Mic 1 */
{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
/* unmute Line In 2 (for PASD boards Mic 2) */
{0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))},
/* Unmute input amps for the line out paths to support the output path of
* analog loopback
* the mixers on the output path has 2 inputs, one from the DAC and
* one from the mixer
*/
/* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
/* Unmute Front out path */
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
/* Unmute Surround (used as HP) out path */
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
/* Unmute C/LFE out path */
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
/* Unmute rear Surround out path */
{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
{ }
};
static struct hda_verb alc880_w810_init_verbs[] = {
/* front channel selector/amp: input 0: DAC: unmuted, (no volume selection) */
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
/* front channel selector/amp: input 1: capture mix: muted, (no volume selection) */
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7180},
/* front channel selector/amp: output 0: unmuted, max volume */
{0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* front out pin: muted, (no volume selection) */
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
/* front out pin: NOT headphone enable, out enable, vref disabled */
{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
/* surround channel selector/amp: input 0: DAC: unmuted, (no volume selection) */
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
/* surround channel selector/amp: input 1: capture mix: muted, (no volume selection) */
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7180},
/* surround channel selector/amp: output 0: unmuted, max volume */
{0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* surround out pin: muted, (no volume selection) */
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
/* surround out pin: NOT headphone enable, out enable, vref disabled */
{0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
/* c/lfe channel selector/amp: input 0: DAC: unmuted, (no volume selection) */
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
/* c/lfe channel selector/amp: input 1: capture mix: muted, (no volume selection) */
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0x7180},
/* c/lfe channel selector/amp: output 0: unmuted, max volume */
{0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* c/lfe out pin: muted, (no volume selection) */
{0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
/* c/lfe out pin: NOT headphone enable, out enable, vref disabled */
{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
/* hphone/speaker input selector: front DAC */
{0x13, AC_VERB_SET_CONNECT_SEL, 0x0},
/* hphone/speaker out pin: muted, (no volume selection) */
{0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
/* hphone/speaker out pin: NOT headphone enable, out enable, vref disabled */
{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
{ }
};
static int alc_init(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
snd_hda_sequence_write(codec, spec->init_verbs);
return 0;
}
#ifdef CONFIG_PM
/*
* resume
*/
static int alc_resume(struct hda_codec *codec, unsigned int state)
{
struct alc_spec *spec = codec->spec;
int i;
alc_init(codec);
for (i = 0; i < spec->num_mixers; i++) {
snd_hda_resume_ctls(codec, spec->mixers[i]);
}
if (spec->multiout.dig_out_nid)
snd_hda_resume_spdif_out(codec);
return 0;
}
#endif
/*
* Analog playback callbacks
*/
static int alc880_playback_pcm_open(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct alc_spec *spec = codec->spec;
return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
}
static int alc880_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
unsigned int stream_tag,
unsigned int format,
snd_pcm_substream_t *substream)
{
struct alc_spec *spec = codec->spec;
return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
format, substream);
}
static int alc880_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct alc_spec *spec = codec->spec;
return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
}
/*
* Digital out
*/
static int alc880_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct alc_spec *spec = codec->spec;
return snd_hda_multi_out_dig_open(codec, &spec->multiout);
}
static int alc880_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct alc_spec *spec = codec->spec;
return snd_hda_multi_out_dig_close(codec, &spec->multiout);
}
/*
* Analog capture
*/
static int alc880_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
unsigned int stream_tag,
unsigned int format,
snd_pcm_substream_t *substream)
{
struct alc_spec *spec = codec->spec;
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
stream_tag, 0, format);
return 0;
}
static int alc880_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
snd_pcm_substream_t *substream)
{
struct alc_spec *spec = codec->spec;
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], 0, 0, 0);
return 0;
}
/*
*/
static struct hda_pcm_stream alc880_pcm_analog_playback = {
.substreams = 1,
.channels_min = 2,
.channels_max = 8,
.nid = 0x02, /* NID to query formats and rates */
.ops = {
.open = alc880_playback_pcm_open,
.prepare = alc880_playback_pcm_prepare,
.cleanup = alc880_playback_pcm_cleanup
},
};
static struct hda_pcm_stream alc880_pcm_analog_capture = {
.substreams = 2,
.channels_min = 2,
.channels_max = 2,
.nid = 0x07, /* NID to query formats and rates */
.ops = {
.prepare = alc880_capture_pcm_prepare,
.cleanup = alc880_capture_pcm_cleanup
},
};
static struct hda_pcm_stream alc880_pcm_digital_playback = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
/* NID is set in alc_build_pcms */
.ops = {
.open = alc880_dig_playback_pcm_open,
.close = alc880_dig_playback_pcm_close
},
};
static struct hda_pcm_stream alc880_pcm_digital_capture = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
/* NID is set in alc_build_pcms */
};
static int alc_build_pcms(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
struct hda_pcm *info = spec->pcm_rec;
int i;
codec->num_pcms = 1;
codec->pcm_info = info;
info->name = spec->stream_name_analog;
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *(spec->stream_analog_playback);
info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_analog_capture);
info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 0;
for (i = 0; i < spec->num_channel_mode; i++) {
if (spec->channel_mode[i].channels > info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max) {
info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = spec->channel_mode[i].channels;
}
}
if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
codec->num_pcms++;
info++;
info->name = spec->stream_name_digital;
if (spec->multiout.dig_out_nid) {
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *(spec->stream_digital_playback);
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
}
if (spec->dig_in_nid) {
info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_digital_capture);
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
}
}
return 0;
}
static void alc_free(struct hda_codec *codec)
{
kfree(codec->spec);
}
/*
*/
static struct hda_codec_ops alc_patch_ops = {
.build_controls = alc_build_controls,
.build_pcms = alc_build_pcms,
.init = alc_init,
.free = alc_free,
#ifdef CONFIG_PM
.resume = alc_resume,
#endif
};
/*
*/
static struct hda_board_config alc880_cfg_tbl[] = {
/* Back 3 jack, front 2 jack */
{ .modelname = "3stack", .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe200, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe201, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe202, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe203, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe204, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe205, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe206, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe207, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe208, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe209, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe20a, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe20b, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe20c, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe20d, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe20e, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe20f, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe210, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe211, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe214, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe302, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe303, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe304, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe306, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe307, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xe404, .config = ALC880_3ST },
{ .pci_vendor = 0x8086, .pci_device = 0xa101, .config = ALC880_3ST },
{ .pci_vendor = 0x107b, .pci_device = 0x3031, .config = ALC880_3ST },
{ .pci_vendor = 0x107b, .pci_device = 0x4036, .config = ALC880_3ST },
{ .pci_vendor = 0x107b, .pci_device = 0x4037, .config = ALC880_3ST },
{ .pci_vendor = 0x107b, .pci_device = 0x4038, .config = ALC880_3ST },
{ .pci_vendor = 0x107b, .pci_device = 0x4040, .config = ALC880_3ST },
{ .pci_vendor = 0x107b, .pci_device = 0x4041, .config = ALC880_3ST },
/* Back 3 jack, front 2 jack (Internal add Aux-In) */
{ .pci_vendor = 0x1025, .pci_device = 0xe310, .config = ALC880_3ST },
/* Back 3 jack plus 1 SPDIF out jack, front 2 jack */
{ .modelname = "3stack-digout", .config = ALC880_3ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xe308, .config = ALC880_3ST_DIG },
/* Back 3 jack plus 1 SPDIF out jack, front 2 jack (Internal add Aux-In)*/
{ .pci_vendor = 0x8086, .pci_device = 0xe305, .config = ALC880_3ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xd402, .config = ALC880_3ST_DIG },
{ .pci_vendor = 0x1025, .pci_device = 0xe309, .config = ALC880_3ST_DIG },
/* Back 5 jack, front 2 jack */
{ .modelname = "5stack", .config = ALC880_5ST },
{ .pci_vendor = 0x107b, .pci_device = 0x3033, .config = ALC880_5ST },
{ .pci_vendor = 0x107b, .pci_device = 0x4039, .config = ALC880_5ST },
{ .pci_vendor = 0x107b, .pci_device = 0x3032, .config = ALC880_5ST },
{ .pci_vendor = 0x103c, .pci_device = 0x2a09, .config = ALC880_5ST },
/* Back 5 jack plus 1 SPDIF out jack, front 2 jack */
{ .modelname = "5stack-digout", .config = ALC880_5ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xe224, .config = ALC880_5ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xe400, .config = ALC880_5ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xe401, .config = ALC880_5ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xe402, .config = ALC880_5ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xd400, .config = ALC880_5ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xd401, .config = ALC880_5ST_DIG },
{ .pci_vendor = 0x8086, .pci_device = 0xa100, .config = ALC880_5ST_DIG },
{ .pci_vendor = 0x1565, .pci_device = 0x8202, .config = ALC880_5ST_DIG },
{ .modelname = "w810", .config = ALC880_W810 },
{ .pci_vendor = 0x161f, .pci_device = 0x203d, .config = ALC880_W810 },
{}
};
static int patch_alc880(struct hda_codec *codec)
{
struct alc_spec *spec;
int board_config;
spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
if (spec == NULL)
return -ENOMEM;
codec->spec = spec;
board_config = snd_hda_check_board_config(codec, alc880_cfg_tbl);
if (board_config < 0) {
snd_printd(KERN_INFO "hda_codec: Unknown model for ALC880\n");
board_config = ALC880_MINIMAL;
}
switch (board_config) {
case ALC880_W810:
spec->mixers[spec->num_mixers] = alc880_w810_base_mixer;
spec->num_mixers++;
break;
default:
spec->mixers[spec->num_mixers] = alc880_base_mixer;
spec->num_mixers++;
break;
}
switch (board_config) {
case ALC880_3ST_DIG:
case ALC880_5ST_DIG:
case ALC880_W810:
spec->multiout.dig_out_nid = ALC880_DIGOUT_NID;
break;
default:
break;
}
switch (board_config) {
case ALC880_3ST:
case ALC880_3ST_DIG:
case ALC880_5ST:
case ALC880_5ST_DIG:
case ALC880_W810:
spec->front_panel = 1;
break;
default:
break;
}
switch (board_config) {
case ALC880_5ST:
case ALC880_5ST_DIG:
spec->mixers[spec->num_mixers] = alc880_side_mixer;
spec->num_mixers++;
spec->init_verbs = alc880_init_verbs_five_stack;
spec->channel_mode = alc880_fivestack_modes;
spec->num_channel_mode = ARRAY_SIZE(alc880_fivestack_modes);
break;
case ALC880_W810:
spec->init_verbs = alc880_w810_init_verbs;
spec->channel_mode = alc880_w810_modes;
spec->num_channel_mode = ARRAY_SIZE(alc880_w810_modes);
break;
default:
spec->init_verbs = alc880_init_verbs_three_stack;
spec->channel_mode = alc880_threestack_modes;
spec->num_channel_mode = ARRAY_SIZE(alc880_threestack_modes);
break;
}
spec->stream_name_analog = "ALC880 Analog";
spec->stream_analog_playback = &alc880_pcm_analog_playback;
spec->stream_analog_capture = &alc880_pcm_analog_capture;
spec->stream_name_digital = "ALC880 Digital";
spec->stream_digital_playback = &alc880_pcm_digital_playback;
spec->stream_digital_capture = &alc880_pcm_digital_capture;
spec->multiout.max_channels = spec->channel_mode[0].channels;
switch (board_config) {
case ALC880_W810:
spec->multiout.num_dacs = ARRAY_SIZE(alc880_w810_dac_nids);
spec->multiout.dac_nids = alc880_w810_dac_nids;
// No dedicated headphone socket - it's shared with built-in speakers.
break;
default:
spec->multiout.num_dacs = ARRAY_SIZE(alc880_dac_nids);
spec->multiout.dac_nids = alc880_dac_nids;
spec->multiout.hp_nid = 0x03; /* rear-surround NID */
break;
}
spec->input_mux = &alc880_capture_source;
spec->num_adc_nids = ARRAY_SIZE(alc880_adc_nids);
spec->adc_nids = alc880_adc_nids;
codec->patch_ops = alc_patch_ops;
return 0;
}
/*
* ALC260 support
*/
/*
* This is just place-holder, so there's something for alc_build_pcms to look
* at when it calculates the maximum number of channels. ALC260 has no mixer
* element which allows changing the channel mode, so the verb list is
* never used.
*/
static struct alc_channel_mode alc260_modes[1] = {
{ 2, 0 },
};
snd_kcontrol_new_t alc260_base_mixer[] = {
HDA_CODEC_VOLUME("PCM Playback Volume", 0x08, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("PCM Playback Switch", 0x0f, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT),
HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT),
HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT),
HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT),
HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT),
HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT),
HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x07, 0x05, HDA_INPUT),
HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x07, 0x05, HDA_INPUT),
HDA_CODEC_VOLUME("Headphone Playback Volume", 0x09, 0x0, HDA_OUTPUT),
HDA_CODEC_MUTE("Headphone Playback Switch", 0x10, 0x0, HDA_OUTPUT),
HDA_CODEC_VOLUME("Capture Volume", 0x04, 0x0, HDA_INPUT),
HDA_CODEC_MUTE("Capture Switch", 0x04, 0x0, HDA_INPUT),
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Source",
.info = alc_mux_enum_info,
.get = alc_mux_enum_get,
.put = alc_mux_enum_put,
},
{ } /* end */
};
static struct hda_verb alc260_init_verbs[] = {
/* Line In pin widget for input */
{0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
/* CD pin widget for input */
{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
/* Mic1 (rear panel) pin widget for input and vref at 80% */
{0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
/* Mic2 (front panel) pin widget for input and vref at 80% */
{0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
/* unmute amp left and right */
{0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
/* set connection select to line in (default select for this ADC) */
{0x04, AC_VERB_SET_CONNECT_SEL, 0x02},
/* unmute Line-Out mixer amp left and right (volume = 0) */
{0x08, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* mute pin widget amp left and right (no gain on this amp) */
{0x0f, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* unmute HP mixer amp left and right (volume = 0) */
{0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
/* mute pin widget amp left and right (no gain on this amp) */
{0x10, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
/* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & Line In 2 = 0x03 */
/* unmute CD */
{0x07, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))},
/* unmute Line In */
{0x07, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))},
/* unmute Mic */
{0x07, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
/* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
/* Unmute Front out path */
{0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
/* Unmute Headphone out path */
{0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
{0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
{ }
};
static struct hda_pcm_stream alc260_pcm_analog_playback = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
.nid = 0x2,
};
static struct hda_pcm_stream alc260_pcm_analog_capture = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2,
.nid = 0x4,
};
static int patch_alc260(struct hda_codec *codec)
{
struct alc_spec *spec;
spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
if (spec == NULL)
return -ENOMEM;
codec->spec = spec;
spec->mixers[spec->num_mixers] = alc260_base_mixer;
spec->num_mixers++;
spec->init_verbs = alc260_init_verbs;
spec->channel_mode = alc260_modes;
spec->num_channel_mode = ARRAY_SIZE(alc260_modes);
spec->stream_name_analog = "ALC260 Analog";
spec->stream_analog_playback = &alc260_pcm_analog_playback;
spec->stream_analog_capture = &alc260_pcm_analog_capture;
spec->multiout.max_channels = spec->channel_mode[0].channels;
spec->multiout.num_dacs = ARRAY_SIZE(alc260_dac_nids);
spec->multiout.dac_nids = alc260_dac_nids;
spec->input_mux = &alc260_capture_source;
spec->num_adc_nids = ARRAY_SIZE(alc260_adc_nids);
spec->adc_nids = alc260_adc_nids;
codec->patch_ops = alc_patch_ops;
return 0;
}
/*
* patch entries
*/
struct hda_codec_preset snd_hda_preset_realtek[] = {
{ .id = 0x10ec0880, .name = "ALC880", .patch = patch_alc880 },
{ .id = 0x10ec0260, .name = "ALC260", .patch = patch_alc260 },
{} /* terminator */
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment