mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 01:51:34 +00:00
df8bcf36be
The recent refactoring of es1688 object creation with the use of
devres caused a problem with the non-PnP probe of GUS driver, as it
tries to probe multiple times with different parameters That is, this
object needs the immediate resource release and the devres doesn't fit
for it.
This patch reverts partially the commit for restoring the classic
resource management for es1688 object.
Fixes: 1bb11c1c7f
("ALSA: es1688: Allocate resources with device-managed APIs")
Reported-by: kernel test robot <oliver.sang@intel.com>
Link: https://lore.kernel.org/r/20210805032513.GA30485@xsang-OptiPlex-9020
Link: https://lore.kernel.org/r/20210805062148.30951-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
993 lines
29 KiB
C
993 lines
29 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
|
* Routines for control of ESS ES1688/688/488 chip
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/module.h>
|
|
#include <linux/io.h>
|
|
#include <sound/core.h>
|
|
#include <sound/es1688.h>
|
|
#include <sound/initval.h>
|
|
|
|
#include <asm/dma.h>
|
|
|
|
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
|
MODULE_DESCRIPTION("ESS ESx688 lowlevel module");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
static int snd_es1688_dsp_command(struct snd_es1688 *chip, unsigned char val)
|
|
{
|
|
int i;
|
|
|
|
for (i = 10000; i; i--)
|
|
if ((inb(ES1688P(chip, STATUS)) & 0x80) == 0) {
|
|
outb(val, ES1688P(chip, COMMAND));
|
|
return 1;
|
|
}
|
|
#ifdef CONFIG_SND_DEBUG
|
|
printk(KERN_DEBUG "snd_es1688_dsp_command: timeout (0x%x)\n", val);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_dsp_get_byte(struct snd_es1688 *chip)
|
|
{
|
|
int i;
|
|
|
|
for (i = 1000; i; i--)
|
|
if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80)
|
|
return inb(ES1688P(chip, READ));
|
|
snd_printd("es1688 get byte failed: 0x%lx = 0x%x!!!\n", ES1688P(chip, DATA_AVAIL), inb(ES1688P(chip, DATA_AVAIL)));
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int snd_es1688_write(struct snd_es1688 *chip,
|
|
unsigned char reg, unsigned char data)
|
|
{
|
|
if (!snd_es1688_dsp_command(chip, reg))
|
|
return 0;
|
|
return snd_es1688_dsp_command(chip, data);
|
|
}
|
|
|
|
static int snd_es1688_read(struct snd_es1688 *chip, unsigned char reg)
|
|
{
|
|
/* Read a byte from an extended mode register of ES1688 */
|
|
if (!snd_es1688_dsp_command(chip, 0xc0))
|
|
return -1;
|
|
if (!snd_es1688_dsp_command(chip, reg))
|
|
return -1;
|
|
return snd_es1688_dsp_get_byte(chip);
|
|
}
|
|
|
|
void snd_es1688_mixer_write(struct snd_es1688 *chip,
|
|
unsigned char reg, unsigned char data)
|
|
{
|
|
outb(reg, ES1688P(chip, MIXER_ADDR));
|
|
udelay(10);
|
|
outb(data, ES1688P(chip, MIXER_DATA));
|
|
udelay(10);
|
|
}
|
|
|
|
static unsigned char snd_es1688_mixer_read(struct snd_es1688 *chip, unsigned char reg)
|
|
{
|
|
unsigned char result;
|
|
|
|
outb(reg, ES1688P(chip, MIXER_ADDR));
|
|
udelay(10);
|
|
result = inb(ES1688P(chip, MIXER_DATA));
|
|
udelay(10);
|
|
return result;
|
|
}
|
|
|
|
int snd_es1688_reset(struct snd_es1688 *chip)
|
|
{
|
|
int i;
|
|
|
|
outb(3, ES1688P(chip, RESET)); /* valid only for ESS chips, SB -> 1 */
|
|
udelay(10);
|
|
outb(0, ES1688P(chip, RESET));
|
|
udelay(30);
|
|
for (i = 0; i < 1000 && !(inb(ES1688P(chip, DATA_AVAIL)) & 0x80); i++);
|
|
if (inb(ES1688P(chip, READ)) != 0xaa) {
|
|
snd_printd("ess_reset at 0x%lx: failed!!!\n", chip->port);
|
|
return -ENODEV;
|
|
}
|
|
snd_es1688_dsp_command(chip, 0xc6); /* enable extended mode */
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(snd_es1688_reset);
|
|
|
|
static int snd_es1688_probe(struct snd_es1688 *chip)
|
|
{
|
|
unsigned long flags;
|
|
unsigned short major, minor;
|
|
int i;
|
|
|
|
/*
|
|
* initialization sequence
|
|
*/
|
|
|
|
spin_lock_irqsave(&chip->reg_lock, flags); /* Some ESS1688 cards need this */
|
|
inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
|
|
inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
|
|
inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
|
|
inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */
|
|
inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
|
|
inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */
|
|
inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
|
|
inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
|
|
inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */
|
|
inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */
|
|
inb(ES1688P(chip, ENABLE0)); /* ENABLE0 */
|
|
|
|
if (snd_es1688_reset(chip) < 0) {
|
|
snd_printdd("ESS: [0x%lx] reset failed... 0x%x\n", chip->port, inb(ES1688P(chip, READ)));
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
return -ENODEV;
|
|
}
|
|
snd_es1688_dsp_command(chip, 0xe7); /* return identification */
|
|
|
|
for (i = 1000, major = minor = 0; i; i--) {
|
|
if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80) {
|
|
if (major == 0) {
|
|
major = inb(ES1688P(chip, READ));
|
|
} else {
|
|
minor = inb(ES1688P(chip, READ));
|
|
}
|
|
}
|
|
}
|
|
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
|
|
snd_printdd("ESS: [0x%lx] found.. major = 0x%x, minor = 0x%x\n", chip->port, major, minor);
|
|
|
|
chip->version = (major << 8) | minor;
|
|
if (!chip->version)
|
|
return -ENODEV; /* probably SB */
|
|
|
|
switch (chip->version & 0xfff0) {
|
|
case 0x4880:
|
|
snd_printk(KERN_ERR "[0x%lx] ESS: AudioDrive ES488 detected, "
|
|
"but driver is in another place\n", chip->port);
|
|
return -ENODEV;
|
|
case 0x6880:
|
|
break;
|
|
default:
|
|
snd_printk(KERN_ERR "[0x%lx] ESS: unknown AudioDrive chip "
|
|
"with version 0x%x (Jazz16 soundcard?)\n",
|
|
chip->port, chip->version);
|
|
return -ENODEV;
|
|
}
|
|
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_write(chip, 0xb1, 0x10); /* disable IRQ */
|
|
snd_es1688_write(chip, 0xb2, 0x00); /* disable DMA */
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
|
|
/* enable joystick, but disable OPL3 */
|
|
spin_lock_irqsave(&chip->mixer_lock, flags);
|
|
snd_es1688_mixer_write(chip, 0x40, 0x01);
|
|
spin_unlock_irqrestore(&chip->mixer_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_init(struct snd_es1688 * chip, int enable)
|
|
{
|
|
static const int irqs[16] = {-1, -1, 0, -1, -1, 1, -1, 2, -1, 0, 3, -1, -1, -1, -1, -1};
|
|
unsigned long flags;
|
|
int cfg, irq_bits, dma, dma_bits, tmp, tmp1;
|
|
|
|
/* ok.. setup MPU-401 port and joystick and OPL3 */
|
|
cfg = 0x01; /* enable joystick, but disable OPL3 */
|
|
if (enable && chip->mpu_port >= 0x300 && chip->mpu_irq > 0 && chip->hardware != ES1688_HW_688) {
|
|
tmp = (chip->mpu_port & 0x0f0) >> 4;
|
|
if (tmp <= 3) {
|
|
switch (chip->mpu_irq) {
|
|
case 9:
|
|
tmp1 = 4;
|
|
break;
|
|
case 5:
|
|
tmp1 = 5;
|
|
break;
|
|
case 7:
|
|
tmp1 = 6;
|
|
break;
|
|
case 10:
|
|
tmp1 = 7;
|
|
break;
|
|
default:
|
|
tmp1 = 0;
|
|
}
|
|
if (tmp1) {
|
|
cfg |= (tmp << 3) | (tmp1 << 5);
|
|
}
|
|
}
|
|
}
|
|
#if 0
|
|
snd_printk(KERN_DEBUG "mpu cfg = 0x%x\n", cfg);
|
|
#endif
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_mixer_write(chip, 0x40, cfg);
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
/* --- */
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_read(chip, 0xb1);
|
|
snd_es1688_read(chip, 0xb2);
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
if (enable) {
|
|
cfg = 0xf0; /* enable only DMA counter interrupt */
|
|
irq_bits = irqs[chip->irq & 0x0f];
|
|
if (irq_bits < 0) {
|
|
snd_printk(KERN_ERR "[0x%lx] ESS: bad IRQ %d "
|
|
"for ES1688 chip!!\n",
|
|
chip->port, chip->irq);
|
|
#if 0
|
|
irq_bits = 0;
|
|
cfg = 0x10;
|
|
#endif
|
|
return -EINVAL;
|
|
}
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_write(chip, 0xb1, cfg | (irq_bits << 2));
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
cfg = 0xf0; /* extended mode DMA enable */
|
|
dma = chip->dma8;
|
|
if (dma > 3 || dma == 2) {
|
|
snd_printk(KERN_ERR "[0x%lx] ESS: bad DMA channel %d "
|
|
"for ES1688 chip!!\n", chip->port, dma);
|
|
#if 0
|
|
dma_bits = 0;
|
|
cfg = 0x00; /* disable all DMA */
|
|
#endif
|
|
return -EINVAL;
|
|
} else {
|
|
dma_bits = dma;
|
|
if (dma != 3)
|
|
dma_bits++;
|
|
}
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_write(chip, 0xb2, cfg | (dma_bits << 2));
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
} else {
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_write(chip, 0xb1, 0x10); /* disable IRQ */
|
|
snd_es1688_write(chip, 0xb2, 0x00); /* disable DMA */
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
}
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_read(chip, 0xb1);
|
|
snd_es1688_read(chip, 0xb2);
|
|
snd_es1688_reset(chip);
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
static const struct snd_ratnum clocks[2] = {
|
|
{
|
|
.num = 795444,
|
|
.den_min = 1,
|
|
.den_max = 128,
|
|
.den_step = 1,
|
|
},
|
|
{
|
|
.num = 397722,
|
|
.den_min = 1,
|
|
.den_max = 128,
|
|
.den_step = 1,
|
|
}
|
|
};
|
|
|
|
static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = {
|
|
.nrats = 2,
|
|
.rats = clocks,
|
|
};
|
|
|
|
static void snd_es1688_set_rate(struct snd_es1688 *chip, struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
unsigned int bits, divider;
|
|
|
|
if (runtime->rate_num == clocks[0].num)
|
|
bits = 256 - runtime->rate_den;
|
|
else
|
|
bits = 128 - runtime->rate_den;
|
|
/* set filter register */
|
|
divider = 256 - 7160000*20/(8*82*runtime->rate);
|
|
/* write result to hardware */
|
|
snd_es1688_write(chip, 0xa1, bits);
|
|
snd_es1688_write(chip, 0xa2, divider);
|
|
}
|
|
|
|
static int snd_es1688_trigger(struct snd_es1688 *chip, int cmd, unsigned char value)
|
|
{
|
|
int val;
|
|
|
|
if (cmd == SNDRV_PCM_TRIGGER_STOP) {
|
|
value = 0x00;
|
|
} else if (cmd != SNDRV_PCM_TRIGGER_START) {
|
|
return -EINVAL;
|
|
}
|
|
spin_lock(&chip->reg_lock);
|
|
chip->trigger_value = value;
|
|
val = snd_es1688_read(chip, 0xb8);
|
|
if ((val < 0) || (val & 0x0f) == value) {
|
|
spin_unlock(&chip->reg_lock);
|
|
return -EINVAL; /* something is wrong */
|
|
}
|
|
#if 0
|
|
printk(KERN_DEBUG "trigger: val = 0x%x, value = 0x%x\n", val, value);
|
|
printk(KERN_DEBUG "trigger: pointer = 0x%x\n",
|
|
snd_dma_pointer(chip->dma8, chip->dma_size));
|
|
#endif
|
|
snd_es1688_write(chip, 0xb8, (val & 0xf0) | value);
|
|
spin_unlock(&chip->reg_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_playback_prepare(struct snd_pcm_substream *substream)
|
|
{
|
|
unsigned long flags;
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
|
|
unsigned int count = snd_pcm_lib_period_bytes(substream);
|
|
|
|
chip->dma_size = size;
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_reset(chip);
|
|
snd_es1688_set_rate(chip, substream);
|
|
snd_es1688_write(chip, 0xb8, 4); /* auto init DMA mode */
|
|
snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels));
|
|
snd_es1688_write(chip, 0xb9, 2); /* demand mode (4 bytes/request) */
|
|
if (runtime->channels == 1) {
|
|
if (snd_pcm_format_width(runtime->format) == 8) {
|
|
/* 8. bit mono */
|
|
snd_es1688_write(chip, 0xb6, 0x80);
|
|
snd_es1688_write(chip, 0xb7, 0x51);
|
|
snd_es1688_write(chip, 0xb7, 0xd0);
|
|
} else {
|
|
/* 16. bit mono */
|
|
snd_es1688_write(chip, 0xb6, 0x00);
|
|
snd_es1688_write(chip, 0xb7, 0x71);
|
|
snd_es1688_write(chip, 0xb7, 0xf4);
|
|
}
|
|
} else {
|
|
if (snd_pcm_format_width(runtime->format) == 8) {
|
|
/* 8. bit stereo */
|
|
snd_es1688_write(chip, 0xb6, 0x80);
|
|
snd_es1688_write(chip, 0xb7, 0x51);
|
|
snd_es1688_write(chip, 0xb7, 0x98);
|
|
} else {
|
|
/* 16. bit stereo */
|
|
snd_es1688_write(chip, 0xb6, 0x00);
|
|
snd_es1688_write(chip, 0xb7, 0x71);
|
|
snd_es1688_write(chip, 0xb7, 0xbc);
|
|
}
|
|
}
|
|
snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50);
|
|
snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50);
|
|
snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKON);
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
/* --- */
|
|
count = -count;
|
|
snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT);
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_write(chip, 0xa4, (unsigned char) count);
|
|
snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8));
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_playback_trigger(struct snd_pcm_substream *substream,
|
|
int cmd)
|
|
{
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
return snd_es1688_trigger(chip, cmd, 0x05);
|
|
}
|
|
|
|
static int snd_es1688_capture_prepare(struct snd_pcm_substream *substream)
|
|
{
|
|
unsigned long flags;
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
|
|
unsigned int count = snd_pcm_lib_period_bytes(substream);
|
|
|
|
chip->dma_size = size;
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_reset(chip);
|
|
snd_es1688_set_rate(chip, substream);
|
|
snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKOFF);
|
|
snd_es1688_write(chip, 0xb8, 0x0e); /* auto init DMA mode */
|
|
snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels));
|
|
snd_es1688_write(chip, 0xb9, 2); /* demand mode (4 bytes/request) */
|
|
if (runtime->channels == 1) {
|
|
if (snd_pcm_format_width(runtime->format) == 8) {
|
|
/* 8. bit mono */
|
|
snd_es1688_write(chip, 0xb7, 0x51);
|
|
snd_es1688_write(chip, 0xb7, 0xd0);
|
|
} else {
|
|
/* 16. bit mono */
|
|
snd_es1688_write(chip, 0xb7, 0x71);
|
|
snd_es1688_write(chip, 0xb7, 0xf4);
|
|
}
|
|
} else {
|
|
if (snd_pcm_format_width(runtime->format) == 8) {
|
|
/* 8. bit stereo */
|
|
snd_es1688_write(chip, 0xb7, 0x51);
|
|
snd_es1688_write(chip, 0xb7, 0x98);
|
|
} else {
|
|
/* 16. bit stereo */
|
|
snd_es1688_write(chip, 0xb7, 0x71);
|
|
snd_es1688_write(chip, 0xb7, 0xbc);
|
|
}
|
|
}
|
|
snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50);
|
|
snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50);
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
/* --- */
|
|
count = -count;
|
|
snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_es1688_write(chip, 0xa4, (unsigned char) count);
|
|
snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8));
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_capture_trigger(struct snd_pcm_substream *substream,
|
|
int cmd)
|
|
{
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
return snd_es1688_trigger(chip, cmd, 0x0f);
|
|
}
|
|
|
|
static irqreturn_t snd_es1688_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct snd_es1688 *chip = dev_id;
|
|
|
|
if (chip->trigger_value == 0x05) /* ok.. playback is active */
|
|
snd_pcm_period_elapsed(chip->playback_substream);
|
|
if (chip->trigger_value == 0x0f) /* ok.. capture is active */
|
|
snd_pcm_period_elapsed(chip->capture_substream);
|
|
|
|
inb(ES1688P(chip, DATA_AVAIL)); /* ack interrupt */
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static snd_pcm_uframes_t snd_es1688_playback_pointer(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
size_t ptr;
|
|
|
|
if (chip->trigger_value != 0x05)
|
|
return 0;
|
|
ptr = snd_dma_pointer(chip->dma8, chip->dma_size);
|
|
return bytes_to_frames(substream->runtime, ptr);
|
|
}
|
|
|
|
static snd_pcm_uframes_t snd_es1688_capture_pointer(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
size_t ptr;
|
|
|
|
if (chip->trigger_value != 0x0f)
|
|
return 0;
|
|
ptr = snd_dma_pointer(chip->dma8, chip->dma_size);
|
|
return bytes_to_frames(substream->runtime, ptr);
|
|
}
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
static const struct snd_pcm_hardware snd_es1688_playback =
|
|
{
|
|
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_MMAP_VALID),
|
|
.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
|
|
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
|
|
.rate_min = 4000,
|
|
.rate_max = 48000,
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = 65536,
|
|
.period_bytes_min = 64,
|
|
.period_bytes_max = 65536,
|
|
.periods_min = 1,
|
|
.periods_max = 1024,
|
|
.fifo_size = 0,
|
|
};
|
|
|
|
static const struct snd_pcm_hardware snd_es1688_capture =
|
|
{
|
|
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_MMAP_VALID),
|
|
.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
|
|
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
|
|
.rate_min = 4000,
|
|
.rate_max = 48000,
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.buffer_bytes_max = 65536,
|
|
.period_bytes_min = 64,
|
|
.period_bytes_max = 65536,
|
|
.periods_min = 1,
|
|
.periods_max = 1024,
|
|
.fifo_size = 0,
|
|
};
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
static int snd_es1688_playback_open(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
if (chip->capture_substream != NULL)
|
|
return -EAGAIN;
|
|
chip->playback_substream = substream;
|
|
runtime->hw = snd_es1688_playback;
|
|
snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
|
&hw_constraints_clocks);
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_capture_open(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
if (chip->playback_substream != NULL)
|
|
return -EAGAIN;
|
|
chip->capture_substream = substream;
|
|
runtime->hw = snd_es1688_capture;
|
|
snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
|
&hw_constraints_clocks);
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_playback_close(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
|
|
chip->playback_substream = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_capture_close(struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_es1688 *chip = snd_pcm_substream_chip(substream);
|
|
|
|
chip->capture_substream = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_free(struct snd_es1688 *chip)
|
|
{
|
|
if (chip->hardware != ES1688_HW_UNDEF)
|
|
snd_es1688_init(chip, 0);
|
|
release_and_free_resource(chip->res_port);
|
|
if (chip->irq >= 0)
|
|
free_irq(chip->irq, (void *) chip);
|
|
if (chip->dma8 >= 0) {
|
|
disable_dma(chip->dma8);
|
|
free_dma(chip->dma8);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_dev_free(struct snd_device *device)
|
|
{
|
|
struct snd_es1688 *chip = device->device_data;
|
|
return snd_es1688_free(chip);
|
|
}
|
|
|
|
static const char *snd_es1688_chip_id(struct snd_es1688 *chip)
|
|
{
|
|
static char tmp[16];
|
|
sprintf(tmp, "ES%s688 rev %i", chip->hardware == ES1688_HW_688 ? "" : "1", chip->version & 0x0f);
|
|
return tmp;
|
|
}
|
|
|
|
int snd_es1688_create(struct snd_card *card,
|
|
struct snd_es1688 *chip,
|
|
unsigned long port,
|
|
unsigned long mpu_port,
|
|
int irq,
|
|
int mpu_irq,
|
|
int dma8,
|
|
unsigned short hardware)
|
|
{
|
|
static const struct snd_device_ops ops = {
|
|
.dev_free = snd_es1688_dev_free,
|
|
};
|
|
|
|
int err;
|
|
|
|
if (chip == NULL)
|
|
return -ENOMEM;
|
|
chip->irq = -1;
|
|
chip->dma8 = -1;
|
|
chip->hardware = ES1688_HW_UNDEF;
|
|
|
|
chip->res_port = request_region(port + 4, 12, "ES1688");
|
|
if (chip->res_port == NULL) {
|
|
snd_printk(KERN_ERR "es1688: can't grab port 0x%lx\n", port + 4);
|
|
err = -EBUSY;
|
|
goto exit;
|
|
}
|
|
|
|
err = request_irq(irq, snd_es1688_interrupt, 0, "ES1688", (void *) chip);
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "es1688: can't grab IRQ %d\n", irq);
|
|
goto exit;
|
|
}
|
|
|
|
chip->irq = irq;
|
|
card->sync_irq = chip->irq;
|
|
err = request_dma(dma8, "ES1688");
|
|
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "es1688: can't grab DMA8 %d\n", dma8);
|
|
goto exit;
|
|
}
|
|
chip->dma8 = dma8;
|
|
|
|
spin_lock_init(&chip->reg_lock);
|
|
spin_lock_init(&chip->mixer_lock);
|
|
chip->port = port;
|
|
mpu_port &= ~0x000f;
|
|
if (mpu_port < 0x300 || mpu_port > 0x330)
|
|
mpu_port = 0;
|
|
chip->mpu_port = mpu_port;
|
|
chip->mpu_irq = mpu_irq;
|
|
chip->hardware = hardware;
|
|
|
|
err = snd_es1688_probe(chip);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
err = snd_es1688_init(chip, 1);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
/* Register device */
|
|
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
|
|
exit:
|
|
if (err)
|
|
snd_es1688_free(chip);
|
|
return err;
|
|
}
|
|
|
|
static const struct snd_pcm_ops snd_es1688_playback_ops = {
|
|
.open = snd_es1688_playback_open,
|
|
.close = snd_es1688_playback_close,
|
|
.prepare = snd_es1688_playback_prepare,
|
|
.trigger = snd_es1688_playback_trigger,
|
|
.pointer = snd_es1688_playback_pointer,
|
|
};
|
|
|
|
static const struct snd_pcm_ops snd_es1688_capture_ops = {
|
|
.open = snd_es1688_capture_open,
|
|
.close = snd_es1688_capture_close,
|
|
.prepare = snd_es1688_capture_prepare,
|
|
.trigger = snd_es1688_capture_trigger,
|
|
.pointer = snd_es1688_capture_pointer,
|
|
};
|
|
|
|
int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device)
|
|
{
|
|
struct snd_pcm *pcm;
|
|
int err;
|
|
|
|
err = snd_pcm_new(card, "ESx688", device, 1, 1, &pcm);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1688_playback_ops);
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1688_capture_ops);
|
|
|
|
pcm->private_data = chip;
|
|
pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
|
|
strcpy(pcm->name, snd_es1688_chip_id(chip));
|
|
chip->pcm = pcm;
|
|
|
|
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, card->dev,
|
|
64*1024, 64*1024);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* MIXER part
|
|
*/
|
|
|
|
static int snd_es1688_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
static const char * const texts[8] = {
|
|
"Mic", "Mic Master", "CD", "AOUT",
|
|
"Mic1", "Mix", "Line", "Master"
|
|
};
|
|
|
|
return snd_ctl_enum_info(uinfo, 1, 8, texts);
|
|
}
|
|
|
|
static int snd_es1688_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
|
|
ucontrol->value.enumerated.item[0] = snd_es1688_mixer_read(chip, ES1688_REC_DEV) & 7;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
|
|
unsigned long flags;
|
|
unsigned char oval, nval;
|
|
int change;
|
|
|
|
if (ucontrol->value.enumerated.item[0] > 8)
|
|
return -EINVAL;
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
oval = snd_es1688_mixer_read(chip, ES1688_REC_DEV);
|
|
nval = (ucontrol->value.enumerated.item[0] & 7) | (oval & ~15);
|
|
change = nval != oval;
|
|
if (change)
|
|
snd_es1688_mixer_write(chip, ES1688_REC_DEV, nval);
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
return change;
|
|
}
|
|
|
|
#define ES1688_SINGLE(xname, xindex, reg, shift, mask, invert) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
|
|
.info = snd_es1688_info_single, \
|
|
.get = snd_es1688_get_single, .put = snd_es1688_put_single, \
|
|
.private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) }
|
|
|
|
static int snd_es1688_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
int mask = (kcontrol->private_value >> 16) & 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_es1688_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
|
|
unsigned long flags;
|
|
int reg = kcontrol->private_value & 0xff;
|
|
int shift = (kcontrol->private_value >> 8) & 0xff;
|
|
int mask = (kcontrol->private_value >> 16) & 0xff;
|
|
int invert = (kcontrol->private_value >> 24) & 0xff;
|
|
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
ucontrol->value.integer.value[0] = (snd_es1688_mixer_read(chip, reg) >> shift) & mask;
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
if (invert)
|
|
ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
|
|
unsigned long flags;
|
|
int reg = kcontrol->private_value & 0xff;
|
|
int shift = (kcontrol->private_value >> 8) & 0xff;
|
|
int mask = (kcontrol->private_value >> 16) & 0xff;
|
|
int invert = (kcontrol->private_value >> 24) & 0xff;
|
|
int change;
|
|
unsigned char oval, nval;
|
|
|
|
nval = (ucontrol->value.integer.value[0] & mask);
|
|
if (invert)
|
|
nval = mask - nval;
|
|
nval <<= shift;
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
oval = snd_es1688_mixer_read(chip, reg);
|
|
nval = (oval & ~(mask << shift)) | nval;
|
|
change = nval != oval;
|
|
if (change)
|
|
snd_es1688_mixer_write(chip, reg, nval);
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
return change;
|
|
}
|
|
|
|
#define ES1688_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
|
|
.info = snd_es1688_info_double, \
|
|
.get = snd_es1688_get_double, .put = snd_es1688_put_double, \
|
|
.private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) }
|
|
|
|
static int snd_es1688_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_es1688_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
|
|
unsigned long flags;
|
|
int left_reg = kcontrol->private_value & 0xff;
|
|
int right_reg = (kcontrol->private_value >> 8) & 0xff;
|
|
int shift_left = (kcontrol->private_value >> 16) & 0x07;
|
|
int shift_right = (kcontrol->private_value >> 19) & 0x07;
|
|
int mask = (kcontrol->private_value >> 24) & 0xff;
|
|
int invert = (kcontrol->private_value >> 22) & 1;
|
|
unsigned char left, right;
|
|
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
if (left_reg < 0xa0)
|
|
left = snd_es1688_mixer_read(chip, left_reg);
|
|
else
|
|
left = snd_es1688_read(chip, left_reg);
|
|
if (left_reg != right_reg) {
|
|
if (right_reg < 0xa0)
|
|
right = snd_es1688_mixer_read(chip, right_reg);
|
|
else
|
|
right = snd_es1688_read(chip, right_reg);
|
|
} else
|
|
right = left;
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
|
|
ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
|
|
if (invert) {
|
|
ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0];
|
|
ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int snd_es1688_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol);
|
|
unsigned long flags;
|
|
int left_reg = kcontrol->private_value & 0xff;
|
|
int right_reg = (kcontrol->private_value >> 8) & 0xff;
|
|
int shift_left = (kcontrol->private_value >> 16) & 0x07;
|
|
int shift_right = (kcontrol->private_value >> 19) & 0x07;
|
|
int mask = (kcontrol->private_value >> 24) & 0xff;
|
|
int invert = (kcontrol->private_value >> 22) & 1;
|
|
int change;
|
|
unsigned char val1, val2, oval1, oval2;
|
|
|
|
val1 = ucontrol->value.integer.value[0] & mask;
|
|
val2 = ucontrol->value.integer.value[1] & mask;
|
|
if (invert) {
|
|
val1 = mask - val1;
|
|
val2 = mask - val2;
|
|
}
|
|
val1 <<= shift_left;
|
|
val2 <<= shift_right;
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
if (left_reg != right_reg) {
|
|
if (left_reg < 0xa0)
|
|
oval1 = snd_es1688_mixer_read(chip, left_reg);
|
|
else
|
|
oval1 = snd_es1688_read(chip, left_reg);
|
|
if (right_reg < 0xa0)
|
|
oval2 = snd_es1688_mixer_read(chip, right_reg);
|
|
else
|
|
oval2 = snd_es1688_read(chip, right_reg);
|
|
val1 = (oval1 & ~(mask << shift_left)) | val1;
|
|
val2 = (oval2 & ~(mask << shift_right)) | val2;
|
|
change = val1 != oval1 || val2 != oval2;
|
|
if (change) {
|
|
if (left_reg < 0xa0)
|
|
snd_es1688_mixer_write(chip, left_reg, val1);
|
|
else
|
|
snd_es1688_write(chip, left_reg, val1);
|
|
if (right_reg < 0xa0)
|
|
snd_es1688_mixer_write(chip, right_reg, val1);
|
|
else
|
|
snd_es1688_write(chip, right_reg, val1);
|
|
}
|
|
} else {
|
|
if (left_reg < 0xa0)
|
|
oval1 = snd_es1688_mixer_read(chip, left_reg);
|
|
else
|
|
oval1 = snd_es1688_read(chip, left_reg);
|
|
val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2;
|
|
change = val1 != oval1;
|
|
if (change) {
|
|
if (left_reg < 0xa0)
|
|
snd_es1688_mixer_write(chip, left_reg, val1);
|
|
else
|
|
snd_es1688_write(chip, left_reg, val1);
|
|
}
|
|
|
|
}
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
return change;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new snd_es1688_controls[] = {
|
|
ES1688_DOUBLE("Master Playback Volume", 0, ES1688_MASTER_DEV, ES1688_MASTER_DEV, 4, 0, 15, 0),
|
|
ES1688_DOUBLE("PCM Playback Volume", 0, ES1688_PCM_DEV, ES1688_PCM_DEV, 4, 0, 15, 0),
|
|
ES1688_DOUBLE("Line Playback Volume", 0, ES1688_LINE_DEV, ES1688_LINE_DEV, 4, 0, 15, 0),
|
|
ES1688_DOUBLE("CD Playback Volume", 0, ES1688_CD_DEV, ES1688_CD_DEV, 4, 0, 15, 0),
|
|
ES1688_DOUBLE("FM Playback Volume", 0, ES1688_FM_DEV, ES1688_FM_DEV, 4, 0, 15, 0),
|
|
ES1688_DOUBLE("Mic Playback Volume", 0, ES1688_MIC_DEV, ES1688_MIC_DEV, 4, 0, 15, 0),
|
|
ES1688_DOUBLE("Aux Playback Volume", 0, ES1688_AUX_DEV, ES1688_AUX_DEV, 4, 0, 15, 0),
|
|
ES1688_SINGLE("Beep Playback Volume", 0, ES1688_SPEAKER_DEV, 0, 7, 0),
|
|
ES1688_DOUBLE("Capture Volume", 0, ES1688_RECLEV_DEV, ES1688_RECLEV_DEV, 4, 0, 15, 0),
|
|
ES1688_SINGLE("Capture Switch", 0, ES1688_REC_DEV, 4, 1, 1),
|
|
{
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Capture Source",
|
|
.info = snd_es1688_info_mux,
|
|
.get = snd_es1688_get_mux,
|
|
.put = snd_es1688_put_mux,
|
|
},
|
|
};
|
|
|
|
#define ES1688_INIT_TABLE_SIZE (sizeof(snd_es1688_init_table)/2)
|
|
|
|
static const unsigned char snd_es1688_init_table[][2] = {
|
|
{ ES1688_MASTER_DEV, 0 },
|
|
{ ES1688_PCM_DEV, 0 },
|
|
{ ES1688_LINE_DEV, 0 },
|
|
{ ES1688_CD_DEV, 0 },
|
|
{ ES1688_FM_DEV, 0 },
|
|
{ ES1688_MIC_DEV, 0 },
|
|
{ ES1688_AUX_DEV, 0 },
|
|
{ ES1688_SPEAKER_DEV, 0 },
|
|
{ ES1688_RECLEV_DEV, 0 },
|
|
{ ES1688_REC_DEV, 0x17 }
|
|
};
|
|
|
|
int snd_es1688_mixer(struct snd_card *card, struct snd_es1688 *chip)
|
|
{
|
|
unsigned int idx;
|
|
int err;
|
|
unsigned char reg, val;
|
|
|
|
if (snd_BUG_ON(!chip || !card))
|
|
return -EINVAL;
|
|
|
|
strcpy(card->mixername, snd_es1688_chip_id(chip));
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(snd_es1688_controls); idx++) {
|
|
err = snd_ctl_add(card, snd_ctl_new1(&snd_es1688_controls[idx], chip));
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
for (idx = 0; idx < ES1688_INIT_TABLE_SIZE; idx++) {
|
|
reg = snd_es1688_init_table[idx][0];
|
|
val = snd_es1688_init_table[idx][1];
|
|
if (reg < 0xa0)
|
|
snd_es1688_mixer_write(chip, reg, val);
|
|
else
|
|
snd_es1688_write(chip, reg, val);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_SYMBOL(snd_es1688_mixer_write);
|
|
EXPORT_SYMBOL(snd_es1688_create);
|
|
EXPORT_SYMBOL(snd_es1688_pcm);
|
|
EXPORT_SYMBOL(snd_es1688_mixer);
|