--- sys/conf/files.orig Fri Jun 9 01:48:35 2006 +++ sys/conf/files Tue Apr 17 20:53:33 2007 @@ -40,6 +40,21 @@ compile-with "CC=${CC} AWK=${AWK} sh $S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/emu10k1-alsa.h emu10k1-alsa%diked.h" \ no-obj no-implicit-rule before-depend \ clean "emu10k1-alsa%diked.h" +emu10k1-alsa%diked.h optional snd_emu10kx pci \ + dependency "$S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/emu10k1-alsa.h" \ + compile-with "CC='${CC}' AWK=${AWK} sh $S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/emu10k1-alsa.h emu10k1-alsa%diked.h" \ + no-obj no-implicit-rule before-depend \ + clean "emu10k1-alsa%diked.h" +p16v-alsa%diked.h optional snd_emu10kx pci \ + dependency "$S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/p16v-alsa.h" \ + compile-with "CC='${CC}' AWK=${AWK} sh $S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/p16v-alsa.h p16v-alsa%diked.h" \ + no-obj no-implicit-rule before-depend \ + clean "p16v-alsa%diked.h" +p17v-alsa%diked.h optional snd_emu10kx pci \ + dependency "$S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/p17v-alsa.h" \ + compile-with "CC='${CC}' AWK=${AWK} sh $S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/p17v-alsa.h p17v-alsa%diked.h" \ + no-obj no-implicit-rule before-depend \ + clean "p17v-alsa%diked.h" miidevs.h optional miibus \ dependency "$S/tools/miidevs2h.awk $S/dev/mii/miidevs" \ compile-with "${AWK} -f $S/tools/miidevs2h.awk $S/dev/mii/miidevs" \ @@ -695,8 +710,9 @@ dev/sn/if_sn_pccard.c optional sn card dev/sn/if_sn_pccard.c optional sn pccard dev/snp/snp.c optional snp +dev/sound/clone.c optional sound +dev/sound/unit.c optional sound dev/sound/isa/ad1816.c optional snd_ad1816 isa -dev/sound/isa/es1888.c optional snd_ess isa dev/sound/isa/ess.c optional snd_ess isa dev/sound/isa/gusc.c optional snd_gusc isa dev/sound/isa/mss.c optional snd_mss isa @@ -705,6 +721,7 @@ dev/sound/isa/sbc.c optional snd_sbc isa dev/sound/isa/sndbuf_dma.c optional sound isa dev/sound/pci/als4000.c optional snd_als4000 pci +dev/sound/pci/atiixp.c optional snd_atiixp pci #dev/sound/pci/au88x0.c optional snd_au88x0 pci dev/sound/pci/cmi.c optional snd_cmi pci dev/sound/pci/cs4281.c optional snd_cs4281 pci @@ -713,8 +730,22 @@ dev/sound/pci/csapcm.c optional snd_csa pci dev/sound/pci/ds1.c optional snd_ds1 pci dev/sound/pci/emu10k1.c optional snd_emu10k1 pci \ + dependency "emu10k1-alsa%diked.h" +dev/sound/pci/emu10kx.c optional snd_emu10kx pci \ + dependency "emu10k1-alsa%diked.h" \ + dependency "p16v-alsa%diked.h" \ + dependency "p17v-alsa%diked.h" \ + warning "kernel contains GPL contaminated emu10kx headers" +dev/sound/pci/emu10kx-pcm.c optional snd_emu10kx pci \ + dependency "emu10k1-alsa%diked.h" \ + dependency "p16v-alsa%diked.h" \ + dependency "p17v-alsa%diked.h" \ + warning "kernel contains GPL contaminated emu10kx headers" +dev/sound/pci/emu10kx-midi.c optional snd_emu10kx pci \ dependency "emu10k1-alsa%diked.h" \ - warning "kernel contains GPL contaminated emu10k1 headers" + warning "kernel contains GPL contaminated emu10kx headers" +dev/sound/pci/envy24.c optional snd_envy24 pci +dev/sound/pci/envy24ht.c optional snd_envy24ht pci dev/sound/pci/es137x.c optional snd_es137x pci dev/sound/pci/fm801.c optional snd_fm801 pci dev/sound/pci/ich.c optional snd_ich pci @@ -723,10 +754,12 @@ warning "kernel contains GPL contaminated maestro3 headers" dev/sound/pci/neomagic.c optional snd_neomagic pci dev/sound/pci/solo.c optional snd_solo pci +dev/sound/pci/spicds.c optional snd_spicds pci dev/sound/pci/t4dwave.c optional snd_t4dwave pci dev/sound/pci/via8233.c optional snd_via8233 pci dev/sound/pci/via82c686.c optional snd_via82c686 pci dev/sound/pci/vibes.c optional snd_vibes pci +dev/sound/pci/hda/hdac.c optional snd_hda pci dev/sound/pcm/ac97.c optional sound dev/sound/pcm/ac97_patch.c optional sound dev/sound/pcm/ac97_if.m optional sound @@ -739,6 +772,7 @@ dev/sound/pcm/feeder_if.m optional sound dev/sound/pcm/feeder_fmt.c optional sound dev/sound/pcm/feeder_rate.c optional sound +dev/sound/pcm/feeder_volume.c optional sound dev/sound/pcm/mixer.c optional sound dev/sound/pcm/mixer_if.m optional sound dev/sound/pcm/sndstat.c optional sound @@ -747,6 +781,12 @@ #dev/sound/usb/upcm.c optional snd_upcm usb dev/sound/usb/uaudio.c optional snd_uaudio usb dev/sound/usb/uaudio_pcm.c optional snd_uaudio usb +dev/sound/midi/midi.c optional sound +dev/sound/midi/mpu401.c optional sound +dev/sound/midi/mpu_if.m optional sound +dev/sound/midi/mpufoi_if.m optional sound +dev/sound/midi/sequencer.c optional sound +dev/sound/midi/synth_if.m optional sound dev/sr/if_sr.c optional sr dev/sr/if_sr_pci.c optional sr pci dev/streams/streams.c optional streams --- sys/conf/kmod.mk.orig Thu Mar 17 01:51:56 2005 +++ sys/conf/kmod.mk Fri Nov 3 22:10:30 2006 @@ -299,7 +299,8 @@ dev/pci/pcib_if.m dev/ppbus/ppbus_if.m dev/smbus/smbus_if.m \ dev/sound/pcm/ac97_if.m dev/sound/pcm/channel_if.m \ dev/sound/pcm/feeder_if.m dev/sound/pcm/mixer_if.m dev/uart/uart_if.m \ - dev/usb/usb_if.m isa/isa_if.m \ + dev/sound/midi/mpu_if.m dev/sound/midi/mpufoi_if.m \ + dev/sound/midi/synth_if.m dev/usb/usb_if.m isa/isa_if.m \ kern/bus_if.m kern/cpufreq_if.m kern/device_if.m \ libkern/iconv_converter_if.m opencrypto/crypto_if.m \ pc98/pc98/canbus_if.m pci/agp_if.m sparc64/pci/ofw_pci_if.m --- sys/conf/options.orig Wed Jul 20 14:04:21 2005 +++ sys/conf/options Tue Apr 17 20:56:54 2007 @@ -690,3 +690,6 @@ DCONS_POLL_HZ opt_dcons.h DCONS_FORCE_CONSOLE opt_dcons.h DCONS_FORCE_GDB opt_dcons.h + +# snd_emu10kx sound driver options +SND_EMU10KX_MULTICHANNEL opt_emu10kx.h --- sys/sys/bus.h.orig Tue Apr 17 23:28:41 2007 +++ sys/sys/bus.h Tue Apr 17 23:28:13 2007 @@ -321,6 +321,8 @@ return (bus_alloc_resource(dev, type, rid, 0ul, ~0ul, 1, flags)); } +#define bus_get_dma_tag(a) NULL + /* * Access functions for device. */ --- sys/sys/soundcard.h.orig Tue Feb 1 07:26:57 2005 +++ sys/sys/soundcard.h Thu Jul 12 12:04:19 2007 @@ -3,7 +3,7 @@ */ /*- - * Copyright by Hannu Savolainen 1993 + * Copyright by Hannu Savolainen 1993 / 4Front Technologies 1993-2006 * Modified for the new FreeBSD sound driver by Luigi Rizzo, 1997 * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,13 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/sys/sys/soundcard.h,v 1.43.4.1 2005/01/31 23:26:57 imp Exp $ + * $FreeBSD: src/sys/sys/soundcard.h,v 1.48 2006/11/26 11:55:48 netchild Exp $ + */ + +/* + * Unless coordinating changes with 4Front Technologies, do NOT make any + * modifications to ioctl commands, types, etc. that would break + * compatibility with the OSS API. */ #ifndef _SYS_SOUNDCARD_H_ @@ -180,6 +186,10 @@ #define AFMT_S32_BE 0x00002000 /* Big endian signed 32-bit */ #define AFMT_U32_LE 0x00004000 /* Little endian unsigned 32-bit */ #define AFMT_U32_BE 0x00008000 /* Big endian unsigned 32-bit */ +#define AFMT_S24_LE 0x00010000 /* Little endian signed 24-bit */ +#define AFMT_S24_BE 0x00020000 /* Big endian signed 24-bit */ +#define AFMT_U24_LE 0x00040000 /* Little endian unsigned 24-bit */ +#define AFMT_U24_BE 0x00080000 /* Big endian unsigned 24-bit */ #define AFMT_STEREO 0x10000000 /* can do/want stereo */ @@ -715,8 +725,6 @@ int caps; }; -#define MIDI_CAP_MPU401 1 /* MPU-401 intelligent mode */ - struct midi_info { char name[30]; int device; /* 0-N. INITIALIZE BEFORE CALLING */ @@ -1432,5 +1440,439 @@ #define SOUND_PCM_GETOPTR SNDCTL_DSP_GETOPTR #define SOUND_PCM_MAPINBUF SNDCTL_DSP_MAPINBUF #define SOUND_PCM_MAPOUTBUF SNDCTL_DSP_MAPOUTBUF + +/***********************************************************************/ + +/** + * XXX OSSv4 defines -- some bits taken straight out of the new + * sys/soundcard.h bundled with recent OSS releases. + * + * NB: These macros and structures will be reorganized and inserted + * in appropriate places throughout this file once the code begins + * to take shape. + * + * @todo reorganize layout more like the 4Front version + * @todo ask about maintaining __SIOWR vs. _IOWR ioctl cmd defines + */ + +/** + * @note The @c OSSV4_EXPERIMENT macro is meant to wrap new development code + * in the sound system relevant to adopting 4Front's OSSv4 specification. + * Users should not enable this! Really! + */ +#if 0 +# define OSSV4_EXPERIMENT 1 +#else +# undef OSSV4_EXPERIMENT +#endif + +#ifdef SOUND_VERSION +# undef SOUND_VERSION +# define SOUND_VERSION 0x040000 +#endif /* !SOUND_VERSION */ + +#define OSS_LONGNAME_SIZE 64 +#define OSS_LABEL_SIZE 16 +#define OSS_DEVNODE_SIZE 32 +typedef char oss_longname_t[OSS_LONGNAME_SIZE]; +typedef char oss_label_t[OSS_LABEL_SIZE]; +typedef char oss_devnode_t[OSS_DEVNODE_SIZE]; + +typedef struct audio_errinfo +{ + int play_underruns; + int rec_overruns; + unsigned int play_ptradjust; + unsigned int rec_ptradjust; + int play_errorcount; + int rec_errorcount; + int play_lasterror; + int rec_lasterror; + long play_errorparm; + long rec_errorparm; + int filler[16]; +} audio_errinfo; + +#define SNDCTL_DSP_GETPLAYVOL _IOR ('P', 24, int) +#define SNDCTL_DSP_SETPLAYVOL _IOWR('P', 24, int) +#define SNDCTL_DSP_GETERROR _IOR ('P', 25, audio_errinfo) + + +/* + **************************************************************************** + * Sync groups for audio devices + */ +typedef struct oss_syncgroup +{ + int id; + int mode; + int filler[16]; +} oss_syncgroup; + +#define SNDCTL_DSP_SYNCGROUP _IOWR('P', 28, oss_syncgroup) +#define SNDCTL_DSP_SYNCSTART _IOW ('P', 29, int) + +/* + ************************************************************************** + * "cooked" mode enables software based conversions for sample rate, sample + * format (bits) and number of channels (mono/stereo). These conversions are + * required with some devices that support only one sample rate or just stereo + * to let the applications to use other formats. The cooked mode is enabled by + * default. However it's necessary to disable this mode when mmap() is used or + * when very deterministic timing is required. SNDCTL_DSP_COOKEDMODE is an + * optional call introduced in OSS 3.9.6f. It's _error return must be ignored_ + * since normally this call will return erno=EINVAL. + * + * SNDCTL_DSP_COOKEDMODE must be called immediately after open before doing + * anything else. Otherwise the call will not have any effect. + */ +#define SNDCTL_DSP_COOKEDMODE _IOW ('P', 30, int) + +/* + ************************************************************************** + * SNDCTL_DSP_SILENCE and SNDCTL_DSP_SKIP are new calls in OSS 3.99.0 + * that can be used to implement pause/continue during playback (no effect + * on recording). + */ +#define SNDCTL_DSP_SILENCE _IO ('P', 31) +#define SNDCTL_DSP_SKIP _IO ('P', 32) + +/* + **************************************************************************** + * Abort transfer (reset) functions for input and output + */ +#define SNDCTL_DSP_HALT_INPUT _IO ('P', 33) +#define SNDCTL_DSP_RESET_INPUT SNDCTL_DSP_HALT_INPUT /* Old name */ +#define SNDCTL_DSP_HALT_OUTPUT _IO ('P', 34) +#define SNDCTL_DSP_RESET_OUTPUT SNDCTL_DSP_HALT_OUTPUT /* Old name */ + +/* + **************************************************************************** + * Low water level control + */ +#define SNDCTL_DSP_LOW_WATER _IOW ('P', 34, int) + +/** @todo Get rid of OSS_NO_LONG_LONG references? */ + +/* + **************************************************************************** + * 64 bit pointer support. Only available in environments that support + * the 64 bit (long long) integer type. + */ +#ifndef OSS_NO_LONG_LONG +typedef struct +{ + long long samples; + int fifo_samples; + int filler[32]; /* For future use */ +} oss_count_t; + +#define SNDCTL_DSP_CURRENT_IPTR _IOR ('P', 35, oss_count_t) +#define SNDCTL_DSP_CURRENT_OPTR _IOR ('P', 36, oss_count_t) +#endif + +/* + **************************************************************************** + * Interface for selecting recording sources and playback output routings. + */ +#define SNDCTL_DSP_GET_RECSRC_NAMES _IOR ('P', 37, oss_mixer_enuminfo) +#define SNDCTL_DSP_GET_RECSRC _IOR ('P', 38, int) +#define SNDCTL_DSP_SET_RECSRC _IOWR('P', 38, int) + +#define SNDCTL_DSP_GET_PLAYTGT_NAMES _IOR ('P', 39, oss_mixer_enuminfo) +#define SNDCTL_DSP_GET_PLAYTGT _IOR ('P', 40, int) +#define SNDCTL_DSP_SET_PLAYTGT _IOWR('P', 40, int) +#define SNDCTL_DSP_GETRECVOL _IOR ('P', 41, int) +#define SNDCTL_DSP_SETRECVOL _IOWR('P', 41, int) + +/* + *************************************************************************** + * Some calls for setting the channel assignment with multi channel devices + * (see the manual for details). */ +#define SNDCTL_DSP_GET_CHNORDER _IOR ('P', 42, unsigned long long) +#define SNDCTL_DSP_SET_CHNORDER _IOWR('P', 42, unsigned long long) +# define CHID_UNDEF 0 +# define CHID_L 1 # define CHID_R 2 +# define CHID_C 3 +# define CHID_LFE 4 +# define CHID_LS 5 +# define CHID_RS 6 +# define CHID_LR 7 +# define CHID_RR 8 +#define CHNORDER_UNDEF 0x0000000000000000ULL +#define CHNORDER_NORMAL 0x0000000087654321ULL + +#define MAX_PEAK_CHANNELS 128 +typedef unsigned short oss_peaks_t[MAX_PEAK_CHANNELS]; +#define SNDCTL_DSP_GETIPEAKS _IOR('P', 43, oss_peaks_t) +#define SNDCTL_DSP_GETOPEAKS _IOR('P', 44, oss_peaks_t) +#define SNDCTL_DSP_POLICY _IOW('P', 45, int) /* See the manual */ + +/* + * OSS_SYSIFO is obsolete. Use SNDCTL_SYSINFO insteads. + */ +#define OSS_GETVERSION _IOR ('M', 118, int) + +/** + * @brief Argument for SNDCTL_SYSINFO ioctl. + * + * For use w/ the SNDCTL_SYSINFO ioctl available on audio (/dev/dsp*), + * mixer, and MIDI devices. + */ +typedef struct oss_sysinfo +{ + char product[32]; /* For example OSS/Free, OSS/Linux or + OSS/Solaris */ + char version[32]; /* For example 4.0a */ + int versionnum; /* See OSS_GETVERSION */ + char options[128]; /* Reserved */ + + int numaudios; /* # of audio/dsp devices */ + int openedaudio[8]; /* Bit mask telling which audio devices + are busy */ + + int numsynths; /* # of availavle synth devices */ + int nummidis; /* # of available MIDI ports */ + int numtimers; /* # of available timer devices */ + int nummixers; /* # of mixer devices */ + + int openedmidi[8]; /* Bit mask telling which midi devices + are busy */ + int numcards; /* Number of sound cards in the system */ + int filler[241]; /* For future expansion (set to -1) */ +} oss_sysinfo; + +typedef struct oss_mixext +{ + int dev; /* Mixer device number */ + int ctrl; /* Controller number */ + int type; /* Entry type */ +# define MIXT_DEVROOT 0 /* Device root entry */ +# define MIXT_GROUP 1 /* Controller group */ +# define MIXT_ONOFF 2 /* OFF (0) or ON (1) */ +# define MIXT_ENUM 3 /* Enumerated (0 to maxvalue) */ +# define MIXT_MONOSLIDER 4 /* Mono slider (0 to 100) */ +# define MIXT_STEREOSLIDER 5 /* Stereo slider (dual 0 to 100) */ +# define MIXT_MESSAGE 6 /* (Readable) textual message */ +# define MIXT_MONOVU 7 /* VU meter value (mono) */ +# define MIXT_STEREOVU 8 /* VU meter value (stereo) */ +# define MIXT_MONOPEAK 9 /* VU meter peak value (mono) */ +# define MIXT_STEREOPEAK 10 /* VU meter peak value (stereo) */ +# define MIXT_RADIOGROUP 11 /* Radio button group */ +# define MIXT_MARKER 12 /* Separator between normal and extension entries */ +# define MIXT_VALUE 13 /* Decimal value entry */ +# define MIXT_HEXVALUE 14 /* Hexadecimal value entry */ +# define MIXT_MONODB 15 /* Mono atten. slider (0 to -144) */ +# define MIXT_STEREODB 16 /* Stereo atten. slider (dual 0 to -144) */ +# define MIXT_SLIDER 17 /* Slider (mono) with full integer range */ +# define MIXT_3D 18 + + /* Possible value range (minvalue to maxvalue) */ + /* Note that maxvalue may also be smaller than minvalue */ + int maxvalue; + int minvalue; + + int flags; +# define MIXF_READABLE 0x00000001 /* Has readable value */ +# define MIXF_WRITEABLE 0x00000002 /* Has writeable value */ +# define MIXF_POLL 0x00000004 /* May change itself */ +# define MIXF_HZ 0x00000008 /* Herz scale */ +# define MIXF_STRING 0x00000010 /* Use dynamic extensions for value */ +# define MIXF_DYNAMIC 0x00000010 /* Supports dynamic extensions */ +# define MIXF_OKFAIL 0x00000020 /* Interpret value as 1=OK, 0=FAIL */ +# define MIXF_FLAT 0x00000040 /* Flat vertical space requirements */ +# define MIXF_LEGACY 0x00000080 /* Legacy mixer control group */ + char id[16]; /* Mnemonic ID (mainly for internal use) */ + int parent; /* Entry# of parent (group) node (-1 if root) */ + + int dummy; /* Internal use */ + + int timestamp; + + char data[64]; /* Misc data (entry type dependent) */ + unsigned char enum_present[32]; /* Mask of allowed enum values */ + int control_no; /* SOUND_MIXER_VOLUME..SOUND_MIXER_MIDI */ + /* (-1 means not indicated) */ + +/* + * The desc field is reserved for internal purposes of OSS. It should not be + * used by applications. + */ + unsigned int desc; +#define MIXEXT_SCOPE_MASK 0x0000003f +#define MIXEXT_SCOPE_OTHER 0x00000000 +#define MIXEXT_SCOPE_INPUT 0x00000001 +#define MIXEXT_SCOPE_OUTPUT 0x00000002 +#define MIXEXT_SCOPE_MONITOR 0x00000003 +#define MIXEXT_SCOPE_RECSWITCH 0x00000004 + + char extname[32]; + int update_counter; + int filler[7]; +} oss_mixext; + +typedef struct oss_mixext_root +{ + char id[16]; + char name[48]; +} oss_mixext_root; + +typedef struct oss_mixer_value +{ + int dev; + int ctrl; + int value; + int flags; /* Reserved for future use. Initialize to 0 */ + int timestamp; /* Must be set to oss_mixext.timestamp */ + int filler[8]; /* Reserved for future use. Initialize to 0 */ +} oss_mixer_value; + +#define OSS_ENUM_MAXVALUE 255 +typedef struct oss_mixer_enuminfo +{ + int dev; + int ctrl; + int nvalues; + int version; /* Read the manual */ + short strindex[OSS_ENUM_MAXVALUE]; + char strings[3000]; +} oss_mixer_enuminfo; + +#define OPEN_READ PCM_ENABLE_INPUT +#define OPEN_WRITE PCM_ENABLE_OUTPUT +#define OPEN_READWRITE (OPEN_READ|OPEN_WRITE) + +/** + * @brief Argument for SNDCTL_AUDIOINFO ioctl. + * + * For use w/ the SNDCTL_AUDIOINFO ioctl available on audio (/dev/dsp*) + * devices. + */ +typedef struct oss_audioinfo +{ + int dev; /* Audio device number */ + char name[64]; + int busy; /* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */ + int pid; + int caps; /* DSP_CAP_INPUT, DSP_CAP_OUTPUT */ + int iformats; + int oformats; + int magic; /* Reserved for internal use */ + char cmd[64]; /* Command using the device (if known) */ + int card_number; + int port_number; + int mixer_dev; + int real_device; /* Obsolete field. Replaced by devnode */ + int enabled; /* 1=enabled, 0=device not ready at this + moment */ + int flags; /* For internal use only - no practical + meaning */ + int min_rate; /* Sample rate limits */ + int max_rate; + int min_channels; /* Number of channels supported */ + int max_channels; + int binding; /* DSP_BIND_FRONT, etc. 0 means undefined */ + int rate_source; + char handle[32]; + #define OSS_MAX_SAMPLE_RATES 20 /* Cannot be changed */ + unsigned int nrates; + unsigned int rates[OSS_MAX_SAMPLE_RATES]; /* Please read the manual before using these */ + oss_longname_t song_name; /* Song name (if given) */ + oss_label_t label; /* Device label (if given) */ + int latency; /* In usecs, -1=unknown */ + oss_devnode_t devnode; /* Device special file name (inside + /dev) */ + int filler[186]; +} oss_audioinfo; + +typedef struct oss_mixerinfo +{ + int dev; + char id[16]; + char name[32]; + int modify_counter; + int card_number; + int port_number; + char handle[32]; + int magic; /* Reserved */ + int enabled; /* Reserved */ + int caps; +#define MIXER_CAP_VIRTUAL 0x00000001 + int flags; /* Reserved */ + int nrext; + /* + * The priority field can be used to select the default (motherboard) + * mixer device. The mixer with the highest priority is the + * most preferred one. -2 or less means that this device cannot be used + * as the default mixer. + */ + int priority; + int filler[254]; /* Reserved */ +} oss_mixerinfo; + +typedef struct oss_midi_info +{ + int dev; /* Midi device number */ + char name[64]; + int busy; /* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */ + int pid; + char cmd[64]; /* Command using the device (if known) */ + int caps; +#define MIDI_CAP_MPU401 0x00000001 /**** OBSOLETE ****/ +#define MIDI_CAP_INPUT 0x00000002 +#define MIDI_CAP_OUTPUT 0x00000004 +#define MIDI_CAP_INOUT (MIDI_CAP_INPUT|MIDI_CAP_OUTPUT) +#define MIDI_CAP_VIRTUAL 0x00000008 /* Pseudo device */ +#define MIDI_CAP_MTCINPUT 0x00000010 /* Supports SNDCTL_MIDI_MTCINPUT */ +#define MIDI_CAP_CLIENT 0x00000020 /* Virtual client side device */ +#define MIDI_CAP_SERVER 0x00000040 /* Virtual server side device */ +#define MIDI_CAP_INTERNAL 0x00000080 /* Internal (synth) device */ +#define MIDI_CAP_EXTERNAL 0x00000100 /* external (MIDI port) device */ +#define MIDI_CAP_PTOP 0x00000200 /* Point to point link to one device */ +#define MIDI_CAP_MTC 0x00000400 /* MTC/SMPTE (control) device */ + int magic; /* Reserved for internal use */ + int card_number; + int port_number; + int enabled; /* 1=enabled, 0=device not ready at this moment */ + int flags; /* For internal use only - no practical meaning */ + char handle[32]; + oss_longname_t song_name; /* Song name (if known) */ + oss_label_t label; /* Device label (if given) */ + int latency; /* In usecs, -1=unknown */ + int filler[244]; +} oss_midi_info; + +typedef struct oss_card_info +{ + int card; + char shortname[16]; + char longname[128]; + int flags; + int filler[256]; +} oss_card_info; + +#define SNDCTL_SYSINFO _IOR ('X', 1, oss_sysinfo) +#define OSS_SYSINFO SNDCTL_SYSINFO /* Old name */ + +#define SNDCTL_MIX_NRMIX _IOR ('X', 2, int) +#define SNDCTL_MIX_NREXT _IOWR('X', 3, int) +#define SNDCTL_MIX_EXTINFO _IOWR('X', 4, oss_mixext) +#define SNDCTL_MIX_READ _IOWR('X', 5, oss_mixer_value) +#define SNDCTL_MIX_WRITE _IOWR('X', 6, oss_mixer_value) + +#define SNDCTL_AUDIOINFO _IOWR('X', 7, oss_audioinfo) +#define SNDCTL_MIX_ENUMINFO _IOWR('X', 8, oss_mixer_enuminfo) +#define SNDCTL_MIDIINFO _IOWR('X', 9, oss_midi_info) +#define SNDCTL_MIXERINFO _IOWR('X',10, oss_mixerinfo) +#define SNDCTL_CARDINFO _IOWR('X',11, oss_card_info) + +/* + * Few more "globally" available ioctl calls. + */ +#define SNDCTL_SETSONG _IOW ('Y', 2, oss_longname_t) +#define SNDCTL_GETSONG _IOR ('Y', 2, oss_longname_t) +#define SNDCTL_SETNAME _IOW ('Y', 3, oss_longname_t) +#define SNDCTL_SETLABEL _IOW ('Y', 4, oss_label_t) +#define SNDCTL_GETLABEL _IOR ('Y', 4, oss_label_t) #endif /* !_SYS_SOUNDCARD_H_ */ --- sys/dev/sound/clone.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/clone.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,795 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/clone.c,v 1.4 2007/06/14 11:10:21 ariff Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#if defined(SND_DIAGNOSTIC) || defined(SND_DEBUG) +#include +#endif + +#include + +/* + * So here we go again, another clonedevs manager. Unlike default clonedevs, + * this clone manager is designed to withstand various abusive behavior + * (such as 'while : ; do ls /dev/whatever ; done', etc.), reusable object + * after reaching certain expiration threshold, aggressive garbage collector, + * transparent device allocator and concurrency handling across multiple + * thread/proc. Due to limited information given by dev_clone EVENTHANDLER, + * we don't have much clues whether the caller wants a real open() or simply + * making fun of us with things like stat(), mtime() etc. Assuming that: + * 1) Time window between dev_clone EH <-> real open() should be small + * enough and 2) mtime()/stat() etc. always looks like a half way / stalled + * operation, we can decide whether a new cdev must be created, old + * (expired) cdev can be reused or an existing cdev can be shared. + * + * Most of the operations and logics are generic enough and can be applied + * on other places (such as if_tap, snp, etc). Perhaps this can be + * rearranged to complement clone_*(). However, due to this still being + * specific to the sound driver (and as a proof of concept on how it can be + * done), si_drv2 is used to keep the pointer of the clone list entry to + * avoid expensive lookup. + */ + +/* clone entry */ +struct snd_clone_entry { + TAILQ_ENTRY(snd_clone_entry) link; + struct snd_clone *parent; + struct cdev *devt; + struct timespec tsp; + uint32_t flags; + pid_t pid; + int unit; +}; + +/* clone manager */ +struct snd_clone { + TAILQ_HEAD(link_head, snd_clone_entry) head; + struct timespec tsp; + int refcount; + int size; + int typemask; + int maxunit; + int deadline; + uint32_t flags; +}; + +#ifdef SND_DIAGNOSTIC +#define SND_CLONE_ASSERT(x, y) do { \ + if (!(x)) \ + panic y; \ +} while(0) +#else +#define SND_CLONE_ASSERT(x...) KASSERT(x) +#endif + +/* + * Shamelessly ripped off from vfs_subr.c + * We need at least 1/HZ precision as default timestamping. + */ +enum { SND_TSP_SEC, SND_TSP_HZ, SND_TSP_USEC, SND_TSP_NSEC }; + +static int snd_timestamp_precision = SND_TSP_HZ; +TUNABLE_INT("hw.snd.timestamp_precision", &snd_timestamp_precision); + +void +snd_timestamp(struct timespec *tsp) +{ + struct timeval tv; + + switch (snd_timestamp_precision) { + case SND_TSP_SEC: + tsp->tv_sec = time_second; + tsp->tv_nsec = 0; + break; + case SND_TSP_HZ: + getnanouptime(tsp); + break; + case SND_TSP_USEC: + microuptime(&tv); + TIMEVAL_TO_TIMESPEC(&tv, tsp); + break; + case SND_TSP_NSEC: + nanouptime(tsp); + break; + default: + snd_timestamp_precision = SND_TSP_HZ; + getnanouptime(tsp); + break; + } +} + +#if defined(SND_DIAGNOSTIC) || defined(SND_DEBUG) +static int +sysctl_hw_snd_timestamp_precision(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = snd_timestamp_precision; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err == 0 && req->newptr != NULL) { + switch (val) { + case SND_TSP_SEC: + case SND_TSP_HZ: + case SND_TSP_USEC: + case SND_TSP_NSEC: + snd_timestamp_precision = val; + break; + default: + break; + } + } + + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, timestamp_precision, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_timestamp_precision, "I", + "timestamp precision (0=s 1=hz 2=us 3=ns)"); +#endif + +/* + * snd_clone_create() : Return opaque allocated clone manager. + */ +struct snd_clone * +snd_clone_create(int typemask, int maxunit, int deadline, uint32_t flags) +{ + struct snd_clone *c; + + SND_CLONE_ASSERT(!(typemask & ~SND_CLONE_MAXUNIT), + ("invalid typemask: 0x%08x", typemask)); + SND_CLONE_ASSERT(maxunit == -1 || + !(maxunit & ~(~typemask & SND_CLONE_MAXUNIT)), + ("maxunit overflow: typemask=0x%08x maxunit=%d", + typemask, maxunit)); + SND_CLONE_ASSERT(!(flags & ~SND_CLONE_MASK), + ("invalid clone flags=0x%08x", flags)); + + c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK | M_ZERO); + c->refcount = 0; + c->size = 0; + c->typemask = typemask; + c->maxunit = (maxunit == -1) ? (~typemask & SND_CLONE_MAXUNIT) : + maxunit; + c->deadline = deadline; + c->flags = flags; + snd_timestamp(&c->tsp); + TAILQ_INIT(&c->head); + + return (c); +} + +int +snd_clone_busy(struct snd_clone *c) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + if (c->size == 0) + return (0); + + TAILQ_FOREACH(ce, &c->head, link) { + if ((ce->flags & SND_CLONE_BUSY) || + (ce->devt != NULL && ce->devt->si_threadcount != 0)) + return (EBUSY); + } + + return (0); +} + +/* + * snd_clone_enable()/disable() : Suspend/resume clone allocation through + * snd_clone_alloc(). Everything else will not be affected by this. + */ +int +snd_clone_enable(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + if (c->flags & SND_CLONE_ENABLE) + return (EINVAL); + + c->flags |= SND_CLONE_ENABLE; + + return (0); +} + +int +snd_clone_disable(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + if (!(c->flags & SND_CLONE_ENABLE)) + return (EINVAL); + + c->flags &= ~SND_CLONE_ENABLE; + + return (0); +} + +/* + * Getters / Setters. Not worth explaining :) + */ +int +snd_clone_getsize(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + return (c->size); +} + +int +snd_clone_getmaxunit(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + return (c->maxunit); +} + +int +snd_clone_setmaxunit(struct snd_clone *c, int maxunit) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + SND_CLONE_ASSERT(maxunit == -1 || + !(maxunit & ~(~c->typemask & SND_CLONE_MAXUNIT)), + ("maxunit overflow: typemask=0x%08x maxunit=%d", + c->typemask, maxunit)); + + c->maxunit = (maxunit == -1) ? (~c->typemask & SND_CLONE_MAXUNIT) : + maxunit; + + return (c->maxunit); +} + +int +snd_clone_getdeadline(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + return (c->deadline); +} + +int +snd_clone_setdeadline(struct snd_clone *c, int deadline) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + c->deadline = deadline; + + return (c->deadline); +} + +int +snd_clone_gettime(struct snd_clone *c, struct timespec *tsp) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + SND_CLONE_ASSERT(tsp != NULL, ("NULL timespec")); + + *tsp = c->tsp; + + return (0); +} + +uint32_t +snd_clone_getflags(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + return (c->flags); +} + +uint32_t +snd_clone_setflags(struct snd_clone *c, uint32_t flags) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + SND_CLONE_ASSERT(!(flags & ~SND_CLONE_MASK), + ("invalid clone flags=0x%08x", flags)); + + c->flags = flags; + + return (c->flags); +} + +int +snd_clone_getdevtime(struct cdev *dev, struct timespec *tsp) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + SND_CLONE_ASSERT(tsp != NULL, ("NULL timespec")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + *tsp = ce->tsp; + + return (0); +} + +uint32_t +snd_clone_getdevflags(struct cdev *dev) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0xffffffff); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + return (ce->flags); +} + +uint32_t +snd_clone_setdevflags(struct cdev *dev, uint32_t flags) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + SND_CLONE_ASSERT(!(flags & ~SND_CLONE_DEVMASK), + ("invalid clone dev flags=0x%08x", flags)); + + ce = dev->si_drv2; + if (ce == NULL) + return (0xffffffff); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + ce->flags = flags; + + return (ce->flags); +} + +/* Elapsed time conversion to ms */ +#define SND_CLONE_ELAPSED(x, y) \ + ((((x)->tv_sec - (y)->tv_sec) * 1000) + \ + (((y)->tv_nsec > (x)->tv_nsec) ? \ + (((1000000000L + (x)->tv_nsec - \ + (y)->tv_nsec) / 1000000) - 1000) : \ + (((x)->tv_nsec - (y)->tv_nsec) / 1000000))) + +#define SND_CLONE_EXPIRED(x, y, z) \ + ((x)->deadline < 1 || \ + ((y)->tv_sec - (z)->tv_sec) > ((x)->deadline / 1000) || \ + SND_CLONE_ELAPSED(y, z) > (x)->deadline) + +/* + * snd_clone_gc() : Garbage collector for stalled, expired objects. Refer to + * clone.h for explanations on GC settings. + */ +int +snd_clone_gc(struct snd_clone *c) +{ + struct snd_clone_entry *ce, *tce; + struct timespec now; + int pruned; + + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + if (!(c->flags & SND_CLONE_GC_ENABLE) || c->size == 0) + return (0); + + snd_timestamp(&now); + + /* + * Bail out if the last clone handler was invoked below the deadline + * threshold. + */ + if ((c->flags & SND_CLONE_GC_EXPIRED) && + !SND_CLONE_EXPIRED(c, &now, &c->tsp)) + return (0); + + pruned = 0; + + /* + * Visit each object in reverse order. If the object is still being + * referenced by a valid open(), skip it. Look for expired objects + * and either revoke its clone invocation status or mercilessly + * throw it away. + */ + TAILQ_FOREACH_REVERSE_SAFE(ce, &c->head, link_head, link, tce) { + if (!(ce->flags & SND_CLONE_BUSY) && + (!(ce->flags & SND_CLONE_INVOKE) || + SND_CLONE_EXPIRED(c, &now, &ce->tsp))) { + if ((c->flags & SND_CLONE_GC_REVOKE) || + ce->devt->si_threadcount != 0) { + ce->flags &= ~SND_CLONE_INVOKE; + ce->pid = -1; + } else { + TAILQ_REMOVE(&c->head, ce, link); + destroy_dev(ce->devt); + free(ce, M_DEVBUF); + c->size--; + } + pruned++; + } + } + + /* return total pruned objects */ + return (pruned); +} + +void +snd_clone_destroy(struct snd_clone *c) +{ + struct snd_clone_entry *ce, *tmp; + + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + ce = TAILQ_FIRST(&c->head); + while (ce != NULL) { + tmp = TAILQ_NEXT(ce, link); + if (ce->devt != NULL) + destroy_dev(ce->devt); + free(ce, M_DEVBUF); + ce = tmp; + } + + free(c, M_DEVBUF); +} + +/* + * snd_clone_acquire() : The vital part of concurrency management. Must be + * called somewhere at the beginning of open() handler. ENODEV is not really + * fatal since it just tell the caller that this is not cloned stuff. + * EBUSY is *real*, don't forget that! + */ +int +snd_clone_acquire(struct cdev *dev) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + ce->flags &= ~SND_CLONE_INVOKE; + + if (ce->flags & SND_CLONE_BUSY) + return (EBUSY); + + ce->flags |= SND_CLONE_BUSY; + + return (0); +} + +/* + * snd_clone_release() : Release busy status. Must be called somewhere at + * the end of close() handler, or somewhere after fail open(). + */ +int +snd_clone_release(struct cdev *dev) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + ce->flags &= ~SND_CLONE_INVOKE; + + if (!(ce->flags & SND_CLONE_BUSY)) + return (EBADF); + + ce->flags &= ~SND_CLONE_BUSY; + ce->pid = -1; + + return (0); +} + +/* + * snd_clone_ref/unref() : Garbage collector reference counter. To make + * garbage collector run automatically, the sequence must be something like + * this (both in open() and close() handlers): + * + * open() - 1) snd_clone_acquire() + * 2) .... check check ... if failed, snd_clone_release() + * 3) Success. Call snd_clone_ref() + * + * close() - 1) .... check check check .... + * 2) Success. snd_clone_release() + * 3) snd_clone_unref() . Garbage collector will run at this point + * if this is the last referenced object. + */ +int +snd_clone_ref(struct cdev *dev) +{ + struct snd_clone_entry *ce; + struct snd_clone *c; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0); + + c = ce->parent; + SND_CLONE_ASSERT(c != NULL, ("NULL parent")); + SND_CLONE_ASSERT(c->refcount >= 0, ("refcount < 0")); + + return (++c->refcount); +} + +int +snd_clone_unref(struct cdev *dev) +{ + struct snd_clone_entry *ce; + struct snd_clone *c; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0); + + c = ce->parent; + SND_CLONE_ASSERT(c != NULL, ("NULL parent")); + SND_CLONE_ASSERT(c->refcount > 0, ("refcount <= 0")); + + c->refcount--; + + /* + * Run automatic garbage collector, if needed. + */ + if ((c->flags & SND_CLONE_GC_UNREF) && + (!(c->flags & SND_CLONE_GC_LASTREF) || + (c->refcount == 0 && (c->flags & SND_CLONE_GC_LASTREF)))) + (void)snd_clone_gc(c); + + return (c->refcount); +} + +void +snd_clone_register(struct snd_clone_entry *ce, struct cdev *dev) +{ + SND_CLONE_ASSERT(ce != NULL, ("NULL snd_clone_entry")); + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + SND_CLONE_ASSERT(dev->si_drv2 == NULL, ("dev->si_drv2 not NULL")); + SND_CLONE_ASSERT((ce->flags & SND_CLONE_ALLOC) == SND_CLONE_ALLOC, + ("invalid clone alloc flags=0x%08x", ce->flags)); + SND_CLONE_ASSERT(ce->devt == NULL, ("ce->devt not NULL")); + SND_CLONE_ASSERT(ce->unit == dev2unit(dev), + ("invalid unit ce->unit=0x%08x dev2unit=0x%08x", + ce->unit, dev2unit(dev))); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + dev->si_drv2 = ce; + ce->devt = dev; + ce->flags &= ~SND_CLONE_ALLOC; + ce->flags |= SND_CLONE_INVOKE; +} + +struct snd_clone_entry * +snd_clone_alloc(struct snd_clone *c, struct cdev **dev, int *unit, int tmask) +{ + struct snd_clone_entry *ce, *after, *bce, *cce, *nce, *tce; + struct timespec now; + int cunit, allocunit; + pid_t curpid; + + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + SND_CLONE_ASSERT(dev != NULL, ("NULL dev pointer")); + SND_CLONE_ASSERT((c->typemask & tmask) == tmask, + ("invalid tmask: typemask=0x%08x tmask=0x%08x", + c->typemask, tmask)); + SND_CLONE_ASSERT(unit != NULL, ("NULL unit pointer")); + SND_CLONE_ASSERT(*unit == -1 || !(*unit & (c->typemask | tmask)), + ("typemask collision: typemask=0x%08x tmask=0x%08x *unit=%d", + c->typemask, tmask, *unit)); + + if (!(c->flags & SND_CLONE_ENABLE) || + (*unit != -1 && *unit > c->maxunit)) + return (NULL); + + ce = NULL; + after = NULL; + bce = NULL; /* "b"usy candidate */ + cce = NULL; /* "c"urthread/proc candidate */ + nce = NULL; /* "n"ull, totally unbusy candidate */ + tce = NULL; /* Last "t"ry candidate */ + cunit = 0; + allocunit = (*unit == -1) ? 0 : *unit; + curpid = curthread->td_proc->p_pid; + + snd_timestamp(&now); + + TAILQ_FOREACH(ce, &c->head, link) { + /* + * Sort incrementally according to device type. + */ + if (tmask > (ce->unit & c->typemask)) { + if (cunit == 0) + after = ce; + continue; + } else if (tmask < (ce->unit & c->typemask)) + break; + + /* + * Shoot.. this is where the grumpiness begin. Just + * return immediately. + */ + if (*unit != -1 && *unit == (ce->unit & ~tmask)) + goto snd_clone_alloc_out; + + cunit++; + /* + * Simmilar device type. Sort incrementally according + * to allocation unit. While here, look for free slot + * and possible collision for new / future allocation. + */ + if (*unit == -1 && (ce->unit & ~tmask) == allocunit) + allocunit++; + if ((ce->unit & ~tmask) < allocunit) + after = ce; + /* + * Clone logic: + * 1. Look for non busy, but keep track of the best + * possible busy cdev. + * 2. Look for the best (oldest referenced) entry that is + * in a same process / thread. + * 3. Look for the best (oldest referenced), absolute free + * entry. + * 4. Lastly, look for the best (oldest referenced) + * any entries that doesn't fit with anything above. + */ + if (ce->flags & SND_CLONE_BUSY) { + if (ce->devt != NULL && (bce == NULL || + timespeccmp(&ce->tsp, &bce->tsp, <))) + bce = ce; + continue; + } + if (ce->pid == curpid && + (cce == NULL || timespeccmp(&ce->tsp, &cce->tsp, <))) + cce = ce; + else if (!(ce->flags & SND_CLONE_INVOKE) && + (nce == NULL || timespeccmp(&ce->tsp, &nce->tsp, <))) + nce = ce; + else if (tce == NULL || timespeccmp(&ce->tsp, &tce->tsp, <)) + tce = ce; + } + if (*unit != -1) + goto snd_clone_alloc_new; + else if (cce != NULL) { + /* Same proc entry found, go for it */ + ce = cce; + goto snd_clone_alloc_out; + } else if (nce != NULL) { + /* + * Next, try absolute free entry. If the calculated + * allocunit is smaller, create new entry instead. + */ + if (allocunit < (nce->unit & ~tmask)) + goto snd_clone_alloc_new; + ce = nce; + goto snd_clone_alloc_out; + } else if (allocunit > c->maxunit) { + /* + * Maximum allowable unit reached. Try returning any + * available cdev and hope for the best. If the lookup is + * done for things like stat(), mtime() etc. , things should + * be ok. Otherwise, open() handler should do further checks + * and decide whether to return correct error code or not. + */ + if (tce != NULL) { + ce = tce; + goto snd_clone_alloc_out; + } else if (bce != NULL) { + ce = bce; + goto snd_clone_alloc_out; + } + return (NULL); + } + +snd_clone_alloc_new: + /* + * No free entries found, and we still haven't reached maximum + * allowable units. Allocate, setup a minimal unique entry with busy + * status so nobody will monkey on this new entry. Unit magic is set + * right here to avoid collision with other contesting handler. + * The caller must be carefull here to maintain its own + * synchronization, as long as it will not conflict with malloc(9) + * operations. + * + * That said, go figure. + */ + ce = malloc(sizeof(*ce), M_DEVBUF, + ((c->flags & SND_CLONE_WAITOK) ? M_WAITOK : M_NOWAIT) | M_ZERO); + if (ce == NULL) { + if (*unit != -1) + return (NULL); + /* + * We're being dense, ignorance is bliss, + * Super Regulatory Measure (TM).. TRY AGAIN! + */ + if (nce != NULL) { + ce = nce; + goto snd_clone_alloc_out; + } else if (tce != NULL) { + ce = tce; + goto snd_clone_alloc_out; + } else if (bce != NULL) { + ce = bce; + goto snd_clone_alloc_out; + } + return (NULL); + } + /* Setup new entry */ + ce->parent = c; + ce->unit = tmask | allocunit; + ce->pid = curpid; + ce->tsp = now; + ce->flags |= SND_CLONE_ALLOC; + if (after != NULL) { + TAILQ_INSERT_AFTER(&c->head, after, ce, link); + } else { + TAILQ_INSERT_HEAD(&c->head, ce, link); + } + c->size++; + c->tsp = now; + /* + * Save new allocation unit for caller which will be used + * by make_dev(). + */ + *unit = allocunit; + + return (ce); + +snd_clone_alloc_out: + /* + * Set, mark, timestamp the entry if this is a truly free entry. + * Leave busy entry alone. + */ + if (!(ce->flags & SND_CLONE_BUSY)) { + ce->pid = curpid; + ce->tsp = now; + ce->flags |= SND_CLONE_INVOKE; + } + c->tsp = now; + *dev = ce->devt; + + return (NULL); +} --- sys/dev/sound/clone.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/clone.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,132 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/clone.h,v 1.2 2007/06/14 11:10:21 ariff Exp $ + */ + +#ifndef _SND_CLONE_H_ +#define _SND_CLONE_H_ + +struct snd_clone_entry; +struct snd_clone; + +/* + * 750 milisecond default deadline. Short enough to not cause excessive + * garbage collection, long enough to indicate stalled VFS. + */ +#define SND_CLONE_DEADLINE_DEFAULT 750 + +/* + * Fit within 24bit MAXMINOR. + */ +#define SND_CLONE_MAXUNIT 0xffffff + +/* + * Creation flags, mostly related to the behaviour of garbage collector. + * + * SND_CLONE_ENABLE - Enable clone allocation. + * SND_CLONE_GC_ENABLE - Enable garbage collector operation, automatically + * or if explicitly called upon. + * SND_CLONE_GC_UNREF - Garbage collect during unref operation. + * SND_CLONE_GC_LASTREF - Garbage collect during last reference + * (refcount = 0) + * SND_CLONE_GC_EXPIRED - Don't garbage collect unless the global clone + * handler has been expired. + * SND_CLONE_GC_REVOKE - Revoke clone invocation status which has been + * expired instead of removing and freeing it. + * SND_CLONE_WAITOK - malloc() is allowed to sleep while allocating + * clone entry. + */ +#define SND_CLONE_ENABLE 0x00000001 +#define SND_CLONE_GC_ENABLE 0x00000002 +#define SND_CLONE_GC_UNREF 0x00000004 +#define SND_CLONE_GC_LASTREF 0x00000008 +#define SND_CLONE_GC_EXPIRED 0x00000010 +#define SND_CLONE_GC_REVOKE 0x00000020 +#define SND_CLONE_WAITOK 0x80000000 + +#define SND_CLONE_GC_MASK (SND_CLONE_GC_ENABLE | \ + SND_CLONE_GC_UNREF | \ + SND_CLONE_GC_LASTREF | \ + SND_CLONE_GC_EXPIRED | \ + SND_CLONE_GC_REVOKE) + +#define SND_CLONE_MASK (SND_CLONE_ENABLE | SND_CLONE_GC_MASK | \ + SND_CLONE_WAITOK) + +/* + * Runtime clone device flags + * + * These are mostly private to the clone manager operation: + * + * SND_CLONE_NEW - New clone allocation in progress. + * SND_CLONE_INVOKE - Cloning being invoked, waiting for next VFS operation. + * SND_CLONE_BUSY - In progress, being referenced by living thread/proc. + */ +#define SND_CLONE_NEW 0x00000001 +#define SND_CLONE_INVOKE 0x00000002 +#define SND_CLONE_BUSY 0x00000004 + +/* + * Nothing important, just for convenience. + */ +#define SND_CLONE_ALLOC (SND_CLONE_NEW | SND_CLONE_INVOKE | \ + SND_CLONE_BUSY) + +#define SND_CLONE_DEVMASK SND_CLONE_ALLOC + + +void snd_timestamp(struct timespec *); + +struct snd_clone *snd_clone_create(int, int, int, uint32_t); +int snd_clone_busy(struct snd_clone *); +int snd_clone_enable(struct snd_clone *); +int snd_clone_disable(struct snd_clone *); +int snd_clone_getsize(struct snd_clone *); +int snd_clone_getmaxunit(struct snd_clone *); +int snd_clone_setmaxunit(struct snd_clone *, int); +int snd_clone_getdeadline(struct snd_clone *); +int snd_clone_setdeadline(struct snd_clone *, int); +int snd_clone_gettime(struct snd_clone *, struct timespec *); +uint32_t snd_clone_getflags(struct snd_clone *); +uint32_t snd_clone_setflags(struct snd_clone *, uint32_t); +int snd_clone_getdevtime(struct cdev *, struct timespec *); +uint32_t snd_clone_getdevflags(struct cdev *); +uint32_t snd_clone_setdevflags(struct cdev *, uint32_t); +int snd_clone_gc(struct snd_clone *); +void snd_clone_destroy(struct snd_clone *); +int snd_clone_acquire(struct cdev *); +int snd_clone_release(struct cdev *); +int snd_clone_ref(struct cdev *); +int snd_clone_unref(struct cdev *); +void snd_clone_register(struct snd_clone_entry *, struct cdev *); +struct snd_clone_entry *snd_clone_alloc(struct snd_clone *, struct cdev **, + int *, int); + +#define snd_clone_enabled(x) ((x) != NULL && \ + (snd_clone_getflags(x) & SND_CLONE_ENABLE)) +#define snd_clone_disabled(x) (!snd_clone_enabled(x)) + +#endif /* !_SND_CLONE_H */ --- sys/dev/sound/driver.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/driver.c Thu Jul 12 12:04:19 2007 @@ -18,12 +18,12 @@ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/driver.c,v 1.12.2.1 2005/01/30 01:00:03 imp Exp $ + * $FreeBSD: src/sys/dev/sound/driver.c,v 1.21 2007/01/31 08:53:45 joel Exp $ */ #include @@ -54,27 +54,31 @@ MODULE_DEPEND(snd_driver, snd_ad1816, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_als4000, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_atiixp, 1, 1, 1); /* MODULE_DEPEND(snd_driver, snd_aureal, 1, 1, 1); */ MODULE_DEPEND(snd_driver, snd_cmi, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_cs4281, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_csa, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_csapcm, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_ds1, 1, 1, 1); -MODULE_DEPEND(snd_driver, snd_emu10k1, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_emu10kx, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_envy24, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_envy24ht, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_es137x, 1, 1, 1); -MODULE_DEPEND(snd_driver, snd_es1888, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_ess, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_fm801, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_gusc, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_hda, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_ich, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_maestro, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_maestro3, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_mss, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_neomagic, 1, 1, 1); -MODULE_DEPEND(snd_driver, snd_sb8, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_sb16, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_sb8, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_sbc, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_solo, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_spicds, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_t4dwave, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_via8233, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_via82c686, 1, 1, 1); --- sys/dev/sound/isa/ad1816.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/ad1816.c Thu Jul 12 12:04:19 2007 @@ -1,7 +1,7 @@ /*- * Copyright (c) 1999 Cameron Grant - * Copyright Luigi Rizzo, 1997,1998 - * Copyright by Hannu Savolainen 1994, 1995 + * Copyright (c) 1997,1998 Luigi Rizzo + * Copyright (c) 1994,1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/ad1816.c,v 1.33.2.3 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/ad1816.c,v 1.45 2007/06/17 06:10:40 ariff Exp $"); struct ad1816_info; @@ -138,12 +138,16 @@ } /* check for capture interupt */ if (sndbuf_runsz(ad1816->rch.buffer) && (c & AD1816_INTRCI)) { + ad1816_unlock(ad1816); chn_intr(ad1816->rch.channel); + ad1816_lock(ad1816); served |= AD1816_INTRCI; /* cp served */ } /* check for playback interupt */ if (sndbuf_runsz(ad1816->pch.buffer) && (c & AD1816_INTRPI)) { + ad1816_unlock(ad1816); chn_intr(ad1816->pch.channel); + ad1816_lock(ad1816); served |= AD1816_INTRPI; /* pb served */ } if (served == 0) { @@ -314,7 +318,7 @@ ch->parent = ad1816; ch->channel = c; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, ad1816->parent_dmat, ad1816->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, ad1816->parent_dmat, 0, ad1816->bufsize) != 0) return NULL; return ch; } @@ -407,7 +411,7 @@ struct ad1816_info *ad1816 = ch->parent; int wr, reg; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); @@ -576,11 +580,14 @@ case 0x80719304: /* ADS7180 */ s = "AD1816"; break; + case 0x50719304: /* ADS7150 */ + s = "AD1815"; + break; } if (s) { device_set_desc(dev, s); - return 0; + return BUS_PROBE_DEFAULT; } return ENXIO; } @@ -591,10 +598,9 @@ struct ad1816_info *ad1816; char status[SND_STATUSLEN], status2[SND_STATUSLEN]; - ad1816 = (struct ad1816_info *)malloc(sizeof *ad1816, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!ad1816) return ENXIO; - - ad1816->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + ad1816 = malloc(sizeof(*ad1816), M_DEVBUF, M_WAITOK | M_ZERO); + ad1816->lock = snd_mtxcreate(device_get_nameunit(dev), + "snd_ad1816 softc"); ad1816->io_rid = 2; ad1816->irq_rid = 0; ad1816->drq1_rid = 0; @@ -606,7 +612,8 @@ if (mixer_init(dev, &ad1816mixer_class, ad1816)) goto no; snd_setup_intr(dev, ad1816->irq, 0, ad1816_intr, ad1816, &ad1816->ih); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/isa/ad1816.h.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/ad1816.h Thu Jul 12 12:04:19 2007 @@ -1,10 +1,34 @@ /*- - * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * Copyright (c) 1997 Luigi Rizzo + * All rights reserved. * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/isa/ad1816.h,v 1.4 2007/02/02 13:44:09 joel Exp $ + */ + +/* * This file contains information and macro definitions for * the ad1816 chip - * - * $FreeBSD: src/sys/dev/sound/isa/ad1816.h,v 1.1.12.1 2005/01/30 01:00:03 imp Exp $ */ /* AD1816 register macros */ --- sys/dev/sound/isa/es1888.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/es1888.c Thu Jan 1 07:30:00 1970 @@ -1,177 +0,0 @@ -/*- - * Copyright (c) 1999 Doug Rabson - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include -#include - -#include - -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/es1888.c,v 1.11.2.1 2005/01/30 01:00:03 imp Exp $"); - -#ifdef __alpha__ -static int -es1888_dspready(u_int32_t port) -{ - return ((inb(port + SBDSP_STATUS) & 0x80) == 0); -} - -static int -es1888_dspwr(u_int32_t port, u_char val) -{ - int i; - - for (i = 0; i < 1000; i++) { - if (es1888_dspready(port)) { - outb(port + SBDSP_CMD, val); - return 0; - } - if (i > 10) DELAY((i > 100)? 1000 : 10); - } - return ENXIO; -} - -static u_int -es1888_get_byte(u_int32_t port) -{ - int i; - - for (i = 1000; i > 0; i--) { - if (inb(port + DSP_DATA_AVAIL) & 0x80) - return inb(port + DSP_READ); - else - DELAY(20); - } - return 0xffff; -} - -static int -es1888_reset(u_int32_t port) -{ - outb(port + SBDSP_RST, 3); - DELAY(100); - outb(port + SBDSP_RST, 0); - if (es1888_get_byte(port) != 0xAA) { - return ENXIO; /* Sorry */ - } - return 0; -} - -static void -es1888_configuration_mode(void) -{ - /* - * Emit the Read-Sequence-Key to enter configuration - * mode. Note this only works after a reset (or after bit 2 of - * mixer register 0x40 is set). - * - * 3 reads from 0x229 in a row guarantees reset of key - * sequence to beginning. - */ - inb(0x229); - inb(0x229); - inb(0x229); - - inb(0x22b); /* state 1 */ - inb(0x229); /* state 2 */ - inb(0x22b); /* state 3 */ - inb(0x229); /* state 4 */ - inb(0x229); /* state 5 */ - inb(0x22b); /* state 6 */ - inb(0x229); /* state 7 */ -} - -static void -es1888_set_port(u_int32_t port) -{ - es1888_configuration_mode(); - inb(port); -} -#endif - -static void -es1888_identify(driver_t *driver, device_t parent) -{ -/* - * Only use this on alpha since PNPBIOS is a better solution on x86. - */ -#ifdef __alpha__ - u_int32_t lo, hi; - device_t dev; - - es1888_set_port(0x220); - if (es1888_reset(0x220)) - return; - - /* - * Check identification bytes for es1888. - */ - if (es1888_dspwr(0x220, 0xe7)) - return; - hi = es1888_get_byte(0x220); - lo = es1888_get_byte(0x220); - if (hi != 0x68 || (lo & 0xf0) != 0x80) - return; - - /* - * Program irq and drq. - */ - if (es1888_dspwr(0x220, 0xc6) /* enter extended mode */ - || es1888_dspwr(0x220, 0xb1) /* write register b1 */ - || es1888_dspwr(0x220, 0x14) /* enable irq 5 */ - || es1888_dspwr(0x220, 0xb2) /* write register b1 */ - || es1888_dspwr(0x220, 0x18)) /* enable drq 1 */ - return; - - /* - * Create the device and program its resources. - */ - dev = BUS_ADD_CHILD(parent, ISA_ORDER_PNP, NULL, -1); - bus_set_resource(dev, SYS_RES_IOPORT, 0, 0x220, 0x10); - bus_set_resource(dev, SYS_RES_IRQ, 0, 5, 1); - bus_set_resource(dev, SYS_RES_DRQ, 0, 1, 1); - isa_set_vendorid(dev, PNP_EISAID("ESS1888")); - isa_set_logicalid(dev, PNP_EISAID("ESS1888")); -#endif -} - -static device_method_t es1888_methods[] = { - /* Device interface */ - DEVMETHOD(device_identify, es1888_identify), - - { 0, 0 } -}; - -static driver_t es1888_driver = { - "pcm", - es1888_methods, - 1, /* no softc */ -}; - -DRIVER_MODULE(snd_es1888, isa, es1888_driver, pcm_devclass, 0, 0); -MODULE_DEPEND(snd_es1888, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); -MODULE_VERSION(snd_es1888, 1); - - --- sys/dev/sound/isa/ess.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/ess.c Thu Jul 12 12:04:19 2007 @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant - * Copyright 1997,1998 Luigi Rizzo. + * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright @@ -38,7 +38,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/ess.c,v 1.31.2.3 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/ess.c,v 1.42 2007/06/17 06:10:40 ariff Exp $"); #define ESS_BUFFSIZE (4096) #define ABS(x) (((x) < 0)? -(x) : (x)) @@ -61,7 +61,7 @@ 0 }; -static struct pcmchan_caps ess_playcaps = {5000, 49000, ess_pfmt, 0}; +static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_pfmt, 0}; static u_int32_t ess_rfmt[] = { AFMT_U8, @@ -75,7 +75,7 @@ 0 }; -static struct pcmchan_caps ess_reccaps = {5000, 49000, ess_rfmt, 0}; +static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_rfmt, 0}; struct ess_info; @@ -97,7 +97,8 @@ bus_dma_tag_t parent_dmat; unsigned int bufsize; - int type, duplex:1, newspeed:1; + int type; + unsigned int duplex:1, newspeed:1; u_long bd_flags; /* board-specific flags */ struct ess_chinfo pch, rch; }; @@ -361,8 +362,11 @@ rirq = (src & sc->rch.hwch)? 1 : 0; if (pirq) { - if (sc->pch.run) + if (sc->pch.run) { + ess_unlock(sc); chn_intr(sc->pch.channel); + ess_lock(sc); + } if (sc->pch.stopping) { sc->pch.run = 0; sndbuf_dma(sc->pch.buffer, PCMTRIG_STOP); @@ -375,8 +379,11 @@ } if (rirq) { - if (sc->rch.run) + if (sc->rch.run) { + ess_unlock(sc); chn_intr(sc->rch.channel); + ess_lock(sc); + } if (sc->rch.stopping) { sc->rch.run = 0; sndbuf_dma(sc->rch.buffer, PCMTRIG_STOP); @@ -557,7 +564,7 @@ ch->parent = sc; ch->channel = c; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsize) != 0) return NULL; ch->dir = dir; ch->hwch = 1; @@ -604,7 +611,7 @@ { struct ess_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; switch (go) { @@ -802,10 +809,7 @@ char status[SND_STATUSLEN], buf[64]; int ver; - sc = (struct ess_info *)malloc(sizeof *sc, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!sc) - return ENXIO; - + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->parent_dev = device_get_parent(dev); sc->bufsize = pcm_getbuffersize(dev, 4096, ESS_BUFFSIZE, 65536); if (ess_alloc_resources(sc, dev)) @@ -845,7 +849,8 @@ if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/isa/gusc.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/gusc.c Thu Jul 12 12:04:19 2007 @@ -41,11 +41,8 @@ #include #include -#ifdef __alpha__ /* XXX workaround a stupid warning */ -#include -#endif -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/gusc.c,v 1.14.2.2 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/gusc.c,v 1.19 2007/02/23 19:40:13 ariff Exp $"); #define LOGICALID_NOPNP 0 #define LOGICALID_PCM 0x0000561e @@ -319,7 +316,7 @@ } if (scp->irq != NULL) - bus_setup_intr(dev, scp->irq, INTR_TYPE_AV, gusc_intr, scp, &ih); + snd_setup_intr(dev, scp->irq, 0, gusc_intr, scp, &ih); bus_generic_attach(dev); return (0); @@ -421,12 +418,21 @@ } static int -gusc_setup_intr(device_t dev, device_t child, struct resource *irq, - int flags, driver_intr_t *intr, void *arg, void **cookiep) +gusc_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif + driver_intr_t *intr, void *arg, void **cookiep) { sc_p scp = (sc_p)device_get_softc(dev); devclass_t devclass; +#if __FreeBSD_version >= 700031 + if (filter != NULL) { + printf("gusc.c: we cannot use a filter here\n"); + return (EINVAL); + } +#endif devclass = device_get_devclass(child); if (strcmp(devclass_get_name(devclass), "midi") == 0) { scp->midi_intr.intr = intr; @@ -437,8 +443,11 @@ scp->pcm_intr.arg = arg; return 0; } - return bus_generic_setup_intr(dev, child, irq, flags, intr, - arg, cookiep); + return bus_generic_setup_intr(dev, child, irq, flags, +#if __FreeBSD_version >= 700031 + filter, +#endif + intr, arg, cookiep); } static device_t --- sys/dev/sound/isa/mss.c.orig Mon Feb 28 07:32:21 2005 +++ sys/dev/sound/isa/mss.c Thu Jul 12 12:04:19 2007 @@ -1,8 +1,8 @@ /*- * Copyright (c) 2001 George Reid * Copyright (c) 1999 Cameron Grant - * Copyright Luigi Rizzo, 1997,1998 - * Copyright by Hannu Savolainen 1994, 1995 + * Copyright (c) 1997,1998 Luigi Rizzo + * Copyright (c) 1994,1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/mss.c,v 1.90.2.4 2005/02/27 23:32:21 mdodd Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/mss.c,v 1.112 2007/06/17 06:10:40 ariff Exp $"); /* board-specific include files */ #include @@ -92,7 +92,9 @@ /* prototypes for local functions */ static int mss_detect(device_t dev, struct mss_info *mss); +#ifndef PC98 static int opti_detect(device_t dev, struct mss_info *mss); +#endif static char *ymf_test(device_t dev, struct mss_info *mss); static void ad_unmute(struct mss_info *mss); @@ -111,7 +113,9 @@ /* OPTi-specific functions */ static void opti_write(struct mss_info *mss, u_char reg, u_char data); +#ifndef PC98 static u_char opti_read(struct mss_info *mss, u_char reg); +#endif static int opti_init(device_t dev, struct mss_info *mss); /* io primitives */ @@ -795,11 +799,15 @@ c &= ~served; if (sndbuf_runsz(mss->pch.buffer) && (c & 0x10)) { served |= 0x10; + mss_unlock(mss); chn_intr(mss->pch.channel); + mss_lock(mss); } if (sndbuf_runsz(mss->rch.buffer) && (c & 0x20)) { served |= 0x20; + mss_unlock(mss); chn_intr(mss->rch.channel); + mss_lock(mss); } /* now ack the interrupt */ if (FULL_DUPLEX(mss)) ad_write(mss, 24, ~c); /* ack selectively */ @@ -970,6 +978,7 @@ abs(speed-speeds[i]) < abs(speed-speeds[sel])) sel = i; speed = speeds[sel]; ad_write(mss, 8, (ad_read(mss, 8) & 0xf0) | sel); + ad_wait_init(mss, 10000); } ad_leave_MCE(mss); @@ -1009,7 +1018,11 @@ arg <<= 4; ad_enter_MCE(mss); ad_write(mss, 8, (ad_read(mss, 8) & 0x0f) | arg); - if (FULL_DUPLEX(mss)) ad_write(mss, 28, arg); /* capture mode */ + ad_wait_init(mss, 10000); + if (ad_read(mss, 12) & 0x40) { /* mode2? */ + ad_write(mss, 28, arg); /* capture mode */ + ad_wait_init(mss, 10000); + } ad_leave_MCE(mss); return format; } @@ -1111,8 +1124,16 @@ return; } - if (sndbuf_runsz(mss->rch.buffer) && (mc11 & 8)) chn_intr(mss->rch.channel); - if (sndbuf_runsz(mss->pch.buffer) && (mc11 & 4)) chn_intr(mss->pch.channel); + if (sndbuf_runsz(mss->rch.buffer) && (mc11 & 8)) { + mss_unlock(mss); + chn_intr(mss->rch.channel); + mss_lock(mss); + } + if (sndbuf_runsz(mss->pch.buffer) && (mc11 & 4)) { + mss_unlock(mss); + chn_intr(mss->pch.channel); + mss_lock(mss); + } opti_wr(mss, 11, ~mc11); /* ack */ if (--loops) goto again; mss_unlock(mss); @@ -1131,7 +1152,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, mss->parent_dmat, mss->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, mss->parent_dmat, 0, mss->bufsize) != 0) return NULL; sndbuf_dmasetup(ch->buffer, (dir == PCMDIR_PLAY)? mss->drq1 : mss->drq2); return ch; @@ -1180,7 +1201,7 @@ struct mss_chinfo *ch = data; struct mss_info *mss = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); @@ -1299,7 +1320,7 @@ goto mss_probe_end; } tmp &= 0x3f; - if (!(tmp == 0x04 || tmp == 0x0f || tmp == 0x00)) { + if (!(tmp == 0x04 || tmp == 0x0f || tmp == 0x00 || tmp == 0x05)) { BVDDB(printf("No MSS signature detected on port 0x%lx (0x%x)\n", rman_get_start(mss->io_base), tmpx)); goto no; @@ -1355,6 +1376,7 @@ name = "AD1848"; mss->bd_id = MD_AD1848; /* AD1848 or CS4248 */ +#ifndef PC98 if (opti_detect(dev, mss)) { switch (mss->bd_id) { case MD_OPTI924: @@ -1367,6 +1389,7 @@ printf("Found OPTi device %s\n", name); if (opti_init(dev, mss) == 0) goto gotit; } +#endif /* * Check that the I/O address is in use. @@ -1573,6 +1596,7 @@ return ENXIO; } +#ifndef PC98 static int opti_detect(device_t dev, struct mss_info *mss) { @@ -1618,6 +1642,7 @@ } return 0; } +#endif static char * ymf_test(device_t dev, struct mss_info *mss) @@ -1671,7 +1696,7 @@ int pdma, rdma, flags = device_get_flags(dev); char status[SND_STATUSLEN], status2[SND_STATUSLEN]; - mss->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + mss->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_mss softc"); mss->bufsize = pcm_getbuffersize(dev, 4096, MSS_DEFAULT_BUFSZ, 65536); if (!mss_alloc_resources(mss, dev)) goto no; mss_init(mss, dev); @@ -1719,7 +1744,8 @@ } if (pdma == rdma) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -1883,7 +1909,6 @@ }; DRIVER_MODULE(snd_mss, isa, mss_driver, pcm_devclass, 0, 0); -DRIVER_MODULE(snd_mss, acpi, mss_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_mss, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_mss, 1); @@ -1954,10 +1979,7 @@ { struct mss_info *mss; - mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!mss) - return ENXIO; - + mss = malloc(sizeof(*mss), M_DEVBUF, M_WAITOK | M_ZERO); mss->io_rid = 0; mss->conf_rid = -1; mss->irq_rid = 0; @@ -2003,8 +2025,10 @@ mss->conf_rid = 3; mss->bd_id = MD_OPTI924; mss->bd_flags |= BD_F_924PNP; - if(opti_init(dev, mss) != 0) + if(opti_init(dev, mss) != 0) { + free(mss, M_DEVBUF); return ENXIO; + } break; case 0x1022b839: /* NMX2210 */ @@ -2013,8 +2037,10 @@ case 0x01005407: /* AZT0001 */ /* put into MSS mode first (snatched from NetBSD) */ - if (azt2320_mss_mode(mss, dev) == -1) + if (azt2320_mss_mode(mss, dev) == -1) { + free(mss, M_DEVBUF); return ENXIO; + } mss->bd_flags |= BD_F_MSS_OFFSET; mss->io_rid = 2; @@ -2153,6 +2179,7 @@ } } +#ifndef PC98 u_char opti_read(struct mss_info *mss, u_char reg) { @@ -2176,6 +2203,7 @@ } return -1; } +#endif static device_method_t pnpmss_methods[] = { /* Device interface */ --- sys/dev/sound/isa/mss.h.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/mss.h Thu Jul 12 12:04:19 2007 @@ -1,15 +1,6 @@ /*- - * file: mss.h - * - * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) - * - * This file contains information and macro definitions for - * AD1848-compatible devices, used in the MSS/WSS compatible boards. - * - */ - -/*- * Copyright (c) 1999 Doug Rabson + * Copyright (c) 1997 Luigi Rizzo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +24,12 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/isa/mss.h,v 1.11.8.1 2005/01/30 01:00:03 imp Exp $ + * $FreeBSD: src/sys/dev/sound/isa/mss.h,v 1.14 2007/02/02 13:44:09 joel Exp $ + */ + +/* + * This file contains information and macro definitions for + * AD1848-compatible devices, used in the MSS/WSS compatible boards. */ /* @@ -305,34 +301,6 @@ (SOUND_MASK_VOLUME | SOUND_MASK_SYNTH | SOUND_MASK_PCM | \ SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | \ SOUND_MASK_IGAIN | SOUND_MASK_LINE1 ) - -/*- - * Copyright (c) 1999 Doug Rabson - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * $FreeBSD: src/sys/dev/sound/isa/mss.h,v 1.11.8.1 2005/01/30 01:00:03 imp Exp $ - */ /* * Register definitions for the Yamaha OPL3-SA[23x]. --- sys/dev/sound/isa/sb.h.orig Thu May 13 19:32:54 2004 +++ sys/dev/sound/isa/sb.h Thu Jul 12 12:04:19 2007 @@ -1,6 +1,29 @@ -/* - * file: sbcard.h - * $FreeBSD: src/sys/dev/sound/isa/sb.h,v 1.15 2004/05/13 11:32:54 truckman Exp $ +/*- + * Copyright (c) 1997,1998 Luigi Rizzo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/isa/sb.h,v 1.16 2007/02/02 13:33:35 joel Exp $ */ #ifndef SB_H --- sys/dev/sound/isa/sb16.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/sb16.c Thu Jul 12 12:04:19 2007 @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant - * Copyright 1997,1998 Luigi Rizzo. + * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright @@ -38,7 +38,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sb16.c,v 1.87.2.3 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sb16.c,v 1.97 2007/06/17 06:10:40 ariff Exp $"); #define SB16_BUFFSIZE 4096 #define PLAIN_SB16(x) ((((x)->bd_flags) & (BD_F_SB16|BD_F_SB16X)) == BD_F_SB16) @@ -370,23 +370,32 @@ sb16mix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct sb_info *sb = mix_getdevinfo(m); - u_char recdev; + u_char recdev_l, recdev_r; - recdev = 0; - if (src & SOUND_MASK_MIC) - recdev |= 0x01; /* mono mic */ + recdev_l = 0; + recdev_r = 0; + if (src & SOUND_MASK_MIC) { + recdev_l |= 0x01; /* mono mic */ + recdev_r |= 0x01; + } - if (src & SOUND_MASK_CD) - recdev |= 0x06; /* l+r cd */ + if (src & SOUND_MASK_CD) { + recdev_l |= 0x04; /* l cd */ + recdev_r |= 0x02; /* r cd */ + } - if (src & SOUND_MASK_LINE) - recdev |= 0x18; /* l+r line */ + if (src & SOUND_MASK_LINE) { + recdev_l |= 0x10; /* l line */ + recdev_r |= 0x08; /* r line */ + } - if (src & SOUND_MASK_SYNTH) - recdev |= 0x60; /* l+r midi */ + if (src & SOUND_MASK_SYNTH) { + recdev_l |= 0x40; /* l midi */ + recdev_r |= 0x20; /* r midi */ + } - sb_setmixer(sb, SB16_IMASK_L, recdev); - sb_setmixer(sb, SB16_IMASK_R, recdev); + sb_setmixer(sb, SB16_IMASK_L, recdev_l); + sb_setmixer(sb, SB16_IMASK_R, recdev_r); /* Switch on/off FM tuner source */ if (src & SOUND_MASK_LINE1) @@ -494,7 +503,7 @@ sb_intr(void *arg) { struct sb_info *sb = (struct sb_info *)arg; - int reason = 3, c; + int reason, c; /* * The Vibra16X has separate flags for 8 and 16 bit transfers, but @@ -570,8 +579,9 @@ sb_reset_dsp(sb); if (sb->bd_flags & BD_F_SB16X) { + /* full-duplex doesn't work! */ pprio = sb->pch.run? 1 : 0; - sndbuf_dmasetup(sb->pch.buffer, pprio? sb->drq1 : NULL); + sndbuf_dmasetup(sb->pch.buffer, pprio? sb->drq1 : sb->drq2); sb->pch.dch = pprio? 1 : 0; sndbuf_dmasetup(sb->rch.buffer, pprio? sb->drq2 : sb->drq1); sb->rch.dch = pprio? 2 : 1; @@ -671,7 +681,7 @@ ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, sb->parent_dmat, sb->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, sb->parent_dmat, 0, sb->bufsize) != 0) return NULL; return ch; @@ -714,7 +724,7 @@ struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) @@ -803,10 +813,7 @@ uintptr_t ver; char status[SND_STATUSLEN], status2[SND_STATUSLEN]; - sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!sb) - return ENXIO; - + sb = malloc(sizeof(*sb), M_DEVBUF, M_WAITOK | M_ZERO); sb->parent_dev = device_get_parent(dev); BUS_READ_IVAR(sb->parent_dev, dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; @@ -831,7 +838,8 @@ sb->prio = 0; - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -848,7 +856,7 @@ else status2[0] = '\0'; - snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %ud %s", + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld drq %ld%s bufsz %u %s", rman_get_start(sb->io_base), rman_get_start(sb->irq), rman_get_start(sb->drq1), status2, sb->bufsize, PCM_KLDSTRING(snd_sb16)); --- sys/dev/sound/isa/sb8.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/sb8.c Thu Jul 12 12:04:19 2007 @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant - * Copyright 1997,1998 Luigi Rizzo. + * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright @@ -38,7 +38,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sb8.c,v 1.77.2.2 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sb8.c,v 1.86 2007/06/17 06:10:41 ariff Exp $"); #define SB_DEFAULT_BUFSZ 4096 @@ -475,11 +475,17 @@ struct sb_info *sb = (struct sb_info *)arg; sb_lock(sb); - if (sndbuf_runsz(sb->pch.buffer) > 0) + if (sndbuf_runsz(sb->pch.buffer) > 0) { + sb_unlock(sb); chn_intr(sb->pch.channel); + sb_lock(sb); + } - if (sndbuf_runsz(sb->rch.buffer) > 0) + if (sndbuf_runsz(sb->rch.buffer) > 0) { + sb_unlock(sb); chn_intr(sb->rch.channel); + sb_lock(sb); + } sb_rd(sb, DSP_DATA_AVAIL); /* int ack */ sb_unlock(sb); @@ -564,8 +570,16 @@ sb_lock(sb); if (sb->bd_flags & BD_F_HISPEED) sb_reset_dsp(sb); - else + else { +#if 0 + /* + * NOTE: DSP_CMD_DMAEXIT_8 does not work with old + * soundblaster. + */ sb_cmd(sb, DSP_CMD_DMAEXIT_8); +#endif + sb_reset_dsp(sb); + } if (play) sb_cmd(sb, DSP_CMD_SPKOFF); /* speaker off */ @@ -585,7 +599,7 @@ ch->channel = c; ch->dir = dir; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, sb->parent_dmat, sb->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, sb->parent_dmat, 0, sb->bufsize) != 0) return NULL; sndbuf_dmasetup(ch->buffer, sb->drq); return ch; @@ -623,7 +637,7 @@ { struct sb_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); @@ -700,10 +714,7 @@ char status[SND_STATUSLEN]; uintptr_t ver; - sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!sb) - return ENXIO; - + sb = malloc(sizeof(*sb), M_DEVBUF, M_WAITOK | M_ZERO); sb->parent_dev = device_get_parent(dev); BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; @@ -721,7 +732,8 @@ pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/isa/sbc.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/sbc.c Thu Jul 12 12:04:19 2007 @@ -30,7 +30,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sbc.c,v 1.42.2.2 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sbc.c,v 1.48 2007/03/15 16:41:25 ariff Exp $"); #define IO_MAX 3 #define IRQ_MAX 1 @@ -80,8 +80,12 @@ static int sbc_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r); static int sbc_setup_intr(device_t dev, device_t child, struct resource *irq, - int flags, driver_intr_t *intr, void *arg, - void **cookiep); + int flags, +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif + driver_intr_t *intr, + void *arg, void **cookiep); static int sbc_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie); @@ -116,7 +120,8 @@ static void sbc_lockinit(struct sbc_softc *scp) { - scp->lock = snd_mtxcreate(device_get_nameunit(scp->dev), "sound softc"); + scp->lock = snd_mtxcreate(device_get_nameunit(scp->dev), + "snd_sbc softc"); } static void @@ -259,6 +264,7 @@ {0x81167316, "ESS ES1681"}, /* ESS1681 */ {0x02017316, "ESS ES1688"}, /* ESS1688 */ + {0x68097316, "ESS ES1688"}, /* ESS1688 */ {0x68187316, "ESS ES1868"}, /* ESS1868 */ {0x03007316, "ESS ES1869"}, /* ESS1869 */ {0x69187316, "ESS ES1869"}, /* ESS1869 */ @@ -501,14 +507,23 @@ } static int -sbc_setup_intr(device_t dev, device_t child, struct resource *irq, - int flags, driver_intr_t *intr, void *arg, - void **cookiep) +sbc_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif + driver_intr_t *intr, + void *arg, void **cookiep) { struct sbc_softc *scp = device_get_softc(dev); struct sbc_ihl *ihl = NULL; int i, ret; +#if __FreeBSD_version >= 700031 + if (filter != NULL) { + printf("sbc.c: we cannot use a filter here\n"); + return (EINVAL); + } +#endif sbc_lock(scp); i = 0; while (i < IRQ_MAX) { --- sys/dev/sound/isa/sndbuf_dma.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/isa/sndbuf_dma.c Thu May 31 22:00:53 2007 @@ -28,7 +28,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sndbuf_dma.c,v 1.2.4.1 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sndbuf_dma.c,v 1.3 2005/01/06 01:43:17 imp Exp $"); int sndbuf_dmasetup(struct snd_dbuf *b, struct resource *drq) --- sys/dev/sound/lpmap.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/lpmap.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,307 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + * "Little-pmap": Lost Technology for RELENG_5/RELENG_6. + * + */ + +#ifndef _LPMAP_C_ +#define _LPMAP_C_ + +#if !(defined(__i386__) || defined(__amd64__)) +#error "Not applicable for non-i386/non-amd64" +#endif + +/* For cpu_feature, VM_MAXUSER_ADDRESS */ +#include +#include +#include + +#ifndef PG_PTE_PAT +#define PG_PTE_PAT 0x0080 +#endif + +#ifndef PG_PDE_PAT +#define PG_PDE_PAT 0x1000 +#endif + +#ifndef PAT_UNCACHEABLE +#define PAT_UNCACHEABLE 0x00 +#endif + +#ifndef PAT_WRITE_COMBINING +#define PAT_WRITE_COMBINING 0x01 +#endif + +#ifndef PAT_WRITE_THROUGH +#define PAT_WRITE_THROUGH 0x04 +#endif + +#ifndef PAT_WRITE_PROTECTED +#define PAT_WRITE_PROTECTED 0x05 +#endif + +#ifndef PAT_WRITE_BACK +#define PAT_WRITE_BACK 0x06 +#endif + +#ifndef PAT_UNCACHED +#define PAT_UNCACHED 0x07 +#endif + +#define lpmap_p(x, y...) /*printf("%s(): "x, __func__, y)*/ + +#undef pmap_change_attr +#define pmap_change_attr lpmap_change_attr + +#if defined(__i386__) +#define lpmap_pde(m, v) (&((m)->pm_pdir[(vm_offset_t)(v) >> PDRSHIFT])) +#elif defined(__amd64__) +static __inline vm_pindex_t +lpmap_pde_index(vm_offset_t va) +{ + return ((va >> PDRSHIFT) & ((1UL << NPDEPGSHIFT) - 1)); +} + +static __inline vm_pindex_t +lpmap_pdpe_index(vm_offset_t va) +{ + return ((va >> PDPSHIFT) & ((1UL << NPDPEPGSHIFT) - 1)); +} + +static __inline vm_pindex_t +lpmap_pml4e_index(vm_offset_t va) +{ + return ((va >> PML4SHIFT) & ((1UL << NPML4EPGSHIFT) - 1)); +} + +static __inline pd_entry_t * +lpmap_pdpe_to_pde(pdp_entry_t *pdpe, vm_offset_t va) +{ + pd_entry_t *pde; + + pde = (pd_entry_t *)PHYS_TO_DMAP(*pdpe & PG_FRAME); + return (&pde[lpmap_pde_index(va)]); +} + +static __inline pml4_entry_t * +lpmap_pml4e(pmap_t pmap, vm_offset_t va) +{ + if (pmap == NULL) + return (NULL); + return (&pmap->pm_pml4[lpmap_pml4e_index(va)]); +} + +static __inline pdp_entry_t * +lpmap_pml4e_to_pdpe(pml4_entry_t *pml4e, vm_offset_t va) +{ + pdp_entry_t *pdpe; + + pdpe = (pdp_entry_t *)PHYS_TO_DMAP(*pml4e & PG_FRAME); + return (&pdpe[lpmap_pdpe_index(va)]); +} + +static __inline pdp_entry_t * +lpmap_pdpe(pmap_t pmap, vm_offset_t va) +{ + pml4_entry_t *pml4e; + + pml4e = lpmap_pml4e(pmap, va); + if (pml4e == NULL || (*pml4e & PG_V) == 0) + return (NULL); + return (lpmap_pml4e_to_pdpe(pml4e, va)); +} + +static __inline pd_entry_t * +lpmap_pde(pmap_t pmap, vm_offset_t va) +{ + pdp_entry_t *pdpe; + + pdpe = lpmap_pdpe(pmap, va); + if (pdpe == NULL || (*pdpe & PG_V) == 0) + return (NULL); + return (lpmap_pdpe_to_pde(pdpe, va)); +} +#endif + +static __inline int +lpmap_validate_range(vm_offset_t base, vm_size_t size) +{ + vm_offset_t va; + pt_entry_t *pte; + pd_entry_t *pde; + + if (base <= VM_MAXUSER_ADDRESS) { + lpmap_p("base <= VM_MAXUSER_ADDRESS : base=0x%jx\n", + (uintmax_t)base); + return (EINVAL); + } + + va = base; + while (va < (base + size)) { + pde = lpmap_pde(kernel_pmap, va); + if (pde == NULL || *pde == 0) { + lpmap_p("Failed: %s : va=0x%jx\n", + (pde == NULL) ? "pde == NULL" : "*pde == 0", + (uintmax_t)va); + return (EINVAL); + } + if (*pde & PG_PS) { +#if defined(__amd64__) + if (size < NBPDR) { + lpmap_p("Failed: size < NBPDR : va=0x%jx\n", + (uintmax_t)va); + return (EINVAL); + } + va += NBPDR; +#else + lpmap_p("Failed: (*pde & PG_PS) != 0 : va=0x%jx\n", + (uintmax_t)va); + return (EINVAL); +#endif + } else { + pte = vtopte(va); + if (pte == NULL || *pte == 0) { + lpmap_p("Failed: %s : va=0x%jx\n", + (pte == NULL) ? "pte == NULL" : "*pte == 0", + (uintmax_t)va); + return (EINVAL); + } + va += PAGE_SIZE; + } + } + + return (0); +} + +extern int osreldate; + +static int +lpmap_change_attr(vm_offset_t va, vm_size_t size, int mode) +{ + vm_offset_t base, offset; + pt_entry_t *pte; +#if defined(__amd64__) + pd_entry_t *pde; +#endif + u_int opxe, npxe, ptefl, pdefl, attr; + int err, flushtlb; + + switch (mode) { + case PAT_UNCACHED: + case PAT_WRITE_COMBINING: + case PAT_WRITE_PROTECTED: + if (!(cpu_feature & CPUID_PAT)) + mode = PAT_UNCACHEABLE; + break; + default: + break; + } + + attr = 0; + + switch (mode) { + case PAT_UNCACHED: + case PAT_UNCACHEABLE: + case PAT_WRITE_PROTECTED: + attr |= PG_NC_PCD; + case PAT_WRITE_THROUGH: + attr |= PG_NC_PWT; + break; + case PAT_WRITE_COMBINING: + attr |= PG_NC_PCD; + break; + case PAT_WRITE_BACK: + break; + default: + lpmap_p("Unsupported mode=0x%08x\n", mode); + return (EINVAL); + break; + } + + ptefl = PG_NC_PCD | PG_NC_PWT; + pdefl = PG_NC_PCD | PG_NC_PWT; + if (osreldate >= 602110) { + ptefl |= PG_PTE_PAT; + pdefl |= PG_PDE_PAT; + } + + base = va & PG_FRAME; + offset = va & PAGE_MASK; + size = roundup(offset + size, PAGE_SIZE); + + err = lpmap_validate_range(base, size); + if (err != 0) { + lpmap_p("Validation failed! " + "vm_offset_t=0x%jx vm_size_t=%ju attr=0x%04x\n", + (uintmax_t)va, (uintmax_t)size, attr); + return (err); + } + + lpmap_p("vm_offset_t=0x%jx vm_size_t=%ju attr=0x%04x\n", + (uintmax_t)va, (uintmax_t)size, attr); + + flushtlb = 0; + + while (size > 0) { +#if defined(__amd64__) + pde = lpmap_pde(kernel_pmap, base); + if (*pde & PG_PS) { + do { + opxe = *(u_int *)pde; + npxe = opxe & ~pdefl; + npxe |= attr; + } while (npxe != opxe && ++flushtlb && + !atomic_cmpset_int((u_int *)pde, opxe, npxe)); + size -= NBPDR; + base += NBPDR; + } else { +#endif + pte = vtopte(base); + do { + opxe = *(u_int *)pte; + npxe = opxe & ~ptefl; + npxe |= attr; + } while (npxe != opxe && ++flushtlb && + !atomic_cmpset_int((u_int *)pte, opxe, npxe)); + size -= PAGE_SIZE; + base += PAGE_SIZE; +#if defined(__amd64__) + } +#endif + } + + /* XXX Gross!! */ + if (flushtlb != 0) { + lpmap_p("flushtlb=%d\n", flushtlb); + pmap_invalidate_all(kernel_pmap); + } + + return (0); +} + +#endif /* !_LPMAP_C_ */ --- sys/dev/sound/midi/midi.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/midi.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,1528 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@netbsd.org). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + /* + * Parts of this file started out as NetBSD: midi.c 1.31 + * They are mostly gone. Still the most obvious will be the state + * machine midi_in + */ + +#include +__FBSDID("$FreeBSD: src/sys/dev/sound/midi/midi.c,v 1.24 2007/04/02 06:03:47 ariff Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "mpu_if.h" + +#include +#include "synth_if.h" +MALLOC_DEFINE(M_MIDI, "midi buffers", "Midi data allocation area"); + + +#define PCMMKMINOR(u, d, c) ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) +#define MIDIMKMINOR(u, d, c) PCMMKMINOR(u, d, c) + +#define MIDI_DEV_RAW 2 +#define MIDI_DEV_MIDICTL 12 + +enum midi_states { + MIDI_IN_START, MIDI_IN_SYSEX, MIDI_IN_DATA +}; + +/* + * The MPU interface current has init() uninit() inqsize(( outqsize() + * callback() : fiddle with the tx|rx status. + */ + +#include "mpu_if.h" + +/* + * /dev/rmidi Structure definitions + */ + +#define MIDI_NAMELEN 16 +struct snd_midi { + KOBJ_FIELDS; + struct mtx lock; /* Protects all but queues */ + void *cookie; + + int unit; /* Should only be used in midistat */ + int channel; /* Should only be used in midistat */ + + int busy; + int flags; /* File flags */ + char name[MIDI_NAMELEN]; + struct mtx qlock; /* Protects inq, outq and flags */ + MIDIQ_HEAD(, char) inq, outq; + int rchan, wchan; + struct selinfo rsel, wsel; + int hiwat; /* QLEN(outq)>High-water -> disable + * writes from userland */ + enum midi_states inq_state; + int inq_status, inq_left; /* Variables for the state machine in + * Midi_in, this is to provide that + * signals only get issued only + * complete command packets. */ + struct proc *async; + struct cdev *dev; + struct synth_midi *synth; + int synth_flags; + TAILQ_ENTRY(snd_midi) link; +}; + +struct synth_midi { + KOBJ_FIELDS; + struct snd_midi *m; +}; + +static synth_open_t midisynth_open; +static synth_close_t midisynth_close; +static synth_writeraw_t midisynth_writeraw; +static synth_killnote_t midisynth_killnote; +static synth_startnote_t midisynth_startnote; +static synth_setinstr_t midisynth_setinstr; +static synth_alloc_t midisynth_alloc; +static synth_controller_t midisynth_controller; +static synth_bender_t midisynth_bender; + + +static kobj_method_t midisynth_methods[] = { + KOBJMETHOD(synth_open, midisynth_open), + KOBJMETHOD(synth_close, midisynth_close), + KOBJMETHOD(synth_writeraw, midisynth_writeraw), + KOBJMETHOD(synth_setinstr, midisynth_setinstr), + KOBJMETHOD(synth_startnote, midisynth_startnote), + KOBJMETHOD(synth_killnote, midisynth_killnote), + KOBJMETHOD(synth_alloc, midisynth_alloc), + KOBJMETHOD(synth_controller, midisynth_controller), + KOBJMETHOD(synth_bender, midisynth_bender), + {0, 0} +}; + +DEFINE_CLASS(midisynth, midisynth_methods, 0); + +/* + * Module Exports & Interface + * + * struct midi_chan *midi_init(MPU_CLASS cls, int unit, int chan) int + * midi_uninit(struct snd_midi *) 0 == no error EBUSY or other error int + * Midi_in(struct midi_chan *, char *buf, int count) int Midi_out(struct + * midi_chan *, char *buf, int count) + * + * midi_{in,out} return actual size transfered + * + */ + + +/* + * midi_devs tailq, holder of all rmidi instances protected by midistat_lock + */ + +TAILQ_HEAD(, snd_midi) midi_devs; + +/* + * /dev/midistat variables and declarations, protected by midistat_lock + */ + +static struct mtx midistat_lock; +static int midistat_isopen = 0; +static struct sbuf midistat_sbuf; +static int midistat_bufptr; +static struct cdev *midistat_dev; + +/* + * /dev/midistat dev_t declarations + */ + +static d_open_t midistat_open; +static d_close_t midistat_close; +static d_read_t midistat_read; + +static struct cdevsw midistat_cdevsw = { + .d_version = D_VERSION, + .d_open = midistat_open, + .d_close = midistat_close, + .d_read = midistat_read, + .d_name = "midistat", +}; + + +/* + * /dev/rmidi dev_t declarations, struct variable access is protected by + * locks contained within the structure. + */ + +static d_open_t midi_open; +static d_close_t midi_close; +static d_ioctl_t midi_ioctl; +static d_read_t midi_read; +static d_write_t midi_write; +static d_poll_t midi_poll; + +static struct cdevsw midi_cdevsw = { + .d_version = D_VERSION, + .d_open = midi_open, + .d_close = midi_close, + .d_read = midi_read, + .d_write = midi_write, + .d_ioctl = midi_ioctl, + .d_poll = midi_poll, + .d_name = "rmidi", +}; + +/* + * Prototypes of library functions + */ + +static int midi_destroy(struct snd_midi *, int); +static int midistat_prepare(struct sbuf * s); +static int midi_load(void); +static int midi_unload(void); + +/* + * Misc declr. + */ +SYSCTL_NODE(_hw, OID_AUTO, midi, CTLFLAG_RD, 0, "Midi driver"); +SYSCTL_NODE(_hw_midi, OID_AUTO, stat, CTLFLAG_RD, 0, "Status device"); + +int midi_debug; +/* XXX: should this be moved into debug.midi? */ +SYSCTL_INT(_hw_midi, OID_AUTO, debug, CTLFLAG_RW, &midi_debug, 0, ""); + +int midi_dumpraw; +SYSCTL_INT(_hw_midi, OID_AUTO, dumpraw, CTLFLAG_RW, &midi_dumpraw, 0, ""); + +int midi_instroff; +SYSCTL_INT(_hw_midi, OID_AUTO, instroff, CTLFLAG_RW, &midi_instroff, 0, ""); + +int midistat_verbose; +SYSCTL_INT(_hw_midi_stat, OID_AUTO, verbose, CTLFLAG_RW, + &midistat_verbose, 0, ""); + +#define MIDI_DEBUG(l,a) if(midi_debug>=l) a +/* + * CODE START + */ + +/* + * Register a new rmidi device. cls midi_if interface unit == 0 means + * auto-assign new unit number unit != 0 already assigned a unit number, eg. + * not the first channel provided by this device. channel, sub-unit + * cookie is passed back on MPU calls Typical device drivers will call with + * unit=0, channel=1..(number of channels) and cookie=soft_c and won't care + * what unit number is used. + * + * It is an error to call midi_init with an already used unit/channel combo. + * + * Returns NULL on error + * + */ +struct snd_midi * +midi_init(kobj_class_t cls, int unit, int channel, void *cookie) +{ + struct snd_midi *m; + int i; + int inqsize, outqsize; + MIDI_TYPE *buf; + + MIDI_DEBUG(1, printf("midiinit: unit %d/%d.\n", unit, channel)); + mtx_lock(&midistat_lock); + /* + * Protect against call with existing unit/channel or auto-allocate a + * new unit number. + */ + i = -1; + TAILQ_FOREACH(m, &midi_devs, link) { + mtx_lock(&m->lock); + if (unit != 0) { + if (m->unit == unit && m->channel == channel) { + mtx_unlock(&m->lock); + goto err0; + } + } else { + /* + * Find a better unit number + */ + if (m->unit > i) + i = m->unit; + } + mtx_unlock(&m->lock); + } + + if (unit == 0) + unit = i + 1; + + MIDI_DEBUG(1, printf("midiinit #2: unit %d/%d.\n", unit, channel)); + m = malloc(sizeof(*m), M_MIDI, M_NOWAIT | M_ZERO); + if (m == NULL) + goto err0; + + m->synth = malloc(sizeof(*m->synth), M_MIDI, M_NOWAIT | M_ZERO); + kobj_init((kobj_t)m->synth, &midisynth_class); + m->synth->m = m; + kobj_init((kobj_t)m, cls); + inqsize = MPU_INQSIZE(m, cookie); + outqsize = MPU_OUTQSIZE(m, cookie); + + MIDI_DEBUG(1, printf("midiinit queues %d/%d.\n", inqsize, outqsize)); + if (!inqsize && !outqsize) + goto err1; + + mtx_init(&m->lock, "raw midi", NULL, 0); + mtx_init(&m->qlock, "q raw midi", NULL, 0); + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (inqsize) + buf = malloc(sizeof(MIDI_TYPE) * inqsize, M_MIDI, M_NOWAIT); + else + buf = NULL; + + MIDIQ_INIT(m->inq, buf, inqsize); + + if (outqsize) + buf = malloc(sizeof(MIDI_TYPE) * outqsize, M_MIDI, M_NOWAIT); + else + buf = NULL; + m->hiwat = outqsize / 2; + + MIDIQ_INIT(m->outq, buf, outqsize); + + if ((inqsize && !MIDIQ_BUF(m->inq)) || + (outqsize && !MIDIQ_BUF(m->outq))) + goto err2; + + + m->busy = 0; + m->flags = 0; + m->unit = unit; + m->channel = channel; + m->cookie = cookie; + + if (MPU_INIT(m, cookie)) + goto err2; + + mtx_unlock(&m->lock); + mtx_unlock(&m->qlock); + + TAILQ_INSERT_TAIL(&midi_devs, m, link); + + mtx_unlock(&midistat_lock); + + m->dev = make_dev(&midi_cdevsw, + MIDIMKMINOR(unit, MIDI_DEV_RAW, channel), + UID_ROOT, GID_WHEEL, 0666, "midi%d.%d", unit, channel); + m->dev->si_drv1 = m; + + return m; + +err2: mtx_destroy(&m->qlock); + mtx_destroy(&m->lock); + + if (MIDIQ_BUF(m->inq)) + free(MIDIQ_BUF(m->inq), M_MIDI); + if (MIDIQ_BUF(m->outq)) + free(MIDIQ_BUF(m->outq), M_MIDI); +err1: free(m, M_MIDI); +err0: mtx_unlock(&midistat_lock); + MIDI_DEBUG(1, printf("midi_init ended in error\n")); + return NULL; +} + +/* + * midi_uninit does not call MIDI_UNINIT, as since this is the implementors + * entry point. midi_unint if fact, does not send any methods. A call to + * midi_uninit is a defacto promise that you won't manipulate ch anymore + * + */ + +int +midi_uninit(struct snd_midi *m) +{ + int err; + + err = ENXIO; + mtx_lock(&midistat_lock); + mtx_lock(&m->lock); + if (m->busy) { + if (!(m->rchan || m->wchan)) + goto err; + + if (m->rchan) { + wakeup(&m->rchan); + m->rchan = 0; + } + if (m->wchan) { + wakeup(&m->wchan); + m->wchan = 0; + } + } + err = midi_destroy(m, 0); + if (!err) + goto exit; + +err: mtx_unlock(&m->lock); +exit: mtx_unlock(&midistat_lock); + return err; +} + +/* + * midi_in: process all data until the queue is full, then discards the rest. + * Since midi_in is a state machine, data discards can cause it to get out of + * whack. Process as much as possible. It calls, wakeup, selnotify and + * psignal at most once. + */ + +#ifdef notdef +static int midi_lengths[] = {2, 2, 2, 2, 1, 1, 2, 0}; + +#endif /* notdef */ +/* Number of bytes in a MIDI command */ +#define MIDI_LENGTH(d) (midi_lengths[((d) >> 4) & 7]) +#define MIDI_ACK 0xfe +#define MIDI_IS_STATUS(d) ((d) >= 0x80) +#define MIDI_IS_COMMON(d) ((d) >= 0xf0) + +#define MIDI_SYSEX_START 0xF0 +#define MIDI_SYSEX_END 0xF7 + + +int +midi_in(struct snd_midi *m, MIDI_TYPE *buf, int size) +{ + /* int i, sig, enq; */ + int used; + + /* MIDI_TYPE data; */ + MIDI_DEBUG(5, printf("midi_in: m=%p size=%d\n", m, size)); + +/* + * XXX: locking flub + */ + if (!(m->flags & M_RX)) + return size; + + used = 0; + + mtx_lock(&m->qlock); +#if 0 + /* + * Don't bother queuing if not in read mode. Discard everything and + * return size so the caller doesn't freak out. + */ + + if (!(m->flags & M_RX)) + return size; + + for (i = sig = 0; i < size; i++) { + + data = buf[i]; + enq = 0; + if (data == MIDI_ACK) + continue; + + switch (m->inq_state) { + case MIDI_IN_START: + if (MIDI_IS_STATUS(data)) { + switch (data) { + case 0xf0: /* Sysex */ + m->inq_state = MIDI_IN_SYSEX; + break; + case 0xf1: /* MTC quarter frame */ + case 0xf3: /* Song select */ + m->inq_state = MIDI_IN_DATA; + enq = 1; + m->inq_left = 1; + break; + case 0xf2: /* Song position pointer */ + m->inq_state = MIDI_IN_DATA; + enq = 1; + m->inq_left = 2; + break; + default: + if (MIDI_IS_COMMON(data)) { + enq = 1; + sig = 1; + } else { + m->inq_state = MIDI_IN_DATA; + enq = 1; + m->inq_status = data; + m->inq_left = MIDI_LENGTH(data); + } + break; + } + } else if (MIDI_IS_STATUS(m->inq_status)) { + m->inq_state = MIDI_IN_DATA; + if (!MIDIQ_FULL(m->inq)) { + used++; + MIDIQ_ENQ(m->inq, &m->inq_status, 1); + } + enq = 1; + m->inq_left = MIDI_LENGTH(m->inq_status) - 1; + } + break; + /* + * End of case MIDI_IN_START: + */ + + case MIDI_IN_DATA: + enq = 1; + if (--m->inq_left <= 0) + sig = 1;/* deliver data */ + break; + case MIDI_IN_SYSEX: + if (data == MIDI_SYSEX_END) + m->inq_state = MIDI_IN_START; + break; + } + + if (enq) + if (!MIDIQ_FULL(m->inq)) { + MIDIQ_ENQ(m->inq, &data, 1); + used++; + } + /* + * End of the state machines main "for loop" + */ + } + if (sig) { +#endif + MIDI_DEBUG(6, printf("midi_in: len %jd avail %jd\n", + (intmax_t)MIDIQ_LEN(m->inq), + (intmax_t)MIDIQ_AVAIL(m->inq))); + if (MIDIQ_AVAIL(m->inq) > size) { + used = size; + MIDIQ_ENQ(m->inq, buf, size); + } else { + MIDI_DEBUG(4, printf("midi_in: Discarding data qu\n")); + mtx_unlock(&m->qlock); + return 0; + } + if (m->rchan) { + wakeup(&m->rchan); + m->rchan = 0; + } + selwakeup(&m->rsel); + if (m->async) { + PROC_LOCK(m->async); + psignal(m->async, SIGIO); + PROC_UNLOCK(m->async); + } +#if 0 + } +#endif + mtx_unlock(&m->qlock); + return used; +} + +/* + * midi_out: The only clearer of the M_TXEN flag. + */ +int +midi_out(struct snd_midi *m, MIDI_TYPE *buf, int size) +{ + int used; + +/* + * XXX: locking flub + */ + if (!(m->flags & M_TXEN)) + return 0; + + MIDI_DEBUG(2, printf("midi_out: %p\n", m)); + mtx_lock(&m->qlock); + used = MIN(size, MIDIQ_LEN(m->outq)); + MIDI_DEBUG(3, printf("midi_out: used %d\n", used)); + if (used) + MIDIQ_DEQ(m->outq, buf, used); + if (MIDIQ_EMPTY(m->outq)) { + m->flags &= ~M_TXEN; + MPU_CALLBACKP(m, m->cookie, m->flags); + } + if (used && MIDIQ_AVAIL(m->outq) > m->hiwat) { + if (m->wchan) { + wakeup(&m->wchan); + m->wchan = 0; + } + selwakeup(&m->wsel); + if (m->async) { + PROC_LOCK(m->async); + psignal(m->async, SIGIO); + PROC_UNLOCK(m->async); + } + } + mtx_unlock(&m->qlock); + return used; +} + + +/* + * /dev/rmidi#.# device access functions + */ +int +midi_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + struct snd_midi *m = i_dev->si_drv1; + int retval; + + MIDI_DEBUG(1, printf("midiopen %p %s %s\n", td, + flags & FREAD ? "M_RX" : "", flags & FWRITE ? "M_TX" : "")); + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + retval = 0; + + if (flags & FREAD) { + if (MIDIQ_SIZE(m->inq) == 0) + retval = ENXIO; + else if (m->flags & M_RX) + retval = EBUSY; + if (retval) + goto err; + } + if (flags & FWRITE) { + if (MIDIQ_SIZE(m->outq) == 0) + retval = ENXIO; + else if (m->flags & M_TX) + retval = EBUSY; + if (retval) + goto err; + } + m->busy++; + + m->rchan = 0; + m->wchan = 0; + m->async = 0; + + if (flags & FREAD) { + m->flags |= M_RX | M_RXEN; + /* + * Only clear the inq, the outq might still have data to drain + * from a previous session + */ + MIDIQ_CLEAR(m->inq); + }; + + if (flags & FWRITE) + m->flags |= M_TX; + + MPU_CALLBACK(m, m->cookie, m->flags); + + MIDI_DEBUG(2, printf("midi_open: opened.\n")); + +err: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); + return retval; +} + +int +midi_close(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + struct snd_midi *m = i_dev->si_drv1; + int retval; + int oldflags; + + MIDI_DEBUG(1, printf("midi_close %p %s %s\n", td, + flags & FREAD ? "M_RX" : "", flags & FWRITE ? "M_TX" : "")); + + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if ((flags & FREAD && !(m->flags & M_RX)) || + (flags & FWRITE && !(m->flags & M_TX))) { + retval = ENXIO; + goto err; + } + m->busy--; + + oldflags = m->flags; + + if (flags & FREAD) + m->flags &= ~(M_RX | M_RXEN); + if (flags & FWRITE) + m->flags &= ~M_TX; + + if ((m->flags & (M_TXEN | M_RXEN)) != (oldflags & (M_RXEN | M_TXEN))) + MPU_CALLBACK(m, m->cookie, m->flags); + + MIDI_DEBUG(1, printf("midi_close: closed, busy = %d.\n", m->busy)); + + mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); + retval = 0; +err: return retval; +} + +/* + * TODO: midi_read, per oss programmer's guide pg. 42 should return as soon + * as data is available. + */ +int +midi_read(struct cdev *i_dev, struct uio *uio, int ioflag) +{ +#define MIDI_RSIZE 32 + struct snd_midi *m = i_dev->si_drv1; + int retval; + int used; + char buf[MIDI_RSIZE]; + + MIDI_DEBUG(5, printf("midiread: count=%lu\n", + (unsigned long)uio->uio_resid)); + + retval = EIO; + + if (m == NULL) + goto err0; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (!(m->flags & M_RX)) + goto err1; + + while (uio->uio_resid > 0) { + while (MIDIQ_EMPTY(m->inq)) { + retval = EWOULDBLOCK; + if (ioflag & O_NONBLOCK) + goto err1; + mtx_unlock(&m->lock); + m->rchan = 1; + retval = msleep(&m->rchan, &m->qlock, + PCATCH | PDROP, "midi RX", 0); + /* + * We slept, maybe things have changed since last + * dying check + */ + if (retval == EINTR) + goto err0; + if (m != i_dev->si_drv1) + retval = ENXIO; + /* if (retval && retval != ERESTART) */ + if (retval) + goto err0; + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + m->rchan = 0; + if (!m->busy) + goto err1; + } + MIDI_DEBUG(6, printf("midi_read start\n")); + /* + * At this point, it is certain that m->inq has data + */ + + used = MIN(MIDIQ_LEN(m->inq), uio->uio_resid); + used = MIN(used, MIDI_RSIZE); + + MIDI_DEBUG(6, printf("midiread: uiomove cc=%d\n", used)); + MIDIQ_DEQ(m->inq, buf, used); + retval = uiomove(buf, used, uio); + if (retval) + goto err1; + } + + /* + * If we Made it here then transfer is good + */ + retval = 0; +err1: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); +err0: MIDI_DEBUG(4, printf("midi_read: ret %d\n", retval)); + return retval; +} + +/* + * midi_write: The only setter of M_TXEN + */ + +int +midi_write(struct cdev *i_dev, struct uio *uio, int ioflag) +{ +#define MIDI_WSIZE 32 + struct snd_midi *m = i_dev->si_drv1; + int retval; + int used; + char buf[MIDI_WSIZE]; + + + MIDI_DEBUG(4, printf("midi_write\n")); + retval = 0; + if (m == NULL) + goto err0; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (!(m->flags & M_TX)) + goto err1; + + while (uio->uio_resid > 0) { + while (MIDIQ_AVAIL(m->outq) == 0) { + retval = EWOULDBLOCK; + if (ioflag & O_NONBLOCK) + goto err1; + mtx_unlock(&m->lock); + m->wchan = 1; + MIDI_DEBUG(3, printf("midi_write msleep\n")); + retval = msleep(&m->wchan, &m->qlock, + PCATCH | PDROP, "midi TX", 0); + /* + * We slept, maybe things have changed since last + * dying check + */ + if (retval == EINTR) + goto err0; + if (m != i_dev->si_drv1) + retval = ENXIO; + if (retval) + goto err0; + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + m->wchan = 0; + if (!m->busy) + goto err1; + } + + /* + * We are certain than data can be placed on the queue + */ + + used = MIN(MIDIQ_AVAIL(m->outq), uio->uio_resid); + used = MIN(used, MIDI_WSIZE); + MIDI_DEBUG(5, printf("midiout: resid %d len %jd avail %jd\n", + uio->uio_resid, (intmax_t)MIDIQ_LEN(m->outq), + (intmax_t)MIDIQ_AVAIL(m->outq))); + + + MIDI_DEBUG(5, printf("midi_write: uiomove cc=%d\n", used)); + retval = uiomove(buf, used, uio); + if (retval) + goto err1; + MIDIQ_ENQ(m->outq, buf, used); + /* + * Inform the bottom half that data can be written + */ + if (!(m->flags & M_TXEN)) { + m->flags |= M_TXEN; + MPU_CALLBACK(m, m->cookie, m->flags); + } + } + /* + * If we Made it here then transfer is good + */ + retval = 0; +err1: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); +err0: return retval; +} + +int +midi_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + return ENXIO; +} + +int +midi_poll(struct cdev *i_dev, int events, struct thread *td) +{ + struct snd_midi *m = i_dev->si_drv1; + int revents; + + if (m == NULL) + return 0; + + revents = 0; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (events & (POLLIN | POLLRDNORM)) + if (!MIDIQ_EMPTY(m->inq)) + events |= events & (POLLIN | POLLRDNORM); + + if (events & (POLLOUT | POLLWRNORM)) + if (MIDIQ_AVAIL(m->outq) < m->hiwat) + events |= events & (POLLOUT | POLLWRNORM); + + if (revents == 0) { + if (events & (POLLIN | POLLRDNORM)) + selrecord(td, &m->rsel); + + if (events & (POLLOUT | POLLWRNORM)) + selrecord(td, &m->wsel); + } + mtx_unlock(&m->lock); + mtx_unlock(&m->qlock); + + return (revents); +} + +/* + * /dev/midistat device functions + * + */ +static int +midistat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + int error; + + MIDI_DEBUG(1, printf("midistat_open\n")); + mtx_lock(&midistat_lock); + + if (midistat_isopen) { + mtx_unlock(&midistat_lock); + return EBUSY; + } + midistat_isopen = 1; + mtx_unlock(&midistat_lock); + + if (sbuf_new(&midistat_sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { + error = ENXIO; + mtx_lock(&midistat_lock); + goto out; + } + mtx_lock(&midistat_lock); + midistat_bufptr = 0; + error = (midistat_prepare(&midistat_sbuf) > 0) ? 0 : ENOMEM; + +out: if (error) + midistat_isopen = 0; + mtx_unlock(&midistat_lock); + return error; +} + +static int +midistat_close(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + MIDI_DEBUG(1, printf("midistat_close\n")); + mtx_lock(&midistat_lock); + if (!midistat_isopen) { + mtx_unlock(&midistat_lock); + return EBADF; + } + sbuf_delete(&midistat_sbuf); + midistat_isopen = 0; + + mtx_unlock(&midistat_lock); + return 0; +} + +static int +midistat_read(struct cdev *i_dev, struct uio *buf, int flag) +{ + int l, err; + + MIDI_DEBUG(4, printf("midistat_read\n")); + mtx_lock(&midistat_lock); + if (!midistat_isopen) { + mtx_unlock(&midistat_lock); + return EBADF; + } + l = min(buf->uio_resid, sbuf_len(&midistat_sbuf) - midistat_bufptr); + err = 0; + if (l > 0) { + mtx_unlock(&midistat_lock); + err = uiomove(sbuf_data(&midistat_sbuf) + midistat_bufptr, l, + buf); + mtx_lock(&midistat_lock); + } else + l = 0; + midistat_bufptr += l; + mtx_unlock(&midistat_lock); + return err; +} + +/* + * Module library functions + */ + +static int +midistat_prepare(struct sbuf *s) +{ + struct snd_midi *m; + + mtx_assert(&midistat_lock, MA_OWNED); + + sbuf_printf(s, "FreeBSD Midi Driver (midi2)\n"); + if (TAILQ_EMPTY(&midi_devs)) { + sbuf_printf(s, "No devices installed.\n"); + sbuf_finish(s); + return sbuf_len(s); + } + sbuf_printf(s, "Installed devices:\n"); + + TAILQ_FOREACH(m, &midi_devs, link) { + mtx_lock(&m->lock); + sbuf_printf(s, "%s [%d/%d:%s]", m->name, m->unit, m->channel, + MPU_PROVIDER(m, m->cookie)); + sbuf_printf(s, "%s", MPU_DESCR(m, m->cookie, midistat_verbose)); + sbuf_printf(s, "\n"); + mtx_unlock(&m->lock); + } + + sbuf_finish(s); + return sbuf_len(s); +} + +#ifdef notdef +/* + * Convert IOCTL command to string for debugging + */ + +static char * +midi_cmdname(int cmd) +{ + static struct { + int cmd; + char *name; + } *tab, cmdtab_midiioctl[] = { +#define A(x) {x, ## x} + /* + * Once we have some real IOCTLs define, the following will + * be relavant. + * + * A(SNDCTL_MIDI_PRETIME), A(SNDCTL_MIDI_MPUMODE), + * A(SNDCTL_MIDI_MPUCMD), A(SNDCTL_SYNTH_INFO), + * A(SNDCTL_MIDI_INFO), A(SNDCTL_SYNTH_MEMAVL), + * A(SNDCTL_FM_LOAD_INSTR), A(SNDCTL_FM_4OP_ENABLE), + * A(MIOSPASSTHRU), A(MIOGPASSTHRU), A(AIONWRITE), + * A(AIOGSIZE), A(AIOSSIZE), A(AIOGFMT), A(AIOSFMT), + * A(AIOGMIX), A(AIOSMIX), A(AIOSTOP), A(AIOSYNC), + * A(AIOGCAP), + */ +#undef A + { + -1, "unknown" + }, + }; + + for (tab = cmdtab_midiioctl; tab->cmd != cmd && tab->cmd != -1; tab++); + return tab->name; +} + +#endif /* notdef */ + +/* + * midisynth + */ + + +int +midisynth_open(void *n, void *arg, int flags) +{ + struct snd_midi *m = ((struct synth_midi *)n)->m; + int retval; + + MIDI_DEBUG(1, printf("midisynth_open %s %s\n", + flags & FREAD ? "M_RX" : "", flags & FWRITE ? "M_TX" : "")); + + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + retval = 0; + + if (flags & FREAD) { + if (MIDIQ_SIZE(m->inq) == 0) + retval = ENXIO; + else if (m->flags & M_RX) + retval = EBUSY; + if (retval) + goto err; + } + if (flags & FWRITE) { + if (MIDIQ_SIZE(m->outq) == 0) + retval = ENXIO; + else if (m->flags & M_TX) + retval = EBUSY; + if (retval) + goto err; + } + m->busy++; + + /* + * TODO: Consider m->async = 0; + */ + + if (flags & FREAD) { + m->flags |= M_RX | M_RXEN; + /* + * Only clear the inq, the outq might still have data to drain + * from a previous session + */ + MIDIQ_CLEAR(m->inq); + m->rchan = 0; + }; + + if (flags & FWRITE) { + m->flags |= M_TX; + m->wchan = 0; + } + m->synth_flags = flags & (FREAD | FWRITE); + + MPU_CALLBACK(m, m->cookie, m->flags); + + +err: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); + MIDI_DEBUG(2, printf("midisynth_open: return %d.\n", retval)); + return retval; +} + +int +midisynth_close(void *n) +{ + struct snd_midi *m = ((struct synth_midi *)n)->m; + int retval; + int oldflags; + + MIDI_DEBUG(1, printf("midisynth_close %s %s\n", + m->synth_flags & FREAD ? "M_RX" : "", + m->synth_flags & FWRITE ? "M_TX" : "")); + + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if ((m->synth_flags & FREAD && !(m->flags & M_RX)) || + (m->synth_flags & FWRITE && !(m->flags & M_TX))) { + retval = ENXIO; + goto err; + } + m->busy--; + + oldflags = m->flags; + + if (m->synth_flags & FREAD) + m->flags &= ~(M_RX | M_RXEN); + if (m->synth_flags & FWRITE) + m->flags &= ~M_TX; + + if ((m->flags & (M_TXEN | M_RXEN)) != (oldflags & (M_RXEN | M_TXEN))) + MPU_CALLBACK(m, m->cookie, m->flags); + + MIDI_DEBUG(1, printf("midi_close: closed, busy = %d.\n", m->busy)); + + mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); + retval = 0; +err: return retval; +} + +/* + * Always blocking. + */ + +int +midisynth_writeraw(void *n, uint8_t *buf, size_t len) +{ + struct snd_midi *m = ((struct synth_midi *)n)->m; + int retval; + int used; + int i; + + MIDI_DEBUG(4, printf("midisynth_writeraw\n")); + + retval = 0; + + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (!(m->flags & M_TX)) + goto err1; + + if (midi_dumpraw) + printf("midi dump: "); + + while (len > 0) { + while (MIDIQ_AVAIL(m->outq) == 0) { + if (!(m->flags & M_TXEN)) { + m->flags |= M_TXEN; + MPU_CALLBACK(m, m->cookie, m->flags); + } + mtx_unlock(&m->lock); + m->wchan = 1; + MIDI_DEBUG(3, printf("midisynth_writeraw msleep\n")); + retval = msleep(&m->wchan, &m->qlock, + PCATCH | PDROP, "midi TX", 0); + /* + * We slept, maybe things have changed since last + * dying check + */ + if (retval == EINTR) + goto err0; + + if (retval) + goto err0; + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + m->wchan = 0; + if (!m->busy) + goto err1; + } + + /* + * We are certain than data can be placed on the queue + */ + + used = MIN(MIDIQ_AVAIL(m->outq), len); + used = MIN(used, MIDI_WSIZE); + MIDI_DEBUG(5, + printf("midi_synth: resid %zu len %jd avail %jd\n", + len, (intmax_t)MIDIQ_LEN(m->outq), + (intmax_t)MIDIQ_AVAIL(m->outq))); + + if (midi_dumpraw) + for (i = 0; i < used; i++) + printf("%x ", buf[i]); + + MIDIQ_ENQ(m->outq, buf, used); + len -= used; + + /* + * Inform the bottom half that data can be written + */ + if (!(m->flags & M_TXEN)) { + m->flags |= M_TXEN; + MPU_CALLBACK(m, m->cookie, m->flags); + } + } + /* + * If we Made it here then transfer is good + */ + if (midi_dumpraw) + printf("\n"); + + retval = 0; +err1: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); +err0: return retval; +} + +static int +midisynth_killnote(void *n, uint8_t chn, uint8_t note, uint8_t vel) +{ + u_char c[3]; + + + if (note > 127 || chn > 15) + return (EINVAL); + + if (vel > 127) + vel = 127; + + if (vel == 64) { + c[0] = 0x90 | (chn & 0x0f); /* Note on. */ + c[1] = (u_char)note; + c[2] = 0; + } else { + c[0] = 0x80 | (chn & 0x0f); /* Note off. */ + c[1] = (u_char)note; + c[2] = (u_char)vel; + } + + return midisynth_writeraw(n, c, 3); +} + +static int +midisynth_setinstr(void *n, uint8_t chn, uint16_t instr) +{ + u_char c[2]; + + if (instr > 127 || chn > 15) + return EINVAL; + + c[0] = 0xc0 | (chn & 0x0f); /* Progamme change. */ + c[1] = instr + midi_instroff; + + return midisynth_writeraw(n, c, 2); +} + +static int +midisynth_startnote(void *n, uint8_t chn, uint8_t note, uint8_t vel) +{ + u_char c[3]; + + if (note > 127 || chn > 15) + return EINVAL; + + if (vel > 127) + vel = 127; + + c[0] = 0x90 | (chn & 0x0f); /* Note on. */ + c[1] = (u_char)note; + c[2] = (u_char)vel; + + return midisynth_writeraw(n, c, 3); +} +static int +midisynth_alloc(void *n, uint8_t chan, uint8_t note) +{ + return chan; +} + +static int +midisynth_controller(void *n, uint8_t chn, uint8_t ctrlnum, uint16_t val) +{ + u_char c[3]; + + if (ctrlnum > 127 || chn > 15) + return EINVAL; + + c[0] = 0xb0 | (chn & 0x0f); /* Control Message. */ + c[1] = ctrlnum; + c[2] = val; + return midisynth_writeraw(n, c, 3); +} + +static int +midisynth_bender(void *n, uint8_t chn, uint16_t val) +{ + u_char c[3]; + + + if (val > 16383 || chn > 15) + return EINVAL; + + c[0] = 0xe0 | (chn & 0x0f); /* Pitch bend. */ + c[1] = (u_char)val & 0x7f; + c[2] = (u_char)(val >> 7) & 0x7f; + + return midisynth_writeraw(n, c, 3); +} + +/* + * Single point of midi destructions. + */ +static int +midi_destroy(struct snd_midi *m, int midiuninit) +{ + + mtx_assert(&midistat_lock, MA_OWNED); + mtx_assert(&m->lock, MA_OWNED); + + MIDI_DEBUG(3, printf("midi_destroy\n")); + m->dev->si_drv1 = NULL; + destroy_dev(m->dev); + TAILQ_REMOVE(&midi_devs, m, link); + if (midiuninit) + MPU_UNINIT(m, m->cookie); + free(MIDIQ_BUF(m->inq), M_MIDI); + free(MIDIQ_BUF(m->outq), M_MIDI); + mtx_destroy(&m->qlock); + mtx_destroy(&m->lock); + free(m, M_MIDI); + return 0; +} + +/* + * Load and unload functions, creates the /dev/midistat device + */ + +static int +midi_load() +{ + mtx_init(&midistat_lock, "midistat lock", NULL, 0); + TAILQ_INIT(&midi_devs); /* Initialize the queue. */ + + midistat_dev = make_dev(&midistat_cdevsw, + MIDIMKMINOR(0, MIDI_DEV_MIDICTL, 0), + UID_ROOT, GID_WHEEL, 0666, "midistat"); + + return 0; +} + +static int +midi_unload() +{ + struct snd_midi *m; + int retval; + + MIDI_DEBUG(1, printf("midi_unload()\n")); + retval = EBUSY; + mtx_lock(&midistat_lock); + if (midistat_isopen) + goto exit0; + + TAILQ_FOREACH(m, &midi_devs, link) { + mtx_lock(&m->lock); + if (m->busy) + retval = EBUSY; + else + retval = midi_destroy(m, 1); + if (retval) + goto exit1; + } + + destroy_dev(midistat_dev); + /* + * Made it here then unload is complete + */ + mtx_destroy(&midistat_lock); + return 0; + +exit1: + mtx_unlock(&m->lock); +exit0: + mtx_unlock(&midistat_lock); + if (retval) + MIDI_DEBUG(2, printf("midi_unload: failed\n")); + return retval; +} + +extern int seq_modevent(module_t mod, int type, void *data); + +static int +midi_modevent(module_t mod, int type, void *data) +{ + int retval; + + retval = 0; + + switch (type) { + case MOD_LOAD: + retval = midi_load(); +#if 0 + if (retval == 0) + retval = seq_modevent(mod, type, data); +#endif + break; + + case MOD_UNLOAD: + retval = midi_unload(); +#if 0 + if (retval == 0) + retval = seq_modevent(mod, type, data); +#endif + break; + + default: + break; + } + + return retval; +} + +kobj_t +midimapper_addseq(void *arg1, int *unit, void **cookie) +{ + unit = 0; + + return (kobj_t)arg1; +} + +int +midimapper_open(void *arg1, void **cookie) +{ + int retval = 0; + struct snd_midi *m; + + mtx_lock(&midistat_lock); + + TAILQ_FOREACH(m, &midi_devs, link) { + retval++; + } + + mtx_unlock(&midistat_lock); + return retval; +} + +int +midimapper_close(void *arg1, void *cookie) +{ + return 0; +} + +kobj_t +midimapper_fetch_synth(void *arg, void *cookie, int unit) +{ + struct snd_midi *m; + int retval = 0; + + mtx_lock(&midistat_lock); + + TAILQ_FOREACH(m, &midi_devs, link) { + if (unit == retval) { + mtx_unlock(&midistat_lock); + return (kobj_t)m->synth; + } + retval++; + } + + mtx_unlock(&midistat_lock); + return NULL; +} + +DEV_MODULE(midi, midi_modevent, NULL); +MODULE_VERSION(midi, 1); --- sys/dev/sound/midi/midi.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/midi.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/midi/midi.h,v 1.16 2007/02/25 13:51:51 netchild Exp $ + */ + +#ifndef MIDI_H +#define MIDI_H + +#include +#include + +MALLOC_DECLARE(M_MIDI); + +#define M_RX 0x01 +#define M_TX 0x02 +#define M_RXEN 0x04 +#define M_TXEN 0x08 + +#define MIDI_TYPE unsigned char + +struct snd_midi; + +struct snd_midi * +midi_init(kobj_class_t _mpu_cls, int _unit, int _channel, void *cookie); +int midi_uninit(struct snd_midi *_m); +int midi_out(struct snd_midi *_m, MIDI_TYPE *_buf, int _size); +int midi_in(struct snd_midi *_m, MIDI_TYPE *_buf, int _size); + +kobj_t midimapper_addseq(void *arg1, int *unit, void **cookie); +int midimapper_open(void *arg1, void **cookie); +int midimapper_close(void *arg1, void *cookie); +kobj_t midimapper_fetch_synth(void *arg, void *cookie, int unit); + +#endif --- sys/dev/sound/midi/midiq.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/midiq.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,106 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/midi/midiq.h,v 1.3 2007/02/25 13:51:51 netchild Exp $ + */ + +#ifndef MIDIQ_H +#define MIDIQ_H + +#define MIDIQ_MOVE(a,b,c) bcopy(b,a,c) + +#define MIDIQ_HEAD(name, type) \ +struct name { \ + int h, t, s; \ + type * b; \ +} + +#define MIDIQ_INIT(head, buf, size) do { \ + (head).h=(head).t=0; \ + (head).s=size; \ + (head).b=buf; \ +} while (0) + +#define MIDIQ_EMPTY(head) ((head).h == (head).t ) + +#define MIDIQ_LENBASE(head) ((head).h - (head).t < 0 ? \ + (head).h - (head).t + (head).s : \ + (head).h - (head).t) + +#define MIDIQ_FULL(head) ((head).h == -1) +#define MIDIQ_AVAIL(head) (MIDIQ_FULL(head) ? 0 : (head).s - MIDIQ_LENBASE(head)) +#define MIDIQ_LEN(head) ((head).s - MIDIQ_AVAIL(head)) +#define MIDIQ_DEBUG 0 +/* + * No protection against overflow, underflow + */ +#define MIDIQ_ENQ(head, buf, size) do { \ + if(MIDIQ_DEBUG)\ + printf("#1 %p %p bytes copied %jd tran req s %d h %d t %d\n", \ + &(head).b[(head).h], (buf), \ + (intmax_t)(sizeof(*(head).b) * \ + MIN( (size), (head).s - (head).h) ), \ + (size), (head).h, (head).t); \ + MIDIQ_MOVE(&(head).b[(head).h], (buf), sizeof(*(head).b) * MIN((size), (head).s - (head).h)); \ + if( (head).s - (head).h < (size) ) { \ + if(MIDIQ_DEBUG) \ + printf("#2 %p %p bytes copied %jd\n", (head).b, (buf) + (head).s - (head).h, (intmax_t)sizeof(*(head).b) * ((size) - (head).s + (head).h) ); \ + MIDIQ_MOVE((head).b, (buf) + (head).s - (head).h, sizeof(*(head).b) * ((size) - (head).s + (head).h) ); \ + } \ + (head).h+=(size); \ + (head).h%=(head).s; \ + if(MIDIQ_EMPTY(head)) (head).h=-1; \ + if(MIDIQ_DEBUG)\ + printf("#E h %d t %d\n", (head).h, (head).t); \ +} while (0) + +#define MIDIQ_DEQ_I(head, buf, size, move, update) do { \ + if(MIDIQ_FULL(head)) (head).h=(head).t; \ + if(MIDIQ_DEBUG)\ + printf("#1 %p %p bytes copied %jd tran req s %d h %d t %d\n", &(head).b[(head).t], (buf), (intmax_t)sizeof(*(head).b) * MIN((size), (head).s - (head).t), (size), (head).h, (head).t); \ + if (move) MIDIQ_MOVE((buf), &(head).b[(head).t], sizeof(*(head).b) * MIN((size), (head).s - (head).t)); \ + if( (head).s - (head).t < (size) ) { \ + if(MIDIQ_DEBUG) \ + printf("#2 %p %p bytes copied %jd\n", (head).b, (buf) + (head).s - (head).t, (intmax_t)sizeof(*(head).b) * ((size) - (head).s + (head).t) ); \ + if (move) MIDIQ_MOVE((buf) + (head).s - (head).t, (head).b, sizeof(*(head).b) * ((size) - (head).s + (head).t) ); \ + } \ + if (update) { \ + (head).t+=(size); \ + (head).t%=(head).s; \ + } else { \ + if (MIDIQ_EMPTY(head)) (head).h=-1; \ + } \ + if(MIDIQ_DEBUG)\ + printf("#E h %d t %d\n", (head).h, (head).t); \ +} while (0) + +#define MIDIQ_SIZE(head) ((head).s) +#define MIDIQ_CLEAR(head) ((head).h = (head).t = 0) +#define MIDIQ_BUF(head) ((head).b) +#define MIDIQ_DEQ(head, buf, size) MIDIQ_DEQ_I(head, buf, size, 1, 1) +#define MIDIQ_PEEK(head, buf, size) MIDIQ_DEQ_I(head, buf, size, 1, 0) +#define MIDIQ_POP(head, size) MIDIQ_DEQ_I(head, &head, size, 0, 1) + +#endif --- sys/dev/sound/midi/mpu401.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/mpu401.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,287 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD: src/sys/dev/sound/midi/mpu401.c,v 1.3 2007/02/25 13:51:51 netchild Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* to get driver_intr_t */ + +#include +#include + +#include "mpu_if.h" +#include "mpufoi_if.h" + +#define MPU_DATAPORT 0 +#define MPU_CMDPORT 1 +#define MPU_STATPORT 1 +#define MPU_RESET 0xff +#define MPU_UART 0x3f +#define MPU_ACK 0xfe +#define MPU_STATMASK 0xc0 +#define MPU_OUTPUTBUSY 0x40 +#define MPU_INPUTBUSY 0x80 +#define MPU_TRYDATA 50 +#define MPU_DELAY 2500 + +#define CMD(m,d) MPUFOI_WRITE(m, m->cookie, MPU_CMDPORT,d) +#define STATUS(m) MPUFOI_READ(m, m->cookie, MPU_STATPORT) +#define READ(m) MPUFOI_READ(m, m->cookie, MPU_DATAPORT) +#define WRITE(m,d) MPUFOI_WRITE(m, m->cookie, MPU_DATAPORT,d) + +struct mpu401 { + KOBJ_FIELDS; + struct snd_midi *mid; + int flags; + driver_intr_t *si; + void *cookie; + struct callout timer; +}; + +static void mpu401_timeout(void *m); +static mpu401_intr_t mpu401_intr; + +static int mpu401_minit(kobj_t obj, struct mpu401 *m); +static int mpu401_muninit(kobj_t obj, struct mpu401 *m); +static int mpu401_minqsize(kobj_t obj, struct mpu401 *m); +static int mpu401_moutqsize(kobj_t obj, struct mpu401 *m); +static void mpu401_mcallback(kobj_t obj, struct mpu401 *m, int flags); +static void mpu401_mcallbackp(kobj_t obj, struct mpu401 *m, int flags); +static const char *mpu401_mdescr(kobj_t obj, struct mpu401 *m, int verbosity); +static const char *mpu401_mprovider(kobj_t obj, struct mpu401 *m); + +static kobj_method_t mpu401_methods[] = { + KOBJMETHOD(mpu_init, mpu401_minit), + KOBJMETHOD(mpu_uninit, mpu401_muninit), + KOBJMETHOD(mpu_inqsize, mpu401_minqsize), + KOBJMETHOD(mpu_outqsize, mpu401_moutqsize), + KOBJMETHOD(mpu_callback, mpu401_mcallback), + KOBJMETHOD(mpu_callbackp, mpu401_mcallbackp), + KOBJMETHOD(mpu_descr, mpu401_mdescr), + KOBJMETHOD(mpu_provider, mpu401_mprovider), + {0, 0} +}; + +DEFINE_CLASS(mpu401, mpu401_methods, 0); + +void +mpu401_timeout(void *a) +{ + struct mpu401 *m = (struct mpu401 *)a; + + if (m->si) + (m->si)(m->cookie); + +} +static int +mpu401_intr(struct mpu401 *m) +{ +#define MPU_INTR_BUF 16 + MIDI_TYPE b[MPU_INTR_BUF]; + int i; + int s; + +/* + printf("mpu401_intr\n"); +*/ +#define RXRDY(m) ( (STATUS(m) & MPU_INPUTBUSY) == 0) +#define TXRDY(m) ( (STATUS(m) & MPU_OUTPUTBUSY) == 0) +#if 0 +#define D(x,l) printf("mpu401_intr %d %x %s %s\n",l, x, x&MPU_INPUTBUSY?"RX":"", x&MPU_OUTPUTBUSY?"TX":"") +#else +#define D(x,l) +#endif + i = 0; + s = STATUS(m); + D(s, 1); + while ((s & MPU_INPUTBUSY) == 0 && i < MPU_INTR_BUF) { + b[i] = READ(m); +/* + printf("mpu401_intr in i %d d %d\n", i, b[i]); +*/ + i++; + s = STATUS(m); + } + if (i) + midi_in(m->mid, b, i); + i = 0; + while (!(s & MPU_OUTPUTBUSY) && i < MPU_INTR_BUF) { + if (midi_out(m->mid, b, 1)) { +/* + printf("mpu401_intr out i %d d %d\n", i, b[0]); +*/ + + WRITE(m, *b); + } else { +/* + printf("mpu401_intr write: no output\n"); +*/ + return 0; + } + i++; + /* DELAY(100); */ + s = STATUS(m); + } + + if ((m->flags & M_TXEN) && (m->si)) { + callout_reset(&m->timer, 1, mpu401_timeout, m); + } + return (m->flags & M_TXEN) == M_TXEN; +} + +struct mpu401 * +mpu401_init(kobj_class_t cls, void *cookie, driver_intr_t softintr, + mpu401_intr_t ** cb) +{ + struct mpu401 *m; + + *cb = NULL; + m = malloc(sizeof(*m), M_MIDI, M_NOWAIT | M_ZERO); + + if (!m) + return NULL; + + kobj_init((kobj_t)m, cls); + + callout_init(&m->timer, 1); + + m->si = softintr; + m->cookie = cookie; + m->flags = 0; + + m->mid = midi_init(&mpu401_class, 0, 0, m); + if (!m->mid) + goto err; + *cb = mpu401_intr; + return m; +err: + printf("mpu401_init error\n"); + free(m, M_MIDI); + return NULL; +} + +int +mpu401_uninit(struct mpu401 *m) +{ + int retval; + + CMD(m, MPU_RESET); + retval = midi_uninit(m->mid); + if (retval) + return retval; + free(m, M_MIDI); + return 0; +} + +static int +mpu401_minit(kobj_t obj, struct mpu401 *m) +{ + int i; + + CMD(m, MPU_RESET); + CMD(m, MPU_UART); + return 0; + i = 0; + while (++i < 2000) { + if (RXRDY(m)) + if (READ(m) == MPU_ACK) + break; + } + + if (i < 2000) { + CMD(m, MPU_UART); + return 0; + } + printf("mpu401_minit failed active sensing\n"); + return 1; +} + + +int +mpu401_muninit(kobj_t obj, struct mpu401 *m) +{ + + return MPUFOI_UNINIT(m, m->cookie); +} + +int +mpu401_minqsize(kobj_t obj, struct mpu401 *m) +{ + return 128; +} + +int +mpu401_moutqsize(kobj_t obj, struct mpu401 *m) +{ + return 128; +} + +static void +mpu401_mcallback(kobj_t obj, struct mpu401 *m, int flags) +{ +#if 0 + printf("mpu401_callback %s %s %s %s\n", + flags & M_RX ? "M_RX" : "", + flags & M_TX ? "M_TX" : "", + flags & M_RXEN ? "M_RXEN" : "", + flags & M_TXEN ? "M_TXEN" : ""); +#endif + if (flags & M_TXEN && m->si) { + callout_reset(&m->timer, 1, mpu401_timeout, m); + } + m->flags = flags; +} + +static void +mpu401_mcallbackp(kobj_t obj, struct mpu401 *m, int flags) +{ +/* printf("mpu401_callbackp\n"); */ + mpu401_mcallback(obj, m, flags); +} + +static const char * +mpu401_mdescr(kobj_t obj, struct mpu401 *m, int verbosity) +{ + + return "descr mpu401"; +} + +static const char * +mpu401_mprovider(kobj_t obj, struct mpu401 *m) +{ + return "provider mpu401"; +} --- sys/dev/sound/midi/mpu401.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/mpu401.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/midi/mpu401.h,v 1.3 2007/02/25 13:51:51 netchild Exp $ + */ + +#ifndef MPU401_H +#define MPU401_H + +struct mpu401; + +typedef int mpu401_intr_t(struct mpu401 *_obj); + +extern struct mpu401 * +mpu401_init(kobj_class_t _cls, void *cookie, driver_intr_t *_softintr, + mpu401_intr_t ** _cb); +extern int mpu401_uninit(struct mpu401 *_obj); + +#endif --- sys/dev/sound/midi/mpu_if.m.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/mpu_if.m Thu Jul 12 12:04:19 2007 @@ -0,0 +1,74 @@ +#- +# Copyright (c) 2003 Mathew Kanner +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD: src/sys/dev/sound/midi/mpu_if.m,v 1.3 2007/02/25 13:51:51 netchild Exp $ +# + +#include + +INTERFACE mpu; + +METHOD int inqsize { + struct snd_midi *_kobj; + void *_cookie; +}; + +METHOD int outqsize { + struct snd_midi *_kobj; + void *_cookie; +}; + +METHOD int init { + struct snd_midi *_kobj; + void *_cookie; +}; + +METHOD void callbackp { + struct snd_midi *_kobj; + void *_cookie; + int _flags; +}; + +METHOD void callback { + struct snd_midi *_kobj; + void *_cookie; + int _flags; +}; + +METHOD const char * provider { + struct snd_midi *_kobj; + void *_cookie; +}; + +METHOD const char * descr { + struct snd_midi *_kobj; + void *_cookie; + int _verbosity; +}; + +METHOD int uninit { + struct snd_midi *_kobj; + void *_cookie; +}; --- sys/dev/sound/midi/mpufoi_if.m.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/mpufoi_if.m Thu Jul 12 12:04:19 2007 @@ -0,0 +1,50 @@ +#- +# Copyright (c) 2003 Mathew Kanner +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD: src/sys/dev/sound/midi/mpufoi_if.m,v 1.3 2007/02/25 13:51:52 netchild Exp $ +# + +#include +#include + +INTERFACE mpufoi; + +METHOD unsigned char read { + struct mpu401 *_kobj; + void *_cookie; + int _reg; +}; + +METHOD void write { + struct mpu401 *_kobj; + void *_cookie; + int _reg; + unsigned char _d; +}; + +METHOD int uninit { + struct mpu401 *_kobj; + void *_cookie; +}; --- sys/dev/sound/midi/sequencer.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/sequencer.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,2088 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * Copyright (c) 1993 Hannu Savolainen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * The sequencer personality manager. + */ + +#include +__FBSDID("$FreeBSD: src/sys/dev/sound/midi/sequencer.c,v 1.26 2007/03/15 14:57:54 ariff Exp $"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include /* for DATA_SET */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for DELAY */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include "synth_if.h" + +#include + +#define TMR_TIMERBASE 13 + +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + * synthesizer and MIDI output) */ +#define SND_DEV_MUSIC 8 /* /dev/music, level 2 interface */ + +/* Length of a sequencer event. */ +#define EV_SZ 8 +#define IEV_SZ 8 + +/* Lookup modes */ +#define LOOKUP_EXIST (0) +#define LOOKUP_OPEN (1) +#define LOOKUP_CLOSE (2) + +#define PCMMKMINOR(u, d, c) \ + ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) +#define MIDIMKMINOR(u, d, c) PCMMKMINOR(u, d, c) +#define MIDIUNIT(y) ((minor(y) >> 4) & 0x0f) +#define MIDIDEV(y) (minor(y) & 0x0f) + +/* These are the entries to the sequencer driver. */ +static d_open_t seq_open; +static d_close_t seq_close; +static d_ioctl_t seq_ioctl; +static d_read_t seq_read; +static d_write_t seq_write; +static d_poll_t seq_poll; + +static struct cdevsw seq_cdevsw = { + .d_version = D_VERSION, + .d_open = seq_open, + .d_close = seq_close, + .d_read = seq_read, + .d_write = seq_write, + .d_ioctl = seq_ioctl, + .d_poll = seq_poll, + .d_name = "sequencer", +}; + +struct seq_softc { + KOBJ_FIELDS; + + struct mtx seq_lock, q_lock; + struct cv empty_cv, reset_cv, in_cv, out_cv, state_cv, th_cv; + + MIDIQ_HEAD(, u_char) in_q, out_q; + + u_long flags; + /* Flags (protected by flag_mtx of mididev_info) */ + int fflags; /* Access mode */ + int music; + + int out_water; /* Sequence output threshould */ + snd_sync_parm sync_parm; /* AIOSYNC parameter set */ + struct thread *sync_thread; /* AIOSYNCing thread */ + struct selinfo in_sel, out_sel; + int midi_number; + struct cdev *seqdev, *musicdev; + int unit; + int maxunits; + kobj_t *midis; + int *midi_flags; + kobj_t mapper; + void *mapper_cookie; + struct timeval timerstop, timersub; + int timerbase, tempo; + int timerrun; + int done; + int playing; + int recording; + int busy; + int pre_event_timeout; + int waiting; +}; + +/* + * Module specific stuff, including how many sequecers + * we currently own. + */ + +SYSCTL_NODE(_hw_midi, OID_AUTO, seq, CTLFLAG_RD, 0, "Midi sequencer"); + +int seq_debug; +/* XXX: should this be moved into debug.midi? */ +SYSCTL_INT(_hw_midi_seq, OID_AUTO, debug, CTLFLAG_RW, &seq_debug, 0, ""); + +midi_cmdtab cmdtab_seqevent[] = { + {SEQ_NOTEOFF, "SEQ_NOTEOFF"}, + {SEQ_NOTEON, "SEQ_NOTEON"}, + {SEQ_WAIT, "SEQ_WAIT"}, + {SEQ_PGMCHANGE, "SEQ_PGMCHANGE"}, + {SEQ_SYNCTIMER, "SEQ_SYNCTIMER"}, + {SEQ_MIDIPUTC, "SEQ_MIDIPUTC"}, + {SEQ_DRUMON, "SEQ_DRUMON"}, + {SEQ_DRUMOFF, "SEQ_DRUMOFF"}, + {SEQ_ECHO, "SEQ_ECHO"}, + {SEQ_AFTERTOUCH, "SEQ_AFTERTOUCH"}, + {SEQ_CONTROLLER, "SEQ_CONTROLLER"}, + {SEQ_BALANCE, "SEQ_BALANCE"}, + {SEQ_VOLMODE, "SEQ_VOLMODE"}, + {SEQ_FULLSIZE, "SEQ_FULLSIZE"}, + {SEQ_PRIVATE, "SEQ_PRIVATE"}, + {SEQ_EXTENDED, "SEQ_EXTENDED"}, + {EV_SEQ_LOCAL, "EV_SEQ_LOCAL"}, + {EV_TIMING, "EV_TIMING"}, + {EV_CHN_COMMON, "EV_CHN_COMMON"}, + {EV_CHN_VOICE, "EV_CHN_VOICE"}, + {EV_SYSEX, "EV_SYSEX"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_seqioctl[] = { + {SNDCTL_SEQ_RESET, "SNDCTL_SEQ_RESET"}, + {SNDCTL_SEQ_SYNC, "SNDCTL_SEQ_SYNC"}, + {SNDCTL_SYNTH_INFO, "SNDCTL_SYNTH_INFO"}, + {SNDCTL_SEQ_CTRLRATE, "SNDCTL_SEQ_CTRLRATE"}, + {SNDCTL_SEQ_GETOUTCOUNT, "SNDCTL_SEQ_GETOUTCOUNT"}, + {SNDCTL_SEQ_GETINCOUNT, "SNDCTL_SEQ_GETINCOUNT"}, + {SNDCTL_SEQ_PERCMODE, "SNDCTL_SEQ_PERCMODE"}, + {SNDCTL_FM_LOAD_INSTR, "SNDCTL_FM_LOAD_INSTR"}, + {SNDCTL_SEQ_TESTMIDI, "SNDCTL_SEQ_TESTMIDI"}, + {SNDCTL_SEQ_RESETSAMPLES, "SNDCTL_SEQ_RESETSAMPLES"}, + {SNDCTL_SEQ_NRSYNTHS, "SNDCTL_SEQ_NRSYNTHS"}, + {SNDCTL_SEQ_NRMIDIS, "SNDCTL_SEQ_NRMIDIS"}, + {SNDCTL_SEQ_GETTIME, "SNDCTL_SEQ_GETTIME"}, + {SNDCTL_MIDI_INFO, "SNDCTL_MIDI_INFO"}, + {SNDCTL_SEQ_THRESHOLD, "SNDCTL_SEQ_THRESHOLD"}, + {SNDCTL_SYNTH_MEMAVL, "SNDCTL_SYNTH_MEMAVL"}, + {SNDCTL_FM_4OP_ENABLE, "SNDCTL_FM_4OP_ENABLE"}, + {SNDCTL_PMGR_ACCESS, "SNDCTL_PMGR_ACCESS"}, + {SNDCTL_SEQ_PANIC, "SNDCTL_SEQ_PANIC"}, + {SNDCTL_SEQ_OUTOFBAND, "SNDCTL_SEQ_OUTOFBAND"}, + {SNDCTL_TMR_TIMEBASE, "SNDCTL_TMR_TIMEBASE"}, + {SNDCTL_TMR_START, "SNDCTL_TMR_START"}, + {SNDCTL_TMR_STOP, "SNDCTL_TMR_STOP"}, + {SNDCTL_TMR_CONTINUE, "SNDCTL_TMR_CONTINUE"}, + {SNDCTL_TMR_TEMPO, "SNDCTL_TMR_TEMPO"}, + {SNDCTL_TMR_SOURCE, "SNDCTL_TMR_SOURCE"}, + {SNDCTL_TMR_METRONOME, "SNDCTL_TMR_METRONOME"}, + {SNDCTL_TMR_SELECT, "SNDCTL_TMR_SELECT"}, + {SNDCTL_MIDI_PRETIME, "SNDCTL_MIDI_PRETIME"}, + {AIONWRITE, "AIONWRITE"}, + {AIOGSIZE, "AIOGSIZE"}, + {AIOSSIZE, "AIOSSIZE"}, + {AIOGFMT, "AIOGFMT"}, + {AIOSFMT, "AIOSFMT"}, + {AIOGMIX, "AIOGMIX"}, + {AIOSMIX, "AIOSMIX"}, + {AIOSTOP, "AIOSTOP"}, + {AIOSYNC, "AIOSYNC"}, + {AIOGCAP, "AIOGCAP"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_timer[] = { + {TMR_WAIT_REL, "TMR_WAIT_REL"}, + {TMR_WAIT_ABS, "TMR_WAIT_ABS"}, + {TMR_STOP, "TMR_STOP"}, + {TMR_START, "TMR_START"}, + {TMR_CONTINUE, "TMR_CONTINUE"}, + {TMR_TEMPO, "TMR_TEMPO"}, + {TMR_ECHO, "TMR_ECHO"}, + {TMR_CLOCK, "TMR_CLOCK"}, + {TMR_SPP, "TMR_SPP"}, + {TMR_TIMESIG, "TMR_TIMESIG"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_seqcv[] = { + {MIDI_NOTEOFF, "MIDI_NOTEOFF"}, + {MIDI_NOTEON, "MIDI_NOTEON"}, + {MIDI_KEY_PRESSURE, "MIDI_KEY_PRESSURE"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_seqccmn[] = { + {MIDI_CTL_CHANGE, "MIDI_CTL_CHANGE"}, + {MIDI_PGM_CHANGE, "MIDI_PGM_CHANGE"}, + {MIDI_CHN_PRESSURE, "MIDI_CHN_PRESSURE"}, + {MIDI_PITCH_BEND, "MIDI_PITCH_BEND"}, + {MIDI_SYSTEM_PREFIX, "MIDI_SYSTEM_PREFIX"}, + {-1, NULL}, +}; + +/* + * static const char *mpu401_mprovider(kobj_t obj, struct mpu401 *m); + */ + +static kobj_method_t seq_methods[] = { + /* KOBJMETHOD(mpu_provider,mpu401_mprovider), */ + {0, 0} +}; + +DEFINE_CLASS(sequencer, seq_methods, 0); + +/* The followings are the local function. */ +static int seq_convertold(u_char *event, u_char *out); + +/* + * static void seq_midiinput(struct seq_softc * scp, void *md); + */ +static void seq_reset(struct seq_softc *scp); +static int seq_sync(struct seq_softc *scp); + +static int seq_processevent(struct seq_softc *scp, u_char *event); + +static int seq_timing(struct seq_softc *scp, u_char *event); +static int seq_local(struct seq_softc *scp, u_char *event); + +static int seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event); +static int seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event); +static int seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event); + +static int seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md); +void seq_copytoinput(struct seq_softc *scp, u_char *event, int len); +int seq_modevent(module_t mod, int type, void *data); +struct seq_softc *seqs[10]; +static struct mtx seqinfo_mtx; +static u_long nseq = 0; + +static void timer_start(struct seq_softc *t); +static void timer_stop(struct seq_softc *t); +static void timer_setvals(struct seq_softc *t, int tempo, int timerbase); +static void timer_wait(struct seq_softc *t, int ticks, int wait_abs); +static int timer_now(struct seq_softc *t); + + +static void +timer_start(struct seq_softc *t) +{ + t->timerrun = 1; + getmicrotime(&t->timersub); +} + +static void +timer_continue(struct seq_softc *t) +{ + struct timeval now; + + if (t->timerrun == 1) + return; + t->timerrun = 1; + getmicrotime(&now); + timevalsub(&now, &t->timerstop); + timevaladd(&t->timersub, &now); +} + +static void +timer_stop(struct seq_softc *t) +{ + t->timerrun = 0; + getmicrotime(&t->timerstop); +} + +static void +timer_setvals(struct seq_softc *t, int tempo, int timerbase) +{ + t->tempo = tempo; + t->timerbase = timerbase; +} + +static void +timer_wait(struct seq_softc *t, int ticks, int wait_abs) +{ + struct timeval now, when; + int ret; + unsigned long long i; + + while (t->timerrun == 0) { + SEQ_DEBUG(2, printf("Timer wait when timer isn't running\n")); + /* + * The old sequencer used timeouts that only increased + * the timer when the timer was running. + * Hence the sequencer would stick (?) if the + * timer was disabled. + */ + cv_wait(&t->reset_cv, &t->seq_lock); + if (t->playing == 0) + return; + } + + i = ticks * 60ull * 1000000ull / (t->tempo * t->timerbase); + + when.tv_sec = i / 1000000; + when.tv_usec = i % 1000000; + +#if 0 + printf("timer_wait tempo %d timerbase %d ticks %d abs %d u_sec %llu\n", + t->tempo, t->timerbase, ticks, wait_abs, i); +#endif + + if (wait_abs != 0) { + getmicrotime(&now); + timevalsub(&now, &t->timersub); + timevalsub(&when, &now); + } + if (when.tv_sec < 0 || when.tv_usec < 0) { + SEQ_DEBUG(3, + printf("seq_timer error negative time %lds.%06lds\n", + (long)when.tv_sec, (long)when.tv_usec)); + return; + } + i = when.tv_sec * 1000000ull; + i += when.tv_usec; + i *= hz; + i /= 1000000ull; +#if 0 + printf("seq_timer usec %llu ticks %llu\n", + when.tv_sec * 1000000ull + when.tv_usec, i); +#endif + t->waiting = 1; + ret = cv_timedwait(&t->reset_cv, &t->seq_lock, i + 1); + t->waiting = 0; + + if (ret != EWOULDBLOCK) + SEQ_DEBUG(3, printf("seq_timer didn't timeout\n")); + +} + +static int +timer_now(struct seq_softc *t) +{ + struct timeval now; + unsigned long long i; + int ret; + + if (t->timerrun == 0) + now = t->timerstop; + else + getmicrotime(&now); + + timevalsub(&now, &t->timersub); + + i = now.tv_sec * 1000000ull; + i += now.tv_usec; + i *= t->timerbase; +/* i /= t->tempo; */ + i /= 1000000ull; + + ret = i; + /* + * printf("timer_now: %llu %d\n", i, ret); + */ + + return ret; +} + +static void +seq_eventthread(void *arg) +{ + struct seq_softc *scp = arg; + char event[EV_SZ]; + + mtx_lock(&scp->seq_lock); + SEQ_DEBUG(2, printf("seq_eventthread started\n")); + while (scp->done == 0) { +restart: + while (scp->playing == 0) { + cv_wait(&scp->state_cv, &scp->seq_lock); + if (scp->done) + goto done; + } + + while (MIDIQ_EMPTY(scp->out_q)) { + cv_broadcast(&scp->empty_cv); + cv_wait(&scp->out_cv, &scp->seq_lock); + if (scp->playing == 0) + goto restart; + if (scp->done) + goto done; + } + + MIDIQ_DEQ(scp->out_q, event, EV_SZ); + + if (MIDIQ_AVAIL(scp->out_q) < scp->out_water) { + cv_broadcast(&scp->out_cv); + selwakeup(&scp->out_sel); + } + seq_processevent(scp, event); + } + +done: + cv_broadcast(&scp->th_cv); + mtx_unlock(&scp->seq_lock); + mtx_lock(&Giant); + SEQ_DEBUG(2, printf("seq_eventthread finished\n")); + kthread_exit(0); +} + +/* + * seq_processevent: This maybe called by the event thread or the IOCTL + * handler for queued and out of band events respectively. + */ +static int +seq_processevent(struct seq_softc *scp, u_char *event) +{ + int ret; + kobj_t m; + + ret = 0; + + if (event[0] == EV_SEQ_LOCAL) + ret = seq_local(scp, event); + else if (event[0] == EV_TIMING) + ret = seq_timing(scp, event); + else if (event[0] != EV_CHN_VOICE && + event[0] != EV_CHN_COMMON && + event[0] != EV_SYSEX && + event[0] != SEQ_MIDIPUTC) { + ret = 1; + SEQ_DEBUG(2, printf("seq_processevent not known %d\n", + event[0])); + } else if (seq_fetch_mid(scp, event[1], &m) != 0) { + ret = 1; + SEQ_DEBUG(2, printf("seq_processevent midi unit not found %d\n", + event[1])); + } else + switch (event[0]) { + case EV_CHN_VOICE: + ret = seq_chnvoice(scp, m, event); + break; + case EV_CHN_COMMON: + ret = seq_chncommon(scp, m, event); + break; + case EV_SYSEX: + ret = seq_sysex(scp, m, event); + break; + case SEQ_MIDIPUTC: + mtx_unlock(&scp->seq_lock); + ret = SYNTH_WRITERAW(m, &event[2], 1); + mtx_lock(&scp->seq_lock); + break; + } + return ret; +} + +static int +seq_addunit(void) +{ + struct seq_softc *scp; + int ret; + u_char *buf; + + /* Allocate the softc. */ + ret = ENOMEM; + scp = malloc(sizeof(*scp), M_DEVBUF, M_NOWAIT | M_ZERO); + if (scp == NULL) { + SEQ_DEBUG(1, printf("seq_addunit: softc allocation failed.\n")); + goto err; + } + kobj_init((kobj_t)scp, &sequencer_class); + + buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO); + if (buf == NULL) + goto err; + MIDIQ_INIT(scp->in_q, buf, EV_SZ * 1024); + buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO); + if (buf == NULL) + goto err; + MIDIQ_INIT(scp->out_q, buf, EV_SZ * 1024); + ret = EINVAL; + + scp->midis = malloc(sizeof(kobj_t) * 32, M_TEMP, M_NOWAIT | M_ZERO); + scp->midi_flags = malloc(sizeof(*scp->midi_flags) * 32, M_TEMP, + M_NOWAIT | M_ZERO); + + if (scp->midis == NULL || scp->midi_flags == NULL) + goto err; + + scp->flags = 0; + + mtx_init(&scp->seq_lock, "seqflq", NULL, 0); + cv_init(&scp->state_cv, "seqstate"); + cv_init(&scp->empty_cv, "seqempty"); + cv_init(&scp->reset_cv, "seqtimer"); + cv_init(&scp->out_cv, "seqqout"); + cv_init(&scp->in_cv, "seqqin"); + cv_init(&scp->th_cv, "seqstart"); + + /* + * Init the damn timer + */ + + scp->mapper = midimapper_addseq(scp, &scp->unit, &scp->mapper_cookie); + if (scp->mapper == NULL) + goto err; + + scp->seqdev = make_dev(&seq_cdevsw, + MIDIMKMINOR(scp->unit, SND_DEV_SEQ, 0), UID_ROOT, + GID_WHEEL, 0666, "sequencer%d", scp->unit); + + scp->musicdev = make_dev(&seq_cdevsw, + MIDIMKMINOR(scp->unit, SND_DEV_MUSIC, 0), UID_ROOT, + GID_WHEEL, 0666, "music%d", scp->unit); + + if (scp->seqdev == NULL || scp->musicdev == NULL) + goto err; + /* + * TODO: Add to list of sequencers this module provides + */ + + ret = kthread_create(seq_eventthread, scp, NULL, RFHIGHPID, 0, + "sequencer %02d", scp->unit); + + if (ret) + goto err; + + scp->seqdev->si_drv1 = scp->musicdev->si_drv1 = scp; + + SEQ_DEBUG(2, printf("sequencer %d created scp %p\n", scp->unit, scp)); + + ret = 0; + + mtx_lock(&seqinfo_mtx); + seqs[nseq++] = scp; + mtx_unlock(&seqinfo_mtx); + + goto ok; + +err: + if (scp != NULL) { + if (scp->seqdev != NULL) + destroy_dev(scp->seqdev); + if (scp->musicdev != NULL) + destroy_dev(scp->musicdev); + /* + * TODO: Destroy mutex and cv + */ + if (scp->midis != NULL) + free(scp->midis, M_TEMP); + if (scp->midi_flags != NULL) + free(scp->midi_flags, M_TEMP); + if (scp->out_q.b) + free(scp->out_q.b, M_TEMP); + if (scp->in_q.b) + free(scp->in_q.b, M_TEMP); + free(scp, M_DEVBUF); + } +ok: + return ret; +} + +static int +seq_delunit(int unit) +{ + struct seq_softc *scp = seqs[unit]; + int i; + + //SEQ_DEBUG(4, printf("seq_delunit: %d\n", unit)); + SEQ_DEBUG(1, printf("seq_delunit: 1 \n")); + mtx_lock(&scp->seq_lock); + + scp->playing = 0; + scp->done = 1; + cv_broadcast(&scp->out_cv); + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->reset_cv); + SEQ_DEBUG(1, printf("seq_delunit: 2 \n")); + cv_wait(&scp->th_cv, &scp->seq_lock); + SEQ_DEBUG(1, printf("seq_delunit: 3.0 \n")); + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(1, printf("seq_delunit: 3.1 \n")); + + cv_destroy(&scp->state_cv); + SEQ_DEBUG(1, printf("seq_delunit: 4 \n")); + cv_destroy(&scp->empty_cv); + SEQ_DEBUG(1, printf("seq_delunit: 5 \n")); + cv_destroy(&scp->reset_cv); + SEQ_DEBUG(1, printf("seq_delunit: 6 \n")); + cv_destroy(&scp->out_cv); + SEQ_DEBUG(1, printf("seq_delunit: 7 \n")); + cv_destroy(&scp->in_cv); + SEQ_DEBUG(1, printf("seq_delunit: 8 \n")); + cv_destroy(&scp->th_cv); + + SEQ_DEBUG(1, printf("seq_delunit: 10 \n")); + if (scp->seqdev) + destroy_dev(scp->seqdev); + SEQ_DEBUG(1, printf("seq_delunit: 11 \n")); + if (scp->musicdev) + destroy_dev(scp->musicdev); + SEQ_DEBUG(1, printf("seq_delunit: 12 \n")); + scp->seqdev = scp->musicdev = NULL; + if (scp->midis != NULL) + free(scp->midis, M_TEMP); + SEQ_DEBUG(1, printf("seq_delunit: 13 \n")); + if (scp->midi_flags != NULL) + free(scp->midi_flags, M_TEMP); + SEQ_DEBUG(1, printf("seq_delunit: 14 \n")); + free(scp->out_q.b, M_TEMP); + SEQ_DEBUG(1, printf("seq_delunit: 15 \n")); + free(scp->in_q.b, M_TEMP); + + SEQ_DEBUG(1, printf("seq_delunit: 16 \n")); + + mtx_destroy(&scp->seq_lock); + SEQ_DEBUG(1, printf("seq_delunit: 17 \n")); + free(scp, M_DEVBUF); + + mtx_lock(&seqinfo_mtx); + for (i = unit; i < (nseq - 1); i++) + seqs[i] = seqs[i + 1]; + nseq--; + mtx_unlock(&seqinfo_mtx); + + return 0; +} + +int +seq_modevent(module_t mod, int type, void *data) +{ + int retval, r; + + retval = 0; + + switch (type) { + case MOD_LOAD: + mtx_init(&seqinfo_mtx, "seqmod", NULL, 0); + retval = seq_addunit(); + break; + + case MOD_UNLOAD: + while (nseq) { + r = seq_delunit(nseq - 1); + if (r) { + retval = r; + break; + } + } + if (nseq == 0) { + retval = 0; + mtx_destroy(&seqinfo_mtx); + } + break; + + default: + break; + } + + return retval; +} + +static int +seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md) +{ + + if (unit > scp->midi_number || unit < 0) + return EINVAL; + + *md = scp->midis[unit]; + + return 0; +} + +int +seq_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + struct seq_softc *scp = i_dev->si_drv1; + int i; + + if (scp == NULL) + return ENXIO; + + SEQ_DEBUG(3, printf("seq_open: scp %p unit %d, flags 0x%x.\n", + scp, scp->unit, flags)); + + /* + * Mark this device busy. + */ + + mtx_lock(&scp->seq_lock); + if (scp->busy) { + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(2, printf("seq_open: unit %d is busy.\n", scp->unit)); + return EBUSY; + } + scp->fflags = flags; + /* + if ((scp->fflags & O_NONBLOCK) != 0) + scp->flags |= SEQ_F_NBIO; + */ + scp->music = MIDIDEV(i_dev) == SND_DEV_MUSIC; + + /* + * Enumerate the available midi devices + */ + scp->midi_number = 0; + scp->maxunits = midimapper_open(scp->mapper, &scp->mapper_cookie); + + if (scp->maxunits == 0) + SEQ_DEBUG(2, printf("seq_open: no midi devices\n")); + + for (i = 0; i < scp->maxunits; i++) { + scp->midis[scp->midi_number] = + midimapper_fetch_synth(scp->mapper, scp->mapper_cookie, i); + if (scp->midis[scp->midi_number]) { + if (SYNTH_OPEN(scp->midis[scp->midi_number], scp, + scp->fflags) != 0) + scp->midis[scp->midi_number] = NULL; + else { + scp->midi_flags[scp->midi_number] = + SYNTH_QUERY(scp->midis[scp->midi_number]); + scp->midi_number++; + } + } + } + + timer_setvals(scp, 60, 100); + + timer_start(scp); + timer_stop(scp); + /* + * actually, if we're in rdonly mode, we should start the timer + */ + /* + * TODO: Handle recording now + */ + + scp->out_water = MIDIQ_SIZE(scp->out_q) / 2; + + scp->busy = 1; + mtx_unlock(&scp->seq_lock); + + SEQ_DEBUG(2, printf("seq_open: opened, mode %s.\n", + scp->music ? "music" : "sequencer")); + SEQ_DEBUG(2, + printf("Sequencer %d %p opened maxunits %d midi_number %d:\n", + scp->unit, scp, scp->maxunits, scp->midi_number)); + for (i = 0; i < scp->midi_number; i++) + SEQ_DEBUG(3, printf(" midi %d %p\n", i, scp->midis[i])); + + return 0; +} + +/* + * seq_close + */ +int +seq_close(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + int i; + struct seq_softc *scp = i_dev->si_drv1; + int ret; + + if (scp == NULL) + return ENXIO; + + SEQ_DEBUG(2, printf("seq_close: unit %d.\n", scp->unit)); + + mtx_lock(&scp->seq_lock); + + ret = ENXIO; + if (scp->busy == 0) + goto err; + + seq_reset(scp); + seq_sync(scp); + + for (i = 0; i < scp->midi_number; i++) + if (scp->midis[i]) + SYNTH_CLOSE(scp->midis[i]); + + midimapper_close(scp->mapper, scp->mapper_cookie); + + timer_stop(scp); + + scp->busy = 0; + ret = 0; + +err: + SEQ_DEBUG(3, printf("seq_close: closed ret = %d.\n", ret)); + mtx_unlock(&scp->seq_lock); + return ret; +} + +int +seq_read(struct cdev *i_dev, struct uio *uio, int ioflag) +{ + int retval, used; + struct seq_softc *scp = i_dev->si_drv1; + +#define SEQ_RSIZE 32 + u_char buf[SEQ_RSIZE]; + + if (scp == NULL) + return ENXIO; + + SEQ_DEBUG(7, printf("seq_read: unit %d, resid %d.\n", + scp->unit, uio->uio_resid)); + + mtx_lock(&scp->seq_lock); + if ((scp->fflags & FREAD) == 0) { + SEQ_DEBUG(2, printf("seq_read: unit %d is not for reading.\n", + scp->unit)); + retval = EIO; + goto err1; + } + /* + * Begin recording. + */ + /* + * if ((scp->flags & SEQ_F_READING) == 0) + */ + /* + * TODO, start recording if not alread + */ + + /* + * I think the semantics are to return as soon + * as possible. + * Second thought, it doens't seem like midimoutain + * expects that at all. + * TODO: Look up in some sort of spec + */ + + while (uio->uio_resid > 0) { + while (MIDIQ_EMPTY(scp->in_q)) { + retval = EWOULDBLOCK; + /* + * I wish I knew which one to care about + */ + + if (scp->fflags & O_NONBLOCK) + goto err1; + if (ioflag & O_NONBLOCK) + goto err1; + + retval = cv_wait_sig(&scp->in_cv, &scp->seq_lock); + if (retval == EINTR) + goto err1; + } + + used = MIN(MIDIQ_LEN(scp->in_q), uio->uio_resid); + used = MIN(used, SEQ_RSIZE); + + SEQ_DEBUG(8, printf("midiread: uiomove cc=%d\n", used)); + MIDIQ_DEQ(scp->in_q, buf, used); + retval = uiomove(buf, used, uio); + if (retval) + goto err1; + } + + retval = 0; +err1: + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(6, printf("seq_read: ret %d, resid %d.\n", + retval, uio->uio_resid)); + + return retval; +} + +int +seq_write(struct cdev *i_dev, struct uio *uio, int ioflag) +{ + u_char event[EV_SZ], newevent[EV_SZ], ev_code; + struct seq_softc *scp = i_dev->si_drv1; + int retval; + int used; + + SEQ_DEBUG(7, printf("seq_write: unit %d, resid %d.\n", + scp->unit, uio->uio_resid)); + + if (scp == NULL) + return ENXIO; + + mtx_lock(&scp->seq_lock); + + if ((scp->fflags & FWRITE) == 0) { + SEQ_DEBUG(2, printf("seq_write: unit %d is not for writing.\n", + scp->unit)); + retval = EIO; + goto err0; + } + while (uio->uio_resid > 0) { + while (MIDIQ_AVAIL(scp->out_q) == 0) { + retval = EWOULDBLOCK; + if (scp->fflags & O_NONBLOCK) + goto err0; + if (ioflag & O_NONBLOCK) + goto err0; + SEQ_DEBUG(8, printf("seq_write cvwait\n")); + + scp->playing = 1; + cv_broadcast(&scp->out_cv); + cv_broadcast(&scp->state_cv); + + retval = cv_wait_sig(&scp->out_cv, &scp->seq_lock); + /* + * We slept, maybe things have changed since last + * dying check + */ + if (retval == EINTR) + goto err0; +#if 0 + /* + * Useless test + */ + if (scp != i_dev->si_drv1) + retval = ENXIO; +#endif + } + + used = MIN(uio->uio_resid, 4); + + SEQ_DEBUG(8, printf("seqout: resid %d len %jd avail %jd\n", + uio->uio_resid, (intmax_t)MIDIQ_LEN(scp->out_q), + (intmax_t)MIDIQ_AVAIL(scp->out_q))); + + if (used != 4) { + retval = ENXIO; + goto err0; + } + retval = uiomove(event, used, uio); + if (retval) + goto err0; + + ev_code = event[0]; + SEQ_DEBUG(8, printf("seq_write: unit %d, event %s.\n", + scp->unit, midi_cmdname(ev_code, cmdtab_seqevent))); + + /* Have a look at the event code. */ + if (ev_code == SEQ_FULLSIZE) { + + /* + * TODO: restore code for SEQ_FULLSIZE + */ +#if 0 + /* + * A long event, these are the patches/samples for a + * synthesizer. + */ + midiunit = *(u_short *)&event[2]; + mtx_lock(&sd->seq_lock); + ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md); + mtx_unlock(&sd->seq_lock); + if (ret != 0) + return (ret); + + SEQ_DEBUG(printf("seq_write: loading a patch to the unit %d.\n", midiunit)); + + ret = md->synth.loadpatch(md, *(short *)&event[0], buf, + p + 4, count, 0); + return (ret); +#else + /* + * For now, just flush the darn buffer + */ + SEQ_DEBUG(2, + printf("seq_write: SEQ_FULLSIZE flusing buffer.\n")); + while (uio->uio_resid > 0) { + retval = uiomove(event, EV_SZ, uio); + if (retval) + goto err0; + + } + retval = 0; + goto err0; +#endif + } + retval = EINVAL; + if (ev_code >= 128) { + + /* + * Some sort of an extended event. The size is eight + * bytes. scoop extra info. + */ + if (scp->music && ev_code == SEQ_EXTENDED) { + SEQ_DEBUG(2, printf("seq_write: invalid level two event %x.\n", ev_code)); + goto err0; + } + if (uiomove((caddr_t)&event[4], 4, uio)) { + SEQ_DEBUG(2, + printf("seq_write: user memory mangled?\n")); + goto err0; + } + } else { + /* + * Size four event. + */ + if (scp->music) { + SEQ_DEBUG(2, printf("seq_write: four byte event in music mode.\n")); + goto err0; + } + } + if (ev_code == SEQ_MIDIPUTC) { + /* + * TODO: event[2] is unit number to receive char. + * Range check it. + */ + } + if (scp->music) { +#ifdef not_ever_ever + if (event[0] == EV_TIMING && + (event[1] == TMR_START || event[1] == TMR_STOP)) { + /* + * For now, try to make midimoutain work by + * forcing these events to be processed + * immediatly. + */ + seq_processevent(scp, event); + } else + MIDIQ_ENQ(scp->out_q, event, EV_SZ); +#else + MIDIQ_ENQ(scp->out_q, event, EV_SZ); +#endif + } else { + if (seq_convertold(event, newevent) > 0) + MIDIQ_ENQ(scp->out_q, newevent, EV_SZ); +#if 0 + else + goto err0; +#endif + } + + } + + scp->playing = 1; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + + retval = 0; + +err0: + SEQ_DEBUG(6, + printf("seq_write done: leftover buffer length %d retval %d\n", + uio->uio_resid, retval)); + mtx_unlock(&scp->seq_lock); + return retval; +} + +int +seq_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + int midiunit, ret, tmp; + struct seq_softc *scp = i_dev->si_drv1; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + u_char event[EV_SZ]; + u_char newevent[EV_SZ]; + + kobj_t md; + + /* + * struct snd_size *sndsize; + */ + + if (scp == NULL) + return ENXIO; + + SEQ_DEBUG(6, printf("seq_ioctl: unit %d, cmd %s.\n", + scp->unit, midi_cmdname(cmd, cmdtab_seqioctl))); + + ret = 0; + + switch (cmd) { + case SNDCTL_SEQ_GETTIME: + /* + * ioctl needed by libtse + */ + mtx_lock(&scp->seq_lock); + *(int *)arg = timer_now(scp); + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(6, printf("seq_ioctl: gettime %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_TMR_METRONOME: + /* fallthrough */ + case SNDCTL_TMR_SOURCE: + /* + * Not implemented + */ + ret = 0; + break; + case SNDCTL_TMR_TEMPO: + event[1] = TMR_TEMPO; + event[4] = *(int *)arg & 0xFF; + event[5] = (*(int *)arg >> 8) & 0xFF; + event[6] = (*(int *)arg >> 16) & 0xFF; + event[7] = (*(int *)arg >> 24) & 0xFF; + goto timerevent; + case SNDCTL_TMR_TIMEBASE: + event[1] = TMR_TIMERBASE; + event[4] = *(int *)arg & 0xFF; + event[5] = (*(int *)arg >> 8) & 0xFF; + event[6] = (*(int *)arg >> 16) & 0xFF; + event[7] = (*(int *)arg >> 24) & 0xFF; + goto timerevent; + case SNDCTL_TMR_START: + event[1] = TMR_START; + goto timerevent; + case SNDCTL_TMR_STOP: + event[1] = TMR_STOP; + goto timerevent; + case SNDCTL_TMR_CONTINUE: + event[1] = TMR_CONTINUE; +timerevent: + event[0] = EV_TIMING; + mtx_lock(&scp->seq_lock); + if (!scp->music) { + ret = EINVAL; + mtx_unlock(&scp->seq_lock); + break; + } + seq_processevent(scp, event); + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_TMR_SELECT: + SEQ_DEBUG(2, + printf("seq_ioctl: SNDCTL_TMR_SELECT not supported\n")); + ret = EINVAL; + break; + case SNDCTL_SEQ_SYNC: + if (mode == O_RDONLY) { + ret = 0; + break; + } + mtx_lock(&scp->seq_lock); + ret = seq_sync(scp); + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_SEQ_PANIC: + /* fallthrough */ + case SNDCTL_SEQ_RESET: + /* + * SNDCTL_SEQ_PANIC == SNDCTL_SEQ_RESET + */ + mtx_lock(&scp->seq_lock); + seq_reset(scp); + mtx_unlock(&scp->seq_lock); + ret = 0; + break; + case SNDCTL_SEQ_TESTMIDI: + mtx_lock(&scp->seq_lock); + /* + * TODO: SNDCTL_SEQ_TESTMIDI now means "can I write to the + * device?". + */ + mtx_unlock(&scp->seq_lock); + break; +#if 0 + case SNDCTL_SEQ_GETINCOUNT: + if (mode == O_WRONLY) + *(int *)arg = 0; + else { + mtx_lock(&scp->seq_lock); + *(int *)arg = scp->in_q.rl; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(printf("seq_ioctl: incount %d.\n", + *(int *)arg)); + } + ret = 0; + break; + case SNDCTL_SEQ_GETOUTCOUNT: + if (mode == O_RDONLY) + *(int *)arg = 0; + else { + mtx_lock(&scp->seq_lock); + *(int *)arg = scp->out_q.fl; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(printf("seq_ioctl: outcount %d.\n", + *(int *)arg)); + } + ret = 0; + break; +#endif + case SNDCTL_SEQ_CTRLRATE: + if (*(int *)arg != 0) { + ret = EINVAL; + break; + } + mtx_lock(&scp->seq_lock); + *(int *)arg = scp->timerbase; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: ctrlrate %d.\n", *(int *)arg)); + ret = 0; + break; + /* + * TODO: ioctl SNDCTL_SEQ_RESETSAMPLES + */ +#if 0 + case SNDCTL_SEQ_RESETSAMPLES: + mtx_lock(&scp->seq_lock); + ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); + mtx_unlock(&scp->seq_lock); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, + SND_DEV_MIDIN), cmd, arg, mode, td); + break; +#endif + case SNDCTL_SEQ_NRSYNTHS: + mtx_lock(&scp->seq_lock); + *(int *)arg = scp->midi_number; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: synths %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_SEQ_NRMIDIS: + mtx_lock(&scp->seq_lock); + if (scp->music) + *(int *)arg = 0; + else { + /* + * TODO: count the numbder of devices that can WRITERAW + */ + *(int *)arg = scp->midi_number; + } + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: midis %d.\n", *(int *)arg)); + ret = 0; + break; + /* + * TODO: ioctl SNDCTL_SYNTH_MEMAVL + */ +#if 0 + case SNDCTL_SYNTH_MEMAVL: + mtx_lock(&scp->seq_lock); + ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); + mtx_unlock(&scp->seq_lock); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, + SND_DEV_MIDIN), cmd, arg, mode, td); + break; +#endif + case SNDCTL_SEQ_OUTOFBAND: + for (ret = 0; ret < EV_SZ; ret++) + event[ret] = (u_char)arg[0]; + + mtx_lock(&scp->seq_lock); + if (scp->music) + ret = seq_processevent(scp, event); + else { + if (seq_convertold(event, newevent) > 0) + ret = seq_processevent(scp, newevent); + else + ret = EINVAL; + } + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + midiunit = synthinfo->device; + mtx_lock(&scp->seq_lock); + if (seq_fetch_mid(scp, midiunit, &md) == 0) { + bzero(synthinfo, sizeof(*synthinfo)); + synthinfo->name[0] = 'f'; + synthinfo->name[1] = 'a'; + synthinfo->name[2] = 'k'; + synthinfo->name[3] = 'e'; + synthinfo->name[4] = 's'; + synthinfo->name[5] = 'y'; + synthinfo->name[6] = 'n'; + synthinfo->name[7] = 't'; + synthinfo->name[8] = 'h'; + synthinfo->device = midiunit; + synthinfo->synth_type = SYNTH_TYPE_MIDI; + synthinfo->capabilities = scp->midi_flags[midiunit]; + ret = 0; + } else + ret = EINVAL; + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + midiunit = midiinfo->device; + mtx_lock(&scp->seq_lock); + if (seq_fetch_mid(scp, midiunit, &md) == 0) { + bzero(midiinfo, sizeof(*midiinfo)); + midiinfo->name[0] = 'f'; + midiinfo->name[1] = 'a'; + midiinfo->name[2] = 'k'; + midiinfo->name[3] = 'e'; + midiinfo->name[4] = 'm'; + midiinfo->name[5] = 'i'; + midiinfo->name[6] = 'd'; + midiinfo->name[7] = 'i'; + midiinfo->device = midiunit; + midiinfo->capabilities = scp->midi_flags[midiunit]; + /* + * TODO: What devtype? + */ + midiinfo->dev_type = 0x01; + ret = 0; + } else + ret = EINVAL; + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_SEQ_THRESHOLD: + mtx_lock(&scp->seq_lock); + RANGE(*(int *)arg, 1, MIDIQ_SIZE(scp->out_q) - 1); + scp->out_water = *(int *)arg; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: water %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_MIDI_PRETIME: + tmp = *(int *)arg; + if (tmp < 0) + tmp = 0; + mtx_lock(&scp->seq_lock); + scp->pre_event_timeout = (hz * tmp) / 10; + *(int *)arg = scp->pre_event_timeout; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: pretime %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_FM_4OP_ENABLE: + case SNDCTL_PMGR_IFACE: + case SNDCTL_PMGR_ACCESS: + /* + * Patch manager and fm are ded, ded, ded. + */ + /* fallthrough */ + default: + /* + * TODO: Consider ioctl default case. + * Old code used to + * if ((scp->fflags & O_ACCMODE) == FREAD) { + * ret = EIO; + * break; + * } + * Then pass on the ioctl to device 0 + */ + SEQ_DEBUG(2, + printf("seq_ioctl: unsupported IOCTL %ld.\n", cmd)); + ret = EINVAL; + break; + } + + return ret; +} + +int +seq_poll(struct cdev *i_dev, int events, struct thread *td) +{ + int ret, lim; + struct seq_softc *scp = i_dev->si_drv1; + + SEQ_DEBUG(3, printf("seq_poll: unit %d.\n", scp->unit)); + SEQ_DEBUG(1, printf("seq_poll: unit %d.\n", scp->unit)); + + mtx_lock(&scp->seq_lock); + + ret = 0; + + /* Look up the apropriate queue and select it. */ + if ((events & (POLLOUT | POLLWRNORM)) != 0) { + /* Start playing. */ + scp->playing = 1; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + + lim = scp->out_water; + + if (MIDIQ_AVAIL(scp->out_q) < lim) + /* No enough space, record select. */ + selrecord(td, &scp->out_sel); + else + /* We can write now. */ + ret |= events & (POLLOUT | POLLWRNORM); + } + if ((events & (POLLIN | POLLRDNORM)) != 0) { + /* TODO: Start recording. */ + + /* Find out the boundary. */ + lim = 1; + if (MIDIQ_LEN(scp->in_q) < lim) + /* No data ready, record select. */ + selrecord(td, &scp->in_sel); + else + /* We can read now. */ + ret |= events & (POLLIN | POLLRDNORM); + } + mtx_unlock(&scp->seq_lock); + + return (ret); +} + +#if 0 +static void +sein_qtr(void *p, void /* mididev_info */ *md) +{ + struct seq_softc *scp; + + scp = (struct seq_softc *)p; + + mtx_lock(&scp->seq_lock); + + /* Restart playing if we have the data to output. */ + if (scp->queueout_pending) + seq_callback(scp, SEQ_CB_START | SEQ_CB_WR); + /* Check the midi device if we are reading. */ + if ((scp->flags & SEQ_F_READING) != 0) + seq_midiinput(scp, md); + + mtx_unlock(&scp->seq_lock); +} + +#endif +/* + * seq_convertold + * Was the old playevent. Use this to convert and old + * style /dev/sequencer event to a /dev/music event + */ +static int +seq_convertold(u_char *event, u_char *out) +{ + int used; + u_char dev, chn, note, vel; + + out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] = + out[7] = 0; + + dev = 0; + chn = event[1]; + note = event[2]; + vel = event[3]; + + used = 0; + +restart: + /* + * TODO: Debug statement + */ + switch (event[0]) { + case EV_TIMING: + case EV_CHN_VOICE: + case EV_CHN_COMMON: + case EV_SYSEX: + case EV_SEQ_LOCAL: + out[0] = event[0]; + out[1] = event[1]; + out[2] = event[2]; + out[3] = event[3]; + out[4] = event[4]; + out[5] = event[5]; + out[6] = event[6]; + out[7] = event[7]; + used += 8; + break; + case SEQ_NOTEOFF: + out[0] = EV_CHN_VOICE; + out[1] = dev; + out[2] = MIDI_NOTEOFF; + out[3] = chn; + out[4] = note; + out[5] = 255; + used += 4; + break; + + case SEQ_NOTEON: + out[0] = EV_CHN_VOICE; + out[1] = dev; + out[2] = MIDI_NOTEON; + out[3] = chn; + out[4] = note; + out[5] = vel; + used += 4; + break; + + /* + * wait delay = (event[2] << 16) + (event[3] << 8) + event[4] + */ + + case SEQ_PGMCHANGE: + out[0] = EV_CHN_COMMON; + out[1] = dev; + out[2] = MIDI_PGM_CHANGE; + out[3] = chn; + out[4] = note; + out[5] = vel; + used += 4; + break; +/* + out[0] = EV_TIMING; + out[1] = dev; + out[2] = MIDI_PGM_CHANGE; + out[3] = chn; + out[4] = note; + out[5] = vel; + SEQ_DEBUG(4,printf("seq_playevent: synctimer\n")); + break; +*/ + + case SEQ_MIDIPUTC: + SEQ_DEBUG(4, + printf("seq_playevent: put data 0x%02x, unit %d.\n", + event[1], event[2])); + /* + * Pass through to the midi device. + * device = event[2] + * data = event[1] + */ + out[0] = SEQ_MIDIPUTC; + out[1] = dev; + out[2] = chn; + used += 4; + break; +#ifdef notyet + case SEQ_ECHO: + /* + * This isn't handled here yet because I don't know if I can + * just use four bytes events. There might be consequences + * in the _read routing + */ + if (seq_copytoinput(scp, event, 4) == EAGAIN) { + ret = QUEUEFULL; + break; + } + ret = MORE; + break; +#endif + case SEQ_EXTENDED: + switch (event[1]) { + case SEQ_NOTEOFF: + case SEQ_NOTEON: + case SEQ_PGMCHANGE: + event++; + used = 4; + goto restart; + break; + case SEQ_AFTERTOUCH: + /* + * SYNTH_AFTERTOUCH(md, event[3], event[4]) + */ + case SEQ_BALANCE: + /* + * SYNTH_PANNING(md, event[3], (char)event[4]) + */ + case SEQ_CONTROLLER: + /* + * SYNTH_CONTROLLER(md, event[3], event[4], *(short *)&event[5]) + */ + case SEQ_VOLMODE: + /* + * SYNTH_VOLUMEMETHOD(md, event[3]) + */ + default: + SEQ_DEBUG(2, + printf("seq_convertold: SEQ_EXTENDED type %d" + "not handled\n", event[1])); + break; + } + break; + case SEQ_WAIT: + out[0] = EV_TIMING; + out[1] = TMR_WAIT_REL; + out[4] = event[2]; + out[5] = event[3]; + out[6] = event[4]; + + SEQ_DEBUG(5, printf("SEQ_WAIT %d", + event[2] + (event[3] << 8) + (event[4] << 24))); + + used += 4; + break; + + case SEQ_ECHO: + case SEQ_SYNCTIMER: + case SEQ_PRIVATE: + default: + SEQ_DEBUG(2, + printf("seq_convertold: event type %d not handled %d %d %d\n", + event[0], event[1], event[2], event[3])); + break; + } + return used; +} + +/* + * Writting to the sequencer buffer never blocks and drops + * input which cannot be queued + */ +void +seq_copytoinput(struct seq_softc *scp, u_char *event, int len) +{ + + mtx_assert(&scp->seq_lock, MA_OWNED); + + if (MIDIQ_AVAIL(scp->in_q) < len) { + /* + * ENOROOM? EINPUTDROPPED? ETOUGHLUCK? + */ + SEQ_DEBUG(2, printf("seq_copytoinput: queue full\n")); + } else { + MIDIQ_ENQ(scp->in_q, event, len); + selwakeup(&scp->in_sel); + cv_broadcast(&scp->in_cv); + } + +} + +static int +seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event) +{ + int ret, voice; + u_char cmd, chn, note, parm; + + ret = 0; + cmd = event[2]; + chn = event[3]; + note = event[4]; + parm = event[5]; + + mtx_assert(&scp->seq_lock, MA_OWNED); + + SEQ_DEBUG(5, printf("seq_chnvoice: unit %d, dev %d, cmd %s," + " chn %d, note %d, parm %d.\n", scp->unit, event[1], + midi_cmdname(cmd, cmdtab_seqcv), chn, note, parm)); + + voice = SYNTH_ALLOC(md, chn, note); + + mtx_unlock(&scp->seq_lock); + + switch (cmd) { + case MIDI_NOTEON: + if (note < 128 || note == 255) { +#if 0 + if (scp->music && chn == 9) { + /* + * This channel is a percussion. The note + * number is the patch number. + */ + /* + mtx_unlock(&scp->seq_lock); + if (SYNTH_SETINSTR(md, voice, 128 + note) + == EAGAIN) { + mtx_lock(&scp->seq_lock); + return (QUEUEFULL); + } + mtx_lock(&scp->seq_lock); + */ + note = 60; /* Middle C. */ + } +#endif + if (scp->music) { + /* + mtx_unlock(&scp->seq_lock); + if (SYNTH_SETUPVOICE(md, voice, chn) + == EAGAIN) { + mtx_lock(&scp->seq_lock); + return (QUEUEFULL); + } + mtx_lock(&scp->seq_lock); + */ + } + SYNTH_STARTNOTE(md, voice, note, parm); + } + break; + case MIDI_NOTEOFF: + SYNTH_KILLNOTE(md, voice, note, parm); + break; + case MIDI_KEY_PRESSURE: + SYNTH_AFTERTOUCH(md, voice, parm); + break; + default: + ret = 1; + SEQ_DEBUG(2, printf("seq_chnvoice event type %d not handled\n", + event[1])); + break; + } + + mtx_lock(&scp->seq_lock); + return ret; +} + +static int +seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event) +{ + int ret; + u_short w14; + u_char cmd, chn, p1; + + ret = 0; + cmd = event[2]; + chn = event[3]; + p1 = event[4]; + w14 = *(u_short *)&event[6]; + + SEQ_DEBUG(5, printf("seq_chncommon: unit %d, dev %d, cmd %s, chn %d," + " p1 %d, w14 %d.\n", scp->unit, event[1], + midi_cmdname(cmd, cmdtab_seqccmn), chn, p1, w14)); + mtx_unlock(&scp->seq_lock); + switch (cmd) { + case MIDI_PGM_CHANGE: + SEQ_DEBUG(4, printf("seq_chncommon pgmchn chn %d pg %d\n", + chn, p1)); + SYNTH_SETINSTR(md, chn, p1); + break; + case MIDI_CTL_CHANGE: + SEQ_DEBUG(4, printf("seq_chncommon ctlch chn %d pg %d %d\n", + chn, p1, w14)); + SYNTH_CONTROLLER(md, chn, p1, w14); + break; + case MIDI_PITCH_BEND: + if (scp->music) { + /* + * TODO: MIDI_PITCH_BEND + */ +#if 0 + mtx_lock(&md->synth.vc_mtx); + md->synth.chn_info[chn].bender_value = w14; + if (md->midiunit >= 0) { + /* + * Handle all of the notes playing on this + * channel. + */ + key = ((int)chn << 8); + for (i = 0; i < md->synth.alloc.max_voice; i++) + if ((md->synth.alloc.map[i] & 0xff00) == key) { + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&scp->seq_lock); + if (md->synth.bender(md, i, w14) == EAGAIN) { + mtx_lock(&scp->seq_lock); + return (QUEUEFULL); + } + mtx_lock(&scp->seq_lock); + } + } else { + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&scp->seq_lock); + if (md->synth.bender(md, chn, w14) == EAGAIN) { + mtx_lock(&scp->seq_lock); + return (QUEUEFULL); + } + mtx_lock(&scp->seq_lock); + } +#endif + } else + SYNTH_BENDER(md, chn, w14); + break; + default: + ret = 1; + SEQ_DEBUG(2, + printf("seq_chncommon event type %d not handled.\n", + event[1])); + break; + + } + mtx_lock(&scp->seq_lock); + return ret; +} + +static int +seq_timing(struct seq_softc *scp, u_char *event) +{ + int param; + int ret; + + ret = 0; + param = event[4] + (event[5] << 8) + + (event[6] << 16) + (event[7] << 24); + + SEQ_DEBUG(5, printf("seq_timing: unit %d, cmd %d, param %d.\n", + scp->unit, event[1], param)); + switch (event[1]) { + case TMR_WAIT_REL: + timer_wait(scp, param, 0); + break; + case TMR_WAIT_ABS: + timer_wait(scp, param, 1); + break; + case TMR_START: + timer_start(scp); + cv_broadcast(&scp->reset_cv); + break; + case TMR_STOP: + timer_stop(scp); + /* + * The following cv_broadcast isn't needed since we only + * wait for 0->1 transitions. It probably won't hurt + */ + cv_broadcast(&scp->reset_cv); + break; + case TMR_CONTINUE: + timer_continue(scp); + cv_broadcast(&scp->reset_cv); + break; + case TMR_TEMPO: + if (param < 8) + param = 8; + if (param > 360) + param = 360; + SEQ_DEBUG(4, printf("Timer set tempo %d\n", param)); + timer_setvals(scp, param, scp->timerbase); + break; + case TMR_TIMERBASE: + if (param < 1) + param = 1; + if (param > 1000) + param = 1000; + SEQ_DEBUG(4, printf("Timer set timerbase %d\n", param)); + timer_setvals(scp, scp->tempo, param); + break; + case TMR_ECHO: + /* + * TODO: Consider making 4-byte events for /dev/sequencer + * PRO: Maybe needed by legacy apps + * CON: soundcard.h has been warning for a while many years + * to expect 8 byte events. + */ +#if 0 + if (scp->music) + seq_copytoinput(scp, event, 8); + else { + param = (param << 8 | SEQ_ECHO); + seq_copytoinput(scp, (u_char *)¶m, 4); + } +#else + seq_copytoinput(scp, event, 8); +#endif + break; + default: + SEQ_DEBUG(2, printf("seq_timing event type %d not handled.\n", + event[1])); + ret = 1; + break; + } + return ret; +} + +static int +seq_local(struct seq_softc *scp, u_char *event) +{ + int ret; + + ret = 0; + mtx_assert(&scp->seq_lock, MA_OWNED); + + SEQ_DEBUG(5, printf("seq_local: unit %d, cmd %d\n", scp->unit, + event[1])); + switch (event[1]) { + default: + SEQ_DEBUG(1, printf("seq_local event type %d not handled\n", + event[1])); + ret = 1; + break; + } + return ret; +} + +static int +seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event) +{ + int i, l; + + mtx_assert(&scp->seq_lock, MA_OWNED); + SEQ_DEBUG(5, printf("seq_sysex: unit %d device %d\n", scp->unit, + event[1])); + l = 0; + for (i = 0; i < 6 && event[i + 2] != 0xff; i++) + l = i + 1; + if (l > 0) { + mtx_unlock(&scp->seq_lock); + if (SYNTH_SENDSYSEX(md, &event[2], l) == EAGAIN) { + mtx_lock(&scp->seq_lock); + return 1; + } + mtx_lock(&scp->seq_lock); + } + return 0; +} + +/* + * Reset no longer closes the raw devices nor seq_sync's + * Callers are IOCTL and seq_close + */ +static void +seq_reset(struct seq_softc *scp) +{ + int chn, i; + kobj_t m; + + mtx_assert(&scp->seq_lock, MA_OWNED); + + SEQ_DEBUG(5, printf("seq_reset: unit %d.\n", scp->unit)); + + /* + * Stop reading and writing. + */ + + /* scp->recording = 0; */ + scp->playing = 0; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + cv_broadcast(&scp->reset_cv); + + /* + * For now, don't reset the timers. + */ + MIDIQ_CLEAR(scp->in_q); + MIDIQ_CLEAR(scp->out_q); + + for (i = 0; i < scp->midi_number; i++) { + m = scp->midis[i]; + mtx_unlock(&scp->seq_lock); + SYNTH_RESET(m); + for (chn = 0; chn < 16; chn++) { + SYNTH_CONTROLLER(m, chn, 123, 0); + SYNTH_CONTROLLER(m, chn, 121, 0); + SYNTH_BENDER(m, chn, 1 << 13); + } + mtx_lock(&scp->seq_lock); + } +} + +/* + * seq_sync + * *really* flush the output queue + * flush the event queue, then flush the synthsisers. + * Callers are IOCTL and close + */ + +#define SEQ_SYNC_TIMEOUT 8 +static int +seq_sync(struct seq_softc *scp) +{ + int i, rl, sync[16], done; + + mtx_assert(&scp->seq_lock, MA_OWNED); + + SEQ_DEBUG(4, printf("seq_sync: unit %d.\n", scp->unit)); + + /* + * Wait until output queue is empty. Check every so often to see if + * the queue is moving along. If it isn't just abort. + */ + while (!MIDIQ_EMPTY(scp->out_q)) { + + if (!scp->playing) { + scp->playing = 1; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + } + rl = MIDIQ_LEN(scp->out_q); + + i = cv_timedwait_sig(&scp->out_cv, + &scp->seq_lock, SEQ_SYNC_TIMEOUT * hz); + + if (i == EINTR || i == ERESTART) { + if (i == EINTR) { + /* + * XXX: I don't know why we stop playing + */ + scp->playing = 0; + cv_broadcast(&scp->out_cv); + } + return i; + } + if (i == EWOULDBLOCK && rl == MIDIQ_LEN(scp->out_q) && + scp->waiting == 0) { + /* + * A queue seems to be stuck up. Give up and clear + * queues. + */ + MIDIQ_CLEAR(scp->out_q); + scp->playing = 0; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + cv_broadcast(&scp->reset_cv); + + /* + * TODO: Consider if the raw devices need to be flushed + */ + + SEQ_DEBUG(1, printf("seq_sync queue stuck, aborting\n")); + + return i; + } + } + + scp->playing = 0; + /* + * Since syncing a midi device might block, unlock scp->seq_lock. + */ + + mtx_unlock(&scp->seq_lock); + for (i = 0; i < scp->midi_number; i++) + sync[i] = 1; + + do { + done = 1; + for (i = 0; i < scp->midi_number; i++) + if (sync[i]) { + if (SYNTH_INSYNC(scp->midis[i]) == 0) + sync[i] = 0; + else + done = 0; + } + if (!done) + DELAY(5000); + + } while (!done); + + mtx_lock(&scp->seq_lock); + return 0; +} + +char * +midi_cmdname(int cmd, midi_cmdtab *tab) +{ + while (tab->name != NULL) { + if (cmd == tab->cmd) + return (tab->name); + tab++; + } + + return ("unknown"); +} --- sys/dev/sound/midi/sequencer.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/sequencer.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,90 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * Copyright (c) 1999 Seigo Tanimura + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/midi/sequencer.h,v 1.9 2007/02/25 13:51:52 netchild Exp $ + */ + +/* + * Include file for the midi sequence driver. + */ + +#ifndef _SEQUENCER_H_ +#define _SEQUENCER_H_ + + +#define NSEQ_MAX 16 + +/* + * many variables should be reduced to a range. Here define a macro + */ + +#define RANGE(var, low, high) (var) = \ +((var)<(low)?(low) : (var)>(high)?(high) : (var)) + +#ifdef _KERNEL + +void seq_timer(void *arg); + +SYSCTL_DECL(_hw_midi_seq); + +extern int seq_debug; + +#define SEQ_DEBUG(y, x) \ + do { \ + if (seq_debug >= y) { \ + (x); \ + } \ + } while(0) + +SYSCTL_DECL(_hw_midi); + +#endif /* _KERNEL */ + +#define SYNTHPROP_MIDI 1 +#define SYNTHPROP_SYNTH 2 +#define SYNTHPROP_RX 4 +#define SYNTHPROP_TX 8 + +struct _midi_cmdtab { + int cmd; + char *name; +}; +typedef struct _midi_cmdtab midi_cmdtab; +extern midi_cmdtab cmdtab_seqevent[]; +extern midi_cmdtab cmdtab_seqioctl[]; +extern midi_cmdtab cmdtab_timer[]; +extern midi_cmdtab cmdtab_seqcv[]; +extern midi_cmdtab cmdtab_seqccmn[]; + +char *midi_cmdname(int cmd, midi_cmdtab * tab); + +enum { + MORE, + TIMERARMED, + QUEUEFULL +}; + +#endif --- sys/dev/sound/midi/synth_if.m.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/synth_if.m Thu Jul 12 12:04:19 2007 @@ -0,0 +1,313 @@ +#- +# Copyright (c) 2003 Mathew Kanner +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD: src/sys/dev/sound/midi/synth_if.m,v 1.3 2007/02/25 13:51:52 netchild Exp $ +# + +INTERFACE synth; + +#include + +CODE { + +synth_killnote_t nokillnote; +synth_startnote_t nostartnote; +synth_setinstr_t nosetinstr; +synth_hwcontrol_t nohwcontrol; +synth_aftertouch_t noaftertouch; +synth_panning_t nopanning; +synth_controller_t nocontroller; +synth_volumemethod_t novolumemethod; +synth_bender_t nobender; +synth_setupvoice_t nosetupvoice; +synth_sendsysex_t nosendsysex; +synth_allocvoice_t noallocvoice; +synth_writeraw_t nowriteraw; +synth_reset_t noreset; +synth_shortname_t noshortname; +synth_open_t noopen; +synth_close_t noclose; +synth_query_t noquery; +synth_insync_t noinsync; +synth_alloc_t noalloc; + + int + nokillnote(void *_kobj, uint8_t _chn, uint8_t _note, uint8_t _vel) + { + printf("nokillnote\n"); + return 0; + } + + int + noopen(void *_kobj, void *_arg, int mode) + { + printf("noopen\n"); + return 0; + } + + int + noquery(void *_kboj) + { + printf("noquery\n"); + return 0; + } + + int + nostartnote(void *_kb, uint8_t _voice, uint8_t _note, uint8_t _parm) + { + printf("nostartnote\n"); + return 0; + } + + int + nosetinstr(void *_kb, uint8_t _chn, uint16_t _patchno) + { + printf("nosetinstr\n"); + return 0; + } + + int + nohwcontrol(void *_kb, uint8_t *_event) + { + printf("nohwcontrol\n"); + return 0; + } + + int + noaftertouch ( void /* X */ * _kobj, uint8_t _x1, uint8_t _x2) + { + printf("noaftertouch\n"); + return 0; + } + + int + nopanning ( void /* X */ * _kobj, uint8_t _x1, uint8_t _x2) + { + printf("nopanning\n"); + return 0; + } + + int + nocontroller ( void /* X */ * _kobj, uint8_t _x1, uint8_t _x2, uint16_t _x3) + { + printf("nocontroller\n"); + return 0; + } + + int + novolumemethod ( + void /* X */ * _kobj, + uint8_t _x1) + { + printf("novolumemethod\n"); + return 0; + } + + int + nobender ( void /* X */ * _kobj, uint8_t _voice, uint16_t _bend) + { + printf("nobender\n"); + return 0; + } + + int + nosetupvoice ( void /* X */ * _kobj, uint8_t _voice, uint8_t _chn) + { + + printf("nosetupvoice\n"); + return 0; + } + + int + nosendsysex ( void /* X */ * _kobj, void * _buf, size_t _len) + { + printf("nosendsysex\n"); + return 0; + } + + int + noallocvoice ( void /* X */ * _kobj, uint8_t _chn, uint8_t _note, void *_x) + { + printf("noallocvoice\n"); + return 0; + } + + int + nowriteraw ( void /* X */ * _kobjt, uint8_t * _buf, size_t _len) + { + printf("nowriteraw\n"); + return 1; + } + + int + noreset ( void /* X */ * _kobjt) + { + + printf("noreset\n"); + return 0; + } + + char * + noshortname (void /* X */ * _kobjt) + { + printf("noshortname\n"); + return "noshortname"; + } + + int + noclose ( void /* X */ * _kobjt) + { + + printf("noclose\n"); + return 0; + } + + int + noinsync (void /* X */ * _kobjt) + { + + printf("noinsync\n"); + return 0; + } + + int + noalloc ( void /* x */ * _kbojt, uint8_t _chn, uint8_t _note) + { + printf("noalloc\n"); + return 0; + } +} + +METHOD int killnote { + void /* X */ *_kobj; + uint8_t _chan; + uint8_t _note; + uint8_t _vel; +} DEFAULT nokillnote; + +METHOD int startnote { + void /* X */ *_kobj; + uint8_t _voice; + uint8_t _note; + uint8_t _parm; +} DEFAULT nostartnote; + +METHOD int setinstr { + void /* X */ *_kobj; + uint8_t _chn; + uint16_t _patchno; +} DEFAULT nosetinstr; + +METHOD int hwcontrol { + void /* X */ *_kobj; + uint8_t *_event; +} DEFAULT nohwcontrol; + +METHOD int aftertouch { + void /* X */ *_kobj; + uint8_t _x1; + uint8_t _x2; +} DEFAULT noaftertouch; + +METHOD int panning { + void /* X */ *_kobj; + uint8_t _x1; + uint8_t _x2; +} DEFAULT nopanning; + +METHOD int controller { + void /* X */ *_kobj; + uint8_t _x1; + uint8_t _x2; + uint16_t _x3; +} DEFAULT nocontroller; + +METHOD int volumemethod { + void /* X */ *_kobj; + uint8_t _x1; +} DEFAULT novolumemethod; + +METHOD int bender { + void /* X */ *_kobj; + uint8_t _voice; + uint16_t _bend; +} DEFAULT nobender; + +METHOD int setupvoice { + void /* X */ *_kobj; + uint8_t _voice; + uint8_t _chn; +} DEFAULT nosetupvoice; + +METHOD int sendsysex { + void /* X */ *_kobj; + void *_buf; + size_t _len; +} DEFAULT nosendsysex; + +METHOD int allocvoice { + void /* X */ *_kobj; + uint8_t _chn; + uint8_t _note; + void *_x; +} DEFAULT noallocvoice; + +METHOD int writeraw { + void /* X */ *_kobjt; + uint8_t *_buf; + size_t _len; +} DEFAULT nowriteraw; + +METHOD int reset { + void /* X */ *_kobjt; +} DEFAULT noreset; + +METHOD char * shortname { + void /* X */ *_kobjt; +} DEFAULT noshortname; + +METHOD int open { + void /* X */ *_kobjt; + void *_sythn; + int _mode; +} DEFAULT noopen; + +METHOD int close { + void /* X */ *_kobjt; +} DEFAULT noclose; + +METHOD int query { + void /* X */ *_kobjt; +} DEFAULT noquery; + +METHOD int insync { + void /* X */ *_kobjt; +} DEFAULT noinsync; + +METHOD int alloc { + void /* x */ *_kbojt; + uint8_t _chn; + uint8_t _note; +} DEFAULT noalloc; --- sys/dev/sound/null.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/null.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,610 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +#define SNDNULL_DESC "NULL Audio" + +#define SNDNULL_RATE_MIN 4000 +#define SNDNULL_RATE_MAX 192000 + +#define SNDNULL_RATE_DEFAULT 48000 +#define SNDNULL_FMT_DEFAULT (AFMT_STEREO | AFMT_S16_LE) +#define SNDNULL_FMTSTR_DEFAULT "s16le" + +#define SNDNULL_NPCHAN 1 +#define SNDNULL_NRCHAN 1 +#define SNDNULL_MAXCHAN (SNDNULL_NPCHAN + SNDNULL_NRCHAN) + +#define SNDNULL_BUFSZ_MIN 4096 +#define SNDNULL_BUFSZ_MAX 65536 +#define SNDNULL_BUFSZ_DEFAULT 4096 + +#define SNDNULL_BLKCNT_MIN 2 +#define SNDNULL_BLKCNT_MAX 512 +#define SNDNULL_BLKCNT_DEFAULT SNDNULL_BLKCNT_MIN + +#define SNDNULL_LOCK(sc) snd_mtxlock((sc)->lock) +#define SNDNULL_UNLOCK(sc) snd_mtxunlock((sc)->lock) + +struct sndnull_info; + +struct sndnull_chinfo { + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct pcmchan_caps *caps; + struct sndnull_info *parent; + uint32_t ptr, intrcnt; + int dir, active; +}; + +struct sndnull_info { + device_t dev; + struct sndnull_chinfo ch[SNDNULL_MAXCHAN]; + struct pcmchan_caps caps; + uint32_t bufsz; + uint32_t blkcnt; + uint32_t fmtlist[2]; + struct mtx *lock; + uint8_t *ringbuffer; + int chnum; + + struct callout poll_timer; + int poll_ticks, polling; +}; + +static void * +sndnull_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct sndnull_info *sc = devinfo; + struct sndnull_chinfo *ch; + + SNDNULL_LOCK(sc); + + ch = &sc->ch[sc->chnum++]; + ch->buffer = b; + ch->parent = sc; + ch->channel = c; + ch->dir = dir; + ch->caps = &sc->caps; + + SNDNULL_UNLOCK(sc); + + if (sndbuf_setup(ch->buffer, sc->ringbuffer, sc->bufsz) == -1) + return (NULL); + + return (ch); +} + +static int +sndnull_chan_setformat(kobj_t obj, void *data, uint32_t format) +{ + struct sndnull_chinfo *ch = data; + + if (ch->caps->fmtlist[0] != format) + return (-1); + + return (0); +} + +static int +sndnull_chan_setspeed(kobj_t obj, void *data, uint32_t spd) +{ + struct sndnull_chinfo *ch = data; + + if (spd < ch->caps->minspeed) + spd = ch->caps->minspeed; + if (spd > ch->caps->maxspeed) + spd = ch->caps->maxspeed; + + return (spd); +} + +static int +sndnull_chan_setfragments(kobj_t obj, void *data, + uint32_t blksz, uint32_t blkcnt) +{ + struct sndnull_chinfo *ch = data; + struct sndnull_info *sc = ch->parent; + + blkcnt = sc->blkcnt; + blksz = sndbuf_getmaxsize(ch->buffer) / blkcnt; + blksz -= blksz % sndbuf_getbps(ch->buffer); + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + return (1); +} + +static int +sndnull_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct sndnull_chinfo *ch = data; + struct sndnull_info *sc = ch->parent; + + sndnull_chan_setfragments(obj, data, blksz, sc->blkcnt); + + return (sndbuf_getblksz(ch->buffer)); +} + +#define SNDNULL_CHAN_ACTIVE(ch) ((ch)->active != 0) + +static __inline int +sndnull_anychan_active(struct sndnull_info *sc) +{ + int i; + + for (i = 0; i < sc->chnum; i++) { + if (SNDNULL_CHAN_ACTIVE(&sc->ch[i])) + return (1); + } + + return (0); +} + +static void +sndnull_poll_callback(void *arg) +{ + struct sndnull_info *sc = arg; + struct sndnull_chinfo *ch; + int i; + + if (sc == NULL) + return; + + SNDNULL_LOCK(sc); + + if (!sndnull_anychan_active(sc)) { + SNDNULL_UNLOCK(sc); + return; + } + + for (i = 0; i < sc->chnum; i++) { + ch = &sc->ch[i]; + if (SNDNULL_CHAN_ACTIVE(ch)) { + ch->ptr += sndbuf_getblksz(ch->buffer); + ch->intrcnt += 1; + SNDNULL_UNLOCK(sc); + chn_intr(ch->channel); + SNDNULL_LOCK(sc); + } + } + + callout_reset(&sc->poll_timer, sc->poll_ticks, + sndnull_poll_callback, sc); + + SNDNULL_UNLOCK(sc); +} + +static int +sndnull_chan_trigger(kobj_t obj, void *data, int go) +{ + struct sndnull_chinfo *ch = data; + struct sndnull_info *sc = ch->parent; + int pollticks; + + if (!PCMTRIG_COMMON(go)) + return (0); + + SNDNULL_LOCK(sc); + + switch (go) { + case PCMTRIG_START: + if (!sndnull_anychan_active(sc)) { + pollticks = ((uint64_t)hz * + sndbuf_getblksz(ch->buffer)) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + if (pollticks < 1) + pollticks = 1; + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_timer, 1, + sndnull_poll_callback, sc); + if (bootverbose) + device_printf(sc->dev, + "PCMTRIG_START: pollticks=%d\n", + pollticks); + } + if (ch->dir == PCMDIR_REC) + memset(sc->ringbuffer, sndbuf_zerodata( + sndbuf_getfmt(ch->buffer)), + sndbuf_getmaxsize(ch->buffer)); + ch->ptr = 0; + ch->intrcnt = 0; + ch->active = 1; + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + ch->active = 0; + if (!sndnull_anychan_active(sc)) + callout_stop(&sc->poll_timer); + if (ch->dir == PCMDIR_PLAY) + memset(sc->ringbuffer, sndbuf_zerodata( + sndbuf_getfmt(ch->buffer)), + sndbuf_getmaxsize(ch->buffer)); + break; + default: + break; + } + + SNDNULL_UNLOCK(sc); + + return (0); +} + +static int +sndnull_chan_getptr(kobj_t obj, void *data) +{ + struct sndnull_chinfo *ch = data; + struct sndnull_info *sc = ch->parent; + uint32_t ptr; + + SNDNULL_LOCK(sc); + ptr = (SNDNULL_CHAN_ACTIVE(ch)) ? ch->ptr : 0; + SNDNULL_UNLOCK(sc); + + return (ptr); +} + +static struct pcmchan_caps * +sndnull_chan_getcaps(kobj_t obj, void *data) +{ + return (((struct sndnull_chinfo *)data)->caps); +} + +static kobj_method_t sndnull_chan_methods[] = { + KOBJMETHOD(channel_init, sndnull_chan_init), + KOBJMETHOD(channel_setformat, sndnull_chan_setformat), + KOBJMETHOD(channel_setspeed, sndnull_chan_setspeed), + KOBJMETHOD(channel_setblocksize, sndnull_chan_setblocksize), + KOBJMETHOD(channel_setfragments, sndnull_chan_setfragments), + KOBJMETHOD(channel_trigger, sndnull_chan_trigger), + KOBJMETHOD(channel_getptr, sndnull_chan_getptr), + KOBJMETHOD(channel_getcaps, sndnull_chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(sndnull_chan); + +static const struct { + int ctl; + int rec; +} sndnull_mixer_ctls[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = { 1, 0 }, + [SOUND_MIXER_BASS] = { 1, 0 }, + [SOUND_MIXER_TREBLE] = { 1, 0 }, + [SOUND_MIXER_SYNTH] = { 1, 1 }, + [SOUND_MIXER_PCM] = { 1, 1 }, + [SOUND_MIXER_SPEAKER] = { 1, 0 }, + [SOUND_MIXER_LINE] = { 1, 1 }, + [SOUND_MIXER_MIC] = { 1, 1 }, + [SOUND_MIXER_CD] = { 1, 1 }, + [SOUND_MIXER_IMIX] = { 1, 1 }, + [SOUND_MIXER_RECLEV] = { 1, 0 }, +}; + +static int +sndnull_mixer_init(struct snd_mixer *m) +{ + uint32_t mask, recmask; + int i; + + mask = 0; + recmask = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (sndnull_mixer_ctls[i].ctl != 0) + mask |= 1 << i; + if (sndnull_mixer_ctls[i].rec != 0) + recmask |= 1 << i; + } + + mix_setdevs(m, mask); + mix_setrecdevs(m, recmask); + + return (0); +} + +static int +sndnull_mixer_set(struct snd_mixer *m, unsigned dev, + unsigned left, unsigned right) +{ + if (!(dev < SOUND_MIXER_NRDEVICES && sndnull_mixer_ctls[dev].ctl != 0)) + return (-1); + + return (left | (right << 8)); +} + +static int +sndnull_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) +{ + uint32_t recsrc; + int i; + + recsrc = src; + + if (recsrc & SOUND_MASK_IMIX) + recsrc &= SOUND_MASK_IMIX; + else { + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (sndnull_mixer_ctls[i].rec == 0) + recsrc &= ~(1 << i); + } + } + + return (recsrc); +} + +static kobj_method_t sndnull_mixer_methods[] = { + KOBJMETHOD(mixer_init, sndnull_mixer_init), + KOBJMETHOD(mixer_set, sndnull_mixer_set), + KOBJMETHOD(mixer_setrecsrc, sndnull_mixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(sndnull_mixer); + +static int +sysctl_sndnull_rate(SYSCTL_HANDLER_ARGS) +{ + struct sndnull_info *sc; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return (EINVAL); + + SNDNULL_LOCK(sc); + val = sc->caps.maxspeed; + SNDNULL_UNLOCK(sc); + + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err != 0 || req->newptr == NULL) + return (err); + + if (val < SNDNULL_RATE_MIN) + val = SNDNULL_RATE_MIN; + if (val > SNDNULL_RATE_MAX) + val = SNDNULL_RATE_MAX; + + SNDNULL_LOCK(sc); + if (sndnull_anychan_active(sc)) + err = EBUSY; + else { + sc->caps.minspeed = (uint32_t)val; + sc->caps.maxspeed = sc->caps.minspeed; + } + SNDNULL_UNLOCK(sc); + + return (err); +} + +static int +sysctl_sndnull_format(SYSCTL_HANDLER_ARGS) +{ + struct sndnull_info *sc; + device_t dev; + int err; + char fmtstr[AFMTSTR_MAXSZ]; + uint32_t fmt; + + dev = oidp->oid_arg1; + + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return (EINVAL); + + SNDNULL_LOCK(sc); + fmt = sc->fmtlist[0]; + if (fmt != afmt2afmtstr(NULL, fmt, fmtstr, sizeof(fmtstr), + AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) + strlcpy(fmtstr, SNDNULL_FMTSTR_DEFAULT, sizeof(fmtstr)); + SNDNULL_UNLOCK(sc); + + err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); + + if (err != 0 || req->newptr == NULL) + return (err); + + fmt = afmtstr2afmt(NULL, fmtstr, AFMTSTR_STEREO_RETURN); + if (fmt == 0) + return (EINVAL); + + SNDNULL_LOCK(sc); + if (fmt != sc->fmtlist[0]) { + if (sndnull_anychan_active(sc)) + err = EBUSY; + else + sc->fmtlist[0] = fmt; + } + SNDNULL_UNLOCK(sc); + + return (err); +} + +static device_t sndnull_dev = NULL; + +static void +sndnull_dev_identify(driver_t *driver, device_t parent) +{ + if (sndnull_dev == NULL) + sndnull_dev = BUS_ADD_CHILD(parent, 0, "pcm", -1); +} + +static int +sndnull_dev_probe(device_t dev) +{ + if (dev != NULL && dev == sndnull_dev) { + device_set_desc(dev, SNDNULL_DESC); + return (BUS_PROBE_DEFAULT); + } + + return (ENXIO); +} + +static int +sndnull_dev_attach(device_t dev) +{ + struct sndnull_info *sc; + char status[SND_STATUSLEN]; + int i; + + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_null softc"); + sc->dev = dev; + + callout_init(&sc->poll_timer, CALLOUT_MPSAFE); + sc->poll_ticks = 1; + + sc->caps.minspeed = SNDNULL_RATE_DEFAULT; + sc->caps.maxspeed = SNDNULL_RATE_DEFAULT; + sc->fmtlist[0] = SNDNULL_FMT_DEFAULT; + sc->fmtlist[1] = 0; + sc->caps.fmtlist = sc->fmtlist; + + sc->bufsz = pcm_getbuffersize(dev, SNDNULL_BUFSZ_MIN, + SNDNULL_BUFSZ_DEFAULT, SNDNULL_BUFSZ_MAX); + sc->blkcnt = SNDNULL_BLKCNT_DEFAULT; + + sc->ringbuffer = malloc(sc->bufsz, M_DEVBUF, M_WAITOK | M_ZERO); + + if (mixer_init(dev, &sndnull_mixer_class, sc) != 0) + device_printf(dev, "mixer_init() failed\n"); + + if (pcm_register(dev, sc, SNDNULL_NPCHAN, SNDNULL_NRCHAN)) + return (ENXIO); + + for (i = 0; i < SNDNULL_NPCHAN; i++) + pcm_addchan(dev, PCMDIR_PLAY, &sndnull_chan_class, sc); + for (i = 0; i < SNDNULL_NRCHAN; i++) + pcm_addchan(dev, PCMDIR_REC, &sndnull_chan_class, sc); + + snprintf(status, SND_STATUSLEN, "at %s %s", + device_get_nameunit(device_get_parent(dev)), + PCM_KLDSTRING(snd_null)); + + pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); + pcm_setstatus(dev, status); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "rate", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_sndnull_rate, "I", "runtime rate"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "format", CTLTYPE_STRING | CTLFLAG_RW, dev, sizeof(dev), + sysctl_sndnull_format, "A", "runtime format"); + + return (0); +} + +static void +sndnull_release_resources(struct sndnull_info *sc) +{ + if (sc == NULL) + return; + if (sc->chnum != 0) { + SNDNULL_LOCK(sc); + callout_stop(&sc->poll_timer); + SNDNULL_UNLOCK(sc); + callout_drain(&sc->poll_timer); + } + if (sc->ringbuffer != NULL) { + free(sc->ringbuffer, M_DEVBUF); + sc->ringbuffer = NULL; + } + if (sc->lock != NULL) { + snd_mtxfree(sc->lock); + sc->lock = NULL; + } + free(sc, M_DEVBUF); +} + +static int +sndnull_dev_detach(device_t dev) +{ + struct sndnull_info *sc; + int err; + + sc = pcm_getdevinfo(dev); + if (sc != NULL) { + err = pcm_unregister(dev); + if (err != 0) + return (err); + sndnull_release_resources(sc); + } + + return (0); +} + +static device_method_t sndnull_methods[] = { + DEVMETHOD(device_identify, sndnull_dev_identify), + DEVMETHOD(device_probe, sndnull_dev_probe), + DEVMETHOD(device_attach, sndnull_dev_attach), + DEVMETHOD(device_detach, sndnull_dev_detach), + { 0, 0 } +}; + +static driver_t sndnull_driver = { + "pcm", + sndnull_methods, + PCM_SOFTC_SIZE, +}; + +static int +sndnull_modevent(module_t mod, int type, void *data) +{ + switch (type) { + case MOD_UNLOAD: + if (sndnull_dev != NULL) + device_delete_child(device_get_parent(sndnull_dev), + sndnull_dev); + sndnull_dev = NULL; + case MOD_LOAD: + return (0); + break; + default: + break; + } + + return (EOPNOTSUPP); +} + +DRIVER_MODULE(snd_null, nexus, sndnull_driver, pcm_devclass, sndnull_modevent, + 0); +MODULE_DEPEND(snd_null, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_null, 1); --- sys/dev/sound/pci/als4000.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/als4000.c Thu Jul 12 12:04:19 2007 @@ -42,7 +42,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/als4000.c,v 1.16.2.1 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/als4000.c,v 1.27 2007/06/17 06:10:41 ariff Exp $"); /* Debugging macro's */ #undef DEB @@ -75,6 +75,7 @@ struct resource *reg, *irq; int regid, irqid; void *ih; + struct mtx *lock; unsigned int bufsz; struct sc_chinfo pch, rch; @@ -90,7 +91,11 @@ 0 }; -static struct pcmchan_caps als_caps = { 4000, 48000, als_format, 0 }; +/* + * I don't believe this rotten soundcard can do 48k, really, + * trust me. + */ +static struct pcmchan_caps als_caps = { 4000, 44100, als_format, 0 }; /* ------------------------------------------------------------------------- */ /* Register Utilities */ @@ -199,6 +204,7 @@ struct sc_info *sc = devinfo; struct sc_chinfo *ch; + snd_mtxlock(sc->lock); if (dir == PCMDIR_PLAY) { ch = &sc->pch; ch->gcr_fifo_status = ALS_GCR_FIFO0_STATUS; @@ -213,9 +219,11 @@ ch->format = AFMT_U8; ch->speed = DSP_DEFAULT_SPEED; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + snd_mtxunlock(sc->lock); + + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; - } + return ch; } @@ -263,9 +271,12 @@ alschan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; int32_t pos, sz; + snd_mtxlock(sc->lock); pos = als_gcr_rd(ch->parent, ch->gcr_fifo_status) & 0xffff; + snd_mtxunlock(sc->lock); sz = sndbuf_getsize(ch->buffer); return (2 * sz - pos - 1) % sz; } @@ -378,15 +389,24 @@ alspchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + + if (!PCMTRIG_COMMON(go)) + return 0; + snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_playback_start(ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: als_playback_stop(ch); break; + default: + break; } + snd_mtxunlock(sc->lock); return 0; } @@ -468,15 +488,19 @@ alsrchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_capture_start(ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: als_capture_stop(ch); break; } + snd_mtxunlock(sc->lock); return 0; } @@ -578,8 +602,13 @@ for (i = l = r = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (src & (1 << i)) { - l |= amt[i].iselect; - r |= amt[i].iselect << 1; + if (amt[i].iselect == 1) { /* microphone */ + l |= amt[i].iselect; + r |= amt[i].iselect; + } else { + l |= amt[i].iselect; + r |= amt[i].iselect >> 1; + } } } @@ -605,13 +634,20 @@ struct sc_info *sc = (struct sc_info *)p; u_int8_t intr, sb_status; + snd_mtxlock(sc->lock); intr = als_intr_rd(sc); - if (intr & 0x80) + if (intr & 0x80) { + snd_mtxunlock(sc->lock); chn_intr(sc->pch.channel); + snd_mtxlock(sc->lock); + } - if (intr & 0x40) + if (intr & 0x40) { + snd_mtxunlock(sc->lock); chn_intr(sc->rch.channel); + snd_mtxlock(sc->lock); + } /* ACK interrupt in PCI core */ als_intr_wr(sc, intr); @@ -627,6 +663,8 @@ als_ack_read(sc, ALS_MIDI_DATA); if (sb_status & ALS_IRQ_CR1E) als_ack_read(sc, ALS_CR1E_ACK_PORT); + + snd_mtxunlock(sc->lock); return; } @@ -684,7 +722,7 @@ { if (pci_get_devid(dev) == ALS_PCI_ID0) { device_set_desc(dev, "Avance Logic ALS4000"); - return 0; + return BUS_PROBE_DEFAULT; } return ENXIO; } @@ -708,6 +746,10 @@ bus_dma_tag_destroy(sc->parent_dmat); sc->parent_dmat = 0; } + if (sc->lock) { + snd_mtxfree(sc->lock); + sc->lock = NULL; + } } static int @@ -730,7 +772,7 @@ goto bad; } - if (bus_setup_intr(dev, sc->irq, INTR_TYPE_AV, als_intr, + if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, als_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; @@ -738,15 +780,15 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, ALS_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &sc->parent_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -763,11 +805,8 @@ u_int32_t data; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_als4000 softc"); sc->dev = dev; data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -851,9 +890,11 @@ { struct sc_info *sc = pcm_getdevinfo(dev); + snd_mtxlock(sc->lock); sc->pch.dma_was_active = als_playback_stop(&sc->pch); sc->rch.dma_was_active = als_capture_stop(&sc->rch); als_uninit(sc); + snd_mtxunlock(sc->lock); return 0; } @@ -862,13 +903,17 @@ { struct sc_info *sc = pcm_getdevinfo(dev); + + snd_mtxlock(sc->lock); if (als_init(sc) != 0) { device_printf(dev, "unable to reinitialize the card\n"); + snd_mtxunlock(sc->lock); return ENXIO; } if (mixer_reinit(dev) != 0) { device_printf(dev, "unable to reinitialize the mixer\n"); + snd_mtxunlock(sc->lock); return ENXIO; } @@ -879,6 +924,8 @@ if (sc->rch.dma_was_active) { als_capture_start(&sc->rch); } + snd_mtxunlock(sc->lock); + return 0; } --- sys/dev/sound/pci/als4000.h.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/als4000.h Thu Jan 6 09:43:18 2005 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/als4000.h,v 1.2.8.1 2005/01/30 01:00:03 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/als4000.h,v 1.3 2005/01/06 01:43:18 imp Exp $ */ #define ALS_PCI_ID0 0x40004005 --- sys/dev/sound/pci/atiixp.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/atiixp.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,1428 @@ +/*- + * Copyright (c) 2005 Ariff Abdullah + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * FreeBSD pcm driver for ATI IXP 150/200/250/300 AC97 controllers + * + * Features + * * 16bit playback / recording + * * 32bit native playback - yay! + * * 32bit native recording (seems broken on few hardwares) + * + * Issues / TODO: + * * SPDIF + * * Support for more than 2 channels. + * * VRA ? VRM ? DRA ? + * * 32bit native recording seems broken on few hardwares, most + * probably because of incomplete VRA/DRA cleanup. + * + * + * Thanks goes to: + * + * Shaharil @ SCAN Associates whom relentlessly providing me the + * mind blowing Acer Ferrari 4002 WLMi with this ATI IXP hardware. + * + * Reinoud Zandijk (auixp), which this driver is + * largely based upon although large part of it has been reworked. His + * driver is the primary reference and pretty much well documented. + * + * Takashi Iwai (ALSA snd-atiixp), for register definitions and some + * random ninja hackery. + */ + +#include +#include + +#include +#include +#include +#include + +#include + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/atiixp.c,v 1.19 2007/07/09 20:41:23 ariff Exp $"); + +#define ATI_IXP_DMA_RETRY_MAX 100 + +#define ATI_IXP_BUFSZ_MIN 4096 +#define ATI_IXP_BUFSZ_MAX 65536 +#define ATI_IXP_BUFSZ_DEFAULT 16384 + +#define ATI_IXP_BLK_MIN 32 +#define ATI_IXP_BLK_ALIGN (~(ATI_IXP_BLK_MIN - 1)) + +#define ATI_IXP_CHN_RUNNING 0x00000001 +#define ATI_IXP_CHN_SUSPEND 0x00000002 + +struct atiixp_dma_op { + volatile uint32_t addr; + volatile uint16_t status; + volatile uint16_t size; + volatile uint32_t next; +}; + +struct atiixp_info; + +struct atiixp_chinfo { + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct atiixp_info *parent; + struct atiixp_dma_op *sgd_table; + bus_addr_t sgd_addr; + uint32_t enable_bit, flush_bit, linkptr_bit, dt_cur_bit; + uint32_t blksz, blkcnt; + uint32_t ptr, prevptr; + uint32_t fmt; + uint32_t flags; + int caps_32bit, dir; +}; + +struct atiixp_info { + device_t dev; + + bus_space_tag_t st; + bus_space_handle_t sh; + bus_dma_tag_t parent_dmat; + bus_dma_tag_t sgd_dmat; + bus_dmamap_t sgd_dmamap; + bus_addr_t sgd_addr; + + struct resource *reg, *irq; + int regtype, regid, irqid; + void *ih; + struct ac97_info *codec; + + struct atiixp_chinfo pch; + struct atiixp_chinfo rch; + struct atiixp_dma_op *sgd_table; + struct intr_config_hook delayed_attach; + + uint32_t bufsz; + uint32_t codec_not_ready_bits, codec_idx, codec_found; + uint32_t blkcnt; + int registered_channels; + + struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; +}; + +#define atiixp_rd(_sc, _reg) \ + bus_space_read_4((_sc)->st, (_sc)->sh, _reg) +#define atiixp_wr(_sc, _reg, _val) \ + bus_space_write_4((_sc)->st, (_sc)->sh, _reg, _val) + +#define atiixp_lock(_sc) snd_mtxlock((_sc)->lock) +#define atiixp_unlock(_sc) snd_mtxunlock((_sc)->lock) +#define atiixp_assert(_sc) snd_mtxassert((_sc)->lock) + +static uint32_t atiixp_fmt_32bit[] = { + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; + +static uint32_t atiixp_fmt[] = { + AFMT_STEREO | AFMT_S16_LE, + 0 +}; + +static struct pcmchan_caps atiixp_caps_32bit = { + ATI_IXP_BASE_RATE, + ATI_IXP_BASE_RATE, + atiixp_fmt_32bit, 0 +}; + +static struct pcmchan_caps atiixp_caps = { + ATI_IXP_BASE_RATE, + ATI_IXP_BASE_RATE, + atiixp_fmt, 0 +}; + +static const struct { + uint16_t vendor; + uint16_t devid; + char *desc; +} atiixp_hw[] = { + { ATI_VENDOR_ID, ATI_IXP_200_ID, "ATI IXP 200" }, + { ATI_VENDOR_ID, ATI_IXP_300_ID, "ATI IXP 300" }, + { ATI_VENDOR_ID, ATI_IXP_400_ID, "ATI IXP 400" }, +}; + +static void atiixp_enable_interrupts(struct atiixp_info *); +static void atiixp_disable_interrupts(struct atiixp_info *); +static void atiixp_reset_aclink(struct atiixp_info *); +static void atiixp_flush_dma(struct atiixp_chinfo *); +static void atiixp_enable_dma(struct atiixp_chinfo *); +static void atiixp_disable_dma(struct atiixp_chinfo *); + +static int atiixp_waitready_codec(struct atiixp_info *); +static int atiixp_rdcd(kobj_t, void *, int); +static int atiixp_wrcd(kobj_t, void *, int, uint32_t); + +static void *atiixp_chan_init(kobj_t, void *, struct snd_dbuf *, + struct pcm_channel *, int); +static int atiixp_chan_setformat(kobj_t, void *, uint32_t); +static int atiixp_chan_setspeed(kobj_t, void *, uint32_t); +static int atiixp_chan_setfragments(kobj_t, void *, uint32_t, uint32_t); +static int atiixp_chan_setblocksize(kobj_t, void *, uint32_t); +static void atiixp_buildsgdt(struct atiixp_chinfo *); +static int atiixp_chan_trigger(kobj_t, void *, int); +static __inline uint32_t atiixp_dmapos(struct atiixp_chinfo *); +static int atiixp_chan_getptr(kobj_t, void *); +static struct pcmchan_caps *atiixp_chan_getcaps(kobj_t, void *); + +static void atiixp_intr(void *); +static void atiixp_dma_cb(void *, bus_dma_segment_t *, int, int); +static void atiixp_chip_pre_init(struct atiixp_info *); +static void atiixp_chip_post_init(void *); +static void atiixp_release_resource(struct atiixp_info *); +static int atiixp_pci_probe(device_t); +static int atiixp_pci_attach(device_t); +static int atiixp_pci_detach(device_t); +static int atiixp_pci_suspend(device_t); +static int atiixp_pci_resume(device_t); + +/* + * ATI IXP helper functions + */ +static void +atiixp_enable_interrupts(struct atiixp_info *sc) +{ + uint32_t value; + + /* clear all pending */ + atiixp_wr(sc, ATI_REG_ISR, 0xffffffff); + + /* enable all relevant interrupt sources we can handle */ + value = atiixp_rd(sc, ATI_REG_IER); + + value |= ATI_REG_IER_IO_STATUS_EN; + + /* + * Disable / ignore internal xrun/spdf interrupt flags + * since it doesn't interest us (for now). + */ +#if 1 + value &= ~(ATI_REG_IER_IN_XRUN_EN | ATI_REG_IER_OUT_XRUN_EN | + ATI_REG_IER_SPDF_XRUN_EN | ATI_REG_IER_SPDF_STATUS_EN); +#else + value |= ATI_REG_IER_IN_XRUN_EN; + value |= ATI_REG_IER_OUT_XRUN_EN; + + value |= ATI_REG_IER_SPDF_XRUN_EN; + value |= ATI_REG_IER_SPDF_STATUS_EN; +#endif + + atiixp_wr(sc, ATI_REG_IER, value); +} + +static void +atiixp_disable_interrupts(struct atiixp_info *sc) +{ + /* disable all interrupt sources */ + atiixp_wr(sc, ATI_REG_IER, 0); + + /* clear all pending */ + atiixp_wr(sc, ATI_REG_ISR, 0xffffffff); +} + +static void +atiixp_reset_aclink(struct atiixp_info *sc) +{ + uint32_t value, timeout; + + /* if power is down, power it up */ + value = atiixp_rd(sc, ATI_REG_CMD); + if (value & ATI_REG_CMD_POWERDOWN) { + /* explicitly enable power */ + value &= ~ATI_REG_CMD_POWERDOWN; + atiixp_wr(sc, ATI_REG_CMD, value); + + /* have to wait at least 10 usec for it to initialise */ + DELAY(20); + } + + /* perform a soft reset */ + value = atiixp_rd(sc, ATI_REG_CMD); + value |= ATI_REG_CMD_AC_SOFT_RESET; + atiixp_wr(sc, ATI_REG_CMD, value); + + /* need to read the CMD reg and wait aprox. 10 usec to init */ + value = atiixp_rd(sc, ATI_REG_CMD); + DELAY(20); + + /* clear soft reset flag again */ + value = atiixp_rd(sc, ATI_REG_CMD); + value &= ~ATI_REG_CMD_AC_SOFT_RESET; + atiixp_wr(sc, ATI_REG_CMD, value); + + /* check if the ac-link is working; reset device otherwise */ + timeout = 10; + value = atiixp_rd(sc, ATI_REG_CMD); + while (!(value & ATI_REG_CMD_ACLINK_ACTIVE) && --timeout) { +#if 0 + device_printf(sc->dev, "not up; resetting aclink hardware\n"); +#endif + + /* dip aclink reset but keep the acsync */ + value &= ~ATI_REG_CMD_AC_RESET; + value |= ATI_REG_CMD_AC_SYNC; + atiixp_wr(sc, ATI_REG_CMD, value); + + /* need to read CMD again and wait again (clocking in issue?) */ + value = atiixp_rd(sc, ATI_REG_CMD); + DELAY(20); + + /* assert aclink reset again */ + value = atiixp_rd(sc, ATI_REG_CMD); + value |= ATI_REG_CMD_AC_RESET; + atiixp_wr(sc, ATI_REG_CMD, value); + + /* check if its active now */ + value = atiixp_rd(sc, ATI_REG_CMD); + } + + if (timeout == 0) + device_printf(sc->dev, "giving up aclink reset\n"); +#if 0 + if (timeout != 10) + device_printf(sc->dev, "aclink hardware reset successful\n"); +#endif + + /* assert reset and sync for safety */ + value = atiixp_rd(sc, ATI_REG_CMD); + value |= ATI_REG_CMD_AC_SYNC | ATI_REG_CMD_AC_RESET; + atiixp_wr(sc, ATI_REG_CMD, value); +} + +static void +atiixp_flush_dma(struct atiixp_chinfo *ch) +{ + atiixp_wr(ch->parent, ATI_REG_FIFO_FLUSH, ch->flush_bit); +} + +static void +atiixp_enable_dma(struct atiixp_chinfo *ch) +{ + uint32_t value; + + value = atiixp_rd(ch->parent, ATI_REG_CMD); + if (!(value & ch->enable_bit)) { + value |= ch->enable_bit; + atiixp_wr(ch->parent, ATI_REG_CMD, value); + } +} + +static void +atiixp_disable_dma(struct atiixp_chinfo *ch) +{ + uint32_t value; + + value = atiixp_rd(ch->parent, ATI_REG_CMD); + if (value & ch->enable_bit) { + value &= ~ch->enable_bit; + atiixp_wr(ch->parent, ATI_REG_CMD, value); + } +} + +/* + * AC97 interface + */ +static int +atiixp_waitready_codec(struct atiixp_info *sc) +{ + int timeout = 500; + + do { + if ((atiixp_rd(sc, ATI_REG_PHYS_OUT_ADDR) & + ATI_REG_PHYS_OUT_ADDR_EN) == 0) + return (0); + DELAY(1); + } while (--timeout); + + return (-1); +} + +static int +atiixp_rdcd(kobj_t obj, void *devinfo, int reg) +{ + struct atiixp_info *sc = devinfo; + uint32_t data; + int timeout; + + if (atiixp_waitready_codec(sc)) + return (-1); + + data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | + ATI_REG_PHYS_OUT_ADDR_EN | ATI_REG_PHYS_OUT_RW | sc->codec_idx; + + atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); + + if (atiixp_waitready_codec(sc)) + return (-1); + + timeout = 500; + do { + data = atiixp_rd(sc, ATI_REG_PHYS_IN_ADDR); + if (data & ATI_REG_PHYS_IN_READ_FLAG) + return (data >> ATI_REG_PHYS_IN_DATA_SHIFT); + DELAY(1); + } while (--timeout); + + if (reg < 0x7c) + device_printf(sc->dev, "codec read timeout! (reg 0x%x)\n", reg); + + return (-1); +} + +static int +atiixp_wrcd(kobj_t obj, void *devinfo, int reg, uint32_t data) +{ + struct atiixp_info *sc = devinfo; + + if (atiixp_waitready_codec(sc)) + return (-1); + + data = (data << ATI_REG_PHYS_OUT_DATA_SHIFT) | + (((uint32_t)reg) << ATI_REG_PHYS_OUT_ADDR_SHIFT) | + ATI_REG_PHYS_OUT_ADDR_EN | sc->codec_idx; + + atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); + + return (0); +} + +static kobj_method_t atiixp_ac97_methods[] = { + KOBJMETHOD(ac97_read, atiixp_rdcd), + KOBJMETHOD(ac97_write, atiixp_wrcd), + { 0, 0 } +}; +AC97_DECLARE(atiixp_ac97); + +/* + * Playback / Record channel interface + */ +static void * +atiixp_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct atiixp_info *sc = devinfo; + struct atiixp_chinfo *ch; + int num; + + atiixp_lock(sc); + + if (dir == PCMDIR_PLAY) { + ch = &sc->pch; + ch->linkptr_bit = ATI_REG_OUT_DMA_LINKPTR; + ch->enable_bit = ATI_REG_CMD_OUT_DMA_EN | ATI_REG_CMD_SEND_EN; + ch->flush_bit = ATI_REG_FIFO_OUT_FLUSH; + ch->dt_cur_bit = ATI_REG_OUT_DMA_DT_CUR; + /* Native 32bit playback working properly */ + ch->caps_32bit = 1; + } else { + ch = &sc->rch; + ch->linkptr_bit = ATI_REG_IN_DMA_LINKPTR; + ch->enable_bit = ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_RECEIVE_EN; + ch->flush_bit = ATI_REG_FIFO_IN_FLUSH; + ch->dt_cur_bit = ATI_REG_IN_DMA_DT_CUR; + /* XXX Native 32bit recording appear to be broken */ + ch->caps_32bit = 1; + } + + ch->buffer = b; + ch->parent = sc; + ch->channel = c; + ch->dir = dir; + ch->blkcnt = sc->blkcnt; + ch->blksz = sc->bufsz / ch->blkcnt; + + atiixp_unlock(sc); + + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) == -1) + return (NULL); + + atiixp_lock(sc); + num = sc->registered_channels++; + ch->sgd_table = &sc->sgd_table[num * ATI_IXP_DMA_CHSEGS_MAX]; + ch->sgd_addr = sc->sgd_addr + (num * ATI_IXP_DMA_CHSEGS_MAX * + sizeof(struct atiixp_dma_op)); + atiixp_disable_dma(ch); + atiixp_unlock(sc); + + return (ch); +} + +static int +atiixp_chan_setformat(kobj_t obj, void *data, uint32_t format) +{ + struct atiixp_chinfo *ch = data; + struct atiixp_info *sc = ch->parent; + uint32_t value; + + atiixp_lock(sc); + if (ch->dir == PCMDIR_REC) { + value = atiixp_rd(sc, ATI_REG_CMD); + value &= ~ATI_REG_CMD_INTERLEAVE_IN; + if ((format & AFMT_32BIT) == 0) + value |= ATI_REG_CMD_INTERLEAVE_IN; + atiixp_wr(sc, ATI_REG_CMD, value); + } else { + value = atiixp_rd(sc, ATI_REG_OUT_DMA_SLOT); + value &= ~ATI_REG_OUT_DMA_SLOT_MASK; + /* We do not have support for more than 2 channels, _yet_. */ + value |= ATI_REG_OUT_DMA_SLOT_BIT(3) | + ATI_REG_OUT_DMA_SLOT_BIT(4); + value |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; + atiixp_wr(sc, ATI_REG_OUT_DMA_SLOT, value); + value = atiixp_rd(sc, ATI_REG_CMD); + value &= ~ATI_REG_CMD_INTERLEAVE_OUT; + if ((format & AFMT_32BIT) == 0) + value |= ATI_REG_CMD_INTERLEAVE_OUT; + atiixp_wr(sc, ATI_REG_CMD, value); + value = atiixp_rd(sc, ATI_REG_6CH_REORDER); + value &= ~ATI_REG_6CH_REORDER_EN; + atiixp_wr(sc, ATI_REG_6CH_REORDER, value); + } + ch->fmt = format; + atiixp_unlock(sc); + + return (0); +} + +static int +atiixp_chan_setspeed(kobj_t obj, void *data, uint32_t spd) +{ + /* XXX We're supposed to do VRA/DRA processing right here */ + return (ATI_IXP_BASE_RATE); +} + +static int +atiixp_chan_setfragments(kobj_t obj, void *data, + uint32_t blksz, uint32_t blkcnt) +{ + struct atiixp_chinfo *ch = data; + struct atiixp_info *sc = ch->parent; + + blksz &= ATI_IXP_BLK_ALIGN; + + if (blksz > (sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN)) + blksz = sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN; + if (blksz < ATI_IXP_BLK_MIN) + blksz = ATI_IXP_BLK_MIN; + if (blkcnt > ATI_IXP_DMA_CHSEGS_MAX) + blkcnt = ATI_IXP_DMA_CHSEGS_MAX; + if (blkcnt < ATI_IXP_DMA_CHSEGS_MIN) + blkcnt = ATI_IXP_DMA_CHSEGS_MIN; + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= ATI_IXP_DMA_CHSEGS_MIN) + blkcnt >>= 1; + else if ((blksz >> 1) >= ATI_IXP_BLK_MIN) + blksz >>= 1; + else + break; + } + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + ch->blksz = sndbuf_getblksz(ch->buffer); + ch->blkcnt = sndbuf_getblkcnt(ch->buffer); + + return (1); +} + +static int +atiixp_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct atiixp_chinfo *ch = data; + struct atiixp_info *sc = ch->parent; + + atiixp_chan_setfragments(obj, data, blksz, sc->blkcnt); + + return (ch->blksz); +} + +static void +atiixp_buildsgdt(struct atiixp_chinfo *ch) +{ + struct atiixp_info *sc = ch->parent; + uint32_t addr, blksz, blkcnt; + int i; + + addr = sndbuf_getbufaddr(ch->buffer); + + if (sc->polling != 0) { + blksz = ch->blksz * ch->blkcnt; + blkcnt = 1; + } else { + blksz = ch->blksz; + blkcnt = ch->blkcnt; + } + + for (i = 0; i < blkcnt; i++) { + ch->sgd_table[i].addr = htole32(addr + (i * blksz)); + ch->sgd_table[i].status = htole16(0); + ch->sgd_table[i].size = htole16(blksz >> 2); + ch->sgd_table[i].next = htole32((uint32_t)ch->sgd_addr + + (((i + 1) % blkcnt) * sizeof(struct atiixp_dma_op))); + } +} + +static __inline uint32_t +atiixp_dmapos(struct atiixp_chinfo *ch) +{ + struct atiixp_info *sc = ch->parent; + uint32_t reg, addr, sz, retry; + volatile uint32_t ptr; + + reg = ch->dt_cur_bit; + addr = sndbuf_getbufaddr(ch->buffer); + sz = ch->blkcnt * ch->blksz; + retry = ATI_IXP_DMA_RETRY_MAX; + + do { + ptr = atiixp_rd(sc, reg); + if (ptr < addr) + continue; + ptr -= addr; + if (ptr < sz) { +#if 0 +#ifdef ATI_IXP_DEBUG + if ((ptr & ~(ch->blksz - 1)) != ch->ptr) { + uint32_t delta; + + delta = (sz + ptr - ch->prevptr) % sz; +#ifndef ATI_IXP_DEBUG_VERBOSE + if (delta < ch->blksz) +#endif + device_printf(sc->dev, + "PCMDIR_%s: incoherent DMA " + "prevptr=%u ptr=%u " + "ptr=%u blkcnt=%u " + "[delta=%u != blksz=%u] " + "(%s)\n", + (ch->dir == PCMDIR_PLAY) ? + "PLAY" : "REC", + ch->prevptr, ptr, + ch->ptr, ch->blkcnt, + delta, ch->blksz, + (delta < ch->blksz) ? + "OVERLAPPED!" : "Ok"); + ch->ptr = ptr & ~(ch->blksz - 1); + } + ch->prevptr = ptr; +#endif +#endif + return (ptr); + } + } while (--retry); + + device_printf(sc->dev, "PCMDIR_%s: invalid DMA pointer ptr=%u\n", + (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ptr); + + return (0); +} + +static __inline int +atiixp_poll_channel(struct atiixp_chinfo *ch) +{ + uint32_t sz, delta; + volatile uint32_t ptr; + + if (!(ch->flags & ATI_IXP_CHN_RUNNING)) + return (0); + + sz = ch->blksz * ch->blkcnt; + ptr = atiixp_dmapos(ch); + ch->ptr = ptr; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + delta = (sz + ptr - ch->prevptr) % sz; + + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +#define atiixp_chan_active(sc) (((sc)->pch.flags | (sc)->rch.flags) & \ + ATI_IXP_CHN_RUNNING) + +static void +atiixp_poll_callback(void *arg) +{ + struct atiixp_info *sc = arg; + uint32_t trigger = 0; + + if (sc == NULL) + return; + + atiixp_lock(sc); + if (sc->polling == 0 || atiixp_chan_active(sc) == 0) { + atiixp_unlock(sc); + return; + } + + trigger |= (atiixp_poll_channel(&sc->pch) != 0) ? 1 : 0; + trigger |= (atiixp_poll_channel(&sc->rch) != 0) ? 2 : 0; + + /* XXX */ + callout_reset(&sc->poll_timer, 1/*sc->poll_ticks*/, + atiixp_poll_callback, sc); + + atiixp_unlock(sc); + + if (trigger & 1) + chn_intr(sc->pch.channel); + if (trigger & 2) + chn_intr(sc->rch.channel); +} + +static int +atiixp_chan_trigger(kobj_t obj, void *data, int go) +{ + struct atiixp_chinfo *ch = data; + struct atiixp_info *sc = ch->parent; + uint32_t value; + int pollticks; + + if (!PCMTRIG_COMMON(go)) + return (0); + + atiixp_lock(sc); + + switch (go) { + case PCMTRIG_START: + atiixp_flush_dma(ch); + atiixp_buildsgdt(ch); + atiixp_wr(sc, ch->linkptr_bit, 0); + atiixp_enable_dma(ch); + atiixp_wr(sc, ch->linkptr_bit, + (uint32_t)ch->sgd_addr | ATI_REG_LINKPTR_EN); + if (sc->polling != 0) { + ch->ptr = 0; + ch->prevptr = 0; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (atiixp_chan_active(sc) == 0 || + pollticks < sc->poll_ticks) { + if (bootverbose) { + if (atiixp_chan_active(sc) == 0) + device_printf(sc->dev, + "%s: pollticks=%d\n", + __func__, pollticks); + else + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + } + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_timer, 1, + atiixp_poll_callback, sc); + } + } + ch->flags |= ATI_IXP_CHN_RUNNING; + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + atiixp_disable_dma(ch); + atiixp_flush_dma(ch); + ch->flags &= ~ATI_IXP_CHN_RUNNING; + if (sc->polling != 0) { + if (atiixp_chan_active(sc) == 0) { + callout_stop(&sc->poll_timer); + sc->poll_ticks = 1; + } else { + if (sc->pch.flags & ATI_IXP_CHN_RUNNING) + ch = &sc->pch; + else + ch = &sc->rch; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (pollticks > sc->poll_ticks) { + if (bootverbose) + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_timer, + 1, atiixp_poll_callback, + sc); + } + } + } + break; + default: + atiixp_unlock(sc); + return (0); + break; + } + + /* Update bus busy status */ + value = atiixp_rd(sc, ATI_REG_IER); + if (atiixp_rd(sc, ATI_REG_CMD) & (ATI_REG_CMD_SEND_EN | + ATI_REG_CMD_RECEIVE_EN | ATI_REG_CMD_SPDF_OUT_EN)) + value |= ATI_REG_IER_SET_BUS_BUSY; + else + value &= ~ATI_REG_IER_SET_BUS_BUSY; + atiixp_wr(sc, ATI_REG_IER, value); + + atiixp_unlock(sc); + + return (0); +} + +static int +atiixp_chan_getptr(kobj_t obj, void *data) +{ + struct atiixp_chinfo *ch = data; + struct atiixp_info *sc = ch->parent; + uint32_t ptr; + + atiixp_lock(sc); + if (sc->polling != 0) + ptr = ch->ptr; + else + ptr = atiixp_dmapos(ch); + atiixp_unlock(sc); + + return (ptr); +} + +static struct pcmchan_caps * +atiixp_chan_getcaps(kobj_t obj, void *data) +{ + struct atiixp_chinfo *ch = data; + + if (ch->caps_32bit) + return (&atiixp_caps_32bit); + return (&atiixp_caps); +} + +static kobj_method_t atiixp_chan_methods[] = { + KOBJMETHOD(channel_init, atiixp_chan_init), + KOBJMETHOD(channel_setformat, atiixp_chan_setformat), + KOBJMETHOD(channel_setspeed, atiixp_chan_setspeed), + KOBJMETHOD(channel_setblocksize, atiixp_chan_setblocksize), + KOBJMETHOD(channel_setfragments, atiixp_chan_setfragments), + KOBJMETHOD(channel_trigger, atiixp_chan_trigger), + KOBJMETHOD(channel_getptr, atiixp_chan_getptr), + KOBJMETHOD(channel_getcaps, atiixp_chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(atiixp_chan); + +/* + * PCI driver interface + */ +static void +atiixp_intr(void *p) +{ + struct atiixp_info *sc = p; + uint32_t status, enable, detected_codecs; + uint32_t trigger = 0; + + atiixp_lock(sc); + if (sc->polling != 0) { + atiixp_unlock(sc); + return; + } + status = atiixp_rd(sc, ATI_REG_ISR); + + if (status == 0) { + atiixp_unlock(sc); + return; + } + + if ((status & ATI_REG_ISR_OUT_STATUS) && + (sc->pch.flags & ATI_IXP_CHN_RUNNING)) + trigger |= 1; + if ((status & ATI_REG_ISR_IN_STATUS) && + (sc->rch.flags & ATI_IXP_CHN_RUNNING)) + trigger |= 2; + +#if 0 + if (status & ATI_REG_ISR_IN_XRUN) { + device_printf(sc->dev, + "Recieve IN XRUN interrupt\n"); + } + if (status & ATI_REG_ISR_OUT_XRUN) { + device_printf(sc->dev, + "Recieve OUT XRUN interrupt\n"); + } +#endif + + if (status & CODEC_CHECK_BITS) { + /* mark missing codecs as not ready */ + detected_codecs = status & CODEC_CHECK_BITS; + sc->codec_not_ready_bits |= detected_codecs; + + /* disable detected interupt sources */ + enable = atiixp_rd(sc, ATI_REG_IER); + enable &= ~detected_codecs; + atiixp_wr(sc, ATI_REG_IER, enable); + wakeup(sc); + } + + /* acknowledge */ + atiixp_wr(sc, ATI_REG_ISR, status); + atiixp_unlock(sc); + + if (trigger & 1) + chn_intr(sc->pch.channel); + if (trigger & 2) + chn_intr(sc->rch.channel); +} + +static void +atiixp_dma_cb(void *p, bus_dma_segment_t *bds, int a, int b) +{ + struct atiixp_info *sc = (struct atiixp_info *)p; + sc->sgd_addr = bds->ds_addr; +} + +static void +atiixp_chip_pre_init(struct atiixp_info *sc) +{ + uint32_t value; + + atiixp_lock(sc); + + /* disable interrupts */ + atiixp_disable_interrupts(sc); + + /* clear all DMA enables (preserving rest of settings) */ + value = atiixp_rd(sc, ATI_REG_CMD); + value &= ~(ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_OUT_DMA_EN | + ATI_REG_CMD_SPDF_OUT_EN ); + atiixp_wr(sc, ATI_REG_CMD, value); + + /* reset aclink */ + atiixp_reset_aclink(sc); + + sc->codec_not_ready_bits = 0; + + /* enable all codecs to interrupt as well as the new frame interrupt */ + atiixp_wr(sc, ATI_REG_IER, CODEC_CHECK_BITS); + + atiixp_unlock(sc); +} + +#ifdef SND_DYNSYSCTL +static int +sysctl_atiixp_polling(SYSCTL_HANDLER_ARGS) +{ + struct atiixp_info *sc; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return (EINVAL); + atiixp_lock(sc); + val = sc->polling; + atiixp_unlock(sc); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + atiixp_lock(sc); + if (val != sc->polling) { + if (atiixp_chan_active(sc) != 0) + err = EBUSY; + else if (val == 0) { + atiixp_enable_interrupts(sc); + sc->polling = 0; + DELAY(1000); + } else { + atiixp_disable_interrupts(sc); + sc->polling = 1; + DELAY(1000); + } + } + atiixp_unlock(sc); + + return (err); +} +#endif + +static void +atiixp_chip_post_init(void *arg) +{ + struct atiixp_info *sc = (struct atiixp_info *)arg; + uint32_t subdev; + int i, timeout, found, polling; + char status[SND_STATUSLEN]; + + atiixp_lock(sc); + + if (sc->delayed_attach.ich_func) { + config_intrhook_disestablish(&sc->delayed_attach); + sc->delayed_attach.ich_func = NULL; + } + + polling = sc->polling; + sc->polling = 0; + + timeout = 10; + if (sc->codec_not_ready_bits == 0) { + /* wait for the interrupts to happen */ + do { + msleep(sc, sc->lock, PWAIT, "ixpslp", max(hz / 10, 1)); + if (sc->codec_not_ready_bits != 0) + break; + } while (--timeout); + } + + sc->polling = polling; + atiixp_disable_interrupts(sc); + + if (sc->codec_not_ready_bits == 0 && timeout == 0) { + device_printf(sc->dev, + "WARNING: timeout during codec detection; " + "codecs might be present but haven't interrupted\n"); + atiixp_unlock(sc); + goto postinitbad; + } + + found = 0; + + /* + * ATI IXP can have upto 3 codecs, but single codec should be + * suffice for now. + */ + if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC0_NOT_READY)) { + /* codec 0 present */ + sc->codec_found++; + sc->codec_idx = 0; + found++; + } + + if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC1_NOT_READY)) { + /* codec 1 present */ + sc->codec_found++; + } + + if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC2_NOT_READY)) { + /* codec 2 present */ + sc->codec_found++; + } + + atiixp_unlock(sc); + + if (found == 0) + goto postinitbad; + + /* create/init mixer */ + sc->codec = AC97_CREATE(sc->dev, sc, atiixp_ac97); + if (sc->codec == NULL) + goto postinitbad; + + subdev = (pci_get_subdevice(sc->dev) << 16) | + pci_get_subvendor(sc->dev); + switch (subdev) { + case 0x11831043: /* ASUS A6R */ + case 0x2043161f: /* Maxselect x710s - http://maxselect.ru/ */ + ac97_setflags(sc->codec, ac97_getflags(sc->codec) | + AC97_F_EAPD_INV); + break; + default: + break; + } + + mixer_init(sc->dev, ac97_getmixerclass(), sc->codec); + + if (pcm_register(sc->dev, sc, ATI_IXP_NPCHAN, ATI_IXP_NRCHAN)) + goto postinitbad; + + for (i = 0; i < ATI_IXP_NPCHAN; i++) + pcm_addchan(sc->dev, PCMDIR_PLAY, &atiixp_chan_class, sc); + for (i = 0; i < ATI_IXP_NRCHAN; i++) + pcm_addchan(sc->dev, PCMDIR_REC, &atiixp_chan_class, sc); + +#ifdef SND_DYNSYSCTL + SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), + sysctl_atiixp_polling, "I", "Enable polling mode"); +#endif + + snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s", + rman_get_start(sc->reg), rman_get_start(sc->irq), + PCM_KLDSTRING(snd_atiixp)); + + pcm_setstatus(sc->dev, status); + + atiixp_lock(sc); + if (sc->polling == 0) + atiixp_enable_interrupts(sc); + atiixp_unlock(sc); + + return; + +postinitbad: + atiixp_release_resource(sc); +} + +static void +atiixp_release_resource(struct atiixp_info *sc) +{ + if (sc == NULL) + return; + if (sc->registered_channels != 0) { + atiixp_lock(sc); + sc->polling = 0; + callout_stop(&sc->poll_timer); + atiixp_unlock(sc); + callout_drain(&sc->poll_timer); + } + if (sc->codec) { + ac97_destroy(sc->codec); + sc->codec = NULL; + } + if (sc->ih) { + bus_teardown_intr(sc->dev, sc->irq, sc->ih); + sc->ih = NULL; + } + if (sc->reg) { + bus_release_resource(sc->dev, sc->regtype, sc->regid, sc->reg); + sc->reg = NULL; + } + if (sc->irq) { + bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irqid, sc->irq); + sc->irq = NULL; + } + if (sc->parent_dmat) { + bus_dma_tag_destroy(sc->parent_dmat); + sc->parent_dmat = NULL; + } + if (sc->sgd_dmamap) + bus_dmamap_unload(sc->sgd_dmat, sc->sgd_dmamap); + if (sc->sgd_table) { + bus_dmamem_free(sc->sgd_dmat, sc->sgd_table, sc->sgd_dmamap); + sc->sgd_table = NULL; + } + sc->sgd_dmamap = NULL; + if (sc->sgd_dmat) { + bus_dma_tag_destroy(sc->sgd_dmat); + sc->sgd_dmat = NULL; + } + if (sc->lock) { + snd_mtxfree(sc->lock); + sc->lock = NULL; + } + free(sc, M_DEVBUF); +} + +static int +atiixp_pci_probe(device_t dev) +{ + int i; + uint16_t devid, vendor; + + vendor = pci_get_vendor(dev); + devid = pci_get_device(dev); + for (i = 0; i < sizeof(atiixp_hw) / sizeof(atiixp_hw[0]); i++) { + if (vendor == atiixp_hw[i].vendor && + devid == atiixp_hw[i].devid) { + device_set_desc(dev, atiixp_hw[i].desc); + return (BUS_PROBE_DEFAULT); + } + } + + return (ENXIO); +} + +static int +atiixp_pci_attach(device_t dev) +{ + struct atiixp_info *sc; + int i; + + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_atiixp softc"); + sc->dev = dev; + + callout_init(&sc->poll_timer, CALLOUT_MPSAFE); + sc->poll_ticks = 1; + + if (resource_int_value(device_get_name(sc->dev), + device_get_unit(sc->dev), "polling", &i) == 0 && i != 0) + sc->polling = 1; + else + sc->polling = 0; + + pci_set_powerstate(dev, PCI_POWERSTATE_D0); + pci_enable_busmaster(dev); + + sc->regid = PCIR_BAR(0); + sc->regtype = SYS_RES_MEMORY; + sc->reg = bus_alloc_resource_any(dev, sc->regtype, + &sc->regid, RF_ACTIVE); + + if (!sc->reg) { + device_printf(dev, "unable to allocate register space\n"); + goto bad; + } + + sc->st = rman_get_bustag(sc->reg); + sc->sh = rman_get_bushandle(sc->reg); + + sc->bufsz = pcm_getbuffersize(dev, ATI_IXP_BUFSZ_MIN, + ATI_IXP_BUFSZ_DEFAULT, ATI_IXP_BUFSZ_MAX); + + sc->irqid = 0; + sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, + RF_ACTIVE | RF_SHAREABLE); + if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, + atiixp_intr, sc, &sc->ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + + /* + * Let the user choose the best DMA segments. + */ + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + i &= ATI_IXP_BLK_ALIGN; + if (i < ATI_IXP_BLK_MIN) + i = ATI_IXP_BLK_MIN; + sc->blkcnt = sc->bufsz / i; + i = 0; + while (sc->blkcnt >> i) + i++; + sc->blkcnt = 1 << (i - 1); + if (sc->blkcnt < ATI_IXP_DMA_CHSEGS_MIN) + sc->blkcnt = ATI_IXP_DMA_CHSEGS_MIN; + else if (sc->blkcnt > ATI_IXP_DMA_CHSEGS_MAX) + sc->blkcnt = ATI_IXP_DMA_CHSEGS_MAX; + + } else + sc->blkcnt = ATI_IXP_DMA_CHSEGS; + + /* + * DMA tag for scatter-gather buffers and link pointers + */ + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &sc->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * + sizeof(struct atiixp_dma_op), + /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &sc->sgd_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + if (bus_dmamem_alloc(sc->sgd_dmat, (void **)&sc->sgd_table, + BUS_DMA_NOWAIT, &sc->sgd_dmamap) == -1) + goto bad; + + if (bus_dmamap_load(sc->sgd_dmat, sc->sgd_dmamap, sc->sgd_table, + ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * + sizeof(struct atiixp_dma_op), atiixp_dma_cb, sc, 0)) + goto bad; + + + atiixp_chip_pre_init(sc); + + sc->delayed_attach.ich_func = atiixp_chip_post_init; + sc->delayed_attach.ich_arg = sc; + if (cold == 0 || + config_intrhook_establish(&sc->delayed_attach) != 0) { + sc->delayed_attach.ich_func = NULL; + atiixp_chip_post_init(sc); + } + + return (0); + +bad: + atiixp_release_resource(sc); + return (ENXIO); +} + +static int +atiixp_pci_detach(device_t dev) +{ + int r; + struct atiixp_info *sc; + + sc = pcm_getdevinfo(dev); + if (sc != NULL) { + if (sc->codec != NULL) { + r = pcm_unregister(dev); + if (r) + return (r); + } + sc->codec = NULL; + if (sc->st != 0 && sc->sh != 0) + atiixp_disable_interrupts(sc); + atiixp_release_resource(sc); + } + return (0); +} + +static int +atiixp_pci_suspend(device_t dev) +{ + struct atiixp_info *sc = pcm_getdevinfo(dev); + uint32_t value; + + /* quickly disable interrupts and save channels active state */ + atiixp_lock(sc); + atiixp_disable_interrupts(sc); + atiixp_unlock(sc); + + /* stop everything */ + if (sc->pch.flags & ATI_IXP_CHN_RUNNING) { + atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_STOP); + sc->pch.flags |= ATI_IXP_CHN_SUSPEND; + } + if (sc->rch.flags & ATI_IXP_CHN_RUNNING) { + atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_STOP); + sc->rch.flags |= ATI_IXP_CHN_SUSPEND; + } + + /* power down aclink and pci bus */ + atiixp_lock(sc); + value = atiixp_rd(sc, ATI_REG_CMD); + value |= ATI_REG_CMD_POWERDOWN | ATI_REG_CMD_AC_RESET; + atiixp_wr(sc, ATI_REG_CMD, ATI_REG_CMD_POWERDOWN); + pci_set_powerstate(dev, PCI_POWERSTATE_D3); + atiixp_unlock(sc); + + return (0); +} + +static int +atiixp_pci_resume(device_t dev) +{ + struct atiixp_info *sc = pcm_getdevinfo(dev); + + atiixp_lock(sc); + /* power up pci bus */ + pci_set_powerstate(dev, PCI_POWERSTATE_D0); + pci_enable_io(dev, SYS_RES_MEMORY); + pci_enable_busmaster(dev); + /* reset / power up aclink */ + atiixp_reset_aclink(sc); + atiixp_unlock(sc); + + if (mixer_reinit(dev) == -1) { + device_printf(dev, "unable to reinitialize the mixer\n"); + return (ENXIO); + } + + /* + * Resume channel activities. Reset channel format regardless + * of its previous state. + */ + if (sc->pch.channel != NULL) { + if (sc->pch.fmt != 0) + atiixp_chan_setformat(NULL, &sc->pch, sc->pch.fmt); + if (sc->pch.flags & ATI_IXP_CHN_SUSPEND) { + sc->pch.flags &= ~ATI_IXP_CHN_SUSPEND; + atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_START); + } + } + if (sc->rch.channel != NULL) { + if (sc->rch.fmt != 0) + atiixp_chan_setformat(NULL, &sc->rch, sc->rch.fmt); + if (sc->rch.flags & ATI_IXP_CHN_SUSPEND) { + sc->rch.flags &= ~ATI_IXP_CHN_SUSPEND; + atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_START); + } + } + + /* enable interrupts */ + atiixp_lock(sc); + if (sc->polling == 0) + atiixp_enable_interrupts(sc); + atiixp_unlock(sc); + + return (0); +} + +static device_method_t atiixp_methods[] = { + DEVMETHOD(device_probe, atiixp_pci_probe), + DEVMETHOD(device_attach, atiixp_pci_attach), + DEVMETHOD(device_detach, atiixp_pci_detach), + DEVMETHOD(device_suspend, atiixp_pci_suspend), + DEVMETHOD(device_resume, atiixp_pci_resume), + { 0, 0 } +}; + +static driver_t atiixp_driver = { + "pcm", + atiixp_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_atiixp, pci, atiixp_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_atiixp, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_atiixp, 1); --- sys/dev/sound/pci/atiixp.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/atiixp.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,202 @@ +/*- + * Copyright (c) 2005 Ariff Abdullah + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/pci/atiixp.h,v 1.3 2006/10/01 13:30:30 ariff Exp $ + */ + +#ifndef _ATIIXP_H_ +#define _ATIIXP_H_ + +/* + * Constants, pretty much FreeBSD specific. + */ + +/* Number of playback / recording channel */ +#define ATI_IXP_NPCHAN 1 +#define ATI_IXP_NRCHAN 1 +#define ATI_IXP_NCHANS (ATI_IXP_NPCHAN + ATI_IXP_NRCHAN) + +/* + * Maximum segments/descriptors is 256, but 2 for + * each channel should be more than enough for us. + */ +#define ATI_IXP_DMA_CHSEGS 2 +#define ATI_IXP_DMA_CHSEGS_MIN 2 +#define ATI_IXP_DMA_CHSEGS_MAX 256 + +#define ATI_VENDOR_ID 0x1002 /* ATI Technologies */ + +#define ATI_IXP_200_ID 0x4341 +#define ATI_IXP_300_ID 0x4361 +#define ATI_IXP_400_ID 0x4370 + +#define ATI_IXP_BASE_RATE 48000 + +/* + * Register definitions for ATI IXP + * + * References: ALSA snd-atiixp.c , OpenBSD/NetBSD auixp-*.h + */ + +#define ATI_IXP_CODECS 3 + +#define ATI_REG_ISR 0x00 /* interrupt source */ +#define ATI_REG_ISR_IN_XRUN (1U<<0) +#define ATI_REG_ISR_IN_STATUS (1U<<1) +#define ATI_REG_ISR_OUT_XRUN (1U<<2) +#define ATI_REG_ISR_OUT_STATUS (1U<<3) +#define ATI_REG_ISR_SPDF_XRUN (1U<<4) +#define ATI_REG_ISR_SPDF_STATUS (1U<<5) +#define ATI_REG_ISR_PHYS_INTR (1U<<8) +#define ATI_REG_ISR_PHYS_MISMATCH (1U<<9) +#define ATI_REG_ISR_CODEC0_NOT_READY (1U<<10) +#define ATI_REG_ISR_CODEC1_NOT_READY (1U<<11) +#define ATI_REG_ISR_CODEC2_NOT_READY (1U<<12) +#define ATI_REG_ISR_NEW_FRAME (1U<<13) + +#define ATI_REG_IER 0x04 /* interrupt enable */ +#define ATI_REG_IER_IN_XRUN_EN (1U<<0) +#define ATI_REG_IER_IO_STATUS_EN (1U<<1) +#define ATI_REG_IER_OUT_XRUN_EN (1U<<2) +#define ATI_REG_IER_OUT_XRUN_COND (1U<<3) +#define ATI_REG_IER_SPDF_XRUN_EN (1U<<4) +#define ATI_REG_IER_SPDF_STATUS_EN (1U<<5) +#define ATI_REG_IER_PHYS_INTR_EN (1U<<8) +#define ATI_REG_IER_PHYS_MISMATCH_EN (1U<<9) +#define ATI_REG_IER_CODEC0_INTR_EN (1U<<10) +#define ATI_REG_IER_CODEC1_INTR_EN (1U<<11) +#define ATI_REG_IER_CODEC2_INTR_EN (1U<<12) +#define ATI_REG_IER_NEW_FRAME_EN (1U<<13) /* (RO) */ +#define ATI_REG_IER_SET_BUS_BUSY (1U<<14) /* (WO) audio is running */ + +#define ATI_REG_CMD 0x08 /* command */ +#define ATI_REG_CMD_POWERDOWN (1U<<0) +#define ATI_REG_CMD_RECEIVE_EN (1U<<1) +#define ATI_REG_CMD_SEND_EN (1U<<2) +#define ATI_REG_CMD_STATUS_MEM (1U<<3) +#define ATI_REG_CMD_SPDF_OUT_EN (1U<<4) +#define ATI_REG_CMD_SPDF_STATUS_MEM (1U<<5) +#define ATI_REG_CMD_SPDF_THRESHOLD (3U<<6) +#define ATI_REG_CMD_SPDF_THRESHOLD_SHIFT 6 +#define ATI_REG_CMD_IN_DMA_EN (1U<<8) +#define ATI_REG_CMD_OUT_DMA_EN (1U<<9) +#define ATI_REG_CMD_SPDF_DMA_EN (1U<<10) +#define ATI_REG_CMD_SPDF_OUT_STOPPED (1U<<11) +#define ATI_REG_CMD_SPDF_CONFIG_MASK (7U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_34 (1U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_78 (2U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_69 (3U<<12) +#define ATI_REG_CMD_SPDF_CONFIG_01 (4U<<12) +#define ATI_REG_CMD_INTERLEAVE_SPDF (1U<<16) +#define ATI_REG_CMD_AUDIO_PRESENT (1U<<20) +#define ATI_REG_CMD_INTERLEAVE_IN (1U<<21) +#define ATI_REG_CMD_INTERLEAVE_OUT (1U<<22) +#define ATI_REG_CMD_LOOPBACK_EN (1U<<23) +#define ATI_REG_CMD_PACKED_DIS (1U<<24) +#define ATI_REG_CMD_BURST_EN (1U<<25) +#define ATI_REG_CMD_PANIC_EN (1U<<26) +#define ATI_REG_CMD_MODEM_PRESENT (1U<<27) +#define ATI_REG_CMD_ACLINK_ACTIVE (1U<<28) +#define ATI_REG_CMD_AC_SOFT_RESET (1U<<29) +#define ATI_REG_CMD_AC_SYNC (1U<<30) +#define ATI_REG_CMD_AC_RESET (1U<<31) + +#define ATI_REG_PHYS_OUT_ADDR 0x0c +#define ATI_REG_PHYS_OUT_CODEC_MASK (3U<<0) +#define ATI_REG_PHYS_OUT_RW (1U<<2) +#define ATI_REG_PHYS_OUT_ADDR_EN (1U<<8) +#define ATI_REG_PHYS_OUT_ADDR_SHIFT 9 +#define ATI_REG_PHYS_OUT_DATA_SHIFT 16 + +#define ATI_REG_PHYS_IN_ADDR 0x10 +#define ATI_REG_PHYS_IN_READ_FLAG (1U<<8) +#define ATI_REG_PHYS_IN_ADDR_SHIFT 9 +#define ATI_REG_PHYS_IN_DATA_SHIFT 16 + +#define ATI_REG_SLOTREQ 0x14 + +#define ATI_REG_COUNTER 0x18 +#define ATI_REG_COUNTER_SLOT (3U<<0) /* slot # */ +#define ATI_REG_COUNTER_BITCLOCK (31U<<8) + +#define ATI_REG_IN_FIFO_THRESHOLD 0x1c + +#define ATI_REG_IN_DMA_LINKPTR 0x20 +#define ATI_REG_IN_DMA_DT_START 0x24 /* RO */ +#define ATI_REG_IN_DMA_DT_NEXT 0x28 /* RO */ +#define ATI_REG_IN_DMA_DT_CUR 0x2c /* RO */ +#define ATI_REG_IN_DMA_DT_SIZE 0x30 + +#define ATI_REG_OUT_DMA_SLOT 0x34 +#define ATI_REG_OUT_DMA_SLOT_BIT(x) (1U << ((x) - 3)) +#define ATI_REG_OUT_DMA_SLOT_MASK 0x1ff +#define ATI_REG_OUT_DMA_THRESHOLD_MASK 0xf800 +#define ATI_REG_OUT_DMA_THRESHOLD_SHIFT 11 + +#define ATI_REG_OUT_DMA_LINKPTR 0x38 +#define ATI_REG_OUT_DMA_DT_START 0x3c /* RO */ +#define ATI_REG_OUT_DMA_DT_NEXT 0x40 /* RO */ +#define ATI_REG_OUT_DMA_DT_CUR 0x44 /* RO */ +#define ATI_REG_OUT_DMA_DT_SIZE 0x48 + +#define ATI_REG_SPDF_CMD 0x4c +#define ATI_REG_SPDF_CMD_LFSR (1U<<4) +#define ATI_REG_SPDF_CMD_SINGLE_CH (1U<<5) +#define ATI_REG_SPDF_CMD_LFSR_ACC (0xff<<8) /* RO */ + +#define ATI_REG_SPDF_DMA_LINKPTR 0x50 +#define ATI_REG_SPDF_DMA_DT_START 0x54 /* RO */ +#define ATI_REG_SPDF_DMA_DT_NEXT 0x58 /* RO */ +#define ATI_REG_SPDF_DMA_DT_CUR 0x5c /* RO */ +#define ATI_REG_SPDF_DMA_DT_SIZE 0x60 + +#define ATI_REG_MODEM_MIRROR 0x7c +#define ATI_REG_AUDIO_MIRROR 0x80 + +#define ATI_REG_6CH_REORDER 0x84 /* reorder slots for 6ch */ +#define ATI_REG_6CH_REORDER_EN (1U<<0) /* 3,4,7,8,6,9 -> 3,4,6,9,7,8 */ + +#define ATI_REG_FIFO_FLUSH 0x88 +#define ATI_REG_FIFO_OUT_FLUSH (1U<<0) +#define ATI_REG_FIFO_IN_FLUSH (1U<<1) + +/* LINKPTR */ +#define ATI_REG_LINKPTR_EN (1U<<0) + +/* [INT|OUT|SPDIF]_DMA_DT_SIZE */ +#define ATI_REG_DMA_DT_SIZE (0xffffU<<0) +#define ATI_REG_DMA_FIFO_USED (0x1fU<<16) +#define ATI_REG_DMA_FIFO_FREE (0x1fU<<21) +#define ATI_REG_DMA_STATE (7U<<26) + +#define ATI_MAX_DESCRIPTORS 256 /* max number of descriptor packets */ + +/* codec detection constant indicating the interrupt flags */ +#define ALL_CODECS_NOT_READY \ + (ATI_REG_ISR_CODEC0_NOT_READY | ATI_REG_ISR_CODEC1_NOT_READY |\ + ATI_REG_ISR_CODEC2_NOT_READY) +#define CODEC_CHECK_BITS (ALL_CODECS_NOT_READY|ATI_REG_ISR_NEW_FRAME) + +#endif --- sys/dev/sound/pci/au88x0.c.orig Tue Nov 23 16:37:48 2004 +++ sys/dev/sound/pci/au88x0.c Thu Jul 12 12:04:19 2007 @@ -25,7 +25,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/au88x0.c,v 1.8.2.1 2004/11/23 08:37:48 yongari Exp $ + * $FreeBSD: src/sys/dev/sound/pci/au88x0.c,v 1.13 2007/06/17 06:10:41 ariff Exp $ */ #include @@ -332,7 +332,7 @@ struct au88x0_info *aui = arg; struct au88x0_chan_info *auci = au88x0_channel(aui, dir); - if (sndbuf_alloc(buf, aui->aui_dmat, aui->aui_bufsize) != 0) + if (sndbuf_alloc(buf, aui->aui_dmat, 0, aui->aui_bufsize) != 0) return (NULL); auci->auci_aui = aui; auci->auci_pcmchan = chan; @@ -555,7 +555,7 @@ for (auc = au88x0_chipsets; auc->auc_pci_id; ++auc) { if (auc->auc_pci_id == pci_id) { device_set_desc(dev, auc->auc_name); - return (0); + return BUS_PROBE_DEFAULT; } } return (ENXIO); @@ -572,10 +572,7 @@ uint32_t config; int error; - if ((aui = malloc(sizeof *aui, M_DEVBUF, M_NOWAIT|M_ZERO)) == NULL) { - device_printf(dev, "failed to allocate softc\n"); - return (ENXIO); - } + aui = malloc(sizeof(*aui), M_DEVBUF, M_WAITOK | M_ZERO); aui->aui_dev = dev; /* Model-specific parameters */ @@ -636,7 +633,7 @@ /* DMA mapping */ aui->aui_bufsize = pcm_getbuffersize(dev, AU88X0_BUFSIZE_MIN, AU88X0_BUFSIZE_DFLT, AU88X0_BUFSIZE_MAX); - error = bus_dma_tag_create(NULL, + error = bus_dma_tag_create(bus_get_dma_tag(dev), 2, 0, /* 16-bit alignment, no boundary */ BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, /* restrict to 4GB */ NULL, NULL, /* no filter */ --- sys/dev/sound/pci/aureal.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/aureal.c Thu Jul 12 12:04:19 2007 @@ -31,7 +31,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/aureal.c,v 1.29.2.2 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/aureal.c,v 1.36 2007/06/17 06:10:41 ariff Exp $"); /* PCI IDs of supported chips */ #define AU8820_PCI_ID 0x000112eb @@ -303,7 +303,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, au->parent_dmat, AU_BUFFSIZE) != 0) + if (sndbuf_alloc(ch->buffer, au->parent_dmat, 0, AU_BUFFSIZE) != 0) return NULL; return ch; } @@ -339,12 +339,12 @@ struct au_chinfo *ch = data; struct au_info *au = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (ch->dir == PCMDIR_PLAY) { au_setadb(au, 0x11, (go)? 1 : 0); - if (!go) { + if (go != PCMTRIG_START) { au_wr(au, 0, 0xf800, 0, 4); au_wr(au, 0, 0xf804, 0, 4); au_delroute(au, 0x58); @@ -537,7 +537,7 @@ { if (pci_get_devid(dev) == AU8820_PCI_ID) { device_set_desc(dev, "Aureal Vortex 8820"); - return 0; + return BUS_PROBE_DEFAULT; } return ENXIO; @@ -558,11 +558,7 @@ struct ac97_info *codec; char status[SND_STATUSLEN]; - if ((au = malloc(sizeof(*au), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + au = malloc(sizeof(*au), M_DEVBUF, M_WAITOK | M_ZERO); au->unit = device_get_unit(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -637,7 +633,8 @@ if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/aureal.h.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/aureal.h Thu Jan 6 09:43:18 2005 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/aureal.h,v 1.3.4.1 2005/01/30 01:00:03 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/aureal.h,v 1.4 2005/01/06 01:43:18 imp Exp $ */ #ifndef _AU8820_REG_H --- sys/dev/sound/pci/cmi.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/cmi.c Thu Jul 12 12:04:19 2007 @@ -48,10 +48,12 @@ #include #include +#include #include "mixer_if.h" +#include "mpufoi_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/cmi.c,v 1.29.2.1 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/cmi.c,v 1.44 2007/06/17 06:10:41 ariff Exp $"); /* Supported chip ID's */ #define CMI8338A_PCI_ID 0x010013f6 @@ -112,6 +114,13 @@ int spdif_enabled; unsigned int bufsz; struct sc_chinfo pch, rch; + + struct mpu401 *mpu; + mpu401_intr_t *mpu_intr; + struct resource *mpu_reg; + int mpu_regid; + bus_space_tag_t mpu_bt; + bus_space_handle_t mpu_bh; }; /* Channel caps */ @@ -340,7 +349,7 @@ ch->spd = DSP_DEFAULT_SPEED; ch->buffer = b; ch->dma_active = 0; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("cmichan_init failed\n")); return NULL; } @@ -466,12 +475,16 @@ struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + if (!PCMTRIG_COMMON(go)) + return 0; + snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { switch(go) { case PCMTRIG_START: cmi_ch0_start(sc, ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: cmi_ch0_stop(sc, ch); break; @@ -481,6 +494,7 @@ case PCMTRIG_START: cmi_ch1_start(sc, ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: cmi_ch1_stop(sc, ch); break; @@ -551,6 +565,9 @@ } } + if(sc->mpu_intr) { + (sc->mpu_intr)(sc->mpu); + } snd_mtxunlock(sc->lock); return; } @@ -729,8 +746,13 @@ cmi_initsys(struct sc_info* sc) { #ifdef SND_DYNSYSCTL - SYSCTL_ADD_INT(snd_sysctl_tree(sc->dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(sc->dev)), + /* XXX: an user should be able to set this with a control tool, + if not done before 7.0-RELEASE, this needs to be converted + to a device specific sysctl "dev.pcm.X.yyy" via + device_get_sysctl_*() as discussed on multimedia@ in msg-id + <861wujij2q.fsf@xps.des.no> */ + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "spdif_enabled", CTLFLAG_RW, &sc->spdif_enabled, 0, "enable SPDIF output at 44.1 kHz and above"); @@ -747,6 +769,74 @@ }; MIXER_DECLARE(cmi_mixer); +/* + * mpu401 functions + */ + +static unsigned char +cmi_mread(void *arg, struct sc_info *sc, int reg) +{ + unsigned int d; + + d = bus_space_read_1(0,0, 0x330 + reg); + /* printf("cmi_mread: reg %x %x\n",reg, d); + */ + return d; +} + +static void +cmi_mwrite(void *arg, struct sc_info *sc, int reg, unsigned char b) +{ + + bus_space_write_1(0,0,0x330 + reg , b); +} + +static int +cmi_muninit(void *arg, struct sc_info *sc) +{ + + snd_mtxlock(sc->lock); + sc->mpu_intr = 0; + sc->mpu = 0; + snd_mtxunlock(sc->lock); + + return 0; +} + +static kobj_method_t cmi_mpu_methods[] = { + KOBJMETHOD(mpufoi_read, cmi_mread), + KOBJMETHOD(mpufoi_write, cmi_mwrite), + KOBJMETHOD(mpufoi_uninit, cmi_muninit), + { 0, 0 } +}; + +static DEFINE_CLASS(cmi_mpu, cmi_mpu_methods, 0); + +static void +cmi_midiattach(struct sc_info *sc) { +/* + const struct { + int port,bits; + } *p, ports[] = { + {0x330,0}, + {0x320,1}, + {0x310,2}, + {0x300,3}, + {0,0} } ; + Notes, CMPCI_REG_VMPUSEL sets the io port for the mpu. Does + anyone know how to bus_space tag? +*/ + cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); + cmi_clr4(sc, CMPCI_REG_LEGACY_CTRL, + CMPCI_REG_VMPUSEL_MASK << CMPCI_REG_VMPUSEL_SHIFT); + cmi_set4(sc, CMPCI_REG_LEGACY_CTRL, + 0 << CMPCI_REG_VMPUSEL_SHIFT ); + cmi_set4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); + sc->mpu = mpu401_init(&cmi_mpu_class, sc, cmi_intr, &sc->mpu_intr); +} + + + /* ------------------------------------------------------------------------- */ /* Power and reset */ @@ -802,6 +892,10 @@ CMPCI_REG_TDMA_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE | CMPCI_REG_CH1_ENABLE); + cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); + + if( sc->mpu ) + sc->mpu_intr = 0; } /* ------------------------------------------------------------------------- */ @@ -812,16 +906,16 @@ switch(pci_get_devid(dev)) { case CMI8338A_PCI_ID: device_set_desc(dev, "CMedia CMI8338A"); - return 0; + return BUS_PROBE_DEFAULT; case CMI8338B_PCI_ID: device_set_desc(dev, "CMedia CMI8338B"); - return 0; + return BUS_PROBE_DEFAULT; case CMI8738_PCI_ID: device_set_desc(dev, "CMedia CMI8738"); - return 0; + return BUS_PROBE_DEFAULT; case CMI8738B_PCI_ID: device_set_desc(dev, "CMedia CMI8738B"); - return 0; + return BUS_PROBE_DEFAULT; default: return ENXIO; } @@ -830,19 +924,12 @@ static int cmi_attach(device_t dev) { - struct snddev_info *d; struct sc_info *sc; u_int32_t data; char status[SND_STATUSLEN]; - d = device_get_softc(dev); - sc = malloc(sizeof(struct sc_info), M_DEVBUF, M_NOWAIT | M_ZERO); - if (sc == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_cmi softc"); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); @@ -850,8 +937,8 @@ sc->dev = dev; sc->regid = PCIR_BAR(0); - sc->reg = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->regid, - 0, BUS_SPACE_UNRESTRICTED, 1, RF_ACTIVE); + sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->regid, + RF_ACTIVE); if (!sc->reg) { device_printf(dev, "cmi_attach: Cannot allocate bus resource\n"); goto bad; @@ -859,6 +946,9 @@ sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); + if (0) + cmi_midiattach(sc); + sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); @@ -870,14 +960,15 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, CMI_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, - /*lockfunc*/busdma_lock_mutex, - /*lockfunc*/&Giant, + /*lockfunc*/NULL, + /*lockfunc*/NULL, &sc->parent_dmat) != 0) { device_printf(dev, "cmi_attach: Unable to create dma tag\n"); goto bad; @@ -938,7 +1029,12 @@ bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + if(sc->mpu) + mpu401_uninit(sc->mpu); bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); + if (sc->mpu_reg) + bus_release_resource(dev, SYS_RES_IOPORT, sc->mpu_regid, sc->mpu_reg); + snd_mtxfree(sc->lock); free(sc, M_DEVBUF); @@ -1009,4 +1105,5 @@ DRIVER_MODULE(snd_cmi, pci, cmi_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_cmi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_DEPEND(snd_cmi, midi, 1,1,1); MODULE_VERSION(snd_cmi, 1); --- sys/dev/sound/pci/cmireg.h.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/cmireg.h Thu Jan 6 09:43:19 2005 @@ -26,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/cmireg.h,v 1.2.8.1 2005/01/30 01:00:03 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/cmireg.h,v 1.3 2005/01/06 01:43:19 imp Exp $ */ /* C-Media CMI8x38 Audio Chip Support */ --- sys/dev/sound/pci/cs4281.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/cs4281.c Thu Jul 12 12:04:19 2007 @@ -37,7 +37,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/cs4281.c,v 1.20.2.1 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/cs4281.c,v 1.26 2007/06/17 06:10:41 ariff Exp $"); #define CS4281_DEFAULT_BUFSZ 16384 @@ -314,7 +314,7 @@ struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { return NULL; } ch->parent = sc; @@ -425,6 +425,7 @@ adcdac_prog(ch); adcdac_go(ch, 1); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: adcdac_go(ch, 0); break; @@ -742,7 +743,7 @@ if (s) device_set_desc(dev, s); - return s ? 0 : ENXIO; + return s ? BUS_PROBE_DEFAULT : ENXIO; } static int @@ -753,11 +754,7 @@ u_int32_t data; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); @@ -824,7 +821,8 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, CS4281_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/cs4281.h.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/cs4281.h Thu Jan 6 09:43:19 2005 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/cs4281.h,v 1.3.8.1 2005/01/30 01:00:03 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/cs4281.h,v 1.4 2005/01/06 01:43:19 imp Exp $ */ #ifndef _CS4281_H_ --- sys/dev/sound/pci/csa.c.orig Thu Nov 3 19:49:12 2005 +++ sys/dev/sound/pci/csa.c Thu Jul 12 12:04:19 2007 @@ -48,7 +48,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/csa.c,v 1.30.2.2 2005/11/03 11:49:12 glebius Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/csa.c,v 1.37 2007/03/17 19:37:09 ariff Exp $"); /* This is the pci device id. */ #define CS4610_PCI_ID 0x60011013 @@ -82,7 +82,10 @@ struct resource *r); static int csa_setup_intr(device_t bus, device_t child, struct resource *irq, int flags, - driver_intr_t *intr, void *arg, void **cookiep); +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif + driver_intr_t *intr, void *arg, void **cookiep); static int csa_teardown_intr(device_t bus, device_t child, struct resource *irq, void *cookie); static driver_intr_t csa_intr; @@ -225,7 +228,7 @@ card = csa_findcard(dev); if (card) { device_set_desc(dev, card->name); - return 0; + return BUS_PROBE_DEFAULT; } return ENXIO; } @@ -336,23 +339,31 @@ { csa_res *resp; sc_p scp; + struct sndcard_func *func; int err; scp = device_get_softc(dev); resp = &scp->res; - err = 0; - if (scp->midi != NULL) + if (scp->midi != NULL) { + func = device_get_ivars(scp->midi); err = device_delete_child(dev, scp->midi); - if (err) - return err; - scp->midi = NULL; + if (err != 0) + return err; + if (func != NULL) + free(func, M_DEVBUF); + scp->midi = NULL; + } - if (scp->pcm != NULL) + if (scp->pcm != NULL) { + func = device_get_ivars(scp->pcm); err = device_delete_child(dev, scp->pcm); - if (err) - return err; - scp->pcm = NULL; + if (err != 0) + return err; + if (func != NULL) + free(func, M_DEVBUF); + scp->pcm = NULL; + } bus_teardown_intr(dev, resp->irq, scp->ih); bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); @@ -439,12 +450,21 @@ static int csa_setup_intr(device_t bus, device_t child, struct resource *irq, int flags, +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif driver_intr_t *intr, void *arg, void **cookiep) { sc_p scp; csa_res *resp; struct sndcard_func *func; +#if __FreeBSD_version >= 700031 + if (filter != NULL) { + printf("ata-csa.c: we cannot use a filter here\n"); + return (EINVAL); + } +#endif scp = device_get_softc(bus); resp = &scp->res; @@ -644,7 +664,7 @@ /* * Set the serial port FIFO pointer to the first sample in the FIFO. */ -#if notdef +#ifdef notdef csa_writeio(resp, BA0_SERBSP, 0); #endif /* notdef */ @@ -698,7 +718,7 @@ * First, lets wait a short while to let things settle out a bit, * and to prevent retrying the read too quickly. */ -#if notdef +#ifdef notdef DELAY(10000000L); /* clw */ #else DELAY(1000); @@ -728,7 +748,7 @@ * Power down the DAC and ADC. We will power them up (if) when we need * them. */ -#if notdef +#ifdef notdef csa_writeio(resp, BA0_AC97_POWERDOWN, 0x300); #endif /* notdef */ @@ -736,7 +756,7 @@ * Turn off the Processor by turning off the software clock enable flag in * the clock control register. */ -#if notdef +#ifdef notdef clkcr1 = csa_readio(resp, BA0_CLKCR1) & ~CLKCR1_SWCE; csa_writeio(resp, BA0_CLKCR1, clkcr1); #endif /* notdef */ --- sys/dev/sound/pci/csapcm.c.orig Thu Nov 3 19:49:12 2005 +++ sys/dev/sound/pci/csapcm.c Thu Jul 12 12:04:19 2007 @@ -38,7 +38,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/csapcm.c,v 1.31.2.3 2005/11/03 11:49:12 glebius Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/csapcm.c,v 1.41 2007/06/17 06:10:41 ariff Exp $"); /* Buffer size on dma transfer. Fixed for CS416x. */ #define CS461x_BUFFSIZE (4 * 1024) @@ -534,7 +534,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, csa->parent_dmat, CS461x_BUFFSIZE) != 0) + if (sndbuf_alloc(ch->buffer, csa->parent_dmat, 0, CS461x_BUFFSIZE) != 0) return NULL; return ch; } @@ -569,7 +569,7 @@ struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { @@ -667,6 +667,14 @@ /* Crank up the power on the DAC and ADC. */ csa_setplaysamplerate(resp, 8000); csa_setcapturesamplerate(resp, 8000); + /* Set defaults */ + csa_writeio(resp, BA0_EGPIODR, EGPIODR_GPOE0); + csa_writeio(resp, BA0_EGPIOPTR, EGPIOPTR_GPPT0); + /* Power up amplifier */ + csa_writeio(resp, BA0_EGPIODR, csa_readio(resp, BA0_EGPIODR) | + EGPIODR_GPOE2); + csa_writeio(resp, BA0_EGPIOPTR, csa_readio(resp, BA0_EGPIOPTR) | + EGPIOPTR_GPPT2); return 0; } @@ -696,7 +704,9 @@ if (resp->irq == NULL) return (1); } - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/CS461x_BUFFSIZE, /*boundary*/CS461x_BUFFSIZE, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), + /*alignment*/CS461x_BUFFSIZE, + /*boundary*/CS461x_BUFFSIZE, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -714,6 +724,8 @@ { csa_res *resp; + KASSERT(csa != NULL, ("called with bogus resource structure")); + resp = &csa->res; if (resp->irq != NULL) { if (csa->ih) @@ -733,10 +745,8 @@ bus_dma_tag_destroy(csa->parent_dmat); csa->parent_dmat = NULL; } - if (csa != NULL) { - free(csa, M_DEVBUF); - csa = NULL; - } + + free(csa, M_DEVBUF); } static int @@ -767,9 +777,7 @@ struct ac97_info *codec; struct sndcard_func *func; - csa = malloc(sizeof(*csa), M_DEVBUF, M_NOWAIT | M_ZERO); - if (csa == NULL) - return (ENOMEM); + csa = malloc(sizeof(*csa), M_DEVBUF, M_WAITOK | M_ZERO); unit = device_get_unit(dev); func = device_get_ivars(dev); csa->binfo = func->varinfo; --- sys/dev/sound/pci/csareg.h.orig Thu Nov 3 19:49:12 2005 +++ sys/dev/sound/pci/csareg.h Mon Jun 27 15:43:57 2005 @@ -27,7 +27,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/csareg.h,v 1.3.8.1 2005/11/03 11:49:12 glebius Exp $ + * $FreeBSD: src/sys/dev/sound/pci/csareg.h,v 1.4 2005/06/27 07:43:57 glebius Exp $ */ #ifndef _CSA_REG_H --- sys/dev/sound/pci/csavar.h.orig Thu Nov 3 19:49:12 2005 +++ sys/dev/sound/pci/csavar.h Mon Jun 27 15:43:57 2005 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/csavar.h,v 1.4.8.1 2005/11/03 11:49:12 glebius Exp $ + * $FreeBSD: src/sys/dev/sound/pci/csavar.h,v 1.5 2005/06/27 07:43:57 glebius Exp $ */ #ifndef _CSA_VAR_H --- sys/dev/sound/pci/ds1-fw.h.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/ds1-fw.h Thu Jan 6 09:43:19 2005 @@ -30,7 +30,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/ds1-fw.h,v 1.3.8.1 2005/01/30 01:00:03 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/ds1-fw.h,v 1.4 2005/01/06 01:43:19 imp Exp $ */ #ifndef _HWMCODE_ #define _HWMCODE_ --- sys/dev/sound/pci/ds1.c.orig Sun Jan 30 09:00:03 2005 +++ sys/dev/sound/pci/ds1.c Thu Jul 12 12:04:19 2007 @@ -33,7 +33,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/ds1.c,v 1.40.2.2 2005/01/30 01:00:03 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/ds1.c,v 1.52 2007/06/17 06:10:41 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -490,7 +490,7 @@ ch->fmt = AFMT_U8; ch->spd = 8000; ch->run = 0; - if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, 0, sc->bufsz) != 0) return NULL; else { ch->lsnum = sc->pslotfree; @@ -545,7 +545,7 @@ struct sc_info *sc = ch->parent; int stereo; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; if (go == PCMTRIG_START) { @@ -621,7 +621,7 @@ ch->dir = dir; ch->fmt = AFMT_U8; ch->spd = 8000; - if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, 0, sc->bufsz) != 0) return NULL; else { ch->slot = (ch->num == DS1_RECPRIMARY)? sc->rbank + 2: sc->rbank; @@ -673,7 +673,7 @@ struct sc_info *sc = ch->parent; u_int32_t x; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { ch->run = 1; @@ -743,13 +743,17 @@ for (i = 0; i < DS1_CHANS; i++) { if (sc->pch[i].run) { x = 1; + snd_mtxunlock(sc->lock); chn_intr(sc->pch[i].channel); + snd_mtxlock(sc->lock); } } for (i = 0; i < 2; i++) { if (sc->rch[i].run) { x = 1; + snd_mtxunlock(sc->lock); chn_intr(sc->rch[i].channel); + snd_mtxlock(sc->lock); } } i = ds_rd(sc, YDSXGR_MODE, 4); @@ -829,9 +833,11 @@ memsz += (64 + 1) * 4; if (sc->regbase == NULL) { - if (bus_dma_tag_create(NULL, 2, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, - NULL, NULL, memsz, 1, memsz, 0, busdma_lock_mutex, - &Giant, &sc->control_dmat)) + if (bus_dma_tag_create(bus_get_dma_tag(sc->dev), 2, 0, + BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, + NULL, NULL, memsz, 1, memsz, 0, NULL, + NULL, &sc->control_dmat)) return -1; if (bus_dmamem_alloc(sc->control_dmat, &buf, BUS_DMA_NOWAIT, &sc->map)) return -1; @@ -923,7 +929,7 @@ i = ds_finddev(pci_get_devid(dev), subdev); if (i >= 0) { device_set_desc(dev, ds_devs[i].name); - return 0; + return BUS_PROBE_DEFAULT; } else return ENXIO; } @@ -937,12 +943,8 @@ struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_ds1 softc"); sc->dev = dev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); sc->type = ds_finddev(pci_get_devid(dev), subdev); @@ -966,13 +968,14 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, DS1_BUFFSIZE, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/sc->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &sc->buffer_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &sc->buffer_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -986,12 +989,23 @@ codec = AC97_CREATE(dev, sc, ds_ac97); if (codec == NULL) goto bad; + /* + * Turn on inverted external amplifier sense flags for few + * 'special' boards. + */ + switch (subdev) { + case 0x81171033: /* NEC ValueStar (VT550/0) */ + ac97_setflags(codec, ac97_getflags(codec) | AC97_F_EAPD_INV); + break; + default: + break; + } mixer_init(dev, ac97_getmixerclass(), codec); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); - if (!sc->irq || snd_setup_intr(dev, sc->irq, 0, ds_intr, sc, &sc->ih)) { + if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ds_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } --- sys/dev/sound/pci/emu10k1.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/emu10k1.c Thu Jul 12 12:04:19 2007 @@ -28,14 +28,16 @@ #include #include -#include #include "emu10k1-alsa%diked.h" #include #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/emu10k1.c,v 1.52.2.2 2005/01/30 01:00:04 imp Exp $"); +#include +#include "mpufoi_if.h" + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/emu10k1.c,v 1.69 2007/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -45,6 +47,7 @@ #define EMUMAXPAGES (WAVEOUT_MAXBUFSIZE * NUM_G / EMUPAGESIZE) #define EMU10K1_PCI_ID 0x00021102 /* 1102 => Creative Labs Vendor ID */ #define EMU10K2_PCI_ID 0x00041102 +#define EMU10K3_PCI_ID 0x00081102 #define EMU_DEFAULT_BUFSZ 4096 #define EMU_MAX_CHANS 8 #define EMU_CHANS 4 @@ -84,7 +87,7 @@ struct emu_voice { int vnum; - int b16:1, stereo:1, busy:1, running:1, ismaster:1; + unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1; int speed; int start, end, vol; int fxrt1; /* FX routing */ @@ -136,6 +139,9 @@ struct emu_voice voice[64]; struct sc_pchinfo pch[EMU_MAX_CHANS]; struct sc_rchinfo rch[3]; + struct mpu401 *mpu; + mpu401_intr_t *mpu_intr; + int mputx; }; /* -------------------------------------------------------------------- */ @@ -271,7 +277,7 @@ static void emu_wrefx(struct sc_info *sc, unsigned int pc, unsigned int data) { - pc += sc->audigy ? AUDIGY_CODEBASE : MICROCODEBASE; + pc += sc->audigy ? A_MICROCODEBASE : MICROCODEBASE; emu_wrptr(sc, 0, pc, data); } @@ -493,12 +499,12 @@ m->buf = tmp_addr; m->slave = s; if (sc->audigy) { - m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_LEFT << 8 | - FXBUS_PCM_RIGHT << 16 | FXBUS_MIDI_REVERB << 24; + m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_RIGHT << 8 | + FXBUS_PCM_LEFT << 16 | FXBUS_MIDI_REVERB << 24; m->fxrt2 = 0x3f3f3f3f; /* No effects on second route */ } else { - m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_LEFT << 4 | - FXBUS_PCM_RIGHT << 8 | FXBUS_MIDI_REVERB << 12; + m->fxrt1 = FXBUS_MIDI_CHORUS | FXBUS_PCM_RIGHT << 4 | + FXBUS_PCM_LEFT << 8 | FXBUS_MIDI_REVERB << 12; m->fxrt2 = 0; } @@ -789,7 +795,7 @@ struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); @@ -889,7 +895,7 @@ break; } sc->rnum++; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; else { snd_mtxlock(sc->lock); @@ -952,6 +958,9 @@ struct sc_info *sc = ch->parent; u_int32_t val, sz; + if (!PCMTRIG_COMMON(go)) + return 0; + switch(sc->bufsz) { case 4096: sz = ADCBS_BUFSIZE_4096; @@ -1058,8 +1067,65 @@ }; CHANNEL_DECLARE(emurchan); +static unsigned char +emu_mread(void *arg, struct sc_info *sc, int reg) +{ + unsigned int d; + + d = emu_rd(sc, 0x18 + reg, 1); + return d; +} + +static void +emu_mwrite(void *arg, struct sc_info *sc, int reg, unsigned char b) +{ + + emu_wr(sc, 0x18 + reg, b, 1); +} + +static int +emu_muninit(void *arg, struct sc_info *sc) +{ + + snd_mtxlock(sc->lock); + sc->mpu_intr = 0; + snd_mtxunlock(sc->lock); + + return 0; +} + +static kobj_method_t emu_mpu_methods[] = { + KOBJMETHOD(mpufoi_read, emu_mread), + KOBJMETHOD(mpufoi_write, emu_mwrite), + KOBJMETHOD(mpufoi_uninit, emu_muninit), + { 0, 0 } +}; + +static DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0); + +static void +emu_intr2(void *p) +{ + struct sc_info *sc = (struct sc_info *)p; + + if (sc->mpu_intr) + (sc->mpu_intr)(sc->mpu); +} + +static void +emu_midiattach(struct sc_info *sc) +{ + int i; + + i = emu_rd(sc, INTE, 4); + i |= INTE_MIDIRXENABLE; + emu_wr(sc, INTE, i, 4); + + sc->mpu = mpu401_init(&emu_mpu_class, sc, emu_intr2, &sc->mpu_intr); +} /* -------------------------------------------------------------------- */ /* The interrupt handler */ + static void emu_intr(void *data) { @@ -1099,6 +1165,11 @@ #endif } + if (stat & IPR_MIDIRECVBUFEMPTY) + if (sc->mpu_intr) { + (sc->mpu_intr)(sc->mpu); + ack |= IPR_MIDIRECVBUFEMPTY | IPR_MIDITRANSBUFEMPTY; + } if (stat & ~ack) device_printf(sc->dev, "dodgy irq: %x (harmless)\n", stat & ~ack); @@ -1311,12 +1382,12 @@ /* Audigy 2 (EMU10K2) DSP Registers: FX Bus - 0x000-0x00f : 16 registers (???) + 0x000-0x00f : 16 registers (?) Input 0x040/0x041 : AC97 Codec (l/r) 0x042/0x043 : ADC, S/PDIF (l/r) 0x044/0x045 : Optical S/PDIF in (l/r) - 0x046/0x047 : ??? + 0x046/0x047 : ? 0x048/0x049 : Line/Mic 2 (l/r) 0x04a/0x04b : RCA S/PDIF (l/r) 0x04c/0x04d : Aux 2 (l/r) @@ -1327,11 +1398,11 @@ 0x066/0x067 : Digital Rear (l/r) 0x068/0x069 : Analog Front (l/r) 0x06a/0x06b : Analog Center/LFE - 0x06c/0x06d : ??? + 0x06c/0x06d : ? 0x06e/0x06f : Analog Rear (l/r) 0x070/0x071 : AC97 Output (l/r) - 0x072/0x073 : ??? - 0x074/0x075 : ??? + 0x072/0x073 : ? + 0x074/0x075 : ? 0x076/0x077 : ADC Recording Buffer (l/r) Constants 0x0c0 - 0x0c4 = 0 - 4 @@ -1340,9 +1411,9 @@ 0x0cb = 0x10000000, 0x0cc = 0x20000000, 0x0cd = 0x40000000 0x0ce = 0x80000000, 0x0cf = 0x7fffffff, 0x0d0 = 0xffffffff 0x0d1 = 0xfffffffe, 0x0d2 = 0xc0000000, 0x0d3 = 0x41fbbcdc - 0x0d4 = 0x5a7ef9db, 0x0d5 = 0x00100000, 0x0dc = 0x00000001 (???) + 0x0d4 = 0x5a7ef9db, 0x0d5 = 0x00100000, 0x0dc = 0x00000001 (?) Temporary Values - 0x0d6 : Accumulator (???) + 0x0d6 : Accumulator (?) 0x0d7 : Condition Register 0x0d8 : Noise source 0x0d9 : Noise source @@ -1559,10 +1630,10 @@ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ALFE), C_00000000, GPR(1), GPR(2), &pc); /* Digital Center = GPR[0] + GPR[2] */ - emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_CENTER), C_00000000, + emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_CENTER), C_00000000, GPR(0), GPR(2), &pc); /* Digital Sub = GPR[1] + GPR[2] */ - emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_LFE), C_00000000, + emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_LFE), C_00000000, GPR(1), GPR(2), &pc); /* Headphones[l/r] = GPR[0/1] */ @@ -1870,6 +1941,8 @@ emu_free(sc, sc->mem.ptb_pages); emu_free(sc, sc->mem.silent_page); + if(sc->mpu) + mpu401_uninit(sc->mpu); return 0; } @@ -1890,12 +1963,16 @@ s = "Creative Audigy (EMU10K2)"; break; + case EMU10K3_PCI_ID: + s = "Creative Audigy 2 (EMU10K3)"; + break; + default: return ENXIO; } device_set_desc(dev, s); - return 0; + return BUS_PROBE_LOW_PRIORITY; } static int @@ -1907,16 +1984,12 @@ int i, gotmic; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10k1 softc"); sc->dev = dev; sc->type = pci_get_devid(dev); sc->rev = pci_get_revid(dev); - sc->audigy = (sc->type == EMU10K2_PCI_ID); + sc->audigy = sc->type == EMU10K2_PCI_ID || sc->type == EMU10K3_PCI_ID; sc->audigy2 = (sc->audigy && sc->rev == 0x04); sc->nchans = sc->audigy ? 8 : 4; sc->addrmask = sc->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK; @@ -1937,7 +2010,8 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, EMU_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/1 << 31, /* can only access 0-2gb */ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -1958,6 +2032,8 @@ gotmic = (ac97_getcaps(codec) & AC97_CAP_MICCHANNEL) ? 1 : 0; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; + emu_midiattach(sc); + i = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE); @@ -2033,8 +2109,10 @@ }; DRIVER_MODULE(snd_emu10k1, pci, emu_driver, pcm_devclass, 0, 0); +DRIVER_MODULE(snd_emu10k1, cardbus, emu_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_emu10k1, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10k1, 1); +MODULE_DEPEND(snd_emu10k1, midi, 1, 1, 1); /* dummy driver to silence the joystick device */ static int --- sys/dev/sound/pci/emu10kx-midi.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/emu10kx-midi.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,250 @@ +/*- + * Copyright (c) 1999 Seigo Tanimura + * (c) 2003 Mathew Kanner + * Copyright (c) 2003-2006 Yuriy Tsibizov + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/pci/emu10kx-midi.c,v 1.3 2006/07/16 20:10:08 netchild Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include "mpufoi_if.h" + +#include "opt_emu10kx.h" +#include +#include "emu10k1-alsa%diked.h" + +struct emu_midi_softc { + struct mtx mtx; + device_t dev; + struct mpu401 *mpu; + mpu401_intr_t *mpu_intr; + struct emu_sc_info *card; + int port; /* I/O port or I/O ptr reg */ + int is_emu10k1; + int fflags; /* File flags */ + int ihandle; /* interrupt manager handle */ +}; + +static uint32_t emu_midi_card_intr(void *p, uint32_t arg); +static devclass_t emu_midi_devclass; + +static unsigned char +emu_mread(void *arg __unused, struct emu_midi_softc *sc, int reg) +{ + unsigned int d; + + d = 0; + if (sc->is_emu10k1) + d = emu_rd(sc->card, 0x18 + reg, 1); + else + d = emu_rdptr(sc->card, 0, sc->port + reg); + + return (d); +} + +static void +emu_mwrite(void *arg __unused, struct emu_midi_softc *sc, int reg, unsigned char b) +{ + + if (sc->is_emu10k1) + emu_wr(sc->card, 0x18 + reg, b, 1); + else + emu_wrptr(sc->card, 0, sc->port + reg, b); +} + +static int +emu_muninit(void *arg __unused, struct emu_midi_softc *sc) +{ + + mtx_lock(&sc->mtx); + sc->mpu_intr = NULL; + mtx_unlock(&sc->mtx); + + return (0); +} + +static kobj_method_t emu_mpu_methods[] = { + KOBJMETHOD(mpufoi_read, emu_mread), + KOBJMETHOD(mpufoi_write, emu_mwrite), + KOBJMETHOD(mpufoi_uninit, emu_muninit), + {0, 0} +}; +static DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0); + +static uint32_t +emu_midi_card_intr(void *p, uint32_t intr_status) +{ + struct emu_midi_softc *sc = (struct emu_midi_softc *)p; + if (sc->mpu_intr) + (sc->mpu_intr) (sc->mpu); + if (sc->mpu_intr == NULL) { + /* We should read MIDI event to unlock card after + * interrupt. XXX - check, why this happens. */ +#ifdef SND_EMU10KX_DEBUG + device_printf(sc->dev, "midi interrupt %08x without interrupt handler, force mread!\n", intr_status); +#endif + (void)emu_mread((void *)(NULL), sc, 0); + } + return (intr_status); /* Acknowledge everything */ +} + +static void +emu_midi_intr(void *p) +{ + (void)emu_midi_card_intr(p, 0); +} + +static int +emu_midi_probe(device_t dev) +{ + struct emu_midi_softc *scp; + uintptr_t func, r, is_emu10k1; + + r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); + if (func != SCF_MIDI) + return (ENXIO); + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &is_emu10k1); + scp->is_emu10k1 = is_emu10k1 ? 1 : 0; + + device_set_desc(dev, "EMU10Kx MIDI Interface"); + return (0); +} + +static int +emu_midi_attach(device_t dev) +{ + struct emu_midi_softc * scp; + struct sndcard_func *func; + struct emu_midiinfo *midiinfo; + uint32_t inte_val, ipr_val; + + scp = device_get_softc(dev); + func = device_get_ivars(dev); + + scp->dev = dev; + midiinfo = (struct emu_midiinfo *)func->varinfo; + scp->port = midiinfo->port; + scp->card = midiinfo->card; + + mtx_init(&scp->mtx, "emu10kx_midi", NULL, MTX_DEF); + + if (scp->is_emu10k1) { + /* SB Live! - only one MIDI device here */ + inte_val = 0; + /* inte_val |= INTE_MIDITXENABLE;*/ + inte_val |= INTE_MIDIRXENABLE; + ipr_val = IPR_MIDITRANSBUFEMPTY; + ipr_val |= IPR_MIDIRECVBUFEMPTY; + } else { + if (scp->port == A_MUDATA1) { + /* EXTERNAL MIDI (AudigyDrive) */ + inte_val = 0; + /* inte_val |= A_INTE_MIDITXENABLE1;*/ + inte_val |= INTE_MIDIRXENABLE; + ipr_val = IPR_MIDITRANSBUFEMPTY; + ipr_val |= IPR_MIDIRECVBUFEMPTY; + } else { + /* MIDI hw config port 2 */ + inte_val = 0; + /* inte_val |= A_INTE_MIDITXENABLE2;*/ + inte_val |= INTE_A_MIDIRXENABLE2; + ipr_val = IPR_A_MIDITRANSBUFEMPTY2; + ipr_val |= IPR_A_MIDIRECVBUFEMPTY2; + } + } + if (inte_val == 0) + return (ENXIO); + + scp->ihandle = emu_intr_register(scp->card, inte_val, ipr_val, &emu_midi_card_intr, scp); + /* Init the interface. */ + scp->mpu = mpu401_init(&emu_mpu_class, scp, emu_midi_intr, &scp->mpu_intr); + if (scp->mpu == NULL) { + emu_intr_unregister(scp->card, scp->ihandle); + mtx_destroy(&scp->mtx); + return (ENOMEM); + } + /* + * XXX I don't know how to check for Live!Drive / AudigyDrive + * presence. Let's hope that IR enabling code will not harm if + * it is not present. + */ + if (scp->is_emu10k1) + emu_enable_ir(scp->card); + else { + if (scp->port == A_MUDATA1) + emu_enable_ir(scp->card); + } + + return (0); +} + + +static int +emu_midi_detach(device_t dev) +{ + struct emu_midi_softc *scp; + + scp = device_get_softc(dev); + mpu401_uninit(scp->mpu); + emu_intr_unregister(scp->card, scp->ihandle); + mtx_destroy(&scp->mtx); + return (0); +} + +static device_method_t emu_midi_methods[] = { + DEVMETHOD(device_probe, emu_midi_probe), + DEVMETHOD(device_attach, emu_midi_attach), + DEVMETHOD(device_detach, emu_midi_detach), + + {0, 0}, +}; + +static driver_t emu_midi_driver = { + "midi", + emu_midi_methods, + sizeof(struct emu_midi_softc), +}; +DRIVER_MODULE(snd_emu10kx_midi, emu10kx, emu_midi_driver, emu_midi_devclass, 0, 0); +MODULE_DEPEND(snd_emu10kx_midi, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); +MODULE_DEPEND(snd_emu10kx_midi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_emu10kx_midi, SND_EMU10KX_PREFVER); --- sys/dev/sound/pci/emu10kx-pcm.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/emu10kx-pcm.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,1190 @@ +/*- + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2003-2006 Yuriy Tsibizov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/pci/emu10kx-pcm.c,v 1.10 2007/06/17 06:10:42 ariff Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mixer_if.h" + +#include "opt_emu10kx.h" +#include +#include "emu10k1-alsa%diked.h" + +struct emu_pcm_pchinfo { + int spd; + int fmt; + int blksz; + int run; + struct emu_voice *master; + struct emu_voice *slave; + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct emu_pcm_info *pcm; + int timer; +}; + +struct emu_pcm_rchinfo { + int spd; + int fmt; + int blksz; + int run; + uint32_t idxreg; + uint32_t basereg; + uint32_t sizereg; + uint32_t setupreg; + uint32_t irqmask; + uint32_t iprmask; + int ihandle; + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct emu_pcm_info *pcm; +}; + +/* XXX Hardware playback channels */ +#define MAX_CHANNELS 4 + +#if MAX_CHANNELS > 13 +#error Too many hardware channels defined. 13 is the maximum +#endif + +struct emu_pcm_info { + struct mtx *lock; + device_t dev; /* device information */ + struct snddev_info *devinfo; /* pcm device information */ + struct emu_sc_info *card; + struct emu_pcm_pchinfo pch[MAX_CHANNELS]; /* hardware channels */ + int pnum; /* next free channel number */ + struct emu_pcm_rchinfo rch_adc; + struct emu_pcm_rchinfo rch_efx; + struct emu_route rt; + struct emu_route rt_mono; + int route; + int ihandle; /* interrupt handler */ + unsigned int bufsz; + int is_emu10k1; + struct ac97_info *codec; + uint32_t ac97_state[0x7F]; +}; + + +static uint32_t emu_rfmt_adc[] = { + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; +static struct pcmchan_caps emu_reccaps_adc = { + 8000, 48000, emu_rfmt_adc, 0 +}; + +static uint32_t emu_rfmt_efx[] = { + AFMT_S16_LE, + 0 +}; + +static struct pcmchan_caps emu_reccaps_efx_live = { + 48000*32, 48000*32, emu_rfmt_efx, 0 +}; + +static struct pcmchan_caps emu_reccaps_efx_audigy = { + 48000*64, 48000*64, emu_rfmt_efx, 0 +}; + +static uint32_t emu_pfmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; +static uint32_t emu_pfmt_mono[] = { + AFMT_U8, + AFMT_S16_LE, + 0 +}; + +static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; +static struct pcmchan_caps emu_playcaps_mono = {4000, 48000, emu_pfmt_mono, 0}; + +static int emu10k1_adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; +/* audigy supports 12kHz. */ +static int emu10k2_adcspeed[9] = {48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000}; + +static uint32_t emu_pcm_intr(void *pcm, uint32_t stat); + +static const struct emu_dspmix_props { + u_int8_t present; +} dspmix [SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = {1}, + [SOUND_MIXER_PCM] = {1}, +}; + +static int +emu_dspmixer_init(struct snd_mixer *m) +{ + int i; + int v; + + v = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (dspmix[i].present) + v |= 1 << i; + } + mix_setdevs(m, v); + + mix_setrecdevs(m, 0); + return (0); +} + +static int +emu_dspmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct emu_pcm_info *sc; + + sc = mix_getdevinfo(m); + + switch (dev) { + case SOUND_MIXER_VOLUME: + switch (sc->route) { + case RT_REAR: + emumix_set_volume(sc->card, M_MASTER_REAR_L, left); + emumix_set_volume(sc->card, M_MASTER_REAR_R, right); + break; + case RT_CENTER: + emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); + break; + case RT_SUB: + emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); + break; + } + break; + case SOUND_MIXER_PCM: + switch (sc->route) { + case RT_REAR: + emumix_set_volume(sc->card, M_FX2_REAR_L, left); + emumix_set_volume(sc->card, M_FX3_REAR_R, right); + break; + case RT_CENTER: + emumix_set_volume(sc->card, M_FX4_CENTER, (left+right)/2); + break; + case RT_SUB: + emumix_set_volume(sc->card, M_FX5_SUBWOOFER, (left+right)/2); + break; + } + break; + default: + device_printf(sc->dev, "mixer error: unknown device %d\n", dev); + } + return (0); +} + +static int +emu_dspmixer_setrecsrc(struct snd_mixer *m __unused, u_int32_t src __unused) +{ + return (0); +} + +static kobj_method_t emudspmixer_methods[] = { + KOBJMETHOD(mixer_init, emu_dspmixer_init), + KOBJMETHOD(mixer_set, emu_dspmixer_set), + KOBJMETHOD(mixer_setrecsrc, emu_dspmixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(emudspmixer); + +/* + * AC97 emulation code for Audigy and later cards. + * Some parts of AC97 codec are not used by hardware, but can be used + * to change some DSP controls via AC97 mixer interface. This includes: + * - master volume controls MASTER_FRONT_[R|L] + * - pcm volume controls FX[0|1]_FRONT_[R|L] + * - rec volume controls MASTER_REC_[R|L] + * We do it because we need to put it under user control.... + * We also keep some parts of AC97 disabled to get better sound quality + */ + +#define AC97LEFT(x) ((x & 0x7F00)>>8) +#define AC97RIGHT(x) (x & 0x007F) +#define AC97MUTE(x) ((x & 0x8000)>>15) +#define BIT4_TO100(x) (100-(x)*100/(0x0f)) +#define BIT6_TO100(x) (100-(x)*100/(0x3f)) +#define BIT4_TO255(x) (255-(x)*255/(0x0f)) +#define BIT6_TO255(x) (255-(x)*255/(0x3f)) +#define V100_TOBIT6(x) (0x3f*(100-x)/100) +#define V100_TOBIT4(x) (0x0f*(100-x)/100) +#define AC97ENCODE(x_muted,x_left,x_right) (((x_muted&1)<<15) | ((x_left&0x3f)<<8) | (x_right&0x3f)) + +static int +emu_ac97_read_emulation(struct emu_pcm_info *sc, int regno) +{ + int use_ac97; + int emulated; + int tmp; + + use_ac97 = 1; + emulated = 0; + + switch (regno) { + case AC97_MIX_MASTER: + emulated = sc->ac97_state[AC97_MIX_MASTER]; + use_ac97 = 0; + break; + case AC97_MIX_PCM: + emulated = sc->ac97_state[AC97_MIX_PCM]; + use_ac97 = 0; + break; + case AC97_REG_RECSEL: + emulated = 0x0505; + use_ac97 = 0; + break; + case AC97_MIX_RGAIN: + emulated = sc->ac97_state[AC97_MIX_RGAIN]; + use_ac97 = 0; + break; + } + + emu_wr(sc->card, AC97ADDRESS, regno, 1); + tmp = emu_rd(sc->card, AC97DATA, 2); + + if (use_ac97) + emulated = tmp; + + return (emulated); +} + +static void +emu_ac97_write_emulation(struct emu_pcm_info *sc, int regno, uint32_t data) +{ + int write_ac97; + int left, right; + uint32_t emu_left, emu_right; + int is_mute; + + write_ac97 = 1; + + left = AC97LEFT(data); + emu_left = BIT6_TO100(left); /* We show us as 6-bit AC97 mixer */ + right = AC97RIGHT(data); + emu_right = BIT6_TO100(right); + is_mute = AC97MUTE(data); + if (is_mute) + emu_left = emu_right = 0; + + switch (regno) { + /* TODO: reset emulator on AC97_RESET */ + case AC97_MIX_MASTER: + emumix_set_volume(sc->card, M_MASTER_FRONT_L, emu_left); + emumix_set_volume(sc->card, M_MASTER_FRONT_R, emu_right); + sc->ac97_state[AC97_MIX_MASTER] = data & (0x8000 | 0x3f3f); + data = 0x8000; /* Mute AC97 main out */ + break; + case AC97_MIX_PCM: /* PCM OUT VOL */ + emumix_set_volume(sc->card, M_FX0_FRONT_L, emu_left); + emumix_set_volume(sc->card, M_FX1_FRONT_R, emu_right); + sc->ac97_state[AC97_MIX_PCM] = data & (0x8000 | 0x3f3f); + data = 0x8000; /* Mute AC97 PCM out */ + break; + case AC97_REG_RECSEL: + /* + * PCM recording source is set to "stereo mix" (labeled "vol" + * in mixer) XXX !I can't remember why! + */ + data = 0x0505; + break; + case AC97_MIX_RGAIN: /* RECORD GAIN */ + emu_left = BIT4_TO100(left); /* rgain is 4-bit */ + emu_right = BIT4_TO100(right); + emumix_set_volume(sc->card, M_MASTER_REC_L, 100-emu_left); + emumix_set_volume(sc->card, M_MASTER_REC_R, 100-emu_right); + /* + * Record gain on AC97 should stay zero to get AC97 sound on + * AC97_[RL] connectors on EMU10K2 chip. AC97 on Audigy is not + * directly connected to any output, only to EMU10K2 chip Use + * this control to set AC97 mix volume inside EMU10K2 chip + */ + sc->ac97_state[AC97_MIX_RGAIN] = data & (0x8000 | 0x0f0f); + data = 0x0000; + break; + } + if (write_ac97) { + emu_wr(sc->card, AC97ADDRESS, regno, 1); + emu_wr(sc->card, AC97DATA, data, 2); + } +} + +static int +emu_erdcd(kobj_t obj __unused, void *devinfo, int regno) +{ + struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; + + return (emu_ac97_read_emulation(sc, regno)); +} + +static int +emu_ewrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) +{ + struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; + + emu_ac97_write_emulation(sc, regno, data); + return (0); +} + +static kobj_method_t emu_eac97_methods[] = { + KOBJMETHOD(ac97_read, emu_erdcd), + KOBJMETHOD(ac97_write, emu_ewrcd), + {0, 0} +}; +AC97_DECLARE(emu_eac97); + +/* real ac97 codec */ +static int +emu_rdcd(kobj_t obj __unused, void *devinfo, int regno) +{ + int rd; + struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; + + KASSERT(sc->card != NULL, ("emu_rdcd: no soundcard")); + emu_wr(sc->card, AC97ADDRESS, regno, 1); + rd = emu_rd(sc->card, AC97DATA, 2); + return (rd); +} + +static int +emu_wrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) +{ + struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; + + KASSERT(sc->card != NULL, ("emu_wrcd: no soundcard")); + emu_wr(sc->card, AC97ADDRESS, regno, 1); + emu_wr(sc->card, AC97DATA, data, 2); + return (0); +} + +static kobj_method_t emu_ac97_methods[] = { + KOBJMETHOD(ac97_read, emu_rdcd), + KOBJMETHOD(ac97_write, emu_wrcd), + {0, 0} +}; +AC97_DECLARE(emu_ac97); + + +static int +emu_k1_recval(int speed) +{ + int val; + + val = 0; + while ((val < 7) && (speed < emu10k1_adcspeed[val])) + val++; + if (val == 6) val=5; /* XXX 8kHz does not work */ + return (val); +} + +static int +emu_k2_recval(int speed) +{ + int val; + + val = 0; + while ((val < 8) && (speed < emu10k2_adcspeed[val])) + val++; + if (val == 7) val=6; /* XXX 8kHz does not work */ + return (val); +} + +static void * +emupchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) +{ + struct emu_pcm_info *sc = devinfo; + struct emu_pcm_pchinfo *ch; + void *r; + + KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); + KASSERT(sc->card != NULL, ("empchan_init: no soundcard")); + + + if (sc->pnum >= MAX_CHANNELS) + return (NULL); + ch = &(sc->pch[sc->pnum++]); + ch->buffer = b; + ch->pcm = sc; + ch->channel = c; + ch->blksz = sc->bufsz; + ch->fmt = AFMT_U8; + ch->spd = 8000; + ch->master = emu_valloc(sc->card); + /* + * XXX we have to allocate slave even for mono channel until we + * fix emu_vfree to handle this case. + */ + ch->slave = emu_valloc(sc->card); + ch->timer = emu_timer_create(sc->card); + r = (emu_vinit(sc->card, ch->master, ch->slave, EMU_PLAY_BUFSZ, ch->buffer)) ? NULL : ch; + return (r); +} + +static int +emupchan_free(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + emu_timer_clear(sc->card, ch->timer); + if (ch->slave != NULL) + emu_vfree(sc->card, ch->slave); + emu_vfree(sc->card, ch->master); + return (0); +} + +static int +emupchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + + ch->fmt = format; + return (0); +} + +static int +emupchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + + ch->spd = speed; + return (ch->spd); +} + +static int +emupchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + if (blocksize > ch->pcm->bufsz) + blocksize = ch->pcm->bufsz; + snd_mtxlock(sc->lock); + ch->blksz = blocksize; + emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getbps(ch->buffer)); + snd_mtxunlock(sc->lock); + return (blocksize); +} + +static int +emupchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + if (!PCMTRIG_COMMON(go)) + return (0); + + snd_mtxlock(sc->lock); /* XXX can we trigger on parallel threads ? */ + if (go == PCMTRIG_START) { + emu_vsetup(ch->master, ch->fmt, ch->spd); + if ((ch->fmt & AFMT_STEREO) == AFMT_STEREO) + emu_vroute(sc->card, &(sc->rt), ch->master); + else + emu_vroute(sc->card, &(sc->rt_mono), ch->master); + emu_vwrite(sc->card, ch->master); + emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getbps(ch->buffer)); + emu_timer_enable(sc->card, ch->timer, 1); + } + /* PCM interrupt handler will handle PCMTRIG_STOP event */ + ch->run = (go == PCMTRIG_START) ? 1 : 0; + emu_vtrigger(sc->card, ch->master, ch->run); + snd_mtxunlock(sc->lock); + return (0); +} + +static int +emupchan_getptr(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + int r; + + r = emu_vpos(sc->card, ch->master); + + return (r); +} + +static struct pcmchan_caps * +emupchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + switch (sc->route) { + case RT_FRONT: + /* FALLTHROUGH */ + case RT_REAR: + /* FALLTHROUGH */ + case RT_SIDE: + return (&emu_playcaps); + break; + case RT_CENTER: + /* FALLTHROUGH */ + case RT_SUB: + return (&emu_playcaps_mono); + break; + } + return (NULL); +} + +static kobj_method_t emupchan_methods[] = { + KOBJMETHOD(channel_init, emupchan_init), + KOBJMETHOD(channel_free, emupchan_free), + KOBJMETHOD(channel_setformat, emupchan_setformat), + KOBJMETHOD(channel_setspeed, emupchan_setspeed), + KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), + KOBJMETHOD(channel_trigger, emupchan_trigger), + KOBJMETHOD(channel_getptr, emupchan_getptr), + KOBJMETHOD(channel_getcaps, emupchan_getcaps), + {0, 0} +}; +CHANNEL_DECLARE(emupchan); + +static void * +emurchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) +{ + struct emu_pcm_info *sc = devinfo; + struct emu_pcm_rchinfo *ch; + + KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); + ch = &sc->rch_adc; + ch->buffer = b; + ch->pcm = sc; + ch->channel = c; + ch->blksz = sc->bufsz / 2; /* We rise interrupt for half-full buffer */ + ch->fmt = AFMT_U8; + ch->spd = 8000; + ch->idxreg = sc->is_emu10k1 ? ADCIDX : A_ADCIDX; + ch->basereg = ADCBA; + ch->sizereg = ADCBS; + ch->setupreg = ADCCR; + ch->irqmask = INTE_ADCBUFENABLE; + ch->iprmask = IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL; + + if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) + return (NULL); + else { + emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); + emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ + return (ch); + } +} + +static int +emurchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + ch->fmt = format; + return (0); +} + +static int +emurchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + if (ch->pcm->is_emu10k1) { + speed = emu10k1_adcspeed[emu_k1_recval(speed)]; + } else { + speed = emu10k2_adcspeed[emu_k2_recval(speed)]; + } + ch->spd = speed; + return (ch->spd); +} + +static int +emurchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + ch->blksz = blocksize; + /* If blocksize is less than half of buffer size we will not get + interrupt in time and channel will die due to interrupt timeout */ + if(ch->blksz < (ch->pcm->bufsz / 2)) + ch->blksz = ch->pcm->bufsz / 2; + return (ch->blksz); +} + +static int +emurchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + uint32_t val, sz; + + if (!PCMTRIG_COMMON(go)) + return (0); + + switch (sc->bufsz) { + case 4096: + sz = ADCBS_BUFSIZE_4096; + break; + case 8192: + sz = ADCBS_BUFSIZE_8192; + break; + case 16384: + sz = ADCBS_BUFSIZE_16384; + break; + case 32768: + sz = ADCBS_BUFSIZE_32768; + break; + case 65536: + sz = ADCBS_BUFSIZE_65536; + break; + default: + sz = ADCBS_BUFSIZE_4096; + } + + snd_mtxlock(sc->lock); + switch (go) { + case PCMTRIG_START: + ch->run = 1; + emu_wrptr(sc->card, 0, ch->sizereg, sz); + val = sc->is_emu10k1 ? ADCCR_LCHANENABLE : A_ADCCR_LCHANENABLE; + if (ch->fmt & AFMT_STEREO) + val |= sc->is_emu10k1 ? ADCCR_RCHANENABLE : A_ADCCR_RCHANENABLE; + val |= sc->is_emu10k1 ? emu_k1_recval(ch->spd) : emu_k2_recval(ch->spd); + emu_wrptr(sc->card, 0, ch->setupreg, 0); + emu_wrptr(sc->card, 0, ch->setupreg, val); + ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); + break; + case PCMTRIG_STOP: + /* FALLTHROUGH */ + case PCMTRIG_ABORT: + ch->run = 0; + emu_wrptr(sc->card, 0, ch->sizereg, 0); + if (ch->setupreg) + emu_wrptr(sc->card, 0, ch->setupreg, 0); + (void)emu_intr_unregister(sc->card, ch->ihandle); + break; + case PCMTRIG_EMLDMAWR: + /* FALLTHROUGH */ + case PCMTRIG_EMLDMARD: + /* FALLTHROUGH */ + default: + break; + } + snd_mtxunlock(sc->lock); + + return (0); +} + +static int +emurchan_getptr(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + int r; + + r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; + + return (r); +} + +static struct pcmchan_caps * +emurchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) +{ + return (&emu_reccaps_adc); +} + +static kobj_method_t emurchan_methods[] = { + KOBJMETHOD(channel_init, emurchan_init), + KOBJMETHOD(channel_setformat, emurchan_setformat), + KOBJMETHOD(channel_setspeed, emurchan_setspeed), + KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), + KOBJMETHOD(channel_trigger, emurchan_trigger), + KOBJMETHOD(channel_getptr, emurchan_getptr), + KOBJMETHOD(channel_getcaps, emurchan_getcaps), + {0, 0} +}; +CHANNEL_DECLARE(emurchan); + +static void * +emufxrchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) +{ + struct emu_pcm_info *sc = devinfo; + struct emu_pcm_rchinfo *ch; + + KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); + + if (sc == NULL) return (NULL); + + ch = &(sc->rch_efx); + ch->fmt = AFMT_S16_LE; + ch->spd = sc->is_emu10k1 ? 48000*32 : 48000 * 64; + ch->idxreg = FXIDX; + ch->basereg = FXBA; + ch->sizereg = FXBS; + ch->irqmask = INTE_EFXBUFENABLE; + ch->iprmask = IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL; + ch->buffer = b; + ch->pcm = sc; + ch->channel = c; + ch->blksz = sc->bufsz; + + if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) + return (NULL); + else { + emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); + emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ + return (ch); + } +} + +static int +emufxrchan_setformat(kobj_t obj __unused, void *c_devinfo __unused, uint32_t format) +{ + if (format == AFMT_S16_LE) return (0); + return (-1); +} + +static int +emufxrchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + /* FIXED RATE CHANNEL */ + return (ch->spd); +} + +static int +emufxrchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + ch->blksz = blocksize; + /* If blocksize is less than half of buffer size we will not get + interrupt in time and channel will die due to interrupt timeout */ + if(ch->blksz < (ch->pcm->bufsz / 2)) + ch->blksz = ch->pcm->bufsz / 2; + return (ch->blksz); +} + +static int +emufxrchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + uint32_t sz; + + if (!PCMTRIG_COMMON(go)) + return (0); + + switch (sc->bufsz) { + case 4096: + sz = ADCBS_BUFSIZE_4096; + break; + case 8192: + sz = ADCBS_BUFSIZE_8192; + break; + case 16384: + sz = ADCBS_BUFSIZE_16384; + break; + case 32768: + sz = ADCBS_BUFSIZE_32768; + break; + case 65536: + sz = ADCBS_BUFSIZE_65536; + break; + default: + sz = ADCBS_BUFSIZE_4096; + } + + snd_mtxlock(sc->lock); + switch (go) { + case PCMTRIG_START: + ch->run = 1; + emu_wrptr(sc->card, 0, ch->sizereg, sz); + ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); + /* + SB Live! is limited to 32 mono channels. Audigy + has 64 mono channels, each of them is selected from + one of two A_FXWC[1|2] registers. + */ + /* XXX there is no way to demultiplex this streams for now */ + if(sc->is_emu10k1) { + emu_wrptr(sc->card, 0, FXWC, 0xffffffff); + } else { + emu_wrptr(sc->card, 0, A_FXWC1, 0xffffffff); + emu_wrptr(sc->card, 0, A_FXWC2, 0xffffffff); + } + break; + case PCMTRIG_STOP: + /* FALLTHROUGH */ + case PCMTRIG_ABORT: + ch->run = 0; + if(sc->is_emu10k1) { + emu_wrptr(sc->card, 0, FXWC, 0x0); + } else { + emu_wrptr(sc->card, 0, A_FXWC1, 0x0); + emu_wrptr(sc->card, 0, A_FXWC2, 0x0); + } + emu_wrptr(sc->card, 0, ch->sizereg, 0); + (void)emu_intr_unregister(sc->card, ch->ihandle); + break; + case PCMTRIG_EMLDMAWR: + /* FALLTHROUGH */ + case PCMTRIG_EMLDMARD: + /* FALLTHROUGH */ + default: + break; + } + snd_mtxunlock(sc->lock); + + return (0); +} + +static int +emufxrchan_getptr(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + int r; + + r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; + + return (r); +} + +static struct pcmchan_caps * +emufxrchan_getcaps(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + if(sc->is_emu10k1) + return (&emu_reccaps_efx_live); + return (&emu_reccaps_efx_audigy); + +} + +static kobj_method_t emufxrchan_methods[] = { + KOBJMETHOD(channel_init, emufxrchan_init), + KOBJMETHOD(channel_setformat, emufxrchan_setformat), + KOBJMETHOD(channel_setspeed, emufxrchan_setspeed), + KOBJMETHOD(channel_setblocksize, emufxrchan_setblocksize), + KOBJMETHOD(channel_trigger, emufxrchan_trigger), + KOBJMETHOD(channel_getptr, emufxrchan_getptr), + KOBJMETHOD(channel_getcaps, emufxrchan_getcaps), + {0, 0} +}; +CHANNEL_DECLARE(emufxrchan); + + +static uint32_t +emu_pcm_intr(void *pcm, uint32_t stat) +{ + struct emu_pcm_info *sc = (struct emu_pcm_info *)pcm; + uint32_t ack; + int i; + + ack = 0; + + if (stat & IPR_INTERVALTIMER) { + ack |= IPR_INTERVALTIMER; + for (i = 0; i < MAX_CHANNELS; i++) + if (sc->pch[i].channel) { + if (sc->pch[i].run == 1) + chn_intr(sc->pch[i].channel); + else + emu_timer_enable(sc->card, sc->pch[i].timer, 0); + } + } + + + if (stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL)) { + ack |= stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL); + if (sc->rch_adc.channel) + chn_intr(sc->rch_adc.channel); + } + + if (stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL)) { + ack |= stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL); + if (sc->rch_efx.channel) + chn_intr(sc->rch_efx.channel); + } + return (ack); +} + +static int +emu_pcm_init(struct emu_pcm_info *sc) +{ + sc->bufsz = pcm_getbuffersize(sc->dev, EMUPAGESIZE, EMU_REC_BUFSZ, EMU_MAX_BUFSZ); + return (0); +} + +static int +emu_pcm_uninit(struct emu_pcm_info *sc __unused) +{ + return (0); +} + +static int +emu_pcm_probe(device_t dev) +{ + uintptr_t func, route, r; + const char *rt; + char buffer[255]; + + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_FUNC, &func); + + if (func != SCF_PCM) + return (ENXIO); + + rt = "UNKNOWN"; + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); + switch (route) { + case RT_FRONT: + rt = "front"; + break; + case RT_REAR: + rt = "rear"; + break; + case RT_CENTER: + rt = "center"; + break; + case RT_SUB: + rt = "subwoofer"; + break; + case RT_SIDE: + rt = "side"; + break; + case RT_MCHRECORD: + rt = "multichannel recording"; + break; + } + + snprintf(buffer, 255, "EMU10Kx DSP %s PCM interface", rt); + device_set_desc_copy(dev, buffer); + return (0); +} + +static int +emu_pcm_attach(device_t dev) +{ + struct emu_pcm_info *sc; + unsigned int i; + char status[SND_STATUSLEN]; + uint32_t inte, ipr; + uintptr_t route, r, is_emu10k1; + + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->card = (struct emu_sc_info *)(device_get_softc(device_get_parent(dev))); + if (sc->card == NULL) { + device_printf(dev, "cannot get bridge conf\n"); + free(sc, M_DEVBUF); + return (ENXIO); + } + + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10kx softc"); + sc->dev = dev; + + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &is_emu10k1); + sc->is_emu10k1 = is_emu10k1 ? 1 : 0; + + sc->codec = NULL; + + for (i = 0; i < 8; i++) { + sc->rt.routing_left[i] = i; + sc->rt.amounts_left[i] = 0x00; + sc->rt.routing_right[i] = i; + sc->rt.amounts_right[i] = 0x00; + } + + for (i = 0; i < 8; i++) { + sc->rt_mono.routing_left[i] = i; + sc->rt_mono.amounts_left[i] = 0x00; + sc->rt_mono.routing_right[i] = i; + sc->rt_mono.amounts_right[i] = 0x00; + } + + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); + sc->route = route; + switch (route) { + case RT_FRONT: + sc->rt.amounts_left[0] = 0xff; + sc->rt.amounts_right[1] = 0xff; + sc->rt_mono.amounts_left[0] = 0xff; + sc->rt_mono.amounts_left[1] = 0xff; + if (sc->is_emu10k1) + sc->codec = AC97_CREATE(dev, sc, emu_ac97); + else + sc->codec = AC97_CREATE(dev, sc, emu_eac97); + if (sc->codec == NULL) { + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize DSP mixer\n"); + goto bad; + } + } else + if (mixer_init(dev, ac97_getmixerclass(), sc->codec) == -1) { + device_printf(dev, "can't initialize AC97 mixer!\n"); + goto bad; + } + break; + case RT_REAR: + sc->rt.amounts_left[2] = 0xff; + sc->rt.amounts_right[3] = 0xff; + sc->rt_mono.amounts_left[2] = 0xff; + sc->rt_mono.amounts_left[3] = 0xff; + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad; + } + break; + case RT_CENTER: + sc->rt.amounts_left[4] = 0xff; + sc->rt_mono.amounts_left[4] = 0xff; + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad; + } + break; + case RT_SUB: + sc->rt.amounts_left[5] = 0xff; + sc->rt_mono.amounts_left[5] = 0xff; + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad; + } + break; + case RT_SIDE: + sc->rt.amounts_left[6] = 0xff; + sc->rt.amounts_right[7] = 0xff; + sc->rt_mono.amounts_left[6] = 0xff; + sc->rt_mono.amounts_left[7] = 0xff; + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad; + } + break; + case RT_MCHRECORD: + /* XXX add mixer here */ + break; + default: + device_printf(dev, "invalid default route\n"); + goto bad; + } + + inte = INTE_INTERVALTIMERENB; + ipr = IPR_INTERVALTIMER; /* Used by playback */ + sc->ihandle = emu_intr_register(sc->card, inte, ipr, &emu_pcm_intr, sc); + + if (emu_pcm_init(sc) == -1) { + device_printf(dev, "unable to initialize PCM part of the card\n"); + goto bad; + } + + /* XXX we should better get number of available channels from parent */ + if (pcm_register(dev, sc, (route == RT_FRONT) ? MAX_CHANNELS : 1, (route == RT_FRONT) ? 1 : 0)) { + device_printf(dev, "can't register PCM channels!\n"); + goto bad; + } + sc->pnum = 0; + if (route != RT_MCHRECORD) + pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); + if (route == RT_FRONT) { + for (i = 1; i < MAX_CHANNELS; i++) + pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); + pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); + } + if (route == RT_MCHRECORD) + pcm_addchan(dev, PCMDIR_REC, &emufxrchan_class, sc); + + snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); + pcm_setstatus(dev, status); + + return (0); + +bad: + if (sc->codec) + ac97_destroy(sc->codec); + if (sc->lock) + snd_mtxfree(sc->lock); + free(sc, M_DEVBUF); + return (ENXIO); +} + +static int +emu_pcm_detach(device_t dev) +{ + int r; + struct emu_pcm_info *sc; + + sc = pcm_getdevinfo(dev); + + r = pcm_unregister(dev); + + if (r) return (r); + + emu_pcm_uninit(sc); + + if (sc->lock) + snd_mtxfree(sc->lock); + free(sc, M_DEVBUF); + + return (0); +} + +static device_method_t emu_pcm_methods[] = { + DEVMETHOD(device_probe, emu_pcm_probe), + DEVMETHOD(device_attach, emu_pcm_attach), + DEVMETHOD(device_detach, emu_pcm_detach), + + {0, 0} +}; + +static driver_t emu_pcm_driver = { + "pcm", + emu_pcm_methods, + PCM_SOFTC_SIZE, + NULL, + 0, + NULL +}; +DRIVER_MODULE(snd_emu10kx_pcm, emu10kx, emu_pcm_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_emu10kx_pcm, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); +MODULE_DEPEND(snd_emu10kx_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_emu10kx_pcm, SND_EMU10KX_PREFVER); --- sys/dev/sound/pci/emu10kx.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/emu10kx.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,3167 @@ +/*- + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2003-2006 Yuriy Tsibizov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/pci/emu10kx.c,v 1.11 2007/06/04 18:25:04 dwmalone Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include /* for DELAY */ + +#include +#include +#include + +#include "opt_emu10kx.h" +#include + +/* hw flags */ +#define HAS_51 0x0001 +#define HAS_71 0x0002 +#define HAS_AC97 0x0004 + +#define IS_EMU10K1 0x0008 +#define IS_EMU10K2 0x0010 +#define IS_CA0102 0x0020 +#define IS_CA0108 0x0040 +#define IS_UNKNOWN 0x0080 + +#define BROKEN_DIGITAL 0x0100 +#define DIGITAL_ONLY 0x0200 + +#define IS_CARDBUS 0x0400 + +#define MODE_ANALOG 1 +#define MODE_DIGITAL 2 +#define SPDIF_MODE_PCM 1 +#define SPDIF_MODE_AC3 2 + +#define MACS 0x0 +#define MACS1 0x1 +#define MACW 0x2 +#define MACW1 0x3 +#define MACINTS 0x4 +#define MACINTW 0x5 +#define ACC3 0x6 +#define MACMV 0x7 +#define ANDXOR 0x8 +#define TSTNEG 0x9 +#define LIMIT 0xA +#define LIMIT1 0xB +#define LOG 0xC +#define EXP 0xD +#define INTERP 0xE +#define SKIP 0xF + +#define GPR(i) (sc->gpr_base+(i)) +#define INP(i) (sc->input_base+(i)) +#define OUTP(i) (sc->output_base+(i)) +#define FX(i) (i) +#define FX2(i) (sc->efxc_base+(i)) +#define DSP_CONST(i) (sc->dsp_zero+(i)) + +#define COND_NORMALIZED DSP_CONST(0x1) +#define COND_BORROW DSP_CONST(0x2) +#define COND_MINUS DSP_CONST(0x3) +#define COND_LESS_ZERO DSP_CONST(0x4) +#define COND_EQ_ZERO DSP_CONST(0x5) +#define COND_SATURATION DSP_CONST(0x6) +#define COND_NEQ_ZERO DSP_CONST(0x8) + +/* Live! Inputs */ +#define IN_AC97_L 0x00 +#define IN_AC97_R 0x01 +#define IN_AC97 IN_AC97_L +#define IN_SPDIF_CD_L 0x02 +#define IN_SPDIF_CD_R 0x03 +#define IN_SPDIF_CD IN_SPDIF_CD_L +#define IN_ZOOM_L 0x04 +#define IN_ZOOM_R 0x05 +#define IN_ZOOM IN_ZOOM_L +#define IN_TOSLINK_L 0x06 +#define IN_TOSLINK_R 0x07 +#define IN_TOSLINK IN_TOSLINK_L +#define IN_LINE1_L 0x08 +#define IN_LINE1_R 0x09 +#define IN_LINE1 IN_LINE1_L +#define IN_COAX_SPDIF_L 0x0a +#define IN_COAX_SPDIF_R 0x0b +#define IN_COAX_SPDIF IN_COAX_SPDIF_L +#define IN_LINE2_L 0x0c +#define IN_LINE2_R 0x0d +#define IN_LINE2 IN_LINE2_L +#define IN_0E 0x0e +#define IN_0F 0x0f + +/* Outputs */ +#define OUT_AC97_L 0x00 +#define OUT_AC97_R 0x01 +#define OUT_AC97 OUT_AC97_L +#define OUT_A_FRONT OUT_AC97 +#define OUT_TOSLINK_L 0x02 +#define OUT_TOSLINK_R 0x03 +#define OUT_TOSLINK OUT_TOSLINK_L +#define OUT_D_CENTER 0x04 +#define OUT_D_SUB 0x05 +#define OUT_HEADPHONE_L 0x06 +#define OUT_HEADPHONE_R 0x07 +#define OUT_HEADPHONE OUT_HEADPHONE_L +#define OUT_REAR_L 0x08 +#define OUT_REAR_R 0x09 +#define OUT_REAR OUT_REAR_L +#define OUT_ADC_REC_L 0x0a +#define OUT_ADC_REC_R 0x0b +#define OUT_ADC_REC OUT_ADC_REC_L +#define OUT_MIC_CAP 0x0c + +/* Live! 5.1 Digital, non-standart 5.1 (center & sub) outputs */ +#define OUT_A_CENTER 0x11 +#define OUT_A_SUB 0x12 + +/* Audigy Inputs */ +#define A_IN_AC97_L 0x00 +#define A_IN_AC97_R 0x01 +#define A_IN_AC97 A_IN_AC97_L +#define A_IN_SPDIF_CD_L 0x02 +#define A_IN_SPDIF_CD_R 0x03 +#define A_IN_SPDIF_CD A_IN_SPDIF_CD_L +#define A_IN_O_SPDIF_L 0x04 +#define A_IN_O_SPDIF_R 0x05 +#define A_IN_O_SPDIF A_IN_O_SPDIF_L +#define A_IN_LINE2_L 0x08 +#define A_IN_LINE2_R 0x09 +#define A_IN_LINE2 A_IN_LINE2_L +#define A_IN_R_SPDIF_L 0x0a +#define A_IN_R_SPDIF_R 0x0b +#define A_IN_R_SPDIF A_IN_R_SPDIF_L +#define A_IN_AUX2_L 0x0c +#define A_IN_AUX2_R 0x0d +#define A_IN_AUX2 A_IN_AUX2_L + +/* Audigiy Outputs */ +#define A_OUT_D_FRONT_L 0x00 +#define A_OUT_D_FRONT_R 0x01 +#define A_OUT_D_FRONT A_OUT_D_FRONT_L +#define A_OUT_D_CENTER 0x02 +#define A_OUT_D_SUB 0x03 +#define A_OUT_D_SIDE_L 0x04 +#define A_OUT_D_SIDE_R 0x05 +#define A_OUT_D_SIDE A_OUT_D_SIDE_L +#define A_OUT_D_REAR_L 0x06 +#define A_OUT_D_REAR_R 0x07 +#define A_OUT_D_REAR A_OUT_D_REAR_L + +/* on Audigy Platinum only */ +#define A_OUT_HPHONE_L 0x04 +#define A_OUT_HPHONE_R 0x05 +#define A_OUT_HPHONE A_OUT_HPHONE_L + +#define A_OUT_A_FRONT_L 0x08 +#define A_OUT_A_FRONT_R 0x09 +#define A_OUT_A_FRONT A_OUT_A_FRONT_L +#define A_OUT_A_CENTER 0x0a +#define A_OUT_A_SUB 0x0b +#define A_OUT_A_SIDE_L 0x0c +#define A_OUT_A_SIDE_R 0x0d +#define A_OUT_A_SIDE A_OUT_A_SIDE_L +#define A_OUT_A_REAR_L 0x0e +#define A_OUT_A_REAR_R 0x0f +#define A_OUT_A_REAR A_OUT_A_REAR_L +#define A_OUT_AC97_L 0x10 +#define A_OUT_AC97_R 0x11 +#define A_OUT_AC97 A_OUT_AC97_L +#define A_OUT_ADC_REC_L 0x16 +#define A_OUT_ADC_REC_R 0x17 +#define A_OUT_ADC_REC A_OUT_ADC_REC_L + +#include "emu10k1-alsa%diked.h" +#include "p16v-alsa%diked.h" +#include "p17v-alsa%diked.h" + +#define C_FRONT_L 0 +#define C_FRONT_R 1 +#define C_REC_L 2 +#define C_REC_R 3 +#define C_REAR_L 4 +#define C_REAR_R 5 +#define C_CENTER 6 +#define C_SUB 7 +#define C_SIDE_L 8 +#define C_SIDE_R 9 +#define NUM_CACHES 10 + +#define NUM_DUMMIES 64 + +#define EMU_MAX_GPR 512 +#define EMU_MAX_IRQ_CONSUMERS 32 + +struct emu_voice { + int vnum; + unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1; + int speed; + int start; + int end; + int vol; + uint32_t buf; + void *vbuf; + struct emu_voice *slave; + uint32_t sa; + uint32_t ea; +}; + +struct emu_memblk { + SLIST_ENTRY(emu_memblk) link; + void *buf; + char owner[16]; + bus_addr_t buf_addr; + uint32_t pte_start, pte_size; +}; + +struct emu_mem { + uint8_t bmap[EMU_MAXPAGES / 8]; + uint32_t *ptb_pages; + void *silent_page; + bus_addr_t silent_page_addr; + bus_addr_t ptb_pages_addr; + bus_dma_tag_t dmat; + SLIST_HEAD(, emu_memblk) blocks; +}; + +/* rm */ +struct emu_rm { + struct emu_sc_info *card; + struct mtx gpr_lock; + signed int allocmap[EMU_MAX_GPR]; + int num_gprs; + int last_free_gpr; + int num_used; +}; + +struct emu_intr_handler { + void* softc; + uint32_t intr_mask; + uint32_t inte_mask; + uint32_t(*irq_func) (void *softc, uint32_t irq); +}; + +struct emu_sc_info { + struct mtx lock; + struct mtx rw; /* Hardware exclusive access lock */ + + /* Hardware and subdevices */ + device_t dev; + device_t pcm[RT_COUNT]; + device_t midi[2]; + uint32_t type; + uint32_t rev; + + bus_space_tag_t st; + bus_space_handle_t sh; + + struct cdev *cdev; /* /dev/emu10k character device */ + struct mtx emu10kx_lock; + int emu10kx_isopen; + struct sbuf emu10kx_sbuf; + int emu10kx_bufptr; + + + /* Resources */ + struct resource *reg; + struct resource *irq; + void *ih; + + /* IRQ handlers */ + struct emu_intr_handler ihandler[EMU_MAX_IRQ_CONSUMERS]; + + /* Card HW configuration */ + unsigned int mchannel_fx; + unsigned int dsp_zero; + unsigned int code_base; + unsigned int code_size; + unsigned int gpr_base; + unsigned int num_gprs; + unsigned int input_base; + unsigned int output_base; + unsigned int efxc_base; + unsigned int opcode_shift; + unsigned int high_operand_shift; + unsigned int address_mask; + uint32_t is_emu10k1:1, is_emu10k2, is_ca0102, is_ca0108:1, + has_ac97:1, has_51:1, has_71:1, + enable_ir:1, enable_debug:1, + broken_digital:1, is_cardbus:1; + + unsigned int num_inputs; + unsigned int num_outputs; + unsigned int num_fxbuses; + unsigned int routing_code_start; + unsigned int routing_code_end; + + /* HW resources */ + struct emu_voice voice[NUM_G]; /* Hardware voices */ + uint32_t irq_mask[EMU_MAX_IRQ_CONSUMERS]; /* IRQ manager data */ + int timer[EMU_MAX_IRQ_CONSUMERS]; /* timer */ + int timerinterval; + struct emu_rm *rm; + struct emu_mem mem; /* memory */ + + /* Mixer */ + int mixer_gpr[NUM_MIXERS]; + int mixer_volcache[NUM_MIXERS]; + int cache_gpr[NUM_CACHES]; + int dummy_gpr[NUM_DUMMIES]; + struct sysctl_ctx_list *ctx; + struct sysctl_oid *root; +}; + +static void emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error); +static void* emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr); +static void emu_free(struct emu_mem *mem, void *dmabuf); +static void* emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char * owner); +static int emu_memfree(struct emu_mem *mem, void *membuf); +static int emu_memstart(struct emu_mem *mem, void *membuf); + +/* /dev */ +static int emu10kx_dev_init(struct emu_sc_info *sc); +static int emu10kx_dev_uninit(struct emu_sc_info *sc); +static int emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s); + +static void emumix_set_mode(struct emu_sc_info *sc, int mode); +static void emumix_set_spdif_mode(struct emu_sc_info *sc, int mode); +static void emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol); +static void emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val); +static int sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS); + +static int emu_rm_init(struct emu_sc_info *sc); +static int emu_rm_uninit(struct emu_sc_info *sc); +static int emu_rm_gpr_alloc(struct emu_rm *rm, int count); + +static unsigned int emu_getcard(device_t dev); +static uint32_t emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size); +static void emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size); +static void emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data); + +static void emu_vstop(struct emu_sc_info *sc, char channel, int enable); + +static void emu_intr(void *p); +static void emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data); +static void emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc); +static void emu_initefx(struct emu_sc_info *sc); + +static int emu_cardbus_init(struct emu_sc_info *sc); +static int emu_init(struct emu_sc_info *sc); +static int emu_uninit(struct emu_sc_info *sc); + +static int emu_read_ivar(device_t bus __unused, device_t dev, int ivar_index, uintptr_t * result); +static int emu_write_ivar(device_t bus __unused, device_t dev __unused, + int ivar_index, uintptr_t value __unused); + +static int emu_pci_probe(device_t dev); +static int emu_pci_attach(device_t dev); +static int emu_pci_detach(device_t dev); +static int emu_modevent(module_t mod __unused, int cmd, void *data __unused); + +/* Supported cards */ +struct emu_hwinfo { + uint16_t vendor; + uint16_t device; + uint16_t subvendor; + uint16_t subdevice; + char SBcode[8]; + char desc[32]; + int flags; +}; + +static struct emu_hwinfo emu_cards[] = { + {0xffff, 0xffff, 0xffff, 0xffff, "BADCRD", "Not a compatible card", 0}, + /* 0x0020..0x002f 4.0 EMU10K1 cards */ + {0x1102, 0x0002, 0x1102, 0x0020, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x0021, "CT4620", "SBLive!", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x002f, "CT????", "SBLive! mainboard implementation", HAS_AC97 | IS_EMU10K1}, + + /* (range unknown) 5.1 EMU10K1 cards */ + {0x1102, 0x0002, 0x1102, 0x100a, "CT????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, + + /* 0x80??..0x805? 4.0 EMU10K1 cards */ + {0x1102, 0x0002, 0x1102, 0x8022, "CT4780", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8023, "CT4790", "SB PCI512", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8024, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8025, "CT????", "SBLive! Mainboard Implementation", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8026, "CT4830", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8027, "CT4832", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8028, "CT4760", "SBLive! OEM version", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8031, "CT4831", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8040, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8051, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + + /* 0x8061..0x???? 5.1 EMU10K1 cards */ + {0x1102, 0x0002, 0x1102, 0x8061, "SB????", "SBLive! Player 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8064, "SB????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8065, "SB0220", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8066, "CT4780", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8067, "SB????", "SBLive!", HAS_AC97 | HAS_51 | IS_EMU10K1}, + + /* Generic SB Live! */ + {0x1102, 0x0002, 0x1102, 0x0000, "SB????", "SBLive! (Unknown model)", HAS_AC97 | IS_EMU10K1}, + + /* 0x0041..0x0043 EMU10K2 (some kind of Audigy) cards */ + + /* 0x0051..0x0051 5.1 CA0100-IAF cards */ + {0x1102, 0x0004, 0x1102, 0x0051, "SB0090", "Audigy", HAS_AC97 | HAS_51 | IS_EMU10K2}, + /* ES is CA0100-IDF chip that don't work in digital mode */ + {0x1102, 0x0004, 0x1102, 0x0052, "SB0160", "Audigy ES", HAS_AC97 | HAS_71 | IS_EMU10K2 | BROKEN_DIGITAL}, + /* 0x0053..0x005C 5.1 CA0101-NAF cards */ + {0x1102, 0x0004, 0x1102, 0x0053, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2}, + {0x1102, 0x0004, 0x1102, 0x0058, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2}, + + /* 0x1002..0x1009 5.1 CA0102-IAT cards */ + {0x1102, 0x0004, 0x1102, 0x1002, "SB????", "Audigy 2 Platinum", HAS_51 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x1005, "SB????", "Audigy 2 Platinum EX", HAS_51 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x1007, "SB0240", "Audigy 2", HAS_AC97 | HAS_51 | IS_CA0102}, + + /* 0x2001..0x2003 7.1 CA0102-ICT cards */ + {0x1102, 0x0004, 0x1102, 0x2001, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x2002, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + /* XXX No reports about 0x2003 & 0x2004 cards */ + {0x1102, 0x0004, 0x1102, 0x2003, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x2004, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x2005, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + + /* (range unknown) 7.1 CA0102-xxx Audigy 4 cards */ + {0x1102, 0x0004, 0x1102, 0x2007, "SB0380", "Audigy 4 Pro", HAS_AC97 | HAS_71 | IS_CA0102}, + + /* Generic Audigy or Audigy 2 */ + {0x1102, 0x0004, 0x1102, 0x0000, "SB????", "Audigy (Unknown model)", HAS_AC97 | HAS_51 | IS_EMU10K2}, + + /* We don't support CA0103-DAT (Audigy LS) cards */ + /* There is NO CA0104-xxx cards */ + /* There is NO CA0105-xxx cards */ + /* We don't support CA0106-DAT (SB Live! 24 bit) cards */ + /* There is NO CA0107-xxx cards */ + + /* 0x1000..0x1001 7.1 CA0108-IAT cards */ + {0x1102, 0x0008, 0x1102, 0x1000, "SB????", "Audigy 2 LS", HAS_AC97 | HAS_51 | IS_CA0108 | DIGITAL_ONLY}, + {0x1102, 0x0008, 0x1102, 0x1001, "SB0400", "Audigy 2 Value", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY}, + {0x1102, 0x0008, 0x1102, 0x1021, "SB0610", "Audigy 4", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY}, + + {0x1102, 0x0008, 0x1102, 0x2001, "SB0530", "Audigy 2 ZS CardBus", HAS_AC97 | HAS_71 | IS_CA0108 | IS_CARDBUS}, + + {0x1102, 0x0008, 0x0000, 0x0000, "SB????", "Audigy 2 Value (Unknown model)", HAS_AC97 | HAS_51 | IS_CA0108}, +}; +/* Unsupported cards */ + +static struct emu_hwinfo emu_bad_cards[] = { + /* APS cards should be possible to support */ + {0x1102, 0x0002, 0x1102, 0x4001, "EMUAPS", "E-mu APS", 0}, + {0x1102, 0x0002, 0x1102, 0x4002, "EMUAPS", "E-mu APS", 0}, + {0x1102, 0x0004, 0x1102, 0x4001, "EMU???", "E-mu 1212m [4001]", 0}, + /* Similar-named ("Live!" or "Audigy") cards on different chipsets */ + {0x1102, 0x8064, 0x0000, 0x0000, "SB0100", "SBLive! 5.1 OEM", 0}, + {0x1102, 0x0006, 0x0000, 0x0000, "SB0200", "DELL OEM SBLive! Value", 0}, + {0x1102, 0x0007, 0x0000, 0x0000, "SB0310", "Audigy LS", 0}, +}; + +/* + * Get best known information about device. + */ +static unsigned int +emu_getcard(device_t dev) +{ + uint16_t device; + uint16_t subdevice; + int n_cards; + unsigned int thiscard; + int i; + + device = pci_read_config(dev, PCIR_DEVICE, /* bytes */ 2); + subdevice = pci_read_config(dev, PCIR_SUBDEV_0, /* bytes */ 2); + + n_cards = sizeof(emu_cards) / sizeof(struct emu_hwinfo); + thiscard = 0; + for (i = 1; i < n_cards; i++) { + if (device == emu_cards[i].device) { + if (subdevice == emu_cards[i].subdevice) { + thiscard = i; + break; + } + if (0x0000 == emu_cards[i].subdevice) { + thiscard = i; + /* don't break, we can get more specific card + * later in the list */ + } + } + } + + n_cards = sizeof(emu_bad_cards) / sizeof(struct emu_hwinfo); + for (i = 0; i < n_cards; i++) { + if (device == emu_bad_cards[i].device) { + if (subdevice == emu_bad_cards[i].subdevice) { + thiscard = 0; + break; + } + if (0x0000 == emu_bad_cards[i].subdevice) { + thiscard = 0; + break; /* we avoid all this cards */ + } + } + } + return (thiscard); +} + + +/* + * Base hardware interface are 32 (Audigy) or 64 (Audigy2) registers. + * Some of them are used directly, some of them provide pointer / data pairs. + */ +static uint32_t +emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size) +{ + + KASSERT(sc != NULL, ("emu_rd: NULL sc")); + switch (size) { + case 1: + return (bus_space_read_1(sc->st, sc->sh, regno)); + case 2: + return (bus_space_read_2(sc->st, sc->sh, regno)); + case 4: + return (bus_space_read_4(sc->st, sc->sh, regno)); + } + return (0xffffffff); +} + +static void +emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size) +{ + + KASSERT(sc != NULL, ("emu_rd: NULL sc")); + switch (size) { + case 1: + bus_space_write_1(sc->st, sc->sh, regno, data); + break; + case 2: + bus_space_write_2(sc->st, sc->sh, regno, data); + break; + case 4: + bus_space_write_4(sc->st, sc->sh, regno, data); + break; + } +} +/* + * PTR / DATA interface. Access to EMU10Kx is made + * via (channel, register) pair. Some registers are channel-specific, + * some not. + */ +uint32_t +emu_rdptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg) +{ + uint32_t ptr, val, mask, size, offset; + + ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK); + mtx_lock(&sc->rw); + emu_wr_nolock(sc, PTR, ptr, 4); + val = emu_rd_nolock(sc, DATA, 4); + mtx_unlock(&sc->rw); + /* + * XXX Some register numbers has data size and offset encoded in + * it to get only part of 32bit register. This use is not described + * in register name, be careful! + */ + if (reg & 0xff000000) { + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + val &= mask; + val >>= offset; + } + return (val); +} + +void +emu_wrptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg, uint32_t data) +{ + uint32_t ptr, mask, size, offset; + ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK); + mtx_lock(&sc->rw); + emu_wr_nolock(sc, PTR, ptr, 4); + /* + * XXX Another kind of magic encoding in register number. This can + * give you side effect - it will read previous data from register + * and change only required bits. + */ + if (reg & 0xff000000) { + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + data <<= offset; + data &= mask; + data |= emu_rd_nolock(sc, DATA, 4) & ~mask; + } + emu_wr_nolock(sc, DATA, data, 4); + mtx_unlock(&sc->rw); +} +/* + * PTR2 / DATA2 interface. Access to P16v is made + * via (channel, register) pair. Some registers are channel-specific, + * some not. This interface is supported by CA0102 and CA0108 chips only. + */ +uint32_t +emu_rd_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg) +{ + uint32_t val; + + mtx_lock(&sc->rw); + emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4); + val = emu_rd_nolock(sc, DATA2, 4); + mtx_unlock(&sc->rw); + return (val); +} + +void +emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data) +{ + + mtx_lock(&sc->rw); + emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4); + emu_wr_nolock(sc, DATA2, data, 4); + mtx_unlock(&sc->rw); +} +/* + * XXX CardBus interface. Not tested on any real hardware. + */ +static void +emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data) +{ + uint32_t val; + + /* 0x38 is IPE3 (CD S/PDIF interrupt pending register) on CA0102 Seems + * to be some reg/value accessible kind of config register on CardBus + * CA0108, with value(?) in top 16 bit, address(?) in low 16 */ + mtx_lock(&sc->rw); + val = emu_rd_nolock(sc, 0x38, 4); + emu_wr_nolock(sc, 0x38, data, 4); + val = emu_rd_nolock(sc, 0x38, 4); + mtx_unlock(&sc->rw); +} + +/* + * Direct hardware register access + */ +void +emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size) +{ + + mtx_lock(&sc->rw); + emu_wr_nolock(sc, regno, data, size); + mtx_unlock(&sc->rw); +} + +uint32_t +emu_rd(struct emu_sc_info *sc, unsigned int regno, unsigned int size) +{ + uint32_t rd; + + mtx_lock(&sc->rw); + rd = emu_rd_nolock(sc, regno, size); + mtx_unlock(&sc->rw); + return (rd); +} + +/* + * Enabling IR MIDI messages is another kind of black magic. It just + * has to be made this way. It really do it. + */ +void +emu_enable_ir(struct emu_sc_info *sc) +{ + uint32_t iocfg; + + mtx_lock(&sc->rw); + if (sc->is_emu10k2 || sc->is_ca0102) { + iocfg = emu_rd_nolock(sc, A_IOCFG, 2); + emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT2, 2); + DELAY(500); + emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, 2); + DELAY(500); + emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT1, 2); + DELAY(100); + emu_wr_nolock(sc, A_IOCFG, iocfg, 2); + device_printf(sc->dev, "Audigy IR MIDI events enabled.\n"); + sc->enable_ir = 1; + } + if (sc->is_emu10k1) { + iocfg = emu_rd_nolock(sc, HCFG, 4); + emu_wr_nolock(sc, HCFG, iocfg | HCFG_GPOUT2, 4); + DELAY(500); + emu_wr_nolock(sc, HCFG, iocfg | HCFG_GPOUT1 | HCFG_GPOUT2, 4); + DELAY(100); + emu_wr_nolock(sc, HCFG, iocfg, 4); + device_printf(sc->dev, "SB Live! IR MIDI events enabled.\n"); + sc->enable_ir = 1; + } + mtx_unlock(&sc->rw); +} + + +/* + * emu_timer_ - HW timer managment + */ +int +emu_timer_create(struct emu_sc_info *sc) +{ + int i, timer; + + timer = -1; + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + if (sc->timer[i] == 0) { + sc->timer[i] = -1; /* disable it */ + timer = i; + return (timer); + } + + return (-1); +} + +int +emu_timer_set(struct emu_sc_info *sc, int timer, int delay) +{ + int i; + + if(timer < 0) + return (-1); + + RANGE(delay, 16, 1024); + RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); + + sc->timer[timer] = delay; + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + if (sc->timerinterval > sc->timer[i]) + sc->timerinterval = sc->timer[i]; + + emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2); + return (timer); +} + +int +emu_timer_enable(struct emu_sc_info *sc, int timer, int go) +{ + uint32_t x; + int ena_int; + int i; + + if(timer < 0) + return (-1); + + RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); + + mtx_lock(&sc->lock); + + if ((go == 1) && (sc->timer[timer] < 0)) + sc->timer[timer] = -sc->timer[timer]; + if ((go == 0) && (sc->timer[timer] > 0)) + sc->timer[timer] = -sc->timer[timer]; + + ena_int = 0; + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) { + if (sc->timerinterval > sc->timer[i]) + sc->timerinterval = sc->timer[i]; + if (sc->timer[i] > 0) + ena_int = 1; + } + + emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2); + + if (ena_int == 1) { + x = emu_rd(sc, INTE, 4); + x |= INTE_INTERVALTIMERENB; + emu_wr(sc, INTE, x, 4); + } else { + x = emu_rd(sc, INTE, 4); + x &= ~INTE_INTERVALTIMERENB; + emu_wr(sc, INTE, x, 4); + } + mtx_unlock(&sc->lock); + return (0); +} + +int +emu_timer_clear(struct emu_sc_info *sc, int timer) +{ + if(timer < 0) + return (-1); + + RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); + + emu_timer_enable(sc, timer, 0); + + mtx_lock(&sc->lock); + if (sc->timer[timer] != 0) + sc->timer[timer] = 0; + mtx_unlock(&sc->lock); + + return (timer); +} + +/* + * emu_intr_ - HW interrupt handler managment + */ +int +emu_intr_register(struct emu_sc_info *sc, uint32_t inte_mask, uint32_t intr_mask, uint32_t(*func) (void *softc, uint32_t irq), void *isc) +{ + int i; + uint32_t x; + + mtx_lock(&sc->lock); + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + if (sc->ihandler[i].inte_mask == 0) { + sc->ihandler[i].inte_mask = inte_mask; + sc->ihandler[i].intr_mask = intr_mask; + sc->ihandler[i].softc = isc; + sc->ihandler[i].irq_func = func; + x = emu_rd(sc, INTE, 4); + x |= inte_mask; + emu_wr(sc, INTE, x, 4); + mtx_unlock(&sc->lock); +#ifdef SND_EMU10KX_DEBUG + device_printf(sc->dev, "ihandle %d registered\n", i); +#endif + return (i); + } + mtx_unlock(&sc->lock); +#ifdef SND_EMU10KX_DEBUG + device_printf(sc->dev, "ihandle not registered\n"); +#endif + return (-1); +} + +int +emu_intr_unregister(struct emu_sc_info *sc, int hnumber) +{ + uint32_t x; + int i; + + mtx_lock(&sc->lock); + + if (sc->ihandler[hnumber].inte_mask == 0) { + mtx_unlock(&sc->lock); + return (-1); + } + + x = emu_rd(sc, INTE, 4); + x &= ~sc->ihandler[hnumber].inte_mask; + + sc->ihandler[hnumber].inte_mask = 0; + sc->ihandler[hnumber].intr_mask = 0; + sc->ihandler[hnumber].softc = NULL; + sc->ihandler[hnumber].irq_func = NULL; + + /* other interupt handlers may use this INTE value */ + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + if (sc->ihandler[i].inte_mask != 0) + x |= sc->ihandler[i].inte_mask; + + emu_wr(sc, INTE, x, 4); + + mtx_unlock(&sc->lock); + return (hnumber); +} + +static void +emu_intr(void *p) +{ + struct emu_sc_info *sc = (struct emu_sc_info *)p; + uint32_t stat, ack; + int i; + + for (;;) { + stat = emu_rd(sc, IPR, 4); + ack = 0; + if (stat == 0) + break; + emu_wr(sc, IPR, stat, 4); + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) { + if ((((sc->ihandler[i].intr_mask) & stat) != 0) && + (((void *)sc->ihandler[i].irq_func) != NULL)) { + ack |= sc->ihandler[i].irq_func(sc->ihandler[i].softc, + (sc->ihandler[i].intr_mask) & stat); + } + } +#ifdef SND_EMU10KX_DEBUG + if(stat & (~ack)) + device_printf(sc->dev, "Unhandled interrupt: %08x\n", stat & (~ack)); +#endif + } + + if ((sc->is_ca0102) || (sc->is_ca0108)) + for (;;) { + stat = emu_rd(sc, IPR2, 4); + ack = 0; + if (stat == 0) + break; + emu_wr(sc, IPR2, stat, 4); + device_printf(sc->dev, "IPR2: %08x\n", stat); + break; /* to avoid infinite loop. shoud be removed + * after completion of P16V interface. */ + } + + if (sc->is_ca0102) + for (;;) { + stat = emu_rd(sc, IPR3, 4); + ack = 0; + if (stat == 0) + break; + emu_wr(sc, IPR3, stat, 4); + device_printf(sc->dev, "IPR3: %08x\n", stat); + break; /* to avoid infinite loop. should be removed + * after completion of S/PDIF interface */ + } +} + + +/* + * Get data from private emu10kx structure for PCM buffer allocation. + * Used by PCM code only. + */ +bus_dma_tag_t +emu_gettag(struct emu_sc_info *sc) +{ + return (sc->mem.dmat); +} + +static void +emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error) +{ + bus_addr_t *phys = (bus_addr_t *) arg; + + *phys = error ? 0 : (bus_addr_t) segs->ds_addr; + + if (bootverbose) { + printf("emu10kx: setmap (%lx, %lx), nseg=%d, error=%d\n", + (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len, + nseg, error); + } +} + +static void * +emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr) +{ + void *dmabuf; + bus_dmamap_t map; + + *addr = 0; + if (bus_dmamem_alloc(mem->dmat, &dmabuf, BUS_DMA_NOWAIT, &map)) + return (NULL); + if (bus_dmamap_load(mem->dmat, map, dmabuf, sz, emu_setmap, addr, 0) || !*addr) + return (NULL); + return (dmabuf); +} + +static void +emu_free(struct emu_mem *mem, void *dmabuf) +{ + bus_dmamem_free(mem->dmat, dmabuf, NULL); +} + +static void * +emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char *owner) +{ + uint32_t blksz, start, idx, ofs, tmp, found; + struct emu_memblk *blk; + void *membuf; + + blksz = sz / EMUPAGESIZE; + if (sz > (blksz * EMUPAGESIZE)) + blksz++; + if (blksz > EMU_MAX_BUFSZ / EMUPAGESIZE) + return (NULL); + /* find a free block in the bitmap */ + found = 0; + start = 1; + while (!found && start + blksz < EMU_MAXPAGES) { + found = 1; + for (idx = start; idx < start + blksz; idx++) + if (mem->bmap[idx >> 3] & (1 << (idx & 7))) + found = 0; + if (!found) + start++; + } + if (!found) + return (NULL); + blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT); + if (blk == NULL) + return (NULL); + bzero(blk, sizeof(*blk)); + membuf = emu_malloc(mem, sz, &blk->buf_addr); + *addr = blk->buf_addr; + if (membuf == NULL) { + free(blk, M_DEVBUF); + return (NULL); + } + blk->buf = membuf; + blk->pte_start = start; + blk->pte_size = blksz; + strncpy(blk->owner, owner, 15); + blk->owner[15] = '\0'; +#ifdef SND_EMU10KX_DEBUG + printf("emu10kx emu_memalloc: allocating %d for %s\n", blk->pte_size, blk->owner); +#endif + ofs = 0; + for (idx = start; idx < start + blksz; idx++) { + mem->bmap[idx >> 3] |= 1 << (idx & 7); + tmp = (uint32_t) (u_long) ((uint8_t *) blk->buf_addr + ofs); + mem->ptb_pages[idx] = (tmp << 1) | idx; + ofs += EMUPAGESIZE; + } + SLIST_INSERT_HEAD(&mem->blocks, blk, link); + return (membuf); +} + +static int +emu_memfree(struct emu_mem *mem, void *membuf) +{ + uint32_t idx, tmp; + struct emu_memblk *blk, *i; + + blk = NULL; + SLIST_FOREACH(i, &mem->blocks, link) { + if (i->buf == membuf) + blk = i; + } + if (blk == NULL) + return (EINVAL); +#ifdef SND_EMU10KX_DEBUG + printf("emu10kx emu_memfree: freeing %d for %s\n", blk->pte_size, blk->owner); +#endif + SLIST_REMOVE(&mem->blocks, blk, emu_memblk, link); + emu_free(mem, membuf); + tmp = (uint32_t) (mem->silent_page_addr) << 1; + for (idx = blk->pte_start; idx < blk->pte_start + blk->pte_size; idx++) { + mem->bmap[idx >> 3] &= ~(1 << (idx & 7)); + mem->ptb_pages[idx] = tmp | idx; + } + free(blk, M_DEVBUF); + return (0); +} + +static int +emu_memstart(struct emu_mem *mem, void *membuf) +{ + struct emu_memblk *blk, *i; + + blk = NULL; + SLIST_FOREACH(i, &mem->blocks, link) { + if (i->buf == membuf) + blk = i; + } + if (blk == NULL) + return (-1); + return (blk->pte_start); +} + + +static uint32_t +emu_rate_to_pitch(uint32_t rate) +{ + static uint32_t logMagTable[128] = { + 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, + 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, + 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, + 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, + 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, + 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, + 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, + 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, + 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, + 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, + 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, + 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, + 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, + 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, + 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, + 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df + }; + static char logSlopeTable[128] = { + 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, + 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, + 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, + 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, + 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, + 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, + 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, + 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, + 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, + 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, + 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, + 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f + }; + int i; + + if (rate == 0) + return (0); + rate *= 11185; /* Scale 48000 to 0x20002380 */ + for (i = 31; i > 0; i--) { + if (rate & 0x80000000) { /* Detect leading "1" */ + return (((uint32_t) (i - 15) << 20) + + logMagTable[0x7f & (rate >> 24)] + + (0x7f & (rate >> 17)) * + logSlopeTable[0x7f & (rate >> 24)]); + } + rate <<= 1; + } + /* NOTREACHED */ + return (0); +} + +static uint32_t +emu_rate_to_linearpitch(uint32_t rate) +{ + rate = (rate << 8) / 375; + return ((rate >> 1) + (rate & 1)); +} + +struct emu_voice * +emu_valloc(struct emu_sc_info *sc) +{ + struct emu_voice *v; + int i; + + v = NULL; + mtx_lock(&sc->lock); + for (i = 0; i < NUM_G && sc->voice[i].busy; i++); + if (i < NUM_G) { + v = &sc->voice[i]; + v->busy = 1; + } + mtx_unlock(&sc->lock); + return (v); +} + +void +emu_vfree(struct emu_sc_info *sc, struct emu_voice *v) +{ + int i, r; + + mtx_lock(&sc->lock); + for (i = 0; i < NUM_G; i++) { + if (v == &sc->voice[i] && sc->voice[i].busy) { + v->busy = 0; + /* XXX What we should do with mono channels? + See -pcm.c emupchan_init for other side of + this problem */ + if (v->slave != NULL) + r = emu_memfree(&sc->mem, v->vbuf); + } + } + mtx_unlock(&sc->lock); +} + +int +emu_vinit(struct emu_sc_info *sc, struct emu_voice *m, struct emu_voice *s, + uint32_t sz, struct snd_dbuf *b) +{ + void *vbuf; + bus_addr_t tmp_addr; + + vbuf = emu_memalloc(&sc->mem, sz, &tmp_addr, "vinit"); + if (vbuf == NULL) + return (ENOMEM); + if (b != NULL) + sndbuf_setup(b, vbuf, sz); + m->start = emu_memstart(&sc->mem, vbuf) * EMUPAGESIZE; + if (m->start == -1) { + emu_memfree(&sc->mem, vbuf); + return (ENOMEM); + } + m->end = m->start + sz; + m->speed = 0; + m->b16 = 0; + m->stereo = 0; + m->running = 0; + m->ismaster = 1; + m->vol = 0xff; + m->buf = tmp_addr; + m->vbuf = vbuf; + m->slave = s; + if (s != NULL) { + s->start = m->start; + s->end = m->end; + s->speed = 0; + s->b16 = 0; + s->stereo = 0; + s->running = 0; + s->ismaster = 0; + s->vol = m->vol; + s->buf = m->buf; + s->vbuf = NULL; + s->slave = NULL; + } + return (0); +} + +void +emu_vsetup(struct emu_voice *v, int fmt, int spd) +{ + if (fmt) { + v->b16 = (fmt & AFMT_16BIT) ? 1 : 0; + v->stereo = (fmt & AFMT_STEREO) ? 1 : 0; + if (v->slave != NULL) { + v->slave->b16 = v->b16; + v->slave->stereo = v->stereo; + } + } + if (spd) { + v->speed = spd; + if (v->slave != NULL) + v->slave->speed = v->speed; + } +} + +void +emu_vroute(struct emu_sc_info *sc, struct emu_route *rt, struct emu_voice *v) +{ + unsigned int routing[8], amounts[8]; + int i; + + for (i = 0; i < 8; i++) { + routing[i] = rt->routing_left[i]; + amounts[i] = rt->amounts_left[i]; + } + if ((v->stereo) && (v->ismaster == 0)) + for (i = 0; i < 8; i++) { + routing[i] = rt->routing_right[i]; + amounts[i] = rt->amounts_right[i]; + } + + if (sc->is_emu10k1) { + emu_wrptr(sc, v->vnum, FXRT, ((routing[3] << 12) | + (routing[2] << 8) | + (routing[1] << 4) | + (routing[0] << 0)) << 16); + } else { + emu_wrptr(sc, v->vnum, A_FXRT1, (routing[3] << 24) | + (routing[2] << 16) | + (routing[1] << 8) | + (routing[0] << 0)); + emu_wrptr(sc, v->vnum, A_FXRT2, (routing[7] << 24) | + (routing[6] << 16) | + (routing[5] << 8) | + (routing[4] << 0)); + emu_wrptr(sc, v->vnum, A_SENDAMOUNTS, (amounts[7] << 24) | + (amounts[6] << 26) | + (amounts[5] << 8) | + (amounts[4] << 0)); + } + emu_wrptr(sc, v->vnum, PTRX, (amounts[0] << 8) | (amounts[1] << 0)); + emu_wrptr(sc, v->vnum, DSL, v->ea | (amounts[3] << 24)); + emu_wrptr(sc, v->vnum, PSST, v->sa | (amounts[2] << 24)); + if ((v->stereo) && (v->slave != NULL)) + emu_vroute(sc, rt, v->slave); +} + +void +emu_vwrite(struct emu_sc_info *sc, struct emu_voice *v) +{ + int s; + uint32_t am_2, am_3, start, val, silent_page; + + s = (v->stereo ? 1 : 0) + (v->b16 ? 1 : 0); + + v->sa = v->start >> s; + v->ea = v->end >> s; + + + if (v->stereo) { + emu_wrptr(sc, v->vnum, CPF, CPF_STEREO_MASK); + } else { + emu_wrptr(sc, v->vnum, CPF, 0); + } + val = v->stereo ? 28 : 30; + val *= v->b16 ? 1 : 2; + start = v->sa + val; + + am_3 = emu_rdptr(sc, v->vnum, DSL) & 0xff000000; + emu_wrptr(sc, v->vnum, DSL, v->ea | am_3); + am_2 = emu_rdptr(sc, v->vnum, PSST) & 0xff000000; + emu_wrptr(sc, v->vnum, PSST, v->sa | am_2); + + emu_wrptr(sc, v->vnum, CCCA, start | (v->b16 ? 0 : CCCA_8BITSELECT)); + emu_wrptr(sc, v->vnum, Z1, 0); + emu_wrptr(sc, v->vnum, Z2, 0); + + silent_page = ((uint32_t) (sc->mem.silent_page_addr) << 1) | MAP_PTI_MASK; + emu_wrptr(sc, v->vnum, MAPA, silent_page); + emu_wrptr(sc, v->vnum, MAPB, silent_page); + + emu_wrptr(sc, v->vnum, CVCF, CVCF_CURRENTFILTER_MASK); + emu_wrptr(sc, v->vnum, VTFT, VTFT_FILTERTARGET_MASK); + emu_wrptr(sc, v->vnum, ATKHLDM, 0); + emu_wrptr(sc, v->vnum, DCYSUSM, DCYSUSM_DECAYTIME_MASK); + emu_wrptr(sc, v->vnum, LFOVAL1, 0x8000); + emu_wrptr(sc, v->vnum, LFOVAL2, 0x8000); + emu_wrptr(sc, v->vnum, FMMOD, 0); + emu_wrptr(sc, v->vnum, TREMFRQ, 0); + emu_wrptr(sc, v->vnum, FM2FRQ2, 0); + emu_wrptr(sc, v->vnum, ENVVAL, 0x8000); + + emu_wrptr(sc, v->vnum, ATKHLDV, ATKHLDV_HOLDTIME_MASK | ATKHLDV_ATTACKTIME_MASK); + emu_wrptr(sc, v->vnum, ENVVOL, 0x8000); + + emu_wrptr(sc, v->vnum, PEFE_FILTERAMOUNT, 0x7f); + emu_wrptr(sc, v->vnum, PEFE_PITCHAMOUNT, 0); + if ((v->stereo) && (v->slave != NULL)) + emu_vwrite(sc, v->slave); +} + +static void +emu_vstop(struct emu_sc_info *sc, char channel, int enable) +{ + int reg; + + reg = (channel & 0x20) ? SOLEH : SOLEL; + channel &= 0x1f; + reg |= 1 << 24; + reg |= channel << 16; + emu_wrptr(sc, 0, reg, enable); +} + +void +emu_vtrigger(struct emu_sc_info *sc, struct emu_voice *v, int go) +{ + uint32_t pitch_target, initial_pitch; + uint32_t cra, cs, ccis; + uint32_t sample, i; + + if (go) { + cra = 64; + cs = v->stereo ? 4 : 2; + ccis = v->stereo ? 28 : 30; + ccis *= v->b16 ? 1 : 2; + sample = v->b16 ? 0x00000000 : 0x80808080; + for (i = 0; i < cs; i++) + emu_wrptr(sc, v->vnum, CD0 + i, sample); + emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, 0); + emu_wrptr(sc, v->vnum, CCR_READADDRESS, cra); + emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, ccis); + + emu_wrptr(sc, v->vnum, IFATN, 0xff00); + emu_wrptr(sc, v->vnum, VTFT, 0xffffffff); + emu_wrptr(sc, v->vnum, CVCF, 0xffffffff); + emu_wrptr(sc, v->vnum, DCYSUSV, 0x00007f7f); + emu_vstop(sc, v->vnum, 0); + + pitch_target = emu_rate_to_linearpitch(v->speed); + initial_pitch = emu_rate_to_pitch(v->speed) >> 8; + emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, pitch_target); + emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, pitch_target); + emu_wrptr(sc, v->vnum, IP, initial_pitch); + } else { + emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, 0); + emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, 0); + emu_wrptr(sc, v->vnum, IFATN, 0xffff); + emu_wrptr(sc, v->vnum, VTFT, 0x0000ffff); + emu_wrptr(sc, v->vnum, CVCF, 0x0000ffff); + emu_wrptr(sc, v->vnum, IP, 0); + emu_vstop(sc, v->vnum, 1); + } + if ((v->stereo) && (v->slave != NULL)) + emu_vtrigger(sc, v->slave, go); +} + +int +emu_vpos(struct emu_sc_info *sc, struct emu_voice *v) +{ + int s, ptr; + + s = (v->b16 ? 1 : 0) + (v->stereo ? 1 : 0); + ptr = (emu_rdptr(sc, v->vnum, CCCA_CURRADDR) - (v->start >> s)) << s; + return (ptr & ~0x0000001f); +} + + +/* fx */ +static void +emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data) +{ + emu_wrptr(sc, 0, sc->code_base + pc, data); +} + + +static void +emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc) +{ + if ((*pc) + 1 > sc->code_size) { + device_printf(sc->dev, "DSP CODE OVERRUN: attept to write past code_size (pc=%d)\n", (*pc)); + return; + } + emu_wrefx(sc, (*pc) * 2, (x << sc->high_operand_shift) | y); + emu_wrefx(sc, (*pc) * 2 + 1, (op << sc->opcode_shift) | (z << sc->high_operand_shift) | w); + (*pc)++; +} + +static int +sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS) +{ + struct emu_sc_info *sc; + int mixer_id; + int new_vol; + int err; + + sc = arg1; + mixer_id = arg2; + + new_vol = emumix_get_volume(sc, mixer_id); + err = sysctl_handle_int(oidp, &new_vol, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (new_vol < 0 || new_vol > 100) + return (EINVAL); + emumix_set_volume(sc, mixer_id, new_vol); + + return (0); +} + +static int +emu_addefxmixer(struct emu_sc_info *sc, const char *mix_name, const int mix_id, uint32_t defvolume) +{ + int volgpr; + char sysctl_name[32]; + + volgpr = emu_rm_gpr_alloc(sc->rm, 1); + emumix_set_fxvol(sc, volgpr, defvolume); + /* Mixer controls with NULL mix_name are handled by AC97 emulation + code or PCM mixer. */ + if (mix_name != NULL) { + /* Temporary sysctls should start with underscore, + * see freebsd-current mailing list, emu10kx driver + * discussion around 2006-05-24. */ + snprintf(sysctl_name, 32, "_%s", mix_name); + SYSCTL_ADD_PROC(sc->ctx, + SYSCTL_CHILDREN(sc->root), + OID_AUTO, sysctl_name, + CTLTYPE_INT | CTLFLAG_RW, sc, mix_id, + sysctl_emu_mixer_control, "I",""); + } + + return (volgpr); +} + +/* allocate cache GPRs that will hold mixed output channels + * and clear it on every DSP run. + */ +#define EFX_CACHE(CACHE_IDX) do { \ + sc->cache_gpr[CACHE_IDX] = emu_rm_gpr_alloc(sc->rm, 1); \ + emu_addefxop(sc, ACC3, \ + GPR(sc->cache_gpr[CACHE_IDX]), \ + DSP_CONST(0), \ + DSP_CONST(0), \ + DSP_CONST(0), \ + &pc); \ +} while (0) + +/* Allocate GPR for volume control and route sound: OUT = OUT + IN * VOL */ +#define EFX_ROUTE(TITLE, INP_NR, IN_GPR_IDX, OUT_CACHE_IDX, DEF) do { \ + sc->mixer_gpr[IN_GPR_IDX] = emu_addefxmixer(sc, TITLE, IN_GPR_IDX, DEF); \ + sc->mixer_volcache[IN_GPR_IDX] = DEF; \ + emu_addefxop(sc, MACS, \ + GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ + GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ + INP_NR, \ + GPR(sc->mixer_gpr[IN_GPR_IDX]), \ + &pc); \ +} while (0) + +/* allocate GPR, OUT = IN * VOL */ +#define EFX_OUTPUT(TITLE,OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR, DEF) do { \ + sc->mixer_gpr[OUT_GPR_IDX] = emu_addefxmixer(sc, TITLE, OUT_GPR_IDX, DEF); \ + sc->mixer_volcache[OUT_GPR_IDX] = DEF; \ + emu_addefxop(sc, MACS, \ + OUTP(OUTP_NR), \ + DSP_CONST(0), \ + GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ + GPR(sc->mixer_gpr[OUT_GPR_IDX]), \ + &pc); \ +} while (0) + +/* like EFX_OUTPUT, but don't allocate mixer gpr */ +#define EFX_OUTPUTD(OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR) do{ \ + emu_addefxop(sc, MACS, \ + OUTP(OUTP_NR), \ + DSP_CONST(0), \ + GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ + GPR(sc->mixer_gpr[OUT_GPR_IDX]), \ + &pc); \ +} while(0) + +/* mute, if FLAG != 0 */ +/* XXX */ +#define EFX_MUTEIF(GPR_IDX, FLAG) do { \ +} while(0) + +/* allocate dummy GPR. It's content will be used somewhere */ +#define EFX_DUMMY(DUMMY_IDX, DUMMY_VALUE) do { \ + sc->dummy_gpr[DUMMY_IDX] = emu_rm_gpr_alloc(sc->rm, 1); \ + emumix_set_gpr(sc, sc->dummy_gpr[DUMMY_IDX], DUMMY_VALUE); \ + emu_addefxop(sc, ACC3, \ + FX2(DUMMY_IDX), \ + GPR(sc->dummy_gpr[DUMMY_IDX]), \ + DSP_CONST(0), \ + DSP_CONST(0), \ + &pc); \ +} while (0) + + +static void +emu_initefx(struct emu_sc_info *sc) +{ + unsigned int i; + uint32_t pc; + + /* stop DSP */ + if (sc->is_emu10k1) { + emu_wrptr(sc, 0, DBG, EMU10K1_DBG_SINGLE_STEP); + } else { + emu_wrptr(sc, 0, A_DBG, A_DBG_SINGLE_STEP); + } + + /* code size is in instructions */ + pc = 0; + for (i = 0; i < sc->code_size; i++) { + if (sc->is_emu10k1) { + emu_addefxop(sc, ACC3, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), &pc); + } else { + emu_addefxop(sc, SKIP, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0xf), DSP_CONST(0x0), &pc); + } + } + + pc = 0; + + /* + * DSP code below is not good, because: + * 1. It can be written smaller, if it can use DSP accumulator register + * instead of cache_gpr[]. + * 2. It can be more careful when volume is 100%, because in DSP + * x*0x7fffffff may not be equal to x ! + */ + + /* clean outputs */ + for (i = 0; i < 16 ; i++) { + emu_addefxop(sc, ACC3, OUTP(i), DSP_CONST(0), DSP_CONST(0), DSP_CONST(0), &pc); + } + + + if (sc->is_emu10k1) { + EFX_CACHE(C_FRONT_L); + EFX_CACHE(C_FRONT_R); + EFX_CACHE(C_REC_L); + EFX_CACHE(C_REC_R); + + /* fx0 to front/record, 100%/muted by default */ + EFX_ROUTE("pcm_front_l", FX(0), M_FX0_FRONT_L, C_FRONT_L, 100); + EFX_ROUTE("pcm_front_r", FX(1), M_FX1_FRONT_R, C_FRONT_R, 100); + EFX_ROUTE("pcm_rec_l", FX(0), M_FX0_REC_L, C_REC_L, 0); + EFX_ROUTE("pcm_rec_r", FX(1), M_FX1_REC_R, C_REC_R, 0); + + /* in0, from AC97 codec output */ + EFX_ROUTE("ac97_front_l", INP(IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("ac97_front_r", INP(IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("ac97_rec_l", INP(IN_AC97_L), M_IN0_REC_L, C_REC_L, 0); + EFX_ROUTE("ac97_rec_r", INP(IN_AC97_R), M_IN0_REC_R, C_REC_R, 0); + + /* in1, from CD S/PDIF */ + EFX_ROUTE("cdspdif_front_l", INP(IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0); + EFX_MUTEIF(M_IN1_FRONT_L, CDSPDIFMUTE); + EFX_ROUTE("cdspdif_front_r", INP(IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0); + EFX_MUTEIF(M_IN1_FRONT_R, CDSPDIFMUTE); + EFX_ROUTE("cdspdif_rec_l", INP(IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0); + EFX_MUTEIF(M_IN1_REC_L, CDSPDIFMUTE); + EFX_ROUTE("cdspdif_rec_r", INP(IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0); + EFX_MUTEIF(M_IN1_REC_L, CDSPDIFMUTE); +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in2, ZoomVide (???) */ + EFX_ROUTE("zoom_front_l", INP(IN_ZOOM_L), M_IN2_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("zoom_front_r", INP(IN_ZOOM_R), M_IN2_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("zoom_rec_l", INP(IN_ZOOM_L), M_IN2_REC_L, C_REC_L, 0); + EFX_ROUTE("zoom_rec_r", INP(IN_ZOOM_R), M_IN2_REC_R, C_REC_R, 0); +#endif +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in3, TOSLink (???) */ + EFX_ROUTE("toslink_front_l", INP(IN_TOSLINK_L), M_IN3_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("toslink_front_r", INP(IN_TOSLINK_R), M_IN3_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("toslink_rec_l", INP(IN_TOSLINK_L), M_IN3_REC_L, C_REC_L, 0); + EFX_ROUTE("toslink_rec_r", INP(IN_TOSLINK_R), M_IN3_REC_R, C_REC_R, 0); +#endif + /* in4, LineIn */ + EFX_ROUTE("linein_front_l", INP(IN_LINE1_L), M_IN4_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("linein_front_r", INP(IN_LINE1_R), M_IN4_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("linein_rec_l", INP(IN_LINE1_L), M_IN4_REC_L, C_REC_L, 0); + EFX_ROUTE("linein_rec_r", INP(IN_LINE1_R), M_IN4_REC_R, C_REC_R, 0); + + /* in5, on-card S/PDIF */ + EFX_ROUTE("spdif_front_l", INP(IN_COAX_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("spdif_front_r", INP(IN_COAX_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("spdif_rec_l", INP(IN_COAX_SPDIF_L), M_IN5_REC_L, C_REC_L, 0); + EFX_ROUTE("spdif_rec_r", INP(IN_COAX_SPDIF_R), M_IN5_REC_R, C_REC_R, 0); + + /* in6, Line2 on Live!Drive */ + EFX_ROUTE("line2_front_l", INP(IN_LINE2_L), M_IN6_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("line2_front_r", INP(IN_LINE2_R), M_IN6_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("line2_rec_l", INP(IN_LINE2_L), M_IN6_REC_L, C_REC_L, 0); + EFX_ROUTE("line2_rec_r", INP(IN_LINE2_R), M_IN6_REC_R, C_REC_R, 0); +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in7, unknown */ + EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0); + EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0); +#endif + /* front output to hedaphones and both analog and digital */ + EFX_OUTPUT("master_front_l", C_FRONT_L, M_MASTER_FRONT_L, OUT_AC97_L, 100); + EFX_OUTPUT("master_front_r", C_FRONT_R, M_MASTER_FRONT_R, OUT_AC97_R, 100); + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_HEADPHONE_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_HEADPHONE_R); + + /* rec output to "ADC" */ + EFX_OUTPUT("master_rec_l", C_REC_L, M_MASTER_REC_L, OUT_ADC_REC_L, 100); + EFX_OUTPUT("master_rec_r", C_REC_R, M_MASTER_REC_R, OUT_ADC_REC_R, 100); +#ifdef SND_EMU10KX_MULTICHANNEL + /* + * Additional channel volume is controlled by mixer in + * emu_dspmixer_set() in -pcm.c + */ + + /* fx2/3 (pcm1) to rear */ + EFX_CACHE(C_REAR_L); + EFX_CACHE(C_REAR_R); + EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100); + EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100); + + EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, OUT_REAR_L, 100); + EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, OUT_REAR_R, 100); + if (sc->has_51) { + /* fx4 (pcm2) to center */ + EFX_CACHE(C_CENTER); + EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100); + EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, OUT_D_CENTER, 100); +#if 0 + /* XXX in digital mode (default) this should be muted because + this output is shared with digital out */ + EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, OUT_A_CENTER); +#endif + /* fx5 (pcm3) to sub */ + EFX_CACHE(C_SUB); + EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100); + EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, OUT_D_SUB, 100); +#if 0 + /* XXX in digital mode (default) this should be muted because + this output is shared with digital out */ + EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, OUT_A_SUB); +#endif + } +#ifdef SND_EMU10KX_MCH_RECORDING + /* MCH RECORDING , hight 16 slots. On 5.1 cards first 4 slots are used + as outputs and already filled with data */ + for(i = (sc->has_51 ? 4 : 0); i < 16; i++) { + /* XXX fill with dummy data */ + EFX_DUMMY(i,i*0x10000); + emu_addefxop(sc, ACC3, + FX2(i), + DSP_CONST(0), + DSP_CONST(0), + GPR(sc->dummy_gpr[i]), + &pc); + + } +#endif +#else /* !SND_EMU10KX_MULTICHANNEL */ + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_REAR_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_REAR_R); +#endif + } else /* emu10k2 and later */ { + EFX_CACHE(C_FRONT_L); + EFX_CACHE(C_FRONT_R); + EFX_CACHE(C_REC_L); + EFX_CACHE(C_REC_R); + + /* fx0 to front/record, 100%/muted by default */ + /* + * FRONT_[L|R] is controlled by AC97 emulation in + * emu_ac97_[read|write]_emulation in -pcm.c + */ + EFX_ROUTE(NULL, FX(0), M_FX0_FRONT_L, C_FRONT_L, 100); + EFX_ROUTE(NULL, FX(1), M_FX1_FRONT_R, C_FRONT_R, 100); + EFX_ROUTE("pcm_rec_l", FX(0), M_FX0_REC_L, C_REC_L, 0); + EFX_ROUTE("pcm_rec_r", FX(1), M_FX1_REC_R, C_REC_R, 0); + + /* in0, from AC97 codec output */ + EFX_ROUTE("ac97_front_l", INP(A_IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 100); + EFX_ROUTE("ac97_front_r", INP(A_IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 100); + EFX_ROUTE("ac97_rec_l", INP(A_IN_AC97_L), M_IN0_REC_L, C_REC_L, 0); + EFX_ROUTE("ac97_rec_r", INP(A_IN_AC97_R), M_IN0_REC_R, C_REC_R, 0); + + /* in1, from CD S/PDIF */ + EFX_ROUTE("cdspdif_front_l", INP(A_IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("cdspdif_front_r", INP(A_IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("cdspdif_rec_l", INP(A_IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0); + EFX_ROUTE("cdspdif_rec_r", INP(A_IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0); + + /* in2, optical & coax S/PDIF on AudigyDrive*/ + /* XXX Should be muted when GPRSCS valid stream == 0 */ + EFX_ROUTE("ospdif_front_l", INP(A_IN_O_SPDIF_L), M_IN2_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("ospdif_front_r", INP(A_IN_O_SPDIF_R), M_IN2_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("ospdif_rec_l", INP(A_IN_O_SPDIF_L), M_IN2_REC_L, C_REC_L, 0); + EFX_ROUTE("ospdif_rec_r", INP(A_IN_O_SPDIF_R), M_IN2_REC_R, C_REC_R, 0); +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in3, unknown */ + EFX_ROUTE("in3_front_l", INP(0x6), M_IN3_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("in3_front_r", INP(0x7), M_IN3_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("in3_rec_l", INP(0x6), M_IN3_REC_L, C_REC_L, 0); + EFX_ROUTE("in3_rec_r", INP(0x7), M_IN3_REC_R, C_REC_R, 0); +#endif + /* in4, LineIn 2 on AudigyDrive */ + EFX_ROUTE("linein2_front_l", INP(A_IN_LINE2_L), M_IN4_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("linein2_front_r", INP(A_IN_LINE2_R), M_IN4_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("linein2_rec_l", INP(A_IN_LINE2_L), M_IN4_REC_L, C_REC_L, 0); + EFX_ROUTE("linein2_rec_r", INP(A_IN_LINE2_R), M_IN4_REC_R, C_REC_R, 0); + + /* in5, on-card S/PDIF */ + EFX_ROUTE("spdif_front_l", INP(A_IN_R_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("spdif_front_r", INP(A_IN_R_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("spdif_rec_l", INP(A_IN_R_SPDIF_L), M_IN5_REC_L, C_REC_L, 0); + EFX_ROUTE("spdif_rec_r", INP(A_IN_R_SPDIF_R), M_IN5_REC_R, C_REC_R, 0); + + /* in6, AUX2 on AudigyDrive */ + EFX_ROUTE("aux2_front_l", INP(A_IN_AUX2_L), M_IN6_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("aux2_front_r", INP(A_IN_AUX2_R), M_IN6_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("aux2_rec_l", INP(A_IN_AUX2_L), M_IN6_REC_L, C_REC_L, 0); + EFX_ROUTE("aux2_rec_r", INP(A_IN_AUX2_R), M_IN6_REC_R, C_REC_R, 0); +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in7, unknown */ + EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0); + EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0); +#endif + /* front output to headphones and alog and digital *front */ + /* volume controlled by AC97 emulation */ + EFX_OUTPUT(NULL, C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_FRONT_L, 100); + EFX_OUTPUT(NULL, C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_FRONT_R, 100); + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_FRONT_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_FRONT_R); + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_HPHONE_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_HPHONE_R); + + /* rec output to "ADC" */ + /* volume controlled by AC97 emulation */ + EFX_OUTPUT(NULL, C_REC_L, M_MASTER_REC_L, A_OUT_ADC_REC_L, 100); + EFX_OUTPUT(NULL, C_REC_R, M_MASTER_REC_R, A_OUT_ADC_REC_R, 100); +#ifdef SND_EMU10KX_MULTICHANNEL + /* + * Additional channel volume is controlled by mixer in + * emu_dspmixer_set() in -pcm.c + */ + + /* fx2/3 (pcm1) to rear */ + EFX_CACHE(C_REAR_L); + EFX_CACHE(C_REAR_R); + EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100); + EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100); + + EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, A_OUT_A_REAR_L, 100); + EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, A_OUT_A_REAR_R, 100); + EFX_OUTPUTD(C_REAR_L, M_MASTER_REAR_L, A_OUT_D_REAR_L); + EFX_OUTPUTD(C_REAR_R, M_MASTER_REAR_R, A_OUT_D_REAR_R); + + /* fx4 (pcm2) to center */ + EFX_CACHE(C_CENTER); + EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100); + EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, A_OUT_D_CENTER, 100); +#if 0 + /* XXX in digital mode (default) this should be muted because + this output is shared with digital out */ + EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, A_OUT_A_CENTER); +#endif + /* fx5 (pcm3) to sub */ + EFX_CACHE(C_SUB); + EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100); + EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, A_OUT_D_SUB, 100); +#if 0 + /* XXX in digital mode (default) this should be muted because + this output is shared with digital out */ + EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, A_OUT_A_SUB); +#endif + if (sc->has_71) { + /* XXX this will broke headphones on AudigyDrive */ + /* fx6/7 (pcm4) to side */ + EFX_CACHE(C_SIDE_L); + EFX_CACHE(C_SIDE_R); + EFX_ROUTE(NULL, FX(6), M_FX6_SIDE_L, C_SIDE_L, 100); + EFX_ROUTE(NULL, FX(7), M_FX7_SIDE_R, C_SIDE_R, 100); + EFX_OUTPUT(NULL, C_SIDE_L, M_MASTER_SIDE_L, A_OUT_A_SIDE_L, 100); + EFX_OUTPUT(NULL, C_SIDE_R, M_MASTER_SIDE_R, A_OUT_A_SIDE_R, 100); + EFX_OUTPUTD(C_SIDE_L, M_MASTER_SIDE_L, A_OUT_D_SIDE_L); + EFX_OUTPUTD(C_SIDE_R, M_MASTER_SIDE_R, A_OUT_D_SIDE_R); + } +#ifdef SND_EMU10KX_MCH_RECORDING + /* MCH RECORDING, high 32 slots */ + for(i = 0; i < 32; i++) { + /* XXX fill with dummy data */ + EFX_DUMMY(i,i*0x10000); + emu_addefxop(sc, ACC3, + FX2(i), + DSP_CONST(0), + DSP_CONST(0), + GPR(sc->dummy_gpr[i]), + &pc); + } +#endif +#else /* !SND_EMU10KX_MULTICHANNEL */ + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_REAR_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_REAR_R); + + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_REAR_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_REAR_R); +#endif + } + + sc->routing_code_end = pc; + + /* start DSP */ + if (sc->is_emu10k1) { + emu_wrptr(sc, 0, DBG, 0); + } else { + emu_wrptr(sc, 0, A_DBG, 0); + } +} + +/* /dev/em10kx */ +static d_open_t emu10kx_open; +static d_close_t emu10kx_close; +static d_read_t emu10kx_read; + +static struct cdevsw emu10kx_cdevsw = { + .d_open = emu10kx_open, + .d_close = emu10kx_close, + .d_read = emu10kx_read, + .d_name = "emu10kx", + .d_version = D_VERSION, +}; + + +static int +emu10kx_open(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused) +{ + int error; + struct emu_sc_info *sc; + + sc = i_dev->si_drv1; + mtx_lock(&sc->emu10kx_lock); + if (sc->emu10kx_isopen) { + mtx_unlock(&sc->emu10kx_lock); + return (EBUSY); + } + sc->emu10kx_isopen = 1; + mtx_unlock(&sc->emu10kx_lock); + if (sbuf_new(&sc->emu10kx_sbuf, NULL, 4096, 0) == NULL) { + error = ENXIO; + goto out; + } + sc->emu10kx_bufptr = 0; + error = (emu10kx_prepare(sc, &sc->emu10kx_sbuf) > 0) ? 0 : ENOMEM; +out: + if (error) { + mtx_lock(&sc->emu10kx_lock); + sc->emu10kx_isopen = 0; + mtx_unlock(&sc->emu10kx_lock); + } + return (error); +} + +static int +emu10kx_close(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused) +{ + struct emu_sc_info *sc; + + sc = i_dev->si_drv1; + + mtx_lock(&sc->emu10kx_lock); + if (!(sc->emu10kx_isopen)) { + mtx_unlock(&sc->emu10kx_lock); + return (EBADF); + } + sbuf_delete(&sc->emu10kx_sbuf); + sc->emu10kx_isopen = 0; + mtx_unlock(&sc->emu10kx_lock); + + return (0); +} + +static int +emu10kx_read(struct cdev *i_dev, struct uio *buf, int flag __unused) +{ + int l, err; + struct emu_sc_info *sc; + + sc = i_dev->si_drv1; + mtx_lock(&sc->emu10kx_lock); + if (!(sc->emu10kx_isopen)) { + mtx_unlock(&sc->emu10kx_lock); + return (EBADF); + } + mtx_unlock(&sc->emu10kx_lock); + + l = min(buf->uio_resid, sbuf_len(&sc->emu10kx_sbuf) - sc->emu10kx_bufptr); + err = (l > 0) ? uiomove(sbuf_data(&sc->emu10kx_sbuf) + sc->emu10kx_bufptr, l, buf) : 0; + sc->emu10kx_bufptr += l; + + return (err); +} + +static int +emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s) +{ + int i; + + sbuf_printf(s, "FreeBSD EMU10Kx Audio Driver\n"); + sbuf_printf(s, "\nHardware resource usage:\n"); + sbuf_printf(s, "DSP General Purpose Registers: %d used, %d total\n", sc->rm->num_used, sc->rm->num_gprs); + sbuf_printf(s, "DSP Instruction Registers: %d used, %d total\n", sc->routing_code_end, sc->code_size); + sbuf_printf(s, "Card supports"); + if (sc->has_ac97) { + sbuf_printf(s, " AC97 codec"); + } else { + sbuf_printf(s, " NO AC97 codec"); + } + if (sc->has_51) { + if (sc->has_71) + sbuf_printf(s, " and 7.1 output"); + else + sbuf_printf(s, " and 5.1 output"); + } + if (sc->is_emu10k1) + sbuf_printf(s, ", SBLive! DSP code"); + if (sc->is_emu10k2) + sbuf_printf(s, ", Audigy DSP code"); + if (sc->is_ca0102) + sbuf_printf(s, ", Audigy DSP code with Audigy2 hacks"); + if (sc->is_ca0108) + sbuf_printf(s, ", Audigy DSP code with Audigy2Value hacks"); + sbuf_printf(s, "\n"); + if (sc->broken_digital) + sbuf_printf(s, "Digital mode unsupported\n"); + sbuf_printf(s, "\nInstalled devices:\n"); + for (i = 0; i < RT_COUNT; i++) + if (sc->pcm[i] != NULL) + if (device_is_attached(sc->pcm[i])) { + sbuf_printf(s, "%s on %s\n", device_get_desc(sc->pcm[i]), device_get_nameunit(sc->pcm[i])); + } + if (sc->midi[0] != NULL) + if (device_is_attached(sc->midi[0])) { + sbuf_printf(s, "EMU10Kx MIDI Interface\n"); + sbuf_printf(s, "\tOn-card connector on %s\n", device_get_nameunit(sc->midi[0])); + } + if (sc->midi[1] != NULL) + if (device_is_attached(sc->midi[1])) { + sbuf_printf(s, "\tOn-Drive connector on %s\n", device_get_nameunit(sc->midi[1])); + } + if (sc->midi[0] != NULL) + if (device_is_attached(sc->midi[0])) { + sbuf_printf(s, "\tIR reciever MIDI events %s\n", sc->enable_ir ? "enabled" : "disabled"); + } + sbuf_finish(s); + return (sbuf_len(s)); +} + +/* INIT & UNINIT */ +static int +emu10kx_dev_init(struct emu_sc_info *sc) +{ + int unit; + + mtx_init(&sc->emu10kx_lock, "kxdevlock", NULL, 0); + unit = device_get_unit(sc->dev); + + sc->cdev = make_dev(&emu10kx_cdevsw, unit2minor(unit), UID_ROOT, GID_WHEEL, 0640, "emu10kx%d", unit); + if (sc->cdev != NULL) { + sc->cdev->si_drv1 = sc; + return (0); + } + return (ENXIO); +} + +static int +emu10kx_dev_uninit(struct emu_sc_info *sc) +{ + intrmask_t s; + + s = spltty(); + mtx_lock(&sc->emu10kx_lock); + if (sc->emu10kx_isopen) { + mtx_unlock(&sc->emu10kx_lock); + splx(s); + return (EBUSY); + } + if (sc->cdev) + destroy_dev(sc->cdev); + sc->cdev = 0; + + splx(s); + mtx_destroy(&sc->emu10kx_lock); + return (0); +} + +/* resource manager */ +int +emu_rm_init(struct emu_sc_info *sc) +{ + int i; + int maxcount; + struct emu_rm *rm; + + rm = malloc(sizeof(struct emu_rm), M_DEVBUF, M_NOWAIT | M_ZERO); + if (rm == NULL) { + return (ENOMEM); + } + sc->rm = rm; + rm->card = sc; + maxcount = sc->num_gprs; + rm->num_used = 0; + mtx_init(&(rm->gpr_lock), "emu10k", "gpr alloc", MTX_DEF); + rm->num_gprs = (maxcount < EMU_MAX_GPR ? maxcount : EMU_MAX_GPR); + for (i = 0; i < rm->num_gprs; i++) + rm->allocmap[i] = 0; + rm->last_free_gpr = 0; + + return (0); +} + +int +emu_rm_uninit(struct emu_sc_info *sc) +{ +#ifdef SND_EMU10KX_DEBUG + int i; + + mtx_lock(&(sc->rm->gpr_lock)); + for (i = 0; i < sc->rm->last_free_gpr; i++) + if (sc->rm->allocmap[i] > 0) + device_printf(sc->dev, "rm: gpr %d not free before uninit\n", i); + mtx_unlock(&(sc->rm->gpr_lock)); +#endif + mtx_destroy(&(sc->rm->gpr_lock)); + free(sc->rm, M_DEVBUF); + return (0); +} + +static int +emu_rm_gpr_alloc(struct emu_rm *rm, int count) +{ + int i, j; + int allocated_gpr; + + allocated_gpr = rm->num_gprs; + /* try fast way first */ + mtx_lock(&(rm->gpr_lock)); + if (rm->last_free_gpr + count <= rm->num_gprs) { + allocated_gpr = rm->last_free_gpr; + rm->last_free_gpr += count; + rm->allocmap[allocated_gpr] = count; + for (i = 1; i < count; i++) + rm->allocmap[allocated_gpr + i] = -(count - i); + } else { + /* longer */ + i = 0; + allocated_gpr = rm->num_gprs; + while (i < rm->last_free_gpr - count) { + if (rm->allocmap[i] > 0) { + i += rm->allocmap[i]; + } else { + allocated_gpr = i; + for (j = 1; j < count; j++) { + if (rm->allocmap[i + j] != 0) + allocated_gpr = rm->num_gprs; + } + if (allocated_gpr == i) + break; + } + } + if (allocated_gpr + count < rm->last_free_gpr) { + rm->allocmap[allocated_gpr] = count; + for (i = 1; i < count; i++) + rm->allocmap[allocated_gpr + i] = -(count - i); + + } + } + if (allocated_gpr == rm->num_gprs) + allocated_gpr = (-1); + if (allocated_gpr >= 0) + rm->num_used += count; + mtx_unlock(&(rm->gpr_lock)); + return (allocated_gpr); +} + +/* mixer */ +void +emumix_set_mode(struct emu_sc_info *sc, int mode) +{ + uint32_t a_iocfg; + uint32_t hcfg; + uint32_t tmp; + + switch (mode) { + case MODE_DIGITAL: + /* FALLTHROUGH */ + case MODE_ANALOG: + break; + default: + return; + } + + hcfg = HCFG_AUDIOENABLE | HCFG_AUTOMUTE; + a_iocfg = 0; + + if (sc->rev >= 6) + hcfg |= HCFG_JOYENABLE; + + if (sc->is_emu10k1) + hcfg |= HCFG_LOCKTANKCACHE_MASK; + else + hcfg |= HCFG_CODECFORMAT_I2S | HCFG_JOYENABLE; + + + if (mode == MODE_DIGITAL) { + if (sc->broken_digital) { + device_printf(sc->dev, "Digital mode is reported as broken on this card,\n"); + } + a_iocfg |= A_IOCFG_ENABLE_DIGITAL; + hcfg |= HCFG_GPOUT0; + } + + if (mode == MODE_ANALOG) + emumix_set_spdif_mode(sc, SPDIF_MODE_PCM); + + if (sc->is_emu10k2) + a_iocfg |= 0x80; /* XXX */ + + if ((sc->is_ca0102) || (sc->is_ca0108)) + a_iocfg |= A_IOCFG_DISABLE_ANALOG; /* means "don't disable" + on this two cards. Means "disable" on emu10k2. */ + + if (sc->is_ca0108) + a_iocfg |= 0x20; /* XXX */ + + emu_wr(sc, HCFG, hcfg, 4); + + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { + tmp = emu_rd(sc, A_IOCFG, 2); + tmp = a_iocfg; + emu_wr(sc, A_IOCFG, tmp, 2); + } + + /* + * XXX Mute center/sub if we go digital on Audigy or later card. + * Route to analog center / sub in emu_initef should be disabled + * until this problem is fixed. + */ +} + +void +emumix_set_spdif_mode(struct emu_sc_info *sc, int mode) +{ + uint32_t spcs; + + switch (mode) { + case SPDIF_MODE_PCM: + break; + case SPDIF_MODE_AC3: + device_printf(sc->dev, "AC3 mode does not work and disabled\n"); + return; + default: + return; + } + + spcs = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | + SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 | + SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT; + + mode = SPDIF_MODE_PCM; + + emu_wrptr(sc, 0, SPCS0, spcs); + emu_wrptr(sc, 0, SPCS1, spcs); + emu_wrptr(sc, 0, SPCS2, spcs); +} + +#define L2L_POINTS 10 + +static int l2l_df[L2L_POINTS] = { + 0x572C5CA, /* 100..90 */ + 0x3211625, /* 90..80 */ + 0x1CC1A76, /* 80..70 */ + 0x108428F, /* 70..60 */ + 0x097C70A, /* 60..50 */ + 0x0572C5C, /* 50..40 */ + 0x0321162, /* 40..30 */ + 0x01CC1A7, /* 30..20 */ + 0x0108428, /* 20..10 */ + 0x016493D /* 10..0 */ +}; + +static int l2l_f[L2L_POINTS] = { + 0x4984461A, /* 90 */ + 0x2A3968A7, /* 80 */ + 0x18406003, /* 70 */ + 0x0DEDC66D, /* 60 */ + 0x07FFFFFF, /* 50 */ + 0x04984461, /* 40 */ + 0x02A3968A, /* 30 */ + 0x01840600, /* 20 */ + 0x00DEDC66, /* 10 */ + 0x00000000 /* 0 */ +}; + + +static int +log2lin(int log_t) +{ + int lin_t; + int idx, lin; + + if (log_t <= 0) { + lin_t = 0x00000000; + return (lin_t); + } + + if (log_t >= 100) { + lin_t = 0x7fffffff; + return (lin_t); + } + + idx = (L2L_POINTS - 1) - log_t / (L2L_POINTS); + lin = log_t % (L2L_POINTS); + lin_t = l2l_df[idx] * lin + l2l_f[idx]; + return (lin_t); +} + + +void +emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol) +{ + + vol = log2lin(vol); + emumix_set_gpr(sc, gpr, vol); +} + +void +emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val) +{ + + emu_wrptr(sc, 0, GPR(gpr), val); +} + +void +emumix_set_volume(struct emu_sc_info *sc, int mixer_idx, int volume) +{ + + RANGE(volume, 0, 100); + if (mixer_idx < NUM_MIXERS) { + sc->mixer_volcache[mixer_idx] = volume; + emumix_set_fxvol(sc, sc->mixer_gpr[mixer_idx], volume); + } +} + +int +emumix_get_volume(struct emu_sc_info *sc, int mixer_idx) +{ + if ((mixer_idx < NUM_MIXERS) && (mixer_idx >= 0)) + return (sc->mixer_volcache[mixer_idx]); + return (-1); +} + +/* Init CardBus part */ +static int +emu_cardbus_init(struct emu_sc_info *sc) +{ + + /* + * XXX May not need this if we have IPR3 handler. + * Is it a real init calls, or IPR3 interrupt acknowledgments? + * Looks much like "(data << 16) | register". + */ + emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0000); + emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0001); + emu_wr_cbptr(sc, (0x00d0 << 16) | 0x005f); + emu_wr_cbptr(sc, (0x00d0 << 16) | 0x007f); + + emu_wr_cbptr(sc, (0x0090 << 16) | 0x007f); + + return (0); +} + +/* Probe and attach the card */ +static int +emu_init(struct emu_sc_info *sc) +{ + uint32_t ch, tmp; + uint32_t spdif_sr; + uint32_t ac97slot; + int def_mode; + int i; + + /* disable audio and lock cache */ + emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4); + + /* reset recording buffers */ + emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, MICBA, 0); + emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, FXBA, 0); + emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, ADCBA, 0); + + /* disable channel interrupt */ + emu_wr(sc, INTE, INTE_INTERVALTIMERENB | INTE_SAMPLERATETRACKER | INTE_PCIERRORENABLE, 4); + emu_wrptr(sc, 0, CLIEL, 0); + emu_wrptr(sc, 0, CLIEH, 0); + emu_wrptr(sc, 0, SOLEL, 0); + emu_wrptr(sc, 0, SOLEH, 0); + + /* disable P16V and S/PDIF interrupts */ + if ((sc->is_ca0102) || (sc->is_ca0108)) + emu_wr(sc, INTE2, 0, 4); + + if (sc->is_ca0102) + emu_wr(sc, INTE3, 0, 4); + + /* init phys inputs and outputs */ + ac97slot = 0; + if (sc->has_51) + ac97slot = AC97SLOT_CNTR | AC97SLOT_LFE; + if (sc->has_71) + ac97slot = AC97SLOT_CNTR | AC97SLOT_LFE | AC97SLOT_REAR_LEFT | AC97SLOT_REAR_RIGHT; + if (sc->is_emu10k2) + ac97slot |= 0x40; + emu_wrptr(sc, 0, AC97SLOT, ac97slot); + + if (sc->is_emu10k2) /* XXX for later cards? */ + emu_wrptr(sc, 0, SPBYPASS, 0xf00); /* What will happen if + * we write 1 here? */ + + if (bus_dma_tag_create( /* parent */ bus_get_dma_tag(sc->dev), + /* alignment */ 2, /* boundary */ 0, + /* lowaddr */ 1 << 31, /* can only access 0-2gb */ + /* highaddr */ BUS_SPACE_MAXADDR, + /* filter */ NULL, /* filterarg */ NULL, + /* maxsize */ EMU_MAX_BUFSZ, /* nsegments */ 1, /* maxsegz */ 0x3ffff, + /* flags */ 0, /* lockfunc */ busdma_lock_mutex, + /* lockarg */ &Giant, &(sc->mem.dmat)) != 0) { + device_printf(sc->dev, "unable to create dma tag\n"); + bus_dma_tag_destroy(sc->mem.dmat); + return (ENOMEM); + } + + SLIST_INIT(&sc->mem.blocks); + sc->mem.ptb_pages = emu_malloc(&sc->mem, EMU_MAXPAGES * sizeof(uint32_t), &sc->mem.ptb_pages_addr); + if (sc->mem.ptb_pages == NULL) + return (ENOMEM); + + sc->mem.silent_page = emu_malloc(&sc->mem, EMUPAGESIZE, &sc->mem.silent_page_addr); + if (sc->mem.silent_page == NULL) { + emu_free(&sc->mem, sc->mem.ptb_pages); + return (ENOMEM); + } + /* Clear page with silence & setup all pointers to this page */ + bzero(sc->mem.silent_page, EMUPAGESIZE); + tmp = (uint32_t) (sc->mem.silent_page_addr) << 1; + for (i = 0; i < EMU_MAXPAGES; i++) + sc->mem.ptb_pages[i] = tmp | i; + + for (ch = 0; ch < NUM_G; ch++) { + emu_wrptr(sc, ch, MAPA, tmp | MAP_PTI_MASK); + emu_wrptr(sc, ch, MAPB, tmp | MAP_PTI_MASK); + } + emu_wrptr(sc, 0, PTB, (sc->mem.ptb_pages_addr)); + emu_wrptr(sc, 0, TCB, 0); /* taken from original driver */ + emu_wrptr(sc, 0, TCBS, 0); /* taken from original driver */ + + /* init envelope engine */ + for (ch = 0; ch < NUM_G; ch++) { + emu_wrptr(sc, ch, DCYSUSV, 0); + emu_wrptr(sc, ch, IP, 0); + emu_wrptr(sc, ch, VTFT, 0xffff); + emu_wrptr(sc, ch, CVCF, 0xffff); + emu_wrptr(sc, ch, PTRX, 0); + emu_wrptr(sc, ch, CPF, 0); + emu_wrptr(sc, ch, CCR, 0); + + emu_wrptr(sc, ch, PSST, 0); + emu_wrptr(sc, ch, DSL, 0x10); + emu_wrptr(sc, ch, CCCA, 0); + emu_wrptr(sc, ch, Z1, 0); + emu_wrptr(sc, ch, Z2, 0); + emu_wrptr(sc, ch, FXRT, 0xd01c0000); + + emu_wrptr(sc, ch, ATKHLDM, 0); + emu_wrptr(sc, ch, DCYSUSM, 0); + emu_wrptr(sc, ch, IFATN, 0xffff); + emu_wrptr(sc, ch, PEFE, 0); + emu_wrptr(sc, ch, FMMOD, 0); + emu_wrptr(sc, ch, TREMFRQ, 24); /* 1 Hz */ + emu_wrptr(sc, ch, FM2FRQ2, 24); /* 1 Hz */ + emu_wrptr(sc, ch, TEMPENV, 0); + + /*** these are last so OFF prevents writing ***/ + emu_wrptr(sc, ch, LFOVAL2, 0); + emu_wrptr(sc, ch, LFOVAL1, 0); + emu_wrptr(sc, ch, ATKHLDV, 0); + emu_wrptr(sc, ch, ENVVOL, 0); + emu_wrptr(sc, ch, ENVVAL, 0); + + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { + emu_wrptr(sc, ch, 0x4c, 0x0); + emu_wrptr(sc, ch, 0x4d, 0x0); + emu_wrptr(sc, ch, 0x4e, 0x0); + emu_wrptr(sc, ch, 0x4f, 0x0); + emu_wrptr(sc, ch, A_FXRT1, 0x3f3f3f3f); + emu_wrptr(sc, ch, A_FXRT2, 0x3f3f3f3f); + emu_wrptr(sc, ch, A_SENDAMOUNTS, 0x0); + } + } + + emumix_set_spdif_mode(sc, SPDIF_MODE_PCM); + + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) + emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, A_SPDIF_48000); + + /* + * CAxxxx cards needs additional setup: + * 1. Set I2S capture sample rate to 96000 + * 2. Disable P16v / P17v proceesing + * 3. Allow EMU10K DSP inputs + */ + if ((sc->is_ca0102) || (sc->is_ca0108)) { + + spdif_sr = emu_rdptr(sc, 0, A_SPDIF_SAMPLERATE); + spdif_sr &= 0xfffff1ff; + spdif_sr |= A_I2S_CAPTURE_96000; + emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, spdif_sr); + + /* Disable P16v processing */ + emu_wr_p16vptr(sc, 0, SRCSel, 0x14); + + /* Setup P16v/P17v sound routing */ + if (sc->is_ca0102) + emu_wr_p16vptr(sc, 0, SRCMULTI_ENABLE, 0xFF00FF00); + else { + emu_wr_p16vptr(sc, 0, P17V_MIXER_I2S_ENABLE, 0xFF000000); + emu_wr_p16vptr(sc, 0, P17V_MIXER_SPDIF_ENABLE, 0xFF000000); + + tmp = emu_rd(sc, A_IOCFG, 2); + emu_wr(sc, A_IOCFG, tmp & ~0x8, 2); + } + } + emu_initefx(sc); + + def_mode = MODE_ANALOG; + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) + def_mode = MODE_DIGITAL; + if (((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) && (sc->broken_digital)) { + device_printf(sc->dev, "Audigy card initialized in analog mode.\n"); + def_mode = MODE_ANALOG; + } + emumix_set_mode(sc, def_mode); + + if (bootverbose) { + tmp = emu_rd(sc, HCFG, 4); + device_printf(sc->dev, "Card Configuration ( 0x%08x )\n", tmp); + device_printf(sc->dev, "Card Configuration ( & 0xff000000 ) : %s%s%s%s%s%s%s%s\n", + (tmp & 0x80000000 ? "[Legacy MPIC] " : ""), + (tmp & 0x40000000 ? "[0x40] " : ""), + (tmp & 0x20000000 ? "[0x20] " : ""), + (tmp & 0x10000000 ? "[0x10] " : ""), + (tmp & 0x08000000 ? "[0x08] " : ""), + (tmp & 0x04000000 ? "[0x04] " : ""), + (tmp & 0x02000000 ? "[0x02] " : ""), + (tmp & 0x01000000 ? "[0x01]" : " ")); + device_printf(sc->dev, "Card Configuration ( & 0x00ff0000 ) : %s%s%s%s%s%s%s%s\n", + (tmp & 0x00800000 ? "[0x80] " : ""), + (tmp & 0x00400000 ? "[0x40] " : ""), + (tmp & 0x00200000 ? "[Legacy INT] " : ""), + (tmp & 0x00100000 ? "[0x10] " : ""), + (tmp & 0x00080000 ? "[0x08] " : ""), + (tmp & 0x00040000 ? "[Codec4] " : ""), + (tmp & 0x00020000 ? "[Codec2] " : ""), + (tmp & 0x00010000 ? "[I2S Codec]" : " ")); + device_printf(sc->dev, "Card Configuration ( & 0x0000ff00 ) : %s%s%s%s%s%s%s%s\n", + (tmp & 0x00008000 ? "[0x80] " : ""), + (tmp & 0x00004000 ? "[GPINPUT0] " : ""), + (tmp & 0x00002000 ? "[GPINPUT1] " : ""), + (tmp & 0x00001000 ? "[GPOUT0] " : ""), + (tmp & 0x00000800 ? "[GPOUT1] " : ""), + (tmp & 0x00000400 ? "[GPOUT2] " : ""), + (tmp & 0x00000200 ? "[Joystick] " : ""), + (tmp & 0x00000100 ? "[0x01]" : " ")); + device_printf(sc->dev, "Card Configuration ( & 0x000000ff ) : %s%s%s%s%s%s%s%s\n", + (tmp & 0x00000080 ? "[0x80] " : ""), + (tmp & 0x00000040 ? "[0x40] " : ""), + (tmp & 0x00000020 ? "[0x20] " : ""), + (tmp & 0x00000010 ? "[AUTOMUTE] " : ""), + (tmp & 0x00000008 ? "[LOCKSOUNDCACHE] " : ""), + (tmp & 0x00000004 ? "[LOCKTANKCACHE] " : ""), + (tmp & 0x00000002 ? "[MUTEBUTTONENABLE] " : ""), + (tmp & 0x00000001 ? "[AUDIOENABLE]" : " ")); + + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { + tmp = emu_rd(sc, A_IOCFG, 2); + device_printf(sc->dev, "Audigy Card Configuration ( 0x%04x )\n", tmp); + device_printf(sc->dev, "Audigy Card Configuration ( & 0xff00 )"); + printf(" : %s%s%s%s%s%s%s%s\n", + (tmp & 0x8000 ? "[Rear Speakers] " : ""), + (tmp & 0x4000 ? "[Front Speakers] " : ""), + (tmp & 0x2000 ? "[0x20] " : ""), + (tmp & 0x1000 ? "[0x10] " : ""), + (tmp & 0x0800 ? "[0x08] " : ""), + (tmp & 0x0400 ? "[0x04] " : ""), + (tmp & 0x0200 ? "[0x02] " : ""), + (tmp & 0x0100 ? "[AudigyDrive Phones]" : " ")); + device_printf(sc->dev, "Audigy Card Configuration ( & 0x00ff )"); + printf(" : %s%s%s%s%s%s%s%s\n", + (tmp & 0x0080 ? "[0x80] " : ""), + (tmp & 0x0040 ? "[Mute AnalogOut] " : ""), + (tmp & 0x0020 ? "[0x20] " : ""), + (tmp & 0x0010 ? "[0x10] " : ""), + (tmp & 0x0008 ? "[0x08] " : ""), + (tmp & 0x0004 ? "[GPOUT0] " : ""), + (tmp & 0x0002 ? "[GPOUT1] " : ""), + (tmp & 0x0001 ? "[GPOUT2]" : " ")); + } /* is_emu10k2 or ca* */ + } /* bootverbose */ + return (0); +} + +static int +emu_uninit(struct emu_sc_info *sc) +{ + uint32_t ch; + struct emu_memblk *blk; + + emu_wr(sc, INTE, 0, 4); + for (ch = 0; ch < NUM_G; ch++) + emu_wrptr(sc, ch, DCYSUSV, 0); + for (ch = 0; ch < NUM_G; ch++) { + emu_wrptr(sc, ch, VTFT, 0); + emu_wrptr(sc, ch, CVCF, 0); + emu_wrptr(sc, ch, PTRX, 0); + emu_wrptr(sc, ch, CPF, 0); + } + + /* disable audio and lock cache */ + emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4); + + emu_wrptr(sc, 0, PTB, 0); + /* reset recording buffers */ + emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, MICBA, 0); + emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, FXBA, 0); + emu_wrptr(sc, 0, FXWC, 0); + emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, ADCBA, 0); + emu_wrptr(sc, 0, TCB, 0); + emu_wrptr(sc, 0, TCBS, 0); + + /* disable channel interrupt */ + emu_wrptr(sc, 0, CLIEL, 0); + emu_wrptr(sc, 0, CLIEH, 0); + emu_wrptr(sc, 0, SOLEL, 0); + emu_wrptr(sc, 0, SOLEH, 0); + + if (!SLIST_EMPTY(&sc->mem.blocks)) + device_printf(sc->dev, "warning: memblock list not empty\n"); + + SLIST_FOREACH(blk, &sc->mem.blocks, link) + if (blk != NULL) + device_printf(sc->dev, "lost %d for %s\n", blk->pte_size, blk->owner); + + emu_free(&sc->mem, sc->mem.ptb_pages); + emu_free(&sc->mem, sc->mem.silent_page); + + return (0); +} + +static int +emu_read_ivar(device_t bus, device_t dev, int ivar_index, uintptr_t * result) +{ + struct sndcard_func *func = device_get_ivars(dev); + struct emu_sc_info *sc = device_get_softc(bus); + + switch (ivar_index) { + case EMU_VAR_FUNC: + *result = func->func; + break; + case EMU_VAR_ROUTE: + *result = ((struct emu_pcminfo *)func->varinfo)->route; + break; + case EMU_VAR_ISEMU10K1: + *result = sc->is_emu10k1; + break; + default: + return (ENOENT); + } + + return (0); +} + +static int +emu_write_ivar(device_t bus __unused, device_t dev __unused, + int ivar_index, uintptr_t value __unused) +{ + + switch (ivar_index) { + case 0: + return (EINVAL); + + default: + return (ENOENT); + } +} + +static int +emu_pci_probe(device_t dev) +{ + struct sbuf *s; + unsigned int thiscard = 0; + uint16_t vendor; + + vendor = pci_read_config(dev, PCIR_DEVVENDOR, /* bytes */ 2); + if (vendor != 0x1102) + return (ENXIO); /* Not Creative */ + + thiscard = emu_getcard(dev); + if (thiscard == 0) + return (ENXIO); + + s = sbuf_new(NULL, NULL, 4096, 0); + if (s == NULL) + return (ENOMEM); + sbuf_printf(s, "Creative %s [%s]", emu_cards[thiscard].desc, emu_cards[thiscard].SBcode); + sbuf_finish(s); + + device_set_desc_copy(dev, sbuf_data(s)); + return (BUS_PROBE_DEFAULT); +} + + +static int +emu_pci_attach(device_t dev) +{ + struct sndcard_func *func; + struct emu_sc_info *sc; + struct emu_pcminfo *pcminfo; + struct emu_midiinfo *midiinfo[3]; + uint32_t data; + int i; + int device_flags; + char status[255]; + int error = ENXIO; + + sc = device_get_softc(dev); + + /* Fill in the softc. */ + mtx_init(&sc->lock, "emu10kx", "bridge conf", MTX_DEF); + mtx_init(&sc->rw, "emu10kx", "atomic op", MTX_DEF); + sc->dev = dev; + sc->type = pci_get_devid(dev); + sc->rev = pci_get_revid(dev); + sc->enable_ir = 0; + sc->enable_debug = 0; + sc->has_ac97 = 0; + sc->has_51 = 0; + sc->has_71 = 0; + sc->broken_digital = 0; + sc->is_emu10k1 = 0; + sc->is_emu10k2 = 0; + sc->is_ca0102 = 0; + sc->is_ca0108 = 0; + sc->is_cardbus = 0; + + device_flags = emu_cards[emu_getcard(dev)].flags; + if (device_flags & HAS_51) + sc->has_51 = 1; + if (device_flags & HAS_71) { + sc->has_51 = 1; + sc->has_71 = 1; + } + if (device_flags & IS_EMU10K1) + sc->is_emu10k1 = 1; + if (device_flags & IS_EMU10K2) + sc->is_emu10k2 = 1; + if (device_flags & IS_CA0102) + sc->is_ca0102 = 1; + if (device_flags & IS_CA0108) + sc->is_ca0108 = 1; + if ((sc->is_emu10k2) && (sc->rev == 4)) { + sc->is_emu10k2 = 0; + sc->is_ca0102 = 1; /* for unknown Audigy 2 cards */ + } + if ((sc->is_ca0102 == 1) || (sc->is_ca0108 == 1)) + if (device_flags & IS_CARDBUS) + sc->is_cardbus = 1; + + if ((sc->is_emu10k1 + sc->is_emu10k2 + sc->is_ca0102 + sc->is_ca0108) != 1) { + device_printf(sc->dev, "Unable to detect HW chipset\n"); + goto bad; + } + if (device_flags & BROKEN_DIGITAL) + sc->broken_digital = 1; + if (device_flags & HAS_AC97) + sc->has_ac97 = 1; + + sc->opcode_shift = 0; + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { + sc->opcode_shift = 24; + sc->high_operand_shift = 12; + + /* DSP map */ + /* sc->fx_base = 0x0 */ + sc->input_base = 0x40; + /* sc->p16vinput_base = 0x50; */ + sc->output_base = 0x60; + sc->efxc_base = 0x80; + /* sc->output32h_base = 0xa0; */ + /* sc->output32l_base = 0xb0; */ + sc->dsp_zero = 0xc0; + /* 0xe0...0x100 are unknown */ + /* sc->tram_base = 0x200 */ + /* sc->tram_addr_base = 0x300 */ + sc->gpr_base = A_FXGPREGBASE; + sc->num_gprs = 0x200; + sc->code_base = A_MICROCODEBASE; + sc->code_size = 0x800 / 2; /* 0x600-0xdff, 2048 words, + * 1024 instructions */ + + sc->mchannel_fx = 8; + sc->num_fxbuses = 16; + sc->num_inputs = 8; + sc->num_outputs = 16; + sc->address_mask = A_PTR_ADDRESS_MASK; + } + if (sc->is_emu10k1) { + sc->has_51 = 0; /* We don't support 5.1 sound Live! 5.1 */ + sc->opcode_shift = 20; + sc->high_operand_shift = 10; + sc->code_base = MICROCODEBASE; + sc->code_size = 0x400 / 2; /* 0x400-0x7ff, 1024 words, + * 512 instructions */ + sc->gpr_base = FXGPREGBASE; + sc->num_gprs = 0x100; + sc->input_base = 0x10; + sc->output_base = 0x20; + /* + * XXX 5.1 Analog outputs are inside efxc address space! + * They use ouput+0x11/+0x12 (=efxc+1/+2). + * Don't use this efx registers for recording on SB Live! 5.1! + */ + sc->efxc_base = 0x30; + sc->dsp_zero = 0x40; + sc->mchannel_fx = 0; + sc->num_fxbuses = 8; + sc->num_inputs = 8; + sc->num_outputs = 16; + sc->address_mask = PTR_ADDRESS_MASK; + } + if (sc->opcode_shift == 0) + goto bad; + + data = pci_read_config(dev, PCIR_COMMAND, 2); + data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN); + pci_write_config(dev, PCIR_COMMAND, data, 2); + data = pci_read_config(dev, PCIR_COMMAND, 2); + + pci_enable_busmaster(dev); + + i = PCIR_BAR(0); + sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &i, RF_ACTIVE); + if (sc->reg == NULL) { + device_printf(dev, "unable to map register space\n"); + goto bad; + } + sc->st = rman_get_bustag(sc->reg); + sc->sh = rman_get_bushandle(sc->reg); + + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + sc->timer[i] = 0; /* disable it */ + + i = 0; + sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE); + if ((sc->irq == NULL) || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, emu_intr, sc, &sc->ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + if (emu_rm_init(sc) != 0) { + device_printf(dev, "unable to create resource manager\n"); + goto bad; + } + if (sc->is_cardbus) + if (emu_cardbus_init(sc) != 0) { + device_printf(dev, "unable to initialize CardBus interface\n"); + goto bad; + } + sc->ctx = device_get_sysctl_ctx(dev); + if (sc->ctx == NULL) + goto bad; + sc->root = device_get_sysctl_tree(dev); + if (sc->root == NULL) + goto bad; + if (emu_init(sc) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + if (emu10kx_dev_init(sc) == ENXIO) { + device_printf(dev, "unable to create control device\n"); + goto bad; + } + snprintf(status, 255, "rev %d at io 0x%lx irq %ld", sc->rev, rman_get_start(sc->reg), rman_get_start(sc->irq)); + + /* Voices */ + for (i = 0; i < NUM_G; i++) { + sc->voice[i].vnum = i; + sc->voice[i].slave = NULL; + sc->voice[i].busy = 0; + sc->voice[i].ismaster = 0; + sc->voice[i].running = 0; + sc->voice[i].b16 = 0; + sc->voice[i].stereo = 0; + sc->voice[i].speed = 0; + sc->voice[i].start = 0; + sc->voice[i].end = 0; + } + + /* PCM Audio */ + /* FRONT */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_FRONT; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_FRONT] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_FRONT], func); + +#ifdef SND_EMU10KX_MULTICHANNEL + /* REAR */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_REAR; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_REAR] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_REAR], func); + if (sc->has_51) { + /* CENTER */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_CENTER; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_CENTER] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_CENTER], func); + /* SUB */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_SUB; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_SUB] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_SUB], func); + } + if (sc->has_71) { + /* SIDE */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_SIDE; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_SIDE] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_SIDE], func); + }; +#ifdef SND_EMU10KX_MCH_RECORDING + /* MULTICHANNEL RECORDING */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_MCHRECORD; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_MCHRECORD] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_MCHRECORD], func); + +#endif /* SMD_EMU10KX_MCH_RECORDING */ +#endif /* SND_EMU10KX_MULTICHANNEL */ + + /* Midi Interface 1: Live!, Audigy, Audigy 2 */ + if ((sc->is_emu10k1) || (sc->is_emu10k2) || (sc->is_ca0102)) { + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + midiinfo[0] = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (midiinfo[0] == NULL) { + error = ENOMEM; + goto bad; + } + midiinfo[0]->card = sc; + if (sc->is_emu10k2 || (sc->is_ca0102)) { + midiinfo[0]->port = A_MUDATA1; + midiinfo[0]->portnr = 1; + } + if (sc->is_emu10k1) { + midiinfo[0]->port = MUDATA; + midiinfo[0]->portnr = 1; + } + func->func = SCF_MIDI; + func->varinfo = midiinfo[0]; + sc->midi[0] = device_add_child(dev, "midi", -1); + device_set_ivars(sc->midi[0], func); + } + /* Midi Interface 2: Audigy, Audigy 2 (on AudigyDrive) */ + if (sc->is_emu10k2 || (sc->is_ca0102)) { + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + midiinfo[1] = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (midiinfo[1] == NULL) { + error = ENOMEM; + goto bad; + } + midiinfo[1]->card = sc; + + midiinfo[1]->port = A_MUDATA2; + midiinfo[1]->portnr = 2; + + func->func = SCF_MIDI; + func->varinfo = midiinfo[1]; + sc->midi[1] = device_add_child(dev, "midi", -1); + device_set_ivars(sc->midi[1], func); + } + + return (bus_generic_attach(dev)); + +bad: + /* XXX can we just call emu_pci_detach here? */ + if (sc->cdev) + emu10kx_dev_uninit(sc); + if (sc->rm != NULL) + emu_rm_uninit(sc); + if (sc->reg) + bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); + if (sc->ih) + bus_teardown_intr(dev, sc->irq, sc->ih); + if (sc->irq) + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); + mtx_destroy(&sc->lock); + mtx_destroy(&sc->rw); + return (error); +} + +static int +emu_pci_detach(device_t dev) +{ + struct emu_sc_info *sc; + int devcount, i; + device_t *childlist; + int r = 0; + + sc = device_get_softc(dev); + + for (i = 0; i < RT_COUNT; i++) { + if (sc->pcm[i] != NULL) + r = device_delete_child(dev, sc->pcm[i]); + if (r) + return (r); + } + if (sc->midi[0] != NULL) + r = device_delete_child(dev, sc->midi[0]); + if (r) + return (r); + if (sc->midi[1] != NULL) + r = device_delete_child(dev, sc->midi[1]); + if (r) + return (r); + (void)device_get_children(dev, &childlist, &devcount); + for (i = 0; i < devcount - 1; i++) { + device_printf(dev, "removing stale child %d (unit %d)\n", i, device_get_unit(childlist[i])); + device_delete_child(dev, childlist[i]); + } + free(childlist, M_TEMP); + + /* shutdown chip */ + emu_uninit(sc); + r = emu10kx_dev_uninit(sc); + if (r) + return (r); + emu_rm_uninit(sc); + + if (sc->mem.dmat) + bus_dma_tag_destroy(sc->mem.dmat); + + if (sc->reg) + bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); + bus_teardown_intr(dev, sc->irq, sc->ih); + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); + mtx_destroy(&sc->lock); + mtx_destroy(&sc->rw); + return (bus_generic_detach(dev)); +} +/* add suspend, resume */ +static device_method_t emu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, emu_pci_probe), + DEVMETHOD(device_attach, emu_pci_attach), + DEVMETHOD(device_detach, emu_pci_detach), + /* Bus methods */ + DEVMETHOD(bus_read_ivar, emu_read_ivar), + DEVMETHOD(bus_write_ivar, emu_write_ivar), + + {0, 0} +}; + + +static driver_t emu_driver = { + "emu10kx", + emu_methods, + sizeof(struct emu_sc_info), + NULL, + 0, + NULL +}; + +static int +emu_modevent(module_t mod __unused, int cmd, void *data __unused) +{ + int err = 0; + + switch (cmd) { + case MOD_LOAD: + break; /* Success */ + + case MOD_UNLOAD: + case MOD_SHUTDOWN: + + /* XXX Should we check state of pcm & midi subdevices here? */ + + break; /* Success */ + + default: + err = EINVAL; + break; + } + + return (err); + +} + +static devclass_t emu_devclass; + +DRIVER_MODULE(snd_emu10kx, pci, emu_driver, emu_devclass, emu_modevent, NULL); +DRIVER_MODULE(snd_emu10kx, cardbus, emu_driver, emu_devclass, emu_modevent, NULL); +MODULE_VERSION(snd_emu10kx, SND_EMU10KX_PREFVER); --- sys/dev/sound/pci/emu10kx.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/emu10kx.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,180 @@ +/*- + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2003-2006 Yuriy Tsibizov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/pci/emu10kx.h,v 1.3 2007/01/06 18:59:35 netchild Exp $ + */ + +#ifndef EMU10KX_H +#define EMU10KX_H + +#define SND_EMU10KX_MINVER 1 +#define SND_EMU10KX_PREFVER 1 +#define SND_EMU10KX_MAXVER 1 + +#ifdef _KERNEL + +#define EMUPAGESIZE 4096 +#define NUM_G 64 +#define EMU_PLAY_BUFSZ EMUPAGESIZE*16 +/* Recording is limited by EMUPAGESIZE*16=64K buffer */ +#define EMU_REC_BUFSZ EMUPAGESIZE*16 +#define EMU_MAX_BUFSZ EMUPAGESIZE*16 +#define EMU_MAXPAGES 8192 + + +#define EMU_VAR_FUNC 0 +#define EMU_VAR_ROUTE 1 +#define EMU_VAR_ISEMU10K1 2 + +#define RT_FRONT 0 +#define RT_REAR 1 +#define RT_CENTER 2 +#define RT_SUB 3 +#define RT_SIDE 4 +#define RT_MCHRECORD 5 +#define RT_COUNT 6 + +/* mixer controls */ +/* fx play */ +#define M_FX0_FRONT_L 0 +#define M_FX1_FRONT_R 1 +#define M_FX2_REAR_L 2 +#define M_FX3_REAR_R 3 +#define M_FX4_CENTER 4 +#define M_FX5_SUBWOOFER 5 +#define M_FX6_SIDE_L 6 +#define M_FX7_SIDE_R 7 +/* fx rec */ +#define M_FX0_REC_L 8 +#define M_FX1_REC_R 9 +/* inputs play */ +#define M_IN0_FRONT_L 10 +#define M_IN0_FRONT_R 11 +#define M_IN1_FRONT_L 12 +#define M_IN1_FRONT_R 13 +#define M_IN2_FRONT_L 14 +#define M_IN2_FRONT_R 15 +#define M_IN3_FRONT_L 16 +#define M_IN3_FRONT_R 17 +#define M_IN4_FRONT_L 18 +#define M_IN4_FRONT_R 19 +#define M_IN5_FRONT_L 20 +#define M_IN5_FRONT_R 21 +#define M_IN6_FRONT_L 22 +#define M_IN6_FRONT_R 23 +#define M_IN7_FRONT_L 24 +#define M_IN7_FRONT_R 25 +/* inputs rec */ +#define M_IN0_REC_L 26 +#define M_IN0_REC_R 27 +#define M_IN1_REC_L 28 +#define M_IN1_REC_R 29 +#define M_IN2_REC_L 30 +#define M_IN2_REC_R 31 +#define M_IN3_REC_L 32 +#define M_IN3_REC_R 33 +#define M_IN4_REC_L 34 +#define M_IN4_REC_R 35 +#define M_IN5_REC_L 36 +#define M_IN5_REC_R 37 +#define M_IN6_REC_L 38 +#define M_IN6_REC_R 39 +#define M_IN7_REC_L 40 +#define M_IN7_REC_R 41 +/* master volume */ +#define M_MASTER_FRONT_L 42 +#define M_MASTER_FRONT_R 43 +#define M_MASTER_REAR_L 44 +#define M_MASTER_REAR_R 45 +#define M_MASTER_CENTER 46 +#define M_MASTER_SUBWOOFER 47 +#define M_MASTER_SIDE_L 48 +#define M_MASTER_SIDE_R 49 +/* master rec volume */ +#define M_MASTER_REC_L 50 +#define M_MASTER_REC_R 51 + +#define NUM_MIXERS 52 + +struct emu_sc_info; + +/* MIDI device parameters */ +struct emu_midiinfo { + struct emu_sc_info *card; + int port; + int portnr; +}; + +/* PCM device parameters */ +struct emu_pcminfo { + struct emu_sc_info *card; + int route; +}; + +int emu_intr_register(struct emu_sc_info *sc, uint32_t inte_mask, uint32_t intr_mask, uint32_t(*func) (void *softc, uint32_t irq), void *isc); +int emu_intr_unregister(struct emu_sc_info *sc, int ihandle); + +uint32_t emu_rd(struct emu_sc_info *sc, unsigned int regno, unsigned int size); +void emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size); + +uint32_t emu_rdptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg); +void emu_wrptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg, uint32_t data); + +uint32_t emu_rd_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg); +void emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data); + +int emu_timer_create(struct emu_sc_info *sc); +int emu_timer_set(struct emu_sc_info *sc, int timer, int delay); +int emu_timer_enable(struct emu_sc_info *sc, int timer, int go); +int emu_timer_clear(struct emu_sc_info *sc, int timer); + +struct emu_voice; + +struct emu_route { + int routing_left[8]; + int amounts_left[8]; + int routing_right[8]; + int amounts_right[8]; +}; + +struct emu_voice* emu_valloc(struct emu_sc_info *sc); +void emu_vfree(struct emu_sc_info *sc, struct emu_voice *v); +int emu_vinit(struct emu_sc_info *sc, struct emu_voice *m, struct emu_voice *s, + uint32_t sz, struct snd_dbuf *b); +void emu_vroute(struct emu_sc_info *sc, struct emu_route *rt, struct emu_voice *v); +void emu_vsetup(struct emu_voice *v, int fmt, int spd); +void emu_vwrite(struct emu_sc_info *sc, struct emu_voice *v); +void emu_vtrigger(struct emu_sc_info *sc, struct emu_voice *v, int go); +int emu_vpos(struct emu_sc_info *sc, struct emu_voice *v); + +bus_dma_tag_t emu_gettag(struct emu_sc_info *sc); + +void emumix_set_volume(struct emu_sc_info *sc, int mixer_idx, int volume); +int emumix_get_volume(struct emu_sc_info *sc, int mixer_idx); + +void emu_enable_ir(struct emu_sc_info *sc); +#endif /* _KERNEL */ +#endif /* EMU10K1_H */ --- sys/dev/sound/pci/envy24.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/envy24.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,2654 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include + +#include +#include + +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/envy24.c,v 1.13 2007/05/27 19:58:39 joel Exp $"); + +MALLOC_DEFINE(M_ENVY24, "envy24", "envy24 audio"); + +/* -------------------------------------------------------------------- */ + +struct sc_info; + +#define ENVY24_PLAY_CHNUM 10 +#define ENVY24_REC_CHNUM 12 +#define ENVY24_PLAY_BUFUNIT (4 /* byte/sample */ * 10 /* channel */) +#define ENVY24_REC_BUFUNIT (4 /* byte/sample */ * 12 /* channel */) +#define ENVY24_SAMPLE_NUM 4096 + +#define ENVY24_TIMEOUT 1000 + +#define ENVY24_DEFAULT_FORMAT (AFMT_STEREO | AFMT_S16_LE) + +#define ENVY24_NAMELEN 32 + +#define SDA_GPIO 0x10 +#define SCL_GPIO 0x20 + +struct envy24_sample { + volatile u_int32_t buffer; +}; + +typedef struct envy24_sample sample32_t; + +/* channel registers */ +struct sc_chinfo { + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct sc_info *parent; + int dir; + unsigned num; /* hw channel number */ + + /* channel information */ + u_int32_t format; + u_int32_t speed; + u_int32_t blk; /* hw block size(dword) */ + + /* format conversion structure */ + u_int8_t *data; + unsigned int size; /* data buffer size(byte) */ + int unit; /* sample size(byte) */ + unsigned int offset; /* samples number offset */ + void (*emldma)(struct sc_chinfo *); + + /* flags */ + int run; +}; + +/* codec interface entrys */ +struct codec_entry { + void *(*create)(device_t dev, void *devinfo, int dir, int num); + void (*destroy)(void *codec); + void (*init)(void *codec); + void (*reinit)(void *codec); + void (*setvolume)(void *codec, int dir, unsigned int left, unsigned int right); + void (*setrate)(void *codec, int which, int rate); +}; + +/* system configuration information */ +struct cfg_info { + char *name; + u_int16_t subvendor, subdevice; + u_int8_t scfg, acl, i2s, spdif; + u_int8_t gpiomask, gpiostate, gpiodir; + u_int8_t cdti, cclk, cs, cif, type; + u_int8_t free; + struct codec_entry *codec; +}; + +/* device private data */ +struct sc_info { + device_t dev; + struct mtx *lock; + + /* Control/Status registor */ + struct resource *cs; + int csid; + bus_space_tag_t cst; + bus_space_handle_t csh; + /* DDMA registor */ + struct resource *ddma; + int ddmaid; + bus_space_tag_t ddmat; + bus_space_handle_t ddmah; + /* Consumer Section DMA Channel Registers */ + struct resource *ds; + int dsid; + bus_space_tag_t dst; + bus_space_handle_t dsh; + /* MultiTrack registor */ + struct resource *mt; + int mtid; + bus_space_tag_t mtt; + bus_space_handle_t mth; + /* DMA tag */ + bus_dma_tag_t dmat; + /* IRQ resource */ + struct resource *irq; + int irqid; + void *ih; + + /* system configuration data */ + struct cfg_info *cfg; + + /* ADC/DAC number and info */ + int adcn, dacn; + void *adc[4], *dac[4]; + + /* mixer control data */ + u_int32_t src; + u_int8_t left[ENVY24_CHAN_NUM]; + u_int8_t right[ENVY24_CHAN_NUM]; + + /* Play/Record DMA fifo */ + sample32_t *pbuf; + sample32_t *rbuf; + u_int32_t psize, rsize; /* DMA buffer size(byte) */ + u_int16_t blk[2]; /* transfer check blocksize(dword) */ + bus_dmamap_t pmap, rmap; + + /* current status */ + u_int32_t speed; + int run[2]; + u_int16_t intr[2]; + struct pcmchan_caps caps[2]; + + /* channel info table */ + unsigned chnum; + struct sc_chinfo chan[11]; +}; + +/* -------------------------------------------------------------------- */ + +/* + * prototypes + */ + +/* DMA emulator */ +static void envy24_p8u(struct sc_chinfo *); +static void envy24_p16sl(struct sc_chinfo *); +static void envy24_p32sl(struct sc_chinfo *); +static void envy24_r16sl(struct sc_chinfo *); +static void envy24_r32sl(struct sc_chinfo *); + +/* channel interface */ +static void *envy24chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); +static int envy24chan_setformat(kobj_t, void *, u_int32_t); +static int envy24chan_setspeed(kobj_t, void *, u_int32_t); +static int envy24chan_setblocksize(kobj_t, void *, u_int32_t); +static int envy24chan_trigger(kobj_t, void *, int); +static int envy24chan_getptr(kobj_t, void *); +static struct pcmchan_caps *envy24chan_getcaps(kobj_t, void *); + +/* mixer interface */ +static int envy24mixer_init(struct snd_mixer *); +static int envy24mixer_reinit(struct snd_mixer *); +static int envy24mixer_uninit(struct snd_mixer *); +static int envy24mixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); +static u_int32_t envy24mixer_setrecsrc(struct snd_mixer *, u_int32_t); + +/* M-Audio Delta series AK4524 access interface */ +static void *envy24_delta_ak4524_create(device_t, void *, int, int); +static void envy24_delta_ak4524_destroy(void *); +static void envy24_delta_ak4524_init(void *); +static void envy24_delta_ak4524_reinit(void *); +static void envy24_delta_ak4524_setvolume(void *, int, unsigned int, unsigned int); + +/* -------------------------------------------------------------------- */ + +/* + system constant tables +*/ + +/* API -> hardware channel map */ +static unsigned envy24_chanmap[ENVY24_CHAN_NUM] = { + ENVY24_CHAN_PLAY_SPDIF, /* 0 */ + ENVY24_CHAN_PLAY_DAC1, /* 1 */ + ENVY24_CHAN_PLAY_DAC2, /* 2 */ + ENVY24_CHAN_PLAY_DAC3, /* 3 */ + ENVY24_CHAN_PLAY_DAC4, /* 4 */ + ENVY24_CHAN_REC_MIX, /* 5 */ + ENVY24_CHAN_REC_SPDIF, /* 6 */ + ENVY24_CHAN_REC_ADC1, /* 7 */ + ENVY24_CHAN_REC_ADC2, /* 8 */ + ENVY24_CHAN_REC_ADC3, /* 9 */ + ENVY24_CHAN_REC_ADC4, /* 10 */ +}; + +/* mixer -> API channel map. see above */ +static int envy24_mixmap[] = { + -1, /* Master output level. It is depend on codec support */ + -1, /* Treble level of all output channels */ + -1, /* Bass level of all output channels */ + -1, /* Volume of synthesier input */ + 0, /* Output level for the audio device */ + -1, /* Output level for the PC speaker */ + 7, /* line in jack */ + -1, /* microphone jack */ + -1, /* CD audio input */ + -1, /* Recording monitor */ + 1, /* alternative codec */ + -1, /* global recording level */ + -1, /* Input gain */ + -1, /* Output gain */ + 8, /* Input source 1 */ + 9, /* Input source 2 */ + 10, /* Input source 3 */ + 6, /* Digital (input) 1 */ + -1, /* Digital (input) 2 */ + -1, /* Digital (input) 3 */ + -1, /* Phone input */ + -1, /* Phone output */ + -1, /* Video/TV (audio) in */ + -1, /* Radio in */ + -1, /* Monitor volume */ +}; + +/* variable rate audio */ +static u_int32_t envy24_speed[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, + 12000, 11025, 9600, 8000, 0 +}; + +/* known boards configuration */ +static struct codec_entry delta_codec = { + envy24_delta_ak4524_create, + envy24_delta_ak4524_destroy, + envy24_delta_ak4524_init, + envy24_delta_ak4524_reinit, + envy24_delta_ak4524_setvolume, + NULL, /* setrate */ +}; + +static struct cfg_info cfg_table[] = { + { + "Envy24 audio (M Audio Delta Dio 2496)", + 0x1412, 0xd631, + 0x10, 0x80, 0xf0, 0x03, + 0xff, 0x00, 0x00, + 0x10, 0x20, 0x40, 0x00, 0x00, + 0x00, + &delta_codec, + }, + { + "Envy24 audio (Terratec DMX 6fire)", + 0x153b, 0x1138, + 0x2f, 0x80, 0xf0, 0x03, + 0xc0, 0xff, 0x7f, + 0x10, 0x20, 0x01, 0x01, 0x00, + 0x00, + &delta_codec, + }, + { + "Envy24 audio (M Audio Audiophile 2496)", + 0x1412, 0xd634, + 0x10, 0x80, 0x72, 0x03, + 0x04, 0xfe, 0xfb, + 0x08, 0x02, 0x20, 0x00, 0x01, + 0x00, + &delta_codec, + }, + { + "Envy24 audio (Generic)", + 0, 0, + 0x0f, 0x00, 0x01, 0x03, + 0xff, 0x00, 0x00, + 0x10, 0x20, 0x40, 0x00, 0x00, + 0x00, + &delta_codec, /* default codec routines */ + } +}; + +static u_int32_t envy24_recfmt[] = { + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; +static struct pcmchan_caps envy24_reccaps = {8000, 96000, envy24_recfmt, 0}; + +static u_int32_t envy24_playfmt[] = { + AFMT_STEREO | AFMT_U8, + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; + +static struct pcmchan_caps envy24_playcaps = {8000, 96000, envy24_playfmt, 0}; + +struct envy24_emldma { + u_int32_t format; + void (*emldma)(struct sc_chinfo *); + int unit; +}; + +static struct envy24_emldma envy24_pemltab[] = { + {AFMT_STEREO | AFMT_U8, envy24_p8u, 2}, + {AFMT_STEREO | AFMT_S16_LE, envy24_p16sl, 4}, + {AFMT_STEREO | AFMT_S32_LE, envy24_p32sl, 8}, + {0, NULL, 0} +}; + +static struct envy24_emldma envy24_remltab[] = { + {AFMT_STEREO | AFMT_S16_LE, envy24_r16sl, 4}, + {AFMT_STEREO | AFMT_S32_LE, envy24_r32sl, 8}, + {0, NULL, 0} +}; + +/* -------------------------------------------------------------------- */ + +/* common routines */ +static u_int32_t +envy24_rdcs(struct sc_info *sc, int regno, int size) +{ + switch (size) { + case 1: + return bus_space_read_1(sc->cst, sc->csh, regno); + case 2: + return bus_space_read_2(sc->cst, sc->csh, regno); + case 4: + return bus_space_read_4(sc->cst, sc->csh, regno); + default: + return 0xffffffff; + } +} + +static void +envy24_wrcs(struct sc_info *sc, int regno, u_int32_t data, int size) +{ + switch (size) { + case 1: + bus_space_write_1(sc->cst, sc->csh, regno, data); + break; + case 2: + bus_space_write_2(sc->cst, sc->csh, regno, data); + break; + case 4: + bus_space_write_4(sc->cst, sc->csh, regno, data); + break; + } +} + +static u_int32_t +envy24_rdmt(struct sc_info *sc, int regno, int size) +{ + switch (size) { + case 1: + return bus_space_read_1(sc->mtt, sc->mth, regno); + case 2: + return bus_space_read_2(sc->mtt, sc->mth, regno); + case 4: + return bus_space_read_4(sc->mtt, sc->mth, regno); + default: + return 0xffffffff; + } +} + +static void +envy24_wrmt(struct sc_info *sc, int regno, u_int32_t data, int size) +{ + switch (size) { + case 1: + bus_space_write_1(sc->mtt, sc->mth, regno, data); + break; + case 2: + bus_space_write_2(sc->mtt, sc->mth, regno, data); + break; + case 4: + bus_space_write_4(sc->mtt, sc->mth, regno, data); + break; + } +} + +static u_int32_t +envy24_rdci(struct sc_info *sc, int regno) +{ + envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); + return envy24_rdcs(sc, ENVY24_CCS_DATA, 1); +} + +static void +envy24_wrci(struct sc_info *sc, int regno, u_int32_t data) +{ + envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); + envy24_wrcs(sc, ENVY24_CCS_DATA, data, 1); +} + +/* -------------------------------------------------------------------- */ + +/* I2C port/E2PROM access routines */ + +static int +envy24_rdi2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); +#endif + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDEV, + (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_RD, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + data = envy24_rdcs(sc, ENVY24_CCS_I2CDATA, 1); + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(): return 0x%x\n", data); +#endif + return (int)data; +} + +#if 0 +static int +envy24_wri2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr, u_int32_t data) +{ + u_int32_t tmp; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); +#endif + for (i = 0; i < ENVY24_TIMEOUT; i++) { + tmp = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((tmp & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDATA, data, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDEV, + (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_WR, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + + return 0; +} +#endif + +static int +envy24_rdrom(struct sc_info *sc, u_int32_t addr) +{ + u_int32_t data; + +#if(0) + device_printf(sc->dev, "envy24_rdrom(sc, 0x%02x)\n", addr); +#endif + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_ROM) == 0) { +#if(0) + device_printf(sc->dev, "envy24_rdrom(): E2PROM not presented\n"); +#endif + return -1; + } + + return envy24_rdi2c(sc, ENVY24_CCS_I2CDEV_ROM, addr); +} + +static struct cfg_info * +envy24_rom2cfg(struct sc_info *sc) +{ + struct cfg_info *buff; + int size; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(sc)\n"); +#endif + size = envy24_rdrom(sc, ENVY24_E2PROM_SIZE); + if (size < ENVY24_E2PROM_GPIODIR + 1) { +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(): ENVY24_E2PROM_SIZE-->%d\n", size); +#endif + return NULL; + } + buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); + if (buff == NULL) { +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(): malloc()\n"); +#endif + return NULL; + } + buff->free = 1; + + buff->subvendor = envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR) << 8; + buff->subvendor += envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR + 1); + buff->subdevice = envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE) << 8; + buff->subdevice += envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE + 1); + buff->scfg = envy24_rdrom(sc, ENVY24_E2PROM_SCFG); + buff->acl = envy24_rdrom(sc, ENVY24_E2PROM_ACL); + buff->i2s = envy24_rdrom(sc, ENVY24_E2PROM_I2S); + buff->spdif = envy24_rdrom(sc, ENVY24_E2PROM_SPDIF); + buff->gpiomask = envy24_rdrom(sc, ENVY24_E2PROM_GPIOMASK); + buff->gpiostate = envy24_rdrom(sc, ENVY24_E2PROM_GPIOSTATE); + buff->gpiodir = envy24_rdrom(sc, ENVY24_E2PROM_GPIODIR); + + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) + if (cfg_table[i].subvendor == buff->subvendor && + cfg_table[i].subdevice == buff->subdevice) + break; + buff->name = cfg_table[i].name; + buff->codec = cfg_table[i].codec; + + return buff; +} + +static void +envy24_cfgfree(struct cfg_info *cfg) { + if (cfg == NULL) + return; + if (cfg->free) + free(cfg, M_ENVY24); + return; +} + +/* -------------------------------------------------------------------- */ + +/* AC'97 codec access routines */ + +#if 0 +static int +envy24_coldcd(struct sc_info *sc) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_coldcd()\n"); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_CLD, 1); + DELAY(10); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); + DELAY(1000); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if (data & ENVY24_MT_AC97CMD_RDY) { + return 0; + } + } + + return -1; +} +#endif + +static int +envy24_slavecd(struct sc_info *sc) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_slavecd()\n"); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97CMD, + ENVY24_MT_AC97CMD_CLD | ENVY24_MT_AC97CMD_WRM, 1); + DELAY(10); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); + DELAY(1000); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if (data & ENVY24_MT_AC97CMD_RDY) { + return 0; + } + } + + return -1; +} + +#if 0 +static int +envy24_rdcd(kobj_t obj, void *devinfo, int regno) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdcd(obj, sc, 0x%02x)\n", regno); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_RD, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if ((data & ENVY24_MT_AC97CMD_RD) == 0) + break; + } + data = envy24_rdmt(sc, ENVY24_MT_AC97DLO, 2); + +#if(0) + device_printf(sc->dev, "envy24_rdcd(): return 0x%x\n", data); +#endif + return (int)data; +} + +static int +envy24_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + u_int32_t cmd; + int i; + +#if(0) + device_printf(sc->dev, "envy24_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); + envy24_wrmt(sc, ENVY24_MT_AC97DLO, (u_int32_t)data, 2); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_WR, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + cmd = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if ((cmd & ENVY24_MT_AC97CMD_WR) == 0) + break; + } + + return 0; +} + +static kobj_method_t envy24_ac97_methods[] = { + KOBJMETHOD(ac97_read, envy24_rdcd), + KOBJMETHOD(ac97_write, envy24_wrcd), + {0, 0} +}; +AC97_DECLARE(envy24_ac97); +#endif + +/* -------------------------------------------------------------------- */ + +/* GPIO access routines */ + +static u_int32_t +envy24_gpiord(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIODAT); +} + +static void +envy24_gpiowr(struct sc_info *sc, u_int32_t data) +{ +#if(0) + device_printf(sc->dev, "envy24_gpiowr(sc, 0x%02x)\n", data & 0xff); + return; +#endif + envy24_wrci(sc, ENVY24_CCI_GPIODAT, data); + return; +} + +#if 0 +static u_int32_t +envy24_gpiogetmask(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIOMASK); +} +#endif + +static void +envy24_gpiosetmask(struct sc_info *sc, u_int32_t mask) +{ + envy24_wrci(sc, ENVY24_CCI_GPIOMASK, mask); + return; +} + +#if 0 +static u_int32_t +envy24_gpiogetdir(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIOCTL); +} +#endif + +static void +envy24_gpiosetdir(struct sc_info *sc, u_int32_t dir) +{ + envy24_wrci(sc, ENVY24_CCI_GPIOCTL, dir); + return; +} + +/* -------------------------------------------------------------------- */ + +/* Envy24 I2C through GPIO bit-banging */ + +struct envy24_delta_ak4524_codec { + struct spicds_info *info; + struct sc_info *parent; + int dir; + int num; + int cs, cclk, cdti; +}; + +static void +envy24_gpio_i2c_ctl(void *codec, unsigned int scl, unsigned int sda) +{ + u_int32_t data = 0; + struct envy24_delta_ak4524_codec *ptr = codec; +#if(0) + device_printf(ptr->parent->dev, "--> %d, %d\n", scl, sda); +#endif + data = envy24_gpiord(ptr->parent); + data &= ~(SDA_GPIO | SCL_GPIO); + if (scl) data += SCL_GPIO; + if (sda) data += SDA_GPIO; + envy24_gpiowr(ptr->parent, data); + return; +} + +static void +i2c_wrbit(void *codec, void (*ctrl)(void*, unsigned int, unsigned int), int bit) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + unsigned int sda; + + if (bit) + sda = 1; + else + sda = 0; + + ctrl(ptr, 0, sda); + DELAY(I2C_DELAY); + ctrl(ptr, 1, sda); + DELAY(I2C_DELAY); + ctrl(ptr, 0, sda); + DELAY(I2C_DELAY); +} + +static void +i2c_start(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + + ctrl(ptr, 1, 1); + DELAY(I2C_DELAY); + ctrl(ptr, 1, 0); + DELAY(I2C_DELAY); + ctrl(ptr, 0, 0); + DELAY(I2C_DELAY); +} + +static void +i2c_stop(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + + ctrl(ptr, 0, 0); + DELAY(I2C_DELAY); + ctrl(ptr, 1, 0); + DELAY(I2C_DELAY); + ctrl(ptr, 1, 1); + DELAY(I2C_DELAY); +} + +static void +i2c_ack(void *codec, void (*ctrl)(void*, unsigned int, unsigned int)) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + + ctrl(ptr, 0, 1); + DELAY(I2C_DELAY); + ctrl(ptr, 1, 1); + DELAY(I2C_DELAY); + /* dummy, need routine to change gpio direction */ + ctrl(ptr, 0, 1); + DELAY(I2C_DELAY); +} + +static void +i2c_wr(void *codec, void (*ctrl)(void*, unsigned int, unsigned int), u_int32_t dev, int reg, u_int8_t val) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + int mask; + + i2c_start(ptr, ctrl); + + for (mask = 0x80; mask != 0; mask >>= 1) + i2c_wrbit(ptr, ctrl, dev & mask); + i2c_ack(ptr, ctrl); + + if (reg != 0xff) { + for (mask = 0x80; mask != 0; mask >>= 1) + i2c_wrbit(ptr, ctrl, reg & mask); + i2c_ack(ptr, ctrl); + } + + for (mask = 0x80; mask != 0; mask >>= 1) + i2c_wrbit(ptr, ctrl, val & mask); + i2c_ack(ptr, ctrl); + + i2c_stop(ptr, ctrl); +} + +/* -------------------------------------------------------------------- */ + +/* M-Audio Delta series AK4524 access interface routine */ + +static void +envy24_delta_ak4524_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) +{ + u_int32_t data = 0; + struct envy24_delta_ak4524_codec *ptr = codec; + +#if(0) + device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti); +#endif + data = envy24_gpiord(ptr->parent); + data &= ~(ptr->cs | ptr->cclk | ptr->cdti); + if (cs) data += ptr->cs; + if (cclk) data += ptr->cclk; + if (cdti) data += ptr->cdti; + envy24_gpiowr(ptr->parent, data); + return; +} + +static void * +envy24_delta_ak4524_create(device_t dev, void *info, int dir, int num) +{ + struct sc_info *sc = info; + struct envy24_delta_ak4524_codec *buff = NULL; + +#if(0) + device_printf(sc->dev, "envy24_delta_ak4524_create(dev, sc, %d, %d)\n", dir, num); +#endif + + buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); + if (buff == NULL) + return NULL; + + if (dir == PCMDIR_REC && sc->adc[num] != NULL) + buff->info = ((struct envy24_delta_ak4524_codec *)sc->adc[num])->info; + else if (dir == PCMDIR_PLAY && sc->dac[num] != NULL) + buff->info = ((struct envy24_delta_ak4524_codec *)sc->dac[num])->info; + else + buff->info = spicds_create(dev, buff, num, envy24_delta_ak4524_ctl); + if (buff->info == NULL) { + free(buff, M_ENVY24); + return NULL; + } + + buff->parent = sc; + buff->dir = dir; + buff->num = num; + + return (void *)buff; +} + +static void +envy24_delta_ak4524_destroy(void *codec) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_destroy()\n"); +#endif + + if (ptr->dir == PCMDIR_PLAY) { + if (ptr->parent->dac[ptr->num] != NULL) + spicds_destroy(ptr->info); + } + else { + if (ptr->parent->adc[ptr->num] != NULL) + spicds_destroy(ptr->info); + } + + free(codec, M_ENVY24); +} + +static void +envy24_delta_ak4524_init(void *codec) +{ +#if 0 + u_int32_t gpiomask, gpiodir; +#endif + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_init()\n"); +#endif + + /* + gpiomask = envy24_gpiogetmask(ptr->parent); + gpiomask &= ~(ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1); + envy24_gpiosetmask(ptr->parent, gpiomask); + gpiodir = envy24_gpiogetdir(ptr->parent); + gpiodir |= ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1; + envy24_gpiosetdir(ptr->parent, gpiodir); + */ + ptr->cs = ptr->parent->cfg->cs; +#if 0 + envy24_gpiosetmask(ptr->parent, ENVY24_GPIO_CS8414_STATUS); + envy24_gpiosetdir(ptr->parent, ~ENVY24_GPIO_CS8414_STATUS); + if (ptr->num == 0) + ptr->cs = ENVY24_GPIO_AK4524_CS0; + else + ptr->cs = ENVY24_GPIO_AK4524_CS1; + ptr->cclk = ENVY24_GPIO_AK4524_CCLK; +#endif + ptr->cclk = ptr->parent->cfg->cclk; + ptr->cdti = ptr->parent->cfg->cdti; + spicds_settype(ptr->info, ptr->parent->cfg->type); + spicds_setcif(ptr->info, ptr->parent->cfg->cif); + spicds_setformat(ptr->info, + AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X); + spicds_setdvc(ptr->info, AK452X_DVC_DEMOFF); + /* for the time being, init only first codec */ + if (ptr->num == 0) + spicds_init(ptr->info); + + /* 6fire rear input init test, set ptr->num to 1 for test */ + if (ptr->parent->cfg->subvendor == 0x153b && \ + ptr->parent->cfg->subdevice == 0x1138 && ptr->num == 100) { + ptr->cs = 0x02; + spicds_init(ptr->info); + device_printf(ptr->parent->dev, "6fire rear input init\n"); + i2c_wr(ptr, envy24_gpio_i2c_ctl, \ + PCA9554_I2CDEV, PCA9554_DIR, 0x80); + i2c_wr(ptr, envy24_gpio_i2c_ctl, \ + PCA9554_I2CDEV, PCA9554_OUT, 0x02); + } +} + +static void +envy24_delta_ak4524_reinit(void *codec) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_reinit()\n"); +#endif + + spicds_reinit(ptr->info); +} + +static void +envy24_delta_ak4524_setvolume(void *codec, int dir, unsigned int left, unsigned int right) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_set()\n"); +#endif + + spicds_set(ptr->info, dir, left, right); +} + +/* + There is no need for AK452[48] codec to set sample rate + static void + envy24_delta_ak4524_setrate(struct envy24_delta_ak4524_codec *codec, int which, int rate) + { + } +*/ + +/* -------------------------------------------------------------------- */ + +/* hardware access routeines */ + +static struct { + u_int32_t speed; + u_int32_t code; +} envy24_speedtab[] = { + {48000, ENVY24_MT_RATE_48000}, + {24000, ENVY24_MT_RATE_24000}, + {12000, ENVY24_MT_RATE_12000}, + {9600, ENVY24_MT_RATE_9600}, + {32000, ENVY24_MT_RATE_32000}, + {16000, ENVY24_MT_RATE_16000}, + {8000, ENVY24_MT_RATE_8000}, + {96000, ENVY24_MT_RATE_96000}, + {64000, ENVY24_MT_RATE_64000}, + {44100, ENVY24_MT_RATE_44100}, + {22050, ENVY24_MT_RATE_22050}, + {11025, ENVY24_MT_RATE_11025}, + {88200, ENVY24_MT_RATE_88200}, + {0, 0x10} +}; + +static int +envy24_setspeed(struct sc_info *sc, u_int32_t speed) { + u_int32_t code; + int i = 0; + +#if(0) + device_printf(sc->dev, "envy24_setspeed(sc, %d)\n", speed); +#endif + if (speed == 0) { + code = ENVY24_MT_RATE_SPDIF; /* external master clock */ + envy24_slavecd(sc); + } + else { + for (i = 0; envy24_speedtab[i].speed != 0; i++) { + if (envy24_speedtab[i].speed == speed) + break; + } + code = envy24_speedtab[i].code; + } +#if(0) + device_printf(sc->dev, "envy24_setspeed(): speed %d/code 0x%04x\n", envy24_speedtab[i].speed, code); +#endif + if (code < 0x10) { + envy24_wrmt(sc, ENVY24_MT_RATE, code, 1); + code = envy24_rdmt(sc, ENVY24_MT_RATE, 1); + code &= ENVY24_MT_RATE_MASK; + for (i = 0; envy24_speedtab[i].code < 0x10; i++) { + if (envy24_speedtab[i].code == code) + break; + } + speed = envy24_speedtab[i].speed; + } + else + speed = 0; + +#if(0) + device_printf(sc->dev, "envy24_setspeed(): return %d\n", speed); +#endif + return speed; +} + +static void +envy24_setvolume(struct sc_info *sc, unsigned ch) +{ +#if(0) + device_printf(sc->dev, "envy24_setvolume(sc, %d)\n", ch); +#endif +if (sc->cfg->subvendor==0x153b && sc->cfg->subdevice==0x1138 ) { + envy24_wrmt(sc, ENVY24_MT_VOLIDX, 16, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f7f, 2); + envy24_wrmt(sc, ENVY24_MT_VOLIDX, 17, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f7f, 2); + } + + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f00 | sc->left[ch], 2); + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); +} + +static void +envy24_mutevolume(struct sc_info *sc, unsigned ch) +{ + u_int32_t vol; + +#if(0) + device_printf(sc->dev, "envy24_mutevolume(sc, %d)\n", ch); +#endif + vol = ENVY24_VOL_MUTE << 8 | ENVY24_VOL_MUTE; + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); +} + +static u_int32_t +envy24_gethwptr(struct sc_info *sc, int dir) +{ + int unit, regno; + u_int32_t ptr, rtn; + +#if(0) + device_printf(sc->dev, "envy24_gethwptr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) { + rtn = sc->psize / 4; + unit = ENVY24_PLAY_BUFUNIT / 4; + regno = ENVY24_MT_PCNT; + } + else { + rtn = sc->rsize / 4; + unit = ENVY24_REC_BUFUNIT / 4; + regno = ENVY24_MT_RCNT; + } + + ptr = envy24_rdmt(sc, regno, 2); + rtn -= (ptr + 1); + rtn /= unit; + +#if(0) + device_printf(sc->dev, "envy24_gethwptr(): return %d\n", rtn); +#endif + return rtn; +} + +static void +envy24_updintr(struct sc_info *sc, int dir) +{ + int regptr, regintr; + u_int32_t mask, intr; + u_int32_t ptr, size, cnt; + u_int16_t blk; + +#if(0) + device_printf(sc->dev, "envy24_updintr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) { + blk = sc->blk[0]; + size = sc->psize / 4; + regptr = ENVY24_MT_PCNT; + regintr = ENVY24_MT_PTERM; + mask = ~ENVY24_MT_INT_PMASK; + } + else { + blk = sc->blk[1]; + size = sc->rsize / 4; + regptr = ENVY24_MT_RCNT; + regintr = ENVY24_MT_RTERM; + mask = ~ENVY24_MT_INT_RMASK; + } + + ptr = size - envy24_rdmt(sc, regptr, 2) - 1; + /* + cnt = blk - ptr % blk - 1; + if (cnt == 0) + cnt = blk - 1; + */ + cnt = blk - 1; +#if(0) + device_printf(sc->dev, "envy24_updintr():ptr = %d, blk = %d, cnt = %d\n", ptr, blk, cnt); +#endif + envy24_wrmt(sc, regintr, cnt, 2); + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); +#if(0) + device_printf(sc->dev, "envy24_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask); +#endif + envy24_wrmt(sc, ENVY24_MT_INT, intr & mask, 1); +#if(0) + device_printf(sc->dev, "envy24_updintr():INT-->0x%02x\n", + envy24_rdmt(sc, ENVY24_MT_INT, 1)); +#endif + + return; +} + +#if 0 +static void +envy24_maskintr(struct sc_info *sc, int dir) +{ + u_int32_t mask, intr; + +#if(0) + device_printf(sc->dev, "envy24_maskintr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + mask = ENVY24_MT_INT_PMASK; + else + mask = ENVY24_MT_INT_RMASK; + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); + envy24_wrmt(sc, ENVY24_MT_INT, intr | mask, 1); + + return; +} +#endif + +static int +envy24_checkintr(struct sc_info *sc, int dir) +{ + u_int32_t mask, stat, intr, rtn; + +#if(0) + device_printf(sc->dev, "envy24_checkintr(sc, %d)\n", dir); +#endif + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); + if (dir == PCMDIR_PLAY) { + if ((rtn = intr & ENVY24_MT_INT_PSTAT) != 0) { + mask = ~ENVY24_MT_INT_RSTAT; + stat = ENVY24_MT_INT_PSTAT | ENVY24_MT_INT_PMASK; + envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); + } + } + else { + if ((rtn = intr & ENVY24_MT_INT_RSTAT) != 0) { + mask = ~ENVY24_MT_INT_PSTAT; + stat = ENVY24_MT_INT_RSTAT | ENVY24_MT_INT_RMASK; + envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); + } + } + + return rtn; +} + +static void +envy24_start(struct sc_info *sc, int dir) +{ + u_int32_t stat, sw; + +#if(0) + device_printf(sc->dev, "envy24_start(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + sw = ENVY24_MT_PCTL_PSTART; + else + sw = ENVY24_MT_PCTL_RSTART; + + stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); + envy24_wrmt(sc, ENVY24_MT_PCTL, stat | sw, 1); +#if(0) + DELAY(100); + device_printf(sc->dev, "PADDR:0x%08x\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); + device_printf(sc->dev, "PCNT:%ld\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); +#endif + + return; +} + +static void +envy24_stop(struct sc_info *sc, int dir) +{ + u_int32_t stat, sw; + +#if(0) + device_printf(sc->dev, "envy24_stop(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + sw = ~ENVY24_MT_PCTL_PSTART; + else + sw = ~ENVY24_MT_PCTL_RSTART; + + stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); + envy24_wrmt(sc, ENVY24_MT_PCTL, stat & sw, 1); + + return; +} + +static int +envy24_route(struct sc_info *sc, int dac, int class, int adc, int rev) +{ + u_int32_t reg, mask; + u_int32_t left, right; + +#if(0) + device_printf(sc->dev, "envy24_route(sc, %d, %d, %d, %d)\n", + dac, class, adc, rev); +#endif + /* parameter pattern check */ + if (dac < 0 || ENVY24_ROUTE_DAC_SPDIF < dac) + return -1; + if (class == ENVY24_ROUTE_CLASS_MIX && + (dac != ENVY24_ROUTE_DAC_1 && dac != ENVY24_ROUTE_DAC_SPDIF)) + return -1; + if (rev) { + left = ENVY24_ROUTE_RIGHT; + right = ENVY24_ROUTE_LEFT; + } + else { + left = ENVY24_ROUTE_LEFT; + right = ENVY24_ROUTE_RIGHT; + } + + if (dac == ENVY24_ROUTE_DAC_SPDIF) { + reg = class | class << 2 | + ((adc << 1 | left) | left << 3) << 8 | + ((adc << 1 | right) | right << 3) << 12; +#if(0) + device_printf(sc->dev, "envy24_route(): MT_SPDOUT-->0x%04x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_SPDOUT, reg, 2); + } + else { + mask = ~(0x0303 << dac * 2); + reg = envy24_rdmt(sc, ENVY24_MT_PSDOUT, 2); + reg = (reg & mask) | ((class | class << 8) << dac * 2); +#if(0) + device_printf(sc->dev, "envy24_route(): MT_PSDOUT-->0x%04x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_PSDOUT, reg, 2); + mask = ~(0xff << dac * 8); + reg = envy24_rdmt(sc, ENVY24_MT_RECORD, 4); + reg = (reg & mask) | + (((adc << 1 | left) | left << 3) | + ((adc << 1 | right) | right << 3) << 4) << dac * 8; +#if(0) + device_printf(sc->dev, "envy24_route(): MT_RECORD-->0x%08x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_RECORD, reg, 4); + + /* 6fire rear input init test */ + envy24_wrmt(sc, ENVY24_MT_RECORD, 0x00, 4); + } + + return 0; +} + +/* -------------------------------------------------------------------- */ + +/* buffer copy routines */ +static void +envy24_p32sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int32_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getready(ch->buffer) / 8; + dmabuf = ch->parent->pbuf; + data = (u_int32_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer) / 4; + dst = src / 2 + ch->offset; + ssize = ch->size / 4; + dsize = ch->size / 8; + slot = ch->num * 2; + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = data[src]; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = data[src + 1]; + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } + + return; +} + +static void +envy24_p16sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int16_t *data; + int src, dst, ssize, dsize, slot; + int i; + +#if(0) + device_printf(ch->parent->dev, "envy24_p16sl()\n"); +#endif + length = sndbuf_getready(ch->buffer) / 4; + dmabuf = ch->parent->pbuf; + data = (u_int16_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer) / 2; + dst = src / 2 + ch->offset; + ssize = ch->size / 2; + dsize = ch->size / 4; + slot = ch->num * 2; +#if(0) + device_printf(ch->parent->dev, "envy24_p16sl():%lu-->%lu(%lu)\n", src, dst, length); +#endif + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = (u_int32_t)data[src] << 16; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = (u_int32_t)data[src + 1] << 16; +#if(0) + if (i < 16) { + printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot]); + printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1]); + } +#endif + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } +#if(0) + printf("\n"); +#endif + + return; +} + +static void +envy24_p8u(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int8_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getready(ch->buffer) / 2; + dmabuf = ch->parent->pbuf; + data = (u_int8_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer); + dst = src / 2 + ch->offset; + ssize = ch->size; + dsize = ch->size / 4; + slot = ch->num * 2; + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = ((u_int32_t)data[src] ^ 0x80) << 24; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = ((u_int32_t)data[src + 1] ^ 0x80) << 24; + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } + + return; +} + +static void +envy24_r32sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int32_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getfree(ch->buffer) / 8; + dmabuf = ch->parent->rbuf; + data = (u_int32_t *)ch->data; + dst = sndbuf_getfreeptr(ch->buffer) / 4; + src = dst / 2 + ch->offset; + dsize = ch->size / 4; + ssize = ch->size / 8; + slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; + + for (i = 0; i < length; i++) { + data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer; + data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer; + dst += 2; + dst %= dsize; + src++; + src %= ssize; + } + + return; +} + +static void +envy24_r16sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int16_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getfree(ch->buffer) / 4; + dmabuf = ch->parent->rbuf; + data = (u_int16_t *)ch->data; + dst = sndbuf_getfreeptr(ch->buffer) / 2; + src = dst / 2 + ch->offset; + dsize = ch->size / 2; + ssize = ch->size / 8; + slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; + + for (i = 0; i < length; i++) { + data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer; + data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer; + dst += 2; + dst %= dsize; + src++; + src %= ssize; + } + + return; +} + +/* -------------------------------------------------------------------- */ + +/* channel interface */ +static void * +envy24chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + struct sc_chinfo *ch; + unsigned num; + +#if(0) + device_printf(sc->dev, "envy24chan_init(obj, devinfo, b, c, %d)\n", dir); +#endif + snd_mtxlock(sc->lock); + if ((sc->chnum > ENVY24_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || + (sc->chnum < ENVY24_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { + snd_mtxunlock(sc->lock); + return NULL; + } + num = sc->chnum; + + ch = &sc->chan[num]; + ch->size = 8 * ENVY24_SAMPLE_NUM; + ch->data = malloc(ch->size, M_ENVY24, M_NOWAIT); + if (ch->data == NULL) { + ch->size = 0; + ch = NULL; + } + else { + ch->buffer = b; + ch->channel = c; + ch->parent = sc; + ch->dir = dir; + /* set channel map */ + ch->num = envy24_chanmap[num]; + snd_mtxunlock(sc->lock); + sndbuf_setup(ch->buffer, ch->data, ch->size); + snd_mtxlock(sc->lock); + /* these 2 values are dummy */ + ch->unit = 4; + ch->blk = 10240; + } + snd_mtxunlock(sc->lock); + + return ch; +} + +static int +envy24chan_free(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + +#if(0) + device_printf(sc->dev, "envy24chan_free()\n"); +#endif + snd_mtxlock(sc->lock); + if (ch->data != NULL) { + free(ch->data, M_ENVY24); + ch->data = NULL; + } + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24chan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + struct envy24_emldma *emltab; + /* unsigned int bcnt, bsize; */ + int i; + +#if(0) + device_printf(sc->dev, "envy24chan_setformat(obj, data, 0x%08x)\n", format); +#endif + snd_mtxlock(sc->lock); + /* check and get format related information */ + if (ch->dir == PCMDIR_PLAY) + emltab = envy24_pemltab; + else + emltab = envy24_remltab; + if (emltab == NULL) { + snd_mtxunlock(sc->lock); + return -1; + } + for (i = 0; emltab[i].format != 0; i++) + if (emltab[i].format == format) + break; + if (emltab[i].format == 0) { + snd_mtxunlock(sc->lock); + return -1; + } + + /* set format information */ + ch->format = format; + ch->emldma = emltab[i].emldma; + if (ch->unit > emltab[i].unit) + ch->blk *= ch->unit / emltab[i].unit; + else + ch->blk /= emltab[i].unit / ch->unit; + ch->unit = emltab[i].unit; + + /* set channel buffer information */ + ch->size = ch->unit * ENVY24_SAMPLE_NUM; +#if 0 + if (ch->dir == PCMDIR_PLAY) + bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT; + else + bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT; + bsize *= ch->unit; + bcnt = ch->size / bsize; + sndbuf_resize(ch->buffer, bcnt, bsize); +#endif + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24chan_setformat(): return 0x%08x\n", 0); +#endif + return 0; +} + +/* + IMPLEMENT NOTICE: In this driver, setspeed function only do setting + of speed information value. And real hardware speed setting is done + at start triggered(see envy24chan_trigger()). So, at this function + is called, any value that ENVY24 can use is able to set. But, at + start triggerd, some other channel is running, and that channel's + speed isn't same with, then trigger function will fail. +*/ +static int +envy24chan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct sc_chinfo *ch = data; + u_int32_t val, prev; + int i; + +#if(0) + device_printf(ch->parent->dev, "envy24chan_setspeed(obj, data, %d)\n", speed); +#endif + prev = 0x7fffffff; + for (i = 0; (val = envy24_speed[i]) != 0; i++) { + if (abs(val - speed) < abs(prev - speed)) + prev = val; + else + break; + } + ch->speed = prev; + +#if(0) + device_printf(ch->parent->dev, "envy24chan_setspeed(): return %d\n", ch->speed); +#endif + return ch->speed; +} + +static int +envy24chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct sc_chinfo *ch = data; + /* struct sc_info *sc = ch->parent; */ + u_int32_t size, prev; + unsigned int bcnt, bsize; + +#if(0) + device_printf(sc->dev, "envy24chan_setblocksize(obj, data, %d)\n", blocksize); +#endif + prev = 0x7fffffff; + /* snd_mtxlock(sc->lock); */ + for (size = ch->size / 2; size > 0; size /= 2) { + if (abs(size - blocksize) < abs(prev - blocksize)) + prev = size; + else + break; + } + + ch->blk = prev / ch->unit; + if (ch->dir == PCMDIR_PLAY) + ch->blk *= ENVY24_PLAY_BUFUNIT / 4; + else + ch->blk *= ENVY24_REC_BUFUNIT / 4; + /* set channel buffer information */ + /* ch->size = ch->unit * ENVY24_SAMPLE_NUM; */ + if (ch->dir == PCMDIR_PLAY) + bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT; + else + bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT; + bsize *= ch->unit; + bcnt = ch->size / bsize; + sndbuf_resize(ch->buffer, bcnt, bsize); + /* snd_mtxunlock(sc->lock); */ + +#if(0) + device_printf(sc->dev, "envy24chan_setblocksize(): return %d\n", prev); +#endif + return prev; +} + +/* semantic note: must start at beginning of buffer */ +static int +envy24chan_trigger(kobj_t obj, void *data, int go) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t ptr; + int slot; +#if 0 + int i; + + device_printf(sc->dev, "envy24chan_trigger(obj, data, %d)\n", go); +#endif + snd_mtxlock(sc->lock); + if (ch->dir == PCMDIR_PLAY) + slot = 0; + else + slot = 1; + switch (go) { + case PCMTRIG_START: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): start\n"); +#endif + /* check or set channel speed */ + if (sc->run[0] == 0 && sc->run[1] == 0) { + sc->speed = envy24_setspeed(sc, ch->speed); + sc->caps[0].minspeed = sc->caps[0].maxspeed = sc->speed; + sc->caps[1].minspeed = sc->caps[1].maxspeed = sc->speed; + } + else if (ch->speed != 0 && ch->speed != sc->speed) + return -1; + if (ch->speed == 0) + ch->channel->speed = sc->speed; + /* start or enable channel */ + sc->run[slot]++; + if (sc->run[slot] == 1) { + /* first channel */ + ch->offset = 0; + sc->blk[slot] = ch->blk; + } + else { + ptr = envy24_gethwptr(sc, ch->dir); + ch->offset = ((ptr / ch->blk + 1) * ch->blk % + (ch->size / 4)) * 4 / ch->unit; + if (ch->blk < sc->blk[slot]) + sc->blk[slot] = ch->blk; + } + if (ch->dir == PCMDIR_PLAY) { + ch->emldma(ch); + envy24_setvolume(sc, ch->num); + } + envy24_updintr(sc, ch->dir); + if (sc->run[slot] == 1) + envy24_start(sc, ch->dir); + ch->run = 1; + break; + case PCMTRIG_EMLDMAWR: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): emldmawr\n"); +#endif + if (ch->run != 1) + return -1; + ch->emldma(ch); + break; + case PCMTRIG_EMLDMARD: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): emldmard\n"); +#endif + if (ch->run != 1) + return -1; + ch->emldma(ch); + break; + case PCMTRIG_ABORT: + if (ch->run) { +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): abort\n"); +#endif + ch->run = 0; + sc->run[slot]--; + if (ch->dir == PCMDIR_PLAY) + envy24_mutevolume(sc, ch->num); + if (sc->run[slot] == 0) { + envy24_stop(sc, ch->dir); + sc->intr[slot] = 0; + } +#if 0 + else if (ch->blk == sc->blk[slot]) { + sc->blk[slot] = ENVY24_SAMPLE_NUM / 2; + for (i = 0; i < ENVY24_CHAN_NUM; i++) { + if (sc->chan[i].dir == ch->dir && + sc->chan[i].run == 1 && + sc->chan[i].blk < sc->blk[slot]) + sc->blk[slot] = sc->chan[i].blk; + } + if (ch->blk != sc->blk[slot]) + envy24_updintr(sc, ch->dir); + } +#endif + } + break; + } + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24chan_getptr(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t ptr; + int rtn; + +#if(0) + device_printf(sc->dev, "envy24chan_getptr()\n"); +#endif + snd_mtxlock(sc->lock); + ptr = envy24_gethwptr(sc, ch->dir); + rtn = ptr * ch->unit; + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24chan_getptr(): return %d\n", + rtn); +#endif + return rtn; +} + +static struct pcmchan_caps * +envy24chan_getcaps(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + struct pcmchan_caps *rtn; + +#if(0) + device_printf(sc->dev, "envy24chan_getcaps()\n"); +#endif + snd_mtxlock(sc->lock); + if (ch->dir == PCMDIR_PLAY) { + if (sc->run[0] == 0) + rtn = &envy24_playcaps; + else + rtn = &sc->caps[0]; + } + else { + if (sc->run[1] == 0) + rtn = &envy24_reccaps; + else + rtn = &sc->caps[1]; + } + snd_mtxunlock(sc->lock); + + return rtn; +} + +static kobj_method_t envy24chan_methods[] = { + KOBJMETHOD(channel_init, envy24chan_init), + KOBJMETHOD(channel_free, envy24chan_free), + KOBJMETHOD(channel_setformat, envy24chan_setformat), + KOBJMETHOD(channel_setspeed, envy24chan_setspeed), + KOBJMETHOD(channel_setblocksize, envy24chan_setblocksize), + KOBJMETHOD(channel_trigger, envy24chan_trigger), + KOBJMETHOD(channel_getptr, envy24chan_getptr), + KOBJMETHOD(channel_getcaps, envy24chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(envy24chan); + +/* -------------------------------------------------------------------- */ + +/* mixer interface */ + +static int +envy24mixer_init(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + +#if(0) + device_printf(sc->dev, "envy24mixer_init()\n"); +#endif + if (sc == NULL) + return -1; + + /* set volume control rate */ + snd_mtxlock(sc->lock); + envy24_wrmt(sc, ENVY24_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */ + + mix_setdevs(m, ENVY24_MIX_MASK); + mix_setrecdevs(m, ENVY24_MIX_REC_MASK); + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24mixer_reinit(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + + if (sc == NULL) + return -1; +#if(0) + device_printf(sc->dev, "envy24mixer_reinit()\n"); +#endif + + return 0; +} + +static int +envy24mixer_uninit(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + + if (sc == NULL) + return -1; +#if(0) + device_printf(sc->dev, "envy24mixer_uninit()\n"); +#endif + + return 0; +} + +static int +envy24mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct sc_info *sc = mix_getdevinfo(m); + int ch = envy24_mixmap[dev]; + int hwch; + int i; + + if (sc == NULL) + return -1; + if (dev == 0 && sc->cfg->codec->setvolume == NULL) + return -1; + if (dev != 0 && ch == -1) + return -1; + hwch = envy24_chanmap[ch]; +#if(0) + device_printf(sc->dev, "envy24mixer_set(m, %d, %d, %d)\n", + dev, left, right); +#endif + + snd_mtxlock(sc->lock); + if (dev == 0) { + for (i = 0; i < sc->dacn; i++) { + sc->cfg->codec->setvolume(sc->dac[i], PCMDIR_PLAY, left, right); + } + } + else { + /* set volume value for hardware */ + if ((sc->left[hwch] = 100 - left) > ENVY24_VOL_MIN) + sc->left[hwch] = ENVY24_VOL_MUTE; + if ((sc->right[hwch] = 100 - right) > ENVY24_VOL_MIN) + sc->right[hwch] = ENVY24_VOL_MUTE; + + /* set volume for record channel and running play channel */ + if (hwch > ENVY24_CHAN_PLAY_SPDIF || sc->chan[ch].run) + envy24_setvolume(sc, hwch); + } + snd_mtxunlock(sc->lock); + + return right << 8 | left; +} + +static u_int32_t +envy24mixer_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct sc_info *sc = mix_getdevinfo(m); + int ch = envy24_mixmap[src]; +#if(0) + device_printf(sc->dev, "envy24mixer_setrecsrc(m, %d)\n", src); +#endif + + if (ch > ENVY24_CHAN_PLAY_SPDIF) + sc->src = ch; + return src; +} + +static kobj_method_t envy24mixer_methods[] = { + KOBJMETHOD(mixer_init, envy24mixer_init), + KOBJMETHOD(mixer_reinit, envy24mixer_reinit), + KOBJMETHOD(mixer_uninit, envy24mixer_uninit), + KOBJMETHOD(mixer_set, envy24mixer_set), + KOBJMETHOD(mixer_setrecsrc, envy24mixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(envy24mixer); + +/* -------------------------------------------------------------------- */ + +/* The interrupt handler */ +static void +envy24_intr(void *p) +{ + struct sc_info *sc = (struct sc_info *)p; + struct sc_chinfo *ch; + u_int32_t ptr, dsize, feed; + int i; + +#if(0) + device_printf(sc->dev, "envy24_intr()\n"); +#endif + snd_mtxlock(sc->lock); + if (envy24_checkintr(sc, PCMDIR_PLAY)) { +#if(0) + device_printf(sc->dev, "envy24_intr(): play\n"); +#endif + dsize = sc->psize / 4; + ptr = dsize - envy24_rdmt(sc, ENVY24_MT_PCNT, 2) - 1; +#if(0) + device_printf(sc->dev, "envy24_intr(): ptr = %d-->", ptr); +#endif + ptr -= ptr % sc->blk[0]; + feed = (ptr + dsize - sc->intr[0]) % dsize; +#if(0) + printf("%d intr = %d feed = %d\n", ptr, sc->intr[0], feed); +#endif + for (i = ENVY24_CHAN_PLAY_DAC1; i <= ENVY24_CHAN_PLAY_SPDIF; i++) { + ch = &sc->chan[i]; +#if(0) + if (ch->run) + device_printf(sc->dev, "envy24_intr(): chan[%d].blk = %d\n", i, ch->blk); +#endif + if (ch->run && ch->blk <= feed) { + snd_mtxunlock(sc->lock); + chn_intr(ch->channel); + snd_mtxlock(sc->lock); + } + } + sc->intr[0] = ptr; + envy24_updintr(sc, PCMDIR_PLAY); + } + if (envy24_checkintr(sc, PCMDIR_REC)) { +#if(0) + device_printf(sc->dev, "envy24_intr(): rec\n"); +#endif + dsize = sc->rsize / 4; + ptr = dsize - envy24_rdmt(sc, ENVY24_MT_RCNT, 2) - 1; + ptr -= ptr % sc->blk[1]; + feed = (ptr + dsize - sc->intr[1]) % dsize; + for (i = ENVY24_CHAN_REC_ADC1; i <= ENVY24_CHAN_REC_SPDIF; i++) { + ch = &sc->chan[i]; + if (ch->run && ch->blk <= feed) { + snd_mtxunlock(sc->lock); + chn_intr(ch->channel); + snd_mtxlock(sc->lock); + } + } + sc->intr[1] = ptr; + envy24_updintr(sc, PCMDIR_REC); + } + snd_mtxunlock(sc->lock); + + return; +} + +/* + * Probe and attach the card + */ + +static int +envy24_pci_probe(device_t dev) +{ + u_int16_t sv, sd; + int i; + +#if(0) + printf("envy24_pci_probe()\n"); +#endif + if (pci_get_device(dev) == PCID_ENVY24 && + pci_get_vendor(dev) == PCIV_ENVY24) { + sv = pci_get_subvendor(dev); + sd = pci_get_subdevice(dev); + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { + if (cfg_table[i].subvendor == sv && + cfg_table[i].subdevice == sd) { + break; + } + } + device_set_desc(dev, cfg_table[i].name); +#if(0) + printf("envy24_pci_probe(): return 0\n"); +#endif + return 0; + } + else { +#if(0) + printf("envy24_pci_probe(): return ENXIO\n"); +#endif + return ENXIO; + } +} + +static void +envy24_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + /* struct sc_info *sc = (struct sc_info *)arg; */ + +#if(0) + device_printf(sc->dev, "envy24_dmapsetmap()\n"); + if (bootverbose) { + printf("envy24(play): setmap %lx, %lx; ", + (unsigned long)segs->ds_addr, + (unsigned long)segs->ds_len); + printf("%p -> %lx\n", sc->pmap, (unsigned long)vtophys(sc->pmap)); + } +#endif +} + +static void +envy24_dmarsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + /* struct sc_info *sc = (struct sc_info *)arg; */ + +#if(0) + device_printf(sc->dev, "envy24_dmarsetmap()\n"); + if (bootverbose) { + printf("envy24(record): setmap %lx, %lx; ", + (unsigned long)segs->ds_addr, + (unsigned long)segs->ds_len); + printf("%p -> %lx\n", sc->rmap, (unsigned long)vtophys(sc->pmap)); + } +#endif +} + +static void +envy24_dmafree(struct sc_info *sc) +{ +#if(0) + device_printf(sc->dev, "envy24_dmafree():"); + if (sc->rmap) printf(" sc->rmap(0x%08x)", (u_int32_t)sc->rmap); + else printf(" sc->rmap(null)"); + if (sc->pmap) printf(" sc->pmap(0x%08x)", (u_int32_t)sc->pmap); + else printf(" sc->pmap(null)"); + if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (u_int32_t)sc->rbuf); + else printf(" sc->rbuf(null)"); + if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (u_int32_t)sc->pbuf); + else printf(" sc->pbuf(null)\n"); +#endif +#if(0) + if (sc->rmap) + bus_dmamap_unload(sc->dmat, sc->rmap); + if (sc->pmap) + bus_dmamap_unload(sc->dmat, sc->pmap); + if (sc->rbuf) + bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); + if (sc->pbuf) + bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); +#else + bus_dmamap_unload(sc->dmat, sc->rmap); + bus_dmamap_unload(sc->dmat, sc->pmap); + bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); + bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); +#endif + + sc->rmap = sc->pmap = NULL; + sc->pbuf = NULL; + sc->rbuf = NULL; + + return; +} + +static int +envy24_dmainit(struct sc_info *sc) +{ + u_int32_t addr; + +#if(0) + device_printf(sc->dev, "envy24_dmainit()\n"); +#endif + /* init values */ + sc->psize = ENVY24_PLAY_BUFUNIT * ENVY24_SAMPLE_NUM; + sc->rsize = ENVY24_REC_BUFUNIT * ENVY24_SAMPLE_NUM; + sc->pbuf = NULL; + sc->rbuf = NULL; + sc->pmap = sc->rmap = NULL; + sc->blk[0] = sc->blk[1] = 0; + + /* allocate DMA buffer */ +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->pbuf\n"); +#endif + if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_NOWAIT, &sc->pmap)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->rbuf\n"); +#endif + if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_NOWAIT, &sc->rmap)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->pmap\n"); +#endif + if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24_dmapsetmap, sc, 0)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->rmap\n"); +#endif + if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24_dmarsetmap, sc, 0)) + goto bad; + bzero(sc->pbuf, sc->psize); + bzero(sc->rbuf, sc->rsize); + + /* set values to register */ + addr = vtophys(sc->pbuf); +#if(0) + device_printf(sc->dev, "pbuf(0x%08x)\n", addr); +#endif + envy24_wrmt(sc, ENVY24_MT_PADDR, addr, 4); +#if(0) + device_printf(sc->dev, "PADDR-->(0x%08x)\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); + device_printf(sc->dev, "psize(%ld)\n", sc->psize / 4 - 1); +#endif + envy24_wrmt(sc, ENVY24_MT_PCNT, sc->psize / 4 - 1, 2); +#if(0) + device_printf(sc->dev, "PCNT-->(%ld)\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); +#endif + addr = vtophys(sc->rbuf); + envy24_wrmt(sc, ENVY24_MT_RADDR, addr, 4); + envy24_wrmt(sc, ENVY24_MT_RCNT, sc->rsize / 4 - 1, 2); + + return 0; + bad: + envy24_dmafree(sc); + return ENOSPC; +} + +static void +envy24_putcfg(struct sc_info *sc) +{ + device_printf(sc->dev, "system configuration\n"); + printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n", + sc->cfg->subvendor, sc->cfg->subdevice); + printf(" XIN2 Clock Source: "); + switch (sc->cfg->scfg & PCIM_SCFG_XIN2) { + case 0x00: + printf("22.5792MHz(44.1kHz*512)\n"); + break; + case 0x40: + printf("16.9344MHz(44.1kHz*384)\n"); + break; + case 0x80: + printf("from external clock synthesizer chip\n"); + break; + default: + printf("illeagal system setting\n"); + } + printf(" MPU-401 UART(s) #: "); + if (sc->cfg->scfg & PCIM_SCFG_MPU) + printf("2\n"); + else + printf("1\n"); + printf(" AC'97 codec: "); + if (sc->cfg->scfg & PCIM_SCFG_AC97) + printf("not exist\n"); + else + printf("exist\n"); + printf(" ADC #: "); + printf("%d\n", sc->adcn); + printf(" DAC #: "); + printf("%d\n", sc->dacn); + printf(" Multi-track converter type: "); + if ((sc->cfg->acl & PCIM_ACL_MTC) == 0) { + printf("AC'97(SDATA_OUT:"); + if (sc->cfg->acl & PCIM_ACL_OMODE) + printf("packed"); + else + printf("split"); + printf("|SDATA_IN:"); + if (sc->cfg->acl & PCIM_ACL_IMODE) + printf("packed"); + else + printf("split"); + printf(")\n"); + } + else { + printf("I2S("); + if (sc->cfg->i2s & PCIM_I2S_VOL) + printf("with volume, "); + if (sc->cfg->i2s & PCIM_I2S_96KHZ) + printf("96KHz support, "); + switch (sc->cfg->i2s & PCIM_I2S_RES) { + case PCIM_I2S_16BIT: + printf("16bit resolution, "); + break; + case PCIM_I2S_18BIT: + printf("18bit resolution, "); + break; + case PCIM_I2S_20BIT: + printf("20bit resolution, "); + break; + case PCIM_I2S_24BIT: + printf("24bit resolution, "); + break; + } + printf("ID#0x%x)\n", sc->cfg->i2s & PCIM_I2S_ID); + } + printf(" S/PDIF(IN/OUT): "); + if (sc->cfg->spdif & PCIM_SPDIF_IN) + printf("1/"); + else + printf("0/"); + if (sc->cfg->spdif & PCIM_SPDIF_OUT) + printf("1 "); + else + printf("0 "); + if (sc->cfg->spdif & (PCIM_SPDIF_IN | PCIM_SPDIF_OUT)) + printf("ID# 0x%02x\n", (sc->cfg->spdif & PCIM_SPDIF_ID) >> 2); + printf(" GPIO(mask/dir/state): 0x%02x/0x%02x/0x%02x\n", + sc->cfg->gpiomask, sc->cfg->gpiodir, sc->cfg->gpiostate); +} + +static int +envy24_init(struct sc_info *sc) +{ + u_int32_t data; +#if(0) + int rtn; +#endif + int i; + u_int32_t sv, sd; + + +#if(0) + device_printf(sc->dev, "envy24_init()\n"); +#endif + + /* reset chip */ + envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_RESET | ENVY24_CCS_CTL_NATIVE, 1); + DELAY(200); + envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_NATIVE, 1); + DELAY(200); + + /* legacy hardware disable */ + data = pci_read_config(sc->dev, PCIR_LAC, 2); + data |= PCIM_LAC_DISABLE; + pci_write_config(sc->dev, PCIR_LAC, data, 2); + + /* check system configuration */ + sc->cfg = NULL; + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { + /* 1st: search configuration from table */ + sv = pci_get_subvendor(sc->dev); + sd = pci_get_subdevice(sc->dev); + if (sv == cfg_table[i].subvendor && sd == cfg_table[i].subdevice) { +#if(0) + device_printf(sc->dev, "Set configuration from table\n"); +#endif + sc->cfg = &cfg_table[i]; + break; + } + } + if (sc->cfg == NULL) { + /* 2nd: read configuration from table */ + sc->cfg = envy24_rom2cfg(sc); + } + sc->adcn = ((sc->cfg->scfg & PCIM_SCFG_ADC) >> 2) + 1; + sc->dacn = (sc->cfg->scfg & PCIM_SCFG_DAC) + 1; + + if (1 /* bootverbose */) { + envy24_putcfg(sc); + } + + /* set system configuration */ + pci_write_config(sc->dev, PCIR_SCFG, sc->cfg->scfg, 1); + pci_write_config(sc->dev, PCIR_ACL, sc->cfg->acl, 1); + pci_write_config(sc->dev, PCIR_I2S, sc->cfg->i2s, 1); + pci_write_config(sc->dev, PCIR_SPDIF, sc->cfg->spdif, 1); + envy24_gpiosetmask(sc, sc->cfg->gpiomask); + envy24_gpiosetdir(sc, sc->cfg->gpiodir); + envy24_gpiowr(sc, sc->cfg->gpiostate); + for (i = 0; i < sc->adcn; i++) { + sc->adc[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_REC, i); + sc->cfg->codec->init(sc->adc[i]); + } + for (i = 0; i < sc->dacn; i++) { + sc->dac[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_PLAY, i); + sc->cfg->codec->init(sc->dac[i]); + } + + /* initialize DMA buffer */ +#if(0) + device_printf(sc->dev, "envy24_init(): initialize DMA buffer\n"); +#endif + if (envy24_dmainit(sc)) + return ENOSPC; + + /* initialize status */ + sc->run[0] = sc->run[1] = 0; + sc->intr[0] = sc->intr[1] = 0; + sc->speed = 0; + sc->caps[0].fmtlist = envy24_playfmt; + sc->caps[1].fmtlist = envy24_recfmt; + + /* set channel router */ + envy24_route(sc, ENVY24_ROUTE_DAC_1, ENVY24_ROUTE_CLASS_MIX, 0, 0); + envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_DMA, 0, 0); + /* envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_MIX, 0, 0); */ + + /* set macro interrupt mask */ + data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); + envy24_wrcs(sc, ENVY24_CCS_IMASK, data & ~ENVY24_CCS_IMASK_PMT, 1); + data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); +#if(0) + device_printf(sc->dev, "envy24_init(): CCS_IMASK-->0x%02x\n", data); +#endif + + return 0; +} + +static int +envy24_alloc_resource(struct sc_info *sc) +{ + /* allocate I/O port resource */ + sc->csid = PCIR_CCS; + sc->cs = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->csid, 0, ~0, 1, RF_ACTIVE); + sc->ddmaid = PCIR_DDMA; + sc->ddma = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->ddmaid, 0, ~0, 1, RF_ACTIVE); + sc->dsid = PCIR_DS; + sc->ds = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->dsid, 0, ~0, 1, RF_ACTIVE); + sc->mtid = PCIR_MT; + sc->mt = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->mtid, 0, ~0, 1, RF_ACTIVE); + if (!sc->cs || !sc->ddma || !sc->ds || !sc->mt) { + device_printf(sc->dev, "unable to map IO port space\n"); + return ENXIO; + } + sc->cst = rman_get_bustag(sc->cs); + sc->csh = rman_get_bushandle(sc->cs); + sc->ddmat = rman_get_bustag(sc->ddma); + sc->ddmah = rman_get_bushandle(sc->ddma); + sc->dst = rman_get_bustag(sc->ds); + sc->dsh = rman_get_bushandle(sc->ds); + sc->mtt = rman_get_bustag(sc->mt); + sc->mth = rman_get_bushandle(sc->mt); +#if(0) + device_printf(sc->dev, + "IO port register values\nCCS: 0x%lx\nDDMA: 0x%lx\nDS: 0x%lx\nMT: 0x%lx\n", + pci_read_config(sc->dev, PCIR_CCS, 4), + pci_read_config(sc->dev, PCIR_DDMA, 4), + pci_read_config(sc->dev, PCIR_DS, 4), + pci_read_config(sc->dev, PCIR_MT, 4)); +#endif + + /* allocate interupt resource */ + sc->irqid = 0; + sc->irq = bus_alloc_resource(sc->dev, SYS_RES_IRQ, &sc->irqid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (!sc->irq || + snd_setup_intr(sc->dev, sc->irq, INTR_MPSAFE, envy24_intr, sc, &sc->ih)) { + device_printf(sc->dev, "unable to map interrupt\n"); + return ENXIO; + } + + /* allocate DMA resource */ + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev), + /*alignment*/4, + /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_ENVY24, + /*highaddr*/BUS_SPACE_MAXADDR_ENVY24, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/BUS_SPACE_MAXSIZE_ENVY24, + /*nsegments*/1, /*maxsegsz*/0x3ffff, + /*flags*/0, /*lockfunc*/busdma_lock_mutex, + /*lockarg*/&Giant, &sc->dmat) != 0) { + device_printf(sc->dev, "unable to create dma tag\n"); + return ENXIO; + } + + return 0; +} + +static int +envy24_pci_attach(device_t dev) +{ + u_int32_t data; + struct sc_info *sc; + char status[SND_STATUSLEN]; + int err = 0; + int i; + +#if(0) + device_printf(dev, "envy24_pci_attach()\n"); +#endif + /* get sc_info data area */ + if ((sc = malloc(sizeof(*sc), M_ENVY24, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(sc, sizeof(*sc)); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_envy24 softc"); + sc->dev = dev; + + /* initialize PCI interface */ + data = pci_read_config(dev, PCIR_COMMAND, 2); + data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN); + pci_write_config(dev, PCIR_COMMAND, data, 2); + data = pci_read_config(dev, PCIR_COMMAND, 2); + + /* allocate resources */ + err = envy24_alloc_resource(sc); + if (err) { + device_printf(dev, "unable to allocate system resources\n"); + goto bad; + } + + /* initialize card */ + err = envy24_init(sc); + if (err) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + /* set multi track mixer */ + mixer_init(dev, &envy24mixer_class, sc); + + /* set channel information */ + err = pcm_register(dev, sc, 5, 2 + sc->adcn); + if (err) + goto bad; + sc->chnum = 0; + for (i = 0; i < 5; i++) { + pcm_addchan(dev, PCMDIR_PLAY, &envy24chan_class, sc); + sc->chnum++; + } + for (i = 0; i < 2 + sc->adcn; i++) { + pcm_addchan(dev, PCMDIR_REC, &envy24chan_class, sc); + sc->chnum++; + } + + /* set status iformation */ + snprintf(status, SND_STATUSLEN, + "at io 0x%lx:%ld,0x%lx:%ld,0x%lx:%ld,0x%lx:%ld irq %ld", + rman_get_start(sc->cs), + rman_get_end(sc->cs) - rman_get_start(sc->cs) + 1, + rman_get_start(sc->ddma), + rman_get_end(sc->ddma) - rman_get_start(sc->ddma) + 1, + rman_get_start(sc->ds), + rman_get_end(sc->ds) - rman_get_start(sc->ds) + 1, + rman_get_start(sc->mt), + rman_get_end(sc->mt) - rman_get_start(sc->mt) + 1, + rman_get_start(sc->irq)); + pcm_setstatus(dev, status); + + return 0; + +bad: + if (sc->ih) + bus_teardown_intr(dev, sc->irq, sc->ih); + if (sc->irq) + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + envy24_dmafree(sc); + if (sc->dmat) + bus_dma_tag_destroy(sc->dmat); + if (sc->cfg->codec->destroy != NULL) { + for (i = 0; i < sc->adcn; i++) + sc->cfg->codec->destroy(sc->adc[i]); + for (i = 0; i < sc->dacn; i++) + sc->cfg->codec->destroy(sc->dac[i]); + } + envy24_cfgfree(sc->cfg); + if (sc->cs) + bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); + if (sc->ddma) + bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); + if (sc->ds) + bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); + if (sc->mt) + bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); + if (sc->lock) + snd_mtxfree(sc->lock); + free(sc, M_ENVY24); + return err; +} + +static int +envy24_pci_detach(device_t dev) +{ + struct sc_info *sc; + int r; + int i; + +#if(0) + device_printf(dev, "envy24_pci_detach()\n"); +#endif + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return 0; + r = pcm_unregister(dev); + if (r) + return r; + + envy24_dmafree(sc); + if (sc->cfg->codec->destroy != NULL) { + for (i = 0; i < sc->adcn; i++) + sc->cfg->codec->destroy(sc->adc[i]); + for (i = 0; i < sc->dacn; i++) + sc->cfg->codec->destroy(sc->dac[i]); + } + envy24_cfgfree(sc->cfg); + bus_dma_tag_destroy(sc->dmat); + bus_teardown_intr(dev, sc->irq, sc->ih); + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); + bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); + bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); + bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); + snd_mtxfree(sc->lock); + free(sc, M_ENVY24); + return 0; +} + +static device_method_t envy24_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, envy24_pci_probe), + DEVMETHOD(device_attach, envy24_pci_attach), + DEVMETHOD(device_detach, envy24_pci_detach), + { 0, 0 } +}; + +static driver_t envy24_driver = { + "pcm", + envy24_methods, +#if __FreeBSD_version > 500000 + PCM_SOFTC_SIZE, +#else + sizeof(struct snddev_info), +#endif +}; + +DRIVER_MODULE(snd_envy24, pci, envy24_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_envy24, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_DEPEND(snd_envy24, snd_spicds, 1, 1, 1); +MODULE_VERSION(snd_envy24, 1); --- sys/dev/sound/pci/envy24.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/envy24.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/sound/pci/envy24.h,v 1.2 2007/05/27 19:58:39 joel Exp $ + */ + + +/* -------------------------------------------------------------------- */ + +/* PCI device ID */ +#define PCIV_ENVY24 0x1412 +#define PCID_ENVY24 0x1712 + +/* PCI Registers */ + +#define PCIR_CCS 0x10 /* Controller I/O Base Address */ +#define PCIR_DDMA 0x14 /* DDMA I/O Base Address */ +#define PCIR_DS 0x18 /* DMA Path Registers I/O Base Address */ +#define PCIR_MT 0x1c /* Professional Multi-Track I/O Base Address */ + +#define PCIR_LAC 0x40 /* Legacy Audio Control */ +#define PCIM_LAC_DISABLE 0x8000 /* Legacy Audio Hardware disabled */ +#define PCIM_LAC_SBDMA0 0x0000 /* SB DMA Channel Select: 0 */ +#define PCIM_LAC_SBDMA1 0x0040 /* SB DMA Channel Select: 1 */ +#define PCIM_LAC_SBDMA3 0x00c0 /* SB DMA Channel Select: 3 */ +#define PCIM_LAC_IOADDR10 0x0020 /* I/O Address Alias Control */ +#define PCIM_LAC_MPU401 0x0008 /* MPU-401 I/O enable */ +#define PCIM_LAC_GAME 0x0004 /* Game Port enable (200h) */ +#define PCIM_LAC_FM 0x0002 /* FM I/O enable (AdLib 388h base) */ +#define PCIM_LAC_SB 0x0001 /* SB I/O enable */ + +#define PCIR_LCC 0x42 /* Legacy Configuration Control */ +#define PCIM_LCC_VINT 0xff00 /* Interrupt vector to be snooped */ +#define PCIM_LCC_SVIDRW 0x0080 /* SVID read/write enable */ +#define PCIM_LCC_SNPSB 0x0040 /* snoop SB 22C/24Ch I/O write cycle */ +#define PCIM_LCC_SNPPIC 0x0020 /* snoop PIC I/O R/W cycle */ +#define PCIM_LCC_SNPPCI 0x0010 /* snoop PCI bus interrupt acknowledge cycle */ +#define PCIM_LCC_SBBASE 0x0008 /* SB base 240h(1)/220h(0) */ +#define PCIM_LCC_MPUBASE 0x0006 /* MPU-401 base 300h-330h */ +#define PCIM_LCC_LDMA 0x0001 /* Legacy DMA enable */ + +#define PCIR_SCFG 0x60 /* System Configuration Register */ +#define PCIM_SCFG_XIN2 0xc0 /* XIN2 Clock Source Configuration */ + /* 00: 22.5792MHz(44.1kHz*512) */ + /* 01: 16.9344MHz(44.1kHz*384) */ + /* 10: from external clock synthesizer chip */ +#define PCIM_SCFG_MPU 0x20 /* 1(0)/2(1) MPU-401 UART(s) */ +#define PCIM_SCFG_AC97 0x10 /* 0: AC'97 codec exist */ + /* 1: AC'97 codec not exist */ +#define PCIM_SCFG_ADC 0x0c /* 1-4 stereo ADC connected */ +#define PCIM_SCFG_DAC 0x03 /* 1-4 stereo DAC connected */ + +#define PCIR_ACL 0x61 /* AC-Link Configuration Register */ +#define PCIM_ACL_MTC 0x80 /* Multi-track converter type: 0:AC'97 1:I2S */ +#define PCIM_ACL_OMODE 0x02 /* AC 97 codec SDATA_OUT 0:split 1:packed */ +#define PCIM_ACL_IMODE 0x01 /* AC 97 codec SDATA_IN 0:split 1:packed */ + +#define PCIR_I2S 0x62 /* I2S Converters Features Register */ +#define PCIM_I2S_VOL 0x80 /* I2S codec Volume and mute */ +#define PCIM_I2S_96KHZ 0x40 /* I2S converter 96kHz sampling rate support */ +#define PCIM_I2S_RES 0x30 /* Converter resolution */ +#define PCIM_I2S_16BIT 0x00 /* 16bit */ +#define PCIM_I2S_18BIT 0x10 /* 18bit */ +#define PCIM_I2S_20BIT 0x20 /* 20bit */ +#define PCIM_I2S_24BIT 0x30 /* 24bit */ +#define PCIM_I2S_ID 0x0f /* Other I2S IDs */ + +#define PCIR_SPDIF 0x63 /* S/PDIF Configuration Register */ +#define PCIM_SPDIF_ID 0xfc /* S/PDIF chip ID */ +#define PCIM_SPDIF_IN 0x02 /* S/PDIF Stereo In is present */ +#define PCIM_SPDIF_OUT 0x01 /* S/PDIF Stereo Out is present */ + +#define PCIR_POWER_STAT 0x84 /* Power Management Control and Status */ + +/* Controller Registers */ + +#define ENVY24_CCS_CTL 0x00 /* Control/Status Register */ +#define ENVY24_CCS_CTL_RESET 0x80 /* Entire Chip soft reset */ +#define ENVY24_CCS_CTL_DMAINT 0x40 /* DS DMA Channel-C interrupt */ +#define ENVY24_CCS_CTL_DOSVOL 0x10 /* set the DOS WT volume control */ +#define ENVY24_CCS_CTL_EDGE 0x08 /* SERR# edge (only one PCI clock width) */ +#define ENVY24_CCS_CTL_SBINT 0x02 /* SERR# assertion for SB interrupt */ +#define ENVY24_CCS_CTL_NATIVE 0x01 /* Mode select: 0:SB mode 1:native mode */ + +#define ENVY24_CCS_IMASK 0x01 /* Interrupt Mask Register */ +#define ENVY24_CCS_IMASK_PMIDI 0x80 /* Primary MIDI */ +#define ENVY24_CCS_IMASK_TIMER 0x40 /* Timer */ +#define ENVY24_CCS_IMASK_SMIDI 0x20 /* Secondary MIDI */ +#define ENVY24_CCS_IMASK_PMT 0x10 /* Professional Multi-track */ +#define ENVY24_CCS_IMASK_FM 0x08 /* FM/MIDI trapping */ +#define ENVY24_CCS_IMASK_PDMA 0x04 /* Playback DS DMA */ +#define ENVY24_CCS_IMASK_RDMA 0x02 /* Consumer record DMA */ +#define ENVY24_CCS_IMASK_SB 0x01 /* Consumer/SB mode playback */ + +#define ENVY24_CCS_ISTAT 0x02 /* Interrupt Status Register */ +#define ENVY24_CCS_ISTAT_PMIDI 0x80 /* Primary MIDI */ +#define ENVY24_CCS_ISTAT_TIMER 0x40 /* Timer */ +#define ENVY24_CCS_ISTAT_SMIDI 0x20 /* Secondary MIDI */ +#define ENVY24_CCS_ISTAT_PMT 0x10 /* Professional Multi-track */ +#define ENVY24_CCS_ISTAT_FM 0x08 /* FM/MIDI trapping */ +#define ENVY24_CCS_ISTAT_PDMA 0x04 /* Playback DS DMA */ +#define ENVY24_CCS_ISTAT_RDMA 0x02 /* Consumer record DMA */ +#define ENVY24_CCS_ISTAT_SB 0x01 /* Consumer/SB mode playback */ + +#define ENVY24_CCS_INDEX 0x03 /* Envy24 Index Register */ +#define ENVY24_CCS_DATA 0x04 /* Envy24 Data Register */ + +#define ENVY24_CCS_NMI1 0x05 /* NMI Status Register 1 */ +#define ENVY24_CCS_NMI1_PCI 0x80 /* PCI I/O read/write cycle */ +#define ENVY24_CCS_NMI1_SB 0x40 /* SB 22C/24C write */ +#define ENVY24_CCS_NMI1_SBDMA 0x10