/*
* Driver for Digigram VX soundcards
*
* Common mixer part
*
* Copyright (c) 2002 by 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
*/
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include <sound/vx_core.h>
#include "vx_cmd.h"
/*
* write a codec data (24bit)
*/
static void vx_write_codec_reg(struct vx_core *chip, int codec, unsigned int data)
{
unsigned long flags;
if (snd_BUG_ON(!chip->ops->write_codec))
return;
if (chip->chip_status & VX_STAT_IS_STALE)
return;
spin_lock_irqsave(&chip->lock, flags);
chip->ops->write_codec(chip, codec, data);
spin_unlock_irqrestore(&chip->lock, flags);
}
/*
* Data type used to access the Codec
*/
union vx_codec_data {
u32 l;
#ifdef SNDRV_BIG_ENDIAN
struct w {
u16 h;
u16 l;
} w;
struct b {
u8 hh;
u8 mh;
u8 ml;
u8 ll;
} b;
#else /* LITTLE_ENDIAN */
struct w {
u16 l;
u16 h;
} w;
struct b {
u8 ll;
u8 ml;
u8 mh;
u8 hh;
} b;
#endif
};
#define SET_CDC_DATA_SEL(di,s) ((di).b.mh = (u8) (s))
#define SET_CDC_DATA_REG(di,r) ((di).b.ml = (u8) (r))
#define SET_CDC_DATA_VAL(di,d) ((di).b.ll = (u8) (d))
#define SET_CDC_DATA_INIT(di) ((di).l = 0L, SET_CDC_DATA_SEL(di,XX_CODEC_SELECTOR))
/*
* set up codec register and write the value
* @codec: the codec id, 0 or 1
* @reg: register index
* @val: data value
*/
static void vx_set_codec_reg(struct vx_core *chip, int codec, int reg, int val)
{
union vx_codec_data data;
/* DAC control register */
SET_CDC_DATA_INIT(data);
SET_CDC_DATA_REG(data, reg);
SET_CDC_DATA_VAL(data, val);
vx_write_codec_reg(chip, codec, data.l);
}
/*
* vx_set_analog_output_level - set the output attenuation level
* @codec: the output codec, 0 or 1. (1 for VXP440 only)
* @left: left output level, 0 = mute
* @right: right output level
*/
static void vx_set_analog_output_level(struct vx_core *chip, int codec, int left, int right)
{
left = chip->hw->output_level_max - left;
right = chip->hw->output_level_max - right;
if (chip->ops->akm_write) {
chip->ops->akm_write(chip, XX_CODEC_LEVEL_LEFT_REGISTER, left);
chip->ops->akm_write(chip, XX_CODEC_LEVEL_RIGHT_REGISTER, right);
} else {
/* convert to attenuation level: 0 = 0dB (max), 0xe3 = -113.5 dB (min) */
vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_LEFT_REGISTER, left);
vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_RIGHT_REGISTER, right);
}
}
/*
* vx_toggle_dac_mute - mute/unmute DAC
* @mute: 0 = unmute, 1 = mute
*/
#define DAC_ATTEN_MIN 0x08
#define DAC_ATTEN_MAX 0x38
void vx_toggle_dac_mute(struct vx_core *chip, int mute)
{
unsigned int i;
for (i = 0; i < chip->hw->num_codecs; i++) {
if (chip->ops->akm_write)
chip->ops->akm_write(chip, XX_CODEC_DAC_CONTROL_REGISTER, mute); /* XXX */
else
vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER,
mute ? DAC_ATTEN_MAX : DAC_ATTEN_MIN);
}
}
/*
* vx_reset_codec - reset and initialize the codecs
*/
void vx_reset_codec(struct vx_core *chip, int cold_reset)
{
unsigned int i;
int port = chip->type >= VX_TYPE_VXPOCKET ? 0x75 : 0x65;
chip->ops->reset_codec(chip);
/* AKM codecs should be initialized in reset_codec callback */
if (! chip->ops->akm_write) {
/* initialize old codecs */
for (i = 0; i < chip->hw->num_codecs; i++) {
/* DAC control register (change level when zero crossing + mute) */
vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, DAC_ATTEN_MAX);
/* ADC control register */
vx_set_codec_reg(chip, i, XX_CODEC_ADC_CONTROL_REGISTER, 0x00);
/* Port mode register */
vx_set_codec_reg(chip, i, XX_CODEC_PORT_MODE_REGISTER, port);
/* Clock control register */
vx_set_codec_reg(chip, i, XX_CODEC_CLOCK_CONTROL_REGISTER, 0x00);
}
}
/* mute analog output */
for (