diff -r 0d5f43585ca7 Documentation/README.maya44 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Documentation/README.maya44 Fri Mar 28 21:47:56 2008 +0100 @@ -0,0 +1,158 @@ + +STATE OF DEVELOPMENT: + +This driver is being developed on the initiative of Piotr Makowski (oponek@gmail.com) and financed by Lars Bergmann. +Development is carried out by Rainer Zimmermann (mail@lightshed.de). + +ESI provided a sample Maya44 card for the development work. + +However, unfortunately it has turned out difficult to get detailed programming information, so I (Rainer Zimmermann) had to find out some card-specific information by experiment and conjecture. Some information (in particular, several GPIO bits) is still missing. + +This is the first testing version of the Maya44 driver released to the alsa-devel mailing list (Feb 5, 2008). + + +The following functions work, as tested by Rainer Zimmermann and Piotr Makowski: + +- playback and capture at all sampling rates +- input/output level +- crossmixing +- line/mic switch +- phantom power switch +- analogue monitor a.k.a bypass + + +The following functions *should* work, but are not fully tested: + +- Channel 3+4 analogue - S/PDIF input switching +- S/PDIF output +- all inputs/outputs on the M/IO/DIO extension card +- internal/external clock selection + + +*In particular, we would appreciate testing of these functions by anyone who has access to an M/IO/DIO extension card.* + + +Things that do not seem to work: + +- The level meters ("multi track") in 'alsamixer' do not seem to react to signals in (if this is a bug, it would probably be in the existing ICE1724 code). + +- Ardour 2.1 seems to work only via JACK, not using ALSA directly or via OSS. This still needs to be tracked down. + + +DRIVER DETAILS: + +the following files were added: + +pci/ice1724/maya44.c - Maya44 specific code +pci/ice1724/maya44.h +pci/ice1724/ice1724.patch +pci/ice1724/ice1724.h.patch - PROPOSED patch to ice1724.h (see SAMPLING RATES) +i2c/other/wm8776.c - low-level access routines for Wolfson WM8776 codecs +include/wm8776.h + + +Note that the wm8776.c code is meant to be card-independent and does not actually register the codec with the ALSA infrastructure. +This is done in maya44.c, mainly because some of the WM8776 controls are used in Maya44-specific ways, and should be named appropriately. + + +the following files were created in pci/ice1724, simply #including the corresponding file from the alsa-kernel tree: + +wtm.h +vt1720_mobo.h +revo.h +prodigy192.h +pontis.h +phase.h +maya44.h +juli.h +aureon.h +amp.h +envy24ht.h +se.h +prodigy_hifi.h + + +*I hope this is the correct way to do things.* + + +SAMPLING RATES: + +The Maya44 card (or more exactly, the Wolfson WM8776 codecs) allow a maximum sampling rate of 192 kHz for playback and 92 kHz for capture. + +As the ICE1724 chip only allows one global sampling rate, this is handled as follows: + +* setting the sampling rate on any open PCM device on the maya44 card will always set the *global* sampling rate for all playback and capture channels. + +* In the current state of the driver, setting rates of up to 192 kHz is permitted even for capture devices. + +*AVOID CAPTURING AT RATES ABOVE 96kHz*, even though it may appear to work. The codec cannot actually capture at such rates, meaning poor quality. + + +I propose some additional code for limiting the sampling rate when setting on a capture pcm device. However because of the global sampling rate, this logic would be somewhat problematic. + +The proposed code (currently deactivated) is in ice1712.h.patch, ice1724.c and maya44.c (in pci/ice1712). + + +SOUND DEVICES: + +PCM devices correspond to inputs/outputs as follows (assuming Maya44 is card #0): + +hw:0,0 input - stereo, analog input 1+2 +hw:0,0 output - stereo, analog output 1+2 +hw:0,1 input - stereo, analog input 3+4 OR S/PDIF input +hw:0,1 output - stereo, analog output 3+4 (and SPDIF out) + + +NAMING OF MIXER CONTROLS: + +(for more information about the signal flow, please refer to the block diagram on p.24 of the ESI Maya44 manual, or in the ESI windows software). + + +PCM: (digital) output level for channel 1+2 +PCM 1: same for channel 3+4 + +Mic Phantom+48V: switch for +48V phantom power for electrostatic microphones on input 1/2. + Make sure this is not turned on while any other source is connected to input 1/2. + It might damage the source and/or the maya44 card. + +Mic/Line input: if switch is is on, input jack 1/2 is microphone input (mono), otherwise line input (stereo). + +Bypass: analogue bypass from ADC input to output for channel 1+2. Same as "Monitor" in the windows driver. +Bypass 1: same for channel 3+4. + +Crossmix: cross-mixer from channels 1+2 to channels 3+4 +Crossmix 1: cross-mixer from channels 3+4 to channels 1+2 + +IEC958 Output: switch for S/PDIF output. + This is not supported by the ESI windows driver. + S/PDIF should output the same signal as channel 3+4. [untested!] + + +Digitial output selectors: + + These switches allow a direct digital routing from the ADCs to the DACs. + Each switch determines where the digital input data to one of the DACs comes from. + They are not supported by the ESI windows driver. + For normal operation, they should all be set to "PCM out". + +H/W: Output source channel 1 +H/W 1: Output source channel 2 +H/W 2: Output source channel 3 +H/W 3: Output source channel 4 + +H/W 4 ... H/W 9: unknown function, left in to enable testing. + Possibly some of these control S/PDIF output(s). + If these turn out to be unused, they will go away in later driver versions. + +Selectable values for each of the digital output selectors are: + "PCM out" -> DAC output of the corresponding channel (default setting) + "Input 1"... + "Input 4" -> direct routing from ADC output of the selected input channel + + +-------- + +Feb 14, 2008 +Rainer Zimmermann +mail@lightshed.de + diff -r 0d5f43585ca7 i2c/other/Makefile --- a/i2c/other/Makefile Sat Mar 22 10:26:05 2008 +0100 +++ b/i2c/other/Makefile Fri Mar 28 21:47:56 2008 +0100 @@ -8,9 +8,10 @@ snd-ak4xxx-adda-objs := ak4xxx-adda.o snd-ak4xxx-adda-objs := ak4xxx-adda.o snd-pt2258-objs := pt2258.o snd-tea575x-tuner-objs := tea575x-tuner.o +snd-wm8776-objs := wm8776.o # Module Dependency obj-$(CONFIG_SND_PDAUDIOCF) += snd-ak4117.o obj-$(CONFIG_SND_ICE1712) += snd-ak4xxx-adda.o -obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o snd-ak4xxx-adda.o snd-pt2258.o +obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o snd-ak4xxx-adda.o snd-pt2258.o snd-wm8776.o obj-$(CONFIG_SND_FM801_TEA575X) += snd-tea575x-tuner.o diff -r 0d5f43585ca7 i2c/other/wm8776.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/i2c/other/wm8776.c Fri Mar 28 21:47:56 2008 +0100 @@ -0,0 +1,410 @@ +/* + * Low-level routines for Wolfson WM8776 codec + * + * Copyright (c) 2007 Rainer Zimmermann + * + * 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 + * + */ + +/* + * not implemented: + * ADC highpass, + * AGC/limiter functions, + * DAC output phase+output control + * de-emphasis mode + * etc... + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Rainer Zimmermann "); +MODULE_DESCRIPTION("Routines for control of WM8776 codecs"); +MODULE_LICENSE("GPL"); + +/* + * If defined, ADC attenuation down to -103dB will be allowed, using the + * WM8776's digital attenuator. IMO this is rather useless because it will + * not prevent ADC overdrive, and digital attenuation can always be done in + * software. + * If not defined, ADC level runs from -21 to +24dB, using the analogue PGA. + * + * NOTE: this driver does not currently support input mute. Since input level + * applies also to the analogue bypass besides the ADC, muting should be + * done using MUTERA/MUTELA in R21, rather than the digital mute using + * R14/R15. + */ +#undef WM8776_ALLOW_ADC_DIGITAL_ATTEN + +/* + * dB tables + */ +/* headphone output: mute, -73..+6db (1db step) */ +static const DECLARE_TLV_DB_SCALE(tlv_scale_headphone, -7400, 100, 1); +/* DAC output: mute, -127..0db (0.5db step) */ +static const DECLARE_TLV_DB_SCALE(tlv_scale_dac, -12750, 50, 1); + +#ifdef WM8776_ALLOW_ADC_DIGITAL_ATTEN +#define ADC_REG_LOW 0x01 +/* ADC gain: mute, -103..+24db (0.5db step) */ +static const DECLARE_TLV_DB_SCALE(tlv_scale_adcgain, -10300, 50, 0); +#else +#define ADC_REG_LOW 0xa5 +/* ADC gain: mute, -21..+24db (0.5db step) */ +static const DECLARE_TLV_DB_SCALE(tlv_scale_adcgain, -2100, 50, 0); +#endif + +static struct snd_wm8776_volume_info volumeinfo[WM8776_NUM_VOLUMES] = +{ + [WM8776_VOL_HEADPHONE_L] = { + .nlvl = 80, + .tlv_scale = tlv_scale_headphone, + .reg_idx = WM8776_REG_HEADPHONE_L, + .reg_mask = 0x7f, + .reg_low = 0x30, + .reg_mute = 0x00 + }, + [WM8776_VOL_HEADPHONE_R] = { + .nlvl = 80, + .tlv_scale = tlv_scale_headphone, + .reg_idx = WM8776_REG_HEADPHONE_R, + .reg_mask = 0x7f, + .reg_low = 0x30, + .reg_mute = 0x00 + }, + [WM8776_VOL_DAC_L] = { + .nlvl = 255, + .tlv_scale = tlv_scale_dac, + .reg_idx = WM8776_REG_DAC_ATTEN_L, + .reg_mask = 0xff, + .reg_low = 0x01, + .reg_mute = 0x00 + }, + [WM8776_VOL_DAC_R] = { + .nlvl = 255, + .tlv_scale = tlv_scale_dac, + .reg_idx = WM8776_REG_DAC_ATTEN_R, + .reg_mask = 0xff, + .reg_low = 0x01, + .reg_mute = 0x00 + }, + [WM8776_VOL_ADC_GAIN_L] = { + .nlvl = 91, + .tlv_scale = tlv_scale_adcgain, + .reg_idx = WM8776_REG_ADC_ATTEN_L, + .reg_mask = 0xff, + .reg_low = ADC_REG_LOW, + .reg_mute = ADC_REG_LOW + }, + [WM8776_VOL_ADC_GAIN_R] = { + .nlvl = 91, + .tlv_scale = tlv_scale_adcgain, + .reg_idx = WM8776_REG_ADC_ATTEN_R, + .reg_mask = 0xff, + .reg_low = ADC_REG_LOW, + .reg_mute = ADC_REG_LOW + } +}; + +static struct snd_wm8776_switch_info switchinfo[WM8776_NUM_SWITCHES] = +{ + [WM8776_SW_OUT_DAC] = { + .n_states = 2, + .reg_idx = 0x16, + .reg_inv = 0, + .reg_mask = 0x01 + }, + [WM8776_SW_OUT_AUX] = { + .n_states = 2, + .reg_idx = 0x16, + .reg_inv = 0, + .reg_mask = 0x02 + }, + [WM8776_SW_OUT_BYPASS] = { + .n_states = 2, + .reg_idx = 0x16, + .reg_inv = 0, + .reg_mask = 0x04 + }, + [WM8776_SW_IN_1] = { + .n_states = 2, + .reg_idx = 0x15, + .reg_inv = 0, + .reg_mask = 0x01 + }, + [WM8776_SW_IN_2] = { + .n_states = 2, + .reg_idx = 0x15, + .reg_inv = 0, + .reg_mask = 0x02 + }, + [WM8776_SW_IN_3] = { + .n_states = 2, + .reg_idx = 0x15, + .reg_inv = 0, + .reg_mask = 0x04 + }, + [WM8776_SW_IN_4] = { + .n_states = 2, + .reg_idx = 0x15, + .reg_inv = 0, + .reg_mask = 0x08 + }, + [WM8776_SW_IN_5] = { + .n_states = 2, + .reg_idx = 0x15, + .reg_inv = 0, + .reg_mask = 0x10 + } +}; + +/* write the given register and save the data to the cache */ +void snd_wm8776_reg_write(struct snd_wm8776 *wm, unsigned char reg, + unsigned short val) +{ +/* printk(KERN_DEBUG "wm8776: wm8776_reg_write\n"); */ + + mutex_lock(&wm->wm_mutex); + wm->ops.write(wm->private_data, wm->chip, reg, val); + wm->regs[reg] = val; + mutex_unlock(&wm->wm_mutex); +} +EXPORT_SYMBOL(snd_wm8776_reg_write); + +/* + * update the given register with and/or mask and save the data to the cache + */ +void snd_wm8776_reg_mask(struct snd_wm8776 *wm, unsigned char reg, + unsigned short and_mask, unsigned short or_mask) +{ + unsigned short val = (wm->regs[reg] & and_mask) | or_mask; + if (val != wm->regs[reg]) { + mutex_lock(&wm->wm_mutex); + wm->ops.write(wm->private_data, wm->chip, reg, val); + wm->regs[reg] = val; + mutex_unlock(&wm->wm_mutex); + } +} + +/* + * change a volume setting on a WM8776 codec. + * index is one of the WM8776_VOL_XXX constants defined in wm8776.h. + * value runs from 1 to nlvl specified in volume_info, or 0 for mute. + * returns 1 if changed, 0 otherwise + */ +int snd_wm8776_set_volume(struct snd_wm8776 *wm, int index, int value) +{ + struct snd_wm8776_volume_info *vi = &volumeinfo[index]; + unsigned short flags; + + if (wm->volumes[index] == value) + return 0; + + wm->volumes[index] = value; + + /* + * HEADPHONE (index<2): UPDATE and zero cross enable (0x180), + * ADC GAIN: zero cross enable (0x100), + * DAC ATTEN: UPDATE (0x100) + */ + flags = (index < 2) ? 0x180 : 0x100; + snd_wm8776_reg_write(wm, vi->reg_idx, + ((value == 0) ? vi->reg_mute : vi->reg_low+value-1) | flags); + + /* handle ADC mute */ + if (index == WM8776_VOL_ADC_GAIN_L) + snd_wm8776_reg_mask(wm, WM8776_REG_ADC_MUX, ~0x80, + (value == 0) ? 0x80 : 0); + + if (index == WM8776_VOL_ADC_GAIN_R) + snd_wm8776_reg_mask(wm, WM8776_REG_ADC_MUX, ~0x40, + (value == 0) ? 0x40 : 0); + + return 1; +} +EXPORT_SYMBOL(snd_wm8776_set_volume); + +int snd_wm8776_get_volume(struct snd_wm8776 *wm, int index) +{ + return wm->volumes[index]; +} +EXPORT_SYMBOL(snd_wm8776_get_volume); + +const struct snd_wm8776_volume_info *snd_wm8776_get_volume_info(int index) +{ + return volumeinfo+index; +} +EXPORT_SYMBOL(snd_wm8776_get_volume_info); + +/* return 1 if changed, 0 otherwise */ +int snd_wm8776_set_switch(struct snd_wm8776 *wm, int index, int value) +{ + struct snd_wm8776_switch_info *vi = &switchinfo[index]; + unsigned int mask = 1<switches & ~mask) | (value?mask:0); + if (sw == wm->switches) + return 0; + wm->switches = sw; + if (vi->reg_inv) + value = !value; + snd_wm8776_reg_mask(wm, vi->reg_idx, ~vi->reg_mask, + value?vi->reg_mask:0); + return 1; +} +EXPORT_SYMBOL(snd_wm8776_set_switch); + +int snd_wm8776_get_switch(struct snd_wm8776 *wm, int index) +{ + return (wm->switches & (1<wm_mutex); + wm->card = card; + wm->chip = chip; + wm->private_data = private_data; + memcpy(&wm->ops, ops, sizeof(wm->ops)); + + memset(wm->regs, 0, sizeof(wm->regs)); + memset(wm->volumes, 0, sizeof(wm->volumes)); + + /* enable DAC output; mute bypass, aux & all inputs */ + wm->switches = (1<private_data, chip); +} +EXPORT_SYMBOL(snd_wm8776_init); + + +static int __init alsa_wm8776_module_init(void) +{ + return 0; +} + +static void __exit alsa_wm8776_module_exit(void) +{ +} + + +module_init(alsa_wm8776_module_init) +module_exit(alsa_wm8776_module_exit) + + diff -r 0d5f43585ca7 include/wm8776.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/wm8776.h Fri Mar 28 21:47:56 2008 +0100 @@ -0,0 +1,149 @@ +#ifndef __SOUND_WM8776_H +#define __SOUND_WM8776_H + +/* + * Low-level routines for Wolfson WM8776 codec + * + * Copyright (c) 2007 Rainer Zimmermann + * + * 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 + * + */ + +struct snd_wm8776; + +/* register indexes */ +#define WM8776_REG_HEADPHONE_L 0x00 +#define WM8776_REG_HEADPHONE_R 0x01 +#define WM8776_REG_HEADPHONE_MASTER 0x02 +/* digital attenuation */ +#define WM8776_REG_DAC_ATTEN_L 0x03 +#define WM8776_REG_DAC_ATTEN_R 0x04 +#define WM8776_REG_DAC_ATTEN_MASTER 0x05 +#define WM8776_REG_DAC_PHASE 0x06 +#define WM8776_REG_DAC_CONTROL 0x07 +#define WM8776_REG_DAC_MUTE 0x08 +#define WM8776_REG_DAC_DEEMPH 0x09 +#define WM8776_REG_DAC_IF_CONTROL 0x0a +#define WM8776_REG_ADC_IF_CONTROL 0x0b +#define WM8776_REG_MASTER_MODE_CONTROL 0x0c +#define WM8776_REG_POWERDOWN 0x0d +/* analogue input pga + digital attenuation */ +#define WM8776_REG_ADC_ATTEN_L 0x0e +#define WM8776_REG_ADC_ATTEN_R 0x0f +#define WM8776_REG_ADC_ALC1 0x10 +#define WM8776_REG_ADC_ALC2 0x11 +#define WM8776_REG_ADC_ALC3 0x12 +#define WM8776_REG_ADC_NOISE_GATE 0x13 +#define WM8776_REG_ADC_LIMITER 0x14 +#define WM8776_REG_ADC_MUX 0x15 +#define WM8776_REG_OUTPUT_MUX 0x16 +#define WM8776_REG_RESET 0x17 + +#define WM8776_NUM_REGS 0x18 + + +/* clock ratio identifiers for snd_wm8776_set_rate() */ +#define WM8776_CLOCK_RATIO_128FS 0 +#define WM8776_CLOCK_RATIO_192FS 1 +#define WM8776_CLOCK_RATIO_256FS 2 +#define WM8776_CLOCK_RATIO_384FS 3 +#define WM8776_CLOCK_RATIO_512FS 4 +#define WM8776_CLOCK_RATIO_768FS 5 + + +enum { + WM8776_VOL_HEADPHONE_L = 0, + WM8776_VOL_HEADPHONE_R, + WM8776_VOL_DAC_L, + WM8776_VOL_DAC_R, + WM8776_VOL_ADC_GAIN_L, + WM8776_VOL_ADC_GAIN_R, + + WM8776_NUM_VOLUMES +}; + +enum { + WM8776_SW_OUT_DAC = 0, + WM8776_SW_OUT_AUX, + WM8776_SW_OUT_BYPASS, + WM8776_SW_IN_1, + WM8776_SW_IN_2, + WM8776_SW_IN_3, + WM8776_SW_IN_4, + WM8776_SW_IN_5, + + WM8776_NUM_SWITCHES +}; + + +struct snd_wm8776_ops { + /* currently only 1 op... */ + void (*write)(void *private_data, int chip, unsigned char reg, + unsigned short val); +}; + +struct snd_wm8776 { + struct snd_card *card; + unsigned char chip; + void *private_data; + struct snd_wm8776_ops ops; + unsigned short regs[WM8776_NUM_REGS]; + unsigned char volumes[WM8776_NUM_VOLUMES]; + unsigned int switches:WM8776_NUM_SWITCHES; /* bit field */ + struct mutex wm_mutex; /* mutex for WM8776 register access */ +}; + +struct snd_wm8776_volume_info { + /* number of levels (not counting mute) */ + unsigned char nlvl; + /* declaration of dB scale (using DECLARE_TLV_DB_SCALE) */ + const unsigned int *tlv_scale; + + /*private data*/ + unsigned char reg_idx; + unsigned short reg_mask; + unsigned short reg_low; /* register value for lowest level */ + unsigned short reg_mute; /* register value for mute */ +}; + +struct snd_wm8776_switch_info { + unsigned char n_states; + + /*private data*/ + unsigned char reg_idx; + unsigned char reg_inv; + unsigned short reg_mask; +}; + +void snd_wm8776_reset(struct snd_wm8776 *wm, int state); +void snd_wm8776_init(struct snd_wm8776 *wm, struct snd_card *card, + void *private_data, int chip, + const struct snd_wm8776_ops *ops); + +void snd_wm8776_reg_write(struct snd_wm8776 *wm, unsigned char reg, + unsigned short val); +#define snd_wm8776_reg_get(wm, reg) ((wm)->regs[(reg)]) + +int snd_wm8776_set_volume(struct snd_wm8776 *wm, int index, int value); +int snd_wm8776_get_volume(struct snd_wm8776 *wm, int index); +const struct snd_wm8776_volume_info *snd_wm8776_get_volume_info(int index); +int snd_wm8776_set_switch(struct snd_wm8776 *wm, int index, int value); +int snd_wm8776_get_switch(struct snd_wm8776 *wm, int index); +const struct snd_wm8776_switch_info *snd_wm8776_get_switch_info(int index); +void snd_wm8776_set_rate(struct snd_wm8776 *wm, unsigned char clock_ratio); + + +#endif /* __SOUND_WM8776_H */ diff -r 0d5f43585ca7 pci/ice1712/Makefile --- a/pci/ice1712/Makefile Sat Mar 22 10:26:05 2008 +0100 +++ b/pci/ice1712/Makefile Fri Mar 28 21:47:56 2008 +0100 @@ -5,7 +5,7 @@ snd-ice17xx-ak4xxx-objs := ak4xxx.o snd-ice1712-objs := ice1712.o delta.o hoontech.o ews.o -snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o +snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o # Toplevel Module Dependency obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o diff -r 0d5f43585ca7 pci/ice1712/ice1712.h --- a/pci/ice1712/ice1712.h Sat Mar 22 10:26:05 2008 +0100 +++ b/pci/ice1712/ice1712.h Fri Mar 28 21:47:56 2008 +0100 @@ -452,10 +452,17 @@ static inline int snd_ice1712_gpio_read_ return (snd_ice1712_gpio_read(ice) & mask); } +/* route access functions */ +int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift); +int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val, + int shift); + int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice); -int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *template, - const struct snd_ak4xxx_private *priv, struct snd_ice1712 *ice); +int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, + const struct snd_akm4xxx *template, + const struct snd_ak4xxx_private *priv, + struct snd_ice1712 *ice); void snd_ice1712_akm4xxx_free(struct snd_ice1712 *ice); int snd_ice1712_akm4xxx_build_controls(struct snd_ice1712 *ice); diff -r 0d5f43585ca7 pci/ice1712/ice1724.c --- a/pci/ice1712/ice1724.c Sat Mar 22 10:26:05 2008 +0100 +++ b/pci/ice1712/ice1724.c Fri Mar 28 21:47:56 2008 +0100 @@ -49,6 +49,7 @@ #include "prodigy192.h" #include "prodigy_hifi.h" #include "juli.h" +#include "maya44.h" #include "phase.h" #include "wtm.h" #include "se.h" @@ -65,6 +66,7 @@ MODULE_SUPPORTED_DEVICE("{" PRODIGY192_DEVICE_DESC PRODIGY_HIFI_DEVICE_DESC JULI_DEVICE_DESC + MAYA44_DEVICE_DESC PHASE_DEVICE_DESC WTM_DEVICE_DESC SE_DEVICE_DESC @@ -439,7 +441,7 @@ static unsigned char stdclock_set_mclk(s return 0; } -static void snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, +static int snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, int force) { unsigned long flags; @@ -447,17 +449,18 @@ static void snd_vt1724_set_pro_rate(stru unsigned int i, old_rate; if (rate > ice->hw_rates->list[ice->hw_rates->count - 1]) - return; + return -EINVAL; + spin_lock_irqsave(&ice->reg_lock, flags); if ((inb(ICEMT1724(ice, DMA_CONTROL)) & DMA_STARTS) || (inb(ICEMT1724(ice, DMA_PAUSE)) & DMA_PAUSES)) { /* running? we cannot change the rate now... */ spin_unlock_irqrestore(&ice->reg_lock, flags); - return; + return -EBUSY; } if (!force && is_pro_rate_locked(ice)) { spin_unlock_irqrestore(&ice->reg_lock, flags); - return; + return (rate == ice->cur_rate) ? 0 : -EBUSY; } old_rate = ice->get_rate(ice); @@ -465,7 +468,7 @@ static void snd_vt1724_set_pro_rate(stru ice->set_rate(ice, rate); else if (rate == ice->cur_rate) { spin_unlock_irqrestore(&ice->reg_lock, flags); - return; + return 0; } ice->cur_rate = rate; @@ -487,13 +490,15 @@ static void snd_vt1724_set_pro_rate(stru } if (ice->spdif.ops.setup_rate) ice->spdif.ops.setup_rate(ice, rate); + + return 0; } static int snd_vt1724_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_ice1712 *ice = snd_pcm_substream_chip(substream); - int i, chs; + int i, chs, err; chs = params_channels(hw_params); mutex_lock(&ice->open_mutex); @@ -528,7 +533,11 @@ static int snd_vt1724_pcm_hw_params(stru } } mutex_unlock(&ice->open_mutex); - snd_vt1724_set_pro_rate(ice, params_rate(hw_params), 0); + + err = snd_vt1724_set_pro_rate(ice, params_rate(hw_params), 0); + if (err < 0) + return err; + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } @@ -654,19 +663,39 @@ static snd_pcm_uframes_t snd_vt1724_pcm_ #endif } -static const struct vt1724_pcm_reg vt1724_playback_pro_reg = { +static const struct vt1724_pcm_reg vt1724_pdma0_reg = { .addr = VT1724_MT_PLAYBACK_ADDR, .size = VT1724_MT_PLAYBACK_SIZE, .count = VT1724_MT_PLAYBACK_COUNT, .start = VT1724_PDMA0_START, }; -static const struct vt1724_pcm_reg vt1724_capture_pro_reg = { +static const struct vt1724_pcm_reg vt1724_pdma4_reg = { + .addr = VT1724_MT_PDMA4_ADDR, + .size = VT1724_MT_PDMA4_SIZE, + .count = VT1724_MT_PDMA4_COUNT, + .start = VT1724_PDMA4_START, +}; + +static const struct vt1724_pcm_reg vt1724_rdma0_reg = { .addr = VT1724_MT_CAPTURE_ADDR, .size = VT1724_MT_CAPTURE_SIZE, .count = VT1724_MT_CAPTURE_COUNT, .start = VT1724_RDMA0_START, }; + +static const struct vt1724_pcm_reg vt1724_rdma1_reg = { + .addr = VT1724_MT_RDMA1_ADDR, + .size = VT1724_MT_RDMA1_SIZE, + .count = VT1724_MT_RDMA1_COUNT, + .start = VT1724_RDMA1_START, +}; + +#define vt1724_playback_pro_reg vt1724_pdma0_reg +#define vt1724_playback_spdif_reg vt1724_pdma4_reg +#define vt1724_capture_pro_reg vt1724_rdma0_reg +#define vt1724_capture_spdif_reg vt1724_rdma1_reg + static const struct snd_pcm_hardware snd_vt1724_playback_pro = { @@ -801,10 +830,12 @@ static int snd_vt1724_capture_pro_open(s runtime->private_data = (void *)&vt1724_capture_pro_reg; ice->capture_pro_substream = substream; + runtime->hw = snd_vt1724_2ch_stereo; snd_pcm_set_sync(substream); snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); set_rate_constraints(ice, substream); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, VT1724_BUFFER_ALIGN); snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, @@ -830,6 +861,7 @@ static int snd_vt1724_capture_pro_close( if (PRO_RATE_RESET) snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0); ice->capture_pro_substream = NULL; + return 0; } @@ -884,20 +916,6 @@ static int __devinit snd_vt1724_pcm_prof /* * SPDIF PCM */ - -static const struct vt1724_pcm_reg vt1724_playback_spdif_reg = { - .addr = VT1724_MT_PDMA4_ADDR, - .size = VT1724_MT_PDMA4_SIZE, - .count = VT1724_MT_PDMA4_COUNT, - .start = VT1724_PDMA4_START, -}; - -static const struct vt1724_pcm_reg vt1724_capture_spdif_reg = { - .addr = VT1724_MT_RDMA1_ADDR, - .size = VT1724_MT_RDMA1_SIZE, - .count = VT1724_MT_RDMA1_COUNT, - .start = VT1724_RDMA1_START, -}; /* update spdif control bits; call with reg_lock */ static void update_spdif_bits(struct snd_ice1712 *ice, unsigned int val) @@ -1065,7 +1083,7 @@ static int __devinit snd_vt1724_pcm_spdi if (ice->force_pdma4 || ice->force_rdma1) name = "ICE1724 Secondary"; else - name = "IEC1724 IEC958"; + name = "ICE1724 IEC958"; err = snd_pcm_new(ice->card, name, device, play, capt, &pcm); if (err < 0) return err; @@ -1769,7 +1787,7 @@ static inline int digital_route_shift(in return idx * 3; } -static int get_route_val(struct snd_ice1712 *ice, int shift) +int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift) { unsigned long val; unsigned char eitem; @@ -1788,7 +1806,8 @@ static int get_route_val(struct snd_ice1 return eitem; } -static int put_route_val(struct snd_ice1712 *ice, unsigned int val, int shift) +int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val, + int shift) { unsigned int old_val, nval; int change; @@ -1816,7 +1835,7 @@ static int snd_vt1724_pro_route_analog_g struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); ucontrol->value.enumerated.item[0] = - get_route_val(ice, analog_route_shift(idx)); + snd_ice1724_get_route_val(ice, analog_route_shift(idx)); return 0; } @@ -1825,8 +1844,9 @@ static int snd_vt1724_pro_route_analog_p { struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - return put_route_val(ice, ucontrol->value.enumerated.item[0], - analog_route_shift(idx)); + return snd_ice1724_put_route_val(ice, + ucontrol->value.enumerated.item[0], + analog_route_shift(idx)); } static int snd_vt1724_pro_route_spdif_get(struct snd_kcontrol *kcontrol, @@ -1835,7 +1855,7 @@ static int snd_vt1724_pro_route_spdif_ge struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); ucontrol->value.enumerated.item[0] = - get_route_val(ice, digital_route_shift(idx)); + snd_ice1724_get_route_val(ice, digital_route_shift(idx)); return 0; } @@ -1844,11 +1864,13 @@ static int snd_vt1724_pro_route_spdif_pu { struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - return put_route_val(ice, ucontrol->value.enumerated.item[0], - digital_route_shift(idx)); -} - -static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route __devinitdata = { + return snd_ice1724_put_route_val(ice, + ucontrol->value.enumerated.item[0], + digital_route_shift(idx)); +} + +static struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route __devinitdata = +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "H/W Playback Route", .info = snd_vt1724_pro_route_info, @@ -1915,6 +1937,7 @@ static struct snd_ice1712_card_info *car snd_vt1724_prodigy_hifi_cards, snd_vt1724_prodigy192_cards, snd_vt1724_juli_cards, + snd_vt1724_maya44_cards, snd_vt1724_phase_cards, snd_vt1724_wtm_cards, snd_vt1724_se_cards, @@ -2048,9 +2071,12 @@ static int __devinit snd_vt1724_chip_ini static int __devinit snd_vt1724_chip_init(struct snd_ice1712 *ice) { outb(VT1724_RESET , ICEREG1724(ice, CONTROL)); + inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */ udelay(200); outb(0, ICEREG1724(ice, CONTROL)); + inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */ udelay(200); + outb(ice->eeprom.data[ICE_EEP2_SYSCONF], ICEREG1724(ice, SYS_CFG)); outb(ice->eeprom.data[ICE_EEP2_ACLINK], ICEREG1724(ice, AC97_CFG)); outb(ice->eeprom.data[ICE_EEP2_I2S], ICEREG1724(ice, I2S_FEATURES)); @@ -2074,9 +2100,12 @@ static int __devinit snd_vt1724_spdif_bu snd_assert(ice->pcm != NULL, return -EIO); - err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_mixer_pro_spdif_route, ice)); - if (err < 0) - return err; + if (ice->eeprom.subvendor != VT1724_SUBDEVICE_MAYA44) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&snd_vt1724_mixer_pro_spdif_route, ice)); + if (err < 0) + return err; + } err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_spdif_switch, ice)); if (err < 0) @@ -2123,7 +2152,12 @@ static int __devinit snd_vt1724_build_co if (err < 0) return err; - if (ice->num_total_dacs > 0) { + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_MAYA44) { + err = snd_ctl_add(ice->card, + snd_ctl_new1(&snd_vt1724_mixer_pro_maya44_route, ice)); + if (err < 0) + return err; + } else if (ice->num_total_dacs > 0) { struct snd_kcontrol_new tmp = snd_vt1724_mixer_pro_analog_route; tmp.count = ice->num_total_dacs; if (ice->vt1720 && tmp.count > 2) @@ -2390,6 +2424,7 @@ static int __devinit snd_vt1724_probe(st } pci_set_drvdata(pci, card); dev++; + return 0; } diff -r 0d5f43585ca7 pci/ice1712/maya44.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pci/ice1712/maya44.c Fri Mar 28 21:47:56 2008 +0100 @@ -0,0 +1,761 @@ +#include "adriver.h" +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for ESI Maya44 cards + * + * Copyright (c) 2007 Rainer Zimmermann + * + * 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 + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "maya44.h" + +/* + * If defined, allow separate lists of allowable rates for ADC/DAC. + * Actually, ADC & DAC rates are linked, so setting invalid + * ADC rates (>96 kHz) cannot be prevented in all cases. + * NOTE: this requires changes in ice1712.h (see ice1712.h.patch) + * and enabling the corresponding code in ice1724.c + */ +#undef SEPARATE_ADC_RATES + +struct maya44_spec { + struct snd_wm8776 *wm[2]; + unsigned int miodio:1; +}; + +/* + * chip addresses on I2C bus + */ +#define WM8776_0_ADDR 0x34 /* Codec 0 */ +#define WM8776_1_ADDR 0x36 /* Codec 1 */ + +/* + * GPIO pins (known ones for maya44) + */ +#define GPIO_PHANTOM_OFF (1<<2) +#define GPIO_MIC_RELAY (1<<4) +#define GPIO_SPDIF_IN_INV (1<<5) /* CHECK! */ +#define GPIO_MUST_BE_0 (1<<7) + + +/* + * WM8776 codecs section + */ + +static void maya44_wm_reg_write(void *private_data, int chip, + unsigned char addr, unsigned short data) +{ + struct snd_ice1712 *ice = (struct snd_ice1712 *)private_data; + +#if 0 + snd_printd(KERN_DEBUG + "maya44: maya44_wm_reg_write, ice=%p, chip=%i, addr=%i, " + "data=%i\n", + ice, chip, (int)addr, (int)data); +#endif + + snd_assert(chip == 0 || chip == 1, return); + +/* + * WM8776 registers are up to 9 bits wide, bit 8 is placed in the LSB + * of the address field + */ + snd_vt1724_write_i2c(ice, + chip ? WM8776_1_ADDR : WM8776_0_ADDR, + (addr<<1) | ((data>>8) & 1), + data&0xff + ); +} + +/* + * change the rate on the WM8776 codecs. + * this assumes that the VT17xx's rate is changed by the calling function. + * NOTE: even though the WM8776's are running in slave mode and rate + * selection is automatic, we need to call snd_wm8776_set_rate() here + * to make sure some flags are set correctly. + */ +static void set_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + struct maya44_spec *maya44_spec = ice->spec; + int ratio, i; + + switch (rate) { + case 192000: + ratio = WM8776_CLOCK_RATIO_128FS; + break; + case 176400: + ratio = WM8776_CLOCK_RATIO_128FS; + break; + case 96000: + ratio = WM8776_CLOCK_RATIO_256FS; + break; + case 88200: + ratio = WM8776_CLOCK_RATIO_384FS; + break; + case 48000: + ratio = WM8776_CLOCK_RATIO_512FS; + break; + case 44100: + ratio = WM8776_CLOCK_RATIO_512FS; + break; + case 32000: + ratio = WM8776_CLOCK_RATIO_768FS; + break; + case 0: + /* no hint - S/PDIF input is master, simply return */ + return; + default: + snd_BUG(); + return; + } + + snd_printd(KERN_DEBUG "maya44: set rate %i, ratio=%d\n", rate, ratio); + + for (i = 0; i < 2; i++) + snd_wm8776_set_rate(maya44_spec->wm[i], ratio); +} + +static int maya44_wm_dev_free(struct snd_device *device) +{ + snd_printd(KERN_DEBUG + "maya44: maya44_wm_dev_free, dev=%p, device_data=%p\n", + device, device->device_data); + kfree(device->device_data); + return 0; +} + +static struct wm_vol_control { + char *name; + int index[2]; +} wm_vol_controls[] = +{ + { .name = "Crossmix Playback Volume", + .index = { WM8776_VOL_HEADPHONE_L, WM8776_VOL_HEADPHONE_R } }, + { .name = "PCM Playback Volume", + .index = { WM8776_VOL_DAC_L, WM8776_VOL_DAC_R } }, + { .name = "Line Capture Volume", + .index = { WM8776_VOL_ADC_GAIN_L, WM8776_VOL_ADC_GAIN_R } }, + {} /* terminator */ +}; + +/* definition of "1 per codec chip" switches */ + +static struct wm_switch_control { + char *name; + int index; +} wm_switch_controls[] = +{ + { .name = "PCM Playback Switch", .index = WM8776_SW_OUT_DAC }, +/* Aux is not used & set to OFF in wm8776.c by default */ +/* { .name = "Aux Playback Switch", .index = WM8776_SW_OUT_AUX },*/ + { .name = "Bypass Playback Switch", .index = WM8776_SW_OUT_BYPASS }, +#ifdef _TESTMODE_ + { .name = "Line Capture Switch 1", .index = WM8776_SW_IN_1 }, + { .name = "Line Capture Switch 2", .index = WM8776_SW_IN_2 }, + { .name = "Line Capture Switch 3", .index = WM8776_SW_IN_3 }, + { .name = "Line Capture Switch 4", .index = WM8776_SW_IN_4 }, + { .name = "Line Capture Switch 5", .index = WM8776_SW_IN_5 }, +#endif + {} /* terminator */ +}; + +#define WM_MAKE_PRIV(chip, idx) (((chip)<<8)|(idx)) +#define WM_GET_CHIP(priv) ((priv)>>8) +#define WM_GET_IDX(priv) ((priv)&0xff) + +#define TLV_HAS_MUTE(tlv) (((tlv)[3]&0x10000) != 0) + +static int wm_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + const struct snd_wm8776_volume_info *vi = + snd_wm8776_get_volume_info(WM_GET_IDX(kcontrol->private_value)); + +#if 0 + snd_printd(KERN_DEBUG "maya44: wm_vol_info, prv=%i\n", + (int)kcontrol->private_value); +#endif + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* 2 channels per codec */ + uinfo->value.integer.min = TLV_HAS_MUTE(vi->tlv_scale)?0:1; + uinfo->value.integer.max = vi->nlvl; + return 0; +} + +static int wm_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct maya44_spec *maya44_spec = ice->spec; + struct snd_wm8776 *wm; + +#if 0 + snd_printd(KERN_DEBUG "maya44: wm_vol_get, ice=%p, prv=%i\n", + ice, (int)kcontrol->private_value); +#endif + + /* get L+R volumes from one WM8776 codec */ + wm = maya44_spec->wm[WM_GET_CHIP(kcontrol->private_value)]; + /* left channel */ + ucontrol->value.integer.value[0] = + snd_wm8776_get_volume(wm, + WM_GET_IDX(kcontrol->private_value)); + /* right channel */ + ucontrol->value.integer.value[1] = + snd_wm8776_get_volume(wm, + WM_GET_IDX(kcontrol->private_value)+1); + + return 0; +} + +static int wm_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct maya44_spec *maya44_spec = ice->spec; + struct snd_wm8776 *wm; + int changed = 0; + +#if 0 + snd_printd(KERN_DEBUG "maya44: wm_vol_put, ice=%p, prv=%i, val=%i,%i\n", + ice, (int)kcontrol->private_value, + (int)ucontrol->value.integer.value[0], + (int)ucontrol->value.integer.value[1]); +#endif + + /* put L+R volumes for one WM8776 codec */ + wm = maya44_spec->wm[WM_GET_CHIP(kcontrol->private_value)]; + /* left channel */ + changed = snd_wm8776_set_volume(wm, + WM_GET_IDX(kcontrol->private_value), + ucontrol->value.integer.value[0]); + /* right channel */ + changed |= snd_wm8776_set_volume(wm, + WM_GET_IDX(kcontrol->private_value)+1, + ucontrol->value.integer.value[1]); + + return changed; +} + +#if 0 +static int wm_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; /* switches are always one per codec */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} +#endif + +static int wm_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct maya44_spec *maya44_spec = ice->spec; + +#if 0 + snd_printd(KERN_DEBUG "maya44: wm_switch_get, ice=%p, prv=%i\n", + ice, (int)kcontrol->private_value); +#endif + + /* get switch from one WM8776 codec */ + ucontrol->value.integer.value[0] = + snd_wm8776_get_switch( + maya44_spec->wm[WM_GET_CHIP(kcontrol->private_value)], + WM_GET_IDX(kcontrol->private_value)); + + return 0; +} + +static int wm_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct maya44_spec *maya44_spec = ice->spec; + int changed = 0; + +#if 0 + snd_printd(KERN_DEBUG "maya44: wm_switch_put, ice=%p, prv=%i\n", + ice, (int)kcontrol->private_value); +#endif + + /* put switch for one WM8776 codec */ + changed = snd_wm8776_set_switch( + maya44_spec->wm[WM_GET_CHIP(kcontrol->private_value)], + WM_GET_IDX(kcontrol->private_value), + ucontrol->value.integer.value[0]); + + return changed; +} + +#ifdef _TESTMODE_ + +static int gpio_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int data, mask = 1<private_value; + +#if 0 + snd_printd(KERN_DEBUG "maya44: gpio_switch_get, ice=%p, prv=%i\n", + ice, (int)kcontrol->private_value); +#endif + + data = ice->gpio.get_data(ice); + ucontrol->value.integer.value[0] = ((data & mask) != 0); + return 0; +} + +static int gpio_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int data, mask = 1<private_value; + unsigned int bit = ucontrol->value.integer.value[0]?mask:0; + int changed = 0; + +#if 0 + snd_printd(KERN_DEBUG "maya44: gpio_switch_put, ice=%p, prv=%i\n", + ice, (int)kcontrol->private_value); +#endif + + mutex_lock(&ice->gpio_mutex); + data = ice->gpio.get_data(ice); + if ((data&mask) != bit) { + ice->gpio.set_data(ice, (data & ~mask)|bit); + changed = 1; + } + mutex_unlock(&ice->gpio_mutex); + return changed; +} + +#endif /* _TESTMODE_ */ + +enum { SW_MIC = 0, SW_PHANTOM, SW_SPDIF_IN }; + +static const char *other_switch_names[] = { + [SW_MIC] = "Mic/Line Input Switch", + [SW_PHANTOM] = "Mic Phantom+48V Switch", +/* + * the next one should probably be called "IEC958 Capture Switch", but + * then alsamixer wouldn't handle it properly. + */ + [SW_SPDIF_IN] = "S/PDIF Capture Switch", + NULL /* terminator */ +}; + +static unsigned char sw_other[] = { + [SW_MIC] = 0, + [SW_PHANTOM] = 0, + [SW_SPDIF_IN] = 0 +}; + +static int set_gpio_bits(struct snd_ice1712 *ice, unsigned int mask, + unsigned int bits) +{ + unsigned int data; + unsigned char changed = 0; + mutex_lock(&ice->gpio_mutex); + data = ice->gpio.get_data(ice); + if ((data&mask) != bits) { + ice->gpio.set_data(ice, (data & ~mask)|bits); + changed = 1; + } + mutex_unlock(&ice->gpio_mutex); + return changed; +} + +static int other_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ +#if 0 + snd_printd(KERN_DEBUG "maya44: other_switch_get, prv=%i\n", + (int)kcontrol->private_value); +#endif + + ucontrol->value.integer.value[0] = + sw_other[(int)kcontrol->private_value]; + return 0; +} + +static int other_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct maya44_spec *maya44_spec = ice->spec; + int j; + int set = ucontrol->value.integer.value[0]; + int idx = kcontrol->private_value; + +#if 0 + snd_printd(KERN_DEBUG "maya44: other_switch_put, ice=%p, prv=%i\n", + ice, (int)kcontrol->private_value); +#endif + + if (set == sw_other[idx]) + return 0; /* not changed */ + + switch (kcontrol->private_value) { + case SW_PHANTOM: + set_gpio_bits(ice, GPIO_PHANTOM_OFF, + set ? 0 : GPIO_PHANTOM_OFF); + break; + case SW_MIC: + set_gpio_bits(ice, GPIO_MIC_RELAY, + set ? GPIO_MIC_RELAY : 0); + /* line input = IN2, mic input = IN4 */ + for (j = WM8776_SW_IN_1; j <= WM8776_SW_IN_5; j++) + snd_wm8776_set_switch( + maya44_spec->wm[0], + j, + (j == (set?WM8776_SW_IN_4:WM8776_SW_IN_2))); + break; + case SW_SPDIF_IN: + set_gpio_bits(ice, GPIO_SPDIF_IN_INV, + set ? 0 : GPIO_SPDIF_IN_INV); + break; + default: + snd_BUG(); + return 0; + } + + sw_other[idx] = set; + return 1; +} + + +/* + * Maya44 routing switch settings have different meanings than the standard + * ice1724 switches as defined in snd_vt1724_pro_route_info (ice1724.c). + */ +int snd_vt1724_pro_route_maya44_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[] = { + "PCM Out", /* 0 */ + "Input 1", "Input 2", "Input 3", "Input 4" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 5; + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + + strcpy(uinfo->value.enumerated.name, + texts[uinfo->value.enumerated.item]); + return 0; +} + +int maya44_route_shift(int idx) +{ + static const unsigned char shift[10] = + { 8, 20, 0, 3, 11, 23, 14, 26, 17, 29 }; + return shift[idx%10]; +} + +static int snd_vt1724_pro_route_maya44_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + ucontrol->value.enumerated.item[0] = + snd_ice1724_get_route_val(ice, maya44_route_shift(idx)); + return 0; +} + +static int snd_vt1724_pro_route_maya44_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + return snd_ice1724_put_route_val(ice, + ucontrol->value.enumerated.item[0], + maya44_route_shift(idx)); +} + + +struct snd_kcontrol_new snd_vt1724_mixer_pro_maya44_route __devinitdata = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "H/W Playback Route", + .info = snd_vt1724_pro_route_maya44_info, + .get = snd_vt1724_pro_route_maya44_get, + .put = snd_vt1724_pro_route_maya44_put, +#if 1 + .count = 10 /* XXX check whether controls 5-9 have any meaning */ +#else + .count = 4 +#endif +}; + +static int __devinit maya44_add_controls(struct snd_ice1712 *ice) +{ + int err, i; + struct snd_kcontrol_new newc; + struct wm_vol_control *pv; + struct wm_switch_control *ps; + +#if 0 + snd_printd(KERN_DEBUG "maya44: maya44_add_controls, ice=%p\n", ice); +#endif + + /* add volume controls for both WM8776 codecs */ + + memset(&newc, 0, sizeof(newc)); + newc.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + newc.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + newc.info = wm_vol_info; + newc.get = wm_vol_get; + newc.put = wm_vol_put; + newc.count = 1; + + for (pv = wm_vol_controls; pv->name != NULL; pv++) { + newc.name = pv->name; + newc.tlv.p = + snd_wm8776_get_volume_info(pv->index[0])->tlv_scale; + + for (newc.index = 0; newc.index < 2; newc.index++) { + newc.private_value = + WM_MAKE_PRIV(newc.index, pv->index[0]); +#if 0 + snd_printd(KERN_DEBUG + "maya44: add control '%s' idx %i, pval=%i\n", + pv->name, (int)newc.index, + (int)newc.private_value); +#endif + + err = snd_ctl_add(ice->card, snd_ctl_new1(&newc, ice)); +#if 0 + snd_printd(KERN_DEBUG "maya44: err=%i\n", err); +#endif + if (err < 0) + return err; + } + } + + /* add switch controls for both WM8776 codecs */ + + memset(&newc, 0, sizeof(newc)); + newc.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + newc.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + newc.info = /*wm_switch_info*/ snd_ctl_boolean_mono_info; + newc.get = wm_switch_get; + newc.put = wm_switch_put; + newc.count = 1; + + for (ps = wm_switch_controls; ps->name != NULL; ps++) { + newc.name = ps->name; + for (newc.index = 0; newc.index < 2; newc.index++) { + newc.private_value = + WM_MAKE_PRIV(newc.index, ps->index); + err = snd_ctl_add(ice->card, snd_ctl_new1(&newc, ice)); + if (err < 0) + return err; + } + } + +#ifdef _TESTMODE_ + /* add GPIO controls */ + + memset(&newc, 0, sizeof(newc)); + newc.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + newc.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + newc.info = snd_ctl_boolean_mono_info; /* simple switch */ + newc.get = gpio_switch_get; + newc.put = gpio_switch_put; + newc.count = 1; + + { + char gpname[40]; + int i; + for (i = 0; i < 24; i++) { + sprintf(gpname, "GPIO bit %02i", i); + newc.name = gpname; + newc.private_value = i; + + err = snd_ctl_add(ice->card, snd_ctl_new1(&newc, ice)); + if (err < 0) + return err; + } + } +#endif + + /* add other controls */ + + memset(&newc, 0, sizeof(newc)); + newc.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + newc.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + newc.info = snd_ctl_boolean_mono_info; /* simple switch */ + newc.get = other_switch_get; + newc.put = other_switch_put; + newc.count = 1; + + for (i = 0; (newc.name = (char *)other_switch_names[i]) != NULL; i++) { + newc.private_value = i; +#if 0 + snd_printd(KERN_DEBUG "maya44: add control '%s', i=%i\n", + newc.name, i); +#endif + err = snd_ctl_add(ice->card, snd_ctl_new1(&newc, ice)); + if (err < 0) + return err; + } + + return 0; +} + +/* + * initialize the chip + */ +static int __devinit maya44_init(struct snd_ice1712 *ice) +{ + int err, i, j; + struct maya44_spec *maya44_spec; + static unsigned int rates[] = { + 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000 + }; +#ifdef SEPARATE_ADC_RATES + /* record rates: 32..96 kHz */ + static struct snd_pcm_hw_constraint_list adc_rates = { + .count = ARRAY_SIZE(rates)-2, + .list = rates, + .mask = 0 + }; +#endif + /* playback rates: 32..192 kHz */ + static struct snd_pcm_hw_constraint_list dac_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0 + }; + + static struct snd_wm8776_ops _wm_ops = { .write = maya44_wm_reg_write }; + static struct snd_device_ops _dev_ops = { + .dev_free = maya44_wm_dev_free, + /* .dev_register=..., */ + /* .dev_disconnect=... */ + }; + + snd_printd(KERN_DEBUG "maya44: maya44_init start\n"); + + maya44_spec = kzalloc(sizeof(*maya44_spec), GFP_KERNEL); + if (!maya44_spec) + return -ENOMEM; + ice->spec = maya44_spec; + + /* initialise codecs */ + ice->num_total_dacs = 4; + ice->num_total_adcs = 4; + ice->akm_codecs = 0; + + for (i = 0; i < 2; i++) { + struct snd_wm8776 *wm; + wm = maya44_spec->wm[i] = + kzalloc(sizeof(struct snd_wm8776), GFP_KERNEL); + if (!wm) + return -ENOMEM; + err = snd_device_new(ice->card, SNDRV_DEV_LOWLEVEL, + wm, &_dev_ops); + if (err < 0) { + kfree(wm); + return err; + } + snd_wm8776_init(wm, ice->card, ice, i, &_wm_ops); + snd_printd(KERN_DEBUG "maya44: wm8776_init %i done\n", i); + + /* Maya44 line inputs are seen on IN2 */ + for (j = WM8776_SW_IN_1; j <= WM8776_SW_IN_5; j++) + snd_wm8776_set_switch(wm, j, (j == WM8776_SW_IN_2)); + } + +/* + * set card specific rates. + */ +#ifdef SEPARATE_ADC_RATES + ice->hw_rates = &dac_rates; + ice->hw_rates_adc = &adc_rates; +#else + ice->hw_rates = &dac_rates; +#endif + + /* register change rate notifier */ + ice->gpio.set_pro_rate = set_rate; + + /* RDMA1 (2nd input channel) is used for ADC by default */ + ice->force_rdma1 = 1; + + snd_printd(KERN_DEBUG "maya44: maya44_init finished\n"); + + return 0; +} + + +/* + * Maya44 boards don't provide the EEPROM data except for the vendor IDs. + * hence the driver needs to sets up it properly. + */ + +static unsigned char maya44_eeprom[] __devinitdata = { + [ICE_EEP2_SYSCONF] = 0x45, + /* clock xin1=49.152MHz, mpu401, 2 stereo ADCs+DACs */ + [ICE_EEP2_ACLINK] = 0x80, + /* I2S */ + [ICE_EEP2_I2S] = 0xf8, + /* vol, 96k, 24bit, 192k */ + [ICE_EEP2_SPDIF] = 0xc3, + /* enable spdif out, spdif out supp, spdif-in, ext spdif out */ + [ICE_EEP2_GPIO_DIR] = 0xff, + [ICE_EEP2_GPIO_DIR1] = 0xff, + [ICE_EEP2_GPIO_DIR2] = 0xff, + [ICE_EEP2_GPIO_MASK] = 0/*0x9f*/, + [ICE_EEP2_GPIO_MASK1] = 0/*0xff*/, + [ICE_EEP2_GPIO_MASK2] = 0/*0x7f*/, + [ICE_EEP2_GPIO_STATE] = GPIO_PHANTOM_OFF | GPIO_SPDIF_IN_INV, + [ICE_EEP2_GPIO_STATE1] = 0x00, + [ICE_EEP2_GPIO_STATE2] = 0x00, +}; + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_maya44_cards[] __devinitdata = { + { + .subvendor = VT1724_SUBDEVICE_MAYA44, + .name = "ESI Maya44", + .model = "maya44", + .chip_init = maya44_init, + .build_controls = maya44_add_controls, + .eeprom_size = sizeof(maya44_eeprom), + .eeprom_data = maya44_eeprom, + }, + { } /* terminator */ +}; diff -r 0d5f43585ca7 pci/ice1712/maya44.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pci/ice1712/maya44.h Fri Mar 28 21:47:56 2008 +0100 @@ -0,0 +1,16 @@ +#ifndef __SOUND_MAYA44_H +#define __SOUND_MAYA44_H + +#define MAYA44_DEVICE_DESC "{ESI,Maya44}," + +#define VT1724_SUBDEVICE_MAYA44 0x34315441 /* Maya44 */ + +extern struct snd_ice1712_card_info snd_vt1724_maya44_cards[]; + +int snd_vt1724_pro_route_maya44_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +int maya44_route_shift(int idx); + +extern struct snd_kcontrol_new snd_vt1724_mixer_pro_maya44_route; + +#endif /* __SOUND_MAYA44_H */