📄 pcxhr.c
字号:
/* * Driver for Digigram pcxhr compatible soundcards * * main file with alsa callbacks * * Copyright (c) 2004 by Digigram <alsa@digigram.com> * * 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/driver.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/slab.h>#include <linux/pci.h>#include <linux/delay.h>#include <linux/moduleparam.h>#include <sound/core.h>#include <sound/initval.h>#include <sound/info.h>#include <sound/control.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include "pcxhr.h"#include "pcxhr_mixer.h"#include "pcxhr_hwdep.h"#include "pcxhr_core.h"#define DRIVER_NAME "pcxhr"MODULE_AUTHOR("Markus Bollinger <bollinger@digigram.com>");MODULE_DESCRIPTION("Digigram " DRIVER_NAME " " PCXHR_DRIVER_VERSION_STRING);MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{Digigram," DRIVER_NAME "}}");static int 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 int mono[SNDRV_CARDS]; /* capture in mono only */module_param_array(index, int, NULL, 0444);MODULE_PARM_DESC(index, "Index value for Digigram " DRIVER_NAME " soundcard");module_param_array(id, charp, NULL, 0444);MODULE_PARM_DESC(id, "ID string for Digigram " DRIVER_NAME " soundcard");module_param_array(enable, bool, NULL, 0444);MODULE_PARM_DESC(enable, "Enable Digigram " DRIVER_NAME " soundcard");module_param_array(mono, bool, NULL, 0444);MODULE_PARM_DESC(mono, "Mono capture mode (default is stereo)");enum { PCI_ID_VX882HR, PCI_ID_PCX882HR, PCI_ID_VX881HR, PCI_ID_PCX881HR, PCI_ID_PCX1222HR, PCI_ID_PCX1221HR, PCI_ID_LAST};static struct pci_device_id pcxhr_ids[] = { { 0x10b5, 0x9656, 0x1369, 0xb001, 0, 0, PCI_ID_VX882HR, }, /* VX882HR */ { 0x10b5, 0x9656, 0x1369, 0xb101, 0, 0, PCI_ID_PCX882HR, }, /* PCX882HR */ { 0x10b5, 0x9656, 0x1369, 0xb201, 0, 0, PCI_ID_VX881HR, }, /* VX881HR */ { 0x10b5, 0x9656, 0x1369, 0xb301, 0, 0, PCI_ID_PCX881HR, }, /* PCX881HR */ { 0x10b5, 0x9656, 0x1369, 0xb501, 0, 0, PCI_ID_PCX1222HR, }, /* PCX1222HR */ { 0x10b5, 0x9656, 0x1369, 0xb701, 0, 0, PCI_ID_PCX1221HR, }, /* PCX1221HR */ { 0, }};MODULE_DEVICE_TABLE(pci, pcxhr_ids);struct board_parameters { char* board_name; short playback_chips; short capture_chips; short firmware_num;};static struct board_parameters pcxhr_board_params[] = {[PCI_ID_VX882HR] = { "VX882HR", 4, 4, 41, },[PCI_ID_PCX882HR] = { "PCX882HR", 4, 4, 41, },[PCI_ID_VX881HR] = { "VX881HR", 4, 4, 41, },[PCI_ID_PCX881HR] = { "PCX881HR", 4, 4, 41, },[PCI_ID_PCX1222HR] = { "PCX1222HR", 6, 1, 42, },[PCI_ID_PCX1221HR] = { "PCX1221HR", 6, 1, 42, },};static int pcxhr_pll_freq_register(unsigned int freq, unsigned int* pllreg, unsigned int* realfreq){ unsigned int reg; if (freq < 6900 || freq > 110250) return -EINVAL; reg = (28224000 * 10) / freq; reg = (reg + 5) / 10; if (reg < 0x200) *pllreg = reg + 0x800; else if (reg < 0x400) *pllreg = reg & 0x1ff; else if (reg < 0x800) { *pllreg = ((reg >> 1) & 0x1ff) + 0x200; reg &= ~1; } else { *pllreg = ((reg >> 2) & 0x1ff) + 0x400; reg &= ~3; } if (realfreq) *realfreq = ((28224000 * 10) / reg + 5) / 10; return 0;}#define PCXHR_FREQ_REG_MASK 0x1f#define PCXHR_FREQ_QUARTZ_48000 0x00#define PCXHR_FREQ_QUARTZ_24000 0x01#define PCXHR_FREQ_QUARTZ_12000 0x09#define PCXHR_FREQ_QUARTZ_32000 0x08#define PCXHR_FREQ_QUARTZ_16000 0x04#define PCXHR_FREQ_QUARTZ_8000 0x0c#define PCXHR_FREQ_QUARTZ_44100 0x02#define PCXHR_FREQ_QUARTZ_22050 0x0a#define PCXHR_FREQ_QUARTZ_11025 0x06#define PCXHR_FREQ_PLL 0x05#define PCXHR_FREQ_QUARTZ_192000 0x10#define PCXHR_FREQ_QUARTZ_96000 0x18#define PCXHR_FREQ_QUARTZ_176400 0x14#define PCXHR_FREQ_QUARTZ_88200 0x1c#define PCXHR_FREQ_QUARTZ_128000 0x12#define PCXHR_FREQ_QUARTZ_64000 0x1a#define PCXHR_FREQ_WORD_CLOCK 0x0f#define PCXHR_FREQ_SYNC_AES 0x0e#define PCXHR_FREQ_AES_1 0x07#define PCXHR_FREQ_AES_2 0x0b#define PCXHR_FREQ_AES_3 0x03#define PCXHR_FREQ_AES_4 0x0d#define PCXHR_MODIFY_CLOCK_S_BIT 0x04#define PCXHR_IRQ_TIMER_FREQ 92000#define PCXHR_IRQ_TIMER_PERIOD 48static int pcxhr_get_clock_reg(struct pcxhr_mgr *mgr, unsigned int rate, unsigned int *reg, unsigned int *freq){ unsigned int val, realfreq, pllreg; struct pcxhr_rmh rmh; int err; realfreq = rate; switch (mgr->use_clock_type) { case PCXHR_CLOCK_TYPE_INTERNAL : /* clock by quartz or pll */ switch (rate) { case 48000 : val = PCXHR_FREQ_QUARTZ_48000; break; case 24000 : val = PCXHR_FREQ_QUARTZ_24000; break; case 12000 : val = PCXHR_FREQ_QUARTZ_12000; break; case 32000 : val = PCXHR_FREQ_QUARTZ_32000; break; case 16000 : val = PCXHR_FREQ_QUARTZ_16000; break; case 8000 : val = PCXHR_FREQ_QUARTZ_8000; break; case 44100 : val = PCXHR_FREQ_QUARTZ_44100; break; case 22050 : val = PCXHR_FREQ_QUARTZ_22050; break; case 11025 : val = PCXHR_FREQ_QUARTZ_11025; break; case 192000 : val = PCXHR_FREQ_QUARTZ_192000; break; case 96000 : val = PCXHR_FREQ_QUARTZ_96000; break; case 176400 : val = PCXHR_FREQ_QUARTZ_176400; break; case 88200 : val = PCXHR_FREQ_QUARTZ_88200; break; case 128000 : val = PCXHR_FREQ_QUARTZ_128000; break; case 64000 : val = PCXHR_FREQ_QUARTZ_64000; break; default : val = PCXHR_FREQ_PLL; /* get the value for the pll register */ err = pcxhr_pll_freq_register(rate, &pllreg, &realfreq); if (err) return err; pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); rmh.cmd[0] |= IO_NUM_REG_GENCLK; rmh.cmd[1] = pllreg & MASK_DSP_WORD; rmh.cmd[2] = pllreg >> 24; rmh.cmd_len = 3; err = pcxhr_send_msg(mgr, &rmh); if (err < 0) { snd_printk(KERN_ERR "error CMD_ACCESS_IO_WRITE for PLL register : %x!\n", err ); return err; } } break; case PCXHR_CLOCK_TYPE_WORD_CLOCK : val = PCXHR_FREQ_WORD_CLOCK; break; case PCXHR_CLOCK_TYPE_AES_SYNC : val = PCXHR_FREQ_SYNC_AES; break; case PCXHR_CLOCK_TYPE_AES_1 : val = PCXHR_FREQ_AES_1; break; case PCXHR_CLOCK_TYPE_AES_2 : val = PCXHR_FREQ_AES_2; break; case PCXHR_CLOCK_TYPE_AES_3 : val = PCXHR_FREQ_AES_3; break; case PCXHR_CLOCK_TYPE_AES_4 : val = PCXHR_FREQ_AES_4; break; default : return -EINVAL; } *reg = val; *freq = realfreq; return 0;}int pcxhr_set_clock(struct pcxhr_mgr *mgr, unsigned int rate){ unsigned int val, realfreq, speed; struct pcxhr_rmh rmh; int err, changed; if (rate == 0) return 0; /* nothing to do */ err = pcxhr_get_clock_reg(mgr, rate, &val, &realfreq); if (err) return err; /* codec speed modes */ if (rate < 55000) speed = 0; /* single speed */ else if (rate < 100000) speed = 1; /* dual speed */ else speed = 2; /* quad speed */ if (mgr->codec_speed != speed) { pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* mute outputs */ rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; err = pcxhr_send_msg(mgr, &rmh); if (err) return err; pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); /* set speed ratio */ rmh.cmd[0] |= IO_NUM_SPEED_RATIO; rmh.cmd[1] = speed; rmh.cmd_len = 2; err = pcxhr_send_msg(mgr, &rmh); if (err) return err; } /* set the new frequency */ snd_printdd("clock register : set %x\n", val); err = pcxhr_write_io_num_reg_cont(mgr, PCXHR_FREQ_REG_MASK, val, &changed); if (err) return err; mgr->sample_rate_real = realfreq; mgr->cur_clock_type = mgr->use_clock_type; /* unmute after codec speed modes */ if (mgr->codec_speed != speed) { pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); /* unmute outputs */ rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; err = pcxhr_send_msg(mgr, &rmh); if (err) return err; mgr->codec_speed = speed; /* save new codec speed */ } if (changed) { pcxhr_init_rmh(&rmh, CMD_MODIFY_CLOCK); rmh.cmd[0] |= PCXHR_MODIFY_CLOCK_S_BIT; /* resync fifos */ if (rate < PCXHR_IRQ_TIMER_FREQ) rmh.cmd[1] = PCXHR_IRQ_TIMER_PERIOD; else rmh.cmd[1] = PCXHR_IRQ_TIMER_PERIOD * 2; rmh.cmd[2] = rate; rmh.cmd_len = 3; err = pcxhr_send_msg(mgr, &rmh); if (err) return err; } snd_printdd("pcxhr_set_clock to %dHz (realfreq=%d)\n", rate, realfreq); return 0;}int pcxhr_get_external_clock(struct pcxhr_mgr *mgr, enum pcxhr_clock_type clock_type, int *sample_rate){ struct pcxhr_rmh rmh; unsigned char reg; int err, rate; switch (clock_type) { case PCXHR_CLOCK_TYPE_WORD_CLOCK : reg = REG_STATUS_WORD_CLOCK; break; case PCXHR_CLOCK_TYPE_AES_SYNC : reg = REG_STATUS_AES_SYNC; break; case PCXHR_CLOCK_TYPE_AES_1 : reg = REG_STATUS_AES_1; break; case PCXHR_CLOCK_TYPE_AES_2 : reg = REG_STATUS_AES_2; break; case PCXHR_CLOCK_TYPE_AES_3 : reg = REG_STATUS_AES_3; break; case PCXHR_CLOCK_TYPE_AES_4 : reg = REG_STATUS_AES_4; break; default : return -EINVAL; } pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); rmh.cmd_len = 2; rmh.cmd[0] |= IO_NUM_REG_STATUS; if (mgr->last_reg_stat != reg) { rmh.cmd[1] = reg; err = pcxhr_send_msg(mgr, &rmh); if (err) return err; udelay(100); /* wait minimum 2 sample_frames at 32kHz ! */ mgr->last_reg_stat = reg; } rmh.cmd[1] = REG_STATUS_CURRENT; err = pcxhr_send_msg(mgr, &rmh); if (err) return err; switch (rmh.stat[1] & 0x0f) { case REG_STATUS_SYNC_32000 : rate = 32000; break; case REG_STATUS_SYNC_44100 : rate = 44100; break; case REG_STATUS_SYNC_48000 : rate = 48000; break; case REG_STATUS_SYNC_64000 : rate = 64000; break; case REG_STATUS_SYNC_88200 : rate = 88200; break; case REG_STATUS_SYNC_96000 : rate = 96000; break; case REG_STATUS_SYNC_128000 : rate = 128000; break; case REG_STATUS_SYNC_176400 : rate = 176400; break; case REG_STATUS_SYNC_192000 : rate = 192000; break; default: rate = 0; } snd_printdd("External clock is at %d Hz\n", rate); *sample_rate = rate; return 0;}/* * start or stop playback/capture substream */static int pcxhr_set_stream_state(struct pcxhr_stream *stream){ int err; struct snd_pcxhr *chip; struct pcxhr_rmh rmh; int stream_mask, start; if (stream->status == PCXHR_STREAM_STATUS_SCHEDULE_RUN) start = 1; else { if (stream->status != PCXHR_STREAM_STATUS_SCHEDULE_STOP) { snd_printk(KERN_ERR "ERROR pcxhr_set_stream_state CANNOT be stopped\n"); return -EINVAL; } start = 0; } if (!stream->substream) return -EINVAL; stream->timer_abs_periods = 0; stream->timer_period_frag = 0; /* reset theoretical stream pos */ stream->timer_buf_periods = 0; stream->timer_is_synced = 0; stream_mask = stream->pipe->is_capture ? 1 : 1<<stream->substream->number; pcxhr_init_rmh(&rmh, start ? CMD_START_STREAM : CMD_STOP_STREAM); pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, stream->pipe->first_audio, 0, stream_mask); chip = snd_pcm_substream_chip(stream->substream); err = pcxhr_send_msg(chip->mgr, &rmh); if (err) snd_printk(KERN_ERR "ERROR pcxhr_set_stream_state err=%x;\n", err); stream->status = start ? PCXHR_STREAM_STATUS_STARTED : PCXHR_STREAM_STATUS_STOPPED; return err;}#define HEADER_FMT_BASE_LIN 0xfed00000#define HEADER_FMT_BASE_FLOAT 0xfad00000#define HEADER_FMT_INTEL 0x00008000#define HEADER_FMT_24BITS 0x00004000#define HEADER_FMT_16BITS 0x00002000#define HEADER_FMT_UPTO11 0x00000200#define HEADER_FMT_UPTO32 0x00000100#define HEADER_FMT_MONO 0x00000080static int pcxhr_set_format(struct pcxhr_stream *stream){ int err, is_capture, sample_rate, stream_num; struct snd_pcxhr *chip; struct pcxhr_rmh rmh; unsigned int header; switch (stream->format) { case SNDRV_PCM_FORMAT_U8: header = HEADER_FMT_BASE_LIN; break; case SNDRV_PCM_FORMAT_S16_LE: header = HEADER_FMT_BASE_LIN | HEADER_FMT_16BITS | HEADER_FMT_INTEL; break; case SNDRV_PCM_FORMAT_S16_BE: header = HEADER_FMT_BASE_LIN | HEADER_FMT_16BITS; break; case SNDRV_PCM_FORMAT_S24_3LE: header = HEADER_FMT_BASE_LIN | HEADER_FMT_24BITS | HEADER_FMT_INTEL; break; case SNDRV_PCM_FORMAT_S24_3BE: header = HEADER_FMT_BASE_LIN | HEADER_FMT_24BITS; break; case SNDRV_PCM_FORMAT_FLOAT_LE: header = HEADER_FMT_BASE_FLOAT | HEADER_FMT_INTEL; break; default: snd_printk(KERN_ERR "error pcxhr_set_format() : unknown format\n"); return -EINVAL; } chip = snd_pcm_substream_chip(stream->substream); sample_rate = chip->mgr->sample_rate; if (sample_rate <= 32000 && sample_rate !=0) { if (sample_rate <= 11025) header |= HEADER_FMT_UPTO11; else header |= HEADER_FMT_UPTO32; } if (stream->channels == 1) header |= HEADER_FMT_MONO; is_capture = stream->pipe->is_capture; stream_num = is_capture ? 0 : stream->substream->number; pcxhr_init_rmh(&rmh, is_capture ? CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT); pcxhr_set_pipe_cmd_params(&rmh, is_capture, stream->pipe->first_audio, stream_num, 0); if (is_capture) rmh.cmd[0] |= 1<<12; rmh.cmd[1] = 0; rmh.cmd[2] = header >> 8; rmh.cmd[3] = (header & 0xff) << 16; rmh.cmd_len = 4; err = pcxhr_send_msg(chip->mgr, &rmh); if (err) snd_printk(KERN_ERR "ERROR pcxhr_set_format err=%x;\n", err); return err;}static int pcxhr_update_r_buffer(struct pcxhr_stream *stream){ int err, is_capture, stream_num; struct pcxhr_rmh rmh; struct snd_pcm_substream *subs = stream->substream; struct snd_pcxhr *chip = snd_pcm_substream_chip(subs); is_capture = (subs->stream == SNDRV_PCM_STREAM_CAPTURE); stream_num = is_capture ? 0 : subs->number; snd_printdd("pcxhr_update_r_buffer(pcm%c%d) : addr(%p) bytes(%zx) subs(%d)\n",
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -