📄 opl3sa2.c
字号:
/* * sound/opl3sa2.c * * A low level driver for Yamaha OPL3-SA2 and SA3 cards. * SAx cards should work, as they are just variants of the SA3. * * Copyright 1998, 1999 Scott Murray <scottm@interlog.com> * * Originally based on the CS4232 driver (in cs4232.c) by Hannu Savolainen * and others. Now incorporates code/ideas from pss.c, also by Hannu * Savolainen. Both of those files are distributed with the following * license: * * "Copyright (C) by Hannu Savolainen 1993-1997 * * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) * Version 2 (June 1991). See the "COPYING" file distributed with this software * for more info." * * As such, in accordance with the above license, this file, opl3sa2.c, is * distributed under the GNU GENERAL PUBLIC LICENSE (GPL) Version 2 (June 1991). * See the "COPYING" file distributed with this software for more information. * * Change History * -------------- * Scott Murray Original driver (Jun 14, 1998) * Paul J.Y. Lahaie Changed probing / attach code order * Scott Murray Added mixer support (Dec 03, 1998) * Scott Murray Changed detection code to be more forgiving, * added force option as last resort, * fixed ioctl return values. (Dec 30, 1998) * Scott Murray Simpler detection code should work all the time now * (with thanks to Ben Hutchings for the heuristic), * removed now unnecessary force option. (Jan 5, 1999) * Christoph Hellwig Adapted to module_init/module_exit (Mar 4, 2000) * */#include <linux/config.h>#include <linux/init.h>#include <linux/module.h>#include "sound_config.h"#include "ad1848.h"#include "mpu401.h"/* Useful control port indexes: */#define OPL3SA2_MASTER_LEFT 0x07#define OPL3SA2_MASTER_RIGHT 0x08#define OPL3SA2_MIC 0x09#define OPL3SA2_MISC 0x0A#define OPL3SA3_WIDE 0x14#define OPL3SA3_BASS 0x15#define OPL3SA3_TREBLE 0x16/* Useful constants: */#define DEFAULT_VOLUME 50#define DEFAULT_MIC 50#define DEFAULT_TIMBRE 0#define CHIPSET_UNKNOWN -1/* * These are used both as masks against what the card returns, * and as constants. */#define CHIPSET_OPL3SA2 1#define CHIPSET_OPL3SA3 2#define CHIPSET_OPL3SAX 4/* What's my version? */static int chipset = CHIPSET_UNKNOWN;/* Oh well, let's just cache the name */static char chipset_name[16];/* Where's my mixer */static int opl3sa2_mixer = -1;/* Bag o' mixer data */typedef struct opl3sa2_mixerdata { unsigned short cfg_port; unsigned short padding; int ad_mixer_dev; unsigned int volume_l; unsigned int volume_r; unsigned int mic; unsigned int bass; unsigned int treble;} opl3sa2_mixerdata;#ifdef CONFIG_OPL3SA2_CTRL_BASE/* Set control port if compiled into the kernel */static opl3sa2_mixerdata opl3sa2_data = { CONFIG_OPL3SA2_CTRL_BASE, };#elsestatic opl3sa2_mixerdata opl3sa2_data;#endifstatic opl3sa2_mixerdata *devc = &opl3sa2_data;/* Standard read and write functions */static void opl3sa2_write(unsigned short port, unsigned char index, unsigned char data){ outb_p(index, port); outb(data, port + 1);}static void opl3sa2_read(unsigned short port, unsigned char index, unsigned char* data){ outb_p(index, port); *data = inb(port + 1);}/* All of the mixer functions... */static void opl3sa2_set_volume(opl3sa2_mixerdata *devc, int left, int right){ static unsigned char scale[101] = { 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00 }; unsigned char vol; vol = scale[left]; /* If level is zero, turn on mute */ if(!left) vol |= 0x80; opl3sa2_write(devc->cfg_port, OPL3SA2_MASTER_LEFT, vol); vol = scale[right]; /* If level is zero, turn on mute */ if(!right) vol |= 0x80; opl3sa2_write(devc->cfg_port, OPL3SA2_MASTER_RIGHT, vol);}static void opl3sa2_set_mic(opl3sa2_mixerdata *devc, int level){ unsigned char vol = 0x1F; if((level >= 0) && (level <= 100)) vol = 0x1F - (unsigned char) (0x1F * level / 100L); /* If level is zero, turn on mute */ if(!level) vol |= 0x80; opl3sa2_write(devc->cfg_port, OPL3SA2_MIC, vol);}static void opl3sa3_set_bass(opl3sa2_mixerdata *devc, int level){ unsigned char bass; bass = level ? ((unsigned char) (0x07 * level / 100L)) : 0; bass |= (bass << 4); opl3sa2_write(devc->cfg_port, OPL3SA3_BASS, bass);}static void opl3sa3_set_treble(opl3sa2_mixerdata *devc, int level){ unsigned char treble; treble = level ? ((unsigned char) (0x07 * level / 100L)) : 0; treble |= (treble << 4); opl3sa2_write(devc->cfg_port, OPL3SA3_TREBLE, treble);}static void opl3sa2_mixer_reset(opl3sa2_mixerdata *devc){ if(devc) { opl3sa2_set_volume(devc, DEFAULT_VOLUME, DEFAULT_VOLUME); devc->volume_l = devc->volume_r = DEFAULT_VOLUME; opl3sa2_set_mic(devc, DEFAULT_MIC); devc->mic = DEFAULT_MIC; opl3sa3_set_bass(devc, DEFAULT_TIMBRE); opl3sa3_set_treble(devc, DEFAULT_TIMBRE); devc->bass = devc->treble = DEFAULT_TIMBRE; }}static void arg_to_volume_mono(unsigned int volume, int *aleft){ int left; left = volume & 0x00ff; if (left > 100) left = 100; *aleft = left;}static void arg_to_volume_stereo(unsigned int volume, int *aleft, int *aright){ arg_to_volume_mono(volume, aleft); arg_to_volume_mono(volume >> 8, aright);}static int ret_vol_mono(int left){ return ((left << 8) | left);}static int ret_vol_stereo(int left, int right){ return ((right << 8) | left);}static int call_ad_mixer(opl3sa2_mixerdata *devc, unsigned int cmd, caddr_t arg){ if(devc->ad_mixer_dev != -1) return mixer_devs[devc->ad_mixer_dev]->ioctl(devc->ad_mixer_dev, cmd, arg); else return -EINVAL;}static int opl3sa2_mixer_ioctl(int dev, unsigned int cmd, caddr_t arg){ int cmdf = cmd & 0xff; opl3sa2_mixerdata* devc = (opl3sa2_mixerdata*) mixer_devs[dev]->devc; switch(cmdf) { case SOUND_MIXER_VOLUME: case SOUND_MIXER_MIC: case SOUND_MIXER_BASS: case SOUND_MIXER_TREBLE: case SOUND_MIXER_DEVMASK: case SOUND_MIXER_STEREODEVS: case SOUND_MIXER_RECMASK: case SOUND_MIXER_CAPS: case SOUND_MIXER_RECSRC: break; default: return call_ad_mixer(devc, cmd, arg); } if(((cmd >> 8) & 0xff) != 'M') return -EINVAL; if(_SIOC_DIR (cmd) & _SIOC_WRITE) { switch (cmdf) { case SOUND_MIXER_RECSRC: if(devc->ad_mixer_dev != -1) return call_ad_mixer(devc, cmd, arg); else { if(*(int*)arg != 0) return -EINVAL; return 0; } case SOUND_MIXER_VOLUME: arg_to_volume_stereo(*(unsigned int*)arg, &devc->volume_l, &devc->volume_r); opl3sa2_set_volume(devc, devc->volume_l, devc->volume_r); *(int*)arg = ret_vol_stereo(devc->volume_l, devc->volume_r); return 0; case SOUND_MIXER_MIC: arg_to_volume_mono(*(unsigned int*)arg, &devc->mic); opl3sa2_set_mic(devc, devc->mic); *(int*)arg = ret_vol_mono(devc->mic); return 0; case SOUND_MIXER_BASS: if(chipset != CHIPSET_OPL3SA2) { arg_to_volume_mono(*(unsigned int*)arg, &devc->bass); opl3sa3_set_bass(devc, devc->bass); *(int*)arg = ret_vol_mono(devc->bass); return 0; } return -EINVAL; case SOUND_MIXER_TREBLE: if(chipset != CHIPSET_OPL3SA2) { arg_to_volume_mono(*(unsigned int *)arg, &devc->treble); opl3sa3_set_treble(devc, devc->treble); *(int*)arg = ret_vol_mono(devc->treble); return 0; } return -EINVAL; default: return -EINVAL; } } else { /* * Return parameters */ switch (cmdf) { case SOUND_MIXER_DEVMASK: if(call_ad_mixer(devc, cmd, arg) == -EINVAL) *(int*)arg = 0; /* no mixer devices */ *(int*)arg |= (SOUND_MASK_VOLUME | SOUND_MASK_MIC); /* OPL3-SA2 has no bass and treble mixers */ if(chipset != CHIPSET_OPL3SA2) *(int*)arg |= (SOUND_MASK_BASS | SOUND_MASK_TREBLE); return 0; case SOUND_MIXER_STEREODEVS: if(call_ad_mixer(devc, cmd, arg) == -EINVAL) *(int*)arg = 0; /* no stereo devices */ *(int*)arg |= SOUND_MASK_VOLUME; return 0; case SOUND_MIXER_RECMASK: if(devc->ad_mixer_dev != -1) { return call_ad_mixer(devc, cmd, arg); } else
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -