📄 es1938.cpp
字号:
/****************************************************************** MODULE: ES1938 Sound Card Driver file** DESCRIPTION: Driver for capture/playback functions on the ESS ES1938
* chipset** ORIGINAL AUTHOR: GNU ALSA Linux Source Code (see header below)
* Modified for use with VxWorks by Dan Walkes* UPDATED BY: * * CREATED: Oct, 2005* MODIFIED:
* * NOTES:** This file was modified as little as possible to make future porting of
* ALSA code to VxWorks as easy as possible. I also attempted to keep as much code
* as possible resident in the file so it could be referenced if any issues arise with
* operation in the future. In many cases #if VX_UNUSED compile options were placed
* around code sections which I did not plan on including but may possibly be included
* in future revisions.** CODE USAGE:** This file requires files VxSound.cpp and VxPCI.cpp. See documentation
* in these modules for more information. Playback has been tested with
* the vx_tone_ap.cpp source file which generates a continuous tone at
* a specified frequency. Capture has not yet been tested.
*** REVISION HISTORY AND NOTES:** Date Update* ---------------------------------------------------------------------* Oct , 2005 Created.** REFERENCES:** 1) "DSSolo1.pdf" datasheet - ES1938 datasheet.** 2) ALSA Linux driver page at http://www.alsa-project.org/alsa-doc/alsa-lib/index.html* * 3) "vxWALSA Sound Driver" document included with this release.
*****************************************************************/
/* Header below is the original header included with the es1938.cpp file
found in the ALSA library. Functions were modified for use with VxWorks */
/* * Driver for ESS Solo-1 (ES1938, ES1946, ES1969) soundcard * Copyright (c) by Jaromir Koutek <miri@punknet.cz>, * Jaroslav Kysela <perex@suse.cz>, * Thomas Sailer <sailer@ife.ee.ethz.ch>, * Abramo Bagnara <abramo@alsa-project.org>, * Markus Gruber <gruber@eikon.tum.de> * * Rewritten from sonicvibes.c source. * * TODO: * Rewrite better spinlocks * * * 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 * *//* NOTES: - Capture data is written unaligned starting from dma_base + 1 so I need to disable mmap and to add a copy callback. - After several cycle of the following: while : ; do arecord -d1 -f cd -t raw | aplay -f cd ; done a "playback write error (DMA or IRQ trouble?)" may happen. This is due to playback interrupts not generated. I suspect a timing issue. - Sometimes the interrupt handler is invoked wrongly during playback. This generates some harmless "Unexpected hw_pointer: wrong interrupt acknowledge". I've seen that using small period sizes. Reproducible with: mpg123 test.mp3 & hdparm -t -T /dev/hda*/
#include "VxWorks.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "VxTypes.h"
#include "VxSound.h"
#include "VxPCI.h"
#include "es1938.h"
typedef struct _snd_es1938 es1938_t;
es1938_t *pGlobChip;
#define SAVED_REG_SIZE 32 /* max. number of registers to save */
struct _snd_es1938 {
int irq;
unsigned long io_port;
unsigned long sb_port;
unsigned long vc_port;
unsigned long mpu_port;
unsigned long game_port;
unsigned long ddma_port;
unsigned char irqmask;
unsigned char revision;
snd_kcontrol_t *hw_volume;
snd_kcontrol_t *hw_switch;
snd_kcontrol_t *master_volume;
snd_kcontrol_t *master_switch;
struct pci_dev *pci;
snd_card_t *card;
snd_pcm_t *pcm;
snd_pcm_substream_t *capture_substream;
snd_pcm_substream_t *playback1_substream;
snd_pcm_substream_t *playback2_substream;
#if VXWORKS_UNUSED
snd_kmixer_t *mixer;
snd_rawmidi_t *rmidi;
spinlock_t reg_lock;
spinlock_t mixer_lock;
snd_info_entry_t *proc_entry;
#ifdef SUPPORT_JOYSTICK
struct gameport *gameport;
#endif
#ifdef CONFIG_PM
unsigned char saved_regs[SAVED_REG_SIZE];
#endif
#endif
unsigned int dma1_size;
unsigned int dma2_size;
unsigned int dma1_start;
unsigned int dma2_start;
unsigned int dma1_shift;
unsigned int dma2_shift;
unsigned int active;
};
#ifndef PCI_VENDOR_ID_ESS
#define PCI_VENDOR_ID_ESS 0x125d
#endif
#ifndef PCI_DEVICE_ID_ESS_ES1938
#define PCI_DEVICE_ID_ESS_ES1938 0x1969
#endif
static const struct pci_device_id snd_es1938_ids[] = {
{ 0x125d, 0x1969, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, }, /* Solo-1 */
{ 0, }
};
#define SNDRV_DEFAULT_IDX { -1, -1, -1, -1, -1, -1, -1, -1 }
#define SNDRV_DEFAULT_STR { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
#define SNDRV_DEFAULT_ENABLE { 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
#define SNDRV_DEFAULT_ENABLE_PNP { 1, 1, 1, 1, 1, 1, 1, 1 }
/* for support of multiple cards */
static int a_index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
static void snd_es1938_reset_fifo(es1938_t *chip);
static void snd_es1938_rate_set(es1938_t *chip,
snd_pcm_substream_t *substream,
int mode);
static void snd_es1938_mixer_write(es1938_t *chip, unsigned char reg, unsigned char val);
static void snd_es1938_playback1_setdma(es1938_t *chip);
static void snd_es1938_write(es1938_t *chip, unsigned char reg, unsigned char val);
static int snd_es1938_bits(es1938_t *chip, unsigned char reg, unsigned char mask, unsigned char val);
static int snd_es1938_mixer_bits(es1938_t *chip, unsigned char reg, unsigned char mask, unsigned char val);
static int snd_es1938_set_volume(snd_pcm_substream_t * substream, uint8 value );
static void snd_es1938_reset(es1938_t *chip);
/* --------------------------------------------------------------------
* Interrupt handler
* -------------------------------------------------------------------- */
static irqreturn_t snd_es1938_interrupt( int dev_id )
{
es1938_t *chip = (es1938_t *)dev_id;
unsigned char status, audiostatus;
status = inb(SLIO_REG(chip, IRQCONTROL));
printk("Es1938debug - interrupt status: =0x%x\n", status);
/* AUDIO 1 */
if (status & 0x10) {
#if 0
printk("Es1938debug - AUDIO channel 1 interrupt\n");
printk("Es1938debug - AUDIO channel 1 DMAC DMA count: %u\n", inw(SLDM_REG(chip, DMACOUNT)));
printk("Es1938debug - AUDIO channel 1 DMAC DMA base: %u\n", inl(SLDM_REG(chip, DMAADDR)));
printk("Es1938debug - AUDIO channel 1 DMAC DMA status: 0x%x\n", inl(SLDM_REG(chip, DMASTATUS)));
#endif
/* clear irq */
audiostatus = inb(SLSB_REG(chip, STATUS));
if (chip->active & ADC1)
snd_pcm_period_elapsed(chip->capture_substream);
else if (chip->active & DAC1)
snd_pcm_period_elapsed(chip->playback2_substream);
}
/* AUDIO 2 */
if (status & 0x20) {
#if 0
printk("Es1938debug - AUDIO channel 2 interrupt\n");
printk("Es1938debug - AUDIO channel 2 DMAC DMA count: %u\n", inw(SLIO_REG(chip, AUDIO2DMACOUNT)));
printk("Es1938debug - AUDIO channel 2 DMAC DMA base: %u\n", inl(SLIO_REG(chip, AUDIO2DMAADDR)));
#endif
/* clear irq */
snd_es1938_mixer_bits(chip, ESSSB_IREG_AUDIO2CONTROL2, 0x80, 0);
if (chip->active & DAC2)
snd_pcm_period_elapsed(chip->playback1_substream);
}
#if ADD_LATER_INTERRUPT /* add the volume and MPU401 handler later */
(int irq, void *dev_id, struct pt_regs *regs) /* need to set these.. are passed values in linux implementation */
/* Hardware volume */
if (status & 0x40) {
int split = snd_es1938_mixer_read(chip, 0x64) & 0x80;
handled = 1;
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_switch->id);
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_volume->id);
if (!split) {
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_switch->id);
snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_volume->id);
}
/* ack interrupt */
snd_es1938_mixer_write(chip, 0x66, 0x00);
}
/* MPU401 */
if (status & 0x80) {
// the following line is evil! It switches off MIDI interrupt handling after the first interrupt received.
// replacing the last 0 by 0x40 works for ESS-Solo1, but just doing nothing works as well!
// andreas@flying-snail.de
// snd_es1938_mixer_bits(chip, ESSSB_IREG_MPU401CONTROL, 0x40, 0); /* ack? */
if (chip->rmidi) {
handled = 1;
snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data, regs);
}
}
#else
if (status & 0x40) {
snd_es1938_mixer_write(chip, 0x66, 0x00);
}
#endif
return IRQ_RETVAL(handled);
}
/* ------------------------------------------------------------------------------
* Second Audio channel DAC Operation
* ------------------------------------------------------------------------------*/
static int snd_es1938_playback1_prepare(snd_pcm_substream_t * substream)
{
es1938_t *chip = (es1938_t *)snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int u, is8, mono;
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
unsigned int count = snd_pcm_lib_period_bytes(substream);
chip->dma2_size = size;
chip->dma2_start = runtime->dma_addr;
mono = (runtime->channels > 1) ? 0 : 1;
is8 = snd_pcm_format_width(runtime->format) == 16 ? 0 : 1;
u = snd_pcm_format_unsigned(runtime->format);
chip->dma2_shift = 2 - mono - is8;
snd_es1938_reset_fifo(chip);
/* set clock and counters */
snd_es1938_rate_set(chip, substream, DAC2);
count >>= 1;
count = 0x10000 - count;
snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2TCOUNTL, count & 0xff);
snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2TCOUNTH, count >> 8);
/* initialize and configure Audio 2 DAC */
snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2CONTROL2, 0x40 | (u ? 0 : 4) | (mono ? 0 : 2) | (is8 ? 0 : 1));
/* program DMA */
snd_es1938_playback1_setdma(chip);
return 0;
}
/* --------------------------------------------------------------------
* Reset the FIFOs
* --------------------------------------------------------------------*/
static void snd_es1938_reset_fifo(es1938_t *chip)
{
outb(2, SLSB_REG(chip, RESET));
outb(0, SLSB_REG(chip, RESET));
}
static ratnum_t clocks[2] = {
{
/*.num =*/ 793800,
/*.den_min = */ 1,
/*.den_max = */ 128,
/*.den_step = */ 1,
},
{
/*.num = */ 768000,
/*.den_min =*/ 1,
/*.den_max =*/ 128,
/*.den_step =*/ 1,
}
};
static snd_pcm_hw_constraint_ratnums_t hw_constraints_clocks = {
/*.nrats =*/ 2,
/*.rats = */ clocks,
};
static void snd_es1938_rate_set(es1938_t *chip,
snd_pcm_substream_t *substream,
int mode)
{
unsigned int bits, div0;
snd_pcm_runtime_t *runtime = substream->runtime;
if (runtime->rate_num == clocks[0].num)
bits = 128 - runtime->rate_den;
else
bits = 256 - runtime->rate_den;
/* set filter register */
div0 = 256 - 7160000*20/(8*82*runtime->rate);
if (mode == DAC2) {
snd_es1938_mixer_write(chip, 0x70, bits);
snd_es1938_mixer_write(chip, 0x72, div0);
} else {
snd_es1938_write(chip, 0xA1, bits);
snd_es1938_write(chip, 0xA2, div0);
}
}
static void snd_es1938_playback1_setdma(es1938_t *chip)
{
outb(0x00, SLIO_REG(chip, AUDIO2MODE));
outl(chip->dma2_start, SLIO_REG(chip, AUDIO2DMAADDR));
outw(0, SLIO_REG(chip, AUDIO2DMACOUNT));
outw(chip->dma2_size, SLIO_REG(chip, AUDIO2DMACOUNT));
}
static void snd_es1938_playback2_setdma(es1938_t *chip)
{
/* Enable DMA controller */
outb(0xc4, SLDM_REG(chip, DMACOMMAND));
/* 1. Master reset */
outb(0, SLDM_REG(chip, DMACLEAR));
/* 2. Mask DMA */
outb(1, SLDM_REG(chip, DMAMASK));
outb(0x18, SLDM_REG(chip, DMAMODE));
outl(chip->dma1_start, SLDM_REG(chip, DMAADDR));
outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT));
/* 3. Unmask DMA */
outb(0, SLDM_REG(chip, DMAMASK));
}
static void snd_es1938_capture_setdma(es1938_t *chip)
{
/* Enable DMA controller */
outb(0xc4, SLDM_REG(chip, DMACOMMAND));
/* 1. Master reset */
outb(0, SLDM_REG(chip, DMACLEAR));
/* 2. Mask DMA */
outb(1, SLDM_REG(chip, DMAMASK));
outb(0x14, SLDM_REG(chip, DMAMODE));
outl(chip->dma1_start, SLDM_REG(chip, DMAADDR));
outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT));
/* 3. Unmask DMA */
outb(0, SLDM_REG(chip, DMAMASK));
}
static int snd_es1938_playback2_prepare(snd_pcm_substream_t * substream)
{
es1938_t *chip = (es1938_t *)snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
int u, is8, mono;
unsigned int size = snd_pcm_lib_buffer_bytes(substream);
unsigned int count = snd_pcm_lib_period_bytes(substream);
chip->dma1_size = size;
chip->dma1_start = runtime->dma_addr;
mono = (runtime->channels > 1) ? 0 : 1;
is8 = snd_pcm_format_width(runtime->format) == 16 ? 0 : 1;
u = snd_pcm_format_unsigned(runtime->format);
chip->dma1_shift = 2 - mono - is8;
count = 0x10000 - count;
/* reset */
snd_es1938_reset_fifo(chip);
snd_es1938_bits(chip, ESS_CMD_ANALOGCONTROL, 0x03, (mono ? 2 : 1));
/* set clock and counters */
snd_es1938_rate_set(chip, substream, DAC1);
snd_es1938_write(chip, ESS_CMD_DMACNTRELOADL, count & 0xff);
snd_es1938_write(chip, ESS_CMD_DMACNTRELOADH, count >> 8);
/* initialized and configure DAC */
snd_es1938_write(chip, ESS_CMD_SETFORMAT, u ? 0x80 : 0x00);
snd_es1938_write(chip, ESS_CMD_SETFORMAT, u ? 0x51 : 0x71);
snd_es1938_write(chip, ESS_CMD_SETFORMAT2,
0x90 | (mono ? 0x40 : 0x08) |
(is8 ? 0x00 : 0x04) | (u ? 0x00 : 0x20));
/* program DMA */
snd_es1938_playback2_setdma(chip);
return 0;
}
static int snd_es1938_playback_prepare(snd_pcm_substream_t *substream)
{
switch (substream->number) {
case 0:
return snd_es1938_playback1_prepare(substream);
case 1:
return snd_es1938_playback2_prepare(substream);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -