📄 harmony.c
字号:
/* * Harmony chipset driver * * This is a sound driver for ASP's and Lasi's Harmony sound chip * and is unlikely to be used for anything other than on a HP PA-RISC. * * Harmony is found in HP 712s, 715/new and many other GSC based machines. * On older 715 machines you'll find the technically identical chip * called 'Vivace'. Both Harmony and Vivace are supported by this driver. * * this ALSA driver is based on OSS driver by: * Copyright 2000 (c) Linuxcare Canada, Alex deVries <alex@linuxcare.com> * Copyright 2000-2002 (c) Helge Deller <deller@gmx.de> * Copyright 2001 (c) Matthieu Delahaye <delahaym@esiee.fr> * * TODO: * - use generic DMA interface and ioremap()/iounmap() * - capture is still untested (and probaby non-working) * - spin locks * - implement non-consistent DMA pages * - implement gain meter * - module parameters * - correct cleaning sequence * - better error checking * - try to have a better quality. * *//* * Harmony chipset 'modus operandi'. * - This chipset is found in some HP 32bit workstations, like 712, or B132 class. * most of controls are done through registers. Register are found at a fixed offset * from the hard physical adress, given in struct dev by register_parisc_driver. * * Playback and recording use 4kb pages (dma or not, depending on the machine). * * Most of PCM playback & capture is done through interrupt. When harmony needs * a new buffer to put recorded data or read played PCM, it sends an interrupt. * Bits 2 and 10 of DSTATUS register are '1' when harmony needs respectively * a new page for recording and playing. * Interrupt are disabled/enabled by writing to bit 32 of DSTATUS. * Adresses of next page to be played is put in PNXTADD register, next page * to be recorded is put in RNXTADD. There is 2 read-only registers, PCURADD and * RCURADD that provides adress of current page. * * Harmony has no way to control full duplex or half duplex mode. It means * that we always need to provide adresses of playback and capture data, even * when this is not needed. That's why we statically alloc one graveyard * buffer (to put recorded data in play-only mode) and a silence buffer. * * Bitrate, number of channels and data format are controlled with * the CNTL register. * * Mixer work is done through one register (GAINCTL). Only input gain, * output attenuation and general attenuation control is provided. There is * also controls for enabling/disabling internal speaker and line * input. * * Buffers used by this driver are all DMA consistent. */#include <linux/delay.h>#include <sound/driver.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/slab.h>#include <linux/time.h>#include <linux/wait.h>#include <linux/moduleparam.h>#include <sound/core.h>#include <sound/control.h>#include <sound/pcm.h>#include <sound/rawmidi.h>#include <sound/initval.h>#include <sound/info.h>#include <asm/hardware.h>#include <asm/io.h>#include <asm/parisc-device.h>MODULE_AUTHOR("Laurent Canet <canetl@esiee.fr>");MODULE_DESCRIPTION("ALSA Harmony sound driver");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{ALSA,Harmony soundcard}}");#undef DEBUG#ifdef DEBUG# define DPRINTK printk #else# define DPRINTK(x,...)#endif#define PFX "harmony: "#define MAX_PCM_DEVICES 1#define MAX_PCM_SUBSTREAMS 4#define MAX_MIDI_DEVICES 0#define HARMONY_BUF_SIZE 4096#define MAX_BUFS 10#define MAX_BUFFER_SIZE (MAX_BUFS * HARMONY_BUF_SIZE)/* number of silence & graveyard buffers */#define GRAVEYARD_BUFS 3#define SILENCE_BUFS 3#define HARMONY_CNTL_C 0x80000000#define HARMONY_DSTATUS_PN 0x00000200#define HARMONY_DSTATUS_RN 0x00000002#define HARMONY_DSTATUS_IE 0x80000000#define HARMONY_DF_16BIT_LINEAR 0x00000000#define HARMONY_DF_8BIT_ULAW 0x00000001#define HARMONY_DF_8BIT_ALAW 0x00000002#define HARMONY_SS_MONO 0x00000000#define HARMONY_SS_STEREO 0x00000001/* * Channels Mask in mixer register * try some "reasonable" default gain values */#define HARMONY_GAIN_TOTAL_SILENCE 0x00F00FFF/* the following should be enough (mixer is * very sensible on harmony) */#define HARMONY_GAIN_DEFAULT 0x0F2FF082/* useless since only one card is supported ATM */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;static int boot_devs;module_param_array(index, int, boot_devs, 0444);MODULE_PARM_DESC(index, "Index value for Sun CS4231 soundcard.");module_param_array(id, charp, boot_devs, 0444);MODULE_PARM_DESC(id, "ID string for Sun CS4231 soundcard.");module_param_array(enable, bool, boot_devs, 0444);MODULE_PARM_DESC(enable, "Enable Sun CS4231 soundcard.");/* Register offset (from base hpa) */#define REG_ID 0x00#define REG_RESET 0x04#define REG_CNTL 0x08#define REG_GAINCTL 0x0C#define REG_PNXTADD 0x10#define REG_PCURADD 0x14#define REG_RNXTADD 0x18#define REG_RCURADD 0x1C#define REG_DSTATUS 0x20#define REG_OV 0x24#define REG_PIO 0x28#define REG_DIAG 0x3C/* * main harmony structure */typedef struct snd_card_harmony { /* spinlocks (To be done) */ spinlock_t mixer_lock; spinlock_t control_lock; /* parameters */ int irq; unsigned long hpa; int id; int rev; u32 current_gain; int data_format; /* HARMONY_DF_xx_BIT_xxx */ int sample_rate; /* HARMONY_SR_xx_KHZ */ int stereo_select; /* HARMONY_SS_MONO or HARMONY_SS_STEREO */ int format_initialized; unsigned long ply_buffer; int ply_buf; int ply_count; int ply_size; int ply_stopped; int ply_total; unsigned long cap_buffer; int cap_buf; int cap_count; int cap_size; int cap_stopped; int cap_total; struct parisc_device *pa_dev; struct snd_dma_device dma_dev; /* the graveyard buffer is used as recording buffer when playback, * because harmony always want a buffer to put recorded data */ struct snd_dma_buffer graveyard_dma; int graveyard_count; /* same thing for silence buffer */ struct snd_dma_buffer silence_dma; int silence_count; /* alsa stuff */ snd_card_t *card; snd_pcm_t *pcm; snd_pcm_substream_t *playback_substream; snd_pcm_substream_t *capture_substream; snd_info_entry_t *proc_entry;} snd_card_harmony_t;static snd_card_t *snd_harmony_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;/* wait to be out of control mode */static inline void snd_harmony_wait_cntl(snd_card_harmony_t *harmony){ int timeout = 5000; while ( (gsc_readl(harmony->hpa+REG_CNTL) & HARMONY_CNTL_C) && --timeout) { /* Wait */ ; } if (timeout == 0) DPRINTK(KERN_DEBUG PFX "Error: wait cntl timeouted\n");}/* * sample rate routines */static unsigned int snd_card_harmony_rates[] = { 5125, 6615, 8000, 9600, 11025, 16000, 18900, 22050, 27428, 32000, 33075, 37800, 44100, 48000};static snd_pcm_hw_constraint_list_t hw_constraint_rates = { .count = ARRAY_SIZE(snd_card_harmony_rates), .list = snd_card_harmony_rates, .mask = 0,};#define HARMONY_SR_8KHZ 0x08#define HARMONY_SR_16KHZ 0x09#define HARMONY_SR_27KHZ 0x0A#define HARMONY_SR_32KHZ 0x0B#define HARMONY_SR_48KHZ 0x0E#define HARMONY_SR_9KHZ 0x0F#define HARMONY_SR_5KHZ 0x10#define HARMONY_SR_11KHZ 0x11#define HARMONY_SR_18KHZ 0x12#define HARMONY_SR_22KHZ 0x13#define HARMONY_SR_37KHZ 0x14#define HARMONY_SR_44KHZ 0x15#define HARMONY_SR_33KHZ 0x16#define HARMONY_SR_6KHZ 0x17/* bits corresponding to the entries of snd_card_harmony_rates */static unsigned int rate_bits[14] = { HARMONY_SR_5KHZ, HARMONY_SR_6KHZ, HARMONY_SR_8KHZ, HARMONY_SR_9KHZ, HARMONY_SR_11KHZ, HARMONY_SR_16KHZ, HARMONY_SR_18KHZ, HARMONY_SR_22KHZ, HARMONY_SR_27KHZ, HARMONY_SR_32KHZ, HARMONY_SR_33KHZ, HARMONY_SR_37KHZ, HARMONY_SR_44KHZ, HARMONY_SR_48KHZ};/* snd_card_harmony_rate_bits * @rate: index of current data rate in list * returns: harmony hex code for registers */static unsigned int snd_card_harmony_rate_bits(int rate){ unsigned int idx; for (idx = 0; idx <= ARRAY_SIZE(snd_card_harmony_rates); idx++) if (snd_card_harmony_rates[idx] == rate) return rate_bits[idx]; return HARMONY_SR_44KHZ; /* fallback */}/* * update controls (data format, sample rate, number of channels) * according to value supplied in data structure */void snd_harmony_update_control(snd_card_harmony_t *harmony) { u32 default_cntl; /* Set CNTL */ default_cntl = (HARMONY_CNTL_C | /* The C bit */ (harmony->data_format << 6) | /* Set the data format */ (harmony->stereo_select << 5) | /* Stereo select */ (harmony->sample_rate)); /* Set sample rate */ /* initialize CNTL */ snd_harmony_wait_cntl(harmony); gsc_writel(default_cntl, harmony->hpa+REG_CNTL); }/* * interruption controls routines */static void snd_harmony_disable_interrupts(snd_card_harmony_t *chip) { snd_harmony_wait_cntl(chip); gsc_writel(0, chip->hpa+REG_DSTATUS); }static void snd_harmony_enable_interrupts(snd_card_harmony_t *chip) { snd_harmony_wait_cntl(chip); gsc_writel(HARMONY_DSTATUS_IE, chip->hpa+REG_DSTATUS); }/* * interruption routine: * The interrupt routine must provide adresse of next physical pages * used by harmony */static int snd_card_harmony_interrupt(int irq, void *dev, struct pt_regs *regs){ snd_card_harmony_t *harmony = (snd_card_harmony_t *)dev; u32 dstatus = 0; unsigned long hpa = harmony->hpa; /* Turn off interrupts */ snd_harmony_disable_interrupts(harmony); /* wait for control to free */ snd_harmony_wait_cntl(harmony); /* Read dstatus and pcuradd (the current address) */ dstatus = gsc_readl(hpa+REG_DSTATUS); /* Check if this is a request to get the next play buffer */ if (dstatus & HARMONY_DSTATUS_PN) { if (harmony->playback_substream) { harmony->ply_buf += harmony->ply_count; harmony->ply_buf %= harmony->ply_size; gsc_writel(harmony->ply_buffer + harmony->ply_buf, hpa+REG_PNXTADD); snd_pcm_period_elapsed(harmony->playback_substream); harmony->ply_total++; } else { gsc_writel(harmony->silence_dma.addr + (HARMONY_BUF_SIZE*harmony->silence_count), hpa+REG_PNXTADD); harmony->silence_count++; harmony->silence_count %= SILENCE_BUFS; } } /* Check if we're being asked to fill in a recording buffer */ if (dstatus & HARMONY_DSTATUS_RN) { if (harmony->capture_substream) { harmony->cap_buf += harmony->cap_count; harmony->cap_buf %= harmony->cap_size; gsc_writel(harmony->cap_buffer + harmony->cap_buf, hpa+REG_RNXTADD); snd_pcm_period_elapsed(harmony->capture_substream); harmony->cap_total++; } else { /* graveyard buffer */ gsc_writel(harmony->graveyard_dma.addr + (HARMONY_BUF_SIZE*harmony->graveyard_count), hpa+REG_RNXTADD); harmony->graveyard_count++; harmony->graveyard_count %= GRAVEYARD_BUFS; } } snd_harmony_enable_interrupts(harmony);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -