// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Routines for Sound Blaster mixer control
*/
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/sb.h>
#include <sound/control.h>
#undef IO_DEBUG
void snd_sbmixer_write(struct snd_sb *chip, unsigned char reg, unsigned char data)
{
outb(reg, SBP(chip, MIXER_ADDR));
udelay(10);
outb(data, SBP(chip, MIXER_DATA));
udelay(10);
#ifdef IO_DEBUG
snd_printk(KERN_DEBUG "mixer_write 0x%x 0x%x\n", reg, data);
#endif
}
unsigned char snd_sbmixer_read(struct snd_sb *chip, unsigned char reg)
{
unsigned char result;
outb(reg, SBP(chip, MIXER_ADDR));
udelay(10);
result = inb(SBP(chip, MIXER_DATA));
udelay(10);
#ifdef IO_DEBUG
snd_printk(KERN_DEBUG "mixer_read 0x%x 0x%x\n", reg, result);
#endif
return result;
}
/*
* Single channel mixer element
*/
static int snd_sbmixer_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 24) & 0xff;
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 16) & 0xff;
int mask = (kcontrol->private_value >> 24) & 0xff;
unsigned char val;
spin_lock_irqsave(&sb->mixer_lock, flags);
val = (snd_sbmixer_read(sb, reg) >> shift) & mask;
spin_unlock_irqrestore(&sb->mixer_lock, flags);
ucontrol->value.integer.value[0] = val;
return 0;
}
static int snd_sbmixer_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int reg = kcontrol->private_value & 0xff;
int shift = (kcontrol->private_value >> 16) & 0x07;
int mask = (kcontrol->private_value >> 24) & 0xff;
int change;
unsigned char val, oval;
val = (ucontrol->value.integer.value[0] & mask) << shift;
spin_lock_irqsave(&sb->mixer_lock, flags);
oval = snd_sbmixer_read(sb, reg);
val = (oval & ~(mask << shift)) | val;
change = val != oval;
if (change)
snd_sbmixer_write(sb, reg, val);
spin_unlock_irqrestore(&sb->mixer_lock, flags);
return change;
}
/*
* Double channel mixer element
*/
static int snd_sbmixer_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 24) & 0xff;
uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
static int snd_sbmixer_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_sb *sb = snd_kcontrol_chip(kcontrol);
unsigned long flags;
int left_reg = kcontrol->private_value & 0xff;
int right_reg = (kcontrol->private_value >> 8) & 0xff;
int left_shift = (kcontrol->private_value >> 16) & 0x07;
int right_shift =